/* ============================================================
   对话页 · 模型选择器 + 输入框（`css/chat-selector.css`）

   来历：这套界面先在**预览件**里做了十几轮打磨（用户 2026-09-15 深夜～09-16 凌晨，
   逐轮原话与理由见 `01-交接文档/AIP-交接文档.md` §15 的 16) 一节），
   他验收通过后整份搬进线上这一份。**预览件是原型，这份才是线上代码。**

   搬进来时改了 4 处（都是"预览页是独立页面"和"线上是 App 外壳里的一块"的差别）：
     ① 删掉预览页的整页脚手架：html/body、`.page`、`.topbar`、`.seg`、假对话、`.dock` ——
        线上这些由外壳（chat.css）负责；
     ② `--adm-*` 的**对比度加强**从 `:root` 收进 `.input-box, .panel` 作用域 ——
        预览页可以整页加对比，线上整页加就等于**把整个控制台都改了**（那不在这次范围内）；
     ③ 发送键 `.send-btn` 改名 `.ms-send`：styles.css 里已经有一个不相干的 `.send-btn`
        （还带 `[data-theme="light"]` 的高权重覆盖），不改名会被它抢样式；
     ④ `.panel` 的 z-index 抬到 70：线上外壳有导航栏/Tab 栏/抽屉（最高 60），预览页没有。
     另外浮层在线上挂在 `document.body` 上（**不是**塞在输入框旁边）——
     线上输入区带 `backdrop-filter`，那会变成 fixed 定位的包含块，`placePanel()` 的
     viewport 算式就全错了。JS 里 `ensurePanel()` 会把节点 append 到 body。

   他给的那份设计原样保留：
     · 那颗胶囊 .effort-btn（他的 .composer 行：胶囊 + 发送键；麦克风按他要求删掉）
     · 浮层 .panel + 两个视图（推理强度 / 模型列表）
     · 他的滑块（大白圆 + 轨道 + 4 个刻度点）
     · 极高那档「更快的消耗额度」的提示

   ⚠️ 脚本一律外部文件（CSP `script-src 'self'`，内联 <script> 不执行）。
   ============================================================ */

/* ============================================================
   对比度加强 —— 只加在【本组件】上（`.input-box` / `.panel` 及其子孙继承）
   为什么收作用域：预览页可以整页提对比，线上整页提 = 把控制台其它页面一起改了。
   值一比一照抄预览件（他看过的就是这一套），别自己调。
   ============================================================ */
.input-box, .panel {                      /* 暗色（admin.css 默认就是暗色） */
  --adm-label-2: rgba(235, 235, 245, 0.78);
  --adm-fill:    rgba(120, 120, 128, 0.44);
  --adm-fill-2:  rgba(120, 120, 128, 0.30);
  --adm-sep:     rgba(120, 120, 130, 0.72);
  --ms-panel:    rgba(30, 30, 32, 0.82);
}
[data-theme="light"] .input-box,
[data-theme="light"] .panel {
  --adm-label-2: rgba(60, 60, 67, 0.76);
  --adm-fill:    rgba(120, 120, 128, 0.26);
  --adm-fill-2:  rgba(120, 120, 128, 0.18);
  --adm-sep:     rgba(60, 60, 67, 0.40);
  --ms-panel:    rgba(255, 255, 255, 0.86);
}

/* 输入框在线上要跟正文同宽、居中（预览页是 .dock 里自己撑开的） */
.cx-composer > .input-box {
  width: 100%;
  max-width: var(--content-max-width, 900px);
  margin: 0 auto;
}

/* 滑块那套蓝（和线上对话页滑块同一批色值） */
:root {
  --cx-b1: #00c8ff; --cx-b2: #0a84ff; --cx-b3: #0038bd; --cx-b4: #4b6bff;
  --cx-flat: #2d6fd8;
  /* 极高那两层叠色专用的两个高饱和色（原稿只有四种蓝，叠出来区别太小）：
     电青 + 紫。只用在极高上，别处不碰。 */
  /* 极高那层「能量」专用的两个色：电紫 + 亮电青。
     第二版只用三种蓝叠，色相跨度太窄 —— 换色的时候眼睛看不出「换了」，
     用户连着两次说「不够明显」。这两个色【只】用在 .slider-liquid 上，别处不碰。 */
  --cx-e1: #a855ff; --cx-e2: #3cf0ff;
  /* 「低」那一档的填色 —— 比「中」更淡更沉。
     四档是一条【能量坡】：低（沉）→ 中 → 高（艳）→ 极高（流动的多色），
     这样每切一档，除了白圆在走，颜色本身也在告诉你「往上走了一格」。 */
  --cx-fill-0: #4f7fc4;
  /* 极高那句「更快的消耗额度」的字色：**仍然是蓝的**，只是慢慢在两个蓝之间游走。
     用户 2026-09-15：「更快的消耗额度，这个颜色怎么变了？不是蓝色的了，
                       我也想要这个字，它就是颜色渐变的变化的那种，轻微变化就行，蓝色的」
     —— 上一版我把它换成琥珀，他不要（琥珀是「高速模式」那个开关的颜色，
        用在这句话上确实串了语义）。所以回到蓝，变化幅度收小，只做「轻微」。
     两个端色都取自本页已有的蓝，色相差一档（偏青 ↔ 偏靛）。 */
  --ev-hue-a: #3cc8ff; --ev-hue-b: #4b6bff;
}
[data-theme="light"] {
  --cx-b1: #00a8f0; --cx-b2: #007aff; --cx-b3: #0040c8; --cx-b4: #3a5cff;
  --cx-flat: #6aa9ea;
  --cx-e1: #8330ff; --cx-e2: #00c4e8;
  --cx-fill-0: #9dc4ef;
  /* 浅色主题这两个蓝压深了一档：原来的亮蓝在白底上只有 2.4~3:1，看不清 */
  --ev-hue-a: #0077d4; --ev-hue-b: #3a5cff;
}

/* 尺寸：沿用用户那份文件的流体尺寸 */
:root {
  --ctrl-h:   clamp(52px, 16vw, 60px);
  --knob:     clamp(38px, 10.5vw, 46px);
  --slider-h: calc(var(--knob) - 8px);
  --pad:      clamp(12px, 3.6vw, 15px);   /* 跟着字号一起收紧 */

  /* ---- 这一版的动效令牌 ----
     用户 2026-09-15（第二遍）：「动画都太快了，不够柔和细腻」。
     「柔和」不是在时间轴上硬拖，是三件事一起调：
       ① 时长拉长：进场 420 → 620ms，退场 220 → 340ms；
       ② 过冲收小：1.36 → 1.22（冲得越少越软，1.36 那下是「弹」，1.22 是「陷下去再浮起来」）；
       ③ 曲线上做尾巴：末段控制点 x 收到 0.34 —— 前段走得多、后段拖一条长尾巴慢慢落定。
          这一条才是「细腻」的来源；只加时长会变成「慢」，不是「柔」。 */
  --ease-out:    cubic-bezier(0.23, 1, 0.32, 1);
  --ms-settle:   cubic-bezier(0.16, 1, 0.30, 1);     /* 出场：无回弹，但尾段很长 */
  --ms-spring:   cubic-bezier(0.32, 1.22, 0.34, 1);  /* 进场：轻回弹 + 长尾巴 */
  --ms-grow:     cubic-bezier(0.28, 1.10, 0.36, 1);  /* 换视图长高：几乎只留一点点起伏 */
  /* 对称的缓入缓出（easeInOutCubic）。凡是「出去再回来」的动画（呼吸、抖动、摆动）
     都必须用它，不能用 ease-out：来回的终点速度是 0，用 ease-out 会在极值点上「一顿」，
     看起来是硬的。 */
  --ms-in-out:   cubic-bezier(0.65, 0, 0.35, 1);
  --ease-spring: cubic-bezier(0.32, 1.22, 0.34, 1);
}


/* ============================================================
   慢速 / 高速 —— 在模型选择器【里面】的左上角
   用户口径（第一遍）：「在左上角加一个按钮，这是高速模式，一个闪电图标，打开和关闭都有
   比较明显的动画，要提示……1.5倍率消耗……小提示框就行了，排版要好，
   实际上这个接口后面再做，你先把它的UI写出来。」
   用户口径（第二遍，更正）：「那个慢速按钮的那个在哪里呢，我是让你做到模型选择器里面，
   一个带圆角的正方形按钮，参照Codex.」
   ——所以：【从顶部条搬进浮层】，「推理强度」那一页的左上角，正方形 + 圆角，参照 Codex。
   只做 UI：没有网络请求、没有把倍率真的改掉。

   颜色没有自己造：直接用 admin.css 里现成的 --adm-warn（暗色 #ff9f0a / 淡色 #ff9500）。
   整页唯一一处非黑即彩的地方就是它 —— 状态色该有颜色，其余仍然是黑白灰。
   ============================================================ */
:root {
  --ms-turbo:      var(--adm-warn);
  --ms-turbo-ink:  #2a1a00;                       /* 实心琥珀上的字用深色：对比度约 9:1，
                                                     换成白字只有约 2:1，看不清 */
  --ms-turbo-soft: rgba(255, 159, 10, 0.16);
  --ms-turbo-edge: rgba(255, 159, 10, 0.34);
}
[data-theme="light"] {
  --ms-turbo-soft: rgba(255, 149, 0, 0.13);
  --ms-turbo-edge: rgba(255, 149, 0, 0.30);
}

/* 左上角那一行：方块按钮 + 状态文字 */
.ev-top {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 8px;
}

/* ★ 带圆角的正方形按钮（参照 Codex）：
   36×36、圆角 11px —— 不是圆的、也不是胶囊，就是「方中带圆」。 */
.speed-btn {
  position: relative;
  flex: 0 0 auto;
  width: 36px; height: 36px;
  display: grid; place-items: center;
  padding: 0;
  border: 0;
  border-radius: 11px;
  background: var(--adm-fill-2);        /* 慢速：中性灰底 */
  color: var(--adm-label-2);
  cursor: pointer;
  transition: background-color 240ms linear,
              color 300ms linear,
              transform 260ms var(--ms-spring);
}
.speed-btn:active {
  /* 按下 120ms 内要给到反馈，不能拖；松手那下才走回弹（base 里那条 260ms --ms-spring） */
  transform: scale(.93);
  transition: transform 120ms ease;
}

/* 底色用一个独立层「涨上来」，不是直接改 background ——
   于是你看得见它是从闪电那个位置扩开的，而不是整块突然变色。 */
.speed-btn::before {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background: var(--ms-turbo);
  transform: scale(.4);
  opacity: 0;
  transition: transform 460ms var(--ms-spring),
              opacity 240ms linear;
}
.speed-btn[aria-pressed="true"]::before { transform: scale(1); opacity: 1; }
.speed-btn[aria-pressed="true"] { color: var(--ms-turbo-ink); }   /* 高速：整体点亮 */

/* 图标和那圈电波都得抬到 ::before 上面 */
.speed-icon {
  position: relative; z-index: 1;
  display: grid; place-items: center;
  width: 20px; height: 20px;
}
.speed-bolt { width: 17px; height: 17px; fill: currentColor; transform-origin: 50% 55%; }

/* 打开：闪电「打」一下（缩 → 弹大 → 回正，带一点歪） */
/* ⚠️ 段与段之间用【对称缓入缓出】，不是 ease-out：
   这是个「出去再回来」的摆动，每一段的终点都是一个折返点 ——
   折返点上速度必须是 0。用 ease-out 的话它会「冲」到极值再被硬拽回来，
   看起来是抖，不是摆。 */
@keyframes msZap {
  0%   { transform: scale(.70) rotate(0deg);    animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  34%  { transform: scale(1.26) rotate(-9deg);  animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  62%  { transform: scale(.95) rotate(4deg);    animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  100% { transform: scale(1) rotate(0deg); }
}
.speed-btn[aria-pressed="true"] .speed-bolt { animation: msZap 640ms var(--ms-in-out); }

/* ============================================================
   打开：一圈电波从闪电那里扩出去（这是「明显」的主要来源）

   ⚠️ 这里拆成【两层、【两条】动画，因为「扩开」和「淡出」是两件相反的事：
     · 扩开 = 进场。用强 ease-out（`--ms-settle`）—— 一冲到底再收尾，正合适。
     · 淡出 = 退场。**绝对不能用 ease-out**。ease-out 的曲线前 10% 就把值拉到 90% 了，
       拿它去淡出，不透明度会在二十几毫秒里掉光 —— 眼睛看到的就是「啪一下没了」。
       用户 2026-09-15 说的正是这个：「它消失是直接消失，没有动画的消失」。
       所以淡出单独走一条 linear：从 14% 的峰值到 100%，整整 86% 的时间都在慢慢淡，
       这才叫「化掉」。
   两层各管一个属性，互不干扰（同一层上没法给两个属性配两条曲线）。
   ============================================================ */
.speed-ring {
  position: absolute; inset: 0;
  pointer-events: none;
  transform: scale(.45);
}
.speed-ring-core {
  display: block;
  width: 100%; height: 100%;
  border-radius: 50%;
  border: 2px solid var(--ms-turbo);
  opacity: 0;
}
.speed-ring.is-on                   { animation: msPingExpand 900ms var(--ms-settle); }
.speed-ring.is-on .speed-ring-core  { animation: msPingFade 900ms linear; }

@keyframes msPingExpand {
  /* 2.4，不是 2.9：这圈是从 20px 的图标框扩出去的，2.9 会撞到浮层的左边缘被裁掉。
     2.4 ≈ 48px，刚好完整地扩出方块外面一点点，看得见又不被裁。 */
  from { transform: scale(.45); }
  to   { transform: scale(2.4); }
}
@keyframes msPingFade {
  0%   { opacity: 0; }
  14%  { opacity: .85; }   /* 先快速亮起来（约 126ms）*/
  100% { opacity: 0; }     /* 然后剩下 774ms 一路慢慢化掉 */
}

/* 方块右边那两个字：慢速 / 高速
   不是「啪」地把文字换掉 —— 两个词叠在同一格里，旧的往上滚出去、新的从下面滚进来。
   字数一样（都是两个汉字），所以宽度不变，滚动过程中不会把旁边的东西顶开。 */
.speed-label {
  display: inline-grid;
  height: 1.25em;
  line-height: 1.25;
  overflow: hidden;                    /* 只露一行，滚出去的那半截被裁掉 */
  font-size: 12px; font-weight: 500; letter-spacing: 0;
  color: var(--adm-label-2);
  transition: color 320ms var(--ms-settle);
}
.speed-label > span {
  grid-area: 1 / 1;                    /* 两个词叠在同一格 */
  white-space: nowrap;
  transition: transform 460ms var(--ms-spring),
              opacity 300ms linear;      /* 滚出去的那个词也是「淡出」，同样走线性 */
}
.sl-on { transform: translateY(118%); opacity: 0; }          /* 高速：在下面等着 */
.speed-btn[aria-pressed="true"] + .speed-label .sl-off { transform: translateY(-118%); opacity: 0; }
.speed-btn[aria-pressed="true"] + .speed-label .sl-on  { transform: none; opacity: 1; }
.speed-btn[aria-pressed="true"] + .speed-label { color: var(--ms-turbo); font-weight: 600; }

/* ============================================================
   提示框：在滑块下面，跟着这一页一起长高
   高度由脚本量（和浮层内层同一套做法，这台机器上已经验过能跑）
   ============================================================ */
.tip {
  height: 0;
  overflow: hidden;
  visibility: hidden;                 /* 关着的时候从无障碍树里也拿掉 */
  /* ⚠️ 曲线和时长必须和 .panel.is-spring .panel-inner 一模一样（580ms + --ms-spring）：
     提示框长在浮层里面，浮层也在同时长高 —— 两边用不同的曲线就会「外面的框和内层脱节」，
     看起来像两个东西在动。脚本切这个开关时会把浮层强制回 is-spring，把这条锁死。 */
  transition: height 580ms var(--ms-spring),
              visibility 0s linear 580ms;
}
.tip.is-on {
  visibility: visible;
  transition: height 580ms var(--ms-spring),
              visibility 0s;
}
.tip-inner {
  padding-top: 12px;                  /* 和滑块之间留口气 */
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 280ms linear,        /* 进和出都走线性：这层两边都要能「化」得掉 */
              transform 460ms var(--ms-spring);
}
.tip.is-on .tip-inner {
  opacity: 1;
  transform: none;
  transition-delay: 90ms;
}

/* 卡片里的东西【错开】冒出来（30~80ms 一档）：
   徽标先「啵」一下，标题跟上，两行说明再依次浮现。
   一起出现像一张贴图，一样一样来才像东西被放进去。 */
@keyframes msTipIn {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: none; }
}
@keyframes msBadgeIn {
  from { opacity: 0; transform: scale(.62); }
  to   { opacity: 1; transform: none; }
}
.tip.is-on .tip-badge  { animation: msBadgeIn 460ms var(--ms-spring) 170ms both; }
.tip.is-on .tip-title  { animation: msTipIn 360ms var(--ms-settle) 210ms both; }
.tip.is-on .tip-line   { animation: msTipIn 360ms var(--ms-settle) 250ms both; }
.tip.is-on .tip-line:nth-of-type(3) { animation-delay: 290ms; }
.tip.is-on .tip-close  { animation: msTipIn 360ms var(--ms-settle) 330ms both; }

.tip-card {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 10px 6px 10px 11px;
  border-radius: var(--adm-r-md);     /* 里面这层圆角比浮层小一档，是内嵌卡片 */
  background: var(--ms-turbo-soft);
  box-shadow: inset 0 0 0 0.5px var(--ms-turbo-edge);
  /* 关掉提示时：卡片自己先「收一下再淡掉」，不是整块凭空消失 */
  transition: transform 320ms var(--ms-settle),
              opacity 320ms linear;             /* 同上：淡出走线性才「化」得掉 */
}
.tip-card.is-out { transform: scale(.972); opacity: 0; }

/* 关闭键：视觉上小，手指按得到（34×34）。
   颜色用 --adm-label-3（比正文淡一档）—— 它是次要动作，不该抢「× 1.5」的注意力。 */
.tip-close {
  flex: 0 0 auto;
  width: 34px; height: 34px;
  display: grid; place-items: center;
  margin: -4px -3px 0 0;
  padding: 0;
  border: 0; border-radius: 9px;
  background: transparent;
  color: var(--adm-label-3);
  cursor: pointer;
  transition: background-color 180ms var(--ms-settle),
              color 180ms var(--ms-settle),
              transform 240ms var(--ms-spring);
}
.tip-close svg { width: 13px; height: 13px; display: block; }
.tip-close:active {
  transform: scale(.88);
  transition: background-color 110ms ease, color 110ms ease, transform 110ms ease;
}
.tip-badge {
  flex: 0 0 auto;
  width: 22px; height: 22px;
  display: grid; place-items: center;
  border-radius: 7px;
  background: var(--ms-turbo);
  color: var(--ms-turbo-ink);
}
.tip-bolt { width: 12px; height: 12px; fill: currentColor; }
.tip-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.tip-title {
  margin: 0;
  font-size: 12.5px; font-weight: 600; letter-spacing: -0.01em;
  line-height: 1.25;
  color: var(--adm-label);
}
.tip-line {
  margin: 0;
  font-size: 11.5px; line-height: 1.45;
  color: var(--adm-label-2);
}
/* 「× 1.5」单独拎出来做成小药丸 —— 这句话里最要紧的就是这个数字 */
.tip-mult {
  display: inline-block;
  padding: 0 4px;
  border-radius: 5px;
  background: var(--ms-turbo);
  color: var(--ms-turbo-ink);
  font-size: 11px; font-weight: 600;
  letter-spacing: 0;
}

/* 读屏用的一句话播报（视觉上不占位） */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ============================================================
   ★ 输入框（本轮唯一新增）—— 一个盒子装完
   用户口径：「你把这些东西放在那个输入框里面去，然后高度高一些，
   高度和宽度跟之前一样，只是把那个选择的地方移到右边去，然后发送按钮也是这样子。」
   所以：
     · 整个输入区就【一个】盒子（和线上输入框同一个圆角/发丝边/内投影）；
     · 输入框更高一些（min-height 56px），装得下更多字；
     · 选择那颗胶囊 + 发送键都收进这个盒子里，靠【右】；
     · 尺寸保持之前那档（胶囊 34px / 发送键 44px，就是线上那套尺度）；
     · 胶囊不再是 flex:1 撑满 —— 它只包住自己的字，所以整体不会被撑大。
   ============================================================ */
.input-box {
  padding: 6px 6px 6px 12px;
  border-radius: var(--adm-r-lg);
  background: var(--adm-surface);
  box-shadow: 0 0 0 0.5px var(--adm-sep), var(--adm-shadow-raise);
  transition: box-shadow 160ms var(--adm-ease-out);
}
.input-box:focus-within { box-shadow: 0 0 0 1px var(--adm-label-4), var(--adm-shadow-raise); }

.ms-ta {
  display: block;
  width: 100%;
  min-height: 32px;                    /* 矮下来：整块回到和线上输入框差不多的高度 */
  max-height: 168px;
  padding: 4px 6px 4px 0;
  border: 0;
  background: transparent;
  color: var(--adm-label);
  font-size: max(16px, 1rem);          /* ≥16px：iOS 聚焦时不会把整页放大 */
  line-height: 1.5;
  letter-spacing: -0.02rem;
  resize: none;
  outline: none;
  overflow-y: auto;
}
.ms-ta::placeholder { color: var(--adm-label-3); }

/* 盒子里那一行：胶囊 + 发送键，整体靠右 */
.composer-row {
  display: flex;
  align-items: center;
  justify-content: flex-end;           /* ← 移到右边 */
  gap: 8px;
  margin-top: 4px;
}

/* ============================================================
   ↓↓↓ 以下全部照搬用户给的 模型选择器.html，别改 ↓↓↓
   ============ 胶囊 + 发送键（他设计里的那两件东西，尺寸保持原档） ============ */
.effort-btn {
  min-width: 0;
  max-width: 68%;
  min-height: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.34em;
  padding: 0 2px;
  border: 0;
  border-radius: var(--adm-r-full);
  /* ⚠️ 不要灰底 —— 用户 2026-09-15：那个把 Select effort 框住的灰色框不需要。
     所以这里 background 是透明的，只留文字 + 箭头；按下时才给一点点反馈。 */
  background: transparent;
  color: var(--adm-label);
  /* 原来是他原稿的 clamp(15px, 4.7vw, 19px)，用户 2026-09-15 说「字太大」→ 下调一档。
     字重仍照他原稿（400），只有大小动。 */
  font-size: clamp(12.5px, 3.5vw, 14px);
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1;
  white-space: nowrap;
  overflow: hidden;
  cursor: pointer;
  transition: background-color 160ms var(--adm-ease-out);
}
.effort-btn:active { background: var(--adm-fill-3); }
.effort-btn-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ⛔ 胶囊上那行字不再「向上弹一下」了。用户 2026-09-15：
     「我切换模型的时候，输入框里面的那个东西不要跳啊，不要向上弹一下」。
   （msTick 就是往上 2.5px 再回来 —— 就是他看到的那一下。keyframes 一并删了。）
   “选上了”现在靠：那一行的勾弹出来 + 胶囊上的字换成新模型名。 */
.effort-btn .chev-down {
  flex: 0 0 auto;
  width: 12px; height: 12px;
  color: var(--adm-label-2);
  transition: transform 420ms var(--ms-spring);   /* 翻箭头也带一点回弹，别硬翻 */
}
/* 浮层开着时箭头翻过来（底色依旧不加，别把灰框加回来） */
.effort-btn[aria-expanded="true"] .chev-down { transform: rotate(180deg); }

.ms-send {
  flex: 0 0 auto;
  width: 40px; height: 40px;           /* 略收一点，整块别那么高 */
  display: grid; place-items: center;
  padding: 0;
  border: 0; border-radius: 50%;
  background: var(--adm-fill-2);
  color: var(--adm-label-3);
  cursor: pointer;
  transition: background-color 180ms var(--adm-ease-out), color 180ms var(--adm-ease-out),
              transform 140ms var(--adm-ease-out);
}
.ms-send:active {
  transform: scale(.94);
  transition: background-color 110ms ease, color 110ms ease, transform 110ms ease;
}
.ms-send.is-active { background: var(--adm-accent); color: var(--adm-on-accent); }
.ms-send svg { display: block; width: 20px; height: 20px; }


/* ============================================================
   浮层：自然地弹在 Select 上面
   用户口径：「它这个弹窗就自然地弹在那个 Select 那个上面就行了」
            「然后那个变大的动画也没有了」
   所以：位置回到输入区【上方】，并且恢复「变大」——内层高度从 0 长开 + 整体 scale 弹起。
   ============================================================ */
.panel {
  /* ⚠️ 位置由 JS 按那颗 Select 的位置现算（placePanel）——
     所以是 fixed + left/bottom 内联值，不是「贴在输入框上沿」。
     用户：「它就弹在选择的那个上面就行了，不是弹在输入框上面，把它覆盖住也可以」。
     下面这两个值是没算之前的兜底，别删。 */
  position: fixed;
  left: 12px;
  bottom: 96px;
  width: min(90vw, 320px);              /* 收窄一点：字别占那么宽 */
  max-height: 80dvh;
  border-radius: var(--adm-r-xl);
  background: var(--ms-panel);
  -webkit-backdrop-filter: blur(40px) saturate(180%);
  backdrop-filter: blur(40px) saturate(180%);
  box-shadow: var(--adm-shadow-pop);
  transform-origin: var(--ms-origin, 24px) 100%;   /* 从触发它的那颗 Select 上长出来 */
  transform: translateY(16px) scale(.92);          /* 初始小一圈（不是 scale(0)）*/
  opacity: 0;
  pointer-events: none;
  z-index: 70;                      /* 线上外壳的导航栏/Tab 栏/抽屉最高 60（见 CSS 头注释 ④）*/
  will-change: transform, opacity;
  /* 收起：走 base 这份 —— 无回弹、和进场【同一条路】（原路退回）。
     340ms 比原来的 220ms 慢，但仍然明显快于进场 620ms：退场要「干脆」，不能「拖」。 */
  transition: opacity 240ms linear,          /* 淡出一定线性（ease-out 会在头 10% 掉光）*/
              transform 340ms var(--ms-settle);
}
.panel.is-open {
  opacity: 1;
  transform: translateY(0) scale(1);                /* ← 「变大」 */
  pointer-events: auto;
  /* 展开：620ms + 轻回弹 + 长尾巴。用户「太快了，不够柔和」说的就是这里。
     opacity 不跟着拉到 620 —— 淡入太慢会显得「糊」，240ms 就够。 */
  transition: opacity 240ms var(--ms-settle),
             transform 620ms var(--ms-spring);
}

/* 内层高度从 0 长开 —— 和上面那条 scale 同一条曲线、同一个节拍，
   两件事叠在一起才是「整块弹开」，不是「框变大 + 内容各演各的」 */
.panel-inner {
  position: relative;
  height: 0;
  overflow: hidden;
  border-radius: var(--adm-r-xl);
  transition: height 320ms var(--ms-settle);
}
.panel.is-spring .panel-inner { transition: height 580ms var(--ms-spring); }
/* 换视图时的长高：单独的曲线 —— 260ms 内开完，淡入才有地方发生。
   1.18 是「一点点就够」的过冲：框会比内容多出几个 px 再收回来，那一下就是手感。 */
.panel.is-swap .panel-inner { transition: height 380ms var(--ms-grow); }

/* ============ 两个视图：淡出 / 淡入 ============
   用户 2026-09-15：「点选择模型，和返回的淡出过渡动画没了」——
   上一版这里确实是「看不见淡入淡出」，原因不是没写 transition，是【被裁掉了】：
   panel-inner 是 overflow:hidden，切视图时它的高度同时在从 140 → 380 长，
   新内容 180ms 就淡完了，可那时框才开到一半，剩下的部分是一点点「露」出来的 ——
   视觉上就只剩「变大」，淡入淡出全被裁在框外。
   修法：把两件事排开 —— 框先开，新内容【等 110ms 再淡入】，旧内容 200ms 先走。
   于是先淡出 → 框开 → 再淡入，三段都看得见。
   ⚠️ 这里有个必须守住的不等式（自检里是硬断言，不是写在注释里就完事）：
        框张开的时间 ≤ 淡入的延迟 + 淡入时长
      380ms ≤ 110 + 320 = 430ms ✓ —— 框开完之前淡入不会结束，所以淡入全程都看得见。
      上一版「看不见淡入淡出」就是这条不等式被破了（180ms 就淡完，框才开一半）。

   方向：进场上浮 6px、退场退回原位（原路进出，同一侧的位移不会互相打架）；
   绝不做左右滑（用户：「滑来滑去，看着烦」）。 */
.view {
  position: absolute; top: 0; left: 0;
  width: 100%;
  padding: var(--pad);
  opacity: 0;
  transform: translateY(6px) scale(.994);
  pointer-events: none;
  /* ↓ 这一段是【退场】用的：纯淡出、没有延迟。200ms —— 比淡入快，但不脆。 */
  transition: opacity 200ms linear,
              transform 200ms var(--ms-settle);
}
.view.is-active {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  /* ↓ 这一段是【进场】用的：等框张开一点（110ms）再淡入，位移用长尾巴的回弹曲线 */
  transition: opacity 320ms var(--ms-settle) 110ms,
              transform 520ms var(--ms-spring) 110ms;
}

/* 模型列表：逐行错开 45ms 冒出来（这是「细腻」那部分 —— 四行一起来像贴图，
   一行一行来才像东西被放进去）。animation 只在每次切进模型视图时重挂一次，
   不是能连点的高频交互，所以用 keyframes 没问题。
   ⚠️ 这里是【一次性闸门】：闸门 = .is-enter，由 chat-selector.js 的 armStaggerEnd()
     在整段播完之后摘掉（不是靠 animationend —— reduced-motion 下它永远不来）。
     ⛔ 别改成「常驻类名」：那样任何一次列表重建都会让整列重播一遍
     （用户 2026-09-16 报的「选了个模型，弹出动画又给我来一次」就是这么来的）。 */
@keyframes msRowIn {
  from { opacity: 0; transform: translateY(9px); }
  to   { opacity: 1; transform: none; }
}
.model-list.is-enter .model-row {
  animation: msRowIn 380ms var(--ms-settle) both;
  animation-delay: calc(110ms + var(--ms-i, 0) * 45ms);
}

/* ---------- 推理强度视图（他那一份） ---------- */
.effort-display { display: flex; flex-direction: column; align-items: center; }

.effort-value {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px;
  width: 100%;
  padding: 2px 0 0;
  border: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
  min-height: 44px;
}
.ev-main {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 3px;
  color: var(--cx-b2);
  font-size: 18px;                        /* 原来是 title2（22px）—— 太大，降一档 */
  font-weight: 500;
  letter-spacing: var(--adm-ls-title2);
  line-height: 1.15;
}
.ev-main .chev-right {
  flex: 0 0 auto;
  width: .6em; height: .6em;
  margin-top: .06em;
  transition: opacity 340ms linear;
}
.ev-sub { transition: opacity 340ms linear; }

/* ============================================================
   换档「换字」——每切一档都要有过渡，不允许字直接跳
   用户 2026-09-15：「他从第一切换到下一个档的时候，他的动画也要好，有动画，
                     每个要切换的时候，它都要一点过渡动画」

   做法：方向感知的两段式换字。
     · 旧字先【走出去】——往上换档就往上走 7px，往下换档就往下走
     · 新字再【从对面升上来】——从 -9px / +9px 落到原位

   两条曲线分开写，因为它们是两种东西（这是这一版最容易写错的地方）：
     · 透明度 = 线性。ease-out 挂在淡出上，前 10% 就把透明度拉到底，
                剩下的时间你什么都看不见 —— 那等于没淡出。
     · 位移 = 退场用 settle（快出慢收），进场用 spring（落到位时留一点余韵）。

   为什么退场只有 150ms：换字必须「先出去再进来」，但退场一慢，
   手指已经换好档了、字还没升上来，手感就木了。
   拖动时脚本会把 --ev-out 改成 90ms —— 别拖手指的后腿。
   ⚠️ --ev-out 这个数【脚本和 CSS 用的是同一个】：JS 写 var、CSS 拿它当动画时长，
      改一边记得改另一边（check.mjs 里锁着这条）。
   ============================================================ */
.effort-value { --ev-out: 150ms; }
.ev-label {
  display: inline-block;          /* 要能 transform：行内元素是转不动的 */
  /* 这里【故意不挂 will-change】：常驻的图层提升会让文字的次像素抗锯齿变糊，
     换字是一闪而过的事，交给浏览器自己在动画期间决定就够了。 */
}
.effort-value.is-wo[data-dir="up"] .ev-label {
  animation: msWordFadeOut var(--ev-out) linear both,
             msWordLiftOut var(--ev-out) var(--ms-settle) both;
}
.effort-value.is-wo[data-dir="down"] .ev-label {
  animation: msWordFadeOut var(--ev-out) linear both,
             msWordDropOut var(--ev-out) var(--ms-settle) both;
}
.effort-value.is-wi[data-dir="up"] .ev-label {
  animation: msWordFadeIn 260ms var(--ms-settle) both,
             msWordRiseIn 460ms var(--ms-spring) both;
}
.effort-value.is-wi[data-dir="down"] .ev-label {
  animation: msWordFadeIn 260ms var(--ms-settle) both,
             msWordSinkIn 460ms var(--ms-spring) both;
}
/* 拖动中那一档：退场更短、进场【不弹】（手指还在动，弹一下是干扰），
   整段也更短 —— 后面排队的下一档才不用等太久。 */
.effort-value.is-quick.is-wi[data-dir="up"] .ev-label {
  animation: msWordFadeIn 130ms var(--ms-settle) both,
             msWordRiseIn 200ms var(--ms-settle) both;
}
.effort-value.is-quick.is-wi[data-dir="down"] .ev-label {
  animation: msWordFadeIn 130ms var(--ms-settle) both,
             msWordSinkIn 200ms var(--ms-settle) both;
}

/* ============================================================
   极高那句「更快的消耗额度」：要【明显】
   用户 2026-09-15：「那个动画里面还是不够明显，太不明显了」

   所以它比普通换字大一整号：
     · 从 14px 外升上来（普通换字是 9px），并且从 0.86 撑到 1.07 再落回
     · 淡入给到 380ms（普通是 260ms）—— 给它时间被看见

   用户 2026-09-15（第二遍）：「更快的消耗额度，这个颜色怎么变了？不是蓝色的了，
                              我也想要这个字，它就是颜色渐变的变化的那种，
                              轻微变化就行，蓝色的」
   → 琥珀撤掉，回到蓝，而且只在【两个蓝之间】慢慢游走：
     · 三站（0% / 50% / 100%），两端同色 —— 这是一口「呼吸」；三个色来回跳就是闪烁，
       那不是他说的「轻微」
     · 周期 4.3s：慢到你能读完这句话，才觉得它是在「渐变」而不是在眨眼
     · 两个端色都取自本页已有的蓝，色差只有一档：偏青 ↔ 偏靛

   颜色只挂在 .is-warn 上，【不动 transform 的基态】——
   基态一变，退场那条 keyframes 的 from 就对不上了，字会跳一下（这一轮刚修的就是这类毛病）。
   ============================================================ */
/* 颜色漂移挂在【父层 .ev-main】上，不挂在字上。
   为什么：字上那几条进场/退场动画是【整条 animation 覆盖】的，
   漂移一旦挂在字上，退场那一瞬就被整条换掉 —— 颜色「啪」地跳回基色。
   挂在父层就没人碰得到它，行内的颜色由字继承下去。 */
.effort-value.is-warn .ev-main {
  color: inherit;
  animation: msWarnHue 4.3s var(--ms-in-out) infinite;
}
[data-theme="light"] .effort-value.is-warn .ev-main {
  animation: msWarnHueLight 4.3s var(--ms-in-out) infinite;
}
.effort-value.is-warn.is-wi .ev-label {
  /* 进场那两条要一起写：animation 是【整条覆盖】的 ——
     少写一条，颜色的漂移就会在进场那 460ms 里停一下再从头开始（看得见的那一顿）。 */
  animation: msWordFadeIn 360ms var(--ms-settle) both,
             msWarnRise 520ms var(--ms-spring) both;   /* 弹簧：落到位时过一点头再收住 */
}
/* 在两个蓝之间慢慢游走。两端同色 + 对称曲线（两端速度都是 0）→ 循环接缝看不出来。
   ⚠️ 颜色写【字面量】，不写 var(--ev-hue-*) —— 和下面曲线那个坑同一个道理：
      @keyframes 里的 var() 在部分浏览器里不生效，写死最稳。
      浅色主题单独一组（下面有一条覆盖），两个值必须和 --ev-hue-* 一模一样，check.mjs 会比对。 */
@keyframes msWarnHue {
  0%, 100% { color: #3cc8ff; }
  50%      { color: #4b6bff; }
}
@keyframes msWarnHueLight {
  0%, 100% { color: #0077d4; }
  50%      { color: #3a5cff; }
}
/* ⚠️ 单段 + 弹簧，【不再靠每一站自带的曲线】。
   用户 2026-09-15：「更快的消耗额度，它这几个字出来的，它就是线性动画，不是非线性，就是不流畅」。
   原因就是这个写法：animation 级那条给的是 linear，指望「每一站的 animation-timing-function」
   去覆盖它 —— 那个覆盖在某些浏览器里【不生效】，于是整段真的变成匀速直线运动。
   现在改成：只有 from / to 两站，曲线写在 animation 那一行（var() 写在动画属性上是稳的），
   弹簧曲线自带过冲 → 字是「落」下来的，一眼就是非线性。 */
@keyframes msWarnRise {
  from { transform: translate3d(0, 14px, 0) scale(.86); }
  to   { transform: none; }
}

/* 箭头跟着字一起换：新字比旧字宽窄不一样，箭头会横跳一下；
   让它跟字同时淡，那一跳就藏在「看不见」的那一瞬间里。 */
.effort-value.is-wo .chev-right { animation: msWordFadeOut var(--ev-out) linear both; }
.effort-value.is-wi .chev-right { animation: msWordFadeIn 320ms var(--ms-settle) both; }
/* ⚠️ 警告那句话挂着的时候，箭头必须【消失】。
   用户 2026-09-15：「它的那个箭头还是在，这个应该消失掉」。
   为什么上一版没消失：脚本写的是行内 style.opacity = '0'，
   而上面那条【动画】带着 both 填充模式 ——
   CSS 动画的优先级高于行内样式，于是箭头被动画又拉回了不透明。
   这里直接把动画按住（关掉）并把不透明度写死为 0，就不会再被拉回去。 */
.effort-value.is-warn .chev-right { animation: none; opacity: 0; }

@keyframes msWordFadeOut { from { opacity: 1 } to { opacity: 0 } }
@keyframes msWordFadeIn  { from { opacity: 0 } to { opacity: 1 } }
@keyframes msWordLiftOut { from { transform: none } to { transform: translate3d(0, -7px, 0) } }
@keyframes msWordDropOut { from { transform: none } to { transform: translate3d(0,  7px, 0) } }
@keyframes msWordRiseIn  { from { transform: translate3d(0,  9px, 0) } to { transform: none } }
@keyframes msWordSinkIn  { from { transform: translate3d(0, -9px, 0) } to { transform: none } }
.ev-sub {
  max-width: 100%;
  font-size: 12.5px;                      /* 原来是 subhead（15px）—— 降一档 */
  font-weight: 400;
  letter-spacing: var(--adm-ls-sub);
  line-height: 1.25;
  color: var(--adm-label-2);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- 滑块（他那一份：大白圆 + 轨道 + 4 个刻度点） ---------- */
.slider {
  position: relative;
  margin-top: clamp(12px, 3.6vw, 16px);
  height: var(--slider-h);
  border-radius: var(--adm-r-full);
  background: var(--adm-fill-2);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  cursor: pointer;
  outline: none;
  overflow: visible;
}
.slider:focus-visible { box-shadow: 0 0 0 3px var(--cx-b2); }

/* 填色层：外壳只负责【剪】，真正那条会动的胶囊在里面（::before）。
   为什么要拆两层 —— 这是三轮才定下来的：
     · 第一版：每帧改 width。用户：「滑动一点都不流畅，会一卡一卡的」。
       width 是布局属性，每帧一次布局 + 重绘，必卡。
     · 第二版：整条胶囊 + clip-path 剪个口子。不重排了，但那个口子是【一刀切平】的，
       白圆是圆的、盖不住它上下两侧的角 —— 用户当场看见：「那个条怎么又变成直角了？它是个平的」。
     · 第三版（这一版）：外面那层用 overflow:hidden + border-radius 把内容剪成胶囊，
       里面那条【整颗胶囊】只做 translateX —— 纯合成器动画，不重排、不重画，圆角一分不少。
       往左推多少 = 轨道宽 - 要露出的长度，于是它的【右端圆头】正好停在白圆右边，
       左端推出屏幕外，露出来的是外壳自己的圆角。两头都是圆的。 */
.slider-fill {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;                  /* 只用来剪，它自己不画任何东西 */
  pointer-events: none;
  z-index: 1;
}
.slider-fill::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;            /* 自己也是一颗完整的胶囊 */
  background: var(--cx-flat);
  /* ⚠️ 位移只能走 transform（--fill-x 由脚本每帧写）。
       千万别改回去用 width，也别用 clip-path 切口 —— 上面写了两者的下场。 */
  /* 默认值给 -100%：脚本跑之前它就是藏在屏幕外的，
     不然开屏那一瞬会闪一条满格的蓝条出来。 */
  transform: translate3d(var(--fill-x, -100%), 0, 0);
  will-change: transform;            /* 提前提到自己一层，省得拖到一半才提升（那一下会掉帧） */
  /* 颜色也要过渡：换一档颜色跟着深一格，硬切会显得那一下特别干 */
  transition: background-color 360ms var(--ms-settle);
}

/* 四档的颜色坡：低（沉）→ 中 → 高（艳），极高交给上面那层流动的多色。
   每切一档，除了白圆在走，颜色本身也在说「往上走了一格」。 */
.slider[data-lv="0"] .slider-fill::before { background: var(--cx-fill-0); }
.slider[data-lv="1"] .slider-fill::before { background: var(--cx-flat); }
.slider[data-lv="2"] .slider-fill::before { background: var(--cx-b2); }
.slider[data-lv="3"] .slider-fill::before { background: var(--cx-b2); }

/* ============================================================
   极高（max）那一层「能量」—— 第三版，往【明显】上调

   用户 2026-09-15（第一遍）：「切换到极高之后，那个里面的动画感觉还是不怎么地」
   用户 2026-09-15（第二遍）：「那个动画里面还是不够明显，太不明显了」+「就是流动的那个」
   所以这一版只改一件事：让「活」这件事一眼可见。改法逐条写清楚，别再改回去。

   旧做法（第一版）：一层渐变 + filter: hue-rotate/saturate 呼吸。三个毛病：
     ① filter 是【逐帧重绘】，比透明度贵一个量级，手机上掉帧；
     ② hue-rotate 转过中间色会把饱和的蓝「洗」成灰蓝 —— 中间那段是浑的；
     ③ 一条曲线来回摆，节奏太规整，看两眼就知道在循环。

   第二版是「三层静止渐变 + 透明度交叉淡入淡出」，方向是对的，但【幅度太小】：
     · 两层都从 0 淡到峰值再回 0 —— 大部分时间只有底色那层可见，看着像一块死蓝；
     · 周期 4.9s / 7.3s，慢到你不盯着看就发现不了它在换色；
     · 三种蓝叠来叠去，色相跨度窄，换色的时候你看不出「换了」。

   这一版（第三版）四条改动，每一条都对着上面一个毛病：
     ① 【幅度】：::before 的呼吸【不再落回 0】，垫在 .22 —— 紫和电青永远在场一部分，
        底色那层再也压不住它们，胶囊里【任何时刻都至少两种色相在混】；
     ② 【速度】：周期 3.7s / 5.4s（37 与 54 互质，20 秒内根本对不齐），
        比原来快一档，不用盯着看也能感觉到它在走；
     ③ 【色相跨度】：新加两个【只在极高用】的色（电紫 + 亮电青），
        三种蓝换成「蓝 → 电青 → 电紫」的大跨度 —— 换色那一下是看得出来的；
     ④ 【亮端会走】：三层各自的亮端落在不同位置（底色亮端在最右、::before 在最左、
        ::after 在中间偏右），透明度交叉的时候，胶囊的「亮的那一侧」自己在挪 ——
        这就是「活的」的来源。

   铁律一条没破：
     · 全程只动 opacity，走合成器，不重绘、不掉帧；
     · 三层都是 inset:0 + 圆角继承 —— 仍然是「一层铺满整颗胶囊」；
     · 白圆永远在最上面（z-index 4）；
     · ⚠️ 依旧【不挂 background-position、也没有任何位移】。用户说过：
       「把它调的激进，并不代表它的速度就变得动，那个滑来滑去，看着烦」。
       猛，靠的是颜色浓度、色相跨度和交叉幅度，不是位移。
   ============================================================ */
.slider-liquid {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  z-index: 2;
  /* 淡入淡出都走线性：ease-out 用在淡出上等于没淡出（见上面电波那段） */
  transition: opacity 420ms linear;
  /* 底色：永远铺满 —— 中间任何时候都不会发白。
     亮端压在【中间偏左】（电青那一块），右端（极高时白圆待的地方）落回靛蓝 ——
     白圆落在最亮的那块上会发糊，所以最亮的色故意不放在右边。 */
  background-image: linear-gradient(100deg,
    var(--cx-b3) 0%, var(--cx-b1) 20%, var(--cx-e2) 40%, var(--cx-b2) 66%, var(--cx-b4) 100%);
}
.slider-liquid::before,
.slider-liquid::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  opacity: 0;
  pointer-events: none;
}
/* 亮端在【最左】（电青 → 电紫）——和底色的亮端反着来 */
.slider-liquid::before {
  background-image: linear-gradient(158deg,
    var(--cx-e2) 0%, var(--cx-e1) 26%, var(--cx-b2) 54%, var(--cx-b3) 100%);
  animation: cxSurge 3.7s var(--ms-in-out) infinite;
}
/* 亮端在【中间偏右】（电紫 → 电青）——第三个位置 */
.slider-liquid::after {
  background-image: linear-gradient(62deg,
    var(--cx-e1) 0%, var(--cx-b1) 30%, var(--cx-e2) 60%, var(--cx-b4) 100%);
  animation: cxSweep 5.4s var(--ms-in-out) infinite;
  animation-delay: -2.3s;          /* 负的相位差：一打开就不是同步的 */
}
.slider[data-lv="3"] .slider-liquid { opacity: 1; }

/* 两组「呼吸」。两端的值都必须【和另一端的自己相等】、而且是这条对称曲线的极值点
   （速度天然为 0）—— 这样循环接缝才看不出来。
   区别只在幅度：cxSurge 垫在 .22（永远在场一部分），cxSweep 允许回到 0。 */
@keyframes cxSurge {
  0%, 100% { opacity: .22; }
  50%      { opacity: 1; }
}
@keyframes cxSweep {
  0%, 100% { opacity: 0; }
  50%      { opacity: .9; }
}

.slider-dots {
  position: absolute;
  inset: 0;
  padding: 0 calc(var(--knob) / 2);
  display: flex; align-items: center; justify-content: space-between;
  pointer-events: none;
  z-index: 3;
}
.slider-dot {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--adm-label-3);
  flex: 0 0 auto;
  transition: background-color 460ms var(--ms-settle);   /* 跟着能量层一起淡，不硬切 */
}
.slider[data-lv="3"] .slider-dot { background: rgba(255, 255, 255, .62); }

/* ── 滑块下面那排档位名（低 / 中 / 高 / 极高）──
   和「我的 → 默认推理等级」那颗滑块是同一套长相（app.js 的 .cx-slider-ticks）。
   同一个产品里，「同一件事的控件」只有一种长相 —— 用户的两处原话：
     · 那边：「我会在设置里调它」；
     · 这边：「在对话的主页面也沿用我现在这个风格的UI」。
   所以四个名字的位置算式**逐字**照抄那边的：
       left = --knob/2 + (100% - --knob) × 档位 / 3  然后 translateX(-50%)
   这和 .slider-dots 的 padding + space-between、以及 .slider-knob 的
   translate3d 走的是同一条算式 —— 名字、刻度点、白圆三者严格重合。
   （那边写的是 15px + (100% - 30px) × f，因为那边是写死的 30px 胶囊 + 26px 圆；
     这边是流体尺寸，所以换成 --knob。两条算式是同一个东西。）

   ⚠️ 触摸目标：这一行本身只有 36px 高，够不到 44px。用 ::after 撑一个 44×44 的
      透明热区（必须是 position:absolute，否则会把这一行撑高）。
      热区往【上】只多出 4px —— 正好等于白圆探出轨道的那 4px，
      所以它一格都不侵占上面的滑块拖动区（多一点，拖到轨道下沿就会变成点名字）。 */
.slider-ticks {
  position: relative;
  height: 36px;
  margin-top: 0;
}
.slider-ticks button {
  position: absolute;
  top: 0;
  height: 36px;
  padding: 0 6px;
  /* 字在这一行里【垂直居中】：36px 的行高只放一行 12px 的字，靠上排的话
     会紧贴着白圆的下沿（白圆还会探出轨道 4px），看着像「粘在一起」。 */
  display: flex;
  align-items: center;
  justify-content: center;
  transform: translateX(-50%);
  border: 0;
  border-radius: var(--adm-r-sm);
  background: transparent;
  color: var(--adm-label-3);
  font: inherit;
  font-size: var(--adm-t-caption2);
  letter-spacing: 0;
  white-space: nowrap;
  cursor: pointer;
  transition: color 200ms var(--adm-ease-out);
}
.slider-ticks button::after {          /* 44×44 的隐形热区 */
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -4px;
  height: 44px;
}
.slider-ticks button:nth-child(1) { left: calc(var(--knob) / 2); }
.slider-ticks button:nth-child(2) { left: calc(var(--knob) / 2 + (100% - var(--knob)) * 0.3333); }
.slider-ticks button:nth-child(3) { left: calc(var(--knob) / 2 + (100% - var(--knob)) * 0.6667); }
.slider-ticks button:nth-child(4) { left: calc(100% - var(--knob) / 2); }
.slider-ticks button.is-active { color: var(--adm-label); font-weight: 600; }
/* ⚠️ hover 只在真的有悬停能力的设备上生效（手机上 :hover 会「粘住」——
   点完一次那个颜色就再也退不掉，用户会以为它一直是选中的状态）。 */
@media (hover: hover) and (pointer: fine) {
  .slider-ticks button:not(.is-active):hover { color: var(--adm-label-2); }
}

/* 落点那一下：点到的刻度点自己弹一下 —— 每切一档都有个「回音」落在刻度上。
   ⚠️ 用的是 scale【独立属性】，不是 transform。白圆和刻度点的位置都是脚本写
      transform 摆的，动画一旦碰 transform 就会把那个位置【整个盖掉】
      （CSS 动画的优先级高于行内样式）—— 白圆会瞬间跳回左端，这是个真会翻车的坑。 */
.slider-dot.is-hit { animation: msDotHit 460ms var(--ms-spring); }
@keyframes msDotHit {
  0%   { scale: 1;   animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  42%  { scale: 1.9; animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  100% { scale: 1; }
}

/* 白圆：永远在最上面、永远是白的（线上铁律，别改）。
   极高时它身子底下会亮起一圈光晕 —— 光晕用 box-shadow 过渡（一个小圆，代价可以忽略）。 */
.slider-knob {
  position: absolute;
  top: 50%; left: 0;
  width: var(--knob); height: var(--knob);
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .20), 0 0 0 1px rgba(0, 0, 0, .06);
  transform: translate3d(0, -50%, 0);
  pointer-events: none;
  will-change: transform;
  z-index: 4;
  /* 位置（transform）由脚本写死，且【不参与过渡】—— 它一过渡，白圆就跟不上手指。
     白圆不再有任何缩放/光圈/电波（见下两段注释），所以这里也没有 scale 的事。 */
  transition: box-shadow 520ms var(--ms-settle);
}
/* ⛔ 白圆连「啵一下」也不要了。用户 2026-09-15：
     「而且它还会瞎动，一大一小的那样子」。
   原因很具体：这个缩放每跨一档就播一次，而你正好就是在拖它的时候跨档 ——
   白圆就在你手指底下一大一小，看起来就是「不跟手、乱滑」。
   每过一档的反馈留给【刻度点】（它不在你手指下）和【颜色阶】。
   白圆就只是一个白圆：位置由轨道给，大小永远不变。 */
/* ⚠️ 白圆【不再有光晕】。用户 2026-09-15：
     「切换到最高极高的时候，它那个白色的点，它那个放光出来，那个不好看，取消掉」。
   所以 [data-lv="3"] 这条 box-shadow 直接删掉了 —— 别再加回来。
   极高这件事由别的东西说：身底下那层流动的多色、四个刻度点变白、那句话换掉。
   ⛔ 白圆身上【不许再挂任何圈】：用户 2026-09-15 连着说了两遍 ——
      「它那个放光出来，那个不好看，取消掉」→「那个白色圆点外面套的一层那个光圈也给我删掉，
        看着烦」。上一版删的是光晕（box-shadow），留着两圈「电波」（每过一档的小圈、
        冲到极高的大圈），他说的就是这个。所以 ::after 连同 msIgniteGrow/Fade、
        msStepRingGrow/Fade 全部删干净了 —— 别再加回来。
      白圆就只是一个白圆：它自己在原地啵一下（scale），位置由轨道给，够了。 */

/* 松手归位：白圆带一点回弹（会稍过头再回来），填色层老实跟上 —— 手感就是这里出来的 */
.slider.is-snapping .slider-fill::before {
  /* 归位【才】给过渡。拖动过程中绝对不能有 —— 有过渡就等于填色层追不上手指。 */
  transition: transform 340ms var(--ms-settle), background-color 360ms var(--ms-settle);
}
/* ⚠️ 这条是【整条 transition 的覆盖】，所以 box-shadow 必须一起写上：
   漏了它，「拖到极高」那一下白圆的光晕会在 is-snapping 的 560ms 里没有过渡 = 硬跳出来。
   （用户已经抓过一次「有的地方消失/出现是硬切的」这类毛病，别再犯。） */
.slider.is-snapping .slider-knob {
  transition: transform 460ms var(--ms-spring),
              box-shadow 520ms var(--ms-settle);
}

/* ---------- 模型列表视图（他那一份） ---------- */
.mv-head {
  display: flex; align-items: center;
  gap: clamp(4px, 1.6vw, 8px);
  margin-bottom: clamp(2px, 1.4vw, 6px);
}
.back-btn {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  padding: 0;
  border: 0; border-radius: 50%;
  background: transparent;
  color: var(--adm-label);
  cursor: pointer;
  transition: background-color 160ms var(--adm-ease-out);
}
.back-btn:active { background: var(--adm-fill-2); transition: background-color 110ms ease; }
.back-btn svg { width: 16px; height: 16px; }

.mv-head h2 {
  margin: 0;
  font-size: 15px;                        /* 原来是 headline（17px）—— 降一档 */
  font-weight: 600;
  letter-spacing: var(--adm-ls-body);
  color: var(--adm-label);
}

.model-list {
  list-style: none;
  margin: 0; padding: 0;
  display: flex; flex-direction: column;
  max-height: min(46dvh, 300px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: var(--adm-label-4) transparent;
  padding-bottom: 6px;                 /* 最后一行留点余量，别贴边 */
  /* 滚动渐隐：默认【两端都不渐隐】（--fade-* 都是 0）。
     只有列表真的溢出了（JS 加 .is-scroll）才渐隐，而且滚到顶/底那一端立刻归零 ——
     用户 2026-09-15：「选择模型的那一个地方，感觉在选择模型的下面，它会有挡住一些」，
     就是常驻渐隐把最后一行糊掉了。mask 只挂在【会滚动的列表】上，绝不挂浮层/输入框。 */
  --fade-t: 0px;
  --fade-b: 0px;
  -webkit-mask-image: linear-gradient(to bottom,
      transparent 0, #000 var(--fade-t), #000 calc(100% - var(--fade-b)), transparent 100%);
  mask-image: linear-gradient(to bottom,
      transparent 0, #000 var(--fade-t), #000 calc(100% - var(--fade-b)), transparent 100%);
}
.model-list.is-scroll { --fade-t: 12px; --fade-b: 12px; }
.model-list.is-scroll.at-top { --fade-t: 0px; }      /* 到顶了：上面不渐隐 */
.model-list.is-scroll.at-bottom { --fade-b: 0px; }   /* 到底了：下面不渐隐（别再糊住最后一行） */
.model-list::-webkit-scrollbar { width: 4px; }
.model-list::-webkit-scrollbar-thumb { background: var(--adm-label-4); border-radius: 2px; }
.model-list li { margin: 0; }

/* ---------- 模型行：两行 ----------
   用户口径：「上面稍大一点，用加粗字体显示模型名称，下面小字灰色的显示描述就行」
   取值跟线上那个模型列表完全一致：
     名字 = --adm-t-callout (16px) / 600 ；
     描述 = --adm-t-footnote (13px) / --adm-label-2 。
   模型数据用的是线上库里的真模型（名字 + description），不是编的。 */
.model-row {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  padding: 9px clamp(8px, 2.6vw, 11px);
  min-height: 50px;                       /* 56 → 50：行别再那么敦实 */
  border: 0;
  border-radius: var(--adm-r-md);
  background: transparent;
  color: var(--adm-label);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color 150ms var(--adm-ease-out);
}
.model-row:active { background: var(--adm-fill-3); }
/* 选中态要比按下态【重】 */
.model-row.is-selected { background: var(--adm-fill-2); }

.model-row-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.model-row-name {
  font-size: 15px;                      /* 原来是 callout（16px）；仍然「比描述大、加粗」 */
  font-weight: 600;                     /* 加粗（这条是用户当初点名要的，别丢） */
  letter-spacing: -0.01rem;
  color: var(--adm-label);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.model-row-desc {
  font-size: 12.5px;                    /* 原来是 footnote（13px） */
  line-height: 1.4;
  color: var(--adm-label-2);            /* 灰色（跟随主题的次级标签色） */
  overflow-wrap: anywhere;
}

.model-row .check {
  flex: 0 0 auto;
  width: 16px; height: 16px;
  color: var(--cx-b2);
  opacity: 0;
  transform: scale(.55);
  /* 勾是【选中的唯一反馈】（选完不再自动关浮层了，见脚本里的说明），
     所以给它一个带回弹的「啵」：从小缩着弹到过头一点点再落回 1。 */
  transition: opacity 200ms var(--ms-settle),
              transform 460ms var(--ms-spring);
}
.model-row.is-selected .check { opacity: 1; transform: scale(1); }

/* ============ 拖动中：把重的东西临时关掉 ============
   用户 2026-09-15：「然后滑动一点都不流畅，会一卡一卡的」。
   卡的不是 JS —— 是每一帧都在重算两样很贵的东西：
     ① .panel 的 backdrop-filter: blur(40px)：面板里任何一个像素变了，
        这一层模糊就得重算一遍（一帧一次 40px 的模糊，手机扛不住）；
     ② 极高那两层呼吸（全宽渐变互相交叉淡入淡出）同时在跑。
   所以手指按下去的这段时间（html.is-dragging）：
     · 面板的模糊摘掉（真正的性能大头）；
     · 两层呼吸【暂停】而不是关掉（paused 保留当前这一帧的颜色，松手接着跑，不会跳色）。
   松手立刻恢复。

   ⚠️⚠️ 2026-09-16 第七轮修（用户原话：「滑动推理深度那个滑块时候，那个拖住的卡片
   会变的透明一下，把这个修复，改成不透明」）：
   上一条注释里那句「背景本来就是 86% 不透明的白，视觉上几乎看不出区别」是**错的**。
   `--ms-panel` 是 rgba（深色 0.82 / 浅色 0.86），它看上去「不透明」**全靠那层模糊撑着**：
   把 backdrop-filter 摘掉的那一刻，底下的文字/图形会**清晰地透出来**，
   面板瞬间变成半透明的玻璃片 —— 就是用户看到的那「透明一下」。
   按下去的一瞬间发生、松手一瞬间恢复，所以是「闪一下」。

   正确做法：摘掉模糊的同时，把面板换成**真正不透明的底色**。
   底下那行 `linear-gradient(var(--ms-panel), var(--ms-panel)), var(--adm-bg)`
   就是「把半透明面板色压在页面底色上」的一次合成 —— 由浏览器现算，
   和上面 rgba 是同一个来源，以后改 token 不用回来改这里。
   它是**完全不透明**的，所以浏览器根本不需要为它分配 backdrop 图层，
   模糊这块钱照样省掉，外观也不会变透。 */
html.is-dragging .panel {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: linear-gradient(var(--ms-panel), var(--ms-panel)), var(--adm-bg);
}
html.is-dragging .slider-liquid::before,
html.is-dragging .slider-liquid::after { animation-play-state: paused; }
/* 刻度点的颜色过渡也停掉：4 颗 5px 的小点颜色渐变 460ms，
   期间每一帧都要重绘它们所在的层 —— 拖动时这点细节不值得，松手照旧会过渡。 */
html.is-dragging .slider-dot { transition: none; }

/* ============ 无障碍 ============
   减的是「动」，不是「信息」：颜色 / 透明度照旧留着，只把【位移、缩放、弹性】拿掉。
   （上一版这里是 transition-duration: 1ms !important 一刀切，等于把淡入淡出也一起杀了，
   所以系统一开「减弱动态效果」就什么都看不见。） */
@media (prefers-reduced-motion: reduce) {
  .panel, .panel.is-open { transform: none; }
  .panel        { transition: opacity 150ms linear; }
  .panel.is-open { transition: opacity 150ms linear; }

  .panel-inner,
  .panel.is-spring .panel-inner,
  .panel.is-swap   .panel-inner { transition: height 1ms linear; }

  .view, .view.is-active { transform: none; }
  .view          { transition: opacity 130ms linear; }
  .view.is-active { transition: opacity 170ms linear; }

  /* 逐行冒出来是纯装饰，直接不演 */
  .model-list.is-enter .model-row { animation: none; }

  .slider.is-snapping .slider-fill,
  .slider.is-snapping .slider-knob { transition: none; }

  /* 慢速 / 高速：留着「亮起来」这个状态变化，去掉闪电那下和电波那圈 */
  .speed-btn, .speed-btn::before {
    transition: background-color 150ms linear, color 150ms linear, opacity 150ms linear;
  }
  .speed-btn:active { transform: none; }
  .speed-btn[aria-pressed="true"] .speed-bolt { animation: none; }
  .speed-ring, .speed-ring-core { animation: none !important; }
  .speed-ring-core { opacity: 0 !important; }
  .tip { transition: height 1ms linear, visibility 0s; }
  .tip-inner, .tip.is-on .tip-inner { transform: none; transition: opacity 200ms linear; }
  /* 提示框里的错开冒出来、徽标的「啵」、卡片的收缩、文字的滚动 —— 全是纯装饰，直接不演 */
  .tip.is-on .tip-badge,
  .tip.is-on .tip-title,
  .tip.is-on .tip-line,
  .tip.is-on .tip-close { animation: none; }
  .tip-card { transition: opacity 200ms linear; }
  .tip-card.is-out { transform: none; }
  .speed-label > span { transition: opacity 200ms linear; }
  .speed-label .sl-on { transform: none; }
  .speed-label .sl-off { transform: none; }

  /* 极高的底色照旧显示（底层那层永远铺满），只是三层不再互相呼吸 ——
     减的是动，不是信息：「现在是最猛的一档」这件事一眼还是看得出来。 */
  .slider-liquid,
  .slider-liquid::before,
  .slider-liquid::after { animation: none !important; }
  /* 每切一档的那点小动作：纯装饰，去掉缩放，但【换字】的淡入淡出留着 ——
     那是「档位真的换了」的反馈，减动不等于把信息也减掉。 */
  .slider-dot.is-hit { animation: none; }
  .effort-value.is-wo[data-dir] .ev-label { animation: msWordFadeOut 120ms linear both; }
  .effort-value.is-wi[data-dir] .ev-label { animation: msWordFadeIn 180ms linear both; }
  /* ⚠️ 拖动那档和警告那档的规则【比上面两条多一个类】，不一起写就会盖掉上面的减动 ——
     减动模式下它们同样只留淡入淡出（警告的琥珀色留着：那是信息，不是动效）。 */
  .effort-value.is-quick.is-wi[data-dir] .ev-label,
  .effort-value.is-warn.is-wi .ev-label { animation: msWordFadeIn 180ms linear both; }
  /* 警告那句的颜色漂移【留着】：那是信息（这一档更贵），不是位移。减动 = 减位移，不是减信息。 */
  .effort-value.is-warn .ev-main { animation: msWarnHue 4.3s var(--ms-in-out) infinite; }
  .effort-value.is-warn .ev-label { animation: none; }
}
/* 切主题的过渡由线上外壳自己管（chat.css / admin.css 那套）；
   这里只保证「本组件在 reduced-motion 下不跟着硬跳」——见上面各条 transition 的写法。 */
@media (prefers-reduced-transparency: reduce) {
  .panel {
    background: var(--adm-surface);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
@media (prefers-contrast: more) {
  .panel, .input-box, .effort-btn, .slider, .speed-btn, .tip-card { box-shadow: 0 0 0 1px currentColor; }
}

/* ============================================================
   发送键 / 停止键：尺寸对齐 + 谁在台前
   发送键是 40px（他验收过的那一版），而线上旧的 .cx-stop 是 44px ——
   两颗是【互相替换】着显示的（发消息时收起发送、亮出停止），差 4px 会让整行高度
   变一下，输入区看着「轻轻跳一下」。这里把它对齐成同一档。

   ★★ 显隐状态【归 CSS 管】，只由 .composer-row 上的 .is-generating 一个类决定。
   ⛔ 绝不要在 JS 里写 style.display —— 那是行内样式，会盖掉下面的 display:grid，
      place-items:center 当场失效，箭头就贴到左上角去了。
      线上真实事故（2026-09-16，用户：「发送一条信息之后，它就不居中了，它就偏左还偏下了」）：
      上一版这两颗键是 inline-flex + align/justify:center，所以 JS 里那句
      style.display='flex' 恰好和 CSS 一致、看不出问题；这次换新输入框时
      .ms-send 改成了 display:grid + place-items:center，行内 flex 一来就把它顶掉了
      —— 于是「第一次发消息之前是正的，发完就歪了」，而且再也回不去（行内样式不会自己消失）。
      自检里那条「JS 不许往这两颗键上写 display」就是钉这件事的。 */
.ms-send, .cx-stop {
  width: 40px; height: 40px;
  display: grid; place-items: center;
}
.cx-stop { display: none; }                                  /* 空闲：只有发送键在 */
.composer-row.is-generating .cx-stop { display: grid; }       /* 生成中：亮出停止键 */
.composer-row.is-generating .ms-send { display: none; }       /*          收起发送键 */
