/* ==========================================================================
   勤思 AI 工作中枢 — 共享视觉系统 v2
   --------------------------------------------------------------------------
   视觉语言参考（已蒸馏，非复制）：
     · 近黑画布 + 高饱和强调色，取代蓝调暗底 + 粉薄荷绿
     · 真实毛玻璃材质：渐变描边 + 内高光 + 分层投影 + 饱和提升
     · 卡片内嵌极光渐变光斑作为「配图」，取代全屏模糊渐变洗色
     · 粒子景深场（Canvas 2D，零依赖）承担空间层
     · 3D 透视：卡片堆叠与 Pipeline 步骤具备真实景深
   分层（自下而上）：
     body 底色 → body::before 轨迹网格 → #field 粒子景深场
     → body::after 暗角 → .shell 内容 → .dock / .toast
   状态 token（只驱动 CSS，不新增后端状态）：
     idle / active / awaiting-human / verified / blocked
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Design tokens
   -------------------------------------------------------------------------- */
:root {
  /* 画布：近黑，极轻的冷调，不用蓝调暗底 */
  --bg: #050607;
  --bg2: #0a0c0d;

  /* 玻璃表面 —— 不透明度需留出余量，让背后的生成式景观透出来 */
  --panel: rgba(14, 24, 30, .40);
  --panel2: rgba(20, 28, 34, .80);
  /* v6：磨砂玻璃参数整体下调 —— 用户反馈「霓虹玻璃/塑料感」来自高光与边线过强 */
  --glass-hi: rgba(255, 255, 255, .030);
  --glass-lo: rgba(0, 0, 0, .28);

  --line: rgba(255, 255, 255, .05);
  --line2: rgba(255, 255, 255, .09);

  /* 文本 */
  --text: #eef2f0;
  --muted: #7f8983;
  --muted2: #545c58;

  /* 强调色：高饱和，取代粉薄荷 */
  --green: #3ee07f;
  --blue: #4fd1e8;
  --purple: #8b7cf6;
  --yellow: #f5c451;
  --red: #f2728c;
  --idle: #5d6663;

  --shadow: 0 34px 80px -24px rgba(0, 0, 0, .92);

  /* 由 app.js 写入 */
  --mx: 0px;
  --my: 0px;

  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
    "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
}

* { box-sizing: border-box; }

html { background: var(--bg); }

/* --------------------------------------------------------------------------
   2. Base field
   -------------------------------------------------------------------------- */
body {
  margin: 0;
  min-height: 100vh;
  color: var(--text);
  /* v6：去掉页面级彩色光晕，改为极淡的近中性环境光。
     用户反馈「彩色光晕 + 玻璃透明卡片 + 边缘高光」叠加会形成透明塑料板的观感。 */
  background:
    radial-gradient(120% 70% at 50% -10%, rgba(255,255,255,.018), transparent 60%),
    radial-gradient(90% 60% at 85% 110%, rgba(255,255,255,.012), transparent 62%),
    var(--bg);
  letter-spacing: .005em;
  -webkit-font-smoothing: antialiased;
}

/* --------------------------------------------------------------------------
   3. 轨迹网格层（极淡，低速漂移）
   -------------------------------------------------------------------------- */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  /* v6：透明度从 .5 降到 .25，网格感更含蓄 —— 配合磨砂整体材质 */
  opacity: .25;
  background-image:
    linear-gradient(rgba(255, 255, 255, .012) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .009) 1px, transparent 1px);
  background-size: 96px 96px, 96px 96px;
  -webkit-mask-image: radial-gradient(110% 85% at 50% 0%, #000 20%, transparent 82%);
  mask-image: radial-gradient(110% 85% at 50% 0%, #000 20%, transparent 82%);
  animation: traceDrift 18s ease-in-out infinite alternate;
}

/* --------------------------------------------------------------------------
   4. 粒子景深场 + Spline 3D 背景层
   -------------------------------------------------------------------------- */

/* Spline 3D 场景：最底层全屏背景。
   pointer-events: none 保证不吞掉内容区的点击；
   交互靠 events-target="global"，由 viewer 自行监听 window 上的指针事件。
   未就绪时保持透明，加载完成后淡入，失败则完全不出现（由 Canvas 景观层兜底）。 */
#spline-bg {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.4s ease;
}
#spline-bg.is-ready { opacity: 1; }

#field {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
  pointer-events: none;
}

/* 暗角：压住画面边缘，让中心内容浮起来。
   注意：不能压得太狠，否则边缘的生成式景观会被吃掉 */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  background:
    radial-gradient(130% 100% at 50% 45%, transparent 58%, rgba(5, 6, 7, .55) 100%);
}

/* Spline 3D 背景激活时：页面自身背景与暗角让位，
   让 3D 场景在所有「没有绘制」的地方透出来（玻璃面板本身是半透明的，会自然透出）。 */
body.spline-on { background: transparent; }
body.spline-on::after {
  background:
    radial-gradient(135% 105% at 50% 45%, transparent 62%, rgba(5, 6, 7, .42) 100%);
}
body.spline-on {
  --panel: rgba(12, 20, 26, .34);
  --panel2: rgba(18, 26, 32, .74);
}

/* --------------------------------------------------------------------------
   4b. 材质层 —— 消除「塑料感」
   --------------------------------------------------------------------------
   塑料感的可修成因有三条，各对应一层（元素由 app.js 注入，全站统一）：
     #atmo-grain  胶片颗粒（overlay）—— 微观噪声，主药。
                  完美平滑的渐变会被人眼读成「塑料/矢量」，真实材质一定有噪声。
     #atmo-light  体积光斑（screen）—— 让画面有空气与介质感，解决高光过硬。
     #atmo-fringe 边缘色散（screen）—— 极轻微红蓝分离，给数字渐变一点镜头味。
   三者皆 pointer-events:none。颗粒层 z-index 15（内容 2 < 15 < dock 20），
   因此覆盖内容但绝不遮挡交互控件。
   -------------------------------------------------------------------------- */
#atmo-light,
#atmo-fringe,
#atmo-grain {
  position: fixed;
  pointer-events: none;
}

/* 体积光：v6 调整为低饱和、加大模糊、更柔的环境光，避免彩色穿透卡片形成「塑料感」。
     只留一个很弱的偏冷中性光，作为层次提示而非主体装饰。 */
#atmo-light {
  inset: -18%;
  z-index: 1;
  mix-blend-mode: screen;
  background:
    radial-gradient(58% 46% at 32% 24%, rgba(140, 165, 180, .07), transparent 72%),
    radial-gradient(70% 56% at 72% 82%, rgba(120, 135, 150, .05), transparent 74%);
  filter: blur(36px);
  animation: atmoDrift 42s ease-in-out infinite alternate;
}

/* 边缘色散：只在画面外围出现，中心保持干净不影响阅读 */
#atmo-fringe {
  inset: 0;
  z-index: 1;
  mix-blend-mode: screen;
  background:
    radial-gradient(128% 96% at 50% 50%, transparent 56%, rgba(255, 48, 96, .055) 90%, transparent 100%),
    radial-gradient(132% 100% at 50% 50%, transparent 60%, rgba(48, 140, 255, .05) 94%, transparent 100%);
}

/* 胶片颗粒：SVG feTurbulence 静态噪声 + 8 帧抖动，模拟真实胶片颗粒的随机位移 */
#atmo-grain {
  inset: -90px;
  z-index: 15;
  /* v19.3：0.55 → 0.30。
     原 0.55 是按「面板」调的，但 overlay 混合在 *亮* 底上会被放大：
     实测同一次渲染里，机器人额头白区被这层从 L=155.5 抬到 L=179.5，
     高频噪声从 6.7/255 加到 10.9/255 —— 光滑亮面上一旦有均匀噪声，
     读起来就是「砂纸/磨砂塑料」，正是要去的塑料感。
     降到 0.30 后面板仍有 2.92/255（基线 0.36，仍高 8 倍，胶片质感保住），
     机器人亮面噪声回落到接近纯渲染水平。
     扫描数据（面板 / 机器人额头）：0.55→3.58 / 10.9 · 0.30→2.92 / 6.9 */
  opacity: .30;
  mix-blend-mode: overlay;
  /* feTurbulence 的 alpha 通道本身也是随机值，会让噪声半透明、幅度不可控。
     用 feComponentTransfer 把 alpha 强制为 1，只保留灰度起伏；
     再叠一层 feColorMatrix 去饱和，得到中性灰噪声（中值 0.5 → overlay 下不偏色）。 */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='table' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 220px 220px;
  animation: grainShift .72s steps(1, end) infinite;
}

/* 3D 场景的调色：Spline 默认材质高光偏硬、饱和度偏高，
   降饱和压亮度后更接近「环境」而非「前景玩具」。
   只用色彩矩阵类滤镜（便宜），不用 blur（每帧重算，成本高）。 */
#spline-bg {
  filter: saturate(.78) brightness(.84) contrast(.95);
}

/* --------------------------------------------------------------------------
   5. Shell & layout
   -------------------------------------------------------------------------- */
.shell {
  max-width: 1500px;
  margin: 0 auto;
  padding: 28px 44px 60px 128px;
  position: relative;
  z-index: 2;
  animation: pageEnter .6s cubic-bezier(.16, .84, .3, 1) both;
}

.is-leaving .shell { animation: pageLeave .32s cubic-bezier(.4, 0, .7, .2) both; }

.dashboard-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 22px;
}

/* --------------------------------------------------------------------------
   5b. Bento Grid — 仅 Workspace 使用的不对称布局
   -------------------------------------------------------------------------- */
.dashboard-grid.bento {
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto auto auto auto auto;
  grid-template-areas:
    "hero   hero    command"
    "hero   hero    status"
    "hero   hero    activity"
    "metrics metrics metrics"
    "pulse  pulse   notes";
  gap: 18px;
}

.bento-hero { grid-area: hero; }
.bento-command { grid-area: command; }
.bento-status { grid-area: status; }
.bento-activity { grid-area: activity; }
.bento-metrics { grid-area: metrics; }
.bento-pulse { grid-area: pulse; }
.bento-notes { grid-area: notes; }

/* Bento 子元素不继承 12 列的 span，由 grid-area 接管 */
.dashboard-grid.bento .hero,
.dashboard-grid.bento .command-panel,
.dashboard-grid.bento .status-zone,
.dashboard-grid.bento .activity-panel,
.dashboard-grid.bento .panel.wide,
.dashboard-grid.bento .panel.narrow {
  min-height: auto;
}
/* 强制 grid-area 覆盖 base 的 grid-column（同等特异度下后定义者胜） */
.bento-hero { grid-area: hero !important; }
.bento-command { grid-area: command !important; }
.bento-status { grid-area: status !important; }
.bento-activity { grid-area: activity !important; }
.bento-metrics { grid-area: metrics !important; }
.bento-pulse { grid-area: pulse !important; }
.bento-notes { grid-area: notes !important; }

/* Bento 卡片入场动画：错开、带轻微 3D 俯仰 */
.dashboard-grid.bento > section {
  animation: bentoIn .75s cubic-bezier(.16, .84, .3, 1) both;
}
.dashboard-grid.bento > section:nth-child(1) { animation-delay: .04s; }
.dashboard-grid.bento > section:nth-child(2) { animation-delay: .10s; }
.dashboard-grid.bento > section:nth-child(3) { animation-delay: .16s; }
.dashboard-grid.bento > section:nth-child(4) { animation-delay: .22s; }
.dashboard-grid.bento > section:nth-child(5) { animation-delay: .28s; }
.dashboard-grid.bento > section:nth-child(6) { animation-delay: .34s; }
.dashboard-grid.bento > section:nth-child(7) { animation-delay: .40s; }

a { color: inherit; }

/* --------------------------------------------------------------------------
   6. 毛玻璃材质基元
   -------------------------------------------------------------------------- */
.glass {
  position: relative;
  isolation: isolate;
  border-radius: 26px;
  background:
    linear-gradient(158deg, var(--glass-hi) 0%, rgba(255, 255, 255, .012) 38%, var(--glass-lo) 100%),
    var(--panel);
  backdrop-filter: blur(30px) saturate(150%);
  -webkit-backdrop-filter: blur(30px) saturate(150%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .07),
    inset 0 -1px 0 rgba(0, 0, 0, .5),
    0 1px 0 rgba(0, 0, 0, .4),
    var(--shadow);
}

/* 渐变描边：用 mask 挖空，得到上亮下暗的一圈边光 */
.glass::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  background: linear-gradient(152deg,
    rgba(255, 255, 255, .26) 0%,
    rgba(255, 255, 255, .05) 26%,
    rgba(255, 255, 255, 0) 52%,
    rgba(62, 224, 127, .1) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  z-index: 2;
}

.glass > :not(.blob) { position: relative; z-index: 1; }

/* 卡片内的极光光斑「配图」 */
.blob {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  filter: blur(30px);
  opacity: .55;
  z-index: 0;
}
.blob.green { background: radial-gradient(circle, rgba(62, 224, 127, .85), transparent 68%); }
.blob.violet { background: radial-gradient(circle, rgba(139, 124, 246, .8), transparent 68%); }
.blob.cyan { background: radial-gradient(circle, rgba(79, 209, 232, .75), transparent 68%); }
.blob.amber { background: radial-gradient(circle, rgba(245, 196, 81, .7), transparent 68%); }
.blob.rose { background: radial-gradient(circle, rgba(242, 114, 140, .7), transparent 68%); }

/* --------------------------------------------------------------------------
   7. Topbar / brand
   -------------------------------------------------------------------------- */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 34px;
}

.brand { display: flex; align-items: center; gap: 15px; }

.brand-mark {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: 14px;
  color: #04150b;
  font-weight: 800;
  font-size: 17px;
  background: linear-gradient(150deg, #6ef2a4, var(--green) 55%, #22a75c);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .5),
    inset 0 -2px 6px rgba(0, 0, 0, .25),
    0 8px 26px -6px rgba(62, 224, 127, .55);
}

.brand-title { font-size: 15px; font-weight: 650; letter-spacing: -.01em; }

.eyebrow,
.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .18em;
  text-transform: uppercase;
  font-size: 9.5px;
  color: var(--muted2);
}

.eyebrow { margin-bottom: 10px; }

.top-actions { display: flex; gap: 10px; align-items: center; }

.pill,
.icon-button {
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .03);
  color: var(--muted);
  border-radius: 999px;
  padding: 9px 14px;
  font-size: 10.5px;
  letter-spacing: .06em;
  backdrop-filter: blur(14px);
}

.icon-button {
  cursor: pointer;
  border-radius: 13px;
  font-size: 19px;
  line-height: 1;
  padding: 5px 11px;
}

/* --------------------------------------------------------------------------
   8. Live island + 状态 token
   -------------------------------------------------------------------------- */
.live-island {
  display: flex;
  align-items: center;
  gap: 15px;
  width: max-content;
  max-width: 100%;
  padding: 12px 18px;
  border-radius: 20px;
  margin-bottom: 22px;
  position: relative;
  isolation: isolate;
  background:
    linear-gradient(158deg, rgba(255, 255, 255, .07), rgba(255, 255, 255, .015) 40%, rgba(0, 0, 0, .3)),
    rgba(18, 22, 21, .5);
  backdrop-filter: blur(30px) saturate(150%);
  -webkit-backdrop-filter: blur(30px) saturate(150%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .09),
    0 20px 50px -18px rgba(0, 0, 0, .85);
  animation: islandSettle .9s cubic-bezier(.16, .84, .3, 1) both;
  transition: box-shadow .6s ease;
}
.live-island::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  background: linear-gradient(150deg, rgba(255, 255, 255, .22), rgba(255, 255, 255, .03) 40%, transparent 70%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
}

.island-copy { min-width: 210px; }
.island-title { font-size: 12px; font-weight: 650; letter-spacing: -.005em; }
.island-sub { font-size: 10.5px; color: var(--muted); margin-top: 5px; }

.island-meter {
  width: 96px;
  height: 3px;
  border-radius: 4px;
  background: rgba(255, 255, 255, .07);
  overflow: hidden;
}
.island-meter span {
  display: block;
  width: 52%;
  height: 100%;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--purple), var(--green));
  box-shadow: 0 0 16px rgba(62, 224, 127, .8);
  animation: meter 4.4s ease-in-out infinite alternate;
}

/* 灵动岛均衡器 */
.island-eq {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 14px;
  margin-left: 10px;
}
.island-eq span {
  width: 2px;
  background: linear-gradient(180deg, var(--green), var(--purple));
  border-radius: 1px;
  animation: eqBar 1.1s ease-in-out infinite;
  transform-origin: bottom;
}
.island-eq span:nth-child(1) { height: 5px; animation-delay: 0s; }
.island-eq span:nth-child(2) { height: 9px; animation-delay: .18s; }
.island-eq span:nth-child(3) { height: 13px; animation-delay: .36s; }
.island-eq span:nth-child(4) { height: 7px; animation-delay: .12s; }

.live-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--idle);
  display: inline-block;
  flex: none;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, .04);
  transition: background .45s ease, box-shadow .45s ease;
}

.state-chip {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 9.5px;
  letter-spacing: .14em;
  color: var(--idle);
  transition: color .45s ease;
}

/* --- 状态 token ---------------------------------------------------------- */
[data-state="idle"] .live-dot {
  background: var(--idle);
  box-shadow: 0 0 0 4px rgba(255, 255, 255, .04), 0 0 16px rgba(93, 102, 99, .6);
  animation: statusBreath 5.4s ease-in-out infinite;
}
[data-state="idle"] .state-chip { color: var(--idle); }

[data-state="active"] .live-dot {
  background: var(--purple);
  box-shadow: 0 0 0 4px rgba(139, 124, 246, .1), 0 0 20px rgba(139, 124, 246, .9);
  animation: statusBreath 2.4s ease-in-out infinite;
}
[data-state="active"] .state-chip { color: var(--purple); }

[data-state="awaiting-human"] .live-dot {
  background: var(--yellow);
  box-shadow: 0 0 0 4px rgba(245, 196, 81, .1), 0 0 20px rgba(245, 196, 81, .85);
  animation: statusBreath 3.4s ease-in-out infinite;
}
[data-state="awaiting-human"] .state-chip { color: var(--yellow); }
[data-state="awaiting-human"] .live-island {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .09),
    inset 0 0 0 1px rgba(245, 196, 81, .2),
    0 20px 50px -18px rgba(0, 0, 0, .85),
    0 0 60px -14px rgba(245, 196, 81, .35);
}

[data-state="verified"] .live-dot {
  background: var(--green);
  box-shadow: 0 0 0 4px rgba(62, 224, 127, .1), 0 0 20px rgba(62, 224, 127, .95);
  animation: statusBreath 3.4s ease-in-out infinite;
}
[data-state="verified"] .state-chip { color: var(--green); }

/* blocked：不呼吸。静止表达「已停止」，避免被误读为仍在运行 */
[data-state="blocked"] .live-dot {
  background: var(--red);
  box-shadow: 0 0 0 4px rgba(242, 114, 140, .1), 0 0 18px rgba(242, 114, 140, .8);
  animation: none;
}
[data-state="blocked"] .state-chip { color: var(--red); }
[data-state="blocked"] .live-island {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .09),
    inset 0 0 0 1px rgba(242, 114, 140, .22),
    0 20px 50px -18px rgba(0, 0, 0, .85);
}

/* --------------------------------------------------------------------------
   9. Hero + AI Core
   -------------------------------------------------------------------------- */
.hero {
  grid-column: span 8;
  min-height: 396px;
  padding: 52px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  overflow: hidden;
  container-type: inline-size;
  position: relative;
  isolation: isolate;
  border-radius: 26px;
  background:
    linear-gradient(158deg, var(--glass-hi) 0%, rgba(255, 255, 255, .012) 38%, var(--glass-lo) 100%),
    var(--panel);
  backdrop-filter: blur(30px) saturate(150%);
  -webkit-backdrop-filter: blur(30px) saturate(150%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .07),
    inset 0 -1px 0 rgba(0, 0, 0, .5),
    0 1px 0 rgba(0, 0, 0, .4),
    var(--shadow);
}

/* 内容层抬升。必须排除装饰层（.blob / .ai-core / .robot-stage），
   否则会覆盖它们的 position:absolute，使装饰层回到文档流并撑高容器 */
.hero > :not(.blob):not(.ai-core):not(.robot-stage) { position: relative; z-index: 3; }

/* ---------------------------------------------------------------- Robot (Hero 背景)
   自绘 SVG 占位 或 Spline 3D 嵌入；URL 配置后自动用 3D 接管。
   默认被当作"背景层"放在内容之下：低不透明度、不抢主视觉、不夸张动效。 */
.robot-stage {
  position: absolute;
  right: -8%;
  bottom: -2%;
  width: 78%;
  max-width: 760px;
  height: 110%;
  pointer-events: none;
  z-index: 1;
  opacity: 0.72;            /* 背景化：降低不透明度让位内容 */
  transform: translate3d(calc(var(--mx, 0px) * .15), calc(var(--my, 0px) * .08), 0);
  transition: transform 1.2s cubic-bezier(.16,.84,.3,1);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
}
/* 移除"打拳"感：呼吸动画 + LED 闪烁都禁掉，让它静下来 */
.robot-stage #robot .breathing, .robot-stage #robot .pulse-anim { display: none; }
#robot {
  width: 100%;
  height: auto;
  max-height: 100%;
  display: block;
  filter: drop-shadow(0 28px 48px rgba(0,0,0,.35));
  overflow: visible;
}
.robot-floor { display: none; }   /* 背景化不需要脚下光斑 */
/* 3D 接管时（Spline 或 Three.js）：占满 stage、露出透明背景 */
#robot-3d,
#robot-3d-canvas {
  width: 100%;
  height: 100%;
  position: absolute;
  inset: 0;
  display: block;
  pointer-events: none;
}
/* v14：3D canvas 单独放开点击 —— pointer-events 可在子元素上覆盖父级的 none。
   内容层是 z-index 3（见上方 .hero > :not(...)），在 canvas 之上，按钮照常可点。
   机器人因此能响应点击：核心爆闪 + 整体后仰回弹。 */
#robot-3d-canvas { pointer-events: auto; cursor: pointer; }

/* v14：非 home 页（hero.compact）—— 数据优先，但机器人也要有存在感。
   单独放大 stage + 略微提高不透明度，让精模机器人在每个数据页都看得见。 */
.hero.compact .robot-stage {
  width: 70%;
  height: 115%;
  right: -5%;
  bottom: -4%;
  opacity: 0.62;
}

/* v20 / v20.2：首页（完整 hero）的机器人尺寸与位置。
   --------------------------------------------------------------------------
   起因是「hero 那句品牌绿看着很脏」。量化之后发现绿色本身没问题 ——
   它在深色底上对比度 9.98:1，是页面上最清楚的一行字。
   真正的问题是**空间打架**：机器人（投影 328px 宽）正好横穿标题第二行，
   把它从中间切断（1560 视口下绿字 33.2% 的面积被机器人 alpha 覆盖，
   最差处对比度 1.00:1，< 3:1 的像素占 22.31%，而 WCAG AA 大号文字要求 3:1）。

   v20 的做法是「两边各让一步」：标题上限 66→56px、机器人 78%→46%。
   它把 1560 视口修好了，但**只在 1560 成立** —— v20.2 跨视口扫描发现，
   视口 620~1440 下机器人仍然压住绿字 48~103px（<3:1 占比 4.7%~13.6%）。

   根因是结构性的，不是调参能绕开的：
     .hero 的容器查询单位取的是**内容盒**（heroW − 104），
     所以 8.6cqw 在未触顶时让标题恒定占据内容宽的 90.3%，
     机器人只剩 9.7% 可用 —— 无论多小都放不下。
     而字号上限 56px 只在内容宽 ≥ 651px（heroW ≥ 755）时才生效，
     偏偏 620~1440 视口全都落在「未触顶」区。

   v20.2 的解法分两段（数据见 HERO_RESPONSIVE_REPORT.md）：
     · 基线（容器 ≥ 775px）：stage 58%，机器人投影 232px、右缘 1033 < hero 右缘 1037
     · 窄容器（≤ 774px）：见下方 @supports 里的 @container 分支 ——
       同时收窄标题（8.6→7cqw）与机器人（58%），两者才都放得下

   实测（prefers-reduced-motion 冻结姿态 + 同 tick render/toDataURL 合成，
   避免 preserveDrawingBuffer:false 下的截图相位竞争）：

     视口   机器人        压绿字      绿字对比度最差   <3:1
     620    128→163px     95→0px      1.00→4.12       13.56%→0
     768    156→199px    103→0px      ——→4.6 量级      8%→0
     1100   124→157px     78→0px      1.00→4.53        8.37%→0
     1280   151→190px    102→0px      1.00→2.29       11.91%→0.01%
     1440   175→221px     48→0px      1.15→5.65        4.65%→0
     1560   184→232px     20→0px      2.11→5.76        0.36%→0
     1920   184→232px     20→0px      （同 1560 的相对几何）
   八个视口全部做到「零重叠 + 机器人完整可见」，且机器人在每个视口都更大。

   作用域严格限定 .hero:not(.compact)：另外 5 个数据页是 .hero.compact，
   标题短、机器人在右，实测本就不重叠（tasks 页标题右端 415 vs 机器人左缘 ≈760），
   套上这套尺寸反而会把它们的机器人缩小。 */
.hero:not(.compact) .robot-stage {
  width: 58%;
  max-width: 560px;
  right: -15.3%;
  bottom: -20%;
  height: 131.2%;
}

/* v19.4：机器人左缘淡出 —— 替代 v18 的深色遮罩（原 .hero .robot-stage::before）。
   深色遮罩实测把白机身压暗 21.8 级，且「矩形 + 线性渐变」的边界在深色玻璃上
   是一条可见的竖直硬边，读起来就是「贴了一层膜」。
   淡出改为作用在 alpha 上：白仍是白，不污染颜色，轮廓自然消失。
   只在完整 hero 生效 —— 数据页是 compact（标题短、机器人靠右），
   实测两者不重叠（tasks 页标题文字右端 415 vs 机器人左缘 ≈760），
   在那里加淡出反而会把机器人左半边切虚。 */
.hero:not(.compact) .robot-stage {
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, .34) 18%, #000 46%);
          mask-image: linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, .34) 18%, #000 46%);
}
.robot-stage.is-3d-ready svg,
.robot-stage.is-webgl-ready svg { display: none !important; }

/* v19.4：v18 那层「左深右透的深色遮罩」退役（原本是这里的 .robot-stage::before）。
   它确实压住了标题尾巴，但代价是给白色机身盖了一层灰：实测同一帧里机器人区域
   亮度被压低 14.2 级（L 63.9 → 78.1），而且遮罩是「矩形 + 线性渐变」，
   边界在深色玻璃上是一条可见的竖直硬边，读起来就是「贴了一层膜」——
   这正是「不高级」的来源之一。
   改成让机器人自身左缘淡出（见下方 .robot-stage 的 mask）：白仍然是白，
   只是 alpha 降低，不污染颜色，轮廓自然消失，也不需要任何「加暗」操作。
   影响范围已实测只落在遮罩覆盖区（hero 左半对照区 Δ 仅 +0.2）。 */

/* Three.js 半身像：底部渐隐融进页面，避免下方出现生硬截断 */
.robot-stage.is-webgl-ready {
  opacity: 0.88;
  -webkit-mask-image: linear-gradient(to bottom, #000 52%, rgba(0, 0, 0, .55) 82%, transparent 98%);
          mask-image: linear-gradient(to bottom, #000 52%, rgba(0, 0, 0, .55) 82%, transparent 98%);
}

/* 完整 hero + 3D：左缘淡出 × 底部渐隐，取交集。
   必须显式写 mask-composite —— 默认 add（相加）会让两处衰减互相抵消，等于都没淡出。 */
.hero:not(.compact) .robot-stage.is-webgl-ready {
  -webkit-mask-image:
    linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, .34) 18%, #000 46%),
    linear-gradient(to bottom, #000 52%, rgba(0, 0, 0, .55) 82%, transparent 98%);
          mask-image:
    linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, .34) 18%, #000 46%),
    linear-gradient(to bottom, #000 52%, rgba(0, 0, 0, .55) 82%, transparent 98%);
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}

/* 内容层抬升：必须排除装饰层（.blob / .ai-core / .robot-stage），
   否则会覆盖它们的 position:absolute，使装饰层回到文档流并撑高容器 */
.hero > :not(.blob):not(.ai-core):not(.robot-stage) { position: relative; z-index: 3; }

/* 内容衬底：在 hero 标题/正文下方垫一层极淡暗色玻璃，提升文本可读性 */
.hero > h1, .hero > p, .hero > .eyebrow, .hero > .hero-actions {
  text-shadow: 0 1px 18px rgba(0,0,0,.45);
}
@media (max-width: 980px) {
  .robot-stage { width: 95%; right: -2%; opacity: 0.55; }
  .hero > h1 { font-size: clamp(28px, 7cqw, 50px); }
}
@media (max-width: 640px) {
  .robot-stage { width: 110%; right: -8%; opacity: 0.42; bottom: -8%; height: 90%; }
}

/* 默认绿（idle） */
#robotAntennaTip, #robotLedCore { fill: #6ee09b; }
#robotAntennaGlow, #robotLed { fill: #6ee09b; }
/* 状态联动：active 蓝、awaiting-human 琥珀、blocked 红、verified 亮绿、idle 灰 */
[data-state="active"]          #robotAntennaTip, [data-state="active"]          #robotLedCore { fill: #6cc7ff; }
[data-state="active"]          #robotAntennaGlow, [data-state="active"]          #robotLed     { fill: #6cc7ff; }
[data-state="awaiting-human"]  #robotAntennaTip, [data-state="awaiting-human"]  #robotLedCore { fill: #f0b03a; }
[data-state="awaiting-human"]  #robotAntennaGlow, [data-state="awaiting-human"]  #robotLed     { fill: #f0b03a; }
[data-state="blocked"]         #robotAntennaTip, [data-state="blocked"]         #robotLedCore { fill: #e8555a; }
[data-state="blocked"]         #robotAntennaGlow, [data-state="blocked"]         #robotLed     { fill: #e8555a; }
[data-state="verified"]        #robotAntennaTip, [data-state="verified"]        #robotLedCore { fill: #6ee09b; }
[data-state="verified"]        #robotAntennaGlow, [data-state="verified"]        #robotLed     { fill: #6ee09b; }
[data-state="idle"]            #robotAntennaTip, [data-state="idle"]            #robotLedCore { fill: #5c6577; }
[data-state="idle"]            #robotAntennaGlow, [data-state="idle"]            #robotLed     { fill: #5c6577; opacity: .45; }

/* 极光光斑：卡片内的「配图」，取代全屏模糊渐变 */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(38% 34% at 86% 6%, rgba(62, 224, 127, .13), transparent 68%),
    radial-gradient(34% 32% at 8% 98%, rgba(139, 124, 246, .1), transparent 70%);
}

/* 渐变描边 */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  z-index: 2;
  background: linear-gradient(152deg,
    rgba(255, 255, 255, .26) 0%,
    rgba(255, 255, 255, .05) 26%,
    rgba(255, 255, 255, 0) 52%,
    rgba(62, 224, 127, .1) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
}

.hero.full { grid-column: span 12; }
.hero.compact { min-height: 316px; padding: 28px 38px; }   /* v14: 268→360，给 robot 留足舞台 */

h1 {
  font-size: clamp(38px, 4.6vw, 66px);
  line-height: 1.02;
  letter-spacing: -.045em;
  margin: 12px 0 22px;
  max-width: 780px;
  font-weight: 620;
}
/* v20：不支持 cqw 的老引擎兜底，与下面 @supports 分支里的上限保持一致。
   v20.2：系数 4.6vw → 4.0vw。这条路径拿不到 @container 分支（cqw 与 @container
   是同一代特性），所以只能靠「更窄的标题」来抵消新 stage 尺寸带来的重叠风险。
   实际上 Chromium 105+ / Safari 16+ / Firefox 110+ 都走不到这里，属于保险丝。
   v20.3：上限 56 → 54.5px，与 @supports 分支同步（理由见该处注释）。 */
.hero:not(.compact) h1 { font-size: clamp(34px, 4vw, 54.5px); }
@supports (font-size: 1cqw) {
  h1 { font-size: clamp(38px, 8.6cqw, 66px); }
  .hero.compact h1 { font-size: clamp(30px, 7cqw, 54px); }
  /* v20：首页标题封顶 56px 而不是 66px。
     不是为了「小一点好看」，是为了把绿字行盒从 693px 收进 588px，
     好让机器人（缩到 184px 后左缘落在 778）不再横穿它 —— 见上方 .robot-stage 注释。
     只压上限、不动 8.6cqw 的缩放曲线：879px 宽的 hero 下 8.6cqw = 75.6px，
     仍然稳定落在 56px 的封顶上，所以桌面各宽度下的观感是一致的。
     v20.3：上限 56 → 54.5px。
     起因是 v20.2 的「零重叠」其实是**包围盒零重叠** —— 1560/1920 下绿字右缘
     与机器人左缘只隔 3px，1024 视口更是实际重叠 2.9px（v20.2 的视口清单
     恰好跳过了 1024）。要把间距做到 ≥14px，有两条路：

       降字号    1px font  ->  +10.5px gap   （绿字行 = 11 字 × 0.955 字距）
       缩 stage  1px robot ->  +1px  gap     （1:1）

     差 10.5 倍，所以本轮只用字号杠杆，stage 尺寸**原样不动** ——
     v20.2「机器人在每个视口都更大」的收益因此完全不倒退。
     实测：1560/1920 间距 3.0 → 18.7px，机器人仍是 231.7px。 */
  .hero:not(.compact) h1 { font-size: clamp(38px, 8.6cqw, 54.5px); }

  /* v20.2：窄 hero 的「标题 × 机器人」联合让位。
     ------------------------------------------------------------------------
     为什么需要这一段：.hero 是容器（container-type: inline-size，见 L578），
     容器查询单位取的是**内容盒**宽度。内容宽 775px 时 8.6cqw = 66.6px，
     被 56px 上限截住 —— 桌面端看着一致；但内容宽掉到 651px 以下
     （对应 heroW < 755，即视口约 620~1440）时 8.6cqw 就低于上限开始等比缩，
     于是标题恒定占满内容宽的 90.3%，机器人无论多小都放不下。

     解法不是单方面缩机器人（那样只剩贴纸大小），而是同时收标题：
       标题 8.6 → 7cqw（占内容宽 90.3% → 73.5%）
       机器人 stage 58% + 右移一点（right -15.3% → -14%）
     两者一起才腾出空间。7cqw 与 .hero.compact h1 用的是同一个系数，
     风格上是自洽的。

     阈值 775px 是算出来的：基线 stage 58% 下机器人投影 ≈ 0.27 × heroW，
     要它不超过可用宽（heroW − 640）需 heroW ≥ 878，即内容宽 ≥ 774。
     774 以下走本分支，774 以上走基线 —— 两边在边界上连续。

     注意本段嵌在 @supports (font-size: 1cqw) 里：cqw 与 @container 是同一代
     特性，一起生效/一起退化，不会出现「标题按 cqw 缩了、机器人没缩」的错配。

     ------------------------------------------------------------------------
     v20.3：把「一档」拆成两档，并整体下调系数与地板。
     起因同样是 v20.2 遗留的两个缺口：
       · 1560/1920 间距只有 3px、1024 实际重叠 2.9px（视口清单漏了 1024）；
       · 620 视口间距 0.1px。
     经 12 视口解析扫描（benchmark/tmp/scan-hero-tiers.py，公式见该文件头），
     要「处处 ≥14px 且机器人不缩」，唯一可行的是继续压字号：
       774 档：7.00 → 6.86cqw      （1560 之外的上窄带，受 900 视口约束）
       560 档：新增 6.70cqw        （620/1024/1100 等最紧的一带）
       地板  ：34 → 32px           （1024 的内容宽只有 458px，原地板 34 会顶住不下）
     第三档（430px）实测与两档完全相同 —— 地板 32 先命中，再加档无收益，故不加。

     为什么必须两档而不是一档：容器查询只认内容宽，而最紧的约束**不是**
     内容宽单调的。900 视口（内容宽 744）与 1440（735）内容宽几乎相同，
     但 k = robotW/stageW 分别是 0.4823 / 0.4525（stage 纵横比不同导致取景不同），
     间距 3.4 vs 10.8。所以上窄带只能按最紧的 900 取系数，下窄带才放得开。
     一档（6.86）实测 620 只剩 8.1px，不达标。

     顺序要紧：560 档写在 774 档**之后**，同特异性下后者胜出。 */
  @container (max-width: 774px) {
    .hero:not(.compact) h1 { font-size: clamp(32px, 6.86cqw, 54.5px); }
    .hero:not(.compact) .robot-stage {
      width: 58%;
      right: -14%;
      bottom: -18%;
      height: 131.2%;
    }
  }
  @container (max-width: 560px) {
    .hero:not(.compact) h1 { font-size: clamp(32px, 6.70cqw, 54.5px); }
  }
}

.hero p {
  color: var(--muted);
  max-width: 600px;
  line-height: 1.8;
  margin: 0;
  font-size: 13.5px;
}

.hero-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 34px; }

/* AI Core：有焦点的弧线系统，不是静态同心圆装饰 */
.ai-core {
  position: absolute;
  right: -70px;
  top: -60px;
  width: 520px;
  height: 520px;
  pointer-events: none;
  z-index: 1;
  transform: translate3d(calc(var(--mx) * .3), calc(var(--my) * .3), 0);
  transition: transform 1.2s cubic-bezier(.16, .84, .3, 1), opacity .7s ease;
}
/* v19.6 修正：这里原本带 inset: 0，而它的特异性 (0,1,1) 高于
   .core-stream / .core-glow / .core-ticks 三条规则 (0,1,0)，
   于是那三个元素各自的 inset 被静默吃掉 —— 实测全部塌成 520×520：
     .core-glow   设计 43%（≈73px 的核）→ 实际 520px 的一团绿雾
     .core-stream 设计 20%（312px 内弧） → 实际 520px
     .core-ticks  设计  4%（478px 刻度环）→ 实际 520px
   而 .core-ring:nth-child(3)/(4) 因为带了伪类 (0,2,0) 才幸免，所以弧线只剩最外圈。
   inset 改由各元素自己声明，只有 .core-ring 需要兜底 0。 */
.ai-core > span { position: absolute; border-radius: 50%; display: block; }

.core-ring {
  inset: 0;
  border: 1px solid rgba(62, 224, 127, .22);
  -webkit-mask-image: conic-gradient(from 18deg, #000 0 58%, transparent 58% 100%);
  mask-image: conic-gradient(from 18deg, #000 0 58%, transparent 58% 100%);
  animation: coreSpin 68s linear infinite;
}
.core-ring:nth-child(3) {
  inset: 16%;
  border-color: rgba(139, 124, 246, .22);
  -webkit-mask-image: conic-gradient(from 200deg, #000 0 44%, transparent 44% 100%);
  mask-image: conic-gradient(from 200deg, #000 0 44%, transparent 44% 100%);
  animation: coreSpin 94s linear infinite reverse;
}
.core-ring:nth-child(4) {
  inset: 32%;
  border-color: rgba(79, 209, 232, .18);
  -webkit-mask-image: conic-gradient(from 300deg, #000 0 36%, transparent 36% 100%);
  mask-image: conic-gradient(from 300deg, #000 0 36%, transparent 36% 100%);
  animation: coreSpin 120s linear infinite;
}

.core-ticks {
  inset: 4%;
  background: repeating-conic-gradient(from 6deg,
    transparent 0 11deg, rgba(255, 255, 255, .1) 11.4deg 11.9deg, transparent 12.3deg 24deg);
  -webkit-mask-image: radial-gradient(circle, transparent 0 58%, #000 64%, transparent 78%);
  mask-image: radial-gradient(circle, transparent 0 58%, #000 64%, transparent 78%);
}

.core-stream {
  inset: 20%;
  border: 1px solid transparent;
  border-top-color: rgba(62, 224, 127, .55);
  -webkit-mask-image: conic-gradient(from 0deg, #000 0 11%, transparent 11% 100%);
  mask-image: conic-gradient(from 0deg, #000 0 11%, transparent 11% 100%);
  animation: coreSpin 18s linear infinite;
  filter: drop-shadow(0 0 6px rgba(62, 224, 127, .5));
}

/* 焦点：一个高亮的核，取代大而空的中心 */
.core-glow {
  inset: 43%;
  background: radial-gradient(circle at 50% 50%, rgba(62, 224, 127, .55) 0 5%, transparent 58%);
  filter: blur(3px);
  animation: corePulse 6.5s ease-in-out infinite;
}

[data-state="idle"] .ai-core { opacity: .5; }
[data-state="active"] .ai-core { opacity: 1; }
[data-state="active"] .core-stream { animation-duration: 7s; }
[data-state="active"] .core-ring { animation-duration: 32s; }
[data-state="awaiting-human"] .ai-core { opacity: .8; }
[data-state="awaiting-human"] .core-stream { animation: none; }
[data-state="awaiting-human"] .core-glow { background: radial-gradient(circle at 50% 50%, rgba(245, 196, 81, .5) 0 6%, transparent 62%); }
[data-state="verified"] .ai-core { opacity: .95; }
[data-state="blocked"] .ai-core { opacity: .32; }
[data-state="blocked"] .core-stream { animation: none; }
[data-state="blocked"] .core-glow { background: radial-gradient(circle at 50% 50%, rgba(242, 114, 140, .45) 0 6%, transparent 62%); }

[data-state="verified"] .ai-core::after {
  content: "";
  position: absolute;
  inset: 16%;
  border-radius: 50%;
  border: 1px solid rgba(62, 224, 127, .6);
  animation: verifiedTrace .75s cubic-bezier(.16, .84, .3, 1) both;
}

/* --------------------------------------------------------------------------
   10. 面板
   -------------------------------------------------------------------------- */
.panel {
  position: relative;
  isolation: isolate;
  border-radius: 26px;
  background:
    linear-gradient(158deg, var(--glass-hi) 0%, rgba(255, 255, 255, .012) 38%, var(--glass-lo) 100%),
    var(--panel);
  backdrop-filter: blur(30px) saturate(150%);
  -webkit-backdrop-filter: blur(30px) saturate(150%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .07),
    inset 0 -1px 0 rgba(0, 0, 0, .5),
    0 1px 0 rgba(0, 0, 0, .4),
    var(--shadow);
}

.panel::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  background: linear-gradient(152deg,
    rgba(255, 255, 255, .26) 0%,
    rgba(255, 255, 255, .05) 26%,
    rgba(255, 255, 255, 0) 52%,
    rgba(62, 224, 127, .1) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  z-index: 2;
}

.panel > :not(.blob) { position: relative; z-index: 1; }

.pad { padding: 26px; }
.wide { grid-column: span 8; }
.narrow { grid-column: span 4; }
.stat-card { grid-column: span 3; min-height: 152px; position: relative; overflow: hidden; }

/* 命令面板 */
.command-panel {
  grid-column: span 4;
  min-height: 396px;
  padding: 32px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: 26px;
  background:
    linear-gradient(158deg, var(--glass-hi), rgba(255, 255, 255, .01) 40%, var(--glass-lo)),
    var(--panel);
  backdrop-filter: blur(30px) saturate(150%);
  -webkit-backdrop-filter: blur(30px) saturate(150%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .07),
    inset 0 -1px 0 rgba(0, 0, 0, .5),
    var(--shadow);
}
.command-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 0;
  background: radial-gradient(46% 30% at 50% 0%, rgba(62, 224, 127, .12), transparent 70%);
}
.command-panel::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  background: linear-gradient(152deg, rgba(255, 255, 255, .26), rgba(255, 255, 255, .04) 30%, transparent 60%, rgba(62, 224, 127, .12));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  z-index: 2;
}
.command-panel > :not(.blob) { position: relative; z-index: 1; }

/* v19.7 修正：这里原本是 `.command-panel h2, .section-title h2`。
   但 .command-panel 里唯一的 h2 就是 <h2 class="command-prompt">，
   而 `.command-panel h2` 的特异性 (0,1,1) 高于 `.command-prompt` (0,1,0)，
   于是把提示词的 font-size 27px / letter-spacing / font-weight / margin 24px 全吃掉 ——
   实测主提示词一直按 13.5px 渲染、上下间距为 0，只有设计尺寸的一半。
   该组规则的本意是「小节标题」，.command-panel h2 这一半没有任何合法目标，直接去掉。 */
.section-title h2 { font-size: 13.5px; letter-spacing: -.01em; margin: 0; font-weight: 650; }

.command-prompt {
  font-size: 27px;
  line-height: 1.2;
  letter-spacing: -.035em;
  margin: 24px 0;
  color: var(--text);
  font-weight: 600;
}

.command-input {
  border-radius: 16px;
  padding: 16px;
  font-size: 12px;
  color: var(--muted);
  cursor: text;
  background: rgba(0, 0, 0, .3);
  box-shadow: inset 0 0 0 1px var(--line);
  animation: commandWait 5.6s ease-in-out infinite;
  transition: box-shadow .4s ease;
}
.command-input:hover {
  box-shadow: inset 0 0 0 1px rgba(62, 224, 127, .35), 0 0 30px -8px rgba(62, 224, 127, .4);
}

.quick-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; margin-top: 16px; }
.quick-actions .button { justify-content: flex-start; font-size: 11px; padding: 11px 13px; }

.section-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 20px;
}

/* --------------------------------------------------------------------------
   11. Status zone
   -------------------------------------------------------------------------- */
.status-zone {
  grid-column: span 7;
  padding: 30px;
  min-height: 232px;
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: 26px;
  background:
    linear-gradient(158deg, var(--glass-hi), rgba(255, 255, 255, .01) 40%, var(--glass-lo)),
    var(--panel);
  backdrop-filter: blur(30px) saturate(150%);
  -webkit-backdrop-filter: blur(30px) saturate(150%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .07),
    inset 0 -1px 0 rgba(0, 0, 0, .5),
    var(--shadow);
}
.status-zone::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  background: linear-gradient(152deg, rgba(255, 255, 255, .24), rgba(255, 255, 255, .04) 30%, transparent 62%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  z-index: 2;
}
.status-zone > :not(.blob) { position: relative; z-index: 1; }

.status-heading { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 24px; }

.ai-online { font-size: 24px; letter-spacing: -.035em; display: flex; gap: 11px; align-items: center; font-weight: 600; }
.ai-online .live-dot { width: 9px; height: 9px; }

.status-lines { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }

.status-line { padding: 16px 0 0; border-top: 1px solid var(--line); }
.status-line strong { display: block; font-size: 11.5px; margin-bottom: 7px; letter-spacing: .01em; }
.status-line span { font-size: 10.5px; color: var(--muted2); }

.status-line em {
  font-style: normal;
  color: var(--idle);
  font-size: 9.5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .1em;
  text-transform: uppercase;
  display: block;
  margin-top: 9px;
  animation: statusBreath 3.6s ease-in-out infinite;
}
.status-line:nth-child(2) em { animation-delay: .45s; }
.status-line:nth-child(3) em { animation-delay: .9s; }

.status-line[data-state="verified"] em { color: var(--green); }
.status-line[data-state="idle"] em { color: var(--muted2); animation: none; }
.status-line[data-state="awaiting-human"] em { color: var(--yellow); }
.status-line[data-state="blocked"] em { color: var(--red); animation: none; }

/* --------------------------------------------------------------------------
   12. Activity
   -------------------------------------------------------------------------- */
.activity-panel { grid-column: span 5; padding: 30px; min-height: 232px; }
.activity { display: grid; }

.activity-row {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 14px 0;
  border-bottom: 1px solid rgba(255, 255, 255, .05);
  animation: rowIn .34s cubic-bezier(.16, .84, .3, 1) both;
  animation-delay: calc(var(--i, 0) * 45ms);
}
.activity-row:last-child { border-bottom: 0; }

.activity-icon {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border-radius: 11px;
  flex: none;
  font-size: 13px;
  color: var(--green);
  background: linear-gradient(150deg, rgba(62, 224, 127, .16), rgba(62, 224, 127, .05));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), inset 0 0 0 1px rgba(62, 224, 127, .16);
}

.activity-copy { flex: 1; min-width: 0; }
.activity-title { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.activity-time { font-size: 10px; color: var(--muted2); margin-top: 5px; }

.empty {
  padding: 34px;
  color: var(--muted);
  text-align: center;
  font-size: 12px;
  position: relative;
}
.empty::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 24px;
  width: 130px;
  height: 130px;
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(62, 224, 127, .09), transparent 66%);
  animation: emptyWait 6.6s ease-in-out infinite;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   13. Status chips
   -------------------------------------------------------------------------- */
.status {
  font-size: 9.5px;
  padding: 5px 9px;
  border-radius: 999px;
  white-space: nowrap;
  letter-spacing: .08em;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  text-transform: uppercase;
}
.status.ok { color: var(--green); background: rgba(62, 224, 127, .1); box-shadow: inset 0 0 0 1px rgba(62, 224, 127, .18); }
.status.warn { color: var(--yellow); background: rgba(245, 196, 81, .1); box-shadow: inset 0 0 0 1px rgba(245, 196, 81, .18); }
.status.processing { color: var(--purple); background: rgba(139, 124, 246, .12); box-shadow: inset 0 0 0 1px rgba(139, 124, 246, .2); }
.status.idle { color: var(--idle); background: rgba(93, 102, 99, .14); box-shadow: inset 0 0 0 1px rgba(93, 102, 99, .2); }
.status.blocked { color: var(--red); background: rgba(242, 114, 140, .12); box-shadow: inset 0 0 0 1px rgba(242, 114, 140, .2); }

/* --------------------------------------------------------------------------
   14. Module strip / metrics
   -------------------------------------------------------------------------- */
/* 传统条带（非 Bento 页面仍使用） */
.module-strip {
  grid-column: span 12;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  padding: 26px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.module-metric { padding: 0 24px; border-right: 1px solid var(--line); }
.module-metric:first-child { padding-left: 0; }
.module-metric:last-child { border-right: 0; }
.module-label {
  font-size: 9.5px;
  color: var(--muted2);
  margin-bottom: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.module-value {
  font-size: 21px;
  letter-spacing: -.035em;
  font-weight: 560;
  font-variant-numeric: tabular-nums;
}

/* ── 浮动指标挂件（Bento 专用）─────────────────────────────────────────── */
.metric-floats {
  grid-area: metrics;
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: space-between;
}

.metric-float {
  flex: 1 1 140px;
  min-width: 140px;
  padding: 20px 22px;
  border-radius: 20px;
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background:
    linear-gradient(158deg, var(--glass-hi), rgba(255,255,255,.01) 40%, var(--glass-lo)),
    var(--panel);
  backdrop-filter: blur(30px) saturate(150%);
  -webkit-backdrop-filter: blur(30px) saturate(150%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.07),
    inset 0 -1px 0 rgba(0,0,0,.5),
    0 1px 0 rgba(0,0,0,.4),
    var(--shadow);
  transition: transform .45s cubic-bezier(.16,.84,.3,1), box-shadow .45s ease;
}
.metric-float:hover {
  transform: translateY(-5px) scale(1.02);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.1),
    0 24px 56px -18px rgba(0,0,0,.85),
    0 0 50px -14px rgba(62,224,127,.18);
}
.metric-float::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  background: linear-gradient(152deg, rgba(255,255,255,.22), rgba(255,255,255,.04) 30%, transparent 60%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
}
.metric-float .module-label { margin-bottom: 10px; }
.metric-float .module-value { font-size: 24px; }
.metric-value {
  font-size: 33px;
  letter-spacing: -.05em;
  margin: 16px 0 6px;
  font-weight: 560;
  font-variant-numeric: tabular-nums;
}
.metric-title { display: flex; justify-content: space-between; color: var(--muted); font-size: 11px; }
.metric-foot { font-size: 10px; color: var(--muted2); }
.muted { color: var(--muted); }

/* --------------------------------------------------------------------------
   15. Buttons / forms / tables / dropzone
   -------------------------------------------------------------------------- */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 0;
  border-radius: 13px;
  padding: 11px 16px;
  font-size: 11.5px;
  font-family: inherit;
  letter-spacing: .01em;
  color: var(--text);
  text-decoration: none;
  cursor: pointer;
  background: linear-gradient(158deg, rgba(255, 255, 255, .09), rgba(255, 255, 255, .025));
  box-shadow:
    inset 0 0 0 1px var(--line2),
    inset 0 1px 0 rgba(255, 255, 255, .1),
    0 6px 18px -8px rgba(0, 0, 0, .9);
  transition: transform .3s cubic-bezier(.16, .84, .3, 1), box-shadow .3s ease, background .3s ease;
}
.button:hover {
  transform: translateY(-2px);
  background: linear-gradient(158deg, rgba(62, 224, 127, .14), rgba(62, 224, 127, .04));
  box-shadow:
    inset 0 0 0 1px rgba(62, 224, 127, .4),
    inset 0 1px 0 rgba(255, 255, 255, .14),
    0 14px 34px -12px rgba(62, 224, 127, .5);
}
.button.primary {
  color: #04150b;
  font-weight: 700;
  background: linear-gradient(150deg, #7bf3ae, var(--green) 52%, #21a457);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .55),
    inset 0 -2px 8px rgba(0, 0, 0, .22),
    0 12px 32px -10px rgba(62, 224, 127, .7);
}
.button.primary:hover { box-shadow: inset 0 1px 0 rgba(255, 255, 255, .6), 0 18px 42px -12px rgba(62, 224, 127, .85); }
.button.ghost { background: rgba(255, 255, 255, .025); }
.button:disabled { opacity: .38; cursor: not-allowed; transform: none; }

.risk-box {
  padding: 15px;
  border-radius: 15px;
  font-size: 12px;
  line-height: 1.65;
  color: var(--yellow);
  background: rgba(245, 196, 81, .07);
  box-shadow: inset 0 0 0 1px rgba(245, 196, 81, .2);
}

.hidden { display: none !important; }

.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; }
.field { display: grid; gap: 8px; }
.field.full { grid-column: 1 / -1; }
.field label {
  font-size: 9.5px;
  color: var(--muted2);
  letter-spacing: .14em;
  text-transform: uppercase;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

input,
select,
textarea {
  width: 100%;
  padding: 12px 14px;
  color: var(--text);
  font-family: inherit;
  font-size: 12px;
  border: 0;
  border-radius: 13px;
  outline: none;
  background: rgba(0, 0, 0, .34);
  box-shadow: inset 0 0 0 1px var(--line), inset 0 2px 6px rgba(0, 0, 0, .35);
  transition: box-shadow .3s ease;
}
input:focus,
select:focus,
textarea:focus { box-shadow: inset 0 0 0 1px rgba(62, 224, 127, .5), 0 0 0 4px rgba(62, 224, 127, .1); }
input[readonly] { color: var(--muted); }
textarea { resize: vertical; }
select { appearance: none; cursor: pointer; }

.dropzone {
  border-radius: 22px;
  padding: 44px;
  text-align: center;
  margin: 22px 0;
  position: relative;
  background:
    radial-gradient(70% 90% at 50% 0%, rgba(62, 224, 127, .07), transparent 70%),
    rgba(0, 0, 0, .26);
  box-shadow: inset 0 0 0 1px var(--line);
  transition: box-shadow .4s ease, background .4s ease;
}
.dropzone:hover,
.dropzone.is-dragover {
  background:
    radial-gradient(70% 90% at 50% 0%, rgba(62, 224, 127, .16), transparent 70%),
    rgba(0, 0, 0, .3);
  box-shadow:
    inset 0 0 0 1px rgba(62, 224, 127, .5),
    0 0 60px -16px rgba(62, 224, 127, .55);
}
.dropzone input { display: none; }
.dropzone label { display: block; cursor: pointer; }
.dropzone strong { display: block; margin: 14px 0 7px; font-size: 13.5px; }
.dropzone span { font-size: 10.5px; color: var(--muted2); }

.table-wrap { overflow: auto; }

/* contain: layout 是修一个 Chrome 的「陈旧布局」问题，不是为了性能。
   空态行是 app.js 在 <table> 已经完成布局之后才插入的；插入之后
   .table-wrap(overflow:auto) 的 scrollHeight 会停在一个陈旧值上，比实际内容高 7px
   （ledger 页实测，ai-cost 页恰好不触发）。识别特征：
     · 任何一次重排都会把它修正 —— 所以「页面加载后再注入样式」测出来永远是 0，
       只有冷加载才看得到这 7px；改完 colspan 再改回来，也不复发。
     · 与装饰圆无关（把 ::before 设成 display:none 仍是 7）、与单元格高度无关
       （padding:0 让格子从 84px 变 16px，仍是 7）、与 overflow 取值无关
       （visible / hidden / clip 都是 7，只要装饰圆已被裁掉）。
   把布局隔离在表格内部之后，这个陈旧值不再产生（冷加载稳定 0）。
   作用域只能是 .data-table：写在 .table-wrap / tbody / td 上实测都无效。
   实测副作用：单元格高度、行高、列宽、表格宽度全部不变（rows=[41,84]、cellH=84）。 */
.data-table { width: 100%; border-collapse: collapse; font-size: 11.5px; contain: layout; }
.data-table th,
.data-table td { text-align: left; padding: 15px 14px; border-bottom: 1px solid rgba(255, 255, 255, .05); white-space: nowrap; }

/* v19.7 修正：上面这条通用单元格规则特异性 (0,1,1) 高于 `.empty` (0,1,0)，
   把空态单元格自己的 padding: 34px / text-align: center 吃掉了。
   后果是同一个 .empty 类在两种上下文里长得不一样：
     列表里的 <div class="empty"> → 34px + 居中（真生效）
     表格里的 <td class="empty">   → 15px + 左对齐（被覆盖）
   这里用 (0,2,1) 显式恢复空态单元格的对齐与内边距。

   裁切是为了兜住 .empty::before 那个装饰圆：
   它按 top:24px + 130px 摆放，下缘到 154px，而单元格内容盒只有 83px ——
   多出的 70px 会把 .table-wrap(overflow:auto) 撑出一条多余滚动条。

   四种裁法实测（每个候选都 goto 一次全新加载、只注入一个 style 标签、
   动画已冻结，测 .table-wrap 的 scrollHeight - clientHeight）：
     不裁（overflow: visible） → ledger 70px / ai-cost 70px   ← 基线
     overflow: hidden          → ledger  7px / ai-cost  0px   裁不干净
     clip-path: inset(0)       → ledger 70px / ai-cost 70px   完全无效（等于不裁）
     contain: paint            → ledger  0px / ai-cost  0px   有效
     overflow: clip            → ledger  0px / ai-cost  0px   有效
   取 overflow: clip 而非 contain: paint：后者按规范隐含 layout containment，
   会改变单元格内在尺寸的参与方式；overflow: clip 只做绘制层裁切，无此副作用。
   前置的 overflow: hidden 是给不支持 clip 的老引擎兜底（至少能裁掉 63/70px）。
   注意：ledger 残留的那 7px 后来查明**不是**装饰圆造成的，
   而是表格的陈旧布局值 —— 已由上面 .data-table 的 contain: layout 处理。

   踩坑备忘一：本条一度被写成 clip-path: inset(0)，那是**注入污染**造成的误判 ——
   在同一页面里连续 addStyleTag 注入多个候选，!important 规则按注入顺序互相覆盖，
   先注入 overflow 得到 0，再注入 clip-path 仍读到 0。注入式 A/B 必须每次重建页面。
   踩坑备忘二：测量前必须冻结 .empty::before 的 emptyWait 动画，它带 scale(.94→1.05)，
   而 transform 会计入祖先的 scrollable overflow，不冻结时读数会在 70/73 之间跳。 */
.data-table td.empty {
  padding: 34px;
  text-align: center;
  white-space: normal;
  overflow: hidden;
  overflow: clip;
}

.data-table th {
  color: var(--muted2);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .14em;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.data-table td { font-variant-numeric: tabular-nums; }
.data-table tbody tr {
  transition: background .35s ease;
  animation: rowIn .3s cubic-bezier(.16, .84, .3, 1) both;
  animation-delay: calc(var(--i, 0) * 32ms);
}
.data-table tbody tr:hover { background: rgba(62, 224, 127, .045); }

/* --------------------------------------------------------------------------
   16. Pipeline — 真实景深
   -------------------------------------------------------------------------- */
.workflow-shell {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 10px;
  margin: 24px 0;
  position: relative;
  perspective: 1100px;
  perspective-origin: 50% 40%;
}

.workflow-shell::before {
  content: "";
  position: absolute;
  left: 3%;
  right: 3%;
  top: 50%;
  height: 1px;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .12) 14% 86%, transparent);
}

.workflow-step {
  position: relative;
  overflow: hidden;
  z-index: 1;
  padding: 14px;
  border-radius: 16px;
  min-height: 88px;
  opacity: .5;
  transform: translateZ(-24px);
  background: linear-gradient(158deg, rgba(255, 255, 255, .05), rgba(0, 0, 0, .3));
  box-shadow: inset 0 0 0 1px var(--line), 0 10px 24px -14px rgba(0, 0, 0, .9);
  transition:
    transform .6s cubic-bezier(.16, .84, .3, 1),
    opacity .6s ease,
    box-shadow .6s ease;
}
.workflow-step span {
  font: 9.5px ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .14em;
  color: var(--muted2);
  display: block;
  margin-bottom: 14px;
}
.workflow-step strong { font-size: 11px; letter-spacing: -.005em; }

.workflow-step::after {
  content: "";
  position: absolute;
  top: 0;
  left: -80%;
  width: 55%;
  height: 1px;
  opacity: 0;
  transform: skewX(-25deg);
  background: linear-gradient(90deg, transparent, var(--green), transparent);
  box-shadow: 0 0 12px rgba(62, 224, 127, .9);
}

/* 激活：向观察者前移，配合 perspective 产生真实抬升 */
.workflow-step.active {
  opacity: 1;
  transform: translateZ(48px) translateY(-2px);
  background: linear-gradient(158deg, rgba(139, 124, 246, .16), rgba(0, 0, 0, .34));
  box-shadow:
    inset 0 0 0 1px rgba(139, 124, 246, .55),
    inset 0 1px 0 rgba(255, 255, 255, .14),
    0 26px 50px -18px rgba(139, 124, 246, .6);
}
.workflow-step.active::after { opacity: 1; animation: stepSweep 2.6s ease-in-out infinite; }

.workflow-step.done {
  opacity: .88;
  transform: translateZ(-8px);
  color: var(--green);
  box-shadow: inset 0 0 0 1px rgba(62, 224, 127, .3), 0 10px 24px -16px rgba(62, 224, 127, .5);
}
.workflow-step.blocked {
  opacity: 1;
  transform: translateZ(30px);
  color: var(--red);
  box-shadow: inset 0 0 0 1px rgba(242, 114, 140, .5), 0 20px 40px -18px rgba(242, 114, 140, .5);
}
.workflow-step.blocked::after { display: none; }

.workflow-shell[data-state="idle"] .workflow-step:first-child { animation: idlePulse 5.8s ease-in-out infinite; }

.workflow-shell[data-state="awaiting-human"] .workflow-step.active::after { animation: none; opacity: 0; }
.workflow-shell[data-state="awaiting-human"] .workflow-step.active {
  background: linear-gradient(158deg, rgba(245, 196, 81, .16), rgba(0, 0, 0, .34));
  box-shadow:
    inset 0 0 0 1px rgba(245, 196, 81, .55),
    inset 0 1px 0 rgba(255, 255, 255, .14),
    0 26px 50px -18px rgba(245, 196, 81, .55);
  animation: reviewPause 3.6s ease-in-out infinite;
}

.workflow-shell[data-state="verified"] .workflow-step.done { animation: verifiedGlow .75s cubic-bezier(.16, .84, .3, 1) both; }

/* --------------------------------------------------------------------------
   17. Ledger trace
   -------------------------------------------------------------------------- */
.trace-path {
  grid-column: span 12;
  position: relative;
  height: 1px;
  margin: -6px 0;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(62, 224, 127, .18) 20% 80%, transparent);
}
.trace-path::after {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 22%;
  height: 3px;
  border-radius: 3px;
  background: linear-gradient(90deg, transparent, rgba(62, 224, 127, .7), transparent);
  animation: traceFlow 9s ease-in-out infinite;
}

.verified-glow { animation: verifiedEdge .7s cubic-bezier(.16, .84, .3, 1) both; }

/* --------------------------------------------------------------------------
   18. Toast
   -------------------------------------------------------------------------- */
.toast {
  position: fixed;
  right: 26px;
  bottom: 30px;
  max-width: 370px;
  padding: 15px 17px;
  border-radius: 16px;
  z-index: 30;
  font-size: 12px;
  color: var(--text);
  background: linear-gradient(158deg, rgba(255, 255, 255, .08), rgba(0, 0, 0, .4)), rgba(20, 24, 23, .92);
  backdrop-filter: blur(26px) saturate(150%);
  -webkit-backdrop-filter: blur(26px) saturate(150%);
  box-shadow:
    inset 0 0 0 1px var(--line2),
    inset 0 1px 0 rgba(255, 255, 255, .1),
    0 30px 60px -20px rgba(0, 0, 0, .95);
  animation: slideIn .34s cubic-bezier(.16, .84, .3, 1);
}

/* --------------------------------------------------------------------------
   19. Dock nav
   -------------------------------------------------------------------------- */
.dock {
  position: fixed;
  left: 26px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 9px;
  border-radius: 20px;
  z-index: 20;
  /* 导航项增至 8 个后竖排总高约 432px（每项 48px + 4px 间距）；短视口下必须可滚动，
     否则底部两项会被推出屏幕、永远点不到（窄屏另有横向布局覆盖）。 */
  max-height: calc(100vh - 36px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: linear-gradient(158deg, rgba(255, 255, 255, .07), rgba(0, 0, 0, .35)), rgba(14, 17, 16, .62);
  backdrop-filter: blur(30px) saturate(150%);
  -webkit-backdrop-filter: blur(30px) saturate(150%);
  box-shadow:
    inset 0 0 0 1px var(--line),
    inset 0 1px 0 rgba(255, 255, 255, .09),
    0 30px 70px -24px rgba(0, 0, 0, .95);
}

.dock a {
  width: 70px;
  height: 48px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  border-radius: 13px;
  color: var(--muted2);
  text-decoration: none;
  font-size: 9px;
  letter-spacing: .04em;
  transition: color .3s ease, background .3s ease, transform .35s cubic-bezier(.16, .84, .3, 1);
}
.dock a strong { font-size: 16px; font-weight: 500; }
.dock a:hover { color: var(--text); background: rgba(255, 255, 255, .05); transform: translateX(3px); }
.dock a.active {
  color: var(--green);
  background: linear-gradient(150deg, rgba(62, 224, 127, .16), rgba(62, 224, 127, .04));
  box-shadow: inset 0 0 0 1px rgba(62, 224, 127, .26), inset 0 1px 0 rgba(255, 255, 255, .1);
  transform: translateX(3px);
}

/* --------------------------------------------------------------------------
   20. Keyframes
   -------------------------------------------------------------------------- */
@keyframes traceDrift {
  0% { transform: translate3d(0, 0, 0); }
  100% { transform: translate3d(-18px, 12px, 0); }
}

/* 体积光斑漂移：幅度很小，只在长时间尺度上让画面「呼吸」 */
@keyframes atmoDrift {
  0%   { transform: translate3d(-1.6%, -1.2%, 0) scale(1.02); }
  100% { transform: translate3d(2.2%, 1.6%, 0) scale(1.09); }
}

/* 胶片颗粒抖动：steps(1) 逐帧跳变，不做补间——真实颗粒是逐帧随机的，
   补间会变成「缓慢位移的噪点图」，反而穿帮。 */
@keyframes grainShift {
  0%   { transform: translate3d(0, 0, 0); }
  12%  { transform: translate3d(-6px, 4px, 0); }
  25%  { transform: translate3d(5px, -7px, 0); }
  37%  { transform: translate3d(-9px, -3px, 0); }
  50%  { transform: translate3d(7px, 6px, 0); }
  62%  { transform: translate3d(-4px, -8px, 0); }
  75%  { transform: translate3d(8px, 2px, 0); }
  87%  { transform: translate3d(-7px, 7px, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

@keyframes bentoIn {
  from { opacity: 0; transform: translateY(18px) rotateX(4deg); }
  to { opacity: 1; transform: translateY(0) rotateX(0); }
}

@keyframes eqBar {
  0%, 100% { transform: scaleY(0.55); opacity: .45; }
  50% { transform: scaleY(1); opacity: 1; }
}

@keyframes coreSpin { to { transform: rotate(360deg); } }

@keyframes corePulse {
  0%, 100% { opacity: .6; transform: scale(.94); }
  50% { opacity: 1; transform: scale(1.06); }
}

@keyframes statusBreath {
  0%, 100% { opacity: .74; }
  50% { opacity: 1; }
}

@keyframes meter { to { width: 86%; } }

@keyframes stepSweep { 0% { left: -70%; } 55%, 100% { left: 120%; } }

@keyframes idlePulse {
  0%, 100% { opacity: .42; }
  50% { opacity: .68; }
}

@keyframes reviewPause {
  0%, 100% { box-shadow: inset 0 0 0 1px rgba(245, 196, 81, .5), 0 22px 44px -20px rgba(245, 196, 81, .4); }
  50% { box-shadow: inset 0 0 0 1px rgba(245, 196, 81, .8), 0 30px 60px -18px rgba(245, 196, 81, .7); }
}

@keyframes verifiedGlow {
  0% { box-shadow: inset 0 0 0 1px rgba(62, 224, 127, .3); }
  45% { box-shadow: inset 0 0 0 1px rgba(62, 224, 127, .7), 0 0 50px -10px rgba(62, 224, 127, .7); }
  100% { box-shadow: inset 0 0 0 1px rgba(62, 224, 127, .3); }
}

@keyframes verifiedTrace {
  from { opacity: .95; transform: scale(.92); }
  to { opacity: 0; transform: scale(1.12); }
}

@keyframes verifiedEdge {
  0% { box-shadow: var(--shadow); }
  45% { box-shadow: var(--shadow), inset 0 0 0 1px rgba(62, 224, 127, .5), 0 0 70px -14px rgba(62, 224, 127, .6); }
  100% { box-shadow: var(--shadow); }
}

@keyframes traceFlow {
  0% { left: -22%; opacity: 0; }
  20% { opacity: .9; }
  70% { opacity: .5; }
  100% { left: 100%; opacity: 0; }
}

@keyframes emptyWait {
  0%, 100% { opacity: .4; transform: translateX(-50%) scale(.94); }
  50% { opacity: .9; transform: translateX(-50%) scale(1.05); }
}

@keyframes commandWait {
  0%, 100% { box-shadow: inset 0 0 0 1px var(--line); }
  50% { box-shadow: inset 0 0 0 1px rgba(62, 224, 127, .22), 0 0 26px -10px rgba(62, 224, 127, .4); }
}

@keyframes rowIn {
  from { opacity: 0; transform: translateY(7px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes slideIn { from { transform: translateY(14px); opacity: 0; } }

@keyframes islandSettle {
  from { opacity: 0; transform: translateY(-10px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes pageEnter {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes pageLeave {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(-8px); }
}

/* --------------------------------------------------------------------------
   21. Responsive
   -------------------------------------------------------------------------- */
@media (max-width: 1180px) {
  .ai-core { right: -150px; }
}

@media (max-width: 980px) {
  .shell { padding: 30px 26px 116px; }

  .dock {
    left: 50%;
    top: auto;
    bottom: 18px;
    transform: translateX(-50%);
    flex-direction: row;
    border-radius: 22px;
    max-width: calc(100vw - 28px);
    overflow-x: auto;
  }
  .dock a { width: 58px; height: 46px; }
  .dock a:hover,
  .dock a.active { transform: translateY(-3px); }

  .hero,
  .command-panel,
  .status-zone,
  .activity-panel,
  .wide,
  .narrow { grid-column: span 12; }

  .module-strip { grid-template-columns: repeat(3, 1fr); gap: 20px; }
  .module-metric:nth-child(3) { border-right: 0; }
  .module-metric:nth-child(n + 4) { padding-top: 16px; }

  .workflow-shell { grid-template-columns: repeat(2, 1fr); perspective: none; }
  .workflow-shell::before { display: none; }
  .workflow-step,
  .workflow-step.active,
  .workflow-step.done,
  .workflow-step.blocked { transform: none; }

  .ai-core { right: -200px; opacity: .4; }

  /* Bento 在平板以下退化为单列 */
  .dashboard-grid.bento {
    grid-template-columns: 1fr;
    grid-template-areas:
      "hero"
      "command"
      "status"
      "activity"
      "metrics"
      "pulse"
      "notes";
  }
  .metric-floats { flex-direction: column; }
}

@media (max-width: 620px) {
  .shell { padding: 22px 16px 108px; }
  .topbar { margin-bottom: 34px; }
  .top-actions .pill { display: none; }

  .hero { padding: 30px; min-height: 348px; }
  .hero h1 { font-size: 42px; }
  .command-panel { min-height: 320px; }
  .status-lines { grid-template-columns: 1fr; }
  .module-strip { grid-template-columns: repeat(2, 1fr); }
  .module-metric:nth-child(2n) { border-right: 0; }
  .form-grid { grid-template-columns: 1fr; }
  .field.full { grid-column: auto; }
  .workflow-shell { grid-template-columns: 1fr; }
  .panel.pad { padding: 20px; }
  .live-island { width: 100%; }
  .island-meter { display: none; }
  .ai-core { display: none; }
  .island-eq { display: none; }
}

/* --------------------------------------------------------------------------
   22. Reduced motion — 保留静态层级与状态颜色
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }

  body::before { animation: none; }
  .ai-core { transform: none !important; opacity: .6 !important; }
  .ai-core > span { animation: none !important; }
  .ai-core::after { display: none; }

  .dashboard-grid.bento > section { animation: none; }
  .island-eq span { animation: none; }

  /* 材质层：颗粒是「纹理」不是「动效」，保留但停止逐帧抖动；体积光停止漂移。
     （reduced-motion 下 3D 运行时本就不加载，无需处理 #spline-bg） */
  #atmo-grain { animation: none; }
  #atmo-light { animation: none; }

  .workflow-step { transform: none !important; }
  .workflow-step::after { display: none; }
  .trace-path::after { display: none; }
  .empty::before { animation: none; opacity: .5; }
  .command-input { animation: none; }

  .live-dot { animation: none !important; }
  .status-line em { animation: none !important; }
  .island-meter span { animation: none !important; width: 72%; }

  .dock a:hover,
  .dock a.active,
  .button:hover { transform: none; }
}
