/* PromptDeck · style.css
   §8.1 基调：主色 1 个；中性 4 阶；圆角只用 8/12px；间距只用 4/8/12/16/20/24；
        无渐变、无纹理、无玻璃拟态；阴影最多 1 层且只给牌；字重只用 400/500/600。
   C9 图片素材：原「零图片素材」约束已于 2026-09-27 放弃（素材现为牌盒图 / 壁纸 / 雕花框 / 按钮图
        / 牌面，见 AGENTS.md §C9），全文另有 35 处 CSS 外部引用函数（url(...)）指向它们。
   §8.3 禁止 * { transition: all }。 */

/* 中文一律用系统里的「方正姚体」（Windows 上装的是 FZYTK.TTF，注册名就是"方正姚体"）。
   只对中文生效：用 unicode-range 把这条 @font-face 限定在 CJK 汉字 + 中文标点上，
   拉丁字母和数字继续走 --font 后面的那条栈——否则 "PromptDeck" 词头和牌面的 A/2/3…/J/Q/K
   会一起被换成姚体那种手写味字形。
   本机没装这个字体时，这条 @font-face 取不到字体源，浏览器就按 unicode-range 逐字
   回退到后面的字体，不会出现方框（§11 的同一条降级思路）。 */
@font-face {
  font-family: "PD-Han";
  src: local("方正姚体"), local("FZYaoTi"), local("FZYTK--GBK1-0");
  unicode-range: U+2E80-2EFF, U+2F00-2FDF, U+3000-303F, U+3400-4DBF,
                 U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF;
}

:root {
  color-scheme: light dark;

  /* 主色（1 个） */
  --primary: #1D4ED8;

  /* 中性 4 阶：背景 / 卡片 / 分隔 / 次级文字 */
  --bg: #F4F4F5;
  --surface: #FFFFFF;
  --divider: #D8D8DC;
  --text-weak: #5B5B63;
  --text: #1A1A1F;

  /* 全场壁纸，四段（首页 / 过场 / 澄清 / 输出）共用；--bg 仍是它取不到时的纯色兜底 */
  --bg-image: url("./assets/cards/bg-light.webp");

  /* 维多利亚雕花框（§8.1 问题气泡，只用在这一处）。
     素材 img/victorian_frame_{light,dark}_cut.png（3547×1182，约 3:1，RGBA 抠图），
     缩到 1120×373 存成 webp；框内是干净面板（亮版米白银灰雕花、暗版近黑铜金雕花），
     文字压在面板中央。内框线在左右 7.8% / 上下 21.2%（PIL 扫描实测，两主题几乎一致）。
     随主题切换，四处写法与 --bg-image 一致。 */
  --frame-image: url("./assets/cards/frame-light.webp");

  /* 成品提示词的大牌框（§8.5）。素材 img/大牌_{亮,暗}.png（1852×2264，RGBA 圆角），
     缩到 1240×1516 存 webp；中央空白区就是提示词的文字区（padding 见 #manual-card）。
     雕花两主题同为红色系，所以它不随 data-type 换色（那一套边框染色已删）。 */
  --bigcard-image: url("./assets/cards/bigcard-light.webp");

  /* 四色（§8.2 亮色列） */
  --art: #C8102E;
  --tech: #1D4ED8;
  --content: #047857;
  --tone: #6D28D9;

  --card-shadow: 0 2px 8px rgba(0, 0, 0, .10);

  /* 牌宽：由「5 张答案牌 + 4 个间距必须在桌面区单排放得下」反推。
     桌面区可用宽 1392px（1440 视口 − 左右 padding 24×2；原先还要再减「待选择牌堆」的 240px，
     那块面板已删除，所以现在比下面这个反推还要宽裕），
     原先 252px 时 5 张需 1292px，超宽后 layoutPositions() 会折成两排，
     第二排被手牌区挤掉 → 用户看到「答案牌显示不全」（事故台账：折行不等于可见）。
     取 208px：5 张需 208×5 + 8×4 = 1072px，余量充足，窄屏也不易触发折行。
     牌高按原 252:308 比例同步缩到 254px。
     代价是 16 字的 label 会折 2 行——牌面图中央留白高 168px，折 2 行仍装得下（见 .card .label）。 */
  --card-w: 208px;
  --card-h: 254px;
  --gap: 8px;

  /* 版面让位值（原「全场画框」留下的三个变量，画框已移除，只保留中性数值）：
     --topbar-h 顶栏净高（主题按钮 44 + 上 4 / 下 12）；
     --bottom-pad 正文末尾留白；--inset-x 左右版心。
     整屏高的阶段（#clarify）要从前两项算 100vh，否则底栏会被推出首屏。 */
  --topbar-h: 60px;
  --bottom-pad: 24px;
  --inset-x: 24px;

  --font: "PD-Han", "PingFang SC", "HarmonyOS Sans SC", "Microsoft YaHei", system-ui, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #131315;
    --surface: #1F1F22;
    --divider: #34343A;
    --text-weak: #A2A2AC;
    --text: #F2F2F5;
    --art: #FF6B73;
    --tech: #7CA9FF;
    --content: #34D399;
    --tone: #B08CFF;
    --bg-image: url("./assets/cards/bg-dark.webp");
    --frame-image: url("./assets/cards/frame-dark.webp");
    --bigcard-image: url("./assets/cards/bigcard-dark.webp");
  }
}

html[data-theme="light"] {
  /* :root 的 `light dark` 是两值形式——UA 按**系统偏好**挑一个，跟 data-theme 无关。
     系统是暗色而用户手动切亮时，滚动条 / 输入框内部 / 缩放柄这些 UA 自绘件仍是暗的，
     所以这里必须跟着主题把 color-scheme 钉成单值（成品正文 #manual-text 是 overflow:auto）。 */
  color-scheme: light;

  --bg: #F4F4F5;
  --surface: #FFFFFF;
  --divider: #D8D8DC;
  --text-weak: #5B5B63;
  --text: #1A1A1F;
  --art: #C8102E;
  --tech: #1D4ED8;
  --content: #047857;
  --tone: #6D28D9;
  --bg-image: url("./assets/cards/bg-light.webp");
  --frame-image: url("./assets/cards/frame-light.webp");
  --bigcard-image: url("./assets/cards/bigcard-light.webp");
}

html[data-theme="dark"] {
  color-scheme: dark;   /* 同上：手动切暗时也要钉住，不能只靠系统偏好 */

  --bg: #131315;
  --surface: #1F1F22;
  --divider: #34343A;
  --text-weak: #A2A2AC;
  --text: #F2F2F5;
  --art: #FF6B73;
  --tech: #7CA9FF;
  --content: #34D399;
  --tone: #B08CFF;
  --bg-image: url("./assets/cards/bg-dark.webp");
  --frame-image: url("./assets/cards/frame-dark.webp");
  --bigcard-image: url("./assets/cards/bigcard-dark.webp");
}

/* §8.3 主题切换：只过渡三个属性，过渡结束由 JS 移除 class */
html.theme-fading,
html.theme-fading body,
html.theme-fading .card,
html.theme-fading .frame-wrap,
html.theme-fading input,
html.theme-fading textarea,
html.theme-fading button {
  transition: background-color 200ms linear, color 200ms linear, border-color 200ms linear;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  min-height: 100%;
  color: var(--text);
  font-family: var(--font);
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
}

/* 全场壁纸：首页 → 过场 → 澄清 → 输出共用这一个根背景，阶段切换不换底。
   body 不再自己铺底色，否则会把根背景盖掉。
   用 repeat 而不是 cover：素材本身是无缝平铺花纹（左右/上下接缝的像素差只有
   图内相邻两列的 1.2–1.6 倍，已实测），平铺才不受文档高度影响——
   cover 是相对根元素的盒子裁的，页面一长就会把图案放大到失真。
   用 fixed 让图案锚在视口上：#box-transition 这个固定覆盖层
   铺的是同一张图，相位天然对齐，转场时图案不会"跳一格"。
   --bg 保留为纯色兜底：图取不到时退回中性底色，不会白屏（§11）。 */
html {
  background-color: var(--bg);
  background-image: var(--bg-image);
  background-repeat: repeat;
  background-attachment: fixed;
}

/* ========== 问题气泡的雕花框（§8.1 装饰；见 .frame-wrap） ==========
   全场画框（原 #page-frame 的四条边线框）已按产品决定移除：
   它那圈细线在实机上「太丑」，同一套维多利亚纹样改用整幅雕花框承载问题气泡。 */

/* 画框已移除，正文不再需要为它让位；左右仍留 24px 版心。 */
body { padding: 0 var(--inset-x) var(--bottom-pad); }

h1 { font-size: 20px; font-weight: 600; margin: 0 0 12px; }

button { font-family: var(--font); font-size: 14px; cursor: pointer; }

.icon { width: 16px; height: 16px; fill: currentColor; flex: none; }

.note { color: var(--text-weak); font-size: 14px; margin: 4px 0 0; }
.error { color: var(--art); font-size: 14px; margin: 4px 0 0; min-height: 20px; }
.req, .opt { color: var(--text-weak); font-weight: 400; margin-left: 8px; font-size: 14px; }

/* 命中区 >= 44x44（§8.7） */
/* 图片按钮：背景是维多利亚卷草雕花按钮图，透明底 webp。
   2026-09-30 起全站 8 个按钮都走 .img-btn（只有首页 #box-start 仍是 CSS 文字按钮）：
   顶部两个 + 顶栏主题 + 输出阶段三个 + 确认弹窗两个。宽度一律写死为「素材宽 ÷ 4」——
   站点素材是 DPR 4 出的，这样在 4 倍屏上是 1:1 像素映射，不会被 background-size
   拉伸（.img-btn 是 100% 100%），长宽比也不会失真。

   主题写法必须与 --bg-image / 牌面图同套的**三段式**，不能只用
   `html:not([data-theme="light"])`：
     ① 默认亮色写在 #id 里；
     ② 系统档的暗色放在 @media (prefers-color-scheme: dark) 内，并带
        html:not([data-theme="light"])（用户手动选了亮色就压过去）；
     ③ 用户手动选暗色再写一条 html[data-theme="dark"]。
   2026-09-30 修正：原写法漏了第 ② 段（只有 ① 和裸的 html:not([data-theme="light"])），
   于是「系统是亮色 + 用户从没点过主题按钮」时 data-theme 属性不存在、那个选择器成立，
   按钮显示暗版素材，而页面其余部分（--bg-image / 文字 / 牌面）都是亮色。
   四态实测见 qa/verify-buttons-img.mjs 的 theme matrix。 */
.img-btn {
  height: 44px;
  padding: 0;
  border: none;
  background: none;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
  cursor: pointer;
}
/* ① 亮色（默认） */
#skip-btn { width: 124px; background-image: url("./assets/cards/btn-skip-light.webp"); }
#finalize-btn { width: 112px; background-image: url("./assets/cards/btn-finalize-light.webp"); }
/* 输出阶段按钮区（复制全文 121.25 / 下载 txt 115.25 / 下载 md 119.5）
   与确认弹窗（再答几题 / 现在生成，均 97.25） */
#copy-btn { width: 121.25px; background-image: url("./assets/cards/btn-copy-light.webp"); }
#dl-txt-btn { width: 115.25px; background-image: url("./assets/cards/btn-dltxt-light.webp"); }
#dl-md-btn { width: 119.5px; background-image: url("./assets/cards/btn-dlmd-light.webp"); }
#confirm-cancel { width: 97.25px; background-image: url("./assets/cards/btn-confirm-cancel-light.webp"); }
#confirm-ok { width: 97.25px; background-image: url("./assets/cards/btn-confirm-ok-light.webp"); }

/* ② 系统档为暗色时（且用户没手动选亮色） */
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) #skip-btn { background-image: url("./assets/cards/btn-skip-dark.webp"); }
  html:not([data-theme="light"]) #finalize-btn { background-image: url("./assets/cards/btn-finalize-dark.webp"); }
  html:not([data-theme="light"]) #copy-btn { background-image: url("./assets/cards/btn-copy-dark.webp"); }
  html:not([data-theme="light"]) #dl-txt-btn { background-image: url("./assets/cards/btn-dltxt-dark.webp"); }
  html:not([data-theme="light"]) #dl-md-btn { background-image: url("./assets/cards/btn-dlmd-dark.webp"); }
  html:not([data-theme="light"]) #confirm-cancel { background-image: url("./assets/cards/btn-confirm-cancel-dark.webp"); }
  html:not([data-theme="light"]) #confirm-ok { background-image: url("./assets/cards/btn-confirm-ok-dark.webp"); }
}

/* ③ 用户手动切到暗色 */
html[data-theme="dark"] #skip-btn { background-image: url("./assets/cards/btn-skip-dark.webp"); }
html[data-theme="dark"] #finalize-btn { background-image: url("./assets/cards/btn-finalize-dark.webp"); }
html[data-theme="dark"] #copy-btn { background-image: url("./assets/cards/btn-copy-dark.webp"); }
html[data-theme="dark"] #dl-txt-btn { background-image: url("./assets/cards/btn-dltxt-dark.webp"); }
html[data-theme="dark"] #dl-md-btn { background-image: url("./assets/cards/btn-dlmd-dark.webp"); }
html[data-theme="dark"] #confirm-cancel { background-image: url("./assets/cards/btn-confirm-cancel-dark.webp"); }
html[data-theme="dark"] #confirm-ok { background-image: url("./assets/cards/btn-confirm-ok-dark.webp"); }
.img-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.img-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.primary-btn .btn-icon, .ghost-btn .btn-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}
.primary-btn, .ghost-btn {
  min-height: 44px;
  min-width: 44px;
  padding: 8px 20px;
  border-radius: 8px;
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.primary-btn {
  background-color: var(--primary);
  color: #FFFFFF;
  border: 1px solid var(--primary);
}
.primary-btn:disabled {
  background-color: var(--divider);
  border-color: var(--divider);
  color: var(--text-weak);
  cursor: not-allowed;
}

.ghost-btn {
  background-color: var(--surface);
  color: var(--text);
  border: 1px solid var(--divider);
}
/* 注（2026-09-30）：.ghost-btn 现在只剩一处使用者——app.js 的 beginWrite() 在空白牌里
   动态生成的「写好了」按钮（不在维多利亚按钮图那套里，牌面内也不该压雕花）。
   .primary-btn 已无使用者（原 #confirm-ok 已改成图片按钮），规则留着作为回落。 */

/* 主题切换按钮：44×44 正方形，按钮面是维多利亚风格图片（与 #skip-btn 同一套 .img-btn 写法，
   44px 高度来自 .img-btn）。2026-09-30 起素材换成新版卷草雕花日／月，
   源图 img/theme-toggle-{light,dark}.png（176×176，由 img/_gen_btn9.py 从同一母版派生）。
   跟随「当前生效主题」：亮色显月亮（提示点击转暗），暗色显圆太阳（提示点击转亮）。
   系统档（无 data-theme）由 prefers-color-scheme 判定，与牌面/背景同套写法。 */
#theme-toggle { width: 44px; background-image: url("./assets/cards/btn-theme-light.webp"); }
/* 与上面 8 个按钮同一套三段式（② 段必须在媒体查询里，原因见 .img-btn 上方注释） */
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) #theme-toggle { background-image: url("./assets/cards/btn-theme-dark.webp"); }
}
html[data-theme="dark"] #theme-toggle { background-image: url("./assets/cards/btn-theme-dark.webp"); }

.primary-btn:focus-visible,
.ghost-btn:focus-visible,
input:focus-visible,
textarea:focus-visible,
.card:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

#topbar {
  display: flex;
  justify-content: flex-end;
  padding: 4px 0 12px;
}
/* 介绍页入口（2026-10-01）。它靠 margin-right:auto 顶到最左，
   #topbar 的 justify-content:flex-end 不动，右侧两件（思考点 / 主题按钮）位置与尺寸不变。
   字号 13px、用次级文字色：它是一个入口，不该与主题按钮抢注意力。 */
#about-link {
  margin-right: auto;
  align-self: center;
  font-size: 13px;
  color: var(--text-weak);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding-bottom: 1px;
  transition: color 140ms linear, border-color 140ms linear;
}
#about-link:hover {
  color: var(--text);
  border-bottom-color: var(--divider);
}

/* 降级状态条（§11 状态可见性）：吸顶 */
#status-bar {
  position: sticky;
  top: 0;
  z-index: 30;
  padding: 8px 0;
  background-color: var(--surface);
  border-bottom: 1px solid var(--divider);
  color: var(--text);
  font-weight: 500;
}
#status-bar[hidden] { display: none; }

/* 思考指示点（2026-09-29 产品决定）：取题等待不占状态条，只亮一个小点，
   鼠标悬浮元素本身可见 title 提示「正在想问题…」。只动 opacity（C7）。 */
#think-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background-color: var(--primary);
  align-self: center;
  margin-right: 10px;
  animation: think-pulse 1.2s ease-in-out infinite;
}
#think-dot[hidden] { display: none; }
@keyframes think-pulse {
  0%, 100% { opacity: 0.25; }
  50%      { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  #think-dot { animation: none; opacity: 0.65; } /* C8：减弱动效下只静显不脉动 */
}

/* ========== 首页 ========== */

#home {
  max-width: 680px;
  margin: 0 auto;
  padding: 0 0 24px;
}
#home[hidden] { display: none; }
/* 牌盒变成主视觉后，logo 牌阵缩小让位 */
#logo-deck { margin-bottom: 12px; }

/* 首页用牌拼字：每张牌一个 div，字母用文字渲染（C9） */
#logo-deck {
  display: flex;
  gap: 4px;
  justify-content: center;
  margin-bottom: 20px;
  flex-wrap: wrap;
}
.logo-card {
  width: 34px;
  height: 48px;
  border-radius: 8px;
  background-color: var(--surface);
  border: 1px solid var(--divider);
  box-shadow: var(--card-shadow);
  display: grid;
  place-items: center;
  font-size: 20px;
  font-weight: 600;
  color: var(--text);
}
.logo-card[data-type="art"] { border-color: var(--art); color: var(--art); }
.logo-card[data-type="tech"] { border-color: var(--tech); color: var(--tech); }
.logo-card[data-type="content"] { border-color: var(--content); color: var(--content); }
.logo-card[data-type="tone"] { border-color: var(--tone); color: var(--tone); }

/* ========== 牌盒即表单 ==========
   牌盒图（720×1080，宽高比 2:3）作为首页主视觉，三个输入位用百分比绝对定位
   落在盒面原有的三处留白上。坐标由素材实测得出（局部方差扫描 + 放大目视互校）：
     ① 标题下窄带   x 24.2–76.5%  y 20.6–28.1%   → 作品类型下拉
     ② 中部大框     x 26.7–72.9%  y 39.6–75.4%   → 需求描述
     ③ 右下描边框   x 54.7–88.1%  y 86.4–90.4%   → 手机号
   下面的 slot 坐标在实测留白基础上各边再内缩约 1%，避免控件贴住花纹。
   换牌盒图必须重新量，不能沿用这组数。 */
#deck-box {
  position: relative;
  width: min(560px, 100%);
  aspect-ratio: 2 / 3;
  margin: 0 auto;
  /* box-cut-*.webp 是抠完底的盒图：四周底板（大理石底）已透明，只留盒子本身。
     盒子自带形状，所以这里既不能垫底色——垫了会在盒角/四周重新露出一圈底板，
     也不能再加 border-radius——盒角本来是接近直角的，圆角会啃掉红色描边。
     裁剪范围与 box-*.png 逐像素一致，所以下面三个 slot 的百分比坐标不用重测。 */
  background-image: url("./assets/cards/box-cut-light.webp");
  background-size: 100% 100%;
  background-repeat: no-repeat;
  background-color: transparent;
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) #deck-box { background-image: url("./assets/cards/box-cut-dark.webp"); }
}
html[data-theme="dark"] #deck-box { background-image: url("./assets/cards/box-cut-dark.webp"); }

/* 盒盖：转场动画的旋转对象（§8.4 0–200ms）。图上盖子是印死的，
   这里只做一个与盒顶重合的透明层供 GSAP 旋转，不再画边框。 */
.box-lid {
  position: absolute;
  left: 2%;
  right: 2%;
  top: 0;
  height: 12%;
  transform-origin: 50% 100%;
  pointer-events: none;
}

/* 三个空位：控件绝对铺满整块留白。
   注意不能用 grid + align-content:center——那会让行高收缩到内容高度，
   textarea 的 height:100% 会退化成 rows 属性的高度，中部大框会空掉一大半。 */
#deck-box .slot {
  position: absolute;
  display: block;
}
/* 空位里的 label 只给读屏，不占视觉（盒面本身就是版式） */
#deck-box .slot > label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* 空位里的报错浮在该位下方，不撑开布局 */
#deck-box .slot > .error {
  position: absolute;
  left: 0;
  top: 100%;
  margin: 2px 0 0;
  font-size: 12px;
  min-height: 0;
  line-height: 1.3;
  white-space: nowrap;
  pointer-events: none;
}

/* 盒面上的控件：底色半透明，既让盒面花纹透出来，又让人看得出这里能输入，
   并严格铺满所属留白。牌盒留白本身带印刷框线，控件不再加自己的粗边框。 */
#deck-box .slot select,
#deck-box .slot input,
#deck-box .slot textarea {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  min-height: 0;
  padding: 2px 6px;
  border: 0;
  border-radius: 4px;
  /* 框做成不可见：底色与描边全部去掉，直接让盒面花纹透出来，
     视觉上就是"在牌盒上写字"。可输入性由 placeholder 文字 +
     hover/focus 时才浮现的极淡底色来暗示，不常驻任何框线。 */
  background-color: transparent;
  box-shadow: none;
  color: var(--text);
  font-family: var(--font);
  text-align: center;
  /* 只过渡 background-color（C7：box-shadow 不在可补间清单里，残留会撞 C7 校验词） */
  transition: background-color 150ms linear;
}
/* 只有鼠标悬停或聚焦时才浮现一层极淡的底，提示"这里能填"，松手就消失 */
#deck-box .slot select:hover,
#deck-box .slot input:hover,
#deck-box .slot textarea:hover {
  background-color: color-mix(in srgb, var(--surface) 30%, transparent);
  box-shadow: none;
}
#deck-box .slot select:focus,
#deck-box .slot input:focus,
#deck-box .slot textarea:focus {
  background-color: color-mix(in srgb, var(--surface) 45%, transparent);
  box-shadow: none;
}
#deck-box .slot select:focus-visible,
#deck-box .slot input:focus-visible,
#deck-box .slot textarea:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 1px;
}
/* 占位文字：框不可见后，它成了「这里能填」的唯一常驻提示，
   所以要比常规 placeholder 更清楚一档，但仍弱于用户填入的正文。 */
#deck-box .slot ::placeholder {
  color: color-mix(in srgb, var(--text) 62%, transparent);
  font-weight: 400;
}

/* 暗色主题：盒面是黑底红花纹。框同样不可见，只把文字与占位色提亮，
   保证在深色盒面上读得清。 */
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) #deck-box .slot select,
  html:not([data-theme="light"]) #deck-box .slot input,
  html:not([data-theme="light"]) #deck-box .slot textarea {
    background-color: transparent;
    box-shadow: none;
  }
  html:not([data-theme="light"]) #deck-box .slot select:hover,
  html:not([data-theme="light"]) #deck-box .slot input:hover,
  html:not([data-theme="light"]) #deck-box .slot textarea:hover {
    background-color: color-mix(in srgb, var(--surface) 34%, transparent);
  }
  html:not([data-theme="light"]) #deck-box .slot select:focus,
  html:not([data-theme="light"]) #deck-box .slot input:focus,
  html:not([data-theme="light"]) #deck-box .slot textarea:focus {
    background-color: color-mix(in srgb, var(--surface) 50%, transparent);
  }
  html:not([data-theme="light"]) #deck-box .slot ::placeholder {
    color: color-mix(in srgb, var(--text) 62%, transparent);
  }
}
html[data-theme="dark"] #deck-box .slot select,
html[data-theme="dark"] #deck-box .slot input,
html[data-theme="dark"] #deck-box .slot textarea {
  background-color: transparent;
  box-shadow: none;
}
html[data-theme="dark"] #deck-box .slot select:hover,
html[data-theme="dark"] #deck-box .slot input:hover,
html[data-theme="dark"] #deck-box .slot textarea:hover {
  background-color: color-mix(in srgb, var(--surface) 34%, transparent);
}
html[data-theme="dark"] #deck-box .slot select:focus,
html[data-theme="dark"] #deck-box .slot input:focus,
html[data-theme="dark"] #deck-box .slot textarea:focus {
  background-color: color-mix(in srgb, var(--surface) 50%, transparent);
}
html[data-theme="dark"] #deck-box .slot ::placeholder {
  color: color-mix(in srgb, var(--text) 62%, transparent);
}

/* ========== 启动位：盒面印刷的 PromptDeck 标题 ==========
   热区落在盒图 720×1080 里的标题字形上（实测字形 bbox x198..518 y133..208，
   宽度占盒面 27.5%–71.9%，正好夹在两侧花纹之间的缝里），四边各留约 1% 余量。
   平时透明，印刷标题原样露着；悬停/聚焦才浮现不透明底板，把印刷字盖住换成
   「开启 PromptDeck」——于是"盒上的字自己变了"，而不是多出一块浮层。
   底板用 --surface：浅色主题比盒面（实测 #F7F8FB）亮约 8 级，深色主题比盒面
   （实测 #14171C）亮约 8 级，两侧都刚好读成"浮现了一块牌子"。
   位置下沿停在分隔线（y212）之上，hover 时那条细线不会被盖掉。 */
#box-start {
  position: absolute;
  left: 26.4%;
  top: 11.7%;
  width: 46.8%;   /* 73.2 - 26.4 */
  height: 7.8%;   /* 19.5 - 11.7 */
  padding: 0;
  border: 0;
  border-radius: 8px;
  background-color: transparent;
  color: var(--text);
  font-family: var(--font);
  font-weight: 500;
  font-size: clamp(11px, 2.8cqw, 17px);
  line-height: 1;
  cursor: pointer;
}
#box-start:hover,
#box-start:focus-visible { background-color: var(--surface); }

/* 提示字平时不可见。用 opacity 而不是 visibility/display：
   读屏始终能报出按钮名，键盘用户也拿得到焦点。 */
#box-start .box-start-hint {
  opacity: 0;
  transition: opacity 150ms linear;
}
#box-start:hover .box-start-hint,
#box-start:focus-visible .box-start-hint { opacity: 1; }

/* 禁用态：沿用原「开始」按钮的语义——必填未过就不让进，缺什么由 #start-hint 说明。
   这里只把提示字压灰并换成禁止光标；底板照常浮现，让用户知道动作是什么。 */
#box-start:disabled { cursor: not-allowed; }
#box-start:disabled .box-start-hint { color: var(--text-weak); }

/* 触摸设备没有悬停态，常驻浮现，否则首页就没有可见的启动位了 */
@media (hover: none) {
  #box-start { background-color: var(--surface); }
  #box-start .box-start-hint { opacity: 1; }
}

/* ① 作品类型：标题下窄带。默认就是可直接填写的 input，挂 datalist 供下拉选预设 */
.slot-type {
  left: 25.5%;
  top: 21.5%;
  width: 49.5%;   /* 75.0 - 25.5 */
  height: 5.5%;   /* 27.0 - 21.5 */
}
#deck-box .slot-type input {
  font-size: clamp(11px, 2.6cqw, 15px);
  /* 居中要真的落在控件中心，左右内边距必须相等；只有 padding-right 的话，
     内容会整体左偏 (18-6)/2 ≈ 6px。18px 同时让开右侧自绘三角。 */
  padding: 2px 18px;
}
/* 自绘下拉指示三角：提示这里除了能打字，还能点开选预设项。
   datalist 在多数浏览器不提供可见的下拉按钮，所以自己画一个。 */
#deck-box .slot-type::after {
  content: "";
  position: absolute;
  right: 3%;
  top: 50%;
  z-index: 1;
  width: 0;
  height: 0;
  margin-top: -2px;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid color-mix(in srgb, var(--text) 72%, transparent);
  pointer-events: none;
}
/* 隐藏 Chrome 自带的 datalist 下拉箭头，避免和自绘三角重复 */
#deck-box .slot-type input::-webkit-calendar-picker-indicator {
  opacity: 0;
  width: 16px;
  cursor: pointer;
}

/* ② 需求描述：中部大框 */
.slot-brief {
  left: 27.7%;
  top: 40.6%;
  width: 44.2%;   /* 71.9 - 27.7 */
  height: 34.2%;  /* 74.8 - 40.6 */
}
#deck-box .slot-brief textarea {
  resize: none;
  font-size: clamp(11px, 2.2cqw, 13px);
  line-height: 1.55;
  text-align: left;
  overflow: auto;
  /* 上面是折行后的对齐；内容一行内放得下时由 .is-oneline 切成居中
     （class 由 app.js 的 syncBriefAlign() 按实际文本宽度切换） */
  /* 文字从顶部开始排，输入变多时向下生长，与"在盒面上写字"的直觉一致 */
  padding: 8px 10px;
  /* 框不可见：不画描边，直接露出盒面中部的留白 */
  box-shadow: none;
}
/* 短内容（含占位文字）居中 */
#deck-box .slot-brief textarea.is-oneline { text-align: center; }

/* ③ 手机号：右下描边框内 */
.slot-phone {
  left: 56%;
  top: 86.8%;
  width: 30.5%;   /* 86.5 - 56 */
  height: 3.2%;   /* 90.0 - 86.8 */
}
#deck-box .slot-phone input {
  font-size: clamp(10px, 2.1cqw, 13px);
  /* 内容不足一行时居中显示，与作品类型那格一致 */
  text-align: center;
  letter-spacing: .04em;
  /* 盒面这块留白本身印着一圈描边框，控件自己不再画任何框线 */
  box-shadow: none;
  border-radius: 2px;
}
/* hover/focus 的浮现底色沿用公共规则（半透明），不要在这里填成不透明白底，
   否则会把盒面印的那圈描边框糊掉 */
#deck-box .slot-phone input:hover,
#deck-box .slot-phone input:focus { box-shadow: none; }
#deck-box .slot-phone > .error { left: auto; right: 0; }

/* 牌盒容器按自身宽度换算字号（clamp 里的 cqw） */
#home-form { container-type: inline-size; display: grid; gap: 16px; }

/* 牌盒图加载失败：取消绝对定位，三个空位退回常规表单排版，不让输入框浮在空白上 */
html.box-art-failed #deck-box {
  aspect-ratio: auto;
  width: 100%;
  background-image: none;
  border: 1px solid var(--divider);
  padding: 20px;
  display: grid;
  gap: 20px;
}
html.box-art-failed .box-lid { display: none; }
html.box-art-failed #deck-box .slot {
  position: static;
  width: 100%;
  height: auto;
  gap: 4px;
}
html.box-art-failed #deck-box .slot > label {
  position: static;
  width: auto;
  height: auto;
  clip-path: none;
  font-weight: 500;
}
html.box-art-failed #deck-box .slot > .error { position: static; font-size: 14px; white-space: normal; }
html.box-art-failed #deck-box .slot select,
html.box-art-failed #deck-box .slot input,
html.box-art-failed #deck-box .slot textarea {
  position: static;
  inset: auto;
  height: auto;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--divider);
  border-radius: 8px;
  background-color: var(--surface);
  box-shadow: none;
  text-align: left;
  font-size: 14px;
}
html.box-art-failed #deck-box .slot-type::after { display: none; }
html.box-art-failed #deck-box .slot-type input { padding-right: 12px; }
html.box-art-failed #deck-box .slot-brief textarea { min-height: 96px; resize: vertical; }
/* 盒图没加载出来时标题带不存在了，启动位退回常规主按钮，否则它会浮在空表单上无处可点 */
html.box-art-failed #box-start {
  position: static;
  width: 100%;
  height: auto;
  min-height: 44px;
  padding: 8px 20px;
  background-color: var(--primary);
  color: #FFFFFF;
  font-size: 14px;
}
html.box-art-failed #box-start .box-start-hint { opacity: 1; }
html.box-art-failed #box-start:disabled {
  background-color: var(--divider);
  color: var(--text);
  opacity: .72;
}

#home-foot {
  display: grid;
  gap: 4px;
  justify-items: center;
  text-align: center;
}
/* 启动动作已移到盒面标题（#box-start），这里只剩"还差什么"的说明与手机号口径 */

.field { display: grid; gap: 4px; }
.field > label { font-weight: 500; }

input[type="tel"], input[type="text"], textarea {
  width: 100%;
  min-height: 44px;
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid var(--divider);
  background-color: var(--surface);
  color: var(--text);
  font-family: var(--font);
  font-size: 14px;
}
textarea { min-height: 96px; resize: vertical; }

/* 作品类型原先用 chips（预设按钮组）表达，牌盒改版后改为下拉 + 自定义框（见 .slot-type），
   chips 样式随之移除，避免死代码误导后续改动。 */

/* ========== 澄清阶段布局（§8.6：>=1024px 三区） ========== */

#clarify {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr auto auto;
  grid-template-areas:
    "table"
    "hand"
    "actions";
  gap: 16px;
  padding: 0 0 var(--bottom-pad);
  /* 这一页是整屏高的：减掉顶栏与末尾留白，否则底栏会被推到屏幕外。 */
  min-height: calc(100vh - var(--topbar-h) - var(--bottom-pad));
}
#clarify[hidden] { display: none; }

#table {
  grid-area: table;
  position: relative;
  min-height: 420px;
}

/* 问题气泡层（维多利亚雕花框）。
   2026-10-02 竖屏自适应：框宽由 560px 定值改为 min(560px, 100%)，高度按素材原比例
   （3547×1182 = 3.001:1，显示盒沿用 560×196 = 2.857:1）由 aspect-ratio 反推 ——
   在 >=608px 的视口上 min() 取 560，尺寸与改造前**逐像素相同**（qa/verify-frame.mjs 的
   「.frame box is 560x196」照旧成立）；窄屏才等比缩小，不会被拉伸变形。
   高度不再写死，所以 #bubble-layer 的高度由流内的 .bubble-slot 撑（见 index.html 的注释）。 */
#bubble-layer {
  position: relative;
  padding: 20px 0;
}
/* 框的比例：196 / 560 = 0.35。宽高都从这里算，改一处就够 */
.bubble-slot {
  width: min(560px, 100%);
  aspect-ratio: 20 / 7;
  margin: 0 auto;
}
.frame-wrap {
  position: absolute;
  left: 50%;
  top: 20px;
  width: min(560px, 100%);
  aspect-ratio: 20 / 7;
  /* 居中用 margin 而不是 transform：.frame-wrap 的 transform 归 GSAP（上下位移），
     写成 translateX(-50%) 会被 GSAP 的 y 补间整条覆盖掉，框就横着偏一半。
     百分比在这条 margin 上按**包含块宽度**换算，父层又没有水平 padding，所以是精确居中。 */
  margin-left: calc(min(560px, 100%) / -2);
  opacity: 0;
}
/* 素材 img/victorian_frame_{light,dark}_cut.png 是 3547×1182（3.001:1），
   显示盒沿用原气泡的 560×196（2.857:1），纵向多拉 5%——雕花纹样看不出这个量级，
   换来的是 #bubble-layer / #table / 出牌时序的既有尺寸一个都不用改。
   2026-09-30：确认弹窗曾短暂改用按钮母版派生的框，气泡**不跟**，已随本条恢复原素材。
   2026-10-01：弹窗也按产品要求改回同一张素材，两者重新共用 --frame-image，
   `--dialog-frame-image` 变量已删（画法差异只在 border-image vs 整图拉伸，见 #confirm-box）。 */
.frame {
  width: 100%;
  height: 100%;
  display: block;
  background-image: var(--frame-image);
  background-size: 100% 100%;
  background-repeat: no-repeat;
}

/* 文字是 HTML，压在雕花框中央的干净面板上（不用 <text>）。
   素材的干净板面占 x 8%–92%（换算到 560 宽即 45–515），文字必须整体落在里面才
   不会压到角叶，故左右各留 64 → 内容宽 432，40 字（§7 上限）在这个宽度里折 2 行不溢出。
   上下藤蔓带在 y 21%–79% 之外（盒里 41–155 之外），文字垂直居中，不碰。
   2026-10-02：64px 是 560 的 11.43%，窄屏改用同一比例（clamp 兜住 20px 下界），
   否则框缩小后 64px 会吃掉整个面板。 */
.frame-text {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 15px;
  font-weight: 500;
  color: var(--text);
  text-align: center;
  padding: 0 clamp(20px, 11.43%, 64px);
  white-space: normal;
  line-height: 1.45;
}
.frame-line { display: block; }

/* 左下那 3 颗思考圆泡（.dots）已按产品决定去掉（2026-09-27）：
   雕花框只有亮/暗两版、本身不随 type 变色，去了圆泡后气泡内不再有任何随维度变色的元素。 */

/* §7 与 §8.1 的几何冲突（已记入 AGENTS.md 事故台账 #1、#9）：
   问题上限由 24 字放宽到 40 字后，原先 320×112 的云朵气泡彻底装不下，改为 560×196。
   气泡换成雕花框后尺寸不变，仍由 app.js 的 fitFrameText() 在 15→12px 之间按实测高度降档兜底；
   短问题因此仍是 §8.1 规定的 15px，只有超长问题才降档。 */

/* ========== 牌 ========== */

#card-area {
  position: relative;
  /* 牌高 254 + 余量：单排时不需要更高，两排由 fitCardAreaHeight() 动态撑开 */
  min-height: 266px;
}

.card {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--card-w);
  height: var(--card-h);
  /* 牌是 grid 容器：grid 默认 min-width/min-height 为 auto，内容（长 label + padding）
     会把轨道撑破，实测 208×254 被撑成 411×388，牌互相重叠、右侧溢出看不全。
     这里锁死尺寸，让内容在牌内部自行换行/滚动，而不是反过来撑大牌。 */
  min-width: 0;
  min-height: 0;
  max-width: var(--card-w);
  max-height: var(--card-h);
  overflow: hidden;
  border-radius: 12px;
  background-color: var(--surface);
  /* 图片牌面（需求变更：作废 C9）。四花色 × 亮暗共 8 张，由 data-type + 主题选择；
     加载失败时 CSS 的 background-color 与下方 .card--nobg 回退保证不白屏。 */
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border: 1px solid transparent;
  box-shadow: var(--card-shadow);
  /* 文字内缩到牌面图的中央留白区（花纹边框约占四周 16–18%）。
     注意：padding 用百分比会按**包含块（#card-area，宽 1136px）**换算，
     0.17×1136≈193px、0.18×1136≈204px，直接把 208px 的牌撑成 411px，
     牌互相重叠、右侧溢出——这正是「答案牌显示不全」的真因。
     改用 em：相对牌自身的字号，与牌宽同步缩放，不受容器宽度影响。
     13px 字号下 3.4em≈44px ≈ 208×21%，2.9em≈38px ≈ 254×15%，落在留白区内。 */
  padding: 2.9em 3.4em;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: 4px;
  color: var(--text);
  text-align: center;
  /* padding 用 em，必须锁定牌自身字号，否则继承 body 的 14px 会让内缩量漂移。
     2026-10-02 多排自适应：牌面缩小时字号跟着缩（`--cs` 由 app.js 的 applyCardSize() 写在
     牌节点上，随节点走、搬层也不丢），否则 padding 会按未缩放的字号把留白区挤空。
     `--cs` 缺省 1 = 满尺寸 13px，桌面档与改造前逐像素相同。 */
  font-size: calc(13px * var(--cs, 1));
  will-change: auto;
}

/* 亮色主题：四花色牌面图 */
.card[data-type="art"]     { background-image: url("./assets/cards/art-light.png"); }
.card[data-type="tech"]    { background-image: url("./assets/cards/tech-light.png"); }
.card[data-type="content"] { background-image: url("./assets/cards/content-light.png"); }
.card[data-type="tone"]    { background-image: url("./assets/cards/tone-light.png"); }

/* 暗色主题：跟随系统或手动切换 */
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) .card[data-type="art"]     { background-image: url("./assets/cards/art-dark.png"); }
  html:not([data-theme="light"]) .card[data-type="tech"]    { background-image: url("./assets/cards/tech-dark.png"); }
  html:not([data-theme="light"]) .card[data-type="content"] { background-image: url("./assets/cards/content-dark.png"); }
  html:not([data-theme="light"]) .card[data-type="tone"]    { background-image: url("./assets/cards/tone-dark.png"); }
}
html[data-theme="dark"] .card[data-type="art"]     { background-image: url("./assets/cards/art-dark.png"); }
html[data-theme="dark"] .card[data-type="tech"]    { background-image: url("./assets/cards/tech-dark.png"); }
html[data-theme="dark"] .card[data-type="content"] { background-image: url("./assets/cards/content-dark.png"); }
html[data-theme="dark"] .card[data-type="tone"]    { background-image: url("./assets/cards/tone-dark.png"); }

/* 牌面图四角自带花色符号，故正常态下文字层不画任何牌角文字；
   .suit-glyph 只在牌面图加载失败的回退路径（.card--nobg）里显示 */
.card .suit {
  font-size: 1em;              /* 1em = 牌自身的 --cs 字号（原 13px），下同 */
  font-weight: 600;
  min-height: 1.23em;          /* 原 16px / 13px */
  display: flex;
  justify-content: flex-end;
  align-items: flex-start;
}
.card .suit .suit-glyph { display: none; }   /* 花色由牌面图表达，隐藏文字花色 */
.card[data-type="art"] .suit { color: var(--art); }
.card[data-type="tech"] .suit { color: var(--tech); }
.card[data-type="content"] .suit { color: var(--content); }
.card[data-type="tone"] .suit { color: var(--tone); }

/* 选项文字：牌面核心内容，居中于留白区，加半透明底衬保证在花纹上也清晰。
   牌缩到 208×254 后，中央留白约 133×168px，15px 的 16 字会折 3 行偏挤，
   故字号降到 13px：每行约 10 字，16 字折 2 行，留白高度绰绰有余。 */
.card .label {
  align-self: center;
  font-size: 1em;
  font-weight: 600;
  line-height: 1.35;
  color: var(--text);
  overflow-wrap: anywhere;
  padding: 0.23em 0.385em;     /* 原 3px 5px @13px */
  border-radius: 0.46em;       /* 原 6px @13px */
  /* 不允许把牌撑大：超长文本在牌内滚动裁切，而不是顶破固定宽高 */
  min-width: 0;
  min-height: 0;
  max-height: 100%;
  overflow: hidden;
}
/* 维度标签：花色已表达维度，默认隐藏；保留节点供无障碍读取 */
.card .foot {
  font-size: 0;
  height: 0;
  overflow: hidden;
}

/* 空白牌：牌面图为对应花色的浅版，文字提示"我要自己写"。
   牌面缩小时输入区与「写好了」按钮一起按 --cs 缩，否则会顶破牌内留白区。 */
.card.blank .label { background: transparent; }
.card.blank .blank-hint {
  font-size: 0.72em;
  line-height: 1.5;
  color: var(--text-weak);
  opacity: 0.82;
  margin: 0.31em 0 0;
  white-space: normal;
  word-break: break-word;
}
.card.blank textarea {
  min-height: calc(60px * var(--cs, 1));
  font-size: 1em;
  padding: 0.31em 0.615em;     /* 原 4px 8px @13px */
  width: 100%;
}
.card.blank .ghost-btn {
  font-size: 1em;
  min-height: 2.77em;          /* 原 36px @13px */
  min-width: 0;
  padding: 0.31em 0.615em;
  gap: 0.31em;
}

/* 牌背：用牌背图（亮/暗） */
.card.back {
  background-image: url("./assets/cards/back-light.png");
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) .card.back { background-image: url("./assets/cards/back-dark.png"); }
}
html[data-theme="dark"] .card.back { background-image: url("./assets/cards/back-dark.png"); }

/* 图片加载失败回退：JS 给 .card 加 .card--nobg，恢复纯色底 + 花色描边，不白屏 */
.card--nobg { background-image: none !important; border-color: var(--divider); }
.card--nobg[data-type="art"]     { border-color: var(--art); }
.card--nobg[data-type="tech"]    { border-color: var(--tech); }
.card--nobg[data-type="content"] { border-color: var(--content); }
.card--nobg[data-type="tone"]    { border-color: var(--tone); }
.card--nobg .suit .suit-glyph { display: inline; }
.card--nobg .foot { font-size: 1em; height: auto; color: var(--text-weak); }

/* 手牌扇形区 */
#hand-area {
  grid-area: hand;
  border-top: 1px solid var(--divider);
  padding-top: 12px;
}
/* 有牌时由 JS 加 .has-cards 撑开，空态只占一行高度，避免撑出大片空白 */
#hand-cards {
  position: relative;
  height: 0;
}
/* 扇形旋转会让牌角下探，留出余量避免压住下面的 #clarify-actions（牌高 254 + 8 偏移 + 40 余量） */
#hand-cards.has-cards { height: 302px; }
#hand-cards .card {
  transform-origin: 50% 120%;
  cursor: pointer;
}

#clarify-actions {
  grid-area: actions;
  display: flex;
  gap: 12px;
  align-items: center;
  border-top: 1px solid var(--divider);
  padding-top: 12px;
}

/* ========== 输出阶段 ========== */

#output {
  max-width: 760px;
  margin: 0 auto;
  padding: 0 0 24px;
}
#output[hidden] { display: none; }

/* 成品提示词大牌（§8.5，2026-09-29 改）：整张卡就是 img/大牌_{亮,暗}.png 那副雕花大牌，
   提示词写在中央空白区。原先是纯色边框盒（border + surface 底 + data-type 四色边），
   雕花是统一红色系后 data-type 染色失去落点，四条规则已删（JS 仍设 dataset.type，无害）。
   尺寸三重钳制：760px 上限（= #output 列宽，牌撑满整列）/ 94vw 窄屏 / 90vh×宽高比（保证整牌
   基本入视口，actions 最多轻微滚动）。2026-09-29 从 640px/86vh 放大一档（用户要求「牌更大」）。
   牌自带圆角与透明边角（RGBA），所以**不能有 box-shadow**（阴影是矩形的，会从圆角外露出），
   也不需要 border-radius。背景 100% 100% 拉伸前提是 aspect-ratio 与素材严格同比。
   **padding 的坑（本轮踩过）**：CSS 百分比 padding 参照的是**父容器**（#output，最宽 760px）
   的宽度，不是牌自身的宽度——按画布占比直接写百分数会把文字区挤小一圈。所以先把宽度存进
   `--bw`，padding 用 `calc(var(--bw) * 系数)` 表达，系数 = 画布 inset ÷ 1852。
   安全区 = 素材内框再让呼吸（2026-09-29 四调：用户仍见文字压雕花，重扫素材发现
   三调的算术错误——上305/下308/左318/右315 全部比实测内框还小 10–12px，等于文字
   四边都嵌进雕花带。本次按 PIL 重扫（qa/_scan_bigcard_frame.py）取两主题交集内框
   上317/下318/左330/右326，再让 **48px** 呼吸（≈760px 渲染宽下的 20px）：
   上 365、下 366、左 378、右 374（画布 px）→ ÷1852 得下面四个系数。
   **改素材必须同步重测**（PIL 扫红色 run，见 qa/_scan_bigcard_frame.py）。 */
#manual-card {
  --bw: min(760px, 94vw, calc(90vh * 1852 / 2264));
  width: var(--bw);
  aspect-ratio: 1852 / 2264;
  margin: 0 auto;
  background: var(--bigcard-image) center / 100% 100% no-repeat;
  display: flex;
  flex-direction: column;
  padding: calc(var(--bw) * 0.1971) calc(var(--bw) * 0.2019)
           calc(var(--bw) * 0.1976) calc(var(--bw) * 0.2041);
}

#manual-title {
  margin: 0;
  font-size: 20px;
  line-height: 1.35;
}

#manual-text {
  /* 牌面高度由 aspect-ratio 锁死，提示词长就在这块里滚，**不许撑大牌**——
     一撑背景就拉伸变形，雕花全歪。flex:1 + min-height:0 是「滚动而不撑破」的标准写法；
     旧版 max-height:60vh 与新布局无关，已删。 */
  flex: 1;
  min-height: 0;
  margin: 12px 0 0;
  overflow: auto;
  white-space: pre-wrap;
  font-family: var(--font);
  font-size: 14px;            /* 14 → 16（同日「填更满」）→ 14（用户实测 16px 时文字压到雕花，收回来） */
  line-height: 1.6;
}
.uncovered { color: var(--text-weak); margin: 0; }
.uncovered[hidden] { display: none; }

#output-actions {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 16px;
}

/* 确认弹层 */
#confirm-layer {
  position: fixed;
  inset: 0;
  z-index: 35;
  background-color: rgba(0, 0, 0, .32);
  display: grid;
  place-items: center;
}
#confirm-layer[hidden] { display: none; }
/* 确认弹窗的框 = **原来的维多利亚立体浮雕素材**，与问题气泡共用同一张
   `--frame-image`。
   2026-10-01 按产品要求还原：09-30 曾把弹窗与气泡一起换成按钮母版派生的框
   （img/_gen_dialog_frame.py），随后气泡先改回了原素材；现在弹窗也一并改回，
   两者重新共用一张图，`--dialog-frame-image` 变量随之删除。
   素材 dialog-frame-{light,dark}.webp 仍留在 public/assets/cards/ 与 img/ 里追溯
   （与 frame-sprite.webp 同例），只是不再有任何规则引用。
   画法回到 border-image —— 这是弹窗与气泡**唯一该有的差别**：气泡高度固定 196px，
   可以直接整图拉伸；弹窗高度由内容决定，必须用九宫格才能保证雕花不被纵向拉扁。
   切片 `79 87` = 内框线在 1120×373 素材上的像素位置（上下 79 / 左右 87），
   四角原样缩放、四边拉伸、`fill` 铺中央板面。
   边框宽必须写成 `41px 44px` 而**不是** `border: 41px` —— 四条边取切片的一半（素材是 2×），
   左右比上下宽 3px；写成四边同宽会让布局边框比 border-image-width 窄，
   边框图就会往 padding 里压 3px，内容区随之变宽 6px。
   border-style 不能是 none，故写成 transparent 实边框，它同时把布局空间占住
   （全局 `box-sizing: border-box` 下 width 已含这四条边）。
   再叠 padding 把内容退到雕花以内：左右 44+20 = 64px，与 `.frame-text` 的 64px 净空
   对齐，内容宽 560−2×64 = 432px —— 与气泡的内容宽一模一样。
   不写 border-radius / box-shadow / background-color：框外四角是透明抠图，
   矩形底色或阴影会从角外露出来（与 #manual-card 同理）。 */
#confirm-box {
  width: min(560px, calc(100vw - 48px));
  border-width: 41px 44px;
  border-style: solid;
  border-color: transparent;
  border-image: var(--frame-image) 79 87 fill / 41px 44px;
  padding: 16px 20px;
}
/* 各类答案牌的数量（2026-10-02）。
   产品口径：**只列已有答案牌的类**，数量为 0 的整行不出现（一张都没有时整块隐藏）。
   顺序固定为 SUIT / TYPE_LABEL 的键序（艺术设计 / 技术实现 / 内容结构 / 风格调性），
   不按数量重排 —— 否则同一次澄清里数字一变，行的次序就跳。
   放在文案之前、按钮组之前：文案以「现在生成？」收尾，得紧贴按钮。
   颜色用 --text-weak 而不是牌型的四色：#confirm-box 的面板是雕花框的 fill（亮版米白 /
   暗版近黑），四色里的浅色在暗版面板上会糊；明细本来就是次要信息。 */
#confirm-tally {
  list-style: none;
  margin: 0;
  padding: 0;
  color: var(--text-weak);
  font-size: 14px;
  font-variant-numeric: tabular-nums;   /* 两位数时冒号仍对齐 */
}
#confirm-tally[hidden] { display: none; }
/* 按钮组本来就贴内容区右边界，那正好落在右下角叶的尖端上（间隙 3px）；
   往内收 8px 让开。 */
.confirm-actions {
  display: flex;
  gap: 12px;
  justify-content: flex-end;
  margin-top: 16px;
  padding-right: 8px;
}

/* ========== §8.6 响应式 ========== */

/* 2026-10-02：原 C6「触摸设备竖向弹『请横屏使用』遮罩」已按产品要求取消，
   方向遮罩的节点与三条规则（默认 display:none + 三条件媒体查询 + 内部排版）一并删除。
   竖屏不再被拦：答案牌与手牌都按可用宽度**分行**排布（app.js 的 cardMetrics / renderHand），
   窄屏把一批牌全部摆进桌面区，不需要转屏。因此这里也不再有屏幕方向相关的媒体查询，
   更没有 screen.orientation.lock()（iOS 不支持且会静默失败，这条禁令本身保留）。 */

/* 窄屏（手机竖屏为主，也覆盖桌面窄窗口）：版心与行距收一点，把省下的高度让给牌。
   三行一列的单列布局本来就是默认值（§8.6），这里不再重复声明。 */
@media (max-width: 1023px) {
  :root {
    /* 只收版心与末尾留白。--topbar-h 不动：它是 #clarify 算 100vh 的减项，
       改小只会让整屏高的阶段算出比实际更高的 min-height、平白多出一截滚动。 */
    --inset-x: 12px;
    --bottom-pad: 16px;
  }
  #clarify { gap: 10px; }
  #hand-area { padding-top: 8px; }
  #clarify-actions {
    padding-top: 8px;
    gap: 8px;
    /* 两个按钮图合计 236px，320px 视口减去左右版心也放得下，但仍允许换行兜底 */
    flex-wrap: wrap;
  }
}

/* ========== C8 减弱动效 ========== */

@media (prefers-reduced-motion: reduce) {
  html.theme-fading,
  html.theme-fading body,
  html.theme-fading .card,
  html.theme-fading .frame-wrap,
  html.theme-fading input,
  html.theme-fading textarea,
  html.theme-fading button {
    transition: none;
  }
}

/* ========== §8.4 转场：首页牌盒开盒动画 ==========
   图层是固定覆盖层，底色跟主题走（动画画布本身透明，没有大理石底）。
   #box-stage 用 transform 摆位：#deck-box 的实测矩形 → 拉远后的终态。 */

#box-transition {
  position: fixed;
  inset: 0;
  z-index: 60;
  overflow: hidden;
  background-color: var(--bg);
  /* 与根背景逐像素同相位，否则覆盖层一出现就会看到壁纸跳一格 */
  background-image: var(--bg-image);
  background-repeat: repeat;

  /* 转场期间（含等 AI）鼠标显示加载中；hidden 后自动恢复 */
  cursor: wait;
}

/* 转场期间（含等 AI）整个覆盖层及内部所有元素强制加载光标 */
#box-transition, #box-transition * { cursor: wait !important; }
#box-transition[hidden] { display: none; }
/* 淡出用；出现时不加 .out，直接不透明，避免首页与澄清阶段之间闪一帧 */
#box-transition.out { opacity: 0; transition: opacity 240ms linear; }

/* ========== §8.4 拼版层：手牌 → 成品卡 ==========
   点【生成提示词】后先**什么都不发生**：手牌留在手牌区，等待秀在上面播（见 #wait-stage）。
   成文返回的那一刻，手牌被原地搬进这一层（固定铺满视口，所以层内坐标就是视口坐标），
   再由 assembleIntoCard() 逐张浮到成品卡的矩形里拼出它的版式、随后散去。
   搬层是必需的：#clarify 紧接着要整块收起，牌不能跟着那棵树一起消失。
   #stack-stage .card 的 transform-origin 必须与 #hand-cards .card 逐字一致：
   搬容器时按 FLIP 补的那次位移是拿屏幕矩形算的，旋转中心一变，牌就会跳一下。 */
#stack-stage {
  position: fixed;
  inset: 0;
  z-index: 40;
  /* 不拦点击，也不弹牌上那句 title 提示——此时这些牌已经在飞走了 */
  pointer-events: none;
}
#stack-stage[hidden] { display: none; }
#stack-stage .card { transform-origin: 50% 120%; }

/* 拼版期间（`assembleIntoCard()` 给这一层加上 .mosaic）去掉牌自己的投影。
   成品卡本身**没有**投影（#manual-card 不加 box-shadow），拼版要「正在变成那张卡」，
   带着一圈投影就不像了 —— 而且在亮色主题下，10% 黑的投影压在两块牌的交界处会显出一道
   灰缝，「边贴边」立刻破功（暗色主题下壁纸本身就是 2–6 级灰，投影看不出来，只有亮色主题
   会暴露）。这不是动画，是瞬时切换的类，不违反 C7。
   类由 liftHandToStage() 每次开场时清掉，运行到一半关掉页面也不会带到下一轮。 */
#stack-stage.mosaic .card { box-shadow: none; }

/* 弹跳牌层（§8.4）：**已下线，2026-10-01 起不在流程里**（成文等待改播等待秀）。
   规则与节点都留着供回退，详见 index.html 里 #bounce-stage 的注释与 app.js §13.5。
   z-index 30 低于 #stack-stage(40) —— 叠好的那摞牌压在弹跳牌之上，不会被拖尾盖掉。
   素材是横版 1600×900 `cover`：铺满整个视口，不留侧边空条。background-image 仍留页面
   壁纸 —— `cover` 下它照不到，但素材取不到时这一层不会露出底下的牌摞/问题框。 */
#bounce-stage {
  position: fixed;
  inset: 0;
  z-index: 30;
  overflow: hidden;
  pointer-events: none;
  background-image: var(--bg-image);
  background-size: cover;
  background-position: center;
}
#bounce-stage[hidden] { display: none; }
/* 素材走 <img> 引用、不内联（app.js §13.4b：内联时近 2000 个元素的无限循环动画会把主线程
   占掉 3.6s/3s、DOM 涨到 4558 个节点）。
   `object-fit: cover` + `object-position: center bottom`：画布 16:9，与所有常见桌面视口的
   宽高比同比或更扁，所以**只裁左右、永不裁上下**（事故台账 #16 实测三视口）；超宽屏
   (>16:9) 才会裁上下，那时 `bottom` 把裁切全放到顶部，地板线不被切掉。
   少了 object-fit 这一行，`<img>` 默认的 `fill` 会把 1600×900 的牌拉变形 —— 不是装饰。
   注：竖版 900×1200 素材配 `contain` 的旧写法已作废（横屏下左右各留一条壁纸空条）。 */
#bounce-stage .anim-art {
  display: block; width: 100%; height: 100%;
  object-fit: cover; object-position: center bottom;
}
#bounce-stage.out { opacity: 0; transition: opacity 320ms linear; }

/* 等待秀层（§8.4）：**两处等待共用**——池空等模型出题、以及点【生成提示词】后等成文
   （2026-10-01 起后者也用它，原先那段是全屏弹跳牌）。位置刻意不变：成文等待期间手牌
   留在手牌区，同一块地方播同一段表演，观感上就是「同一件事在继续」。
   **挂在 #table 内、absolute 定位**（2026-09-29 晚五改，用户两问拍板）：
   ① 「跟随页面滑动」——原先 fixed 全屏不随文档滚，页面一动它留在原地像贴片；
   ② 「不要到手牌区」——94vh 居中纵向占屏 3%–97%，把下方手牌整个盖住。
   现在 bottom:0 对齐 #table 底边（= 手牌区上沿），向上伸展最高 min(80vh, 780px)，
   **结构性不会碰到手牌**，与页面一起滚动。播它时旧问题框已收走、牌区已清空
   （advance() 的 hideBubble → clearCardArea 之后），这块桌面正空着。
   overflow: hidden 是段间转场（±7° 旋转、±8% 平移）不溢出的前提。 */
#wait-stage {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: min(80vh, 780px);
  z-index: 20;                /* 低于 #bounce-stage(30) 与 #stack-stage(40)：只是等待期表演 */
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  /* 舞台本身不拦点击：它盖着的桌面这时正好空着，但保持穿透，免得将来桌上放回什么
     就点不到了。**悬浮反馈不在这里做**——它在下面 #wait-art 上单独放行（2026-10-01）。 */
  pointer-events: none;
}
#wait-stage[hidden] { display: none; }

#wait-art {
  position: relative;
  /* 高度跟随 #wait-stage 容器（不再自己定 vh）。「做大」的历史：58vh → 82vh → 94vh
     都是 fixed 全屏时代的写法；搬进 #table 后由容器约束，牌有多大只看容器多高。
     **不要再引入 max-height**——上一版 max-height:74vh 曾把 height 悄悄压回去
     （事故台账 #17 附近）。max-width: 88vw 是窄屏兜底：宽被卡住时 SVG（meet）
     只 letterbox 不变形。 */
  height: 100%;
  aspect-ratio: 900 / 1200;   /* 素材画布同比，配套 SVG 用 meet 完整显示 */
  max-width: 88vw;
  opacity: 0;                 /* 入场前不可见；由 GSAP 补到 1（C7：只动 opacity/transform） */
  transform: scale(0.94);

  /* 悬浮反馈（2026-10-01）：等待动画播着时，鼠标悬到**动画这一块**上显示「正在加载…」
     一类的提示，指针同时变加载态。两件事都只落在这一层：
       - pointer-events: auto —— 父层 #wait-stage 是 none，这里单独放行。
         子元素能在 none 的父元素里重新成为命中目标，所以舞台周围的空白仍然穿透。
       - cursor: wait —— 与开盒转场期 #box-transition 同一套（那层也是整块 cursor: wait）。
     提示文案**不写在这里**：等待秀两处共用（等出题 / 等成文），文案不同，
     由 app.js 的 playWaitShow(label) 写进 title（见 WAIT_LABEL_* 常量）。
     opacity: 0 的入场帧同样能悬停 —— 命中测试不看透明度，只是那一下基本没人来得及悬上去。 */
  pointer-events: auto;
  cursor: wait;
}
#wait-art .wait-seg {
  position: absolute;
  inset: 0;
  opacity: 0;                 /* 段间交叉淡入淡出用的两层，同样由 GSAP 接管 */
}
/* 同上：素材走 <img>（app.js §13.4b），`contain` 顶替素材自带的 `meet`。
   #wait-art 的 max-width:88vw 会让段盒比 900:1200 更窄，此时由 contain 决定 letterbox
   方向 —— 与内联时代的 svg 表现一致，不要改成 cover。 */
#wait-art .anim-art { display: block; width: 100%; height: 100%; object-fit: contain; }

#box-stage {
  position: absolute;
  left: 0;
  top: 0;
  width: 720px;   /* 与素材 1:1，画布坐标即 CSS 像素，便于按 #deck-box 实测值摆位 */
  height: 1960px;
  transform-origin: 0 0;
  transition: transform 700ms cubic-bezier(.4, .05, .25, 1);
  will-change: transform;
}
/* 第一段「缩小 + 向下移动」：给画布里的图整体加 0.4 倍缩放，支点取画布底边中点。
   盒底因此留在原处，盒口线从画布 y 880 落到 1528 —— 看上去就是「缩小并向下移」。
   0.4 由「盒身宽 720 → 与一张飞牌同宽 288」反推（牌宽见 img/_gen_cover.py 的 CW，
   新盒口 1528 也是从它算出来的，两边必须一起改）。这一段与 #box-stage 自身的
   700ms 拉远同时起播，两个位移叠成一条曲线。

   只选**直接子级** svg（开盒那一棵）：飞牌 SVG 会被插进来当嵌套 svg（见 .cover-nest），
   不能一起吃这条 transform / transition —— 它自己有一份 1/0.4 的反向缩放。
   盒口那截盒身窗口（.box-face-mouth）同样是嵌套 svg，靠 x/y/width/height 自己定位；
   它要是也吃这条，盒面就会被再缩一次、跑到盒子外面去。

   overflow 放开：飞牌要盖满整个视口，必然画到画布之外，根元素默认 hidden 会把它们裁掉
   （那样只盖得住画布那一小块）。开盒素材全在画布内，放开它对第一段没有副作用；
   真正的裁剪交给 #box-transition 的 overflow:hidden —— 它正好就是视口。 */
#box-stage > svg {
  display: block;
  width: 720px;
  height: 1960px;
  transform-origin: 360px 1960px;
  transition: transform 700ms cubic-bezier(.4, .05, .25, 1);
  will-change: transform;
  overflow: visible;
}

/* 第二段「开盒」比缩放下移晚 0.7s 起播（先缩放下移、再开盒，不并行）。
   SVG 内是 7.4s 无限循环，这里压到 1.2s 播一次；app.js 在 1.3s 冻帧，那时正好走到
   50%，即原时间轴的 50%（整摞牌刚抽出）。
   fill-mode 必须是 both：延迟的这 0.7s 里要停在 0% 的闭合态（盖板 scaleY(0)、双耳
   scaleX(0)、整摞牌藏在盒口以下），否则这段会先露出开着的盖板，与首页那张闭合盒图对不上。 */
#box-stage #lid,
#box-stage #earL,
#box-stage #earR,
#box-stage #deck {
  animation-duration: 3s !important;
  animation-delay: .1s !important;
  animation-iteration-count: 1 !important;
  animation-fill-mode: both !important;
}

@media (prefers-reduced-motion: reduce) {
  #box-stage { transition: none; }
  #box-stage > svg { transition: none; }
  #box-transition.out { transition: none; }
}

/* ========== §8.4 转场第二段：飞牌盖满桌面 ==========
   飞牌 SVG 被 app.js 插进开盒 SVG 里（mountCover），所以它不是独立一层、也没有自己的摆位
   transform：位置、缩放全部继承 #box-stage 那棵 SVG，衔接处没有任何位移可跳。
   盒身正面同时要满足两条相反的要求（飞牌时在牌上、落牌时在牌下），所以被拆成两份
   （app.js 的 splitBoxFace），绘制顺序因此是
     盒盖 → 整摞牌 → 完整盒身 → 飞牌 → 只留盒口那一段的盒身
   起飞时牌埋在盒口里，下半截被上面那份挡住 —— 看着是从盒子里钻出来，而不是从盒子前面
   滑过去；盖住盒口那一块的那张牌一落定，上面那份就撤掉（COVER_MOUTH_MS，判据见 app.js），
   此后落下来的牌盖的就是下面那份完整盒身 —— 不会再有一小块盒面压在牌上。

   两处是嵌套独有的：
   ① 父级 `#box-stage > svg` 上有 `scale(0.4)`，会连飞牌一起缩掉；而飞牌素材的坐标本来就是
      按缩小后的盒口（画布 y 1528）标的 —— 所以 app.js 给牌套一层 `g.cover-scale`，用 SVG 的
      `transform` 属性补 `scale(1 / BOX_SHRINK)`。支点必须与父级同点（画布底边中点），两者精确相消。
      **这一份补偿刻意不写成这里的一条 CSS transform**：CSS 的缩放支点要先由 transform-box /
      transform-origin 解释，而 iOS Safari 上这层补偿实测落空（牌只剩盒面四成宽、盖不满屏；
      桌面与 Chrome 移动模拟全对）。SVG 属性的支点固定是用户坐标系原点，不认 transform-box，
      各引擎一致 —— 所以 `.cover-nest` 这里**只有 overflow**，没有任何 transform。
   ② 牌要盖满整个视口，必然会画到画布之外，嵌套 svg 自己也要放开 overflow —— 否则它作为
      容器会把超出的牌裁掉（父级放开只管到父级那一层）。 */
.cover-nest {
  overflow: visible;
}

/* 铺满那一刻（牌已盖住整屏）把**开盒层**撤掉：不做的话牌沉下去会把冻帧的牌盒重新露出来。
   只撤这棵 SVG 里的盒盖/整摞牌/两份盒身，飞牌那个嵌套 svg 必须留下（牌还要落）。
   这里把它们的 transition 换成 opacity 的——此刻 transform 早已停在终态，不再需要过渡。 */
#box-transition.cover-done #box-stage > svg > :not(.cover-nest) {
  opacity: 0;
  transition: opacity 160ms linear;
}

/* 压在牌上面的那一份盒身（app.js 的 splitBoxFace 把它塞进一个嵌套 <svg> 窗口，默认的
   overflow:hidden 就是那一截裁剪）。盖住盒口那一块的那张牌落定之后它就没用了 —— 撤掉它，
   落在盒口这一条带上的牌才露得出来，否则会有好几秒的一小块盒面压在牌上（用户说的
   「落定的时候牌要能遮住牌盒」）。判据见 app.js 的 COVER_MOUTH_MS。
   只淡出，不碰 x/y/width/height：位置全靠那几个属性，动一下整块盒面就跑偏。 */
#box-transition.mouth-clear .box-face-mouth {
  opacity: 0;
  transition: opacity 240ms linear;
}

/* 放牌前把覆盖层的壁纸底撤掉：此时整屏都被牌盖着，撤底看不出任何变化；等牌一张张落下去，
   露出来的就是牌后面那层已经渲染停当的澄清界面（而不是壁纸）。
   只去 background-image/color，不做淡出——淡出会连牌一起变透明。 */
#box-transition.bare {
  background-image: none;
  background-color: transparent;
}
