/* Minimalist Modern —— 克制在数量，笃定在执行。
   电蓝渐变是这套系统的心跳，只在真正重要的地方出现。 */

@import url("https://fonts.googleapis.com/css2?family=Calistoga&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap");

:root {
  --background: #FAFAFA;
  --foreground: #0F172A;
  --muted: #F1F5F9;
  --muted-foreground: #64748B;
  --accent: #0052FF;
  --accent-secondary: #4D7CFF;
  --accent-foreground: #FFFFFF;
  --border: #E2E8F0;
  --card: #FFFFFF;

  --grad: linear-gradient(135deg, var(--accent), var(--accent-secondary));
  --grad-r: linear-gradient(to right, var(--accent), var(--accent-secondary));

  --shadow-sm: 0 1px 3px rgba(0,0,0,.06);
  --shadow-md: 0 4px 6px rgba(0,0,0,.07);
  --shadow-lg: 0 10px 15px rgba(0,0,0,.08);
  --shadow-xl: 0 20px 25px rgba(0,0,0,.1);
  --shadow-accent: 0 4px 14px rgba(0,82,255,.25);
  --shadow-accent-lg: 0 8px 24px rgba(0,82,255,.35);

  --display: "Calistoga", Georgia, "Songti SC", serif;
  --ui: "Inter", system-ui, "PingFang SC", "Microsoft YaHei", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --r-lg: 10px; --r-xl: 12px; --r-2xl: 16px;
}

* { box-sizing: border-box; }
html { -webkit-font-smoothing: antialiased; }
body {
  margin: 0; background: var(--background); color: var(--foreground);
  font: 400 15px/1.7 var(--ui);
}
h1, h2, h3 { margin: 0; }
a { color: var(--accent); text-decoration: none; }

.display { font-family: var(--display); font-weight: 400; letter-spacing: -.02em; }
.gradient-text {
  background: var(--grad-r); -webkit-background-clip: text;
  background-clip: text; color: transparent;
}

/* ── 区块标签（每个区域开头的胶囊）───────────── */
.label {
  display: inline-flex; align-items: center; gap: 10px;
  border: 1px solid rgba(0,82,255,.3); background: rgba(0,82,255,.05);
  border-radius: 999px; padding: 5px 14px;
}
.label .dot {
  width: 7px; height: 7px; border-radius: 999px; background: var(--accent); flex: none;
}
.label .dot.live { animation: pulse 2s ease-in-out infinite; }
.label span {
  font: 500 11px/1 var(--mono); text-transform: uppercase;
  letter-spacing: .15em; color: var(--accent);
}
@keyframes pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.35); opacity: .6; }
}

/* ── 反色顶栏：整页唯一的深色块，给节奏一个锚点 ── */
.topbar {
  position: relative; overflow: hidden;
  background: var(--foreground); color: #fff;
  padding: 22px 34px;
}
/* 两个装饰层必须 pointer-events:none。
   ::after 是 .topbar 的最后一个生成子元素，绝对定位又没有 z-index，
   会盖在 .inner 上面把右侧那几个链接的点击全吃掉——按钮"点不动"就是这么来的。 */
.topbar::before {                     /* 点阵纹理，感觉得到但看不见 */
  content: ""; position: absolute; inset: 0; opacity: .04; pointer-events: none;
  background-image: radial-gradient(circle, #fff 1px, transparent 1px);
  background-size: 32px 32px;
}
.topbar::after {                      /* 角落的辉光 */
  content: ""; position: absolute; right: -180px; top: -220px; pointer-events: none;
  width: 460px; height: 460px; border-radius: 999px;
  background: var(--accent); opacity: .16; filter: blur(150px);
}
.topbar .inner {
  position: relative; z-index: 1; display: flex; align-items: center;
  flex-wrap: wrap; gap: 12px 18px;
  max-width: 1400px; margin: 0 auto;
}
/* 窄屏下 spacer 不再占位，否则右侧那排会被推到下一行的最右边、彼此散开 */
@media (max-width: 900px) { .topbar .inner .spacer { flex-basis: 100%; } }
a.chip { cursor: pointer; }
.topbar h1 { font-family: var(--display); font-size: 22px; letter-spacing: -.02em; }
.topbar .sub { font: 400 12px/1 var(--mono); color: rgba(255,255,255,.45); letter-spacing: .1em; }
.spacer { flex: 1; }

.chip {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  /* **高度写死。** 顶栏里 span / a / button 三种元素都用 .chip，
     靠 padding 对齐永远差几像素——button 还会被全局 button 规则改字号。
     实测 #who(span) 28.5px、其余(button) 41.5px，差 13px。 */
  height: 34px; padding: 0 14px; border-radius: 999px;
  font: 500 12px var(--ui); line-height: 1;
  border: 1px solid rgba(255,255,255,.18); color: rgba(255,255,255,.82);
  background: transparent; transition: all .2s ease-out;
  /* 不许换行、不许被压缩：挤窄了会变成一列一个字，读都读不出来 */
  white-space: nowrap; flex: 0 0 auto;
}
/* **特异性必须压过全局的 `button:hover:not(:disabled)`。**
   那条把背景刷成 var(--muted)——浅灰，是给浅色页面用的；而顶栏是深色底，
   于是一悬浮就成了近白色的一块，字都看不清。这里用 `.chip:hover:not(:disabled)`
   把特异性拉平再加上元素选择器，稳稳盖住。 */
.chip:hover:not(:disabled),
button.chip:hover:not(:disabled),
a.chip:hover {
  border-color: rgba(255,255,255,.34);
  background: rgba(255,255,255,.08);
  color: #fff;
}
button.chip:active:not(:disabled) {
  background: rgba(255,255,255,.14); transform: none;
}
button.chip:focus-visible { outline: 2px solid rgba(255,255,255,.5); outline-offset: 2px; }
/* 再按顶栏限定一次。上面那条靠特异性已经压得住全局 button:hover，
   但顶栏是深色区、全局那条刷的是浅色，这里出错的代价是「字看不清」，
   多一层限定不亏。 */
.topbar .chip:hover:not(:disabled) {
  background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.34); color: #fff;
}
.topbar .chip.primary:hover:not(:disabled) {
  background: var(--accent); border-color: var(--accent);
  color: var(--accent-foreground); filter: brightness(1.12);
}
.chip.warn { border-color: rgba(255,180,80,.5); color: #ffcf87; }
.chip.on { border-color: rgba(0,82,255,.7); background: rgba(0,82,255,.22); color: #cfe0ff; }
.chip b { color: #fff; }

/* ── 布局 ───────────────────────────────────── */
.wrap { max-width: 1400px; margin: 0 auto; padding: 34px; }
.stack > * + * { margin-top: 26px; }

/* 非对称：左侧表单略占优势 */
.asym { display: grid; grid-template-columns: 1.1fr .9fr; gap: 22px; align-items: start; }
@media (max-width: 940px) { .asym { grid-template-columns: 1fr; } }

/* 单列，与上方「新建任务」卡片同宽。原来是自适应多列，卡片会窄一截，
   和新建卡对不齐，进度条和操作按钮也挤。一屏少放几张换对齐和可读性。 */
.board { display: flex; flex-direction: column; gap: 20px; }
@media (max-width: 700px) { .wrap { padding: 20px; } }

/* ── 卡片 ───────────────────────────────────── */
.card {
  position: relative; background: var(--card); border: 1px solid var(--border);
  border-radius: var(--r-2xl); padding: 24px;
  box-shadow: var(--shadow-md); transition: box-shadow .3s ease-out, transform .3s ease-out;
}
.card:hover { box-shadow: var(--shadow-lg); }
.card.live { border-color: rgba(0,82,255,.35); box-shadow: var(--shadow-accent); }
.card.bad { border-color: #fca5a5; }
.card h2 {
  font: 600 17px/1.35 var(--ui); letter-spacing: -.01em; margin: 12px 0 0;
}
.card .desc { color: var(--muted-foreground); font-size: 14px; margin: 6px 0 0; }

.head { display: flex; align-items: center; gap: 12px; }

/* ── 表单 ───────────────────────────────────── */
label.f { display: block; font: 500 12px/1 var(--ui); color: var(--muted-foreground); margin: 18px 0 7px; }
input[type=text], input[type=password], input[type=number], input[type=email], textarea, select {
  width: 100%; height: 48px; padding: 0 14px; font: 400 14px/48px var(--ui);
  border: 1px solid var(--border); border-radius: var(--r-xl);
  background: rgba(241,245,249,.4); color: var(--foreground);
  transition: all .2s ease-out;
}
textarea { height: auto; line-height: 1.7; padding: 12px 14px; resize: vertical; min-height: 96px; }
select { appearance: none; background-image: none; cursor: pointer; }
input::placeholder, textarea::placeholder { color: rgba(100,116,139,.55); }
input:focus, textarea:focus, select:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(0,82,255,.16);
  background: var(--card);
}

.seg { display: flex; gap: 10px; }
.seg label {
  flex: 1; cursor: pointer; text-align: center; padding: 13px;
  border: 1px solid var(--border); border-radius: var(--r-xl);
  font: 500 14px var(--ui); transition: all .2s ease-out; background: var(--card);
}
.seg input { display: none; }
.seg label:hover { border-color: rgba(0,82,255,.3); }
.seg label:has(input:checked) {
  border-color: transparent; color: #fff; background: var(--grad);
  box-shadow: var(--shadow-accent);
}

/* ── 按钮 ───────────────────────────────────── */
button {
  font: 500 14px var(--ui); cursor: pointer; border-radius: var(--r-xl);
  border: 1px solid var(--border); background: transparent; color: var(--foreground);
  height: 48px; padding: 0 22px; transition: all .2s ease-out;
}
button:hover:not(:disabled) { background: var(--muted); border-color: rgba(0,82,255,.3); }
button:active:not(:disabled) { transform: scale(.98); }
button:disabled { opacity: .4; cursor: not-allowed; }
button.primary {
  background: var(--grad-r); border-color: transparent; color: #fff;
  box-shadow: var(--shadow-sm);
}
button.primary:hover:not(:disabled) {
  transform: translateY(-2px); filter: brightness(1.1);
  box-shadow: var(--shadow-accent-lg); background: var(--grad-r);
}
button.sm { height: 34px; padding: 0 13px; font-size: 13px; border-radius: var(--r-lg); }

/* 看板每页条数选择器。select 的文字垂直居中跨浏览器行为差异大
   （Chrome/Edge 认 line-height，Firefox 走系统渲染不认），
   所以用 padding + 固定高度统一盒模型，不依赖 line-height。 */
.page-size {
  width: auto; height: 34px; padding: 0 8px 0 12px;
  font: 400 13px/1 var(--ui);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: rgba(241,245,249,.4); color: var(--foreground);
  cursor: pointer;
  box-sizing: border-box;
}
.page-size:hover { border-color: rgba(0,82,255,.35); }
.page-size:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(0,82,255,.16); background: var(--card);
}
button.ghost { border-color: transparent; color: var(--muted-foreground); }
button.ghost:hover:not(:disabled) { color: var(--foreground); background: var(--muted); }

/* 纯图标按钮：与相邻输入框同高同圆角，贴在它右边 */
button.icon {
  width: 48px; padding: 0; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted-foreground);
}
button.icon:hover:not(:disabled) { color: var(--accent); }
button.icon svg { display: block; }
.row { display: flex; gap: 10px; align-items: center; }
.row.end { justify-content: flex-end; }

/* ── 状态 ───────────────────────────────────── */
.pill {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  font: 500 11px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase;
  padding: 6px 11px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--muted-foreground); background: var(--muted);
}
.pill.running { border-color: transparent; background: var(--grad); color: #fff; box-shadow: var(--shadow-accent); }
.pill.ready { border-color: rgba(22,163,74,.35); background: rgba(22,163,74,.08); color: #15803d; }
.pill.error { border-color: rgba(220,38,38,.35); background: rgba(220,38,38,.07); color: #dc2626; }
.pill .dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; }
.pill.running .dot { background: #fff; animation: pulse 2s ease-in-out infinite; }

/* ── 事件流 ─────────────────────────────────── */
.stream {
  margin-top: 16px; max-height: 300px; overflow: auto;
  display: flex; flex-direction: column; gap: 7px;
  border-top: 1px solid var(--border); padding-top: 16px;
}
.ev { font-size: 13.5px; line-height: 1.65; animation: fadeUp .5s cubic-bezier(.16,1,.3,1); }
.ev.text { white-space: pre-wrap; }
.ev.tool {
  font: 400 11.5px/1.6 var(--mono); color: var(--muted-foreground);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ev.tool b { color: var(--accent); font-weight: 500; }
.ev.user {
  white-space: pre-wrap; background: rgba(0,82,255,.05);
  border-left: 2px solid var(--accent); padding: 9px 13px; border-radius: 0 var(--r-lg) var(--r-lg) 0;
}
.ev.denied { font-size: 12px; color: #b45309; }
.ev.error { white-space: pre-wrap; color: #dc2626; font-size: 13px; }
.ev.sys { font: 400 11px/1.6 var(--mono); color: var(--muted-foreground); letter-spacing: .06em; }
@keyframes fadeUp { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* ── 文件 ───────────────────────────────────── */
.files { list-style: none; margin: 14px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 7px; }
.files li {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  font: 400 12px var(--ui); padding: 6px 11px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--card);
  transition: all .2s ease-out;
}
.files li:hover { border-color: var(--accent); color: var(--accent); transform: translateY(-1px); }
.files li .s { color: var(--muted-foreground); font: 400 10.5px var(--mono); }

/* 步骤分组标签。flex-basis:100% 是为了让它独占一行、把后面的文件挤到下一行
   ——不这么写它会跟文件挤在同一行，标签和它标的那组看不出从属关系。
   只用确实存在的变量（--muted-foreground / --border）。 */
.files li.grp {
  flex: 0 0 100%; cursor: default; background: none; border: 0;
  padding: 10px 0 2px; margin: 0;
  color: var(--muted-foreground); font: 500 11px var(--ui); letter-spacing: .04em;
}
.files li.grp:hover { color: var(--muted-foreground); transform: none; }
.files li.grp::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--border); margin-left: 10px;
}

dialog {
  border: 0; border-radius: var(--r-2xl); padding: 0; max-width: 92vw; max-height: 90vh;
  box-shadow: var(--shadow-xl); background: var(--card);
}
/* 必须限定 [open]：直接给 dialog 写 display:flex 会盖掉浏览器默认的
   `dialog:not([open]) { display: none }`，弹窗关不掉，会一直摊在页面上。 */
dialog[open] { display: flex; flex-direction: column; }
dialog::backdrop { background: rgba(15,23,42,.55); backdrop-filter: blur(3px); }
dialog .dhead { flex: 0 0 auto; display: flex; align-items: center; gap: 12px; padding: 16px 20px; border-bottom: 1px solid var(--border); }
dialog .dbody { flex: 1 1 auto; min-height: 0; padding: 18px; overflow: auto; max-height: calc(90vh - 70px); }

/* 弹窗尺寸固定。
   原来宽高都由内容决定，于是同一个预览窗口每换一种文件就变一次形状：
   markdown 又窄又高、三维模型又宽又矮、图片随图大小跳。定死之后，
   窗口位置稳定，来回点不同文件不会满屏乱蹦。宽度取三维预览那档，高度取文本那档。 */
#viewer, #detail, #logDlg, #ledgerDlg {
  width: min(1160px, 94vw); height: min(880px, 90vh);
}
#viewer .dbody, #detail .dbody, #logDlg .dbody, #ledgerDlg .dbody { max-height: none; }
/* 图片按窗口大小等比装下，别撑出滚动条 */
#viewer img { max-height: 100%; object-fit: contain; margin: 0 auto; }
dialog img { max-width: 100%; display: block; }
dialog pre { margin: 0; white-space: pre-wrap; word-break: break-word; font: 400 12.5px/1.75 var(--mono); }

/* ── 表格（管理后台）─────────────────────────── */
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 13px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
th { font: 500 11px var(--mono); text-transform: uppercase; letter-spacing: .12em; color: var(--muted-foreground); }
/* 金额、token 这类要竖着对齐的列 */
td.num { font-family: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
tbody tr { transition: background .2s ease-out; }
tbody tr:hover { background: rgba(241,245,249,.6); }

.muted { color: var(--muted-foreground); }
.empty { color: var(--muted-foreground); text-align: center; padding: 40px 16px; font-size: 14px; }
.note {
  border-left: 2px solid var(--accent); background: rgba(0,82,255,.04);
  padding: 11px 15px; border-radius: 0 var(--r-lg) var(--r-lg) 0;
  font-size: 13px; color: var(--muted-foreground);
}
.note b { color: var(--foreground); }

/* ── 流程进度 ──────────────────────────────── */
/* 不换行：一旦折行，连线会落在第二行行首，左边没有节点，看着像条断线。
   窄屏改成横向滚动，进度的视觉逻辑就不会被折行打断。 */
.steps {
  display: flex; flex-wrap: nowrap; margin: 16px 0 4px;
  overflow-x: auto; scrollbar-width: none;
}
.steps::-webkit-scrollbar { display: none; }
/* flex:1 + min-width:0 会把七个标签压成等宽再打省略号（"读取…""专利…"）。
   改成按内容撑开、只把富余空间摊给连接线，放不下才换行。 */
/* 节点**不参与伸缩**。让节点 flex:1 的话，多出来的空间会摊进节点盒子里、
   落在标签后面，于是连线被整体推向右侧——看着就是"线没在两个节点中间"。
   富余空间全部交给连线本身，左右外边距相等，线自然居中。 */
.steps .step {
  display: flex; align-items: center; gap: 7px; flex: 0 0 auto;
  font-size: 11.5px; color: var(--muted-foreground); position: relative;
}
/* 不画连接线：标签按内容排布后节点之间几乎没有富余，线只剩一小截，
   看着像渲染残留而不是连接。靠圆点的填充和标签颜色表达进度已经够清楚。 */
.steps .step i {
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; z-index: 1;
  background: var(--background); border: 1.5px solid var(--border);
}
.steps .step span { z-index: 1; white-space: nowrap; }
/* 步与步之间的分界。比虚线连接线重一档，让人一眼看出「这里换了一步」。 */
.steps .sep {
  flex: 0 0 auto; align-self: stretch; width: 1px; margin: 0 14px;
  background: var(--border);
}
/* 节点之间的虚线：独立元素，把富余空间全吃掉，线就一定画在两点之间 */
.steps .link {
  flex: 1 1 14px; min-width: 14px; height: 0; align-self: center;
  margin: 0 9px; border-top: 1px dashed var(--border);
}
.steps .link.done { border-top-color: var(--accent); }
.steps.big .link { margin: 0 11px; }
.steps .step.done i { background: var(--accent); border-color: var(--accent); }
.steps .step.done span { color: var(--muted-foreground); }
.steps .step.cur i {
  background: var(--accent); border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(0,82,255,.16);
}
.steps .step.cur span { color: var(--accent); font-weight: 600; }

/* 当前节点转圈——但**只在真的在跑的时候转**。
   暂停/出错/已完成的任务上转圈是在骗人：看着像还在干活，其实早停了。
   由 .steps.spin 控制，那个 class 只在 running/queued 时挂上去。 */
.steps.spin .step.cur i {
  background: transparent; border-color: rgba(0,82,255,.25);
  border-top-color: var(--accent); border-right-color: var(--accent);
  box-shadow: none; animation: stepspin .8s linear infinite;
}
@keyframes stepspin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /* 不转，但仍要看得出是"当前"这一步 */
  .steps.spin .step.cur i {
    animation: none; background: var(--accent); border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(0,82,255,.16);
  }
}
.steps.big { margin: 0 0 18px; }
.steps.big .step { font-size: 12.5px; gap: 9px; }

/* ── 操作明细 ──────────────────────────────── */
.ops { font: 400 12px/1.7 var(--mono); }
.ops .op {
  display: flex; gap: 10px; padding: 5px 0;
  border-bottom: 1px solid rgba(148,163,184,.14);
}
.ops .op .t { color: rgba(100,116,139,.7); flex: 0 0 58px; }
.ops .op .k { flex: 0 0 34px; color: var(--muted-foreground); }
.ops .op .b { flex: 1; min-width: 0; word-break: break-word; }
.ops .op .b b { color: var(--accent); font-weight: 600; }
.ops .op.model_text .b { color: var(--foreground); }
.ops .op.denied .k, .ops .op.denied .b { color: #b45309; }
.ops .op.model_error .k, .ops .op.model_error .b { color: #b91c1c; }
.ops .op.thinking { opacity: .55; }

/* ── 评审：按任务分组 ──────────────────────── */
.jobgrp {
  border: 1px solid var(--border); border-radius: var(--r-xl);
  padding: 14px 16px; margin-bottom: 14px; background: var(--background);
}
.jobgrp > .row { align-items: flex-start; }
.jobgrp > .row b { font-size: 14px; display: block; margin-bottom: 3px; }
.jobgrp .tblwrap { margin-top: 12px; overflow-x: auto; }
.jobgrp table { min-width: 700px; }
/* 两个按钮不许换行——挤成上下两行会让人以为是两组不同的操作 */
.jobgrp td:last-child { white-space: nowrap; }
.jobgrp .nores {
  margin-top: 10px; font-size: 12.5px; line-height: 1.7;
  padding-top: 10px; border-top: 1px dashed var(--border);
}

/* 顶栏里的按钮要长得和 .chip 一样，但它是 <button>，得把默认样式抹掉 */
/* `font: inherit` 会把 .chip 的 12px 顶成顶栏继承下来的 15px，
   于是 button 比 span 高出 13px——高度不齐就是这么来的。尺寸全交给 .chip。 */
button.chip.as-btn { cursor: pointer; }

/* ── 登录页 ────────────────────────────────── */
/* 与顶栏同一底色（反色区），登录页整屏用它 */
.loginpage { background: var(--foreground); min-height: 100vh; }
.loginwrap {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.loginbox { width: min(420px, 100%); }
.loginbox h1 { font: 400 30px/1.15 var(--display); letter-spacing: -.01em; }
.loginbox .desc { margin-bottom: 22px; }
.loginbox label.f:first-of-type { margin-top: 4px; }

button.danger { color: #b91c1c; }
button.danger:hover:not(:disabled) { background: rgba(220,38,38,.06); border-color: rgba(220,38,38,.3); }
.pill.paused { color: #b45309; background: rgba(180,83,9,.08); }
.pill.paused .dot { background: #b45309; }
.note.bad {
  border-left-color: #dc2626; background: rgba(220,38,38,.05); color: #b91c1c;
}
.note code {
  font: 400 12px/1 var(--mono); background: rgba(0,82,255,.08);
  padding: 2px 5px; border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
}

/* ── 任务行（新建卡里可增删的一条）───────────── */
.task {
  border: 1px solid var(--border); border-radius: var(--r-xl);
  padding: 16px 18px 18px; margin-top: 14px; background: rgba(241,245,249,.35);
  animation: fadeUp .45s cubic-bezier(.16,1,.3,1);
}
.task .label.sm { padding: 3px 11px; }
.task .label.sm span { font-size: 10px; }
.task label.f:first-of-type { margin-top: 12px; }

/* 左边一列放类型 / 名称 / 参考文件，右边整列留给描述。
   描述是这张表里唯一需要大面积输入的字段，单列排下来它只能是中间一个矮框，
   下面还压着参考文件。分两列之后它能撑满整列高度。 */
.task-grid { display: grid; grid-template-columns: 1fr 1.25fr; gap: 30px; }
.task-col { display: flex; flex-direction: column; min-width: 0; }
/* 描述框吃掉右列的剩余高度，底边与左列对齐。
   靠的是 grid 默认的 align-items: stretch —— 两列等高，右列才有确定高度给它撑。 */
.task-col.brief textarea { flex: 1 1 auto; }
@media (max-width: 940px) { .task-grid { grid-template-columns: 1fr; gap: 0; } }
.task .del { visibility: hidden; }
#tasks .task:not(:only-child) .del { visibility: visible; }

/* 「未完成」的说明：是提示不是错误——轮次跑完了但交付件没齐，
   跟真正报错要能一眼分开 */
.ev.warn {
  border-left: 2px solid #d97706; background: rgba(217,119,6,.06);
  color: #92400e; padding: 9px 13px; border-radius: 0 var(--r-lg) var(--r-lg) 0;
}

/* ── 三维 / 图纸预览 ────────────────────────── */
/* 窗口高度固定了，画布就跟着填满剩余空间，不再自己写死高度 */
.v3d { display: flex; flex-direction: column; gap: 10px; height: 100%; }
.v3d canvas, .v2d {
  flex: 1 1 auto; min-height: 0; width: 100%; display: block;
  border: 1px solid var(--border); border-radius: var(--r-xl);
  background: #f9fafb; cursor: grab; touch-action: none;
}
.v3d canvas:active, .v2d:active { cursor: grabbing; }
.v2d { overflow: hidden; background: #fff; }
/* pointer-events:none —— 缩放平移全由外层 .v2d 接管。
   SVG 自己是 visiblePainted 命中，只有线条本身接事件，空白处按不动。 */
.v2d svg { width: 100%; height: 100%; display: block; pointer-events: none; }
.v3dbar { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; font-size: 12.5px; }

/* ── 分页 ───────────────────────────────────── */
.pager {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 22px; justify-content: center;
}
button.pg {
  min-width: 36px; height: 36px; padding: 0 10px; font-size: 13px;
  border-radius: var(--r-lg); background: var(--card);
}
button.pg.on {
  background: var(--grad-r); border-color: transparent; color: #fff;
  box-shadow: var(--shadow-accent);
}
button.pg.on:hover { background: var(--grad-r); }

.head .headcol {
  display: flex; flex-direction: column; align-items: flex-end; gap: 8px; flex: 0 0 auto;
}
button.fold {
  width: 26px; height: 22px; padding: 0; border-radius: 999px;
  border-color: transparent; background: var(--muted);
  color: var(--muted-foreground); line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
button.fold:hover:not(:disabled) { background: rgba(0,82,255,.1); color: var(--accent); }
/* 用伪元素画箭头：收起时朝下（点开），展开时朝上（点合） */
button.fold::before {
  content: ""; width: 7px; height: 7px; margin-top: -3px;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: rotate(-135deg); transition: transform .2s ease-out;
}
.card.folded button.fold::before { transform: rotate(45deg); margin-top: -5px; }

/* 收起：进度条及以下整块藏起来，只留类型、标题、描述和状态 */
.card.folded .cbody { display: none; }
/* 收起时把卡片压紧一点，一屏能多看几张 */
.card.folded { padding-bottom: 18px; }
.card.folded h2 { margin-top: 8px; }

/* ── 任务行（新建卡里可增删的一条）───────────── */
.task {
  border: 1px solid var(--border); border-radius: var(--r-xl);
  padding: 16px 18px 18px; margin-top: 14px; background: rgba(241,245,249,.35);
  animation: fadeUp .45s cubic-bezier(.16,1,.3,1);
}
.task .label.sm { padding: 3px 11px; }
.task .label.sm span { font-size: 10px; }
.task label.f:first-of-type { margin-top: 12px; }

/* 左边一列放类型 / 名称 / 参考文件，右边整列留给描述。
   描述是这张表里唯一需要大面积输入的字段，单列排下来它只能是中间一个矮框，
   下面还压着参考文件。分两列之后它能撑满整列高度。 */
.task-grid { display: grid; grid-template-columns: 1fr 1.25fr; gap: 30px; }
.task-col { display: flex; flex-direction: column; min-width: 0; }
/* 描述框吃掉右列的剩余高度，底边与左列对齐。
   靠的是 grid 默认的 align-items: stretch —— 两列等高，右列才有确定高度给它撑。 */
.task-col.brief textarea { flex: 1 1 auto; }
@media (max-width: 940px) { .task-grid { grid-template-columns: 1fr; gap: 0; } }
.task .del { visibility: hidden; }
#tasks .task:not(:only-child) .del { visibility: visible; }

/* 「未完成」的说明：是提示不是错误——轮次跑完了但交付件没齐，
   跟真正报错要能一眼分开 */
.ev.warn {
  border-left: 2px solid #d97706; background: rgba(217,119,6,.06);
  color: #92400e; padding: 9px 13px; border-radius: 0 var(--r-lg) var(--r-lg) 0;
}

/* ── 三维 / 图纸预览 ────────────────────────── */
/* 窗口高度固定了，画布就跟着填满剩余空间，不再自己写死高度 */
.v3d { display: flex; flex-direction: column; gap: 10px; height: 100%; }
.v3d canvas, .v2d {
  flex: 1 1 auto; min-height: 0; width: 100%; display: block;
  border: 1px solid var(--border); border-radius: var(--r-xl);
  background: #f9fafb; cursor: grab; touch-action: none;
}
.v3d canvas:active, .v2d:active { cursor: grabbing; }
.v2d { overflow: hidden; background: #fff; }
/* pointer-events:none —— 缩放平移全由外层 .v2d 接管。
   SVG 自己是 visiblePainted 命中，只有线条本身接事件，空白处按不动。 */
.v2d svg { width: 100%; height: 100%; display: block; pointer-events: none; }
.v3dbar { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; font-size: 12.5px; }

/* ── 三步流程条 ───────────────────────────────────────────
   和阶段条（.steps）共用 .link 连线，但节点样式分开：
   流程条是「哪一步」，阶段条是「这一步内部走到哪」，
   两条并排画在一起会分不清层级，所以流程条更粗、带锁标记。

   变量名照 :root 里真实定义的来（--border / --muted-foreground / --card），
   不要凭印象写 --line / --fg 这类——变量不存在时整条 shorthand 会失效，
   border 直接变成 none，肉眼还看不出是哪里错了。 */
.flow { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; flex-wrap: wrap; }
.fstep { display: flex; align-items: center; gap: 6px; padding: 4px 10px;
         border: 1px solid var(--border); border-radius: 999px;
         font-size: 12px; color: var(--muted-foreground); background: var(--card); }
.fstep i { width: 7px; height: 7px; border-radius: 50%; background: var(--border); }
.fstep.done { color: var(--foreground); border-color: var(--accent-secondary); }
.fstep.done i { background: var(--accent-secondary); }
.fstep.cur { color: var(--foreground); border-color: var(--accent); font-weight: 600; }
.fstep.cur i { background: var(--accent); }
.fstep .lock { font-size: 11px; font-weight: 500; padding: 1px 6px;
               border-radius: 4px; background: var(--muted);
               color: var(--muted-foreground); }
.fstep.done .lock { background: var(--accent); color: var(--accent-foreground); }
/* 免费标签比额度标签轻一档：它是「安心」信息，不该抢眼。
   只用确实存在的变量。 */
.fstep .freetag {
  font-size: 11px; font-weight: 500; padding: 1px 6px;
  border-radius: 999px; background: var(--muted); color: var(--muted-foreground);
}

/* 闸门：这一步做完之后，进不进下一步由用户点 */
.gate { display: flex; align-items: center; gap: 10px; margin: 10px 0 4px;
        padding: 9px 12px; border: 1px dashed var(--border); border-radius: 8px;
        font-size: 12px; flex-wrap: wrap; background: var(--background); }

/* 服务跑旧代码时的警告横幅。只用确实存在的变量——
   写错变量名整条简写会失效且肉眼看不出来，栽过一次。 */
.stale {
  position: sticky;
  top: 0;
  z-index: 999;
  padding: 10px 16px;
  background: #b45309;
  color: #fff;
  font: 500 13px/1.5 var(--ui);
  border-bottom: 1px solid var(--border);
}

/* Word 预览。排版照着 export_deliverables.py 的样式表来，
   让预览和真在 Word 里打开看到的是同一份东西。
   只用确实存在的变量——编变量名会让整条简写静默失效，栽过一次。 */
.docx { max-width: 46em; margin: 0 auto; color: var(--foreground); line-height: 1.75; }
.docx h1 { font-size: 20px; font-weight: 600; text-align: center; margin: 4px 0 22px; }
.docx h2 { font-size: 17px; font-weight: 600; margin: 26px 0 10px; }
.docx h3 { font-size: 15px; font-weight: 600; margin: 20px 0 8px; }
.docx h4 { font-size: 14px; font-weight: 600; margin: 16px 0 6px; }
.docx p { margin: 0 0 9px; font-size: 14px; }
.docx strong { font-weight: 600; }
.docx table {
  border-collapse: collapse; width: 100%; margin: 10px 0 18px;
  font-size: 13px; table-layout: fixed;
}
.docx th, .docx td {
  border: 1px solid var(--border); padding: 6px 9px;
  vertical-align: top; word-break: break-word;
}
.docx th { background: var(--muted); font-weight: 600; text-align: center; }
.docx td p, .docx th p { margin: 0; font-size: 13px; }
.docx figure { margin: 18px 0; text-align: center; }
.docx figure img {
  max-width: 100%; height: auto;
  border: 1px solid var(--border); border-radius: 4px; background: #fff;
}
.docx figcaption {
  margin-top: 7px; font-size: 12.5px; color: var(--muted-foreground);
}
/* 段落对齐由 docx2html 按原文档还原 */
.docx .c { text-align: center; }
.docx .r { text-align: right; }
.docx .j { text-align: justify; }

/* 充值弹窗。只用确实存在的变量。 */
#topupDlg { width: min(92vw, 380px); }
.dbody.topup { text-align: center; padding: 22px 24px 26px; }
.dbody.topup img {
  width: 240px; height: 240px; object-fit: contain;
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: #fff; padding: 8px;
}
.dbody.topup .tprice {
  margin: 14px 0 10px; font: 600 14px var(--ui); color: var(--foreground);
  min-height: 20px;
}
.dbody.topup .rate {
  margin: 0 0 14px; padding: 9px 12px; border-radius: 8px;
  background: var(--muted); font: 400 12.5px var(--ui); color: var(--muted-foreground);
}
.dbody.topup .rate b { color: var(--foreground); font-weight: 600; }
.dbody.topup .amts { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.dbody.topup .amts .amt {
  height: 40px; padding: 0; border-radius: 10px; font: 600 14px var(--ui);
  border: 1px solid var(--border); background: var(--card); color: var(--foreground);
}
.dbody.topup .amts .amt:hover:not(:disabled) {
  border-color: var(--accent); background: var(--card);
}
.dbody.topup .amts .amt.on {
  border-color: var(--accent); background: var(--accent); color: var(--accent-foreground);
}
.dbody.topup .f { display: block; text-align: left; font: 500 12.5px var(--ui);
  color: var(--muted-foreground); margin-bottom: 6px; }
.dbody.topup input { text-align: center; }
.dbody.topup .muted {
  margin: 0; font: 400 12.5px/1.7 var(--ui); color: var(--muted-foreground);
  text-align: left;
}
.chip.as-btn.primary {
  background: var(--accent); color: var(--accent-foreground); border-color: var(--accent);
}
button.chip.primary:hover:not(:disabled) {
  background: var(--accent); border-color: var(--accent);
  color: var(--accent-foreground); filter: brightness(1.12);
}

/* 合规声明。要看得见，但不该抢主操作的注意力——
   比正文小一档、用弱色，加一条左边线把它和说明文字区分开。 */
.legal {
  margin: 10px 0 0; padding: 9px 12px;
  border-left: 3px solid var(--border); border-radius: 0 6px 6px 0;
  background: var(--muted);
  font: 400 12px/1.75 var(--ui); color: var(--muted-foreground);
}
.legal b { font-weight: 600; color: var(--foreground); }

/* 余额明细表。金额右对齐并用等宽字体——一列数字对不齐就看不出大小。 */
table.ledger { border-collapse: collapse; width: 100%; font: 400 13px var(--ui); }
table.ledger th, table.ledger td {
  padding: 9px 12px; border-bottom: 1px solid var(--border); text-align: left;
  vertical-align: top;
}
table.ledger th {
  position: sticky; top: 0; background: var(--card); z-index: 1;
  font-weight: 600; color: var(--muted-foreground); font-size: 12px;
}
table.ledger td.t { white-space: nowrap; color: var(--muted-foreground); font-size: 12.5px; }
table.ledger td.amt {
  text-align: right; white-space: nowrap; font: 500 13px var(--mono);
}
table.ledger td.amt.in { color: #15803d; }
table.ledger td.amt.out { color: var(--foreground); }
table.ledger td.note { color: var(--muted-foreground); word-break: break-all; }
table.ledger tr:last-child td { border-bottom: 0; }

/* 登录页底部那一行：左边「还没有账号？注册一个」，右边「忘记密码」。
   两边各自成组，中间用 space-between 撑开——不这么写它们会挤成一堆。 */
.loginfoot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 16px; font: 400 13px var(--ui); color: var(--muted-foreground);
}
.loginfoot a { color: var(--accent); text-decoration: none; }
.loginfoot a:hover { text-decoration: underline; }

/* 账号设置弹窗。比查看器窄得多——里面只有几个输入框，撑成 1160 宽会很空。 */
#acctDlg { width: min(440px, 92vw); }
.dbody.acct { padding: 20px 22px 24px; }
.dbody.acct h3 { margin: 0 0 4px; font: 600 14px var(--ui); color: var(--foreground); }
.dbody.acct h3 + .muted { margin: 0 0 10px; font-size: 12.5px; }
.dbody.acct .f { display: block; margin: 12px 0 6px; font: 500 12.5px var(--ui); }
.dbody.acct button { margin-top: 14px; }
.dbody.acct hr { margin: 22px 0 18px; border: 0; border-top: 1px solid var(--border); }

/* 参考文件那一段。原来标题和一长串格式说明挤在同一行，读起来像一句话。
   拆成两行：第一行是这一栏叫什么，第二行才是提示。 */
.fhint {
  margin: -2px 0 8px; font: 400 12px/1.6 var(--ui); color: var(--muted-foreground);
}
/* 原生 file 输入的按钮由浏览器画，和页面上任何按钮都不像。
   用 ::file-selector-button 把它换成和「＋ 新建任务」（button.sm）同一套。 */
input[type=file] {
  width: 100%; border: 1px solid var(--border); border-radius: var(--r-xl);
  background: rgba(241,245,249,.4); color: var(--muted-foreground);
  font: 400 13px var(--ui); padding: 9px 12px;
}
input[type=file]::file-selector-button {
  height: 34px; padding: 0 13px; margin-right: 12px;
  font: 500 13px var(--ui); border-radius: var(--r-lg); cursor: pointer;
  border: 1px solid var(--border); background: transparent; color: var(--foreground);
  transition: all .2s ease-out;
}
input[type=file]::file-selector-button:hover {
  background: var(--muted); border-color: rgba(0,82,255,.3);
}
input[type=file]:focus { outline: none; border-color: var(--accent); }

/* 标识。略高于文字锁定，等距零件、微网格和顶栏点阵形成一套工程视觉语言。 */
.logo {
  flex: 0 0 auto; display: block; border-radius: 12px;
  filter: drop-shadow(0 7px 12px rgba(0,82,255,.24));
}
.topbar .inner > .logo {
  width: 44px; height: 44px; margin-right: 2px;
  transition: transform .3s cubic-bezier(.16,1,.3,1), filter .3s ease-out;
}
.topbar .inner:hover > .logo {
  transform: translateY(-1px) rotate(-1deg);
  filter: drop-shadow(0 9px 16px rgba(0,82,255,.34));
}
.logo.big { margin: 0 auto; border-radius: 16px; }
@media (max-width: 700px) {
  .topbar .inner > .logo { width: 40px; height: 40px; }
}
