@import url("./tokens.css");

/* 骨架令牌统一见 ./tokens.css（diyunc 品牌共享源）——本文件不再重复定义色值。 */
/* ============================================================================
   ChinaPass 站点色板 —— 权威源是 App 自己的主题色（composeApp …/ui/theme/Theme.kt
   的 object V9），不是共享骨架 tokens.css。

   背景：站点此前沿用共享骨架的 TimeCove 青绿兼容别名（--teal-950 #062f32 /
   --teal-900 #083f42），与 App 的松绿差 19.7° 色相、34.5pp 饱和度；纸底
   #f6f3ec 饱和度 100%（高饱和奶油黄），与 App 的中性纸 #F6F3EC 也差 64pp。
   结果就是「站点看着冷、App 看着暖」，同一品牌两套观感。

   规则（沿用 App Theme.kt 的设计原则「朱砂只用于三处：完成印章、紧急卡、
   危险操作；信息密集区保持工具级干净」）：
     · 结构（标题/导航/正文/次级按钮/链接）→ 松绿 + 墨
     · 朱砂（--accent）只做强调：章节小标 eyebrow、主 CTA、序号、紧急内容
     · 琥珀只做装饰（波浪下划线、时间线节点），且深色面板上必须换浅阶
   ========================================================================== */
:root {
  color-scheme: light;

  /* ---- 纸（背景层级）→ V9.Paper #F6F3EC ---- */
  --paper: #f6f3ec;      /* 全站背景基色（中性暖纸） */
  --paper-2: #efece3;    /* 渐变深处 / 浅卡底 */
  --card: #ffffff;       /* 卡片面、浮层 */

  /* ---- 墨（文字层级）→ V9.Ink / V9.Sub ---- */
  --ink: #22302a;        /* 标题与正文 */
  --muted: #5f6a63;      /* 弱化文字（对纸底 5.1:1、对 --paper-2 4.8:1，AA 通过） */

  /* ---- 线 / 琥珀（装饰）---- */
  --line: #e2ded3;       /* 分隔线、卡片描边 */

  /* ---- 角色令牌（2026-09-13 令牌收敛；深色模式要按角色整体改写，不能只改色相）---- */
  --heading: var(--pine-900);      /* 标题/当前页文字 */
  --brand-ink: var(--pine-700);    /* 导航与链接文字 */
  --brand-fill: var(--pine-700);   /* 主按钮/实心块背景 */
  --brand-fill-ink: var(--white);  /* 实心块上的文字 */
  --device-bezel: #16382e;         /* 手机外框（深色模式里它必须仍是深色）*/
  --panel-dark: var(--pine-900);   /* 深面板底（skip-link / 可靠性面板）*/
  --panel-dark-2: var(--pine-900); /* 同族次阶（原渐变两端同色，保持）*/
  --on-panel: var(--card);         /* 深面板上的文字/描边刻度（浅色模式下即白）*/
  /* 长文正文比 --muted 更实；--text-soft 是说明性正文（原 #3d4f47/#3d4f4c 近色归一）*/
  --text-body: #4a5a52;
  --text-soft: #3d4f4c;
  /* 浅松底（区域徽标/通知块）。此前是 `var(--card-soft, #eef3f0)` 的悬空兜底 —— 令牌根本没定义 */
  --card-soft: #eef3f0;
  /* 状态色（草稿横幅） */
  --warn-line: #7a5a1e;
  --warn-bg: #f5eedc;
  --warn-ink: #7a5a1e;
  /* 半透明描边：由墨色/松绿派生，深色模式自动跟随 */
  --line-soft: color-mix(in srgb, var(--ink) 11%, transparent);
  --pine-line: color-mix(in srgb, var(--pine-700) 22%, transparent);
  --amber: #8a6a2f;      /* V9.Amber —— 浅底上的装饰色（深色面板上不用它） */
  --amber-on-dark: #e0b871; /* 深松面板上的同族浅阶（原 #f1c37d 亮度对齐） */

  /* ---- 松（主色）→ V9.Pine 家族 ---- */
  --pine-900: #16382e;   /* V9.PineDark —— 标题、深面板 */
  --pine-700: #1e4d40;   /* V9.Pine —— 主导航、主按钮 */
  --pine-500: #2a6353;   /* 中间阶 —— 悬浮/次级强调 */
  --pine-50:  #e7eee9;   /* V9.PineSoft —— 浅松底 */

  /* ---- 印（强调）→ V9.Seal，与 Play 商店的红版式同源 ---- */
  --accent: #c2401f;
  --accent-deep: #9e3116;
  --seal-soft: #f7e9e2;

  /* ---- 阴影 / 圆角 / 版面尺度 ---- */
  --shadow: 0 24px 70px rgba(22, 56, 46, 0.16);
  /* 阴影分档（2026-09-13 令牌收敛）：悬浮顶栏 ≥ 内容卡 ≥ 浅块。深色模式整体改写这一组。 */
  --shadow-chrome: 0 12px 32px rgba(20, 40, 33, 0.12);
  --shadow-card: 0 12px 28px rgba(22, 56, 46, 0.09);
  --shadow-soft: 0 16px 40px rgba(22, 56, 46, 0.07);
  --shadow-soft-2: 0 6px 16px rgba(22, 56, 46, 0.08);
  --shadow-soft-3: 0 12px 34px rgba(22, 56, 46, 0.07);
  --shadow-brand: 0 10px 24px rgba(22, 56, 46, 0.18);
  --shadow-deep: 0 14px 34px rgba(22, 56, 46, 0.13);
  --radius-xl: 28px;
  --radius-lg: 20px;
  /* 收尾轮 15：把与已有 token 同值的裸字面量收进 token（**计算值零变化**，
     复扫的圆角并集必须和改前逐项相同才算收口而不是改版）。
     --radius-md/sm/pill 在共享骨架 tokens.css 里不存在（那边只有 xl/lg），不会串到别的产品。
     留在字面量的 24/22/44/18/14px 是不同角色（原型那条纪律一样：非同义散档不并）。 */
  --radius-md: 16px;
  --radius-sm: 12px;
  --radius-pill: 999px;
  /* 「大写小标」角色族的两个档位。改前实测：同一个角色全站 16 组（色@档），
     字号散在 14/13.6/12.8/12.16/11.2px，颜色散在 accent / accent-deep / muted / on-panel。
     收成一个角色一个 token、表面负责换值：深色面板/柔底面板在自己的规则上重声明它
     （而不是各写各的 color），子标签走 --label-sub。
     默认值 2026-09-21 由 --accent 改成 --accent-deep（用户拍板）：朱砂 14px/800 在纸面渐变
     较差端点 --paper-2 上只有 4.39，不到 AA 4.5；深一档给 6.52/6.12，暗面另有第二主题值。 */
  --label-ink: var(--accent-deep);
  --label-sub: var(--muted);
  --label-size: 0.875rem;
  --label-sub-size: 0.8rem;
  --label-track: 0.13em;
  --label-sub-track: 0.1em;
  --max: 1140px;

  /* ---- 阅读尺度（长文行宽控制在 60–75 字符）---- */
  --measure: 44rem;      /* 704px —— 内容页正文栏宽 */

  /* ---- 兼容别名：全部指向新色板，旧选择器一次性对齐 ----
     （tokens.css 仍提供同名旧值，这里同特异性后置覆盖） */
  --cream-50: var(--paper);
  --cream-100: var(--paper-2);
  --white: var(--card);
  --teal-950: var(--pine-900);
  --teal-900: var(--pine-700);
  --teal-800: var(--pine-900);
  --teal-600: var(--accent);
  --mint-100: var(--pine-50);
  --sand-200: var(--line);
  --amber-300: var(--amber);
}

/* ============================================================================
   「大写小标」角色的字距对泰语关掉（#34-#11，轮 29）
   ----------------------------------------------------------------------------
   理由**不是**"字距太大"：实测分距占字面推进的比例，泰语 eyebrow 26.5% 与英/西/印尼
   24–27% 同档 —— em 字距对各语言一视同仁。泰语特有的是**词内没有空格**：
   实测 Chrome 153 按**字素簇**加分距而不是按码点（«ช่วงเวลาที่ดีที่สุด» 19 码点 / 12 簇
   → 恰好 12 次分距；三种量法互证，见台账轮 29 §①），所以基字与音标不被拉开
   —— 台账轮 22 §⑧-4 写的"组合音标与基字被拉开、截图可见"是**错的**（轮 15 §⑨
   其实早量出"未拆散组合符"，两处自相矛盾登记着谁也没核对），而它说的"两处选择器"
   也差得远：这个角色实测是 6 条规则 / 7 个选择器 / 4 个字距值 0.13·0.12·0.1·0.04em。
   真正发生的是分距落在词内部的每个簇之间，把连写的 «ช่วงเวลา» 切成 ช່|ว|ง|เ|ว|ล|า
   七个孤立块，读者赖以分词的连续字流被抹掉。而 `text-transform: uppercase` 在泰语上
   是空操作（实测同文本 uppercase/none 的宽高逐位相同；英文则明显不同）⇒ 这一角色想要的
   "大写拉开以强调"在泰语上从来没有兑现对象，只剩副作用。
   两处例外按**容器**排除（CSS 判不了元素自己的文本）：`.task-card` 里的序号 `01`–`10`
   与 `.issue` 里的排障 slug（`vpn-blocked` 这类十语同源、故意不译）在泰语页上是拉丁文本。
   实测口径（11 个泰语抽样页 @1440×1600 亮态）：角色成员 70 处 = 含泰文 51 + 不含泰文 19，
   后者正是这两个容器（10 + 8）再加下面那 1 处；泰文那 51 处对应全站口径 —— 复扫 96 个
   泰语页态合计 408 处。给不含泰文的 19 处归零字距与泰语排版无关，只是顺手改掉装饰，
   故保住它们。漏网的由 `site_sweep` 的 `thTrack` 断言兜：它按语义枚举
   「自身文本含泰文的大写小标」，任何一处还带字距就报，且泰语页检测集为空也报。
   实测代价：泰语指南页 «💳 Alipay + WeChat Pay» 这一处拉丁品牌行落在 `.page-head .eyebrow`，
   与泰文章节小标同容器，按容器排除不掉 ⇒ 它也跟着丢掉字距（19 处里唯一没保住的）。
   为什么不改成"按语言翻 token"（`html:lang(th){--label-track:normal}`，看着更系统）：
   这 7 个选择器里只有一个 `var(--label-track)` 与两个 `var(--label-sub-track)` 吃 token，
   `.language-switcher label`(0.12em) / `.index-filter label`(0.1em) / `.hl-k`(0.04em) 三处是
   字面量 ⇒ 翻 token 只覆盖 4/7；把它们都收进 token 又要新造一档（0.12em 现在无 token 可归），
   那是改动**所有语言**的渲染来换一处少写两行。本轮的口径是零连带（读数见台账轮 29 §④）。
   `!important` 只为赢过 `.eyebrow` 自己那条 `!important`（它的由来见其注释）；
   特异性主规则 (0,2,2) > `.eyebrow` 的 (0,1,0)，例外规则 (0,3,1) > 主规则（`:is(.task-card,.issue)`
   按参数里最复杂者计，多一个类）—— 两侧都带 important，所以先比 important 再比特异性。
   例外写成第二条规则而不是塞进主规则的 `:not(...)`：`:not()` 要表达"祖先不在该集合"得放
   后代选择器进去（Level 4 复杂参数），而本项目**没有认证过任何选择器兼容性下限**
   —— 没认证过的能力不当修法（轮 28 当年对 300 档就是"显式声明不支持"而不是硬凑；那一档到轮 39
   才按全 14 族 × 10 语的复扫升成认证档，见下面 ≤400 那条规则的注释）。
   同理，本轮全部读数只在 Chrome 153 上量过（`:lang(th)` 前缀匹配 `th-TH` 亦是实测 True），
   跨浏览器未验证。
   ========================================================================== */
html:lang(th) :is(.eyebrow, .city-facts dt, .index-filter label, .phrase-filter label,
                  .phrase-labels span, .language-switcher label, .hl-k) {
  letter-spacing: normal !important;
}

/* 上面那条的两个例外：这两容器下的 `.eyebrow` 在泰语页上是拉丁文本
   （实测 70 处角色成员里 18 处在此，另有 1 处例外保不住，见上面的注释）。
   写在后面且特异性更高（:is(.task-card,.issue) 多一个类），所以它赢。 */
html:lang(th) :is(.task-card, .issue) .eyebrow {
  letter-spacing: var(--label-track) !important;
}

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  overflow-x: hidden;
  /* 正文里会直接写 URL（隐私说明里就是 https://chinapass.app/privacy/），
     默认不断字 → min-content 被整串 URL 撑到 373px，在 360px 视口上顶出横向滚动。 */
  overflow-wrap: break-word;
  /* 字族：品牌字优先，其后补系统 UI 与中日韩字面（原链在 Windows 落到
     Trebuchet MS，字重与基线都劣于 Segoe UI，且缺 CJK 兜底）。 */
  font-family: "Avenir Next", "Avenir", system-ui, -apple-system, "Segoe UI",
    "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", "Microsoft YaHei",
    "Trebuchet MS", Verdana, sans-serif;
  /* 正文基准 17px（原为继承 16px）：长文阅读舒适区，且与内容页 h1 的
     尺度比从 5.6:1 收敛到 ~2.4:1。 */
  font-size: 1.0625rem;
  color: var(--ink);
  /* 背景改为中性纸 + 极淡的品牌双色晕染。原实现是「高饱和奶油黄 +
     青色光晕 + 薄荷渐变收尾」，三色叠加后发浑、底部泛绿。 */
  background:
    radial-gradient(circle at 6% 0%, color-mix(in srgb, var(--accent) 4.5%, transparent), transparent 28rem),
    radial-gradient(circle at 94% 6%, color-mix(in srgb, var(--pine-700) 7%, transparent), transparent 30rem),
    linear-gradient(180deg, var(--paper) 0%, var(--paper) 52%, var(--paper-2) 100%);
  background-attachment: fixed;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img { max-width: 100%; display: block; }

a { color: inherit; }

.skip-link {
  position: absolute;
  left: 1rem;
  top: -4rem;
  z-index: 10;
  background: var(--panel-dark);
  color: var(--on-panel);
  padding: 0.75rem 1rem;
  border-radius: var(--radius-pill);
}

.skip-link:focus { top: 1rem; }

.site-header {
  width: min(var(--max), calc(100% - 2rem));
  margin: 1rem auto 0;
  padding: 0.75rem 0.9rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  border: 1px solid color-mix(in srgb, var(--ink) 11%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--card) 72%, transparent);
  backdrop-filter: blur(18px);
  box-shadow: var(--shadow-chrome);
}

.site-header.compact { margin-bottom: 2rem; }

/* 品牌与导航之间的空洞（实测：品牌右缘 401、导航左起 541，而右缘只剩 21px）。
   让导航占满剩余宽度并均匀分布，空洞分摊到各项之间。仅桌面：≤680 是纵向卡片布局。 */
@media (min-width: 681px) {
  .site-header nav { flex: 1; justify-content: space-between; margin-left: clamp(0.75rem, 2vw, 2rem); }
}

.brand {
  display: inline-flex;
  align-items: center;
  /* 移动端实测 236×40 —— 差 4px 到 44px 可点区建议值 */
  min-height: 2.75rem;
  gap: 0.65rem;
  color: var(--heading);
  font-weight: 800;
  text-decoration: none;
  letter-spacing: -0.02em;
}

.brand img {
  border-radius: var(--radius-sm);
  box-shadow: 0 10px 24px color-mix(in srgb, var(--pine-900) 13%, transparent);
}

nav {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  flex-wrap: wrap;
  justify-content: flex-end;
}

nav a {
  /* 移动端实测：0.92rem = 14.72px，且 padding 撑出的可点区只有 ~34px，
     低于 WCAG 2.5.5 建议的 44px。抬字号 + min-height 一起补。 */
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: 0.6rem 0.9rem;
  border-radius: var(--radius-pill);
  color: var(--brand-ink);
  font-weight: 700;
  font-size: 0.98rem;
  text-decoration: none;
}

nav a:hover { background: color-mix(in srgb, var(--pine-700) 10%, transparent); }

/* 当前页标识（2026-09-13 视觉评审）：siteNav 一直在输出 aria-current="page"，
   但**没有任何样式** —— 用户看不出自己在这一页。底色 + 加粗双通道。 */
nav a[aria-current="page"] {
  background: color-mix(in srgb, var(--pine-700) 12%, transparent);
  color: var(--heading);
  font-weight: 800;
}

/* WCAG 2.4.7 焦点可见：原来 nav 与 .button 的 :focus-visible 都是 `outline: none`，
   只靠 10% 底色 / 上移 2px 表示焦点；开了 prefers-reduced-motion 后位移被禁用，
   键盘焦点等于完全不可见。这里给所有可聚焦元素补实描边。 */
a:focus-visible,
button:focus-visible,
select:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--accent-deep);
  outline-offset: 2px;
}
nav a:focus-visible {
  background: color-mix(in srgb, var(--pine-700) 10%, transparent);
  outline: 3px solid var(--accent-deep);
  outline-offset: 2px;
}
/* 深色面板上深红描边看不清，换浅琥珀 */
.app-hero :focus-visible { outline-color: var(--amber-on-dark); }

.language-switcher label {
  /* 语言控件的 label 与导航项同字号同粗细时，会被读成第 7 个导航项（视觉评审）。 */
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

.language-switcher {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.18rem 0.3rem 0.18rem 0.75rem;
  /* 与次级按钮同一套规格（描边走 --line-strong）：原先容器描边对自身底色只有 1.1:1（几乎看不见），
     且里面那个深绿实心胶囊与主 CTA 同色同规格，读起来像顶栏里的第二个主按钮。 */
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--card);
  color: var(--brand-ink);
  font-size: 0.92rem;
  font-weight: 800;
}

.language-switcher select {
  max-width: 12rem;
  /* 2.2rem = 35px，低于 44px 可点区建议值 */
  min-height: 2.75rem;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius-pill);
  /* 由深绿实心改为次级控件外观：顶栏里不再出现与主 CTA 同规格的实心块。 */
  background: var(--card);
  color: var(--heading);
  font: inherit;
  font-weight: 800;
  padding: 0 0.65rem;
}

.section-shell {
  width: min(var(--max), calc(100% - 2rem));
  margin: 0 auto;
}

.hero {
  min-height: 760px;
  display: grid;
  grid-template-columns: minmax(0, 1.04fr) minmax(320px, 0.96fr);
  align-items: center;
  gap: clamp(2rem, 6vw, 5.5rem);
  /* 原 clamp(4rem,7vw,7rem)：应用页 hero 因此到 926px，847px 视口下正文卡被切断
     （2026-09-13 视觉评审实测）。收到 5vw 后 hero ≈800px。 */
  padding: clamp(3rem, 5vw, 5rem) 0 4rem;
}

.eyebrow {
  margin: 0 0 0.7rem;
  /* 章节小标是全站唯一长期使用朱砂的地方（对齐 App「朱砂只用于三处」的克制原则）：
     体量小、频次低，正好当品牌强调，不会把信息区染红。
     —— 这条决策保留，但角色值改由 --label-ink 提供（面板自己重声明 token，别再各写各的 color）。
     !important 不是偷懒：角色 token 是 (0,1,0)，组件的裸 `p` 规则是 (0,1,1)，实测首页
     `.app-entry p{color:var(--muted)}` 就把那块 eyebrow 的颜色偷走了（改前量到 5 处 3 组）。
     收尾轮 14 在原型上已验证"靠源序/升特异性"会在 @media 后置面前静默失效，所以钉在 token 上。 */
  color: var(--label-ink) !important;
  font-size: var(--label-size) !important;
  font-weight: 800;
  letter-spacing: var(--label-track) !important;
  text-transform: uppercase;
}

h1, h2, h3 {
  color: var(--pine-900);
  line-height: 1.16;
  font-weight: 800;
  /* 中日韩标题逐字断行，长标题很容易出现「末行只剩一个字」的孤字
     （实测首页 h1 在 13ch 限宽下末行只剩「国。」）。balance 让浏览器均分行宽；
     不支持的浏览器会忽略该声明，不影响布局。 */
  text-wrap: balance;
}

h1 {
  margin: 0;
  /* 原为 13ch —— 对中日韩等于「每行 13 字」，过窄且制造孤字。 */
  max-width: 17ch;
  /* 原 clamp(2.35rem, 5.2vw, 4rem)（最大 64px）+ 字距 -0.045em：
     中文标题被压得过紧、且与正文 17px 的比例达 3.8:1。 */
  font-size: clamp(2.25rem, 4.6vw, 3.4rem);
  letter-spacing: -0.028em;
  overflow-wrap: anywhere;
}

h2 {
  margin: 0;
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  letter-spacing: -0.022em;
}

h3 {
  margin: 1.15rem 0 0.55rem;
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: -0.015em;
}

.hero-lede {
  max-width: 42rem;
  margin: 1.35rem 0 0;
  color: var(--text-body);
  font-size: clamp(1.02rem, 1.8vw, 1.22rem);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem;
  margin-top: 2rem;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3.25rem;
  /* 视觉评审实测：主按钮靠更胖的内边距（39px vs 22px）取胜，两枚并排像两种规格。
     统一内边距后，主次只由填充/描边/一档投影区分。 */
  padding: 0.85rem 1.6rem;
  border-radius: var(--radius-pill);
  font-weight: 700;
  text-decoration: none;
  transition: transform 180ms ease, box-shadow 180ms ease, background 180ms ease;
}

.button:hover { transform: translateY(-2px); }
.button:focus-visible {
  outline: 3px solid var(--accent-deep);
  outline-offset: 3px;
}

.button.primary {
  /* 角色令牌：深色模式下 --white 会跟着 --card 变深，直接用它会让主按钮变成深字深底；
     填充与填充上的文字必须各有令牌。 */
  color: var(--brand-fill-ink);
  background: var(--brand-fill);
  /* 投影比次级重一档即可（原 0 16px 36px/0.22 过重，与次级的「无投影」并排像两种规格）。 */
  box-shadow: var(--shadow-brand);
}

.button.secondary {
  color: var(--heading);
  /* 与主按钮同一套「会浮起来」的规格，只是轻一档。 */
  box-shadow: var(--shadow-soft-2);
  /* 白填充 + 极浅描边在米色底上认不出是按钮；描边改走 --line-strong（对纸底 ≥3:1）。 */
  background: var(--card);
  border: 1.5px solid var(--line-strong);
}

.hero-pill {
  /* 状态徽章不是动作：保持小尺寸即可，但离按钮远一点，别被按钮投影吃掉间距。 */
  margin: 2rem 0 0;
}

/* 首屏的「装 App」入口（2026-10-22）：第三顺位的一行细链，不是第三个按钮。
   为什么不是按钮：首页首屏的主任务是「去哪玩 / 实用指南」，把装 App 提成同权重，
   是拿内容入口换下载，两边都不讨好；为什么又必须出现在首屏：下方说服力更强的那块
   （.bridge「Take this guide offline」）在 88% 处，30 天里全站下载 CTA 只有 4 次真实点击，
   低到分不清「没人想要 App」和「没人看得到按钮」。先把它挪到能被看到的地方，再读数。 */
.hero-install {
  margin: 1rem 0 0;
  font-size: 0.95rem;
}

.hero-install a {
  /* 用品牌墨色而不是正文色：它是可点的动作，不是说明文字。 */
  color: var(--brand-ink);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
}

.hero-install a:hover,
.hero-install a:focus-visible {
  text-decoration-thickness: 2px;
}

.hero-visual {
  position: relative;
  min-height: 660px;
  padding-block: 1rem;
  display: grid;
  place-items: center;
}

/* 截图宽度是「图 + 说明文字」的唯一事实源：两者写死同一个值，
   框才能贴合；否则 fit-content 会被 figcaption 的 max-content 撑开（见下）。 */
.phone-frame {
  --shot-w: min(310px, 72vw);
  /* 首屏里的手机框单独收一档（2026-09-13）：746px 高的框把 hero 撑到 926px，
     847px 视口下正文卡被折叠线切断。截图区不受影响。 */
  position: relative;
  overflow: hidden;
  /* 关键：框必须是 fit-content。栅格项默认 stretch，`justify-items: center`
     也救不回来 —— 因为 figcaption 一行文字的 max-content（实测 338px）比截图
     （280.8px）还宽，fit-content 取的就是它，结果框被撑到 358px、截图贴在左边、
     右边空出 67px 的深色板，看起来像「四边间距不一样」。 */
  width: fit-content;
  /* 兜底：三列断点（1024px 附近）下每列只有约 310px，而框的内容宽 + 40px 装饰
     是 350px —— 没有 max-width 就会把栅格顶破（实测 1024px 溢出 4px）。 */
  max-width: 100%;
  margin-inline: auto;
  /* 截图四边留白：原先图片紧贴 10px 深色边框，视觉上内容顶到边上。
     加 10px 内边距后，四边是均匀的 10px 边圈 + 10px 留白 = 20px。 */
  padding: 10px;
  border: 10px solid var(--device-bezel);
  border-radius: 44px;
  background: var(--device-bezel);
  box-shadow: var(--shadow);
}

.phone-frame picture {
  display: block;
}

.phone-frame img {
  width: var(--shot-w);
  max-width: 100%;
  height: auto;
  aspect-ratio: 9 / 20;
  object-fit: contain;
  /* 内圆角 = 外圆角 44 − 边框 10 − 内边距 10 = 24，否则图片圆角比框的内圆角大，
     四角会露出一条深色月牙。 */
  border-radius: 24px;
}

.phone-frame.tilted {
  transform: rotate(3deg);
}

/* 首屏里的手机框单独收一档（2026-09-13 视觉评审）：746px 高的框把应用页 hero 撑到
   926px，而 847px 视口下正文卡被折叠线切断。截图区（.screenshots）不受影响。 */
.hero-visual .phone-frame { --shot-w: min(264px, 62vw); }

.floating-card {
  position: absolute;
  left: 0;
  bottom: 5rem;
  width: min(280px, 76vw);
  padding: 1rem;
  border-radius: 22px;
  color: var(--heading);
  /* 原 color-mix(in srgb, var(--card) 88%, transparent) + blur：手机深色边框会透出来，卡内出现一道贯穿竖条，
     看起来像污迹（2026-09-13 视觉评审）。浮卡是不透明面。 */
  background: var(--card);
  box-shadow: var(--shadow-deep);
}

.floating-card span {
  display: block;
  margin-top: 0.3rem;
  color: var(--muted);
  font-size: 0.92rem;
}

/* 一句话定位条（原型 .facts-strip，#33 收尾轮 22）：上下两道细线夹一行点分短句。
   改前这里是 .trust-strip —— 三格渐变卡 + 阴影，把三句定位话做成了「三张功能卡」，
   和它下面真正的 feature 区抢同一层视觉重量。
   字号取 0.95rem 而不是原型的 12px：站内正文可读下限是 16px（移动端 0.96rem 曾被抓为缺陷，
   见 .feature p 的注释），这里保留原 .trust-strip 的 0.95rem 档，不新开一档。 */
.facts-strip {
  width: min(var(--max), calc(100% - 2rem));
  margin: 0 auto var(--page-gap);
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem 2.2rem;
  padding-block: 1.05rem;
  border-block: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.95rem;
  line-height: 1.5;
}

.facts-strip span::before {
  content: "·";
  margin-right: 0.55rem;
  color: var(--pine-500);
  font-weight: 800;
}

.section { padding-block: var(--page-gap); }

.section-heading {
  max-width: 760px;
  margin-bottom: 2rem;
}

.section-heading.narrow { max-width: 680px; text-align: center; margin-inline: auto; }
.section-heading p:not(.eyebrow) { color: var(--muted); font-size: 1.08rem; }

.feature-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1rem clamp(1.25rem, 3vw, 2.5rem);
}

/* 功能条目（原型 .feature）：改前是 .feature-card —— 四块带描边、渐变底、阴影的卡片，
   270px 定高把短句顶成大片空白。原型改成「一根细线 + 标题 + 说明」，靠线分组不靠盒子。
   排版档位沿用站内既有的一组（与 .task-card h3/p 同值），不新开字号。 */
.feature {
  min-width: 0;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}

/* 移动端实测（Chrome CDP，414px）：原 1.05rem = 16.8px，比正文 17px **还小**，
   层级反了 —— 卡片标题看着比正文弱。 */
.feature h3 { margin: 0.5rem 0 0; font-size: 1.14rem; letter-spacing: -0.005em; }
/* 0.96rem = 15.36px，跌破移动端 16px 可读下限。 */
.feature p { margin: 0.5rem 0 0; color: var(--muted); font-size: 1rem; line-height: 1.62; }
/* 序号与章节小标同角色（朱砂小号大字），所以走同一个 --label-ink：
   裸 --accent 在 14px 档对较差端点 --paper-2 只有 4.39（< AA 4.5，HEAD 树同探针复量到）。
   改前它藏在 .feature-card 的渐变底里，探针量不出底色 ⇒ 从没进过任何一批 AA 读数。 */
.feature-number { color: var(--label-ink); font-size: 0.875rem; font-weight: 800; letter-spacing: 0.08em; }

.screenshots {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 3vw, 2rem);
  align-items: start;
  justify-items: center;
}

.screenshots .phone-frame {
  margin: 0;
  background: var(--device-bezel);
}

.screenshots figcaption {
  /* 说明文字在机框**内部**（.phone-frame 自己有 --device-bezel 底），不是在纸面上。
     改前这里写 var(--heading)：浅色模式下 --heading = --pine-900 = #16382e，而
     --device-bezel 也是 #16382e —— 同色，标题栏实测对比度 1.0，三张截图的说明整行看不见
     （HEAD 树 same-probe 复量：`.screenshot-row figcaption` light 1.0 ✗，11 页都中招）。
     深色模式反而正常（--heading 在暗面是浅色），所以它躲过了只看一套主题的复扫。 */
  color: var(--on-panel);
  font-weight: 800;
  text-align: center;
  padding: 0.8rem 0 0.2rem;
  /* 与截图同宽，否则这一行字的 max-content 会成为整个框的宽度。 */
  width: var(--shot-w);
  max-width: 100%;
}

/* 应用页首屏（原型 .app-hero，#33 收尾轮 22）：一整块深松面板。
   底色复用站内既有令牌对 --panel-dark / --on-panel（原可靠性面板在用），
   所以这里**不新增任何色值** —— 深色模式只覆盖令牌，面板靠翻转自动跟上。 */
.app-hero {
  width: min(var(--max), calc(100% - 2rem));
  margin: clamp(1.5rem, 4vw, 3rem) auto 0;
  padding: clamp(1.5rem, 4vw, 3rem);
  border-radius: var(--radius-xl);
  display: grid;
  grid-template-columns: minmax(0, 1.04fr) minmax(320px, 0.96fr);
  /* 左栏与手机顶对齐：居中排版会让左栏比手机顶低 82px（2026-09-13 视觉评审实测，
     原 .hero--app 的两条对齐修正随面板一起搬过来）。 */
  align-items: start;
  gap: clamp(2rem, 6vw, 5.5rem);
  color: var(--on-panel);
  /* background-color 单独写：面板本体要有一层**实心**底色，否则 computed background-color
     是 transparent，AA 探针取不到表面（只会退到纸底端点，量出来的数字与面板无关）。
     两层渐变原样保留在 background-image 上（深色模式两端是 #223032→#1b2729，不是同值）；
     实心层取较亮那一端，白字对它是**保守**读数。 */
  background-color: var(--panel-dark);
  background-image:
    radial-gradient(circle at 90% 12%, color-mix(in srgb, var(--amber-on-dark) 20%, transparent), transparent 18rem),
    linear-gradient(135deg, var(--panel-dark), var(--panel-dark-2));
  box-shadow: var(--shadow);
  --label-ink: var(--on-panel);
}
/* 栅格子项默认 min-width:auto —— 下限是 min-content，长 URL 会把轨道撑破容器，
   即使轨道本身写的是 minmax(0, 1fr)。 */
.app-hero > *, .hero > * { min-width: 0; }
.app-hero .hero-copy { padding-top: 0.55rem; }
.app-hero h1 { color: var(--on-panel); }
.app-hero .hero-lede { color: color-mix(in srgb, var(--on-panel) 80%, transparent); }

/* 面板上的按钮必须换填充方向：--brand-fill 是松绿实心，落在深松面板上边界只有约 1.2:1，
   认不出是可点件（原型 `.app-hero .button` 同一处理：纸底 + 松字）。
   次级改描边款 —— 主次仍由「实心 / 描边」区分，不是两块同色。 */
.app-hero .button.primary {
  background: var(--on-panel);
  color: var(--panel-dark);
  box-shadow: var(--shadow-deep);
}
.app-hero .button.secondary {
  background: transparent;
  border-color: color-mix(in srgb, var(--on-panel) 45%, transparent);
  color: var(--on-panel);
  box-shadow: none;
}

/* privacy-callout 原与 reliability 同款深绿渐变——两个深色大块连排把首页后段
   压得很沉（2026-09-12 风格走查）。改为纸感卡片，只保留结构布局。 */
.privacy-callout {
  margin-bottom: 6rem;
  padding: clamp(1.5rem, 4vw, 3rem);
  border-radius: var(--radius-xl);
  background: var(--card);
  border: 0.5px solid var(--line);
}
.privacy-callout h2 { color: var(--ink); }
.privacy-callout p { color: var(--muted); }

/* 开发者引言（原型 .quote）：改前是 .dev-note —— 描边圆角卡 + 斜体小字，
   把一句真引言做成了「附注卡」。原型让引言本身当版面（衬线大字居中），
   署名走组件词汇表里的 .sig。文字全部来自站内现有 developerQuote / developerMeta。 */
.quote-section { text-align: center; }
.quote {
  max-width: 38ch;
  margin: 1.5rem auto 0;
  color: var(--pine-900);
  font-family: var(--serif);
  font-size: clamp(1.4rem, 2.8vw, 1.75rem);
  line-height: 1.55;
}
.quote p { margin: 0; }
.quote .sig { display: block; margin-top: 1.1rem; font-size: 1rem; }
/* 全局 `a { color: inherit }` 会让署名里的链接和「弱化斜体署名」完全分不出来
   （改前这条由 .dev-note-meta a 负责），照搬一次，别让它静默变成不可识别的链接。 */
.quote .sig a { color: var(--accent-deep); font-weight: 700; text-decoration: none; }

/* 改前这里是 `display:flex; justify-content:space-between` 的两列（左文案右按钮）。
   收尾轮 17 批 1 把「看全部」入口收进 .section-heading 的右端后，这一块变成纵向：
   标题行 → 法律声明正文，不再需要 flex 壳。 */
/* 收尾轮 17 批 1：正文栏宽从 `> div` 挪到正文本身 —— 标题行现在是要占满行的
   flex（左标题右「看全部」），给它套 40rem 会把链接挤到标题下面。 */
.privacy-callout .hero-lede { max-width: 40rem; }

.site-footer {
  width: min(var(--max), calc(100% - 2rem));
  margin: 0 auto;
  padding: 2rem 0 3rem;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  color: var(--muted);
  border-top: 1px solid color-mix(in srgb, var(--ink) 11%, transparent);
}

.site-footer a { font-weight: 800; text-decoration: none; color: var(--brand-ink); }

.draft-banner {
  margin-bottom: 1rem;
  padding: 1rem 1.15rem;
  border: 1px solid color-mix(in srgb, var(--warn-ink) 28%, transparent);
  border-radius: var(--radius-lg);
  background: var(--warn-bg);
  color: var(--warn-ink);
}

.draft-banner.is-production-ready {
  border-color: color-mix(in srgb, var(--pine-700) 22%, transparent);
  background: var(--pine-50);
  color: var(--heading);
}

/* 隐私政策译本页顶端的「英文版为准」提示（2026-09-12 隐私页七语言化）。
   与 .draft-banner 同族（页面级说明条），但这是法律提示而非发布警告 —— 用松墨色系。
   #31d 起隐私页正文改走 `.article-shell`（不再是 `.policy-card`），提示条随之提到
   `.page-head` 之下、`.spread` 之上 ⇒ 选择器去掉 `.policy-card` 前缀（那段特异性
   本就是为了压过 `.policy-card p` 的颜色，现在它不在 `.policy-card` 里了）。 */
.policy-notice {
  margin: 1.15rem 0 1.4rem;
  padding: 1rem 1.15rem;
  border: 1px solid color-mix(in srgb, var(--pine-700) 22%, transparent);
  border-radius: var(--radius-lg);
  background: var(--card-soft);
}
.policy-notice strong { display: block; color: var(--heading); }
.policy-notice p {
  margin: 0.4rem 0 0;
  font-size: 0.94rem;
  color: var(--text-soft);
}

/* 原型 privacy（#31d）：`.policy-top` 是生效日的顶栏（原型：flex 两端 + 下细线）；
   `.legal` 只声明正文一档字号，章节标题/列表/表格的版式沿用 `.article`（#29 立的骨架）。
   原型 `.article-table` 的角色由站内既有的全局 `table` 规则承担（同 §⑧ 的「同角色异名」口径），
   不再新增一个无样式的空类。 */
.policy-top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.25rem 0;
  margin-bottom: 2.5rem;
  border-bottom: 1px solid var(--line);
}
.policy-top small { color: var(--muted); }
.legal { font-size: 0.95rem; }

.policy-meta { margin: 0.65rem 0 0; font-weight: 700; font-size: 0.88rem; color: var(--muted) !important; }

/* 规格表（下载页的「版本 / SHA-256」）—— 之前全站**没有任何** table 样式，
   纯浏览器默认：既没有内边距和分隔线，移动端还会被 64 位 SHA-256 撑到 737px，
   在 414px 视口上把整页顶出横向滚动（CDP 实测 scrollWidth 778 vs vw 414）。
   table-layout: fixed 让单元格换行而不是撑宽；长哈希再单独允许断字。
   `#31d` 起 `.policy-card` 整族撤掉，这里收敛成裸 `table` / `table th, table td` ——
   隐私页的 `.article-table` 与下载页的 `.tech-list` 都靠这两条（见 §⑧ 的「同角色异名」）。 */
table {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  margin: 1.1rem 0 0;
  font-size: 0.95rem;
}

table th,
table td {
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}
/* 只给**首列**定宽（规格表的第一列是标签）。此前这条对每一列都生效：隐私政策的三列
   表格因此被固定成 3 × 8.5rem = 408px，360px 视口横向溢出 89px（实测 scrollWidth 449）。
   多列表格应当让其余列自适应。 */
table th:first-child {
  width: 8.5rem;
}
table th {
  color: var(--pine-900);
  font-weight: 800;
}
table td { color: var(--text-body); }
/* 等宽串（SHA-256、URL、路径）默认不断字，是横向溢出的头号来源 */
code {
  overflow-wrap: anywhere;
  word-break: break-word;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9em;
}
table tr:last-child th,
table tr:last-child td { border-bottom: 0; }

/* 原来是 `both`：动画结束后 `to` 状态（transform:translateY(0)）被永久钉在元素上，
   优先级高于 :hover 的 transform，于是带 .reveal 的卡（首页六张 + 季节卡全都带）
   永远抬不起来。`backwards` 只在 delay 期间套用 from，结束后交回元素自身样式 ——
   终态本来就是 opacity:1 / 无 transform，视觉逐像素相同，但 hover 位移恢复了。 */
.reveal {
  animation: rise-in 700ms ease backwards;
}

.delay-1 { animation-delay: 90ms; }
.delay-2 { animation-delay: 180ms; }
.delay-3 { animation-delay: 270ms; }

@keyframes rise-in {
  from { opacity: 0; transform: translateY(18px); }
  to { opacity: 1; transform: translateY(0); }
}

/* /help/ 索引页（#31a，原型 `helpIndex`）：两段式 —— 上面是「先选主题」的
   `.topic-grid` 卡片栅格（450 条问题分 11 组，第一屏该看到有哪几类，而不是直接
   掉进第一组），下面是每组的锚点块，组内问题行走原型 `.topic-links` 的
   「两列 + 发丝分隔」。卡片顶上是原型那条 2px 松绿实线（`.answer` 用的是同一个
   `--brand-fill` 角色令牌，不引新色）。 */
.topic-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 1.25rem;
  margin: 2.5rem 0 0;
}

.topic {
  display: block;
  padding: 1.1rem 0 0;
  border-top: 2px solid var(--brand-fill);
  color: inherit;
  text-decoration: none;
}

.topic small {
  display: block;
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.topic h2 { margin: 0.35rem 0 0; font-size: 1.12rem; letter-spacing: -0.012em; }
.topic:hover h2 { color: var(--brand-ink); }

/* 卡片第三行 = 该分类的头三条问题标题（既有内容拼的预览，不是新文案）。
   行数上限 3 行：分类条数不均（2 条 vs 60 条），不夹住会把栅格拉成锯齿。 */
.topic p {
  display: -webkit-box;
  margin: 0.5rem 0 0;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--text-soft);
}

/* 锚点组。外层不设容器类 —— 间距由兄弟选择器给，省一个只为 `gap` 存在的类。 */
.topic-group { scroll-margin-top: 1.5rem; }
.topic-grid + .topic-group { margin-top: 3.5rem; }
.topic-group + .topic-group { margin-top: 2.75rem; }

.topic-group h2 { margin: 0; font-size: 1.3rem; letter-spacing: -0.015em; }

.help-index-count { color: var(--muted); font-weight: 600; font-size: 0.88em; }

.topic-links {
  list-style: none;
  margin: 0.9rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 2.5rem;
}

/* 原型 `.topic-links a { padding: 20px 0 }` 的整行可点：390 下也保住 ≥44px 触控目标
   （上一版分组卡里的链接实测只有 23px 高）。 */
.topic-links a {
  display: block;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--line);
  color: var(--brand-ink);
  font-weight: 600;
  line-height: 1.45;
  text-decoration: none;
}

.topic-links a:hover { text-decoration: underline; }

/* 响应式紧跟族定义之后（本文件的既定做法）：760 下两列问题行收成一列。
   原型同断点也是 `grid-template-columns: 1fr`。 */
@media (max-width: 760px) {
  .topic-links { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .topic-group + .topic-group { margin-top: 2.25rem; }
}

/* help 文章的数据表（对照表磁铁页）在窄视口：`table th { width: 8.5rem }`
   是给下载页两列规格表设计的，3–4 列的数据表在 390px 视口会被列宽之和
   撑到 408px（CDP 实测 sw 449 vs vw 390）。改为横向滚动容器——列宽保住
   可读性，溢出由容器吸收。 */
.table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin-top: 1.1rem;
}

.table-wrap table { margin-top: 0; min-width: 540px; }
.table-wrap table th { width: auto; }

.faq-list {
  display: grid;
  gap: 0.75rem;
  /* 行长：不设上限时答案撑满 1140px 容器（实测最长行 ≈145 字符），
     读起来要来回扫视。收到 680px 与上方 .section-heading.narrow 对齐。 */
  max-width: 680px;
  margin-inline: auto;
}

.faq-list details {
  padding: 1rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--card) 82%, transparent);
}

.faq-list summary {
  cursor: pointer;
  color: var(--pine-900);
  font-size: 1.02rem;
  font-weight: 700;
}
.faq-list summary::marker { color: var(--accent); }
.faq-list details > :not(summary) { margin-top: 0.6rem; }

/* ============================================================================
   旅行先行首页（2026-09-12）
   把首页从「app 介绍页」改成「中国旅行入口页」：首屏换成城市照片，其下依次是
   精选城市橱窗（#where）、实务入口（#practical），app 区降到最后（#app）。
   结构类只用一次，卡片词汇复用站内既有档：城市橱窗用 `.city-grid`/`.city-card`
   （与 /cities/ 索引页同源，见下方「首页族按原型重写」那段），实务入口用
   `.task-grid`/`.task-card`（收尾轮 17 按原型 `.home` 重写时用 `.feature-grid`/
   `.feature-card`，那对现在归应用页的 #features 用）；这里只补三处：首屏照片框、
   城市卡双列、区块末尾的「看全部」行。
   ========================================================================== */
.hero--travel { min-height: auto; padding-bottom: 0; }

/* 区块之间的统一间隔（2026-09-12 UI 审计）：新加的 #where / #practical 此前**没有任何
   垂直内边距**（实测 padding 0/0），页面节奏完全由标题的 margin 偶然决定 —— 视觉上就是
   「时紧时松」。现在每个区块自带同一个上间距，改一个令牌即可调整全页节奏。 */
:root { --page-gap: clamp(2.5rem, 6vw, 4.25rem); }

.home-where, .home-practical, .home-seasonal { padding-block: var(--page-gap); }
.home-seasonal { background: color-mix(in srgb, var(--card-soft) 72%, transparent); }
.home-seasonal .task-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* 首屏视觉换成照片：.hero-visual 原本为「手机框 + 悬浮卡」预留了 740px 高与居中
   place-items，直接塞照片会撑出大片空白 —— 这三条把它还原成照片框。 */
.hero-visual--photo {
  min-height: 0;
  padding-block: 0;
  place-items: stretch;
  align-self: center;
}

.hero-photo {
  position: relative;
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius-xl);
  /* 比悬浮顶栏低一档：此前照片卡用的是全局 --shadow（很重），而顶栏几乎平的，
     视觉上「该浮起来的导航像贴纸、内容卡被抬得过重」。 */
  box-shadow: var(--shadow-card);
}

.hero-photo img {
  display: block;
  width: 100%;
  height: clamp(320px, 46vw, 560px);
  object-fit: cover;
}

/* CC BY/BY-SA 城市图必须署名。⚠️ 这条规则**当前在生成的 HTML 里命中 0 次**，
   但绝不能按「死规则」删掉 —— 发射方是数据条件分支：
   `generate-localized-site.mjs` 只在 `heroCity.credit` 有值时才渲染它，
   而当前的 heroCity 恰好是 beijing（Unsplash 授权，免署名）。
   credits.json 里 37 条有 30 条是 CC 授权，一旦把杭州/成都这类换到首屏，
   缺了这条 CSS 就会得到一行**没有底衬的裸署名文本**压在照片上
   （「有 DOM 没样式」，与 #30 的筛选钩子同一类缺陷）。
   收尾轮 21 一度误删，靠「生成器能发射的类名 ↔ CSS 定义」的比对捞回来。 */
.hero-photo-credit {
  position: absolute;
  right: 0.75rem;
  bottom: 0.75rem;
  margin: 0;
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--pine-900) 62%, transparent);
  color: var(--white);
  font-size: 0.72rem;
}

/* ---- 首页族按原型重写（收尾轮 17 批 1，见台账 §⑩）------------------------------
   改前三块分别借用了索引页的 .index-list/.index-card、功能页的 .feature-grid/
   .feature-card，和一张带描边的 .app-entry 白卡 —— 于是首页与 /cities/ 索引页、
   应用页看起来是同一套卡，没有自己的层级。现在按原型 website.css 的口径各给一档：
   城市卡「图左文右 + 整卡可点 + 一行区域·看点」，实务卡「无底卡、顶部一条松绿实线」，
   app 入口「柔底横条」。字号全部取站内已有的档（1.25rem / 1.14rem / 1rem / 0.92rem），
   不新增散档 —— 收尾轮 15 刚把「大写小标」的 6 档收成 2 档，别再散回去。
   ========================================================================== */
.city-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 clamp(1.5rem, 4vw, 3rem); }
.city-card {
  position: relative;
  display: grid;
  grid-template-columns: 9.4rem minmax(0, 1fr);
  gap: 1.25rem;
  align-items: start;
  min-width: 0;
  padding: 1.35rem 0;
  border-bottom: 1px solid var(--line);
  color: inherit;
  text-decoration: none;
}
.city-card .photo { margin: 0; overflow: hidden; border-radius: var(--radius-md); }
.city-card .photo img { display: block; width: 100%; height: 100%; min-height: 7rem; object-fit: cover; }
/* min-width:0 —— 卡片正文是 grid 项，默认 min-width:auto 会让「不许换行的
   .index-spot 芯片」把整张卡撑破容器（实测 /cities/ 索引页 360px 横向溢出 69px、
   首页城市橱窗 27px）。与 .index-list 的 minmax(0,1fr) 是同一个坑的两半，别删。
   （#30 之前这条挂在 .index-card > div 上，卡片改 .city-card 后随族搬过来。） */
.city-card .copy { min-width: 0; padding-top: 0.3rem; }
.city-card h3 { margin: 0; font-size: 1.25rem; }
.city-card p { margin: 0.4rem 0 0; color: var(--muted); font-size: 1rem; line-height: 1.6; }
.city-card .tiny { font-size: 0.92rem; color: var(--brand-ink); }
/* CC 署名行：与 .tiny 同档（0.92rem 是本页已有档，不新开一档），用 --muted 压成次要层。
   它不是装饰 —— credits.json 有值的图缺这行就是违约，validate-site 逐城断言。 */
.city-card .credit { margin-top: 0.3rem; color: var(--muted); font-size: 0.92rem; }
.city-card .arrow { position: absolute; right: 0; top: 1.7rem; color: var(--brand-ink); }
.city-card:hover h3, .city-card:focus-visible h3 { text-decoration: underline; text-underline-offset: 0.28em; }

/* 行间距此前是 `gap: 0 clamp(...)` —— 纵轴 0，于是「行前必读」六张卡上下贴合、
   只靠一条 2px 顶线分隔，整片看起来是六段文字而不是六张卡。横纵都留白。 */
.task-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1.15rem, 1.8vw, 1.6rem);
}
/* 卡片化（2026-10-05）：此前只有一条 2px 绿色顶线 + 纯纸底，与站内其它卡（.index-card
   等）形态不一致。改用既有角色令牌 --card/--line/--radius-lg/--shadow-soft-2，
   深色模式与窄色板都跟着走（令牌在 2144/2154 已有覆写），不新开颜色。
   flex 列 + .read 的 margin-top:auto 让同排卡片等高、行动行底部对齐。 */
.task-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: clamp(1.15rem, 1.6vw, 1.5rem);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--card);
  box-shadow: var(--shadow-soft-2);
  color: inherit;
  text-decoration: none;
  transition: box-shadow 180ms ease, transform 180ms ease;
}
.task-card:hover, .task-card:focus-visible { box-shadow: var(--shadow-card); transform: translateY(-2px); }
.task-card h3 { margin: 0.45rem 0 0; font-size: 1.14rem; letter-spacing: -0.005em; }
.task-card p { margin: 0.5rem 0 0; color: var(--muted); font-size: 1rem; line-height: 1.62; }
.task-card .read {
  display: block;
  margin-top: auto;          /* 等高对齐：短正文的卡也把行动行压到同一基线 */
  padding-top: 1.15rem;      /* 与原 margin-top:1.15rem 同一档间距，改为内边距才吃得到 auto */
  color: var(--brand-ink);
  font-size: 0.92rem;
  font-weight: 800;
}
.task-card:hover .read, .task-card:focus-visible .read { text-decoration: underline; text-underline-offset: 0.25em; }

/* 季节区只有一条时（例如国庆一条），两列网格会让它缩在左半边、右半边空一大片。
   把它铺满整行，同时正文限宽、行动行靠右 —— 否则单卡横跨 1240px 后每行都拉到
   140+ 字符宽，扫读会跳行。窄屏（<=680）回到纵列，见该断点内的覆盖。 */
.home-seasonal[data-seasonal-count="1"] .task-grid { grid-template-columns: minmax(0, 1fr); }
.home-seasonal[data-seasonal-count="1"] .task-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  /* 显式三行（序号 / 标题 / 正文）两个作用：① 让 .read 的 `grid-row: 1 / -1`
     真的是「跨满整卡」而不是只跨第 1 行（隐含网格只有一条行线时 -1 就是第 1 行）；
     ② 把文字钉进第 1 列后行数确定，行动行才能靠 align-self:end 落到卡片底部。 */
  grid-template-rows: auto auto auto;
  align-items: end;
  column-gap: clamp(1.5rem, 4vw, 3rem);
}
/* 行主序自动放置会把正文落到 col2/row2（按行扫描那一格比 col1/row3 先遇到），
   于是桌面版式变成「正文在右、行动行浮在顶上」——与上面注释写的「正文左、行动右」相反。
   2026-10-06 实测（1432 宽）：`阅读攻略 →` 与序号 `01` 同一行、正文挤在右列。
   把文字三块显式钉到第 1 列，第 2 列整列留给行动行。 */
.home-seasonal[data-seasonal-count="1"] .task-card > p,
.home-seasonal[data-seasonal-count="1"] .task-card > h3 { grid-column: 1; }
.home-seasonal[data-seasonal-count="1"] .task-card p { max-width: 62ch; }
.home-seasonal[data-seasonal-count="1"] .task-card .read {
  grid-column: 2;
  grid-row: 1 / -1;
  margin-top: 0;
  align-self: end;
  white-space: nowrap;
}

/* app 入口横条（原型 .bridge）：柔底、无描边，比白卡弱一档 —— 首页最后要收得住，
   不再和上面的实务卡抢「主行动」的分量。 */
.bridge {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(1.25rem, 4vw, 3rem);
  margin-block: var(--page-gap);
  padding: clamp(1.5rem, 4vw, 2.5rem);
  border-radius: var(--radius-xl);
  background: var(--card-soft);
}
.bridge h2 { margin: 0.35rem 0 0.6rem; }
.bridge p { margin: 0; color: var(--muted); }
.bridge .actions { margin: 0; justify-content: flex-end; flex-wrap: wrap; }

/* 标题行右侧的「看全部 →」是原型 sectionHead 的另一半；只在这三块启用 flex，
   因为 .section-heading/.narrow 还被应用页用着（居中排版会跟着散）。 */
.home-where .section-heading,
.home-practical .section-heading,
.home-seasonal .section-heading,
.privacy-callout .section-heading {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 1.75rem;
  max-width: none;
}
.home-where .section-heading > div,
.home-practical .section-heading > div,
.home-seasonal .section-heading > div,
.privacy-callout .section-heading > div { min-width: 0; }
.home-where .section-heading a,
.home-practical .section-heading a,
.home-seasonal .section-heading a,
.privacy-callout .section-heading a {
  flex: none;
  display: inline-flex;
  align-items: center;
  min-height: 44px; /* 触控目标下限 */
  font-size: 0.95rem;
  font-weight: 800;
  color: var(--brand-ink);
  text-decoration: none;
  white-space: nowrap;
}
.home-where .section-heading a:hover,
.home-practical .section-heading a:hover,
.home-seasonal .section-heading a:hover,
.privacy-callout .section-heading a:hover { text-decoration: underline; text-underline-offset: 0.25em; }

/* 下载页的 iOS 说明（2026-09-12）：不要给人「只支持 Android」的印象，iOS 用户要有出口。 */
/* `.ios-note`（#31c 删）：下载页的「非 Android 用户」提示改走 `.callout`（原型 `.callout`），
   这条规则失去唯一消费者 —— 留着就是下一个「按老类名查样式」的陷阱。 */

@media (min-width: 1560px) {
  :root { --max: 1240px; }
}

@media (max-width: 980px) {
  .hero {
    min-height: auto;
    grid-template-columns: minmax(0, 1fr);
    padding-top: 4rem;
  }

  /* 原为 9ch（旧 13ch 时代的遗留）：单列 hero 下把标题压成 227–284px 宽的 5 行窄条，
     而同一屏的首屏照片有 668–948px 宽（实测 700/768/900/980px）。单列时给舒适行宽。 */
  h1 { max-width: 24ch; }

  .hero-visual { min-height: 720px; }
  .hero-visual--photo { min-height: 0; max-width: 720px; margin-inline: auto; }
  .bridge { grid-template-columns: minmax(0, 1fr); }
  .bridge .actions { justify-content: flex-start; }
  .hero-photo img { height: clamp(260px, 52vw, 420px); }
  /* 首页族按原型重写（收尾轮 17 批 1）的响应式：单列城市卡、两列实务卡。
     980 以下城市卡的 9.4rem 图位仍然成立（正文最窄也有 ~330px），所以只并成一列。 */
  .city-grid { grid-template-columns: minmax(0, 1fr); }
  .task-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .feature-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .screenshots { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .app-hero { grid-template-columns: minmax(0, 1fr); }
  /* 文章族（批 2）的两栏收窄在 `.spread` 定义之后另起 @media（原因见那里的注释）。 */
  .bridge > .button { justify-self: start; }
}

@media (max-width: 680px) {
  .site-header {
    align-items: flex-start;
    border-radius: var(--radius-xl);
    flex-direction: column;
    /* 卡内上下留白不均（视觉评审：logo 距顶 14px、语言面板距底 26.5px）→ 统一。 */
    padding: 0.95rem;
  }

  /* 手机端导航：此前是自动换行成两行、右端参差（236/286 对不到卡右界 358），
     且文字左缘比 logo 右缩 11px。改成等宽 3 列、左对齐，行尾自然对齐。 */
  .site-header nav {
    /* 三列按内容宽度排（不是 equal 1fr）：等高列会在第三列右侧留 36–45px 死区，
       且两行文字右端对不齐。max-content + space-between 让首项贴左界、末项贴右界。 */
    display: grid;
    grid-template-columns: repeat(3, max-content);
    gap: 0.15rem 0.75rem;
    width: 100%;
    justify-content: space-between;
    margin-left: 0;
  }
  .site-header nav > a {
    justify-content: flex-start;
    justify-self: start;
    padding-inline: 0;
  }
  /* 三列对齐体系：首列贴左、中列居中、末列贴右 —— 否则两行右端差 7.5px（Guides 698 vs The app 713）。 */
  .site-header nav > a:nth-child(3n + 2) { justify-self: center; }
  .site-header nav > a:nth-child(3n) { justify-self: end; }
  /* 选中态胶囊此前紧贴字形（左右仅 1px）。用负外边距给胶囊留白，文字仍落在列起点。 */
  .site-header nav > a[aria-current="page"] {
    padding-inline: 0.45rem;
    margin-inline: -0.45rem;
  }
  .site-header nav .language-switcher {
    grid-column: 1 / -1;
    margin-top: 0.5rem;
    /* 面板内左右内边距不对称（label 侧 14px vs 胶囊侧 7px）。 */
    padding-right: 0.35rem;
  }

  /* CTA 行撑满栏宽（此前两枚胶囊共 322px，右侧空 35px，视觉上左重右轻）。 */
  .actions { width: 100%; }
  /* 等宽撑满 + 收内边距：否则「Practical guides」在 172px 的按钮里折成两行，两枚按钮视觉重量不齐。
     `min-width` 不能是 `0`：配 `white-space: nowrap` 时它允许把盒子压到文字以下，于是长词语言整段文字
     溢出胶囊、压到相邻按钮上（实测 ru-RU 应用页 @390：文字需 212px、盒子只有 147px）。
     取 `min(max-content, 100%)` 两头的事故都要挡：`max-content` 让放不下的那一枚换到整行；
     但连整行也放不下时（ru「Политика конфиденциальности」max-content 311px > 360 视口的 280px 内容宽）
     纯 `max-content` 会把页面顶出横滚，所以再拿 `100%` 兜住 —— 此时按钮占满整行、文字在胶囊内换行。 */
  .actions .button { flex: 1 1 0; min-width: min(max-content, 100%); padding-inline: 1rem; }

  nav { justify-content: flex-start; }
  nav a { padding-inline: 0.7rem; }
  .language-switcher {
    width: 100%;
    justify-content: space-between;
    border-radius: 18px;
  }
  .language-switcher select {
    max-width: min(15rem, 62vw);
  }

  .hero { padding-top: 3rem; }
  h1 {
    width: 100%;
    max-width: 100%;
    font-size: clamp(2.5rem, 11.5vw, 4.2rem);
    letter-spacing: -0.055em;
  }
  .hero-visual { min-height: auto; padding: 1rem 0 7rem; }
  .floating-card { left: 50%; transform: translateX(-50%); bottom: 1rem; }
  /* 点分短句在 390 下靠折行成纵列，只收一档间距（原型的 10px 字号不跟：站内正文有可读下限）。 */
  .facts-strip { gap: 0.4rem 1.1rem; padding-block: 0.9rem; }
  .feature-grid { grid-template-columns: minmax(0, 1fr); }
  .task-grid { grid-template-columns: minmax(0, 1fr); }
  .home-seasonal .task-grid { grid-template-columns: minmax(0, 1fr); }
  /* 单卡横向版式（桌面：正文左、行动右）在 390 下会把正文压到 200px 出头，
     反而比纵列更窄 —— 回到普通卡片形态，行距由 .read 的 padding-top 顶开。 */
  .home-seasonal[data-seasonal-count="1"] .task-card {
    display: flex;
    flex-direction: column;
  }
  .home-seasonal[data-seasonal-count="1"] .task-card .read {
    margin-top: auto;
    white-space: normal;
  }
  .screenshots { grid-template-columns: minmax(0, 1fr); }
  /* 城市卡在 390px 下按原型同样收一档图位（150→112px），否则正文只剩 200px 出头。 */
  .city-card { grid-template-columns: 7rem minmax(0, 1fr); gap: 1rem; }
  .city-card .photo img { min-height: 5.25rem; }
  /* 标题行的「看全部 →」在窄屏换行，且与标题左对齐（原型 @680 同一处理）。
     不跟着缩字号：那会新开一档（站内原本只有 `.step-action .pill` 用 0.85rem，
     批 2 随指南族重写连同这一档一起删掉了）—— 换行已经解决窄屏挤压，字号不是问题的解。 */
  /* 纵列而不是继续横排：@360 横排下链接只剩 94~104px 宽（vi 的 «Tất cả cẩm nang…» 排成
     5 行、en 63px/5 行），不溢出了但读不成一条整句。#34-#7 改纵列后全容器宽可用。 */
  .home-where .section-heading,
  .home-practical .section-heading,
  .home-seasonal .section-heading,
  .privacy-callout .section-heading { flex-direction: column; align-items: start; gap: 0.5rem; }
  .home-where .section-heading a,
  .home-practical .section-heading a,
  .home-seasonal .section-heading a,
  .privacy-callout .section-heading a { white-space: normal;
    /* `flex: none`（基础规则 0 0 auto）在这里还留着：允许换行并不会让盒子变窄，
       于是长字符串语言整条溢出标题行 —— vi 首页 @360 实测该链接 353.9px、右缘 377.9px，
       而容器 `div.section-heading` 只到 344px（#34-#7 补 360 档才暴露）。
       放开收缩的同时**必须去掉** `min-width: 44px`：它把 flex 的自动最小尺寸压到比最长单词还窄，
       于是文字溢出自己的盒子 —— ru 首页 @360 单因注入实测：只把这三条链接的 min-width 设回 44px
       就把 scrollWidth 从 360 顶到 420（`a` 的 scrollWidth 104 > clientWidth 53、163 > 62），
       而**没有任何元素矩形越界**（行内溢出不进 getBoundingClientRect，所以明细列表是空的）。
       去掉后自动最小尺寸 = min-content：既换行又不 spill；触控目标由 `min-height: 44px` 保证。 */
    flex: 0 1 auto; }
  .site-footer { flex-direction: column; }
  /* 页脚链接实测只有 23px 高（<44px 触控目标）。给纵向内边距，不改字号与排版。 */
  .site-footer a { display: inline-block; padding-block: 0.5rem; }
  /* 文章族（批 2）的窄屏版式**不写在这里**：本文件里 `.spread` 的基础规则在后面
     （同特异性下后者胜），写在这儿会被顶掉 —— 390 下实测目录仍占 208px 左栏、
     正文被挤到 94px，而"横向溢出 0 / console 0"的复扫照样全绿。见 `.issue` 之后。 */
}

/* 顶栏折叠（#34-#17，2026-09-23）。桌面/平板（>400）：折叠器**完全不参与布局也不出现** ——
   `.nav-menu` 用 display:contents 把 details 与 nav 直接交还给 .site-header 的轨道，
   details 自身 display:none（连带 summary 从无障碍树里消失）。
   实测四档（1440/681/500/401）× 5 页 = 20 行的（头部高度, 最长链接宽度）与折叠前**逐条相等**、
   0 行不同。复现 = `python3 scripts/proto-review/nav_collapse_probe.py --plan ship`：`出厂闭` 的
   头部高必须仍是 126.8 / 224.9 / 294.5 / 294.5（th app 那一族 77.8 / 175.9 / 247 / 247，
   vi 隐私页在后两档也是 247），
   改前那一版树的对照数只能对快照跑（探针文档说怎么起 `SWEEP_BASE`）。
   ⇒ validate-site 有配对判据：这两条规则与下面的 ≤400 两条必须同存，漂移即报错。 */
.site-header .nav-menu { display: contents; }
.site-header .nav-toggle { display: none; }

/* 360dp 档（#34-#7）→ 已被上面的折叠方案取代：≤680 的三列 `repeat(3, max-content)` 轨道**不可压**，
   360 下 ru 的三列和 341.4px > 容器 295.6px ⇒ 第三列越界 14px；降到两列（`repeat(2, max-content)`）
   代价是 401→400 头部常驻 +47.4px（294.5→341.9），而 300 档仍把**文档**撑宽：
   vi 隐私页 doc 311（>视口 11px，越界 24 条里有 3 条 `a` + 1 条 `form.language-switcher`）、
   ru 城市索引 doc 302（1 条 `a`）。根因都是「不可压轨道 + nowrap 下拉行」这一类。 */
@media (max-width: 400px) {
  /* 折叠态 = 品牌行 + 一枚 Menu 药丸：头部常驻 139.4px（300 档 151.5）。
     比两列那版短 202.5px（首页族 341.9→139.4；6 项的隐私/app 页 294.5→139.4 短 155.1px），
     且 400/390/360/320/300 五档**顶栏自身横向溢出 0**、doc 不再被撑宽（300 档 vi 311→300、
     ru cities 302→300）。vi 隐私页那张表在 ≤681 各档常驻 14–19 条越界，改前改后同数 ⇒ 与折叠无关，
     是另一条既有缺陷。⇒ 轮 39 已把那次复扫跑完：全 14 族 × 10 语 × 2 主题，把 300 与 320 放进
     **同一次**复扫逐页态对账 ⇒ `ovf>1` 0/0、`doc≠视口` 0/0、被可滚容器过滤掉的越界元素两档**逐页态**
     同数（各 860 条，都落在 40 个页态的 `div.table-wrap` 里，且这一列确认对宽度敏感）
     ⇒ 300 升成认证档，支持下限从 320 改成 300（判据与复扫口径记在 `site_sweep.py` 的档位注释）。
     展开态取两列等宽而不是一列：一列实测头部 504–551px（300 档视口高 640，等于占满整屏），
     两列 349.5–435px；`minmax(0, 1fr)`（不是 max-content）是**这一档不再撑宽文档**的直接原因，
     代价是最长词条换行（300 档 ru 链接宽 145.5→116.6）。 */
  .site-header .nav-menu { display: block; width: 100%; }
  .site-header .nav-toggle { display: block; }
  .site-header .nav-toggle > summary {
    /* 与导航项同款的药丸：字号/字重/内边距照抄 `nav a`，可点区 47.1px ≥ 44px 触控建议值。 */
    list-style: none;
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    padding: 0.6rem 0.9rem;
    border-radius: var(--radius-pill);
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    background: color-mix(in srgb, var(--pine-700) 8%, transparent);
    color: var(--brand-ink);
    font-weight: 700;
    font-size: 0.98rem;
  }
  .site-header .nav-toggle > summary::-webkit-details-marker { display: none; }
  .site-header .nav-menu > nav { display: none; }
  .site-header .nav-toggle[open] ~ nav {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.15rem 0.75rem;
    justify-content: start;
    width: 100%;
    margin-top: 0.5rem;
    margin-left: 0;
  }
  /* ≤680 的三列对齐体系（中列居中、末列贴右）在菜单里不成立，逐项贴左。 */
  .site-header .nav-toggle[open] ~ nav > a { justify-self: start; justify-content: flex-start; padding-inline: 0; }
  .site-header .nav-toggle[open] ~ nav > a[aria-current="page"] {
    padding-inline: 0.45rem;
    margin-inline: -0.45rem;
  }
  .site-header .nav-toggle[open] ~ nav .language-switcher { grid-column: 1 / -1; margin-top: 0.5rem; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-duration: 1ms !important;
  }

  .phone-frame.tilted { transform: none; }
  /* transition-duration:1ms 只是把抬起动画变成瞬跳，位移本身还在 ——
     位移是与平面相关的运动，reduce 下直接撤掉，保留阴影变化（无位移）。 */
  .task-card:hover, .task-card:focus-visible { transform: none; }
}

/* ============================================================================
   内容层（批2）：guides / cities / phrasebook —— 由 site/scripts/generate-content-pages.mjs 产出。
   骨架词汇现在**只有一套**：`.article-shell / .page-head / .spread / .article`
   （原型 guideDetail 的骨架）。city / guide / help / news / download / privacy /
   phrasebook 七族都已转过来，旧那套 `.policy-shell / .help-article / .answer-card`
   （以及 `.app-cta`）已随 #31 全部撤掉。共用的列表与药丸：`.link-list / .source-list /
   .faq-list / .pill / .toc`。
   ========================================================================== */

.breadcrumbs {
  margin: 1.5rem 0 1rem;
  color: var(--muted);
  font-size: 0.88rem;
  font-weight: 600;
  /* `.breadcrumbs` 是 <nav>，而全局 `nav { display:flex; justify-content:space-between }`
     是给顶栏设计的 —— 于是面包屑的 5 个子节点被推到两端，1440 下"Home"跑到最右边
     （改前/改后截图都看得到，是存量缺陷）。原型 `.bread` 的口径是左对齐 + 换行，
     这里显式覆盖回来。`.toc` 那条注释早就点过同一个坑（"必须显式覆盖全局 nav"）。 */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: 0.5rem;
}
/* 链接走「导航与链接文字」这个角色令牌，不写死色阶。
   原来面包屑/正文链接/来源链接全是赭红，一页上出现四五组红链接，页面发燥，
   2026-09-12 改成松绿深阶 —— 但改的是**字面令牌** `--pine-700`，而深色模式下它是
   **填充色**档（#2f6f5e），于是文章族的链接在暗面掉到 2.68/3.04（收尾轮 18 改前实测：
   .link-list a / .source-list a 2.68、.breadcrumbs a 3.04，AA 门槛 4.5）。
   `--brand-ink` 浅色模式下就等于 `--pine-700`（#1e4d40），暗面才是浅松墨字 ——
   即浅色模式**逐点不变**，只把暗面拉回可读。 */
.breadcrumbs a {
  color: var(--brand-ink);
  text-decoration: none;
  font-weight: 700;
  /* 面包屑链接实测 23px 高（390 下 <44px 触控目标）。与页脚、目录同一口径给到
     2.75rem，不动字号与排版。原型 `.bread a` 只有 6px 纵向内边距（≈37px），
     这一条比原型严 —— 触控目标是站自己的门槛，不是从原型抄来的。 */
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
}
.breadcrumbs a:hover { text-decoration: underline; }

/* ============================================================================
   文章族（收尾轮 18 / C 批 2）：按原型 `guideDetail` 的骨架 ——
   .article-shell（宽容器）> .page-head（页头）+ .spread（左目录 / 右正文两栏）。
   原型 `.spread` 是 210px + 60px + 750px；这里右栏直接用站内既有的阅读尺度
   `--measure`(704px)，左栏 13rem(208px)、间距 3.5rem(56px) —— 数值就近取站内档，
   不为了对齐原型像素去新开一档行宽。
   ========================================================================== */
.article-shell {
  width: min(var(--max), calc(100% - 2rem));
  margin: 0 auto 5rem;
}

/* 页头：面包屑 + 小标 + 标题 + 导语 + 核对日期。原型 max-width:860px，
   这里取 54rem(864px) —— 比正文栏宽，让标题行不受两栏切分影响。 */
.page-head { max-width: 54rem; }
.page-head h1 {
  /* 内容页标题不能压过正文：原 clamp(2.7rem,7vw,5.6rem) 桌面端 89.6px，
     是正文(17px)的 5.3 倍 —— 标题抢走全部视觉层级，正文显得像脚注。
     收到 ~44px，比例约 2.6:1。七族内容页共用这一档。 */
  max-width: 22ch;
  font-size: clamp(2rem, 3.4vw, 2.75rem);
}

.spread {
  display: grid;
  grid-template-columns: 13rem minmax(0, var(--measure));
  gap: 3.5rem;
  align-items: start;
  padding-bottom: 3rem;
}
/* 正文显式落在第 2 栏：不足 3 个章节时 `tocParts` 不给目录，那时第 1 栏留空，
   正文也不会被挤进 13rem 的目录位。 */
.spread .article { grid-column: 2; min-width: 0; }

/* 左栏目录：从「正文里的卡片」变成「 sticky 的旁注」（原型 `.toc`）——
   无底、无圆角，顶部一条 2px 松绿实线当栏目线，跟着正文滚动。 */
.spread > .toc {
  position: sticky;
  top: 1.75rem;
  margin: 0;
  padding: 1.25rem 0 0;
  background: none;
  border: 0;
  border-top: 2px solid var(--brand-fill);
  border-radius: 0;
  box-shadow: none;
}
/* 目录项不带编号（原型 `.toc` 也没有）：`<ol>` 的序号在 1440 会挂出栏外、
   在 680 横排时又整个消失（inline-block 的 marker 不渲染），同一族两个断点
   长得不一样就是这种细节。索性两边都不要 marker。 */
.spread > .toc ol { padding-left: 0; gap: 0; list-style: none; }
.spread > .toc a {
  display: flex;
  align-items: center;
  min-height: 2.75rem;
  font-size: 0.88rem;
  line-height: 1.35;
}

.article > section { margin-bottom: 2.7rem; }

/* 短答：原型 `.answer` —— 柔底 + 左侧一条 3px 松绿实线，不留圆角的那一角。
   字号跟正文同档（站内没有 18px 这一档，不为一块底色新开散档）。
   自己不写 margin-bottom：章节间距由 `.article > section` 统一负责（那条特异性更高，
   写了也是死声明）；标签与正文的间距由 `.eyebrow` 的 0.7rem 底边距负责。 */
.answer {
  padding: 1.25rem 1.5rem;
  border-left: 3px solid var(--brand-fill);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--card-soft);
}
/* `.eyebrow` 是单类选择器（特异性 0,1,0），写 `.answer p` 会把它一起吃掉 ——
   小标的角色色必须由角色令牌负责，这里显式排除。 */
.answer > p:not(.eyebrow) { color: var(--brand-ink); }

/* 步骤：序号是 CSS 计数器（原型 `.steps li:before` 的 decimal-leading-zero），
   所以 `<ol>` 关掉原生编号，`<li>` 之间用一条横线分段。
   字号取 h2 的 clamp 上限 1.9rem —— 复用本页已有的档，不新开一档。 */
.steps { margin: 0.9rem 0 0; padding: 0; list-style: none; counter-reset: step; }
.steps li {
  position: relative;
  counter-increment: step;
  padding: 0 0 1.6rem 3.5rem;
  margin-bottom: 1.6rem;
  border-bottom: 1px solid var(--line);
}
.steps li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute;
  top: -0.2rem;
  left: 0;
  color: var(--brand-ink);
  /* 走骨架 token。此前这里是字面 Georgia 栈，理由写的是「tokens.css 没有任何页面
     链接它，所以 var(--serif) 是空转」—— **那条理由是错的**：本文件第 1 行就是
     `@import url("./tokens.css")`，实测 `getComputedStyle(:root).getPropertyValue
     ('--serif')` = `Georgia, "Songti SC", "Noto Serif SC", serif`，与字面值逐字相同。
     按 tokens.css 自己的规矩（不要在 styles.css 里另写一套），一律用 token。 */
  font-family: var(--serif);
  font-size: 1.9rem;
  line-height: 1.1;
}
.steps h3 { margin-top: 0; }
.steps .pill { margin-top: 0.45rem; }

/* 提示块：原型 `.callout` 的米色底留着，但**分层不再押在这层底色上**（轮 31 补的一条边）。
   原型 `body{background:var(--paper)}` 是平涂，所以它那条 `background:var(--paper2)` 在任何
   位置都是同一强度的信号；本站给 body 加了 `--paper → --paper-2` 渐变并且带
   `background-attachment: fixed`（上面 :191-195）⇒ 渐变按**视口**铺开、52% 拐点每一屏都有，
   同一个块滚到视口下部时，它的半透明底会和脚下那层画布合成成同一个颜色。
   实测（en-US/guide/bind-overseas-card 一块，dpr2）：面色 vs 画布 Δ欧氏 亮色从视口 6% 处 7.1 →
   35% 处 9.3 → 80% 处 **1.7**；暗色 8.1 → 6.9 → **1.4**（Δ<3 属肉眼分不清）。
   补的边取站内已有的发丝档（先例是 `.privacy-callout` 那条 `--line` 半像素边框），不新开权重：
   32 组读数（1440/390 两档宽 × 视口 4 个位置 × 亮暗 × dpr1/dpr2）里这条边的 Δ
   最低 19.8、最高 64.5，而它替掉的底色在同点位最低只剩 1.4 —— 强一个数量级以上。
   （Chrome 把 0.5px 的计算值报成 `1px` 并按满 alpha 上屏，dpr1 与 dpr2 同强度，所以这一档
   不需要"半像素会不会被抗锯齿抹掉"的担心；块高因此 +2px。）
   整体仍比 `.answer` 弱一档：那边是 3px 品牌实线单边 + 不透明 `--card-soft`，这边是发丝 + 半透明。 */
.callout {
  padding: 1.5rem;
  border: 0.5px solid var(--line);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--paper-2) 70%, transparent);
}

/* 故障对照：原型 `.issue-grid` 两列，每格顶上一条 3px 朱砂线（`--seal`）。
   格内**不给底色也不给圆角**（原型只有边框）：这一块已经在 `.answer`/`.callout` 之后，
   再加一层白卡就是一页三种卡片语言；边框 + 顶部朱砂线足够把它读成一个分组。
   圆角这里留 0 还有一条硬理由：我第一版给它写了 `0 0 12px 12px`（下圆上方），
   复扫的圆角并集当场多出一种形状 —— 而原型根本没有这条，是自己加的戏。 */
.issue-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.9rem; }
.issue {
  padding: 1.25rem;
  border: 1px solid var(--line);
  border-top: 3px solid var(--accent);
}
.issue h3 { margin-top: 0; font-size: 1.15rem; }
.issue p { margin: 0.3rem 0 0; }

/* 响应式紧跟族定义之后。踩过的坑：这两条本来写在文件前面那两条 @media 里，
   而同特异性下**源码顺序**说了算 ⇒ 后面的 `.spread { display: grid }` 把
   `display: block` 顶掉，390 下正文列实测只有 94px（目录仍占 208px 左栏），
   而"横向溢出 0 / console 0 / token 对账 480/480"的复扫全绿 —— 溢出判据看不出
   "没溢出但栏宽塌了"。改版式的响应式必须和它覆盖的规则挨着放。
   @980：目录栏收窄一档（原型 @1100 同一处理），正文仍守 --measure 不跟着变宽。
   @680：两栏堆叠成单列；目录从左侧 sticky 变成正文前一条**横排**目录 ——
   竖排 9 项会在 390 下把首屏全部吃掉，横排换行只占 3~4 行（实测 6~8 行/9 项），
   行高仍 ≥44px（原型同一口径）。 */
@media (max-width: 980px) {
  .spread { grid-template-columns: 10.5rem minmax(0, var(--measure)); gap: 2.25rem; }
}

@media (max-width: 680px) {
  .spread { display: block; padding-bottom: 1rem; }
  .spread .article { grid-column: auto; }
  .spread > .toc {
    position: static;
    margin-bottom: 1.75rem;
    /* 上边也要留白：堆叠后目录直接接在页头"Reviewed …"下面，
       只给下边距会让"On this page"贴着核对日期（390 截图实测）。 */
    padding: 1rem 0;
    border-top: 0;
    border-bottom: 1px solid var(--line);
  }
  .spread > .toc ol { display: block; }
  /* `<li>` 默认 block —— 只改 `<a>` 不会横排，九项还是九行。 */
  .spread > .toc li { display: inline-block; }
  .spread > .toc a { display: inline-flex; margin-right: 1.25rem; }
  .issue-grid { grid-template-columns: minmax(0, 1fr); }
  /* 序号留白 3.5rem 在 390 下吃掉近一成行宽；收到 2.75rem。序号字号不动
     （1.9rem 是本页 h2 的上限档，不新开档）。 */
  .steps li { padding-left: 2.75rem; }
}

/* 正文排版只保留 `.article` 一个作用域（每章一个 `<section>` + 少量裸 `<h2>`）。
   #30 把 city 也转成 `.article` 之后，`.content-article` 已经没有任何页面发射它了，
   按这段原来的约定整族撤掉 —— 口径并存两处，就意味着以后只改一处、另一族静默跑偏。 */
.article h2 { scroll-margin-top: 1.5rem; }

.article h2 {
  font-size: clamp(1.4rem, 2.5vw, 1.9rem);
  letter-spacing: -0.018em;
}
/* 城市页仍有**裸 `<h2>`** 直属 `.article`（预约段标题），而全局 `h2 { margin: 0 }`
   会生效 —— 于是「Good to know」这类区块标题会和上一块贴死（截图里
   “Frequently asked questions” 直接压在卡片下沿）。这里只给直属 h2 补垂直节奏；
   章节内的 h2 由 `<section>` 自己的 margin-bottom 负责，标题不留上边距。
   收尾 CTA 的 h2 在 `<aside class="bridge">` 里，由 `.bridge h2` 负责，不走这条。 */
.article > h2 { margin-top: 2.6rem; }
.article > h2:first-of-type { margin-top: 1.5rem; }
.article > h2 + * { margin-top: 0.9rem; }

/* 正文节奏：段间距与行高。17px 正文配 1.72 行高，中文密排也不挤。
   必须**排除 `.eyebrow`**：`margin` 简写会把小标自己的 `margin: 0 0 .7rem` 底边距
   清零（(0,1,1) > (0,1,0)；`.eyebrow` 的 color/size 早就因为同一个坑钉了
   !important，见上面 :320 那段注释）。 */
.article p:not(.eyebrow) { margin: 0.75rem 0 0; line-height: 1.72; }
/* 标题之后的节奏：一份规则管所有「h2 的第一块内容」。
   `dl` 是给 `.city-facts` 留的（速览表自己 margin-top:0，靠这一条拿 0.9rem）——
   漏掉它，速览表就会和上面的 h2 贴死。 */
.article h2 + p, .article h2 + ul, .article h2 + ol, .article h2 + div, .article h2 + dl { margin-top: 0.9rem; }

.article h3 {
  /* 原 1.05rem(16.8px) —— 比正文(17px)**还小**，层级反了。 */
  font-size: 1.18rem;
  letter-spacing: -0.01em;
  margin: 0.15rem 0 0.4rem;
}

/* 跨站引用区块（2026-09-28）。视觉上必须和「相关问句」**分得开** ——
   它指向另一个域名，读者在点之前就该看出来那是另一处，不是本站的相关问答。
   做法是给它自己的描边与左侧标记，而不是靠缩进或灰度去暗示。 */
.other-site {
  margin-top: 2rem;
  padding: 1rem 1.1rem;
  border: 1px solid color-mix(in srgb, var(--brand-ink) 28%, transparent);
  border-left: 3px solid var(--brand-ink);
  border-radius: 4px;
}
.other-site h2 {
  margin-top: 0;
  font-size: 1.05rem;
}
.other-site-note {
  margin: 0.45rem 0 0;
  color: var(--muted);
  font-size: 0.92rem;
  line-height: 1.55;
}
.other-site .link-list {
  margin-top: 0.7rem;
}

.link-list, .source-list, .rule-list, .booking-list {
  margin: 0.4rem 0 0;
  padding-left: 1.2rem;
  display: grid;
  gap: 0.55rem;
}
/* 正文链接 = 「导航与链接文字」角色令牌 + 下划线，与上面 .breadcrumbs a 同一个口径
   （写死 `--pine-700` 的那次改版把暗面链接带到 2.68，见上面的注释）。 */
.link-list a, .source-list a {
  color: var(--brand-ink);
  font-weight: 700;
  text-underline-offset: 0.18em;
  text-decoration-color: color-mix(in srgb, var(--brand-ink) 35%, transparent);
}
/* 来源区列的是**原始 URL**（如 https://www.bmy.com.cn/guide/notice/367.html），
   默认不断字 —— 360px 下把城市页顶出横向滚动 85px（实测 scrollWidth 445）。
   与 code 同一处理：允许任意处断行。 */
.source-list li, .link-list li, .source-list a, .link-list a {
  overflow-wrap: anywhere;
  word-break: break-word;
}
.link-list a:hover, .source-list a:hover { text-decoration-color: currentColor; }
.source-list { font-size: 0.95rem; }
.source-date { color: var(--muted); font-size: 0.88rem; }

/* 文章族里的链接列表按原型 `.link-list` 的口径改成**整行可点 + 发丝分隔**
   （原型：`li { border-bottom:1px } ` + `a { display:block; padding:13px 0 }`）。
   只作用于 `.article`：city / phrasebook / help 还没重写，那边保持项目符号列表。
   顺带把触控目标从实测 23px 抬到 ≥44px（390 下整行都能点）—— 原型自己在
   `.bread a` 上没做到 44px，这一条按站自己的触控门槛办，见 `.breadcrumbs a`。
   药丸（platform / kind）用 flex 排在行尾：`<a>` 变块后它们否则会掉到下一行。 */
.article .link-list, .article .source-list { padding-left: 0; gap: 0; }
.article .link-list li, .article .source-list li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  list-style: none;
  border-bottom: 1px solid var(--line);
}
.article .link-list a, .article .source-list a { flex: 1 1 auto; padding: 0.7rem 0; }
.booking-list { list-style: none; padding-left: 0; }
.booking-list > li {
  padding: 0.85rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--card);
}
.booking-list > li p { margin: 0.35rem 0 0; color: var(--muted); }

/* 城市详情（#30）：按原型 .city-facts / .highlights / .highlight / .eat-list 重写骨架。
   全部用站内既有令牌（--card/--line/--muted/--brand-ink/--accent-deep）；不引入原型色名。
   清掉上面三条 .content-section 死规则（card 壳已拆成裸 <section>）。 */
/* 速览（原型 `.city-facts`）：2×2 的发丝规格表 —— 容器只有顶边一条线、每格底边一条线，
   格内 dt 在上、dd 在下。原型在 760px 下**保持两列**（那个 @media 只重声明了 dd 字号，
   没有改 grid-template），所以这里也不收成单列。
   dt 是「大写小标」角色：原型靠**全局** dt 规则实现（11px / uppercase / .08em 字距），
   本站同一个角色有专门的 --label-sub* 令牌（改前的 `.city-fact dt` 用的就是它）。
   ⚠️ 这一版一度把 dt 写成 `0.95rem` 句首大写 —— 等于把 12.8px 大写小标悄悄换成
   15.2px 正文灰，同时让 --label-sub* 三个令牌失去**全部**消费者（它们就是为这个角色
   建的）。全站可见文本比对看不见字号与大小写，是 token 对账才定性的。 */
.city-facts {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
  border-top: 1px solid var(--line);
}
.city-facts > div {
  padding: 1.3rem 0.95rem 1.3rem 0;
  border-bottom: 1px solid var(--line);
}
.city-facts dt {
  font-size: var(--label-sub-size);
  font-weight: 800;
  letter-spacing: var(--label-sub-track);
  text-transform: uppercase;
  color: var(--label-sub);
}
.city-facts dd { margin: 0.45rem 0 0; font-size: 0.875rem; }

.highlights { margin: 0.8rem 0 0; display: grid; gap: 1rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.highlight {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--card);
}
.highlight h3 { font-size: 1.1rem; }
.highlight .mini { margin: 0; font-size: 0.88rem; color: var(--muted); }
.highlight .hl-k {
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 0.72rem;
  color: var(--brand-ink);
  margin-right: 0.3rem;
}
.highlight-url { margin: 0.2rem 0 0; }

.eat-list { margin: 0.6rem 0 0; padding-left: 0; display: grid; gap: 0.5rem; list-style: none; }
.eat-list li { display: flex; flex-wrap: wrap; gap: 0.4rem 0.6rem; align-items: baseline; }
/* 每道菜现在是 <h3 class="eat-name"> + <p class="eat-note">（2026-10-31：从 <strong>/<span>
   升成标题与段落，检索器与 AI 才会把「Hotpot」当成一个实体，并且有 #eat-N 可深链）。
   视觉必须与改前一致：h3 自带的字号与上下外边距在这里全部复位，
   颜色、字重、字号逐条对齐原来的 .eat-list strong / span。 */
/* 选择器带上 .article 是**故意**的：正文族有一条
   `.article p:not(.eyebrow) { margin: .75rem 0 0; line-height: 1.72 }`（0,2,1），
   只写 `.eat-list .eat-note`（0,2,0）会输给它 —— 菜名下的说明会被塞进 12px 上边距、
   行高从 28.05px 变成 26.14px。0,3,0 稳定胜出，不依赖规则书写顺序。 */
.article .eat-list .eat-name {
  font: inherit;          /* 字号/字族/行高全部回到正文：h3 默认 1.25rem + 1.16 行高 */
  font-weight: 700;       /* 原 <strong> 的 700 */
  margin: 0;              /* 原 <strong> 无外边距；h3 默认 1.15rem/0.55rem */
  color: inherit;         /* ⚠️ 全局 h1,h2,h3 把标题刷成 --pine-900；<strong> 原先是正文色。
                             不复位这一条，菜名会从正文灰黑悄悄变成深松绿 —— 只有肉眼能发现 */
  letter-spacing: normal; /* 全局 h3 有 -0.015em 字距 */
}
.article .eat-list .eat-note {
  margin: 0;
  line-height: inherit;   /* 原 <span> 继承的是 li 算好的 28.05px，不是正文段的 1.72 倍 */
  color: var(--muted);
  font-size: 0.95rem;
}

/* 城市详情「官方站点」链接：原型 .text-button（无背景、accent 文字 + 箭头）。 */
.text-button {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.3rem;
  color: var(--accent-deep);
  font-weight: 700;
  text-decoration: none;
  text-underline-offset: 0.18em;
}
.text-button:hover { text-decoration: underline; }

.note { margin: 0.8rem 0 0; color: var(--muted); font-size: 0.85rem; font-style: italic; }

/* 指南索引行（#30）：原型 .guide-row —— 序号 + 阶段药丸 + 标题 + 箭头。 */
.guide-row {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr) 1.5rem;
  gap: 1rem;
  align-items: center;
  padding: 1rem 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  text-decoration: none;
}
.guide-row:hover h3 { text-decoration: underline; text-underline-offset: 0.2em; }
.guide-row .number { font-family: var(--serif); font-size: 1.4rem; color: var(--muted); }
.guide-row > div { min-width: 0; }
.guide-row h3 { margin: 0.1rem 0 0; font-size: 1.2rem; }
.guide-row p { margin: 0.3rem 0 0; color: var(--muted); font-size: 0.95rem; }
.guide-row small { display: block; margin-top: 0.35rem; color: var(--muted); font-size: 0.85rem; }
.guide-row .row-arrow { color: var(--brand-ink); text-align: right; }
@media (max-width: 760px) {
  .highlights { grid-template-columns: 1fr; }
  .guide-row { grid-template-columns: 2.2rem minmax(0, 1fr) 1.2rem; }
}

.city-hero { margin: 1.25rem 0 0; }
.city-hero img {
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
}
.city-hero figcaption { margin-top: 0.6rem; color: var(--muted); font-size: 0.9rem; }

.index-list { list-style: none; margin: 1.25rem 0 0; padding: 0; display: grid; gap: 1rem; grid-template-columns: minmax(0, 1fr); }

/* ============================================================================
   索引页筛选（Stage4）：/<L>/cities/ 与 /<L>/guides/ 的搜索 + 下拉筛选 + 结果计数 +
   空态，对齐原型 website.js 的 filterBar/empty。与短语手册（.phrase-filter/.phrase-empty）
   同一套做法：原生 JS、无依赖、无 localStorage。计数沿用 .eyebrow，文案模板由生成器
   写在 data-count-template 上（JS 里没有硬编码文案）。
   ========================================================================== */
.index-filter { display: grid; gap: 0.85rem; margin: 1.5rem 0 0.5rem; }
.index-filter .filter-field { display: grid; gap: 0.35rem; margin: 0; }
.index-filter label { font-size: 0.875rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.index-filter input,
.index-filter select {
  width: 100%;
  min-height: 44px; /* 触控目标下限：交互控件高度 ≥44px */
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--card);
  font: inherit;
  color: var(--ink);
}
/* 搜索框与下拉并排（宽屏）。窄屏堆叠，避免两列把 360px 挤到横向溢出。 */
@media (min-width: 720px) {
  .index-filter { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); align-items: end; }
}
/* 筛选契约（收尾轮 20 定的形）：**钩子 `[hidden]` + 钩子选择器**，两边都挂在
   `.filter-item` 上，谁都不认表现形式类。三个筛选页（/cities/ /guides/ /phrasebook/）
   的行元素都同时带自己的表现形式类与这个钩子，`[hidden]` 规则因此只有这一条。 */
/* 行在 author 样式里是 display:grid/flex（.city-card / .guide-row / .phrase-row），
   会盖过 UA 的 [hidden]{display:none}——不写这一条，筛选后卡片仍然留在页面上
   （JS 走 row.hidden = !hit）。
   ⚠️ 为什么要换成钩子：这批（#30）把卡片体类从 `.index-card` 拆成城市 `.city-card` /
   指南 `.guide-row`，而筛选恰好挂在旧类上 —— 卡片一改名，`querySelectorAll` 就
   再没命中过任何元素，`row.hidden` 照常赋值、不报错、不抛异常，静态契约与视觉复扫
   都不点交互 ⇒ 一路绿灯。钩子把「表现形式」和「交互契约」解耦：以后换卡片词汇，
   筛选不用跟着改（改动点只剩 JS 里那一个 `.filter-item`）。 */
.filter-item[hidden] { display: none; }
.index-empty { text-align: center; color: var(--muted); }

/* 原型 phrasebook（#31e）：筛选收进 `.phrase-head`，词条为双列（中文 大字号衬线 /
   目标语），行间细线分隔（原型 `border-bottom`），不再是纸感小卡。
   中文衬线走骨架 token `--serif`（同 `.steps li::before`：tokens.css 由 styles.css
   顶部的 @import 载入，`--serif` 实测解析得到，不再是字面栈）。 */
.phrase-head { margin: 1.5rem 0 0; }
.phrase-filter { display: grid; gap: 0.35rem; margin: 0; }
/* 表头与筛选 label 同一个「大写小标」角色（原 --label-sub 组），所以共用一条规则：
   分两处写就会一处改字号、另一处留在旧值，而色@档门禁是按组数统计的。 */
.phrase-filter label,
.phrase-labels span {
  font-size: var(--label-size);
  font-weight: 800;
  letter-spacing: var(--label-sub-track);
  text-transform: uppercase;
  color: var(--label-sub);
}
.phrase-filter input {
  width: 100%;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--card);
  font: inherit;
  color: var(--ink);
}
.phrase-list { list-style: none; margin: 0.5rem 0 0; padding: 0; }
/* 原型 `.phrase-labels`：两列表头压在列表上方，2px 松绿下划线。
   列宽/间距必须与 `.phrase-row` 逐字相同，否则表头与词条错位；而 ≤640 那条媒体查询里
   `.phrase-row` 收成单列，表头在那里一起收掉（见本文件末尾的 640 段）。 */
.phrase-labels {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: 1.4rem;
  margin: 1.5rem 0 0;
  padding: 0.95rem 0;
  border-bottom: 2px solid var(--brand-ink);
}
.phrase-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  align-items: center;
  gap: 1.4rem;
  padding: 1.1rem 0;
  border-bottom: 1px solid var(--line);
}
.phrase-row .zh {
  font-family: var(--serif);
  font-size: 1.9rem;
  line-height: 1.25;
  /* 用 --brand-ink 而不是 --pine-700：后者暗面是 #2f6f5e（中绿），
     390 下 .zh 只有 22.4px（未达 WCAG 大字阈值）⇒ 暗面 3.04 破 AA。
     --brand-ink 亮 #1e4d40 / 暗 #cfe0d9，两主题都过（8.11 / ~11.5）。 */
  color: var(--brand-ink);
}
.phrase-row .english { font-size: 1.14rem; color: var(--text-body); }
.phrase-empty { text-align: center; color: var(--muted); }
@media (max-width: 760px) {
  .phrase-row { gap: 1rem; }
  .phrase-row .zh { font-size: 1.4rem; }
  .phrase-row .english { font-size: 1rem; }
}

/* 原型 news（#31b）：索引是「日期 + 摘要」整行可点的 `.news-row`（原型：左侧日期列 +
   右侧 eyebrow/h3/p，行间 `border-top`）；详情沿用 `.article-shell` 两栏骨架，
   `.meta` 放发布/复核两行小字，收尾 CTA 走 `.bridge`。日期列取原型 100px → 6.25rem。 */
.news-list { display: grid; }
.news-row {
  display: grid;
  grid-template-columns: 6.25rem minmax(0, 1fr);
  gap: 2.5rem;
  padding: 1.6rem 0;
  border-top: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
}
.news-row:last-child { border-bottom: 1px solid var(--line); }
.news-row time { font-size: 0.875rem; color: var(--muted); }
.news-row h3 { font-size: 1.25rem; margin: 0.35rem 0 0.5rem; }
.news-row p { font-size: 0.95rem; color: var(--muted); margin: 0; }
.news-row .eyebrow { margin: 0; }
.meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.25rem;
  margin: 0 0 2rem;
  font-size: 0.875rem;
  color: var(--muted);
}
@media (max-width: 760px) {
  .news-row { grid-template-columns: 4.5rem minmax(0, 1fr); gap: 1.25rem; }
  .news-row h3 { font-size: 1.14rem; }
}

/* 原型 download（#31c）：`.download-grid` 两列（左 CTA 面板 / 右技术参数 `dl`），
   下方 `.article` 承载安装步骤（`.steps`）、更新说明与平台提示（`.callout`）。
   面板底色取站内既有的 `--card-soft`（**不是**原型的 `--deep` 深底）：#28 起 `.bridge`
   已把重面板统一收敛到 `--card-soft`，这里跟同一套语言；换深底要重新量一遍对比度（§⑧）。 */
.download-grid {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: 3rem;
  align-items: start;
  margin: 0 0 3rem;
}
.download-panel {
  padding: 1.75rem;
  border: 1px solid color-mix(in srgb, var(--ink) 11%, transparent);
  border-radius: var(--radius-lg);
  background: var(--card-soft);
}
.download-panel .button { width: 100%; margin-bottom: 1rem; }
.download-panel .meta { margin: 0 0 0.75rem; }
.download-panel .note { margin: 0; }
.tech-list { margin: 0; }
.tech-list > div {
  display: grid;
  grid-template-columns: 6rem minmax(0, 1fr);
  gap: 1rem;
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--line);
}
.tech-list dt { color: var(--muted); font-size: 0.92rem; }
.tech-list dd { margin: 0; font-size: 0.95rem; overflow-wrap: anywhere; }
/* 下载页的 SHA-256 走原型的等宽块 `.code`（原型：`font-family:monospace` + `font-size:12px` +
   `background:var(--paper2)` + `padding:18px` + `1px solid var(--line)` + `border-radius:var(--r-field)`）。
   载体与原型不同，是刻意的：原型那一块是 `</dl>` **之后**的
   `<div class="code">SHA-256: [release checksum belongs here]</div>` —— 它手上没有真哈希可放，
   只能另开一块占位；本站这个 `dd` 里就是真哈希（64 位十六进制），再开一块等于同一个串印两遍，
   所以这里把块的**形状**给 `dd` 里的 `<code>`，而不是抄它的 DOM 位置。
   （原型那段标记在 `website.js` 的 `downloadPage()` 里，不在 `website.html` —— 只扫后者会数成 0 次。）

   四处不照抄，各带理由：
   · 圆角取 `--radius-sm`(12px) 而非 `--r-field`(4px) —— 复扫认证的是 #main 圆角 **9 档并集**
     （12/16/20/22/24/28/44/999 再加一条 `12px 12px 0 0` 复合），4px 会凭空开第 10 档。
   · padding 取 0.75rem 而非 18px —— 320 档 `dd` 列只有 176px，双边 36px 吃掉 20% 可用宽。
   · 字号不动（全局 `code` 的 0.9em = 13.68px）—— 换行位置与块高两个读数会跟着重测，而字号不是
     这一条的缺陷对象（块内换行由全局 `code` 的 `overflow-wrap:anywhere` 兜住）。
   · 原型的 `margin:15px 0` 没抄 —— 它那块是 `</dl>` 后独立的一段，需要自己拉开；本站这块在
     `.tech-list > div` 里，行自带 `padding:0.9rem 0`，再加外边距就是同一处间距算两遍。
   底色 `--paper-2` 看不看得见，是量过的不是推的：块底 #efece3 对局部画布 #f6f3ec 的像素 Δ欧氏
   亮 13.4 / 暗 9.8（改前同一坐标 Δ=0.0，即纯画布）。但上一版解释**为什么**看得见时写错了机制：
   它说"`body` 渐变 52% 之后才转深，而这一行在文档深度 36–41%" —— 本文件 :195 是
   `background-attachment: fixed`，那条渐变按**视口**铺开，跟文档深度无关；上面那组 Δ 只是"块此刻
   停在视口上部"的读数，同一块滚到视口下部会另算一个数。
   所以这一块从不靠这层底色分层：它自带 `border: 1px solid var(--line)`，底色只是第二层线索。
   同一处机制在"只靠底色"的块上是什么后果，见 :1452 的 `.callout`。
   作用域收在 `.tech-list dd > code` 而不是裸 `code`：全站 `<code>` 60 处里只有这 10 处该成块，
   另外 50 处是隐私页正文里的行内反引号，裸选择器等于给整段正文刷底色。 */
.tech-list dd > code {
  display: block;
  padding: 0.75rem;
  background: var(--paper-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
@media (max-width: 760px) {
  .download-grid { grid-template-columns: minmax(0, 1fr); gap: 2rem; }
}

/* ============================================================================
   城市攻略 v2（批69）：城市页从「预约清单」改成「玩什么 / 吃什么 / 怎么约」。
   本处只剩两条**不随 #30 改写**的规则：定位行 .city-tagline、「为什么来」.city-intro。
   速览 / 特色推荐 / 吃什么 三块已按原型改写成 .city-facts / .highlights / .highlight /
   .eat-list，规则统一收在上方「城市详情（#30）」那一段 —— 别在这里再写一份：
   后写的同名规则会盖掉先写的，上一轮就因此让速览被旧的 auto-fit 两列栅格劫持过。
   ========================================================================== */

/* 定位行：H1 下面是 seoTitle（一行长标题），先给一句人话的定位把语气放松下来。 */
.city-tagline {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.05rem, 1.7vw, 1.25rem);
  color: var(--accent-deep);
  margin: 0.5rem 0 0;
}

/* 「为什么来」：比正文大一档，接在头图之后。 */
.city-intro {
  margin-top: 1.1rem;
  font-size: 1.06rem;
  line-height: 1.75;
  color: var(--text-soft);
}


/* 区域徽标（城市索引卡）：21 座城市时索引页是长列表，把 7 个区域（枢纽 / 古都 /
   海滨 / 西南 / 东北 / 云南 / 丝路）标在城名旁边，读者不必逐个点开就能筛出想去的那一类。
   词条走 labels 的 `region*`，与下拉筛选的 `data-region` 同源。
   ⚠️ 它是**信息**不是装饰：收尾轮 20 的 city-card 重写一度把它漏掉（变量还在，
   只是没人渲染它，data-term 里仍含区域名 ⇒ 筛选照常、卡面看不到），
   全站可见文本比对才把它捞出来。 */
.index-region {
  font-size: 0.8rem; font-weight: 700; letter-spacing: 0.02em;
  color: var(--accent-deep); background: var(--card-soft);
  border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 0.08rem 0.5rem;
  white-space: nowrap; vertical-align: 0.15em;
}

.index-spots { display: flex; flex-wrap: wrap; gap: 0.35rem 0.75rem; margin-top: 0.5rem !important; }
.index-spot { font-size: 0.92rem; color: var(--muted); white-space: nowrap; }

@media (max-width: 640px) {
  .phrase-row { grid-template-columns: minmax(0, 1fr); gap: 0.15rem; }
  /* 原型在窄口仍留两列表头，因为它 `.phrase-row` 到窄口仍是两列；本站词条在 ≤640
     竖排（中文一行、释义一行），双列表头就没有对应的列可指了 —— 收掉，
     竖排行本身已经是「词在上、义在下」。 */
  .phrase-labels { display: none; }
  /* 芯片在宽屏是「不许换行的标签」，但 360px 下「⚔️ Terracotta Army & the First
     Emperor's Mausoleum」这类芯片的 min-content 就有 354px，会把卡片顶破容器
     （实测首页 27px、/cities/ 索引页 52px 横向溢出）。窄屏放开换行即可。 */
  .index-spot { white-space: normal; }
  /* 同一类缺陷的第二处，#34-#16 补 320 档时才被抓到：区域徽标是 `white-space: nowrap` 的
     **行内**盒，ru 的 «Древние столицы и природа» 在 320 下把 `<h3>` 顶到 357px（溢出 37px），
     es 348px（28px）；360 档两个语言都还塞得下 ⇒ 这一档从来没跑过也就从来没报过。
     只放开 `white-space` 不够：行内盒一旦折行，padding/边框/圆角会按行片段各画一份
     （一枚胶囊断成两截），所以要连 `display: inline-block` 一起给 —— 盒子整体换到下一行，
     内部再折字，圆角保持完整。 */
  .index-region { display: inline-block; white-space: normal; }
}

/* 文章页目录（guide/city/help 通用）：细边框卡片，锚点列表，移动端不折叠——
   目录本身就是快速导航，折叠反而多一步。注意 .toc 是 <nav>，必须显式覆盖
   全局 nav 的 flex 布局和 nav a 的胶囊样式（否则标题/列表被推到两端）。 */
.toc {
  display: block;
  margin: 1.5rem 0 2rem;
  padding: 1rem 1.25rem;
  background: var(--cream-50);
  border: 0.5px solid var(--line);
  border-radius: var(--radius-lg);
}
.toc-title {
  margin: 0 0 0.5rem;
  font-weight: 500;
  font-size: 0.9rem;
  color: var(--accent-deep);
}
.toc ol {
  margin: 0;
  padding-left: 1.25rem;
  display: grid;
  gap: 0.35rem;
}
.toc li { margin: 0; }
.toc a {
  display: inline;
  min-height: 0;
  padding: 0;
  border-radius: 0;
  color: var(--ink);
  font-weight: inherit;
  font-size: inherit;
  text-decoration: none;
}
.toc a:hover { background: none; text-decoration: underline; }
html { scroll-behavior: smooth; }

/* ============================================================================
   #33 补做：404 与应用页的原型骨架（收尾轮 22）
   原型 `notFound` = 左文案右巨号数字；窄屏 `column-reverse` 把数字提到上面。
   数字整块 aria-hidden ⇒ 纯装饰，不进门禁的可见文本；`.error-number::after`
   那枚朱砂印章同样只做装饰，写在 CSS 里而不是 DOM 里：它不是文案，
   没有要翻译的东西，也不该进十语言词表与「汉字混入」护栏。
   ========================================================================== */
.error-page {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(2rem, 7vw, 4.4rem);
  align-items: center;
  padding-block: clamp(2.5rem, 7vw, 3.8rem) clamp(3rem, 10vw, 6.2rem);
}
.error-page h1 { font-size: clamp(2rem, 5vw, 2.7rem); }
.error-number {
  position: relative;
  color: var(--pine-50);
  font: clamp(7.5rem, 18vw, 11.25rem)/1 var(--serif);
  letter-spacing: -0.08em;
}
.error-number::after {
  content: '未找到';
  position: absolute;
  right: 0.6rem;
  bottom: 0;
  padding: 0.45em 0.6em;
  border: 1px solid var(--accent-deep);
  color: var(--accent-deep);
  font: 1.125rem/1.2 var(--serif);
  /* 数字本体为了压字距写的 -0.08em 会按**父级字号**算成绝对值（180px 下 -14.4px），
     而 letter-spacing 不随子级字号缩放 —— 印章继承它会把三个汉字叠成一坨。
     原型 CSS 同样漏了这一条（它的 :after 也没重置），这里是修而不是照搬。 */
  letter-spacing: normal;
  transform: rotate(-8deg);
}

@media (max-width: 760px) {
  .error-page {
    display: flex;
    flex-direction: column-reverse;
    gap: 1.6rem;
    align-items: flex-start;
    padding-block: 1.9rem 3.4rem;
  }
}

/* ============================================================================
   深色模式（2026-09-13）
   前置条件已经具备：所有颜色都走令牌（62 处内联字面量收敛为令牌 + color-mix 派生，
   浅色模式的计算样式逐点比对无变化）。所以这里**只覆盖令牌值**，不动任何规则。
   品牌姿态：不是"反相"，而是把纸感换成**深松墨底 + 浅松墨字**，朱砂整体提亮一档
   以保证对比度（深底上的 #c2401f 只有 ~3.4:1，不到正文 4.5:1）。
   角色令牌（--heading/--brand-ink/--brand-fill/--panel-dark/--on-panel/--device-bezel）
   分开覆盖 —— 同一个色相在深色下的取值由角色决定，不能一刀切。
   ========================================================================== */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    /* 纸：深松墨（保留一点绿调，避免纯灰失去品牌感） */
    --paper: #12181a;
    --paper-2: #0d1214;
    --card: #1b2426;

    /* 墨：正文用暖白，次级降饱和 */
    --ink: #eef3f0;
    --heading: #f4f8f5;
    --text-body: #cfdad5;
    --text-soft: #bfccc7;
    --muted: #9fb0aa;

    /* 线：深底上的描边要亮一档才看得见 */
    --line: #2d3b3d;
    --line-strong: #8ea69e;
    --line-soft: color-mix(in srgb, var(--ink) 14%, transparent);
    --pine-line: color-mix(in srgb, var(--pine-500) 34%, transparent);

    /* 松：文字侧翻浅，填充侧保持可辨认的品牌块 */
    --pine-900: #e9f2ed;
    --pine-700: #2f6f5e;
    --pine-500: #7fb3a2;
    --pine-50: #1d2a29;
    --brand-ink: #cfe0d9;
    --brand-fill: #2f6f5e;
    --brand-fill-ink: #f2f8f5;

    /* 印（朱砂）：深底上提亮，正文级对比仍 ≥4.5:1 */
    --accent: #f0805c;
    --accent-deep: #f59b7c;
    --seal-soft: #2c1a15;

    /* 琥珀与状态色 */
    --amber: #d8b071;
    --amber-on-dark: #e0b871;
    --warn-bg: #2b2416;
    --warn-ink: #e6c98a;
    --warn-line: #8a6a2f;

    /* 面 */
    --card-soft: #1e2a2b;
    --surface-glass: color-mix(in srgb, var(--card) 86%, transparent);

    /* 深面板：深色模式下要**比纸底亮一档**才分得出来（浅色模式下它是暗块） */
    --panel-dark: #223032;
    --panel-dark-2: #1b2729;
    --on-panel: #eef3f0;
    --device-bezel: #0a0f10;

    /* 阴影：深底上单靠暗度分不出层级，改用更高不透明度 + 更紧的扩散 */
    --shadow: 0 24px 70px rgba(0, 0, 0, 0.65);
    --shadow-chrome: 0 12px 32px rgba(0, 0, 0, 0.55);
    --shadow-card: 0 12px 28px rgba(0, 0, 0, 0.45);
    --shadow-soft: 0 16px 40px rgba(0, 0, 0, 0.42);
    --shadow-soft-2: 0 6px 16px rgba(0, 0, 0, 0.42);
    --shadow-soft-3: 0 12px 34px rgba(0, 0, 0, 0.42);
    --shadow-brand: 0 10px 24px rgba(0, 0, 0, 0.5);
    --shadow-deep: 0 14px 34px rgba(0, 0, 0, 0.5);
  }

  /* 图片在深底上会显得刺眼：给照片与截图一点亮度收敛与描边 */
  .hero-photo img, .city-card .photo img, .screenshots img {
    filter: brightness(0.94) saturate(0.96);
  }

  .hero-photo, .city-card, .privacy-callout {
    border-color: var(--line);
  }
}


/* 图片加载失败的占位（Stage4，2026-09-18）。
   此前全站没有任何回落：城市照片/App 截图 404 就留一个空洞。
   注意 <img> 是**替换元素**、::before/::after 不渲染 —— 所以由内联 onerror
   把它换成一个带文案的 span（构造器见 scripts/app-config.mjs 的 photoFallbackAttrs）。 */
.photo-error {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  min-height: 88px;
  padding: 0.75rem;
  background: var(--card-soft, var(--paper-2));
  border: 1px dashed var(--line);
  border-radius: var(--radius-lg);
  color: var(--muted);
  font-size: 0.76rem;
  line-height: 1.4;
}

/* 空态里的"清除搜索与筛选"按钮（Stage4）。此前只写了按钮、没有任何样式
   ⇒ 它会渲染成一颗裸的 UA 默认按钮，和站内其他 CTA 完全不像。 */
.filter-clear {
  margin-top: 0.85rem;
  min-height: 44px;
  padding: 0.6rem 1.1rem;
  background: var(--pine-700, var(--teal-900));
  color: var(--white);
  border: 0;
  border-radius: var(--radius-lg);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.filter-clear:hover { filter: brightness(1.08); }
