/* Boswell Journal — all styles. Extracted from the original single-file index.html. */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        /* CJK 回退的字面高对位。
           把 CJK 码位映射到系统里存在的中文字体，并缩放到与旁边的拉丁字面对齐。
           只用 local()，不下载任何东西。

           批次 05（补丁 A1）：原来一个 'CJK Adjusted' 族服务两套拉丁字体 —— 而它的
           local() 清单全是黑体，衬线那一侧其实一直配错。拆成两族：衬线配宋体、
           无衬线配黑体。

           批次 05b（补丁 F1）：对齐基准是拉丁**大写高**，不是 x-height。
           中英混排时方块字挨着的是大写字母和上伸部，x-height 是内部指标、
           从来不是视觉对齐面。A1 原写的 96% / 94% 已作废。

               size-adjust = capHeight(拉丁族) ÷ 中文字面高

           两个 cap-height 由 fontTools 从字体二进制读出（OS/2 sCapHeight，
           并用 'H' 字形轮廓交叉验证，两者一致）：
               Vollkorn        0.6760 em
               Hanken Grotesk  0.6970 em

           ⚠️ 除数不是 0.90。F1 原写 0.90，那是我在 Noto Serif SC / Noto Sans SC 上
           量的（0.9075 / 0.9095）—— 但 Noto **不是实际参与渲染的字体**。下面
           真机指纹比对确认命中的是 **SimSun 和 Microsoft YaHei** —— 自检页跑在
           Windows 上，src 里排前面的 Songti SC / PingFang SC / Noto Serif CJK SC /
           Source Han Serif SC 在那台机器上一个都不存在，命中的是列表靠后的两款。
           这两款的字面高比 Noto 矮得多，真机实测反推：

               SimSun          字面高 0.7663 em  → 0.6760 / 0.7663 = 88.2%
               Microsoft YaHei 字面高 0.8050 em  → 0.6970 / 0.8050 = 86.6%

           ⚠️ 这两个数只对命中 SimSun / Microsoft YaHei 的机器成立。macOS 上命中
           Songti SC / PingFang SC，字面高不同，size-adjust 会生效但缩放量偏 —— 
           比安卓的「完全不生效」更隐蔽。拆规则的方案见 MIGRATION-OPEN #21 / #24。

           所以除数是**每族各自的实测值**，不是一个共用常数。这也解释了为什么
           早前按 0.90 算出的 75% / 77% 偏小：分母用错了字体。

           这两个数是纯算得值，上调量 0（F1 允许 +0~+6% 的真机目视上调，未执行）。
           改这个数之前先确认 Rendered Fonts 里命中的中文字体没变 —— 换一款
           中文字体，分母就变了，这两个数也得跟着重算。
           校准页：scripts/verify-cjk-adjust.html */
        /* THE SERIF SIDE OF THE CJK FALLBACK WAS DECLARED HERE AND IS GONE (2026-09-25).

           @font-face ‘CJK Serif Adj’: Songti SC, Noto Serif CJK SC, Source Han Serif SC, SimSun,
           at size-adjust 94% — 88% computed (Vollkorn cap 0.6760 ÷ SimSun face height 0.7663,
           measured on Windows) plus the +6% the eye asked for, because a Chinese glyph fills its
           face where a Latin letter does not, so strict cap-height parity reads small.

           NOTHING NAMED IT. Not one font stack in public/css — only the comment at line 188
           explaining where such a face must sit. It died with Vollkorn on 2026-09-17: the serif
           went, --font-serif stopped being a serif, and the serif’s CJK companion was left
           declared and unreferenced. The browser agreed before I did — document.fonts reported
           it `unloaded` while ‘CJK Sans Adj’ beside it reported `loaded`.

           The measurement is kept because the NUMBER is the expensive part and the method
           outlives the face: divide by the actual Chinese font’s face height, not a shared
           constant, and re-derive if the family that wins changes. scripts/verify-cjk-adjust.html
           and scripts/specimen-15px-body.html are how it was judged. If this world ever sets a
           serif again, that is where to start rather than from nothing. */

        @font-face {
            font-family: 'CJK Sans Adj';
            src: local('PingFang SC'), local('Hiragino Sans GB'),
                 local('Microsoft YaHei'), local('Noto Sans CJK SC');
            /* 算得值 87%（Hanken cap 0.6970 ÷ Microsoft YaHei 字面高 0.8050 = 86.6，Windows 实测）
               上调量 +6%（F1 允许的上限），最终 93%。
               上调是眼睛定的。除了与衬线相同的「汉字撑满字面」之外，这一档还有一条：
               12px 是无衬线一族最小的内容档，汉字在小字号下笔画密度高、可辨性衰减比
               拉丁快，宁可偏大。
               判定材料：scripts/specimen-12px-ui.html（12px 界面文本中英混排，四档同排）。

               ⚠️ 与衬线那个数各自独立。两个都取 +6 是各自判出来的，不是同一个决定 ——
                  分母是两款不同的中文字体，判断场景也不同（长段落正文 vs 短词界面标签）。
                  将来任一侧要改，只改那一侧，不要合并成一个 token。 */
            size-adjust: 93%;
            unicode-range: U+2E80-9FFF, U+3000-303F, U+FF00-FFEF,
                           U+3400-4DBF, U+F900-FAFF;
        }

        :root {
            /* ══════════════════════════════════════════════════════════════
               THE OUTGOING WORLD’S PALETTE STOOD HERE AND IS GONE (2026-09-24).

               18 declarations: --primary, --primary-dark, --primary-light, --text-primary, --text-secondary, --text-light, --glyph-muted, --bg-primary, --bg-secondary, --border, --accent, --success, --danger, --rule-soft, --on-dark, --text-faint, --surface-sheet, --surface-field.

               Nothing in the stylesheets or the JavaScript names any of them any more. The last live
               one was not a colour on a rule — it was --fill-ground and --fill-ink in
               02-auth.css aliasing --surface-field and --text-primary for the activity form’s
               currency menu, which is only visible when the BROWSER autofills a remembered
               value. No fixture does that and no photograph ever caught it.


               (Writing the JS glob with a slash and a star inside a CSS comment would open a
               second comment here — which is why that sentence says "the JavaScript" instead.)
               WHAT STAYED, and why, because both look like palette from here:
                 --nav-h is a measurement, not a colour.
                 --person-1..9 and --person-none are the travellers’ own marks and are LIVE —
                 02-state.js:3761 builds their names by hand, `var(--person-` + n + `)`, from a
                 hash of the name. A search for var(--person-3) finds nothing, which is exactly
                 how a token that is still in use comes to look dead.
               ══════════════════════════════════════════════════════════════ */
            /* ── 米色底上只放得下两种文字颜色 · 2026-09-03 ─────────────────────
               --text-light 原来是 #8A8880,注释写着「decorative only」,可全库 226 处
               引用里有 200 处是 color: —— 它从来就是个文字颜色,只是没人承认。
               在 #E8E3D9 上它是 3.0:1,而正文的 AA 门槛是 4.5:1。

               把它抬到 4.5:1 之后有件事跑不掉:算出来的值是 ~#676560,和
               --text-secondary(#66645C)差三个色阶,肉眼分不出。这不是取值取得不好,
               是这片米色底 + AA 门槛本身只容得下**两级**文字颜色 —— 近黑,和这一级。
               所以这里不再假装有第三级:--text-light 就是 --text-secondary 的别名,
               「次要 vs 更次要」的层级从此靠字号、字距和全大写去分,不靠颜色。
               (226 处引用因此一处都不用动;真想合并成一个名字,留给日后的 extract。)

               真正装饰性的那 9 处(描边、分隔块背景)搬去下面的 --glyph-muted,
               它们保留原来的浅色 —— 那才是 #8A8880 该干的活。 */
            /* 2026-09-03 二次:#8A8880 在最暗的 --bg-primary(#E8E3D9)上实测 2.78:1,
               而非文字 UI 元素(图标描边、分隔块)的门槛是 3:1 —— 它在别的底色上过了,
               偏偏在页面主底色上没过。压到 #7F7D75,四种底色上 3.25–3.6:1 全过。
               保持同一支暖灰,不换色相。 */
            /* 底部导航的高度。三处要对齐：导航自己、页面的下留白、AI 那层的下沿。
               写成三个 48 的话改一处就漏两处 —— 之前 home 56 / profile 56+32 /
               boswell 48，三种间距就是这么来的。 */
            --nav-h: 48px;

            /* Added 2026-08-19 while folding 297 hardcoded hex values out of the JS.
               These five each appeared often enough inline to deserve a name; the
               remaining one-off shades were left as literals rather than inventing
               tokens nobody would recognise. */
            /* 同上一条:#A9A59D 是 2.2:1,比 --text-light 还低,而它的 20 处引用里
               19 处是 color:。时间戳和「3 of 8 nights unplanned」这种注脚也是文字,
               读不了就等于没写。第三级颜色既然不存在,它也归到同一级。 */
            /* 同行人色 · 取语义十色里的九支彩色(§2 编码层),不另立一套色。
               同一个 hex 在两处不冲突,因为形不同:类别色出现在图标与票据染色上,
               人物色只出现在写着首字母的方块上 —— 一处是图,一处是字。
               顺序按相邻色相差最大排,前三个人一定是蓝 / 红 / 绿。
               #6B6D71(others 的中性灰)不进轮换,留给「未分配 / 已退出」。 */
            --person-1: #426E97;   /* traveller marks, cycled by a hash of the name */
            --person-2: #9C4E48;
            --person-3: #3D6035;
            --person-4: #795F8A;
            --person-5: #83652E;
            --person-6: #2A5E62;
            --person-7: #794866;
            --person-8: #6A4635;
            --person-9: #4E4F7B;
            --person-none: #6B6D71;

            /* ---- Paper design tokens — the SINGLE source of truth shared with the
               canvas file (Stop 页面重设计.dc.html). Canvas hardcodes these exact hexes;
               the app references them so an applied design matches the canvas 1:1.
               Change a value here to re-skin every paper surface at once. ---- */

            /* Pale sage, for panels that should read as a distinct surface rather than
               another sheet — currently the journal entry's Details drawer.
               ⚠️ NEW TOKEN, flagged per the design system's rule about introducing values.
               Derived with color-mix rather than written as a hex on purpose: --accent is
               the system's sage (#5D675B) but it is a mid-dark ink colour, far too heavy
               to sit behind text as a background. Mixing it into the parchment gives the
               light sage the drawer wants while staying tied to the token — re-colour
               --accent and this follows, which a pasted hex would not.
               color-mix is Chrome 111+ / Safari 16.2+, so both Capacitor WebViews have it;
               the plain declaration above is the fallback for anything older.

               12% of --surface-field, not more, because contrast decides it: the drawer
               is almost entirely --text-secondary, which has little headroom to begin
               with (5.1:1 on parchment). At 14% of --bg-secondary it fell to 4.23:1 and
               --danger with it, both under AA. This lands at 4.82:1.

               The tint is subtle by construction and that is correct rather than a
               compromise: --accent is itself a desaturated sage, so any pale mix of it
               is quiet. It reads as a distinct surface because it is COOL against the
               warm parchment, not because it is darker — the two are near-identical in
               lightness. Going greener means moving the drawer's labels to
               --text-primary first. */
            /* --surface-sage and --rule-sage were declared here and are GONE (2026-09-24).
               They dressed one thing — the "just for you" panel in the trip settings, a 12%
               and a 32% mix of the retired accent — and when that panel took this world’s
               heading band instead, nothing in the app named either of them again. A token
               with no user is not a token, it is a colour somebody might reach for by
               accident. Deleting them is the only way that stops being possible. */

            /* 批次 05 删掉了 Cormorant Garamond / Optima / Candara / Inter /
               Helvetica Neue；批次 05b（补丁 E1）装上 Vollkorn + Hanken Grotesk。
               两批是同一件事的两半：05 拆旧栈，05b 装新栈。

               ‘CJK Sans Adj’ 必须排在拉丁字体之后、系统回退之前 ——
               它靠 unicode-range 只接管 CJK 码位，拉丁一侧完全不参与匹配。
               排在最前会让它抢不到（没有拉丁字形），排在最后则轮不到（系统回退
               已经命中）。这个位置是唯一正确的。

               这条规则现在管的是 02-auth.css 里的 --log-print / --log-write（于 2026-09-24
               补上）—— 它们是全应用 91% 的文字。本文件里已经没有字体栈了。

               字号阶、字重、字距、--type-* 一项都没动 —— 那些是阶段二的主体。 */
            /* --font-serif AND --font-sans WERE DECLARED HERE AND ARE GONE (2026-09-25).

               Both held the same stack by the end — Hanken Grotesk, ‘CJK Sans Adj’, then the system
               fallback — because Vollkorn went on 2026-09-17 and --font-serif stopped being a serif
               that day. Two names for one face is not a choice, it is a pair of labels a reader must
               check against the value before trusting either.

               What reads the face now is --log-print / --log-write / --log-type, declared in
               02-auth.css with this world’s own names. The legacy rules that named --font-serif
               went with the thirteen legacy parts (2026-09-23/24); the count of reads reached zero
               and these declarations became two tokens nothing asks for. */

            /* ---- Type scale ----
               Derived from the sizes already dominant in the app (0.75/0.85/0.9),
               tightened to a ~1.2 ratio. Seven steps, no in-between values. */
            /* 11px 是这套界面 UI 文字的地板,不再往下走。原来这一档是 0.65rem(10.4px),
               而散落在各处的硬编码还有 9 / 9.5 / 10 / 10.5px —— 单看每一处都只差半个像素,
               合起来是 190 多处读不了的标签,包括整条底部导航和大半的表单标题。
               9.5 与 10.5 之间那 1px 从来不是层级,只是噪音;它们现在统一到 11。 */
            --fs-micro: 0.6875rem;   /* 11px — letter-spaced caps labels, badges;本套字号的下限 */
            --fs-small: 0.75rem;   /* secondary text, metadata */
            --fs-body: 0.85rem;    /* default UI text */
            --fs-base: 1rem;       /* emphasised body, card titles */
            --fs-lg: 1.25rem;      /* section headings */
            --fs-xl: 1.5rem;       /* page titles */
            --fs-display: 2rem;    /* hero / trip names */

            /* ---- Spacing scale ---- */
            --sp-2xs: 0.25rem;
            --sp-xs: 0.5rem;
            --sp-sm: 0.75rem;
            --sp-md: 1rem;
            --sp-lg: 1.5rem;
            --sp-xl: 2rem;
            --sp-2xl: 3rem;

            /* ---- Weights ----
               Serif display: 400 regular / 500 emphasis.
               Sans UI: 300 body / 500 medium / 600 strong. No 'bold'. */
            --fw-light: 300;
            --fw-regular: 400;
            --fw-medium: 500;
            --fw-strong: 600;
        }

        body {
            /* ⚠️ AND THE FACE, WHICH THE FONT ROUND LEFT BEHIND (2026-09-25, found by review).
               This line read var(--font-sans) until 2026-09-24, and --font-sans was on :root, so it
               resolved. The conversion to this world’s own names moved it to --log-write and did NOT
               carry a fallback — and --log-write, like every --log-*, is declared on the room selector
               list at the head of css/02-auth.css, not on :root. So the app’s base face silently became
               the browser’s default serif. 203 elements that carry text of their own set no family and
               inherit this one: every .form-label in the activity form, all 168 rows of the currency
               list, the modal subtitles, the viewer’s words. Times New Roman, and no CJK adjustment
               either — the same fault, in the same round, as the one the round was opened to fix.
               The fallback form is the one the two declarations below have used since 2026-09-17 and
               for the same reason; the value is --log-write’s, kept in step by hand. */
            font-family: var(--log-write, 'Hanken Grotesk', 'CJK Sans Adj', system-ui, sans-serif);
            /* 300 across the board left small text (0.65–0.75rem) too fragile on the
               textured parchment ground; 400 is the readable floor for UI copy. */
            font-weight: var(--fw-regular);
            /* The chart stock, not --bg-primary (#E8E3D9, the outgoing parchment), which showed wherever nothing
               covered the body — an overscroll, a sheet's gap (2026-09-17, what the outgoing world still painted (capture --legacy)). The --log-* tokens are set on the
               rooms, not on body, so the value rides as the fallback. */
            background: var(--log-stock, #D9DFDD);
            /* ⚠️ AND THE INK, WHICH THE LINE ABOVE LEFT BEHIND (2026-09-21). The ground was
               corrected to the chart stock on 2026-09-17 and the colour on the next line was
               not, so the app's default ink stayed the outgoing world's near-black while its
               default paper was this world's. The same fallback form, for the same reason:
               body resolves `--log-ink` to nothing, because the --log-* tokens are declared
               on the room selector list at the head of css/02-auth.css and not on :root.

               What this actually reaches is smaller than it looks and was measured before it
               was changed: 778 elements inherit it, but only 175 carry any text of their own
               and 168 of those are one component repeated — the currency list. The rest are
               six stragglers that never named a colour (.stats-quiet, .fs-plate-note,
               .modal-title, .currency-symbol, .trip-section-sheet-title, and a line on the
               profile). Every other room sets its own ink. */
            color: var(--log-ink, #43276B);
            /* The tint the browser itself paints inside a checked box, a radio and a range
               track. It was a bare #4A3728 — the outgoing brown — on a stray :root rule in
               css/10-legacy-home-sheet.css: a token declaration sitting in a surface part,
               where nobody would look for it. Five checkboxes take it.
               accent-color INHERITS, so from here it reaches exactly the elements :root reached.
               Magenta because Stamped-State says the live mark is magenta.

               ⚠️ IT DOES NOT REACH EVERYTHING, and the first version of this comment implied it
               did. `input[type="date"]` and `[type="time"]` set their own accent-color
               (05-interaction.css), and for one commit theirs stayed #4A3728 while this one went
               magenta — two accents where there had been one. Changing this line means changing
               that one. Found in review, 2026-09-22.

               ⚠️ AND THE var() HERE IS DECORATION. body is not on the room selector list at the
               head of css/02-auth.css, so --log-magenta resolves to nothing and the literal
               #A81E6E is what every room inherits. Same as the two lines above; written this way
               so the intent is readable, not because the token is reaching anything. */
            accent-color: var(--log-magenta, #A81E6E);
            line-height: 1.6;
            overflow-x: hidden;
        }

        /* A CONTROL DOES NOT INHERIT ITS FACE (2026-09-25, found by the same review). The UA
           sets font-family on button, input, select and textarea, so a control that names no
           face wears the browser’s, not the app’s — which is why some forty rules in this world
           say `font: inherit` one at a time. Five that never got the line were printing in bare
           Arial: the viewer’s CLOSE and DELETE, the modal ×, and the section sheet’s ADD and
           CLOSE, with the currency and payment words and the editor’s B I U S inheriting Arial
           from the buttons around them. One element rule ends the whole class of fault; it is
           the weakest specificity in the app, so every `font: inherit` and every named face
           still wins over it, and only a control that named nothing changes. */
        button, input, select, textarea { font-family: inherit; }

        /* Caps labels are the app's signature device — they need the tracking to breathe
           and enough weight to hold at micro sizes. Single source of truth. */
        /* 分类更正：这条「caps labels 单一真源」原来把两类东西混在一起 ——
           给区块命名的（section-title / section-label）和给字段贴标签的
           （stat-label / day-label）。规格判据「给区块命名还是给字段贴标签？
           后者一律降级」。三个 *-stat-label / *-day-label 已移出本组。 */
        .stop-detail-section-title {
            font-size: 11px;
            font-weight: var(--fw-medium);
            letter-spacing: 0.1em;
            text-transform: uppercase;
            color: var(--log-ink-soft);
        }

        /* Desktop & Tablet: phone-like frame centered */
        @media (min-width: 768px) {
            html {
                background: #242424;
            }
            body {
                background: #242424;
            }
            .app-frame {
                max-width: 430px;
                margin: 0 auto;
                height: 100vh;
                overflow-y: auto;
                overflow-x: hidden;
                position: relative;
                transform: translateZ(0); /* contain fixed children */
                background: var(--log-stock, #D9DFDD);   /* the frame's ground; see body above */
                /* 闸门例外（补丁 G/#4）：产品画布之外的展示容器，不受 §2.4 约束。
               这是桌面视口下模拟手机外框的投影，不是界面元素 —— 它画的是
               「手机之外」，产品本身在它里面。闸门白名单里有同一句话。
               作用域例外，与 D3「跳过注入式片段」并列。勿删。 */
            box-shadow: 0 0 60px rgba(0,0,0,0.3);   /* gate:allow app-frame */
            }
        }

        /* Hide scrollbar but keep functionality */
        * {
            scrollbar-width: none;
            /* Firefox */
            -ms-overflow-style: none;
            /* IE and Edge */
        }

        *::-webkit-scrollbar {
            display: none;
            /* Chrome, Safari, Opera */
        }

        .page {
            display: none;
            min-height: 100vh;
            /* The paper. Was --bg-secondary, the outgoing cream. A page is the ruled sheet;
               the band is for a heading on it, which is why this is not --log-stock-band. */
            background: var(--log-stock);
            position: relative;
            z-index: 1;
        }

        .page.active {
            display: block;
        }

