
        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/09-modal.css · 2026-09-21  (prepended)
           1 rules. DESIGN.md: a surface's rules go in that surface's part, and the
           legacy parts only shrink.
           ⚠️ 09-modal.css loaded BEFORE this file. Placing them
           at the head keeps their order against THIS PART’S OWN rules — and nothing else.
           Against the parts BETWEEN #32 and #8 the order is reversed by this move, and
           whether that matters was a separate question somebody had to answer. See DESIGN.md.
           ══════════════════════════════════════════════════════════════ */
        /* write modal expand + link field */
        #writeJournalModal .rich-text-editor.expanded { min-height:340px; }

        /* ==========================================================================
           #AB  The journal's editor — the writing surface
           ==========================================================================

           Round three of the journal, and the last of them: the reading page went on
           2026-09-10, the list and the calendar on 2026-09-11, and this is where an entry
           is actually written.

           WHAT THIS ROOM IS FOR is the writing, and everything else on the screen is an
           instrument around it. That is why the drawer exists at all — the date, the place
           and the link are set once and rarely looked at again, while the page is a page a
           traveller sits in front of. The outgoing version had already worked that out; what
           it did not have was a material for it.

           ⚠️ AND THIS ROOM HAD NO TOKENS EITHER. #writeJournalModal is a `.modal` in
           index.html, a sibling of the pages like every overlay in this app, so every var()
           written here resolved to an empty string. Checked before writing rather than
           after: getComputedStyle returned "" for --log-ink. Third surface in three days —
           the section sheet, the day page's stack, and now this — and the list in
           css/02-auth.css is the first place to look when a migrated room comes out cream. */

        /* ---- the sheet --------------------------------------------------------- */

        #writeJournalModal .modal-content {
            background: var(--log-stock);
        }

        /* ---- the head ---------------------------------------------------------- */

        /* One ruling tall, like every other head in this world — see --log-row. */
        #writeJournalModal .jw-top {
            min-height: var(--log-row);
            padding: var(--sp-2xs) var(--sp-lg);
            background: var(--log-stock-band);
            border-bottom: 1px solid var(--log-rule-firm);
        }

        #writeJournalModal .jw-close,
        #writeJournalModal .jw-action {
            min-height: 44px;
            padding: 0;
            border: none;
            background: none;
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            color: var(--log-ink);
        }

        /* Which of the two things this screen is. Absent for a new entry — a form that is
           not in any particular state should not print one — and the softer pressure,
           because it is the app's note about the form rather than an act in it. */
        #writeJournalModal .jw-state {
            flex: 1;
            min-width: 0;
            padding-left: var(--sp-lg);
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            font-variant-numeric: lining-nums tabular-nums;
            color: var(--log-ink-soft);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        #writeJournalModal .jw-top-actions {
            display: flex;
            align-items: center;
            gap: var(--sp-lg);
        }

        /* SAVE is the act this room exists for, so it takes the magenta and DRAFT does not.
           Both write to the record; only one of them is what the reader came here to do,
           and two acts of equal weight side by side is a choice offered where there is a
           default. */
        #writeJournalModal .jw-action.is-primary { color: var(--log-magenta); }

        #writeJournalModal .jw-action.is-primary:active {
            box-shadow: inset 0 -2px 0 0 var(--log-magenta);
        }

        /* ---- above the writing -------------------------------------------------- */

        /* What this entry is about, when it is about something: a caption, not a control.
           The thing that changes it is in the drawer with the rest of the metadata. */
        /* ⚠️ NO HORIZONTAL PADDING ON ANYTHING INSIDE .jw-surface. That element already
           carries the room's margin column (24px, in 19-legacy-media.css), so a child that
           adds --sp-lg of its own puts the writing at 48 — a second margin column, exactly
           the fault the day page had when it borrowed the Itinerary tab's wrapper. Measured:
           the editor's text began at 48 while the toolbar's words began at 24. */
        #writeJournalModal .jw-linked {
            display: flex;
            align-items: baseline;
            gap: var(--sp-sm);
            padding: var(--sp-xs) 0;
            border-bottom: 1px solid var(--log-rule);
            background: var(--log-stock);
        }

        #writeJournalModal .jw-linked-label {
            flex: none;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            color: var(--log-ink-soft);
        }

        #writeJournalModal .jw-linked-name {
            min-width: 0;
            font-family: var(--log-write);
            font-size: var(--fs-body);
            color: var(--log-ink);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        #writeJournalModal .jw-tags {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: var(--sp-xs) var(--sp-md);
            padding: var(--sp-sm) 0 0;
        }

        #writeJournalModal .diary-tag-chips {
            display: contents;
        }

        /* A tag being written reads as the tag it will be — the same printed word with the
           same hash the reading page and the list show, with the way to take it off beside
           it. The chip's box, its fill and its rounded corner all went: a tag is a word, and
           this world does not put words in capsules. */
        #writeJournalModal .diary-tag-chip {
            display: inline-flex;
            align-items: baseline;
            gap: var(--sp-2xs);
            padding: 0;
            border: none;
            border-radius: 0;
            background: none;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.12em;
            text-transform: uppercase;
            color: var(--log-ink);
        }

        /* The hash belongs to the word, not beside it — a flex container counts a
           pseudo-element as an item, so the row's gap was opening a space after it. */
        #writeJournalModal .diary-tag-chip::before { content: '#'; margin-right: -0.2em; }

        /* Taking a tag off is a small act on a small thing, so it is the one place in this
           room that keeps a mark rather than a word: a word per chip would be longer than
           the chips. It takes the magenta because it changes the record, and it carries a
           44px hit area without taking 44px of the line. */
        #writeJournalModal .diary-tag-chip button {
            position: relative;
            padding: 0;
            border: none;
            background: none;
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-small);
            line-height: 1;
            color: var(--log-magenta);
        }

        /* Centred it read 41 × 45: the next control is 16px to the right and the box reached 19 past
           the mark, so the neighbour, later in the document, kept the last 3. Moved 4 toward the
           tag's own word, which answers to nothing (tap audit, 2026-09-17). */
        #writeJournalModal .diary-tag-chip button::after {
            content: '';
            position: absolute;
            inset: 50% auto auto 50%;
            transform: translate(calc(-50% - 4px), -50%);
            width: 44px;
            height: 44px;
        }

        /* Adding one writes to the entry. */
        #writeJournalModal .jw-tag-add {
            min-height: 44px;
            padding: 0;
            border: none;
            border-radius: 0;
            background: none;
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            color: var(--log-magenta);
        }

        /* The writing line's grammar, the one the confirm dialog and Settings use: no box,
           transparent on the stock, an ink baseline when it has the cursor. */
        #writeJournalModal #diaryTagInput,
        #writeJournalModal .jw-input {
            display: block;
            width: 100%;
            box-sizing: border-box;
            min-height: 44px;
            padding: 0;
            border: none;
            border-bottom: 1px solid var(--log-rule);
            border-radius: 0;
            background: transparent;
            font-family: var(--log-write);
            font-size: var(--fs-body);
            color: var(--log-ink);
            caret-color: var(--log-ink);
            -webkit-appearance: none;
            appearance: none;
        }

        #writeJournalModal #diaryTagInput::placeholder,
        #writeJournalModal .jw-input::placeholder { color: var(--log-ink-soft); }

        #writeJournalModal #diaryTagInput:focus,
        #writeJournalModal .jw-input:focus {
            outline: 1px solid var(--log-rule);
            box-shadow: inset 0 -2px 0 0 var(--log-ink);
        }

        #writeJournalModal .jw-tag-add-btn {
            min-height: 44px;
            padding: 0 var(--sp-sm);
            border: none;
            border-radius: 0;
            background: none;
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            color: var(--log-magenta);
        }

        /* ---- the writing ------------------------------------------------------- */

        /* The written voice, at the size the reading page reads at — an entry should not
           change size between being written and being read. The first line is the title,
           one step up, which is the rule the reading page keeps too. */
        #writeJournalModal .jw-editor {
            padding: var(--sp-md) 0 var(--sp-2xl);
            font-family: var(--log-write);
            font-size: var(--fs-body);
            line-height: 1.7;
            color: var(--log-ink);
            background: var(--log-stock);
            caret-color: var(--log-ink);
        }

        #writeJournalModal .jw-editor > *:first-child {
            font-size: var(--fs-base);
            font-weight: 500;
            line-height: 1.3;
            margin-bottom: var(--sp-md);
        }

        #writeJournalModal .jw-editor:empty::before,
        #writeJournalModal .jw-editor[data-placeholder]:empty::before {
            content: attr(data-placeholder);
            color: var(--log-ink-soft);
        }

        /* ---- the toolbar ------------------------------------------------------- */

        /* ⚠️ THE ONE PLACE IN THIS WORLD THAT KEEPS MARKS (owner, 2026-09-11: 这些还是用
           图标吧). I had made all eight into words on the rule that this world has no glyphs
           for actions, and the owner put the marks back.

           The rule holds, and this is a carve-out with a reason. Everywhere else an icon
           stands FOR a thing — a pin for a place, a chevron for a way out — and a word says
           it better and says it once. A FORMATTING RACK is the one place where the mark IS
           the thing: the three ruled lines with dots are not a picture of a list, they are a
           list; the two commas are not a picture of a quotation. B, I and U were the clue
           all along, and were never going to become words. Eight words read as eight labels
           to be read; eight marks read as a rack to be reached into, which is what this row
           is to somebody mid-sentence.

           Drawn in the app's own hand rather than borrowed, the same one the weather marks
           use: 1.5px, butt caps, mitred joins (no rounded corners — see .jw-tool svg below),
           one ink, and no fill except the quote, which is a solid mark by nature. */
        #writeJournalModal .jw-toolbar {
            display: flex;
            align-items: center;
            gap: var(--sp-sm);
            padding: 0 var(--sp-lg);
            background: var(--log-stock-band);
            border-top: 1px solid var(--log-rule-firm);
            overflow-x: auto;
            scrollbar-width: none;
        }

        #writeJournalModal .jw-toolbar::-webkit-scrollbar { display: none; }

        /* ⚠️ THE WIDTH IS THE TARGET, AND 24 IS MEASURED — DO NOT "TIDY" IT UP TO 44.
           These eight buttons had `min-height: 44px` and nothing on the width, so `I` rendered
           4.3px wide, `B` and `U` 8.6, and the five marks 16. The 44×44 transparent hit area
           in css/05-interaction.css looked like it covered that and did not: it carried
           `pointer-events: none`, which makes a pseudo-element hit area do nothing at all, so
           the hit box was the visible box to the pixel — walked out with elementFromPoint from
           each centre: 4×44, 9×44, 17×44. Below WCAG 2.2 AA's 24×24 floor, and `I` was four
           pixels of a moving thumb.

           WHY 24 AND NOT THIS WORLD'S OWN 44. Measured in the room, not reasoned: at 24 the
           toolbar's content is 350px and fits inside both 430 and 375 with nothing scrolling.
           At 44 it is 510px — 80px past a 430 screen, behind a scrollbar this toolbar hides
           (`scrollbar-width: none` above), so two tools would sit off-screen with nothing
           saying so. Eight tools at 44 do not fit a 430 column; that is a decision about how
           many instruments this toolbar holds, not a number to raise here. Recorded in
           DEBTS.md rather than fixed quietly. */
        #writeJournalModal .jw-tool {
            flex: none;
            min-height: 44px;
            min-width: 24px;
            padding: 0;
            border: none;
            border-radius: 0;
            background: none;
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--log-ink);
        }

        /* B, I and U say what they do by being what they do. */
        #writeJournalModal .jw-tool strong,
        #writeJournalModal .jw-tool em,
        #writeJournalModal .jw-tool span {
            font-family: var(--log-print);
            font-size: var(--fs-small);
        }

        /* The marks, in this world's hand. Butt caps and mitred joins are the whole
           difference between a drawing that belongs to a ruled instrument and one that
           belongs to a phone: a rounded cap is a soft edge, and there are none here. */
        #writeJournalModal .jw-tool svg {
            display: block;
            width: 16px;
            height: 16px;
            stroke-linecap: butt;
            stroke-linejoin: miter;
        }

        #writeJournalModal .jw-tool:active { box-shadow: inset 0 -2px 0 0 var(--log-ink); }

        #writeJournalModal .jw-tool-sep {
            flex: none;
            width: 1px;
            height: 16px;
            background: var(--log-rule);
        }

        /* ---- the fold ---------------------------------------------------------- */

        #writeJournalModal .jw-status {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: var(--sp-md);
            min-height: 44px;
            padding: 0 var(--sp-lg);
            background: var(--log-stock-band);
            border-top: 1px solid var(--log-rule);
            cursor: pointer;
        }

        #writeJournalModal .jw-status-line {
            min-width: 0;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            font-variant-numeric: lining-nums tabular-nums;
            color: var(--log-ink-soft);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        /* The word says which way the fold goes, like the reading page's. Drawn here rather
           than written into the markup because the state is a class on the drawer, and a
           handler that has to remember to change a word is a handler that will forget. */
        /* ⚠️ `display: inline-flex`, and it is not a nicety. The word is drawn by a
           pseudo-element, and an empty <button> with only an ::after is ZERO PIXELS WIDE —
           measured at width 0 with its right edge at the sheet's own right edge, so the word
           hung off the end of the screen and DETAILS was clipped in half. A flex container
           takes the pseudo-element as an item and gets its width from it. */
        #writeJournalModal .jw-status-toggle {
            flex: none;
            display: inline-flex;
            align-items: center;
            white-space: nowrap;
            min-height: 44px;
            padding: 0;
            border: none;
            background: none;
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            color: var(--log-ink);
        }

        /* ⚠️ `position: static` UNDOES SOMETHING THIS ROOM DID NOT ASK FOR. The shared
           hit-area rule in css/05-interaction.css gives `.jw-status-toggle::after` an empty,
           absolutely-positioned 44px square — a tap target for a 15px chevron. This rule
           beats it on `content`, but `position: absolute` stayed, so the word was taken out
           of flow and the button measured ZERO PIXELS WIDE with its right edge at the
           sheet's: DETAILS hung off the end of the screen, half of it gone. The toggle is a
           word with its own 44px now and needs no pretend target, so it comes off that list
           too — but this line is the guard, because the list is shared by a dozen controls
           and the next edit to it will not be thinking about this one. */
        #writeJournalModal .jw-status-toggle::after {
            content: 'Details';
            position: static;
            transform: none;
            width: auto;
            height: auto;
        }

        /* Which way the fold goes. The drawer's open class is on the drawer, so the word is
           chosen from the sheet that contains both. */
        #writeJournalModal .modal-content:has(.jw-drawer.is-open) .jw-status-toggle::after {
            content: 'Hide';
        }

        /* ---- the drawer -------------------------------------------------------- */

        #writeJournalModal .jw-drawer {
            background: var(--log-stock);
            border-top: 1px solid var(--log-rule-firm);
        }

        /* The grip was a rounded pill saying "drag me", and nothing drags: the fold opens
           from the word beside the status line. */
        #writeJournalModal .jw-drawer-grip { display: none; }

        #writeJournalModal .jw-drawer-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: var(--sp-md);
            min-height: 44px;
            padding: 0 var(--sp-lg);
            background: var(--log-stock-band);
            border-bottom: 1px solid var(--log-rule);
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.18em;
            text-transform: uppercase;
            color: var(--log-ink);
        }

        #writeJournalModal .jw-drawer-close {
            flex: none;
            min-height: 44px;
            padding: 0;
            border: none;
            background: none;
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            color: var(--log-ink);
        }

        #writeJournalModal .jw-drawer-body { padding: 0 var(--sp-lg) var(--sp-md); }

        #writeJournalModal .jw-field-row {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: var(--sp-lg);
        }

        #writeJournalModal .jw-field { padding-top: var(--sp-sm); min-width: 0; }

        #writeJournalModal .jw-label {
            display: block;
            margin-bottom: 2px;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            color: var(--log-ink-soft);
        }

        /* What is linked, and how to change it. The chip was a dashed box — this world's
           unset state is a ruled line with nothing written on it, the same as every other
           field here. */
        /* ⚠️ `align-items: center` on BOTH, and the row's own is an override: the rule this
           replaces stretched its two children, and with the chip aligning its text to a
           baseline and CHOOSE centring in a 44px box, the two words sat on different lines
           of the same row. */
        #writeJournalModal .jw-link-row {
            display: flex;
            align-items: center;
            gap: var(--sp-md);
            border-bottom: 1px solid var(--log-rule);
        }

        #writeJournalModal .jw-link-chip {
            flex: 1;
            min-width: 0;
            display: flex;
            align-items: center;
            gap: var(--sp-xs);
            min-height: 44px;
            padding: 0;
            border: none;
            border-radius: 0;
            background: none;
            cursor: pointer;
        }

        #writeJournalModal .jw-link-name {
            min-width: 0;
            font-family: var(--log-write);
            font-size: var(--fs-body);
            color: var(--log-ink);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        #writeJournalModal .jw-link-meta {
            flex: none;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            letter-spacing: 0.12em;
            text-transform: uppercase;
            color: var(--log-ink-soft);
        }

        #writeJournalModal .jw-link-btn {
            flex: none;
            min-height: 44px;
            padding: 0;
            border: none;
            border-radius: 0;
            background: none;
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            color: var(--log-ink);
        }

        /* What the entry knows about itself, on the rule at the foot of the fold. */
        #writeJournalModal .jw-drawer-foot {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: var(--sp-md);
            margin-top: var(--sp-md);
            padding-top: var(--sp-xs);
            border-top: 1px solid var(--log-rule);
            background: none;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 400;
            letter-spacing: 0.12em;
            text-transform: uppercase;
            font-variant-numeric: lining-nums tabular-nums;
            color: var(--log-ink-soft);
        }

        /* ---- the sheet CHOOSE opens ------------------------------------------------ */

        /* Reported by the owner (2026-09-11: details 里面 choose an activity 还是旧世界), and
           it is the same shape as the photographs page a day earlier: a migrated room
           handing one press onward into a room that had not moved. A picker is not a
           separate world from the form that opens it. */

        #journalLinkModal .modal-content,
        #photoActivityModal .modal-content {
            background: var(--log-stock);
            /* Same chassis, same three-layer inset — see the drafts sheet below. */
            padding: 0;
        }

        #journalLinkModal .modal-body,
        #photoActivityModal .modal-body { margin: 0; padding: 0; width: auto; }

        #journalLinkModal .jlink-sheet-hd,
        #photoActivityModal .jlink-sheet-hd {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: var(--sp-md);
            min-height: var(--log-row);
            padding: var(--sp-2xs) var(--sp-lg);
            background: var(--log-stock-band);
            border-bottom: 1px solid var(--log-rule-firm);
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.18em;
            text-transform: uppercase;
            color: var(--log-ink);
        }

        #journalLinkModal .jlink-sheet-hd button,
        #photoActivityModal .jlink-sheet-hd button {
            flex: none;
            width: auto;
            height: auto;
            min-height: 44px;
            padding: 0;
            border: none;
            border-radius: 0;
            background: none;
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            color: var(--log-ink);
        }

        /* The writing line's grammar again. Scoped to the two sheets that draw it: `.pa-search`
           is shared with them by the outgoing rules in 20-legacy-calendar.css. */
        #journalLinkModal .pa-search,
        #photoActivityModal .pa-search {
            display: block;
            padding: 0 var(--sp-lg);
            border-bottom: 1px solid var(--log-rule);
            background: var(--log-stock);
        }

        #journalLinkModal .pa-search input,
        #photoActivityModal .pa-search input {
            display: block;
            width: 100%;
            box-sizing: border-box;
            min-height: 44px;
            padding: 0;
            border: none;
            border-radius: 0;
            background: transparent;
            font-family: var(--log-write);
            font-size: var(--fs-body);
            color: var(--log-ink);
            caret-color: var(--log-ink);
            -webkit-appearance: none;
            appearance: none;
        }

        #journalLinkModal .pa-search input::placeholder,
        #photoActivityModal .pa-search input::placeholder { color: var(--log-ink-soft); }

        #journalLinkModal .pa-search input:focus,
        #photoActivityModal .pa-search input:focus {
            outline: 1px solid var(--log-rule);
            box-shadow: inset 0 -2px 0 0 var(--log-ink);
        }

        /* Taking the link off is a row like the others, not a footnote under them. */
        #journalLinkModal .jlink-clear,
        #photoActivityModal .jlink-clear {
            min-height: 44px;
            display: flex;
            align-items: center;
            padding: 0 var(--sp-lg);
            border-bottom: 1px solid var(--log-rule);
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            color: var(--log-ink-soft);
        }

        #journalLinkModal .jlink-item,
        #photoActivityModal .jlink-item {
            display: flex;
            align-items: center;
            gap: var(--sp-md);
            min-height: 44px;
            padding: var(--sp-xs) var(--sp-lg);
            border-bottom: 1px solid var(--log-rule);
            cursor: pointer;
        }

        #journalLinkModal .jlink-item:active,
        #photoActivityModal .jlink-item:active { background: var(--log-stock-band); }

        /* The category's own colour, the same mark Saved uses and from the same table. */
        #journalLinkModal .jlink-mark,
        #photoActivityModal .jlink-mark {
            flex: none;
            width: 9px;
            height: 9px;
            background: var(--c, var(--log-rule));
        }

        #journalLinkModal .jlink-tx,
        #photoActivityModal .jlink-tx { min-width: 0; }

        #journalLinkModal .jlink-tx .nm,
        #photoActivityModal .jlink-tx .nm {
            font-family: var(--log-write);
            font-size: var(--fs-body);
            font-weight: 400;
            line-height: 1.3;
            color: var(--log-ink);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        #journalLinkModal .jlink-tx .mt,
        #photoActivityModal .jlink-tx .mt {
            margin-top: 1px;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            letter-spacing: 0.12em;
            text-transform: uppercase;
            font-variant-numeric: lining-nums tabular-nums;
            color: var(--log-ink-soft);
        }

        #journalLinkModal .pa-empty,
        #photoActivityModal .pa-empty {
            padding: var(--sp-md) var(--sp-lg);
            font-family: var(--log-write);
            font-size: var(--fs-body);
            color: var(--log-ink-soft);
        }

        /* ---- the same sheet, behind a photograph's activity mark ------------------- */

        /* ⚠️ THE PHOTOGRAPH'S PICKER IS THIS SHEET (owner, 2026-09-14: link to activiy 还是旧世界 这个在journal里
           做过). Every rule above names both. What follows is only what differs: the outgoing rules in
           20-legacy-calendar.css load after this file and give the photograph's sheet a fixed 62dvh height, so the
           height is taken back with one more class; and the activity already linked is marked. */
        .modal#photoActivityModal .modal-content {
            height: auto;
            max-height: 70vh;
            margin-top: auto;
        }

        #photoActivityModal .jlink-tx { flex: 1 1 auto; }

        #photoActivityModal .jlink-item.is-current,
        #photoActivityModal .jlink-clear.is-current { background: var(--log-stock-band); }

        #photoActivityModal .jlink-now {
            flex: none;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            color: var(--log-ink-soft);
        }

        /* ---- where the writing is searched for a place -------------------------- */

        /* ⚠️ THIS PICKER WAS ALWAYS HERE AND COULD NOT BE USED (owner, 2026-09-11:
           location 是自己输入字段 不能选择). It is the same place autocomplete the journey's
           stops use, wired to this field since the form was built. Measured: the list opened
           166px wide, because it was absolutely positioned inside one half of a two-column
           row, and its bottom fell past the drawer's own scroller, which clips — laid out,
           opaque, invisible. The field behaved exactly like plain text entry, which is what
           it was reported as.

           In normal flow now, and out of the row (see 35-markup-journal.js): it spans the
           drawer, pushes the fields under it down, and the drawer scrolls to it. Scoped to
           this form because `.place-suggestions` is shared with the stops picker, which is
           in another room's round. */
        /* The geometry this room needs, and only that: in normal flow, not floating. Its
           INK moved to the shared block in css/17t-stop.css on 2026-09-11 — which is the
           round the note above predicted ("shared with the stops picker, which is in another
           room's round"). */
        #writeJournalModal .place-suggestions {
            position: static;
            display: none;
            width: auto;
            max-height: 220px;
            margin-top: var(--sp-xs);
            border: none;
            border-top: 1px solid var(--log-rule-firm);
        }

        /* Rows in this room are separated below rather than above, because the list has no
           box around it — its top rule is the container's. */
        #writeJournalModal .place-suggestion {
            padding: var(--sp-2xs) 0;
            border-top: none;
            border-bottom: 1px solid var(--log-rule);
            font-size: var(--fs-body);
        }

        /* ---- the drafts sheet -------------------------------------------------- */

        /* Behind OPEN on the journal list's unfinished row (owner, 2026-09-11: 1 draft 点
           open 还是旧世界) — the third room in two days reached in one press from a migrated
           one and left in the outgoing world. */

        /* ⚠️ THREE LAYERS EACH GIVING THE SAME INSET (owner, 2026-09-11: 文字和 unfinished
           左对齐 / delete 和 close 右对齐).

           Measured, because the arithmetic is the whole story: `.modal-content` gives 24,
           `.modal-body` gives a further 17.8 of MARGIN, and each row gives 24 of padding —
           so the head, which is not inside the body, landed at 48 while the rows landed at
           66, and DELETE stopped 18px short of CLOSE. Three owners of one margin column, and
           the two that are not this room's are invisible from here.

           The room owns its column once: the sheet and the body give none, and the head and
           the rows each give the 24 this world uses everywhere. Same shape as the journal's
           own sheet, which never had the problem because it does not use `.modal-body`. */
        #diaryDraftsModal .modal-content {
            background: var(--log-stock);
            padding: 0;
            /* ⚠️ AND THE BODY HAS TO STRETCH. `.modal-content` is a flex column that does not
               stretch its children, so the list took the width of its own longest line —
               measured at 363.6 inside a 404 sheet. The rows were therefore forty pixels
               narrower than the head above them and DELETE stopped short of CLOSE even after
               every inset agreed. Three causes, one symptom, and each one hid the next:
               the sheet's padding, the body's margin and padding, and this. */
            align-items: stretch;
        }

        /* ⚠️ BOTH, and forgetting one is how this took two passes. `.modal-body` carries a
           MARGIN of 17.8 and a PADDING of 20 from two different legacy rules; zeroing the
           margin alone left the rows forty pixels narrower than the head above them, so the
           left edges lined up and DELETE still stopped short of CLOSE. */
        #diaryDraftsModal .modal-body { margin: 0; padding: 0; width: auto; }

        #diaryDraftsModal .jdraft-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: var(--sp-md);
            min-height: var(--log-row);
            padding: var(--sp-2xs) var(--sp-lg);
            background: var(--log-stock-band);
            border-bottom: 1px solid var(--log-rule-firm);
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.18em;
            text-transform: uppercase;
            color: var(--log-ink);
        }

        #diaryDraftsModal .jdraft-head button {
            flex: none;
            min-height: 44px;
            padding: 0;
            border: none;
            background: none;
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            color: var(--log-ink);
        }



        #diaryDraftsModal .jrnl-draft-item {
            display: flex;
            align-items: center;
            gap: var(--sp-md);
            min-height: 44px;
            padding: var(--sp-xs) var(--sp-lg);
            border-bottom: 1px solid var(--log-rule);
        }

        #diaryDraftsModal .jrnl-draft-main { flex: 1; min-width: 0; cursor: pointer; }

        #diaryDraftsModal .jrnl-draft-meta {
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            font-variant-numeric: lining-nums tabular-nums;
            color: var(--log-ink-soft);
            margin-bottom: 2px;
        }

        #diaryDraftsModal .jrnl-draft-preview {
            font-family: var(--log-write);
            font-size: var(--fs-body);
            line-height: 1.4;
            color: var(--log-ink);
        }

        /* Throwing away an unfinished entry is the one act on this row that changes the
           record, so it takes the magenta and says what it does. It used to be a quiet bin,
           on the argument that a delete button should not draw the eye on a list of things
           you meant to finish — a word that reads DELETE cannot be mistaken for the row
           itself, which is what that argument was really protecting against. */
        #diaryDraftsModal .jrnl-draft-del {
            flex: none;
            width: auto;
            height: auto;
            min-height: 44px;
            padding: 0;
            border: none;
            background: none;
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            color: var(--log-magenta);
        }

        #diaryDraftsModal .jrnl-draft-empty {
            padding: var(--sp-md) var(--sp-lg);
            font-family: var(--log-write);
            font-size: var(--fs-body);
            color: var(--log-ink-soft);
        }

        /* ---- the tray of photographs not yet placed ----------------------------- */

        /* ⚠️ NEVER MIGRATED, AND ITS REMOVE CONTROL WAS INVISIBLE (2026-09-11 finish review,
           and no capture shows it because no capture stages a photograph).

           `.diary-photo-remove` was `color: white` on `background: var(--bg-primary)` —
           #E8E3D9, the outgoing cream. White on cream is about 1.2:1: a mark that cannot be
           seen at all. It was 24x24 against the 44 this world holds everywhere, and it
           hovered to `rgba(232,93,34,0.9)`, the retired parchment orange. So the only way to
           take a photograph back out of an entry was a control you could not see, in a
           colour this world does not have.

           The mark it carries is the one exception this room already makes — the same one
           the tag chips make, and for the same reason: a word per tile would be wider than
           the tile. It takes the magenta, because taking a photograph out changes the entry,
           and it carries its 44px as a pseudo-element rather than as a box. */
        /* ⚠️ THE LINES BELONG TO THE TILES, NOT TO THE GROUND — AND THAT IS THE SECOND
           VERSION OF THIS RULE. Until 2026-09-16 the grid drew this world's lines by painting
           --log-rule as its own background and letting it show through 1px of gap and 1px of
           padding. That is a good trick and it has one condition nobody stated: it only works
           when every cell is covered. Wherever a tile was missing, the ground simply showed.

           The owner found it at zero photographs — "这里的分割线是怎么回事" — where the whole
           grid collapsed to a 382×2 bar of rule colour across the measure, a divider dividing
           nothing. Fixing only that would have left its larger sibling: at FOUR photographs
           the second row holds one tile and two bare cells, and those two rendered as a solid
           254×126 block of rule colour. Photographed for the first time the same day
           (editor-photos.png — no shot in this harness had ever staged diaryPhotos), which is
           the only reason it was seen at all. Five, seven and eight do the same.

           So the ground is gone and each tile carries the line itself. `outline` rather than
           `border`, because an outline is drawn outside the box and takes no layout space: two
           neighbours each draw 1px into the same 1px gap and land on one crisp hairline, and
           at the outer edge the outline fills the grid's 1px padding. An empty cell now draws
           nothing, at any count. Checked safe first: nothing in public/css gives
           .diary-photo-item an outline or a focus ring that this would fight.

           --log-rule is exempt from the 3:1 non-text floor on the stated grounds that it
           "never carries meaning on its own" (css/02-auth.css:171). At zero photographs it was
           the only thing on the screen and carried nothing, which is the one way that
           exemption can be wrong. */
        #writeJournalModal .diary-photo-grid {
            display: grid;
            grid-template-columns: repeat(3, minmax(0, 1fr));
            gap: 1px;
            margin-top: var(--sp-md);
            padding: 1px;
        }

        /* Even with the ground gone, an empty grid still reserves its 16px of margin and 2px
           of padding for nothing. `:empty` is exact here rather than approximate: it counts a
           whitespace text node as content, and all three write paths leave these genuinely
           empty — `grid.innerHTML = unplaced.map(...).join('')` is '' at zero items
           (07-diary.js), `hint.textContent = ''` (07-diary.js:167), and the reset is
           `grid.innerHTML = ''` (06-expenses.js:1809). */
        #writeJournalModal .diary-photo-grid:empty { display: none; }

        #writeJournalModal .diary-photo-hint:empty {
            min-height: 0;   /* 19-legacy-media.css:337 sets 1em and this world never took it back */
            padding-top: 0;
        }

        #writeJournalModal .diary-photo-item {
            position: relative;
            aspect-ratio: 1;
            min-width: 0;
            border-radius: 0;
            overflow: hidden;
            background: var(--log-stock-band);
            /* The tile carries the gridline. See the note on .diary-photo-grid above for why
               it is here and not on the grid's ground, and why it is an outline. */
            outline: 1px solid var(--log-rule);
        }

        #writeJournalModal .diary-photo-item img,
        #writeJournalModal .diary-photo-item video {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
        }

        /* What a video is, in a word. It was a triangle drawn in white with a shadow. */
        #writeJournalModal .diary-photo-kind {
            position: absolute;
            left: 0;
            bottom: 0;
            padding: 2px var(--sp-2xs);
            background: var(--log-oilskin);
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--log-on-oilskin);
        }

        #writeJournalModal .diary-photo-remove {
            /* ── carried from css/09-modal.css, 2026-09-22. The × is centred by the
               outgoing rule and by nothing here, so these three were the only part of it still
               drawing. ── */
            display: flex;
            align-items: center;
            justify-content: center;
            position: absolute;
            top: 0;
            right: 0;
            width: auto;
            height: auto;
            min-width: 0;
            padding: 0 var(--sp-2xs);
            border: none;
            border-radius: 0;
            background: var(--log-stock);
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-small);
            line-height: 1.4;
            color: var(--log-magenta);
            transition: none;
        }

        /* ⚠️ ANCHORED TO THE CORNER, NOT CENTRED ON THE MARK — AND THAT IS THE WHOLE FIX.
           This declared 44×44 and delivered 29×31. The pseudo was centred on the mark with
           `inset: 50% auto auto 50%` and a -50% translate, the mark sits at top: 0; right: 0
           of a .diary-photo-item that carries `overflow: hidden`, and a 44 box centred on a
           13.8×16.8 mark in the tile's corner has 22px of itself outside two of the tile's
           edges. The tile cut it. Measured 2026-09-16, the first time this control was ever
           photographed: 29 × 31, which is 22px left + 7.1px to the tile's right edge, and
           8.4px up + 22px down. Shrinking the pseudo to 20px gave 17×19, which is how the
           clipping was told apart from the declaration.

           `inset: 0 0 auto auto` puts the box's own top-right corner on the mark's, so all 44
           of it extends left and down — inward, into the tile — and none of it meets an edge
           that clips. The mark does not move by a pixel; only the invisible box does. The two
           alternatives were worse: `overflow: visible` on the tile would let a photograph
           bleed past its square, and moving the mark 14px in from the corner to make the
           centred box fit would relocate a visible control to serve an invisible one.

           The tag chip's version of this stays centred (see .diary-tag-chip button::after)
           because nothing clips it — measured 44×45 there. Same pattern, different
           surroundings, and the surroundings are the reason one of them had to change. */
        #writeJournalModal .diary-photo-remove::after {
            content: '';
            position: absolute;
            inset: 0 0 auto auto;
            width: 44px;
            height: 44px;
        }

        #writeJournalModal .diary-photo-remove:hover,
        #writeJournalModal .diary-photo-remove:active {
            background: var(--log-stock-band);
            transform: none;
        }

        #writeJournalModal .diary-photo-hint {
            padding-top: var(--sp-xs);
            font-family: var(--log-write);
            font-size: var(--fs-small);
            line-height: 1.5;
            color: var(--log-ink-soft);
        }

        /* ---- the sheet the rack's last mark opens ------------------------------- */

        /* The fourth room reached in one press from a migrated one and left behind. Its head
           was a serif "Choose Photos" under a "This trip" subtitle naming the same thing as
           the title, and its act was a greyed "Add 0". */

        #mediaPickerModal .modal-content {
            background: var(--log-stock);
            padding: 0;
            align-items: stretch;
        }

        #mediaPickerModal .modal-body { margin: 0; padding: 0; width: auto; }

        #mediaPickerModal .mp-head {
            display: flex;
            align-items: center;
            gap: var(--sp-lg);
            min-height: var(--log-row);
            padding: var(--sp-2xs) var(--sp-lg);
            background: var(--log-stock-band);
            border-bottom: 1px solid var(--log-rule-firm);
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.18em;
            text-transform: uppercase;
            color: var(--log-ink);
        }

        #mediaPickerModal .mp-head > span { flex: 1; min-width: 0; }

        #mediaPickerModal .mp-add,
        #mediaPickerModal .mp-close {
            flex: none;
            /* ⚠️ `gap`, because the space between ADD and its count is not a space. A shared
               rule in css/05-interaction.css makes `.mp-add` a flex container, and a flex
               container discards the whitespace text node between its children — so the
               markup's "Add <span>0</span>" rendered as ADD0. */
            display: inline-flex;
            align-items: center;
            gap: 0.35em;
            width: auto;
            height: auto;
            min-height: 44px;
            padding: 0;
            border: none;
            border-radius: 0;
            background: none;
            cursor: pointer;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
        }

        /* Putting photographs into an entry writes to the record. */
        #mediaPickerModal .mp-add { color: var(--log-magenta); }
        #mediaPickerModal .mp-close { color: var(--log-ink); }

        /* Quieted, not hidden: it says the act is here and waiting for a choice. */
        #mediaPickerModal .mp-add:disabled {
            color: var(--log-ink-soft);
            text-decoration: none;
            cursor: default;
        }

        #mediaPickerModal .media-picker-grid {
            display: grid;
            grid-template-columns: repeat(3, minmax(0, 1fr));
            gap: 1px;
            padding: 1px;
            background: var(--log-rule);
        }

        #mediaPickerModal .media-pick-tile {
            position: relative;
            aspect-ratio: 1;
            min-width: 0;
            overflow: hidden;
            background: var(--log-stock-band);
            cursor: pointer;
        }

        #mediaPickerModal .media-pick-tile img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
        }

        /* The same tick box the photographs page uses, for the same act — one shape for
           "chosen", wherever a photograph is being chosen. */
        #mediaPickerModal .media-pick-check {
            position: absolute;
            top: 6px;
            left: 6px;
            width: 16px;
            height: 16px;
            border: 1px solid var(--log-on-oilskin);
            background: rgba(22, 25, 26, 0.34);
        }

        #mediaPickerModal .media-pick-tile.is-picked .media-pick-check,
        #mediaPickerModal .media-pick-tile.picked .media-pick-check {
            background: var(--log-magenta);
            border-color: var(--log-magenta);
        }

        #mediaPickerModal .media-pick-tile.is-picked img,
        #mediaPickerModal .media-pick-tile.picked img { opacity: 0.55; }

        /* Already in the entry: a stamped word, not a control.

           ⚠️ `inset: auto auto 0 0` and not just `left`/`bottom`. The rule this replaces made
           it a full-tile scrim with the word centred in it, so setting two edges left the
           other two at 0 and the stamp covered the photograph it was stamping. */
        #mediaPickerModal .media-pick-used {
            position: absolute;
            inset: auto auto 0 0;
            display: block;
            width: auto;
            height: auto;
            padding: 2px var(--sp-2xs);
            background: var(--log-oilskin);
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--log-on-oilskin);
        }

        #mediaPickerModal .media-pick-tile.is-used { cursor: default; }

        #mediaPickerModal .mp-empty {
            padding: var(--sp-md) var(--sp-lg);
            font-family: var(--log-write);
            font-size: var(--fs-body);
            color: var(--log-ink-soft);
        }

        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/19-legacy-media.css · 2026-09-21
           18 rules. DESIGN.md: a surface's rules go in that surface's part, and the
           legacy parts only shrink. They sat in a part numbered for the outgoing world
           and dressed this one.

           ⚠️ THEY MOVED EARLIER IN THE CASCADE. 19-legacy-media.css loads after this file, so every
           one of these used to beat anything here that matched the same element. Appended
           at the END for that reason: it keeps their order relative to this part's own
           rules exactly as it was. What changed is their order against the parts BETWEEN
           the two, and the photographs are what say whether that mattered.
           ══════════════════════════════════════════════════════════════ */
        /* ⚠️ HALF OF THIS BLOCK MOVED AND HALF DID NOT (2026-09-11, the journal's editor).
           Everything that says what the room LOOKS like — the type, the ink, the borders,
           the tag chip's capsule, the toolbar's icon sizing, VISIBLE TO, the toolbar's two
           greyed placeholders and the status toggle's rotating chevron — is in
           css/17q-editor.css now, and is gone from here so nothing owns it twice.

           What stayed is STRUCTURE this migration did not re-derive: the flex chain from
           .modal down to .jw-surface that lets the editor fill the sheet, the drawer's
           transform and its open state, the editor's blockquote and link rules, and
           focus-mode. Those are listed on DEBTS.md — a room whose dressing is in one file
           and whose mechanics are in another is not finished, it is halfway. */
        .jw-top {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            z-index: 6;
            height: 56px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0 var(--sp-lg);
            background: var(--log-stock-band);
        }

        /* The toolbar and status bar are pinned to the bottom edge outright rather than
           being pushed there by a flex column.
           
           Six attempts at the flex approach all ended with them floating mid-screen: the
           chain from .modal down to .jw-surface has to resolve a height at every step, and
           somewhere along it — a percentage, a vh unit, an alignment rule — one link kept
           coming out short. Anchoring to the bottom edge removes the chain: `bottom: 0`
           against a box with `inset: 0` needs nothing computed.
           
           The writing area then reserves room for them with padding rather than by
           sharing space. */
        .jw-surface {
            position: absolute;
            /* clears the 56px header */
            top: 56px;
            left: 0;
            right: 0;
            /* toolbar 49px + status bar 45px */
            bottom: 94px;
            overflow-y: auto;
            -webkit-overflow-scrolling: touch;
            padding: 0 var(--sp-lg) var(--sp-lg);
        }


        .jw-toolbar {
            position: absolute;
            left: 0;
            right: 0;
            bottom: 45px;
            z-index: 10;
        }


        .jw-status {
            position: absolute;
            left: 0;
            right: 0;
            bottom: 0;
            z-index: 11;
        }

        /* A bordered strip, not a panel. It's a caption identifying what the entry is
           about — at the earlier size it competed with the title directly beneath it,
           which is the one thing on the screen that should dominate. */
        .jw-linked {
            display: flex;
            align-items: center;
            gap: var(--sp-sm);
            padding: 4px var(--sp-md);
            border: 1px solid var(--log-rule);
            border-left: 1px solid var(--log-rule-firm);
            background: var(--log-stock-band);
            margin-bottom: var(--sp-md);
        }


        .jw-tags {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: var(--sp-2xs);
            margin-bottom: var(--sp-lg);
            position: relative;
        }

        /* Serif, and larger. The editor used --font-sans at 0.88rem while the saved entry
           rendered in the display serif — so writing and reading looked like two different
           documents. */
        .jw-editor {
            font-family: var(--log-write);
            font-size: 15px;
            line-height: 1.4;
            color: var(--log-ink);
            border: none;
            background: none;
            padding: 0;
            min-height: 240px;
            outline: none;
        }

        /* space-between rather than a fixed gap. Eleven controls at a fixed spacing left
           a ragged gutter on the right of a narrow screen; distributing the free space
           puts the same margin between every pair at any width. */
        .jw-toolbar {
            display: flex;
            align-items: center;
            justify-content: space-between;
            height: 49px;
            padding: 0 var(--sp-md);
            border-top: 1px solid var(--log-rule);
            background: var(--log-stock-band);
        }

        /* Greyed and inert. The feature behind it doesn't exist yet, and a control that
           looks live but does nothing is worse than one that plainly says "not yet". */
        .jw-status {
            display: flex;
            align-items: center;
            justify-content: space-between;
            height: 45px;
            padding: 0 var(--sp-lg);
            border-top: 1px solid var(--log-rule);
            background: var(--log-stock-band);
            cursor: pointer;
            /* Below the drawer, not above it. Sitting at 13 against the drawer's 12 meant
               the status bar painted over the open panel — hence two chevrons on screen
               and a LINKED ACTIVITY row hidden underneath one of them. */
            z-index: 11;
            transition: opacity 0.18s;
        }

        /* Hidden outright while the drawer is open. The drawer carries its own close
           control, so keeping the status bar's chevron visible offers two buttons for the
           same action — and the one on top wasn't even the one that worked. */
        .modal-content.drawer-open .jw-status,
        .modal-content:has(.jw-drawer.is-open) .jw-status {
            opacity: 0;
            pointer-events: none;
        }

        /* Slides up over the writing rather than pushing it — the text stays where it was,
           so closing the drawer returns you to the same place on the page. */
        .jw-drawer {
            position: absolute;
            left: 0; right: 0; bottom: 0;
            /* Lighter than the writing surface behind it — the panel should feel laid on
               top, not carved out of the page. */
            background: var(--log-stock-band);
            border-top: 1px solid var(--log-rule-firm);
            transform: translateY(101%);
            transition: transform 0.26s cubic-bezier(0.32, 0.72, 0, 1);
            z-index: 12;
            /* Tall enough to cover the toolbar and status bar it slides over — at 78% they
               peeked out below it, which is what put two chevrons on screen. */
            max-height: 82%;
            min-height: 260px;
            display: flex;
            flex-direction: column;
        }

        /* visibility and pointer-events as well as the transform. A transformed element is
           still hit-testable and still focusable, so a closed drawer could swallow taps
           meant for the toolbar beneath it. */
        .jw-drawer {
            visibility: hidden;
            pointer-events: none;
        }


        .jw-drawer.is-open {
            transform: translateY(0);
            visibility: visible;
            pointer-events: auto;
        }


        .jw-drawer-body {
            padding: 0 var(--sp-lg) var(--sp-lg);
            overflow-y: auto;
            -webkit-overflow-scrolling: touch;
        }


        .jw-field-row {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: var(--sp-md);
        }

        /* Underline, not a box. Fewer edges on a screen that already has a drawer, a
           toolbar and a status bar. */
        .jw-drawer-photos {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: var(--sp-2xs);
        }


        .jw-drawer-photo {
            aspect-ratio: 1;
            overflow: hidden;
            background: var(--log-stock-band);
        }

        /* Everything but the paragraph in hand recedes. Applied only while the editor has
           focus — dimming during a read-back would make the entry hard to review, which is
           the opposite of the point. */
        .jw-editor.focus-mode > * {
            opacity: 0.34;
            transition: opacity 0.22s ease;
        }

        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/19-legacy-media.css · 2026-09-21
           13 rules. DESIGN.md: a surface's rules go in that surface's part, and the
           legacy parts only shrink. They sat in a part numbered for the outgoing world
           and dressed this one.

           ⚠️ THEY MOVED EARLIER IN THE CASCADE. 19-legacy-media.css loads after this file, so every
           one of these used to beat anything here that matched the same element. Appended
           at the END for that reason: it keeps their order relative to this part's own
           rules exactly as it was. What changed is their order against the parts BETWEEN
           the two, and the photographs are what say whether that mattered.
           ══════════════════════════════════════════════════════════════ */
        /* The row's dressing moved with the list's — see the note above. The decision it
           carried is worth keeping in words: 行间不画线 —— 一列等距的行本身就读得出是一列.
           The deck log overrules it, because a ruled sheet rules its rows; that is a change
           of world rather than a change of mind. */

        .diary-tag-chips {
            display: flex;
            flex-wrap: wrap;
            gap: var(--sp-2xs);
            margin-bottom: var(--sp-2xs);
        }


        .diary-tag-chip {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            padding: 0 4px 0 8px;
            background: var(--log-stock-band);
            border: 1px solid var(--log-rule-firm);
            border-radius: 0;
            font-family: var(--log-write);
            font-size: var(--fs-small);
            color: var(--log-ink);
        }


        .diary-tag-chip button {
            border: none;
            background: none;
            color: var(--log-ink-soft);
            font-size: var(--fs-base);
            line-height: 1;
            padding: 0 0;
            cursor: pointer;
        }

        /* ===== Inline diary images ===== */
        .diary-inline-img {
            display: block;
            max-width: 100%;
            height: auto;
            margin: var(--sp-md) 0;
            border: 1px solid var(--log-rule-firm);
        }


        .diary-photo-hint {
            font-family: var(--log-write);
            font-size: 13px;
            color: var(--log-ink-soft);
            margin-top: var(--sp-sm);
            min-height: 1em;
        }


        .diary-photo-item img {
            -webkit-user-drag: none;
            -webkit-touch-callout: none;
            user-select: none;
        }

        /* Follows the finger. pointer-events:none matters — without it the ghost sits
           under the touch point and every hit test returns the ghost instead of the text
           beneath, so the drop caret never finds a position. */
        .diary-drag-ghost {
            position: fixed;
            width: 84px;
            height: 84px;
            object-fit: cover;
            transform: translate(-50%, -50%) rotate(-2deg);
            border: 1px solid var(--log-stock);
            /* 批次 07：悬浮（拖拽幽灵）阴影删除。本规则已有 1px 实线边框、底色不透明，阴影的分层职责已有人接替。 */
            pointer-events: none;
            z-index: 10000;
            opacity: 0.94;
        }

        /* Where it will land. A full-width rule reads as "between these two lines" far
           more clearly than a blinking caret. */
        .diary-drop-caret {
            position: fixed;
            height: 2px;
            background: var(--log-magenta);
            pointer-events: none;
            z-index: 9999;
            display: none;
        }


        .diary-drop-caret::before {
            content: '';
            position: absolute;
            left: -3px;
            top: -3px;
            width: 8px;
            height: 8px;
            border-radius: 0;
            background: var(--log-magenta);
        }

        /* Only selection is suppressed here — not scrolling. The sheet has to keep
           scrolling during a drag, because that's how a drop target below the fold is
           reached; preventDefault on touchmove stops the *finger* scrolling it, while
           updateEdgeScroll drives it deliberately from the edges. Locking overflow would
           freeze both. */
        body.diary-dragging {
            user-select: none;
            -webkit-user-select: none;
        }

        /* Hints where the auto-scroll zones are, only while dragging. Without any cue the
           behaviour feels accidental the first time it happens. */
        body.diary-dragging .modal-sheet .modal-body::before,
        body.diary-dragging .modal-sheet .modal-body::after {
            content: '';
            position: sticky;
            display: block;
            left: 0;
            right: 0;
            height: 40px;
            margin-bottom: -40px;
            pointer-events: none;
            z-index: 5;
        }


        body.diary-dragging .modal-sheet .modal-body::before {
            top: 0;
            background: linear-gradient(to bottom, var(--log-stock), transparent);
        }


        body.diary-dragging .modal-sheet .modal-body::after {
            bottom: 0;
            margin-top: -40px;
            margin-bottom: 0;
            background: linear-gradient(to top, var(--log-stock), transparent);
        }

        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/19-legacy-media.css · 2026-09-21
           7 rules. DESIGN.md: a surface's rules go in that surface's part, and the
           legacy parts only shrink. They sat in a part numbered for the outgoing world
           and dressed this one.

           ⚠️ THEY MOVED EARLIER IN THE CASCADE. 19-legacy-media.css loads after this file, so every
           one of these used to beat anything here that matched the same element. Appended
           at the END for that reason: it keeps their order relative to this part's own
           rules exactly as it was. What changed is their order against the parts BETWEEN
           the two, and the photographs are what say whether that mattered.
           ══════════════════════════════════════════════════════════════ */
        /* ===== Media picker ===== */
        .media-picker-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: var(--sp-2xs);
        }


        .media-pick-tile {
            position: relative;
            aspect-ratio: 1;
            overflow: hidden;
            background: var(--log-stock-band);
            cursor: pointer;
        }


        .media-pick-tile img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
        }


        .media-pick-check {
            position: absolute;
            top: 5px;
            right: 5px;
            width: 20px;
            height: 20px;
            border-radius: 0;
            border: 2px solid var(--log-on-night-plate);
            background: rgba(22, 25, 26, 0.3);   /* the oilskin, not the outgoing #242424 (2026-09-17) */
        }


        .media-pick-tile.is-picked .media-pick-check {
            background: var(--log-ink);
            border-color: var(--log-ink);
        }


        .media-pick-tile.is-picked .media-pick-check::after {
            content: '✓';
            position: absolute;
            inset: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            color: var(--log-on-oilskin);
            font-size: 11px;
        }


        .media-pick-used {
            position: absolute;
            inset: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: var(--log-write);
            font-size: 13px;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            color: var(--log-ink-soft);
        }

        /* ⚠️ AND THE THREE WRITTEN ON ONE LINE. The mover walks rule blocks by finding a line
           that ends in an opening brace, so a rule written entirely on one line was invisible
           to it — the same blind spot that hid 21 declarations from two scans earlier today.
           Taken by hand, which is what that blind spot costs every time. */
        .media-pick-tile.is-picked img { opacity: 0.62; }

        /* Already in this entry — shown but not selectable, so it is clear why it cannot be
           picked rather than simply missing from the grid. */
        .media-pick-tile.is-used { cursor: default; }
        .media-pick-tile.is-used img { opacity: 0.32; }

        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/20-legacy-calendar.css · 2026-09-21
           5 rules. DESIGN.md: a surface's rules go in that surface's part, and the
           legacy parts only shrink. They sat in a part numbered for the outgoing world
           and dressed this one.

           ⚠️ THEY MOVED EARLIER IN THE CASCADE. 20-legacy-calendar.css loads after this file, so every
           one of these used to beat anything here that matched the same element. Appended
           at the END for that reason: it keeps their order relative to this part's own
           rules exactly as it was. What changed is their order against the parts BETWEEN
           the two, and the photographs are what say whether that mattered.
           ══════════════════════════════════════════════════════════════ */
        /* Fixed at half the screen, not sized to its contents. A sheet that changes height
           with the number of photos moves the grid under your thumb between sessions; half
           the screen is predictable, shows two rows at a glance, and leaves the entry
           you're writing visible above it. */
        #mediaPickerModal .modal-content {
            height: 50dvh;
            max-height: 50dvh;
            display: flex;
            flex-direction: column;
            margin-top: auto;
        }

        /* `#mediaPickerModal .modal-header` stood here and was REMOVED 2026-09-21, the first
           thing the combination scan found (dev/css-dead.mjs). That picker's head is its own
           component — .mp-head, with .mp-add and .mp-close inside it — so the room has never
           held a .modal-header. The name is live in #addActivityModal, which is why the older
           scan saw nothing wrong. */
        #mediaPickerModal .modal-body {
            flex: 1;
            min-height: 0;
            overflow-y: auto;
            -webkit-overflow-scrolling: touch;
            /* Barely any inset. The photographs are the content — margins around a picture
               grid are just less picture. */
            padding: 0 0 0;
        }

        /* Four across. At three the thumbnails were larger than they need to be for
           recognising your own photos, and fewer fitted on screen. */
        #mediaPickerModal .media-picker-grid {
            grid-template-columns: repeat(4, 1fr);
            gap: 3px;
        }

        /* Same treatment for the drafts sheet — it grows with the number of drafts and has
           the identical structure. */
        #diaryDraftsModal .modal-content {
            max-height: 78dvh;
            display: flex;
            flex-direction: column;
            margin-top: auto;
        }

        /* Same again, same day: the drafts sheet's head is .jdraft-head, so
           `#diaryDraftsModal .modal-header` dressed nothing. */
        #diaryDraftsModal .modal-body {
            flex: 1;
            min-height: 0;
            overflow-y: auto;
            -webkit-overflow-scrolling: touch;
        }

        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/20-legacy-calendar.css · 2026-09-21
           7 rules. DESIGN.md: a surface's rules go in that surface's part, and the
           legacy parts only shrink. They sat in a part numbered for the outgoing world
           and dressed this one.

           ⚠️ THEY MOVED EARLIER IN THE CASCADE. 20-legacy-calendar.css loads after this file, so every
           one of these used to beat anything here that matched the same element. Appended
           at the END for that reason: it keeps their order relative to this part's own
           rules exactly as it was. What changed is their order against the parts BETWEEN
           the two, and the photographs are what say whether that mattered.
           ══════════════════════════════════════════════════════════════ */
        /* Tag entry: input and a commit button on one row. The button is what makes this
           work on Android, where the keyboard's return key isn't reliably reported. */
        .jw-tag-entry-row {
            display: flex;
            align-items: center;
            gap: var(--sp-2xs);
        }


        .jw-tag-add-btn {
            flex: none;
            padding: 4px var(--sp-md);
            border: 1px solid var(--log-rule-firm);
            border-radius: 0;
            background: var(--log-stock-band);
            font-family: var(--log-write);
            font-size: 11px;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--log-ink-soft);
            cursor: pointer;
        }

        /* Add lives in the header, within reach of the thumb that just tapped a photo. */
        .mp-add {
            flex: none;
            /* 阶段二 · 第二批：垂直 padding 删除 —— min-height:44 已主导高度，
               这份 padding 不再影响尺寸，只剩水平方向起作用。删它零视觉变化。 */
            padding: 0 var(--sp-md);
            border: 1px solid var(--log-oilskin);
            border-radius: 0;
            background: var(--log-oilskin);
            color: var(--log-on-oilskin);
            font-family: var(--log-write);
            font-size: var(--fs-small);
            letter-spacing: 0.12em;
            text-transform: uppercase;
            cursor: pointer;
            font-variant-numeric: lining-nums tabular-nums;
        }


        .mp-add:disabled {
            background: none;
            border-color: var(--log-rule);
            color: var(--log-ink-soft);
            cursor: default;
        }

        /* `.jrnl-card-preview` was a fourth selector on this guard, and there is no such
           element anywhere in public/ — it named a card preview that has not existed for a
           long time. Removed 2026-09-11. */
        #diaryRichEditor [style*="background"],
        .jrnl-dt-text [style*="background"] {
            background: none;
        }

        /* Same for pasted text colour and typeface — an entry should read in the journal's
           own voice, not in whatever font the source page used. */
        #diaryRichEditor [style*="color"],
        .jrnl-dt-text [style*="color"] {
            color: inherit;
        }


        #diaryRichEditor [style*="font-family"],
        #diaryRichEditor font,
        .jrnl-dt-text [style*="font-family"],
        .jrnl-dt-text font {
            font-family: inherit;
            font-size: inherit;
        }

        /* One more written on one line, and invisible to the mover for that reason. */
        .jw-tag-add-btn:active { background: var(--log-stock); }

        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/19-legacy-media.css · 2026-09-21  (appended)
           17 rules. DESIGN.md: a surface's rules go in that surface's part, and the
           legacy parts only shrink.
           ⚠️ 19-legacy-media.css loaded BEFORE this file. This placement keeps
           their order against THIS PART’S OWN rules and nothing else: against the 14 part(s)
           between #19 and #34 the move reverses it. The banner used to claim they "keep that
           place" full stop, which was never true (review, 2026-09-22). Checked separately.
           ══════════════════════════════════════════════════════════════ */
            .diary-photo-grid { gap: var(--sp-xs); }


        .diary-tag-chip button:active { color: var(--log-port); }

        /* Staged photos are tappable, so they need to look it. */
        .diary-photo-item img { cursor: pointer; }

        /* Draft is the secondary action — publishing is how an entry normally ends. */
        #diaryDraftBtn { flex: 0 0 auto; }

        /* ===== Long-press drag ===== */
        #diaryRichEditor img {
            cursor: grab;
            transition: opacity 0.15s;
            /* Stops the browser's own image-drag and the iOS callout menu from firing
               during the hold — both would pre-empt the gesture. */
            -webkit-user-drag: none;
            -webkit-touch-callout: none;
            user-select: none;
        }

        /* Full screen, not a sheet. Writing is the whole task while it's happening — a
           partial sheet leaves the map showing above it, which pulls the eye away from the
           page and makes the writing area feel provisional.
           
           The base `.modal.active` centres its content vertically and `.modal-sheet
           .modal-content` caps it at 85vh; both are overridden here rather than changed
           globally, because every other sheet in the app still wants that behaviour. */
        .modal.jrnl-write,
        .modal.jrnl-write.active {
            align-items: stretch;
            /* The drawer sits at translateY(101%) when closed. With the modal scrollable,
               that translated box stays inside the scroll area and shows up as content
               below the fold — which is why the details panel appeared open. */
            overflow: hidden;
        }


        .jrnl-write .modal-content {
            display: flex;
            flex-direction: column;
            width: 100%;
            /* The writing surface sits a shade darker than the drawer that slides over it.
               Without the difference the drawer reads as more page rather than as a panel,
               and its top edge is the only thing separating them. */
            background: var(--log-stock);
            /* Anchored to the frame rather than sized in percentages. `height: 100%`
               depends on every ancestor resolving a height, and .modal is a flex container
               with its own alignment — absolute positioning against the modal removes that
               chain entirely and always fills it. */
            position: absolute;
            inset: 0;
            height: auto;
            max-height: none;
            max-width: 100%;
            margin: 0;
            border-radius: 0;
            padding: 0;
            overflow: hidden;
            /* The sheet slide-up ends on a transform, and a transformed element becomes
               the containing block for its fixed descendants — which is precisely how the
               drawer inside it ended up positioned against the wrong box. Fade in instead;
               nothing about a full-screen editor needs to look like a sheet rising. */
            animation: jwFadeIn 0.2s ease-out;
        }

        /* Pinned to the top edge, the same way the toolbar and status bar are pinned to
           the bottom. Left in normal flow it was overlapped by .jw-surface — which is
           absolutely positioned at top:0 — and the close and save controls disappeared
           beneath the writing area.
           
           All four regions are now positioned against .modal-content directly: header,
           surface, toolbar, status. Nothing is in flow, so nothing can push anything
           else out of place. */

        .jw-top-actions { display: flex; align-items: center; gap: var(--sp-sm); }


        .jw-tag-entry { position: relative; flex: 1 1 140px; min-width: 140px; }

        /* The opening line is the title, so it has to look unmistakably like one — at
           1.5rem against 1.06rem body it read as merely emphasised. Nearly double now,
           with tighter leading as display type wants. */
        .jw-tray { margin-top: var(--sp-lg); }

            .jw-toolbar { padding-left: var(--sp-sm); padding-right: var(--sp-sm); }


        .jw-field { margin-bottom: var(--sp-lg); position: relative; }


        .jw-drawer-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

        /* The quote and the link, read and written, moved to css/17zd-leftovers.css, 2026-09-14. */

        .jw-editor.focus-mode > .is-active-block { opacity: 1; }

        /* Images stay legible regardless: a dimmed photo reads as a loading failure. */
        .jw-editor.focus-mode > img { opacity: 0.72; }

        /* ===== Linked activity control (in the drawer) ===== */
        .jw-link-row { display: flex; gap: var(--sp-2xs); align-items: stretch; }

        /* Tag entry sits on its own line once there are chips — sharing a row with the
           + TAG button squeezed the input to a sliver on a narrow screen. */
        .jw-tag-entry { flex: 1 1 100%; min-width: 0; margin-top: var(--sp-2xs); }

        /* ⚠️ CARRIED FROM css/19-legacy-media.css ON ITS DELETION, 2026-09-21.
           `animation: jwFadeIn` is written in this file already; the keyframes were in that one.
           Keyframes are global whatever the order, so it worked from there — and deleting the
           file would have taken the animation away and left the property naming nothing. No
           photograph could have caught it: a fade that never runs ends where one that runs ends. */
        @keyframes jwFadeIn {
            from { opacity: 0; }
            to   { opacity: 1; }
        }

        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/22-diagnostics.css · 2026-09-21  (appended)
           1 rules. DESIGN.md: a surface's rules go in that surface's part, and the
           legacy parts only shrink.
           ⚠️ 22-diagnostics.css loaded AFTER this file. Placing them
           at the foot keeps their order against THIS PART’S OWN rules — and nothing else.
           Against the parts BETWEEN #32 and #49 the order is reversed by this move, and
           whether that matters was a separate question somebody had to answer. See DESIGN.md.
           ══════════════════════════════════════════════════════════════ */
        /* A field that stopped the save. Underline in the warning colour rather than a
           box — the form's fields are underlined, and a box would be the only one. */
        .jw-input.is-invalid {
            /* Was --danger, the outgoing brick #9B4D4D. A fault in this world is written in port
               red — 02-auth.css says so of the sign-in sheet and the toast says it of itself. */
            border-bottom-color: var(--log-port);
            /* 批次 07：inset shadow 画的下划线。本规则上一行已有 border-bottom-color，加粗靠 border-bottom-width，不再用 shadow。 */
            border-bottom-width: 1px;
        }

        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/20-legacy-calendar.css · 2026-09-21  (appended)
           12 rules. DESIGN.md: a surface's rules go in that surface's part, and the
           legacy parts only shrink.
           ⚠️ 20-legacy-calendar.css loaded AFTER this file. Placing them
           at the foot keeps their order against THIS PART’S OWN rules — and nothing else.
           Against the parts BETWEEN #30 and #46 the order is reversed by this move, and
           whether that matters was a separate question somebody had to answer. See DESIGN.md.
           ══════════════════════════════════════════════════════════════ */
        /* Title one step down. 1.95rem was set against English; Chinese glyphs fill the
           full em box, so the same size reads considerably larger — and a title that fills
           two lines before the entry has begun is more announcement than heading. */
        .jw-editor > *:first-child { font-size: 19px; }


        .jw-tag-entry-row input { flex: 1; min-width: 0; }

        /* ===== Media picker: bounded and scrollable =====
           The grid grows with the trip's photo count, and .modal-body had neither a flex
           basis nor overflow — so twelve pictures pushed the content past the 85vh cap and
           it overflowed *upwards*, taking the title off screen and leaving the Add button
           stranded mid-page with background below it.
           
           Header and actions are fixed; only the grid scrolls. */

        .mp-add:active:not(:disabled) { opacity: 0.82; }

        /* Picker list */
        #photoActivityModal .modal-content {
            height: 62dvh;
            max-height: 62dvh;
            display: flex;
            flex-direction: column;
            margin-top: auto;
        }


        #photoActivityModal .modal-header { flex: none; }


        #photoActivityModal .modal-body {
            flex: 1;
            min-height: 0;
            overflow-y: auto;
            -webkit-overflow-scrolling: touch;
        }

        /* ===== Activity picker: no scrim over the photo =====
           The default modal backdrop dimmed the picture you are trying to identify, which
           is the one thing you need to see while choosing what it shows. The sheet itself
           is opaque, so the photo above it stays untouched. */
        #photoActivityModal { background: none; }

        /* Search box, shared by both activity pickers. */
        .pa-search {
            flex: none;
            padding: 0 var(--sp-lg) var(--sp-sm);
            position: relative;
        }


        .pa-search input {
            width: 100%;
            border: none;
            border-bottom: 1px solid var(--log-rule);
            background: none;
            padding: 4px 0 4px 20px;
            font-family: var(--log-write);
            font-size: var(--fs-base);
            color: var(--log-ink);
            outline: none;
        }


        .pa-search input:focus { border-bottom-color: var(--log-ink, #43276B); }


        .pa-search svg {
            position: absolute;
            left: var(--sp-lg);
            top: 9px;
            color: var(--log-ink-soft);
            pointer-events: none;
        }


        .pa-empty {
            padding: var(--sp-xl) var(--sp-lg);
            text-align: center;
            font-family: var(--log-write);
            font-size: var(--fs-small);
            color: var(--log-ink-soft);
        }
