/* ============================================================
   水晶精灵 Crystal Sprite Cute — 设计系统 v2
   风格来源:2026-10-12 用户设计图(薰衣草夜幕 + 白玻璃拟态 + 金色点缀 + 水晶图标)。
   色值全部程序化取样 / OKLCH 计算 + WCAG 实测,不是猜的。
   约定:玻璃面上的文字是深蓝的(--navy-*),
        页级文字过去是白的(因为页底是夜色),**2026-10-14 起翻成深靛**(见下)。

   —— 2026-10-27 标题字体:接入「小赖字体 / Xiaolai」(源自濑户字体 + 简体补全,OFL 1.1)。
      只用于**标题层**(页标题 / 牌堆名字 / 读数名字 / 卡面名字 / 发现页名字),正文一律不动:
      手写体做 15px 正文会发虚。字形按 unicode 区块拆成 5 片,浏览器只下当前语言需要的那片
      (中文 358KB / 拉丁 19KB / 西里尔 7KB / 希腊 6KB / 韩文 96KB)。
      **阿拉伯不给**:实测缺 7% 的字符,一半换字比不换更难看,让它整段回退系统字体。
      子集来源:参考设计/字体/Xiaolai-Regular.ttf(21.2MB)→ tools 里那次子集化,files 在 vendor/fonts/。
      —— 以下为历史说明 ——

   —— 2026-10-14 第一次改版:页底整体压深(峰值 L 0.85 → 0.70)且色度提一倍。
      原因是白瓷按钮本身就是不透明的 #fff→#eef1fa,原先压在同样发白的页底上
      几乎没有分离度(ΔL 只有 0.08),整页看着一片白。压深之后 ΔL 到 0.22,
      白瓷与玻璃面才立得起来。色相全程恒定在 -92°(只在同一个色系里加深)。

   —— 2026-10-14 第二次改版(用户设计图 2):**整页从「夜幕」翻成「白昼」**。
      页底换成浅薰衣草(顶/底都浅),中间偏上留一团暖白光晕给精灵 —— 和上一版
      正好相反,所以那次「压深」的底部径向光晕这次去掉了,换成一层很淡的紫灰压角。
      底部导航带从深靛玻璃改成**近白磨砂**;选中项从白色改成设计图那颗靛蓝
      (#4f61d2),未选中是灰紫(#5a5a7d)。
      **这不是把色号调浅就完事**:页级文字(标题/说明/页脚)整套从白翻成深靛
      (--ink*)、页级描边从白翻成深靛(--line*)、页级装饰(星尘/感应圈/加载圈)
      从白翻成暖金或紫 —— 浅底上的白字白圈等于没有。
      白玻璃面(卡片/磁贴/抽屉)一处没改:它们本来就是「浅底上的白面」,
      翻主题后照样成立,分离度仍靠 --bg 的亮度差(实测 ΔL 0.22,与上一版同一口径)。
      换主题只动 :root 这一段 + body 那三层 background。
   ============================================================ */

/* ============ 标题字体:小赖字体 / Xiaolai(OFL 1.1)============
   源文件 21.2 MB(44,917 字形),这里按 unicode 区块拆成 5 片、只留项目里真正出现的字符:
     cjk 1806 字 358KB / latin 186 字 19KB / cyrillic 66 字 7KB / greek 59 字 6KB / kr 496 字 96KB
   **阿拉伯语刻意没有**:实测缺 7% 的字符,一句话里一半换字体比不换更难认,让它整段回退 --sans。
   `font-display: swap` —— 首帧先用系统字体,字体到了再换,不挡渲染。 */
@font-face {
  font-family: "Xiaolai";
  src: url("/vendor/fonts/xiaolai-cjk.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
  unicode-range: U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+FF00-FFEF;
}
@font-face {
  font-family: "Xiaolai";
  src: url("/vendor/fonts/xiaolai-latin.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0000-024F, U+1E00-1EFF, U+2000-206F;
}
@font-face {
  font-family: "Xiaolai";
  src: url("/vendor/fonts/xiaolai-cyrillic.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0400-052F;
}
@font-face {
  font-family: "Xiaolai";
  src: url("/vendor/fonts/xiaolai-greek.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0370-03FF, U+1F00-1FFF;
}
@font-face {
  font-family: "Xiaolai";
  src: url("/vendor/fonts/xiaolai-kr.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
  unicode-range: U+1100-11FF, U+AC00-D7AF;
}

/* 标题层只挂这几处(手写体不碰正文,理由见文件顶部的说明) */
.topbar h1,
.plz-h2,
.dk-head .dk-name,
.dk-ro-name,
.pf-title,
.disco-invite h2 { font-family: var(--display); }

:root {
  /* 页底(从上到下)—— 单向渐变,六档全部由 body 直接消费(别再写死字面量)
     2026-10-14 第三版:用户要「页顶文字用白色」→ **顶上一段压深**(夜色 periwinkle,
     白字 5.4:1 / 4.7:1),中下部保持设计图的亮色(精灵与卡片都在那儿)。
     所以页级文字分成两层:页顶那几行走 --top-ink*(白),页顶以下仍是 --ink*(深)——
     下半页是浅底,白字在那里看不见,别整体改白。 */
  --bg-0: #5a689c;
  --bg-1: #6472a4;
  --bg-2: #8d97bd;
  --bg-3: #d3d6e6;
  --bg-4: #e6e0e4;
  --bg-5: #cdc9d8;

  /* 玻璃面 */
  --glass-0: rgba(255, 255, 255, 0.74);
  --glass-1: rgba(255, 255, 255, 0.52);
  --glass-2: rgba(255, 255, 255, 0.28);
  --glass-3: rgba(255, 255, 255, 0.14);
  /* 送礼人页「试听中」那颗播放键的紫罗兰底(giver.html 里 inline 取用) */
  --tile-on-hi: #c8cfeb;
  --tile-on-lo: #7c8fd1;
  --glass-line: rgba(255, 255, 255, 0.8);
  --glass-line-2: rgba(255, 255, 255, 0.44);
  /* ===== 玻璃 v2(2026-10-27,对照用户给的 iOS 精灵设置面板)=====
     和 v1 的差别:模糊更强(34px)、白衬更淡、描边几乎看不见(1px/42%)、阴影更收。
     v1 那套(0.74 白 + 1.5px 亮边)偏"白瓷",v2 才是"毛玻璃":背后内容被糊成色块,
     但层次和文字仍然立得住。 */
  --g2-blur: 34px;
  --g2-blur-card: 20px;
  --g2-tint: rgba(246, 247, 252, 0.62);
  --g2-tint-strong: rgba(250, 250, 253, 0.74);
  --g2-edge: rgba(255, 255, 255, 0.42);
  --g2-shadow: 0 -18px 48px -28px rgba(32, 38, 66, 0.42);
  --g2-radius: 30px;
  /* ===== 玻璃材质三档(2026-10-27)=====
     44 处 backdrop-filter 原来各写各的模糊值(1~20px 九种),观感不统一。
     收敛成三档:thick 浮层 / reg 卡片面板 / thin 按钮标签。
     噪点是玻璃质感的主要来源(比模糊更重要),低透明度叠加,零性能代价。 */
  --g-thick-blur: 18px;
  --g-thick-tint: 0.62;
  --g-reg-blur: 14px;
  --g-reg-tint: 0.48;
  --g-thin-blur: 8px;
  --g-thin-tint: 0.34;
  --g-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
  --shadow: rgba(52, 60, 102, 0.42);
  --shadow-soft: rgba(52, 60, 102, 0.3);

  /* 金(点缀:信封 / 星光 / 徽章 / 主按钮) */
  --gold-1: #f9e6b8;
  --gold-2: #e9c273;
  --gold-3: #c9963f;
  --gold-4: #8a6b3a;
  --gold-ink: #8a6116;          /* 浅玻璃面上的金色文字。
     2026-10-14 白昼主题:页底变浅 → 合成出的玻璃更亮 → 原 #a5761f 在玻璃上只剩 3.7:1,
     深一档后 5.0:1(14px 的积分数字也要过 AA,不能只靠"22px 算大字"那条豁免)。 */
  --gold-line: rgba(246, 214, 150, 0.85);
  --gold-line-soft: rgba(246, 214, 150, 0.5);
  --gold-glow: rgba(240, 190, 100, 0.4);

  /* 玻璃面上的文字(深蓝) */
  --navy-1: #3a3a4c;
  --navy-2: #555a70;                          /* 实测:浅玻璃面上 4.7:1 */
  --navy-3: #6a6e85;                          /* 实测:浅玻璃面上 4.6:1(placeholder / 弱化说明都用它) */

  /* 页顶文字(白)—— 专给"压在页顶那段深色上"的标题与副标题用。
     白晕是设计图里就用过的做法,浅底上也立得住。 */
  --top-ink: #ffffff;
  --top-ink-2: rgba(255, 255, 255, 0.93);
  --top-glow: 0 2px 12px rgba(44, 52, 96, 0.42), 0 0 26px rgba(255, 255, 255, 0.22);
  --top-glow-2: 0 1px 8px rgba(44, 52, 96, 0.36);

  /* 页级文字(深靛)—— 页顶以下那部分用。设计图上标题 #45407A、副标题更淡一档;
     三个值都在最亮的那段页底上实测 ≥ 4.5:1。 */
  --ink: #45407a;
  --ink-2: #55507f;
  --ink-3: #565374;            /* 页脚小字:页底偏紫那一段也要 ≥5:1,留出余量 */
  --line: rgba(69, 64, 122, 0.26);
  --line-2: rgba(69, 64, 122, 0.14);
  --danger: #b6425c;

  /* 旧变量名保留:送礼人页 / 登录页的内联样式仍按这些名字取色 */
  --cream-0: #f5f7fd;
  --cream-1: #e8ecf8;
  --cream-2: #dbe1f2;
  --brown-1: #3a3a4c;
  --brown-2: #555a70;
  --brown-3: #83879c;

  /* ============ 主题色:所有"选中 / 高亮"特效的唯一来源 ============
     设计图里「选中」= 设计图那颗**靛蓝**(#4f61d2)+ 柔光。上一版(夜幕)里
     「选中」是白色描边 —— 白昼主题下白色描边等于没有,所以这个值跟着主题翻了。
     换主题只改这一段:
       --theme-select*  选中描边与光晕(导航药丸 / 召唤圆徽 / 选中文字)
       --theme-accent*  选中态玻璃与主按钮(磁贴选中 / 主按钮 / 主互动键) */
  /* 2026-10-14 第三版:用户要导航选中回到「透明白玻璃」那套质感 →
     选中语言回到**白色描边 + 白发光**(上一版就是这个)。 */
  --theme-select: #ffffff;
  --theme-select-glow: rgba(255, 255, 255, 0.72);
  --theme-accent: #8fa0e6;
  --theme-accent-lite: #c8cfeb;
  --theme-accent-deep: #6f83cb;
  --theme-accent-glow: rgba(150, 160, 235, 0.55);

  /* ============ 底部导航带 ============
     深靛磨砂(回到上一版):压在页底 --bg-5 上合成 #3e4875 → #232a4f。
     半透明 + backdrop blur,页底滚过去时带子会变色 —— 标签的对比度要按"带子合成后的
     实际色"算,而不是按这两个 rgba 本身。白 88% 的标签在带上缘 6.8:1。 */
  --nav-ink: rgba(244, 247, 255, 0.88);   /* 未选中(白 88%) */
  /* v2(2026-10-27):底栏改成深色毛玻璃,0.78/0.92 → 0.58/0.66,让折射与背后水光透出来 */
  --nav-0: rgba(46, 54, 102, 0.58);
  --nav-1: rgba(24, 29, 62, 0.66);
  --nav-line: rgba(255, 255, 255, 0.3);
  /* 悬浮 dock:左右/底部的留白;选中项改成图标外的一圈白框(06 SEABREEZE) */
  --nav-inset-x: 14px;
  --nav-inset-b: 10px;
  --nav-frame-line: rgba(255, 255, 255, 0.92);
  --nav-frame-ring: rgba(190, 210, 255, 0.55);
  --nav-frame-glow: rgba(205, 225, 255, 0.22);

  --r-sm: 14px;
  --r: 20px;
  --r-lg: 26px;
  --r-pill: 999px;
  /* ============ 可视高(--app-h)= 所有「正好一屏」的容器高度唯一来源 ============
     真值由 public/js/viewport-height.js 在首帧前写进 :root(innerHeight)。
     这里的两行只是**兜底**:
       · 100vh  = JS 完全没跑到时的最后退路(大视口,带工具栏的浏览器上会偏大 —— 但不至于 0);
       · 100dvh = 能理解动态视口的内核上,连"JS 跑之前的那一帧"也准。
     为什么不用 dvh 当唯一来源:旧内核(微信安卓 X5 / 老 WebView)不认它,会退回 100vh,
     于是"一屏"在这些浏览器上整体高出一截 —— 这正是「换个浏览器显示不一样」的主因。 */
  --app-h: 100vh;
  --app-w: 460px;
  --nav-h: 70px;
  /* 左右内边距:横屏刘海屏上左右各有一截被圆角/刘海压住(env 实测 44px),
     浏览器里横屏时两侧并没有被挖掉 —— 所以也和顶部一样,**只在"整屏"里吃**这两段
     (见文件末尾的 display-mode 规则);这里先给竖屏/浏览器的 20px。 */
  --pad-x: 20px;
  /* 页面为固定底导预留的高度(底导高度 + 呼吸间距),以及顶部安全区。
     **必须带上安全区**:index.html 是 viewport-fit=cover,刘海屏上 `.nav` 的实际高度是
     --nav-h + env(safe-area-inset-bottom)(真机实测 102px,不是 70px)—— 少算这一截,
     每屏最后一块都会往底导里钻几十像素(发现页那行脚注就是这么被盖住的)。
     底部这一截**任何浏览器都要加**(fixed 元素贴着视口底边)。
     顶部**只在"整屏应用"里加**(见文件末尾的 display-mode 规则):浏览器里打开时页面并不在
     状态栏底下,而部分手机浏览器(X5 / 微信那个内核)照样会把状态栏高度报进 safe-area-inset-top ——
     直接吃这一截,每页顶上就白白多出四五十像素(用户 2026-10-14 报的「很多页面头部有一些留空」)。 */
  --app-pad-b: calc(var(--nav-h) + var(--nav-inset-b) + 16px + var(--sab));
  --app-pad-t: 0px;

  /* 底部安全区(刘海机型的 home 条)。抽成变量是为了**键盘弹起时能一把关掉**:
     这 34px 是给"贴着屏幕底"的元素躲 home 条的,键盘一上来它就成了白占的一条空白。
     键盘状态与写入见 public/js/keyboard.js(它同时给 --kb / --vvh / --vvtop)。 */
  --sab: env(safe-area-inset-bottom, 0px);

  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB",
    "Noto Sans CJK SC", "Microsoft YaHei", sans-serif;
  /* 标题层(2026-10-27):小赖字体。按 unicode 区块分成 5 片(见文件顶部 @font-face),
     浏览器只为当前语言那一两片发请求;阿拉伯语没有这一族,自动落回 --sans。 */
  --display: "Xiaolai", var(--sans);
  --serif: "Didot", "Bodoni MT", "Playfair Display", "Songti SC", Georgia, serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* JS 没跑到时的次优兜底:认识 dvh 的内核(Chrome 108+ / iOS 15.4+)上,动态视口比 100vh 准。
   注意:JS 一旦写过内联的 --app-h,内联样式优先,这一段就只剩"首帧前"的意义。 */
@supports (height: 100dvh) {
  :root { --app-h: 100dvh; }
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* 触控:按钮/链接/输入这类"按一下就该响应"的控件,双击缩放没有意义 ——
   manipulation 把双击缩放与旧 WebView 的 300ms 点击延迟一起去掉(双指缩放不受影响),
   也顺带挡住"手快点两下页面就被放大"这种各家浏览器表现不一样的意外。
   **不要**套到可滚动容器上(那会把 pan 手势也一起关掉;轮播的 .plz-arc 就有自己的 pan-y)。 */
button, a, [role="button"], input, textarea, select, label { touch-action: manipulation; }

html {
  overflow-x: hidden;
  text-size-adjust: 100%;
  /* 微信「字体大小」/ 安卓系统字号缩放会把页内文字按倍数放大(行数一变,"一屏"就破)——
     -webkit- 前缀是老内核唯一认得的形式,必须两条都写。 */
  -webkit-text-size-adjust: 100%;
  /* 挡住安卓 Chrome 的强制深色 / 微信深色模式把浅色主题反色("打开颜色不一样") */
  color-scheme: light only;
  /* 禁掉整页橡皮筋与下拉刷新(Chrome 安卓):固定底导不会在回弹时"脱位"。
     不认识它的旧内核直接忽略,无副作用。 */
  overscroll-behavior-y: none;
}

body {
  /* 2026-11-02：动作视频 transform:scale(1.15) 后那圈透明边距会溢出，
     在 body 上裁掉即可 —— 放在 .screen-home 上会改变它的布局宽度，把内容挤窄。 */
  overflow-x: hidden;
  margin: 0;
  min-height: var(--app-h);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.75;
  /* 页顶夜色 periwinkle → 中偏上那团暖白光晕(精灵带)→ 底部一层很淡的紫灰压角。
     右上那圈现在往下压深(和页顶同向),白字在右上角也立得住。
     六档色值全部来自 --bg-*(改主题只动 :root)。 */
  background:
    radial-gradient(120% 46% at 86% 0%, rgba(44, 52, 96, 0.55), rgba(44, 52, 96, 0) 62%),
    radial-gradient(112% 40% at 52% 40%, rgba(255, 250, 240, 0.62), rgba(255, 250, 240, 0) 68%),
    radial-gradient(120% 26% at 50% 100%, rgba(120, 118, 160, 0.16), transparent 72%),
    linear-gradient(180deg, var(--bg-0) 0%, var(--bg-1) 13%, var(--bg-2) 29%, var(--bg-3) 43%,
      var(--bg-4) 67%, var(--bg-5) 100%);
  background-attachment: fixed;
}

/* ============ 页底场景图(2026-10-16 起,2026-10-26 换过默认那张):全站 ============
   页底从"那套渐变"换成图 —— **先只做了首页 / 广场,当天又铺满全站**。
   底下的渐变**整套让位** —— 它是不透明的,留在场景图上面就等于没有场景图
   (所以这个页面里 `body` 那条 background 只对"还没有场景"的状态生效,现在等于只剩开机那一帧)。
   两张图都是 941×1672(手机竖屏比例),竖屏上 `cover` 左右各裁掉一点点正好;
   **为什么是 `center top` 而不是居中**:桌面面板(1280×720)上 cover 会把图放大到 2274 高、
   只露最上面一条 —— 那一条正好是画面的上半部;要是居中对齐,露出来的就是中间那片空雾,
   整屏等于一块渐变。

   两张图的分工(文件名按**内容**取,不按"最先用它的那一屏"):
     · `.scene-isles`   = **全站默认**:首页 / 发现 / 召唤 / 我的 / 我的祝福 / 祝福详情 /
                          收到祝福 / 选择使用方式 / 拦截页 / 送礼人页。
                          2026-10-16~25 是「云海浮岛」(`bg-isles` 这名字从那会儿留下来);
                          2026-10-26 起换成用户的「柔光 pastel 波纹渐变壁纸」
                          (柔光蓝白渐变,没有浮岛也没有云海 —— 名字没改,免得动 JS 里那张映射表);
     · `.scene-gallery` = 精灵画廊(`bg-gallery.webp`)—— 广场 + 图鉴(图鉴是广场那张「画廊」的
                          详情页,同一片景)。这张没换,还是 2026-10-16 的深蓝夜色设计图。
   类名由各页自己的 JS 按**当前这一屏**扳(app.js 那个 MutationObserver / giver.js 顶部),
   不靠每个渲染函数自己记 —— 漏挂的后果不是"少一张图",是整片页底掉成白底(见下面 .scene-bg)。 */
body.scene-bg {
  background-repeat: no-repeat;
  background-position: center top;
  background-size: cover;
  background-attachment: fixed;
  /* 图还没到的那一帧的兜底:2026-10-26 换图时跟着改成"新壁纸顶部压深后的蓝"
     (原 #5c6ea8 是照旧图那层深蓝天取的),别在深色底上闪一下 */
  background-color: #56628f;
  /* 兜底压深 —— 只服务"既没挂 isles 也没挂 gallery"的意外状态(那时变量必须有个值,
     否则 background-image 整条声明失效、页底掉成白底)。真正上场的是下面两条。
     这几个数字仍然是 2026-10-16 那版(给深蓝天 / 云海花境挑的)。 */
  --scene-scrim: linear-gradient(180deg,
    rgba(38, 46, 92, 0.6) 0%, rgba(38, 46, 92, 0.56) 6%, rgba(38, 46, 92, 0.44) 12%,
    rgba(38, 46, 92, 0.16) 21%, rgba(38, 46, 92, 0) 33%);
}
/* ---- 页底两张图**各带一条压深**(2026-10-26 分家)----
   原来两张共用 .scene-bg 那一条,因为两张图顶部亮度差不多(都是深蓝夜空)。
   换图之后这个前提没了:新壁纸顶部是「柔光蓝白」——行均值 rgb(180,204,243)、相对亮度 0.60,
   旧场景图顶部是深蓝天(0.26)。同一条压深要么压不住壁纸、要么把广场压成黑带,所以拆成两条。
   两条都得写在 .scene-bg 之后:都是 body 上的同特异性声明,靠**源序**分胜负。

   `scene-isles`(柔光 pastel 波纹渐变壁纸 · 全站默认):
     逐列实测(390×844,算的是页底本身,不含 --top-glow 那圈暗晕):
       标题行(22px/800,大字按 3:1)   5% 分位 4.92 / 中位 5.50
       副标题行(15px,按 4.5:1)       5% 分位 3.94 / 中位 4.62
     旧图在 0.6 那条下是 4.32 / 3.77,所以按"不退步"重新标定:整条 +0.08,
     收干位置 33% → 42%(壁纸到 33% 处还有 0.68,比旧图那一带亮,得多压一会儿)。
     360×640 那档最吃紧(标题 4.36 / 副标题 3.16),副标题靠 --top-glow 暗晕托底;
     想把最差那几列也钉死 4.5 得整条再 +0.1 —— 页顶就糊成一块石板蓝了,不划算。
   `scene-gallery`(精灵画廊 · 广场 + 图鉴):还是那张深蓝夜色的图,原值照旧、一个字没动。 */
body.scene-isles { --scene-scrim: linear-gradient(180deg,
    rgba(38, 46, 92, 0.68) 0%, rgba(38, 46, 92, 0.64) 6%, rgba(38, 46, 92, 0.5) 12%,
    rgba(38, 46, 92, 0.18) 24%, rgba(38, 46, 92, 0.05) 36%, rgba(38, 46, 92, 0) 42%);
  background-image: var(--scene-scrim), url("/assets/bg-isles.webp"); }
body.scene-gallery { --scene-scrim: linear-gradient(180deg,
    rgba(38, 46, 92, 0.6) 0%, rgba(38, 46, 92, 0.56) 6%, rgba(38, 46, 92, 0.44) 12%,
    rgba(38, 46, 92, 0.16) 21%, rgba(38, 46, 92, 0) 33%);
  background-image: var(--scene-scrim), url("/assets/bg-gallery.webp"); }
/* 「头部偏低」的那几屏单独一条更长的压深(拦截页 / 选择使用方式 / 碰串直出祝福)。
   这三屏的白色头部文字**不在顶栏里**(标题 + 一句说明直接排在 16%~26% 处),
   而默认那条压深到 12% 就开始收 —— 白字落到那儿只剩 1.9~2.4:1,和原来的渐变页底
   (那一带约 2.9~3.7)比是退步。这一条把压深撑到 ~26%、到 58% 才收干。
   这三屏挂的是 isles,2026-10-26 换图后跟着整条 +0.04(壁纸比旧图亮,幅度小于默认那条
   —— 它本来就压得深,再加就撞上"α>0.7 等于黑带"那条线了)。
   (**必须排在 .scene-isles 后面** —— 同特异性,靠源序分胜负。
    压深只作用在页底上,卡片/立绘这些内容一层不受影响。) */
body.scene-head-low { --scene-scrim: linear-gradient(180deg,
    rgba(38, 46, 92, 0.68) 0%, rgba(38, 46, 92, 0.66) 8%, rgba(38, 46, 92, 0.64) 18%,
    rgba(38, 46, 92, 0.64) 26%, rgba(38, 46, 92, 0.52) 36%, rgba(38, 46, 92, 0.24) 48%,
    rgba(38, 46, 92, 0) 58%); }
/* 页级「淡墨小字」在场景页底上整体深一档。
   这一组都是**直接压在页底上、不在玻璃卡里**的小字(页脚说明 / 滑动提示 / 页底链接),
   它们原本按"浅薰衣草页底"挑的色:场景页底(天空 + 水面)明显更亮更蓝,
   --navy-2 / --ink-3 在亮处只有 3.5~4.4:1(实测:图鉴页脚 3.84、广场滑动提示 3.55)。
   统一换 --navy-1 —— 实测在同样的位置是 5.2~9.3:1,视觉上仍是"淡墨",不是正文那一档。 */
body.scene-bg .foot-note,
body.scene-bg .plz-hint,
body.scene-bg .uc-note,
body.scene-bg .tg-note { color: var(--navy-1); }

/* ============ 开机动画(2026-10-16)============
   用户给的一段 5 秒动画(素材生成见 tools/build-boot-video.py),**碰一下手串之后、进产品之前**
   满屏放一遍。什么时候放 / 什么时候不放,判据在 app.js 的 playBootAnimation 那一段,这里只管画。
   层级 60:盖得住底导(20)、抽屉(30)、收容动画层(40)、自家确认弹窗(50),
   **但让 toast(70)与横屏提示(80)留在上面** —— 认下手串那句 toast 正好在这时候弹,
   而横屏该提示还是要提示(动画救不了横屏版式)。 */
.boot {
  position: fixed;
  inset: 0;
  z-index: 60;
  overflow: hidden;
  /* 与动画首帧的深蓝同色:万一视频还没出第一帧,或者被 contain 留了边,露出来的是它,
     不是一个突兀的黑框(首帧实测 #101a35 上下)。 */
  background: #0b1226;
  opacity: 0;
  transition: opacity 0.42s ease;
}
.boot.on { opacity: 1; }
.boot-video {
  /* **必须绝对定位铺满,不能用 `display:grid; place-items:center` + `height:100%`**:
     宽屏上那一行会被视频的固有高度(1280 宽 → 2276 高)撑大,`height:100%` 于是解析成 2276,
     视频盒子比视口高一倍多、被 overflow 裁掉 —— `object-fit` 还在,但已经是在一个大盒子里
     居中裁一块,看着就是"整幅被放大"。(窄屏上那一行被 align-content 拉满,所以看不出来 ——
     2026-10-16 就是这么漏过去的:竖屏一路正常,桌面面板上才发现。)
     absolute + inset:0 让盒子直接等于视口的 padding box,尺寸确定,object-fit 才按预期生效。 */
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  /* 竖屏手机上铺满(和页底那两张场景图同一个口径:9:16 的素材在 0.46 的屏上左右各裁一点点);
     **比 9:16 更宽的屏**(桌面面板 / 平板 / 横屏)改成整幅可见、两侧留边 ——
     那种屏上 cover 会把这套构图裁得只剩中间一条,精灵被放大到认不出来。 */
  object-fit: cover;
}
@media (min-aspect-ratio: 9/16) {
  .boot-video { object-fit: contain; }
}
/* 减少动效偏好:淡入淡出也一并去掉(整段动画本身在那条路上直接不放,见 app.js) */
@media (prefers-reduced-motion: reduce) {
  .boot { transition: none; }
}

/* 星尘:稀疏的金点 + 两颗四角星,缓慢闪烁(铺太密会变成花布)。
   白昼主题里白点压在浅底上等于没有,所以整片走暖金(设计图的星尘也是金/暖白)。 */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 340'%3E%3Cpath d='M58 89 C58.8 93.6 60.4 95.2 65 96 C60.4 96.8 58.8 98.4 58 103 C57.2 98.4 55.6 96.8 51 96 C55.6 95.2 57.2 93.6 58 89 Z' fill='%23e7c68a'/%3E%3Cpath d='M232 205.5 C232.5 208.4 233.6 209.5 236.5 210 C233.6 210.5 232.5 211.6 232 214.5 C231.5 211.6 230.4 210.5 227.5 210 C230.4 209.5 231.5 208.4 232 205.5 Z' fill='%23f3dcae'/%3E%3Ccircle cx='168' cy='40' r='1.2' fill='%23f6e6c2'/%3E%3Ccircle cx='96' cy='268' r='1.6' fill='%23f0d8a4'/%3E%3C/svg%3E"),
    radial-gradient(1.4px 1.4px at 40px 58px, rgba(236, 205, 148, 0.9), transparent 55%),
    radial-gradient(1.1px 1.1px at 206px 152px, rgba(240, 214, 165, 0.8), transparent 55%),
    radial-gradient(1.3px 1.3px at 118px 252px, rgba(246, 226, 186, 0.85), transparent 55%);
  background-size: 300px 340px, 300px 340px, 300px 340px, 300px 340px;
  background-repeat: repeat;
  opacity: 0.5;
  animation: twinkle 7s ease-in-out infinite;
}
@keyframes twinkle {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 0.24; }
}

#app {
  max-width: var(--app-w);
  margin: 0 auto;
  min-height: var(--app-h);
  padding: var(--app-pad-t) var(--pad-x) var(--app-pad-b);
  position: relative;
}
/* 没有底导的那几屏(收到祝福 / 我的祝福 / 各类闸页)不该替底导留出那 96px ——
   留着的话页面底下凭空多一截死区,内容明明只多出几十像素,却整页能滚。
   判据从 `:has(.nav)` 换成 JS 打的 `#app.no-nav`(app.js 里的 MutationObserver):
   `:has` 在旧内核(微信安卓 X5 等)不认识,那条规则等于不存在 —— 同一份页面在两个
   浏览器上一个不滚一个能滚,正是"显示不一样"的一种。class 没打上时退回上面的默认值
   (照样能滚,只是多一截空白),不会坏。 */
#app.no-nav { padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px)); }

/* 释放阀(JS 加 .scrolls,见 app.js 的 relaxScreen):「定高 + 让位」的几屏(首页/召唤/祝福详情)
   在极端情况下(极矮视口、横屏、系统字号放大)地板之和也会超过可视高 —— 这时把这屏
   松成**自然高度 + 整页滚动**,而不是继续压缩内容。
   两个属性都不能少:
     · height:auto —— 让子项回到自然尺寸(flex 不再分配负余量,谁也不缩);
     · margin-bottom:0 —— 把底导那 96px 的负 margin 还回来。留着它的话,滚动到底时
       最后一块会正好落在固定底导底下(96px 白留)。 */
.screen-home.scrolls,
.screen-crystal.scrolls,
.screen-view.scrolls {
  height: auto;
  min-height: 0;
  margin-bottom: 0;
}

a { color: var(--gold-2); text-decoration: none; }
button { font-family: inherit; }
img { display: block; max-width: 100%; }

/* ---------- 顶栏 ---------- */
.topbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 20px 0 6px;
}
.topbar .tb-left { min-width: 0; }
.topbar h1 {
  margin: 0;
  font-size: 25px;
  font-weight: 800;
  letter-spacing: 0.01em;
  line-height: 1.25;
  /* 标题是**用户自己起的精灵名**(长度不可控),顶栏右边又固定挂着语言/头像 ——
     窄屏 + 长名字时它会折行,而"5 字 + 1 个字"那种断法看着像出了错。
     折行本身不可避免(名字不能截、右侧控件不能挤),但断点可以平衡:
     同样的高度下变成 3/3,英文标题也不会把最后一个词孤零零留在第二行。
     不认这条的旧内核直接忽略,退回今天的断法。 */
  text-wrap: balance;
  color: var(--top-ink);
  text-shadow: var(--top-glow);
}
.topbar .tb-sub {
  color: var(--top-ink-2);
  font-size: 13px;
  margin-top: 3px;
  text-shadow: var(--top-glow-2);
}
.center-title { text-align: center; flex: 1; }
.center-title .tb-sub { margin-top: 4px; }

/* 带返回键的页(`.topbar.has-center`):标题要在**整条顶栏**里居中。
   原来靠 flex:1 + text-align:center —— 标题块从返回键右边开始,整体被挤偏
   (实测偏 26px,用户直接看出来了)。改成三列 grid、两侧等宽(1fr auto 1fr):
   标题落中间列,不管左边返回键、右边积分或按钮多宽都居中;标题本体不接点击,免得盖住两侧按钮。 */
.topbar.has-center {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: start;
  column-gap: 12px;
}
.topbar.has-center .back-btn { grid-column: 1; justify-self: start; }
.topbar.has-center .center-title { grid-column: 2; flex: none; pointer-events: none; }
.topbar.has-center > :last-child:not(.center-title) { grid-column: 3; justify-self: end; }

.back-btn {
  flex: 0 0 auto;
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--glass-line);
  background: rgba(74, 84, 124, 0.3);
  color: #fdfbff;
  cursor: pointer;
  backdrop-filter: blur(var(--g-reg-blur));
  box-shadow: 0 0 14px rgba(255, 255, 255, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.28);
}
.back-btn:active { transform: scale(0.95); }

/* 右上角圆钮(设计图:玻璃圆 + 奶油幽灵脸;有头像时显示头像) */
.avatar-btn {
  width: 42px; height: 42px;
  border-radius: 50%;
  overflow: hidden;
  padding: 0;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  cursor: pointer;
  border: 1.5px solid rgba(255, 255, 255, 0.86);
  background: rgba(70, 80, 120, 0.42);
  color: #f7f1dc;
  backdrop-filter: blur(var(--g-reg-blur));
  box-shadow:
    0 0 16px rgba(255, 255, 255, 0.26),
    0 6px 16px -8px rgba(44, 52, 96, 0.7),
    inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
.avatar-btn img { width: 100%; height: 100%; object-fit: cover; }
.avatar-btn svg { filter: drop-shadow(0 1px 3px rgba(30, 36, 70, 0.5)); }

/* 顶栏右上那一组(语言下拉 + 头像)。整组固定宽、两枚控件等高居中:
   头像 42px 是基准,34px 的药丸居中放在它左边 —— 底边对齐比顶边对齐更像一组控件。
   顺序刻意是「语言在左、头像在右」:右下角那枚永远通往「我的」,别让设置挤掉它。 */
.tb-acts {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
}

/* ---------- 语言药丸 + 它点开的那层弹层(首页顶栏 / 碰串首屏各一枚) ----------
   药丸:一枚玻璃药丸,材质与右上角那枚头像/返回键同族(深靛半透 + 白线 + 内高光)。
   这两处都落在页顶的深色带上,所以文字用白色。白字压在这层玻璃上的对比度实测约 6:1
   (rgba(74,84,124,.3) 合成到 --bg-0 上 ≈ #556292),过 4.5:1。
   它自己就是按钮(`<button data-lang-open>`),点开的是 `openLangPicker()` 那层弹层 ——
   2026-10-15 换掉了原生 `<select>`:原生下拉的长相由浏览器/系统决定(桌面只能改选项底色,
   手机整块系统弹窗),而用户要的是"弹窗也要和产品主题一样"。 */
.lang-pick {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 12px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--glass-line);
  background: rgba(74, 84, 124, 0.3);
  color: #fdfbff;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  cursor: pointer;
  backdrop-filter: blur(var(--g-reg-blur));
  box-shadow: 0 0 14px rgba(255, 255, 255, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.28);
  text-shadow: var(--top-glow-2);
  transition: transform 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}
.lang-pick:active { transform: scale(0.96); }
.lang-pick svg { flex: 0 0 auto; opacity: 0.92; }
/* 小箭头:和「我的」页折叠行同一套画法(旋转的方角),颜色跟着药丸的文字走。
   上移 2px 是视觉居中 —— 45° 旋转后的方角重心偏下。展开时翻个身,和弹层的出现对上。 */
.lp-caret {
  width: 6px;
  height: 6px;
  margin-left: 1px;
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  opacity: 0.88;
  transition: transform 0.18s ease;
}
.lang-pick[aria-expanded="true"] .lp-caret { transform: translateY(0) rotate(-135deg); }

/* 弹层里的语言行:选中态与「陪伴形象」同一套(accent 描边 + 一点光晕 + 一枚 ✓),
   所以它看起来就是产品自己的一屏,而不是外来控件。整行都是点击区(≥48px)。 */
.lang-opts {
  display: grid;
  /* 9 门语言(2026-10-20):单列 9×52px + 间距会顶出 568 高的屏幕,所以两列;
     再窄的屏(320)每列仍有 ~140px,最长的「Français / Русский」也放得下。 */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 9px;
  margin-top: 4px;
}
/* 23 门语言是**奇数**(2026-10-27 从 9 加到 23 之后):两列排到最后,最后一项会孤零零占半行、
   右边空一格 —— 看着像"少了半个按钮"。让它横跨两列补满那一行。
   选择器用 :nth-child(odd)——偶数项数时最后一项本来就是右列,不该跨。 */
.lang-opts > .lang-opt:last-child:nth-child(odd) {
  grid-column: 1 / -1;
}
.lang-opt {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 52px;
  padding: 0 16px;
  border-radius: 16px;
  border: 1.5px solid var(--glass-line);
  background: rgba(255, 255, 255, 0.815);
  color: var(--navy-1);
  font-family: inherit;
  font-size: 15.5px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.14s ease;
}
.lang-opt:active { transform: scale(0.985); }
/* 键盘的聚焦环:用**这套设计的强调色**,并且画在选中态描边的外面几像素 ——
   否则它和选中态那圈 accent 边框在同一个位置重叠(实测看着像"双重描边、还压了一条深灰")。
   指针用户看不到它(点开弹层不产生 :focus-visible),键盘进来才画。 */
.lang-opt:focus-visible {
  outline: 2px solid var(--theme-accent-deep);
  outline-offset: 3px;
}
.lang-opt.on {
  border-color: var(--theme-select);
  background: linear-gradient(180deg, #ffffff, #dfe5f7);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.85), 0 0 18px rgba(180, 190, 235, 0.7);
}
/* 纯 CSS 的小对勾,和选中态同一份判据(同 .comp-item.on 的画法) */
.lang-opt.on::after {
  content: '✓';
  position: absolute;
  top: 50%;
  right: 14px;
  translate: 0 -50%;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--theme-accent-deep);
  color: #fff;
  font-size: 12.5px;
  line-height: 1;
}

/* ---------- 金色渐变标题(用在浅玻璃面上,所以取深金:最亮端也还有 3:1) ---------- */
.gold-title {
  background: linear-gradient(180deg, #c8963a 4%, #a5761f 52%, #8a5f16 96%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 1px 0 rgba(255, 255, 255, 0.7));
}

/* ---------- 精灵立绘区 ---------- */
.sprite-stage {
  position: relative;
  display: grid;
  place-items: center;
  padding: 6px 0 10px;
  min-height: 240px;
}
.sprite-stage .sprite {
  position: relative;
  width: min(64vw, 280px);
  filter: drop-shadow(0 0 38px rgba(255, 250, 240, 0.6)) drop-shadow(0 18px 40px rgba(88, 98, 150, 0.34));
  animation: breathe 6.5s ease-in-out infinite;
}
.sprite-stage .sprite.open-pose { width: min(58vw, 250px); }
.sprite-stage.hero { min-height: 300px; }

@keyframes breathe {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-9px); }
}

/* ---------- 首页:每只精灵各自的待机动作 ----------
   上面那条 `breathe` 是水晶页 / 聊天页 / 祝福页的地基,这里一个字都不改;
   首页那只由 `spriteStage({ cls:'hero', motion })` 多带一个 data-member,下面这组
   选择器只命中 `.screen-home`。作用域写这么长是刻意的:少一层都可能把别的页卷进来。

   动作原型只有五套,每只的差异全落在 CSS 变量上(振幅 / 周期 / 相位):
   加一只新精灵却不写参数时,退化成默认的慢飘 —— 也就是现在的观感,不会坏。 */
.screen-home .home-hero .sprite-stage .sprite {
  animation-name: var(--mo-name, mo-float);
  animation-duration: var(--mo-dur, 6.5s);
  animation-timing-function: var(--mo-ease, ease-in-out);
  animation-delay: var(--mo-delay, 0s);
  animation-iteration-count: infinite;
  transform-origin: var(--mo-origin, 50% 50%);
  will-change: transform;
}

/* 慢飘:升起来时向右带一点、落下去时向左回 —— 一条斜着的小环,不是纯上下 */
@keyframes mo-float {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(0); }
  33% {
    transform: translate3d(var(--mo-x, 0px), calc(var(--mo-y, 9px) * -1), 0)
      rotate(var(--mo-r, 0deg));
  }
  66% {
    transform: translate3d(calc(var(--mo-x, 0px) * -1), calc(var(--mo-y, 9px) * -0.35), 0)
      rotate(calc(var(--mo-r, 0deg) * -1));
  }
}

/* 轻跳:落地先压一下(squash)→ 起跳 → 回到地面再回弹一点。
   两段缓动写在关键帧里:起跳那段落得快、落下那段带重力感。 */
@keyframes mo-bounce {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1, 1) rotate(0); }
  18% {
    transform: translate3d(0, 2px, 0) scale(1.02, 0.97);
    animation-timing-function: cubic-bezier(0.25, 0.6, 0.35, 1);
  }
  45% {
    transform: translate3d(0, calc(var(--mo-y, 7px) * -1), 0) scale(0.995, 1.01)
      rotate(var(--mo-r, 0deg));
    animation-timing-function: cubic-bezier(0.55, 0, 0.8, 0.5);
  }
  72% { transform: translate3d(0, 1px, 0) scale(1.015, 0.985) rotate(0); }
}

/* 钟摆:以底部为支点左右摆(transform-origin 由成员参数给) */
@keyframes mo-sway {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(0); }
  30% {
    transform: translate3d(var(--mo-x, 4px), calc(var(--mo-y, 0px) * -1), 0)
      rotate(var(--mo-r, 1.8deg));
  }
  70% {
    transform: translate3d(calc(var(--mo-x, 4px) * -1), calc(var(--mo-y, 0px) * -1), 0)
      rotate(calc(var(--mo-r, 1.8deg) * -1));
  }
}

/* 呼吸:整体轻微缩放 + 上升一点点(火焰 / 兔子那种) */
@keyframes mo-pulse {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50% { transform: translate3d(0, calc(var(--mo-y, 3px) * -1), 0) scale(var(--mo-sc, 1.02)); }
}

/* 几乎不动:石头那种,只有极慢的一点点起伏 */
@keyframes mo-still {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50% { transform: translate3d(0, calc(var(--mo-y, 1.5px) * -1), 0); }
}

/* ---------- 绒光幽灵(默认精灵)的首页专属动画 ----------
   **时间线不在 CSS 里**:它是一段剧本(开场呼吸 → 12 个随机落点的漂移 → 回中心,循环),
   由 public/js/arun-motion.js 用 GSAP 驱动;点一下精灵随机换表情也写在那个文件里。
   参数在 ARUN_CONFIG 里。这里只留三件事:关掉 CSS 动画、给亮度呼吸留一个 CSS 变量、
   GSAP 加载失败时的兜底慢飘。其他 12 只精灵的 mo-* 规则与本块无关。 */

/* 粒子闪烁:整块淡入淡出 + 轻微放大(点本身画在 background-image 里) */
@keyframes mo-arun-dust {
  0%, 100% { opacity: 0.18; transform: scale(0.96); }
  42% { opacity: 0.92; transform: scale(1.03); }
  62% { opacity: 0.55; transform: scale(1); }
}

/* 逐只参数。--mo-safe = 振幅 + 旋转包围盒增量 + 一点余量,
   给首页那条 max-height 留位(见 .screen-home .home-hero .sprite-stage .sprite)。
   负延迟让每只从自己周期里的不同位置开始,不会一起吸一口气。 */
.screen-home .home-hero .sprite-stage[data-member="arun"] {
  /* GSAP 接管时这几项只服务"兜底慢飘";--mo-safe 仍然要给 max-height 留位 */
  --mo-dur: 7.5s;
  --mo-y: 9px;
  --mo-x: 2px;
  --mo-r: 0.6deg;
  --mo-safe: 34px;
}

/* 绒光幽灵:GSAP 时间线负责位移/旋转/缩放,CSS 动画必须先关掉(两套时钟会打架)。 */
.screen-home .home-hero .sprite-stage[data-member="arun"] .sprite {
  animation: none;
  z-index: 1;
}
/* GSAP 或模块没加载成功时才启用:最朴素的慢飘,首页不会站着不动。 */
.screen-home .home-hero .sprite-stage.arun-css-fallback .sprite {
  animation: mo-float var(--mo-dur, 7.5s) ease-in-out infinite;
}
/* 亮度呼吸:GSAP 补间 --arun-glow,CSS 把它混进原来的两层 drop-shadow 里。
   两个 drop-shadow 必须和基础 .sprite 那条保持一致,否则挂载瞬间会换一层光。 */
.screen-home .home-hero .sprite-stage.arun-gsap .sprite {
  filter: brightness(var(--arun-glow, 1))
    drop-shadow(0 0 38px rgba(255, 250, 240, 0.6))
    drop-shadow(0 18px 40px rgba(88, 98, 150, 0.34));
  will-change: transform, filter;
}

/* arun 专属的动画面(只挂首页这一只;其他精灵仍是一个 img)。
   里面始终只有一张立绘:GSAP 在 .arun-body 上做位移/轻微倾斜/缩放(到处飘),
   **没有转身/翻转**;点一下精灵由 JS 随机换一张表情(src 换的是表情帧,不是立绘)。
   .arun-rig 只是居中容器,.arun-body 是真正做变换的盒子(尺寸由 JS 写)。 */
.screen-home .home-hero .sprite-stage .arun-rig {
  width: 100%;
  height: 100%;
  min-height: 0;
  display: grid;
  place-items: center;
}
.screen-home .home-hero .sprite-stage .arun-body {
  position: relative;
  will-change: transform;
}
/* 只有默认精灵那只「点一下换表情」;其他精灵点一下只用来申请运动权限(iOS),别给可点的指针 */
.screen-home .home-hero .sprite-stage[data-member="arun"] .arun-body { cursor: pointer; }
.screen-home .home-hero .sprite-stage .arun-body .sprite {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-height: none;
  object-fit: contain;
}
/* 只有默认精灵由 GSAP 接管点位(两套时钟会打架),才关掉 CSS 待机动画;
   其他精灵的 `.arun-body` 只是摇晃用的外壳,里面那张立绘**照旧跑自己的关键帧**。 */
.screen-home .home-hero .sprite-stage[data-member="arun"] .arun-body .sprite { animation: none; }
/* 旧的 CSS 金色拖尾已随 GSAP 时间线一起去掉;点状星尘(::after)保留。 */
.screen-home .home-hero .sprite-stage[data-member="arun"]::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0; bottom: 0;
  margin: auto;
  width: min(62vw, 272px);
  height: min(62vw, 272px);
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(circle at 18% 34%, rgba(255, 240, 205, 0.95) 0 1.6px, transparent 2.6px),
    radial-gradient(circle at 82% 28%, rgba(255, 216, 150, 0.9) 0 1.3px, transparent 2.3px),
    radial-gradient(circle at 26% 74%, rgba(255, 246, 220, 0.9) 0 1.5px, transparent 2.5px),
    radial-gradient(circle at 74% 78%, rgba(255, 208, 138, 0.85) 0 1.2px, transparent 2.2px),
    radial-gradient(circle at 90% 56%, rgba(255, 236, 195, 0.8) 0 1px, transparent 2px),
    radial-gradient(circle at 10% 58%, rgba(255, 232, 190, 0.8) 0 1px, transparent 2px);
  background-repeat: no-repeat;
  animation: mo-arun-dust 4.6s ease-in-out infinite;
  animation-delay: -1.4s;
  will-change: opacity, transform;
}
/* 原有的四颗星在这一只身上闪得稍快一点(其他精灵仍按原来的 3.8s) */
.screen-home .home-hero .sprite-stage[data-member="arun"] .spark {
  animation-duration: 2.9s;
}
@media (prefers-reduced-motion: reduce) {
  .screen-home .home-hero .sprite-stage[data-member="arun"] .sprite { animation: none; }
  .screen-home .home-hero .sprite-stage[data-member="arun"]::after { animation: none; }
}
.screen-home .home-hero .sprite-stage[data-member="yunrong"] {
  --mo-name: mo-bounce; --mo-dur: 5.6s; --mo-y: 6px; --mo-r: 0.8deg;
  --mo-delay: -1.6s; --mo-origin: 50% 92%; --mo-safe: 22px;
}
.screen-home .home-hero .sprite-stage[data-member="xingling"] {
  --mo-name: mo-float; --mo-dur: 9s; --mo-y: 8px; --mo-x: 3px; --mo-r: 1.6deg;
  --mo-delay: -2.2s; --mo-safe: 24px;
}
.screen-home .home-hero .sprite-stage[data-member="xingyi"] {
  --mo-name: mo-bounce; --mo-dur: 3.8s; --mo-y: 7px; --mo-r: 1.6deg;
  --mo-delay: -1.1s; --mo-origin: 50% 92%; --mo-safe: 28px;
}
.screen-home .home-hero .sprite-stage[data-member="yantuan"] {
  --mo-name: mo-pulse; --mo-dur: 3.2s; --mo-y: 4px; --mo-sc: 1.025;
  --mo-delay: -0.5s; --mo-safe: 16px;
}
.screen-home .home-hero .sprite-stage[data-member="chulong"] {
  --mo-name: mo-float; --mo-dur: 8.5s; --mo-y: 10px; --mo-x: 2px; --mo-r: 0.8deg;
  --mo-delay: -1.9s; --mo-safe: 26px;
}
.screen-home .home-hero .sprite-stage[data-member="lanyu"] {
  --mo-name: mo-sway; --mo-dur: 7s; --mo-x: 5px; --mo-r: 1.8deg;
  --mo-delay: -0.7s; --mo-origin: 50% 92%; --mo-safe: 20px;
}
.screen-home .home-hero .sprite-stage[data-member="fuguang"] {
  --mo-name: mo-float; --mo-dur: 10s; --mo-y: 8px; --mo-x: 4px; --mo-r: 2deg;
  --mo-delay: -2.3s; --mo-safe: 26px;
}
.screen-home .home-hero .sprite-stage[data-member="yeyin"] {
  --mo-name: mo-sway; --mo-dur: 4.6s; --mo-x: 2px; --mo-r: 1deg;
  --mo-delay: -1.4s; --mo-origin: 50% 92%; --mo-safe: 16px;
}
.screen-home .home-hero .sprite-stage[data-member="huali"] {
  --mo-name: mo-bounce; --mo-dur: 4.2s; --mo-y: 6px; --mo-r: 2deg;
  --mo-delay: -0.9s; --mo-origin: 50% 92%; --mo-safe: 24px;
}
.screen-home .home-hero .sprite-stage[data-member="xingdeng"] {
  --mo-name: mo-sway; --mo-dur: 6.2s; --mo-x: 3px; --mo-r: 1.5deg;
  --mo-delay: -1.7s; --mo-origin: 50% 92%; --mo-safe: 18px;
}
.screen-home .home-hero .sprite-stage[data-member="mianyue"] {
  --mo-name: mo-pulse; --mo-dur: 6.8s; --mo-y: 3px; --mo-sc: 1.015;
  --mo-delay: -2s; --mo-safe: 14px;
}
.screen-home .home-hero .sprite-stage[data-member="shitai"] {
  --mo-name: mo-still; --mo-dur: 11s; --mo-y: 1.5px;
  --mo-delay: -0.4s; --mo-safe: 12px;
}

/* 装饰星 */
.spark {
  position: absolute;
  color: var(--gold-2);              /* 白昼主题:白点在浅底上看不见,走金色 */
  opacity: 0.8;
  text-shadow: 0 0 12px rgba(255, 250, 235, 0.95);
  animation: spark 3.8s ease-in-out infinite;
  pointer-events: none;
}
.spark.s1 { top: 12%; left: 10%; font-size: 15px; animation-delay: 0.2s; }
.spark.s2 { top: 26%; right: 8%; font-size: 12px; animation-delay: 1.4s; }
.spark.s3 { bottom: 18%; left: 6%; font-size: 11px; animation-delay: 2.2s; }
.spark.s4 { bottom: 30%; right: 12%; font-size: 14px; animation-delay: 3s; }
@keyframes spark {
  0%, 100% { opacity: 0.25; transform: scale(0.85); }
  50% { opacity: 0.95; transform: scale(1.1); }
}

/* ---------- 首页:按设计图铺满一屏 ---------- */
/* 三段各自独立、互不牵连(立绘 / 快捷块 / 祝福卡),都写死 flex 不参与收缩:
   底部两排永远按自己的尺寸完整显示,空间盈亏全部由中间的立绘段吸收。 */
.screen-home {
  display: flex;
  flex-direction: column;
  /* 定高(不是 min-height):小屏上压力要真的落到立绘段身上,让它**等比缩小**着让位
     (见下面的 home-hero/sprite);磁贴与祝福卡永远按自己的尺寸完整显示。
     min-height 在这儿是错的:flex 容器会跟着内容一起长高,收缩就永远不生效。
     连地板都装不下时(极矮屏 / 系统字号放大),app.js 的 relaxScreen() 会把这屏
     松成"自然高度 + 整页滚动"——宁可滚,不能挤。
     高度从 --app-h(可视高单源)算:旧内核不认 dvh 的话,以前这里按大视口算会高几十像素。 */
  height: calc(var(--app-h) - var(--app-pad-t) - var(--app-pad-b));
}
.screen-home .topbar { padding: 18px 0 2px; flex: 0 0 auto; }

/* ① 立绘段:唯一可伸缩的一段,只在自身内部居中,不挤压下方。
   高度不够时立绘**等比缩小**(下面的 max-height 把 stage 撑满,img 跟着收),
   而不是被这一段裁掉 —— 裁掉的那截用户看不到,还会以为是图挂了。
   一路缩到 88px 的地板就不再缩;再装不下就该整屏滚(见 .screen-home 的 min-height)。 */
.screen-home .home-hero {
  flex: 1 1 auto;
  min-height: 88px;
  display: grid;
  justify-items: center;
  align-items: stretch;      /* stage 填满这一段,img 的 max-height:100% 才有基准可算 */
  overflow: hidden;
}
/* 摇晃期间(只有 .arun-body 的物理 ticker 在跑时,见 arun-motion.js 的 setShaking):
   立绘要能"撞出舞台"一小截 —— 390 档舞台只比立绘宽 25px,不放行就撞不出球感。
   回中之后 JS 立刻把这个类摘掉,别的精灵/别的页仍然裁在 hero 里。 */
.screen-home .home-hero.sprite-shaking { overflow: visible; }
.screen-home .home-hero .sprite-stage {
  width: 100%;
  min-height: 0;
  padding: 0;
  /* 行高必须是**确定值**:不写这条时隐式行按内容 sizing(就是立绘自己那么高),
     下面 .sprite 的 `max-height: calc(100% - …)` 的 100% 就解析成"立绘自己的高度",
     等于永远不生效(老注释里那 12px 预留其实一直没起作用)。
     写满 100% 之后百分比对着 stage 的高度解析,短屏才会真的把立绘缩小让位。 */
  grid-template-rows: 100%;
}
.screen-home .home-hero .sprite-stage .sprite {
  width: auto;
  max-width: 100%;
  height: min(38vh, 340px);      /* 自己的尺寸,不依赖兄弟节点(矮屏靠它让位给下面两块) */
  /* 减掉的是**这一只**动作的峰值:振幅 + 旋转包围盒增量(逐只写在 --mo-safe 里,
     见上面「每只精灵各自的待机动作」)。没写参数的成员走默认慢飘(9px),这里按 20px 留位。
     缩到最小时峰值也不会被 hero 的 overflow:hidden 裁掉。 */
  max-height: calc(100% - var(--mo-safe, 20px));
  object-fit: contain;
}

/* ② 快捷块:整段固定尺寸,四块按等分铺满 */
.screen-home .home-actions {
  flex: 0 0 auto;
  padding: 12px 0 16px;
}
/* ③ 祝福卡 —— **2026-10-26 删除**:用户说「我的祝福,在我的页面已经有了,首页的是冗余」。
   首页不再有这一块,`.screen-home .home-cards` 与 `.card-fur` 一族(下面那段)一起删了;
   首页现在只剩 立绘(flex:1) + 四宫格(.home-actions) 两段,"我的祝福"入口只留「我的」页。
   要恢复:照 art/src/_prev 的备份或版本历史把 DOM 与这两段样式一起取回。 */

/* 首页四宫格快捷块(设计图:浅玻璃方砖,上半水晶图标、下方白字标签)
   四块共用同一张浅玻璃底;悬停 / 聚焦 / 按下时淡入设计图的紫罗兰「选中」玻璃。
   选中态由开关 --qc-on(0 静息 → 1 选中)驱动:外观只在这里写一遍,
   三个状态(:hover / :focus-visible / :active)只负责扳开关。 */
.quick-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 9px;
  padding: 0;
  flex: 0 0 auto;
}
.quick-card {
  --qc-on: 0;
  position: relative;
  z-index: 0;                            /* 建层叠上下文,让 ::before 的 z-index:-1 只压在自己内部 */
  display: grid;
  place-items: center;
  align-content: center;
  gap: 5px;
  aspect-ratio: 1;
  min-width: 0;
  padding: 6px 2px 8px;
  border: 1.5px solid rgba(255, 255, 255, calc(0.72 + 0.2 * var(--qc-on)));
  border-radius: 21px;
  /* 白昼主题:磁贴是**白瓷** —— 上一版的灰紫渐变是给暗色页底调的,压在现在的浅底上
     比页底还沉、一眼看过去是四块灰方块(设计图的磁贴也是近白 #F2F2F7)。 */
  background: linear-gradient(180deg, #ffffff 0%, #f4f5fa 52%, #eaecf6 100%);
  cursor: pointer;
  /* 不能 overflow:hidden —— 会把选中层 ::after 的辉光裁掉(角上的圆角由 ::after 自己的
     border-radius: inherit 负责,不需要靠裁切);标签极窄时的溢出由 .qc-title 自己裁 */
  box-shadow:
    0 0 0 4px rgba(255, 255, 255, 0.16),          /* 外圈柔光,与设计图的一圈白边一致 */
    0 12px 24px -12px var(--shadow),
    inset 0 1px 0 rgba(255, 255, 255, 0.9);
  transform: translateY(calc(-2px * var(--qc-on)));            /* 选中时轻轻抬起 */
  transition: transform 0.14s ease, box-shadow 0.22s ease, border-color 0.2s ease;
}
/* 玻璃内胆:顶沿高光 + 一圈更亮的白边(素材里那层"玻璃反光") */
.quick-card::before {
  content: "";
  position: absolute;
  inset: 3.5px;
  z-index: -1;                           /* 压在内容之下:标签是子元素,拿到 z-index:1 */
  border-radius: 17.5px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.34) 0%, rgba(255, 255, 255, 0.08) 34%, rgba(255, 255, 255, 0) 60%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.16), rgba(196, 203, 228, 0.24));
  box-shadow:
    inset 0 0 0 1.5px rgba(255, 255, 255, 0.86),
    inset 0 -7px 12px -6px rgba(110, 124, 178, 0.26);
}
/* 选中态的紫罗兰玻璃:z-index 比内胆(-1)更低,所以垫在浅玻璃之上、内胆高光之下,靠 opacity 淡入 */
.quick-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  border-radius: inherit;
  background: linear-gradient(180deg, var(--theme-accent-lite) 0%, var(--theme-accent) 48%, var(--theme-accent-deep) 100%);
  box-shadow: 0 0 22px var(--theme-accent-glow);   /* 辉光跟选中层一起淡入,颜色取主题变量 */
  opacity: var(--qc-on);
  transition: opacity 0.24s ease;        /* 松手后慢慢褪回浅玻璃 */
  pointer-events: none;
}
.quick-card > * { position: relative; z-index: 1; }
.quick-card .qc-ico {
  /* 高度主导,但**每块给各自的值**(--ico-h):四张素材宽高比从 0.69 到 1.13,
     等高会让宽的那两张显得大一圈 —— 设计图里四块是**等面积**(21×30 / 25×26 / 25×24 / 27×24),
     所以照面积反推出各自高度,填在 app.js 的 quick 里。 */
  height: calc(var(--ico-h, 30px) * var(--ico-scale, 1));
  width: auto;
  max-width: 100%;
  filter: drop-shadow(0 3px 6px rgba(74, 86, 140, 0.35));
  pointer-events: none;
}
.quick-card .qc-title {
  font-size: 12.5px;                     /* 「和它说话」4 字,窄屏要挤得下不换行 */
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--navy-1);                  /* 白昼主题:浅玻璃上是深蓝,不是白字加暗描边 */
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;                      /* 极窄屏也不许标签顶出磁贴 */
  text-overflow: ellipsis;               /* 兜底:标签单行不换行,放不下的文案要看到「…」而不是被切半个词
                                            (英文标签按「一个词」写,见 lang/en.js 的说明;这条只管万一) */
}
/* 选中态的三个触发:鼠标悬停(只给真指针设备,免得触屏点完一直亮着)/ 键盘聚焦 / 手指按下。
   悬停用更短的淡入;按下不要淡入 —— 手指一碰就亮,反馈才跟得上 */
@media (hover: hover) and (pointer: fine) {
  .quick-card:hover { --qc-on: 1; }
  .quick-card:hover::after { transition-duration: 0.16s; }
}
.quick-card:focus-visible { --qc-on: 1; }
.quick-card:active { --qc-on: 1; transform: scale(0.965); }
.quick-card:active::after { transition-duration: 0s; }

/* ---------- 主按钮:紫罗兰玻璃胶囊(设计图的选中态语言) ---------- */
.btn-fur {
  position: relative;
  z-index: 0;                            /* 建层叠上下文,让 ::before 的 z-index:-1 只压在自己内部 */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 58px;
  padding: 14px 24px;
  border: 1.5px solid var(--theme-select);
  border-radius: var(--r-pill);
  background: transparent;               /* 玻璃面交给 ::before,边框内沿到玻璃留一圈空隙 */
  color: #fff;
  font-size: 17px;
  font-weight: 800;
  letter-spacing: 0.04em;
  cursor: pointer;
  box-shadow:
    0 0 20px var(--theme-accent-glow),
    0 14px 30px -16px var(--shadow);
  transition: transform 0.14s ease, box-shadow 0.2s ease, filter 0.2s ease;
  text-shadow: 0 1px 4px rgba(58, 66, 130, 0.55);
}
.btn-fur::before {
  content: "";
  position: absolute;
  inset: 4px;                            /* 空隙:玻璃面与描边之间透出背景 */
  z-index: -1;                           /* 压在文字之下(文字是裸文本节点,拿不到 z-index) */
  border-radius: calc(var(--r-pill) - 4px);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.4) 0%, rgba(255, 255, 255, 0.06) 38%, rgba(255, 255, 255, 0) 60%),
    linear-gradient(180deg, var(--theme-accent-lite), var(--theme-accent-deep));
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.7),
    inset 0 -8px 16px -10px rgba(70, 84, 160, 0.7);
}
.btn-fur > * { position: relative; z-index: 1; }
.btn-fur:active { transform: scale(0.984); filter: brightness(1.05); }
.btn-fur[disabled] { opacity: 0.55; cursor: default; }
.btn-fur .fur-ico { color: #fff; display: grid; place-items: center; }

/* 「我的祝福」玻璃胶囊卡(.card-fur 一族)—— **2026-10-26 删除**(首页那块冗余入口的配套样式)。
   它只服务首页那张卡,`.cf-icon/.cf-body/.cf-title/.cf-desc/.cf-mark` 全是它的子层,一起删。
   恢复入口时:DOM 在 app.js 的 home 模板里(注释标了位置),样式照 art/src/_prev 或版本历史取回。 */


/* 广场页「更多玩法」：三个外部玩法入口（答案之书/每日一句/明信片），浅玻璃磁贴三宫格。
   观感与四宫格快捷块(.quick-card)同源：白瓷底 + 外圈柔光，点按轻压。 */
.plz-ext {
  flex: 0 0 auto;
  padding: 2px 0 14px;
}
.plz-ext .ext-title {
  display: block;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--navy-2);
  margin: 0 4px 8px;
}
.ext-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 9px;
}
.ext-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-width: 0;
  padding: 14px 4px 12px;
  border: 1.5px solid rgba(255, 255, 255, 0.78);
  border-radius: 18px;
  background: linear-gradient(180deg, #ffffff 0%, #f4f5fa 52%, #eaecf6 100%);
  cursor: pointer;
  box-shadow:
    0 0 0 4px rgba(255, 255, 255, 0.16),
    0 12px 24px -12px var(--shadow),
    inset 0 1px 0 rgba(255, 255, 255, 0.9);
  transition: transform 0.14s ease;
}
.ext-card:active { transform: scale(0.965); }
.ext-card .ext-ico {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 11px;
  color: #fff;
  background: linear-gradient(180deg, var(--theme-accent-lite), var(--theme-accent));
  box-shadow: 0 6px 14px -6px var(--theme-accent-glow);
}
.ext-card .ext-name {
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--navy-1);
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 送礼人页的四个磁贴:白玻璃卡(描边 → 一圈空隙 → 斜向渐变的玻璃面)。
   几何按 2026-10-12 设计图量得:卡外框 71px 高、圆角 14(内圈同心 10)、
   左右间距 15 / 上下 12;四个图标高度不同(素材各自的原比例,见 giver.js)。 */
.tile-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 15px; }
/* 磁贴外面套一格:磁贴本身是 <button>,「删除」不能再塞进它里面(嵌套 button 是无效 HTML)——
   ✕ 定位在这层格子上,盖住磁贴右上角。 */
.tile-cell { position: relative; min-width: 0; }
.tile-cell > .tile-fur { height: 100%; }
.tile-del {
  position: absolute;
  top: 6px; right: 6px;
  width: 22px; height: 22px;
  border-radius: 50%;
  display: grid; place-items: center;
  padding: 0; cursor: pointer;
  border: 1px solid rgba(120, 130, 180, 0.32);
  background: rgba(255, 255, 255, 0.92);
  color: var(--navy-2);
  box-shadow: 0 4px 10px -6px var(--shadow);
  transition: transform 0.12s ease, background 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}
.tile-del:active { transform: scale(0.9); background: #fff; }
/* 有 ✕ 时,金色 ✓ 往左让一格,两个角标不叠在一起 */
.tile-cell:has(.tile-del) .tf-check { right: 32px; }
.tile-fur {
  position: relative;
  z-index: 0;                            /* 建层叠上下文,让 ::before 的 z-index:-1 只压在自己内部 */
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-width: 0;                          /* 允许格子在 1fr 里被压缩:否则子行的 nowrap 文本会把列撑宽 */
  min-height: 71px;
  padding: 6px 8px;
  border: 1.5px solid rgba(255, 255, 255, 0.82);
  border-radius: 14px;
  background: transparent;               /* 玻璃面交给 ::before */
  color: var(--navy-1);
  text-align: left;
  cursor: pointer;
  box-shadow:
    0 0 0 4px rgba(255, 255, 255, 0.16),
    0 14px 28px -14px var(--shadow);
  transition: transform 0.14s ease;
}
.tile-fur::before {
  content: "";
  position: absolute;
  inset: 4px;                            /* 空隙:玻璃面与描边之间透出背景 */
  z-index: -1;                           /* 与 .btn-fur 同一套层叠约定:玻璃面永远在内容之下 */
  border-radius: 10px;                   /* 同心圆角:外 14 - 内缩 4 */
  background:                            /* 斜向渐变(左上近白 → 右下淡紫),三点取自设计图磁贴 */
    linear-gradient(180deg, rgba(255, 255, 255, 0.44) 0%, rgba(255, 255, 255, 0.1) 38%, rgba(255, 255, 255, 0) 62%),
    linear-gradient(135deg, #f9f9fb 0%, #e3e4f0 52%, #ced0e3 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.85),
    inset 0 -8px 14px -10px rgba(120, 132, 180, 0.42);
}
.tile-fur > * { position: relative; z-index: 1; }
.tile-fur:active { transform: scale(0.982); }
/* 上传中 / 待重传:标题下面那行会说「上传中 45%」或「上传失败,点这里重传」。
   不靠变暗表达状态(文字已经说了),只是让"这一刻点它没用"和"点了会重传"看起来不一样 */
.tile-fur.busy { cursor: progress; }
.tile-fur.busy:active { transform: none; }
.tile-fur[disabled] { cursor: progress; }
.tile-fur[disabled] .tf-sub { color: var(--navy-1); }
/* 图标槽固定宽,让四个磁贴的标题左沿对齐;图标高矮按素材原比例(设计图里也不等高) */
.tile-fur .tf-ico { flex: 0 0 46px; display: grid; place-items: center; }
.tile-fur .tf-cluster { max-width: 100%; width: auto; height: auto; }
.tile-fur[data-tile="text"] .tf-cluster { height: 50px; }
.tile-fur[data-tile="audio"] .tf-cluster { height: 36px; }
.tile-fur[data-tile="photo"] .tf-cluster { height: 42px; }
.tile-fur[data-tile="video"] .tf-cluster { height: 48px; }
.tile-fur .tf-body { min-width: 0; flex: 1; display: block; }
.tile-fur .tf-title { display: block; font-size: 16px; font-weight: 800; color: var(--navy-1); line-height: 1.3; }
.tile-fur .tf-sub {
  display: block; font-size: 11.5px; color: var(--navy-2); margin-top: 2px; line-height: 1.5;
  /* 已填内容那行(文件名/时长)可能很长:截断成一行,四格高度才不会被撑开 */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tile-fur .tf-check {
  position: absolute;
  top: 8px; right: 8px;
  width: 20px; height: 20px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: linear-gradient(180deg, #f7dda4, #d9a94f);
  color: #4c3410;
  box-shadow: 0 0 12px rgba(240, 190, 100, 0.6);
}
.tile-fur.filled { border-color: var(--theme-select); box-shadow: 0 0 0 4px rgba(79, 97, 210, 0.16), 0 14px 28px -14px var(--shadow); }

/* ---------- 主按钮:白瓷胶囊(2026-10-12 用户:金色是上一版的底色,统一换成主题的白玻璃质感) ----------
   材质与「我的祝福」那张卡(.card-fur)同源:白渐层 + 内高光 + 柔光外圈,
   另加一道淡紫灰细描边 —— 这一页的主按钮有时落在白色弹层上(保存赠言/开始录音),
   纯白对纯白会糊在一起,有这道边才立得住。金色只留作点缀:图标金、文字深蓝。 */
.btn-gold {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 54px;
  padding: 13px 22px;
  border: 1px solid rgba(120, 130, 180, 0.28);
  border-radius: var(--r-pill);
  background: linear-gradient(135deg, #fdfdff 0%, #e9ebf7 52%, #d3d7ea 100%);
  color: var(--navy-1);
  font-size: 17px;
  font-weight: 800;
  letter-spacing: 0.06em;
  cursor: pointer;
  box-shadow:
    0 0 0 4px rgba(255, 255, 255, 0.22),
    0 14px 30px -16px var(--shadow),
    inset 0 1px 0 #fff;
  transition: transform 0.14s ease, filter 0.2s ease;
}
.btn-gold svg { color: var(--gold-ink); }
.btn-gold:active { transform: scale(0.984); filter: brightness(1.02); }
.btn-gold[disabled] { opacity: 0.5; cursor: default; }
/* 危险动作(解绑 / 清空 / 不保留 / 取消账号):材质照旧,只把字与描边换成危险色 ——
   比整块红按钮更贴这套浅玻璃;以前这几处是各写一遍内联 style,收口在这里 */
.btn-gold.danger { color: var(--danger); border-color: rgba(182, 66, 92, 0.34); }
.btn-gold.danger svg { color: var(--danger); }
.btn-line.danger { border-color: rgba(182, 66, 92, 0.42); color: var(--danger); }

/* 线性描边按钮(稍后查看 / 次要动作):玻璃胶囊,深浅两种底都读得清 */
.btn-line {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 50px;
  padding: 12px 20px;
  border: 1.5px solid var(--glass-line);
  border-radius: var(--r-pill);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.89), rgba(255, 255, 255, 0.695));
  color: var(--navy-1);
  font-size: 16px;
  font-weight: 700;
  cursor: pointer;
  backdrop-filter: blur(var(--g-reg-blur));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8), 0 10px 22px -14px var(--shadow-soft);
  transition: transform 0.14s ease;
}
.btn-line:active { transform: scale(0.984); }
.btn-line svg { color: var(--gold-ink); }

/* 安静的文字按钮 */
.btn-text {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 0;
  background: none;
  color: var(--navy-2);
  font-size: 13.5px;
  cursor: pointer;
  padding: 8px 4px;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-color: rgba(92, 96, 118, 0.32);
}
.btn-text.danger { color: var(--danger); text-decoration-color: rgba(182, 66, 92, 0.4); }

/* ---------- 卡片:浅玻璃面(卡内文字一律深蓝) ---------- */
.card-cream {
  border-radius: var(--r-lg);
  border: 1.5px solid var(--glass-line);
  background:
    radial-gradient(120% 70% at 50% -10%, rgba(255, 255, 255, 0.92), transparent 62%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(233, 237, 250, 0.86));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--navy-1);
  box-shadow:
    0 22px 46px -26px var(--shadow),
    inset 0 1px 0 rgba(255, 255, 255, 0.92);
  padding: 22px 20px;
  position: relative;
}
.card-cream + .card-cream { margin-top: 14px; }

.card-glass {
  border: 1.5px solid var(--glass-line);
  border-radius: var(--r);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.905), rgba(240, 243, 252, 0.755));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--navy-1);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.86), 0 16px 34px -22px var(--shadow-soft);
  padding: 18px;
}

/* 玻璃卡内的分节 */
.c-sec { padding: 14px 0; }
.c-sec + .c-sec { border-top: 1px solid rgba(120, 130, 180, 0.22); }
.c-sec:first-child { padding-top: 0; }
.c-label {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--gold-ink);
  letter-spacing: 0.14em;
  margin-bottom: 8px;
}
.c-label::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(180, 150, 96, 0.42), transparent);
}
.c-label svg { color: var(--gold-ink); }
/* 落在页面背景上的小节标题:按设计稿走白字(与顶栏同一套),
   浅薰衣草底上白字靠一层深色柔光托住,不然会糊进背景 */
.c-label.on-bg {
  color: var(--ink);
  font-size: 14.5px;
}
.c-label.on-bg svg {
  color: var(--ink-2);
}
.c-label.on-bg::after {
  background: linear-gradient(90deg, var(--line), transparent);
}
.c-text { font-size: 16.5px; line-height: 1.9; color: var(--navy-1); letter-spacing: 0.01em; }
.c-quote {
  font-family: var(--sans);
  font-size: 15.5px;
  line-height: 1.75;
  color: var(--navy-2);
}
.c-muted { color: var(--navy-3); font-size: 13px; }

/* 信息行(送礼人/类型/时间) */
.info-rows { display: grid; gap: 9px; }
.info-row { display: flex; gap: 12px; font-size: 15px; align-items: baseline; }
.info-row .k { flex: 0 0 5.2em; color: var(--gold-ink); font-weight: 600; }
.info-row .v { color: var(--navy-1); font-weight: 600; }

/* 照片 / 视频两个半宽卡 */
.duo-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.duo-grid.single { grid-template-columns: 1fr; }
.duo-card {
  border: 1px solid rgba(255, 255, 255, 0.8);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.815);
  padding: 10px 12px 12px;
  overflow: hidden;
  color: var(--navy-1);
}
.duo-card .c-label { margin-bottom: 8px; }
.duo-card img, .duo-card video {
  width: 100%;
  border-radius: 10px;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: rgba(220, 225, 242, 0.8);
}

/* 语音条:波形 + 播放 */
.voice-row {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid rgba(255, 255, 255, 0.8);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.815);
  padding: 12px 14px;
  color: var(--navy-1);
}
.wave {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 2px;
  height: 38px;
  overflow: hidden;
}
.wave i {
  flex: 1;
  min-width: 2px;
  border-radius: 2px;
  background: rgba(140, 150, 196, 0.72);
  transition: background 0.2s;
}
.wave i.on { background: linear-gradient(180deg, #e9c273, #c9963f); }
.play-btn {
  flex: 0 0 auto;
  width: 46px; height: 46px;
  border-radius: 50%;
  border: 1px solid rgba(120, 130, 180, 0.32);
  background: linear-gradient(180deg, #ffffff, #eef1fa);
  color: var(--gold-ink);
  display: grid; place-items: center;
  cursor: pointer;
  box-shadow: 0 6px 14px -8px var(--shadow-soft), inset 0 1px 0 #fff;
}
.play-btn:active { transform: scale(0.95); }
.voice-time { font-size: 12px; color: var(--navy-3); font-variant-numeric: tabular-nums; }

/* ---------- 表单 ---------- */
.field { display: block; margin-bottom: 12px; }
.field .lab { font-size: 13px; color: var(--ink-3); margin: 0 0 6px 4px; }
.input {
  width: 100%;
  min-height: 52px;
  padding: 13px 16px;
  border-radius: 16px;
  border: 1.5px solid var(--glass-line);
  background: rgba(255, 255, 255, 0.853);
  color: var(--navy-1);
  font-size: 16px;
  font-family: inherit;
  outline: none;
  backdrop-filter: blur(var(--g-reg-blur));
  transition: border-color 0.18s, box-shadow 0.18s, background 0.18s;
}
.input::placeholder { color: var(--navy-3); }
.input:focus {
  background: rgba(255, 255, 255, 0.78);
  border-color: var(--navy-1);            /* 白描边在浅底上看不见,用中性深色(不是紫) */
  box-shadow: 0 0 0 4px rgba(58, 58, 76, 0.12), 0 0 22px rgba(180, 190, 235, 0.5);
}
textarea.input { min-height: 120px; resize: vertical; line-height: 1.7; }
.input.mono { font-family: var(--mono); letter-spacing: 0.12em; text-transform: uppercase; }

/* 玻璃卡内的输入(名字沿用 input-cream) */
.input-cream {
  width: 100%;
  min-height: 50px;
  padding: 12px 15px;
  border-radius: 14px;
  border: 1.5px solid var(--glass-line);
  background: rgba(255, 255, 255, 0.92);
  color: var(--navy-1);
  font-size: 16px;
  font-family: inherit;
  outline: none;
}
.input-cream:focus { border-color: var(--navy-1); box-shadow: 0 0 0 4px rgba(58, 58, 76, 0.12), 0 0 20px rgba(180, 190, 235, 0.5); }
.input-cream::placeholder { color: var(--navy-3); }

/* 验证码一行 */
.code-row { display: flex; gap: 10px; }
.code-row .input { flex: 1; }
.code-btn {
  flex: 0 0 auto;
  min-height: 52px;
  padding: 0 16px;
  border-radius: 16px;
  border: 1.5px solid var(--glass-line);
  background: rgba(255, 255, 255, 0.815);
  color: var(--navy-1);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
  backdrop-filter: blur(var(--g-reg-blur));
}
.code-btn[disabled] { opacity: 0.5; }
.code-btn:active { transform: scale(0.97); }

/* 分段标签:玻璃槽 + 白色实心胶囊 */
.seg {
  display: flex;
  gap: 6px;
  padding: 5px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--glass-line-2);
  background: rgba(255, 255, 255, 0.78);
  backdrop-filter: blur(var(--g-reg-blur));
  margin: 16px 0 4px;
}
.seg button {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border: 0;
  border-radius: var(--r-pill);
  background: none;
  color: #4b4f6b;
  font-size: 14px;
  font-weight: 700;
  padding: 9px 6px;
  cursor: pointer;
  transition: all 0.18s;
}
.seg button.on {
  background: linear-gradient(180deg, #ffffff, #e8ecf8);
  color: var(--navy-1);
  box-shadow:
    0 0 16px var(--theme-select-glow),
    0 4px 14px -8px var(--shadow),
    inset 0 1px 0 #fff;
}

/* 选项芯片 */
.chips { display: flex; flex-wrap: wrap; gap: 9px; }
.chip {
  border-radius: var(--r-pill);
  border: 1.5px solid var(--glass-line);
  background: rgba(255, 255, 255, 0.815);
  color: var(--navy-2);
  font-size: 14px;
  font-weight: 600;
  padding: 9px 16px;
  cursor: pointer;
  transition: all 0.16s;
}
.chip.on {
  border-color: var(--theme-select);
  color: var(--navy-1);
  background: linear-gradient(180deg, #ffffff, #e4e9f8);
  box-shadow: 0 0 18px rgba(180, 190, 235, 0.6);
}
.chip-static {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--glass-line);
  background: rgba(255, 255, 255, 0.83);
  color: var(--navy-1);
  font-size: 12.5px;
  font-weight: 600;
  padding: 5px 12px;
  white-space: nowrap;
  backdrop-filter: blur(var(--g-thin-blur));
}
.chip-static svg { color: var(--gold-ink); }
/* 名字这类"长度不可控"的内容一律单行、超出打省略号:
   靠 .chip-ellip 自己收缩,而不是把邻居挤到下一行 —— 一旦换行卡片就长高,
   把整页顶出屏幕(实测 12 字名字让「我的」页从"刚好一屏"变成要多滚 40px)。
   .chip-name 标出"让位"的那个 chip,同一行里其余 chip 宽度固定不动。 */
.chip-static .chip-ellip { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* 切陪伴形象的行内确认(2026-10-19):不弹浮层,在「它叫「X」」这枚胶囊上亮一枚 ✓。
   徽章绝对定位 —— 不占宽度、不挤「还能聊 N 条」,也不会让页面长高。 */
.chip-static.chip-name {
  position: relative;
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.chip-static.swap-ok {
  border-color: rgba(214, 178, 112, 0.9);
  box-shadow: 0 0 0 3px rgba(231, 198, 138, 0.22), inset 0 1px 0 #fff;
}
.chip-static.swap-ok::after {
  content: '✓';
  position: absolute;
  top: -7px;
  right: -7px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: linear-gradient(180deg, #f4e2bb, #dfbd78);
  color: #5d4826;
  font-size: 11px;
  font-weight: 800;
  line-height: 1;
  pointer-events: none;
  box-shadow: 0 2px 7px rgba(120, 90, 40, 0.26);
  animation: swap-ok-pop 0.2s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
@keyframes swap-ok-pop {
  from { opacity: 0; transform: scale(0.6); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .chip-static.swap-ok::after { animation: none; }
}

/* 开关 */
.switch { display: flex; align-items: center; gap: 10px; cursor: pointer; }
.switch input { display: none; }
.switch .track {
  width: 46px; height: 27px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.5);
  border: 1px solid rgba(255, 255, 255, 0.7);
  position: relative;
  transition: background 0.2s;
  flex: 0 0 auto;
}
.switch .track::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 21px; height: 21px;
  border-radius: 50%;
  background: #fff;
  transition: transform 0.2s;
  box-shadow: 0 2px 6px rgba(52, 60, 102, 0.35);
}
.switch input:checked + .track { background: linear-gradient(135deg, var(--theme-accent), var(--theme-accent-deep)); }
.switch input:checked + .track::after { transform: translateX(19px); }

/* ---------- 列表卡 ---------- */
.list { display: grid; gap: 12px; margin-top: 14px; }
.gift-card {
  border: 1.5px solid var(--glass-line);
  border-radius: var(--r);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.92), rgba(240, 243, 252, 0.77));
  backdrop-filter: blur(var(--g-thick-blur));
  padding: 16px;
  cursor: pointer;
  color: var(--navy-1);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 14px 30px -20px var(--shadow-soft);
  transition: transform 0.14s ease, border-color 0.2s;
  text-align: left;
  width: 100%;
}
.gift-card:active { transform: scale(0.99); }
.gift-card .gc-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.gift-card .gc-who { font-weight: 700; font-size: 15.5px; overflow-wrap: anywhere; }
.gift-card .gc-quote {
  margin-top: 8px;
  color: var(--navy-2);
  font-size: 14px;
  line-height: 1.7;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;      /* 长串(链接等连续字符)不撑破卡片 */
}
.gift-card .gc-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
  font-size: 12px;
  color: var(--navy-3);
}
.gc-kinds { display: flex; gap: 6px; flex-wrap: wrap; }
/* 卡头右侧那一组:金对勾 + 状态角标(设计图 2026-10-14)。对勾只给「已领取 / 已珍藏」。 */
.gift-card .gc-status { display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto; }
.gc-tick { display: grid; place-items: center; color: var(--gold-3); }
.gc-tick svg { stroke-width: 2.6; }

/* ---------- 「我的祝福」页的两枚金色图标(设计图 2026-10-14) ----------
   钥匙 / 礼盒是用户给的 3D 渲染图抠出来的素材(见 tools/key-blessing-icons.py),
   尺寸按 2x 素材的一半;箭头是内联 SVG(细长一条,方图放不下),跟着 currentColor 取金色。
   顶部那两个页签(我的祝福 / 领取祝福)**不加图标** —— 用户口径。 */
/* 尺寸对着设计图量过:礼盒 ~33、钥匙 ~20(CSS)。两个都**不许把按钮撑高** ——
   第一版给了 44 / 26,主按钮从 54 被顶到 72,整行看着就"胖"了(用户报的)。 */
.ico-key { height: 22px; width: auto; flex: 0 0 auto; }
.ico-gift { height: 34px; width: auto; flex: 0 0 auto; }
/* 礼盒 34 比主按钮的内边距容得下的还高一点,把上下内边距收掉,按钮高度仍由 min-height 定 */
.claim-btn { padding-top: 7px; padding-bottom: 7px; }
/* 两处的金色箭头都贴右端:文字保持居中,箭头不会把文字挤偏 */
.claim-btn, .claim-jump { position: relative; }
.claim-btn .ico-arrow, .claim-jump .ico-arrow {
  position: absolute;
  right: 18px; top: 50%;
  transform: translateY(-50%);
}
.claim-jump .ico-arrow { right: 14px; }
.claim-btn { gap: 10px; }
.claim-jump { gap: 9px; }
/* 标签行里的钥匙比文字大,别把整行的基线顶歪(c-label 是 flex + center) */
.c-label .ico-key { margin: -4px 0; }

.status-chip {
  font-size: 12px;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(120, 130, 180, 0.3);
  color: var(--navy-2);
}
.status-chip.gold { border-color: rgba(201, 150, 63, 0.55); color: var(--gold-ink); background: rgba(240, 190, 100, 0.16); }
.status-chip.dim { opacity: 0.7; }
.status-chip.danger { border-color: rgba(182, 66, 92, 0.4); color: var(--danger); }

/* 空态:做成一块玻璃卡 —— 它可能出现在页面的任何位置(含最上面那段深色),
   卡内文字才能一律用深蓝 */
.empty {
  text-align: center;
  padding: 30px 20px;
  border-radius: var(--r-lg);
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.905), rgba(238, 242, 252, 0.74));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--navy-2);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.88), 0 16px 34px -24px var(--shadow-soft);
}
.empty img { width: 132px; margin: 0 auto 12px; opacity: 0.95; filter: drop-shadow(0 10px 26px rgba(88, 98, 150, 0.4)); }
/* 大号空态(我的祝福:设计图里礼盒更大、面板更透气) */
.empty.lg { padding: 38px 24px 32px; }
.empty.lg img { width: 176px; margin-bottom: 16px; filter: drop-shadow(0 14px 30px rgba(88, 98, 150, 0.42)); }
.empty.lg .e-title { font-size: 17px; letter-spacing: 0.01em; }
.empty.lg .e-sub { font-size: 13.5px; margin-top: 6px; }
.empty .e-title { color: var(--navy-1); font-size: 15px; font-weight: 700; }
.empty .e-sub { font-size: 13px; margin-top: 4px; color: var(--navy-2); }

/* ---------- 底部导航:近白磨砂带 + 靛蓝描边药丸 + 发光水晶 ---------- */
/* 悬浮圆角 dock(06 SEABREEZE 的轮廓 + 现有深靛玻璃):
   四周留白 + 28px 圆角 + 很淡的双内环,让底栏不再是一条贴边的硬横带。
   **不要加 overflow:hidden** —— 中心水晶的 104px 柔光与选中上抬都在 dock 外。
   2026-10-26 曾短暂改成「液态玻璃胶囊 + 四项」,当天按用户要求**整段回退** ——
   想再试就照 git 历史/备份取回那一版(要点:lg-nav 的 --lg-h 在窄屏会被组件自己改成 56px、
   host 上有顶栏用的 padding、自带 pill 会盖住按钮文字)。 */
.nav {
  position: fixed;
  left: var(--nav-inset-x);
  right: var(--nav-inset-x);
  bottom: calc(var(--nav-inset-b) + var(--sab));
  height: var(--nav-h);
  max-width: calc(var(--app-w) - 2 * var(--nav-inset-x));
  margin-inline: auto;
  display: flex;
  justify-content: center;
  border-radius: 28px;
  border: 1px solid var(--nav-line);
  background: linear-gradient(180deg, var(--nav-0), var(--nav-1));
  backdrop-filter: blur(26px) saturate(1.08);
  -webkit-backdrop-filter: blur(26px) saturate(1.08);
  box-shadow:
    inset 0 0 0 2px rgba(255, 255, 255, 0.10),
    inset 0 0 0 4px rgba(255, 255, 255, 0.05),
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    0 18px 38px -22px rgba(18, 24, 52, 0.75);
  z-index: 20;
}
/* 旧内核没有 backdrop-filter:给一层更实的底,别让 dock 变半透明 */
@supports not ((backdrop-filter: blur(var(--g-thin-blur))) or (-webkit-backdrop-filter: blur(var(--g-thin-blur)))) {
  /* 底栏底衬 0.97/0.99 → 0.80/0.86:折射(backdrop-filter)要能"看见"后面的内容,
   不透明底会把效果整个盖掉。降下来的对比度由 1px 内高光 + 噪点层补回来。 */
.nav {
  /* v2(2026-10-27):深色毛玻璃 —— 半透明更高(0.80/0.86 → 0.58/0.66),
     折射(glass-lens.js)再叠一层位移;不支持折射的浏览器由下面的 @supports 补普通磨砂。 */
  background: linear-gradient(180deg, rgba(46, 54, 102, 0.58), rgba(24, 29, 62, 0.66));
  border: 1px solid rgba(255, 255, 255, 0.14);
  backdrop-filter: blur(var(--g2-blur-card)) saturate(160%);
  -webkit-backdrop-filter: blur(var(--g2-blur-card)) saturate(160%);
}
}

/* ---------- 收起 / 展开底栏的胶囊(2026-10-26) ----------
   用户:「首页做一个胶囊,用来收起导航栏」。位置刻意选在**四宫格与底栏之间那条空隙里、居中**:
   · 居中是因为它是"底栏的开关",得跟底栏对齐;
   · 不能骑在底栏上沿 —— 那儿正对中间那颗水晶凸起(Crystal 中键),两条弧线叠在一起看不清谁是谁;
   · 于是把 `.home-actions` 的下内边距从 16 加到 34,腾出这条 22px 的带子给它(立绘是 flex:1,
     少这 18px 不影响一屏)。
   收起后胶囊落到贴底的位置(仍是同一个可点目标),箭头同时翻向;底栏靠 transform 滑出,
   **不动 --app-pad-b**:自定义属性没法过渡,内容区跟着变高会跳一下,而底栏只是浮层。
   只在首页存在(app.js 的 screenHome 才渲染它),换屏由 render 的 finally 复位。 */
.nav { transition: transform 0.34s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.28s ease; }
body.nav-hidden .nav {
  transform: translateY(calc(100% + var(--nav-inset-b) + var(--sab) + 14px));
  opacity: 0;
  pointer-events: none;
}
.nav-toggle {
  position: fixed;
  left: 50%;
  z-index: 21;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 22px;
  margin: 0;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.42);
  border-radius: 999px;
  background: rgba(28, 34, 62, 0.55);
  color: rgba(255, 255, 255, 0.9);
  cursor: pointer;
  transform: translateX(-50%);
  bottom: calc(var(--nav-inset-b) + var(--sab) + var(--nav-h) + 6px);
  backdrop-filter: blur(var(--g-reg-blur));
  -webkit-backdrop-filter: blur(var(--g-reg-blur));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.30),
    0 4px 14px -6px rgba(18, 24, 52, 0.55);
  transition: bottom 0.34s cubic-bezier(0.22, 1, 0.36, 1), background 0.3s, color 0.3s;
}
/* 常态:箭头朝下(点它收起);收起后翻上来(点它展开) */
.nav-toggle svg {
  width: 14px;
  height: 14px;
  display: block;
  overflow: visible;
  transform: rotate(0deg);
  transform-origin: 50% 50%;
  transition: transform 0.3s ease;
}
body.nav-hidden .nav-toggle { bottom: calc(var(--nav-inset-b) + var(--sab) + 10px); }
body.nav-hidden .nav-toggle svg { transform: rotate(180deg); }
/* 底栏收起后,原本留给它的那 96px 也该收回去 —— 不然四宫格下面拖着一条空带。
   覆盖 --app-pad-b 就行(这个类只可能出现在首页);**过渡写在 `.screen` 的 padding-bottom 上**,
   自定义属性本身不能过渡,能过渡的是用到它的那个属性。 */
body.nav-hidden { --app-pad-b: calc(var(--nav-inset-b) + var(--sab) + 44px); }
.screen { transition: padding-bottom 0.34s cubic-bezier(0.22, 1, 0.36, 1); }
.nav-toggle:focus-visible { outline: 2px solid rgba(232, 240, 255, 0.95); outline-offset: 2px; }
.nav-toggle:active { transform: translateX(-50%) scale(0.94); }
/* 首页那条空隙:从 16 加到 34,刚好容下上面的胶囊 */
.screen-home .home-actions {
  padding-bottom: 34px;
  transition: padding-bottom 0.34s cubic-bezier(0.22, 1, 0.36, 1);
}
/* 收起态:那条 34px 是为"胶囊夹在中间"留的;胶囊落到贴底后它就没用了,收回到 12px,
   四宫格跟着往下走,屏幕底下不再吊着一条空带。 */
body.nav-hidden .screen-home .home-actions { padding-bottom: 12px; }
@media (max-width: 360px) {
  :root { --nav-inset-x: 12px; }

  .nav { border-radius: 24px; }
}
.nav-inner {
  width: 100%;
  max-width: var(--app-w);
  display: flex;
}
.nav-item {
  flex: 1;
  white-space: nowrap;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding: 6px 2px 0;
  gap: 3px;
  border: 0;
  background: none;
  color: var(--nav-ink);
  font-size: 12.5px;
  cursor: pointer;
  position: relative;
  transition: color 0.18s;
  text-shadow: 0 1px 4px rgba(38, 46, 84, 0.4);
}
.nav-item svg { transition: filter 0.2s; }
/* 图标外框:未选中是透明的,选中时变成 06 的白色内嵌框(边框常驻,不会让布局跳动) */
.nav-ico {
  display: grid;
  place-items: center;
  width: 44px;
  height: 36px;
  flex: none;
  border: 2px solid transparent;
  border-radius: 14px;
  transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
}
.nav-item.on:not(.nav-crystal) .nav-ico {
  border-color: var(--nav-frame-line);
  background: rgba(255, 255, 255, 0.06);
  box-shadow:
    inset 0 0 0 1px var(--nav-frame-ring),
    inset 0 0 16px var(--nav-frame-glow),
    0 0 14px rgba(165, 190, 255, 0.18);
}
.nav-label {
  display: block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.nav-item.on { color: var(--theme-select); font-weight: 700; }
.nav-item.on svg { filter: drop-shadow(0 0 7px var(--theme-select-glow)); }
/* 深靛底上全局的深色焦点描边不够清楚,导航项用浅冷白 */
.nav-item:focus-visible {
  outline-color: rgba(232, 240, 255, 0.95);
  outline-offset: 2px;
}

/* 水晶中键:**一颗发光的水晶浮在导航带上**(做法照参考项目 D:\Crystal_Sprite 的 nav-summon)。
   那里没有可见的圆形按钮 —— 球体是 SVG 自己画出来的,所以这里不要描边、不要玻璃底、不要内阴影,
   只留一层径向柔光当可点提示;外圈扩散与呼吸交给独立伪元素,才不会把按钮撑大。
   动效(点上去 / 停在上面 / 切走落回)分两层做,免得打架:
   · `.nc-gem` 这一层管**位移**(静止 0、当前页抬起 -11px,带弹簧过渡)+ 三个一次性动画;
   · 里面的 svg 管**漂浮**(停在上面的那一页缓缓浮动);光晕再单独跟着抬高、变亮。 */
.nav-item.nav-crystal {
  padding-top: 0;
  gap: 1px;
  color: var(--nav-ink);
  --nc-lift: 0px;
  --nc-scale: 1;
}
.nav-item.nav-crystal > .nc-gem {
  display: block;
  margin-top: -2px;            /* 标签比其余四项低约 5px(与旧版圆徽一致) */
  transform: translateY(var(--nc-lift)) scale(var(--nc-scale));
  transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1);
}
.nav-item.nav-crystal > .nc-gem > svg {
  position: relative;            /* 必须在流内抬一层,否则被下面的柔光盖住 */
  display: block;
  /* 线稿靠 drop-shadow 出辉光,叠两层才有"浸在光里"的观感(白昼主题:光晕走紫,不走白) */
  filter: drop-shadow(0 0 5px rgba(228, 238, 255, 0.85)) drop-shadow(0 0 14px rgba(150, 170, 240, 0.5));
  transition: filter 0.22s;
}
/* 外圈柔光:伪元素做,才能在不撑大按钮的前提下扩散出去(带一点呼吸) */
.nav-item.nav-crystal::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 20px;                     /* 球心 */
  width: 104px;
  height: 104px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle, var(--theme-accent-glow), transparent 64%);
  filter: blur(9px);
  pointer-events: none;
  transition: top 0.42s cubic-bezier(0.22, 1, 0.36, 1);
  animation: nc-breathe 4.6s ease-in-out infinite;
}
/* 悬停 / 键盘聚焦:轻轻抬一点、光晕亮起来(触屏上不生效,无所谓) */
@media (hover: hover) {
  .nav-item.nav-crystal:not(.on):hover { --nc-lift: -5px; }
  .nav-item.nav-crystal:not(.on):hover > .nc-gem > svg {
    filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.95)) drop-shadow(0 0 16px var(--theme-accent-glow));
  }
}
.nav-item.nav-crystal:not(.on):focus-visible { --nc-lift: -5px; }
/* 当前页:水晶升到导航带上方停住(高度 44px + 抬 11px:球体上半浮在带外,
   带外是浅底,所以多加一层向下的暗投影,让玻璃球在浅底上也立得住),光晕跟着上移 */
.nav-item.nav-crystal.on { --nc-lift: -11px; --nc-scale: 1.06; color: var(--theme-select); }
.nav-item.nav-crystal.on::before { top: 9px; }
.nav-item.nav-crystal.on > .nc-gem > svg {
  filter:
    drop-shadow(0 0 6px var(--theme-select-glow))
    drop-shadow(0 0 18px var(--theme-accent-glow))
    drop-shadow(0 4px 10px rgba(38, 46, 84, 0.34));
  animation: nc-float 3.8s ease-in-out infinite;    /* 停在上面的那一页:缓缓浮动 */
}
/* 切页时放一次性动画:类由 app.js 的 navCrystalFx() 按"上一页是哪一页"挂,
   因为导航栏每次渲染都会重建,CSS 的 transition 跨不过去 */
.nav-item.nav-crystal.rise > .nc-gem { animation: nc-rise 0.54s cubic-bezier(0.22, 1, 0.36, 1); }
.nav-item.nav-crystal.settle > .nc-gem { animation: nc-settle 0.44s cubic-bezier(0.3, 0.9, 0.4, 1); }
.nav-item.nav-crystal.pop > .nc-gem { animation: nc-pop 0.5s cubic-bezier(0.22, 1, 0.36, 1); }
/* 按下去:立刻缩一下给手感(松手回到 0.42s 的弹簧) */
.nav-item.nav-crystal:active { --nc-scale: 0.97; }
.nav-item.nav-crystal:active > .nc-gem { transition-duration: 0.12s; }
/* 0 → -11:先冲过头一点再落到位,"点一下水晶弹上去"的手感 */
@keyframes nc-rise {
  0% { transform: translateY(0) scale(1); }
  58% { transform: translateY(-16px) scale(1.085); }
  100% { transform: translateY(-11px) scale(1.06); }
}
/* -11 → 0:落地时先沉一点点,别有"掉下来"的硬感 */
@keyframes nc-settle {
  0% { transform: translateY(-11px) scale(1.06); }
  58% { transform: translateY(2px) scale(0.99); }
  100% { transform: translateY(0) scale(1); }
}
/* 已经在召唤页又点一下:原地往上弹一次 */
@keyframes nc-pop {
  0%, 100% { transform: translateY(-11px) scale(1.06); }
  46% { transform: translateY(-17px) scale(1.075); }
}
@keyframes nc-float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
@keyframes nc-breathe {
  0%, 100% { opacity: 0.72; transform: translate(-50%, -50%) scale(0.96); }
  50% { opacity: 1; transform: translate(-50%, -50%) scale(1.06); }
}

/* ---------- 对话 ---------- */
/* 对话气泡(交流页面板内;输入栏样式见「水晶 · 交流页」一节) */
.chat-panel .bubble {
  max-width: 86%;
  padding: 10px 14px;
  border-radius: 16px;
  font-size: 14.5px;
  line-height: 1.7;
  white-space: pre-wrap;
  word-break: break-word;
  animation: page-in 0.22s ease;
}
.chat-panel .bubble.sprite {
  align-self: flex-start;
  background: linear-gradient(180deg, #ffffff, #eef1fa);
  color: var(--navy-1);
  border: 1px solid rgba(255, 255, 255, 0.9);
  border-bottom-left-radius: 7px;
  box-shadow: 0 8px 20px -14px var(--shadow);
}
.chat-panel .bubble.user {
  align-self: flex-end;
  background: linear-gradient(180deg, #b3bfe8, #8f9ed8);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.6);
  border-bottom-right-radius: 7px;
  text-shadow: 0 1px 4px rgba(50, 58, 116, 0.4);
}
.chat-panel .bubble.typing {
  background: none;
  border: 0;
  box-shadow: none;
  color: var(--navy-3);
  letter-spacing: 0.3em;
}
.send-btn {
  flex: 0 0 auto;
  width: 52px; height: 52px;
  border-radius: 50%;
  border: 1px solid rgba(120, 130, 180, 0.32);
  background: linear-gradient(180deg, #ffffff, #eef1fa);
  color: var(--gold-ink);
  display: grid; place-items: center;
  cursor: pointer;
  box-shadow: 0 6px 16px -8px var(--shadow-soft), inset 0 1px 0 #fff;
}
.send-btn:active { transform: scale(0.95); }
.send-btn[disabled] { opacity: 0.5; }

/* ---------- 我的:头像上传 ---------- */
.me-avatar {
  position: relative;
  width: 96px;
  height: 96px;
  margin: 0 auto 12px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.9);
  background: rgba(255, 255, 255, 0.45);
  overflow: visible;
  cursor: pointer;
  display: block;
  box-shadow: 0 0 26px rgba(255, 255, 255, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.8);
}
.me-avatar .av-img {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  overflow: hidden;
  display: block;
}
.me-avatar .av-img img { width: 100%; height: 100%; object-fit: cover; }
.me-avatar .av-img svg { margin: 0 auto; }
/* 换头像的相机徽章:尺寸必须**跟着头像直径走**。
   原来是写死的 30px —— 96px 的大头像没问题,可「我的」页紧凑卡里头像只有 46px(矮屏 40px),
   30px 的徽章直接盖住大半个脸(2026-10-12 用户在手机上看到的就是这个)。
   改成百分比:96→29.8、46→14.3、40→12.4,和设计稿同一个比例。
   图标是内联 SVG(宽高写死在属性里),用 CSS 覆盖即可 —— viewBox 是 0 0 24 24,线宽会一起缩。 */
.me-avatar .av-badge {
  position: absolute;
  right: -3%;
  bottom: -3%;
  width: 31%;
  height: 31%;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: linear-gradient(180deg, #fae3ae, #d9a94f);
  border: 1px solid rgba(255, 250, 236, 0.95);
  color: #5d4116;
  box-shadow: 0 0 14px rgba(240, 190, 100, 0.6);
}
.me-avatar .av-badge svg { width: 47%; height: 47%; }
/* 「我的」页的头像只有 46px(矮屏 40px),严格按 31% 算下来相机图标只剩 4px 一坨。
   小头上放一点(徽章 36%、图标 62% → 40px 头 = 13px 徽章 + 8px 图标),
   仍然缩在右下角、不压脸,但看得出是相机。 */
.me-card .me-avatar .av-badge { width: 36%; height: 36%; }
.me-card .me-avatar .av-badge svg { width: 62%; height: 62%; }
.me-avatar:active { transform: scale(0.97); }

/* ---------- 水晶 · 交流页(按设计图) ---------- */
.screen-guard-chat {
  display: flex;
  flex-direction: column;
  /* 可视高单源(--app-h):旧内核没有 dvh 时,原来那条 100vh 会把输入栏顶到屏幕外 */
  min-height: calc(var(--app-h) - var(--app-pad-t) - var(--app-pad-b));
  /* 底导是固定定位:抵消 #app 预留的下内边距,保证输入栏不被遮住、整屏不滚动 */
  margin-bottom: calc(-1 * var(--app-pad-b));
  padding-bottom: 10px;
}
.screen-guard-chat .topbar { padding: 12px 0 0; }
.screen-guard-chat .topbar h1 { font-size: 28px; letter-spacing: 0.02em; }

.chat-stage {
  position: relative;
  display: grid;
  place-items: center;
  padding: 2px 0 0;
}
.chat-sprite {
  width: auto;
  height: min(25vh, 214px);
  filter: drop-shadow(0 0 34px rgba(255, 250, 240, 0.55)) drop-shadow(0 18px 40px rgba(88, 98, 150, 0.3));
  animation: breathe 6.5s ease-in-out infinite;
  transition: opacity 0.12s ease;
}
.speak-bubble {
  position: relative;
  margin-top: -6px;
  /* 2026-10-15 收小一档:同样是「最新一条精灵消息」,这段话在下面的对话框里
     **同一个字一个字地同步长出来** —— 气泡少占的每一像素都直接变成面板的可读高度。
     原先 14/20 的内边距 + 16.5px 字号在矮屏上要吃掉 110px,面板被压到地板(96px)。 */
  padding: 10px 16px;
  border-radius: 20px;
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(240, 243, 252, 0.83));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--navy-1);
  font-size: 15.5px;
  line-height: 1.5;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    0 16px 34px -22px var(--shadow);
  animation: page-in 0.26s ease;
}
/* 气泡里的文字:封顶 2 行。
   气泡是「最新一条精灵消息」的镜像,整段话在下面的对话面板里**同时在逐字长出来**,
   所以这里截断一点不丢内容(这是它和对话框里那条的本质区别)。
   精灵回复最长 240 字,不封顶时气泡能长到 136px+,把整列顶出视口 ——
   输入栏就滑到固定底导下面去了(矮屏尤其明显)。
   封顶后「立绘 + 气泡」的高度与对话长度无关,整列高度才是可预算的。
   裁剪必须落在这一层而不是 .speak-bubble:小尾巴是气泡的 ::before、位于 top:-7px,
   给气泡本体加 overflow 会把尾巴的尖儿一起裁掉。 */
.speak-bubble .sb-text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}
.speak-bubble::before {
  content: "";
  position: absolute;
  top: -7px;
  left: 30px;
  width: 13px;
  height: 13px;
  transform: rotate(45deg);
  border-left: 1.5px solid var(--glass-line);
  border-top: 1.5px solid var(--glass-line);
  background: rgba(255, 255, 255, 0.72);
  border-radius: 3px;
}
.speak-bubble.empty { display: none; }

.chat-panel {
  /* basis:0 —— 面板只吃"剩下的空间"。用 auto 的话它的理想高度等于全部对话内容,
     收缩权重会压过立绘段,把立绘先压成 0(实测整段变 0px、精灵看不见了)。 */
  flex: 1 1 0;
  /* 这里刻意没有 max-height:面板高度必须是「上面还剩多少空间」,
     而 vh 百分比是个绝对量,不知道还剩多少 —— 之前是 max-height:46vh,
     对话一多就顶到 388px,整列 893px 塞进 844px 的视口,输入栏被推出屏幕。
     min-height 是它的**地板**(96px ≈ 三行对话):装得下时它吃满余量,
     真的挤到地板以下就不再缩 —— 屏幕会整体长高、页面能滚,
     输入栏永远在它下面(对话区被压成一条缝"看不见",和输入栏被挤走是同一类 bug)。 */
  min-height: 96px;
  overflow-y: auto;
  margin-top: 14px;
  padding: 14px 13px;
  border-radius: 22px;
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.56), rgba(236, 240, 251, 0.4));
  backdrop-filter: blur(var(--g-thick-blur));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.86), 0 18px 40px -26px var(--shadow);
  display: flex;
  flex-direction: column;
  gap: 10px;
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 132, 180, 0.5) transparent;
}
.chat-panel::-webkit-scrollbar { width: 6px; }
.chat-panel::-webkit-scrollbar-thumb { background: rgba(120, 132, 180, 0.45); border-radius: 3px; }
.chat-hint {
  color: var(--navy-3);
  font-size: 13px;
  text-align: center;
  margin: auto;
}
.chat-day {
  align-self: center;
  padding: 2px 10px;
  font-size: 11.5px;
  letter-spacing: 0.06em;
  color: var(--navy-3);
  opacity: 0.85;
}

.emotion-card {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  margin-top: 12px;
  padding: 10px 15px;
  border-radius: 20px;
  border: 1.5px solid var(--glass-line);
  background:
    radial-gradient(120% 100% at 0% 0%, rgba(255, 226, 170, 0.28), transparent 62%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(240, 243, 252, 0.785));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--navy-1);
  text-align: left;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.88), 0 14px 30px -20px var(--shadow-soft);
}
.emotion-card:active { transform: scale(0.99); }
.emotion-card .ec-thumb {
  flex: 0 0 auto;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  overflow: hidden;
  border: 1.5px solid rgba(255, 255, 255, 0.9);
  box-shadow: 0 0 16px rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.4);
}
.emotion-card .ec-thumb img { width: 100%; height: 100%; object-fit: cover; }
.emotion-card .ec-body { flex: 1; min-width: 0; display: block; }
.emotion-card .ec-title { display: block; font-size: 16.5px; font-weight: 700; }
.emotion-card .ec-sub { display: block; font-size: 12.5px; color: var(--navy-2); margin-top: 2px; }
.emotion-card .ec-go { flex: 0 0 auto; color: var(--navy-3); font-size: 20px; line-height: 1; }

.composer {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 0 4px;
}
/* 今日聊天额度那颗 chip:复用 .chip-static 的玻璃语汇(不新造面),内容由 JS 换。
   宽度靠文案控制 —— 它和名字挤在同一行,名字那枚会让位,所以:
     · 文案尽量短(四种状态都 ≤ 8 个汉字);
     · 内边距比通用 chip 收 2px —— 省下的宽度直接让给名字(实测:10 字名字在
       「还能聊 N 条」这一态下正好整整齐齐放下,不再差 2px 就被省略号切掉);
     · flex:0 0 auto —— 它不缩,让位的是名字。 */
.chat-quota { flex: 0 0 auto; padding: 5px 10px; }
/* 额度胶囊现在是按钮(点开看时区/额度说明):只清 button 的默认字体/外观,
   **border 与 background 留给 .chip-static** —— 清掉的话它就没有玻璃底和描边,
   和旁边「它叫「X」」不是一套了。
   **不要靠 title/hover** —— 这是手机 H5,悬停基本触发不了。 */
button.chat-quota {
  /* 只继承字体族;字号继续由 .chip-static 的 12.5px 决定
     (写 font: inherit 会把字号覆盖成父级的 15px,胶囊就比名字那枚大一圈)。
     **不要写 color: inherit** —— 那会盖掉 .chip-static 的 --navy-1,文字变成浅色。 */
  font-family: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
button.chat-quota:active { transform: scale(0.97); }
.composer .input { flex: 1; min-height: 52px; border-radius: var(--r-pill); padding: 13px 18px; }
/* 额度到顶时输入栏与发送键一起禁用(状态由 JS 加 disabled) */
.composer .input[disabled] { opacity: 0.6; }
.send-pill {
  flex: 0 0 auto;
  height: 52px;
  padding: 0 24px;
  border-radius: var(--r-pill);
  border: 1.5px solid rgba(120, 130, 180, 0.3);
  background: linear-gradient(180deg, #ffffff 0%, #f2f4fc 58%, #e5e9f6 100%);
  color: var(--navy-1);
  font-size: 16px;
  font-weight: 800;
  letter-spacing: 0.08em;
  cursor: pointer;
  box-shadow:
    0 0 0 4px rgba(255, 255, 255, 0.2),
    0 12px 24px -14px var(--shadow),
    inset 0 1px 0 #fff;
}
.send-pill:active { transform: scale(0.97); }
.send-pill[disabled] { opacity: 0.5; }

/* 身份卡 */
.id-card {
  border-radius: var(--r-lg);
  border: 1.5px solid var(--glass-line);
  background:
    radial-gradient(120% 90% at 50% -20%, rgba(255, 234, 190, 0.42), transparent 60%),
    linear-gradient(170deg, rgba(255, 255, 255, 0.92), rgba(238, 242, 252, 0.785));
  backdrop-filter: blur(var(--g-thick-blur));
  padding: 22px 20px;
  text-align: center;
  color: var(--navy-1);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 18px 40px -26px var(--shadow);
}
.id-card .id-avatar {
  width: 92px; height: 92px;
  margin: 0 auto 12px;
  border-radius: 50%;
  overflow: hidden;
  border: 2px solid rgba(255, 255, 255, 0.9);
  box-shadow: 0 0 30px rgba(255, 255, 255, 0.45);
  background: rgba(255, 255, 255, 0.4);
}
.id-card .id-name { font-size: 22px; font-weight: 800; letter-spacing: 0.04em; }
.id-card .id-no {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--gold-ink);
  letter-spacing: 0.16em;
  margin-top: 6px;
}
.id-card .id-meta { font-size: 13px; color: var(--navy-3); margin-top: 8px; }
.id-card .id-chips { display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; margin-top: 12px; }

/* ---------- 码展示 ---------- */
/* 礼物码展示:与全站一致的浅玻璃面(原来是一块米黄底 + 金色虚线,和现在的主题不搭) */
.code-display {
  text-align: center;
  padding: 18px 16px;
  border-radius: var(--r);
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.92), rgba(236, 240, 252, 0.815));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 16px 32px -24px var(--shadow-soft);
  color: var(--navy-1);
}
.code-display .cd-label { font-size: 12.5px; color: var(--navy-2); letter-spacing: 0.2em; }
.code-display .cd-value {
  font-family: var(--mono);
  font-size: 30px;
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--gold-ink);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
  margin: 8px 0 12px;
}

/* 提示条 */
.notice-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(238, 242, 252, 0.815));
  color: var(--navy-2);
  font-size: 13.5px;
  padding: 12px 18px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 12px 26px -18px var(--shadow-soft);
  text-align: center;
}

/* 页脚小字(页级:在浅色中段,用深蓝)
   例外:直接用在小卡片里时也跟着变深,所以统一给深蓝 + 极淡白描边 */
.foot-note {
  text-align: center;
  color: var(--navy-2);
  font-size: 12.5px;
  margin-top: 18px;
  text-shadow: 0 1px 6px rgba(255, 255, 255, 0.5);
}

/* ---------- 它记得的事(记忆列表) ----------
   **这一段的高度不随记忆条数变**(2026-10-15 用户:「记得的越多,就会把东西挤到下面去」)。
   做法与同一张抽屉里的「陪伴形象」一致:默认露出前 3 条,其余靠标题行右侧那颗
   「展开全部 N 条」放出来 —— 记忆最多能攒到 60 条,而抽屉只有 86vh 高。
   以前是"列表自己滚(234px 封顶)":两个滚动区叠在一起(抽屉滚一截、列表再滚一截),
   而实测 1280×720 / 12 条记忆时,动作区(保存 / 让它忘掉我们聊过的 / 清空对话记录 / 解除绑定)
   被顶到抽屉可视区**下方 381px** —— 完全看不见,页面上也没有任何提示说下面还有东西。
   现在默认只占 3 条(≈170px),想看全就展开,展开后由**抽屉**这一个滚动区负责滚。 */
.mem-list {
  display: flex;
  flex-direction: column;
  gap: 7px;
}
/* 藏掉第 4 条起(**不是不渲染**):删掉一条,后面那条会自动补上来,不必重画整张抽屉 */
.mem-list:not(.open) .mem-item:nth-child(n + 4) { display: none; }
.mem-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, 0.853);
  border: 1px solid var(--line-2);
  font-size: 13.5px;
  color: var(--navy-1);
}
/* 单行省略:记忆长短不一,绝不许换行把抽屉撑高(同 .chip-ellip 的口径) */
.mem-item .mem-text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mem-item .mem-x {
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: var(--navy-3);
  font-size: 12.5px;
  padding: 6px 4px;
  min-height: 32px;
  cursor: pointer;
}
.mem-empty {
  font-size: 13px;
  color: var(--navy-3);
  padding: 4px 0;
}

/* ---------- 弹层 / 抽屉 ---------- */
.sheet-mask {
  position: fixed;
  inset: 0;
  /* v2:遮罩只压一点暗(0.42 → 0.18),靠 sheet 自己的强模糊撑住层次 */
  background: rgba(30, 36, 60, 0.18);
  backdrop-filter: blur(var(--g-thin-blur));
  z-index: 30;
  animation: page-in 0.18s ease;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  /* 底部弹层让出键盘:那一截的高度由 public/js/keyboard.js 写进 --kb。
     **为什么不是改 mask 的 inset**:iOS 的键盘不动布局视口,这层 fixed 的底仍停在屏幕底 ——
     把整层往上抬,等于连遮罩一起抬(下方会漏出页面);只把**内容**顶上去才是对的。
     没有 keyboard.js 的旧内核/桌面上 --kb 是 0,这条等于不存在。 */
  padding-bottom: var(--kb, 0px);
}
.sheet {
  width: 100%;
  max-width: var(--app-w);
  border-radius: var(--g2-radius) var(--g2-radius) 0 0;
  border: 1px solid var(--g2-edge);
  border-bottom: 0;
  /* v2 毛玻璃:半透明白衬 + 强模糊 + 高饱和,背后内容糊成色块而不是消失 */
  backdrop-filter: blur(var(--g2-blur)) saturate(180%);
  -webkit-backdrop-filter: blur(var(--g2-blur)) saturate(180%);
  background: var(--g2-tint);
  color: var(--navy-1);
  padding: 26px 20px calc(24px + var(--sab));
  box-shadow: var(--g2-shadow);
  animation: sheet-in 0.24s cubic-bezier(0.16, 1, 0.3, 1);
  /* max-height 两层取小:
       · 86vh     —— 设计上的"别占满整屏"(注意 iOS 的 vh 是**大**视口,所以它单独用不可靠);
       · 100%     —— mask 的内容盒(已经减掉了 --kb 那条 padding)= 键盘之上真正剩下的高度,
                     这一条才是"输入框一定在键盘上方"的保证。 */
  max-height: min(86vh, 100%);
  overflow: auto;
}
@keyframes sheet-in {
  from { transform: translateY(26px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
.sheet h3 { margin: 0 0 4px; font-size: 18px; }
/* 弹层右上角的关闭键 + 全屏看照片/视频(详情页与送礼人页共用,收口在这里) */
.sheet { position: relative; }
.sheet .sheet-x {
  position: absolute; top: 14px; right: 14px; z-index: 2;
  width: 32px; height: 32px; border-radius: 50%; padding: 0;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid rgba(120, 130, 180, 0.3);
  background: rgba(255, 255, 255, 0.905); color: var(--navy-1);
  transition: transform 0.12s ease, background 0.15s ease;
}
.sheet .sheet-x:active { transform: scale(0.92); background: #fff; }
.gp-video-full { width: 100%; max-height: 62vh; border-radius: 16px; background: #000; margin: 4px 0 2px; }
.gp-photo-full {
  width: 100%; max-height: 62vh; border-radius: 16px;
  object-fit: contain; background: rgba(140, 150, 190, 0.28); margin: 4px 0 2px;
  display: block;
}
/* `overflow-wrap: anywhere`:弹窗里可能塞进一整条手串链接(长 URL 是一段没有断点的 token),
   不断行就会顶出玻璃卡右缘(后台那条「空手串」确认就是这么被用户发现的)。
   对正常句子没有副作用 —— 只在实在放不下时才断。 */
.sheet .s-sub { color: var(--navy-3); font-size: 13px; margin-bottom: 14px; overflow-wrap: anywhere; }
.sheet h3 { overflow-wrap: anywhere; }
.sheet-actions { display: grid; gap: 10px; margin-top: 16px; }

/* ---------- 水晶设置:内容可滚,底部动作常驻 ----------
   旧版把 4 个按钮 + 一句长说明放在内容流最后,「记得的事」和陪伴形象一多,
   保存键就被推到抽屉可视区下面(实测 390×844:内容 1038 / 可视 725,要滚 313)。
   现在拆成:
     · .settings-scroll:标题/名字/陪伴形象/语气/「它记得的事」入口 —— 这一层滚;
     · .settings-foot:保存 + 三个次要动作 —— 始终贴在抽屉底,不会滚丢。
   记忆列表本身也搬到单独一层了(见 openGuardMemories),所以主层高度不再随记忆条数长。 */
.sheet.sheet-settings {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}
.sheet-settings .settings-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 14px 20px 8px;
}
/* 这一段的所有尺寸都只为"手机上装得下"服务,作用域锁在 .sheet-settings 里:
   别的表单/弹窗的输入框与语气 chip 一点都不改。 */
.sheet-settings .s-sub {
  font-size: 12.5px;
  line-height: 1.4;
  margin-bottom: 10px;
}
.sheet-settings .field { margin-bottom: 10px; }
.sheet-settings .input { min-height: 46px; padding: 10px 14px; }
.sheet-settings .chips { gap: 7px; }
.sheet-settings .chip { padding: 7px 13px; font-size: 13.5px; }
.sheet-settings .comp-grid { gap: 7px; }
.sheet-settings .comp-item { padding: 4px 3px; }
.sheet-settings .comp-item img { height: 46px; }
.sheet-settings .comp-item .comp-name { font-size: 10.5px; margin-top: 2px; }
.sheet-settings .settings-foot {
  position: relative;
  flex: 0 0 auto;
  padding: 10px 20px calc(12px + var(--sab));
  border-top: 1px solid var(--glass-line);
  background: linear-gradient(180deg, rgba(246, 248, 254, 0.96), #e9edf8);
  box-shadow: 0 -12px 26px -20px rgba(44, 52, 92, 0.55);
}
/* 底部动作栏上沿的一道淡出:内容被裁在栏上方时,看得出来"下面还有" */
.sheet-settings .settings-foot::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -16px;
  height: 16px;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(233, 237, 248, 0), rgba(233, 237, 248, 0.94));
}
.sheet-settings .settings-danger {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0 14px;
  margin-top: 2px;
}
.sheet-settings .me-row { margin-bottom: 12px; }
.sheet-settings .me-row .mem-count { color: var(--navy-3); font-weight: 600; }
/* 「下面还有」的小箭头:只在内容确实高于可视区时出现(JS 加 .on),滚到底就消失。
   手机上滚动条通常不显示,这颗箭头就是唯一的滚动提示。 */
.sheet-settings .settings-more {
  position: absolute;
  top: -20px;
  left: 50%;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--glass-line);
  background: rgba(255, 255, 255, 0.92);
  color: var(--navy-2);
  box-shadow: 0 6px 14px -10px rgba(44, 52, 92, 0.7);
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, 0) rotate(90deg);
  transition: opacity 0.18s ease;
  animation: settings-nudge 1.8s ease-in-out infinite;
}
.sheet-settings .settings-more.on { opacity: 1; }
@keyframes settings-nudge {
  0%, 100% { transform: translate(-50%, 0) rotate(90deg); }
  50% { transform: translate(-50%, 3px) rotate(90deg); }
}
@media (prefers-reduced-motion: reduce) {
  .sheet-settings .settings-more { animation: none; }
}
/* 记忆列表的「展开全部 N 条」在单独一层里没有标题行,靠右放 */
.mem-more { display: flex; justify-content: flex-end; margin-top: 8px; }

/* ---------- 键盘弹起(<html>.kb-open;值由 public/js/keyboard.js 写) ----------
   2026-10-16 用户报的 iPhone 问题:输入框与弹层被键盘挡住。根因不是"弹层在底部",
   是 iOS 的键盘**不动布局视口** —— fixed 的底部元素仍停在屏幕底,而遮挡区在它上面。
   做法统一成一句"谁贴底谁抬起来",三件事:
     ① 关掉给 home 条留的安全区(那 34px 是躲底部横条的,键盘在时白占);
     ② 底部导航抬到键盘之上(和原生应用把 tab bar 顶上去一样);
     ③ 有输入栏的那几屏**整屏上移**键盘那一截 —— 输入栏与它上面的内容一起留在键盘之上。
        为什么不是"压缩高度":这一屏的几段各有地板(立绘段 132、面板 96),压到地板以下就溢出到
        键盘下面去了,而 iOS 键盘的高度 280~400+ 都有(中文输入法还多一条候选栏),
        没有哪组地板能同时适配。上移则一个字都不用改内部几何:输入栏永远落在"键盘之上、
        底导之下"那 96px 里,和原生应用一样(屏幕上沿被推出去的是标题与立绘 —— 它们本来也被键盘盖着)。
        **用 position:relative + top,不用 transform**:底导是这一屏的 fixed 子节点,
        而 transform 会把 fixed 子节点的包含块换成自己(底导会跟着掉进屏里、压住输入栏);
        relative 不影响 fixed 子节点 —— 底导照旧贴着视口底,再由 .kb-open .nav 自己上移。
   **只在键盘真的弹起时生效**(keyboard.js 的 --kb > 8 才挂 .kb-open):
   没有键盘 / 没有 visualViewport 的旧内核 / 桌面上,这几条一条都不作用。 */
.kb-open { --sab: 0px; }
.kb-open .nav { transform: translateY(calc(-1 * var(--kb, 0px))); }
.kb-open .screen-crystal {
  position: relative;
  top: calc(-1 * var(--kb, 0px));
}

/* ---------- 搬运确认层:警示块 + 两列对照表(2026-10-14) ----------
   旧版把「另一边会清空」写成 .s-sub 的 13px 灰字 —— 一张会毁数据的弹窗里最不起眼的一行。
   现在它是一块**暖玫瑰色的玻璃警示面**:高对比标题色(#8f2c43 在这层上约 9.6:1)+
   一枚画出来的警示图标。刻意不做「左侧色条」那类 callout —— 那是套路,不在这套玻璃语言里。 */
.move-warn {
  display: flex;
  gap: 11px;
  margin-top: 12px;
  padding: 11px 13px;
  border-radius: var(--r);
  border: 1.5px solid rgba(182, 66, 92, 0.34);
  background: linear-gradient(180deg, rgba(255, 246, 249, 0.96), rgba(253, 235, 241, 0.88));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.92),
    0 14px 30px -26px var(--shadow);
}
.move-warn .mw-ico {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1.5px solid rgba(182, 66, 92, 0.3);
  background: rgba(255, 255, 255, 0.72);
  color: #8f2c43;
}
.move-warn .mw-body { display: block; min-width: 0; }
.move-warn .mw-t { display: block; font-size: 14px; font-weight: 800; color: #8f2c43; }
.move-warn .mw-d { display: block; margin-top: 3px; font-size: 12.5px; line-height: 1.6; color: var(--navy-2); }

/* 两列对照:名目 | 旧手串 | 这条手串。用真 <table> —— 行列对照,读屏也该按表格念。
   数字走 tabular-nums 才能上下对齐;行列分隔用发丝线,不用色条。 */
.move-cmp {
  width: 100%;
  margin-top: 14px;
  border-collapse: collapse;
  table-layout: fixed;
}
.move-cmp th,
.move-cmp td { padding: 7px 6px; }
.move-cmp thead th {
  padding-bottom: 5px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  color: var(--navy-3);
  text-align: right;
}
.move-cmp tbody tr { border-top: 1px solid rgba(150, 168, 220, 0.24); }
.move-cmp tbody th {
  width: 4.4em;
  padding-left: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--navy-2);
  text-align: left;
}
.move-cmp td {
  font-size: 14px;
  font-weight: 700;
  color: var(--navy-1);
  text-align: right;
  font-variant-numeric: tabular-nums;
  /* 精灵那格是名字,长短不一:单行省略,别把表撑破 */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.move-cmp thead th:last-child,
.move-cmp td:last-child { padding-right: 0; }

/* 合并规则:表格的注脚。比正文小一档,但用 navy-2(不是更弱的 navy-3)——
   它写的是「精灵会被那只替换」这种容易被忽略、但用户会在意的事。 */
.move-note {
  margin: 10px 0 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--navy-2);
}
.sheet .field .lab { color: var(--navy-2); }

/* 确认弹窗(askConfirm):压在任何抽屉之上(抽屉 30、图鉴动画层 40、toast 70),观感仍是同一套 sheet */
.ask-mask { z-index: 50; }

/* 录音状态 */
.rec-state {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 18px;
  border-radius: var(--r);
  border: 1.5px solid var(--glass-line);
  background: rgba(255, 255, 255, 0.853);
  color: var(--navy-1);
}
.rec-dot {
  width: 10px; height: 10px;
  border-radius: 50%;
  background: #e0576f;
  box-shadow: 0 0 12px rgba(224, 87, 111, 0.8);
  animation: rec-blink 1.1s ease-in-out infinite;
}
@keyframes rec-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
.rec-time { font-family: var(--mono); font-size: 18px; color: var(--gold-ink); }

/* 预览媒体 */
.media-preview { border-radius: var(--r); overflow: hidden; border: 1.5px solid var(--glass-line); }
.media-preview img, .media-preview video { width: 100%; display: block; }

/* ---------- 操作提示(Toast):单槽 · 顶部胶囊 ----------
   同一时刻只显示 1 条(队列/去重全在 js/toast.js),所以这里的 grid 不是用来
   堆叠的——它让「正在退场的旧条」和「刚进场的新条」落在同一个格子里交叉淡出,
   而不是把容器撑成两行。位置放顶部、且**压在标题栏下方**:全站的重量都在下半屏
   (底导/按钮/输入栏),底部弹提示会盖住正在操作的按钮和表单;而贴最顶上会正好
   盖住页面标题(实测各页标题栏底边 55~82px),看着像坏了。90px 让它在所有页面上
   都落在标题之下。 */
.toast-wrap {
  position: fixed;
  left: 50%;
  /* 顶部安全区全站只在这里用一次:刘海屏上别把提示塞进状态栏 */
  top: calc(env(safe-area-inset-top, 0px) + 90px);
  transform: translateX(-50%);
  z-index: 70;                 /* 高于底导 20 / 抽屉 30 / 图鉴动画层 40 */
  display: grid;
  justify-items: center;
  width: min(88vw, 420px);
  pointer-events: none;        /* 提示永远不吃点击 */
}
.toast-wrap > .toast { grid-area: 1 / 1; }
.toast {
  max-width: 100%;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(238, 241, 251, 0.9));
  color: var(--navy-1);
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.45;
  padding: 10px 16px;
  text-align: center;
  box-shadow: 0 18px 40px -20px var(--shadow), inset 0 1px 0 #fff;
  backdrop-filter: blur(var(--g-thick-blur));
  -webkit-backdrop-filter: blur(var(--g-thick-blur));
  /* 长文案最多两行,超出省略(全文挂在 title 上);再长也不许把胶囊撑破 */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  animation: toast-in 0.22s cubic-bezier(0.2, 0.9, 0.3, 1.15);
}
.toast.warn { border-color: rgba(182, 66, 92, 0.5); color: #8f2c43; }
.toast.leaving { animation: toast-out 0.16s ease forwards; }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-10px) scale(0.96); }
  to { opacity: 1; transform: none; }
}
@keyframes toast-out {
  to { opacity: 0; transform: translateY(-8px) scale(0.98); }
}
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
  .toast.leaving { opacity: 0; }   /* 动画被全局关掉时,靠这条让退场那条立刻消失 */
}

/* 加载 */
.loading { display: grid; place-items: center; padding: 60px 0; color: var(--ink-3); }
.spinner {
  width: 30px; height: 30px;
  border-radius: 50%;
  border: 2.5px solid rgba(69, 64, 122, 0.22);
  border-top-color: var(--theme-select);
  animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------- 拦截页:没碰过手串的人进不来(设计图 2026-10-13) ----------
   身份就是「碰一下手串」,所以直接打开域名的人不该看见应用。key 藏在 NFC 链接的
   # 片段里、片段不发给服务端 —— 这道闸只能在客户端看(见 app.js 的 tryEnter)。
   它是一张满屏的独立屏:没有底导(去不了任何页),只有感应圈和一个重试键。 */
#app.gate { padding-bottom: 0; }          /* 这张屏没有底导,不替它留那 96px */
.nfc-gate {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* 减掉 --app-pad-t:这是「正好一屏」的满屏页,不减的话桌面(4px)与刘海屏(几十像素)
     会正好多出那一截,凭空多一条滚动。高度取 --app-h(可视高单源,旧内核同样准)。 */
  min-height: calc(var(--app-h) - var(--app-pad-t));
  /* 第一行是语言药丸(34px 高 + 与标题之间 6px 的气),和 .uc-screen 同一套算法:
     顶上只留「原来的 10vh 减去这 40px」—— 标题落在和加药丸之前**一模一样**的位置上,
     药丸是吃掉了上面那段空白,而不是把标题往下推(矮屏 10vh 不够 40,夹在 0)。 */
  padding: max(0px, calc(10vh - 40px)) 0 calc(24px + env(safe-area-inset-bottom, 0px));
  text-align: center;
}
/* 拦截页顶部的语言药丸(2026-10-21 用户点名:这页也要能换语言,而且要**一直显示当前语言**,
   和首页那枚一样 —— 只留一个图标,别人不知道按钮是干什么的)。
   占一行、靠右,排在标题之前(DOM 顺序 = 键盘顺序);stretch 必须给,父级是 align-items:center。 */
.ng-lang {
  align-self: stretch;
  display: flex;
  justify-content: flex-end;
  padding: 0 14px;
  margin-bottom: 6px;
  z-index: 3;
}
.ng-title {
  margin: 0;
  font-size: clamp(28px, 8.4vw, 35px);
  font-weight: 800;
  letter-spacing: 0.1em;
  color: var(--top-ink);
  text-shadow: var(--top-glow);
}
.ng-sub {
  margin: 11px 0 0;
  font-size: 13.5px;
  letter-spacing: 0.04em;
  color: var(--top-ink-2);
  text-shadow: var(--top-glow-2);
}

/* 感应圈:一圈玻璃环 + 一层暖白光晕 + 中心亮核,四周散几颗星点。
   全是 CSS/内联 SVG 画的,不引图片素材(和发现页那条夜轨同一个路子) */
.ng-stage {
  position: relative;
  width: min(58vw, 230px);
  aspect-ratio: 1;
  margin: clamp(26px, 6.5vh, 62px) 0 0;
}
.ng-halo {                                /* 光晕单独一层:只动 opacity/scale,不动 box-shadow */
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(255, 250, 240, 0.52), rgba(255, 250, 240, 0) 72%);
  filter: blur(var(--g-thin-blur));
  animation: ng-breathe 3.6s ease-in-out infinite;
}
@keyframes ng-breathe {
  0%, 100% { opacity: 0.72; transform: scale(0.97); }
  50% { opacity: 1; transform: scale(1.03); }
}
.ng-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1.5px solid rgba(126, 134, 208, 0.5);
  box-shadow:
    0 0 26px rgba(255, 250, 240, 0.85),
    0 0 70px rgba(150, 160, 235, 0.34),
    inset 0 0 28px rgba(255, 252, 244, 0.7);
}
.ng-core {                                /* 中心那点亮核(设计图里最亮的一点) */
  position: absolute;
  left: 50%; top: 50%;
  width: 13%; aspect-ratio: 1;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, #fffaf0, rgba(255, 238, 200, 0.85) 50%, rgba(246, 216, 150, 0) 100%);
  box-shadow: 0 0 22px rgba(240, 205, 140, 0.75);
}
.ng-waves { position: absolute; inset: 0; width: 100%; height: 100%; }
/* 星点摆在 .ng-stage 的百分比坐标上,允许超出这一圈(设计图里星尘比感应圈铺得更开) */
.ng-star {
  position: absolute;
  left: var(--x); top: var(--y);
  width: var(--s); height: var(--s);
  margin: calc(var(--s) / -2) 0 0 calc(var(--s) / -2);
  animation: ng-twinkle 4.6s ease-in-out infinite;
  animation-delay: var(--d, 0s);
}
@keyframes ng-twinkle {
  0%, 100% { opacity: calc(var(--o, 0.85) * 0.45); }
  50% { opacity: var(--o, 0.85); }
}

.ng-hint {
  margin: clamp(20px, 5vh, 46px) 0 0;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ink-3);            /* 这句在页面中段(浅底上),不能跟着用白字 */
}
.ng-card {                                /* 底部白玻璃卡:一句话 + 一个重试键(贴底,同设计图) */
  margin-top: auto;
  width: 100%;
  padding: 26px 22px 20px;
  border-radius: var(--r-lg);
  border: 1.5px solid var(--glass-line);
  background:
    radial-gradient(120% 70% at 50% -10%, rgba(255, 255, 255, 0.78), transparent 62%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(233, 237, 250, 0.875));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--navy-1);
  box-shadow: 0 24px 50px -28px var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.94);
}
.ng-card .ng-lead { margin: 0 0 18px; font-size: 15px; line-height: 1.7; }
.ng-note { margin: 13px 0 0; font-size: 12.5px; color: var(--navy-3); }
.ng-note.warn { color: var(--danger); }

/* 拦截页的矮屏分档。这一屏是**用户碰手串后看到的第一眼**,必须一屏装下 ——
   320×568 上原来是 646px(底部那张卡整个在屏幕外,连"重试"都够不到)。
   让位的顺序:先收顶部留白与感应圈(装饰),再收提示行的间距,卡片本身只收内边距。 */
@media (max-height: 700px) {
  .nfc-gate { padding-top: max(0px, calc(7vh - 40px)); }
  /* 这一档的「装饰让位」再紧一点:长语言(德/俄)的说明会多占一行,这里回收 ~10px */
  .ng-stage { width: min(52vw, 200px); margin-top: clamp(14px, 3.4vh, 34px); }
  .ng-hint { margin-top: clamp(12px, 2.8vh, 26px); font-size: 15px; }
  .ng-card { padding: 20px 20px 16px; }
  .ng-card .ng-lead { margin-bottom: 14px; }
}

@media (max-height: 600px) {
  .nfc-gate { padding-top: max(0px, calc(5vh - 40px)); }
  .ng-title { font-size: 25px; }
  /* 这一档 5vh 连药丸那一行都放不下(标题会被推下 12~16px),感应圈再收一档把这截换回来 */
  .ng-stage { width: min(40vw, 150px); margin-top: 16px; }
  .ng-sub { margin-top: 8px; font-size: 12.5px; }
  .ng-hint { margin-top: 13px; font-size: 14.5px; }
  .ng-card { padding: 16px 17px 13px; }
  .ng-card .ng-lead { font-size: 14px; margin-bottom: 11px; }
}

/* ---------- 选择使用方式(设计图 2026-10-14) ----------
   和上面那张拦截页同一族:满屏、没有底导、顶部白字带描边、白玻璃卡。
   差别只在内容 —— 这里是两张可以按的大卡(自己使用 / 送给别人),按下去才进产品。
   位置在「碰一下手串」之后、「产品任何一页」之前:这条手串拿来干嘛,是这个产品要问的第一件事。
   卡里的文字与说明:深蓝压在白玻璃上(设计稿量出来 12:1 / 9.5:1,远高于 7:1)。
   卡片内部照设计图:名称与说明**居中在上**,金色大图标落在**左下角**,右侧一枚小标(星 / 心)。
   图标不是手画的线描 SVG,是 tools/key-uc-icons.py 从设计源图抠出来的金色 3D 素材。
   下面这些数字都是**照设计稿量出来的比值**(设计稿卡宽 997 px,按 ÷997 换成百分比/px):
     大图标 16.6% 卡宽(自己使用)/ 25.2%(送给别人),距左 10.4% / 8.5%;
     名称字身 25px、说明 15px(按「卡宽 424 CSS px ↔ 设计稿 997」换算),右小标 3.2% / 5.0%。
   **一律用比值、不用固定 px**:卡片宽度随视口变(手机 343、面板 424),写死就会走形。
   横内边距是 0(百分比 margin 是按内容宽算的,留了内边距那个 10.4% 就不再是「距卡边 10.4%」),
   所以说明那句自己带一点左右留白,窄屏换行时不贴到卡边。 */
.uc-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* 同 .nfc-gate:满屏页要把顶部安全区/桌面那 4px 减掉,否则正好多出一条滚动。 */
  min-height: calc(var(--app-h) - var(--app-pad-t));
  /* 第一行是语言药丸(34px 高 + 与标题之间 6px 气),所以顶上只需再留
     「原来那条 8vh 减去 40」—— 标题落在和加药丸之前**一模一样**的位置上,
     药丸是凭空吃掉了上面那段空白(不是把标题往下推)。
     矮屏 8vh 会比这 40px 还小,减成负数没有意义,所以夹在 0。 */
  padding: max(0px, calc(8vh - 40px)) 0 calc(22px + env(safe-area-inset-bottom, 0px));
  text-align: center;
}
/* 语言药丸那一行:占满整行、靠右,排在标题之前(DOM 顺序 = 键盘顺序:
   先够到语言,再到标题与两张卡)。stretch 是必须的 —— 这个 flex 列是
   align-items:center,不 stretch 这一行就只有药丸那么宽,靠右也就无从谈起。 */
.uc-top {
  align-self: stretch;
  display: flex;
  justify-content: flex-end;
  /* 药丸与标题之间的那道气:不给的话矮屏上两行会贴死(标题紧跟着药丸的下沿)。 */
  margin-bottom: 6px;
}
.uc-title {
  margin: 0;
  font-size: clamp(26px, 7.6vw, 32px);
  font-weight: 800;
  letter-spacing: 0.1em;
  color: var(--top-ink);
  text-shadow: var(--top-glow);
  /* 英文标题在这块窄内容里会折行("How will you use it?" 折出孤零零一个 "it?")——
     和顶栏 h1 同一条纪律,断点交给浏览器平衡。 */
  text-wrap: balance;
}
.uc-sub {
  margin: 10px 0 0;
  font-size: 13.5px;
  letter-spacing: 0.04em;
  color: var(--top-ink-2);
  text-shadow: var(--top-glow-2);
}
.uc-cards {
  display: grid;
  gap: 24px;                       /* 设计稿卡间距 73 / 卡宽 997 ≈ 7.3% */
  width: 100%;
  margin: clamp(22px, 5.6vh, 52px) 0 0;
}
.uc-card {
  position: relative;
  display: block;
  padding: 18px 0 22px;
  border-radius: var(--r-lg);
  border: 1.5px solid var(--glass-line);
  background:
    radial-gradient(120% 70% at 50% -10%, rgba(255, 255, 255, 0.78), transparent 62%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(233, 237, 250, 0.875));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--navy-1);
  box-shadow: 0 24px 50px -28px var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.94);
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}
.uc-card:active { transform: scale(0.985); }
.uc-card:focus-visible { outline: 2px solid var(--navy-1); outline-offset: 3px; }
.uc-card[disabled] { opacity: 0.62; }
.uc-text { display: block; }
.uc-name { font-size: 25px; font-weight: 800; letter-spacing: 0.05em; }
.uc-desc {
  display: block;
  margin-top: 6px;
  padding: 0 7%;
  font-size: 15px;
  color: var(--navy-2);
}
/* 大图标:落在左下角。16.6% / 10.4% 是设计稿量的;27% 那张是「礼盒 + 飞机」合成素材
   (设计稿里那两张是分开摆的、比例也不同,合起来按「礼盒 ≈ 卡宽 17.9%、飞机 ≈ 9.2%」对齐) */
.uc-ico { display: block; width: 16.6%; height: auto; margin: 16px 0 0 10.4%; }
.uc-card[data-mode="gift"] .uc-ico { width: 27%; margin-left: 8.5%; }
/* 右小标:竖着落在图标那一带(设计稿里它的下缘约在卡底上方 34px ≈ 卡高的 16%) */
.uc-accent { position: absolute; right: 5.7%; bottom: 16%; width: 3.2%; height: auto; }
.uc-card[data-mode="gift"] .uc-accent { right: 7.1%; width: 5%; }
/* 底下那行小字离底边有一段距离(设计图里它是「页脚」,不是卡片的尾巴) */
.uc-note {
  margin: auto 0 0;
  padding-top: 20px;
  font-size: 12.5px;
  letter-spacing: 0.02em;
  color: var(--ink-3);
}

/* ---------- 收礼人碰一下手串:直出的那份祝福 ----------
   上头是精灵 + 打开的水晶礼盒(和「收到祝福」页同款头图),下面一张白玻璃卡:
   谁送的、什么类型、什么时候到的、赠言,然后才是两个选择。
   顺序是刻意的 —— 先看见祝福,再决定要不要保留。 */
.tap-gift { padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px)); }
/* 头图比领取页再收一档:这一屏下面还有整块内容面板(赠言/语音/照片/视频)+ 两个选择,
   立绘再高就把「先看见内容」挤到屏幕外了。内容多时整页滚动(和「祝福详情」一样)。 */
.tap-gift .view-stage { height: clamp(112px, 16vh, 140px); margin-top: 0; }
.tap-gift .view-stage .vs-sprite { height: clamp(112px, 15.5vh, 138px); }
.tap-gift .view-stage .vs-box { height: clamp(72px, 9.5vh, 88px); }
.tg-title {
  margin: 4px 0 3px;
  text-align: center;
  font-size: clamp(19px, 5.4vw, 23px);
  font-weight: 800;
  letter-spacing: 0.06em;
  color: var(--top-ink);
  text-shadow: var(--top-glow);
}
.tg-sub {
  margin: 0 0 13px;
  text-align: center;
  font-size: 13px;
  letter-spacing: 0.02em;
  color: var(--top-ink-2);
  text-shadow: var(--top-glow-2);
}
.tg-acts { display: grid; gap: 10px; margin-top: 14px; }
/* 这句在页底(白玻璃卡外面),所以是浅色字 —— 和 .uc-note / .view-links 同一套 */
.tg-note {
  margin: 12px 0 0;
  font-size: 12.5px;
  line-height: 1.62;
  color: var(--ink-3);
}
/* ---------- 碰串直出那一屏:矮屏分档(2026-10-15) ----------
   用户报「这一屏还是要上下滑」。实测(头部+标题+面板+两个动作+礼物码兜底):
   390×664 差 17px、360×640 差 30px、375×553 差 83px、320×568 差 92px。
   让位顺序照旧:头图(装饰)→ 媒体高度(让位主力)→ 间距 → 兜底那句的字号;
   **两个动作键永远不动**(它们是这一屏的用途),压到最后一档也保 44px 的可点高度。 */
@media (max-height: 760px) {
  .tap-gift .view-stage { height: clamp(96px, 14vh, 120px); }
  .tap-gift .view-stage .vs-sprite { height: clamp(96px, 13.5vh, 118px); }
  .tap-gift .view-stage .vs-box { height: clamp(62px, 8.5vh, 76px); }
  .tap-gift .view-media { max-height: min(32vh, 230px); }
  .tg-title { font-size: 21px; }
  .tg-acts { margin-top: 10px; gap: 8px; }
  .tg-acts .btn-gold, .tg-acts .btn-line { min-height: 48px; }
  .tg-note { margin-top: 9px; }
}
@media (max-height: 660px) {
  .tap-gift .view-stage { height: clamp(84px, 13vh, 104px); }
  .tap-gift .view-stage .vs-sprite { height: clamp(84px, 12.5vh, 102px); }
  .tap-gift .view-stage .vs-box { height: clamp(54px, 7.5vh, 66px); }
  .tap-gift .view-media { max-height: min(27vh, 186px); }
  .tg-title { font-size: 19px; margin: 3px 0 2px; }
  .tg-sub { font-size: 12.5px; }
  .tg-acts { margin-top: 8px; gap: 7px; }
  .tg-acts .btn-gold, .tg-acts .btn-line { min-height: 46px; }
  .tg-note { margin-top: 8px; font-size: 12px; line-height: 1.55; }
}
@media (max-height: 600px) {
  .tap-gift .view-stage { height: clamp(72px, 12vh, 88px); }
  .tap-gift .view-stage .vs-sprite { height: clamp(72px, 11.5vh, 86px); }
  .tap-gift .view-stage .vs-box { height: clamp(46px, 7vh, 56px); }
  .tap-gift .view-media { max-height: min(23vh, 152px); }
  .tg-title { font-size: 18px; }
  .tg-acts .btn-gold, .tg-acts .btn-line { min-height: 44px; }
  .tg-note { font-size: 11.5px; line-height: 1.5; }
}
@media (max-height: 780px) {
  .uc-screen { padding-top: max(4px, calc(5vh - 40px)); }
  .uc-cards { margin-top: 16px; gap: 13px; }
  .uc-card { padding: 15px 0 17px; }
  .uc-name { font-size: 22px; }
  .uc-desc { font-size: 14px; }
  .uc-ico { margin-top: 14px; }
}

/* 入场只做淡入:容器上带 transform 会让内部 fixed 底导改以它定位,
   动画期间底导会跟着位移 */
@keyframes page-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
.screen { animation: page-in 0.2s ease; }

/* ============================================================
   发现 / 广场 / 水晶:导航改版新增界面
   ============================================================ */

/* ---------- 顶部积分徽章(发现页右上角「+ 积分 20」) ----------
   2026-10-12 设计图:白玻璃底 + 白细描边(原来是奶油金底),金色只留图标 */
.points-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 16px 8px 13px;
  border-radius: var(--r-pill);
  border: 1.5px solid rgba(255, 255, 255, 0.62);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.853), rgba(255, 255, 255, 0.755));
  color: var(--navy-1);
  font-size: 13.5px;
  line-height: 1.3;            /* 设计图里这颗胶囊约 41px 高:收紧行高,别让继承的 1.65 把它撑胖 */
  letter-spacing: 0.04em;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    0 10px 24px -18px var(--shadow);
  white-space: nowrap;
  /* 2026-10-14 起它是个按钮(点它去积分任务页):<button> 的 UA 样式里没有 pointer,
     再补一档按下的缩放,和全站按钮的反馈语言一致 */
  cursor: pointer;
  transition: transform 0.14s ease;
}
.points-chip:active { transform: scale(0.97); }
/* 「+」是装饰(积分二字已表意,svg 自带 aria-hidden),按设计图保留金色;
   取深金 —— 白玻璃底上亮金几乎看不见(实测 #a5761f 只有 1.9:1) */
.points-chip svg { color: var(--gold-4); }
.points-chip b { font-size: 16.5px; font-weight: 800; letter-spacing: 0.02em; }

/* ---------- 发现页 ---------- */
/* 一屏装下:立绘 + 遭遇面板 + 亲密度 + 反应 + 互动按钮,不留滚动 */
.screen-discover {
  min-height: calc(var(--app-h) - var(--app-pad-t) - var(--app-pad-b));
  margin-bottom: calc(-1 * var(--app-pad-b));
  padding-bottom: 8px;
  display: flex;
  flex-direction: column;
}
.screen-discover .topbar { padding: 16px 0 2px; }
.screen-discover .topbar h1 { font-size: 26px; font-weight: 800; }
/* 发现页也进了「统一量尺」:整幅缩放之后不会再装不下,所以这条兜底只在
   极端窄/矮(JS 关掉缩放,**并且** .scrolls 由 fitDiscover 量出来)时才可能生效。 */
.screen-discover.scrolls { margin-bottom: 0; }
/* 这三处留白原先是「≤900 才收」(怕动作键下滑到导航底下)。现在整幅缩放兜住了这件事,
   所以写进基础规则:sprite 大小/卡片间距在**任何**高度下都一样,构图才谈得上一致。 */
.screen-discover .intimacy-card { margin-top: 6px; }
.screen-discover .react-line { margin-top: 6px; padding: 8px 14px; }
.screen-discover .foot-note { margin-top: 6px; }
.disco-body { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
.disco-right { display: flex; align-items: center; gap: 8px; }

/* 分段标签在该页单独收窄 */
.screen-discover .seg {
  gap: 0;
  padding: 4px;
  margin: 12px 0 4px;
}

/* ---------- 积分任务(发现页第二个 tab)----------
   这一页是后加的,一直没进「一屏预算」:上面那几档矮屏阶梯只收「遇见」那套块
   (`.disco-scene/.wild-card/.intimacy-card/...`),`.task-card` 与说明小字一个档位都没有,
   于是矮屏上第 4 张卡与说明落到固定底导下面(用户 2026-10-21 报的)。
   下面这些规则**只匹配任务页自己的块**,遇见页的分毫不动。
   说明段原先是行内 `style="text-align:left;margin-top:14px"` —— 行内样式把这一页
   所有矮屏档位都盖掉了,所以搬进 `.task-note`(类名只给任务页)。 */
.screen-discover .topbar.sub-full { flex-wrap: wrap; align-items: center; row-gap: 2px; }
/* 副标题独占一整行:右侧那颗积分胶囊很宽,长副标题留在左列会被挤成两三行(日语实测 3 行) */
.screen-discover .topbar.sub-full .tb-sub {
  flex: 1 1 100%;
  order: 3;
  font-size: 12.5px;
  margin-top: 2px;
}
.screen-discover .task-card { padding: 12px 15px; margin-top: 8px; }
/* 副标题是次要信息,给它一个确定的紧凑行高:浏览器默认(normal)在 CJK 字体上接近 1.9,
   两张卡起就白吃二十来像素 —— 矮屏上正好是"差那一点点"的来源。 */
.screen-discover .task-card .tk-sub { margin-top: 2px; line-height: 1.45; }
.screen-discover .task-note {
  text-align: left;
  margin-top: 12px;
  line-height: 1.45;
}

/* 立绘段的尺寸**只有一处真源**:--disco-h 是立绘的**期望上限**(定义在舞台上,立绘继承),
   矮屏由下面几档 media query 收它;而「立绘绝不许高过舞台」由 app.js 的 fitDiscover()
   按舞台实测高度再压一道(留 20px 给呼吸动画 ±9px 与投影)。
   以前两边各写各的(舞台下限 146px / 立绘 26vh):窄机上立绘盒能比舞台还高,
   溢出部分上下各一半压到分段标签和下面的按钮上 —— 用户报的「按钮和精灵重合」就是这么来的,
   而且是间歇的(动画一呼吸才碰到)。舞台下限保持 120px:它是**让位**用的,
   不跟立绘上限绑死(绑死会让 700~800 高的屏整页溢出)。 */
.disco-scene {
  position: relative;
  /* 立绘**绝不许溢出舞台**,这条以前只靠 JS 的那道上限(app.js 的 fitDiscover 按实测高度写
     inline max-height),而 inline 的值一旦定下就不会再变 —— iOS 的地址栏/键盘随时会改可视高,
     量过一次的上限就过期了(用户 2026-10-16 报的「精灵不在它该在的位置」就是这个类别)。
     所以改成**布局保证**:stage 是列向 flex,立绘是可收缩的项(flex:0 1 auto + min-height:0),
     舞台矮了立绘就自己等比缩小 —— 纯 CSS,不依赖任何一次测量。
     place-items 时代的"溢出"在 grid 里会整块往下沉(轨道比容器高时,居中项会从中间偏移),
     这正是它看起来"掉到卡片上/页面中段底部"的来由;flex 这一版没有这个失效模式。 */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 1 1 auto;
  /* 2026-10-26:卡面多了「性格 / 介绍 / 能力」三行,立绘让两档(184→140)把这一屏换回来;
     再小就低于 .disco-scene 的 120px 下限,精灵会缩成图标。 */
  --disco-h: min(20vh, 140px);
  min-height: 120px;
  padding: 2px 0 0;
}

/* 精灵身后的柔光(设计图里立绘是「坐」在这片光上的,不是直接贴在页底):
   脚下最亮一圈托住精灵 → 再往上散成冷白 → 往外沉一层淡紫,给环轨与彗尾当底 */
.disco-scene::before {
  content: "";
  position: absolute;
  inset: 0 calc(-1 * var(--pad-x));
  pointer-events: none;
  background:
    radial-gradient(30% 24% at 50% 58%, rgba(255, 252, 242, 0.5), rgba(255, 252, 242, 0) 72%),
    radial-gradient(58% 46% at 50% 46%, rgba(246, 249, 255, 0.48), rgba(246, 249, 255, 0) 74%),
    radial-gradient(94% 66% at 50% 62%, rgba(213, 223, 255, 0.5), rgba(213, 223, 255, 0) 78%);
}

/* 立绘舞台:夜轨环 + 两道彗尾 + 星芒(设计稿里的「精灵背景」,替掉原来的金色环素材)。
   **舞台只占立绘那一段**(inset 0),整幅在段内等比缩放 —— 矮屏上段矮,环与星就跟着缩;
   原来按机身宽度撑高(200/390),矮屏段高不够时下半圈会探到下面的卡片后面(真机实测很难看)。 */
.disco-stage {
  position: absolute;
  inset: 0 calc(-1 * var(--pad-x));
  pointer-events: none;
}
.disco-stage .ds-art { width: 100%; height: 100%; display: block; }

/* 星芒缓慢明灭、彗尾极慢漂移:让这一屏活着,但不抢立绘的呼吸 */
.ds-stars { filter: drop-shadow(0 0 3px rgba(255, 255, 255, 0.75)); }
.ds-stars .ds-star { animation: ds-twinkle 4.6s ease-in-out infinite; }
.ds-stars .ds-star:nth-child(2) { animation-delay: 0.7s; }
.ds-stars .ds-star:nth-child(3) { animation-delay: 1.6s; }
.ds-stars .ds-star:nth-child(4) { animation-delay: 2.4s; }
.ds-stars .ds-star:nth-child(5) { animation-delay: 3.1s; }
.ds-stars .ds-star:nth-child(6) { animation-delay: 3.8s; }
.ds-stars .ds-star:nth-child(7) { animation-delay: 1.2s; }
@keyframes ds-twinkle {
  0%, 100% { opacity: calc(var(--o, 0.8) * 0.34); }
  50% { opacity: var(--o, 0.9); }
}
.ds-comets { animation: ds-drift 18s ease-in-out infinite; }
@keyframes ds-drift {
  0%, 100% { transform: translateX(0); }
  50% { transform: translateX(-7px); }
}
.ds-orbit { animation: ds-breathe 9s ease-in-out infinite; }
@keyframes ds-breathe {
  0%, 100% { opacity: 0.86; }
  50% { opacity: 1; }
}

.disco-hero {
  position: relative;
  width: auto;
  max-width: min(41vw, 172px);
  max-height: var(--disco-h);       /* 与 .disco-scene 的下限同一处真源,保证装得进舞台 */
  /* 可收缩 + min-height:0 = "舞台不够高时立绘自己缩小"(见 .disco-scene 的说明)。
     立绘是替换元素、宽度 auto,所以只动高度、宽高比照旧。 */
  flex: 0 1 auto;
  min-height: 0;
  object-fit: contain;
  filter: drop-shadow(0 0 30px rgba(255, 250, 240, 0.5)) drop-shadow(0 14px 34px rgba(88, 98, 150, 0.3));
  animation: breathe 6.5s ease-in-out infinite;
}
.disco-hero.dim { opacity: 0.45; filter: grayscale(0.5) blur(var(--g-thin-blur)); }
/* 收容动画期间:停掉漂浮(JS 给原精灵挂上这个类),位置让给克隆体 */
.disco-hero.cap-held { animation: none; visibility: hidden; }

/* ============================================================
   收容:亲密度够了 → 点「收容」→ 光点汇聚 → 精灵化作图鉴卡 → 「收入图鉴」
   一整套只有 CSS keyframes(没有第三方库、没有 canvas 粒子),JS 只做三件事:
   量一次精灵的位置、按视口撒 10~18 颗光点、按时间轴放行按钮。
   **层级是设计定死的**:光点(1) < 精灵(2) < 图鉴卡片(3) < 按钮(4)。
   卡面上不写「收容成功」也不写「已收入图鉴」—— 落库发生在点下「收入图鉴」那一下,
   成功由那一刻的 toast 报(见 app.js 的 doAdopt)。
   时间轴(与 app.js 的 CAP_TOTAL 对齐;改时长只改这一段):
     0     ─ 620ms   光点四面汇聚到精灵中心,抵达即消失
     520   ─ 940ms   精灵缩小淡出
     700   ─ 1600ms  卡片从精灵的位置滑到中央偏下,落地回弹 + 淡蓝外发光
     1560  ─ 1900ms  「收入图鉴」淡入
   ============================================================ */

/* 「收容」按钮:金线白瓷。金色在这套设计里专给「结缘」这一刻(徽章 / 信封 / 星光同源),
   所以它不用紫罗兰的常规主按钮;底色仍是白瓷、文字深蓝,对比度照浅玻璃面的口径走。 */
.cap-cta {
  flex: 0 0 auto;
  display: grid;
  gap: 9px;
  margin-top: 10px;
  animation: cap-cta-in 0.46s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes cap-cta-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}
/* 动画一开演:提示线与按钮一起退场,把台子让给卡片 */
.cap-cta.cap-fade {
  animation: none;
  transition: opacity 0.34s ease, transform 0.34s ease;
  opacity: 0;
  transform: translateY(6px);
}
.cap-cta-line {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  margin: 0;
  color: var(--ink-2);
  font-size: 12.5px;
  letter-spacing: 0.04em;
  text-shadow: 0 1px 8px rgba(44, 52, 96, 0.42);
}
.cap-cta-line svg { color: var(--gold-2); }
.cap-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  min-height: 54px;
  padding: 13px 22px;
  border: 1px solid rgba(206, 168, 96, 0.62);
  border-radius: var(--r-pill);
  background: linear-gradient(180deg, #fdfdff 0%, #f2efe1 46%, #e3dcc4 100%);
  color: var(--navy-1);
  font-size: 16.5px;
  font-weight: 800;
  letter-spacing: 0.06em;
  cursor: pointer;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.22), 0 14px 30px -16px var(--shadow), inset 0 1px 0 #fff;
  animation: cap-btn-breathe 3.6s ease-in-out infinite;
}
.cap-btn svg { color: var(--gold-ink); }
@keyframes cap-btn-breathe {
  0%, 100% { box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.22), 0 14px 30px -16px var(--shadow), inset 0 1px 0 #fff; }
  50% { box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.22), 0 14px 30px -16px var(--shadow), inset 0 1px 0 #fff, 0 0 22px var(--gold-glow); }
}
.cap-btn:active { transform: scale(0.984); }
.cap-btn[disabled] { opacity: 0.62; cursor: default; animation: none; }
/* 收容价格:跟着「收容」主标的一行小字。按钮是白瓷底,所以用静音藏青(不是 --ink-2 那种浅色)。 */
.cap-btn em {
  font-style: normal;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: rgba(58, 58, 76, 0.62);
}
/* 积分不够时的差额提示:只在不够时出现(够的时候连节点都没有),别常驻占一屏预算 */
.cap-hint {
  text-align: center;
  font-size: 12px;
  color: var(--ink-2);
  text-shadow: 0 1px 8px rgba(44, 52, 96, 0.42);
}
.cap-hint b { color: var(--gold-2); }

/* ---------- 动画层 ----------
   固定铺满视口、盖住底导(这一屏两秒里只演这一件事);pointer-events:none —— 不锁页面,
   用户点走就整屏重渲,这层跟着一起没了,不会留下半拉子状态。 */
.cap-layer {
  position: fixed;
  inset: 0;
  z-index: 40;
  overflow: hidden;
  pointer-events: none;
}
/* 浅蓝柔雾:把身后那一屏(遭遇卡 / 亲密度 / 底导)压成一片柔光,卡片才站得住,
   同时不做压暗 —— 治愈向的一刻不该变黑。雾本身很轻,真正的亮度来自上面两层 radial。 */
.cap-bloom {
  position: absolute;
  inset: 0;
  opacity: 0;
  background:
    radial-gradient(56% 40% at 50% 44%, rgba(232, 240, 255, 0.72), rgba(232, 240, 255, 0) 74%),
    radial-gradient(130% 96% at 50% 50%, rgba(163, 180, 240, 0.42), rgba(163, 180, 240, 0) 80%),
    linear-gradient(180deg, rgba(228, 237, 255, 0.86), rgba(202, 216, 248, 0.76));
}
.cap-points { position: absolute; inset: 0; z-index: 1; }      /* 层级 1:光点 */
.cap-layer.play .cap-bloom { animation: cap-bloom 0.9s ease-out both; }
@keyframes cap-bloom {
  0% { opacity: 0; }
  42% { opacity: 1; }
  100% { opacity: 0.88; }
}

/* 光点:外壳走直线(带一点加速),内胆沿垂线鼓出去往返 —— 两个不同缓动的位移叠起来
   就是一条柔和的曲线。抵达中心时缩到最小并淡掉(不是圆环,就是一颗光点消失)。
   2026-10-14 用户反馈「看不太清」:身下那层 cap-bloom 是浅蓝白,白点压在浅底上等于隐身 ——
   所以核心改成实心白(中段不再半透明),光晕加厚成三层且偏暖,靠"亮芯 + 暖晕"在浅底上立住。
   改亮了就要保住观感的锚:光点仍是**小圆点**,别加描边、别加环。 */
.cap-points .cap-pt {
  position: absolute;
  display: block;
  border-radius: 50%;
}
.cap-points .cap-pt i {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, #fff 0%, #fff 46%, rgba(255, 249, 232, 0.88) 72%, rgba(255, 246, 220, 0) 100%);
  box-shadow:
    0 0 6px rgba(255, 255, 255, 0.98),
    0 0 16px rgba(255, 247, 220, 0.92),
    0 0 28px rgba(255, 232, 178, 0.6);
}
.cap-points .cap-pt.gold i {
  background: radial-gradient(50% 50% at 50% 50%, #fff8e6 0%, var(--gold-1) 46%, rgba(244, 208, 132, 0.9) 72%, rgba(240, 205, 130, 0) 100%);
  box-shadow:
    0 0 6px rgba(255, 252, 238, 0.98),
    0 0 16px rgba(248, 220, 158, 0.95),
    0 0 28px rgba(240, 198, 116, 0.64);
}
.cap-layer.play .cap-pt {
  animation:
    cap-fly var(--cap-dur, 560ms) cubic-bezier(0.4, 0, 0.62, 1) var(--cap-delay, 0ms) both,
    cap-dot var(--cap-dur, 560ms) linear var(--cap-delay, 0ms) both;
}
.cap-layer.play .cap-pt i {
  animation: cap-bow calc(var(--cap-dur, 560ms) * 1.06) ease-in-out var(--cap-delay, 0ms) both;
}
@keyframes cap-fly {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(var(--cap-tx, 0), var(--cap-ty, 0), 0); }
}
@keyframes cap-bow {
  0% { transform: translate3d(0, 0, 0) scale(1); }
  52% { transform: translate3d(var(--cap-bx, 0), var(--cap-by, 0), 0) scale(1.18); }
  100% { transform: translate3d(0, 0, 0) scale(0.3); }
}
@keyframes cap-dot {
  0% { opacity: 0; }
  16% { opacity: 1; }
  78% { opacity: 1; }
  100% { opacity: 0; }
}

/* 精灵(克隆体):站在原精灵的位置上,缩一圈淡出。
   起点由 JS 的时间线给(--cap-sprite-at):必须在**光点全部抵达之后**才淡出,不能提前。 */
.cap-sprite {
  position: fixed;
  display: block;
  z-index: 2;                                                    /* 层级 2:精灵 */
  object-fit: contain;
  pointer-events: none;
}
.cap-layer.play .cap-sprite {
  animation: cap-sprite-out var(--cap-sprite-dur, 600ms) cubic-bezier(0.4, 0, 0.3, 1) var(--cap-sprite-at, 1420ms) both;
}
@keyframes cap-sprite-out {
  from { opacity: 1; transform: scale(1); }
  to { opacity: 0; transform: scale(0.74); }
}

/* 卡片与收尾按钮:一列,横向居中;**竖向贴底导上沿排**,不是按视口中心算 ——
   卡面宽高比逐张不同(0.72~0.87),同一个百分比落到矮屏上,这一列就会压到底导上
   (实测 375×553:按钮盖住底导那排图标)。贴底锚定之后,不管哪张卡、哪个档位,
   按钮都稳稳落在底导之上,卡片只往上长。 */
.cap-column {
  position: absolute;
  left: 50%;
  bottom: calc(var(--nav-h) + var(--nav-inset-b) + 16px + var(--sab));
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  /* 卡宽还要按**可用高度**收:卡高 ≈ 卡宽 / 0.72,极矮屏上只贴底不封顶会把卡顶出屏幕。
     0.74 是「宽 / 高」的下界,220px 留给「按钮 + 间距 + 上下呼吸」。 */
  width: min(74vw, 268px, calc((var(--app-h, 100vh) - 220px) * 0.74));
  transform: translateX(-50%);
}
/* 图鉴卡:玻璃质感 + 淡蓝细描边;起点由 JS 用 --cap-dx/--cap-dy/--cap-s 拉回精灵身上 */
.cap-card {
  position: relative;
  z-index: 3;                                                    /* 层级 3:卡片 */
  width: 100%;
  margin: 0;
  padding: 16px 16px 14px;
  border: 1px solid rgba(202, 220, 255, 0.92);
  border-radius: var(--r-lg);
  background:
    radial-gradient(120% 78% at 50% 0%, rgba(255, 255, 255, 0.94), rgba(255, 255, 255, 0.6) 62%),
    linear-gradient(180deg, rgba(240, 245, 255, 0.9), rgba(219, 229, 250, 0.78));
  backdrop-filter: blur(var(--g-thick-blur)) saturate(1.08);
  box-shadow: 0 18px 44px -20px var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.95);
  opacity: 0;
  text-align: center;
}
/* 落地后亮起的那圈淡蓝外发光:只动 opacity,不每帧重画阴影 */
.cap-card-halo {
  position: absolute;
  inset: -8px;
  z-index: -1;
  border-radius: calc(var(--r-lg) + 8px);
  box-shadow: 0 0 26px 6px rgba(150, 172, 240, 0.55);
  opacity: 0;
}
/* 卡片:**起点与终点沿用现在这版**——起点由 JS 用 --cap-dx/--cap-dy/--cap-s 拉回精灵身上,
   终点是「中央偏下」那一列;缓动与落地手感沿用上一版(带一点过冲回弹)。 */
.cap-layer.play .cap-card {
  animation: cap-card-in var(--cap-card-dur, 950ms) cubic-bezier(0.22, 0.9, 0.24, 1) var(--cap-card-at, 1750ms) both;
}
@keyframes cap-card-in {
  0% { opacity: 0; transform: translate(var(--cap-dx, 0), var(--cap-dy, 0)) scale(var(--cap-s, 0.5)); }
  22% { opacity: 1; }
  62% { opacity: 1; transform: translate(0, 0) scale(1.035); }     /* 落地:轻微过冲 */
  78% { transform: translate(0, 0) scale(0.996); }                 /* 回弹一点点 */
  100% { opacity: 1; transform: translate(0, 0) scale(1); }
}
/* 落地后亮起的那圈淡蓝外发光:只动 opacity,不每帧重画阴影。时刻 = 卡片落稳那一下 */
.cap-layer.play .cap-card-halo { animation: cap-halo 0.44s ease-out var(--cap-land-at, 2760ms) both; }
@keyframes cap-halo {
  from { opacity: 0; }
  to { opacity: 1; }
}
.cap-card-art {
  width: 100%;
  height: 124px;
  object-fit: contain;
  filter: drop-shadow(0 8px 20px rgba(96, 108, 168, 0.28));
}
.cap-card-name {
  margin: 6px 0 0;
  color: var(--navy-1);
  font-size: 18px;
  font-weight: 800;
  letter-spacing: 0.04em;
}
.cap-card-info {
  display: flex;
  justify-content: center;
  gap: 18px;
  margin: 10px 0 0;
  padding: 9px 0 0;
  border-top: 1px solid rgba(150, 168, 220, 0.28);
}
.cap-card-info > div { display: grid; gap: 2px; }
.cap-card-info dt { color: var(--navy-3); font-size: 11px; letter-spacing: 0.08em; }
.cap-card-info dd { margin: 0; color: var(--navy-1); font-size: 13px; font-weight: 700; }
/* 收尾按钮:只淡入 opacity —— 不带 transform 的关键帧,免得 fill:both 把 :active 的按压缩放锁死。
   `visibility` 是一条既有 bug 的补丁:`.btn-fur[disabled]` 的 opacity 优先级比 `.cap-take` 高,
   会把它盖成 0.55 —— 动画还没走到点时按钮会以一道淡影露在外面(实测)。 */
.cap-take { z-index: 4; opacity: 0; visibility: hidden; }
.cap-layer.play .cap-take {
  visibility: visible;
  pointer-events: auto;
  animation: cap-take-in 0.42s ease-out calc(var(--cap-land-at, 2760ms) + 60ms) both;
}
@keyframes cap-take-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* 矮屏(横屏手机 / 小屏):卡片整列收一档,别让卡片顶出屏幕 */
@media (max-height: 640px) {
  .cap-column { gap: 10px; width: min(64vw, 232px, calc((var(--app-h, 100vh) - 200px) * 0.74)); }
  .cap-card { padding: 12px 14px 11px; }
  .cap-card-art { height: 92px; }
  .cap-card-name { font-size: 16.5px; }
  .cap-card-info { margin-top: 8px; padding-top: 7px; }
}

/* 系统开了「减弱动态效果」:整段压成瞬时,只留最终的静止画面(顺序与层级不变) */
@media (prefers-reduced-motion: reduce) {
  .cap-cta,
  .cap-btn { animation: none; }
  .cap-layer.play .cap-bloom,
  .cap-layer.play .cap-pt,
  .cap-layer.play .cap-pt i,
  .cap-layer.play .cap-sprite,
  .cap-layer.play .cap-card,
  .cap-layer.play .cap-card-halo,
  .cap-layer.play .cap-take {
    animation-duration: 1ms;
    animation-delay: 0ms;
  }
}

/* 还没遇到精灵:邀请卡 */
.disco-invite {
  flex: 0 0 auto;
  border-radius: var(--r-lg);
  border: 1.5px solid var(--glass-line);
  background:
    radial-gradient(120% 90% at 50% -20%, rgba(255, 238, 200, 0.74), transparent 60%),
    linear-gradient(170deg, rgba(255, 255, 255, 0.92), rgba(238, 242, 252, 0.785));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--navy-1);
  padding: 18px 18px 20px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 18px 40px -26px var(--shadow);
}
.disco-invite .di-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 16.5px;
  font-weight: 800;
  color: var(--navy-1);
}
.disco-invite .di-title svg { color: var(--gold-ink); }
.disco-invite .di-text {
  margin: 8px 0 16px;
  color: var(--navy-2);
  font-size: 13.5px;
  line-height: 1.75;
}

/* 野生精灵卡:外层玻璃卡「✦ 野生精灵出现! + 渐隐横线」,内嵌一层更亮的身份卡 */
.wild-card {
  flex: 0 0 auto;
  padding: 9px 9px 10px;
  border-radius: 24px;
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.905), rgba(238, 242, 252, 0.74));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--navy-1);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 18px 40px -26px var(--shadow);
}
.wild-card .wc-head {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 2px 3px 9px;
  color: var(--navy-1);
  font-size: 15px;
  font-weight: 800;
  line-height: 1.3;
  letter-spacing: 0.05em;
}
.wild-card .wc-head svg { flex: 0 0 auto; color: var(--gold-ink); }
.wild-card .wc-head i {
  flex: 1 1 auto;
  height: 1px;
  margin-left: 3px;
  background: linear-gradient(90deg, rgba(120, 130, 180, 0.35), rgba(120, 130, 180, 0));
}
.enc-panel {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 10px;
  row-gap: 6px;
  align-items: center;
  padding: 10px 10px 11px;
  border: 1px solid rgba(255, 255, 255, 0.86);
  border-radius: 18px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.76), rgba(240, 243, 252, 0.5));
  box-shadow: inset 0 1px 0 #fff;
}
.enc-panel .ep-avatar {
  grid-row: 1 / span 2;
  flex: 0 0 auto;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  overflow: hidden;
  border: 1.5px solid rgba(255, 255, 255, 0.95);
  background: rgba(255, 255, 255, 0.5);
  box-shadow: 0 0 14px rgba(255, 255, 255, 0.5);
}
.enc-panel .ep-avatar img { width: 100%; height: 100%; object-fit: cover; }
.enc-panel .ep-name {
  grid-column: 2;
  align-self: end;
  font-size: 22px;
  font-weight: 800;
  line-height: 1.25;
  color: var(--navy-1);
  letter-spacing: 0.04em;
  display: flex;
  align-items: center;
  gap: 8px;
}
/* 「一只精灵 = 一个主题」:名字后面三枚色点,一眼看出这只的配色(2026-10-26)。
   色值来自名册的 theme,服务端下发;不放文字 —— 主题色是感觉,不是名词。 */
.enc-panel .ep-name .et-dots {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
}
.enc-panel .ep-name .et-dots i {
  display: block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 1px solid rgba(40, 50, 90, 0.16);
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.85);
}
/* 性格:一行小字(中点分隔),不占独立卡片位置 */
.enc-nature {
  margin: 8px 2px 0;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--navy-3);
  letter-spacing: 0.04em;
}
/* 精灵介绍:卡面正文,两行封顶 —— 再长就把这一屏顶出去 */
.enc-intro {
  margin: 4px 2px 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--navy-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* 能做什么(agent 那一面):**单行横向滚动**的 chip —— 换行会让三枚占两行(再吃掉 34px) */
.ability-row {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  margin-top: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 0;
}
.ability-row::-webkit-scrollbar { display: none; }
.ability-row .ab-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px 5px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.62);
  border: 1px solid rgba(120, 138, 200, 0.24);
  font-size: 12px;
  line-height: 1.3;
  color: var(--navy-2);
  white-space: nowrap;
}
.ability-row .ab-chip svg {
  flex: 0 0 auto;
  color: var(--theme-accent-deep, #6f83cb);
}
/* 标签与限时同一行:限时钉在右侧,挤不下时由标签换行,限时不会掉到下一行 */
.enc-panel .ep-row {
  grid-column: 2;
  display: flex;
  align-items: center;
  gap: 6px 10px;
}
.enc-panel .ep-badges { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; gap: 5px; }
.enc-panel .ep-badges .tag {
  font-size: 12px;
  padding: 3px 8px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(120, 130, 180, 0.34);
  background: rgba(255, 255, 255, 0.815);
  color: var(--navy-2);
  font-weight: 600;
  white-space: nowrap;
}
.enc-panel .ep-badges .tag.on {
  border-color: rgba(201, 150, 63, 0.6);
  background: rgba(255, 226, 168, 0.5);
  color: #8a6115;
}
.enc-panel .ep-left {
  flex: 0 0 auto;
  margin-left: auto;
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  font-size: 11px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.enc-panel .ep-left em { font-style: normal; color: var(--navy-3); }
.enc-panel .ep-left b { color: var(--navy-1); font-weight: 700; letter-spacing: 0.02em; }

/* 亲密度卡 */
.intimacy-card {
  margin-top: 10px;
  padding: 11px 14px 12px;
  flex: 0 0 auto;
  border-radius: 20px;
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.905), rgba(238, 242, 252, 0.74));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--navy-1);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.88), 0 14px 30px -22px var(--shadow-soft);
}
.intimacy-card .ic-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.intimacy-card .ic-label { font-size: 14px; color: var(--navy-1); font-weight: 700; letter-spacing: 0.08em; }
.intimacy-card .ic-num { font-size: 13.5px; line-height: 1.3; color: var(--navy-2); font-variant-numeric: tabular-nums; }
.intimacy-card .ic-num b { font-size: 22px; line-height: 1.1; color: #a5761f; }
.intimacy-card .ic-track {
  margin: 8px 0 7px;
  height: 10px;
  border-radius: var(--r-pill);
  background: rgba(120, 130, 180, 0.26);
  box-shadow: inset 0 1px 3px rgba(52, 60, 102, 0.28);
  overflow: hidden;
}
.intimacy-card .ic-track i {
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, #d9a94f, #f9e3ae);
  box-shadow: 0 0 14px rgba(240, 190, 100, 0.7);
  transition: width 0.45s cubic-bezier(0.3, 1, 0.4, 1);
}
.intimacy-card .ic-foot { font-size: 12.5px; color: var(--navy-2); }
.intimacy-card .ic-foot b { color: #a5761f; font-size: 14px; }

/* 「换一只」:已收容的重复款既不能互动、也不能重复收容,只能干等到时限 —— 给它一枚主动刷掉的按钮。
   冷却状态写在**按钮自己身上**(以前是按钮下面一行小字,落在页面最底部、紧贴底导,几乎没人看见):
   冷却中禁用 + 写倒计时,冷却过了写「现在可以换」+ 可点。两行字:动作在上,状态在下。 */
.refresh-row { margin-top: 10px; }
.reroll-btn {
  flex-direction: column;
  gap: 3px;
  padding: 10px 20px;
}
.reroll-btn .rb-top { display: inline-flex; align-items: center; gap: 7px; font-size: 16px; }
.reroll-btn em {
  font-style: normal;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--navy-2);
}
.reroll-btn em b { font-weight: 700; font-variant-numeric: tabular-nums; }
/* 冷却中:**读数上位、标签退后** —— 此刻用户要知道的是「还要多久」。
   这里刻意不整块降透明度:那会把倒计时一起弄淡,又变回「不起眼」。
   禁用感交给:平面底(去掉渐变与投影)、图标转灰、标签转次级色、cursor:default。 */
.reroll-btn[disabled] {
  cursor: default;
  /* 底要够亮:标签退到 --navy-2 之后,压在页面渐变最底部那一段上只有 4.1:1(实测不达标);
     抬到 0.72 白以后是 5.7:1。顺带也让"按下不去"这件事看得出来(没有渐变、没有投影)。 */
  background: rgba(255, 255, 255, 0.72);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.reroll-btn[disabled] .rb-top { color: var(--navy-2); }
.reroll-btn[disabled] svg { color: var(--navy-3); }
.reroll-btn[disabled] em { color: var(--navy-1); }

/* 未收容那一版的「换一只」:遭遇卡标题行右端的一枚小胶囊(塞在渐隐横线之后)。
   这一页按「一屏装下」设计,只剩 ~34px 余量 —— 它必须**零高度成本**,所以材质照抄卡里的
   .tag / 页面上的 .chip-static(同一套玻璃语言),只是它是可点的:补上 hover / active / disabled。
   不动 .wc-head 本身(那行的字距、金色星标、渐隐横线都是设计好的)。 */
.rr-pill {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border: 1px solid rgba(120, 130, 180, 0.4);
  border-radius: var(--r-pill);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.82), rgba(255, 255, 255, 0.52));
  color: var(--navy-1);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: normal;        /* 标题行有 0.05em 字距,胶囊里不要跟着散开 */
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: inset 0 1px 0 #fff, 0 4px 10px -8px var(--shadow-soft);
  transition: transform 0.14s ease, background 0.2s ease, border-color 0.2s ease;
}
.rr-pill svg { flex: 0 0 auto; width: 13px; height: 13px; color: var(--gold-ink); }
.rr-pill b { font-weight: 700; font-variant-numeric: tabular-nums; }
.rr-pill:active { transform: scale(0.96); }
.rr-pill[disabled] { opacity: 0.55; cursor: default; background: rgba(255, 255, 255, 0.42); }
.rr-pill[disabled] svg { color: var(--navy-3); }

/* 互动反应条:只说感受,不说原因 */
.react-line {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  min-height: 38px;
  padding: 10px 14px;
  flex: 0 0 auto;
  border-radius: 18px;
  font-size: 13.5px;
  line-height: 1.75;
  animation: page-in 0.24s ease;
}
.react-line.idle {
  background: rgba(255, 255, 255, 0.755);
  border: 1px solid rgba(255, 255, 255, 0.6);
  color: var(--navy-2);
}
.react-line.up {
  background: linear-gradient(180deg, rgba(255, 246, 218, 0.72), rgba(255, 232, 180, 0.42));
  border: 1.5px solid rgba(246, 214, 150, 0.9);
  color: #7d5810;
}
.react-line.down {
  background: rgba(255, 226, 232, 0.6);
  border: 1.5px solid rgba(182, 66, 92, 0.35);
  color: #8f2c43;
}
.react-line .rl-delta {
  flex: 0 0 auto;
  min-width: 40px;
  text-align: center;
  padding: 3px 8px;
  border-radius: var(--r-pill);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  background: rgba(255, 255, 255, 0.6);
}
.react-line.up .rl-delta { color: #8a6115; }
.react-line.down .rl-delta { color: #8f2c43; }

/* 互动按钮排:三颗玻璃胶囊,**同一行、同一尺寸**。
   以前中间「抚摸」按设计图放大 1.22 倍、并给了金色强调(注释里那句"设计图的选中态玻璃语言")——
   它只是视觉重量,不代表推荐动作,却让这一排看着一高一低。2026-10-16 用户口径:
   「缩小一点,一行排开,同样大小,简洁一点」→ 三颗拉平、整体收一档、把外发光与描边收薄。
   信息一个没少:标签 + 消耗仍两行都在(只是各收一档字号)。 */
.action-bar {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: auto;
  padding-top: 12px;
}
.act-btn {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 6px 8px;
  border: 1px solid rgba(255, 255, 255, 0.7);
  border-radius: var(--r-pill);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.06) 52%, rgba(255, 255, 255, 0)),
    linear-gradient(180deg, #a3b0dd, #7b8ecf);
  color: #fff;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.06em;
  cursor: pointer;
  overflow: hidden;
  text-shadow: 0 1px 4px rgba(48, 56, 112, 0.6);
  box-shadow:
    0 8px 18px -14px var(--shadow),
    inset 0 1px 0 rgba(255, 255, 255, 0.55);
  transition: transform 0.14s ease, box-shadow 0.2s ease;
}
/* 顶沿玻璃高光 */
.act-btn::before {
  content: "";
  position: absolute;
  inset: 1px 3px auto;
  height: 44%;
  border-radius: var(--r-pill);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0));
  pointer-events: none;
}
.act-btn:active { transform: scale(0.982); box-shadow: 0 0 18px rgba(255, 255, 255, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.55); }
.act-btn .ab-top { display: inline-flex; align-items: center; gap: 5px; line-height: 1.2; }
/* 消耗积分:窄屏按钮只有 ~100px,不能换行 */
.act-btn em {
  font-style: normal;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  color: rgba(255, 255, 255, 0.82);
  line-height: 1.25;
}

/* 最近的相遇 */
.recent-row { display: flex; flex-wrap: wrap; gap: 8px; }
.recent-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 12px 5px 6px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--glass-line);
  background: rgba(255, 255, 255, 0.785);
  color: var(--navy-2);
  font-size: 12px;
}
.recent-chip img {
  width: 24px; height: 24px;
  border-radius: 50%;
  object-fit: cover;
  border: 1.5px solid rgba(255, 255, 255, 0.9);
  background: rgba(255, 255, 255, 0.4);
}

/* 积分任务 */
.task-card {
  border-radius: var(--r);
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.92), rgba(240, 243, 252, 0.755));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--navy-1);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.88), 0 14px 30px -22px var(--shadow-soft);
  padding: 14px 16px;
  margin-top: 10px;
}
.task-card .tk-main { display: flex; align-items: center; gap: 12px; }
.task-card .tk-body { flex: 1; min-width: 0; display: block; }
.task-card .tk-title {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 15.5px;
  font-weight: 700;
  color: var(--navy-1);
}
.task-card .tk-title svg { color: var(--gold-ink); }
.task-card .tk-sub { display: block; font-size: 12px; color: var(--navy-2); margin-top: 3px; }
.task-card .tk-right { flex: 0 0 auto; }
.task-card .tk-count {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--gold-ink);
  padding: 4px 10px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(201, 150, 63, 0.5);
  background: rgba(255, 226, 168, 0.4);
}
.tk-ok {
  width: 28px; height: 28px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: linear-gradient(180deg, #f7dda4, #d9a94f);
  color: #4c3410;
  box-shadow: 0 0 14px rgba(240, 190, 100, 0.55);
}
.task-card .tk-btn {
  width: auto;
  min-width: 104px;
  min-height: 46px;
  padding: 0 20px;
  font-size: 15px;
}

/* 喂食选择抽屉 */
.food-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.food-item {
  display: grid;
  gap: 4px;
  text-align: left;
  padding: 13px 14px;
  border-radius: 18px;
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(165deg, rgba(255, 255, 255, 0.92), rgba(238, 242, 252, 0.815));
  color: var(--navy-1);
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
  transition: transform 0.14s ease, box-shadow 0.2s ease;
}
.food-item:active { transform: scale(0.975); }
.food-item .fi-name { font-size: 15.5px; font-weight: 700; }
.food-item .fi-price {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12.5px;
  color: var(--gold-ink);
}
.food-item .fi-bio { font-size: 11.5px; color: var(--navy-3); line-height: 1.6; }
.food-item.off { opacity: 0.45; }

/* ---------- 精灵广场:蓝白梦幻 + 玻璃拟态 + 3D 弧形画廊 ----------
   设计稿(2026-10-12):顶栏 → 「精灵画廊」介绍卡 → 「图鉴完成度」模块(缩略格)
   → 「精灵画廊」筛选 + 3D 弧形轮播(中间放大水晶卡)→ 圆点/箭头 → 底部导航。
   配色是浅蓝白,和全站主题同一套玻璃语言:浅蓝渐变底 + 白描边 + 柔光,
   金色只留在「结缘」徽章上(和首页/祝福页的金色点缀同源)。 */
.screen-plaza,
.screen-codex,
.sheet {
  /* 广场/图鉴自己的「钢蓝」墨色(2026-10-14 中午曾被并到主题靛蓝,用户看过说
     「精灵图鉴卡片上的颜色给我改回去」→ 已还原成这一版)。 */
  --plz-ink: #2c456f;          /* 玻璃面上的主文字(深蓝) */
  --plz-ink-2: #4f6b93;        /* 次级文字(实测:浅玻璃面上 4.8:1) */
  --plz-blue: #3f6bb0;         /* 数值与高亮 */
  --plz-blue-lite: #9dc4f0;
  --plz-line: rgba(255, 255, 255, 0.84);
}

/* 一屏预算(同「我的」页的写法):内容不足一屏时**让舞台吃掉剩下的高度**。
   卡牌本身尺寸不变、在舞台里竖向居中 —— 舞台变高等于给那圈弧形光晕更多余量
   (设计稿里光环本来就比中间的卡大一圈),比把两百来像素空白留在底导上方自然。
   矮屏上舞台有 min-height 兜底,内容真放不下时页面照旧滚动。 */
.screen-plaza {
  display: flex;
  flex-direction: column;
  /* **必须减掉 --app-pad-t**:桌面(≥520 宽)是 4px、刘海屏 standalone 是几十像素,
     只减 --app-pad-b 的话这一屏会比可用高度高出一截,整页凭空多滚几下
     (1280×720 面板上就是这么多出 4px 的)。 */
  min-height: calc(var(--app-h) - var(--app-pad-t) - var(--app-pad-b));
  padding-bottom: 10px;
}
.screen-plaza > .topbar { flex: 0 0 auto; }
.screen-plaza > #plaza-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
#plaza-body > .plz-head,
#plaza-body > .plz-foot,
#plaza-body > .plz-hint,
#plaza-body > .plz-ext { flex: 0 0 auto; }

.plz-h2 {
  margin: 0;
  font-size: 17px;
  font-weight: 800;
  letter-spacing: 0.03em;
  color: var(--plz-ink);
  text-shadow: 0 1px 10px rgba(255, 255, 255, 0.7);
}

/* ---------- 1) 画廊头卡:标题 + 筛选 + 图鉴完成度 ----------
   这三样都在回答「这是哪个画廊、收到哪儿了、现在看哪一档」,是同一件事的三个面,
   所以合成**一张**头卡(而不是三张各自为政的玻璃块),下面直接接卡牌区。 */
.plz-head {
  margin-top: 12px;
  padding: 14px 15px 15px;
  border-radius: 22px;
  /* v2(2026-10-27):白瓷(0.92 渐变 + 1.5px 亮边)→ 毛玻璃:更透、边更淡 */
  border: 1px solid var(--g2-edge);
  background: var(--g2-tint);
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--plz-ink);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.94), 0 16px 34px -24px var(--shadow-soft);
}
.plz-head-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px 10px;
  flex-wrap: wrap;                     /* 335px 窄屏上让筛选整行落到标题下面,而不是硬挤成一团 */
}
.plz-head-top .plz-filters { margin-left: auto; }
.plz-prog { display: flex; align-items: center; gap: 10px; margin-top: 11px; }
.plz-prog-lab { flex: 0 0 auto; font-size: 13px; color: var(--plz-ink-2); }
.plz-prog-num { flex: 0 0 auto; font-size: 12.5px; font-weight: 800; color: var(--plz-blue); font-variant-numeric: tabular-nums; }
.plz-prog-track {
  flex: 1;
  height: 8px;
  border-radius: var(--r-pill);
  background: rgba(122, 148, 194, 0.24);
  box-shadow: inset 0 1px 2px rgba(52, 60, 102, 0.22);
  overflow: hidden;
}
.plz-prog-track i {
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--plz-blue-lite), #5f92d8);
  box-shadow: 0 0 10px rgba(130, 180, 240, 0.6);
  transition: width 0.5s cubic-bezier(0.3, 1, 0.4, 1);
}
/* 滑动提示是**给下面那排卡**的说明,所以贴着圆点放(离它描述的东西最近),不留在头卡里。
   2026-10-14:页底压深之后这句改成**页级浅色字**。它本来就落在页底上(不在玻璃卡里),
   而全站页级文字那一层本来就是白的(顶部夜色 + 底部导航带),原先用深蓝 --plz-ink 是这一页的特例:
   页底一深就掉到 2.6:1。描边也跟着从白晕改成暗晕(深底上白字靠暗晕托)。 */
.plz-hint {
  margin: 8px 0 0;
  text-align: center;
  font-size: 11.5px;
  color: var(--ink-3);        /* 和其他页的页脚小字同一档 */
}

/* ---------- 2) 筛选标签 ---------- */
.plz-filters {
  display: flex;
  gap: 6px;
  /* 西语/德语/俄语的筛选词比中英长(「Todos / Obtenido / Vinculado / Contratado」):
     320 宽下四个 chip 直接顶出页面 42px。**折行会把页头抬高 24px、顶破一屏预算**,
     所以改成这一排自己横向滚动(不产生纵向高度,也不让页面横向溢出)。 */
  flex-wrap: nowrap;
  justify-content: flex-start;
  max-width: 100%;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.plz-filters::-webkit-scrollbar { display: none; }
.plz-chip { flex: 0 0 auto; }
.plz-chip {
  padding: 5px 12px;
  border-radius: var(--r-pill);
  border: 1.5px solid rgba(255, 255, 255, 0.72);
  background: rgba(255, 255, 255, 0.815);
  color: var(--plz-ink-2);
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  backdrop-filter: blur(var(--g-thin-blur));
  transition: background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
.plz-chip.on {
  background: linear-gradient(180deg, #ffffff, #e8f1ff);
  border-color: var(--theme-select);
  color: var(--plz-blue);
  box-shadow: 0 0 16px rgba(255, 255, 255, 0.75);
}
/* 英文下这排筛选取窄一点的左右内边距。
   实测(375×553):`.plz-head-top` 一行只有 307px —— 中文那份 h2 66 + 筛选 230 = 306,差 1px 就换行;
   英文的 Bonded / Hired 比「结缘 / 雇佣」宽得多(筛选 252),整行 321 会把筛选挤到第二行,
   整页因此多 34px,在 553 高的屏上就多一条滚动条(破一屏预算)。
   这是**文字度量的差别**不是文案问题:每个 chip 省 6px 内边距,比把 Bonded 换成更短的词更保真。 */
html:not([lang^="zh"]) .plz-chip { padding: 5px 9px; }

/* ---------- 3) 3D 弧形轮播 ---------- */
/* ===== 牌堆(2026-10-26 重做,参照 demos/01-plateform-deck) =====
   为什么自绘条在**卡底**而不是卡头:卡面是用户画的整张美术图,13 张顶部各不相同,
   露卡头等于露一片纹理;压一条自绘的底条在卡下,叠放时露出的就是"下一条"的
   编号 + 名字 + 稀有度 —— 顶卡永远完整可见,认卡也不靠猜。
   层级:slot0 在最上(完整),slot1/2 依次下移压暗、只露自己的底条;`lifted` 是抬起态
   (顶卡上浮放大,下面两张后退虚化),对应 demo 的点击抬起。
   宽度按视口收(60vw/244px):这一屏还装着完成度、读数、三键与更多玩法。 */
.plz-deck {
  --dk-w: min(56vw, 226px);
  --dk-gap: 30px;
  --dk-head: 44px;
  position: relative;
  width: var(--dk-w);
  margin: 8px auto 0;
  height: calc(var(--dk-w) * 4 / 3 + var(--dk-head) + 2 * var(--dk-gap));
  /* 2026-10-27 手感(照 demos/01-plateform-deck):整副牌随指针倾斜、拖拽时跟手。
     三个变量由 bindDeckFx 的 rAF 写,这里只负责把它们套进一条 transform ——
     **位移/旋转只走 transform**,和 demo 同一条纪律。
     子卡自己的 slot 位移在 .dk-card 上,互不打架(父级 transform 复合到子级)。 */
  perspective: 1100px;
  transform: rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg)) translateX(var(--drag-x, 0px));
  transition: transform 0.18s ease-out;
}
/* 拖拽中:去掉过渡,否则整副牌会"追着手指慢半拍" */
.plz-deck.dragging { transition: none; }
/* 入场发牌:三张错峰弹出(from 状态压住 slot 位移,落地后回到各自位置) */
.plz-deck.deal .dk-card { animation: dk-deal 0.6s cubic-bezier(0.22, 1, 0.36, 1) backwards; }
.plz-deck.deal .dk-card[data-slot="1"] { animation-delay: 0.08s; }
.plz-deck.deal .dk-card[data-slot="2"] { animation-delay: 0.16s; }
@keyframes dk-deal {
  from { opacity: 0; transform: translateY(26px) scale(0.92) rotate(-2deg); }
}
.dk-card {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  border-radius: 18px;
  overflow: hidden;
  background: #fff;
  box-shadow: 0 12px 30px -18px rgba(20, 28, 52, 0.65), 0 0 0 1px rgba(255, 255, 255, 0.6);
  transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.3s, filter 0.3s;
  cursor: pointer;
}
.dk-card[data-slot="0"] { z-index: 30; transform: none; }
.dk-card[data-slot="1"] { z-index: 20; transform: translateY(var(--dk-gap)); opacity: 0.97; }
.dk-card[data-slot="2"] { z-index: 10; transform: translateY(calc(var(--dk-gap) * 2)); opacity: 0.92; }
.plz-deck.lifted .dk-card[data-slot="0"] { transform: translateY(-6px) scale(1.03); }
.plz-deck.lifted .dk-card[data-slot="1"] {
  opacity: 0.5;
  filter: blur(1.5px);
  transform: translateY(calc(var(--dk-gap) + 10px)) scale(0.95);
}
.plz-deck.lifted .dk-card[data-slot="2"] {
  opacity: 0.35;
  filter: blur(var(--g-thin-blur));
  transform: translateY(calc(var(--dk-gap) * 2 + 16px)) scale(0.9);
}
/* 卡面窗口统一 3:4(原图 0.72~0.87,统一裁一点点),这样叠放高度可算 */
.dk-face {
  aspect-ratio: 3 / 4;
  width: 100%;
  overflow: hidden;
  background: #eef2fb;
}
.dk-face .plz-card { margin: 0; border: 0; border-radius: 0; box-shadow: none; background: none; height: 100%; }
.dk-face .plz-card img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 牌堆里卡面只留**美术图本身**:卡名、四格信息、箴言、详情键都藏起来 ——
   底条已经写了编号/名字/稀有度,叠放时再露一遍就重复;抬起后进详情页看完整信息。
   这里用 !important:face 卡那套样式在后面定义,同权重时会反过来盖住本块(2026-10-26 踩过)。 */
.dk-face .pf-title-row,
.dk-face .pf-info,
.dk-face .pf-motto,
.dk-face .plz-info,
.dk-face .plz-plate,
.dk-face .plz-tier,
.dk-face .plz-origin,
.dk-face .plz-title,
.dk-face .plz-lock { display: none !important; }
/* 底条:叠放时唯一露出的那一条 */
.dk-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  height: var(--dk-head);
  padding: 0 12px;
  background: rgba(255, 255, 255, 0.94);
  border-top: 1px solid rgba(120, 138, 200, 0.18);
}
.dk-head .dk-no {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 11px;
  letter-spacing: 0.16em;
  color: var(--navy-3);
}
.dk-head .dk-name {
  font-size: 14.5px;
  font-weight: 700;
  color: var(--navy-1);
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dk-head .dk-rar {
  margin-left: auto;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: #a5761f;
}
.dk-head .dk-dots { display: inline-flex; gap: 3px; flex: 0 0 auto; }
.dk-head .dk-dots i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.6);
}
/* 读数区:卡面已经印了属性/系列/编号,这里只放卡面没有的(性格、能力、收容状态) */
.dk-readout { margin: 12px 18px 0; }
.dk-readout-top { display: flex; align-items: baseline; gap: 8px; }
.dk-ro-no {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 11px;
  letter-spacing: 0.16em;
  color: var(--navy-3);
}
.dk-ro-name { font-size: 17px; font-weight: 800; color: var(--navy-1); letter-spacing: 0.02em; }
.dk-ro-rar { font-size: 11px; font-weight: 700; color: #a5761f; letter-spacing: 0.12em; }
.dk-ro-state { margin-left: auto; font-size: 11.5px; color: var(--navy-3); }
.dk-ro-state.on { color: #2f8a5e; }
.dk-nature { margin: 4px 0 0; font-size: 12px; color: var(--navy-3); letter-spacing: 0.04em; }
.dk-abilities { display: flex; gap: 6px; margin-top: 6px; overflow-x: auto; scrollbar-width: none; }
.dk-abilities::-webkit-scrollbar { display: none; }
.dk-ctrl { display: flex; align-items: center; justify-content: center; gap: 14px; margin-top: 12px; }
.dk-btn {
  width: 38px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  border: 1px solid rgba(120, 138, 200, 0.28);
  background: rgba(255, 255, 255, 0.72);
  color: var(--navy-1);
  cursor: pointer;
}
.dk-btn.next svg { transform: rotate(180deg); }
.dk-btn .dk-redo { font-size: 15px; line-height: 1; transform: translateY(-1px); }
.dk-count {
  min-width: 54px;
  text-align: center;
  font-size: 12px;
  color: var(--navy-3);
  letter-spacing: 0.08em;
  font-variant-numeric: tabular-nums;
}

.plz-stage {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;             /* 卡牌在舞台里竖向居中(见 .screen-plaza 的一屏预算) */
  flex: 1 1 auto;
  min-height: 300px;
  margin-top: 14px;                    /* 头卡(说明)→ 卡牌区(主体):这里是整页第二重要的间隔 */
  padding: 12px 0 8px;
  border-radius: 24px;
  border: 1.5px solid rgba(255, 255, 255, 0.62);
  background: linear-gradient(180deg, rgba(210, 228, 252, 0.5), rgba(182, 206, 242, 0.34));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72), 0 22px 44px -30px var(--shadow);
  overflow: hidden;
}
/* 舞台里的弧形光晕(设计稿里那两道同心椭圆)。
   高度按**舞台高度**给(不是按宽度算出来的固定椭圆):舞台被拉伸来吃掉剩余高度时,
   光环跟着长高,那圈空出来的地方才不是干的。上限是别让它在大屏上拉成一根长条。 */
.plz-ring {
  position: absolute;
  left: 50%;
  top: 52%;
  width: 124%;
  height: 74%;
  max-height: 430px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.5);
  background: radial-gradient(52% 42% at 50% 46%, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0) 72%);
  pointer-events: none;
}
/* 星光粒子:稀疏几颗,缓慢闪(铺太密就是花布了) */
.plz-star {
  position: absolute;
  color: var(--gold-2);              /* 白昼主题:白点在浅底上看不见,走金色 */
  font-size: 9px;
  opacity: 0.8;
  text-shadow: 0 0 8px rgba(255, 250, 235, 0.95);
  pointer-events: none;
  animation: plz-twinkle 3.8s ease-in-out infinite;
}
.plz-star.s1 { left: 9%;  top: 15%; animation-delay: 0s; }
.plz-star.s2 { left: 86%; top: 23%; animation-delay: 0.9s; font-size: 7px; }
.plz-star.s3 { left: 15%; top: 72%; animation-delay: 1.7s; font-size: 7px; }
.plz-star.s4 { left: 78%; top: 64%; animation-delay: 2.4s; }
@keyframes plz-twinkle {
  0%, 100% { opacity: 0.75; transform: scale(1); }
  50% { opacity: 0.18; transform: scale(0.78); }
}

/* 视窗:这里**没有原生滚动了** —— 整条卡列的位移由 bindPlazaCarousel 每帧写 translate3d 决定。
   原生滚动跑在合成器线程、卡片姿态要等主线程的 scroll 回调,两个时钟对不齐就是卡帧的来源;
   现在位移和姿态在**同一帧**里由同一个数(pos)算出来,不需要谁等谁。
   卡列两端垫了克隆卡,所以第一张的左右两侧也一直有卡(见 plazaArcHtml)。 */
.plz-arc {
  position: relative;
  display: flex;
  align-items: stretch;        /* 卡片等高:底下的状态条靠 margin-top:auto 对齐 */
  gap: 12px;                   /* 与 JS 里的 stepPx()(= 卡宽 + 12)必须一致 */
  /* 高度按内容走(最高的那张卡),不跟着舞台长 —— 舞台那边是把它竖向居中 */
  overflow: hidden;
  perspective: 1100px;
  perspective-origin: 50% 46%;
  /* 竖向留给页面滚动,横向归轮播自己 —— 少了 pan-y,手指横滑时页面就滚不动了 */
  touch-action: pan-y;
  cursor: grab;                /* 手型光标就是那句「按住能拖」的提示 */
  user-select: none;
  -webkit-user-select: none;
}
.plz-arc::-webkit-scrollbar { display: none; }
.plz-arc.dragging { cursor: grabbing; }
/* 拖动过程中别选中卡面上的文字 / 别让图片被原生拖走(那会拖出一张「幽灵图」) */
.plz-arc.dragging,
.plz-arc.dragging * { user-select: none; -webkit-user-select: none; }
.plz-arc img { -webkit-user-drag: none; }

.plz-card {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;                /* 德语长词不能让 52% 的卡变宽:变宽会连带把轮播的中心算偏 */      /* 底下的状态条用 margin-top:auto 贴到卡底:暗影卡没有属性表,高度也得一致 */
  flex: 0 0 52%;               /* 视窗宽的 52%(JS 里的 cardW() 按同一个数算,改一处就得改另一处) */
  padding: 10px 10px 8px;
  border-radius: 20px;
  border: 1.5px solid rgba(255, 255, 255, 0.9);
  background: linear-gradient(180deg, #f5faff, #dce9fc 62%, #d0e0f8);
  box-shadow: 0 18px 36px -22px rgba(52, 60, 102, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.95);
  color: var(--plz-ink);
  transform-origin: 50% 50%;
  will-change: transform, opacity;
  transition: box-shadow 0.25s ease;
}
.plz-card.active {
  box-shadow:
    0 0 0 2px rgba(255, 255, 255, 0.92),
    0 0 34px rgba(178, 210, 255, 0.72),
    0 20px 40px -22px rgba(52, 60, 102, 0.6);
}
.plz-origin {
  position: absolute;
  top: 11px;
  left: 13px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 10px;
  font-weight: 700;
  color: var(--plz-ink-2);
}
.plz-origin svg { color: var(--plz-blue-lite); }
.plz-tier {
  position: absolute;
  top: 9px;
  right: 11px;
  z-index: 3;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(255, 250, 232, 0.92);
  background: linear-gradient(180deg, #fbeec4, #e6c785);
  color: #5a4018;
  font-size: 10.5px;
  font-weight: 700;
  box-shadow: 0 3px 8px -4px rgba(120, 85, 30, 0.6);
}

/* 立绘区:淡蓝水晶底 —— 柔光 + 几道切面高光 */
.plz-art {
  position: relative;
  flex: 0 0 auto;
  margin: 0;
  aspect-ratio: 1 / 0.76;
  border-radius: 14px;
  overflow: hidden;
  background:
    radial-gradient(62% 56% at 50% 46%, rgba(255, 255, 255, 0.96), rgba(208, 230, 255, 0.55) 62%, rgba(178, 208, 246, 0.34));
}
.plz-art::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(114deg, transparent 33%, rgba(255, 255, 255, 0.68) 34.2%, transparent 35.4%),
    linear-gradient(63deg, transparent 61%, rgba(255, 255, 255, 0.55) 62%, transparent 63.4%),
    linear-gradient(158deg, transparent 73%, rgba(214, 236, 255, 0.8) 74%, transparent 75.4%);
  pointer-events: none;
}
.plz-halo {
  position: absolute;
  left: 50%;
  top: 52%;
  width: 66%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 253, 244, 0.95), rgba(216, 234, 255, 0.5) 56%, rgba(200, 226, 255, 0) 73%);
  pointer-events: none;
}
.plz-art img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 2% 8% 0;
  filter: drop-shadow(0 10px 18px rgba(90, 130, 190, 0.32));
}
.plz-spark {
  position: absolute;
  color: var(--gold-2);
  font-size: 9px;
  opacity: 0.85;
  text-shadow: 0 0 8px rgba(255, 250, 235, 0.95);
  animation: plz-twinkle 3.2s ease-in-out infinite;
}
.plz-spark.p1 { left: 14%; top: 22%; }
.plz-spark.p2 { left: 82%; top: 34%; animation-delay: 1.1s; font-size: 7px; }
.plz-spark.p3 { left: 24%; top: 70%; animation-delay: 2s; font-size: 7px; }

/* 名字:蓝渐变字 + 白描边观感(靠 drop-shadow 叠出来的描边)。
   上边距要留够,别顶到两角那对徽章上。 */
.plz-title {
  margin: 22px 0 6px;
  text-align: center;
  font-size: 21px;
  font-weight: 800;
  letter-spacing: 0.06em;
  background: linear-gradient(180deg, #7fa8e0, #3f6bb0);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 1px 0 rgba(255, 255, 255, 0.95)) drop-shadow(0 6px 10px rgba(90, 130, 190, 0.35));
}

.plz-info {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3px 10px;
  margin: 8px 0 8px;
  padding: 6px 2px 0;
  border-top: 1px solid rgba(150, 180, 225, 0.36);
}
/* 「属性:光 / 系列:云漫纪」这种短词组一律不许折行 —— 折了就把 2×2 的格子撑成四行 */
.plz-info > div { display: flex; align-items: baseline; gap: 5px; min-width: 0; font-size: 10.5px; white-space: nowrap; }
.plz-info dt { flex: 0 0 auto; color: var(--plz-ink-2); }
.plz-info dd {
  flex: 0 1 auto;
  min-width: 0;
  margin: 0;
  font-weight: 700;
  color: var(--plz-blue);
  overflow: hidden;
  text-overflow: ellipsis;
}

.plz-plate {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;                   /* 贴到卡底(暗影卡就靠它对齐高度) */
  padding: 6px 9px;
  border-radius: 13px;
  border: 1px solid rgba(255, 255, 255, 0.9);
  background: rgba(255, 255, 255, 0.74);
  box-shadow: inset 0 1px 0 #fff;
}
.plz-name { flex: 1; min-width: 0; font-size: 13px; font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.plz-state { flex: 0 0 auto; font-size: 11px; color: var(--plz-ink-2); }
.plz-state.on { color: var(--plz-blue); font-weight: 700; }
.plz-detail {
  flex: 0 0 auto;
  padding: 4px 12px;
  border-radius: var(--r-pill);
  border: 1.5px solid rgba(150, 180, 225, 0.6);
  background: linear-gradient(180deg, #ffffff, #e8f1ff);
  color: var(--plz-blue);
  font-size: 11.5px;
  font-weight: 700;
  cursor: pointer;
}

/* ---------- 卡面牌(2026-10-16):整张设计图当卡,卡上的字由我们画 ----------
   尺寸与文字坐标的关系:素材的宽高比**逐张不同**(0.72~0.87),所以卡盒的高度交给 img
   自己撑(`width:100%;height:auto`),叠加层一律按**卡盒的百分比**定位 ——
   `--pf-by`/`--pf-bh` 是底下信息带的上下界、`--pf-ty` 是卡名那条带子的中线(都由
   tools/build-card-faces.py 量出来、经名册下发)。于是 13 张卡共用这一段样式,
   只有那几个数值不同 —— 不然就得给每张卡写一段 CSS。
   素材里原先印着的卡名与信息板文字**已经从像素上抹掉**,这里的字是唯一的一份,切语言跟着变。 */
.plz-card.face {
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
  border-radius: 0;
  justify-content: center;              /* 行高由最高的那张卡定,这一张在整格里竖向居中 */
}
.plz-card.face .pf-frame {
  position: relative;
  width: 100%;
  /* 圆角只用来收掉素材四边那圈虚化底(卡自己的圆角在素材里面) */
  border-radius: 6% / 4.6%;
  overflow: hidden;
  box-shadow: 0 16px 30px -20px rgba(38, 44, 78, 0.58);
  transition: box-shadow 0.25s ease;
}
.plz-card.face.active .pf-frame {
  box-shadow:
    0 0 30px rgba(178, 210, 255, 0.8),
    0 18px 34px -20px rgba(38, 44, 78, 0.62);
}
.plz-card.face .pf-img { display: block; width: 100%; height: auto; }

/* 卡名那一行:名字 + 一枚「详情」引导键(2026-10-16 用户点名:卡片上要有详情按钮做引导)。
   两件东西排在同一行里居中 —— 分开放就会撞(卡面顶上那条带子只有卡名那么宽的位置)。
   名字自己可以省略号收口,按钮永远在。 */
.plz-card.face .pf-title-row {
  position: absolute;
  left: 5%;
  right: 5%;
  top: calc(var(--pf-ty) * 100%);
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  min-width: 0;
}
/* 落款:卡片底部那行小字(现在只有雇佣精灵「答案之书」有 —— 名册里带 `motto` 的形象)。
   位置由卡面素材量出来(`--pf-mo`,按卡高归一),一行居中;浅底/深底各一套字色。 */
.plz-card.face .pf-motto {
  position: absolute;
  left: 8%;
  right: 8%;
  top: calc(var(--pf-mo) * 100%);
  transform: translateY(-50%);
  text-align: center;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.12em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.plz-card.face.dk .pf-motto { color: rgba(240, 226, 198, 0.86); text-shadow: 0 1px 3px rgba(14, 18, 40, 0.7); }
.plz-card.face.lt .pf-motto { color: #6b5a45; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.85); }
.plz-card.face .pf-title {
  margin: 0;
  min-width: 0;
  font-size: 19px;
  font-weight: 800;
  letter-spacing: 0.05em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.plz-card.face .pf-detail {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 1px;
  padding: 3px 6px 3px 9px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(255, 255, 255, 0.9);
  background: rgba(255, 255, 255, 0.88);
  color: var(--plz-blue);
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  /* 阴影要够深:浅底卡面(奶油/米白那几张)上,白药丸只靠描边会糊在底色里 */
  box-shadow: 0 2px 8px -3px rgba(30, 38, 74, 0.55);
}
/* 详情键**每张卡长一样**(2026-10-16 用户点名):原先深底卡面(.dk)另给了一套深色药丸 +
   白字,同一排里两张卡看着就不是一个东西了 —— 现在 13 张共用上面那一套(白药丸 + 蓝字),
   深底/浅底都靠它自己的描边与阴影立住。 */
.plz-card.face .pf-detail-go { display: inline-flex; opacity: 0.75; }
.plz-card.face.lt .pf-title { color: #4a3f33; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.9), 0 4px 10px rgba(255, 255, 255, 0.7); }
.plz-card.face.dk .pf-title { color: #f2f7ff; text-shadow: 0 1px 4px rgba(16, 22, 46, 0.75), 0 0 16px rgba(150, 190, 255, 0.55); }

/* 信息带:四格 = 属性 / 系列 / 稀有度 / 编号。位置与高度都由名册下发的那两个数定 */
.plz-card.face .pf-info {
  position: absolute;
  left: 6.5%;
  right: 6.5%;
  /* 贴住带子的**下沿**,高度交给内容 —— 这条带子只有卡高的 9.5%(实测 390 宽下 20~23px),
     而两行字最少也要 22px 上下,写死 `height` 的话第二行(稀有度 / 编号)就被卡底裁掉
     (2026-10-16 用户报「属性那些内容都超出卡片的底部了」)。改成 bottom 锚定 + 高度自适应:
     放得下就正好落在设计图那条带子里,放不下就**往上长**(压到画面上,而不是漏到卡外)。 */
  bottom: calc((1 - var(--pf-by) - var(--pf-bh)) * 100%);
  min-height: calc(var(--pf-bh) * 100%);
  margin: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 2px 4px;          /* 行间距用 px:容器高是 auto 的,百分比的 row-gap 会退化成 0 */
  align-items: stretch;
}
/* 一格 = 「标签 + 值」。**板的宽度是死的**(它在设计图里就那么宽,手机上一格只有 60 来个像素),
   所以标签要比值小一档(8px / 10px):实测 390×844 两者同号时「系列 云端梦境」差 3px 被省略成
   「云端…」—— 系列名正好是每张卡最长的那一格。 */
.plz-card.face .pf-info > div {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  padding: 0 5px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.66);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.92);
  font-size: 10px;
  line-height: 1.1;      /* 行高收一档:两行才塞得进那条带子(见上面 .pf-info 的说明) */
  white-space: nowrap;
}
.plz-card.face.dk .pf-info > div { background: rgba(26, 32, 58, 0.46); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22); }
.plz-card.face.lt .pf-info dt { color: #6d6153; }
.plz-card.face.lt .pf-info dd { color: #382f26; }
.plz-card.face.dk .pf-info dt { color: rgba(230, 238, 255, 0.74); }
.plz-card.face.dk .pf-info dd { color: #ffffff; }
.plz-card.face .pf-info dt { flex: 0 0 auto; font-size: 8px; }
.plz-card.face .pf-info dd { flex: 0 1 auto; min-width: 0; margin: 0; font-weight: 800; overflow: hidden; text-overflow: ellipsis; }
.plz-card.face .pf-info .pf-none {
  grid-column: 1 / -1;
  grid-row: 1 / -1;
  justify-content: center;
  background: rgba(26, 32, 58, 0.5);
  color: rgba(255, 255, 255, 0.8);
  font-size: 11px;
  letter-spacing: 0.08em;
}

/* 没收容:同一张卡面**压暗压灰**(用户口径,不再是另一套暗卡版式)+ 一层冷雾,立绘认不出来 */
.plz-card.face.dark .pf-img { filter: brightness(0.4) saturate(0.32); }
.plz-card.face .pf-veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(30, 34, 58, 0.5), rgba(18, 22, 40, 0.66));
}
.plz-card.face.dark .pf-title { color: rgba(255, 255, 255, 0.82); }

/* 卡上**没有**可见的详情键 / 徽章:设计图顶上那条带子里只有卡名,再塞一颗圆的进去
   就和卡名撞在一起了(实测 390×844:Starter 那枚药丸压在 Flufflight 上)。
   详情改走「正中间那张再点一下」(见 app.js 的卡牌点击分支);
   键盘/读屏用这颗视觉隐藏、但**真的可聚焦**的按钮。 */
.plz-card.face .pf-lock {
  position: absolute;
  top: 4.5%;
  right: 5%;
  z-index: 3;
  color: rgba(255, 255, 255, 0.82);
}
/* 英文名比中文长得多(Nightcrystal / Star-Moon Dream 那一档),窄卡上收一档字号;
   信息板同理:板的宽度只够放**一个**字符串,英文的 Element / Series / Rarity 加上值两个都放不下
   (实测 390×844:属性和系列被省略成「H…」「Clo…」)。英文下只留值 ——
   哪一格是什么本来就由位置说得清,和卡面设计图「一格一项」的排法一致。 */
html:not([lang^="zh"]) .plz-card.face .pf-title { font-size: 15px; }
html:not([lang^="zh"]) .plz-card.face .pf-detail { padding: 3px 5px 3px 8px; font-size: 10px; }
html:not([lang^="zh"]) .plz-card.face .pf-info dt { display: none; }
html:not([lang^="zh"]) .plz-card.face .pf-info > div { padding: 0 6px; font-size: 9.5px; }

/* 收容动画里那张卡 = 画廊卡面(2026-10-20 用户点名「收容动画出现的卡片使用相应的精灵
   画廊上的卡片」)。卡面样式整套照用 `.plz-card.face`(字的位置、浅底/深底、英文收字号
   都在那边,只有一份),这里只补**场地差异**:
   · 轮播里那张卡是横向一行里的 flex 子项(basis 52%),这里站在竖向 flex 列里 —— 别继承那个 basis;
   · `.cap-card` 原来那层玻璃(背景/描边/阴影)已被 `.plz-card.face` 置空,但 `backdrop-filter`
     不在它的重置清单里,不关掉的话卡后面会多出一块模糊;
   · 卡片阴影交给 `.pf-frame`(素材自带的卡框),外层只留落地那圈淡蓝发光。 */
.cap-card.plz-card {
  flex: 0 0 auto;
  width: 100%;
  margin: 0;
  backdrop-filter: none;
}
/* 外发光跟着卡面的圆角走(卡自己的圆角在素材里,盒子的圆角只是收掉四边那圈虚化底) */
.cap-card.face .cap-card-halo { border-radius: 7% / 5.5%; }

/* 暗影占位卡:卡面还没设计的成员。黑色剪影 + 一把锁,别的都不画。 */
.plz-card.dark {
  border-color: rgba(255, 255, 255, 0.26);
  background: linear-gradient(180deg, #6e7999, #4c5578 58%, #3d4462);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 16px 32px -22px rgba(30, 36, 70, 0.7);
}
.plz-card.dark .plz-art {
  margin-top: 26px;
  background: linear-gradient(180deg, #333b58, #222840);
}
.plz-card.dark .plz-art::before { opacity: 0.16; }
.plz-card.dark .plz-art img.silh {
  filter: brightness(0) saturate(0);
  opacity: 0.92;
  padding: 8% 12% 0;
}
/* 占位卡(这一组还没做形象的位置):一样是暗卡,但立绘是**通用形象**、比上面那张更淡 */
.plz-card.ph-card .plz-art img.silh { opacity: 0.42; }
.plz-card.dark .plz-plate {
  border-color: rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.12);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
}
.plz-card.dark .plz-name { color: rgba(255, 255, 255, 0.78); }
.plz-card.dark .plz-state { color: rgba(255, 255, 255, 0.62); }
.plz-lock {
  position: absolute;
  top: 11px;
  right: 12px;
  z-index: 3;
  color: rgba(255, 255, 255, 0.7);
}

/* 通用:把立绘压成剪影(图鉴里没点亮的格子 / 弹层里的暗影) */
img.silh { filter: brightness(0) saturate(0); opacity: 0.34; }

.plz-arc-empty {
  display: grid;
  place-items: center;
  gap: 10px;
  padding: 34px 34px 40px;
  text-align: center;
  color: var(--plz-ink-2);
}
.plz-arc-empty svg { color: rgba(63, 107, 176, 0.6); }
.plz-arc-empty p { margin: 0; font-size: 12.5px; line-height: 1.75; }

/* ---------- 4) 圆点 + 箭头 ---------- */
.plz-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 10px;
}
.plz-dots { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 6px; }
.plz-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.55);
  box-shadow: inset 0 0 0 1px rgba(110, 145, 195, 0.4);
  transition: width 0.25s ease, background 0.25s ease;
}
.plz-dot.on {
  width: 18px;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, #ffffff, #cfe2ff);
  box-shadow: 0 0 10px rgba(255, 255, 255, 0.85);
}
.plz-arrow {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, 0.78);
  background: rgba(255, 255, 255, 0.77);
  color: var(--plz-ink);
  cursor: pointer;
  backdrop-filter: blur(var(--g-thin-blur));
  transition: background 0.18s ease, box-shadow 0.18s ease;
}
.plz-arrow svg { transform: rotate(180deg); }   /* 图标集里的 back 是「左」,转过来当「右」 */

/* 顶栏右上角的「图鉴」胶囊 */
.plz-codex-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: 0 0 auto;
  margin-top: 3px;
  padding: 6px 14px;
  border-radius: var(--r-pill);
  border: 1.5px solid rgba(255, 255, 255, 0.86);
  background: rgba(255, 255, 255, 0.89);
  color: var(--plz-ink);
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  backdrop-filter: blur(var(--g-reg-blur));
  box-shadow: 0 10px 22px -16px var(--shadow-soft);
}
.plz-codex-pill svg { color: var(--gold-ink); }

/* ---------- 卡牌详情弹层 ---------- */
.sd { display: grid; gap: 10px; text-align: center; color: var(--plz-ink); }
.sd-art {
  position: relative;
  display: grid;
  place-items: center;
  margin: 0 auto;
  width: 62%;
  aspect-ratio: 1;
  border-radius: 18px;
  background: radial-gradient(60% 56% at 50% 46%, rgba(255, 255, 255, 0.95), rgba(208, 230, 255, 0.6) 66%, rgba(178, 208, 246, 0.4));
  box-shadow: inset 0 1px 0 #fff, 0 14px 28px -20px var(--shadow-soft);
}
.sd-art img { width: 82%; height: 82%; object-fit: contain; }
.sd-lock { position: absolute; top: 8px; right: 10px; color: rgba(79, 107, 147, 0.6); }
.sd-name { margin: 0; font-size: 19px; font-weight: 800; }
.sd-sub { margin: -4px 0 0; font-size: 12px; color: var(--plz-ink-2); }
.sd-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin: 2px 0 4px;
}
.sd-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 11px;
  border-radius: 12px;
  border: 1.5px solid var(--plz-line);
  background: rgba(255, 255, 255, 0.6);
  font-size: 12px;
}
.sd-row span { color: var(--plz-ink-2); }
.sd-row b { color: var(--plz-blue); }
.sd-note { margin: 0; font-size: 12px; line-height: 1.7; color: var(--plz-ink-2); }
.sd .btn-line[disabled] { opacity: 0.6; cursor: default; }

/* 矮屏(720 面板 / 横屏)把画廊压一档,免得一进页面就只剩半个舞台 */
@media (max-height: 760px) {
  .plz-card { flex-basis: 50%; }
  .plz-head { padding: 12px 14px 13px; }
  .plz-prog { margin-top: 9px; }
  .plz-stage { margin-top: 11px; }
  .plz-art { aspect-ratio: 1 / 0.72; }
  .plz-title { font-size: 19px; margin: 21px 0 6px; }
  .plz-plate { padding: 6px 9px; }
}

/* 更矮一档(≤700,手机带工具栏 / 667 那种 iPhone SE):
   这一页的构成是「顶栏 + 画廊头卡 + 弧形轮播 + 圆点 + 提示」,而**卡片高度跟着宽度走**
   (卡宽 52% 的视窗宽,卡高 ≈ 卡宽 × 1.7)。矮屏上能吃的只有两块:舞台上下那圈余量、
   卡片自己的纵向留白。这里两样一起收:舞台下限 300→258、画布比例 0.72→0.6、标题上边距 21→14。
   **卡片宽度一格没动** —— 左右仍看得见邻卡,而轮播那套按卡宽算的位移/姿态
   (app.js 的 cardW/stepPx)完全不受影响。 */
@media (max-height: 700px) {
  .plz-card { flex-basis: 46%; }
  .screen-plaza > .topbar { padding: 10px 0 4px; }
  .screen-plaza > .topbar h1 { font-size: 22px; }
  .plz-head { margin-top: 8px; padding: 10px 13px 11px; }
  .plz-h2 { font-size: 16px; }
  .plz-prog { margin-top: 7px; }
  .plz-stage { margin-top: 8px; padding: 8px 0 6px; min-height: 258px; }
  .plz-art { aspect-ratio: 1 / 0.6; }
  .plz-title { font-size: 18px; margin: 14px 0 5px; }
  .plz-info { margin: 6px 0; padding-top: 5px; }
  .plz-plate { padding: 5px 8px; }
  .plz-foot { margin-top: 7px; }
  .plz-hint { margin-top: 5px; font-size: 11px; }
}

/* 更矮(≤640,小屏安卓):舞台再让一档。 */
@media (max-height: 640px) {
  .plz-card { flex-basis: 43%; }
  .screen-plaza > .topbar { padding: 8px 0 2px; }
  .plz-head { margin-top: 6px; padding: 9px 12px 10px; }
  .plz-stage { margin-top: 6px; min-height: 238px; }
  .plz-art { aspect-ratio: 1 / 0.56; }
  .plz-title { margin: 12px 0 4px; }
}

/* 更矮(≤600,老款 320×568、iPhone SE 带工具栏 553):画布与卡面留白再让一档。
   到这一档仍然**不删任何一块** —— 头卡、轮播、圆点、提示全在,只是卡片更扁、间距更紧。 */
@media (max-height: 600px) {
  .plz-card { flex-basis: 40%; }
  .screen-plaza > .topbar { padding: 4px 0 0; }
  .screen-plaza > .topbar h1 { font-size: 20px; }
  .plz-head { margin-top: 5px; padding: 8px 12px 9px; }
  .plz-stage { margin-top: 5px; min-height: 200px; }
  .plz-art { aspect-ratio: 1 / 0.44; }
  .plz-title { font-size: 17px; margin: 8px 0 3px; }
  .plz-info { margin: 5px 0; padding-top: 4px; }
  .plz-plate { padding: 4px 7px; }
  .plz-foot { margin-top: 4px; }
  .plz-hint { margin-top: 1px; }
}

/* 宽而矮的窗口(1280×720 那块桌面面板):内容和手机一样高,但 app 列宽到 460,
   卡片按比例更高 —— 所以这一档得**单独收**,不能只靠 ≤700 那套手机断点。
   带 min-width:520 是为了不误伤 700~760 档的手机(iPhone 8 Plus 736 不缺血,
   卡片该照原尺寸显示)。 */
@media (max-height: 780px) and (min-width: 520px) {
  .plz-card { flex-basis: 44%; }
  .plz-head { margin-top: 8px; padding: 11px 14px 12px; }
  .plz-prog { margin-top: 8px; }
  .plz-stage { margin-top: 9px; padding: 8px 0 6px; min-height: 264px; }
  .plz-art { aspect-ratio: 1 / 0.58; }
  .plz-title { font-size: 19px; margin: 16px 0 5px; }
  .plz-foot { margin-top: 7px; }
  .plz-hint { margin-top: 5px; }
}

/* ---------- 精灵图鉴(广场右上角「图鉴」进入) ----------
   广场页的轮播是「一张张卡片」的视角,这一页是「一览」的视角:
   完成度 + 两棵可折叠的分组树(每棵 4 列格子)。格子点亮过显立绘,没点亮显暗格 + 锁 + ???。 */
.screen-codex { padding-bottom: 10px; }

.progress-card {
  margin-top: 14px;
  padding: 15px 17px;
  border-radius: 22px;
  border: 1.5px solid var(--plz-line);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.92), rgba(226, 239, 255, 0.8));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--plz-ink);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.92), 0 16px 34px -24px var(--shadow-soft);
}
.progress-card .pr-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 14.5px;
  color: var(--plz-ink-2);
}
.progress-card .pr-top b { font-size: 17px; color: var(--plz-blue); font-variant-numeric: tabular-nums; }
.progress-card .pr-track {
  margin: 10px 0 7px;
  height: 10px;
  border-radius: var(--r-pill);
  background: rgba(140, 150, 220, 0.26);
  box-shadow: inset 0 1px 3px rgba(52, 60, 102, 0.24);
  overflow: hidden;
}
.progress-card .pr-track i {
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--plz-blue-lite), var(--theme-accent-deep));
  box-shadow: 0 0 12px rgba(130, 180, 240, 0.6);
  transition: width 0.5s cubic-bezier(0.3, 1, 0.4, 1);
}
.progress-card .pr-foot { font-size: 12px; color: var(--plz-ink-2); }

/* 分组(可折叠) */
.tree { margin-top: 14px; display: grid; gap: 12px; }
.tree-group {
  border-radius: var(--r-lg);
  border: 1.5px solid var(--plz-line);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.92), rgba(226, 239, 255, 0.785));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--plz-ink);
  overflow: hidden;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 16px 34px -24px var(--shadow-soft);
}
.tree-head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 14px 16px;
  border: 0;
  background: none;
  color: var(--plz-ink);
  cursor: pointer;
  text-align: left;
}
.tree-head .th-title { flex: 1; display: flex; align-items: baseline; gap: 7px; font-size: 15.5px; font-weight: 800; }
.tree-head .th-meta { font-size: 12.5px; color: var(--plz-ink-2); font-variant-numeric: tabular-nums; }
.tree-head .th-pct { font-size: 13px; color: var(--plz-blue); font-variant-numeric: tabular-nums; }
.tree-head .th-caret {
  width: 9px; height: 9px;
  border-right: 1.6px solid var(--plz-ink-2);
  border-bottom: 1.6px solid var(--plz-ink-2);
  transform: rotate(45deg);
  transition: transform 0.22s ease;
  margin-left: 2px;
}
.tree-group.open .th-caret { transform: rotate(-135deg); }
/* 折叠**故意不加 transition**:grid-template-rows 的过渡要走主线程的动画时钟,
   而被节流的面板(见文件顶部说明)里那个时钟不走 —— 实测摘掉 .open 之后行高仍停在 1fr,
   树根本收不起来。收起是功能,不是装饰:宁可少一个过渡,也不能在那种环境里点了没反应。 */
.tree-body { display: grid; grid-template-rows: 0fr; }
.tree-group.open .tree-body { grid-template-rows: 1fr; }
.tree-body > * { overflow: hidden; }
.tree-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 9px;
  padding: 2px 14px 16px;
}

/* 矮屏上把格子排成 5 列:**结缘精灵 2026-10-16 加到 13 只之后,4 列要摆 4 行**
   (12 只时刚好 3 行)—— 实测 375×553 多出 44px、390×844 多出 9px、1280×720 多出 79px,
   六档手机 + 那块面板全部破一屏预算。多开一列,13 格仍是 3 行就装得下。
   格子里的立绘是 contain 的,格子窄一点只是立绘小一点(60 来像素宽也放得下名字和「已点亮」)。
   (规则必须写在上面这条**之后**:同权重下后写的才生效,放在基础规则前面的媒体查询会被它盖掉。) */
@media (max-height: 900px) {
  .tree-grid { grid-template-columns: repeat(5, 1fr); }
}

/* 图鉴格:点亮过的用立绘,没点亮是暗格 + 锁 */
.point {
  position: relative;
  border-radius: 15px;
  border: 1.5px solid var(--plz-line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.86), rgba(230, 240, 255, 0.6));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.92);
  overflow: hidden;
}
.point .pt-thumb { display: block; width: 100%; aspect-ratio: 1; background: rgba(255, 255, 255, 0.52); }
.point.lit .pt-thumb img { width: 100%; height: 100%; object-fit: contain; padding: 4px 4px 12px; }
/* 没点亮的格(包括**还没做形象**的位置)—— 统一用**通用形象**(`.ph`)压成很淡的剪影 + 锁 + `???`:
   用户口径 2026-10-13 是不露任何形象自己的剪影(免得提前露形),所以这里没有 `img.silh`。
   注意别拿 `.empty` 当新组件的类名:全局空态组件 `.empty` 带 30px/20px 内边距,会把格子里的图挤成一小点(踩过)。 */
.point:not(.lit) .pt-thumb img { width: 100%; height: 100%; object-fit: contain; padding: 6px 6px 16px; }
.point .pt-thumb img.ph { filter: brightness(0) saturate(0); opacity: 0.24; }
.point .pt-name {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 8px 4px 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: center;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--plz-ink);
  background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.92));
}
/* 「已点亮」角标 2026-10-16 已删(用户口径:它压在立绘上把精灵挡住了,格子上只留精灵)——
   这条 `.point .pt-badge` 的样式一并撤掉,别留着当死代码。 */
.point:not(.lit) {
  border-color: rgba(255, 255, 255, 0.5);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.36), rgba(255, 255, 255, 0.2));
  box-shadow: none;
}
.point:not(.lit) .pt-thumb { background: rgba(255, 255, 255, 0.34); }
.point .pt-lock {
  position: absolute;
  top: 6px; right: 6px;
  color: rgba(79, 107, 147, 0.55);
}

/* 图鉴的矮屏分档。这一页的高度几乎全部来自格子(4 列 × 每格 = 一格宽的正方形 + 12 格 = 3 行),
   所以矮屏上**先收格子的边距与行间**,再收标题、完成度卡、脚注的留白;
   「默认只展开有收集的那棵」交给 app.js 的 treeDefaultOpen(它才是让整页回到一屏的那一刀)。
   断点跟全站一致(700 / 640):667 那种 iPhone SE 落在 ≤700 里,不会掉进档间缝。 */
@media (max-height: 700px) {
  .screen-codex > .topbar { padding: 12px 0 4px; }
  .screen-codex > .topbar h1 { font-size: 21px; }
  .progress-card { margin-top: 10px; padding: 12px 14px; }
  .progress-card .pr-track { margin: 8px 0 6px; height: 9px; }
  .tree { margin-top: 10px; gap: 10px; }
  .tree-head { padding: 11px 14px; }
  .tree-head .th-title { font-size: 15px; }
  .tree-grid { gap: 8px; padding: 2px 12px 12px; }
  /* 格子压成略扁的:三行格子是这一页最大的一块(≈210px),正方形改扁是唯一
     能一次吃掉几十像素、又不删内容的杠杆(素材 object-fit:contain,不会变形)。 */
  .point .pt-thumb { aspect-ratio: 1 / 0.86; }
  .screen-codex .foot-note { margin-top: 12px; font-size: 12px; }
}

/* 更矮(≤680:640 小屏安卓与 664 那一档——iPhone 带工具栏、安卓 360×640):
   再收一档,才够装下「完成度 + 一棵 3 行的树 + 另一棵的标题 + 脚注」。
   断点取 680 而不是 640:664(浏览器工具栏还开着)正好落在 640~700 的档间缝里,
   实测那里还差 5px。 */
@media (max-height: 680px) {
  .screen-codex > .topbar { padding: 8px 0 2px; }
  .progress-card { margin-top: 8px; padding: 10px 13px; }
  .progress-card .pr-foot { font-size: 11.5px; }
  .tree { margin-top: 8px; gap: 8px; }
  .tree-head { padding: 10px 13px; }
  .tree-grid { gap: 7px; padding: 2px 10px 10px; }
  .point .pt-thumb { aspect-ratio: 1 / 0.78; }
  .screen-codex .foot-note { margin-top: 10px; font-size: 11.5px; line-height: 1.6; }
}

/* 加一档 ≤600(老款 320×568、iPhone SE 带工具栏 553):
   把格子、标题、脚注的留白一起再收一轮 —— 到这一档仍**不删任何内容**,
   只是格子更扁一点(素材 object-fit:contain,不会变形)。 */
@media (max-height: 600px) {
  .screen-codex > .topbar { padding: 4px 0 0; }
  .progress-card { margin-top: 5px; padding: 7px 11px; }
  .progress-card .pr-track { margin: 5px 0 4px; height: 8px; }
  .tree { margin-top: 5px; gap: 5px; }
  .tree-head { padding: 6px 12px; }
  .tree-head .th-title { font-size: 14.5px; }
  .tree-grid { gap: 5px; padding: 2px 8px 6px; }
  .point .pt-thumb { aspect-ratio: 1 / 0.66; }
  .screen-codex .foot-note { margin-top: 4px; font-size: 11px; }
}

/* 宽而矮的窗口(1280×720 那块桌面面板):app 列宽到 460、格子跟着变宽,
   三行格子按 1:1 排下来就是 300px —— 面板上装不下的正是这一块。
   把格子压成**略扁**的(素材是 object-fit:contain,不会被切),三名一行仍是三名一行。
   手机(≤520 宽)不吃这一档:它们的格子本来就窄,该保持正方形。 */
@media (max-height: 780px) and (min-width: 520px) {
  .screen-codex > .topbar { padding: 14px 0 4px; }
  .progress-card { margin-top: 10px; padding: 12px 14px; }
  .tree { margin-top: 10px; gap: 10px; }
  .tree-head { padding: 11px 14px; }
  .tree-grid { gap: 8px; padding: 2px 12px 12px; }
  .point .pt-thumb { aspect-ratio: 1 / 0.78; }
  .screen-codex .foot-note { margin-top: 12px; font-size: 12px; }
}

/* ---------- 水晶页 ---------- */
.screen-crystal {
  display: flex;
  flex-direction: column;
  /* 定高,不是 min-height。
     min-height 下 flex 容器会跟着内容一起长高,子项的 flex-shrink 就永远不生效,
     面板会一路顶到自己的上限再把输入栏挤出屏幕(2026-10-13 的原始 bug)。定高之后 flex 才会真的去分配空间。
     高度取 --app-h(可视高单源):旧内核不认 dvh 时,原来那条 100vh = 大视口,会把输入栏顶到屏幕外。
     让位次序(压力从上往下落,每块都有地板,详见各块注释):
       对话面板(地板 96px)→ 立绘段(地板 132px,立绘**等比缩小**而不是被裁)→ 其余块不动。
     连地板都装不下时(极矮屏/系统字号放大),app.js 的 relaxScreen() 把这屏松成
     自然高度 + 整页滚动:宁可滚,不能挤。 */
  height: calc(var(--app-h) - var(--app-pad-t) - var(--app-pad-b));
  margin-bottom: calc(-1 * var(--app-pad-b));
  padding-bottom: 10px;
}
/* 一屏的高度预算:顺序比数值重要 ——
   顶栏 / 身份条 / 情感交流卡 / 输入栏一律 flex:0 0 auto,不许被压;
   只有「立绘 + 气泡」和对话面板参与分配,且让的先后有讲究:
     · 面板 flex-basis:0 → 它的"理想高度"是 0,只负责吃剩下的空间,不参与"被压";
     · 立绘段 flex-basis:auto → 保留自然高度,只在真的不够时才被裁。
   这样空间不足时的顺序是:先压面板(它本来就是滚动区),还不够再压立绘段,
   输入栏因为 flex:0 0 auto 永远不动 —— 「输入栏紧贴底导上方」成了结构性保证。
   之前是反的:面板封着 46vh 上限,压力全落到最下面的输入栏身上,它就被推出屏幕了。 */
.screen-crystal > .topbar,
.screen-crystal > .crystal-meta,
.screen-crystal > .emotion-card,
.screen-crystal > .composer { flex: 0 0 auto; }
.screen-crystal > .chat-stage {
  flex: 0 1 auto;
  /* 地板 132px:立绘还有得看的下限。到这里就不再缩,立绘改在内部等比缩小(.chat-sprite 的 max-height)。
     以前这里是 min-height:0 —— 整段可以被压成 0px(实测真发生过:"精灵看不见了")。
     地板之下还装不下时由 relaxScreen 整页滚,而不是把这段压没。 */
  min-height: 132px;
  /* 立绘那行可被压缩、气泡行按内容占:被压到地板时是"立绘变小",气泡不被裁 */
  grid-template-rows: minmax(0, 1fr) auto;
  overflow: hidden;
}
.screen-crystal .topbar { padding: 12px 0 0; }
.screen-crystal .topbar h1 { font-size: 26px; }
/* 身份 + 额度:一条状态行,名字在左、额度在右(空出来的右半边正好用上)。
   align-items:center 让两枚 chip 等高居中(默认 stretch 会让它们各自拉满行高)。 */
.screen-crystal .crystal-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: nowrap;          /* 同「我的」:名字再长也不换行,只把名字那枚收窄 */
  margin: 8px 0 2px;
}
.crystal-meta .chip-static { flex: 0 0 auto; }
.crystal-meta .chip-static.chip-name { flex: 0 1 auto; min-width: 0; }
/* 立绘段**必须给浮动留出上方的余地**。
   .chat-sprite 有 breathe 动画(translateY(-9px)),而这一层是 overflow:hidden
   (挡立绘溢出,保一屏预算)。padding 为 0 时立绘正好顶到这一层的上边界,
   浮到最高点时**头顶被裁掉一条**——而且它就在「它叫」那行下面,看着像钻到上一层底下去了。
   给 12px = 动画幅度 9px + 3px 余量,峰值就一直在框内。 */
.screen-crystal .chat-stage { padding: 12px 0 0; }
/* max-height:100% = 立绘段被压时**等比缩小**(盒子受 1fr 行限制),而不是被裁掉半截。
   12px 的 padding 正好给呼吸动画(±9px)留了余量,峰值不被裁。

   立绘高度**这一档档往下降过一次**(2026-10-15 用户:「对话记录框希望能够展示更多……
   现在的对话框高度有点低」)。它和下面那条 `flex:1 1 0` 的面板是同一个天平的两端:
   面板的高度 = 一屏减掉所有定高块、再减掉立绘段,**立绘少要多高,面板就长多高**。
   所以这一条就是"面板能看多少"的总闸门 —— 沿用的是这一页早就定下的规矩
   (见 ≤620 那档的注释:先保对话面板,立绘与气泡让位)。 */
.screen-crystal .chat-sprite { height: min(18vh, 172px); max-height: 100%; }
.chat-sprite.cat-art { height: min(20vh, 196px); }

/* 「我的」:紧凑的一屏布局(账号卡 + 若干行,长内容折进行里)。
   这一页没有弹性区吸收高度,所以尺寸直接按"真实面板 720 高"定:
   内容(≈591px)+ #app 为底导预留的 96px 必须 ≤ 视口,否则就会出滚动条。
   名字变长由 .chip-ellip 吸收,不再改变这里的高度;行数增加就再收一档。 */
.screen-me { padding-bottom: 8px; }
.screen-me .topbar { padding: 2px 0 0; }
.screen-me .topbar h1 { font-size: 24px; }
.screen-me .topbar .tb-sub { font-size: 12px; }

.me-card {
  text-align: center;
  padding: 8px 15px;
  margin-bottom: 10px;
}
.me-card .guest-seal {
  width: 64px;
  height: 64px;
  margin: 0 auto;
  display: block;
  overflow: hidden;
  border-radius: 50%;
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.72), rgba(238, 241, 251, 0.5));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 10px 24px -18px var(--shadow-soft);
}
.me-card .guest-seal img { width: 100%; height: 100%; object-fit: cover; }
.me-card .me-avatar { width: 46px; height: 46px; margin: 0 auto 4px; }
.me-card .me-name {
  font-size: 17.5px;
  font-weight: 700;
  color: var(--navy-1);
  margin-top: 4px;
  line-height: 1.3;
}
.me-card .me-sub { font-size: 12.5px; color: var(--navy-2); margin-top: 3px; }
.me-card .me-chips {
  display: flex;
  gap: 7px;
  justify-content: center;
  flex-wrap: nowrap;          /* 三个 chip 永远一行:换行会把下面的行整体推下去 */
  margin-top: 5px;
}
.me-chips .chip-static { flex: 0 0 auto; }
.me-chips .chip-static.chip-name { flex: 0 1 auto; min-width: 0; }   /* 只有名字这一枚让位 */
.me-card .me-clear { margin-top: 6px; font-size: 12.5px; }

/* 行:图标 + 标题 + 说明 + 箭头(单行 ~48px) */
.me-row {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  /* 2026-10-27:行高 42 → 58（7 行从"挤成一条"变成手指好按的列表;iOS 列表标准 44+,
     带图标+标题的行通常 56-64），行间距 4 → 8 */
  min-height: 58px;
  padding: 12px 14px;
  margin-bottom: 8px;
  border-radius: var(--r);
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.905), rgba(240, 243, 252, 0.755));
  backdrop-filter: blur(var(--g-thick-blur));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.86), 0 14px 30px -24px var(--shadow-soft);
  color: var(--navy-1);
  text-align: left;
  cursor: pointer;
  transition: transform 0.14s ease, border-color 0.2s ease;
}
.me-row:active { transform: scale(0.99); }
.me-row .mr-ico {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1.5px solid var(--glass-line);
  background: rgba(255, 255, 255, 0.89);
  color: var(--gold-ink);
}
.me-row .mr-body { flex: 1; min-width: 0; display: block; }
.me-row .mr-title { display: block; font-size: 16.5px; font-weight: 700; color: var(--navy-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.me-row .mr-sub {
  display: block;
  font-size: 12.5px;
  color: var(--navy-2);
  margin-top: 1px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.me-row .mr-go { flex: 0 0 auto; display: inline-flex; align-items: center; color: var(--gold-ink); }
.me-row .mr-go svg { display: block; }

/* 底部动作行:整页只留一个主动作 */
.me-actions { margin-top: 0; }

/* 极矮屏(iPhone SE / 小屏安卓 ≤700):「我的」再收一档,才够一屏不滚 */
/* 矮屏(≤780,含 1280×720 那块桌面面板):「我的」多了「语言」一行,静止态正好差十几像素。
   这里把行距与卡片留白各收 1px 补回来 —— 手机高度(≥781)一点不受影响,
   而 ≤700 那档在后面,更矮的屏仍按原来那档收得更狠。 */
@media (max-height: 780px) {
  .screen-me { padding-bottom: 6px; }
  .me-card { padding: 7px 13px; margin-bottom: 8px; }
  .me-card .me-name { line-height: 1.25; }
  .me-card .me-chips { margin-top: 4px; }
  .me-row { padding: 4px 12px; margin-bottom: 3px; }
}

@media (max-height: 700px) {
  .screen-me .topbar h1 { font-size: 20px; }
  .screen-me .topbar .tb-sub { font-size: 11.5px; }
  .me-card { padding: 5px 13px; margin-bottom: 7px; }
  .me-card .me-avatar { width: 40px; height: 40px; }
  .me-card .me-name { margin-top: 2px; font-size: 16.5px; }
  .me-card .me-clear { margin-top: 4px; }
  .me-card .me-chips { margin-top: 3px; }
  .me-row { padding: 2px 11px; margin-bottom: 3px; }
  .me-row .mr-ico { width: 28px; height: 28px; }
  .me-actions .btn-line { min-height: 38px; }
}

/* ≤680(640 / 568 这类矮屏,iPhone SE 带工具栏、小屏安卓的真实可视高):
   卡片收成紧凑版 —— 头像与标题同一行、去掉那行「这条手串 · 时间 启用」,
   行再收一档。**只动留白与字号,不动任何入口**:七个入口一个不少,只是排得更紧。
   实测这一档把整页压到 ~450px,568 高的屏也一屏放得下。 */
@media (max-height: 680px) {
  .screen-me { padding-bottom: 4px; }
  .screen-me .topbar h1 { font-size: 19px; }
  .me-card { padding: 4px 12px 6px; margin-bottom: 6px; }
  .me-card .me-avatar { width: 36px; height: 36px; margin-bottom: 2px; }
  .me-card .me-name { margin-top: 1px; font-size: 16px; }
  .me-card .me-sub { font-size: 11.5px; margin-top: 1px; }
  .me-card .me-chips { margin-top: 2px; gap: 6px; }
  .me-card .chip-static { font-size: 11.5px; padding: 3px 8px; }
  .me-card .me-clear { margin-top: 3px; font-size: 12px; }
  .me-row { padding: 1px 10px; margin-bottom: 2px; }
  .me-row .mr-ico { width: 26px; height: 26px; }
  .me-row .mr-title { font-size: 14.5px; }
  .me-row .mr-sub { font-size: 11.5px; }
}

/* ≤600(568 高:iPhone SE 带工具栏、老款 320×568):
   这一档不再动版式,只把**冗余的一行**去掉、把字号与内边距各收一档 ——
   「这条手串 · X 启用」和顶栏那句「这条手串上的记录」说的是同一件事,
   矮屏上留一句就够;七个入口、说明文字、点击区高度全都还在(行高仍 ~38px)。 */
@media (max-height: 600px) {
  .screen-me .topbar { padding: 0; }
  .screen-me .topbar h1 { font-size: 18px; }
  .screen-me .topbar .tb-sub { font-size: 11px; }
  .me-card { padding: 3px 12px 4px; margin-bottom: 5px; }
  .me-card .me-sub { display: none; }
  .me-card .me-avatar { width: 34px; height: 34px; }
  .me-card .me-name { font-size: 15.5px; }
  .me-card .chip-static { font-size: 11px; padding: 2px 7px; }
  .me-card .me-clear { margin-top: 2px; padding: 5px 8px; font-size: 11.5px; }
  .me-row { padding: 1px 10px; margin-bottom: 1px; }
  .me-row .mr-title { font-size: 14px; }
  .me-row .mr-sub { font-size: 11px; }
}

/* 关于弹层里的分节(浅玻璃面上的正文用 navy 系列) */
.about-sec { padding: 11px 0; }
.about-sec + .about-sec { border-top: 1px solid var(--line-2); }
.about-sec .c-label { margin-bottom: 6px; }
.about-sec p { margin: 0; font-size: 13.5px; line-height: 1.8; color: var(--navy-2); }

/* 「我的」游客态:一枚手串印记 */
.guest-seal {
  width: 86px;
  height: 86px;
  margin: 0 auto;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1.5px solid var(--glass-line);
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(255, 234, 190, 0.4), transparent 62%),
    rgba(255, 255, 255, 0.5);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 0 24px rgba(255, 255, 255, 0.4);
}
.guest-seal svg { color: #a5761f; }

/* 陪伴形象选择 —— 这一块的高度**必须与收容了多少只无关**,否则收得越多抽屉越难看。
   旧版是一排 74px 的大磁贴横向滚:满 4 只就开始滚,滚到后面既没有提示、右端还被裁掉半个,
   而且整个抽屉底部跟着长出一条横向滚动条。现在改成**固定 4 列的网格**:
     · 固定列数(不用 auto-fill)是为了「8 只 = 正好两排」这个整数关系在任何宽度下都成立;
     · 名字单行省略 —— 名字是用户起的,长度不可控,一旦换行整排磁贴高度就参差不齐;
     · 超过两排先收起(见 .comp-grid:not(.open)),要看得按标题行右侧那颗「展开全部 N 个」。
   JS 那边负责把**当前这只排在最前**,所以收起状态下也一定看得见自己现在陪着谁。 */
/* 标题行 + 右侧那颗「展开全部 N」:抽屉里**两处**都长这样
   (陪伴形象 8 只 / 它记得的事 3 条),所以名字按角色取(.field-head / .field-toggle),
   不再挂在 comp- 前缀下 —— 新加一段"多了就收起"的内容直接复用这两个类即可。 */
.field-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 0 0 6px 4px;
}
.field-head .lab { margin: 0; }
.comp-note { color: var(--navy-3); font-weight: 400; margin-left: 5px; }
.field-toggle {
  flex: 0 0 auto;
  border: 0;
  background: none;
  padding: 2px 0;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--theme-accent-deep);
  cursor: pointer;
  white-space: nowrap;
  transition: opacity 0.14s ease;
}
.field-toggle:active { opacity: 0.55; }

.comp-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 9px;
}
.comp-grid:not(.open) .comp-item:nth-child(n + 9) { display: none; }   /* 4 列 × 2 排 = 8 只 */
.comp-item {
  position: relative;
  min-width: 0;
  border: 1.5px solid var(--glass-line);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.815);
  padding: 6px 4px 5px;
  cursor: pointer;
  color: var(--navy-1);
  transition: border-color 0.16s ease, box-shadow 0.16s ease, background 0.16s ease;
}
.comp-item img {
  width: 100%;
  height: 52px;
  object-fit: contain;
  filter: drop-shadow(0 3px 8px rgba(88, 98, 150, 0.35));
}
.comp-item .comp-name {
  display: block;
  font-size: 11px;
  color: var(--navy-2);
  margin-top: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 选中态沿用同一张抽屉里「相处语气」那颗 chip 的语汇(白描边 + 淡紫渐变 + 淡紫光晕),
   只是磁贴比 chip 大,光晕给足一档 —— 二十四格里要一眼看出现在陪着的是哪一只。 */
.comp-item.on {
  border-color: var(--theme-select);
  background: linear-gradient(180deg, #ffffff, #dfe5f7);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.85), 0 0 18px rgba(180, 190, 235, 0.7);
}
.comp-item.on .comp-name { color: var(--navy-1); font-weight: 700; }
/* 二十几只长得很像的精灵摆在一起时,**光晕不够**——白描边压在浅玻璃面上本来就近色,
   加一枚小对勾把「现在陪着你的就是这一只」钉死。纯 CSS 画,和选中态永远同一份判据。 */
.comp-item.on::after {
  content: '✓';
  position: absolute;
  top: 5px;
  right: 5px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  color: #fff;
  background: var(--theme-accent-deep);
  box-shadow: 0 2px 6px rgba(88, 98, 150, 0.45);
}

/* ---------- 祝福详情:一屏三段(立绘 / 内容面板 / 动作) ----------
   面板里每一块都是"浅色小面 + 左上角标签行",和送礼人页的预览同一套语言;
   赠言超长时**框内滚动**(和送礼人页 .gp-text 一样:渲染后按实际是否溢出来加 .scrollable)。

   2026-10-14:改成**定高 + 面板自己内滚**(和召唤页同一套写法)。
   以前是 min-height + 整页滚动,于是手机上一进页面就要往下滑才能看见「珍藏」——
   有照片/视频时整页 900px,而手机可视高只有 560~840,动作键就压在固定底导底下。
   现在高度 = --app-h(可视高单源) - 上下预留,底导由负 margin 抵消;三段里**只有面板可伸缩**:
   装得下时它是自然高度(余量由 stage/actions 的 auto margin 平分,整组仍然居中),
   装不下时它先被压扁、内容在面板内滚 —— 头图与动作键永远在原位。
   面板自己也有地板(96px,见 .view-panel):真缩到地板以下就交给 relaxScreen 整页滚。 */
.screen-view {
  display: flex;
  flex-direction: column;
  height: calc(var(--app-h) - var(--app-pad-t) - var(--app-pad-b));
  margin-bottom: calc(-1 * var(--app-pad-b));
  padding-bottom: 8px;
}
.screen-view .topbar { padding: 12px 0 0; }
.screen-view .topbar h1 { font-size: 24px; }

/* ① 精灵头图:按设计图把「水晶精灵 + 打开的礼物盒」并排摆出来(用户:两个都要尽量看得到)。
   两个都按**可见高度**配:立绘素材四周有透明留白(逐张量过,见 app.js 的 spritePadFor),
   所以用 translate 把立绘往下坐,让它和礼物盒落在同一条底线上;盒子比立绘高一档(设计图如此)。
   2026-10-14:去掉 `margin-top: auto` —— 那是"内容少时整组居中"用的,
   真机上是白给的一截页头留白(用户报「很多页面头部有一些留空」),现在一律紧接顶栏。 */
.view-stage {
  flex: 0 0 auto;
  position: relative;
  height: clamp(150px, 22.5vh, 210px);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: 2px;
}
.view-stage .vs-sprite {
  height: clamp(156px, 21.5vh, 196px);
  width: auto;
  /* 宽立绘(叶隐灵头顶的叶子展开后比例约 1.32)在窄屏上会和礼盒一起捅出视口:
     允许 flex 把立绘收窄,再用 contain 保持比例(不横向压扁);礼盒固定不缩(见下一条)。 */
  min-width: 0;
  max-width: 100%;
  object-fit: contain;
  filter: drop-shadow(0 0 30px rgba(255, 250, 240, 0.55)) drop-shadow(0 12px 30px rgba(88, 98, 150, 0.3));
  animation: breathe 6.5s ease-in-out infinite;
}
.view-stage .vs-box {
  height: clamp(140px, 19.5vh, 176px);
  width: auto;
  flex: 0 0 auto;
  margin-left: clamp(-16px, -2.2vw, -6px);   /* 贴着立绘站,像设计图那样紧挨着 */
  filter: drop-shadow(0 0 26px rgba(255, 226, 160, 0.45)) drop-shadow(0 12px 28px rgba(88, 98, 150, 0.34));
}

/* 收到祝福页(领取码)的头图:同一套「精灵 + 打开的水晶礼盒」,但这一页**精灵更大、盒子更小**
   (2026-10-13 用户),所以不跟详情页共用一个尺寸,只共用 .view-stage 的对齐方式。
   这一页不是 flex 容器,详情页那条 margin-top:auto 在这儿按 0 算,换成一点顶部间距。 */
.rec-stage { height: clamp(196px, 28vh, 268px); margin-top: 6px; }
.rec-stage .vs-sprite { height: clamp(196px, 26.5vh, 250px); }
.rec-stage .vs-box { height: clamp(100px, 13.5vh, 128px); margin-left: clamp(-18px, -2.6vw, -8px); }

/* 收到祝福页(领取码)的矮屏分档:这一页只有「头图 + 一张卡片」,就让头图先让位。
   它没有底导,所以 #app 也不再替底导留那 96px(见上面的 #app:not(:has(.nav)))——
   多出来的这一截正好补上高屏与矮屏的差额。 */
@media (max-height: 700px) {
  .screen-receive > .topbar { padding: 10px 0 2px; }
  .screen-receive > .topbar h1 { font-size: 21px; }
  .screen-receive .rec-stage { height: clamp(140px, 20vh, 186px); }
  .screen-receive .rec-stage .vs-sprite { height: clamp(142px, 19vh, 180px); }
  .screen-receive .rec-stage .vs-box { height: clamp(74px, 10vh, 100px); }
  #receive-body .card-cream { padding: 16px; }
}

/* 更矮(≤600):头图再让一档。到这一档以下(320×568)赠言长的会整页滚一下,
   按钮就在卡片末尾,滚得到。 */
@media (max-height: 600px) {
  .screen-receive > .topbar { padding: 6px 0 0; }
  .screen-receive .rec-stage { height: clamp(104px, 17vh, 140px); margin-top: 2px; }
  .screen-receive .rec-stage .vs-sprite { height: clamp(106px, 16vh, 136px); }
  .screen-receive .rec-stage .vs-box { height: clamp(56px, 8vh, 78px); }
  #receive-body .card-cream { padding: 13px; }
}

/* ② 内容面板:自然高度(**没有媒体时**不涨不缩,免得撑出一片空玻璃);
   **有媒体时**吃满剩余高度,让照片/视频按同一比例缩放着铺满 —— 用户 2026-10-14:
   「预览的图片等东西不能全部展示出来,是否可以同比例缩小让东西展示出来」。
   所以这一页改成:头图与动作让位,**媒体吸收高度**;媒体有 84px 的下限,
   真挤到下限以下时面板才退回内滚(兜底,正常手机上用不到)。 */
.view-panel {
  flex: 0 1 auto;
  /* 地板 96px ≈ 赠言两行 + 标签行:压到这儿就不再压(内容在面板内滚),
     再不够就交给 relaxScreen 整页滚 —— 面板被压成一条缝"什么都看不见"是同一类 bug。 */
  min-height: 96px;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* 2026-10-19:浏览器(尤其 iOS/微信内核)的 scroll anchoring 会在照片/视频框量完比例、
     面板高度落定后把 scrollTop 悄悄往下拨十几像素,第一块标题就被面板上沿切掉。
     禁掉锚定;JS 的 pinViewPanel() 在用户没碰过面板前再拉回顶部(兜底)。 */
  overflow-anchor: none;
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 130, 180, 0.45) transparent;
  border-radius: var(--r-lg);
  border: 1.5px solid var(--glass-line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(233, 237, 250, 0.83));
  backdrop-filter: blur(var(--g-thick-blur));
  color: var(--navy-1);
  padding: 10px;
  display: flex;
  flex-direction: column;
  /* 每个块按内容排,不许被拉伸:面板一旦从外面拿到多余高度,
     自动行被撑开(「赠言」那张卡就会被撑成一大片空白 —— 字只有两个字、框却有半屏高,
     用户报的正是这个)。所以只有 .view-duo(媒体那一块)吃剩余高度。 */
  gap: 8px;
  box-shadow: 0 22px 46px -26px var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.view-panel.has-media { flex: 1 1 auto; }        /* 有媒体:面板吃满,媒体再在内部按比例缩 */
.view-panel > .view-box { flex: 0 0 auto; }      /* 赠言 / 语音:自然高度 */
.view-panel::-webkit-scrollbar { width: 4px; }
.view-panel::-webkit-scrollbar-thumb { background: rgba(120, 130, 180, 0.42); border-radius: 2px; }
.view-box {
  border-radius: 16px;
  border: 1px solid rgba(255, 255, 255, 0.85);
  background: linear-gradient(180deg, #ffffff, #eef1fa);
  padding: 9px 11px 10px;
  min-width: 0;
  box-shadow: inset 0 1px 0 #fff;
}
/* 标签行:小图标 + 名字;右侧留给「下载」这类轻动作 */
.vb-head {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  white-space: nowrap;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--gold-ink);
  letter-spacing: 0.05em;
  margin-bottom: 5px;
}
.vb-head svg { flex: 0 0 auto; }
.vb-head .vb-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.vb-head .vb-dl {
  flex: 0 0 auto;
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 7px 0 7px 10px;       /* 可见的字小,点击区给到 ~32px */
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--navy-2);
  text-decoration: none;
}
.vb-head .vb-dl:active { opacity: 0.65; }
/* 两列的媒体块(照片 + 视频)里,这一行只剩 136px:英文的「Download」自己就要 79px,
   于是标签被挤成「Ph…」「Vi…」(用户 2026-10-16 报的「字显示不正常」)。
   两列时**把下载键收成图标**:⭳ 是通用写法,词留给无障碍名与 tooltip(见 app.js 的 dl());
   单列时空间够(328px),照旧带字 —— 同一个界面里不必为了窄的那格牺牲宽的那格。
   中文眼下挤不满,但它是"刚好够"(祝福视频 53 + 下载 49 ≈ 102/136),再加一个词就会重演,
   所以这条按**两列**这个结构条件走,不按语言走。 */
.view-duo:not(.single) .vb-dl .vb-dl-t { display: none; }
.view-duo:not(.single) .vb-dl { padding: 7px 8px 7px 12px; }   /* 词没了,点击区用左右内边距补回来(33×27,行高不变) */

/* 赠言:可视约三到四行,超出框内滚动(头图让出高度后,赠言也该多露两行) */
.view-text .body {
  font-size: 15px;
  line-height: 1.72;
  color: var(--navy-1);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: clamp(52px, 12.5vh, 170px);
  overflow-y: hidden;
  overscroll-behavior: contain;
}
.view-text .body.scrollable {
  overflow-y: auto;
  -webkit-mask-image: linear-gradient(180deg, #000 calc(100% - 14px), transparent);
  mask-image: linear-gradient(180deg, #000 calc(100% - 14px), transparent);
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 130, 180, 0.45) transparent;
}
.view-text .body.scrollable::-webkit-scrollbar { width: 4px; }
.view-text .body.scrollable::-webkit-scrollbar-thumb { background: rgba(120, 130, 180, 0.42); border-radius: 2px; }
.view-text .vfoot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid rgba(120, 130, 180, 0.2);
  font-size: 11px;
  color: var(--navy-2);          /* 11px 文字也要 4.5:1,--navy-3 只有 3.6:1 */
}

/* 语音:波形 + 播放 */
.view-audio .row { display: flex; align-items: center; gap: 10px; }
.view-audio .wave { flex: 1; min-width: 0; height: 30px; }
.view-audio .play-btn { width: 38px; height: 38px; }
.view-audio .t { font-size: 12px; color: var(--navy-2); font-variant-numeric: tabular-nums; }
/* 这台设备放不出这段语音(JS 挂 .audio-dead):**播放器整行被换掉**,这一行说清"为什么"
   并给一个出口(下载原件)。2026-10-15 那份 .amr 的语音就是这一屏:以前只有一个按不动的
   播放键、没有一句话解释,收礼人只能干瞪眼。
   高度上刻意与原来的 .row 相当(详情页的面板是定高的:往下再叠一行会把照片/视频挤成一条缝),
   所以这里没有 margin-top,只有一条分隔线。 */
.view-audio .vb-note {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  flex-wrap: wrap;                    /* 窄屏上让下载键整块换行,别把句子挤成一条一条 */
  padding-top: 4px;
  border-top: 1px solid rgba(120, 130, 180, 0.22);
  font-size: 12px; line-height: 1.5; color: var(--navy-2);
}
.view-audio .vb-note > span { min-width: 0; }
.view-audio .vb-note .vb-dl {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 3px;
  padding: 6px 0 6px 8px;          /* 字小,点击区补到 ~32px */
  font-size: 12px; font-weight: 600; color: var(--navy-2); text-decoration: none;
}
.view-audio .vb-note .vb-dl:active { opacity: 0.65; }

/* 照片 / 视频:两块并排,只有一块时铺满。
   2026-10-14:这一块**吸收面板的剩余高度**,画面按同一比例缩放着完整显示
   (`object-fit: contain`,不裁不切)—— 用户要的是「能看见整张」,而不是铺满一个方框。
   `aspect-ratio: 1/1` 只当"没有可分配高度时"的自然尺寸(碰串直出/收礼页那种整页滚动的场景);
   在详情页里面板有确定高度,flex 会把它压扁或拉高,max-height 是防它拉得太长的上限。
   下限 84px:再挤就轮到面板内滚兜底了。 */
.view-duo { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; align-items: stretch; flex: 1 1 auto; min-height: 0; }
.view-duo.single { grid-template-columns: 1fr; }
.view-duo > .view-box { display: flex; flex-direction: column; min-height: 0; }
.view-duo .view-box .vb-head { flex: 0 0 auto; }
/* 2026-10-19 用户报「祝福的照片和视频封面都超出那个框」(手机/微信内核):
   两列的**行高必须由面板剩余高度决定**。原来只有 `align-items: stretch`、行是隐式 auto ——
   国产内核不认 `aspect-ratio`,竖视频的固有高度会把 auto 行撑高,媒体框就顶出白卡片。
   详情页(唯一"媒体吸收剩余高度"的那一屏)把行钉成 1fr、媒体 flex-basis 归零,
   再给卡片兜一层裁切;碰串直出那一屏是自然高度,不受这条影响(见上面那段注释)。 */
/* 祝福详情 · 媒体布局契约(2026-10-19;门:npm run viewports 的 giftMedia):
   ① 照片/视频必须完整可见(object-fit:contain),不裁、不拉伸;
   ② 媒体框必须落在白卡片的内边距以内;
   ③ 媒体框高度不低于 84px;
   ④ 面板放不下时由 .view-panel 内滚兜底 —— 不允许把媒体压到 84 以下或裁掉。
   实现:行高 1fr + min-height 132(84 + 标题/内边距)保证卡片给得出 84;
   媒体高度由 app.js 的 fitMediaHeight() 按卡片实测写死(老内核 flex 不肯缩的那一步);
   卡片 overflow:hidden 只收圆角,不承担裁切语义。 */
.screen-view .view-duo { grid-template-rows: minmax(0, 1fr); min-height: 132px; }
.screen-view .view-duo > .view-box { overflow: hidden; }
.screen-view .view-media { flex: 1 1 0; }
/* 两列(照片 + 视频)时,**碰串直出那一屏**让两块各按自己的比例站着、在行内垂直居中。
   这一屏是"自然高度"排的,媒体不需要吸收剩余高度 —— 而 .hug 的 aspect-ratio 是"用高反推宽",
   被 stretch 拉成同高之后,一张方照片会算出 242px 宽、捅出自己那 160px 的格子
   (用户 2026-10-15 报的「图片超出后面照片的 div」)。
   **「祝福详情」那一屏不能这么改**:那里媒体要靠 stretch 吸收面板剩余高度(用户点名要的"同比例缩小"),
   它只吃 .view-media 上的 max-width 兜底。 */
.tap-gift .view-duo:not(.single) > .view-box { align-self: start; }   /* 顶部对齐:两块的标题行齐平,才像一套 */
.tap-gift .view-duo:not(.single) .view-media { height: auto; }
.tap-gift .view-duo:not(.single) .view-media img,
.tap-gift .view-duo:not(.single) .view-media video { height: auto; max-height: 100%; }
.view-media {
  position: relative;
  display: block;
  width: 100%;
  /* 兜底:任何上下文里都不许超出自己的容器(两列的格子尤其)。
     以前没有这条时,`.hug` 的"用高反推宽"能把方照片推到 242px 宽、捅出 160px 的格子。 */
  max-width: 100%;
  flex: 1 1 auto;
  min-height: 84px;
  aspect-ratio: 1 / 1;
  max-height: min(38vh, 300px);
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.85);
  border-radius: 12px;
  overflow: hidden;
  background: linear-gradient(165deg, #d5dbec, #b9c1dc);
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.view-media img, .view-media video { width: 100%; height: 100%; object-fit: contain; display: block; }
/* 图上量到真实比例后(app.js 的 hugMediaBoxes)框子收窄成同样的比例并居中:
   竖图给竖框、横图给横框,`contain` 就不会在两侧留一大片空白把照片挤小。
   量不到比例时保持上面的 100% 宽 + 1:1,照旧不裁切。
   **宽由 JS 写死**(hugBoxWidth):`aspect-ratio` 与 `margin-inline` 这两条国产内核(微信安卓
   X5 那一族)都可能不认,只靠它们的话框会塌成"满宽 + 下限高度"的一条扁框,竖图被压成小小一条
   (2026-10-16 用户报的就是这个)。这里的两条留作"JS 还没跑到"那一瞬间的兜底,
   用 margin-left/right 而不是 margin-inline —— 后者是同一件事里更晚才被认的那条。 */
.view-media.hug { width: auto; margin-left: auto; margin-right: auto; }
.view-media .vm-zoom {
  position: absolute;
  top: 6px; right: 6px;
  width: 24px; height: 24px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(30, 36, 66, 0.5);
  color: #fdf8ec;
  backdrop-filter: blur(var(--g-thin-blur));
}
/* 视频缩略图:正中一枚播放角标 —— 整块是按钮,点开和照片同一个弹层播放。
   角标只是 aria-hidden 的提示,不是独立控件。 */
.view-media .vm-play {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 46px; height: 46px;
  border-radius: 50%;
  display: grid; place-items: center;
  padding-left: 3px;                       /* 播放三角的视觉重心偏左一点 */
  background: rgba(30, 36, 66, 0.52);
  color: #fff;
  border: 1.5px solid rgba(255, 255, 255, 0.7);
  backdrop-filter: blur(var(--g-thin-blur));
  box-shadow: 0 4px 14px rgba(30, 36, 66, 0.3);
  pointer-events: none;                    /* 点击穿透给整块按钮 */
}
/* 缩略图是封面不是播放器:视频缩略图不带控件,也别抢点击 */
.view-media video { pointer-events: none; }
/* 媒体加载失败(JS 挂 .vm-fail):这一块不再装成"看着正常却点不出东西"的黑块 ——
   描边转琥珀、底转暖灰、角标换成警示图标;再点它一次会带穿透参数重新取一次。 */
.view-media.vm-fail { border-color: rgba(190, 124, 32, 0.6); background: linear-gradient(165deg, #ece2cc, #d3c5a8); }
.view-media.vm-fail .vm-play,
.view-media.vm-fail .vm-zoom { background: rgba(122, 78, 8, 0.66); }
.gp-video-full.vm-fail { outline: 2px solid rgba(190, 124, 32, 0.7); outline-offset: 2px; }
/* 预览里媒体起不来时的「诊断行」:一行小字(HTTP 状态 / 长度 / 播放器错误码)+ 一个出口。
   为什么要它:手机上没有控制台,而"失败"与"成功"在页面上原本长得一模一样(都是黑块)——
   2026-10-15 那个「收礼那台安卓看不了视频」卡了两轮,缺的就是现场信息。
   「在新窗口打开原件」同时是分水岭:手机自带播放器能放 = 我们页面/管线的问题;也放不了 = 服务端或文件。 */
.media-diag {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 8px; padding-top: 7px;
  border-top: 1px solid rgba(120, 130, 180, 0.22);
}
.media-diag .md-line {
  font-family: var(--mono); font-size: 11px; color: var(--navy-2);
  min-width: 0; overflow-wrap: anywhere;
}
.media-diag .md-open { flex: 0 0 auto; font-size: 12.5px; }

/* ③ 动作区:贴在底导上方(**不**再靠 auto margin 上下平分余量 ——
   那样矮屏上「珍藏」会被推到屏幕外,高屏上又会离卡片几百像素)。
   剩余高度归内容面板与媒体,动作区永远落在最下面。 */
.view-actions { flex: 0 0 auto; margin-top: auto; display: grid; gap: 7px; padding: 10px 0 0; }
.view-actions .btn-gold { min-height: 50px; }
.view-links { display: flex; align-items: center; justify-content: center; gap: 18px; }

/* 矮屏(≤780,含 1280×720 面板与 760 档手机):头图先让一档,
   把高度让给内容面板 —— 这一页的"全部展示出来"指的是照片/视频那些预览块(用户口径)。 */
@media (max-height: 780px) {
  .view-stage { height: clamp(140px, 19vh, 168px); }
  .view-stage .vs-sprite { height: clamp(142px, 18vh, 164px); }
  .view-stage .vs-box { height: clamp(126px, 16vh, 148px); }
  .view-panel { padding: 8px; gap: 7px; }
  .view-box { padding: 6px 9px 7px; }
  .view-audio .wave { height: 26px; }
  .view-audio .play-btn { width: 34px; height: 34px; }
  .view-media { min-height: 72px; }
}

/* 矮屏:面板与动作再收一档,保证一屏 —— 而这一页的"一屏"是**连照片一起**:
   2026-10-14 用户真机报「预览的图片等东西不能全部展示出来」(照片被面板的滚动边界切在下面),
   要求「同比例缩小让东西展示出来」。所以这一档把**头图、赠言框、语音块一起让位**,
   把高度让给媒体那一块 —— 面板里三块都在时也不再需要内滚。 */
@media (max-height: 700px) {
  .screen-view .topbar { padding: 8px 0 0; }
  .screen-view .topbar h1 { font-size: 21px; }
  .view-stage { height: clamp(104px, 15.5vh, 132px); }
  .view-stage .vs-sprite { height: clamp(108px, 14.5vh, 130px); }
  .view-stage .vs-box { height: clamp(96px, 13vh, 118px); }
  /* 领取页那份「精灵更大、盒子更小」在矮屏也要守住 */
  .rec-stage { height: clamp(150px, 23vh, 196px); }
  .rec-stage .vs-sprite { height: clamp(152px, 22vh, 190px); }
  .rec-stage .vs-box { height: clamp(80px, 11.5vh, 100px); }
  .view-panel { padding: 6px; gap: 6px; }
  .view-box { padding: 6px 9px 6px; }
  .vb-head { margin-bottom: 4px; }
  /* 赠言:矮屏留 3 行左右(常见长度就是一两条),再长才框内滚 ——
     别为了给照片腾地方把一句话切成半行(用户读到的第一句就断了,比照片小更难受)。 */
  .view-text .body { max-height: min(4.2em, 13vh); }
  .view-text .vfoot { margin-top: 3px; padding-top: 3px; }
  .view-audio .row { gap: 8px; }
  .view-audio .wave { height: 26px; }
  .view-audio .play-btn { width: 34px; height: 34px; }
  /* 媒体:下限再压低一点 —— 面板里三块都在时才算得过来(见文件顶部说明) */
  .view-media { min-height: 72px; max-height: min(34vh, 260px); }
  .view-actions { padding-top: 7px; gap: 5px; }
  .view-actions .btn-gold { min-height: 46px; }
  .view-links { gap: 14px; }
  .view-links .btn-text { padding: 5px 4px; }
}

/* ≤620:再让一档(头图与三块的留白各收一点),媒体仍看得见整张。
   这一档里**头图是最后才保的**:用户的诉求是"看得见照片",头图只是装饰,
   所以高度几乎全部让给面板;状态条(已珍藏…)也只留必要内边距。 */
@media (max-height: 620px) {
  .screen-view > .topbar { padding: 6px 0 0; }
  .view-stage { height: clamp(48px, 8.5vh, 72px); }
  .view-stage .vs-sprite { height: clamp(50px, 8vh, 72px); }
  .view-stage .vs-box { height: clamp(46px, 7.5vh, 64px); }
  .view-panel { padding: 5px; gap: 5px; }
  .view-box { padding: 5px 8px 5px; }
  .vb-head { margin-bottom: 3px; font-size: 12px; }
  .vb-head .vb-dl { padding: 4px 0 4px 8px; }
  .view-text .body { max-height: min(3.4em, 11vh); }
  .view-audio .wave { height: 22px; }
  .view-audio .play-btn { width: 28px; height: 28px; }
  .view-media { min-height: 56px; }
  .notice-bar { padding: 8px 12px; }
  .view-actions { padding-top: 5px; }
  .view-actions .btn-gold { min-height: 44px; }
}

/* ≤580(老款 320×568、iPhone SE 带工具栏):头图让到只剩"认出是精灵 + 礼盒"的程度,
   高度全部给内容面板 —— 这一档用户的诉求是「看得见整张照片」,不是「头图要大」。 */
@media (max-height: 580px) {
  .view-stage { height: clamp(48px, 9vh, 64px); }
  .view-stage .vs-sprite { height: clamp(50px, 8.5vh, 64px); }
  .view-stage .vs-box { height: clamp(46px, 7.5vh, 58px); }
  /* 3.5em ≈ 正好两行(行高 1.72 × 15px = 25.8):赠言常见的两三行里,
     至少整两行要完整读得到 —— 宁可面板滚一点点,也不能把句子切半行。 */
  .view-text .body { max-height: min(3.5em, 11vh); }
  .view-media { min-height: 52px; }
  .notice-bar { padding: 7px 12px; font-size: 12.5px; }
}

/* 极矮视口(横屏手机):放弃"一屏塞下",允许整页滚动(此时输入栏靠滚动够到,而不是被裁掉)。
   和 JS 的 relaxScreen 是同一件事的 CSS 版:JS 没跑到时(或横屏提示层还没生效时)它照样兜住。 */
@media (max-height: 509px) {
  .screen-discover,
  .screen-crystal,
  .screen-home,
  .screen-view { height: auto; min-height: 0; margin-bottom: 0; }
  /* 但滚动也要有个头:不给面板上限的话整页会有 3400px,
     输入栏落在长页最底部,得滑很久才够得到 */
  .chat-panel { max-height: 34vh; }
  .disco-scene { --disco-h: 120px; flex: 0 0 auto; }
}

/* 矮屏(笔记本窗口、横屏手机):收紧发现页与首页,免得下面那行提示被固定底导盖住 */
@media (max-height: 760px) {
  .disco-scene { --disco-h: min(21vh, 150px); }
  .screen-discover .topbar { padding: 10px 0 0; }
  .screen-discover .seg { margin: 8px 0 2px; }
  .screen-discover .task-card { padding: 11px 14px; margin-top: 7px; }
  .screen-discover .task-card .tk-sub { font-size: 11.5px; }
  .screen-discover .task-note { margin-top: 10px; font-size: 11.5px; line-height: 1.4; }
  .screen-home .home-actions { padding: 8px 0 12px; }
  .quick-grid { gap: 8px; }
  .quick-card { --ico-scale: 0.84; }
  .quick-card .qc-title { font-size: 12px; }
  /* 水晶页同理:矮屏下把立绘和间距收一档 —— 电脑窗口(1280×720 那档)和 667 高的手机都落在这里,
     正是"对话框看不到几行"最明显的地方,所以这一档让得最狠。
     注意这里**不设**面板下限 —— 面板的下限交给 flex 去压,
     设了固定下限就又变成"塞不下时把输入栏顶出去"。 */
  .screen-crystal .chat-sprite { height: min(15vh, 140px); }
  .screen-crystal .chat-stage { padding: 10px 0 0; }
  /* 对话面板要的高度,一半是从下面这三块身上省出来的(它们都是定高块,不参与分配):
     情感交流卡的内边距与缩略图、输入栏的高度、面板上下的两个间距。
     都是几像素几像素地抠,合起来差不多就是一整条气泡。 */
  .screen-crystal .emotion-card { margin-top: 8px; padding: 8px 13px; }
  .emotion-card .ec-thumb { width: 38px; height: 38px; }
  .screen-crystal .composer { padding: 8px 0 2px; }
  .composer .input { min-height: 48px; }
  .send-pill { height: 48px; }
  .screen-crystal .topbar h1 { font-size: 23px; }
  .screen-crystal .crystal-meta { margin: 6px 0 0; }
  .chat-panel { margin-top: 10px; padding: 11px 12px; }
}

/* 手机浏览器带工具栏时的真实可视高大多落在 620~700(实测 393×660:遭遇态内容比屏幕高 90px,
   用户得往下滑才能看见互动三连)—— 这一档把发现页**每一块都收一收**,让遭遇态也一屏装下;
   再矮就交给 .scrolls 的滚动兜底(宁可滚,不能看不见)。只动间距与字号,不动版式与配色。 */
@media (max-height: 700px) {
  .disco-scene { --disco-h: min(14vh, 104px); }
  .screen-discover .topbar { padding: 5px 0 0; }
  .screen-discover .topbar h1 { font-size: 20px; }
  .screen-discover .seg { margin: 6px 0 2px; padding: 3px; }
  .screen-discover .seg button { font-size: 13px; padding: 7px 6px; }
  .wild-card { padding: 6px 8px 7px; border-radius: 20px; }
  .wild-card .wc-head { padding: 0 2px 5px; font-size: 13.5px; }
  .enc-panel { padding: 7px 9px 8px; row-gap: 3px; }
  .enc-panel .ep-avatar { width: 40px; height: 40px; }
  .enc-panel .ep-name { font-size: 18px; }
  .intimacy-card { margin-top: 7px; padding: 8px 12px 9px; }
  .intimacy-card .ic-track { margin: 5px 0 4px; height: 9px; }
  .intimacy-card .ic-num b { font-size: 19px; }
  .intimacy-card .ic-foot { font-size: 12px; }
  .screen-discover .react-line { margin-top: 4px; padding: 5px 12px; min-height: 30px; font-size: 12.5px; }
  .screen-discover .foot-note { margin-top: 3px; }
  .action-bar { gap: 8px; padding-top: 6px; }
  .act-btn { min-height: 42px; font-size: 14.5px; }
  /* 积分任务页:这一档是带工具栏手机的真实可视高(620~700),4 张卡 + 说明要一起进来 */
  .screen-discover .task-card { padding: 10px 13px; margin-top: 6px; }
  .screen-discover .task-card .tk-title { font-size: 14.5px; }
  .screen-discover .task-card .tk-btn { min-width: 96px; min-height: 42px; padding: 0 16px; font-size: 14.5px; }
  .screen-discover .task-note { margin-top: 8px; font-size: 11.5px; }
}
/* ↑ 这个 `}` 曾经漏了(2026-10-14 修)。漏掉之后**文件后面所有规则都被并进这条
   `@media (max-height: 700px)` 里**,只在矮屏生效 —— 高屏手机上:
     · `.sr-only` 的判据失效,发现页里「点开去积分任务」这句只该给读屏的提示**肉眼可见**;
     · `:focus-visible` 的聚焦环、`prefers-reduced-motion` 的减动效一起失效(无障碍倒退);
     · `@media (max-width: 360px)` 那条窄屏规则也只在矮屏生效,同样 360 宽的手机,
       高一点的那台就换一套动作栏排版。
   表现就是「同一个页面在不同手机上长得不一样」,而且改任何一档都像是没生效。 */

/* 更矮一档(≤660,640 那种小屏安卓、iPhone SE 带工具栏):发现页再收一轮,
   把「立绘 → 遭遇卡 → 亲密度 → 互动三连 → 脚注」这五段整体压进 544px。
   顺序照旧:先让**装饰**(立绘那一段)让位,按钮只收内边距、字号不动。 */
@media (max-height: 660px) {
  .disco-scene { min-height: 104px; --disco-h: min(13vh, 96px); }
  .screen-discover .seg { margin: 4px 0 2px; }
  .wild-card { padding: 5px 8px 6px; }
  .wild-card .wc-head { padding: 0 2px 4px; }
  .enc-panel { padding: 6px 9px 6px; }
  .enc-panel .ep-avatar { width: 36px; height: 36px; }
  .intimacy-card { margin-top: 6px; padding: 6px 12px 7px; }
  .intimacy-card .ic-track { margin: 4px 0 3px; height: 8px; }
  .screen-discover .react-line { margin-top: 3px; padding: 4px 12px; }
  .action-bar { padding-top: 4px; }
  .act-btn { padding: 5px 6px; }
  .screen-discover .foot-note { margin-top: 2px; font-size: 11.5px; }
  .screen-discover .task-card { padding: 9px 12px; margin-top: 5px; }
  .screen-discover .task-note { margin-top: 6px; font-size: 11px; }
}

/* 更矮一档(≤640,小屏安卓 / 带工具栏的 iPhone):
   真机上「底部那排动作键被固定底导咬掉一截」是这一页最容易复现的观感问题,
   所以这一档把**上方每一段都再压一点**(立绘场景 → 遭遇卡 → 亲密度卡 → 提示行),
   给最底下的动作栏与脚注留出余量。仍然不删任何一块。 */
@media (max-height: 640px) {
  .screen-discover .topbar { padding: 3px 0 0; }
  .screen-discover .seg { margin: 4px 0 2px; }
  .disco-scene { min-height: 76px; --disco-h: 76px; }
  .wild-card { padding: 5px 8px 5px; }
  .wild-card .wc-head { padding: 0 2px 4px; }
  .enc-panel { padding: 5px 9px 5px; }
  .intimacy-card { margin-top: 5px; padding: 5px 11px 6px; }
  .intimacy-card .ic-track { margin: 3px 0 3px; }
  .screen-discover .react-line { margin-top: 2px; padding: 3px 11px; font-size: 11.5px; }
  .action-bar { padding-top: 3px; }
  .act-btn { min-height: 42px; padding: 5px 5px; }
  .screen-discover .foot-note { margin-top: 2px; }
  .screen-discover .task-card { padding: 8px 12px; margin-top: 4px; }
  .screen-discover .task-card .tk-title { font-size: 14px; }
  .screen-discover .task-note { margin-top: 5px; font-size: 10.5px; }
}

/* 极矮屏(≤600;iPhone SE 带工具栏约 553,老款 320×568 是地板):
   在上一档的基础上**再往下压一头**(注意别在这里把 ≤640 已经压小的值写回去 ——
   媒体查询是按源码顺序覆盖的,写回大值会把上一档抵消掉,这一轮就踩过这个坑)。
   这一档仍然**不藏任何一块**:立绘、遭遇、亲密度、三个动作、脚注全在;
   再挤不下就交给 .scrolls 整页滚动兜底(宁可滚,不能看不见)。 */
@media (max-height: 600px) {
  .disco-scene { min-height: 68px; --disco-h: 68px; }
  .screen-discover .seg { margin: 3px 0 2px; }
  .wild-card { padding: 4px 8px 4px; }
  .wild-card .wc-head { font-size: 13px; }
  .enc-panel { padding: 4px 9px 4px; }
  .enc-panel .ep-avatar { width: 32px; height: 32px; }
  .enc-panel .ep-name { font-size: 16.5px; }
  .intimacy-card { margin-top: 4px; padding: 4px 11px 5px; }
  .screen-discover .react-line { margin-top: 2px; min-height: 22px; font-size: 11.5px; }
  .action-bar { gap: 7px; padding-top: 2px; }
  .act-btn { min-height: 40px; }
  .act-btn em { font-size: 10.5px; }
  .screen-discover .foot-note { font-size: 11.5px; }
  .screen-discover .task-card { padding: 7px 11px; margin-top: 4px; }
  .screen-discover .task-card .tk-sub { font-size: 11px; }
  .screen-discover .task-note { margin-top: 4px; font-size: 10.5px; line-height: 1.35; }
}

/* 宽而矮的窗口(1280×720 那块桌面面板):发现页的固定块之和比手机那套断点算的更高
   (卡片、亲密度卡在 460 列宽下的内边距与字号都大一号),所以得单独收一档 ——
   同样带 min-width:520,不误伤 700~760 档的手机。 */
@media (max-height: 780px) and (min-width: 520px) {
  .disco-scene { --disco-h: min(16vh, 118px); }
  .screen-discover .seg { margin: 8px 0 2px; padding: 3px; }
  .wild-card { padding: 7px 9px 8px; }
  .wild-card .wc-head { padding: 0 2px 5px; font-size: 13.5px; }
  .enc-panel { padding: 7px 9px 8px; row-gap: 3px; }
  .intimacy-card { margin-top: 6px; padding: 8px 12px 9px; }
  .intimacy-card .ic-track { margin: 5px 0 4px; height: 9px; }
  .screen-discover .react-line { margin-top: 4px; padding: 5px 12px; min-height: 30px; font-size: 12.5px; }
  .screen-discover .foot-note { margin-top: 3px; }
  .action-bar { gap: 8px; padding-top: 6px; }
  .act-btn { min-height: 42px; }
  .screen-discover .task-card { padding: 10px 13px; margin-top: 6px; }
  .screen-discover .task-note { margin-top: 8px; font-size: 11.5px; }
}

/* 水晶页的极矮屏一档:立绘的高是按视口算的,而它那一段会被 flex 压扁 ——
   568 高的屏上立绘比段高多出三十来像素,段的 overflow:hidden 会把精灵上下各切掉一截。
   这一档把立绘收到段装得下。 */
@media (max-height: 600px) {
  .screen-crystal .chat-sprite { height: min(11vh, 96px); }
}

/* 更矮(≤700):再收一档立绘,让对话面板别被压得太扁 */
@media (max-height: 700px) {
  .screen-crystal .chat-sprite { height: min(13vh, 116px); }
  .chat-panel { margin-top: 8px; }
  .screen-crystal .emotion-card { padding: 8px 12px; }
}

/* ≤620(小屏安卓、iPhone SE 带工具栏的真实可视高的下半段):
   **这一页的对话面板需要有下限**。面板是 `flex:1 1 0`,只吃"立绘段之后剩下的空间" ——
   而矮屏上立绘段(立绘 + 气泡)自己就快把一屏占满了,面板于是被挤到 20~60px:
   用户看到的是「中间那个对话框完全看不见内容」(2026-10-14 真机报的)。
   这一档把顺序倒过来:**先保对话面板**,立绘与气泡让位(全屏文字在面板里一字不少地留着)。 */
@media (max-height: 620px) {
  .screen-crystal > .topbar { padding: 6px 0 0; }
  .screen-crystal .topbar h1 { font-size: 21px; }
  .screen-crystal .topbar .tb-sub { font-size: 11.5px; }
  .screen-crystal .crystal-meta { margin: 4px 0 0; }
  .screen-crystal .chat-stage { padding: 8px 0 0; }
  .screen-crystal .chat-sprite { height: min(12vh, 92px); }
  /* 气泡的基础尺寸已是「小一号」(2 行 + 15.5px),这一档只再收内边距:
     字号别再往下走,再小就不好读了 —— 省下来的高度全给下面的对话面板。 */
  .speak-bubble { margin-top: 2px; padding: 8px 14px; }
  .chat-panel { margin-top: 6px; padding: 9px 11px; min-height: 104px; }
  .screen-crystal .emotion-card { margin-top: 8px; padding: 7px 11px; }
  .emotion-card .ec-thumb { width: 36px; height: 36px; }
  .emotion-card .ec-title { font-size: 15.5px; }
  .emotion-card .ec-sub { font-size: 12px; }
}

/* 再矮一档(≤600):立绘继续让位,面板的下限不动 —— 宁可精灵小一点,不能没有对话。 */
@media (max-height: 600px) {
  .screen-crystal .chat-sprite { height: min(10vh, 74px); }
  .chat-panel { min-height: 96px; }
  .composer { padding: 7px 0 2px; }
  .composer .input { min-height: 42px; }
}

/* 矮屏(≤780):把省下的高度让给精灵立绘 */
/* 矮屏(≤780):把省下的高度让给精灵立绘 */
@media (max-height: 780px) {
  .screen-home .topbar { padding: 12px 0 0; }
  .screen-home .home-hero { min-height: 60px; }
  .screen-home .home-hero .sprite-stage .sprite { height: min(32vh, 240px); }
}

/* 极窄屏(≤360):动作按钮下的「消耗 8~15 积分」会顶到按钮边框,这一档再收一档字号/内边距。
   (三栏拉平从 2026-10-16 起已是全局默认,不再只在这一档生效) */
@media (max-width: 360px) {
  .action-bar { grid-template-columns: repeat(3, 1fr); gap: 8px; }
  .act-btn { padding: 5px 5px; }
  .act-btn em { font-size: 10px; }
  .quick-card .qc-title { font-size: 12px; }
}

/* 极窄屏(≤335,老款 320 宽):磁贴只剩 64px,12px 的「亲密度互动」还差 3px,
   再收一档才不会被 .qc-title 的 overflow:hidden 切掉最后一字 */
@media (max-width: 335px) {
  .quick-card .qc-title { font-size: 11px; }
}

/* 常见手机高度(≤900):原来在这里收发现页三处留白,现已写进基础规则(见上),
   整幅缩放会兜住高度差 —— 这里不再有发现页的规则 */

/* ---------- 横屏提示层(通用:index / giver 都靠它) ----------
   手机横屏(高 ≤600;平板与桌面横屏高 768+,不会命中)时整页只剩三百多像素,
   怎么"让位"都排不出好看的版式 —— 这一档**不做横屏版式**,直接给一张提示请用户竖回来
   (2026-10-15 用户选的方案)。三个刻意的决定:
     · 纯 CSS(媒体查询旋回竖屏就不再命中)→ 转回竖屏自己消失,不依赖任何脚本;
     · 不给关闭键 —— 能关掉就等于允许在坏版式上用,不如不留退路;
     · z-index 80:压在底导 20 / 抽屉 30 / 图鉴动画 40 / askConfirm 50 / 拦截页 70 之上。
   兜底:下面 ≤509 那条"放开滚动"仍然保留 —— 万一这张层没生效(极旧内核),
   内容还是滚得到(宁可滚,不能看不见)。 */
.rotate-hint { display: none; }
@media (orientation: landscape) and (max-height: 600px) {
  .rotate-hint {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 80;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    text-align: center;
    padding: calc(16px + env(safe-area-inset-top, 0px)) calc(28px + env(safe-area-inset-right, 0px))
      calc(16px + env(safe-area-inset-bottom, 0px)) calc(28px + env(safe-area-inset-left, 0px));
    background: linear-gradient(180deg, var(--bg-0) 0%, var(--bg-1) 58%, var(--bg-2) 100%);
    color: var(--top-ink);
  }
  .rotate-hint .rh-ico { width: 62px; height: 62px; color: #f0d9a6; margin-bottom: 2px; }
  .rotate-hint .rh-title { font-size: 17px; font-weight: 800; letter-spacing: 0.06em; text-shadow: var(--top-glow); }
  .rotate-hint .rh-sub { font-size: 12.5px; color: var(--top-ink-2); }
}

/* 无障碍 */
:focus-visible {
  outline: 2px solid rgba(58, 58, 76, 0.9);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.55);   /* 白色光圈:深色页底上也能看清 */
}
::selection { background: rgba(143, 160, 230, 0.4); color: inherit; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* 桌面端:手机列居中,两侧留夜空 */
@media (min-width: 520px) {
  :root { --app-pad-t: 4px; }
}

/* 「添加到主屏幕」/全屏运行时才吃顶部安全区:这时视口真的顶到状态栏,不加标题会被时间/电池压住。
   浏览器里打开时页面在地址栏下面,顶部安全区一律按 0 算(见 --app-pad-t 的说明)。
   左右同理:横屏刘海屏的 44px 在整屏模式下是真的,浏览器里横屏并没有被挖掉,吃了白缩一圈。 */
@media (display-mode: standalone), (display-mode: fullscreen), (display-mode: minimal-ui) {
  :root {
    --app-pad-t: env(safe-area-inset-top, 0px);
    --pad-x: max(20px, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px));
  }
}

/* 手机上把顶栏往上提一点(pages 自己的 `.screen-x .topbar` 规则优先级更高,不受影响):
   真实机上可视高本来就紧,而"头部留空"是用户点名的观感问题。 */
@media (max-height: 780px) {
  .topbar { padding: 12px 0 3px; }
}

/* ---------- 触屏上的 :hover 守卫 ----------
   手触设备上 :hover 会"粘住":点一下,元素就停在 hover 态直到点别处(iOS Safari 尤其明显)——
   看着像"卡住的选中态",而且各家浏览器(安卓 Chrome / 微信内核)对它的处理并不一样。
   所以**纯装饰性的 hover 一律收进 (hover: hover)**:手触设备干脆不匹配,触摸反馈全交给 :active
   (本项目各组件本来就都写了 :active)。quick-card 与导航水晶那两处早就是这么守卫的,这里补齐漏掉的七处。
   放在文件末尾:cascade 上最稳妥(这几处比基础规则只多一个伪类,优先级本来就高,顺序只是保险)。 */
@media (hover: hover) {
  .btn-text:hover { color: var(--navy-1); }
  .mem-item .mem-x:hover { color: var(--danger); }
  .rr-pill:hover { background: #fff; border-color: rgba(120, 130, 180, 0.56); }
  .food-item:hover { box-shadow: 0 0 22px rgba(180, 190, 235, 0.55); }
  .plz-chip:hover { background: rgba(255, 255, 255, 0.72); }
  .plz-arrow:hover { background: rgba(255, 255, 255, 0.76); box-shadow: 0 0 16px rgba(255, 255, 255, 0.7); }
  .me-row:hover { border-color: var(--gold-line-soft); }
}

/* ================= 多语言排版(2026-10-20) =================
   ① 字体栈按语言分开:日文/韩文用各自的黑体(用中文字体会出现"日文汉字字形不对")、
      阿拉伯语用 Noto Sans Arabic / Tahoma;其它语言沿用 --sans。
   ② **方向策略是全站 LTR**(2026-10-27 改):阿拉伯文靠 Unicode bidi 自己右起排字,
      但布局不镜像 —— 整页镜像会把身份卡这种"固定设计的图像"一起翻掉。
      原来这里有一组 html[dir="rtl"] 覆盖(方向图标 scaleX(-1)、text-align、开关滑块…),
      因为 dir 不会再是 rtl,已整段删除。真要开 RTL,见 docs/i18n-direction-design.md。
      flex 的 row 会自动翻转,不需要改;要改的是绝对定位、text-align、translateX 与图标朝向。
   ③ 轮播(.plz-arc)的位移由 JS 按物理坐标算,所以它内部固定 direction:ltr,
      卡片文字再单独恢复 rtl —— 否则整个卡列会左右颠倒。 */
html[lang="ja"] body {
  font-family: -apple-system, BlinkMacSystemFont, "Hiragino Sans", "Hiragino Kaku Gothic ProN",
    "Yu Gothic", "YuGothic", "Noto Sans JP", "PingFang SC", "Microsoft YaHei", sans-serif;
}
html[lang="ko"] body {
  font-family: -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Malgun Gothic",
    "Noto Sans KR", "PingFang SC", "Microsoft YaHei", sans-serif;
}
html[lang="ar"] body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans Arabic", "Geeza Pro",
    Tahoma, "PingFang SC", "Microsoft YaHei", sans-serif;
  /* 阿拉伯字母有上下升降部,1.65 的行高会把相邻行挤在一起 */
  line-height: 1.75;
}

/* 阿拉伯语的行高(全局 1.85)在「我的」页那七行上会把整页顶出 11~38px:
   这两行都是单行标题/单行说明,不需要给升降部留那么多。 */
html[lang="ar"] .me-row .mr-title,
html[lang="ar"] .me-row .mr-sub { line-height: 1.45; }
/* 阿拉伯语的行高让「我的」页在 667/664 两档多出 2~5px;页底那 8px 内边距对 RTL 收掉即可。 */
html[lang="ar"] .screen-me { padding-bottom: 0; }
/* 日文/韩文字体的 ascent/descent 比拉丁字体高,「我的」那两行会各多出 ~5px:
   单行标题/说明不需要那么多行距,收紧到 1.35(与阿拉伯语那条同理)。 */
html[lang="ja"] .me-row .mr-title,
html[lang="ja"] .me-row .mr-sub,
html[lang="ko"] .me-row .mr-title,
html[lang="ko"] .me-row .mr-sub { line-height: 1.35; }

/* ==========================================================================
   广场·身份卡滚轮(2026-10-27,把 public/demo/sprite-id-card.html 的形态搬进主线)
   卡片结构、比例、栅格照抄 demos/01-plateform-deck:横版 1 : 0.655、chrome 条 7.4%、
   body padding 3.2%/4.4%、三格信息带贴底、页脚 2.4%。字号统一用 --wcw 的百分比算,
   这样 264 的窄卡和 380 的宽卡观感一致(纯 u 单位在窄卡上会缩到 4-5px,读不出来)。
   ========================================================================== */
@font-face{
  font-family:'Archivo';
  src:url('/vendor/fonts/demo-Archivo-100900-latin.woff2') format('woff2-variations');
  font-weight:100 900; font-stretch:62% 125%; font-display:swap;
}
@font-face{font-family:'PlexMono';src:url('/vendor/fonts/demo-IBMPlexMono-400-latin.woff2') format('woff2');font-weight:400;font-display:swap}
@font-face{font-family:'PlexMono';src:url('/vendor/fonts/demo-IBMPlexMono-500-latin.woff2') format('woff2');font-weight:500;font-display:swap}

.plz-wheel{
  --wcw:264px;
  position:relative;
  /* 突破 .screen 的左右各 20px 内边距:卡要占满屏宽才够大(用户 2026-10-27 反馈两轮) */
  margin:0 -18px;   /* 左右各吃掉内边距(width:auto + 负 margin 才是满宽写法);留 2px 容差,免得倾斜的卡角把视口撑出 4px */
  display:grid;place-items:center;
  margin:2px auto 0;
  perspective:1000px;perspective-origin:50% 46%;
  touch-action:none;user-select:none;-webkit-user-select:none;
  /* 26 张里远处的卡 ty 能到 1400px:不裁的话 absolute 元素照样把页面滚动区撑到 1972px
     (2026-10-27 踩过:viewports 报"整页溢出 1092px")。裁掉滚轮外的部分即可。 */
  overflow:hidden;
}
.wh-deck{position:relative;width:var(--wcw);height:100%}
.wh-card{
  position:absolute;top:50%;left:50%;
  width:var(--wcw);height:calc(var(--wcw) * .70);   /* 0.655 → 0.70:多一段精灵介绍 */
  margin:0;border-radius:calc(var(--wcw) * .031);
  background:#f2f1ec;color:#131312;
  overflow:hidden;cursor:pointer;
  transform:translate(-50%,-50%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.75),0 1px 2px rgba(19,19,18,.20),0 14px 30px -18px rgba(19,19,18,.5);
  will-change:transform,opacity;backface-visibility:hidden;
}
.wh-card.is-dark{background:#141413;color:#f1f0ea;box-shadow:inset 0 1px 0 rgba(255,255,255,.12),0 1px 2px rgba(0,0,0,.35),0 16px 34px -18px rgba(12,12,11,.65)}
.wh-card.is-locked{
  background:linear-gradient(155deg,#d9dadd,#bfc1c6) !important;
  color:#62666e !important;
  cursor:default;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.55),0 12px 28px -20px rgba(35,39,51,.52) !important;
}
.wh-card.is-locked::after{box-shadow:inset 0 0 0 1px rgba(72,76,88,.16) !important}
.wh-card.is-locked .wh-photo img{
  filter:grayscale(1) brightness(.72) contrast(.82) !important;
  opacity:.34;
}
.wh-card.is-locked .wh-rar,
.wh-card.is-locked .wh-tag,
.wh-card.is-locked .wh-cn,
.wh-card.is-locked .wh-foot{color:#696d76}
.wh-card.is-locked .wh-cell,
.wh-card.is-locked .wh-chrome{border-color:rgba(84,89,102,.18) !important}
.wh-card::after{content:'';position:absolute;inset:0;z-index:3;pointer-events:none;border-radius:inherit;box-shadow:inset 0 0 0 1px rgba(19,19,18,.07)}
.wh-card.is-dark::after{box-shadow:inset 0 0 0 1px rgba(255,255,255,.07)}

/* chrome 条 */
.wh-chrome{
  position:absolute;top:0;left:0;right:0;height:calc(var(--wcw) * .074);
  display:flex;align-items:center;justify-content:space-between;
  padding:0 calc(var(--wcw) * .044);
  border-bottom:1px solid rgba(19,19,18,.10);z-index:2;
}
.wh-card.is-dark .wh-chrome{border-bottom-color:rgba(255,255,255,.16)}
.wh-mark{
  font-family:'Archivo',var(--sans,sans-serif);font-weight:600;font-stretch:86%;
  font-size:calc(var(--wcw) * .040);letter-spacing:.12em;text-transform:uppercase;
  opacity:.85;display:flex;align-items:center;gap:.7em;white-space:nowrap;
}
.wh-mark::before{content:'';width:calc(var(--wcw) * .022);height:calc(var(--wcw) * .022);border-radius:50%;background:currentColor;opacity:.9;flex:0 0 auto}
.wh-no{font-family:'PlexMono',monospace;font-size:calc(var(--wcw) * .036);letter-spacing:.14em;opacity:.62;white-space:nowrap}
.wh-dots{display:flex;gap:calc(var(--wcw) * .011);opacity:.5}
.wh-dots i{width:calc(var(--wcw) * .01);height:calc(var(--wcw) * .01);border-radius:50%;background:currentColor}

/* 卡体 */
.wh-body{position:absolute;inset:calc(var(--wcw) * .074) 0 0 0;padding:calc(var(--wcw) * .032) calc(var(--wcw) * .044) 0;z-index:1}
.wh-top{display:flex;align-items:baseline;justify-content:space-between;gap:1em;min-width:0}
.wh-tag{min-width:0;overflow:hidden;text-overflow:ellipsis;font-family:'PlexMono',monospace;font-size:calc(var(--wcw) * .040);letter-spacing:.14em;text-transform:uppercase;opacity:.75;line-height:1;white-space:nowrap}
.wh-tag.is-dim{opacity:.45}
.wh-name{display:flex;align-items:flex-end;justify-content:space-between;gap:.5em;margin-top:calc(var(--wcw) * .028);min-width:0}
.wh-cn{flex:1 1 auto;min-width:0;font-family:var(--display,ui-sans-serif);font-weight:800;font-size:calc(var(--wcw) * .098);line-height:.94;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.wh-rar{font-family:'Archivo',sans-serif;font-weight:800;font-stretch:76%;font-size:calc(var(--wcw) * .070);line-height:1;color:#f43b1c;flex:0 0 auto}
.wh-traits{margin-top:calc(var(--wcw) * .022);font-family:'PlexMono',monospace;font-size:calc(var(--wcw) * .038);letter-spacing:.06em;opacity:.6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.wh-traits em{font-style:normal;opacity:.5}
.wh-abil{display:flex;gap:calc(var(--wcw) * .022);margin-top:calc(var(--wcw) * .018);overflow:hidden}
.wh-abil span{font-family:'PlexMono',monospace;font-size:calc(var(--wcw) * .038);letter-spacing:.04em;opacity:.75;white-space:nowrap}
.wh-abil span::before{content:'✦ ';opacity:.6}
.wh-abil span.act::before{content:'♡ '}

/* 证件照 */
.wh-photo{
  position:absolute;right:calc(var(--wcw) * -.02);top:calc(var(--wcw) * .10);
  width:calc(var(--wcw) * .46);height:calc(var(--wcw) * .40);
  display:flex;align-items:flex-end;justify-content:center;pointer-events:none;z-index:0;
  -webkit-mask-image:radial-gradient(72% 74% at 52% 56%,#000 58%,transparent 86%);
          mask-image:radial-gradient(72% 74% at 52% 56%,#000 58%,transparent 86%);
}
.wh-photo img{max-width:100%;max-height:100%;object-fit:contain;filter:drop-shadow(0 calc(var(--wcw)*.014) calc(var(--wcw)*.02) rgba(19,19,18,.22))}

/* 三格信息带 + 页脚 */
.wh-grid{
  position:absolute;left:calc(var(--wcw) * .044);right:calc(var(--wcw) * .044);bottom:calc(var(--wcw) * .062);
  display:grid;grid-template-columns:1fr 1fr 1fr;gap:calc(var(--wcw) * .014);z-index:1;
}
.wh-cell{border:1px solid rgba(19,19,18,.16);border-radius:calc(var(--wcw) * .012);padding:calc(var(--wcw) * .012) calc(var(--wcw) * .014);display:flex;flex-direction:column;gap:calc(var(--wcw) * .006);min-width:0}
.wh-card.is-dark .wh-cell{border-color:rgba(255,255,255,.16)}
.wh-cell b{font-family:'Archivo',sans-serif;font-weight:700;font-stretch:88%;font-size:calc(var(--wcw) * .052);line-height:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.wh-cell span{font-family:'PlexMono',monospace;font-size:calc(var(--wcw) * .034);letter-spacing:.12em;text-transform:uppercase;opacity:.55;line-height:1}
.wh-sw{display:flex;gap:calc(var(--wcw) * .008);align-items:center;height:calc(var(--wcw) * .042)}
.wh-sw i{width:calc(var(--wcw) * .032);height:calc(var(--wcw) * .032);border-radius:calc(var(--wcw) * .008)}
.wh-foot{
  position:absolute;left:calc(var(--wcw) * .044);right:calc(var(--wcw) * .044);bottom:calc(var(--wcw) * .022);
  display:flex;align-items:center;justify-content:space-between;gap:1em;z-index:1;
  font-family:'PlexMono',monospace;font-size:calc(var(--wcw) * .034);letter-spacing:.14em;text-transform:uppercase;opacity:.5;line-height:1;
}

/* 刻度条:滚轮里只看得见 5 张,这条让"一共 N 张"被看见 */
.wh-ticks{position:absolute;right:0;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:3px;z-index:40;pointer-events:none}
.wh-ticks i{display:block;width:2px;height:6px;border-radius:1px;background:rgba(19,19,18,.16);transition:width .18s ease,background .18s ease}
.wh-ticks i.is-on{width:10px;background:#f43b1c}

/* 三键 */
.wh-ctrl{display:flex;align-items:center;justify-content:center;gap:10px;margin-top:4px}
.wh-btn{
  width:46px;height:38px;display:grid;place-items:center;border-radius:999px;
  border:1px solid rgba(120,138,200,.28);background:rgba(255,255,255,.72);color:var(--navy-1,#2b3452);cursor:pointer;
}
.wh-btn svg{transform:rotate(90deg)}
.wh-btn[data-action="wheel-next"] svg{transform:rotate(-90deg)}
.wh-count{min-width:72px;text-align:center;font-family:'PlexMono',monospace;font-size:13px;letter-spacing:.14em;color:var(--navy-3,#7b86a4)}









/* 详情键:焦点卡常显(2026-11-02 去掉"抬起"两段式后,它只是把"点这张能进去"说出来) */
.wh-card .wh-detail{
  position:absolute;right:calc(var(--wcw) * .044);bottom:calc(var(--wcw) * .062);
  display:none;align-items:center;gap:4px;height:calc(var(--wcw) * .072);padding:0 calc(var(--wcw) * .034);
  border-radius:999px;border:1px solid rgba(19,19,18,.16);background:#fff;color:#131312;
  font-family:'PlexMono',monospace;font-size:calc(var(--wcw) * .034);letter-spacing:.1em;
  cursor:pointer;z-index:4;white-space:nowrap;
}
.wh-card.is-dark .wh-detail{border-color:rgba(255,255,255,.2);background:rgba(255,255,255,.92);color:#131312}
.wh-card.is-focus .wh-detail{display:flex}






/* 精灵介绍(2026-10-27):名字下面两行,超出截断。字号按 --wcw 走,和卡同步缩放。 */
.wh-intro{
  margin:calc(var(--wcw) * .018) 0 0;
  font-family:var(--sans);
  font-size:calc(var(--wcw) * .034);
  line-height:1.38;
  max-width:60%;   /* 右边留给立绘,介绍只占左侧六成宽 */
  letter-spacing:.01em;
  opacity:.72;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;
}
.wh-card.is-dark .wh-intro{opacity:.78}


/* 性格 + 能力并成一行(2026-10-27):加了介绍之后两条各自成行会顶到三格信息带上,
   变成一行按"性格 · 能力"排,超出部分省略 —— 省 30px,正好让开信息带。 */
.wh-meta{
  display:flex;align-items:baseline;gap:calc(var(--wcw) * .028);
  margin-top:calc(var(--wcw) * .014);
  font-family:'PlexMono',monospace;font-size:calc(var(--wcw) * .036);
  letter-spacing:.04em;white-space:nowrap;overflow:hidden;
}
.wh-meta .wh-traits{opacity:.62;overflow:hidden;text-overflow:ellipsis;flex:0 1 auto}
.wh-meta .wh-abil{display:inline-flex;gap:.6em;opacity:.8;flex:0 0 auto}
.wh-meta .wh-abil span::before{content:'✦ ';opacity:.6}
.wh-meta .wh-abil span.act::before{content:'♡ '}


/* ===== 玻璃噪点层(2026-10-27)=====
   玻璃的"质感"主要来自噪点 + 内高光,不是模糊。给关键玻璃面加一层 5.5% 的噪点,
   `mix-blend-mode: overlay` 只提对比不改色相;`pointer-events:none` 不吃点击。
   只挂在**大的玻璃面**上(底栏/头部/抽屉/滚轮外壳),不挂到 26 张卡上 ——
   每张卡一个伪元素 × 26 会明显拖慢滚动。 */
/* 注意:**不要在这里写 position** —— `.nav` 是 fixed,margin 同权重会被这条覆盖成 relative,
   底栏就掉回文档流,整页高度直接多出 73px(2026-10-27 踩过)。定位由调用方自己保证。 */
.g-noise{ isolation:isolate; }
.g-noise::after{
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  border-radius:inherit;
  background-image:var(--g-noise); background-size:160px 160px;
  opacity:.055; mix-blend-mode:overlay;
}
/* 深色玻璃(底栏)噪点要重一点才看得出来 */
.g-noise.is-dark::after{ opacity:.085; mix-blend-mode:soft-light; }



/* 折射的降级(2026-10-27):Safari / Firefox / 大部分手机浏览器不支持
   `backdrop-filter: url()`,拿不到位移滤镜。底栏底衬已经降到 0.80/0.86 半透明,
   没有模糊的话文字会从底下透出来 —— 这里补一层普通磨砂,保证可读性不输以前。 */
@supports not (backdrop-filter: url(#x)) {
  .nav { backdrop-filter: blur(var(--g-thick-blur)) saturate(140%); -webkit-backdrop-filter: blur(var(--g-thick-blur)) saturate(140%); }
}

/* 行内"值"(2026-10-27 用户:副标题说明太挤,全删;只留值靠右) */
.me-row .mr-value{
  margin-left:auto;margin-right:2px;flex:0 0 auto;
  max-width:44%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size:14.5px;color:var(--navy-3);
}


/* ===== 毛玻璃组件(2026-10-27)=====
   一套给全局用:面板 / 卡片 / 设置行 / 分段控件 / 主按钮 / 抓手。
   这些只是**样式类**,不绑定结构 —— 现有 .sheet / .plz-head / .me-row 按需挂上即可。 */
.g-panel{
  background: var(--g2-tint);
  backdrop-filter: blur(var(--g2-blur)) saturate(180%);
  -webkit-backdrop-filter: blur(var(--g2-blur)) saturate(180%);
  border: 1px solid var(--g2-edge);
  border-radius: var(--g2-radius);
  box-shadow: var(--g2-shadow);
}
.g-card{
  background: var(--g2-tint-strong);
  backdrop-filter: blur(var(--g2-blur-card)) saturate(160%);
  -webkit-backdrop-filter: blur(var(--g2-blur-card)) saturate(160%);
  border: 1px solid var(--g2-edge);
  border-radius: 22px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 12px 30px -22px rgba(32, 38, 66, 0.4);
}
/* 抽屉顶部的抓手:参考图那条 38×4 的短杠 */
.g-handle{ position: relative; }
.g-handle::before{
  content: ''; position: absolute; top: 8px; left: 50%; transform: translateX(-50%);
  width: 38px; height: 4px; border-radius: 2px; background: rgba(40, 46, 72, 0.22);
}
/* 设置行:左标签右控件,52 高,和「我的」列表同一套节奏 */
.g-row{ display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 52px; }
.g-row + .g-row{ border-top: 1px solid rgba(40, 46, 72, 0.07); }
/* 分段控件:参考图「语气」那排,选中项实心胶囊 */
.g-seg{ display: inline-flex; gap: 4px; padding: 3px; border-radius: 999px; background: rgba(255, 255, 255, 0.5); }
.g-seg button{
  height: 30px; padding: 0 14px; border: 0; border-radius: 999px; background: transparent;
  font-size: 13.5px; color: var(--navy-2); cursor: pointer; transition: background 0.18s, color 0.18s;
}
.g-seg button.on{ background: var(--theme-select, #7b6cf0); color: #fff; box-shadow: 0 6px 14px -8px rgba(90, 70, 220, 0.6); }
/* 主按钮:紫色胶囊 */
.g-btn-primary{
  width: 100%; height: 50px; border: 0; border-radius: 999px; cursor: pointer;
  background: linear-gradient(180deg, #8b7cf6, #7364ea); color: #fff;
  font-size: 16px; font-weight: 700; letter-spacing: 0.02em;
  box-shadow: 0 14px 26px -14px rgba(90, 70, 220, 0.7);
}
.g-btn-primary:active{ transform: scale(0.985); }


/* 主操作按钮(2026-10-27,v2):抽屉里那枚「保存 / 确定」原来是一块白瓷胶囊,
   在毛玻璃上分不出主次。改成参考图那种紫色实心胶囊,全站 .btn-gold 统一走这里。 */
.btn-gold{
  background: linear-gradient(180deg, #8b7cf6, #7364ea) !important;
  color: #fff !important;
  border: 0 !important;
  border-radius: 999px !important;
  height: 50px;
  font-size: 16px;
  font-weight: 700;
  box-shadow: 0 14px 26px -14px rgba(90, 70, 220, 0.7);
}
.btn-gold:active{ transform: scale(0.985); }
/* 毛玻璃里的输入框:深色描边太重,换成极淡的一圈 */
.sheet .input{ border-color: rgba(40, 46, 72, 0.12) !important; background: rgba(255, 255, 255, 0.62) !important; }

/* ==========================================================================
   Glass v2 覆盖(2026-10-27):四处大面统一成参考图那种毛玻璃。
   写在文件末尾 + 具体选择器 —— 这四个组件的原始规则分散在多处(媒体查询/主题覆盖),
   逐个改容易漏,这里做一层最终覆盖;后续可以把它们内联回原规则再删掉这一段。
   ========================================================================== */
/* ① 广场头部块 */
.plz-head{
  background: var(--g2-tint) !important;
  border: 1px solid var(--g2-edge) !important;
  backdrop-filter: blur(var(--g2-blur)) saturate(180%) !important;
  -webkit-backdrop-filter: blur(var(--g2-blur)) saturate(180%) !important;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.55), 0 16px 34px -26px rgba(32,38,66,.42) !important;
}
/* ② 「我的」列表行:**不加 backdrop-filter** —— 7 行 + 底栏 + 头部会超"同屏 6 个"的预算,
   滚动时掉帧。改成纯半透明 + 细边,背后本来就是一整片背景,视觉几乎一样。
   噪点由外层 .g-noise 提供。 */
.me-row{
  background: rgba(246, 247, 252, 0.72) !important;
  border: 1px solid var(--g2-edge) !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.6), 0 10px 24px -20px rgba(32,38,66,.36) !important;
}
/* ④ 首页快捷磁贴 */
.quick-card{
  background: var(--g2-tint-strong) !important;
  border: 1px solid var(--g2-edge) !important;
  backdrop-filter: blur(var(--g2-blur-card)) saturate(160%) !important;
  -webkit-backdrop-filter: blur(var(--g2-blur-card)) saturate(160%) !important;
}

/* 收起胶囊(2026-10-27 修):胶囊现在跟着底栏走(每页都有),但**展开态**时它悬在底栏上方,
   会和页面内容(比如广场的三键)挤在一起。展开态淡显 + 缩小,收起态才是主角。 */
body:not(.nav-hidden) .nav-toggle{
  opacity: .42;
  transform: translateX(-50%) scale(.82);
}
body:not(.nav-hidden) .nav-toggle:hover,
body:not(.nav-hidden) .nav-toggle:focus-visible{
  opacity: .9;
  transform: translateX(-50%) scale(1);
}


/* ==========================================================================
   合成层预算(2026-10-27):实测同屏 backdrop-filter 数量 —— 首页 8 / 广场 8 / 抽屉 9,
   超过"同屏 ≤6"的红线(移动端会掉帧)。这里把**小面积**容器的滤镜全撤掉:
   它们背后就是背景色,撤掉模糊肉眼几乎看不出,但每个都省一个合成层。
   保留:头部块、底栏、抽屉、发现页两张主卡、首页顶栏两个小件。
   ========================================================================== */
.plz-chip,
.nav-toggle,
.chip-static,
.sheet-mask,
.sheet .input{
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}
/* 首页四块磁贴:面积不小,但背后是纯色渐变 —— 改纯半透明,省 4 层 */
.quick-card{
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  background: rgba(250, 250, 253, 0.80) !important;
}

/* ===== 首页背景视频(2026-10-27)=====
   视频铺满整屏(cover),内容层抬到 z-index 1 之上;底栏/顶栏的 backdrop-filter 会实时
   模糊视频 —— 这是这套玻璃唯一"活"的地方,但也最吃 GPU,所以:
     · 视频只有 720×1280 / 30fps / 620KB(转码时已压过);
     · prefers-reduced-motion 与页面不可见时暂停(见 bootstrapHomeBg)。
   注意 **不要** 给 .home-bg 加 filter 或 transform 动画,那会触发逐帧重绘。 */
.screen-home { position: relative; }
.screen-home .home-bg {
  position: absolute;
  /* 左右各吃掉 .screen 的 20px 内边距 —— 背景要出血到屏幕边,不然两侧留蓝边 */
  inset: 0 calc(-1 * var(--pad-x));
  width: calc(100% + 2 * var(--pad-x));
  /* 高度补到屏幕底: .screen 底部有 --app-pad-b(给底栏让出的留白),不补就会在底栏上方
     露出一条 54px 的页面底色。底栏本身是 fixed,仍在最上层,不会被盖。 */
  height: calc(100% + var(--app-pad-b));
  object-fit: cover;
  object-position: center 42%;
  z-index: 0;
  pointer-events: none;
  background: #c9d3ea;   /* poster 到位之前的兜底色,和视频首帧同色系 */
}
/* **只给内容块加层** —— 千万不要写 `.screen-home > *` 通配:
   底栏 .nav 是 position:fixed,通配里的 relative 会把它拽回文档流(整页被撑高、视频只占上半屏)。
   (2026-10-27 踩过,和之前 .g-noise 那次同一个坑。) */
.screen-home .topbar,
.screen-home .home-hero { position: relative; z-index: 1; }
/* 省电/减弱动效:直接把视频藏掉,只留 poster 那一帧(见 bootstrapHomeBg 的 pause) */
@media (prefers-reduced-motion: reduce) {
  .screen-home .home-bg { display: none; }
  .screen-home::before {
    content: ''; position: absolute; inset: 0; z-index: 0;
    background: url('/assets/home-bg-poster.jpg') center 42% / cover no-repeat;
  }
}


/* 首页立绘(2026-10-27 用户:"中间动的精灵先去掉,直接显示视频的效果即可")。
   **只隐藏、不删** —— spriteStage 那张 img 是 GSAP 剧本(arun-motion.js)的作用对象,
   删了节点会让它拿空引用;display:none 的节点浏览器不参与渲染,动画开销可以忽略。
   想恢复就把这行删掉。 */
.screen-home .home-hero { display: none; }


/* ===== 阿拉伯语排版(2026-10-27)=====
   布局是 LTR(见 i18n.js 顶部:整页镜像会把身份卡这种固定设计的图像一起翻过去),
   但**阿拉伯文本身的排版**要单独照顾。这条不是"镜像",是"混排正确":

   1) unicode-bidi: plaintext —— 让**每一段**自己决定方向(以首字符为准):
      纯阿拉伯文的段落 → 右起,`·` 夹在词间不会跑到错误的一侧;
      纯数字的段落(`26 / 01`) → 左起,不会变成 `01 / 26`。
      这是 CSS 里处理"阿拉伯文 + 拉丁标点 + 数字"混排的标准做法,比逐个包 <bdi> 省事。
   2) line-height 抬高 —— 阿拉伯文有上伸部/下伸部,同 px 下比拉丁文挤,1.4 会切到字头字尾。
   3) letter-spacing: 0 —— **阿拉伯字母是连写的**,任何字距都会把连写拆开(这是硬性禁忌)。
   4) 数字强制西方数字形,避免某些字体回退到阿拉伯-印度数字(٠١٢٣)。 */
[lang="ar"] .wh-traits,
[lang="ar"] .wh-abil,
[lang="ar"] .wh-tag,
[lang="ar"] .wh-intro,
[lang="ar"] .wh-ctrl-num,
[lang="ar"] .wh-count,
[lang="ar"] .mr-value,
[lang="ar"] .nav-label {
  unicode-bidi: plaintext;
}
[lang="ar"] .wh-intro { line-height: 1.75; }
[lang="ar"] .wh-traits,
[lang="ar"] .wh-abil,
[lang="ar"] .wh-tag,
[lang="ar"] .wh-cell span,
[lang="ar"] .wh-cell b {
  letter-spacing: 0;
  line-height: 1.6;
}
[lang="ar"] .wh-ctrl-num,
[lang="ar"] .wh-count,
[lang="ar"] .wh-cell b {
  font-variant-numeric: lining-nums;
  unicode-bidi: isolate;
}


/* ===== 首页矢量狐(2026-10-28,取代背景视频)=====
   SVG viewBox 950×1100(比例 0.86,竖向),屏幕是 390×844(比例 0.46)——
   所以**不铺满**(铺满会裁掉耳朵或尾巴),而是:居中、宽度占满内容区、底部贴住底栏上方。
   高度用 aspect-ratio 由宽度算出来,超过可视高度就按高度反推宽度(见 max-height)。 */
.screen-home .home-fox {
  position: relative;
  /* 2026-10-30: 这里的 z-index 去掉了。
     它会创建新的层叠上下文，导致 .fox-act 的 mix-blend-mode 只能和容器内部
     （透明）混合 —— 视频的纯黑底混不出去，只能看到一块黑板。
     层级改由 DOM 顺序保证（它在 .bg / .halo 之后、顶栏之前）。 */
  isolation: auto;
  display: grid;
  place-items: end center;          /* 底部对齐:脚踩在底栏上方 */
  width: 100%;
  flex: 1 1 auto;                   /* 吃掉顶栏与底栏之间的全部高度 */
  min-height: 0;
  touch-action: pan-y;              /* 竖向滚动留给页面,横向/视线跟随交给自己 */
  /* SVG 是 950×1100(0.86),屏幕 390×844(0.46):按宽度铺会超出高度,
     所以用 contain 让整只狐狸完整可见,再靠 place-items 沉到底部。 */
}
/* ---------- 动作视频版立绘（2026-10-30 v2）----------
   待机 = 透明 PNG（和浅蓝天空天然融合，视觉与原来一致）
   动作 = 视频铺在深色聚焦遮罩上 → 黑底融进遮罩，看起来像"镜头推近、背景压暗"
   播完 = 遮罩淡出，回到透明 PNG

   为什么不是 mix-blend-mode：主线首页是浅色背景，黑底无论 screen/lighten
   都会显形；而 .home-fox 有 z-index:1 创建了层叠上下文，混合也出不去。
   为什么不用 alpha 通道：colorkey/lumakey 实测抠不干净（会吃掉深色描边）。 */
/* 立绘热区（.home-fox 内绝对定位，百分比坐标） */
.screen-home .home-fox .fox-zone{
  position:absolute;
  z-index:3;
  border-radius:16px;
  background:transparent;
  transition:background .16s;
  cursor:pointer;
  touch-action:manipulation;
}
.screen-home .home-fox .fox-zone:active{background:rgba(255,255,255,.10)}

.screen-home .home-fox.fox-stage{ isolation:auto; }

/* 深色聚焦遮罩 */
.screen-home .home-fox .fox-veil{
  position:absolute;
  left:0; right:0; bottom:0;
  width:100%; height:auto; max-height:100%;
  aspect-ratio:1 / 1;
  margin:0 auto;
  z-index:0;
  border-radius:0;
  /* 最保守的形态：一层很淡的深色圆角底，只为让白毛在浅蓝天空上有对比，
     不做模糊（模糊会让边界更显眼），不做椭圆（圆盘比方块更突兀）。 */
  background:rgba(40,34,58,.28);
  box-shadow:none;
  opacity:0;
  transition:opacity .28s ease;
  pointer-events:none;
}
.screen-home .home-fox.is-acting .fox-veil{opacity:1}

/* ── 2026-11-02：换成真透明素材（motions/*_cutout.webm，VP9 带 alpha 轨道）──
   body.has-alpha 由 fox-model.js 打上（见 MOTION_EXT 的判断）。
   有 alpha 就不用再"消化黑底"：不混合、不加滤镜、不垫暗影，原样叠在天空上。
   没 alpha 的浏览器（Safari）继续走下面那条老规则：lighten 吃掉黑底。 */
body.has-alpha .screen-home .home-fox .fox-veil{
  background:transparent;
  opacity:0 !important;
}
body:not(.has-alpha) .screen-home .home-fox.is-acting .fox-act{
  /* 老素材的兜底：黑(0) 在 lighten 下永远取背景色，黑底就看不见了。
     代价是比背景暗的颜色会被替换，角色偏淡 —— 用亮度补偿拉回来一点。 */
  mix-blend-mode:lighten;
  filter:brightness(1.06) contrast(.98) saturate(1.10);
}

/* ── 2026-11-03：没有 alpha 视频能力的设备（iPhone / iPad Safari 全系）──
   三层按能力递降，哪一层真的画出画面，JS 就给 .home-fox 打上 video-on：
     ① HEVC-alpha 的 <video class="fox-act hevc">  —— 有 <name>_alpha.mov 就走这条（标准路线）
     ② WebGL 双流合成的 <canvas class="fox-gl">    —— 没有 mov 时用 彩色+遮罩 现场合成
     ③ 矢量 SVG 狐（.fox-svg）                     —— 前两条都起不来的**保底**：
                                                    呼吸/眨眼/耳朵/甩尾全在 SVG 里，
                                                    不需要视频，**永远有动作**
   这两版改动的由来：先是黑方块（黑底 mp4 露出），再是「不会动」（落到静图）。 */
.screen-home .home-fox.no-alpha .fox-veil{ display:none !important; }
.screen-home .home-fox.no-alpha .fox-act:not(.hevc){ display:none !important; }
.screen-home .home-fox.no-alpha .fox-svg{ display:block; opacity:1; }
/* 视频真的出画面了，才把 SVG 收起来。**别在这里再给 PNG 加 opacity:1**：
   PNG 有 .has-svg 那条 opacity:0 在管，两处都压着就会和 SVG 叠成两只狐
   （2026-11-03 用户：「两个叠在一起了」）。 */
.screen-home .home-fox.no-alpha.video-on .fox-svg{ display:none !important; }
/* 标准路线：HEVC-alpha 视频本身就是 .fox-act，它必须能上场 */
.screen-home .home-fox.no-alpha .fox-act.hevc{ display:block !important; }
/* iOS 的双流合成画布：几何刻意和 .fox-act 一模一样（同一套底部对齐 + 1.08 放大），
   这样手机和电脑上角色的大小/位置是同一个。 */
.screen-home .home-fox .fox-gl{
  position:absolute;
  left:0; right:0; bottom:0;
  width:100%;
  height:auto;
  max-height:100%;
  aspect-ratio:1 / 1;
  margin:0 auto;
  z-index:3;
  display:block;
  pointer-events:none;
  opacity:0;
  transform:scale(1.08);
  transform-origin:50% 100%;
  transition:opacity .2s ease;
}
.screen-home .home-fox .fox-gl.on{opacity:1}

/* 合成用的两条视频（彩色 + 遮罩）。**不能藏在屏外、也不能 opacity:0** ——
   iOS 会把它们当成不可见元素，直接停掉解码，画布就永远只有一帧（2026-11-03 用户报「不动」）。
   这里给一个贴在视口左下角的 1×1、opacity .01 的可见容器，视频按原尺寸放进去被裁掉：
   元素仍在视口内、仍参与合成，解码就不会停；视觉上它只有一个像素的透明度 1%。 */
.screen-home .home-fox .fox-src{
  position:fixed;
  left:0; bottom:0;
  width:1px; height:1px;
  overflow:hidden;
  opacity:.01;
  pointer-events:none;
  z-index:0;
}
.screen-home .home-fox .fox-src video{
  display:block;
  width:1024px;
  height:2048px;
  pointer-events:none;
}

/* ── 2026-11-02：待机换成 Live2D 模型 ──
   层级：veil(0) < PNG(1) < Live2D canvas(2) < 动作视频(3)
   Live2D 加载成功后 .home-fox 会带上 has-l2d，PNG 淡出、canvas 淡入；
   点热区播动作时 canvas 淡出、视频盖上，播完再淡回来。
   加载失败就是什么都不发生 —— PNG 一直在，产品不受影响。 */
.screen-home .home-fox .fox-l2d-canvas{
  position:absolute;
  inset:0;
  width:100%; height:100%;
  z-index:2;
  opacity:0;
  transition:opacity .35s ease;
  pointer-events:none;          /* 视线跟随由 host 上的 pointermove 处理，canvas 不拦事件 */
}
.screen-home .home-fox .fox-l2d-canvas.on{opacity:1}

/* Live2D 就绪 → PNG 让位（模型自带呼吸/眨眼/物理，比 CSS 呼吸好看得多） */
.screen-home .home-fox.has-l2d .fox-still{
  opacity:0;
  transition:opacity .35s ease;
}

/* ── 2026-11-02：待机也走视频 ──
   待机 = idle-a 循环（首尾差 0.72，肉眼无缝），动作 = 对应那段。
   全程只有一块 video 在台面上，PNG/SVG 都沉下去，切换时没有画风跳变。 */
.screen-home .home-fox .fox-act{z-index:3}
.screen-home .home-fox .fox-act.on{opacity:1}
.screen-home .home-fox.is-idle .fox-svg,
.screen-home .home-fox.is-idle .fox-still{opacity:0}


/* ── 2026-11-02：待机用矢量 SVG 立绘（41 层 / 8.7KB / 不糊）──
   层级：veil(0) < PNG(1) < SVG(2) < 动作视频(3)
   SVG 自带呼吸、眨眼、耳朵摆动、蝴蝶结、尾巴逐点形变、视线跟随、说话嘴型。 */
.screen-home .home-fox .fox-svg{
  position:absolute;
  inset:0;
  z-index:2;
  opacity:0;
  transition:opacity .25s ease;
  pointer-events:none;          /* 视线跟随由 svgHost 的 pointermove 处理 */
}
.screen-home .home-fox .fox-svg.on{opacity:1}
.screen-home .home-fox .fox-svg svg{
  width:100%; height:100%;
  display:block;
  overflow:visible;
}
/* SVG 就绪后 PNG 让位 */
.screen-home .home-fox.has-svg .fox-still{opacity:0; transition:opacity .25s ease}

/* 待机：透明 PNG（呼吸动画保留） */
.screen-home .home-fox .fox-still{
  position:relative; z-index:1;
  width:100%; height:100%;
  object-fit:contain;
  object-position:50% 100%;
  display:block;
  pointer-events:none;
  opacity:1;
  transition:opacity .2s ease;
  animation:foxBreathe 5.2s ease-in-out infinite;
  transform-origin:50% 100%;
}
@keyframes foxBreathe{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(-4px) scale(1.006)}}
.screen-home .home-fox.is-acting .fox-still{opacity:0}

/* 动作视频：叠在遮罩之上、PNG 同一位置 */
.screen-home .home-fox .fox-act{
  position:absolute;
  left:0; right:0; bottom:0;
  width:100%;
  height:auto;
  max-height:100%;
  aspect-ratio:1 / 1;
  margin:0 auto;
  z-index:2;
  object-fit:contain;
  object-position:50% 100%;
  display:block;
  pointer-events:none;
  opacity:0;
  /* 2026-11-02: 圆角去掉了。视频是真透明素材（webm 带 alpha），
     几何裁切只会把角色切掉一块 —— 而且放大 1.12 后那个半径也跟着放大，四角很明显。 */
  border-radius:0;
  background:transparent;
  /* 2026-11-02：视频层要和待机 PNG 的**角色高度**对齐。
     PNG 是 1122x1402 竖版，contain 后角色高约 414px；
     视频是 1024x1024 方形，里面角色占 93% 高，350 的框只能给到 325 —— 小了约 25%。
     用 transform 放大 1.33 倍：350×0.93×1.33 ≈ 433，和 PNG 的 414 基本一致。
     **必须用 transform 而不是 width:133%** —— 后者会改变布局尺寸，把页面撑出横向滚动条。 */
  /* 2026-11-02：1.12 → 1.08。
     1.12 时 Pro Max（容器 390）算出来 437，比 430 屏宽还大 7px —— 两边各溢 3px。
     1.08 的账单：Pro Max 421<430 ✓  14 378<390 ✓  SE 362<375 ✓  iPad 454<768 ✓ */
  transform:scale(1.08);
  transform-origin:50% 100%;
  transition:opacity .2s ease;
}
.screen-home .home-fox.is-acting .fox-act{
  opacity:1;
}

/* 减弱动效：停掉呼吸 */
@media (prefers-reduced-motion: reduce){

  .screen-home .home-fox .fox-still{animation:none}
  .screen-home .home-fox .fox-veil,
  .screen-home .home-fox .fox-act,
  .screen-home .home-fox .fox-still{transition:none}
}

.screen-home .home-fox svg {
  width: 100%;
  height: 100%;
  object-fit: contain;
  overflow: visible;
  display: block;
}
.screen-home .home-fox svg {
  width: 100%;
  height: 100%;
  overflow: visible;                /* 尾巴/耳朵允许越出 viewBox 一点 */
  display: block;
}
/* ---------- 遇见 · 当天视频（2026-11-03） ----------
   .home-fox 是 display:grid + place-items:end center，动态插入的 <video>
   会变成一个 grid 子项、被排到立绘下方。所以这里用绝对定位把它铺满
   整个容器、盖在立绘上，尺寸和 object-fit 跟 .fox-still 保持完全一致
   （contain + 底部对齐），这样静态立绘和视频在页面上的位置分毫不差。

   muted / playsinline / loop 三个属性由 JS 加（iOS 自动播放的硬要求），
   CSS 只管几何。 */
.screen-home .home-fox .daily-video{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:contain;
  object-position:50% 100%;
  z-index:2;                 /* 盖在 .fox-still(z:1) 上，在 .fox-act(z:3) 之下 */
  pointer-events:none;       /* 点击穿透，热区仍然是 .fox-zone */
  display:block;
  background:transparent;
  /* 视频顶边是硬切，直接压在页面的深蓝渐变上会看到一条直线。
     用 mask 把最上面 11% 淡出，让它自己融进背景。 */
  -webkit-mask-image:linear-gradient(to bottom,transparent 0%,#000 11%,#000 100%);
          mask-image:linear-gradient(to bottom,transparent 0%,#000 11%,#000 100%);
  /* 底部同理淡出一点，避免和底栏之间出现硬边 */
  -webkit-mask-composite:source-over;
}

/* 减弱动效:模型只渲一帧静态(见 fox-model.js 的 reduce 分支),这里不再另加动画 */
