/* ============================================================
   贴图生成 — 应用层页面样式
   取值全部来自 tokens.css（《设计规范》v1.0）。

   用在：模板库 / 定价 / 登录 / 收藏 / 记录 / 个人中心 / 创作中心 /
         我的创作 / 用户作品库 / API 文档 / API Key / 协议 / 管理后台（另见 admin.css）

   三条硬约束（§5 / §8）：圆角一律 0（头像那种等宽高圆形除外）、
   不用 box-shadow、标题一律衬线。
   ============================================================ */

/* ══════════ 页面头（区块头三件套 §6.7）══════════
   眉标 → 页标题 → 头注。原来这里叫 .page-hero（居中 + 渐变字），
   现在一律左对齐、无渐变 —— 渐变文字是 §8 点名要清掉的那一类。 */
.page-head { padding: 56px 0 36px; }
.page-head__title {
  font-family: var(--font-serif);
  font-size: 40px;
  font-weight: 700;
  line-height: 1.2;
  margin: 14px 0 0;
  color: var(--ink);
}
.page-head__sub {
  font-size: 15px;
  line-height: 1.9;
  color: var(--ink-sub);
  margin: 14px 0 0;
  max-width: 720px;
}

/* 兼容旧类名（还有页面在用，逐页换掉之前别删） */
.page-hero { padding: 56px 0 36px; }
.page-hero-title { font-family: var(--font-serif); font-size: 40px; font-weight: 700; line-height: 1.2; margin: 0; color: var(--ink); }
.page-hero-sub { font-size: 15px; line-height: 1.9; color: var(--ink-sub); margin: 14px 0 0; max-width: 720px; }

/* ══════════ 模板库搜索 ══════════ */
.gallery-search {
  position: relative;
  margin: 28px 0 0;
  max-width: 420px;
}
.gallery-search-ico {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--mute);
  pointer-events: none;
}
.gallery-search-input {
  width: 100%;
  height: 48px;
  padding: 0 14px 0 42px;
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 15px;
}
.gallery-search-input::placeholder { color: var(--mute); }
.gallery-search-input:focus { outline: none; border-color: var(--ink); }
.gallery-search-input::-webkit-search-cancel-button { cursor: pointer; }

/* ══════════ 瀑布流画廊 ══════════ */
.gallery { min-height: 300px; padding-bottom: 64px; }
/* 列数写在自定义属性里，`.wf-col` 由 pages.js 建、读这个变量决定分几列。
   **不要退回 `column-count`**：多列布局是「填满一列再填下一列」，而接口给的是从新
   到旧，于是第一行从左往右读出来是第 1、5、9… 张 —— 看着就是从新到旧被打乱了
   （小程序 `utils/masonry.js` 用的也是轮流分列，两边保持一致）。
   列数按规范 §4.2 的作品库网格：6 列 · 列间隙 14。 */
.waterfall {
  /* 宽屏一行 5 张（原来是 6）。1328 的内容宽度下：6 列每张 210px，海报是 9:16 的，
     缩略图只有 373px 高；5 列每张 254px、缩略图 452px —— 用户报「显得图片展示太小了」。
     列数只由这一个变量决定，JS 从 computed style 里读它（见 `desiredColumns()`）。 */
  --wf-cols: 5;
  display: flex;
  column-gap: 14px;
  align-items: flex-start;
}
/* 卡片自己的 margin-bottom 就是列内间距，这里只管横向 */
.wf-col { flex: 1 1 0; min-width: 0; }

.tpl-card {
  position: relative;
  display: block;
  width: 100%;
  margin: 0 0 14px;
  padding: 0;
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
  cursor: pointer;
  text-align: left;
  color: inherit;
  break-inside: avoid;
  transition: border-color var(--t-fast);
}
/* 悬停只换描边色 —— 不上浮、不加投影（§5.3）。 */
.tpl-card:hover { border-color: var(--ink); }
.tpl-card .tpl-thumb {
  position: relative;
  width: 100%;
  overflow: hidden;
  background: var(--stage);
}
.tpl-thumb.ar-a { aspect-ratio: 3 / 4; }
.tpl-thumb.ar-b { aspect-ratio: 9 / 13; }
.tpl-thumb.ar-c { aspect-ratio: 4 / 5; }
.tpl-thumb.ar-d { aspect-ratio: 3 / 5; }
.tpl-thumb.ar-e { aspect-ratio: 5 / 6; }
.tpl-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}
/* 悬停遮罩是渐变 —— 删掉。缩略图保持原样，只把「预览大图」那行字摆出来。 */
.tpl-cta {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  padding: 8px 12px;
  background: rgba(23, 20, 18, 0.82);
  color: var(--paper);
  font-size: 12px;
  font-weight: 500;
  text-align: center;
  opacity: 0;
  transition: opacity var(--t-fast);
  pointer-events: none;
  white-space: nowrap;
}
.tpl-card:hover .tpl-cta { opacity: 1; }

.tpl-info {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  padding: 14px 16px;
  border-top: var(--line-width) solid var(--line);
  background: var(--surface);
}
/* 标题那一栏必须**能缩到比自己的内容还窄**，下面 `.tpl-name` 的
   `text-overflow: ellipsis` 才轮得到生效。

   flex 项默认 `min-width: auto` = min-content，而标题是 `white-space: nowrap`
   —— 它的 min-content 就是整句话的宽度。于是这一栏拒绝收缩，实测撑到 **728px**
   而卡片只有 208px，`.tpl-name` 自己的 `scrollWidth == clientWidth`（没得可省略），
   最后一刀是 `.tpl-card` 的 `overflow: hidden` 砍的：**切在字中间、没有省略号**。
   用户报的就是这个。`min-width: 0` 一加，这一栏才肯让位。 */
.tpl-info > div:first-child { min-width: 0; }
.tpl-name {
  font-family: var(--font-serif);
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tpl-desc {
  font-size: 12px;
  color: var(--mute);
  margin-top: 3px;
  line-height: 1.6;
}
.tpl-arrow {
  flex-shrink: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--mute);
  transition: color var(--t-fast);
}
.tpl-card:hover .tpl-arrow { color: var(--accent); }

/* 模板编号：卡片角上那枚可复制的标签。直角 + 发丝线，不做胶囊、不做渐变。 */
.tpl-code {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  padding: 3px 8px;
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-sub);
  cursor: pointer;
  user-select: none;
  line-height: 1.4;
  vertical-align: middle;
  transition: border-color var(--t-fast), color var(--t-fast);
}
.tpl-code:hover { border-color: var(--ink); color: var(--ink); }
.tpl-code-label {
  font-family: var(--font-sans);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--mute);
}
.tpl-code-num { font-weight: 700; letter-spacing: 0.12em; color: var(--ink); }
.tpl-code-ico { font-family: var(--font-sans); font-size: 10px; color: var(--mute); }
.tpl-code:hover .tpl-code-ico { color: var(--accent); }

/* 缩略图左上角那枚：底是深色块反转（§5.3 的第三种层级手段） */
.tpl-code--float {
  position: absolute;
  left: 10px;
  top: 10px;
  z-index: 3;
  padding: 4px 8px;
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.tpl-code--float .tpl-code-num { color: var(--paper); }
.tpl-code--float .tpl-code-label { color: var(--dark-label); }
.tpl-code--float .tpl-code-ico { color: var(--dark-label); }
.tpl-code--float:hover { border-color: var(--accent); color: var(--paper); }

.tpl-code--inline { margin-top: 6px; }
.tpl-code--lg { margin: 0 0 0 10px; padding: 5px 12px; font-size: 13px; }
.tpl-code--lg .tpl-code-num { font-size: 14px; }

.workbench-heading .tpl-code { margin-top: 0; vertical-align: middle; }
.workbench-title-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 0; }
.imgpreview-code-row { margin-top: 6px !important; }
/* .fav-info 是 flex 列，默认 align-items:stretch —— 不加这句，编号会被拉成整卡宽，
   左边挤着「编号 482170」右边空一大条。收藏页和我的创作共用这张卡，两边一起收窄。 */
.fav-info .tpl-code { margin: 6px 0 4px; align-self: flex-start; }

/* 收藏星标：功能性开关，不是装饰。未收藏弱化、已收藏朱红。 */
.tpl-fav-btn {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 3;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--mute);
  font-size: 15px;
  line-height: 1;
  opacity: 0;
  transition: opacity var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.tpl-card:hover .tpl-fav-btn,
.tpl-fav-btn:focus-visible { opacity: 1; }
.tpl-fav-btn:hover { color: var(--ink); border-color: var(--ink); }
.tpl-fav-btn.favorited { opacity: 1; color: var(--accent); border-color: var(--accent); }

/* ══════════ 工作台 ══════════
   **进来就是全屏**（2026-10 改版）：原来它是个 `max-width: 1160px` 的窗口，
   右上角一颗「最大化」翻 `is-max` 铺满整屏 —— 那颗按钮只能按一次，而它占的
   位置正好是右上角最该留给「关闭」的地方。现在没有窗口大小这一档，
   `is-max` 这一支整个删掉。 */
.workbench-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-workbench);
  background: rgba(23, 20, 18, 0.62);
  display: flex;
  padding: 0;
  animation: fade-in 0.18s var(--ease);
}
/* `height: 100%` 是**确定高度**，`.canvas-stage` 的 `flex: 1` 靠它才分得到高度
   （前提链见 tests/test_pc_workbench_frames.py）。别改成 `auto`。 */
.workbench {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  overflow: hidden;
  animation: wb-in 0.22s var(--ease);
}
@keyframes wb-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
.workbench-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 22px;
  border-bottom: var(--line-width) solid var(--line);
  flex-shrink: 0;
}
.workbench-head-left { display: flex; align-items: center; gap: 14px; min-width: 0; }
.workbench-head-right { display: flex; align-items: center; gap: 8px; }
.workbench-heading { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.workbench-title {
  font-family: var(--font-serif);
  font-size: 18px;
  font-weight: 700;
  margin: 0;
  color: var(--ink);
}
.workbench-sub { font-size: 12px; color: var(--mute); }
.wb-fav-btn { font-size: 13px; }
.wb-fav-btn.favorited { color: var(--accent); border-color: var(--accent); }
.workbench-body { display: flex; flex: 1; min-height: 0; }
/* 左栏：写。**两侧等宽**（和右栏一样 560）—— 不管屏多宽，中栏都是视觉中心，
   海报居中不偏。窄一档的取值见文件末尾那几条断点。 */
.pane-left {
  width: 560px;
  min-width: 340px;
  border-right: var(--line-width) solid var(--line);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  overflow-y: auto;
  background: var(--paper);
}
/* 中栏 + 右栏的容器。**存在的唯一理由是窄屏**：1180 以下右栏要「掉到中栏
   下面」，而那要一个能换方向的父层（宽屏行向、窄屏列向）。没有它的话
   `flex-wrap` 会把右栏甩到左栏下面去，左栏也跟着变矮。 */
.wb-main {
  flex: 1;
  min-width: 0;
  display: flex;
}
/* 中栏：看。**上下结构**（2026-10 改）：上面主图占 9，下面缩略图条占 1。
   画布是那个会长大的孩子（`flex: 1` + `min-height: 0`），海报就是靠这条链
   撑满剩下的高度；条子按 10% 分。 */
.pane-mid {
  flex: 1;
  min-width: 380px;
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--surface);
}
/* 缩略图条：像幻灯片一样一排小图，点哪张上面换成哪张。
   **`flex: 1` 对 `flex: 9`** —— 这就是「上方占 9、下方占 1」那条比例。
   `[hidden]` 必须显式写：`display: flex` 会盖过 `hidden` 自带的 `display: none`
   （一张图时那条本来就不该占地方）。 */
.canvas-strip {
  flex: 1;
  /* 十分之一在矮屏上会缩到三十几个像素高，缩略图小得认不出是哪张 ——
     给一个下限，让主图去让这几像素。 */
  min-height: 88px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  overflow-x: auto;
  overflow-y: hidden;
  border-top: var(--line-width) solid var(--line);
  background: var(--surface);
}
.canvas-strip[hidden] { display: none; }
.strip-thumb {
  flex: 0 0 auto;
  height: 100%;
  aspect-ratio: 9 / 16;
  padding: 0;
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  background: var(--band);
  cursor: pointer;
  overflow: hidden;
  opacity: 0.72;
  transition: opacity var(--t-fast), border-color var(--t-fast);
}
.strip-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.strip-thumb:hover { opacity: 1; }
/* 当前那张：墨色描边 + 不透明。**不用朱红**（那是 CTA 的颜色），
   **也不用 box-shadow**（设计规范明令全站零阴影，`test_pc_shell` 那条 grep 钉着）。 */
.strip-thumb.is-on {
  opacity: 1;
  border-color: var(--ink);
  outline: var(--line-width) solid var(--ink);
  outline-offset: -2px;
}
/* 右栏：改写。**整栏滚**，不钉高度 —— 原来它挂在画布下面，必须
   `max-height: 46%` 才不会把图挤没；现在是并排的一栏，没有可挤的对象了，
   钉住反而让一段长文案在栏里再套一个小滚动条。 */
.pane-copy {
  flex: 0 0 560px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  border-left: var(--line-width) solid var(--line);
  background: var(--paper);
  overflow-y: auto;
}
.pane-section { display: flex; flex-direction: column; gap: 9px; }
.pane-section.pane-grow { flex: 1; min-height: 0; }
.pane-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-sub);
}
/* 「必填」用朱红 —— 它是行动提示（该填了），不是装饰。 */
.req-dot { font-size: 11px; color: var(--accent); font-weight: 500; }
.textarea-wrap {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  background: var(--surface);
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  transition: border-color var(--t-fast);
}
.wb-url-row { display: flex; gap: 8px; align-items: center; margin: 0 0 8px; flex-shrink: 0; }
.wb-url-input {
  flex: 1;
  min-width: 0;
  height: 40px;
  padding: 0 14px;
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 13px;
}
/* 聚焦 = 1px 墨色描边替换发丝线，**不加发光**（§6.4） */
.wb-url-input:focus { outline: none; border-color: var(--ink); }
.wb-url-input::placeholder { color: var(--mute); }
.wb-url-row .btn { flex-shrink: 0; white-space: nowrap; }
.textarea-wrap:focus-within { border-color: var(--ink); }
.textarea-wrap textarea {
  flex: 1;
  width: 100%;
  min-height: 160px;
  padding: 14px;
  background: transparent;
  border: none;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.8;
  resize: none;
}
.textarea-wrap textarea:focus { outline: none; }
.textarea-wrap textarea::placeholder { color: var(--mute); line-height: 1.9; }
.textarea-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 14px;
  border-top: var(--line-width) solid var(--line);
}
.char-count { font-size: 12px; color: var(--mute); font-variant-numeric: tabular-nums; }
.kbd-hint { font-size: 11px; color: var(--mute); }
.pane-actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.canvas-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 22px;
  border-bottom: var(--line-width) solid var(--line);
  flex-shrink: 0;
}
.canvas-title { font-size: 13px; font-weight: 500; color: var(--ink-sub); }
.canvas-actions { display: flex; gap: 8px; }
/* 预览舞台：色块（--stage）+ 1px 发丝线。没有投影，也没有点阵底纹。
   `flex: 9` 配下面 `.canvas-strip` 的 `flex: 1` —— 那就是「上方占 9、下方占 1」。
   条子藏起来时（只有一张图）舞台照旧吃满。 */
.canvas-stage {
  flex: 9;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 28px;
  min-height: 0;
  overflow: hidden;
  position: relative;
  background: var(--stage);
  border: var(--line-width) solid var(--line);
}
.canvas-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
  position: relative;
}
/* 空态的「标记块」：直角色块，不是旋转的虚线圈、不是 emoji。 */
.canvas-empty-ring {
  width: 40px;
  height: 40px;
  border: var(--line-width) solid var(--line);
  background: var(--band);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 6px;
  font-size: 0;            /* 里面原来那颗 emoji 已经删了，兜底不让它露出来 */
}
.canvas-empty-title { font-family: var(--font-serif); font-size: 16px; font-weight: 700; color: var(--ink); margin: 0; }
.canvas-empty-sub { font-size: 13px; color: var(--ink-sub); margin: 0; }
#preview-img {
  position: relative;
  max-width: 100%;
  max-height: 100%;
  border: var(--line-width) solid var(--line);
  cursor: zoom-in;
}
/* ══════════ 生成中的骨架 ══════════
   一块**正在显影的海报**：9:16 的白卡 + 发丝线（§5.3：白卡置于纸底之上靠描边
   浮起、不投影），内部按海报的固定结构排（§2.4：眉标 → 标题 → 弹性留白 → 页脚）。
   **不含朱红**（§6.8 明令）、**不转圈** —— 只有里面那几根条在缓慢呼吸。

   以前这里是 240×180 的空灰方块，三个问题叠在一起：
   ① `--band`(#F5F0E8) 叠在 `--stage`(#F0EBE3) 上**只差 5/5/5**，几乎看不见；
   ② 形状是**横的**，而海报是 1080×1920 的 9:16 —— 它没预告要来的是什么；
   ③ 里面什么都没有 —— 那不是骨架，是一块空白。
   结构本身由 `pages.js::loadingSkeletonHTML()` 出（五处共用一份）。 */
.canvas-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 0;
}
.loading-ring {
  flex: 1 1 auto;
  min-height: 0;
  aspect-ratio: 9 / 16;   /* 海报的真实比例：骨架先把这个「形状」交出去 */
  max-width: 100%;
  padding: 8%;
  /* 条子的尺寸走 **`cqw`**（= 卡宽的百分比），不走高度百分比。
     高度百分比是相对**父层**解析的，而组内那些条的父层是 `.sk-grp`
     —— 它由内容撑高（auto），百分比高度对着 auto 解析成 **0**：整组会塌掉
     （实测过，只有组外的 eyebrow 和页脚有高度）。`cqw` 是对着**卡宽**算的，
     卡是 9:16、宽高成比例，所以按宽算等于按高算，还顺带解决了缩放。 */
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  /* **呼吸均匀落在「组与组之间」**，而不是拿一整块弹性留白垫在中间 ——
     后者会把内容全挤在顶上、中间留出一个洞，读起来像「内容没渲染出来」
     而不是「内容正在来」。组内部用 `.sk-grp` 自己收紧。 */
  justify-content: space-between;
  overflow: hidden;
  background: var(--surface);
  border: var(--line-width) solid var(--line);
}
/* 一组紧挨着的行（大标题两行、一个正文块的三行）—— 组内是紧密的，
   组间留给 `space-between` 去撑。 */
.loading-ring .sk-grp {
  display: flex;
  flex-direction: column;
  gap: 1.5cqw;           /* 组内那几行是一伙的 —— 紧，但跟着卡缩放 */
  flex: 0 0 auto;
}
/* 结构条：**高度用卡高的百分比、宽度用百分比** —— 卡会随舞台缩放（1440 桌面
   到 375 手机差一倍多），写死 px 的话小舞台上那几根条会粗得像配色块。
   组内那个 4px 的小间隙是刻意固定的：它要的是「这几行是一伙的」，
   不是等比缩放的版式细节。
   **下面每一条都带 `.loading-ring` 前缀**：`.sk--xxx` 单类只有 (0,1,0)，
   而基础规则 `.loading-ring .sk` 是 (0,2,0) —— 单类会被基础规则压掉，
   于是 `flex`/`background` 这类覆盖静默失效。 */
.loading-ring .sk {
  display: block;
  flex: 0 0 auto;
  background: var(--line);
  animation: skeleton-pulse 1.9s ease-in-out infinite;
}
.loading-ring .sk--eyebrow { width: 30%; height: 2.2cqw; }
.loading-ring .sk--title { width: 100%; height: 6cqw; }
.loading-ring .sk--title.sk--short { width: 62%; }
.loading-ring .sk--head { width: 46%; height: 3cqw; }
.loading-ring .sk--line { width: 100%; height: 2cqw; }
.loading-ring .sk--line.sk--short { width: 70%; }
.loading-ring .sk--foot { width: 38%; height: 2cqw; }

.loading-text { font-size: 14px; font-weight: 500; margin: 0; color: var(--ink); }
.loading-sub { font-size: 12px; color: var(--mute); margin: 0; }

/* 一组图的框（`.wb-frame`）本身就是 9:16、而且是深色底 —— 骨架直接铺满它，
   **别再套一层白卡**（那会在框里再嵌一张纸）。 */
.wb-frame .loading-ring {
  align-self: stretch;
  width: 100%;
  aspect-ratio: auto;
  max-width: none;
  padding: 14px;
  background: none;
  border: none;
}
/* 深色区那套：`--dark-line`(#2E2722) 叠在 `--dark-bg`(#171412) 上只差 23/19/16，
   在暗底上比浅色区那对儿糊得多（实测截图里几乎看不见）。用 `--dark-body` 的
   透明度版本取一个和浅色区相当的对比 —— `color-mix` 而不是写死 rgba，
   色值仍然只有一个来源（tokens.css）。 */
.wb-frame .loading-ring .sk {
  background: color-mix(in srgb, var(--dark-body) 34%, transparent);
}

/* 服务端出图之后预览区铺的就是这张 `<img>`（`pages.js::renderImages`）。
   它 `src` 里是 `preview_url`（**LIST 变体**）：CDN 开着时是 600px 宽的 webp，
   CDN 关着时就是 2160 宽的原图 —— 两种大小都不该顶着原始像素把预览区撑爆，
   所以夹高跟尺寸无关，两个变体都要夹。
   **两个轴都必须夹住**：`.canvas-stage` 是 `overflow: hidden` 的 flex 行，
   `.wb-shot` 是它的 flex 项、高度由内容决定 —— 内容高度是 auto 时，子元素
   `max-height: 100%` 的百分比无处可解、会被直接忽略。所以壳上必须给一个
   确定高度，图上的 `max-height` 才有意义；这条链是三环：
   `.workbench { height: min(92vh, 860px) }` → `.canvas-stage { flex: 1; min-height: 0 }`
   → `.wb-shot { height: 100% }`。少了任何一环，图上的 `max-height` 就解不出来。 */
.wb-shot { display: flex; align-items: center; justify-content: center; height: 100%; }
.wb-shot img {
  max-width: 100%;
  max-height: 100%;
  border: var(--line-width) solid var(--line);
  cursor: zoom-in;
}

/* 一屏装不下那一块（`pages.js` 的 `renderOverflow`）。
   它由 `stage.insertAdjacentHTML("beforeend", …)` 插进 `.canvas-stage` ——
   而那个容器是 `display: flex`（**行向**），所以这块默认落在图的**右边**、还把图
   挤窄（实测 panelLeftVsShotRight = 0）。`renderOverflow` 因此给容器加一个
   `is-overflow`，把那一栏转成竖向：图在上面，决策在下面。 */
.canvas-stage.is-overflow {
  flex-direction: column;
  /* 中央对齐在这儿是有害的：内容一旦高过容器，`center` 会把**顶部**推到滚动区
     之外，图的上半截直接看不见（flex 的经典坑）。贴着顶排，溢出才有得滚。 */
  justify-content: flex-start;
  gap: 18px;
  overflow-y: auto;
}
/* 列的剩余空间给图，但那两颗按钮才是这一步的**决策点**，必须同屏可见 ——
   所以图让位：`flex: 1 1 auto` + `min-height: 0` 让它吃掉「减掉这一块之后」的
   全部高度，`max-height: 100%`（基类那条）于是有了可解的高度基准。
   不这么写的话 `.wb-shot` 继承来的 `height: 100%` 会让图占满整栏、把按钮推到
   看不见的地方：`overflow-y: auto` 救得了滚动，救不了「压根不知道下面还有按钮」。

   **不用 `vh` 封顶**（试过 `max-height: 46vh`）：那是拿视口当「这一栏有多高」的
   代理，两者在真实工作台里差着一整条 `canvas-bar` 加内边距，白白浪费掉五分之一
   的高度（实测窄栏里 234×414，改成让位之后是 308×546）。
   `min-height` 那个下限只在窗口极矮时生效 —— 那时本来就该滚，但图不能缩成 0。 */
.canvas-stage.is-overflow .wb-shot { height: auto; flex: 1 1 auto; min-height: 0; }
.canvas-stage.is-overflow .wb-shot img { max-height: 100%; min-height: 160px; }

.wb-overflow {
  flex: 0 0 auto;
  width: min(100%, 380px);   /* 别横成一条 444px 的通栏 */
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.wb-overflow-tip {
  font-size: 13px;
  line-height: 1.7;
  color: var(--ink-sub);
  text-align: center;
}
.wb-overflow .btn { width: 100%; }

/* 「生成平台文案」。**两个落点共用这一块**：
   ① 工作台右栏（`.pane-copy > .wb-copy`）—— 一整栏，栏自己滚；
   ② 历史详情弹窗（也在 `.pane-copy` 里 —— 2026-10 起两个弹窗都是「左中右」，
      右栏都是这一块）。
   所以基底这一条**不带任何布局**，布局由各自那条给。这里原来钉着
   `max-height: 46%` + `overflow-y: auto`：那是「挂在画布下面、不能把图挤没」
   那个位置的要求 —— 位置没了（2026-10 改成三分栏），那两条也就没有意义了，
   留着反而会在右栏里套出第二个滚动条。 */
.wb-copy { min-width: 0; }
.pane-copy .wb-copy {
  flex: 1;
  padding: 20px;
  /* 栏滚，不是块滚 —— 一段长文案要能一次读完 */
  overflow-y: auto;
}
.wb-copy-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.wb-copy-title { font-size: 13px; font-weight: 600; color: var(--ink); }
.wb-copy-sub { font-size: 12px; color: var(--mute); }
.wb-copy-tabs { display: flex; flex-wrap: wrap; gap: 0; margin-bottom: 12px; border: var(--line-width) solid var(--line); width: fit-content; }
.wb-copy-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 36px;
  padding: 0 14px;
  border: none;
  border-right: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink-sub);
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.wb-copy-tab:last-child { border-right: none; }
.wb-copy-tab:hover { color: var(--ink); }
/* 平台 tab 上的**真品牌 logo**：和首页那一排「出好的图，直接发到这些平台」
   用的是同一批文件（`static/images/platforms/<icon>.png`，键就是
   `app/copy_types.py::COPY_ICONS` 里的键，也是文件名）。16px —— tab 是 36px
   高的一颗小方块，图标只负责「一眼认出是哪个平台」，不该抢在名字前面。
   旧描边键（book / music / …）没有对应文件，`data-fallback-remove` 会把
   那个 <img> 摘掉、只剩名字（同头像那条规矩）。 */
.wb-copy-tab-ico {
  width: 16px;
  height: 16px;
  display: block;
  object-fit: contain;
  flex: 0 0 auto;
}
/* 「这一段是给谁写的 · 多长」。字数和小程序那一份同一个规矩
   （`styles/field.wxss`）：**有上限才写 `N / 上限`**，这里没有上限，就是 `N 字`。 */
.wb-copy-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin: 0 0 6px;
}
.wb-copy-name { font-size: 13px; font-weight: 500; color: var(--ink); }
.wb-copy-chars { font-size: 12px; color: var(--mute); font-variant-numeric: tabular-nums; }
/* 选中项墨底纸白 —— 和 §6.4 的分段控件同一条规矩 */
.wb-copy-tab.is-on { background: var(--ink); color: var(--paper); font-weight: 500; }
.wb-copy-dot { width: 6px; height: 6px; background: currentColor; opacity: 0.55; }
.wb-copy-hint { font-size: 13px; color: var(--ink-sub); margin: 0 0 10px; }
/* `pre-wrap` 不能省：小红书的话题标签、抖音的一句一行，换行本身就是文案的一部分 */
.wb-copy-text {
  font-size: 14px;
  line-height: 1.8;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
  background: var(--surface);
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  padding: 12px 14px;
  margin-bottom: 10px;
}
.wb-copy-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.wb-copy-time { font-size: 12px; color: var(--mute); margin-left: auto; }
/* 历史详情弹窗里那一份：也在 `.pane-copy` 里，和历史记录那条同一个位置。
   （2026-10 之前它长在普通块流里，靠 `.wb-copy-inline` 把高度约束让掉；
   现在两个落点的布局由 `.pane-copy .wb-copy` 一份给。） */
/* 出图**之前**的右栏：不是一块空白，而是一句「接下来能做什么」。
   阶段提示 —— 和 `.wb-copy-hint`（有记录、还没生成这个平台的）是两件事。 */
.wb-copy-lead {
  font-size: 13px;
  line-height: 1.75;
  color: var(--mute);
  background: var(--surface);
  border: var(--line-width) dashed var(--line);
  padding: 14px 16px;
  margin: 0;
}

/* ══════════ 定价 ══════════ */
.pricing-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  padding-bottom: 64px;
}
.pricing-card {
  padding: 32px 24px;
  text-align: left;
  display: flex;
  flex-direction: column;
}
/* 推荐 / 最划算：不靠放大和投影，靠**描边颜色 + 一枚标签**（§5.3）。
   标签包进固定高度槽位，免得把卡片撑高（§6.5 的等高规则）。 */
.pricing-card .pricing-slot { height: 26px; margin-bottom: 12px; }
.pricing-tag {
  display: inline-block;
  padding: 3px 10px;
  background: var(--accent);
  color: var(--paper);
  font-size: 11px;
  font-weight: 600;
}
.pricing-card.recommended { border-color: var(--accent); }
.pricing-card.best-value { border-color: var(--ink); }
.pricing-name { font-family: var(--font-serif); font-size: 20px; font-weight: 700; margin: 0 0 12px; color: var(--ink); }
.pricing-price { font-family: var(--font-serif); font-size: 22px; font-weight: 700; color: var(--ink); margin: 0 0 6px; }
.pricing-points { font-family: var(--font-serif); font-size: 40px; font-weight: 700; line-height: 1.1; margin: 0 0 6px; color: var(--ink); }
.pricing-unit { font-size: 13px; color: var(--ink-sub); margin: 0 0 24px; }
/* 四档的差别只有积分和有效期（会员**不锁功能**），所以这里不是「功能对比表」，
   是规格与单价。单价那一行是真正在卖的东西，给它墨色 + 衬线，别混在灰字里。 */
.pricing-facts { list-style: none; padding: 0; margin: 0 0 24px; flex: 1; }
.pricing-facts li {
  padding: 9px 0;
  font-size: 13px;
  color: var(--ink-sub);
  border-bottom: var(--line-width) solid var(--line);
}
.pricing-facts li:last-child { border-bottom: none; }
.pricing-perpoint { font-family: var(--font-serif); font-size: 15px; font-weight: 700; color: var(--ink); }
.pricing-card .btn { width: 100%; }

/* ── 「扫码去小程序开通」弹层 ──
   PC 端不接支付：点「立即开通」弹的就是这张小程序码（图由后台上传）。
   遮罩沿用 `.lightbox-overlay`，它是 `cursor: zoom-out` —— 这里不是在看图，
   得改回默认，否则鼠标悬上去像「点一下会放大」。 */
.payqr-overlay { cursor: default; }
.payqr-card {
  background: var(--surface);
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  padding: 28px;
  width: min(360px, calc(100vw - 40px));
  text-align: center;
}
.payqr-title {
  margin: 0 0 8px;
  font-family: var(--font-serif);
  font-size: 20px;
  font-weight: 700;
  color: var(--ink);
}
.payqr-sub { margin: 0 0 20px; font-size: 13px; line-height: 1.7; color: var(--ink-sub); }
/* 尺寸自己写：通用那条 `.lightbox-overlay img`（92vw/92vh）已经删了，
   它是后代选择器、会漏进历史详情那种卡片里。二维码给足 220 —— 手机扫屏幕上的码
   离得远，小了就对不上焦。 */
.payqr-img {
  display: block;
  width: 220px;
  height: 220px;
  margin: 0 auto 16px;
  object-fit: contain;
  background: var(--paper);
  border: var(--line-width) solid var(--line);
}
.payqr-pick { margin: 0 0 10px; font-size: 13px; color: var(--ink); }
.payqr-pick strong { font-family: var(--font-serif); }
.payqr-tip { margin: 0 0 20px; font-size: 12px; line-height: 1.7; color: var(--mute); }

/* ══════════ 登录页 ══════════
   左侧是深色展示侧（§2 明列的三个深色场景之一）：--dark-bg 底 + --dark-accent 强调。 */
.auth-page-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(100vh - var(--nav-h-app) - 80px);
  padding: 48px 24px;
}
.auth-split {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  width: 100%;
  max-width: 960px;
  min-height: 600px;
  border: var(--line-width) solid var(--line);
  background: var(--surface);
  overflow: hidden;
}
.auth-brand {
  position: relative;
  overflow: hidden;
  padding: 48px 44px;
  background: var(--dark-bg);
  color: var(--paper);
}
.auth-brand-inner { position: relative; z-index: 1; display: flex; flex-direction: column; height: 100%; }
.auth-brand-logo {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-serif);
  font-size: 19px;
  font-weight: 700;
  margin-bottom: auto;
}
/* 这块是深色底，方块里的 logo 自带纯黑底，本身就够跳。
   原来那圈半透明白底 + 白描边是给旧的「白字渐变砖」配的，套在黑 logo 上
   会变成给贴纸加相框，所以撤掉。现在连柔影也没有了 —— 全站零投影。 */
.auth-brand-logo .nav-brand-mark { flex: 0 0 auto; }
.auth-brand-headline {
  font-family: var(--font-serif);
  font-size: 40px;
  font-weight: 700;
  line-height: 1.2;
  margin: 40px 0 14px;
  color: var(--paper);
}
/* 深色区用 --dark-accent，不得混用浅色区的 --accent */
.auth-brand-accent { color: var(--dark-accent); }
.auth-brand-sub { font-size: 15px; line-height: 1.9; color: var(--dark-body); margin: 0 0 36px; max-width: 340px; }
.auth-brand-points { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0; }
.auth-brand-points li {
  display: block;
  padding: 14px 0;
  border-top: var(--line-width) solid var(--dark-line);
}
.auth-brand-points strong { display: block; font-size: 14px; font-weight: 600; color: var(--paper); }
.auth-brand-points em { display: block; font-style: normal; font-size: 12px; color: var(--dark-label); margin-top: 2px; }
/* 光晕已删 —— 装饰性大面积渐变是 §8 点名的那一类 */
.auth-brand-glow { display: none; }

.auth-panel { display: flex; align-items: center; padding: 44px 40px; background: var(--surface); }
.auth-panel-inner { width: 100%; }
.auth-page-tabs {
  display: flex;
  border: var(--line-width) solid var(--line);
  margin-bottom: 28px;
}
.auth-page-tab {
  flex: 1;
  height: 36px;
  border: none;
  border-right: var(--line-width) solid var(--line);
  background: var(--surface);
  color: var(--ink-sub);
  font-size: 14px;
  font-weight: 500;
  font-family: var(--font-sans);
  transition: background var(--t-fast), color var(--t-fast);
}
.auth-page-tab:last-child { border-right: none; }
.auth-page-tab.active { background: var(--ink); color: var(--paper); }
.auth-page-hero { text-align: left; margin-bottom: 24px; }
.auth-page-hero h2 { font-family: var(--font-serif); font-size: 28px; font-weight: 700; margin: 0 0 8px; color: var(--ink); }
.auth-page-hero p { font-size: 13px; color: var(--ink-sub); margin: 0; }

.auth-page-form { display: flex; flex-direction: column; gap: 16px; }
.auth-page-field { display: flex; flex-direction: column; gap: 9px; }
.auth-page-label { font-size: 13px; font-weight: 500; color: var(--ink-sub); }
.auth-page-label .optional { font-weight: 400; font-size: 11px; color: var(--mute); }
.auth-page-input {
  width: 100%;
  height: 48px;
  padding: 0 14px;
  background: var(--surface);
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 15px;
  transition: border-color var(--t-fast);
}
.auth-page-input:focus { outline: none; border-color: var(--ink); }
.auth-page-input::placeholder { color: var(--mute); }
textarea.auth-page-input { height: auto; padding: 12px 14px; line-height: 1.8; }
.auth-submit { width: 100%; margin-top: 4px; }
.auth-page-footer { margin-top: 22px; text-align: left; }
.auth-page-footer p { font-size: 12px; color: var(--mute); margin: 10px 0 0; }
.auth-page-footer strong { color: var(--ink); font-weight: 600; }
.auth-page-error {
  font-size: 13px;
  color: var(--accent);
  padding: 9px 0;
  border-top: var(--line-width) solid var(--accent);
  border-bottom: var(--line-width) solid var(--accent);
}
.auth-link-btn {
  background: none;
  border: none;
  padding: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--accent);
}
.auth-link-btn:hover { color: var(--ink); }

.auth-logged-in { padding: 8px 0 4px; }
.auth-logged-in-avatar {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  margin: 0 0 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--band);
  color: var(--ink);
  font-family: var(--font-serif);
  font-size: 26px;
  font-weight: 700;
  overflow: hidden;
  position: relative;
}
.auth-logged-in h2 { font-family: var(--font-serif); font-size: 28px; font-weight: 700; margin: 0 0 8px; color: var(--ink); }
.auth-logged-in > p { font-size: 13px; color: var(--ink-sub); margin: 0 0 6px; line-height: 1.7; }
.auth-logged-in-meta { margin-bottom: 14px !important; }
.auth-logged-in-meta strong { color: var(--ink); font-weight: 600; }
.auth-logged-in-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 22px; }
.auth-logged-in-actions .btn { width: 100%; }

/* ══════════ 扫码登录 ══════════ */
.qr-panel { display: flex; flex-direction: column; align-items: center; gap: 16px; margin: 8px 0 4px; }
.qr-box {
  position: relative;
  width: 220px;
  height: 220px;
  background: var(--surface);
  border: var(--line-width) solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.qr-img { width: 200px; height: 200px; display: block; }
/* 二维码画出来之前盖在这儿的一块占位。
   **不是转圈图标** —— `shared.css` 那条规矩：加载态一律用呼吸的骨架色块。
   棋盘格是为了「一眼看出这是在生成二维码」，而不是一块不知道是什么的白。
   两档色都取纸色（band / stage），**不碰朱红**（骨架用强调色是禁止项）。
   尺寸和 `.qr-img` 对齐：盒子 220 − 边框 2 − 图 200，两边各 9。
   万一 `repeating-conic-gradient` 不认（老浏览器），background-color 兜底成
   一块平色骨架 —— 掉的是纹理，不是「正在加载」这件事。 */
.qr-skeleton {
  position: absolute;
  inset: 9px;
  background-color: var(--stage);
  background-image: repeating-conic-gradient(var(--band) 0% 25%, var(--stage) 0% 50%);
  background-size: 22px 22px;
  animation: skeleton-pulse 1.6s ease-in-out infinite;
}
.qr-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  background: var(--surface);
}
.qr-overlay-text { margin: 0; font-size: 14px; color: var(--ink-sub); }
.qr-hint { margin: 0; font-size: 13px; color: var(--mute); text-align: center; }

/* ══════════ 收藏 / 记录 / 我的创作 卡片 ══════════ */
.cards-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  padding-bottom: 64px;
}
.fav-card {
  overflow: hidden;
  cursor: pointer;
  transition: border-color var(--t-fast);
}
.fav-card:hover { border-color: var(--ink); }
.fav-thumb {
  width: 100%;
  aspect-ratio: 9 / 13;
  overflow: hidden;
  background: var(--stage);
  cursor: zoom-in;
}
.fav-thumb img { width: 100%; height: 100%; object-fit: cover; object-position: top center; }
.fav-info {
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-top: var(--line-width) solid var(--line);
  background: var(--surface);
}
.fav-name {
  font-family: var(--font-serif);
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fav-desc, .fav-meta { color: var(--mute) !important; }
.fav-actions { display: flex; gap: 8px; }
.fav-actions .btn { flex: 1 1 auto; min-width: 0; }
/* 我的创作的卡片比收藏、记录多一个按钮（使用 / 分享切换 / 删除）。四列网格里卡片身宽
   只有 ~256px，视口刚过 1024px（仍是 4 列）时更只剩 ~193px —— 完整文案光文字就要 155px，
   加上内边距和间距必然把「删除」挤到第二行。所以卡片上用短文案（详情弹窗里仍是全称）。
   flex:0 0 auto = 不缩不撑，宁可换行也不把文字压出格子。 */
.fav-actions.mc-actions { flex-wrap: wrap; justify-content: flex-start; gap: 6px; }
.fav-actions.mc-actions .btn { flex: 0 0 auto; padding-left: 10px; padding-right: 10px; white-space: nowrap; }
.mc-private-badge { color: var(--ink-sub); background: var(--band); }
.mc-badge-slot { margin-left: 4px; }
.mc-input { width: 100%; }
.mc-textarea { min-height: 96px; resize: vertical; font-family: inherit; line-height: 1.8; }
.workbench-author { display: inline-block; margin-left: 8px; font-size: 12px; color: var(--ink-sub); }
.tpl-author { color: var(--ink-sub) !important; font-weight: 400; }
.wb-public-toggle {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-right: auto;
  font-size: 13px;
  color: var(--ink-sub);
  cursor: pointer;
  user-select: none;
}
/* 公开状态角标：色块 + 文字，不用语义色气泡。文案由服务端给（审核中 / 已公开 / 未通过）。 */
.hist-pub-badge {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 7px;
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-sub);
  background: var(--band);
  vertical-align: middle;
}
.wb-retry-hint {
  flex: 1 1 100%;
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--accent);
  line-height: 1.6;
}

/* ══════════ 空态（§6.8）══════════
   标记块 + 标题 + 说明 + 一个朱红行动按钮。
   **禁止「暂无数据」这类无信息量的写法** —— 要说清「为什么空」和「怎么不空」。 */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 72px 24px;
  color: var(--ink-sub);
}
.empty-state-icon {
  width: 40px;
  height: 40px;
  background: var(--band);
  border: var(--line-width) solid var(--line);
  margin-bottom: 20px;
  font-size: 0;          /* emoji 已删；这条兜底不让残留字符露出来 */
  flex-shrink: 0;
}
.empty-state-title { font-family: var(--font-serif); font-size: 20px; font-weight: 700; color: var(--ink); margin: 0 0 8px; }
.empty-state-sub { font-size: 14px; line-height: 1.9; margin: 0; color: var(--ink-sub); max-width: 440px; }
/* `:not(.btn)` 不能省：`.empty-state a` 比 `.btn-primary` 高一个特异度
   （0,1,1 vs 0,1,0），会把朱红按钮的文字也染成朱红 —— 朱字配朱底，
   整颗按钮变成一块没有字的红方块（截图里一眼看得见）。 */
.empty-state a:not(.btn) { color: var(--accent); font-weight: 600; }
.empty-state .btn { margin-top: 20px; }

/* ══════════ 无限滚动 ══════════
   页脚是**骨架**，不是转圈（§6.8）。三块并列的卡片形状 —— 说的是「后面还有」，
   比一根转圈的线诚实；宽度收在 640 以内，不跟上面的瀑布流抢视线。 */
.infinite-sentinel {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 8px 16px 56px;
  color: var(--mute);
  font-size: 13px;
}
.infinite-spinner {
  display: flex;
  gap: 14px;
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
}
.infinite-spinner i {
  flex: 1 1 0;
  height: 150px;
  background: var(--band);
  animation: skeleton-pulse 1.6s ease-in-out infinite;
}
/* 三块错开半个周期，看起来是在「陆续到」而不是整块一起闪 */
.infinite-spinner i:nth-child(2) { animation-delay: 0.25s; }
.infinite-spinner i:nth-child(3) { animation-delay: 0.5s; }
.infinite-sentinel.is-error { gap: 12px; }

/* ══════════ 图片预览（广场 / 模板库 → 点开大图）══════════
   z-index 写**字面量**：tests/test_moderation.py 用正则读它，
   `var(--z-preview)` 匹配不上。值必须和 tokens.css 的 --z-preview 一致。 */
.imgpreview-overlay {
  position: fixed;
  inset: 0;
  z-index: 650;
  background: rgba(23, 20, 18, 0.94);
  display: flex;
  flex-direction: column;
  animation: fade-in 0.16s var(--ease);
}
.imgpreview-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 22px;
  flex-shrink: 0;
  border-bottom: var(--line-width) solid var(--dark-line);
}
.imgpreview-meta { min-width: 0; }
.imgpreview-title {
  color: var(--paper);
  font-family: var(--font-serif);
  font-size: 16px;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.imgpreview-sub {
  color: var(--dark-label);
  font-size: 12px;
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.imgpreview-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.imgpreview-zoom { color: var(--dark-label); font-size: 12px; font-variant-numeric: tabular-nums; min-width: 44px; text-align: right; }
.imgpreview-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 7px 14px;
  border: var(--line-width) solid var(--dark-line);
  border-radius: var(--radius);
  background: transparent;
  color: var(--paper);
  font-size: 13px;
  font-weight: 500;
  font-family: var(--font-sans);
  transition: border-color var(--t-fast), background var(--t-fast);
}
.imgpreview-btn:hover { border-color: var(--paper); }
.imgpreview-close { padding: 7px 16px; }
.imgpreview-fav { width: 34px; height: 34px; padding: 0; font-size: 16px; line-height: 1; color: var(--dark-label); }
.imgpreview-fav.favorited { color: var(--dark-accent); border-color: var(--dark-accent); }
.imgpreview-stage {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  touch-action: none;
}
.imgpreview-stage.dragging { cursor: grabbing; }
.imgpreview-stage img {
  max-width: 92vw;
  max-height: calc(100vh - 200px);
  border: var(--line-width) solid var(--dark-line);
  user-select: none;
  -webkit-user-drag: none;
  will-change: transform;
}
/* 最大化（is-max）：舞台占满整屏 —— 工具栏 / 底栏 / 缩略图条浮到图上
   （absolute，不再各自占一行），图放到 100vw × 100vh。 */
.imgpreview-overlay.is-max .imgpreview-stage img { max-width: 100vw; max-height: 100vh; border: none; }
.imgpreview-overlay.is-max .imgpreview-toolbar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  background: rgba(23, 20, 18, 0.72);
}
.imgpreview-overlay.is-max .imgpreview-footer,
.imgpreview-overlay.is-max .imgpreview-thumbs {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 2;
}
.imgpreview-footer {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 14px 22px 18px;
  border-top: var(--line-width) solid var(--dark-line);
  background: rgba(23, 20, 18, 0.72);
}
.imgpreview-footer .imgpreview-hint { margin: 0; padding: 0; text-align: center; color: var(--dark-label); font-size: 12px; width: 100%; }
.imgpreview-footer-actions { display: flex; align-items: center; justify-content: center; gap: 10px; flex-shrink: 0; }
/* 「使用此模板」：全屏预览里唯一的主按钮 —— 朱红，且不脉冲（§8 不做装饰动画）。 */
.imgpreview-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-width: 180px;
  padding: 14px 28px;
  border: none;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--paper);
  font-size: 15px;
  font-weight: 500;
  font-family: var(--font-sans);
  transition: background var(--t-fast);
}
.imgpreview-cta:hover { background: var(--dark-accent); }
.imgpreview-cta-arrow { font-size: 16px; line-height: 1; }
.imgpreview-hint { flex-shrink: 0; text-align: center; padding: 10px; color: var(--dark-label); font-size: 12px; }

/* ══════════ Lightbox ══════════
   `.lightbox-overlay` 今天是个**通用弹窗遮罩**，三个消费方都是「遮罩 + 一张卡片」：
   历史详情、创作详情、举报框。看大图那条路走的是另一个类（`.imgpreview-overlay`），
   跟这里没关系。

   这里原来有一条 `.lightbox-overlay img { max-width: 92vw; max-height: 92vh;
   border: … var(--dark-line) }`（初始提交就在），是当年「遮罩里直接放一张大图」
   留下的。**那条规则现在一个消费方都没有，副作用却还在**：它是**后代**选择器，
   会命中所选卡片里的每一张图，而 (0,1,1) 的权重压过 `.history-detail-img`（0,1,0）
   —— 于是历史详情里那张海报拿到的是 `92vw / 92vh` 而不是 `100% / 70vh`，
   撑出 360px 的海报栏（实测图 466px、栏 360px）**盖到右边的文字栏上**。
   删掉；将来真要在遮罩里放裸图，给那条规则加 `> img`，别再让它漏进卡片里。 */
.lightbox-overlay {
  position: fixed;
  inset: 0;
  z-index: 600;
  background: rgba(23, 20, 18, 0.86);
  display: flex;
  align-items: center;
  justify-content: center;
  animation: fade-in 0.18s var(--ease);
  cursor: zoom-out;
}
.lightbox-close {
  position: absolute;
  top: 22px;
  right: 22px;
  padding: 12px 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--paper);
  border: none;
  border-radius: var(--radius);
  color: var(--ink);
  font-size: 14px;
  font-weight: 500;
  font-family: var(--font-sans);
  transition: background var(--t-fast), color var(--t-fast);
}
.lightbox-close:hover { background: var(--accent); color: var(--paper); }

/* ══════════ 历史详情 / 创作详情 ══════════
   **和工作台同一套壳**（2026-10 改成全屏三栏）：头部一行（标题 + 关闭），
   下面 `.pane-left` / `.pane-mid` / `.pane-copy` 三栏。列宽、断点、窄屏收口
   全部写在 `.pane-*` 那一套里（`@media` 那几条在文件末尾），这里不重复 ——
   两个弹窗的几何因此**逐字相同**，改一处两边一起动。

   历史详情：左「原文 + 操作」/ 中「海报（+ 一组图的缩略图条）」/ 右「生成平台文案」。
   创作详情：同壳，**没有右栏**（它没有「原文 → 改写」那一步），中栏照旧吃满。 */
.history-detail {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  overflow: hidden;
  /* 遮罩是 `cursor: zoom-out`（点空白关掉），卡片内部要还原成普通指针 */
  cursor: default;
}
.history-detail-close { padding: 6px 10px; }
/* 只读的原文块。外观对齐工作台那个 textarea 框（`.textarea-wrap`）——
   同一段「文本」在两个弹窗里不该长得不一样；高度也归它自己滚。 */
.history-detail-text {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 14px;
  background: var(--surface);
  border: var(--line-width) solid var(--line);
  font-size: 14px;
  line-height: 1.8;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
}
/* 图由 `.wb-shot img` 那条链夹住（壳给确定高度、图两个轴都 100%），
   这里只管指针和描边 —— 少一条夹取就是竖版海报被裁掉上下两截。 */
.history-detail-img { max-width: 100%; max-height: 100%; cursor: zoom-in; }

/* ══════════ 个人中心 ══════════ */
/* 占满 `.page-container` 那一栏（1328），**不要再自己收窄** —— 收成 1080 居中的话，
   这一页的内容就从 x=416 开始，而导航、以及站内其它每一页都是 292。
   上边距交给上面那块 `.page-head`（它自己有 36px 的底部内边距），别再叠一层。 */
.profile-layout {
  display: grid;
  grid-template-columns: 240px 1fr;
  gap: 16px;
  align-items: start;
  margin: 0 0 64px;
}
.profile-nav { padding: 20px 16px; }
.profile-nav-head {
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 16px;
  padding: 0 12px 14px;
  color: var(--ink);
}
.profile-nav-list { display: flex; flex-direction: column; gap: 0; }
.profile-nav-item {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 10px 12px;
  border-left: 2px solid transparent;
  color: var(--ink-sub);
  font-size: 14px;
  transition: color var(--t-fast), border-color var(--t-fast), background var(--t-fast);
}
.profile-nav-item:hover { color: var(--ink); background: var(--band); }
/* 当前态 = 朱红 2px 左侧内描边 + 文字墨色 600 —— 和导航同一条语言 */
.profile-nav-item.is-active { color: var(--ink); font-weight: 600; border-left-color: var(--accent); }
.profile-main-col { min-width: 0; }
.profile-panel { padding: 28px; min-height: 200px; }
.profile-id { display: flex; align-items: center; gap: 16px; margin-bottom: 20px; }
/* 头像是全站唯一的圆形（§5.1）。圆由父层 overflow 裁 —— 给 <img> 自己写
   border-radius，父层不裁的话照样是方角。 */
.profile-avatar {
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--band);
  color: var(--ink);
  font-family: var(--font-serif);
  font-size: 22px;
  font-weight: 700;
  flex-shrink: 0;
  overflow: hidden;
  position: relative;
}
/* 同 .nav-avatar-img：圆由父层裁，图自己只负责铺满；绝对定位是为了盖住底下
   那个首字母 —— 图 404 时 onerror 摘掉它，露出首字母。 */
.profile-avatar-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  background: none;
}
.profile-name { font-family: var(--font-serif); font-size: 20px; font-weight: 700; margin: 0 0 4px; color: var(--ink); }
.profile-meta { font-size: 13px; color: var(--mute); display: flex; flex-wrap: wrap; gap: 8px; }
.profile-badge {
  display: inline-block;
  margin-top: 6px;
  padding: 2px 10px;
  font-size: 12px;
  font-weight: 500;
  background: var(--band);
  color: var(--ink-sub);
}
.profile-kpis { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; border: var(--line-width) solid var(--line); }
.profile-kpi { padding: 20px 16px; border-right: var(--line-width) solid var(--line); }
.profile-kpi:last-child { border-right: none; }
.profile-kpi-value { font-family: var(--font-serif); font-size: 28px; font-weight: 700; line-height: 1.1; color: var(--ink); }
.profile-kpi-label { font-size: 12px; color: var(--mute); margin-top: 4px; }
.profile-alert {
  margin-top: 16px;
  padding: 14px 16px;
  border: var(--line-width) solid var(--accent);
  font-size: 14px;
  color: var(--ink);
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  justify-content: space-between;
}
.profile-links { margin-top: 16px; font-size: 13px; display: flex; gap: 16px; flex-wrap: wrap; }
.profile-links a { color: var(--accent); }
.profile-links a:hover { color: var(--ink); }
.profile-panel-title { font-family: var(--font-serif); font-size: 20px; font-weight: 700; margin: 0 0 18px; color: var(--ink); }
.profile-form { display: flex; flex-direction: column; gap: 12px; max-width: 420px; }
.profile-form label { font-size: 13px; font-weight: 500; color: var(--ink-sub); }
.profile-form input {
  width: 100%;
  height: 48px;
  padding: 0 14px;
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  font-size: 15px;
  font-family: var(--font-sans);
  background: var(--surface);
  color: var(--ink);
}
.profile-form input::placeholder { color: var(--mute); }
.profile-form input:focus { outline: none; border-color: var(--ink); }
.profile-form input[readonly] { background: var(--band); color: var(--ink-sub); }
.profile-form .btn { align-self: flex-start; margin-top: 4px; }
/* 积分流水 / 购买记录：走 §6.6 的表格 */
.profile-ledger { width: 100%; border-collapse: collapse; font-size: 13px; }
.profile-ledger th,
.profile-ledger td {
  text-align: left;                 /* 一律左对齐，数字**不**右对齐 */
  padding: 15px 22px;
  border-bottom: var(--line-width) solid var(--line);
  color: var(--ink);
}
.profile-ledger th {
  background: var(--band);
  font-size: 12px;
  color: var(--ink-sub);
  font-weight: 600;
}
.profile-ledger tbody tr:last-child td { border-bottom: none; }
.profile-ledger .amt-pos { color: var(--ink); font-weight: 600; }
.profile-ledger .amt-neg { color: var(--ink-sub); }
.profile-empty { color: var(--mute); font-size: 14px; padding: 32px 0; text-align: center; }
.profile-pager { display: flex; gap: 8px; align-items: center; margin-top: 16px; flex-wrap: wrap; }
.profile-pager-info { font-size: 13px; color: var(--mute); }

/* 购买记录 */
.order-state {
  display: inline-block;
  padding: 2px 8px;
  font-size: 12px;
  font-weight: 500;
  background: var(--band);
  color: var(--ink-sub);
}
.order-state.is-paid { background: var(--ink); color: var(--paper); }
.order-sn {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: var(--mute);
  max-width: 190px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ══════════ 创作中心（三步向导）══════════ */
.create-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
  padding-bottom: 64px;
  align-items: start;
}
#create-guest .create-card { grid-column: 1 / -1; }
.create-card { padding: 22px; }
#create-form .create-card { display: flex; flex-direction: column; gap: 14px; }

.create-step { display: flex; align-items: flex-start; gap: 10px; margin-top: 6px; }
.create-step:first-child { margin-top: 0; }
.create-step-badge {
  flex: none;
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-serif);
  font-size: 13px;
  font-weight: 700;
  color: var(--paper);
  background: var(--ink);
}
.create-step-title { margin: 0; font-family: var(--font-serif); font-size: 18px; font-weight: 700; color: var(--ink); }
.create-step-sub { margin: 4px 0 0; font-size: 12px; color: var(--mute); line-height: 1.7; }

/* 步骤条：① 上传 → ② 生成 → ③ 保存。直角 + 发丝线，当前态朱红下描边。 */
.create-steps { display: flex; align-items: stretch; gap: 0; margin: 0 0 4px; padding: 0; list-style: none; border: var(--line-width) solid var(--line); }
.create-step-item {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 10px 12px;
  border-right: var(--line-width) solid var(--line);
  border-bottom: 2px solid transparent;
  font-size: 12px;
  font-weight: 500;
  color: var(--mute);
  transition: color var(--t-fast), border-color var(--t-fast);
}
.create-step-item:last-child { border-right: none; }
.create-step-item > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.create-step-num {
  flex: none;
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  color: var(--ink-sub);
  background: var(--band);
}
.create-step-item.is-active { color: var(--ink); font-weight: 600; border-bottom-color: var(--accent); }
.create-step-item.is-active .create-step-num { color: var(--paper); background: var(--accent); }
.create-step-item.is-done { color: var(--ink-sub); }
.create-step-item.is-done .create-step-num { color: var(--paper); background: var(--ink); }
/* 未轮到的那一步：舞台色填充 + 弱化色文字，**不加透明度**（§6.8 的禁用态） */
.create-step-item.is-locked { color: var(--mute); background: var(--stage); }

.create-panel { display: flex; flex-direction: column; gap: 14px; }
.create-panel.is-locked { pointer-events: none; }
.create-panel.is-locked .create-step-title,
.create-panel.is-locked .create-step-sub { color: var(--mute); }
.create-locked-note { display: none; margin: 0; font-size: 12px; color: var(--mute); }
.create-panel.is-locked .create-locked-note { display: block; }

.create-picked {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  background: var(--band);
  border: var(--line-width) solid var(--line);
}
.create-picked img { flex: none; width: 44px; height: 44px; object-fit: cover; background: var(--stage); }
.create-picked-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.create-picked-name { font-size: 13px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.create-picked-sub { font-size: 12px; color: var(--mute); }

/* 保存成功面板。✓ 字形删了 —— 换成一块墨色标记（§8 不引入图标集）。 */
.create-success {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding: 22px;
  border: var(--line-width) solid var(--ink);
  text-align: left;
}
.create-success-mark {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0;
  background: var(--ink);
}
.create-success-title { margin: 0; font-family: var(--font-serif); font-size: 18px; font-weight: 700; color: var(--ink); }
.create-success-sub { margin: 0; font-size: 13px; color: var(--ink-sub); line-height: 1.8; }
.create-success-link { color: var(--accent); text-decoration: underline; margin-left: 4px; }

/* 上传区：虚线框是「这里可以放东西」的通用语言，不是装饰，留着；
   但底色不再用渐变。 */
.create-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 260px;
  border: var(--line-width) dashed var(--line);
  background: var(--surface);
  cursor: pointer;
  overflow: hidden;
  position: relative;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.create-drop.is-drag { border-color: var(--accent); background: var(--band); }
.create-drop-hint { display: flex; flex-direction: column; align-items: center; gap: 8px; color: var(--ink-sub); text-align: center; padding: 24px; }
.create-drop-hint strong { font-family: var(--font-serif); font-size: 17px; color: var(--ink); }
.create-drop-hint span { font-size: 13px; color: var(--mute); }
#create-preview { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: var(--stage); }
.create-textarea { min-height: 130px; resize: vertical; line-height: 1.8; }
.create-note { margin: 0; font-size: 13px; color: var(--ink-sub); line-height: 1.8; }
.create-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.create-checks { display: flex; flex-wrap: wrap; gap: 12px 24px; }
.create-check { display: inline-flex; align-items: center; gap: 9px; font-size: 13px; color: var(--ink); cursor: pointer; user-select: none; }
.create-hint { margin: 0; font-size: 12px; color: var(--mute); line-height: 1.8; }

/* 右侧效果栏 */
.create-result { position: sticky; top: 88px; }
.create-result-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.create-result-title { margin: 0; font-family: var(--font-serif); font-size: 18px; font-weight: 700; color: var(--ink); }
.create-result-badge {
  font-size: 11px;
  font-weight: 500;
  padding: 2px 9px;
  background: var(--band);
  color: var(--ink-sub);
  white-space: nowrap;
}
.create-result-badge.is-private { background: var(--stage); color: var(--ink-sub); }
.create-stage { flex: 1; min-height: 460px; }
.create-stage img { position: relative; max-width: 100%; max-height: 100%; border: var(--line-width) solid var(--line); cursor: zoom-in; }
.create-result-actions { display: flex; flex-wrap: wrap; gap: 8px; }

/* ══════════ API 文档 + API Key ══════════
   文档页栅格（§4.2）：侧栏 280（内边距 28）+ 内容列（内边距 64）。 */
.api-docs-shell { max-width: var(--page-width); margin: 0 auto 64px; }
.api-docs-hero { padding: 56px 0 36px; }
/* 英文眉标：字距 2.5（§3.2），全大写交给 CSS —— 文本本身写自然大小写，
   这样读屏和 Ctrl+F 都读得通。 */
.api-docs-kicker { font-size: 11px; font-weight: 600; letter-spacing: 2.5px; text-transform: uppercase; color: var(--mute); margin: 0 0 14px; }
.api-docs-title {
  font-family: var(--font-serif);
  font-size: 40px;
  font-weight: 700;
  line-height: 1.2;
  margin: 0 0 14px;
  color: var(--ink);
}
.api-docs-lead { font-size: 16px; line-height: 1.9; color: var(--ink-sub); margin: 0 0 20px; max-width: 720px; }
/* 行内 code：浅底 + 等宽，不用彩色 */
.api-docs-lead code, .api-docs-section code, .api-docs-chips code,
.ak-guest-sub code, .ak-empty-sub code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.92em;
  background: var(--band);
  border: var(--line-width) solid var(--line);
  padding: 1px 6px;
  color: var(--ink);
}
.api-docs-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 24px; }
.api-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--ink-sub);
  background: var(--surface);
  border: var(--line-width) solid var(--line);
  padding: 6px 12px;
}
.api-chip-post { color: var(--ink); font-weight: 600; letter-spacing: 0.04em; }
.api-docs-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.api-docs-layout {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  gap: 0;
  align-items: start;
  border-top: var(--line-width) solid var(--line);
}
.api-docs-toc {
  position: sticky;
  top: calc(var(--nav-h-app) + 16px);
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 28px 28px 28px 0;
  border-right: var(--line-width) solid var(--line);
}
.api-docs-toc-label { font-size: 11px; font-weight: 600; letter-spacing: 1.5px; color: var(--mute); margin: 0 0 12px; }
.api-docs-toc a {
  font-size: 14px;
  color: var(--ink-sub);
  padding: 8px 0;
  border-left: 2px solid transparent;
  padding-left: 12px;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.api-docs-toc a:hover { color: var(--ink); border-left-color: var(--accent); }
.api-docs-main { display: flex; flex-direction: column; gap: 0; min-width: 0; padding: 0 0 0 var(--gutter-doc); }
.api-docs-section { padding: 40px 0; border-bottom: var(--line-width) solid var(--line); scroll-margin-top: calc(var(--nav-h-app) + 16px); }
.api-docs-section:last-child { border-bottom: none; }
.api-docs-section h2 { font-family: var(--font-serif); font-size: 24px; font-weight: 700; margin: 0 0 16px; color: var(--ink); }
.api-docs-section h3 { font-family: var(--font-serif); font-size: 18px; font-weight: 700; margin: 20px 0 8px; color: var(--ink); }
.api-docs-section p, .api-docs-section li { font-size: 15px; line-height: 1.9; color: var(--ink-sub); }
.api-docs-section a { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
.api-docs-section ul { padding-left: 1.2em; margin: 0; }
.api-muted { font-size: 13px; color: var(--mute) !important; margin-top: 12px !important; }
.api-endpoint {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 14px;
  background: var(--band);
  border: var(--line-width) solid var(--line);
  margin: 0 0 16px;
}
.api-method { font-size: 11px; font-weight: 600; letter-spacing: 0.06em; background: var(--ink); color: var(--paper); padding: 4px 8px; }
.api-path { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 14px; color: var(--ink); background: none; padding: 0; border: none; }
/* 代码块是 §2 明列的三个深色场景之一：--dark-bg 底 + --dark-body 正文。 */
.api-code-wrap { margin: 16px 0 0; border: var(--line-width) solid var(--dark-line); background: var(--dark-bg); overflow: hidden; }
.api-code-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 14px;
  border-bottom: var(--line-width) solid var(--dark-line);
  color: var(--dark-label);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}
.api-copy {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  appearance: none;
  border: var(--line-width) solid var(--dark-line);
  background: transparent;
  color: var(--dark-body);
  font-size: 12px;
  font-weight: 500;
  padding: 6px 10px;
  min-height: 32px;
  transition: border-color var(--t-fast), color var(--t-fast);
}
.api-copy:hover { border-color: var(--dark-accent); color: var(--dark-accent); }
.api-code {
  display: block;
  margin: 0;
  padding: 16px;
  background: transparent;
  color: var(--dark-body);
  font-size: 12.5px;
  line-height: 1.7;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
}
/* 文档里的表格也走 §6.6 */
.api-table-wrap { overflow-x: auto; margin-top: 8px; }
.api-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.api-table th, .api-table td {
  text-align: left;
  padding: 15px 22px;
  border-bottom: var(--line-width) solid var(--line);
  vertical-align: top;
  color: var(--ink);
}
.api-table th { background: var(--band); color: var(--ink-sub); font-size: 12px; font-weight: 600; }
.api-table tbody tr:last-child td { border-bottom: none; }
.api-req, .api-opt { display: inline-block; font-size: 11px; font-weight: 600; padding: 2px 7px; white-space: nowrap; }
.api-req { background: var(--accent); color: var(--paper); }
.api-opt { background: var(--band); color: var(--ink-sub); }
.http-badge {
  display: inline-block;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  font-weight: 600;
  padding: 2px 8px;
  background: var(--band);
  color: var(--ink);
}
/* 4xx / 5xx 不靠颜色区分（朱红只给行动）—— 5xx 多一条左描边，
   因为那一档是「服务端出问题，已经退点」，比参数错更值得先看见。 */
.http-4 { border-left: 2px solid var(--mute); }
.http-5 { border-left: 2px solid var(--accent); }
.api-stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0; border: var(--line-width) solid var(--line); }
.api-stat { padding: 20px; border-right: var(--line-width) solid var(--line); display: flex; flex-direction: column; gap: 4px; }
.api-stat:last-child { border-right: none; }
.api-stat strong { font-family: var(--font-serif); font-size: 18px; color: var(--ink); }
.api-stat span { font-size: 13px; line-height: 1.8; color: var(--ink-sub); }

/* API Key 页刻意复用 /api-docs 的 hero 与代码块语言：两页互为上下游
   （文档 → 拿密钥 → 回文档调接口），视觉跳变越小越好。 */
.ak-ico { display: none; }   /* 图标一律不显示（§8） */
.ak-hero { padding-bottom: 32px; }
.ak-guest { display: flex; justify-content: center; padding: 72px 0 88px; }
.ak-guest-card { width: 100%; max-width: 480px; padding: 40px 32px; }
.ak-guest-ico { display: none; }
.ak-guest-title { font-family: var(--font-serif); font-size: 24px; font-weight: 700; color: var(--ink); margin: 0 0 10px; }
.ak-guest-sub { font-size: 15px; line-height: 1.9; color: var(--ink-sub); margin: 0 0 24px; }
.ak-guest-actions { display: flex; flex-wrap: wrap; gap: 10px; }

/* 一次性密钥展示：朱红描边 —— 它是「现在必须做点什么」的提示。 */
.ak-once { padding: 22px; margin: 0 0 16px; border-color: var(--accent); }
.ak-once-head { display: flex; gap: 12px; align-items: flex-start; margin-bottom: 16px; }
.ak-once-ico { display: none; }
.ak-once-copy { min-width: 0; }
.ak-once-title { margin: 0 0 4px; font-family: var(--font-serif); font-size: 18px; font-weight: 700; color: var(--accent); }
.ak-once-sub { margin: 0; font-size: 13px; line-height: 1.8; color: var(--ink-sub); }
.ak-once-code { margin-top: 0; }
.ak-once-code .api-code { padding: 16px; font-size: 13px; }

.ak-layout { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 16px; align-items: start; }
.ak-panel { overflow: hidden; padding: 0; }
.ak-panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 20px 22px;
  border-bottom: var(--line-width) solid var(--line);
}
.ak-panel-title { font-family: var(--font-serif); font-size: 18px; font-weight: 700; color: var(--ink); margin: 0; }
.ak-count { font-size: 12px; color: var(--mute); }

.apikey-list { display: flex; flex-direction: column; }
.apikey-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 22px;
  border-bottom: var(--line-width) solid var(--line);
  transition: background var(--t-fast);
}
.apikey-row:last-child { border-bottom: none; }
.apikey-row:hover { background: var(--band); }
.apikey-row-ico { display: none; }
.apikey-row-main { flex: 1 1 auto; min-width: 0; }
.apikey-row-name {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  font-family: var(--font-serif);
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  margin: 0 0 5px;
}
.apikey-badge {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-sub);
  background: var(--band);
  padding: 1px 8px;
}
.apikey-row-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin: 0; font-size: 12px; color: var(--mute); }
.apikey-row-prefix {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  color: var(--ink);
  background: var(--band);
  padding: 2px 8px;
}
.apikey-sep { color: var(--line); }
.apikey-revoke { flex: 0 0 auto; }

.ak-empty { display: flex; flex-direction: column; align-items: flex-start; text-align: left; padding: 48px 22px 52px; }
.ak-empty-ico { display: none; }
.ak-empty-title { font-family: var(--font-serif); font-size: 18px; font-weight: 700; color: var(--ink); margin: 0 0 6px; }
.ak-empty-sub { max-width: 380px; margin: 0 0 20px; font-size: 13px; line-height: 1.9; color: var(--ink-sub); }

.ak-side { display: flex; flex-direction: column; gap: 16px; }
.ak-side-card { padding: 22px; }
.ak-side-title { font-family: var(--font-serif); font-size: 18px; font-weight: 700; color: var(--ink); margin: 0 0 16px; }
.ak-side-note { margin: 12px 0 0; font-size: 12px; line-height: 1.8; color: var(--mute); }
.ak-side-note a { color: var(--accent); font-weight: 600; }
.ak-tips { display: flex; flex-direction: column; gap: 10px; margin: 0; padding-left: 1.15em; }
.ak-tips li { font-size: 13px; line-height: 1.8; color: var(--ink-sub); }

/* ══════════ 协议 / 政策页 ══════════
   这是一份**法律文件**：行长要短、行高要松、字号小一档。正文由服务端渲染成块
   （app/doc_format.py）。

   **整页不再自己收窄。** 原来这里有一条 `.doc-page { max-width: 760px }`，挂在
   `.page-container` 上，而那个盒子是 `margin: 0 auto` —— 于是**整页**在 1440 下
   从 x=336 开始排，而导航栏和全站每一页都在 x=56（用户报的「宽与其它页面的宽
   不一样」就是它，实测差 280px）。要收窄的是**正文那一列的阅读宽度**，不是页面
   盒子；页面盒子照旧跟全站对齐。版式沿用 `/profile` 那条左栏（240px + 1fr、
   当前态 = 朱红 2px 左描边）。 */
.doc-layout {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  gap: 0;
  align-items: start;
  border-top: var(--line-width) solid var(--line);
}

/* 三份文档的切换器。**竖排 + 吸顶**：正文两千多像素高，原来那条横排 tab 一滚就
   没了，想换一份得先滚回顶。 */
.doc-tabs {
  position: sticky;
  top: calc(var(--nav-h-app) + 16px);
  display: flex;
  flex-direction: column;
  padding: 28px 24px 28px 0;
  border-right: var(--line-width) solid var(--line);
}
.doc-tabs-label { font-size: 11px; font-weight: 600; letter-spacing: 1.5px; color: var(--mute); margin: 0 0 12px; padding-left: 12px; }
.doc-tab {
  display: block;
  padding: 10px 12px;
  border-left: 2px solid transparent;
  font-size: 15px;
  color: var(--ink-sub);
  transition: color var(--t-fast), border-color var(--t-fast);
}
.doc-tab:hover { color: var(--ink); }
/* 当前态 = 朱红 2px 左侧内描边 + 文字墨色 600 —— 和 `.profile-nav-item`、导航同一条语言 */
.doc-tab.is-on { color: var(--ink); font-weight: 600; border-left-color: var(--accent); }

/* 阅读宽度收在**这一列**上：法律条文一行太长没法读。`--gutter-doc`（64）和
   `/api-docs` 同一条。行高 1.9 + 段距 14px：长文阅读靠这两样，不靠字号。 */
.doc-body { min-width: 0; max-width: 760px; padding: 40px 0 64px var(--gutter-doc); }
.doc-p, .doc-li { font-size: 15px; line-height: 1.9; color: var(--ink); margin: 0 0 14px; }
.doc-li { padding-left: 18px; text-indent: -18px; }
.doc-dot { color: var(--mute); margin-right: 6px; }
.doc-h { font-family: var(--font-serif); font-size: 20px; font-weight: 700; color: var(--ink); margin: 32px 0 12px; }
/* 正文第一块常常就是 `## 一、…`，顶着 32px 的上边距会离标题太远 */
.doc-h:first-child { margin-top: 0; }
.doc-empty { color: var(--mute); }

/* ══════════ 意见反馈页 ══════════
   骨架和 `/agreement`、`/profile` 同一条：左栏 240px + 右栏 1fr、分隔线只画一条
   发丝线。右栏表单是**纸色卡**（`--surface`），不加阴影、不加描边 ——
   层级靠底色深浅（《设计规范》§2）。 */
.feedback-layout {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  gap: 0;
  align-items: start;
  border-top: var(--line-width) solid var(--line);
}
.feedback-note { padding: 36px 24px 36px 0; border-right: var(--line-width) solid var(--line); }
.feedback-note__k {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.5px;
  color: var(--mute);
  margin: 0 0 12px;
}
.feedback-note__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.feedback-note__list li {
  position: relative;
  padding-left: 14px;
  font-size: 13px;
  line-height: 1.8;
  color: var(--ink-sub);
}
.feedback-note__list li::before {
  content: "·";
  position: absolute;
  left: 0;
  color: var(--accent);
  font-weight: 700;
}
.feedback-note__tip {
  margin: 20px 0 0;
  padding-top: 16px;
  border-top: var(--line-width) solid var(--line);
  font-size: 12px;
  line-height: 1.8;
  color: var(--mute);
}
.feedback-note__tip strong { color: var(--ink-sub); font-weight: 600; }

.feedback-card { max-width: 760px; padding: 36px 0 64px var(--gutter-doc); }
.feedback-field { display: flex; flex-direction: column; gap: 9px; margin-bottom: 22px; }
.feedback-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.feedback-label { font-size: 13px; font-weight: 600; color: var(--ink); }
.feedback-optional { font-size: 11px; color: var(--mute); }
.feedback-count { font-size: 11px; color: var(--mute); font-variant-numeric: tabular-nums; }
.feedback-textarea,
.feedback-input {
  width: 100%;
  border: var(--line-width) solid var(--line);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.8;
  padding: 12px 14px;
  border-radius: var(--radius);
  transition: border-color var(--t-fast);
}
.feedback-textarea { resize: vertical; min-height: 200px; }
.feedback-input { height: 44px; }
.feedback-textarea:focus,
.feedback-input:focus { outline: none; border-color: var(--ink); }
.feedback-textarea::placeholder,
.feedback-input::placeholder { color: var(--mute); }
.feedback-actions { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.feedback-status { margin: 0; font-size: 12px; line-height: 1.6; color: var(--accent); }

.feedback-done { padding: 8px 0; }
.feedback-done[hidden] { display: none; }
.feedback-done__k {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.5px;
  color: var(--accent);
  margin: 0 0 10px;
}
.feedback-done__title {
  font-family: var(--font-serif);
  font-size: 26px;
  font-weight: 700;
  color: var(--ink);
  margin: 0 0 10px;
}
.feedback-done__p { font-size: 14px; line-height: 1.9; color: var(--ink-sub); margin: 0 0 22px; }


/* 「注册即表示同意 …」那一行。放在按钮下面、字号小一档 —— 它是告知，不是拦路。 */
.auth-legal, .pricing-legal { margin: 16px 0 0; font-size: 12px; line-height: 1.8; color: var(--mute); }
.pricing-legal { margin-top: 24px; }
.auth-legal a, .pricing-legal a { color: var(--ink-sub); text-decoration: underline; }
.auth-legal a:hover, .pricing-legal a:hover { color: var(--accent); }

/* ══════════ 举报框 ══════════
   和别处的 lightbox 同一套遮罩，卡片窄一点 —— 它是个表单，不是看图。
   **z-index 必须比 `.imgpreview-overlay`（650）高**：举报是从预览里点出来的，
   不抬的话框弹在预览后面，点了没反应（截图抓到的）。
   写**字面量**：tests/test_moderation.py 用正则读它。 */
.report-overlay { z-index: 700; }
.report-card {
  background: var(--surface);
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  padding: 28px;
  width: min(440px, calc(100vw - 40px));
}
.report-title { margin: 0 0 4px; font-family: var(--font-serif); font-size: 20px; font-weight: 700; color: var(--ink); }
.report-sub { margin: 0 0 16px; font-size: 13px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.report-reasons { display: flex; flex-direction: column; gap: 0; margin-bottom: 14px; border: var(--line-width) solid var(--line); }
.report-reason {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-bottom: var(--line-width) solid var(--line);
  cursor: pointer;
  font-size: 14px;
  color: var(--ink);
  transition: background var(--t-fast);
}
.report-reason:last-child { border-bottom: none; }
.report-reason:hover { background: var(--band); }
.report-detail {
  width: 100%;
  min-height: 76px;
  resize: vertical;
  padding: 12px 14px;
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  font: inherit;
  font-size: 14px;
  color: var(--ink);
}
.report-detail::placeholder { color: var(--mute); }
.report-detail:focus { outline: none; border-color: var(--ink); }
.report-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
/* 预览工具栏里那颗举报：朱红 —— 它是行动入口（§3.2 允许的例外） */
.imgpreview-report { color: var(--dark-accent); }

/* ══════════ 一组图（series）══════════
   三选一 / 张数选择 / 分镜编辑 / 横排预览。 */
.wb-modes { display: flex; border: var(--line-width) solid var(--line); }
.wb-mode { flex: 1; position: relative; cursor: pointer; }
.wb-mode input { position: absolute; opacity: 0; width: 0; height: 0; }
.wb-mode span {
  display: block;
  text-align: center;
  padding: 8px 0;
  border-right: var(--line-width) solid var(--line);
  font-size: 13px;
  color: var(--ink-sub);
  transition: background var(--t-fast), color var(--t-fast);
}
.wb-mode:last-child span { border-right: none; }
.wb-mode input:checked + span { background: var(--ink); color: var(--paper); font-weight: 500; }
.wb-mode input:focus-visible + span { outline: var(--focus-ring); outline-offset: -2px; }

.wb-count { display: flex; align-items: center; gap: 0; border: var(--line-width) solid var(--line); }
.wb-count[hidden] { display: none; }
.wb-count-label { flex: 0 0 auto; padding: 0 12px; font-size: 12px; color: var(--mute); }
.wb-count .wb-mode { flex: 0 0 64px; }

.wb-outline-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.wb-outline-role { font-size: 11px; color: var(--mute); }
/* 这张卡用了多少原文 —— 用户才知道哪张长哪张短、删/改之后总账对不对。 */
.wb-outline-size { flex: 0 0 auto; font-size: 11px; color: var(--mute); font-variant-numeric: tabular-nums; }
.wb-outline-title {
  width: 100%;
  box-sizing: border-box;
  height: 34px;
  padding: 0 10px;
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  font-size: 13px;
  font-family: var(--font-sans);
}
/* 引文直接编辑。textarea 自带可改的样子，只对齐 `.wb-outline-title` 的框和字号，
   再限个高 —— 超长引文在卡上滚动，卡不跟着撑开。 */
.wb-outline-quote-edit {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  min-height: 60px;
  max-height: 160px;
  padding: 8px 10px;
  border: var(--line-width) solid var(--line);
  border-radius: var(--radius);
  font-size: 12px;
  line-height: 1.8;
  color: var(--ink);
  font-family: var(--font-sans);
}
.wb-outline-del { font-size: 12px; align-self: flex-start; margin-top: auto; }
.wb-outline-del[disabled] { color: var(--mute); border-color: var(--line); cursor: not-allowed; }

/* 多图（一组图）时主图那个壳里多两行：一行「第 2/5 张 · 正文」，一行按钮。
   壳因此转成**列向**（单张那份是一行、图居中，不动）。
   图用 `flex: 1 1 auto; min-height: 0` 吃掉剩下的高度 —— 不能用
   `max-height: 100%` 当唯一约束（那是相对**整个壳**的，下面两行会被挤到壳外面）。 */
.wb-shot-multi { flex-direction: column; gap: 8px; }
.wb-shot-multi img { flex: 1 1 auto; min-height: 0; max-height: 100%; object-fit: contain; }
.wb-shot-cap { flex: 0 0 auto; font-size: 12px; color: var(--mute); text-align: center; }
.wb-shot-btns { display: flex; gap: 8px; justify-content: center; flex: 0 0 auto; }

/* 分镜编辑阶段（确认前那一屏）的舞台：竖着放 —— 顶上一行提示，下面一排
   编辑卡横向滚动。成图之后整块换成画廊（主图 + 缩略图条），不共用这一套。 */
.canvas-stage.stage-outline {
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 10px;
  padding: 14px;
  overflow-y: auto;
}
.wb-outline-hint { flex: 0 0 auto; font-size: 12px; color: var(--ink-sub); }
.wb-outline-cards { flex: 0 0 auto; display: flex; gap: 14px; overflow-x: auto; padding-bottom: 4px; }

/* ── 海报画框：框就是海报的形状（9:16），编辑 / 加载 / 成图 / 失败 / 重生成
   五种状态只翻类名，框是同一个节点。深色块反转是 §5.3 允许的第三种层级手段。 */
.wb-frame {
  position: relative;
  flex: 0 0 248px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  aspect-ratio: 9 / 16;
  padding: 12px;
  overflow: hidden;
  color: var(--paper);
  background: var(--dark-bg);
  border: var(--line-width) solid var(--dark-line);
}
/* 垫底的那张模板缩略图（编辑态）：铺满 + 压暗 —— 不压暗上面的字看不清。
   走 LIST 变体的 URL（600px webp），框宽 248、2× DPR 正好。 */
.wb-frame-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0.4;
}
/* 底图是 absolute，按绘制顺序会盖在 static 的字段内容**上面** ——
   编辑 / 重生成态的字段全体抬到它上面（成图态的图和按钮自己带定位）。 */
.wb-frame.is-edit > *, .wb-frame.is-regen > * { position: relative; z-index: 1; }
/* 加载 / 失败：骨架和那句话居中。骨架不得使用朱红。 */
.wb-frame.is-loading, .wb-frame.is-failed { align-items: center; justify-content: center; }
.wb-frame.is-failed { border-color: var(--accent); }
.wb-frame-msg { margin: 0; font-size: 12px; color: var(--dark-body); text-align: center; padding: 0 12px; }
/* 成图态：图铺满框（截的就是 9:16，cover 不裁），点图开灯箱。 */
.wb-frame-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; cursor: zoom-in; }
.wb-frame-cap {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 2;
  padding: 2px 8px;
  font-size: 11px;
  color: var(--paper);
  background: rgba(23, 20, 18, 0.72);
}
/* 框下那排按钮浮在图上：竖着排 —— 248px 宽的框横排塞不下「重新生成 · 1 点」。
   z-index 要压过 `.wb-frame-img`（absolute 同层按 DOM 序，img 在前面）。 */
.wb-frame-actions {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 26px 10px 10px;
  background: rgba(23, 20, 18, 0.82);
}
.wb-frame-actions .btn {
  flex: 0 0 auto;
  min-width: 0;
  background: transparent;
  color: var(--paper);
  border: var(--line-width) solid var(--dark-line);
}
.wb-frame-actions .btn:hover { border-color: var(--paper); }
/* 编辑 / 重生成态的字都叠在压暗底图上，控件给浅底深字 —— 表单控件的
   color 不继承（UA 样式给的是系统色），不写明就是黑字配透明底。 */
.wb-frame.is-edit .wb-outline-role, .wb-frame.is-edit .wb-outline-size,
.wb-frame.is-regen .wb-outline-role, .wb-frame.is-regen .wb-outline-size { color: var(--dark-body); }
.wb-frame .wb-outline-title, .wb-frame .wb-outline-quote-edit {
  background: var(--paper);
  color: var(--ink);
  border-color: transparent;
}
.wb-frame .wb-outline-del { color: var(--dark-body); border-color: var(--dark-line); background: transparent; }
/* 重生成态的按钮回文档流、压到框底（同编辑态那颗「删除这张」的活儿）。
   这时它们不在图上，所以把深底上那套「透明 + 白字」换回普通按钮 ——
   不换的话那颗「生成这张 · 1 点」会丢掉朱红填充，看起来和「取消」一样。 */
.wb-frame.is-regen .wb-frame-actions { position: static; margin-top: auto; padding: 10px 0 0; background: none; }
.wb-frame.is-regen .wb-frame-actions .btn {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--line);
}
.wb-frame.is-regen .wb-frame-actions .btn-primary {
  background: var(--accent);
  color: var(--paper);
  border-color: var(--accent);
}

/* ── 一组图：历史/广场卡片角标 + 详情/预览弹窗的缩略图条 ── */
/* 「一组 N 张」和 `.hist-pub-badge` 同一套尺寸 —— 一个是可见性、一个是数量，
   别让它们看起来是同一类信息。 */
.hist-series-badge, .tpl-series-badge {
  display: inline-block;
  padding: 1px 6px;
  font-size: 11px;
  font-weight: 500;
  vertical-align: middle;
  background: var(--band);
  color: var(--ink-sub);
}
.tpl-series-badge { position: absolute; top: 8px; left: 8px; z-index: 2; }
/* 灯箱里那条缩略图。历史详情那条**已经换成共用的 `.canvas-strip` / `.strip-thumb`**
   （2026-10：两个弹窗都改成了「主图 + 缩略图条」，各写一套必然漂）。 */
.imgpreview-thumb {
  flex: 0 0 auto;
  padding: 0;
  border: var(--line-width) solid transparent;
  background: none;
  cursor: pointer;
  overflow: hidden;
  line-height: 0;
}
.imgpreview-thumb img { display: block; width: 56px; height: 100px; object-fit: cover; }
.imgpreview-thumb.active { border-color: var(--accent); }
.imgpreview-thumbs {
  display: flex;
  gap: 8px;
  justify-content: center;
  padding: 10px 16px;
  overflow-x: auto;
  border-top: var(--line-width) solid var(--dark-line);
}
.imgpreview-thumb img { width: 44px; height: 78px; }

/* ══════════ 响应式 ══════════ */
/* 工作台三分栏的收口（2026-10 改版）。两侧**等宽**，一档一档收窄：
   560 → 480 → 400；1180 以下右栏**掉到中栏下面**（`.wb-main` 转列向，
   左栏不动 —— 它装的是用户正在写的东西，不该跟着变形）。
   等宽是关键：不管屏多宽，中栏都是视觉中心，海报居中不偏。 */
@media (max-width: 1600px) {
  .pane-left { width: 480px; }
  .pane-copy { flex-basis: 480px; }
}
@media (max-width: 1280px) {
  .pane-left { width: 400px; min-width: 300px; }
  .pane-copy { flex-basis: 400px; }
}
@media (max-width: 1180px) {
  .wb-main { flex-direction: column; }
  .pane-mid { flex: 1 1 60%; min-width: 0; }
  .pane-copy {
    flex: 1 1 40%;
    border-left: none;
    border-top: var(--line-width) solid var(--line);
  }
}
/* 下面三个断点是**契约**：tests/test_gallery_order.py 逐个断言它们出现在
   pages.css 里，且列数只由 CSS 的 --wf-cols 决定（JS 读它，不自己算）。 */
@media (max-width: 1280px) {
  .waterfall { --wf-cols: 4; }
  .pricing-grid { grid-template-columns: repeat(2, 1fr); }
  .cards-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 960px) {
  .waterfall { --wf-cols: 3; column-gap: 12px; }
  .page-head, .page-hero, .api-docs-hero { padding: 36px 0 24px; }
  .page-head__title, .page-hero-title, .api-docs-title { font-size: 30px; }
  .create-layout { grid-template-columns: 1fr; }
  .create-result { position: static; }
  .create-stage { min-height: 320px; }
  .api-docs-layout { grid-template-columns: 1fr; }
  .api-docs-toc { position: static; flex-direction: row; flex-wrap: wrap; gap: 8px; padding: 20px 0; border-right: none; border-bottom: var(--line-width) solid var(--line); }
  .api-docs-toc-label { width: 100%; margin-bottom: 0; }
  .api-docs-toc a { border-left: none; border-bottom: 2px solid transparent; padding: 4px 0; }
  .api-docs-toc a:hover { border-left: none; border-bottom-color: var(--accent); }
  .api-docs-main { padding: 0; }
  .api-docs-section { padding: 28px 0; }
  .ak-layout { grid-template-columns: 1fr; }
  .ak-side { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
  .profile-layout { grid-template-columns: 1fr; margin: 24px auto 48px; }
  .profile-nav-list { flex-direction: row; overflow-x: auto; }
  .profile-nav-item { white-space: nowrap; border-left: none; border-bottom: 2px solid transparent; }
  .profile-nav-item.is-active { border-left: none; border-bottom-color: var(--accent); }
  .profile-kpis { grid-template-columns: 1fr; }
  .profile-kpi { border-right: none; border-bottom: var(--line-width) solid var(--line); }
  .profile-kpi:last-child { border-bottom: none; }
  /* 协议页的左栏在窄屏翻成横排 —— 和 `.profile-nav-list` 同一条路子：
     竖排的左栏在手机上一屏只装得下三个词，横排还能一眼看全三份。 */
  .doc-layout { grid-template-columns: 1fr; }
  .doc-tabs {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 4px 16px;
    padding: 20px 0;
    border-right: none;
    border-bottom: var(--line-width) solid var(--line);
  }
  .doc-tabs-label { width: 100%; margin-bottom: 0; padding-left: 0; }
  .doc-tab { padding: 4px 0; border-left: none; border-bottom: 2px solid transparent; }
  .doc-tab.is-on { border-left: none; border-bottom-color: var(--accent); }
  /* 意见反馈页同一条路子：左栏说明在窄屏翻成上面一段，表单吃满整行。 */
  .feedback-layout { grid-template-columns: 1fr; }
  .feedback-note {
    padding: 24px 0;
    border-right: none;
    border-bottom: var(--line-width) solid var(--line);
  }
  .feedback-card { padding: 24px 0 48px; }
}
@media (max-width: 900px) {
  /* 工作台在窄屏上铺满整屏（工作台本身已经全屏了，这里收拾的是纵向堆叠） */
  .workbench-overlay { padding: 0; align-items: stretch; }
  .workbench { max-width: 100%; width: 100%; height: 100dvh; max-height: 100dvh; border: none; }
  .workbench-head { padding: 12px 14px; padding-top: max(12px, env(safe-area-inset-top, 0px)); gap: 8px; flex-wrap: wrap; }
  .workbench-head-left { flex: 1; min-width: 0; gap: 10px; }
  .workbench-title { font-size: 16px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .workbench-head-right { flex-shrink: 0; }
  .workbench-body { flex-direction: column; overflow-y: auto; -webkit-overflow-scrolling: touch; }
  .pane-left { width: 100%; min-width: 0; border-right: none; border-bottom: var(--line-width) solid var(--line); padding: 16px; gap: 14px; overflow: visible; }
  .textarea-wrap textarea { min-height: 120px; }
  .kbd-hint { display: none; }
  .pane-actions {
    position: sticky;
    bottom: 0;
    z-index: 2;
    margin: 0 -16px -4px;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
    background: var(--paper);
    border-top: var(--line-width) solid var(--line);
    gap: 8px;
  }
  .pane-actions .btn { flex: 1; min-width: 0; }
  .wb-public-toggle { flex: 1 1 100%; margin: 0 0 4px; min-height: 40px; }
  /* 整页在滚（`.workbench-body` 那条），所以两栏都退回自然高度：
     `flex: 1` 在滚动容器里分到的是内容高度，百分比也解不出来。 */
  .wb-main { flex-direction: column; overflow: visible; }
  .pane-mid { min-height: 52vh; flex: none; }
  .canvas-strip { min-height: 72px; padding: 6px 12px; }
  .pane-copy { flex: none; border-left: none; border-top: var(--line-width) solid var(--line); overflow: visible; }
  .pane-copy .wb-copy { padding: 16px; }
  .canvas-bar { padding: 10px 14px; flex-wrap: wrap; gap: 8px; }
  .canvas-actions { width: 100%; flex-wrap: wrap; }
  .canvas-actions .btn { flex: 1; min-width: 0; }
  .canvas-stage { min-height: 360px; padding: 16px; }
  .auth-split { grid-template-columns: 1fr; max-width: 440px; min-height: 0; }
  .auth-brand { display: none; }
  .auth-panel { padding: 36px 26px; }
  .auth-page-wrap { padding: 32px 16px; }
}
@media (max-width: 768px) {
  .cards-grid { grid-template-columns: repeat(2, 1fr); }
  .api-stat-grid { grid-template-columns: 1fr; }
  .api-stat { border-right: none; border-bottom: var(--line-width) solid var(--line); }
  .api-stat:last-child { border-bottom: none; }
  .imgpreview-toolbar { flex-wrap: wrap; }
  .imgpreview-footer { gap: 12px; padding: 12px 16px 16px; }
  .imgpreview-footer .imgpreview-hint { font-size: 11px; }
  .imgpreview-footer-actions { width: 100%; }
  .imgpreview-cta { flex: 1; min-width: 0; padding: 15px 20px; }
  .imgpreview-stage img { max-height: calc(100vh - 220px); }
  .doc-body { padding: 28px 0 48px; }
  .ak-guest { padding: 40px 0 56px; }
  .ak-guest-card { padding: 32px 22px; }
  .ak-guest-actions .btn { flex: 1 1 auto; }
  .ak-once, .ak-side-card { padding: 18px; }
  .ak-panel-head { padding: 16px; }
  .apikey-row { flex-wrap: wrap; padding: 16px; }
  .apikey-row-main { flex: 1 1 100%; }
  .pricing-card { padding: 28px 20px; }
  .pricing-points { font-size: 32px; }
  .fav-actions { flex-direction: column; }
  .fav-actions .btn { width: 100%; }
  .auth-page-input { font-size: 16px; }   /* 防 iOS 聚焦缩放 */
  .imgpreview-toolbar { padding-top: max(12px, env(safe-area-inset-top, 0px)); }
  .imgpreview-footer { padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px)); }
  .history-detail { width: 100%; max-height: 100dvh; padding: 20px 16px calc(20px + env(safe-area-inset-bottom, 0px)); }
}
@media (max-width: 560px) {
  .waterfall { --wf-cols: 2; column-gap: 10px; }
  .tpl-card { margin-bottom: 10px; }
  .tpl-info { padding: 12px; }
  .tpl-name { font-size: 14px; }
  .tpl-arrow { display: none; }
  /* 触屏：收藏键和 CTA 常驻 —— 没有 hover 可用 */
  .tpl-fav-btn { opacity: 1; width: 34px; height: 34px; }
  .tpl-cta { opacity: 1; }
  .tpl-code--float { left: 6px; top: 6px; padding: 3px 7px; font-size: 11px; }
  .tpl-code--float .tpl-code-label { display: none; }
  .tpl-code--inline .tpl-code-label { display: none; }
  .pricing-grid { grid-template-columns: 1fr; }
  .cards-grid { grid-template-columns: 1fr; }
  .canvas-stage { min-height: 300px; padding: 12px; }
  .workbench-sub { display: none; }
}

/* ── 我的落款：个人中心面板 + 工作台弹窗（共用一套 DOM，见 pages.js）──
   **一行三块**：二维码 → 联系方式 → 效果示意，横着读就是「填什么」到「长什么样」。
   版式开关摆在示意那一栏里 —— 改版式和看结果在同一处，不用在页面上来回找。

   节奏照 `账号资料` 那一栏：标签在上、字数在右（有上限才给分母）、说明在下，
   按钮跟着表单左对齐。个人中心里两个面板长得像一件事，比各自好看更要紧。

   示意是**画出来的**（不是服务端渲的真图）：真值在 app/contact.py，这里只按同样的
   三种排法摆一遍。贴图占位保持 1080×1920 的比例 —— 画成横的就看不出「接在下面」。 */
.contact-lead { margin: 0 0 20px; font-size: 13px; line-height: 1.8; color: var(--ink-sub); }
.contact-editor { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 28px; }
.contact-hint { margin: 0; font-size: 12px; line-height: 1.7; color: var(--mute); }

/* 二维码（可选）：**整块就是那颗按钮**，和小程序那一页同一个形状 */
.contact-qr-col { flex: 0 0 148px; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.contact-qr-box {
  width: 148px;
  height: 148px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  border: var(--line-width) dashed var(--line);
  color: var(--mute);
  font-family: var(--font-sans);
  font-size: 12px;
  cursor: pointer;
  overflow: hidden;
}
.contact-qr-box:hover { border-color: var(--ink); color: var(--ink-sub); }
.contact-qr-box:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.contact-qr-ph { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.contact-qr-img { display: block; width: 100%; height: 100%; object-fit: contain; }
.contact-link {
  padding: 0;
  border: none;
  background: none;
  color: var(--accent);
  font-family: var(--font-sans);
  font-size: 12px;
  cursor: pointer;
}
.contact-link:hover { text-decoration: underline; }

/* 联系方式 */
.contact-fields { flex: 1 1 320px; min-width: 0; max-width: 420px; display: flex; flex-direction: column; gap: 8px; }
.contact-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.contact-label { font-size: 13px; font-weight: 500; color: var(--ink-sub); }
.contact-count { font-size: 12px; color: var(--mute); font-variant-numeric: tabular-nums; }
.contact-fields textarea {
  width: 100%;
  min-height: 96px;
  padding: 10px 12px;
  background: var(--surface);
  border: var(--line-width) solid var(--line);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.6;
  resize: vertical;
}
.contact-fields textarea:focus { outline: none; border-color: var(--ink); }

/* ├ 效果示意：左边那列就是成品的样子（贴图 + 接在下面的落款条），右边是版式开关。
   `align-items: center` —— 右边那栏（标题 + 三格 + 一句说明）比左边那块贴图矮得多，
   顶部对齐的话下面会空出一大片米色，看着像没画完。 */
.contact-preview {
  flex: 1 1 300px;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 16px;
  background: var(--band);
  border: var(--line-width) solid var(--line);
}
.cp-stage { flex: 0 0 auto; display: flex; flex-direction: column; }
.cp-title { font-size: 13px; font-weight: 500; color: var(--ink-sub); }
.cp-side { flex: 1 1 160px; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.cp-poster {
  width: 150px;
  aspect-ratio: 1080 / 1920;
  background: var(--surface);
  border: var(--line-width) solid var(--line);
  border-bottom: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  color: var(--mute);
}
/* 落款条：三种排法就是 app/contact.py 那三套（row / column / row-reverse） */
.cp-strip {
  width: 150px;
  padding: 8px;
  background: #FFFFFF;
  border: var(--line-width) solid var(--line);
  display: flex;
  align-items: center;
  gap: 8px;
}
.cp-strip.is-left { flex-direction: row; }
.cp-strip.is-center { flex-direction: column; text-align: center; }
.cp-strip.is-right { flex-direction: row-reverse; }
.cp-qr {
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
  background: var(--band);
  border: 1px dashed var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.cp-qr img { display: block; width: 100%; height: 100%; object-fit: contain; }
.cp-lines { font-size: 10px; line-height: 1.5; color: var(--ink); white-space: pre-wrap; word-break: break-word; }

/* 版式：三格分段控件，跟着它上面那张示意一起看 */
.contact-layouts { display: flex; border: var(--line-width) solid var(--line); background: var(--surface); }
.contact-layout {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 30px;
  border-right: var(--line-width) solid var(--line);
  font-size: 12px;
  color: var(--ink-sub);
  cursor: pointer;
}
.contact-layout:last-child { border-right: none; }
.contact-layout:has(input:checked) { background: var(--ink); color: var(--paper); }

/* 保存：跟着表单走（同 `账号资料` 那一栏）。弹窗里那一排按钮才右对齐 */
.contact-actions { display: flex; gap: 10px; }
.contact-actions-end { justify-content: flex-start; margin-top: 20px; }
/* 塞在字段栏里的那一份：挨着它保存的那个框，间距交给字段栏自己的 gap */
.contact-actions-inline { margin-top: 0; }
.contact-modal .contact-actions-end { justify-content: flex-end; }

/* 工作台那颗「设置/编辑」小按钮：和开关并排 */
.wb-contact-edit {
  height: 26px;
  padding: 0 10px;
  border: var(--line-width) solid var(--line);
  background: var(--surface);
  color: var(--ink-sub);
  font-family: var(--font-sans);
  font-size: 12px;
  cursor: pointer;
}
.wb-contact-edit:hover { border-color: var(--ink); color: var(--ink); }

/* 弹窗：从工作台那颗开关点出来的。工作台是 --z-workbench(400)，弹窗走
   --z-modal(800) —— 按阶梯来，别写一个「刚好够大」的数字。 */
.contact-overlay { cursor: default; z-index: var(--z-modal); }
.contact-modal {
  width: min(560px, calc(100vw - 40px));
  max-height: calc(100vh - 80px);
  overflow-y: auto;
  background: var(--surface);
  border: var(--line-width) solid var(--line);
  padding: 24px;
}
.contact-modal-title { margin: 0 0 8px; font-family: var(--font-serif); font-size: 20px; font-weight: 700; color: var(--ink); }
