/* app.css · 108619 第②步（9.27）从 index.html 原样搬出，一行未改。改了它就要 bump index.html 里的 ?v= 和 sw.js 的 CACHE。 */
  /* Special Elite — 磨损打字机,逾水 8.27 选定。OFL,自托管。
     unicode-range 把它锁在纯拉丁:·、…、。、？ 这些中西都有的标点让给苹方,
     否则每换一次西文字体,中文那一行的标点就跟着换一次形状。 */
  @font-face{
    font-family:'SpecialElite';
    src:url('./fonts/specialelite-400.woff2') format('woff2');
    font-weight:400; font-style:normal; font-display:swap;
    ascent-override:115.1%; descent-override:28.6%;
    unicode-range:U+0020-00A0,U+00A2-00B6,U+00B8-024F,U+0259,U+2010-2015,U+2018-201D,U+2020-2022,U+2030,U+2032-2033,U+2039-203A,U+2044,U+20AC,U+2122,U+2212;
  }
  :root{
    --screen-height: 100vh;
    --keyboard-offset: 0px;
    --ios-pwa-bottom-bleed: 0px;
    --ref-width: 941px;
    --ref-height: 1672px;
    /* 'NotoSerifSC' 必须排在通用关键字 monospace 前面。通用关键字在 WebKit 里
       一定能解析成某个字体,回退链走到它就停了,后面的 --font-cn 永远轮不到——
       所以中文会卡在等宽的系统兜底上,思源宋一块切片都不会被请求。 */
    --font-en: 'SpecialElite', 'NotoSerifSC', ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
    --font-mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;  /* 需要等宽对齐的地方留一条后路 */
    /* 中文:思源宋体 500,自托管切片版(见 fonts/nsc.css)。苹方留在后面兜底——
       切片没到、或出现连思源也没有的字时,不至于掉到系统默认。 */
    /* SpecialElite 排在最前:它有 unicode-range 锁死在拉丁,所以只接英文,汉字接不到,
       会往后落到思源宋。不这样写的话,思源宋自己也带拉丁字形,凡是 --font-cn 打头的
       地方(atrium 日记等)英文就会被宋体接走,打字机换不进去。 */
    --font-cn: 'SpecialElite', 'NotoSerifSC', -apple-system, "PingFang SC", "Noto Sans SC", "Hiragino Sans GB", system-ui, sans-serif;
    --font-cn-sys: -apple-system, "PingFang SC", "Noto Sans SC", "Hiragino Sans GB", system-ui, sans-serif;
    --wall-default: none;
    --menu-wall-default: none;
    /* ── CONCRETE DAY · 裸建筑(default) ── */
    --bg: #e8e4df;
    --header-bg: #e8e4df;
    --hairline: #c4bfb8;
    --text: #2c2a26;
    --text-soft: #7a756d;
    --text-faint: #b0aaa2;
    --bubble-ai-bg: #f2efeb;
    --bubble-ai-fg: #2c2a26;
    --bubble-ai-line: transparent;
    --bubble-human-bg: #d4d0ca;
    --bubble-human-fg: #2c2a26;
    --bubble-human-line: transparent;
    --accent: #4a7c9b;
    --send-bg: #4a7c9b;
    --tick: #b0aaa2;
    --accent-fg: #ffffff;
    /* 毛玻璃:雾白玻璃体 + 极细高光描边 + 很轻的悬浮阴影。
       不要灰色半透明遮罩——那是塑料。玻璃靠的是背后被模糊 + 边缘厚度 + 内部折射。 */
    --glass-a: rgba(255,255,255,.50);   /* 玻璃体上缘 */
    --glass-b: rgba(255,255,255,.26);   /* 玻璃体下缘,薄一点让背景透出来 */
    --glass-edge: rgba(255,255,255,.78);/* 边缘高光:玻璃的"厚度" */
    --glass-hair: rgba(116,126,142,.20);/* 外圈发丝线,浅底上把形状勾出来 */
    --glass-sheen: rgba(255,255,255,.58);/* 表面斜向反光 */
    --glass-sh1: rgba(30,36,48,.085);
    --glass-sh2: rgba(30,36,48,.115);
    --glass-bloom: .72;                 /* 内部折射光斑的浓度 */
    /* 9.7：状态药丸不再用 backdrop-filter（WebKit 不把它的取景矩形裁进 border-radius，
       圆角外会探出一个方角 —— 她拍到了特写）。玻璃改成"真半透明底 + 一层淡纱"，
       这一支就是补回 brightness(1.05)/saturate 那点亮度的纱。深浅各一支。 */
    --glass-veil: rgba(255,255,255,.30);
    --think-flourish: rgba(74, 124, 155, 0.40);
    /* thinking 记号里那根金线。色源是走廊 pool 站的环境色(lightRGB 215,188,115),
       不是新调的——记号和走廊说同一种金。蓝线直接用 --accent,膜用 --hairline。 */
    --think-mark-gold: #d7bc73;
    --think-label: #7a756d;
    --think-body: #5a564f;
    --composer-bg: #e8e4df;
    --field-bg: #f2efeb;
    --field-bg-focus: #f7f5f2;
    --scroll-thumb: rgba(90,84,76,.3);
    --scroll-thumb-hover: rgba(90,84,76,.45);
    --send-idle-bg: #e2ddd6;
    --send-idle-fg: #aca69e;
    --chip-bg: rgba(255,255,255,.55);
    --chip-line: rgba(255,255,255,.75);
    --chip-shadow: rgba(60,50,40,.07);
    --field-line: #c4bfb8;
    --shadow: none;
    --soft-shadow: none;
    --scrim: none;
    --header-h: 56px;
    --side-pad: clamp(16px, 4vw, 40px);
    /* 气泡的左右内边距和附件图片的宽度必须一起看：附件气泡的上限就是
       「图片 + 两份 padding」。以前这两条各写各的（图片 64vw / 气泡 61vw），
       64vw 恒大于 61vw − 26px，图片就从气泡右边冒出去（8.31）。
       提成 token 之后，改 padding 或改图片宽度，另一头自己跟上。 */
    --bubble-pad-x: clamp(13px, 1.65vw, 18px);
    --att-w: min(64vw, 260px);          /* 图片 */
    --att-file-w: 250px;                /* 文件卡 */
    --att-max: max(var(--att-w), var(--att-file-w));   /* 附件里最宽的那个 */
    --bubble-radius: 0px;
    --composer-h: clamp(44px, 6vw, 72px);
    --motion-fast: 150ms;
    --motion-normal: 260ms;
    --ease-soft: cubic-bezier(0.25, 0, 0.1, 1);
    --composer-zone: calc(var(--composer-h) + 16px + env(safe-area-inset-bottom) + var(--keyboard-offset));
    --scroll-gap: 34px;                /* 消息区和输入框之间固定留出的距离 */
    --scroll-bottom: calc(var(--composer-zone) + var(--scroll-gap));
    --edge-fade-top: clamp(24px, 5vw, 52px);
    --edge-fade-tail: clamp(16px, 3vw, 30px);
    --panel-bg: #f2efeb;
    --card-bg: #f2efeb;
    --card-line: #c4bfb8;
    --card-shadow: none;
    --seg-track: #d4d0ca;
    --seg-thumb: #f2efeb;
    --seg-thumb-shadow: none;
    --seg-line: #c4bfb8;
    --row-press: rgba(196, 191, 184, .18);
    --slider-track: #c4bfb8;
    --slider-thumb: #4a7c9b;
    --info-ring: #c4bfb8;
    --status-dot: #5b7a6a;
    /* voice call */
    --call-glass-top: rgba(242, 239, 235, .72);
    --call-glass-bot: rgba(232, 228, 223, .50);
    --call-glass-edge: #c4bfb8;
    --call-glass-hi: rgba(242, 239, 235, .60);
    --call-glass-glow: rgba(74, 124, 155, .12);
    --call-glass-shadow: none;
    --call-scrim: rgba(44, 42, 38, .04);
    --call-hangup-bg: #a85d3a;
    --call-hangup-shadow: rgba(168, 93, 58, .30);
  }
  /* ── CONCRETE AT NIGHT · 夜间混凝土 ── */
  :root[data-theme="dark"]{
      --bg: #1a1917;
      --header-bg: #1a1917;
      --hairline: #3a3733;
      --text: #d4d0ca;
      --text-soft: #8a847c;
      --text-faint: #4a4640;
      --bubble-ai-bg: #242220;
      --bubble-ai-fg: #d4d0ca;
      --bubble-human-bg: #2e2b28;
      --bubble-human-fg: #d4d0ca;
      --accent: #6a9cbb;
      --send-bg: #6a9cbb;
      --tick: #4a4640;
      --accent-fg: #0a0908;
      --glass-a: rgba(62,59,55,.40);
      --glass-b: rgba(40,38,35,.22);
      --glass-edge: rgba(255,255,255,.17);
      --glass-hair: rgba(0,0,0,.38);
      --glass-sheen: rgba(255,255,255,.13);
      --glass-sh1: rgba(0,0,0,.38);
      --glass-sh2: rgba(0,0,0,.46);
      --glass-bloom: .52;
      --glass-veil: rgba(255,255,255,.055);
      --room-rgb: 100,158,210;
      --think-flourish: rgba(106, 156, 187, 0.36);
      --think-mark-gold: #eed28c;                /* pool 站的 darkRGB 238,210,140 */
      --think-label: #8a847c;
      --think-body: #a8a29a;
      --composer-bg: #1a1917;
      --field-bg: #242220;
      --field-bg-focus: #2b2825;
      --scroll-thumb: rgba(220,214,206,.26);
      --scroll-thumb-hover: rgba(220,214,206,.4);
      --send-idle-bg: #34312d;
      --send-idle-fg: #6d6862;
      --chip-bg: rgba(255,255,255,.07);
      --chip-line: rgba(255,255,255,.12);
      --chip-shadow: rgba(0,0,0,.28);
      --field-line: #3a3733;
      --panel-bg: #242220;
      --card-bg: #242220;
      --card-line: #3a3733;
      --seg-track: #2e2b28;
      --seg-thumb: #3a3733;
      --seg-line: #3a3733;
      --slider-track: #3a3733;
      --slider-thumb: #6a9cbb;
      --info-ring: #3a3733;
      --status-dot: #7a9a8a;
      --call-glass-top: rgba(36, 34, 32, .72);
      --call-glass-bot: rgba(26, 25, 23, .50);
      --call-glass-edge: #3a3733;
      --call-glass-hi: rgba(36, 34, 32, .60);
      --call-hangup-bg: #c87d5a;
      --call-hangup-shadow: rgba(200, 125, 90, .30);
      --row-press: rgba(58, 55, 51, .30);
  }

  *{ box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
  html, body{ margin:0; padding:0; height:100%; overflow:hidden; overscroll-behavior:none; }
  body{ position: fixed; inset: 0; width: 100%;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(14px, 1.45vw, 16px);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    overscroll-behavior: none;
  }

  .app{
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1;
    width: min(100vw, var(--ref-width));
    margin: 0 auto;
    overflow: hidden;
  }
  .wall{
    position:fixed;
    inset:0;
    width:100vw;
    height:var(--screen-height);
    z-index:0;
    background-color: var(--bg);
    background-image: var(--wall-default);
    background-size: cover; background-position: center;
    transform: translateZ(0);
    -webkit-transform: translateZ(0);
  }
  .wall::after{
    content:""; position:absolute; inset:0; background: var(--scrim); pointer-events:none;
  }
  .topbar, .scroll, .composer{ position: relative; z-index: 1; }

  /* ── Header (floating frosted pill) ── */
  .topbar{
    position: sticky; top: 0; z-index: 10;
    display: flex; align-items: center; justify-content: flex-start;
    gap: 8px;
    height: calc(var(--header-h) + env(safe-area-inset-top));
    padding: calc(env(safe-area-inset-top) + 8px) var(--side-pad) 10px;
    border-bottom: 1px solid var(--hairline);
    pointer-events: none;
  }
  .topbar > *{ pointer-events: auto; }
  .peerpill{
    display:flex; flex-direction:row; align-items:baseline; gap: 6px; line-height:1.15;
    background: transparent;
    border: none;
    border-radius: 0;
    padding: 0;
    margin-left: 48px;
    /* 能被挤扁。以前 .header-actions 是 position:absolute、不占位,peerpill
       想多宽就多宽,状态文字一变长("///typing"+三个点)就直接钻到搜索图标底下
       重叠(逾水 8.28 截图)。现在图标组回到文档流里(margin-left:auto 顶右边),
       这里加 min-width:0 + overflow,挤不下就让状态文字打省略号,不许压上去。 */
    min-width: 0;
    flex: 0 1 auto;
    overflow: hidden;
  }
  .peerpill .name{
    /* 挤不下的时候截名字,不截状态——名字是常驻的、看一眼就知道，
       状态("///typing"+三个点)是瞬时信息,截掉就白显示了。
       以前是反过来的,导致 typing 的三个点被 overflow 剪没。 */
    flex: 0 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-family: var(--font-en);
    font-size: clamp(14px, 2vw, 17px);
    font-weight: 400;
    color: var(--text);
    letter-spacing: 0.5px;
  }
  .peerpill .status{
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(11px, 1.4vw, 13px);
    color: var(--text-faint);
    letter-spacing: 0;
    flex: none;
    white-space: nowrap;
  }
  .ai-dot{
    display: inline-block;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--status-dot);
    opacity: 0;
    /* 不要再单独留 margin——.peerpill 是 flex,gap 已经管了间距,
       这个 margin 是纯多出来的 5px,正是它把名字挤到打省略号。 */
    vertical-align: middle;
    transition: opacity .6s ease;
  }
  .ai-dot.on{ opacity: 1; }
  .app[data-conn="reconnecting"] .peerpill .status{ color:#9a8061; }
  .peerpill .status.typing{
    color: var(--accent);
    font-weight: 400;
    display: inline-flex; align-items: center;
  }
  .typing-dots{ display:inline-flex; align-items:center; gap:3px; margin-left:6px; }
  .typing-dots i{ width:4px; height:4px; border-radius:50%; background:currentColor; animation: typingDot 1.25s ease-in-out infinite; }
  .typing-dots i:nth-child(2){ animation-delay:.16s; }
  .typing-dots i:nth-child(3){ animation-delay:.32s; }
  @keyframes typingDot{ 0%,70%,100%{ transform:translateY(0); opacity:.4; } 35%{ transform:translateY(-3px); opacity:1; } }
  .backbtn{
    position:absolute; left: calc(var(--side-pad) + 4px);
    top: 50%; transform: translateY(-50%);
    width:36px; height:36px; border-radius:50%; padding:0; flex:none; cursor:pointer;
    border: none; background: transparent;
    color: var(--accent); display:grid; place-items:center;
  }
  .backbtn:active{ transform: translateY(-50%) scale(.9); }
  .backbtn svg{ width:20px; height:20px; display:block; margin-left:-2px; }
  .header-actions{
    /* 从 position:absolute 改回文档流 + margin-left:auto 顶到右边。
       绝对定位时它不占位,标题栏不知道右边被占了多少,才会重叠。 */
    margin-left: auto;
    margin-right: -2px;
    flex: none;
    display:flex;
    align-items:center;
    justify-content:flex-end;
    gap: clamp(10px, 2.4vw, 26px);
    height:36px;
  }
  .topbtn{
    /* 图标用主题蓝,不用正文墨色——顶栏是配件,不该跟正文一样重。8.27
       .active 的蓝没有跟它冲突:终端面板是 inset:0 的全屏覆盖,开着的时候顶栏根本看不见;
       callBtn 默认带 hidden。两个选中态在实际使用里都不出现。 */
    width:32px;
    height:32px;
    border:0;
    border-radius:50%;
    padding:0;
    background:transparent;
    color: var(--accent);
    display:grid;
    place-items:center;
    cursor:pointer;
    transition: transform .15s ease;
  }
  .topbtn:active{ transform: scale(.9); }
  .topbtn svg{ width:20px; height:20px; display:block; }
  .topbtn.more svg{ width:18px; height:18px; }
  .topbtn.terminal.active{ color:var(--accent); }
  /* ── Corridor Axis (Layer 2) ── */
  .corridor-axis{
    position:relative; width:100%;
    flex-shrink:0; z-index:5;
    padding: 0 var(--side-pad);
    pointer-events: auto;
    touch-action: none;
  }
  .corridor-track{
    position:relative;
    display:flex; justify-content:space-between;
    align-items:flex-end;
    width:100%;
    padding-bottom: 5px;
  }
  .corridor-stream{
    position:absolute;
    bottom:0; left:0;
    height:2px;
    width:0%;
    background: linear-gradient(to right, var(--accent), color-mix(in srgb, var(--accent) 20%, transparent));
    transition: width 1.2s cubic-bezier(0.25, 0, 0.1, 1);
    pointer-events:none;
  }
  .corridor-node{
    font-size:10px; letter-spacing:.08em;
    text-transform:uppercase;
    color: var(--text-faint);
    padding: 4px 0 0;
    white-space:nowrap;
    cursor:pointer;
    transition: color .4s cubic-bezier(0.25, 0, 0.1, 1);
    user-select:none;
  }
  .corridor-node:hover{ color: var(--text-soft); }
  .corridor-node.active{ color: var(--accent); }
  /* ── Corridor Light Patch (Layer 1) ── */
  .corridor-light{
    position:fixed; z-index:0;
    pointer-events:none;
    top:0; left:0;
    width:100vw; height:100vh;
    filter: blur(30px);
    opacity:0;
    /* 9.4 省电，她定 C：**动画整个撤掉**，只留换房间时的渐变。
       原来是 `lightSway 6s infinite` 在两层 100vw×100vh + blur(30px) 上做
       平移＋缩放 —— 缩放会让 WebKit 每帧把那张全屏模糊重做一遍，是"屏幕活跃"
       里最贵的一类。撤的理由不是省电压过观感，是**观众为零**：
       她被点名去找都没找到（原话「原来有这个？我从来没发现过！」）。
       @keyframes lightSway 一并删了，不留死代码。 */
    transition:
      background 2s cubic-bezier(0.25, 0, 0.1, 1),
      opacity 1.6s cubic-bezier(0.25, 0, 0.1, 1);
  }
  @media(max-width:520px){
    .corridor-node{ font-size:8px; padding:5px 0; }
  }

  /* ── Atrium layer (chat home state) ── */
  .atrium-layer{
    position:relative; z-index:2;
    flex:1; min-height:0;
    overflow-y:auto; overflow-x:hidden;
    scrollbar-width:none; -ms-overflow-style:none;
    -webkit-overflow-scrolling:touch;
    padding: 40px var(--side-pad) calc(var(--composer-zone) + 20px);
    opacity:1;
    transition: opacity 0.4s ease;
    pointer-events:auto;
  }
  .atrium-layer::-webkit-scrollbar{ display:none; }
  .atrium-layer{
    -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 calc(100% - var(--composer-zone) - 30px), transparent calc(100% - var(--composer-zone)));
    mask-image: linear-gradient(to bottom, #000 0, #000 calc(100% - var(--composer-zone) - 30px), transparent calc(100% - var(--composer-zone)));
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  }
  .atrium-layer.hidden{ opacity:0; pointer-events:none; display:none; }
  /* 下拉刷新。指示点绝对定位在内容上方 —— 只有 scrollTop 为 0 时才拉得动，
     所以它跟着内容一起被推下来的那一段里，页面本身没有在滚。 */
  .atrium-ptr{
    position:absolute; left:0; right:0; top:0;
    display:flex; align-items:center; justify-content:center;
    height:34px; margin-top:-34px;
    opacity:0; pointer-events:none;
  }
  .atrium-ptr-dot{
    width:7px; height:7px; border-radius:50%;
    background:var(--text-faint);
    transition:transform .2s ease, background .2s ease;
  }
  .atrium-ptr.ready .atrium-ptr-dot{ transform:scale(1.7); background:var(--accent); }
  .atrium-ptr.busy .atrium-ptr-dot{ animation:atriumPtrPulse .9s ease-in-out infinite; background:var(--accent); }
  @keyframes atriumPtrPulse{ 0%,100%{ transform:scale(1); opacity:.45 } 50%{ transform:scale(1.7); opacity:1 } }
  .atrium-inner.snapback, .atrium-ptr.snapback{ transition:transform .26s cubic-bezier(.2,.8,.3,1), opacity .26s ease; }
  @media (prefers-reduced-motion: reduce){
    .atrium-ptr-dot{ transition:none }
    .atrium-ptr.busy .atrium-ptr-dot{ animation:none; opacity:1 }
  }
  .atrium-inner{ max-width:640px; margin:0 auto; overflow:hidden; padding-bottom:calc(var(--composer-zone) + 40px); }

  /* notes — written on the board */
  .atrium-note{
    padding:10px 0;
    font-size:14px; color:var(--text);
    line-height:1.9;
    padding-left:2px;
    font-family: var(--font-cn), var(--font-en);
    letter-spacing:0.02em;
  }
  .atrium-note + .atrium-note{
    border-top:1px solid rgba(196,191,184,0.4);
  }
  .atrium-note-pin{
    display:inline-block; width:4px; height:4px;
    background:var(--accent); margin-right:6px;
    vertical-align:middle; position:relative; top:-1px;
  }
  .atrium-note-ts{
    font-family:var(--font-en);
    font-size:10px; color:var(--text-faint);
    margin-top:4px; letter-spacing:0.03em;
  }
  .atrium-notes{ margin-bottom:24px; }
  /* 9.2 全貌板入口。长相照 .atrium-note 那套（同字号、同行高、同时间戳小字），
     只是右边多一个箭头。**单独一个块**，上下都是 margin，不动 atrium 其他东西的顺序。 */
  /* 9.2：入口行撤掉，换成把全貌直接嵌进来。
     「等你」段沿用整页那边的通栏卡（有等待才出现），
     「最近动过」压成一行一房间的紧凑行 —— 门厅要的是一眼扫过，不是六张卡占三屏。
     取数跟 /chat/atlas.html 共用 atlas-data.js，判据只有一份。 */
  .atrium-atlas{ margin-bottom:24px; }
  .atrium-atlas-sec{
    font-family:var(--font-en); font-size:10px; letter-spacing:0.08em;
    color:var(--text-faint); text-transform:uppercase; margin-bottom:8px;
  }
  .atrium-atlas-sec.later{ margin-top:14px; }
  .atrium-atlas-card{
    display:block; text-decoration:none; color:var(--text);
    background:var(--card-bg); border:1px solid var(--card-line);
    border-left:3px solid var(--accent);
    padding:10px 11px; margin-bottom:8px;
  }
  .atrium-atlas-card:active{ opacity:0.6; }
  .atrium-atlas-room{
    font-family:var(--font-en); font-size:10px; letter-spacing:0.16em; text-transform:uppercase;
  }
  .atrium-atlas-say{ font-size:12px; line-height:1.7; color:var(--text-soft); margin-top:3px; }
  .atrium-atlas-foot{
    display:flex; align-items:center; justify-content:space-between; gap:6px; margin-top:5px;
  }
  .atrium-atlas-when{
    font-family:var(--font-en); font-size:10px; color:var(--text-faint); white-space:nowrap;
  }
  .atrium-atlas-badge{
    font-size:9px; letter-spacing:0.12em; color:var(--accent);
    border:1px solid var(--accent); padding:1px 5px; white-space:nowrap;
  }
  /* 紧凑行：房间名 / 一句话 / 相对时间，一行一个 */
  .atrium-atlas-row{
    display:flex; align-items:baseline; gap:9px; text-decoration:none; color:var(--text);
    padding:7px 0 7px 2px;
  }
  .atrium-atlas-row + .atrium-atlas-row{ border-top:1px solid rgba(196,191,184,0.4); }
  .atrium-atlas-row:active{ opacity:0.6; }
  .atrium-atlas-row .atrium-atlas-room{ flex:0 0 58px; }
  /* 9.28：原来单行省略号，LIBRARY 那行「N 条批注 · 最近在《书名》」一长，最先被吃掉的就是书名。
     改成最多两行、第二行末尾才省略 —— 关键词在前一行半里就露出来了。 */
  .atrium-atlas-row .r-say{
    flex:1; min-width:0; font-size:12px; color:var(--text-soft); line-height:1.5;
    overflow:hidden; display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2;
  }
  .atrium-atlas-more{
    display:block; text-decoration:none; margin-top:12px; padding-left:2px;
    font-family:var(--font-en); font-size:11px; letter-spacing:0.08em; color:var(--text-faint);
  }
  .atrium-atlas-more:active{ color:var(--text); }
  /* tasks 折叠：未完成的（active/submitted）全部常驻，闭环的进「历史 N 条」 */
  .atrium-tasks-more{
    font-family:var(--font-en); font-size:10px; letter-spacing:0.08em; color:var(--text-faint);
    padding:10px 0 2px 2px; cursor:pointer;
  }
  .atrium-tasks-more:active{ color:var(--text); }
  .atrium-tasks-history{ display:none; }
  .atrium-tasks-history.open{ display:block; }
  .atrium-note.seen-flash{
    animation: noteSeen 1.2s ease;
  }
  @keyframes noteSeen{
    0%{ background:transparent; }
    20%{ background:rgba(196,191,184,0.25); }
    60%{ background:rgba(196,191,184,0.25); }
    100%{ background:transparent; }
  }
  /* reminders — with checkbox */
  .atrium-reminder{
    padding:8px 0;
    font-size:13px; color:var(--text);
    line-height:1.8;
    display:flex; align-items:flex-start; gap:8px;
    cursor:pointer;
  }
  .atrium-reminder + .atrium-reminder{
    border-top:1px solid rgba(196,191,184,0.2);
  }
  .atrium-reminder.done .atrium-reminder-text{
    text-decoration:line-through; opacity:0.45;
  }
  .atrium-reminder-check{
    width:14px; height:14px; flex-shrink:0;
    border:1.5px solid var(--text-soft); border-radius:2px;
    margin-top:4px; position:relative;
    transition: border-color 0.2s;
  }
  .atrium-reminder.done .atrium-reminder-check{
    border-color:var(--accent);
    background:var(--accent);
  }
  .atrium-reminder.done .atrium-reminder-check::after{
    content:''; position:absolute;
    left:3px; top:0px; width:5px; height:9px;
    border:solid #fff; border-width:0 1.5px 1.5px 0;
    transform:rotate(45deg);
  }
  .atrium-reminder-text{ flex:1; }
  .atrium-reminders{ margin-bottom:24px; }
  .atrium-rem-n{ font-family:var(--font-en); font-size:10px; color:var(--text-faint); }
  /* ②B chips：骨架照 .atrium-atlas-badge 的细边框；选中那枚填底 */
  .atrium-rem-chips{ display:flex; flex-wrap:wrap; gap:6px; margin:2px 0 8px; }
  .atrium-rem-chip{ font-size:12px; color:var(--text-soft); background:transparent;
    border:1px solid var(--card-line); padding:3px 9px; cursor:pointer; font-family:inherit; }
  .atrium-rem-chip.on{ color:var(--text); border-color:var(--accent); background:var(--card-bg); }
  .atrium-rem-chip:active{ opacity:0.6; }
  .atrium-rem-tag{ font-size:11px; color:var(--text-faint); margin-right:6px; }


  /* topic cards — items on the whiteboard */
  .atrium-diary-toggle{
    font-family:var(--font-en); font-size:10px;
    letter-spacing:0.08em; color:var(--text-faint);
    background:none; border:none; cursor:pointer;
    padding:0; margin-top:4px;
  }
  .atrium-diary-toggle:active{ color:var(--accent); }
  .atrium-diary-entries{ margin-top:16px; display:none; }
  .atrium-diary-entries.open{ display:block; }
  .atrium-diary-day{ margin-bottom:16px; }
  .atrium-diary-day-label{
    font-family:var(--font-en); font-size:10px;
    letter-spacing:0.08em; color:var(--text-faint);
    margin-bottom:6px; border-bottom:1px solid rgba(196,191,184,0.3);
    padding-bottom:4px;
  }
  .atrium-diary-entry{ padding:6px 0; }
  .atrium-diary-entry-text{
    font-size:12px; color:var(--text-soft); line-height:1.7;
    font-family: var(--font-cn), var(--font-en);
  }
  .atrium-diary-entry-time{
    font-family:var(--font-en); font-size:10px;
    color:var(--text-faint); margin-top:2px;
  }

  .atrium-tasks{ margin-bottom:24px; }
  .atrium-tasks-label{
    font-family:var(--font-en); font-size:10px; letter-spacing:0.08em;
    color:var(--text-faint); text-transform:uppercase; margin-bottom:8px;
  }
  .atrium-task{
    padding:12px 0; position:relative;
  }
  .atrium-task + .atrium-task{
    border-top:1px solid rgba(196,191,184,0.4);
  }
  .atrium-task-title{
    font-size:13px; color:var(--text); line-height:1.5;
    font-weight:500;
  }
  .atrium-task-desc{
    font-size:12px; color:var(--text-soft); line-height:1.5;
    margin-top:4px;
  }
  .atrium-task-due{
    font-family:var(--font-en); font-size:10px;
    color:var(--text-faint); margin-top:4px;
  }
  .atrium-task-status{
    font-family:var(--font-en); font-size:10px;
    color:var(--accent); margin-top:4px; letter-spacing:0.04em;
  }
  .atrium-task-submit{
    display:inline-block; margin-top:8px; padding:6px 16px;
    font-family:var(--font-en); font-size:11px;
    color:var(--accent); background:transparent;
    border:1px solid var(--accent); cursor:pointer;
    letter-spacing:0.04em; transition:opacity 0.15s;
  }
  .atrium-task-submit:active{ opacity:0.5; }
  .atrium-task-submit:disabled{ opacity:0.3; cursor:default; }
  .atrium-task-outcome{
    font-size:12px; margin-top:6px; padding:8px 0;
    color:var(--text-soft); line-height:1.5;
  }
  .atrium-task-outcome b{
    color:var(--text); font-weight:500;
  }
  .atrium-task.reviewed .atrium-task-title{
    color:var(--text-soft);
  }
  /* ③ 收起态：标题＋一行摘要，右侧一枚会转的 › */
  .atrium-task.collapsible .atrium-task-head{ cursor:pointer; position:relative; padding-right:18px; }
  .atrium-task.collapsible .atrium-task-head::after{ content:"›"; position:absolute; right:2px; top:0;
    font-size:16px; line-height:1.2; color:var(--text-faint); transition:transform .15s; }
  .atrium-task.collapsible.open .atrium-task-head::after{ transform:rotate(90deg); }
  .atrium-task-sum{ font-size:12px; color:var(--text-soft); line-height:1.5; margin-top:4px;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .atrium-task.collapsible .atrium-task-body{ display:none; }
  .atrium-task.collapsible.open .atrium-task-body{ display:block; }
  .atrium-task.collapsible.open .atrium-task-sum{ display:none; }
  .atrium-task-input{
    width:100%; margin-top:8px; padding:8px;
    font-size:12px; font-family:var(--font-en), var(--font-cn);
    color:var(--text); background:var(--field-bg);
    border:1px solid var(--field-line);
    resize:vertical; min-height:36px; max-height:120px;
  }

  .atrium-cards{ margin-bottom:24px; overflow:hidden; }
  .atrium-cards-grid{ overflow:hidden; }
  .atrium-card{
    padding:12px 2px; margin-bottom:0;
    cursor:pointer;
    transition:opacity 0.15s ease;
    overflow:hidden; min-width:0;
  }
  .atrium-card + .atrium-card{
    border-top:1px solid rgba(196,191,184,0.4);
  }
  .atrium-card:active{ opacity:0.6; }
  .atrium-card-hook{
    font-size:13px; color:var(--text);
    line-height:1.7;
    display:-webkit-box;
    -webkit-line-clamp:2;
    -webkit-box-orient:vertical;
    overflow:hidden;
    word-break:break-word;
  }
  .atrium-card-source{
    font-family:var(--font-en);
    font-size:10px; color:var(--text-faint);
    line-height:1.5; margin-top:3px;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    max-width:100%;
  }

  /* detail modal */
  .atrium-modal-overlay{
    position:fixed; inset:0; z-index:200;
    background:rgba(44,42,38,0.25);
    display:flex; align-items:center; justify-content:center;
    padding:24px;
    opacity:0; pointer-events:none;
    visibility:hidden;                                   /* 10.24 同上：透明的整屏遮罩也别留在顶端当取色对象 */
    transition:opacity 0.2s ease, visibility 0s linear .2s;
  }
  .atrium-modal-overlay.open{
    opacity:1; pointer-events:auto;
    visibility:visible; transition:opacity 0.2s ease, visibility 0s;
  }
  .atrium-modal{
    background:var(--bg);
    border:1px solid var(--hairline);
    max-width:480px; width:100%;
    max-height:70vh; overflow-y:auto;
    padding:28px 24px 20px;
    position:relative;
  }
  .atrium-modal-hook{
    font-size:14px; color:var(--text);
    line-height:1.8; margin-bottom:14px;
    word-break:break-word;
  }
  .atrium-modal-title{
    font-family:var(--font-en), var(--font-cn);
    font-size:11px; color:var(--text-soft);
    line-height:1.6; margin-bottom:4px;
  }
  .atrium-modal-domain{
    font-family:var(--font-en);
    font-size:10px; color:var(--text-faint);
    letter-spacing:0.03em;
    margin-bottom:16px;
  }
  .atrium-modal-domain a{
    color:var(--text-faint); text-decoration:none;
  }
  .atrium-modal-actions{
    display:flex; gap:10px;
    padding-top:14px;
    border-top:1px solid rgba(196,191,184,0.4);
  }
  .atrium-modal-btn{
    font-family:var(--font-en);
    font-size:11px; letter-spacing:0.04em;
    padding:7px 16px;
    border:1px solid var(--hairline);
    background:none; color:var(--text-soft); cursor:pointer;
  }
  .atrium-modal-btn:active{ background:rgba(128,128,128,0.1); }
  .atrium-modal-btn-primary{ border-color:var(--accent); color:var(--accent); }
  .atrium-modal-close{
    position:absolute; top:12px; right:14px;
    background:none; border:none; cursor:pointer;
    font-family:var(--font-en);
    font-size:16px; color:var(--text-faint);
    line-height:1; padding:4px;
  }

  @media(min-width:768px){
    .atrium-inner{ max-width:720px; }
    .atrium-cards-grid{ display:grid; grid-template-columns:1fr 1fr; gap:0 20px; }
  }

  .ctx-bar{
    position:relative; width:100%; height:12px;
    background:var(--hairline); overflow:hidden;
    display:flex; align-items:center;
    flex-shrink:0;
    transition: opacity var(--motion-normal) var(--ease-soft);
  }
  .ctx-bar.hidden{ display:none; }
  .ctx-fill{
    position:absolute; left:0; top:0; bottom:0;
    background:var(--accent); opacity:.35;
    transition: width .6s var(--ease-soft), background .4s;
  }
  .ctx-bar.warn .ctx-fill{ background:#b08030; opacity:.5; }
  .ctx-bar.crit .ctx-fill{ background:#a04040; opacity:.55; }
  .ctx-label{
    position:relative; z-index:1;
    width:100%; text-align:center;
    font-family:var(--font-en); font-size:9px; letter-spacing:.5px;
    color:var(--text-soft);
    pointer-events:none; user-select:none;
  }
  .ctx-bar.crit .ctx-label{ color:var(--text); font-weight:500; }
  .ctx-float{
    position:fixed; top: calc(env(safe-area-inset-top) + 8px); left:50%;
    transform: translate(-50%, -60px);
    z-index:400;
    font-family:var(--font-en); font-size:13px; letter-spacing:.3px;
    padding:8px 20px;
    border:1px solid var(--hairline);
    background:var(--bg); color:var(--text);
    opacity:0; pointer-events:none;
    transition: opacity .3s ease, transform .3s ease;
  }
  .ctx-float.show{ opacity:1; transform: translate(-50%, 0); pointer-events:auto; }
  .ctx-float.warn{ border-color:#b08030; color:#b08030; }
  .ctx-float.crit{ border-color:#a04040; color:#a04040; font-weight:500; }
  .session-pop{
    position:absolute;
    right: calc(var(--side-pad) + 34px);
    top: calc(env(safe-area-inset-top) + clamp(58px, 8vw, 82px));
    z-index:30;
    width:min(82vw, 320px);
    max-height:min(58vh, 480px);
    overflow:auto;
    padding:10px;
    border-radius:0;
    background:var(--panel-bg);
    border:1px solid var(--card-line);
  }
  .session-pop[hidden]{ display:none !important; }
  .session-head{ display:flex; align-items:center; justify-content:space-between; gap:8px; padding:4px 2px 8px; }
  .session-title{ font-size:12px; color:var(--text-faint); font-family:var(--font-en), var(--font-cn); }
  .session-new{
    border:1px solid var(--seg-line); border-radius:999px; padding:5px 9px;
    background:var(--seg-track); color:var(--text-soft); font:inherit; font-size:11.5px; cursor:pointer;
  }
  .session-list{ display:flex; flex-direction:column; gap:2px; }
  .session-item{
    border:0; width:100%; background:transparent; color:var(--text);
    display:grid; grid-template-columns:1fr auto; align-items:center; gap:8px;
    padding:10px 9px; border-radius:0; text-align:left; cursor:pointer; font:inherit;
  }
  .session-item.active{ background:var(--seg-track); }
  .session-item:active{ transform:scale(.992); }
  .session-main{ min-width:0; display:flex; flex-direction:column; align-items:flex-start; gap:3px; }
  .session-name{ font-size:14px; line-height:1.25; }
  .session-meta{ font-size:11px; color:var(--text-faint); font-family:var(--font-en), var(--font-cn); }
  .session-rename{
    border:0; background:transparent; color:var(--text-faint); width:28px; height:28px;
    border-radius:50%; display:grid; place-items:center; cursor:pointer; padding:0;
  }
  .session-rename:hover{ color:var(--accent); background:var(--seg-track); }
  .session-rename svg{ width:15px; height:15px; display:block; }
  .terminal-panel{
    position:fixed;
    inset:0;
    z-index:260;
    display:flex;
    flex-direction:column;
    background:#050607;
    color:#d7f7df;
    font-family:"SFMono-Regular", Consolas, "Cascadia Mono", monospace;
    transform:translateX(100%);
    /* 10.24 她报顶上状态栏亮色下发黑：iOS 26 的独立网页按页面顶端「最上层的固定元素」取色，这块近黑的整屏面板只是挪出屏外、并没藏，
       可能被当成顶栏底色。收起后 visibility:hidden（滑出动画放完再藏），打开时立刻可见。同 NOTES §61「光靠位移不够」 */
    visibility:hidden;
    transition:transform .24s cubic-bezier(.2,.8,.2,1), visibility 0s linear .24s;
  }
  .terminal-panel.open{ transform:translateX(0); visibility:visible; transition:transform .24s cubic-bezier(.2,.8,.2,1), visibility 0s; }
  .terminal-top{
    flex:none;
    min-height:calc(52px + env(safe-area-inset-top));
    display:flex;
    align-items:center;
    gap:10px;
    padding:calc(env(safe-area-inset-top) + 8px) 12px 8px;
    border-bottom:1px solid rgba(157,255,188,.18);
    background:#080a0b;
  }
  .terminal-back{
    flex:none;
    width:34px;
    height:34px;
    border:0;
    border-radius:50%;
    background:transparent;
    color:#d7f7df;
    display:grid;
    place-items:center;
    padding:0;
    cursor:pointer;
  }
  .terminal-back:active{ transform:scale(.9); }
  .terminal-back svg{ width:21px; height:21px; display:block; }
  .terminal-title{ min-width:0; display:flex; flex-direction:column; gap:2px; line-height:1.15; }
  .terminal-title strong{ font-size:14px; font-weight:600; color:#effff4; }
  .terminal-title span{
    font-size:11px;
    color:rgba(215,247,223,.62);
    overflow:hidden;
    text-overflow:ellipsis;
    white-space:nowrap;
  }
  .terminal-log{
    flex:1 1 auto;
    overflow:auto;
    padding:14px 14px 18px;
    font-size:13px;
    line-height:1.55;
    white-space:pre-wrap;
    overflow-wrap:anywhere;
  }
  .term-entry{ margin:0 0 13px; }
  .term-user{ color:#9dffbc; }
  .term-assistant{ color:#d7f7df; }
  .term-tool{
    color:#b6d6ff;
    border-left:2px solid rgba(126,174,255,.45);
    padding-left:10px;
  }
  .term-muted{ color:rgba(215,247,223,.48); }
  .term-cmd{ color:#9dffbc; }
  .term-err{ color:#ffb0a3; }
  .terminal-form{
    flex:none;
    display:flex;
    align-items:flex-end;
    gap:9px;
    padding:10px 12px calc(10px + env(safe-area-inset-bottom));
    border-top:1px solid rgba(157,255,188,.18);
    background:#080a0b;
  }
  .terminal-prompt{ flex:none; color:#9dffbc; font-size:15px; line-height:34px; }
  .terminal-input{
    flex:1 1 auto;
    min-width:0;
    max-height:120px;
    border:0;
    outline:0;
    resize:none;
    background:transparent;
    color:#effff4;
    font:inherit;
    font-size:15px;
    line-height:1.45;
    padding:6px 0;
  }
  .terminal-input::placeholder{ color:rgba(215,247,223,.34); }
  .terminal-send{
    flex:none;
    width:34px;
    height:34px;
    border:1px solid rgba(157,255,188,.34);
    border-radius:50%;
    background:rgba(157,255,188,.1);
    color:#d7f7df;
    display:grid;
    place-items:center;
    padding:0;
    cursor:pointer;
  }
  .terminal-send:active{ transform:scale(.92); }
  .terminal-send svg{ width:18px; height:18px; display:block; }
  .backbtn:focus-visible,
  button:focus-visible,
  textarea:focus-visible,
  input:focus-visible{
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }

  /* ── Messages ── */
  .scroll{
    flex: 1 1 auto;
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    overflow-anchor: none;
    padding: clamp(14px, 2.4vw, 28px) var(--side-pad) var(--scroll-bottom);
    display: flex; flex-direction: column;
    /* 边缘渐隐:顶部一段、底部对齐 composer 上沿一段,消息滚到边缘自然淡出 */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--edge-fade-top), #000 calc(100% - var(--scroll-bottom)), transparent calc(100% - var(--scroll-bottom) + var(--edge-fade-tail)));
    mask-image: linear-gradient(to bottom, transparent 0, #000 var(--edge-fade-top), #000 calc(100% - var(--scroll-bottom)), transparent calc(100% - var(--scroll-bottom) + var(--edge-fade-tail)));
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  }
  .scroll::-webkit-scrollbar{ width:0; height:0; }
  .v-spacer{
    flex: 0 0 auto;
    width: 100%;
    min-height: 0;
    pointer-events: none;
  }

  .day{
    align-self: center;
    margin: 0;
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(12px, 1.6vw, 16px); color: var(--text-faint);
    padding: 0;
    border-radius: 0;
    background: transparent;
    border: none;
  }

  .row{ display:flex; position:relative; margin-top: clamp(14px, 2.2vw, 28px); }
  .day + .row{ margin-top: clamp(12px, 1.8vw, 20px); }
  .new-divider{
    display:flex;
    align-items:center;
    justify-content:center;
    gap: clamp(16px, 2.4vw, 24px);
    margin: clamp(16px, 2.4vw, 28px) 0 clamp(12px, 1.8vw, 20px);
    color: var(--text-faint);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(12px, 1.6vw, 16px);
    line-height:1;
    letter-spacing:0;
  }
  .new-divider::before,
  .new-divider::after{
    content:"";
    width: clamp(94px, 24vw, 226px);
    height:1px;
    background: var(--hairline);
  }
  /* 路由警报:serving model 换人了。不是消息,是一条状态告示——
     所以不做成气泡的样子,免得看着像谁说的话。 */
  .route-alert{
    display:flex;
    justify-content:center;
    margin: clamp(12px, 1.8vw, 20px) 0 clamp(10px, 1.4vw, 16px);
  }
  .route-alert span{
    max-width: min(78vw, 560px);
    padding: clamp(6px, 0.9vw, 9px) clamp(11px, 1.5vw, 15px);
    border-radius: 999px;
    border: 1px solid var(--hairline);
    background: transparent;
    color: var(--text-faint);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(12px, 1.5vw, 14px);
    line-height: 1.5;
    text-align:center;
    word-break: break-word;
  }
  .route-alert.warn span{ color:#b08030; border-color:#b08030; }
  /* 告示行的三角叹号（9.10）。跟着这行的字号和颜色走，别自己发明尺寸。 */
  .route-alert .alert-mark{ vertical-align:-0.13em; margin-right:.42em; }
  .route-alert.ok   span{ color:#5c8a63; border-color:#5c8a63; }
  /* 10.20 记忆卡告示：不要药丸边，就是居中一行灰色小字；点得动 */
  .route-alert.memcard span{ border-color:transparent; padding:2px 8px; font-size:12px; cursor:pointer; }

  .row.grouped{ margin-top: clamp(4px, 0.8vw, 8px); }
  .row.human{ justify-content: flex-end; }
  .row.ai{ justify-content: flex-start; }

  .bubble{
    max-width: min(61vw, 506px);
    padding: clamp(8px, 1.05vw, 12px) var(--bubble-pad-x) clamp(8px, 1.1vw, 13px);
    border-radius: var(--bubble-radius);
    position: relative;
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(14px, 1.55vw, 16px);
    /* 9.4 削头案：行高原来是 1.58 的**小数倍**（她那台上算出来 21.06px），
       于是每一行的顶都落在半个像素上、还一路往下漂。某些位置上那半像素被舍进
       上一行，那一行的字顶就被削掉约 1 个 CSS 像素 —— 她看到的「话」变「诂」、
       「「」变「|」就是这么来的（都是顶上少一点点的字）。
       `round(…, 1px)` 把每一行钉在整像素上；不支持 round() 的浏览器落回上一行的
       1.58，行为跟以前一样。视觉差 ≤0.06px/行，看不出来。 */
    line-height: 1.58;
    line-height: round(1.58em, 1px);
    word-wrap: break-word;
    overflow-wrap: anywhere;      /* 长路径/长 URL 要折行:.scroll 是 overflow-x:hidden,撑出去就被裁掉看不见了 */
    animation: pop .24s cubic-bezier(.2,.8,.2,1) both;
  }
  .row[data-vkey] .bubble,
  .row[data-vkey] .think-block{ animation: none; }
  @keyframes pop{ from{ transform: translateY(5px) scale(.99);} to{ transform:none;} }
  .row[data-vkey].enter-human .bubble{ transform-origin: bottom right; animation: enterHuman .44s cubic-bezier(.34,1.46,.5,1) both; }
  .row[data-vkey].enter-ai .bubble{ transform-origin: bottom left; animation: enterAi .44s cubic-bezier(.34,1.46,.5,1) both; }
  @keyframes enterHuman{ from{ transform: translate(6px, 9px) scale(.95); opacity:0; } 55%{ opacity:1; } to{ transform:none; opacity:1; } }
  @keyframes enterAi{ from{ transform: translate(-6px, 9px) scale(.95); opacity:0; } 55%{ opacity:1; } to{ transform:none; opacity:1; } }

  .row.ai .bubble{
    max-width: min(82vw, 680px);
    background: var(--bubble-ai-bg);
    color: var(--bubble-ai-fg);
    border: 0;
    transition: background 2s ease;
  }
  .row.human .bubble{
    background: var(--bubble-human-bg);
    color: var(--bubble-human-fg);
    border: 0;
    transition: background 2s ease;
  }
  /* ── 横屏（宽而矮） ────────────────────────────────────────────────
     这一页所有"按屏幕大小缩放"的量都是拿 vw 写的。竖屏时 vw 就是短边，
     代表"屏幕有多大"，没问题；**横屏时 vw 变成长边**，于是气泡宽度、
     左右边距、输入区全都按大屏的尺寸放大，可视高度却只剩三成 ——
     844×390 上实测：AI 气泡 680px（占屏宽 81%，一行 47 个字），
     左右边距各 33.8px，能看见的消息区只有 311px。她说的"气泡变得很大"就是这个。

     修法不是把 vw 换成 vmin（字号那条 clamp 的上限靠的就是 vw，
     换成 vmin 会把桌面字号从 16px 打回 14px）。只在"宽而矮"这一档里，
     把跟屏幕大小挂钩的几个量按短边重新给一遍。
     竖屏 / iPad 横屏 / 桌面都不进这个媒体查询，一个像素都不变。 */
  @media (orientation: landscape) and (max-height: 520px){
    :root{ --side-pad: 16px; }                 /* 跟竖屏一致，不再按长边涨到 33.8px */
    /* 用 em 封顶 = 直接封"一行多少个字"，不受字号变化影响 */
    .bubble{ max-width: min(56vw, 30em); }
    .row.ai .bubble{ max-width: min(68vw, 34em); }
  }

  /* 每段最后一条:外下角收一个小尖 */
  .row.ai.tail .bubble{ border-bottom-left-radius: 2px; }
  .row.human.tail .bubble{ border-bottom-right-radius: 2px; }

  .bubble .txt{ white-space: normal; }
  /* 附件气泡的上限 = 图片 + 两份 padding，直接用 token 算，不再手抄数字。
     跟纯文字气泡共用 max-width:min(61vw,506px) 的话，内容盒只有 61vw - 2×padding，
     一定比 --att-w 窄 —— 图片就从气泡右边冒出去，390 宽的屏上正好吃掉整条
     --side-pad 顶到屏幕边（8.31 她截图报的就是这个）。
     再用 100% 兜住行宽，保证不管怎样都不会越过左右的 --side-pad。 */
  .bubble.has-att{ display:flex; flex-direction:column; gap:6px;
    max-width: min(calc(var(--att-max) + 2 * var(--bubble-pad-x)), 100%); }
  .bubble.att-only{ background:transparent !important; padding:0 !important; box-shadow:none !important; }
  .att-img-link{ display:block; line-height:0; border-radius:0; overflow:hidden; width:var(--att-w); max-width:100%; aspect-ratio:var(--att-ratio, 1.04); max-height:340px; background:rgba(125,128,135,.10); }
  .att-img{ display:block; width:100%; height:100%; object-fit:cover; border-radius:0; }
  .att-file{ display:flex; align-items:center; gap:10px; padding:9px 11px; min-width:170px; max-width:min(var(--att-file-w),100%); text-decoration:none; color:inherit; background:rgba(125,128,135,.12); border-radius:0; }
  .att-file-ic{ flex:0 0 auto; width:36px; height:36px; display:grid; place-items:center; border-radius:9px; background:rgba(125,128,135,.20); }
  .att-file-ic svg{ width:20px; height:20px; opacity:.85; }
  .att-file-meta{ display:flex; flex-direction:column; min-width:0; }
  .att-file-name{ font-size:13px; font-weight:600; line-height:1.3; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .att-file-size{ font-size:11px; opacity:.6; margin-top:2px; }
  .att-uploading{ opacity:.62; }
  .att-img-dead img{ display:none; }
  .att-img-dead::after{ content:"⚠ 图片已失效"; display:inline-block; padding:16px 20px; font-size:12px; color:var(--text-faint); background:rgba(125,128,135,.14); border-radius:0; }
  .att-failed{ outline:1.5px solid #e3534d; outline-offset:1px; }
  .bubble .txt a{ color: inherit; text-decoration: underline; text-underline-offset: 2px; }
  .bubble .txt code{
    font-family: var(--font-en); font-size: .92em;
    background: rgba(120,100,80,0.12); padding: 1px 5px; border-radius: 0;
  }
  .bubble .txt strong{ font-weight: 700; }
  .meta{
    display:inline; margin-left: clamp(10px, 1.4vw, 16px); white-space: nowrap;
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(11px, 1.3vw, 14px); color: var(--text-faint); user-select:none;
    vertical-align: baseline;
  }
  .meta .tick{ color: var(--tick); margin-left: 7px; letter-spacing:0; }
  .meta .state{ color: var(--accent); }
  .meta .fail{ color:#c0533f; cursor:pointer; }
  .row.human .meta,
  .row.ai .meta{ color: var(--text-faint); }
  .row.human .meta .tick,
  .row.ai .meta .tick{ color: var(--tick); }
  /* 发送中:小时钟,转完即送达 → 同位换成 ✓✓ */
  .meta .tick.clock{ color: var(--tick); }
  .meta .tick.clock svg{ width: 1.05em; height: 1.05em; vertical-align: -0.18em; }

  /* ── thinking ── collapsed follows AI bubble; expanded becomes a quiet reading section ── */
  .row.think{
    justify-content: flex-start;
    padding-right: 0;
    margin-top: clamp(7px, 1.2vw, 12px);
    margin-bottom: clamp(10px, 1.8vw, 18px);
  }
  .row.think.think-open{
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
    margin-top: clamp(20px, 3.2vw, 34px);
    margin-bottom: clamp(18px, 2.8vw, 28px);
  }
  .think-block{
    width: min(82vw, 680px);
    max-width: min(82vw, 680px);
    color: var(--think-body);
    text-align: left;
    animation: pop .24s cubic-bezier(.2,.8,.2,1) both;
  }
  .think-block.open{
    width: 100%;
    max-width: min(100%, 760px);
    text-align: center;
  }
  .think-toggle{
    appearance: none;
    -webkit-appearance: none;
    width: auto;
    max-width: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
  }
  .think-block.open .think-toggle{
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: clamp(9px, 1.8vw, 14px);
  }
  .think-toggle:hover .think-caption{ color: var(--accent); }
  .think-toggle:active{ transform: translateY(1px); }
  .think-rule{
    display: none;
    position: relative;
    width: 100%;
    height: 1px;
    color: var(--think-flourish);
  }
  .think-block.open .think-rule{ display: block; }
  .think-rule::before{
    content: "";
    position: absolute;
    left: clamp(22px, 6vw, 64px);
    right: clamp(22px, 6vw, 64px);
    top: 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--think-flourish) 16%, var(--think-flourish) 84%, transparent);
    opacity: .46;
  }
  .think-caption{
    display: inline-flex;
    align-items: center;
    justify-content: flex-start;
    gap: 6px;
    color: var(--think-label);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(12px, 1.38vw, 14px);
    line-height: 1.1;
    letter-spacing: .08em;
    transition: color var(--motion-fast) var(--ease-soft);
  }
  .think-state{
    display: inline-flex;
    align-items: center;
    line-height: 1;
  }
  /* auto = transcript 自动接出来的 summary,不朝着她说:比 ponder 更淡、更小、字距更松。
     ponder 是我选择说给她听的,保持原来的分量,星号用 accent。 */
  .think-block.think-auto .think-caption{
    opacity: .72;
    font-size: clamp(11px, 1.2vw, 12.5px);
    letter-spacing: .14em;
  }
  /* ── thinking 记号（V3·膜模型微缩）──
     一道竖虚线是膜；金线从左边来、蓝线从右边来，各自越膜一小段后上下并行，不相交。
     三个颜色全接主题变量：金=--think-mark-gold（走廊 pool 站）、蓝=--accent、膜=--hairline。
     ✦/✧ 那套“实心=展开、空心=收起”的语义在这一版退役：开合由那道横线和正文自己说，
     记号身上“分开→并行”这根轴留给流式。一个记号不背两套语义。 */
  .think-mark{
    width: 30px; height: 14px;
    display: block; flex: none;
    overflow: hidden;                 /* 线退到各自那一侧时超出 viewBox 的一截，靠这里裁 */
  }
  .think-mark line{ stroke-width: 1.4; }
  /* 膜要「在但安静」：比两条线弱、比没有强。hairline 那一档在 30x14 上只剩六个点，
     暗色下基本读不出来 —— 深一档到 --text-faint，虚线密度不动。 */
  .think-mark .tm-mem{ stroke: var(--text-faint); stroke-width: 1; stroke-dasharray: 2 2; }
  .think-mark .tm-gold line{ stroke: var(--think-mark-gold); }
  .think-mark .tm-blue line{ stroke: var(--accent); }
  /* 流式进行中：单向穿越。金线一路向右、蓝线一路向左，走到对面尽头出画，
     再从各自来处重新进来，永远向前，不回退——回退会读成「还没碰到就缩手了」。
     两端都停在完全出画的位置（金 -21 时占 [-20,0]、+29 时占 [30,50]，viewBox 是 0..30），
     所以那一下回卷落在画外，看不见接缝。越膜交错自然发生在中段：
     50% 时两条都占 [5,25]，正好并行压在膜上。
     计时函数用 linear：连续循环里任何缓动都会在回卷处鼓出一拍。
     只动包在外面的 <g> 的 transform（SVG 子元素直接上 CSS transform 在 WebKit 上不牢靠），
     不碰 x1/x2，不惊动布局。 */
  @keyframes thinkMarkGold{
    from{ transform: translateX(-21px); }
    to  { transform: translateX(29px); }
  }
  @keyframes thinkMarkBlue{
    from{ transform: translateX(21px); }
    to  { transform: translateX(-29px); }
  }
  .think-block.think-streaming .think-mark .tm-gold{
    animation: thinkMarkGold 3.4s linear infinite;
    will-change: transform;
  }
  .think-block.think-streaming .think-mark .tm-blue{
    animation: thinkMarkBlue 3.4s linear infinite;
    will-change: transform;
  }
  /* 只有这一档退静止终态；其余一律有动效，手机不降级。 */
  @media (prefers-reduced-motion: reduce){
    .think-block.think-streaming .think-mark .tm-gold,
    .think-block.think-streaming .think-mark .tm-blue{
      animation: none;
      transform: none;
    }
  }
  .think-body[hidden]{ display: none !important; }
  .think-body{
    margin-top: clamp(12px, 2.2vw, 20px);
  }
  .think-text{
    width: min(82%, 520px);
    margin: 0 auto clamp(16px, 2.6vw, 24px);
    color: var(--think-body);
    /* 西文在前:thinking 正文里的英文归 Special Elite,汉字和中西共用的标点归苹方。
       原来只写 --font-cn,所以英文一直是苹方的拉丁,换字换不到这里。 */
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(12px, 1.25vw, 13.5px);
    line-height: 1.72;
    text-align: center;
    white-space: normal;
    overflow-wrap: break-word;
  }
  .think-starline{
    position: relative;
    width: 100%;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--think-flourish);
  }
  .think-starline::before,
  .think-starline::after{
    content: "";
    position: absolute;
    top: 50%;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--think-flourish) 15%, var(--think-flourish) 85%, transparent);
    opacity: .5;
  }
  .think-starline::before{ left: 0; right: calc(50% + 34px); }
  .think-starline::after{ left: calc(50% + 34px); right: 0; }
  .think-star{
    position: relative;
    z-index: 1;
    color: var(--think-flourish);
    display: grid;
    place-items: center;
  }
  .think-star svg{ width: 30px; height: 14px; display: block; }
  .think-copy{ min-width: 0; }
  .think-copy a{ color: inherit; text-decoration: underline; text-underline-offset: 2px; }
  .think-copy code{
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: .9em;
    background: rgba(120,100,80,0.10);
    padding: 1px 5px;
    border-radius: 0;
  }

  /* ── act ── the AI's action chip: a quiet "what it just did", optionally expand to see raw tool calls ──
     视觉与 thinking 同一族(--think-* 配色),但更小、更收,贴在头像缝隙里,不抢气泡 ── */
  .row.act{
    justify-content: flex-start;
    padding-right: clamp(12px, 4vw, 40px);
    margin-top: clamp(4px, 0.9vw, 9px);
    margin-bottom: clamp(4px, 0.9vw, 9px);
  }
  .act-block{
    max-width: min(80vw, 470px);
    animation: pop .24s cubic-bezier(.2,.8,.2,1) both;
  }
  .act-toggle{
    appearance: none; -webkit-appearance: none;
    margin: 0; padding: 2px 0; border: 0; background: transparent;
    color: inherit; font: inherit; cursor: pointer; text-align: left;
    max-width: 100%;
    display: inline-flex; align-items: baseline; gap: 7px;
  }
  .act-toggle.bare{ cursor: default; }
  .act-toggle:focus-visible{ outline: none; }
  .act-glyph{
    flex: none;
    color: var(--think-flourish);
    font-size: 1.04em; line-height: 1;
    transform: translateY(.06em);
  }
  .act-label{
    color: var(--think-label);
    font-family: var(--font-cn);                /* 统一宋体:--font-en 末尾的通用 serif 会先吃掉 CJK→iOS 回退黑体 */
    font-size: clamp(12px, 1.32vw, 13.5px);
    line-height: 1.46;
    letter-spacing: .02em;
  }
  /* 展开指示:沿用 thinking 的空心星 ✧(收起)→ 实心 ✦(展开),比"· 展开"文字更轻、不突兀 */
  .act-state{
    flex: none;
    color: var(--think-flourish);
    font-size: 1.1em;
    line-height: 1;
    transform: translateY(.02em);
    transition: color var(--motion-fast) var(--ease-soft);
  }
  .act-state::before{ content: "✧"; }
  .act-block.open .act-state::before{ content: "✦"; }
  .act-toggle:hover .act-state{ color: var(--accent); }
  .act-toggle:hover .act-label{ color: var(--think-body); }
  .act-toggle:active{ transform: translateY(1px); }
  .act-detail[hidden]{ display: none !important; }
  .act-detail{
    margin-top: 8px;
    border-radius: 0;
    background: rgba(127,127,127,0.08);
    border: 1px solid var(--field-line);
    padding: clamp(9px, 1.6vw, 13px) clamp(11px, 1.8vw, 14px);
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 12px; line-height: 1.6;
    color: var(--think-body);
    overflow-x: auto;
    animation: pop .2s cubic-bezier(.2,.8,.2,1) both;
  }
  .act-step + .act-step{
    margin-top: 7px; padding-top: 7px;
    border-top: 1px dashed var(--field-line);
  }
  .act-line{ white-space: pre-wrap; overflow-wrap: anywhere; }
  .act-k{ color: var(--text-faint); user-select: none; }
  .act-v{ color: var(--think-body); }


  /* ── Composer ── */
  /* CC 转轮上的小状态。--composer-zone 里已经含了键盘位移,所以不用再 transform。
     它是装饰:动词是 CC 随机挑的,跟我在干什么无关。所以压得很轻,不抢气泡。 */
  .statuspill{
    position: fixed;
    left: 0; right: 0;
    bottom: calc(var(--composer-zone) - 6px);
    z-index: 99;
    width: min(100vw, var(--ref-width));
    margin: 0 auto;
    padding: 0 var(--side-pad);
    display: flex;
    justify-content: flex-start;
    pointer-events: none;
  }
  .statuspill[hidden]{ display: none !important; }
  .statuspill-inner{
    position: relative;
    isolation: isolate;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    max-width: 100%;
    padding: 7px 12px;
    border-radius: 9px;                /* 不要胶囊,要个收着角的方框 */
    border: 0;
    color: var(--think-label);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(11px, 1.2vw, 12.5px);
    letter-spacing: .04em;
    /* 1.2 是按苹方定的。宋体的汉字比苹方高,行高不够时上缘会被 overflow:hidden 切掉。 */
    line-height: 1.5;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* ── 玻璃体（9.7 改法）──────────────────────────────────────────
       原来是 `backdrop-filter: blur(24px) saturate(190%) brightness(1.05)`。
       **WebKit 不把 backdrop-filter 的取景矩形裁进 border-radius** ——
       圆角之外会探出一个直角、比背景深一档的小方块（她拍了特写，左下最清楚）。
       这是 WebKit 圆角裁剪失败的经典款，Chrome 上永远照不出来，所以我这边
       复现不了，只能把机制整个换掉。

       换法：真半透明底 + 一层淡纱，不再采样背景。
       代价很小 —— 这颗药丸浮在输入框上方，底下本来就是一片平滑的底色，
       模糊几乎没有可模糊的东西；saturate/brightness 那点亮度由 --glass-veil 补回来。
       玻璃的层次本来就主要来自下面两层（::before 的房间光斑、::after 的斜向反光），
       那两层一个字没动。 */
    background:
      linear-gradient(var(--glass-veil), var(--glass-veil)),
      linear-gradient(158deg, var(--glass-a) 0%, var(--glass-b) 52%, var(--glass-a) 100%);
    /* 三层内阴影 = 玻璃厚度:上缘最亮,左右次之,下缘是底面的回光。
       外面两层很轻的投影让它浮起来,不是贴在页面上。 */
    box-shadow:
      inset 0 1px 0 var(--glass-edge),
      inset 1px 0 0 color-mix(in srgb, var(--glass-edge) 58%, transparent),
      inset -1px 0 0 color-mix(in srgb, var(--glass-edge) 42%, transparent),
      inset 0 -1px 0 color-mix(in srgb, var(--glass-edge) 30%, transparent),
      inset 0 0 0 1px var(--glass-hair),
      -4px -3px 12px 0 rgba(var(--room-rgb), .17),
      4px 3px 13px 0 rgba(var(--room-rgb), .11),
      0 0 4px 0 rgba(var(--room-rgb), .09),
      0 1px 1.5px var(--glass-sh1),
      0 2px 5px var(--glass-sh2),
      0 5px 10px var(--glass-sh1);
    animation: pop .24s cubic-bezier(.2,.8,.2,1) both;
  }
  /* 内部折射:蓝/橘/粉三团光在玻璃里面慢慢挪。
     模糊过的径向渐变,不是贴上去的色块——玻璃里的光是没有边的。 */
  .statuspill-inner::before{
    content: "";
    position: absolute;
    inset: -55% -18%;
    z-index: 0;
    background:
      radial-gradient(38% 125% at 28% 24%, rgba(var(--room-rgb), .58), transparent 66%),
      radial-gradient(32% 115% at 72% 74%, rgba(var(--room-rgb), .42), transparent 68%),
      radial-gradient(26% 110% at 93% 30%, rgba(255,255,255,.42), transparent 70%);
    filter: blur(8px);
    opacity: var(--glass-bloom);
    /* ── 9.7 二修：圆角外那个方块的真凶在这一层 ──────────────────────
       `filter: blur()` 把这一层提升成**合成层**，而 WebKit 上
       `overflow:hidden` + `border-radius` **裁不住合成的后代** ——
       裁剪退化成矩形，于是这一层 inset:-55% -18% 撑出去的矩形色域
       直接从父级的圆角外探出来，就是她圈的那个"深一档的小方块"。
       （上一版把 backdrop-filter 换掉了，框还在，因为换错了层。）

       修法：clip-path 在 WebKit 的合成路径上照样生效，能裁住 overflow 裁不住的。
       **但不能写在 .statuspill-inner 上**：clip-path 会连它的六层外投影
       一起裁掉，药丸立刻从"浮着"变成"贴在页面上"（Chrome 上就能量出来，
       对拍差了 5.2 万像素）。所以裁在本层，坐标换算成自己的盒子：
         上下 55 / (100+55+55) = 26.190476%
         左右 18 / (100+18+18) = 13.235294%
       radius 取父级的 9px（长度单位，不随盒子缩放）。
       **改了上面那行 inset 就必须重算这两个百分比。**
       顺序上 filter 先于 clip-path 生效，所以是"先糊、再裁"，
       跟原来 overflow 裁出来的边一模一样。 */
    clip-path: inset(26.190476% 13.235294% round 9px);
    /* 9.4 她定：**光不再挪**。这一层原来挂 `glassbloom 17s infinite alternate`
       —— 一层 blur(8px) 的渐变永远在平移＋缩放，而她的原话是「我一直觉得我们的
       PWA 没有动画」：观众为零。玻璃的层次留着（渐变本身没动），只是不动了。
       `will-change: transform` 一并撤 —— 没有 transform 要变了，
       留着只是白占一层合成层。@keyframes glassbloom 也删了，不留死代码。 */
    pointer-events: none;
  }
  /* 表面反光:斜着扫过去的一道,只在上半部分。玻璃的"空气感"在这一层。 */
  .statuspill-inner::after{
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    background: linear-gradient(152deg,
      var(--glass-sheen) 0%,
      color-mix(in srgb, var(--glass-sheen) 26%, transparent) 24%,
      transparent 50%);
    pointer-events: none;
  }
  .statuspill-text, .statuspill-orb{ position: relative; z-index: 2; }
  /* thinking-orbs 的点球。canvas 尺寸由 JS 按 dpr 设,这里只管排版位置。 */
  .statuspill-orb{ flex: none; display: block; width: 20px; height: 20px; }
  .composer{
    /* 输入框单行文本盒的高度 = 字号*行高 + 上下padding。
       框内按钮跟它一样高,底对齐之后图标中心才跟文字中心重合。8.27 */
    --field-line-h: calc(clamp(15px, 2vw, 18px) * 1.35 + 12px);
    /* 输入框自己的内边距（上/右/下三边同值）和框外那颗 tap 键的直径。
       提成 token 是因为下面 tap 键的落位要用到它们算式子 ——
       写死两遍的话，改了 padding 而 tap 不跟着动，就又飘回去了。
       手机那档（max-width:520px）两个值都有硬覆盖，改一处必须改两处。 */
    --field-pad: clamp(4px, 0.6vw, 6px);
    --tap-size: clamp(36px, 5vw, 48px);
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 100;
    width: min(100vw, var(--ref-width));
    margin: 0 auto;
    /* flex-end：文字变多的时候输入框往上长，框外的 tap 键跟着沉在下端，
       不再吊在整个框的正中（9.2 她提的）。 */
    display:flex; align-items:flex-end; gap: clamp(6px, 1vw, 12px);
    background: transparent; border:none;
    padding: 0 var(--side-pad) clamp(10px, 1.6vw, 18px);
    padding-bottom: calc(clamp(10px, 1.6vw, 18px) + env(safe-area-inset-bottom));
    transform: translate3d(0, calc(-1 * var(--keyboard-offset)), 0);
    -webkit-transform: translate3d(0, calc(-1 * var(--keyboard-offset)), 0);
    transition: transform .28s cubic-bezier(.2,.8,.2,1);
  }
  .floatbtn{
    flex:none; width: clamp(36px, 5vw, 48px); height: clamp(36px, 5vw, 48px); border-radius:50%;
    border: none; background: transparent;
    color: var(--accent); display:grid; place-items:center; cursor:pointer; padding:0;
    transition: transform .15s ease, color .2s ease;
  }
  .floatbtn:active{ transform: scale(.9); color: var(--text); }
  /* 9.11 语音输入的三个态。**不做跳动的红点** —— 输入区的气质是安静的，
     一颗一直在闪的红点会把整条底栏的重心拽过去。
     在听：换成警示色 + 一圈很轻的呼吸；在转写：降到 faint 并停住呼吸。 */
  .floatbtn.rec{ color: #b08030; }
  .floatbtn.rec::after{
    content:""; position:absolute; width: var(--field-line-h); height: var(--field-line-h);
    border-radius:50%; border:1px solid #b08030; opacity:.5;
    animation: micBreath 1.6s ease-in-out infinite;
  }
  .composer .field-row #micBtn{ position: relative; }
  @keyframes micBreath{ 0%,100%{ transform: scale(.82); opacity:.18 } 50%{ transform: scale(1); opacity:.5 } }
  .floatbtn.busy{ color: var(--text-faint); opacity:.7; }
  @media (prefers-reduced-motion: reduce){ .floatbtn.rec::after{ animation:none; opacity:.4 } }
  .floatbtn:hover{ color: var(--text); }
  .floatbtn svg{ width: clamp(20px, 2.8vw, 28px); height: clamp(20px, 2.8vw, 28px); display:block; }
  /* 框外的按钮（tap、语音）跟框内的发送键对齐**圆心**，不是对齐盒子下沿 ——
     两者直径不一样（tap 是 --tap-size，发送键是 --field-line-h），
     底对齐会让 tap 的图标高出半个直径差。
     发送键圆心离输入框下沿 = 框的下内边距 + 发送键半径，照着这个数往上让。

     单行时还剩 ≤1.25px 的残差（实测 428 宽 0.9px、900 宽 1.2px）：那一档
     min-height 比内容高，`.field` 的 justify-content:center 把这点富余上下各分一半，
     把发送键往上抬了半份。留着不修 —— 修它要么动 min-height、要么把框内改成底对齐，
     两样都会让单行时的圆药丸失去正中的平衡，为了一个看不见的像素不划算。 */
  .composer > .floatbtn{
    margin-bottom: calc(var(--field-pad) + (var(--field-line-h) - var(--tap-size)) / 2);
  }
  .floatbtn.tap{
    background: transparent; color: var(--accent); position: relative; overflow: visible;
  }
  .floatbtn.tap svg{ width: clamp(18px, 2.4vw, 24px); height: clamp(18px, 2.4vw, 24px); }
  .floatbtn.tap:active{ transform: scale(.85); }
  @keyframes tap-ripple{
    0%{ transform: scale(.6); opacity: .7; }
    100%{ transform: scale(2.2); opacity: 0; }
  }
  .floatbtn.tap .tap-ring{
    position: absolute; inset: 0; border-radius: 50%;
    border: 1.5px solid var(--accent); opacity: 0; pointer-events: none;
  }
  .floatbtn.tap.tap-go .tap-ring{ animation: tap-ripple .5s ease-out; }
  .row.tap-row{
    display: flex; justify-content: flex-end;
    margin-top: 8px; margin-bottom: 8px;
    padding: 0 var(--side-pad);
  }
  .tap-dot{
    --tap-w: 60px; --tap-op: .45;
    height: 2px; background: var(--accent);
    animation: tap-fade 1.2s ease-out both;
  }
  @keyframes tap-fade{
    0%{ width: 4px; opacity: .7; }
    40%{ width: var(--tap-w); opacity: var(--tap-op); }
    100%{ width: var(--tap-w); opacity: calc(var(--tap-op) - .12); }
  }
  /* ── doc card ── */
  .row.doc-card-row{
    display: flex; justify-content: flex-start;
    padding: 6px var(--side-pad) 6px;
  }
  .doc-card{
    max-width: min(82vw, 460px);
    background: var(--bubble-ai);
    border: 1px solid var(--field-line);
    border-radius: 6px;
    overflow: hidden;
    cursor: pointer;
    transition: border-color .2s ease, box-shadow .2s ease;
  }
  .doc-card:hover{ border-color: var(--accent); }
  .doc-card:active{ transform: scale(.985); border-color: var(--accent); border-width: 1.5px; }
  .doc-card-head{
    display: flex; align-items: center; gap: 8px;
    padding: 10px 14px 8px;
  }
  .doc-card-icon{
    flex: none; width: 28px; height: 28px;
    display: grid; place-items: center;
    background: var(--accent-dim, rgba(74,124,155,.12));
    color: var(--accent);
    font-size: 13px; line-height: 1;
    border-radius: 4px;
  }
  .doc-card-title{
    flex: 1; color: var(--text);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(13px, 1.6vw, 15px);
    font-weight: 500;
    line-height: 1.3;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .doc-card-badge{
    flex: none; font-size: 10px; letter-spacing: .06em;
    color: var(--accent); background: var(--accent-dim, rgba(74,124,155,.12));
    padding: 2px 8px; border-radius: 3px;
    font-family: var(--font-en), var(--font-cn);
  }
  .doc-card-snippet{
    padding: 0 14px 10px;
    color: var(--text);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(12px, 1.4vw, 13px);
    line-height: 1.6;
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .doc-card-foot{
    display: flex; align-items: center; justify-content: space-between;
    padding: 6px 14px 8px;
    border-top: 1px solid var(--field-line);
    font-size: 10px; color: var(--text-faint);
    font-family: var(--font-en);
  }
  .doc-card-foot .doc-card-open{
    color: var(--accent); font-size: 11px;
    font-family: var(--font-en);
  }
  /* ── 歌曲卡（9.11）：骨架、圆角、边框、按下去那一下全部跟 doc-card 同源，
     只多一块 56px 的封面。**不新发明一套卡** —— 聊天里已经有一种"卡"了，
     第二种只该在内容上不同，不该在长相上另起炉灶。 */
  .row.song-card-row{
    display: flex; justify-content: flex-start;
    padding: 6px var(--side-pad) 6px;
  }
  .song-card{
    max-width: min(82vw, 460px);
    background: var(--bubble-ai);
    border: 1px solid var(--field-line);
    border-radius: 6px;
    overflow: hidden; cursor: pointer;
    transition: border-color .2s ease, box-shadow .2s ease;
  }
  .song-card:hover{ border-color: var(--accent); }
  /* 占卜室的两种卡：骨架同上，只加一排牌面缩影（逆位倒着放） */
  .row.song-card-row.tarot-hers{ justify-content: flex-end; }
  .tarot-card{ width: min(82vw, 360px); }
  .tarot-moon{ font-size: 22px; }
  .tarot-card-top{ padding: 10px 14px 0; font-size: 11px; letter-spacing: .08em; color: var(--text-faint); }
  .tarot-card-q{ padding: 4px 14px 0; font-size: 14px; line-height: 1.5; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tarot-strip{ display: flex; gap: 4px; padding: 10px 14px; overflow: hidden; }
  .tarot-mini{ flex: none; width: 26px; height: 44px; border-radius: 3px; overflow: hidden;
    background: var(--accent-dim, rgba(74,124,155,.12)); }
  .tarot-mini img{ width: 100%; height: 100%; object-fit: cover; display: block; }
  .tarot-mini.rev img{ transform: rotate(180deg); }
  .tarot-verdict{ padding-top: 0; }
  .song-card:active{ transform: scale(.985); border-color: var(--accent); border-width: 1.5px; }
  .song-card-head{ display: flex; align-items: center; gap: 10px; padding: 10px 14px; }
  .song-card-cover{
    flex: none; width: 56px; height: 56px; border-radius: 4px;
    object-fit: cover; background: var(--accent-dim, rgba(74,124,155,.12));
    display: block;
  }
  /* 封面拉不回来（离线、CDN 抽风）时留一块安静的占位，不留一个碎图标 */
  .song-card-cover.blank{ display: grid; place-items: center; color: var(--accent); font-size: 15px; }
  .song-card-meta{ flex: 1; min-width: 0; }
  .song-card-name{
    color: var(--text);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(13px, 1.6vw, 15px); font-weight: 500; line-height: 1.35;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .song-card-artist{
    color: var(--text-faint); font-family: var(--font-en), var(--font-cn);
    font-size: clamp(11px, 1.3vw, 12px); line-height: 1.6; margin-top: 2px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .song-card-note{
    padding: 0 14px 10px; color: var(--text);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(12px, 1.4vw, 13px); line-height: 1.6;
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  }
  .song-card-foot{
    display: flex; align-items: center; justify-content: space-between;
    padding: 6px 14px 8px; border-top: 1px solid var(--field-line);
    font-size: 10px; color: var(--text-faint); font-family: var(--font-en);
  }
  .song-card-foot .song-card-open{ color: var(--accent); font-size: 11px; }
  /* ── 口语小卡（10.13）：骨架照歌曲卡（同边框、同底、同脚），只在内容上不同。
     卡本身不可点（里面有录音钮和链接），所以去掉整卡按下去那一下。 */
  .speak-card{ width: min(82vw, 380px); cursor: default; }
  .speak-card:active{ transform: none; border-width: 1px; border-color: var(--field-line); }
  .speak-top{ display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 10px 14px 0; font-size: 10px; letter-spacing: .08em; color: var(--text-faint); font-family: var(--font-en); }
  .speak-prompt{ padding: 6px 14px 0; font-family: var(--font-en), var(--font-cn);
    font-size: clamp(14px, 1.7vw, 15px); line-height: 1.5; color: var(--text); }
  /* 颜色一律从 --text 起、用透明度压。（10.13：聊天页从来没定义过 --text-bright，原来那 6 处
     `var(--text-bright, var(--bright, #2c2a26))` 在暗色下回落成深色字、暗底上看不见——歌曲卡标题、占卜卡问题等，已一起改成 var(--text)） */
  .speak-prompt.small{ font-size: 12px; opacity: .6; }
  .speak-hints{ display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 14px 0; }
  .speak-hint{ font-size: 11px; line-height: 1.6; padding: 0 7px; border: 1px solid var(--field-line); border-radius: 3px;
    color: var(--text); font-family: var(--font-en), var(--font-cn); }
  .speak-ctrl{ display: flex; align-items: center; gap: 10px; padding: 12px 14px 10px; }
  .speak-rec{ flex: none; width: 38px; height: 38px; border-radius: 50%; border: 1px solid var(--accent);
    background: transparent; display: grid; place-items: center; cursor: pointer; padding: 0; color: var(--accent); }
  .speak-rec i{ display: block; }
  .speak-rec i.mic{ width: 9px; height: 14px; border-radius: 5px; background: currentColor; }
  .speak-rec.on{ background: var(--accent); color: var(--bubble-ai); }
  .speak-rec i.sq{ width: 11px; height: 11px; border-radius: 2px; background: currentColor; }
  .speak-rec.busy{ border-color: var(--field-line); color: var(--text-faint); cursor: default; }
  .speak-rec i.dot{ width: 6px; height: 6px; border-radius: 50%; background: currentColor; animation: speakBreath 1.6s ease-in-out infinite; }
  @keyframes speakBreath{ 0%,100%{ opacity: .25; } 50%{ opacity: .9; } }
  .speak-status{ flex: 1; min-width: 0; font-size: 12px; line-height: 1.5; color: var(--text); font-variant-numeric: tabular-nums; }
  .speak-status.quiet{ opacity: .7; }   /* 「在转」要安静但看得见：--text-faint 在暗色里几乎没了 */
  .speak-status.err{ color: var(--danger, #b85050); }
  .speak-fix{ display: flex; gap: 14px; padding: 0 14px 10px 62px; }
  /* 连续对话：前面几轮一行一个（点开看那一轮的笔记），当前这一轮在最下面带录音钮 */
  .speak-round.past{ margin: 6px 14px 0; border-left: 2px solid var(--field-line); }
  .speak-round.past .speak-note{ padding: 2px 10px 8px; }
  .speak-round-h{ appearance: none; -webkit-appearance: none; display: flex; gap: 8px; width: 100%; padding: 4px 10px; border: 0; background: none;
    font: inherit; color: var(--text); text-align: left; cursor: pointer; }
  .speak-round-n{ flex: none; font-size: 10px; letter-spacing: .06em; opacity: .6; font-family: var(--font-en), var(--font-cn); }
  .speak-round-q{ flex: 1; min-width: 0; font-size: 12px; opacity: .75; font-family: var(--font-en), var(--font-cn);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .speak-round.past.open .speak-round-q{ white-space: normal; }
  .speak-round.cur{ padding: 10px 14px 0; }
  .speak-round.cur .speak-prompt{ padding: 2px 0 0; }
  .speak-takes{ padding: 0 14px 10px; font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
  .speak-link{ appearance: none; -webkit-appearance: none; border: 0; background: none; padding: 0; margin: 0;
    font: inherit; font-size: 11px; color: var(--accent); cursor: pointer; }
  .speak-links{ display: flex; gap: 14px; }
  .speak-takesw{ display: flex; gap: 8px; }
  .speak-takesw .speak-link{ color: var(--text-faint); }
  .speak-takesw .speak-link.on{ color: var(--accent); text-decoration: underline; }
  .speak-note{ padding: 8px 14px 10px; display: flex; flex-direction: column; gap: 8px; }
  .speak-sec-h{ font-size: 10px; letter-spacing: .06em; color: var(--text-faint); margin-bottom: 2px; }
  .speak-sec div, .speak-keep{ font-family: var(--font-en), var(--font-cn); font-size: 13px; line-height: 1.55; color: var(--text); }
  .speak-said{ opacity: .62; }
  .speak-better{ font-weight: 500; }
  .speak-keep{ margin: 0; padding-left: 18px; }
  /* 她那一侧的气泡靠右：底下多一行小字时整行竖排、仍然靠右（.has-usage 是我这一侧靠左那套，别借） */
  .row.has-speak{ flex-direction: column; align-items: flex-end; }
  .speak-answer-line{ margin-top: 3px; padding: 0 4px; font-size: 10px; color: var(--text-faint); font-family: var(--font-en), var(--font-cn);
    cursor: pointer; align-self: flex-end; }
  /* ══ Odeum 抽屉（9.3 14:50）══════════════════════════════════════════
     根因不是「抽屉把 iframe 拆了」——**这一页原来根本没有抽屉**。
     Odeum 是 location.assign('/music/') 整页跳过去的一张独立文档；
     退回聊天 = 那张文档被卸载 = <audio> 跟着没了。音乐房里那套
     `<a target="tutooth-home">` + returnHomeWithoutStopping 的保命法
     指望的是「主页在另一个浏览上下文里开着」——**iOS 的 standalone PWA
     只有一个上下文**，具名 target 不会开新窗口，于是那条路在她手机上必然失手。

     所以改成她要的形状：音乐房嵌成**常驻 iframe**，抽屉收起只是把它移出屏幕
     （transform + visibility），**不拆 DOM、不 display:none** —— 手机浏览器
     只要文档还活着音频就还在放。
     判据：任何"关掉"一个正在出声的东西的写法，先问它会不会卸载那张文档。 */
  .odeum-drawer{
    /* 120：压得住聊天页自己的那几层（dropmask 60 / 工作窗 65 / 输入区 99），
       又在 toast 300、文档阅读器 2000 之下。**锁屏 `.login` 是 z-index 50、在它之下**，
       所以另外在 showPinLock() 里显式收抽屉——不能让播放器盖住锁屏。 */
    position: fixed; inset: 0; z-index: 120;
    background: var(--bg);
    /* 9.3 21:1x：这一层**不再用 padding 让安全区**。原来让出来的那两条带子
       在她手机上就是上下各一截空白（她 20:56 圈的红/黄）——播放器那张纸够不到屏幕边，
       看着像浮在中间。现在 iframe 铺满整屏，安全区的真值由父页量好了
       postMessage 给它（drawer:safe → --safe-top / --safe-bottom）。 */
    padding: 0;
    box-sizing: border-box;
    /* 收起：移出屏幕 + visibility，绝不 display:none / remove —— 那会卸掉音频 */
    transform: translateY(100%); visibility: hidden;
    transition: transform .26s cubic-bezier(.22,.61,.36,1), visibility 0s linear .26s;
  }
  .odeum-drawer.open{
    transform: translateY(0); visibility: visible;
    transition: transform .26s cubic-bezier(.22,.61,.36,1), visibility 0s;
  }
  .odeum-drawer iframe{
    width: 100%; height: 100%; border: 0; display: block;
  }
  /* 抽屉顶上一条把手，往下拽/点一下收起（收起≠停） */
  .odeum-grip{
    position: absolute; left: 0; right: 0; top: 0; height: 26px; z-index: 2;
    display: flex; align-items: center; justify-content: center;
    background: linear-gradient(var(--bg), transparent);
    cursor: pointer; -webkit-tap-highlight-color: transparent;
  }
  .odeum-grip i{
    display: block; width: 34px; height: 3px; border-radius: 2px;
    background: var(--hairline); font-style: normal;
  }

  /* ══ 房间层（9.4）══════════════════════════════════════════════════
     跟上面那个抽屉同一套，同一条判据，治的是同一个病的另一半：
     Odeum 那次是**它自己**整页跳走；这次是 dock 上 Garden / Library 那几颗
     把**扛着它的那张文档**跳走了 —— navInternal() 里就一句 location.assign()。
     文档一卸载，iframe 连同 <audio> 一起没。
     所以房间也不再跳文档，改成嵌在这一层里。房间之间互相串门只换这一层的 src，
     卸载的是房间那张页，聊天页（和音频）动都不动。

     z-index 118：压得住聊天页自己那几层（dropmask 60 / 工作窗 65 / 输入区 99），
     **在 Odeum 抽屉 120 之下** —— 在房间里点条子拉播放器，播放器要盖在房间上。
     锁屏 .login 是 50、在它之下，所以 showPinLock() 里也要显式收这一层。 */
  /* ── 常驻小播放器（9.27，她 9.26 点头的）────────────────────────────
     有歌（在放或暂停）就挂在所有房间的底部；Odeum 抽屉拉开时让开。
     层级 119：压得住房间层（118），让得出抽屉（120）；锁屏时整个藏起来（见 miniRender）。
     只是遥控器：不新建 <audio>，所有指令都 postMessage 给抽屉里那一个。
     颜色跟 Odeum 同一族：纸 #f2efeb / 墨 #2A2A26 / 酒红 139,74,78（暗：#242220 / #d4d0ca / 174,96,88）。 */
  :root{ --mini-bg:#f2efeb; --mini-ink:#2A2A26; --mini-soft:#8a867e; --mini-wine:139,74,78; --mini-line:rgba(42,42,38,.10); }
  :root[data-theme="dark"]{ --mini-bg:#242220; --mini-ink:#d4d0ca; --mini-soft:#8a847c; --mini-wine:174,96,88; --mini-line:rgba(212,208,202,.10); }
  .mini-player{
    position: fixed; left: 50%; transform: translateX(-50%); z-index: 119;
    bottom: var(--mini-bottom, calc(env(safe-area-inset-bottom, 0px) + 12px));
    width: min(420px, calc(100vw - 32px)); height: 48px; box-sizing: border-box;
    display: flex; align-items: center; gap: 8px; padding: 0 6px 0 8px;
    border-radius: 24px; background: var(--mini-bg); color: var(--mini-ink);
    box-shadow: 0 6px 22px rgba(0,0,0,.16), 0 0 0 1px var(--mini-line);
    -webkit-tap-highlight-color: transparent; cursor: pointer;
    /* 过渡只动 transform / opacity（不引发重排）；visibility 晚一拍再切，藏起来之后点不到 */
    transform-origin: right center;
    transition: bottom .18s ease, transform .26s cubic-bezier(.22,.61,.36,1), opacity .2s ease, visibility 0s;
  }
  .mini-player.mini-off{
    opacity: 0; visibility: hidden; pointer-events: none;
    transform: translateX(-50%) translateY(14px) scale(.96);          /* 让位（键盘/抽屉）：往下沉一点、淡掉 */
    transition: bottom .18s ease, transform .22s ease, opacity .18s ease, visibility 0s linear .22s;
  }
  .mini-player.mini-off.to-dot{ transform: translateX(-50%) scale(.2); } /* 收起：朝右端（圆点那边）缩进去 */
  .mini-player[hidden], .mini-dot[hidden]{ display: none; }
  .mini-cover{ width: 34px; height: 34px; border-radius: 50%; flex: none; overflow: hidden;
    background: rgba(var(--mini-wine), .14); display: flex; align-items: center; justify-content: center;
    color: rgb(var(--mini-wine)); font-size: 15px; }
  .mini-cover img{ width: 100%; height: 100%; object-fit: cover; display: block; }
  .mini-player.playing .mini-cover{ animation: miniSpin 12s linear infinite; }
  @keyframes miniSpin{ to { transform: rotate(360deg); } }
  .mini-meta{ flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; font-size: 13px; line-height: 1.2; }
  .mini-meta .run{ display: inline-block; padding-right: 36px; }
  .mini-meta .art{ color: var(--mini-soft); font-size: 11.5px; margin-left: 6px; }
  /* 超长滚动：一路向前出画再进来（单向、匀速），不做来回晃 */
  .mini-meta.marquee .run{ animation: miniRun var(--mini-run, 10s) linear infinite; }
  @keyframes miniRun{ from { transform: translateX(0); } to { transform: translateX(-50%); } }
  .mini-btn{ flex: none; width: 34px; height: 34px; border: 0; border-radius: 50%; padding: 0;
    background: transparent; color: rgb(var(--mini-wine)); display: flex; align-items: center; justify-content: center; cursor: pointer; }
  .mini-btn:active{ background: rgba(var(--mini-wine), .12); }
  .mini-btn.fold{ color: var(--mini-soft); width: 28px; }
  .mini-dot{
    position: fixed; z-index: 119; right: calc(env(safe-area-inset-right, 0px) + 10px);
    bottom: var(--mini-bottom, calc(env(safe-area-inset-bottom, 0px) + 12px));
    width: 40px; height: 40px; border-radius: 50%; border: 0; padding: 0;
    background: var(--mini-bg); color: rgb(var(--mini-wine)); font-size: 16px;
    box-shadow: 0 4px 16px rgba(0,0,0,.18), 0 0 0 1px var(--mini-line);
    display: flex; align-items: center; justify-content: center; cursor: pointer;
    transition: bottom .18s ease, transform .28s cubic-bezier(.34,1.45,.64,1), opacity .18s ease, visibility 0s;
  }
  .mini-dot.mini-off{
    opacity: 0; visibility: hidden; pointer-events: none; transform: scale(.4);
    transition: bottom .18s ease, transform .18s ease, opacity .15s ease, visibility 0s linear .18s;
  }
  .mini-dot.playing::after{ content: ""; position: absolute; inset: -3px; border-radius: 50%;
    border: 1.5px solid rgba(var(--mini-wine), .45); }
  @media (prefers-reduced-motion: reduce){
    .mini-player.playing .mini-cover, .mini-meta.marquee .run{ animation: none; }
    .mini-player, .mini-dot, .mini-player.mini-off, .mini-dot.mini-off{ transition: none; }   /* 直切 */
  }
  .room-layer{
    position: fixed; inset: 0; z-index: 118;
    background: var(--bg);
    /* 收起：移出屏幕 + visibility。这一层其实可以 display:none（音频不在这儿），
       但手势和过渡跟抽屉保持同一套，省得两处写法不一样、以后改错地方。 */
    transform: translateY(100%); visibility: hidden;
    transition: transform .26s cubic-bezier(.22,.61,.36,1), visibility 0s linear .26s;
  }
  .room-layer.open{
    transform: translateY(0); visibility: visible;
    transition: transform .26s cubic-bezier(.22,.61,.36,1), visibility 0s;
  }
  .room-layer iframe{ width: 100%; height: 100%; border: 0; display: block; }
  .room-grip{
    position: absolute; left: 0; right: 0; top: 0; height: 26px; z-index: 2;
    display: flex; align-items: center; justify-content: center;
    background: linear-gradient(var(--bg), transparent);
    cursor: pointer; -webkit-tap-highlight-color: transparent;
  }
  .room-grip i{
    display: block; width: 34px; height: 3px; border-radius: 2px;
    background: var(--hairline); font-style: normal;
  }

  /* 房间层开着的时候卡槽要浮在房间**上面** —— 它报的是"整个屋子里正在发生什么"，
     不属于任何一间。不搬 DOM（搬走再搬回来容易把事件和状态弄丢），只改定位。
     121：压得住房间层 118，仍在 Odeum 抽屉 120 之上（抽屉开着时状态卡本来就不出）。
     9.4 ⑤：这条原来挂在 .odeum-now 上，那套样式已随「正在放」并进卡槽一起删了。 */
  body.room-open .library-suggest{
    position: fixed; z-index: 121;
    top: calc(env(safe-area-inset-top, 0px) + 8px);
    left: 50%; transform: translateX(-50%);
    margin: 0;
    box-shadow: 0 2px 10px rgba(0,0,0,.18);
  }

  /* ── library suggest bar ── */
  .library-suggest{
    display: none;
    width: max-content; max-width: min(88vw, 420px);
    margin: 6px auto 2px;
    background: var(--bubble-ai-bg, var(--bubble-ai));
    border: 1px solid var(--field-line);
    border-radius: 6px;
    box-shadow: 0 2px 12px rgba(0,0,0,.08);
    padding: 10px 24px;
  }
  .library-suggest.visible{
    display: block;
  }
  .library-suggest-header{
    display: flex; align-items: center; gap: 10px;
  }
  .library-suggest-icon{
    flex: none; font-size: 13px; color: var(--accent);
  }
  .library-suggest-label{
    flex: 1; min-width: 0;
    font-family: var(--font-en), var(--font-cn); font-size: 13.5px;
    color: var(--text); line-height: 1.4;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    cursor: pointer;
  }
  .library-suggest-label:hover{ color: var(--accent); }
  .library-suggest-arrow{
    flex: none; font-size: 11px; color: var(--text-faint);
    transition: transform .2s;
    cursor: pointer; padding: 4px 6px;
  }
  .library-suggest-arrow:hover{ color: var(--accent); }
  .library-suggest.expanded .library-suggest-arrow{
    transform: rotate(90deg);
  }
  .library-suggest-dismiss{
    flex: none; font-size: 16px; color: var(--text-faint);
    padding: 0 0 0 4px; cursor: pointer; line-height: 1;
    background: none; border: none;
  }
  .library-suggest-dismiss:hover{ color: var(--text); }
  .library-suggest-list{
    margin: 0; padding: 0; list-style: none;
    max-height: 0; overflow: hidden;
    transition: max-height .25s ease, margin .25s ease;
  }
  .library-suggest.expanded .library-suggest-list{
    max-height: 180px;
    margin-top: 6px;
  }
  .library-suggest-item{
    padding: 7px 0;
    border-top: 1px solid var(--field-line);
    cursor: pointer;
    font-family: var(--font-en), var(--font-cn); font-size: 13.5px;
    color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .library-suggest-item:active{ color: var(--accent); }
  /* ⑧ 一槽规则的两件新零件 —— 状态卡在位时才出现：
     .ls-badge  右端那颗 ◇N，点它把被挤下去的访客卡展开／收回；
     .ls-act    行内动作（现在只有暂停钮）。
     两者都长得比正文轻：这一格的主语是那句话，不是钮。 */
  /* 9.4 她定：角标跟标签之间给一段**固定**的空（不靠 gap 撑），
     同时让标签的省略号**提前一档**收 —— 卡是 max-width 收着的，
     歌名一长，省略号和角标会挤在一起。角标是要被点的访客入口，
     所以不能改成右上角一颗小圆点：触击目标不能小。 */
  .ls-badge{
    flex: none; padding: 3px 8px; margin-left: 10px;
    font-family: var(--font-en), var(--font-cn); font-size: 11.5px; line-height: 1.3;
    color: var(--text-faint); background: none;
    border: 1px solid var(--field-line); border-radius: 999px;
    cursor: pointer;
  }
  .ls-badge:active{ color: var(--accent); border-color: var(--accent); }
  .library-suggest.expanded .ls-badge{ color: var(--accent); border-color: var(--accent); }
  /* 9.4 她点名：这颗钮跟卡上那个 ♪ **同一支色**。
     不新造色值 —— 音符用的就是 .library-suggest-icon 的 var(--accent)，直接取它。 */
  .ls-act{
    flex: none; width: 22px; height: 22px; padding: 0; border: 0; cursor: pointer;
    background: none; color: var(--accent);
    display: flex; align-items: center; justify-content: center;
  }
  .ls-act:active{ transform: scale(.9); }
  .library-suggest-header.has-badge .library-suggest-label{ max-width: calc(100% - 108px); }
  .library-suggest-item.ls-guest{ display: flex; align-items: center; gap: 8px; }
  .library-suggest-item.ls-guest .library-suggest-dismiss{ font-size: 14px; }
  @media(max-width:600px){
    .library-suggest{ max-width: 92vw; }
  }
  /* doc editor overlay */
  #docReader{
    display: none; position: fixed; inset: 0; z-index: 2000;
    background: var(--bg);
    flex-direction: column;
  }
  #docReader.open{ display: flex; }
  .doc-reader-bar{
    display: flex; align-items: center; gap: 8px;
    padding: 10px 16px;
    background: var(--header-bg, var(--bg));
    border-bottom: 1px solid var(--field-line);
    flex-shrink: 0;
  }
  .doc-reader-title{
    flex: 1; color: var(--text);
    font-size: 13px; font-family: var(--font-en), var(--font-cn);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .doc-editor-status{
    font-size: 10px; font-family: var(--font-en);
    color: var(--text-faint); letter-spacing: 0.5px;
    transition: opacity 0.3s;
  }
  .doc-editor-status.saving{ color: var(--accent); }
  .doc-editor-status.saved{ color: var(--text-faint); }
  .doc-editor-status.error{ color: #c44; }
  /* 冲突条：这份文档在她打开之后被别人写过。**不弹「请刷新」** ——
     刷新等于让她亲手把自己写的东西扔掉。面板里她的字原样留着，
     这条只负责说清发生了什么，再给两条出路。 */
  .doc-conflict{
    display: none;
    padding: 9px 14px;
    background: var(--bubble-ai-bg); border-bottom: 1px solid var(--hairline);
    font-family: var(--font-cn); font-size: 12.5px; line-height: 1.7; color: var(--text);
  }
  .doc-conflict.show{ display: block; }
  .doc-conflict b{ font-weight: normal; color: var(--accent); }
  .doc-conflict .why{ color: var(--text-soft); font-size: 11.5px; display: block; margin-top: 2px; }
  .doc-conflict .acts{ margin-top: 8px; display: flex; gap: 8px; flex-wrap: wrap; }
  .doc-conflict button{
    appearance: none; -webkit-appearance: none;
    border: 1px solid var(--hairline); background: transparent; color: var(--text);
    font-family: var(--font-cn); font-size: 12px; line-height: 1;
    padding: 6px 11px; border-radius: 999px; cursor: pointer;
  }
  .doc-conflict button.primary{ border-color: var(--accent); color: var(--accent); }
  .doc-reader-close{
    background: transparent; border: 1px solid var(--field-line);
    color: var(--text); font-family: var(--font-en); font-size: 11px;
    padding: 4px 12px; border-radius: 0; cursor: pointer;
  }
  .doc-reader-close:hover{ border-color: var(--accent); color: var(--text); }
  .doc-reader-body{
    flex: 1; overflow: auto; padding: 20px 16px 60px;
    color: var(--text);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(14px, 1.8vw, 16px);
    line-height: 1.8;
    white-space: pre-wrap;
    word-break: break-word;
    outline: none;
    caret-color: var(--accent);
  }
  .doc-reader-body:focus{
    background: color-mix(in srgb, var(--bg) 95%, var(--accent) 5%);
  }
  .doc-reader-loading{
    text-align: center; padding: 40px;
    color: var(--text-faint); font-size: 13px;
  }
  .doc-charcount{
    position: absolute; bottom: 12px; right: 16px;
    font-size: 10px; font-family: var(--font-en);
    color: var(--text-faint); pointer-events: none;
    opacity: 0.6;
  }

  .floatbtn.send{
    background: var(--send-bg);
    color: var(--accent-fg);
    border-radius: 50%;
    width: clamp(30px, 4vw, 36px); height: clamp(30px, 4vw, 36px);
    transition: background .2s ease, color .2s ease;
  }
  /* 待命态:没话可发的时候整颗退成灰。蓝色只在有东西要发的时候亮起来,
     这样蓝出现的每一次都有原因,不是一块永远在那儿的家具。逾水 8.27 选的 */
  .floatbtn.send.idle{ background: var(--send-idle-bg); color: var(--send-idle-fg); }
  .send-tw{
    display:block; width: clamp(18px, 2.4vw, 21px); height: clamp(18px, 2.4vw, 21px);
    background: currentColor;
    /* 必须写长属性。iOS Safari 解析不了 -webkit-mask 的 center/contain 简写,
       整条声明被丢掉,于是蒙版没了、图标不显示(按钮空成一个圆)。8.27 */
    -webkit-mask-image: url(/chat/typewriter-mask.png);
    -webkit-mask-position: center;
    -webkit-mask-size: contain;
    -webkit-mask-repeat: no-repeat;
    mask-image: url(/chat/typewriter-mask.png);
    mask-position: center;
    mask-size: contain;
    mask-repeat: no-repeat;
  }
  .floatbtn.send:hover{ color:#ffffff; }
  .floatbtn.send:active{ transform: scale(.97); }      /* 发送键按压:轻收 3%(比通用 .floatbtn 的 .9 克制) */
  @keyframes send-go{ from{ transform: scale(.96); } to{ transform: scale(1); } }
  .floatbtn.send.send-go{ animation: send-go .22s cubic-bezier(.2,.8,.2,1); }
  @media (prefers-reduced-motion: reduce){ .floatbtn.send.send-go{ animation: none; } }
  .composer .field{
    flex:1 1 auto; display:flex; flex-direction:column; align-items:stretch; gap:0;
    /* min-height 比内容高的时候,富余的空间默认全堆在下面,整行就往上飘。
       居中之后上下各分一半,按钮才落在框的正中。8.27 */
    justify-content: center;
    background: var(--field-bg);
    /* 不用边框:这界面本来是平的,气泡和房间名都没有投影。
       框的形状交给填充色,不交给线。圆角是形状,不是高度。8.27 */
    border: 1px solid transparent;
    border-radius: 21px;
    padding: var(--field-pad) var(--field-pad) var(--field-pad) clamp(10px, 1.5vw, 15px);
    min-height: clamp(42px, 5.5vw, 56px);
    transition: background-color .2s ease;
  }
  /* 点进去不要冒出边框:边框是上一版留下的,现在框本来就没有线。
     聚焦改成填充色微微变浅,能感觉到,但不长出一圈线。8.27 */
  .composer .field:focus-within{ border-color: transparent; background: var(--field-bg-focus); }
  .composer .field-row{
    /* flex-end:文字变长的时候回形针和发送键沉到底,跟最后一行齐平。 */
    display:flex; align-items:flex-end; gap: clamp(4px, 0.8vw, 10px);
  }
  /* 框内的按钮必须一样高。回形针原本是 clamp(36,5vw,48)、发送键是 clamp(30,4vw,36),
     底对齐之后矮的那个就吊在下面——单行时看着发送键偏低。8.27 */
  .composer .field-row .floatbtn{
    width: var(--field-line-h); height: var(--field-line-h);
  }
  .composer .field-row .floatbtn svg{
    width: clamp(19px, 2.5vw, 22px); height: clamp(19px, 2.5vw, 22px);
  }
  .composer textarea:focus,
  .composer textarea:focus-visible,
  .composer button:focus-visible{
    outline: none;
  }
  /* 电脑端的滚动条默认又宽又带箭头。改成 iPhone 那种细条:
     只有一根半透明的拇指,没有轨道、没有箭头。8.27 */
  .composer textarea{
    scrollbar-width: thin;
    scrollbar-color: var(--scroll-thumb) transparent;
  }
  .composer textarea::-webkit-scrollbar{ width: 5px; height: 5px; }
  .composer textarea::-webkit-scrollbar-track{ background: transparent; }
  .composer textarea::-webkit-scrollbar-thumb{
    background: var(--scroll-thumb); border-radius: 3px;
  }
  .composer textarea::-webkit-scrollbar-thumb:hover{ background: var(--scroll-thumb-hover); }
  .composer textarea::-webkit-scrollbar-button{ display: none; width: 0; height: 0; }
  .composer textarea::-webkit-scrollbar-corner{ background: transparent; }
  textarea{
    flex:1 1 auto; border:none; outline:none; resize:none; background:transparent;
    color: var(--text); font-family: var(--font-en), var(--font-cn); font-size: clamp(15px, 2vw, 18px); line-height:1.35;
    max-height: 40vh; padding: 6px 0; margin:0;
  }
  textarea::placeholder{ color: var(--text-faint); opacity: 1; }
  .emojibtn{
    flex:none; width: clamp(28px, 3.8vw, 38px); height: clamp(28px, 3.8vw, 38px); border:none; background:transparent;
    color: var(--accent); display:grid; place-items:center; cursor:pointer; padding:0;
    transition: transform .15s ease, color .2s ease;
  }
  .emojibtn:active{ transform: scale(.85); color: var(--text); }
  .emojibtn:hover{ color: var(--text); }
  .emojibtn svg{ width: clamp(22px, 3vw, 30px); height: clamp(22px, 3vw, 30px); display:block; }
  /* ── 回到最新 · 翻历史时来新消息,底部悬浮一枚安静的半透明胶囊 ── */
  .newmsg-pill{
    position: fixed; left: 50%; z-index: 90;
    bottom: calc(clamp(78px, 13vw, 96px) + env(safe-area-inset-bottom));
    display: inline-flex; align-items: center; gap: 5px;
    padding: 7px 14px 7px 11px; border: none; border-radius: 999px;
    background: var(--bubble-ai-bg); color: var(--text);
    border: 1px solid var(--hairline);
    font-family: var(--font-en), var(--font-cn); font-size: 13px; line-height: 1; letter-spacing: .01em;
    cursor: pointer; opacity: 0; pointer-events: none;
    transform: translate3d(-50%, calc(8px - var(--keyboard-offset, 0px)), 0);
    transition: opacity .2s ease, transform .28s cubic-bezier(.2,.8,.2,1);
  }
  .newmsg-pill.show{ opacity: 1; pointer-events: auto; transform: translate3d(-50%, calc(-1 * var(--keyboard-offset, 0px)), 0); }
  .newmsg-pill svg{ width: 15px; height: 15px; flex: none; }
  @media (prefers-reduced-motion: reduce){ .newmsg-pill{ transition: opacity .2s ease; } }
  /* 「有新版」报信条。**只报信，不自动刷** —— 她可能正在输入框里写着话，
     location.reload() 会把没发出去的字吞掉。跟着「新消息」那颗药丸的样式走，
     只是靠右、更淡：这是一句提醒，不是一件要她马上处理的事。 */
  .update-pill{
    position: fixed; right: clamp(12px, 3vw, 24px); z-index: 90;
    bottom: calc(clamp(78px, 13vw, 96px) + env(safe-area-inset-bottom));
    display: inline-flex; align-items: center; gap: 7px;
    padding: 7px 12px; border-radius: 999px;
    background: var(--bubble-ai-bg); color: var(--text-soft);
    border: 1px solid var(--hairline);
    font-family: var(--font-en), var(--font-cn); font-size: 12.5px; line-height: 1; letter-spacing: .02em;
    cursor: pointer; opacity: 0; pointer-events: none;
    transform: translate3d(0, calc(8px - var(--keyboard-offset, 0px)), 0);
    transition: opacity .2s ease, transform .28s cubic-bezier(.2,.8,.2,1);
  }
  .update-pill.show{ opacity: 1; pointer-events: auto; transform: translate3d(0, calc(-1 * var(--keyboard-offset, 0px)), 0); }
  .update-pill svg{ width: 14px; height: 14px; flex: none; color: var(--accent); }
  @media (prefers-reduced-motion: reduce){ .update-pill{ transition: opacity .2s ease; } }

  /* 站内跳转回来时先压住登录/PIN 屏，免得闪一下。这个类只是"防闪"，不是"免检"——
     真要画 PIN 屏或登录框时，showPinLock()/showLogin() 会把它摘掉（9.2 深夜：
     改成离开制之后 _nav_ts 不再免检，忘了摘就会变成"锁上了但屏是空的"）。 */
  .nav-return #login, .nav-return #pinLock { display: none !important; }

  /* ── Login ── */
  .login{
    position: fixed; inset:0; z-index: 50;
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    gap: 22px; padding: 28px;
    padding-top: calc(28px + env(safe-area-inset-top));
    background-color: var(--bg);
  }
  .login .orb{ display:none; }
  .login h1{ margin:0; font-size:34px; font-weight:400; letter-spacing:-0.02em; font-family:var(--font-en), var(--font-cn); }
  .login .sub{ margin:4px 0 0; color: var(--text-faint); font-size:11px; letter-spacing:0.08em; text-transform:uppercase; text-align:center; }
  .login form{ display:flex; flex-direction:column; gap:11px; width:100%; max-width:320px; margin-top:8px; }
  .login input{
    width:100%; padding: 10px 0; font: inherit; color: var(--text);
    background: transparent; border:none; border-bottom:1px solid var(--hairline); border-radius:0; outline:none;
    transition: border-color .2s ease; font-family:var(--font-en), var(--font-cn);
  }
  .login input:focus{ border-bottom-color: var(--accent); }
  .login button{
    width:100%; padding: 12px; font: inherit; font-weight:400; font-size:11px; letter-spacing:0.16em; text-transform:uppercase;
    background: var(--text); color: var(--bg); border:none; border-radius:0; cursor:pointer;
    font-family:var(--font-en), var(--font-cn);
  }
  .login button:active{ transform: scale(.99); }
  .login .err{ color:#c0533f; font-size:13px; min-height:16px; text-align:center; margin:0; }
  .hidden{ display:none !important; }
  .app.kb .composer{ padding-bottom: 8px; }

  /* ── Profile page (full-screen settings) ── */
  .profile-panel{
    position: fixed;
    inset: 0;
    z-index: 200;
    pointer-events: none;
    transform: translateX(100%);
    opacity: 0;
    transition: transform .34s var(--ease-soft), opacity .26s ease;
  }
  .profile-panel.open{ transform: none; opacity: 1; pointer-events: auto; }
  /* own wallpaper layer so the chat behind is fully covered, theme-matched */
  .profile-page{
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    background-color: var(--bg);
    overflow: hidden;
  }
  .profile-topbar{
    position: relative; z-index:1;
    flex: 0 0 auto;
    display:flex; align-items:center; gap:10px;
    height: calc(56px + env(safe-area-inset-top));
    padding: env(safe-area-inset-top) var(--side-pad) 0;
  }
  .profile-back{
    width:40px; height:40px; flex:none;
    border:0; border-radius:50%; padding:0; margin-left:-6px;
    background: transparent; color: var(--text);
    display:grid; place-items:center; cursor:pointer;
    transition: transform .15s ease;
  }
  .profile-back:active{ transform: scale(.9); }
  .profile-back svg{ width:24px; height:24px; display:block; }
  .profile-scroll{
    position: relative; z-index:1;
    flex: 1 1 auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding: 10px var(--side-pad) calc(30px + env(safe-area-inset-bottom));
    display:flex; flex-direction:column; gap:24px;
  }
  .profile-scroll::-webkit-scrollbar{ width:0; height:0; }

  /* identity */
  .profile-identity{
    display:grid;
    grid-template-columns: auto 1fr;
    gap:18px; align-items:center;
    padding: 4px 4px 16px;
  }
  .profile-id-copy{ min-width:0; }
  .profile-name-row{ display:flex; align-items:center; gap:9px; margin:0 0 5px; }
  .profile-name-row h3,
  #profileNameInput{
    margin:0;
    font-family: var(--font-en), var(--font-cn);
    font-size:23px; font-weight:500; letter-spacing:0;
    color: var(--text); line-height:1.1;
  }
  #profileNameInput{
    width:6.6em; max-width:62%;
    border:0; border-radius:10px;
    background: var(--seg-track); outline:none;
    padding:3px 11px; margin:-3px 0;
  }
  .profile-name-edit{
    width:26px; height:26px; flex:none;
    border:0; border-radius:50%; padding:0;
    background:transparent; color: var(--text-soft);
    display:grid; place-items:center; cursor:pointer;
    transition: color .2s ease, transform .15s ease;
  }
  .profile-name-edit:active{ transform: scale(.88); }
  .profile-name-edit svg{ width:15px; height:15px; display:block; }
  .profile-handle{
    display:flex; align-items:center; gap:7px; margin:0 0 8px;
    color: var(--text-soft);
    font-family: var(--font-en), var(--font-cn); font-size:13.5px;
  }
  .profile-bio{ margin:0; color: var(--text-soft); font-size:13px; line-height:1.55; }

  /* setting card */
  .settings-card{
    background: var(--card-bg);
    border: 1px solid var(--card-line);
    border-radius:0;
    padding:15px 16px;
  }
  .settings-label{
    display:flex; align-items:center; gap:6px;
    color: var(--text-soft); font-size:12.5px;
    margin-bottom:11px; letter-spacing:.01em;
  }
  .info-i{
    width:14px; height:14px; flex:none; border-radius:50%;
    border:1px solid var(--info-ring); color: var(--text-faint);
    display:inline-grid; place-items:center;
    font-size:9px; font-style:normal; line-height:1;
    font-family: var(--font-en), var(--font-cn);
    cursor:help; user-select:none;
  }
  .push-hint{
    margin-top:9px; font-size:11.5px; line-height:1.55;
    color: var(--text-faint); letter-spacing:.01em;
  }

  /* segmented control */
  .segmented{
    display:grid; grid-auto-flow:column; grid-auto-columns:1fr;
    gap:1px; background: var(--seg-track);
    border-radius:0; padding:0;
  }
  .segmented button{
    border:0; background: var(--card-bg); color: var(--text-soft);
    border-radius:0; padding:8px 3px;
    font: inherit; font-size: clamp(10.5px, 2.9vw, 12.5px);
    letter-spacing: 0.04em; white-space:nowrap; cursor:pointer;
    transition: background .2s ease, color .2s ease;
  }
  .segmented button.active{
    background: var(--seg-thumb); color: var(--text);
    font-weight:500;
  }

  /* context slider */
  .context-slider-wrap{ padding:4px 2px 2px; }
  .context-slider{
    -webkit-appearance:none; appearance:none;
    width:100%; height:4px; border-radius:999px;
    background: var(--slider-track);
    outline:none; cursor:pointer; margin:6px 0;
  }
  .context-slider::-webkit-slider-thumb{
    -webkit-appearance:none; appearance:none;
    width:21px; height:21px; border-radius:50%;
    background: var(--slider-thumb);
    border:2.5px solid var(--card-bg);
    box-shadow: 0 3px 10px rgba(24,46,67,.26); cursor:pointer;
  }
  .context-slider::-moz-range-thumb{
    width:21px; height:21px; border-radius:50%;
    background: var(--slider-thumb);
    border:2.5px solid var(--card-bg);
    box-shadow: 0 3px 10px rgba(24,46,67,.26); cursor:pointer;
  }
  .context-scale{
    display:flex; justify-content:space-between; margin-top:9px;
    color: var(--text-faint);
    font-family: var(--font-en), var(--font-cn); font-size:11.5px;
  }

  /* action rows */
  .action-row{
    display:flex; align-items:center; gap:13px;
    width:100%; text-align:left;
    background: var(--card-bg);
    border:1px solid var(--card-line);
    border-radius:0; padding:13px 16px; color: var(--text); font: inherit; cursor:pointer;
    transition: background .18s ease, transform .12s ease;
  }
  button.action-row:active{ background: var(--row-press); transform: scale(.992); }
  .action-icon{ width:21px; height:21px; flex:none; color: var(--accent); display:grid; place-items:center; }
  .action-icon svg{ width:19px; height:19px; display:block; }
  .action-name{ font-family: var(--font-en), var(--font-cn); font-size:14.5px; color: var(--text); flex:none; }
  .action-value{
    margin-left:auto; color: var(--text-soft); font-size:12.5px;
    text-align:right; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  .action-chevron{ flex:none; color: var(--text-faint); display:grid; place-items:center; margin-left:2px; }
  .action-chevron svg{ width:16px; height:16px; display:block; }
  .action-row.status{ cursor:default; }
  .action-row.status .status-main{ margin-left:auto; display:flex; flex-direction:column; align-items:flex-end; gap:2px; min-width:0; }
  .status-used{ color: var(--text); font-family: var(--font-en), var(--font-cn); font-size:13px; }
  .status-sid{ display:flex; align-items:center; gap:6px; color: var(--text-faint); font-size:11.5px; font-family: ui-monospace, "SF Mono", Menlo, monospace; }
  .status-copy{
    width:24px; height:24px; flex:none; border:0; border-radius:8px; padding:0;
    background:transparent; color: var(--text-soft);
    display:grid; place-items:center; cursor:pointer;
    transition: color .2s ease, transform .15s ease;
  }
  .status-copy:active{ transform: scale(.85); }
  .status-copy svg{ width:15px; height:15px; display:block; }

  /* toast */
  /* 9.11：`.high` 是给输入区那边的提示用的 —— 普通 toast 蹲在 40px 高度上，
     她手机上软键盘一抬，那一格正好被键盘盖住（她第一次真机测语音时
     两条 502 的失败提示就是这么"没看见"的）。
     `--composer-zone` 是这一页本来就在维护的输入区高度，借它抬到输入框上面。 */
  .toast.high{ bottom: calc(var(--composer-zone, 40px) + 18px + env(safe-area-inset-bottom)); }
  .toast{
    position: fixed; left:50%; bottom: calc(40px + env(safe-area-inset-bottom));
    transform: translate(-50%, 12px); z-index:300;
    background: var(--bubble-human-bg); color: var(--text); font-size:14px;
    padding:10px 18px; border-radius:0;
    border: 1px solid var(--hairline);
    opacity:0; pointer-events:none;
    transition: opacity .24s ease, transform .24s ease;
  }
  .toast.show{ opacity:1; transform: translate(-50%, 0); }

  @media (max-width: 520px){
    :root{
      --side-pad: 16px;
      --header-h: 56px;
    }
    .peerpill{ padding: 0; margin-left: 40px; }
    .peerpill .name{ font-size: 14px; }
    .peerpill .status{ font-size: 11px; }
    /* 9.30 ⑥：手机上状态挪到名字下面一行。同一行时 "///typing"+三个点(约 75px)
       直接从名字里扣宽度，390 以下 typing 一出来名字就打省略号(逾水换新机截图)。
       叠两行后名字独占一行，状态再长也不跟它抢。两行共 ~29px，56px 顶栏装得下。
       点跟名字一行，状态对齐名字左边缘(第 2 列)，不对齐点。 */
    .peerpill{
      display: grid;
      grid-template-columns: auto minmax(0, auto);
      grid-template-rows: auto auto;
      align-items: center;
      column-gap: 6px; row-gap: 0;
      /* 1.15 装不下 Special Elite 的下伸部分：状态在最底一行，"typing" 的 p/y/g
         被 peerpill 的 overflow:hidden 切掉半截。1.3 → 两行共 ~33px，仍在 38px 内容区里。 */
      line-height: 1.3;
    }
    /* 点坐名字的基线上（跟原来同一行时 align-items:baseline 的效果一样），
       用 center 的话这个字体会让点比字高出 2px。 */
    .ai-dot{ grid-column: 1; grid-row: 1; align-self: baseline; }
    .peerpill .name{ grid-column: 2; grid-row: 1; align-self: baseline; }
    .peerpill .status{ grid-column: 2; grid-row: 2; justify-self: start; }
    .backbtn{ left: 16px; width: 32px; height: 32px; }
    .backbtn svg{ width: 20px; height: 20px; }
    .header-actions{ margin-right: 0; gap: 8px; height:32px; }
    .topbtn{ width:30px; height:30px; }
    .topbtn svg{ width:19px; height:19px; }
    .topbtn.more svg{ width:17px; height:17px; }
    /* 9.30 ⑥：320 那一档，状态挪走以后名字还差 11px。收图标之间的空：
       gap 8→4、按钮热区 30→28；图标本身 19px 不动，看上去只是挨近了一点。 */
    @media (max-width: 359px){
      .header-actions{ gap: 4px; }
      .topbtn{ width: 28px; height: 28px; }
    }
    .row{ margin-top: 18px; }
    .row.grouped{ margin-top: 8px; }
    /* 手机上竖向 padding 收紧成定值；左右仍然走 token —— 这条 media 才是手机上
       真正生效的那条，如果这里写死 13px，--bubble-pad-x 就是个只对桌面有效的假 token。
       ≤520px 时 clamp(13px,1.65vw,18px) 本来就恒等于 13px，换过来一个像素不差。 */
    /* ≤520px 这一条才是她手机上真正生效的行高（13.5 × 1.56 = 21.06px）——
       上面那条 clamp 的 1.58 在这儿被盖掉了。削头案要钉的是这一条。 */
    .bubble{ max-width: min(63vw, 520px); font-size: 13.5px;
      line-height: 1.56;
      line-height: round(1.56em, 1px);
      padding: 8px var(--bubble-pad-x) 8px; }
    .row.ai .bubble{ max-width: min(82vw, 600px); }
    .meta{ font-size: 11px; margin-left: 8px; }
    .day{ font-size: 12px; }
    .new-divider{ font-size: 12px; margin: 14px 0 10px; }
    .composer{ gap: 5px; padding-left: 16px; padding-right: 16px; }
    .floatbtn{ width: 36px; height: 36px; }
    .floatbtn svg{ width: 20px; height: 20px; }
    .composer .field{ min-height: 42px; padding: var(--field-pad) var(--field-pad) var(--field-pad) 12px; }
    /* 这三个是一套：改了上面 .floatbtn 的 36px，--tap-size 要跟着改。 */
    .composer{ --field-line-h: calc(15px * 1.35 + 12px); --field-pad: 4px; --tap-size: 36px; }
    textarea{ font-size: 15px; }
    .emojibtn{ width: 28px; height: 28px; }
    .emojibtn svg{ width: 22px; height: 22px; }
    .row.think{ padding-right: 0; }
    .think-block{ width: min(82vw, 600px); max-width: min(82vw, 600px); }
    .think-block.open{ width: 100%; max-width: 100%; }
    .row.think.think-open{ padding-left: 0; padding-right: 0; }
    .think-caption{ font-size: 12.5px; letter-spacing: .07em; }
    .think-body{ margin-top: 12px; }
    .think-text{ width: min(86%, 360px); font-size: 12px; line-height: 1.66; }
    .profile-identity{ gap:15px; padding-bottom:14px; }
    .profile-name-row h3, #profileNameInput{ font-size:21px; }
    .profile-handle{ font-size:13px; }
    .profile-bio{ font-size:12.5px; }
    .settings-card{ padding:14px 15px; }
    .action-row{ padding:12px 15px; }
    .profile-scroll{ gap:22px; }
  }

  /* ── Menu (hub / home page · 从聊天页左箭头进入) ── */
  .menu-panel{
    position: fixed; inset: 0; z-index: 190;
    pointer-events: none;
    transform: translateX(-100%);
    opacity: 0;
    transition: transform .38s var(--ease-soft), opacity .28s ease;
  }
  .menu-panel.open{ transform: none; opacity: 1; pointer-events: auto; }
  .menu-page{
    position: absolute; inset: 0;
    display: flex; flex-direction: column;
    background-color: var(--bg);
    overflow: hidden;
  }
  .menu-scroll{
    position: relative; z-index: 1;
    flex: 1 1 auto; overflow-y: auto;
    -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
    display: flex; flex-direction: column;
    padding: calc(env(safe-area-inset-top) + clamp(18px, 4vw, 30px)) clamp(26px, 7vw, 54px) calc(env(safe-area-inset-bottom) + clamp(20px, 5vw, 30px));
  }
  .menu-scroll::-webkit-scrollbar{ width:0; height:0; }

  /* header: 玻璃液态日夜 toggle(左) · PRIVATE SPACE(右) */
  .menu-head{
    flex: 0 0 auto;
    display:flex; align-items:flex-start; justify-content:space-between;
    margin-bottom: clamp(14px, 4vw, 24px);
  }
  /* (主题切换已移至设置页 #themeSeg;原菜单页玻璃 toggle 的 CSS 已随标记一并移除) */
  .menu-private{
    text-align:right; line-height:1.55;
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(8px, 2.1vw, 9.5px); letter-spacing:.12em;
    color: var(--text-soft); text-transform:none;
  }
  .menu-private::after{
    content:""; display:block; margin-left:auto; margin-top:8px;
    width: clamp(20px, 5.5vw, 26px); height:1px; background: var(--text-faint); opacity:.6;
  }

  /* hero: app title · since · days */
  .menu-hero{ flex: 0 0 auto; margin-bottom: clamp(16px, 4vw, 28px); }
  .menu-title{
    font-family: var(--font-en);
    font-weight: 400;
    font-size: clamp(28px, 9vw, 48px);
    line-height: 1.1;
    color: var(--text);
    margin: 0 0 clamp(16px, 4.4vw, 26px);
    letter-spacing: 0.02em;
  }
  .menu-since{
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(11px, 2.9vw, 13.5px);
    letter-spacing:.14em; color: var(--text-soft);
    margin: 0 0 clamp(8px, 2.2vw, 12px);
  }
  .menu-days{
    display:flex; align-items:baseline; gap: clamp(8px, 2.6vw, 12px);
    font-family: var(--font-en), var(--font-cn); color: var(--text);
  }
  .menu-days .num{ font-size: clamp(30px, 8.4vw, 48px); font-weight:500; line-height:1; letter-spacing:.01em; }
  .menu-days .lab{ font-size: clamp(12px, 3.1vw, 15px); letter-spacing:.13em; color: var(--text-soft); }

  /* calendar in menu */
  .menu-cal{ flex: 0 0 auto; margin-bottom: 8px; }
  .menu-cal-head{
    display:flex; align-items:center; justify-content:center; gap:16px;
    margin-bottom: 10px;
  }
  .menu-cal-arrow{
    background:none; border:none; color:var(--accent); font-size:20px;
    cursor:pointer; padding:4px 10px; line-height:1;
  }
  .menu-cal-label{
    font-family: var(--font-en); font-size: 14px; color:var(--text-soft);
    min-width:80px; text-align:center; letter-spacing:.08em;
  }
  .menu-cal-weekdays{
    display:grid; grid-template-columns: repeat(7,1fr);
    text-align:center; color:var(--text-faint); font-size:11px;
    padding-bottom:4px; font-family: var(--font-en), var(--font-cn);
  }
  .menu-cal-grid{
    display:grid; grid-template-columns: repeat(7,1fr); gap:2px;
  }
  .mcal-cell{
    aspect-ratio:1; display:flex; flex-direction:column;
    align-items:center; justify-content:center;
    font-size:13px; cursor:default; border-radius:4px;
    position:relative; transition: background 150ms;
    font-family: var(--font-en);
  }
  .mcal-cell.has-data{ cursor:pointer; color:var(--text); }
  .mcal-cell.has-data:active{ background:var(--row-press); }
  .mcal-cell.has-data::after{
    content:''; position:absolute; bottom:3px;
    width:4px; height:4px; border-radius:50%; background:var(--accent);
  }
  .mcal-cell.empty{ color:var(--text-faint); }
  .mcal-cell.other-month{ color:var(--text-faint); opacity:.4; }
  .mcal-cell.selected{ background:var(--accent); color:var(--accent-fg); }
  .mcal-cell.selected::after{ background:var(--accent-fg); }
  .mcal-cell.today{ font-weight:700; }
  .mcal-cell .mcal-cnt{ font-size:8px; color:var(--text-faint); margin-top:-2px; }
  .mcal-cell.selected .mcal-cnt{ color:var(--accent-fg); opacity:.7; }
  /* 月份快选（点年月标题进入年视图） */
  .menu-cal-grid.months{ grid-template-columns: repeat(4,1fr); gap:6px; }
  .mcal-cell.mcal-month{
    aspect-ratio:auto; padding:10px 0; font-size:13px;
    font-family: var(--font-en), var(--font-cn);
  }
  .mcal-cell.mcal-month.has-data::after{ display:none; }
  .mcal-cell.mcal-month.today{ background:var(--accent); color:var(--accent-fg); font-weight:400; }

  @media (min-width: 768px) {
    .menu-page { max-width: 520px; }
    .mcal-cell { aspect-ratio: 1.3; }
  }

  /* 月刊入口（10.21）：月历下面那块当月的布。布是剪下来的一角，不加框、不加底；字照日历那一套小字 */
  .menu-wrapped{ flex: 0 0 auto; display:flex; align-items:flex-start; gap: clamp(12px, 3.6vw, 18px); margin: 6px 0 14px; }
  .menu-wrapped:not(.shown){ margin:0; }
  .menu-wrapped.open{ cursor:pointer; -webkit-tap-highlight-color:transparent; }
  .menu-wrapped.open:active .mwr-swatch{ opacity:.7; }
  .mwr-swatch{ flex:none; display:block; }
  .mwr-txt{ display:flex; flex-direction:column; gap:3px; padding-top:2px; font-family: var(--font-en), var(--font-cn); font-size:11px; line-height:1.5; letter-spacing:.08em; color:var(--text-soft); }
  /* 两端对齐（10.21 她定 A，122568）：布卡贴左版心，字组推到右边、组内右对齐。
     右缘不是网格框，是「日」那列数字的视觉右边：格子宽＝(100% − 6 个 2px 间隙)/7，数字在格子里居中，
     两位数宽 2ch（日历数字 13px var(--font-en)）→ 右边让出 半格 − 1ch。字号或间隙改了这里跟着改 */
  .menu-wrapped{ justify-content:space-between; font-family: var(--font-en); font-size:13px; padding-right: calc((100% - 12px) / 14 - 1ch); }
  .mwr-txt{ align-items:flex-end; text-align:right; }
  .mwr-no{ color:var(--mwr-col); font-size:12px; }
  :root[data-theme="dark"] .mwr-no{ color:var(--mwr-col-dark); }
  /* 创刊号的门：since 那行里的三个字，样式跟那行一样；点按区域往外撑（内边距＋等量负外边距，不挪版面） */
  .mwr-founding{ cursor:pointer; padding: 12px 8px; margin: -12px -8px; -webkit-tap-highlight-color:transparent;
    /* 暗门要找得回来（122473）：一条很淡的点线，不抢 since 那行。color-mix 不认的老内核退回 --text-faint */
    text-decoration: underline dotted 1px; text-underline-offset: 3px;
    text-decoration-color: var(--text-faint); text-decoration-color: color-mix(in srgb, currentColor 40%, transparent); }
  .mwr-founding:active{ opacity:.6; }

  /* day detail in menu */
  .menu-day-view{
    flex: 0 0 auto; padding: 0; overflow-y:auto; max-height:40vh;
  }
  .menu-day-view:empty{ display:none; }
  .mdv-head{
    display:flex; align-items:center; gap:8px;
    margin-bottom:8px; padding-top:8px;
    border-top:1px solid var(--hairline);
  }
  .mdv-date{ font-size:14px; font-weight:600; font-family:var(--font-en); }
  .mdv-count{ font-size:11px; color:var(--text-soft); }
  .mdv-sum-btn{
    margin-left:auto; background:var(--accent); color:var(--accent-fg);
    border:none; border-radius:4px; padding:3px 10px; font-size:11px;
    cursor:pointer; font-family:inherit;
  }
  .mdv-sum-btn:disabled{ opacity:.5; cursor:default; }
  .mdv-summary{
    background:var(--card-bg); border:1px solid var(--card-line);
    border-radius:6px; padding:10px 12px; margin-bottom:10px;
    font-size:12px; line-height:1.6; color:var(--text);
  }
  .mdv-summary .mdv-slabel{
    font-size:10px; color:var(--text-faint); margin-bottom:4px;
    text-transform:uppercase; letter-spacing:.5px;
  }
  .mdv-msgs{ display:flex; flex-direction:column; gap:2px; }
  .mdv-row{ display:flex; gap:6px; align-items:flex-start; padding:1px 0; }
  .mdv-time{ font-size:10px; color:var(--text-faint); min-width:32px; padding-top:1px; flex-shrink:0; font-family:var(--font-en); }
  .mdv-who{ font-size:10px; font-weight:600; min-width:36px; padding-top:1px; flex-shrink:0; }
  .mdv-who.human{ color:var(--accent); }
  .mdv-who.ai{ color:var(--text-soft); }
  .mdv-txt{ font-size:12px; line-height:1.5; word-break:break-word; flex:1; padding:1px 0; }

  /* dock at bottom of menu */
  /* 9.3：第 8 个牌子（ARCADE）加回来之后一排放不下了。
     做法：整条 dock 横向可滚，每个牌子固定成 **视宽的 1/7.5** ——
     于是任何屏宽下都是"前 7 个整的 + 第 8 个半个"。
     露半个**就是**滚动暗示，所以不加箭头、不加渐隐层（她的原话：less is more）。
     用 1/7.5 而不是写死 px：428 是她的屏，但不是唯一的屏。 */
  .menu-dock{
    flex: 0 0 auto;
    display:flex; align-items:center;
    padding: 12px 0 calc(env(safe-area-inset-bottom) + 8px);
    border-top: 1px solid var(--hairline);
    margin-top: 0;
    overflow-x:auto; overflow-y:hidden;
    scrollbar-width:none; -webkit-overflow-scrolling:touch;
    overscroll-behavior-x:contain;
  }
  .menu-dock::-webkit-scrollbar{ display:none; }
  .dock-item{
    flex:0 0 calc(100% / 7.5);       /* 7 个整 + 半个 */
    display:flex; flex-direction:column; align-items:center; gap:4px;
    background:none; border:none; color:var(--text-soft);
    cursor:pointer; padding:6px 4px; font:inherit;
    transition: color .15s ease;
  }
  .dock-item:active{ color:var(--accent); }
  .dock-item svg{ width:20px; height:20px; display:block; }
  .dock-item span{
    font-family:var(--font-en),var(--font-cn);
    font-size:9px; letter-spacing:.08em; text-transform:uppercase;
  }

  /* ── 9.6 电脑端（她提：这排牌子在电脑上拖不动）─────────────────────
     触屏能滑，鼠标既没有横滚也没有拖拽，那半个露出来的牌子对鼠标等于没说话。
     **够宽就别滚**：八个牌子平分一行；1/7.5 那条只在窄窗口里还管用。
     窄窗口 + 鼠标：给拖拽和滚轮横滚，并且把"这边还有"画出来一层。
     手机这一路完全不进（规则都在 min-width / hover:hover 里）。 */
  .dock-wrap{ position:relative; flex:0 0 auto; }
  .dock-wrap::before, .dock-wrap::after{
    content:""; position:absolute; top:1px; bottom:0; width:34px; z-index:2;
    pointer-events:none; opacity:0; transition:opacity .18s ease;
  }
  .dock-wrap::before{ left:0;  background:linear-gradient(to right, var(--bg), transparent); }
  .dock-wrap::after { right:0; background:linear-gradient(to left,  var(--bg), transparent); }
  /* 摊开的下限：八个牌子各要 ~65px 才不挤（9px 的 LIBRARY/ARCADE 是最宽的两块），
     8×65 = 520 —— 560 留一点富余。手机竖屏 390/428 在这条线以下，走的还是老路。
     注意 .menu-page 在 ≥768 上被压到 520 宽，所以电脑上这排的实际宽度是 520 不是满屏。 */
  @media (min-width: 560px){
    .menu-dock{ overflow-x:hidden; }
    .dock-item{ flex:1 1 0; }          /* 摊开：八个平分，不滚 */
  }
  @media (hover: hover){
    .menu-dock{ cursor:grab; }
    .menu-dock.dragging{ cursor:grabbing; }
    .menu-dock.dragging .dock-item{ pointer-events:none; }
    .dock-wrap.can-l::before{ opacity:1; }
    .dock-wrap.can-r::after { opacity:1; }
  }

  /* footer */
  .menu-foot{
    flex: 1 1 auto;
    display:flex; align-items:flex-end; justify-content:center;
    text-align:center;
    padding-top: clamp(22px, 6vw, 40px);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(10.5px, 2.7vw, 12.5px); font-style:italic;
    letter-spacing:.05em; color: var(--text-faint);
  }

  /* ── Voice call (speaking overlay · 假按键 / 纯 UI,后端以后接) ── */
  .call-overlay{
    position: fixed; inset: 0; z-index: 150;
    pointer-events: none;
  }
  .call-scrim{
    position: absolute; inset: 0;
    background: var(--bg);
    opacity: 0;
    pointer-events: none;
    transition: opacity .3s ease;
  }
  .call-overlay.open .call-scrim{ opacity: 1; }
  .call-sheet{
    position: absolute; inset: 0;
    display: flex; flex-direction: column; align-items: center;
    justify-content: flex-start;
    padding: calc(env(safe-area-inset-top) + clamp(50px, 10vw, 84px)) var(--side-pad) calc(env(safe-area-inset-bottom) + 26px);
    text-align: center;
    background-color: var(--bg);
    opacity: 0; pointer-events: none;
    transform: translateY(12px);
    transition: opacity .34s var(--ease-soft), transform .34s var(--ease-soft);
    overflow: hidden;
  }
  .call-overlay.open .call-sheet{ opacity: 1; pointer-events: auto; transform: translateY(0); }

  /* ── 声纹：通话时左右两翼的音量条（10.25 中间那颗头像圆拆了，见 index.html「固定聊天背景」注释） ── */
  .call-vu-stage{
    display: flex; align-items: center; justify-content: center;
    width: 100%;
    gap: clamp(8px, 2.2vw, 16px);
    margin-top: clamp(4px, 3vh, 30px);
    flex-shrink: 0;
  }
  .call-vu{
    --vu-level: 0;
    flex: 1 1 0; min-width: 0;
    height: clamp(70px, 17vw, 100px);
    display: flex; align-items: center;
    gap: clamp(2.5px, 0.8vw, 4px);
  }
  .call-vu-left{ flex-direction: row-reverse; }
  .call-vu i{
    flex: 0 0 auto;
    width: clamp(2px, 0.65vw, 3px);
    height: calc(100% * var(--bar, .4) * (0.46 + 0.54 * var(--vu-level)));
    min-height: 2px;
    border-radius: 99px;
    background: var(--accent);
    opacity: calc(var(--bar-op, .4) * (0.6 + 0.4 * var(--vu-level)));
    animation: callVuIdle var(--vu-dur, 1.6s) var(--ease-soft) infinite alternate;
    animation-delay: var(--vu-delay, 0s);
    will-change: transform, opacity;
  }
  @keyframes callVuIdle{ from{ transform: scaleY(.55); } to{ transform: scaleY(1); } }
  @media (prefers-reduced-motion: reduce){ .call-vu i{ animation: none; } }

  /* ── Name / status / timer ── */
  .call-name{
    margin-top: clamp(18px, 4vw, 28px);
    color: var(--text);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(27px, 7vw, 36px);
    font-weight: 500;
    line-height: 1.2;
  }
  .call-status{
    display: inline-flex; align-items: center; gap: 11px;
    color: var(--think-label);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(13px, 1.8vw, 15.5px);
    letter-spacing: .16em;
    line-height: 1;
    margin-top: 8px;
  }
  .call-status .call-star{ color: var(--think-flourish); display: grid; place-items: center; }
  .call-status .call-star svg{ width: 16px; height: 16px; display: block; }
  .call-timer{
    font-family: var(--font-en), var(--font-cn);
    font-variant-numeric: tabular-nums;
    font-size: clamp(19px, 5vw, 25px);
    font-weight: 500;
    letter-spacing: .04em;
    line-height: 1;
    color: var(--text-soft);
    margin-top: 9px;
  }

  /* ── Quote (the AI's last spoken line) ── */
  .call-quote{
    flex: 1 1 auto;
    min-height: 0;                 /* 让 flex 能真正约束它,长文本不再撑破/盖住头像与字幕 */
    display: flex; align-items: center; justify-content: center;
    width: min(88vw, 400px);
    padding: clamp(8px, 2vw, 18px) clamp(8px, 2vw, 16px);
    overflow: hidden;              /* 兜底:绝不溢出到相邻区域 */
  }
  .call-quote-mark{
    color: var(--text-faint);
    font-family: var(--font-en);
    font-size: clamp(40px, 9.5vw, 56px);
    line-height: 1;
    user-select: none;
    flex-shrink: 0;
    align-self: flex-start;
    opacity: .5;
  }
  .call-quote-mark:last-child{ align-self: flex-end; }
  .call-quote-text{
    max-height: 100%;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: none;                 /* Firefox 隐藏滚动条 */
    color: var(--text);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(20px, 5.2vw, 27px);
    line-height: 1.52;
    text-align: center;
    padding: 0 clamp(6px, 1.5vw, 12px);
    overflow-wrap: break-word;
  }
  .call-quote-text::-webkit-scrollbar{ display: none; }   /* WebKit 隐藏滚动条 */
  /* 仅当文字真的溢出时(由 JS 加 .clip)才底部渐隐(顶部保持清晰,因为总是从头读起,
     渐隐只用来暗示"下面还有,全文在字幕条里") */
  .call-quote-text.clip{
    -webkit-mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 22px), transparent 100%);
            mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 22px), transparent 100%);
  }
  .call-quote-text.hint{
    color: var(--text-soft);
    font-size: clamp(18px, 4.8vw, 24px);
  }
  .call-quote-text.lyric-mode{
    font-size: clamp(14px, 3.8vw, 19px);
    text-align: center;
  }
  .lyric-line{
    display: block;
    opacity: 0.28;
    transition: opacity 0.45s ease, transform 0.45s ease;
    transform: scale(0.96);
    padding: 3px 0;
  }
  .lyric-line.active{
    opacity: 1;
    transform: scale(1);
  }

  /* ── Transcript section ── */
  .call-transcript-section{
    width: min(92vw, 440px);
    display: flex; flex-direction: column; align-items: center;
    gap: 10px;
    flex-shrink: 0;
  }
  .call-transcript-label{
    position: relative;
    display: flex; align-items: center; justify-content: center;
    width: 100%;
    color: var(--text-faint);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(12px, 1.5vw, 13px);
    letter-spacing: .06em;
  }
  .call-transcript-cap{ display: inline-flex; align-items: center; gap: 7px; }
  .call-transcript-cap::before{
    content: ""; display: inline-block;
    width: 16px; height: 14px;
    background: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round'%3E%3Cline x1='4' y1='9' x2='4' y2='15'/%3E%3Cline x1='9' y1='4.5' x2='9' y2='19.5'/%3E%3Cline x1='14' y1='7.5' x2='14' y2='16.5'/%3E%3Cline x1='19' y1='10.5' x2='19' y2='13.5'/%3E%3C/svg%3E") center/contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round'%3E%3Cline x1='4' y1='9' x2='4' y2='15'/%3E%3Cline x1='9' y1='4.5' x2='9' y2='19.5'/%3E%3Cline x1='14' y1='7.5' x2='14' y2='16.5'/%3E%3Cline x1='19' y1='10.5' x2='19' y2='13.5'/%3E%3C/svg%3E") center/contain no-repeat;
  }
  /* 「我在想 …」跳动点(引号区,贴心提示) */
  .call-dots{ display: inline-flex; align-items: center; gap: 4px; }
  .call-dots i{
    width: 4px; height: 4px; border-radius: 50%;
    background: currentColor;
    animation: callTypingDot 1.25s ease-in-out infinite;
  }
  .call-dots i:nth-child(2){ animation-delay: .16s; }
  .call-dots i:nth-child(3){ animation-delay: .32s; }
  @keyframes callTypingDot{ 0%,70%,100%{ transform: translateY(0); opacity:.4; } 35%{ transform: translateY(-3px); opacity:1; } }
  .call-dots-quote{ margin-left: 10px; vertical-align: middle; gap: 5px; }
  .call-dots-quote i{ width: 6px; height: 6px; }
  .call-transcript-bar{
    width: 100%;
    min-height: 2em;
    padding: clamp(11px, 2.2vw, 15px) clamp(18px, 4vw, 26px);
    border-radius: 0;
    background: var(--field-bg);
    border: 1px solid var(--call-glass-edge);
    color: var(--text);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(14.5px, 2vw, 16.5px);
    line-height: 1.6;
    text-align: center;
    overflow-wrap: break-word;
    max-height: clamp(72px, 16vh, 132px);   /* 长字幕也封顶,不再把控制键/挂断顶出屏外 */
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: none;
  }
  .call-transcript-bar::-webkit-scrollbar{ display: none; }
  .call-transcript-bar .caret{
    display: inline-block; width: 1px; height: 1.04em; margin-left: 2px;
    background: currentColor; opacity: .55;
    vertical-align: -0.14em;
    animation: callCaret 1s steps(1) infinite;
  }
  @keyframes callCaret{ 0%,49%{ opacity:.55; } 50%,100%{ opacity:0; } }

  /* ── Controls row ── */
  .call-controls{
    display: flex; align-items: flex-start; justify-content: center;
    gap: clamp(30px, 9vw, 50px);
    margin-top: clamp(22px, 4vw, 34px);
    flex-shrink: 0;
  }
  .call-ctrl-btn{
    display: flex; flex-direction: column; align-items: center; gap: 9px;
    border: 0; padding: 0; cursor: pointer;
    background: transparent;
    color: var(--text-soft);
    -webkit-tap-highlight-color: transparent;
  }
  .call-ctrl-btn > svg{
    width: clamp(58px, 14.5vw, 70px); height: clamp(58px, 14.5vw, 70px);
    padding: clamp(15px, 3.4vw, 19px);
    border-radius: 50%;
    background: linear-gradient(157deg, var(--call-glass-top), var(--call-glass-bot));
    border: 1px solid var(--call-glass-edge);
    -webkit-backdrop-filter: blur(20px) saturate(1.6);
    backdrop-filter: blur(20px) saturate(1.6);
    box-shadow: 0 8px 22px rgba(40,60,78,.10), inset 0 1px 0 var(--call-glass-hi);
    transition: background .15s ease, transform .12s ease, color .15s ease;
  }
  .call-ctrl-btn:active > svg{ transform: scale(.92); }
  .call-ctrl-btn > span{
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(12px, 1.6vw, 13.5px);
    color: var(--text-faint);
  }
  .call-ctrl-btn.active > svg{
    background: var(--accent);
    color: var(--accent-fg);
    border-color: transparent;
  }

  /* ── Hangup ── */
  .call-hangup{
    margin-top: clamp(20px, 4vw, 32px);
    width: clamp(64px, 15.5vw, 74px); height: clamp(64px, 15.5vw, 74px);
    border: 0; border-radius: 50%; padding: 0; cursor: pointer;
    background: var(--call-hangup-bg);
    color: #fff;
    display: grid; place-items: center;
    box-shadow: 0 10px 26px var(--call-hangup-shadow);
    transition: transform .15s ease, filter .2s ease;
    flex-shrink: 0;
  }
  .call-hangup:hover{ filter: brightness(1.05); }
  .call-hangup:active{ transform: scale(.92); }
  .call-hangup svg{ width: clamp(26px, 5.6vw, 31px); height: clamp(26px, 5.6vw, 31px); display: block; transform: rotate(135deg); }

  /* ── Minimize (top-left) ── */
  .call-minimize-top{
    position: absolute;
    top: calc(env(safe-area-inset-top) + clamp(10px, 2vw, 16px));
    left: clamp(10px, 2vw, 16px);
    width: 36px; height: 36px;
    border: 0; border-radius: 50%; padding: 0; cursor: pointer;
    background: var(--card-bg);
    border: 1px solid var(--card-line);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
    color: var(--text-soft);
    display: grid; place-items: center;
    z-index: 2;
    -webkit-tap-highlight-color: transparent;
    transition: background .15s ease, transform .12s ease;
  }
  .call-minimize-top:active{ transform: scale(.9); }
  .call-minimize-top svg{ width: 18px; height: 18px; display: block; }

  /* ── Hide old wave (no longer visible) ── */
  .call-wave{ display: none; }

  /* ── Call mini-bar (WeChat-style green strip) ── */
  .call-mini{
    display: none;
    position: fixed;
    left: 0; right: 0;
    top: calc(var(--header-h) + env(safe-area-inset-top));
    height: 34px;
    background: var(--accent);
    color: var(--accent-fg);
    font-family: var(--font-en), var(--font-cn);
    font-size: 13px;
    align-items: center; justify-content: center;
    gap: 8px;
    z-index: 152;
    cursor: pointer;
    pointer-events: auto;
    -webkit-tap-highlight-color: transparent;
    transition: opacity .2s ease;
  }
  .call-mini:active{ filter: brightness(.88); }
  .call-mini-dot{
    width: 7px; height: 7px;
    border-radius: 50%;
    background: currentColor;
    animation: callMiniPulse 1.4s ease-in-out infinite;
  }
  @keyframes callMiniPulse{
    0%,100%{ opacity: 1; }
    50%{ opacity: .35; }
  }
  .call-mini-timer{
    font-family: var(--font-en);
    font-variant-numeric: tabular-nums;
    letter-spacing: .04em;
  }
  .call-overlay.open.minimized .call-scrim{ opacity: 0; pointer-events: none; }
  .call-overlay.open.minimized .call-sheet{ opacity: 0; pointer-events: none; transform: translateY(12px); }
  .call-overlay.open.minimized .call-mini{ display: flex; }
  .call-overlay.open.minimized{ pointer-events: none; }

  /* 来电:全屏(与通话中同一张画布,接听后无缝衔接,不再是顶部小卡片) */
  .incoming-call-overlay{ z-index: 151; }
  .incoming-call-overlay .call-scrim{ background: var(--call-scrim); }
  .incoming-call-overlay .call-sheet{
    justify-content: flex-start;
    gap: clamp(12px, 2.6vw, 20px);
  }
  /* caption 行(✦ incoming call ✦)——与 .call-status 同款横排 */
  .call-caption{
    display: inline-flex; align-items: center; gap: 11px;
    color: var(--think-label);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(13px, 1.8vw, 15.5px);
    letter-spacing: .16em; line-height: 1;
    margin-top: clamp(2px, 2vh, 14px);
  }
  .call-caption .call-star{ color: var(--think-flourish); display: grid; place-items: center; }
  .call-caption .call-star svg{ width: 16px; height: 16px; display: block; }
  /* 来电动作区:推到底部,拒接/接听左右分开(iOS 式) */
  .incoming-call-actions{
    margin-top: auto;
    margin-bottom: calc(env(safe-area-inset-bottom) + clamp(14px, 5vh, 52px));
    display: flex; align-items: flex-end; justify-content: center;
    gap: clamp(46px, 22vw, 108px);
    width: 100%;
  }
  .incoming-call-slot{ display: flex; flex-direction: column; align-items: center; gap: 11px; }
  .incoming-call-label{
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(12px, 1.6vw, 13.5px);
    color: var(--text-faint);
  }
  .incoming-call-title{
    margin-top: clamp(4px, 2vw, 12px);
    color: var(--text);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(27px, 6.6vw, 36px);
    line-height: 1.2;
    font-weight: 500;
  }
  .incoming-call-text{
    width: min(80vw, 360px);
    min-height: 1.6em;
    color: var(--text-soft);
    font-family: var(--font-en), var(--font-cn);
    font-size: clamp(14px, 1.9vw, 16px);
    line-height: 1.6;
    text-align: center;
    overflow-wrap: break-word;
  }
  .incoming-call-actions{
    display: flex; align-items: center; justify-content: center;
    gap: clamp(18px, 6vw, 34px);
    width: 100%;
  }
  .incoming-call-btn{
    width: clamp(62px, 15.5vw, 74px); height: clamp(62px, 15.5vw, 74px);
    border: 0; border-radius: 50%; padding: 0; cursor: pointer;
    color: #fff;
    display: grid; place-items: center;
    transition: transform .15s ease, filter .2s ease;
  }
  .incoming-call-btn:hover{ filter: brightness(1.05); }
  .incoming-call-btn:active{ transform: scale(.92); }
  .incoming-call-btn svg{ width: clamp(26px, 6vw, 30px); height: clamp(26px, 6vw, 30px); display: block; }
  .incoming-call-btn.accept{
    background: #5aa77a;
    box-shadow: 0 10px 26px rgba(69,132,96,.34);
  }
  .incoming-call-btn.decline{
    background: var(--call-hangup-bg);
    box-shadow: 0 10px 26px var(--call-hangup-shadow);
  }
  .incoming-call-btn.decline svg{ transform: rotate(135deg); }
  .topbtn.call.active{ color: var(--accent); }

  /* ── 长按气泡 · 复制菜单 (TG 式抬起+遮罩) ── */
  /* 长按由我们接管 → 彻底屏蔽聊天区(#scroll)的系统选择/放大镜/callout,否则和我们的菜单打架。
     用 !important 压过一切(含 iOS 误报 hover/pointer 的情况)。输入框在 #scroll 外,单独保持可选可编辑;
     气泡里要复制文字走长按菜单的 Copy。 */
  #scroll, #scroll *{
    -webkit-user-select:none !important; user-select:none !important;
    -webkit-touch-callout:none !important;
  }
  @media (hover:hover) and (pointer:fine){
    #scroll .bubble, #scroll .bubble *{
      -webkit-user-select:text !important; user-select:text !important;
    }
  }
  textarea, input, .composer, .composer *{
    -webkit-user-select:text !important; user-select:text !important;
    -webkit-touch-callout:default !important;
  }
  #scroll .bubble.selectable, #scroll .bubble.selectable *{
    -webkit-user-select:text !important; user-select:text !important;
    -webkit-touch-callout:default !important;
  }
  .bubble.selectable{
    outline: 1px solid var(--accent);
    outline-offset: 2px;
  }

  .msg-menu{ position:fixed; inset:0; z-index:250; opacity:0; pointer-events:none; }
  .msg-menu.open{ opacity:1; pointer-events:auto; }
  .msg-menu-scrim{
    position:absolute; inset:0;
    background:rgba(10,16,24,.30);
    -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px);
    opacity:0; transition:opacity .26s var(--ease-soft);
  }
  .msg-menu.open .msg-menu-scrim{ opacity:1; }
  .msg-menu-clone{
    position:fixed !important; margin:0 !important; animation:none !important;
    pointer-events:none; box-sizing:border-box;
    box-shadow:0 22px 50px rgba(18,34,52,.30);
    transform:scale(1); transition:transform .26s var(--ease-soft); will-change:transform;
  }
  .msg-menu.open .msg-menu-clone{ transform:scale(1.02); }
  .msg-menu-clone.clone-human{ background:var(--bubble-human-bg) !important; color:var(--bubble-human-fg) !important; }
  .msg-menu-clone.clone-ai{ background:var(--bubble-ai-bg) !important; color:var(--bubble-ai-fg) !important; }
  .msg-menu-actions{
    position:fixed; display:flex; flex-direction:column; min-width:150px; padding:5px;
    border-radius:0; background:var(--composer-bg); border:1px solid var(--hairline);
    opacity:0; transform:translateY(8px) scale(.96); transform-origin:top center;
    transition:opacity .2s var(--ease-soft) .05s, transform .26s var(--ease-soft) .05s;
  }
  .msg-menu.open .msg-menu-actions{ opacity:1; transform:none; }
  .msg-act{
    appearance:none; -webkit-appearance:none; border:0; background:transparent; color:var(--text);
    font-family:var(--font-en), var(--font-cn); font-size:15px; display:flex; align-items:center; gap:11px;
    padding:10px 14px; border-radius:0; cursor:pointer; text-align:left; width:100%;
    transition:background var(--motion-fast);
  }
  .msg-act:hover{ background:var(--row-press); }
  .msg-act:active{ background:var(--row-press); transform:scale(.98); }
  .msg-act svg{ width:18px; height:18px; opacity:.82; flex:0 0 auto; }
  .msg-act.danger{ color:#e05a4d; }                       /* 删除(仅本地) */
  /* 举起来等第二下（9.19）：她 9.18 夜里想点 Select 误点了 Delete。
     Delete 就挨着 Select，而这个动作**没有撤销**（本地隐藏会被 persist，刷新也回不来）。
     所以第一下只把它举起来、第二下才真删；4 秒没动静自己放下。
     不弹系统 confirm()：在 PWA 里那个框长得像系统报错，而这只是一次轻确认。 */
  .msg-act.danger.armed{ background:rgba(224,90,77,.13); font-weight:600; }

  /* ── reaction chips (AI poke) ── */
  .row.has-reactions{ flex-direction:column; gap:5px; }
  .row.human.has-reactions{ align-items:flex-end; }
  .row.ai.has-reactions{ align-items:flex-start; }
  .reactions{ display:flex; flex-wrap:wrap; gap:5px; max-width:min(61vw,506px); }
  .reaction{
    display:inline-flex; align-items:center; justify-content:center;
    font-size:15px; line-height:1; padding:4px 9px; border-radius:0;
    background:var(--bubble-ai-bg); border:1px solid var(--hairline);
    -webkit-user-select:none; user-select:none;
  }
  .reaction.pop-in{ animation:reactionPop .52s cubic-bezier(.34,1.56,.5,1) both; }
  @keyframes reactionPop{
    0%{ transform:scale(0) rotate(-16deg); opacity:0; }
    55%{ transform:scale(1.22) rotate(5deg); opacity:1; }
    100%{ transform:none; opacity:1; }
  }

  @media (prefers-reduced-motion: reduce){
    *,
    *::before,
    *::after{
      animation: none !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
    /* 通话声纹是"对方正在说话"的功能性反馈,即便系统减弱动态也保留(细窄低位移,不刺激) */
    .call-wave i{
      animation: callWave var(--dur, 1s) var(--ease-soft) var(--d, 0s) infinite alternate !important;
    }
  }

  /* ── 二级对话框的外壳（.sheet-*）──────────────────────────────────
     体裁出处：换窗口确认（swap preview）那张面板 —— 她拍板的"二级"就穿这身衣服：
     打字机字体、纸面（--bg）、直角、发丝线、按钮一素一实（主动作实色填充）。
     debate-room 的形状：不做圆角玻璃卡片。
     **外壳和内容分开**：外壳是这一组 .sheet-*，swap 自己的数据格/清单是下面的
     .swap-tiles / .swap-sec / .swap-line 那几条，只有 swap 用。
     烧钱动作的确认走 sheetConfirm()，只用外壳 + .sheet-say 一句话，不带清单。 */
  .sheet-scrim{
    position: fixed; inset: 0; z-index: 200;
    background: rgba(20,18,16,.44);
    -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
    opacity: 0; transition: opacity var(--motion-normal) var(--ease-soft);
  }
  .sheet-scrim.open{ opacity: 1; }
  .sheet-scrim.hidden{ display: none; }

  .sheet-panel{
    position: fixed; z-index: 201;
    left: 50%; bottom: 0; transform: translate(-50%, 14px);
    width: min(560px, 100%);
    max-height: min(88vh, 900px);
    display: flex; flex-direction: column;
    background: var(--bg);
    border: 1px solid var(--hairline);
    border-bottom: none;
    opacity: 0;
    transition: opacity var(--motion-normal) var(--ease-soft),
                transform var(--motion-normal) var(--ease-soft);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .sheet-scrim.open + .sheet-panel{ opacity: 1; transform: translate(-50%, 0); }
  .sheet-panel.hidden{ display: none; }

  .sheet-head{
    padding: 18px var(--side-pad) 14px;
    border-bottom: 1px solid var(--hairline);
    flex: none;
  }
  .sheet-title{ font-family: var(--font-en), var(--font-cn); font-size: 1.05rem; font-weight: 600; color: var(--text); letter-spacing: .02em; }
  .sheet-sub{ margin-top: 5px; font-family: var(--font-en); font-size: .68rem; color: var(--text-faint); word-break: break-all; }

  .sheet-body{ overflow-y: auto; -webkit-overflow-scrolling: touch; flex: 1 1 auto; }

  /* 三格数字：舷窗排成一行，用发丝线分隔，不用药丸 */
  .swap-tiles{ display: grid; grid-template-columns: repeat(3, 1fr); border-bottom: 1px solid var(--hairline); }
  .swap-tile{ padding: 14px 8px; text-align: center; border-right: 1px solid var(--hairline); }
  .swap-tile:last-child{ border-right: none; }
  .swap-tile b{ display: block; font-family: var(--font-en); font-size: 1.32rem; font-weight: 600; color: var(--text); line-height: 1.15; }
  .swap-tile b.keep{ color: var(--accent); }
  .swap-tile span{ display: block; margin-top: 4px; font-family: var(--font-en), var(--font-cn); font-size: .68rem; color: var(--text-soft); }

  .swap-sec{ padding: 14px var(--side-pad); border-bottom: 1px solid var(--hairline); }
  .swap-sec:last-child{ border-bottom: none; }
  .swap-sec-h{ font-family: var(--font-en), var(--font-cn); font-size: .7rem; color: var(--text-soft); letter-spacing: .06em; margin-bottom: 9px; }

  .swap-turn{
    background: var(--field-bg);
    border-left: 2px solid var(--hairline);
    border-radius: var(--bubble-radius);
    padding: 7px 10px; margin-bottom: 6px;
  }
  .swap-turn:last-child{ margin-bottom: 0; }
  .swap-turn.mine{ border-left-color: var(--accent); }
  .swap-turn i{ font-style: normal; font-family: var(--font-en); font-size: .64rem; color: var(--text-faint); margin-right: 7px; }
  .swap-turn em{ font-style: normal; font-family: var(--font-en), var(--font-cn); font-size: .78rem; color: var(--text); word-break: break-word; }
  .swap-gap{ text-align: center; font-family: var(--font-en); font-size: .68rem; color: var(--text-faint); padding: 3px 0 8px; }

  .swap-line{ display: flex; align-items: baseline; gap: 10px; padding: 4px 0; }
  .swap-line span{ flex: 1 1 auto; font-family: var(--font-en), var(--font-cn); font-size: .78rem; color: var(--text); }
  .swap-line b{ flex: none; font-family: var(--font-en); font-size: .78rem; font-weight: 600; color: var(--text-soft); }
  .swap-line.drop span, .swap-line.drop b{ color: var(--text-faint); }
  .swap-line.drop span{ text-decoration: line-through; text-decoration-color: var(--text-faint); }

  .swap-note{ padding: 12px var(--side-pad); font-family: var(--font-en), var(--font-cn); font-size: .7rem; line-height: 1.6; color: var(--text-soft); border-top: 1px solid var(--hairline); }
  .swap-note b{ color: var(--accent); font-weight: 600; }

  /* 9.24 对照试验：reasoning_extraction 拦截是不是跟着带过去的推理块走。
     对话照带 20 轮，推理块只留最近 5 轮的（swap.py --thinking-last）。每次打开面板回到「全部」，不记住。 */
  .swap-retain{ display: flex; align-items: center; gap: 6px; padding: 10px var(--side-pad); border-bottom: 1px solid var(--hairline); flex: none; }
  .swap-retain span{ flex: 1 1 auto; font-family: var(--font-en), var(--font-cn); font-size: .72rem; color: var(--text-soft); }
  .swap-retain button{
    appearance: none; cursor: pointer; min-width: 44px; padding: 5px 10px;
    font-family: var(--font-en); font-size: .78rem;
    background: var(--bg); color: var(--text-soft);
    border: 1px solid var(--hairline); border-radius: 999px;
  }
  .swap-retain button[aria-pressed="true"]{ background: var(--accent); color: var(--accent-fg); border-color: var(--accent); font-weight: 600; }

  .sheet-acts{ display: grid; grid-template-columns: 1fr 1fr; gap: 0; border-top: 1px solid var(--hairline); flex: none; }
  .sheet-acts button{
    appearance: none; border: none; cursor: pointer;
    padding: 15px 8px;
    font-family: var(--font-en), var(--font-cn); font-size: .88rem; letter-spacing: .04em;
    background: var(--bg); color: var(--text-soft);
    border-right: 1px solid var(--hairline);
    border-radius: 0;
  }
  .sheet-acts button:last-child{ border-right: none; background: var(--accent); color: var(--accent-fg); font-weight: 600; }
  .sheet-acts button:active{ opacity: .82; }
  .sheet-acts button[disabled]{ opacity: .5; cursor: default; }

  /* 二级里最常见的一种正文：一句问话，最多再加一行上下文。
     烧钱动作用的就是这个 —— 数据格和留下/剥掉清单是 swap 独有的，别往这儿搬。 */
  .sheet-say{
    padding: 20px var(--side-pad) 22px;
    font-family: var(--font-en), var(--font-cn);
    font-size: .88rem; line-height: 1.8; color: var(--text);
  }
  .sheet-ctx{
    margin-top: 11px;
    font-family: var(--font-en), var(--font-cn);
    font-size: .74rem; line-height: 1.7; color: var(--text-soft);
  }
  .sheet-ctx b{ color: var(--text); font-weight: 600; }

  .swap-load{ padding: 34px var(--side-pad); text-align: center; font-family: var(--font-en), var(--font-cn); font-size: .8rem; color: var(--text-soft); }
  .swap-load.err{ color: #b4553f; }

  /* ── Search panel ── */
  .search-panel{
    position: fixed; inset: 0; z-index: 200;
    display: flex; flex-direction: column;
    background: var(--bg);
    transform: translateY(100%);
    opacity: 0;
    transition: transform .32s var(--ease-soft), opacity .22s ease;
    pointer-events: none;
  }
  .search-panel.open{ transform: none; opacity: 1; pointer-events: auto; }
  .search-bar{
    flex: 0 0 auto;
    display: flex; align-items: center; gap: 8px;
    padding: calc(env(safe-area-inset-top) + 10px) 14px 10px;
    border-bottom: 1px solid var(--hairline);
    background: var(--bg);
  }
  .search-bar input{
    flex: 1;
    appearance: none; border: none; outline: none;
    background: var(--bubble-them);
    color: var(--text);
    font-family: var(--font-en), var(--font-cn); font-size: .92rem;
    padding: 9px 14px;
    border-radius: 20px;
    min-width: 0;
  }
  .search-bar input::placeholder{ color: var(--text-faint); }
  .search-cancel{
    appearance: none; border: none; background: none; cursor: pointer;
    color: var(--accent); font-family: var(--font-en), var(--font-cn); font-size: .88rem;
    padding: 6px 4px; white-space: nowrap;
  }
  .search-results{
    flex: 1 1 auto; overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding: 0;
  }
  .search-results::-webkit-scrollbar{ width: 0; }
  .search-empty{
    padding: 48px 20px; text-align: center;
    font-family: var(--font-en), var(--font-cn); font-size: .82rem;
    color: var(--text-faint);
  }
  /* 分隔点式(逾水 8.27 选的 C)。不用方框:这界面里所有切换视图的东西
     都是纯文字(ATRIUM LIBRARY LAB…),塞方框等于换了一套语言。
     没有边框,也就不会再跟搜索栏的分隔线贴成一条。 */
  /* 待发附件卡片:磨砂玻璃,浮在输入框内部上方。8.27 */
  /* 拖文件进来时的那层"放这里"。dragenter/dragleave 会在每个子元素上各触发一次，
     所以用计数器判进出；遮罩自己不吃指针事件，不然一进去就 dragleave 闪个不停。 */
  .dropmask{
    position:fixed; inset:0; z-index:60; display:none;
    align-items:center; justify-content:center;
    background:rgba(0,0,0,.28); backdrop-filter:blur(2px); -webkit-backdrop-filter:blur(2px);
    pointer-events:none;
  }
  .dropmask.on{ display:flex; }
  .dropmask span{
    padding:18px 26px; border-radius:14px;
    border:1.5px dashed var(--hairline); background:var(--card-bg);
    font-family:var(--font-en), var(--font-cn); font-size:14px;
    color:var(--text-soft); letter-spacing:.08em;
  }
  .attbar{
    display:flex; gap:6px; flex-wrap:wrap;
    padding:5px 0 8px;
  }
  .attbar.hidden{ display:none; }
  /* 10.28：附件条限高能滚——她一次附了几百个，条子把输入框顶出屏幕上沿、还滚不动。
     最多占视口四成，多了在条子里面滚；滚到头不带着整页走。 */
  #attBar{ max-height:40vh; max-height:40dvh; overflow-y:auto; overscroll-behavior:contain;
    -webkit-overflow-scrolling:touch; align-content:flex-start; }
  .attbar-head{ display:flex; align-items:center; justify-content:space-between; gap:10px;
    padding:4px 2px 0; font-family:var(--font-en), var(--font-cn); font-size:11.5px; color:var(--text-soft); }
  .attbar-head.hidden{ display:none; }
  .attbar-clear{ border:1px solid var(--chip-line); background:var(--chip-bg); color:var(--text-soft);
    border-radius:999px; padding:3px 11px; font:inherit; cursor:pointer; }
  .attbar-clear.armed{ color:#b05050; border-color:rgba(176,80,80,.45); }
  .attchip{
    position:relative; display:flex; align-items:center; gap:8px;
    max-width:190px; padding:6px 27px 6px 6px;
    border-radius:11px;
    background:var(--chip-bg);
    border:1px solid var(--chip-line);
    box-shadow:0 1px 4px var(--chip-shadow);
    -webkit-backdrop-filter:blur(10px) saturate(1.35);
    backdrop-filter:blur(10px) saturate(1.35);
  }
  .attchip img{
    width:30px; height:30px; object-fit:cover; display:block;
    flex-shrink:0; border-radius:7px;
  }
  .attchip-doc{
    width:30px; height:30px; flex-shrink:0; border-radius:7px;
    display:flex; align-items:center; justify-content:center;
    color:var(--text-faint);
  }
  .attchip-doc svg{ width:16px; height:16px; }
  .attchip-name{
    font-family:var(--font-en); font-size:10.5px; color:var(--text-soft);
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis; letter-spacing:.02em;
  }
  .attchip-x{
    position:absolute; top:0; right:0; width:24px; height:100%;
    background:none; border:none; padding:0; cursor:pointer;
    color:var(--text-faint); font-size:14px; line-height:1;
    font-family:var(--font-en); border-radius:0 11px 11px 0;
  }
  .attchip-x:active{ color:var(--text); }
  .attchip.uploading{ opacity:.6; }
  .search-tabs{
    display:flex; align-items:baseline; flex-wrap:wrap;
    padding:10px 14px 12px;
    font-family:var(--font-en); font-size:11px; letter-spacing:0.08em;
  }
  .search-tab{
    font:inherit; letter-spacing:inherit;
    background:none; border:none; padding:4px 0; margin:0;
    color:var(--text-faint); cursor:pointer;
  }
  .search-tab.active{ color:var(--text); }
  .search-tab-sep{ color:var(--hairline); padding:0 9px; user-select:none; }
  .search-grid{
    /* 按宽度自适应:手机上正好三列,电脑上自己多排几列。
       写死 repeat(3,1fr) 的话,1920 的屏幕每张缩略图会有 600px。 */
    display:grid; gap:3px; padding:0 14px 14px;
    grid-template-columns:repeat(auto-fill, minmax(110px, 1fr));
  }
  .search-thumb{
    aspect-ratio:1; overflow:hidden; background:var(--row-press); cursor:pointer;
  }
  .search-thumb img{ width:100%; height:100%; object-fit:cover; display:block; }
  .search-file{
    display:flex; flex-direction:column; gap:3px;
    padding:11px 14px; border-bottom:1px solid var(--hairline); cursor:pointer;
  }
  .search-file:active{ background:var(--row-press); }
  .search-file-name{ font-size:13px; color:var(--text); word-break:break-all; line-height:1.45; }
  .search-file-meta{ font-size:11px; color:var(--text-faint); font-family:var(--font-en); }
  .search-daysep{
    font-family:var(--font-en); font-size:10px; letter-spacing:0.08em;
    color:var(--text-faint); padding:12px 14px 5px;
  }
  .search-hit{
    display: flex; flex-direction: column; gap: 3px;
    padding: 12px 18px;
    border-bottom: 1px solid var(--hairline);
    cursor: pointer;
    transition: background .12s;
  }
  .search-hit:active{ background: var(--row-press); }
  .search-hit-head{
    display: flex; justify-content: space-between; align-items: baseline;
    font-size: .72rem; color: var(--text-faint);
    font-family: var(--font-en), var(--font-cn);
  }
  .search-hit-from{ font-weight: 500; color: var(--text-soft); }
  .search-hit-text{
    font-family: var(--font-en), var(--font-cn); font-size: .84rem;
    color: var(--text); line-height: 1.5;
    overflow: hidden;
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  }
  .search-hit-text mark{
    background: var(--accent); color: var(--accent-fg);
    border-radius: 2px; padding: 0 2px;
  }
  .search-more{
    padding: 16px; text-align: center;
    font-family: var(--font-en), var(--font-cn); font-size: .8rem;
    color: var(--accent); cursor: pointer;
  }
  .search-more:active{ opacity: .7; }
  /* ── 工作窗 ────────────────────────────────────────────────────────
     不是终端。她的原话:"结论字太小了而且埋在一大堆工作调用中间"。
     所以这里的主体是它说的话,正文大小跟聊天一样;工具调用压成一行小字,
     点开才展开。桌面上是浮在内容上的大窗,手机上整页。 */
  .work-scrim{
    position:fixed; inset:0; z-index:64; background:rgba(20,18,16,.34);
    backdrop-filter:blur(2px); -webkit-backdrop-filter:blur(2px);
    opacity:0; pointer-events:none; transition:opacity .22s ease;
  }
  .work-scrim.open{ opacity:1; pointer-events:auto; }
  .work-panel{
    position:fixed; z-index:65; display:flex; flex-direction:column;
    background:var(--bg); color:var(--text);
    inset:0;                                   /* 手机:整页 */
    transform:translateY(14px); opacity:0; pointer-events:none;
    transition:transform .24s cubic-bezier(.22,.9,.3,1), opacity .2s ease;
  }
  .work-panel.open{ transform:none; opacity:1; pointer-events:auto; }
  @media (min-width:900px){
    .work-panel{
      inset:auto; top:58px; right:22px; bottom:22px;
      width:min(880px, 72vw);
      border:1px solid var(--hairline); border-radius:16px;
      box-shadow:0 22px 60px rgba(20,18,16,.26);
      overflow:hidden; transform:translateY(18px) scale(.995);
    }
    .work-panel.open{ transform:none; }
  }
  .work-top{
    flex:none; display:flex; align-items:center; gap:10px;
    padding:12px 14px; border-bottom:1px solid var(--hairline);
    background:var(--header-bg);
  }
  .work-back{
    flex:none; width:34px; height:34px; border:0; background:transparent;
    color:var(--text-soft); border-radius:10px; display:grid; place-items:center;
    cursor:pointer;
  }
  .work-back:active{ transform:scale(.92); }
  .work-back svg{ width:21px; height:21px; display:block; }
  .work-new{
    flex:none; width:34px; height:34px; border:0; background:transparent;
    color:var(--text-soft); border-radius:10px; display:grid; place-items:center;
    cursor:pointer;
  }
  .work-new:active{ transform:scale(.92); }
  .work-new svg{ width:19px; height:19px; display:block; }
  /* 跳过 600s 冷却的快进键。跟 ➕ 一样大,颜色淡一档——它是"绕过保险"的
     动作,不该比正常开新窗更抢眼。冷却中时 .hot 让它亮起来,提示可以用。 */
  .work-skip{
    flex:none; width:34px; height:34px; border:0; background:transparent;
    color:var(--text-faint); border-radius:10px; display:grid; place-items:center;
    cursor:pointer;
  }
  .work-skip:active{ transform:scale(.92); }
  .work-skip svg{ width:17px; height:17px; display:block; }
  .work-skip.hot{ color:var(--accent); }
  /* 10.9 三方群聊开关：灰＝关、强调色＝开；读不到现况时淡到几乎看不见、点了先重读不切 */
  .work-group{
    flex:none; width:34px; height:34px; border:0; background:transparent;
    color:var(--text-faint); border-radius:10px; display:grid; place-items:center;
    cursor:pointer; -webkit-tap-highlight-color:transparent; touch-action:manipulation;
  }
  .work-group:active{ transform:scale(.92); }
  .work-group svg{ width:19px; height:19px; display:block; }
  .work-group.on{ color:var(--accent); }
  .work-group.unknown{ opacity:.3; }
  .work-head{ min-width:0; flex:1; display:flex; flex-direction:column; gap:2px; line-height:1.2; }
  .work-head strong{ font-family:var(--font-en); font-size:14px; font-weight:600; letter-spacing:.02em; }
  .work-ctx{
    font-family:var(--font-en); font-size:11px; font-weight:500;
    color:var(--text-faint); margin-left:6px;
  }
  .work-ctx.warn{ color:#b08030; }
  .work-ctx.crit{ color:#a04040; }
  .work-state{
    font-family:var(--font-en); font-size:11.5px; color:var(--text-faint);
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  .work-state.live{ color:var(--accent); }
  .work-log{
    flex:1; min-height:0; overflow-y:auto; overflow-x:hidden; overscroll-behavior:contain;
    padding:18px 16px 8px; display:flex; flex-direction:column; gap:16px;
  }
  /* 列方向 flex 的孩子默认 min-width:auto,超宽内容会把它顶出去。
     聊天页的 .scroll 一直有 overflow-x:hidden,工作窗漏了 —— 手机上整页
     能左右拖就是这么来的(8.30)。 */
  .work-log > *{ min-width:0; max-width:100%; }
  /* 它说的话 —— 这是主体,正文大小 */
  .work-say{
    /* 同上：15.5 × 1.72 = 26.66px，小数部分 .66 漂得最快。钉到整像素。 */
    font-family:var(--font-cn); font-size:15.5px;
    line-height:1.72;
    line-height:round(1.72em, 1px);
    color:var(--text); white-space:pre-wrap; word-break:break-word;
    overflow-wrap:anywhere;
    padding-bottom:14px; border-bottom:1px solid var(--hairline);
  }
  .work-log > .work-say:last-child{ border-bottom:0; }
  .work-say code{
    font-family:var(--font-mono); font-size:13.5px;
    background:var(--bubble-ai-bg); padding:1px 5px; border-radius:5px;
    white-space:pre-wrap; overflow-wrap:anywhere;
  }
  .work-say pre{
    font-family:var(--font-mono); font-size:12.5px; line-height:1.6;
    background:var(--bubble-ai-bg); border:1px solid var(--hairline);
    border-radius:10px; padding:10px 12px; margin:10px 0; overflow-x:auto;
    max-width:100%;
  }
  /* 我们说的话 */
  .work-mine{
    align-self:flex-end; max-width:82%; min-width:0; overflow-wrap:anywhere;
    background:var(--bubble-ai-bg); border:1px solid var(--hairline);
    border-radius:14px; padding:9px 13px;
    font-family:var(--font-cn); font-size:15px; line-height:1.62;
    white-space:pre-wrap; word-break:break-word;
  }
  .work-who{
    display:block; font-family:var(--font-en); font-size:11.5px;
    letter-spacing:.07em; text-transform:uppercase;
    color:var(--text-soft); margin-bottom:4px; font-weight:500;
  }
  /* 她说的：实线气泡 */
  .work-mine.from-her .work-who{ color:var(--text-soft); }
  /* 我说的：虚线气泡,一眼分得开 */
  .work-mine.from-claude{ background:transparent; border-style:dashed; }
  .work-mine.from-claude .work-who{ color:var(--text-faint); }
  /* 工作窗自己的话也标一下 */
  .work-say .work-who{ margin-bottom:5px; }
  /* 工具调用 —— 压成一行,默认是背景不是内容 */
  .work-act{
    font-family:var(--font-en); font-size:11.5px; color:var(--text-faint);
    display:flex; align-items:center; gap:7px; cursor:pointer;
    padding:2px 0; user-select:none;
  }
  .work-act:hover{ color:var(--text-soft); }
  .work-act .dot{ width:4px; height:4px; border-radius:50%; background:currentColor; flex:none; }
  .work-act-detail{
    font-family:var(--font-mono); font-size:11.5px; line-height:1.62;
    color:var(--text-soft); background:var(--bubble-ai-bg);
    border-radius:8px; padding:8px 10px; margin-top:5px;
    white-space:pre-wrap; word-break:break-all; max-height:280px; overflow:auto;
  }
  .work-empty{
    font-family:var(--font-en); font-size:12.5px; color:var(--text-faint);
    text-align:center; padding:40px 0;
  }
  .work-form{
    flex:none; display:flex; flex-direction:column; gap:8px;
    padding:10px 12px calc(10px + var(--ios-pwa-bottom-bleed));
    border-top:1px solid var(--hairline); background:var(--header-bg);
  }
  .work-form-row{ display:flex; align-items:flex-end; gap:9px; }
  /* 待发附件条,横着排、能划。chip 本身复用主聊天页的 .attchip 那套。 */
  .work-attbar{
    display:flex; gap:7px; overflow-x:auto; padding:1px 0 2px;
    -webkit-overflow-scrolling:touch;
    /* 左边缩进到跟输入框左边缘齐平 = 📎 的宽度(38) + form-row 的 gap(9)。
       不缩进的话 chip 顶在 📎 正上方、比输入框往左突出一截,很难看。
       (逾水 8.29 截图指出) */
    padding-left:47px;
  }
  .work-attbar.hidden{ display:none; }
  .work-clip{
    flex:none; width:38px; height:38px; border:0; border-radius:11px;
    background:transparent; color:var(--text-soft);
    display:grid; place-items:center; cursor:pointer;
  }
  .work-clip:active{ transform:scale(.93); }
  .work-clip svg{ width:18px; height:18px; display:block; }
  /* 气泡里的附件 */
  .work-atts{ display:flex; flex-direction:column; gap:6px; margin-top:6px; }
  .work-atts img{
    max-width:100%; max-height:260px; width:auto; border-radius:10px;
    display:block; object-fit:contain;
  }
  .work-att-file{
    display:flex; align-items:center; gap:7px; text-decoration:none;
    color:var(--text-soft); font-family:var(--font-en); font-size:11.5px;
  }
  .work-att-file svg{ width:14px; height:14px; flex:none; }
  .work-input{
    flex:1; min-height:38px; max-height:150px; resize:none;
    border:1px solid var(--hairline); border-radius:12px;
    background:var(--bg); color:var(--text);
    font-family:var(--font-cn); font-size:15px; line-height:1.55;
    padding:8px 12px; outline:none;
  }
  .work-input:focus{ border-color:var(--accent); }
  .work-send{
    flex:none; width:38px; height:38px; border:0; border-radius:11px;
    background:var(--accent); color:#fff; display:grid; place-items:center; cursor:pointer;
  }
  .work-send:active{ transform:scale(.93); }
  .work-send svg{ width:18px; height:18px; display:block; }

  /* 提醒区挪到 diary 上面以后，diary 按钮下面直接接 TASKS，补回原来提醒区留的那段空 */
  .atrium-diary-toggle{ margin-bottom:24px; }

  /* ── 9.29 药柜（mod-pills.js ＋ vendor/pilulier）──────────────────────
     入口键跟 tap 同一套（透明底、accent 色、同尺寸），挂了药时右上角一个小点。
     胶囊 chip 用附件 chip 那一套壳（.attchip），左边一枚小胶囊代替缩略图。 */
  .floatbtn.pillbtn .tap-ring{ display:none; }
  .floatbtn.pillbtn.has-pills::after{
    content:""; position:absolute; top:18%; right:18%; width:6px; height:6px; border-radius:50%;
    background: var(--accent);
  }
  .pillchip{ padding-left:10px; }
  .pillchip-glyph{
    width:18px; height:8px; border-radius:4px; flex-shrink:0;
    border:1.2px solid var(--text-soft);
    background: linear-gradient(90deg, var(--text-soft) 0 50%, transparent 50% 100%);
    transform: rotate(-30deg);
  }

  /* 组件的主题变量桥到聊天页自己的变量：黑白灰、同一套字体。
     组件的样式表插在 <head> 末尾、比 app.css 晚，所以这里多一层 :root 压过它的 .pill-light / .pill-dark。
     深浅由 mod-pills.js 按聊天页的 data-theme 设 theme 属性；聊天页的变量本身已随 data-theme 变，这里不用分两份。 */
  :root .pill-light, :root .pill-dark{
    --pill-paper: var(--bg);
    --pill-sheet: var(--card-bg);
    --pill-ink: var(--text);
    --pill-ink-2: var(--text-soft);
    --pill-ink-3: var(--text-faint);
    --pill-ink-4: var(--card-line);
    --pill-line: var(--card-line);
    --pill-serif: var(--font-en);
    --pill-script: var(--font-en);
    --pill-cjk: var(--font-cn);
  }

  /* ── 选项卡（10.3）──────────────────────────────────────────────
     我那条气泡下一行小字（点开 = 底部卡），她点选发出的那句画成胶囊。
     颜色全走现成的 token（--accent / --text* / --panel-bg / --hairline），不另起色板；
     暗色跟着 :root[data-theme="dark"] 那组 token 自动换。
     iOS：这里没有任何 :hover 规则（WebKit 会把第一下轻触当 hover、第二下才派 click）；
     卡里的输入框 16px（<16px 会整页放大）；收起态 visibility:hidden + 滑出距离带上 --os-bottom。 */
  .row.has-opts{ flex-direction: column; align-items: flex-start; }
  .row.has-opts.has-reactions{ gap: 0; }
  .opts-line{
    display: inline-flex; align-items: center; gap: 6px;
    margin: 6px 0 0 2px; padding: 4px 0; border: 0; background: transparent;
    color: var(--accent); font-family: var(--font-en), var(--font-cn); font-size: 11.5px; letter-spacing: .06em;
    cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  }
  .opts-line svg{ flex-shrink: 0; }
  .opts-line:active{ opacity: .6; }
  .opts-line.done{ color: var(--text-soft); }

  /* ── 用量小行（10.9）：我那条气泡下，默认收着只露「用量」；纸面灰、打字机字，跟选项卡同一行高 ── */
  .row.has-usage{ flex-direction: column; align-items: flex-start; }
  .row.has-usage.has-reactions{ gap: 0; }
  .usage-line{
    display: inline-block; margin: 2px 0 0 2px; padding: 3px 0; border: 0; background: transparent;
    color: var(--text-soft); font-family: var(--font-en), var(--font-cn); font-size: 10.5px; letter-spacing: .06em;
    font-variant-numeric: tabular-nums; opacity: .7;
    cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  }
  .usage-line.open{ opacity: 1; }
  .usage-line:active{ opacity: .45; }

  /* ── 记忆小条（10.24）：她气泡下，默认收着只露「记忆 · N」；跟用量小行同一族（纸面灰、打字机字、一样的行高），靠右 ── */
  .row.has-recall{ flex-direction: column; align-items: flex-end; }
  .recall-line{ display: flex; flex-direction: column; align-items: flex-end; margin: 2px 2px 0 0; max-width: min(78vw, 420px); }
  .recall-head{
    padding: 3px 0; border: 0; background: transparent; color: var(--text-soft);
    font-family: var(--font-en), var(--font-cn); font-size: 10.5px; letter-spacing: .06em; font-variant-numeric: tabular-nums;
    opacity: .7; cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  }
  .recall-line.open .recall-head{ opacity: 1; }
  .recall-head:active{ opacity: .45; }
  /* 展开：每行「日期 · 题眼 —— preview」，行文左对齐（preview 会折行，右对齐读着别扭），整块贴她气泡右缘 */
  .recall-list{ display: flex; flex-direction: column; gap: 5px; padding: 2px 0 6px; text-align: left; align-self: stretch;
    font-family: var(--font-en), var(--font-cn); font-size: 11px; line-height: 1.55; color: var(--text-soft); }
  .recall-list div{ overflow-wrap: anywhere; }
  .recall-dt{ color: var(--text-faint); font-variant-numeric: tabular-nums; }
  .recall-nm.dim, .recall-sep{ color: var(--text-faint); }
  .recall-pv{ color: var(--text-soft); }

  .row.human .bubble.pick{
    border-radius: 999px;
    padding-left: 16px; padding-right: 16px;
    background: color-mix(in srgb, var(--accent) 12%, var(--bubble-human-bg));
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
  }
  .row.human.tail .bubble.pick{ border-bottom-right-radius: 999px; }

  /* 落位跟输入栏读同一个变量（--composer-zone 已含键盘偏移），见 index.html 选项卡那段的注释 */
  .opt-veil{ position: fixed; left: 0; right: 0; top: 0; bottom: calc(var(--composer-zone) - 16px); z-index: 150; background: transparent;
    pointer-events: none; }
  .opt-veil.on{ pointer-events: auto; }
  .opt-sheet{
    --os-bottom: calc(var(--composer-zone) - 8px);
    position: fixed; z-index: 151; left: 10px; right: 10px; bottom: var(--os-bottom);
    max-width: 560px; margin: 0 auto; box-sizing: border-box;
    padding: 12px 16px 12px;
    background: var(--panel-bg); color: var(--text);
    border: 1px solid var(--hairline);
    box-shadow: 0 -6px 32px rgba(0,0,0,.16);
    transform: translateY(calc(100% + var(--os-bottom, 10px) + 24px)); visibility: hidden;
    transition: transform .38s cubic-bezier(.2,.8,.2,1), visibility 0s linear .38s;
  }
  .opt-sheet.on{ transform: none; visibility: visible; transition-delay: 0s; }
  .opt-sheet .os-grip{ width: 36px; height: 3px; background: var(--hairline); margin: 0 auto 10px; }
  .opt-sheet .os-eye{ font-family: var(--font-en), var(--font-cn); font-size: 10.5px; letter-spacing: .16em; color: var(--accent); }
  .opt-sheet .os-title{ font-family: var(--font-en), var(--font-cn); font-size: 15.5px; line-height: 1.5; color: var(--text);
    margin: 4px 36px 8px 0; word-break: break-word; }
  .opt-sheet .os-x{ position: absolute; right: 12px; top: 14px; width: 32px; height: 32px; border: 0; border-radius: 50%;
    background: transparent; color: var(--text-soft); font-size: 20px; line-height: 1; cursor: pointer;
    -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
  .opt-sheet .os-x:active{ background: var(--row-press); }
  .opt-sheet .os-rows{ max-height: 46vh; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
  .opt-sheet .os-row{
    display: flex; align-items: center; gap: 12px; width: 100%; padding: 11px 6px; margin: 0;
    border: 0; border-top: 1px solid var(--hairline); background: transparent; color: var(--text);
    font-family: var(--font-en), var(--font-cn); font-size: 15px; line-height: 1.45; text-align: left;
    cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  }
  .opt-sheet .os-row:active{ background: var(--row-press); }
  .opt-sheet .os-k{ width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; flex-shrink: 0;
    font-family: var(--font-en); font-size: 12px; color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, transparent); }
  .opt-sheet .os-t{ flex: 1; min-width: 0; word-break: break-word; }
  .opt-sheet .os-c{ color: var(--text-faint); font-size: 18px; line-height: 1; display: grid; place-items: center; }
  .opt-sheet .os-own{ display: flex; align-items: center; gap: 8px; margin-top: 10px; padding: 4px 4px 4px 14px;
    border: 1px solid var(--field-line); background: var(--field-bg); }
  .opt-sheet .os-own input{ flex: 1; min-width: 0; border: 0; background: transparent; color: var(--text);
    font-family: var(--font-cn-sys); font-size: 16px; outline: 0; padding: 6px 0; }
  .opt-sheet .os-own input::placeholder{ color: var(--text-faint); }
  .opt-sheet .os-send{ width: 34px; height: 34px; border: 0; border-radius: 50%; display: grid; place-items: center; flex-shrink: 0;
    background: var(--send-idle-bg); color: var(--send-idle-fg); cursor: pointer;
    -webkit-tap-highlight-color: transparent; touch-action: manipulation; transition: background .2s, color .2s; }
  .opt-sheet .os-own input:not(:placeholder-shown) + .os-send{ background: var(--send-bg); color: var(--accent-fg); }

  /* 记录态：只看不发 */
  .opt-sheet.rec .os-own{ display: none; }
  .opt-sheet.rec .os-row{ pointer-events: none; cursor: default; opacity: .5; }
  .opt-sheet.rec .os-row.on{ opacity: 1; }
  .opt-sheet.rec .os-row.on .os-k{ background: var(--accent); color: var(--accent-fg); }
  .opt-sheet.rec .os-row.on .os-c{ color: var(--accent); }
  .opt-sheet .os-her{ display: flex; align-items: flex-start; gap: 12px; margin: 8px 0 2px; padding: 10px 12px;
    color: var(--text); font-size: 15px; line-height: 1.5;
    background: color-mix(in srgb, var(--accent) 8%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); }
  .opt-sheet .os-her .os-k{ background: var(--accent); color: var(--accent-fg); margin-top: 1px; }
  .opt-sheet .os-her .os-t{ white-space: pre-wrap; }
  .opt-sheet .os-none{ margin: 6px 0 2px; padding: 8px 6px 0; border-top: 1px solid var(--hairline);
    font-family: var(--font-en), var(--font-cn); font-size: 11.5px; letter-spacing: .1em; color: var(--text-faint); }
