/* ============================================================
   Hallmark · macrostructure: Catalogue · genre: editorial
   theme: custom (newspaper) · anchor hue: 30 (印刷朱红) · paper: oklch(97.6% 0.008 78)
   nav: 刊头 masthead (N6 变体：站名左置 + 页脚版本志) · footer: Ft4 dense colophon
   fingerprint: 刊头 → 筛选与索引横滑带 → 编号规格网格（No.001）→ 版本志
   · contrast: pass (40–41) · slop: pass (42–45) · nav/footer: pass (42–43)
   · honest: pass (46) · chrome: pass (47) · tokens: pass (48) · responsive: pass (49)
   · mobile: pass (34, 49, 50–57) · icons: pass (30)
   Hallmark · pre-emit critique: P5 H5 E5 S5 R5 V5
   tokens.css 是本文件的唯一取值来源：这里不出现任何内联颜色或字体族。
   主题通过 <html data-theme="light|dark"> 切换（auto 由 app.js 解析后写入）
   ============================================================ */

/* ---------- 基础 ---------- */
*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

/* 闸门 34：两边都用 clip。hidden 会新建滚动容器，吃掉 position: sticky。 */
html,
body { overflow-x: clip; }

html { scroll-padding-top: var(--space-2xl); }

body {
  margin: 0;
  background: var(--color-paper);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

body.locked { overflow: hidden; }

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 600;
  font-style: normal; /* 闸门 38a：标题一律正体，强调用字重/颜色 */
  line-height: var(--lh-title);
  overflow-wrap: anywhere;
  min-width: 0;
}

button,
input,
select,
textarea { font: inherit; color: inherit; }

button { cursor: pointer; border: none; background: none; }

/* 弹层都设了 display，会盖掉浏览器默认的 [hidden]{display:none} */
[hidden] { display: none !important; }

a { color: var(--color-accent-ink); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 0.18em; }

/**
 * 全局键盘焦点环。:focus-visible 只在键盘导航时出现。
 * 只写 outline / outline-offset，不进 transition —— 焦点环必须瞬时出现（闸门 15）。
 */
html :focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

.muted { color: var(--color-ink-3); }
.small { font-size: var(--text-sm); }

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

kbd {
  font-family: var(--font-body);
  background: var(--color-paper-2);
  border: 1px solid var(--color-rule);
  border-radius: 0;
  padding: 0 var(--space-2xs);
  font-size: var(--text-2xs);
}

/* 数字一律等宽：计数、序号、尺寸在变化时行宽不抖 */
.cnt,
.sel-count,
.plate__no,
.plate__ext,
.lb-meta,
.job-nums,
.stats-box,
.dateline { font-variant-numeric: tabular-nums; }

/* ---------- 版面切换 ----------
   单页应用用 hidden 切版面；切版面只动 hidden，不做过渡动画：
   版面级动画在「读内容」的站点上是噪音。 */
.view[hidden] { display: none !important; }

/* 版面导航已改用 React Bits 的 Dock（见下面「刊头导航（Dock）」一节）。
   原来那套 .views / .views__link 文字导航的规则已删除，避免死规则堆积。
   注意：Dock 的项不带 data-view-link（app.js 原来靠它同步 aria-current，
   现在 React 用 is-current 类名自己处理高亮，那段原生代码会自然跳过）。
   但 .toolbar / .act 必须保留 —— 图库版面的工具行仍是原生实现。 */

/* ---------- 首页：自我介绍（居中） ----------
   站主要求这里不放标题、也不放填充效果，只留这段自我介绍文字与下面的入口。 */
.intro {
  padding: var(--space-2xl) var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--color-rule);
  text-align: center;
}
.intro__lede {
  margin: var(--space-sm) auto 0;
  max-width: 40ch;
  font-size: var(--text-md);
  line-height: 1.7;
  color: var(--color-ink-2);
  overflow-wrap: anywhere;
}
.intro__meta { margin: var(--space-md) 0 0; font-size: var(--text-sm); }
/* .intro__actions 已删除：站主要求去掉首页那个「进入图库」入口，元素没了，规则也不留。 */

.section {
  padding: var(--space-lg) 0;
  border-bottom: 1px solid var(--color-rule);
}
.section:last-of-type { border-bottom: none; }
.section__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  margin-bottom: var(--space-md);
}
.section__head h2 { font-size: var(--text-lg); }
.section__head p { margin: 0; font-size: var(--text-sm); color: var(--color-ink-3); }
/* .prose 是正文的包裹块。它自带 max-width: var(--measure)（62ch）——比文章版心窄，
   而块本身是左对齐的，于是出现「标题居中、正文偏左」（站主反馈的现象）。
   正确做法：版心**居中、宽度交给版心决定**；行宽由外层 .post__body 的
   56/58rem 与字号共同决定（CJK 下约 54 字一行，仍在舒适区间）。
   保留 .prose 的顶部间距规则，不再限制它的宽度。 */
.prose {
  max-width: none;
  margin-inline: auto;
}
.prose p + p { margin-top: var(--space-sm); }

/* ---------- 逐字手写标题 ----------
   样式与动画由 React 组件自带（app/components/StrokeText.css + GSAP）。
   这里刻意**不再定义 .stroke-text / .stroke-text__stroke / __fill 任何规则**：
   两套同名规则会互相打架（原生版靠 CSS @keyframes，React 版靠 GSAP 直设
   strokeDashoffset，谁后加载谁生效）。

   只覆盖 SVG 的尺寸策略，理由见 App.jsx 的注释：
   组件自带 `width: 100%` 会让 SVG 按容器宽度放大，fontSize 就失去意义。
   改成「高度决定尺寸」后，fontSize 就是实际渲染字号（≈1:1）。 */
.stroke-text__svg {
  width: auto;
  max-width: 100%;
}

/* 关于页的联系方式 */
.contact { display: flex; flex-wrap: wrap; gap: var(--space-md); font-size: var(--text-base); }
.pl { background: var(--color-accent-soft); color: var(--color-accent-ink); padding: 0 var(--space-2xs); }

/* ---------- 光标拖尾（React Bits 的 GlowCursor，WebGL 着色器版） ----------
   ⚠️ 这个组件铺成 fixed 全屏浮层时，**容器和 canvas 都必须 pointer-events: none**，
   否则它就像一张透明玻璃盖住整页：按钮和链接全都点不到，只剩滚轮能用。
   （我先只给容器设了 none、把 canvas 设成 auto 当"事件接收面"，结果照样点不动——
   因为 canvas 在容器内部，父级穿透不等于子级穿透。）

   既然两者都穿透，组件就收不到指针事件了。所以本站对它做了唯一一处改动：
   把指针监听从「容器」移到「window」（见 GlowCursor.jsx 文件头）。
   坐标换算不变，仍以容器的 bounding rect 为参照。

   定位（fixed + inset:0）由 app/App.jsx 通过官方 style prop 传入。 */
.glow-cursor {
  z-index: var(--z-cursor);
  pointer-events: none;
}
.glow-cursor__canvas {
  pointer-events: none;
  mix-blend-mode: var(--glow-cursor-blend-mode, screen);
  opacity: var(--glow-cursor-opacity, 1);
}

@media (max-width: 40rem) {
  .views { gap: var(--space-md); }
  .intro { padding: var(--space-lg) 0 var(--space-md); }
  .intro__lede { font-size: var(--text-md); }
}

/* ---------- 文章正文 ---------- */
/* 文章正文整体居中，并且**随屏幕宽度缩放**。
   站主反馈：内容不够居中、要能适应屏幕大小。
   原因有两个，一起修掉：
   ① 宽度写死 46rem(736px)：在 1440 屏上正文只占一半不到，两侧各空约 400px；
   ② 正文在 .page 内居中，而 .page 自带 padding-inline —— 左侧视觉留白 =
      页面内边距 + 居中留白，右侧只有一半，所以看起来偏左。
   改法：
   · 宽度用 min(92vw, 56rem)：小屏几乎占满，大屏封顶到 56rem（再宽一行字数过多、
     阅读会累），中间连续过渡，不再有"跳档"；
   · 在**宽屏**下把 .page 的横向内边距让给正文（下面那条媒体查询），
     这样留白是左右对称的，真正居中。
   代码块例外：保留 text-align: left —— 居中的代码没法读，缩进关系会全丢。 */
.post__body {
  width: 100%;
  max-width: min(92vw, 56rem);
  margin-inline: auto;
  padding: var(--space-lg) 0 var(--space-2xl);
  text-align: center;
}
/* 代码块例外：等宽代码必须左对齐，且仍占满版心宽度（hug 内容会显得零碎） */
.post__body pre {
  text-align: left;
  margin-inline: auto;
}
.post__head { padding-bottom: var(--space-md); border-bottom: 1px solid var(--color-rule); }
.post__kicker { margin: 0 0 var(--space-sm); font-size: var(--text-sm); }
.post__title {
  margin: 0;
  font-size: var(--text-xl);
  line-height: 1.28;
  font-style: normal;
  overflow-wrap: anywhere;
}
.post__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs);
  margin: var(--space-sm) 0 0;
  font-size: var(--text-xs);
  color: var(--color-ink-3);
}
.post__body p { margin: var(--space-md) 0 0; line-height: 1.8; }
.post__lede {
  margin-top: var(--space-lg) !important;
  font-size: var(--text-md);
  color: var(--color-ink-2);
}
.post__body h2 {
  margin: var(--space-2xl) 0 0;
  padding-top: var(--space-sm);
  font-size: var(--text-lg);
  border-top: 1px solid var(--color-rule);
}
.post__list { margin: var(--space-md) 0 0; padding-left: var(--space-lg); }
.post__list li { margin-top: var(--space-2xs); line-height: 1.8; }

/* 正文里的列表与代码块。posts.js 用的是普通 ul / pre，
   不是 .post__list（那个类名是原生版留下的），所以两组选择器都写上。 */
.post__body ul {
  margin: var(--space-md) 0 0;
  padding-left: var(--space-lg);
  /* 不再限制宽度：这一块属于正文版心，宽度由 .post__body 决定 */
  max-width: none;
  text-align: left; /* 列表项左对齐：居中会把项目符号与文字拉散，读不出层级 */
  width: fit-content;
  margin-inline: auto;
}
.post__body ul li { margin-top: var(--space-2xs); line-height: 1.8; }

/* 行内 code：只给底色与细边，不要大块背景 */
.post__body :not(pre) > code {
  padding: 0 var(--space-3xs);
  background: var(--color-paper-2);
  border: 1px solid var(--color-rule);
  font-family: var(--font-body);
  font-size: 0.9em;
  overflow-wrap: anywhere;
}
/* 代码块：等宽、可横向滚动（不能撑破版心）
   宽度交给 .post__body（原来的 max-width: var(--measure) 会让它比版心窄、看着偏左）。 */
.post__body pre {
  margin: var(--space-md) auto 0;
  padding: var(--space-sm) var(--space-md);
  background: var(--color-paper-2);
  border: 1px solid var(--color-rule);
  border-left: 3px solid var(--color-rule-strong);
  overflow-x: auto;
  max-width: none;
  width: 100%;
}
.post__body pre code {
  padding: 0;
  background: none;
  border: none;
  font-size: var(--text-sm);
  line-height: 1.7;
  white-space: pre;
}
/* 引述：用左侧粗线区分，不加引号装饰（站内不做装饰性引号）。
   同样去掉 --measure 限制，改由版心决定宽度。 */
.post__body blockquote {
  margin: var(--space-md) auto 0;
  padding: var(--space-2xs) 0 var(--space-2xs) var(--space-md);
  border-left: 3px solid var(--color-rule-strong);
  color: var(--color-ink-2);
  max-width: none;
  text-align: left; /* 引述左对齐：左粗线是它的语义标记，居中会削弱这个标记 */
}

.post__end { margin-top: var(--space-2xl) !important; padding-top: var(--space-md); border-top: 1px solid var(--color-rule); }
.post__missing { padding: var(--space-2xl) 0; }

/* ---------- 文章卡片（首页） ----------
   悬浮质感 + 可换背景图：
   · 背景图通过 --card-img 传入（HTML 里写 style="--card-img: url(...)"）；
     没有图时退回站点自带的纸纹渐变，不会出现一块空白。
   · --card-shade 是压在背景图上的纸色蒙层，保证任何图上文字都读得清。
   · 悬浮感来自「细边 + 抬起」，而不是彩色光晕（深色主题下光晕会变成脏影）。 */
.posts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  gap: var(--space-md);
  align-items: stretch; /* 让每个格子的倾斜容器等高 */
}

/* 倾斜容器（React Bits TiltedCard）：填满格子，最小高度沿用原来的 15rem。
   下面两条覆盖是必需的（组件自带样式不够用）：
   · .tilted-card-img 官方给的是固定 300px；本站不用图片，铺满即可。
   · .tilted-card-overlay 官方只有 top/left、**没有尺寸**，它会缩到内容大小；
     于是里面的 .pcard（inset: 0）也跟着缩成一小块，再被 overflow: hidden 裁掉文字，
     表现就是「一个小卡片 + 只剩日期」。必须补 inset: 0 让它铺满旋转层。 */
/* 显式高度，与 App.jsx 里的 CARD_HEIGHT 保持一致（官方示例也是显式尺寸）。
   不要用百分比或只写 min-height：组件自带 .tilted-card-figure 是 height: 100%，
   而网格项的高度常被算作 auto，百分比解析不出来时整条高度链
   （figure → inner → overlay → .pcard）会塌掉，表现为卡片空白或被裁。 */
.posts .tilted-card-figure { height: 17rem; }
.posts .tilted-card-inner,
.posts .tilted-card-img,
.posts .tilted-card-overlay {
  width: 100%;
  height: 100%;
  inset: 0;
  /* 组件给图片的是 15px 圆角，而站内是编辑风格的直角（没有 --radius-* 令牌，
     只有圆形头像用 50%）。这里归零，避免照片出现圆角遮罩、与卡片边框不一致。 */
  border-radius: 0;
}
/* 干掉 overlay 的 translateZ(30px)。
   组件用它让文字"浮"在图片上方 30px；但父级 figure 有 perspective: 800px，
   于是 overlay 会被放大 800/(800−30) ≈ 1.039 倍 —— 实测卡片 669px、照片 644px，
   比值正是 1.039。多出来的约 11px/边由蒙层填充，在纸面上形成一圈色差带，
   看上去就像卡片被描了边（站主反馈的「边框」）。三者对齐到同一平面即可消除。 */
.posts .tilted-card-overlay {
  transform: none;
}

.pcard {
  /* 卡片现在住在 .tilted-card-overlay 里，那一层是绝对定位 + 300px 兜底，
     所以卡片必须自己铺满，否则内层塌成 0 高、内容不可见。 */
  position: absolute;
  inset: 0;
  display: block;
  padding: var(--space-lg);
  /* 不描边：照片自己就是卡片的轮廓，再描一圈线只会把图框住、显得廉价。
     卡片与纸面之间靠照片边缘的明暗差、以及纸色蒙层来区分。
     角保持直角：站内没有 --radius-* 令牌，编辑风格就是直角（只有圆形头像是 50%）。 */
  border: none;
  color: var(--color-ink);
  /* 用 hidden 而不是 auto：卡片整块都在 3D 变换里（overlay 有 translateZ），
     而 3D 变换 + overflow: auto 会建出滚动容器与合成层，某些浏览器下该层的
     内容会整个不绘制 —— 症状正是「DOM 里有文字、量得出尺寸，但屏幕上一片空白」。
     卡片内容本来就不该滚动，用 hidden 更稳。 */
  overflow: hidden;
  background-color: transparent;
  background-image: var(--card-img, none);
  background-size: cover;
  background-position: center;
  transition: box-shadow var(--dur-short) var(--ease-out);
}
/* 无配图时的兜底：纸色底。
   ⚠️ 有配图的卡片会被加上 .pcard--photo，把底色与蒙层都去掉 —— 卡片是
   position: absolute; inset: 0，整块压在照片图层（<img>）之上，
   只要它自己画了底色，照片就一定被盖住（这正是站主反复看到"图看不到"的原因）。
   刻意**不用** :not([style*='--card-img']) 那种写法：它检查元素内联 style 里有没有
   --card-img 字样，而 React 渲染的卡片没有内联 style，于是 :not() 永远成立。 */
.pcard { background-color: var(--color-paper-2); }
.pcard--photo { background-color: transparent; }

/* 蒙层：从左到右渐弱，给文字留一块"纸"，右侧把照片让出来。
   参数是按**实测几何**定的，不是估的（诊断数据）：
     卡片宽 669px，文字体 .pcard__body 在卡片内 x 26→372（约 4%~55%），
     照片在卡片内 x 61→705（约 2%~98%）—— 照片几乎横跨整张卡片。
   所以关键在 55%~62% 这段必须快速淡出，否则照片主体（城堡在图片中部）
   正好落在文字区、被纸色糊掉，只剩下最右侧一条天空可见（这就是"图完全没有"的原因）。
   竖向那条纸色 100% 只在 0~46%（文字的 46% 以内）真正不透明，文字对比度有保证。 */
.pcard::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to right,
    var(--color-paper) 0%,
    color-mix(in oklab, var(--color-paper) 97%, transparent) 40%,
    color-mix(in oklab, var(--color-paper) 80%, transparent) 50%,
    color-mix(in oklab, var(--color-paper) 22%, transparent) 58%,
    color-mix(in oklab, var(--color-paper) 4%, transparent) 100%
  );
  opacity: var(--card-shade, 1);
  transition: opacity var(--dur-short) var(--ease-out);
}

/* 有配图时，上面那条均匀铺开的"纸"会整块盖住照片，所以换成一块**贴在文字侧**的
   软遮罩：左侧足够不透明以保文字对比度，到 58% 处完全消失，右侧照片原样露出。
   用它而不是直接去掉蒙层 —— 少了它，正文压在照片的亮部上会读不清。 */
.pcard--photo::before {
  background: linear-gradient(
    to right,
    var(--color-paper) 0%,
    color-mix(in oklab, var(--color-paper) 96%, transparent) 34%,
    color-mix(in oklab, var(--color-paper) 84%, transparent) 46%,
    transparent 62%
  );
  /* 只是平滑收尾，不再铺到整张卡片 */
  opacity: var(--card-shade, 1);
  background-repeat: no-repeat;
}
.pcard > * { position: relative; }

.pcard:hover,
.pcard:focus-visible {
  text-decoration: none;
  /* 悬停反馈只剩阴影：边框已去掉，原来的 translateY 位移也已删除
     （位移会与 TiltedCard 的 3D 旋转打架）。 */
  box-shadow: var(--shadow-overlay);
}
.pcard:hover::before { opacity: 0.7; }

/* 文字只占左半边：配合上面那条「左深右浅」的渐变蒙层，
   既保证文字对比度，也不让文字压到照片主体上。 */
.pcard__body { display: flex; flex-direction: column; height: 100%; gap: var(--space-2xs); max-width: 46%; }
.pcard__date {
  font-size: var(--text-2xs);
  letter-spacing: 0.06em;
  color: var(--color-ink-3);
  font-variant-numeric: tabular-nums;
}
.pcard__title {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 600;
  line-height: 1.4;
  overflow-wrap: anywhere;
}
.pcard:hover .pcard__title { color: var(--color-accent-ink); }
.pcard__excerpt {
  margin-top: var(--space-2xs);
  font-size: var(--text-sm);
  line-height: 1.65;
  color: var(--color-ink-2);
  display: -webkit-box;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.pcard__meta { margin-top: auto; padding-top: var(--space-sm); }
.pcard__views {
  font-size: var(--text-2xs);
  color: var(--color-ink-3);
  font-variant-numeric: tabular-nums;
}

/* ---------- 刊头 ---------- */
.masthead {
  border-bottom: 3px double var(--color-rule-strong);
  background: var(--color-paper);
  /* 站名的逐字填充效果靠光效"画过"刊头才好看，所以刊头要高于光标层
     （--z-cursor 350 < --z-header 360 < 弹层 400）。 */
  position: relative;
  /* Dock 的尺寸令牌（--dock-panel-height / --dock-radius / --dock-radius-item /
     --dock-border-width）统一声明在 tokens.css 里，见那里的「刊头导航（Dock）」一节。 */
  z-index: var(--z-header);
}

/* ---------- 刊头导航（React Bits 的 Dock） ----------
   官方 Dock.css 是给深色 macOS 底栏写的：硬编码 #120F17 / #222 / #fff，
   以及 position: absolute + bottom: 0.5rem（会飘到页面底部）。
   这里全部覆盖为站内令牌，并改成"就地占位"：
   · 去掉 absolute / bottom / left / translateX，让它作为刊头的一格随流布局；
   · 配色走 --color-paper / --color-rule / --color-ink，深浅主题自动适配。
     刻意不加阴影：站内只有 --shadow-overlay 一个阴影令牌（18px 48px，弹层级），
     用在刊头这条小面板上太重，靠纸色底 + 细线区分即可。
   动效（悬停放大 + 文字标签）完全保留官方实现。 */
.masthead .dock-outer {
  /* 组件会把这一层的高度从 panelHeight 动画到 maxHeight（默认 68 → 256），
     在底栏里是"抽屉升起"，在刊头里就是凭空长出一个大框 —— 那正是站主看到的
     "外框变大、像破掉"。刊头在页面顶部，不该有高度动画，固定住。 */
  height: var(--dock-panel-height) !important;
  margin: 0;
  align-items: center;
  overflow: visible;
  scrollbar-width: none;
}
.masthead .dock-outer::-webkit-scrollbar { display: none; }

.masthead .dock-panel {
  position: static;
  transform: none;
  bottom: auto;
  left: auto;
  align-items: center;
  gap: var(--space-xs);
  width: fit-content;
  /* 面板高度必须**大于**图标的最大放大尺寸（App.jsx 传的 magnification），
     否则图标会从面板上下溢出，视觉上就是"撑破了框"。
     这里：panel-height 3.75rem(60px)，而最小项的放大上限≈56px，留 4px 余量。 */
  height: var(--dock-panel-height) !important;
  padding: 0 var(--space-xs);
  border: var(--dock-border-width) solid var(--color-rule);
  border-radius: var(--dock-radius);
  background-color: var(--color-paper-2);
  overflow: visible; /* 放大的图标可以微微出格，但不该被裁掉 */
}

.masthead .dock-item {
  border: var(--dock-border-width) solid transparent;
  border-radius: var(--dock-radius-item);
  background-color: transparent;
  color: var(--color-ink-2);
  box-shadow: none;
  /* 注意：transition 里**不能**包含 color —— 那会让键盘焦点环延迟出现。
     站内规则是焦点环必须瞬时可见（只写 outline / outline-offset，不进 transition）。 */
  transition: background-color var(--dur-micro) var(--ease-out),
    border-color var(--dur-micro) var(--ease-out);
}
.masthead .dock-item:hover {
  background-color: var(--color-paper);
  color: var(--color-ink);
}
/* 当前版面：与旧的文字导航 aria-current 保持同一套视觉语言
   （类名由 App.jsx 的 dockItems 传入，见那里的 className: 'is-current'） */
.masthead .dock-item.is-current {
  background-color: var(--color-accent-soft);
  color: var(--color-accent-ink);
  border-color: color-mix(in oklab, var(--color-accent) 35%, transparent);
}
/* 官方 Dock.css 给 .dock-item 写了 outline: none，会压掉全局键盘焦点环。
   这里补回来（站内规则：焦点环必须瞬时出现，不进 transition）。 */
.masthead .dock-item:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

.masthead .dock-icon {
  color: inherit;
  /* 项放大时给图标一点额外增幅，让"变大"更明显。
     上限 1.1 是为了避免把 SVG 非等比拉伸 —— 以正方形视框渲染时等比，
     留一点余量更稳。 */
  transform: scale(var(--dock-icon-scale, 1));
  transition: transform var(--dur-short) var(--ease-out);
}
.masthead .dock-item:hover .dock-icon,
.masthead .dock-item:focus-visible .dock-icon { --dock-icon-scale: 1.1; }

.masthead .dock-label {
  /* 官方定位是 top: -1.5rem（在图标**上方** 1.5rem），因为 macOS 底栏在屏幕底部、
     上方有空间。但本站的 Dock 在**刊头**里 —— 向上浮出会直接跑到页面顶边之外、
     标签被截断（站主反馈"提示词显示到页面外面了"）。
     所以改为锚到图标**下方**：top: 100% 把标签顶边放到图标底边，再加上 0.5rem 间距。
     配合组件的 labelDirection="below"（它负责那 10px 的入场位移方向）。 */
  top: calc(100% + 0.5rem);
  border: var(--dock-border-width) solid var(--color-rule);
  border-radius: var(--dock-radius-item);
  background-color: var(--color-ink);
  color: var(--color-paper);
  padding: 0.125rem var(--space-2xs);
  font-size: var(--text-2xs);
  white-space: nowrap;
  z-index: 1;
  pointer-events: none;
}

@media (max-width: 40rem) {
  /* 窄屏：放大动效会溢出到工具行上，关掉标签、缩小间距 */
  .masthead .dock-label { display: none; }
  .masthead .dock-panel { gap: var(--space-3xs); }
}

.masthead__top {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--space-lg) var(--page-pad) var(--space-sm);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-md);
}

.masthead__brand { display: flex; flex-direction: column; gap: var(--space-3xs); min-width: 0; }

.wordmark__link { display: inline-block; color: inherit; }
.wordmark__link:hover { text-decoration: none; color: var(--color-accent-ink); }

/* 刊头站名：字形由 StrokeText 画进 SVG，所以这里的 font-size 只影响 h1 的行高盒，
   不决定字的实际大小（那是组件的 fontSize prop，见 App.jsx）。
   给一个略大于 SVG 高度的行高，避免 h1 的盒子切到 SVG 的上下缘。 */
.wordmark__name {
  display: block;
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 600;
  font-style: normal;
  line-height: var(--lh-title);
  letter-spacing: -0.015em;
  overflow-wrap: anywhere;
  min-width: 0;
}

.masthead__tagline {
  margin: 0;
  font-size: var(--text-2xs);
  letter-spacing: 0.1em;
  color: var(--color-ink-3);
}

/* 工具行：文字 + 线性图标，单项 hover 只改一处信号 */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs);
}

.act {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-height: 2.25rem;
  padding: var(--space-2xs) var(--space-xs);
  border: 1px solid transparent;
  color: var(--color-ink-2);
  font-size: var(--text-sm);
  line-height: 1;
  white-space: nowrap;
  transition: background-color var(--dur-micro) var(--ease-out),
    border-color var(--dur-micro) var(--ease-out),
    color var(--dur-micro) var(--ease-out);
}
.act__icon { display: inline-flex; align-items: center; line-height: 1; }
.act__label { display: inline; }
.act:hover { background: var(--color-paper-2); color: var(--color-ink); }
.act:active { background: var(--color-paper-3); }
/* 单选式开关：既在 aria-pressed 上，也在视觉上 */
.act[aria-pressed='true'] {
  border-color: var(--color-accent-ink);
  color: var(--color-accent-ink);
}
/* 图标专用按钮（弹层关闭等）：方形热区，热区就是视觉尺寸 */
.act--icon { width: var(--hit); height: var(--hit); justify-content: center; padding: 0; }
.act--icon .act__label { display: none; }

/* ---------- 版面骨架 ----------
   单列：刊头 → 筛选与索引（整宽横滑标签带）→ 目录区 → 版本志。
   没有侧栏，宽屏与窄屏是同一套结构，主区永远整宽。 */
.page {
  max-width: var(--page-max);
  margin: 0 auto;
  padding-inline: var(--page-pad);
}

/* ---------- 正文区背景图 ----------
   站主要求：除顶栏与页脚外，其余部分铺满背景图。
   ⚠️ 这里的选择器只认**正文区里的 .page**（#root > .page 或 #view-gallery > .page），
   而不是所有 .page —— .page 是通用内容容器，刊头与页脚里也各有一层，
   写成 `.page` 会把图铺到那两个地方去（站主反馈的"页脚文字区域透明"就是这么来的）。
   用结构选择器排除，比"给通用规则再逐处覆盖"更不容易漏。

   · background-attachment: scroll, fixed —— 承托层随元素滚、照片钉在视口上，
     内容再长也只铺一屏、不出现重复接缝，滚动时还有一点视差。
   · 承托层（第一层渐变）不是装饰，是可读性必需：照片最亮处灰度约 161，
     墨色正文压上去只有 4.14:1，低于 4.5 门槛。数值推导见 tokens.css 的
     --bg-scrim-* 与 .shots/contrast.mjs 的背景承托检查。
     左浓右淡：文字在左侧（介绍区居中、卡片文字在左），右侧把照片留出来。 */
#root > .page,
#view-gallery > .page {
  background-color: var(--color-paper);
  background-image:
    linear-gradient(
      to right,
      /* 承托值走令牌，不在 CSS 里重复写数字 —— .shots/contrast.mjs 的
         背景承托检查读的也是同一处分号，两边不会各说各话。 */
      color-mix(in oklab, var(--color-paper) calc(var(--bg-scrim-strong) * 100%), transparent) 0%,
      color-mix(in oklab, var(--color-paper) calc(var(--bg-scrim-soft) * 100%), transparent) 62%,
      color-mix(in oklab, var(--color-paper) calc(var(--bg-scrim-min) * 100%), transparent) 100%
    ),
    var(--bg-image, none);
  background-size: auto, cover;
  background-position: center, center;
  background-repeat: no-repeat, no-repeat;
  background-attachment: scroll, fixed;
}

.filters {
  display: flex;
  flex-direction: column;
  gap: 0;
  border-bottom: 1px solid var(--color-rule);
}

/* 第一行：收藏 / 待审核 / 排序 / 清空。宽屏左对齐，与标签带同一根铅线 */
.filters__views {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs);
  margin: 0;
  padding: var(--space-sm) 0;
  list-style: none;
}
.filters__spacer { flex: 1 1 auto; }
.filters__label { font-size: var(--text-xs); color: var(--color-ink-3); margin-right: var(--space-2xs); }

/* 筛选片：直角、细边、tabular 计数 */
.filt {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-height: 2rem;
  padding: var(--space-2xs) var(--space-sm);
  border: 1px solid var(--color-rule);
  background: var(--color-paper);
  color: var(--color-ink-2);
  font-size: var(--text-sm);
  line-height: 1;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  transition: color var(--dur-micro) var(--ease-out),
    border-color var(--dur-micro) var(--ease-out),
    background-color var(--dur-micro) var(--ease-out);
}
.filt:hover { color: var(--color-accent-ink); border-color: var(--color-rule-strong); }
.filt:active { background: var(--color-paper-2); }
.filt.is-on { color: var(--color-ink); border-color: var(--color-ink); font-weight: 600; }
.filt .cnt { color: var(--color-ink-3); font-weight: 400; }
.filt.is-on .cnt { color: var(--color-accent-ink); }

/* 第二行：标签索引，整宽横滑。宽屏也不折成侧栏。 */
.filters__tags {
  display: flex;
  gap: var(--space-md);
  margin: 0;
  padding: 0 0 var(--space-sm);
  list-style: none;
  overflow-x: auto;
  scrollbar-width: none;
  /* 右端渐隐，提示「还能往右滑」 */
  mask-image: linear-gradient(to right, var(--color-ink) calc(100% - 2.5rem), transparent 100%);
}
.filters__tags::-webkit-scrollbar { display: none; }

.idx__item {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2xs);
  padding: var(--space-2xs) 0;
  border-bottom: 1px solid var(--color-rule);
  color: var(--color-ink-2);
  font-size: var(--text-sm);
  line-height: 1.4;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  transition: color var(--dur-micro) var(--ease-out),
    border-color var(--dur-micro) var(--ease-out);
}
.idx__item:hover { color: var(--color-accent-ink); border-color: var(--color-rule-strong); }
.idx__item:active { color: var(--color-ink); }
.idx__item.is-on { color: var(--color-ink); font-weight: 600; border-color: var(--color-ink); }
.idx__item .cnt { color: var(--color-ink-3); font-weight: 400; }
.idx__item.is-on .cnt { color: var(--color-accent-ink); }

.filters__note {
  margin: var(--space-xs) 0 0;
  font-size: var(--text-2xs);
  line-height: 1.6;
  color: var(--color-ink-3);
}

/* ---------- 目录区 ---------- */
.catalogue {
  min-width: 0;
  padding-bottom: var(--space-2xl);
}

.query {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-sm) 0;
  background: var(--color-paper);
  border-bottom: 1px solid var(--color-rule);
}
.query__mark { display: inline-flex; color: var(--color-ink-3); }

.query input {
  flex: 1;
  min-width: 0;
  height: var(--hit);
  padding: 0;
  border: none;
  background: none;
  font-size: var(--text-md);
}
/* 焦点环由全局 html :focus-visible 画在输入框本身；这条只补底色提示 */
.query input:focus { background: var(--color-paper-2); }
.query:focus-within { border-bottom-color: var(--color-focus); }
.query input:focus-visible { outline-offset: 3px; }
.query input::-webkit-search-cancel-button { display: none; }

.query__clear {
  width: 1.75rem;
  height: 1.75rem;
  display: grid;
  place-items: center;
  color: var(--color-ink-3);
  font-size: var(--text-sm);
}
.query__clear:hover { color: var(--color-accent-ink); }
.query__clear:active { color: var(--color-ink); }

/* 期号行：张数 · 排序。真实计数，不是装饰 */
.dateline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs);
  margin: 0;
  padding: var(--space-xs) 0 0;
  font-size: var(--text-sm);
  color: var(--color-ink-2);
}
.dateline__sep { color: var(--color-rule-strong); }
.dateline__sort { color: var(--color-ink-3); }

/* ---------- 规格网格 ----------
   统一规格 + 细线：1px gap 配 1px 外边线就是排版里的「铅线」。
   每格外形由图片自己的宽高比决定（--ar，app.js 读出后夹取），
   所以竖图和横图占比一致，网格不会因裁剪而丢信息。 */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--plate-min)), 1fr));
  gap: var(--plate-gap);
  border: 1px solid var(--color-rule);
  margin-top: var(--space-lg);
  background: var(--color-rule);
}
/* 网格背景色透到格子缝里，等于给每个格子画了 1px 铅线 */
.grid > * { background: var(--color-paper); }

.plate {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin: -1px 0 0 -1px;
  border: 1px solid var(--color-rule);
  background: var(--color-paper);
}

.plate__thumb {
  display: block;
  width: 100%;
  aspect-ratio: var(--ar, 4 / 3);
  padding: 0;
  border: none;
  background: var(--color-paper-2);
  overflow: hidden;
  cursor: zoom-in;
}
.plate__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity var(--dur-short) var(--ease-out);
}
.plate__thumb img.loaded { opacity: 1; }
/* 图片坏掉时不要留一块静默的空白 */
.plate__thumb.broken::after {
  content: '图片无法加载';
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: var(--text-xs);
  color: var(--color-ink-3);
}

.plate__cap {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  padding: var(--space-xs) var(--space-sm) var(--space-sm);
  border-top: 1px solid var(--color-rule);
}

.plate__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs);
  min-width: 0;
}
.plate__no {
  font-size: var(--text-2xs);
  letter-spacing: 0.08em;
  color: var(--color-ink-3);
}
.plate__ext {
  font-size: var(--text-2xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-ink-3);
}

.plate__title {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--color-ink-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* 闸门 51：长 URL / 无空格长串在容器内换行，不撑破布局 */
  overflow-wrap: anywhere;
  min-width: 0;
}

.plate__tags { display: flex; flex-wrap: wrap; gap: var(--space-2xs) var(--space-sm); }
.tag {
  padding: 0;
  font-size: var(--text-2xs);
  color: var(--color-ink-3);
  border-bottom: 1px solid var(--color-rule);
  transition: color var(--dur-micro) var(--ease-out),
    border-color var(--dur-micro) var(--ease-out);
}
.tag:hover { color: var(--color-accent-ink); border-color: var(--color-accent-ink); }
.tag:active { color: var(--color-ink); }

/* 待审核：虚线是语义（还没过一遍），不是装饰 */
.plate--pending { border-color: var(--color-accent); border-style: dashed; }
.plate__flag {
  font-size: var(--text-2xs);
  color: var(--color-accent-ink);
}
.plate__fav {
  display: none;
  color: var(--color-accent-ink);
  line-height: 0;
}
/* 只在真的收藏时才占位：没有收藏就不该在说明行里留一个空槽 */
.plate:has([data-act='fav'][aria-pressed='true']) .plate__fav { display: inline-flex; }

/* 多选：整块可点，勾选态用外描边（不用阴影） */
.card .pick,
.plate .pick {
  position: absolute;
  top: var(--space-xs);
  left: var(--space-xs);
  z-index: var(--z-raised);
  width: 1.5rem;
  height: 1.5rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--color-on-media);
  background: var(--color-scrim);
  color: transparent;
  font-size: var(--text-xs);
  line-height: 1;
  padding: 0;
}
.plate .pick[aria-pressed='true'] {
  background: var(--color-accent);
  color: var(--color-on-accent);
  border-color: var(--color-accent);
}
.plate.picked { outline: 3px solid var(--color-accent); outline-offset: -3px; }
/* 24px 的勾选块：焦点环往里收，避免被格子裁掉 */
.plate .pick:focus-visible { outline: 2px solid var(--color-on-media); outline-offset: -3px; }
/* 缩略图是「打开」热区，键盘走到它时必须看得见落点 */
.plate__thumb:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }

/* 卡片操作：桌面悬停/聚焦显形，触屏常驻。四处动作，不用五处图标堆叠。 */
.plate__acts {
  position: absolute;
  top: var(--space-xs);
  right: var(--space-xs);
  display: flex;
  gap: var(--space-2xs);
  opacity: 0;
  transition: opacity var(--dur-short) var(--ease-out);
}
@media (hover: hover) {
  .plate:hover .plate__acts,
  .plate:focus-within .plate__acts { opacity: 1; }
}
@media (hover: none) {
  .plate__acts { opacity: 1; }
}
.plate__acts button {
  width: 2rem;
  height: 2rem;
  display: grid;
  place-items: center;
  background: var(--color-scrim);
  color: var(--color-on-media);
  transition: background-color var(--dur-micro) var(--ease-out),
    color var(--dur-micro) var(--ease-out);
}
.plate__acts button:hover { background: var(--color-accent); color: var(--color-on-accent); }
.plate__acts button:active { background: var(--color-accent-ink); color: var(--color-on-accent); }
.plate__acts button[aria-pressed='true'] { background: var(--color-danger); color: var(--color-on-danger); }

/* ---------- 骨架屏 ---------- */
.sk-grid { background: var(--color-rule); }
.sk-card {
  aspect-ratio: 4 / 3;
  background: linear-gradient(100deg, var(--color-skeleton) 30%, var(--color-paper-2) 50%, var(--color-skeleton) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.3s infinite linear;
}
@keyframes shimmer {
  from { background-position: 180% 0; }
  to { background-position: -80% 0; }
}

.loading-more {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  padding: var(--space-lg);
  color: var(--color-ink-3);
  font-size: var(--text-sm);
}
.spinner {
  width: 0.9rem;
  height: 0.9rem;
  border: 2px solid var(--color-rule);
  border-top-color: var(--color-accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.sentinel { height: 1px; }

/* ---------- 空状态 ---------- */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-3xl) var(--space-md);
  text-align: center;
}
.empty__mark { color: var(--color-rule-strong); }
.empty h2 { margin: var(--space-xs) 0 0; font-size: var(--text-lg); }
.empty p { margin: 0; max-width: 40ch; font-size: var(--text-sm); }
.empty__actions { display: flex; flex-wrap: wrap; gap: var(--space-xs); justify-content: center; margin-top: var(--space-sm); }

/* ---------- 按钮 ----------
   直角、细边、无渐变。主按钮是实心填充；其余靠边线区分。 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: var(--hit);
  padding: var(--space-xs) var(--space-md);
  border: 1px solid var(--color-rule-strong);
  background: var(--color-paper);
  color: var(--color-ink);
  font-size: var(--text-sm);
  line-height: 1;
  white-space: nowrap; /* 闸门 49：可点文字绝不折行 */
  transition: background-color var(--dur-micro) var(--ease-out),
    border-color var(--dur-micro) var(--ease-out),
    color var(--dur-micro) var(--ease-out);
}
.btn:hover { border-color: var(--color-ink); background: var(--color-paper-2); }
.btn:active { background: var(--color-paper-3); }
.btn--primary {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-on-accent);
  font-weight: 600;
}
.btn--primary:hover {
  background: var(--color-accent-ink);
  border-color: var(--color-accent-ink);
  color: var(--color-on-accent);
}
.btn--danger { color: var(--color-danger); border-color: var(--color-rule-strong); }
.btn--danger:hover {
  background: var(--color-danger-soft);
  border-color: var(--color-danger);
  color: var(--color-danger);
}
.btn--ghost { background: transparent; border-color: var(--color-rule); }
.btn--ghost:hover { background: var(--color-paper-2); border-color: var(--color-rule-strong); }
.btn--block { width: 100%; }
.btn--sm { min-height: 2rem; padding: var(--space-2xs) var(--space-sm); font-size: var(--text-xs); }
/* 禁用三通道（闸门 39）：透明度 + 光标 + 语义属性 */
.btn[disabled],
.btn[aria-disabled='true'] { opacity: 0.55; cursor: not-allowed; pointer-events: none; }
.btn-row { display: flex; flex-wrap: wrap; gap: var(--space-xs); }

/* ---------- 多选操作条 ---------- */
.selection-bar {
  position: fixed;
  left: 50%;
  bottom: var(--space-lg);
  transform: translateX(-50%);
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-xs) var(--space-md);
  max-width: calc(100% - var(--space-lg) * 2);
  overflow-x: auto;
  background: var(--color-paper);
  border: 1px solid var(--color-ink);
  box-shadow: var(--shadow-overlay);
  scrollbar-width: none;
}
.selection-bar::-webkit-scrollbar { display: none; }
.sel-count { font-size: var(--text-sm); font-weight: 600; white-space: nowrap; }
.sel-actions { display: flex; gap: var(--space-2xs); }

/* ---------- 大图弹层 ---------- */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: var(--space-lg);
}
.lb-backdrop {
  position: absolute;
  inset: 0;
  background: var(--color-backdrop);
}
.lb-panel {
  position: relative;
  display: flex;
  max-width: min(1180px, 100%);
  max-height: calc(100dvh - var(--space-lg) * 2);
  background: var(--color-paper);
  border: 1px solid var(--color-rule-strong);
  overflow: hidden;
  box-shadow: var(--shadow-overlay);
  animation: lb-in var(--dur-short) var(--ease-out);
}
@keyframes lb-in {
  from { opacity: 0; transform: scale(0.985); }
  to { opacity: 1; transform: none; }
}
/* 面板用 tabindex="-1" 接管焦点，键盘用户必须看得见焦点落在哪儿 */
.lb-panel:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

.lb-image-wrap {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  place-items: center;
  background: var(--color-stage);
  overflow: auto;
}
.lb-image-wrap img {
  display: block;
  max-width: 100%;
  max-height: calc(100dvh - var(--space-lg) * 2);
  object-fit: contain;
}

.lb-info {
  flex: 0 0 20rem;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-lg);
  overflow-y: auto;
  border-left: 1px solid var(--color-rule);
}
.lb-info h2 {
  margin: 0;
  font-size: var(--text-md);
  line-height: var(--lh-title);
}
.lb-tags { display: flex; flex-wrap: wrap; gap: var(--space-2xs) var(--space-sm); }
.lb-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  font-size: var(--text-xs);
  color: var(--color-ink-2);
  border-bottom: 1px solid var(--color-rule);
}
.lb-tag button { color: var(--color-ink-3); font-size: var(--text-xs); line-height: 1; }
.lb-tag button:hover { color: var(--color-danger); }
.lb-addtag { display: flex; gap: var(--space-2xs); align-items: center; }
.lb-meta { margin: 0; font-size: var(--text-xs); color: var(--color-ink-3); line-height: 1.8; }
.lb-actions { display: flex; flex-wrap: wrap; gap: var(--space-2xs); margin-top: auto; }
.lb-actions .btn { min-height: 2.25rem; padding: var(--space-2xs) var(--space-sm); font-size: var(--text-xs); }

.lb-close {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
  z-index: var(--z-raised);
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  background: var(--color-scrim);
  color: var(--color-on-media);
}
.lb-close:hover { background: var(--color-danger); color: var(--color-on-danger); }

/* 圆形按钮压在深色舞台/图片上：焦点环配一圈暗影，
   这样落在浅色图片边缘时也不会消失（闸门 40） */
.lb-close:focus-visible,
.lb-nav:focus-visible {
  outline: 2px solid var(--color-on-media);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-scrim);
}

.lb-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: var(--z-raised);
  width: var(--hit);
  height: var(--hit);
  display: grid;
  place-items: center;
  background: var(--color-scrim);
  color: var(--color-on-media);
}
.lb-nav:hover { background: var(--color-accent); }
.lb-nav.prev { left: var(--space-sm); }
.lb-nav.next { right: var(--space-sm); }

/* ---------- 抽屉 ---------- */
.drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: calc(var(--z-modal) + 10);
  width: min(26rem, 100%);
  display: flex;
  flex-direction: column;
  background: var(--color-paper);
  border-left: 1px solid var(--color-rule-strong);
  box-shadow: var(--shadow-overlay);
  transform: translateX(100%);
  transition: transform var(--dur-short) var(--ease-out);
}
.drawer.open { transform: none; }
.drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-md) var(--space-lg);
  border-bottom: 3px double var(--color-rule-strong);
}
.drawer__head h2 { margin: 0; font-size: var(--text-lg); }
.drawer__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-lg);
  overflow-y: auto;
}

.backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: var(--color-backdrop);
  animation: fade var(--dur-short) var(--ease-out);
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

/* ---------- 表单 ---------- */
.field { display: flex; flex-direction: column; gap: var(--space-2xs); }
.field label { font-size: var(--text-sm); font-weight: 600; color: var(--color-ink-2); }
.field input,
.field select,
.lb-addtag input,
.filters__select {
  min-height: var(--hit);
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--color-rule-strong);
  border-radius: 0;
  background: var(--color-paper-2);
  color: var(--color-ink);
  transition: border-color var(--dur-micro) var(--ease-out);
}
.field select { cursor: pointer; }
.filters__select { cursor: pointer; min-height: 2rem; padding: var(--space-2xs) var(--space-sm); }
.field input:focus,
.field select:focus,
.lb-addtag input:focus,
.filters__select:focus { border-color: var(--color-focus); }
/* 辅助文字槽：即使为空也占一行，错误出现时不推页面（闸门 39） */
.field .hint { margin: 0; min-height: 1lh; font-size: var(--text-xs); color: var(--color-ink-3); line-height: 1.6; }
.field-row { display: flex; gap: var(--space-sm); }
.field-row .field { flex: 1; min-width: 0; }

.quick-tags { display: flex; flex-wrap: wrap; gap: var(--space-2xs) var(--space-sm); margin-top: var(--space-3xs); }
.quick-tags button {
  font-size: var(--text-xs);
  color: var(--color-ink-2);
  border-bottom: 1px solid var(--color-rule);
  transition: color var(--dur-micro) var(--ease-out),
    border-color var(--dur-micro) var(--ease-out);
}
.quick-tags button:hover { color: var(--color-accent-ink); border-color: var(--color-accent-ink); }

/* 抽屉里的分组名是信息结构，不是章节序号：不加装饰性大写/字距 */
.section-title {
  margin: var(--space-sm) 0 0;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-ink-2);
}
.stats-box { font-size: var(--text-sm); line-height: 1.9; }
.stats-box b { color: var(--color-ink); }
.kbd-list { margin: 0; padding-left: var(--space-md); font-size: var(--text-sm); line-height: 1.9; }

.panel-note {
  margin: 0;
  padding: var(--space-xs) var(--space-sm);
  background: var(--color-accent-soft);
  color: var(--color-accent-ink);
  font-size: var(--text-sm);
}

/* ---------- 抓取任务 ---------- */
.job {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin-top: var(--space-xs);
  padding: var(--space-sm);
  border: 1px solid var(--color-rule);
  background: var(--color-paper-2);
}
.job-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-xs); font-size: var(--text-sm); }
.job-title { font-weight: 600; }
.job-state { font-size: var(--text-2xs); color: var(--color-ink-3); border-bottom: 1px solid var(--color-rule); }
.job-state.running { color: var(--color-accent-ink); border-color: var(--color-accent-ink); }
.job-state.done { color: var(--color-success); border-color: var(--color-success); }
.job-state.failed { color: var(--color-danger); border-color: var(--color-danger); }
.job-msg { font-size: var(--text-sm); color: var(--color-ink-2); }
.job-bar { height: 3px; background: var(--color-paper-3); overflow: hidden; }
/* 进度条走 transform 而不是 width：width 每次更新都会触发布局（闸门 14） */
.job-bar span {
  display: block;
  height: 100%;
  background: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-long) var(--ease-out);
}
.job-nums { display: flex; flex-wrap: wrap; gap: var(--space-sm); font-size: var(--text-xs); color: var(--color-ink-3); }
.job-log {
  max-height: 10rem;
  overflow-y: auto;
  padding: var(--space-xs) var(--space-sm);
  background: var(--color-paper);
  border: 1px solid var(--color-rule);
  font-family: var(--font-body);
  font-size: var(--text-2xs);
  line-height: 1.7;
  color: var(--color-ink-3);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ---------- Toast ---------- */
.toasts {
  position: fixed;
  right: var(--space-lg);
  bottom: var(--space-lg);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-xs);
  pointer-events: none;
}
.toast {
  max-width: min(90vw, 26rem);
  padding: var(--space-xs) var(--space-md);
  background: var(--color-paper);
  border: 1px solid var(--color-ink);
  box-shadow: var(--shadow-overlay);
  font-size: var(--text-sm);
  animation: toast-in var(--dur-short) var(--ease-out);
}
@keyframes toast-in {
  from { opacity: 0; transform: translateY(0.5rem); }
  to { opacity: 1; transform: none; }
}
.toast.error { border-color: var(--color-danger); color: var(--color-danger); }
.toast.success { border-color: var(--color-success); background: var(--color-success-soft); }
.job-state.done { background: var(--color-success-soft); }

/* ---------- 简易文本输入弹层（app.js 动态创建） ---------- */
.dialog {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-modal) + 20);
  display: grid;
  place-items: center;
  padding: var(--space-md);
}
.dialog__panel {
  position: relative;
  z-index: var(--z-raised);
  width: min(24rem, 100%);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-lg);
  background: var(--color-paper);
  border: 1px solid var(--color-rule-strong);
  box-shadow: var(--shadow-overlay);
  animation: lb-in var(--dur-short) var(--ease-out);
}
.dialog__panel h2 { margin: 0; font-size: var(--text-md); }
.dialog__actions { display: flex; justify-content: flex-end; gap: var(--space-xs); }

/* ---------- 版本志 ---------- */
.colophon {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--space-2xl) var(--page-pad) var(--space-3xl);
  border-top: 3px double var(--color-rule-strong);
  /* 必须有纸色底：正文区的 .page 上铺了背景图，而页脚自身不带背景就是透的，
     照片会从它背后透出来 —— 那就不是"除顶栏页脚以外"了。 */
  background: var(--color-paper);
  display: grid;
  gap: var(--space-xs);
}
.colophon__mark {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: var(--lh-title);
}
.colophon__links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-md);
  margin: 0;
  font-size: var(--text-sm);
}
.colophon__links a { color: var(--color-ink-3); }
.colophon__links a:hover { color: var(--color-accent-ink); text-decoration: none; }
.colophon__note {
  margin: 0;
  max-width: var(--measure);
  font-size: var(--text-xs);
  line-height: 1.8;
  color: var(--color-ink-3);
}

/* ---------- 响应式 ----------
   结构不再跨断点变形：筛选带在宽屏和窄屏都是横滑条。
   60rem 只做弹层改向与间距收紧，40rem 降一档字号。 */
@media (max-width: 60rem) {
  /* 收窄后按钮本身要吃满手指，触控下限提到 44px */
  .filt { min-height: var(--hit); }

  .lb-panel { flex-direction: column; max-height: calc(100dvh - var(--space-md) * 2); }
  .lb-image-wrap img { max-height: 52dvh; }
  .lb-info {
    flex: 1 1 auto;
    border-left: none;
    border-top: 1px solid var(--color-rule);
    padding: var(--space-md);
  }
  .lightbox { padding: var(--space-sm); }
}

@media (max-width: 40rem) {
  :root { --page-pad: var(--space-md); --plate-min: 9.5rem; }

  .wordmark__name { font-size: var(--text-xl); }

  /* 工具行保持一行：只留图标，靠 aria-label / title 命名 */
  .act__label { display: none; }
  .act { min-width: var(--hit); min-height: var(--hit); justify-content: center; padding: 0; }
  .toolbar { gap: var(--space-3xs); }

  .empty { padding: var(--space-2xl) var(--space-md); }
  .selection-bar { bottom: var(--space-sm); gap: var(--space-sm); padding: var(--space-xs) var(--space-sm); }
  .toasts { right: var(--space-sm); left: var(--space-sm); bottom: var(--space-sm); align-items: stretch; }
  .toast { max-width: none; }
  .lb-nav { width: 2.25rem; height: 2.25rem; }
  .colophon { padding-bottom: var(--space-2xl); }
}

/* 文章页：宽屏下把 .page 的横向内边距让给正文（正文自己有 92vw/56rem 的约束，
   不需要再叠加一层页面内边距）。这样左右留白对称，正文才是真正居中。
   窄屏（≤60rem）保持 .page 的内边距 —— 那里正文本来就接近满宽，
   去掉内边距会让文字贴到屏幕边缘。 */
@media (min-width: 60.0625rem) {
  .post__body { max-width: min(90vw, 58rem); }
}
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 150ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 150ms !important;
  }
  .plate:hover { transform: none; }
}
