/* app 内页面的样式 */

.pad    { padding: var(--sp-4); }
.pad-x  { padding-left: var(--sp-4); padding-right: var(--sp-4); }
.pad-t  { padding-top: var(--sp-4); }
.pad-b  { padding-bottom: var(--sp-6); }
.quiet-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }

/* 外观预设 */
.look-list { display: flex; flex-direction: column; gap: var(--sp-2); padding: 0 var(--sp-4); }
.look-row { display: flex; align-items: center; gap: var(--sp-2); }
.look-card {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3); border-radius: var(--r-md);
  background: var(--group-fill); text-align: left;
}
.look-card:active { opacity: .7; }
.look-thumb {
  flex: none; width: 46px; height: 46px; border-radius: var(--r-sm);
  background: var(--surface-3); color: var(--text-3);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.look-thumb.has-image { background-size: cover; background-position: center; }
.look-meta { flex: 1; min-width: 0; }
.look-name { font-size: var(--fs-15); font-weight: var(--fw-medium); }
.look-sub { font-size: var(--fs-12); color: var(--text-3); margin-top: 2px; }
.look-more { flex: none; padding: var(--sp-2); color: var(--text-3); }

/* 字体 */
.font-slot-val { max-width: 130px; font-size: var(--fs-14); color: var(--text-2); }
.li-hint { font-size: var(--fs-13); color: var(--text-3); }
/* 导入预览里被勾掉的那几条 */
.is-off { opacity: .4; }
.char-name { font-size: var(--fs-17); font-weight: var(--fw-semi); }
/* 账号与小号 */
.acc-tag {
  margin-left: 6px; padding: 1px 6px; border-radius: var(--r-full);
  background: var(--group-fill); color: var(--text-3); font-size: var(--fs-11);
  font-weight: var(--fw-normal);
}
.acc-alt { padding-left: var(--sp-5); }

/* 联系：卡片墙 */
.ct-sec {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  padding: var(--sp-5) var(--sp-4) var(--sp-2);
  font-size: var(--fs-12); color: var(--text-3);
}
/* 一行三张。两张一行时一屏只看得到四个角色，角色一多要翻很久才找得到人 */
.ct-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-3) var(--sp-2); padding: 0 var(--sp-4);
}
.ct-card { text-align: left; min-width: 0; }
.ct-card:active { opacity: .72; }
.ct-cover {
  position: relative; width: 100%; aspect-ratio: 1;
  border-radius: var(--r-md); overflow: hidden;
  background: var(--group-fill); color: var(--text-3);
  display: flex; align-items: center; justify-content: center;
}
.ct-cover.has-image { background-size: cover; background-position: center; }
.ct-initial { font-size: var(--fs-28); font-weight: var(--fw-medium); }
.ct-badge {
  position: absolute; left: var(--sp-2); bottom: var(--sp-2);
  padding: 2px 7px; border-radius: var(--r-full);
  background: var(--scrim-strong); color: var(--on-scrim); font-size: var(--fs-11);
}
.ct-name { margin-top: var(--sp-2); font-size: var(--fs-14); font-weight: var(--fw-semi); }
.ct-sign { margin-top: 1px; font-size: var(--fs-12); color: var(--text-3); }

/* 联系：资料页 */
.pf-head { padding: var(--sp-6) var(--sp-4) var(--sp-4); text-align: center; }
.pf-avatar { display: flex; justify-content: center; }
.pf-name { margin-top: var(--sp-3); font-size: var(--fs-20); font-weight: var(--fw-semi); }
.pf-sign { margin-top: 4px; font-size: var(--fs-13); color: var(--text-2); }
.pf-facts {
  display: flex; justify-content: center; gap: var(--sp-5);
  margin-top: var(--sp-4);
}
.pf-fact { text-align: center; }
.pf-fact b { display: block; font-size: var(--fs-15); font-weight: var(--fw-medium); }
.pf-fact span { font-size: var(--fs-11); color: var(--text-3); }
.pf-acts { display: flex; gap: var(--sp-2); padding: var(--sp-4); }
.pf-acts > * { flex: 1; }

/* 关系网 */
.net-wrap { padding: var(--sp-4); }
.net-svg { width: 100%; height: auto; display: block; }
.net-node-label { font-size: 11px; fill: var(--text); }
.net-edge-label { font-size: 9px; fill: var(--text-3); }
.font-sample {
  font-size: var(--fs-17); color: var(--text); margin-top: 4px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* 文字按钮同理：看着还是那几个字，命中区撑到 44 高 */
.nav-text {
  position: relative;
  font-size: var(--fs-14); color: var(--accent); padding: var(--sp-2);
  min-height: 34px; display: inline-flex; align-items: center;
}
.nav-text::after { content: ''; position: absolute; inset: -5px; }

.hint-box {
  font-size: var(--fs-12); color: var(--text-2); line-height: 1.65;
  background: var(--surface-2); border-radius: var(--r-sm);
  padding: var(--sp-3); margin-bottom: var(--sp-4);
}
.warn-box {
  font-size: var(--fs-12); color: var(--text); line-height: 1.6;
  background: var(--surface-3); border-left: 3px solid var(--text); border-radius: var(--r-sm);
  padding: var(--sp-3); margin-bottom: var(--sp-4);
}
.settings-foot {
  text-align: center; font-size: var(--fs-11); color: var(--text-3);
  padding: var(--sp-6) var(--sp-4);
}
.settings-foot.is-error { color: var(--danger); word-break: break-word; }

/* 美化库里的静态预览。里面是一个 shadow root，那份美化的 CSS 只在里面生效，
   所以外面这一圈框由本表负责，不会被它改掉。 */
.skin-preview {
  margin: var(--sp-4); border-radius: var(--r-lg); overflow: hidden;
  border: 1px solid var(--border); background: var(--surface);
  height: 220px;
}

.chip-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-2); }
.chip {
  padding: 5px var(--sp-3); border-radius: var(--r-full);
  background: var(--surface-2); color: var(--text-2); font-size: var(--fs-12);
  border: 1px solid transparent;
}
.chip.is-active { background: var(--accent-weak); color: var(--accent); border-color: var(--accent); }

.avatar-picker {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--sp-3); padding: var(--sp-4) 0 var(--sp-6);
}

input[type="range"] {
  width: 100%; padding: 0; background: none; border: none;
  accent-color: var(--accent);
}

/* 注入顺序 */
.order-list { display: flex; flex-direction: column; gap: var(--sp-2); }
.order-row {
  display: flex; align-items: center; gap: var(--sp-2);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: var(--sp-2) var(--sp-3);
}
.order-idx {
  width: 20px; height: 20px; flex: none; border-radius: var(--r-xs);
  background: var(--surface-2); color: var(--text-3);
  font-size: var(--fs-11); display: flex; align-items: center; justify-content: center;
}
.order-body { flex: 1; min-width: 0; }
.order-label { font-size: var(--fs-13); }
.order-desc { font-size: var(--fs-11); color: var(--text-3); margin-top: 1px; }
.order-btn {
  width: 28px; height: 28px; flex: none; border-radius: var(--r-xs);
  background: var(--surface-2); color: var(--text-2);
  display: flex; align-items: center; justify-content: center;
}
.order-btn:disabled { opacity: .3; }

.tpl-vars {
  font-size: var(--fs-11); color: var(--text-3);
  font-family: var(--font-mono); margin: var(--sp-2) 0 var(--sp-4);
}

/* 记忆 */
.stat-row { display: flex; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.stat-chip {
  flex: 1; background: var(--surface); border-radius: var(--r-sm);
  padding: var(--sp-2); text-align: center;
}
.stat-chip b { display: block; font-size: var(--fs-17); font-weight: var(--fw-semi); }
.stat-chip span { font-size: var(--fs-11); color: var(--text-3); }
.rank {
  width: 22px; height: 22px; border-radius: var(--r-xs);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-11); font-weight: var(--fw-semi);
  background: var(--surface-2); color: var(--text-2);
}
.rank-S { background: var(--accent); color: var(--on-accent); }
.rank-A { background: var(--accent-weak); color: var(--accent); }

/* 会话 */
.chat-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; font-size: var(--fs-11); }

.conv { height: 100%; display: flex; flex-direction: column; }
/* 消息区 + 浮在它上面的东西。定位上下文只能是这一层：
   放到 .conv 上的话，「回到最新」会压在发送键上 */
.conv-main { flex: 1; min-height: 0; display: flex; flex-direction: column; position: relative; }
.conv-body { flex: 1; min-height: 0; padding: var(--sp-4) var(--sp-3); display: flex; flex-direction: column; gap: var(--bubble-gap); }
.conv-hint { margin: auto; color: var(--text-3); font-size: var(--fs-12); }

.msg { display: flex; gap: var(--sp-2); align-items: flex-start; }
.msg.is-mine { flex-direction: row-reverse; }
/* 同一个人接着说的那一条，和上一条之间收紧。收紧到的距离跟着「同一轮里两个气泡之间」走，
   美化里改了那个间距，这里跟着变 */
.msg.is-cont { margin-top: calc(var(--bubble-gap-in) * 2 - var(--bubble-gap)); }
/* 两条消息中间居中的那一行时间（相隔五分钟以上才有） */
.time-sep { align-self: center; padding: var(--sp-1) 0; font-size: var(--fs-12); color: var(--text-3); }
.msg-col { display: flex; flex-direction: column; gap: var(--bubble-gap-in); max-width: 74%; min-width: 0; }
.msg.is-mine .msg-col { align-items: flex-end; }
/* 气泡上的时刻与已读回执。两样默认都关着，开了才有这个节点。
   靠着气泡画的时候要给它让出位置，否则窄屏上会把气泡挤出去 */
.msg.has-aside .msg-col { max-width: 62%; }
.msg-meta {
  flex: none; display: flex; gap: var(--sp-1);
  font-size: var(--fs-11); color: var(--text-3); line-height: 1.3;
  white-space: nowrap;
}
.msg-meta.at-side {
  align-self: flex-end; flex-direction: column; align-items: flex-start;
  gap: 1px; padding-bottom: 3px;
}
.msg.is-mine .msg-meta.at-side { align-items: flex-end; }
.msg-meta.at-below { align-items: baseline; padding-top: 1px; }
.bubble {
  padding: var(--bubble-py) var(--bubble-px); border-radius: var(--bubble-r);
  background: var(--surface); font-size: var(--bubble-fs); line-height: 1.6;
  white-space: pre-wrap; word-break: break-word;
  box-shadow: var(--shadow-1);
}
.msg.is-mine .bubble { background: var(--accent); color: var(--on-accent); }

/* 正在输入写在标题栏上，名字整个换掉 —— 这句话说的是「对方现在在干什么」，
   属于这段对话的状态，不属于消息流。聊天记录里凭空多一个空泡，
   是这个界面在说自己的事。 */
.conv-typing { color: var(--text-3); font-weight: var(--fw-normal); }


/* 回复失败那一条：报错原文（最多三行）、重试、去充值 */
.fail-note { display: flex; flex-direction: column; gap: var(--sp-1); max-width: 260px; }
.fail-text { font-size: var(--fs-11); color: var(--text-3); line-height: 1.5; word-break: break-all;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.fail-acts { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.fail-hint { font-size: var(--fs-11); color: var(--text-3); line-height: 1.5; }
.msg-retry { display: flex; align-items: center; gap: 4px; font-size: var(--fs-11); color: var(--text-2); text-decoration: underline; }
.swipes { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-11); color: var(--text-3); }
/* 命中禁写词的记号。低调地摆在气泡下面，不拦着也不弹窗 —— 要不要重掷是用户的事 */
.msg-ban { font-size: var(--fs-11); color: var(--text-3); padding: var(--sp-1) 0 0; }
.swipe-btn { color: var(--text-2); display: flex; }

/* 引用块。气泡上面顶一小条，点一下跳回原话。
   align-self 收着，不然它会撑满整列，比底下那条正文还宽 */
.quote-ref {
  align-self: flex-start;
  display: flex; align-items: baseline; gap: var(--sp-2);
  max-width: 100%; min-width: 0; text-align: left;
  padding: 5px var(--sp-2); border-radius: var(--r-sm);
  background: var(--group-fill); border-left: 2px solid var(--group-line);
  font-size: var(--fs-11); color: var(--text-3); line-height: 1.5;
}
.quote-ref.is-dead { opacity: .6; }
.quote-name { flex: none; color: var(--text-2); }
.quote-text { min-width: 0; }
/* 自己发的靠右，但名字仍然读在前面，别跟着一起翻过来 */
.msg.is-mine .quote-ref {
  align-self: flex-end;
  border-left: none; border-right: 2px solid var(--group-line);
}

/* 被引用的那条跳过去之后闪一下 */
.msg.is-flash .bubble { animation: msg-flash var(--dur-slow) var(--ease-out) 2 alternate; }
@keyframes msg-flash { to { background: var(--accent-weak); } }

/* 长按菜单里先把这条原样摆出来，别让人对着一列动作猜自己按中了哪条 */
.msg-menu-quote {
  margin: 0 var(--sp-4) var(--sp-3); padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm); background: var(--group-fill);
  font-size: var(--fs-12); color: var(--text-2); line-height: 1.6;
  max-height: 84px; overflow: hidden;
}

/* 修格式 */
.fix-head {
  padding: 0 var(--sp-4) var(--sp-3);
  font-size: var(--fs-12); color: var(--text-3); line-height: 1.6;
}
.fix-preview {
  margin: 0 var(--sp-4) var(--sp-3); padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm); background: var(--group-fill);
  font-size: var(--fs-12); color: var(--text-2); line-height: 1.6;
  white-space: pre-wrap; word-break: break-word;
  max-height: 120px; overflow: hidden;
}

/* 多选。只用左边那个勾表示选中，不去框气泡 —— 描一圈边太吵 */
.msg.is-picking { cursor: pointer; }
.msg.is-picking .msg-col { pointer-events: none; }
.pick-dot {
  flex: none; align-self: center; width: 18px; height: 18px; border-radius: var(--r-full);
  border: 1px solid var(--group-line); background: var(--surface);
  display: flex; align-items: center; justify-content: center; color: var(--on-accent);
}
.pick-dot.is-on { background: var(--accent); border-color: var(--accent); }

.select-bar {
  flex: none; display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); padding: var(--sp-3) var(--sp-4);
  padding-bottom: calc(var(--sp-3) + var(--safe-bottom));
  background: var(--bg);
}
.select-hint { font-size: var(--fs-13); color: var(--text-3); }
.nav-text.is-off { color: var(--text-3); }
.nav-text.is-danger { color: var(--danger); }

/* 待办确认栏。和引用栏同一个位置：有一件事等你点头，就压在输入框上面。
   不做成弹窗 —— 聊天里说「我想」的频率很高，一句一个弹窗没法聊天 */
.todo-bar {
  flex: none; display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3) 0;
}
.todo-ask { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--sp-2); }
.todo-ask b {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-13); font-weight: 500;
}
.todo-tag {
  flex: none; font-size: var(--fs-11); color: var(--text-3);
  background: var(--surface-2); border-radius: var(--r-xs); padding: 1px var(--sp-2);
}
.todo-bar .nav-text { flex: none; }
.todo-acts { display: flex; align-items: center; gap: var(--sp-2); }
/* 账户下面那一排「存入 / 取出」。贴着它自己那一行，不另起一段 */
.acc-money {
  display: flex; gap: var(--sp-2);
  padding: 0 var(--sp-4) var(--sp-3);
}
/* 支出分类那一条占比。细细一道，看的是长短不是数字 */
.sp-bar {
  display: block; height: 3px; border-radius: var(--r-full);
  background: var(--surface-3); margin: var(--sp-1) 0;
}
.sp-bar > span { display: block; height: 100%; border-radius: var(--r-full); background: var(--accent); }
/* 通话里那句「为什么没有这个角色的声音」。和字幕同一列，低一档 */
/* 两张脸：原本的与现在在用的。并排摆，一眼看得出换过 */
.face-pair { display: flex; gap: var(--sp-5); justify-content: center; }
.face-one { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); }
.face-one span { font-size: var(--fs-11); color: var(--text-3); }
/* 主页上那张小的，贴在大头像旁边，点一下换回去 */

.call-why {
  flex: none; text-align: center; font-size: var(--fs-11);
  color: var(--text-3); padding: 0 var(--sp-4) var(--sp-2);
}
.todo-tick { display: flex; color: var(--text-3); padding: var(--sp-1); }

/* 引用栏：正要引着谁说话，压在输入框上面 */
.quote-bar {
  flex: none; display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3) 0;
}
.quote-bar .quote-ref { flex: 1; min-width: 0; }
.quote-bar > button { flex: none; color: var(--text-3); display: flex; padding: var(--sp-1); }

/* 回到最新。往上翻着看旧消息时才出现，压在输入框正上方 ——
   那是拇指本来就在的位置，不必伸到别处去。 */
.to-bottom {
  position: absolute; right: var(--sp-4); bottom: var(--sp-3);
  width: 38px; height: 38px; border-radius: var(--r-full);
  display: flex; align-items: center; justify-content: center;
  background: var(--surface); color: var(--text-2);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-2);
  animation: fade-in var(--dur-fast) var(--ease);
  z-index: 3;
}

/* 输入区与消息区同底，不要分隔线也不要换颜色 */
.composer-bar {
  flex: none; display: flex; gap: var(--sp-2); align-items: flex-end;
  padding: var(--composer-pad) var(--sp-3);
  padding-bottom: calc(var(--composer-pad) + var(--safe-bottom));
  background: var(--bg);
}
.composer-side {
  position: relative;
  width: var(--composer-h); height: var(--composer-h); flex: none; border-radius: var(--r-full);
  display: flex; align-items: center; justify-content: center; color: var(--text-2);
}
/* 输入栏这几个是**最常按**的，命中区多给一点。
   往上扩得比往下多：拇指从下方够过来，落点总是偏下 */
.composer-side::after { content: ''; position: absolute; inset: -8px -3px -4px; }
.composer-input {
  flex: 1; max-height: 110px; min-height: var(--composer-h); border-radius: var(--r-lg);
  padding: var(--sp-2) var(--sp-3); resize: none;
  background: var(--surface-2); border: 1px solid var(--border);
}
.composer-panel {
  flex: none; background: var(--bg); padding: var(--sp-3) var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--border);
}
.panel-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-4) var(--sp-2); }
.panel-item {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  font-size: var(--fs-11); color: var(--text-2);
}
.panel-icon {
  width: 52px; height: 52px; border-radius: var(--r-md);
  background: var(--surface-2); display: flex; align-items: center; justify-content: center;
  color: var(--text);
}
.panel-empty { font-size: var(--fs-12); color: var(--text-3); line-height: 1.7; padding: var(--sp-4) 0; }

/* 兼容朋友圈评论区里那个内联输入 */
.composer { display: flex; gap: var(--sp-2); align-items: flex-end; }
.composer-inline { padding: var(--sp-3) 0 0; }
.composer textarea {
  flex: 1; max-height: 110px; min-height: 38px; border-radius: var(--r-lg);
  padding: var(--sp-2) var(--sp-3); resize: none;
}
.send-btn {
  position: relative;
  width: var(--composer-h); height: var(--composer-h); flex: none; border-radius: var(--r-full);
  background: var(--accent); color: var(--on-accent);
  display: flex; align-items: center; justify-content: center;
}
.send-btn::after { content: ''; position: absolute; inset: -8px -3px -4px; }
.send-btn:disabled { opacity: .35; }
.send-btn.is-stop { background: var(--surface-3); color: var(--text); }
/* 让对方回复：不要底色，只留图标 */
.send-btn.is-ghost { background: none; color: var(--text); }

/* 主页（Instagram 式，见 ARCHITECTURE 4.149） */
.ig-head { display: flex; align-items: center; gap: var(--sp-5); padding: var(--sp-4) var(--sp-4) var(--sp-3); }
.ig-head .avatar { flex: none; }
.ig-face { position: relative; flex: none; }
/* 「原本的」那一张贴在头像右下角。从前它在按钮那一行里，把整行撑开 */
.ig-face-base { position: absolute; right: -2px; bottom: -2px; border-radius: var(--r-full); }
.ig-face-base .avatar { box-shadow: 0 0 0 2px var(--bg); }
.ig-stats { flex: 1; display: flex; justify-content: space-around; min-width: 0; }
.ig-stat { text-align: center; padding: var(--sp-1) var(--sp-2); }
.ig-stat b { display: block; font-size: var(--fs-17); font-weight: var(--fw-semi); }
.ig-stat span { font-size: var(--fs-12); color: var(--text-3); }
.ig-bio { padding: 0 var(--sp-4); }
.ig-name { font-size: var(--fs-15); font-weight: var(--fw-semi); }
.ig-sign { font-size: var(--fs-13); color: var(--text-2); margin-top: 2px; white-space: pre-wrap; word-break: break-word; }
.ig-acts { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4) var(--sp-2); }
.ig-acts > .btn { flex: 1; }
.ig-hl { display: flex; gap: var(--sp-3); padding: var(--sp-2) var(--sp-4) var(--sp-3); overflow-x: auto; }
.ig-hl-item { flex: none; width: 64px; display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); }
.ig-hl-ring { width: 60px; height: 60px; border-radius: var(--r-full); padding: 2px; border: 1px solid var(--border-2); }
.ig-hl-ring.is-add { border-style: dashed; }
.ig-hl-img {
  width: 100%; height: 100%; border-radius: var(--r-full);
  background: var(--surface-3); background-size: cover; background-position: center;
  display: flex; align-items: center; justify-content: center; color: var(--text-3);
}
.ig-hl-item span { font-size: var(--fs-11); color: var(--text-2); max-width: 64px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ig-hl-view {
  aspect-ratio: 1; border-radius: var(--r-md); background-color: var(--surface-3);
  background-size: contain; background-repeat: no-repeat; background-position: center;
}
.ig-tabs { display: flex; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.ig-tab { flex: 1; display: flex; justify-content: center; padding: var(--sp-3) 0; color: var(--text-3); border-bottom: 1px solid transparent; margin-bottom: -1px; }
.ig-tab.is-on { color: var(--text); border-bottom-color: var(--text); }
.ig-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px; }
.ig-cell { position: relative; aspect-ratio: 1; background: var(--surface-3); background-size: cover; background-position: center; }
.ig-cell-multi {
  position: absolute; top: var(--sp-2); right: var(--sp-2);
  width: 22px; height: 22px; border-radius: var(--r-full);
  background: var(--scrim); color: var(--on-scrim);
  display: flex; align-items: center; justify-content: center;
}

/* 动态详情 */
.ig-author { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); }
.ig-author-name { font-size: var(--fs-14); font-weight: var(--fw-medium); }
.ig-author-time { font-size: var(--fs-11); color: var(--text-3); }
.ig-pager { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; aspect-ratio: 1; background: var(--surface-3); }
.ig-slide { flex: none; width: 100%; height: 100%; scroll-snap-align: start; background-size: contain; background-repeat: no-repeat; background-position: center; }
.ig-dots { display: flex; justify-content: center; gap: 5px; padding: var(--sp-2) 0 0; }
.ig-dot { width: 6px; height: 6px; border-radius: var(--r-full); background: var(--border-2); }
.ig-dot.is-on { background: var(--text); }
.ig-foot { display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-3) var(--sp-4) var(--sp-2); }
.ig-foot .mo-act { font-size: var(--fs-13); }
.ig-text { padding: 0 var(--sp-4) var(--sp-3); font-size: var(--fs-14); line-height: 1.6; white-space: pre-wrap; word-break: break-word; }
.ig-comments { padding: 0 var(--sp-4) var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); }

/* 朋友圈 */
.moments { padding-bottom: var(--sp-6); }
.mo-header {
  position: relative; height: 210px; margin-bottom: 34px;
  background: var(--surface-3); background-size: cover; background-position: center;
}
.mo-header-acts {
  position: absolute; top: var(--sp-3); right: var(--sp-3);
  display: flex; gap: var(--sp-2);
}
.mo-header-btn {
  width: 34px; height: 34px; border-radius: var(--r-full);
  background: var(--scrim); color: var(--on-scrim);
  display: flex; align-items: center; justify-content: center;
}
.mo-header-btn svg { color: var(--on-scrim); }
.mo-header-me {
  position: absolute; right: var(--sp-4); bottom: -28px;
  display: flex; align-items: flex-end; gap: var(--sp-3);
}
.mo-header-name {
  font-size: var(--fs-15); font-weight: var(--fw-semi); color: var(--on-scrim);
  text-shadow: 0 1px 6px rgba(0, 0, 0, .55); padding-bottom: 34px;
}
.mo-header-me .avatar { box-shadow: 0 0 0 3px var(--bg); }
.mo-card {
  display: flex; gap: var(--sp-3); padding: var(--sp-4);
  border-bottom: 1px solid var(--border); background: var(--surface);
}
.mo-main { flex: 1; min-width: 0; }
.mo-name { font-size: var(--fs-13); font-weight: var(--fw-medium); color: var(--accent); }
.mo-text { font-size: var(--fs-14); line-height: 1.6; margin-top: 3px; white-space: pre-wrap; word-break: break-word; }
.mo-photos { display: grid; gap: 3px; margin-top: var(--sp-2); grid-template-columns: repeat(3, 1fr); }
.mo-photos.n1 { grid-template-columns: 1fr; max-width: 62%; }
.mo-photos.n2, .mo-photos.n4 { grid-template-columns: repeat(2, 1fr); max-width: 76%; }
.mo-photo { aspect-ratio: 1; background: var(--surface-3); background-size: cover; background-position: center; border-radius: var(--r-xs); }
.mo-foot { display: flex; align-items: center; justify-content: space-between; margin-top: var(--sp-2); }
.mo-time { font-size: var(--fs-11); color: var(--text-3); }
.mo-actions { display: flex; gap: var(--sp-3); }
.mo-act { display: flex; align-items: center; gap: 4px; font-size: var(--fs-11); color: var(--text-3); }
.mo-act.is-on { color: var(--text); }
.mo-comments { margin-top: var(--sp-2); padding: var(--sp-2); background: var(--surface-2); border-radius: var(--r-sm); }
.mo-comment { font-size: var(--fs-12); line-height: 1.7; word-break: break-word; }
.mo-comment b { color: var(--accent); font-weight: var(--fw-medium); }
.mo-comment-list { display: flex; flex-direction: column; gap: 2px; }
.mo-upload { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin: var(--sp-3) 0; }
.mo-thumb { position: relative; width: 66px; }
.mo-thumb .mo-photo { border-radius: var(--r-sm); }
.mo-thumb-x {
  position: absolute; top: -5px; right: -5px; width: 19px; height: 19px;
  border-radius: var(--r-full); background: var(--text); color: var(--bg);
  display: flex; align-items: center; justify-content: center;
}
.mo-add {
  width: 66px; aspect-ratio: 1; border: 1px dashed var(--border-2);
  border-radius: var(--r-sm); color: var(--text-3);
  display: flex; align-items: center; justify-content: center;
}

/* 壁纸与图标自定义 */
.wp-row {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--border);
}
.wp-row:last-child { border-bottom: none; }
.wp-preview {
  flex: none; width: 52px; height: 92px; border-radius: var(--r-sm);
  background: var(--surface-2); background-size: cover; background-position: center;
  display: flex; align-items: center; justify-content: center; color: var(--text-3);
  border: 1px solid var(--border);
}
.wp-body { flex: 1; min-width: 0; }
.wp-acts { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }

.icon-chip {
  width: 30px; height: 30px; border-radius: 24%; background: var(--tile-bg);
  box-shadow: var(--tile-shadow);
  display: flex; align-items: center; justify-content: center;
}
.color-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.swatch {
  width: 36px; height: 36px; border-radius: var(--r-full);
  border: 1px solid var(--border-2); box-shadow: inset 0 0 0 2px var(--surface);
  position: relative;
}
.swatch.is-active { box-shadow: inset 0 0 0 2px var(--surface), 0 0 0 2px var(--text); }
.swatch-custom {
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--text-2); cursor: pointer;
}
.swatch-custom input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.app-tile-mini { width: 30px; height: 30px; max-width: none; border-radius: 24%; }
.app-tile-mini svg { width: 62%; height: 62%; }
.mono { font-family: var(--font-mono); font-size: var(--fs-11); }
.icon-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
  gap: var(--sp-2);
}
.icon-pick {
  aspect-ratio: 1; border-radius: var(--r-sm); background: var(--surface-2);
  color: var(--text-2); display: flex; align-items: center; justify-content: center;
  border: 1px solid transparent;
}
.icon-pick.is-active { background: var(--accent-weak); color: var(--text); border-color: var(--text); }
.sheet-acts { display: flex; gap: var(--sp-2); margin-top: var(--sp-4); }
.sheet-acts .btn { flex: 1; }

/* 服务配置 */
.svc-dot {
  width: 8px; height: 8px; border-radius: var(--r-full);
  background: var(--border-2);
}
.svc-dot.is-main { background: var(--text); }
.svc-dot.is-spare { box-shadow: inset 0 0 0 1.5px var(--text); background: transparent; }
.svc-tag {
  font-size: var(--fs-11); color: var(--text-2);
  border: 1px solid var(--border-2); border-radius: var(--r-full);
  padding: 1px var(--sp-2);
}
.btn-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* 滑杆加数字框。生成器上每一项都是这一对，所以定义在这儿，各处引用 */
.slider-row { display: flex; align-items: center; gap: var(--sp-3); }
.slider-row input[type="range"] { flex: 1; min-width: 0; }
.slider-num {
  flex: none; width: 58px; text-align: right;
  background: var(--surface-2); border: 0; border-radius: var(--r-sm);
  padding: 5px var(--sp-2); font-size: var(--fs-13); color: var(--text);
  font-variant-numeric: tabular-nums;
}
.slider-unit { flex: none; font-size: var(--fs-12); color: var(--text-3); min-width: 20px; }
/* 没设过的那一档：滑杆压淡一点，一眼看出它现在不生效 */
.slider-row.is-unset input[type="range"] { opacity: .45; }
.slider-row.is-unset .slider-num { color: var(--text-3); }

/* 取色。色块、色值、以及那个「不改」 */
.color-row { display: flex; align-items: center; gap: var(--sp-3); }
.color-swatch {
  flex: none; width: 40px; height: 28px; padding: 0;
  border: 1px solid var(--border-2); border-radius: var(--r-sm); background: none;
}
.color-hex { flex: 1; min-width: 0; font-size: var(--fs-12); color: var(--text-2); }
.color-clear {
  flex: none; border: 1px solid var(--border-2); border-radius: var(--r-sm);
  background: none; color: var(--text-2); font-size: var(--fs-12); padding: 4px var(--sp-2);
}

/* 生成器：左边一条常驻竖栏，右边上半预览、下半旋钮。
   从上到下每一层都要把高度钉死 —— `.page-body` 自己不是 flex 容器，
   中间断一层，里面那个 .scroll 就撑成内容那么高，永远滚不动。 */
.gen { height: 100%; display: flex; min-height: 0; }

.gen-rail {
  flex: none; width: 62px; min-height: 0;
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--sp-2) 4px; border-right: 1px solid var(--border);
  background: var(--surface-2);
}
.gen-rail-btn {
  position: relative; flex: none;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: var(--sp-2) 2px; border: 0; border-radius: var(--r-sm);
  background: none; color: var(--text-2); cursor: pointer;
}
.gen-rail-btn.is-on { background: var(--surface); color: var(--text); }
.gen-rail-label { font-size: var(--fs-11); line-height: 1.1; text-align: center; }
.gen-rail-dot {
  position: absolute; top: 5px; right: 6px;
  width: 6px; height: 6px; border-radius: var(--r-full); background: var(--accent);
}
.gen-rail-sep { flex: none; height: 1px; background: var(--border); margin: var(--sp-2) 6px; }

.gen-main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.gen-stage-wrap {
  flex: none; height: 46%; min-height: 180px;
  border-bottom: 1px solid var(--border); background: var(--surface-2);
}
/* 旋钮收起来时预览占满整块 */
.gen-stage-wrap.is-full { flex: 1; height: auto; border-bottom: 0; }
.gen-stage { height: 100%; width: 100%; overflow: hidden; position: relative; }

.gen-panel { flex: 1; min-height: 0; padding-bottom: var(--safe-bottom); }
.gen-panel-head {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-3) var(--sp-4);
  background: var(--bg); border-bottom: 1px solid var(--border);
}
.gen-panel-title { font-size: var(--fs-14); font-weight: var(--fw-semi); color: var(--text); }
/* 「卡片」那一格里先选改哪一种。动过的那几种右上角一个小点，和竖栏上一样 */
.gen-card-pick { display: flex; flex-wrap: wrap; gap: var(--sp-2); padding: 0 var(--sp-4) var(--sp-3); }
.gen-card-pick .chip { position: relative; }
.gen-card-pick .chip.has-dot::after {
  content: ''; position: absolute; top: 2px; right: 2px; width: 6px; height: 6px;
  border-radius: var(--r-full); background: var(--accent);
}
/* 这一组改的是哪个类名。手写 CSS 的人照着抄 */
.gen-panel-desc {
  padding: 0 var(--sp-4) var(--sp-2); font-size: var(--fs-12); color: var(--text-3);
  font-family: var(--font-mono); user-select: text; -webkit-user-select: text;
}
.gen-panel-close {
  border: 0; background: none; color: var(--text-2); font-size: var(--fs-13);
  padding: 2px var(--sp-1);
}
.gen-group { padding: 0 var(--sp-4) var(--sp-4); }
.gen-out {
  margin: var(--sp-4); padding: var(--sp-3); border-radius: var(--r-md);
  background: var(--surface-2); color: var(--text-2);
  font-family: var(--font-mono); font-size: var(--fs-11); line-height: 1.6;
  white-space: pre-wrap; word-break: break-all; max-height: 260px; overflow: auto;
}
.gen-pic { display: flex; align-items: center; gap: var(--sp-3); }
.gen-pic-box {
  flex: none; width: 46px; height: 46px; border-radius: var(--r-sm);
  border: 1px solid var(--border-2); background-size: contain;
  background-position: center; background-repeat: no-repeat;
}
.btn-row .btn { flex: 1; min-width: 0; }
/* 一排可选的小标签（类型、型号、尺寸）：按内容定宽，放不下就换行，不挤成半截字 */
.btn-row.is-chips .btn { flex: none; }

/* 头像框：编辑页上那一小块试戴。和会话里一样，框挂在头像那个盒子外面，
   所以这里的结构也是「一个 36 见方的盒子 + 一张绝对定位的图」。 */
.frame-try { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-2) 0; }
.frame-try-face { position: relative; width: 36px; height: 36px; flex: none; }
.frame-try-ring {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  object-fit: contain; pointer-events: none;
}
.frame-try-note { font-size: var(--fs-11); color: var(--text-3); }

/* 美化页的样板间：上面几条消息，下面一条底栏。底栏也画进来，否则
   「底栏按钮大小」「底栏内边距」两项令牌怎么调都看不出动静。 */
.skin-sample-wrap { display: flex; flex-direction: column; border-bottom: 1px solid var(--border); }
/* 样板间不在屏幕底部，Home Indicator 那一块留白挪过来只会多出一条空档 */
.skin-sample-wrap .composer-bar { padding-bottom: var(--composer-pad); }

.picker-state {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: var(--sp-5); color: var(--text-3); font-size: var(--fs-12);
}
.model-list {
  display: flex; flex-direction: column;
  margin-top: var(--sp-3); border-top: 1px solid var(--border);
}
.model-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-1); font-size: var(--fs-13);
  border-bottom: 1px solid var(--border); text-align: left;
  font-family: var(--font-mono);
}
.model-row.is-active { font-weight: var(--fw-semi); }

/* 消息列表与联系人：分组胶囊，细黑边 */
.msg-list { padding: var(--sp-3) var(--sp-4) var(--sp-6); }

.cap-wrap { margin-bottom: var(--sp-4); }
.cap-title {
  font-size: var(--fs-12); color: var(--text-3);
  padding: 0 var(--sp-3) var(--sp-2);
}
.capsule {
  border-radius: var(--r-xl);
  overflow: hidden;
  background: var(--group-fill);
}
:root[data-wallpaper="on"] .capsule {
  background: var(--wg-solid);
}
:root[data-wallpaper="on"][data-glass="on"] .capsule { backdrop-filter: blur(var(--glass-blur)); -webkit-backdrop-filter: blur(var(--glass-blur)); }

.msg-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); min-height: 62px; cursor: pointer;
  position: relative;
}
.msg-row + .msg-row::before {
  content: ''; position: absolute; left: calc(var(--sp-4) + 46px + var(--sp-3));
  right: 0; top: 0; height: 1px; background: var(--group-line);
}
.msg-main { flex: 1; min-width: 0; }
.msg-line { display: flex; align-items: baseline; gap: var(--sp-2); }
.msg-line + .msg-line { margin-top: 3px; }
.msg-name { flex: 1; min-width: 0; font-size: var(--fs-15); font-weight: var(--fw-medium); }
.msg-time { flex: none; font-size: var(--fs-11); color: var(--text-3); }
.msg-preview { flex: 1; min-width: 0; font-size: var(--fs-12); color: var(--text-3); }

.search-bar {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4); margin-bottom: var(--sp-4);
  background: var(--group-fill); border-radius: var(--r-full);
  color: var(--text-3);
}
.search-bar input {
  flex: 1; min-width: 0; border: none; background: none; padding: 0;
  font-size: var(--fs-14); color: var(--text);
}
.search-bar input:focus { border: none; }
.search-bar button { color: var(--text-3); display: flex; }

/* 表情包 */
.stk-img {
  width: 72px; height: 72px; object-fit: contain;
  border-radius: var(--r-sm); background: var(--group-fill);
}
.stk-miss {
  width: 72px; height: 72px; border-radius: var(--r-sm);
  background: var(--group-fill); color: var(--text-3);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-12);
}

.stk-panel { display: flex; flex-direction: column; gap: var(--sp-2); max-height: 260px; }
.stk-tabs { display: flex; gap: var(--sp-2); overflow-x: auto; padding-bottom: var(--sp-1); }
.stk-tabs::-webkit-scrollbar { display: none; }
.stk-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
  gap: var(--sp-2); max-height: 214px; align-content: start;
}
.stk-manage { max-height: none; padding: 0 var(--sp-1); }
.stk-cell { display: flex; align-items: center; justify-content: center; }
.stk-cell .stk-img, .stk-cell .stk-miss { width: 100%; height: auto; aspect-ratio: 1; }
/* 整理分组时选中的那几个 */
.stk-cell.is-picked { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--r-xs); }

.stk-suggest {
  flex: none; display: flex; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); overflow-x: auto;
}
.stk-suggest::-webkit-scrollbar { display: none; }
.stk-sug { flex: none; }

.bubble-sticker { padding: 0; background: none; }
.bubble-sticker .stk-img {
  width: 112px; height: 112px; background: none; border-radius: var(--r-md);
}

.stk-preview {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: var(--sp-2); margin: var(--sp-3) 0;
}
.stk-prev-item { display: flex; flex-direction: column; align-items: center; gap: 3px; min-width: 0; }
.stk-prev-item .stk-img, .stk-prev-item .stk-miss { width: 100%; height: auto; aspect-ratio: 1; }
.stk-prev-item span { font-size: var(--fs-11); color: var(--text-3); max-width: 100%; }

.stk-group-head { display: flex; align-items: center; justify-content: space-between; }
.stk-group-head button { color: var(--text-3); display: flex; padding: var(--sp-1); }
.stk-group-acts { display: flex; align-items: center; gap: var(--sp-1); }
.stk-edit-preview { display: flex; justify-content: center; padding: var(--sp-3) 0 var(--sp-4); }
.stk-edit-preview .stk-img, .stk-edit-preview .stk-miss { width: 96px; height: 96px; }

.icon-upload { display: flex; align-items: center; gap: var(--sp-4); }
.app-tile-preview {
  width: 64px; height: 64px; flex: none; border-radius: 24%;
  background: var(--tile-bg); box-shadow: var(--tile-shadow);
  display: flex; align-items: center; justify-content: center;
}
.icon-upload-acts { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* 图片与语音消息 */
.media-pending, .media-failed {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-12); color: var(--text-3);
}
.media-failed { flex-direction: column; align-items: flex-start; gap: 3px; }
.media-prompt { font-size: var(--fs-13); color: var(--text-2); }
.media-note { font-size: var(--fs-11); color: var(--text-3); }

.bubble-image { padding: 0; background: none; max-width: 200px; }
.bubble-image img {
  width: 100%; border-radius: var(--r-md); display: block;
  box-shadow: var(--shadow-1);
}

/* 一次发好几张图：摞成一叠。背后那两张**要漏出图**，所以得摊得开一点 ——
   压得太紧就只剩一道彩色的边，看不出下面还有照片。
   点这一叠换最上面那张，不必展开就能挨个看。 */
.img-stack { display: flex; align-items: center; gap: var(--sp-3); }
.msg.is-mine .img-stack { flex-direction: row-reverse; }

.stack-cards {
  position: relative; flex: none; max-width: 150px;
  cursor: pointer;
  /* 背后那两张往外探，这边留出它们的地方，免得被气泡列裁掉 */
  margin: 4px 26px 20px 0;
}
.msg.is-mine .stack-cards { margin: 4px 0 20px 26px; }

.stack-card {
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--border);
}
.stack-card:not(.is-back) {
  position: relative; z-index: 2; overflow: hidden;
  box-shadow: 0 6px 16px rgba(0, 0, 0, .10), 0 1px 2px rgba(0, 0, 0, .06);
  transition: transform var(--dur-fast) var(--ease);
}
.stack-cards:active .stack-card:not(.is-back) { transform: scale(.97); }
.stack-card img { width: 100%; height: auto; display: block; }

/* --i 是从上往下数第几张。绕底边转一点，像一摞随手放下的照片。
   偏移给足：下面那张要露出小半个角，不是一条边 */
.stack-card.is-back {
  position: absolute; inset: 0; z-index: 1;
  overflow: hidden;
  transform-origin: 50% 100%;
  transform: translate(calc(var(--i) * 13px), calc(var(--i) * 9px))
             rotate(calc(var(--i) * 3.5deg));
  box-shadow: 0 3px 9px rgba(0, 0, 0, .07);
}
.msg.is-mine .stack-card.is-back {
  transform: translate(calc(var(--i) * -13px), calc(var(--i) * 9px))
             rotate(calc(var(--i) * -3.5deg));
}
.stack-card.is-back img { height: 100%; object-fit: cover; }

/* 第几张。压在右下角，告诉你这一叠还能点 */
.stack-n {
  position: absolute; z-index: 3; right: 7px; bottom: 7px;
  padding: 2px 7px; border-radius: var(--r-full);
  background: rgba(0, 0, 0, .46); color: #fff;
  font-size: var(--fs-11); line-height: 1.5;
}
:root[data-glass="on"] .stack-n { backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }

.stack-more, .stack-fold {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  padding: 6px var(--sp-4);
  border: 0; border-radius: var(--r-full);
  background: var(--surface-2); color: var(--text-2);
  font-size: var(--fs-12); cursor: pointer;
}
.stack-fold {
  align-self: flex-start; margin-top: 2px;
  padding: 6px var(--sp-3);
  color: var(--text-3);
}
.msg.is-mine .stack-fold { align-self: flex-end; }
.stack-fold svg { color: var(--text-3); }

.voice-wrap { display: flex; flex-direction: column; gap: 4px; align-items: inherit; }
.bubble-voice {
  display: flex; align-items: center; gap: var(--sp-2);
  min-width: 108px; text-align: left;
}
.voice-bars { display: flex; align-items: flex-end; gap: 2px; height: 14px; }
.voice-bars i {
  width: 2px; background: currentColor; border-radius: 1px; opacity: .45;
  display: block;
}
.voice-bars i.is-on { opacity: 1; animation: voice-pulse .7s ease-in-out infinite alternate; }
.voice-bars i.is-on:nth-child(2) { animation-delay: .12s; }
.voice-bars i.is-on:nth-child(3) { animation-delay: .24s; }
.voice-bars i.is-on:nth-child(4) { animation-delay: .36s; }
@keyframes voice-pulse { from { transform: scaleY(.5); } to { transform: scaleY(1.5); } }
.voice-len { font-size: var(--fs-11); opacity: .7; }
.voice-acts { display: flex; gap: var(--sp-3); padding: 0 var(--sp-1); }
.voice-acts button { color: var(--text-3); display: flex; }
.voice-text {
  font-size: var(--fs-12); color: var(--text-2); line-height: 1.6;
  background: var(--group-fill); border-radius: var(--r-sm);
  padding: var(--sp-2) var(--sp-3); max-width: 100%;
}

.mo-genning, .mo-genfail {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-12); color: var(--text-3); margin-top: var(--sp-2);
}

/* 批量换图标 */
.batch-acts { display: flex; gap: var(--sp-2); }
.batch-list {
  display: flex; flex-direction: column;
  border-radius: var(--r-md); overflow: hidden; background: var(--group-fill);
  margin-bottom: var(--sp-3);
}
.batch-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); text-align: left;
  border-bottom: 1px solid var(--group-line); color: var(--text-3);
}
.batch-row:last-child { border-bottom: none; }
.batch-row.is-on { color: var(--text); }
.batch-num {
  width: 20px; height: 20px; flex: none; border-radius: var(--r-full);
  font-size: var(--fs-11); display: flex; align-items: center; justify-content: center;
  background: var(--surface-3); color: var(--text-2);
}
.batch-row.is-on .batch-num { background: var(--accent); color: var(--on-accent); }
.batch-name { flex: 1; min-width: 0; font-size: var(--fs-14); }

/* 时间感知 */
.zone-now {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-13); color: var(--text-3); font-variant-numeric: tabular-nums;
}
.dt-input {
  width: 100%; box-sizing: border-box;
  padding: var(--sp-3); border: none; border-radius: var(--r-md);
  background: var(--surface-2); color: var(--text);
  font: inherit; font-size: var(--fs-15);
}
.time-preview {
  padding: var(--sp-5) var(--sp-4); border-radius: var(--r-lg);
  background: var(--group-fill); text-align: center;
}
.time-big {
  font-size: var(--fs-56); line-height: 1.1; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.time-sub { margin-top: var(--sp-2); font-size: var(--fs-13); color: var(--text-2); }
.time-note { margin-top: 3px; font-size: var(--fs-11); color: var(--text-3); }

/* 用户发的图片与语音 */
/* 用文字写的一张图（MediaBubble 的 photo-text）：顶上一行小标「图片」，下面是画面描述 */
.photo-text { max-width: 240px; }
.photo-text-head { display: flex; align-items: center; gap: 4px; font-size: var(--fs-11); opacity: .6; margin-bottom: 2px; }
.photo-text-body { white-space: pre-wrap; word-break: break-word; }
.media-wrap { display: flex; flex-direction: column; gap: 4px; align-items: inherit; }

/* 视频消息。海报 + 一个播放标记，点一下才播（长按是消息菜单，见第 12 条） */
.bubble-clip {
  position: relative; padding: 0; background: none; max-width: 200px;
  border-radius: var(--r-md); overflow: hidden; display: block;
}
.bubble-clip img, .bubble-clip video { display: block; width: 100%; height: auto; }
.clip-blank {
  width: 160px; height: 110px; background: var(--surface-3);
  display: flex; align-items: center; justify-content: center; color: var(--text-3);
}
.clip-play {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 40px; height: 40px; border-radius: var(--r-full);
  background: var(--scrim-strong); color: var(--on-scrim);
  display: flex; align-items: center; justify-content: center;
}
.clip-time {
  position: absolute; right: var(--sp-2); bottom: var(--sp-2);
  padding: 1px var(--sp-2); border-radius: var(--r-xs);
  background: var(--scrim-strong); color: var(--on-scrim); font-size: var(--fs-11);
}
.voice-tone { margin-top: 4px; color: var(--text-3); }
.voice-trans { margin-top: var(--sp-1); padding-top: var(--sp-1); border-top: 1px solid var(--border); color: var(--text-2); }

/* 录音条。和多选栏共用 .select-bar 的位置与留白 */
.rec-live {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-15); color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.rec-dot {
  width: 8px; height: 8px; border-radius: var(--r-full);
  background: var(--accent); animation: rec-blink 1s steps(1) infinite;
}
@keyframes rec-blink { 50% { opacity: .2; } }

/* 角色写了一个表情名，但库里没有对得上的。显示它想发的是哪个，
   总比悄悄吞掉一条消息强。 */
.stk-gone {
  display: inline-block; padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-md); background: var(--group-fill);
  font-size: var(--fs-12); color: var(--text-3);
  display: inline-flex; flex-direction: column; align-items: flex-start; gap: 2px;
}
/* 认不出时点一下自己指认。不写这一句，人只会以为是坏了 */
.stk-gone-hint { font-size: var(--fs-11); color: var(--accent); }

/* 译文收在原文气泡里。点一下展开，不单独占一条消息，也不另加按钮 */
.bubble.has-trans { cursor: pointer; }
.bubble-trans {
  margin-top: 6px; padding-top: 6px;
  border-top: 1px solid var(--group-line);
  font-size: var(--fs-13); line-height: 1.55; opacity: .72;
}
.msg.is-mine .bubble-trans { border-top-color: var(--capsule-border); }

/* 搜索结果 */
/* 单色界面里不拿颜色做高亮：周围的预览是次级灰，命中处用正文色加重，
   对比够了，深色模式下也不用另写一套 */
.hit { background: none; color: var(--text); font-weight: var(--fw-medium); }
.hit-who { color: var(--text-3); }
.search-note {
  padding: var(--sp-4); text-align: center;
  font-size: var(--fs-12); color: var(--text-3);
}

/* 会话里往上翻一段 */
.conv-earlier {
  display: block; margin: 0 auto var(--sp-3); padding: var(--sp-2) var(--sp-4);
  border: none; border-radius: var(--r-full);
  background: var(--group-fill); color: var(--text-3); font-size: var(--fs-12);
}
.search-bar .search-hint { flex: 1; text-align: left; font-size: var(--fs-14); }
button.search-bar { width: 100%; border: none; }

/* 转账气泡。单色卡片，金额是主角，状态压在底下一行 */
.bubble-transfer {
  min-width: 168px; max-width: 232px; padding: var(--sp-3) var(--sp-4) 0;
  cursor: default;
}
.bubble-transfer .tr-top { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.bubble-transfer .tr-body { min-width: 0; }
.tr-amount { font-size: var(--fs-20); font-weight: var(--fw-medium); line-height: 1.2; }
.tr-note { font-size: var(--fs-12); opacity: .7; margin-top: 2px; }
.tr-foot {
  margin: var(--sp-3) calc(var(--sp-4) * -1) 0; padding: var(--sp-2) var(--sp-4);
  border-top: 1px solid var(--border); font-size: var(--fs-12); opacity: .62;
}
/* 自己这边的气泡是深底，分隔线要用亮的那一侧 */
.msg.is-mine .tr-foot { border-top-color: rgba(255, 255, 255, .22); }
.bubble-transfer.is-done { opacity: .58; }

/* 提示行。不是谁说的话，居中一行灰字 */
.conv-notice {
  text-align: center; padding: var(--sp-2) var(--sp-6);
  font-size: var(--fs-12); color: var(--text-3); line-height: 1.6;
}

/* 录音条上的「改为输入」 */
.rec-write {
  margin-left: var(--sp-2); padding: 2px var(--sp-2);
  border: none; border-radius: var(--r-full);
  background: var(--group-fill); color: var(--text-3); font-size: var(--fs-12);
}

/* 位置气泡。虚拟定位，没有真地图，所以也不画一块假的地图底纹 */
.bubble-location {
  display: flex; align-items: center; gap: var(--sp-3);
  min-width: 168px; max-width: 244px;
}
.bubble-location .loc-body { min-width: 0; flex: 1; }
.loc-name { font-size: var(--fs-15); font-weight: var(--fw-medium); }
.loc-addr { font-size: var(--fs-12); opacity: .66; margin-top: 2px; }
.bubble-location .loc-map {
  flex: none; width: 44px; height: 44px; border-radius: var(--r-sm);
  display: flex; align-items: center; justify-content: center;
  background: var(--group-fill); opacity: .8;
}
.msg.is-mine .bubble-location .loc-map { background: rgba(255, 255, 255, .16); }

/* ---- 通话 ---- */
.call-layer {
  position: absolute; inset: 0; z-index: var(--z-call);
  display: flex; flex-direction: column;
  padding: calc(var(--safe-top) + var(--sp-8)) var(--sp-5)
           calc(var(--safe-bottom) + var(--sp-5));
  background: var(--bg);
}
.call-head { flex: none; display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); }
.call-name { font-size: var(--fs-20); font-weight: var(--fw-medium); margin-top: var(--sp-2); }
.call-status { font-size: var(--fs-13); color: var(--text-3); font-variant-numeric: tabular-nums; }
.call-error { font-size: var(--fs-12); color: var(--text-2); text-align: center; }

/* 字幕。一句一句往上走，自己说的靠右 */
.call-lines { flex: 1; min-height: 0; padding: var(--sp-6) 0; }
.call-line { margin-bottom: var(--sp-4); }
.call-line.is-mine { text-align: right; }
.call-who { display: block; font-size: var(--fs-12); color: var(--text-3); margin-bottom: 2px; }
.call-text { font-size: var(--fs-17); line-height: 1.65; }
/* 译文在原文下面另起一行。念出来的是原文，看得懂的是这一行 */
.call-trans { display: block; margin-top: 2px; font-size: var(--fs-14); line-height: 1.6; color: var(--text-2); }

.call-heard {
  flex: none; min-height: 22px; text-align: center;
  font-size: var(--fs-13); color: var(--text-3); margin-bottom: var(--sp-3);
}
.call-input {
  flex: none; display: flex; align-items: center; gap: var(--sp-2);
  background: var(--group-fill); border-radius: var(--r-full);
  padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-5);
}
.call-input input {
  flex: 1; min-width: 0; border: none; background: none; padding: 0;
  font-size: var(--fs-15); color: var(--text);
}
.call-input input:focus { border: none; }
.call-input button { flex: none; color: var(--text-2); display: flex; }
.call-input button:disabled { color: var(--text-3); opacity: .5; }

.call-bar {
  flex: none; display: flex; align-items: center; justify-content: center;
  gap: var(--sp-8);
}
.call-key {
  width: 58px; height: 58px; border-radius: var(--r-full); border: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--group-fill); color: var(--text-2);
}
/* 开着的挡位用实心填；挂断也要实心，所以靠大一号来区分主次 —— 单色界面里
   大小是比颜色更靠得住的主次信号 */
.call-key.is-on { background: var(--accent); color: var(--on-accent); }
.call-key.is-hang,
.call-key.is-take {
  width: 66px; height: 66px; background: var(--accent); color: var(--on-accent);
}
.call-foot {
  flex: none; text-align: center; margin-top: var(--sp-4);
  font-size: var(--fs-12); color: var(--text-3);
}

/* 通话记录气泡与全文 */
.bubble-call { display: flex; align-items: center; gap: var(--sp-2); }
.bubble-call.is-miss { opacity: .62; }
.log-line { margin-bottom: var(--sp-4); }
.log-who { display: block; font-size: var(--fs-12); color: var(--text-3); margin-bottom: 2px; }
.log-text { font-size: var(--fs-15); line-height: 1.65; }
.log-line { display: grid; grid-template-columns: 1fr auto; column-gap: var(--sp-2); align-items: start; }
.log-line .log-who { grid-column: 1 / -1; }
.log-body { display: block; min-width: 0; }
.log-trans { display: block; margin-top: 2px; font-size: var(--fs-13); line-height: 1.6; color: var(--text-2); }
.log-play {
  width: 28px; height: 28px; border-radius: var(--r-full); margin-top: 2px;
  background: var(--surface-2); color: var(--text-2);
  display: flex; align-items: center; justify-content: center;
}
/* 总结放最上面：打开记录先看到的是「这通电话说了什么」，不是第一句话 */
.log-sum {
  margin-bottom: var(--sp-5); padding: var(--sp-3);
  border-radius: var(--r-md); background: var(--surface-2);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.log-sum-text { font-size: var(--fs-14); line-height: 1.7; white-space: pre-wrap; }
.log-sum-empty { font-size: var(--fs-13); color: var(--text-3); }

/* 视频通话：对方铺满整屏，字幕压在上面，所以要一层压暗 */
.call-layer.is-video { color: var(--on-scrim); }
.call-back {
  position: absolute; inset: 0; background-size: cover; background-position: center;
}
.call-scrim {
  position: absolute; inset: 0;
  background: linear-gradient(180deg,
    var(--scrim-strong) 0%, var(--scrim) 26%, var(--scrim) 58%, var(--scrim-strong) 100%);
}
/* 铺满的画面和压暗层在最底下，内容全部抬到它们上面。
   这里要一个一个点名，不能写 `> *` —— 那会把小窗的绝对定位一起盖掉，
   它就从右上角掉回文档流里去了。 */
.call-head, .call-lines, .call-heard, .call-input, .call-bar, .call-foot {
  position: relative; z-index: 1;
}
.call-layer.is-video .call-name,
.call-layer.is-video .call-status,
.call-layer.is-video .call-foot,
.call-layer.is-video .call-error { color: var(--on-scrim); }
.call-layer.is-video .call-status,
.call-layer.is-video .call-foot { opacity: .8; }
.call-lines.on-dark .call-who { color: var(--on-scrim); opacity: .7; }
.call-lines.on-dark .call-text { color: var(--on-scrim); }
.call-lines.on-dark .call-trans { color: var(--on-scrim); opacity: .75; }
/* 压在画面上时全部半透明；开着的那个加一圈描边，挂断是唯一的实心，
   这样「实心=挂断」在视频里也成立 */
.call-layer.is-video .call-key { background: rgba(255, 255, 255, .2); color: var(--on-scrim); }
.call-layer.is-video .call-key.is-on {
  background: rgba(255, 255, 255, .34);
  box-shadow: inset 0 0 0 1.5px var(--scrim-fill);
}
.call-layer.is-video .call-key.is-hang,
.call-layer.is-video .call-key.is-take {
  background: var(--scrim-fill); color: var(--on-scrim-fill); box-shadow: none;
}
.call-layer.is-video .call-input { background: rgba(255, 255, 255, .22); }
.call-layer.is-video .call-input input { color: var(--on-scrim); }
.call-layer.is-video .call-input input::placeholder { color: rgba(255, 255, 255, .6); }
.call-layer.is-video .call-input button { color: var(--on-scrim); }

/* 我这边的小窗。右上角，摄像头画面按镜像显示 */
.call-self {
  position: absolute; top: calc(var(--safe-top) + var(--sp-4)); right: var(--sp-4);
  width: 96px; height: 128px; border-radius: var(--r-md); overflow: hidden;
  background: var(--surface-3);
  display: flex; align-items: center; justify-content: center;
}
.call-self video { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }

/* 角色卡上的视频画面预览 */
.call-scene-preview {
  margin-top: var(--sp-3); height: 140px; border-radius: var(--r-md);
  background-size: cover; background-position: center;
}

/* 礼物气泡。拆开之前只有封面那一行 */
.bubble-gift { min-width: 168px; max-width: 232px; padding: var(--sp-3) var(--sp-4) 0; }
.bubble-gift .tr-top { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.gift-cover { font-size: var(--fs-17); font-weight: var(--fw-medium); }
.gift-inner { font-size: var(--fs-12); opacity: .7; margin-top: 2px; }
.bubble-gift.is-done { opacity: .62; }
.face-preview {
  margin-top: var(--sp-3); height: 120px; width: 120px; border-radius: var(--r-md);
  background-size: cover; background-position: center;
}

/* 一起听的播放条。压在会话顶上，不占页面 —— 听歌是背景，聊天还是主角 */
.listen-bar {
  flex: none; display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--group-fill); border-radius: var(--r-md);
  margin: 0 var(--sp-4) var(--sp-2);
}
.listen-main { flex: 1; min-width: 0; }
.listen-title { font-size: var(--fs-13); font-weight: var(--fw-medium); }
.listen-sub { font-size: var(--fs-12); color: var(--text-3); margin-top: 1px; }
.listen-key {
  flex: none; width: 28px; height: 28px; border: none; border-radius: var(--r-full);
  display: flex; align-items: center; justify-content: center;
  background: var(--surface); color: var(--text-2);
}
.row-acts { display: inline-flex; gap: var(--sp-3); }

/* ---- 情侣空间 ---- */
.sp-row {
  display: flex; align-items: center; gap: var(--sp-3); width: 100%;
  padding: var(--sp-3) var(--sp-4); text-align: left;
  border-bottom: 1px solid var(--border);
}
.sp-row:last-child { border-bottom: none; }
.sp-row-body { flex: 1; min-width: 0; }
.sp-row-name { font-size: var(--fs-17); font-weight: var(--fw-medium); }
.sp-row-sub { font-size: var(--fs-12); color: var(--text-3); margin-top: 2px; }

.sp-head { padding: var(--sp-6) var(--sp-4) var(--sp-4); text-align: center; }
.sp-faces {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-4);
  color: var(--text-3);
}
.sp-days {
  display: block; margin: var(--sp-4) auto 0; padding: var(--sp-2) var(--sp-5);
  border-radius: var(--r-full); background: var(--group-fill); color: var(--text-2);
}
.sp-days b {
  display: block; font-size: var(--fs-24); font-weight: var(--fw-semi);
  color: var(--text); line-height: 1.2; font-variant-numeric: tabular-nums;
}
.sp-days span { font-size: var(--fs-12); }

.sp-thumb {
  width: 32px; height: 32px; border-radius: var(--r-xs);
  object-fit: cover; background: var(--surface-2);
}
.sp-letter {
  white-space: pre-wrap; line-height: 1.8; font-size: var(--fs-15);
  padding: var(--sp-4); border-radius: var(--r-md); background: var(--group-fill);
}

/* 约定与信的气泡。和转账、礼物同一套骨架 */
.bubble-pact, .bubble-letter {
  min-width: 168px; max-width: 232px; padding: var(--sp-3) var(--sp-4) 0;
}
.bubble-pact .tr-top, .bubble-letter .tr-top {
  display: flex; align-items: center; gap: var(--sp-3); min-width: 0;
}
.pact-title { font-size: var(--fs-15); font-weight: var(--fw-medium); line-height: 1.45; }
.letter-title { font-size: var(--fs-17); font-weight: var(--fw-medium); }
.bubble-pact.is-done { opacity: .62; }
.bubble-pact.is-done .pact-title { text-decoration: line-through; }

/* ---- 随机事件库的九宫格 ---- */
.ev-grid {
  display: grid; grid-template-columns: auto repeat(3, 1fr);
  gap: var(--sp-2); align-items: stretch; margin-bottom: var(--sp-4);
}
.ev-head {
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-12); color: var(--text-3); padding: var(--sp-1) 0;
}
.ev-head.is-row { justify-content: flex-end; padding-right: var(--sp-2); }
.ev-cell {
  padding: var(--sp-4) 0; border-radius: var(--r-md);
  background: var(--group-fill); color: var(--text);
}
.ev-cell b { font-size: var(--fs-20); font-weight: var(--fw-semi); font-variant-numeric: tabular-nums; }
.ev-cell.is-empty { color: var(--text-3); }
.ev-input { display: flex; flex-direction: column; gap: 2px; }
.ev-have { font-size: var(--fs-11); color: var(--text-3); text-align: center; }

/* 取消掉的事项：留在那儿，但一眼看得出不作数了 */
.list-item.is-dropped .li-title { text-decoration: line-through; color: var(--text-3); }

/* ---- 关系小件 ---- */
/* 头像是可点的：单击看心声，双击拍一拍。所以要禁掉系统的选中浮层 */
.msg-face { flex: none; cursor: pointer; }

.bubble-dice {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-width: 72px; padding: var(--sp-3) var(--sp-4);
}
.dice-num {
  font-size: var(--fs-24); font-weight: var(--fw-semi); line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.dice-face { font-size: var(--fs-11); opacity: .6; }

.inner-voice { margin-top: var(--sp-2); line-height: 1.6; }
.inner-quiet {
  font-size: var(--fs-12); color: var(--text-3); padding: 0 var(--sp-1);
}
.inner-card {
  font-size: var(--fs-13); color: var(--text-2);
  padding: var(--sp-3); border-radius: var(--r-md);
  background: var(--group-fill);
}
.msg.is-mine .inner-quiet, .msg.is-mine .inner-card { text-align: right; }

.pool-thumb {
  width: 32px; height: 32px; border-radius: var(--r-full);
  object-fit: cover; background: var(--surface-2);
}
.msg-star { flex: none; color: var(--accent); }

/* ---- 点外卖 ---- */
.bubble-meal { min-width: 168px; max-width: 244px; padding: var(--sp-3) var(--sp-4) 0; }
.bubble-meal .tr-top { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.meal-item { font-size: var(--fs-15); font-weight: var(--fw-medium); }
.meal-price { font-size: var(--fs-12); opacity: .7; margin-top: 2px; }
.bubble-meal.is-done { opacity: .6; }

/* 会话顶上那一行状态：自动回复开着，或者正在等她回 */
.pace-bar {
  flex: none; width: 100%; text-align: center;
  padding: var(--sp-2) var(--sp-4); margin-bottom: var(--sp-2);
  font-size: var(--fs-12); color: var(--text-3);
  background: var(--group-fill);
}

/* 请求记录。一段可折叠的原文，等宽字体按原样显示，不换行折断单词 */
.trace-block { margin-bottom: var(--sp-3); }
.trace-head {
  width: 100%; display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); color: var(--text-1);
  font-size: var(--fs-13); text-align: left; cursor: pointer;
}
.trace-head > span:first-child { flex: 1; min-width: 0; }
.trace-size { color: var(--text-3); font-size: var(--fs-11); }
.trace-body {
  margin: var(--sp-2) 0 0;
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface-2); border-radius: var(--r-md);
  color: var(--text-2); font-size: var(--fs-12); line-height: 1.6;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: pre-wrap; word-break: break-word;
  /* 这一段是拿来读和复制的，放开选中 */
  -webkit-user-select: text; user-select: text; -webkit-touch-callout: default;
  /* 不用 vh：视口单位只许出现在外壳那三层（CLAUDE.md 第 1 条） */
  max-height: 420px; overflow: auto;
}

/* 音乐：三个页签 + 底部那条 */
/* 撑满一屏，底部那条才贴得住（sticky 只在内容够长时才吸底） */
.mu-page { min-height: 100%; }
.mu-wait { display: flex; justify-content: center; padding: var(--sp-8) 0; }

.mu-sec {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-5) var(--sp-4) var(--sp-2);
  font-size: var(--fs-12); color: var(--text-3);
}

.mu-cover { display: block; object-fit: cover; background: var(--group-fill); flex: none; }
.mu-cover-empty {
  display: flex; align-items: center; justify-content: center;
  color: var(--text-3); font-size: var(--fs-17); font-weight: var(--fw-medium);
}

/* 最近播放：横着推 */
.mu-strip {
  display: flex; gap: var(--sp-3); overflow-x: auto;
  padding: 0 var(--sp-4) var(--sp-2);
  scrollbar-width: none;
}
.mu-strip::-webkit-scrollbar { display: none; }
.mu-card { width: 104px; flex: none; text-align: left; }
.mu-card:active { opacity: .72; }
.mu-card-name { margin-top: var(--sp-2); font-size: var(--fs-13); font-weight: var(--fw-medium); }
.mu-card-sub { font-size: var(--fs-11); color: var(--text-3); }

/* 一行一首 */
.mu-list { padding: 0 var(--sp-2); }
.mu-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-md);
}
.mu-row.is-on .mu-title { color: var(--accent); }
.mu-idx {
  width: 26px; flex: none; text-align: center;
  font-size: var(--fs-13); color: var(--text-3); font-variant-numeric: tabular-nums;
}
.mu-main { flex: 1; min-width: 0; }
.mu-title { font-size: var(--fs-14); }
.mu-sub { margin-top: 1px; font-size: var(--fs-12); color: var(--text-3); }
.mu-keep {
  flex: none; width: 32px; height: 32px; border-radius: var(--r-full);
  display: flex; align-items: center; justify-content: center; color: var(--text-3);
}
.mu-count {
  flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 3px;
  font-size: var(--fs-11); color: var(--text-3); font-variant-numeric: tabular-nums;
}
.mu-count i { display: block; height: 3px; border-radius: var(--r-full); background: var(--accent-weak); }

/* 我的 */
.mu-me { padding: var(--sp-6) var(--sp-4) var(--sp-4); text-align: center; }
.mu-me-face { display: flex; justify-content: center; }
.mu-me-name { margin-top: var(--sp-3); font-size: var(--fs-17); font-weight: var(--fw-semi); }
.mu-me-sign { margin-top: 2px; font-size: var(--fs-12); color: var(--text-3); }
.mu-me-meta { margin-top: var(--sp-2); font-size: var(--fs-11); color: var(--text-3); }
.mu-stats {
  display: grid; grid-template-columns: repeat(4, 1fr);
  padding: 0 var(--sp-4) var(--sp-2); text-align: center;
}
.mu-stat b { display: block; font-size: var(--fs-17); font-weight: var(--fw-semi); }
.mu-stat span { font-size: var(--fs-11); color: var(--text-3); }
.mu-total {
  padding: var(--sp-3) var(--sp-4) 0;
  font-size: var(--fs-13); color: var(--text-2);
}
.mu-total-note { margin-top: 2px; font-size: var(--fs-11); color: var(--text-3); }

/* 歌单页头 */
.mu-head { display: flex; gap: var(--sp-4); align-items: center; padding: var(--sp-4); }
.mu-head-n { font-size: var(--fs-13); color: var(--text-3); margin-bottom: var(--sp-2); }

/* 底部那条 */
.mu-bar {
  position: sticky; bottom: 0; z-index: 2;
  background: var(--surface); border-top: 1px solid var(--border);
}
.mu-bar-line { height: 2px; background: var(--border); position: relative; }
/* 条子只有两像素，手指点不准。伪元素把上下各撑出一截当热区，不影响外观 */
.mu-bar-line::after { content: ""; position: absolute; inset: -9px 0; }
.mu-bar-line i { display: block; height: 100%; background: var(--accent); }
.mu-bar-safe .mu-bar-body { padding-bottom: calc(var(--sp-2) + var(--safe-bottom)); }
.mu-bar-body {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
}
.mu-ctl {
  flex: none; width: 36px; height: 36px; border-radius: var(--r-full);
  display: flex; align-items: center; justify-content: center; color: var(--text);
}
.mu-ctl:active { opacity: .6; }

/* ---- 正在播放（apps/music/NowPage.js）---- */
.mu-bar-body { cursor: pointer; }
.mu-now {
  height: 100%; display: flex; flex-direction: column; min-height: 0;
  padding: 0 var(--sp-5) calc(var(--sp-4) + var(--safe-bottom));
}
.mu-now-artist { text-align: center; font-size: var(--fs-13); color: var(--text-2); margin-bottom: var(--sp-3); }
.mu-now-face {
  flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-4); background: none; border: 0; padding: 0; color: inherit; cursor: pointer;
}
.mu-now-face .mu-cover { box-shadow: var(--shadow-2); }
.mu-now-line { font-size: var(--fs-15); text-align: center; line-height: 1.5; min-height: 1.5em; padding: 0 var(--sp-2); }
.mu-now-line-t { font-size: var(--fs-13); color: var(--text-2); text-align: center; margin-top: calc(var(--sp-3) * -1); }
/* 歌词那一面。上下各留半屏空白，第一句和最后一句也能滚到正中 */
.mu-lyrics { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; text-align: center;
  mask-image: linear-gradient(transparent, var(--text) 12%, var(--text) 88%, transparent);
  -webkit-mask-image: linear-gradient(transparent, var(--text) 12%, var(--text) 88%, transparent); }
.mu-lyrics-pad { height: 42%; }
.mu-ly {
  margin: 0; padding: var(--sp-2) var(--sp-2); font-size: var(--fs-15); line-height: 1.55;
  color: var(--text-3); cursor: pointer; transition: color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.mu-ly.is-on { color: var(--text); font-weight: var(--fw-semi); transform: scale(1.04); }
.mu-ly-t { display: block; font-size: var(--fs-12); font-weight: normal; margin-top: 2px; }
.mu-lyrics-empty { height: 100%; display: flex; align-items: center; justify-content: center;
  color: var(--text-3); font-size: var(--fs-14); padding: 0 var(--sp-5); line-height: 1.6; }
.mu-trans-btn { color: var(--text-3); }
.mu-trans-btn.is-on { color: var(--text); }
.mu-now-ctl { flex: none; padding-top: var(--sp-4); }
.mu-now-seek { display: flex; align-items: center; gap: var(--sp-3); }
.mu-now-t { flex: none; width: 3em; font-size: var(--fs-12); color: var(--text-3); font-variant-numeric: tabular-nums; }
.mu-now-t:last-child { text-align: right; }
.mu-range {
  flex: 1; min-width: 0; height: 20px; margin: 0; background: transparent;
  -webkit-appearance: none; appearance: none; cursor: pointer;
}
.mu-range::-webkit-slider-runnable-track {
  height: 3px; border-radius: var(--r-full);
  background: linear-gradient(to right, var(--text) var(--pct, 0%), var(--border) var(--pct, 0%));
}
.mu-range::-moz-range-track { height: 3px; border-radius: var(--r-full); background: var(--border); }
.mu-range::-moz-range-progress { height: 3px; border-radius: var(--r-full); background: var(--text); }
.mu-range::-webkit-slider-thumb {
  -webkit-appearance: none; width: 12px; height: 12px; margin-top: -4.5px;
  border-radius: var(--r-full); background: var(--text); border: 0;
}
.mu-range::-moz-range-thumb { width: 12px; height: 12px; border-radius: var(--r-full); background: var(--text); border: 0; }
.mu-now-err { margin-top: var(--sp-2); text-align: center; font-size: var(--fs-12); color: var(--danger); }
.mu-now-btns { display: flex; align-items: center; justify-content: center; gap: var(--sp-8); margin-top: var(--sp-3); }
.mu-now-btn {
  width: 48px; height: 48px; border-radius: var(--r-full); background: none; border: 0;
  display: flex; align-items: center; justify-content: center; color: var(--text);
}
.mu-now-main { width: 64px; height: 64px; background: var(--text); color: var(--bg); }

/* 片库：转换进度 */
.vd-work { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-2) 0; }
.vd-work-line { flex: 1; height: 3px; border-radius: var(--r-full); background: var(--border); }
.vd-work-line i { display: block; height: 100%; border-radius: var(--r-full); background: var(--accent); }
.vd-work span { font-size: var(--fs-12); color: var(--text-3); white-space: nowrap; }

/* 一起看 */
.wt { height: 100%; display: flex; flex-direction: column; }
/* 画面这一块按 16:9 留位置。**不能用 vh**（第 1 条），
   aspect-ratio 靠宽度算高度，结果一样而且不跟着地址栏抽动。 */
.wt-stage { position: relative; flex: none; background: var(--stage-bg); aspect-ratio: 16 / 9; }
.wt-video { display: block; width: 100%; height: 100%; object-fit: contain; }
/* 字幕与互动层收在同一个纵向栈里 —— 各自 absolute 会叠在一起，
   互动层打开时字幕正好压在输入框上。 */
.wt-bottom {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; flex-direction: column; gap: var(--sp-2);
  padding-bottom: var(--sp-2);
}
.wt-cue {
  padding: 0 var(--sp-4);
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  color: var(--on-scrim); font-size: var(--fs-13);
  text-shadow: 0 1px 4px rgba(0, 0, 0, .8);
}
/* 台词旁边那个段评气泡。压在画面上，所以描边要够亮 */
.wt-cue .rd-dot {
  margin-top: 0; opacity: .8;
  border-color: var(--on-scrim); color: var(--on-scrim);
}
.wt-cue .rd-dot.has-n {
  opacity: 1; background: var(--on-scrim);
  color: var(--stage-bg); border-color: var(--on-scrim);
}

/* ---- 影片全屏横屏 ----
   片子是 16:9 的，竖着全屏等于白全屏，所以全屏默认把画面这一层转过来。

   **用容器查询单位 cqw / cqh，不是视口单位。** 它们认的是容器自己的盒子，
   地址栏一收一放动不了它们，也就不碰第 1 条要求整齐划一的那串外壳 ——
   那一条要防的正是这种抽动，这里连量都不用量。

   手机本来就横着拿的时候不要再转一次。系统方向锁成功时也走这一条：
   锁成之后视口自己就是横的，这个 @media 不成立，CSS 这层自动让开。 */
.wt-rot { position: absolute; inset: 0; }
.wt.is-full .wt-stage { container-type: size; }

@media (orientation: portrait) {
  .wt.is-full.is-rot-l .wt-rot,
  .wt.is-full.is-rot-r .wt-rot {
    inset: auto; top: 50%; left: 50%;
    width: 100cqh; height: 100cqw;
  }
  /* 手机向左转（左边朝下）时，画面要顺时针补回来 */
  .wt.is-full.is-rot-l .wt-rot { transform: translate(-50%, -50%) rotate(90deg); }
  .wt.is-full.is-rot-r .wt-rot { transform: translate(-50%, -50%) rotate(-90deg); }
}

/* 正用着的那个方向键点亮 */
.wt-panel-bar .mu-ctl.is-on { color: var(--accent); }

/* ---- 影片全屏 ----
   画面吃掉剩下的全部高度。**不用 vh**（第 1 条）：.wt 本来就是 height:100%
   的 flex 列，这里把 aspect-ratio 撤掉改 flex:1 就够，不必自己量高度。 */
.wt.is-full > :not(.wt-stage) { display: none; }
.wt.is-full .wt-stage { flex: 1; aspect-ratio: auto; min-height: 0; }

.wt-exit {
  position: absolute; top: var(--sp-3); right: var(--sp-3);
  width: 34px; height: 34px; border: 0; border-radius: var(--r-full);
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .45); color: var(--on-scrim);
}

/* 全屏里点一下画面叫出来的互动层。压在画面底部，不挡字幕上方的内容 */
.wt-panel {
  padding: var(--sp-3) var(--sp-3) calc(var(--sp-3) + env(safe-area-inset-bottom));
  margin-bottom: calc(-1 * var(--sp-2));
  background: rgba(0, 0, 0, .58);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.wt-panel-bar {
  display: flex; align-items: center; gap: var(--sp-3);
  color: var(--on-scrim);
}
.wt-panel-bar .mu-ctl { color: var(--on-scrim); }
.wt-panel-bar .wt-time { color: var(--on-scrim); margin-left: auto; }
.wt-panel-msgs {
  display: flex; flex-direction: column; gap: var(--sp-2);
  max-height: 9em; overflow-y: auto;
}
.wt-panel .wt-msg { color: var(--on-scrim); background: rgba(255, 255, 255, .14); }
.wt-panel .wt-notice { color: var(--on-scrim); opacity: .75; }
.wt-panel .wt-input { border-top: 0; padding: 0; background: none; }
.wt-panel .wt-input input {
  background: rgba(255, 255, 255, .14); color: var(--on-scrim);
}
.wt-bar {
  flex: none; display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--border);
}
.wt-line { flex: 1; height: 3px; border-radius: var(--r-full); background: var(--border); position: relative; }
.wt-line::after { content: ""; position: absolute; inset: -10px 0; }
.wt-line i { display: block; height: 100%; border-radius: var(--r-full); background: var(--accent); }
.wt-time { font-size: var(--fs-12); color: var(--text-3); font-variant-numeric: tabular-nums; }
/* 字幕对轴那一条。只在有字幕时出现 */
.wt-sync {
  flex: none; display: flex; align-items: center; gap: var(--sp-3);
  padding: 0 var(--sp-4) var(--sp-2);
  font-size: var(--fs-12); color: var(--text-3);
  border-bottom: 1px solid var(--border);
}
.wt-sync button { color: var(--accent); padding: var(--sp-1) 0; }
.wt-sync button:active { opacity: .6; }
.wt-sync b { font-weight: var(--fw-medium); color: var(--text-2); font-variant-numeric: tabular-nums; }

.wt-msgs {
  flex: 1; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
}
/* 话贴着下边，和聊天一样。用 margin-top:auto 而不是 justify-content:flex-end ——
   后者在内容超出时，上面那几条会滚不到。 */
.wt-msgs > :first-child { margin-top: auto; }
.wt-msg {
  max-width: 78%; align-self: flex-start;
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-lg);
  background: var(--surface-2); font-size: var(--fs-14);
  white-space: pre-wrap; word-break: break-word;
}
.wt-msg.is-me { align-self: flex-end; background: var(--accent); color: var(--on-accent); }
.wt-notice { color: var(--text-3); font-size: var(--fs-12); }
.wt-msg.is-me .wt-notice { color: var(--on-accent); }
.wt-input {
  flex: none; display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3) calc(var(--sp-2) + var(--safe-bottom));
  border-top: 1px solid var(--border);
}
.wt-input input {
  flex: 1; min-width: 0; height: 38px; padding: 0 var(--sp-3);
  border-radius: var(--r-full); background: var(--surface-2);
  font-size: var(--fs-14); color: var(--text);
}

/* ---- 记账 ----
   全套黑白，进账出账只靠正负号和字重分，不引入颜色相（第 4 条）。 */
.bl-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--sp-4);
}
.bl-card-head { font-size: var(--fs-13); color: var(--text-3); margin-bottom: var(--sp-3); }
.bl-card-row { display: flex; gap: var(--sp-6); }
.bl-card-row > div { display: flex; flex-direction: column; gap: var(--sp-1); }
.bl-lab { font-size: var(--fs-12); color: var(--text-3); }
.bl-card-foot {
  margin-top: var(--sp-3); padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-12); color: var(--text-3);
}

.bl-money { font-variant-numeric: tabular-nums; color: var(--text); }
.bl-money.bl-lg { font-size: var(--fs-24); }
/* 进账写得轻一些：一列账单里，扎眼的应该是花出去的那些 */
.bl-money.bl-in { color: var(--text-2); font-weight: 400; }

.bl-list { padding-bottom: var(--safe-bottom); }
.bl-day {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: var(--sp-5) var(--sp-4) var(--sp-2);
  font-size: var(--fs-12); color: var(--text-3);
}
.bl-day-sum { font-variant-numeric: tabular-nums; }
.bl-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface); border-bottom: 1px solid var(--border);
}
.bl-cat {
  flex: none; width: 44px; height: 44px; border-radius: var(--r-md);
  display: grid; place-items: center;
  background: var(--surface-2); color: var(--text-2); font-size: var(--fs-12);
}
.bl-main { flex: 1; min-width: 0; }
.bl-title { font-size: var(--fs-15); color: var(--text); }
.bl-sub { font-size: var(--fs-12); color: var(--text-3); margin-top: 2px; }
/* 申请气泡的标题比金额短，不必用金额那个字号 */
.bl-req-title { font-size: var(--fs-15); }
.bl-pend { display: flex; flex-direction: column; align-items: flex-end; gap: var(--sp-2); }
.bl-pend-act { display: flex; gap: var(--sp-2); }

/* ---- 书 ---- */

.rd { display: flex; flex-direction: column; height: 100%; }
.rd-body { flex: 1; padding: var(--sp-4) var(--sp-5) var(--sp-5); }
.rd-chapter {
  font-size: var(--fs-13); color: var(--text-3);
  padding-bottom: var(--sp-3); margin-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border);
}
.rd-p {
  margin: 0 0 var(--sp-4); text-indent: 2em;
  font-size: var(--fs-17); line-height: 1.9; color: var(--text);
}
.rd-bar {
  flex: none; display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4); padding: var(--sp-3) var(--sp-5);
  border-top: 1px solid var(--border); background: var(--surface);
}
.rd-meta { text-align: center; display: flex; flex-direction: column; gap: 2px; }
.rd-meta > span:first-child { font-size: var(--fs-14); font-weight: var(--fw-semi); }
.rd-sub { font-size: var(--fs-11); color: var(--text-3); }

/* 一起读时她说的那几句。压在正文和翻页条之间，不遮正文 */
.rd-say {
  flex: none; max-height: 9em; overflow-y: auto;
  padding: var(--sp-3) var(--sp-5);
  border-top: 1px solid var(--border); background: var(--surface-2);
}
.rd-say-who { font-size: var(--fs-11); color: var(--text-3); margin-bottom: 4px; }
.rd-say-line {
  font-size: var(--fs-14); line-height: 1.7; color: var(--text);
  margin-bottom: var(--sp-2);
}
.rd-say-line:last-child { margin-bottom: 0; }

/* ---- 封面 ---- */
.cv {
  flex: none; width: 100%; aspect-ratio: 2 / 3;
  border-radius: var(--r-sm); overflow: hidden;
  background: var(--surface-2);
  /* 白封面压在白列表上，靠发丝线与阴影站住，不靠灰底 */
  box-shadow: 0 0 0 1px var(--border-2), var(--shadow-1);
}
.cv-mini { width: 30px; border-radius: var(--r-xs); }
.cv-photo { width: 100%; height: 100%; background-size: cover; background-position: center; }
.cv-drawn { display: block; width: 100%; height: 100%; }

.cv-ground { fill: var(--cv-bg); }
.cv-frame  { fill: none; stroke: var(--cv-ink); stroke-width: .4; opacity: .45; }
.cv-title  { fill: var(--cv-ink); font-family: var(--font-serif); }
.cv-rule   { stroke: var(--cv-ink); stroke-width: .6; opacity: .55; }
.cv-author { fill: var(--cv-ink); font-family: var(--font-serif); opacity: .62; }

/* 黑白两版。底色与墨色直接取 --surface / --text，
   暗色模式下这两个本来就是对调的，不必另写一套 */
.cv-t1 { --cv-bg: var(--surface); --cv-ink: var(--text); }
.cv-t2 { --cv-bg: var(--text);    --cv-ink: var(--surface); }

/* ---- 角色的书架 ---- */
/* minmax(0,1fr) 而不是 1fr：1fr 的下限是 auto，书名又带 .ellipsis（nowrap），
   于是一本长书名的最小内容宽就把自己那一格撑开，旁边两格被压到看不见。
   同样的道理，格子里每一层都要 min-width:0，否则下限照样往上顶。 */
.shelf-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4); padding: var(--sp-4) var(--sp-5);
}
.shelf-item {
  display: flex; flex-direction: column; gap: 6px; min-width: 0;
  background: none; border: 0; padding: 0; text-align: left;
}
.shelf-item > * { min-width: 0; max-width: 100%; }
.shelf-item.is-ghost .cv { opacity: .62; }
.shelf-name { font-size: var(--fs-13); color: var(--text); }
.shelf-sub { font-size: var(--fs-11); color: var(--text-3); }


/* 影评与书评 */
.rv-when { font-size: var(--fs-12); color: var(--text-3); padding: var(--sp-2) 0 var(--sp-3); }
.rv-p {
  margin: 0 0 var(--sp-4); text-indent: 2em;
  font-size: var(--fs-15); line-height: 1.85; color: var(--text);
}

/* ---- 一起看：顶部那两个大切换 ---- */
/* 比 .segmented 高一档，也更靠上 —— 它是这一页的主导航，不是一个表单控件 */
.top-switch {
  display: flex; gap: 2px; padding: 3px; margin: 0 var(--sp-5) var(--sp-3);
  background: var(--surface-2); border-radius: var(--r-md);
}
.top-switch-item {
  flex: 1; height: 38px; border-radius: var(--r-sm);
  font-size: var(--fs-15); font-weight: var(--fw-medium);
  color: var(--text-2); background: none; border: 0;
  transition: all var(--dur-fast) var(--ease);
}
.top-switch-item.is-active {
  background: var(--surface); color: var(--text); box-shadow: var(--shadow-1);
}

/* ---- 阅读器外观 ----
   纸色、字号、字体走 --rd-* 变量，由 system/reader.js 按用户设置生成。
   它们只作用在 .rd 里面，不碰 app 的主题。 */
.rd {
  --rd-bg: var(--surface);
  --rd-ink: var(--text);
  --rd-fs: var(--fs-17);
  --rd-lh: 1.9;
  --rd-font: var(--font-serif);
  background: var(--rd-bg);
  background-image: var(--rd-bg-image, none);
  background-size: cover; background-position: center;
}
.rd-paper-paper { --rd-bg: var(--rd-paper-bg); --rd-ink: var(--rd-paper-ink); }
.rd-paper-sepia { --rd-bg: var(--rd-sepia-bg); --rd-ink: var(--rd-sepia-ink); }
.rd-paper-night { --rd-bg: var(--rd-night-bg); --rd-ink: var(--rd-night-ink); }

.rd .rd-p {
  font-size: var(--rd-fs); line-height: var(--rd-lh);
  color: var(--rd-ink); font-family: var(--rd-font);
}
.rd .rd-chapter { color: var(--rd-ink); opacity: .62; border-bottom-color: currentColor; }
.rd-paper-theme .rd-chapter { border-bottom-color: var(--border); }

/* 点区盖住整块正文。左右各三分之一翻页，中间在全屏时收放顶栏 */
.rd-tap { flex: 1; display: flex; min-height: 0; }
.rd-tap > .rd-body { flex: 1; }

/* 全屏：顶栏和翻页条都不在，正文自己留出安全区 */
.rd.is-bare .rd-body { padding-top: calc(var(--sp-6) + env(safe-area-inset-top)); }
.rd.is-bare { padding-bottom: env(safe-area-inset-bottom); }

/* 翻页动画。旧的一页先出去，换页，新的一页再进来 —— 同一份正文做变换，
   不复制第二份 DOM。四种都是这一条路子。 */
.rd-body.is-out { animation: rd-out var(--rd-out) var(--ease) both; }
.rd-body.is-in  { animation: rd-in var(--rd-in) var(--ease) both; }
.rd-body { --rd-out: 170ms; --rd-in: 190ms; --rd-from: 0; --rd-to: 0; }

.fx-slide.is-out.is-fwd  { --rd-to: -18%; }
.fx-slide.is-out.is-back { --rd-to: 18%; }
.fx-slide.is-in.is-fwd   { --rd-from: 18%; }
.fx-slide.is-in.is-back  { --rd-from: -18%; }
.fx-slide.is-out { animation-name: rd-out-x; }
.fx-slide.is-in  { animation-name: rd-in-x; }

.fx-vertical.is-out.is-fwd  { --rd-to: -12%; }
.fx-vertical.is-out.is-back { --rd-to: 12%; }
.fx-vertical.is-in.is-fwd   { --rd-from: 12%; }
.fx-vertical.is-in.is-back  { --rd-from: -12%; }
.fx-vertical.is-out { animation-name: rd-out-y; }
.fx-vertical.is-in  { animation-name: rd-in-y; }

@keyframes rd-out   { from { opacity: 1; } to { opacity: 0; } }
@keyframes rd-in    { from { opacity: 0; } to { opacity: 1; } }
@keyframes rd-out-x { from { opacity: 1; transform: none; }
                      to { opacity: 0; transform: translateX(var(--rd-to)); } }
@keyframes rd-in-x  { from { opacity: 0; transform: translateX(var(--rd-from)); }
                      to { opacity: 1; transform: none; } }
@keyframes rd-out-y { from { opacity: 1; transform: none; }
                      to { opacity: 0; transform: translateY(var(--rd-to)); } }
@keyframes rd-in-y  { from { opacity: 0; transform: translateY(var(--rd-from)); }
                      to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .rd-body.is-out, .rd-body.is-in { animation-duration: 1ms; }
}

/* 顶栏上并排的几个动作 */
.nav-acts { display: flex; align-items: center; gap: var(--sp-1); }

/* 取色器 + 手填色值 */
.rd-color { display: flex; align-items: center; gap: var(--sp-3); }
.rd-color .rd-swatch {
  flex: none; width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--border); border-radius: var(--r-xs); background: none;
}
.rd-color > *:nth-child(2) { flex: 1; }

/* ---- 书摘卡片 ---- */
.xc-card {
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface); padding: var(--sp-4); max-width: 17em;
}
.xc-from { font-size: var(--fs-11); color: var(--text-3); margin-bottom: var(--sp-2); }
.xc-quote {
  font-family: var(--font-serif); font-size: var(--fs-15); line-height: 1.8;
  color: var(--text); border-left: 2px solid var(--border-2);
  padding-left: var(--sp-3); margin: 0;
}
.xc-note {
  margin: var(--sp-3) 0 0; font-size: var(--fs-13);
  color: var(--text-2); line-height: 1.6;
}

/* ---- 段评 ----
   每一段正文旁边一个小气泡。有评论显示条数，没有就是个淡的，点开是那一段的评论页。
   气泡占一条固定窄列，不压在正文上，也不让正文每段宽度不一。 */
.rd-para { display: flex; align-items: flex-start; gap: var(--sp-2); }
.rd-para > .rd-p { flex: 1; min-width: 0; }
.rd-dot {
  flex: none; width: 22px; height: 22px; margin-top: .5em;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-2); border-radius: var(--r-full);
  background: none; color: var(--text-3); opacity: .5;
}
.rd-dot.has-n {
  opacity: 1; color: var(--surface);
  background: var(--text); border-color: var(--text);
}
.rd-dot-n { font-size: 10px; font-weight: var(--fw-medium); line-height: 1; }

/* 评论页顶上那一段原文 */
.pr-passage {
  font-family: var(--font-serif); font-size: var(--fs-16); line-height: 1.85;
  color: var(--text); padding: var(--sp-4);
  border-left: 2px solid var(--border-2); background: var(--surface-2);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
}
.pr-from { text-align: right; }

/* 她说的话默认收着，点一下才展开 */
.rd-say-head {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; padding: 0; background: none; border: 0; color: var(--text-3);
}
.rd-say.is-folded { padding-top: var(--sp-2); padding-bottom: var(--sp-2); }

/* 先读的那个角色读到哪儿了。一条细线，不抢正文 */
.ra-line {
  flex: none; display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-5);
  font-size: var(--fs-11); color: var(--text-3);
  border-top: 1px solid var(--border);
}

/* ---- 健康 ---- */
/* 四个大格子。数字要一眼看见，所以标题小、数值大 */
.hl-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-3); padding: var(--sp-4) var(--sp-5);
}
.hl-tile {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  min-width: 0; padding: var(--sp-4);
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface); color: var(--text-3); text-align: left;
}
.hl-tile b {
  font-size: var(--fs-20); font-weight: var(--fw-semi); color: var(--text);
  font-variant-numeric: tabular-nums; line-height: 1.2;
}
.hl-tile span {
  font-size: var(--fs-11); color: var(--text-3);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* 加减那一格。数字仍然是一眼看见的那个，两个键靠右，不抢它的位置 */
.hl-tile-count { text-align: left; }
.hl-count { display: flex; align-items: center; gap: var(--sp-2); width: 100%; }
.hl-count b { flex: 1; min-width: 0; }
.hl-count-acts { flex: none; display: flex; gap: var(--sp-1); }
.hl-count-acts button {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: var(--r-full);
  background: var(--surface-2); color: var(--text-2);
}
.hl-count-acts button:disabled { opacity: .35; }

/* 排便一次一条。每条上面一行是序号、时间、删除，下面一行是形态 */
.hl-poop {
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border);
}
.hl-poop:first-child { padding-top: var(--sp-2); }
.hl-poop-head { display: flex; align-items: center; gap: var(--sp-3); }
.hl-poop-no {
  flex: none; font-size: var(--fs-12); color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.hl-poop-head input { flex: 1; min-width: 0; }
.hl-poop-head button { flex: none; color: var(--text-3); }

/* 趋势那一条。黑白，跟着 currentColor 走 */
.hl-trend { padding: var(--sp-3) 0 var(--sp-2); }
.hl-spark {
  display: block; width: 100%; height: 44px;
  fill: none; stroke: var(--text); stroke-width: 1.5;
  stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.hl-spark path { vector-effect: non-scaling-stroke; }
.hl-trend-foot {
  display: flex; justify-content: space-between;
  font-size: var(--fs-11); color: var(--text-3); padding-top: var(--sp-2);
}

/* ---- 角色手机 ---- */
/* 一块主屏。**做成一块真的桌面**：壁纸铺满、顶上时间、底下一排图标。
   从前这里是两列带副标题的卡片，看着像一页设置 —— 而这是「拿起别人的
   手机」这件事的第一眼。没有导航栏（Page 的 hideBar），顶上那一行自己画。 */
.tp-desk {
  /* .page-body 是个普通块，不是 flex 容器，所以这里得写 height: 100%
     才真的铺到底；写 flex: 1 只会缩成内容那么高，底下那一条就吊在半空 */
  height: 100%; display: flex; flex-direction: column;
  padding: var(--sp-5) var(--sp-5) var(--sp-3);
  background-size: cover; background-position: center;
}
.tp-desk.has-img { position: relative; }
/* 壁纸上压一层，不然浅色壁纸上的字读不出来 */
.tp-desk.has-img::before {
  content: ''; position: absolute; inset: 0;
  background: var(--scrim); pointer-events: none;
}
.tp-desk > * { position: relative; }

/* 顶上那一行：角色那边此刻几点。真机上这一行没有名字，
   但这里同时开着好几台，不写是谁会认错 */
.tp-desk-top {
  flex: none; display: flex; flex-direction: column;
  align-items: center; gap: 2px; padding: var(--sp-4) 0 var(--sp-6);
}
.tp-desk-top b {
  font-size: var(--fs-28); font-weight: var(--fw-semi); line-height: 1.1;
  color: var(--text); font-variant-numeric: tabular-nums;
}
.tp-desk-top span { font-size: var(--fs-12); color: var(--text-2); }
.tp-desk.has-img .tp-desk-top b { color: var(--on-scrim); }
.tp-desk.has-img .tp-desk-top span { color: var(--on-scrim); opacity: .85; }

/* 图标网格。四列，和真的主界面一样是圆角方块加下面一行名字，
   不写副标题 —— 桌面上的图标不解释自己里面有几条 */
.tp-apps {
  flex: 1; min-height: 0; overflow-y: auto;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-5) var(--sp-3); align-content: start;
}
.tp-cell {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  min-width: 0; background: none; border: 0; padding: 0;
}
.tp-tile {
  display: flex; align-items: center; justify-content: center;
  width: 100%; max-width: 58px; aspect-ratio: 1;
  border-radius: 24%; background: var(--tile-bg); color: var(--icon-color);
  box-shadow: var(--tile-shadow);
  transition: transform var(--dur-fast) var(--ease);
}
.tp-cell:active .tp-tile { transform: scale(.93); }
/* 图标整张放进去，永远不裁 —— 和主界面那套同一个理由 */
.tp-tile.has-image {
  background-size: contain; background-position: center; background-repeat: no-repeat;
}
.tp-name {
  font-size: var(--fs-11); font-weight: var(--fw-normal);
  color: var(--text-2); max-width: 100%; text-align: center;
}
.tp-desk.has-img .tp-name {
  color: var(--on-scrim); text-shadow: 0 1px 3px rgba(0, 0, 0, .55);
}

/* 底下那一条。生成、外观、锁上是**你的**工具，不是这台手机上的应用，
   所以和上面的图标分开摆，做成一条而不是几格 */
.tp-dock {
  flex: none; display: flex; justify-content: space-around; gap: var(--sp-2);
  margin-top: var(--sp-4); padding: var(--sp-3) var(--sp-2);
  border-radius: var(--r-xl);
  background: var(--capsule-bg); border: 1px solid var(--capsule-border);
}
.tp-dock-btn {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  flex: 1; min-width: 0; padding: var(--sp-1) 0;
  color: var(--text-2); font-size: var(--fs-11);
}
.tp-desk.has-img .tp-dock-btn { color: var(--on-scrim); }


/* 换成图片的图标，和换壁纸那一块的预览 */
.tp-ico-img {
  display: block; width: 28px; height: 28px; border-radius: var(--r-sm);
  background: var(--surface-2) center / cover no-repeat;
}
.tp-wallpick {
  display: flex; align-items: center; justify-content: center;
  /* 固定像素，不用 vh —— 第 1 条：vh 只许出现在外壳那三条上 */
  width: 100%; height: 240px;
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface-2) center / cover no-repeat;
  color: var(--text-3); font-size: var(--fs-12);
}

/* 相册。一格一张，图在上描述在下 —— 没有图的那些只剩描述，
   所以格子不做成纯图的正方形网格 */
.tp-shots {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-3); padding: var(--sp-4) var(--sp-5);
}
.tp-shot {
  display: flex; flex-direction: column; gap: var(--sp-2);
  min-width: 0; padding: var(--sp-2);
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface); text-align: left;
}
.tp-shot-img, .tp-shot-none {
  display: flex; align-items: center; justify-content: center;
  width: 100%; aspect-ratio: 1; border-radius: var(--r-sm);
  background: var(--surface-2) center / cover no-repeat; color: var(--text-3);
}
.tp-shot-note { font-size: var(--fs-12); color: var(--text-2); max-width: 100%; }
/* 「你发的」那一枚。角色自己存下来的和生成出来的混在一起分不清哪张是真的 */
.tp-shot-from {
  align-self: flex-start; padding: 1px var(--sp-2);
  border-radius: var(--r-full); background: var(--accent-weak);
  color: var(--accent); font-size: var(--fs-11);
}

/* 生成出来的那一段对话。不复用会话页那套气泡：那一套跟着用户的美化 CSS 走，
   这里是「别人手机上的聊天」，不该被自己的皮肤影响 */
.tp-who {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-md);
  background: var(--surface-2); color: var(--text-2); font-size: var(--fs-13);
}
.tp-who span { flex: 1; min-width: 0; }
.tp-wait {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: var(--sp-6); color: var(--text-3); font-size: var(--fs-13);
}
.tp-talk { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-4) var(--sp-5); }
.tp-line { display: flex; align-items: flex-end; gap: var(--sp-2); }
.tp-line.is-me { flex-direction: row-reverse; }
.tp-bubble {
  max-width: 72%; padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-md); background: var(--surface);
  border: 1px solid var(--border);
  font-size: var(--fs-14); line-height: 1.5; color: var(--text);
  white-space: pre-wrap; word-break: break-word;
}
.tp-line.is-me .tp-bubble { background: var(--accent-weak); border-color: var(--accent-weak); }
/* 和你那一段里的图片。只画缩略图 —— 这一页是看个样子，不是会话页 */
.tp-bubble-img { padding: 3px; }
.tp-bubble-img img {
  display: block; max-width: 180px; width: 100%; height: auto;
  border-radius: var(--r-sm);
}

/* 锁屏。壁纸铺底，上面时间与头像，下面数字键盘 */
.tp-lock {
  /* 同 .tp-desk：要 height: 100%，否则 space-between 无从分配 */
  height: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: space-between;
  padding: var(--sp-6) var(--sp-5) var(--sp-5);
  background-size: cover; background-position: center;
}
.tp-lock.has-img { position: relative; }
.tp-lock.has-img::before {
  content: ''; position: absolute; inset: 0;
  background: var(--scrim-strong); pointer-events: none;
}
.tp-lock > * { position: relative; }
/* 上面只有一张头像。时刻与星期撤了：主屏上它是「这台手机此刻几点」，
   有用；锁屏上你是来输密码的，那两行只占地方 */
.tp-lock-top { display: flex; align-items: center; justify-content: center; }

/* 圆点与那一行说明是一件事，拢在一起 —— 分成两个 flex 子项的话
   space-between 会把它们撑开老远 */
.tp-lock-mid { display: flex; flex-direction: column; align-items: center; }
.tp-dots { display: flex; gap: var(--sp-3); }
.tp-dots i {
  width: 11px; height: 11px; border-radius: var(--r-full);
  border: 1px solid var(--text-3); background: transparent;
}
.tp-dots i.is-on { background: var(--text); border-color: var(--text); }
.tp-lock-note { font-size: var(--fs-12); color: var(--text-3); margin-top: var(--sp-4); }

.tp-keys {
  display: grid; grid-template-columns: repeat(3, 72px);
  gap: var(--sp-3); justify-content: center;
}
.tp-key {
  display: flex; align-items: center; justify-content: center;
  width: 72px; height: 72px; border-radius: var(--r-full);
  background: var(--surface-2); color: var(--text);
  font-size: var(--fs-24); font-variant-numeric: tabular-nums;
}
.tp-lock-acts { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); }
.tp-lock-acts button { font-size: var(--fs-13); color: var(--text-2); padding: var(--sp-2); }

/* ---- 相册 ---- */
.photo-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 3px; padding: 0 var(--sp-3) var(--sp-4);
}
.photo-cell { padding: 0; border: 0; background: none; min-width: 0; }
.photo-tile {
  width: 100%; aspect-ratio: 1 / 1; border-radius: var(--r-xs);
  background-size: cover; background-position: center;
  background-color: var(--surface-2);
}
.photo-tile.is-blank {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; color: var(--text-3);
  box-shadow: inset 0 0 0 1px var(--border);
}
.photo-tile.is-blank span { font-size: var(--fs-11); }

.photo-stage {
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-4) var(--sp-3); background: var(--surface-2);
}
.photo-full { max-width: 100%; display: block; border-radius: var(--r-sm); }
/* 卡片那一层：里面是 shadow root，外面只管给它一个白底和圆角 */
.cardbox {
  max-width: 100%; overflow: hidden;
  border-radius: var(--r-sm); background: var(--surface);
  box-shadow: var(--shadow-1);
}

/* ---- 出行 ---- */
/* 详情页顶上那一块。写的是算出来的数（还有几天、第几天），不写形容 */
.trip-head {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4); border-radius: var(--r-md);
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text-2);
}
.trip-head > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.trip-head b { font-size: var(--fs-15); font-weight: var(--fw-semi); color: var(--text); }
.trip-head span { font-size: var(--fs-12); color: var(--text-3); }

/* 会话里那条出行提议。和外卖那一条共用 tr-top / tr-body / tr-foot 那几层 */
.bubble-trip { min-width: 190px; }
/* 票那一行右边的两个动作。按钮与删除挨着，不各占一行 */
.tk-acts { display: flex; align-items: center; gap: var(--sp-2); }

/* ---- 线下 ----
   见 ARCHITECTURE 4.107。颜色、字号、行距全走 --sg-* 变量，由
   system/stage.js 按用户设置生成，只作用在 .sg 里面，不碰 app 的主题。
   和阅读器的 --rd-* 是两套：线下想要的纸色字号，和读书时想要的不是一回事。 */
.sg {
  --sg-bg: var(--surface);
  --sg-ink: var(--text);
  --sg-dim: var(--text-2);
  --sg-line: var(--border);
  --sg-mark: var(--accent);
  --sg-mark-soft: transparent;
  --sg-fs: var(--fs-17);
  --sg-lh: 1.9;
  --sg-gap: 1em;
  --sg-measure: 34em;
  --sg-font: var(--font-serif);

  /* .page-body 是块级容器，flex:1 在它里面撑不开，要 height */
  position: relative;
  height: 100%;
  display: flex; flex-direction: column;
  background: var(--sg-bg);
  background-image: var(--sg-bg-image, none);
  background-size: cover; background-position: center;
  color: var(--sg-ink);
}

/* 刊头。整块顶栏归线下自己画 —— 用 app 的 navbar 会在深色主题上方
   留一条白边，那就不是「整个页面进入线下」了。铺满屏幕时整条不在 */
.sg-head {
  flex: none;
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  padding-top: calc(var(--sp-2) + env(safe-area-inset-top));
  border-bottom: 1px solid var(--sg-line);
}
.sg-head-text { flex: 1; min-width: 0; }
.sg-icon {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: var(--r-md);
  color: var(--sg-ink); background: none; border: 0;
}
.sg-eyebrow {
  font-size: var(--fs-11); letter-spacing: .1em;
  color: var(--sg-dim); text-transform: uppercase;
}
.sg-title {
  font-family: var(--sg-font); font-size: var(--fs-15);
  color: var(--sg-ink); text-wrap: balance;
}

/* 点区盖住整块正文，左右各三分之一翻页 */
.sg-tap { flex: 1; display: flex; min-height: 0; }
.sg-body {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: var(--sp-6) var(--sp-4);
  -webkit-overflow-scrolling: touch;
}
/* 铺满屏幕时没有顶栏，正文自己让出上方的安全区。
   下方不用管：.page-body 是最后一个孩子，安全区由它让（见 ui.css） */
.sg.is-spread .sg-body {
  padding-top: calc(var(--sp-8) + env(safe-area-inset-top));
  padding-bottom: var(--sp-8);
}

/* 正文栏。宽度归它，排版归 .sg-text —— 邮戳要和正文同一条栏宽对齐 */
.sg-col { max-width: var(--sg-measure); margin: 0 auto; }

/* 正文。一行三十来个汉字，段距分段，首行不缩进 ——
   屏幕上缩进远不如段距清楚 */
.sg-text {
  font-family: var(--sg-font);
  font-size: var(--sg-fs);
  line-height: var(--sg-lh);
  color: var(--sg-ink);
}
.sg-text p { margin: 0 0 var(--sg-gap); }
.sg-text p:last-child { margin-bottom: 0; }
.sg-q { color: var(--sg-ink); }
.sg-act { color: var(--sg-dim); font-style: italic; }

/* 生成中那一段直接往这里塞 textContent，绕开 diff ——
   几万字走 Preact 每秒几十次 diff 一个巨大文本节点，界面会僵住 */
.sg-live { white-space: pre-wrap; }

/* 一段开头的署名。全是小字 —— 撑开篇那一页的是正文的首字下沉，不是这里。
   刊物里的页码（folio）一向是小的、推到页边的，所以编号并进这一行，
   不再是上一版那个 56px 的大数字（见 ARCHITECTURE 4.107）。 */
.sg-sign {
  display: flex; align-items: center; gap: var(--sp-3);
  margin: 0 0 var(--sp-5);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--sg-line);
  /* 跟着正文那套字走。从前这里落回系统黑体，正文是宋体，两种字挤在
     一块儿，署名就显得比正文硬 */
  font-family: var(--sg-font);
}
.sg-sign-text { min-width: 0; }
.sg-sign-name {
  font-size: var(--fs-12); letter-spacing: .2em; line-height: 1.6;
  color: var(--sg-ink);
}
.sg-sign.is-mine .sg-sign-name { color: var(--sg-dim); }
/* 不用 tabular-nums：这里的数字不排成列，等宽只会让时刻看着像块表 */
.sg-sign-meta {
  font-size: 10px; letter-spacing: .14em; line-height: 1.6;
  color: var(--sg-dim);
}
/* 头像。方的，一道发丝框，不做圆 —— 圆头像一出现，整页就读成聊天了 */
.sg-face {
  flex: none; width: 46px; height: 46px; object-fit: cover;
  border-radius: 3px; border: 1px solid var(--sg-line);
}

/* 首字下沉。杂志开篇那一页真正的大件。
   initial-letter 各家支持不齐，稳的是 ::first-letter 加浮动。
   开头是标点的那一段不加（canDrop 在 StageBits.js 里判） */
.sg-text.is-drop > p:first-child::first-letter {
  float: left;
  font-size: 3em; line-height: .86;
  padding: .06em .12em 0 0;
  color: var(--sg-ink);
  font-weight: var(--fw-normal);
}

/* 只要一行的那一档。同一份字距，去掉头像与细线 */
.sg-byline {
  font-family: var(--sg-font);
  font-size: 10px; letter-spacing: .16em; color: var(--sg-dim);
  margin-bottom: var(--sp-5);
}

/* 明信片那一档。竖着滑，一张一张排下去 */
.sg-feed {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: var(--sp-4);
  -webkit-overflow-scrolling: touch;
}
.sg-card {
  max-width: var(--sg-measure); margin: 0 auto var(--sp-4);
  padding: var(--sp-5) var(--sp-4);
  border: 1px solid var(--sg-line);
  border-radius: 3px;
  background: var(--sg-bg);
  display: flex; flex-direction: column;
}
.sg-card:last-child { margin-bottom: 0; }
/* 每片一样大那一档：真明信片的比例（105 × 148 毫米），文字在片内滚。
   高度不能用 vh —— 第 1 条只许外壳那三层用，这里靠比例 */
.sg-card.is-fixed { aspect-ratio: 105 / 148; }
.sg-card-body { flex: 1; min-height: 0; }
.sg-card.is-fixed .sg-card-body { overflow-y: auto; -webkit-overflow-scrolling: touch; }
.sg.is-spread .sg-feed {
  padding-top: calc(var(--sp-4) + env(safe-area-inset-top));
}

/* 场外指示的标记。不占一张，只在刊头留一个点得开的记号 */
.sg-note {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-11); letter-spacing: .1em;
  color: var(--sg-dim);
  border: 1px solid var(--sg-line); border-radius: var(--r-pill);
  padding: 2px var(--sp-2);
  background: none;
}

/* 页码那一条。一根细线加一个数 */
.sg-foot {
  flex: none;
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
  color: var(--sg-dim); font-size: var(--fs-11);
  font-variant-numeric: tabular-nums;
}
.sg-rule { flex: 1; height: 1px; background: var(--sg-line); }

/* 底下那一条。平时收着，写的时候升起全屏编辑器 */
.sg-bar {
  flex: none;
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-top: 1px solid var(--sg-line);
  background: var(--sg-bg);
}
.sg-write {
  flex: 1; text-align: left;
  font-family: var(--sg-font); font-size: var(--fs-14);
  color: var(--sg-dim);
  border: 1px solid var(--sg-line); border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
  background: none;
}
.sg-act-btn {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border-radius: var(--r-md);
  color: var(--sg-ink); background: none; border: 1px solid var(--sg-line);
}
.sg-act-btn:disabled { opacity: .4; }

/* 铺满屏幕：刊头、页码、底栏都退掉，只剩正文 */
.sg.is-spread .sg-head, .sg.is-spread .sg-foot, .sg.is-spread .sg-bar { display: none; }

/* 翻页。旧的一页先出去，换页，新的一页再进来 —— 同一份正文做变换 */
.sg-body.is-out { animation: sg-out 160ms var(--ease) both; }
.sg-body.is-in  { animation: sg-in 200ms var(--ease) both; }
@keyframes sg-out { to { opacity: 0; } }
@keyframes sg-in { from { opacity: 0; } }
.sg[data-effect="slide"] .sg-body.is-out { animation-name: sg-out-slide; }
.sg[data-effect="slide"] .sg-body.is-in  { animation-name: sg-in-slide; }
@keyframes sg-out-slide { to { opacity: 0; transform: translateX(-12%); } }
@keyframes sg-in-slide { from { opacity: 0; transform: translateX(12%); } }
.sg[data-effect="vertical"] .sg-body.is-out { animation-name: sg-out-up; }
.sg[data-effect="vertical"] .sg-body.is-in  { animation-name: sg-in-up; }
@keyframes sg-out-up { to { opacity: 0; transform: translateY(-8%); } }
@keyframes sg-in-up { from { opacity: 0; transform: translateY(8%); } }
.sg[data-effect="instant"] .sg-body.is-out,
.sg[data-effect="instant"] .sg-body.is-in { animation: none; }
@media (prefers-reduced-motion: reduce) {
  .sg-body.is-out, .sg-body.is-in { animation: none; }
}

/* 场次列表。每一条是一篇稿子的目录行，不画卡片 */
.sc-row { display: flex; flex-direction: column; gap: 2px; }
.sc-meta { font-size: var(--fs-12); color: var(--text-2); letter-spacing: .04em; }

/* 一段的第几版。重写不删旧的，在这儿翻 */
.sg-vers {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  margin-top: var(--sp-5);
  color: var(--sg-dim); font-size: 10px; letter-spacing: .16em;
}
.sg-vers-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: var(--r-sm);
  color: inherit; background: none; border: 1px solid var(--sg-line);
}
.sg-vers-btn:disabled { opacity: .35; }
.sg-vers-no { font-variant-numeric: tabular-nums; }

/* ---- 线下：就在聊天里演的那一档 ----
   见 ARCHITECTURE 4.110。整场戏渲染在一条消息里，--sg-* 只作用在这个块上，
   块外面仍然是聊天的样子。 */
.sc-block {
  margin: var(--sp-4) 0;
  padding: var(--sp-4);
  border-radius: var(--r-md);
  background: var(--sg-bg);
  color: var(--sg-ink);
  font-family: var(--sg-font);
}
.sc-block .sg-text {
  font-size: var(--sg-fs); line-height: var(--sg-lh); color: var(--sg-ink);
}
.sc-block .sg-text p { margin: 0 0 var(--sg-gap); }
.sc-block .sg-text p:last-child { margin-bottom: 0; }

/* 划的那条线。开场一条，收场一条 */
.sc-div {
  display: flex; align-items: center; gap: var(--sp-3);
  margin: var(--sp-2) 0 var(--sp-5);
}
.sc-div-line { flex: 1; height: 1px; background: var(--sg-line); }
.sc-div-text {
  flex: none; font-size: 10px; letter-spacing: .22em; color: var(--sg-dim);
}
.sc-div.is-end { margin: var(--sp-5) 0 var(--sp-2); }

.sc-beat + .sc-beat { margin-top: var(--sp-6); }
.sc-empty { text-align: center; padding: var(--sp-4) 0; }

/* 底下那三个。线下自己的色，不跟 app 的按钮走 */
.sc-acts {
  display: flex; gap: var(--sp-2); justify-content: flex-end;
  margin-top: var(--sp-5); padding-top: var(--sp-3);
  border-top: 1px solid var(--sg-line);
}
.sc-act {
  font-size: var(--fs-12); letter-spacing: .1em;
  color: var(--sg-dim); background: none;
  border: 1px solid var(--sg-line); border-radius: var(--r-sm);
  padding: var(--sp-1) var(--sp-3);
}

/* 正在写的那一段。铺在列表末尾，不进块里 —— 块是 memo 过的 */
.sc-live { opacity: .85; }
.sc-live .sg-text { white-space: pre-wrap; }

/* 线下开着时的输入框。换个底色，一眼看出发出去的是正文不是消息 */
.composer-input.is-scene {
  background: var(--wg-bg);
  border-radius: var(--r-sm);
}

/* 悬浮窗。内联时没有线下那一页的菜单可挂，常用的几项浮在这儿 */
.sc-float-wrap {
  position: absolute; inset: 0; z-index: var(--z-sheet);
  background: var(--scrim);
  display: flex; align-items: flex-end; justify-content: center;
  padding: var(--sp-4);
  padding-bottom: calc(var(--sp-8) + var(--safe-bottom));
}
.sc-float {
  width: 100%; max-width: 360px;
  max-height: 70%;
  display: flex; flex-direction: column;
  background: var(--surface); color: var(--text);
  border-radius: var(--r-lg); box-shadow: var(--shadow-3);
  overflow: hidden;
}
.sc-float-head {
  flex: none; display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-15); font-weight: var(--fw-medium);
}
.sc-float-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: var(--r-sm);
  color: var(--text-2); background: none; border: 0;
}
.sc-float-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--sp-3) 0; }
.sc-float-themes {
  display: flex; gap: var(--sp-2); padding: 0 var(--sp-4) var(--sp-2);
}
.sc-swatch {
  flex: 1; padding: var(--sp-3) 0; border-radius: var(--r-sm);
  border: 1px solid; font-size: var(--fs-12);
}
.sc-swatch.is-on { box-shadow: 0 0 0 2px var(--accent); }
.sc-float-note {
  padding: 0 var(--sp-4) var(--sp-2);
  font-size: var(--fs-12); color: var(--text-2); line-height: 1.5;
}

/* ---- 我们：目录上那几个小按钮 ---- */
.wk-ops { display: inline-flex; align-items: center; gap: 2px; }
.wk-op {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: var(--r-sm);
  color: var(--text-3); background: none; border: 0;
}

/* ---- 气泡那一档：一段一个长气泡，气泡里按句断行 ---- */
/* 见 ARCHITECTURE 4.118。阅读那两档是刊物的排法，这一档是另一种读法。
   封面不在这儿 —— 读的时候上面不该压着一张图，那是扉页的事（.wk-hero） */
.sg-bubs {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: var(--sp-4) var(--sp-4) var(--sp-6);
  -webkit-overflow-scrolling: touch;
}

.sg-bub {
  max-width: var(--sg-measure); margin: 0 auto var(--sp-4);
  padding: var(--sp-4) var(--sp-4) var(--sp-5);
  border-radius: 20px;
  background: var(--sg-mark-soft);
  border: 1px solid transparent;
}
/* 自己写的那些不填底，只描一道线 —— 一屏全是实心块会糊成一片 */
.sg-bub.is-mine { background: none; border-color: var(--sg-line); }
.sg-bub:last-child { margin-bottom: 0; }
.sg-bub-who {
  font-family: var(--sg-font); font-size: var(--fs-11);
  letter-spacing: .22em; color: var(--sg-dim);
  margin-bottom: var(--sp-3);
}

/* 歌词页那样：一句一行，行距拉开，字比正文大一号 */
.sg-lyric {
  font-family: var(--sg-font);
  font-size: calc(var(--sg-fs) * 1.18);
  line-height: 1.5;
  color: var(--sg-ink);
}
.sg-lyric p { margin: 0 0 .6em; }
.sg-lyric p:last-child { margin-bottom: 0; }
.sg-lyric p.is-said { font-weight: var(--fw-medium); }

/* 我们：作品封面那一格 */
.wk-cover-row { display: flex; align-items: center; gap: var(--sp-3); }
.wk-cover {
  flex: none; width: 96px; height: 96px;
  border-radius: var(--r-sm); overflow: hidden;
  border: 1px dashed var(--line); background: var(--surface);
  color: var(--text-3); font-size: var(--fs-12);
  display: flex; align-items: center; justify-content: center; padding: 0;
}
.wk-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- 我们：扉页 ----
   点进一部作品先看到的那一屏。用这部作品自己的纸色，不是 app 的底色 ——
   它是作品的一部分，不是列表的表头 */
.wk-hero {
  background: var(--sg-bg); color: var(--sg-ink);
  padding: var(--sp-6) var(--sp-5) var(--sp-5);
  text-align: center;
  border-bottom: 1px solid var(--sg-line);
}
.wk-hero-art {
  width: 168px; aspect-ratio: 1; margin: 0 auto var(--sp-4);
  border-radius: 4px; overflow: hidden;
  background: var(--sg-mark-soft);
  border: 1px solid var(--sg-line);
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-2);
}
.wk-hero-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wk-hero-glyph { font-family: var(--sg-font); font-size: 64px; color: var(--sg-mark); line-height: 1; }
.wk-hero-title {
  font-family: var(--sg-font); font-size: var(--fs-20);
  color: var(--sg-ink); line-height: 1.3;
}
.wk-hero-names {
  margin-top: 8px; font-family: var(--sg-font);
  font-size: var(--fs-13); letter-spacing: .18em; color: var(--sg-dim);
}
.wk-hero-meta {
  margin-top: 5px; font-family: var(--sg-font);
  font-size: var(--fs-11); letter-spacing: .1em; color: var(--sg-dim);
}

/* ---- 群聊（ARCHITECTURE 4.162） ---- */
/* 群头像：最多四个成员拼一格。尺寸由 GroupFace 以自定义属性传入 */
.group-face {
  flex: none; width: var(--gf-size); height: var(--gf-size);
  border-radius: var(--r-sm); overflow: hidden;
  background: var(--surface); border: 1px solid var(--border);
  display: grid; grid-template-columns: 1fr; place-items: center;
}
.group-face.n-2, .group-face.n-3, .group-face.n-4 {
  grid-template-columns: 1fr 1fr; gap: 2px; padding: 2px; align-content: center;
}
.group-face.n-3 > :first-child { grid-column: 1 / 3; }
.group-head {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  padding: var(--sp-5) 0 var(--sp-2);
}
.group-head-acts { display: flex; gap: var(--sp-4); }
.group-add-foot { padding: var(--sp-4); }
/* 群里别人说的话，气泡上方写着是谁 */
.msg-who { font-size: var(--fs-11); color: var(--text-3); padding: 0 var(--sp-1); }
.mention-bar { padding: 0 var(--sp-3) var(--sp-2); margin-top: 0; }

/* ---- 互动标识（ARCHITECTURE 4.164） ----
   单色：一律是 currentColor。动效只在形态上做：火苗晃、快断时一明一暗、
   余烬暗着、太阳慢慢转、满级那一圈的亮点绕着走。
   系统设置里开了「减弱动态效果」的，全部停下来 */
.bd-g { display: inline-flex; align-items: center; justify-content: center; color: var(--text); line-height: 0; }
.bd-g.is-off { color: var(--text-3); }
.bd-g.is-ember { color: var(--text-3); }
.bd-g.is-lit.is-spark svg, .bd-g.is-lit.is-flame svg, .bd-g.is-lit.is-torch svg {
  transform-origin: 50% 90%; animation: bd-flicker calc(var(--dur-slow) * 6) var(--ease) infinite;
}
.bd-g.is-risk svg { animation: bd-risk calc(var(--dur-slow) * 4) ease-in-out infinite; }
.bd-g.is-lit.is-sun svg { animation: bd-spin calc(var(--dur-slow) * 60) linear infinite; }
.bd-g.is-lit.is-star svg { animation: bd-twinkle calc(var(--dur-slow) * 8) ease-in-out infinite; }
@keyframes bd-flicker {
  0%, 100% { transform: scale(1, 1) rotate(0); }
  30% { transform: scale(.96, 1.05) rotate(-2deg); }
  60% { transform: scale(1.03, .97) rotate(2deg); }
}
@keyframes bd-risk { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
@keyframes bd-spin { to { transform: rotate(360deg); } }
@keyframes bd-twinkle { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }

.bd-streak { display: inline-flex; align-items: center; gap: 2px; margin-left: var(--sp-1); vertical-align: middle; }
.bd-num { font-size: var(--fs-11); color: var(--text-2); font-weight: var(--fw-medium); }
.bd-num.is-risk { color: var(--text-3); }
.bd-list { flex: none; display: inline-flex; align-items: center; gap: var(--sp-1); }
/* 名字与标识挨着，时刻仍然靠右 */
.msg-name-wrap { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--sp-1); }
.msg-name-wrap .msg-name { flex: 0 1 auto; }

/* 头像外那一圈：比头像大一圈，圈本身不挡点击 */
.lv-ring { position: relative; display: inline-flex; width: var(--lv-size); height: var(--lv-size); color: var(--text); }
.lv-svg { position: absolute; inset: -6px; width: calc(100% + 12px); height: calc(100% + 12px);
  transform: rotate(-90deg); pointer-events: none; overflow: visible; }
.lv-track { fill: none; stroke: var(--border-2); stroke-width: 1.5; }
.lv-arc { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round;
  transition: stroke-dasharray var(--dur-slow) var(--ease-out); }
.lv-orbit { fill: currentColor; transform-origin: 50% 50%; animation: bd-spin calc(var(--dur-slow) * 16) linear infinite; }

.bd-strip {
  display: flex; align-items: center; gap: var(--sp-2); width: calc(100% - var(--sp-8));
  margin: var(--sp-2) var(--sp-4) 0; padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-md); background: var(--surface-2); color: var(--text); font-size: var(--fs-12);
}
.bd-strip .bd-streak { margin-left: 0; }
.bd-strip-lv { font-weight: var(--fw-medium); }
.bd-strip-n { margin-left: auto; color: var(--text-2); }

.bd-award {
  display: flex; align-items: center; gap: var(--sp-3); min-width: 200px; max-width: 260px;
  padding: var(--sp-3); border-radius: var(--r-lg); border: 1px solid var(--border-2);
  background: var(--surface);
}
.bd-award-body { min-width: 0; }
.bd-award-tag { font-size: var(--fs-11); color: var(--text-3); }
.bd-award-name { font-size: var(--fs-15); font-weight: var(--fw-medium); }
.bd-award-why { font-size: var(--fs-12); color: var(--text-2); margin-top: 2px; }

/* 解锁那一下：从上面落下来，图形放大后落定 */
.bd-toast {
  position: absolute; left: var(--sp-4); right: var(--sp-4); top: var(--sp-3); z-index: 30;
  display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-lg); background: var(--surface); box-shadow: var(--shadow-3);
  animation: bd-drop var(--dur-slow) var(--ease-out);
}
.bd-toast-glyph { animation: bd-pop calc(var(--dur-slow) * 2) var(--ease-out); }
.bd-toast-tag { font-size: var(--fs-11); color: var(--text-3); }
.bd-toast-name { font-size: var(--fs-17); font-weight: var(--fw-medium); }
.bd-toast-desc { font-size: var(--fs-12); color: var(--text-2); }
@keyframes bd-drop { from { transform: translateY(-120%); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes bd-pop { 0% { transform: scale(.2); } 60% { transform: scale(1.25); } 100% { transform: scale(1); } }
.conv { position: relative; }

.bd-hero { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); padding: var(--sp-6) 0 var(--sp-2); }
.bd-hero-face { width: 100%; height: 100%; border-radius: var(--r-full); background: var(--surface-2);
  display: flex; align-items: center; justify-content: center; }
.bd-hero-lv { font-size: var(--fs-17); font-weight: var(--fw-medium); margin-top: var(--sp-2); }
.bd-hero-sub { font-size: var(--fs-12); color: var(--text-3); }
.bd-forms { display: flex; justify-content: space-between; padding: var(--sp-3) var(--sp-4) var(--sp-4); }
.bd-form { display: flex; flex-direction: column; align-items: center; gap: 2px; font-size: var(--fs-12); color: var(--text-3); }
.bd-form.is-got { color: var(--text); }
.bd-form small { font-size: var(--fs-11); color: var(--text-3); }
.bd-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-2); padding: var(--sp-3); }
.bd-cell { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); text-align: center;
  padding: var(--sp-3) var(--sp-2); border-radius: var(--r-md); background: var(--surface-2); color: var(--text-3); }
.bd-cell.is-got { color: var(--text); }
.bd-cell b { font-size: var(--fs-13); font-weight: var(--fw-medium); }
.bd-cell small { font-size: var(--fs-11); color: var(--text-3); line-height: 1.4; }

.yr-head { display: flex; align-items: center; justify-content: space-between; padding: var(--sp-4); }
.yr-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-2); padding: 0 var(--sp-4) var(--sp-2); }
.yr-cell { display: flex; flex-direction: column; padding: var(--sp-4); border-radius: var(--r-lg); background: var(--surface-2); }
.yr-cell b { font-size: var(--fs-24); font-weight: var(--fw-medium); }
.yr-cell span { font-size: var(--fs-12); color: var(--text-2); }
.yr-note { padding: var(--sp-4); font-size: var(--fs-14); line-height: 1.7; white-space: pre-wrap; }

/* 那年今天。一行一条：谁、几点、说了什么。不画成气泡 —— 这是翻旧账，不是再聊一遍 */
.otd-back { display: flex; justify-content: center; margin-top: calc(var(--sp-2) * -1); padding-bottom: var(--sp-2); }
.otd-day { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); }
.otd-line { display: grid; grid-template-columns: auto 1fr; column-gap: var(--sp-2); row-gap: var(--sp-1); }
.otd-who { font-size: var(--fs-12); color: var(--text-2); }
.otd-line.is-mine .otd-who { color: var(--accent); }
.otd-time { font-size: var(--fs-12); color: var(--text-3); }
.otd-text { grid-column: 1 / -1; font-size: var(--fs-14); line-height: 1.6; white-space: pre-wrap; word-break: break-word; }

@media (prefers-reduced-motion: reduce) {
  .bd-g svg, .lv-orbit, .bd-toast, .bd-toast-glyph { animation: none !important; }
}

/* 分享的一首歌（chat/pages/SongCard.js）。会话气泡与朋友圈共用，照 Apple Music 的分享卡片。
   底色是封面主色按两成多混进面板底色（--song-tint 由脚本从封面取），取不到就是面板底色 */
.song-card {
  --song-bg: var(--surface-2);
  display: flex; text-align: left; color: var(--text); cursor: pointer;
  background: var(--song-bg); border-radius: var(--r-lg); overflow: hidden;
  -webkit-tap-highlight-color: transparent;
}
.song-card.is-tinted { --song-bg: color-mix(in srgb, var(--song-tint) 26%, var(--surface-2)); }
.song-card:active { filter: brightness(.97); }
.song-card.is-off { cursor: default; }
.song-card.is-off:active { filter: none; }
.song-art {
  flex: none; display: flex; align-items: center; justify-content: center; overflow: hidden;
  background: var(--surface-3); color: var(--text-3);
}
.song-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.song-art.is-pending { animation: song-wait 1.4s var(--ease-out) infinite alternate; }
@keyframes song-wait { from { opacity: 1; } to { opacity: .55; } }
.song-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.song-title { font-size: var(--fs-15); font-weight: var(--fw-semi); line-height: 1.35; }
.song-artist { font-size: var(--fs-13); color: var(--text-2); line-height: 1.35; }
.song-note { font-size: var(--fs-11); color: var(--text-3); line-height: 1.4; margin-top: 3px; }

/* 会话里：上封面、下文字 */
.song-card.is-tile { flex-direction: column; width: 216px; max-width: 100%; box-shadow: var(--shadow-1); }
.song-top { position: relative; display: block; }
.song-card.is-tile .song-art { width: 100%; aspect-ratio: 1 / 1; }
.song-card.is-tile .song-key { position: absolute; right: var(--sp-2); bottom: var(--sp-2); }
.song-foot { display: flex; padding: var(--sp-2) var(--sp-3) var(--sp-3); }

/* 朋友圈：一行 */
.song-card.is-row { align-items: center; gap: var(--sp-3); padding: var(--sp-2); width: 100%; }
.song-card.is-row .song-art { width: 52px; height: 52px; border-radius: var(--r-sm); }
.song-card.is-row .song-title { font-size: var(--fs-14); }
.song-card.is-row .song-key { margin-right: var(--sp-1); }

/* 圆形播放键。放着的时候外面一圈是进度 */
.song-key {
  position: relative; flex: none; width: var(--key); height: var(--key); border-radius: 50%;
  display: flex; align-items: center; justify-content: center; border: 0; padding: 0;
  background: var(--surface); color: var(--text); box-shadow: var(--shadow-2); cursor: pointer;
}
.song-key:disabled { opacity: .4; }
.song-key svg:not(.song-ring) { margin-left: 2px; }
.song-key.is-playing svg:not(.song-ring) { margin-left: 0; }
.song-ring { position: absolute; inset: -3px; width: calc(100% + 6px); height: calc(100% + 6px); transform: rotate(-90deg); pointer-events: none; }
.song-ring circle { fill: none; stroke: var(--surface); stroke-width: 2.4; }
.song-ring .song-ring-on { stroke: var(--text); transition: stroke-dashoffset .5s linear; }

/* 会话里这张不挂 .bubble：卡片自己就是那一块，两边一样，不跟着强调色变黑 */
.mo-song { margin-top: var(--sp-2); }
.mo-song-pick { display: flex; align-items: center; gap: var(--sp-2); margin: var(--sp-3) 0; }
.mo-song-pick .song-card { flex: 1; width: auto; }
.song-search { display: flex; gap: var(--sp-2); align-items: center; padding: 0 0 var(--sp-3); }
.song-search input { flex: 1; min-width: 0; }

@media (prefers-reduced-motion: reduce) {
  .song-art.is-pending { animation: none; }
  .song-ring .song-ring-on { transition: none; }
}
button.conv-notice { background: none; border: 0; font: inherit; cursor: pointer; }

/* 角色调用 MCP 工具的那一条（chat/pages/ToolBits.js） */
.tool-card { display: flex; flex-direction: column; gap: 4px; width: 260px; max-width: 100%; cursor: pointer; }
.tool-head { display: flex; align-items: center; gap: 6px; font-size: var(--fs-13); font-weight: var(--fw-semi); min-width: 0; }
.tool-name { flex: 1; min-width: 0; }
.tool-server { flex: none; max-width: 40%; font-size: var(--fs-11); font-weight: var(--fw-normal); color: var(--text-3); }
.tool-args { font-size: var(--fs-12); color: var(--text-2); }
.tool-state { display: flex; align-items: center; gap: 6px; font-size: var(--fs-12); color: var(--text-3); }
.tool-state.is-error { color: var(--danger); display: block; word-break: break-word; }
.tool-result {
  font-size: var(--fs-13); line-height: 1.5; white-space: pre-wrap; word-break: break-word;
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
  border-top: 1px solid var(--border); padding-top: 4px;
}
.tool-acts { display: flex; gap: var(--sp-2); justify-content: flex-end; }
.tool-detail { font-size: var(--fs-13); display: flex; flex-direction: column; gap: 4px; }
.tool-detail-k { font-size: var(--fs-11); color: var(--text-3); margin-top: var(--sp-2); }
.tool-detail pre {
  margin: 0; white-space: pre-wrap; word-break: break-word; font-size: var(--fs-12);
  background: var(--surface-2); border-radius: var(--r-sm); padding: var(--sp-2);
}

/* MCP app 的「试用」页（apps/mcp/ServersPage.js 的 ToolPage） */
.mcp-tool { display: flex; flex-direction: column; gap: var(--sp-2); }
.mcp-tool-meta { font-size: var(--fs-12); color: var(--text-3); }
.mcp-tool-desc { font-size: var(--fs-14); line-height: 1.6; }
.mcp-tool-k { font-size: var(--fs-11); color: var(--text-3); margin-top: var(--sp-3); }
.mcp-pre {
  margin: 0; white-space: pre-wrap; word-break: break-word; font-size: var(--fs-12);
  background: var(--surface-2); border-radius: var(--r-sm); padding: var(--sp-2);
}
.mcp-pre.is-error { color: var(--danger); }


/* 管理页：刚生成的账号与密码，整段可选中复制 */
.acct-out { font-size: var(--fs-13); line-height: 1.8; white-space: pre-wrap; word-break: break-all;
  background: var(--surface-2); border-radius: var(--r-sm); padding: var(--sp-3); margin: var(--sp-3) 0;
  max-height: 320px; overflow-y: auto; }

/* 自己的动态：请角色评论 */
.mo-ask { padding: var(--sp-3) 0; border-top: 1px solid var(--border); margin-top: var(--sp-2); }
.mo-ask-title { font-size: var(--fs-13); color: var(--text); font-weight: var(--fw-medium); }
.mo-ask-desc { font-size: var(--fs-12); color: var(--text-3); line-height: 1.5; margin: var(--sp-1) 0 var(--sp-2); }

/* 发布动态：谁可以看 */
.mo-vis { margin: var(--sp-3) 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.mo-vis-title { font-size: var(--fs-13); color: var(--text-2); }
.mo-vis-desc { font-size: var(--fs-12); color: var(--text-3); line-height: 1.5; }

/* ---- 聊天背景与上下栏（system/chatlook.js，ARCHITECTURE 4.198）----
   值由会话页以 --ph-* 挂在 .page 上；没设过的那一栏不挂类名，保持应用本来的样子。
   背景画在整页上（顶栏、输入栏底下也是它），遮罩朝当前主题的底色淡过去，
   浅色主题上是一层白、深色主题上是一层黑，气泡外那些灰字都还看得清 */
.page.has-chat-bg {
  background:
    linear-gradient(color-mix(in srgb, var(--bg) var(--ph-chat-veil, 0%), transparent),
                    color-mix(in srgb, var(--bg) var(--ph-chat-veil, 0%), transparent)),
    var(--ph-chat-bg, none) center / cover no-repeat,
    var(--bg);
}
/* 压在图上、不在气泡里的那几行字，垫一块底 */
.has-chat-bg .time-sep,
.has-chat-bg .conv-hint,
.has-chat-bg .conv-notice {
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  border-radius: var(--r-full); padding-inline: var(--sp-3);
}
.has-chat-bg .conv-notice {
  width: fit-content; max-width: 86%; margin-inline: auto; border-radius: var(--r-md);
}

/* 上下两栏。下面那一栏是输入框连同压在它上面的那几条（引用、待办、表情联想、@ 成员）
   以及展开的面板、多选栏 —— 看上去是同一块，一起换 */
.page.look-top > .navbar {
  background: var(--ph-bar-top, var(--bg));
  -webkit-backdrop-filter: blur(var(--ph-bar-top-blur, 0px));
  backdrop-filter: blur(var(--ph-bar-top-blur, 0px));
}
.page.look-bottom :is(.composer-bar, .composer-panel, .select-bar, .quote-bar,
                      .todo-bar, .stk-suggest, .mention-bar) {
  background: var(--ph-bar-bottom, var(--bg));
  -webkit-backdrop-filter: blur(var(--ph-bar-bottom-blur, 0px));
  backdrop-filter: blur(var(--ph-bar-bottom-blur, 0px));
}
/* 字色。深色的栏配浅字：栏里用到的几档字色与输入框底色就地换掉，
   里面的元素照旧读 --text 那几个，不必一个个改 */
.page.look-top-light > .navbar,
.page.look-bottom-light :is(.composer-bar, .composer-panel, .select-bar, .quote-bar,
                            .todo-bar, .stk-suggest, .mention-bar) {
  --text: var(--on-scrim);
  --text-2: color-mix(in srgb, var(--on-scrim) 78%, transparent);
  --text-3: color-mix(in srgb, var(--on-scrim) 58%, transparent);
  --surface-2: color-mix(in srgb, var(--on-scrim) 14%, transparent);
  --border: color-mix(in srgb, var(--on-scrim) 22%, transparent);
  color: var(--text);
}
.page.look-top-dark > .navbar,
.page.look-bottom-dark :is(.composer-bar, .composer-panel, .select-bar, .quote-bar,
                           .todo-bar, .stk-suggest, .mention-bar) {
  --text: var(--on-scrim-fill);
  --text-2: color-mix(in srgb, var(--on-scrim-fill) 72%, transparent);
  --text-3: color-mix(in srgb, var(--on-scrim-fill) 50%, transparent);
  --surface-2: color-mix(in srgb, var(--on-scrim-fill) 6%, transparent);
  --border: color-mix(in srgb, var(--on-scrim-fill) 14%, transparent);
  color: var(--text);
}

/* 调节面板 */
.look-colors { display: flex; flex-wrap: wrap; gap: var(--sp-2); padding: var(--sp-2) var(--sp-4) var(--sp-3); }
.look-color {
  width: 30px; height: 30px; border-radius: var(--r-full);
  border: 1px solid var(--border-2); background: var(--c, var(--bg));
  position: relative;
}
.look-color.is-on { outline: 2px solid var(--accent); outline-offset: 2px; }
.look-color.is-theme::after {
  content: ''; position: absolute; inset: 6px; border-radius: var(--r-full);
  border: 1px dashed var(--text-3);
}
.look-color input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.look-bg {
  display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4);
}
.look-bg-thumb {
  width: 56px; height: 84px; flex: none; border-radius: var(--r-sm);
  background: var(--surface-2) center / cover no-repeat; background-image: var(--thumb, none);
  display: flex; align-items: center; justify-content: center; color: var(--text-3);
}
.look-bg-acts { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
/* 一组列表里，某一行下面自己带一排东西（颜色块）时的那行小标题 */
.list-sub-title { font-size: var(--fs-14); padding: var(--sp-3) var(--sp-4) 0; }

/* 输入栏那一组（会话页底部）。平时就是按顺序叠着，和从前一样 */
.conv-foot { flex: none; display: flex; flex-direction: column; }

/* ---- 栏浮在消息列表上（聊天背景里栏不是实色，system/chatlook.js）----
   消息从栏底下滚过去，半透明、毛玻璃才透得出东西。
   顶栏高度是定的，直接算；输入栏那一组会变高，会话页量出来挂成 --foot-h */
.page.float-top > .navbar {
  position: relative; z-index: 3;
  margin-bottom: calc(-1 * (var(--navbar-h) + var(--top-inset)));
}
/* 会话顶上那几条（一起听、一起看、节奏）照旧排在顶栏下面；没有它们时，列表一直伸到顶栏底下 */
.page.float-top .conv { padding-top: calc(var(--navbar-h) + var(--top-inset)); }
.page.float-top .conv > .conv-main:first-child { margin-top: calc(-1 * (var(--navbar-h) + var(--top-inset))); }
.page.float-top .conv > .conv-main:first-child > .conv-body {
  padding-top: calc(var(--navbar-h) + var(--top-inset) + var(--sp-4));
}
.page.float-bottom .conv-foot {
  position: relative; z-index: 3;
  margin-top: calc(-1 * var(--foot-h, 0px));
}
.page.float-bottom .conv-body { padding-bottom: calc(var(--foot-h, 0px) + var(--sp-4)); }
.page.float-bottom .to-bottom { bottom: calc(var(--foot-h, 0px) + var(--sp-3)); }

/* ---- 角色卡展示页：拍立得（chat/pages/CharCard.js）----
   相纸是 --surface，深色主题下跟着变深。微微斜一点，像随手放在桌上的一张 */
.pola-stage { display: flex; justify-content: center; padding: var(--sp-6) var(--sp-5) var(--sp-4); }
.pola {
  width: 78%; max-width: 320px; display: block; text-align: center;
  background: var(--surface); border: 1px solid var(--border);
  padding: var(--sp-3) var(--sp-3) 0; border-radius: var(--r-xs);
  box-shadow: var(--shadow-3); transform: rotate(-2deg);
}
.pola-photo {
  aspect-ratio: 4 / 5; width: 100%;
  background: var(--surface-2) center / cover no-repeat; background-image: var(--pola, none);
  display: flex; align-items: center; justify-content: center;
}
.pola-hint { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); color: var(--text-3); font-size: var(--fs-13); }
.pola-foot { padding: var(--sp-4) var(--sp-2) var(--sp-5); }
/* 个性签名用手写体放大写在白边上；下面一行小小的 @名字，像照片背后顺手签的名 */
.pola-sign { font-family: var(--font-hand); font-size: var(--fs-24); color: var(--text); line-height: 1.35; word-break: break-word; }
.pola-at { font-size: var(--fs-11); color: var(--text-3); margin-top: var(--sp-2); text-align: right; letter-spacing: .5px; }
.hand-sample { font-family: var(--hand), var(--font-hand-base); font-size: var(--fs-20); color: var(--text); }

/* 朋友圈评论表：输入框在最上面（MomentBits.js 的 CommentSheet），键盘只盖得住下半截 */
.mo-comment-input { padding: 0 0 var(--sp-3); border-bottom: 1px solid var(--border); }
.mo-comment-list { padding-top: var(--sp-3); }
