/* ============================================================
   基础层（base）—— 两套设计共用的「结构 + 默认视觉」
   ------------------------------------------------------------
   2026-10-07 从 skin-birthday.css 里切出来的。为什么切：
     skin-letter.css 原来靠 `@import skin-birthday.css` 拿结构
     （层的定位、过渡、密码键盘骨架…）。结果是【两套设计耦合】——
     改生日皮的结构会连带改信纸皮。
     现在结构住在 base.css，两套皮各自只写自己的视觉。

   谁加载它：
     index.html 里 `<link href="base.css">` 排在最前面 ——
     ⚠️ 用 <link> 而不是让皮去 @import：@import 会多一层请求瀑布，
        而且和「引擎换 skinLink 的 href」这套机制不搭。
     之后才是 `id="skinLink"` 指向的那套皮。换设计 = 换 skinLink，
     base.css 不动。

   ⚠️ 内容是从 skin-birthday.css 【原样搬过来的，一个字没改】。
      切分脚本：tools/_tmp/split-base.mjs（一次性）

   ⚠️ 改这里 = 两套设计一起改（这是对的，它就是共用骨架）。
   ⚠️ 已知的「不够纯」：base 里还留着生日那套的默认视觉值
      （:root 的粉色调、26px 圆角、粉色阴影…）。信纸皮会把这些
      逐条覆盖掉，所以实际互不影响；但真要「只改生日、不动信纸」，
      得把那些值再挪回 skin-birthday.css —— 这一步【没做】，
      因为搬动量大、收益只是架构洁癖，而当前目标
      （改生日皮不再连带改信纸皮）已经达成。
   ============================================================ */

/* ========== 主题变量的默认值（= 生日那套的粉）==========
   ⚠️ 皮里没写 / data-theme 认不出来时用这里的值。
      skin-birthday.css 里那 8 套主题色块会按 data-theme 覆盖它。 */
/* ⚠️ 2026-10-08：手机深色模式 —— 不让浏览器（安卓 Chrome 的 Auto Dark Theme）自动暗化。
   index.html 的 <head> 里已经有一条 <meta name="color-scheme" content="only light">，
   这里是【同一条声明的 CSS 写法】。两条路 Chrome 官方博客都认可（auto-dark-theme 那篇），
   都写 = 覆盖面最全：
     · meta 的好处是【在 CSS 解析前就生效】，不会出现「先暗一下、再变亮」的闪暗
     · CSS 的好处是兼容更老的内核（color-scheme 属性比 meta 支持得早）
   ⚠️ 只写在 base.css 是【对的】：两套设计都是浅色调，都该拒绝暗化。
   ⚠️ 浅色系统下这条声明是 no-op（本来就用浅色调色板）→ 对老贺卡零影响。
   ⚠️ 不要改成 prefers-color-scheme 逐个元素修补 —— 官方博客专门警告过：
      那个媒体查询在没有 Auto Dark Theme 的浏览器上也会生效，
      结果变成「浅色页面里只有几个元素变暗」，比不修还难看。 */
:root{
  color-scheme:only light;
  --c1:#ee9ca7; --c2:#ffdde1; --bg1:#fff7f9; --bg2:#ffdde1;
  --ink:#7a4a55; --ink2:#a8747f;
  --card:rgba(255,255,255,.8);
  --line:rgba(238,156,167,.32);
  /* --gbH = 礼物盒的色相角（2026-10-06 加）。
     礼物盒原来把色值写死成粉色，客户换主题后会「粉盒子配主题色的字」。
     现在只换色相、明度/饱和度阶梯共用一套 —— 见下面「礼物盒配色」那段。 */
  --gbH:352;
  --serif:"STKaiti","KaiTi","Kaiti SC","楷体","Songti SC",Georgia,serif;
}

*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{height:100%;overflow:hidden;overscroll-behavior:none}
body{
  background:linear-gradient(160deg,var(--bg1) 0%,var(--bg2) 100%);
  color:var(--ink);
  font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
  position:relative;transition:background .8s ease;
  -webkit-font-smoothing:antialiased;
}
/* 柔光斑 */
body::before{
  content:'';position:fixed;inset:-15%;z-index:0;pointer-events:none;
  background:
    radial-gradient(42% 30% at 50% 12%, rgba(255,255,255,.75) 0%, transparent 70%),
    radial-gradient(38% 26% at 84% 86%, var(--c2) 0%, transparent 72%);
  opacity:.9;
}

#fx{position:fixed;inset:0;z-index:4;pointer-events:none;display:block}

/* ========== 通用层 ========== */
.layer{
  position:fixed;inset:0;z-index:6;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:32px 24px;text-align:center;
  opacity:0;pointer-events:none;transition:opacity .55s ease;
}
.layer.on{opacity:1;pointer-events:auto}
.layer.bye{opacity:0;transition:opacity .45s ease}

/* ⚠️⚠️ 2026-10-08：首屏防闪（配合 index.html <head> 里那段同步脚本）。
   ------------------------------------------------------------
   背景 = 【这份 HTML 里写死的那套（生日卡）】和【JS 读回来才知道的那套（情书）】
   之间的空窗期。带 ?id= 时先亮这一层中性的加载画面盖住，定稿后 hideLoad() 摘标记。
   ⚠️ 颜色【写死】成中性米白，不能跟 --bg1 / --bg2 ——
      那两个变量此刻还是生日那套的粉（:root 的默认值），跟着走就等于没防。
   ⚠️ 必须给 #lyLoad 自己铺不透明底（而不是去改 body 的渐变）——
      它是 position:fixed;inset:0 的整屏层，自己盖住最稳，也不影响别的层。 */
html.bc-loading #lyLoad{
  opacity:1;pointer-events:auto;
  background:linear-gradient(160deg,#fffdf9 0%,#f7f2ea 100%);
}
/* 加载字也跟着中性化（默认的 --ink2 是生日那套的粉褐）。
   ⚠️ 必须限定在 #lyLoad 里面 —— 写成 html.bc-loading .miniLabel 会连
      #lyPw / #lyCd / #lyErr 那几层的字一起改（它们也是 .miniLabel），
      regression 会抓到 6 处计算样式的差异。虽然那几层此刻不可见、
      肉眼没影响，但「改了不该改的东西」本身就是隐患。 */
html.bc-loading #lyLoad .miniLabel{color:#9a8f84}

/* ========== ① 密码 ========== */
.miniLabel{font-size:13px;letter-spacing:.3em;text-indent:.3em;color:var(--ink2);margin-bottom:14px}
.pwEmoji{font-size:clamp(44px,14vw,64px);line-height:1;filter:drop-shadow(0 6px 18px rgba(0,0,0,.1))}
.pwTip{font-size:13px;letter-spacing:.16em;color:var(--ink2);margin-top:14px}
.pwBox{display:flex;gap:9px;margin:28px 0 6px;width:100%;max-width:332px}
.pwDot{
  flex:1 1 0;min-width:0;height:clamp(46px,13vw,58px);border-radius:14px;
  border:1.5px solid var(--line);background:rgba(255,255,255,.7);
  display:flex;align-items:center;justify-content:center;
  font-size:clamp(19px,5.5vw,24px);font-weight:600;color:var(--c1);
  font-variant-numeric:tabular-nums;
  transition:border-color .2s,background .2s,transform .14s;
  box-shadow:0 4px 14px rgba(0,0,0,.04);
}
.pwDot.fill{border-color:var(--c1);background:rgba(255,255,255,.98);transform:translateY(-2px)}
.pwKeys{display:grid;grid-template-columns:repeat(3,72px);gap:12px;margin-top:24px}
.pwKeys button{
  height:56px;border-radius:16px;border:1.5px solid var(--line);
  background:rgba(255,255,255,.78);color:var(--ink);
  font-size:21px;font-weight:500;font-family:inherit;cursor:pointer;
  transition:transform .12s,background .2s;box-shadow:0 4px 12px rgba(0,0,0,.04);
}
.pwKeys button:active{transform:scale(.93);background:#fff}
.pwErr{font-size:12px;color:#d9544d;letter-spacing:.1em;height:18px;margin-top:14px;opacity:0;transition:opacity .3s}
.pwErr.on{opacity:1}
.shake{animation:shake .42s}
@keyframes shake{0%,100%{transform:translateX(0)}20%{transform:translateX(-9px)}40%{transform:translateX(9px)}60%{transform:translateX(-6px)}80%{transform:translateX(6px)}}

/* ========== ② 倒计时 ========== */
.cdRow{display:flex;gap:16px;margin:26px 0 6px;justify-content:center}
.cdCell{min-width:58px}
.cdCell b{display:block;font-size:30px;font-weight:700;color:var(--c1);font-variant-numeric:tabular-nums;line-height:1.2}
.cdCell span{display:block;font-size:11px;letter-spacing:.16em;color:var(--ink2);margin-top:6px}
.cdHint2{
  margin-top:12px;max-width:290px;line-height:1.8;
  font-family:var(--serif);font-size:14px;letter-spacing:.04em;color:var(--c1);
  opacity:0;transition:opacity .5s ease .3s;
}
.cdHint2.on{opacity:.92}
.cdHint2 .k{font-size:12px;letter-spacing:.16em;color:var(--ink2);display:block;margin-bottom:3px}

/* ========== ③ 封面 ========== */
.coverWord{
  font-family:var(--serif);
  font-size:clamp(46px,15vw,78px);font-weight:700;line-height:1.15;letter-spacing:.04em;
  color:var(--c1);
  text-shadow:0 2px 0 #fff,0 8px 30px rgba(0,0,0,.09);
  /* ⚠️ 2026-10-08：客户现在能自己填封面大字（?cw=），写长了得能自己折行 ——
     中文本来就会断行，但一长串英文（HappyBirthday）没有断点，会直接溢出屏幕。
     ⚠️ 对老贺卡是 no-op：Happy / Birthday 都短，永远用不到这条。 */
  overflow-wrap:anywhere;
}
.coverSub{font-family:var(--serif);font-size:clamp(17px,5vw,22px);color:var(--ink2);letter-spacing:.3em;text-indent:.3em;margin-top:16px}
.coverLine{width:64px;height:2px;margin:26px auto 0;border-radius:2px;background:var(--c1);opacity:.45}
.coverTo{font-family:var(--serif);font-size:clamp(20px,6vw,26px);color:var(--ink);margin-top:26px;letter-spacing:.1em}

.tapHint{margin-top:26px;font-size:13px;letter-spacing:.3em;text-indent:.3em;color:var(--ink2);animation:breathe 2.4s ease-in-out infinite}
@keyframes breathe{0%,100%{opacity:.35}50%{opacity:.9}}
.tapHint.hide{opacity:0;animation:none;transition:opacity .4s}

/* ========== ④ 贺卡 ========== */
#wrap{
  position:fixed;inset:0;z-index:3;overflow-y:auto;overflow-x:hidden;
  -webkit-overflow-scrolling:touch;
  padding:calc(34px + env(safe-area-inset-top)) 18px calc(70px + env(safe-area-inset-bottom));
  opacity:0;pointer-events:none;transition:opacity .8s ease;
}
#wrap.show{opacity:1;pointer-events:auto}
.card{
  margin:0 auto;width:100%;max-width:430px;position:relative;
  padding:36px 22px 30px;border-radius:26px;
  background:var(--card);
  border:1px solid rgba(255,255,255,.85);
  backdrop-filter:blur(16px) saturate(150%);
  -webkit-backdrop-filter:blur(16px) saturate(150%);
  box-shadow:0 18px 50px rgba(140,90,100,.13),0 1px 0 rgba(255,255,255,.9) inset;
  transform:translateY(40px) scale(.97);
  transition:transform 1.1s cubic-bezier(.16,.84,.28,1);
}
#wrap.show .card{transform:none}

.c-to{font-family:var(--serif);font-size:17px;letter-spacing:.2em;color:var(--ink2);text-align:center}
.c-rule{width:54px;height:1.5px;margin:16px auto 26px;border-radius:2px;background:var(--c1);opacity:.5}

.c-title{
  font-family:var(--serif);
  font-size:clamp(30px,9vw,42px);font-weight:700;letter-spacing:.06em;line-height:1.3;
  color:var(--c1);text-align:center;
  text-shadow:0 2px 0 #fff,0 6px 22px rgba(0,0,0,.07);
  margin-bottom:28px;white-space:pre-wrap;word-break:break-word;
}

/* —— 照片时间轴 —— */
.timeline{position:relative;padding-left:34px;margin-bottom:30px}
.timeline::before{
  content:'';position:absolute;left:9px;top:14px;bottom:14px;width:2px;border-radius:2px;
  background:linear-gradient(180deg,transparent,var(--line) 8%,var(--line) 92%,transparent);
}
.tl-item{position:relative;margin-bottom:24px;opacity:0;transform:translateY(22px)}
.tl-item.in{opacity:1;transform:none;transition:opacity .7s ease,transform .7s cubic-bezier(.16,.84,.28,1)}
.tl-item:last-child{margin-bottom:0}
.tl-dot{
  position:absolute;left:-31px;top:16px;width:12px;height:12px;border-radius:50%;
  background:#fff;border:2.5px solid var(--c1);
  box-shadow:0 0 0 4px rgba(255,255,255,.75);
}
/* ⚠️ 这里的 aspect-ratio:4/3 只是【兜底】（2026-10-07）——
   照片 JSON 里带 w/h 时，引擎 photoEl() 会按照片真实比例写 inline aspect-ratio
   （竖图就是竖的，cover 等于不裁）。这条只管两种图：
     ① 老贺卡（10-07 前发布，JSON 里没有 w/h）→ 外观一字不变
     ② 没选图的占位框
   改这里会连带影响所有老贺卡，别随手动。 */
.tl-photo{
  width:100%;aspect-ratio:4/3;border-radius:16px;overflow:hidden;
  background:linear-gradient(135deg,var(--c2),var(--c1));
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 8px 22px rgba(140,90,100,.15);
  position:relative;
}
.tl-photo img{width:100%;height:100%;object-fit:cover;display:block}
.tl-photo .ph{color:rgba(255,255,255,.92);font-size:12px;letter-spacing:.28em;text-indent:.28em;text-align:center}
.tl-photo .ph b{display:block;font-size:26px;font-weight:700;letter-spacing:0;text-indent:0;margin-bottom:6px;opacity:.9}
.tl-cap{
  font-family:var(--serif);font-size:14.5px;line-height:1.85;color:var(--ink);
  margin-top:11px;padding-left:2px;letter-spacing:.03em;
}

/* —— 祝福语 —— */
.c-msg{
  font-family:var(--serif);
  font-size:16px;line-height:2.15;letter-spacing:.05em;color:var(--ink);
  white-space:pre-wrap;word-break:break-word;text-align:left;
  padding-top:26px;border-top:1px solid var(--line);
}
.c-msg .ln{display:block;opacity:0;transform:translateY(9px);animation:lnIn .7s ease forwards}
@keyframes lnIn{to{opacity:1;transform:none}}
/* 长文折叠：默认只露前面一段，点「展开全文」才全出 */
.c-msg .msgMore{
  display:block;margin:20px auto 0;padding:9px 22px;cursor:pointer;
  font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;
  font-size:13px;letter-spacing:.08em;line-height:1.4;
  color:var(--c1);background:rgba(255,255,255,.72);
  border:1px solid var(--line);border-radius:999px;
  opacity:0;animation:lnIn .7s ease forwards;
}
.c-msg .msgMore:active{transform:scale(.97)}

/* —— 语音条 —— */
.voice{
  display:flex;align-items:center;gap:12px;margin-top:24px;
  padding:14px 16px;border-radius:16px;
  background:rgba(255,255,255,.72);border:1px solid var(--line);
}
.voice .vPlay{
  width:38px;height:38px;flex:0 0 38px;border-radius:50%;border:0;cursor:pointer;
  background:var(--c1);color:#fff;font-size:14px;line-height:1;
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 4px 12px rgba(0,0,0,.12);transition:transform .12s;
}
.voice .vPlay:active{transform:scale(.92)}
.voice .vBody{flex:1;min-width:0}
.voice .vName{font-size:12.5px;letter-spacing:.1em;color:var(--ink2)}
.voice .vBar{margin-top:7px;height:3px;border-radius:2px;background:var(--line);overflow:hidden}
.voice .vBar i{display:block;height:100%;width:0;background:var(--c1);border-radius:2px;transition:width .25s linear}
.voice .vTime{font-size:12px;color:var(--ink2);font-variant-numeric:tabular-nums;flex:0 0 auto}

.c-from{margin-top:26px;padding-top:20px;border-top:1px solid var(--line);text-align:right;
  font-family:var(--serif);font-size:15px;letter-spacing:.14em;color:var(--ink)}
.c-date{margin-top:8px;text-align:right;font-size:11px;letter-spacing:.22em;color:var(--ink2);opacity:.75}

/* ⚠️ 2026-10-17 试过在贺卡卡片底部加一行「隐私说明」，【已删除】。
   为什么删（用户指出的）：
     · 出戏 —— 贺卡是情绪产品，底部出现「隐私说明」四个字，读者会瞬间从
       「有人在给我写信」跳回「这是个网站」。信纸皮和生日皮都躲不过。
     · 而且法律上不需要 —— 收礼人不是我们的用户，他的信息是【制作人】提供的。
       隐私政策里已经写了「收礼人如需行使权利，可以通过制作人联系我们」，
       制作页那边也有入口。贺卡页再挂一个，属于自我安慰。
   → 别再往贺卡页加这类行政文案。要加之前先问：收礼人此刻该看到的是不是只有贺卡。 */

/* ========== 底部 ========== */
/* 原来这里有一条「长按保存 · 转发给 TA」。
   ⚠️ 2026-10-04 删掉，原因（用户提的）：
     ① 这里是【收礼人】在看贺卡，他没什么要保存的；
     ② 而且长按根本不会保存 —— 那是个固定的 div，不是图片。
   以后想加提示的话，先想清楚「谁在看、他真能做什么」。 */
#snd{
  position:fixed;top:calc(16px + env(safe-area-inset-top));right:16px;z-index:8;
  width:38px;height:38px;border-radius:50%;border:1.5px solid var(--line);
  background:rgba(255,255,255,.82);color:var(--c1);font-size:16px;line-height:1;
  display:none;align-items:center;justify-content:center;cursor:pointer;
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
}
#snd.on{display:flex}
/* ========== 预览模式返回键 ========== */
#back{
  position:fixed;top:calc(16px + env(safe-area-inset-top));left:16px;z-index:9;
  height:38px;padding:0 15px 0 12px;border-radius:19px;cursor:pointer;
  border:1.5px solid var(--c1);
  background:rgba(255,255,255,.95);color:var(--ink);
  font-family:inherit;font-size:13px;font-weight:600;letter-spacing:.06em;
  display:none;align-items:center;gap:5px;
  box-shadow:0 4px 16px rgba(140,90,100,.16);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  transition:transform .12s,background .2s;
}
#back.on{display:flex}
#back:active{transform:scale(.95);background:#fff}

/* ========== 加载中 / 打不开 ========== */
.spin{
  width:34px;height:34px;border-radius:50%;margin-top:24px;
  border:2.5px solid var(--line);border-top-color:var(--c1);
  animation:sp .85s linear infinite;
}
@keyframes sp{to{transform:rotate(360deg)}}
.errEmoji{font-size:clamp(40px,13vw,58px);line-height:1;filter:drop-shadow(0 6px 18px rgba(0,0,0,.1))}
.retryBtn{
  margin-top:24px;height:44px;padding:0 26px;border-radius:22px;cursor:pointer;
  border:1.5px solid var(--c1);background:rgba(255,255,255,.9);
  color:var(--ink);font-family:inherit;font-size:14px;font-weight:600;letter-spacing:.1em;
  box-shadow:0 6px 18px rgba(140,90,100,.14);
  transition:transform .12s,background .2s;
}
.retryBtn:active{transform:scale(.95);background:#fff}
