        /* ── carried from css/03-legacy-home-nav.css on 2026-09-22, as that part was deleted.
           It describes the hero and profile card, whose rules are all in this file now. ── */
        /* ==========================================
           Home hero (map) + profile card — sits between the header and the existing
           Upcoming/Current/Past tabs on mainPage. Deliberately doesn't touch that tabs/
           tripsList logic at all; this is purely a new section above it.
           ========================================== */


        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/03-legacy-home-nav.css · 2026-09-21  (prepended)
           7 rules. DESIGN.md: a surface's rules go in that surface's part, and the
           legacy parts only shrink.
           ⚠️ 03-legacy-home-nav.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 #9 and #3 the order is reversed by this move, and
           whether that matters was a separate question somebody had to answer. See DESIGN.md.
           ══════════════════════════════════════════════════════════════ */
        /* 封面角上的两个按钮。压在夜色图版上，所以取 --on-dark；
           不加底板不加描边 —— 图版本来就深，描一圈反而多出一个方框。
           44 见方是可见尺寸（与 §26 关闭按钮同口径）。 */
        .hero-corner-btn {
            position: absolute;
            top: 8px;
            width: 44px;
            height: 44px;
            padding: 0;
            border: none;
            border-radius: 0;
            background: transparent;
            color: var(--log-on-oilskin);
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            z-index: 2;
        }

        .hero-corner-btn svg { width: 20px; height: 20px; }

        .hero-corner-left { left: 4px; }

        .hero-corner-right { right: 4px; }


        #homeHeroMap {
            position: absolute;
            inset: 0;
        }


        .home-hero-scrim {
            position: absolute;
            inset: 0;
            pointer-events: none;
            /* 批次 06：装饰性渐变删除。hero 里只有地图和这一层 scrim，profile card 是
               下方的兄弟节点 —— 没有任何文字压在它上面，所以它不承担可读性，是纯装饰。
               这一处没有「改平色」：inset:0 铺满，填任何不透明色都会把地图盖死。
               装饰性覆盖层一旦不能用渐变，它就没有内容了，正确处置是删掉背景。 */
        }

        .home-profile-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

        /* ⚠️ THESE WERE APPENDED TO THE FOOT OF THIS FILE FIRST, AND IT CHANGED HOME.
           css/10-legacy-home-sheet.css loads at position 10 and this part at 11, so every
           rule here used to come AFTER the ones below. Putting them at the foot inverted
           that: any tie between a moved rule and one of this part’s own resolved the other
           way. Six of Home’s nine photographs changed and the move was meant to be inert.
           They belong at the head, which is the place they had. (2026-09-21) */

        /* ══════════════════════════════════════════════════════════════
           MOVED OUT OF css/10-legacy-home-sheet.css · 2026-09-21  (prepended — see below)
           27 rules. DESIGN.md: a surface's rules go in that surface's part, and the
           legacy parts only shrink.
           ⚠️ 10-legacy-home-sheet.css loaded BEFORE this file. This placement keeps
           their order against THIS PART’S OWN rules and nothing else: against the 0 part(s)
           between #10 and #11 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.
           ══════════════════════════════════════════════════════════════ */

        /* ── carried by hand from the head of css/10-legacy-home-sheet.css, 2026-09-21 ──
           The mover takes the comment directly above a rule and nothing else, so a file’s
           own header is always left behind. This was that part’s header, and every rule it
           argues about is now below. Its 「作用域限定在 #mainPage 与 #tripsList」 is finally
           true of the file it sits in. */
        /* ══════════════════════════════════════════════════════════════
           Dashboard · Cereal 模式（无彩 + 双字体）
           作用域限定在 #mainPage 与 #tripsList —— 别处不动。

           「双字体」这一半已经成立：名字与数字走 Vollkorn，眉标与按钮走
           Hanken Grotesk。所以这一轮真正要做的是「无彩」。

           首页上唯一的彩色是 --primary(#4A3728) 那个暖棕 —— 它在别处表示
           「住宿 / 木 / 室内」和主色，放在首页只是「按钮该有个颜色」的惯性。
           换成 --text-primary 的墨黑：首页于是只有纸、墨、灰三样。

           这样做还顺手立了一条更硬的规则：**颜色只在表示类别时出现**
           （Expenses 的十色编码），凡是装饰性的彩色一律退成墨黑。
           首页没有类别，所以首页无彩。
           ══════════════════════════════════════════════════════════════ */
        /* 封面 = 扉页那一层
           两版都太白：第一版封面用纸面（三层同色），第二版用 --bg-primary（#E8E3D9，
           只比纸深 4%）。这一版把封面压到 #DED7C7 —— 这个值不是新造的，
           它就是这份代码里 .home-hero 原来的底色（第 625 行），
           迁移时被我换掉了，现在换回来。

           册子的做法：正文是纸，扉页/插页比正文深一档。封面因此是「插页」，
           球体是印在插页上的图版，下面的资料卡回到正文纸。
           三层各有其色，上半部分才有落点。 */

        /* ══════════════════════════════════════════════════════════════
           首页资料卡 · 对齐 Cereal 那一版
           统计原来是三列居中的仪表盘写法。Cereal 那版是一行手写体的账：
           「3 trips 6 countries 3 friends」—— 数字衬线、标签小写无衬线，
           左对齐排一行。居中三列会把三个数读成三个指标；排成一行读作一句话。
           ══════════════════════════════════════════════════════════════ */

        /* Past 卡：把名字移出图片。
           原来是 color:#fff 直接压在照片上，而 ::after 的蒙版是 background:none ——
           白字压任意照片、无底块无蒙版，是全库最险的一处可读性。
           阶段三 ① 已经对 Upcoming 卡做过图文分离（「只保留状态角标，且必须带不透明底块」），
           Past 卡漏了。这一轮补上，也正合 Cereal 那版的裸缩略图。 */

        /* 三格等分，中间竖线、下面横线 —— 表格的读法：三个格子是同一行账目。
           竖线用 --rule-soft（比横线浅一档）：竖线分格，横线收行，
           两条同色会把这一行读成一个方框。 */
        #mainPage .home-stats-row {
            display: flex;
            gap: 0;
            padding: 0;
            margin: 10px 0 12px;
            border-bottom: 1px solid var(--log-rule);
        }

        #mainPage .home-stat {
            flex: 1 1 0;
            text-align: left;
            display: flex;
            align-items: baseline;
            gap: 5px;
            padding: 0 0 10px 12px;
        }

        #mainPage .home-stat:first-child { padding-left: 0; }

        #mainPage .home-stat + .home-stat { border-left: 1px solid var(--log-rule); }

        #mainPage .home-stat-num {
            font-family: var(--log-write);
            font-size: 19px;
            font-weight: 600;
            color: var(--log-ink);
            line-height: 1;
        }

        #mainPage .home-stat-label {
            font-size: 13px;
            letter-spacing: 0;
            text-transform: none;
            color: var(--log-ink-soft);
            margin-top: 0;
        }

        /* 头像写首字母，不画人形图标 —— Cereal 那版是一个方块 + 两个字母。
           人形图标是"没有头像"的意思；首字母是"这个人"。 */
        #mainPage .home-profile-avatar {
            width: 34px;
            height: 34px;
            background: var(--log-stock-band);
            color: var(--log-ink-soft);
            font-family: var(--log-write);
            font-size: 11px;
            letter-spacing: 0.06em;
            font-weight: 600;
        }

        /* css/03-legacy-home-nav.css gave this svg a 26x26, and that was the only thing of its
           rule still drawing — except it was not drawing, because of the line below. The size is
           not carried over with the rest on 2026-09-22; if this is ever un-hidden it wants one. */
        #mainPage .home-profile-avatar svg { display: none; }

        #mainPage .home-btn {
            /* ── carried from css/03-legacy-home-nav.css, 2026-09-22: the only declarations of
               that rule this one had not already re-declared. ── */
            gap: 8px;
            white-space: nowrap;
            background: transparent;
            letter-spacing: 0.12em;
        }

        #mainPage .home-btn-icon {
            width: 44px;
            /* ── carried from css/03-legacy-home-nav.css, 2026-09-22: the only declarations of
               that rule this one had not already re-declared. ── */
            flex: none;
        }

        /* ══════════════════════════════════════════════════════════════
           可推的面板
           封面固定在**展开后的高度**，地球按那个高度居中画一次；面板叠在它上面，
           上下推动改变的只是面板的位置 —— 地球一帧都不重画。
           （另一种写法是拖动时改封面高度并重算投影，那要每帧重投 5,562 个点。）

           两个停靠位：
             收起 = 面板上沿贴在封面下沿上方 —— 地球被裁掉下半，露出下面的行程
             展开 = 面板上沿降到 Add a Trip 那行 —— 整个地球可见，顶部露出刊名
           ══════════════════════════════════════════════════════════════ */
        #mainPage {
            position: relative;
            /* 面板靠 margin-top 定位，而父元素没有边框也没有内边距时，
               子元素的上外边距会与父元素**合并** —— 结果不是面板往下移，
               而是 #mainPage 连同绝对定位的封面一起往下移，顶上露出页框的白底，
               封面反而盖住了面板。

               先试的是 display: flow-root，但 showPage() 会写 element.style.display，
               内联样式赢过样式表，flow-root 当场失效。
               改用一条透明的上边框：只要父元素有边框，外边距就不再合并 ——
               这条不依赖 display，谁也覆盖不掉。 */
            /* 用内边距而不是边框：透明边框会把父元素的白底透出来，
               在深色封面顶上就是一条白线（你截图里那条）。0.05px 内边距同样
               阻止外边距合并，而且没有任何可见厚度。 */
            padding-top: 0.05px;
        }

        /* 封面改成**粘性**，并且留在文档流里。
           原来是绝对定位：页面一滚，封面就跟着滚走 —— 顶上先没的是刊名和北极，
           而想要的是南极先被盖住。绝对定位那条路无论怎么钉 scrollTop 都治不了根，
           因为封面本身在滚。

           sticky + 面板负外边距的组合才对：
             · 封面钉在视口顶端不动
             · 面板压在它上面（负外边距决定盖住多少）
             · 往上滚 / 往上推 → 面板上移 → 封面从**下缘**被吃掉
           滚动本身就成了这个动作。 */
        #mainPage .home-hero {
            /* ── carried from css/03-legacy-home-nav.css, 2026-09-22: the only declarations of
               that rule this one had not already re-declared. ── */
            min-height: 260px;
            overflow: hidden;
            position: sticky;
            top: 0;
            /* 封面高度：面板展开时它自己的上半（抓手+姓名+统计+按钮，约 164px）
               仍然盖在封面上，所以"整球可见"要求 封面高 − 164 ≥ 球径。 */
            height: var(--hero-full, 86vh);
            max-height: none;
            z-index: 0;
        }

        /* 刊名只在面板推下去时出现。
           收起状态下它在球的上方、离面板很远，读起来像页眉而不像封面题名；
           推下去时它是"整张封面"的一部分，才成立。 */
        #mainPage .home-hero-title {
            position: absolute;
            top: 18px; left: 0; right: 0;
            text-align: center;
            /* The cover stamp, in the same voice as the sign-in sheet's. It was still in
               the outgoing world's serif, which only showed once the sheet was pushed
               down — so the one moment this screen reveals its own name was the one
               moment it spoke in the old world's accent. */
            font-family: var(--log-print);
            font-size: var(--fs-body);
            font-weight: 600;
            letter-spacing: 0.18em;
            text-transform: uppercase;
            color: #DCE9F8;
            opacity: 0;
            transition: opacity 0.28s ease;
            pointer-events: none;
        }

        #mainPage.sheet-open .home-hero-title {
            opacity: 0.9;
        }

        /* 负外边距 = 盖住封面多少。0 就是完全不盖（整球可见）。 */
        #mainPage .home-sheet {
            position: relative;
            z-index: 1;
            background: var(--log-stock);
            border-top: 1px solid var(--log-rule);
            margin-top: calc(-1 * var(--sheet-cover, 220px));
            transition: margin-top 0.32s cubic-bezier(.22,.61,.36,1);
            min-height: 70vh;
        }

        #mainPage .home-sheet.is-dragging {
            transition: none;
        }

        /* 抓手：一条 26px 的短线，44px 高的命中区（手段 1，不是新机制） */
        #mainPage .home-sheet-grip {
            display: block;
            width: 100%;
            min-height: 44px;
            padding: 0;
            border: none;
            border-radius: 0;
            background: transparent;
            cursor: grab;
            touch-action: none;
            position: relative;
        }

        #mainPage .home-sheet-grip::before {
            content: '';
            position: absolute;
            left: 50%; top: 50%;
            transform: translate(-50%, -50%);
            width: 26px; height: 1px;
            background: var(--log-rule-firm);
        }

        #mainPage .home-sheet-grip:active { cursor: grabbing; }

        #mainPage .home-profile-card { padding-top: 0; }

        /* 封面 = 插页图版（夜色）
           这一版按你选的方向来：深底、大气辉光、浅蓝标记 —— 也就是对比图里的 1c。
           它与这一套的无彩规则确实张力很大，所以处理成一张**图版**：
           边界干净利落，不做渐变过渡，下面立刻回到正文纸。
           册子里插一张深色图版是成立的；把深色蔓延到正文就不成立。 */
        #mainPage .home-hero {
            background: #0B1220;
            border-bottom: 1px solid #0B1220;
        }

        #mainPage .home-profile-avatar {
            color: var(--log-ink-soft);
        }

        #mainPage .home-btn {
            border-color: var(--log-ink);
            color: var(--log-ink);
        }

        #mainPage .home-btn-primary {
            /* ── carried from css/03-legacy-home-nav.css, 2026-09-22: the only declarations of
               that rule this one had not already re-declared. ── */
            flex: 1;
            background: var(--log-oilskin);
            color: var(--log-on-oilskin);
        }

        #mainPage .home-btn-secondary {
            /* ── carried from css/03-legacy-home-nav.css, 2026-09-22: the only declarations of
               that rule this one had not already re-declared. ── */
            flex: 1;
            background: transparent;
            color: var(--log-ink);
        }

        #mainPage .home-btn-icon {
            border-color: var(--log-rule-firm);
            color: var(--log-ink-soft);
        }

        #tripsList .stop-detail-add-link {
            color: var(--log-ink);
        }

        /* ==========================================================================
           #B  Home — the deck log's own page
           ==========================================================================

           Second surface in the deck log world, 2026-09-03. The globe keeps the head:
           it pins the traveller's own geotagged photos where they were taken, so it is
           content, not a hero image, and it is not touched here.

           Below it the sheet is the log page. A trip appears as a cachet — the entry
           stamp for that journey — carrying its photo the way a stamp carries an
           engraving: small, inside the printed matter, never as the card. Live entries
           (current, planned) keep the sheet's margin column and are ruled rows. Past
           trips leave the ruling and become a collected page, two up, because that is
           what a page of stamps actually looks like once the journeys are over.

           Tokens come from the block at the top of the auth section; this surface adds
           no colour, face or rule weight of its own. */

        /* ---- The room head: one, for every room off Home ---------------------- */

        /* ⚠️ ONE HEAD, WRITTEN ONCE (owner, 2026-09-16: 保持独立 共用一个头部).

           The Rooms-off-Home Rule in DESIGN.md is one specification — a sticky head on the
           stock, Back as an underlined word at the left with no arrow, the title centred in
           print caps, at most one action at the right, a firm rule under — and it had been
           implemented fifteen times: .wall-bar, .shelf-bar, .stats-bar, .nt-bar, .pf-bar,
           .pe-bar, .bo-bar, .fr-bar, .tk-bar, .sy-bar, .st-bar, .ab-bar, .hp-bar, .wm-bar,
           .sv-bar, each in its room's own stylesheet, each carrying the same seven lines for
           the bar, the same ten for the title and the same sixteen for Back. Read side by
           side they were copies, not variations. The cost of copies is that one gets
           changed and fourteen do not: a tap-target sweep the same day measured fourteen
           Back words at 33 × 44, one per room, from fourteen class names.

           The rooms stay independent — they are not on the page stack, their doors are their
           own, and every Back keeps its own onclick, because Sync must stop first, the Tool
           kit must stop its clock and Password returns to Settings. What they share is the
           head, and it is here. Each room's stylesheet keeps only what is really its own:

             - HOW THE HEAD STAYS PUT. `position: sticky; top: 0; z-index: 2` where the whole
               page scrolls (the Wall, the Shelf, the Register, Notifications, the Profile), or
               `flex: none` where the page is a column with its own scroller inside it. That is
               the page's structure, not the head's style, so it stays with the page.
             - A FLOOR HEIGHT, on Boswell and Saved, whose side cells can both be empty.
             - THE REGISTER'S `space-between`, the rule's one written variant: no title,
               because the Register's title is the head that follows.

           The markup carries both classes — `room-bar st-bar` — so a room can still say
           something about its own head, and this block, being a single class, is outranked
           by any room rule that does. Tokens resolve here exactly as they did in the room
           rules: the same elements read the same variables. */
        .room-bar {
            display: grid;
            grid-template-columns: 1fr auto 1fr;
            align-items: center;
            gap: var(--sp-sm);
            padding: calc(var(--sp-2xs) + env(safe-area-inset-top, 0px)) var(--sp-lg) var(--sp-2xs);
            background: var(--log-stock);
            border-bottom: 1px solid var(--log-rule-firm);
        }

        /* ⚠️ NO `> :first-child` / `> :last-child` ALIGNMENT HERE, AND THAT WAS MEASURED, NOT
           ASSUMED. The first version of this block carried both, on the reasoning that every
           room outranks a single class so nothing could move. That reasoning is only true for
           properties a room rule sets. Nine rooms never set justify-self on their right cell,
           so the new rule reached them: their empty right-hand spans went from filling the
           1fr column to zero wide, which cannot be seen — and PASSWORD'S SAVE went from a
           142.8-wide box at x 263 to a 30.8-wide word at the right edge, which can, and which
           would also have cut its tap target from 143 to 31. A per-room snapshot of every
           head, taken before any of this, is what caught it.

           Password's Save not sitting at the right is a real departure from the rule ("at most
           one action at the right") — the other rooms' actions carry justify-self: end and
           Password's does not. But fixing it is a visible change and a smaller target, so it
           is recorded in DEBTS.md for the owner rather than made here under a heading that
           promises nothing moves. The four rooms that already aligned their cells this way
           (the Profile, Edit profile, Boswell, Saved) keep their own rules for it. */

        .room-title {
            margin: 0;
            font-family: var(--log-print);
            font-size: var(--fs-small);
            font-weight: 600;
            letter-spacing: 0.16em;
            text-transform: uppercase;
            line-height: 1.2;
            color: var(--log-ink);
            text-align: center;
            white-space: nowrap;
        }

        /* ⚠️ 44 BY 44, AND THE WORD DOES NOT MOVE (owner, 2026-09-16: back改吧). Back measured
           33 × 44 in every room — the word BACK is 32.6px wide, the box was the word, and
           PRODUCT.md commits to 44. WCAG 2.2 AA's 24 it always cleared; this product's own
           floor it did not.

           min-width, not padding, and the difference is the whole point. The box is a flex
           container starting its content at the left and it sits at the start of its 1fr
           cell, so a minimum width makes the box grow RIGHTWARD into the empty cell while the
           glyphs stay exactly where they were: measured in all fourteen rooms that draw a
           Back, the word's rectangle before and after was identical to the tenth of a pixel,
           nothing else in any head moved, and the hit test read 44 × 44 in every one. Padding
           would have pushed the word in from the margin column. The underline is the text's,
           so it still runs under the word and not under the empty part of the box. */
        .room-back {
            justify-self: start;
            display: inline-flex;
            align-items: center;
            min-height: 44px;
            min-width: 44px;
            padding: 0;
            background: none;
            border: none;
            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);
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
        }

        /* ---- The sheet over the globe ---------------------------------------- */

        #mainPage,
        #mainPage .home-sheet {
            background: var(--log-stock);
        }

        /* The most-looked-at line on the screen: the sheet's edge against the night
           plate. It was still the retiring world's warm grey border, run between the two
           coolest surfaces here. */
        #mainPage .home-sheet {
            border-top-color: var(--log-rule-firm);
        }

        /* Ink, not glow — the same focus mark the sign-in sheet uses. It was scoped to
           the auth pages only, so every control on this page (cachets, the four actions,
           the section links, the grip) fell back to the browser's default ring. */
        #mainPage :focus-visible {
            outline: 2px solid var(--log-ink);
            outline-offset: 2px;
        }

        /* The sheet's travel is authored motion and belongs with the rest of it. */
        @media (prefers-reduced-motion: reduce) {
            #mainPage .home-sheet {
                transition: none;
            }
        }

        #mainPage .home-sheet-grip::after,
        #mainPage .home-sheet-grip::before {
            background: var(--log-rule-firm);
        }

        /* ---- Identity, and the register line ---------------------------------- */

        #mainPage .home-profile-card {
            padding: 0;
            background: transparent;
            border: none;
        }

        #mainPage .home-profile-row {
            /* ── carried from css/03-legacy-home-nav.css, 2026-09-22: the only declarations of
               that rule this one had not already re-declared. ── */
            margin-bottom: var(--sp-xs);
            display: flex;
            align-items: center;
            gap: var(--sp-sm);
            padding: var(--sp-md) var(--sp-lg);
            border-bottom: 1px solid var(--log-rule-firm);
        }

        #mainPage .home-profile-avatar {
            width: 34px;
            height: 34px;
            flex: none;
            border: 1px solid var(--log-rule-firm);
            border-radius: 0;
            background: var(--log-stock-band);
            color: var(--log-ink);
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.08em;
            display: flex;
            align-items: center;
            justify-content: center;
            overflow: hidden;
        }

        #mainPage .home-profile-name {
            font-family: var(--log-print);
            font-size: var(--fs-lg);
            font-weight: 700;
            letter-spacing: 0.04em;
            text-transform: uppercase;
            color: var(--log-ink);
            line-height: 1.1;
        }

        /* The three counts stay — they are real on a signed-in account. They stop being
           a floating metric row and become the register line: three ruled cells reading
           what this log contains, in the same cell grammar as the sign-in sheet's
           column heads. Value and unit sit on one baseline so the number is read as a
           quantity of something, not as a score. */
        #mainPage .home-stats-row {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            border-bottom: 1px solid var(--log-rule-firm);
            margin: 0;
            padding: 0;
        }

        #mainPage .home-stat {
            display: flex;
            align-items: baseline;
            justify-content: center;
            gap: 0.4em;
            padding: var(--sp-sm) var(--sp-2xs);
            font-family: var(--log-print);
            border: none;
        }

        #mainPage .home-stat + .home-stat {
            border-left: 1px solid var(--log-rule);
        }

        #mainPage .home-stat-num {
            /* The printed face, as its label beside it. Without a face of its own the figure took
               10-legacy-home-sheet.css's --font-serif: Vollkorn, the outgoing world's, on Home (2026-09-17, what the outgoing world still painted (capture --legacy)). */
            font-family: var(--log-print);
            font-size: var(--fs-base);
            font-weight: 700;
            color: var(--log-ink);
            font-variant-numeric: tabular-nums;
        }

        #mainPage .home-stat-label {
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--log-ink);
        }

        /* ---- Actions ---------------------------------------------------------- */

        /* Two words and two marks on one line: the words share what is left after the
           two 44px squares are set aside. It broke into two lines on 2026-09-06, when
           the Almanac made a third mark and four columns stopped fitting; the Almanac
           came down on 2026-09-07 and the row is the one row again. */
        #mainPage .home-actions-row {
            display: grid;
            grid-template-columns: 1fr 1fr auto auto;
            gap: var(--sp-xs);
            padding: var(--sp-md) var(--sp-lg);
            border-bottom: 1px solid var(--log-rule-firm);
        }

        #mainPage .home-btn {
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            border-radius: 0;
            min-height: 44px;
            padding: 0 var(--sp-sm);
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        #mainPage .home-btn-primary {
            background: var(--log-oilskin);
            color: var(--log-on-oilskin);
            border: none;
        }

        #mainPage .home-btn-secondary,
        #mainPage .home-btn-icon {
            background: transparent;
            color: var(--log-ink);
            border: 1px solid var(--log-ink);
        }

        #mainPage .home-btn-icon {
            width: 44px;
            padding: 0;
        }

        #mainPage .home-btn-icon svg {
            stroke: currentColor;
        }

        /* ---- Section heads ----------------------------------------------------- */

        #mainPage .log-section {
            display: flex;
            align-items: center;
            justify-content: space-between;
            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);
        }

        #mainPage .log-section-action {
            background: none;
            border: none;
            font: inherit;
            color: var(--log-magenta);
            text-decoration: underline;
            text-underline-offset: 0.25em;
            text-decoration-thickness: 1px;
            cursor: pointer;
            min-height: 44px;
            display: inline-flex;
            align-items: center;
            padding: 0;
        }

        /* ---- The cachet: a live entry, ruled --------------------------------- */

        #mainPage .log-cachet {
            display: grid;
            grid-template-columns: 6.5rem 1fr;
            width: 100%;
            text-align: left;
            background: transparent;
            border: none;
            border-bottom: 1px solid var(--log-rule);
            padding: 0;
            cursor: pointer;
            font: inherit;
            color: inherit;
        }

        #mainPage .log-cachet-mark {
            font-family: var(--log-print);
            font-size: var(--fs-micro);
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--log-ink);
            line-height: 1.25;
            background: var(--log-stock-band);
            border-right: 1px solid var(--log-rule-firm);
            display: flex;
            flex-direction: column;
            justify-content: center;
            gap: 2px;
            padding: var(--sp-sm);
            min-height: 44px;
        }

        /* ⚠️ MAGENTA, NOT PORT (owner, 2026-09-09: current 这个红色不属于这个世界).

           Port is this world's colour for something WRONG — a refused sync line, a warning
           note, a form that will not submit, the warning toast. It was carrying CURRENT,
           which is not a warning about anything: it is a mark laid over a standing record
           saying which entry you are inside. That is the magenta's job, and has been since
           the rule was widened to "acts that change the record, and notices laid over it" —
           the same colour the nights scale spends on tonight and Home's empty state spends
           on SPECIMEN.

           Both the word and the edge. The 3px inset down the card's left is the same mark in
           another shape, and leaving it red would have made the card argue with itself. */
        #mainPage .log-cachet[data-state="current"] .log-cachet-mark {
            color: var(--log-magenta);
        }

        #mainPage .log-cachet[data-state="current"] {
            box-shadow: inset 3px 0 0 0 var(--log-magenta);
        }

        #mainPage .log-cachet-day {
            font-size: var(--fs-micro);
            font-weight: 400;
            letter-spacing: 0.08em;
            color: var(--log-ink-soft);
        }

        #mainPage .log-cachet-body {
            display: grid;
            grid-template-columns: 52px 1fr;
            align-items: center;
            gap: var(--sp-sm);
            padding: var(--sp-sm) var(--sp-md);
            min-height: 44px;
        }

        /* The engraving on the stamp. Squared, hairlined, never the card itself. */
        #mainPage .log-cachet-pic {
            width: 52px;
            height: 52px;
            flex: none;
            border: 1px solid var(--log-rule-firm);
            background: var(--log-stock-band) center/cover no-repeat;
        }

        /* These are spans inside a <button> — a button may not contain block-level
           children, so the stacking has to come from display, not from the tag. */
        #mainPage .log-cachet-body > span,
        #mainPage .log-cachet-place,
        #mainPage .log-cachet-dates,
        #mainPage .log-cachet-dur {
            display: block;
        }

        #mainPage .log-cachet-place {
            font-family: var(--log-print);
            font-size: var(--fs-body);
            font-weight: 600;
            letter-spacing: 0.06em;
            text-transform: uppercase;
            color: var(--log-ink);
            line-height: 1.2;
        }

        #mainPage .log-cachet-dates {
            font-family: var(--log-write);
            font-size: var(--fs-small);
            color: var(--log-ink);
            margin-top: 2px;
        }

        #mainPage .log-cachet-dur {
            font-family: var(--log-write);
            font-size: var(--fs-small);
            color: var(--log-ink-soft);
        }

        /* Marked by a tap on its pin. The mark is the sheet's own annotation colour,
           drawn as a rule down the margin edge — the same gesture the sign-in sheet uses
           to say "this row", not a highlight wash. */
        #mainPage .log-cachet.is-marked {
            box-shadow: inset 3px 0 0 0 var(--log-magenta);
            background: var(--log-stock-band);
        }

        #mainPage .log-collected .log-cachet.is-marked {
            box-shadow: inset 0 -3px 0 0 var(--log-magenta);
        }

        /* ---- The live entry, given the width it deserves --------------------- */
        /* A trip in progress or about to start is the most important thing on this page
           and it had the smallest picture on it — a 52px thumbnail, while finished trips
           got a full-width plate. The ruling still holds it: the state keeps its own
           ruled strip, the photograph spans the sheet, and the name and dates read
           underneath. What changes is which entry the page gives its width to. */

        #mainPage .log-cachet[data-state="current"],
        #mainPage .log-cachet[data-state="planned"] {
            grid-template-columns: 1fr;
        }

        #mainPage .log-cachet[data-state="current"] .log-cachet-mark,
        #mainPage .log-cachet[data-state="planned"] .log-cachet-mark {
            flex-direction: row;
            align-items: baseline;
            gap: var(--sp-sm);
            min-height: 0;
            padding: var(--sp-xs) var(--sp-md);
            border-right: none;
            border-bottom: 1px solid var(--log-rule);
        }

        #mainPage .log-cachet[data-state="current"] .log-cachet-body,
        #mainPage .log-cachet[data-state="planned"] .log-cachet-body {
            display: block;
            padding: 0;
        }

        /* Inset, not bled. A photograph running edge to edge is a magazine cover; on a
           ruled page it reads as the page having been cut off. Held clear of both margins
           it is a plate mounted on the sheet, which is what it is -- and the name below
           starts at the same left edge. */
        #mainPage .log-cachet[data-state="current"] .log-cachet-pic,
        #mainPage .log-cachet[data-state="planned"] .log-cachet-pic {
            width: auto;
            height: 168px;
            margin: var(--sp-md) var(--sp-md) 0;
            border: 1px solid var(--log-rule-firm);
        }

        /* Dates and duration read as one line, divided by a column rule.
           Stacked, they left the right two thirds of a full-width entry empty and made
           three lines of the two facts that answer one question — when, and for how long.
           The divider is the sheet's own device: the same hairline that separates the
           three cells of the register line above, not a pipe character standing in for
           one. Live entries only; a finished journey carries no duration. */
        #mainPage .log-cachet[data-state="current"] .log-cachet-place,
        #mainPage .log-cachet[data-state="planned"] .log-cachet-place {
            margin-bottom: 3px;
        }

        #mainPage .log-cachet[data-state="current"] .log-cachet-dates,
        #mainPage .log-cachet[data-state="planned"] .log-cachet-dates {
            display: inline;
            margin-top: 0;
        }

        #mainPage .log-cachet[data-state="current"] .log-cachet-dur,
        #mainPage .log-cachet[data-state="planned"] .log-cachet-dur {
            display: inline;
            margin-left: var(--sp-xs);
            padding-left: var(--sp-xs);
            border-left: 1px solid var(--log-rule-firm);
        }

        #mainPage .log-cachet[data-state="current"] .log-cachet-body > span:last-child,
        #mainPage .log-cachet[data-state="planned"] .log-cachet-body > span:last-child {
            display: block;
            padding: var(--sp-sm) var(--sp-md);
        }

        /* ---- Past trips: the collected page ---------------------------------- */
        /* Journeys that are over leave the ruling. A finished page of a stamp album is
           a grid of cachets, not a list of rows, and nothing on it is pending. */

        #mainPage .log-collected {
            display: grid;
            grid-template-columns: 1fr 1fr;
            border-bottom: 1px solid var(--log-rule-firm);
        }

        /* A column, so that however many lines a name takes the plates still line up
           along the top and the dates along the foot. A grid of stamps that does not
           align is just a pile. */
        #mainPage .log-collected .log-cachet {
            display: flex;
            flex-direction: column;
            grid-template-columns: none;
            background: var(--log-stock);
            padding: var(--sp-sm);
            border-right: 1px solid var(--log-rule);
            border-bottom: 1px solid var(--log-rule);
        }

        #mainPage .log-collected .log-cachet:nth-child(2n) {
            border-right: none;
        }

        /* align-items has to be reset, not merely inherited past. The row version of
           this body centres its two columns vertically; turned on its side into a column,
           that same centre pulls every name and date into the middle of the card, which
           is what was happening -- text centred by a rule written for an axis that no
           longer exists. */
        #mainPage .log-collected .log-cachet-body {
            display: flex;
            flex-direction: column;
            align-items: stretch;
            flex: 1;
            padding: 0;
        }

        /* Pushed to the foot so every date in the grid sits on one line. */
        #mainPage .log-collected .log-cachet-dates {
            margin-top: auto;
            padding-top: var(--sp-2xs);
        }

        #mainPage .log-collected .log-cachet-pic {
            width: 100%;
            height: 88px;
        }

        /* The strike is gone. It was meant to read as a ledger closing an entry, but
           on a name it reads as a correction — as though the journey were cancelled
           rather than completed. Past tense is already carried by the section it sits
           under and by the dates beneath it; nothing needs to be crossed out. */
        #mainPage .log-collected .log-cachet-place {
            margin-top: var(--sp-xs);
        }

        /* ---- The specimen ------------------------------------------------------ */
        /* Shown only when the log has no entries at all. It is a printed sample, not
           the traveller's data: nothing is written to the account, and it disappears
           the moment a real trip exists. Marked so it can never be mistaken for one. */

        /* Full strength, on its own ground.
           It was set at 0.55, which put the sample entry near 2.8:1 on the stock and the
           word SPECIMEN — the mark that makes the whole device honest — near 2:1. A
           demonstration entry has to be legibly marked as one, and fading it attacks the
           marking before it attacks the sample. What says "not yours" is the band it sits
           on, the hatched plate where a photograph would be, the stamp across it and the
           sentence above it; none of those need to be dim to be read as a sample. */
        #mainPage .log-specimen {
            position: relative;
            background: var(--log-stock-band);
            pointer-events: none;
        }

        #mainPage .log-specimen .log-cachet-pic {
            background: repeating-linear-gradient(45deg,
                    var(--log-stock-band) 0 6px,
                    var(--log-stock) 6px 12px);
        }

        #mainPage .log-specimen-stamp {
            position: absolute;
            top: 50%;
            left: 6.5rem;
            transform: translateY(-50%) rotate(-4deg);
            margin-left: var(--sp-md);
            font-family: var(--log-print);
            font-size: var(--fs-body);
            font-weight: 700;
            letter-spacing: 0.28em;
            text-transform: uppercase;
            color: var(--log-magenta);
            border: 2px solid var(--log-magenta);
            padding: 2px var(--sp-xs);
        }

        #mainPage .log-empty-note {
            font-family: var(--log-write);
            font-size: var(--fs-small);
            color: var(--log-ink);
            line-height: 1.5;
            padding: var(--sp-md) var(--sp-lg);
            border-bottom: 1px solid var(--log-rule);
        }

        /* ---- Unwritten rows to the foot --------------------------------------- */

        #mainPage .log-fill {
            min-height: calc(var(--log-row) * 2);
        }

        /* The corner buttons stand on the night plate or a photograph, where ink is 1.6:1: the plate's light ink (focus
           audit, 2026-09-14). */
        #mainPage .hero-corner-btn:focus-visible {
            outline: 2px solid var(--log-on-night-plate, #DCE9F8);
            outline-offset: 2px;
            /* Under the light ring, a dark one: over a bright photograph the light alone was no mark (review, 2026-09-15). */
            box-shadow: 0 0 0 4px var(--log-night-plate, #0B1220);
        }
