/* 《未发送》Ch3《短信》—— 2003，STN 液晶的黄绿背光、深灰塑料，夜，屏幕自发光
   配色取《美术生成规格》Ch3 档，是第四套色，与 Ch1 牛皮纸黄／Ch2 稻田绿／Ch4 冷白都不同。

   本章有两处结构性反转，都是前两章没有的：
   1. **9:16 竖屏**（UI 规格 §1）。两侧留黑是设计，不是没填满。
   2. **谜题主体是亮底深字**。前两章是暗底亮字，所以「幽灵态 = 更淡」在这里
      意味着**更亮**、更靠近背景，对比度算法整个反过来。alpha 不能照抄 Ch1 的 .25。 */

:root {
  --fs: 1;                      /* 字号缩放，无障碍项（UI 规格 §5） */

  /* 液晶屏 */
  --lcd:      #b8c46a;          /* STN 黄绿背光 */
  --lcd-edge: #a3b055;          /* 屏幕边缘略暗，液晶不匀 */
  --lcd-ink:  #1d2410;          /* 点阵墨色 */

  /* 机身与夜 */
  --shell:    #33342f;          /* 深灰塑料 */
  --night:    #0c0d0b;

  /* 屏幕外的文字（压在照片上） */
  --paper:    #e8e4d6;
  --paper-dim:#b9b4a5;

  /* 红线。**这个值由「线本身要看得见」定的，不是由标签定的。**
     红线是本章的核心图形（WCAG 1.4.11：有意义的图形对相邻色需 3:1），
     原来的 #d8433a 压在液晶上只有 1.86:1 —— 整章的机制看不清。
     三个约束一起解（实测，取液晶渐变最暗的 #a3b055 当最坏情况）：
       线 vs 液晶 ≥ 3      → 最紧的一条，决定了这个值
       标签白字 vs 红 ≥ 4.5
       分享卡上红字 vs 卡底 ≥ 3
     #d8433a: 1.86 / 4.39 / 2.69 ——三条全不过
     #a32d25: 3.01 / 7.10 / 4.34 ——刚好卡在线上，太紧
     #992a22: 3.29 / 7.75 / 4.75 ——取这个 */
  --danger:   #992a22;

  /* 幽灵态：**亮底上的删除字**。
     0.25 是 Ch1 暗底的值，照搬到这儿会淡到读不出来 ——
     亮底上降 alpha 是往背景色靠，不是往黑里沉。

     实测（1280×720，最差背景取渐变边缘 #a3b055）：
       墨色 L*13，液晶 L*69.1 → 想同时「过 4.5」和「比墨色浅」，
       可用区间只有 4.5–6.88 这一段，alpha 下限 0.79。
       0.30 → 1.68 ✗   0.62 → 3.19 ✗   0.78 → 4.49 ✗   0.80 → 4.69 ✓
     所以这里取 0.80。**亮底上「淡」和「读得出」是互斥的**，
     Ch1 两样都要得到只是因为它的底是暗的。
     代价：alpha 差已经不足以单独表达「这个字被删了」，
     状态改由 .ch.gone 的形状线索承担（见下面那条规则）。 */
  --ghost: 0.80;
}

* { box-sizing: border-box; }

/* 只给读屏，不占画面。
   不用 display:none / visibility:hidden —— 那两个连读屏一起藏。 */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

html, body {
  margin: 0; padding: 0;
  height: 100%;
  background: var(--night);
  overflow: hidden;
  font-family: "Songti SC", "SimSun", "Source Han Serif SC", serif;
  -webkit-font-smoothing: antialiased;
}

/* 舞台居中：上下也要居中，否则比 9:16 更高的屏上舞台贴顶、底下空一条。 */
body {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ---------- 9:16 舞台 ----------
   两侧留黑（UI 规格 §1）。

   踩过的坑：原先是 `height:100%` + `aspect-ratio:9/16`，
   宽度**只由高度推**。在比 9:16 更高的真手机上（390×844 是 9:19.5）
   算出来是 844×9/16 = 475px 宽，而屏幕只有 390px ——
   舞台比屏幕宽 85px，body 的 overflow:hidden 把两边各切掉 42.5px，
   诺基亚的左右边缘连字一起被裁掉。**现代手机基本都比 16:9 高，
   所以这不是边缘情况，是主流机型上的默认表现。**

   改成两个方向同时约束：宽取「屏宽」与「按屏高算的 9:16 宽」的较小值，
   高再由这个宽度反推。这样无论屏比 9:16 更高还是更宽，
   舞台都完整可见，多出来的方向留黑。 */
#stage {
  position: relative;
  width:  min(100vw, calc(100vh * 9 / 16));
  height: min(100vh, calc(100vw * 16 / 9));
  flex: 0 0 auto;
  overflow: hidden;
  background: #000;
}

/* ---------- 画面 ---------- */
.screen {
  position: absolute; inset: 0;
  display: none;
  opacity: 0;
  transition: opacity .5s;
}
.screen.show  { display: block; }
.screen.in    { opacity: 1; }

.bg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  user-select: none; -webkit-user-drag: none;
}
.bg.dim        { filter: brightness(.34) saturate(.85); }
.puzzle-bg     { filter: brightness(.52) saturate(.9); }
.ending-bg     { filter: brightness(.42) saturate(.88); }

/* ---------- 按钮 ---------- */
.btn {
  font: inherit;
  font-size: calc(15px * var(--fs));
  letter-spacing: .1em;
  padding: 11px 26px;
  border-radius: 3px;
  border: 1px solid rgba(232, 228, 214, .78);
  background: rgba(20, 22, 16, .58);
  color: #f2efe4;
  cursor: pointer;
  transition: background .18s, border-color .18s, color .18s;
  backdrop-filter: blur(3px);
}
.btn:hover { background: rgba(40, 44, 30, .72); border-color: #fff; }
.btn:disabled { opacity: .34; cursor: default; }
.btn:focus-visible { outline: 2px solid var(--lcd); outline-offset: 2px; }

/* 幽灵键：压在插图上。alpha 与色值都要单独验，
   不能照抄 Ch2 —— 那一章的底是稻田绿，这一章是婚礼／站台／地铁。 */
.btn-ghost {
  background: rgba(14, 15, 12, .42);
  border-color: rgba(215, 209, 192, .68);
  color: #d9d3c2;
}
.btn-ghost:hover { color: #f7f4ea; }

/* ---------- 标题 ---------- */
.title-wrap {
  position: absolute;
  left: 0; right: 0; bottom: 12%;
  text-align: center;
  color: var(--paper);
  text-shadow: 0 1px 16px rgba(0, 0, 0, .8);
}
.ch-no   { font-size: calc(14px * var(--fs)); letter-spacing: .52em; color: #d6d0be; }
.ch-name { font-size: calc(58px * var(--fs)); letter-spacing: .24em; margin: 8px 0 16px; font-weight: 500; }
.ch-when { font-size: calc(13px * var(--fs)); letter-spacing: .1em; color: #c4bfae; }
.ch-sub  { font-size: calc(15px * var(--fs)); letter-spacing: .06em; margin: 26px 0 34px; color: #e2ddcd; }

/* ---------- 心声浮字 ---------- */
.voice-stack {
  position: absolute;
  left: 8%; right: 8%; top: 50%;
  transform: translateY(-50%);
}
.voice-stack p {
  font-size: calc(17px * var(--fs));
  line-height: 2.15;
  color: #ece7d9;
  margin: 20px 0;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 1s, transform 1s;
  text-shadow: 0 1px 16px rgba(0, 0, 0, .88);
}
.voice-stack p.in { opacity: 1; transform: none; }

/* 谜题区心声：单行，压在屏幕下方 */
.voice-line {
  position: absolute;
  left: 7%; right: 7%; bottom: 15.5%;
  text-align: center;
  font-size: calc(16px * var(--fs));
  line-height: 1.85;
  min-height: 3.4em;
  color: #ece7d9;
  opacity: 0;
  transition: opacity .6s;
  text-shadow: 0 1px 14px rgba(0, 0, 0, .9);
  pointer-events: none;
}
.voice-line.in { opacity: 1; }

/* ---------- 顶栏 ----------
   渐变的拐点要压过整条文字带再收口，别让它落在文字下沿上
   —— Ch2 就是这么漏掉一处对比度的。 */
#topbar {
  position: absolute; top: 0; left: 0; right: 0;
  z-index: 5;
  display: flex; justify-content: space-between; align-items: center;
  padding: 13px 18px 22px;
  font-size: calc(13px * var(--fs));
  letter-spacing: .06em;
  color: #e4dfd0;
  background: linear-gradient(rgba(10, 11, 9, .88),
                              rgba(10, 11, 9, .82) 76%,
                              rgba(10, 11, 9, 0) 100%);
  pointer-events: none;
}
#top-quota { color: #cfc9b8; }

/* ---------- 诺基亚屏 ---------- */
#screen-frame {
  position: absolute;
  left: 6.5%; right: 6.5%;
  top: 13%;
  z-index: 3;
}

/* 液晶面板本体。
   `overflow: visible` —— 红线要能贴在字缝里，标签「第 2 条」允许探出屏幕边缘一点，
   那正是诺基亚上「这条要分两条」的样子。 */
#nokia {
  position: relative;
  padding: 18px 15px 20px;
  border-radius: 4px;
  background:
    radial-gradient(120% 100% at 50% 0%, var(--lcd) 62%, var(--lcd-edge) 100%);
  box-shadow:
    0 0 0 2px #2a2b26,
    0 0 0 8px var(--shell),
    0 0 34px 10px rgba(184, 196, 106, .18),   /* 屏幕自发光溢到机身上 */
    0 14px 40px rgba(0, 0, 0, .6);
}

/* 一行 = 原稿的一个自然短句。
   这不只是排版：红线落在行尾 = 一句话说完了，落在行中间 = 掐在词里，
   玩家一眼看得出，不用去数第几个字。本章的题目靠这个才是可见的。 */
.line {
  display: block;
  white-space: nowrap;
  text-align: left;
}

/* 字元。48/82 个字都是同一个东西：CharToken。 */
.ch {
  display: inline-block;
  font-family: "Songti SC", "SimSun", serif;
  font-size: calc(19px * var(--fs));
  line-height: 1.72;
  color: var(--lcd-ink);
  letter-spacing: 0;
  transition: opacity .12s, transform .12s;
  user-select: none;
}

/* 可删的字：**没有高亮、没有描边、没有底色**。
   和 Ch2 的可擦字同一条原则（UI 规格 §3）——
   玩家要自己看出哪句话是多余的，不是被 UI 指着看。
   唯一的区别是它可以点，所以只给光标和一个极轻的悬停反馈。 */
.ch.del-ok { cursor: pointer; }
.ch.del-ok:hover { background: rgba(29, 36, 16, .1); }

/* 幽灵态 —— 本作的视觉签名（UI 规格 §3）：
   被删的字**永远留在原位**，不消失、不重排、不塌缩。
   玩家在任何时刻都能同时看到「她想说的全部」与「她能发出的部分」。

   三条线索，其中两条与对比度无关（UI 规格 §5：状态区分不依赖色相，
   这里进一步要求也不能只依赖明度）：
     1. alpha 0.80 —— 见 :root 里的实测，只能到这么淡；
     2. scale(.9) —— 字在原位缩小，**外框宽度不变**，所以不引起重排；
     3. 点线下划线 —— 唯一真正的形状线索。分不清 0.80 与 1.0 的人
        靠它就能确定「这个字被删了」。选点线而不是删除线：
        删除线压在字身上会伤可读性，而幽灵字是要读的；
        点线也更贴合点阵屏的物理（它本来就是一排点）。 */
.ch.gone {
  opacity: var(--ghost);
  transform: scale(.9);
  text-decoration: underline dotted currentColor;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

/* 键盘焦点 */
.ch:focus-visible {
  outline: 2px solid #3d4a1c;
  outline-offset: 1px;
}

/* ---------- 红线 ----------
   2px + 虚线纹理（色盲安全，UI 规格 §5：状态区分不依赖色相）。
   每次删字滑动 150ms —— 这是本章的核心视觉反馈，玩家看得见它在挪。 */
#breakline {
  position: absolute;
  width: 2px;
  background-image: linear-gradient(var(--danger) 0 4px, transparent 4px 8px);
  background-size: 2px 8px;
  background-repeat: repeat-y;
  transition: left .15s ease, top .15s ease, opacity .35s;
  pointer-events: none;
  opacity: 1;
}
/* 全文 ≤70 字 → 红线淡出消失。**这个消失本身就是奖励反馈** ——
   「你不用赌了」不需要一句话来说明。 */
#breakline.vanished { opacity: 0; }

#break-tag {
  position: absolute;
  left: 5px; top: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  font-size: calc(10px * var(--fs));
  font-weight: 600;  /* 只为小字清晰，不再用来凑「大字号」豁免：白字 / --danger 实测 7.75 ✓ */
  letter-spacing: .04em;
  color: #fff;
  background: var(--danger);
  padding: 1px 5px;
  border-radius: 2px;
}

/* ---------- 字数 ----------
   从 82 数到 70 是本章唯一的进度条。 */
#counter {
  margin-top: 13px;
  text-align: right;
  font-size: calc(13px * var(--fs));
  letter-spacing: .05em;
  color: #ded9ca;
  text-shadow: 0 1px 10px rgba(0, 0, 0, .9);
}
#count-num { font-size: calc(19px * var(--fs)); color: #f4f1e6; }
.count-slash { color: #b6b1a1; }
#count-note {
  display: inline-block;
  margin-left: 9px;
  padding: 1px 7px;
  border-radius: 2px;
  /* 由 --danger 推出来，不另写一份红（下同）。 */
  border: 1px solid color-mix(in srgb, var(--danger) 80%, transparent);
  color: #f0a49e;
  font-size: calc(11px * var(--fs));
}
/* 到 70 字：换成「一条就发完」。红线消失之外的第二个奖励信号，
   给的是**文字**，因为色相不能是唯一区分（UI 规格 §5）。 */
#count-note.ok {
  border-color: rgba(184, 196, 106, .85);
  color: #d3dc94;
}

/* ---------- 操作 ---------- */
#puzzle-btns {
  position: absolute;
  left: 0; right: 0; bottom: 5.5%;
  z-index: 5;
  display: flex; justify-content: center; gap: 12px;
  flex-wrap: wrap;
  padding: 0 6%;
}

/* ---------- 发送预览 ----------
   本章的核心可读性设计（剧本 §4 / UI 规格 §3）：
   玩家必须能在发送前**看见**「第 1 条单独读起来是什么样」，
   否则整章的题目是隐形的。**不消耗次数，可反复看。** */
#preview-layer {
  position: absolute; inset: 0;
  z-index: 20;
  display: flex; align-items: center; justify-content: center;
  padding: 7%;
  background: rgba(6, 7, 5, .8);
  backdrop-filter: blur(4px);
  opacity: 0;
  transition: opacity .25s;
}
#preview-layer.in { opacity: 1; }

/* **这条不能删。**
   `hidden` 属性是靠 UA 样式表里的 `[hidden]{display:none}` 生效的，权重等于零个类，
   而上面那条 `#preview-layer{display:flex}` 是 ID 选择器，权重更高 —— 它赢。
   结果是关掉的预览层 `hidden=true` 但 `display:flex` 照旧：
   元素还在无障碍树里、Tab 还能走进去、`aria-modal` 还生效，
   只是 opacity 0 所以看不见。读屏用户会撞进一个明眼人看不到的对话框。

   原先这里只写了 `#preview-layer[hidden]`，就地补一处。改成下面这条兜底：
   **逐个补漏是补不完的** —— 这一章 `hidden` 用在 4 个元素上
   （preview-layer / ending-paper / dev / share-canvas），
   今天只有 preview-layer 撞上了 id 规则，但以后谁给另外三个加一条
   `#x{display:...}`，它就静默坏掉，不报错，而且只有命中测试查得出来。
   Ch5 也是这条（那边是 `#off-layer` 把整章盖住，鼠标一个键点不动）。 */
[hidden] { display: none !important; }

#preview-box {
  width: 100%;
  max-height: 100%;
  overflow-y: auto;
  padding: 22px 20px 20px;
  border-radius: 5px;
  background: #17190f;
  border: 1px solid rgba(184, 196, 106, .3);
  text-align: center;
}
#preview-title {
  font-size: calc(14px * var(--fs));
  letter-spacing: .18em;
  color: #c9c3b1;
  margin-bottom: 18px;
}

/* 气泡：他那头的收件箱。**无空格连排** ——
   她在编辑器里看到的是分好行的短句，他看到的是一整坨。
   这个落差是 3A 的全部要害。 */
.bubble {
  position: relative;
  text-align: left;
  font-size: calc(15px * var(--fs));
  line-height: 1.95;
  padding: 12px 14px;
  margin: 0 0 12px;
  border-radius: 10px 10px 10px 2px;
  background: #333629;
  color: #ece8da;
  word-break: break-all;
}
.bubble-no {
  display: block;
  font-size: calc(11px * var(--fs));
  letter-spacing: .1em;
  color: #a9a394;
  margin-bottom: 5px;
}
/* 第 2 条：半透明 + 标注「可能失败」（剧本 §4）。
   半透明是氛围，那行字才是信息 —— 不依赖透明度传达状态。 */
.bubble.risky {
  opacity: .55;
  border: 1px dashed color-mix(in srgb, var(--danger) 75%, transparent);
  background: #2b2a24;
}
.bubble-warn {
  display: inline-block;
  margin-top: 7px;
  font-size: calc(11px * var(--fs));
  color: #f0a49e;
  letter-spacing: .06em;
}

#preview-hint {
  font-size: calc(13px * var(--fs));
  line-height: 1.8;
  color: #bdb7a6;
  margin: 4px 0 18px;
}
#preview-hint b { color: #e6e1d1; font-weight: 600; }

/* ---------- 结局 ---------- */
#ending-scroll {
  position: absolute; inset: 0;
  z-index: 4;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
#ending-inner {
  min-height: 100%;
  padding: 13% 8% 9%;
  display: flex; flex-direction: column; justify-content: center;
  text-align: center;
}
#ending-title {
  font-size: calc(29px * var(--fs));
  letter-spacing: .2em;
  font-weight: 500;
  color: #f2eee2;
  margin: 0 0 24px;
  text-shadow: 0 1px 16px rgba(0, 0, 0, .9);
}

/* 3A 里他收到的那一条：原样呈现，无空格，读起来就是一坨。
   放在正文之前，因为叙述要先让玩家自己读一遍。 */
#ending-received {
  font-size: calc(13px * var(--fs));
  line-height: 2.05;
  text-align: left;
  color: #dcd7c7;
  background: rgba(12, 13, 10, .72);
  border-left: 2px solid color-mix(in srgb, var(--danger) 80%, transparent);
  padding: 12px 14px;
  margin: 0 0 24px;
  word-break: break-all;
}

#ending-text p, #ending-tail p {
  font-size: calc(16px * var(--fs));
  line-height: 2.1;
  /* 原为 #e9e4d6，压在三张插图最亮处只有 4.20，过不了 4.5。

     实测方法：整图逐像素过 saturate(.88) + brightness(.42)（.ending-bg 的滤镜，
     brightness 是 sRGB 直乘），取滤镜之后最亮的那个像素当最坏背景。
     三张图都有纯白像素，所以最坏背景都是 255×.42 ≈ #6b6b6b。
       #e9e4d6 → 4.20 ✗    #f4f1e8 → 4.72 ✓
     同一背景下另两个色也过：粗体 #fbf8ef → 5.02，标题 #f2eee2 → 4.59
     （标题 29px 属大字号，3:1 就够，但它按 4.5 也过）。

     text-shadow 实际上帮了很大忙，但**模糊阴影不算背景**，
     WCAG 的比值是字色对背景色，不能拿它凑数 —— 所以这里直接提字色。
     没有去压暗插图（.ending-bg 的 brightness）：那三张是结局的情绪落点，
     为了半点比值把画面压黑不划算。 */
  color: #f4f1e8;
  margin: 17px 0;
  text-shadow: 0 1px 14px rgba(0, 0, 0, .9);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .9s, transform .9s;
}
#ending-text p.in, #ending-tail p.in { opacity: 1; transform: none; }
#ending-text b, #ending-tail b { color: #fbf8ef; font-weight: 600; }

/* 引擎叠字：出站口他举着的那张纸。
   美术规格里 D-3C 留了白，两个字由引擎叠上去。 */
#ending-paper {
  align-self: center;
  margin: 22px 0 6px;
  padding: 15px 30px;
  background: #efe9d8;
  color: #24211c;
  font-size: calc(30px * var(--fs));
  letter-spacing: .34em;
  text-indent: .34em;      /* 抵掉末字后面那一格字距，让它看着是居中的 */
  border-radius: 1px;
  box-shadow: 0 5px 22px rgba(0, 0, 0, .55);
  transform: rotate(-1.4deg);
  opacity: 0;
  transition: opacity 1.1s;
}
#ending-paper.in { opacity: 1; }

#ending-ach {
  margin: 30px 0 4px;
  font-size: calc(13px * var(--fs));
  letter-spacing: .1em;
  color: #d5cfbd;
  opacity: 0;
  transition: opacity .8s;
}
#ending-ach.in { opacity: 1; }
.ach-tag {
  display: inline-block;
  margin: 5px 5px 0;
  padding: 4px 11px;
  border: 1px solid rgba(184, 196, 106, .62);
  border-radius: 2px;
  color: #dfe0b6;
}

.ending-btns {
  margin-top: 34px;
  display: flex; justify-content: center; gap: 12px;
  flex-wrap: wrap;
  opacity: 0;
  transition: opacity .8s;
}
.ending-btns.in { opacity: 1; }

/* ---------- DEV ---------- */
#dev {
  position: fixed; right: 10px; top: 10px;
  z-index: 99;
  width: 178px;
  padding: 9px;
  background: rgba(8, 9, 7, .93);
  border: 1px solid #3c3f33;
  border-radius: 4px;
  font: 11px/1.5 ui-monospace, monospace;
  color: #9fa88a;
}
.dev-t { margin-bottom: 6px; color: #c3cba6; }
#dev button {
  display: block; width: 100%;
  margin: 3px 0;
  padding: 4px;
  font: inherit;
  background: #1d2016; color: #cfd6b6;
  border: 1px solid #454a38; border-radius: 3px;
  cursor: pointer;
}
#dev button:hover { background: #2b3020; }
.dev-row { display: flex; align-items: center; gap: 3px; margin-top: 5px; }
.dev-row span { flex: 0 0 34px; }
.dev-row button { margin: 0; }

/* ---------- 窄屏 ----------
   本章天生是竖屏，所以「窄」在这里只意味着**矮** ——
   高度不够时液晶屏会顶到按钮。绑高度，不绑宽度。 */
@media (max-height: 700px) {
  #screen-frame { top: 11%; }
  .ch { font-size: calc(17px * var(--fs)); line-height: 1.62; }
  .voice-line { bottom: 14%; font-size: calc(15px * var(--fs)); }
}
@media (max-height: 580px) {
  #screen-frame { top: 9.5%; left: 4.5%; right: 4.5%; }
  #nokia { padding: 13px 11px 15px; }
  .ch { font-size: calc(15px * var(--fs)); line-height: 1.54; }
  .title-wrap { bottom: 8%; }
  .ch-name { font-size: calc(46px * var(--fs)); }
}

/* ===== 构建期注入（build.py）=====
   调试面板在公开版永久关闭。bindDev() 的调用已在构建时摘掉，
   这条 CSS 是第二道保险 —— 面板里有「填真解」「折第4行」
   「extend_recall 4min」这类直接给答案或改核心机制的按钮。
   display:none 同时也把它从 tab 顺序和无障碍树里摘掉了。 */
#dev, #devpanel { display: none !important; }
