﻿/*
 * Theme tokens. The default (dark) block below doubles as the "dark" theme;
 * the [data-theme="…"] blocks after it override a subset of tokens per theme.
 * Selectors are attribute-based (not :root-only) so a nested element carrying
 * data-theme — e.g. the theme preview swatches in settings — resolves the same
 * tokens as a full page would.
 *
 * Derived transparencies (focus rings, tinted buttons, danger borders) are
 * produced with color-mix() from --accent / --danger / --bar-critical, so a
 * theme that swaps the hue gets every derived shade for free.
 */
:root,
[data-theme="dark"] {
    color-scheme: dark;

    /* Canvas & surfaces: neutral cool greys, no pure black (OLED smear); depth via lightness only */
    --bg: #0f1012;
    --surface-1: #151619;
    --surface-2: #1d1f23;
    --surface-2-hover: #24272c;
    /* 次级交互底色（hover / 次级按钮）。原来只有引用、从来没有定义，
       于是所有用它的地方背景静默变透明——2026-08-19 实测发现。 */
    --surface-3: color-mix(in srgb, var(--surface-2-hover) 88%, white);
    --bubble-user: #24262b;
    --border: rgba(255, 255, 255, 0.07);
    --border-strong: rgba(255, 255, 255, 0.13);

    /* Text: every token clears 4.5:1 on the surfaces it lands on (see design-system) */
    --text-primary: #ececee;
    --text-secondary: #b4b8bf;
    --text-muted: #9aa0a8;
    --text-faint: #848a93;
    --text-heading: #f4f7fa;
    --text-code: #d7dce5;

    /* Accent means "state" only: streaming, current conversation, focus. Body text and links stay neutral. */
    --accent: #46dcc0;
    --link: #6fa8ff;   /* 对话里的超链接：蓝色、无下划线（2026-09-17 用户定的）；对 --bg 7.9 : 1 */
    /* 侧栏品牌标（固定色 PNG）在这套主题下的提亮量：暗色提、浅色不动，见 .rail-logo 的注释 */
    --logo-lift: brightness(1.35);
    --accent-dim: color-mix(in srgb, var(--accent) 10%, transparent);
    --accent-glow: color-mix(in srgb, var(--accent) 24%, transparent);
    --accent-tint: color-mix(in srgb, var(--accent) 14%, transparent);
    --accent-tint-hover: color-mix(in srgb, var(--accent) 22%, transparent);
    --accent-tint-border: color-mix(in srgb, var(--accent) 30%, transparent);
    --accent-ok-border: color-mix(in srgb, var(--accent) 18%, transparent);
    --focus-border: color-mix(in srgb, var(--accent) 35%, transparent);
    --focus-ring: color-mix(in srgb, var(--accent) 12%, transparent);

    /* 存疑（模型协同对不上的数据）：警示但不是错误——刻意不用 --danger 那支红。
       墨蓝主题同为暗色，继承 :root 这一份即可，只有两套浅色主题需要压深。 */
    --caution: #e0a44a;
    --caution-dim: color-mix(in srgb, var(--caution) 14%, transparent);
    --danger: #ef6b6b;
    --danger-dim: color-mix(in srgb, var(--danger) 12%, transparent);
    --danger-border-soft: color-mix(in srgb, var(--danger) 18%, transparent);
    --danger-border: color-mix(in srgb, var(--danger) 22%, transparent);
    --danger-border-strong: color-mix(in srgb, var(--danger) 42%, transparent);
    --danger-hover: color-mix(in srgb, var(--danger) 25%, transparent);
    /* Danger text is lightness-shifted per theme, not alpha-derived */
    --danger-text: #fda4af;
    --danger-text-strong: #fecdd3;

    /* Interaction details that don't derive cleanly from the palette */
    --input-border-focus: rgba(255, 255, 255, 0.22);
    --scrollbar-thumb-hover: #33373d;
    --send-button-hover: #ffffff;

    /* On-surface tints (reasoning block bg, gantt row hover) — white-based on dark, ink-based on light */
    --tint-1: rgba(255, 255, 255, 0.015);
    --tint-2: rgba(255, 255, 255, 0.022);

    /* Scrims & shadows */
    --scrim: rgba(2, 4, 7, 0.66);
    --scrim-strong: rgba(2, 4, 7, 0.82);
    --scrim-drawer: rgba(3, 5, 8, 0.55);
    --shadow-soft: 0 10px 30px rgba(0, 0, 0, 0.28);
    --shadow-pop: 0 18px 48px rgba(0, 0, 0, 0.5);
    --shadow-modal: 0 28px 80px rgba(0, 0, 0, 0.48);
    --shadow-rail: 24px 0 54px rgba(0, 0, 0, 0.38);
    --shadow-tooltip: 0 14px 40px rgba(0, 0, 0, 0.5);
    /* 暗色的深度语言补最后两档：浮层比家具再亮一档（--surface-float），
       上缘一根受光内亮边（--edge-light）——近黑底上纯黑阴影物理上不可测，
       "浮起来"只能靠自己更亮和受光。浅色主题两个都退化：亮边透明、浮层还是纸白。 */
    --edge-light: color-mix(in srgb, #fff 6%, transparent);
    --surface-float: var(--surface-2);

    /* Gantt identity colours — dataviz-validated on the dark surfaces (see design-system/pages/chat.md) */
    --bar-normal: #8a63e0;
    --bar-critical: #22a892;
    --bar-summary: #6b7280;
    --bar-progress: rgba(255, 255, 255, 0.34);

    /* Attachment-card tile colours (file-type identity, ≥4.5:1 on --bubble-user).
       MPP is a lightened step of --bar-normal: the bar colour itself is tuned for
       3:1 graphics contrast and falls short as text on the dark bubbles. */
    --file-pdf: #f08c7d;
    --file-doc: #7fb0ef;
    --file-mpp: #a98ff0;
    --file-xlsx: #7dbf8e;

    /* 版式节奏 */
    --drawer-width: 268px;
    /* Thread and composer widths are decoupled on purpose: the thread holds
       wide content (tables, code, gantt) at --column-thread while prose is
       capped separately (see .message-content measure rule); the composer
       stays at the narrower --column, centered. */
    --column: 768px;
    --column-thread: 1024px;
    /* 输入框浮层实测高度，由 chat.js 的 ResizeObserver 写入；此处只是首帧兜底 */
    --composer-h: 156px;
    /* 对话字号缩放系数（设置 › 主题 › 对话字号）。默认 1，此时所有尺寸与引入这个
       机制之前逐像素相同。凡是"对话正文"范畴的字号、行内间距、气泡内边距都乘上它，
       所以放大缩小是整体等比的，不会出现字变大而行距不变的别扭比例。
       界面外壳（侧栏、设置、按钮、协同状态条这些）刻意不跟着变——那是框架不是内容。 */
    --msg-scale: 1;

    --radius-sm: 8px;
    --radius-md: 12px;
    --radius-lg: 18px;
    --radius-pill: 999px;

    /* 运动：全站只有两条曲线、三个时长 */
    --ease: cubic-bezier(0.16, 1, 0.3, 1);
    --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
    --dur-1: 120ms;
    --dur-2: 200ms;
    --dur-3: 320ms;

    --font-sans: Inter, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
    --font-mono: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
    font-family: var(--font-sans);
}

/* ─── Theme: 晨雾浅色 (light) — mirror of the default dark, paper-grey canvas ─── */
[data-theme="light"] {
    color-scheme: light;

    --bg: #f1f2f4;
    --surface-1: #fafbfc;
    --surface-2: #ebedf0;
    --surface-2-hover: #e2e5e9;
    /* 次级交互底色（hover / 次级按钮）。原来只有引用、从来没有定义，
       于是所有用它的地方背景静默变透明——2026-08-19 实测发现。 */
    --surface-3: color-mix(in srgb, var(--surface-2-hover) 88%, black);
    --bubble-user: #e7e9ed;
    --edge-light: transparent;
    --surface-float: var(--surface-1);
    --border: rgba(20, 26, 35, 0.09);
    --border-strong: rgba(20, 26, 35, 0.17);

    --text-primary: #1b1e23;
    --text-secondary: #444a52;
    --text-muted: #59616b;
    --text-faint: #656c76;
    --text-heading: #131519;
    --text-code: #3a414a;

    /* Mint stepped down to keep ≥4.5:1 when used as text on light surfaces */
    --accent: #0b7a60;
    --link: #1155a1;   /* 品牌蓝，对 --bg 6.6 : 1 */
    --logo-lift: none;
    --caution: #9a6510;
    --caution-dim: color-mix(in srgb, var(--caution) 12%, transparent);
    --danger: #c43d3d;
    --danger-text: #a92f2f;
    --danger-text-strong: #8c2323;

    --input-border-focus: rgba(20, 26, 35, 0.30);
    --scrollbar-thumb-hover: #c9ced5;
    --send-button-hover: #000000;

    --tint-1: rgba(20, 26, 35, 0.02);
    --tint-2: rgba(20, 26, 35, 0.03);

    --scrim: rgba(23, 28, 36, 0.40);
    --scrim-strong: rgba(23, 28, 36, 0.55);
    --scrim-drawer: rgba(23, 28, 36, 0.35);
    --shadow-soft: 0 10px 30px rgba(23, 28, 36, 0.10);
    --shadow-pop: 0 18px 48px rgba(23, 28, 36, 0.14);
    --shadow-modal: 0 28px 80px rgba(23, 28, 36, 0.18);
    --shadow-rail: 24px 0 54px rgba(23, 28, 36, 0.16);
    --shadow-tooltip: 0 14px 40px rgba(23, 28, 36, 0.14);

    /* Bars darkened for light surfaces: ≥3:1 non-text contrast on --surface-1 */
    --bar-normal: #6d46c9;
    --bar-critical: #0e7c66;
    --bar-summary: #9aa1ab;
    --bar-progress: rgba(255, 255, 255, 0.42);

    --file-pdf: #b0402e;
    --file-doc: #2f66b3;
    --file-mpp: var(--bar-normal);
    --file-xlsx: #2f7a4a;
}

/* ─── Theme: 墨蓝夜航 (indigo) — blue-ink dark, ice-blue accent ─── */
[data-theme="indigo"] {
    color-scheme: dark;

    --bg: #0b0f18;
    --surface-1: #111624;
    --surface-2: #182031;
    --surface-2-hover: #1e2839;
    /* 次级交互底色（hover / 次级按钮）。原来只有引用、从来没有定义，
       于是所有用它的地方背景静默变透明——2026-08-19 实测发现。 */
    --surface-3: color-mix(in srgb, var(--surface-2-hover) 88%, white);
    --bubble-user: #1c2536;
    --border: rgba(170, 195, 255, 0.09);
    --border-strong: rgba(170, 195, 255, 0.16);

    --text-primary: #e9edf6;
    --text-secondary: #b5bdce;
    --text-muted: #9aa4b8;
    --text-faint: #8591a8;
    --text-heading: #f3f6fc;
    --text-code: #ccd6e8;

    --accent: #62b8ff;
    --link: #7fb3ff;   /* 对 --bg 8.9 : 1 */
    --logo-lift: brightness(1.35);

    --input-border-focus: rgba(190, 210, 255, 0.25);
    --scrollbar-thumb-hover: #2b3650;

    --tint-1: rgba(190, 210, 255, 0.02);
    --tint-2: rgba(190, 210, 255, 0.028);

    --scrim: rgba(2, 5, 12, 0.66);
    --scrim-strong: rgba(2, 5, 12, 0.82);
    --scrim-drawer: rgba(2, 5, 12, 0.55);
}

/* ─── Theme: 暖砂阅读 (warm) — cream canvas, terracotta accent, reading-first ─── */
[data-theme="warm"] {
    color-scheme: light;

    --bg: #f4efe6;
    --surface-1: #fbf8f2;
    --surface-2: #ece5d8;
    --surface-2-hover: #e3dbcb;
    /* 次级交互底色（hover / 次级按钮）。原来只有引用、从来没有定义，
       于是所有用它的地方背景静默变透明——2026-08-19 实测发现。 */
    --surface-3: color-mix(in srgb, var(--surface-2-hover) 88%, black);
    --bubble-user: #eae2d3;
    --caution: #8f5f12;
    --caution-dim: color-mix(in srgb, var(--caution) 12%, transparent);
    --edge-light: transparent;
    --surface-float: var(--surface-1);
    --border: rgba(74, 60, 42, 0.11);
    --border-strong: rgba(74, 60, 42, 0.19);

    --text-primary: #2a251e;
    --text-secondary: #4d463c;
    --text-muted: #60584c;
    --text-faint: #6e6659;
    --text-heading: #211c15;
    --text-code: #473f34;

    --accent: #a34724;
    --link: #1155a1;   /* 品牌蓝，对 --bg 6.5 : 1 */
    --logo-lift: none;
    --danger: #b3372f;
    --danger-text: #9a2b24;
    --danger-text-strong: #7f1f1a;

    --input-border-focus: rgba(74, 60, 42, 0.32);
    --scrollbar-thumb-hover: #d2c8b6;
    --send-button-hover: #000000;

    --tint-1: rgba(74, 60, 42, 0.025);
    --tint-2: rgba(74, 60, 42, 0.035);

    --scrim: rgba(46, 38, 26, 0.40);
    --scrim-strong: rgba(46, 38, 26, 0.55);
    --scrim-drawer: rgba(46, 38, 26, 0.35);
    --shadow-soft: 0 10px 30px rgba(46, 38, 26, 0.10);
    --shadow-pop: 0 18px 48px rgba(46, 38, 26, 0.14);
    --shadow-modal: 0 28px 80px rgba(46, 38, 26, 0.18);
    --shadow-rail: 24px 0 54px rgba(46, 38, 26, 0.16);
    --shadow-tooltip: 0 14px 40px rgba(46, 38, 26, 0.14);

    --bar-normal: #6d46c9;
    --bar-critical: #0e7c66;
    --bar-summary: #a89a84;
    --bar-progress: rgba(255, 255, 255, 0.42);

    --file-pdf: #a5352b;
    --file-doc: #2d5fa8;
    --file-mpp: var(--bar-normal);
    --file-xlsx: #2d7048;
}

* {
    box-sizing: border-box;
}

html,
body {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
}

body.modal-open { overflow: hidden; }

body {
    display: flex;
    min-width: 320px;
    overflow: hidden;
    background-color: var(--bg);
    color: var(--text-primary);
    -webkit-font-smoothing: antialiased;
    /* 换主题时底色滑过去（2026-09-08，配动态背景的颜色过渡）：光幕在滑、底色一跳就穿帮 */
    transition: background-color 700ms ease;
}

button,
textarea {
    font: inherit;
}

button,
a {
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
}

button:not(:disabled),
a {
    cursor: pointer;
}

button:disabled {
    cursor: not-allowed;
}

/* 全站唯一的按下反馈，一次加给每个按钮。用 opacity 而不是 transform：
   很多按钮自己的 hover 态已经在用 translateY 做提起效果，opacity 是正交的，
   不会跟那些声明打架、也不会在 hover→active 切换时出现残影跳变。 */
button:not(:disabled):active,
a:active {
    opacity: .82;
}

svg {
    display: block;
    fill: none;
    stroke: currentColor;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 1.6;
}

.mono {
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--text-primary);
}

.rail {
    width: var(--drawer-width);
    min-width: var(--drawer-width);
    display: flex;
    flex: 0 0 var(--drawer-width);
    flex-direction: column;
    overflow: hidden;
    position: relative;
    z-index: 10;
    border-right: 1px solid var(--border);
    /* 左侧栏轻透底色，保留背景纹理；阅读区与全局背景强度独立保持原样。 */
    background: color-mix(in srgb, var(--surface-1) 12%, transparent);
    backdrop-filter: blur(3px) saturate(1.2);
    -webkit-backdrop-filter: blur(3px) saturate(1.2);
    transition: width var(--dur-3) var(--ease), min-width var(--dur-3) var(--ease), flex-basis var(--dur-3) var(--ease);
}

.rail.is-collapsed {
    width: 64px;
    min-width: 64px;
    flex-basis: 64px;
}

.drawer-head {
    min-height: 72px;
    display: flex;
    align-items: center;
    gap: 10px;
    /* 上下 14 是算出来的：44px 的品牌标 + 14×2 正好等于 72 的 min-height，
       标放大了但头部总高不变，下面整条侧栏的垂直节奏不用跟着动 */
    padding: 14px;
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
}

/*
 * 品牌标是一张固定色的 PNG（#1155A1 深蓝），直接透在侧栏底上：没有底板、边框、阴影、渐变、光圈，
 * 悬停也不加框——它是品牌标不是按钮；键盘焦点提示走全局 a:focus-visible 那条描边，不另写。
 * 白底板 2026-09-15 撤掉（用户要求）。当年垫它是为对比度：这个蓝在暗色 surface-2 上只有 2.23:1
 *（幽谷暗色）/ 2.20:1（墨蓝夜航）。现在改成暗色主题按 --logo-lift 提亮——brightness(1.35) 是纯乘法、
 * 色相不动，得 #1773D9，对 surface-1 3.87:1 / surface-2 3.53:1，两套暗色都过图形 3:1 那条线；
 * 浅色主题 --logo-lift: none，保留原始品牌蓝（surface-1 上 7.1:1）。四个值在顶部各主题块里。
 * 图片本身已经洗掉白色毛边（design-system/eijie-agent/preview/logo-review/rebuild-logo.py）：
 * 原件是白底抠图，每条边带一圈混白像素，底板一撤就是一圈白边——只去 CSS 边框只做了一半。
 */
.rail-logo {
    width: 44px;
    height: 44px;
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    /* 图本身已经裁掉了透明留白（见 img/logo-original.png 是未裁的原件），
       所以这里只留 3px 呼吸位，不能再靠 padding 撑——那样等于把留白加回去 */
    padding: 3px;
    /* 圆角只服务焦点描边（outline 跟着 border-radius 走），透明底上它本身看不见 */
    border-radius: var(--radius-md);
    background: transparent;
    border: 0;
}

.rail-logo img {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: contain;
    filter: var(--logo-lift);
}

.drawer-brand { min-width: 0; display: flex; align-items: center; flex: 1; overflow: hidden; transition: opacity 140ms ease, transform 180ms ease; }
.drawer-brand strong {
    font-size: 21px;
    font-weight: 700;
    /* 中文字面本来就密，放大后收一点字距才不显松散；1 是为了让单行标题垂直居中不带多余行高 */
    letter-spacing: .04em;
    line-height: 1;
    color: var(--text-primary);
}

.drawer-toggle,
.mobile-drawer-button {
    width: 44px;
    height: 44px;
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--text-muted);
    transition: background 160ms ease, color 160ms ease, transform 220ms cubic-bezier(.16, 1, .3, 1);
}
.drawer-toggle:hover, .mobile-drawer-button:hover { background: var(--surface-2); color: var(--text-primary); }
.drawer-toggle svg, .mobile-drawer-button svg { width: 19px; height: 19px; }
.rail.is-collapsed .drawer-head { justify-content: center; padding: 14px 10px; }
.rail.is-collapsed .rail-logo { display: none; }
.rail.is-collapsed .drawer-brand { width: 0; opacity: 0; transform: translateX(-8px); }
.rail.is-collapsed .drawer-toggle { transform: rotate(180deg); }

/* 项目专栏是常驻列表，本身就撑开侧栏剩余高度，把本设备会话/设置推到底部，
   不需要额外的 spacer 元素。 */
/* 原生滚动条放在侧栏最左边；内容恢复 LTR，避免图标、日期和文字顺序反转。 */
.drawer-history { min-height: 0; flex: 1; overflow-y: auto; padding: 2px 0 12px; direction: rtl; }
.drawer-history > * { direction: ltr; }
.rail.is-collapsed .drawer-history { opacity: 0; pointer-events: none; transition: opacity 90ms ease; }

.drawer-foot { min-height: 54px; display: flex; align-items: center; gap: 9px; padding: 0 20px; border-top: 1px solid var(--border); color: var(--text-faint); font-size: 12px; white-space: nowrap; overflow: hidden; }
.drawer-presence { width: 6px; height: 6px; flex: 0 0 auto; border-radius: 50%; background: var(--text-faint); transition: background-color 300ms ease, box-shadow 300ms ease; }
/* 三态：灰=还没联络过；薄荷=通着；琥珀慢闪=上次请求失败/浏览器离线，等下次请求自动翻回 */
.drawer-presence[data-state="ok"] { background: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.drawer-presence[data-state="down"] { background: var(--caution); box-shadow: 0 0 0 3px var(--caution-dim); animation: presence-blink 2.4s ease-in-out infinite; }
@keyframes presence-blink { 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) { .drawer-presence[data-state="down"] { animation: none; } }
/* 沉浸模式按钮：住在「本设备会话」右侧；抽屉收成窄条时跟连接灯上下叠放，退出入口不随两栏消失 */
.drawer-immersive { margin-left: auto; width: 28px; height: 28px; flex: 0 0 auto; display: grid; place-items: center; border: 0; border-radius: 8px; background: transparent; color: var(--text-faint); cursor: pointer; transition: color 140ms ease, background 140ms ease; }
.drawer-immersive:hover { color: var(--text-primary); background: var(--surface-2); }
.drawer-immersive svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.drawer-immersive .immersive-exit { display: none; }
.drawer-immersive[aria-pressed="true"] { color: var(--accent); }
.drawer-immersive[aria-pressed="true"] .immersive-enter { display: none; }
.drawer-immersive[aria-pressed="true"] .immersive-exit { display: block; }
/* 退出登录（2026-09-21）：跟沉浸按钮同款；登录关着时 hidden。它排在沉浸按钮前面，露出时把 margin-left:auto 接过来 */
.drawer-logout { margin-left: auto; width: 28px; height: 28px; flex: 0 0 auto; display: grid; place-items: center; border: 0; border-radius: 8px; background: transparent; color: var(--text-faint); cursor: pointer; transition: color 140ms ease, background 140ms ease; }
.drawer-logout:hover { color: var(--text-primary); background: var(--surface-2); }
.drawer-logout svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.drawer-logout[hidden] { display: none; }
/* 登录第二阶段：普通成员建不了项目、改不了名、删不了——按钮不露，服务端另有一道 */
.is-member-only #new-project-button, .is-member-only .project-section-rename, .is-member-only .project-section-delete,
.is-member-only .project-empty-flow { display: none; }
/* 智管联动（2026-09-22）：接入/更换/解除是管理员专属（服务端另有一道），成员态不露 */
/* 用户管理 / 项目管理（2026-09-21）：沿用"行"的语法；同日美化（用户："美观好用、不那么 AI 化"）——
   头像圆、只标值得标的小标签、行尾一个「⋯」代替一排描边按钮、成员小片带头像、
   虚线「＋ 添加成员」代替原生下拉框。颜色全走 token，四个主题各自跟着走。 */
.settings-group-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.settings-group-head .settings-group-title { margin-top: 4px; }
/* 新建账号：一块浅底内嵌区，不是浮在行之间的一堆输入框 */
.admin-form { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; margin: 4px 0 14px; padding: 16px 18px 14px; border: 1px solid var(--border-soft, var(--border-strong)); border-radius: 14px; background: color-mix(in srgb, var(--surface-1) 70%, transparent); }
.admin-form[hidden] { display: none; }
.admin-form label { display: flex; flex-direction: column; gap: 6px; font-size: 12px; color: var(--text-secondary); }
.admin-form input, .admin-form select { min-height: 38px; padding: 0 10px; border: 1px solid var(--border-strong); border-radius: 9px; background: var(--surface-2); color: var(--text-primary); font: inherit; font-size: 13px; outline: none; }
.admin-form input:focus, .admin-form select:focus { border-color: var(--input-border-focus); box-shadow: 0 0 0 3px var(--focus-ring); }
.admin-form-actions { grid-column: 1 / -1; display: flex; gap: 8px; justify-content: flex-end; }

/* 账号行：名字 用户名 标签… | ⋯（单字头像 2026-09-21 按用户要求去掉） */
.admin-user-row { align-items: center; gap: 14px; }
.admin-user-row.is-off .admin-user-name { color: var(--text-muted); text-decoration: line-through; text-decoration-color: var(--text-faint); }
.admin-user-main { min-width: 0; }
.admin-user-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; min-width: 0; }
.admin-user-name { font-size: 14px; font-weight: 600; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.admin-user-handle { font-size: 12px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.admin-tag { display: inline-flex; align-items: center; gap: 5px; padding: 1px 8px; border-radius: 999px; font-size: 11px; line-height: 18px; color: var(--text-secondary); background: var(--surface-2); }
.admin-tag.is-admin { color: var(--accent); background: var(--accent-tint); }
.admin-tag.is-me { color: var(--text-muted); background: transparent; box-shadow: inset 0 0 0 1px var(--border-strong); }
.admin-tag.is-pending { color: var(--caution); background: var(--caution-dim); }
.admin-tag.is-pending::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.admin-tag.is-off { color: var(--text-muted); background: var(--surface-2); }
.admin-project-count { font-size: 12px; color: var(--text-muted); }
.admin-project-count.is-empty { color: var(--text-faint); }

/* 成员小片：名字 + 淡的 ×；「＋ 添加成员」是虚线的同款小片 */
.member-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; }
.member-chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 6px 4px 11px; border-radius: 999px; background: var(--surface-2); font-size: 12px; color: var(--text-primary); }
.member-chip.is-disabled { opacity: .5; }
.member-chip.is-disabled .member-chip-name { text-decoration: line-through; }
.member-chip-remove { display: inline-grid; place-items: center; width: 18px; height: 18px; border: 0; border-radius: 50%; background: transparent; color: var(--text-faint); cursor: pointer; padding: 0; font-size: 14px; line-height: 1; transition: color 120ms ease, background 120ms ease; }
.member-chip-remove:hover { color: var(--danger-text); background: var(--danger-dim); }
.member-add { display: inline-flex; align-items: center; gap: 6px; min-height: 28px; padding: 0 12px 0 10px; border: 1px dashed var(--border-strong); border-radius: 999px; background: transparent; color: var(--text-muted); font: inherit; font-size: 12px; cursor: pointer; transition: color 120ms ease, border-color 120ms ease, background 120ms ease; }
.member-add > span { font-size: 13px; line-height: 1; }
.member-add:hover, .member-add[aria-expanded="true"] { color: var(--accent); border-color: var(--accent-tint-border); background: var(--accent-dim); }
.member-add:disabled { opacity: .5; cursor: wait; }
/* 候选人菜单：名字 + 用户名小字 */
.member-picker { min-width: 200px; }
.member-picker .member-picker-item { display: flex; align-items: center; gap: 9px; padding: 6px 8px; }   /* 压过后面 .materials-rail-menu-item 的 display: block */
.member-picker-name { color: var(--text-primary); }
.member-picker-handle { margin-left: auto; color: var(--text-faint); font-size: 11px; }
@media (max-width: 720px) { .admin-form { grid-template-columns: 1fr; } .admin-user-row { gap: 10px; } }
.drawer-logout:not([hidden]) ~ .drawer-immersive { margin-left: 0; }
.rail.is-collapsed .drawer-logout { margin-left: 0; }
.rail.is-collapsed .drawer-foot { padding: 10px 0; min-height: 0; flex-direction: column; justify-content: center; gap: 10px; }
/* 灯旁那行字（2026-09-23）：状态 + 名字两段。展开时一行「已连接 · 张三」，中点由样式补；左栏收成 64px 窄条
   （沉浸模式）时上下两行居中，名字长了按字折、最多三行——挤在一行会被截成「接 · 量维科技」 */
.drawer-presence-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.drawer-presence-who::before { content: ' · '; }
.rail.is-collapsed .drawer-presence-label { display: flex; flex-direction: column; align-items: center; gap: 2px; max-width: 56px; white-space: normal; text-align: center; line-height: 1.35; }
.rail.is-collapsed .drawer-presence-who::before { content: none; }
.rail.is-collapsed .drawer-presence-who { max-width: 100%; word-break: break-all; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; }
.rail.is-collapsed .drawer-immersive { margin-left: 0; }

.main {
    min-width: 0;
    display: flex;
    flex: 1;
    flex-direction: column;
    position: relative;
    z-index: 5;
}

/* 对话行：单行紧凑密度，标题占满、时间贴右。项目手风琴里可能好几个项目同时展开，
   不再按日期分组——分组标题在这个密度下只会占地方。 */
.conversation-row { position: relative; }
.conversation-item {
    width: 100%;
    min-height: 34px;
    display: flex;
    align-items: center;
    gap: 8px;
    /* 左 22 是圆点那一列的固定宽度（列宽与标题长短无关，标题永远从 51px 对齐线起排，
       跟展开区其它四组齐平）；圆点本身绝对定位，不参与 flex，挤不到标题和时间。 */
    padding: 0 34px 0 22px;
    position: relative;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-secondary);
    text-align: left;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.conversation-item:hover { background: var(--surface-2); color: var(--text-primary); }
/* 当前项不叠左侧强调竖线：树线 + 圆点 + 选中底色三重强调只会互相打架，
   身份交给填充的强调色圆点，"被选中"交给底色和文字色（不单靠颜色）。 */
.conversation-item.is-current {
    background: var(--surface-2);
    color: var(--text-primary);
}
.conversation-item-title { min-width: 0; flex: 1; overflow: hidden; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }

/* 被省略的名字悬停滚动（2026-09-24，chat.js startNameMarquee）：两侧栏的项目名/对话标题/资料名/文件夹名共用。
   滚动时不要「…」，两端淡出；轨道 = 原文 + 空隙 + 副本，左移一份宽度正好首尾接上。时间轴（每圈开头停 0.5 秒、
   之后匀速 40 像素/秒）由脚本按名字长度现算，这里只管长相。 */
/* 写 span.is-marquee 而不是 .is-marquee：四个名字各自那条规则（同一个特异性、位置更靠后）会把 clip 盖回 ellipsis，
   Chrome 看着没事，但按规范省略号可以把整条轨道藏掉只剩「…」 */
span.is-marquee {
    text-overflow: clip;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 6px, #000 calc(100% - 14px), transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 6px, #000 calc(100% - 14px), transparent 100%);
}
.name-marquee-track { display: inline-flex; will-change: transform; }
.name-marquee-copy { flex: 0 0 auto; padding-right: 2.5em; white-space: nowrap; }
.conversation-item-time { flex: 0 0 auto; font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.conversation-delete {
    width: 24px;
    height: 24px;
    position: absolute;
    right: 5px;
    top: 50%;
    transform: translateY(-50%);
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--text-faint);
    opacity: 0;
    transition: opacity var(--dur-1) var(--ease-soft), background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.conversation-row:hover .conversation-delete, .conversation-delete:focus-visible { opacity: 1; }
.conversation-delete:hover { background: var(--danger-dim); color: var(--danger); }
.conversation-delete svg { width: 13px; height: 13px; stroke-width: 1.6; }
/* 双击标题就地改名。输入框浮在行上、不顶掉按钮：按钮还在原位撑着行高，树线的圆点
   （.conversation-item::before）也就还画得出来——把按钮 display:none 会让圆点一起消失，
   改名途中那一行看着像从树上掉下来了。
   左 18 + 内边距 4 = 22，正好是标题原来的起点（叠上手风琴的 42 就是全局 51px 对齐线），
   换进换出时文字不跳位；左边 0–18 那段留白让圆点露出来。 */
.conversation-rename-input {
    position: absolute;
    left: 18px;
    right: 4px;
    top: 3px;
    bottom: 3px;
    padding: 0 6px 0 4px;
    border: 0;
    border-radius: var(--radius-sm);
    /* 用 box-shadow 而不是 border 描边：1px 边框会把内容盒挤窄 1px，上面那笔对齐账就得重算 */
    box-shadow: 0 0 0 1px var(--accent);
    background: var(--surface-2);
    color: var(--text-primary);
    font: inherit;
    font-size: 13px;
    line-height: 1;
    outline: none;
}

.menu-empty { margin: 6px 9px; color: var(--text-faint); font-size: 12.5px; line-height: 1.6; }

.mobile-drawer-button, .drawer-scrim { display: none; }

/* 顶栏不画分割线：与画布同色，只靠留白和内容列的起始位置分区 */
.topbar {
    height: 56px;
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    padding: 0 20px;
    /* 顶栏不再填底色（2026-09-08）：它在对话区上方、不跟正文重叠，透明才不会在光幕上切出一道横条 */
    background: transparent;
}

.topbar-left {
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 10px;
}

.model-name {
    /* min-width: 0 是必需的，不是装饰：它是 .model-picker-button 这个 flex 容器的子项，
       flex 子项默认 min-width:auto = 不肯缩到内容宽度以下，于是下面那三行省略号设置
       永远不会触发，整个按钮被撑到内容那么宽、顶出父容器，把右侧的日期压在下面。
       抽屉打开、主区变窄时必现（实测按钮 244px 溢出 148px 的父容器 88px）。 */
    min-width: 0;
    overflow: hidden;
    font-size: 13px;
    font-weight: 500;
    letter-spacing: 0.2px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.status-ring {
    width: 14px;
    height: 14px;
    display: grid;
    place-items: center;
    position: relative;
}

/* 已配置模型时点外多一圈静态光晕；未配置就只剩红点，不做任何动画 */
.status-ring::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: var(--accent-dim);
    opacity: 0;
    transition: opacity var(--dur-2) var(--ease-soft);
}

.status-ring.is-configured::before {
    opacity: 1;
}

.status-dot {
    width: 7px;
    height: 7px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--accent);
    position: relative;
    z-index: 1;
}

.status-dot.is-offline {
    background: var(--danger);
}

.status-dot.is-streaming {
    animation: pulse 1.1s ease-in-out infinite;
}

.topbar-meta {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 14px;
    color: var(--text-faint);
    font-size: 12px;
}

/* 天气挂件：跟时间同一梯队的环境信息，中性色；点击去设置里改城市。 */
.topbar-weather {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin: -4px 0;
    padding: 3px 8px;
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--text-faint);
    font: inherit;
    white-space: nowrap;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}

.topbar-weather:hover {
    background: var(--surface-2);
    color: var(--text-primary);
}

.topbar-weather[hidden] { display: none; }

.topbar-weather svg {
    width: 15px;
    height: 15px;
    flex: 0 0 auto;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* 划选是交互状态，跟主题走——不管它就是操作系统的外来蓝 */
::selection {
    background: color-mix(in srgb, var(--accent) 25%, transparent);
}

.thread-wrap[hidden] { display: none; }

.thread-wrap {
    min-height: 0;
    display: flex;
    flex: 1;
    justify-content: center;
    /* 不能用默认的 stretch：那会把 .thread 的高度钉在滚动容器高度上，正文溢出盒子，
       底部 padding 留在盒内而不在内容末尾，scrollHeight 收不进去 → 最后一段被输入框压住 */
    align-items: flex-start;
    overflow-x: hidden;
    overflow-y: auto;
    scrollbar-color: var(--surface-2-hover) transparent;
    scrollbar-width: thin;
    /* 流式界面必须关掉浏览器滚动锚定：正文整块重写时锚定会自作主张挪 scrollTop，
       产生既不是程序回声、也不是用户动作的滚动事件，跟随开关会被它误关 */
    overflow-anchor: none;
}

.thread-wrap::-webkit-scrollbar {
    width: 10px;
}

.thread-wrap::-webkit-scrollbar-thumb {
    border: 3px solid transparent;
    border-radius: var(--radius-pill);
    background: var(--surface-2-hover);
    background-clip: content-box;
}

.thread-wrap::-webkit-scrollbar-thumb:hover {
    background: var(--scrollbar-thumb-hover);
    background-clip: content-box;
}

/* 对话列整体左移 80px（用户定案 2026-08-27）：居中态下正文到常驻文件栏的缝只有 6px，
   贴得太死。实现用"右侧内边距 160"——居中把它对半分，等效左移 80。只挂在常驻栏
   存在的宽度档（≥1600），窄窗口本来就没有并排、零影响；1600 档主区 1332−160=1172
   仍放得下 1024 正文列，列宽不缩。输入区同款规则见 .composer-wrap。 */
@media (min-width: 1600px) {
    .thread-wrap {
        padding-right: 160px;
    }

    /* 输入区的同款三条规则**不在这里**，在 .composer-wrap 基础规则的后面——原因见那处注释。 */

    /* 画布开着的时候把这 160 退回去：它买的是“到常驻文件栏的缝”，而那条栏在
       body.artifact-open 下本就 display:none（见 .materials-rail）——缝的对面没东西了，
       160 就只是白白吃掉宽度。而且这条规则挂在**视口**宽度上，画布并排时对话主栏
       只剩 480–730，此时再扣 160 会把正文压到 264px（1680 视口开画布实测）。 */
    body.artifact-open .thread-wrap { padding-right: 0; }
    /* 常驻栏被收起时同样退回去：缝的对面没东西了。状态类在 <html> 上，理由见 .materials-rail 处。 */
    :root.materials-collapsed .thread-wrap { padding-right: 0; }
}

/* 窄窗口给资料栏把手让位（2026-09-24）。<1600 时资料栏收成右上角一枚浮着的把手（右起 14、宽 34，
   占掉最右 48），对话列却一直铺到离主区右边 34（正文列内边距 24 + 滚动条约 10）——差的 14 像素让
   靠右的附件卡「查看」、提问气泡滚过时被把手压住（1280 宽左栏展开、1024 宽左栏收起实测）。
   让位量随主区宽度连续变：主区 ≥ 正文列 + 44 时为 0（居中本来就留够了缝，1440 宽一个像素不动），
   再窄逐渐加到 44 封顶（把手和正文之间 8 到 30 的缝）。输入区同一个量，写在它基础规则后面，
   左边线跟正文对齐不破。画布开着时资料栏整个不显示，不让。手机另有一套，这里不管。 */
@media (min-width: 721px) and (max-width: 1599px) {
    body:has(#materials-rail:not([hidden])):not(.artifact-open) {
        --puck-gutter: clamp(0px, calc(var(--column-thread) + 44px - 100%), 44px);
    }

    body:has(#materials-rail:not([hidden])):not(.artifact-open) .thread-wrap {
        padding-right: var(--puck-gutter);
    }
}

.thread {
    width: 100%;
    max-width: var(--column-thread);
    /* 底部留白跟随输入框实测高度：换行、附件条都会把浮层撑高，写死的常量必然被压住 */
    padding: 20px 24px calc(var(--composer-h) + 8px);
}

.empty-state[hidden] {
    display: none;
}

.empty-state {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 64vh;
    animation: rise var(--dur-3) var(--ease) both;
}

.empty-greeting {
    margin: 0 0 8px;
    color: var(--text-primary);
    font-size: 30px;
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.25;
}

.empty-hint {
    margin: 0 0 28px;
    max-width: 46ch;
    color: var(--text-muted);
    font-size: 15px;
    line-height: 1.65;
}

.msg {
    display: flex;
    /* 轮次靠间距分组：回答之后是下一轮，拉开；问题和它的回答是一组，贴紧（下面 .msg.user）。
       旧状态一律 30px，长对话是等距一串块，只能靠气泡方向数轮次。 */
    margin-bottom: 46px;
    animation: rise var(--dur-3) var(--ease) both;
}

/* 打开历史对话时整批消息一次画完——进场动画是给"新消息到来"的信号，
   整屏齐动读出来是闪烁。回放画出的消息不播，实时新增照旧。 */
.msg.is-replay {
    animation: none;
}

.msg.user {
    justify-content: flex-end;
    margin-bottom: 14px;
}

.msg.user .signal-rail,
.msg.user .message-label,
.msg.user .copy-message-button {
    display: none;
}

/* 共用对话（2026-09-21）：提问标谁问的——登录着才有名字，标签露出来、用正文字体不用等宽 */
.msg.user.has-author .message-label { display: block; margin: 0 0 4px; color: var(--text-muted); font-family: var(--font-sans); font-size: 11px; letter-spacing: 0; }
/* 别人那一轮还没写完：不是失败，不标红不给重跑，只是一句说明 */
.msg.assistant.is-in-flight .message-content { color: var(--text-muted); }
.msg.assistant.is-in-flight .copy-message-button, .msg.assistant.is-in-flight .speak-message-button { display: none; }

/* 提问用中性气泡：内容本身已经是重点，不需要再上色 */
.msg.user .message-shell {
    /* Absolute cap keeps questions compact now that the thread column is wider */
    max-width: min(76%, 620px);
    padding: calc(11px * var(--msg-scale)) calc(17px * var(--msg-scale));
    border-radius: var(--radius-lg);
    background: var(--bubble-user);
}

.msg.user .message-content {
    color: var(--text-primary);
    font-size: calc(15px * var(--msg-scale));
    line-height: 1.7;
    overflow-wrap: anywhere;
    white-space: pre-wrap;
}

.msg.assistant {
    align-items: flex-start;
    gap: 14px;
}

/*
 * 全站唯一的动效元素：回答左侧的信号线。
 * 静止 = 一条中性细线；流式输出时才变绿并向下走光。动的东西只剩它，所以它有意义。
 */
.signal-rail {
    width: 2px;
    min-height: 36px;
    align-self: stretch;
    flex: 0 0 auto;
    margin-top: 6px;
    border-radius: var(--radius-pill);
    background: var(--border-strong);
    position: relative;
    overflow: hidden;
    transition: background var(--dur-3) var(--ease-soft);
}

.msg.assistant.is-streaming .signal-rail {
    background: var(--accent-dim);
}

.signal-rail::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: -40%;
    height: 40%;
    background: linear-gradient(180deg, transparent, var(--accent), transparent);
    opacity: 0;
}

.msg.assistant.is-streaming .signal-rail::after {
    opacity: 1;
    animation: signal-shimmer 1.4s linear infinite;
}

@keyframes signal-shimmer {
    0% { top: -40%; }
    100% { top: 100%; }
}

.message-shell {
    min-width: 0;
    max-width: 100%;
    flex: 1;
}

.msg.user .message-shell {
    flex: 0 1 auto;
}

.message-label {
    margin: 0 0 7px;
    color: var(--text-faint);
    font-family: var(--font-mono);
    font-size: 10.5px;
    font-weight: 400;
    letter-spacing: 0.1em;
}

.assistant-body,
.msg.assistant .message-content {
    color: var(--text-primary);
    font-size: calc(15.5px * var(--msg-scale));
    line-height: 1.78;
    overflow-wrap: anywhere;
}

/* 正文旁白（2026-09-30）：最后一次工具调用之前的过程话灰一档，结论正常。字号行距不动，只换颜色 */
.message-interim {
    color: var(--text-muted);
}

.message-content > :first-child {
    margin-top: 0;
}

.message-content > :last-child {
    margin-bottom: 0;
}

/* 回答正文不再单独卡阅读行长（用户定案 2026-08-27）：正文块跟着 .message-shell 铺满对话列，
   右缘落在 .thread 的 24 内边距上，与代码块、表格、嵌入成果同一条右线；左缘不动（仍在信号线 + 14 间距之后）。
   宽度全由布局给：.thread 是 width:100% / max-width:--column-thread，所以窗口变窄、
   右侧画布并排把主区挤窄时正文自动跟着收，不用再为断点写一遍定值。 */

.message-content p,
.message-content ul,
.message-content ol,
.message-content blockquote,
.message-content pre,
.message-content table {
    margin: 0 0 calc(13px * var(--msg-scale));
}

.message-content h1,
.message-content h2,
.message-content h3,
.message-content h4 {
    color: var(--text-heading);
    font-weight: 600;
    line-height: 1.4;
}

/* 梯子每级至少 1.5px：中文没有斜体和大小写可借力，字号字重是层级的全部载体。
   旧梯子 21/18/16/14.5 对 15.5 正文——h3 只比正文大 0.5px、h4 比正文还小，层级是倒的。
   最低级标题与正文同号、靠字重和标题色区分，是成熟排法。上边距随级别递减。 */
.message-content h1 { font-size: calc(21px * var(--msg-scale)); margin: calc(26px * var(--msg-scale)) 0 calc(10px * var(--msg-scale)); }
.message-content h2 { font-size: calc(18.5px * var(--msg-scale)); margin: calc(26px * var(--msg-scale)) 0 calc(10px * var(--msg-scale)); }
.message-content h3 { font-size: calc(17px * var(--msg-scale)); margin: calc(20px * var(--msg-scale)) 0 calc(8px * var(--msg-scale)); }
.message-content h4 { font-size: calc(15.5px * var(--msg-scale)); margin: calc(20px * var(--msg-scale)) 0 calc(8px * var(--msg-scale)); }

.message-content ul,
.message-content ol {
    padding-left: calc(21px * var(--msg-scale));
}

.message-content li {
    margin: calc(5px * var(--msg-scale)) 0;
}

.message-content li::marker {
    color: var(--text-faint);
}

/* 对话里的超链接：蓝色、不带下划线（2026-09-17 用户定的）。颜色是各主题自己的 --link，不借 --accent（那个只给状态）；
   悬停只把蓝往正文色靠一点，仍然没有下划线；键盘焦点走全局的描边 */
.message-content a {
    color: var(--link);
    text-decoration: none;
    transition: color var(--dur-1) var(--ease-soft);
}

.message-content a:hover {
    color: color-mix(in srgb, var(--link) 78%, var(--text-primary));
    text-decoration: none;
}

.message-content code {
    padding: 1.5px 6px;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text-primary);
    font-family: var(--font-mono);
    font-size: calc(13px * var(--msg-scale));
}

.message-content pre {
    max-width: 100%;
    overflow-x: auto;
    padding: 15px 16px;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface-1);
}

.message-content pre code {
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-code);
    line-height: 1.65;
}

.message-content blockquote {
    padding: 4px 14px;
    border-left: 2px solid var(--border-strong);
    color: var(--text-muted);
}

/* 表格是横线极简风：四边框会把工程数据表渲染成"嵌进来的小 Excel"，
   与四周无边框大留白的正文断裂。只留横向发丝线，表头靠字重和下缘重线立住；
   数字必须等宽——上下行的量对不齐，工程表就没法扫读。外层横向滚动容器不动。 */
.message-content table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    border-collapse: collapse;
    font-size: calc(14px * var(--msg-scale));
    font-variant-numeric: tabular-nums;
}

.message-content th,
.message-content td {
    min-width: 110px;
    padding: 10px 14px;
    border: 0;
    border-bottom: 1px solid var(--border);
    text-align: left;
}

.message-content th {
    font-weight: 600;
    border-bottom: 1px solid var(--border-strong);
}

.message-content tbody tr:last-child td {
    border-bottom: 0;
}

/*
 * 流式光标。两件事分开放，各有各的理由：
 *
 * 1) 位置挂在<b>最后一个块元素</b>的行尾，不是内容框末尾——挂内容框上它会自己占一行。
 * 2) 起伏靠 --caret-opacity/--caret-scale，动画挂在 .message-content 上，<b>不挂在伪元素自己身上</b>。
 *    因为流式期间 updateAssistantMessage 每一帧都重写 innerHTML，`> :last-child::after`
 *    这个伪元素跟着每帧销毁重建，animation 每次从 0 重新开始，永远走不到 50% 那个
 *    opacity:0——实测 2.2 秒 128 帧里采到的不透明度只有 "1"，光标是常亮的，根本不闪。
 *    .message-content 这个元素本身不会被替换（只换它的 innerHTML），动画挂它身上才连续；
 *    自定义属性会继承给伪元素，于是伪元素怎么重建都读得到当前这一刻的值。
 *
 * `:empty` 那条覆盖的是正文还没有任何内容的阶段——思考和工具调用往往是整轮里最长的等待，
 * 那时候原来连光标都没有，界面看着像卡死。
 */
@property --caret-opacity {
    syntax: "<number>";
    inherits: true;
    initial-value: 1;
}

@property --caret-scale {
    syntax: "<number>";
    inherits: true;
    initial-value: 1;
}

/* 呼吸而不是闪烁：透明度和尺寸缓慢起伏、永不熄灭。原来是 7×15 的方块 step-end
   硬开关（亮—灭—亮，没有过渡），一股终端等输入的味道；呼吸的圆点读作"正在思考着写"。
   最低只到 0.45——彻底消失的瞬间会被眼睛读成闪烁，那正是要去掉的东西。 */
@keyframes caret-breathe {
    50% {
        --caret-opacity: 0.45;
        --caret-scale: 0.86;
    }
}

.msg.assistant.is-streaming .message-content {
    animation: caret-breathe 2s var(--ease-soft) infinite;
}

.msg.assistant.is-streaming .message-content > :last-child:not(ul):not(ol)::after,
/* 列表要跟到最后一项的<b>行尾</b>：挂在 ul/ol 上的话，它们是块级的，光标会掉到列表下面
   单独占一行，看着像正文里多出来一个色块，不像光标。表格/代码块没有这个问题——
   它们本来就是独立的块，后面另起一行反而是对的。 */
.msg.assistant.is-streaming .message-content > ul:last-child > li:last-child::after,
.msg.assistant.is-streaming .message-content > ol:last-child > li:last-child::after,
.msg.assistant.is-streaming .message-content:empty::after {
    content: "";
    width: 9px;
    height: 9px;
    display: inline-block;
    margin-left: 5px;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 10px var(--accent-glow);
    opacity: var(--caret-opacity);
    transform: scale(var(--caret-scale));
}

/* 动画偏好关掉时呼吸也停：光点常亮即可，状态信息（正在生成）一点不丢 */
@media (prefers-reduced-motion: reduce) {
    .msg.assistant.is-streaming .message-content {
        animation: none;
    }
}

.msg.error .signal-rail {
    background: linear-gradient(180deg, var(--danger), transparent);
}

.msg.error .message-label,
.msg.error .message-content {
    color: var(--danger-text);
}

.copy-message-button {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 7px;
    padding: 0 8px;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--text-faint);
    font-size: 11px;
    /* 2026-08-25 起常驻可见（此前悬停才显形）：用户明确要求所有动作按钮常显。
       颜色停在最淡一档，靠"淡"而不是"藏"来控噪；悬停整条回复时也不再有显形动画 */
    transition: color 150ms ease, background 150ms ease;
}

.copy-message-button:hover {
    background: var(--surface-2);
    color: var(--text-muted);
}

.copy-message-button svg {
    width: 14px;
    height: 14px;
}

/* 正在朗读是状态（跟流式光标同级），用薄荷色。opacity 是保险：按钮如今常驻可见，
   但"声音在响，停止入口必须看得见"这条约束不跟随常驻决定走——哪天常驻改回去，这里也不许藏 */
.speak-message-button.is-speaking {
    opacity: 1;
    color: var(--accent);
}

/* 播放中两道声波轮流荡出去：喇叭本体常亮，内波先亮、外波隔半拍跟上，
   读作"声音正在出来"。节奏跟流式呼吸光点同一个语言，图标不重建，动画挂着安全
   （光标那次的教训是挂在每帧重建的节点上，这里不是）。 */
@keyframes speak-wave {
    0%, 100% { opacity: 0.25; }
    30% { opacity: 1; }
}

.speak-message-button.is-speaking svg path:nth-of-type(2) {
    animation: speak-wave 1.4s var(--ease-soft) infinite;
}

.speak-message-button.is-speaking svg path:nth-of-type(3) {
    animation: speak-wave 1.4s var(--ease-soft) 0.35s infinite;
}

/* 动画偏好关掉时波形不闪：常亮薄荷色已经足够表达"在读" */
@media (prefers-reduced-motion: reduce) {
    .speak-message-button.is-speaking svg path:nth-of-type(2),
    .speak-message-button.is-speaking svg path:nth-of-type(3) {
        animation: none;
    }
}

/* 起步问题：眉标是真实的能力分区，不是装饰编号 */
/* 输入框浮在对话流之上，正文从它下面滚过去，靠渐隐收边而不是一条硬分割线 */
.composer-wrap[hidden] { display: none; }

.composer-wrap {
    position: absolute;
    z-index: 15;
    left: 0;
    right: 0;
    bottom: 0;
    /* Container for the token pill's gutter check. Must be a container query, not a
       viewport one: the usable width here shrinks when the rail expands *and* when
       the artifact drawer opens (up to 56vw), which @media cannot see. */
    container: composer / inline-size;
    display: flex;
    justify-content: center;
    padding: 44px 24px 16px;
    /* 退出全屏后窗口压在任务栏底下时（--taskbar-overlap，见 measureTaskbarOverlap），输入框跟着抬高；写在简写之后才盖得住 */
    padding-bottom: calc(16px + var(--taskbar-overlap, 0px));
    background: linear-gradient(180deg, transparent, var(--bg) 52%);
    pointer-events: none;
}

/* 输入区跟着正文列一起左移 80（＝右侧内边距 24+160），三条规则跟 .thread-wrap 那三条一一对应。
   **它们必须写在上面那条基础规则的后面**：基础规则用的是 padding 简写，特异性一样、位置更靠后，
   写在前面的话简写会把 padding-right 整个盖掉——2026-08-27 那版就写在了前面，于是正文列左移了
   而输入框纹丝不动，两者错开约 70px；一直到 2026-09-04 做资料栏收放动画时才被夹具量出来
   （收放时正文在动、输入框不动，错位当场看得见）。 */
@media (min-width: 1600px) {
    .composer-wrap { padding-right: 184px; }
    body.artifact-open .composer-wrap { padding-right: 24px; }
    :root.materials-collapsed .composer-wrap { padding-right: 24px; }
}

/* 窄窗口给资料栏把手让位：跟 .thread-wrap 那条同一个量（--puck-gutter 的定义与原因在那边）。 */
@media (min-width: 721px) and (max-width: 1599px) {
    body:has(#materials-rail:not([hidden])):not(.artifact-open) .composer-wrap {
        padding-right: calc(24px + var(--puck-gutter));
    }
}

/* track 的宽度 = 对话列的内容宽（--column-thread 减去 .thread 两侧 24 内边距）。
   输入框在里面按块级默认靠左，左边缘与回答正文同线；空对话的问候语也是同一条线。
   窄视口时 track 收到 100%，和对话列一起贴着同样的 24 内边距，对齐关系不破。 */
.composer-track {
    width: 100%;
    max-width: calc(var(--column-thread) - 48px);
    pointer-events: none;
}

.composer {
    width: 100%;
    /* 封 880（2026-09-09 续十一，用户定的数）：左边缘压回答行首的信号线（续八），右边比对话列（976）短 96，
       给右侧的 token 胶囊留位置。之前封 768 右边空出两百多像素看着偏左；撑满 976 又把胶囊挤到框外太远 */
    max-width: 880px;
    pointer-events: auto;
    /* /技能 选择器和已指定技能的 pin chip 都挂在这个容器下面，需要一个定位上下文 */
    position: relative;
}

/* /技能 手动指定：弹层浮在输入框上方（composer 贴底部，往下弹会出视口）。 */
.skill-picker-menu {
    position: absolute;
    z-index: 40;
    bottom: calc(100% + 8px);
    left: 0;
    width: min(280px, 100%);
    max-height: 240px;
    overflow-y: auto;
    padding: 6px;
    border: 1px solid var(--border-strong);
    border-radius: 12px;
    background: var(--surface-float);
    box-shadow: inset 0 1px 0 var(--edge-light), var(--shadow-pop);
}
.skill-picker-menu[hidden] { display: none; }

.skill-picker-item {
    width: 100%;
    display: block;
    padding: 7px 9px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text-primary);
    font-size: 13px;
    text-align: left;
    transition: background var(--dur-1) var(--ease-soft);
}
.skill-picker-item:hover { background: var(--surface-2-hover); }

.skill-picker-empty {
    margin: 0;
    padding: 8px 9px;
    color: var(--text-faint);
    font-size: 12.5px;
}

/* 已指定技能的 pin——用户明确选过，一直挂着直到发送这轮或手动取消。 */
.skill-pin-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    width: fit-content;
    margin: 0 0 6px;
    padding: 4px 6px 4px 10px;
    border-radius: 999px;
    background: var(--accent-dim);
    color: var(--accent);
    font-size: 12px;
}
.skill-pin-chip[hidden] { display: none; }
.skill-pin-chip button {
    border: 0;
    background: transparent;
    color: inherit;
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    padding: 0 3px;
}

/* 成果工作台：对话绑定的成果——跟 .skill-pin-chip 同一套 token，同一个"持久 pin，
   点 × 或关闭工作台才消失"的交互模式。 */
.editing-artifact-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    width: fit-content;
    margin: 0 0 6px;
    padding: 4px 6px 4px 10px;
    border-radius: 999px;
    background: var(--accent-dim);
    color: var(--accent);
    font-size: 12px;
}
.editing-artifact-chip[hidden] { display: none; }
.editing-artifact-chip b { font-weight: 600; }
.editing-artifact-chip button {
    border: 0;
    background: transparent;
    color: inherit;
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    padding: 0 3px;
}

.input-shell {
    /* Anchor for the token pill, which sits outside this box's border */
    position: relative;
    display: flex;
    align-items: flex-end;
    gap: 8px;
    min-height: 60px;
    padding: 8px 8px 8px 16px;
    border: 1px solid var(--border-strong);
    border-radius: 26px;
    background: var(--surface-1);
    box-shadow: inset 0 1px 0 var(--edge-light), var(--shadow-soft);
    transition: border-color var(--dur-2) var(--ease-soft), background var(--dur-2) var(--ease-soft);
}

.input-shell:focus-within {
    border-color: var(--input-border-focus);
    background: var(--surface-2);
}

.input-shell textarea {
    min-height: 44px;
    max-height: 180px;
    flex: 1;
    /* flex 默认 min-width: auto，会让打字区宁可撑破外壳也不收缩；显式归零是兜底，
       真正的窄容器适配在下面按 composer 容器分档的规则里 */
    min-width: 0;
    resize: none;
    border: 0;
    outline: 0;
    padding: 10px 0;
    background: transparent;
    color: var(--text-primary);
    font-size: calc(15.5px * var(--msg-scale));
    line-height: 1.5;
}

.input-shell textarea::placeholder {
    color: var(--text-faint);
}

.input-shell textarea:disabled {
    cursor: not-allowed;
}

/* 禁用态的 placeholder 不再是"提示"，而是"工作中……"这个状态本身，所以要比一句可有可无的
   提示更看得清——--text-faint 是给可忽略的提示用的，状态得站得住。 */
.input-shell textarea:disabled::placeholder {
    color: var(--text-muted);
}

.input-shell textarea:focus-visible {
    outline: 0;
}

/* 深度思考开关。默认（关）是静默的轮廓态，开启才染上 --accent ——
   accent 在本站只表示状态，这里表示的是“这一轮会先推理”。 */
/* 本轮模式（2026-09-09 方案 D）：一个圆钮收着本轮开关（深度思考、模型协同），开着的在圆钮左边冒出带字的小标签。
   圆钮跟两端的附件/语音同一种长相——这一排三个门、一个实心圆，没有别的形状。 */
.turn-modes {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    align-self: flex-end;
    gap: 6px;
}
.turn-modes-button {
    width: 40px;
    height: 44px;
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--text-muted);
    transition: color var(--dur-1) var(--ease-soft), background var(--dur-1) var(--ease-soft);
}
.turn-modes-button:hover,
.turn-modes-button[aria-expanded="true"] { color: var(--text-primary); background: var(--surface-2-hover); }
/* 有开关开着：图标染状态色，跟旁边的标签同色 */
.turn-modes-button.is-on { color: var(--accent); }
.turn-modes-button svg {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.turn-modes-button:focus-visible { outline: 2px solid var(--accent); outline-offset: -4px; }

/* 开着的开关 = 带字的小标签，点它就关；都关着时这一格不存在 */
.turn-mode-chips { display: inline-flex; align-items: center; gap: 6px; }
.turn-mode-chips:empty { display: none; }
.turn-mode-chip {
    height: 28px;
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 5px;
    padding: 0 7px 0 10px;
    border: 1px solid var(--accent-tint-border);
    border-radius: var(--radius-pill);
    background: var(--accent-tint);
    color: var(--accent);
    font-size: 12px;
    white-space: nowrap;
    transition: border-color var(--dur-1) var(--ease-soft), background var(--dur-1) var(--ease-soft);
}
.turn-mode-chip:hover { border-color: var(--accent); }
.turn-mode-chip svg {
    width: 12px;
    height: 12px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    opacity: .65;
}
.turn-mode-chip:hover svg { opacity: 1; }
.turn-mode-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* 菜单：打开时挂在 body 下 fixed 定位，贴在圆钮上方右对齐（输入框在屏幕底部）。长相跟物料栏菜单同一套 */
.turn-modes-menu {
    position: fixed;
    z-index: 120;
    min-width: 172px;
    padding: 5px;
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    background: var(--surface-1);
    box-shadow: var(--shadow-pop);
    animation: dialog-in 160ms cubic-bezier(.16, 1, .3, 1) both;
}
.turn-modes-menu[hidden] { display: none; }
.turn-mode-item {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 8px;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--text-secondary);
    font-size: 12.5px;
    text-align: left;
    white-space: nowrap;
}
.turn-mode-item:hover { background: var(--surface-2); color: var(--text-primary); }
.turn-mode-item[aria-checked="true"] { color: var(--text-primary); }
.turn-mode-item > span { flex: 1 1 auto; }
.turn-mode-item svg {
    width: 15px;
    height: 15px;
    flex: 0 0 auto;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}
/* 焦点环往里画：菜单项之间没有留给外环的空隙 */
.turn-mode-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* 右侧的小开关：跟设置里的开关同一语法（开 = 薄荷底、圆点靠右） */
.turn-mode-switch {
    position: relative;
    width: 26px;
    height: 15px;
    flex: 0 0 auto;
    border-radius: 999px;
    background: var(--border-strong);
    transition: background var(--dur-1) var(--ease-soft);
}
.turn-mode-switch::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 11px;
    height: 11px;
    border-radius: 50%;
    background: var(--surface-1);
    transition: transform var(--dur-1) var(--ease-soft);
}
.turn-mode-item[aria-checked="true"] .turn-mode-switch { background: var(--accent); }
.turn-mode-item[aria-checked="true"] .turn-mode-switch::after { transform: translateX(11px); }

/* Ultra 模式入口（2026-09-09 改名 + 字标）：Ultra 字标当图标（U 大写、ltra 小写——用户拍板，也窄了三成）——斜切、利角、一道贯穿的速度切口。
   静时钢色渐变（文字 token，四套主题各自成立：暗底亮钢、浅底深钢），悬停提亮，开着时薄荷渐变 + 光晕（状态色只落在状态上）。
   无边框、透明底，跟左端附件、中间模式圆钮同一家族（都是门），只是形状随字标变宽 */
.voice-button {
    height: 44px;
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    padding: 0 8px;
    border: 0;
    border-radius: 14px;
    background: transparent;
    color: var(--text-muted);
    transition: background var(--dur-1) var(--ease-soft);
}
.voice-button:hover { background: var(--surface-2-hover); }
.voice-button:hover .ultra-mark { filter: brightness(1.18); }
.voice-button[aria-expanded="true"] .ultra-mark {
    fill: url(#ultra-live);
    filter: drop-shadow(0 0 6px color-mix(in srgb, var(--accent) 65%, transparent));
}
.ultra-mark { display: block; width: 51px; height: 14px; fill: url(#ultra-steel); }   /* 88×24 的画布按 14 高缩：51 宽，比第一版 74 宽的全大写窄三成 */
/* 字标渐变的颜色点走主题 token（stop 元素挂类，CSS 给 stop-color） */
.ultra-steel-a { stop-color: var(--text-faint); }
.ultra-steel-b { stop-color: var(--text-primary); }
.ultra-live-a { stop-color: var(--accent); }
.ultra-live-b { stop-color: color-mix(in srgb, var(--accent) 35%, #fff); }

.file-input { display: none; }

.attach-button {
    width: 40px;
    height: 44px;
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--text-muted);
    transition: color var(--dur-1) var(--ease-soft), background var(--dur-1) var(--ease-soft);
}

.attach-button:hover {
    color: var(--text-primary);
    background: var(--surface-2-hover);
}

.attach-button svg {
    width: 18px;
    height: 18px;
}

.file-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 0 19px 8px;
    /* 单条消息最多能带 20 个附件，一路 wrap 下去会把输入框顶出屏幕。
       封顶三行多一点，超了自己滚——输入框的位置不能被附件数量左右。
       故意露出第四行的上半截：那是"下面还有"最通用的暗示，比只给一根滚动条清楚
       （Windows 上滚动条默认是隐藏的，不滚起来根本看不见）。底部渐隐是为了让这半行
       看起来是有意裁的，而不是被切坏了。 */
    max-height: 132px;
    overflow-y: auto;
    overscroll-behavior: contain;
    mask-image: linear-gradient(to bottom, #000 calc(100% - 16px), transparent);
}

.file-chips[hidden] { display: none; }

/* 上传失败：跟合法附件（.file-chips）分开的独立、可关闭临时提示条，不是永久 chip。
   全宽单行一条，文字换行不截断——上传失败的原因要看得全，不能像文件名那样省略号截断。 */
.upload-errors {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 0 19px 8px;
}

.upload-errors[hidden] { display: none; }

.upload-error {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 8px 10px;
    border: 1px solid var(--danger-border-strong);
    border-radius: 9px;
    background: var(--danger-dim);
    color: var(--danger);
    font-size: 12.5px;
    line-height: 1.5;
    animation: chip-in 200ms cubic-bezier(.16, 1, .3, 1) both;
}

.upload-error-icon { width: 16px; height: 16px; flex-shrink: 0; margin-top: 1px; }
.upload-error-icon svg { width: 100%; height: 100%; display: block; }

.upload-error-text { flex: 1; min-width: 0; word-break: break-word; }

.upload-error-dismiss {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 5px;
    background: transparent;
    color: var(--danger);
    opacity: 0.75;
    transition: opacity 120ms ease, background 120ms ease;
}

.upload-error-dismiss:hover { opacity: 1; background: var(--danger-border-strong); }
.upload-error-dismiss svg { width: 12px; height: 12px; }

.file-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 8px 5px 10px;
    border: 1px solid var(--border);
    border-radius: 9px;
    background: var(--surface-2);
    font-size: 12px;
    color: var(--text-primary);
    animation: chip-in 200ms cubic-bezier(.16, 1, .3, 1) both;
}

@keyframes chip-in {
    from { opacity: 0; transform: translateY(4px) scale(0.96); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}

.file-chip.is-uploading {
    opacity: 0.55;
    animation: chip-pulse 1s ease infinite;
}

@keyframes chip-pulse {
    0%, 100% { opacity: 0.45; }
    50% { opacity: 0.75; }
}

/* 上传中：环形持续转圈 + 文字 + 百分数 */
.file-chip.is-uploading .upload-ring {
    animation: upload-spin 900ms linear infinite;
}

@keyframes upload-spin {
    from { transform: rotate(-90deg); }
    to { transform: rotate(270deg); }
}

/* 上传进度环 */
.upload-ring {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

.upload-ring svg {
    width: 100%;
    height: 100%;
    display: block;
}

.upload-ring-track {
    fill: none;
    stroke: var(--border);
    stroke-width: 3;
}

.upload-ring-bar {
    fill: none;
    stroke: var(--accent);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-dasharray: 74 24;
}

.upload-label {
    font-size: 11px;
    color: var(--text-secondary);
}

.upload-pct {
    font-size: 11px;
    color: var(--text-faint);
    font-variant-numeric: tabular-nums;
    min-width: 30px;
    text-align: right;
}

.file-chip-icon { width: 20px; height: 20px; flex-shrink: 0; }
.file-chip-icon svg { width: 100%; height: 100%; display: block; }

.file-chip-name {
    max-width: 180px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12px;
}

.file-chip-remove {
    width: 18px;
    height: 18px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 5px;
    background: transparent;
    color: var(--text-faint);
    font-size: 10px;
    transition: color 120ms ease, background 120ms ease;
}

.file-chip-remove:hover {
    color: var(--danger);
    background: var(--danger-dim);
}

.file-chip-remove svg { width: 12px; height: 12px; }

/* attachment strips — standalone file pills above user bubbles */
/* ── Sent attachments: cards grouped with the user bubble ── */

.attach-card-group {
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    margin-bottom: 8px;
}

.attach-card {
    width: min(76%, 380px);
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--bubble-user);
    text-align: left;
}

button.attach-card {
    cursor: pointer;
    transition: border-color 150ms ease, background 150ms ease;
}

button.attach-card:hover {
    border-color: var(--border-strong);
    background: var(--surface-2-hover);
}

/* Type identity: tinted tile + mono abbreviation, coloured per file type */
.attach-card-tile {
    width: 38px;
    height: 38px;
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    border-radius: 9px;
    background: color-mix(in srgb, var(--file-tile, var(--text-faint)) 14%, transparent);
    color: var(--file-tile, var(--text-faint));
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.05em;
}

.attach-card.is-pdf { --file-tile: var(--file-pdf); }
.attach-card.is-docx { --file-tile: var(--file-doc); }
.attach-card.is-mpp { --file-tile: var(--file-mpp); }
/* MSPDI 是系统改完日期写出的计划，跟 .mpp 是同一类东西，用同一个颜色 */
.attach-card.is-mspdi { --file-tile: var(--file-mpp); }
.attach-card.is-xlsx { --file-tile: var(--file-xlsx); }

.attach-card-main {
    min-width: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.attach-card-name {
    overflow: hidden;
    color: var(--text-primary);
    font-size: 13px;
    font-weight: 500;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.attach-card-meta {
    color: var(--text-faint);
    font-size: 11.5px;
}

.attach-card-action {
    flex: 0 0 auto;
    padding-left: 8px;
    color: var(--accent);
    font-size: 11.5px;
    white-space: nowrap;
}

.composer-controls {
    width: 44px;
    height: 44px;
    flex: 0 0 auto;
    position: relative;
}

/* Token counter: pill parked in the gutter outside the input shell, level with the
   send button — in the eye line when sending without eating typing width.
   bottom: 15px centres the 30px pill against the 44px button that sits on the
   shell's 8px bottom padding (8 + (44 − 30) / 2). */
.token-badge {
    position: absolute;
    left: 100%;
    bottom: 15px;
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 5px;
    height: 30px;
    margin: 0 0 0 12px;
    padding: 0 11px;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--surface-2);
    color: var(--text-faint);
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: .08em;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.token-badge b {
    color: var(--text-primary);
    font-size: 12.5px;
    font-weight: 600;
    letter-spacing: 0;
}

/* No gutter left to park in — fall back inside the shell rather than let the pill
   ride off the edge. 1006px：胶囊停在框外要 880 框 + 12 间距 + 67 胶囊 = 959 的 track，track = 容器宽 − 2×24，
   即容器 ≥ 1007 才停得下；再窄（1280 视口时容器 988）就收进框内，不然压到右边收起的资料栏手柄上。 */
@container composer (max-width: 1006px) {
    .token-badge {
        position: static;
        align-self: flex-end;
        margin: 7px 2px 7px 0;
    }
}

/* 下面两档同样按容器宽度分，不按视口：聊天列在侧栏展开、成果抽屉打开（最多 56vw）时
   都会变窄，而浏览器本身还是桌面宽度——@media 看不见这种挤压（2026-08-27 修：抽屉一开
   输入行被定宽控件挤到只剩百来像素，中文逐字换行、触到高度上限出内部滚动条）。 */

/* 第一档：token 收起文字只留数字。开关的标签不收——它们只在开着时才出现，字就是状态本身。 */
@container composer (max-width: 840px) {
    .token-badge { padding: 0 9px; }
    .token-badge > span { display: none; }
}

/* 第二档：折成两排——打字区独占第一排，附件、开关、token、发送键落到第二排，
   附件在左、token 和发送键贴右。纯 flex 换行 + order，不动 HTML 结构。 */
@container composer (max-width: 600px) {
    .input-shell { flex-wrap: wrap; }
    .input-shell textarea { order: -1; flex: 1 1 100%; }
    .token-badge { order: 1; margin-left: auto; }
    .voice-button { order: 2; }        /* 语音跟发送成对，一起贴右 */
    .composer-controls { order: 2; }
}

/* 输入框下方一行：免责说明居中，状态/错误消息靠右浮现 */
.composer-foot {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    min-height: 26px;
    margin-top: 8px;
}

.composer-status {
    position: absolute;
    right: 4px;
    max-width: 60%;
    overflow: hidden;
    font-family: var(--font-mono);
    font-size: 10.5px;
    color: var(--danger);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.composer-status[hidden] { display: none; }

/* 发送键是页面上唯一的实心控件——一眼看得到，不需要靠颜色喊 */
.send-button,
.stop-button {
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: var(--text-primary);
    color: var(--bg);
    transition: background var(--dur-1) var(--ease-soft), opacity var(--dur-1) var(--ease-soft);
}

.send-button:hover:not(:disabled),
.stop-button:hover {
    background: var(--send-button-hover);
}

.send-button:disabled {
    cursor: not-allowed;
    background: var(--surface-2-hover);
    color: var(--text-faint);
}

.send-button[hidden] {
    display: none;
}

.send-button svg {
    width: 16px;
    height: 16px;
}

.stop-button {
    position: absolute;
    inset: 0;
}

.stop-button[hidden] {
    display: none;
}

.stop-button span {
    width: 11px;
    height: 11px;
    border: 1.5px solid currentColor;
    border-radius: 2px;
}

.telemetry {
    min-height: 28px;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 7px 16px;
    margin-top: 9px;
    color: var(--text-faint);
    font-size: 11px;
    letter-spacing: 0.2px;
}

.telemetry span {
    display: flex;
    align-items: center;
    gap: 5px;
}

.telemetry b {
    color: var(--text-muted);
    font-weight: 500;
}

#request-status {
    color: var(--text-muted);
}

.ai-notice {
    margin: 0;
    color: var(--text-faint);
    font-size: 11.5px;
    text-align: center;
}

.skip-link {
    position: fixed;
    z-index: 100;
    top: 8px;
    left: 74px;
    padding: 9px 12px;
    border-radius: 8px;
    background: var(--accent);
    color: var(--bg);
    font-size: 12px;
    font-weight: 600;
    text-decoration: none;
    transform: translateY(-160%);
    transition: transform 160ms ease;
}

.skip-link:focus {
    transform: translateY(0);
}

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

button:focus-visible,
a:focus-visible,
textarea:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
}

@keyframes rise {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: translateY(0); }
}

@keyframes fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

@media (max-width: 720px) {
    .rail {
        /* 手机抽屉覆盖正文，保持原磨砂强度以免文字重叠。 */
        background: color-mix(in srgb, var(--surface-1) 38%, transparent);
        backdrop-filter: blur(14px) saturate(1.2);
        -webkit-backdrop-filter: blur(14px) saturate(1.2);
        width: min(286px, calc(100vw - 38px));
        min-width: min(286px, calc(100vw - 38px));
        flex-basis: min(286px, calc(100vw - 38px));
        position: fixed;
        z-index: 30;
        top: 0;
        bottom: 0;
        left: 0;
        box-shadow: var(--shadow-rail);
        transform: translateX(-105%);
        transition: transform var(--dur-3) var(--ease);
    }

    .rail.is-mobile-open { transform: translateX(0); }
    .rail.is-collapsed { width: min(286px, calc(100vw - 38px)); min-width: min(286px, calc(100vw - 38px)); flex-basis: min(286px, calc(100vw - 38px)); }
    .rail.is-collapsed .drawer-brand { width: auto; opacity: 1; transform: none; }
    /* 移动端折叠态本来就照常显示品牌文字，标也得跟着显示，不然只剩“诶皆”两个字没有标 */
    .rail.is-collapsed .rail-logo { display: grid; }
    .rail.is-collapsed .drawer-toggle { transform: none; }
    .rail.is-collapsed .project-create-button { width: auto; min-width: 0; margin: 14px; padding: 0 12px; justify-content: flex-start; }
    .rail.is-collapsed .project-create-button span { display: inline; }
    .rail.is-collapsed .drawer-history { opacity: 1; pointer-events: auto; }
    .rail.is-collapsed .drawer-foot { padding: 0 20px; min-height: 54px; flex-direction: row; gap: 9px; }
    /* 手机上收起态照常是整条抽屉：灯旁那行字回到一行 */
    .rail.is-collapsed .drawer-presence-label { display: block; max-width: none; white-space: nowrap; text-align: left; line-height: inherit; }
    .rail.is-collapsed .drawer-presence-who { display: inline; }
    .rail.is-collapsed .drawer-presence-who::before { content: ' · '; }
    /* 手机上两栏本来就是浮层，iOS Safari 也没有全屏接口：不露这枚按钮 */
    .drawer-immersive { display: none; }

    .drawer-toggle { display: none; }
    .mobile-drawer-button { display: grid; margin-left: -9px; }
    .drawer-scrim { position: fixed; z-index: 25; inset: 0; border: 0; background: var(--scrim-drawer); backdrop-filter: blur(2px); }
    .drawer-scrim:not([hidden]) { display: block; animation: scrim-in 180ms ease-out both; }
    .conversation-delete { opacity: 1; }

    .topbar {
        height: 52px;
        padding: 0 14px;
    }

    /* 早报浮层跟着矮一点的顶栏走，两侧留 10px */
    .patrol-card { top: 60px; width: calc(100% - 20px); }

    .topbar-meta span:last-child {
        display: none;
    }

    .thread {
        padding: 16px 15px calc(var(--composer-h) + 8px);
    }

    .empty-state {
        min-height: 46vh;
    }

    .empty-greeting {
        font-size: 25px;
    }

    .empty-hint {
        font-size: 14px;
        margin-bottom: 22px;
    }

    .msg {
        margin-bottom: 36px;
    }

    .msg.user {
        margin-bottom: 12px;
    }

    .msg.assistant {
        gap: 10px;
    }

    .msg.user .message-shell {
        max-width: 86%;
    }

    .assistant-body,
    .msg.assistant .message-content,
    .msg.user .message-content {
        font-size: calc(15px * var(--msg-scale));
    }

    .copy-message-button {
        min-height: 44px;
    }

    .composer-wrap {
        padding: 40px 10px max(10px, env(safe-area-inset-bottom));
    }

    .telemetry {
        gap: 5px 11px;
        font-size: 10.5px;
    }
}

/* （原来这里有一组 520px 视口规则收 TOKEN/开关的文字——已并进上面按 composer 容器
   分档的 840px 那档：手机上容器宽≈视口宽，行为不变；桌面开抽屉时容器规则才看得见挤压。） */

@media (max-width: 390px) {
    .topbar-meta {
        display: none;
    }

    .telemetry span:nth-child(3) {
        display: none;
    }
}

@keyframes scrim-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

.confirm-dialog-backdrop {
    position: fixed;
    z-index: 100;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 20px;
    background: var(--scrim);
    backdrop-filter: blur(8px);
}

.confirm-dialog-backdrop[hidden] { display: none; }

.confirm-dialog {
    width: min(100%, 410px);
    display: grid;
    gap: 15px;
    padding: 24px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    background: var(--surface-float);
    box-shadow: inset 0 1px 0 var(--edge-light), var(--shadow-modal);
    animation: dialog-in var(--dur-2) var(--ease) both;
}

.confirm-dialog-mark {
    width: 42px;
    height: 42px;
    display: grid;
    place-items: center;
    border: 1px solid var(--danger-border);
    border-radius: 13px;
    background: var(--danger-dim);
    color: var(--danger-text);
}
.confirm-dialog-mark svg { width: 21px; height: 21px; }
.confirm-dialog-eyebrow { margin: 0 0 7px; color: var(--danger); font-size: 11px; letter-spacing: .08em; }
.confirm-dialog h2 { margin: 0; color: var(--text-primary); font-size: 18px; line-height: 1.35; }
.confirm-dialog p:not(.confirm-dialog-eyebrow) { margin: 7px 0 0; color: var(--text-muted); font-size: 13px; line-height: 1.65; }
/* 2026-10-09 删除弹窗定稿：文件名单独一行、正文一句——换行由脚本给（pre-line 认它）；长文件名允许任意位置折行 */
#confirm-dialog-description { white-space: pre-line; overflow-wrap: anywhere; }
.confirm-dialog-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 3px; }
/* 确认弹窗里的失败原因。以前失败只写进输入框底下那行 10.5px 的状态字、2.5 秒消失，而弹窗的模糊
   遮罩正压在它上面——服务端解释得再清楚，用户看到的也只是按钮闪一下"处理中…"（2026-09-03 删 dwg
   事故）。选择器要压过上面 .confirm-dialog p:not(.confirm-dialog-eyebrow) 那条（它会把颜色改回 muted）。 */
.confirm-dialog p.confirm-dialog-error { margin: 9px 0 0; color: var(--danger); font-size: 12.5px; line-height: 1.6; }
.dialog-button { min-width: 80px; min-height: 44px; padding: 0 15px; border-radius: 10px; font-size: 13px; font-weight: 600; transition: transform 150ms ease, background 150ms ease, opacity 150ms ease; }
.dialog-button:hover:not(:disabled) { transform: translateY(-1px); }
.dialog-button:disabled { opacity: .55; }
.dialog-button-secondary { border: 1px solid var(--border-strong); background: var(--surface-2); color: var(--text-muted); }
.dialog-button-danger { border: 1px solid var(--danger-border-strong); background: var(--danger-dim); color: var(--danger-text-strong); }
.dialog-button-danger:hover:not(:disabled) { background: var(--danger-hover); }

@keyframes dialog-in {
    from { opacity: 0; transform: translateY(8px) scale(.98); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}

/* ─── Settings ─── */

.drawer-settings {
    min-height: 48px;
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 0 20px;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: var(--text-faint);
    font-size: 11px;
    white-space: nowrap;
    cursor: pointer;
    transition: color 160ms ease, background 160ms ease;
}

.drawer-settings:hover {
    color: var(--text-primary);
    background: var(--surface-2);
}

.drawer-settings svg {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
    transition: transform 260ms ease;
}

.drawer-settings:hover svg {
    transform: rotate(30deg);
}

.drawer-settings-label {
    font-family: var(--font-sans);
    transition: opacity 140ms ease;
}

.rail.is-collapsed .drawer-settings {
    padding: 0;
    justify-content: center;
}

.rail.is-collapsed .drawer-settings-label {
    display: none;
}

.settings-dialog-backdrop {
    position: fixed;
    z-index: 100;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 20px;
    background: var(--scrim);
    backdrop-filter: blur(8px);
}

.settings-dialog-backdrop[hidden] { display: none; }

/* 2026-09-08：弹窗按内容列定宽。原来 1080 宽、内容列收到 640 之后右边空出一条什么都没有的带子
   （用户："最右侧没用上"）。200 侧栏 + 34/34 内边距 + 652 内容 = 920，行铺满内容区，模型面板同宽。 */
.settings-dialog {
    width: min(100%, 920px);
    height: min(680px, calc(100dvh - 40px));
    min-height: 0;
    max-height: calc(100dvh - 40px);
    display: flex;
    position: relative;
    border: 1px solid var(--border-strong);
    border-radius: 16px;
    background: var(--surface-1);
    box-shadow: inset 0 1px 0 var(--edge-light), var(--shadow-modal);
    animation: dialog-in var(--dur-2) var(--ease) both;
    overflow: hidden;
}

.settings-dialog-close {
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 5;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text-faint);
    transition: background 150ms ease, color 150ms ease;
}

.settings-dialog-close:hover {
    background: var(--surface-2);
    color: var(--text-primary);
}

.settings-dialog-close svg {
    width: 15px;
    height: 15px;
    stroke-width: 1.8;
}

/* ── Sidebar ── */

.settings-sidebar {
    width: 200px;
    flex: 0 0 200px;
    display: flex;
    flex-direction: column;
    padding: 0;
    border-right: 1px solid var(--border);
    background: var(--surface-2);
}

.settings-sidebar-title {
    margin: 0;
    padding: 28px 20px 18px;
    color: var(--text-primary);
    font-size: 15px;
    font-weight: 650;
    letter-spacing: -0.01em;
}

.settings-nav-items {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 0 12px;
}

.settings-nav-item {
    min-height: 42px;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 10px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text-muted);
    font-size: 13px;
    font-weight: 500;
    text-align: left;
    cursor: pointer;
    transition: background 150ms ease, color 150ms ease;
}

.settings-nav-item:hover {
    background: var(--surface-2-hover);
    color: var(--text-primary);
}

.settings-nav-item.is-active {
    background: var(--accent-dim);
    color: var(--accent);
}

.settings-nav-item svg {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
}

/* ── Content ── */

.settings-content {
    flex: 1;
    min-width: 0;
    overflow-y: auto;
    padding: 0;
}

.settings-panel {
    display: none;
    min-height: 100%;
    padding: 30px 34px 34px;
}

.settings-panel.is-active {
    display: block;
}

/* 内容列（2026-09-08）：860px 宽只放两三行控件，每个面板都像没做完；~650 才是"列表"不是"海报"。
   宽度由弹窗本身定（.settings-dialog 920），这里不再另设上限——否则右边又空出一条带子。 */
.settings-panel-inner {
    max-width: none;
}

.settings-panel-title {
    margin: 0 0 14px;
    color: var(--text-primary);
    font-size: 16px;
    font-weight: 600;
    letter-spacing: -0.01em;
}

/* 一个面板里叠两组列表（可用技能 + 项目技能权限）时，第二组标题需要跟上一组拉开距离。 */
.settings-panel-title--spaced {
    margin-top: 24px;
}

.settings-panel-desc {
    margin: 0 0 24px;
    color: var(--text-muted);
    font-size: 13px;
    line-height: 1.55;
}

/* 标题下只放"状态"（协同：够不够两家），不放解释句 */
.settings-panel-status { margin: -6px 0 14px; color: var(--text-faint); font-size: 12px; line-height: 1.5; }
/* 联网搜索（2026-09-23）：一组四行，跟沙盒同一套——右边只放小号按钮；密钥输入框也压成行内高度（28），不用表单那种 42 高的框。 */
.settings-panel[data-panel="websearch"] .settings-row > .settings-inline-button { flex: 0 0 auto; margin-bottom: 0; }
.websearch-get-key { text-decoration: none; }
.websearch-sep { color: var(--text-faint); }
.websearch-key-controls { flex: 0 1 auto; display: flex; gap: 6px; align-items: center; min-width: 0; }
.websearch-key-controls .settings-inline-button { flex: 0 0 auto; margin-bottom: 0; min-height: 28px; }
.websearch-key-input {
    width: 220px;
    min-width: 0;
    height: 28px;
    padding: 0 10px;
    border: 1px solid var(--border-strong);
    border-radius: 7px;
    background: var(--surface-2);
    color: var(--text-primary);
    font: 12.5px var(--font-mono);
    outline: none;
    transition: border-color 150ms ease;
}
.websearch-key-input::placeholder { color: var(--text-faint); font-family: inherit; }
.websearch-key-input:focus { border-color: var(--focus-border); }
@media (max-width: 720px) {
    .settings-row.websearch-key { flex-wrap: wrap; }
    .websearch-key-controls { flex: 1 1 100%; }
    .websearch-key-input { flex: 1 1 auto; width: auto; }
}
.websearch-clear { border: 0; padding: 0; background: transparent; color: inherit; font: inherit; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.websearch-clear:hover { color: var(--text-primary); }
.websearch-clear:disabled { opacity: 0.5; cursor: default; }
.websearch-clear:focus-visible { outline: 2px solid var(--text-secondary); outline-offset: 4px; border-radius: 3px; }
.settings-panel-status:empty { display: none; }

/* ── 设置的"行"语法（2026-09-08，模型面板之外的六个面板统一用它）──
   左标签（下面可挂一句短提示）、右控件、行间发丝线、几行一组、组名一行淡色小字。
   刻意没有卡片：设置本身已经是带边框的弹窗，里面再一张张带边框带内边距的卡是容器套容器；
   也刻意没有每个标题下的解释句——控件的名字就是解释。JS 渲染的项目/技能列表沿用
   .notify-project-list/.notify-project-row 这对老类名，样式在下面并到同一套。 */
.settings-group { display: flex; flex-direction: column; }
.settings-group-title {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin: 22px 0 4px;
    color: var(--text-faint);
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: .04em;
}
.settings-group-title:first-child, .settings-panel-title + .settings-group-title { margin-top: 4px; }
.settings-group-hint { color: var(--text-faint); font-size: 11.5px; font-weight: 400; letter-spacing: 0; }
.settings-group + .settings-group-title { margin-top: 26px; }
/* 朗读：音色和下面「Ultra 模式」那组之间画一条线隔开（2026-09-23 用户要的）；组里最后一行本来没有下边线 */
.settings-panel[data-panel="voice"] #voice-list { padding-bottom: 6px; border-bottom: 1px solid var(--border); }

.settings-row,
.notify-project-row {
    display: flex;
    align-items: center;
    gap: 16px;
    min-height: 48px;
    padding: 9px 0;
    border-bottom: 1px solid var(--border);
}
.settings-row:last-child, .notify-project-row:last-child { border-bottom: 0; }
/* 从属行（复核模型跟在成果复核下面）：缩进、更矮；标签直接当行的左半边，控件靠右 */
.settings-row.is-sub { padding-left: 16px; min-height: 40px; }
.settings-row > .settings-row-label { flex: 1 1 auto; }
.settings-row-main, .notify-project-main { min-width: 0; flex: 1 1 auto; display: grid; gap: 2px; }
.settings-row-label, .notify-project-name {
    color: var(--text-primary);
    font-size: 13.5px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.settings-row-main .settings-row-hint { margin: 0; }
/* 校验没过的技能：开关禁用，旁边一枚小标签说为什么（完整原因在悬停里） */
.settings-row-tag {
    flex: 0 0 auto;
    padding: 1px 7px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    color: var(--text-faint);
    font-size: 10.5px;
    line-height: 1.6;
}

/* 分段控件：朗读的女声｜男声、对话字号的四档。选中段用 surface-1 浮起 + 主色字，
   不用薄荷底——薄荷只表示状态，"当前选的是哪个"用重量表达。 */
.settings-segmented {
    flex: 0 0 auto;
    display: inline-flex;
    padding: 2px;
    border: 1px solid var(--border);
    border-radius: 9px;
    background: var(--surface-2);
}
.settings-segment {
    min-height: 26px;
    padding: 0 11px;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--text-muted);
    font-size: 12.5px;
    font-weight: 500;
    line-height: 1;
    cursor: pointer;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft), box-shadow var(--dur-1) var(--ease-soft);
}
.settings-segment:hover { color: var(--text-primary); }
.settings-segment.is-active {
    background: var(--surface-1);
    color: var(--text-primary);
    box-shadow: inset 0 0 0 1px var(--border-strong);
}
.collab-switch:disabled { opacity: .45; cursor: not-allowed; }

.settings-field {
    margin-top: 16px;
}

.settings-field:first-of-type {
    margin-top: 0;
}

.settings-field-row {
    display: flex;
    gap: 14px;
    align-items: flex-end;
}

.settings-field-row .settings-field {
    margin-top: 0;
    min-width: 0;
}

.settings-field--provider {
    width: 200px;
    flex: 0 0 200px;
}

.settings-field--baseurl {
    flex: 1;
}

.settings-field--apikey,
.settings-field--model {
    flex: 1;
}

.settings-label {
    display: block;
    margin-bottom: 6px;
    color: var(--text-muted);
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.02em;
}

.settings-select-wrap {
    position: relative;
}

.settings-select {
    width: 100%;
    min-height: 42px;
    padding: 0 32px 0 12px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface-2);
    color: var(--text-primary);
    font-size: 13px;
    font-family: var(--font-sans);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    outline: none;
    transition: border-color 160ms ease, box-shadow 160ms ease;
}

.settings-select:focus-visible {
    border-color: var(--focus-border);
    box-shadow: 0 0 0 1px var(--focus-ring);
}

.settings-select-chevron {
    width: 15px;
    height: 15px;
    position: absolute;
    right: 10px;
    top: 50%;
    transform: translateY(-50%);
    pointer-events: none;
    color: var(--text-faint);
    stroke-width: 2;
}

.settings-input-row {
    display: flex;
    align-items: center;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface-2);
    transition: border-color 160ms ease, box-shadow 160ms ease;
}

.settings-input-row:focus-within {
    border-color: var(--focus-border);
    box-shadow: 0 0 0 1px var(--focus-ring);
}

.settings-input {
    flex: 1;
    min-width: 0;
    min-height: 42px;
    padding: 0 12px;
    border: 0;
    border-radius: 9px;
    background: transparent;
    color: var(--text-primary);
    font-size: 13px;
    font-family: var(--font-mono);
    outline: none;
}

.settings-input::placeholder {
    color: var(--text-faint);
    font-family: var(--font-sans);
}

.settings-input-action {
    width: 34px;
    height: 34px;
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    margin-right: 3px;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--text-faint);
    transition: color 150ms ease, background 150ms ease;
}

.settings-input-action:hover {
    color: var(--text-muted);
    background: var(--surface-2-hover);
}

.settings-input-action svg {
    width: 15px;
    height: 15px;
    stroke-width: 1.6;
}

.settings-input-action svg[hidden] { display: none; }

.settings-dialog-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: auto;
    padding-top: 16px;
    border-top: 1px solid var(--border);
}

.settings-button-primary,
.settings-button-secondary {
    min-width: 80px;
    min-height: 44px;
    padding: 0 15px;
    border-radius: 10px;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.01em;
    transition: transform 150ms ease, background 150ms ease, opacity 150ms ease, box-shadow 150ms ease;
}

.settings-button-primary:hover:not(:disabled),
.settings-button-secondary:hover:not(:disabled) {
    transform: translateY(-1px);
}

.settings-button-primary:disabled,
.settings-button-secondary:disabled {
    opacity: .55;
}

.settings-button-primary {
    border: 1px solid var(--accent-tint-border);
    background: var(--accent-tint);
    color: var(--accent);
}

.settings-button-primary:hover:not(:disabled) {
    background: var(--accent-tint-hover);
    box-shadow: 0 0 18px var(--accent-dim);
}

.settings-button-secondary {
    border: 1px solid var(--border-strong);
    background: var(--surface-2);
    color: var(--text-muted);
}

.settings-button-secondary:hover:not(:disabled) {
    background: var(--surface-2-hover);
    color: var(--text-primary);
}

.settings-feedback {
    margin-top: 12px;
    padding: 9px 12px;
    border-radius: 9px;
    font-size: 12.5px;
    line-height: 1.5;
}

.settings-feedback[hidden] { display: none; }

.settings-feedback.is-ok {
    background: var(--accent-dim);
    color: var(--accent);
    border: 1px solid var(--accent-ok-border);
}

.settings-feedback.is-error {
    background: var(--danger-dim);
    color: var(--danger-text);
    border: 1px solid var(--danger-border-soft);
}

/* ── Theme picker (settings › 主题) ── */

/* 设置 › 协同：2026-09-08 起是"行"（见 .settings-row），卡片那套类（.collab-card*）已从模板里退场。
   开关是状态，用薄荷色。 */
.collab-switch {
    flex: 0 0 auto;
    width: 34px;
    height: 19px;
    border: 0;
    border-radius: var(--radius-pill);
    background: var(--accent);
    position: relative;
    cursor: pointer;
    transition: background var(--dur-1) var(--ease-soft);
}
.collab-switch::after {
    content: "";
    position: absolute;
    right: 2px;
    top: 2px;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: #fff;
    transition: right var(--dur-1) var(--ease-soft), left var(--dur-1) var(--ease-soft);
}
.collab-switch.is-off { background: var(--surface-3); }
.collab-switch.is-off::after { right: 17px; }

/* 协同设置：服务商选择与完整模型名称分行，避免长文本挤压箭头。 */
.collab-availability { margin: -4px 0 24px; color: var(--text-secondary); font-size: 12px; line-height: 1.7; }
.collab-policy { padding-bottom: 24px; border-bottom: 1px solid var(--border); }
.settings-panel[data-panel="collab"] .settings-row { padding: 12px 0; border: 0; }
.settings-panel[data-panel="collab"] .settings-row-label { font-size: 14px; font-weight: 500; }
.settings-panel[data-panel="collab"] .settings-row-hint { margin-top: 5px; line-height: 1.7; }
.collab-review-field { margin-top: 16px; display: grid; grid-template-columns: 100px minmax(0, 1fr); align-items: center; gap: 8px 20px; }
.collab-review-field > label { font-size: 12px; color: var(--text-secondary); }
.collab-select { width: 100%; min-width: 0; max-width: none; min-height: 44px; padding: 10px 40px 10px 14px; text-overflow: ellipsis; }
.collab-model-detail { grid-column: 2; margin: 0; color: var(--text-secondary); font-size: 12px; line-height: 1.7; overflow-wrap: anywhere; }
.settings-panel[data-panel="collab"] .collab-failover-row { padding: 25px 0; border-bottom: 1px solid var(--border); }
.collab-statistics { margin-top: 28px; }
.collab-statistics h4 { margin: 0 0 16px; font-size: 13px; color: var(--text-primary); font-weight: 500; }
#collab-report { overflow-x: auto; }
#collab-report:focus-visible { outline: 2px solid var(--focus-border); outline-offset: 3px; }
.collab-report-table { width: 100%; min-width: 460px; border-collapse: collapse; font-size: 12px; font-variant-numeric: tabular-nums; }
.collab-report-table th { color: var(--text-secondary); font-weight: 400; text-align: right; padding: 0 12px 12px; border-bottom: 1px solid var(--border); white-space: nowrap; }
.collab-report-table td { padding: 14px 12px; border-bottom: 1px solid var(--border); color: var(--text-secondary); text-align: right; white-space: nowrap; }
.collab-report-table th:first-child, .collab-report-table td:first-child { text-align: left; padding-left: 0; white-space: normal; overflow-wrap: anywhere; min-width: 110px; }
.collab-report-table td:first-child { color: var(--text-primary); }
.collab-report-table th:last-child, .collab-report-table td:last-child { padding-right: 0; }
.collab-report-table tr:last-child td { border-bottom: 0; }
.collab-report-empty { margin: 0; padding: 8px 0; font-size: 12px; color: var(--text-secondary); line-height: 1.7; }
@media (max-width: 720px) {
    .collab-review-field { grid-template-columns: minmax(0, 1fr); gap: 10px; }
    .collab-model-detail { grid-column: 1; }
}

/* ─── 模型协同：当轮左右并排 → 合卷归一 ───────────────────────────
   这一整块只作用于协同开着时才建出来的节点（.collab-* 、mark.collab-mark）。
   开关关掉的轮次里这些节点根本不存在，回答与单模型时逐像素相同。 */

/* 双栏协同：右栏是"把同一个问题发给协同模型"的对等答案——无框、无底色、
   与主答完全同一套正文排版（同 class 同管线渲染），只靠名牌和中缝细线区分身份。
   两栏都是自然高度，页面照常滚动，不做任何内部滚动条（用户定案）。
   定局后右栏保留，报告横跨全宽落在两栏之下；历史里同样重建。窄了自动上下叠。
   （2026-08-27 曾改环绕式——右栏浮动、主答回流，用户实测不喜欢，当天退回本版。） */
.msg.assistant.is-collab-duo {
    flex-wrap: wrap;
    row-gap: 14px;
}

.msg.assistant.is-collab-duo .message-shell {
    flex: 1 1 340px;
}

.collab-shell {
    flex: 1 1 340px;
    min-width: 0;
    /* 中缝细线：分身份，不围框 */
    border-left: 1px solid var(--border);
    padding-left: 18px;
}

/* 左栏名牌第二行：本轮主答的模型名。协同双栏时挂，与右栏名牌身份对等 */
.message-label .model-sub {
    display: block;
    margin-top: 2px;
    color: var(--text-faint);
}

.collab-label {
    display: flex;
    align-items: center;
    gap: 7px;
}

.collab-status-dot {
    width: 7px;
    height: 7px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 8px var(--accent-glow);
    animation: caret-breathe 2s var(--ease-soft) infinite;
    opacity: var(--caret-opacity, 1);
}

.collab-role {
    flex: 0 0 auto;
    padding: 1px 7px;
    border-radius: var(--radius-pill);
    background: var(--surface-3);
    color: var(--text-muted);
    font-size: 10px;
    letter-spacing: 0;
}

@media (prefers-reduced-motion: reduce) {
    .collab-status-dot { animation: none; }
}

/* 合卷等待：查资料轮里左栏憋着不放时的占位说明（正文由揭晓时补入） */
.collab-hold-status {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--text-muted);
    font-size: 13px;
}

.collab-hold-status::before {
    content: '';
    width: 7px;
    height: 7px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 8px var(--accent-glow);
    animation: caret-breathe 2s var(--ease-soft) infinite;
}

@media (prefers-reduced-motion: reduce) {
    .collab-hold-status::before { animation: none; }
}

/* ─── 定局：升格结果卡。分歧琥珀头、一致薄荷头，对数表在分歧时默认摊开 ─── */
.collab-result {
    margin-top: 12px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    overflow: hidden;
}

/* 双栏布局里报告独占一整行（在信号线右侧起步，与正文左缘对齐） */
.msg.assistant.is-collab-duo .collab-result {
    flex: 1 0 100%;
    margin-left: 16px;
}

/* 协同轮的工具时间线是"共用一趟查询"的总过程：独占首行横跨两栏。
   单栏轮次它照旧挂在主答壳里，这条规则不生效 */
.msg.assistant.is-collab-duo > .tool-steps {
    flex: 1 0 100%;
    margin-left: 16px;
}

.collab-result-head {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 14px;
    border: 0;
    background: transparent;
    color: var(--text-faint);
    font-size: 12px;
    text-align: left;
}

.collab-result-head.is-expandable { cursor: pointer; }

.collab-result.is-diff .collab-result-head {
    background: var(--caution-dim);
    color: var(--caution);
}

.collab-result.is-agree .collab-result-head {
    background: color-mix(in srgb, var(--accent), transparent 93%);
    color: color-mix(in srgb, var(--accent), var(--text-secondary) 30%);
}

.collab-dot {
    width: 8px;
    height: 8px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: currentColor;
}

.collab-text {
    flex: 0 1 auto;
    min-width: 0;
}

.collab-who {
    margin-left: auto;
    flex: 0 0 auto;
    color: var(--text-faint);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

.collab-result-body {
    padding: 10px 14px 12px;
    border-top: 1px solid var(--border);
}

.collab-note { font-size: 11.5px; color: var(--text-faint); line-height: 1.7; }

.collab-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12.5px;
    margin-top: 8px;
    font-variant-numeric: tabular-nums;
}
.collab-table th {
    text-align: left;
    padding: 6px 10px;
    border-bottom: 1px solid var(--border-strong);
    color: var(--text-muted);
    font-size: 11.5px;
    font-weight: 600;
}
.collab-table td {
    padding: 7px 10px;
    border-bottom: 1px solid var(--border);
    color: var(--text-secondary);
}
.collab-table tr:last-child td { border-bottom: 0; }
.collab-table .is-same td { color: var(--text-faint); }
.collab-table .is-diff td { color: var(--text-primary); }
.collab-table .is-diff td:first-child { box-shadow: inset 2px 0 0 var(--caution); }

.collab-more { margin-top: 10px; font-size: 12px; }
.collab-more summary { color: var(--text-faint); cursor: pointer; }
.collab-answer {
    margin-top: 8px;
    padding-left: 10px;
    border-left: 2px solid var(--border-strong);
    color: var(--text-muted);
    font-size: 12.5px;
    line-height: 1.7;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    max-height: 300px;
    overflow-y: auto;
}

/* 正文里对不上的那个值：琥珀虚线 + 一个 ≠ 角标。只标第一处，不满屏黄底 */
mark.collab-mark {
    background: var(--caution-dim);
    color: inherit;
    padding: 0 1px;
    border-radius: 2px;
    border-bottom: 1px dashed var(--caution);
}
sup.collab-ref {
    margin-left: 1px;
    color: var(--caution);
    font-size: 10px;
}

/* 任务卡下的复核条：淡、折叠、不抢主结果的戏 */
.task-review { margin-top: 8px; border-top: 1px solid var(--border); padding-top: 8px; }
.task-review-head {
    border: 0;
    background: transparent;
    padding: 0;
    color: var(--text-faint);
    font-size: 12px;
    cursor: pointer;
}
.task-review-head:hover { color: var(--text-secondary); }

/* 统一核对分数徽章（复核卡/协同报告共用）：色阶走警示色体系，薄荷不碰 */
.check-score {
    display: inline-block;
    margin-right: 8px;
    padding: 1px 8px;
    border-radius: var(--radius-pill);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}
.check-score.is-good { background: var(--surface-3); color: var(--text-secondary); }
.check-score.is-warn { background: color-mix(in srgb, var(--caution) 18%, transparent); color: var(--caution); }
.check-score.is-bad { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger); }

/* 意见的严重度小标：高/中/低 */
.review-severity {
    display: inline-block;
    margin-right: 6px;
    padding: 0 6px;
    border-radius: var(--radius-pill);
    font-size: 10px;
    line-height: 1.7;
}
.review-severity.is-high { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger); }
.review-severity.is-mid { background: color-mix(in srgb, var(--caution) 18%, transparent); color: var(--caution); }
.review-severity.is-low { background: var(--surface-3); color: var(--text-muted); }

.task-review-breakdown {
    color: var(--text-faint);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}
.task-review-list { margin-top: 8px; display: flex; flex-direction: column; gap: 8px; }
/* 复核意见默认摊开、点标题收起：没有这条，上面的 display 压过 [hidden]，点标题收不起来 */
.task-review-list[hidden] { display: none; }
.task-review-item { font-size: 12.5px; line-height: 1.65; }
.task-review-issue { color: var(--text-secondary); }
.task-review-suggestion { color: var(--text-faint); margin-top: 2px; }
.task-review-adopt {
    margin-top: 4px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--text-secondary);
    font-size: 11px;
    padding: 2px 10px;
    cursor: pointer;
}
.task-review-adopt:hover { border-color: var(--accent); color: var(--accent); }

/* 设置 › 朗读音色：卡片网格，跟主题卡同一家人（同样的描边+选中光环语言） */
/* 朗读（2026-09-08 起）：一行「音色 ｜ 女声·男声」，分段控件见 .settings-segmented；
   .voice-card 只剩类名给 JS 认，长相全在 .settings-segment。 */

/* 设置 › 主题 › 对话字号（2026-09-08 起）：四段式，每段用自己的绝对字号写自己的名字——
   不乘系数（跟着当前选择变的话四段永远一样大，就没法比了）；段的高度对齐，字号只影响字。 */
.fontsize-grid .settings-segment { min-height: 30px; }
.fontsize-card[data-scale="compact"] { font-size: 12px; }
.fontsize-card[data-scale="default"] { font-size: 13px; }
.fontsize-card[data-scale="comfortable"] { font-size: 14.5px; }
.fontsize-card[data-scale="large"] { font-size: 16.5px; }

.theme-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 10px;
    margin: 0 0 8px;
}

.theme-card {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 3px;
    padding: 10px 12px 12px;
    border: 1px solid var(--border-strong);
    border-radius: 12px;
    background: var(--surface-2);
    text-align: left;
    transition: border-color 150ms ease, box-shadow 150ms ease;
}

.theme-card:hover {
    border-color: var(--input-border-focus);
}

.theme-card.is-active {
    border-color: var(--focus-border);
    box-shadow: 0 0 0 1px var(--focus-ring);
}

.theme-card:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Miniature of the chat layout. The data-theme attribute on this element re-resolves
   the colour tokens locally, so each swatch previews its theme's real palette. */
.theme-swatch {
    display: flex;
    height: 84px;
    margin-bottom: 9px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg);
    overflow: hidden;
}

.theme-swatch-rail {
    width: 26%;
    border-right: 1px solid var(--border);
    background: var(--surface-1);
}

.theme-swatch-body {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 7px;
    padding: 10px 12px;
}

.theme-swatch-line {
    width: 82%;
    height: 6px;
    border-radius: 3px;
    background: var(--surface-2-hover);
}

.theme-swatch-line.is-strong {
    width: 58%;
    background: var(--text-faint);
}

.theme-swatch-dot {
    width: 16px;
    height: 6px;
    border-radius: 3px;
    background: var(--accent);
}

.theme-card-name {
    color: var(--text-primary);
    font-size: 13px;
    font-weight: 600;
}

.theme-card.is-active .theme-card-name {
    color: var(--accent);
}

.theme-card-desc {
    color: var(--text-faint);
    font-size: 11.5px;
}

/* ── Responsive ── */

@media (max-width: 720px) {
    .settings-dialog {
        width: min(100%, calc(100vw - 24px));
        height: min(760px, calc(100dvh - 24px));
        min-height: 0;
        max-height: calc(100dvh - 24px);
        flex-direction: column;
    }

    .settings-sidebar {
        width: 100%;
        flex: 0 0 auto;
        border-right: 0;
        border-bottom: 1px solid var(--border);
    }

    .settings-sidebar-title {
        padding: 16px 16px 10px;
        font-size: 14px;
    }

    .settings-nav-items {
        padding: 0 10px 10px;
    }

    .settings-nav-item {
        width: max-content;
        padding: 0 8px;
        font-size: 12px;
    }

    .settings-content { min-height: 0; }

    .settings-field-row {
        flex-direction: column;
        gap: 14px;
        align-items: stretch;
    }

    .settings-field--provider {
        width: 100%;
        flex: 0 0 auto;
    }

    .settings-panel {
        padding: 20px 16px 24px;
    }

    .settings-dialog-actions {
        position: sticky;
        bottom: -16px;
        z-index: 2;
        margin-top: 20px;
        padding: 14px 0 0;
        background: linear-gradient(180deg, transparent, var(--surface-1) 28%);
    }
}

/* ─── End Settings ─── */

/* ─── Model picker (topbar) ─── */

.model-picker {
    /* display:flex 是功能性的，不是排版偏好：它让按钮变成可收缩的 flex 子项，
       宽度由 flex 布局给（有空间就是内容宽，没空间才收缩）。
       原来按钮是块级子元素、靠 max-width:100% 去够父容器，而父容器的宽度反过来又由按钮的
       内容决定——这个循环让 Chromium 把按钮定在比内容窄 8px 的位置，于是 glm-5.3 这种
       短名字在任何视口下都被省略号截掉（实测 name 49/41，缺的正好是被 flex 压掉的那 8px）。 */
    display: flex;
    min-width: 0;
    position: relative;
}

.model-picker-button {
    /* 340px 是"再宽也就这样"的上限。"绝不越过父容器"这条不再靠 max-width:100%——
       那个百分比对着一个宽度由本按钮决定的父元素解析，循环依赖会白白吃掉 8px（见上）。
       现在靠 flex:0 1 auto + min-width:0：空间够就取内容宽（短名字完整显示），
       空间不够才收缩，收缩后由 .model-name 的 text-overflow 出省略号。
       margin-left:-8px 的视觉左移靠 padding 抵消，不影响宽度计算。 */
    max-width: 340px;
    flex: 0 1 auto;
    min-width: 0;
    min-height: 32px;
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: -8px;
    padding: 0 8px;
    border: 1px solid transparent;
    border-radius: 8px;
    background: transparent;
    color: var(--text-primary);
    transition: background 180ms ease, border-color 180ms ease;
}

.model-picker-button:hover {
    border-color: var(--border);
    background: var(--surface-2);
}

.model-picker.is-open .model-picker-button {
    border-color: var(--border-strong);
    background: var(--surface-2);
}

.model-picker-chevron {
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
    color: var(--text-faint);
    transition: transform 180ms cubic-bezier(.16, 1, .3, 1);
}

.model-picker.is-open .model-picker-chevron {
    transform: rotate(180deg);
}

.model-picker-menu {
    width: min(320px, calc(100vw - 32px));
    max-height: min(60vh, 420px);
    position: absolute;
    z-index: 40;
    top: calc(100% + 8px);
    left: 0;
    padding: 6px;
    border: 1px solid var(--border-strong);
    border-radius: 12px;
    background: var(--surface-float);
    box-shadow: inset 0 1px 0 var(--edge-light), var(--shadow-pop);
    overflow-y: auto;
    scrollbar-color: var(--surface-2-hover) transparent;
    scrollbar-width: thin;
    animation: dialog-in 160ms cubic-bezier(.16, 1, .3, 1) both;
}

.model-picker-menu[hidden] { display: none; }

.model-picker-group {
    margin: 10px 8px 4px;
    color: var(--text-faint);
    font-size: 11px;
    letter-spacing: .08em;
    text-transform: uppercase;
}

.model-picker-group:first-child { margin-top: 4px; }

.model-picker-item {
    width: 100%;
    min-height: 38px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 0 10px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text-muted);
    text-align: left;
    transition: background 150ms ease, color 150ms ease;
}

.model-picker-item:hover:not(:disabled) {
    background: var(--surface-2-hover);
    color: var(--text-primary);
}

.model-picker-item:disabled {
    opacity: .45;
    cursor: not-allowed;
}

.model-picker-item.is-current {
    background: var(--accent-dim);
    color: var(--accent);
}

.model-picker-item-label {
    overflow: hidden;
    font-size: 12.5px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.model-picker-check {
    width: 15px;
    height: 15px;
    flex: 0 0 auto;
    opacity: 0;
    transition: opacity 150ms ease;
}

.model-picker-check svg { width: 15px; height: 15px; }

.model-picker-item.is-current .model-picker-check { opacity: 1; }

.model-picker-empty {
    margin: 10px;
    color: var(--text-faint);
    font-size: 12px;
    line-height: 1.6;
}

/* ─── Reasoning (deepseek-reasoner 等推理模型的思考过程) ─── */

.reasoning-block {
    margin: 0 0 10px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--tint-1);
    overflow: hidden;
}

.reasoning-block[hidden] { display: none; }

.reasoning-summary {
    min-height: 34px;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 10px;
    color: var(--text-faint);
    font-size: 12px;
    list-style: none;
    cursor: pointer;
    transition: color 150ms ease, background 150ms ease;
}

.reasoning-summary::-webkit-details-marker { display: none; }
.reasoning-summary::marker { content: ""; }

.reasoning-summary:hover {
    background: var(--surface-2-hover);
    color: var(--text-muted);
}

.reasoning-summary-icon,
.reasoning-summary-chevron {
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
}

.reasoning-summary-label { flex: 1; }

.reasoning-summary-chevron {
    transition: transform 180ms cubic-bezier(.16, 1, .3, 1);
}

.reasoning-block[open] .reasoning-summary-chevron { transform: rotate(180deg); }

.msg.is-streaming .reasoning-summary { color: var(--accent); }

.reasoning-content {
    max-height: 320px;
    padding: 10px 12px 12px;
    border-top: 1px solid var(--border);
    color: var(--text-faint);
    font-size: 12.5px;
    line-height: 1.75;
    white-space: pre-wrap;
    word-break: break-word;
    overflow-y: auto;
    scrollbar-color: var(--surface-2-hover) transparent;
    scrollbar-width: thin;
}

/* ─── Provider management (settings › 模型) ─── */

#provider-list-view[hidden] { display: none; }

.provider-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin: 4px 0 14px;
}

.provider-empty {
    margin: 6px 0 0;
    color: var(--text-faint);
    font-size: 12.5px;
    line-height: 1.7;
}

.provider-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--surface-2);
    transition: border-color 180ms ease, background 180ms ease;
}

.provider-row:hover {
    border-color: var(--border-strong);
    background: var(--surface-2-hover);
}

.provider-row-main {
    min-width: 0;
    flex: 1;
}

.provider-row-name {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    color: var(--text-primary);
    font-size: 13.5px;
    font-weight: 600;
}

.provider-row-warn {
    padding: 2px 7px;
    border-radius: 999px;
    background: var(--danger-dim);
    color: var(--danger);
    font-size: 11px;
    font-weight: 500;
}

.provider-row-meta {
    overflow: hidden;
    margin: 5px 0 0;
    color: var(--text-faint);
    font-size: 11.5px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.provider-row-actions {
    display: flex;
    flex: 0 0 auto;
    gap: 4px;
}

.provider-row-action {
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text-faint);
    transition: color 150ms ease, background 150ms ease;
}

.provider-row-action:hover {
    background: var(--surface-1);
    color: var(--text-primary);
}

.provider-row-action.is-danger:hover {
    background: var(--danger-dim);
    color: var(--danger);
}

.provider-row-action svg {
    width: 15px;
    height: 15px;
    stroke-width: 1.7;
}

.provider-add-button {
    width: 100%;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    border: 1px dashed var(--border-strong);
    border-radius: 12px;
    background: transparent;
    color: var(--text-muted);
    font-size: 13px;
    font-weight: 500;
    transition: color 150ms ease, border-color 150ms ease, background 150ms ease;
}

.provider-add-button:hover {
    border-color: var(--focus-border);
    background: var(--accent-dim);
    color: var(--accent);
}

.provider-add-button svg {
    width: 15px;
    height: 15px;
}

.provider-form {
    min-height: 100%;
    display: flex;
    flex-direction: column;
}

.provider-form[hidden] { display: none; }

.provider-form .settings-field-row { margin-top: 16px; }

.settings-field-hint {
    margin: 6px 0 0;
    color: var(--text-faint);
    font-size: 11.5px;
    line-height: 1.6;
}

.settings-field-hint code {
    padding: 1px 5px;
    border-radius: 5px;
    background: var(--surface-2);
    font-family: var(--font-mono);
    font-size: 11px;
}

/* 可用模型独占整行，扫描结果直接铺在里面 */
.settings-field--models { width: 100%; }

@media (max-width: 720px) {
    .model-picker-button { max-width: 200px; }
}

/* 可用模型盒子：扫描结果与手动添加都直接落在这里 */
.models-box {
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    background: var(--surface-2);
    overflow: hidden;
}

.models-scan-cta {
    width: 100%;
    min-height: 88px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    font-size: 14px;
    font-weight: 500;
    transition: color 150ms ease, background 150ms ease;
}

.models-scan-cta[hidden] { display: none; }

.models-scan-cta:hover:not(:disabled) {
    background: var(--accent-dim);
    color: var(--accent);
}

.models-scan-cta:disabled { opacity: .6; }

.models-scan-cta svg {
    width: 19px;
    height: 19px;
    stroke-width: 1.8;
}

.models-scan-cta.is-busy svg { animation: scan-spin 900ms linear infinite; }

/* 有模型后收成一条窄的，仍是唯一的扫描入口 */
.models-scan-cta.is-compact {
    min-height: 38px;
    border-bottom: 1px solid var(--border);
    color: var(--text-faint);
    font-size: 12px;
}

.models-scan-cta.is-compact svg { width: 14px; height: 14px; }

.models-list {
    max-height: 300px;
    padding: 0 6px;
    overflow-y: auto;
    scrollbar-color: var(--surface-2-hover) transparent;
    scrollbar-width: thin;
}

.models-list:not(:empty) { padding-bottom: 6px; }

.models-manual {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 8px;
    border-top: 1px solid var(--border);
}

.models-manual-input {
    flex: 1;
    min-width: 0;
    min-height: 32px;
    padding: 0 10px;
    border: 1px solid var(--border);
    border-radius: 7px;
    background: var(--surface-1);
    color: var(--text-primary);
    font-size: 12px;
    outline: none;
    transition: border-color 150ms ease;
}

.models-manual-input:focus { border-color: var(--focus-border); }

.models-manual-input::placeholder {
    color: var(--text-faint);
    font-family: var(--font-sans);
}

.models-manual .settings-inline-button { margin-bottom: 0; }

/* ─── Model scan (settings › 模型 › 扫描) ─── */

.settings-label-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.settings-label-row .settings-label { margin-bottom: 6px; }

.settings-inline-button {
    min-height: 24px;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin-bottom: 6px;
    padding: 0 8px;
    border: 1px solid var(--border-strong);
    border-radius: 7px;
    background: transparent;
    color: var(--text-muted);
    font-size: 11.5px;
    font-weight: 500;
    transition: color 150ms ease, border-color 150ms ease, background 150ms ease;
}

.settings-inline-button:hover:not(:disabled) {
    border-color: var(--focus-border);
    background: var(--accent-dim);
    color: var(--accent);
}

.settings-inline-button:disabled { opacity: .55; }

.settings-inline-button svg {
    width: 12px;
    height: 12px;
    stroke-width: 2;
}

.settings-inline-button.is-busy svg {
    animation: scan-spin 900ms linear infinite;
}

@keyframes scan-spin {
    to { transform: rotate(360deg); }
}

.scan-group {
    margin: 10px 6px 4px;
    color: var(--text-faint);
    font-size: 11px;
    letter-spacing: .06em;
    text-transform: uppercase;
}

.scan-item {
    display: flex;
    align-items: center;
    gap: 9px;
    min-height: 32px;
    padding: 0 8px;
    border-radius: 7px;
    color: var(--text-muted);
    cursor: pointer;
    transition: background 150ms ease, color 150ms ease;
}

.scan-item:hover {
    background: var(--surface-2-hover);
    color: var(--text-primary);
}

.scan-item-box {
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
    accent-color: var(--accent);
    cursor: pointer;
}

.scan-item-name {
    overflow: hidden;
    flex: 1;
    font-size: 12px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.scan-item-badge {
    flex: 0 0 auto;
    padding: 2px 7px;
    border-radius: 999px;
    background: var(--surface-1);
    color: var(--text-faint);
    font-size: 10.5px;
}

.scan-item-badge.is-dead {
    background: var(--danger-dim);
    color: var(--danger);
}

/* AVAILABLE（验证通过）——用强调色标出真正验证成功的模型，跟中性/失败态区分开 */
.scan-item-badge.is-ok {
    background: var(--accent-dim);
    color: var(--accent);
}

/* UNKNOWN（暂时无法确认）——中性色，不是"错了"，只是这次没判定出来，跟 is-dead 区分开 */
.scan-item-badge.is-pending {
    color: var(--text-faint);
}

/* UNAVAILABLE/UNKNOWN 的复选框天生 disabled；样式上再弱化一层，
   让"能不能勾"在扫一眼时就分得出来，不用逐行读徽标文字 */
.scan-item.is-disabled { cursor: not-allowed; opacity: .55; }
.scan-item.is-disabled:hover { background: transparent; color: var(--text-muted); }
.scan-item-box:disabled { cursor: not-allowed; }

/* 获取/验证请求还在飞：列表区变暗、暂停交互，避免这期间点到马上要被替换掉的行 */
.models-list.is-verifying { opacity: .5; pointer-events: none; }

.scan-verifying {
    margin: 20px 8px;
    color: var(--text-faint);
    font-size: 12.5px;
    text-align: center;
}

/* ─── 图片附件 ─── */

/* chip 里直接放缩略图，比文件类型图标有用 */
.file-chip.is-image .file-chip-icon {
    width: 28px;
    height: 28px;
    border-radius: 6px;
    overflow: hidden;
}

.file-chip.is-image .file-chip-icon img {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: cover;
}

.attach-images {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 8px;
}

.attach-image {
    width: 132px;
    height: 132px;
    padding: 0;
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    background: var(--surface-2);
    overflow: hidden;
    transition: border-color 150ms ease, transform 150ms ease;
}

.attach-image:hover {
    border-color: var(--border-strong);
    transform: translateY(-1px);
}

.attach-image img {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: cover;
}

.image-viewer {
    position: fixed;
    z-index: 200;
    inset: 0;
    display: grid;
    /* 大图的固有尺寸不能撑开网格，否则图片会在超出视口的行内居中。 */
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: 44px minmax(0, 1fr) 44px;
    gap: 16px;
    place-items: center;
    width: 100%;
    height: 100dvh;
    max-width: none;
    max-height: none;
    margin: 0;
    border: 0;
    padding: 16px 32px;
    /* 退出全屏后窗口压在任务栏底下时（--taskbar-overlap 由 measureTaskbarOverlap 量出），底边多让出那几十像素 */
    padding-bottom: calc(16px + var(--taskbar-overlap, 0px));
    background: var(--scrim-strong);
    backdrop-filter: blur(6px);
    cursor: zoom-out;
    animation: fade-in 160ms ease-out both;
    color: var(--text-primary);
    font-family: var(--font-sans);
}
.image-viewer:not([open]) { display: none; }
.image-viewer::backdrop { background: transparent; }
.image-viewer-header {
    position: relative;
    z-index: 1;
    pointer-events: none;
    width: min(100%, 1100px);
    display: flex;
    align-items: center;
    justify-content: flex-end; /* 2026-10-09 起只是占位的一行：文件名去掉了，关闭键钉在整个预览的右上角（见 .image-viewer-close） */
    gap: 16px;
    cursor: default;
}
.image-viewer-stage {
    position: relative;
    width: 100%;
    height: 100%;
    min-height: 0;
    min-width: 0;
    display: grid;
    grid-template: minmax(0, 1fr) / minmax(0, 1fr);
    place-items: center;
    /* 初始适配仍为控件留空间；缩放和拖动后只在窗口边缘裁剪。 */
    overflow: visible;
}
.image-viewer-toolbar {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    height: 44px;
    padding: 3px 6px;
    gap: 2px;
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    background: var(--surface-float);
    box-shadow: var(--shadow-soft);
    cursor: default;
}
.image-viewer-button {
    display: inline-grid;
    place-items: center;
    flex: 0 0 auto;
    min-width: 36px;
    height: 36px;
    padding: 0 8px;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--text-primary);
    font: inherit;
    font-size: 20px;
    cursor: pointer;
}
.image-viewer-button:hover:not(:disabled) { background: var(--surface-2-hover); }
.image-viewer-button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.image-viewer-button:disabled { opacity: .35; cursor: default; }
/* 关闭键：钉在整个预览的右上角（2026-10-09 用户要的），圆形带描边；图标是画的两条线，不用文字「×」——字形在字体里偏上，圆环里看着不正 */
.image-viewer-close {
    position: absolute;
    top: 16px;
    right: 16px;
    z-index: 2;
    width: 40px;
    height: 40px;
    min-width: 0;
    padding: 0;
    border: 1px solid var(--border-strong);
    background: var(--surface-float);
    box-shadow: var(--shadow-soft);
    pointer-events: auto;
}
.image-viewer-close svg { width: 18px; height: 18px; display: block; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
.image-viewer-percentage { min-width: 58px; text-align: center; font-size: 12px; font-variant-numeric: tabular-nums; }
.image-viewer-reset { margin-left: 6px; padding: 0 12px; font-size: 12px; border-left: 1px solid var(--border-strong); border-radius: 0 999px 999px 0; }
.image-viewer-status { display: flex; align-items: center; gap: 12px; color: #f4f5f7; font-size: 13px; }
.image-viewer-retry { font-size: 13px; background: var(--surface-float); }
.image-viewer [hidden] { display: none; }
@media (max-width: 720px) {
    .image-viewer { padding-left: 12px; padding-right: 12px; gap: 10px; }
    .image-viewer-close { top: 10px; right: 10px; }
}

.image-viewer {
    overflow: hidden; /* 放大后溢出的部分不撑滚动条 */
}

.image-viewer img {
    /* 默认小一点（2026-09-30 用户要的）：最多占视口 78%，滚轮再放大；缩放走 transform，原点在中心 */
    max-width: min(100%, 78vw);
    max-height: min(100%, 78vh);
    border-radius: 12px;
    box-shadow: var(--shadow-modal);
    transform-origin: center;
    cursor: zoom-in;
    user-select: none;
    touch-action: none;
    will-change: transform;
}

.image-viewer img.is-zoomed {
    cursor: grab;
}

.image-viewer img.is-zoomed:active {
    cursor: grabbing;
}

@media (max-width: 720px) {
    .attach-image {
        width: 104px;
        height: 104px;
    }
}

/* ─── 附件视图抽屉 ─── */

.artifact-drawer {
    width: min(56vw, 900px);
    /* 可收缩（flex-shrink: 1）：对话列在并排模式下守着 480px 下限（见 body.artifact-open
       .main），挤不下的部分由抽屉让出。340 下限 = 并排模式最窄的 1101px 视口 − 268 侧栏
       − 480 对话列，再窄就该走 1100 那条全屏浮层规则了。 */
    min-width: min(340px, 100%);
    display: flex;
    flex: 0 1 auto;
    flex-direction: column;
    border-left: 1px solid var(--border-strong);
    background: var(--surface-1);
    animation: artifact-in 220ms cubic-bezier(.16, 1, .3, 1) both;
}

.artifact-drawer[hidden] { display: none; }

@keyframes artifact-in {
    from { opacity: 0; transform: translateX(24px); }
    to { opacity: 1; transform: none; }
}

.artifact-head {
    height: 56px;
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 10px;
    padding: 0 12px 0 18px;
    border-bottom: 1px solid var(--border);
}

.artifact-kind {
    flex: 0 0 auto;
    padding: 3px 8px;
    border-radius: 999px;
    background: var(--accent-dim);
    color: var(--accent);
    font-size: 10.5px;
    letter-spacing: .08em;
}

.artifact-title {
    overflow: hidden;
    flex: 1;
    margin: 0;
    color: var(--text-primary);
    font-size: 13px;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.artifact-close,
/* 画布上当前生效的筛选（对话里设的，会一直留在这份计划上）：可点清除。2026-09-08 审计 #22 */
.artifact-filter-chip {
    flex: 0 0 auto;
    border: 0;
    padding: 3px 9px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--accent) 16%, transparent);
    color: var(--text-primary);
    font-size: 12px;
    line-height: 1.4;
    cursor: pointer;
}
.artifact-filter-chip:hover { background: color-mix(in srgb, var(--accent) 26%, transparent); }

.artifact-download {
    width: 32px;
    height: 32px;
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text-faint);
    transition: color 150ms ease, background 150ms ease;
}

.artifact-close:hover,
.artifact-download:hover {
    background: var(--surface-2);
    color: var(--text-primary);
}

.artifact-close svg, .artifact-download svg { width: 16px; height: 16px; }

/* 成果工作台：只在打开的是 AI 生成成果时出现（见 chat.js openArtifact），普通上传文件
   没有这一条。状态徽标 + 格式切换 + 确认/放弃，三块一行，超宽时格式区自己横向滚动，
   不挤压两侧的状态/操作按钮。 */
.workbench-bar {
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 8px 12px 8px 18px;
    border-bottom: 1px solid var(--border);
    background: var(--surface-2);
}
.workbench-bar[hidden] { display: none; }

/* 失败时保留旧内容原地展示原因——独占一行，不挤占状态/格式/操作那一行的空间。 */
.workbench-failure {
    flex: 1 1 100%;
    padding: 4px 0 0;
    color: var(--danger);
    font-size: 11.5px;
}
.workbench-failure[hidden] { display: none; }

.workbench-status {
    flex: 0 0 auto;
    padding: 2px 8px;
    border-radius: var(--radius-sm);
    font-size: 11px;
    font-weight: 600;
    white-space: nowrap;
}
.workbench-status.is-draft { background: var(--surface-3); color: var(--text-faint); }
.workbench-status.is-updating { background: var(--accent-dim); color: var(--accent); }
.workbench-status.is-active { background: var(--accent-dim); color: var(--accent); }
.workbench-status.is-discarded { background: var(--surface-3); color: var(--text-faint); }

.workbench-formats {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    gap: 4px;
    overflow-x: auto;
    scrollbar-width: none;
}
.workbench-formats::-webkit-scrollbar { display: none; }

.workbench-format-tab {
    flex: 0 0 auto;
    padding: 3px 9px;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--text-faint);
    font-size: 11.5px;
    white-space: nowrap;
    cursor: pointer;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.workbench-format-tab:hover { background: var(--surface-3); color: var(--text-primary); }
.workbench-format-tab.is-active { background: var(--accent-dim); color: var(--accent); font-weight: 600; }
.workbench-format-tab.is-stale { opacity: .6; }

.workbench-actions {
    display: flex;
    flex: 0 0 auto;
    gap: 6px;
}

.workbench-discard,
.workbench-previous,
.workbench-confirm {
    flex: 0 0 auto;
    padding: 5px 12px;
    border: 0;
    border-radius: var(--radius-sm);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: background var(--dur-1) var(--ease-soft), opacity var(--dur-1) var(--ease-soft);
}
/* 这两个按钮在"生成即入库"之后换了职责，视觉重量也得跟着换：
   discard 位现在是「完成编辑」——把成果交出去、结束编辑，是这一栏的正向主操作，给 accent；
   confirm 位现在是「撤销上次修改」——回退，是补救动作。让补救动作当全栏最抢眼的那个，
   等于在鼓励用户点它。薄荷色只用于状态和主操作（见 design-system/pages/chat.md）。 */
.workbench-discard { background: var(--accent); color: var(--surface-1); }
.workbench-discard:hover { filter: brightness(1.06); }
/* 「上一版」是查看类动作，不该跟撤销/完成编辑抢视觉重量——只描一圈边，不填底色 */
.workbench-previous {
    background: transparent;
    color: var(--text-faint);
    box-shadow: inset 0 0 0 1px var(--border-strong);
    font-weight: 500;
}
.workbench-previous:hover { color: var(--text-primary); background: var(--surface-3); }
.workbench-confirm { background: var(--surface-3); color: var(--text-secondary); }
.workbench-confirm:hover { background: var(--surface-2-hover); color: var(--text-primary); }
.workbench-confirm:disabled,
.workbench-previous:disabled,
.workbench-discard:disabled {
    opacity: .5;
    cursor: not-allowed;
}
.workbench-discard[hidden],
.workbench-previous[hidden],
.workbench-confirm[hidden] { display: none; }

/* 「+ 添加格式」下拉。按钮挂在 .workbench-formats【旁边】而不是里面：那一排是
   overflow-x:auto 的横向滚动容器，规范规定另一轴会连带从 visible 算成 auto——
   也就是说纵向同样裁剪，放在里面时往下展开的菜单整个被裁没（2026-08-24 修复）。 */
.workbench-add-format { position: relative; flex: 0 0 auto; }
.workbench-format-tab.is-add {
    color: var(--text-faint);
    box-shadow: inset 0 0 0 1px var(--border-strong);
}
.workbench-format-tab.is-add:hover { color: var(--text-primary); }
.workbench-add-format-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 30;
    display: flex;
    flex-direction: column;
    min-width: 92px;
    padding: 4px;
    gap: 1px;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    border: 1px solid var(--border-strong);
    box-shadow: var(--shadow-pop);
}
.workbench-add-format-menu[hidden] { display: none; }
.workbench-add-format-item {
    border: 0;
    border-radius: 4px;
    padding: 5px 10px;
    text-align: left;
    background: transparent;
    color: var(--text-secondary);
    font-size: 11.5px;
    cursor: pointer;
}
.workbench-add-format-item:hover { background: var(--surface-3); color: var(--text-primary); }

/* 编辑权归属提示：当前对话只能看这份成果时出现，带一个明确的接管入口。
   整行占满工作台栏的宽度，换行摆在格式 Tab 下面——它是一条状态说明，不是一个按钮。 */
.workbench-ownership {
    flex: 1 0 100%;
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 6px;
    font-size: 11.5px;
    color: var(--text-faint);
}
.workbench-ownership[hidden] { display: none; }
.workbench-inline-button {
    border: 0;
    border-radius: var(--radius-sm);
    padding: 3px 9px;
    background: var(--surface-3);
    color: var(--text-secondary);
    font-size: 11.5px;
    cursor: pointer;
}
.workbench-inline-button:hover { background: var(--surface-2-hover); color: var(--text-primary); }
.workbench-inline-button:disabled { opacity: .5; cursor: not-allowed; }

.artifact-body {
    min-height: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
}

.artifact-loading,
.artifact-error {
    margin: 24px 18px;
    color: var(--text-faint);
    font-size: 13px;
}

.artifact-error { color: var(--danger); }

/* PDF preview: the browser's native viewer inside the drawer */
.artifact-frame {
    width: 100%;
    min-height: 0;
    flex: 1;
    border: 0;
    background: var(--surface-1);
}

/* DOCX preview: extracted text, i.e. exactly what the model receives */
/* DOCX/XLSX 没有原生排版预览，抽屉里显示的就是逐字提取的纯文本——这条提示明确说清楚
   "这不是真实 Word/Excel 版式"，不然转换/生成之后打开一看只有纯文字，用户很容易以为
   转换没生效或者结果是坏的。PDF 走真实渲染（iframe），不需要也不该出现这条提示。 */
.artifact-preview-note {
    flex: 0 0 auto;
    padding: 8px 20px;
    border-bottom: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text-faint);
    font-size: 11.5px;
}

/* 原文查看里的嵌入图：限宽、圆角、说明文字用最淡一档 */
.artifact-figure {
    margin: 14px 0;
    text-align: center;
}

.artifact-figure img {
    max-width: min(100%, 640px);
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
}

.artifact-figure figcaption {
    margin-top: 6px;
    font-size: 12px;
    color: var(--text-faint);
}

.artifact-figure-missing {
    color: var(--text-faint);
}

.artifact-text {
    min-height: 0;
    flex: 1;
    overflow-y: auto;
    padding: 18px 20px 28px;
    color: var(--text-secondary);
    font-size: 13.5px;
    line-height: 1.8;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    scrollbar-color: var(--surface-2-hover) transparent;
    scrollbar-width: thin;
}

/* M5：表格视图。列宽不固定，横向内容多时自己滚，不撑坏抽屉宽度。
 *
 * 左右 20px 的文字缩进放在**单元格**上，不放在这个滚动容器上。放容器上的话表头背景和
 * 那条 border-bottom 也跟着缩进 20px，而 .artifact-head 的下边线是通铺满宽的——
 * 两条本该接成一体的线在两端对不齐，看着就是"文件栏和表格没贴合"。
 * 上内边距必须是 0：留 4px 的话，未滚动时文件栏下边线和表头之间就夹着一条 4px 的底色缝。
 */
.schedule-table-wrap {
    min-height: 0;
    flex: 1;
    overflow: auto;
    padding: 0 0 24px;
    scrollbar-color: var(--surface-2-hover) transparent;
    scrollbar-width: thin;
}

.schedule-table {
    width: 100%;
    /*
     * separate + 0 间距，**不能**用 collapse。
     * border-collapse: collapse 下，单元格的背景和边框是由 table 统一绘制的，不归 th 自己；
     * 于是 position: sticky 把 th 的盒子钉住了，背景和下边线却仍然跟着表格滚走——
     * 一滚动，行就直接从表头文字后面穿过去。间距归零后视觉与 collapse 一致，
     * 而且这里只用到 border-bottom，不存在相邻边框变双线的问题。
     */
    border-collapse: separate;
    border-spacing: 0;
    font-size: 13px;
}

.schedule-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    padding: 8px 10px;
    background: var(--surface-1);
    border-bottom: 1px solid var(--border-strong);
    color: var(--text-faint);
    font-size: 11.5px;
    font-weight: 600;
    text-align: left;
    white-space: nowrap;
}

.schedule-table tbody td {
    padding: 7px 10px;
    border-bottom: 1px solid var(--border);
    color: var(--text-secondary);
    white-space: nowrap;
}

/* 首末列补回原先由容器承担的 20px 缩进：文字位置不变，但背景和边框通铺到抽屉两边，
   跟 .artifact-head 的下边线严丝合缝。横向滚动时它们跟着内容走，和原来的容器内边距行为一致。 */
.schedule-table th:first-child, .schedule-table td:first-child { padding-left: 20px; }
.schedule-table th:last-child, .schedule-table td:last-child { padding-right: 20px; }

.schedule-table-name { color: var(--text-primary); white-space: normal !important; min-width: 220px; }
.schedule-table-critical { color: var(--bar-critical); font-weight: 600; }
.schedule-table tbody tr.is-critical td { background: color-mix(in srgb, var(--bar-critical) 6%, transparent); }
.schedule-table-empty { padding: 24px 10px; color: var(--text-faint); text-align: center; white-space: normal; }

/* Phase C：Excel 在线预览——真数据网格，跟 .schedule-table 同一个技术方案
 * （separate + 0 间距，不能用 collapse，理由同上）。 */
.xlsx-grid {
    min-height: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
}

.xlsx-grid-tabs {
    flex: 0 0 auto;
    display: flex;
    gap: 4px;
    padding: 8px 20px;
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
    scrollbar-width: none;
}
.xlsx-grid-tabs::-webkit-scrollbar { display: none; }

.xlsx-grid-tab {
    flex: 0 0 auto;
    padding: 4px 10px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-faint);
    font-size: 12px;
    white-space: nowrap;
    cursor: pointer;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.xlsx-grid-tab:hover { background: var(--surface-2); color: var(--text-primary); }
.xlsx-grid-tab.is-active { background: var(--surface-2); color: var(--accent); font-weight: 600; }

.xlsx-grid-body {
    min-height: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
}

.xlsx-grid-meta {
    flex: 0 0 auto;
    padding: 8px 20px;
    color: var(--text-faint);
    font-size: 11.5px;
}

.xlsx-grid-scroll {
    min-height: 0;
    flex: 1;
    overflow: auto;
    padding: 0 0 24px;
    scrollbar-color: var(--surface-2-hover) transparent;
    scrollbar-width: thin;
}

.xlsx-grid-table {
    border-collapse: separate;
    border-spacing: 0;
    font-size: 12.5px;
}

.xlsx-grid-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    padding: 7px 10px;
    background: var(--surface-1);
    border-bottom: 1px solid var(--border-strong);
    color: var(--text-faint);
    font-weight: 600;
    text-align: left;
    white-space: nowrap;
}

.xlsx-grid-table tbody td {
    padding: 6px 10px;
    border-bottom: 1px solid var(--border);
    color: var(--text-secondary);
    white-space: nowrap;
}

.xlsx-grid-table th:first-child, .xlsx-grid-table td:first-child { padding-left: 20px; }
.xlsx-grid-table th:last-child, .xlsx-grid-table td:last-child { padding-right: 20px; }

/* M5：看板视图。按完成度分三列——进度计划数据里能推出的最自然的分组。 */
.schedule-kanban {
    min-height: 0;
    flex: 1;
    display: flex;
    gap: 14px;
    overflow-x: auto;
    padding: 16px 20px 24px;
    scrollbar-color: var(--surface-2-hover) transparent;
    scrollbar-width: thin;
}

.schedule-kanban-column {
    width: 220px;
    flex: 0 0 220px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.schedule-kanban-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 2px;
    color: var(--text-muted);
    font-size: 12.5px;
    font-weight: 600;
}

.schedule-kanban-count { color: var(--text-faint); font-size: 11px; }

.schedule-kanban-list {
    min-height: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 7px;
    overflow-y: auto;
}

.schedule-kanban-card {
    padding: 9px 11px;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface-2);
}
.schedule-kanban-card.is-critical { border-color: color-mix(in srgb, var(--bar-critical) 42%, transparent); }

.schedule-kanban-card-name { color: var(--text-primary); font-size: 12.5px; line-height: 1.4; }
.schedule-kanban-card-meta { margin-top: 4px; color: var(--text-faint); font-size: 11px; }

.schedule-kanban-empty { margin: 0; padding: 4px 2px; color: var(--text-faint); font-size: 12px; }

/* 抽屉打开时对话列收窄，而不是被盖住——但并排模式下要守住 480px 的可读下限，
   挤不下的部分由抽屉（flex: 0 1 auto + min-width 340）让出。1101 与抽屉转全屏浮层的
   1100 断点成对：浮层模式抽屉脱离文档流，对话列不再受挤，地板也就不需要了。 */
body.artifact-open .main { min-width: 0; }
@media (min-width: 1101px) {
    body.artifact-open .main { min-width: 480px; }
}

/* ─── 甘特图 ─── */

.gantt {
    min-height: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
    position: relative;
    /* Bar identity colours (--bar-*) live in the theme token blocks at the top of this file,
       so each theme can override and re-validate them; this block only consumes them via var(). */
}

.gantt-empty {
    margin: 24px 18px;
    color: var(--text-faint);
    font-size: 13px;
}

.gantt-toolbar {
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    padding: 10px 16px;
    border-bottom: 1px solid var(--border);
}

.gantt-title {
    min-width: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.gantt-title strong {
    overflow: hidden;
    color: var(--text-primary);
    font-size: 13px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.gantt-title-meta {
    color: var(--text-faint);
    font-size: 11px;
}

.gantt-legend {
    display: flex;
    flex: 0 0 auto;
    gap: 12px;
}

.gantt-legend-item {
    display: flex;
    align-items: center;
    gap: 5px;
    color: var(--text-muted);
    font-size: 11.5px;
}

.gantt-legend-swatch {
    width: 10px;
    height: 10px;
    border-radius: 3px;
}

.gantt-legend-swatch.is-normal { background: var(--bar-normal); }
.gantt-legend-swatch.is-critical { background: var(--bar-critical); }
.gantt-legend-swatch.is-summary { background: var(--bar-summary); }

.gantt-controls {
    display: flex;
    flex: 0 0 auto;
    gap: 6px;
}

.gantt-zoom {
    display: flex;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--border);
    border-radius: 8px;
}

.gantt-btn {
    min-height: 26px;
    padding: 0 9px;
    border: 1px solid transparent;
    border-radius: 6px;
    background: transparent;
    color: var(--text-muted);
    font-size: 11.5px;
    transition: color 150ms ease, background 150ms ease, border-color 150ms ease;
}

.gantt-btn:hover { background: var(--surface-2-hover); color: var(--text-primary); }

.gantt-btn.is-active {
    background: var(--accent-dim);
    color: var(--accent);
}

.gantt-controls > .gantt-btn { border-color: var(--border); }

.gantt-viewport {
    min-height: 0;
    flex: 1;
    overflow: auto;
    scrollbar-color: var(--surface-2-hover) transparent;
    scrollbar-width: thin;
}

.gantt-canvas { position: relative; }

.gantt-axis {
    height: 44px;
    display: flex;
    position: sticky;
    z-index: 3;
    top: 0;
    border-bottom: 1px solid var(--border);
    background: var(--surface-1);
    font-variant-numeric: tabular-nums;
}

.gantt-axis-corner {
    width: 300px;
    display: flex;
    flex: 0 0 auto;
    position: sticky;
    z-index: 4;
    left: 0;
    align-items: center;
    padding-left: 14px;
    border-right: 1px solid var(--border);
    background: var(--surface-1);
    color: var(--text-faint);
    font-size: 11px;
}

.gantt-axis-track { position: relative; }

.gantt-tick {
    position: absolute;
    top: 14px;
    padding-left: 5px;
    border-left: 1px solid var(--border);
    color: var(--text-faint);
    font-size: 10.5px;
    font-family: var(--font-mono);
    line-height: 1;
    white-space: nowrap;
}

.gantt-rows { position: absolute; inset: 0; }

.gantt-arrows {
    position: absolute;
    z-index: 1;
    top: 0;
    left: 0;
    pointer-events: none;
}

/* 依赖线保持退让，别盖过条本身 */
.gantt-arrow {
    fill: none;
    stroke: var(--border-strong);
    stroke-width: 1;
}

.gantt-arrow-head { fill: var(--border-strong); }

.gantt-row {
    height: 28px;
    display: flex;
    position: absolute;
    z-index: 2;
    left: 0;
    align-items: center;
}

.gantt-row:hover { background: var(--tint-2); }

.gantt-row.is-summary-row .gantt-name-text {
    color: var(--text-primary);
    font-weight: 600;
}

.gantt-name {
    width: 300px;
    height: 100%;
    display: flex;
    flex: 0 0 auto;
    position: sticky;
    z-index: 2;
    left: 0;
    align-items: center;
    gap: 6px;
    padding-right: 10px;
    border-right: 1px solid var(--border);
    background: var(--surface-1);
}

.gantt-row:hover .gantt-name { background: var(--surface-2); }

.gantt-toggle {
    width: 16px;
    height: 16px;
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: var(--text-faint);
    font-size: 10px;
    line-height: 1;
}

.gantt-toggle:hover { background: var(--surface-2-hover); color: var(--text-primary); }

.gantt-name-text {
    overflow: hidden;
    flex: 1;
    color: var(--text-muted);
    font-size: 12px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 关键任务的第二重编码：不只靠颜色 */
.gantt-flag {
    flex: 0 0 auto;
    padding: 1px 5px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--bar-critical) 16%, transparent);
    color: var(--bar-critical);
    font-size: 10px;
}

.gantt-track {
    height: 100%;
    flex: 0 0 auto;
    position: relative;
}

.gantt-bar {
    height: 14px;
    position: absolute;
    top: 7px;
    /* 4px 圆角数据端 + 2px 表面间隙，见 dataviz mark specs */
    border-radius: 4px;
    background: var(--bar-normal);
    box-shadow: 0 0 0 2px var(--surface-1);
    overflow: hidden;
    cursor: pointer;
}

.gantt-bar:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.gantt-bar.is-critical { background: var(--bar-critical); }

.gantt-bar.is-summary {
    height: 8px;
    top: 10px;
    border-radius: 2px;
    background: var(--bar-summary);
    opacity: .85;
}

.gantt-bar-progress {
    height: 100%;
    background: var(--bar-progress);
}

.gantt-tooltip {
    max-width: 320px;
    position: absolute;
    z-index: 10;
    padding: 9px 11px;
    border: 1px solid var(--border-strong);
    border-radius: 9px;
    background: var(--surface-2);
    box-shadow: var(--shadow-tooltip);
    pointer-events: none;
}

.gantt-tooltip[hidden] { display: none; }

.gantt-tooltip-name {
    margin: 0 0 5px;
    color: var(--text-primary);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.4;
}

.gantt-tooltip-row {
    margin: 2px 0 0;
    color: var(--text-muted);
    font-size: 11.5px;
    line-height: 1.5;
}

.gantt-tooltip-row.is-critical { color: var(--bar-critical); }

@media (max-width: 1100px) {
    .artifact-drawer {
        width: 100%;
        position: fixed;
        z-index: 90;
        inset: 0;
        border-left: 0;
    }
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        scroll-behavior: auto !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* 服务商表单里的能力勾选 */
/*
 * 服务商表单的"高级选项"折叠区。装的是能力声明（reasoning_effort / tools）——
 * 正确值几乎总是默认值，只有接到不认这些字段的服务商时才需要来关，
 * 所以默认收起，不占日常视线，但保留入口（这两项无法自动探测，见 ProviderServiceImpl）。
 */
.settings-advanced { margin-top: 4px; }

.settings-advanced-summary {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 0;
    color: var(--text-muted);
    font-size: 12.5px;
    cursor: pointer;
    list-style: none;             /* 去掉默认三角，用自带的 chevron */
    transition: color var(--dur-1) var(--ease-soft);
}
.settings-advanced-summary::-webkit-details-marker { display: none; }
.settings-advanced-summary:hover { color: var(--text-primary); }

.settings-advanced-chevron {
    width: 13px;
    height: 13px;
    flex: 0 0 auto;
    color: var(--text-faint);
    stroke-width: 1.6;
    transition: transform var(--dur-1) var(--ease-soft);
}
.settings-advanced[open] .settings-advanced-chevron { transform: rotate(90deg); }

.settings-advanced-body {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px 0 2px 19px;      /* 19 = chevron 13 + gap 6，内容对齐"高级选项"四个字 */
}
/* 折叠区里两组"开关 + 说明"之间要留白，不然两段提示文字会黏在一起 */
.settings-advanced-body .settings-field-hint + .settings-check { margin-top: 10px; }

.settings-check {
    display: flex;
    align-items: center;
    gap: 9px;
    color: var(--text-secondary);
    font-size: 13px;
    cursor: pointer;
}

.settings-check code {
    padding: 1px 5px;
    border-radius: 5px;
    background: var(--surface-2);
    color: var(--text-code);
    font-family: var(--font-mono);
    font-size: 11.5px;
}

.settings-checkbox {
    width: 15px;
    height: 15px;
    flex: 0 0 auto;
    accent-color: var(--accent);
}

/* 整窗投放区。命中面积就是整个窗口，不用瞄准输入框；
   松手后能渲染的文件直接在右侧面板打开，那本来就是它们的归宿。 */
.drop-overlay {
    position: fixed;
    inset: 0;
    /* 要压过成果抽屉(90)和物料浮层：抽屉开着时拖文件进来，提示被盖住，
       用户以为拖拽没反应。对话框场景本来就挂起拖拽（dropSuspended），不用压过它们 */
    z-index: 140;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 32px;
    background: var(--scrim);
    /* 只做视觉遮罩：指针事件全部放行，否则 dragleave 会在进入遮罩时乱跳 */
    pointer-events: none;
}

.drop-overlay[hidden] { display: none; }

.drop-overlay-card {
    width: min(520px, 100%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 40px 32px;
    border: 2px dashed var(--accent-tint-border);
    border-radius: var(--radius-lg);
    background: var(--surface-1);
    box-shadow: var(--shadow-modal);
    text-align: center;
}

.drop-overlay-card svg {
    width: 34px;
    height: 34px;
    margin-bottom: 6px;
    fill: none;
    stroke: var(--accent);
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.drop-overlay-title {
    margin: 0;
    color: var(--text-heading);
    font-size: 16px;
    font-weight: 600;
}

.drop-overlay-hint {
    margin: 0;
    color: var(--text-muted);
    font-size: 12.5px;
}

/* 零项目态 / 加载失败态：都不是盖住整页的遮罩——侧栏（新建项目/收放/设置）全程可
   操作，主区域只在原来对话+输入框的位置换成一段居中引导。真正的行动指引落在侧栏
   "新建项目"按钮的呼吸高亮上（见下方 is-pulsing），这里不重复放一个新建按钮。
   两个 <section> 共用这一块布局和背景，同一时刻靠各自的 [hidden] 至多显示一个。
   背景的极淡网格用 --border 画：这个 token 本就是四套主题各自调过色相的"结构线"
   颜色（白基调深色 / 墨基调浅色 / 蓝调靛蓝 / 棕调暖砂），直接拿来当网格线不需要
   再单独适配每套主题。 */
.project-empty-state[hidden] { display: none; }

.project-empty-state {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 32px;
    background-image:
        linear-gradient(var(--border) 1px, transparent 1px),
        linear-gradient(90deg, var(--border) 1px, transparent 1px);
    background-size: 40px 40px;
    background-position: center;
}

.project-empty-content {
    display: flex;
    max-width: 420px;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    text-align: center;
}

.project-empty-icon {
    width: 52px;
    height: 52px;
    margin-bottom: 4px;
    color: var(--text-faint);
    stroke-width: 1.3;
}

.project-load-error .project-empty-icon { color: var(--danger); }

.project-empty-title {
    margin: 0;
    color: var(--text-heading);
    font-size: 19px;
    font-weight: 600;
}

.project-empty-desc {
    margin: 0;
    color: var(--text-muted);
    font-size: 14px;
    line-height: 1.6;
}

.project-empty-flow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 6px 10px;
    margin: 8px 0 0;
    font-size: 13.5px;
}

/* 每个 step 连同它后面的箭头是同一个 flex item、且不换行——避免窄屏换行时
   箭头被单独甩到下一行行首。 */
.project-empty-flow-step { white-space: nowrap; color: var(--text-secondary); font-weight: 600; }
.project-empty-flow-arrow { margin-left: 2px; color: var(--text-faint); font-weight: 400; }

.project-load-retry-button {
    margin-top: 6px;
    padding: 8px 22px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    background: var(--surface-1);
    color: var(--text-primary);
    font-size: 13.5px;
    font-weight: 500;
    transition: background var(--dur-1) var(--ease-soft), border-color var(--dur-1) var(--ease-soft);
}
.project-load-retry-button:hover { background: var(--surface-2); border-color: var(--accent-tint-border); }
.project-load-retry-button:disabled { opacity: .6; }

@media (max-width: 720px) {
    .project-empty-state { background-size: 26px 26px; padding: 24px; }
    .project-empty-icon { width: 44px; height: 44px; }
    .project-empty-title { font-size: 17px; }
    .project-empty-desc { font-size: 13.5px; }
}

/* 呼吸高亮：在静态强调色（随时可见，reduced-motion 下也不消失）之上叠加一圈
   缓慢明暗的光环。光环两端都不落到 0——避免读成"闪烁"，而是明暗起伏的"呼吸"。
   全局 prefers-reduced-motion 覆写（第 3450 行）已经把 animation-duration 收到
   0.01ms，动画近似定格在光环中间态，静态强调色本身则不受影响，高亮不会消失。 */
@keyframes cta-breathe {
    0%, 100% { box-shadow: 0 0 0 3px var(--accent-tint); }
    50% { box-shadow: 0 0 0 7px var(--accent-tint); }
}

.project-create-button.is-pulsing,
.mobile-drawer-button.is-pulsing {
    border-color: var(--accent-tint-border);
    background: var(--accent-tint);
    color: var(--accent);
    animation: cta-breathe 2.8s ease-in-out infinite;
}

/* 右侧资料区。透明背景、不占布局宽度——用 position: fixed 浮在对话区右侧的留白里，
   宽度按当前的侧栏宽度 + 对话列宽度反推剩余空间，保证不会压到对话内容。
   图片/任务/文件/问题/合同五栏共用这一块面板；哪一栏没数据由 JS 直接把整个
   .materials-section（连标题）设 hidden，不靠 CSS :empty——标题本身让容器永远非空，
   :empty 选不中。抽屉打开时（同样占右侧空间）直接隐藏，两个右侧浮层不摞在一起。 */
.materials-rail {
    position: fixed;
    top: 66px;
    bottom: 16px;
    right: 14px;
    z-index: 8;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    pointer-events: none; /* 容器本身不挡事件，子元素各自开启 */
}
.materials-rail[hidden] { display: none; }
body.artifact-open .materials-rail { display: none; }

.materials-rail-puck {
    position: relative;
    width: 34px;
    height: 34px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface-1);
    color: var(--text-faint);
    box-shadow: var(--shadow-modal);
    pointer-events: auto;
    transition: color var(--dur-1) var(--ease-soft), border-color var(--dur-1) var(--ease-soft);
}
.materials-rail-puck svg { width: 16px; height: 16px; stroke-width: 1.6; }
.materials-rail-puck:hover { color: var(--accent); border-color: var(--accent-tint-border); }

/* 默认（>=720px）不是抽屉，遮罩不该存在——只在下面 <720px 断点里才真的渲染成一层挡板。
   跟 #drawer-scrim 同一个手法：靠 :not([hidden]) 的更高特异性覆盖这条默认 display:none。 */
.materials-rail-scrim { display: none; }

/* 把手 + 面板的外壳。收起就是这一层整体右移，面板滑出屏幕、把手停在屏幕右缘——
   两者必须在同一层里平移，否则面板走了把手还钉在原地。transform 不引起重排，
   长对话下也不会掉帧（对话列那侧的留白是另一条动画，见 .thread-wrap）。
   <1600 时它 display: contents（等于这一层不存在），浮层那套布局原样不动。 */
.materials-rail-dock {
    display: contents;
}

/* 收放把手：竖向胶囊，钉在面板左边界的中点。≥1600 才出现——窄屏那档本来就是浮层，
   没有"收起"这回事（见 <1600 断点里的 display: none）。

   视觉上**刻意没有边框、没有底色、没有阴影**：这条栏里所有东西（分栏标题、文件行、
   展开箭头）都是"平时透明、hover 才浮出一层 surface-2"，把手是栏的一部分，得说同一种话。
   第一版照着 <1600 那颗圆钮抄了边框 + surface-1 + shadow-modal（**弹窗级**阴影），在栏里
   直接变成一块贴上去的卡片，用户一眼就说突兀——那颗圆钮孤零零浮在页面上，卡片处理是对的，
   搬进栏里就错了级。薄荷只留给键盘聚焦（全局 button:focus-visible 那条描边），
   hover 不染色：薄荷在这套设计里只表示状态。 */
.materials-rail-edge {
    display: none;
    position: relative; /* 热区伪元素（::after）的定位基准 */
    flex: 0 0 auto;
    width: 14px;
    height: 44px;
    place-items: center;
    border: 0;
    border-radius: 4px;   /* 2026-09-17 用户不要"椭圆形的边框"：圆角改小，悬停底和键盘焦点描边都不再是胶囊 */
    background: transparent;
    color: var(--text-faint);
    pointer-events: auto;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
/* 视觉 14×44，热区 24×64：边缘上的窄控件本来就难点中，但把盒子做大会在展开态挤到文件名，
   所以用一层不可见的伪元素往外扩，视觉尺寸一个像素不变。 */
.materials-rail-edge::after {
    content: '';
    position: absolute;
    inset: -10px -5px;
}
/* 展开态悬停只亮箭头、不画底：用户 2026-09-17 点名不要那个椭圆；收起态的常驻底色在下面的媒体查询里，那时它是唯一入口 */
.materials-rail-edge:hover { background: transparent; color: var(--text-primary); }
/* 键盘焦点也不描圈：把手只有 14 宽，全局那圈 2px 描边套上去就是用户说的"椭圆形的边框"（点过之后焦点留在它身上）。
   焦点改用箭头变薄荷表示——薄荷在这套设计里就是状态色 */
.materials-rail-edge:focus-visible { outline: none; color: var(--accent); }
.materials-rail-edge svg {
    width: 11px;
    height: 11px;
    stroke-width: 2;
    transition: transform var(--dur-3) var(--ease);
}
/* 箭头朝右＝把面板推出去；收起后转 180° 朝左＝拉回来。方向跟着动作走，不是装饰。 */
:root.materials-collapsed .materials-rail-edge svg { transform: rotate(180deg); }

.materials-rail-panel {
    min-height: 0;
    /* 目标 280–320px，向左扩展进当前基本空着的侧边距；max-width 是硬约束——
       视口不够宽时自动收窄，永远不会挤到中间 --column-thread 对话正文（rail 本身
       position: fixed，脱离文档流，压不了正文的布局宽度，但视觉上不能盖上去）。 */
    width: 300px;
    /* 下限 220px：1600–1930px 视口下这个公式会算出 ~130-200px，比窄屏的浮层还窄、
       文件名一个都放不下。1600px 时右侧边距约 230px，220 放得下且不压正文 */
    max-width: max(220px, calc((100vw - var(--drawer-width) - var(--column-thread)) / 2 - 20px));
    display: flex;
    flex-direction: column;
    gap: 14px;
    overflow-y: auto;
    padding: 2px;
    pointer-events: auto;
}

/* 左右侧栏共用滚动条外观，不改变各自的尺寸与溢出行为。 */
.drawer-history,
.materials-rail-panel {
    --sidebar-scroll-thumb: color-mix(in srgb, var(--text-muted) 42%, transparent);
    scrollbar-width: thin;
    scrollbar-color: var(--sidebar-scroll-thumb) transparent;
}

.drawer-history:hover,
.drawer-history:focus-within,
.materials-rail-panel:hover,
.materials-rail-panel:focus-within {
    --sidebar-scroll-thumb: color-mix(in srgb, var(--text-muted) 65%, transparent);
}

/* 保留原生拖动，10px 操作区域内只画 6px 滑块；透明留边不遮住侧栏背景。 */
@supports selector(::-webkit-scrollbar) {
    .drawer-history,
    .materials-rail-panel {
        /* Chromium 的标准属性会覆盖伪元素样式，恢复 auto 才能去掉箭头。 */
        scrollbar-width: auto;
        scrollbar-color: auto;
    }
    .drawer-history::-webkit-scrollbar,
    .materials-rail-panel::-webkit-scrollbar {
        width: 10px;
        height: 10px;
        background: transparent;
    }
    .drawer-history::-webkit-scrollbar-track,
    .drawer-history::-webkit-scrollbar-corner,
    .materials-rail-panel::-webkit-scrollbar-track,
    .materials-rail-panel::-webkit-scrollbar-corner {
        background: transparent;
    }
    .drawer-history::-webkit-scrollbar-thumb,
    .materials-rail-panel::-webkit-scrollbar-thumb {
        min-height: 36px;
        border: 2px solid transparent;
        border-radius: 999px;
        background: var(--sidebar-scroll-thumb);
        background-clip: padding-box;
    }
    .drawer-history::-webkit-scrollbar-thumb:hover,
    .materials-rail-panel::-webkit-scrollbar-thumb:hover {
        background-color: color-mix(in srgb, var(--text-muted) 80%, transparent);
    }
    .drawer-history::-webkit-scrollbar-thumb:active,
    .materials-rail-panel::-webkit-scrollbar-thumb:active {
        background-color: var(--text-muted);
    }
    .drawer-history::-webkit-scrollbar-button,
    .materials-rail-panel::-webkit-scrollbar-button {
        display: none;
        width: 0;
        height: 0;
    }
}

/* 移到顶部 .topbar-meta 里，跟"会话 N 条"同一行——时间在前（靠左），会话数在后
   （靠右），用 .topbar-meta 现成的 flex + gap 排布，不需要额外定位。 */
.materials-current-time {
    color: var(--text-faint);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* 面板头：整块面板的名字 + 收起键。六栏各有标题，但没有一处说"这一整块是什么"。
   收起键钉在右上角，收起后把手出现在几乎同一个位置——控件不跳。
   标题比栏标题（10.5px/faint）高半级（11.5px/muted），两级层次才分得开。 */
.materials-rail-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    padding: 0 2px 0 7px;
}
.materials-rail-heading {
    margin-right: auto; /* 右边的图标（搜索/上传/收起）靠右站成一排 */
    color: var(--text-muted);
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: .04em;
}
.materials-rail-collapse {
    width: 26px;
    height: 26px;
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-faint);
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.materials-rail-collapse:hover { background: var(--surface-2); color: var(--text-primary); }
.materials-rail-collapse svg { width: 14px; height: 14px; stroke-width: 1.8; }

/* 搜索框（2026-09-08）：栏里唯一有底色和边框的静态控件——它是输入框，得看得出能打字；
   但只比行高高 2px（28px）、圆角跟行一致（7px），别做成设置面板里那种 42px 的大输入框。
   聚焦描边走全局的 --focus-border/--focus-ring（薄荷只给状态）。 */
/* 面板头的图标键（2026-09-17：搜索、上传都是 svg 小图标，用户要"页面简洁一点"）：26×26 无边框无底，
   跟收起键同一套；悬停/展开态才有底。用 hidden 开关又写了 display，[hidden] 覆盖不能少（2026-09-14 那条规矩） */
.materials-rail-icon-button {
    width: 26px;
    height: 26px;
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-faint);
    cursor: pointer;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.materials-rail-icon-button:hover,
.materials-rail-icon-button[aria-expanded="true"] { background: var(--surface-2); color: var(--text-primary); }
.materials-rail-icon-button:disabled { opacity: .5; cursor: default; }
.materials-rail-icon-button[hidden] { display: none; }
.materials-rail-icon-button svg { width: 14px; height: 14px; stroke-width: 1.8; }
.materials-upload-status {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    flex: 0 0 auto;
    margin: -6px 2px 0;
    padding: 6px 8px 6px 10px;
    border-radius: 7px;
    background: var(--surface-2);
    color: var(--text-secondary);
    font-size: 11.5px;
    line-height: 1.5;
}
.materials-upload-status[hidden] { display: none; }
.materials-upload-status.is-error { color: var(--danger); }
.materials-upload-status-text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
/* 上传进度：独立的一条 3px 槽，薄荷（状态色）从左往右填，贴在搜索框下面；用 hidden 开关又写了 display，[hidden] 覆盖不能少 */
.materials-upload-progress { display: block; flex: 0 0 auto; height: 3px; margin: -6px 4px 0; border-radius: 2px; background: var(--border); overflow: hidden; }
.materials-upload-progress[hidden] { display: none; }
.materials-upload-progress-bar { height: 100%; width: 0; border-radius: 2px; background: var(--accent); transition: width var(--dur-2) var(--ease-soft); }
.materials-upload-status-close {
    flex: 0 0 auto;
    border: 0;
    padding: 0 2px;
    background: transparent;
    color: var(--text-faint);
    font: inherit;
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
}
.materials-upload-status-close:hover { color: var(--text-primary); }
/* 拖到资料栏上方：整屏提示换一句话，这里不另画框——提示层压在最上面，框画了也看不见 */
.drop-overlay.is-over-rail .drop-overlay-card { border-color: var(--accent); }

.materials-rail-search {
    position: relative;
    display: flex;
    align-items: center;
    flex: 0 0 auto;
    margin: -6px 2px 0; /* 贴着面板头，跟第一栏之间保留面板的 14px 间距 */
    border: 1px solid var(--border);
    border-radius: 7px;
    background: var(--surface-2);
    transition: border-color var(--dur-1) var(--ease-soft), box-shadow var(--dur-1) var(--ease-soft);
}
.materials-rail-search[hidden] { display: none; }
.materials-rail-search:focus-within {
    border-color: var(--focus-border);
    box-shadow: 0 0 0 1px var(--focus-ring);
}
.materials-rail-search-icon {
    position: absolute;
    left: 8px;
    width: 12px;
    height: 12px;
    color: var(--text-faint);
    stroke-width: 2;
    pointer-events: none;
}
.materials-rail-search-input {
    flex: 1 1 auto;
    min-width: 0;
    height: 28px;
    padding: 0 26px 0 26px;
    border: 0;
    background: transparent;
    color: var(--text-primary);
    font: inherit;
    font-size: 12px;
    outline: none;
}
.materials-rail-search-input::placeholder { color: var(--text-faint); }
/* 原生的清空叉不跟主题、各浏览器长相也不一样——藏掉，右侧那颗是自己画的 */
.materials-rail-search-input::-webkit-search-cancel-button,
.materials-rail-search-input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
.materials-rail-search-clear {
    position: absolute;
    right: 4px;
    width: 20px;
    height: 20px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 5px;
    background: transparent;
    color: var(--text-faint);
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.materials-rail-search-clear[hidden] { display: none; }
.materials-rail-search-clear:hover { background: var(--surface-2-hover); color: var(--text-primary); }
.materials-rail-search-clear svg { width: 11px; height: 11px; stroke-width: 2; }

/* 一个都没搜到：四栏全藏了，这行是面板里唯一的内容，语气跟空态一致（faint、不加框）。 */
.materials-rail-search-empty {
    margin: -6px 0 0;
    padding: 4px 9px;
    color: var(--text-faint);
    font-size: 12px;
    line-height: 1.5;
    overflow-wrap: anywhere;
}
.materials-rail-search-empty[hidden] { display: none; }

/* 搜索命中的那几个字：只提亮不加底色——薄荷只表示状态，"这几个字对上了"不是状态。
   <mark> 自带一块浏览器默认的亮黄底（跟 <button> 默认那圈 outset 边框同一类坑：看 CSS 看不出来，
   截图才发现），必须显式抹掉。 */
.materials-rail-match {
    padding: 0;
    background: transparent;
    color: var(--text-primary);
    font-weight: 600;
}

.materials-section { display: flex; flex-direction: column; gap: 2px; }
.materials-section[hidden] { display: none; }

/* 栏头兼收起/展开开关——跟左侧项目手风琴的 .project-toggle 同一套视觉语言，
   只是没有图标，标题本身足够指代这一栏是什么。 */
.materials-section-toggle {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    padding: 3px 7px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    transition: background var(--dur-1) var(--ease-soft);
}
.materials-section-toggle:hover { background: var(--surface-2); }

.materials-section-title {
    margin: 0;
    color: var(--text-faint);
    font-size: 10.5px;
    font-weight: 600;
}

/* 栏头计数：平时是总数，筛选时写「命中 / 总数」。靠右贴着箭头，跟标题同级同色，
   等宽数字免得 8→12 时抖。收起的栏也留着它——收起后"里面有几份"是唯一还看得见的信息。 */
.materials-section-count {
    margin-left: auto;
    color: var(--text-faint);
    font-size: 10.5px;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.materials-section-count:empty { display: none; }

.materials-section-chevron {
    width: 11px;
    height: 11px;
    flex: 0 0 auto;
    color: var(--text-faint);
    stroke-width: 1.8;
    transition: transform var(--dur-1) var(--ease-soft);
}
/* 展开态箭头下压 90°，跟 .project-chevron 同一个信号，不需要额外图标 */
.materials-section-toggle[aria-expanded="true"] .materials-section-chevron { transform: rotate(90deg); }

/* 收起/展开过渡——跟 .project-section-body 完全同一套 grid-rows 0fr↔1fr 技巧，
   三层职责也一致（body 管高度动画、clip 只裁剪、inner 承载内容位移），
   全局 prefers-reduced-motion 覆写（transition-duration: 0.01ms !important）同样兜底，
   这里不重复写一遍原理，见 chat.css 对 .project-section-body 的详细注释。 */
.materials-section-body {
    display: grid;
    grid-template-rows: 0fr;
    opacity: 0;
    visibility: hidden;
    transition: grid-template-rows var(--dur-2) var(--ease-soft),
                opacity var(--dur-2) var(--ease-soft),
                visibility var(--dur-2);
}
.materials-section-body.is-open {
    grid-template-rows: 1fr;
    opacity: 1;
    visibility: visible;
}
.materials-section-body-clip { overflow: hidden; min-height: 0; }
.materials-section-body-inner {
    padding-top: 2px;
    transform: translateY(-4px);
    transition: transform var(--dur-2) var(--ease-soft);
}
.materials-section-body.is-open .materials-section-body-inner { transform: translateY(0); }

.materials-files-list { display: flex; flex-direction: column; gap: 1px; padding: 0 2px; }

/* 「还有 N 份」折叠（2026-09-08）：每栏默认只摊开最近 8 份，其余收在这个容器里，
   点最下面那行才放出来。收放动画跟栏本身同一套 grid-rows 0fr↔1fr（三层：外层管高度、
   clip 裁剪、inner 承载），所以展开是"从那一行往下长出来"，不是整栏闪一下重画。
   筛选时不折叠——搜到的必须全露出来，这个容器根本不会被画。 */
.materials-files-more {
    display: grid;
    grid-template-rows: 0fr;
    opacity: 0;
    visibility: hidden;
    transition: grid-template-rows var(--dur-3) var(--ease-soft),
                opacity var(--dur-2) var(--ease-soft),
                visibility var(--dur-3);
}
.materials-files-more.is-open {
    grid-template-rows: 1fr;
    opacity: 1;
    visibility: visible;
}
.materials-files-more-clip { overflow: hidden; min-height: 0; }
.materials-files-more-inner {
    display: flex;
    flex-direction: column;
    gap: 1px;
    transform: translateY(-4px);
    transition: transform var(--dur-3) var(--ease-soft);
}
.materials-files-more.is-open .materials-files-more-inner { transform: translateY(0); }

/* 那一行按钮说的是文件行的话（透明底、hover 才浮出 surface-2、12px），只是字更淡半级、
   左边一枚朝下的小箭头顶替圆点的位置，展开后箭头翻上去。 */
.materials-files-reveal {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 5px 7px;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--text-faint);
    font-size: 11.5px;
    text-align: left;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.materials-files-reveal:hover { background: var(--surface-2); color: var(--text-primary); }
.materials-files-reveal svg {
    width: 11px;
    height: 11px;
    flex: 0 0 auto;
    margin: 0 -2px; /* 视觉上跟 7px 圆点对中：图标 11px，两边各收 2px */
    stroke-width: 2;
    transition: transform var(--dur-2) var(--ease-soft);
}
.materials-files-reveal[aria-expanded="true"] svg { transform: rotate(180deg); }
.materials-files-reveal-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 网格两行：首行 名字(可缩出省略号)+⋯+删除，次行 格式徽标整行。
   徽标挤在同一行会把名字压到 0px（220px 窄栏实测）；改 flex-wrap 也不行——
   换行容器按【内容天然宽度】分行，名字一长打开按钮独占整行，两颗按钮被排去下一行，
   收缩是分完行才发生的（2026-08-25 二连实测）。网格的 minmax(0,1fr) 把名字列结构性
   锁在首行里收缩，按钮永远贴在名字右侧。 */
.materials-rail-item {
    position: relative;
    width: 100%;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 0 2px;
    border-radius: 7px;
}

.materials-rail-item:hover,
.materials-rail-item:focus-within,
.materials-rail-item.has-open-menu {
    background: color-mix(in srgb, var(--text-muted) 7%, transparent);
}
/* 就地使用主题强调色，四套主题和切换后的颜色都不依赖固定蓝色。 */
.materials-rail-item.is-selected {
    background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.materials-rail-item.is-selected:hover,
.materials-rail-item.is-selected:focus-within {
    background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.materials-rail-item.is-selected > .materials-rail-item-open {
    color: var(--text-primary);
}

/* 「递进」的位置滑动（2026-09-08，见 chat.js slideMaterialsRows）：挪了位的行带着 is-sliding
   从旧位置过渡到新位置；新出现的行从 is-entering（透明、略下沉）淡入。只在这两个类挂着的
   那 320ms 里有过渡，平时行上没有 transform 过渡，hover 不受影响。 */
.materials-rail-item.is-sliding { transition: transform var(--dur-3) var(--ease); }
.materials-rail-item.is-entering { opacity: 0; }
/* 平时只给 opacity 过渡，刻意不给 transform：FLIP 第一步是把行瞬间摆回旧位置，
   那一步要是也被过渡接住，行会先往回滑一段再滑回来。 */
.materials-rail-item:not(.is-entering) { transition: opacity var(--dur-2) var(--ease-soft); }

.materials-rail-item > .materials-rail-item-open { grid-row: 1; grid-column: 1; }
.materials-rail-item > .materials-rail-more-button { grid-row: 1; grid-column: 2; }
.materials-rail-item > .materials-delete-button { grid-row: 1; grid-column: 3; }

.materials-rail-item-open {
    min-width: 0;
    min-height: 30px;
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 5px 7px;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--text-faint);
    font-size: 12px;
    text-align: left;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.materials-rail-item-open:hover { background: transparent; color: var(--text-primary); }
.materials-rail-item-open:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.materials-rail-dot {
    width: 7px;
    height: 7px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--file-tile, var(--text-faint));
}
.materials-rail-item.is-pdf .materials-rail-dot { --file-tile: var(--file-pdf); }
.materials-rail-item.is-docx .materials-rail-dot { --file-tile: var(--file-doc); }
.materials-rail-item.is-mpp .materials-rail-dot { --file-tile: var(--file-mpp); }
.materials-rail-item.is-mspdi .materials-rail-dot { --file-tile: var(--file-mpp); }
.materials-rail-item.is-xlsx .materials-rail-dot { --file-tile: var(--file-xlsx); }
.materials-rail-item.is-md .materials-rail-dot { --file-tile: var(--file-doc); }

.materials-rail-name {
    min-width: 0;
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 独占第二行、与名字左对齐（7px 内边距 + 7px 圆点 + 7px 间距 = 21px）。
   DOM 顺序不动——读屏和键盘序保持原样，动的只是视觉摆位。 */
.materials-rail-formats {
    grid-row: 2;
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    margin: 0 0 5px 21px;
}

.materials-rail-format-badge {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 28px;
    min-height: 22px;
    padding: 3px 6px;
    border: 0;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text-faint);
    font-size: 10px;
    line-height: 14px;
    white-space: nowrap;
    cursor: pointer;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.materials-rail-format-badge:hover { background: var(--surface-3); color: var(--text-primary); }

/* STALE：内容改过、这份格式副本还没跟上——弱化显示 + 小圆点提示，但仍可点开查看旧内容。 */
.materials-rail-format-badge.is-stale {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    color: var(--text-faint);
    opacity: 0.6;
}
.materials-rail-format-badge.is-stale::after {
    content: "";
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--accent);
}

/* M6：AI 生成、尚未人工确认的草稿——跟普通文件混在一起容易被当成正式资料。 */
.materials-rail-draft-tag {
    flex: 0 0 auto;
    padding: 1px 6px;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--accent);
    font-size: 10px;
    white-space: nowrap;
}

/* 「工序」标签（2026-09-15）：这份文件是项目的工序依据。中性色——它是身份不是状态，薄荷只表示状态 */
.materials-rail-procedure-tag {
    flex: 0 0 auto;
    padding: 1px 6px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: 10px;
    white-space: nowrap;
}

/* 图片栏：小方缩略图网格，跟消息气泡里的附件缩略图（.attach-image）同一视觉语言。
   固定列宽（64px）+ auto-fill：一行放几张完全由栏的实际宽度决定——宽屏 rail（约
   280–320px 内容宽）落在 3～4 张一行，窄屏收起来的浮层/抽屉同样自适应，不用为
   不同断点各调一遍。超过两行（64*2 + 一条 8px 行距 = 136px）就不再继续撑高整个
   资料区，而是这一栏自己纵向滚动——图片可能有几十张，资料区不能被它一个栏喂到
   看不见后面的任务/文件/问题/合同。 */
.materials-images-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, 64px);
    gap: 8px;
    padding: 0 7px;
    max-height: 136px;
    overflow-y: auto;
}

.materials-image-item { position: relative; }

.materials-image-thumb {
    width: 64px;
    height: 64px;
    display: block;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--surface-2);
    transition: border-color var(--dur-1) var(--ease-soft);
}
.materials-image-thumb:hover { border-color: var(--accent-tint-border); }
.materials-image-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* 图片超过 6 张时的分组列表——按日期收放，跟任务栏/文件栏同一个紧凑克制的视觉语言：
   行本身直接复用 .materials-rail-item 一套类（见下方 JS），这里只补组标题和缩放箭头。 */
.materials-image-groups { display: flex; flex-direction: column; gap: 2px; padding: 0 2px; }
.materials-image-groups[hidden] { display: none; }

.materials-image-group { display: flex; flex-direction: column; }

.materials-image-group-header,
.materials-folder-header {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 5px 7px;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--text-faint);
    font-size: 11px;
    font-weight: 600;
    text-align: left;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.materials-image-group-header:hover,
.materials-folder-header:hover { background: var(--surface-2); color: var(--text-primary); }

.materials-image-group-chevron,
.materials-folder-chevron {
    width: 12px;
    height: 12px;
    flex: 0 0 auto;
    stroke-width: 2;
    transition: transform var(--dur-1) var(--ease-soft);
}
.materials-image-group-header[aria-expanded="true"] .materials-image-group-chevron,
.materials-folder-header[aria-expanded="true"] .materials-folder-chevron { transform: rotate(90deg); }

.materials-image-group-label,
.materials-folder-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 资料栏文件夹（2026-09-17）：头跟图片栏的日期分组同一副长相（上面并进了同一条规则），份数在右、等宽数字；
   体里的行缩进一档，让"这些在文件夹里"看得出来；用 hidden 开关又写了 display，[hidden] 覆盖不能少 */
.materials-folder { display: flex; flex-direction: column; }
.materials-folder-count { flex: 0 0 auto; color: var(--text-faint); font-weight: 500; font-variant-numeric: tabular-nums; }
.materials-folder-body { display: flex; flex-direction: column; padding-left: 12px; }
.materials-folder-body[hidden] { display: none; }
.materials-upload-menu { min-width: 132px; }

/* 宽屏（持久 rail，>=1600px）双列；标题始终横跨整栏（.materials-image-group-header
   width:100%，不受这个 grid 影响，它在 body 外面）。约 10 张（双列即 5 行）之后固定
   高度、组内自己纵向滚动——跟前一版"图片栏不能无限撑高资料区"是同一个原则，只是
   这次收口到单个日期组而不是整个图片栏，2px/10px 是估的一行高度和列间距，不追求
   像素级精确。中屏浮层（<1600px）和移动端抽屉（<720px）退回单列，见下方各自的
   @media 覆写。 */
.materials-image-group-body {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 2px 10px;
    padding-left: 10px;
    max-height: 160px;
    overflow-y: auto;
}
.materials-image-group-body[hidden] { display: none; }

/* 删除按钮：文件/任务栏跟在行尾，图片栏叠在缩略图右上角。平时透明，hover 整行才提亮——
   五栏同时常驻一排小叉号会显得杂乱，跟这套设计一贯"操作按钮只在需要时才现身"的原则一致。 */
.materials-delete-button {
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text-faint);
    opacity: 0;
    transition: opacity var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft), background var(--dur-1) var(--ease-soft);
}
.materials-delete-button svg { width: 13px; height: 13px; stroke-width: 1.6; }
.materials-rail-item:hover .materials-delete-button,
.materials-rail-item:focus-within .materials-delete-button,
.materials-image-item:hover .materials-delete-button,
.materials-delete-button:focus-visible {
    opacity: 1;
}
.materials-delete-button:hover { background: var(--danger-dim); color: var(--danger); }

/* "⋯" 溢出菜单——只有 AI 生成的文件才有，跟删除按钮同一个平时透明/hover 才现身的规则。
   物料栏这行文字本来就紧，转换/确认/拒绝三个动作放不下，收进一个菜单比三个常驻图标更克制。 */
.materials-rail-more-button {
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text-faint);
    opacity: 0;
    transition: opacity var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft), background var(--dur-1) var(--ease-soft);
}
.materials-rail-more-button svg { width: 14px; height: 14px; stroke-width: 1.8; }
.materials-rail-item:hover .materials-rail-more-button,
.materials-rail-item:focus-within .materials-rail-more-button,
.materials-rail-more-button:focus-visible,
.materials-rail-item.has-open-menu .materials-rail-more-button {
    opacity: 1;
}
.materials-rail-more-button:hover { background: var(--surface-2-hover); color: var(--text-primary); }

.materials-rail-item > .materials-rail-more-button,
.materials-rail-item > .materials-delete-button {
    width: 28px;
    height: 28px;
}
@media (hover: none) {
    .materials-rail-item > .materials-rail-more-button,
    .materials-rail-item > .materials-delete-button { opacity: 1; }
}

/* position: fixed + 挂到 document.body（见 chat.js materialsMoreButton）而不是留在
   .materials-rail-item 里绝对定位：物料栏两层祖先容器都要靠 overflow 才能工作——
   .materials-rail-panel 是滚动区，.materials-section-body-clip 是收放动画的裁剪层——
   菜单留在里面必然被裁掉一部分选项。top/left 由 JS 按钮位置算出后内联设置，这里不写死。 */
.materials-rail-menu {
    position: fixed;
    z-index: 120;
    min-width: 152px;
    padding: 5px;
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    background: var(--surface-1);
    box-shadow: var(--shadow-pop);
    animation: dialog-in 160ms cubic-bezier(.16, 1, .3, 1) both;
}
.materials-rail-menu[hidden] { display: none; }

.materials-rail-menu-label {
    padding: 4px 8px 2px;
    color: var(--text-faint);
    font-size: 10px;
}

.materials-rail-menu-item {
    width: 100%;
    display: block;
    padding: 6px 8px;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--text-secondary);
    font-size: 12px;
    text-align: left;
}
.materials-rail-menu-item:hover { background: var(--surface-2); color: var(--text-primary); }
.materials-rail-menu-item.is-danger:hover { background: var(--danger-dim); color: var(--danger); }

/* 一格式一行：左边格式名，右边动作词。格式名可以被压缩（长名字省略），
   动作词固定宽度不参与收缩——被挤掉的永远不能是能点的东西。 */
.materials-rail-menu-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 4px 8px;
    border-radius: 7px;
}

.materials-rail-menu-format {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-secondary);
    font-size: 12px;
}

.materials-rail-menu-actions {
    flex: 0 0 auto;
    display: flex;
    gap: 2px;
    margin-left: auto;
}

.materials-rail-menu-action {
    border: 0;   /* 不写就继承浏览器默认 2px outset，看 CSS 看不出来 */
    padding: 2px 6px;
    border-radius: 6px;
    background: transparent;
    color: var(--text-faint);
    font: inherit;
    font-size: 11.5px;
    cursor: pointer;
}

.materials-rail-menu-action:hover { background: var(--surface-2); color: var(--text-primary); }
/* 删除成果：危险动作用危险色，悬停只加底不改字色 */
.materials-rail-menu-action.is-danger { color: var(--danger); }
.materials-rail-menu-action.is-danger:hover { background: var(--danger-dim); color: var(--danger); }

.materials-rail-menu-divider { height: 1px; margin: 4px 2px; background: var(--border); }

.materials-image-item .materials-delete-button {
    position: absolute;
    top: -4px;
    right: -4px;
    background: var(--surface-1);
    border: 1px solid var(--border);
    box-shadow: var(--shadow-soft);
}
.materials-image-item .materials-delete-button:hover { background: var(--danger-dim); border-color: var(--danger-border); }

/* 运行中任务的克制动态状态：一个缓慢明暗的小圆点，两端都不落到 0（不读成"闪烁"）。
   跟侧栏"新建项目"呼吸高亮共用同一条全局 prefers-reduced-motion 覆写
   （animation-duration 收到 0.01ms），这里不用再单独判断一次。 */
@keyframes task-pulse {
    0%, 100% { opacity: .35; }
    50% { opacity: 1; }
}
/* <1600px：空间放不下一行文件名（1440px 上只剩 ~74px），收成一个边缘小图标，
   点开临时展开成同一块五栏面板，浮在内容上方。 */
@media (max-width: 1599px) {
    .materials-rail-panel {
        display: none;
        position: fixed;
        top: 66px;
        bottom: 190px; /* 清开悬浮的输入框：输入区最高长到 ~140px + 边距，120 会被长高的输入框盖住 */
        right: 14px;
        width: min(260px, calc(100vw - 32px));
        max-width: none;
        border: 1px solid var(--border);
        border-radius: var(--radius-md);
        background: var(--surface-1);
        box-shadow: var(--shadow-modal);
        padding: 8px;
    }
    .materials-rail.is-expanded .materials-rail-panel { display: flex; }

    /* 中屏浮层（含更窄的移动端抽屉，<720px 是它的子集，不用再重复一遍）退回单列——
       浮层本身只有 260px 出头，双列会把名称压得比宽屏 rail 还挤，违背"紧凑克制"。 */
    .materials-image-group-body { grid-template-columns: 1fr; }
}
@media (min-width: 1600px) {
    /* 常驻栏的收放。状态类挂在 <html> 而不是 body/rail 上：index.html 的内联脚本在样式表
       画第一帧之前就按 localStorage 把它放好——挂 body 要等 body 解析完，仍会先画一帧展开态。

       这个断点下收放只由**面板左边界中点那个把手**负责：那颗旧圆钮和面板头上的收起键
       都退场。同一个功能三个控件，用户不知道该点哪个；把手还兼了"收起后从哪儿拉回来"，
       位置天然自洽（推出去和拉回来是同一个点）。 */
    .materials-rail-puck { display: none; }
    .materials-rail-head .materials-rail-collapse { display: none; }

    .materials-rail-dock {
        display: flex;
        align-items: center;   /* 把手落在壳的竖向中点 */
        justify-content: flex-end;
        gap: 6px;
        flex: 1 1 auto;        /* 壳占满 rail 的整高，中点才是屏幕中点，不随文件多少上下跳 */
        min-height: 0;
        transition: transform var(--dur-3) var(--ease);
    }
    .materials-rail-edge { display: grid; }
    /* 面板维持改动前的行为：顶对齐、高度跟着内容、超了自己内滚。壳是整高的，所以这里
       必须自己封顶——不封的话长文件列表会顺着壳一直长下去，捅穿 rail 底部。 */
    .materials-rail-panel {
        align-self: flex-start;
        max-height: 100%;
        transition: opacity var(--dur-2) var(--ease-soft);
    }

    /* 收起：整壳右移「自身宽度 − 把手那一截」，面板滑出屏幕，把手正好停在原来的右缘。
       面板刻意不用 display:none——那个没法动画；滑完之后再由 visibility 把它从焦点序列里
       收走（0s 的过渡加 dur-3 延时＝等动画放完再生效）。body 本身 overflow:hidden，
       滑出去的部分不会撑出横向滚动条。 */
    :root.materials-collapsed .materials-rail-dock { transform: translateX(calc(100% - 14px)); }
    /* 收起后把手是屏幕上唯一的入口。09-04 给过一块常驻底色，2026-09-17 用户说"收起时有一个白色的边框，特别难看"
       （浅色主题下那块 surface-2 就是一块近白的板子）——去掉：收起态也不带底，只把箭头从 faint 提到 secondary
       让它还能被找到，悬停亮到正文色。仍然无边框无阴影。 */
    :root.materials-collapsed .materials-rail-edge { background: transparent; color: var(--text-secondary); }
    :root.materials-collapsed .materials-rail-edge:hover { background: transparent; color: var(--text-primary); }
    :root.materials-collapsed .materials-rail-panel {
        opacity: 0;
        visibility: hidden;
        transition: opacity var(--dur-2) var(--ease-soft), visibility 0s linear var(--dur-3);
    }

    /* 对话列那侧的留白跟着一起走，否则栏在滑、正文在跳，两件事看着不像同一个动作。
       这条是会引起重排的动画（padding 不是合成属性），所以只给它一条属性、一个时长。 */
    .thread-wrap,
    .composer-wrap { transition: padding-right var(--dur-3) var(--ease); }
}

/* 关掉动画偏好时，收放立刻到位——状态一点不丢，只是不滑了。 */
@media (prefers-reduced-motion: reduce) {
    .materials-rail-dock,
    .materials-rail-panel,
    .materials-rail-edge svg,
    .thread-wrap,
    .composer-wrap { transition: none; }
}

/* <720px：真正的移动端。720–1599px 那种贴边缘的小卡片在手机上局促，改成从右边缘滑入的
   整高抽屉——跟 #history-drawer 的移动端处理同一套手法（transform 位移 + 独立遮罩），
   fixed 定位脱离文档流，不挤压对话区。仍然是同一个 is-expanded 开关驱动，只是这个断点
   下 CSS 把它从"淡入淡出的浮层"换成了"滑入滑出的抽屉"。 */
@media (max-width: 719px) {
    .materials-rail-scrim {
        position: fixed;
        z-index: 88;
        inset: 0;
        border: 0;
        background: var(--scrim-drawer);
        backdrop-filter: blur(2px);
    }
    .materials-rail-scrim:not([hidden]) { display: block; animation: scrim-in 180ms ease-out both; }

    .materials-rail-panel {
        display: flex;
        position: fixed;
        z-index: 89;
        top: 0;
        bottom: 0;
        right: 0;
        transform: translateX(100%);
        width: min(86vw, 360px);
        max-width: none;
        border-radius: 0;
        border: 0;
        border-left: 1px solid var(--border);
        padding: 16px 12px;
        transition: transform var(--dur-3) var(--ease);
    }
    .materials-rail.is-expanded .materials-rail-panel { transform: translateX(0); }
}

/* 工具调用进度。它是过程不是结论，所以正文一开始吐字就压暗收起。 */
/* ─── 任务块：一个任务一个回复块 ───
   答卡片开启的后续段落不再另起一个带名字的新回复；块里非最后一段的正文
   降级成过程样式——它们是"问到一半"的过场，不是这个任务的结果。 */
.msg:has(+ .msg.is-continuation) {
    margin-bottom: 8px;
}

.msg.is-continuation .message-label {
    display: none;
}

.msg.is-interstitial .message-content {
    color: var(--text-faint);
    font-size: calc(12px * var(--msg-scale));
    line-height: 1.7;
}

.msg.is-interstitial .copy-message-button {
    display: none;
}

/* 答过的卡片收成一行小票；点小票展开完整问答核对 */
.input-card.is-submitted .input-summary-body {
    cursor: pointer;
    min-width: 0;
}

.input-card.is-submitted .input-summary-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.input-card.is-submitted .input-summary-items {
    display: none;
    margin-top: 8px;
}

.input-card.is-submitted.is-open .input-summary-items {
    display: block;
}

/* 工具调用的过程行，画成一条时间线。原来是四个互不相连的圆点，读起来像四条无关日志；
   连成线之后读起来是一条正在走的流程。 */
.tool-steps {
    display: flex;
    flex-direction: column;
    margin-bottom: 10px;
}

/* 刻意没有收尾压暗态（曾是整体 45% 透明度）：压暗读出来的是"褪色"不是"完成"，
   完成感由每个节点的小对勾表达，文字全程保持原亮度（用户定案 2026-08-26） */

/* 一组＝一次调用（外加它自己的若干目标行 / 进度行）。竖线画在组上，
   高度天然等于这一组的高度——不用去猜子项有几行，也<b>不能</b>拿底色去遮多余的一截：
   这块的底色跟消息气泡走，遮罩会画出一条颜色不对的竖条。 */
.step-group {
    position: relative;
}

.step-group:not(:last-child)::before {
    content: "";
    position: absolute;
    left: 3px;
    top: 16px;
    bottom: -2px;
    width: 1px;
    background: var(--border-strong);
}

.tool-step {
    margin: 0;
    padding: 3px 0 3px 20px;
    position: relative;
    display: flex;
    align-items: baseline;
    gap: 9px;
    color: var(--text-muted);
    font-size: 12px;
    line-height: 1.6;
}

/* 做过的节点：中性色小对勾——完成感靠形状，不靠颜色喊。用线条画不用"✓"字符：
   字符在不同系统字体下粗细位置不可控。画在跟原来的环同一个 7px 格子里，
   组间竖线（left:3px, top:16px 起）的几何不受影响。 */
.tool-step::before {
    content: "";
    position: absolute;
    left: 1px;
    top: 8px;
    width: 4px;
    height: 7px;
    box-sizing: border-box;
    border-right: 1.5px solid var(--text-muted);
    border-bottom: 1.5px solid var(--text-muted);
    transform: rotate(45deg);
}

/* 只有正在跑的那一个用薄荷色——accent 在这套设计里只表示状态，全亮着等于没有状态 */
.tool-step.is-active {
    color: var(--text-secondary);
}

/* 正在跑的画回实心圆点：把对勾的三笔（两条边线+旋转）全部显式复位 */
.tool-step.is-active::before {
    left: 0;
    width: 7px;
    height: 7px;
    border: 0;
    border-radius: 50%;
    transform: none;
    background: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-dim);
}

/* 参数/计数：不描边，靠底色轻轻托一下，别跟节点抢注意力 */
/* 标签和胶囊都可收缩、都各自省略。胶囊长的时候把标签挤到换行看着最廉价，
   而 flex 默认的 min-width:auto 会让它宁可换行也不收缩，所以两边都要显式放开。 */
.step-name {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.step-chip {
    flex: 0 1 auto;
    min-width: 0;
    max-width: 220px;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--surface-3);
    /* faint 叠在 surface-3 上四套主题都只有 ~3:1，10px 的小字根本看不清；
       secondary 在四套主题下实算都过 4.5:1 */
    color: var(--text-secondary);
    font-size: 10px;
    line-height: 1.5;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 最后一组（正在跑的那组）平时不画竖线（:not(:last-child) 那条管不到它），
   但它挂着目标/进度行时，分叉的短横线会悬在空中没有主干——补一段只盖住
   自己内容高度的竖线，止于最后一行分叉处，不越界。 */
.step-group:last-child:has(.tool-target)::before {
    content: "";
    position: absolute;
    left: 3px;
    top: 16px;
    bottom: 10px;
    width: 1px;
    background: var(--border-strong);
}

/* 目标行 / 进度行：从竖线上分叉出来的一条短横线，比 └ 这种字符干净 */
.tool-target {
    margin: 0;
    padding: 1px 0 1px 28px;
    position: relative;
    color: var(--text-faint);
    font-size: 11px;
    line-height: 1.7;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tool-target::before {
    content: "";
    position: absolute;
    left: 3px;
    top: 11px;
    width: 16px;
    height: 1px;
    background: var(--border-strong);
}

/* 模型这一步看过的图（2026-09-22）：缩在节点下面一排，点了用图片查看器打开。跟 .tool-target 同一条左缩进 */
.step-images {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 4px 0 6px 28px;
}

.step-image-thumb {
    width: 56px;
    height: 56px;
    padding: 0;
    border: 0;
    border-radius: 6px;
    overflow: hidden;
    background: transparent;
    cursor: pointer;
    box-shadow: 0 0 0 1px var(--border-strong);
}

.step-image-thumb:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.step-image-thumb img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* 图纸页的缩略图（2026-09-29 对话呼出画布）：整页缩进框里看得出是哪张图，裁中间那块看不出 */
.step-image-thumb.is-page img {
    object-fit: contain;
    background: var(--surface-2);
}

/* 正在跑的那条（识别图纸逐块进度就是它，原地更新）。
   pre-line：检索足迹按"一份命中文件一行"显示（用户定的格式），单行进度不受影响 */
.tool-progress {
    color: var(--text-muted);
    white-space: pre-line;
}

.tool-progress::before {
    background: var(--accent);
}

/* Skill 调用的开始/终态提示行。刻意跟 .tool-steps 分开一个容器：正文开始吐字后
   Skill 的结论行（"已完成：X" / "技能失败：X · 原因"）不是过程而是结论，
   所以单独一个容器，不参与时间线的节点/对勾体系。 */
.skill-steps {
    display: flex;
    flex-direction: column;
    gap: 3px;
    margin-bottom: 10px;
}

.skill-step {
    margin: 0;
    padding-left: 15px;
    position: relative;
    color: var(--text-faint);
    font-size: 12px;
    line-height: 1.6;
}

.skill-step::before {
    content: "";
    position: absolute;
    left: 0;
    top: 8px;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--text-faint);
}

/* 终态才上色：进行中保持中性，跟 .tool-step 一个基调；结论出来才用颜色说话。 */
.skill-step.is-terminal[data-phase="SUCCEEDED"],
.skill-step.is-terminal[data-phase="SUBMITTED"] {
    color: var(--accent);
}
.skill-step.is-terminal[data-phase="SUCCEEDED"]::before,
.skill-step.is-terminal[data-phase="SUBMITTED"]::before {
    background: var(--accent);
}

.skill-step.is-terminal[data-phase="FAILED"],
.skill-step.is-terminal[data-phase="REJECTED"],
.skill-step.is-terminal[data-phase="FAILED_TO_SUBMIT"] {
    color: var(--danger);
}
.skill-step.is-terminal[data-phase="FAILED"]::before,
.skill-step.is-terminal[data-phase="REJECTED"]::before,
.skill-step.is-terminal[data-phase="FAILED_TO_SUBMIT"]::before {
    background: var(--danger);
}

/* ── 项目专栏：常驻手风琴，不是弹层 ──
   多个项目能同时展开；"新建项目"是全站唯一的新建入口（钉在列表上方），
   "新对话"下放到每个项目自己的展开区里，归属零歧义。
   图标线宽统一 1.6，圆角一律走 --radius-sm/md token。 */

.project-create-button {
    min-height: 46px;
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 14px;
    padding: 0 12px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--surface-2);
    color: var(--text-primary);
    font-size: 13px;
    font-weight: 550;
    white-space: nowrap;
    transition: background var(--dur-2) var(--ease-soft), border-color var(--dur-2) var(--ease-soft);
}
.project-create-button:hover { border-color: var(--border-strong); background: var(--surface-2-hover); }
.project-create-button svg { width: 18px; height: 18px; flex: 0 0 auto; color: var(--accent); stroke-width: 1.6; }
.rail.is-collapsed .project-create-button { width: 44px; min-width: 44px; margin: 14px 10px; padding: 0; justify-content: center; }
.rail.is-collapsed .project-create-button span { display: none; }

.project-list { display: flex; flex-direction: column; gap: 2px; padding: 0 8px 12px; }

.project-section { border-radius: var(--radius-md); }

.project-section-head { position: relative; display: flex; align-items: center; }

.project-toggle {
    width: 100%;
    min-height: 38px;
    display: flex;
    align-items: center;
    gap: 7px;
    /* 右边只留常驻加号那一个按钮的位置（right:4 + 32 宽 = 36，给到 44 留 8px 余量）。
       改名/删除是 hover 才浮现的绝对定位层，不参与流式布局，不用为它们额外留位。 */
    padding: 0 44px 0 9px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-secondary);
    font-size: 13.5px;
    text-align: left;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.project-toggle:hover { background: var(--surface-2); color: var(--text-primary); }

.project-chevron, .project-icon {
    flex: 0 0 auto;
    stroke-width: 1.6;
    transition: transform var(--dur-1) var(--ease-soft);
}
.project-chevron { width: 13px; height: 13px; color: var(--text-faint); }
.project-icon { width: 15px; height: 15px; color: var(--text-muted); }
/* 展开态箭头下压 90°，唯一的开合信号，不需要额外图标 */
.project-toggle[aria-expanded="true"] .project-chevron { transform: rotate(90deg); }

/* 项目名吃掉到右侧按钮之间的全部剩余宽度，真放不下了才省略号 */
.project-section-name {
    min-width: 0;
    flex: 1 1 auto;
    overflow: hidden;
    font-weight: 560;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* rename/delete 浮在常驻加号左边，hover 才淡入。它们一露面，项目名就往左收出它们的位置
   （2026-09-24：原来是直接盖在名字尾部上，理由是"被盖那截本来就省略了"——可画面上是图标叠在字上，
   9 个字以上的名字一悬停就糊成一团）。102 = 图标组离右边 40 + 两枚图标 54 + 空 8；全名靠悬停滚动看。
   成员态没有这两枚图标，不收。 */
:root:not(.is-member-only) .project-section:hover .project-toggle,
:root:not(.is-member-only) .project-section-head:has(.project-section-actions :focus-visible) .project-toggle {
    padding-right: 102px;
}

.project-section-actions {
    position: absolute;
    right: 40px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    gap: 2px;
    opacity: 0;
    transition: opacity var(--dur-1) var(--ease-soft);
}
.project-section:hover .project-section-actions,
.project-section-rename:focus-visible,
.project-section-delete:focus-visible {
    opacity: 1;
}

.project-section-rename, .project-section-delete {
    width: 26px;
    height: 26px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--text-faint);
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.project-section-rename svg, .project-section-delete svg { width: 13px; height: 13px; stroke-width: 1.6; }
.project-section-rename:hover { background: var(--surface-2-hover); color: var(--text-primary); }
.project-section-delete:hover { background: var(--danger-dim); color: var(--danger); }

/*
 * 展开/收起过渡（200ms）。grid-template-rows 0fr↔1fr 是唯一不用 JS 量高度、天然适配
 * 变长内容（问题/合同/任务/对话列表长短不一）的写法——max-height 定值过渡在内容比假设
 * 值短很多时会"提前收完"。
 *
 * 收起后必须彻底消失，三重保证缺一不可：
 *   高度 —— 0fr + clip 层零 padding（padding 放在 clip 上就塌不掉，那正是之前那条灰印子）
 *   可见 —— visibility 参与过渡：显示时立刻翻 visible，隐藏时整整 200ms 后才翻 hidden，
 *           所以淡出过程看得见、结束瞬间彻底不可见，不留半透明残影
 *   交互 —— inert（JS 里跟 is-open 一起设），退出可访问树也不能 Tab 进去
 *
 * 全局 prefers-reduced-motion 覆写（transition-duration: 0.01ms !important）已兜底。
 */
.project-section-body {
    display: grid;
    grid-template-rows: 0fr;
    opacity: 0;
    visibility: hidden;
    transition: grid-template-rows var(--dur-2) var(--ease-soft),
                opacity var(--dur-2) var(--ease-soft),
                visibility var(--dur-2);
}
.project-section-body.is-open {
    grid-template-rows: 1fr;
    opacity: 1;
    visibility: visible;
}

/* 只干裁剪这一件事。绝对不要在这一层加 padding/border。 */
.project-section-body-clip {
    overflow: hidden;
    min-height: 0;
}

.project-section-body-inner {
    display: flex;
    flex-direction: column;
    /* 分组之间统一 12px；组内间距各组自己定 6px */
    gap: 12px;
    /* 左 42：各组再叠 9px 内边距，正好落在 51px —— 与项目标题文字左边缘对齐
       （9 toggle + 13 箭头 + 7 + 15 文件夹 + 7 = 51） */
    padding: 8px 0 10px 42px;
    /* 内容轻微上移淡入，不是整块硬切 */
    transform: translateY(-4px);
    transition: transform var(--dur-2) var(--ease-soft);
}
.project-section-body.is-open .project-section-body-inner { transform: translateY(0); }

/*
 * M2 起就有的越界/关联问题，早先只是一个只有 hover 才看得到的"N 处待处理"徽标——
 * M7 把违约金金额塞进了 reason 文字，值得读，升级成常驻小列表。VIOLATION 是真出问题，
 * 用 danger 色；STALE/UNVERIFIABLE 只是"还没算完"，中性色——状态文字前缀永远都在，
 * 不单靠颜色区分（跟 Gantt 关键路径必须带"关键"文字标是同一条原则）。
 */
/* 五个内容组统一：左 9（叠上 inner 的 42 = 51，对齐项目标题）、右 9、组内 6px。
   :empty 必须留着——组是空的就 display:none，这样 inner 的 12px flex gap 不会为它留白。 */
.project-issue-list { display: flex; flex-direction: column; gap: 6px; padding: 0 9px; }
.project-issue-list:empty { display: none; }

.project-issue-row {
    display: flex;
    align-items: baseline;
    gap: 6px;
    font-size: 11px;
    /* 窄侧栏下这行会自然换行（违约金金额就在里面，得让人读全，不能截断）。
       1.6 × 11px = 17.6px 行高，行间留白 6.6px，落在 6–8px 区间内。 */
    line-height: 1.6;
    color: var(--text-muted);
}

.project-issue-status {
    flex: 0 0 auto;
    padding: 1px 6px;
    border-radius: var(--radius-sm);
    font-size: 10px;
    white-space: nowrap;
    background: var(--surface-2);
    color: var(--text-muted);
}
.project-issue-row.is-violation .project-issue-status { background: var(--danger-dim); color: var(--danger); }

.project-issue-text { flex: 1 1 auto; }

/* M7：合同事实，只读——抽取/手填/确认都在对话里，这里不放编辑控件。 */

/*
 * M8 通知：设置 › 通知面板里的项目清单。搬离项目手风琴的原因见 settings.html 的注释——
 * 一句话，低频配置不该常驻在高频浏览区，而且集中起来才能一眼看全谁订阅了谁没有。
 */
/* 项目/技能列表（JS 渲染）：2026-09-08 起就是 .settings-row 那套"行"（样式并在那边），
   这里只剩它们自己的状态字和动作区。 */
.notify-project-list { display: flex; flex-direction: column; }

/* 未订阅是中性灰，订阅了才用 accent——accent 在这套设计里只表达状态，正好是它的本职 */
.notify-project-state {
    font-size: 11.5px;
    color: var(--text-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.notify-project-state.is-on { color: var(--accent); }
/* 「发测试邮件」的结果，跟状态同一行下面；用 hidden 开关所以要有 [hidden] 覆盖（2026-09-14 那条规矩） */
.notify-project-test-result { display: block; margin-top: 2px; font-size: 12px; color: var(--text-muted); }
.notify-project-test-result.is-error { color: var(--danger); }
.notify-project-test-result[hidden] { display: none; }

.notify-project-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
/* 行里的小按钮不要那 6px 的底边距——那是给表单里"按钮下面还有东西"准备的，行里会把它顶偏 */
.notify-project-actions .settings-inline-button { margin-bottom: 0; }
/* 取消订阅是收回动作，不该跟"订阅/更改"抢同一个视觉重量 */
.settings-inline-button.is-quiet { color: var(--text-faint); }
.settings-inline-button.is-quiet:hover { color: var(--danger); border-color: var(--danger); }

.settings-empty { margin: 0; color: var(--text-faint); font-size: 13px; line-height: 1.6; }

/*
 * 设置 › 天气。
 *
 * 位置只有两级：项目工地 > 出口 IP。面板按同一个顺序排：实况卡（此刻用的是哪一级）→ 项目工地。
 *
 * 实况卡回答两个问题：现在多少度、这是哪儿的天。第二个问题过去只有一行灰字，
 * 用户看到顶栏显示外地却不知道为什么——来源和出处现在都在卡上明写。
 */
/* 设置 › 天气的实况区（2026-09-09 精简）。没有外框、没有底色：它是一行读数，跟同面板下面的"行"一个重量。
   第一行「图标 温度 天气 · 地点 · 项目」，第二行今日范围，其余收进「天气详情」。 */
.weather-now-card { margin-bottom: 4px; }

.weather-now {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 4px 0 12px;
}

/* 数据还没回来时留住高度：从 0 高突然长出来会把下面整段顶一下 */
.weather-now.is-loading { min-height: 56px; }

.weather-now-icon {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    margin-top: 2px;
    display: grid;
    place-items: center;
    color: var(--text-secondary);
}

/* 还不知道在哪：图标退成纯提示，不该看着像一条有效读数 */
.weather-now.is-blank .weather-now-icon { color: var(--text-faint); }

.weather-now-icon svg {
    width: 24px;
    height: 24px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.weather-now-main {
    min-width: 0;
    flex: 1 1 auto;
    display: grid;
    gap: 3px;
}

/* 第一行：温度 天气 · 地点 · 项目（· 自动定位）。宽屏一行装下，地点和项目名可以被省略号截；
   窄屏（≤720）才折行——两段都被截成几个字不如折成两行。任何情况都不许溢出 */
.weather-now-head {
    margin: 0;
    display: flex;
    flex-wrap: nowrap;
    align-items: baseline;
    gap: 2px 8px;
    min-width: 0;
}
@media (max-width: 720px) {
    .weather-now-head { flex-wrap: wrap; }
}

.weather-now-temp {
    color: var(--text-heading);
    font-size: 26px;
    font-weight: 600;
    line-height: 1.15;
    letter-spacing: -0.02em;
}

/* 温度和天气词不许被挤——"小雨"折成"小/雨"比省略地点难看得多；能收缩的只有地点和项目名 */
.weather-now-temp, .weather-now-desc { flex: 0 0 auto; white-space: nowrap; }
.weather-now-desc { color: var(--text-secondary); font-size: 14px; }

/* 残缺态的主语（尚未确定位置 / 暂无实时数据）：占温度的位置，但不用温度的分量 */
.weather-now-state { color: var(--text-faint); font-size: 15px; }

.weather-now-place,
.weather-now-project {
    flex: 0 1 auto;
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* 地点比项目名重要：两段都挤时先截项目名（项目名在标题栏和侧栏本来就看得见） */
.weather-now-project { flex-shrink: 3; }
.weather-now-place { color: var(--text-primary); font-size: 13.5px; }
.weather-now-project { color: var(--text-muted); font-size: 12.5px; }
/* 段与段之间的中点由 CSS 画，不进文本——省略号截断时不会把分隔符一起吞掉 */
.weather-now-place::before,
.weather-now-project::before { content: '·'; margin-right: 8px; color: var(--text-faint); }

/* 唯一常驻的状态词：按网络位置猜的不是工地。跟工地标签同款描边、不填色底（浅色主题 10.5px 的字叠色底不过 4.5:1） */
.weather-now-tag {
    flex: 0 0 auto;
    padding: 1px 7px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    color: var(--text-faint);
    font-size: 10.5px;
    line-height: 1.6;
}

.weather-now-sub {
    margin: 0;
    color: var(--text-muted);
    font-size: 12.5px;
    line-height: 1.55;
}
.weather-now-sub[hidden] { display: none; }

/* 「天气详情」：原生 details。summary 去掉默认三角，用 CSS 画的小箭头表示展开态 */
.weather-now-details { margin-top: 2px; }
.weather-now-details-summary {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 3px 0;
    color: var(--text-muted);
    font-size: 12.5px;
    cursor: pointer;
    list-style: none;
    border-radius: 4px;
    transition: color var(--dur-1) var(--ease-soft);
}
.weather-now-details-summary::-webkit-details-marker { display: none; }
.weather-now-details-summary:hover { color: var(--text-primary); }
.weather-now-details-summary:focus-visible { outline: 2px solid var(--focus-border); outline-offset: 2px; }
.weather-now-details-summary::before {
    content: '';
    width: 6px;
    height: 6px;
    margin-left: 2px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(-45deg);
    transition: transform var(--dur-1) var(--ease-soft);
}
.weather-now-details[open] .weather-now-details-summary::before { transform: rotate(45deg); }

/* 四项实况：标签在上数值在下，按可用宽度排列。行内一串"湿度 62% · 风速 12 公里/小时"
   读起来是一句话，得逐字扫；分格对齐才能一眼取到想要的那一个。 */
.weather-now-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 24px;
    margin: 6px 0 2px;
}

.weather-now-fact { min-width: 0; }
.weather-now-fact dt { color: var(--text-faint); font-size: 11px; }
.weather-now-fact dd {
    margin: 1px 0 0;
    color: var(--text-secondary);
    font-size: 13px;
    white-space: nowrap;
}

/* ─── 回到最新（2026-09-02）：翻上去读旧内容、或刷新后恢复到上次位置时出现 ─── */
.jump-latest {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: 132px;                      /* 压在输入区上方，不遮正文最后一行 */
    z-index: 25;
    padding: 6px 14px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    background: var(--surface-2);
    color: var(--text-secondary);
    font-size: 12px;
    box-shadow: 0 6px 20px color-mix(in srgb, var(--bg) 60%, transparent);
    transition: color 150ms ease, border-color 150ms ease;
}

.jump-latest:hover { color: var(--text-primary); border-color: var(--focus-border); }

/* ─── M8 主动交流（2026-09-02 定稿；右缘全览尺 2026-09-15 按用户要求整个删掉）：翻找时一眼定位 ───
   左缘一根常驻的淡薄荷细线 + 前置一条「你安静了 N 分钟」分隔线（插叙的语法）。
   标签写「诶皆 · 主动交流 · 时间」，不再用小字标。薄荷色在这套设计里
   专表状态，"它主动发起的"算状态，用最淡那档。 */
.msg.is-proactive .message-shell {
    border-left: 2px solid var(--accent-tint-border);
    padding-left: 14px;
}

.msg.is-proactive .message-label { letter-spacing: 0.06em; }

.quiet-divider {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: -18px 0 18px;   /* 上一条消息自带 46px 的轮次间距，分隔线往上收一点，别把间隙撑成两段 */
    color: var(--text-faint);
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.08em;
}

.quiet-divider::before,
.quiet-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border);
}


/* ─── M8 巡检早报卡 ───
   落在顶栏和对话区之间，是外壳不是内容（刻意不乘 --msg-scale）。中性色为主：
   accent 只表示状态，一张常驻到被收起的提醒卡不配薄荷色，压一档用次要文字色。 */
/* 悬浮卡（2026-09-08，用户反馈）：原来它排在对话上方的正常流里，一出现整个对话区被往下推一截，
   × 收起又跳回去。改成贴在顶栏（56px）下面的浮层：absolute 相对 .main（position:relative），
   对话区的布局一个像素不动。盖在对话上方是刻意的——它就是一张要人看一眼再收掉的便条，
   不是对话的一部分。正文封顶高度自己内滚，长报告不会把整屏盖死。 */
.patrol-card {
    position: absolute;
    top: 66px;
    left: 50%;
    z-index: 16; /* 压过 .thread-wrap 的渐变遮罩层（15）；输入区在下方，不相交 */
    transform: translateX(-50%);
    max-width: 860px;
    width: calc(100% - 48px);
    display: flex;
    flex-direction: column;
    max-height: calc(100% - 66px - 200px); /* 给输入区留出空间 */
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    background: var(--surface-1);
    box-shadow: var(--shadow-pop);
    overflow: hidden;
    animation: patrol-card-in var(--dur-3) var(--ease) both;
}
.patrol-card[hidden] { display: none; }
@keyframes patrol-card-in {
    from { opacity: 0; transform: translate(-50%, -6px); }
    to { opacity: 1; transform: translate(-50%, 0); }
}

.patrol-card-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-bottom: 1px solid var(--border);
}

.patrol-card-title {
    color: var(--text-primary);
    font-size: 12.5px;
    font-weight: 600;
}

.patrol-card-date {
    color: var(--text-faint);
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.04em;
}

.patrol-card-ask {
    margin-left: auto;
    min-height: 22px;
    padding: 0 8px;
    border: 1px solid var(--border-strong);
    border-radius: 7px;
    background: transparent;
    color: var(--text-muted);
    font-size: 11px;
    transition: color 150ms ease, border-color 150ms ease;
}

.patrol-card-ask:hover { color: var(--text-primary); border-color: var(--focus-border); }

.patrol-card-dismiss {
    width: 22px;
    height: 22px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text-faint);
    font-size: 14px;
    line-height: 1;
}

.patrol-card-dismiss:hover { color: var(--text-primary); background: var(--surface-2-hover); }

.patrol-card-body {
    padding: 10px 12px;
    color: var(--text-secondary);
    font-size: 12.5px;
    line-height: 1.7;
    white-space: pre-wrap;   /* 早报正文是纯文本，换行靠它保留——不走 markdown 渲染 */
    max-height: min(320px, 45vh); /* 今日安排进来后正文长了一截（2026-09-15）；仍封顶，内部自己滚 */
    overflow-y: auto;
}

/* 无异常那行字是按钮（2026-09-14 起点得开）：显式清边框和底色，悬停才提色——它是状态提示不是主操作。 */
.patrol-quiet {
    display: inline-flex;
    align-items: center;
    padding: 2px 6px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text-faint);
    font-size: 10.5px;
    font-family: inherit;
    cursor: pointer;
    transition: color 150ms ease, background-color 150ms ease;
}

.patrol-quiet:hover,
.patrol-quiet[aria-expanded="true"] { color: var(--text-primary); background: var(--surface-2-hover); }

/* 早报卡片被收起后顶栏留的回程票：跟旁边那行小字同一号字，是按钮所以显式清边框；
   「有异常」是状态，前面一粒薄荷点——薄荷只用于状态，这里正是状态。 */
.patrol-reopen {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 6px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text-muted);
    font-size: 10.5px;
    font-family: inherit;
    cursor: pointer;
    transition: color 150ms ease, background-color 150ms ease;
}

.patrol-reopen::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    flex: 0 0 auto;
}

.patrol-reopen:hover { color: var(--text-primary); background: var(--surface-2-hover); }
/* 回程票写「今日安排 · N 条在施」时不是异常状态，不带薄荷点 */
.patrol-reopen.is-plan::before { display: none; }

/* 两个按钮都是 inline-flex：author 的 display 会压过浏览器默认的 [hidden]{display:none}，
   不补这一条，「今日早报 · 有异常」从 2026-09-07 上线起就一直露着（无异常的日子也在，点了当然没反应——
   2026-09-14 用户报"点右上角的早报没反应"就是它）。凡是用 hidden 属性开关、又自己写了 display 的元素都要补。 */
.patrol-quiet[hidden],
.patrol-reopen[hidden] { display: none; }

/* 实况区下面的动作行：问一句"未来七天影响不影响"的入口。整行可点、发丝线与实况隔开；
   只预填输入框不发送，反馈在点了之后由状态行说，不常驻。border 除 top 外显式清零。 */
.weather-outlook-ask {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 0;
    border: 0;
    border-top: 1px solid var(--border);
    border-radius: 0;
    background: transparent;
    color: var(--text-secondary);
    font-size: 12.5px;
    font-weight: 500;
    text-align: left;
    transition: color 150ms ease, background 150ms ease;
}

.weather-outlook-ask:hover {
    background: var(--surface-2-hover);
    color: var(--text-primary);
}

/* 项目工地行（2026-09-09 精简）：一行「项目名｜地点｜更改｜更多」。名字和地点是同一个弹性容器里的
   两段，宽屏并排、窄屏自然折成两行；来源标签和出处不常驻，收在「更多 › 查看定位依据」。 */
/* main 沿用 .notify-project-main 的 grid（上下两行：名字+地点那一行、按需展开的定位依据）；
   名字和地点在 .weather-site-line 里并排。宽屏一行：名字最多占六成，地点至少留得住四成，两段各自省略号截；
   窄屏（≤720）才折成两行 */
.weather-site-line {
    display: flex;
    flex-wrap: nowrap;
    align-items: baseline;
    gap: 2px 14px;
    min-width: 0;
}
.weather-site-line .notify-project-name { flex: 0 1 auto; min-width: 0; max-width: 60%; }
@media (max-width: 720px) {
    .weather-site-line { flex-wrap: wrap; }
    .weather-site-line .notify-project-name { max-width: 100%; }
    .weather-site-place { flex-basis: 100%; }
}

.weather-site-place {
    flex: 1 1 120px;
    min-width: 0;
    color: var(--text-secondary);
    font-size: 12.5px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* 未设置：明说按网络位置定位，用最弱的字色——它是一句状态，不是一个地名 */
.weather-site-place.is-unset { color: var(--text-faint); }

/* 设置 › 工序（2026-09-15）：一行「项目名｜《文件》· N 道工序 · 版本｜上传/更换｜查看｜移除」，
   跟项目工地那一行同一个骨架；认不出结构或有没认出来的标题时，第二行一句小字说清 */
.procedure-line {
    display: flex;
    flex-wrap: nowrap;
    align-items: baseline;
    gap: 2px 14px;
    min-width: 0;
}
.procedure-meta {
    flex: 1 1 120px;
    min-width: 0;
    color: var(--text-secondary);
    font-size: 12.5px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.procedure-meta.is-unset { color: var(--text-faint); }
.procedure-note {
    margin: 2px 0 0;
    color: var(--text-faint);
    font-size: 11.5px;
    line-height: 1.5;
}
.procedure-note.is-error { color: var(--danger, var(--text-secondary)); }
/* 设置 › 智管联动（2026-09-22）：项目行沿用工序那一行的骨架；面板顶部那句状态只在"没启用/没配平台标识"时出现 */
.zhiguan-status { margin: 0 0 18px; line-height: 1.7; }
.zhiguan-status[hidden] { display: none; }
/* 设置 › 记忆（2026-09-18）：项目行沿用工序那一行的骨架；展开后一条事实一行——原句在上、类别·来源·日期小字在下，
   被覆盖过的再带一行「上一次：…」。.memory-facts 自己写了 display，所以必须补 [hidden] 覆盖 */
.memory-row { align-items: flex-start; padding: 14px 0; }
.memory-facts { display: flex; flex-direction: column; margin-top: 6px; }
.memory-facts[hidden] { display: none; }
.memory-fact { display: flex; align-items: flex-start; gap: 12px; padding: 8px 0; border-top: 1px solid var(--border); }
.memory-fact-main { min-width: 0; flex: 1 1 auto; display: grid; gap: 2px; }
.memory-fact-text { color: var(--text-primary); font-size: 13px; line-height: 1.6; overflow-wrap: anywhere; }
.memory-fact-meta, .memory-fact-previous { margin: 0; color: var(--text-faint); font-size: 11.5px; line-height: 1.5; overflow-wrap: anywhere; }
.memory-row .notify-project-actions, .memory-fact .notify-project-actions { flex-wrap: wrap; justify-content: flex-end; }
/* 工序菜单里"当前"那一份：是状态不是可点的项 */
.materials-rail-menu-item:disabled { color: var(--text-faint); cursor: default; }
.materials-rail-menu-item:disabled:hover { background: transparent; }
@media (max-width: 720px) {
    .procedure-line { flex-wrap: wrap; }
}

/* 定位依据：按需展开，占整行，完整原文不截断。左侧一条细线把它跟行本身分开 */
.weather-site-basis {
    display: grid;
    gap: 2px;
    margin: 4px 0 2px;
    padding: 6px 10px;
    border-left: 2px solid var(--border-strong);
    color: var(--text-muted);
    font-size: 12px;
    line-height: 1.55;
    overflow-wrap: anywhere;
    outline: none;
}
.weather-site-basis[hidden] { display: none; }
.weather-site-basis:focus-visible { border-left-color: var(--accent); }
.weather-site-basis-source { color: var(--text-secondary); }

/* 行里的「更多」：三个点，常显（物料栏那个悬停才显，这里没有悬停可依赖的行高亮） */
.settings-row-more {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text-faint);
    transition: color var(--dur-1) var(--ease-soft), background var(--dur-1) var(--ease-soft);
}
.settings-row-more svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.8; }
.settings-row-more:hover,
.notify-project-row.has-open-menu .settings-row-more { background: var(--surface-2-hover); color: var(--text-primary); }

/*
 * 对话树：一条 1px 细竖线从项目标题下方垂下来，每条对话在线上挂一个圆点。
 *
 * 横向坐标全部相对 .project-section 左边缘（inner 的 padding-left 是 42）：
 *   -13 负外边距  → 本组的行盒从 29px 起（正是文件夹图标的左边缘），
 *                   这样 hover/选中底色是包住整个树形图的，圆点不会孤零零落在底色外面
 *   竖线 left:9   → 38–39px
 *   圆点 left:6   → 35–42px，圆心 38.5，与竖线中心（38.5）严丝合缝
 *   标题          → 29 + 22 = 51px，和项目标题、其它四组的文字左边缘一条线，位置没动
 * 原先 42→51 那段纯空白，现在正好被竖线和圆点填上——那块空白就是层级读不出来的原因。
 *
 * 全部用伪元素画，不为装饰加一个节点；收起时整个 body 被 clip 层裁掉且 visibility:hidden，
 * 线和点自然一起消失，不会留占位高度或灰印子。
 */
.project-section-conversations {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 0;
    margin-left: -13px;
}

.project-section-conversations .conversation-row::before {
    content: "";
    position: absolute;
    left: 9px;
    /* -6 吃掉上方那 6px 的 flex gap，逐行的线段首尾相接成一条连续的线 */
    top: -6px;
    bottom: 0;
    width: 1px;
    /* 用 --border-strong 而不是 --border：1px 竖线只有一个像素宽，--border 那档透明度
       在 1× 屏上基本看不出来（3× 截图里才勉强成线）。这一档仍然远低于文字对比。 */
    background: var(--border-strong);
    /* 选中行有底色，而 .conversation-item 是 position:relative 的定位元素、在树序里排在
       ::before 之后——不抬 z-index 的话线会被那块底色盖住，在当前对话处断掉一截。 */
    z-index: 1;
}
/* 第一条往上多探 12px（组间距就是 12px），把线接到项目标题底下；
   项目没有越界/合同/任务那几组时，那 12px 正好落在项目行下方，线是从项目名底下垂下来的。 */
.project-section-conversations .conversation-row:first-child::before { top: -12px; }
/* 最后一条止于圆心，线不再往下拖到项目卡片底部 */
.project-section-conversations .conversation-row:last-child::before { bottom: 50%; }

.project-section-conversations .conversation-item::before {
    content: "";
    position: absolute;
    left: 6px;
    top: 50%;
    width: 7px;
    height: 7px;
    margin-top: -3.5px;
    border-radius: 50%;
    background: var(--text-faint);
    /* 压在竖线之上：线从圆点背后穿过，而不是横切过圆点中间 */
    z-index: 2;
    transition: background var(--dur-1) var(--ease-soft);
}
.project-section-conversations .conversation-item.is-current::before { background: var(--accent); }

/*
 * "+ 新对话"从展开区里带文字的整行按钮，收成头部行常驻可见的纯图标——
 * 唯一入口，折叠状态也能点，点了顺带展开该项目（startNewChatIn 本来就这么做，直接复用）。
 * 不跟 rename/delete 一样 hover 才显现：新建对话比改名/删除更高频，值得常驻。
 *
 * 靶区 32×32 而图标仍是 13px：热区要好点，图标不能跟着变大压过项目名。
 */
.project-section-quick-new {
    position: absolute;
    right: 4px;
    top: 50%;
    transform: translateY(-50%);
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--accent);
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.project-section-quick-new svg { width: 13px; height: 13px; stroke-width: 1.6; }
.project-section-quick-new:hover { background: var(--surface-2-hover); }

/* 左 22 是刻意的：容器自己带 -13 负外边距（行盒从 29px 起），补 22 才回到 51px 对齐线。
   空态没有对话行，也就没有线和点——不留半截悬空的竖线。 */
.project-section-empty { margin: 0 9px 0 22px; color: var(--text-faint); font-size: 12px; line-height: 1.6; }

/* 加载失败：跟"没有对话"共用同一套排版，只是文字换成 danger 色——网络错误不能长得和
   "这个项目确实是空的"一样，用户得看得出这是可以重试的故障，不是项目本身没内容。 */
.project-section-empty.is-error { color: var(--danger); }
.project-section-retry {
    margin-left: 4px;
    border: 0;
    padding: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}
.project-section-retry:hover { color: var(--text-primary); }

/* 输入型弹窗：沿用 confirm-dialog 的壳，去掉告警标记 */
.confirm-dialog.is-prompt { gap: 13px; }
.confirm-dialog.is-prompt h2 { font-size: 16.5px; }

.dialog-input {
    width: 100%;
    min-height: 42px;
    padding: 0 13px;
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    background: var(--surface-2);
    color: var(--text-primary);
    font-family: inherit;
    font-size: 14px;
}

/* 校验失败时输入框自己也变红，光靠下面一行小字容易被忽略 */
.dialog-input.is-invalid { border-color: var(--danger); }

.dialog-error {
    margin: -4px 0 0;
    color: var(--danger);
    font-size: 12.5px;
    line-height: 1.6;
}
.dialog-error[hidden] { display: none; }

.dialog-input::placeholder { color: var(--text-faint); }
.dialog-input:focus {
    outline: 0;
    border-color: var(--focus-border);
    box-shadow: 0 0 0 3px var(--focus-ring);
}

/* accent 在本站只表示状态与主操作，弹窗确认键属于后者 */
.dialog-button-primary {
    border: 1px solid var(--accent-tint-border);
    background: var(--accent-tint);
    color: var(--accent);
}

.dialog-button-primary:hover:not(:disabled) { background: var(--accent-tint-hover); }

/*
 * M6 后台任务闭环：站内通知堆叠（右上角）+ 对话线程里的持久化系统状态消息。
 * 两处共用同一套"消息 + 操作按钮"的视觉词汇，只是容器不同——通知是浮动、可关闭的卡片，
 * 系统消息是时间线里的常驻一行。颜色只用现有 token，失败态复用 --danger，
 * 不是靠红色本身表意——文字里已经写了"失败"，颜色是强调，不是唯一线索。
 */
.task-notifications {
    position: fixed;
    top: 16px;
    right: 16px;
    z-index: 150;
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: min(360px, calc(100vw - 32px));
    pointer-events: none; /* 容器本身不挡点击，卡片自己再开回来 */
}
.task-notifications:empty { display: none; }

.task-notification {
    position: relative;
    pointer-events: auto;
    padding: 14px 34px 14px 16px;
    border-radius: var(--radius-md);
    border: 1px solid var(--border-strong);
    background: var(--surface-float);
    box-shadow: inset 0 1px 0 var(--edge-light), var(--shadow-pop);
    animation: task-notification-in 220ms var(--ease-soft) both;
}
@keyframes task-notification-in {
    from { opacity: 0; transform: translateY(-8px); }
    to { opacity: 1; transform: none; }
}
.task-notification.is-failed { border-color: color-mix(in srgb, var(--danger) 40%, var(--border-strong)); }

.task-notification-message {
    margin: 0 0 10px;
    color: var(--text-primary);
    font-size: 13px;
    line-height: 1.6;
}
.task-notification.is-failed .task-notification-message { color: var(--danger); }

.task-notification-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.task-notification-actions button {
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    padding: 5px 12px;
    background: var(--surface-2);
    color: var(--text-secondary);
    font-size: 12px;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.task-notification-actions button:hover { background: var(--surface-2-hover); color: var(--text-primary); }
.task-notification-actions button[data-action="confirm"],
.task-notification-actions button[data-action="retry"] {
    border-color: var(--accent-tint-border);
    background: var(--accent-tint);
    color: var(--accent);
}
.task-notification-actions button[data-action="confirm"]:hover,
.task-notification-actions button[data-action="retry"]:hover { background: var(--accent-tint-hover); }

.task-notification-dismiss {
    position: absolute;
    top: 10px;
    right: 10px;
    width: 22px;
    height: 22px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text-faint);
}
.task-notification-dismiss:hover { background: var(--surface-2-hover); color: var(--text-primary); }
.task-notification-dismiss svg { width: 12px; height: 12px; stroke-width: 1.8; }


/* ─── 后台生成任务卡片（M11.3 重做）───
 *
 * 卡片挂在它那条助手消息的最顶部，并且【替代】了原来那行 "已提交后台任务：X" 的
 * .skill-step 提示——同一件事不能有两处状态显示。
 *
 * 刻意弱化卡片感：没有 border、没有圆角、没有独立卡片底色，只有一条 2px 细竖线，
 * 底色是一层几乎看不见的 accent 染色。它要读起来像正文的一部分，不是压在正文上的一块面板。
 */
.task-card {
    margin: 0 0 12px;
    padding: 2px 0 2px 12px;
    border-left: 2px solid var(--border-strong);
    background: transparent;
}
.task-card.is-running { border-left-color: var(--accent); background: color-mix(in srgb, var(--accent) 4%, transparent); }
.task-card.is-failed { border-left-color: var(--danger); }

.task-card-head {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 0;
    border: 0; /* 不写就会继承浏览器默认 2px outset，CSS 里看不出来，只有渲染才发现 */
    background: none;
    color: var(--text-secondary);
    font: inherit;
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}
.task-card-title { color: var(--text-primary); }
.task-card.is-running .task-card-title { color: var(--text-secondary); font-weight: 400; }
/* 总耗时只在这一行出现——子步骤一律不显示耗时。运行中用 accent：这是"此刻正在发生的状态"，
   跟信号轨、当前会话高亮同一条规则，不是新开的例外。结束后退回 text-faint，耗时就只是记录。 */
.task-card-elapsed { color: var(--text-faint); }
.task-card.is-running .task-card-elapsed { color: var(--accent); font-variant-numeric: tabular-nums; }
.task-card-caret {
    width: 14px; height: 14px;
    margin-left: auto;
    flex: none;
    fill: none;
    stroke: var(--text-faint);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: transform var(--dur-1) var(--ease-soft);
}
.task-card-head[aria-expanded="true"] .task-card-caret { transform: rotate(180deg); }

/* 唯一的动效，且只在真的进行中时动——静止的点就表示不动了。跟助手消息的信号轨同一条纪律：
   循环动画只用来表达状态，不用来装饰。 */
.task-card-dot, .task-step-dot {
    border-radius: 50%;
    background: var(--text-faint);
    flex: none;
}
.task-card-dot { width: 7px; height: 7px; }
.task-card.is-running .task-card-dot { background: var(--accent); animation: task-dot-pulse 1.6s ease-in-out infinite; }
.task-card.is-failed .task-card-dot { background: var(--danger); }
@keyframes task-dot-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
@media (prefers-reduced-motion: reduce) {
    .task-card.is-running .task-card-dot,
    .task-step.is-running .task-step-dot { animation: none; }
}

/* 折叠时只露当前阶段这一行；展开后它让位给完整时间线，两者互斥 */
.task-card-current {
    margin: 4px 0 0;
    padding-left: 15px;
    font-size: 12px;
    color: var(--text-faint);
    line-height: 1.5;
}
.task-card-current:empty { display: none; }

.task-card-body { margin-top: 6px; }
/* 折叠头上的技能徽标：小、弱、不占宽度——它只负责回答"这是不是技能做的"，
   是哪个技能、哪个版本、怎么触发的，展开后时间线第一步会说清楚。 */
.task-card-badge {
    flex: none;
    padding: 1px 5px;
    border-radius: 3px;
    background: var(--accent-tint);
    color: var(--accent);
    font-size: 10.5px;
    line-height: 1.5;
}

/* 时间线里那条技能步骤：它是整条链的起点，比别的步骤稍微亮一点 */
.task-step.is-skill { color: var(--text-secondary); }
.task-step.is-skill .task-step-dot { background: var(--accent); }

.task-step-list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 4px; }
.task-step {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    color: var(--text-faint); /* 已完成步骤默认就是灰的 */
    line-height: 1.5;
    min-width: 0;
}
.task-step-dot { width: 5px; height: 5px; }
.task-step.is-running { color: var(--text-secondary); }
.task-step.is-running .task-step-dot { background: var(--accent); animation: task-dot-pulse 1.6s ease-in-out infinite; }
.task-step.is-error { color: var(--danger); }
.task-step.is-error .task-step-dot { background: var(--danger); }

/* 摘要只放一个可读的值（关键词/文件名），绝不放完整参数、内部路径或空的 {} */
.task-step-detail {
    color: var(--text-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.task-step-detail:empty { display: none; }
/* 重复调用合并后的次数标记，例如 "检索计划任务 × 12" */
.task-step-count { color: var(--text-faint); flex: none; }
.task-step-count:empty { display: none; }
.task-step-empty { margin: 0; font-size: 12px; color: var(--text-faint); }

.task-card-error {
    margin: 6px 0 0;
    padding-left: 15px;
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--danger);
}
.task-card-error:empty { display: none; }
/* 同一行位置也承载成功后的交付结论（邮件发没发）——那不是错误，用次级文字色 */
.task-card-error.is-note { color: var(--text-2); }

.task-card-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.task-card-actions:not(:empty) { margin-top: 10px; }
.task-card-actions button {
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    padding: 4px 11px;
    background: var(--surface-2);
    color: var(--text-secondary);
    font-size: 12.5px;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.task-card-actions button:hover { background: var(--surface-2-hover); color: var(--text-primary); }
.task-card-actions button[data-action="confirm"],
.task-card-actions button[data-action="retry"] {
    border-color: var(--accent-tint-border);
    background: var(--accent-tint);
    color: var(--accent);
}
.task-card-actions button[data-action="confirm"]:hover,
.task-card-actions button[data-action="retry"]:hover { background: var(--accent-tint-hover); }

/* 没有宿主助手消息时（历史里孤立的 task_event）才退化成独立一条，缩进对齐正文 */
.msg.task-event { padding: 4px 0 4px 2px; }

/* M11.7：设置页技能行的副标题——短名当主标题之后，那句长描述降到这里 */
.settings-row-hint {
    margin: 2px 0 0;
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-faint);
}

/* ─── M12：失败的 AI 回复——说清原因，并且能就地重跑 ───
   原来失败只有一个 .error class，而正文往往是空的（网络在第一个 token 之前就断了），
   于是界面上留下一个纯空白气泡。 */
/* 无背景无边框：原因就是一行红字，动作是一个圆图标——跟这一轮弱化卡片感的方向一致 */
.message-failure {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 6px;
}
/* 重新生成一开始就把它藏起来（hidden）：上面那条 display 会压过浏览器默认的 [hidden]，不补这条红字整趟都挂着
   （2026-10-15 用户实测：点了重新生成、新回答在吐字，旧的「服务重启…」红字还在） */
.message-failure[hidden] { display: none; }
.message-failure-reason {
    margin: 0;
    min-width: 0;
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--danger);
}

/* 圆形图标按钮（参考 ChatGPT 的重新生成）。跟 .copy-message-button 不同：那个
   悬停才显形，这个必须常驻——失败之后它是用户唯一能做的动作，藏起来等于没有。 */
/* 任务失败时的重新生成，紧挨着复制按钮排在回复底部那一排。刻意<b>复用</b>
   .message-retry-button 的外观——它跟"回复失败"那个重跑对用户就是同一个动作。

   inline-flex + 负 margin：复制按钮有 44px 的点击高度和 7px 上边距，直接并排会错位；
   这里把它拉回同一条基线上。

   <b>不跟着 hover 隐藏</b>：复制按钮平时是透明的（.msg.assistant:hover 才显形），
   那对"顺手复制一下"合适；但这颗是失败之后唯一的补救入口，藏起来等于没有。 */
.task-card-retry {
    display: inline-flex;
    align-items: center;
    /* 跟复制按钮同高同边距，两颗才在同一条基线上——复制按钮 44px 的点击高度是为触摸留的，
       这里不跟着它就会矮一截、看着像掉下去了。 */
    min-height: 44px;
    margin-top: 7px;
}

.message-retry-button {
    flex: none;
    width: 26px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid var(--border-strong);
    border-radius: 50%;
    background: transparent;
    color: var(--text-faint);
    cursor: pointer;
    transition: background var(--dur-1) var(--ease-soft), color var(--dur-1) var(--ease-soft);
}
.message-retry-button svg {
    width: 14px;
    height: 14px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.message-retry-button:hover:not(:disabled) { background: var(--surface-2); color: var(--text-primary); }
.message-retry-button:disabled { cursor: default; color: var(--text-faint); }
.message-retry-button.is-busy svg { animation: retry-spin 900ms linear infinite; }
@keyframes retry-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .message-retry-button.is-busy svg { animation: none; }
    .message-retry-button.is-busy { opacity: .55; }
}

/* ─── M13：结构化反问卡片（单题分步）───
   宽而扁，一次只显示一题。这是这套界面里唯一有明确边框和白底的卡片——它要求用户停下来
   操作，跟任务卡片那种"只是汇报"的性质不同，所以刻意跟弱化卡片感那条线区分开。 */
.input-card {
    margin: 12px 0 4px;
    padding: 16px 18px;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface-1);
    outline: none;
}
/* 用 :focus 而不是只用 :focus-visible：卡片是不是有焦点，决定了 ← → / Enter 响不响应，
   这已经不是"顺手提示一下键盘位置"而是功能状态本身，必须一直看得见。脚本主动送进来的焦点
   （卡片一出现就接管）在鼠标操作之后往往不匹配 :focus-visible，光靠它这条线会是暗的。 */
.input-card:focus { border-color: var(--accent-tint-border); }

.input-card-head { display: flex; align-items: center; gap: 14px; }
.input-card-title { flex: none; font-size: 14px; color: var(--text-primary); }
/* 细进度条：它只回答"走到第几题了"，不该抢视线 */
.input-card-progress {
    flex: 1 1 auto;
    height: 3px;
    min-width: 40px;
    border-radius: 999px;
    background: var(--border);
    overflow: hidden;
}
.input-card-progress-bar {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--accent);
    transition: width var(--dur-2) var(--ease-soft);
}
.input-card-step { flex: none; font-size: 12.5px; color: var(--text-faint); }

.input-card-body { margin-top: 16px; }
.input-question-title { margin: 0 0 12px; font-size: 15px; color: var(--text-primary); }
.input-question-note {
    margin: -6px 0 12px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-faint);
}
.input-question-note:empty { display: none; }

/* 横向三张：等宽平分，窄屏也不换成竖排堆叠——保持"一次一题、左右切换"的模型 */
.input-option-list { display: flex; gap: 10px; align-items: stretch; }
.input-option {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    align-items: flex-start;
    gap: 9px;
    padding: 12px 13px;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--text-secondary);
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: border-color var(--dur-1) var(--ease-soft), background var(--dur-1) var(--ease-soft);
}
.input-option:hover { border-color: var(--border-strong); }
/* 当前项：薄荷色描边 + 实心圆点。只有这一种高亮，不再区分"光标"和"已选"——
   一次一题的流程里，光标所在就是这一题的答案，两个状态没有分开的必要。 */
.input-option.is-cursor {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 5%, transparent);
    color: var(--text-primary);
}
.input-option-dot {
    flex: none;
    width: 15px;
    height: 15px;
    margin-top: 2px;
    border: 1.5px solid var(--border-strong);
    border-radius: 50%;
    transition: border-color var(--dur-1) var(--ease-soft);
}
.input-option.is-cursor .input-option-dot {
    border-color: var(--accent);
    /* 实心内点用 inset 阴影画，省一层 DOM */
    box-shadow: inset 0 0 0 3.5px var(--accent);
}
.input-option-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.input-option-label { font-size: 13.5px; overflow-wrap: anywhere; }
.input-option-hint { font-size: 11.5px; color: var(--text-faint); overflow-wrap: anywhere; }
.input-option-hint:empty { display: none; }
/* 推荐是个小标签，不上强色——它是建议，不是替用户做的决定 */
.input-option-tag {
    flex: none;
    margin-left: auto;
    align-self: flex-start;
    padding: 1px 6px;
    border-radius: 4px;
    background: var(--accent-tint);
    color: var(--accent);
    font-size: 10.5px;
    line-height: 1.6;
}

.input-question-custom {
    margin-top: 10px;
    width: min(100%, 360px);
    padding: 7px 11px;
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text-primary);
    font: inherit;
    font-size: 13px;
}
.input-question-custom:focus { outline: none; }

.input-card-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 16px;
}
.input-card-hint { margin: 0; font-size: 11.5px; color: var(--text-faint); }
.input-card-next {
    flex: none;
    padding: 7px 16px;
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--accent);
    font-size: 13px;
    cursor: pointer;
    transition: background var(--dur-1) var(--ease-soft);
}
.input-card-next:hover:not(:disabled) { background: var(--accent-tint); }
.input-card-next:disabled { opacity: .45; cursor: default; }

/* 三个区块都是 flex 容器：author 的 display 会压过浏览器默认的 [hidden]{display:none}，
   hidden 属性单独不起作用——2026-09-09 实测未提交的卡片下面一直露着"已收到你的反馈"和「修改」，
   已提交的卡片头部进度条和底部按钮也还在。只在卡片范围内补这一条，不动全局 hidden。 */
.input-card-head[hidden],
.input-card-foot[hidden],
.input-card-summary[hidden] { display: none; }
/* 提交在途：选项和输入框一起锁住，防双击/连按回车把同一份答案提交两遍 */
.input-card.is-pending .input-option { cursor: default; opacity: .7; }
.input-card.is-pending .input-question-custom { opacity: .7; }

/* 折叠态：卡片收成几行"问题：答案"。它是只读记录，没有改回去的入口——
   已确认的答案是记录不是草稿，要改需求走输入框，旧记录留着 */
.input-card.is-submitted { padding: 12px 14px; background: transparent; }
.input-card-summary { display: flex; align-items: flex-start; gap: 12px; }
.input-summary-body { flex: 1 1 auto; min-width: 0; }
/* 折叠态是"AI 确认收到"，不是"用户又说了一句话"——作答的用户气泡已经不再渲染，
   这一行就是它在线程里唯一的痕迹，所以措辞必须站在 AI 这一侧。 */
.input-summary-title { margin: 0 0 6px; font-size: 12.5px; color: var(--text-faint); }
.input-summary-items { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.input-summary-item { display: flex; gap: 8px; font-size: 12.5px; line-height: 1.6; }
.input-summary-question { color: var(--text-faint); flex: none; max-width: 45%; overflow-wrap: anywhere; }
.input-summary-answer { color: var(--text-primary); min-width: 0; overflow-wrap: anywhere; }
/* 「继续执行」：答案存好了但下一轮没发出去（断网/刷新/发送失败）时的恢复入口。
   长相跟「确认并继续」同一家族（薄荷描边），只是更小——它是同一件事的后半截，不是新动作 */
.input-card-resume {
    flex: none;
    padding: 5px 12px;
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--accent);
    font: inherit;
    font-size: 12.5px;
    cursor: pointer;
    transition: background var(--dur-1) var(--ease-soft);
}
.input-card-resume:hover:not(:disabled) { background: var(--accent-tint); }
.input-card-resume:disabled { opacity: .45; cursor: default; }
.input-card-resume[hidden] { display: none; }

/* 窄屏：选项仍然横排，只是压缩内边距和字号——不重新堆叠成竖排，
   否则"一次一题、左右切换"这套模型在手机上就散了。 */
@media (max-width: 720px) {
    .input-card { padding: 13px 14px; }
    .input-option { padding: 9px 9px; gap: 7px; }
    .input-option-label { font-size: 12.5px; }
    .input-option-hint { font-size: 10.5px; }
    .input-option-dot { width: 13px; height: 13px; }
    .input-option-tag { display: none; } /* 宽度不够时先牺牲它，选项名不能被挤没 */
}

/* ─── 超级沙盒面板（2026-09-11 第三版：用户拍板"就是一堆代码在沙盒里跑，结果放正文里"；2026-09-14 第四版：不数次数） ───
   一个在干活的沙盒：头部一句话是模型"正在做什么"；舞台只放当前这一段（代码暗一档、输出往上滚、跑着时末尾有光标），
   换做法时旧的淡出新的飞入；落了文件收成结果区（文件标签，点了在画布打开）；完整流水折在「查看过程」里。
   没有「第 N 次」、没有阶段轨、没有百分比——结果在回答正文和资料栏里。 */
.sandbox-panel {
    margin: 2px 0 14px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--surface-1) 86%, var(--bg));
    overflow: hidden;
    font-size: 13px;
    color: var(--text-secondary);
}
.sandbox-head { display: flex; align-items: center; gap: 10px; padding: 7px 12px; border-bottom: 1px solid var(--border); min-width: 0; }
.sandbox-name { flex: 0 0 auto; color: var(--text-primary); font-weight: 500; }
.sandbox-status { flex: 0 0 auto; padding: 1px 8px; border-radius: var(--radius-pill); background: var(--surface-2); color: var(--text-muted); font-size: 12px; transition: background var(--dur-2) var(--ease-soft), color var(--dur-2) var(--ease-soft); }
.sandbox-panel.is-running .sandbox-status, .sandbox-panel.is-done .sandbox-status { background: var(--accent-tint); color: var(--accent); }
.sandbox-panel.is-partial .sandbox-status { background: var(--caution-dim); color: var(--caution); }
.sandbox-panel.is-failed .sandbox-status { background: var(--danger-dim); color: var(--danger-text); }
/* 正在做什么：模型自己那句话，跟着每一段换；一行放不下就省略号，鼠标放上去看全 */
.sandbox-doing { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; color: var(--text-secondary); }
.sandbox-doing:empty { display: none; }
.sandbox-elapsed { margin-left: auto; flex: 0 0 auto; font-family: var(--font-mono); font-size: 12px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.sandbox-panel.is-running .sandbox-elapsed { color: var(--accent); }
.sandbox-stop { flex: 0 0 auto; border: 1px solid var(--danger-border); background: transparent; color: var(--danger-text); font: inherit; font-size: 12px; padding: 2px 10px; border-radius: var(--radius-pill); cursor: pointer; }
.sandbox-stop:hover { background: var(--danger-dim); }
.sandbox-stop:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sandbox-stop[hidden] { display: none; }
/* 舞台：当前这一段 */
.sandbox-stage {
    max-height: 260px;
    overflow-y: auto;
    background: color-mix(in srgb, var(--bg) 72%, var(--surface-1));
    scrollbar-width: thin;
}
.sandbox-panel.is-settled.has-files .sandbox-stage { display: none; } /* 落了文件：结果替掉过程 */
.sandbox-console { padding: 8px 12px 9px; font-family: var(--font-mono); font-size: 12px; line-height: 1.65; white-space: pre-wrap; word-break: break-all; }
.sandbox-line { display: block; color: var(--text-secondary); animation: sandbox-line-in var(--dur-2) var(--ease); }
.sandbox-line.is-past { opacity: .28; transition: opacity 220ms var(--ease-soft); }
.sandbox-line.is-turn { color: var(--text-primary); margin: 2px 0 4px; }
.sandbox-line.is-reason { color: var(--text-primary); margin-bottom: 4px; }
.sandbox-line.is-code { color: var(--text-faint); }
.sandbox-line .tok-kw { color: var(--text-primary); }
.sandbox-line .tok-fn { color: var(--text-primary); }
.sandbox-line .tok-str { color: var(--text-secondary); }
.sandbox-line .tok-num { color: var(--text-secondary); }
.sandbox-line .tok-cm { color: var(--text-faint); font-style: italic; }
.sandbox-line.is-cmd { color: var(--text-primary); }
.sandbox-line.is-cmd::before { content: '$ '; color: var(--accent); }
.sandbox-line.is-out::before { content: '  '; }
.sandbox-line.is-ok { color: var(--accent); }
.sandbox-line.is-fail { color: var(--danger-text); }
.sandbox-line.is-warn { color: var(--caution); }
.sandbox-line.is-mark { margin-top: 6px; color: var(--text-faint); }
.sandbox-line.is-mark:first-child { margin-top: 0; }
/* 光标只挂在舞台最后一行、只在跑着时出现；行是追加的不会被重建，动画不会被打断 */
.sandbox-panel.is-running .sandbox-stage .sandbox-line:last-child::after { content: '▮'; margin-left: 2px; color: var(--accent); animation: sandbox-blink 1.1s steps(2, start) infinite; }
/* 结果区：文件标签 + 一句话 */
.sandbox-result { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 10px 12px; border-top: 1px solid var(--border); }
.sandbox-result[hidden] { display: none; }
.sandbox-panel.is-settled.has-files .sandbox-result { border-top: 0; }
.sandbox-files { display: flex; flex-wrap: wrap; gap: 8px; }
.sandbox-file { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--border-strong); background: var(--surface-2); color: var(--text-primary); font: inherit; font-size: 12.5px; padding: 4px 10px 4px 9px; border-radius: var(--radius-pill); cursor: pointer; max-width: 100%; }
.sandbox-file::before { content: '▤'; color: var(--accent); font-size: 11px; }
.sandbox-file.is-image::before { content: '▧'; }
.sandbox-file:hover { border-color: var(--accent); }
.sandbox-file:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sandbox-result-note { margin: 0; font-size: 12.5px; color: var(--text-muted); }
.sandbox-result-note.is-fail { color: var(--danger-text); }
/* 沙盒画的图直接显示在回答里（2026-10-14）：正文后面往下排，每份成果只放最新版，点开用图片查看器放大 */
.message-figures { display: grid; gap: 12px; margin-top: 14px; }
.message-figure { display: grid; gap: 6px; justify-items: start; width: fit-content; max-width: 100%; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: zoom-in; }
.message-figure img { display: block; max-width: 100%; max-height: min(480px, 62vh); border-radius: var(--radius-sm); box-shadow: 0 0 0 1px var(--border-strong); }
.message-figure:hover img { box-shadow: 0 0 0 1px var(--accent); }
.message-figure:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--radius-sm); }
.message-figure-name { font-size: 12px; color: var(--text-faint); }
/* 回答下方的文件卡片（2026-10-15）：这条回答在画布打开过 2 份及以上时出现，点哪张画布换成哪份。
   放在读完回答的地方——第一版做在时间线里，用户说要往回翻、也看不出能点。
   「画布中」是状态，所以那张卡片的描边和小点用 --accent；其余全是中性色。
   一行排不下就左右滑（overflow-x 会把纵向也算成 auto，这里面没有下拉菜单，不受那条坑影响） */
.canvas-file-strip { margin-top: 14px; }
.canvas-file-strip-head { margin: 0 0 8px; font-size: 12px; color: var(--text-muted); }
.canvas-file-row { display: flex; gap: 10px; overflow-x: auto; padding: 2px 2px 6px; scrollbar-width: thin; }
.canvas-file-card {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: 6px;
    width: 132px;
    padding: 6px;
    border: 0;
    border-radius: var(--radius-sm);
    background: var(--surface-1);
    box-shadow: 0 0 0 1px var(--border);
    color: var(--text-secondary);
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: box-shadow 140ms ease, background 140ms ease;
}
.canvas-file-card:hover { background: var(--surface-2); box-shadow: 0 0 0 1px var(--border-strong); }
.canvas-file-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.canvas-file-card.is-on-canvas { box-shadow: 0 0 0 1.5px var(--accent); }
.canvas-file-thumb {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 84px;
    border-radius: calc(var(--radius-sm) - 2px);
    overflow: hidden;
    background: var(--surface-2);
}
/* 图纸整页缩进框里看得出是哪张图，裁中间那块看不出（跟时间线里的图纸页缩略图同一条） */
.canvas-file-thumb img { display: block; width: 100%; height: 100%; object-fit: contain; }
.canvas-file-type { font-size: 12px; font-weight: 600; letter-spacing: .04em; color: var(--text-muted); }
.canvas-file-name {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    font-size: 12px;
    line-height: 1.45;
    /* 按字符断：图号「S-203@-0.130~6.780m…」只有连字符一个断点，按词断第一行就只剩「S-」（截图看出来的） */
    word-break: break-all;
}
.canvas-file-current { display: none; align-items: center; gap: 5px; font-size: 11px; color: var(--text-secondary); }
.canvas-file-current::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.canvas-file-card.is-on-canvas .canvas-file-current { display: inline-flex; }
/* 完整流水：给我们自己看，默认折着 */
.sandbox-transcript { border-top: 1px solid var(--border); }
.sandbox-transcript > summary { cursor: pointer; padding: 5px 12px; font-size: 12px; color: var(--text-faint); list-style: none; user-select: none; }
.sandbox-transcript > summary::-webkit-details-marker { display: none; }
.sandbox-transcript > summary::before { content: '▸ '; }
.sandbox-transcript[open] > summary::before { content: '▾ '; }
.sandbox-transcript > summary:hover { color: var(--text-secondary); }
.sandbox-transcript > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.sandbox-transcript .sandbox-console { max-height: 360px; overflow-y: auto; background: color-mix(in srgb, var(--bg) 72%, var(--surface-1)); scrollbar-width: thin; }
@keyframes sandbox-line-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes sandbox-blink { to { visibility: hidden; } }
@media (prefers-reduced-motion: reduce) {
    .sandbox-panel.is-running .sandbox-stage .sandbox-line:last-child::after,
    .sandbox-line { animation: none; }
    .sandbox-line.is-past { transition: none; }
}

/* 超级沙盒设置（2026-09-23）：跟其它面板同一套行——开关一行，运行环境一行（● 状态 · 下一步 + 技术详情小字 + 小号刷新）。
   状态颜色只表示好坏：可用=薄荷点，要处理=琥珀点+琥珀字，关着/没启用=灰点。 */
.sandbox-access .collab-switch:disabled { opacity: .45; cursor: not-allowed; }
.sandbox-env .settings-inline-button { flex: 0 0 auto; margin-bottom: 0; }
.sandbox-env-state { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 0 6px; font-size: 12px; line-height: 1.6; color: var(--text-muted); }
.sandbox-env-dot { width: 6px; height: 6px; flex: 0 0 auto; border-radius: 50%; background: var(--text-faint); }
.sandbox-env-state #sandbox-status { color: var(--text-secondary); font-weight: 500; }
.sandbox-env-sep { color: var(--text-faint); }
/* 可用时没有下一步要说：提示空着，连同前面的「·」一起不画 */
#sandbox-status-hint:empty, .sandbox-env-sep:has(+ #sandbox-status-hint:empty) { display: none; }
.sandbox-env[data-state="ok"] .sandbox-env-dot { background: var(--accent); }
.sandbox-env[data-state="warn"] .sandbox-env-dot { background: var(--caution); }
.sandbox-env[data-state="warn"] .sandbox-env-state #sandbox-status { color: var(--caution); }
.sandbox-env-detail { font-family: var(--font-mono); font-size: 11.5px; white-space: pre-wrap; overflow-wrap: anywhere; }
.sandbox-env-detail:empty { display: none; }

/* 协同选择面板：随内容展开，长名称完整展示，不受弹窗边缘裁切。 */
#collab-picker-label { font-size: 12px; color: var(--text-secondary); }
.collab-picker { min-width: 0; border: 1px solid var(--border-strong); border-radius: 12px; background: var(--surface-2); overflow: hidden; }
.collab-picker summary { display: flex; align-items: center; gap: 16px; justify-content: space-between; padding: 14px 16px; cursor: pointer; list-style: none; font-size: 13px; color: var(--text-primary); }
.collab-picker summary::-webkit-details-marker { display: none; }
#collab-picker-value { overflow-wrap: anywhere; min-width: 0; }
.collab-picker summary svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--text-secondary); }
.collab-picker[open] { border-color: var(--focus-border); }
.collab-picker[open] summary svg { transform: rotate(180deg); }
.collab-picker-options { border-top: 1px solid var(--border); padding: 6px; max-height: 280px; overflow-y: auto; }
.collab-picker-option { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 5px 14px; width: 100%; padding: 12px 10px; border: 0; border-radius: 7px; background: transparent; text-align: left; font: inherit; font-size: 13px; color: var(--text-primary); cursor: pointer; }
.collab-picker-option > span:first-child, .collab-picker-option small { overflow-wrap: anywhere; }
.collab-picker-option small { grid-row: 2; grid-column: 1; font-size: 11px; line-height: 1.7; color: var(--text-secondary); }
.collab-picker-mark { grid-column: 2; grid-row: 1 / 3; align-self: center; font-size: 11px; color: var(--accent); }
.collab-picker-option:hover, .collab-picker-option[aria-pressed="true"] { background: var(--surface-3); }
.collab-picker-option:disabled { opacity: .5; cursor: not-allowed; }
.collab-picker summary:focus-visible, .collab-picker-option:focus-visible { outline: 2px solid var(--focus-border); outline-offset: -3px; }
/* 工序配置：项目名称、文件信息与识别提示分层。 */
.procedure-row { align-items: flex-start; padding: 20px 0; gap: 20px; }
.procedure-row .procedure-line { display: grid; gap: 8px; }
.procedure-row .notify-project-name { white-space: normal; overflow-wrap: anywhere; font-size: 14px; font-weight: 500; }
.procedure-row .procedure-meta { white-space: normal; overflow: visible; overflow-wrap: anywhere; line-height: 1.8; }
/* 第一行：项目名 + 《文件》；文件名长就折到下一行，不截断 */
.procedure-row .procedure-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px; min-width: 0; }
.procedure-row .procedure-title .procedure-file { flex: 0 1 auto; }
/* 第二行：N 道工序　M 条要点　上传于 日期——不用「·」，靠间距分开 */
.procedure-row .procedure-stats { display: flex; flex-wrap: wrap; gap: 0 16px; }
.procedure-row .procedure-note { line-height: 1.8; margin-top: 10px; overflow-wrap: anywhere; }
.procedure-row .notify-project-actions { flex-wrap: wrap; justify-content: flex-end; }
@media (max-width:720px) { .procedure-row { flex-direction: column; gap: 12px; } .procedure-row .notify-project-actions { justify-content: flex-start; } }

#collab-picker-label { align-self: start; padding-top: 14px; }

/* 复核选择菜单悬浮显示，不参与设置页排版。 */
.collab-picker { overflow: visible; }
.collab-picker-options { position: fixed; z-index: 10020; box-sizing: border-box; border: 1px solid var(--border-strong); border-radius: 12px; background: var(--surface-1); padding: 6px; box-shadow: 0 16px 48px rgba(0,0,0,.24), 0 3px 10px rgba(0,0,0,.12); overscroll-behavior: contain; }
.collab-picker:not([open]) .collab-picker-options { display: none; }

.collab-picker-options { margin: 0; inset: auto; }

/* 删除确认：双层圆形底座与抬起的桶盖，只用于删除操作。 */
.confirm-dialog-mark .confirm-symbol-delete { display: none; }
.is-delete-confirm .confirm-symbol-default { display: none; }
.is-delete-confirm .confirm-dialog-mark { width: 62px; height: 62px; border-radius: 50%; position: relative; margin: 2px 0 4px; background: linear-gradient(145deg, var(--danger-dim), var(--surface-float)); border-color: var(--danger-border); box-shadow: inset 0 1px 0 var(--edge-light), 0 4px 14px var(--danger-dim); }
.is-delete-confirm .confirm-dialog-mark::before { content: ''; position: absolute; inset: 5px; border: 1px solid var(--danger-border); border-radius: 50%; opacity: .45; pointer-events: none; }
.is-delete-confirm .confirm-dialog-mark .confirm-symbol-delete { display: block; width: 36px; height: 36px; overflow: visible; fill: none; stroke: currentColor; stroke-width: 1.65; stroke-linecap: round; stroke-linejoin: round; }
.confirm-symbol-delete .delete-body-fill { fill: var(--danger-dim); stroke: none; }
.confirm-symbol-delete .delete-lid { transform: rotate(-12deg) translateY(-1px); transform-origin: 20px 12px; }


/* 智管联动：用连接线表达项目对应关系，操作与凭据各归其位。 */
.settings-panel[data-panel="zhiguan"] .settings-panel-title { margin-bottom: 24px; }
.settings-panel[data-panel="zhiguan"] .settings-group-title { margin: 0; padding-bottom: 12px; border-bottom: 1px solid var(--border); }
.zhiguan-status { padding: 12px 14px; background: var(--surface-2); border-radius: 8px; }
.zhiguan-row { display: grid; grid-template-columns: 36px minmax(0, 1fr); gap: 12px 16px; align-items: start; padding: 24px 0; }
.zhiguan-mark { display: grid; place-items: center; width: 36px; height: 36px; border: 1px solid var(--border-strong); border-radius: 11px; background: var(--surface-2); color: var(--text-muted); box-sizing: border-box; }
.zhiguan-mark svg { width: 21px; height: 21px; }
.zhiguan-row.is-bound .zhiguan-mark { color: var(--accent); background: var(--accent-dim); border-color: var(--accent-tint-border); }
.zhiguan-heading { display: flex; align-items: center; justify-content: flex-start; flex-wrap: wrap; gap: 10px 12px; min-height: 36px; }
.zhiguan-row .notify-project-name { font-size: 13px; font-weight: 600; line-height: 1.65; }
.zhiguan-badge { flex-shrink: 0; display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px; border-radius: 6px; font-size: 13px; line-height: 1.6; color: var(--text-muted); background: var(--surface-2); }
.zhiguan-row.is-bound .zhiguan-badge { color: var(--accent); background: var(--accent-dim); }
.zhiguan-badge::before { content: ''; width: 4px; height: 4px; border: 1px solid currentColor; border-radius: 50%; }
.zhiguan-row.is-bound .zhiguan-badge::before { background: currentColor; }
.zhiguan-metadata { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 10px 20px; margin: 8px 0 4px; padding: 14px 16px; border-radius: 8px; background: var(--surface-2); line-height: 1.65; }
.zhiguan-metadata dt { color: var(--text-muted); }
.zhiguan-metadata dd { margin: 0; color: var(--text-secondary); overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
.zhiguan-row .procedure-note { margin: 8px 0 0; font-size: 13px; }
/* 2026-09-23 用户要求：按钮那一排顶到行首，跟左边的图标方块对齐（原来缩在项目名下面） */
.zhiguan-row .notify-project-actions { grid-column: 1 / -1; display: flex; justify-content: flex-start; flex-wrap: wrap; gap: 8px; padding: 0; }
.zhiguan-row .settings-inline-button { min-height: 34px; padding: 6px 12px; border-radius: 8px; font-size: 13px; transition: background 140ms ease, border-color 140ms ease, color 140ms ease; }
.zhiguan-row .zhiguan-test, .zhiguan-row:not(.is-bound) .zhiguan-bind { border-color: var(--border-strong); background: var(--surface-2); color: var(--text-primary); }
.zhiguan-row .zhiguan-bind { display: inline-flex; align-items: center; gap: 8px; }
.zhiguan-row .zhiguan-unbind { margin-left: auto; border-color: transparent; background: transparent; color: var(--text-muted); }
.zhiguan-row .settings-inline-button:hover:not(:disabled) { background: var(--surface-2-hover); border-color: var(--border-strong); }
.zhiguan-row .zhiguan-unbind:hover:not(:disabled) { background: var(--danger-dim); border-color: transparent; color: var(--danger-text); }
.zhiguan-row .settings-inline-button:disabled { opacity: .6; cursor: wait; }
.zhiguan-row .is-live { padding: 9px 12px; border-left: 2px solid var(--border-strong); background: var(--surface-2); border-radius: 0 6px 6px 0; }
.zhiguan-row .is-live.is-error { color: var(--danger-text); border-color: var(--danger-border); }
.zhiguan-row > .procedure-note.is-live { grid-column: 1 / -1; margin: 0; }
.zhiguan-row :is(button, summary):focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
@media (max-width: 720px) {
    .zhiguan-row { grid-template-columns: 30px minmax(0, 1fr); gap: 10px 12px; padding: 20px 0; }
    .zhiguan-mark { width: 30px; height: 30px; border-radius: 9px; }
    .zhiguan-mark svg { width: 18px; height: 18px; }
    .zhiguan-heading { min-height: 30px; flex-wrap: wrap; gap: 6px 10px; }
    .zhiguan-row .settings-inline-button { min-height: 40px; padding: 7px 10px; }
    .zhiguan-row .notify-project-actions { column-gap: 5px; }
}
@media (prefers-reduced-motion: reduce) {
    .zhiguan-row .settings-inline-button { transition: none; }
}

.zhiguan-view-details { border: 0; background: transparent; padding: 6px 0; color: var(--text-secondary); font: inherit; font-size: 13px; cursor: pointer; text-underline-offset: 4px; }
.zhiguan-view-details:hover { color: var(--text-primary); text-decoration: underline; }
.zhiguan-detail-dialog { box-sizing: border-box; width: min(460px, calc(100vw - 32px)); max-height: calc(100dvh - 48px); margin: auto; padding: 22px; overflow-y: auto; border: 1px solid var(--border-strong); border-radius: 14px; background: var(--surface-1); color: var(--text-primary); font-family: var(--font-sans); font-size: 13px; box-shadow: 0 20px 70px #0004; }
.zhiguan-detail-dialog::backdrop { background: #0006; backdrop-filter: blur(4px); }
.zhiguan-detail-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding-bottom: 16px; border-bottom: 1px solid var(--border); }
.zhiguan-detail-heading h4 { margin: 0; font-size: 13px; font-weight: 600; }
.zhiguan-detail-close { padding: 6px 10px; border: 1px solid var(--border-strong); border-radius: 7px; background: var(--surface-2); color: var(--text-primary); font: inherit; cursor: pointer; }
.zhiguan-detail-close:hover { background: var(--surface-2-hover); }
.zhiguan-detail-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.zhiguan-detail-dialog .zhiguan-metadata { margin: 18px 0 0; padding: 0; gap: 16px; background: transparent; font-size: 13px; }
@media (max-width: 720px) { .zhiguan-view-details, .zhiguan-detail-close { min-height: 40px; } }
/* 智管「接入 / 更换」弹窗（2026-09-23 替掉下拉菜单）：外框借接入详情那个弹窗；上半截智管项目单选清单（管理员才有），
   下半截验证码；选中那一行用状态色的淡底，当前那一行不可点、右边写「当前」。 */
.zhiguan-bind-dialog { width: min(480px, calc(100vw - 32px)); }
.zhiguan-bind-form { margin-top: 16px; }
.zhiguan-bind-dialog .zhiguan-detail-heading { min-height: 28px; }
.zhiguan-bind-dialog .zhiguan-bind-input:not(.zhiguan-bind-search):not(:placeholder-shown) { font-family: var(--font-mono); }
.zhiguan-bind-input { box-sizing: border-box; width: 100%; height: 34px; padding: 0 12px; border: 1px solid var(--border-strong); border-radius: 8px; background: var(--surface-2); color: var(--text-primary); font-size: 13px; outline: none; transition: border-color 150ms ease; }
.zhiguan-bind-input::placeholder { color: var(--text-faint); }
.zhiguan-bind-input:focus { border-color: var(--focus-border); }
.zhiguan-bind-search { margin-bottom: 8px; font-family: inherit; }
.zhiguan-bind-search[hidden] { display: none; }
.zhiguan-bind-list { display: grid; gap: 2px; max-height: min(320px, 45vh); overflow-y: auto; margin: 0 -8px; padding: 0 8px; scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
.zhiguan-bind-option { display: flex; align-items: center; gap: 10px; min-height: 40px; padding: 8px 10px; border-radius: 8px; cursor: pointer; transition: background 140ms ease; }
.zhiguan-bind-option[hidden] { display: none; }
.zhiguan-bind-option:hover { background: var(--surface-2); }
.zhiguan-bind-option:has(input:checked:not(:disabled)) { background: var(--accent-dim); }
.zhiguan-bind-option.is-current { cursor: default; }
.zhiguan-bind-option input { flex: 0 0 auto; margin: 0; accent-color: var(--accent); }
.zhiguan-bind-option input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.zhiguan-bind-name { flex: 1 1 auto; min-width: 0; color: var(--text-primary); font-size: 13px; line-height: 1.5; overflow-wrap: anywhere; }
.zhiguan-bind-meta { flex: 0 1 auto; max-width: 45%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-faint); font-size: 12px; }
.zhiguan-bind-option.is-current .zhiguan-bind-meta { color: var(--accent); }
.zhiguan-bind-empty { margin: 0; padding: 10px; color: var(--text-faint); font-size: 12.5px; line-height: 1.6; }
.zhiguan-bind-empty[hidden] { display: none; }
.zhiguan-bind-code { display: grid; gap: 8px; }
.zhiguan-bind-code.has-divider { margin-top: 14px; padding-top: 16px; border-top: 1px solid var(--border); }
.zhiguan-bind-code label { color: var(--text-secondary); font-size: 12.5px; }
.zhiguan-bind-hint { margin: 0; color: var(--text-faint); font-size: 12px; }
.zhiguan-bind-error { margin: 12px 0 0; color: var(--danger-text); font-size: 12.5px; line-height: 1.6; }
.zhiguan-bind-error:empty { display: none; }
.zhiguan-bind-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; }
