/* ==========================================================================
 * VANSYE 博客 · 样式
 *
 * 这个文件的**规格来源是 `VANSYE-设计方案-v2.md`**，不是我的判断。
 * 里面每个颜色、字号、透明度都能在那份文档里找到对应的一行。
 * 改任何一个值之前先看那份文档第 9 节（禁止事项 12 条）和第 10 节（验收清单）。
 *
 * 主体原样来自 `原型/index.html` 的 <style> 块——那是按规格实现并反复调过的版本，
 * 重写一遍只会引入偏差。我只改两处：
 *   1. 字体从 Google Fonts 换成自托管（见下）
 *   2. 文末新增文章页（纸面）样式——规格 §6.1，原型里没有
 * ========================================================================== */

/* ── 字体：自托管，不用 Google Fonts ─────────────────────────────────────
 *
 * **原型是从 fonts.googleapis.com 加载的，那在大陆访问不了。**
 * 后果不是「慢一点」，是访客全部落到 fallback——而 Libre Caslon Display
 * 就是整个视觉身份（站名巨字、英文大标题），掉到 Georgia 就不是这个设计了。
 *
 * 两个拉丁字体是 OFL 授权，可自托管，加起来 62 KB。
 *
 * 中文**故意不自托管**：Noto Serif SC 全 CJK 十几 MB；做子集化又会在
 * 「出现没被子集进去的字」时静默掉字——那种 bug 只在特定汉字上出现，平时测不到。
 * 所以走系统栈（规格 §2 的 fallback 链本来就列了 STSong / SimSun），
 * 而规格自己留了「可整体换回思源黑体」这个开关。
 * ------------------------------------------------------------------------ */
@font-face{
  font-family:"Libre Caslon Display";
  src:url("/fonts/libre-caslon-display-400.woff2") format("woff2");
  font-weight:400; font-style:normal;
  /* swap：字体没到之前先用 fallback 显示。
     内容站不能为了字体让首屏文字消失几百毫秒 */
  font-display:swap;
}
@font-face{
  font-family:"Courier Prime";
  src:url("/fonts/courier-prime-400.woff2") format("woff2");
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Courier Prime";
  src:url("/fonts/courier-prime-700.woff2") format("woff2");
  font-weight:700; font-style:normal; font-display:swap;
}

/* ============ tokens（规格 v2 §1）============ */
:root{
  --purple:#7929FF;
  --paper:#F4F2F8;
  --white:#FFFFFF;
  /* 小号标签（≤0.8rem）的辅助色。**这个数是量出来的，不是挑出来的**：
     白色叠在 #7929FF 上，65% → 对比度 3.26，78% → 4.11，83% → 4.48，
     都不到 WCAG AA 小字要求的 4.5；阈值在 83.5%。取 84%（4.56，留一点余量）。
     规格 §1 从 65% 升到 78% 的方向是对的，只是没升够。
     **名字里带数字，就必须和值一致**——不然读规则的人会算出错的对比度 */
  --white-84:rgba(255,255,255,.84);
  --white-65:rgba(255,255,255,.65);
  --white-28:rgba(255,255,255,.28);
  --white-16:rgba(255,255,255,.16);
  --violet:#6A1FE8;
  /* 代码块底色。原来是硬写的 #FFF，抽成令牌好让夜间主题换掉它 */
  --code-bg:#FFFFFF;
  --ink:#2B1670;
  --ink-65:rgba(43,22,112,.65);
  --paper-hair:rgba(106,31,232,.18);

  --f-display:"Libre Caslon Display","Libre Caslon Text",Georgia,"Times New Roman",serif;
  --f-mono:"Courier Prime","Courier New",ui-monospace,monospace;
  --f-cn:"Noto Serif SC","Source Han Serif SC","STSong","SimSun",serif;

  --gut:clamp(1.5rem,6vw,7rem);
  /* 紫框只在**纸面**背后才看得见（它是紫的，紫底上等于不存在），
     所以这个值实际只影响文章页和首页 D 区。规格 §4 原值 clamp(14px,1.8vw,26px)
     在 1440px 下量到 25px，纸面被压得太窄——减到一半 */
  --frame:clamp(8px,1vw,14px);
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{
  background:var(--purple);
  color:var(--white);
  font-family:var(--f-cn);
  font-size:clamp(1rem,.9vw + .8rem,1.0625rem);
  line-height:1.9;
  -webkit-font-smoothing:antialiased;
  /* **不能写 hidden。** 按规范 overflow-x:hidden + overflow-y:visible 会把
     overflow-y 算成 auto，body 于是变成滚动容器。实测确认过：
     改之前 body 的计算值就是 x=hidden y=auto。
     clip 一样裁掉横向溢出（出血图要靠它），但**不建立滚动容器**。

     原来这里写的理由是「滚动容器里的 position:sticky 相对它算，页脚揭示会失效」。
     页脚换成 fixed 之后那条理由不成立了，但结论没变，换成了两条新的：
       ① 出血图（hero、人物图）仍然靠它裁横向溢出——这是最初的目的
       ② 下面 --cover 那条 view 时间线默认相对「最近的滚动口」算。
          body 一旦变成滚动容器，时间线的参照就从根滚动口换成它，
          而两者的滚动量并不相同——淡入的时机会莫名偏掉 */
  overflow-x:clip;
  /* 时间线是定义在盖板上、被页脚里的元素引用的，而两者是**兄弟**关系。
     view-timeline-name 默认只对自己的后代可见，所以要在共同祖先上开作用域。
     少了这一条，@supports 那段里的 animation-timeline:--cover 会解析成
     「找不到这条时间线」→ 动画不跑 → 内容停在 opacity:1（可见，但没有淡入）。
     也就是说漏掉它的后果是「效果没了」而不是「页脚不见了」——方向是对的 */
  timeline-scope:--cover;
}
::selection{background:var(--white);color:var(--purple)}
.paper ::selection{background:var(--purple);color:var(--white)}
a{color:inherit;text-decoration:none}
a:focus-visible{outline:2px solid var(--white);outline-offset:4px}
.paper a:focus-visible{outline-color:var(--violet)}
img{display:block;max-width:100%;height:auto;user-select:none;-webkit-user-drag:none}

/* 滚动条：默认隐形，hover 显形 */
html{scrollbar-width:thin;scrollbar-color:var(--white-28) transparent}
::-webkit-scrollbar{width:.25rem;height:.25rem}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--white-28);border-radius:1rem}

/* ============ 全局装置（§4）============ */
.frame{position:fixed;inset:0;z-index:200;pointer-events:none;border:var(--frame) solid var(--purple)}
.grain{position:fixed;inset:0;z-index:190;pointer-events:none;
  background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.72' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:11rem 11rem;mix-blend-mode:color-burn;opacity:.16}

.wrap{max-width:1500px;margin-inline:auto;padding-inline:var(--gut)}

/* mono 标签原子 */
.mono{font-family:var(--f-mono);text-transform:uppercase;letter-spacing:.2em;font-size:.72rem;font-weight:400}
.mono-s{font-family:var(--f-mono);text-transform:uppercase;letter-spacing:.16em;font-size:.64rem;font-weight:400}
.rule{border:0;height:1px;background:var(--white-28)}
.tick{border:0;width:2.4rem;height:1px;background:var(--white)}

/* ============ A 导航 ============ */
.topbar{display:flex;align-items:flex-start;justify-content:space-between;gap:1.5rem;
  padding-top:clamp(1.6rem,4vh,2.8rem);padding-bottom:1.2rem;flex-wrap:wrap}
.brand{display:flex;align-items:center;gap:.75rem}
/* 导航栏的 VA 用加粗版（icon-va-sm）：花体衬线在 37px 下笔画会掉进亚像素，
   页脚印章 4.4rem 有空间，用原版更秀气 */
.brand img{width:2.3rem;height:2.3rem;flex:none}
.brand-name{display:flex;flex-direction:column;gap:.28rem}
.brand-name b{font-family:var(--f-mono);font-weight:700;font-size:.9rem;letter-spacing:.24em}
.brand-name span{font-family:var(--f-mono);font-size:.56rem;letter-spacing:.3em;color:var(--white-84)}
.topnav{display:flex;gap:clamp(1.2rem,3.2vw,3rem);padding-top:.5rem;flex-wrap:wrap}
.topnav a{font-family:var(--f-mono);text-transform:uppercase;letter-spacing:.2em;font-size:.72rem;
  color:var(--white-84);transition:color .15s}
.topnav a:hover{color:var(--white);text-decoration:underline;text-underline-offset:.35em;text-decoration-thickness:1px}

/* ============ B Hero ============ */
.hero{position:relative;overflow:hidden}
.hero-stage{position:relative;min-height:min(88vh,60rem)}
/* Hero 图版：白墨 + alpha 通道，紫色由页面背景透上来。
   不加 filter、不加 mix-blend-mode —— 半调网点已经是「非黑即白」，
   再叠 screen 只会把紫色洗成淡薰衣草色。 */
.hero-art{position:absolute;z-index:1;top:0;right:calc(-1 * var(--gut) - 6vw);
  width:min(72vw,68rem);height:min(82vh,56rem);object-fit:cover;object-position:48% 48%;
  opacity:.74;
  -webkit-mask-image:linear-gradient(90deg,transparent 0%,rgba(0,0,0,.55) 22%,#000 52%);
  mask-image:linear-gradient(90deg,transparent 0%,rgba(0,0,0,.55) 22%,#000 52%);
  pointer-events:none;will-change:transform}
.hero-body{position:relative;z-index:2;padding-top:clamp(2.5rem,9vh,6rem)}
.wordmark{font-family:var(--f-display);font-weight:400;font-size:clamp(4rem,15.5vw,15.5rem);
  line-height:.8;letter-spacing:.01em;user-select:none;}
.hero-sub{display:flex;flex-direction:column;gap:1.15rem;margin-top:clamp(1.6rem,4.5vh,2.8rem)}
.tagline{font-family:var(--f-mono);text-transform:uppercase;letter-spacing:.19em;
  font-size:clamp(.72rem,1.05vw,.85rem);line-height:2;color:var(--white)}
.cta{display:inline-flex;align-items:baseline;gap:.9em;width:max-content;
  font-family:var(--f-mono);text-transform:uppercase;letter-spacing:.2em;font-size:.78rem;
  padding-bottom:.55em;border-bottom:1px solid var(--white-28);transition:border-color .15s}
.cta .arr{transition:transform .15s}
.cta:hover{border-color:var(--white)}
.cta:hover .arr{transform:translateX(.35em)}
.hero-tag{position:absolute;z-index:2;left:0;bottom:clamp(1.2rem,5vh,3rem);
  display:flex;flex-direction:column;gap:.45rem;color:var(--white-84)}
.hero-meta{display:flex;justify-content:space-between;gap:1rem 2rem;flex-wrap:wrap;
  padding-block:1.15rem;color:var(--white-84)}
.hero-meta .g{display:flex;gap:2rem;flex-wrap:wrap}

/* ============ C 精选图版 ============ */
.plates{padding-top:clamp(4rem,10vh,7rem);padding-bottom:clamp(4rem,12vh,8rem)}
.sec-head{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;
  padding-bottom:1.1rem;border-bottom:1px solid var(--white-28)}
.sec-head .mono{color:var(--white)}
.sec-head .mono-s{color:var(--white-84)}
.plate{display:grid;grid-template-columns:1.05fr .95fr;gap:clamp(2rem,5vw,5rem);align-items:center;
  padding-block:clamp(4.5rem,13vh,9rem);border-bottom:1px solid var(--white-28);min-height:55vh}
.plate:last-of-type{border-bottom:0}
.plate-txt{display:flex;flex-direction:column;align-items:flex-start;gap:1.5rem}
.plate-eyebrow{font-family:var(--f-mono);text-transform:uppercase;letter-spacing:.2em;
  font-size:.72rem;color:var(--white-84)}
.plate-title{font-family:var(--f-cn);font-weight:300;font-size:clamp(1.9rem,4.2vw,3.3rem);
  line-height:1.18;letter-spacing:.04em;color:var(--white);text-wrap:balance}
.plate-abs{max-width:42ch;color:var(--white-84);font-size:.95rem;line-height:1.9}
.plate-go{font-family:var(--f-mono);letter-spacing:.2em;font-size:.72rem;color:var(--white-84);transition:color .15s}
/* 默认图版：展示完整标本 */
.plate-art{position:relative;display:flex;align-items:center;justify-content:center;
  min-height:clamp(14rem,30vh,24rem)}
.plate-art img{width:auto;max-width:100%;max-height:clamp(14rem,34vh,26rem);
  opacity:.62;transition:opacity .2s;pointer-events:none}
/* 满铺图版：内容区采用 Hermes 的画框、过扫描、局部颗粒和暗角。
   图是白墨 + alpha，容器背景的紫直接透上来 —— 饱和度由结构保证，
   不像 mix-blend-mode:screen 那样会把 #7929FF 洗成淡薰衣草色。 */
.plate-art--full{display:block;isolation:isolate;aspect-ratio:666/574;min-height:0;
  overflow:hidden;background:var(--purple)}
.plate-art--full img{position:absolute;inset:0;z-index:1;width:100%;height:100%;
  max-width:none;max-height:none;object-fit:cover;object-position:50% 48%;
  transform:scale(1.06);margin:0!important;opacity:.82}
.plate-art--full::before{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.72' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:11rem 11rem;mix-blend-mode:color-burn;opacity:.22}
.plate-art--full::after{content:"";position:absolute;inset:0;z-index:3;pointer-events:none;
  background:radial-gradient(120% 92% at 50% 45%,transparent 48%,var(--purple) 100%);
  opacity:.72}
.plate-line{position:absolute;inset:14px;z-index:4;border:1px solid var(--white-16);
  pointer-events:none}
.plate-mark{position:absolute;z-index:5;left:26px;right:26px;bottom:22px;
  display:flex;justify-content:space-between;gap:1rem;font-family:var(--f-mono);
  text-transform:uppercase;letter-spacing:.14em;font-size:.58rem;color:var(--white-84);
  line-height:1.3;pointer-events:none}
.plate-art--upright{aspect-ratio:4/5;background:var(--purple)}
.plate-art--upright img{object-fit:cover;object-position:50% 46%;transform:scale(1.03)}
.plate-art--upright::after{
  background:radial-gradient(92% 106% at 50% 45%,transparent 42%,var(--purple) 100%);
  opacity:.72}
.plate-art--runner{aspect-ratio:4/3;background:var(--purple)}
.plate-art--runner img{object-fit:cover;object-position:54% 46%;transform:scale(1.05)}
.plate-art--runner::after{
  background:radial-gradient(112% 92% at 48% 50%,transparent 40%,var(--purple) 100%);
  opacity:.7}
.plate:nth-of-type(even) .plate-art{order:-1}
.plate:nth-of-type(even) .plate-art img{margin-right:auto}
.plate:nth-of-type(odd) .plate-art img{margin-left:auto}
.plate:hover .plate-title{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:.18em;
  text-decoration-color:var(--white-65)}
.plate:hover .plate-art img{opacity:.82}
.plate:hover .plate-art--full img{opacity:.95}
.plate:hover .plate-go{color:var(--white)}

/* 横向过渡图版：在紫色档案与纸面索引之间形成一段连续地层。
   居中窄条而非通栏 —— 源图仅 332px 宽，通栏 1600px 是 4.8 倍放大，
   收到 900px 后降到 2.7 倍，网点才不会稀到能数出来。 */
.spine-bridge{position:relative;isolation:isolate;overflow:hidden;
  width:min(900px,100% - var(--gut) * 2);margin-inline:auto;aspect-ratio:1100/656;
  background:var(--purple)}
.spine-bridge>img{position:absolute;inset:0;z-index:1;width:100%;height:100%;
  object-fit:cover;object-position:50% 46%;transform:scale(1.04);opacity:.8;
  -webkit-mask-image:linear-gradient(90deg,transparent 0%,#000 9%,#000 91%,transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0%,#000 9%,#000 91%,transparent 100%)}
.spine-bridge::before{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.72' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:11rem 11rem;mix-blend-mode:color-burn;opacity:.24}
.spine-bridge::after{content:"";position:absolute;inset:0;z-index:3;pointer-events:none;
  background:linear-gradient(180deg,var(--purple) 0%,transparent 18%,transparent 76%,var(--purple) 100%),
    radial-gradient(110% 92% at 50% 48%,transparent 44%,var(--purple) 100%);opacity:.76}
.spine-bridge-line{position:absolute;z-index:4;inset:14px;border-block:1px solid var(--white-16);
  pointer-events:none}
.spine-bridge-meta{position:absolute;z-index:5;left:26px;right:26px;bottom:1.4rem;
  display:flex;justify-content:space-between;gap:1rem;font-family:var(--f-mono);
  text-transform:uppercase;letter-spacing:.16em;font-size:.58rem;color:var(--white-84)}

/* ============ D 索引（§5-D）============
 * 索引表的配色**跟着所在底色走，不写死**。
 *
 * 这一套样式有三个使用现场：首页 D 区（纸面反转）、归档页（紫底）、
 * 关于页的「做过的事」（紫底）。第一版把颜色直接写成 --violet / --ink，
 * 那只对纸面成立——另外两处于是把深紫的字印在紫底上。实测对比度：
 *
 *     .n  图版号   --violet  on #7929FF   1.20
 *     .d  日期     --ink-65  on #7929FF   1.83
 *     .t  标题     --ink     on #7929FF   2.46
 *     td  分隔线   --paper-hair          1.03
 *
 * 也就是**完全看不见**。而 DOM 里字一个不少，PageRenderTest 全绿——
 * 「查字符串在不在 HTML 里」这种断言对配色错误天生没有分辨力。
 *
 * 默认值取**紫底**那一套（body 的默认背景就是紫），纸面是需要显式声明的例外。
 * 这样将来任何页面复用 .idx-table，拿到的默认配色都和它所在的底色配对，
 * 忘了声明的后果是「对了」而不是「看不见」 */
:root{
  --idx-key:var(--white);        /* 图版号、区块标题 */
  --idx-text:var(--white);       /* 文章标题 */
  --idx-dim:var(--white-84);     /* 日期、板块、统计条 */
  --idx-hair:var(--white-28);    /* 行分隔线 */
}
.paper{
  background:var(--paper);color:var(--ink);
  --idx-key:var(--violet);
  --idx-text:var(--ink);
  --idx-dim:var(--ink-65);
  --idx-hair:var(--paper-hair);
}
.index{padding-block:clamp(3rem,9vh,6rem)}
.index .sec-head{border-color:var(--idx-hair)}
.index .sec-head .mono{color:var(--idx-key)}
.index .sec-head .mono-s{color:var(--idx-dim)}
/* 字号：作者反馈「文字太小」。行高和列宽跟着放，否则数字列会挤成两行 */
.idx-table{width:100%;border-collapse:collapse;margin-top:.4rem;font-variant-numeric:tabular-nums}
.idx-table td{padding:.95rem .5rem;border-bottom:1px solid var(--idx-hair);vertical-align:baseline}
.idx-table tr:hover .t{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:.2em}
/* `.n`（编号列）已经从 idxRow 里删掉，规则跟着删——留着一条没有元素命中的规则，
   下一个人会以为那一列还在，然后去找它为什么不显示。
   日期成了首列，所以用 --idx-key（亮色）而不是 --idx-dim：
   它现在是这一行的定位点，不是附属信息 */
.idx-table .d{font-family:var(--f-mono);font-size:.82rem;letter-spacing:.1em;color:var(--idx-key);width:12.5ch;white-space:nowrap}
.idx-table .t{font-family:var(--f-cn);font-weight:400;font-size:1.06rem;color:var(--idx-text)}
.idx-table .c{font-family:var(--f-mono);font-size:.72rem;letter-spacing:.16em;color:var(--idx-dim);text-align:right;width:9.5ch;white-space:nowrap}
.idx-more{display:inline-flex;gap:.9em;margin-top:2rem;font-family:var(--f-mono);
  text-transform:uppercase;letter-spacing:.2em;font-size:.78rem;color:var(--idx-key);
  padding-bottom:.5em;border-bottom:1px solid var(--idx-hair);transition:border-color .15s}
.idx-more:hover{border-color:var(--idx-key)}

/* ============ E 页脚：整页面板（隐藏页）============
 * 一整屏，默认看不见，正文滚完才被揭开。观感是抽屉被拉开 + 内容浮现，
 * 而不是「滚到底看见一个矮页脚」。
 *
 * ═══ 为什么是 fixed 而不是 sticky（这一版换过一次架构）═══
 * 第一版是 `position:sticky; bottom:0; min-height:100svh`——页脚在文档流里，
 * 于是**它的高度是内容算出来的**。内容一超过一屏，sticky bottom:0 就把它
 * 顶部推到视口以上，人物图和身份牌永远看不见。实测窄屏正是这样：
 *   375×667  内容要 780px  → 超出 113px
 *   390×844  内容要 876px  → 超出  32px
 * 当时的处置是「窄屏干脆不做揭示」（position:static），也就是移动端没有这个效果。
 *
 * 换成 `position:fixed; inset:0` 之后，盒子**恒等于视口**——内容再多也顶不动它，
 * 只会被 overflow:hidden 裁掉。揭示因此在任何视口下都成立，不用再关。
 * 参考的是 Hermès 那个页面的做法（素材/hermes：.hw-footer 就是 fixed + inset:0）。
 *
 * **不写 height。** inset:0 已经把四边钉在视口上，盒子就是视口大小，
 * 比 height:100dvh 更稳：省掉一个单位选择（dvh 会随地址栏重算、svh 会留缝）。
 *
 * ═══ 代价：裁切是静默的 ═══
 * 以后往页脚加字，超出的部分**无声消失**，不会有任何报错。
 * 这是刻意换来的——「静默少一块」比「顶部莫名不见」好排查，而且揭示不会坏。
 * 唯一的护栏是那条「底栏没被裁」的断言。
 *
 * z-index:0 是必须的：页脚在 DOM 里排在正文**之后**，不显式压低的话
 * 它按文档顺序画在正文上面，正文就盖不住它了。
 *
 * 版式：右半整块给人物图（出血），左半克制。理由在 layout.html 的片段注释里。 */
.foot{
  position:fixed; inset:0; z-index:0; overflow:hidden;
  display:flex; flex-direction:column; justify-content:space-between;
  /* **不在这里加 padding。** 加了的话 stage 从内边距之后才开始，
     人物图就顶不到页脚上沿，上缘出血没了（实测图顶停在 y=40）。
     纵向间距由 .foot-base 自己出 */
}

/* ── 页脚内容的淡入 ────────────────────────────────────────────────────────
 *
 * ═══ 只淡内容，不淡背景。这是和 Hermès 的一处刻意分歧 ═══
 * 他们淡的是整个 .hw-footer（含背景）。这边不能照抄：文章页 body.paper 的
 * 背景是浅纸色，而纸面下缘那个巨字 VANSYE 是**紫**的——它靠「下面紧接着
 * 一片同色的紫」才有连成一片的错觉。淡入整个页脚的话，淡到一半时
 * 巨字是浮在浅纸上的，错觉在过程中就破了。
 * 所以那一屏紫从头到尾都在（和换架构之前一样），淡的只有图和字。
 *
 * ═══ 默认值 1 是 fail-open，不能反 ═══
 * 页脚里有导航（foot-index 四条）。不支持、时间线找不到、animation 被
 * prefers-reduced-motion 抹掉——任何一种情况都落回 opacity:1，
 * 也就是「没有淡入效果但内容完整可见」。写成默认 0 的话，
 * 上面任何一环出问题都会变成「页脚永远空白」，而那是个不会报错的故障。
 * Hermès 那边就是默认 0（--hw-footer-opacity,0），没 JS 页脚就永远看不见——
 * 对着陆页可以接受，对带导航的博客不行。
 *
 * ═══ pointer-events 必须门控 ═══
 * 页脚现在铺满视口。正常滚动时盖板压在上面、点击落在盖板上，没问题——
 * **但文章页的盖板是 .wrap，它有 max-width:1500px。** 视口宽过 1500 时
 * 两侧的空档不在盖板的盒子里（那儿只有 ::before，而它 pointer-events:none），
 * 于是点击会穿透到 z-index:0 的页脚上——左列那四条导航就成了隐形的点击热区。
 * 所以 .foot 整体 none，内容 auto，再由关键帧在淡入过程中压住。
 * pointer-events 不可插值，在两个关键帧之间会在中点跳变（28%→66% 之间约 47%）。
 *
 * ═══ 时间线为什么挂在盖板上 ═══
 * 不能挂在页脚自己身上：**它是 fixed 的，相对滚动口永远不动，
 * view() 一步也不会走。** 会动的是盖板，所以 view-timeline-name 定义在那边，
 * 这里只是引用（body 上的 timeline-scope 负责让兄弟之间看得见）。
 *
 * ═══ 区间和曲线 ═══
 * animation-range:exit —— 一个 phase 名等于「该阶段 0% 到 100%」，而盖板的
 * exit 阶段是从「下边缘齐视口下沿」到「下边缘齐视口上沿」，**正好一屏滚动**，
 * 和文档总长无关（用 scroll() 的话区间百分比会随页面长短漂）。
 *
 * **别写成 `entry 100% exit 100%`。** 我第一版就是那样，实测淡入从 y≈2100
 * 就开始了（应该 4067）。原因是对高于视口的 subject，`entry` 结束于
 * 「它开始完全盖住视口」那一刻，也就是**盖板顶边齐视口顶边**（实测 954），
 * 不是我以为的「底边齐视口底沿」。三个锚点的实测值（1440×900 首页，
 * 盖板 main top=954 bottom=4967）：
 *     entry 100% = 954    盖板顶边齐视口顶边
 *     exit 0%    = 4067   盖板底边齐视口底沿  ← 要的起点
 *     exit 100%  = 4967   盖板底边齐视口顶沿
 * 写错的表现是「淡入太早、还在正文中段页脚就亮了」，不会有任何报错。
 *
 * 曲线不用改区间去凑，用关键帧偏移刻出来：0–28% 全透明、28→66% 线性升起、
 * 66–100% 保持。这三个数就是 Hermès 那条 JS 曲线
 * （clamp((0.72h - 离底距离) / 0.38h, 0, 1)）换算过来的。
 * **要调快慢就改 28 和 66 这两个数。** */
.foot{ pointer-events:none; }
.foot-stage, .foot-base{ opacity:var(--foot-reveal, 1); pointer-events:auto; }

@keyframes foot-reveal{
  0%,   28%{ opacity:0; pointer-events:none; }
  66%, 100%{ opacity:1; pointer-events:auto; }
}

@supports (animation-timeline: --x){
  .foot-stage, .foot-base{
    animation:foot-reveal linear both;
    animation-timeline:--cover;
    animation-range:exit;
  }
}

/* 盖住页脚的那些层。**用排除法而不是列举法**：
 * 漏掉一个新的 body 子元素，排除法的后果是「它也不透明」（正确），
 * 列举法的后果是「它是透明的，页脚从第一屏透出来」（坏的）。
 * 让忘记声明的那一侧是安全的。
 *   .chrome-wrap  里面是 fixed 的紫框和噪点，给了 z-index 就会被压到正文下面
 *   .progress     文章页顶部的 fixed 进度条，z-index 210，同上
 *   script        没有渲染框，给背景没意义 */
body > :not(.foot, .chrome-wrap, .progress, script){ position:relative; z-index:1; }

/* **默认必须看不见揭示页。**
 * 页脚从第一屏就铺满视口（fixed + inset:0），全靠盖板压住。而短页面
 * （比如只有一篇文章的归档页）的盖板不到一屏高，压不住——实测那时候
 * scroll=0 就露出了 479/528px，等于「揭示页默认是打开的」，效果全没了。
 *
 * 所以紧挨页脚的那一层至少要一屏高。「紧挨页脚的那一层」没法用
 * 「前一个兄弟」选择器表达（CSS 没有这个），但 :has(+ .foot) 可以：
 * 选中「下一个兄弟是 .foot 的那一个」。每页正好命中一个，
 * **不用改任何模板**——首页命中 main、文章页命中 .wrap，
 * 都是本来就该负责遮挡的那一层。
 *
 * ═══ margin-bottom 是换成 fixed 之后新加的，不能少 ═══
 * 页脚离开文档流之后，它原来贡献给文档的那一屏高度**消失了**——
 * 文档矮一屏，于是滚到底时盖板底边正好停在视口底边，页脚一点也露不出来。
 * 这一条把那一屏还回去，也就是「揭示所需的滚动距离」。
 * 用 dvh 而不是 svh：要让盖板最终能完全滚出视口，预留量必须 ≥ 视口高，
 * 而 svh 在地址栏收起时小于视口，会在底部留下一条永远滚不掉的正文。
 * （Hermès 的等价物是 .hw-scroll{margin-bottom:var(--hw-footer-h)}。）
 *
 * ═══ view-timeline-name 是给淡入用的 ═══
 * 淡入的进度不能挂在页脚自己身上：**它是 fixed 的，相对滚动口永远不动，
 * view() 时间线一步也不会走。** 会动的是盖板，所以时间线定义在这里、
 * 由页脚里的元素引用（见下面 @supports 那一段）。
 *
 * ═══ 两处都用 dvh，不能用 svh ═══
 * min-height 原来写的是 100svh。svh 是**地址栏显示时**的那个较小值——
 * 地址栏收起后视口变成 dvh（更大），盖板就比视口短，首屏底部会露出一条
 * 页脚的背景。紫底页看不出来（body 本来就是紫），但**文章页 body.paper 是
 * 浅纸色、而页脚背景是紫**，那条缝会是一道显眼的紫边。
 * Playwright 不模拟地址栏伸缩，这个测不出来——是按单位语义推出来的，
 * 需要真机确认。 */
body > :not(.foot):has(+ .foot){
  min-height:100dvh;
  margin-bottom:100dvh;
  view-timeline-name:--cover;
  view-timeline-axis:block;
}

/* 盖板的底色画在一个**满视口宽**的伪元素上，不能用元素自己的 background。
 *
 * 第一版就是用 background 写的，而文章页的盖板是 `body > .wrap`——
 * 它有 max-width:1500px + margin-inline:auto。视口比 1500 宽的时候
 * 盖板两侧盖不住，z-index:0 的紫页脚就从缝里透出来：
 *
 *     视口 1440   缝   0px   （盖板 1440，正好填满）
 *     视口 1600   缝  50px
 *     视口 1920   缝 210px
 *     视口 2560   缝 530px
 *
 * **我上一轮只量了 1440 —— 1440 < 1500，盖板刚好填满视口，
 * 这个 bug 一点痕迹都没有。** 挑测量宽度的时候没去看 max-width 是多少，
 * 于是挑了一个唯一看不见问题的宽度。以后量这类东西要跨过所有断点取样。
 *
 * left:50% + width:100vw + translateX(-50%)：伪元素的中心对齐盖板的中心，
 * 而盖板是居中的，所以它满视口宽且左右对称。溢出由 body 的 overflow-x:clip 裁掉。
 * z-index:-1 让它落在盖板自己的内容下面，但仍在盖板的层叠上下文里，
 * 所以还是压在页脚（根上下文的 z-index:0）之上。 */
body > :not(.foot, .chrome-wrap, .progress, script)::before{
  content:""; position:absolute; z-index:-1; pointer-events:none;
  top:0; bottom:0; left:50%; width:100vw; transform:translateX(-50%);
  background:var(--purple);
}
body.paper > :not(.foot, .chrome-wrap, .progress, script)::before{ background:var(--paper); }
/* 人物图：右半，上缘和右缘出血（构图三律 §3.3「图永远被裁切」）。
   object-position:left top —— 裁掉右边和下边，脸留在框里。
   线条色和透明纸面是预处理烧进 PNG 的，见 blog/target/make-figure.py */
.foot-figure{
  position:absolute; z-index:0; top:0; right:0;
  width:52%; height:100%;
  object-fit:cover; object-position:left top;
  pointer-events:none; user-select:none;
}

/* stage：图在这里面出血，底栏在它外面。overflow:hidden 负责裁 */
.foot-stage{
  position:relative; flex:1; overflow:hidden;
  display:flex; align-items:center;
}
/* 图的左边缘是一条**硬的直边**——这在这套设计里是对的（§3.3「图永远被裁切」，
   首页的图版块也是硬边）。但光有硬边会读成「贴歪了一张图」，
   所以补一条发丝线，把它变成一条有意的图版边界，和首页 .plate-art 的
   内嵌发丝线是同一个手法。不用渐变淡出：§9 第 1 条禁止任何渐变 */
.foot-stage::after{
  content:""; position:absolute; z-index:1; pointer-events:none;
  top:0; bottom:0; right:52%; width:1px; background:var(--white-28);
}

/* **必须写 width:100%。**
   .wrap 带 margin-inline:auto，而 auto 外边距会取消 flex 的 stretch——
   于是这两块都缩成 fit-content 并且居中了：实测 .foot-panel 只有 482px、
   .foot-base 691px，左列跑到了视口中间。加上 width:100% 才回到
   「满宽、上限 1500、居中」这个 .wrap 本来的意思 */
.foot-panel{ position:relative; z-index:1; width:100%; }
/* 左列宽度**必须跟着图的起点算**，不能只写一个固定上限。
   图从视口 48% 处开始，而 wrap 的左边距是 var(--gut)（宽屏 6vw）。
   第一版写死 min(30rem,100%)：1440 下没事（列 86–566、图 691），
   但 1024 下 gut 只有 61px，列变成 61–541 而图从 492 开始——**压了 49px**，
   实测有 9 个文字块落在图上。
   calc(48vw - var(--gut) - 2rem) 保证列永远在图左边留出 2rem 的空隙。 */
.foot-col{
  width:min(30rem, calc(48vw - var(--gut) - 2rem));
  display:flex; flex-direction:column; gap:clamp(1.8rem,5.5vh,3.4rem);
}

.foot-id{ display:flex; align-items:flex-start; gap:1rem; }
.foot-id img{ width:2.1rem; height:auto; }
.foot-id span{ color:var(--white-84); line-height:1.75; }

.foot-tagline{
  font-family:var(--f-display); font-weight:400;
  font-size:clamp(1.55rem,3.2vw,2.5rem); line-height:1.14;
  letter-spacing:.01em; color:var(--white);
}

/* 索引：索引寄存器的行结构，四行。编号是版式的一部分，不是图版号 */
.foot-index{ display:flex; flex-direction:column; }
.foot-index a{
  display:flex; align-items:baseline; gap:1.2rem;
  padding-block:.78rem; border-bottom:1px solid var(--white-28);
  font-family:var(--f-mono); text-transform:uppercase;
  letter-spacing:.16em; font-size:.7rem;
  color:var(--white-84); transition:color .15s;
}
.foot-index a:first-child{ border-top:1px solid var(--white-28); }
.foot-index .n{ flex:none; color:var(--white); font-variant-numeric:tabular-nums; }
.foot-index .t{ flex:1; }
.foot-index .r{ flex:none; }
.foot-index a:hover{ color:var(--white); }
.foot-index a:hover .t{ text-decoration:underline; text-underline-offset:.3em; }

.foot-seal{ display:flex; align-items:center; gap:1.1rem; }
.foot-seal img{ width:3.2rem; height:auto; }
.foot-seal span{ color:var(--white-84); line-height:1.75; }

.foot-base{
  position:relative; z-index:1; width:100%;
  display:flex; justify-content:space-between; align-items:baseline; gap:.6rem 2rem;
  flex-wrap:wrap;
  padding-block:1.15rem clamp(1.8rem,4.5vh,3.2rem);
  margin-inline:auto;
  border-top:1px solid var(--white-28); color:var(--white-84);
}
.foot-base a:hover{ color:var(--white); text-decoration:underline; text-underline-offset:.35em; }

/* 窄屏：两栏并排放不下，改成上下叠。
 *
 * **图不能垫在文字底下。** 第一版就是那么做的（整幅、opacity .28），
 * 而那样文字落在线条最密处的对比度只有 3.39（`--white-84`）／4.14（纯白），
 * 都不到 4.5。而且**降低透明度救不回来**：薰衣草线条比紫底亮，
 * 越淡只是越接近紫底，中间那一段永远压不到 4.5 以下的亮度差。
 * 所以窄屏改成「图在上、字在下」，两者不重叠——
 * 图还完整看得见，文字回到纯紫底上（4.56）。
 */
@media (max-width:1023px){
  /* 窄屏只改**排布**，不再关掉揭示。
   *
   * 这里原来有一条 `.foot{position:static}`，理由是「揭示成立的前提是页脚
   * 正好一屏，而窄屏内容竖排会超出（375 超 113px、390 超 32px），
   * sticky bottom:0 会把顶部推到视口外」。那条理由随架构一起作废了——
   * 页脚现在是 fixed + inset:0，盒子恒等于视口，内容顶不动它。
   *
   * **代价换了个位置：现在超出的部分会被 overflow:hidden 静默裁掉。**
   * 所以下面那些尺寸不只是「排得下」的问题，是「装进一屏」的硬约束。 */

  /* 图高**必须走变量**：下面 .foot-figure 的 height 和 .foot-stage::after 的 top
     必须永远相等（发丝线就画在图的下边缘上）。原来两处各写一个 32svh，
     改一处漏一处的后果是「线从图上跑开」，而那是个纯视觉故障、没有报错。

     ── 为什么是 dvh 而不是 svh ──
     原来写 svh，理由是「页脚共 100svh」。页脚换成 fixed + inset:0 之后
     它的高度就是**视口**（跟着地址栏走，等于 dvh），再用 svh 的话
     地址栏收起时图会相对页脚变小、比例漂掉。改成 dvh 和页脚同源。

     ── 为什么不能用百分比 ──
     百分比高度要求容器有确定高度。实测 34% 直接没解析，图退回内在高度
     537px 并溢出，矩形上边跑到了 -299。（现在 .foot-stage 的高度其实
     已经是确定的了——flex:1 of 一个定高父元素——但没必要去赌这个。） */
  .foot{ --foot-fig-h:32dvh; }

  .foot-stage{ flex-direction:column; align-items:stretch; }
  /* position:static 把图放回文档流，它就成了横贯的一条带。
     **order:2 把它排到文字下面**——桌面上图在右半边，视线是「左读完、右边是图」；
     竖排之后如果图在最上面，视线变成「先撞图、再读字」，进入方式反了。
     放到文字之后，读完再看到图，和桌面的顺序一致。
     用 order 而不是 column-reverse：DOM 顺序不动（图是 aria-hidden 的装饰，
     读屏软件本来就跳过它），而 column-reverse 会连 align-items 的语义一起翻。 */
  .foot-figure{
    position:static; order:2; flex:none; width:100%; height:var(--foot-fig-h);
    object-position:center 18%;
  }
  /* 发丝线从「竖在两栏之间」转成「横在文字和图之间」，作用不变：
     把裁切边说成有意的图版边界。
     **必须用 bottom 而不是 top。** 图挪到底之后线要贴在图的**上**边缘，
     而 top:var(--foot-fig-h) 量的是从 stage 顶算下来的距离——图在顶部时那正好是
     图的下边缘，图挪到底之后就落在文字中间了。bottom 直接对着图的上边缘量，
     跟 --foot-fig-h 一起变，不会漂。 */
  .foot-stage::after{
    top:auto; bottom:var(--foot-fig-h); left:0; right:auto; width:100%; height:1px;
  }
  .foot-panel{ flex:1; display:flex; align-items:center; }
  .foot-col{ width:100%; }
  .foot-tagline{ font-size:clamp(1.4rem,7vw,2rem); }
}

/* ── 手机：把页脚内容压进一屏 ──────────────────────────────────────────────
 *
 * 换成 fixed 之后页脚不会再被顶高，但**装不下的部分会被 overflow:hidden
 * 静默裁掉**。实测（改之前，线上 DOM + 本地 CSS）：
 *   375×667  stage 只有 550px 而内容要 663px → index 下缘裁 22px、seal 整块不见
 *   390×844  stage 719px 而内容要 751px      → seal 下缘裁 32px
 *   768 及以上                               → 一块都没裁
 * 所以只在 <768px 动手，iPad 那一档保持原样。
 *
 * 三件事各自的效果（375 上）：
 *   隐藏印章    省 54px 内容 + 一个 36.7px 的间隙 = 约 91px
 *   图 32→26dvh 省 40px
 *   间隙收紧    每个省 10px × 2 = 20px
 * 合起来约 151px，需要 113px —— 留了约 37px 余量，不是刚好卡住。
 *
 * 为什么牺牲印章：它是纯装饰（一枚章 + 两行小字），是这一块里唯一
 * 不承载信息也不承载导航的东西。Hermès 在移动端也是这么做的——
 * 他们把巨字 wordmark 和 Nous 的 logo 图都加了 max-md:hidden。 */
@media (max-width:767px){
  .foot{ --foot-fig-h:26dvh; }
  /* 桌面是 clamp(1.8rem,5.5vh,3.4rem)。收紧之后 375 上每个间隙 26.7px（原 36.7） */
  .foot-col{ gap:clamp(1.2rem,4vh,2.6rem); }
  .foot-seal{ display:none; }
}

/* ============ 响应式 ============ */
@media (max-width:767px){
  .frame{display:none}
  .hero-art{right:-34vw;top:auto;bottom:13vh;width:122vw;height:58vh;object-position:48% 50%;
    opacity:.4;-webkit-mask-image:linear-gradient(90deg,transparent 0%,#000 30%);
    mask-image:linear-gradient(90deg,transparent 0%,#000 30%)}
  .hero-stage{min-height:92vh}
  .plate{grid-template-columns:1fr;gap:1.8rem;min-height:0;padding-block:3.6rem}
  .plate:nth-of-type(even) .plate-art{order:0}
  .plate-art{aspect-ratio:2/1}
  .plate-art--full{aspect-ratio:4/3}
  .plate-art--full img{object-position:50% 48%;transform:scale(1.06)}
  .plate-art--upright{aspect-ratio:5/6}
  .plate-art--upright img{object-position:50% 45%;transform:scale(1.03)}
  .plate-art--runner{aspect-ratio:4/3}
  .plate-art--runner img{object-position:54% 46%;transform:scale(1.05)}
  .plate-line{inset:10px}
  .plate-mark{left:18px;right:18px;bottom:15px;gap:.6rem;font-size:.52rem;
    letter-spacing:.08em}
  .spine-bridge{aspect-ratio:4/3}
  .spine-bridge>img{object-position:52% 48%;transform:scale(1.1)}
  .spine-bridge-line{inset:10px}
  .spine-bridge-meta{bottom:1rem;left:18px;right:18px;gap:.35rem;font-size:.52rem}
  .idx-table .c{display:none}
  .topnav{gap:1.1rem}
}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{transition-duration:.01ms!important;animation-duration:.01ms!important}
}

/* ==========================================================================
 * 文章页（规格 §6.1）—— 原型里没有这一页，以下按规格新写
 *
 * 「读者呆最久的地方」，所以整页翻成纸面。紫框保留（§6.1 第一条）。
 * ========================================================================== */

/* 整页纸面。加在 <body> 上，于是 ::selection 的反色规则（.paper 那条）也一起生效 */
body.paper{ background:var(--paper); color:var(--ink); }

/* 纸面上的导航要换色 —— 紫底那套是白色系，直接搬过来在纸上看不见 */
.paper .brand-name b,
.paper .brand-name span,
.paper .topnav a{ color:var(--violet); }
.paper .topnav a{ opacity:.78; }
.paper .topnav a:hover{ opacity:1; }
.paper .rule{ background:var(--paper-hair); }
.paper .tick{ background:var(--violet); }

/* ── 标本签（§6.1 头部，**左对齐不居中**）───────────────────────────── */
.spec-label{ padding-top:clamp(2.5rem,7vh,5rem); }
/* eyebrow：PLATE 012 — 2026.03.08 — LAB — 9 MIN */
.spec-label .eyebrow{
  font-family:var(--f-mono); text-transform:uppercase;
  letter-spacing:.2em; font-size:.72rem; color:var(--violet);
}
.spec-label h1{
  font-family:var(--f-cn); font-weight:300;
  font-size:clamp(1.8rem,4vw,2.6rem); line-height:1.15;
  color:var(--ink); margin-top:1.1rem; max-width:22ch;
}
.spec-label .dek{
  font-size:.95rem; color:var(--ink-65); margin-top:1rem; max-width:52ch;
}
.spec-label hr{ border:0; height:1px; background:var(--paper-hair); margin-top:clamp(2rem,5vh,3rem); }

/* ── 正文（§6.1 中段）──────────────────────────────────────────────────
 * 段宽 68ch、行高 1.9 —— 这两个数字是规格 §2 字号表里写死的，不要「顺手调大一点」
 * -------------------------------------------------------------------- */
.prose{ max-width:68ch; padding-block:clamp(2.5rem,6vh,4rem); }
.prose > * + *{ margin-top:1.4em; }
.prose p{ color:var(--ink); }

/* h2 = mono 编号 + 换行 + 宋体标题。
   规格说「文章是线性的，编号成立」——所以编号是 CSS 计数器自动排的，
   不是作者手写的。作者手写的话，插一节就得全改一遍 */
.prose{ counter-reset:h2; }
.prose h2{
  font-family:var(--f-cn); font-weight:400; font-size:1.35rem;
  line-height:1.35; color:var(--ink); margin-top:2.6em;
}
.prose h2::before{
  counter-increment:h2;
  content:counter(h2,decimal-leading-zero);
  display:block; margin-bottom:.5rem;
  font-family:var(--f-mono); font-size:.7rem; letter-spacing:.2em;
  color:var(--violet);
}
.prose h3{
  font-family:var(--f-cn); font-weight:400; font-size:1.12rem;
  color:var(--ink); margin-top:2em;
}

/* 链接：下划线 25% → hover 变实。两种地面同规则（§6.1） */
.prose a{
  color:var(--violet);
  text-decoration:underline;
  text-decoration-color:rgba(106,31,232,.25);
  text-underline-offset:.22em;
  transition:text-decoration-color .15s;
}
.prose a:hover{ text-decoration-color:var(--violet); }

/* 行内代码：mono + violet + 发丝边框，**无底色**（§6.1） */
.prose :not(pre) > code{
  font-family:var(--f-mono); font-size:.86em; color:var(--violet);
  border:1px solid var(--paper-hair); padding:.05em .35em;
  background:none;
}

/* 代码块：白底 + 发丝边框 + mono violet。
   **不要彩色语法高亮**（§6.1 明文，§9 第 6 条列为禁止事项）——
   所以这里既不引 highlight.js 也不给 .language-* 上颜色。
   「最多用透明度分层」那句留给以后真需要时做，现在一档都不分 */
.prose pre{
  background:var(--code-bg); border:1px solid var(--paper-hair);
  padding:1.1rem 1.2rem; overflow-x:auto;
  /* 直角。规格 §9 第 2 条：圆角 >4px 一律禁止 */
  border-radius:0;
}
.prose pre code{
  font-family:var(--f-mono); font-size:.8rem; line-height:1.75;
  color:var(--violet); white-space:pre;
}

/* 引用块：左 2px 实线，**不用斜体**（中文斜体是伪斜，§6.1） */
.prose blockquote{
  border-left:2px solid var(--violet); padding-left:1.2rem;
  color:var(--ink-65); font-style:normal;
}

/* 表格：索引寄存器的密度，无竖线 */
/* 正文表格要能**自己横向滚动**，不然它把整个文档撑宽。
   实测 390 视口下一张 markdown 表格的 min-content 是 373px，
   加上左边距 scrollWidth 变成 397 —— 整页出现横向滚动条，
   而 body 的 overflow-x:clip 只是把溢出裁掉、并不阻止文档变宽。
   这是内容驱动的溢出：表格是 markdown 渲染出来的，模板包不到它，只能用 CSS 兜。

   display:block + width:max-content + max-width:100% + overflow-x:auto 是
   这件事的标准写法：display:block 让它成为滚动容器，而 thead/tbody/tr/td
   的 display 没动，浏览器会生成匿名表格盒，**内部的列对齐照旧**。 */
.prose table{
  display:block; width:max-content; max-width:100%; overflow-x:auto;
  border-collapse:collapse; font-size:.92rem;
}
.prose th,.prose td{
  text-align:left; padding:.55rem .8rem .55rem 0;
  border-bottom:1px solid var(--paper-hair);
}
.prose th{
  font-family:var(--f-mono); text-transform:uppercase;
  letter-spacing:.16em; font-size:.64rem; font-weight:400; color:var(--violet);
}
.prose td{ color:var(--ink); }

/* 图：占满正文宽。图注见下 */
.prose img{ width:100%; margin-block:1.8em; }
.prose figcaption,
.prose .fig{
  font-family:var(--f-mono); text-transform:uppercase;
  letter-spacing:.16em; font-size:.64rem; color:var(--ink-65);
  margin-top:-1em;
}

.prose ul,.prose ol{ padding-left:1.4rem; }
.prose li + li{ margin-top:.5em; }
.prose li::marker{ color:var(--violet); }
.prose hr{ border:0; height:1px; background:var(--paper-hair); margin-block:2.4em; }

/* ── 文末（§6.1 倒数第二条）：居中眼球印章 + END OF PLATE 012 ──────────
 * 规格 §9 第 5 条禁止居中排版，**印章是明文列出的例外之一** */
.plate-end{ text-align:center; padding-block:clamp(3rem,8vh,5rem); }
.plate-end img{ width:2rem; margin-inline:auto; opacity:1; }
.plate-end span{
  display:block; margin-top:.9rem;
  font-family:var(--f-mono); text-transform:uppercase;
  letter-spacing:.2em; font-size:.64rem; color:var(--ink-65);
}

/* ── prev / next：两行索引寄存器样式（§6.1 最后一条）────────────────── */
.plate-nav{ border-top:1px solid var(--paper-hair); }
.plate-nav a{
  display:flex; gap:1.2rem; align-items:baseline;
  padding:1.1rem 0; border-bottom:1px solid var(--paper-hair);
}
.plate-nav .dir{
  font-family:var(--f-mono); text-transform:uppercase; letter-spacing:.2em;
  font-size:.64rem; color:var(--violet); flex:none; width:5.5rem;
}
/* 日期列，和索引寄存器同一个位置、同一个宽度——邻居行就是索引行的一种。
   **宽度必须和 .idx-table .d 一致（12.5ch）。**
   第一版写的是 7ch：`2026.08.16` 是 10 个字符，加 .1em 字距根本塞不进去，
   而 flex:none 配固定宽度不会把盒子撑开——溢出的字直接压到标题上，
   页面上是「2026.08.低空略过…」。DOM 里两段文字都对，测试全绿，
   **只有截图能看出来**。 */
.plate-nav .d{
  font-family:var(--f-mono); font-size:.78rem; letter-spacing:.1em;
  color:var(--ink); flex:none; width:12.5ch; white-space:nowrap;
  font-variant-numeric:tabular-nums;
}
.plate-nav .t{ font-family:var(--f-cn); color:var(--ink); }
.plate-nav a:hover .t{ text-decoration:underline; text-underline-offset:.22em; }

/* ==========================================================================
 * 文章页夜间主题
 *
 * 三个状态：跟系统（默认）／强制亮／强制暗。手动选择记在 localStorage，
 * 由 <head> 里那段同步脚本在首屏之前贴到 <html data-theme> 上。
 *
 * ── 为什么用 light-dark() 而不是 @media (prefers-color-scheme) ──
 * 用媒体查询的话，同一份「亮值→暗值」的映射要写两遍：
 * 一遍在 `@media (prefers-color-scheme:dark) html:not([data-theme=light])` 里，
 * 一遍在 `html[data-theme=dark]` 里（不然手动开关在亮系统上不生效）。
 * 两份映射会分岔——加一个令牌只改了一处，那个令牌就只在一半情况下变。
 * light-dark() 把两个值写在同一行，三个状态全部由 color-scheme 决定，
 * 映射**只有一份**。实测过 light-dark() 在自定义属性里可用，
 * 且四种组合（系统亮/暗 × 无覆盖/两种覆盖）都解析正确。
 *
 * ── 为什么裹在 @supports 里 ──
 * 自定义属性在**解析时**接受任何词法，`light-dark()` 不被支持时
 * 是在**取值时**才失效——而取值时失效的声明会变成 unset，
 * `background` 于是变透明，比任何一套主题都糟。
 * @supports 保证老浏览器根本看不到这一段，拿到的是 :root 里的亮色值。
 *
 * ── 为什么只覆盖 body.paper，不覆盖 :root ──
 * `.paper` 这个类首页 D 区那条纸面横条也在用。覆盖 :root 会把它一起翻黑，
 * 而要的是「文章页」。页脚同理不受影响：它用的是 --purple / --white 那一套。
 *
 * 深色值都是量过的（底 #130B26）：
 *     正文 #EFEAFF          16.21   AAA
 *     辅助 62% #EFEAFF       6.61   AA
 *     链接/标签 #C4AEFF      9.83   AAA
 *     代码块 #1C1239 与正文面的分离度 1.08 —— 和亮色下 #FFF 印在 #F4F2F8 上
 *     （1.09）是同一个量级，都靠发丝边框划边界，观感一致
 * ========================================================================== */
@supports (color: light-dark(#000, #fff)) {
  body.paper{
    /* 默认跟系统。两个值写在一行里，改一个能立刻看见另一个 */
    color-scheme: light dark;
    --paper:      light-dark(#F4F2F8,              #130B26);
    --ink:        light-dark(#2B1670,              #EFEAFF);
    --ink-65:     light-dark(rgba(43,22,112,.65),  rgba(239,234,255,.62));
    --violet:     light-dark(#6A1FE8,              #C4AEFF);
    --paper-hair: light-dark(rgba(106,31,232,.18), rgba(196,174,255,.24));
    --code-bg:    light-dark(#FFFFFF,              #1C1239);
  }
  /* 手动覆盖。只改 color-scheme，上面那一份映射自己会跟着走 */
  html[data-theme="light"] body.paper{ color-scheme: light; }
  html[data-theme="dark"]  body.paper{ color-scheme: dark;  }
}

/* 主题开关：三个 mono 词，当前那个高亮。
   放在标本签那一行的右端，**只在文章页出现**——
   放进全站导航的话，它在另外四个页面上什么都不做，那是个假控件 */
.spec-head{
  display:flex; justify-content:space-between; align-items:baseline;
  gap:1rem 1.5rem; flex-wrap:wrap;
}
.theme-switch{ display:inline-flex; gap:1em; flex:none; }
.theme-switch button{
  /* 按钮要长得像 mono 标签，不像按钮 —— 但**必须是 button**：
     div 加 onclick 键盘上按不到，而这是个真控件 */
  background:none; border:0; padding:0; cursor:pointer;
  font:inherit; letter-spacing:inherit; text-transform:inherit;
  color:var(--ink-65); transition:color .15s;
}
.theme-switch button:hover{ color:var(--violet); }
.theme-switch button[aria-pressed="true"]{
  color:var(--violet);
  text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:.3em;
}
.theme-switch button:focus-visible{ outline:2px solid var(--violet); outline-offset:3px; }

/* ── 纸面下边缘的巨字（§6.1 之外，作者要的 Hermès 效果）────────────────
 * 巨大的 wordmark 印在纸面最底部，紫色（相对纸面是反色），
 * 被纸面的下边缘裁掉下半截。overflow:hidden 配一个小于字高的 height
 * 就是那个「被裁掉」；height 用 em，所以跟着字号一起缩放，
 * 不会在小屏上变成「裁太多只剩一条」。
 *
 * 不用 mix-blend-mode 做自动反色：这个字整块都在纸面上，不跨边界，
 * 用混合模式等于为一个不会发生的情况付代价，而且混合模式在 sticky
 * 层叠上下文里的行为要额外验。直接给颜色更可控。 */
.edge-mark{
  margin-top:clamp(2rem,6vh,4rem);
  font-size:clamp(3.4rem,17vw,17rem);
  /* 字是**完整**的，不裁。第一版用 overflow:hidden 真把下半截切掉了，
     那是误解——Hermès 那个字本身是完整的，只是和下面的品牌色同色、
     又紧贴内容面的下边缘，看起来才像被切断。
     line-height:.78 已经让盒子小于字高，字整个露在外面。

     要「贴住」纸面下缘，控制的是**盒子高度**，不是外边距。
     试过 margin-bottom:-.03em，没用——.wrap 没有下内边距也没有边框，
     子元素的下外边距会**和父元素的合并**、跑到 .wrap 外面去拉页脚，
     纸面自己的下缘一点没动，间隔还是 7px。

     行盒高 .78em，而全大写的墨迹底边就是基线，基线在盒顶下方约 .75em
     （半行距 (0.78-1.0)/2 加上 Caslon 约 .86em 的 ascent）。
     .75em 会让盒底正好落在基线上——但那样在边界处仍然看得出一条发丝缝
     （抗锯齿的最后一行像素落在纸面上）。压到 .71em，字的最后一小截落到
     纸面之外、印在同色的紫上，两边就真正连成一片。
     **要往下挪就调这一个数**：越小 = 字露出纸面越多 = 交界处越连续。
     overflow 保持 visible：基线以下本来没有墨（全大写没有降部），不会被切到。 */
  line-height:.78;
  height:.71em;
}
.edge-mark span{
  display:block;
  font-family:var(--f-display); font-weight:400;
  letter-spacing:.01em; text-align:center;
  color:var(--purple); white-space:nowrap; user-select:none;
  transform:translateY(-0.0315em);
}

/* ── 阅读进度条（F-09：顶部 1px 发丝线）─────────────────────────────
 * 放在紫框之上（z-index 高于 .frame 的 200），否则被框盖住看不见 */
.progress{
  position:fixed; top:0; left:0; height:1px; width:0;
  background:var(--violet); z-index:210;
  /* 不做 transition：进度条要跟手，加过渡会「拖在后面」 */
}

/* ── 代码块 COPY 按钮（F-09：mono .6rem、78% 白 → 纸面上是 violet）──── */
.prose pre{ position:relative; }
.copy{
  position:absolute; top:.5rem; right:.5rem;
  font-family:var(--f-mono); text-transform:uppercase; letter-spacing:.16em;
  font-size:.6rem; color:var(--violet);
  background:#FFF; border:1px solid var(--paper-hair);
  padding:.25rem .5rem; cursor:pointer; border-radius:0;
  opacity:0; transition:opacity .15s;
}
.prose pre:hover .copy,
.copy:focus-visible{ opacity:1; }

/* 404（§6.6）：允许居中的页面 */
.notfound{ text-align:center; padding-block:clamp(6rem,20vh,12rem); }
.notfound img{ width:min(34rem,70vw); margin-inline:auto; opacity:.45; }
.notfound h1{
  font-family:var(--f-display); font-weight:400;
  font-size:clamp(2rem,7vw,4.5rem); line-height:.9; margin-top:2rem;
}
.notfound p{ margin-top:1.2rem; color:var(--white-65); }
.notfound a{
  display:inline-block; margin-top:2rem;
  font-family:var(--f-mono); text-transform:uppercase; letter-spacing:.2em;
  font-size:.72rem; text-decoration:underline; text-underline-offset:.3em;
  text-decoration-color:var(--white-28);
}
.notfound a:hover{ text-decoration-color:var(--white); }

/* 移动端：紫框在 <768px 关闭（§4 第一条） */
@media (max-width:767px){
  .frame{ display:none; }
}

/* 动效白名单之外一律不做，并且尊重系统设置（§7 第 3 条） */
@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  *,*::before,*::after{ transition:none !important; animation:none !important; }
}

/* ==========================================================================
 * 内页（规格 §6.2–6.5）—— 全部用第 4 节的工具箱，不造新排场
 * ========================================================================== */

/* ── 归档页 §6.2：索引寄存器，**一张插画都没有** ─────────────────────
 * 「密度反差就是设计」。这一页看起来「空」是故意的，别加图 */
.index--page{ padding-block:clamp(2.5rem,7vh,5rem); }
.stratum{ margin-top:clamp(2.5rem,6vh,4rem); }
/* 组头：STRATUM 2026 + 发丝线延伸到行尾 */
.stratum-head{ display:flex; align-items:center; gap:1.2rem; margin-bottom:.6rem; }
.stratum-head .mono{ flex:none; color:var(--idx-key); }
.stratum-head .rule{ flex:1; }

/* ── 随笔页 §6.4：日期 + 一段文字，发丝线分隔，无图 ────────────────── */
.notes-page{ padding-block:clamp(2.5rem,7vh,5rem); }
.note-row{
  display:flex; gap:clamp(1.2rem,4vw,3rem); align-items:baseline;
  padding-block:1.6rem; border-bottom:1px solid var(--white-28);
}
.note-date{ flex:none; width:6.5rem; color:var(--white-84); font-variant-numeric:tabular-nums; }
.note-body{ display:flex; flex-direction:column; gap:.5rem; max-width:52ch; }
.note-title{ font-family:var(--f-cn); font-weight:300; font-size:1.25rem; line-height:1.35; }
.note-abs{ font-size:.95rem; color:var(--white-84); }
.note-row:hover .note-title{ text-decoration:underline; text-underline-offset:.22em; }

/* ── LAB §6.3：栅格，格子 28% 白发丝边框，直角 ──────────────────────
 * hover 时**只有边框变白，背景不变**（规格明文）——加背景色就变成卡片了 */
.lab-page{ padding-block:clamp(2.5rem,7vh,5rem); }
.lab-grid{
  display:grid; grid-template-columns:repeat(auto-fill,minmax(20rem,1fr));
  gap:1px; margin-top:clamp(2rem,5vh,3rem);
}
.lab-cell{
  display:flex; flex-direction:column; gap:.9rem;
  border:1px solid var(--white-28); padding:clamp(1.4rem,3vw,2rem);
  transition:border-color .2s;
}
.lab-cell:hover{ border-color:var(--white); }
.lab-no{ color:var(--white-84); }
.lab-name{ font-family:var(--f-cn); font-weight:300; font-size:1.4rem; line-height:1.25; }
.lab-desc{ font-size:.95rem; color:var(--white-84); }
.lab-tags{ display:flex; flex-wrap:wrap; gap:.4rem; margin-top:.2rem; }
/* 「边框 pill 直角」——规格原话。pill 是形状的名字，直角是明确要求（§9 第 2 条） */
.lab-tag{ border:1px solid var(--white-28); padding:.18rem .5rem; color:var(--white-84); }
.lab-go{ margin-top:auto; color:var(--white-84); }
.lab-cell:hover .lab-go{ color:var(--white); text-decoration:underline; text-underline-offset:.3em; }

/* ── 关于页 §6.5 ───────────────────────────────────────────────────── */
.about-top{ position:relative; padding-block:clamp(2.5rem,7vh,5rem); }
/* 马骨 P-02：右缘出血、opacity .5、在文字下层（构图三律） */
.about-art{
  position:absolute; top:0; right:calc(var(--gut) * -1);
  width:min(42vw,34rem); opacity:.5; z-index:0; pointer-events:none;
}
.about-body{ position:relative; z-index:1; max-width:65ch; }
.about-body .mono{ display:block; color:var(--white-84); margin-bottom:1.5rem; }

/* 通栏地图横条：高约 30vh，opacity .28，上下发丝线夹住 */
.about-band{ margin-block:clamp(2rem,5vh,3.5rem); }
.about-band img{
  width:100%; height:30vh; object-fit:cover; object-position:center 40%;
  opacity:.28; margin-block:1.5rem;
}

.about-projects,.about-links{ padding-block:clamp(2rem,5vh,3.5rem); }

/* 紫底上的正文（关于页和友链用）——文章页那套是纸面色，搬过来在紫底上看不见 */
.prose--purple{ max-width:65ch; }
.prose--purple p,.prose--purple li,.prose--purple td{ color:var(--white); }
.prose--purple h2,.prose--purple h3{ color:var(--white); }
.prose--purple h2::before{ color:var(--white-84); }
.prose--purple a{
  color:var(--white); text-decoration:underline;
  text-decoration-color:var(--white-28); text-underline-offset:.22em;
  transition:text-decoration-color .15s;
}
.prose--purple a:hover{ text-decoration-color:var(--white); }
.prose--purple :not(pre) > code{ color:var(--white); border-color:var(--white-28); }
.prose--purple pre{ background:none; border-color:var(--white-28); }
.prose--purple pre code{ color:var(--white-84); }
.prose--purple blockquote{ border-left-color:var(--white); color:var(--white-65); }
.prose--purple th{ color:var(--white-84); }
.prose--purple th,.prose--purple td{ border-bottom-color:var(--white-28); }
.prose--purple hr{ background:var(--white-28); }
.prose--purple li::marker{ color:var(--white-84); }
/* 紫底上的 COPY 按钮 */
.prose--purple .copy{ color:var(--white-84); background:var(--purple); border-color:var(--white-28); }

@media (max-width:767px){
  .about-art{ display:none; }
  .note-row{ flex-direction:column; gap:.6rem; }
  .note-date{ width:auto; }
}

/* ── 纸面页上的页脚：**翻回紫底** ──────────────────────────────────────
 *
 * 这是一处**规格没写、我做的判断**，理由要说清楚。
 *
 * 发现过程：文章页截图看起来页脚是空的。查计算样式才知道
 * `.foot` 没有自己的背景（透明 → 继承纸面），而里面的字是 --white-84——
 * **白字印在 #F4F2F8 上，等于看不见。** 测试全绿，因为它们只查
 * 「字符串在不在 HTML 里」，而字确实在，只是看不见。
 *
 * 为什么选「页脚翻回紫」而不是「给页脚一套纸面配色」：
 *
 *   页脚里有两张**白线透明 PNG**（眼球印章、足迹）。它们在纸面上一样消失，
 *   而规格 §3.2 明文禁止「在 PS 里把线稿染成紫色再导出——颜色永远由 CSS 给，
 *   同一张图才能横跨紫底和纸面使用」。用 CSS 给白线图上色只能靠 filter 反相，
 *   而反相之后白线变黑线，那是**另一种处理**，不是同一张图换个地面。
 *
 *   翻回紫底不需要动任何图，而且读起来是「正文结束，回到档案馆的地面」。
 *
 * 代价：规格 §1 说「紫→纸的反转是大节奏，一页最多翻一次」，
 * 这样文章页就是紫框 → 纸面 → 紫页脚。**我认为页脚是页面之外的一条带，
 * 不算正文地面的第二次翻转**——但这是我的判断，规格 §6.1 里没有提页脚。
 * 你要是觉得该严格按「只翻一次」，那就得给页脚做纸面配色 + 给两张图做反相处理。
 * ------------------------------------------------------------------------ */
body.paper .foot{
  background:var(--purple);
  color:var(--white);
  /* 原来这里有 margin-top:clamp(3rem,8vh,6rem)，**改成揭示式之后必须去掉**：
     margin 在盖板元素的背景之外，于是那一段变成一条透光的缝，
     纸面还没结束就先看见底下的紫页脚了。
     间距改由盖板自己的 padding-bottom 出（见下一条），
     这样它落在盖板的背景**里面**。
     紫带的上边界仍然是硬边，不做渐变（规格 §9 第 1 条：任何渐变都禁止） */
}
/* 原来这里给 .wrap 加过 padding-bottom 撑间距。**改成巨字之后必须去掉**：
   那个字要紧贴纸面的下边缘才能被裁出「切断」的效果，中间垫一段就成了
   一个完整的字浮在面里。间距现在由 .edge-mark 自己的 margin-top 出 */
