/* ==========================================================================
   中国文化产业促进会网络文化分会 · 官网样式
   设计基调：东方式留白 + 国家一级社团的庄重感
   ========================================================================== */

:root {
  /* 主色 */
  --red: #9E2A2B;
  --red-deep: #7C1D1E;
  --gold: #C8A45C;
  /* 中性 */
  --ink: #1B1B1B;
  --ink-2: #3D3D3D;
  --ink-3: #6B6B6B;
  --ink-4: #9A9A9A;
  --line: #E4E1DC;
  --line-soft: #EFEDE9;
  --paper: #FAF8F5;
  --white: #FFFFFF;
  /* 尺度 */
  --max: 1200px;
  --gap: 32px;
  --r: 2px;
  /* 字体 */
  --serif: "Source Han Serif SC", "Noto Serif SC", "Songti SC", "SimSun", Georgia, serif;
  --sans: "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", "Source Han Sans SC", sans-serif;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.9;
  color: var(--ink-2);
  background: var(--white);
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
ul, ol { list-style: none; }
button { font: inherit; cursor: pointer; border: none; background: none; color: inherit; }
code {
  font-family: ui-monospace, Consolas, Menlo, monospace;
  font-size: .92em;
  background: #F1EEF4;
  padding: 1px 6px;
  border-radius: 2px;
  color: var(--red-deep);
}

.container {
  width: 100%;
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 24px;
}

/* 顶部归属条：一条「宣纸题签」 -------------------------------------------- */
/* 原来是 36px 高的纯色 --ink 黑条，压在白色页头之上像贴了一块墨斑 —— 它信息量
   最低（就一句隶属关系），却是全站第一个撞见眼睛的色块，最不划算。
   ── v2：底纹换成与首屏同源的图片
   上一版铺的是 tex-paper.svg（240px 平铺的随机短纤维）。那套纤维单根只有 5–16px，
   在这一条 36px 高的横带里重复排开，读起来就是一排"短横线"，像故障的扫描线，
   而不是纸的质感 —— 用户反馈的"横条里是短横线"就是它。
   现在改用首屏那张 hero-tex.jpg（反相淡墨 214–246）：
   同一张图、同一套色，顶栏与首屏的材质语言就统一了，整条读作"淡墨横带"。
   ── 尺寸与定位：cover，必须铺满整条
   这里原本写的是「定宽 1400px」，是错的：图片宽度被钉死在 1400px，窗口一宽
   两端就露出纯纸色 —— 1920 下左右各空 260px，接缝肉眼可见，用户反馈的
   「底纹没铺满」就是它。cover 会把 900 高的图压扁吗？不会：压扁是
   background-size: 100% 100%。cover 只裁不压、比例不变，永远盖满整条 ——
   宽 W 的窗口按 W/1200 等比缩放，36px 只是从中切出的一条横切，
   山峦 / 水波的弧线照样看得见。
   background-position 的纵向 44% 取在淡墨较匀的一段，避开原图下方的重部。
   ── 上面两层墨晕保留
   左侧淡朱、右侧暖金两道偏心晕染，与页头、页脚用的是同一组色，
   是"纸上有痕迹"而不是图案。 */
.topbar {
  position: relative;
  background-color: #F3F0E9;
  background-image:
    /* 水渍：两道偏心的大半径晕染，位置不对称，读作"纸上的痕迹"而非图案 */
    radial-gradient(120% 260% at 8% 50%, rgba(158,42,43,.055) 0%, rgba(158,42,43,0) 46%),
    radial-gradient(100% 240% at 82% 50%, rgba(200,164,92,.085) 0%, rgba(200,164,92,0) 48%),
    /* 底纹：与首屏同一张反相淡墨图，取图中部的一条横切 */
    url("../img/hero-tex.jpg");
  background-size: auto, auto, cover;
  background-position: 0 0, 0 0, 50% 44%;
  background-repeat: no-repeat, no-repeat, no-repeat;
  color: var(--ink-2);
  font-size: 12.5px;
  letter-spacing: .04em;
  border-bottom: 1px solid rgba(158,42,43,.22);
}
.topbar .container {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 36px;
}
/* 浅底上金色只有 2:1 对比度，统一走朱红 */
.topbar a { color: var(--ink-3); transition: color .2s; }
.topbar a:hover { color: var(--red); }

/* 页头导航 --------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(255,255,255,.96);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--line);
}
.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 92px;
  gap: 24px;
}

.brand { display: flex; align-items: center; gap: 14px; }
.brand-logo {
  height: 52px;
  width: auto;
  object-fit: contain;
}
/* Logo 文件缺失时的文字徽标（自动回退） */
.brand-fallback {
  width: 52px; height: 52px;
  flex: 0 0 52px;
  border: 1.5px solid var(--red);
  border-radius: 50%;
  display: grid; place-items: center;
  color: var(--red);
  font-family: var(--serif);
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 0;
}
/* 品牌区：中文全称一行；「缩写 + 英文全称」一行（缩写在前）；两行左右两端对齐
   ── 两端对齐靠结构，不靠手调字距
   .brand-text 是竖排列（inline-flex column），列宽 = 较宽的那一行，副行用
   space-between 撑到与中文等宽 —— 换字体、换语言环境都不会跑位。
   ── 核心约束：谁宽谁定列宽，窄的一方必然被撑开
   副行比中文宽 → 中文逐个被拉开，表现为「疏排字距」；
   副行比中文窄 → 宽度被摊进英文词距，表现为「词与词散成一片」。
   两者都是把窄的一方拉向宽的一方。所以正确解法不是"选择让哪边被撑"，而是
   **让副行自然宽 ≈ 中文自然宽，两边都不用被撑**。
   上一版就是没守住这条：副行 371px 对中文 332.6px，中文被推开 2.75px/字
   （总字距 .19em），用户反馈"logo 里的中文字间距太宽"。现在把中文字号抬到
   22px、同时压副行，两者自然宽几乎相等 → 中文字距回到 .06em 的原本值。
   桌面档实测（SimSun 中文 / 微软雅黑英文，见 _build/measure_brand.py）：
     中文全称 22px/.06em  = 347.7px
     缩写 9px/.06em       =  55.5px
     英文全称 9.5px 单词和 = 271.6px（wordItems 拆词后项间无空白节点，
                                     参与计算的只有各单词宽度之和，不含空格）
     副行 = 55.5 + 6(gap) + 271.6 + .22em×9.5×7 ≈ 347.7px  ← 与中文自然宽持平
   ── 唯一的代价是英文变小
   9.5px 在 1x 屏幕上偏小，但它只是中文全称之下的装饰性小字，观感成立。
   若哪天不接受，"副行左对齐、放弃两端对齐"是另一条路 —— 代价是右端参差。
   改字号、改分会名称后请跑 _build/check_site.py，逐断点实测守着：
   要求「中文额外字距 ≤0.02em」且「英文词距落在 0.15–0.45em」。
   ── 缩写为什么整块渲染
   「CCPA-NCB」是连字符连成的单一代号，不含空白、也就没有可对齐的两端；
   拆成 flex 项再两端对齐，只会在字母之间撑出均匀空隙（C C P A - N C B）。
   所以缩写整块渲染 + flex: 0 0 auto，在副行里稳稳占住左端。
   ── 英文为什么除了 space-between 还要 gap
   当副行内容宽恰好就是列宽时，英文那侧一点剩余空间都没有，只靠 space-between
   会得到 0 词距、单词贴死。gap 兜住最小词距，space-between 只在中文更宽时才额外分摊。 */
.brand-text {
  display: inline-flex;
  flex-direction: column;
}
.brand-cn {
  display: flex;
  justify-content: space-between;
  font-family: var(--serif);
  font-size: 22px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--ink);
  letter-spacing: .06em;
  white-space: nowrap;
}
/* 末字不带字距，否则中文这行右端会多出一个空隙、与副行对不齐 */
.brand-cn > span:last-child { letter-spacing: 0; }
.brand-sub {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-top: 3px;
}
.brand-abbr {
  flex: 0 0 auto;
  font-size: 9px;
  line-height: 1.5;
  color: var(--ink-3);
  letter-spacing: .06em;
  white-space: nowrap;
}
.brand-en {
  flex: 1 1 auto;
  display: flex;
  justify-content: space-between;
  gap: .22em;
  font-size: 9.5px;
  line-height: 1.5;
  color: var(--ink-4);
}

.nav { display: flex; align-items: center; gap: 6px; }
.nav-link {
  position: relative;
  padding: 10px 18px;
  font-size: 15.5px;
  color: var(--ink-2);
  letter-spacing: .04em;
  transition: color .2s;
}
.nav-link::after {
  content: "";
  position: absolute;
  left: 18px; right: 18px; bottom: 2px;
  height: 2px;
  background: var(--red);
  transform: scaleX(0);
  transition: transform .25s ease;
}
.nav-link:hover { color: var(--red); }
.nav-link:hover::after,
.nav-link.is-active::after { transform: scaleX(1); }
.nav-link.is-active { color: var(--red); font-weight: 500; }

.nav-toggle { display: none; font-size: 24px; padding: 6px 4px; color: var(--ink); }

/* 首屏：宣纸留白 --------------------------------------------------------- */
/* 不用深色压场。米白底 + 极细点阵纹理 + 墨色字，靠留白与字号撑气势。 */
.hero {
  position: relative;
  background: var(--paper);
  color: var(--ink);
  overflow: hidden;
  border-bottom: 1px solid var(--line);
  /* 首屏的辅助文字色单独加深一档（只作用于 .hero 内部，不动全局变量）。
     ── 为什么非改不可
     全局 --ink-3 #6B6B6B 按**纯宣纸底**定的色，对比度 4.5:1 —— 一点余量都没有。
     首屏现在整屏都铺了底纹，文字区吃到约 82% 的淡墨（合成底色 250 → 221），
     原色立刻掉到 4.2:1，英文副标题 --ink-4 #9A9A9A 更只剩 3.0:1。
     要么把底纹压到几乎看不见，要么把文字压深 —— 选后者，因为"整屏有底纹"
     是这次要的效果，而调深 20 级灰阶肉眼几乎无感。
     ── 加深后（按合成底色 221 复核）
     主标题 12.7:1、栏目标签 5.5:1、导语 5.1:1、英文副标题 4.8:1、信息行 5.1:1。
     其他区块的底色仍是纯宣纸，不需要更深，所以不改成全局变量。 */
  --ink-3: #5A5A5A;
  --ink-4: #5E5E5E;
}
/* 纹理层：宣纸底 + 一层「反相淡墨」的图片底纹
   ── 为什么图片要先做反相淡墨处理
   手上所有配图都是深色调（hero-bg 均值亮度 23、about-culture 65）。深色图直接铺在
   浅底首屏上会糊成一片灰脏，文字也没法读。所以先灰度反相（深底→浅底、亮线→暗线），
   再把对比压进极窄的 [214,246] —— 得到一张本身就很淡的纹理，铺上去才不抢文字。
   生成脚本：_build/gen_hero_tex.py（换图后重跑一次即可）。
   ── v2：整屏都铺，但取景位不动
   上一版只在右侧铺（mask 从左侧全透明），左右不对称。现在铺满整屏，
   用 mask 留一点方向感：左侧 72% → 右侧 100%。
   ── 取景位必须与上一版一致（background-position 72% 44%）
   cover 下 position 决定"从原图里取哪一块"：v1 的 72% 44% 取的是原图偏右的中段，
   右侧那半边观感就是这么定下来的（用户明确说"现在右边的效果很好"）。
   把它改成 50% 会连带把右边也换成另一块图 —— 看起来像"两边都变了"，
   而不是"左边也加上了"。所以 position 一律沿用 v1，只放宽 mask 的左侧。
   ── 左侧为什么定在 72% 而不是更低
   文字就压在左侧 —— 纹理本身是 [214,246] 的淡墨，再压淡就白加了；
   文字可读性改由 .hero 内的 --ink-3 / --ink-4 加深来兜（见上）。
   ── 别把图调深
   判断标准：缩到 1/4 还能一眼看出纹理细节，就是过头了。 */
.hero-bg { position: absolute; inset: 0; pointer-events: none; }
#heroBg {
  background-image: url("../img/hero-tex.jpg");
  background-size: cover;
  background-position: 72% 44%;                       /* 与 v1 一致，别改 */
  background-repeat: no-repeat;
  -webkit-mask-image: linear-gradient(100deg, rgba(0,0,0,.72) 0%, rgba(0,0,0,.80) 30%, rgba(0,0,0,.94) 64%, #000 100%);
  mask-image: linear-gradient(100deg, rgba(0,0,0,.72) 0%, rgba(0,0,0,.80) 30%, rgba(0,0,0,.94) 64%, #000 100%);
}
/* 光晕层：右上暖金 + 左下淡朱，纯氛围，不遮挡文字 */
.hero-veil {
  background:
    radial-gradient(86% 68% at 94% 2%, rgba(200,164,92,.20) 0%, rgba(200,164,92,0) 60%),
    radial-gradient(64% 58% at 2% 100%, rgba(158,42,43,.05) 0%, rgba(158,42,43,0) 64%);
}
.hero-inner { position: relative; z-index: 1; }
/* 只写上下内边距，不要用 padding 简写。
   .hero-inner 与 .container 在同一元素上，而它定义在后面 —— 一条 `padding: 132px 0 108px`
   会把 .container 的左右 24px 一起覆盖成 0，首屏文字直接贴到视口边上（窄屏最明显）。 */
.hero-inner { padding-top: 132px; padding-bottom: 108px; max-width: 880px; }

.hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  letter-spacing: .28em;
  color: var(--red);
  margin-bottom: 28px;
}
.hero-eyebrow::before {
  content: "";
  width: 38px; height: 1px;
  background: var(--red);
}
.hero-title {
  font-family: var(--serif);
  font-size: 46px;
  line-height: 1.35;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: .04em;
  text-wrap: balance;
  margin-bottom: 22px;
}
.hero-en {
  font-size: 13.5px;
  letter-spacing: .16em;
  color: var(--ink-4);
  text-transform: uppercase;
  margin-bottom: 30px;
}
.hero-desc {
  font-size: 17px;
  line-height: 2.05;
  color: var(--ink-3);
  max-width: 640px;
  margin-bottom: 38px;
}
.hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 36px;
  padding-top: 30px;
  border-top: 1px solid rgba(27,27,27,.12);
  font-size: 14px;
  color: var(--ink-3);
}
.hero-meta strong { color: var(--ink); font-weight: 500; }

.hero-actions { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 34px; }

.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 13px 30px;
  font-size: 15px;
  letter-spacing: .06em;
  border: 1px solid transparent;
  transition: all .22s ease;
}
.btn-solid { background: var(--red); color: #fff; border-color: var(--red); }
.btn-solid:hover { background: var(--red-deep); border-color: var(--red-deep); }
.btn-ghost { border-color: #C9C4BB; color: var(--ink); }
.btn-ghost:hover { border-color: var(--red); color: var(--red); }
.btn-outline { border-color: var(--line); color: var(--ink-2); background: #fff; }
.btn-outline:hover { border-color: var(--red); color: var(--red); }

/* 通用区块 --------------------------------------------------------------- */
.section { padding: 96px 0; }
.section-paper { background: var(--paper); }
.section-line { border-top: 1px solid var(--line-soft); }

.section-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 52px;
}
.section-title {
  font-family: var(--serif);
  font-size: 32px;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: .08em;
  line-height: 1.4;
}
.section-en {
  font-size: 11.5px;
  letter-spacing: .2em;
  color: var(--ink-4);
  text-transform: uppercase;
  margin-top: 8px;
}
.section-sub {
  font-size: 15.5px;
  color: var(--ink-3);
  margin-top: 16px;
  max-width: 620px;
  line-height: 1.95;
}
.more-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: var(--ink-3);
  letter-spacing: .06em;
  white-space: nowrap;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--line);
  transition: all .2s;
}
.more-link:hover { color: var(--red); border-color: var(--red); }
.more-link::after { content: "→"; font-size: 13px; }

/* 数字 / 网格卡 ---------------------------------------------------------- */
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gap); }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--gap); }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gap); }

.tile {
  background: #fff;
  border: 1px solid var(--line);
  padding: 36px 30px 34px;
  transition: all .25s ease;
}
.tile:hover {
  border-color: var(--red);
  transform: translateY(-3px);
  box-shadow: 0 12px 30px rgba(27,27,27,.06);
}
.tile-index {
  font-family: var(--serif);
  font-size: 15px;
  color: var(--red);
  letter-spacing: .1em;
  margin-bottom: 16px;
}
.tile-title {
  font-family: var(--serif);
  font-size: 20px;
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 14px;
  letter-spacing: .04em;
}
.tile-text { font-size: 14.5px; color: var(--ink-3); line-height: 1.95; }

/* 新闻卡 ----------------------------------------------------------------- */
.card {
  background: #fff;
  border: 1px solid var(--line);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: all .25s ease;
}
.card:hover {
  border-color: var(--red);
  box-shadow: 0 14px 34px rgba(27,27,27,.07);
}
.card-cover {
  aspect-ratio: 16 / 9;
  background: linear-gradient(135deg, #EFEBE4 0%, #E2DDD4 100%);
  overflow: hidden;
  position: relative;
}
.card-cover img { width: 100%; height: 100%; object-fit: cover; }
.card-body { padding: 26px 26px 28px; flex: 1; display: flex; flex-direction: column; }
.card-top {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 12.5px;
  color: var(--ink-4);
  letter-spacing: .06em;
  margin-bottom: 14px;
}
.tag {
  display: inline-block;
  padding: 2px 10px;
  border: 1px solid var(--red);
  color: var(--red);
  font-size: 12px;
  letter-spacing: .06em;
  white-space: nowrap;
}
.tag-gray { border-color: var(--line); color: var(--ink-3); }
.card-title {
  font-family: var(--serif);
  font-size: 19px;
  font-weight: 700;
  line-height: 1.65;
  color: var(--ink);
  letter-spacing: .03em;
  margin-bottom: 12px;
  transition: color .2s;
}
.card:hover .card-title { color: var(--red); }
.card-sum {
  font-size: 14.5px;
  color: var(--ink-3);
  line-height: 1.9;
  flex: 1;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card-foot {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
  font-size: 13px;
  color: var(--ink-4);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.card-more { color: var(--red); }

/* 新闻列表行 -------------------------------------------------------------- */
.news-list { border-top: 1px solid var(--line); }
.news-item {
  display: grid;
  grid-template-columns: 128px 1fr 220px;
  gap: 32px;
  align-items: center;
  padding: 30px 8px;
  border-bottom: 1px solid var(--line);
  transition: all .22s ease;
}
.news-item:hover { background: var(--paper); padding-left: 20px; padding-right: 20px; }
.news-date {
  font-family: var(--serif);
  color: var(--ink-3);
  line-height: 1.3;
}
.news-date b { font-size: 30px; font-weight: 700; color: var(--ink); }
.news-date span { display: block; font-size: 12.5px; color: var(--ink-4); letter-spacing: .08em; }
.news-main {}
.news-item-title {
  font-family: var(--serif);
  font-size: 20px;
  font-weight: 700;
  color: var(--ink);
  line-height: 1.6;
  letter-spacing: .03em;
  margin-bottom: 8px;
  transition: color .2s;
}
.news-item:hover .news-item-title { color: var(--red); }
.news-item-sum {
  font-size: 14.5px;
  color: var(--ink-3);
  line-height: 1.85;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.news-thumb {
  width: 220px; height: 132px;
  object-fit: cover;
  background: linear-gradient(135deg, #EFEBE4, #E2DDD4);
}

/* 筛选条 ----------------------------------------------------------------- */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 22px 0 34px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 8px;
}
.filter-btn {
  padding: 8px 22px;
  font-size: 14.5px;
  color: var(--ink-3);
  border: 1px solid var(--line);
  letter-spacing: .06em;
  transition: all .2s;
}
.filter-btn:hover { border-color: var(--red); color: var(--red); }
.filter-btn.is-active { background: var(--red); border-color: var(--red); color: #fff; }

.search-box {
  margin-left: auto;
  display: flex;
  align-items: center;
  border: 1px solid var(--line);
  background: #fff;
}
.search-box input {
  border: none;
  outline: none;
  padding: 9px 14px;
  font-size: 14px;
  width: 190px;
  color: var(--ink);
  background: transparent;
}
.search-box button { padding: 9px 14px; color: var(--ink-3); font-size: 14px; }
.search-box button:hover { color: var(--red); }

/* 分页 ------------------------------------------------------------------- */
.pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  margin-top: 56px;
}
.page-btn {
  min-width: 38px; height: 38px;
  padding: 0 12px;
  border: 1px solid var(--line);
  font-size: 14px;
  color: var(--ink-3);
  display: grid; place-items: center;
  transition: all .2s;
}
.page-btn:hover:not(:disabled) { border-color: var(--red); color: var(--red); }
.page-btn.is-active { background: var(--red); border-color: var(--red); color: #fff; }
.page-btn:disabled { opacity: .38; cursor: not-allowed; }

/* 栏目横带（面包屑 + 栏目名称） -------------------------------------------- */
/* 列表页 / 内容页 / 简介页 / 联系页共用这一条横带：上面是面包屑，下面是栏目名。
   它原来是 --paper 纯色 —— 首屏、顶栏、页脚都有材质，只有这里是一块干净的纸色，
   整条读起来像"还没铺完"。现在补上同一套材质语言，三件事与顶栏完全一致：
     · 同一张 hero-tex.jpg，同样用 cover **铺满整条**（定宽会让宽窗口两端露纸色，
       原因见 .topbar 那段说明）。cover 只裁不压：带子越高，看到原图的一段越完整 ——
       顶栏 36px 是一条横切，这里约 200px，弧线更完整；
     · 同样两道偏心墨晕（左淡朱、右暖金），位置不对称，读作"纸上的痕迹"；
     · 不铺 tex-paper.svg 那种平铺纹理 —— 平铺的重复节奏在这个尺度上会读成图案。
   横带比顶栏高得多（约 200px vs 36px），看到的是原图更完整的一段，所以
   取景位定在纵向 40%：避开原图下缘的重部，同时留下可辨的弧线。
   ── 文字色必须重新定档
   面包屑（.crumb）与栏目英文（.page-en）用的是全局 --ink-4 #9A9A9A ——
   那是按**纯纸底**定的色。铺上底纹后，最暗的像素是 (224,219,213)，
   #9A9A9A 在上面只剩 2.6:1，13.5px / 11.5px 的小字直接读不清。
   所以在这一层里重定义 --ink-4（只作用于 .page-head 内部，不动全局）：
   取 #5A5A5A，在最暗像素上 5.0:1、平均底色上 5.6:1，守住 4.5:1。
   栏目名称 .page-title 走 --ink，最暗处仍有 12.5:1，不必动。 */
.page-head {
  position: relative;
  --ink-4: #5A5A5A;
  background-color: var(--paper);
  background-image:
    radial-gradient(120% 260% at 8% 40%, rgba(158,42,43,.05) 0%, rgba(158,42,43,0) 46%),
    radial-gradient(100% 240% at 82% 40%, rgba(200,164,92,.08) 0%, rgba(200,164,92,0) 48%),
    url("../img/hero-tex.jpg");
  background-size: auto, auto, cover;
  background-position: 0 0, 0 0, 50% 40%;
  background-repeat: no-repeat, no-repeat, no-repeat;
  border-bottom: 1px solid var(--line);
  padding: 64px 0 56px;
}
.page-title {
  font-family: var(--serif);
  font-size: 38px;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: .06em;
  line-height: 1.4;
}
.page-en {
  font-size: 11.5px;
  letter-spacing: .2em;
  color: var(--ink-4);
  text-transform: uppercase;
  margin-top: 12px;
}
.crumb {
  font-size: 13.5px;
  color: var(--ink-4);
  margin-bottom: 20px;
  letter-spacing: .04em;
}
.crumb a:hover { color: var(--red); }
.crumb span { margin: 0 8px; color: var(--line); }

.article-wrap { max-width: 800px; margin: 0 auto; }
.article-head { text-align: center; padding: 18px 0 40px; border-bottom: 1px solid var(--line); margin-bottom: 44px; }
.article-title {
  font-family: var(--serif);
  font-size: 34px;
  font-weight: 700;
  line-height: 1.55;
  color: var(--ink);
  letter-spacing: .03em;
  margin-bottom: 24px;
}
.article-meta {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px 26px;
  font-size: 13.5px;
  color: var(--ink-4);
  letter-spacing: .04em;
}
.article-body {
  font-size: 17px;
  line-height: 2.15;
  color: var(--ink-2);
  letter-spacing: .02em;
}
.article-body p { margin-bottom: 26px; text-align: justify; }
.article-body h3 {
  font-family: var(--serif);
  font-size: 22px;
  color: var(--ink);
  margin: 40px 0 18px;
  letter-spacing: .04em;
}
.article-body img { margin: 32px 0; border: 1px solid var(--line); }
.article-body blockquote {
  border-left: 3px solid var(--red);
  padding: 6px 0 6px 24px;
  margin: 30px 0;
  color: var(--ink-3);
  font-size: 16px;
}
.article-foot {
  margin-top: 52px;
  padding-top: 28px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 14px;
  font-size: 14.5px;
  color: var(--ink-3);
}
.article-nav a { color: var(--ink-2); }
.article-nav a:hover { color: var(--red); }
.article-actions { margin-top: 30px; display: flex; gap: 12px; flex-wrap: wrap; }

/* 图文双栏 --------------------------------------------------------------- */
.split {
  display: grid;
  grid-template-columns: 1.05fr .95fr;
  gap: 64px;
  align-items: center;
}
.figure {
  position: relative;
  aspect-ratio: 4 / 3;
  background: linear-gradient(135deg, #EFEBE4, #DFD9CF);
  border: 1px solid var(--line);
  display: grid;
  place-items: center;
  text-align: center;
  padding: 32px;
  overflow: hidden;
}
.figure img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.figure-note {
  position: relative;
  font-size: 13px;
  color: var(--ink-4);
  letter-spacing: .1em;
  line-height: 2;
}

.prose p { margin-bottom: 22px; text-align: justify; font-size: 16.5px; line-height: 2.05; }
.prose p:last-child { margin-bottom: 0; }

/* 信息表 ----------------------------------------------------------------- */
.info-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
}
.info-cell { background: #fff; padding: 30px 32px; }
.info-label {
  font-size: 13px;
  color: var(--ink-4);
  letter-spacing: .12em;
  margin-bottom: 12px;
}
.info-value { font-size: 16.5px; color: var(--ink); line-height: 1.85; }
.info-value a:hover { color: var(--red); }

/* 待补充标记 ------------------------------------------------------------- */
.tbd {
  display: inline-block;
  padding: 1px 8px;
  border: 1px dashed var(--ink-4);
  color: var(--ink-4);
  font-size: 12.5px;
  letter-spacing: .04em;
  border-radius: 2px;
  line-height: 1.7;
}

/* 空状态 ----------------------------------------------------------------- */
.empty-state {
  padding: 90px 0;
  text-align: center;
  color: var(--ink-4);
  font-size: 15px;
  letter-spacing: .06em;
}
.skeleton {
  height: 132px;
  border: 1px solid var(--line-soft);
  border-top: none;
  background: linear-gradient(90deg, #F6F4F1 25%, #EFEDE9 37%, #F6F4F1 63%);
  background-size: 400% 100%;
  animation: sk 1.3s ease infinite;
}
@keyframes sk { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* 入场动画 --------------------------------------------------------------- */
.reveal { opacity: 0; transform: translateY(18px); transition: opacity .7s ease, transform .7s ease; }
.reveal.is-in { opacity: 1; transform: none; }

/* 页脚：墨色山水 ---------------------------------------------------------- */
/* 原来是一整块 --ink 纯黑。纯色大色块的问题不是"深"，是"平"：两百多像素下来
   没有任何层次，像贴了一张黑纸。现在改成三组叠出来的「墨」：
     ① 山峦   ::after + mask，远/中/近三叠山影拖在底部收口（见下方注释）
     ②③ 墨晕 两团偏心的大半径晕染（左上暖金、右上朱红），避开"图案感"
     ④ 墨青  上浅下深的渐变，像砚池里正在化开的墨
   深色因此有了浓淡与远近 —— 它是"墨"，不是"黑"。
   ── 为什么删掉了斜织纹
   原版还压了两道 45° 细斜线（1.2–1.6% 白）当"绢丝"。这类**等距规则线**在屏幕上
   会和像素网格打架生成摩尔纹，看过去就是一层脏兮兮的横纹，读不出材质感 ——
   用户反馈的"横线底纹效果不好"指的就是它。纹理一律改用"不成线"的手法：
   大半径渐变（墨晕）、不规则轮廓（山影），规则线纹全部去掉。
   ── 对比度
   正文 rgba(255,255,255,.66) / #232927 = 6.7:1，底部小字 .55 = 5.5:1，
   金色链接 6.5:1。山影只有 2.2–7% 白且压在内容之下，不影响以上数字。 */
.site-footer {
  position: relative;
  color: rgba(255,255,255,.66);
  font-size: 14px;
  background-color: #232927;
  background-image:
    radial-gradient(120% 76% at 10% 0%, rgba(200,164,92,.10) 0%, rgba(200,164,92,0) 60%),
    radial-gradient(96% 68% at 90% 6%, rgba(158,42,43,.16) 0%, rgba(158,42,43,0) 64%),
    linear-gradient(180deg, #2D3432 0%, #232927 48%, #1A1F1E 100%);
  background-repeat: no-repeat, no-repeat, no-repeat;
}
/* ── 山峦为什么不用 CSS mask 了
   上一版是「一块白底 + mask 挖出山形」。用户那边的结果是：**山整层不见了**。
   证据 —— 页脚底部距底 40px 内逐行「横向极差」全是 0.0（纯色、毫无结构），
   而同一页里用 background-image 铺的顶栏 / 栏目横带底纹显示完全正常；
   把 CSS 里的 mask-image 换成 none，复现出同一组数字（0.0 / 1.0 / 0.7 / 0.7）。
   也就是说**那个环境里 mask 根本没渲染**。mask 失效没有"变淡"这种中间态，
   它是整层消失，所以山峦起伏在那边一直是零 —— 怎么调 opacity 都白调。
   现在改成一张带 alpha 的 PNG，用 background-image 铺（走的就是已验证可用的
   那条路）。浓淡（alpha）内置在图里，CSS 不再写 opacity。
   图由 _build/gen_mountain_svg.py 用同一套高度场渲染，与 tex-mountain.svg 同源。
   ── 两张图分别服务两种配色，浓度都是算出来的
   · hills-ink.png（墨色，浅色页脚用）：最浓处 24%。浅底上是"深字压浅影"，
     墨越浓字越好读，所以这一档不受对比度卡脖子，只受"别糊成一片脏灰"约束；
     按最浓处复算：底色 #EDE8DE → 合成约 (190,186,178)，正文 5.9:1、
     栏目标题 9.4:1、底部 12.5px 小字 4.8:1，全部达标。
   · hills-white.png（白色，深色页脚用）：最浓处 9%。这一档是**对比度算出来的
     天花板** —— 底部小字正好压在山的谷底上，白影每浓一档小字的底色就抬高一档：
     8% → 4.74:1 ｜ 9% → 4.64:1 ｜ 10% → 4.54:1 ｜ 11% → 4.44:1（破 4.5）。
     取 9% 留 0.14 的余量。
   ── 高度从 300 提到 340
   山脊在 340px 里占满，三层远近一眼分得开；300px 时近景的山脚被切得太紧。
   z-index: 0 让它沉在内容之下，文字始终压在山影之上。 */
.site-footer::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 0;
  height: 340px;
  background-image: url("../img/hills-white.png");
  background-size: 100% 340px;
  background-position: bottom center;
  background-repeat: no-repeat;
  pointer-events: none;
}
/* ── 列数必须与 ui.js 输出的 .footer-col 数量一致
   原来写的是 4 轨 1.6fr 1fr 1fr 1.3fr，而 renderFooter() 只输出 3 个 .footer-col：
   第 4 轨（1.3fr ≈ 267px）永远是空的。后果有两层 ——
   ① 右端空出 267px + 一个 48px 间距＝约 315px 的死区，整块内容明显偏左；
   ② 品牌列被压到 329px，英文全称放不下、简介要多折两行再被硬切。
   现在按实际列数收成 3 轨，并把宽度按内容权重重新分配：
     品牌列最宽（要放徽记 + 中英文名 + 简介），链接列最窄（2–5 字一项），
     联系列居中偏宽（地址会折行）。1056px 可用宽度下约为 472 / 248 / 335。 */
.footer-main {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1.9fr 1fr 1.35fr;
  gap: 48px;
  padding: 68px 0 52px;
}
.footer-col h4 {
  font-family: var(--serif);
  font-size: 16px;
  color: #fff;
  letter-spacing: .1em;
  margin-bottom: 22px;
  font-weight: 700;
}
.footer-col li { margin-bottom: 12px; }
.footer-col a:hover { color: var(--gold); }
/* 简介：套在 .footer-lockup 里（见下方「页脚品牌落款」），与上面的中英文两行
   共用同一条宽度，于是落款整体的左右边界只有两条竖线，段落左端与中文行首字对齐。
   --brand-cap 在这里只是**可读性上限**（296px / 13.5px ≈ 22 字一行），
   不会被文字顶破 —— 真超了由 lockup 自动放宽，两行仍等宽。
   上一版这里写 max-width: 400px、而列宽是 472px —— 段落比牌子宽出一大截，
   左边缘对不上，"排得乱"有一半来自这里。
   文本仍左对齐：牌子是居中的对称块，正文要左对齐才好读（中文正文居中会丢回行锚点）。 */
.footer-about {
  max-width: var(--brand-cap);
  margin-top: 20px;
  line-height: 1.85;
  font-size: 13.5px;
  text-align: left;
}
.footer-bottom {
  position: relative;
  z-index: 1;
  border-top: 1px solid rgba(255,255,255,.1);
  padding: 26px 0 34px;
  font-size: 12.5px;
  color: rgba(255,255,255,.55);
  line-height: 2;
}
.footer-bottom a:hover { color: var(--gold); }
.footer-bottom p + p { margin-top: 4px; }

/* 页脚备选配色：淡墨落款（浅色） -------------------------------------------- */
/* 在 site.json 里把 footer.theme 改成 "paper" 即启用（ui.js 会给 <html> 挂 sk-paper）。
   整站从顶到底不再出现深色块：页脚翻成淡墨米色 + 深字 + 墨色山影，
   与首屏的「宣纸留白」首尾呼应，同时比纯宣纸底多一层"落款"的分量。
   ── 为什么不用 --paper 那种近白
   试过 #FAF8F5，页脚和上面两个白色区块几乎连成一片，长页面滚到底会失去收口。
   压到 #EDE8DE 这一档淡墨米，既看得出"页面到头了"，又不构成色块。
   ── 对比度（按山影最深处量，见 check_site.py）
   正文 #3D3D3D = 7.0:1，标题 #1B1B1B = 11.1:1，底部小字 = 5.4:1。 */
html.sk-paper .site-footer {
  color: var(--ink-2);
  border-top: 1px solid #D9D2C4;
  background-color: #EDE8DE;
  /* 与深色版同理：不放斜织纹，也不铺整幅纸纹 —— 168px 的纹理平铺在这么大一片上，
     重复节奏本身就会读成"图案"。材质的层次交给墨晕的浓淡与底部山影。 */
  background-image:
    radial-gradient(120% 76% at 10% 0%, rgba(200,164,92,.16) 0%, rgba(200,164,92,0) 60%),
    radial-gradient(96% 68% at 90% 6%, rgba(158,42,43,.07) 0%, rgba(158,42,43,0) 64%),
    linear-gradient(180deg, #F2EDE4 0%, #EDE8DE 50%, #E4DED1 100%);
  background-repeat: no-repeat, no-repeat, no-repeat;
}
/* 浅色页脚换成墨色那张山图（hills-ink.png，最浓 24%）。
   注意这里**不再有 opacity** —— 浓淡已经烧进 PNG 的 alpha 通道，
   两套配色各用各的图，CSS 只负责换文件。 */
html.sk-paper .site-footer::after { background-image: url("../img/hills-ink.png"); }
html.sk-paper .footer-col h4 { color: var(--ink); }
html.sk-paper .footer-col a:hover { color: var(--red); }
/* 品牌落款在浅色页脚上的两个色：缩写用**深朱**（--red-deep，不是 --red），
   英文副行压深到 rgba(27,27,27,.72)。
   ── 为什么缩写不能用 --red
   校验把它按山影最浓处（hills-ink 最浓 24%）的底色量：#9E2A2B 只剩 4.2:1，
   过不了小字的 4.5。换 --red-deep #7C1D1E 后是 5.3:1，留出余量。
   ── 为什么不是现成的 --ink-3 / --ink-4
   同理，--ink-4 2.4:1、--ink-3 4.25:1 都不够。.72 黑在亮底 6.1:1、山影底 5.0:1。 */
html.sk-paper .footer-abbr { color: var(--red-deep); }
html.sk-paper .footer-en { color: rgba(27,27,27,.72); }
html.sk-paper .footer-bottom {
  border-top-color: #D9D2C4;
  color: rgba(27,27,27,.72);
}
html.sk-paper .footer-bottom a:hover { color: var(--red); }

/* 首屏标语 --------------------------------------------------------------- */
.hero-slogan {
  display: inline-block;
  font-family: var(--serif);
  font-size: 19px;
  letter-spacing: .3em;
  color: var(--red);
  padding: 10px 0 10px 22px;
  border-left: 2px solid rgba(158,42,43,.32);
  margin-bottom: 38px;
}

/* 页脚品牌落款 ------------------------------------------------------------ */
/* 左栏是一列**靠左的落款**，从上到下五段，整块与下方版权行（.footer-bottom）
   共用同一条左竖线：

        [徽记 72×72]                 ← 与中文名、版权行同一条左竖线
            ↕ 12px                   ← 图形 ↔ 它的代号（同属一块标记）
        CCPA-NCB                     ← 左对齐 / 10.5px / 字距 .16em / 品牌色
            ↕ 20px                   ← 标记 ↔ 名称：换层
        中国文化产业促进会网络文化分会   ← 19px / 字距 .04em / 衬线 / 两端对齐
            ↕ 4px                    ← 同一块名字的两行写法
        Network Culture Branch of …   ← 9.5px / 两端对齐 / 副行灰
            ↕ 20px                   ← 牌子 ↔ 正文：换层
        简介 13.5px / 行高 1.85（左对齐，与上面两行共用同一条宽度）

   12 / 20 / 4 三个数必须差开量级，层级才读得出来：原来徽记下边距 22px、
   h4 下边距也是 22px —— 两个 22 一样大，等于把「图形→文字」和「牌子→正文」
   当成同一层，远看成一条均匀的竖列，这就是"排得乱"的成因之一。

   ── 为什么整块靠左（曾经是居中）
   居中时列宽 472px、牌子 296px，落款各段的左缘落在 456 / 453 / 344 / 344px，
   而下方版权行（.footer-bottom）的左缘在 256px —— 两者错开 88px，
   整条左边界成了锯齿，而且落款比版权行"缩进"进去，看着就是没对齐。
   改成 align-items: flex-start 之后五者同为 256px（窄屏同为 18px），
   页面左侧只剩一条竖线。**校验脚本把这条当硬指标守**（见 check_site.py 第 9 节）。

   注意"靠左"指的是**整块**，含徽记与缩写 —— 不是"块内居中"。
   中英文两行的"两边对齐"不受影响：那是 .footer-lockup 内部的
   stretch + space-between 效果，与整块的水平位置无关。

   ── 缩写为什么挪到了中文名上面
   上一版是「徽记 → 中文名 → 缩写 → 英文全称」，缩写夹在两个名字中间，
   既不像代号也不像正文，读者看到的是三行平铺。挪到徽记下面之后它就是这枚
   徽记的代号 —— `CCPA-NCB` 自然宽 69px，与 72px 见方的徽记接近等宽，
   上下叠成一个标记块。五段由此收成三组：标记 / 名称 / 正文。
   ★ 这个宽度是实测的，且**随缩写文字变**：`NCC · CCPA` 曾是 74px，换成
   `CCPA-NCB` 后是 69px。所以别把"几乎等宽"当成必然 —— 换缩写时用
   `_build/probe_metrics.py` 复测一遍，再决定徽记下的留白是否还站得住。

   ── 中英文「两边对齐」怎么实现的
   两行都用 flex + space-between，逐字 / 逐词拆成 flex 项：谁的自然宽大谁定宽，
   另一行被撑满，于是两行左右两端严格对齐（详见 ui.js 的 charItems / wordItems）。
   撑开落在哪个维度是关键：中文逐字撑开是「疏排字距」，观感自然；英文若被撑开
   就成了散开的单词。所以让**中文的自然宽略大于英文**，余量几乎全部摊进
   英文词距（.25em → .37em），中文字距一动不动 —— 用户对"字间距太宽"敏感。

   ── 列宽为什么交给 shrink-to-fit，而不是写死一个 px
   .footer-lockup 是 inline-flex + column：列宽 = 三个子项里最宽的那个。
   同层放简介是为了让它与两行同宽，但它被 --brand-cap 封了顶，于是
   实际列宽 = max(中文自然宽, 英文自然宽, 封顶后的简介) —— 通常等于 --brand-cap。
   ★ 这一层不能省。曾直接给两行写 width: 296px 硬框，结果在窄屏断点下
   中文的自然宽（275px）超过框宽（258px），flex 项从框右侧溢出去 17px，
   中文那行比英文那行宽出一截、也不再居中。改成由内容定宽后，
   "框比字窄"这种失败模式在结构上就不可能发生，换字体也一样。

   ── 296px 这个上限是怎么来的
   按 Windows 落点字体（衬线 SimSun / 正文 微软雅黑）实测：
     中文 19px + 字距 .04em  → 自然宽 295.6px（14 字）
     英文 9.5px + 词距 .25em → 自然宽 288.1px（8 词、7 个词距）
   上限取 296px：比中文自然宽大 0.4px —— 中文额外字距 0.002em，等于没有被撑开。
   英文取 9.5px 与顶栏品牌区的英文同号，整站的双语大小才是同一套。
   三条硬约束（check_site.py 逐断点核）：上限 ≥ 两行自然宽、上限 ≤ 最窄可用宽、
   中文额外字距 ≤ 0.02em。 */
.footer-brand {
  --brand-cap: 296px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;  /* 整块靠左：与 .footer-bottom 的版权行同一条左竖线 */
}
.footer-logo-wrap { margin-bottom: 12px; }
.footer-logo-wrap:empty { margin-bottom: 0; height: 0; }
/* 页脚徽记 52 → 72px。52px 时它跟右栏 16px 的栏目标题差不多重，
   摆在这一列顶上显得小、也压不住下面 26px 的全称；页脚是整页的落款，
   徽记该比顶栏那枚（52px）更有分量。要回退就改这一个数。
   display:block 去掉行内图片基线留出的那几像素空隙，间距才等于上面写的数。 */
.footer-logo { display: block; height: 72px; width: auto; }

/* 缩写：靠左在徽记下面当地理代号。用品牌色（深底金 / 浅底朱），
   语意是"代号"不是正文，所以小字号 + 大写字距。
   自然宽 69px，与 72px 见方的徽记接近等宽 —— 上下叠成一个标记块。
   （宽度随缩写文字变：`NCC · CCPA` 曾是 74px、换 `CCPA-NCB` 后 69px，均为实测。） */
.footer-abbr {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .16em;
  line-height: 1.5;
  color: var(--gold);
  margin-bottom: 20px;
  white-space: nowrap;
}

/* 牌子本体：宽度由内容决定（见上面那大段），max-width 只防极端窄屏 */
.footer-lockup {
  display: inline-flex;
  flex-direction: column;
  align-items: stretch;     /* 三个子项一律撑满列宽 -> 中英文严格等宽 */
  max-width: 100%;
}

/* 中文全称。选择器写成 .footer-col h4.footer-name（比默认的 .footer-col h4 多一个类），
   否则字号 / 字距 / 下边距都会被那三条默认值盖回去。
   19px：比右栏 16px 的栏目标题大半档 —— 它是品牌名，不是栏目名；
   这个字号同时也是「与英文行自然宽持平」解出来的（见上面 --brand-cap 那段）。
   white-space: nowrap：中文行必须是一行，否则 space-between 无处可撑。 */
.footer-col h4.footer-name {
  display: flex;
  justify-content: space-between;
  font-size: 19px;
  letter-spacing: .04em;
  line-height: 1.25;
  margin-bottom: 0;
  white-space: nowrap;
}
/* 末字不带字距：否则行尾会多出 .04em 的空档，两端对齐就虚了一格 */
.footer-name > span:last-child { letter-spacing: 0; }

/* 英文全称：与中文行同宽、同样两端对齐，右端与中文行的右端落在同一条竖线上。
   拆成 8 个单词项，余量由 space-between 摊进词距。
   字号 9.5px 与顶栏品牌区一致。**这里不写字距** —— 写了会让这一行的自然宽
   反超中文行（实测 .03em 就多出 15px），反过来把中文撑开 0.076em，
   那正是用户反馈过的"字间距太宽"。
   ── .62 这个透明度是**按对比度选出来的**，不是随手压暗
   浅色页脚 #EDE8DE 上 --ink-4 #9A9A9A 只有 2.4:1、--ink-3 #6B6B6B 4.25:1，
   都过不了小字的 4.5；而这一行正好压在山影最浓处，底色更暗。浅色版另给了
   rgba(27,27,27,.72)（亮底 6.1:1、山影底 5.0:1）；深色版不用纯白 --white
   （那是正文的亮度），压到 .62 与正文拉开一档。 */
.footer-en {
  display: flex;
  justify-content: space-between;
  gap: .25em;
  margin-top: 4px;
  font-size: 9.5px;
  line-height: 1.5;
  color: rgba(255,255,255,.62);
  white-space: nowrap;
}
.footer-en > span:last-child { letter-spacing: 0; }

/* 正文中的示例提示行 */
.article-body .note-line {
  font-size: 13.5px;
  color: var(--ink-4);
  background: var(--paper);
  border-left: 2px solid var(--line);
  padding: 10px 16px;
  margin-top: 36px;
}

/* 响应式 ----------------------------------------------------------------- */
@media (max-width: 1024px) {
  /* 品牌区降一档：导航还在一行时，中文全称 + 导航要一起塞进容器里。
     中文 20px（自然 316.8px）配副行 334.7px，中文会被撑开 1.3px/字 —— 上一版没注意，
     就是这一档在拖后腿。现在中文 21px（自然 332.6px）+ 副行 336.4px，只差 3.8px，
     折算中文额外字距 0.013em，肉眼看不出。 */
  .brand { gap: 12px; }
  .brand-cn { font-size: 21px; }
  .brand-sub { gap: 6px; }
  .brand-abbr { font-size: 9px; }
  .brand-en { font-size: 9px; }
  .brand-logo, .brand-fallback { height: 46px; width: 46px; flex-basis: 46px; }
  .brand-logo { width: auto; }
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .footer-main { grid-template-columns: 1fr 1fr; gap: 40px; }
  .split { grid-template-columns: 1fr; gap: 40px; }
  .news-item { grid-template-columns: 104px 1fr 170px; gap: 22px; }
  .news-thumb { width: 170px; height: 104px; }
}

@media (max-width: 860px) {
  .nav-toggle { display: block; }
  .nav {
    position: absolute;
    top: 100%; left: 0; right: 0;
    background: #fff;
    border-bottom: 1px solid var(--line);
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 8px 0;
    display: none;
  }
  .nav.is-open { display: flex; }
  .nav-link { padding: 14px 24px; border-top: 1px solid var(--line-soft); }
  .nav-link::after { display: none; }
  .header-inner { height: 72px; }
  /* 手机宽度放不下 61 字符的英文全称，副行只剩缩写在左端 */
  .brand-cn { font-size: 16px; }
  .brand-en { display: none; }
  .brand-abbr { font-size: 9px; }
  .brand-logo, .brand-fallback { height: 42px; width: 42px; flex-basis: 42px; }
  .brand-logo { width: auto; }
  /* 同样只用上下内边距 —— 写成 padding 简写会把容器的左右留白一起清掉 */
  .hero-inner { padding-top: 88px; padding-bottom: 72px; }
  /* 窄屏没有「左文右空」的横向留白，纹理不能全压在右上角；
     改成偏中上落位，mask 也收窄动态范围（左侧留 25% 而不是 0），
     让手机上仍有淡淡一层，但不干扰整列文字。 */
  #heroBg {
    background-position: 62% 38%;
    -webkit-mask-image: linear-gradient(115deg, rgba(0,0,0,.25) 0%, rgba(0,0,0,.55) 50%, rgba(0,0,0,.9) 100%);
    mask-image: linear-gradient(115deg, rgba(0,0,0,.25) 0%, rgba(0,0,0,.55) 50%, rgba(0,0,0,.9) 100%);
  }
  .hero-title { font-size: 34px; }
  .hero-desc { font-size: 15.5px; }
  /* 61 字符的英文全称在窄屏上折成两三行太占地方，收一档字距让它贴合一些 */
  .hero-en { font-size: 11.5px; letter-spacing: .1em; }
  .section { padding: 64px 0; }
  .section-title { font-size: 26px; }
  .section-head { flex-direction: column; align-items: flex-start; gap: 18px; margin-bottom: 36px; }
  .grid-3, .grid-2 { grid-template-columns: 1fr; }
  .info-grid { grid-template-columns: 1fr; }
  .news-item { grid-template-columns: 84px 1fr; gap: 18px; padding: 24px 0; }
  .news-thumb { display: none; }
  .news-date b { font-size: 24px; }
  .news-item-title { font-size: 17px; }
  .page-title { font-size: 28px; }
  .article-title { font-size: 25px; }
  .article-body { font-size: 16px; }
  .search-box { margin-left: 0; width: 100%; }
  .search-box input { flex: 1; width: auto; }
  .footer-main { grid-template-columns: 1fr; gap: 36px; padding: 48px 0 36px; }
  .topbar .container { height: 32px; font-size: 11.5px; }
  .hero-slogan { font-size: 15px; letter-spacing: .2em; padding-left: 16px; margin-bottom: 28px; }
}

/* 小屏手机：要在一行里塞下「Logo + 中文全称 15 字 + 汉堡按钮」，只能压到 14px */
@media (max-width: 480px) {
  /* 16px 在手机上视觉上仍然"贴边"，放宽到 18px；再宽品牌区就塞不下了 */
  .container { padding: 0 18px; }
  .brand { gap: 8px; }
  .brand-cn { font-size: 14px; letter-spacing: .04em; }
  .brand-abbr { font-size: 8px; }
  .brand-logo, .brand-fallback { height: 34px; width: 34px; flex-basis: 34px; }
  .brand-logo { width: auto; }
  /* 页脚落款收一档：桌面那套（中文 19px / 上限 296px）放不进这里 ——
     最窄按 320px 手机算，容器左右各 18px，可用只有 284px。
     收窄的依据与桌面同一套算法（两行自然宽持平）：
       中文 17px + 字距 .04em → 264.5px
       英文 8.5px + 词距 .25em → 257.8px
     列宽由中文定（264.5 < 上限 266），英文的余量摊进词距 0.39em。
     这里只动字号与上限，不需要跟两行宽度对账 —— 列宽是算出来的，不是框出来的。 */
  .footer-brand { --brand-cap: 266px; }
  .footer-col h4.footer-name { font-size: 17px; }
  .footer-en { font-size: 8.5px; }
}

/* ==========================================================================
   单屏首页（v3 专属）—— 首页只占**一个视口**，不出现纵向滚动条
   ── 这一节以外的所有规则与 v2 逐字节相同
   单屏规则全部挂在 html.is-onescreen 下，所以 v2 复制本文件回去也不会受影响；
   反过来 v2 改了首屏样式，直接覆盖过来即可，只有本节需要人工确认。
   ── 怎么做到"恰好一屏"
   不用 calc(100vh - 129px) 这种硬减 —— 页头高度是 36(topbar) + 92(header) + 1(border)，
   窄屏断点下会变成 32 + 72 + 1，写死数字必在某档错位、多出几像素滚动。
   改成让 body 变成一列 flex：页头 auto 高，首屏 flex:1 吃掉剩下的全部高度，
   **高度差由布局自己算**，与页头实际多高无关。
   ── min-height: 0 那行不能删
   flex 子项的 min-height 默认是 auto，意思是"不小于内容高"。
   内容一旦比可用空间高，首屏就会被顶破、页面出现滚动条 ——
   偏偏那种溢出往往只有几十像素，肉眼看着"就是一屏"，很难发现。
   ── 内容被裁 ≠ 页面滚动（两种失效要分开验证）
   .hero 自带 overflow: hidden，所以内容再高也不会把页面撑出滚动条，
   而是**静静被裁掉底部**。所以"无滚动"由结构保证，但"内容没被裁"必须实测：
   _build/check_onescreen.py 逐视口比对 heroText 的自然高与首屏可用高，
   超出或在 0.9 倍以下的视口都会报警（脚本里记着判定依据）。
   ========================================================================== */
html.is-onescreen body {
  min-height: 100vh;
  min-height: 100svh;        /* 移动端用 svh：按地址栏**展开**时算，不会被压掉半行 */
  display: flex;
  flex-direction: column;
}
html.is-onescreen .hero-full {
  flex: 1 1 auto;
  min-height: 0;             /* ★ 见上，删了就会在矮视口顶出滚动条 */
  display: flex;
  align-items: center;
  border-bottom: 0;          /* 首屏底边＝视口底边，别留 1px 缝 */
}
/* 内容改由上面的 flex 居中定位，上下留白收成一档 */
html.is-onescreen .hero-inner {
  padding-top: clamp(20px, 4vh, 56px);
  padding-bottom: clamp(20px, 4vh, 56px);
}
/* 首屏内的段间距随视口高收放：900 高时与 v2 的原值一致（上下限都取原值），
   矮视口自动压紧 —— 一屏版不能靠"内容放不下就滚动"来兜底。
   字号不动，仍由 860 / 480 两档窄屏规则管，避免两套缩放打架。 */
html.is-onescreen .hero-eyebrow { margin-bottom: clamp(12px, 2.2vh, 28px); }
html.is-onescreen .hero-title { margin-bottom: clamp(10px, 1.8vh, 22px); }
html.is-onescreen .hero-en { margin-bottom: clamp(12px, 2.4vh, 30px); }
html.is-onescreen .hero-desc { margin-bottom: clamp(16px, 3vh, 38px); }
html.is-onescreen .hero-slogan { margin-bottom: clamp(16px, 3vh, 38px); }
html.is-onescreen .hero-actions { margin-bottom: clamp(14px, 2.6vh, 34px); }
html.is-onescreen .hero-meta { padding-top: clamp(12px, 2.2vh, 30px); }

/* 矮视口（1366×768 这类笔记本屏）：间距已经按 vh 收过，字号再收一档。
   这里的选择器必须与上面的单屏规则同级（html.is-onescreen .x），
   否则压不过窄屏媒体块里的 .hero-title —— 特异性不够，改了不生效还不报错。 */
@media (max-height: 780px) {
  html.is-onescreen .hero-title { font-size: 38px; }
  html.is-onescreen .hero-desc { font-size: 15.5px; line-height: 1.9; }
  html.is-onescreen .hero-slogan { font-size: 17px; }
  html.is-onescreen .btn { padding: 11px 26px; font-size: 14px; }
}

/* 极矮视口（360×640、375×667 这类小手机）：压到最紧。
   这一档的取舍是明确的 —— **宁可少露两块次要信息，也不让首页出现滚动条**：
   隐藏信息行（成立时间 / 地点，「关于分会」页里有）与英文全称的展开，
   标题、导语、标语、两个按钮必须留。
   实测：320×568（可用高仅 462）也在这一档刚好装下，不再溢出。 */
@media (max-height: 700px) {
  html.is-onescreen .hero-title { font-size: 30px; }
  html.is-onescreen .hero-en { font-size: 10.5px; letter-spacing: .08em; }
  html.is-onescreen .hero-desc { font-size: 14px; line-height: 1.8; }
  html.is-onescreen .hero-slogan { font-size: 15px; letter-spacing: .2em; margin-bottom: 14px; }
  html.is-onescreen .hero-actions { margin-bottom: 0; }   /* meta 已隐藏，别留一段空档 */
  html.is-onescreen .hero-meta { display: none; }
  html.is-onescreen .hero-inner { padding-top: 18px; padding-bottom: 18px; }
}

/* 手机横屏 / 小手机（视口高 ≤ 600 或宽 ≤ 900，如 320×568、844×390）：
   字号、留白、文案三样一起收，收到"最坏情况"也装得下。
   ── 为什么要按最坏情况设计（这里踩过一个双稳态的坑）
   经典滚动条占 15px 宽：页面只要先出现滚动条，被量页的可用宽就从 320 变 305，
   文字多折一行 → 内容更高 → 它继续溢出、滚动条不消失，**自锁在溢出状态**。
   实测同一视口连跑 6 次：内容高 381（无滚动条）与 516（有滚动条）交替出现，
   宽度分别为 320 / 305 —— 看着像"随机"，其实是两套稳定解。
   所以这一档的余量必须按 305 宽算；移动端的滚动条是浮层、不占宽，
   按更窄的桌面情形设计，真机上只会更宽松。
   ── 收了什么
   英文全称（61 字符，矮屏要占两三行）、按钮并排（各占一行会多出 60px）、
   标语的字距与内边距。标题、导语、标语、两个按钮都留着。 */
@media (max-height: 600px) and (max-width: 900px) {
  html.is-onescreen .hero-title { font-size: 26px; }
  html.is-onescreen .hero-en { display: none; }
  /* ⚠ 这一档的**最紧处不是 844×390，而是 667×375**：两者同档，但 375 高比 390 矮，
     可用高只有 375 − 106(页头) − 30(署名行) = 239。
     加上署名行之后，内容自然高 242 正好顶破这 3px，页面会滚 4px。
     ── 为什么 flex 不会替我们压掉这 3px
     body 的高度是 min-height: 100svh 而不是 height —— 高度仍是"auto 由内容决定"，
     容器高度不受限，flex 就没有负空间可分配，flex-shrink 根本不会触发。
     （把 body 改成 height:100svh 确实不滚了，但代价是内容被 overflow:hidden
      静静裁掉 —— 从"能看见的滚动"换成"看不见的丢失"，更糟。）
     所以只能把这一档的内容再压紧：padding 12→8、行高 1.7→1.6、
     标语下边距 10→8，共省出约 12px，最紧的 667×375 也有 10px 以上余量。 */
  html.is-onescreen .hero-desc { font-size: 13px; line-height: 1.6; }
  html.is-onescreen .hero-slogan {
    font-size: 13px; letter-spacing: .1em;
    padding: 6px 0 6px 12px; margin-bottom: 8px;
  }
  html.is-onescreen .hero-actions { margin-bottom: 0; }
  html.is-onescreen .hero-meta { display: none; }
  html.is-onescreen .hero-inner { padding-top: 8px; padding-bottom: 8px; }
  /* 两个按钮必须并排 —— 各占一行要多出 60px 左右，正是这一档最缺的 */
  html.is-onescreen .btn { padding: 9px 16px; font-size: 12.5px; }
}

/* 超矮横屏（iPhone SE 横屏 667×375 这类）：可用高只剩 239px（375 − 106 页头 − 30 署名行），
   上一档压完余量只有 8px —— 换个中文字体就可能让导语多折一行（+21px）直接溢出。
   8px 的余量撑不住字体差异，所以这里再让一档：标题 26→23、导语行高 1.6→1.55、
   内边距 8→6，换来 20px 上下的余量。
   ── 为什么宁可缩字号也不让页面滚动
   一屏版的价值就是"不用滚"。滚动条一旦出现，首屏底部的署名行（版权 / 备案）
   就落到折线以下，等于白加。小一号字仍然读得清，滚动条不行。 */
@media (max-height: 420px) {
  html.is-onescreen .hero-title { font-size: 23px; margin-bottom: 8px; }
  html.is-onescreen .hero-eyebrow { margin-bottom: 8px; }
  html.is-onescreen .hero-desc { font-size: 12.5px; line-height: 1.55; margin-bottom: 12px; }
  html.is-onescreen .hero-slogan { font-size: 12.5px; margin-bottom: 8px; }
  html.is-onescreen .hero-actions { margin-bottom: 0; }
  html.is-onescreen .hero-inner { padding-top: 6px; padding-bottom: 6px; }
}

/* 大屏（视口高 ≥ 950）：首屏内容原样放只有视口六成高，"缩在一角"。
   宣纸留白本来是这套设计的一部分，但留白过半就读成"没做完"。
   只放大标题与正文字号两档 —— 间距已经按 vh 顶到上限，没有再放大的余地。 */
@media (min-height: 950px) {
  html.is-onescreen .hero-title { font-size: 52px; }
  html.is-onescreen .hero-desc { font-size: 18px; }
  html.is-onescreen .hero-slogan { font-size: 21px; }
  html.is-onescreen .hero-en { font-size: 14px; }
}
/* 平板竖屏（834×1112 这类）：宽度走的是窄屏那套字号，别跟着桌面放大到 52px */
@media (min-height: 950px) and (max-width: 860px) {
  html.is-onescreen .hero-title { font-size: 40px; }
  html.is-onescreen .hero-desc { font-size: 16.5px; }
}

/* ==========================================================================
   底部署名行（.onescreen-foot）—— 单屏首页的"版权信息"
   ── 为什么必须有
   版权与备案是网站的标配，也是监管要求；"首页只有一屏"不是省掉它的理由。
   但它也不能照搬页脚：完整页脚是「落款 + 快速链接 + 联系我们 + 版权行」，
   实测 500px 出头，一屏里放不下 —— 搬过来就是"要么滚动、要么把首屏压到裁切"。
   所以只保留**监管必需的那一行**：版权 / ICP 备案 / 公安备案，
   文案与 4 个内页完整页脚的 .footer-bottom 同源（ui.js 里同一个函数出的），
   改 site.json 一处，两处同时更新。
   ── 为什么它自己是一个 flex 子项
   body 是「页头 auto → 首屏 flex:1 → 署名行 auto」的一列：
   署名行的高度参与计算，首屏自动让位。所以"加了一行会不会顶出滚动条"
   这件事由布局自己回答，不需要手改首屏 —— 但也因此必须实测（见 check_onescreen.py）。
   ── flex: 0 0 auto 不能省
   它是一行字，不是容器。默认 flex-shrink: 1 意味着空间不够时先把它压扁，
   文字上下一叠就糊在一起；而首屏因为 flex:1 反而看起来"很正常"，很难注意到。
   ── 极矮视口（844×390 这类横屏手机）是本行的最紧处
   那一档首屏剩余只有 42px，基础档 ~52px 会直接把内容顶出视口。
   下面三档 media 逐级把它压到 ~30px（字号 11 / 行高 1.75 / 上下各 5px），
   宁可小一号，也不能让首页出现滚动条。
   ── 颜色不依赖 sk-paper
   直接取 :root 的 --paper / --line / --ink-3。这样即使 data/site.json 取不到、
   ui.js 没挂上 sk-paper，它也仍是浅底深字，不会变成读不出内容的一团深色。
   ========================================================================== */
html.is-onescreen .onescreen-foot {
  flex: 0 0 auto;                              /* ★ 见上，不参与高度分配、不被压扁 */
  border-top: 1px solid var(--line);
  padding: clamp(9px, 1.5vh, 20px) 0;
  font-size: 12.5px;
  line-height: 1.9;
  color: var(--ink-3);
  background: var(--paper);
}
/* 三段文字用 gap 分隔，而不是往字符串里塞全角空格：
   备案号在窄屏会折行，空格会留在行尾/行首带出一段空隙，gap 不会。
   ── 为什么居中（用户指定）
   首屏内容左对齐、署名行左对齐时，页面左侧是"一条竖线"，规整；
   但署名行单独居中更像**落款收口** —— 它是一屏的最底一行，居中读作"署名"，
   贴左则读作"正文的延续"。两种都成立，按用户的选择走居中。
   ── 断言怎么守（见 check_onescreen.py）
   守的是**墨迹位置**而不是 `justify-content` 这个属性名：
   属性写对了、但给某项加 `margin-left: auto`（或首项加 `margin-left`）照样能把它推偏，
   与页脚"落款靠左"那次遇到的绕过路径是同一类。
   判定用"首项左缘到末项右缘的中点 ≈ 视口中心"，容差 2px。 */
html.is-onescreen .onescreen-foot .container {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 2px 20px;
}

@media (max-height: 780px) {
  html.is-onescreen .onescreen-foot { font-size: 12px; padding: 7px 0; }
}
@media (max-height: 700px) {
  html.is-onescreen .onescreen-foot { font-size: 11.5px; line-height: 1.75; padding: 6px 0; }
}
@media (max-height: 600px) and (max-width: 900px) {
  html.is-onescreen .onescreen-foot { font-size: 11px; padding: 5px 0; gap: 1px 14px; }
}
