/* ============================================================
   Ai宇宙 · Web 版样式
   深色极光主题 + 移动端优先。所有资源走相对路径，
   因此同一套代码在 /Ai宇宙/ 和 /aiyuzhou/ 两个挂载点下都能跑。
   ============================================================ */

:root{
  /* 告诉浏览器本页是暗色，别再做「自动暗化」，否则深色底会被叠一层脏色 */
  color-scheme:dark;
  --bg:#0b0d14;
  --bg-soft:#12151f;
  --panel:rgba(255,255,255,.055);
  --panel-2:rgba(255,255,255,.09);
  --line:rgba(255,255,255,.11);
  --line-strong:rgba(255,255,255,.2);
  --fg:#eef1f8;
  --dim:#9aa3b8;
  --dim-2:#6b7488;
  --accent:#7c9cff;
  --accent-2:#a06cff;
  --danger:#f87171;
  --ok:#4ade80;
  --radius:16px;
  --topbar-h:54px;
  --safe-b:env(safe-area-inset-bottom,0px);
  --safe-t:env(safe-area-inset-top,0px);

  /* 不透明面板（弹窗 / 抽屉 / 卡面 / 代码块的底）——主题切换时要跟着换 */
  --surface:#10131c;
  --surface-2:#141824;
  --code-bg:#0a0c12;
  --topbar-bg:rgba(11,13,20,.72);
  --composer-bg:rgba(11,13,20,.82);
  --tabbar-bg:rgba(11,13,20,.86);
  --toast-bg:rgba(28,32,44,.96);
  --gate-bg:rgba(8,10,17,.9);
  --grad-1:#5d7bf0;
  --grad-2:#8a5ce8;
  /* 极光两团柔光的颜色：跟着「创意」档位换（见 js/theme.js） */
  --aurora-1:rgba(124,156,255,.22);
  --aurora-2:rgba(160,108,255,.18);
  --aurora-3:rgba(72,214,214,.12);
}

/* 浅色主题：与 Android 端 Color.kt 的浅色色板同源 */
html[data-theme="light"]{
  color-scheme:light;
  --bg:#fbf8fa;
  --bg-soft:#ffffff;
  --panel:rgba(20,10,30,.045);
  --panel-2:rgba(20,10,30,.08);
  --line:rgba(20,10,30,.14);
  --line-strong:rgba(20,10,30,.24);
  --fg:#1b1a1f;
  --dim:#5d5a64;
  --dim-2:#8a8694;
  --accent:#d63a6a;
  --accent-2:#9333ea;
  --danger:#dc2626;
  --ok:#16a34a;
  --surface:#ffffff;
  --surface-2:#ffffff;
  --code-bg:#f3eff5;
  --topbar-bg:rgba(251,248,250,.82);
  --composer-bg:rgba(251,248,250,.9);
  --tabbar-bg:rgba(251,248,250,.92);
  --toast-bg:rgba(255,255,255,.98);
  --gate-bg:rgba(246,242,247,.94);
  --aurora-1:rgba(216,58,106,.14);
  --aurora-2:rgba(147,51,234,.12);
  --aurora-3:rgba(56,189,248,.10);
}
html[data-theme="light"] .bubble a{color:#b02a5b}
html[data-theme="light"] .toast{box-shadow:0 10px 26px rgba(20,10,30,.14)}
html[data-theme="light"] .tpl:hover{border-color:rgba(214,58,106,.5)}

*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{height:100%}
body{
  margin:0; background:var(--bg); color:var(--fg);
  font:15px/1.7 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",system-ui,sans-serif;
  overflow:hidden;
}
/* 极光背景：两团柔光缓慢游走，9s 一圈（与 Android 端 AuroraBackdrop 同周期）。
   颜色走 --aurora-* 变量，温度拉到两端时由 js/theme.js 换成 LUXE / EMBER 配色。 */
body::before{
  content:""; position:fixed; top:-25%; right:-25%; bottom:-25%; left:-25%; z-index:0; pointer-events:none;
  background:
    radial-gradient(38% 34% at 16% 18%, var(--aurora-1), transparent 62%),
    radial-gradient(34% 30% at 84% 14%, var(--aurora-2), transparent 64%),
    radial-gradient(42% 36% at 60% 92%, var(--aurora-3), transparent 66%);
  filter:blur(20px);
  transition:background .9s ease;
  animation:auroraDrift 9s ease-in-out infinite alternate;
}
@keyframes auroraDrift{
  from{transform:translate3d(-1.6%,-1.2%,0) scale(1)}
  to  {transform:translate3d(1.6%,1.4%,0) scale(1.07)}
}
/* 创意拉满（天马行空）→ 蓝紫流光加速；拉到零（沉睡）→ 暗红余烬、慢速无星尘 */
html[data-aurora="luxe"]{
  --aurora-1:rgba(147,51,234,.30);
  --aurora-2:rgba(56,189,248,.26);
  --aurora-3:rgba(255,209,102,.18);
}
html[data-aurora="luxe"] body::before{animation-duration:5.2s}
html[data-aurora="ember"]{
  --aurora-1:rgba(180,42,42,.22);
  --aurora-2:rgba(120,26,26,.16);
  --aurora-3:rgba(60,20,20,.12);
}
html[data-aurora="ember"] body::before{animation-duration:16.4s}
button,input,textarea,select{font:inherit;color:inherit}
button{cursor:pointer;border:none;background:none}
/* 滚动条全站隐身：滚轮 / 触摸板 / 手指照常滚，只是不再画那根条。
   App 壳子的 WebView 还会在屏幕右缘补一根系统 overlay 滚动条，
   ::-webkit-scrollbar 置 display:none 一并糊掉；scrollbar-width 管 Firefox。 */
*{scrollbar-width:none; -ms-overflow-style:none}
::-webkit-scrollbar{display:none; width:0; height:0}

/* ------------------------------ 骨架 ------------------------------ */

#app{
  position:relative; z-index:1;
  display:flex; flex-direction:column;
  height:100vh; height:100dvh;
}

/* 三个主视图叠在一起，切谁谁显示（绝对定位，避免切换时布局跳动） */
#views{flex:1; min-height:0; position:relative}
.view{position:absolute; top:0; right:0; bottom:0; left:0; display:none; flex-direction:column; min-height:0}
.view.on{display:flex}

/* ------------------------------ 底部导航 ------------------------------ */

.tabbar{
  flex:none; display:flex; align-items:stretch;
  padding-bottom:var(--safe-b);
  border-top:1px solid var(--line);
  background:var(--tabbar-bg); backdrop-filter:blur(16px) saturate(150%);
}
.tab{
  flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:3px; padding:6px 0 5px; color:var(--dim-2); font-size:11px;
  transition:color .15s;
}
.tab svg{width:22px; height:22px; display:block; transition:padding .18s ease, background .18s ease}
.tab.on{color:var(--fg); font-weight:600}
/* 选中项：图标外面套一颗胶囊（对齐 Android 端 BottomNavBar 的玫红胶囊） */
.tab.on svg{
  box-sizing:content-box; padding:3px 14px; border-radius:13px;
  background:linear-gradient(135deg,var(--grad-1),var(--grad-2)); color:#fff;
}
.tab:active svg{transform:scale(.92)}

.topbar{
  flex:none; height:calc(var(--topbar-h) + var(--safe-t));
  padding-top:var(--safe-t);
  display:flex; align-items:center; gap:6px; padding-left:8px; padding-right:8px;
  border-bottom:1px solid var(--line);
  background:var(--topbar-bg); backdrop-filter:blur(16px) saturate(150%);
}
.topbar .title{
  flex:1; min-width:0; display:flex; flex-direction:column; align-items:center;
  justify-content:center; line-height:1.25;
}
.topbar .title .name{
  max-width:52vw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  font-size:15px; font-weight:600;
}
.topbar .title .sub{font-size:11px; color:var(--dim-2); margin-top:1px}
/* 右侧已无按钮，用等宽占位把标题钉回正中间（不可见、不可点） */
.topbar .topbar-pad{flex:none; width:38px; height:38px}
.icon-btn{
  flex:none; width:38px; height:38px; border-radius:11px;
  display:flex; align-items:center; justify-content:center;
  color:var(--fg); transition:background .15s;
}
.icon-btn:hover{background:var(--panel-2)}
.icon-btn:active{background:rgba(255,255,255,.16)}
.icon-btn[disabled]{opacity:.35; pointer-events:none}
.icon-btn svg{width:20px;height:20px;display:block}

/* ------------------------------ 消息区 ------------------------------ */

#messages{
  flex:1; min-height:0; overflow-y:auto; overscroll-behavior:contain;
  padding:18px 14px 8px; scroll-behavior:smooth;
}
/* 消息区得压到背景层上面，否则整块聊天内容会被图盖住 */
#messages{position:relative; z-index:1}
.msg{display:flex; flex-direction:column; margin-bottom:18px; animation:pop .22s ease-out}
@keyframes pop{from{opacity:0; transform:translateY(6px)} to{opacity:1; transform:none}}
.msg.user{align-items:flex-end}
.msg.assistant{align-items:flex-start}

.bubble{
  max-width:min(86%,660px); padding:11px 14px; border-radius:var(--radius);
  word-break:break-word; overflow-wrap:anywhere;
  font-size:15px; line-height:1.75;
}
/* 聊天页背景图：当前卡的封面，cover 铺满 + 压到 15% 存在感，只在没有内容的地方透出来。
   上下两端用底色渐隐收口，跟顶栏 / 输入栏接得上；光晕（body::before）照旧在它下面。 */
.chat-bg{
  position:absolute; top:0; right:0; bottom:0; left:0; z-index:0; pointer-events:none;
  background-size:cover; background-position:center; background-repeat:no-repeat;
  opacity:0; transition:opacity .5s ease;
}
.chat-bg.on{opacity:.13}
.chat-bg::after{
  content:""; position:absolute; top:0; right:0; bottom:0; left:0;
  background:linear-gradient(to bottom, var(--bg) 0%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 74%, var(--bg) 100%);
}
html[data-theme="light"] .chat-bg.on{opacity:.11}
/* 消息区得压到背景层上面，否则整块聊天内容会被图盖住 */
#messages{position:relative; z-index:1}

.msg.user .bubble{
  background:linear-gradient(135deg,var(--grad-1),var(--grad-2)); color:#fff;
  border-bottom-right-radius:6px; box-shadow:0 6px 20px rgba(93,123,240,.22);
}
.msg.assistant .bubble{
  background:var(--panel); border:1px solid var(--line); color:var(--fg);
  border-bottom-left-radius:6px;
}
.msg.assistant .bubble.error{border-color:rgba(248,113,113,.4); background:rgba(248,113,113,.1)}
.msg.assistant .bubble.cancelled{opacity:.72}

/* 思考过程：只显示状态（脉冲点 + 字数），展开查看的入口已禁用。
   🔴 2026-10-24 用户口径：「不想让用户看见思考中的内容」
   🔴 2026-10-28 口径收紧：不可展开，.rz-head 不再是按钮，思考正文永远不进 DOM。 */
.reasoning{
  margin:0 0 10px; border-radius:11px; overflow:hidden;
  background:rgba(255,255,255,.045); border:1px dashed var(--line);
}
.reasoning .rz-head{
  width:100%; display:flex; align-items:center; gap:6px;
  padding:8px 12px; background:none; color:var(--dim-2); font-size:12px;
  text-align:left; cursor:default;
}
.reasoning .rz-head svg{width:14px; height:14px; flex:none}
.reasoning .rz-head .rz-lb{flex:1; min-width:0}

/* 「它在跑」的活证：脉冲小点（只在流式中出现） */
.rz-dot{width:6px; height:6px; flex:none; margin:0 3px 0 1px; border-radius:50%;
  background:var(--accent); animation:rzpulse 1.1s ease-in-out infinite}
@keyframes rzpulse{0%,100%{opacity:.25; transform:scale(.75)}50%{opacity:1; transform:scale(1.15)}}
/* 结束后的定格字数 */
.rz-num{color:var(--fg); font-variant-numeric:tabular-nums}
.rz-unit{margin-left:1px}
/* odometer：每位一列视口（overflow:hidden）+ 里面的 0-9 条带，末尾多一个 0 供 9→0
   继续往上滚。--d 控 translateY：整数 = 停稳，小数 = 正滚过去。
   🔴 改数字高度要同步 .odo-d / .odo-d u / .odo-d b 三处（差 1px 就会露出半行）。
   🔴 条带必须走合成层（translate3d + will-change）：滚动时只有 GPU 在平移这一列，
      不再每帧重新排版/栅格化文字 —— 这就是「平滑」和「一顿一顿」的分界。 */
.rz-odo{display:inline-flex; height:13px; color:var(--fg)}
/* contain:layout paint：这一列不参与外面任何排版，滚动时重绘范围也被钉死在本列 ——
   不给它划边界，浏览器每帧都要向上确认「这玩意儿会不会撑到别处去」。 */
.odo-d{display:inline-block; width:8px; height:13px; overflow:hidden; vertical-align:bottom;
  contain:layout paint}
.odo-d u{display:block; text-decoration:none; will-change:transform;
  backface-visibility:hidden;
  transform:translate3d(0, calc(var(--d,0) * -13px), 0)}
.odo-d b{display:block; height:13px; line-height:13px; text-align:center;
  font-weight:inherit; font-style:normal; font-variant-numeric:tabular-nums}

.typing{display:inline-block; width:7px; height:15px; vertical-align:-2px;
  background:var(--accent); border-radius:2px; animation:blink 1s steps(2) infinite}
@keyframes blink{50%{opacity:0}}

.actions{
  display:flex; align-items:center; gap:2px; margin-top:5px; padding:0 4px;
  opacity:.5; transition:opacity .15s; font-size:11.5px; color:var(--dim-2);
}
.msg:hover .actions,.actions:focus-within{opacity:1}
.actions .time{margin-right:4px; font-variant-numeric:tabular-nums}
/* 用户气泡与助手气泡的操作栏都保持「时间在前、图标在后」，
   靠 .msg.user 的 align-items:flex-end 把整条操作栏推到右边即可，不要翻转顺序。 */
.act{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;color:var(--dim)}
.act:hover{background:var(--panel-2); color:var(--fg)}
.act svg{width:15px;height:15px}

.empty{
  height:100%; display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:10px; color:var(--dim-2); text-align:center; padding:30px;
}
.empty .big{font-size:17px; color:var(--dim)}

/* Markdown 内部 */
.bubble p{margin:0 0 9px}
.bubble p:last-child{margin-bottom:0}
.bubble h1,.bubble h2,.bubble h3,.bubble h4{margin:14px 0 8px; line-height:1.4}
.bubble h1{font-size:19px} .bubble h2{font-size:17px} .bubble h3{font-size:15.5px}
.bubble ul,.bubble ol{margin:6px 0; padding-left:22px}
.bubble li{margin:2px 0}
.bubble blockquote{margin:8px 0; padding:4px 0 4px 12px; border-left:3px solid var(--line-strong); color:var(--dim)}
.bubble hr{border:none; border-top:1px solid var(--line); margin:12px 0}
.bubble a{color:#9db4ff}
.bubble code{
  background:rgba(255,255,255,.12); padding:1.5px 5px; border-radius:5px;
  font-family:ui-monospace,SFMono-Regular,Consolas,monospace; font-size:.9em;
}
.md-code{
  margin:9px 0; padding:11px 13px; border-radius:11px; overflow-x:auto;
  background:var(--code-bg); border:1px solid var(--line);
}
.md-code code{
  background:none; padding:0; font-size:12.5px; line-height:1.65;
  font-family:ui-monospace,SFMono-Regular,Consolas,monospace; white-space:pre;
}
.bubble table{border-collapse:collapse; margin:9px 0; font-size:13.5px; display:block; overflow-x:auto}
.bubble th,.bubble td{border:1px solid var(--line); padding:5px 9px; text-align:left}
.bubble th{background:rgba(255,255,255,.06)}

/* ------------------------------ 输入栏 ------------------------------ */

.composer{
  /* 底部安全区交给 .tabbar 统一兜住，这里不用再留一遍 */
  flex:none; padding:8px 12px 10px;
  border-top:1px solid var(--line);
  background:var(--composer-bg); backdrop-filter:blur(16px) saturate(150%);
}
.composer .box{
  display:flex; align-items:flex-end; gap:8px;
  background:var(--panel); border:1px solid var(--line); border-radius:20px;
  padding:6px 6px 6px 14px; transition:border-color .15s;
}
.composer .box:focus-within{border-color:rgba(124,156,255,.55)}
.composer textarea{
  flex:1; min-width:0; max-height:150px; min-height:26px;
  background:none; border:none; outline:none; resize:none;
  padding:5px 0; line-height:1.6; font-size:15px;
}
.composer .send{
  flex:none; width:36px; height:36px; border-radius:50%;
  background:linear-gradient(135deg,var(--grad-1),var(--grad-2)); color:#fff;
  display:flex; align-items:center; justify-content:center;
  transition:opacity .15s, transform .12s;
}
.composer .send:active{transform:scale(.93)}
.composer .send[disabled]{opacity:.4; pointer-events:none}
.composer .send svg{width:18px;height:18px}
.composer .send.stop{background:rgba(248,113,113,.9)}

/* ------------------------------ 抽屉 ------------------------------ */

#scrim{
  position:fixed; top:0; right:0; bottom:0; left:0; z-index:20; background:rgba(0,0,0,.5);
  opacity:0; pointer-events:none; transition:opacity .2s;
}
#scrim.on{opacity:1; pointer-events:auto}

#drawer{
  position:fixed; z-index:21; top:0; bottom:0; left:0; width:min(83vw,310px);
  background:var(--surface); border-right:1px solid var(--line);
  transform:translateX(-102%); transition:transform .24s cubic-bezier(.2,.8,.2,1);
  display:flex; flex-direction:column;
  padding-top:var(--safe-t); padding-bottom:var(--safe-b);
}
#drawer.on{transform:none}
.drawer-head{
  flex:none; padding:14px 14px 10px; display:flex; flex-wrap:wrap; align-items:center; gap:10px;
  border-bottom:1px solid var(--line);
}
.drawer-head .who-btn{
  flex:1; min-width:0; display:flex; align-items:center; gap:10px;
  padding:4px; margin:-4px; border-radius:13px; text-align:left; color:inherit;
}
/* 整块可点：悬停时给一点底，让「这里能点」看得出来（手机上靠 touch 反馈） */
.drawer-head .who-btn:hover{background:var(--panel)}
.drawer-head .who-btn:active{background:var(--panel-2)}
.drawer-head .who{flex:1; min-width:0}
.drawer-head .who b{display:block; font-size:14px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.drawer-head .who span{
  display:block; font-size:11.5px; color:var(--dim-2);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.drawer-head .who-btn:hover .who b{color:var(--accent)}
.drawer-head .avatar{
  flex:none; width:34px;height:34px;border-radius:50%;
  background:linear-gradient(135deg,var(--grad-1),var(--grad-2));
  background-size:cover; background-position:center;
  display:flex;align-items:center;justify-content:center;font-size:15px;font-weight:600;color:#fff;
}
/* 有图时内联 background-image 会盖掉上面那条渐变，底色换成素的：
   万一图是透明 PNG，边缘露出的是底色而不是紫蓝光。 */
.drawer-head .avatar.has-img{background-color:var(--panel-2)}
#sessionList{flex:1; min-height:0; overflow-y:auto; padding:8px}
.sess{
  --sww:0px;   /* 左滑已让出的宽度 0 → 46px：拖拽时由 JS 实时写，松手后由 .sw / 默认值接管 */
  display:flex; align-items:center; gap:8px; padding:10px 11px; border-radius:12px;
  cursor:pointer; color:var(--dim); transition:background .12s;
  /* 左滑露出删除键：正文一层，红色删除区一层 */
  position:relative; overflow:hidden;
}
.sess:hover{background:var(--panel)}
.sess.on{background:var(--panel-2); color:var(--fg)}
.sess.sw{--sww:46px}
/* 左滑露出的删除区：从右边长出来的红块，手指拖到哪儿红就到哪儿
 * 宽度 0 时自然看不见，所以不用再叠透明度 */
.sess::after{
  content:''; position:absolute; top:0; right:0; bottom:0; z-index:0;
  width:var(--sww);
  background:linear-gradient(135deg,#f87171,#d92d2d);
  pointer-events:none;
  transition:width .2s cubic-bezier(.2,.8,.2,1);
}
.sess.dragging::after{transition:none}
.sess .txt{
  flex:1; min-width:0; position:relative; z-index:2;
  /* 正文不搬家，只是右边让出同样多（行右内边距 11px 也算在里面）——
   * 标题于是不会被挤出左边缘切掉开头，让出的位置正好被红块占掉 */
  padding-right:max(0px, calc(var(--sww) - 11px));
  transition:padding-right .2s cubic-bezier(.2,.8,.2,1);
}
.sess.dragging .txt{transition:none}
.sess .txt b{display:block; font-size:13.5px; font-weight:500; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:inherit}
.sess .txt span{font-size:11px; color:var(--dim-2)}
.sess .del{
  position:absolute; right:8px; top:50%; margin-top:-13px; z-index:1;
  width:26px;height:26px;border-radius:8px;display:flex;align-items:center;justify-content:center;
  opacity:0;color:var(--dim); transition:opacity .15s, background .15s, color .15s;
}
.sess:hover .del, .sess.sw .del{opacity:1}
.sess .del:hover{background:rgba(248,113,113,.18); color:var(--danger)}
/* 滑开时：图标那块铺满整条红区 —— 看着是红块，点的也是红块，
 * 触摸目标从 26×26 变成 46×整行高，手指糊一点也点得中；
 * z-index 要压到正文之上，否则红区左边那截被透明的正文盒盖住、点不到 */
.sess.sw .del{
  right:0; top:0; bottom:0; margin-top:0; z-index:3;
  width:46px; height:auto; border-radius:0 12px 12px 0;
  background:none; color:#fff;
}
.sess .del svg{width:14px;height:14px}
.sess.sw .del svg{width:16px;height:16px}
.sess.sw .del:hover{background:rgba(0,0,0,.16); color:#fff}
.sess.sw .del:active{background:rgba(0,0,0,.26)}
/* 桌面（有鼠标、能 hover）也走同一套：悬停即让出 46px 露出红块。
 * 以前 hover 只是把灰图标透在标题末尾，而那位置被正文盖着根本点不到 —— 鼠标用户没有删除入口 */
@media (hover:hover) and (pointer:fine){
  .sess:hover{--sww:46px}
  .sess:hover .del{
    right:0; top:0; bottom:0; margin-top:0; z-index:3;
    width:46px; height:auto; border-radius:0 12px 12px 0;
    background:none; color:#fff; opacity:1;
  }
  .sess:hover .del:hover{background:rgba(0,0,0,.16); color:#fff}
}
.drawer-foot{flex:none; padding:10px; border-top:1px solid var(--line); display:flex; align-items:center; gap:8px}
.drawer-foot .btn{flex:1; min-width:0}
.drawer-foot .icon-btn{flex:none; width:40px; height:40px; color:var(--dim)}
.drawer-foot .icon-btn:hover{color:var(--danger); background:rgba(248,113,113,.14)}

/* 余额进度条：当前积分 / 历史最高（账号区第二行，整行铺满抽屉头）。
   历史最高只增不减，所以条子只会「掉下来」，涨回去时会顶到 100%（见 noteQuotaPeak）。
   它同时是积分入口：点一下展开积分明细（原先那个独立角标已并进来）。 */
.quota-pk{
  flex:1 0 100%; min-width:0; display:flex; flex-direction:column; gap:5px;
  padding:5px 8px; margin:1px -8px -3px; border-radius:10px;
  cursor:pointer; transition:background .15s;
}
.quota-pk[hidden]{display:none}      /* hidden 打不过 display:flex，得显式兜住 */
.quota-pk:hover{background:var(--panel)}
.quota-pk:active{background:var(--panel-2)}
.quota-pk:focus-visible{outline:2px solid rgba(124,156,255,.55); outline-offset:-1px}
.quota-pk-top{display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between;
  gap:2px 10px; font-size:10.5px; color:var(--dim-2); font-variant-numeric:tabular-nums}
.quota-pk-top b{font-weight:600; color:var(--fg)}
/* 百分比：小额消耗在接近历史最高时几乎看不出来，给个数字跟着走 */
.quota-pk-pct{font-style:normal; margin-left:6px; font-size:10px; color:var(--dim-2)}
.quota-pk.due .quota-pk-pct{color:var(--accent)}   /* 今天还没签到，点条子去签 */
.quota-pk-bar{height:5px; border-radius:999px; background:var(--panel-2);
  box-shadow:inset 0 0 0 1px var(--line); overflow:hidden}
.quota-pk-bar i{display:block; height:100%; width:0; border-radius:inherit;
  background:linear-gradient(90deg,var(--grad-1),var(--grad-2));
  transition:width .5s cubic-bezier(.2,.8,.2,1)}
/* 余额真的变了：条子亮一下（宽度差异常常不到 1px，光看宽度看不出在扣） */
@keyframes quotaBarTick{0%{filter:brightness(1.7) saturate(1.35)}100%{filter:none}}
.quota-pk-bar i.tick,.q-pk-bar i.tick{animation:quotaBarTick .45s ease-out}

/* 通用按钮 */
.btn{
  display:flex; align-items:center; justify-content:center; gap:7px;
  padding:10px 14px; border-radius:12px; font-size:14px;
  border:1px solid var(--line); background:var(--panel); color:var(--fg);
  transition:background .15s;
}
.btn:hover{background:var(--panel-2)}
.btn:active{transform:translateY(1px)}
.btn.primary{background:linear-gradient(135deg,var(--grad-1),var(--grad-2)); border-color:transparent; color:#fff; font-weight:600}
.btn.ghost{background:none}
.btn.danger{color:var(--danger); border-color:rgba(248,113,113,.3)}
.btn[disabled]{opacity:.5; pointer-events:none}
.btn svg{width:16px;height:16px}

/* ------------------------------ 弹窗 ------------------------------ */

.modal-root{position:fixed; top:0; right:0; bottom:0; left:0; z-index:30; display:none}
.modal-root.on{display:block}
.modal-mask{position:absolute; top:0; right:0; bottom:0; left:0; background:rgba(0,0,0,.62); backdrop-filter:blur(3px)}
.modal-card{
  position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
  /* 宽高都自适应：宽 = 可视区留一条呼吸缝（宽屏再收进 480px 台子），
     高 = 内容说了算、最长不超过可视区。dvh 照顾手机浏览器工具栏伸缩 */
  width:min(calc(100vw - 24px),480px);
  /* 🔴 max-height 必须 vh 先行兜底、dvh 再覆盖：壳子 WebView 内核旧于 Chrome 108
     时不认识 dvh 单位，整条声明会被判无效直接丢弃 —— max-height 没了，卡片高度跟着
     内容飙出屏幕，overflow-y:auto 在 auto 高度上又不产生滚动，表现为「超屏后滑不动」
     （2026-10-17 用户真机报的）。vh 兜底在支持 dvh 的浏览器里会被下面那条覆盖，无 tradeoff。 */
  max-height:86vh;
  max-height:calc(100dvh - 24px);
  overflow-y:auto; overscroll-behavior:contain;
  background:var(--surface); border:1px solid var(--line); border-radius:20px;
  padding:22px; box-shadow:0 30px 70px rgba(0,0,0,.6);
  animation:pop .2s ease-out;
}
/* 🔴 入场动画必须带着 -50% 居中的 transform 一起动：直接复用 .msg 那个 pop 的话，
   动画期间 translate(-50%,-50%) 被 keyframe 的 transform:none 顶掉 —— 实测整个
   200ms 卡片都偏在右侧（x=640），动画结束才啪地跳回居中（x=360）。
   gate 是全屏（transform:none）、≤860px 的编辑器也铺满（transform:none），
   那两个继续用原版 pop，别吃这个。 */
.modal-card:not(.gate){animation:cardPop .2s ease-out}
@keyframes cardPop{
  from{opacity:0; transform:translate(-50%,-50%) translateY(8px) scale(.97)}
  to{opacity:1; transform:translate(-50%,-50%)}
}
/* 编辑器 / 卡片详情是「页面级」弹窗：宽屏上也比普通对话弹窗宽一档 */
.modal-card:has(.ed){width:min(calc(100vw - 24px),560px)}

/* 🔴 手机 / 折叠屏 / 小平板（≤860px）：弹窗铺满可视宽。
   断点取 860 而不是老掉牙的 520——折叠屏内屏、部分平板报的 CSS 宽有 680+，
   520 根本抓不到，那种机器上弹窗一直是 430px 居中信箱，两侧黑边能停航母。 */
@media (max-width:860px){
  .modal-card:not(.gate){
    width:calc(100vw - 16px);
    max-height:calc(100vh - 16px);
    max-height:calc(100dvh - 16px);
  }
  /* 编辑器是张长表单（对齐 Android 的全屏编辑页）：直接铺满整屏，不留缝 */
  .modal-card:has(.ed){
    left:0; top:0; right:auto; bottom:auto; transform:none;
    width:100vw;
    height:100vh; height:100dvh;
    max-height:100vh; max-height:100dvh;
    border-radius:0; border-left:none; border-right:none; border-top:none;
    padding:calc(22px + var(--safe-t)) 20px calc(22px + var(--safe-b));
    animation:pop .2s ease-out;   /* 铺满态 transform:none，吃原版 pop */
  }
  /* 铺满后 sticky 表头的补偿量要连 safe-t 一起算，否则会多陷进去一截 */
  .modal-card:has(.ed) .ed-head{top:calc(-22px - var(--safe-t))}
}
.modal-card.gate{
  position:fixed; left:0; right:0; top:0; bottom:0; transform:none;
  width:100%; max-height:none; border:none; border-radius:0;
  display:flex; align-items:center; justify-content:center;
  /* 门禁挡在最前面时，后面那套聊天骨架别透出来刷存在感 */
  background:
    radial-gradient(64% 46% at 50% 0%, var(--aurora-1), transparent 72%),
    radial-gradient(50% 40% at 88% 100%, var(--aurora-2), transparent 74%),
    var(--gate-bg);
  backdrop-filter:blur(12px) saturate(130%);
  padding:20px;
}
/* 门禁态（登录 / 强制设置）里面的内容盒子：普通弹窗时 .sheet 是透明的，不额外加壳 */
.modal-card.gate .sheet{
  width:min(calc(100vw - 24px),430px);
  max-height:86vh; max-height:calc(100dvh - 24px);   /* vh 兜底，理由同 .modal-card */
  overflow-y:auto;
  background:var(--surface); border:1px solid var(--line); border-radius:20px;
  padding:24px; box-shadow:0 30px 70px rgba(0,0,0,.6);
}
.modal-card h2{margin:0 0 4px; font-size:19px}
.modal-card .desc{color:var(--dim); font-size:13px; margin-bottom:18px}
.field{margin-bottom:13px}
.field label{display:block; font-size:12.5px; color:var(--dim); margin-bottom:6px}
.field input[type=text],.field input[type=password],.field input[type=number],.field input[type=email],.field textarea{
  width:100%; padding:11px 13px; border-radius:12px;
  background:rgba(255,255,255,.05); border:1px solid var(--line);
  outline:none; transition:border-color .15s;
}
.field textarea{resize:vertical; min-height:78px; line-height:1.6}
.field input:focus,.field textarea:focus{border-color:rgba(124,156,255,.6)}
.field .tip{font-size:11.5px; color:var(--dim-2); margin-top:5px}
.field .tip.ok{
  display:flex; align-items:center; gap:7px; margin-top:0;
  color:var(--ok); font-size:12.5px;
  background:rgba(74,222,128,.08); border:1px solid rgba(74,222,128,.22);
  border-radius:12px; padding:10px 12px;
}
.field .tip.ok svg{width:15px; height:15px; flex:none}
.key-row{display:flex; align-items:center; gap:8px}
.key-row input{flex:1; min-width:0}
.key-row .icon-btn{
  width:42px; height:42px; border-radius:12px;
  border:1px solid var(--line); background:rgba(255,255,255,.05); color:var(--dim);
}
.key-row .icon-btn:hover{background:rgba(255,255,255,.1); color:var(--fg)}
.row2{display:flex; gap:10px}
.row2 > *{flex:1}
/* 开关：左右滑的那种，不再是「方框 + 对勾」。
   原生勾选框只当状态载体（点击 / 键盘照样能切），视觉全由 input 自己画：
   轨道是背景色，滑块是 background-image 里那枚白色圆点，靠 background-position 左右跑。
   文字在左、开关在右（order 把 input 拨到末尾），和手机系统开关一个手感。 */
.switch-row{
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  margin:14px 0; cursor:pointer;
}
.switch-row input{
  order:2; flex:none;
  -webkit-appearance:none; appearance:none;
  width:52px; height:30px; margin:0; border-radius:999px;
  background-color:var(--panel-2);
  background-image:radial-gradient(circle at 50% 50%, #fff 0 9.5px, #e7eaf3 10.5px, rgba(231,234,243,0) 11px);
  background-repeat:no-repeat;
  background-size:24px 24px;
  background-position:3px center;
  box-shadow:inset 0 0 0 1px var(--line);
  transition:background-color .22s ease, background-position .22s cubic-bezier(.4,.05,.2,1), box-shadow .22s ease;
  cursor:pointer;
}
.switch-row input:checked{
  background-color:var(--accent);
  background-image:radial-gradient(circle at 50% 50%, #fff 0 9.5px, #e7eaf3 10.5px, rgba(231,234,243,0) 11px);
  background-position:calc(100% - 3px) center;
  box-shadow:inset 0 0 0 1px transparent, 0 0 12px -2px var(--accent);
}
.switch-row input:active{transform:scale(.97)}
.switch-row input:focus-visible{outline:2px solid var(--accent); outline-offset:3px}
.switch-row span{font-size:13.5px; line-height:1.5}
/* 浅色主题：--panel-2 太淡，白圆点会糊进背景，轨道压深一点 */
html[data-theme="light"] .switch-row input{background-color:rgba(20,10,30,.16)}
html[data-theme="light"] .switch-row input:checked{background-color:var(--accent)}
.modal-actions{display:flex; gap:10px; margin-top:20px}
.modal-actions .btn{flex:1}
.err{color:var(--danger); font-size:13px; margin-top:10px; min-height:18px}
.ok{color:var(--ok); font-size:13px; margin-top:10px; min-height:18px}

/* ---------------------- 登录 / 注册 / 找回 门禁 ---------------------- */

.modal-card.gate{padding:calc(18px + var(--safe-t)) 18px calc(18px + var(--safe-b))}
.gate-sheet{animation:pop .22s ease-out}
.gate-sheet.shake{animation:shake .34s ease-in-out}
@keyframes shake{
  0%,100%{transform:translateX(0)}
  18%{transform:translateX(-7px)}
  36%{transform:translateX(6px)}
  54%{transform:translateX(-4px)}
  72%{transform:translateX(2px)}
}

.gate-brand{text-align:center; margin:4px 0 22px}
.gate-logo{
  width:54px; height:54px; margin:0 auto 13px; border-radius:17px;
  display:flex; align-items:center; justify-content:center; color:#fff;
  background:linear-gradient(135deg,var(--grad-1),var(--grad-2));
  box-shadow:0 12px 30px rgba(110,124,240,.42), inset 0 1px 0 rgba(255,255,255,.28);
}
.gate-logo svg{width:27px;height:27px}
.gate-brand h2{margin:0; font-size:22px; letter-spacing:.5px; font-weight:650}
.gate-brand p{margin:7px 0 0; font-size:12.5px; color:var(--dim-2)}

.gate-back{
  display:flex; align-items:center; gap:1px; margin:-6px 0 12px -9px;
  padding:6px 10px 6px 5px; border-radius:10px; color:var(--dim); font-size:13px;
}
.gate-back:hover{background:var(--panel-2); color:var(--fg)}
.gate-back svg{width:17px;height:17px}

/* 带图标的输入框 */
.in-wrap{position:relative; display:flex; align-items:center}
/* hidden 属性在作者样式表里打不过 display:flex，必须显式兜住 */
.in-wrap[hidden]{display:none}
.field[hidden]{display:none}
.in-wrap .in-ico{position:absolute; left:12px; display:flex; color:var(--dim-2); pointer-events:none}
.in-wrap .in-ico svg{width:16px;height:16px}
.in-wrap input{padding-left:38px !important; padding-right:44px !important}
.in-wrap:focus-within .in-ico{color:var(--accent)}
.in-eye{
  position:absolute; right:6px; width:34px; height:34px; border-radius:9px;
  display:flex; align-items:center; justify-content:center; color:var(--dim-2);
}
.in-eye:hover{background:var(--panel-2); color:var(--fg)}
.in-eye.on{color:var(--accent)}
.in-eye svg{width:17px;height:17px}

/* 验证码行内的「获取验证码」小按钮（.in-eye 的加宽版） */
.in-send{
  position:absolute; right:5px; height:32px; padding:0 11px; border-radius:9px;
  display:flex; align-items:center; font-size:12.5px; color:var(--accent);
  background:rgba(124,156,255,.12); white-space:nowrap;
}
.in-send:hover{background:rgba(124,156,255,.22)}
.in-send:disabled{color:var(--dim-2); background:rgba(255,255,255,.05)}
.code-wrap input{padding-right:104px !important}

/* 主按钮 */
.gate-go{width:100%; padding:12px 14px; font-size:15px; margin-top:4px}
.gate-go.loading{pointer-events:none}
.gate-go.loading > svg{display:none}
.gate-go.loading::before{
  content:""; flex:none; width:15px; height:15px; border-radius:50%;
  border:2px solid rgba(255,255,255,.35); border-top-color:#fff;
  animation:spin .7s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg)}}
.gate-go span{white-space:nowrap; overflow:hidden; text-overflow:ellipsis}

/* 底部小链接 */
.gate-links{display:flex; align-items:center; justify-content:center; gap:14px; margin-top:18px}
.link-btn{color:var(--dim); font-size:13px; padding:4px 2px; border-radius:8px}
.link-btn:hover{color:var(--accent)}
.link-btn.hot{color:var(--accent); text-decoration:underline; text-underline-offset:3px}
.link-dot{flex:none; width:3px; height:3px; border-radius:50%; background:var(--dim-2)}

/* 说明块 + 找回密码的三步骤 */
.note{
  font-size:12.5px; color:var(--dim); line-height:1.75;
  background:rgba(255,255,255,.04); border:1px solid var(--line);
  border-radius:12px; padding:10px 12px; margin:0 0 16px;
}
.steps{display:flex; flex-direction:column; gap:10px}
.step{
  display:flex; gap:11px; padding:12px;
  background:rgba(255,255,255,.035); border:1px solid var(--line); border-radius:14px;
}
.step-no{
  flex:none; width:22px; height:22px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  font-size:12px; font-weight:700;
  background:rgba(124,156,255,.16); color:var(--accent); border:1px solid rgba(124,156,255,.3);
}
.step-body{flex:1; min-width:0; display:flex; flex-direction:column; align-items:flex-start; gap:6px}
.step-body b{font-size:13.5px; font-weight:600}
.step-body span{font-size:12px; color:var(--dim-2); line-height:1.65}
.step-body .btn{margin-top:2px; padding:7px 12px; font-size:13px}
.step-body .in-wrap{width:100%}

/* 错误条 */
.gate-err{
  display:none; margin-top:12px; padding:9px 12px; border-radius:11px;
  background:rgba(248,113,113,.1); border:1px solid rgba(248,113,113,.32);
  color:#ffb4b4; font-size:12.5px; line-height:1.6; min-height:0;
}
.gate-err.on{display:block; animation:pop .18s ease-out}

@media (max-width:520px){
  .modal-card.gate{padding:calc(12px + var(--safe-t)) 12px calc(12px + var(--safe-b))}
  .modal-card.gate .sheet{width:100%; max-height:94vh; max-height:94dvh; padding:22px 18px}
}

/* 滑块：轨道 30px 的胶囊，圆点也是 30px —— 正好被轨道整个包住，上下不留缝，
   和右边那排开关同一高度，一行里两个控件看着才像一家人。
   （圆点比轨道小时，已选渐变的切口会在圆点上下露出两条竖线，很脏。）
   轨道和「已选进度」是独立的一层（.range-box i + ::after），
   input 自己只留透明轨道和白色圆点 —— 别指望 range 伪元素画背景，
   Chrome 对它 background-image 的解析时灵时不灵，踩过一次就不想再踩。 */
.range-box{position:relative; flex:1; min-width:0; height:44px; display:flex; align-items:center}
.range-box i{
  position:absolute; left:0; right:0; top:50%; transform:translateY(-50%);
  height:30px; border-radius:999px; overflow:hidden;
  background:var(--panel-2); box-shadow:inset 0 0 0 1px var(--line);
  pointer-events:none;
}
.range-box i::after{
  content:''; position:absolute; left:0; top:0; bottom:0; width:var(--p,0%);
  background:linear-gradient(90deg,var(--grad-1),var(--grad-2));
}
.range-box input[type=range]{
  position:relative; z-index:1; width:100%; height:44px; margin:0;
  -webkit-appearance:none; appearance:none;
  background:transparent; cursor:pointer;
}
/* 透明轨道的高度必须跟 .range-box i 一致，圆点的 margin-top 才是相对它算的 */
.range-box input[type=range]::-webkit-slider-runnable-track{
  height:30px; border-radius:999px; background:transparent;
}
.range-box input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none; appearance:none;
  width:30px; height:30px; border-radius:50%; border:0; margin-top:0;
  background:#fff;
  box-shadow:0 2px 9px rgba(0,0,0,.45), 0 0 0 1px rgba(255,255,255,.18);
}
.range-box input[type=range]:active::-webkit-slider-thumb{transform:scale(1.05)}
.range-box input[type=range]::-moz-range-track{
  height:30px; border-radius:999px; background:transparent; border:0;
}
.range-box input[type=range]::-moz-range-thumb{
  width:30px; height:30px; border:0; border-radius:50%; background:#fff;
  box-shadow:0 2px 9px rgba(0,0,0,.45);
}

.slider-row{display:flex; align-items:center; gap:14px}
.slider-row .val{width:42px; text-align:right; font-size:13.5px; color:var(--accent); font-variant-numeric:tabular-nums}

/* ------------------------------ 轻提示 ------------------------------ */

#toastRoot{position:fixed; left:0; right:0; bottom:calc(84px + var(--safe-b)); z-index:40;
  display:flex; flex-direction:column; align-items:center; gap:8px; pointer-events:none}
.toast{
  max-width:84vw; padding:9px 15px; border-radius:12px; font-size:13.5px;
  background:var(--toast-bg); border:1px solid var(--line);
  box-shadow:0 10px 26px rgba(0,0,0,.45); animation:pop .18s ease-out;
}
.toast.bad{border-color:rgba(248,113,113,.45)}

@media (min-width:760px){
  #messages{padding:24px 26px 8px}
  .bubble{font-size:15px}
  .composer{padding-left:26px; padding-right:26px}
  .composer .box{max-width:760px; margin:0 auto}
}

/* ==================================================================
   卡片库 + 剧本体气泡（把 Ai宇宙 本体的内容搬过来这一批）
   ================================================================== */

/* ---- 助手消息：内部自带多个气泡，外层不再画框 ---- */
.msg.assistant .bubble.stack{
  background:none; border:none; padding:0; box-shadow:none;
  max-width:100%; width:100%; backdrop-filter:none;
}
.bubbles{display:flex; flex-direction:column; gap:10px; align-items:stretch; width:100%}
.bubbles.fold .bblgrp.folded{display:none}

/* ---- 小说模式：整段按长篇小说的段落排，没有头像、没有气泡框 ----
   首行缩进两字、行距放宽，读起来才像在读小说而不是在读聊天记录。
   世界之声单独一条低调旁注（左边一条竖线），混进段落会把阅读节奏读断。 */
.novel{
  font-size:15.5px; line-height:2; letter-spacing:.01em; color:var(--fg);
  max-width:100%; word-break:break-word; overflow-wrap:anywhere;
}
.novel p{margin:0 0 1em; text-indent:2em}
.novel p:last-child{margin-bottom:0}
.novel h1,.novel h2,.novel h3{margin:.5em 0 .6em; font-size:1.05em; text-indent:0}
.novel blockquote{
  margin:0 0 1em; padding-left:12px; border-left:2px solid var(--line-strong); color:var(--dim);
}
.novel-wn{
  margin-top:14px; padding:2px 0 2px 12px;
  border-left:2px solid var(--accent);
  color:var(--dim); font-size:14px; line-height:1.85;
}
.novel-wn p{margin:0 0 .5em; display:flex; gap:7px; align-items:flex-start}
.novel-wn p:last-child{margin-bottom:0}
.novel-wn .wn-ic{flex:none; margin-top:4px; color:var(--accent); opacity:.9}
.novel-wn .wn-ic svg{width:14px; height:14px; display:block}
.fold-more{
  align-self:flex-start; font:inherit; font-size:12.5px; color:var(--accent); cursor:pointer;
  background:none; border:1px dashed var(--line-strong); border-radius:999px; padding:4px 12px;
}
.fold-more:hover{background:var(--panel)}

/* ---- 气泡组：头像 + 名字行 + 该角色这一轮说的所有内容 ---- */
.bblgrp{display:flex; gap:9px; align-items:flex-start; width:100%}
.bblgrp.sys{justify-content:center}
.bblgrp.narr{padding-left:0}
.bbl-av{flex:none; width:34px; margin-top:1px}
.sp-av{
  width:34px; height:34px; border-radius:50%; overflow:hidden;
  display:flex; align-items:center; justify-content:center;
  color:#fff; font-size:14px; font-weight:600; background:#3d3d48;
  border:1px solid var(--line);
}
.sp-av img{width:100%; height:100%; object-fit:cover; display:block}
.sp-av.world{background:linear-gradient(135deg,#8a5ce8,#5d7bf0); border-color:transparent}
.sp-av.world svg{width:19px; height:19px}
.bbl-col{flex:1; min-width:0; display:flex; flex-direction:column; gap:5px; max-width:min(94%,620px)}
.bblgrp.sys .bbl-col{max-width:100%; align-items:center}
/* 角色色：由名字哈希得出，跨会话不漂 */
.bblgrp.char .bbl.talk{background:var(--sp-bg,rgba(255,255,255,.05)); border-color:var(--sp-bd,var(--line))}
.bblgrp.char .bbl.inner{border-color:var(--sp-bd,var(--line)); background:none}
.bblgrp.world .bbl.talk{
  background:linear-gradient(135deg,rgba(160,108,255,.17),rgba(124,156,255,.09));
  border-color:rgba(160,108,255,.36);
}
html[data-theme="light"] .bblgrp.char .bbl.talk{background:var(--sp-bg)}
html[data-theme="light"] .bblgrp.char .bbl.inner{background:none}

.bbl{
  position:relative; max-width:100%; border-radius:14px;
  padding:9px 13px; background:var(--panel); border:1px solid var(--line);
  line-height:1.75; font-size:15px; word-break:break-word;
}
.bbl.narr{background:none; border:none; padding:0 2px}
/* 场景 / 时间标题条：模型每轮开头那句「序章 · 清晨 06:30 · 薄雾微凉 · …」 */
.bbl.scene-head{
  background:none; border:none; padding:3px 2px 4px;
  display:flex; align-items:center; gap:7px;
  color:#a9b7ff; font-size:12.5px; font-weight:600; letter-spacing:.05em;
}
.bbl.scene-head svg{width:14px; height:14px; flex:none; opacity:.9}
.bbl.scene-head span{flex:1; min-width:0}
html[data-theme="light"] .bbl.scene-head{color:#5b6fd8}
.bbl.talk{background:rgba(124,156,255,.10); border-color:rgba(124,156,255,.26)}
.bbl.world{background:linear-gradient(135deg,rgba(160,108,255,.17),rgba(124,156,255,.09));
  border-color:rgba(160,108,255,.36)}
.bbl.inner{background:rgba(255,255,255,.04); border-style:dashed; color:var(--dim); font-style:italic}
.bbl.sys{align-self:center; background:none; border:none; color:var(--dim-2);
  font-size:12.5px; padding:1px 0; letter-spacing:.03em}
.bbl-name{font-size:12.5px; font-weight:600; color:var(--accent); margin-bottom:3px; letter-spacing:.02em}
.bbl.world .bbl-name{color:var(--accent-2)}
.aside{color:var(--dim-2); font-size:12.5px; margin-left:4px}
.q{color:var(--dim-2)}

/* ---- 台词立绘：台词框底上透出这个人的立绘（「更多」里的开关） ----
   `--art` 由 js/app.js 的 cssArt() 挂在 .bblgrp 上，这里只管怎么铺：
     ::before = 立绘本体（cover 铺满整段台词，按框的宽高自动截取）
     ::after  = 从左往右的压暗罩，保证字永远压在图上还看得清
   两层都 z-index:-1，靠 .bbl-col 的 isolation 关在列里 —— 没有它，负层会跑到
   .bbl 自己的背景后面去（那是气泡底色，立绘就整个看不见了）。 */
/* 🔴 铺在**整组**上，不铺在单个台词框里：
   台词框常常只有五六十像素高，3:4 的立绘塞进去只剩腰和腿的一小块，根本认不出是谁；
   铺在组上则跨框连续（视觉上是一整张图垫在这段台词后面），而台词框自己的底色是
   半透明的 --sp-bg（0.12），图能从框里透出来 —— 既保住「框背景有立绘」的观感，
   又不会因为框太矮把人切碎。 */
/* 🔴 挂在**.bbl-col**（台词框那一列）上，不是整组：
   铺在整组时，台词框自己有宽度上限（max-width:min(94%,620px)），宽屏下框右边留一大片空白，
   立绘正好落在那里 —— 结果是图被框的右边界直直切成两半，「被对话框切了一刀」就是这么来的。 */
.bblgrp.char.art .bbl-col{position:relative; isolation:isolate}
.bblgrp.char.art .bbl-col::before,
.bblgrp.char.art .bbl-col::after{
  content:''; position:absolute; inset:0; z-index:-1; pointer-events:none; border-radius:16px;
}
.bblgrp.char.art .bbl-col::before{
  background-image:var(--art);
  background-repeat:no-repeat;
  /* 🔴 **铺满整段台词**（用户口径 2026-10-28 二次纠偏）：
     不再只抠出「头顶到胸口」那一截 —— 用户要的是整张立绘垫满台词框，
     由浏览器**按框的宽高自动截取**（cover：宽框取人像中段、长框多露身体），
     框多大就露多少，不再出现「右边一张脸、左边一大片空」的补丁感。
     🔴 锚点固定在偏上（18%）而不是 center：cover 横向撑满后纵向只剩一条，
        center 会正好切在腰上；18% 保证矮框看到脸、高框从头到腰都留得住。 */
  background-position:center 18%;
  background-size:cover;
  opacity:var(--art-op,.42);
  /* 原图是彩色立绘，压一点饱和免得在深色底上跳出来抢文字 */
  filter:saturate(.95);
  /* 🔴 **跟随台词框架边缘羽化**（用户 2026-10-28 第三次纠偏）：
     之前是椭圆蒙版 —— 那是「中间亮、四周按圆/椭圆收缩」，用户要的是
     **沿矩形的四条边各淡一圈**，不是圆渐晕。
     做法：横向、纵向各一条 linear-gradient，取交集（mask-composite:intersect）——
     结果就是「中间是整张立绘，四条边各自向内虚化」，形状跟着框架走。
     羽化宽度用 px 而不是 %：长台词框不会把左右淡出拉得过大。
     ⚠️ 两条渐变都必须**在元素边界上就归零**（0 / 100% 两个端点），否则边缘会留硬线。 */
  -webkit-mask-image:
    linear-gradient(90deg, rgba(0,0,0,0) 0, #000 26px, #000 calc(100% - 26px), rgba(0,0,0,0) 100%),
    linear-gradient(180deg, rgba(0,0,0,0) 0, #000 12px, #000 calc(100% - 12px), rgba(0,0,0,0) 100%);
  -webkit-mask-composite:source-in;
  mask-image:
    linear-gradient(90deg, rgba(0,0,0,0) 0, #000 26px, #000 calc(100% - 26px), rgba(0,0,0,0) 100%),
    linear-gradient(180deg, rgba(0,0,0,0) 0, #000 12px, #000 calc(100% - 12px), rgba(0,0,0,0) 100%);
  mask-composite:intersect;
}
.bblgrp.char.art .bbl-col::after{
  /* 只护住左边那半截（文字在那边）：右边压太狠会把立绘闷成一块灰斑 */
  background:linear-gradient(100deg,
    var(--art-veil,#0d1017) 2%, var(--art-veil-mid,rgba(13,16,23,.45)) 34%, transparent 62%);
}
/* 🔴 浅色主题下压暗罩必须整体换成**白**的：中间那截 rgba(13,16,23,.45) 若沿用深色，
   会在浅底上糊出一层灰纱 —— 立绘发灰、边缘生硬都是它造成的，不是图本身的问题。 */
html[data-theme="light"] .bblgrp.char.art{--art-op:.20; --art-veil:#fff; --art-veil-mid:rgba(255,255,255,.62)}

/* 头像带高清立绘 → 是可以点开看的，给个手型 + hover 提亮 */
.sp-av.has-art, .pm-av.has-art{cursor:zoom-in; transition:box-shadow .15s, filter .15s}
.sp-av.has-art:hover, .pm-av.has-art:hover{filter:brightness(1.08)}
.sp-av.has-art:hover{box-shadow:0 0 0 2px var(--accent)}
.pm-av.has-art:hover{box-shadow:0 0 0 2px var(--ok)}

/* 高清立绘查看器：弹窗本体透明，只留那张图 */
.modal-card:has(.artv){
  width:min(calc(100vw - 24px),520px);
  background:none; border:none; box-shadow:none; padding:0;
  display:flex; justify-content:center;
}
.artv{display:flex; flex-direction:column; align-items:center; gap:11px; width:100%}
.artv-img{
  display:block; width:auto; max-width:100%;
  max-height:70vh; max-height:calc(70dvh - 30px);   /* vh 兜底在前，理由同 .modal-card */
  object-fit:contain; border-radius:16px;
  border:1px solid var(--line); background:var(--panel);
  box-shadow:0 18px 46px rgba(0,0,0,.5);
}
.artv-cap{display:flex; flex-direction:column; align-items:center; gap:2px; text-align:center}
.artv-cap b{font-size:15px; color:#fff; text-shadow:0 1px 6px rgba(0,0,0,.6)}
.artv-cap span{font-size:12px; color:rgba(255,255,255,.72); text-shadow:0 1px 6px rgba(0,0,0,.6)}
.artv-tip{font-size:11.5px; color:rgba(255,255,255,.5)}

/* ---- 附加板块通用 ---- */
.extras{display:flex; flex-direction:column; gap:10px; margin-top:11px; width:100%}

/* 画面分镜 */
.scard{background:rgba(255,255,255,.045); border:1px solid var(--line); border-radius:14px; padding:11px 13px}
.scard-h{display:flex; align-items:center; justify-content:space-between; gap:8px;
  font-size:12.5px; color:var(--dim); margin-bottom:7px}
.scard-b{font-size:13.5px; line-height:1.75; color:var(--dim); white-space:pre-wrap}
.mini{font:inherit; font-size:12px; color:var(--accent); background:none; border:1px solid var(--line);
  border-radius:8px; padding:3px 9px; cursor:pointer}
.mini:hover{background:var(--panel-2)}

/* 角色卡 */
.ccard{background:linear-gradient(180deg,rgba(124,156,255,.10),rgba(255,255,255,.035));
  border:1px solid rgba(124,156,255,.28); border-radius:14px; padding:12px 14px}
.ccard-h{font-size:14px; font-weight:700; color:var(--accent); margin-bottom:9px; letter-spacing:.02em}
.cf-list{display:flex; flex-direction:column; gap:6px}
.cf{font-size:13.5px; line-height:1.7; display:flex; gap:8px; align-items:baseline}
.cf-label{flex:0 0 auto; width:5.2em; color:var(--accent); font-weight:600; font-size:12.5px}
.cf-body{font-size:13.5px; line-height:1.75; color:var(--dim)}

/* 在场名册 */
.presence{background:rgba(74,222,128,.07); border:1px solid rgba(74,222,128,.24);
  border-radius:14px; padding:11px 13px}
.presence-h{font-size:12.5px; font-weight:600; color:var(--ok); margin-bottom:7px; letter-spacing:.04em}
.presence-list{display:flex; flex-direction:column; gap:7px}
.pm{font-size:13.5px; line-height:1.65; display:flex; gap:9px; align-items:flex-start}
.pm-av{flex:none; width:26px; height:26px; border-radius:9px; overflow:hidden; margin-top:1px;
  display:flex; align-items:center; justify-content:center;
  background:rgba(74,222,128,.14); border:1px solid rgba(74,222,128,.22)}
.pm-av img{width:100%; height:100%; object-fit:cover; display:block}
.pm-av-t{color:#fff; font-size:12px; font-weight:700}
.pm-body{flex:1; min-width:0}
/* 🔴 这里原来写死 #d6ffe4（浅绿白）——深色主题好看，浅色主题下等于隐形。
   一律走主题变量：--ok 在深色是亮绿、浅色是深绿。 */
.pm-name{color:var(--ok); font-weight:600; margin-right:8px}
.pm-meta{color:var(--dim)}

/* 选项按钮 */
.opts-wrap{display:flex; flex-direction:column; gap:7px; width:100%}
.opts-h{font-size:11.5px; font-weight:600; color:var(--dim); letter-spacing:.12em;
  padding-left:2px; opacity:.85}
.opts{display:flex; flex-direction:column; gap:8px; width:100%}
/* 🔴 一条选项 = 一整行，不再是一个按钮：右边那两个图标是它能做的两件事。
   .opt 自己是 flex 容器（不是 button）—— 按钮里不能再塞按钮，浏览器会把内层的
   直接拎出去，界面当场散架。 */
.opt{
  font:inherit; font-size:14px; text-align:left; cursor:pointer;
  border-radius:13px; color:var(--fg);
  background:rgba(124,156,255,.10); border:1px solid rgba(124,156,255,.3);
  transition:background .15s, transform .1s;
  display:flex; align-items:center; gap:0; overflow:hidden;
}
.opt-main{
  flex:1; min-width:0; text-align:left; cursor:pointer;
  padding:11px 6px 11px 14px; background:none; border:none; color:inherit;
  display:flex; align-items:center; gap:10px;
}
.opt-i{
  flex:none; width:20px; height:20px; border-radius:7px; font-style:normal;
  display:inline-flex; align-items:center; justify-content:center;
  background:rgba(124,156,255,.22); color:var(--fg); font-size:11.5px; font-weight:700;
}
.opt-t{flex:1; min-width:0}
.opt-acts{flex:none; display:flex; align-items:center; gap:1px; padding-right:7px}
/* 两个动作：铅笔 = 填进输入框（可以再改改），箭头 = 原样直接发出去 */
.opt-ico{
  flex:none; width:28px; height:28px; border-radius:9px; cursor:pointer;
  display:inline-flex; align-items:center; justify-content:center;
  background:none; border:none; color:var(--dim);
  transition:background .15s, color .15s;
}
.opt-ico svg{width:15px; height:15px; display:block}
.opt-ico:hover{background:rgba(124,156,255,.2); color:var(--fg)}
.opt-ico.go{color:var(--accent); opacity:.75}
.opt-ico.go:hover{background:rgba(124,156,255,.34); color:var(--accent); opacity:1}
.opt:hover{background:rgba(124,156,255,.16)}
.opt:hover .opt-i{background:rgba(124,156,255,.34)}
.opt:active{transform:translateY(1px)}

/* ---- 事件（随机 / 遭遇 / 剧情 / 故事）----
   四种类型各一个主色，写在 --ev 里（R,G,B 三元组），底 / 边框 / 文字全靠它算，
   加新类型只要再给一条 .k-XXX{--ev:...}。 */
.evcard,.evbar,.evpanel{--ev:255,196,84}
.k-ENCOUNTER{--ev:248,113,113}
.k-PLOT{--ev:167,139,250}
.k-STORY{--ev:94,234,212}
/* 浅色底上那些亮色太淡，换成压得住的深色（同一套色相） */
html[data-theme="light"] .evcard,html[data-theme="light"] .evbar,html[data-theme="light"] .evpanel{--ev:176,106,16}
html[data-theme="light"] .k-ENCOUNTER{--ev:200,44,64}
html[data-theme="light"] .k-PLOT{--ev:109,60,190}
html[data-theme="light"] .k-STORY{--ev:15,150,140}

.ev-tag{
  flex:none; font-size:11px; font-weight:700; letter-spacing:.06em;
  padding:2px 7px; border-radius:7px; white-space:nowrap;
  color:rgb(var(--ev)); background:rgba(var(--ev),.16); border:1px solid rgba(var(--ev),.32);
}

/* 待定事件卡：挂在最后一条回复的选项下面，玩家自己决定接不接 */
.evcard{
  width:100%; margin-top:11px; border-radius:14px; padding:12px 14px;
  background:linear-gradient(180deg,rgba(var(--ev),.11),rgba(255,255,255,.035));
  border:1px solid rgba(var(--ev),.32);
}
.evcard .ev-h{display:flex; align-items:center; gap:8px}
.evcard .ev-h b{flex:1; min-width:0; font-size:14.5px; color:var(--fg)}
.evcard .ev-hook{font-size:13.5px; line-height:1.7; color:var(--dim); margin:7px 0 11px}
.ev-acts{display:flex; gap:8px}
.ev-take,.ev-skip{font:inherit; font-size:13px; padding:8px 18px; border-radius:11px; cursor:pointer}
.ev-take{
  color:var(--fg); font-weight:600;
  background:linear-gradient(135deg,rgba(var(--ev),.36),rgba(var(--ev),.18));
  border:1px solid rgba(var(--ev),.55);
}
.ev-take:hover{background:rgba(var(--ev),.32)}
.ev-skip{background:none; border:1px solid var(--line); color:var(--dim)}
.ev-skip:hover{background:var(--panel-2)}
.ev-take:active,.ev-skip:active{transform:translateY(1px)}

/* 事件进行中：输入栏上方那条横幅（跟着会话走，换会话也在） */
.evbar{
  display:flex; align-items:center; gap:9px;
  margin:0 12px 8px; padding:8px 12px; border-radius:13px;
  background:rgba(var(--ev),.10); border:1px solid rgba(var(--ev),.32);
}
.evbar b{
  flex:1; min-width:0; font-size:13.5px; color:var(--fg);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.evbar .ev-end{
  flex:none; font:inherit; font-size:12px; padding:4px 11px; border-radius:9px; cursor:pointer;
  color:var(--fg); background:rgba(var(--ev),.2); border:1px solid rgba(var(--ev),.38);
}
.evbar .ev-end:hover{background:rgba(var(--ev),.32)}
/* 「详细」：展开 / 收起事件内容，只看不推进 —— 所以做成弱按钮 */
.evbar .ev-detail{
  flex:none; font:inherit; font-size:12px; padding:4px 11px; border-radius:9px; cursor:pointer;
  color:var(--dim); background:none; border:1px solid var(--line);
}
.evbar .ev-detail:hover{color:var(--fg); background:var(--panel-2)}
.evbar .ev-detail[aria-expanded="true"]{color:var(--fg); background:var(--panel-2)}
/* 真的开始办了：左边多一道事件色，跟「挂在身上」区分开 */
.evbar.on{box-shadow:inset 3px 0 0 rgb(var(--ev))}
/* 「详细」展开时：横幅收掉下圆角、贴到详情面板上，俩看着是一个整体 */
.evbar.open{margin-bottom:0; border-radius:13px 13px 0 0}
.evpanel{
  margin:0 12px 8px; padding:7px 12px 9px; border-radius:0 0 13px 13px; border-top:none;
  background:rgba(var(--ev),.07); border:1px solid rgba(var(--ev),.32);
}
.evpanel[hidden]{display:none}
.ev-row{display:flex; gap:9px; font-size:12.5px; line-height:1.6; padding:2.5px 0}
.ev-row i{flex:none; font-style:normal; color:var(--dim); min-width:28px}
.ev-row span{flex:1; min-width:0; color:var(--fg); word-break:break-word}
/* 第一行是标题：加粗一点，跟下面的说明行分开 */
.ev-row:first-child span{font-weight:600}

/* 故事编辑器里的「故事事件」列表 */
.ed-events{display:flex; flex-direction:column; gap:6px}
.ed-ev{
  display:flex; align-items:flex-start; gap:8px; padding:7px 9px;
  border-radius:11px; background:var(--panel); border:1px solid var(--line);
}
.ed-ev-k{
  flex:none; margin-top:1px; font-size:11px; font-weight:700; padding:2px 6px; border-radius:6px;
  color:var(--accent); background:rgba(124,156,255,.14); white-space:nowrap;
}
.ed-ev-b{flex:1; min-width:0; display:flex; flex-direction:column; gap:2px}
.ed-ev-b b{font-size:13.5px}
.ed-ev-b span{font-size:12.5px; color:var(--dim); line-height:1.6}
.ed-ev>button{
  flex:none; background:none; border:none; color:var(--dim-2);
  font-size:16px; line-height:1; cursor:pointer; padding:2px 4px;
}
.ed-ev>button:hover{color:var(--danger)}
.ed-ev-add{display:flex; flex-direction:column; gap:7px; margin-top:8px}
.ed-ev-add input{width:100%}
.ed-ev-add .btn{align-self:flex-end; padding-left:30px; padding-right:30px}

/* ---- 空态：刚开卡还没说话 ---- */
/* 卡的图铺在聊天页全屏背景上（.chat-bg，见 updateChatBg），空态只留文字，
   小框体封面已撤（2026-10-17，用户：跟全屏背景重复了）。 */
/* 🔴 只写 max-width 不写 margin:auto 的话，它是 #messages 里的普通块级元素 →
   整块**贴着左边**缩成 340px（2026-10-28 用户截图就是指这个：文字挤在左侧）。
   auto 外边距才把它居中；高度 100% + justify-content 管竖向。 */
.empty.card-open{max-width:340px; margin:0 auto}
.empty.card-open .intro{font-size:13.5px; color:var(--dim); line-height:1.7; margin-top:4px}
.empty.card-open .tags{display:flex; flex-wrap:wrap; gap:6px; justify-content:center; margin-top:12px}
.empty.card-open .tags span{font-size:11.5px; color:var(--dim-2);
  border:1px solid var(--line); border-radius:999px; padding:2px 9px}

/* ---- 会话列表里的卡片标签 ---- */
.sess-tag{display:inline-block; font-size:10.5px; color:var(--accent);
  border:1px solid rgba(124,156,255,.34); border-radius:6px;
  padding:0 5px; margin-right:5px; vertical-align:1px}

/* ---- 卡片库 ---- */
.modal-card .lib{width:min(94vw,900px)}
@media (max-width:520px){ .modal-card:has(.lib){ width:100vw; max-height:100vh; height:100vh;
  border-radius:0; padding:16px; left:0; top:0; transform:none; } }
.lib-head{display:flex; align-items:flex-start; justify-content:space-between; gap:12px}
.lib-title h2{margin:0 0 2px}
.lib-title .desc{margin-bottom:0}
.lib-tabs{display:flex; gap:8px; margin:14px 0 12px; flex-wrap:wrap}
.lib-tabs button{
  font:inherit; font-size:13px; cursor:pointer; color:var(--dim);
  background:var(--panel); border:1px solid var(--line); border-radius:999px; padding:6px 14px;
}
.lib-tabs button.on{color:#fff; background:linear-gradient(135deg,var(--grad-1),var(--grad-2)); border-color:transparent}
.lib-grid{
  display:grid; grid-template-columns:repeat(auto-fill,minmax(132px,1fr)); gap:12px;
  /* grid-auto-rows 必须是 max-content：默认的 auto 在「确定高度 + 卡面靠 aspect-ratio 撑高」
     时会塌成边框高度（手机端实测 2px），显式 max-content 才按内容算高。别改回 auto。 */
  grid-auto-rows:max-content;
  max-height:62vh; overflow-y:auto; padding:2px; -webkit-overflow-scrolling:touch;
}
@media (max-width:520px){ .lib-grid{max-height:none; flex:1} .lib{display:flex; flex-direction:column; height:100%} }
/* 卡面构图 = Android 端 MediaCard：一张图 cover 铺满整张卡 + 底部向上渐变压暗，
   名字与介绍压在渐变上。
   🔴 文字区不许有自己的不透明底色（那会把卡面切成「图 + 色块」两截），
      卡的高度只由 aspect-ratio 决定（内联给出，取卡自己的 cardRatio）。 */
.tpl{
  position:relative; font:inherit; text-align:left; cursor:pointer; padding:0; overflow:hidden;
  background:var(--surface-2); border:1px solid var(--line); border-radius:14px;
  display:block; aspect-ratio:3/4; transition:transform .12s, border-color .15s;
}
.tpl:hover{transform:translateY(-2px); border-color:rgba(124,156,255,.45)}
.tpl:active{transform:translateY(0)}
/* 背景图：绝对定位铺满整卡，cover 自适应裁切（卡面比例 = 裁剪框比例，所以基本不裁） */
.tpl-img{
  position:absolute; top:0; right:0; bottom:0; left:0; width:100%; height:100%;
  object-fit:cover; object-position:center; display:block; background-color:var(--bg-soft);
}
/* 底部向上渐变压暗：与 Android 的 verticalGradient(0f 透明 / 0.38f 黑24% / 1f 黑88%)
   同一口径，只是 CSS 的 0% 在底边，所以上下颠倒着写 */
.tpl-scrim{
  position:absolute; top:0; right:0; bottom:0; left:0; pointer-events:none;
  background:linear-gradient(to top,
    rgba(0,0,0,.88) 0%, rgba(0,0,0,.24) 62%, rgba(0,0,0,0) 100%);
}
/* 文字贴左下角，直接坐在渐变上 —— 自己不带任何底色 */
.tpl-body{
  position:absolute; left:0; right:0; bottom:0; z-index:2;
  padding:10px 10px 9px; display:flex; flex-direction:column; min-width:0;
}
/* 卡面上的字一律白色 + 投影：底子是深色渐变，跟着主题换字色反而看不清 */
.tpl-line{display:flex; align-items:center; gap:4px; min-width:0}
.tpl-name{font-size:14px; font-weight:700; color:#fff; line-height:1.3; min-width:0;
  text-shadow:0 1px 3px rgba(0,0,0,.55);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.tpl-line .g{flex:none; font-style:normal; font-size:15px; font-weight:700; color:#fff; line-height:1;
  text-shadow:0 1px 3px rgba(0,0,0,.55)}
/* 介绍只留一行（Android compact 卡就是 maxLines=1）：卡面小，两行会把图挤没 */
.tpl-intro{font-size:10.5px; color:rgba(255,255,255,.88); margin-top:2px; line-height:14px;
  text-shadow:0 1px 3px rgba(0,0,0,.55);
  display:-webkit-box; -webkit-line-clamp:1; -webkit-box-orient:vertical; overflow:hidden}


/* ==================================================================
   三 Tab 之后新增的一批：宇宙页 / 我的卡 / 编辑器 / 沉浸阅读
   ================================================================== */

.view{overflow:hidden}

/* ------------------------------ 卡片页 ------------------------------ */
.pg-head{display:flex; align-items:flex-start; justify-content:space-between; gap:12px;
  padding:14px 14px 0}
.pg-title h2{margin:0; font-size:19px}
.pg-title .desc{font-size:12.5px; color:var(--dim-2); margin-top:3px}
.pg-acts{display:flex; gap:2px; flex:none}

.pg-search{position:relative; margin:12px 14px 0; display:flex; align-items:center}
.pg-search .s-ico{position:absolute; left:11px; display:flex; color:var(--dim-2); pointer-events:none}
.pg-search .s-ico svg{width:16px;height:16px}
.pg-search input{
  width:100%; padding:9px 34px; border-radius:12px; font:inherit; color:inherit;
  background:var(--panel); border:1px solid var(--line); outline:none;
}
.pg-search input:focus{border-color:rgba(124,156,255,.55)}
.pg-search input::-webkit-search-cancel-button{display:none}
.pg-search .s-clear{position:absolute; right:6px; width:28px;height:28px; border-radius:8px;
  display:flex; align-items:center; justify-content:center; color:var(--dim-2)}
.pg-search .s-clear:hover{background:var(--panel-2); color:var(--fg)}
.pg-search .s-clear svg{width:14px;height:14px}

.pg-tabs{display:flex; flex-direction:column; gap:8px; margin:12px 14px 0}
/* 第一行：分支 tab（宇宙页有）＋ 排序下拉（只有宇宙页有分支，但两页都排得着） */
.pg-bar{display:flex; align-items:center; gap:8px; flex-wrap:wrap}
.pg-sortwrap{margin-left:auto; flex:none; display:flex}
/* 排序下拉：外观跟 .pg-branch 的 pill 对齐，右侧留出箭头位（原生箭头在某些
   内核里贴边又发灰，干脆关掉自己画一个） */
.pg-sort{
  appearance:none; -webkit-appearance:none; -moz-appearance:none;
  font:inherit; font-size:12.5px; line-height:1; cursor:pointer;
  color:var(--dim); background:var(--panel); border:1px solid var(--line);
  border-radius:999px; padding:6px 24px 6px 12px; outline:none;
  background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 8px center; background-size:10px 10px;
}
.pg-sort:hover{color:var(--fg); border-color:rgba(124,156,255,.45)}
.pg-sort:focus-visible{border-color:rgba(124,156,255,.55)}
.pg-branch,.pg-kind{display:flex; gap:8px; flex-wrap:wrap}
.pg-branch button,.pg-kind button{
  font:inherit; font-size:13px; cursor:pointer; color:var(--dim);
  background:var(--panel); border:1px solid var(--line); border-radius:999px; padding:6px 14px;
}
.pg-branch button.on,.pg-kind button.on{
  color:#fff; background:linear-gradient(135deg,var(--grad-1),var(--grad-2)); border-color:transparent;
}
/* 分类 tab 上的「可见数量」：多少张故事卡 / 多少张角色卡。
   数字用 tabular-nums，搜索时跳动不会把按钮宽度带得一抖一抖。 */
.pg-kind button .n{
  display:inline-block; min-width:19px; margin-left:7px; padding:0 5px;
  font-style:normal; font-size:11px; line-height:16px; text-align:center;
  font-variant-numeric:tabular-nums; letter-spacing:.2px;
  border-radius:999px; background:var(--panel-2); border:1px solid var(--line); color:var(--dim);
}
.pg-kind button.on .n{background:rgba(255,255,255,.2); border-color:transparent; color:#fff}
.pg-kind button .n.zero{opacity:.45}

/* grid-auto-rows 必须 max-content：默认的 auto 在「容器高度确定 + 卡面靠 aspect-ratio 撑高」
   时会塌成边框高度（手机端实测 2px）。别改回 auto。 */
.pg-grid{
  display:grid; grid-template-columns:repeat(auto-fill,minmax(132px,1fr)); gap:12px;
  grid-auto-rows:max-content; flex:1; min-height:0; overflow-y:auto;
  padding:12px 14px 20px; -webkit-overflow-scrolling:touch;
}
.pg-empty{padding:44px 22px; text-align:center; color:var(--dim-2); font-size:13.5px; line-height:1.8}

/* 右上角操作钮：得压在图和渐变之上（层序别省，不然会被铺满的卡面盖住） */
.tpl-acts{position:absolute; top:5px; right:5px; z-index:3; display:flex; flex-direction:column; gap:4px}
.tpl-acts button{
  width:26px; height:26px; border-radius:8px; display:flex; align-items:center; justify-content:center;
  background:rgba(8,10,16,.6); color:#fff; backdrop-filter:blur(4px);
}
.tpl-acts button:hover{background:rgba(8,10,16,.86)}
.tpl-acts svg{width:13px;height:13px}
/* 分类标签：跟着文字一起压在渐变上，半透明白底（对齐 Android 的 Color.White 22% 圆角块） */
.tpl-badge{
  align-self:flex-start; margin-bottom:4px; padding:1px 6px; border-radius:6px;
  font-size:9.5px; font-weight:600; color:#fff; line-height:1.5; max-width:100%;
  background:rgba(255,255,255,.22); text-shadow:0 1px 3px rgba(0,0,0,.55);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* 热度徽章：卡面右上角的浮标。
   🔴 必须是 .tpl 的**直接子元素**。放进 .tpl-body 里会被它的 position:absolute 收为
      定位容器 —— top:6px right:6px 就成了「文字块右上角」，看着像压在分类标签上。
   🔴 也别塞进文字行（试过两版：贴名字行把「利姆鲁·特恩佩斯特」挤成「利姆…」，
      贴分类标签行又把「转生史莱姆」挤成「转生史…」）—— 卡面只有 132px 宽，
      任何占位的方案都是拿信息换信息。浮标压在图上，文字区一个字都不让。 */
.tpl-heat{
  position:absolute; top:5px; right:5px; z-index:3;
  display:inline-flex; align-items:center; gap:2px;
  padding:2px 6px 2px 5px; border-radius:999px;
  background:rgba(8,10,16,.62); color:#ffb066;
  font-size:10px; font-weight:700; line-height:1;
  font-variant-numeric:tabular-nums; letter-spacing:.2px;
  box-shadow:0 1px 4px rgba(0,0,0,.35);
  backdrop-filter:blur(4px);
  pointer-events:none;
}
.tpl-heat svg{width:9px; height:9px; display:block}

/* 🔴 .ed 自己不再设 width / max-height / overflow：
   ① 以前它（94vw）比 .modal-card（430px）还宽，宽屏下直接横向溢出、卡里横滚；
   ② 卡里再套一层滚动容器，两层滚动在手机上极难划动（.mem-list 踩过的同款坑）。
   宽高与滚动一律交给 .modal-card（上面 :has(.ed) 给了宽档），这里只留排版规则。 */
.ed{overflow:visible}
.ed-head{
  display:flex; align-items:center; gap:10px; margin:-6px 0 12px;
  position:sticky; top:-22px; z-index:3; background:var(--surface); padding:6px 0 8px;
}
.ed-head b{flex:1; text-align:center; font-size:15.5px}
.ed-x,.ed-save{font:inherit; font-size:13.5px; color:var(--dim); padding:5px 9px; border-radius:9px}
.ed-x:hover,.ed-save:hover{background:var(--panel-2)}
.ed-save{color:var(--accent); font-weight:600}
.ed-save[disabled]{opacity:.5; pointer-events:none}

.ed-avatar-row{display:flex; gap:14px; align-items:center; margin-bottom:14px}
.ed-avatar{
  width:96px; height:96px; border-radius:16px; overflow:hidden; flex:none; cursor:pointer;
  border:1px solid var(--line); background:var(--panel);
  display:flex; align-items:center; justify-content:center; color:var(--dim-2); font-size:13px;
}
.ed-avatar img{width:100%; height:100%; object-fit:cover; display:block}
.ed-avatar.empty{border-style:dashed}
.ed-avatar-side{display:flex; flex-direction:column; gap:7px; align-items:flex-start; min-width:0}
.ed-avatar-side .btn{padding:7px 12px; font-size:13px}

.ed-bg{
  width:100%; max-width:260px; border-radius:14px; border:1px dashed var(--line); cursor:pointer;
  background-size:cover; background-position:center; background-color:var(--panel);
  display:flex; align-items:center; justify-content:center; color:var(--dim-2); font-size:13px;
  min-height:64px;
}
.ed-bg.empty{border-style:dashed}
.ed-bg span{padding:3px 9px; border-radius:999px; background:rgba(8,10,16,.5); color:#fff; font-size:11.5px}
html[data-theme="light"] .ed-bg span{background:rgba(255,255,255,.75); color:var(--fg)}

.ed-pills{display:flex; gap:8px; flex-wrap:wrap}
.pill{
  font:inherit; font-size:13px; cursor:pointer; color:var(--dim);
  background:var(--panel); border:1px solid var(--line); border-radius:999px; padding:6px 14px;
  display:inline-flex; align-items:center; gap:5px;
}
.pill.on{color:#fff; background:linear-gradient(135deg,var(--grad-1),var(--grad-2)); border-color:transparent}
.pill i{font-style:normal; opacity:.8}

.ed-tags{display:flex; flex-wrap:wrap; gap:6px; margin-bottom:8px; font-size:12.5px}
.ed-tags .tg{display:inline-flex; align-items:center; gap:2px; padding:2px 4px 2px 9px;
  border-radius:999px; border:1px solid var(--line); color:var(--dim)}
.ed-tags .tg button{width:16px;height:16px;border-radius:50%;color:var(--dim-2);font-size:14px;line-height:1;
  display:flex;align-items:center;justify-content:center}
.ed-tags .tg button:hover{color:var(--danger)}
.ed-tag-add{display:flex; gap:8px}
.ed-tag-add input{flex:1; min-width:0}

.field .req{font-style:normal; font-size:11px; color:var(--accent); margin-left:4px}
.dim{color:var(--dim-2); font-size:12px}
textarea.tall{min-height:170px}

/* ------------------------- 菜单（玩法 / 更多） ------------------------- */
.menu-list,.mode-list{display:flex; flex-direction:column; gap:8px}
/* 弹窗里的单选 pill 组：性别一行放得下，视角四项用 2×2 网格（窄屏也不挤成 3+1） */
/* 「我的身份」里的头像：点圆图换一张，右边两个纯文字操作。
   没有图时圆里显示名字首字（和抽屉那枚一致），所以「空」也是一种正常状态。 */
.id-ava{display:flex; align-items:center; gap:16px; margin:2px 0 20px}
.id-ava-pick{
  position:relative; flex:none; width:68px; height:68px; border-radius:50%;
  border:1px solid var(--line); overflow:hidden; cursor:pointer;
  background:linear-gradient(135deg,var(--grad-1),var(--grad-2));
  color:#fff; font:inherit; font-size:24px; font-weight:600;
  display:flex; align-items:center; justify-content:center;
}
.id-ava-pick img{position:absolute; top:0; right:0; bottom:0; left:0; width:100%; height:100%; object-fit:cover}
.id-ava-pick b{font-weight:600}
.id-ava-cam{
  position:absolute; right:1px; bottom:1px; width:23px; height:23px; border-radius:50%;
  background:rgba(8,10,16,.62); color:#fff;
  display:flex; align-items:center; justify-content:center;
  box-shadow:0 1px 5px rgba(0,0,0,.4);
}
.id-ava-cam svg{width:13px; height:13px}
.id-ava-side{display:flex; flex-direction:column; gap:2px; align-items:flex-start}
.id-ava-side .lk{
  font:inherit; font-size:13.5px; color:var(--accent);
  padding:5px 9px; margin-left:-9px; border-radius:9px; cursor:pointer;
}
.id-ava-side .lk:hover{background:var(--panel)}
.id-ava-side .lk.dimv{color:var(--dim-2)}
.id-ava-side .lk[hidden]{display:none}

.pick-row{display:flex; gap:8px; flex-wrap:wrap}
.pick-grid{display:grid; grid-template-columns:1fr 1fr; gap:8px}
.pick-grid .pill{justify-content:center}
.menu-item{
  display:flex; align-items:center; gap:11px; width:100%; text-align:left; cursor:pointer;
  padding:11px 13px; border-radius:13px; border:1px solid var(--line); background:var(--panel); color:var(--fg);
}
.menu-item:hover{background:var(--panel-2)}
.menu-item svg{width:17px;height:17px; flex:none; color:var(--dim)}
.menu-item span{display:flex; flex-direction:column; gap:1px; font-size:14px}
.menu-item em{font-style:normal; font-size:11.5px; color:var(--dim-2)}

.mode-item{
  text-align:left; cursor:pointer; padding:11px 13px; border-radius:13px;
  border:1px solid var(--line); background:var(--panel);
}
.mode-item.on{border-color:rgba(124,156,255,.6); background:rgba(124,156,255,.1)}
.mode-item b{display:block; font-size:14px}
.mode-item span{font-size:12px; color:var(--dim-2); line-height:1.65}

/* ------------------------------ 沉浸阅读 ------------------------------ */
body.immersive .topbar,body.immersive .tabbar{display:none}

/* ------------------------------ 杂项 ------------------------------ */
.drawer-search{padding:8px 10px 0}
.drawer-search input{
  width:100%; padding:8px 11px; border-radius:11px; font:inherit; font-size:13.5px; color:inherit;
  background:var(--panel); border:1px solid var(--line); outline:none;
}
.drawer-search input::-webkit-search-cancel-button{-webkit-appearance:none}

.composer .mini-ic{
  flex:none; width:30px; height:30px; border-radius:9px; margin-bottom:3px;
  display:flex; align-items:center; justify-content:center; color:var(--dim-2);
}
.composer .mini-ic:hover{background:var(--panel-2); color:var(--fg)}
.composer .mini-ic svg{width:18px;height:18px}

.topbar .title .sub.tap{cursor:pointer}
.topbar .title .sub.tap:hover{color:var(--accent)}
.topbar .title .sub.tap::after{content:" \25BE"; font-size:10px}
/* 「创意」档名最长四个字（天马行空），给足宽别让它折行 */
.slider-row .val.wide{width:74px}

@media (max-width:520px){
  .pg-grid{grid-template-columns:repeat(auto-fill,minmax(118px,1fr)); gap:10px}
}

/* ==================================================================
   App 手感优化（2026-10-16 第二批）
   ================================================================== */

/* 「我的卡 / 宇宙」有自己的页头（标题+操作钮），聊天顶栏在上面纯属重复，
   还把「＋」和设置摆了两份 —— 非聊天 Tab 直接把顶栏藏掉。 */
body[data-tab="cards"] .topbar,
body[data-tab="universe"] .topbar{display:none}

/* -------------------------- 触摸手感 -------------------------- */
/* 点按无高亮、无双击缩放、无橡皮筋回弹 —— 这三条是「像不像 App」的分水岭 */
html, body{overscroll-behavior:none}
body{
  touch-action:manipulation;
  -webkit-tap-highlight-color:transparent;
}
/* 界面骨架一律不许长按出选择框 / 弹出系统菜单；正文除外（下面单独放开） */
.topbar, .tabbar, .composer, #drawer, .modal-card, .pg-head, .pg-search, .pg-tabs{
  -webkit-user-select:none; user-select:none;
  -webkit-touch-callout:none;
}
/* 聊天正文仍然可以选中复制 —— 但只给鼠标用户（触屏长按要走自家的操作单） */
@media (hover:hover) and (pointer:fine){
  .msg .bubbles{user-select:text; -webkit-user-select:text}
}
/* 滚动区全部吃上动量滚动 */
#messages, #sessionList, .pg-grid, .modal-card, .lib-grid{
  -webkit-overflow-scrolling:touch;
}
/* 触屏上按钮的 hover 悬停态没意义，改为按下反馈 */
@media (hover:none){
  .btn:active, .tab:active, .icon-btn:active, .tpl:active, .sess:active,
  .menu-item:active, .pill:active, .opt:active{
    filter:brightness(1.35);
  }
  /* 触屏上删除键**不再常显**：必须左滑把行拉开才露出来，
     否则「点错一下就删」的老毛病跟着回来。 */
}
/* 积分角标不可点（2026-10-16）：只展示，光标用默认 */

/* ==================================================================
   第五批（2026-10-16）：思考过程折叠 / 主题图标 / 会话左滑 / 应用内确认
   ================================================================== */

/* 抽屉里的主题图标（挨着积分角标） */
.drawer-head .icon-btn{flex:none; width:34px; height:34px; border-radius:11px; color:var(--dim)}
.drawer-head .icon-btn svg{width:18px; height:18px}
.drawer-head .icon-btn:hover{background:var(--panel-2); color:var(--fg)}

/* 「积分」窗口里的余额进度条：当前 / 历史最高（和抽屉同一个口径，见 paintQuota）。
   窗口里不再单独放一行大号余额，当前积分就是这个块的主数字。 */
.q-pk{margin:12px 0 2px; display:flex; flex-direction:column; gap:6px}
.q-pk-top{display:flex; align-items:baseline; justify-content:space-between; gap:10px;
  font-size:11.5px; color:var(--dim-2); font-variant-numeric:tabular-nums}
.q-pk-top b{font-weight:600; color:var(--fg)}
/* 当前积分就是这张卡的主数字（原窗口顶部那行大号余额已并到这里） */
#qPkNow{font-size:30px; font-weight:650; line-height:1.1; letter-spacing:-.01em}
.q-pk-pct{font-style:normal; margin-left:6px; color:var(--dim-2)}
.q-pk-bar{height:6px; border-radius:999px; background:var(--panel-2);
  box-shadow:inset 0 0 0 1px var(--line); overflow:hidden}
.q-pk-bar i{display:block; height:100%; width:0; border-radius:inherit;
  background:linear-gradient(90deg,var(--grad-1),var(--grad-2));
  transition:width .5s cubic-bezier(.2,.8,.2,1)}

/* 「积分」窗口里的明细行（账号 / 登录后已消耗 / 累计兑换） */
/* 邀请码：等宽大字 + 轻字距，方便一眼念给别人 / 截图发出去。
   字距别超过 .15em —— 再大就会被读成「7 y x 3」四个独立字符。 */
.aff-code{
  margin:12px 0 4px; padding:16px 10px; border:1px dashed var(--line-strong); border-radius:14px;
  background:var(--panel-2); text-align:center;
  font:600 25px/1.15 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  letter-spacing:.12em; text-indent:.12em;   /* 字距把末尾也撑开了，用同量首行缩进抵消 */
  color:var(--fg); user-select:all;
}
/* 名单小标题：分区分隔用的虚线上沿，别做成按钮的样子 */
.aff-head{
  margin-top:16px; padding-top:12px; border-top:1px dashed var(--line);
  font-size:12.5px; color:var(--dim);
}

.q-grid{margin:14px 0 2px; border:1px solid var(--line); border-radius:12px; overflow:hidden}
.q-row{display:flex; justify-content:space-between; align-items:center; padding:9px 13px; font-size:13px}
.q-row + .q-row{border-top:1px solid var(--line)}
.q-row span{color:var(--dim)}
.q-row b{font-weight:600; color:var(--fg); font-variant-numeric:tabular-nums}
.q-sep{height:1px; background:var(--line); margin:16px 0 14px}

/* 记录区：兑换记录 / 积分记录 —— 两个标签页共用下面这块内容区 */
.q-tabs{display:flex; gap:6px; margin-top:16px; padding-top:12px; border-top:1px dashed var(--line)}
.q-tab{flex:1; padding:7px 0; border:1px solid var(--line); border-radius:9px; background:transparent;
  color:var(--dim); font:inherit; font-size:12.5px; cursor:pointer;
  transition:color .15s, border-color .15s, background .15s}
.q-tab:hover{color:var(--fg)}
.q-tab.on{background:var(--panel-2); border-color:var(--line-strong); color:var(--fg)}
.q-log{margin-top:6px}
.q-log-row{display:flex; justify-content:space-between; gap:10px; padding:5px 2px; font-size:12.5px}
.q-log-row b{flex:none; font-weight:600; font-variant-numeric:tabular-nums}
.q-log-row b.in{color:var(--ok)}
.q-log-row b.out{color:var(--dim)}
.q-log-row span{min-width:0; color:var(--dim-2);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
/* 积分记录按天分组：每天一张小卡片，日期头可点开合；当天（.q-day-today）不可折叠、不放箭头 */
.q-day{margin-top:8px; border:1px solid var(--line); border-radius:11px;
  background:var(--panel-2); overflow:hidden}
.q-day:first-child{margin-top:4px}
.q-day-head{position:relative; display:flex; align-items:center; justify-content:space-between; gap:10px;
  width:100%; padding:11px 12px 11px 26px; border:0; background:transparent; color:var(--dim);
  font:inherit; font-size:12.5px; cursor:pointer; text-align:left;
  transition:background .15s, color .15s}
/* 单个右向箭头，展开时旋转 90° 朝下 —— 旋转过渡比换字符顺眼得多 */
.q-day-head::before{content:'▸'; position:absolute; left:11px; top:50%;
  transform:translateY(-50%); font-size:11px; color:var(--dim);
  transition:transform .2s ease, color .15s}
.q-day.open > .q-day-head::before{transform:translateY(-50%) rotate(90deg); color:var(--accent)}
.q-day-head:hover{background:var(--panel); color:var(--fg)}
.q-day-date{white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-weight:600; color:var(--fg)}
.q-day-sum{flex:none; display:flex; gap:10px; font-weight:600; font-variant-numeric:tabular-nums}
.q-day-sum b{font-weight:600}
.q-day-sum b.in{color:var(--ok)}
.q-day-sum b.out{color:var(--dim-2)}
/* 平滑收起：grid-template-rows 0fr↔1fr 过渡，不用预先知道内容高度 */
.q-day-body{display:grid; grid-template-rows:0fr; transition:grid-template-rows .26s ease}
.q-day.open > .q-day-body{grid-template-rows:1fr}
.q-day-inner{overflow:hidden; min-height:0}
.q-day-inner .q-log-row{padding-left:26px; padding-right:12px}
.q-day-inner .q-log-row:last-child{padding-bottom:10px}
.q-log-empty{font-size:12px; color:var(--dim-2); padding:6px 2px}
.q-more{width:100%; margin-top:6px; padding:7px 0; border:1px solid var(--line); border-radius:9px;
  background:transparent; color:var(--dim); font:inherit; font-size:12px; cursor:pointer}
.q-more:hover{color:var(--fg); border-color:var(--line-strong)}

/* 每日签到：夹在「明细」和「兑换」之间的一小块，别做得比余额还抢眼 */
.q-check{margin-top:14px; padding:12px 13px; border:1px solid var(--line); border-radius:12px;
  background:var(--panel-2); display:flex; flex-direction:column; gap:9px}
.q-check-head{display:flex; align-items:center; justify-content:space-between; gap:8px; font-size:13px}
.q-check-head b{font-weight:600; color:var(--fg)}
.q-check-head span{font-size:11.5px; color:var(--dim-2); white-space:nowrap}
.q-check .btn{width:100%}

/* 通用按钮的忙碌态（原来只有门禁按钮有） */
.btn.loading{pointer-events:none; opacity:.75}
.btn.loading::before{
  content:""; flex:none; width:15px; height:15px; border-radius:50%;
  border:2px solid rgba(255,255,255,.35); border-top-color:#fff;
  animation:spin .7s linear infinite;
}
.btn.ghost.loading::before{border-color:rgba(140,150,180,.4); border-top-color:var(--fg)}

/* 「更多」面板里的行：label 与控件同一行时别顶太开 */
.modal-card .switch-row{margin:14px 0 4px}

/* ------------------------------ 记忆片段 ------------------------------
 * 记忆片段 = 一小段「值得记住的事」，挂在会话或卡片上。
 * 列表出现在三处：对话菜单里的「记忆片段」、卡片详情、删会话后的追问。
 */

/* 🔴 .btn 这些自带 display 的类会盖掉浏览器默认的 [hidden]{display:none}，
   于是 hidden 属性形同虚设。这里钉死一条：hidden 就是藏起来。 */
[hidden]{display:none !important}

.mem-wrap{display:flex; flex-direction:column; gap:10px}
/* 🔴 这里**不许**再给自己设 max-height + overflow。
   曾经是 `max-height:min(38vh,300px); overflow-y:auto`，两层坑：
   ① 它是 flex column，容器一限高，条目默认 flex-shrink:1 就被**压扁** ——
      实测条目 94px 而里面文字 104px，多出来的被 .mem-item 的 overflow:hidden 裁掉，
      表现就是「多行的话后半截看不见」。
   ② 它嵌在 .modal-card（也是 overflow-y:auto）里，两层滚动容器在手机上极难划动。
   现在让它自适应高度，统一交给弹窗滚。 */
.mem-list{
  display:flex; flex-direction:column; gap:8px;
}
/* 保险：就算将来谁又给列表限了高，条目也不许被压扁 */
.mem-list > .mem-item{flex:none}

/* 记忆列表的外壳容器（2026-10-17 用户要求「收入一个容器里，避免框体过长」）：
   最多露出 MEM_SHOWN(15) 条，超出在容器里纵向滑；滑动条被全局隐身规则糊掉。
   🔴 max-height 由 JS 现量前 15 条的真实高度（fitScroller），**别在这里写死**——
     条目有一行的也有三行的，写死必然错。这里只管容器感与滚动行为。
   🔴 限高限的是这层外壳，.mem-list 本身依旧不限高、不被压扁（老规矩的根因）。
   🔴 `overflow-y:auto` 是这条规矩的魂：没有它，max-height 只是把内容切掉，
     容器根本滑不动（2026-10-17 第一版就漏了它，E2E 当场抓住）。 */
.mem-scroll{
  padding:7px;
  border:1px solid var(--line); border-radius:12px;
  background:rgba(255,255,255,.02);
  overflow-y:auto;
}
/* 标题右边的「清空记忆」（2026-10-17 用户要求加在记忆片段标题旁）：
   小、不起眼，hover 才泛红——危险操作不能比标题还抢戏，但不能没有警示。
   详情弹窗的 .det-sec-h 本来就是 space-between，按钮往那一放就是右对齐；
   记忆窗口的标题行用下面的 .sheet-head 撑出同样的布局。 */
.mem-clear{
  flex:none; display:flex; align-items:center; gap:4px;
  padding:4px 9px; border-radius:8px;
  border:1px solid var(--line); background:none;
  color:var(--dim-2); font-size:12px; line-height:1.4; cursor:pointer;
  transition:color .15s, border-color .15s, background .15s;
}
.mem-clear:hover{
  color:var(--danger); border-color:rgba(248,113,113,.45); background:rgba(248,113,113,.1);
}
.mem-clear svg{width:13px; height:13px}
/* 记忆窗口的标题行：左标题右按钮（沿用 .lib-title 的路子） */
.sheet-head{display:flex; align-items:center; justify-content:space-between; gap:10px}
.sheet-head h2{margin:0}
/* 一条记忆 = 可横向平移的 .mem-body + 藏在它底下的 .mem-del（左滑露出）。
   🔴 别给 .mem-item 直接加 padding/背景：滑动的是里面那层，外壳只负责裁切。 */
.mem-item{
  position:relative; overflow:hidden; border-radius:12px;
}
.mem-item .mem-body{
  position:relative; z-index:1;
  display:flex; align-items:flex-start; gap:8px;
  padding:10px 11px; border-radius:12px;
  background:var(--panel); border:1px solid var(--line);
  transition:transform .18s ease;
  /* 🔴 纵向滚动留给浏览器，只有横向位移归我们 —— 少了这条会和弹窗滚动抢手势 */
  touch-action:pan-y;
}
/* 正文 + 统计行（2026-10-18 加）：两者纵向排成一列，右侧的「卡」标 / 编辑按钮照旧并排。
   🔴 .mem-txt 原来带 flex:1 —— 挪进纵向 flex 后 flex:1 会被当成纵向伸展，去掉。 */
.mem-item .mem-main{flex:1; min-width:0; display:flex; flex-direction:column; gap:3px}
.mem-item .mem-txt{
  min-width:0; font-size:13px; line-height:1.6;
  white-space:pre-wrap; word-break:break-word;
}
/* 每个片段的统计行：注入次数 / 命中次数 / 更新次数 / 最后更新时间 */
.mem-item .mem-meta{
  display:flex; flex-wrap:wrap; gap:2px 10px;
  font-size:11px; line-height:1.4; color:var(--dim-2);
}
.mem-item .mem-meta span{white-space:nowrap}
.mem-item .mem-acts{display:flex; gap:2px; flex:none; opacity:.7; transition:opacity .15s}
.mem-item:hover .mem-acts{opacity:1}
.mem-item .mem-acts button{
  display:flex; align-items:center; justify-content:center;
  width:26px; height:26px; border-radius:8px;
  border:none; background:none; color:var(--dim); cursor:pointer;
}
.mem-item .mem-acts button:hover{background:var(--panel-2); color:var(--fg)}
.mem-item .mem-acts svg{width:14px; height:14px}
.mem-item .mem-tag{
  flex:none; margin-top:1px; padding:1px 5px; border-radius:6px;
  font-size:10.5px; line-height:1.5; color:var(--dim-2);
  border:1px solid var(--line); background:rgba(255,255,255,.03);
}
/* 事件状态小标（2026-10-20 加）：「已完成」= 这件事有始有终、收尾了；
   「已作废」= 已不再成立。状态编码在记忆文本里，这里纯展示，跟 .mem-tag 同形只换颜色。 */
.mem-item .mem-meta .mem-state{
  padding:0 5px; border-radius:6px; font-size:10.5px; line-height:1.5;
  color:var(--ok); border:1px solid currentColor;
}
.mem-item .mem-meta .mem-state.void{color:var(--dim-2)}
/* 「进行中」= 事件刚起手、还没收尾（文本里带「打算 / 正要 / 正在」这类起手词）。 */
.mem-item .mem-meta .mem-state.open{color:var(--accent)}

/* 左滑露出来的删除区
   🔴 必须靠 opacity 藏起来，不能指望被 .mem-body 盖住 ——
      .mem-body 的背景是 --panel，那是个**近乎全透明的玻璃色**
      （实测 alpha 只有 0.043），红色会直接透上来，看着像每条都滑开了。 */
.mem-del{
  position:absolute; top:0; right:0; bottom:0; width:76px; z-index:0;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px;
  border:none; border-radius:0 12px 12px 0;
  background:var(--danger); color:#fff; font-size:12px; cursor:pointer;
  opacity:0; pointer-events:none;
  transition:opacity .15s ease;
}
.mem-del svg{width:16px; height:16px}
/* 正在拖 / 已滑开：才让删除区现形 */
.mem-item.swiping .mem-del,
.mem-item.swiped .mem-del{opacity:1; pointer-events:auto}

/* 行内编辑：正在改的那条淡出，输入框就贴在它下面 */
.mem-item.editing .mem-body{opacity:.45}
.mem-edit{display:flex; flex-direction:column; gap:8px}
.mem-edit .mem-input{min-height:74px}
/* 只有两个按钮，靠右即可 —— 原来那行「Ctrl/⌘ + Enter 保存」的说明已按用户要求去掉，
   快捷键照旧生效，只是不再写在界面上。 */
.mem-edit .mem-edit-acts{display:flex; justify-content:flex-end; gap:8px}
.btn.tiny{padding:6px 13px; font-size:12.5px; border-radius:10px}

/* 卡片详情里的「自动记忆已关闭」提醒 —— 提炼全自动了，开关关掉时不说一声
   用户只会以为记忆坏了。 */
.mem-tip{
  margin-bottom:9px; padding:9px 11px; border-radius:10px;
  font-size:12px; line-height:1.6; color:var(--dim);
  background:rgba(224,164,88,.1); border:1px solid rgba(224,164,88,.32);
}

.mem-empty{
  padding:14px; border-radius:12px; text-align:center;
  font-size:12.5px; color:var(--dim-2);
  background:rgba(255,255,255,.03); border:1px dashed var(--line);
}
.mem-input{
  width:100%; padding:11px 13px; border-radius:12px; min-height:96px;
  background:rgba(255,255,255,.05); border:1px solid var(--line);
  color:var(--fg); font-size:14px; line-height:1.6; resize:vertical;
  outline:none; transition:border-color .15s;
}
.mem-input:focus{border-color:rgba(124,156,255,.6)}
.tip.err{color:var(--danger); margin-top:6px; min-height:1em}
.btn.wide{width:100%}
.btn i{font-style:normal; color:var(--dim-2); font-size:12px}

/* ---------------------------- 卡片详情弹窗 ---------------------------- */

.tpl-detail .det-head{display:flex; gap:13px; margin-bottom:16px}
.tpl-detail .det-cover{
  flex:none; width:82px; aspect-ratio:3/4; border-radius:12px; overflow:hidden;
  background:var(--panel); border:1px solid var(--line);
}
.tpl-detail .det-cover img{width:100%; height:100%; object-fit:cover; display:block}
.tpl-detail .det-meta{flex:1; min-width:0}
.tpl-detail .det-meta h2{margin:0 0 6px; font-size:17px}
.tpl-detail .det-meta .desc{margin:6px 0 0; font-size:12.5px; line-height:1.6}
.tpl-detail .det-tags{display:flex; flex-wrap:wrap; gap:5px}
.tpl-badge{
  font-size:11px; padding:2px 8px; border-radius:99px;
  background:rgba(124,156,255,.14); color:var(--accent);
  border:1px solid rgba(124,156,255,.25);
}
.tpl-badge.ghost{background:rgba(255,255,255,.05); color:var(--dim); border-color:var(--line)}

.tpl-detail .det-sec{margin-top:16px}
.tpl-detail .det-sec-h{
  display:flex; align-items:baseline; justify-content:space-between; gap:8px;
  margin-bottom:8px;
}
.tpl-detail .det-sec-h h3{
  display:flex; align-items:center; gap:6px; margin:0;
  font-size:14px; font-weight:500;
}
.tpl-detail .det-sec-h h3 svg{width:15px; height:15px; color:var(--accent)}
.tpl-detail .det-sec-h h3 i{font-style:normal; color:var(--dim-2); font-size:12px}

/* 详情里的会话历史：与抽屉里那份是同一份数据 */
.det-sess{display:flex; flex-direction:column; gap:6px; max-height:34vh; max-height:34dvh; overflow-y:auto}
.det-sess .ds-row{
  display:flex; align-items:center; gap:8px;
  padding:9px 10px; border-radius:11px;
  background:var(--panel); border:1px solid var(--line); cursor:pointer;
}
.det-sess .ds-row:hover{background:var(--panel-2)}
.det-sess .ds-row .txt{flex:1; min-width:0}
.det-sess .ds-row .txt b{
  display:block; font-size:13px; font-weight:500;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.det-sess .ds-row .txt span{font-size:11px; color:var(--dim-2)}
.det-sess .del-ic{
  flex:none; display:flex; align-items:center; justify-content:center;
  width:28px; height:28px; border-radius:8px;
  border:none; background:none; color:var(--dim); cursor:pointer;
}
.det-sess .del-ic:hover{background:rgba(248,113,113,.16); color:var(--danger)}
.det-sess .del-ic svg{width:15px; height:15px}

/* ============ 启动验证遮罩 ============
   z-index 压在门禁（.modal-root = 30）之下：验不过时门禁要能盖住它。
   撤掉用 .off，别 display:none —— 那样没有任何过渡，界面会硬跳一下。 */
#bootVeil{
  position:fixed; top:0; right:0; bottom:0; left:0; z-index:28;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px;
  background:var(--bg); color:var(--dim);
  transition:opacity .2s ease;
}
#bootVeil.off{opacity:0; visibility:hidden; pointer-events:none}
/* 进度条取代原来的转圈：只报「第几个 / 共几个」，不报内部分步。 */
#bootVeil .bv-bar{
  width:min(62vw,240px); height:6px; border-radius:999px; overflow:hidden;
  background:var(--line);
}
#bootVeil .bv-bar i{
  display:block; width:0; height:100%; border-radius:inherit; background:var(--accent);
  transition:width .34s cubic-bezier(.22,.61,.36,1);
}
/* app.js 还没报到进度前先跑一段走马灯，别让用户对着一条空槽发呆 */
#bootVeil .bv-bar.idle i{width:38%; animation:bvSlide 1.15s ease-in-out infinite}
@keyframes bvSlide{0%{transform:translateX(-110%)}100%{transform:translateX(295%)}}
/* 文案跟进度条同宽同轴：左右两端正好压在条子的起点/终点上 */
#bootVeil > b{width:min(62vw,240px); font-size:13px; font-weight:500; color:var(--fg); text-align:center; word-break:break-word}
#bootVeil > i{font-style:normal; font-size:11px; color:var(--dim-2); letter-spacing:.04em; text-align:center; padding:0 24px; max-width:92vw; word-break:break-all}
