/* ── 配色 ──
   浅色是默认档(裸 :root), 深色走 prefers-color-scheme 与 [data-theme] 两条路。
   裸 :root 必须放完整的一档: 用户没显式选过时 data-theme 是不写的(见 theme.js),
   这时只有媒体查询能区分明暗 —— 任何颜色若只定义在媒体查询里, 未表态的用户就会
   拿到一档的底色配另一档的字色。

   动作色两档不同值: 青绿 #1d6b5c 在深底上偏暗、读不出层次, 深色档换成 #4FBFA3。
   登录页左栏是品牌面, 两档都保持海军蓝 —— 它上面压的是钢构线框图, 换成浅色
   那张图就没了依托。 */
:root {
  --navy: #153b50;      /* 品牌**面**: 登录左栏底色, 两档一致 */
  --brand: #153b50;     /* 品牌**字**: 深色档必须翻过来, 否则近黑色压在深底上直接消失 */
  --teal: #1d6b5c;      /* 动作色: 主按钮 */
  --on-teal: #fff;      /* 动作色上的字 */
  --mint: #e9f2f0;      /* 浅底: 徽章、选中背景 */
  --ink: #0f172a;
  /* 原值 #64748b 在图纸视图那块灰底(#dfe4e7)上只有 3.71:1 —— 而空状态说明正好
     压在那里。#556072 在四种浅底上都过 4.9。 */
  --dim: #556072;
  /* 更弱一档: 时间戳、次要说明。原值 #94a3b8 对白底只有 2.56:1 —— "更弱"
     不等于"可以读不清", 通知面板整列时间戳都用它。 */
  --dim2: #5F6B7F;
  --line: #e2e8f0;
  --line2: #f1f5f9;     /* 更浅的分隔: 表格行线 */
  --line3: #cbd5e1;     /* 更重的边: 图片边框、输出块 */
  --bg: #f1f5f9;
  --card: #fff;
  --field: #fff;        /* 输入框底 */
  --hov: #f8fafc;       /* 悬停面 */
  --evbg: #fbfcfd;      /* 事件行底 */
  --viewer: #dfe4e7;    /* 图纸视图底 */
  --warn: #b45309;  --warnbg: #fffbeb;
  --bad: #b91c1c;   --badbg: #fee2e2;
  --ok: #15803d;    --okbg: #dcfce7;
  --run: #1e40af;   --runbg: #dbeafe;
  --think: #a78bfa; --thinkbg: #faf8ff; --thinktx: #4c1d95;
  --tool: #38bdf8;  --toolbg: #f0f9ff;
  --tintteal: #f0f9f6;
  --estrow: #fffafa; --infrow: #fffdf7;
  --warnline: #f0d5a8; --warnline2: #e8d5b0;
  --shadow: rgba(0,0,0,.07); --shadow2: rgba(0,0,0,.13);
  --backdrop: rgba(15,23,42,.6);
  --sans: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", -apple-system, system-ui, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --teal: #4FBFA3; --on-teal: #06231C; --mint: rgba(79,191,163,.15);
    --brand: #8FD6C4;
    --ink: #E7EBF1; --dim: #9AA4B0; --dim2: #7C8794;
    --line: rgba(255,255,255,.09); --line2: rgba(255,255,255,.05);
    --line3: rgba(255,255,255,.17);
    --bg: #0D1015; --card: #161A21; --field: #11151B; --hov: rgba(255,255,255,.04);
    --evbg: rgba(255,255,255,.028); --viewer: #0A0D11;
    --warn: #F0B429; --warnbg: rgba(240,180,41,.12);
    --bad: #FF6F5E;  --badbg: rgba(255,111,94,.13);
    --ok: #5FD08C;   --okbg: rgba(95,208,140,.14);
    --run: #7FB3F0;  --runbg: rgba(127,179,240,.14);
    --think: #B79BFF; --thinkbg: rgba(183,155,255,.10); --thinktx: #D9CCFF;
    --tool: #5FC7F5;  --toolbg: rgba(95,199,245,.10);
    --tintteal: rgba(79,191,163,.10);
    --estrow: rgba(255,111,94,.06); --infrow: rgba(240,180,41,.06);
    --warnline: rgba(240,180,41,.32); --warnline2: rgba(240,180,41,.2);
    --shadow: rgba(0,0,0,.5); --shadow2: rgba(0,0,0,.6);
    --backdrop: rgba(0,0,0,.7);
  }
}
:root[data-theme="dark"] {
  --teal: #4FBFA3; --on-teal: #06231C; --mint: rgba(79,191,163,.15);
  --brand: #8FD6C4;
  --ink: #E7EBF1; --dim: #9AA4B0; --dim2: #7C8794;
  --line: rgba(255,255,255,.09); --line2: rgba(255,255,255,.05);
  --line3: rgba(255,255,255,.17);
  --bg: #0D1015; --card: #161A21; --field: #11151B; --hov: rgba(255,255,255,.04);
  --evbg: rgba(255,255,255,.028); --viewer: #0A0D11;
  --warn: #F0B429; --warnbg: rgba(240,180,41,.12);
  --bad: #FF6F5E;  --badbg: rgba(255,111,94,.13);
  --ok: #5FD08C;   --okbg: rgba(95,208,140,.14);
  --run: #7FB3F0;  --runbg: rgba(127,179,240,.14);
  --think: #B79BFF; --thinkbg: rgba(183,155,255,.10); --thinktx: #D9CCFF;
  --tool: #5FC7F5;  --toolbg: rgba(95,199,245,.10);
  --tintteal: rgba(79,191,163,.10);
  --estrow: rgba(255,111,94,.06); --infrow: rgba(240,180,41,.06);
  --warnline: rgba(240,180,41,.32); --warnline2: rgba(240,180,41,.2);
  --shadow: rgba(0,0,0,.5); --shadow2: rgba(0,0,0,.6);
  --backdrop: rgba(0,0,0,.7);
}
* { box-sizing: border-box; }
/* 任何写死的 display 都会盖掉 [hidden] 自带的 display:none —— 元素照样占位。
   踩过两次: 改密码弹窗铺在页面底部、隐藏的回放面板把卡片撑高 390px。
   这里一次性兜住, 以后 JS 里 el.hidden = true 就一定是真的隐藏。 */
[hidden] { display: none !important; }
body { margin: 0; font-family: var(--sans); background: var(--bg); color: var(--ink);
       font-size: 14px; -webkit-font-smoothing: antialiased; }
input, select, textarea, button { font-family: inherit; font-size: 14px; }
input, select, textarea {
  width: 100%; padding: 9px 11px; border: 1px solid var(--line); border-radius: 6px;
  background: var(--field); outline: none; font-weight: 600;
}
input:focus, select:focus, textarea:focus { border-color: var(--navy); }
textarea { font-family: var(--mono); font-size: 12px; font-weight: 400; resize: vertical; }
button { cursor: pointer; border-radius: 6px; font-weight: 800; border: 1px solid transparent; }
.primary { background: var(--teal); color: var(--on-teal); padding: 11px 14px; width: 100%; }
.primary:disabled { opacity: .45; cursor: default; }
.ghost { background: var(--card); border-color: var(--line); color: var(--ink); padding: 8px 12px; }
.ghost:hover { background: var(--hov); }
.small { padding: 6px 10px; font-size: 12.5px; }
.dim { color: var(--dim); font-weight: 600; font-size: 12.5px; }
.err { color: var(--bad); font-size: 12.5px; font-weight: 700; min-height: 18px; }
h1 { font-size: 24px; font-weight: 900; letter-spacing: -.02em; margin: 6px 0 4px; }
h3 { font-size: 13px; font-weight: 900; margin: 0 0 10px; letter-spacing: .01em; }

/* ── 登录 ── */
.login { display: grid; grid-template-columns: 1.05fr .95fr; min-height: 100vh; }
/* 左栏原本是一整块纯海军蓝, 空了一大半。铺一张等轴测钢构线框图: 图的上三分之一
   是空的, 正好让文字压在上面, 不用再加半透明遮罩去救可读性。
   contain 而非 cover —— 尺寸标注和轴线编号被裁掉就没意义了。 */
/* 面板固定是海军蓝, 所以字就固定是白 —— 不跟主题走。
   曾经把这里写成 var(--on-teal), 深色档下它是深绿, 整块标题直接消失。 */
.authL { background: var(--navy); color: #fff; padding: 52px 48px; position: relative;
         display: flex; flex-direction: column; justify-content: center; gap: 22px;
         overflow: hidden; }
/* 图当独立图层而不是 background: 只有这样才能在它上面单独盖一层渐变。
   直接铺背景时文字压在钢构线上, 要点那几行几乎读不出来 —— 轴线圈 A/B/C 正好撞在字上。
   渐变从左侧实心 navy 渐到透明, 文字所在的左半边是干净的, 右边保留图的细节。 */
.authL::before { content: ""; position: absolute; right: 0; bottom: 0;
  width: 80%; height: 86%;
  background: url("/assets/auth-side.webp") no-repeat right bottom / contain; }
.authL::after { content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, var(--navy) 30%, rgba(21,59,80,.72) 52%,
                              rgba(21,59,80,0) 78%); }
.authL > * { position: relative; z-index: 1; }
/* 窄屏时左栏被压扁, 图会挤成一条 —— 干脆撤掉, 文字本身站得住 */
@media (max-width: 900px) { .authL { background-image: none; } }
.brandBig { display: flex; align-items: center; gap: 12px; }
.brandBig .logo { width: 40px; height: 40px; color: var(--mint); flex: none; }
.brandBig .bn { font-size: 20px; font-weight: 900; letter-spacing: -.02em; }
/* 下面四个色都压在海军蓝品牌面上, 两档都不变 —— 那面本身不换色。 */
.brandBig .bt { font-size: 11px; color: #9FBACB; font-weight: 700; letter-spacing: .1em;
                margin-top: 2px; }
.authL h2 { font-size: 27px; font-weight: 900; letter-spacing: -.02em; line-height: 1.4;
            margin: 0; max-width: 16ch; }
.authL p { color: #A8BECC; font-weight: 600; line-height: 1.75; margin: 0; max-width: 44ch;
           font-size: 13px; }
.pts { margin: 2px 0 0; padding: 0; list-style: none; display: flex; flex-direction: column;
       gap: 9px; }
.pts li { font-size: 12.5px; color: #C6D8E3; font-weight: 600; display: flex; gap: 9px;
          line-height: 1.6; }
.pts li::before { content: ""; width: 5px; height: 5px; border-radius: 50%; flex: none;
                  background: #5FCFB4; margin-top: 8px; }
.authR { display: grid; place-items: center; padding: 30px; background: var(--card); }
.loginbox { width: min(340px, 100%); display: flex; flex-direction: column; gap: 10px; }
.loginbox h3 { font-size: 19px; font-weight: 900; margin: 0 0 2px; }
.langsw { display: flex; gap: 3px; background: var(--bg); padding: 3px; border-radius: 7px;
          align-self: flex-start; margin-bottom: 6px; }
.langsw button { background: none; border: none; padding: 5px 11px; font-size: 12px;
                 font-weight: 700; color: var(--dim); border-radius: 5px; }
.langsw button.on { background: var(--card); color: var(--ink); font-weight: 800;
                    box-shadow: 0 1px 2px var(--shadow); }
.orline { display: flex; align-items: center; gap: 10px; color: var(--dim); font-size: 11.5px;
          font-weight: 700; margin: 4px 0 2px; }
.orline::before, .orline::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.oauth { display: flex; flex-direction: column; gap: 7px; }
.oa { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%;
      padding: 10px; background: var(--card); border: 1px solid var(--line); color: var(--ink);
      font-weight: 700; font-size: 12.5px; }
.oa:hover { background: var(--bg); }
.oa[data-off] { opacity: .5; cursor: default; }
.oa[data-off]::after { content: attr(data-off); font-size: 10.5px; color: var(--dim);
                       font-weight: 700; }

/* ── 骨架 ── */
.shell { display: grid; grid-template-columns: 64px 1fr; min-height: 100vh; }
/* 左栏钉在视口里。不钉的话它是网格的一列, 高度跟着整页内容长 ——
   底部那个"收起侧栏"被顶到一千多像素以下, 不滚到页面最底下根本看不见,
   等于这个功能不存在。sticky 之后它永远在左下角, 也就是这类开关该在的地方。 */
.rail { background: var(--card); border-right: 1px solid var(--line); display: flex;
        flex-direction: column; align-items: center; gap: 12px; padding: 14px 0;
        position: sticky; top: 0; align-self: start; height: 100vh; }
/* 标志兼作"回官网"的入口。尺寸必须显式给: 原来是裸 <svg> 被 flex 拉到 63px,
   套进 <a> 之后 a 是行内元素, 里面的 svg 没了参照直接塌成 0 宽(试过一次)。
   顺手收到 40px —— 63px 那会儿它比下面的按钮大出一圈, 本来就偏大。 */
.rail .mark { display: block; width: 40px; height: 40px; flex: none; color: var(--ink);
              border-radius: 7px; }
.rail .mark svg { display: block; width: 100%; height: 100%; }
.rail .mark:hover { background: var(--mint); color: var(--brand); }
.railbtn { width: 40px; height: 40px; background: none; border: none; color: var(--dim);
           font-size: 17px; border-radius: 7px; }
.railbtn.on, .railbtn:hover { background: var(--mint); color: var(--brand); }
.col { display: flex; flex-direction: column; min-width: 0; }
/* 顶栏跟随滚动。余额、通知、账号这些是随时要用的, 一往下翻就没了很难受。
   sticky 而不是 fixed: fixed 会脱离文档流, 下面的内容要靠 padding 手动让位,
   而顶栏是换行的(窄屏上按钮会折成两排), 高度不固定, 手动让位一定对不齐。 */
.topbar { background: var(--card); border-bottom: 1px solid var(--line); padding: 11px 18px;
          display: flex; justify-content: space-between; align-items: center; gap: 14px;
          flex-wrap: wrap; position: sticky; top: 0; z-index: 40; }
.topbar .title { font-size: 17px; font-weight: 900; letter-spacing: -.01em; }
.topbar .sub { font-size: 12.5px; color: var(--dim); font-weight: 600; }
.topbar .right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tenant { background: var(--mint); color: var(--brand); padding: 7px 11px; border-radius: 6px;
          font-weight: 900; font-size: 12.5px; }
/* 用量数字很长(输入 262,628 缓存命中 11,407,319 …), 顶栏固定后它会把右侧按钮挤到换行。
   限宽并省略 —— 完整数字在成果区还有一份, 顶栏只需要让人知道"还在跑、缓存正常"。 */
/* 用量只在真有数时才出现。任务没选中时它是一串 "输入 0 输出 0 请求 0 费用 ¥0.00" ——
   占着顶栏最好的位置说一句没有信息量的话。 */
.usage { font-family: var(--mono); font-size: 11.5px; color: var(--dim);
         max-width: 30ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.usage:empty { display: none; }
.body { display: grid; grid-template-columns: 300px 1fr; gap: 14px; padding: 14px;
        align-items: start; }
.side { display: flex; flex-direction: column; gap: 14px; position: sticky; top: 14px; }
.panel, .card { background: var(--card); border: 1px solid var(--line); border-radius: 8px; }
.panel { padding: 14px; display: flex; flex-direction: column; gap: 9px; }
.stage { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

/* ── 上传 ── */
.drop { border: 2px dashed var(--line); border-radius: 7px; padding: 18px; text-align: center;
        cursor: pointer; font-weight: 700; font-size: 12.5px; }
.drop:hover, .drop.over { border-color: var(--teal); background: var(--mint); }
.drop .big { font-size: 22px; color: var(--dim); }
.filelist { font-size: 12px; color: var(--dim); font-family: var(--mono); }
/* 表单里的小标题。引擎这一项不像模型那样自带语义, 不写个字没人知道这下拉是什么。 */
.fieldlbl { font-size: 11.5px; font-weight: 800; color: var(--dim); margin: 2px 0 -4px; letter-spacing: .02em; }
/* 非默认引擎才标出来: 默认那套不用天天提醒, 标出来的都是「这单不一样」 */
.eng { color: var(--brand); font-weight: 900; }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.row3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
.adv summary { cursor: pointer; font-size: 12.5px; color: var(--dim); font-weight: 700;
               padding: 4px 0; }
.allsw { float: right; font-weight: 600; font-size: 11.5px; color: var(--dim); cursor: pointer; }
.allsw input { width: auto; vertical-align: -1px; margin-right: 3px; }

/* ── 任务列表 ── */
.joblist { display: flex; flex-direction: column; gap: 7px; max-height: 46vh; overflow: auto; }
.job { border: 1px solid var(--line); border-radius: 7px; padding: 9px 11px; cursor: pointer;
       background: var(--card); }
.job:hover { background: var(--hov); }
.job.sel { border-color: var(--navy); background: var(--mint); }
.job .p { font-weight: 900; display: flex; justify-content: space-between; gap: 8px;
          align-items: center; }
.job .s { font-size: 11.5px; color: var(--dim); font-weight: 700; margin-top: 3px; }
.job .s.dim2 { color: var(--dim2); font-size: 11px; }
.badge2 { font-size: 10.5px; padding: 2px 7px; border-radius: 20px; font-weight: 900;
          white-space: nowrap; }
.badge2.running { background: var(--runbg); color: var(--run); }
.badge2.done { background: var(--okbg); color: var(--ok); }
.badge2.paused { background: var(--warnbg); color: var(--warn); }
.badge2.failed { background: var(--badbg); color: var(--bad); }

/* ── 指标条 ── */
.hero { background: var(--card); border: 1px solid var(--line); border-radius: 8px; padding: 16px;
        display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: center; }
.badge { display: inline-block; background: var(--mint); color: var(--teal); padding: 4px 10px;
         border-radius: 20px; font-size: 11px; font-weight: 900; }
.metrics { display: grid; grid-template-columns: repeat(3, minmax(96px, 1fr)); gap: 9px; }
.metric { border: 1px solid var(--line); border-radius: 7px; padding: 9px 11px; text-align: center; }
.metric .k { display: block; font-size: 10.5px; font-weight: 900; color: var(--dim); }
.metric .v { display: block; font-size: 21px; font-weight: 900; letter-spacing: -.02em;
             font-variant-numeric: tabular-nums; margin-top: 2px; }
.metric .v.warn { color: var(--warn); }

/* ── 卡片 ── */
/* align-items:start —— 两张卡片各自按内容高度, 不被 grid 拉成等高。
   否则矮的那张底部露白边; 而"让内容区 flex:1 填满"是行不通的:
   卡片高度本身由内容决定, flex:1 的百分比参照就成了循环, 内容区会退化成内容高度
   (实测 430px 的图纸区被一张长图撑到 61896px)。 */
.split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px;
         align-items: start; }
@media (max-width: 1180px) { .split { grid-template-columns: 1fr; }
  .body { grid-template-columns: 1fr; } .side { position: static; } }
/* ── 分区跳转条 ── */
/* 压在顶栏正下方一起 sticky。top 必须是顶栏的实际高度, 而顶栏会换行(窄屏按钮折两排),
   所以用一个 CSS 变量, 由 app.js 量完写进去 —— 写死数字在窄屏上必然错位。 */
.jump { position: sticky; top: var(--topbar-h, 56px); z-index: 30;
        display: flex; gap: 4px; padding: 6px; border: 1px solid var(--line);
        border-radius: 8px; background: var(--card); overflow-x: auto; }
.jbtn { background: none; border: none; padding: 7px 13px; border-radius: 6px;
        color: var(--dim); font-size: 12.5px; font-weight: 700; white-space: nowrap; }
.jbtn:hover { background: var(--bg); color: var(--ink); }
.jbtn.on { background: var(--mint); color: var(--brand); }
/* 锚点落点要躲开顶栏和跳转条, 否则跳过去卡片标题正好被压在下面看不见。
   用 scroll-margin 而不是在 JS 里算偏移: 浏览器原生支持, 键盘 Tab 聚焦时也生效。 */
.card[id], #reviewBox { scroll-margin-top: calc(var(--topbar-h, 56px) + 56px); }

/* ── 单块全屏 ── */
/* 铺满视口而不是用 <dialog>: 卡片里有输入框、SSE 在实时追加内容、还有懒加载的图,
   搬进 dialog 要么丢事件要么得整块重建。position:fixed 则是原地放大, 什么都不动。 */
.card.max { position: fixed; inset: 8px; z-index: 60; margin: 0;
            display: flex; flex-direction: column; overflow: hidden;
            box-shadow: 0 18px 60px var(--shadow); }
.card.max > .cardhead { flex: 0 0 auto; }
/* 内容区接管滚动。卡片自己不滚 —— 让表头那一行留在原地 */
.card.max > .bomwrap, .card.max > #resBody, .card.max > .viewer,
.card.max > .timeline, .card.max > .replay { flex: 1 1 auto; overflow: auto; min-height: 0; }
.card.max .viewer { height: auto; }
/* 全屏时压一层背景, 免得底下的内容从 8px 的缝里透出来晃眼 */
.maxveil { position: fixed; inset: 0; z-index: 59; background: var(--backdrop); }

.cardhead { display: flex; justify-content: space-between; align-items: center; gap: 12px;
            padding: 12px 14px; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
.ch { font-weight: 900; }
.pager { display: flex; align-items: center; gap: 7px; }
.tabs { display: flex; gap: 4px; background: var(--bg); padding: 3px; border-radius: 7px; }
.tabs.pad { margin: 12px 14px 0; }
.tab { background: none; border: none; padding: 6px 12px; border-radius: 5px; color: var(--dim);
       font-size: 12.5px; }
.tab.on { background: var(--card); color: var(--ink); box-shadow: 0 1px 2px var(--shadow); }

/* ── 图纸视图 ── */
/* 高度写死是刻意的: 图纸区要能滚动看长图, 高度必须先于内容确定 */
.viewer { height: 430px; display: grid; place-items: center; background: var(--viewer); padding: 12px;
          overflow: auto; }
/* :not(.art) 是必须的: 白底+边框+投影是给真实图纸缩略图的, 套在空状态插画上
   就变成灰底上贴了一张白纸片。 */
.viewer img:not(.art) { max-height: 100%; max-width: 100%; background: var(--card);
              border: 1px solid var(--line3); box-shadow: 0 6px 20px var(--shadow2); cursor: zoom-in; }
.empty { display: grid; place-items: center; text-align: center; color: var(--dim);
         font-weight: 700; gap: 4px; }
.empty.pad { padding: 40px 14px; }
.empty .big { font-size: 26px; }
/* 空状态插图。三块空区原本只有一行灰字, 新用户第一眼看到的就是三片荒地。
   插图限高而不限宽: 三张图长宽比差得远(表格是扁的、执行流是竖的), 按高度对齐
   才能在各自的容器里都不撑破。 */
/* 宽度用绝对值而不是百分比: .empty 外面是 place-items:center 的收缩容器,
   百分比会去参照一个由内容决定的宽度, 循环依赖直接解成 0(图就消失了)。 */
.empty .art { width: 300px; max-width: 100%; height: auto; max-height: 190px;
              object-fit: contain; opacity: .85; margin-bottom: 6px; }
/* 图纸视图的底是灰的(var(--viewer)), 和插图的浅石板线色本来就接近, 再压不透明度就糊了 */
.viewer .art { opacity: 1; }
.empty.pad .art { width: 400px; max-height: 150px; }

/* ── 执行过程 ── */
.ctrls { display: flex; gap: 8px; align-items: center; padding: 8px 14px;
         border-bottom: 1px solid var(--line); }
.timeline, .replay { height: 390px; overflow: auto; padding: 10px 14px; display: flex;
                     flex-direction: column; gap: 7px; }
.ev { font-size: 12.5px; line-height: 1.55; padding: 7px 10px; border-radius: 6px;
      border-left: 3px solid var(--line); background: var(--evbg); word-break: break-word; }
.ev.assistant { border-left-color: var(--teal); background: var(--tintteal); }
.ev.think { border-left-color: var(--think); background: var(--thinkbg); color: var(--thinktx);
            font-size: 12px; white-space: pre-wrap; }
.ev.tool { border-left-color: var(--tool); background: var(--toolbg); font-family: var(--mono);
           font-size: 11.5px; }
.ev.out { border-left-color: var(--line3); background: var(--hov); font-family: var(--mono);
          font-size: 11px; color: var(--dim); white-space: pre-wrap; max-height: 160px;
          overflow: auto; }
.ev.error { border-left-color: var(--bad); background: var(--badbg); color: var(--bad); }
.ev.status { border-left-color: var(--dim2); color: var(--dim); }
.ev.status.divider { border-top: 1px dashed var(--line); margin-top: 6px; padding-top: 9px; }
.ev.terminal { font-weight: 900; }
.ev.stale { opacity: .42; }
.ev.crop img { max-width: 100%; border-radius: 5px; border: 1px solid var(--line);
               margin-top: 5px; cursor: zoom-in; }
.ev .note { color: var(--dim); font-size: 11.5px; }
.pausedbox { border-left-color: var(--warn); background: var(--warnbg); }

/* 回放: 按轮折叠 */
.turn { border: 1px solid var(--line); border-radius: 7px; overflow: hidden; }
.turn > summary { cursor: pointer; padding: 8px 11px; font-weight: 800; font-size: 12.5px;
                  background: var(--hov); list-style: none; display: flex; gap: 9px;
                  align-items: center; }
.turn > summary::-webkit-details-marker { display: none; }
.turn .no { font-family: var(--mono); color: var(--dim); font-size: 11.5px; }
.turn .tags { margin-left: auto; display: flex; gap: 5px; }
.turn .tg { font-size: 10.5px; padding: 1px 6px; border-radius: 10px; background: var(--bg);
            color: var(--dim); font-weight: 800; }
.turn .inner { padding: 9px 11px; display: flex; flex-direction: column; gap: 6px; }

/* ── 成果 ── */
.downloads { display: flex; gap: 7px; flex-wrap: wrap; }
.downloads a { text-decoration: none; color: var(--ink); border: 1px solid var(--line);
               padding: 7px 11px; border-radius: 6px; font-weight: 800; font-size: 12.5px;
               background: var(--card); }
.downloads a:hover { background: var(--hov); }
.bomwrap { max-height: 440px; overflow: auto; }
.bomwrap table { border-collapse: collapse; width: 100%; font-size: 12.5px; min-width: 900px; }
.bomwrap th { position: sticky; top: 0; background: var(--hov); text-align: left; padding: 8px 10px;
              font-size: 10.5px; text-transform: uppercase; color: var(--dim); font-weight: 900;
              border-bottom: 1px solid var(--line); white-space: nowrap; }
.bomwrap td { padding: 7px 10px; border-bottom: 1px solid var(--line2); }
.bomwrap tr.b-est td { background: var(--estrow); }
.bomwrap tr.b-inf td { background: var(--infrow); }
.basis { font-size: 10.5px; padding: 1px 6px; border-radius: 3px; white-space: nowrap;
         font-weight: 800; }
.basis.b-ok { background: var(--okbg); color: var(--ok); }
.basis.b-inf { background: var(--warnbg); color: var(--warn); }
.basis.b-est { background: var(--badbg); color: var(--bad); }
.report { font-family: var(--mono); font-size: 11.5px; white-space: pre-wrap; max-height: 400px;
          overflow: auto; background: var(--hov); padding: 11px; border-radius: 6px; margin: 0; }

/* 复核提示框 */
.basisbox { border: 1px solid var(--warnline); background: var(--warnbg); border-radius: 7px;
            padding: 11px 13px; margin: 12px 14px; }
.basisbox .bt { font-size: 13px; margin-bottom: 5px; font-weight: 700; }
.basisbox .bt.bad { margin-top: 9px; padding-top: 8px; border-top: 1px dashed var(--warnline2); }
.basisbox .bl { margin: 7px 0 0; padding-left: 17px; font-size: 12px; color: var(--dim); }
.basisbox .bl li { margin-bottom: 2px; }
.basisbox .why { color: var(--dim2); font-size: 11px; margin-left: 4px; }

/* ── 弹窗 ── */
/* dialog 未打开时浏览器默认 display:none, 但只要我们给它写了 display 就会覆盖掉那个
   默认值 —— 弹窗于是直接铺在页面底部。实测"登录后底部多出两个改密码输入框"就是这么来的。
   所以任何 display 都必须限定在 [open] 上。 */
dialog:not([open]) { display: none; }
.dlg { border: none; border-radius: 10px; padding: 0; width: min(860px, 94vw);
       max-height: 88vh; overflow: auto; }
.dlg.small { width: min(380px, 94vw); padding: 18px; }
.dlg.small[open] { display: flex; flex-direction: column; gap: 9px; }
.dlg::backdrop { background: var(--backdrop); }
.dlghead { display: flex; justify-content: space-between; align-items: center;
           padding: 13px 16px; border-bottom: 1px solid var(--line); }
.tabpane { display: none; padding: 14px 16px; flex-direction: column; gap: 9px; }
.tabpane.on { display: flex; }
.dlg table { border-collapse: collapse; width: 100%; font-size: 12.5px; margin-top: 6px; }
.dlg th, .dlg td { padding: 6px 8px; border-bottom: 1px solid var(--line); text-align: left; }
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }

/* ── 账单与套餐 ── */
.balpill { margin-left: 6px; font-family: var(--mono); font-size: 11.5px; color: var(--ok);
           font-weight: 800; }
.balpill.low { color: var(--bad); }
.plancards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
             gap: 11px; }
.plancard { border: 1px solid var(--line); border-radius: 8px; padding: 15px;
            display: flex; flex-direction: column; gap: 8px; }
/* 当前套餐加粗边框而不是换底色: 换底色会让它看起来像被禁用 */
.plancard.on { border-color: var(--navy); border-width: 2px; padding: 14px; }
.plancard .pn { font-weight: 900; font-size: 13px; }
.plancard .pp { font-size: 24px; font-weight: 900; letter-spacing: -.02em;
                font-variant-numeric: tabular-nums; }
.plancard .pp u { font-size: 11.5px; color: var(--dim); text-decoration: none;
                  margin-left: 3px; font-weight: 700; }
.plancard .pf { min-height: 18px; }
.plancard button { margin-top: auto; width: 100%; }

/* ── 轻提示 ── */
/* 取代 alert(): alert 会阻塞整个页面, 用户必须先点掉才能继续 —— 对"已保存"这种
   不需要决策的消息, 那是打断而不是告知。 */
/* toast 刻意不用 --ink 做底: 深色档下 --ink 变成浅色, 再配白字就整块糊了。
   固定用深底浅字, 两档都成立 —— 它是浮在页面之上的提示, 本就该和页面拉开。 */
.toast{position:fixed;left:50%;bottom:30px;transform:translateX(-50%);z-index:90;
  background:#1B2028;color:#F2F4F7;padding:12px 20px;
  border-radius:10px;font-size:13.5px;box-shadow:0 10px 34px rgba(0,0,0,.5);
  max-width:min(460px,90vw);text-align:center}
.toast.bad{background:var(--bad)}

/* ── 登录/注册切换 ── */
.switch{display:flex;align-items:center;justify-content:center;gap:7px;
  font-size:13px;color:var(--dim2);margin-top:2px}
.linkbtn{color:var(--teal);font-weight:700;font-size:13px;padding:2px 0;
  background:none;border:none;cursor:pointer}
.linkbtn:hover{text-decoration:underline}
.bonus{font-size:12.5px;color:var(--teal);background:var(--mint);
  border-radius:8px;padding:9px 12px;line-height:1.55}

/* ── 通知铃铛 ── */
.bell { position: relative; padding-left: 10px; padding-right: 10px; }
/* 小红点而不是数字: 未读 1 条和 12 条要做的事一样 —— 点开看看。
   数字只在"多到需要决定先看哪条"时才有用, 而这里的量级到不了。 */
.dot { position: absolute; top: 5px; right: 6px; width: 7px; height: 7px;
       border-radius: 50%; background: var(--bad); }
#dlgBell { width: min(440px, 94vw); }
.belllist { display: flex; flex-direction: column; max-height: 60vh; overflow: auto; }
.nf { display: flex; flex-direction: column; gap: 3px; padding: 12px 16px;
      border-bottom: 1px solid var(--line); text-align: left; width: 100%;
      background: none; border-left: 3px solid transparent; }
.nf:last-child { border-bottom: none; }
.nf[data-unread] { border-left-color: var(--teal); background: var(--tintteal); }
.nf .t { font-weight: 700; font-size: 13.5px; color: var(--ink); }
.nf .b { font-size: 12.5px; color: var(--dim); line-height: 1.55; font-weight: 500; }
.nf .w { font-size: 11px; color: var(--dim2); font-weight: 600; }
.nf[data-job]:hover { background: var(--mint); cursor: pointer; }
.nf.bad .t { color: var(--bad); }
.nf.warn .t { color: var(--warn); }

/* ── 主题切换 ── */
.themebtn { display: inline-flex; align-items: center; gap: 6px; }
.themebtn svg { flex: none; opacity: .75; }
.loginhead { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.themebtn.mini { border: 1px solid var(--line); border-radius: 7px; background: none;
  color: var(--dim); font-size: 12px; font-weight: 600; padding: 5px 10px; cursor: pointer; }
.themebtn.mini:hover { border-color: var(--line3); color: var(--ink); }

/* ── 账号页 ── */
.acctgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 14px; align-items: start; }
.acctgrid .card h3 { margin: 0 0 14px; font-size: 14px; font-weight: 800; }
.fld { display: flex; flex-direction: column; gap: 5px; margin-bottom: 12px;
  font-size: 12px; color: var(--dim); font-weight: 700; }
.acctgrid .kv { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 9px 0; border-top: 1px solid var(--line2);
  font-size: 13px; color: var(--dim); font-weight: 600; }
.acctgrid .kv b { color: var(--ink); font-weight: 700; }
.acctgrid .acts { display: flex; align-items: center; gap: 12px; margin-top: 14px;
  flex-wrap: wrap; }
.acctgrid .acts .dim { font-size: 12.5px; }
.small { padding: 7px 12px; font-size: 12.5px; width: auto; }
.jobsearch { margin-bottom: 8px; font-size: 12.5px; padding: 7px 10px; font-weight: 600; }

/* ── 图纸全屏 ── */
.dlg.fs { width: 96vw; max-width: none; height: 92vh; max-height: none; padding: 0;
  display: none; background: var(--card); }
.dlg.fs[open] { display: flex; flex-direction: column; }
.fsbar { display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 10px 14px; border-bottom: 1px solid var(--line); flex: none; }
.fsbar .mono { font-family: var(--mono); font-size: 12.5px; color: var(--dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fsnav { display: flex; align-items: center; gap: 8px; flex: none; }
.fsnav .n { font-family: var(--mono); font-size: 13px; color: var(--dim); min-width: 62px;
  text-align: center; }
/* 图按容器等比缩放, 不裁切 —— 看裁图是为了核对细节, 裁掉一角就白看了 */
.fsbody { flex: 1; min-height: 0; display: grid; place-items: center;
  background: var(--viewer); padding: 12px; overflow: auto; }
.fsbody img { max-width: 100%; max-height: 100%; object-fit: contain;
  background: #fff; border-radius: 4px; }

/* ── 用户菜单 ── */
.usermenu { position: relative; }
.userbtn { display: flex; align-items: center; gap: 9px; padding: 6px 10px 6px 6px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--card);
  cursor: pointer; color: var(--ink); }
.userbtn:hover { border-color: var(--line3); }
.avatar { width: 26px; height: 26px; border-radius: 50%; background: var(--mint);
  color: var(--brand); display: grid; place-items: center; font-weight: 900;
  font-size: 12.5px; flex: none; }
.uinfo { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.25; }
.uinfo b { font-size: 12.5px; font-weight: 800; }
.uinfo .balpill { font-style: normal; margin: 0; font-size: 11px; }
.userpop { position: absolute; right: 0; top: calc(100% + 8px); z-index: 60;
  width: 268px; background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: 0 12px 34px var(--shadow2); overflow: hidden;
  padding: 6px; display: flex; flex-direction: column; gap: 1px; }
.upid { padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 3px; }
.upid b { font-size: 13.5px; font-weight: 800; }
.upid .mono { font-family: var(--mono); font-size: 11.5px; color: var(--dim); }
.upi { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; padding: 9px 12px; border: none; background: none; cursor: pointer;
  border-radius: 8px; font-size: 13px; font-weight: 700; color: var(--ink);
  text-align: left; font-family: inherit; }
.upi:hover { background: var(--hov); }
.upi.row { cursor: default; font-weight: 600; color: var(--dim); }
.upi.row:hover { background: none; }
.upi.danger { color: var(--bad); }
.upi .n { font-family: var(--mono); font-size: 12px; color: var(--ok); font-weight: 800; }
.upsep { height: 1px; background: var(--line2); margin: 5px 0; }
.userpop .langsw { transform: scale(.9); transform-origin: right center; }

/* ── 新建面板折叠 ── */
.panelhead { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.panelhead h3 { margin: 0; }
/* 折叠后只留标题栏。用 max-height 而不是 display:none —— 后者没有过渡，
   一下子消失会让人以为点错了。 */
#newPanel.folded > *:not(.panelhead) { display: none; }

/* ── 追问 ── */
.askbar { display: flex; gap: 8px; align-items: flex-end; padding: 10px 14px;
  border-top: 1px solid var(--line); }
.askbar textarea { flex: 1; resize: none; min-height: 38px; max-height: 120px;
  font-family: var(--sans); font-size: 13px; font-weight: 600; line-height: 1.6; }
.askbar .primary { flex: none; width: auto; }
.askhint { padding: 10px 14px; border-top: 1px solid var(--line); font-size: 12.5px; }
.ev.ask { border-left-color: var(--teal); background: var(--tintteal);
  font-weight: 700; color: var(--ink); }

/* ── 全部输出文件 ── */
.filesbox { border-top: 1px solid var(--line); }
.filesbox summary { padding: 11px 16px; cursor: pointer; font-size: 12.5px;
  font-weight: 800; color: var(--dim); display: flex; align-items: center; gap: 8px;
  list-style: none; }
.filesbox summary::-webkit-details-marker { display: none; }
.filesbox summary::before { content: "▸"; transition: transform .15s; }
.filesbox[open] summary::before { transform: rotate(90deg); }
.filesbox summary .n { font-family: var(--mono); color: var(--dim); font-weight: 700; }
.fileslist { display: flex; flex-direction: column; padding: 0 8px 10px; }
.fitem { display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 8px 10px; border-radius: 7px; text-decoration: none; color: var(--ink);
  font-size: 12.5px; }
.fitem:hover { background: var(--hov); }
.fitem .fn { font-family: var(--mono); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.fitem .fs { font-family: var(--mono); color: var(--dim); flex: none; font-size: 11.5px; }
@media (prefers-reduced-motion: reduce) { .filesbox summary::before { transition: none; } }

/* ── 待复核清单 ── */
/* 结果页的第一块。用琥珀色而不是红色: 它不是"错了", 是"这几行请你亲自看一眼"，
   红色会让人以为算量失败了。 */
.reviewbox { border: 1px solid var(--warnline); background: var(--warnbg);
  border-radius: 10px; margin-bottom: 14px; overflow: hidden; }
.rvhead { display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 12px 16px; border-bottom: 1px solid var(--warnline2); flex-wrap: wrap; }
.rvhead b { font-size: 14px; font-weight: 900; color: var(--warn); }
.rvhead .dim { font-size: 12.5px; margin-left: 10px; }
.rvbody { padding: 4px 16px 14px; font-size: 13px; line-height: 1.75; color: var(--ink); }
.rvbody h4 { margin: 14px 0 6px; font-size: 13px; font-weight: 900; color: var(--warn); }
.rvbody p { margin: 8px 0; }
.rvbody ul { margin: 8px 0; padding-left: 18px; display: flex; flex-direction: column; gap: 5px; }
.rvbody code { font-family: var(--mono); font-size: 12px; background: var(--card);
  padding: 1px 5px; border-radius: 4px; }
.rvtable { overflow-x: auto; margin: 10px 0; background: var(--card);
  border-radius: 8px; border: 1px solid var(--warnline2); }
.rvbody table { width: 100%; border-collapse: collapse; font-size: 12.5px;
  font-variant-numeric: tabular-nums; }
.rvbody th { text-align: left; padding: 9px 12px; font-weight: 800; color: var(--dim);
  border-bottom: 1px solid var(--line); white-space: nowrap; font-size: 11.5px; }
.rvbody td { padding: 9px 12px; border-bottom: 1px solid var(--line2);
  vertical-align: top; line-height: 1.65; }
.rvbody tbody tr:last-child td { border-bottom: none; }
.rvbody td:first-child { font-weight: 800; white-space: nowrap; }

/* 有了模型自己写的待复核清单之后, 机械分类的这块降为补充信息:
   收窄、去掉高亮底色, 不再和上面那块抢注意力。 */
.basisbox.secondary { background: none; border: 1px solid var(--line);
  opacity: .82; font-size: 12.5px; }
.basisbox.secondary .bt { font-weight: 700; }

/* ── 侧栏收起 ── */
/* grid-template-columns 从 "300px 1fr" 变成 "1fr" —— 用 grid 而不是给侧栏加
   display:none, 是因为要让右侧真正铺满, 而不是留一条 300px 的空。 */
.body.nosides { grid-template-columns: 1fr; }
.body.nosides > .side { display: none; }
.railbtn.on { background: var(--mint); color: var(--brand); }

/* ── 下载弹窗 ── */
#dlgDl { width: min(480px, 94vw); }
#dlgDl[open] { display: flex; flex-direction: column; padding: 0; }
.dlpick { padding: 4px 14px 10px; display: flex; flex-direction: column; gap: 2px;
  max-height: 52vh; overflow: auto; }
.dlall { display: flex; align-items: center; gap: 9px; padding: 10px 8px;
  border-bottom: 1px solid var(--line2); font-weight: 800; font-size: 13px;
  cursor: pointer; position: sticky; top: 0; background: var(--card); z-index: 1; }
.dlall span { flex: none; }
.dlall .n { margin-left: auto; font-family: var(--mono); font-size: 11.5px;
  color: var(--dim); font-weight: 700; }
.dlitem { display: flex; align-items: center; gap: 9px; padding: 8px;
  border-radius: 7px; cursor: pointer; font-size: 12.5px; }
/* 复选框必须 flex:none + 固定宽。全局 input 规则里有 width:100%(给文本框用的),
   在 flex 行里它会把整行吃光 —— 实测复选框占了 378px, 文件名被挤成 0。 */
.dlitem input[type="checkbox"], .dlall input[type="checkbox"] {
  width: 15px; height: 15px; flex: none; margin: 0; padding: 0; accent-color: var(--teal); }
.dlitem:hover { background: var(--hov); }
/* 裁图缩进一档: 它是过程产物, 和 BOM/报告不是一个层级 */
.dlitem.sub { padding-left: 24px; color: var(--dim); }
/* min-width:0 不能省: flex 子项默认 min-width:auto, 内容再长也不肯收缩, 而
   overflow:hidden 又把它压到 0 —— 结果文件名整列都不见了(只剩右边的体积)。 */
.dlitem .fn { font-family: var(--mono); flex: 1; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.dlitem .fs { font-family: var(--mono); font-size: 11px; color: var(--dim); flex: none; }
.dlfoot { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 14px; border-top: 1px solid var(--line); }
.dlfoot .dim { font-size: 12px; }
.dlbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* 窄栏: 导航在上、视图控制在下, 中间撑开。两类东西并排会让人以为
   "收起侧栏"也是一个可以去的页面。 */
.railgap { flex: 1; }
.rail { display: flex; flex-direction: column; }
/* 一道细线把"视图控制"和上面的导航分开。光靠中间那段空白, 底部这个按钮看着
   像是飘在那儿的, 不知道它管的是什么。 */
#railToggle { position: relative; }
#railToggle::before { content: ""; position: absolute; left: 8px; right: 8px; top: -7px;
                      height: 1px; background: var(--line); }
/* 下载入口。只有这一个 —— 具体下哪些在弹窗里决定。 */
#btnDl { display: inline-flex; align-items: center; gap: 6px; width: auto; }
#btnDl .n { font-family: var(--mono); font-size: 11.5px; opacity: .8; font-weight: 700; }

/* 连接余量比例: 指标卡里可直接改的数; 新建表单里的一行 */
.metric input.ratio { width: 3.2em; font: inherit; font-weight: inherit; text-align: right;
  border: 1px solid var(--line, #ccc); border-radius: 6px; background: transparent; color: inherit; padding: 0 .2em; }
.metric .sub { display: block; font-size: .72em; font-weight: 400; margin-top: .15em; }
.ratiorow { display: flex; align-items: center; gap: .4em; margin: .2em 0 .6em; }
.ratiorow input { width: 5em; }
