/* ==========================================================================
   她 · PWA 界面样式
   --------------------------------------------------------------------------
   风格：极简 / 单向素色 / 大量留白 / 新拟物（neumorphism）
   三条铁律（写代码时反复回看的）：
     1. 不靠颜色区分层次，靠"双色阴影 + 留白"——所以全篇几乎没有彩色，
        连"成功/失败"都尽量用文字说，不用红绿。
     2. 阴影是双向的：左上亮（模拟主光）+ 右下暗（模拟投影）。
        两者必须同源同比例，否则立刻变成"贴纸"而不是"浮雕"。
     3. 圆角很大（气泡 22~28px、卡片 24~32px），且**不用边框**。
        新拟物里加 1px 边框 = 前功尽弃。
   ========================================================================== */

/* ---------- 设计变量：所有阴影/圆角都从这里取，改一处全站一致 ---------- */
:root {
  /* 底色比纯白低一档，亮阴影才有"打在表面上"的余地 */
  --bg: #ececec;
  --bg-soft: #f0f0f0;

  /* ======================================================================
     ★★★ 配色（2026-10-04 用户给了 5 组参考图之后加的）
     ----------------------------------------------------------------------
     ⚠️ **之前全篇是"不引彩色"的** —— 那条是我照第一张风格例图定的。
        用户后来给了 5 组**撞色**参考，并明确要求"改一下配色"，
        所以现在：**结构仍照例图（新拟物），但每个界面一套彩色强调色**。

     每组取 3 个值：
       --accent        强调色（按钮、选中、重点数字）
       --accent-soft   强调色的浅版（背景块、标签底）
       --accent-ink    压在 accent 上的文字色

     ⚠️ **默认那套是"中性"**（和以前一样，不引人注意）——
        具体界面会覆盖它（见文件末尾的分屏配色）。
     ====================================================================== */
  --accent: #6c6c6c;
  --accent-soft: #e4e4e4;
  --accent-ink: #ffffff;
  /** 强调色的投影（按钮浮起来那圈用得上） */
  --accent-glow: rgba(0, 0, 0, 0.12);
  /**
   * **深色内嵌面**（2026-10-04 照风格例图加）。
   *
   * 例图里有三档灰度：① 浅底 ② 更亮的凸起面 ③ **明显的深色内嵌卡**
   * （黑胶唱机那块、波形图那块）。现在的 `--bg-soft` 和 `--bg` 只差一点点，
   * 所以"凹陷"看着不够塌 —— 加这一档专门给**输入轨道 / 预览区 / 时间轴**用。
   *
   * ⚠️ 仍然是灰度、不引彩色（全篇铁律）。
   */
  --bg-sunken: #e3e3e3;
  /** 最深那一档：只给"内容卡"（例图里的黑胶那块），用得越少越有力 */
  --bg-deep: #d8d8d8;

  /* 凸起（浮起来的面） */
  --sh-out: -6px -6px 14px rgba(255, 255, 255, 0.85), 7px 7px 16px rgba(0, 0, 0, 0.1);

  /* 凹陷（按下去的面：输入框、头像底、按钮按下时） */
  --sh-in: inset -4px -4px 10px rgba(255, 255, 255, 0.9), inset 5px 5px 11px rgba(0, 0, 0, 0.075);

  /**
   * **深凹陷**（2026-10-04 照例图加）：给"深色内嵌卡"用。
   * 比 `--sh-in` 的暗部更重一点 —— 底本来就深，阴影不再重一点就"塌不下去"。
   */
  --sh-in-deep: inset -3px -3px 8px rgba(255, 255, 255, 0.72), inset 5px 5px 13px rgba(0, 0, 0, 0.1);

  /* 小控件用的小号凸起，阴影半径跟着尺寸缩，不然会糊成一团 */
  --sh-out-sm: -3px -3px 8px rgba(255, 255, 255, 0.88), 4px 4px 9px rgba(0, 0, 0, 0.1);

  /* 贴在灰底上的浮层（菜单/提示条）需要更大的落差才浮得起来 */
  --sh-float: -8px -8px 20px rgba(255, 255, 255, 0.9), 10px 10px 26px rgba(0, 0, 0, 0.13);

  --ink: #2a2a2a;        /* 正文：深灰，不用纯黑（纯黑在灰底上太硬） */
  --ink-2: #6c6c6c;      /* 次要文字 */
  --ink-3: #8a8a8a;      /* 时间、声明这类"可以忽略"的信息 */
  --ink-4: #a9a9a9;      /* 占位符 */

  --r-bubble: 24px;
  --r-card: 28px;
  --r-pill: 999px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1); /* 克制：起步快、收尾慢，不弹跳 */
  --dur: 200ms;

  /* 标题字重偏轻、字号偏大 —— 极简感一大半来自这里 */
  --fs-body: 16px;

  color-scheme: light;
}

/* ---------- 基础 ---------- */
* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
}

body {
  /**
   * 🚨 **这里原来是 `background: var(--bg)`（不透明的灰）—— 2026-10-05 去掉了**。
   *
   * ⚠️ 因为背景图现在画在 **`html` 元素**上（见文件末尾那一大段说明）。
   *    `body` 是 `html` 的子元素 —— **它一不透明就把那层图整个挡掉**
   *    （这正是"变量全对、图也 200、就是看不见"的原因之一）。
   *
   * ⚠️ 那个灰底现在由 `html` 上的 `background-color` 承担 ——
   *    所以**没配背景的屏看起来和以前一模一样**（还是灰底）。
   *
   * 🚨🚨 **而且必须是 `background-color`，不能是 `background` 简写**（2026-10-05 再修一次）。
   *
   * ⚠️ 简写会把 **`background-image`** 一起重置成 `none` ——
   *    也就是说：**这一条会盖掉"给 body 设背景图"的任何规则**
   *    （除非那条特异性更高）。
   *
   * ⚠️ 它在 `manga.css` **之前**加载，所以目前是 `manga.css` 里那条
   *    `body { background-image: var(--screen-bg) }` 顶着。
   *    ⚠️ 但"靠加载顺序"是**赌**，不是保证：哪天有人调换位置、
   *      或者加一条同样特异性的规则，**屏幕背景又会全看不见**（而且不报错）。
   *
   * ⚠️ 这条铁律项目里一直写着：「**绝不用 `background:` 简写**」——
   *    它能重置的东西比你以为的多（image / position / size / repeat /
   *    attachment / origin / clip / color）。
   *    ⚠️ **要改颜色就只写 `background-color`。**
   */
  background-color: transparent;
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB",
    "Microsoft YaHei", "Noto Sans SC", "Source Han Sans SC", system-ui, sans-serif;
  font-size: var(--fs-body);
  font-weight: 350; /* 偏轻，但不至于细到手机上发虚（300 在低 DPI 屏会发灰） */
  line-height: 1.62;
  letter-spacing: 0.01em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden; /* 页面本身不滚，滚动只发生在消息区 */
  overscroll-behavior: none; /* 关掉下拉刷新/橡皮筋，聊天列表自己管滚动 */
  -webkit-tap-highlight-color: transparent;
}

/* 两团极淡的光晕，给纯灰底一点纵深；用了 fixed + blur，滚动时不动，避免重绘 */
.glow {
  position: fixed;
  border-radius: 50%;
  pointer-events: none;
  filter: blur(46px);
  opacity: 0.55;
  z-index: 0;
}
.glow-a {
  width: 46vw;
  height: 46vw;
  top: -14vw;
  left: -10vw;
  background: radial-gradient(circle at 40% 40%, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0) 68%);
}
.glow-b {
  width: 40vw;
  height: 40vw;
  bottom: -12vw;
  right: -12vw;
  background: radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0) 66%);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- 屏幕（登录 / 聊天）切换 ---------- */
.screen {
  position: relative;
  z-index: 1;
}
.screen[hidden] {
  display: none !important;
}

/* ==========================================================================
   登录页
   ========================================================================== */
#screen-login {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 上下都留安全区，横屏/小屏时不会顶到刘海 */
  padding: calc(24px + env(safe-area-inset-top)) 22px calc(24px + env(safe-area-inset-bottom));
}

.login-wrap {
  width: 100%;
  max-width: 380px;
  text-align: center;
  /* 大量留白：靠间距撑气场，不靠装饰 */
}

.brand {
  width: 104px;
  height: 104px;
  margin: 0 auto 26px;
  display: grid;
  place-items: center;
  color: var(--ink-2);
  border-radius: 34px;
  background: var(--bg);
  box-shadow: var(--sh-out);
}

/**
 * ★ **Kin 的标记**（2026-10-05 晚报换成用户给的那张 logo）。
 *
 * ⚠️ 那张源图是 **JPEG（没有透明通道）** ⇒ 成品是**白底**。
 *    ⚠️ 直接贴上去会在圆角块的灰底上留一个**白方块**——
 *       所以这里给它同样的圆角 + 白底，看起来是"印在纸上的一块"。
 * ⚠️ 尺寸比外面的方块小一圈（76 / 104），留出边距 —— 原图四周本来就有留白。
 */
.brand-mark {
  width: 76px;
  height: 76px;
  border-radius: 24px;
  background: #fff;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
}

/**
 * ★★★ **动态页那条分界线**（2026-10-05 晚报第三批，用户要求"显眼点的分界线"）。
 *
 * 用户原话：
 *   > 把「替她发」移到自己发的下面，然后**加个显眼点的分界线**，**下面才是动态**
 *
 * ⚠️ 上半屏是两个输入框、下半屏是动态流 —— 没有这条线，用户分不清
 *    "我刚发的那条在哪"（会以为下面那一堆也是输入区）。
 * ⚠️ 所以它要**粗**（2.5px 黑线 + 中间一个标签），不能是淡淡的 `<hr>`。
 * ⚠️ 两套主题共用这一条（它是"结构"不是"风格"）—— 所以**不带 `html[data-ui]` 前缀**。
 */
.feed-sep {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 18px 4px 6px;
}
.feed-sep-line {
  flex: 1 1 auto;
  height: 0;
  border-top: 2.5px solid var(--ink, #111);
  opacity: 0.55;
}
.feed-sep-label {
  flex: 0 0 auto;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.14em;
  color: var(--ink, #111);
  opacity: 0.75;
}

/**
 * ★ **「我自己的 API key」那一小块**（2026-10-05 晚报）。
 *
 * ⚠️ 它里面用的 `.field-input` / `.btn-row` / `.field-count` **都是现成的** ——
 *    这里只补"三个元素之间要有点间距"（不加的话那行说明小字会**贴着输入框**，
 *    就是用户这轮反复在说的"贴太紧/粘连"那个毛病）。
 * ⚠️ 间距写在**容器**上（`> * + *`），别写进 `.field-input` ——
 *    那个 class 全站都在用，改它会连带动十几个界面。
 */
.myai-key {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.myai-key > .field-count {
  margin: 0;
}

.login-title {
  margin: 0 0 8px;
  font-size: 28px;
  font-weight: 300; /* 标题比正文还轻，是这套风格的签名 */
  letter-spacing: 0.06em;
}

.login-sub {
  margin: 0 0 26px;
  font-size: 14px;
  color: var(--ink-3);
  font-weight: 350;
}

.login-form {
  display: flex;
  align-items: center;
  gap: 12px;
}

/**
 * ★ **登录页：用户名 + 密码那两块**（2026-10-05 晚报第三批，"真注册"）。
 *
 * ⚠️ 两个输入框要**竖着排**（横排的话在手机上会被挤成两根牙签）——
 *    所以外面这层是 `column`，而 `.login-form` 仍是横排（输入区 + 那个圆按钮）。
 */
.login-fields {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.login-fields[hidden] {
  display: none;
}

/**
 * ★ **"输入框 + 一个小按钮"那一行**（2026-10-06，注册绑邮箱用）。
 * ⚠️ 邮箱那栏和验证码那栏都是这个形状（横排：输入框占满 + 按钮定宽）。
 * ⚠️ 按钮用 `.sheet-btn`（那个类**必须**待在行容器里，不然会贴边 ——
 *    这个坑这个项目踩过四次了）。
 */
.login-inline {
  display: flex;
  align-items: center;
  gap: 8px;
}
.login-inline[hidden] {
  display: none;
}
.login-inline .field {
  flex: 1 1 auto;
  min-width: 0;
}
.login-code-btn {
  flex: 0 0 auto;
  white-space: nowrap;
  font-size: 13.5px;
  padding: 0 14px;
  height: 44px;
}
.login-code-btn:disabled {
  opacity: 0.5;
}
/**
 * ★ 验证码那一行的提示（"发到 xxx 了" / "验证通过"）。
 * ⚠️ 它要给**两种状态**上色（成功绿、失败红）—— 所以两个修饰类都要有。
 */
.login-code-hint {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-3);
  text-align: center;
}
.login-code-hint[hidden] {
  display: none;
}
.login-code-hint.is-ok {
  color: #1a7f4b;
}
.login-code-hint.is-bad {
  color: #c2382f;
}

/**
 * ★ **登录 ↔ 注册 ↔ 用口令 的切换**（三个文字按钮）。
 * ⚠️ "还没有账号？注册一个"那一句**只在注册开着的时候**才显示（后端可以关掉它）。
 */
.login-switch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  margin-top: 14px;
}
.login-link {
  appearance: none;
  border: 0;
  background: none;
  padding: 4px 8px;
  font: inherit;
  font-size: 13.5px;
  color: var(--ink-3);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.login-link:active {
  color: var(--ink);
}
.login-link[hidden] {
  display: none;
}
/**
 * ★★ **"用密码 / 用验证码"那两个切换按钮**（2026-10-06 用户要求）。
 *
 * ⚠️ 它们是**横排**的两个（不是像"注册/口令"那样竖着堆）——
 *    因为它们是**二选一的选项卡**，横排才读得出"选一个"。
 * ⚠️ 选中的那个要**看得出被选中**（不只是加粗）：漫画风里用"墨色实心块"，
 *    极简风里用"深色 + 下划线"。
 */
.login-switch--tabs {
  flex-direction: row;
  gap: 8px;
  margin-top: 0;
  margin-bottom: 14px;
}
.login-switch--tabs[hidden] {
  display: none;
}
.login-switch--tabs .login-link.is-on {
  color: var(--ink);
  text-decoration-thickness: 3px;
  font-weight: 700;
}
/* ⚠️ 漫画风：选中的那个做成"墨色小块" */
html[data-ui='manga'] .login-switch--tabs .login-link.is-on {
  background: var(--manga-ink);
  color: var(--manga-paper);
  text-decoration-color: transparent;
  border-radius: 10px;
}

/* 输入框做成"凹进去"的，和凸起的按钮形成对照 —— 新拟物的基本语法 */
.field {
  flex: 1 1 auto;
  min-width: 0;
  height: 54px;
  padding: 0 20px;
  border: 0;
  outline: 0;
  border-radius: var(--r-pill);
  background: var(--bg);
  box-shadow: var(--sh-in);
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  letter-spacing: 0.04em; /* 口令是一串字符，加点字距更好认 */
}
.field::placeholder {
  color: var(--ink-4);
  letter-spacing: 0.02em;
}
/* 焦点态：不描边、不变色，只把凹陷加深一点点，靠"深浅"表达聚焦 */
.field:focus {
  box-shadow: inset -3px -3px 8px rgba(255, 255, 255, 0.85), inset 6px 6px 14px rgba(0, 0, 0, 0.095);
}

.login-error {
  min-height: 22px; /* 占位：出错时不跳动布局 */
  margin: 16px 4px 0;
  font-size: 13.5px;
  color: var(--ink-2);
  text-align: center;
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
.login-error.is-on {
  opacity: 1;
}
/* 错误用"加个左边的小横杠"表达，而不是变红 —— 无彩色约束下的替代方案 */
.login-error.is-on::before {
  content: "";
  display: inline-block;
  width: 14px;
  height: 1.5px;
  margin-right: 8px;
  vertical-align: 4px;
  background: var(--ink-3);
}

.login-foot {
  margin: 34px 0 0;
  font-size: 12px;
  color: var(--ink-4);
  letter-spacing: 0.04em;
}

/* ==========================================================================
   圆形 / 胶囊按钮
   ========================================================================== */
.round-btn {
  flex: 0 0 auto;
  width: 54px;
  height: 54px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--bg);
  box-shadow: var(--sh-out);
  color: var(--ink);
  cursor: pointer;
  transition: box-shadow 160ms var(--ease), transform 160ms var(--ease), color 160ms var(--ease),
    opacity 160ms var(--ease);
}
.round-btn:hover:not(:disabled) {
  color: #1f1f1f;
}
/* 按下去 = 从"凸起"翻成"凹陷"，这是全篇唯一允许的强反馈 */
.round-btn:active:not(:disabled) {
  box-shadow: var(--sh-in);
  transform: translateY(1px);
}
.round-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  box-shadow: var(--sh-out-sm);
}
.round-btn:focus-visible {
  outline: 2px solid rgba(0, 0, 0, 0.22);
  outline-offset: 3px;
}

/* 无彩色：强调按钮只是稍微深一点的墨色，不引入主色 */
.round-btn--accent {
  color: #3a3a3a;
}

/* ==========================================================================
   媒体入口（图片 / 拍照 / 语音）—— 2026-10-04 PWA 第 1 步
   --------------------------------------------------------------------------
   风格约束（沿用全篇三条铁律）：
     · **不引彩色** —— 连"录音中"也不用红点，改用凹陷 + 轻微脉动
     · **靠双色阴影 + 留白**分层，不加边框
     · 圆角一律偏大
   ========================================================================== */

/* 次要按钮：比主按钮小一号、阴影更浅 —— 让"发送"仍是视觉主角 */
.round-btn--ghost {
  width: 44px;
  height: 44px;
  color: var(--ink-2);
  box-shadow: var(--sh-out-sm);
}
.round-btn--ghost svg {
  width: 20px;
  height: 20px;
}
.round-btn--ghost:active:not(:disabled) {
  box-shadow: var(--sh-in);
}

/* 录音中：不引红色，用"按下去的凹陷 + 缓慢脉动"表示它在工作 */
.round-btn--ghost.is-recording {
  box-shadow: var(--sh-in);
  color: #1f1f1f;
  animation: rec-pulse 1.4s ease-in-out infinite;
}
@keyframes rec-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}
/* 尊重"减少动态效果"的系统设置（前庭敏感的人会晕） */
@media (prefers-reduced-motion: reduce) {
  .round-btn--ghost.is-recording { animation: none; }
}

/* ---------- 待发附件预览 ---------- */
/**
 * 照例图：附件预览也是一条**凹陷轨道**里的胶囊组，
 * 不是散落的独立小方块 —— 散着放会显得"东西掉在输入框上面"。
 */
.attach-preview {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 8px;
  padding: 8px 10px;
  border-radius: var(--r-card);
  background: var(--bg-sunken);
  box-shadow: var(--sh-in-deep);
}
/**
 * ★★★ **`[hidden]` 必须显式处理**（2026-10-04 抓到的真 bug）。
 *
 * ## 为什么
 *
 * 浏览器的默认样式里有 `[hidden] { display: none }`，但它的优先级**很低** ——
 * 上面那条 `.attach-preview { display: flex }` 是**作者样式**，
 * 会把它**盖掉**。于是 `<div hidden>` **照样显示**，
 * 只是里面空空的 → 用户看到输入框上方挂着一个**空的凹陷胶囊**。
 *
 * ## 项目里的先例
 *
 * 这类地方**都专门写过一条**：`.screen[hidden]`、`.menu[hidden]`、
 * `.jump[hidden]`、`.banner[hidden]` —— 就 `attach-preview` 漏了。
 * 漏的原因很清楚：它一开始没有 `display` 声明（是块级），
 * 后来我为了"芯片横排"加了 `display: flex`，**那条默认就失效了**。
 *
 * ⚠️ **凡是给一个"可能带 `hidden` 的元素"加 `display` 的地方，
 *    都必须同时补一条 `[hidden]`** —— 否则就是这种"幽灵元素"。
 */
.attach-preview[hidden] {
  display: none;
}
.attach-chip {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 8px 5px 5px;
  border-radius: var(--r-pill);
  /* 凸起面（嵌在凹陷轨道里）—— 例图里那两颗圆形按钮就是这个关系 */
  background: var(--bg-soft);
  box-shadow: var(--sh-out-sm);
  max-width: 46vw;
}
.attach-thumb {
  width: 44px;
  height: 44px;
  object-fit: cover;
  border-radius: 12px;
  /* 图片底色：透明 PNG 在灰底上也能看清边界 */
  background: var(--bg-soft);
}
.attach-icon {
  font-size: 18px;
  line-height: 1;
  padding: 0 2px;
}
.attach-name {
  font-size: 13px;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 30vw;
}
.attach-del {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--bg);
  box-shadow: var(--sh-out-sm);
  color: var(--ink-2);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.attach-del:active {
  box-shadow: var(--sh-in);
}

/* ---------- 气泡里的媒体 ---------- */
.media-box {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.media-img {
  display: block;
  max-width: min(62vw, 320px);
  max-height: 380px;
  width: auto;
  height: auto;
  border-radius: 18px;
  background: var(--bg-soft);
  cursor: zoom-in;
  /* 图片加载中是塌的，给个最小高度免得气泡跳一下 */
  min-height: 60px;
}
/* 点一下放大（不做灯箱：灯箱要处理滚动锁定/返回键，第一版不值） */
.media-img.is-zoomed {
  max-width: min(92vw, 720px);
  max-height: none;
  cursor: zoom-out;
}
.media-audio {
  width: min(58vw, 260px);
  height: 36px;
}
.media-broken {
  display: inline-block;
  padding: 6px 10px;
  border-radius: 12px;
  background: var(--bg);
  box-shadow: var(--sh-in);
  color: var(--ink-3);
  font-size: 13px;
}
/* 有媒体时，文字部分单独占一行（别和图片挤在一行里） */
.bubble-text {
  margin-top: 6px;
}

/* 拖拽图片进来时给个整屏提示：不然浏览器会直接打开那张图，聊天页就没了 */
.screen.app.is-dragover::after {
  content: '松手就发给我';
  position: absolute;
  inset: 12px;
  display: grid;
  place-items: center;
  border-radius: var(--r-card);
  background: rgba(236, 236, 236, 0.86);
  box-shadow: var(--sh-in);
  color: var(--ink-2);
  font-size: 15px;
  pointer-events: none;
  z-index: 5;
}

.icon-btn {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--bg);
  box-shadow: var(--sh-out-sm);
  color: var(--ink-3);
  cursor: pointer;
  transition: box-shadow 160ms var(--ease), color 160ms var(--ease);
}
.icon-btn:active {
  box-shadow: var(--sh-in);
  color: var(--ink);
}
.icon-btn:focus-visible {
  outline: 2px solid rgba(0, 0, 0, 0.22);
  outline-offset: 3px;
}

/* ==========================================================================
   ★★★ 顶栏的「←/→」（2026-10-06 用户要求）
   --------------------------------------------------------------------------
   ⚠️ 和 `.icon-btn` 的区别：它们**成对出现、而且可能都藏着** ——
      都藏起来的时候标题要**自动贴到最左边**（不然会留一块空白）。
   ========================================================================== */
.nav-btn {
  /** ⚠️ 比关闭键小一点（它们是"辅助导航"，不该和主操作一样重） */
  width: 34px;
  height: 34px;
  /** ⚠️ 无底色（顶栏已经有底了，再加一层圆底会显得很挤） */
  background: transparent;
  box-shadow: none;
  color: var(--ink-2);
}
.nav-btn:active {
  background: var(--bg-soft);
  box-shadow: none;
}
/**
 * ⚠️ **第一个导航键要把标题挤开** ——
 *    `.home-title` 是 `flex: 1 1 auto`，它会把右边那些键推到最右边；
 *    而"返回键"应该紧挨着标题的**左侧**（那是所有人习惯的位置）。
 *    ⇒ 给 `[data-nav="back"]` 一点右边距，别让标题贴上去。
 */
.bar [data-nav="back"] {
  margin-right: 2px;
}
/**
 * ⚠️ **两个都藏着的时候，标题要贴左边** ——
 *    原来标题有 `margin-left: 6px`（那是给"没有导航键时"留的呼吸），
 *    有返回键时那 6px 加上键的宽度会让标题看着"被推了一下"。
 */
.bar:has([data-nav="back"]:not([hidden])) .home-title {
  margin-left: 0;
}

/* ==========================================================================
   聊天页骨架
   ========================================================================== */
.app {
  height: 100vh;
  /* dvh 修 iOS/安卓地址栏收缩导致的高度跳变；老浏览器忽略这行，退回上面的 vh。
     --vvh 由 JS 用 visualViewport 写入，专门修"键盘弹出时输入框被顶出屏幕"。 */
  height: 100dvh;
  height: var(--vvh, 100dvh);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* ---------- 顶栏 ---------- */
.bar {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: calc(10px + env(safe-area-inset-top)) 14px 10px calc(16px + env(safe-area-inset-left));
  min-height: 52px;
}

.bar-status {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 2px 6px;
  font-size: 13px;
  color: var(--ink-3);
  letter-spacing: 0.02em;
}

/**
 * 顶栏里的每一段状态文字。
 *
 * ⚠️⚠️ **原来是 `white-space: nowrap`，窄屏上会压到右边那排按钮**（2026-10-04）。
 *
 * 根因：`.bar-status` 虽然写了 `flex-wrap: wrap`，但**子项内部不换行** ——
 * 于是「正在连接…」这种较长的文字不会折到下一行，而是**直接溢出**，
 * 压住右上角的房子/电话/菜单三个图标。用户截图里那次"重叠"就是它。
 *
 * 修法：允许折行 + 允许收缩（`min-width: 0` 是 flex 子项能真正收缩的前提，
 * 少了它 `flex-shrink` 等于没写 —— 这个坑项目里记过好几次）。
 */
.meta {
  min-width: 0;
  overflow-wrap: anywhere;
}
/* 连接状态比"她睡没睡"更重要，给深一点的墨色 */
.meta-conn {
  color: var(--ink-2);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* 小圆点：新拟物的"凹槽里的珠子"——外圈凹陷 + 珠子本身极淡凸起 */
.conn-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--bg);
  box-shadow: inset -1px -1px 2px rgba(255, 255, 255, 0.9), inset 1.5px 1.5px 3px rgba(0, 0, 0, 0.18);
  transition: background 200ms var(--ease), box-shadow 200ms var(--ease);
}
.is-online .conn-dot {
  background: #9a9a9a; /* "在线" = 实心灰点，不用绿色 */
  box-shadow: inset -1px -1px 2px rgba(255, 255, 255, 0.6), 0 0 0 3px rgba(0, 0, 0, 0.035);
}
.is-offline .conn-dot {
  background: transparent;
}

.dot-sep {
  color: #c9c9c9;
  user-select: none;
}
/* 状态项为空时（还没收到 status）连分隔符一起藏，避免出现"｜ ｜" */
.dot-sep.is-hidden {
  display: none;
}

/* ---------- 顶栏右侧菜单 ---------- */
/**
 * ## 🚨 **`position: fixed` 不能少**（2026-10-05 改的）
 *
 * 原来是 `absolute`（相对聊天页那个头部定位）。而设置齿轮现在在**主页**上 ——
 * `openSettings()` 会让聊天页**闪一下再藏回去**（为了复用原来那套定位）。
 *
 * ⚠️ 而 `absolute` 的元素**挂在被 `hidden` 的父层里就没尺寸了**
 *    （实测 `rect = 0,0 0x0`）→ 表现是"点了齿轮没反应"。
 *
 * → 改成 `fixed`（相对**视口**定位），坐标由 `openSettings()` 算好写进
 *   `style.left/top`。这样它藏在哪个父层里都无所谓。
 */
.menu {
  position: fixed;
  z-index: 90;
  min-width: 176px;
  padding: 8px;
  border-radius: 22px;
  background: var(--bg-soft);
  box-shadow: var(--sh-float);
  display: flex;
  flex-direction: column;
  gap: 4px;
  animation: pop 160ms var(--ease);
  /**
   * ★★ **高度上限 + 可滚**（2026-10-05 晚报第三批）。
   *
   * ⚠️ 这个菜单有 **11 项**，实测在小屏（430×900）上**高 691px** ——
   *    而它是**贴着"⋯"按钮往下摆**的（按钮在顶部）⇒ 底边会到 750，
   *    在更矮的屏幕上（比如横屏、或者下面还有输入区）就**放不下了**。
   * ⚠️ 放不下的表现不是"报错"，是**菜单底下的几项点不到**（被屏幕切掉），
   *    而且**不报任何错**。
   * ⚠️ `dvh` 而不是 `vh` —— 手机浏览器地址栏收起来时 `vh` 会算错（偏大）。
   *    带 `vh` 兜底给老浏览器。
   */
  max-height: calc(100vh - 24px);
  max-height: calc(100dvh - 24px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
/**
 * ⚠️ **`[hidden]` 要真藏住** —— `.menu` 自己写了 `display: flex`，
 *    那个会**盖过** `[hidden]` 的默认 `display: none`（浏览器默认样式的优先级最低）。
 *    不写这条的话菜单**永远是开着的**。
 */
.menu[hidden] {
  display: none;
}
.menu[hidden] {
  display: none;
}
.menu-item {
  padding: 11px 14px;
  border: 0;
  border-radius: 15px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  font-weight: 350;
  text-align: left;
  cursor: pointer;
  transition: box-shadow 160ms var(--ease), background 160ms var(--ease);
}
.menu-item:hover {
  box-shadow: var(--sh-out-sm);
}
.menu-item:active {
  box-shadow: var(--sh-in);
}

@keyframes pop {
  from {
    opacity: 0;
    transform: translateY(-6px) scale(0.985);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ==========================================================================
   消息列表
   ========================================================================== */
.list {
  flex: 1 1 auto;
  min-height: 0; /* 关键：不加这条，flex 子项会被内容撑开，列表就滚不起来了 */
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain; /* 滚到底不要再把滚动传给 body */
  scrollbar-gutter: stable;
  padding: 8px 16px 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  outline: none;
}
.list::-webkit-scrollbar {
  width: 8px;
}
.list::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, 0.09);
  border-radius: 999px;
}
.list::-webkit-scrollbar-thumb:hover {
  background: rgba(0, 0, 0, 0.15);
}

/* 空状态：不画插画，只留两行字 */
.empty {
  margin: auto;
  text-align: center;
  color: var(--ink-3);
  padding: 30px 0 60px;
}
.empty-title {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 350;
  color: var(--ink-2);
}
.empty-sub {
  margin: 0;
  font-size: 13.5px;
  color: var(--ink-4);
}

/* ---------- 气泡行 ---------- */
/**
 * ★★★ **改成横排**（2026-10-05 晚报用户要求「聊天页面添加上自己的头像和对方的头像，用圆形的」）。
 *
 * ## ⚠️ 结构（`buildRow()` 里定的）
 * ```
 * .row  ← 横排
 *   ├─ .row-av     圆形头像
 *   └─ .row-stack  竖排：气泡 + 元信息（时间/发送中/重发）
 * ```
 * ⚠️ **元信息必须在 `stack` 里**，不能是 `row` 的兄弟 ——
 *    不然它会被当成第三个横排项、跑到头像右边。
 *
 * ⚠️ 左右顺序用 **`order`**（不是"插前面/插后面"）：
 *    两套主题的 `flex-direction` 将来可能不一样，`order` 不怕那个。
 */
.row {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 8px;
  max-width: 88%;
  animation: rise 200ms var(--ease);
}
/** 气泡 + 元信息那一竖列 */
.row-stack {
  display: flex;
  flex-direction: column;
  min-width: 0;
  max-width: 100%;
}
/**
 * ★ 圆形头像（用户明确要"圆形的"）。
 *
 * ⚠️ `flex: 0 0 auto` —— 不加的话头像会被 flex **压扁**（圆变椭圆）。
 * ⚠️ 尺寸写死 32px（`border-radius: 50%` 配它才是正圆）。
 * ⚠️ **加载失败就整块藏掉**（`JS` 里挂了 `error` → `hidden`）：
 *    留个空心圈比没有更难看。
 */
.row-av {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--bg-deep);
  align-self: flex-end; /* ⚠️ 贴着气泡下沿 —— 和气泡底部对齐更自然 */
  user-select: none;
  -webkit-user-drag: none;
}
.row--in {
  align-self: flex-start;
  align-items: flex-start;
}
.row--out {
  align-self: flex-end;
  align-items: flex-end;
}
/** ⚠️ 她说的：头像在**左**；我说的：头像在**右** */
.row--in .row-av { order: 0; }
.row--in .row-stack { order: 1; align-items: flex-start; }
.row--out .row-av { order: 1; }
.row--out .row-stack { order: 0; align-items: flex-end; }

/* 新消息入场：只"轻轻抬一下"，不弹跳、不缩放 —— 聊天流里动效越弱越好 */
@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.bubble {
  position: relative;
  max-width: 100%;
  padding: 11px 16px;
  border-radius: var(--r-bubble);
  font-size: 15.5px;
  line-height: 1.62;
  font-weight: 350;
  color: var(--ink);
  /* pre-wrap：保留她回复里的换行和空格（她说话爱换行），但用 textContent 渲染，不解析 HTML */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
  user-select: text;
  -webkit-user-select: text;
}

/* 她：浮起来的浅色面 */
.bubble--in {
  background: var(--bg-soft);
  box-shadow: -5px -5px 12px rgba(255, 255, 255, 0.8), 6px 6px 14px rgba(0, 0, 0, 0.085);
  border-bottom-left-radius: 9px; /* "尾巴感"：只把靠说话人那一侧的角收紧，绝不画尖角三角形 */
}

/* 我：稍微深一点的面 + 反向阴影，和她的形成左右对照 */
.bubble--out {
  background: #e6e6e6;
  box-shadow: -5px -5px 12px rgba(255, 255, 255, 0.72), 6px 6px 14px rgba(0, 0, 0, 0.095);
  border-bottom-right-radius: 9px;
}

/* 单条消息下面的一行小字：时间 / 发送中 / 失败 */
.meta-row {
  margin: 4px 6px 0;
  font-size: 11px;
  color: var(--ink-4);
  letter-spacing: 0.03em;
  line-height: 1.4;
  display: flex;
  gap: 6px;
  align-items: center;
}

/* 发送中：按住时是"凹"的，像是还在路上 */
.state {
  color: var(--ink-4);
}
/* 失败：无彩色，所以用一条短横杠 + 文字表达"出问题了"，并且可以点重发 */
.state.is-fail {
  color: var(--ink-2);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: rgba(0, 0, 0, 0.2);
  text-underline-offset: 3px;
}

/* 日期分隔 */
.day-sep {
  align-self: center;
  margin: 14px 0 4px;
  padding: 4px 14px;
  border-radius: var(--r-pill);
  font-size: 11.5px;
  color: var(--ink-4);
  background: var(--bg);
  box-shadow: var(--sh-in);
  letter-spacing: 0.06em;
}
/* 同一天里的"隔了很久"时间小注：比日期分隔更弱，
   否则一条 16:20 和一条"今天"长得一样，会被误读成跨天了 */
.day-sep.sep-gap {
  margin: 10px 0 2px;
  padding: 0;
  background: transparent;
  box-shadow: none;
  color: #bdbdbd;
  font-size: 11px;
}

/* ---------- 正在输入 ---------- */
.typing-row {
  /* 输入提示不需要入场动画（会一直闪），所以覆盖掉 row 的 rise */
  animation: none;
}
.bubble--typing {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 15px 18px;
}
.tdot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #b4b4b4;
  animation: blink 1.15s var(--ease) infinite;
}
.tdot:nth-child(2) {
  animation-delay: 0.16s;
}
.tdot:nth-child(3) {
  animation-delay: 0.32s;
}
@keyframes blink {
  0%,
  60%,
  100% {
    opacity: 0.3;
    transform: translateY(0);
  }
  30% {
    opacity: 0.85;
    transform: translateY(-2px);
  }
}

/* ---------- 回到最新 ---------- */
.jump {
  position: absolute;
  left: 50%;
  bottom: calc(150px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 30;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 16px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--bg-soft);
  box-shadow: var(--sh-float);
  color: var(--ink-2);
  font: inherit;
  font-size: 12.5px;
  font-weight: 350;
  cursor: pointer;
  animation: rise 200ms var(--ease);
}
.jump[hidden] {
  display: none;
}
.jump:active {
  box-shadow: var(--sh-in);
}

/* ==========================================================================
   底部：AI 声明 + 输入框 + 页脚声明
   ========================================================================== */
.composer {
  position: relative;
  flex: 0 0 auto;
  padding: 6px 14px calc(8px + env(safe-area-inset-bottom)) calc(14px + env(safe-area-inset-left));
  background: linear-gradient(to top, var(--bg) 74%, rgba(236, 236, 236, 0));
}

/* 常驻小字：不可折叠、不可关闭。用极小字号 + 极浅色，存在感低但不能没有 */
.ai-inline {
  margin: 0 0 8px;
  padding-left: 20px;
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--ink-4);
  letter-spacing: 0.04em;
  user-select: none;
}

/* ==========================================================================
   输入栏 —— 照风格例图重排（2026-10-04）
   --------------------------------------------------------------------------
   例图那一条「upload + 圆形按钮」的形状是：
     一整条**浅色凹陷胶囊轨道**（`--sh-in`），里面放输入区，
     两端各嵌一个**凸起圆形按钮**（`--sh-out-sm`，按下翻成 `--sh-in`）。
   所以媒体按钮**搬进轨道里**，不再是外面并排的三颗独立圆钮 ——
   那样三个按钮抢注意力，而且和例图的"一体会话栏"完全不是一个形状。
   ========================================================================== */

/* 外层：浅色浮起的胶囊（例图里那条比背景更亮的轨道） */
.send-form {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 8px;
  border-radius: var(--r-pill);
  background: var(--bg-soft);
  box-shadow: var(--sh-out);
  /**
   * ⚠️ **这里**原来是 `position: relative` + 绝对定位的按钮 —— 已改掉。
   *    原因见下面 `.media-bar` 的说明（按钮会压在输入框圆角上）。
   */
}

/**
 * ★ **左右两组按钮**（2026-10-04 改成真 flex 布局）。
 *
 * ## ⚠️⚠️ 为什么从绝对定位改成 flex（用户截图反馈"UI 重叠"）
 *
 * 第一版是：输入框占满整条轨道，按钮 **`position: absolute` 浮在两端**，
 * 输入框靠 `padding-left/right` 把文字推开。
 *
 * 那套的毛病（用户截图里看得很清楚）：
 *   ① **padding 要手算**（`8 + 44×3 + 6×2 + 12 = 164`）——
 *      改了按钮尺寸/数量忘了改padding，文字立刻被压住
 *   ② 就算数字算对了，**输入框自己的左圆角还是从按钮底下钻出来**
 *      （按钮是浮在上面的层，输入框的 `border-radius` 照样画）
 *
 * 所以改成 flex：**按钮是输入框的兄弟节点**，各占各的位置 ——
 * 结构上就不重叠，不用算任何留白。
 *
 * ## ⚠️ 为什么 `align-items: center` 而不是 `flex-end`
 *
 * 输入框会长高（1~5 行）。`flex-end` 会让按钮**贴到底部**、
 * 多行时和第一行文字错位；`center` 不管多高都在中线。
 */
.media-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
}

/* ==========================================================================
   ★★★ 媒体键那一行（2026-10-07 用户要求「改成和私聊一样放输入栏上面」）
   --------------------------------------------------------------------------
   ## 用户原话
   ```
   > **太挤了**，而且发出去太大了，占了一半屏幕，设置尺寸小一点，
   > 跟微信表情包差不多大小就行
   > ────
   > **改成和私聊一样放输入栏上面就行了**
   ```
   ⚠️ 原来这四个键和输入框**在同一行**（`.media-bar` 是 `.send-form` 的子节点）——
      加了表情包键之后那一行是「4 个圆钮 + 输入框 + 发送钮」⇒ 手机上**挤**。
   ✅ 现在媒体键**单独一行、在输入框上面**（和私聊的 `.dm-tools` 一个样子）。
   ⚠️ 所以这里要给它**自己的左右内边距**（原来靠 `.send-form` 的 `padding`）——
      不补的话那四个键会**贴着屏幕边**。
   ⚠️ 而 `.send-form` 那一行现在只剩「输入框 + 发送钮」⇒ **不用再挤**。
   ========================================================================== */
.composer .media-bar {
  /**
   * ★★★ **这一行的内边距**（2026-10-07 用户两次反馈后的最终值）。
   *
   * ## 用户原话
   * ```
   * > 第三，**这些按键贴到边框了**
   * > ────（我改成 `10px 16px 0` 之后）
   * > 第一是我圈起来的地方**按键贴到输入栏粘一起了**
   * ```
   * ## 🚨 两次反馈说的是**两件事**，别混成一件
   * ```
   * 第一次「贴到边框」         = **左右**太靠边 ⇒ 左右 padding 要够（16px）
   * 第二次「贴到输入栏粘一起」 = 🚨 **上下**贴住了：
   *    下面那个 `0` 让这一行和输入框**零距离**，
   *    而上面 10px 又让它和"本服务内容由 AI 生成"那行分不清。
   * ⇒ 这一行**四周都要留气**：上 8、左右 16、**下 4**。
   * ```
   * ⚠️ **为什么下边只留 4px**（不是 8 或 10）：
   *    `.send-form` 自己有 `padding: 7px 8px` —— 两处加起来是
   *    **4 + 7 = 11px** 的视觉间距。再大就成了"两行离得太远、像两块东西"。
   *    🚨 而原来的 **0** 才是用户说的"粘一起"。
   *
   * ⚠️⚠️ **这里我改了三次才对**（值得记下来）：
   * ```
   * `10px 16px 0`   → 用户："贴到输入栏粘一起了"
   * `8px 16px 4px`  → 🚨 **量出来还是 gap=0**！
   *     原因：这一行**没有背景色** ⇒ 它下面那 4px 内边距**看起来就是背景**，
   *     而输入框紧贴在内容框底边 ⇒ 视觉上仍然"粘着"。
   *     ⚠️ 教训：**"留白"要按"看得见"算，不是按"盒模型算出来有 4px"算**。
   * `8px 16px 12px` → **12px 才看得出断开**
   * ```
   * ⚠️ 这也是**同一类错我改了两次**的教训：
   *    第一次只看了"左右"，把"上下"当成小事 ⇒ 用户又报一次。
   *    ⇒ 改间距要**四周一起看**，别只改被圈出来的那一边。
   */
  padding: 8px 16px 12px;
}

/* ==========================================================================
   ★★★ 表情包在气泡里要**小一点**（2026-10-07 用户要求）
   --------------------------------------------------------------------------
   ## 用户原话
   ```
   > 发出去**太大了，占了一半屏幕**，设置尺寸小一点，
   > **跟微信表情包差不多大小**就行
   ```
   ⚠️ 原来 `.media-img` 是「撑满气泡宽度」（`max-width: 100%`）——
      一张表情包在窄屏上就是**半个屏幕高**，一屏只能放一两条。
   ✅ 表情包那类图**限到一个固定的小尺寸**（微信那档 ≈ 132px）。
   ⚠️ **只有表情包**走这个小尺寸 —— 用户自己拍的**照片**还是要大图
      （那不是表情包，缩成 132px 就看不清了）。
   ⇒ 判据由 JS 打一个 class（`is-sticker`），CSS 只负责尺寸。
   ========================================================================== */
/**
 * ★★★ **表情包那类图要小**（2026-10-07 用户要求「跟微信表情包差不多大小」）。
 *
 * ⚠️ **两个类名都要写** —— 因为**同一个功能有两条渲染路**：
 * ```
 * 和 AI 聊天 → `.media-img`（`renderSegments` / `buildRow` 那条路）
 * 和真人私聊 → `.dm-media`（`buildDmBubble` 那条路）
 * 🚨 我第一版只写了 `.media-img` ⇒ 用户报「**私聊的表情包尺寸没正常**，
 *    AI 聊天的正常了」—— 那个反馈**精确地指出了是两条路**。
 * ⇒ 规矩：**看到一个"只有一处生效"的样式，先去找另一条渲染路**。
 * ```
 * 🚨 判据只看 URL（`/api/stickers/file`），**不看"谁发的"** ——
 *    自己拍的照片不能走这条（那不是表情包，缩成 132px 就看不清了）。
 */
.media-img.is-sticker,
.dm-media.is-sticker {
  max-width: 132px;
  max-height: 132px;
}

/* 胶囊输入框：凹陷 + 自动长高（高度由 JS 写 style.height） */
.input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 50px;
  max-height: 144px; /* ≈5 行，再长就滚动，别把消息区挤没了 */
  /**
   * ★ **留白现在的意义变了**（2026-10-04 改成 flex 布局之后）。
   *
   * 以前它是"躲开浮在上面的按钮"（所以要手算 3 个按钮的总宽，算错就重叠）。
   * 现在按钮是**兄弟节点**、结构上不重叠 —— 这个 padding 只是**呼吸感**，
   * 小一点大一点都不会压住文字。
   *
   * ⚠️ **别再往大调** —— 调大了会把文字区挤窄（尤其手机上）。
   */
  padding: 13px 20px;
  border: 0;
  outline: 0;
  border-radius: var(--r-pill);
  background: var(--bg-soft);
  /* ⚠️ 比外面那层再浅一档的凹陷 —— 例图里"内层比外层更凹"是这个风格的层次来源 */
  box-shadow: inset -3px -3px 9px rgba(255, 255, 255, 0.9), inset 5px 5px 12px rgba(0, 0, 0, 0.085);
  color: var(--ink);
  font: inherit;
  font-size: 15.5px;
  line-height: 1.5;
  /**
   * ⚠️ **文字垂直居中**（2026-10-04）。
   *
   * 输入框单行时高度是 `min-height: 50px`，而 `line-height: 1.5` 的内容只有 ~23px ——
   * 不居中就会**贴着上沿**，而左右两颗按钮是居中的（`.media-bar` 用 `top:50%`），
   * 于是单行状态下"按钮在中间、文字在上面"，看着就是没对齐。
   */
  align-content: center;
  resize: none; /* 手动拖拽会和自动长高打架 */
  overflow-y: auto;
  scrollbar-width: none;
}
.input::-webkit-scrollbar {
  width: 0;
}
.input::placeholder {
  color: var(--ink-4);
}
.input:focus {
  box-shadow: inset -2px -2px 7px rgba(255, 255, 255, 0.85), inset 7px 7px 16px rgba(0, 0, 0, 0.105);
}
/* 断连时输入框禁用：视觉上"塌下去"，明确告诉你现在发不出去 */
.input:disabled {
  color: var(--ink-4);
  box-shadow: inset 3px 3px 8px rgba(0, 0, 0, 0.05), inset -2px -2px 6px rgba(255, 255, 255, 0.6);
}

/**
 * ⚠️ 手机上要把左右留白收窄 —— 96px 是照 44px 按钮算的，
 *    小屏（<=380px）上那样文字区只剩不到一半，会很难打字。
 *    收窄到 84px（按钮 40px）仍然够点。
 */
@media (max-width: 380px) {
  /**
   * ⚠️⚠️ **窄屏上文字区被挤到只剩 ~115px**（2026-10-04 截图+量出来的）。
   *
   * 375px 宽时算一遍（改之前）：
   * ```
   * 12（轨道内边距 6+6） + 40×3（三颗按钮） + 5×2（间距）
   *   + 40（发送键） + 5×2（间距） = 175px
   * → 输入框只剩约 108px —— 还不够打四个字
   * ```
   *
   * 现在：缩按钮、缩间距、缩输入框内边距。算一遍：
   * ```
   * 10 + 36×3 + 4×2 + 44 + 4×2 = 186px → 文字区约 189px ✅
   * ```
   * ⚠️ 发送键**不缩反大**（44px）—— 它是点得最频繁的那个，而且只有一个。
   */
  .round-btn--ghost {
    width: 36px;
    height: 36px;
  }
  .round-btn--ghost svg {
    width: 17px;
    height: 17px;
  }
  .round-btn--accent {
    width: 44px;
    height: 44px;
  }
  .media-bar {
    gap: 4px;
  }
  .send-form {
    gap: 4px;
    padding: 5px 5px;
  }
  /** 输入框的呼吸感也要收（窄屏每一像素都金贵） */
  .input {
    padding: 12px 14px;
    font-size: 15px;
  }
  /** 待发附件预览跟着收，别把输入区挤没 */
  .attach-preview {
    padding: 6px 8px;
    gap: 6px;
  }
  .attach-thumb {
    width: 38px;
    height: 38px;
  }

  /**
   * ⚠️⚠️ **状态栏在窄屏上放不下，只能折行**（2026-10-04 量出来的）。
   *
   * 360px 视口下的实际分配：
   * ```
   * .bar-status 只分到 180px（右边三个图标各 40px + 间距 = 138px）
   * 而它要装「正在醒来… ｜ 心情未知 ｜ 正在连接…」≈ 250px
   * → 只能折行 → 看着"两个东西压在一起"
   * ```
   *
   * 修法：**窄屏上把分隔符藏掉**（省下 2×13px）+ 让每一段能收缩。
   *
   * ⚠️ 为什么藏分隔符而不是藏整段信息：
   *    「她睡没睡」「心情」是用户最想瞄一眼的两项；
   *    分隔符纯粹是装饰 —— **装饰先让路**。
   */
  .dot-sep {
    display: none;
  }
  .bar-status {
    gap: 2px 5px;
    font-size: 12px;
  }
  /** 顶栏的图标也收一档，把宽度让给状态文字 */
  .bar {
    gap: 6px;
    padding-left: 12px;
    padding-right: 10px;
  }
  .icon-btn {
    width: 36px;
    height: 36px;
  }
  /** 搜索栏：按钮再收 2px，输入框留出更多字 */
  .find-nav {
    width: 20px;
    height: 20px;
    font-size: 11px;
  }
  .find-bar {
    padding: 7px 10px;
    gap: 3px;
  }
}


.ai-foot {
  /**
   * 🚨 **下面必须有留白**（2026-10-05 用户报："底下这个字跟边边重叠了"）。
   *
   * ⚠️ 原来写的是 `margin: 8px 0 0` —— **只有上边距、下边距是 0**，
   *    于是这行字（聊天页最底下那条 AI 声明）**贴死在屏幕底沿**：
   *    真机量出来是「距屏底 8px、下边距 0px」，看起来就是"字压在边上"。
   *
   * ⚠️ 为什么不能只写 `env(safe-area-inset-bottom)`：
   *    在没有安全区的设备上它算出来是 **0** → 又贴回去了。
   *    所以用 `max()`：**至少 14px**，有安全区（iPhone 那类）时取更大的那个。
   *    ⚠️ 这个项目**已经在用 `env(safe-area-inset-*)`**（顶栏/输入栏都有），
   *       同一个写法，不引新东西。
   */
  margin: 8px 0 max(14px, env(safe-area-inset-bottom));
  font-size: 11px;
  line-height: 1.4;
  color: #b3b3b3;
  text-align: center;
  letter-spacing: 0.05em;
  user-select: none;
}

/* ==========================================================================
   轻提示条
   ========================================================================== */
.banner {
  position: fixed;
  left: 50%;
  bottom: calc(148px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 50;
  max-width: min(92vw, 460px);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 12px 11px 18px;
  border-radius: var(--r-pill);
  background: var(--bg-soft);
  box-shadow: var(--sh-float);
  color: var(--ink-2);
  font-size: 13px;
  animation: rise 200ms var(--ease);
}
.banner[hidden] {
  display: none;
}
.banner-act {
  flex: 0 0 auto;
  border: 0;
  border-radius: var(--r-pill);
  padding: 7px 14px;
  background: var(--bg);
  box-shadow: var(--sh-out-sm);
  color: var(--ink);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.banner-act:active {
  box-shadow: var(--sh-in);
}
.banner-x {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-4);
  cursor: pointer;
}
.banner-x:hover {
  color: var(--ink-2);
}

/* ==========================================================================
   响应式 / 无障碍
   ========================================================================== */

/* 桌面：把聊天区收成一条居中的"纸带"，两侧留白 —— 极简风里宽度克制很重要 */
@media (min-width: 720px) {
  .app {
    max-width: 760px;
    margin: 0 auto;
  }
  .bar,
  .composer {
    padding-left: 18px;
    padding-right: 18px;
  }
  .list {
    padding-left: 20px;
    padding-right: 20px;
  }
  .row {
    max-width: 72%;
  }
  .jump {
    bottom: calc(158px + env(safe-area-inset-bottom));
  }
}

/* 小屏：气泡占比放宽，否则长句会被挤成一条竖线 */
@media (max-width: 400px) {
  .row {
    max-width: 84%;
  }
  .ai-inline {
    padding-left: 16px;
  }
}

/* 矮屏（横屏手机）：把留白收紧，先保证内容看得见 */
@media (max-height: 520px) {
  .bar {
    min-height: 44px;
    padding-top: calc(6px + env(safe-area-inset-top));
    padding-bottom: 6px;
  }
  .ai-foot {
    display: none; /* 只在这里隐藏页脚声明；输入框上方那行**任何时候都不隐藏** */
  }
  .jump {
    bottom: calc(122px + env(safe-area-inset-bottom));
  }
}

/* 尊重系统的"减弱动态效果"：新拟物的动效本来就该是锦上添花 */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* 触屏设备（无 hover）：把 hover 态的阴影去掉，免得点完一直"浮着" */
@media (hover: none) {
  .menu-item:hover {
    box-shadow: none;
  }
}

/* ==========================================================================
   查找栏 / 浮层面板 / 表情包 / 聊天背景 —— 2026-10-04 PWA 第 1 步
   --------------------------------------------------------------------------
   沿用全篇三条铁律：不加边框、靠双色阴影分层、圆角偏大、不引彩色。
   ========================================================================== */

/* ---------- 查找聊天记录（浮在消息区上方的凹陷条） ---------- */
.find-bar {
  position: absolute;
  top: 8px;
  left: 10px;
  right: 10px;
  z-index: 6;
  display: flex;
  align-items: center;
  /**
   * ⚠️ 间距和上下留白都收了一档（原来是 6px / `6px 10px`）——
   *    量出来内容区只有 26px，而按钮那时也是 26px，**顶死了**。
   *    现在按钮 22px + 上下各 8px，胶囊高 38px，**四周都有余量**。
   */
  gap: 4px;
  padding: 8px 12px;
  border-radius: var(--r-pill);
  background: var(--bg-sunken);
  box-shadow: var(--sh-float);
  color: var(--ink-2);
}
.find-input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 14.5px;
}
.find-count {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.find-nav {
  flex: 0 0 auto;
  /**
   * ⚠️⚠️ **这三个按钮原来和输入框一样高（26px），挤在 38px 的胶囊里** ——
   *    圆几乎顶到胶囊边缘，视觉上像"压着框"（用户截图反馈）。
   *
   * 量出来的数（360px 视口）：
   * ```
   * 容器 340×38（上下内边距各 6px → 内容区只有 26px）
   * 三个 26px 的圆 + 间距 6×2 = 90px 挤在右侧，右边只剩 10px
   * ```
   *
   * 改成：**按钮比输入框矮一档（22px）+ 上下留 8px**，
   * 这样圆不会顶到胶囊边，而且和输入框有明确的层次（不是一样高的一排）。
   */
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--bg-soft);
  box-shadow: var(--sh-out-sm);
  color: var(--ink-2);
  font-size: 12px;
  cursor: pointer;
}
.find-nav:active { box-shadow: var(--sh-in); }

/* 命中的那条：不引彩色，用"凸起 + 左侧竖条"标出来 */
.row.is-hit-current .bubble {
  box-shadow: var(--sh-out);
}
.row.is-hit-current::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 8px;
  bottom: 8px;
  width: 3px;
  border-radius: 2px;
  background: var(--ink-3);
}
.row { position: relative; }

/* ---------- 浮层面板（表情包 / 聊天背景，从底部升起） ---------- */
/**
 * 浮层面板（表情包 / 聊天背景）—— **从视口底部升起**。
 *
 * ## 🚨🚨 为什么是 position: fixed（2026-10-04 用户报"按键没反应"抓到的）
 *
 * 原来是：
 *   .sheet { position: absolute; bottom: calc(100% + 8px); }
 *
 * 它相对 **.composer**（输入栏那一块，只有约 100px 高）定位 ——
 * 于是"在 composer 上方 8px"再往上**撑 482px**，整块跑到了
 * **屏幕外面（上面）**：
 *   实测面板 rect: 8,-489 414x482   ← y 是负的，在视口上方
 *
 * ⚠️ **用户看到的就是"点了没反应"** —— 而它其实打开了，
 *    只是**渲染在看不见的地方**（hidden: false、25 个格子都在）。
 *
 * ⚠️ 这种"功能是好的、只是看不见"最难查：DOM 里一切正常、
 *    没有报错、.click() 测也"通过"（我自己的测试就是这么被骗过去的）。
 *
 * 改成 fixed 之后**不用算 composer 多高** —— 直接贴视口底部往上留一点。
 * 里面 max-height + overflow-y: auto，内容多了自己滚。
 */
.sheet {
  position: fixed;
  left: 8px;
  right: 8px;
  /**
   * ⚠️ 输入栏高度（约 112px）+ 一点间隙 + 安全区。
   *    输入栏高度变了这里要跟着调 —— 但**调大了只是面板位置偏上**，
   *    不会再出现"跑到屏幕外"那种坏法（那是 absolute 才有的问题）。
   */
  bottom: calc(128px + env(safe-area-inset-bottom));
  z-index: 60;
  max-height: 58vh;
  overflow-y: auto;
  padding: 14px 14px 12px;
  border-radius: var(--r-card);
  background: var(--bg-soft);
  box-shadow: var(--sh-float);
}
.sheet-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.sheet-title {
  flex: 1 1 auto;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
}
.sheet-btn {
  flex: 0 0 auto;
  padding: 5px 12px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--bg-soft);
  box-shadow: var(--sh-out-sm);
  color: var(--ink-2);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.sheet-btn:active { box-shadow: var(--sh-in); }
/**
 * ★★ **两个修饰类**（2026-10-06 加，账号那一块要用）。
 *
 * ⚠️ 我原来在这两个按钮上写了 `sheet-btn--accent` / `sheet-btn--danger`，
 *    而 **CSS 里根本没有这两条规则** ⇒ 它们**长得和普通按钮一样**
 *    （"危险操作"和"取消"看着一模一样 —— 那是会让人点错的）。
 * ⚠️ 规矩：**加修饰类就得写规则**，不然就是在骗自己"我区分了"。
 */
/**
 * ⚠️ **颜色变量名要照抄现有的** —— 项目里是 `--accent` / `--accent-soft` /
 *    `--accent-ink`（`--accent-ink` 就是"压在强调色上的文字色"），
 *    不是我随手写的 `--accent` + 硬编码 `#fff`。
 *    ⚠️ 硬编码 `#fff` 在**漫画风**（`--accent` 是深墨色）下会瞎；
 *      用 `--accent-ink` 两种主题都对。
 */
.sheet-btn--accent {
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}
.sheet-btn--danger {
  /** ⚠️ 用**红字**（不是红底）—— 那个按钮平时不该抢眼，点下去之前才该被注意到 */
  color: #c0392b;
  font-weight: 600;
}
.sheet-btn--danger:active {
  background: #c0392b;
  color: #fff;
}
.sheet-hint {
  margin: 10px 0 0;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--ink-3);
}

/* ---------- 表情包格子 ---------- */
.sticker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(68px, 1fr));
  gap: 8px;
}
.sticker-cell {
  position: relative;
  aspect-ratio: 1 / 1;
  display: grid;
  place-items: center;
  padding: 4px;
  border: 0;
  border-radius: 18px;
  background: var(--bg);
  box-shadow: var(--sh-out-sm);
  cursor: pointer;
  overflow: hidden;
}
.sticker-cell:active { box-shadow: var(--sh-in); }
.sticker-img {
  max-width: 100%;
  max-height: 100%;
  border-radius: 12px;
  object-fit: contain;
}
/* ⚠️ 图不在服务器上：画一个明确的占位，别留碎图 */
.sticker-cell.is-missing {
  box-shadow: var(--sh-in);
  cursor: not-allowed;
}
.sticker-missing {
  font-size: 10.5px;
  line-height: 1.3;
  color: var(--ink-3);
  text-align: center;
  padding: 0 2px;
}

/* ---------- 聊天背景色卡 ---------- */
.bg-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
  gap: 8px;
}
.bg-swatch {
  position: relative;
  aspect-ratio: 3 / 4;
  border: 0;
  border-radius: 18px;
  background: var(--bg);
  box-shadow: var(--sh-out-sm);
  cursor: pointer;
  overflow: hidden;
}
.bg-swatch.is-on {
  box-shadow: var(--sh-in), 0 0 0 2px rgba(0, 0, 0, 0.14);
}
.bg-swatch-label {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 4px;
  font-size: 10.5px;
  color: var(--ink-3);
}
.bg-custom {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

/* ==========================================================================
   主页 / 表单页（定制她 · 模型接入 · 关于）—— 2026-10-04 PWA 第 2 步
   --------------------------------------------------------------------------
   照风格例图：这一页是「一整块浅色卡片 + 里面嵌深色内容卡」的最典型用法
   （例图里那块黑胶唱机就是深色内嵌卡）。
   铁律不变：不加边框、双色阴影、大圆角、不引彩色。
   ========================================================================== */

/* ---------- 子页面容器（和聊天页同为整屏，但内容可滚动） ---------- */
#screen-home,
#screen-persona,
#screen-models,
#screen-about {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.home-body,
.form-body {
  flex: 1 1 auto;
  overflow-y: auto;
  /**
   * 🚨 **底部留够 30px**（2026-10-05 用户报"按键跟边框贴一起去了"，音效页 + 模型接入页）。
   *
   * ## 出了什么事
   *
   * ⚠️ 这个滚动容器是 `overflow-y: auto` ⇒ **超出内边距的内容会被裁掉**。
   *    而新拟物的按钮**没有边框**，它的"边"是 `box-shadow` 画出来的：
   * ```
   * --sh-out-sm: -3px -3px 8px #fff, 4px 4px 9px rgba(0,0,0,.1)
   *                                  ↑ 往右下各偏 4px、再加 9px 模糊
   * ```
   *    ⇒ 阴影**往元素外面各溢 ~13px**。
   *
   * ⚠️ 而卡片的内边距是 `16px 18px`、最后一行按钮离卡片边框只剩 **3px**
   *    （真浏览器量出来的 `底余/右余 = 3`）——
   *    于是按钮的"右边和下边"**正好压在卡片边框上**，看起来就是"粘住了"。
   *
   * ⚠️ 原来这里是 `padding: 4px 14px 18px` —— 18px 只够卡片自己的 `margin-bottom: 14px`，
   *    **不够按钮阴影那一圈**。
   *
   * ✅ 现在：底部 30px、左右 18px。⚠️ **只动内边距，不动任何"画法"** ——
   *    背景那套栽过五次的教训就是"别新开一层、别改简写"。
   *
   * ★★★ **关于"底部导航条会不会盖住它"（2026-10-07 查清并更正）**
   *
   * ```
   * 我一度在这里加了一条 `html[data-tabbar='on'] .form-body { padding-bottom: … }`，
   * 理由是"体检发现导航条把设置页最后几行盖住了"。
   * 🚨 **那个理由是错的** —— 后来量清了：
   *    · **底部导航条是"一屏一屏显示"的**（`renderTabs()` 按当前屏 + `activeTab` 判断）
   *    · 而所有用 `.form-body` 的屏（设置 / 模型 / 语音 / 相册…）**都是二级屏**，
   *      进它们的时候导航条**本来就不显示** ⇒ **没有重叠**
   *    · 实测（390×844）：`.form-body` 底边 **743** ＜ 导航条顶边 **780** ⇒ 差 37px
   * ⚠️ 那条"被盖"的报告是**假失败**（我量的是"同一时刻还停在另一屏"的坐标）。
   * ⇒ 那条规则**已经撤掉**了 —— 不留"没坏却改过"的痕迹（那会让下一个人看不懂）。
   * ```
   */
  padding: 4px 18px 30px;
  -webkit-overflow-scrolling: touch;
}
.form-loading {
  padding: 24px 4px;
  color: var(--ink-3);
  font-size: 14px;
}

/* ---------- 卡片 ---------- */
.card {
  margin-bottom: 14px;
  padding: 16px 18px;
  border-radius: var(--r-card);
  background: var(--bg-soft);
  box-shadow: var(--sh-out);
}
/**
 * **深色内嵌卡** —— 例图里最抓眼的那一档层次。
 * 用在"最该被读到的两三条信息"上（她现在的状态、版本号、危险提示）。
 */
.card.sunken {
  background: var(--bg-sunken);
  box-shadow: var(--sh-in-deep);
}
/* 保护段落缺失的警告卡：不引红/黄（全篇铁律），靠"最深的凹陷 + 粗一点的标题"表态 */
.card.warn-card {
  background: var(--bg-deep);
  box-shadow: var(--sh-in-deep);
}
.card.warn-card .about-h {
  font-weight: 600;
  color: var(--ink);
}

/* ---------- 主页：她的照片 ---------- */
.hero {
  display: flex;
  align-items: center;
  gap: 16px;
}
.hero-portrait {
  flex: 0 0 auto;
  width: 92px;
  height: 92px;
  display: grid;
  place-items: center;
  /**
   * ⚠️ **`position: relative` 是给里面的 K 用的**（2026-10-06）：
   *    `.hero-portrait-k` 用 `position:absolute` 脱离文档流（理由见那条注释），
   *    而它要有定位祖先 ⇒ 就是这个圆。
   */
  position: relative;
  border-radius: 50%;
  background: var(--bg);
  box-shadow: var(--sh-in);
  overflow: hidden;
  color: var(--ink-3);
}
.hero-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /**
   * ⚠️ **也绝对定位**（2026-10-06）：圆是 `display:grid`，而 K 也是绝对定位的
   *    兄弟节点 —— 如果形象照是**普通流**，它会和 K **各占一格**，
   *    把圆撑成两倍高（那个 bug 很显眼但很容易漏）。
   *    ⇒ 两个都脱离文档流、都铺满圆，`hidden` 控制谁可见。
   */
  position: absolute;
  inset: 0;
}
/**
 * ★★★ **没设形象照时那个 K**（2026-10-06 用户纠正）。
 *
 * ## 用户原话（他在图上框了左边那个大圆）
 *   > 改错了，不是打蓝叉那里，是**红色框起来那个**，
 *   > 改完把蓝叉那里删了，**名字移过去点**
 *
 * ⚠️ 之前这个圈里是**内联 SVG 画的气泡线稿** —— 用户要的是 **K**。
 * ⚠️ 尺寸比圆小一圈（**62%**）—— 顶满的话那个手写体的笔画会贴到圆边上
 *    （它是斜的，视觉上比边框大）。
 * ⚠️ **和 `.hero-img` 叠在同一格里**（`.hero-portrait` 是 `display:grid` +
 *    `place-items:center`）⇒ 两个 `<img>` 会**各占一格**、把圆撑成两倍高！
 *    所以这里用 **`position:absolute` 脱离文档流**（圆是 `overflow:hidden` 的，
 *    所以要给圆一个 `position:relative`）。
 */
.hero-portrait-k {
  position: absolute;
  width: 62%;
  height: 62%;
  object-fit: contain;
  /** ⚠️ 图片本身是白底方图 ⇒ 给圆角让它和圆形底融合（不给会看到一个白方块） */
  border-radius: 50%;
}
.hero-portrait-k[hidden] {
  display: none;
}
/**
 * ⚠️ **`hidden` 在 `position:absolute` 的元素上仍然有效** —— 但要确认：
 *    浏览器默认的 `[hidden] { display:none }` 优先级很低（0,1,0），
 *    任何带 `display:` 的类都会盖掉它。⇒ 这里两条都显式写一遍（见上面那两条）。
 */
.hero-info {
  min-width: 0;
}
/**
 * ★★ **名字左移**（2026-10-06 用户要求「名字移过去点」）。
 *
 * ⚠️ 原来名字左边挂着一个 32px 的小 logo（`.hero-avatar-sm`，**已删**）——
 *    它占掉的横向空间还在（`.hero-name-row` 是 flex + gap），
 * ⚠️ 原来名字左边挂着一个 30px 的小 logo（`.hero-avatar-sm`，**已删**）——
 *    它占掉的横向空间本来还在（`.hero-name-row` 是 flex + gap），
 *    所以名字看着"还是缩在右边"。
 * ✅ 现在那个 `<img>` 没了、`el` 表里的条目也删了 ⇒ `.hero-name-row` 里
 *    **只剩名字一个子节点**，`gap` 没有东西可分 ⇒ 名字自然贴左边。
 * ⚠️ **父级 `.hero` 的 `gap: 16px` 要留着** —— 那是"圆 ↔ 信息块"之间的间距
 *    （删了名字会贴到圆上）。
 * ⚠️ 所以**这里不需要额外补 padding**：那个小 logo 是**在 `.hero-name-row`
 *    内部**（不是在圆和信息块之间），删掉它名字就左移了。
 */
.hero-name {
  margin: 0;
  font-size: 21px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: 0.4px;
}
.hero-line {
  margin: 6px 0 0;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-2);
}
.hero-sub {
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--ink-3);
}

/* ---------- 主页：状态（深色卡里的四格） ---------- */
.stat-row {
  display: grid;
  /**
   * ⚠️ **2×2**（原来是一行三格）—— 2026-10-06 加了"在忙吗"那一格。
   *    四个格子挤成一行的话，手机上每格只有约 80px，
   *    "专注"这种两字词 + 数字就会换行（那比两行更难看）。
   */
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}
.stat {
  text-align: center;
  padding: 8px 4px;
  border-radius: 18px;
  /* ⚠️ 深色卡里的小格用**凸起**（浅一档），不然一片糊在一起看不出是三格 */
  background: var(--bg-soft);
  box-shadow: var(--sh-out-sm);
}
.stat-label {
  margin: 0;
  font-size: 11px;
  color: var(--ink-3);
}
.stat-value {
  margin: 5px 0 0;
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.stat-note {
  margin: 12px 0 0;
  font-size: 12px;
  line-height: 1.65;
  color: var(--ink-3);
}

/* ---------- 主页：入口 ---------- */
.home-nav {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 4px;
}
.home-link {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  padding: 14px 18px;
  border: 0;
  border-radius: var(--r-card);
  background: var(--bg-soft);
  box-shadow: var(--sh-out);
  text-align: left;
  cursor: pointer;
  transition: box-shadow 160ms var(--ease), transform 160ms var(--ease);
}
.home-link:active {
  box-shadow: var(--sh-in);
  transform: translateY(1px);
}
.home-link-label {
  font-size: 15.5px;
  color: var(--ink);
}
.home-link-hint {
  font-size: 12px;
  color: var(--ink-3);
}
.home-title {
  flex: 1 1 auto;
  margin: 0 0 0 6px;
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
}

/* ---------- 表单 ---------- */
.field-row {
  margin-top: 14px;
}
/**
 * ★★★ **"输入框 → 它下面那个按钮"之间要有缝**（2026-10-06 用户报的）。
 *
 * ## 用户原话（附了截图，圈着"保存这把 key"和它上面的输入框）
 * ```
 * > 这个按键和边框连起来了
 * ```
 *
 * ## 🚨 为什么会连起来
 * ```
 * 「模型接入 → 密钥」那几行是这样拼的：
 *   .field-row > label + p（状态）+ input（.field-input）+ button（.sheet-btn）
 * ⚠️ `.field-input` 是 `display:block; width:100%` ⇒ 它**独占一行**
 * ⚠️ 而那个 button 是 `.sheet-btn`（自己 margin 是 0 —— 它在别处都待在
 *    `.btn-row` 里，间距由那一层给）⇒ 直接塞进 `.field-row` 时
 *    **紧跟在前一个块级元素下面，中间一点空隙都没有**
 * ```
 *
 * ## ✅ 修法：**只给"跟在输入框后面的按钮"加一条上边距**
 * ```
 * 10px —— 和 `.field-label` 的 `margin-bottom: 6px` 凑一起，
 * 让"标签 → 输入框"和"输入框 → 按钮"两种缝看起来是一个量级。
 * ```
 *
 * 🚨 **第一版我用了 `display:flex; flex-direction:column; gap:10px`** —— 那也能修好缝，
 *    但**把按钮拉成了全宽**（`align-items` 默认 `stretch`）：
 *    截图一看，"保存这把 key"从**紧凑小胶囊**变成了**一整条**（用户没要那个）。
 *    ⇒ 教训：**改布局模式会连带改一堆东西**（宽度、对齐、换行），
 *      能用一条 margin 解决的就别动 `display`。
 */
.field-input + .sheet-btn,
.field-input + .btn-row,
.field-note + .sheet-btn {
  margin-top: 10px;
}
.field-label {
  display: block;
  /**
   * ⚠️ **`margin-bottom` 去掉了**（2026-10-06）——
   *    `.field-row` 现在是 `gap: 10px` 的纵向 flex（为了修"按钮和输入框连起来"），
   *    这条 6px 会**和 gap 叠加**成 16px（标签离输入框太远，看着散）。
   *    ⇒ 那个间距统一由 `gap` 管（**一处说了算**，以后不用在两边对着调）。
   */
  font-size: 13px;
  color: var(--ink-2);
}
.field-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 11px 14px;
  border: 0;
  outline: 0;
  border-radius: 18px;
  /**
   * ⚠️ 输入框是**凹陷**的（和聊天页输入框同一个道理）——
   *    这一页卡片本身已经是凸起，输入框再凸就和卡片分不清了。
   */
  background: var(--bg);
  box-shadow: inset -3px -3px 8px rgba(255, 255, 255, 0.9), inset 4px 4px 10px rgba(0, 0, 0, 0.08);
  color: var(--ink);
  font: inherit;
  font-size: 14.5px;
  line-height: 1.55;
  resize: vertical;
}
.field-input:focus {
  box-shadow: inset -2px -2px 6px rgba(255, 255, 255, 0.85), inset 6px 6px 14px rgba(0, 0, 0, 0.1);
}
/**
 * 存不上的时候：**不加红框**（全篇不引彩色），改用
 * "更深的凹陷 + 左侧一道竖条" —— 和查找命中那条一个语言。
 */
.field-input.is-bad {
  box-shadow: inset 4px 4px 12px rgba(0, 0, 0, 0.16), inset -2px -2px 6px rgba(255, 255, 255, 0.6);
  border-left: 3px solid var(--ink-2);
}
/* 人设原文（受保护的那个大文本框）：再深一档，视觉上提醒"这段要小心" */
.field-input.is-guarded {
  background: var(--bg-sunken);
  min-height: 150px;
}
.field-count {
  margin: 5px 2px 0;
  font-size: 11px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.field-count.is-over {
  color: var(--ink);
  font-weight: 500;
}
.field-note {
  margin: 6px 2px 0;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--ink-3);
}

/* ---------- 关于页的文字块 ---------- */
.about-h {
  margin: 0 0 10px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
}
.about-k {
  margin: 0;
  font-size: 11px;
  color: var(--ink-3);
}
.about-v {
  margin: 6px 0 0;
  font-size: 19px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: 0.5px;
}
.about-p {
  margin: 0 0 10px;
  font-size: 13.5px;
  line-height: 1.75;
  color: var(--ink-2);
}
.about-p:last-child {
  margin-bottom: 0;
}
.about-list {
  margin: 0;
  padding-left: 20px;
  font-size: 13.5px;
  line-height: 1.8;
  color: var(--ink-2);
}
.about-list li {
  margin-bottom: 6px;
}
.about-list li:last-child {
  margin-bottom: 0;
}
.about-list code {
  padding: 1px 5px;
  border-radius: 6px;
  background: var(--bg);
  box-shadow: var(--sh-in);
  font-size: 12px;
}

/* 下拉（`choice` 类型字段，2026-10-04 加）—— 和输入框同一个凹陷语言 */
select.field-input {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 38px;
  cursor: pointer;
  /* 自绘一个小箭头（不加 border 的前提下，`background-image` 是唯一不破坏阴影的做法） */
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
    linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
  background-position: calc(100% - 19px) calc(50% + 2px), calc(100% - 14px) calc(50% + 2px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
select.field-input:focus {
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-2) 50%),
    linear-gradient(135deg, var(--ink-2) 50%, transparent 50%);
}

/* ==========================================================================
   通话界面 —— 2026-10-04 PWA 第 3 步
   --------------------------------------------------------------------------
   照风格例图：整屏浅色，中间一个**大的凹陷圆**（她的位置），
   通话中它按状态轻微脉动（在听 / 在想 / 在说）。
   铁律不变：不引彩色、不加边框、双色阴影、大圆角。
   ========================================================================== */
.call-screen {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.call-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 22px 18px 8px;
  overflow-y: auto;
}
.call-state {
  margin: 4px 0 0;
  font-size: 14px;
  color: var(--ink-3);
}
/* 那个大的凹陷圆 —— 她的"位置" */
.call-orb {
  flex: 0 0 auto;
  width: 176px;
  height: 176px;
  margin: 20px 0 6px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--bg);
  box-shadow: var(--sh-in);
  color: var(--ink-3);
  transition: box-shadow 220ms var(--ease), color 220ms var(--ease), transform 220ms var(--ease);
}
/**
 * ⚠️ 三种状态**只用阴影和缩放**区分，不引颜色（全篇铁律）。
 *    · 在听 → 更深的凹陷（"张着耳朵"）
 *    · 在想 → 极轻微的缩小（收着）
 *    · 在说 → 凸起来（像在发声）
 */
.call-orb.is-listening {
  box-shadow: inset -2px -2px 6px rgba(255, 255, 255, 0.7), inset 8px 8px 20px rgba(0, 0, 0, 0.13);
  color: var(--ink-2);
}
.call-orb.is-thinking {
  transform: scale(0.97);
}
.call-orb.is-speaking {
  box-shadow: var(--sh-out);
  color: var(--ink);
  animation: orb-speak 900ms ease-in-out infinite;
}
@keyframes orb-speak {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.025); }
}
@media (prefers-reduced-motion: reduce) {
  .call-orb.is-speaking { animation: none; }
}
.call-timer {
  margin: 10px 0 0;
  font-size: 15px;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.6px;
}
/* 这一通说了什么 */
.call-transcript {
  /**
   * ★★★ **这里必须 `min-height: 0`**（2026-10-07 修用户报的
   *    「**内容把挂断和静音键挤掉了，挂不了对话**」）。
   *
   * ## 🚨 出了什么事（经典 flex 陷阱）
   * ```
   * 原来：flex: 1 1 auto;  min-height: 90px;  overflow-y: auto;
   * 🚨 flex 项的 `min-height` **默认值是 `auto`** ——
   *    意思是"**不许缩到比内容还小**"。
   *    而这里只写了 `min-height: 90px`（那是**下限**，不是上限！）
   *    ⇒ 她的话越说越长 ⇒ transcript 越高 ⇒ **把下面的 `.call-actions`
   *      （挂断 / 静音 / 打字）顶出可视区** ⇒ 用户**挂不了电话**。
   * ⚠️ 表现完全像"布局坏了"，而根因只有一行 CSS。
   * ```
   * ## ✅ 修法：**把它变成"能缩的那个"**
   * ```
   * `min-height: 0` ⇒ 允许它缩到比内容小（内容溢出就**内部滚动**）
   * ＋ 下面 `.call-actions` 加 `flex-shrink: 0` ⇒ **按钮永不被压**
   * ⚠️ 两个一起才成立：只改一个的话，还是有一边会被挤。
   * ```
   */
  flex: 1 1 auto;
  min-height: 0;          /* 🚨 不是 90px —— 那会让它撑着不缩，把按钮顶出去 */
  width: 100%;
  max-width: 520px;
  margin: 16px 0 8px;
  padding: 12px 14px;
  border-radius: var(--r-card);
  background: var(--bg-sunken);
  box-shadow: var(--sh-in-deep);
  overflow-y: auto;
  /** ⚠️ 用 `max-height` 兜一层（极窄的屏幕上别把上面那个圆也挤没） */
  max-height: 42vh;
  -webkit-overflow-scrolling: touch;
}
.call-line {
  margin-bottom: 8px;
}
.call-line:last-child { margin-bottom: 0; }
.call-line-text {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink-2);
}
/* 她说的话凸起来一点、颜色深一点（一眼看出谁说的） */
.call-line--her .call-line-text {
  display: inline-block;
  padding: 7px 12px;
  border-radius: 16px;
  background: var(--bg-soft);
  box-shadow: var(--sh-out-sm);
  color: var(--ink);
}
.call-hint {
  margin: 6px 0 0;
  min-height: 18px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink-3);
  text-align: center;
  max-width: 420px;
}
.call-actions {
  /**
   * ★★ **按钮永不被压**（2026-10-07，和上面 `.call-transcript` 的 `min-height: 0` 配套）。
   *
   * ```
   * 🚨 上面那个 transcript 一旦能长高，**最先被牺牲的就是这里** ——
   *    而挂断键被挤出去 = 用户**挂不了电话**（体验上最严重的一种坏）。
   * ⇒ `flex-shrink: 0` ＋ 底部留出**安全区**（刘海屏/手势条）。
   * ```
   */
  flex: 0 0 auto;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 26px;
  margin: 16px 0 10px;
  /** ⚠️ 底部安全区（不然 iPhone 的手势条会压在挂断键上） */
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
/* 挂断：比别的按钮大一圈（电话里最容易按错的就是它） */
.call-hangup {
  width: 66px;
  height: 66px;
}
.call-type-form {
  display: flex;
  gap: 8px;
  width: 100%;
  max-width: 460px;
  margin-bottom: 8px;
}
.call-type-form .field-input {
  flex: 1 1 auto;
}

/* ==========================================================================
   通讯录 —— 2026-10-04 PWA 第 4 步
   --------------------------------------------------------------------------
   一人一张卡。**不加边框**，靠双色阴影和留白分层（全篇铁律）。
   这一页**只读** —— 所以卡上没有任何输入控件（没有编辑入口）。
   ========================================================================== */
.contact-card {
  padding: 14px 16px;
}
.contact-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.contact-name {
  margin: 0;
  font-size: 16.5px;
  font-weight: 500;
  color: var(--ink);
}
/* 「管理员」标记：不引彩色 —— 用一个凹陷小胶囊 */
.contact-badge {
  flex: 0 0 auto;
  padding: 2px 9px;
  border-radius: var(--r-pill);
  background: var(--bg);
  box-shadow: var(--sh-in);
  font-size: 10.5px;
  color: var(--ink-3);
}
/* 关系档（身份层那五个词之一）靠右 */
.contact-rel {
  margin-left: auto;
  font-size: 12px;
  color: var(--ink-2);
}
/* 她该怎么做 —— 引一句 identity.js 里的规矩，一行小字 */
.contact-how {
  margin: 8px 0 0;
  font-size: 12px;
  line-height: 1.65;
  color: var(--ink-3);
}
.contact-meta {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
/* QQ 号：最次要的信息，浅到快看不见 */
.contact-id {
  margin: 4px 0 0;
  font-size: 11px;
  color: var(--ink-4);
  font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   个性化：昵称 + 头像 —— 2026-10-04 用户要求
   --------------------------------------------------------------------------
   一块卡里：左边一个圆形头像按钮（凹陷底、凸起面），右边昵称输入框。
   铁律不变：不引彩色、不加边框、双色阴影。
   ========================================================================== */
.me-row {
  display: flex;
  /**
   * ⚠️ **`flex-start` + 手调上边距**（2026-10-04 截图之后改的）。
   *
   * 右边那一列是「标签 / 输入框 / 提示」三行，而头像只有一个圆。
   * 用 `center` 的话圆跑到整列中间（看着"居中了但偏下"）；
   * 光用 `flex-start` 又和「显示昵称」那行**标签**齐平（也不齐观感）。
   *
   * 截图看出来的正确位置是**和输入框那一行对齐** ——
   * 所以给头像加一个约等于「标签高度 + 间距」的上边距。
   */
  align-items: flex-start;
  gap: 14px;
}
.me-row .avatar-btn {
  margin-top: 20px;
}
/* 头像按钮：圆形，凹陷底 + 悬停时微微凸起 */
.avatar-btn {
  flex: 0 0 auto;
  width: 68px;
  height: 68px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--bg);
  box-shadow: var(--sh-in);
  cursor: pointer;
  overflow: hidden;
  color: var(--ink-3);
  transition: box-shadow 160ms var(--ease);
}
.avatar-btn:hover {
  box-shadow: var(--sh-out-sm);
}
.avatar-btn:active {
  box-shadow: var(--sh-in);
}
.avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* 没设头像时显示一个字（我的取账号名首字，她的取名字首字） */
.avatar-fallback {
  font-size: 24px;
  font-weight: 500;
  color: var(--ink-2);
}
/* 右边那一列（昵称 + 提示 + 可能的"去掉头像"） */
.me-fields {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}
.me-fields .field-input {
  width: 100%;
}

/* ==========================================================================
   ★★★ 配色 + 动效（2026-10-04 用户给了 5 组参考图之后加的）
   --------------------------------------------------------------------------
   用户原话：
     > 改一下配色，参考这5个配色方式，不同界面可以用不同配色
     > 可以给界面增加点动效，交互反馈感会强一点

   ## 5 组参考（从图里抄的 HEX，一个数都没改）

   | 组 | 主色 | 副色 | 用在哪 |
   | --- | --- | --- | --- |
   | A 机甲灰 + 电光蓝 | `#2C2C34` | `#00D4FF` | 聊天页（她的主界面） |
   | B 冠军黄 + 极速蓝 | `#FFD300` | `#0066FF` | 主页（最有活力的那一屏） |
   | C 丁香紫 + 晨雾灰 | `#B19CD9` | `#D3D3D3` | 定制她（柔和、私人） |
   | D 晴空蓝 + 雪雾白 | `#89C2FF` | `#E6F7FF` | 通讯录 / 设置（清爽、信息多） |
   | E 海湾蓝 + 星云紫 | `#00B4D8` | `#9F7AEA` | 通话 / 朋友圈（科技感） |

   ⚠️ **底色不动**：新拟物的整套阴影（`--sh-out` / `--sh-in`）是照着
      浅灰底调的，换底色会让它们全废。所以**只加强调色**，
      底色仍是 `--bg: #ececec` 那一档。
   ========================================================================== */

/* ---------- A 聊天页：机甲灰 + 电光蓝 ---------- */
#screen-chat {
  --accent: #00d4ff;
  --accent-deep: #00a8cc;
  --accent-soft: #d6f6ff;
  --accent-ink: #06323d;
  --accent-glow: rgba(0, 212, 255, 0.38);
}
/* 我自己说的话：用强调色（一眼分清谁说的） */
#screen-chat .bubble--out {
  background: var(--accent-soft);
  color: #14343d;
  box-shadow: var(--sh-out-sm);
}
/* 发送键：强调色 */
#screen-chat .round-btn--accent {
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--sh-out-sm), 0 4px 14px var(--accent-glow);
}

/* ---------- B 主页：冠军黄 + 极速蓝 ---------- */
#screen-home {
  --accent: #0066ff;
  --accent-deep: #0047b3;
  --accent-soft: #e3eeff;
  --accent-ink: #ffffff;
  --accent-glow: rgba(0, 102, 255, 0.32);
}
/* 她名字那一行用极速蓝；入口胶囊悬停时左边亮一条黄 */
#screen-home .hero-name {
  color: var(--accent);
}
#screen-home .home-link {
  position: relative;
  overflow: hidden;
}
#screen-home .home-link::before {
  content: '';
  position: absolute;
  left: 0;
  top: 12%;
  bottom: 12%;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: #ffd300;
  transform: scaleY(0);
  transform-origin: center;
  transition: transform 180ms var(--ease);
}
#screen-home .home-link:hover::before,
#screen-home .home-link:focus-visible::before {
  transform: scaleY(1);
}
#screen-home .stat-value {
  color: var(--accent);
}

/* ---------- C 定制她：丁香紫 + 晨雾灰 ---------- */
#screen-persona {
  --accent: #9f7aea;
  --accent-deep: #7c5ccc;
  --accent-soft: #f0e9fb;
  --accent-ink: #ffffff;
  --accent-glow: rgba(159, 122, 234, 0.32);
}
#screen-persona .about-h {
  color: var(--accent-deep);
}
#screen-persona .field-input:focus {
  box-shadow: inset -2px -2px 6px rgba(255, 255, 255, 0.85),
    inset 6px 6px 14px rgba(0, 0, 0, 0.1),
    0 0 0 3px var(--accent-glow);
}

/* ---------- D 通讯录 / 昵称头像 / 模型 / 关于：晴空蓝 + 雪雾白 ---------- */
#screen-contacts,
#screen-me,
#screen-models,
#screen-about {
  --accent: #4a9bff;
  --accent-deep: #1f6fe0;
  --accent-soft: #e6f7ff;
  --accent-ink: #ffffff;
  --accent-glow: rgba(137, 194, 255, 0.4);
}
#screen-contacts .contact-name,
#screen-me .about-h,
#screen-models .about-h {
  color: var(--accent-deep);
}
/* 「管理员」标记：从灰胶囊改成淡蓝（一眼看出来） */
#screen-contacts .contact-badge {
  background: var(--accent-soft);
  color: var(--accent-deep);
}
#screen-contacts .stat-value {
  color: var(--accent-deep);
}
/* 头像那圈：悬停时亮一下强调色 */
#screen-me .avatar-btn:hover {
  box-shadow: var(--sh-out-sm), 0 0 0 3px var(--accent-glow);
}

/* ---------- E 通话：海湾蓝 + 星云紫 ---------- */
.call-screen {
  --accent: #00b4d8;
  --accent-deep: #0088a8;
  --accent-soft: #d8f6fb;
  --accent-ink: #ffffff;
  --accent-glow: rgba(0, 180, 216, 0.4);
}
/* 通话中那个大圆：在说的时候发海湾蓝的光 */
.call-orb.is-speaking {
  box-shadow: var(--sh-out), 0 0 0 6px var(--accent-glow);
  color: var(--accent-deep);
}
.call-orb.is-listening {
  color: var(--accent-deep);
}
.call-state {
  color: var(--accent-deep);
}
/* 挂断键保持"最显眼" —— 用星云紫的深版（不是红，全篇不引红） */
.call-hangup {
  background: #9f7aea;
  color: #ffffff;
  box-shadow: var(--sh-out-sm), 0 4px 16px rgba(159, 122, 234, 0.4);
}
.call-line--her .call-line-text {
  background: var(--accent-soft);
  color: #0b3a44;
}

/* ==========================================================================
   动效（用户要求："交互反馈感会强一点"）
   --------------------------------------------------------------------------
   ⚠️ 三条原则：
     1. **短**（120~260ms）—— 长了会显得卡
     2. **只用 transform / opacity / box-shadow** —— 不碰 width/height
        （那会触发重排，手机上会掉帧）
     3. **尊重 `prefers-reduced-motion`** —— 前庭敏感的人会晕
   ========================================================================== */

/* ---------- ① 屏幕切进来时淡入上浮 ---------- */
@keyframes screen-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}
.screen.app:not([hidden]) {
  animation: screen-in 220ms var(--ease);
}

/* ---------- ② 卡片依次浮起来（错开 40ms，有"一层层来"的感觉） ---------- */
@keyframes card-in {
  from { opacity: 0; transform: translateY(10px) scale(0.995); }
  to { opacity: 1; transform: none; }
}
.card,
.home-link,
.contact-card {
  animation: card-in 260ms var(--ease) backwards;
}
.card:nth-child(1), .home-link:nth-child(1) { animation-delay: 0ms; }
.card:nth-child(2), .home-link:nth-child(2) { animation-delay: 40ms; }
.card:nth-child(3), .home-link:nth-child(3) { animation-delay: 80ms; }
.card:nth-child(4), .home-link:nth-child(4) { animation-delay: 120ms; }
.card:nth-child(5), .home-link:nth-child(5) { animation-delay: 160ms; }
.card:nth-child(6), .home-link:nth-child(6) { animation-delay: 200ms; }
.card:nth-child(n+7) { animation-delay: 240ms; }

/* ---------- ③ 按钮：按下时凹进去 + 轻轻缩一下 ---------- */
/* ⚠️ 原来只有 `:active` 换阴影，没有"缩" —— 加上之后手感明显更实 */
.round-btn,
.icon-btn,
.sheet-btn,
.home-link,
.avatar-btn,
.find-nav,
.attach-del {
  transition: box-shadow 140ms var(--ease), transform 140ms var(--ease),
    color 140ms var(--ease), background 180ms var(--ease);
}
.round-btn:active:not(:disabled),
.icon-btn:active,
.sheet-btn:active,
.home-link:active,
.avatar-btn:active {
  transform: scale(0.94);
}

/* ---------- ④ 气泡进来时"冒"出来 ---------- */
@keyframes bubble-in {
  from { opacity: 0; transform: translateY(6px) scale(0.97); }
  to { opacity: 1; transform: none; }
}
.row .bubble {
  animation: bubble-in 180ms var(--ease);
}
/* 打字指示点：三颗依次跳动（原来应该是有的，这里补上节奏） */
@keyframes tdot-bounce {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
  30% { transform: translateY(-3px); opacity: 1; }
}
.bubble--typing .tdot {
  animation: tdot-bounce 1.1s ease-in-out infinite;
}
.bubble--typing .tdot:nth-child(2) { animation-delay: 0.15s; }
.bubble--typing .tdot:nth-child(3) { animation-delay: 0.3s; }

/* ---------- ⑤ 输入栏聚焦时：整条轨道亮一圈强调色 ---------- */
.send-form {
  transition: box-shadow 200ms var(--ease);
}
.send-form:focus-within {
  box-shadow: var(--sh-out), 0 0 0 3px var(--accent-glow);
}

/* ---------- ⑥ 待发附件"跳"进来 ---------- */
@keyframes chip-in {
  from { opacity: 0; transform: scale(0.85); }
  to { opacity: 1; transform: none; }
}
.attach-chip {
  animation: chip-in 160ms var(--ease);
}

/* ---------- ⑦ 表情包格子悬停放大一点（可点性更明确） ---------- */
.sticker-cell,
.bg-swatch {
  transition: box-shadow 140ms var(--ease), transform 140ms var(--ease);
}
.sticker-cell:hover,
.bg-swatch:hover {
  transform: translateY(-2px);
}

/* ---------- ⑧ 接通电话时那个圆"呼吸"一下 ---------- */
@keyframes orb-connect {
  from { transform: scale(0.88); opacity: 0.6; }
  to { transform: none; opacity: 1; }
}
.call-orb {
  animation: orb-connect 420ms var(--ease);
}

/* ---------- ⑨ 版本号/数字变化时闪一下（有"更新了"的反馈） ---------- */
@keyframes num-flash {
  0% { color: var(--accent); }
  100% { color: inherit; }
}
.stat-value {
  animation: num-flash 700ms var(--ease);
}

/* ---------- ⑩ 尊重"减少动态效果" ---------- */
@media (prefers-reduced-motion: reduce) {
  .screen.app:not([hidden]),
  .card, .home-link, .contact-card,
  .row .bubble, .attach-chip, .call-orb, .stat-value {
    animation: none !important;
  }
  .bubble--typing .tdot {
    animation: none !important;
    opacity: 0.6;
  }
  .round-btn, .icon-btn, .sheet-btn, .home-link, .avatar-btn,
  .sticker-cell, .bg-swatch, .send-form {
    transition: none !important;
  }
  .round-btn:active:not(:disabled),
  .icon-btn:active, .sheet-btn:active, .home-link:active,
  .avatar-btn:active, .sticker-cell:hover, .bg-swatch:hover {
    transform: none !important;
  }
}

/* ==========================================================================
   群聊 —— 2026-10-04 PWA 第 4 步
   --------------------------------------------------------------------------
   ⚠️ 这个「群」**只在网页里**（约束：只有纱希一个 QQ 号、不新增容器）。
   布局和私聊几乎一样，区别在：
     · 气泡上方有**说话人**（群里不止两个人，不说清是谁会很乱）
     · 我的发言也靠右（和私聊一致），但**别人**的靠左
   ========================================================================== */

/* 房间列表里的一张卡 */
.room-card {
  display: block;
  width: 100%;
  text-align: left;
  padding: 14px 16px;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.room-name {
  margin: 0;
  font-size: 16px;
  font-weight: 500;
  color: var(--accent-deep, var(--ink));
}
.room-meta {
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--ink-3);
}

/* 群里气泡上方的说话人 */
.room-who {
  margin: 0 0 3px;
  font-size: 11.5px;
  color: var(--ink-3);
  padding-left: 4px;
}
/* 我说的话那一侧，说话人靠右 */
.row--out .room-who {
  text-align: right;
  padding-right: 4px;
}
.room-at {
  margin: 3px 0 0;
  font-size: 10.5px;
  color: var(--ink-4);
  padding-left: 4px;
  font-variant-numeric: tabular-nums;
}
.row--out .room-at {
  text-align: right;
  padding-right: 4px;
}

/* 「她这次不会接」那种提示 —— 居中、淡，别当成消息 */
.room-note {
  margin: 8px auto;
  max-width: 86%;
  text-align: center;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--ink-4);
}

/* 刹车状态卡（按着的时候要**很显眼** —— 不然用户以为她坏了） */
.brake-card.is-engaged {
  box-shadow: var(--sh-in-deep);
}
.brake-card.is-engaged .about-h {
  color: #7c5ccc;
}

/* 房间里那条常驻刹车提示条 */
.brake-bar {
  flex: 0 0 auto;
  margin: 0 14px 6px;
  padding: 8px 12px;
  border-radius: var(--r-card);
  background: #f0e9fb;
  color: #5b3fa0;
  font-size: 12px;
  line-height: 1.6;
}
/* ⚠️ 和 `.attach-preview` 同一个坑：设了样式的元素要显式处理 [hidden] */
.brake-bar[hidden] {
  display: none;
}

/* 群聊屏的配色：清新一点（它比私聊"公共"） */
#screen-rooms, #screen-room {
  --accent: #4a9bff;
  --accent-deep: #1f6fe0;
  --accent-soft: #e6f7ff;
  --accent-ink: #ffffff;
  --accent-glow: rgba(74, 155, 255, 0.34);
}
#screen-rooms .room-name {
  color: var(--accent-deep);
}

/* ==========================================================================
   ★★★ [hidden] 兜底（2026-10-04 一次性修掉 4 个幽灵元素）
   --------------------------------------------------------------------------
   ## 这个坑为什么反复出现

   浏览器默认样式里有 [hidden] { display: none }，但它**优先级极低**：
   **只要作者样式给这个元素写了 display，默认那条就失效**。

   于是 <div hidden> **照样显示** —— 而且**不报任何错**。

   ## 已被这个坑咬过的（五次）

   | 元素 | 用户看到的现象 |
   | --- | --- |
   | .attach-preview | 输入框上方挂着一条**空的凹陷胶囊** |
   | **.find-bar** | ★ **搜索栏一直显示，盖住顶栏三个按钮 → 点不动** |
   | #typing | 打字气泡一直显示 |
   | #call-type-form | 通话时文字输入框一直显示 |
   | .brand | 登录页 logo（另一屏的，看不到但同样错） |

   ## 所以：一次性兜底，不再"发现一个补一个"

   ⚠️ :where() 的优先级是 0，所以它**只兜底**，不会意外盖掉别的规则
   （比如 `.screen[hidden]` 那种显式写过的，行为不变）。

   ⚠️ 为什么加 !important：有些规则是 `display: flex !important` 这种，
      不加的话 `:where()` 的 0 优先级还是压不住。

   以后再加"可能带 hidden 又有 display"的元素时**不用管** ——
   这一条已经全兜住了。`tools/check-hidden-elements.mjs` 会盯着实际效果。
   ========================================================================== */
:where([hidden]) {
  display: none !important;
}

/* ==========================================================================
   ★★★ 配色"铺开"（2026-10-04 用户反馈"没看到效果"）
   --------------------------------------------------------------------------
   ## 用户为什么觉得没配色

   我第一版只把 `--accent` 用在**几个小地方**（`.hero-name`、`.stat-value`、
   `.contact-name`）。于是一个页面上**只有一两个词是彩色的**，整屏看着还是灰的
   —— 用户看到的就是"没效果"。

   ## 这次怎么改

   三个层次**同时**上色，但都克制：

   | 层 | 做法 | 效果 |
   | --- | --- | --- |
   | ① 卡片底 | `--accent-soft` 极淡版（8% 透明度） | 整页有色调，不刺眼 |
   | ② 分组标题 | 彩色**药丸** | 一眼看出"这页是什么色" |
   | ③ 输入框 | 淡色底 + 聚焦时亮一圈 | 有交互感 |

   ⚠️ **底色（`--bg`）和阴影不动** —— 新拟物那套双向阴影是照着浅灰底调的，
      换底色会让 `--sh-out` / `--sh-in` 全部失准（这个坑记过一次了）。
      所以颜色是"**加在卡片上**"，不是"换成有色底"。
   ========================================================================== */

/* ---------- ① 每个界面的卡片带一点自己的色调 ---------- */
/**
 * ⚠️ 用 `color-mix` 而不是写死一个淡色值 ——
 *    每个界面的 `--accent` 不同，写死的话每页都要算一遍颜色。
 *    `color-mix` 在 Edge/Chrome 111+ 都有（2026 年的浏览器全都支持）。
 */
#screen-home .card,
#screen-persona .card,
#screen-contacts .card,
#screen-me .card,
#screen-rooms .card,
#screen-room .card,
#screen-models .card,
#screen-about .card {
  background: color-mix(in srgb, var(--accent) 7%, var(--bg-soft));
}

/* ---------- ② 分组标题做成彩色药丸 ---------- */
/**
 * 「定制她」那页的「身份 / 性格 / 说话方式」这些分组标题 ——
 * 从一行灰字变成**彩色药丸**，一眼看出这页的色调。
 */
/**
 * ⚠️ 分组标题的类名是 **`.about-h`** —— 我第一版写了个臆想的
 *    `form-group-title`（那个类名**根本不存在**），于是这条规则**静默失效**：
 *    CSS 里写错选择器**不报错**，只是不生效。
 *
 * 用 `:scope > .about-h` 限定成"卡片**直接子元素**的标题"，
 * 免得动到卡片里面更深层的标题。
 */
#screen-persona .card > .about-h,
#screen-models .card > .about-h,
#screen-me .card > .about-h,
#screen-contacts .stat-label {
  display: inline-block;
  padding: 3px 12px;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent-deep);
  font-weight: 600;
  font-size: 12.5px;
}
/** 「关于她」那种大标题保持文字（它是整页的标题，不该变成药丸） */
#screen-about .about-h {
  color: var(--accent-deep);
}

/* ---------- ③ 输入框 + 聚焦 ---------- */
#screen-persona .field-input,
#screen-me .field-input,
#screen-models .field-input {
  background: color-mix(in srgb, var(--accent) 5%, var(--bg));
}
#screen-persona .field-input:focus,
#screen-me .field-input:focus,
#screen-models .field-input:focus {
  background: color-mix(in srgb, var(--accent) 10%, var(--bg));
  box-shadow: var(--sh-in), 0 0 0 3px var(--accent-glow);
}

/* ---------- ④ 主页那三个状态格 ---------- */
#screen-home .stat-value {
  color: var(--accent-deep);
  font-weight: 600;
}

/* ---------- ⑤ 群聊 / 通讯录的卡片标题 ---------- */
#screen-rooms .room-name,
#screen-contacts .contact-name {
  color: var(--accent-deep);
}

/* ---------- ⑥ 顶栏的状态点用强调色（每页都不同） ---------- */
#screen-chat .conn-dot.is-on {
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-glow);
}

/* ==========================================================================
   点击音效的菜单项（2026-10-04 用户要求）
   --------------------------------------------------------------------------
   两个部件：一条分隔线、一行「音量 [滑块] 60」。
   ⚠️ 滑块**必须能被摸到** —— 拇指大小，不然手机上拖不动。
   ========================================================================== */
.menu-sep {
  height: 1px;
  margin: 6px 10px;
  /* 用内阴影画一条极淡的分隔线（不引 border，全篇铁律） */
  background: var(--bg-deep);
  opacity: 0.7;
}

.menu-vol {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 16px 10px;
}
/* 关掉音效时整行灰掉（拖了没反应就不会让人以为坏了） */
.menu-vol.is-off {
  opacity: 0.42;
}
.menu-vol-label {
  flex: 0 0 auto;
  font-size: 13px;
  color: var(--ink-3);
}
.menu-vol-range {
  flex: 1 1 auto;
  min-width: 0;
  height: 22px;
  margin: 0;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  cursor: pointer;
}
/* 轨道：凹陷（新拟物那套） */
.menu-vol-range::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--bg-sunken);
  box-shadow: var(--sh-in);
}
.menu-vol-range::-moz-range-track {
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--bg-sunken);
  box-shadow: var(--sh-in);
}
/* 滑块头：凸起的小圆 */
.menu-vol-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 18px;
  margin-top: -6px;
  border: 0;
  border-radius: 50%;
  background: var(--bg-soft);
  box-shadow: var(--sh-out-sm);
}
.menu-vol-range::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border: 0;
  border-radius: 50%;
  background: var(--bg-soft);
  box-shadow: var(--sh-out-sm);
}
.menu-vol-num {
  flex: 0 0 auto;
  width: 2.2em;
  text-align: right;
  font-size: 12px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
/* 「音效 关」的时候那个菜单项淡一点 */
#menu-sfx.is-off {
  color: var(--ink-4);
}

/* ==========================================================================
   形象图素材库的网格（2026-10-04 用户要求「像表情包一样能加能删」）
   ========================================================================== */
.album-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 10px;
  margin: 0 0 4px;
}
.album-cell {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: var(--r-card);
  overflow: hidden;
  background: var(--bg-sunken);
  box-shadow: var(--sh-in);
}
.album-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* 删除键：右上角一个半透明小圆（不抢图的主体） */
.album-del {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(28, 28, 30, 0.62);
  color: #fff;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  backdrop-filter: blur(6px);
}
.album-del:disabled {
  opacity: 0.5;
}
/* 「自带」标记：左下角一个淡标签 */
.album-tag {
  position: absolute;
  left: 6px;
  bottom: 6px;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.82);
  color: #555;
  font-size: 10px;
}

/* 两个键并排（保存 / 恢复默认 / 试听一句） */
.btn-row {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  /**
   * ⚠️ **上面留 14px**（2026-10-05 用户报「贴太紧了」）。
   *
   * 原来只有 4px —— 而它上面紧挨着的是「留空就是用现在这个」那行小字，
   * 于是小字和按钮**几乎粘在一起**（用户截图里看得清清楚楚）。
   *
   * ⚠️ 换行之后的按钮（比如「试听一句」）也要有行距 ——
   *    flex 的 `gap` 两个方向都管（行内 + 行间），所以 12px 已经覆盖了。
   */
  margin-top: 14px;
}

/* ▼ 窄屏：网格更密一点 */
@media (max-width: 380px) {
  .album-grid {
    grid-template-columns: repeat(auto-fill, minmax(82px, 1fr));
    gap: 8px;
  }
}

/* ==========================================================================
   音效选择页（2026-10-04 用户要求「再加个音效选择，用户自己也能自定义音效」）
   ========================================================================== */
.sfx-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 10px;
}
.sfx-cell {
  position: relative;
  /**
   * 🚨 **改成竖排**（2026-10-05 用户报："按键跟边框贴一起来"）。
   *
   * ⚠️ 原来是 `display:flex`（横排）+ `.sfx-tag { position:absolute; bottom:-4px }` ——
   *    那个 `-4px` 让「自带」标签**探出按钮底边**，
   *    而按钮底下**没有留白**，于是标签和按钮一起**压在卡片的边框上**。
   *    真机截图里就是"按键跟边框粘在一起"。
   *
   * ✅ 竖排 + `gap`：按钮一行、标签一行，**都在格子里面**，
   *    再也不会探出去（标签也不再需要 `absolute`）。
   */
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
}
/* 一格一个音效：点它 = 试听 + 设为当前 */
.sfx-pick {
  flex: 1 1 auto;
  min-width: 0;
  padding: 14px 12px;
  border: 0;
  border-radius: var(--r-card);
  background: var(--bg-soft);
  box-shadow: var(--sh-out-sm);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
  text-align: left;
  /** ⚠️ 长的中文名要能换行（不换会被截掉，用户认不出） */
  overflow-wrap: anywhere;
  transition: box-shadow 160ms var(--ease), transform 160ms var(--ease);
}
.sfx-pick:active {
  transform: scale(0.97);
}
/* 当前选中的那个：凹进去 + 强调色 */
.sfx-cell.is-on .sfx-pick {
  box-shadow: var(--sh-in), 0 0 0 2px var(--accent-glow);
  background: var(--accent-soft);
  color: var(--accent-deep);
  font-weight: 600;
}
/* 删除键（只给用户传的） */
.sfx-del {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(28, 28, 30, 0.62);
  color: #fff;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.sfx-del:disabled {
  opacity: 0.5;
}
/**
 * 「自带」标记 —— ⚠️ **不再 `absolute`**（见上面 `.sfx-cell` 那段）：
 * 它现在是格子里的第二行，自己占位，不会压到任何边框。
 */
.sfx-tag {
  align-self: flex-start;
  padding: 1px 8px;
  border-radius: var(--r-pill);
  background: var(--bg-deep);
  color: var(--ink-3);
  font-size: 10px;
  line-height: 1.6;
}

/* ==========================================================================
   ★★ 再铺开一档配色（2026-10-04 用户说"还是没看到"）
   --------------------------------------------------------------------------
   ## 为什么之前"看不到"
   我第一版把颜色用在：主页名字、状态数字、卡片淡底（7%）。
   而**主页那些入口胶囊（`.home-link`）占了大半屏，它们还是纯灰** ——
   所以整体观感仍然是"灰色界面"。

   这次把入口胶囊的**标题和左边那条竖线**上色（一眼能看到）。
   ========================================================================== */

/* 主页入口胶囊的标题用强调色 */
#screen-home .home-link-label {
  color: var(--accent-deep);
  font-weight: 500;
}
/* 左侧那条竖线：平时淡、悬停亮（那个黄条在 `#screen-home .home-link::before` 里） */
#screen-home .home-link {
  border-left: 3px solid color-mix(in srgb, var(--accent) 30%, transparent);
}

/* 群聊/通讯录/音效那些卡片标题也上色 */
#screen-rooms .about-h,
#screen-contacts .about-h,
#screen-me .about-h,
#screen-models .about-h,
#screen-about .about-h {
  color: var(--accent-deep);
}

/* 定制她那页每个输入框上面的标签也用淡强调色（整屏有色调） */
#screen-persona .field-label,
#screen-me .field-label,
#screen-models .field-label {
  color: color-mix(in srgb, var(--accent-deep) 72%, var(--ink-2));
}

/* ==========================================================================
   ★★★ 朋友圈 / 动态流（2026-10-05 PWA 第 5 步）
   --------------------------------------------------------------------------
   ⚠️ 每条动态**自带**「AI 生成」标记（不靠页脚那行）——
      《人工智能拟人化互动服务管理暂行办法》第十八条。
      用户可能只截一条转发出去，所以标注要跟着那一条走。
   ========================================================================== */
.moment {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.moment-who {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.moment-name {
  font-weight: 600;
  color: var(--accent-deep);
  font-size: 14px;
}
/* 心情标签：小胶囊 */
.moment-mood {
  padding: 1px 8px;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent-deep);
  font-size: 11px;
}
.moment-time {
  margin-left: auto;
  color: var(--ink-3);
  font-size: 11.5px;
}
/**
 * ★★★ **"这条给谁看"那个小标签**（2026-10-08 用户要求动态权限）。
 *
 * ⚠️ 三条：
 * ```
 * ① 别抢眼（它是**提醒**，不是标题）—— 所以小、浅底、圆角
 * ② `moment-time` 是 `margin-left: auto`（靠右）的 ⇒ 这个标签排在它**前面**，
 *    所以**不能**也写 auto（两个 auto 会把这一行挤散）
 * ③ 三档要有**颜色差别**（一眼看出"这条是公开的"）——
 *    ⚠️ 但只用极浅的底 + 深一点的字，别做成红黄绿那种警报色
 * ```
 */
.moment-vis-tag {
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: var(--r-pill, 999px);
  background: var(--bg-soft);
  color: var(--ink-2);
  font-size: 11px;
}
/** 公开：最"松"的一档 */
.moment-vis-tag.is-public {
  background: color-mix(in srgb, var(--ok, #38a169) 14%, transparent);
  color: var(--ok, #2f855a);
}
/** 仅自己：最"紧"的一档 */
.moment-vis-tag.is-private {
  background: color-mix(in srgb, var(--ink-3) 16%, transparent);
  color: var(--ink-2);
}
/** 发动态那张卡里的"谁能看"一行 */
.moment-vis {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
}
.moment-vis-sel {
  flex: 1 1 auto;
  min-width: 0;
}
.moment-text {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--ink);
  /** ⚠️ 长的中文/网址要能断行，不然会把卡片撑破 */
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
/* 九宫格（和微信朋友圈一样的排布） */
.moment-imgs {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px;
}
.moment-img {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 8px;
  display: block;
  background: var(--bg-sunken);
}
.moment-bar {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  padding-top: 4px;
  border-top: 1px solid var(--line);
}
.moment-act {
  padding: 5px 12px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--bg-sunken);
  color: var(--ink-2);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
  box-shadow: var(--sh-out-sm);
  transition: box-shadow 140ms var(--ease), color 140ms var(--ease);
}
.moment-act:active {
  transform: scale(0.96);
}
/* 我赞过的：填上强调色 */
.moment-act.is-on {
  color: var(--accent-deep);
  box-shadow: var(--sh-in), 0 0 0 1px var(--accent-glow);
}
.moment-del {
  margin-left: auto;
  color: #c0392b;
}
.moment-comments {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 8px 10px;
  border-radius: 10px;
  background: var(--bg-sunken);
}
.moment-comment {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-2);
  overflow-wrap: anywhere;
}
.moment-comment b {
  color: var(--accent-deep);
  font-weight: 600;
}
/* ★ 每条自带的合规标注 */
.moment-ai {
  margin: 0;
  font-size: 10.5px;
  color: var(--ink-3);
  opacity: 0.75;
}
.moment-input {
  width: 100%;
  resize: vertical;
  font: inherit;
  font-size: 14px;
  line-height: 1.55;
}

/* ==========================================================================
   ★★★ 头像裁剪（2026-10-05 用户要求「头像加个自定义裁剪」）
   --------------------------------------------------------------------------
   ⚠️ 三个要点：
     ① 裁剪框**固定**、动的是下面的图（手机上比"框动图不动"好对准得多）
     ② 加一个**圆形遮罩** —— 头像显示出来是圆的，让用户提前看到效果
     ③ 整层要**盖住全屏**并吃掉滚动（不然手机上拖图会把页面滚走）
   ========================================================================== */
.crop-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(20, 20, 22, 0.62);
  backdrop-filter: blur(3px);
  /* ⚠️ 吃掉手势 —— 拖图时不要让底下的页面跟着滚 */
  touch-action: none;
  overscroll-behavior: contain;
  animation: crop-fade 140ms var(--ease);
}
@keyframes crop-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}
.crop-panel {
  width: 100%;
  max-width: 360px;
  padding: 16px 16px 14px;
  border-radius: var(--r-card);
  background: var(--bg-soft);
  box-shadow: var(--sh-float);
  animation: crop-rise 180ms var(--ease);
}
@keyframes crop-rise {
  from { transform: translateY(10px) scale(0.98); opacity: 0; }
  to { transform: none; opacity: 1; }
}
.crop-title {
  margin: 0 0 12px;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink);
  text-align: center;
}
/* 裁剪台：正方形，中间是画布 + 圆形遮罩 */
.crop-stage {
  position: relative;
  margin: 0 auto;
  border-radius: 12px;
  overflow: hidden;
  background: #e6e6e6;
  cursor: grab;
  /* ⚠️ 必须 —— 不然手机上拖动会变成滚页面（见文件头 ③） */
  touch-action: none;
  box-shadow: var(--sh-in);
}
.crop-stage:active {
  cursor: grabbing;
}
.crop-canvas {
  display: block;
  width: 100%;
  height: 100%;
}
/**
 * ★ **方框**（不是圆形遮罩）—— 2026-10-05 用户说「编辑空出来」。
 *
 * ⚠️ 头像**存的是方图**，圆形遮罩会让人以为"裁出来是圆的"，
 *    而且**四角看不见** —— 想说"我要那个角"时完全没法对准。
 *
 * ⚠️ 用**四角刻度**（下面那个 ::before/::after）代替整圈边框 ——
 *    整圈边框会盖住图片边缘，看不清裁到哪里。
 */
.crop-frame {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: 10px;
  box-shadow: 0 0 0 9999px rgba(20, 20, 22, 0.42);
}
/* 四角：两个小直角，标出"裁到这儿" */
.crop-frame::before,
.crop-frame::after {
  content: '';
  position: absolute;
  width: 22px;
  height: 22px;
  border: 3px solid rgba(255, 255, 255, 0.95);
}
.crop-frame::before {
  top: 0;
  left: 0;
  border-right: 0;
  border-bottom: 0;
  border-top-left-radius: 10px;
}
.crop-frame::after {
  right: 0;
  bottom: 0;
  border-left: 0;
  border-top: 0;
  border-bottom-right-radius: 10px;
}
.crop-hint {
  margin: 12px 0 10px;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--ink-3);
  text-align: center;
}
.crop-zoom {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}
.crop-zoom-icon {
  font-size: 11px;
  color: var(--ink-3);
}
.crop-slider {
  flex: 1 1 auto;
  min-width: 0;
  accent-color: var(--accent);
}
.crop-btns {
  display: flex;
  gap: 10px;
}
.crop-btn {
  flex: 1 1 0;
  padding: 11px 12px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--bg-sunken);
  color: var(--ink-2);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
  box-shadow: var(--sh-out-sm);
  transition: box-shadow 140ms var(--ease), transform 140ms var(--ease);
}
.crop-btn:active {
  transform: scale(0.97);
}
.crop-btn--primary {
  background: var(--accent-soft);
  color: var(--accent-deep);
  font-weight: 600;
  box-shadow: var(--sh-out-sm), 0 0 0 1px var(--accent-glow);
}

/* ==========================================================================
   ★ 麦克风模式的小角标（2026-10-05 用户要求「语音开关」）
   --------------------------------------------------------------------------
   ⚠️ 用户必须**一眼看出**现在按下去是"发语音"还是"转文字" ——
      不然他按了之后才发现行为不对。
      用右下角一个小点区分：实心 = 发语音，空心/方 = 转文字。
   ========================================================================== */
#btn-record {
  position: relative;
}
#btn-record::after {
  content: '';
  position: absolute;
  right: 6px;
  bottom: 6px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 2px var(--bg);
  transition: background 160ms var(--ease), border-radius 160ms var(--ease);
}
/* 转文字模式：角标变成小方块（"字"的感觉），而且变浅 */
#btn-record[data-mic='text']::after {
  border-radius: 2px;
  background: var(--ink-3);
}
/* 正在录音：角标变红并呼吸 */
#btn-record.is-recording::after {
  background: #e5484d;
  animation: mic-pulse 900ms ease-in-out infinite;
}
@keyframes mic-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* ==========================================================================
   ★★★ 消息操作（2026-10-05 用户要求）
   --------------------------------------------------------------------------
   长按/右键弹出的菜单 + 多选底栏 + 引用条 + 收藏夹。
   ⚠️ 菜单是 `position: fixed` —— 用绝对定位的话滚动时会跑偏，
      而且这个项目栽过"面板渲染到屏幕外"（见 .sheet 那次）。
   ========================================================================== */
.msg-menu {
  position: fixed;
  z-index: 210;
  min-width: 132px;
  padding: 6px;
  border-radius: 14px;
  background: var(--bg-soft);
  box-shadow: var(--sh-float);
  display: flex;
  flex-direction: column;
  gap: 2px;
  animation: msg-menu-in 120ms var(--ease);
}
@keyframes msg-menu-in {
  from { opacity: 0; transform: scale(0.96); }
  to { opacity: 1; transform: none; }
}
.msg-menu-item {
  padding: 9px 14px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
}
.msg-menu-item:active {
  background: var(--bg-sunken);
}
.msg-menu-item--danger {
  color: #c0392b;
}

/* 多选时的底部操作条（⚠️ fixed，浮在输入栏上面） */
.multi-bar {
  position: fixed;
  left: 8px;
  right: 8px;
  bottom: calc(128px + env(safe-area-inset-bottom));
  z-index: 205;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px;
  border-radius: var(--r-card);
  background: var(--bg-soft);
  box-shadow: var(--sh-float);
  overflow-x: auto;
}
.multi-btn {
  flex: 0 0 auto;
  padding: 8px 12px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--bg-sunken);
  color: var(--ink-2);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  box-shadow: var(--sh-out-sm);
}
.multi-btn:active { transform: scale(0.96); }
.multi-btn--danger { color: #c0392b; }
.multi-count {
  flex: 0 0 auto;
  margin-left: auto;
  padding: 0 8px;
  color: var(--ink-3);
  font-size: 12px;
  white-space: nowrap;
}

/* 多选模式：每条左边一个圈 */
.row-cb {
  position: absolute;
  left: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid var(--ink-3);
  box-sizing: border-box;
}
/**
 * 🚨 **多选模式下【把头像藏起来】**（2026-10-05 晚报加头像时补的）。
 *
 * ⚠️ 那个勾选圈是 `position: absolute; left: 4px` —— 而头像现在正占着
 *    左边那一块（`.row` 的 padding-left 是 26px）⇒ **两者会叠在一起**
 *    （圈压在头像上，看着像界面坏了）。
 *
 * ⚠️ 为什么是"藏头像"而不是"把圈挪开"：
 *    多选时每一行左边本来就只有 26px 这一条窄带，
 *    挪圈的话**气泡会跟着右移**（一整列都动，很难看）。
 *    藏头像则**布局完全不动** —— 气泡还在原来的位置。
 *    ⚠️ 代价：多选时看不出谁说的。可以接受（那一屏本来就是"批量选"，
 *      而且"她说的"和"我说的"本来就分左右）。
 */
.list.is-multi .row-av {
  display: none;
}
.row.is-picked .row-cb {
  background: var(--accent);
  border-color: var(--accent);
}
.row.is-picked .row-cb::after {
  content: '✓';
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: #fff;
  font-size: 12px;
  line-height: 1;
}
.list.is-multi .row {
  position: relative;
  padding-left: 26px;
  cursor: pointer;
}

/* 引用条（在输入框上面） */
.quote-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 6px;
  padding: 7px 10px;
  border-radius: 10px;
  background: var(--bg-sunken);
  border-left: 3px solid var(--accent);
}
.quote-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.quote-who {
  font-size: 11px;
  color: var(--accent-deep);
  font-weight: 600;
}
.quote-text {
  font-size: 12.5px;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.quote-x {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-3);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}

/* 收藏夹 */
.fav-sheet {
  max-height: 62vh;
}
.fav-item {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
}
.fav-item:last-child { border-bottom: 0; }
.fav-main { flex: 1 1 auto; min-width: 0; }
.fav-text {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ink);
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.fav-meta {
  margin: 3px 0 0;
  font-size: 11px;
  color: var(--ink-3);
}
.fav-del {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 50%;
  background: var(--bg-sunken);
  color: #c0392b;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}

/* ==========================================================================
   ★★★ 视频（2026-10-05 用户要求「视频可以加上静音播放」）
   ========================================================================== */
.media-video {
  display: block;
  width: 100%;
  /**
   * ⚠️ **最大高度要限制** —— 竖屏视频（手机拍的）不限的话能占满好几屏，
   *    用户得滚很久才能看到下一条消息。
   */
  max-height: 320px;
  border-radius: 12px;
  background: #000;
  /** ⚠️ `object-fit: contain` —— 竖屏视频用 cover 会把内容裁掉 */
  object-fit: contain;
}
/* ⚠️ 媒体盒子里的视频要和图片一个间距节奏 */
.media-box .media-video + .media-video,
.media-box .media-video + .media-img,
.media-box .media-img + .media-video {
  margin-top: 6px;
}

/* ==========================================================================
   ★★★ 朋友圈设置（2026-10-05）
   频率 / 评论开关 / 黑名单 / 每个人的可见权限
   ========================================================================== */

/**
 * ⚠️ 开关那一行要**点哪儿都行**（不只点那个小方块）——
 *    手机上那个框太小了，点不准。
 */
.switch-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  margin: 6px 0 2px;
  border-radius: 12px;
  background: var(--sunken, rgba(0, 0, 0, 0.04));
  cursor: pointer;
  /** ⚠️ 手指点上去不该选中文字 */
  user-select: none;
}
.switch-row input[type='checkbox'] {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  accent-color: var(--accent-deep, #3f5566);
  margin: 0;
}
.switch-row span {
  font-size: 13.5px;
  line-height: 1.45;
}

/**
 * 一个人的一行：名字 + 权限下拉 + 拉黑键。
 * ⚠️ 手机上要能**换行**（名字长 + 下拉 + 按钮挤不下 414px）——
 *    所以用 flex-wrap，名字那格允许收缩。
 */
.peer-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 8px 0;
  border-top: 1px solid var(--line, rgba(0, 0, 0, 0.07));
}
.peer-row:first-of-type {
  border-top: 0;
}
.peer-name {
  flex: 1 1 90px;
  min-width: 0;
  font-size: 14px;
  font-weight: 600;
  /** ⚠️ 名字太长要省略号（不能把下拉挤出去） */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.peer-sel {
  flex: 0 1 130px;
  /** ⚠️ 下拉框要比输入框矮一点（一行放得下） */
  padding-top: 6px;
  padding-bottom: 6px;
  font-size: 13px;
}
.peer-block {
  flex: 0 0 auto;
  padding: 6px 12px;
  border: 0;
  border-radius: 10px;
  font-size: 13px;
  cursor: pointer;
  color: #b0403a;
  background: var(--sunken, rgba(0, 0, 0, 0.05));
}
.peer-block:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ==========================================================================
   ★★★ 底部四个主 tab（2026-10-05 用户要求"像微信那样分布在屏幕下方"）
   ==========================================================================
   ⚠️ 它是 `position: fixed` —— 这样"内容滚到最底"时它还在。
   ⚠️ 所以页面内容要**留出它的高度**（见下面 `--tabbar-h` 那段），
      不留的话最后一条内容会被压在它下面（用户滚不到）。
   ========================================================================== */
:root {
  /** 内容要给 tab 栏留的高度（含 iOS 那条 home 指示条的安全区） */
  --tabbar-h: calc(58px + env(safe-area-inset-bottom, 0px));
}

.tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  display: flex;
  align-items: stretch;
  /**
   * ⚠️ **毛玻璃**（微信那种质感）—— 内容从下面滑过时能透出来一点。
   *    `backdrop-filter` 在旧浏览器上不生效，所以**底色要给足**
   *    （不然会看到内容从半透明底上透出来、字看不清）。
   */
  background: color-mix(in srgb, var(--card, #fff) 88%, transparent);
  backdrop-filter: blur(18px) saturate(1.4);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  border-top: 1px solid var(--line, rgba(0, 0, 0, 0.07));
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

.tab {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 8px 2px 6px;
  border: 0;
  background: none;
  cursor: pointer;
  /** ⚠️ 未选中的是灰色（微信那样），选中的才上色 */
  color: var(--muted, #8a94a0);
  transition: color 0.16s ease;
  -webkit-tap-highlight-color: transparent;
}
.tab svg {
  width: 23px;
  height: 23px;
  flex: 0 0 auto;
}
.tab-text {
  font-size: 10.5px;
  line-height: 1;
  letter-spacing: 0.2px;
}
/** ⚠️ 选中的那个要**明显**（颜色 + 一点点加粗）—— 不然用户不知道自己在哪一页 */
.tab.is-on {
  color: var(--accent-deep, #3f5566);
}
.tab.is-on .tab-text {
  font-weight: 650;
}
/** ⚠️ 按下去要有反馈（不然像没点上） */
.tab:active {
  transform: scale(0.94);
}
.tab:disabled {
  opacity: 0.45;
  cursor: default;
}

/**
 * ⚠️ **页面内容要给 tab 栏留位置** ——
 *    所有 `.screen.app` 的底部内边距都加上那一段。
 *    不留的话：滚到最底时**最后一条被压在 tab 栏下面**（永远看不到）。
 */
.screen.app {
  padding-bottom: var(--tabbar-h);
}
/** ⚠️ 聊天页是全屏的（它自己有一层输入框），不用留 —— 它不显示 tab 栏 */
#screen-chat {
  padding-bottom: 0;
}

/* ==========================================================================
   ★★★ 个人主页（2026-10-05 用户要求"像 ins 那种"）
   ==========================================================================
   结构：背景图（整条）→ 头像（压在它下沿）→ 名字 + 签名 → 动作键 → 动态
   ========================================================================== */
.peer-head {
  position: relative;
  /** ⚠️ 要给头像留出"压在下沿"的空间（不留的话它会把签名顶下去） */
  padding: 0 0 8px;
  margin-bottom: 10px;
}

/**
 * ⚠️ 背景图是**整条**（宽度占满）—— 高度 132px。
 *    太高会把下面全挤下去（手机上第一屏就只剩一张图了）。
 */
.peer-cover {
  /**
   * ★★★ **加高加宽**（2026-10-06 用户要求）。
   *
   * ## 用户原话
   * ```
   * > 个人背景编辑加裁剪功能，还要**加高加宽背景显示**
   * ```
   *
   * ## 为什么用 `aspect-ratio` 而不是写死高度
   * ```
   * 写死高度（原来 132px）在**宽屏**上会变成"一条很扁的带子"
   *   （1440 宽 : 132 高 ≈ 11:1）—— 用户上传的图**根本看不出来是什么**。
   * `aspect-ratio` 让它**跟着宽度走**：
   *   手机 414 宽 → 145 高；桌面 800 宽 → 279 高。
   * ```
   * ## 🚨 比例定成 2.6 是**量出来的**，不是拍的
   * ```
   * 先试了 3 / 1 —— 手机 414 宽只有 **126px**，
   * 🚨 **比原来写死的 132px 还矮**（"加高"反而变矮了）。
   * ⇒ 2.6 / 1：414 宽 → 145px（比原来高 13px），800 宽 → 279px（高了一倍多）。
   * ```
   * ⚠️ **比例要和裁剪器一致**（`openImageCropper({ aspect: 2.6 })`）——
   *    不一致的话"用户裁的"和"看到的"就不是同一块（那种不一致最难解释）。
   * ⚠️ `min-height` 兜底：手机上如果宽度很小（比如 320），
   *    2.6:1 只有 123px —— 给一个下限。
   */
  aspect-ratio: 2.6 / 1;
  min-height: 120px;
  border-radius: 16px;
  /**
   * ⚠️ **默认渐变不能省** —— 没设背景图时如果是白色，
   *    那个头像会看起来"浮在空中"（找不到边界）。
   */
  background: linear-gradient(135deg, var(--accent-soft, #cfd9e4) 0%, var(--sunken, #eef2f7) 100%);
  background-size: cover;
  background-position: center;
}
.peer-cover.has-cover {
  /** 有真图时把渐变撤掉（不然图有透明边会透出渐变） */
  background-color: var(--sunken, #eef2f7);
}
/**
 * ★★★ **背景图的"完整显示 + 模糊铺底"两层**（2026-10-08 深夜加）。
 *
 * ## 🚨 为什么改成两层（用户反复报「背景图看不全」）
 * ```
 * 原来：`.peer-cover { aspect-ratio: 2.6/1; background-size: cover }`
 * 🚨 而用户的**原图是 2044×1830（≈1.12:1，接近正方形）**
 *    （服务器上 `data/avatars/_bak-shrink/` 里那份备份实读）
 * ⇒ `cover` 要把接近正方形的图塞进 2.6:1 的扁带子 ⇒ **上下裁掉一大半**。
 * ⚠️ 我第一版量的是"容器 : 现在这张图"，两个都是 2.6:1 ⇒ 量出"裁 0%"，
 *    **差点得出"没问题"的错结论**；真正的证据是**那张备份原图**。
 * ```
 * ## ✅ 现在的做法（微信朋友圈/网易云封面那套）
 * ```
 *   .peer-cover-blur  ← 同一张图，`cover` + `blur` —— **只当底色**，裁掉无所谓
 *   .peer-cover-img   ← 同一张图，`contain` —— **完整看得见**，不裁一个像素
 * ⇒ 既不裁内容，也不留难看的白边。
 * ⚠️ 为什么不用"多层 background"：`filter: blur()` **没法只作用于其中一层**。
 * ```
 * ⚠️ `.peer-cover` 自己要保持 `overflow: hidden` + 圆角 ——
 *    否则模糊层会溢出圆角（那看着像没裁干净）。
 */
.peer-cover {
  position: relative;
  overflow: hidden;
}
.peer-cover-blur {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  /**
   * ⚠️ 模糊 + 稍微放大：**放大是为了把模糊后露出的透明边盖掉**
   *    （`blur` 会让边缘变淡，不放大就会看到一圈发白）。
   */
  filter: blur(18px) saturate(1.15);
  transform: scale(1.12);
  z-index: 0;
}
.peer-cover-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /** ★ **contain = 完整显示**（这是这次修的核心） */
  object-fit: contain;
  z-index: 1;
  /** ⚠️ 没图时它是个空 `<img>`（`src` 没设）—— 别让它挡住下面的渐变 */
  display: none;
}
.peer-cover.has-cover .peer-cover-img {
  display: block;
}

/* ==========================================================================
   ★★★ 通讯录的两个分组（2026-10-06 用户要求"我的"和"它认识的"分开）
   --------------------------------------------------------------------------
   ⚠️ 分组标题**要看得出来是标题**（不是又一张卡片）——
      用"左侧粗条 + 稍大的字"，和卡片区分开。
   ========================================================================== */
.contact-group {
  margin: 18px 0 4px;
}
.contact-group-h {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  /** ⚠️ 左侧粗条：一眼看出"这里开始新的一组"（不用整圈边框，那不是卡片的层次） */
  border-left: 4px solid var(--accent);
  padding-left: 9px;
  line-height: 1.25;
}
.contact-group-hint {
  margin: 5px 0 0;
  padding-left: 13px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink-3);
}
html[data-ui='manga'] .contact-group-h {
  border-left-color: var(--manga-ink);
}

/* ==========================================================================
   ★★★ 动态的两个页签（2026-10-06 用户要求"我的"和"它的"分开）
   --------------------------------------------------------------------------
   ⚠️ 不引彩色：选中的那个用**墨色实心块**（漫画风）/ 深色加粗（极简风），
      和登录页那对"用密码 / 用验证码"是同一套观感。
   ========================================================================== */
.moment-tabs {
  display: flex;
  gap: 8px;
  margin: 14px 0 2px;
}
.moment-tab {
  flex: 1 1 0;
  min-width: 0;
  padding: 9px 10px;
  border: 0;
  border-radius: var(--r-pill, 999px);
  background: var(--bg-soft);
  box-shadow: var(--sh-out-sm);
  color: var(--ink-2);
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
}
.moment-tab.is-on {
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--sh-in);
}
html[data-ui='manga'] .moment-tab.is-on {
  background: var(--manga-ink);
  color: var(--manga-paper);
}

/* ==========================================================================
   ★★★ 通话录音（2026-10-06 用户要求「AI 语音通话录音」）
   --------------------------------------------------------------------------
   ⚠️ `<audio controls>` 在窄屏上会**撑破容器**（它有个默认最小宽度）——
      `width: 100%` 是必须的，不然 414px 的屏上会横向滚动。
   ========================================================================== */
.call-rec-list {
  margin-top: 6px;
}
.call-rec-row {
  margin-top: 12px;
  padding: 12px 13px;
  border-radius: 14px;
  background-color: var(--bg-soft, #eef2f7);
  box-shadow: var(--sh-in);
}
.call-rec-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 8px;
}
.call-rec-audio {
  display: block;
  /** ⚠️ 必须 100%：`<audio>` 的默认宽度在手机上会把容器撑破 */
  width: 100%;
  max-width: 100%;
  height: 38px;
}

/* ==========================================================================
   ★★★ 真人通话（2026-10-06 用户要求「真人通话（跟微信一样）」）
   --------------------------------------------------------------------------
   ⚠️ 那一屏（`#screen-rtc`）**复用** `.call-screen` / `.call-body` / `.call-orb`
      那一套（和它的通话同一个观感）—— 只补两样它没有的：
      · 对方名字（`.rtc-peer`）
      · 来电时那个"接听"键的呼吸感（不然静止画面分不出"在响铃"还是"卡住了"）
   ========================================================================== */
.rtc-peer {
  margin: 10px 0 0;
  font-size: 21px;
  font-weight: 650;
  letter-spacing: 0.3px;
  color: var(--ink);
  text-align: center;
}
/**
 * ★ **响铃时让头像那一圈轻轻呼吸**（只在"响铃"那一段）。
 * ⚠️ 用 `prefers-reduced-motion` 关掉 —— 有人对这个敏感（而且手机省电）。
 */
@keyframes rtc-ring {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.06); opacity: 0.82; }
}
#screen-rtc.is-ringing .call-orb {
  animation: rtc-ring 1.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  #screen-rtc.is-ringing .call-orb {
    animation: none;
  }
}
/** ⚠️ 被叫那一边：接听（绿）和挂断（红）并排，中间留够距离（防误触） */
#screen-rtc .call-actions {
  gap: 34px;
}

/* ==========================================================================
   ★★★ 和好友聊天（2026-10-06 用户要求「跟好友聊天」）
   --------------------------------------------------------------------------
   ⚠️ 结构照聊天页那套（**外壳不滚、只滚消息区**）——
      整屏滚的话输入框会被顶出屏幕（手机上最难受的那种）。
   ========================================================================== */
.dm-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 12px 14px 6px;
}
.dm-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin-bottom: 12px;
}
/** ⚠️ 我发的那条靠右（和聊天页一个观感） */
.dm-row--out {
  align-items: flex-end;
}
.dm-bubble {
  max-width: 78%;
  padding: 9px 13px;
  border-radius: 16px;
  background-color: var(--card, #fff);
  box-shadow: var(--sh-out-sm);
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--ink);
  /** ⚠️ 长网址/长串要能断行，不然会把容器撑宽（横向滚动） */
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.dm-row--out .dm-bubble {
  background-color: var(--accent);
  color: var(--accent-ink);
}
/**
 * ⚠️ **"发送中"要看得出来**（半透明）——
 *    不然用户分不清"发出去了"还是"还在发"。
 * 🚨 而**失败**那条必须**明确标红**（不能静默 —— 那看着像发出去了）。
 */
.dm-row--pending .dm-bubble {
  opacity: 0.55;
}
.dm-row--failed .dm-bubble {
  background-color: var(--bg-soft, #eef2f7);
  color: var(--danger, #c0392b);
  box-shadow: inset 0 0 0 1.5px var(--danger, #c0392b);
}
.dm-when {
  margin-top: 3px;
  font-size: 11px;
  color: var(--ink-3);
}
.dm-note {
  margin: 0 0 12px;
  padding: 8px 11px;
  border-radius: 12px;
  background-color: var(--bg-soft, #eef2f7);
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink-2);
}
.dm-form {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px calc(8px + env(safe-area-inset-bottom)) 12px;
}
.dm-form .field-input {
  flex: 1 1 auto;
  min-width: 0;
}
/* ==========================================================================
   ★★★ 私聊输入栏的媒体键（2026-10-06 用户要求）
   --------------------------------------------------------------------------
   ## 用户原话
   ```
   > 还有**真人和真人聊天也要能发图片，拍视频，发视频，发语音**，
   > 还要**加个表情包按钮在输入栏**不然发不了表情包
   ```
   ⚠️ 四个键**排在输入框上面一行**（不是挤在同一行）——
      手机上那一行本来就窄，五个东西并排每个只有 40px 左右，很难点。
   ========================================================================== */
.dm-tools {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  /**
   * ★★★ **左右要留出内边距**（2026-10-07 用户报「这些按键**贴到边框了**」）。
   *
   * ## 🚨 出了什么事
   * ```
   * 这一行原来只有 `padding: 6px 12px 0` —— 左边 12px、**上边 6px**……
   * ⚠️ 但 `.dm-tools` 是**私聊那一屏**里 `footer` 之后的第一个块，
   *    而那一屏的 `.dm-form` 自己有 `padding: 8px 12px` ——
   *    两处的左右留白**不是同一套数**，看着就是"键贴边了"。
   * ⇒ 现在明确写：**左右 16px、上边 10px**（和输入框那一行的左右对齐）。
   * ```
   * ⚠️ 这条**同时管着聊天页和私聊**（我让它们共用 `.dm-tools` 的尺寸规矩）——
   *    改一处两边都对，不会再出现"一边贴边、一边不贴"。
   */
  padding: 10px 16px 0;
}
/** ⚠️ 小一号（它们不是主操作 —— 主操作是"发"） */
.dm-tool {
  width: 38px;
  height: 38px;
}
/** ★ 录音中那个键要亮（不然用户不知道**在录**还是**没点上**） */
.dm-tool.is-recording {
  background-color: var(--accent);
  color: var(--accent-ink);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}
/** 媒体气泡里的图 / 视频（⚠️ 限宽 —— 不限的话一张大图会把气泡撑破） */
.dm-media {
  display: block;
  max-width: 100%;
  max-height: 260px;
  border-radius: 10px;
}
/**
 * ★★ **语音播放器**（2026-10-08 修「顶出气泡」，⚠️ 中间踩了一次坑）。
 *
 * ## 🚨 原来是什么样（实测数字）
 * ```
 * `<audio controls>` 的**固有宽度是 300px**，而气泡可用宽度只有
 * **282 − 26（左右内边距）= 256px** ⇒ 播放器**右边顶出去 44px**，
 * 时间/音量那几个图标被切掉一块（截图里一眼看得出）。
 * 🔍 根因是 `buildDmBubble` 里**音频那一段漏了 `className = 'dm-media'`** ——
 *    图/视频有，它没有 ⇒ 拿不到 `max-width: 100%` 那条约束。
 *    （代码那边已经补上类名了，见 `app.js` 里那段说明。）
 * ```
 * ## 🚨 我第一版修错了（**记下来，别再犯**）
 * ```
 * 我写了 `width: 100%` ⇒ **气泡反而缩成了 94px**（截图里播放器只剩一个点，
 * 时间/播放键全没了）。
 * 🔍 原因：`.dm-bubble` 是 flex 项、**宽度由内容决定**（它有 max-width 但没 width），
 *    而子元素 `width: 100%` 是"相对父元素" ⇒ **循环依赖** ——
 *    浏览器只能按"音频的最小内容宽度"去算 ⇒ **父缩子、子又缩父**。
 * ⚠️ 也就是说：**"内容撑父容器"的盒子里不能给子元素写 `width: 100%`**。
 * ```
 * ## ✅ 正确的修法：**给一个"最小可用宽度"，剩下的交给外边距**
 * ```
 * · `min-width` —— 保证播放条不至于缩成一个小点（原生控件大约 220px 起才完整）
 * · `max-width: 100%` —— 保证不顶出气泡
 * · `width: auto`（默认）—— 让**音频自己撑开气泡**，而不是反过来
 * ⚠️ 结论：气泡宽度 = min(音频固有宽, 气泡上限 78%) —— 这正是我们要的。
 * ```
 * ⚠️ 为什么不写在 `manga.css` 里：**这一条和主题无关**（极简主题同样会顶出去），
 *    所以要修在 `app.css`（两个主题都吃）。
 */
audio.dm-media {
  min-width: 220px;
  max-width: 100%;
  max-height: none;
}
/**
 * ★★★ **"保存/下载"按钮**（2026-10-08 用户要求「真人传的图片视频都要能下载」）。
 *
 * ⚠️ 三条约束（都是"不加就会难看/难用"的那种）：
 * ```
 * ① 必须**跟着气泡**（不能塞在 `.dm-bubble` 外面 ——
 *    那样一行里会多一个漂着的方块，观感像坏了）
 * ② 不能把纯媒体气泡的内边距规则顶掉
 *    ⇒ 所以下面那条 `:has()` 选择器里**也要写上 `.dm-dl`**（见下）
 * ③ 手机上要**够大能点到**（≥ 32px 高）—— 太小的话点不中，
 *    而 iOS 那边本来就要靠长按，按钮再难点用户就放弃了
 * ```
 */
.dm-dl {
  display: block;
  margin: 6px auto 0;
  padding: 7px 16px;
  min-height: 32px;
  border: 0;
  border-radius: 999px;
  background-color: var(--bg-soft, #eef2f7);
  color: var(--ink-2, #4a5568);
  font-size: 12.5px;
  line-height: 1.2;
  cursor: pointer;
  /** ⚠️ 别让长按弹系统菜单时误触（iOS 上要留给"存储到照片"） */
  -webkit-tap-highlight-color: transparent;
}
.dm-dl:active {
  transform: scale(0.97);
}
.dm-dl:disabled {
  opacity: 0.6;
  cursor: default;
}
/** 文字单独一行（和媒体拼在同一个气泡里时，别贴着图） */
.dm-text {
  display: block;
}
.dm-media + .dm-text {
  margin-top: 6px;
}
/** ⚠️ 纯媒体的气泡不要内边距（有的话图周围一圈白边，看着像没加载出来） */
.dm-bubble:has(> .dm-media):not(:has(> .dm-text)) {
  padding: 0;
  background-color: transparent;
  box-shadow: none;
}
/**
 * ⚠️ **带"保存"按钮的纯媒体气泡**：按钮要留一点底色，所以**不能**全透明。
 *    这里只去内边距、不去背景 —— 不然按钮会直接压在聊天背景上（看不清）。
 */
.dm-bubble:has(> .dm-dl):not(:has(> .dm-text)) {
  padding: 4px;
  background-color: transparent;
  box-shadow: none;
}
/** 发送键（圆形、带箭头 —— 和"给她发"那个观感一致） */
.dm-form .btn-send {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
}

/* ==========================================================================
   ★★★ 回复速度四档（2026-10-06 用户要求「加个 AI回复速度可调节」）
   --------------------------------------------------------------------------
   ⚠️ 四个按钮**两列排**（一列太窄、那两句说明会挤成两行看不清）。
   ========================================================================== */
.speed-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 4px 0 8px;
}
.speed-btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  padding: 10px 12px;
  border-radius: 14px;
  border: 1.6px solid var(--line);
  background-color: var(--card);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
/** ★ **选中那个要一眼看出来**（不然用户不知道自己现在是哪一档） */
.speed-btn.is-on {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1.4px var(--accent);
}
.speed-name {
  font-size: 14.5px;
  font-weight: 600;
}
.speed-hint {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ink-3);
}

/* ==========================================================================
   ★★★ 好友申请（2026-10-06 用户要求「加好友要发个申请过去，对方同意就行了」）
   --------------------------------------------------------------------------
   ⚠️ 三块卡片都在通讯录那一屏里，`#freq-wrap` 统管外边距。
   ========================================================================== */
.freq-wrap {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 0 16px 12px;
}
.freq-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
}
/** ⚠️ 最后一条不要那根线（不然卡片底部多一条悬空的线） */
.freq-row:last-child {
  border-bottom: none;
  padding-bottom: 0;
}
.freq-who {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.freq-name {
  font-size: 14.5px;
  font-weight: 550;
  /** ⚠️ 名字太长要省略号（不然把「同意」按钮挤出去） */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.freq-sub {
  font-size: 11.5px;
  color: var(--ink-3);
}
.freq-acts {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
}
/** ⚠️ 那两个键小一号（它们不是页面主操作，主操作是"发消息"） */
.freq-btn {
  padding: 6px 12px;
  font-size: 13px;
}
/** 加好友那一行的输入框 + 按钮 */
.freq-add {
  display: flex;
  align-items: center;
  gap: 8px;
}
.freq-add .field-input {
  flex: 1 1 auto;
  min-width: 0;
}

/* ==========================================================================
   ★★★ 人设预设（2026-10-06 用户要求「AI人设里添加 8 个动漫人物预设」）
   --------------------------------------------------------------------------
   ⚠️ **一列排**（每个预设都带一句说明 —— 两列会把那句挤成三行，读不了）。
   ========================================================================== */
.preset-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 4px 0 8px;
}
.preset-btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  padding: 10px 12px;
  border-radius: 14px;
  border: 1.6px solid var(--line);
  background-color: var(--card);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
/** ★ **选中那个要一眼看出来**（不然用户不知道自己现在用的是哪一个） */
.preset-btn.is-on {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1.4px var(--accent);
}
.preset-name {
  font-size: 14.5px;
  font-weight: 600;
}
/** ★ 小标题（"有名有姓的完整人物"那一行 —— 和上面那排性格预设分开） */
.preset-sub {
  margin: 12px 0 4px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
}
.preset-hint {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ink-3);
}

/* ==========================================================================
   ★★★ 图片裁剪器（2026-10-06 用户要求「个人背景编辑加个裁剪功能」）
   --------------------------------------------------------------------------
   ⚠️ 三条铁律照旧：不引彩色、不用 `background:` 简写、层级要写清楚。
   --------------------------------------------------------------------------
   🚨 **类名一律带 `icrop-` 前缀（image crop）** —— 2026-10-06 撞名踩过一次：
   ```
   `crop-frame` / `crop-mask` 这些**已经被头像裁剪器占用了**
   （那一套是"背景图 + 固定取景框"的老做法，在本文件上面）
   ⇒ 撞名的话两套样式**互相打架**，而且**不报错**（只是看着不对）
   ```
   ========================================================================== */
.icrop-mask {
  position: fixed;
  inset: 0;
  /**
   * ⚠️ **层级要压过底部导航**（那个是 `z-index: 30` 左右）——
   *    压不过的话用户在浮层里拖动时**会点到底下的 tab**（很难受）。
   */
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  /** ⚠️ 用 `rgba` 而不是 `background:` 简写（铁律） */
  background-color: rgba(18, 21, 26, 0.55);
  overflow: auto;
}
.icrop-box {
  width: 100%;
  max-width: 520px;
  background-color: var(--card, #fff);
  border-radius: 18px;
  padding: 16px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
}
.icrop-frame {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: 12px;
  background-color: var(--bg-soft, #f0f0f0);
  /**
   * ⚠️ **`touch-action: none`** —— 不写这个的话手机上拖动会被当成
   *    "滚动页面"，图**拖不动**（那是最典型的"这个功能在手机上没用"）。
   */
  touch-action: none;
  cursor: grab;
}
.icrop-frame.is-dragging {
  cursor: grabbing;
}
.icrop-img {
  display: block;
  position: absolute;
  left: 0;
  top: 0;
  /** ⚠️ 尺寸和位移都由 JS 每次算好写进来（见 `openImageCropper`） */
  max-width: none;
  user-select: none;
  -webkit-user-drag: none;
}
.icrop-zoom {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
}
.icrop-range {
  flex: 1 1 auto;
  min-width: 0;
}
/**
 * ★ **「整张图」那个小按钮**（2026-10-08 加，见 `openImageCropper` 里那段说明）。
 * ⚠️ 三条：
 *   · `flex: 0 0 auto` —— 不然它会被滑块挤成一条（字都看不清）
 *   · 高度和滑块对齐（手机上手指点得到，别做成 20px 高的小字）
 *   · **不许继承 `.sheet-btn` 的大内边距** —— 那套是给弹层底部那两个大按钮用的
 */
.icrop-fit {
  flex: 0 0 auto;
  padding: 6px 12px;
  min-height: 32px;
  font-size: 12.5px;
  line-height: 1.2;
  white-space: nowrap;
}

/**
 * ★ 头像**压在背景图下沿**（`margin-top: -44px`）——
 *   这是 ins 那个感觉的关键。不压的话就是"两张图上下贴在一起"。
 */
.peer-av {
  width: 88px;
  height: 88px;
  margin: -44px 0 0 16px;
  border-radius: 50%;
  overflow: hidden;
  /** ⚠️ 一圈底色描边（不然头像和背景图糊在一起分不出边界） */
  border: 3px solid var(--card, #fff);
  background: var(--sunken, #eef2f7);
  position: relative;
  /**
   * 🚨🚨 **必须给 z-index —— 否则头像会被背景图盖住**（2026-10-08 深夜修，用户截图报的）。
   *
   * ## 出了什么事
   * ```
   * 头像是 `margin: -44px` **压在背景图下沿**的（这是 ins 那个观感的关键）。
   * 🚨 而我给背景图加了两个 `position: absolute` 的层，其中清晰层是 `z-index: 1`；
   *    头像只有 `position: relative`（z-index = auto ⇒ 算 0）
   * ⇒ **位置更高的 1 赢了** ⇒ 背景图**盖在头像上面**：
   *    用户看到的就是「背景图挡住了头像」（他截图里那块白圆角就是被糊住的头像）。
   * ⚠️ 这是**我这轮改动引入的回归** —— 改之前背景图是 `background-image`（不是定位元素），
   *    所以头像天然在它上面。
   * ```
   * ✅ `z-index: 2`（比背景图那两层都高）。
   * 📌 教训：**给一个元素加 `position: absolute` 的兄弟层时，要想清楚谁压在谁上面** ——
   *    尤其是这种靠负 margin 叠上去的构图。
   */
  z-index: 2;
  /**
   * ⚠️ 这三条是**首字母头像**（`.peer-av-letter`）居中用的 ——
   *    我上一版改这条规则时**漏抄了它们**，补回来。
   */
  display: flex;
  align-items: center;
  justify-content: center;
}
.peer-av img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.peer-av-letter {
  font-size: 34px;
  font-weight: 600;
  color: var(--accent-deep, #3f5566);
}

.peer-name-lg {
  margin: 8px 16px 0;
  font-size: 19px;
  font-weight: 700;
  line-height: 1.25;
}
.peer-sig {
  margin: 4px 16px 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-soft, #55606c);
  /** ⚠️ 签名允许换行（ins 那种两行签名） */
  white-space: pre-wrap;
  word-break: break-word;
}
.peer-sig.is-empty {
  color: var(--muted, #98a2ae);
  font-style: italic;
}
.peer-meta {
  margin: 6px 16px 0;
  font-size: 12px;
  color: var(--muted, #8a94a0);
}

/* 两个动作键（语音通话 / 发消息） */
.peer-acts {
  display: flex;
  gap: 10px;
  margin: 4px 16px 12px;
}
.peer-act {
  flex: 1 1 0;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 11px 8px;
  border: 0;
  border-radius: 13px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  color: var(--accent-deep, #3f5566);
  /** ⚠️ 拟物风：内凹底色（和这个项目的整体风格一致） */
  background: var(--sunken, #eef2f7);
  box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.06);
}
.peer-act:active {
  transform: scale(0.97);
}
.peer-act-wide {
  width: calc(100% - 32px);
  margin: 4px 16px 12px;
}
.peer-h3 {
  margin: 14px 4px 6px;
  font-size: 13px;
  font-weight: 650;
  color: var(--muted, #8a94a0);
}

/* ==========================================================================
   ★★★ 两个"清空"（危险操作）—— ⚠️ 视觉上要**明显区分于普通设置**
   ========================================================================== */
.danger-card {
  border: 1px solid rgba(176, 64, 58, 0.28);
}
.danger-item {
  padding: 10px 0;
  border-top: 1px solid var(--line, rgba(0, 0, 0, 0.07));
}
.danger-item:first-of-type {
  border-top: 0;
}
.danger-h {
  margin: 0 0 4px;
  font-size: 14.5px;
  font-weight: 650;
  color: #b0403a;
}
.danger-p {
  margin: 0 0 8px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-soft, #55606c);
  /** ⚠️ 后果那段是多行的（每行一个后果）→ 保留换行 */
  white-space: pre-wrap;
}
.danger-btn {
  color: #b0403a;
  background: rgba(176, 64, 58, 0.09);
}
.danger-btn:disabled {
  opacity: 0.5;
}

/** 签名输入框（两行高，和普通输入框区分开） */
.sig-input {
  min-height: 54px;
  resize: vertical;
  line-height: 1.5;
  font-family: inherit;
}

/** ⚠️ 整张卡可点（进对方主页）—— 要有"能点"的样子（不然没人会去点） */
.contact-card.is-tappable {
  cursor: pointer;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.contact-card.is-tappable:active {
  transform: scale(0.985);
}
/** ⚠️ 键盘/无障碍焦点也要看得见（不然 tab 键走进去不知道在哪） */
.contact-card.is-tappable:focus-visible {
  outline: 2px solid var(--accent-deep, #3f5566);
  outline-offset: 2px;
}

/* ==========================================================================
   ★★★ 主页的聊天列表（2026-10-05 用户要求"有所有的联系人和群聊"）
   ========================================================================== */
.home-block {
  margin: 0 0 12px;
}
.home-h2 {
  margin: 2px 4px 8px;
  font-size: 13px;
  font-weight: 650;
  color: var(--muted, #8a94a0);
}
.chat-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/**
 * ⚠️ 一行 = 头像 + 名字/预览 + 时间 —— **微信那个排布**。
 *    用 `button` 是为了"整行可点"（手机上好按）。
 */
.chat-item {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 10px 12px;
  border: 0;
  border-radius: 15px;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
  background: var(--card, #fff);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
.chat-item:active {
  transform: scale(0.988);
}
/**
 * ★ **只有她那一行是"能聊的"**（有个左边的强调条）——
 *    ⚠️ 不区分的话用户点别人、发现发不出去，会以为是 bug。
 */
.chat-item.is-live {
  box-shadow: inset 3px 0 0 var(--accent-deep, #3f5566), 0 1px 2px rgba(0, 0, 0, 0.04);
}

.chat-av {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--sunken, #eef2f7);
}
.chat-av img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.chat-av-letter {
  font-size: 18px;
  font-weight: 650;
  color: var(--accent-deep, #3f5566);
}
/** 群聊那个用方块（和个人的圆形区分开 —— 一眼看出"这是群"） */
.chat-av-group {
  border-radius: 13px;
  font-size: 15px;
  font-weight: 650;
  color: var(--accent-deep, #3f5566);
}

.chat-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.chat-name {
  font-size: 15px;
  font-weight: 600;
  /** ⚠️ 名字太长要省略号（不能把右边的字挤出去） */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-last {
  font-size: 12.5px;
  color: var(--muted, #8a94a0);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-when {
  flex: 0 0 auto;
  font-size: 11.5px;
  color: var(--muted, #98a2ae);
  align-self: flex-start;
  padding-top: 2px;
}

/* ==========================================================================
   ★★★ **按键回弹动效**（2026-10-05 用户要求「按键按下去加个回弹动效」）
   ==========================================================================
   ## ⚠️⚠️ 为什么这件事纯 CSS 做不到

   CSS 能做"按下去缩一下"（`:active`），但**做不了"松手时弹回来"** ——
   `:active` 只在**按着的时候**成立，松手那一刻就直接变回原样了
   （**没有"过冲"那一下，而那一下才是"回弹"**）。

   → 所以由 `public/press-fx.js` 挂两个类：
     · `is-pressing` —— 按着的时候（**快**，70ms，手要跟得上）
     · `is-spring`   —— 松手的时候（**弹**，420ms，带过冲）

   ## ⚠️ 为什么关键帧是"手工调"的，不是 `cubic-bezier`

   真正的弹簧是 `0.96 → 1.03 → 0.99 → 1`（指数衰减）。
   `cubic-bezier` **只有一个拐点**，拟不出"先过冲再回来"那个形状 ——
   所以直接写关键帧。

   ## ⚠️ `transform-origin: center` 不能省

   元素的 `transform-origin` 默认是 `50% 50%`，但**行内元素/某些 flex 子项**
   会被算到奇怪的位置 —— 显式写上，缩放才是"从中间"。

   ## ⚠️ 尊重"减少动效"那个系统开关

   `prefers-reduced-motion` 打开时**整块关掉**（有人会对缩放动效不适）。
   ========================================================================== */

@keyframes press-spring {
  0% {
    transform: scale(0.96);
  }
  /** ⚠️ 过冲（"弹"就是这个） */
  38% {
    transform: scale(1.035);
  }
  /** ⚠️ 再回一点（一次来回才像弹簧，只过冲一次像"弹飞了"） */
  66% {
    transform: scale(0.99);
  }
  100% {
    transform: scale(1);
  }
}

/**
 * ⚠️ 用 `:where()` 把选择器权重压到 **0** ——
 *    这样**任何一个已有的 `transform` 规则都能盖过它**
 *    （比如 `.tab:active { transform: scale(0.94) }`）。
 *    不这么做的话，这条会**反过来**把那些覆盖掉（手感反而变差）。
 */
:where(.is-pressing) {
  transform: scale(0.96);
  transition: transform 70ms ease-out;
}

/**
 * ⚠️ `animation` 不用 `:where()` 包 —— 它要能盖过上面那条 `transform`。
 *    （同一个元素上 `transform` 和 `animation` 打架时，动画赢。）
 */
.is-spring {
  animation: press-spring 420ms cubic-bezier(0.34, 1.3, 0.5, 1) both;
}

/** ⚠️ 长按 / 拖拽 / 输入框这些**不该有回弹** */
input,
textarea,
select,
.crop-stage,
.msg-menu {
  animation: none !important;
}

@media (prefers-reduced-motion: reduce) {
  :where(.is-pressing) {
    transform: none;
    transition: none;
  }
  .is-spring {
    animation: none;
  }
}

/* ==========================================================================
   ★★★ 动态发布器 + 动态里的视频（2026-10-05 用户要求「动态界面人也要能发」）
   ========================================================================== */
/** 待发媒体的那一排小格子 */
.moment-picked {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin: 8px 0 2px;
}
.moment-pick {
  position: relative;
  width: 62px;
  height: 62px;
  border-radius: 13px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--sunken, #eef2f7);
}
.moment-pick img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/** ⚠️ 视频/语音没有缩略图 → 用两个字标出来（不然是个空格子，看着像坏了） */
.moment-pick-ic {
  font-size: 12px;
  font-weight: 650;
  color: var(--accent-deep, #3f5566);
}
.moment-pick-x {
  position: absolute;
  top: 1px;
  right: 1px;
  width: 20px;
  height: 20px;
  border: 0;
  border-radius: 50%;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  color: var(--manga-paper, #fff);
  background: rgba(18, 21, 26, 0.72);
}

/** 动态里的视频（和聊天里那个一个规矩：限高 + contain） */
.moment-video {
  display: block;
  width: 100%;
  max-height: 320px;
  margin: 6px 0 2px;
  border-radius: 12px;
  background: #000;
  object-fit: contain;
}

/** "我发的"那个署名要看得出来（和她的区分开） */
.moment-name.is-human {
  color: var(--accent-deep, #3f5566);
}

/* ==========================================================================
   ★★★ 通话界面（2026-10-05 用户要求）
   ==========================================================================
   用户原话：
   > 语音通话功能和界面优化一下…**那个圆形 UI 的地方换成对方头像**，
   > 加**免提和听筒**模式，然后**可自定义铃声和背景**
   ========================================================================== */

/**
 * ⚠️ 那个圆**里面**现在是头像 —— 所以圆本身的 `place-items: center`
 *    和 `overflow` 要跟上（不裁的话方图会露出圆外）。
 */
.call-orb {
  overflow: hidden;
  position: relative;
}
.call-avatar {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 50%;
}
/** ⚠️ 头像加载不出来时的兜底（那个鲸鱼剪影）—— 不能留个空白圈 */
.call-avatar-fallback {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  color: inherit;
}
.call-avatar-fallback[hidden] {
  display: none;
}

/**
 * 免提键的"开着"状态。
 * ⚠️ 它是个**开关**（不是"点一下执行"），所以要有持久的样子 ——
 *    不然用户忘了自己切到听筒了，会以为"她声音怎么这么小"。
 */
.round-btn.is-on {
  box-shadow: var(--sh-in);
  color: var(--accent-deep, #3f5566);
}

/** 主页那个"名字左边的小头像" */
.hero-name-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
}
/**
 * ⚠️ **这个类已经不用了**（2026-10-06 用户要求删掉名字左边那个小 logo）。
 *
 * ⚠️ **规则留着、但 `display:none`**：
 * ```
 * · 对应的 <img> 从 HTML 里删了、el 表里的条目也删了
 * · 留着这条规则**没有任何副作用**（没有元素会匹配它）
 * · 而"从 5000 行的 css 里删一段"**风险更大**（这个项目踩过连带删错）
 * ```
 * ⚠️ 但**别再加东西进来** —— 那是"看着还在、其实没有"的残留。
 */
.hero-avatar-sm {
  display: none;
}

/**
 * 通话背景（用户可自定义）。
 * ⚠️ 用 `cover` + `fixed`（和聊天背景一个规矩：跟着滚很晕）。
 */
#screen-call.has-bg {
  background-size: cover;
  background-position: center;
  background-attachment: fixed;
}

/* ==========================================================================
   ★★★ 聊天背景：**真的 `<img>`**（2026-10-05 修"背景显示不对"）
   ==========================================================================
   ⚠️ 见 index.html 里 `#chat-bg` 那段说明 —— 从 `background-image`
      + `background-attachment: fixed` 换过来的。
      那个写法在**实机浏览器**上会因为视口伸缩而算错缩放比
      （桌面 headless 复现不出来）。
   ========================================================================== */
.chat-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /**
   * ★ `object-fit: cover` —— 铺满、按比例、不拉伸。
   * ⚠️ 这是**确定**的行为：不依赖 `background-attachment`，也不依赖视口。
   */
  object-fit: cover;
  /**
   * ⚠️ **焦点在顶部** —— 这几张素材的人物**偏上**（脸在 y≈1/5 处），
   *    居中裁的话会把她的脸切掉一半。
   */
  object-position: center top;
  /** ⚠️ 铺在最底下：不压气泡、不挡输入栏 */
  z-index: 0;
  /** ⚠️ 不许被选中/拖动（它是背景，不是内容） */
  user-select: none;
  pointer-events: none;
  display: block;
}
/** ⚠️ 没有背景（用户选了"默认"）时把整个元素藏掉（别留个占位的空框） */
.chat-bg[hidden] {
  display: none;
}
/**
 * ⚠️ 背景之上的一切都要**抬起来** ——
 *    不然它们会被那个 `z-index: 0` 的图盖住（点不到、也看不见）。
 */
#screen-chat > :not(.chat-bg) {
  position: relative;
  z-index: 1;
}

/** 背景色卡里那张预览图（⚠️ 要和 `.bg-swatch` 的圆角/裁切一致） */
.bg-swatch-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;
  border-radius: inherit;
  pointer-events: none;
}

/**
 * ★★ **通话记录那几行**（2026-10-06）。
 *
 * ⚠️ 用户要求「聊天记录不要保存在聊天页上，**单独开个语音通话记录保存**」——
 *    所以它长在「通话」那一屏里（`#callbg-log`），**不在聊天页**。
 *
 * ⚠️ 三列：时间（左，定宽）/ 谁 + 接没接（中间，占满）/ 时长（右，定宽）。
 *    ⚠️ 用 `min-width: 0` + `overflow: hidden` 让"谁"那一列能被截断 ——
 *       不加的话长名字会把这一行撑破（手机上就是横向滚动条）。
 */
.call-log-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 4px 0 10px;
}
.call-log-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 11px 14px;
  border-radius: 14px;
  background: var(--bg);
  box-shadow: var(--sh-in);
  font-size: 14px;
}
.call-log-when {
  flex: 0 0 auto;
  min-width: 74px;
  color: var(--ink-4);
  font-size: 12.5px;
}
.call-log-who {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink);
}
.call-log-dur {
  flex: 0 0 auto;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums; /* ⚠️ 等宽数字，一列时间才对得齐 */
}

/**
 * 🚨🚨 **`.round-btn` 是 `display: grid`** —— 它会**盖掉浏览器默认的
 * `[hidden] { display: none }`**（那个默认规则优先级极低，项目里踩过好几次：
 * 见 `.menu[hidden]` / `.screen[hidden]` / `.banner[hidden]` 那几条）。
 *
 * ⚠️ 不补这条的话：`el.callSpeaker.hidden = true` **根本不生效** ——
 *    免提键在"真人跟 AI"那通里**照样显示**（正是用户报的那件事）。
 */
.round-btn[hidden] {
  display: none !important;
}

/* ==========================================================================
   ★★★ 每个界面一张背景（2026-10-05 用户要求）
   ==========================================================================
   用户原话：
   > **给每个界面加个背景，不要纯白的**，上面素材**看着选，别重复**就行，
   > **记得调好比例**，用户**同样可自定义**
   ==========================================================================
   ⚠️ 素材是"手账/水彩拼贴"那种 —— **图案都在四边**、中间是留白。
      所以这几张图**天生适合当背景**（中间那片白正好压内容）。
   ========================================================================== */

/**
 * ## 🚨🚨 为什么是 `body::before` 这一层，而不是给 `.screen` 加 `background-image`
 *
 * ⚠️ 因为**卡片是不透明的**（`.card { background: var(--bg-soft) }` = `#f0f0f0`）——
 *    给屏加背景的话，内容区那些卡片**整块盖在上面**，
 *    用户**只看得到卡片缝里那几条**（等于加了跟没加一样）。
 *
 * ⚠️ 所以背景要铺在**最底下的一层**，然后让卡片**稍微透一点**
 *    （见下面 `.card` 那一段）。
 *
 * ⚠️ 用 `body::before` 而不是往每个 `<section>` 里塞 `<img>`：
 *    那些屏的结构是 `flex column` + 内部滚动，
 *    多塞一个子元素会影响它们的排版（尤其是那个"滚动区撑满剩余高度"的逻辑）。
 *    `body::before` 是**纯绘制**，不参与任何布局。
 */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  /**
   * 🚨🚨 **这一层已经不用了 —— 显式关掉**（2026-10-05 第五次修，收敛成一条）。
   *
   * ## 为什么关掉它
   *
   * ⚠️ 这个项目为"屏幕背景"**先后试过三种画法**，而它们**互相盖**：
   * ```
   * ① body::before { z-index: -1 }   —— ⚠️ 被 `.screen { z-index: 1 }` 那一层盖住
   *                                     （每个屏都是层叠上下文）
   * ② html { background-image: … }   —— ⚠️ 被 html 自己的 `background-color` 盖住
   * ③ body  { background-image: … }   ← ★ **现在只用这一条**
   * ```
   * ⚠️ ①②的代码**没删**（注释留着解释历史），于是**三层叠着画同一张图** ——
   *    ⚠️ 结果是"**每一层都被另一层挡住**"，而且**不报错**。
   *
   * ## ✅ 现在的规矩（一句话）
   *
   * ⚠️ **背景图只画一次、只画在 `body` 上**（`background-size: 100% 100%`）。
   *    别的层（`html` / `body::before`）**一律只留底色或不画**。
   *    ⚠️ 想改比例/位置就改 `body` 那一条，**别再新开一层**。
   */
  content: none;
  background-image: none;
  /**
   * ⚠️ 过渡要短（180ms）—— 切页面时背景淡入，太快会闪、太慢像卡。
   */
  transition: opacity 180ms ease;
  opacity: 1;
}

/**
 * ★ **哪一屏用哪张** —— 由 JS 在切屏时给 `<body>` 打一个 `data-bg`。
 *
 * ⚠️ 用 `data-bg` 而不是"给每屏写一条选择器"：
 *    JS 只要写一个属性（一行），不用记得"这一屏对应哪张图"。
 *    ⚠️ 对应关系在 JS 那张表里（`SCREEN_BG`）—— **只有一处**，
 *       改图只改那一处（两边都写的话迟早对不上）。
 */
body[data-bg='home'] { --screen-bg: url('./ui/screen-bg-home.png'); }
body[data-bg='contacts'] { --screen-bg: url('./ui/screen-bg-contacts.png'); }
body[data-bg='moments'] { --screen-bg: url('./ui/screen-bg-moments.png'); }
body[data-bg='me'] { --screen-bg: url('./ui/screen-bg-me.png'); }
body[data-bg='peer'] { --screen-bg: url('./ui/screen-bg-peer.png'); }
body[data-bg='settings'] { --screen-bg: url('./ui/screen-bg-settings.png'); }

/**
 * ★★ **用户自己换的那张**（`--screen-bg-custom` 由 JS 写成 `url(data:…)`）——
 *    优先级**高于**上面那几条（内联样式 > 样式表里的类规则）。
 *    ⚠️ 所以自定义图一设，那六条就自动让位（不用 `!important`）。
 */
body[style*='--screen-bg-custom'] {
  --screen-bg: var(--screen-bg-custom);
}

/**
 * ## ⚠️⚠️ 卡片要**透一点**，否则背景全被盖住
 *
 * ⚠️ `.card` 原来是 `background: var(--bg-soft)` = **不透明的 `#f0f0f0`**。
 *
 * ⚠️ 做成 82% 不透明 + `backdrop-filter` 之后：
 *    · 背景的纹理**透得出来**（否则等于没加）
 *    · 文字仍然清楚（82% 已经压掉大部分花纹）
 *
 * ⚠️ 只改**有背景的那几屏**（`body[data-bg]`）——
 *    聊天页那些气泡**不动**（它自己有漫画风那套）。
 *
 * ⚠️ `backdrop-filter` 在旧浏览器上不生效 → **半透明底色本身**才是关键
 *    （毛玻璃只是锦上添花，没有也不影响可读性）。
 */
/** ⚠️ 这几条**已经被下面那批"重复属性选择器"的替代了** —— 见那一大段说明。
 *    第一版就是这几条，它们**特异性不够**（被 `manga.css` 盖掉了），所以删掉。
 */
/** ⚠️ 这几条**已经被下面那批"重复属性选择器"的替代了** —— 见那一大段说明。
 *    第一版就是这几条，它们**特异性不够**（被 `manga.css` 盖掉了），所以删掉。
 */
/**
 * ⚠️ 顶栏那一条**也已经被下面那批"重复属性选择器"的替代了** ——
 *    第一版就是这条，它**特异性不够**（被 `manga.css` 里的顶栏规则盖掉了），
 *    所以删掉（留着会让下一个读代码的人以为它在生效）。
 */

/* ==========================================================================
   ★★★ 有背景的那几屏：卡片改半透明（2026-10-05 用户要求"每个界面加背景"）
   ==========================================================================
   🚨🚨 **必须放在 `manga.css` *之后*、而且优先级要更高** ——
      第一版只写了 `body[data-bg] .card`（特异性 0,2,1），
      而 `manga.css` 里那条是 `html[data-ui='manga'] .card`（0,2,1）——
      **一样高**，于是**后加载的赢**，而 `manga.css` 在 `app.css` 之后 →
      **半透明被那条不透明的白底盖掉了**（量出来是纯色、一点 alpha 都没有）。

   ⚠️ 修法是**把属性选择器写两遍**（`[data-bg][data-bg]`）——
      特异性变成 **0,3,1**，稳赢，而且**不用 `!important`**
      （`!important` 会让以后想覆盖它的人很难受）。

   ⚠️ 而且这个文件（`app.css`）本来就在 `manga.css` **之前**加载 ——
      所以只靠"更高优先级"就够了（加载顺序帮不上忙）。
   ========================================================================== */

/** 浅色卡：82% 不透明（背景纹理透得出来，文字仍然清楚） */
body[data-bg][data-bg] .card {
  background: color-mix(in srgb, var(--bg-soft) 82%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/** ⚠️ 深色内嵌卡也透一点（它原来是 `--bg-sunken`）—— 透得少一点（要保住"深"） */
body[data-bg][data-bg] .card.sunken {
  background: color-mix(in srgb, var(--bg-sunken) 88%, transparent);
}

/** ⚠️ 那几屏的"强调色微调卡"（`#screen-contacts .card` 那条）也要跟着透 */
/**
 * 🚨🚨 **2026-10-05 晚报：这一条合并了 `manga.css` 里那条重复的**（用户要求「元素别混在一起」）。
 *
 * ⚠️ 原来**两处各有一条**，而且值不一样：
 * ```
 * app.css   ： 82%   —— 少了 #screen-moments / #screen-peer 两屏
 * manga.css ： 72%   —— 十屏齐全
 * ```
 * ⚠️ 特异性都是 **0,3,1**、`manga.css` 后加载 ⇒ **实际生效的是 72% 那条**，
 *    而 `app.css` 这条的 82% **只在它没覆盖到的那两屏上生效** ——
 *    **"同一个东西两个值、还有一个永远不生效"**，正是串味的温床。
 * ⇒ 现在**只剩这一处**，十屏齐全。
 */
body[data-bg][data-bg] #screen-home .card,
body[data-bg][data-bg] #screen-persona .card,
body[data-bg][data-bg] #screen-contacts .card,
body[data-bg][data-bg] #screen-me .card,
body[data-bg][data-bg] #screen-rooms .card,
body[data-bg][data-bg] #screen-room .card,
body[data-bg][data-bg] #screen-models .card,
body[data-bg][data-bg] #screen-about .card,
body[data-bg][data-bg] #screen-moments .card,
body[data-bg][data-bg] #screen-peer .card {
  background: color-mix(in srgb, color-mix(in srgb, var(--accent) 7%, var(--bg-soft)) 72%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/**
 * ⚠️ 顶栏也透一点 ——
 *    不然一条不透明的横杠把背景切成两半，看着很割裂。
 */
body[data-bg][data-bg] .bar {
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

/**
 * 🚨🚨 **2026-10-05 晚报：`.card.hero` / `.card.moment` 那条从 `manga.css` 搬过来了。**
 *
 * ⚠️ 它们是**强调色微调卡**（主页最上面那张、动态那张），比普通 `.card` 亮一点。
 *    原来这条写在 `manga.css` 里、**没有主题前缀**，而 `app.css` 那边**没有对应的一条** ——
 *    ⇒ 极简主题下它们也跟着变（那不算 bug，但"两套主题共用的一条规则"写在漫画那个文件里
 *      **让人没法一眼看懂**，而且一改就串味）。
 * ⚠️ 现在：**它和上面那批一样属于"有背景时的通用处理"，两套主题共用，放在这里**。
 */
body[data-bg][data-bg] .card.hero,
body[data-bg][data-bg] .card.moment {
  background: color-mix(in srgb, color-mix(in srgb, var(--accent) 7%, var(--bg-soft)) 72%, transparent);
}

/* ==========================================================================
   🚨🚨 背景**改画在 `html` 元素自己的背景上**（2026-10-05 第二次修）
   ==========================================================================
   ## 出了什么事

   ⚠️ 第一版用 `body::before { position: fixed; inset: 0; z-index: -1;
      background-image: var(--screen-bg) }` —— **一条都对，就是看不见**。

   量过的账（全部正常）：
   · `body` 上 `--screen-bg` = `url('./ui/screen-bg-home.png')` ✅
   · 伪元素 `background-image` 解析成完整 URL ✅
   · 伪元素 430×900、`display: block`、`opacity: 1` ✅
   · 图 HTTP 200、642KB ✅
   · 把 21 个 `.card`/`.bar` 藏起来截图 → **还是全白** ❌

   ⚠️ 所以它**确实画了，但被盖住了** —— 而"盖住它的东西"在
      `.screen { position: relative; z-index: 1 }` 那一层里
      （每个屏都是**层叠上下文**，里面的白色画在伪元素之上）。

   ## ⚠️⚠️ 修法：**别用伪元素，直接画在 `html` 的背景上**

   ⚠️ `html` 的背景画在**整个画布的最底层**（CSS 规范里那条
      "根元素的背景要铺满画布"）—— **没有任何元素能盖住它**，
      也不需要 `z-index`、不需要伪元素、不参与任何层叠。

   ⚠️ 配套：`body` 必须**透明**（原来是 `background: var(--bg)`）——
      不透明的话它会把 `html` 那层挡掉（body 是 html 的子元素）。
      它原来那个"灰底"现在由 `html` 上的 `background-color` 承担。

   ⚠️ 而且 `<html>` 的 `background-attachment: fixed` 在手机上**是可靠的**
      （`body` 或普通 div 上的 `fixed` 才不可靠 —— 见聊天背景那次）。
   ========================================================================== */

html {
  /**
   * ⚠️ 底色跟着主题走（原来在 `body` 上）
   *
   * 🚨🚨 **`html` 上【只留底色】，绝对不要再画背景图**（2026-10-05 第四次修）。
   *
   * ## 出了什么事
   *
   * ⚠️ 原来这里**也**写了 `background-image: var(--screen-bg, none)` ——
   *    和 `body` 那条**一模一样**。而 `--screen-bg` 在 `html` 上**也有定义**
   *    （`html[data-bg='contacts'] { --screen-bg: url(…) }`），
   *    所以 **`html` 真的也去画那张图了**。
   *
   * ⚠️ 后果（`elementsFromPoint` 量出来的）：
   * ```
   * 5) BODY  bg-color=透明          bg-image=url(...screen-bg-contacts.png)
   * 6) HTML  bg-color=rgb(236,236,236)  bg-image=url(...screen-bg-contacts.png)
   * ```
   * ⚠️ `html` 是**根元素**、画在**最底下**，而它**前面还有一条不透明的灰底**
   *    （`background-color: var(--bg)`）—— 于是：
   *    · `html` 自己的那张图 → **被它自己的底色盖掉**
   *    · `body` 的那张图 → **被 `html` 的底色盖掉**（html 在 body 底下）
   *    ⚠️ **两层叠着，一层都看不见。**
   *
   * ## ⚠️ 为什么要 `elementsFromPoint`
   *
   * ⚠️ `computed` 一直说 `body` 的 `background-image` = `url(...)`、`size=100% 100%` ——
   *    **全对**，但截图还是灰的。
   *    → 只有问浏览器「**这一点上从下到上压着哪些元素、各自什么颜色**」
   *      才看得出是**根元素那层不透明底在挡**。
   *
   * ## ✅ 规矩
   *
   * ⚠️ **背景图只画一次、只画在 `body` 上**（那里还有 `100% 100%` 的比例规则）。
   *    `html` 上**只留 `background-color`** —— 那是"没配背景的屏"的兜底灰底。
   */
  background-color: var(--bg);
  /**
   * ⚠️ 切屏时淡入（太快会闪）——
   *    ⚠️ 挪到 `body` 上了（图在那边），这里留着没用，所以去掉。
   */
}

/**
 * ★ **哪一屏用哪张** —— 由 JS 在切屏时给 `<body>` 打 `data-bg`。
 *   ⚠️ 选择器写在 `body[...]` 上、但**变量会被 `html` 继承到** ——
 *      因为 `--screen-bg` 是在 `body` 上定义的，而 `html` 的
 *      `background-image` 用的是 `var(--screen-bg, none)`
 *      ⚠️⚠️ **这里是错的**：变量**不往父元素传**（只往下传）！
 *      `html` 拿不到 `body` 上的变量 → 必须把定义也写到 `html` 上。
 *      → 所以下面用 `body[data-bg]` 那几条的**值**，同时给 `html` 打属性。
 */
body[data-bg='home'], html[data-bg='home'] { --screen-bg: url('./ui/screen-bg-home.png'); }
body[data-bg='contacts'], html[data-bg='contacts'] { --screen-bg: url('./ui/screen-bg-contacts.png'); }
body[data-bg='moments'], html[data-bg='moments'] { --screen-bg: url('./ui/screen-bg-moments.png'); }
body[data-bg='me'], html[data-bg='me'] { --screen-bg: url('./ui/screen-bg-me.png'); }
body[data-bg='peer'], html[data-bg='peer'] { --screen-bg: url('./ui/screen-bg-peer.png'); }
body[data-bg='settings'], html[data-bg='settings'] { --screen-bg: url('./ui/screen-bg-settings.png'); }

/** ★★ **用户自己换的那张** —— 同样两边都写（见上） */
body[data-bg][style*='--screen-bg-custom'],
html[data-bg][style*='--screen-bg-custom'] {
  --screen-bg: var(--screen-bg-custom);
}

/* ==========================================================================
   🚨🚨🚨 背景：**第三次修 —— 别用 `background` 简写**
   ==========================================================================
   ## 前两次为什么都没成

   ① `body::before { z-index: -1 }` —— **画了但被 `.screen{z-index:1}`
      那一层盖住**（每个屏都是层叠上下文）。
   ② 改成画在 `html` 上，但**用了 `background-color` 简写之外的
      `background: var(--bg)`**（在别处）→ 简写会把 `background-image`
      **重置成 `none`**。

   🚨 **`background: <color>` 这种简写是最坑的写法** ——
      它把 image / repeat / position / size **全部重置**。
      ⚠️ 而且**看起来完全正常**（颜色对、量出来 color 也对），
      只有"图没了"这一个症状 —— 所以很难往"简写"上想。

   ## ✅ 正确写法：**只写具体属性，一个简写都不写**
   ========================================================================== */

html {
  /**
   * ⚠️ **`background-color`（具体属性），不是 `background`（简写）**。
   *    原来那条 `html { background: var(--bg) }` 会把 `background-image` 干掉。
   *
   * 🚨🚨 **而且这里【不画背景图】—— 图只画在 `body` 上**（2026-10-05 第四次修）。
   *
   * ⚠️ 这里原来**又写了一遍** `background-image: var(--screen-bg, none)` ——
   *    和 `body` 那条重复。而 `html` 是**根元素**、画在**最底下**，
   *    前面还有这条**不透明的** `background-color` →
   *    · `html` 自己的图 **被它自己的底色盖掉**
   *    · `body` 的图 **被 `html` 的底色盖掉**
   *    ⚠️ **同一张图画两遍，一层都看不见。**
   *
   * ⚠️ 实测（`elementsFromPoint`）：
   * ```
   * BODY  bg-color=透明              bg-image=url(...screen-bg-contacts.png)
   * HTML  bg-color=rgb(236,236,236)  bg-image=url(...screen-bg-contacts.png)  ← 挡人的就是它
   * ```
   */
  background-color: var(--bg);
}

/**
 * ⚠️ 原来那条 `html { background: var(--bg) }`（简写）—— **必须拆成
 *    `background-color`**，不然它会**重置掉 `background-image`**
 *    （而它在这个文件的**后面**，所以是它赢）。
 */

/**
 * ★★ **动态页那个「动态设置」按键**（2026-10-05 用户要求"整合"）。
 *
 * ⚠️ 它直接放在动态流里（不在 `.btn-row` 里），所以要**自己带上间距** ——
 *    不然会像"替她发一条"那个按钮一样**贴住上下元素**
 *    （用户就是为此报过一次）。
 */
.moment-set-btn {
  display: block;
  width: 100%;
  margin: 0 0 12px;
  text-align: center;
}

/* ==========================================================================
   ★★★ 加好友（UID）那张卡（2026-10-05 用户要求）
   ==========================================================================
   用户原话：
   > 加好友方式（每个账号给个 UID，11 位数随机生成，不能重复，
   > 加好友只能通过 UID，AI 也要有专属的 UID）
   ========================================================================== */

.uid-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/** 一行 = "谁" + 那串 UID + 复制键 */
.uid-row {
  display: flex;
  align-items: center;
  gap: 8px;
  /** ⚠️ 手机上 UID 是 11 位数字，一行可能放不下 —— 让它换行（不挤） */
  flex-wrap: wrap;
}
.uid-label {
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 650;
  color: var(--ink-2, #55606c);
}
.uid-code {
  /**
   * ⚠️ **等宽字体** —— UID 是 11 位数字，等宽才**不会看错**
   *    （比例字体里 `1` 和 `l`、`0` 和 `O` 混起来很容易抄错一位）。
   */
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /** ⚠️ 字距放开一点（一串 11 位数字挤在一起不好读） */
  letter-spacing: 1.5px;
  font-size: 16px;
  font-weight: 700;
  color: var(--ink, #12151a);
  /** ⚠️ 可选中（复制键万一不好使，用户还能长按选中） */
  user-select: all;
}
.uid-tag {
  flex: 0 0 auto;
  font-size: 11px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--accent-soft, #d6f6ff);
  color: var(--accent-deep, #3f5566);
}
/** "对方的 UID + 加好友" 那一行 */
.uid-add {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.uid-add .field-input {
  flex: 1 1 130px;
  min-width: 0;
}

/**
 * ★★ **"加来的好友"那个徽章**（2026-10-05）。
 *
 * ⚠️ 和"管理员"那个徽章**长得不一样** ——
 *    它们说的是两件事（一个是权限，一个是"你加过他"），
 *    长得一样的话用户会以为"好友 = 管理员"。
 */
.contact-badge.is-friend {
  background: var(--manga-blue, #2f4f7f);
  color: #fff;
  font-weight: 600;
}

/**
 * ★★ **新建角色那一行有两个输入框**（id + 名字）——
 *    手机上要能放下，所以让它们一起缩（而不是硬挤/换行）。
 */
.uid-add .field-input + .field-input {
  flex: 1 1 100px;
}
/** ⚠️ 三个元素（id / 名字 / 新建）时让"新建"不缩 */
.uid-add .sheet-btn {
  flex: 0 0 auto;
}

/**
 * ★★★ **灰掉的菜单项要看得出来**（2026-10-05）。
 *
 * ⚠️ 我给"撤回"加了 `disabled`（超过 3 分钟就灰掉），
 *    但 `.msg-menu-item:disabled` **原来没有样式** ——
 *    灰掉和没灰**长得一模一样**，那这个"灰"就等于没做。
 *
 * ⚠️ 这是"**加了状态但没加表现**"的典型：
 *    逻辑对了、测试也能过（`disabled` 属性确实设上了），
 *    但**用户看不出区别**。
 */
.msg-menu-item:disabled {
  opacity: 0.42;
  cursor: not-allowed;
}
/** ⚠️ 灰掉的**别再有 hover/按下反馈**（有反馈就像"能点"，是骗人） */
.msg-menu-item:disabled:hover,
.msg-menu-item:disabled:active {
  background: none;
  transform: none;
}

/**
 * ★★ **个人主页上那行 UID**（2026-10-05 用户要求）。
 *
 * ⚠️ 复用设置页那套 `.uid-row` / `.uid-label` / `.uid-code`／`.sheet-btn` ——
 *    **不另起一套**（两处各写一份迟早不一致）。
 *    这里只补"在主页里怎么摆"这一点。
 */
.peer-uid-row {
  /** ⚠️ 主页那一块是居中排的，UID 跟着居中（不然它贴在左边很突兀） */
  justify-content: center;
  /** ⚠️ 和上面那行签名拉开一点（不然挤在一起像连着的） */
  margin-top: 6px;
}

/* ==========================================================================
   ★★★ 关于 Kin / 开发者 / 协议（2026-10-06 用户要求照例图做）
   --------------------------------------------------------------------------
   ⚠️ 铁律照旧：不引彩色（用 var）、不用 `background:` 简写、层级写清楚。
   ========================================================================== */
/* 那张"头图"卡（例图里是紫色大卡） */
.ak-hero {
  text-align: center;
  padding: 22px 16px 18px;
}
.ak-title {
  margin: 0 0 6px;
  font-size: 26px;
  font-weight: 700;
  letter-spacing: 0.2px;
}
.ak-sub {
  margin: 0 0 12px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-2);
  font-style: italic;
}
.ak-ver {
  display: inline-block;
  margin: 0;
  padding: 3px 12px;
  border-radius: 999px;
  background-color: var(--chip, rgba(0, 0, 0, 0.06));
  font-size: 12.5px;
  color: var(--ink-2);
}
/* 「开发者 / 使用说明」那两个并排的键 */
.ak-row--two {
  display: flex;
  gap: 10px;
}
.ak-btn {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 12px 8px;
  border-radius: 14px;
  border: 1.6px solid var(--line);
  background-color: var(--card);
  color: var(--ink);
  font-size: 14px;
  cursor: pointer;
}
/* 那种"一行一个、右边带值"的列表项 */
.ak-list {
  padding: 4px 4px;
}
.ak-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 13px 12px;
  border: none;
  border-bottom: 1px solid var(--line);
  background-color: transparent;
  color: var(--ink);
  font-size: 14.5px;
  text-align: left;
  cursor: pointer;
}
/* ⚠️ 最后一项不要那根线（不然卡片底部多一条悬空的线） */
.ak-item:last-child {
  border-bottom: none;
}
.ak-k {
  flex: 1 1 auto;
  min-width: 0;
}
.ak-v {
  flex: 0 0 auto;
  font-size: 12.5px;
  color: var(--ink-3);
  max-width: 55%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ak-foot {
  margin: 14px 0 6px;
  text-align: center;
  font-size: 12px;
  line-height: 1.7;
  color: var(--ink-4);
}
/* 开发者那一屏的头部（头像 + 名字 + 标签） */
.dev-head {
  display: flex;
  align-items: center;
  gap: 12px;
}
.dev-avatar {
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
  border: 1.6px solid var(--line);
}
.dev-who {
  flex: 1 1 auto;
  min-width: 0;
}
.dev-name {
  margin: 0 0 3px;
  font-size: 17px;
  font-weight: 650;
}
/* 名字后面那个小号英文（例图里"顾琳凯 GULINKAI"那样） */
.dev-en {
  margin-left: 7px;
  font-size: 11.5px;
  font-weight: 400;
  letter-spacing: 1.2px;
  color: var(--ink-3);
}
.dev-role {
  margin: 0;
  font-size: 12.5px;
  color: var(--ink-3);
}
.dev-tag {
  flex: 0 0 auto;
  padding: 4px 10px;
  border-radius: 999px;
  background-color: var(--chip, rgba(0, 0, 0, 0.06));
  font-size: 11.5px;
  color: var(--ink-2);
}
/* 协议正文 */
.doc-body {
  padding-top: 4px;
}
.doc-h {
  margin: 16px 0 6px;
  font-size: 15px;
  font-weight: 650;
}
/* ⚠️ 第一条小标题不要上面那 16px（不然开头空一块） */
.doc-h:first-child {
  margin-top: 4px;
}
.doc-p {
  margin: 0 0 8px;
  font-size: 13.5px;
  line-height: 1.72;
  color: var(--ink-2);
}
/* ==========================================================================
   ★★★ 「这些是它『像人』的底层逻辑」那张卡（2026-10-06 用户要求重写）
   --------------------------------------------------------------------------
   ⚠️ 每条**三行**：它是什么 / 为什么像人 / ⚠️ 删了会怎样。
      用紧凑的行距把它们绑成一组，别让 14 条看起来像 42 条。
   ========================================================================== */
.locked-list {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
}
.locked-list > li {
  padding: 9px 0 10px;
  border-bottom: 1px solid var(--line);
}
/* ⚠️ 最后一条不要那根线（不然卡片底部多一条悬空的线） */
.locked-list > li:last-child {
  border-bottom: none;
  padding-bottom: 0;
}
.locked-what {
  margin: 0 0 3px;
  font-size: 14.5px;
  font-weight: 600;
}
.locked-why {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-2);
}
/* ⚠️ "删了会怎样"要**看得出来是警告**，但不能吵（它是说明，不是错误） */
.locked-lose {
  margin: 4px 0 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-3);
}