/* ==========================================================================
   星禾双语 · 悦见中秋 — 组件样式
   依赖 tokens.css + base.css。命名遵循 BEM-lite（block__element--state）。
   区块顺序：页头 → 主视觉 → 分区通用 → 文牒主体 → 进度 → 灯笼 →
             月宫卡片 → 密钥区 → 海报 → 相册 → 规则弹层 → 分享引导 →
             轻提示 → 页脚
   ========================================================================== */

/* ==========================================================================
   0. 语言态排版修正
   中文用宋体 + 宽字距；切到英文后应改用西文衬线 + 正常字距，
   否则西文会顶着中文的 0.08em 字距、并被中文宋体栈渲染。
   ========================================================================== */
html:lang(en) {
  --font-serif: var(--font-serif-en);
}

html:lang(en) .hero__title,
html:lang(en) .hero__sub,
html:lang(en) .section-head__title,
html:lang(en) .modal__title,
html:lang(en) .palace__name,
html:lang(en) .palace-rule__name,
html:lang(en) .poster-tile__name,
html:lang(en) .rule-block__title,
html:lang(en) .brand__zh {
  letter-spacing: 0.01em;
}

/* ==========================================================================
   0.5 整页背景（theme-1 云锦纹 + 压暗层）
   --------------------------------------------------------------------------
   素材：assets/img/bg/theme-1.webp —— 「深藏青底 + 银白祥云/卷云」的四方连续云锦纹
   （用户 2026-09-22 从两张候选中选定；由 scripts/prep-assets.py 的 BG 段
     裁掉右下角水印 + _seamless() 交叉淡化 + 768² WebP 而成）。

   ⚠️⚠️ 三层叠出来的底色（顺序由下到上）：
       ① body 自身 background-color(--c-bg #0b2136) + 两团柔光
       ② body::before —— theme-1 云纹，**必须压暗**（关键）
       ③ body::after  —— 压暗/晕影层，保证文字压在更暗的区域上

   🔴 为什么 theme-1 不能满不透明度铺：整图均值 RGB(58,94,120)（亮度 ~85）**偏亮**，
      直接铺白字对比度不够；而它本身又是一张高对比云纹，压太狠就糊成一片。
      实测解：**opacity .5 铺 + 压暗层收边** → 纹理看得见、正文对比度仍在 10:1 以上。

   🔴 这里替换掉了原先的「卷云纹暗纹」(pattern/cloud.webp)：theme-1 本身就是云纹，
      两层云纹叠在一起会互相打乱、显得脏。**要恢复旧暗纹，先确认云纹不打架。**

   ⚠️ 两个伪元素都用 z-index:-1 落到 body 背景之上、正文之下；
      同 z-index 下 ::after 在 ::before 之后绘制，所以压暗层正好盖在云纹上面。
      body 自身背景会扩散到 canvas，canvas 背景总是最先绘制，所以 -1 不会被吞掉。
   ========================================================================== */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image: url("../img/bg/theme-1.webp");
  background-repeat: repeat;
  /* 768² 的原图按 ~540px 平铺：云纹大小约为「一朵云 ≈ 100 视口 px」，
     适合 390px 宽的手机；想调云纹疏密只改这一个数。 */
  background-size: 540px auto;
  background-position: center top;
  /* 浓淡总开关：改这一个数即可，不用回头重跑素材 */
  opacity: 0.44;
  pointer-events: none;
}

/* 压暗层：上下两端更沉，中间留亮 —— 页头/页脚区域读字更稳，中间不透出「平」的感觉 */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    linear-gradient(
      180deg,
      rgba(4, 14, 26, 0.62) 0%,
      rgba(4, 14, 26, 0.24) 18%,
      rgba(4, 14, 26, 0.2) 50%,
      rgba(4, 14, 26, 0.34) 82%,
      rgba(4, 14, 26, 0.62) 100%
    ),
    radial-gradient(
      130% 90% at 50% 42%,
      rgba(4, 14, 26, 0) 0%,
      rgba(4, 14, 26, 0.34) 72%,
      rgba(4, 14, 26, 0.58) 100%
    );
}

/* ==========================================================================
   1. 页头
   ========================================================================== */
.site-head {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  /* 藏青玻璃（原为夜绛暗红玻璃）—— 跟随主题换底，取 --c-bg 的色相再压深一点 */
  background: rgba(9, 27, 45, 0.86);
  border-bottom: 1px solid var(--c-hairline);
}

@supports (backdrop-filter: blur(12px)) {
  .site-head {
    background: rgba(9, 27, 45, 0.68);
    backdrop-filter: blur(14px) saturate(140%);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
  }
}

.site-head__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: 58px;
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  line-height: 1.18;
  color: var(--c-text);
}

/* 纯标志（页头）——
   ⚠️ 必须用 **小尺寸简化版** `logo-mark-solid.webp`：原版的细条纹瓣在 ~30px 时
   条纹与缝隙会混成一团灰绿，这里用的是「条纹填实心」的简化版。
   见 scripts/prep-assets.py 的 logo_solidify()。 */
.brand__mark {
  height: 30px;
  width: auto;
  flex: none;
  filter: drop-shadow(0 0 6px rgba(233, 185, 73, 0.3));
}

.brand__text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.brand__zh {
  font-family: var(--font-serif);
  font-size: 1rem;
  letter-spacing: var(--ls-wide);
}

.brand__en {
  font-family: var(--font-sans-en);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  /* ⚠️ 这里**不要** text-transform: uppercase —— 品牌英文名「Singho Global」
     应按给定大小写显示，转成全大写等于改了品牌写法。 */
  color: var(--c-text-faint);
}

.head-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* 语言切换：分段控件 */
.lang-switch {
  display: inline-flex;
  align-items: center;
  padding: 2px;
  border: 1px solid var(--c-border);
  border-radius: var(--r-pill);
  background: rgba(0, 0, 0, 0.18);
}

.lang-switch__opt {
  min-width: 34px;
  min-height: 30px;
  padding-inline: 8px;
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  color: var(--c-text-muted);
  transition: background var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.lang-switch__opt[aria-pressed="true"] {
  background: rgba(233, 185, 73, 0.16);
  color: var(--c-gold);
}

/* 图标按钮 / 图标 + 文字按钮 */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  padding-inline: 10px;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  color: var(--c-text-muted);
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  transition: color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

.icon-btn:hover {
  color: var(--c-gold);
  background: rgba(233, 185, 73, 0.08);
}

.icon-btn__ico {
  width: 18px;
  height: 18px;
  flex: none;
}

.icon-btn__txt {
  display: none;
}

@media (min-width: 400px) {
  .icon-btn__txt {
    display: inline;
  }
}

/* ==========================================================================
   2. 主视觉（活动介绍）
   ========================================================================== */
.hero {
  position: relative;
  padding-block: var(--sp-7) var(--sp-6);
  text-align: center;
  overflow: hidden;
}

/* 帖头收口：主视觉底部一道双细线 + 中央金点。
   原先 hero 与「我的通关文牒」是**硬切**（两块同色区域直接相接），
   加这道收口后首屏读起来像"一页印好的帖"，而不是两个 div 摞在一起。
   用多层 background 一次画完（上细线 / 下淡线 / 中心金点），不占额外元素。 */
.hero::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0;
  translate: -50% 0;
  width: min(86%, 340px);
  height: 9px;
  pointer-events: none;
  background:
    radial-gradient(
        circle at 50% 50%,
        rgba(233, 185, 73, 0.9) 0 2.4px,
        rgba(233, 185, 73, 0) 2.5px
      )
      50% 0 / 100% 100% no-repeat,
    linear-gradient(
        to right,
        rgba(233, 185, 73, 0) 0%,
        rgba(233, 185, 73, 0.34) 16%,
        rgba(233, 185, 73, 0.34) 84%,
        rgba(233, 185, 73, 0) 100%
      )
      50% 0 / 100% 1px no-repeat,
    linear-gradient(
        to right,
        rgba(233, 185, 73, 0) 0%,
        rgba(233, 185, 73, 0.15) 24%,
        rgba(233, 185, 73, 0.15) 76%,
        rgba(233, 185, 73, 0) 100%
      )
      50% 100% / 100% 1px no-repeat;
}

/* 月晕：极淡的暖光，只做背景层。
   ⚠️ 它是 .hero 的直接子元素，但必须保持 absolute —— 不要再写 `.hero > *`
   这类通配规则，否则会把绝对定位覆盖回文档流，撑出整屏空白。 */
.hero__halo {
  position: absolute;
  top: -34%;
  left: 50%;
  width: 132%;
  aspect-ratio: 1;
  translate: -50% 0;
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 50%,
    rgba(245, 217, 139, 0.14) 0%,
    rgba(245, 217, 139, 0.07) 34%,
    rgba(245, 217, 139, 0) 66%
  );
  pointer-events: none;
}

/* 页头月盘：主视觉锚点，嵌在月晕最亮处。
   盘面是**奶白不透明**的（素材自带金边），所以别再给它叠 opacity；
   只补一层向下投的金色柔光，让圆盘像是"浮在月色上"。
   ⚠️ 盘中主体 2026-09-22 由「线描玉兔」换成**品牌标志**（`logo/moon-disc.webp`）——
   标志用的是夜蓝 + 绛红**深色墨**，不是页头/页脚那套暖金：
   奶白盘上金 #E9B949 的对比度只有 1.6:1，会直接看不见。 */
.hero__moon {
  position: relative;
  z-index: 1;
  width: clamp(100px, 30vw, 138px);
  /* 下距要给足：月华环向外 13%（≈18px），再留 24px 会让眉题贴到环上 */
  margin: 0 auto var(--sp-6);
  filter: drop-shadow(0 8px 22px rgba(233, 185, 73, 0.2));
}

/* 月华：盘外一圈更淡的金环。
   原先盘子是"一枚孤零零的贴纸"飘在留白里；补上这圈环后，它读作**月**——
   盘子是月体、环是月华，与标题形成上下关系，而不是又一个居中方块。 */
.hero__moon::after {
  content: "";
  position: absolute;
  inset: -13%;
  border-radius: 50%;
  border: 1px solid rgba(233, 185, 73, 0.2);
  pointer-events: none;
}

.hero__moon img {
  display: block;
  width: 100%;
  height: auto;
}

.hero .container {
  position: relative;
  z-index: 1;
}

/* 眉题：Marcellus + 两侧细引线。
   🔴 去掉了 `text-transform: uppercase` —— 「全大写 + 0.22em 字距的灰色小字」
   是最没信息量的那一招（任何模板都会这么写）。改成原始大小写后，
   它才像一张印刷品上的副题，而不是"落地页眉题"。 */
.hero__eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  font-family: var(--font-serif-en);
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  /* 末字右侧多一个字距 → 补回来 */
  text-indent: 0.16em;
  /* ⚠️ 不能用 --c-text-faint（α .55）：Marcellus 笔画细，压到 0.55 之后
     在深蓝底上基本读不出来（截图里确认过）。这里是**信息**（年份/节名），
     不是纯装饰，必须可读。 */
  color: rgba(201, 183, 156, 0.82);
}

.hero__eyebrow::before,
.hero__eyebrow::after {
  content: "";
  flex: 1 1 0;
  max-width: 58px;
  height: 1px;
  background: linear-gradient(to right, rgba(233, 185, 73, 0), rgba(233, 185, 73, 0.34));
}

.hero__eyebrow::after {
  background: linear-gradient(to left, rgba(233, 185, 73, 0), rgba(233, 185, 73, 0.34));
}

.hero__title {
  margin-top: var(--sp-4);
  font-size: var(--fs-display);
  /* 中文标题字距拉到 0.1em 才"站稳"（原来是 0.06em，偏挤） */
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  /* 深底墨字：一层压低暗部的投影，让字从夜色里浮起来 —— 不是描边 */
  text-shadow: 0 2px 16px rgba(4, 14, 26, 0.72), 0 0 44px rgba(233, 185, 73, 0.1);
}

/* 副题：两侧夹金线，让它从"第二行标题"变成"一方匾额" */
.hero__sub {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  margin-top: var(--sp-3);
  font-size: var(--fs-h2);
  color: var(--c-gold);
  letter-spacing: var(--ls-wider);
}

.hero__sub::before,
.hero__sub::after {
  content: "";
  width: 32px;
  height: 1px;
  flex: none;
  background: linear-gradient(to right, rgba(233, 185, 73, 0), rgba(233, 185, 73, 0.72));
}

.hero__sub::after {
  background: linear-gradient(to left, rgba(233, 185, 73, 0), rgba(233, 185, 73, 0.72));
}

/* 标题组与正文之间的分隔纹样：**嵌套双菱** + 两侧细线。
   ⚠️ 副题两侧已经用了"单线 + 文字"，这里若还用单菱形就会撞符号 ——
      同一个符号在同一屏出现两次、含义还不一样，是"模板感"的主要来源之一。 */
.hero__ornament {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  margin-top: var(--sp-5);
}

.hero__ornament::before,
.hero__ornament::after {
  content: "";
  width: clamp(30px, 13vw, 68px);
  height: 1px;
  background: linear-gradient(to right, rgba(233, 185, 73, 0), rgba(233, 185, 73, 0.42));
}

.hero__ornament::after {
  background: linear-gradient(to left, rgba(233, 185, 73, 0), rgba(233, 185, 73, 0.42));
}

.hero__ornament i {
  position: relative;
  width: 5px;
  height: 5px;
  rotate: 45deg;
  background: var(--c-gold);
  opacity: 0.85;
}

.hero__ornament i::after {
  content: "";
  position: absolute;
  inset: -4.5px;
  border: 1px solid rgba(233, 185, 73, 0.38);
}

.hero__desc {
  margin-top: var(--sp-5);
  max-width: 32em;
  margin-inline: auto;
  font-size: var(--fs-sm);
}

/* 日期 / 地点：合成**一条抬头**，而不是两枚各自换行的胶囊。
   原来 `.hero__meta` 是 flex-wrap + gap，两枚 pill 在 390px 下各占一行，
   看起来像两个孤立的小控件；改成"共享一条基线 + 中间一道竖细线"，
   才读得出这是同一句抬头。 */
/* 日期 / 地点：合成**一条抬头**。
   🔴 踩过的坑：一开始给第二枚 chip 加了 `border-left` 当分隔符，
      但这两项在 390px 下**必然换行**（合计约 436px > 容器 358px）；
      换行后那条竖线就落在第二行的**行首**，看起来像多出一根莫名的竖杠（截图确认）。
      现在不加分隔符，靠"两行居中对齐 + 两个图标不同"读成一组抬头。 */
.hero__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px 18px;
  margin-top: var(--sp-5);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  color: var(--c-text-muted);
  letter-spacing: 0.06em;
  white-space: nowrap;
}

.chip__ico {
  width: 13px;
  height: 13px;
  flex: none;
  color: var(--c-gold);
}

/* ==========================================================================
   3. 分区通用
   --------------------------------------------------------------------------
   2026-09-23 改版：抬头从「◆ 标题 ◆」改成「序号 → 标题 → 小注 → 细金线」。
   为什么改：原先 3 个分区标题**长得一模一样**（两侧各一枚小菱形 + 一行灰字），
   而正文里的菱形装饰（.hero__ornament / .rule-list--dot）也用了同一个符号 ——
   同一个符号出现 8 次、每次含义还不一样，读起来就是"模板"。
   序号由 HTML 的 `data-mark` 提供（壹/贰/叁），改文案不用碰 CSS。
   ========================================================================== */
.section {
  padding-block: var(--sp-7);
}

.section-head {
  text-align: center;
}

.section-head::before {
  content: attr(data-mark);
  display: block;
  margin-bottom: 6px;
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  letter-spacing: 0.3em;
  /* 末字右侧会多出一个字距 → 补回来，序号才真正居中 */
  text-indent: 0.3em;
  color: var(--c-gold);
  opacity: 0.9;
}

/* 将来新加分区若忘了写 data-mark，不要留一段空白 */
.section-head:not([data-mark])::before {
  content: none;
}

.section-head__title {
  font-size: var(--fs-h2);
  letter-spacing: 0.16em;
  text-indent: 0.16em;
}

.section-head__hint {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
}

/* 抬头下的细金线。
   默认**直接可见** —— 生长动画只在 JS 确认可用时才启用（见文件末的
   html.has-reveal 规则）：脚本挂了、或用户要求减少动效，都必须还能看到这条线。 */
.section-head::after {
  content: "";
  display: block;
  width: min(72%, 240px);
  height: 1px;
  margin: var(--sp-4) auto 0;
  background: linear-gradient(
    to right,
    rgba(233, 185, 73, 0),
    rgba(233, 185, 73, 0.55),
    rgba(233, 185, 73, 0)
  );
  transform-origin: 50% 50%;
}

html.has-reveal .section-head::after {
  transform: scaleX(0);
  transition: transform 700ms var(--ease-soft);
}

html.has-reveal .section-head.is-revealed::after {
  transform: scaleX(1);
}

/* ==========================================================================
   4. 通关文牒主体（卷轴卡）
   ========================================================================== */
.scroll-card {
  position: relative;
  margin-top: var(--sp-5);
  padding: var(--sp-6) clamp(20px, 6vw, 30px) var(--sp-7);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  /* 四角回纹：用 8 条 1px 渐变线一次画完（每角一横一竖），不再靠伪元素。
     为什么不用伪元素：只有 ::before/::after 两个，而这里同时需要**四角 + 两根轴头**，
     伪元素要留给轴头（那是"文牒"最关键的实体线索）。
     ⚠️ 位置用 `left/right/top/bottom 10px` 的边距写法，别用 calc(100% - …) ——
        后者会随字号/盒模型变化算错，边距写法永远贴边。 */
  background:
    linear-gradient(rgba(233, 185, 73, 0.5), rgba(233, 185, 73, 0.5)) left 10px top 10px /
      18px 1px no-repeat,
    linear-gradient(rgba(233, 185, 73, 0.5), rgba(233, 185, 73, 0.5)) left 10px top 10px /
      1px 18px no-repeat,
    linear-gradient(rgba(233, 185, 73, 0.5), rgba(233, 185, 73, 0.5)) right 10px top 10px /
      18px 1px no-repeat,
    linear-gradient(rgba(233, 185, 73, 0.5), rgba(233, 185, 73, 0.5)) right 10px top 10px /
      1px 18px no-repeat,
    linear-gradient(rgba(233, 185, 73, 0.5), rgba(233, 185, 73, 0.5)) left 10px bottom 10px /
      18px 1px no-repeat,
    linear-gradient(rgba(233, 185, 73, 0.5), rgba(233, 185, 73, 0.5)) left 10px bottom 10px /
      1px 18px no-repeat,
    linear-gradient(rgba(233, 185, 73, 0.5), rgba(233, 185, 73, 0.5)) right 10px bottom 10px /
      18px 1px no-repeat,
    linear-gradient(rgba(233, 185, 73, 0.5), rgba(233, 185, 73, 0.5)) right 10px bottom 10px /
      1px 18px no-repeat,
    radial-gradient(
      120% 70% at 50% 0%,
      rgba(233, 185, 73, 0.07) 0%,
      rgba(233, 185, 73, 0) 60%
    ),
    var(--c-bg-elev-1);
  box-shadow: var(--shadow-elev);
}

/* 卷轴轴头：左右各一根，各向外探出 5px。
   这是「通关文牒」这个概念**唯一的实体线索** ——
   原先这张卡只是一个圆角 div + 两个角标，概念完全活在文案里。
   一根轴头 = 竖向渐变柱（左亮右暗，做出圆柱的体积）+ 上下两个圆头。 */
.scroll-card::before,
.scroll-card::after {
  content: "";
  position: absolute;
  top: 12px;
  bottom: 12px;
  width: 9px;
  border-radius: var(--r-pill);
  pointer-events: none;
  background:
    radial-gradient(
        circle at 50% 0,
        rgba(233, 185, 73, 0.6) 0 2.1px,
        rgba(233, 185, 73, 0) 2.2px
      )
      50% 0 / 100% 7px no-repeat,
    radial-gradient(
        circle at 50% 100%,
        rgba(233, 185, 73, 0.6) 0 2.1px,
        rgba(233, 185, 73, 0) 2.2px
      )
      50% 100% / 100% 7px no-repeat,
    linear-gradient(
      to right,
      rgba(233, 185, 73, 0.08) 0%,
      rgba(233, 185, 73, 0.4) 34%,
      rgba(233, 185, 73, 0.18) 62%,
      rgba(233, 185, 73, 0.04) 100%
    );
  box-shadow: 0 0 14px rgba(233, 185, 73, 0.14);
}

.scroll-card::before {
  left: -5px;
}

.scroll-card::after {
  right: -5px;
}

/* ==========================================================================
   5. 进度：已收集 N / 5 盏
   ========================================================================== */
.progress {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 4px;
  /* 数字改走自托管展示衬线（Marcellus）——
     这里是全页最显眼的一个数字，用系统默认字形是最大的浪费。 */
  font-family: var(--font-display);
  color: var(--c-gold);
  letter-spacing: var(--ls-wide);
}

.progress__num {
  font-size: clamp(2.4rem, 10vw, 3.1rem);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.progress__sep,
.progress__total,
.progress__unit {
  font-size: 1rem;
  color: var(--c-text-muted);
  letter-spacing: normal;
}

.progress__total {
  font-family: var(--font-display);
  color: var(--c-text);
}

.progress__label {
  margin-top: var(--sp-2);
  text-align: center;
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wider);
  text-indent: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-text-faint);
}

/* 进度轨：把「0 / 5」和下面五盏灯笼**物理连起来**。
   为什么要它：原先大数字是一处孤立的排版，五盏灯是另一排孤立的图形，
   两者其实是同一件事的两种画法，却没有任何视觉关系。
   --lit 由 lantern.js 的 setCount() 写到 .scroll-card 上，取值 0~5 的**纯数字**
   （必须是纯数字：下面 width 的 calc 直接拿它做乘法）。 */
.progress-rail {
  position: relative;
  width: min(72%, 240px);
  height: 8px;
  margin: var(--sp-5) auto 0;
}

.progress-rail::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  margin-top: -0.5px;
  background: rgba(233, 185, 73, 0.2);
}

.progress-rail i {
  position: absolute;
  left: 0;
  top: 50%;
  height: 2px;
  margin-top: -1px;
  width: calc(var(--lit, 0) * 20%);
  background: linear-gradient(to right, rgba(233, 185, 73, 0.45), var(--c-gold));
  transition: width var(--dur-lit) var(--ease-soft);
}

/* 🔴 这里**不能**给填充条加 box-shadow。
   实测：盏数为 0 时填充宽度是 0，但 `box-shadow: 0 0 8px` 依然会围绕这个
   0 宽元素画出一团 8px 的光 —— 看过去就是轨道正中一个莫名其妙的亮点
   （截图里确认过）。光的"有无"没法用 CSS 跟着 width 走，所以干脆不要光。 */

/* 五道刻度 = 五盏灯。
   位置用百分比对齐到每一段的**中心**（10% / 30% / 50% / 70% / 90%）。
   为什么需要刻度：只有一条空线时，用户会把它读成"分隔线"而不是"进度轨"；
   有了五道刻度，它才和上面那排五盏灯笼显式对应。 */
.progress-rail::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(
    circle,
    rgba(233, 185, 73, 0.55) 0 1.5px,
    rgba(233, 185, 73, 0) 1.6px
  );
  background-repeat: no-repeat;
  background-size: 3px 3px;
  background-position: 10% 50%, 30% 50%, 50% 50%, 70% 50%, 90% 50%;
  pointer-events: none;
}

/* ==========================================================================
   6. 灯笼（五盏）
   ========================================================================== */
.lanterns {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: clamp(6px, 2.6vw, 16px);
  /* 上距收窄：进度轨已经承担了"数字 → 灯笼"的过渡，这里再留 32px 会断开 */
  margin-block: var(--sp-4) var(--sp-5);
}

.lanterns__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
}

/* 灯笼：2026-09-21 起改用**用户提供的真实位图**
   （assets/img/lantern/<slug>-off|-on.webp，两态已用同一包围盒对齐，切换不跳）。
   熄灭态靠压暗褪色、点亮态叠一层 -on 并加光晕。
   ⚠️ 不要再写 aspect-ratio —— 位图自带比例，写死会把灯笼顶偏、与底部基线对不齐。 */
.lantern {
  position: relative;
  display: block;
  width: clamp(40px, 12.5vw, 56px);
  flex: none;
}

.lantern img {
  display: block;
  width: 100%;
  height: auto;
}

/* 熄灭态 */
.lantern .is-off {
  filter: grayscale(0.34) brightness(0.8);
  opacity: 0.88;
  transition: opacity var(--dur-base) var(--ease-out);
}

/* 点亮态：叠在上面，默认透明 */
.lantern .is-on {
  position: absolute;
  inset: 0;
  opacity: 0;
  filter: drop-shadow(0 0 10px rgba(233, 185, 73, 0.55))
    drop-shadow(0 0 26px rgba(245, 217, 139, 0.32));
  transition: opacity var(--dur-base) var(--ease-out);
}

.lanterns__item.is-lit .lantern .is-off {
  opacity: 0;
}

.lanterns__item.is-lit .lantern .is-on {
  opacity: 1;
}

/* 地面光晕：点亮时灯笼下方一小片暖光。
   原来只有"灯笼自己放大 + 发光"一个动作，读完没有落点；
   补上地面光后才有"灯亮在案上"的实体感（幅度刻意做小，宁可弱不可糊）。 */
.lantern::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 86%;
  width: 156%;
  aspect-ratio: 1 / 0.4;
  translate: -50% 0;
  border-radius: 50%;
  background: radial-gradient(
    ellipse at 50% 50%,
    rgba(245, 217, 139, 0.5) 0%,
    rgba(245, 217, 139, 0.16) 46%,
    rgba(245, 217, 139, 0) 74%
  );
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-lit) var(--ease-out);
}

.lanterns__item.is-lit .lantern::after {
  opacity: 1;
}

/* 灯笼下的序号标签 */
.lanterns__idx {
  font-family: var(--font-display);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--c-text-faint);
  transition: color var(--dur-base) var(--ease-out);
}

.lanterns__item.is-lit .lanterns__idx {
  color: var(--c-gold);
}

@keyframes lantern-lit-in {
  0% {
    transform: scale(0.86);
    opacity: 0.5;
  }
  55% {
    transform: scale(1.06);
    opacity: 1;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.lantern.is-lighting {
  animation: lantern-lit-in var(--dur-lit) var(--ease-soft) both;
}

/* ==========================================================================
   7. 月宫卡片
   --------------------------------------------------------------------------
   2026-09-22 改版（用户拍板）：**横向紧凑条**，一屏能看到 5 个月宫。
   三类素材各就各位：
     · 月宫插画 → `.palace__art` 压暗当卡片**底纹**（不撑高卡片）
     · 月宫徽记 → `.palace__seal` 当**通关印记**：未点亮=虚章，点亮=实章
     · 灯笼位图 → `.palace__lamp` 卡片右侧的亮/灭主体
   插画接近奶白，深底上直接放会很刺眼 → 必须压到很低的 opacity，
   点亮时只「亮一档」，跟灯笼的光呼应。
   改 opacity 若发现发灰显脏，可换 `mix-blend-mode: soft-light`。
   ========================================================================== */
.palace-list {
  display: grid;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}

.palace {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 9px;
  padding: 12px;
  /* 原先用 --c-hairline（α .08，偏白）：深蓝底上基本看不见，5 张卡像糊在一起。
     换成暖金 α .1 —— 与卡片点亮后的金边同一色系，未点亮也不会抢戏。 */
  border: 1px solid rgba(233, 185, 73, 0.1);
  border-radius: var(--r-md);
  background: rgba(0, 0, 0, 0.22);
  overflow: hidden;
  /* 建立层叠上下文，好让底纹的 z-index:-1 只在本卡片内生效 */
  isolation: isolate;
  transition: border-color var(--dur-base) var(--ease-out),
    background var(--dur-base) var(--ease-out);
}

.palace.is-lit {
  border-color: rgba(233, 185, 73, 0.32);
  background: rgba(233, 185, 73, 0.06);
}

/* 插画底纹层：铺满卡片、只作背景；图比卡片高（150%）→ 底部出血被裁，
   看起来像一个从右下角透出来的暗水印，而不是一张缩略图。
   🔴 必须加 mask 让它在左侧淡出：否则插画会压到月宫名与简介下面，
      浅色建筑 + 灰字的对比度不够，文字变得很难读（实测就是这个问题）。
   这样插画只出现在右下角（灯笼那一侧），文字区保持干净。
   ⚠️ 也必须给尺寸：绝对定位元素没有内容时宽度为 0，background 会完全不显示。 */
.palace__art {
  position: absolute;
  inset: 0;
  background-repeat: no-repeat;
  background-position: 100% 100%;
  background-size: auto 150%;
  /* 🔴 插画是**接近奶白**的：光调 opacity 不够 —— 白色 ×16% 叠在深底上
     仍然是一片明显的灰（亮度 ~83 vs 底色 ~53），看着像"褪色的插图"而不是暗纹。
     所以要先把白压下去（brightness），再叠 opacity。 */
  opacity: 0.1;
  filter: brightness(0.7) saturate(0.55);
  pointer-events: none;
  z-index: -1;
  -webkit-mask-image: linear-gradient(
    to left,
    #000 0%,
    rgba(0, 0, 0, 0.4) 20%,
    transparent 44%
  );
  mask-image: linear-gradient(
    to left,
    #000 0%,
    rgba(0, 0, 0, 0.4) 20%,
    transparent 44%
  );
  transition: opacity var(--dur-slow) var(--ease-out);
}

/* 点亮后只「亮一档」 */
.palace.is-lit .palace__art {
  opacity: 0.17;
  filter: brightness(0.78) saturate(0.72);
}

/* 徽记印章：虚章 → 实章 */
/* 通关印记 · 虚章：未点亮时必须读作「一枚还没盖上的空印」。
   🔴 原先是 `saturate(.18) brightness(1.08)` + α.42 —— 渲染出来是一团**脏灰圆盘**，
      既不像印章也不像装饰，是整张卡最难看的一块（放大截图确认过）。
      改成「去色 + 提亮 + 压淡」的干净幽灵，再补一圈**虚线圆环**当印框：
      「虚线 = 尚未生效」是印章语言的通用写法，比加文字说明省地方。 */
.palace__seal {
  width: 38px;
  height: 38px;
  flex: none;
  border-radius: 50%;
  opacity: 0.34;
  filter: grayscale(1) brightness(1.7);
  outline: 1px dashed rgba(233, 185, 73, 0.32);
  outline-offset: 4px;
  transition: opacity var(--dur-slow) var(--ease-out),
    filter var(--dur-slow) var(--ease-out),
    outline-color var(--dur-slow) var(--ease-out);
}

.palace.is-lit .palace__seal {
  opacity: 1;
  /* 🔴 徽记实物是「深蓝夜色圆盘 + 楼阁 + 双玉兔 + 桂花枝」的场景徽章 —— 直接放原色
     会在暖金主色里跳出一个**亮蓝圆盘**。这里把它「**烫成金印**」：
     先用 sepia(1) 丢掉蓝、压成单色暖调，再靠 saturate 拉回金、brightness/contrast 提亮，
     最后叠一点暖光晕，让「实章」读起来像盖上去的金印。 */
  filter: sepia(1) saturate(2.6) hue-rotate(-12deg) brightness(1.12) contrast(1.04)
    drop-shadow(0 0 7px rgba(233, 185, 73, 0.4));
  outline-style: solid;
  outline-color: rgba(233, 185, 73, 0.5);
}

.palace__name {
  font-family: var(--font-serif);
  font-size: 1rem;
  letter-spacing: var(--ls-wide);
  display: flex;
  align-items: center;
  gap: 7px;
}

/* 名称文字要能被压缩换行，否则长名字会把徽记/灯笼挤变形 */
.palace__name span {
  min-width: 0;
}

/* 序号 */
.palace__order {
  display: inline-grid;
  place-items: center;
  width: 17px;
  height: 17px;
  flex: none;
  border-radius: 50%;
  border: 1px solid var(--c-border);
  font-family: var(--font-display);
  font-size: 0.62rem;
  color: var(--c-text-faint);
  letter-spacing: 0;
}

.palace.is-lit .palace__order {
  border-color: var(--c-border-strong);
  color: var(--c-gold);
}

/* ⚠️ 字号从 --fs-sm(14px) 连降两级到 0.75rem(12px)，并且把卡片的
   内边距/间距/徽记/灯笼一起收窄，**留出足够横向余量**，让最长的一条
   slogan（「月宫蟾宫,寻谜探趣,解锁中秋智慧」16 字）稳定单行。
   只降字号不腾地方是治标：实测 12.5px 时它仍然断成「…中秋智 / 慧」，
   单字孤行比字略小难看得多。真遇到更长的句子，整齐换行也不会难看。 */
.palace__slogan {
  margin-top: 3px;
  font-size: 0.75rem;
  line-height: 1.55;
  color: var(--c-text-muted);
}

.palace__meta {
  margin-top: 6px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  color: var(--c-text-faint);
}

.palace__host {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.palace__host::before {
  content: "";
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.7;
}

/* 「通关印记」小签：徽记是图案，这里是文字状态。
   虚线边框原先用 --c-hairline（α .08，等于看不见），改成暖金 α .28 ——
   「虚线 = 未生效」这条语言才立得住，也与徽记外圈那圈虚线呼应。 */
.palace__stamp {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border-radius: var(--r-pill);
  border: 1px dashed rgba(233, 185, 73, 0.28);
  font-size: var(--fs-xs);
  white-space: nowrap;
  color: var(--c-text-muted);
  transition: color var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out);
}

.palace__stamp svg {
  width: 12px;
  height: 12px;
}

.palace.is-lit .palace__stamp {
  border-style: solid;
  border-color: rgba(233, 185, 73, 0.5);
  color: var(--c-gold);
}

/* 卡片右侧的点灯主体 */
.palace__lamp {
  position: relative;
  width: clamp(28px, 8vw, 34px);
  flex: none;
}

.palace__lamp img {
  display: block;
  width: 100%;
  height: auto;
}

.palace__lamp .is-off {
  filter: grayscale(0.34) brightness(0.8);
  opacity: 0.88;
  transition: opacity var(--dur-base) var(--ease-out);
}

.palace__lamp .is-on {
  position: absolute;
  inset: 0;
  opacity: 0;
  filter: drop-shadow(0 0 10px rgba(233, 185, 73, 0.55))
    drop-shadow(0 0 22px rgba(245, 217, 139, 0.3));
  transition: opacity var(--dur-base) var(--ease-out);
}

.palace.is-lit .palace__lamp .is-off {
  opacity: 0;
}

.palace.is-lit .palace__lamp .is-on {
  opacity: 1;
}

/* 通关判定（卡片展开区，抽屉内文本） */
.palace__condition {
  grid-column: 2 / -1;
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: 1px dashed var(--c-hairline);
  font-size: var(--fs-xs);
  color: var(--c-text-muted);
}

/* ==========================================================================
   8. 密钥输入区
   ========================================================================== */
.keypad {
  margin-top: var(--sp-6);
  padding: var(--sp-5) var(--sp-4);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  background: rgba(0, 0, 0, 0.2);
  text-align: center;
}

.keypad__label {
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-text-faint);
}

/* 六位密钥位 */
.keypad__slots {
  display: flex;
  justify-content: center;
  gap: clamp(6px, 2.4vw, 10px);
  margin-block: var(--sp-4) var(--sp-3);
}

.key-slot {
  width: clamp(34px, 11vw, 44px);
  aspect-ratio: 0.86;
  display: grid;
  place-items: center;
  border: 1px solid var(--c-border);
  border-radius: var(--r-sm);
  background: rgba(0, 0, 0, 0.22);
  font-family: var(--font-sans-en);
  font-size: 1.05rem;
  letter-spacing: 0;
  color: var(--c-text);
}

.key-slot.is-filled {
  border-color: var(--c-border-strong);
  background: rgba(233, 185, 73, 0.08);
  color: var(--c-gold);
}

.key-slot.is-active {
  border-color: var(--c-gold);
  box-shadow: 0 0 0 1px rgba(233, 185, 73, 0.35);
}

.key-slot__caret {
  width: 2px;
  height: 18px;
  background: var(--c-gold);
  animation: caret-blink 1.1s steps(1, end) infinite;
}

@keyframes caret-blink {
  0%,
  49% {
    opacity: 1;
  }
  50%,
  100% {
    opacity: 0;
  }
}

.keypad__note {
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
}

/* 按钮 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--tap-min);
  padding-inline: clamp(18px, 6vw, 28px);
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  letter-spacing: 0.04em;
  transition: transform var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.btn:active {
  transform: scale(0.97);
}

.btn--primary {
  background: linear-gradient(160deg, #e9b949, #d09a2c);
  color: var(--c-text-on-gold);
  font-weight: 500;
  box-shadow: 0 6px 18px rgba(233, 185, 73, 0.22);
}

.btn--primary:hover {
  background: linear-gradient(160deg, #f5d98b, #e0a936);
}

.btn--ghost {
  border: 1px solid var(--c-border);
  color: var(--c-text-muted);
  /* theme-1 云纹很「忙」，纯描边按钮会糊进纹样里 → 垫一层藏青底把它托成「一个面」 */
  background: rgba(6, 20, 34, 0.42);
}

.btn--ghost:hover {
  border-color: var(--c-border-strong);
  color: var(--c-gold);
}

.btn--block {
  width: 100%;
}

.keypad__btn {
  margin-top: var(--sp-4);
}

/* ==========================================================================
   9. 海报
   ========================================================================== */
/* 🔴 2026-09-23 改版（一轮）：2 列 → 3 列。
   2 列的代价是 5 格排 3 行 → **末行孤 1 格**，且 #poster 高达 1320px（1.6 屏）。
   一轮改 3 列后两行排满，分区高度降到约 800px。

   🔴 2026-09-23 改版（二轮）：grid 3 列 → **flex 3 列/行 + 末行居中**。
   一轮之后仍有洞：5 格排 3 列 = 3 + 2，**末行右侧空着一整个格**
   （截图 `reference/_review/shots-after-fd2/03-poster.png` 右下角，约 114px 宽的空洞）。
   5 套模板数量固定，排不出「无洞」的等宽网格（5 列则每格仅约 65px，画框细节就糊了），
   所以取「末行居中」——视觉上是一块 3-2 居中砖排，而不是「少了一格」。
   用 flex 而非 6 列 grid 的原因：6 列会多出 3 条列间距（40px vs 16px），
   格子被压到约 106px；flex 的 flex-basis 直接算 (100% - 2×gap)/3 = 114px，与一轮完全一致。
   ⚠️ 那个 −0.5px 是防子像素舍入把第 3 格挤到下一行：flex-wrap 按 flex-basis 断行，
   不会自动收缩，宁可每格少半像素也不能让首行变成 2 格。 */
.poster-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2);
  margin-top: var(--sp-5);
}

/* 格子外层是 <li>（列表语义），真正可点的是里面的 <button> */
.poster-cell {
  display: block;
  flex: 0 0 calc((100% - var(--sp-2) * 2) / 3 - 0.5px);
  max-width: calc((100% - var(--sp-2) * 2) / 3 - 0.5px);
  min-width: 0;
}

.poster-tile {
  width: 100%;
  /* 元素从 <li> 换成 <button> 后要把浏览器默认样式清干净 */
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  color: inherit;
  text-align: center;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px 6px;
  border: 1px solid var(--c-hairline);
  border-radius: var(--r-md);
  background: rgba(0, 0, 0, 0.14);
  transition: border-color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out);
}

/* 选中态：金边 + 暖金薄底（与灯笼点亮同一套语言）。
   3 列后格子变小，光靠边框色已经不够醒目 → 补一圈外发光把选中的那格"托出来"。 */
.poster-tile.is-active {
  border-color: var(--c-gold);
  background: rgba(233, 185, 73, 0.1);
  box-shadow: 0 0 0 1px rgba(233, 185, 73, 0.32), 0 8px 20px rgba(0, 0, 0, 0.32);
}

.poster-tile.is-active .poster-tile__name {
  color: var(--c-gold);
}

.poster-tile:hover {
  border-color: var(--c-border-strong);
  background: rgba(233, 185, 73, 0.06);
}

/* 模板缩略：用 CSS 勾出五种外形 */
.poster-tile__thumb {
  position: relative;
  width: 100%;
  /* 🔴 挑选样式时统一展示**竖屏 3比4**那一档（用户 2026-09-24 定），
     5 个尺寸的设计完全一样、只有比例不同，所以只给用户看一个。
     改这里必须同时改 prep-poster-frames.py 的 SHOW_SIZE。 */
  aspect-ratio: 3 / 4;
  border: 1px solid var(--c-border);
  background: linear-gradient(
    170deg,
    rgba(233, 185, 73, 0.13),
    rgba(233, 185, 73, 0.03)
  );
  overflow: hidden;
}

.poster-tile__thumb::after {
  content: "";
  position: absolute;
  inset: 12% 14%;
  border: 1px solid rgba(233, 185, 73, 0.3);
}

/* 真实底图缩略：底图自身已有完整构图与细金边，
   不能再叠 CSS 画的内框，否则两套边框会打架。 */
.poster-tile__thumb--real {
  background-size: cover;
  background-position: center;
  background-color: rgba(0, 0, 0, 0.2);
}

.poster-tile__thumb--real::after {
  display: none;
}

.poster-tile__thumb[data-shape="circle"],
.poster-tile__thumb[data-shape="circle"]::after {
  border-radius: 50%;
}

.poster-tile__thumb[data-shape="circle"]::after {
  inset: 22%;
}

.poster-tile__thumb[data-shape="fan"] {
  border-radius: 50% 50% 8px 8px / 74% 74% 8px 8px;
  clip-path: polygon(50% 100%, 6% 26%, 50% 0%, 94% 26%);
}

.poster-tile__thumb[data-shape="lantern"],
.poster-tile__thumb[data-shape="lantern"]::after {
  border-radius: 46% 46% 44% 44% / 38% 38% 56% 56%;
}

.poster-tile__thumb[data-shape="lantern"]::after {
  inset: 16% 20%;
}

.poster-tile__thumb[data-shape="scroll"] {
  border-radius: var(--r-sm);
  clip-path: polygon(
    0 4%,
    100% 0,
    100% 96%,
    0 100%
  );
}

.poster-tile__thumb[data-shape="lattice"] {
  border-radius: var(--r-sm);
  background-image: linear-gradient(
      90deg,
      rgba(233, 185, 73, 0.14) 1px,
      transparent 1px
    ),
    linear-gradient(rgba(233, 185, 73, 0.14) 1px, transparent 1px);
  background-size: 12px 12px;
}

.poster-tile__thumb[data-shape="lattice"]::after {
  border-radius: 50%;
  inset: 26% 22%;
  background: rgba(233, 185, 73, 0.06);
}

.poster-tile__name {
  font-family: var(--font-serif);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--c-text-muted);
  text-align: center;
}

/* ── 合成流程（M5）：选照片 → 生成 → 长按保存 ── */
.poster-flow {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--c-hairline);
}

.poster-flow__hint {
  margin: 0;
  /* 🔴 原 14px + 满宽：整句会断成「…画框中间的留 / 白里」，末行只剩两个字，
     比缩小字号难看。现在 12.5px + 限宽 16em → 稳定两行、断点均衡；
     同时把颜色从 --c-text-faint 提到 --c-text-muted（它是**操作说明**，
     不是装饰，不该低到读不清）。 */
  max-width: 16em;
  margin-inline: auto;
  font-size: var(--fs-xs);
  line-height: 1.65;
  color: var(--c-text-muted);
  text-align: center;
}

.poster-flow__actions {
  display: flex;
  gap: var(--sp-3);
}

.poster-flow__actions .btn {
  flex: 1 1 0;
}

/* 生成中：按钮变禁用态之外再压一层「正在…」文案 */
.poster-flow.is-busy .poster-flow__actions .btn {
  opacity: 0.55;
  pointer-events: none;
}

.poster-result {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
}

/* 🔴 产出尺寸随**用户照片比例**变化（1:1 / 3:4 / 9:16 / 4:3 / 16:9 五档自动选），
   所以这里**绝不能写死 aspect-ratio** —— 让位图自身的宽高比决定，只用上限夹住。
   （旧版写死 9/16，改方案后会让 4:3、16:9 的成品被拉变形。） */
.poster-result__img {
  max-width: min(100%, 320px);
  max-height: 58vh;
  width: auto;
  height: auto;
  border-radius: var(--r-sm);
  background: rgba(6, 20, 34, 0.42);
  box-shadow: 0 16px 38px rgba(0, 0, 0, 0.46);
}

.poster-result__tip {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--c-text-muted);
  text-align: center;
}

.poster-result__actions {
  display: flex;
  gap: var(--sp-3);
  width: 100%;
}

.poster-result__actions .btn {
  flex: 1 1 0;
}

/* display:flex + hidden 的组合必须补这条，否则关不掉（同 modal / dialog / toast） */
.poster-flow[hidden],
.poster-result[hidden],
.poster-result__actions .btn[hidden] {
  display: none;
}

/* ==========================================================================
   10. 相册
   ========================================================================== */
.wall {
  columns: 2;
  column-gap: var(--sp-2);
  margin-top: var(--sp-5);
}

@media (min-width: 480px) {
  .wall {
    columns: 3;
  }
}

.wall__item {
  break-inside: avoid;
  margin-bottom: var(--sp-2);
  border-radius: var(--r-sm);
  overflow: hidden;
  /* 管理模式下的撤回按钮（.wall__del）拿它当定位基准。
     普通用户态下这个元素不存在，加 relative 对布局零影响。 */
  position: relative;
  /* 🔴 原先用 --c-hairline（α .08）—— 在深蓝底上等于不可见，
     上传的照片就成了一块**裸图贴在深色背景上**（截图里像"图裂了"）。
     改成暖金细边 + 一层内高光，照片才像"装裱过"。 */
  border: 1px solid rgba(233, 185, 73, 0.22);
  background: rgba(0, 0, 0, 0.3);
  box-shadow: inset 0 0 0 1px rgba(251, 243, 228, 0.04),
    0 8px 20px rgba(0, 0, 0, 0.3);
}

.wall__ph {
  width: 100%;
  display: grid;
  place-items: center;
  background: linear-gradient(
    165deg,
    rgba(233, 185, 73, 0.1),
    rgba(233, 185, 73, 0)
  );
}

.wall__ph svg {
  width: 26px;
  height: 26px;
  color: var(--c-gold);
  opacity: 0.22;
}

.wall__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 7px 9px;
  /* 一道淡金分隔线：把「照片」与「信息」分开，信息不再像压在图上 */
  border-top: 1px solid rgba(233, 185, 73, 0.12);
  font-size: 0.68rem;
  color: var(--c-text-faint);
}

/* 撤回按钮：主理人在现场兜底撤下照片用。
   🔴 三条约束，改动时别踩：
     1. 它**只在管理模式（.wall.is-admin）下被 gallery.js 动态插入** ——
        静态页面上这个元素不存在，因此不影响布局截图与元素数量断言；
     2. 用**文字 + CSS 圆形底**画「×」，不引入任何 <img> ——
        否则会撞上「位图无一加载失败 / 无 src 占位」那两条断言；
     3. 绝对定位 + 26px 圆钮，**不改动列宽**，390px 视口下不会撑出横向滚动。 */
.wall__del {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 2;
  width: 26px;
  height: 26px;
  padding: 0 0 1px;
  display: grid;
  place-items: center;
  border: 1px solid rgba(233, 185, 73, 0.42);
  border-radius: 50%;
  background: rgba(9, 27, 45, 0.86);
  color: var(--c-gold);
  font-size: 17px;
  line-height: 1;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.45);
  transition: background var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    transform var(--dur-fast) var(--ease-out);
}

.wall__del:hover {
  border-color: var(--c-gold);
  background: rgba(233, 185, 73, 0.2);
}

.wall__del:active {
  transform: scale(0.9);
}

.wall__del[disabled] {
  opacity: 0.45;
  cursor: default;
}

/* 双保险：没有 .is-admin 就绝不显示撤回按钮。
   gallery.js 本身只在管理模式才插这个元素，这条是**第二道闸** ——
   万一哪天有人改了渲染顺序、把按钮提前插进来，学生也依然看不到。 */
.wall:not(.is-admin) .wall__del {
  display: none;
}

.wall__upload {
  margin-top: var(--sp-5);
}

/* 上传是相册区**唯一的动作**，不该长成"幽灵按钮"——
   纯描边在云纹底上几乎看不见，与主按钮之间只剩"有边框 / 没边框"的差别。
   给实心藏青底 + 金描边，形成真正的「主-次」关系。 */
.wall__upload .btn {
  border-color: rgba(233, 185, 73, 0.34);
  background: rgba(9, 27, 45, 0.72);
  color: var(--c-text);
}

.wall__upload .btn:hover {
  border-color: var(--c-gold);
  color: var(--c-gold);
}

/* ==========================================================================
   11. 规则弹层（全屏 modal）
   ========================================================================== */
.modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
  background: var(--c-bg);
}

.modal[hidden] {
  display: none;
}

.modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--page-pad);
  min-height: 58px;
  border-bottom: 1px solid var(--c-hairline);
  background: var(--c-bg-elev-1);
  padding-top: max(var(--sp-3), env(safe-area-inset-top));
}

.modal__title {
  font-family: var(--font-serif);
  font-size: var(--fs-h3);
  letter-spacing: var(--ls-wide);
}

.modal__body {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: var(--sp-5) var(--page-pad) var(--sp-8);
  padding-bottom: calc(var(--sp-8) + env(safe-area-inset-bottom));
}

.modal__inner {
  max-width: var(--page-max);
  margin-inline: auto;
  display: grid;
  gap: var(--sp-6);
}

.rule-block__title {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-serif);
  font-size: var(--fs-h3);
  color: var(--c-gold);
  letter-spacing: var(--ls-wide);
}

.rule-block__title::before {
  content: "";
  width: 3px;
  height: 16px;
  border-radius: var(--r-pill);
  background: var(--c-gold);
  opacity: 0.8;
}

.rule-list {
  margin-top: var(--sp-3);
  display: grid;
  gap: var(--sp-2);
  counter-reset: rule;
}

.rule-list li {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-3);
  padding: 10px 12px;
  border-radius: var(--r-sm);
  background: rgba(0, 0, 0, 0.16);
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  line-height: 1.65;
}

.rule-list--num li::before {
  counter-increment: rule;
  content: counter(rule);
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--c-border-strong);
  font-family: var(--font-sans-en);
  font-size: 0.68rem;
  color: var(--c-gold);
  align-self: start;
  margin-top: 2px;
}

.rule-list--dot li::before {
  content: "";
  width: 5px;
  height: 5px;
  rotate: 45deg;
  background: var(--c-gold);
  opacity: 0.6;
  align-self: start;
  margin-top: 8px;
}

/* 弹层内的月宫详情卡
   --------------------------------------------------------------------------
   V2.2 起由「一行 = 名称 + 判定」的扁平表升级为逐宫展开的详情卡，
   因为它要承载 玩法 / 判定 / 小彩蛋 三段内容 —— 一张表塞不下。
   视觉沿用「一纸夜灯帖」：发丝边 + 左侧金线 + 虚线分段，不引入新颜色。 */
.palace-rule {
  margin-top: var(--sp-3);
  display: grid;
  gap: var(--sp-3);
}

.palace-rule__card {
  position: relative;
  padding: var(--sp-4);
  padding-left: calc(var(--sp-4) + 4px);
  border: 1px solid var(--c-hairline);
  border-radius: var(--r-sm);
  background: rgba(0, 0, 0, 0.16);
}

/* 左侧金线：与区头 / 弹层小标题前的金色短杠同源 */
.palace-rule__card::before {
  content: "";
  position: absolute;
  left: 0;
  top: var(--sp-4);
  bottom: var(--sp-4);
  width: 2px;
  border-radius: var(--r-pill);
  background: var(--c-gold);
  opacity: 0.45;
}

.palace-rule__head {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-3);
  align-items: start;
}

.palace-rule__idx {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--c-border-strong);
  font-family: var(--font-sans-en);
  font-size: 0.68rem;
  letter-spacing: normal; /* 别继承中文的 0.08em，两位数会散开 */
  color: var(--c-gold);
}

.palace-rule__name {
  font-family: var(--font-serif);
  font-size: var(--fs-sm);
  letter-spacing: 0.05em;
}

.palace-rule__slogan {
  margin-top: 3px;
  font-size: var(--fs-xs);
  color: var(--c-text-muted);
  line-height: 1.6;
}

.palace-rule__body {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px dashed var(--c-hairline);
}

.palace-rule__body > * + * {
  margin-top: var(--sp-3);
}

.palace-rule__label {
  font-size: var(--fs-xs);
  color: var(--c-gold);
  letter-spacing: 0.04em;
}

/* 标签与它后面那段内容要贴紧，所以把「标签 → 内容」的间距压回小档；
   段落之间仍由 .palace-rule__body > * + * 的 --sp-3 拉开。 */
.palace-rule__label + .palace-rule__list,
.palace-rule__label + .palace-rule__cond {
  margin-top: 6px;
}

.palace-rule__list {
  display: grid;
  gap: 6px;
}

.palace-rule__list li {
  position: relative;
  padding-left: 14px;
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  line-height: 1.65;
}

.palace-rule__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 5px;
  height: 5px;
  rotate: 45deg;
  background: var(--c-gold);
  opacity: 0.55;
}

.palace-rule__cond {
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  line-height: 1.65;
}

/* 小彩蛋 / 小提示：这是唯一带底色的一块，
   因为它「有则加勉、无则跳过」（月宫三就没有），底色让它自成一段而不是漏掉了一行。 */
.palace-rule__tip {
  padding: 8px 10px;
  border-radius: var(--r-sm);
  background: rgba(233, 185, 73, 0.07);
  font-size: var(--fs-xs);
  color: var(--c-text-muted);
  line-height: 1.65;
}

/* ==========================================================================
   12. 分享引导浮层（遮罩 ≤45%）
   ========================================================================== */
/* 位置用绝对定位钉死，不走 flex 流 —— 分享引导必须稳定贴住右上角「···」，
   且落在主标题之前的留白区，避免遮挡主题内容。 */
.share-guide {
  position: fixed;
  inset: 0;
  z-index: var(--z-toast);
  background: rgba(4, 12, 22, 0.45); /* 硬约束：不高于 45% */
}

.share-guide[hidden] {
  display: none;
}

.share-guide__hint {
  position: absolute;
  top: calc(56px + env(safe-area-inset-top));
  right: var(--page-pad);
  max-width: 15em;
  padding: 10px 14px;
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-md);
  background: rgba(74, 34, 38, 0.96);
  font-size: var(--fs-sm);
  color: var(--c-text);
  box-shadow: var(--shadow-elev);
}

@keyframes guide-nudge {
  0%,
  100% {
    transform: translate(0, 0);
  }
  50% {
    transform: translate(5px, -7px);
  }
}

.share-guide__arrow {
  position: absolute;
  top: calc(4px + env(safe-area-inset-top));
  right: calc(var(--page-pad) + 8px);
  width: 34px;
  color: var(--c-gold);
  animation: guide-nudge 1.5s var(--ease-in-out) infinite;
}

/* ==========================================================================
   13. 轻提示
   ========================================================================== */
.toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--sp-7) + env(safe-area-inset-bottom));
  translate: -50% 0;
  z-index: var(--z-toast);
  max-width: min(88vw, 420px);
  padding: 12px 18px;
  border: 1px solid var(--c-border-strong);
  /* 🔴 这里刻意从 --r-pill 改成 --r-md。
     原先主按钮 / 次按钮 / chip / 语言切换 / toast **全是同一个胶囊圆角**，
     等于没有层级；toast 是"通告"不是"按钮"，方一点反而更像提示。 */
  border-radius: var(--r-md);
  background: rgba(74, 34, 38, 0.97);
  font-size: var(--fs-sm);
  text-align: center;
  box-shadow: var(--shadow-elev);
}

.toast[hidden] {
  display: none;
}

.toast--ok {
  border-color: rgba(143, 191, 106, 0.45);
}

.toast--warn {
  border-color: rgba(224, 122, 95, 0.55);
}

/* ==========================================================================
   14. 页脚
   ========================================================================== */
.site-foot {
  margin-top: var(--sp-7);
  padding-block: var(--sp-6) var(--sp-8);
  text-align: center;
  font-size: var(--fs-sm);
}

/* 页脚品牌锁版（标志 + 「星禾双语」标准字）
   用完整版 logo-full（保留条纹瓣的细节）—— 这里宽度 ~150px 足够看清条纹。 */
.site-foot__brand {
  display: flex;
  justify-content: center;
  margin-top: var(--sp-5);
}

.site-foot__brand img {
  width: 150px;
  height: auto;
  filter: drop-shadow(0 0 10px rgba(233, 185, 73, 0.22));
}

.site-foot__org {
  margin-top: var(--sp-5);
}

.site-foot__venue {
  margin-top: var(--sp-1);
  color: var(--c-text-faint);
}

/* ==========================================================================
   15. 样张标注面板（仅预览页使用，不进入正式页面）
   ========================================================================== */
.spec-panel {
  position: fixed;
  right: var(--sp-4);
  bottom: var(--sp-4);
  z-index: var(--z-toast);
  width: min(320px, calc(100vw - 32px));
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-md);
  background: rgba(74, 34, 38, 0.97);
  box-shadow: var(--shadow-elev);
  overflow: hidden;
}

.spec-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: 10px 12px;
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-gold);
  cursor: pointer;
}

.spec-panel__body {
  max-height: 46vh;
  overflow-y: auto;
  padding: 0 12px 14px;
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--c-text-muted);
}

.spec-panel[data-open="false"] {
  width: auto;
}

.spec-panel[data-open="false"] .spec-panel__body {
  display: none;
}

.spec-panel[data-open="false"] .spec-panel__head {
  cursor: pointer;
}

.spec-panel[data-open="false"] .spec-panel__head svg {
  rotate: 45deg;
}

.spec-panel dt {
  margin-top: 10px;
  color: var(--c-text);
  font-weight: 500;
}

.spec-panel dd {
  margin-top: 2px;
}

.spec-panel code {
  font-family: var(--font-sans-en);
  color: var(--c-gold-soft);
}

/* ==========================================================================
   16. 分区占位状态（加载中 / 空 / 失败重试）
   --------------------------------------------------------------------------
   正式页的月宫卡、海报格、相册墙都是异步来的，弱网下必须有个像样的等待态，
   否则现场看到的就是一整片空白。三态共用 .state，只靠修饰类换色。
   容器是 <ul> 时，JS 会自动用 <li> 承载（见 assets/js/ui.js）。
   ========================================================================== */
.state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  padding: var(--sp-6) var(--sp-4);
  border: 1px dashed var(--c-border);
  border-radius: var(--r-md);
  /* 同上：云纹底上必须靠底色把空态框托出来，否则虚线框和文案都看不见 */
  background: rgba(6, 20, 34, 0.42);
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  list-style: none;
}

.state--error {
  border-color: rgba(224, 122, 95, 0.4);
  color: var(--c-text-muted);
}

.state__spin {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid rgba(233, 185, 73, 0.22);
  border-top-color: var(--c-gold);
  animation: state-spin 0.85s linear infinite;
}

@keyframes state-spin {
  to {
    rotate: 360deg;
  }
}

.state__retry {
  margin-top: var(--sp-1);
}

/* 相册墙是 CSS 多栏，占位块要横跨所有栏才像「整块区域在等」 */
.wall .state {
  column-span: all;
}

/* ==========================================================================
   17. 确认弹窗（相册上传前的二次确认）
   --------------------------------------------------------------------------
   相册是完全公开墙，上传确认是唯一的用户侧把关，所以这个弹窗必须存在。
   与 §11 规则弹层的区别：规则是**全屏**阅读型，这里是**居中卡片**确认型。
   ⚠️ display:flex + [hidden] 的组合必须补 [hidden]{display:none}，否则关不掉。
   ========================================================================== */
.dialog {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--page-pad);
  background: var(--c-veil);
}

.dialog[hidden] {
  display: none;
}

@supports (backdrop-filter: blur(3px)) {
  .dialog {
    backdrop-filter: blur(3px);
  }
}

.dialog__inner {
  width: 100%;
  max-width: 340px;
}

.dialog__card {
  display: grid;
  gap: var(--sp-4);
  padding: var(--sp-5);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-lg);
  background: var(--c-bg-elev-2);
  box-shadow: var(--shadow-elev);
}

.dialog__title {
  font-family: var(--font-serif);
  font-size: var(--fs-h3);
  letter-spacing: var(--ls-wide);
}

html:lang(en) .dialog__title {
  letter-spacing: 0.01em;
}

.dialog__msg {
  font-size: var(--fs-sm);
  line-height: 1.65;
  color: var(--c-text-muted);
}

.dialog__field {
  display: grid;
  gap: 6px;
}

.dialog__field-label {
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  color: var(--c-text-faint);
}

/* 🔴 font-size 必须 ≥16px：iOS / 微信里小于 16px 的输入框一旦获得焦点会整页放大 */
.dialog__input {
  min-height: var(--tap-min);
  padding: 0 12px;
  border: 1px solid var(--c-border);
  border-radius: var(--r-sm);
  background: rgba(0, 0, 0, 0.28);
  color: var(--c-text);
  font-size: 16px;
}

.dialog__input::placeholder {
  color: var(--c-text-faint);
}

.dialog__input:focus {
  border-color: var(--c-gold);
  outline: none;
}

.dialog__actions {
  display: flex;
  gap: var(--sp-2);
}

.dialog__actions .btn {
  flex: 1;
}

/* ==========================================================================
   18. 正式页装配补充
   ========================================================================== */

/* 密钥输入：真正接键盘的是一个覆盖在密钥格上的透明 <input>。
   为什么不用「隐藏 1px 输入框 + JS 聚焦」：iOS / 微信里对不可见元素调 focus()
   经常不弹键盘。让它实打实铺在格子上、由用户手指去点，最稳。
   🔴 字号同样必须 ≥16px，否则聚焦瞬间整页被放大。 */
.keypad__field {
  position: relative;
}

.keypad__input {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  font-size: 16px;
  color: transparent;
  caret-color: transparent;
  -webkit-text-fill-color: transparent;
  cursor: text;
}

/* 密钥错误时抖一下（视觉上把「不对」说得比 toast 更快） */
@keyframes keypad-shake {
  0%,
  100% {
    transform: translateX(0);
  }
  20% {
    transform: translateX(-6px);
  }
  40% {
    transform: translateX(5px);
  }
  60% {
    transform: translateX(-3px);
  }
  80% {
    transform: translateX(2px);
  }
}

.keypad__slots.is-error {
  animation: keypad-shake var(--dur-slow) var(--ease-out);
}

.keypad__slots.is-error .key-slot {
  border-color: rgba(224, 122, 95, 0.55);
}

/* 相册真实照片：容器已有 inline aspect-ratio，图铺满并裁切 */
.wall__ph img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.wall__ph.is-broken {
  aspect-ratio: 1 / 1;
  background: rgba(224, 122, 95, 0.08);
}

/* 主视觉月盘的图，避免加载前塌高 */
.hero__moon img {
  aspect-ratio: 1;
  object-fit: contain;
}

/* ==========================================================================
   19. 飘落桂花粒子（assets/js/petals.js 插入的 <canvas class="petals">）
   --------------------------------------------------------------------------
   层级取 40：**飘在内容之上**，但压不过 sticky 页头(50) / 规则弹层(100) /
   轻提示(200) —— 页头和弹窗永远清晰。
   `pointer-events: none` 是硬要求：否则整层会把下面的按钮全挡住点不动。
   ========================================================================== */
.petals {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: block;
  pointer-events: none;
  /* 不加 mix-blend-mode：混合模式在微信内置浏览器里会让整层发灰 */
}

/* ==========================================================================
   20. 首屏入场（一次性，不循环）
   --------------------------------------------------------------------------
   一次**编排好的入场**胜过一堆零散微交互：月盘 → 眉题 → 标题 → 副题 →
   纹样 → 正文 → 抬头，依次浮起，像灯一盏盏亮。
   三条约束，都踩过：
   · 纯 CSS，零 JS —— 入场不能依赖脚本，脚本挂了页面也得完整可见；
   · 包在 `prefers-reduced-motion: no-preference` 里，而不是只靠 base.css
     把时长压到 0.01ms —— 显式声明意图，别让可访问性依赖一个全局兜底；
   · **总时长 ≤ 800ms**（末位延迟 360 + 时长 420）。QA 脚本的 SETTLE 是 900ms，
     超了就会量到动画中间帧，出现"布局莫名其妙偏 10px"的假问题。
   ⚠️ 只动 opacity 与 translateY，不动 height/width —— 避免触发重排。
   ========================================================================== */
@keyframes hero-rise {
  from {
    opacity: 0;
    transform: translateY(9px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: no-preference) {
  .hero__moon,
  .hero__eyebrow,
  .hero__title,
  .hero__sub,
  .hero__ornament,
  .hero__desc,
  .hero__meta {
    animation: hero-rise 420ms var(--ease-soft) both;
  }

  .hero__moon {
    animation-delay: 30ms;
  }
  .hero__eyebrow {
    animation-delay: 100ms;
  }
  .hero__title {
    animation-delay: 150ms;
  }
  .hero__sub {
    animation-delay: 210ms;
  }
  .hero__ornament {
    animation-delay: 260ms;
  }
  .hero__desc {
    animation-delay: 310ms;
  }
  .hero__meta {
    animation-delay: 360ms;
  }
}

