        /* ==========================================================================
           #E  The Shelf — where a journey's book is kept
           --------------------------------------------------------------------------
           Fifth surface in the deck log world, 2026-09-05, built as its empty state
           while the book itself is briefed (.impeccable/surfaces/public-edition-html.md).
           The head is the Wall's head, word for word: Back, the name, nothing at the
           right until there is something to do. The empty line is the Specimen — the
           page saying what it is for and what does not exist yet.
           ========================================================================== */

        #shelfPage {
            background: var(--log-stock);
        }

        #shelfPage.active {
            display: flex;
            flex-direction: column;
        }

        /* The head, its title and Back are the room head (.room-bar, css/11-home.css, since
           2026-09-16). What is this room's own: the whole shelf scrolls, so the head is held
           at the top by sticking. */
        #shelfPage .shelf-bar {
            position: sticky;
            top: 0;
            z-index: 2;
        }

        #shelfPage .shelf-empty {
            padding: var(--sp-2xl) var(--sp-lg);
            font-family: var(--log-write);
            font-size: var(--fs-body);
            line-height: 1.5;
            color: var(--log-ink);
        }

        #shelfPage .shelf-empty span {
            display: block;
            margin-top: var(--sp-sm);
            font-size: var(--fs-small);
            color: var(--log-ink-soft);
        }

        /* The books (2026-09-18): only their covers, as Apple Books shows a library (owner: 不想展示过多信息 参考
           apple books的设计), two to a row on a phone and more when wide; on one's own shelf, under each cover's
           left corner, who may see it. The name is the cover's own words, and the button's name for a reader.
           A cover is the book's page 1, so all its colour is the traveller's, as on the Wall. The same books
           stand on a profile's shelf, so every rule names both rooms. */
        :is(#shelfPage, #profilePage) .shelf-books {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
            gap: var(--sp-xl) var(--sp-lg);
            padding: var(--sp-lg) var(--sp-lg) var(--sp-2xl);
        }

        :is(#shelfPage, #profilePage) .shelf-books:empty { display: none; }

        :is(#shelfPage, #profilePage) .shelf-book {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            gap: var(--sp-xs);
            min-width: 0;
        }

        :is(#shelfPage, #profilePage) .shelf-open {
            display: block;
            width: 100%;
            margin: 0;
            padding: 0;
            border: none;
            border-radius: 0;
            background: none;
            cursor: pointer;
            -webkit-tap-highlight-color: transparent;
        }

        :is(#shelfPage, #profilePage) .shelf-cover {
            display: block;
            width: 100%;
            aspect-ratio: 1120 / 1588;
            object-fit: cover;
            background: var(--log-stock-band);
            outline: 1px solid var(--log-rule);
        }

        /* Pressed, the book sinks a hair, as a finger on a board would. */
        :is(#shelfPage, #profilePage) .shelf-open:active .shelf-cover { transform: scale(0.98); }

        :is(#shelfPage, #profilePage) .shelf-vis {
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            color: var(--log-ink-soft);
        }

        /* A book behind its diary says so under its word, in ink: its reader offers to make it again (2026-09-19). */
        :is(#shelfPage, #profilePage) .shelf-stale {
            margin-top: -2px;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            color: var(--log-ink);
        }

        #shelfPage :focus-visible {
            outline: 2px solid var(--log-ink);
            outline-offset: 2px;
        }

        /* ==========================================================================
           #D  The Wall — the log's own album, in strips
           --------------------------------------------------------------------------
           Fourth surface in the deck log world, 2026-09-04, and the first built from
           nothing. Brief: .impeccable/surfaces/public-25-wall-js.md

           This is the house law's exception's home: the one screen made of nothing
           but the traveller's own photographs, so all the colour on it is theirs and
           none of it is the page's. Stock ground, the margin voice for a journey's
           name, firm rules between strips, hairlines between frames, ink and softer
           ink for the few words, the night plate when a photograph is opened alone.

           A journey is one strip: fixed height, every photograph at its own width,
           read sideways. The strip refuses the grid — nothing is cropped and nothing
           is made the same size as anything else — and it refuses the slideshow.
           ========================================================================== */

        #photoWallPage {
            background: var(--log-stock);
        }

        /* The strip's height, on whichever page carries strips (.wall-root: the Wall
           itself, and a traveller's wall on their Profile). 155px at 430 — four
           journeys to a screen with the names hidden, three with them shown; scales
           between 120 and 170 so a small phone is not all strip and a tablet is not
           all stock. */
        .wall-root {
            --wall-h: clamp(120px, 36vw, 170px);
        }

        /* A column, so that a wall shorter than the screen still ends on its foot at
           the bottom and not in the middle. Wins over .page.active by the id. */
        #photoWallPage.active {
            display: flex;
            flex-direction: column;
        }

        #photoWallPage #wallStrips { flex: 1 0 auto; }

        /* ---- The head ------------------------------------------------------------- */

        /* The whole head is this one bar: Back, the title, Share. Three cells with the
           ends equal so the title is centred on the page and not between two words of
           different length. A firm rule under it, because the strips start at once. */
        /* The head, its title and Back are the room head (.room-bar, css/11-home.css, since
           2026-09-16). What is this room's own: the whole wall scrolls, so the head is held at
           the top by sticking — Back is the only way off a page that is not on the bottom
           menu, and it has to stay in reach the length of the wall. */
        #photoWallPage .wall-bar {
            position: sticky;
            top: 0;
            z-index: 2;
        }

        /* The wall's other words — Names in the head, Pin more and Back to top at the foot —
           in Back's word: print caps, underlined, ink. */
        #photoWallPage .wall-names,
        #photoWallPage .wall-foot-word {
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            color: var(--log-ink);
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            background: none;
            border: none;
            padding: 0;
            cursor: pointer;
            min-height: 44px;
            display: inline-flex;
            align-items: center;
        }

        #photoWallPage .wall-names { justify-self: end; }
        #photoWallPage .wall-names[hidden] { display: none; }

        /* The annotation ink for the one word on the page that adds to the record. */
        #photoWallPage .wall-foot-word.is-mark { color: var(--log-magenta); }

        /* ---- The foot --------------------------------------------------------------- */
        /* The wall is not on the bottom menu, so it ends on its own terms: on the band,
           like the Register, with the two things a reader wants at the bottom of a long
           wall — the names, and the way back up. */
        #photoWallPage .wall-foot {
            position: sticky;
            bottom: 0;
            z-index: 2;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: var(--sp-sm);
            /* The head's measure exactly — the same words at the same size between the
               same rules — so the page is bound top and bottom by one bar. */
            padding: var(--sp-2xs) var(--sp-lg) calc(var(--sp-2xs) + env(safe-area-inset-bottom, 0px));
            background: var(--log-stock-band);
            border-top: 1px solid var(--log-rule-firm);
        }

        /* display:flex above outweighs the browser's own [hidden]; said again here so
           the foot is really gone while the wall is waiting or empty. */
        #photoWallPage .wall-foot[hidden] { display: none; }

        /* ---- Waiting ------------------------------------------------------------------ */
        /* Three strips of band at the strip's height, the page's own shape before its
           content, breathing slowly. Still when motion is off. */
        #photoWallPage .wall-waiting i {
            display: block;
            height: var(--wall-h);
            background: var(--log-stock-band);
            border-bottom: 1px solid var(--log-rule-firm);
            animation: wall-breathe 1.8s ease-in-out infinite;
        }
        #photoWallPage .wall-waiting i:nth-child(2) { animation-delay: 0.3s; }
        #photoWallPage .wall-waiting i:nth-child(3) { animation-delay: 0.6s; }

        @keyframes wall-breathe {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.55; }
        }

        /* The words, timed by the stylesheet: nothing for the first moment — most
           answers arrive inside it and a flash of words would be noise — then "Reading
           the wall"; and if it is still reading after a long while, the line that says
           what to do. Two paragraphs, each with its own appearance; no timers to clear. */
        #photoWallPage .wall-waiting { position: relative; }
        #photoWallPage .wall-waiting-word {
            position: absolute;
            left: var(--sp-lg);
            right: var(--sp-lg);
            top: calc(var(--wall-h) * 1.5);
            margin: 0;
            text-align: center;
            font-family: var(--log-write);
            font-size: var(--fs-small);
            line-height: 1.6;
            color: var(--log-ink-soft);
            opacity: 0;
        }
        #photoWallPage .wall-waiting-word.is-soon { animation: wall-word-soon 0.4s ease 1.5s forwards; }
        #photoWallPage .wall-waiting-word.is-late { animation: wall-word-late 0.4s ease 8s forwards; }
        #photoWallPage .wall-waiting-word .wall-foot-word { min-height: 0; display: inline; }
        /* The room's line in the ink and the body size, Boswell at work beneath it — the pair every room's waiting
           and empty page now carries (2026-09-17). */
        #photoWallPage .wall-waiting-word.is-soon { font-size: var(--fs-body); color: var(--log-ink); }
        #photoWallPage .wall-waiting-word.is-soon span {
            display: block;
            margin-top: var(--sp-2xs);
            font-size: var(--fs-small);
            color: var(--log-ink-soft);
        }

        @keyframes wall-word-soon {
            from { opacity: 0; }
            to { opacity: 1; }
        }
        /* The late line takes the early line's place: the same animation on the late
           one, and a second keyframe on the early one that removes it at 8s. */
        @keyframes wall-word-late {
            from { opacity: 0; }
            to { opacity: 1; }
        }
        #photoWallPage .wall-waiting-word.is-soon {
            animation: wall-word-soon 0.4s ease 1.5s forwards, wall-word-gone 0.2s ease 8s forwards;
        }
        @keyframes wall-word-gone {
            to { opacity: 0; visibility: hidden; }
        }

        @media (prefers-reduced-motion: reduce) {
            #photoWallPage .wall-waiting i { animation: none; }
        }

        /* ---- A strip ---------------------------------------------------------------- */

        /* Two pixels of ground between one journey's strip and the next, so two
           strips of similar photographs do not read as one. When the wall is named the
           strip's own head separates them and the gap goes. */
        .wall-root .wall-strip + .wall-strip { margin-top: 2px; }
        .wall-root .is-named .wall-strip + .wall-strip { margin-top: 0; }

        /* The journey's line, in the section-head grammar: the band, print caps, a firm
           rule under it, the count at the right end where the log's heads keep their
           action. Nothing on it acts; unpinning is on the opened photograph.

           Hidden unless the wall is named (Show names, in the foot; kept on the device).
           By default the strips are the photographs alone and the firm rule between
           them is the only mark of where one journey ends. */
        .wall-root .wall-strip-head { display: none; }
        .wall-root .is-named .wall-strip-head {
            display: flex;
            align-items: baseline;
            gap: var(--sp-sm);
            padding: var(--sp-sm) 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.16em;
            text-transform: uppercase;
            color: var(--log-ink);
        }

        /* A long name gives way, with an ellipsis; the dates and the count keep their
           one line. The name is the one thing on the row the reader can recover by
           tapping a photograph, so it is the one thing that may be cut. */
        .wall-root .wall-strip-name {
            flex: 0 1 auto;
            min-width: 0;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        .wall-root .wall-strip-meta {
            flex: none;
            margin-left: auto;
            white-space: nowrap;
            font-family: var(--log-write);
            font-size: var(--fs-small);
            font-weight: 400;
            letter-spacing: 0;
            text-transform: none;
            color: var(--log-ink-soft);
        }

        /* The reel: one row that runs off the right edge and scrolls under the thumb,
           snapping to each frame's left edge so a swipe lands on a photograph rather
           than between two. Its own overflow is what keeps the two axes apart —
           sideways here, up and down on the page — and it must never take a vertical
           drag, which is what pan-x says. No scrollbar: the run-off edge is the
           affordance. */
        .wall-root .wall-reel {
            display: flex;
            align-items: stretch;
            /* Two pixels of ground between prints. A hairline on the frame's edge
               vanished against photographs of the same place taken minutes apart;
               a gap of the page itself does not. */
            gap: 2px;
            height: var(--wall-h);
            overflow-x: auto;
            overflow-y: hidden;
            scroll-snap-type: x mandatory;
            overscroll-behavior-x: contain;
            touch-action: pan-x pan-y;
            -webkit-overflow-scrolling: touch;
            scrollbar-width: none;
            background: var(--log-stock);
        }

        .wall-root .wall-reel::-webkit-scrollbar { display: none; }

        .wall-root .wall-reel.is-drifting { scroll-snap-type: none; }

        /* A frame is exactly as wide as its photograph is at the strip's height — width
           over height times the height — with a hairline on its right and nothing
           between. Fixed in both axes so the strip's rhythm is the photographs' own. */
        .wall-root .wall-frame {
            flex: none;
            height: var(--wall-h);
            width: calc(var(--wall-h) * var(--ar, 1.3333));
            max-width: 92vw;
            padding: 0;
            border: none;
            background: var(--log-stock-band);
            scroll-snap-align: start;
            cursor: pointer;
            overflow: hidden;
            position: relative;
        }

        .wall-root .wall-frame img,
        .wall-root .wall-frame video {
            display: block;
            width: 100%;
            height: 100%;
            object-fit: cover;
        }

        /* The picture comes in rather than appears (markFramesIn). */
        .wall-root .wall-frame img { opacity: 0; transition: opacity 0.35s ease; }
        .wall-root .wall-frame img.is-in { opacity: 1; }
        @media (prefers-reduced-motion: reduce) {
            .wall-root .wall-frame img { transition: none; }
        }

        /* A video with no still yet: the frame is band with the mark in the centre. */
        .wall-root .wall-vbare {
            display: flex;
            align-items: center;
            justify-content: center;
            width: 100%;
            height: 100%;
            background: var(--log-stock-band);
            color: var(--log-ink-soft);
        }
        .wall-root .wall-vbare svg { fill: currentColor; }

        /* A video's still carries the album's small mark: the one glyph the wall
           allows, because "video" written on a picture would be a word on the plate.
           White on a soft dark disc so it reads on any still. */
        .wall-root .wall-vmark,
        #pinSheet .wall-vmark {
            position: absolute;
            right: 6px;
            bottom: 6px;
            width: 18px;
            height: 18px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 50%;
            background: rgba(11, 18, 32, 0.62);
            color: #FFFFFF;
            pointer-events: none;
        }

        .wall-root .wall-vmark svg,
        #pinSheet .wall-vmark svg { fill: currentColor; margin-left: 1px; }

        /* Unmeasured photographs — rows from before width/height were recorded — take a
           4:3 frame and are fitted inside it. Cover would crop them by guesswork;
           contain shows the whole picture on a sliver of band. Honest, and rare. */
        .wall-root .wall-frame.is-unmeasured img { object-fit: contain; }

        .wall-root .wall-frame:focus-visible {
            outline: 2px solid var(--log-ink);
            outline-offset: -2px;
        }

        /* ---- Nothing pinned, or nothing read ---------------------------------------- */

        #photoWallPage .wall-empty {
            padding: var(--sp-2xl) var(--sp-lg);
            font-family: var(--log-write);
            font-size: var(--fs-body);
            line-height: 1.5;
            color: var(--log-ink);
        }

        #photoWallPage .wall-empty span {
            display: block;
            margin-top: var(--sp-sm);
            font-size: var(--fs-small);
            color: var(--log-ink-soft);
        }

        #photoWallPage .wall-empty .wall-foot-word { min-height: 0; margin-left: 0.35em; }
        #photoWallPage .wall-empty > .wall-foot-word { display: inline-flex; margin: var(--sp-md) 0 0; min-height: 44px; }

        /* ---- The pin sheet ------------------------------------------------------------ */
        /* Borrows the Register's sheet (.acs-*) whole — the same moment, the traveller
           adding to their own record — and adds only what a picture needs: a grid of
           tiles, three across, square, and a magenta keyline on the ones chosen. Square
           because this is a working sheet for choosing, not the wall; the wall is where
           nothing is cropped. */
        #pinSheet .acs-row-tag { flex: none; white-space: nowrap; }

        #pinSheet .acs-row-name {
            flex: 1 1 auto;
            min-width: 0;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        #pinSheet .pin-trip {
            display: flex;
            align-items: center;
            gap: var(--sp-sm);
            padding-bottom: var(--sp-xs);
            border-bottom: 1px solid var(--log-rule-firm);
        }

        #pinSheet .pin-trip-name {
            flex: 1 1 auto;
            min-width: 0;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            font-family: var(--log-write);
            font-size: var(--fs-base);
            color: var(--log-ink);
        }

        #pinSheet .pin-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 2px;
            margin-top: var(--sp-md);
        }

        #pinSheet .pin-tile {
            position: relative;
            aspect-ratio: 1;
            padding: 0;
            border: none;
            background: var(--log-stock-band);
            overflow: hidden;
            cursor: pointer;
        }

        #pinSheet .pin-tile img {
            display: block;
            width: 100%;
            height: 100%;
            object-fit: cover;
        }

        #pinSheet .pin-tile.is-picked img { opacity: 0.6; }

        #pinSheet .pin-tile.is-picked::after {
            content: '';
            position: absolute;
            inset: 0;
            box-shadow: inset 0 0 0 3px var(--log-magenta);
            pointer-events: none;
        }

        #pinSheet :focus-visible {
            outline: 2px solid var(--log-ink);
            outline-offset: 2px;
        }

        #pinSheet .pin-tile:focus-visible { outline-offset: -2px; }

        #pinSheet .acs-empty .wall-foot-word {
            display: inline;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            color: var(--log-ink);
            text-decoration: underline;
            text-underline-offset: 0.25em;
            background: none;
            border: none;
            padding: 0;
            cursor: pointer;
        }

        /* ---- A photograph opened alone ---------------------------------------------- */
        /* The night plate, the same second dark the globe sits on: a photograph shown
           by itself is shown on the plate, never on paper. Under it, on stock, the
           caption card — ruled top, the traveller's words, place and date in the softer
           ink, the journey's mark, and for the owner the one thing that acts. */

        .wall-view {
            position: fixed;
            inset: 0;
            z-index: 60;
            display: flex;
            flex-direction: column;
            background: var(--log-night-plate, #0B1220);
            opacity: 0;
            visibility: hidden;
            transition: opacity 0.22s ease, visibility 0s linear 0.22s;
        }

        .wall-view.is-open {
            opacity: 1;
            visibility: visible;
            transition: opacity 0.22s ease, visibility 0s;
        }

        .wall-view-bar {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: calc(var(--sp-sm) + env(safe-area-inset-top, 0px)) var(--sp-lg) var(--sp-sm);
        }

        .wall-view-close {
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            color: var(--log-on-night-plate, #DCE9F8);
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            background: none;
            border: none;
            padding: 0;
            cursor: pointer;
            min-height: 44px;
            display: inline-flex;
            align-items: center;
        }

        .wall-view-index {
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.12em;
            font-variant-numeric: lining-nums tabular-nums;
            color: var(--log-on-night-plate, #DCE9F8);
            opacity: 0.85;
        }

        .wall-view-plate {
            flex: 1;
            min-height: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 0 var(--sp-md);
        }

        .wall-view-plate img,
        .wall-view-plate video {
            max-width: 100%;
            max-height: 100%;
            width: auto;
            height: auto;
            display: block;
        }

        /* The card is as short as its words: the journey's mark over the place and
           date, Unpin at the right, one rule where it meets the plate and no other.
           It was three stacked lines with a rule between them and a third of the
           plate's height; the photograph is what the page is for. */
        .wall-view-card {
            background: var(--log-stock);
            border-top: 1px solid var(--log-rule-firm);
            padding: var(--sp-xs) var(--sp-lg) calc(var(--sp-xs) + env(safe-area-inset-bottom, 0px));
        }

        .wall-view-caption {
            margin: 0 0 var(--sp-2xs);
            font-family: var(--log-write);
            font-size: var(--fs-body);
            line-height: 1.5;
            color: var(--log-ink);
        }

        .wall-view-line {
            display: block;
            margin: 0;
            font-family: var(--log-write);
            font-size: var(--fs-small);
            line-height: 1.4;
            color: var(--log-ink-soft);
        }

        .wall-view-foot {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: var(--sp-sm);
        }

        .wall-view-text {
            flex: 1 1 auto;
            min-width: 0;
        }

        .wall-view-mark {
            display: block;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            line-height: 1.4;
            text-transform: uppercase;
            color: var(--log-ink);
        }

        /* Unpin is the owner's hand taking something off the wall: annotation magenta,
           underlined, the register's own mark for an act on the record. */
        .wall-unpin {
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--log-magenta);
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            background: none;
            border: none;
            padding: 0;
            cursor: pointer;
            min-height: 44px;
            display: inline-flex;
            align-items: center;
        }

        .wall-view :focus-visible {
            outline: 2px solid var(--log-on-night-plate, #DCE9F8);
            outline-offset: 2px;
        }

        .wall-view-card :focus-visible {
            outline-color: var(--log-ink);
        }

        @media (prefers-reduced-motion: reduce) {
            .wall-view,
            .wall-view.is-open {
                transition: none;
            }
        }

        /* ---- The pin, in the album ------------------------------------------------- */
        /* Its rules are beside the heart's in css/19-legacy-media.css since 2026-09-13, top left, with the link mark that
           shared its corner written under the photograph instead. */

