/* ============================================================
   魔卡少女樱 · 库洛牌 × 小樱牌 展览 — 样式
   主题：库洛 = 夜色深蓝 + 库洛黄 ／ 小樱 = 樱花粉 + 小樱粉
   ============================================================ */

:root {
  --radius: 12px;
  /* 卡槽判定外圈的宽度（视觉框之外的额外响应范围），JS 也读这个值 */
  --hit: 12px;
  --card-ratio: 4 / 9;              /* 卡面统一为竖版长卡 */
  --font-title: "KaiTi", "STKaiti", "楷体", "FangSong", serif;
  --font-ui: "Microsoft YaHei", "PingFang SC", "Segoe UI", system-ui, sans-serif;

  /* 三张特殊牌的固定色：不随主题切换而改变 */
  --clow-gold:   #e8c76a;   /* 库洛黄 —— 无 */
  --sakura-pink: #cf5c8b;   /* 小樱粉 —— 爱 */
  /* 希望红：取自 SHope 素材心形平涂处（实测 #B9232F），为兼顾夜色主题下的可读性略作提亮 */
  --hope-red:    #c62a3a;
}

/* 把 --frame 挂到容器上，内部的卡面/拖拽幽灵/卡槽都跟着走 */
.frame-clow   { --frame: var(--clow-gold); }
.frame-sakura { --frame: var(--sakura-pink); }
.frame-hope   { --frame: var(--hope-red); }

* { margin: 0; padding: 0; box-sizing: border-box; }

html {
  scroll-behavior: smooth; background: #191936;
  scrollbar-width: none;          /* 隐藏右侧滚动条（仍可正常滚动） */
  -ms-overflow-style: none;
}
html::-webkit-scrollbar { display: none; }

/* ---------- 双主题（body[data-mode]），两层渐变背景交叉过渡 ---------- */
body {
  --ink: #4b3a55;
  --ink-soft: #8d7f99;
  --panel: rgba(255, 255, 255, .62);
  --panel-strong: rgba(255, 255, 255, .94);
  /* 合成台桌面：比页面底色略浅一点的粉，靠拢但不重合 */
  --panel-ritual: rgba(253, 232, 242, .75);
  /* 诗直接压在卡面上，靠这个颜色描边 + 柔光把字托出来 */
  --halo: rgba(255, 250, 252, .92);
  --outline: 1px 0 0 var(--halo), -1px 0 0 var(--halo),
             0 1px 0 var(--halo), 0 -1px 0 var(--halo),
             1px 1px 0 var(--halo), -1px -1px 0 var(--halo),
             1px -1px 0 var(--halo), -1px 1px 0 var(--halo);
  --line: rgba(75, 58, 85, .15);
  --accent: #d8a03c;
  --accent2: #e88ab0;
  --glow: rgba(232, 138, 176, .45);
  --dot-color: rgba(216, 160, 60, .14);
  --card-border: rgba(255, 255, 255, .92);
  /* 详情按钮：小樱模式下用淡粉底色，hover 转小樱粉 */
  --detail-bg: #FCE6F0;
  --detail-hover: var(--sakura-pink);
  /* 密码门配色：取自背景海报的「黑 / 银白 / 玫瑰红」。
     这是浅色海报那一版：纸底 + 墨黑，红是两版共有的玫瑰红
     （实测深色 #824E4F、浅色 #875253，这里略提亮以便 UI 上看得清） */
  --gate-bg: rgba(226, 219, 203, .95);
  --gate-ink: #17120f;
  --gate-field: rgba(23, 18, 15, .06);
  --gate-line: rgba(23, 18, 15, .22);
  --gate-red: #9e4a4e;
  /* 当前卡组的代表色：卡名与拖拽高亮共用 */
  --mode-color: var(--sakura-pink);
  --name: var(--mode-color);        /* 小樱模式：小樱粉 */
  --name-en: var(--mode-color);
  font-family: var(--font-ui);
  color: var(--ink);
  background: transparent;
  min-height: 100vh;
}

body[data-mode="clow"] {
  --ink: #eee9f8;
  --ink-soft: #9c93bd;
  --panel: rgba(30, 28, 66, .55);
  --panel-strong: rgba(36, 34, 78, .94);
  --panel-ritual: rgba(30, 28, 66, .55);
  --halo: rgba(20, 18, 45, .92);
  --line: rgba(238, 233, 248, .16);
  --accent: #e0bc62;
  --accent2: #8f9ae0;
  --glow: rgba(224, 188, 98, .4);
  --dot-color: rgba(224, 188, 98, .13);
  --card-border: rgba(255, 255, 255, .88);
  --detail-bg: var(--panel-strong);
  --detail-hover: var(--accent);
  /* 密码门：深色海报那一版 —— 纯黑底 + 银白纹饰 */
  --gate-bg: rgba(10, 8, 9, .93);
  --gate-ink: #cac3b0;
  --gate-field: rgba(202, 195, 176, .1);
  --gate-line: rgba(202, 195, 176, .3);
  --mode-color: var(--clow-gold);
  --name: var(--mode-color);        /* 库洛模式：库洛黄 */
  --name-en: var(--mode-color);
}

body::before, body::after {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  transition: opacity .9s ease;
}
body::before { /* 库洛：夜色深蓝 */
  background: linear-gradient(180deg, #26264e 0%, #191936 55%, #10101f 100%);
  opacity: 1;
}
body::after { /* 小樱：樱花晨光 */
  background: linear-gradient(180deg, #fff8f4 0%, #fdeef3 55%, #f9dfe9 100%);
  opacity: 0;
}
body[data-mode="sakura"]::before { opacity: 0; }
body[data-mode="sakura"]::after  { opacity: 1; }

/* 主题切换时主要界面颜色平滑过渡 */
body, .ritual, .modal-body, .sec-title, .sec-sub, .tagline, .en-title,
footer, .hope-caption p, .modal-desc, .modal-en,
.modal-sym, .modal-sym-label, .modal-past, .modal-past-label,
#count, #count-m, .empty, .hero h1, .card-meta .cn, .card-meta .en, .slot, .toast {
  transition: color .9s ease, background-color .9s ease, border-color .9s ease;
}

body.no-scroll { overflow: hidden; }

/* ---------- 背景装饰 ---------- */
#bg-dots {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background-image: radial-gradient(var(--dot-color) 1.2px, transparent 1.2px);
  background-size: 74px 74px;
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 34%, #000);
  mask-image: linear-gradient(180deg, transparent, #000 34%, #000);
}

/* 竖排 flex + min-height:100vh —— 内容不足一屏时页脚沉在视口底部，
   内容超出一屏时页脚跟在正文末尾（随页面滚动，不是常驻可见） */
.page {
  position: relative; z-index: 1;
  display: flex; flex-direction: column;
  min-height: 100vh;
}

/* ============================================================
   密码门：整页遮罩，解开前看不到也点不到后面的内容。
   ============================================================ */
.gate {
  position: fixed; inset: 0; z-index: 4500;
  overflow: hidden;
  /* 海报直接铺满整页。cover 等价于「把画面放大到盖住视口」，
     而视口比例通常窄于海报的 1.9264，此时整幅画面高度是完整可见的 ——
     所以密码框用视口百分比定位，就等同于压在海报的同一处 */
  background-color: #0a0708;
  background-image: url('./asserts/Dantalian-light.webp');
  background-size: cover;
  background-position: center;
  transition: opacity .9s ease, visibility .9s ease;
}
body[data-mode="clow"] .gate { background-image: url('./asserts/Dantalian-dark.webp'); }
.gate.done { opacity: 0; visibility: hidden; pointer-events: none; }

/* 密码框的纵向落位（坐标均为裁切后海报的像素）：
     208  主标题下沿
     234~262  花饰纹章   ┐
     292~308  Episode 00 ├ 都要盖住
     324~352  The Harlequinade ┘
     354~370  空白
     374~390  下方图案开始浮现
     394+     图案实体
   top 37.6% 是实测出来的**下移上限**
   居中用 left/right + margin auto，不用 transform —— 抖动动画要占用 transform */
.gate-panel {
  position: absolute;
  left: 0; right: 0; top: 37.6%;
  margin: 0 auto;
  width: min(560px, 90%);              /* 加宽到最长那句问题也放得下一行，免得折行把面板撑高 */
  padding: 21px 34px 19px;             /* 上下收窄，左右留白加大 */
  border-radius: 22px; text-align: center;
  background: var(--gate-bg);
  /* 面板若留一点透明，海报上的亮字会透出来；加一层背景模糊彻底糊掉它 */
  backdrop-filter: blur(10px);
  border: 1px solid var(--gate-line);
  box-shadow: 0 30px 70px rgba(0, 0, 0, .5);
  transition: opacity .5s ease, transform .5s ease;
}
.gate.done .gate-panel { opacity: 0; transform: scale(.94); }
.gate-title {
  font-family: var(--font-title); font-weight: 700;
  font-size: 1.45rem; letter-spacing: .24em; text-indent: .24em;
  color: var(--gate-red);                /* 海报的玫瑰红 */
  text-shadow: 0 0 22px rgba(158, 74, 78, .45);
}
.gate-question {
  margin-top: 14px; font-size: .92rem; line-height: 1.9;
  letter-spacing: .06em; color: var(--gate-ink);
}

.gate-inputs { display: flex; justify-content: center; gap: 12px; margin: 15px 0 3px; }
.gate-digit {
  width: 54px; height: 64px;
  border-radius: 13px; border: 1px solid var(--gate-line);
  background: var(--gate-field); color: var(--gate-ink);
  font-family: var(--font-title); font-size: 1.7rem; text-align: center;
  outline: none; caret-color: var(--gate-red);
  transition: border-color .3s, box-shadow .3s, transform .3s;
}
.gate-digit:focus {
  border-color: var(--gate-red); transform: translateY(-2px);
  box-shadow: 0 0 0 3px rgba(158, 74, 78, .28);
}

/* min-height 要正好等于一行的行高（1.7em）：留少了，提示语一出现面板就长高、一消失又缩回去 */
.gate-msg {
  margin-top: 11px; min-height: 1.7em;
  font-size: .8rem; letter-spacing: .08em; line-height: 1.7;
  transition: color .3s;
}
.gate-msg.bad  { color: var(--gate-red); }
/* 答对的提示固定用这个绿 */
.gate-msg.good { color: #68ac70; }
.gate-panel.shake { animation: shake .5s; }
.page > main { flex: 1 0 auto; }
.hidden { display: none !important; }

/* ---------- 头部 ---------- */
.hero { text-align: center; padding: 68px 20px 40px; position: relative; }

/* 页头魔法阵：用线稿素材当遮罩，底色取当前卡组色。
   素材本身是透明底的黑白线稿，alpha 即形状。

   注意魔法阵素材是 base64 内联的（见 magic-circle.css），不是文件路径：
   Chrome 在 file:// 下会拒绝为 CSS mask 加载本地图片，
   遮罩取不到图 → 整个元素被完全遮掉 → 页头看不到魔法阵。
   data: URL 属同源，不受此限制。

   阵形随卡组走：库洛牌状态是库洛阵（五芒星），小樱牌状态是樱阵（星·日·月）。
   只换 --ring-src，位置、尺寸、底色都不动，所以切换时只有形状在变。 */
.hero-ring {
  --ring-src: var(--magic-circle-sakura);
  position: absolute; left: 50%; top: 42%; z-index: 0;
  width: min(500px, 94vw); aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background-color: var(--mode-color);         /* 跟随当前卡组：库洛黄 / 小樱粉 */
  -webkit-mask: var(--ring-src) center / 94% no-repeat;
  mask: var(--ring-src) center / 94% no-repeat;
  opacity: .34;
  animation: ringSpin 100s linear infinite;
  transition: background-color .9s ease;
}
body[data-mode="clow"] .hero-ring { --ring-src: var(--magic-circle-clow); }
.hero > :not(.hero-ring) { position: relative; z-index: 1; }

@keyframes ringSpin {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to   { transform: translate(-50%, -50%) rotate(360deg); }
}

.hero .en-title {
  letter-spacing: .46em; font-size: .78rem; color: var(--ink-soft);
  text-transform: uppercase; text-indent: .46em;
}
.hero h1 {
  font-family: var(--font-title); font-weight: 700;
  font-size: clamp(2.2rem, 6vw, 3.4rem);
  letter-spacing: .2em; text-indent: .2em; margin-top: 10px;
  text-shadow: 0 2px 18px var(--glow);
}
.hero .tagline { margin-top: 18px; color: var(--ink-soft); font-size: .9rem; letter-spacing: .05em; }

/* ---------- 区块 ---------- */
section { max-width: 1100px; margin: 0 auto; padding: 12px 12px; }
.sec-title { text-align: center; font-family: var(--font-title); font-size: 1.5rem; letter-spacing: .22em; text-indent: .22em; }
#special .sec-title { color: var(--hope-red); text-shadow: 0 0 20px rgba(198, 42, 58, .4); }   /* 特别篇标题固定希望红 */
.sec-sub { text-align: center; color: var(--ink-soft); font-size: .84rem; margin-top: 16px; letter-spacing: .06em; }
.sec-sub strong { font-weight: 700; color: var(--ink); }   /* 加黑强调关键词 */

/* ---------- 图鉴工具栏 ---------- */
.toolbar {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: 16px; margin: 22px auto 0; max-width: 560px; padding: 0 20px;
}
#search {
  flex: 1; min-width: 220px; border: 1px solid var(--line);
  background: var(--panel); color: var(--ink); border-radius: 999px;
  padding: 10px 22px; font-size: .9rem; outline: none;
  backdrop-filter: blur(8px);
  transition: box-shadow .3s, border-color .3s, background-color .9s, color .9s;
  font-family: var(--font-ui);
}
#search::placeholder { color: var(--ink-soft); }
#search:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--glow); }
#count { color: var(--ink-soft); font-size: .8rem; white-space: nowrap; letter-spacing: .05em; }
#count.revealed { color: var(--name); }
.empty { text-align: center; color: var(--ink-soft); padding: 34px 0; font-size: .9rem; letter-spacing: .1em; }

/* ---------- 卡牌网格 ---------- */
/* 行距 / 卡片顶部留白都给足，hover 上浮时不会被上一行遮挡 */
.grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 9px 18px; margin-top: 18px;
}

.card {
  --flip: 0deg; position: relative; padding-top: 14px;
  user-select: none; -webkit-user-select: none;
}
.card.flipped { --flip: 180deg; }
.card.hidden { display: none; }

.flip-zone {
  position: relative; display: block; width: 100%;
  aspect-ratio: var(--card-ratio);
  border: 0; background: none; padding: 0; cursor: grab;
  perspective: 900px;
  touch-action: pan-y;               /* 移动端保留纵向滚动 */
}
.flip-zone:active { cursor: grabbing; }
.flip-inner {
  position: relative; width: 100%; height: 100%;
  transform-style: preserve-3d;
  transform: rotateY(var(--flip));
  transition: transform .7s cubic-bezier(.35, .05, .25, 1);
}
.card:hover .flip-inner { transform: rotateY(var(--flip)) translateY(-8px); }

.flip-face {
  position: absolute; inset: 0;
  backface-visibility: hidden; -webkit-backface-visibility: hidden;
  border-radius: var(--radius); overflow: hidden;
  box-shadow: 0 10px 26px rgba(15, 10, 30, .28);
  transition: box-shadow .4s;
}
.flip-face img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  -webkit-user-drag: none; user-select: none;
}
.flip-face.front { border: 3px solid var(--frame, var(--card-border)); }
.flip-face.back { transform: rotateY(180deg); border: 3px solid var(--card-border); }
.card:hover .flip-face { box-shadow: 0 16px 38px rgba(15, 10, 30, .4), 0 0 22px var(--glow); }

.card-no {
  position: absolute; top: 8px; left: 8px; z-index: 3;
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: .6rem; color: #fff;
  background: rgba(25, 15, 40, .5); backdrop-filter: blur(4px);
  pointer-events: none; transition: opacity .3s;
}
.card.flipped .card-no { opacity: 0; }

/* 卡名：库洛模式统一库洛黄，小樱模式统一小樱粉 */
.card-meta {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin-top: 12px;
}
.card-meta .cn {
  font-family: var(--font-title); font-size: 1rem; letter-spacing: .12em;
  color: var(--name);
}
.card-meta .en {
  font-size: .68rem; letter-spacing: .04em; color: var(--name-en);
  opacity: .82;
}

/* 隐藏之牌的特别样式 */
.card.special { animation: specialIn .65s cubic-bezier(.2, 1.4, .4, 1); }
@keyframes specialIn { from { opacity: 0; transform: scale(.82) translateY(12px); } }
/* 带特征色描边的卡：正面与背面都要描边，翻面之后同样成立 */
.framed .flip-face.front,
.framed .flip-face.back,
.modal-body.is-special .modal-card .flip-face.front,
.modal-body.is-special .modal-card .flip-face.back {
  border-color: var(--frame);
  box-shadow: 0 0 0 1px var(--frame), 0 0 24px var(--frame), 0 10px 26px rgba(15, 10, 30, .28);
}

/* ---------- 拖拽 ---------- */
.drag-src { opacity: .34; }
.drag-src .flip-inner { transform: rotateY(var(--flip)) scale(.96); }

.drag-ghost {
  position: fixed; left: 0; top: 0; z-index: 4000;
  pointer-events: none; border-radius: var(--radius);
  overflow: hidden; will-change: transform;
  box-shadow: 0 22px 50px rgba(15, 10, 30, .5), 0 0 26px var(--frame, var(--glow));
  transform-origin: center;
}
.drag-ghost img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  border: 3px solid var(--frame, var(--card-border)); border-radius: var(--radius);
}
.drag-ghost.returning { transition: transform .3s cubic-bezier(.3, .7, .3, 1), opacity .3s; opacity: 0; }

body.dragging { cursor: grabbing; }

/* ---------- 特别篇 · 希望之牌合成 ---------- */
.ritual {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: 44px; margin-top: 26px; padding: 40px 24px;
  border-radius: 20px; background: var(--panel-ritual);
  border: 1px solid var(--line); backdrop-filter: blur(10px);
  transition: box-shadow .4s, background-color .9s;
}
/* 拖牌时整块合成台亮起：库洛黄 / 小樱粉，跟着当前卡组走 */

.drop-zone { display: flex; align-items: center; gap: 18px; }
.drop-zone .plus {
  font-size: 1.6rem; color: var(--sakura-pink); opacity: .8;
  text-shadow: 0 0 14px var(--sakura-pink);
  animation: plusPulse 2.2s ease-in-out infinite;
}
@keyframes plusPulse { 0%, 100% { opacity: .5; transform: scale(1); } 50% { opacity: 1; transform: scale(1.14); } }

.slot {
  position: relative; width: 132px; aspect-ratio: var(--card-ratio);
  border: 2px dashed var(--line); border-radius: var(--radius);
  display: grid; place-items: center;
  transition: border-color .35s, box-shadow .35s, transform .35s, background-color .35s;
}
/* 透明的判定外圈：视觉框不变，但拖牌时不必精确对准边框。
   伪元素不会被 elementFromPoint 直接返回，命中的始终是 .slot 本身 */
.slot::before {
  content: ""; position: absolute;
  inset: calc(-1 * var(--hit));
  border-radius: inherit;
}
.slot .slot-ph {
  font-family: var(--font-title); font-size: 2.2rem; color: var(--ink-soft); opacity: .45;
  margin-left: 10px; transition: opacity .3s;
}
.slot.drag-over {
  border-color: var(--mode-color); transform: scale(1.04);
  box-shadow: 0 0 26px var(--mode-color), inset 0 0 20px var(--mode-color);
}
.slot.filled { border-style: solid; border-color: transparent; }
.slot.filled .slot-ph { opacity: 0; }
.slot.reject { animation: shake .5s; border-color: #e0687a; box-shadow: 0 0 20px rgba(224, 104, 122, .5); }
.slot.accepted { animation: slotPop .55s cubic-bezier(.2, 1.5, .4, 1); }
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-8px); }
  40% { transform: translateX(7px); }
  60% { transform: translateX(-5px); }
  80% { transform: translateX(3px); }
}
@keyframes slotPop {
  0% { transform: scale(1); }
  45% { transform: scale(1.1); box-shadow: 0 0 30px var(--glow); }
  100% { transform: scale(1); }
}

.slot-card {
  position: absolute; inset: 0; border-radius: var(--radius);
  transition: transform .62s cubic-bezier(.4, 0, .2, 1), opacity .62s ease;
  will-change: transform, opacity;
}
.slot-card img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  border-radius: var(--radius); border: 3px solid var(--frame, var(--card-border));
  box-shadow: 0 10px 26px rgba(15, 10, 30, .3), 0 0 22px var(--frame, transparent);
}

.ritual-arrow {
  font-size: 1.7rem; color: var(--hope-red);
  text-shadow: 0 0 16px var(--hope-red);
  animation: plusPulse 2.6s ease-in-out infinite;
}

/* ============================================================
   两侧抽屉：合成台（左）· 诗篇（右）
   收起时只露出边缘一块竖牌，展开时整块滑入；拖牌靠近会自动展开
   ============================================================ */
.drawer {
  --tab-w: 46px;
  position: fixed; top: 0; bottom: 0; z-index: 700;
  width: min(400px, 94vw);
  transition: transform .42s cubic-bezier(.3, .7, .3, 1);
  will-change: transform;
}
.drawer-left  { left: 0;  width: min(350px, 94vw); transform: translateX(calc(-100% + var(--tab-w))); }
.drawer-right { right: 0; width: min(680px, 94vw); transform: translateX(calc(100% - var(--tab-w))); }
.drawer.open  { transform: translateX(0); }

.drawer-tab {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: var(--tab-w); height: 188px;
  border: 1px solid var(--line); border-radius: 0 14px 14px 0;
  background: var(--panel-strong); backdrop-filter: blur(14px);
  color: var(--ink); cursor: pointer; padding: 0;
  display: grid; place-items: center;
  transition: box-shadow .4s, color .4s, background-color .9s, border-color .9s;
}
.drawer-right .drawer-tab { border-radius: 14px 0 0 14px; }
.drawer-tab-text {
  writing-mode: vertical-rl; text-orientation: upright;
  font-family: var(--font-title); font-size: .95rem;
  letter-spacing: .26em; text-indent: .26em;
}
.drawer-tab:hover { color: var(--mode-color); }

.drawer-body {
  position: absolute; top: 0; bottom: 0;
  background: var(--panel-strong); backdrop-filter: blur(16px);
  border: 1px solid var(--line);
  overflow-y: auto; overflow-x: hidden;
  padding: 22px 18px 26px;
  transition: box-shadow .4s, background-color .9s, border-color .9s;
  scrollbar-width: thin;
  display: flex; flex-direction: column;
}
.drawer-left .drawer-tab  { right: 0; }
/* 左侧合成区不要外框 */
.drawer-left .drawer-body { left: 0;  right: var(--tab-w); border-radius: 0 18px 18px 0; border: 0; }
.drawer-right .drawer-tab  { left: 0; }
/* 右侧面板不要外框 */
.drawer-right .drawer-body { left: var(--tab-w); right: 0; border-radius: 18px 0 0 18px; border: 0; }


/* 拖牌时两侧边缘发亮；拖到附近时亮得更强，同时抽屉自动展开 */
body.dragging .drawer-tab,
body.dragging .drawer-body { box-shadow: 0 0 0 1px var(--mode-color), 0 0 26px var(--mode-color); }
.drawer.near .drawer-tab,
.drawer.near .drawer-body  { box-shadow: 0 0 0 2px var(--mode-color), 0 0 48px var(--mode-color); }

/* 抽屉内的区块不再需要自己的留白与限宽 */
.drawer-body section { max-width: none; margin: 0; padding: 0; }
/* 内容垂直居中（水平方向靠 align-items:stretch 撑满）。
   用 margin:auto 而不是 justify-content:center ——
   内容超出抽屉高度时不会把顶部裁掉，仍可正常滚动。
   注意只能 auto 上下：左右也 auto 的话 section 会收缩成内容宽度，魔法阵会塌掉。
   也必须写在上一条之后：两者特异性相同，靠顺序取胜 */
.drawer-body > section { margin: auto 0; width: 100%; }
.drawer-body .sec-title { font-size: 1.08rem; letter-spacing: .16em; text-indent: .16em; }
.drawer-body .sec-sub { font-size: .74rem; margin-top: 8px; line-height: 1.8; }

/* ============================================================
   右侧抽屉的诗篇密码。
   解开前 #poem 一直 display:none，抽屉里只剩这一层，
   所以它不需要边框、也不需要底色 —— 抽屉本身就是它的背景。

   竖排的字格高度取两行密码中心距的一半（--lk-text-pitch），各列再整体垂直居中于
   两行的中线上。于是「吾问汝」三个字正好落在 第一行／两行中间／第二行，
   其余各列按同一字距从这条中线往两边排。
   行距要调就改 --lk-row-gap，字的间距会跟着走，对齐不会散。
   ============================================================ */
.drawer-lock {
  --lk-digit-w: 46px;
  --lk-digit-h: 52px;
  --lk-col-gap: 10px;
  --lk-row-gap: 30px;
  --lk-pitch: calc(var(--lk-digit-h) + var(--lk-row-gap));   /* 两行中心的距离 */
  --lk-text-pitch: calc(var(--lk-pitch) / 2);                /* 竖排的字距 */
  --lk-side-gap: 44px;                  /* 密码框与两侧竖排文字的间距 */

  position: absolute; inset: 0; z-index: 1;
  display: flex; justify-content: center;
  padding: 24px 18px;
  transition: opacity .7s ease;
}
/* 上下居中用 margin:auto，内容超高时不会把顶部裁掉 */
.lock-center {
  margin: auto 0;
  display: flex; flex-direction: column; align-items: center;
}
/* 两侧竖排文字的定位基准 = 密码框本身这一层。
   不能直接用 .lock-center：下面那行宣告比密码框宽得多，会把容器撑开，
   两列文字就会被挤出抽屉外面 */
.lock-rows { position: relative; }
/* 解开后整层退场，露出下面的诗篇 */
.drawer-lock.done { opacity: 0; pointer-events: none; }

/* ---------- 背景里的心锁 ----------
   平时是抽屉里一个很淡的暗纹（别跟输入框抢），解开的一刻才亮起来。
   整套动效挂在 .lit 上，靠 delay 排成一条单行道，一段接一段：
        0ms  两行密码消失，左侧两列同时开始浮现（→1670）
     1750ms  钥匙飞入（1750→2250）
     2330ms  宣告铺第一行（每字 38ms，23 字 →3166）
     3300ms  宣告铺第二行（每字 62ms， 8 字 →3734）
     3750ms  末句弹一下（→4450）
     4500ms  钥匙拧转（→5050）
     5050ms  锁孔爆光（→5450）
     5450ms  一记长闪，锁层在闪光底下淡出、诗篇同时切出
   竖排文字的逐字间隔在 app.js 的 LOCK_LINE_STEP / LOCK_LINE_GAP；
   钥匙那条的百分比 × 3700ms + 1750ms = 绝对时刻 */
.lock-art {
  position: absolute; left: 50%; top: 50%; z-index: 0;
  /* 按抽屉高度定尺寸，不按宽度：底下那行宣告是钉在抽屉底端的固定高度，
     锁若只跟宽度走，窗口一矮两者就会撞上 */
  height: min(48%, 460px);
  aspect-ratio: 666 / 900;
  transform: translate(-50%, -50%);
  background: url('./asserts/lock.webp') center / contain no-repeat;
  opacity: .24; pointer-events: none;
  transition: opacity .9s ease, filter .9s ease, transform 1.1s ease;
}
.drawer-lock.lit .lock-art {
  opacity: .95;
  transform: translate(-50%, -50%) scale(1.06);
  filter: drop-shadow(0 0 34px rgba(198, 42, 58, .45));
}

/* 锁孔里爆出来的那团光 */
.lock-art-glow {
  position: absolute; left: 48.6%; top: 58.4%; z-index: 1;
  width: 64%; aspect-ratio: 1;
  transform: translate(-50%, -50%) scale(.15);
  border-radius: 50%; opacity: 0; pointer-events: none;
  background: radial-gradient(circle,
              rgba(255, 238, 214, .95) 0%,
              rgba(226, 92, 86, .5) 28%,
              rgba(198, 42, 58, 0) 66%);
}
.drawer-lock.lit .lock-art-glow { animation: keyholeBloom 400ms ease-out 5050ms both; }
@keyframes keyholeBloom {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.15); }
  35%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: .9; transform: translate(-50%, -50%) scale(1.5); }
}

/* 钥匙。外层负责飞入与「推进去」的位移，内层只负责绕尖端旋转 ——
   拆成两层是因为 transform 一次只能被一条动画接管 */
.lock-key {
  position: absolute; left: 48.6%; top: 58.4%; z-index: 2;
  width: 28%; aspect-ratio: 475 / 1200;
  transform: translate(-50%, -100%);
  perspective: 760px;                          /* 给拧转的 3D 一点纵深 */
  opacity: 0; pointer-events: none;
}
.lock-key-body {
  width: 100%; height: 100%;
  background: url('./asserts/key.webp') center / contain no-repeat;
  transform-origin: 50% 100%;                  /* 绕尖端这根竖轴转，即「插在锁孔里拧」 */
  filter: drop-shadow(0 3px 10px rgba(0, 0, 0, .55));
}
/* 钥匙外层要走完 1400→4800 整条线：飞入 → 停在锁孔上等宣告铺完 → 推入 → 拧 → 沉进锁孔。
   一条动画包办是因为 transform 一次只能被一条动画接管 */
.drawer-lock.lit .lock-key      { animation: keyFly  3700ms linear 1750ms both; }
.drawer-lock.lit .lock-key-body { animation: keyTurn  550ms cubic-bezier(.4, .05, .3, 1) 4500ms both; }

/* 百分比 × 3700ms + 1750ms = 绝对时刻（见上方时间线） */
@keyframes keyFly {
  0%    { opacity: 0; transform: translate(-50%, -100%) translate(210px, -46px) rotate(-38deg) scale(1.35);
          animation-timing-function: cubic-bezier(.2, .9, .25, 1); }      /* 1750 飞入起始 */
  13.5% { opacity: 1; transform: translate(-50%, -100%) translate(0, 0)         rotate(0deg)   scale(1);
          animation-timing-function: linear; }                            /* 2250 到位，停在锁孔上等宣告 */
  74%   { opacity: 1; transform: translate(-50%, -100%) translate(0, 0)         rotate(0deg)   scale(1);
          animation-timing-function: ease-out; }                          /* 4488 宣告铺完 */
  78%   { opacity: 1; transform: translate(-50%, -100%) translate(0, 7px)       rotate(0deg)   scale(1);
          animation-timing-function: linear; }                            /* 4636 往里推到底 */
  89%   { opacity: 1; transform: translate(-50%, -100%) translate(0, 7px)       rotate(0deg)   scale(1);
          animation-timing-function: ease-in; }                           /* 5043 拧完 */
  100%  { opacity: 0; transform: translate(-50%, -100%) translate(0, 36px)      rotate(0deg)   scale(.46); }
                                                                          /* 5450 沉进锁孔 */
}
/* 绕自身长轴转半圈：钥匙始终插在锁孔里，不会甩到锁外面去 */
@keyframes keyTurn {
  from { transform: rotateY(0deg); }
  to   { transform: rotateY(180deg); }
}

/* 一答对，两行密码立刻让位（这一刻锁也同时亮起来） */
.drawer-lock.lit .lock-grid { animation: gridAway 520ms ease-in both; }
@keyframes gridAway {
  0%   { opacity: 1; transform: scale(1);     filter: blur(0); }
  100% { opacity: 0; transform: scale(.88);   filter: blur(6px); }
}

/* ---------- 两行密码 ---------- */
.lock-grid {
  display: grid;
  grid-template-columns: repeat(6, var(--lk-digit-w));
  gap: var(--lk-row-gap) var(--lk-col-gap);
}
/* 与主页密码门同款：同样用 gate-* 那套色。
   之前边框用的是通用的 --line，比 --gate-line 淡一大截，格子看着是虚的 */
.lock-digit {
  width: var(--lk-digit-w); height: var(--lk-digit-h);
  border-radius: 13px; border: 1px solid var(--gate-line);
  background: var(--gate-field); color: var(--gate-ink);
  font-family: var(--font-title); font-size: 1.5rem; text-align: center;
  outline: none; caret-color: var(--mode-color);
  transition: border-color .3s, box-shadow .3s, transform .3s, color .3s;
}
.lock-digit:focus {
  border-color: var(--mode-color); transform: translateY(-2px);
  box-shadow: 0 0 0 3px var(--glow);
}
.lock-msg {
  margin-top: 14px; min-height: 1.7em; text-align: center;
  font-size: .84rem; letter-spacing: .08em; line-height: 1.7;
  transition: color .3s;
}
.lock-msg.bad { color: var(--gate-red); }
.lock-center.shake { animation: shake .5s; }

/* ---------- 竖排两列文字 ----------
   绝对定位挂在密码框两侧，整体垂直居中于两行的中线。
   这个「中线」是两行中心的中点，也就是密码框自身的中线：让各列都居中到它上面，
   三字的列自然就落在 第一行／中／第二行；字数不同的列则从这条线往两边均分。
   两侧都用 row-reverse：DOM 里靠前的那一列排在右边，读起来从右向左 */
.lock-side {
  position: absolute;
  top: calc(var(--lk-digit-h) + var(--lk-row-gap) / 2);   /* 密码框的中线 */
  transform: translateY(-50%);
  display: flex; align-items: center; gap: 16px;
}
.lock-answer { right: calc(100% + var(--lk-side-gap)); flex-direction: row-reverse; }
.lock-riddle { left: calc(100% + var(--lk-side-gap)); flex-direction: row-reverse; }
.lock-side .vline { display: flex; flex-direction: column; }
.lock-side .vline span {
  height: var(--lk-text-pitch);         /* 字格高度 = 两行中心距的一半，对齐就靠这个 */
  display: grid; place-items: center;
  font-family: var(--font-title); font-size: 1.3rem; line-height: 1;
  color: var(--ink); white-space: nowrap;
}
/* 竖排里的逗号该落在字格的右上，而横排字形的墨迹在左下 —— 挪过去。
   用 position:relative 的 top/left 而不是 transform：浮现动画占着 transform */
.lock-side .vline span.punct { position: relative; top: -.7em; left: .32em; }

/* 两侧的竖排文字同一个待遇：平时藏着，展开抽屉／答对时按阅读顺序一个个浮现。
   两边同色，不区分问与答。
   区别只在于「怎么重放」：问句每次拉开抽屉都要演一遍，交给 JS 用 Web Animations 驱动
   （见 riseRiddle 的注释），所以这里不挂 transition；答句只演一次，CSS 过渡就够 */
.lock-riddle .vline span,
.lock-answer .vline span {
  color: var(--mode-color);
  text-shadow: 0 0 18px var(--glow);
  opacity: 0; transform: translateY(7px);
}
.lock-answer .vline span { transition: opacity .55s ease, transform .55s ease; }
.drawer-lock.risen   .lock-riddle .vline span { opacity: 1; transform: none; }
.drawer-lock.solved .lock-answer .vline span { opacity: 1; transform: none; }

/* ---------- 解开封印的宣告 ----------
   固定用希望红，不跟随卡组 —— 这是「最重要的一张」的颜色。
   两行：长句在前，末句单独一行、更大更重。
   浮现做得比别处重：逐字从模糊里凝出来并升起，末句的间距由宽收到常宽，
   落定的同时红光炸一下，背后再涨起一团红光 */
.lock-open {
  position: absolute; left: 20px; right: 20px; z-index: 3;
  /* 垂直居中于「锁的下沿」与「抽屉下沿」之间，而不是贴着最底下。
     锁心落在抽屉中线、缩放后高 min(48%,460px)×1.06，
     所以锁的下沿在 75% 处再加 1/4 个锁高，取这条线的中点即可 */
  top: calc(75% + min(12.72%, 122px));
  transform: translateY(-50%);
  padding-bottom: 9px;
  font-family: var(--font-title);
  text-align: center; color: var(--hope-red);
}
/* 末句落定后，底下收一道红线，像落款 */
.lock-open::after {
  content: ''; position: absolute; left: 50%; bottom: 0; z-index: 1;
  width: 66%; height: 1px;
  transform: translateX(-50%) scaleX(0);
  background: linear-gradient(90deg, transparent,
              var(--hope-red) 22%, var(--hope-red) 78%, transparent);
  transition: transform 1s cubic-bezier(.2, 1, .3, 1) 2.3s;
}
.drawer-lock.lit .lock-open::after { transform: translateX(-50%) scaleX(1); }

/* 宣告压在锁身上，背后是明暗交错的金饰 —— 借页面现成的 --halo 描一圈边把它托出来。
   辉光改用 filter: drop-shadow（见 hopeBurst），这样描边不会被动画覆盖掉 */
.lock-open p {
  position: relative; z-index: 1;
  text-shadow: 2px 0 0 var(--halo), -2px 0 0 var(--halo),
               0 2px 0 var(--halo), 0 -2px 0 var(--halo),
               1px 1px 0 var(--halo), -1px -1px 0 var(--halo),
               1px -1px 0 var(--halo), -1px 1px 0 var(--halo);
}
.lock-open-l1 { font-size: 1.3rem; line-height: 1.9; letter-spacing: .12em; }
.lock-open-l2 {
  margin-top: 2px;
  font-size: 1.8rem; font-weight: 700; line-height: 1.7;
  letter-spacing: .5em;                        /* 由宽收到常宽，落定时更有分量 */
  transition: letter-spacing 1s cubic-bezier(.2, 1, .3, 1) 2.2s;
}
.drawer-lock.lit .lock-open-l2 { letter-spacing: .2em; }

/* 逐字：从模糊里凝出来，一边升起一边收实 */
.lock-open span {
  display: inline-block; opacity: 0;
  filter: blur(8px);
  transform: translateY(12px) scale(1.06);
  transition: opacity .5s ease,
              filter .65s ease,
              transform .8s cubic-bezier(.2, 1.3, .4, 1);
}
.drawer-lock.lit .lock-open span { opacity: 1; filter: blur(0); transform: none; }

/* 末句落定的一刻：往外撑一下再收回来。
   刻意不带辉光 —— 文字背后那团红光已经去掉了，这里只留动作 */
.drawer-lock.lit .lock-open-l2 { animation: hopeBurst 700ms cubic-bezier(.2, 1.2, .3, 1) 3.75s both; }
@keyframes hopeBurst {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.07); }
  100% { transform: scale(1); }
}

/* 窗口矮的时候把行距收一收，整组还是一起缩，不破坏对齐 */
@media (max-height: 720px) {
  .drawer-lock { --lk-digit-h: 44px; --lk-row-gap: 22px; --lk-side-gap: 30px; }
  .lock-side .vline span { font-size: 1.15rem; }
}

/* --- 左侧：合成台改成竖排，整体缩小一档以适配抽屉 --- */
.drawer-left .ritual {
  flex-direction: column; gap: 14px; margin-top: 20px;
  padding: 0; background: none; border: 0; backdrop-filter: none;
}
.drawer-left .drop-zone { gap: 10px; }
.drawer-left .drop-zone .slot { width: 104px; }
.drawer-left .drop-zone .slot .slot-ph { font-size: 1.7rem; }
.drawer-left .ritual-arrow { font-size: 1.1rem; animation: none; }
/* 牌面宽度收在舞台这一层（--face-w），卡面与那颗阵都读它，不会再各写各的 */
.drawer-left .hope-stage { width: 150px; height: 328px; --face-w: 96px; }
.drawer-left .hope-caption h3 { font-size: .95rem; }
.drawer-left .hope-caption p { font-size: .66rem; }
.drawer-left .hope-tag { font-size: .62rem; }

/* --- 右侧：魔法阵按抽屉宽度缩放，字号相应放大 --- */
.drawer-right .poem-circle { width: 100%; margin-top: 18px; }
.drawer-right .poem-title { font-size: 1.32rem; letter-spacing: .18em; }
.drawer-right .poem-line { font-size: 1.15rem; letter-spacing: .06em; line-height: 2; }
.drawer-right .poem-last { font-size: 2.5rem; letter-spacing: .2em; }
.drawer-right .poem-slot .slot-ph { font-size: 1.1rem; }
.drawer-right .sec-sub { font-size: .82rem; }

/* 左侧那颗阵的圆心由 .stage-ring 自己按 --face-w / --face-h 算，这里不用再管 */
.drawer-left .motto { margin-top: 22px; text-align: center; }
.drawer-left .motto .m-hope { font-family: var(--font-title); font-size: .92rem; }

/* 窄屏时抽屉铺满，免得内容被压得太小 */
@media (max-width: 700px) {
  .drawer { width: 100vw !important; }
  .drawer-body { border-radius: 0; }
}

/* ---------- 你手捧希望而来 · 魔法阵 ---------- */
.poem-circle {
  position: relative; margin: 24px auto 0;
  width: min(660px, 94vw); aspect-ratio: 1;
}

/* 与页头同一个素材：底色给卡组色，遮罩给形状。
   尺寸：77% × 遮罩的 94% ≈ 72% → 半径 36%，正好穿过四个卡位的中心 */
.poem-ring {
  position: absolute; left: 50%; top: 50%;
  width: 77%; aspect-ratio: 1; transform: translate(-50%, -50%);
  background-color: var(--mode-color);
  -webkit-mask: var(--magic-circle-sakura) center / 94% no-repeat;
  mask: var(--magic-circle-sakura) center / 94% no-repeat;
  opacity: .26; pointer-events: none;
  transition: opacity .8s ease, background-color .9s ease;
}
/* 集齐后进入「充能」：先加速到高速，随后持续自转并一呼一吸地发亮 */
/* 整个蓄力期间一路加速：缓动用强 ease-in（斜率越来越陡），
   转速从静止一路推到最快，到揭晓那一刻正好接上后面的减速。
   时长由 JS 写成 --charge-ms，与 CHARGE_MS 同源，改一处即可 */
.poem-circle.charging .poem-ring {
  animation: ringSpinUp var(--charge-ms, 4400ms) cubic-bezier(.45, .05, .85, .6) forwards,
             chargePulse 1.4s ease-in-out infinite;
}
@keyframes ringSpinUp {
  from { transform: translate(-50%, -50%) rotate(0deg) scale(1); }
  to   { transform: translate(-50%, -50%) rotate(1080deg) scale(1.12); }   /* 三整圈，落点角度与起点一致 */
}
@keyframes chargePulse {
  0%, 100% { opacity: .5; }
  50%      { opacity: 1; }
}
/* 揭晓后：从高速缓缓减到常速，时长与五个字现身同步。
   --r0 由 JS 按当前实际角度写入，这样接续处不会「跳角度」。
   缓动终点斜率 = (1-.99)/(1-.5) = .02，恰是 360°/1.5s 与 360°/72s 的比值，
   也就是减速结束时正好落在常速自转上 */
/* 起点斜率 .41/.15 ≈ 2.73 → 起始 656°/s，正好接上蓄力段结束时的 655°/s；
   终点斜率 (1-.99)/(1-.5) = .02，正好接上后面的常速自转。 */
.poem-circle.slowing .poem-ring { animation: ringSpinDown 1.5s cubic-bezier(.15, .41, .5, .99) forwards; }
@keyframes ringSpinDown {
  from { transform: translate(-50%, -50%) rotate(var(--r0, 0deg)) scale(1.1); }
  to   { transform: translate(-50%, -50%) rotate(calc(var(--r0, 0deg) + 360deg)) scale(1); }
}
.poem-circle.complete .poem-ring { opacity: .58; animation: ringSpinSlow 72s linear infinite; }
@keyframes ringSpinSlow {
  from { transform: translate(-50%, -50%) rotate(calc(var(--r0, 0deg) + 360deg)); }
  to   { transform: translate(-50%, -50%) rotate(calc(var(--r0, 0deg) + 720deg)); }
}

/* 四个方向 + 阵心。用 margin 百分比定位，避免和 .slot.drag-over 的 transform 打架。
   牌宽 12% → 高 27%，半高 13.5%、半宽 6%；让四个牌心距圆心恰好 36%，与阵线重合 */
.poem-slot { position: absolute; width: 12%; }
.poem-slot .slot-ph { font-size: clamp(.85rem, 2vw, 1.25rem); margin-left: 6px; }
.dir-n { left: 0; right: 0; top: .5%; margin: 0 auto; }
.dir-s { left: 0; right: 0; bottom: .5%; margin: 0 auto; }
.dir-w { left: 8%; top: 50%; margin: -13.5% auto 0 0; }
.dir-e { right: 8%; top: 50%; margin: -13.5% 0 0 auto; }
.poem-center { left: 0; right: 0; top: 50%; width: 19%; margin: -21.375% auto 0; }

/* 诗压在阵心那张牌的上一层；pointer-events 关掉，卡槽才收得到落点 */
.poem-text {
  position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(-50%);
  width: 66%; margin: 0 auto;
  z-index: 3; pointer-events: none;
  text-align: center;
}

/* 先描一圈实边，再叠一层柔光，压在任何卡面上都读得清 */
.poem-title, .poem-line { text-shadow: var(--outline), 0 0 12px var(--halo); }

.poem-title {
  font-family: var(--font-title); font-size: clamp(.9rem, 2.6vw, 1.35rem);
  letter-spacing: .22em; text-indent: .22em; margin-bottom: 12px;
}

.poem-line {
  font-family: var(--font-title); font-size: clamp(.82rem, 2.2vw, 1.15rem);
  letter-spacing: .1em; line-height: 2;
}

/* 遮住的方块：起初整首都是它 */
.poem-mask {
  color: var(--ink); opacity: .72; letter-spacing: .02em;
  transition: color .9s ease, opacity .9s ease, text-shadow .9s ease;
}
/* 题目揭晓后的文字与诗中非关键字同样式，只有关键字「希望」保留特征色 */
.poem-title .poem-mask.shown,
.poem-line .poem-mask.shown { color: var(--ink); opacity: .9; }

/* 关键字：揭开后顶掉相应数量的方块，加粗 + 卡组色 + 双层光晕 */
.poem-key {
  display: inline-block; font-style: normal;
  color: var(--ink); opacity: .72;
  transition: color .9s ease, opacity .9s ease, text-shadow .9s ease;
}
.poem-key.lit {
  color: var(--mode-color); opacity: 1; font-weight: 700;
  text-shadow: var(--outline), 0 0 18px var(--mode-color), 0 0 38px var(--mode-color);
}
.poem-key.pop { animation: keyPop .85s cubic-bezier(.2, 1.5, .4, 1); }
@keyframes keyPop {
  0%   { transform: scale(.3); opacity: 0; }
  55%  { transform: scale(1.35); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
/* 题目里的「希望」同一套逻辑，只换成希望红 */
.poem-title .poem-key.lit {
  color: var(--hope-red);
  text-shadow: var(--outline), 0 0 20px var(--hope-red), 0 0 42px var(--hope-red);
}

/* 最后一句：全部归位之前只占位，每个字单独隐藏、逐个炸开 */
.poem-last {
  position: relative;
  font-family: var(--font-title); font-size: clamp(1.7rem, 6.2vw, 3.4rem);
  letter-spacing: .24em; text-indent: .24em;
  color: var(--hope-red); margin-top: 18px;
  text-shadow: var(--outline), 0 0 24px rgba(198, 42, 58, .7);
}
.poem-last span { display: inline-block; opacity: 0; }
.poem-last.show span { animation: charIn .95s cubic-bezier(.15, 1.4, .35, 1) forwards; }
/* 五个字依次登场，每个都从虚、小、歪里炸出来 */
.poem-last.show span:nth-child(1) { animation-delay: 0s; }
.poem-last.show span:nth-child(2) { animation-delay: .14s; }
.poem-last.show span:nth-child(3) { animation-delay: .28s; }
.poem-last.show span:nth-child(4) { animation-delay: .42s; }
.poem-last.show span:nth-child(5) { animation-delay: .56s; }
@keyframes charIn {
  0%   { opacity: 0; transform: scale(.15) rotate(-14deg); filter: blur(12px); }
  55%  { opacity: 1; transform: scale(1.4) rotate(4deg);   filter: blur(0); }
  75%  { transform: scale(.94) rotate(-2deg); }
  100% { opacity: 1; transform: scale(1) rotate(0); filter: blur(0); }
}
/* 五个字都落定后，开始呼吸式辉光 */
.poem-last.show { animation: loveBreathe 3.4s ease-in-out 1.2s infinite; }
@keyframes loveBreathe {
  0%, 100% { text-shadow: var(--outline), 0 0 24px rgba(198, 42, 58, .7); }
  50%      { text-shadow: var(--outline), 0 0 52px rgba(198, 42, 58, 1), 0 0 96px rgba(198, 42, 58, .55); }
}
/* 魔法阵受击震动：幅度拉到 22px、接近 2 度，蓄力阶段会反复触发 */
.poem-circle.quake { animation: quake 1.1s cubic-bezier(.36, .07, .19, .97); }
@keyframes quake {
  0%, 100% { transform: translate(0, 0) rotate(0deg) scale(1); }
  8%  { transform: translate(-22px, 11px) rotate(-1.9deg) scale(1.012); }
  18% { transform: translate(20px, -14px) rotate(1.7deg) scale(.994); }
  30% { transform: translate(-17px, -9px) rotate(-1.3deg) scale(1.008); }
  44% { transform: translate(14px, 12px) rotate(1deg) scale(.996); }
  58% { transform: translate(-10px, 7px) rotate(-.7deg) scale(1.004); }
  72% { transform: translate(7px, -5px) rotate(.45deg) scale(.998); }
  86% { transform: translate(-3px, 3px) rotate(-.2deg) scale(1); }
}

.poem-circle.complete { filter: drop-shadow(0 0 36px rgba(198, 42, 58, .3)); }
.poem-foot { display: flex; justify-content: center; margin-top: 22px; }

/* ---------- 希望之牌舞台 ---------- */
.hope-stage {
  /* 卡面尺寸只留一个来源：宽写在这里，高按「卡面统一 4:9」（:root 的 --card-ratio
     → 高的倍数 2.25）推算。合成那颗阵要正对牌面中心，得先知道牌面多大，
     所以它直接读这两个变量 —— 以后改牌面宽度，阵会自己跟着走，不会再错位 */
  --face-w: 126px;
  --face-h: calc(var(--face-w) * 2.25);
  --ring-w: 250px;
  position: relative; width: 210px; height: 420px; display: grid; place-items: center;
}

/* 合成台那颗：固定希望红，形状同样取自素材。
   圆心 = (舞台横向正中, 牌面正中)。定位只用 left/top + 负外边距，不借 transform：
   ringBurst 那套 keyframes 把 transform 全占了，写进去会被动画直接抹掉。
   也不能用 inset:0 + margin:auto —— 阵比舞台宽时左右两个 auto 外边距解不出相等值，
   Chrome 会把左边距压成 0、整颗阵往右偏（实测桌面偏 50px、手机偏 74px） */
.stage-ring {
  position: absolute;
  left: 50%; top: calc(var(--face-h) / 2);
  width: var(--ring-w); height: var(--ring-w);
  margin: calc(var(--ring-w) / -2) 0 0 calc(var(--ring-w) / -2);
  background-color: var(--hope-red);
  -webkit-mask: var(--magic-circle-sakura) center / 94% no-repeat;
  mask: var(--magic-circle-sakura) center / 94% no-repeat;
  opacity: 0; transform: scale(.3); pointer-events: none; z-index: 1;
}
.hope-stage.burst .stage-ring { animation: ringBurst 1s ease-out forwards; }
.hope-stage.burst::after {
  content: ""; position: absolute; inset: 0; border-radius: 20px;
  background: radial-gradient(circle, rgba(255, 244, 214, .95), rgba(232, 138, 176, .35) 45%, transparent 75%);
  animation: fadeAway 1s ease-out forwards; pointer-events: none;
}
@keyframes ringBurst {
  0% { opacity: 0; transform: scale(.3) rotate(0deg); }
  35% { opacity: .95; transform: scale(1.15) rotate(120deg); }
  100% { opacity: 0; transform: scale(1.6) rotate(240deg); }
}
@keyframes fadeAway { 0% { opacity: 0; } 25% { opacity: 1; } 100% { opacity: 0; } }

/* 这张牌未合成时是压暗的线稿「未知」态，合成后才亮起 */
.hope-card {
  --flip: 0deg;
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; gap: 14px;
}
.hope-card.flipped { --flip: 180deg; }
/* 合成成功的瞬间：希望之牌「诞生」的弹跳 */
.hope-stage.revealed .hope-card { animation: hopeAppear .9s cubic-bezier(.2, 1.3, .4, 1); }
@keyframes hopeAppear {
  0% { transform: scale(.72); }
  55% { transform: scale(1.12); }
  100% { transform: scale(1); }
}
/* 注意：这里刻意不用 filter —— filter 会把 preserve-3d 压平，破坏翻面 */
.hope-card.pulse { animation: hopePulse 1s cubic-bezier(.2, 1.3, .4, 1); }
.hope-card.pulse .flip-face.front { animation: hopeGlow 1s ease; }
@keyframes hopePulse {
  0% { transform: scale(1); }
  35% { transform: scale(1.14); }
  100% { transform: scale(1); }
}
@keyframes hopeGlow {
  0%, 100% { box-shadow: 0 0 0 1px var(--hope-red), 0 0 24px var(--hope-red), 0 10px 26px rgba(15, 10, 30, .28); }
  40% { box-shadow: 0 0 0 4px var(--hope-red), 0 0 52px var(--hope-red), 0 10px 26px rgba(15, 10, 30, .28); }
}
.hope-card .flip-zone { width: var(--face-w); cursor: pointer; }
.hope-card .flip-face.front img { transition: opacity .9s ease; }

/* 未合成：压暗，且不可翻面 */
.hope-card.locked .flip-zone { cursor: default; }
.hope-card.locked .flip-face.front img { opacity: .025; }   /* 极淡：细看才有线条 */
.hope-card.locked .hope-caption { opacity: .78; }

.hope-caption { text-align: center; transition: opacity .6s ease; }
.hope-caption h3 { font-family: var(--font-title); font-size: 1.2rem; letter-spacing: .3em; text-indent: .3em; color: var(--hope-red); }
.hope-caption h3 .hope-en {
  display: block; font-size: .62rem; letter-spacing: .2em; color: var(--hope-red);
  margin-top: 5px; font-family: var(--font-ui); opacity: .72;
}
.hope-caption p { margin-top: 8px; font-size: .72rem; color: var(--hope-red); opacity: .85; line-height: 1.9; }
.hope-tag {
  display: inline-block; margin-top: 10px; font-size: .68rem; letter-spacing: .16em;
  color: #fff; padding: 3px 12px; border-radius: 999px;
  background: linear-gradient(135deg, var(--hope-red), #e8778c);
  box-shadow: 0 4px 14px rgba(198, 42, 58, .5);
}

.detail-btn, .ritual-detail {
  border: 1px solid var(--line); background: var(--detail-bg);
  color: var(--ink-soft); width: 24px; height: 24px; border-radius: 50%;
  font-size: .68rem; cursor: pointer; display: grid; place-items: center;
  transition: color .3s, background-color .3s, border-color .3s; line-height: 1;
}
.detail-btn:hover, .ritual-detail:hover {
  color: #fff; background: var(--detail-hover); border-color: transparent;
}
.ritual-detail { position: absolute; top: 8px; left: 8px; z-index: 4; }

/* ---------- 固定转化按钮 ---------- */
.transform-fab {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%);
  z-index: 900; border: 0; cursor: pointer; white-space: nowrap;
  font-family: var(--font-ui); font-size: .9rem; letter-spacing: .1em;
  color: #fff; padding: 13px 34px; border-radius: 999px;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  box-shadow: 0 8px 30px var(--glow), 0 2px 12px rgba(0, 0, 0, .3);
  transition: transform .3s, filter .3s, opacity .3s, background .9s;
}
.transform-fab:hover { transform: translateX(-50%) translateY(-3px); filter: brightness(1.08); }
.transform-fab:active { transform: translateX(-50%) translateY(0); }
.transform-fab:disabled { opacity: .55; cursor: default; }

/* 「再读一次」等回放按钮：和上面那颗转化按钮同一套样式，只是小一号、不固定定位 */
.replay-btn {
  border: 0; cursor: pointer; white-space: nowrap;
  font-family: var(--font-ui); font-size: .8rem; letter-spacing: .12em;
  color: #fff; padding: 10px 26px; border-radius: 999px;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  box-shadow: 0 6px 22px var(--glow), 0 2px 10px rgba(0, 0, 0, .25);
  transition: transform .3s, filter .3s, opacity .3s, background .9s;
}
.replay-btn:hover { transform: translateY(-2px); filter: brightness(1.08); }
.replay-btn:active { transform: translateY(0); }
.replay-btn:disabled { opacity: .55; cursor: default; }

/* ---------- 轻提示 ---------- */
.toast {
  position: fixed; left: 50%; bottom: 96px; z-index: 1200;
  transform: translate(-50%, 14px);
  background: var(--panel-strong); border: 1px solid var(--line);
  color: var(--ink); padding: 11px 24px; border-radius: 999px;
  font-size: .82rem; letter-spacing: .08em; white-space: nowrap;
  opacity: 0; pointer-events: none; backdrop-filter: blur(10px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .3);
  transition: opacity .3s, transform .3s, background-color .9s, color .9s;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* ---------- 转化波纹动画 ---------- */
@keyframes morphFlash {
  0% { filter: brightness(1) saturate(1); }
  45% { filter: brightness(2.3) saturate(.45); }
  100% { filter: brightness(1) saturate(1); }
}
.card.morphing .flip-face img { animation: morphFlash .55s ease; }
.card.morphing .flip-face { box-shadow: 0 0 0 3px var(--accent), 0 0 30px var(--glow); }
.card.morphing .flip-zone::after {
  content: ""; position: absolute; inset: -10px; border-radius: 50%;
  pointer-events: none;
  background: conic-gradient(from 0deg, transparent, var(--accent) 20%, transparent 38%);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));
  animation: spinOnce .9s linear;
}
/* 由库洛牌转为小樱牌时，这道波纹换成小樱粉；反向转化仍用金色 */
body.to-sakura .card.morphing .flip-zone::after {
  background: conic-gradient(from 0deg, transparent, var(--sakura-pink) 20%, transparent 38%);
}
@keyframes spinOnce { to { transform: rotate(360deg); } }

/* ---------- 详情弹窗 ---------- */
.modal { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 24px; }
.modal-backdrop {
  position: absolute; inset: 0; background: rgba(12, 8, 28, .66);
  backdrop-filter: blur(6px); animation: fadeIn .3s ease;
}
.modal-body {
  position: relative; display: flex; align-items: center; gap: 32px;
  background: var(--panel-strong); border: 1px solid var(--line);
  border-radius: 22px; padding: 34px; max-width: 720px; width: 100%;
  box-shadow: 0 30px 80px rgba(0, 0, 0, .45);
  animation: popIn .35s cubic-bezier(.2, 1.2, .4, 1);
}
@keyframes popIn { from { opacity: 0; transform: scale(.92) translateY(14px); } }
@keyframes fadeIn { from { opacity: 0; } }

.modal-close {
  position: absolute; top: 14px; right: 14px; width: 34px; height: 34px;
  border-radius: 50%; border: 1px solid var(--line); background: none;
  color: var(--ink-soft); cursor: pointer; font-size: .88rem; transition: .3s;
}
.modal-close:hover { color: #fff; background: var(--mode-color); border-color: transparent; }

.modal-card { --flip: 0deg; position: relative; width: 170px; aspect-ratio: var(--card-ratio); flex: none; }
.modal-card.flipped { --flip: 180deg; }
.modal-card .flip-inner { transition: transform .8s cubic-bezier(.35, .05, .25, 1); }
.modal-card.morphing .flip-face img { animation: morphFlash .6s ease; }
/* 光晕只能加在 .flip-face 上：加到 .flip-inner 上会因 filter 压平 preserve-3d，
   导致翻到背面时 backface-visibility 失效、正面闪现一下 */
.modal-card.morphing .flip-face {
  box-shadow: 0 0 0 3px var(--accent), 0 0 30px var(--glow);
}
/* 单张转化时从牌面里炸出来的那圈阵，用和页头/诗篇同一份图片素材：
   遮罩给形状（94% 与旧 SVG 外圈 226/240 对齐），底色给阵色。
   morphModal() 只负责在 .modal-card 上写 data-morph="sakura|clow"，
   「换哪份阵、配什么颜色」都收在下面两条规则里：
     收服成小樱牌 → 樱阵（星·日·月），固定配小樱粉。--sakura-pink 本来就不随主题走，
                   所以库洛牌状态下转化时也不会被库洛黄带跑
     封印回库洛牌 → 库洛阵（五芒星），仍取当前卡组的强调色 */
.magic-overlay {
  position: absolute; top: 50%; left: 50%; width: 320px; height: 320px;
  transform: translate(-50%, -50%) scale(.2);
  background-color: var(--overlay-ink, var(--accent));
  -webkit-mask: var(--overlay-ring, var(--magic-circle-sakura)) center / 94% no-repeat;
  mask: var(--overlay-ring, var(--magic-circle-sakura)) center / 94% no-repeat;
  opacity: 0; pointer-events: none; z-index: 5;
}
.modal-card[data-morph="sakura"] .magic-overlay {
  --overlay-ring: var(--magic-circle-sakura);
  --overlay-ink: var(--sakura-pink);
}
.modal-card[data-morph="clow"] .magic-overlay {
  --overlay-ring: var(--magic-circle-clow);
}
.modal-card.morphing .magic-overlay { animation: overlayBurst .8s ease-out; }
@keyframes overlayBurst {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(.25) rotate(0deg); }
  30% { opacity: .95; }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.3) rotate(210deg); }
}

.modal-info { flex: 1; min-width: 200px; }
.modal-info h3 { font-family: var(--font-title); font-size: 2.1rem; letter-spacing: .18em; }

/* 弹窗当前展示哪一面，就由 --side-color 提供该面的代表色：英文名与「象征」都取用这一个来源。 */
.modal-body[data-side="clow"]   { --side-color: var(--clow-gold); }
.modal-body[data-side="sakura"] { --side-color: var(--sakura-pink); }

.modal-en { color: var(--side-color); letter-spacing: .14em; font-size: .78rem; margin-top: 6px; text-transform: uppercase; }

/* 三张特殊牌：中英文名都用该牌的特征色，中文名再加点装饰 */
.modal-body.is-special .modal-en { color: var(--frame); opacity: .9; }
.modal-body.is-special .modal-info h3 {
  display: inline-block;
  color: var(--frame);
  font-size: 2.5rem;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--frame);
  text-shadow: 0 0 18px var(--frame);
  animation: nameBreathe 3.2s ease-in-out infinite;
}
.modal-body.is-special .modal-info h3::before,
.modal-body.is-special .modal-info h3::after {
  content: "✦"; font-family: var(--font-ui); font-size: .7rem;
  font-weight: 400; vertical-align: super; opacity: .75;
}
.modal-body.is-special .modal-info h3::before { margin-right: .45em; }
.modal-body.is-special .modal-info h3::after  { margin-left: .45em; }
@keyframes nameBreathe {
  0%, 100% { text-shadow: 0 0 14px var(--frame); }
  50%      { text-shadow: 0 0 30px var(--frame), 0 0 52px var(--frame); }
}
.modal-desc { color: var(--ink-soft); font-size: .85rem; line-height: 1.9; margin-top: 16px; }

/* 详情里的两个补充条目：「象征」与「Past 有话说」。
   两者共用同一套规则 —— 左侧一道竖线像引文，颜色跟着弹窗正反面走。 */
.modal-sym, .modal-past {
  margin-top: 14px; padding: 10px 14px 10px 16px;
  border-left: 2px solid var(--side-color, var(--mode-color));
  background: rgba(130, 120, 150, .08);
  border-radius: 0 10px 10px 0;
}
.modal-sym-label, .modal-past-label {
  display: block; font-size: .64rem; letter-spacing: .3em; text-indent: .3em;
  color: var(--side-color, var(--mode-color)); margin-bottom: 5px;
}
.modal-sym p, .modal-past p { font-size: .84rem; line-height: 1.85; color: var(--ink); }
.modal-actions { display: flex; gap: 12px; margin-top: 24px; flex-wrap: wrap; }
.btn {
  border: 1px solid var(--line); background: none; color: var(--ink);
  font-family: var(--font-ui); font-size: .86rem; letter-spacing: .1em;
  padding: 10px 20px; border-radius: 999px; cursor: pointer; transition: .3s;
}
/* hover 用当前卡组代表色 */
.btn.ghost:hover { border-color: var(--mode-color); color: var(--mode-color); }
.btn.primary {
  color: #fff; border: 0;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  box-shadow: 0 6px 20px var(--glow);
}
.btn.primary:hover {
  filter: brightness(1.1); transform: translateY(-1px);
  box-shadow: 0 8px 26px var(--mode-color);
}

/* ---------- 粒子与花瓣 ---------- */
.particle {
  position: fixed; z-index: 3000; pointer-events: none; line-height: 1;
  animation: fly 1.15s cubic-bezier(.15, .65, .35, 1) forwards;
}
.particle.dot { width: 6px; height: 6px; border-radius: 50%; }
.particle.star { color: #f2c14e; text-shadow: 0 0 8px rgba(242, 193, 78, .9); }
.particle.heart { color: #f58fb4; text-shadow: 0 0 8px rgba(245, 143, 180, .9); }
@keyframes fly {
  0% { transform: translate(0, 0) scale(1); opacity: 1; }
  100% { transform: translate(var(--dx), var(--dy)) scale(.25); opacity: 0; }
}

.petal {
  position: fixed; top: -24px; z-index: 0; width: 13px; height: 13px;
  border-radius: 75% 0 75% 0;
  background: linear-gradient(135deg, #ffd9e6, #f6a5c6);
  opacity: .65; pointer-events: none;
  animation: petalFall linear infinite;
}
.petal.gold { background: linear-gradient(135deg, #ffe9b0, #e3b955); width: 10px; height: 10px; }
/* 献诗仪式期间花瓣定格：不落也不飘 */
body.petals-still .petal { animation-play-state: paused; }
@keyframes petalFall {
  0% { transform: translate3d(0, 0, 0) rotate(0deg); }
  25% { transform: translate3d(-30px, 30vh, 0) rotate(120deg); }
  50% { transform: translate3d(24px, 55vh, 0) rotate(240deg); }
  75% { transform: translate3d(-18px, 80vh, 0) rotate(320deg); }
  100% { transform: translate3d(10px, 112vh, 0) rotate(400deg); }
}

.screen-flash {
  position: fixed; inset: 0; z-index: 2500; pointer-events: none;
  background: radial-gradient(circle at var(--fx, 50%) var(--fy, 45%),
    rgba(255, 236, 190, .95), rgba(232, 138, 176, .4) 45%, transparent 75%);
  animation: screenFlash 1.1s ease-out forwards;
}
@keyframes screenFlash { 0% { opacity: 0; } 18% { opacity: 1; } 100% { opacity: 0; } }

/* 临界那一闪：起亮快、亮得久，但要在文字登场前散尽，免得盖住逐字现身 */
.screen-flash.hold { animation: screenFlashHold 1.5s ease-out forwards; }
@keyframes screenFlashHold {
  0%   { opacity: 0; }
  7%   { opacity: 1; }
  30%  { opacity: .95; }
  55%  { opacity: .35; }
  85%  { opacity: .08; }
  100% { opacity: 0; }
}

/* ---------- 页脚 ---------- */
/* 贴在页面底端，底部留出 80px 给那颗固定在屏幕上的转化按钮 */
footer {
  flex-shrink: 0;
  text-align: center;
  padding: 24px 20px 80px;
  color: var(--ink-soft); font-size: .78rem; letter-spacing: .08em; line-height: 2.2;
}
/* 题词（现位于左侧抽屉）：无=库洛黄、爱=小樱粉、希望=希望红，其余为默认色。 */
.motto { color: var(--ink-soft); font-size: .8rem; letter-spacing: .08em; }
.m-clow   { color: var(--clow-gold); }
.m-sakura { color: var(--sakura-pink); }
.m-hope   { color: var(--hope-red); }
footer .credit { color: var(--ink-soft); opacity: .6; font-size: .7rem; letter-spacing: .04em; margin-top: 6px; }

/* ---------- 响应式 ---------- */
@media (max-width: 720px) {
  .ritual { flex-direction: column; gap: 30px; padding: 30px 18px; }
  .ritual-arrow { transform: rotate(90deg); }
  .hope-stage { width: 100%; height: 400px; }
  .grid { gap: 40px 14px; }
}
@media (max-width: 640px) {
  .modal-body { flex-direction: column; padding: 28px 22px; gap: 24px; }
  .modal-card { width: 150px; }
  .modal-info { text-align: center; }
  .modal-actions { justify-content: center; }
  .slot { width: 108px; }
  .drop-zone .plus { font-size: 1.2rem; }
}

/* ---------- 手机端的底部搜索条（基础态不显示，只在手机端且抽屉展开时出现） ---------- */
.search-dock {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 690;
  display: none; align-items: center; gap: 10px;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
  background: var(--panel-strong); backdrop-filter: blur(16px);
  border-top: 1px solid var(--line);
  transition: background-color .9s ease, border-color .9s ease;
}

/* ---------- 背景音乐圆钮 ----------
   画成一张实心唱片：盘身 + 两道纹 + 中央标签 + 中心孔。
   盘身/盘纹/标签三个颜色走 --disc / --disc-ink / --disc-label，
   换主题、换明暗海报时只换这三个变量，形状不动。
   位置分三种：
     · 密码门还挂着（body.gate-up）→ 贴在海报上方居中那本书上
     · 电脑端进门后             → 贴图鉴左边界（left 由 JS 量，见 placeMusicButton）
     · 手机端进门后             → 左上角
   旋转只转里面那个 svg，按钮本身不转 —— 不然 title 的气泡也跟着晃 */
.music-btn {
  --disc: #14121f;                 /* 盘身固定用黑胶色：跟面板走的话盘子没有「实物感」 */
  --disc-ink: var(--mode-color);   /* 盘纹与描边取当前卡组色 */
  --disc-label: var(--mode-color); /* 中央标签 */
  position: fixed; z-index: 1160;
  width: 40px; height: 40px; padding: 0;
  display: grid; place-items: center;
  border: 0; border-radius: 50%; background: none; cursor: pointer;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, .38));
  transition: filter .3s ease, opacity .4s ease;
}
.music-btn svg { width: 100%; height: 100%; display: block; }
.music-btn .disc   { fill: var(--disc); stroke: var(--disc-ink); stroke-width: 1.4; }
.music-btn .groove { fill: none; stroke: var(--disc-ink); }
.music-btn .label  { fill: var(--disc-label); }
.music-btn .hole   { fill: var(--disc); }
/* 盘缘那段偏心弧 = 唯一的「在转」提示。线要够粗才看得出来，2.8 是量过的 */
.music-btn .mark   { fill: none; stroke: var(--disc-label); stroke-width: 2.8; stroke-linecap: round; }
.music-btn:hover { filter: drop-shadow(0 0 12px var(--glow)) brightness(1.12); }
.music-btn.playing .face-disc { animation: discSpin 3.6s linear infinite; }
@keyframes discSpin { to { transform: rotate(360deg); } }

/* 诗篇完全揭晓之后：唱片换成一颗红心（见 app.js 的 enterHeart）。
   播放时改成心跳似的起伏 —— 转一颗心看着别扭，但「在不在放」还是要一眼看得出来。
   这一态下曲子固定、也不再有抽屉的蒙住效果 */
.music-btn .face-heart { display: none; }
.music-btn.heart .face-disc  { display: none; }
/* 爱心从无到有弹出来（换类的那一下放一次），之后接上心跳。
   两个动画写在一起：心跳延后 0.9s，接缝处两边都是 scale(1)，不会跳 */
.music-btn.heart .face-heart {
  display: block; fill: var(--hope-red);
  animation: heartIn .9s cubic-bezier(.2, 1.5, .4, 1) both;
}
.music-btn.heart.playing .face-heart {
  animation: heartIn .9s cubic-bezier(.2, 1.5, .4, 1) both,
             heartBeat 1.7s ease-in-out .9s infinite;
}
@keyframes heartIn {
  0%   { transform: scale(0) rotate(-150deg); opacity: 0; }
  62%  { transform: scale(1.32) rotate(9deg); opacity: 1; }
  100% { transform: scale(1) rotate(0deg);    opacity: 1; }
}
@keyframes heartBeat {
  0%, 100% { transform: scale(1); }
  16%      { transform: scale(1.15); }
  32%      { transform: scale(1); }
  48%      { transform: scale(1.09); }
  64%      { transform: scale(1); }
}
/* 暂停中：整张盘压暗一点，一眼看得出没在转 */
.music-btn.paused { opacity: .6; }

/* 密码门还挂着的时候：贴在海报上那本书（书心量出来在海报的 50% / 11.04% 处）。
   海报是 cover + 居中，所以它铺开后的高度 = max(视口高, 视口宽 / 1.9264)，
   书心到海报上沿的距离就是这高度的 11.04%。这样窄屏（按高铺）和宽屏（按宽铺）
   两种情形都能对上，不必分端写两套。
   z-index 要抬到门（4500）之上，否则整块被门盖着，压根看不见。
   盘色按海报明暗反过来配 —— 米白盘压在米白海报上等于隐形，这也是一开始
   那张「不起眼」的原因 */
body.gate-up .music-btn {
  --poster-h: max(100vh, calc(100vw / 1.9264));
  --disc: #17141a;                 /* 浅海报：黑胶盘 */
  --disc-ink: #d8c9ab;
  --disc-label: #b4524c;
  z-index: 4600;
  width: 54px; height: 54px;
  left: 50%;
  top: calc((100vh - var(--poster-h)) / 2 + var(--poster-h) * 0.1104);
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 6px 16px rgba(0, 0, 0, .5));
}
body.gate-up[data-mode="clow"] .music-btn {
  --disc: #e6dcc6;                 /* 深海报：米白盘，反过来 */
  --disc-ink: #6a6152;
  --disc-label: #9e4a4e;
}
/* 进门之后：电脑端贴在页面最上侧，横向对齐图鉴左边界（left 交给 JS）。
   z-index 1160 —— 高过两侧抽屉（700 起、每次展开 +1）也高过搜索条，
   所以左侧抽屉展开时圆钮浮在它上面，不会被挡住 */
body:not(.gate-up) .music-btn { top: 14px; }

/* ============================================================
   手机端（≤700px）
   放在最后，靠顺序盖掉上面几条窄屏规则。
   桌面端的一切显示与交互都不动，这里只做覆盖。
   与桌面最大的三处不同：
     1. 两个抽屉收在屏幕顶部，两块把手合成一条导航栏
     2. 取消拖牌，改成「先点卡槽、再点牌」（见 app.js 的 armSlot）
     3. 图鉴每行 4 张，详情弹窗压进一屏
   ============================================================ */
@media (max-width: 700px) {

  /* ---------- 密码门：海报填满整屏 ----------
     竖屏手机上 cover 是「按高度铺」的（H/598 > W/1152 恒成立），
     于是海报高度正好等于屏高，不留黑边、也就不会上下滑。
     代价是标题左右各切掉一截，这是和「标题完整」不可兼得后选的一头。
     高度按铺满算，海报坐标就是屏高的固定百分比：
       主标题下沿 y=208 → 208/598 = 34.8%    图案上沿 y=355 → 59.4%
     所以密码框的可用高度是屏高的 24.6%，比原来宽裕得多，字号也跟着放大 */
  .gate { background-size: cover; background-position: center; }
  /* 落在「主标题下沿 34.8%」与「图案上沿 59.4%」的中线，即 47.1%。
     用 translateY(-50%) 居中，面板高度随位数变化也能自动对中 */
  .gate-panel {
    top: 49%;
    transform: translateY(-50%);
    width: min(440px, 97%);
    padding: 20px 18px 18px;
    border-radius: 20px;
  }
  .gate.done .gate-panel { transform: translateY(-50%) scale(.94); }
  /* 抖动动画要占用 transform，这里得自己写一份带上居中位移的 */
  .gate-panel.shake { animation: shakeM .5s; }
  @keyframes shakeM {
    0%, 100% { transform: translateY(-50%) translateX(0); }
    20%      { transform: translateY(-50%) translateX(-8px); }
    40%      { transform: translateY(-50%) translateX(7px); }
    60%      { transform: translateY(-50%) translateX(-5px); }
    80%      { transform: translateY(-50%) translateX(3px); }
  }
  .gate-title { font-size: 1.32rem; letter-spacing: .2em; text-indent: .2em; }
  /* 0.9rem 是算过的：最长那句（22 字 ＋ .06em 字距）在 97% 宽的框里正好一行，
     折行的话面板会从 224 长到 247，位置也跟着飘 */
  .gate-question { margin-top: 14px; font-size: .9rem; line-height: 1.6; }
  .gate-inputs { gap: 12px; margin: 16px 0 2px; }
  .gate-digit { width: 54px; height: 68px; border-radius: 13px; font-size: 1.6rem; }
  .gate-msg { margin-top: 12px; min-height: 1.6em; font-size: .78rem; line-height: 1.6; }

  /* ---------- 别让页面左右滑 ----------
     手机浏览器会把横向滑动当「后退」手势，实测也确有元素探出视口，
     两头一起堵：overflow-x 挡住溢出，touch-action 只放行纵向与缩放 */
  html, body { overflow-x: hidden; overscroll-behavior: none; }
  body { touch-action: pan-y pinch-zoom; }
  /* 弹窗与抽屉内部滚到头时不要把滚动传给后面的页面 */
  .modal, .modal-body, .drawer-body { overscroll-behavior: contain; }

  /* 转化按钮：避开刘海屏的底部安全区；touch-action 免得点完残留 hover 位移 */
  .transform-fab {
    bottom: calc(18px + env(safe-area-inset-bottom, 0px));
    touch-action: manipulation;
  }

  /* ---------- 页面整体让出顶部导航栏的高度 ---------- */
  .page { padding-top: 46px; }
  .hero { padding: 26px 16px 22px; }
  .hero h1 { font-size: clamp(1.9rem, 9vw, 2.6rem); }
  .tagline { font-size: .72rem; line-height: 1.8; }

  /* ---------- 图鉴：每行 4 张 ---------- */
  .grid { grid-template-columns: repeat(4, 1fr); gap: 20px 7px; margin-top: 12px; }
  .card { padding-top: 8px; }
  /* 卡号徽章跟着桌面端走（22px 圆、同样的字号）—— 之前这里改成了小圆角方框，
     和桌面端不是一套 */
  .card-meta { margin-top: 4px; gap: 0; }
  .card-meta .cn { font-size: .62rem; }
  .card-meta .en { font-size: .48rem; letter-spacing: 0; }
  .detail-btn { width: 15px; height: 15px; font-size: .52rem; }
  .flip-face.front, .flip-face.back { border-width: 2px; }
  .toolbar { flex-direction: column; align-items: stretch; gap: 7px; margin-top: 12px; }
  #search { width: 100%; font-size: .78rem; padding: 9px 12px; }
  #count { text-align: right; font-size: .66rem; }
  .sec-title { font-size: 1.05rem; }

  /* 震翻时换成绕横轴（上下翻），只在合成诗篇那两下里生效（见 quakeFlipGalleryMobile） */
  #grid.axis-x .flip-inner     { transform: rotateX(var(--flip)); }
  #grid.axis-x .flip-face.back { transform: rotateX(180deg); }

  /* 换轴的那一瞬不能带过渡。
     .flip-inner 的 transform 是 rotateY(var(--flip))，加上 .axis-x 就变成
     rotateX(var(--flip)) —— 函数名换了，起终点对不上，浏览器只能退回矩阵插值。
     而矩阵插值碰上 180° 这种「对角」情形，走哪条路（正着转还是反着转、绕哪根轴）
     是不确定的，第一遍震翻就会看到有的牌多转了一圈 —— 第二遍因为两边都是 rotateX，
     能逐函数插值，所以是正常的。
     加 .axis-swap 把过渡关掉，轴一下子切过去；牌背图案基本对称，这一下看不出来。
     JS 那边会强制一次样式重算再把它摘掉，之后 --flip 的变化就是干净的 rotateX → rotateX */
  #grid.axis-swap .flip-inner { transition: none; }

  /* ---------- 详情弹窗：压进一屏 ---------- */
  .modal-body {
    flex-direction: column; align-items: center;
    gap: 12px; padding: 18px 16px 16px;
    max-width: 94vw; max-height: 90vh; overflow-y: auto;
  }
  .modal-card { width: 104px; }
  .modal-info { text-align: center; }
  .modal-info h3 { font-size: 1rem; }
  .modal-en { font-size: .66rem; margin-top: 2px; }
  .modal-desc { font-size: .72rem; line-height: 1.6; margin-top: 8px; }
  .modal-sym, .modal-past { margin-top: 9px; padding: 8px 10px; }
  .modal-sym p, .modal-past p { font-size: .7rem; line-height: 1.6; }
  .modal-actions { justify-content: center; gap: 9px; margin-top: 13px; }
  .modal-actions .btn { font-size: .74rem; padding: 9px 13px; }

  /* ---------- 抽屉里的密码：两列竖排要挤进 390 宽，数字框得跟着收 ----------
     宽度账：两侧文字各 ~44 ＋ 各 8 间距 ＋ 6 格 × 30 ＋ 5 × 6 = 314，装得下 370 */
  /* 密码层铺满抽屉，正好和诗篇用同一个高度 */
  .drawer-lock {
    --lk-digit-w: 30px; --lk-digit-h: 38px;
    --lk-col-gap: 6px;  --lk-row-gap: 22px;
    --lk-side-gap: 8px;
    padding: 16px 10px;
  }
  .lock-digit { font-size: .95rem; border-radius: 8px; }
  .lock-center { width: 100%; }
  .lock-side .vline span { font-size: .82rem; }
  .lock-open { left: 10px; right: 10px; }
  .lock-open-l1 { font-size: .82rem; letter-spacing: .06em; }
  .lock-open-l2 { font-size: 1.15rem; letter-spacing: .12em; }
  .drawer-lock.lit .lock-open-l2 { letter-spacing: .06em; }
  .lock-msg { font-size: .72rem; }

  /* ---------- 抽屉：两块把手合成顶部一条导航栏 ---------- */
  .drawer {
    --m-nav: 46px;                 /* 导航栏高度，面板就吊在它下面 */
    /* 诗篇抽屉的高度下限，解锁前后共用同一个值，切换时不跳。
       取「解锁前后两种状态里更费空间的那个」：

         解锁后 —— #poem 就是「阵 + 8px 上边距」，再加抽屉内边距 32，
                   合计 = 阵高 + 40；阵高本身是 min(88% 内容宽, 40vh)，
                   所以这里照抄同一套算法，各机型自动贴合，不用逐个机型试
         锁着时 —— 宣告钉在 87.72% 处、实测高 67.2，上下各要 h/2 才不撞锁，
                   反推下限 = 67.2 / 0.2456 ≈ 274

       实测 390×844：解锁态 358.5、锁定态 273.7，取大者；之前定的 400 白留了 41.5。
       阵随视口缩，所以这两个数也跟着缩，没有一个放之四海皆准的定值。
       末尾那项是横屏兜底：按宽算出来的可能比屏幕还高。
       这里是下限不是定高，内容真超了它会自己长 */
    --m-sheet: min(max(var(--m-sheet-floor, 280px),
                       calc(min(0.88 * (100vw - 28px), var(--vh, 100vh) * 0.4) + 46px)),
                   calc(var(--vh, 100vh) - var(--m-nav)));
    width: 100vw !important;
    transform: none !important;    /* 桌面那套左右滑入作废 */
    pointer-events: none;          /* 抽屉本身不挡页面；只有把手和面板接事件 */
  }
  /* 两块把手合成一个框：各自按文字撑开、从中心往两边排，
     合起来就是一条菜单栏，宽度由文字决定而不是铺满一行 */
  .drawer-tab {
    position: absolute; top: 0; height: var(--m-nav); width: auto;
    padding: 0 16px; border: 0;
    background: var(--panel-strong); backdrop-filter: blur(16px);
    pointer-events: auto;
  }
  .drawer-left .drawer-tab  { left: 50%;  right: auto; transform: translateX(-100%);
                              border-radius: 0 0 0 15px; }
  .drawer-right .drawer-tab { right: 50%; left: auto;  transform: translateX(100%);
                              border-radius: 0 0 15px 0;
                              border-left: 1px solid var(--line); }
  .drawer.open .drawer-tab { color: var(--mode-color); }
  .drawer-tab-text {
    writing-mode: horizontal-tb; text-orientation: mixed;
    font-size: .88rem; letter-spacing: .16em; text-indent: .16em;
  }
  /* 音乐圆钮：进门后挪到左上角。顶部那条合并把手是从中线往两边撑开的
     （实测 53~195px），圆钮占 12~46px，错得开。
     密码门那会儿仍归 body.gate-up 那条管，位置不动 */
  body:not(.gate-up) .music-btn { top: 8px; left: 12px; width: 36px; height: 36px; }
  /* 桌面那两条 .drawer-left/.drawer-right .drawer-body 比自己这条特异性高，
     得照抄一份同样的前缀才能把左右内缩改掉 */
  .drawer-body,
  .drawer-left .drawer-body,
  .drawer-right .drawer-body {
    /* 用固定高度而不是 bottom:auto —— 右侧抽屉里只有绝对定位的密码层和隐藏的诗篇，
       高度交给内容算会塌成 0，整个面板就看不见了 */
    position: absolute; top: var(--m-nav); left: 0; right: 0; bottom: auto;
    height: var(--m-sheet, calc(100vh - var(--m-nav))); max-height: none;
    border: 0; border-radius: 0 0 20px 20px;
    padding: 14px 14px 18px;
    transform: translateY(calc(-100% - var(--m-nav)));
    transition: transform .34s cubic-bezier(.3, .7, .3, 1);
    pointer-events: auto;
  }
  .drawer.open .drawer-body { transform: none; }
  /* 收起的抽屉里没有东西该接事件 */
  body:not(.picking) .drawer:not(.open) .slot { pointer-events: none; }

  /* 两块说明文字夹住两个卡槽：「搜寻…」在卡槽正上方、「无与爱…」在正下方，
     都对齐落点区那一列的中心线。
     做法是让 .ritual 用 display:contents 把自己「拆掉」—— 里面的落点区/箭头/希望舞台
     直接成为 #special 网格的子项，两条文字才放得进落点区所在的那一列。
     希望舞台跨满三行，整块高度还是它说了算，两条文字就落在它上下的空隙里，
     于是抽屉比原来（两行字独占顶部）矮一截。HTML 不动，桌面端完全不受影响 */
  .drawer-left #special {
    display: grid;
    grid-template-columns: auto auto auto;
    justify-content: center; align-items: center;
    column-gap: 10px;
  }
  .drawer-left .ritual { display: contents; }
  /* nowrap：窄机上这一列会被挤到放不下一行，宁可让它略微溢出（居中溢出，两边各几像素），
     也不要折成两行把抽屉撑高 */
  .drawer-left .sec-sub      { grid-area: 1 / 1; margin: 0 0 6px; white-space: nowrap; }
  .drawer-left .drop-zone    { grid-area: 2 / 1; }
  .drawer-left .ritual-arrow { grid-area: 2 / 2; }
  .drawer-left .hope-stage   { grid-area: 1 / 3 / 4 / 4; }
  .drawer-left .motto        { grid-area: 3 / 1; margin: 6px 0 0; text-align: center; }

  /* 「希望」与「The Hope」并作一行，中间用 • 隔开。
     说明文字允许比卡面宽：卡面只有 62px，不放宽这行必然折行 */
  .drawer-left .hope-caption { width: 172px; }
  .drawer-left .hope-caption h3 { font-size: .72rem; letter-spacing: .16em; text-indent: .16em; white-space: nowrap; }
  .drawer-left .hope-caption h3 .hope-en { display: inline; margin-top: 0; font-size: .6rem; letter-spacing: .1em; }
  .drawer-left .hope-caption h3 .hope-en::before { content: ' • '; }
  .drawer-left .hope-tag { font-size: .58rem; white-space: nowrap; }

  /* 左侧：合成台。三个东西（两卡槽 ＋ 希望牌）压成一横排，面板高度就只由这一排决定 */
  /* 高度交给内容算。底下原本多垫 54px（怕冒出滚动条），现在两块文字挪进卡槽上下、
     占的地方少了，那点垫高就成了纯空白，收回到和右侧抽屉一样的 18px */
  /* 底下留 30px：「最重要的一张」那颗标签是按 .hope-card 的绝对定位排的，
     会探出 206px 高的希望舞台一点点，不额外留白它就正好贴到抽屉下沿（实测只差 3.5px） */
  .drawer-left .drawer-body { height: auto; padding-bottom: 30px; }
  .drawer-left .drop-zone { gap: 10px; }
  .drawer-left .drop-zone .slot { width: 66px; }
  .drawer-left .ritual-arrow { transform: none; font-size: .9rem; }
  /* 牌面收到 62px，那颗阵也跟着收：桌面是 250 : 216 ≈ 1.16 倍卡高，
     手机按同一比例给 160，正好落在这一排里，不会顶到上面的题词、也不出屏幕 */
  .drawer-left .hope-stage { width: 102px; height: 206px; --face-w: 62px; --ring-w: 160px; }
  .drawer-left .hope-caption h3 { font-size: .74rem; }
  .drawer-left .hope-caption p { font-size: .56rem; }
  .drawer-left .motto { font-size: .66rem; }

  /* 右侧：诗篇。高度用 min-height 定一个下限（解锁前后因此一样高，切换时不跳），
     但不写死 —— 万一真实机型上字体换行更多把它顶高了，让它自己长就是，不会冒出滚动条。
     下限本身怎么算见上面 .drawer 的 --m-sheet（按两种状态里更费空间的那个推） */
  .drawer-right .drawer-body {
    height: auto; min-height: var(--m-sheet);
    max-height: calc(var(--vh, 100vh) - var(--m-nav));   /* 同上：别被输入法压矮 */
    justify-content: center;
  }
  /* 阵的尺寸按 --vh（没有键盘时的视口高）算，不直接用 vh ——
     否则一敲密码、输入法把布局视口压矮，整个阵和字都会当场缩水。
     --vh 由 pinViewportHeight() 维护 */
  .drawer-right .poem-circle { width: min(88%, var(--vh, 100vh) * 0.4); margin-top: 8px; }

  /* ---------- 诗篇两侧的竖排 ----------
     「或许…」搬到阵的右侧、「再读一次」搬到左侧，两块都脱离文档流，
     正好落进阵左右各 ~36px 的余量里 —— 阵的尺寸和位置一点没动，
     顺带把诗篇那块的高度从「说明 ＋ 阵 ＋ 按钮」压成「只有阵」。
     这一层盒子的上下沿用绝对定位钉住阵的上下沿：top 与 .poem-circle 的
     margin-top 取同一个数（8px），bottom:0 落在阵的下沿，
     阵高 = min(88% 宽, 40vh) 怎么变，这层盒子都跟着 */
  .drawer-right #poem { position: relative; }
  .drawer-right .sec-sub,
  .drawer-right .poem-foot {
    position: absolute; top: 8px; bottom: 0;
    margin: 0; max-width: 100%;
  }
  /* 竖排：inline 轴变成纵向，于是 text-align:center 是「上下居中」而不是「左右居中」
     —— 阵高多少，字就自动居中多少 */
  .drawer-right .sec-sub {
    right: 0; writing-mode: vertical-rl;
    white-space: nowrap;              /* 宁可略微溢出，也不折成歪歪扭扭的第二列 */
    /* 字号跟阵一起缩：阵高 = min(88% 宽, --vh × 0.4)，25 个字排下来要正好放得进。
       这里也得用 --vh 而不是 vh —— 不然输入法一弹、视口一矮，字号就掉到下限 8px 去了 */
    font-size: clamp(8px, var(--vh, 100vh) * 0.014, 11.5px); line-height: 1.3; letter-spacing: .02em;
  }
  .drawer-right .poem-foot { left: 0; display: flex; align-items: center; }
  /* 按钮也得竖过来，并且收窄：物理宽度 = 行高 ＋ 左右内边距，
     要塞进 36px 的余量里不压到阵线，内边距只留 3 */
  .drawer-right .replay-btn {
    writing-mode: vertical-rl; padding: 20px 3px;
    font-size: .72rem; letter-spacing: .1em; border-radius: 14px;
  }
  /* 阵心改成用 translate 居中：原来那套 margin 百分比定位实测偏上 25px。
     手机端没有拖拽，不会和 .slot.drag-over 抢 transform。
     代价是抖动与弹跳动画也在写 transform，直接复用 shake/slotPop 会把它拽回未偏移的
     位置去抖 —— 这两个得各写一份带上位移的 */
  .drawer-right .poem-center { left: 50%; right: auto; top: 50%; margin: 0; transform: translate(-50%, -50%); }
  .drawer-right .poem-center.reject   { animation: shakeCenter .5s; }
  .drawer-right .poem-center.accepted { animation: popCenter .55s cubic-bezier(.2, 1.5, .4, 1); }
  @keyframes shakeCenter {
    0%, 100% { transform: translate(-50%, -50%) translateX(0); }
    20%      { transform: translate(-50%, -50%) translateX(-8px); }
    40%      { transform: translate(-50%, -50%) translateX(7px); }
    60%      { transform: translate(-50%, -50%) translateX(-5px); }
    80%      { transform: translate(-50%, -50%) translateX(3px); }
  }
  @keyframes popCenter {
    0%   { transform: translate(-50%, -50%) scale(1); }
    55%  { transform: translate(-50%, -50%) scale(1.14); }
    100% { transform: translate(-50%, -50%) scale(1); }
  }

  /* 底部搜索条：抽屉展开时现身，转化按钮相应让位 */
  .search-dock.show { display: flex; }
  .search-dock input {
    flex: 1; min-width: 0; font-size: .8rem; padding: 9px 12px;
    border-radius: 10px; border: 1px solid var(--line);
    background: var(--gate-field); color: var(--ink); outline: none;
  }
  /* 与图鉴顶上那条 #count 同一套配色：平时 ink-soft，命中密语（今夜我爱你）转成卡组色。
     syncDock 会把 #count 的 class 一起搬过来，所以 .revealed 在这里也生效 */
  .search-dock span { font-size: .66rem; white-space: nowrap; color: var(--ink-soft); letter-spacing: .05em; }
  .search-dock span.revealed { color: var(--name); }
  body.dock-open .transform-fab { bottom: calc(78px + env(safe-area-inset-bottom, 0px)); }
  /* 提示条挂在顶部那条合并把手下面 —— 抽屉一拉开，内容就从这里开始，
     提示贴在自己的上沿，视线不用来回跑。原来放在底部搜索框上方，
     但那样会和转化按钮叠在一起（实测提示 707~748、按钮 720~766），
     还得额外把按钮抬起来让位 */
  body.dock-open .toast {
    top: 54px; bottom: auto;
    transform: translate(-50%, -14px);
  }
  body.dock-open .toast.show { transform: translate(-50%, 0); }
  /* 密语那条例外：不管抽屉开没开，一律从下方弹 ——
     在底部那条搜索框里输和在图鉴顶上那个框里输，观感保持一致 */
  body.dock-open .toast.from-bottom {
    top: auto; bottom: calc(64px + env(safe-area-inset-bottom, 0px));
    transform: translate(-50%, 14px);
  }
  body.dock-open .toast.from-bottom.show { transform: translate(-50%, 0); }

  /* 点搜索框弹出输入法时，视口跟着缩（见 head 里的 interactive-widget=resizes-content），
     贴着底部的转化按钮会被顶到半空。打字时本来也用不着它，聚焦期间先收起来 ——
     收起来是「不动」，而不是让它飞到一个奇怪的高度。
     body.kb-up 由 bindKeyboardGuard() 维护；这条只在手机端生效，桌面端打字不受影响 */
  body.kb-up .transform-fab { opacity: 0; pointer-events: none; }
  /* 卡槽必须回到按百分比缩放：定死 74px 的话，在 287px 的阵上五张牌会互相压住。
     12% 是桌面就定好的比例，四个方向槽到阵心的距离正好等于半个阵宽，互不相碰 */
  .slot.poem-slot { width: 12%; }
  .drawer-right .poem-title { font-size: 1.02rem; letter-spacing: .12em; }
  .drawer-right .poem-line { font-size: .84rem; line-height: 1.9; }
  /* 末句五个字必须挤在一行。原来 1.7rem + .24em 字距算下来正好顶满 210px（实测
     scrollWidth 210 / 容器 210.2），换个字体就折行 —— 手机字体各不相同，得留余量。
     字距和字号一起收，另外加 nowrap 兜底：真到了极窄的屏上宁可略微溢出一丁点，
     也不要折成两行 */
  .drawer-right .poem-last {
    font-size: 1.55rem; letter-spacing: .12em; text-indent: .12em; white-space: nowrap;
  }
  /* 说明文字的字号见上面「诗篇两侧的竖排」—— 那边要跟着阵高缩，不能再压一个定值 */

  /* 卡槽被点选后等着接牌的样子 */
  .slot.arming {
    border-color: var(--mode-color);
    box-shadow: 0 0 0 3px var(--glow), inset 0 0 22px var(--glow);
    animation: slotArm 1.1s ease-in-out infinite;
  }
  @keyframes slotArm {
    0%, 100% { box-shadow: 0 0 0 3px var(--glow), inset 0 0 22px var(--glow); }
    50%      { box-shadow: 0 0 0 7px transparent, inset 0 0 30px var(--glow); }
  }

}

