
        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/08-legacy-itinerary.css · 2026-09-21  (prepended)
           16 rules. DESIGN.md: a surface's rules go in that surface's part, and the
           legacy parts only shrink.
           ⚠️ 08-legacy-itinerary.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 #40 and #7 the order is reversed by this move, and
           whether that matters was a separate question somebody had to answer. See DESIGN.md.
           ══════════════════════════════════════════════════════════════ */
        /* Media fullscreen — a page on the stack (2026-09-02), was #mediaFullscreenModal.
           The viewer brings its own top bar, so the stack header is hidden; the page is
           the dark surface the modal used to be. */
        .pgstack-page.media-page { background: rgba(0, 0, 0, 0.95); }

        .pgstack-page.media-page .pgstack-header { display: none; }

        .pgstack-page.media-page .pgstack-body {
            position: relative;
            overflow: hidden;
            padding-bottom: 0;
        }


        .fullscreen-viewer {
            /* Fills the page body, not the viewport. It was position:fixed at 100vw ×
               100vh; inside a transformed .pgstack-page fixed resolves against the page
               anyway, and 100vw would then overflow the 430px frame on desktop. */
            position: absolute;
            inset: 0;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
        }


        .fullscreen-image-container {
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 60px;
            overflow: hidden;
            position: relative;
            touch-action: none;
            /* 禁用默认触摸行为 */
        }


        .fullscreen-image {
            max-width: 100%;
            max-height: 100%;
            width: auto;
            height: auto;
            object-fit: contain;
            border-radius: 0;
            user-select: none;
            cursor: grab;
            transition: transform 0.1s ease-out;
            transform-origin: center center;
        }


        .fullscreen-image.zoomed {
            cursor: grab;
        }


        .fullscreen-image.dragging {
            cursor: grabbing;
        }

        /* Caption + thumbnail strip (17b) */
        .fullscreen-footer {
            position: absolute;
            left: 0; right: 0; bottom: 0;
            padding: 16px 16px 16px;
            /* 阶段三 ②：可读性蒙版删除。①做完之后这块图上已无文字，
               蒙版的唯一职能（让压在图上的文字可读）随之消失（F2）。 */
            background: none;
            z-index: 10;
        }

        .fullscreen-caption { margin-bottom: 12px; }

        .fs-cap-title { font-family: var(--log-write); font-size: 19px; font-weight: 700; color: #F7F5EF; line-height: 1.15; }

        .fullscreen-thumbs { display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }

        .fullscreen-thumbs::-webkit-scrollbar { display: none; }

        .fs-thumb {
            flex: none; width: 46px; height: 46px; border-radius: 0; overflow: hidden;
            cursor: pointer; opacity: 0.5; border: 1px solid transparent; background: #2E2B26;
            transition: opacity 0.15s;
        }

        .fs-thumb img, .fs-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }

        .fs-thumb.active { opacity: 1; border-color: #F4F2EC; }

        /* ==========================================================================
           #AJ  The photograph opened alone: the album's viewer, in the deck log
           ==========================================================================
           Owner, 2026-09-13: 现在看下点开照片展示页面. The Wall's night plate, CLOSE and the count as words.

           ⚠️ AFTER THE PHONE'S OWN GALLERY (owner, 2026-09-14: 我想让照片展示面积更大 … 把linked activity 变成icon 放一起
           只留caption 不要背景 白色字 tap照片 或缩放时 这些东西都消失 只留照片; 把caption也变成图标 …). The card on the stock
           is gone:
           - the bar is CLOSE, the count and DELETE;
           - under the photograph, on the plate with no ground: the caption when its mark opens it, the strip, and the
             marks, the activity, the pin, the heart, the caption and Download (the owner's order);
           - tap the photograph, or zoom it, and the bar and everything under the photograph go, and the photograph is
             given the whole page. Tap again and they come back. Zoomed, a tap leaves the photograph alone.

           ⚠️ AND THE PAGE IS THREE ROWS, LAID OUT BY THE BROWSER (owner, the same day, with a photograph running under the
           strip: icon 横向居中 背景色一致 作为一个整体 画在一个胶囊里 主图片上下居中 下面的一排照片不要半透明). The
           photograph's room had been the whole page less the bar's and the foot's heights, measured by a script into
           padding, and the photograph held to it by a percentage max-height. The owner's own screen showed the
           photograph under the strip, which the harness never did, so neither half is trusted now: the bar, the
           photograph and the foot are the grid's three rows, and the photograph fills its row, fitted whole and at the
           middle by object-fit. Alone, its row spans all three. The marks are one capsule, at the middle, with one
           ground; the strip's photographs are whole, not see-through. The capsule is square-cornered, as everything
           else in this room is (owner: 胶囊要是方形的).

           Every rule is under .pgstack-page.media-page. The viewer's legacy rules (08, 19, 20, 22) name bare classes
           and three ids, and some load after this file; the page's two classes, and the id where a legacy rule names
           one, are what win. The page stack is on the token list in 02-auth.css, so --log-* resolves here. The night
           plate has no token of its own; the Wall's fallbacks are used, the same values. */

        .pgstack-page.media-page { background: var(--log-night-plate, #0B1220); }

        .pgstack-page.media-page .fullscreen-viewer {
            display: grid;
            grid-template-columns: minmax(0, 1fr);
            grid-template-rows: auto minmax(0, 1fr) auto;
            /* Stretched, not centred. 08-legacy-itinerary.css (deleted 2026-09-24) gave the viewer align-items: center from when it was a flex
               box; on a grid that centres each row's item in its row, and the photograph's room, with nothing in flow,
               shrank to nothing at the middle of its row. Caught by the harness, as a photograph drawn 0px tall. */
            align-items: stretch;
            justify-items: stretch;
            justify-content: normal;
            overflow: hidden;
            background: var(--log-night-plate, #0B1220);
        }

        /* ---- The photograph's room: the middle row, or the whole page alone ---- */
        .pgstack-page.media-page .fullscreen-image-container {
            grid-row: 2;
            grid-column: 1;
            position: relative;
            z-index: 1;
            display: block;
            width: auto;
            height: auto;
            min-height: 0;
            padding: 0;
            overflow: hidden;
        }
        /* The photograph fills its room and is fitted whole into it, at the middle. Its box is the room; what is drawn
           is the photograph at its own shape inside it. */
        .pgstack-page.media-page .fullscreen-image,
        .pgstack-page.media-page #fullscreenVideo {
            position: absolute;
            inset: 0;
            width: 100%;
            height: 100%;
            max-width: none;
            max-height: none;
            margin: 0;
            object-fit: contain;
            object-position: 50% 50%;
        }
        .pgstack-page.media-page .fullscreen-viewer.is-bare .fullscreen-image-container { grid-row: 1 / -1; }

        /* ⚠️ A PICTURE ON ITS WAY, AND ONE THAT NEVER CAME (review, 2026-09-14). While the next picture is on its way the
           plate is empty, not left showing the photograph before under the next one's marks; a file that fails says so
           in the plate's own words, and the browser's broken picture is not shown. */
        .pgstack-page.media-page .fullscreen-image.is-loading,
        .pgstack-page.media-page .fullscreen-image-container.is-failed #fullscreenImage { opacity: 0; }
        .pgstack-page.media-page .fs-plate-note {
            display: none;
            position: absolute;
            left: var(--sp-lg);
            right: var(--sp-lg);
            top: 50%;
            transform: translateY(-50%);
            margin: 0;
            text-align: center;
            font-family: var(--log-write);
            font-size: var(--fs-body);
            line-height: 1.5;
            color: var(--log-on-night-plate, #DCE9F8);
        }
        .pgstack-page.media-page .fullscreen-image-container.is-failed .fs-plate-note { display: block; }

        /* ---- The bar: Close, the count, Delete ----
           Three columns, the outer two equal, so the count is at the middle of the page. With space-between it sat
           wherever the two ends left it. */
        .pgstack-page.media-page .fullscreen-topbar {
            grid-row: 1;
            grid-column: 1;
            position: relative;
            top: auto;
            left: auto;
            right: auto;
            z-index: 2;
            display: grid;
            grid-template-columns: 1fr auto 1fr;
            align-items: center;
            gap: var(--sp-sm);
            min-height: 48px;
            /* The same both sides, or the two outer columns are equal inside a box that is not centred: 24 and 12 put
               the count 6px left. CLOSE and DELETE take their own 24px inset as a margin. */
            padding: env(safe-area-inset-top, 0px) var(--sp-sm) 0;
            background: none;
            pointer-events: auto;
            transition: opacity 0.2s ease, visibility 0s linear 0s;
        }

        .pgstack-page.media-page .fs-close,
        .pgstack-page.media-page .fs-word.fs-delete {
            display: inline-flex;
            align-items: center;
            min-height: 44px;
            padding: 0;
            border: none;
            background: none;
            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;
            cursor: pointer;
        }
        .pgstack-page.media-page .fs-close {
            justify-self: start;
            margin-left: calc(var(--sp-lg) - var(--sp-sm));
        }
        /* DELETE is in the plate's light, as CLOSE is, not port: port on the night plate is about 3.1:1, under what
           micro capitals need, and the dialog it opens is where the deletion is said. */
        .pgstack-page.media-page .fs-word.fs-delete {
            justify-self: end;
            margin-right: calc(var(--sp-lg) - var(--sp-sm));
        }

        .pgstack-page.media-page .fullscreen-counter {
            padding: 0;
            background: none;
            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;
        }

        /* ---- The last row: the caption, the strip, the capsule ---- */
        .pgstack-page.media-page .fullscreen-footer {
            grid-row: 3;
            grid-column: 1;
            position: relative;
            left: auto;
            right: auto;
            bottom: auto;
            z-index: 2;
            padding: 0 0 env(safe-area-inset-bottom, 0px);
            border: none;
            background: none;
            transition: opacity 0.2s ease, visibility 0s linear 0s;
        }

        /* Tapped or zoomed: the bar and the foot go, and cannot be pressed while they are gone. Visibility waits for
           the fade, so they are not snatched away. They keep their rows, which the photograph now spans. */
        .pgstack-page.media-page .fullscreen-viewer.is-bare .fullscreen-topbar,
        .pgstack-page.media-page .fullscreen-viewer.is-bare .fullscreen-footer {
            opacity: 0;
            visibility: hidden;
            pointer-events: none;
            transition: opacity 0.2s ease, visibility 0s linear 0.2s;
        }

        /* The caption, opened by its mark: the words in the plate's light, three lines at most, with nothing behind
           them; pressed, the field that writes them, ruled under in the same light. Shut, it takes no room. */
        .pgstack-page.media-page .fullscreen-caption {
            display: none;
            margin: 0;
            padding: var(--sp-2xs) var(--sp-lg) 0;
            background: none;
        }
        .pgstack-page.media-page .fullscreen-footer.is-caption-open .fullscreen-caption { display: block; }
        .pgstack-page.media-page #fsCapTitle[hidden],
        .pgstack-page.media-page #fsCapInput[hidden] { display: none; }
        .pgstack-page.media-page #fsCapTitle {
            display: -webkit-box;
            -webkit-box-orient: vertical;
            -webkit-line-clamp: 3;
            overflow: hidden;
            padding: 10px 0;
            background: none;
            font-family: var(--log-write);
            font-size: var(--fs-body);
            font-weight: 400;
            line-height: 1.5;
            letter-spacing: 0;
            color: var(--log-on-night-plate, #DCE9F8);
            cursor: pointer;
        }
        /* A remembered value fills on the night, in the night's ink (02-auth.css draws the fill; the ground is the
           room's to say). On the viewer and not the field: the viewer is what paints the night under it. */
        .pgstack-page.media-page .fullscreen-viewer {
            --fill-ground: var(--log-night-plate, #0B1220);
            --fill-ink: var(--log-on-night-plate, #DCE9F8);
        }
        .pgstack-page.media-page #fsCapInput {
            display: block;
            box-sizing: border-box;
            width: 100%;
            min-height: 44px;
            margin: 0;
            padding: 10px 0 9px;
            border: none;
            border-bottom: 1px solid rgba(220, 233, 248, 0.45);
            border-radius: 0;
            background: none;
            resize: none;
            /* Five lines, then it scrolls inside itself (review, 2026-09-14). It grew to its whole content, and 500
               characters took the photograph's row. */
            max-height: calc(1.5em * 5 + 19px);
            overflow-y: auto;
            font-family: var(--log-write);
            font-size: var(--fs-body);
            line-height: 1.5;
            color: var(--log-on-night-plate, #DCE9F8);
            caret-color: var(--log-on-night-plate, #DCE9F8);
        }
        .pgstack-page.media-page #fsCapInput::placeholder { color: rgba(220, 233, 248, 0.6); opacity: 1; }
        .pgstack-page.media-page #fsCapInput:focus { outline: none; border-bottom-color: var(--log-on-night-plate, #DCE9F8); }

        .pgstack-page.media-page .fullscreen-thumbs {
            display: flex;
            gap: 2px;
            padding: var(--sp-xs) var(--sp-lg) 0;
        }
        /* Whole, not see-through (owner, 2026-09-14: 下面的一排照片不要半透明). The one open is ruled round. */
        .pgstack-page.media-page .fs-thumb {
            /* A button now (review, 2026-09-14), with none of a button's own dressing. */
            flex: none;
            margin: 0;
            padding: 0;
            -webkit-appearance: none;
            appearance: none;
            cursor: pointer;
            width: 46px;
            height: 46px;
            border: none;
            background: rgba(220, 233, 248, 0.08);
            opacity: 1;
        }
        .pgstack-page.media-page .fs-thumb img.is-failed { visibility: hidden; }
        .pgstack-page.media-page .fs-thumb.active {
            opacity: 1;
            outline: 2px solid var(--log-on-night-plate, #DCE9F8);
            outline-offset: -2px;
        }
        /* ⚠️ A THUMBNAIL, FOCUSED (review of the focus round, 2026-09-15). Stepped outside, its outline was cut by the strip;
           drawn inside, it would be one colour on a photograph. The mark is a layer above the picture, the plate's light ink
           outside its dark, in the thumbnail's own box; the open one's ring gives way to it while focused. */
        .pgstack-page.media-page .fs-thumb { position: relative; }
        .pgstack-page.media-page .fs-thumb:focus-visible { outline: 2px solid transparent; outline-offset: -2px; }
        .pgstack-page.media-page .fs-thumb:focus-visible::after {
            content: '';
            position: absolute;
            inset: 0;
            z-index: 3;
            pointer-events: none;
            border: 2px solid var(--log-on-night-plate, #DCE9F8);
            box-shadow: inset 0 0 0 2px var(--log-night-plate, #0B1220);
        }

        /* ⚠️ THE MARKS, ONE CAPSULE, SQUARE (owner, 2026-09-14: icon 横向居中 背景色一致 作为一个整体 画在一个胶囊里; then, of
           the round-ended one first drawn: 胶囊要是方形的). One ground for
           all five, at the middle of the page; no mark has a ground of its own inside it. Each is a 44px press around a
           20px glyph in the plate's light. On, a dot under the glyph, and the pin and the heart filled: the heart in
           magenta, its outline kept in the plate's light so it does not sink into the capsule. Download has no on. */
        .pgstack-page.media-page .fs-marks {
            display: flex;
            width: max-content;
            gap: var(--sp-xs);
            margin: var(--sp-sm) auto var(--sp-sm);
            padding: 2px var(--sp-sm);
            border-radius: 0;
            background: rgba(220, 233, 248, 0.12);
        }
        .pgstack-page.media-page .fs-mark {
            position: relative;
            flex: none;
            width: 44px;
            height: 44px;
            padding: 0;
            border: none;
            border-radius: 0;
            background: none;
            color: var(--log-on-night-plate, #DCE9F8);
            cursor: pointer;
        }
        .pgstack-page.media-page .fs-mark svg { position: absolute; top: 10px; left: 12px; width: 20px; height: 20px; }
        .pgstack-page.media-page .fs-mark:active { transform: scale(0.92); }
        .pgstack-page.media-page .fs-pin.is-pinned::after,
        .pgstack-page.media-page .fs-activity.is-linked::after,
        .pgstack-page.media-page .fs-caption.is-captioned::after,
        .pgstack-page.media-page .fs-fav.fav-toggle.is-fav::after {
            content: '';
            position: absolute;
            left: 20px;
            bottom: 4px;
            width: 4px;
            height: 4px;
            border-radius: 50%;
            background: var(--log-on-night-plate, #DCE9F8);
        }
        .pgstack-page.media-page .fs-pin.is-pinned .fs-pin-body { fill: currentColor; }
        .pgstack-page.media-page .fs-fav.fav-toggle.is-fav { color: var(--log-magenta); opacity: 1; }
        .pgstack-page.media-page .fs-fav.fav-toggle.is-fav svg path { stroke: var(--log-on-night-plate, #DCE9F8); }

        /* A diary photograph is not the journey's: no caption and no strip, and of the marks only Download. */
        .pgstack-page.media-page .fullscreen-footer.is-diary .fullscreen-thumbs,
        .pgstack-page.media-page .fullscreen-footer.is-diary .fullscreen-caption { display: none; }

        .pgstack-page.media-page :focus-visible {
            outline: 2px solid var(--log-on-night-plate, #DCE9F8);
            outline-offset: 2px;
        }
        /* ⚠️ THE PHOTOGRAPH'S ROOM, FOCUSED (finish review of the focus round, 2026-09-14). Its own outline is painted under the
           photograph, which is positioned inside it and reaches its edges; and light ink on a bright sky is no mark at all.
           The frame is drawn on a layer above the photograph, the plate's light ink outside its dark, so one reads. */
        .pgstack-page.media-page .fullscreen-image-container:focus-visible {
            outline: 2px solid transparent;
            outline-offset: -2px;
            box-shadow: none;   /* the app's role=button inset box, painted under the photograph */
        }
        .pgstack-page.media-page .fullscreen-image-container:focus-visible::after {
            content: '';
            position: absolute;
            inset: 0;
            z-index: 2;
            pointer-events: none;
            border: 2px solid var(--log-on-night-plate, #DCE9F8);
            box-shadow: inset 0 0 0 2px var(--log-night-plate, #0B1220);
        }

        @media (prefers-reduced-motion: reduce) {
            .pgstack-page.media-page .fullscreen-topbar,
            .pgstack-page.media-page .fullscreen-footer,
            .pgstack-page.media-page .fullscreen-viewer.is-bare .fullscreen-topbar,
            .pgstack-page.media-page .fullscreen-viewer.is-bare .fullscreen-footer { transition: none; }
        }

        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/19-legacy-media.css · 2026-09-21  (appended)
           10 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 22 part(s)
           between #19 and #42 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.
           ══════════════════════════════════════════════════════════════ */
        /* Sans, not the display serif. A caption here is a note about the photo, not a
           heading — the serif read as a title and competed with the image. Kept at 0.9rem
           so it sits below the app's body text and stays out of the way. */
        #fsCapTitle {
            font-family: var(--log-write);
            font-size: 15px;
            font-weight: var(--fw-regular);
            color: var(--log-on-oilskin);
            line-height: 1.45;
            letter-spacing: 0.01em;
        }

        /* Top right, matching the grid tiles — the eye looks for it in one place. */

        .fs-fav:active { transform: scale(0.92); }

        /* ===== Fullscreen viewer layout =====
           The viewer had no background, so the map and the Media grid showed through
           behind the photo — which is what made it read as cluttered rather than as a
           lightbox. And the image was sized against the full viewport height while a
           footer sat over its lower third, so a tall photo ran underneath the thumbnail
           strip. Both fixed here. */
        .fullscreen-viewer {
            background: #17150F;
        }

        /* The prev/next chevrons used to sit at left:20px and right:20px, over the photo's
           edges — so the image was effectively inset by ~76px on each side even though
           nothing constrained its width. With them gone it can use the full frame; swipe
           and the arrow keys cover navigation. */
        .fullscreen-image,
        #fullscreenVideo {
            max-width: 100vw;
            max-height: calc(100vh - 190px);
            margin-bottom: 96px;
        }


        .fullscreen-footer {
            /* Solid at the bottom rather than a gradient over the photo: with a dark
               backdrop the gradient no longer separates anything, and the thumbnails
               need a stable surface to sit on. */
            background: rgba(23, 21, 15, 0.94);
            border-top: 1px solid rgba(240, 237, 231, 0.09);
        }


        .fullscreen-thumbs {
            padding-bottom: 0;
        }

        /* ===== Fullscreen top bar =====
           A single flex row. Previously five elements were each absolutely positioned at
           a hand-computed offset (right: 20 / 76 / 132, plus a centred counter). Those
           numbers were tuned against one screen width, so on a narrower phone the star
           slid under its neighbours and disappeared. Flexbox computes the spacing, so it
           holds at any width and a sixth control can be added without arithmetic. */
        .fullscreen-topbar {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            z-index: 20;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: var(--sp-sm);
            padding: var(--sp-md) var(--sp-md);
            /* Fades into the photo rather than sitting on a bar, so nothing is cropped. */
            /* 阶段三 ②：可读性蒙版删除。①做完之后这块图上已无文字，
               蒙版的唯一职能（让压在图上的文字可读）随之消失（F2）。 */
            background: none;
            pointer-events: none;   /* let taps through the gradient itself */
        }


        .fullscreen-topbar > * { pointer-events: auto; }


        .fullscreen-counter {
            font-family: var(--log-write);
            font-size: var(--fs-small);
            color: var(--log-on-night-plate);
            background: rgba(23, 21, 15, 0.52);
            padding: 4px 8px;
            border-radius: 0;
            font-variant-numeric: lining-nums tabular-nums;
            white-space: nowrap;
        }

            .fullscreen-topbar { gap: var(--sp-2xs); padding: var(--sp-sm); }

        /* ⚠️ CARRIED FROM css/19-legacy-media.css ON ITS DELETION, 2026-09-21 — the reasoning
           behind #fsCapTitle and the viewer footer, which moved into this part without it. */
/* The caption had no font rule of its own, and nothing in its ancestor chain
           (.fullscreen-viewer, .fullscreen-footer) declared one either — so it inherited
           all the way to <body> and resolved to whatever each platform picks by default.
           That is why it looked different on the phone and the computer. Declared here. */

/* Leave room for the footer (caption + thumbnail strip) instead of letting the
           photo run under it. calc keeps it responsive to the footer's real height. */
