/* UCanary — 设计系统
 *
 * 排版论点：这个页面的全部主张是「这些是可核对的链上事实」，
 * 所以**每一个可核对的东西都用等宽字**——数字、地址、时间、标签、导语。
 * 等宽字在这里不是装饰，它就是论点本身。人话（标题、正文）才用比例字。
 *
 *   Sora          标题。几何但有脾气，不是加密圈默认的那几张脸
 *   IBM Plex Sans 正文 —— 换掉 Inter 是有理由的：Inter 是这一波生成式
 *                 界面收敛到的同一张脸，好用但没有立场。Plex 本来就是
 *                 为技术文档画的，a/g/M 有辨识度，且与已经承担全部数据的
 *                 等宽字同源，两者放在一起像一套仪器而不是一次拼接。
 *   JetBrains Mono  一切事实：数字、地址、时间戳、eyebrow 标签
 *
 * 字体自托管，不引 CDN：少一次跨域握手，且断网/被墙时不会掉回宋体。
 * 只取 latin 子集，三张脸合计 112KB。
 *
 * 配色：深藏青底 + 琥珀（金丝雀），冷端配青蓝而不是紫 ——
 * 紫渐变是当下 SaaS 站的默认答案，青蓝与琥珀是真正的补色关系，
 * 也更贴「夜里的一盏警示灯」。
 */

/* ── 字体 ──────────────────────────────────────────────────── */
@font-face {
  font-family: "Sora"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/static/fonts/Sora-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Plex Sans"; font-style: normal; font-weight: 100 700; font-display: swap;
  src: url("/static/fonts/PlexSans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono"; font-style: normal; font-weight: 100 800; font-display: swap;
  src: url("/static/fonts/JetBrainsMono-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ── 中文字体 ────────────────────────────────────────────────
 * 只补**中文**那一段，拉丁部分一个字节不动 —— 字体栈里拉丁字体在前，
 * 汉字覆盖不到就自然落到后面这些上，所以英文页面的渲染完全不变。
 *
 * ⚠️ 顺序是「先系统、后自带」，而且这不是妥协：
 *      macOS / iOS 的苹方（PingFang SC）比我们能自带的任何东西都好看，
 *      而且零字节；Windows 的微软雅黑也够用。浏览器按**字形需求**逐字
 *      回退 —— 前面的字体盖得住的字，后面的 woff2 根本不会被下载。
 *      于是绝大多数中文用户是 0 KB，只有两边都没有的平台才付这 325KB。
 *
 * 自带的那份是 Noto Sans SC（OFL），**按站上实际用到的 1135 个字子集化**。
 * 完整的 CJK 字体是 5–10MB，那个体量在首屏上是不可接受的；而我们控制
 * 全部文案，所以可以只带需要的字。改文案后要重新生成 —— 见
 * scripts/build_cjk_subset.sh。
 */
@font-face {
  font-family: "Noto Sans SC Subset";
  font-style: normal; font-weight: 400; font-display: swap;
  src: url("/static/fonts/NotoSansSC-Regular-subset.woff2") format("woff2");
  unicode-range: U+2000-206F, U+3000-303F, U+FF00-FFEF, U+4E00-9FFF, U+3400-4DBF;
}
@font-face {
  font-family: "Noto Sans SC Subset";
  font-style: normal; font-weight: 600 700; font-display: swap;
  src: url("/static/fonts/NotoSansSC-Bold-subset.woff2") format("woff2");
  unicode-range: U+2000-206F, U+3000-303F, U+FF00-FFEF, U+4E00-9FFF, U+3400-4DBF;
}

/* ── 令牌 ──────────────────────────────────────────────────── */
:root {
  --void:   #05070C;
  --ink:    #0A0E16;
  --slate:  #10151F;
  --raise:  #151B27;
  --haze:   #1E2636;
  --hair:   #161D2A;

  --paper:  #EEF2F8;
  --mist:   #8A96AB;
  /* 三级文字层次：paper 17.9:1 / mist 6.8:1 / dust 4.6:1（对 --void）。
     dust 原本是 #5C6879 = 3.56:1，**低于 WCAG AA 对小字的 4.5:1** ——
     而它正用在最需要看清的地方：时间戳、脚注、小标签。抬到刚好过线，
     不再多抬：再亮就和 mist 并成一档，三级层次就塌成两级了。 */
  --dust:   #6C7A8D;

  --signal: #FFC53D;   /* 金丝雀 */
  --ember:  #FF6B2C;
  --deep:   #0E7C9B;   /* 冷端：青蓝，不是紫 */
  --pulse:  #3DDC97;
  --alarm:  #FF5247;
  --ash:    #6E7B8F;

  --cjk: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
         "Noto Sans SC Subset", "Noto Sans CJK SC", "Source Han Sans SC";
  --display: "Sora", var(--cjk), ui-sans-serif, system-ui, sans-serif;
  --body:    "Plex Sans", var(--cjk), ui-sans-serif, system-ui, -apple-system, sans-serif;
  --mono:    "JetBrains Mono", var(--cjk), ui-monospace, SFMono-Regular, Menlo, monospace;

  --r-lg: 18px;
  --r:    12px;
  --r-sm: 8px;
  --maxw: 1140px;

  --ease: cubic-bezier(.22, 1, .36, 1);
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--void);
  color: var(--paper);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.6;
  font-feature-settings: "cv05" 1, "ss03" 1;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* ═══════════════════════════════════════════════════════════
   流体背景
   实际的流动由 static/fluid.js 里的着色器画在 <canvas> 上（域扭曲噪声场）。
   这里只负责三件 CSS 干得比 GL 更省的事：
     1. `.field` —— 一层静态渐变兜底。没有 WebGL、上下文丢失、脚本被拦，
        看到的仍然是一张完整的图，而不是一块纯黑。
     2. `.grain` —— 噪点。它压在 canvas 之上，同时给两层做色阶抖动。
     3. 定位与合成上下文。
   ⚠️ 这里**没有** filter: blur()。实测过：径向渐变本身连续，再叠 88–150px
   高斯模糊视觉上几无差别，代价是每帧重新光栅化整块 —— 软件渲染下
   整页 61fps → 6fps。柔和度归着色器管。
   ═══════════════════════════════════════════════════════════ */
.aurora {
  position: fixed; inset: 0; z-index: 0;
  pointer-events: none; overflow: hidden;
  contain: strict;
  background: var(--void);
}

/* 兜底层：一张静止的场。**刻意不做动画** —— 没有 GL 的设备通常也没有
   富余的算力去补一段 CSS 动画，给一张好看的静止图是更诚实的降级。 */
.aurora .field {
  position: absolute; inset: 0; display: block;
  background:
    radial-gradient(58vw 46vw at 22% -6%,  rgba(255,197,61,.20) 0%,
                    rgba(255,197,61,.085) 34%, rgba(255,197,61,.02) 62%, transparent 82%),
    radial-gradient(52vw 44vw at 88% 4%,   rgba(255,107,44,.16) 0%,
                    rgba(255,107,44,.06) 36%, rgba(255,107,44,.014) 64%, transparent 84%),
    radial-gradient(72vw 56vw at 54% 26%,  rgba(14,124,155,.26) 0%,
                    rgba(14,124,155,.10) 38%, rgba(14,124,155,.025) 66%, transparent 86%),
    radial-gradient(46vw 40vw at -4% 52%,  rgba(43,63,134,.22) 0%,
                    rgba(43,63,134,.08) 40%, transparent 78%),
    linear-gradient(180deg, transparent 40%, rgba(5,7,12,.6) 72%, var(--void) 92%);
}

/* canvas 起手透明，着色器画出第一帧后由脚本加 .on 淡入 ——
   否则会先闪一下未初始化的黑板，正好盖住兜底层最好看的地方。 */
.aurora .fluid {
  position: absolute; inset: 0; display: block;
  width: 100%; height: 100%;
  opacity: 0; transition: opacity .9s var(--ease);
}
.aurora .fluid.on { opacity: 1; }

/* 兜底层跟着一起压暗 —— 否则没有 WebGL 的人在数据页会看到一张
   比有 WebGL 的人更吵的背景，降级不该反而更抢眼。 */
body[data-bg="quiet"] .aurora .field { opacity: .48; }

.aurora .grain {
  position: absolute; inset: -50%;
  /* ⚠️ 这一层不只是质感，它在**抖动色阶**：大面积平滑渐变在 8bit 下
     会显出同心圆环（实测肉眼可见）。噪声把台阶打散 —— 静态、零帧开销，
     比再糊一遍便宜得多。调低于 .06 环就会重新显出来。 */
  opacity: .07; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

.page { position: relative; z-index: 1; }

/* ── 骨架 ──────────────────────────────────────────────────── */
.wrap { max-width: var(--maxw); margin-inline: auto; padding-inline: 24px; }
.narrow { max-width: 760px; }

section { padding-block: 92px; }
section.tight { padding-block: 52px; }

/* ⚠️ 这条通栏分隔线原本是 `border-top: 1px solid var(--hair)`，而 --hair 是
   #161D2A —— 一个**比背景更暗**的颜色。在卡片和表格里那是对的（那里的底色
   是 --ink/--raise，比它亮）；但整幅横过被极光照亮的背景时，同一个值读起来
   就是一道黑疤。深色底上的分节要用**光**，不能用暗。
   两端淡出，让它是一道缝，不是一根杠。 */
.rule {
  border: 0; margin: 0; height: 1px;
  background: linear-gradient(90deg, transparent 0%,
      rgba(255,255,255,.05) 20%, rgba(255,255,255,.075) 50%,
      rgba(255,255,255,.05) 80%, transparent 100%);
}

/* ── 排版 ──────────────────────────────────────────────────── */
h1, h2, h3, h4 { font-family: var(--display); font-weight: 600; letter-spacing: -.028em; margin: 0; }

.status {
  font-family: var(--mono); font-size: .72rem; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: var(--mist);
  display: inline-flex; align-items: center; gap: 9px;
}
/* 首屏签名面板的标题也带一个脉冲点，但它不在 .status 里 —— 只写
   `.status .dot` 时那个 <i> 什么都不渲染，面板看着就不像活的。 */
.status .dot, .flight-title .dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--pulse);
  box-shadow: 0 0 0 0 rgba(61,220,151,.55); animation: ping 2.6s infinite;
}
@keyframes ping {
  0%   { box-shadow: 0 0 0 0 rgba(61,220,151,.5); }
  70%  { box-shadow: 0 0 0 8px rgba(61,220,151,0); }
  100% { box-shadow: 0 0 0 0 rgba(61,220,151,0); }
}

h2.sec { font-size: clamp(1.5rem, 3.2vw, 2.05rem); margin-block: 14px 10px; }
p.sub { color: var(--mist); margin: 0 0 34px; max-width: 60ch; font-size: 1.02rem; }

/* 排版规则（写死，不要凭感觉选）：
     · **展示级**数字（首页标尺、数据页图块）用 Sora —— 大字号下等宽显得笨重
     · **行内 / 表格**数字、地址、时间戳、标签用等宽 —— 需要对齐，也需要
       「这是机器给的事实」这个语气
   之前同一个「46.2 min」在首页是 Sora、在数据页是等宽，就是没定规则的结果。 */
.fact, .addr, td.num, .kv td, code { font-family: var(--mono); }
.ruler-top .big { font-family: var(--display); letter-spacing: -.03em; }
.addr { word-break: break-all; letter-spacing: -.01em; }

a { color: var(--signal); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 4px; }

:focus-visible { outline: 2px solid var(--signal); outline-offset: 3px; border-radius: 4px; }

/* ── 顶栏 ──────────────────────────────────────────────────── */
header.nav {
  position: sticky; top: 0; z-index: 30;
  background: color-mix(in srgb, var(--void) 72%, transparent);
  backdrop-filter: blur(16px) saturate(140%);
  border-bottom: 1px solid var(--hair);
}
.nav .wrap { display: flex; align-items: center; gap: 26px; height: 66px; }
.brand { display: inline-flex; align-items: center; gap: 11px; color: var(--paper); font-family: var(--display); font-weight: 600; letter-spacing: -.02em; }
.brand:hover { text-decoration: none; }
.brand img { width: 28px; height: 28px; border-radius: 8px; display: block; }
.nav nav { display: flex; align-items: center; gap: 26px; margin-left: auto; }
.nav nav a { color: var(--mist); font-size: .9rem; }
.nav nav a:hover { color: var(--paper); text-decoration: none; }
.nav nav a[aria-current="page"] { color: var(--paper); }

/* ── 按钮 ──────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 12px 22px; border-radius: 999px; border: 1px solid transparent;
  font-family: var(--body); font-weight: 550; font-size: .94rem;
  cursor: pointer; white-space: nowrap;
  transition: transform .18s var(--ease), box-shadow .18s var(--ease),
              background .18s var(--ease), border-color .18s var(--ease);
}
.btn:hover { text-decoration: none; transform: translateY(-1px); }
.btn-primary {
  background: linear-gradient(135deg, #FFD469 0%, var(--signal) 45%, var(--ember) 130%);
  color: #17100A; font-weight: 600;
  box-shadow: 0 1px 0 rgba(255,255,255,.35) inset, 0 6px 22px -8px rgba(255,150,50,.55);
}
.btn-primary:hover { box-shadow: 0 1px 0 rgba(255,255,255,.4) inset, 0 10px 30px -8px rgba(255,150,50,.7); }
.btn-ghost { border-color: var(--haze); color: var(--paper); background: color-mix(in srgb, var(--slate) 60%, transparent); }
.btn-ghost:hover { border-color: var(--ash); background: var(--raise); }
.btn-sm { padding: 8px 16px; font-size: .87rem; }

/* ── 首屏 ──────────────────────────────────────────────────── */
.hero { padding-block: 104px 60px; }
.hero h1 {
  text-wrap: balance;
  font-size: clamp(2.5rem, 6.6vw, 4.3rem);
  line-height: 1.02; margin-block: 20px 22px; font-weight: 600;
  max-width: 17ch;
}
/* 强调靠字重与色值本身，不靠给字刷渐变 —— 渐变文字是装饰，不是层级 */
.hero h1 .lit { color: var(--signal); }
.hero .lede { color: var(--mist); font-size: clamp(1.02rem, 1.9vw, 1.16rem); margin: 0 0 34px; max-width: 54ch; }

/* 入场：一次编排好的序列，而不是到处撒动画 */
.rise { opacity: 0; transform: translateY(14px); animation: rise .8s var(--ease) forwards; }
.d1 { animation-delay: .05s } .d2 { animation-delay: .14s } .d3 { animation-delay: .23s }
.d4 { animation-delay: .32s } .d5 { animation-delay: .42s }
@keyframes rise { to { opacity: 1; transform: none; } }
.note { color: var(--dust); font-size: .84rem; margin: 12px 0 0; font-family: var(--mono); letter-spacing: -.01em; }

/* ═══════════════════════════════════════════════════════════
   签名元素：此刻在途的提案
   这是整个产品唯一无法被复制的东西 —— 别人没有这份数据。
   所以它不是一张图表，它就是首屏的主角，而且是活的。
   ═══════════════════════════════════════════════════════════ */
.flight {
  margin-top: 52px; border-radius: var(--r-lg);
  border: 1px solid var(--haze);
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--raise) 90%, transparent) 0%,
                            color-mix(in srgb, var(--ink) 92%, transparent) 100%);
  box-shadow: 0 30px 70px -40px rgba(0,0,0,.9), 0 0 0 1px rgba(255,255,255,.02) inset;
  overflow: hidden;
  backdrop-filter: blur(6px);
}
.flight-head {
  display: flex; align-items: center; gap: 14px;
  padding: 16px 22px; border-bottom: 1px solid var(--hair);
}
.flight-head .status { color: var(--paper); }
.flight-title {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--mono); font-size: .72rem; font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase; color: var(--paper);
}
.flight-head .count {
  margin-left: auto; font-family: var(--mono); font-size: .78rem;
  color: var(--mist); letter-spacing: .04em;
}
.flight-row {
  display: grid; grid-template-columns: 14px 1fr auto auto;
  gap: 16px; align-items: center;
  padding: 15px 22px; border-bottom: 1px solid var(--hair);
  transition: background .18s var(--ease);
}
.flight-row:last-child { border-bottom: 0; }
.flight-row:hover { background: color-mix(in srgb, var(--raise) 70%, transparent); }
.flight-row .mark { width: 8px; height: 8px; border-radius: 2px; transform: rotate(45deg); }
.flight-row.a-add    .mark { background: var(--alarm); box-shadow: 0 0 12px rgba(255,82,71,.6); }
.flight-row.a-remove .mark { background: var(--pulse); }
.flight-row.a-destroy .mark { background: var(--ash); }
.flight-row .who { font-family: var(--mono); font-size: .88rem; color: var(--paper); }
.flight-row .what {
  font-family: var(--mono); font-size: .7rem; letter-spacing: .13em; text-transform: uppercase;
  padding: 4px 10px; border-radius: 999px; border: 1px solid var(--haze); color: var(--mist);
}
.flight-row.a-add .what { color: #FFB0AA; border-color: rgba(255,82,71,.34); background: rgba(255,82,71,.07); }
.flight-row .when { font-family: var(--mono); font-size: .82rem; color: var(--mist); font-variant-numeric: tabular-nums; }
.flight-empty { padding: 30px 22px; color: var(--mist); font-size: .95rem; }
.flight-foot {
  padding: 13px 22px; border-top: 1px solid var(--hair);
  font-family: var(--mono); font-size: .75rem; color: var(--dust); letter-spacing: .03em;
  display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
}

/* ── 窗口标尺 ──────────────────────────────────────────────── */
.ruler { border: 1px solid var(--haze); border-radius: var(--r-lg); padding: 30px 32px; background: color-mix(in srgb, var(--ink) 82%, transparent); }
.ruler-top { display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; margin-bottom: 26px; }
.ruler-top .big { font-size: clamp(2rem, 5vw, 2.9rem); font-weight: 600; letter-spacing: -.03em; line-height: 1; }
.ruler-top .big.amber { color: var(--signal); }
.ruler-top .k { font-family: var(--mono); font-size: .72rem; letter-spacing: .14em; text-transform: uppercase; color: var(--dust); margin-top: 9px; }

/* 刻度在轨道**下方**，中位标记在**上方** —— 挤在同一行时
   「median 46.2 min」会直接压在「1 h」上面。 */
.scale { position: relative; height: 84px; margin-top: 6px; }
.scale-track {
  position: absolute; inset: 38px 0 auto; height: 10px; border-radius: 999px;
  background: linear-gradient(90deg,
    rgba(255,82,71,.85) 0%, rgba(255,107,44,.8) 14%,
    rgba(255,197,61,.8) 38%, rgba(61,220,151,.55) 74%, rgba(14,124,155,.45) 100%);
}
.scale-tick { position: absolute; top: 50px; transform: translateX(-50%); text-align: center; }
.scale-tick i { display: block; width: 1px; height: 9px; background: var(--haze); margin: 0 auto 6px; }
.scale-tick span { font-family: var(--mono); font-size: .68rem; color: var(--dust); letter-spacing: .05em; white-space: nowrap; }

.scale-median { position: absolute; top: 0; transform: translateX(-50%); z-index: 2; text-align: center; }
.scale-median span {
  display: block; font-family: var(--mono); font-size: .71rem; letter-spacing: .09em;
  text-transform: uppercase; color: var(--paper); white-space: nowrap; margin-bottom: 5px;
}
.scale-median i {
  display: block; width: 2px; height: 22px; border-radius: 2px; margin: 0 auto;
  background: linear-gradient(180deg, rgba(255,255,255,.25), var(--paper));
  box-shadow: 0 0 12px rgba(255,255,255,.45);
}

/* ── 卡片 / 网格 ───────────────────────────────────────────── */
.grid { display: grid; gap: 18px; }
/* ⚠️ min(320px, 100%) 里的 min() 不能省。minmax(320px, 1fr) 在容器窄于
   320px 时不会退让 —— 轨道**仍然是 320px**，直接顶穿容器：320px 的屏上
   内容区只有 272px，于是 body 被撑出 48px 横向滚动。包一层 min() 之后
   下限跟着容器走，窄屏自然塌成一列。
   同文件里另外三处 auto-fit 的下限是 140–160px，当前最窄内容区 272px
   还够，没有一起改；真要再窄下去，它们是同一个坑。 */
.g2 { grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }

.card {
  border: 1px solid var(--haze); border-radius: var(--r-lg); padding: 26px;
  background: color-mix(in srgb, var(--ink) 80%, transparent);
  transition: border-color .22s var(--ease), transform .22s var(--ease);
}
.card:hover { border-color: color-mix(in srgb, var(--ash) 55%, transparent); }
/* 「没找到」那两页原本整页没有标题 —— 读屏器用户听不到这一页是干什么的。
   把那句 <strong> 换成真正的 h1，同时把它的排版还原成 strong 的样子：
   页面看起来一个像素都没变，变的只是它在无障碍树里的身份。 */
.notice :is(h1, h2, h3) {
  display: inline; font: inherit; font-weight: 700;
  letter-spacing: inherit; color: inherit; margin: 0;
}

/* ⚠️ 组件样式**不许拿标题标签当选择器**（.card h3、.step h4 这种）。
   标题层级是文档结构的决定 —— 为了不跳级、为了每页有且只有一个 h1，
   它随时会改；而改标签的人不会想到自己顺手弄掉了一条样式：页面照常
   渲染，只是字号忽然变成浏览器默认值。这类回归不报错。
   所以一律写成 :is(h1,…,h6)，让层级自由变动。 */
.card :is(h1,h2,h3,h4,h5,h6) { font-size: 1.06rem; margin-bottom: 10px; }
.card p { color: var(--mist); margin: 0 0 16px; font-size: .96rem; }
.card p:last-child { margin-bottom: 0; }

/* ── 账目表 ──────────────────────────────────────────────────
   代替「六个等大的数字盒」。数字右对齐、等宽、按小数点排齐 ——
   台账就该长成台账，不是仪表盘。 */
.tally { width: 100%; border-collapse: collapse; margin-top: 26px; }
.tally caption {
  caption-side: top; text-align: left; padding-bottom: 12px;
  font-family: var(--mono); font-size: .72rem; letter-spacing: .04em;
  color: var(--dust); max-width: 68ch;
}
.tally th, .tally td { padding: 15px 0; border-bottom: 1px solid var(--hair); vertical-align: baseline; }
.tally tr:last-child th, .tally tr:last-child td { border-bottom: 0; }
.tally th {
  text-align: left; font-weight: 400; color: var(--mist); font-size: .95rem;
  width: 34%;
}
.tally td.n {
  font-family: var(--display); font-size: 1.65rem; letter-spacing: -.03em;
  color: var(--paper); text-align: right; width: 26%; padding-right: 24px;
  font-variant-numeric: tabular-nums;
}
.tally td.x { color: var(--dust); font-size: .88rem; font-family: var(--mono); }
.tally.all-time { margin-top: 34px; }
.tally.all-time td.n { color: var(--signal); }

/* ── 结论横幅 ──────────────────────────────────────────────── */
.verdict {
  border-radius: var(--r-lg); padding: 30px 32px;
  border: 1px solid var(--haze); position: relative; overflow: hidden;
  background: color-mix(in srgb, var(--ink) 86%, transparent);
}
.verdict::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 1px; }
.verdict :is(h1,h2,h3,h4,h5,h6) { font-size: clamp(1.5rem, 3.4vw, 1.95rem); margin-bottom: 8px; }
.verdict p { margin: 0; color: var(--mist); }
.verdict .glyph { font-family: var(--mono); font-size: .72rem; letter-spacing: .16em; text-transform: uppercase; margin-bottom: 12px; display: block; }

.lv-clean::before            { background: var(--pulse); }
.lv-clean :is(h1,h2,h3,h4,h5,h6), .lv-clean .glyph { color: var(--pulse); }
.lv-pending_proposal::before { background: var(--signal); }
.lv-pending_proposal :is(h1,h2,h3,h4,h5,h6), .lv-pending_proposal .glyph { color: var(--signal); }
.lv-pending_proposal         { background: linear-gradient(115deg, rgba(255,197,61,.09), transparent 52%), color-mix(in srgb, var(--ink) 86%, transparent); }
.lv-frozen::before           { background: var(--alarm); }
.lv-frozen :is(h1,h2,h3,h4,h5,h6), .lv-frozen .glyph { color: var(--alarm); }
.lv-frozen                   { background: linear-gradient(115deg, rgba(255,82,71,.11), transparent 52%), color-mix(in srgb, var(--ink) 86%, transparent); }
.lv-destroyed::before        { background: var(--ash); }
.lv-destroyed :is(h1,h2,h3,h4,h5,h6), .lv-destroyed .glyph { color: #AEB8C7; }
.lv-sanctioned::before       { background: var(--alarm); }
.lv-sanctioned :is(h1,h2,h3,h4,h5,h6), .lv-sanctioned .glyph { color: var(--alarm); }
.lv-unknown::before          { background: var(--dust); }
.lv-unknown :is(h1,h2,h3,h4,h5,h6), .lv-unknown .glyph { color: var(--mist); }

/* ── 键值表 ────────────────────────────────────────────────── */
.kv { width: 100%; border-collapse: collapse; }
.kv th, .kv td { text-align: left; padding: 14px 0; border-bottom: 1px solid var(--hair); vertical-align: top; }
.kv th { color: var(--dust); font-weight: 400; width: 36%; font-family: var(--mono); font-size: .72rem; letter-spacing: .12em; text-transform: uppercase; padding-top: 17px; }
.kv td { font-size: .95rem; font-variant-numeric: tabular-nums; }
.kv tr:last-child th, .kv tr:last-child td { border-bottom: 0; }
.kv .na { color: var(--dust); font-style: italic; font-family: var(--body); }

/* ── 数据表 ────────────────────────────────────────────────── */
.table-scroll { overflow-x: auto; border: 1px solid var(--haze); border-radius: var(--r-lg); background: color-mix(in srgb, var(--ink) 80%, transparent); }
table.data { width: 100%; border-collapse: collapse; min-width: 560px; }
table.data th {
  text-align: left; font-family: var(--mono); font-weight: 500; font-size: .69rem;
  letter-spacing: .14em; text-transform: uppercase; color: var(--dust);
  padding: 15px 22px; border-bottom: 1px solid var(--hair);
}
table.data td { padding: 15px 22px; border-bottom: 1px solid var(--hair); font-size: .9rem; }
table.data tr:last-child td { border-bottom: 0; }
table.data tbody tr { transition: background .16s var(--ease); }
table.data tbody tr:hover { background: color-mix(in srgb, var(--raise) 60%, transparent); }
table.data td.num { text-align: right; font-family: var(--mono); color: var(--dust); font-variant-numeric: tabular-nums; }
table.data td .addr { font-size: .87rem; }
/* ⚠️ .addr 全局是 word-break: break-all —— 地址详情页需要它，长地址在卡片里
   必须能折。但表格已经**自己横向滚动**了，再让内容折行就成了两头都退让：
   列被压到 8px 宽，「10640」竖着排成一列一个数字，一行 250px 高。
   表格里一律不折，让表的自然宽度撑开，横向由 .table-scroll 承接。 */
.table-scroll .addr { word-break: normal; white-space: nowrap; }
/* 手指的目标高度。行内元素的上下 padding **不进入行盒高度**，所以这一条
   只把可点区域从 17px 撑到 27px，表格一行还是 60px，版式一点不动。 */
@media (pointer: coarse) {
  .table-scroll .addr { padding-block: 5px; }
}

.tag {
  display: inline-block; font-family: var(--mono); font-size: .67rem;
  letter-spacing: .13em; text-transform: uppercase;
  padding: 4px 10px; border-radius: 999px; border: 1px solid var(--haze); color: var(--mist);
}
.tag.add { color: #FFB0AA; border-color: rgba(255,82,71,.34); background: rgba(255,82,71,.07); }
.tag.remove { color: #9DEBC4; border-color: rgba(61,220,151,.28); background: rgba(61,220,151,.06); }

/* ── 条形图 ────────────────────────────────────────────────── */
.bars { display: grid; gap: 11px; }
.bar-row { display: grid; grid-template-columns: minmax(88px, 116px) 1fr 64px; gap: 16px; align-items: center; }
.bar-row .lbl { font-family: var(--mono); font-size: .76rem; color: var(--mist); letter-spacing: .02em; }
.bar-row .val { font-family: var(--mono); font-size: .82rem; text-align: right; font-variant-numeric: tabular-nums; }
.bar-track { display: block; height: 8px; border-radius: 999px; background: var(--raise); overflow: hidden; }
.bar-fill { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--ember), var(--signal)); }
.bar-fill.ok { background: linear-gradient(90deg, #1C8F62, var(--pulse)); }

/* ── 步骤 ──────────────────────────────────────────────────── */
.steps { display: grid; gap: 4px; }
.step { display: grid; grid-template-columns: 92px 1fr; gap: 26px; padding: 26px 0; border-top: 1px solid var(--hair); }
.step:first-child { border-top: 0; }
.step .idx { font-family: var(--mono); font-size: .72rem; letter-spacing: .16em; color: var(--signal); padding-top: 5px; }
.step :is(h1,h2,h3,h4,h5,h6) { font-size: 1.12rem; margin-bottom: 8px; }
.step p { margin: 0; color: var(--mist); font-size: .97rem; }

/* ── 提示条 ────────────────────────────────────────────────── */
.notice {
  border-radius: var(--r); padding: 17px 20px; font-size: .93rem;
  border: 1px solid var(--haze); color: var(--mist);
  background: color-mix(in srgb, var(--slate) 70%, transparent);
}
.notice strong { color: var(--paper); font-weight: 550; }
.notice.warn   { border-color: rgba(255,197,61,.26); background: rgba(255,197,61,.05); }
.notice.danger { border-color: rgba(255,82,71,.28); background: rgba(255,82,71,.05); color: #FFC4BF; }
.notice.danger strong { color: #FFE3E0; }

details.disclose { border: 1px solid var(--haze); border-radius: var(--r); background: color-mix(in srgb, var(--ink) 80%, transparent); }
details.disclose summary { cursor: pointer; padding: 16px 20px; font-family: var(--mono); font-size: .76rem; letter-spacing: .13em; text-transform: uppercase; color: var(--mist); list-style: none; }
details.disclose summary::-webkit-details-marker { display: none; }
details.disclose summary::after { content: " +"; color: var(--dust); }
details.disclose[open] summary::after { content: " −"; }
details.disclose .body { padding: 0 20px 20px; display: grid; gap: 14px; }
details.disclose .body p { margin: 0; color: var(--mist); font-size: .93rem; }

/* ── 页脚 ──────────────────────────────────────────────────── */
/* 页脚那道线同理 —— 它也是通栏的，也横过亮背景 */
footer.foot {
  border-top: 0; padding-block: 42px 56px; margin-top: 40px; position: relative;
}
footer.foot::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 1px;
  background: linear-gradient(90deg, transparent 0%,
      rgba(255,255,255,.05) 20%, rgba(255,255,255,.075) 50%,
      rgba(255,255,255,.05) 80%, transparent 100%);
}
.foot-row { display: flex; gap: 26px; align-items: baseline; flex-wrap: wrap; }
.foot-row .sp { margin-left: auto; }
footer.foot a { color: var(--mist); font-size: .89rem; }
footer.foot p { color: var(--dust); font-size: .84rem; margin: 18px 0 0; max-width: 76ch; }
.foot-row > span { font-family: var(--mono); font-size: .75rem; letter-spacing: .05em; color: var(--dust); }

/* ── 工具 ──────────────────────────────────────────────────── */
.muted { color: var(--mist); } .faint { color: var(--dust); }
.small { font-size: .87rem; }
.row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.mb0 { margin-bottom: 0; }

/* ═══════════════════════════════════════════════════════════
   Explorer —— 浏览器页专用组件
   排版论点不变：**可核对的东西一律等宽**。浏览器页几乎全是可核对的
   东西，所以这里的等宽比例最高 —— 这不是风格，这就是论点本身。
   ═════════════════════════════════════════════════════════ */

.scanhead { padding-block: 0 0; }
/* 标签页与筛选之间原本隔着一整个 section 的上下内边距 —— 视觉上像断了页。
   它们是同一个控制区，间距该按控件算，不按版块算。 */
.scanhead + section { padding-top: 24px; }

/* 账本状态条 —— 「数据从哪开始、到哪为止」放在顶上才是凭据，
   放在页脚就变成了免责声明 */
.ledgerbar {
  display: flex; align-items: center; gap: 26px; flex-wrap: wrap;
  border: 1px solid var(--haze); border-radius: var(--r-lg);
  padding: 16px 22px; background: color-mix(in srgb, var(--ink) 80%, transparent);
}
.ledgerbar dl { display: flex; gap: 34px; flex-wrap: wrap; margin: 0; }
.ledgerbar dt {
  font-family: var(--mono); font-size: .66rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--dust);
}
.ledgerbar dd { margin: 5px 0 0; font-size: .92rem; color: var(--paper); font-variant-numeric: tabular-nums; }

/* 标签页：下划线而不是胶囊 —— 胶囊会和下面的筛选 chip 撞成一片，
   读者分不出「换表」和「换筛选」是两个不同层级的动作 */
.tabs { display: flex; gap: 4px; margin-top: 26px; border-bottom: 1px solid var(--hair); }
.tabs a {
  padding: 12px 18px; color: var(--mist); font-size: .93rem;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color .16s var(--ease), border-color .16s var(--ease);
}
.tabs a:hover { color: var(--paper); }
.tabs a[aria-current="page"] { color: var(--signal); border-bottom-color: var(--signal); }

/* 筛选。每个 chip 都是真链接：每种组合都有自己的 URL，
   能收藏、能分享、后退键能还原 */
.filters { display: flex; gap: 14px; flex-wrap: wrap; margin: 24px 0 22px; }
.chips { display: flex; gap: 6px; flex-wrap: wrap; padding: 4px; border: 1px solid var(--hair); border-radius: 999px; }
.chip {
  font-family: var(--mono); font-size: .74rem; letter-spacing: .06em;
  padding: 7px 14px; border-radius: 999px; color: var(--mist);
  transition: background .16s var(--ease), color .16s var(--ease);
}
.chip:hover { background: var(--raise); color: var(--paper); }
.chip.on { background: var(--signal); color: #10131A; font-weight: 600; }

.pager { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 22px; }
.pager .sp { margin-left: auto; }
.pager .is-off { opacity: .3; pointer-events: none; }
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.nowrap { white-space: nowrap; }
.mini { font-family: var(--mono); font-size: .72rem; letter-spacing: .14em; text-transform: uppercase; color: var(--dust); margin-bottom: 16px; }
.grid.wide-gap { gap: 46px; }

/* 密表：一屏要装下 50 行，行高必须收紧，否则翻页比读还累 */
table.data.dense th { padding: 12px 16px; }
table.data.dense td { padding: 11px 16px; font-size: .86rem; line-height: 1.35; }
table.data.dense td .addr { font-size: .8rem; }

/* 事件与状态的色标。冻结走警报色、解冻走脉冲色，
   其余一律中性 —— 多签内务不该有情绪 */
.tag.destroy { color: #C9D2E0; border-color: rgba(174,184,199,.3); background: rgba(174,184,199,.06); }
.tag.propose { color: #FFDC96; border-color: rgba(255,197,61,.3); background: rgba(255,197,61,.06); }
.tag.execute, .tag.other { color: var(--mist); }
.tag.failed { color: #FFB0AA; border-color: rgba(255,82,71,.3); }
.tag.st-pending  { color: #FFDC96; border-color: rgba(255,197,61,.34); background: rgba(255,197,61,.07); }
.tag.st-executed { color: var(--mist); }
.tag.st-stale    { color: var(--dust); border-style: dashed; }

/* 提案详情的横幅，与地址页的等级横幅共用 .verdict 骨架 */
.pr-pending::before  { background: var(--signal); }
.pr-pending :is(h1,h2,h3,h4,h5,h6), .pr-pending .glyph { color: var(--signal); }
.pr-pending { background: linear-gradient(115deg, rgba(255,197,61,.09), transparent 52%), color-mix(in srgb, var(--ink) 86%, transparent); }
.pr-executed::before { background: var(--deep); }
.pr-executed .glyph  { color: #6FC6DE; }
.pr-stale::before    { background: var(--dust); }
.pr-stale .glyph     { color: var(--mist); }

/* 柱状图（月度趋势 / 窗口趋势）。
   ⚠️ .col-stack 必须给定高度并 display:flex，段落用 flex 分配 ——
      早先用 <span> 直接设 height 百分比，行内元素不认，全成了空轨道。 */
/* ⚠️ overflow-x 不是为了好看，是这张图**列数会长**：--n 是月份数，每个月
   多一根。1fr 实际是 minmax(auto, 1fr)，auto 的下限是 min-content，而每列
   的下限就是「Aug」这个标签的宽度（19px），压不下去 —— 于是 14 列需要
   344px，320px 屏上直接把 body 撑出横向滚动。今天是 14 列，一年后 26 列
   会撑坏 640px 的屏。让它在自己的容器里横向滚，跟宽表同一个处理方式，
   列数再长也只是这张图变得可滑，不会顶穿页面。 */
.cols { display: grid; grid-template-columns: repeat(var(--n), 1fr); gap: 6px; align-items: end; height: 190px; margin-top: 10px; overflow-x: auto; overflow-y: hidden; }
.col { display: flex; flex-direction: column; justify-content: flex-end; height: 100%; gap: 8px; }
.col-stack { display: flex; flex-direction: column-reverse; width: 100%; min-height: 3px; border-radius: 4px; overflow: hidden; }
.col-stack .seg { display: block; width: 100%; min-height: 0; }
.seg.add     { background: linear-gradient(180deg, var(--alarm), #B23A33); }
.seg.remove  { background: var(--pulse); }
.seg.destroy { background: var(--ash); }
.seg.amber   { background: linear-gradient(180deg, var(--signal), var(--ember)); }
.col-lbl { font-family: var(--mono); font-size: .64rem; color: var(--dust); text-align: center; letter-spacing: .04em; }
.legend { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-top: 18px; font-family: var(--mono); font-size: .72rem; color: var(--mist); }
.legend .sp { margin-left: auto; }
.legend i.sw { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 7px; vertical-align: -1px; }
.sw.add { background: var(--alarm); } .sw.remove { background: var(--pulse); } .sw.destroy { background: var(--ash); }

/* 24 小时作息。别处没有这张图，它是本站最独有的一块数据 */
.hours { display: grid; grid-template-columns: repeat(24, 1fr); gap: 3px; align-items: end; height: 150px; }
.hr { display: flex; flex-direction: column; justify-content: flex-end; height: 100%; gap: 7px; }
.hr-bar { display: block; width: 100%; min-height: 2px; border-radius: 3px 3px 0 0; background: linear-gradient(180deg, var(--signal), rgba(255,107,44,.35)); }
.hr-lbl { font-family: var(--mono); font-size: .6rem; color: var(--dust); text-align: center; height: 12px; }

/* 时间线。竖线由 li 的左边框画出，标记点压在线上 */
.tline { list-style: none; margin: 18px 0 0; padding: 0 0 0 4px; }
.tline li { position: relative; padding: 0 0 22px 28px; border-left: 1px solid var(--hair); }
.tline li:last-child { border-left-color: transparent; padding-bottom: 0; }
.tline .tl-mark { position: absolute; left: -5px; top: 4px; width: 9px; height: 9px; border-radius: 50%; background: var(--ash); border: 2px solid var(--void); }
.tline li.a-add .tl-mark    { background: var(--alarm); box-shadow: 0 0 10px rgba(255,82,71,.55); }
.tline li.a-remove .tl-mark { background: var(--pulse); }
.tline li.a-propose .tl-mark { background: var(--signal); }
.tline li .fact { margin-top: 7px; font-size: .95rem; color: var(--paper); }
.tline li > div > span + span { margin-left: 10px; }

/* ═══════════════════════════════════════════════════════════
   浏览器自带的表面
   这些是「没画的部分」：选中、光标、滚动条、焦点环、下划线偏移、
   表格里的数字。它们默认来自浏览器，不属于任何设计系统 ——
   一个页面是「造出来的」还是「拼出来的」，最先从这里露馅。
   ═════════════════════════════════════════════════════════ */
::selection { background: color-mix(in srgb, var(--signal) 34%, transparent); color: var(--paper); }
::-moz-selection { background: color-mix(in srgb, var(--signal) 34%, transparent); color: var(--paper); }

/* 光标用信号色：深色背景上默认的白光标偏亮，且和品牌无关 */
input, textarea { caret-color: var(--signal); }
/* 复选/单选/进度等原生控件的着色点 */
:root { accent-color: var(--signal); color-scheme: dark; }

/* 滚动条。Firefox 走标准属性，WebKit 走伪元素 —— 两条都写，
   只写一条就是在另一半浏览器上露出系统默认的浅色槽 */
* { scrollbar-width: thin; scrollbar-color: var(--haze) transparent; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--haze); border-radius: 999px;
  border: 3px solid var(--void);           /* 内缩，看起来是一条细条而不是满槽 */
}
::-webkit-scrollbar-thumb:hover { background: var(--ash); }
::-webkit-scrollbar-corner { background: transparent; }

/* 正文链接的下划线：默认贴着基线，且穿过降部。抬开并让降部断开 */
.sub a, .notice a, p a, td a:not(.addr) {
  text-underline-offset: .22em;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, var(--signal) 45%, transparent);
}
.sub a:hover, .notice a:hover, p a:hover { text-decoration-color: var(--signal); }
table, .fact, .kv td, .q-kind, kbd { font-variant-numeric: tabular-nums; }

/* ── 键帽 ──────────────────────────────────────────────────── */
kbd {
  font-family: var(--mono); font-size: .68rem; line-height: 1;
  padding: 4px 6px; border-radius: 5px; color: var(--mist);
  background: var(--raise); border: 1px solid var(--haze);
  box-shadow: 0 1px 0 var(--hair);
}

/* ── 自绘图标 ──────────────────────────────────────────────
   一个描边族、一个粗细。用 ← → ∞ 这些字形冒充图标，
   每个字体里的形状、基线、视觉重量都不一样，凑不齐一套。 */
.ico, .q-ico {
  width: 16px; height: 16px; flex: none; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ── 查询框 ────────────────────────────────────────────────── */
.q { display: flex; gap: 10px; align-items: stretch; }
.q-field {
  /* ⚠️ min-width: 0 不能省。flex 项的**自动最小尺寸**是内容的 min-content，
     <input> 默认按 size=20 个字符要地方 —— 于是这一格拒绝收缩，
     把提交按钮整个挤出屏幕。手机上曾经真的是这样。 */
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px;
  padding: 0 14px; min-height: 46px;
  background: var(--slate); border: 1px solid var(--haze); border-radius: var(--r);
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.q-field:focus-within {
  border-color: color-mix(in srgb, var(--signal) 55%, transparent);
  box-shadow: 0 0 0 4px rgba(255,197,61,.10);
}
.q-field .q-ico { color: var(--dust); }
.q-field:focus-within .q-ico { color: var(--signal); }
.q-field input {
  flex: 1; min-width: 0; background: none; border: 0; outline: none; padding: 0;
  color: var(--paper); font-family: var(--mono); font-size: .92rem;
}
.q-field input::placeholder { color: var(--dust); }

/* 边打字边标注识别到的类型。它是提示，不是判定 —— 所以低调、不抢 */
.q-kind {
  font-family: var(--mono); font-size: .66rem; letter-spacing: .1em;
  text-transform: uppercase; white-space: nowrap;
  padding: 4px 9px; border-radius: 999px;
  color: var(--pulse); background: rgba(61,220,151,.09);
  border: 1px solid rgba(61,220,151,.24);
}
.q-field[data-kind="unknown"] .q-kind { color: var(--mist); background: none; border-color: var(--haze); }
.q-field .q-key { margin-left: 2px; }
.q-field:focus-within .q-key { opacity: 0; }
.q-again { margin: 30px 0 8px; }
/* 首屏的查询框要比正文里的重一档：它是这一屏的主要动作 */
.hero-q { max-width: 660px; margin-bottom: 14px; }
.hero-q .q-field { min-height: 54px; padding-inline: 18px; }
.hero-q .q-field input { font-size: 1rem; }
.hero-q .btn { padding-inline: 26px; }
/* Explorer 页头的查询框：宽度跟着内容走，别撑满一整行 */
.scan-q { margin-top: 20px; max-width: 620px; }

/* 窄屏上把按钮换行铺满：并排时输入框只剩两百来像素，地址一个都放不下，
   而按钮反而占着一块永远点不满的宽度。整行按钮同时也是更大的点击目标。 */
@media (max-width: 520px) {
  .hero-q .q, .scan-q .q { flex-wrap: wrap; }
  .hero-q .q > .btn, .scan-q .q > .btn { flex: 1 0 100%; }
  /* 窄屏上标题和计数并排会各自折成两行，两个两行的小标签挤在一起
     谁也读不利索。给标题整行宽度，计数掉到下一行 —— 标题反而不折了。 */
  .flight-head { flex-wrap: wrap; gap: 5px 14px; }
  .flight-head .count { margin-left: 0; width: 100%; }
}

/* ── 命令面板 ──────────────────────────────────────────────── */
.cmdk-btn {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 7px 10px 7px 12px; border-radius: 999px;
  background: color-mix(in srgb, var(--slate) 70%, transparent);
  border: 1px solid var(--haze); color: var(--mist);
  font: inherit; font-size: .86rem; cursor: pointer;
  transition: color .16s var(--ease), border-color .16s var(--ease);
}
.cmdk-btn:hover { color: var(--paper); border-color: var(--ash); }
.cmdk-btn .ico { width: 15px; height: 15px; }

dialog.cmdk {
  width: min(620px, calc(100vw - 32px)); padding: 0; border: 0;
  margin-top: 12vh; border-radius: var(--r-lg);
  background: var(--ink); color: var(--paper);
  border: 1px solid var(--haze);
  box-shadow: 0 40px 90px -30px rgba(0,0,0,.9);
}
dialog.cmdk::backdrop { background: rgba(3,5,9,.72); backdrop-filter: blur(6px); }
dialog.cmdk .q { padding: 14px; border-bottom: 1px solid var(--hair); }
dialog.cmdk .q-field { border-color: transparent; background: none; }
dialog.cmdk .q-field:focus-within { box-shadow: none; }

.cmdk-list { list-style: none; margin: 0; padding: 8px; max-height: 46vh; overflow-y: auto; }
.cmdk-list li { display: flex; align-items: center; }
.cmdk-list li[hidden] { display: none; }
.cmdk-list a {
  flex: 1; display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; border-radius: var(--r-sm); color: var(--paper);
  font-size: .93rem;
}
.cmdk-list a:hover { background: var(--raise); text-decoration: none; }
.cmdk-list a[aria-selected="true"] { background: var(--raise); box-shadow: inset 0 0 0 1px var(--haze); }
.cmdk-list a + span {
  font-family: var(--mono); font-size: .66rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--dust); padding-right: 12px;
}
/* 输入像个标识符时导航项全部让位，面板只剩「回车去查」这一条路 */
dialog.cmdk[data-mode="query"] .cmdk-list { display: none; }
dialog.cmdk .cmdk-foot .on-query { display: none; }
dialog.cmdk[data-mode="query"] .cmdk-foot .on-nav { display: none; }
dialog.cmdk[data-mode="query"] .cmdk-foot .on-query { display: inline; }
.cmdk-foot {
  display: flex; gap: 16px; align-items: center; flex-wrap: wrap;
  margin: 0; padding: 11px 16px; border-top: 1px solid var(--hair);
  font-size: .74rem; color: var(--dust);
}
.cmdk-foot .sp { margin-left: auto; }
.cmdk-foot kbd { margin-right: 3px; }

/* ── 歧义选择 ──────────────────────────────────────────────── */
.picks { list-style: none; margin: 26px 0 0; padding: 0; display: grid; gap: 10px; }
.picks a {
  display: flex; align-items: center; gap: 16px; padding: 18px 20px;
  border: 1px solid var(--haze); border-radius: var(--r); color: var(--paper);
  background: color-mix(in srgb, var(--ink) 80%, transparent);
  transition: border-color .16s var(--ease), transform .16s var(--ease);
}
.picks a:hover { border-color: var(--ash); transform: translateX(3px); text-decoration: none; }
.picks .fact { font-size: 1.05rem; }
.picks .go { margin-left: auto; color: var(--dust); }
.picks a:hover .go { color: var(--signal); }
.tag.proposal { color: #FFDC96; border-color: rgba(255,197,61,.3); background: rgba(255,197,61,.06); }
.tag.block, .tag.tx { color: #9FD8E8; border-color: rgba(14,124,155,.4); background: rgba(14,124,155,.1); }

/* ── 响应式 ────────────────────────────────────────────────── */
@media (max-width: 760px) {
  section { padding-block: 62px; }
  .hero { padding-block: 68px 44px; }
  /* 导航是整站唯一会把 body 撑出横向滚动条的东西 ——
     390px 屏上「品牌 + 四个入口 + 一个按钮」放不下。收紧间距，
     再窄就把品牌收成图标、把页脚也有的入口藏掉。 */
  .nav .wrap { gap: 14px; }
  .nav nav { gap: 16px; }
  .nav nav a { font-size: .85rem; }
  .btn-sm { padding: 8px 13px; }
  .step { grid-template-columns: 1fr; gap: 8px; }
  .flight-row { grid-template-columns: 12px 1fr auto; row-gap: 8px; }
  .flight-row .when { grid-column: 2 / -1; }
  .ruler { padding: 22px; }
  .card, .verdict { padding: 20px; }
  .ledgerbar dl { gap: 20px; }
  .cols { height: 150px; }
  .hours { height: 110px; }
  .grid.wide-gap { gap: 34px; }
  .tabs a { padding: 11px 12px; font-size: .88rem; }
}

/* 导航自己的门槛，和上面那批版式规则分开写：版式在 760px 换档，而导航
   放不下的临界值是**内容宽度**决定的 —— 英文产品站全显要 792px。两者本来
   就不是同一个数，硬凑在一起的结果就是 761–800 这一段（iPad 竖屏 768px
   正在里面）又漏出来。 */
@media (max-width: 800px) {
  /* ⚠️ 选择器是 .hide-sm 不是 a.hide-sm：搜索按钮里的文字标签是个
     <span class="hide-sm">，旧写法只匹配 <a>，所以它从来没被藏起来，
     在每一个窄屏上白占 43px。 */
  .nav nav .hide-sm { display: none; }
  /* ⌘K 在触屏上不存在，却占 32px。 */
  .cmdk-btn kbd { display: none; }
}

/* 标签条列数由数据决定（批次页有 5 个），窄屏放不下就横滑，不顶穿页面。
   和宽表、月份柱状图同一个处理方式。 */
@media (max-width: 560px) {
  .tabs { overflow-x: auto; scrollbar-width: none; }
  .tabs::-webkit-scrollbar { display: none; }
  .tabs a { white-space: nowrap; }
}

@media (max-width: 470px) {
  /* 只留图标，品牌仍在 —— ⚠️ 但公司站的记号**本身就是一个 span**，
     一并藏掉的结果是页头左边空无一物。这条规则原本只服务于有 <img>
     的产品品牌，得把记号排除在外。 */
  .brand span:not(.mark) { display: none; }
  .nav nav { gap: 14px; }
  .ledgerbar dl { gap: 16px; }
  .filters { gap: 10px; }
  .chip { padding: 6px 11px; font-size: .7rem; }
}

/* 无障碍：关掉所有环境动画，页面依然完整可读 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto; }
  .rise { opacity: 1; transform: none; }
}

/* ═══════════════════════════════════════════════════════════
   批次 · 状态 · API —— 新增页面的构件
   ═══════════════════════════════════════════════════════════ */

/* 一行统计。与 .ledgerbar 的区别：ledgerbar 是「这份账本的身份证」，
   常驻页头、有边框；statrow 是某一页自己的四个数字，无框、更轻。
   两者长得像但语义不同，共用一套类会让其中一处永远显得别扭。 */
.statrow {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 22px; margin: 26px 0 8px;
  padding: 20px 0; border-top: 1px solid var(--hair); border-bottom: 1px solid var(--hair);
}
.statrow dt {
  font-family: var(--mono); font-size: .66rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--dust);
}
.statrow dd {
  margin: 8px 0 0; font-family: var(--display); font-size: 1.5rem;
  font-weight: 600; letter-spacing: -.02em; color: var(--paper);
  font-variant-numeric: tabular-nums;
}

/* 批次组成条。一批是纯冻结、还是冻结解冻混着下发 —— 形状一眼看得出，
   三个数字要读三遍。宽度按数量分配，段与段之间留 1px 缝隙区分。 */
.mixbar {
  display: flex; gap: 1px; width: 110px; height: 6px;
  border-radius: 3px; overflow: hidden; margin-bottom: 6px;
}
.mixbar i { display: block; min-width: 2px; }
.mixbar .s-add     { background: var(--signal); }
.mixbar .s-remove  { background: var(--pulse); }
.mixbar .s-destroy { background: var(--alarm); }

/* 每日采集条。缺口就是缺口 —— 不补零、不平滑，
   「那天我们没在看」和「那天链上很安静」都必须留在图上。 */
.daybars {
  display: flex; gap: 4px; align-items: flex-end;
  height: 130px; margin-top: 14px;
}
.daybars .day { flex: 1; display: flex; flex-direction: column; justify-content: flex-end;
                height: 100%; gap: 7px; min-width: 0; }
.daybars .day i {
  display: block; width: 100%; border-radius: 3px 3px 1px 1px;
  background: linear-gradient(180deg, var(--deep), color-mix(in srgb, var(--deep) 35%, transparent));
}
.daybars .day span {
  font-family: var(--mono); font-size: .58rem; color: var(--dust);
  text-align: center; overflow: hidden;
}

/* 导出条。挨着分页放，不放页头 —— 先看到表，才会想把表拿走。 */
.export {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--hair);
}

/* API 文档。端点用等宽字排成一列，说明用正文字 ——
   路径是可复制的事实，说明是人话，两者字体不同才不会混读。 */
.endpoints { display: grid; gap: 2px; margin-top: 18px; }
.endpoints article {
  padding: 20px 22px; border: 1px solid var(--hair); border-radius: var(--r);
  background: color-mix(in srgb, var(--ink) 55%, transparent);
}
.endpoints :is(h1,h2,h3,h4,h5,h6) {
  font-size: .92rem; font-weight: 500; color: var(--signal);
  letter-spacing: -.01em; margin: 0 0 8px; overflow-wrap: anywhere;
}
.endpoints p { margin: 0; color: var(--mist); font-size: .95rem; }
.params { display: grid; gap: 6px; margin: 14px 0 0; }
.params > div { display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap; }
.params dt { font-size: .78rem; color: var(--paper); min-width: 120px; }
.params dd { margin: 0; font-size: .82rem; color: var(--dust); }

/* 代码块。横向溢出**只在块内滚动** —— 让整个页面横向滚动是最容易
   在手机上犯、又最容易在桌面上漏检的一个错。 */
pre.code {
  margin: 0 0 14px; padding: 18px 20px; overflow-x: auto;
  background: var(--void); border: 1px solid var(--hair); border-radius: var(--r);
  font-family: var(--mono); font-size: .78rem; line-height: 1.7; color: var(--mist);
  tab-size: 2;
}
pre.code::-webkit-scrollbar { height: 8px; }

/* 分位条。「等了 2 小时」是读数，「比 88% 的冻结都久」才是判断。
   这条尺子把前者变成后者，它是这一块存在的全部理由。 */
.pctl { margin-top: 16px; }
.pctl .track {
  position: relative; height: 8px; border-radius: 999px;
  background: var(--slate); border: 1px solid var(--hair); overflow: hidden;
}
.pctl .fill {
  display: block; height: 100%;
  background: linear-gradient(90deg, var(--deep), var(--signal));
}
.pctl .cap {
  display: flex; gap: 10px; align-items: baseline; margin-top: 10px;
  font-size: .88rem; color: var(--mist);
}
.pctl .cap strong { color: var(--paper); font-variant-numeric: tabular-nums; }

@media (max-width: 640px) {
  .daybars { height: 100px; }
  .daybars .day span { display: none; }   /* 30 个日期在手机上必然叠成糊 */
  .statrow dd { font-size: 1.25rem; }
}

/* ── 批量筛查 ────────────────────────────────────────────────
   输入框故意做得大：这一页的动作是「粘一整列进来」，
   一个三行高的框会让人以为只能贴三个。 */
.screen-form { margin-top: 6px; }
.screen-lbl {
  display: block; color: var(--mist); font-size: .95rem; margin-bottom: 12px;
  max-width: 68ch;
}
.screen-form textarea {
  width: 100%; padding: 16px 18px; resize: vertical; min-height: 170px;
  background: var(--slate); color: var(--paper);
  border: 1px solid var(--haze); border-radius: var(--r);
  font-family: var(--mono); font-size: .84rem; line-height: 1.85;
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.screen-form textarea::placeholder { color: var(--dust); }
.screen-form textarea:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--signal) 55%, transparent);
  box-shadow: 0 0 0 4px rgba(255,197,61,.10);
}
.screen-act {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: 14px;
}
.screen-act .note { margin: 0; max-width: 56ch; }

/* ── 反应时间计算器 ──────────────────────────────────────────
   数据页上唯一一个「问一句、答一句」的构件。其余图表给分布，
   这里给读者他自己的那个答案。 */
.reaction {
  border: 1px solid var(--haze); border-radius: var(--r-lg);
  padding: 26px 28px; background: color-mix(in srgb, var(--ink) 70%, transparent);
}
.rx-readout {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 20px; margin-bottom: 26px;
}
.rx-readout dt {
  font-family: var(--mono); font-size: .66rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--dust);
}
.rx-readout dd {
  margin: 8px 0 0; font-family: var(--display); font-size: 1.7rem; font-weight: 600;
  letter-spacing: -.03em; color: var(--paper); font-variant-numeric: tabular-nums;
}
/* 覆盖率是这一屏的答案，给它品牌色；其余两个是它的上下文，留白 */
.rx-readout div:nth-child(2) dd { color: var(--signal); }
.rx-readout div:nth-child(3) dd { font-size: 1.05rem; color: var(--mist); }

.rx-slider { display: block; }
.rx-slider input[type="range"] {
  width: 100%; margin: 0; appearance: none; background: none; cursor: grab;
}
.rx-slider input[type="range"]:active { cursor: grabbing; }
.rx-slider input[type="range"]::-webkit-slider-runnable-track {
  height: 6px; border-radius: 999px;
  background: linear-gradient(90deg, var(--deep), var(--signal));
}
.rx-slider input[type="range"]::-moz-range-track {
  height: 6px; border-radius: 999px;
  background: linear-gradient(90deg, var(--deep), var(--signal));
}
.rx-slider input[type="range"]::-webkit-slider-thumb {
  appearance: none; width: 20px; height: 20px; margin-top: -7px;
  border-radius: 50%; background: var(--paper);
  border: 3px solid var(--void); box-shadow: 0 2px 10px rgba(0,0,0,.5);
}
.rx-slider input[type="range"]::-moz-range-thumb {
  width: 20px; height: 20px; border-radius: 50%; background: var(--paper);
  border: 3px solid var(--void); box-shadow: 0 2px 10px rgba(0,0,0,.5);
}
.rx-slider input[type="range"]:focus-visible { outline: 2px solid var(--signal); outline-offset: 6px; }
.rx-scale {
  display: flex; justify-content: space-between; margin-top: 10px;
  font-family: var(--mono); font-size: .64rem; color: var(--dust);
  letter-spacing: .06em;
}
/* 屏幕外但读屏器可见 —— 滑块需要一个真标签，视觉上又已经有读数了 */
.vh {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ═══════════════════════════════════════════════════════════════
   Utrust Lab —— 公司层
   ═══════════════════════════════════════════════════════════════
   母公司与产品的关系用**颜色分工**表达，不靠第二套字体或第二个 logo：
     Utrust Lab  冷端（青蓝 --deep）与纸白 —— 机构感，中性，能装下第二个产品
     UCanary     琥珀（--signal）—— 警示色，属于产品
   所以公司页上几乎不出现琥珀，产品页上琥珀是主角。这条分工一旦破了，
   两个牌子就会糊成一个。
   ─────────────────────────────────────────────────────────────── */

/* 母公司的记号：一个正在读取的窄缝。不画鸟，鸟是产品的。 */
.brand .mark {
  width: 22px; height: 22px; border-radius: 6px; flex: none;
  border: 1px solid color-mix(in srgb, var(--deep) 55%, transparent);
  background:
    linear-gradient(180deg, transparent 42%, var(--deep) 42%, var(--deep) 58%, transparent 58%),
    color-mix(in srgb, var(--deep) 12%, transparent);
  box-shadow: 0 0 12px -4px color-mix(in srgb, var(--deep) 70%, transparent);
}

/* 从属锁定：母公司在前、小而灰，产品在后、亮。 */
.brand.lockup { gap: 8px; }
.brand.lockup .parent {
  font-family: var(--mono); font-size: .74rem; font-weight: 500;
  letter-spacing: .04em; color: var(--dust);
}
.brand.lockup .slash { color: var(--hair); font-weight: 300; }
.brand.lockup img { width: 26px; height: 26px; }
@media (max-width: 560px) {
  /* 窄屏上锁定会把导航挤没。母公司退到页脚去说，页头只留产品。 */
  .brand.lockup .parent, .brand.lockup .slash { display: none; }
}

/* 首屏一度还有一行「A product of Utrust Lab」。删掉了：页头的从属锁定
   在同一屏里已经说过这件事，同一个事实说两遍不会更可信，只会更吵。
   归属的第二处出口在页脚。 */

.foot-org {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  margin-top: 20px; padding-top: 18px;
  border-top: 1px solid var(--hair);
  font-size: .82rem;
}
.foot-org .org {
  display: inline-flex; align-items: center; gap: 9px;
  color: var(--paper); font-family: var(--display); font-weight: 600;
  letter-spacing: -.01em;
}
.foot-org .org:hover { text-decoration: none; }
.foot-org .mark {
  width: 18px; height: 18px; border-radius: 5px;
  border: 1px solid color-mix(in srgb, var(--deep) 50%, transparent);
  background: linear-gradient(180deg, transparent 42%, var(--deep) 42%,
                               var(--deep) 58%, transparent 58%);
}

/* ── 公司站首屏 ─────────────────────────────────────────────── */
/* 公司页的点题色是冷端，不是琥珀 —— 琥珀是产品的警示色。
   --deep 直接用太暗，掺入亮青提到深底上够读的亮度。 */
.lab-hero h1 .lit { color: color-mix(in srgb, var(--deep) 45%, #8FE9FF); }
.lab-cta { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 44px; }

/* 仪器盘：公司站的签名元素。插画会过期，正在跑的数字不会。 */
.instrument {
  border: 1px solid var(--haze); border-radius: var(--r-lg);
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--raise) 92%, transparent) 0%,
                            color-mix(in srgb, var(--ink) 94%, transparent) 100%);
  box-shadow: 0 30px 70px -44px rgba(0,0,0,.9), 0 0 0 1px rgba(255,255,255,.02) inset;
  overflow: hidden; max-width: 780px;
}
.inst-head {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 22px; border-bottom: 1px solid var(--hair);
}
.inst-link {
  margin-left: auto; font-family: var(--mono); font-size: .76rem; color: var(--mist);
}
.inst-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 1px; background: var(--hair); margin: 0;
}
.inst-grid > div { background: var(--ink); padding: 20px 22px; }
.inst-grid dt {
  font-family: var(--mono); font-size: .68rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--dust); margin-bottom: 8px;
}
.inst-grid dd {
  margin: 0; font-family: var(--mono); font-size: 1.42rem; font-weight: 500;
  color: var(--paper); font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.inst-grid dd.amber { color: var(--signal); }
.inst-foot {
  margin: 0; padding: 13px 22px; border-top: 1px solid var(--hair);
  font-size: .8rem; color: var(--dust);
}

/* ── 分段叙事：左边四段，右边一块跟着换的证据 ─────────────────
   ⚠️ 默认布局（无 JS / 窄屏）是**四块证据平铺**。sticky 切换只在
      section 拿到 data-js 时叠加 —— 见 stack.js 顶部的注释。 */
.stack { padding-block: 72px 20px; }
.stack-rail { position: relative; margin-top: 34px; }
.pipe-track, .pipe-fill {
  position: absolute; left: 7px; top: 10px; bottom: 10px; width: 2px;
  border-radius: 2px;
}
.pipe-track { background: var(--hair); }
.pipe-fill {
  background: linear-gradient(180deg, var(--deep), color-mix(in srgb, var(--deep) 30%, transparent));
  transform-origin: top; transform: scaleY(1);
}
.stack-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 40px; }
.stack-step {
  display: grid; grid-template-columns: 16px 1fr; gap: 24px;
  align-content: center;
}
.pipe-dot {
  align-self: start; margin-top: 7px;
  width: 16px; height: 16px;
  border-radius: 50%; background: var(--ink);
  border: 2px solid var(--deep);
  box-shadow: 0 0 0 4px var(--void), 0 0 16px -2px color-mix(in srgb, var(--deep) 80%, transparent);
  transition: background .3s var(--ease), box-shadow .3s var(--ease), scale .3s var(--ease);
}
.stack-step :is(h1,h2,h3,h4,h5,h6) {
  font-family: var(--display); font-size: 1.14rem; font-weight: 600;
  margin: 0 0 7px; letter-spacing: -.01em; color: var(--paper);
}
.stack-step p { margin: 0; color: var(--mist); max-width: 46ch; }
/* 第二段说的是「这么做的代价 / 由此产生的具体行为」。压一号、再暗一档，
   让它读起来是脚注而不是并列的第二个主张 —— 两段一样重会让人不知道
   该记住哪一句。 */
.stack-step .step-why {
  margin-top: 10px;
  font-size: .875rem;
  color: var(--dust);
}
/* 第四条是「我们不做什么」—— 用红端把它和前三条区分开，
   它在语义上不是流程的第四步，是流程的边界。 */
.stack-refuse .pipe-dot { border-color: var(--ember); box-shadow: 0 0 0 4px var(--void); }
.stack-refuse :is(h1,h2,h3,h4,h5,h6) { color: var(--ember); }

/* ── 产品卡 ─────────────────────────────────────────────────── */
.prodcard {
  display: grid; grid-template-columns: auto 1fr; gap: 22px;
  padding: 26px 28px; border: 1px solid var(--haze); border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--raise) 70%, transparent);
  transition: border-color .18s ease, transform .18s ease, background .18s ease;
}
.prodcard:hover {
  text-decoration: none; border-color: color-mix(in srgb, var(--signal) 40%, transparent);
  background: color-mix(in srgb, var(--raise) 92%, transparent);
  transform: translateY(-2px);
}
.prodcard-mark img { width: 44px; height: 44px; border-radius: 12px; display: block; }
.prodcard-top { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.prodcard-top :is(h1,h2,h3,h4,h5,h6) {
  font-family: var(--display); font-size: 1.32rem; font-weight: 600;
  margin: 0; color: var(--paper); letter-spacing: -.015em;
}
.prodcard-body > p { color: var(--mist); margin: 0 0 14px; max-width: 64ch; }
.prodcard-facts {
  display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px;
}
.prodcard-facts span {
  font-family: var(--mono); font-size: .72rem; color: var(--mist);
  border: 1px solid var(--hair); border-radius: 20px; padding: 3px 10px;
  font-variant-numeric: tabular-nums;
}
.prodcard-go {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--mono); font-size: .8rem; color: var(--signal);
}
.pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--mono); font-size: .68rem; letter-spacing: .06em;
  text-transform: uppercase; padding: 3px 9px; border-radius: 20px;
}
.pill.live {
  color: var(--pulse); background: color-mix(in srgb, var(--pulse) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--pulse) 26%, transparent);
}
.pill.live .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--pulse); }

.notbuilt { display: grid; gap: 20px; margin-top: 8px; }
.notbuilt > div { border-left: 2px solid var(--hair); padding-left: 20px; }
.notbuilt :is(h1,h2,h3,h4,h5,h6) {
  font-family: var(--display); font-size: 1.06rem; font-weight: 600;
  margin: 0 0 6px; color: var(--paper);
}
.notbuilt p { margin: 0; color: var(--mist); max-width: 66ch; }
.rules .card :is(h1,h2,h3,h4,h5,h6) { margin-top: 0; }

@media (max-width: 620px) {
  .prodcard { grid-template-columns: 1fr; gap: 16px; padding: 22px; }
}
h2, h3, h4, h5, h6) {
  font-family: var(--display); font-size: 1.2rem; font-weight: 600;
  margin: 4px 0 8px; color: var(--paper); letter-spacing: -.012em;
}
.art-event {
  display: inline-flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 14px; border: 1px solid var(--hair); border-radius: 10px;
  background: color-mix(in srgb, var(--slate) 60%, transparent);
  font-size: .84rem;
}
.art-event .mono { font-family: var(--mono); color: var(--mist); }
.art-event.done { border-color: color-mix(in srgb, var(--ember) 28%, transparent); }
.gap-rail { position: relative; width: 34px; justify-self: center; }
.gap-track {
  position: absolute; left: 16px; top: 0; bottom: 0; width: 2px;
  background: repeating-linear-gradient(180deg, var(--hair) 0 6px, transparent 6px 12px);
}
.gap-fill {
  position: absolute; left: 15px; top: 0; width: 4px; height: 100%;
  border-radius: 4px; transform-origin: top; transform: scaleY(1);
  background: linear-gradient(180deg,
      color-mix(in srgb, var(--signal) 85%, transparent),
      color-mix(in srgb, var(--ember) 60%, transparent));
  box-shadow: 0 0 18px -3px color-mix(in srgb, var(--signal) 60%, transparent);
}
.gap-k {
  font-family: var(--mono); font-size: .7rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--dust); margin: 0 0 4px;
}
.gap-big {
  font-family: var(--display); font-size: clamp(2.4rem, 7vw, 3.6rem);
  font-weight: 600; color: var(--signal); margin: 0; line-height: 1;
  letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.gap-sub { font-family: var(--mono); font-size: .78rem; color: var(--dust);
           margin: 8px 0 22px; }
.gap-facts { display: grid; gap: 10px; margin-bottom: 26px; max-width: 52ch; }
.gap-facts > div { display: grid; grid-template-columns: 76px 1fr; gap: 14px;
                   align-items: baseline; }
.gap-facts b {
  font-family: var(--mono); font-size: 1.02rem; color: var(--paper);
  font-variant-numeric: tabular-nums; font-weight: 500;
}
.gap-facts span { color: var(--mist); font-size: .92rem; }

.gap-us {
  display: grid; grid-template-columns: auto 1fr; gap: 16px;
  padding: 18px 20px; border-radius: var(--r-lg);
  border: 1px solid color-mix(in srgb, var(--signal) 26%, transparent);
  background: color-mix(in srgb, var(--signal) 6%, transparent);
  max-width: 58ch;
}
.gap-us-mark { padding-top: 6px; }
.gap-us-mark .dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--signal);
  display: block; box-shadow: 0 0 0 0 color-mix(in srgb, var(--signal) 55%, transparent);
  animation: ping 2.6s infinite;
}
.gap-us :is(h1,h2,h3,h4,h5,h6) { margin: 0 0 6px; font-family: var(--display); font-size: 1.06rem;
             font-weight: 600; color: var(--signal); }
.gap-us p { margin: 0; color: var(--mist); font-size: .94rem; }

@media (max-width: 620px) {
  .gap-rail { width: 26px; }
  .gap-track { left: 12px; }
  .gap-fill { left: 11px; }
  .gap-facts > div { grid-template-columns: 62px 1fr; gap: 10px; }
}

/* ── 产品页的分段叙事 ─────────────────────────────────────────
   复用公司站那一整套 .stack-*（布局、sticky、标签、降级都只有一份实现），
   只换两件事：颜色分工，以及第二段那道「缝」的轨道。

   ⚠️ 颜色不是装饰：公司用冷端青蓝，琥珀是**产品的警示色**。
      两边糊在一起，母品牌和产品品牌就都不成立了。
      所以这里把 --deep 在本节内重绑到 --signal，而不是另写一套选择器 ——
      改一个变量，连线、圆点、发光会一起跟着走。 */
.stack-uc { --deep: var(--signal); }

/* 第二段是那道缝，它没有圆点，用的是虚线轨道 + 会被滚动画出来的填充。
   .stack-step 的第一列是 16px（圆点的宽度），轨道要在同一条中线上，
   否则第二段的竖线会比上下两段偏出去几个像素 —— 一眼看不出，
   但整条时间线会显得是歪的。 */
.stack-gap .gap-rail { width: 16px; justify-self: center; }
.stack-gap .gap-track { left: 7px; }
.stack-gap .gap-fill  { left: 6px; }
.stack-uc .pipe-track, .stack-uc .pipe-fill { top: 10px; bottom: 10px; }

/* 分布直方图 —— 中位数是一个数，这是那个数背后的整张形状。
   条形用 <i> 而不是给 <span> 设宽度：行内元素不认 width，
   进度条只剩一条空轨道这件事在这个仓库里已经发生过一次。 */
.dist { list-style: none; margin: 0; padding: 6px 0; }
.dist-row {
  display: grid; grid-template-columns: 62px 1fr 52px;
  gap: 12px; align-items: center;
  padding: 7px 18px;
  font-family: var(--mono); font-size: .74rem;
}
.dist-k { color: var(--mist); white-space: nowrap; }
.dist-n { color: var(--dust); text-align: right; font-variant-numeric: tabular-nums; }
.dist-bar {
  display: block; height: 8px; border-radius: 999px;
  background: color-mix(in srgb, var(--haze) 70%, transparent);
  overflow: hidden;
}
.dist-bar > i {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg,
      color-mix(in srgb, var(--signal) 85%, transparent),
      color-mix(in srgb, var(--ember) 70%, transparent));
}

/* ── 滚动驱动 ────────────────────────────────────────────────
   ⚠️ 上面所有元素的**默认状态就是终态**（scaleY(1)、完全可见）。
      动画只在支持滚动时间线、且用户没有要求减少动效时叠加。
      反过来写（默认隐藏、靠动画显现）的话，那 16% 不支持的浏览器
      会看到一片空白，而且不会有任何报错 —— 这类失败没有症状。 */
@keyframes draw-down { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes lift-in {   /* 同样走 translate，理由见 reveal-up 上的注释 */
  from { opacity: 0; translate: 0 14px; }
  to   { opacity: 1; translate: 0 0; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .gap-fill {
      animation: draw-down linear both;
      animation-timeline: view();
      /* 从这一块刚进视口一点点开始，到它走过大半屏时填满 ——
         让「张开」发生在读者正在读这段字的时候，而不是已经划过去之后 */
      animation-range: entry 24% cover 62%;
    }
    .pipe-fill {
      animation: draw-down linear both;
      animation-timeline: view(); animation-range: entry 10% cover 70%;
    }
.stack-step > div, .gap-us {
      animation: lift-in linear both;
      animation-timeline: view();
      animation-range: entry 8% entry 46%;
    }
.gap-fill, .pipe-fill, .stack-step > div, .gap-us { animation-duration: 1ms; }
  }
}

/* 公司站的主行动按钮：冷端。琥珀留给产品 —— 对比靠稀缺造出来，
   公司页上也用琥珀的话，两个牌子的分工当场消失。 */
.btn-lab {
  background: linear-gradient(135deg,
      color-mix(in srgb, var(--deep) 92%, #7FE3FF) 0%, var(--deep) 60%);
  color: var(--void); border: 0; font-weight: 600;
}
.btn-lab:hover { filter: brightness(1.08); text-decoration: none; }

/* 页面级大标题。⚠️ 不要用 .sec 再拿 style 改字号 —— .sec 的行高是按
   1.4rem 小标题定的（1.6 倍），放大到 45px 之后行距变成 72px，
   两行标题之间能开进一辆车。字号和行高必须一起定。 */
.page-h1 {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(2rem, 5vw, 2.85rem); line-height: 1.08;
  letter-spacing: -.025em; margin: 0 0 16px;
  color: var(--paper); text-wrap: balance;
}
/* 页头与其后的内容属于同一段呼吸。分成两个 section 时，
   前者的 padding-bottom 与后者的 padding-top 会叠成 144px 的空洞。 */
.page-head { padding-block: 76px 34px; }

/* 卡片上的小标签用 --haze —— --hair 在抬高的卡片背景上几乎看不见，
   于是那排「事实」看起来像散落的文字，不像一组标签。 */
.prodcard-facts span { border-color: var(--haze); }

/* 「All 1」与箭头之间断行，箭头掉到下一行。
   ⚠️ 真正的原因是 .ico 被定义成 display:block —— 它是给 flex 容器用的
   （自带 flex:none）。块级盒子会独占一行，white-space:nowrap 对它无效，
   我先试的那一招完全没起作用。把这个 <a> 变成 flex 容器才是对的。 */
.flight-foot a {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
}

/* ── 两栏骨架 ───────────────────────────────────────────────
   左边讲怎么做，右边是**正在做**。此前这一节是一列窄文字挂在 1440 的
   左侧，右边三分之二全空 —— 那不是留白，那是没想好放什么。 */
.stack-grid { display: grid; gap: 46px; align-items: start; }
@media (min-width: 940px) {
  .stack-grid { grid-template-columns: minmax(0, 1fr) 440px; }
}
@media (min-width: 1240px) {
  .stack-grid { grid-template-columns: minmax(0, 1fr) 500px; gap: 64px; }
}
/* ⚠️ align-self: stretch 是这块 sticky 能不能动的**全部关键**。
   .stack-grid 上写着 align-items: start，右栏于是只有内容那么高 ——
   而 sticky 元素只能在**父级盒子的高度范围内**移动。父级和它一样高时，
   可移动距离是 0：属性生效、计算样式一切正常、就是一动不动跟着滚走了。
   这类失败没有任何报错，只能靠量 getBoundingClientRect().top 才看得出来。 */
.stack-side { position: relative; align-self: stretch; }
.stack-sticky { display: grid; gap: 14px; }
@media (min-width: 940px) {
  /* sticky 在这里不是效果，是论点：你读完那四段的时间里，它一直在旁边跑。 */
  .stack-sticky { position: sticky; top: 92px; }
}
.live-panel {
  border: 1px solid var(--haze); border-radius: var(--r-lg); overflow: hidden;
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--raise) 88%, transparent) 0%,
                            color-mix(in srgb, var(--ink) 94%, transparent) 100%);
  box-shadow: 0 26px 60px -44px rgba(0,0,0,.9);
}
.live-head { padding: 13px 18px; border-bottom: 1px solid var(--hair); }
.live-list { list-style: none; margin: 0; padding: 0; }
.live-row {
  display: grid; grid-template-columns: 6px 66px 1fr auto;
  gap: 11px; align-items: center;
  padding: 11px 18px; border-bottom: 1px solid var(--hair);
  font-family: var(--mono); font-size: .76rem;
}
.live-row:last-child { border-bottom: 0; }
.live-row .mark { width: 6px; height: 6px; border-radius: 50%; background: var(--dust); }
.live-row.a-add .mark     { background: var(--ember); }
.live-row.a-remove .mark  { background: var(--pulse); }
.live-row.a-destroy .mark { background: #FF4757; }
.live-row.a-propose .mark { background: var(--signal); }
.live-row .what { color: var(--mist); }
.live-row .who  { color: var(--paper); }
.live-row .when { color: var(--dust); white-space: nowrap; }
.live-foot {
  display: flex; align-items: center; gap: 6px;
  padding: 12px 18px; border-top: 1px solid var(--hair);
  font-family: var(--mono); font-size: .76rem; color: var(--signal);
}
.live-foot:hover { text-decoration: none; }

/* 四条原则排成 2×2。auto-fit 在 1440 下排成 3 列，第四张落单 ——
   一个「四条规矩」的小节里出现一个孤儿卡片，读起来像少写了一条。 */
.rules { grid-template-columns: 1fr; }
@media (min-width: 760px) { .rules { grid-template-columns: 1fr 1fr; } }
.prodcard-go .ico, .live-foot .ico, .flight-foot a .ico, .picks a .go, .inst-link .ico {
  transition: transform .22s var(--ease);
}
.prodcard:hover .prodcard-go .ico, .live-foot:hover .ico, .flight-foot a:hover .ico, .picks a:hover .go { transform: translateX(5px); }

/* ── 2. 卡片：抬起 + 冷光 ────────────────────────────────────
   光用冷端（青蓝），不用琥珀 —— 琥珀在这套系统里是警示色，
   一个 hover 效果不该看起来像在报警。 */
.card {
  transition: border-color .2s var(--ease), transform .2s var(--ease),
              box-shadow .2s var(--ease), background .2s var(--ease);
}
.card:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 30px -18px rgba(0,0,0,.9),
              0 0 22px -12px color-mix(in srgb, var(--deep) 60%, transparent);
}

/* ── 3. 列表行：底色 + 一点点内缩 ────────────────────────────
   内缩比单纯变底色更能说明「这一行是可以点的」。 */
.live-row { transition: background .16s var(--ease); }
.live-row:has(a), .flight-row { }
.flight-row { transition: background .16s var(--ease), padding-left .16s var(--ease); }
.flight-row:hover { padding-left: 26px; }
.live-panel .live-row:hover { background: color-mix(in srgb, var(--raise) 55%, transparent); }

/* ── 4. 渐变描边淡入 ─────────────────────────────────────────
   只给产品卡这一个大块用。到处都用就不叫强调了。 */
.prodcard { position: relative; isolation: isolate; }
.prodcard::before {
  content: ""; position: absolute; inset: -1px; border-radius: inherit;
  padding: 1px; opacity: 0; transition: opacity .24s var(--ease);
  background: linear-gradient(135deg,
      color-mix(in srgb, var(--signal) 55%, transparent),
      color-mix(in srgb, var(--deep) 45%, transparent) 60%,
      transparent);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  pointer-events: none; z-index: -1;
}
.prodcard:hover::before { opacity: 1; }

/* ── 导航下划线 ──────────────────────────────────────────────
   从中间长出来，而不是整条淡入 —— 后者在深色底上几乎看不出发生了什么。 */
.nav nav a:not(.btn) { position: relative; }
.nav nav a:not(.btn)::after {
  content: ""; position: absolute; left: 50%; right: 50%; bottom: -5px; height: 1px;
  background: var(--signal); opacity: .8;
  transition: left .22s var(--ease), right .22s var(--ease);
}
.nav nav a:not(.btn):hover::after,
.nav nav a[aria-current="page"]::after { left: 0; right: 0; }

/* ── 仪器盘的数字：悬停时提亮 ────────────────────────────────── */
.inst-grid > div { transition: background .2s var(--ease); }
.inst-grid > div:hover { background: var(--slate); }

/* ═══ 滚动显现 ═══════════════════════════════════════════════
   一个可复用的类，挂在**区块**上，不挂在每个元素上 —— 满屏各自为政的
   淡入正是「一眼看出是生成的」那种感觉。区块级的依次出现才像编排。

   ⚠️ 与已有的 lift-in 同一条铁律：默认状态就是终态。动画只在支持
      滚动时间线且用户没要求减少动效时叠加。 */
/* ⚠️ 位移走 `translate` 这个独立属性，**不走 transform**。
   原因是一个静默的冲突：动画的优先级高于普通声明，而 fill-mode: both
   让动画结束后一直按住 `to` 的值。写成 transform 的话，任何同时挂了
   .reveal 和 :hover{transform:...} 的元素，hover 位移会被永久压住 ——
   产品卡的抬起就是这么消失的，而且看不出任何报错。
   translate 与 transform 是两个属性，可以叠加。 */
@keyframes reveal-up {
  from { opacity: 0; translate: 0 20px; }
  to   { opacity: 1; translate: 0 0; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .reveal {
      animation: reveal-up linear both 1ms;
      animation-timeline: view();
      animation-range: entry 4% entry 42%;
    }
    /* 依次出现：同一组里的兄弟错开一点，读起来像有顺序，
       而不是一整块同时"啪"地出现。错开量很小 —— 大了会变成等待。 */
    .reveal-group > * {
      animation: reveal-up linear both 1ms;
      animation-timeline: view();
    }
    .reveal-group > :nth-child(1) { animation-range: entry 2%  entry 38%; }
    .reveal-group > :nth-child(2) { animation-range: entry 6%  entry 42%; }
    .reveal-group > :nth-child(3) { animation-range: entry 10% entry 46%; }
    .reveal-group > :nth-child(4) { animation-range: entry 14% entry 50%; }
    .reveal-group > :nth-child(n+5) { animation-range: entry 18% entry 54%; }
  }
}

/* ── 减少动效：连 hover 的位移也一并去掉 ─────────────────────
   已有的那条 @media 只关掉了 transition/animation 时长，
   位移本身还在 —— 对前庭敏感的人来说，瞬间跳 2px 比慢慢移过去更难受。 */
@media (prefers-reduced-motion: reduce) {
  .card:hover, .prodcard:hover { transform: none; }
  .flight-row:hover { padding-left: 22px; }
.prodcard:hover .prodcard-go .ico, .live-foot:hover .ico, .flight-foot a:hover .ico { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════
   分段叙事的交互层：标签栏 + 换面板
   ═══════════════════════════════════════════════════════════════
   ⚠️ 这一整块都挂在 `.stack[data-js]` 下面。没有这个属性时，
      标签栏不显示、四块面板平铺、什么都不藏 —— 那是默认状态，
      也是脚本挂掉时看到的状态。属性由 stack.js 在宽屏上加。
   ─────────────────────────────────────────────────────────────── */

/* 标签栏。滑块用绝对定位的一条 ink，位置由 JS 从当前标签的
   offsetLeft/offsetWidth 算 —— 四个标签宽度不等，纯 CSS 算不出来。 */
.stack-tabs { display: none; }
.stack[data-js] .stack-tabs {
  display: flex; position: relative; gap: 2px;
  padding: 4px; border-radius: 999px;
  border: 1px solid var(--haze);
  background: color-mix(in srgb, var(--ink) 70%, transparent);
  backdrop-filter: blur(10px);
}
.stack-ink {
  position: absolute; left: 4px; top: 4px; bottom: 4px; width: 0;
  border-radius: 999px; background: var(--raise);
  border: 1px solid var(--haze);
  transition: transform .34s var(--ease), width .34s var(--ease);
}
.stack-tab {
  position: relative; z-index: 1; flex: 1;
  padding: 7px 4px; border: 0; background: none; cursor: pointer;
  font-family: var(--mono); font-size: .7rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--dust);
  border-radius: 999px;
  transition: color .22s var(--ease);
}
.stack-tab:hover { color: var(--mist); }
.stack-tab[aria-current] { color: var(--paper); }
.stack-tab-no[aria-current] { color: var(--ember); }

/* 四块面板叠在同一个格子里 —— 容器高度取最高的那块，
   换面板时高度不跳。用 JS 量高度是同一件事的更脆的做法。 */
.stack-panels { display: grid; gap: 16px; }
.stack[data-js] .stack-panels { gap: 0; }
.stack[data-js] .stack-panel {
  grid-area: 1 / 1;
  opacity: 0; translate: 0 10px;
  transition: opacity .38s var(--ease), translate .38s var(--ease);
  pointer-events: none;
}
.stack[data-js] .stack-panel.on {
  opacity: 1; translate: 0 0; pointer-events: auto;
}
/* ⚠️ 卡片按**自己的内容**定高，不拉伸去对齐最高那块。
   试过拉伸：矮的那几块内部就裂开一道缝（内容在上、说明被顶到底边），
   等于把外面的空洞搬进卡片里，看着更奇怪。容器高度本来就取最高者，
   所以卡片上边缘是钉住的 —— 换面板时动的只有下边缘，配上淡入几乎看不出。 */
.stack[data-js] .stack-panel { display: grid; align-content: start; }
.live-foot.no { color: var(--ember); }

/* 当前那一段亮起来，其余压暗。压暗幅度刻意小 ——
   读者的眼睛已经在那一段上了，这里只需要确认，不需要指路。 */
.stack[data-js] .stack-step {
  transition: opacity .34s var(--ease);
  opacity: .42;
}
.stack[data-js] .stack-step.on { opacity: 1; }
.stack[data-js] .stack-step.on .pipe-dot {
  background: var(--deep); scale: 1.12;
  box-shadow: 0 0 0 4px var(--void), 0 0 22px -1px color-mix(in srgb, var(--deep) 90%, transparent);
}
.stack[data-js] .stack-refuse.on .pipe-dot {
  background: var(--ember);
  box-shadow: 0 0 0 4px var(--void), 0 0 22px -1px color-mix(in srgb, var(--ember) 85%, transparent);
}

/* 每段占住一屏的一大半，好让右边那块有足够的滚动距离换过去。
   只在接管时给 —— 平铺状态下这么高就是四个大空洞。

   ⚠️ 这个值和「每段有多少内容」是同一个问题的两半，改一个必须回头看另一个：
      54vh 时每段内容只有 150–175px，于是 64% 是空的 —— 那就是「看着很简陋」；
      收到 30vh 空白降到 40%，但整个模块从 2446px 掉到 1724px，
      滚动叙事失去了三成行程。两次都是在同一根杠杆上找平衡，
      而那根杠杆其实是错的：真正的毛病是每段的料太少。
      现在每段补了第二段（代价/后果），内容涨到 300px 上下，
      40vh 才既有行程、空白又只剩 17%。**再动这个数之前先量一遍内容高度。** */
@media (min-width: 940px) {
  .stack[data-js] .stack-list { gap: 0; }
  .stack[data-js] .stack-step { min-height: 40vh; }
  /* ⚠️ 这段尾巴不是留白，它是 sticky 的**可移动距离**。
     sticky 在「父级盒子底边 - 自身高度 < top」的那一刻脱开 ——
     尾巴不够长时，第四段还在屏幕中间被读着，右边那块已经跟着往上滚了：
     标签栏被顶栏切掉一半，看起来像坏了。

     需要多长是能算的。设视口高 H、面板连标签栏高 P(≈520)、吸顶 top=92：
       第四段可读区间的起点  S_min = 0.30H − 文字高
       要求 S_min + 0.30H + 尾巴 ≥ 92 + P
     H=900 时解出尾巴 ≥ 291px。段落从 54vh 收到 30vh 之后原来的 22vh
     （198px）就不够了 —— 测试里那条「读者在读、面板已被切掉」抓到的
     正是这个。取 340px 留一点余量。 */
  .stack[data-js] .stack-list { padding-bottom: max(340px, 30vh); }
  /* 那条尾巴是 sticky 的行程，不是内容 —— 时间线不该画进去。
     不减掉的话，最后一步下面会拖着一条通向空白的线，看着像没写完。 */
  .stack[data-js] .pipe-track,
  .stack[data-js] .pipe-fill { bottom: calc(max(340px, 30vh) + 10px); }
}

/* 每段自带的刻度。样式跟产品卡上的事实条同源 —— 同一种东西
   （可核对的小事实）在站上只该有一种长相。 */
.step-facts {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 0;
}
.step-facts span {
  font-family: var(--mono); font-size: .68rem; letter-spacing: .05em;
  color: var(--mist); padding: 5px 10px;
  border: 1px solid var(--haze); border-radius: 999px;
  white-space: nowrap;
}
.step-facts.no span {
  color: color-mix(in srgb, var(--ember) 72%, var(--paper));
  border-color: color-mix(in srgb, var(--ember) 32%, transparent);
}

.stack-note {
  margin: 0; padding: 13px 18px; border-top: 1px solid var(--hair);
  font-size: .8rem; line-height: 1.55; color: var(--dust);
}

/* ── 面板 2：一个模块，三个调用点 ────────────────────────────── */
.fan { padding: 18px 18px 6px; }
.fan-k {
  /* ⚠️ margin 不是留白偏好：标签和紧跟的 <code> 之间没有任何文本节点，
     min-width 又拦不住 —— MODULE / 模块 在这个字号下都比 46px 宽，于是
     渲染成 MODULEprojections/risk_card.py。中英文之间要有空格（中文文案
     排版指北），英文两个词之间同样不能黏在一起。 */
  display: inline-block; min-width: 46px; margin-right: 10px;
  font-family: var(--mono); font-size: .62rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--dust);
}
.fan-src { margin: 0 0 4px; }
.fan-src code {
  font-family: var(--mono); font-size: .82rem; color: var(--signal);
}
.fan-out { list-style: none; margin: 0; padding: 0 0 0 22px; position: relative; }
/* 一条从模块名垂下来的枝干 —— 这不是装饰，它就是「一个来源，三个出口」
   这句话本身。三条线各自平着接出去。 */
.fan-out::before {
  content: ""; position: absolute; left: 8px; top: 0; bottom: 15px;
  width: 1px; background: var(--haze);
}
.fan-out li {
  position: relative; padding: 7px 0;
  display: flex; align-items: baseline; gap: 4px;
}
.fan-out li::before {
  content: ""; position: absolute; left: -14px; top: 17px;
  width: 12px; height: 1px; background: var(--haze);
}
.fan-out code {
  font-family: var(--mono); font-size: .78rem; color: var(--paper);
  word-break: break-all;
}

/* ── 面板 3：机器人真发的那条消息 ───────────────────────────── */
.tg { padding: 18px; }
.tg-bubble {
  white-space: pre-wrap; word-break: break-word;
  font-family: var(--body); font-size: .82rem; line-height: 1.6;
  color: var(--paper);
  padding: 14px 16px;
  border-radius: 14px 14px 14px 4px;   /* 左下角收一个尖：这是收到的一方 */
  background: color-mix(in srgb, var(--slate) 90%, transparent);
  border: 1px solid var(--haze);
}
.tg-bubble b { font-weight: 600; }
.tg-bubble code {
  font-family: var(--mono); font-size: .78rem;
  color: var(--signal); word-break: break-all;
}
/* 会被账本填上的位置。用虚线下划线而不是底色块 ——
   底色块会让人以为那是可以点的。 */
.tg-slot {
  color: var(--deep); filter: brightness(1.7);
  border-bottom: 1px dashed color-mix(in srgb, var(--deep) 60%, transparent);
}

/* ── 面板 4：我们不要什么 ───────────────────────────────────── */
.panel-no { border-color: color-mix(in srgb, var(--ember) 24%, var(--haze)); }
.never { list-style: none; margin: 0; padding: 6px 18px 2px; }
.never li {
  padding: 8px 0 8px 26px; position: relative;
  font-size: .88rem; color: var(--mist);
  text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--ember) 70%, transparent);
  text-decoration-thickness: 1px;
}
/* 划掉之外再画一个叉：只靠删除线的话，颜色一淡就读成普通列表了 */
.never li::before {
  content: ""; position: absolute; left: 4px; top: 50%; width: 11px; height: 11px;
  translate: 0 -50%;
  background:
    linear-gradient(45deg, transparent 44%, var(--ember) 44%, var(--ember) 56%, transparent 56%),
    linear-gradient(-45deg, transparent 44%, var(--ember) 44%, var(--ember) 56%, transparent 56%);
  opacity: .8;
}
.never-yes {
  margin: 0; padding: 14px 18px;
  border-top: 1px solid var(--hair);
  font-size: .88rem; color: var(--paper);
  display: flex; align-items: baseline; gap: 4px;
}

/* ── 数字上滚时的状态 ────────────────────────────────────────
   滚动中偏灰，落定时才是纸白 —— 「定格」这件事得看得见，
   否则数字只是在动，读不出它已经停了。 */
.is-counting { color: var(--mist) !important; }
.is-settled { animation: settle .5s var(--ease); }
@keyframes settle {
  0%   { text-shadow: 0 0 14px color-mix(in srgb, var(--signal) 45%, transparent); }
  100% { text-shadow: none; }
}

@media (prefers-reduced-motion: reduce) {
  .stack[data-js] .stack-panel { transition: none; }
  .stack[data-js] .stack-step { opacity: 1; }
  .is-settled { animation: none; }
}

/* ── 语言选择器 ──────────────────────────────────────────────
   一个地球图标，点开是菜单。用 <details> 撑起来：没有 JS 时它仍然是
   一个能展开、能用键盘操作的控件。JS 只负责「点外面关掉」和 Esc
   这两件锦上添花的事（见 search.js 末尾）。 */
.langpick { position: relative; }
.langpick summary {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 999px;
  color: var(--mist);
  transition: color .18s var(--ease), background .18s var(--ease);
}
.langpick summary::-webkit-details-marker { display: none; }
.langpick summary:hover { color: var(--paper); background: var(--slate); }
.langpick[open] summary { color: var(--paper); background: var(--raise); }

.langpick-menu {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 40;
  min-width: 176px; padding: 6px;
  border: 1px solid var(--haze); border-radius: var(--r);
  background: color-mix(in srgb, var(--ink) 96%, transparent);
  backdrop-filter: blur(14px);
  box-shadow: 0 20px 44px -26px rgba(0,0,0,.95);
  display: grid; gap: 2px;
}
.langpick-menu > a, .langpick-menu > b {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 11px; border-radius: var(--r-sm);
  font-size: .88rem; font-weight: 400; color: var(--mist);
  white-space: nowrap;
  transition: color .16s var(--ease), background .16s var(--ease);
}
.langpick-menu > a:hover { color: var(--paper); background: var(--slate); text-decoration: none; }
/* 当前语言不是链接：点一个已经生效的选项没有反馈，
   用户会以为没生效又点一次。用对勾说明它已经是了。 */
.langpick-menu > b { color: var(--paper); background: var(--raise); }
/* ⚠️ 对勾**不用琥珀**。琥珀在这套系统里是警示色，而且是留给 UCanary 的
   品牌色（公司站的强调色是青蓝）—— 一个「已选中」的对勾既不该看起来像
   在报警，也不该在公司站上冒出产品的品牌色。用纸白，安静且两边都成立。 */
.langpick-menu .tick { color: var(--paper); }

/* 窄屏不隐藏 —— 一个图标的宽度总是放得下，而手机上恰恰更需要它：
   系统语言和阅读语言不一致的人，多半就在手机上。 */
@media (max-width: 560px) {
  .langpick summary { width: 28px; height: 28px; }
  .langpick-menu { right: -8px; }
}

@media (prefers-reduced-motion: reduce) {
  .langpick summary, .langpick-menu > a { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════
   中文排版修正
   ═══════════════════════════════════════════════════════════════
   全部挂在 [lang="zh"] 下 —— 英文页面的计算样式一个像素都不变。

   要修的是同一类错误：**这套排版的参数是为拉丁字体调的**，
   直接套在汉字上就会坏。三处最明显：

     1. 标题的负字距（-.028em）。收紧字距是拉丁字体的手法，字母之间
        本来就有空隙可以收。汉字是等宽方块，本来就贴着，再收就是挤在
        一起 —— 68px 的标题上收掉 1.93px，肉眼直接看得出来。
     2. 小标签的 uppercase + .16em 字距。大写对汉字无效（白写），
        而 .16em 的字距会把「正在读」拆成三个互不相干的字。
     3. 行高 1.6。拉丁字有升部降部撑开视觉行距，汉字是满格方块，
        同样的行高看起来就是挤的。中文正文要 1.75 上下。
   ─────────────────────────────────────────────────────────────── */

/* ── 1. 标题：字距归零 ─────────────────────────────────────── */
[lang="zh"] h1, [lang="zh"] h2, [lang="zh"] h3, [lang="zh"] h4,
[lang="zh"] .page-h1, [lang="zh"] .sec {
  letter-spacing: 0;
  /* 汉字没有升部降部，同样的行高比拉丁看着紧 —— 大标题尤其明显 */
  line-height: 1.28;
}
[lang="zh"] .hero h1, [lang="zh"] .lab-hero h1 { line-height: 1.22; }

/* ── 2. 正文：行高与字间微调 ───────────────────────────────── */
[lang="zh"] body { line-height: 1.75; }
[lang="zh"] .lede, [lang="zh"] .sub, [lang="zh"] p { line-height: 1.8; }
/* 中英混排时，汉字与拉丁之间该有一点空气。text-autospace 是浏览器
   自动做这件事的原生方案；不支持的地方就维持现状，不做降级模拟 ——
   手写 <span> 插空格是维护灾难，而且会污染复制出来的文本。 */
@supports (text-autospace: normal) {
  [lang="zh"] body { text-autospace: normal; }
}
/* 标点挤压：中文标点自带的空白在行首行尾会显得空。原生支持时收掉。 */
@supports (text-spacing-trim: trim-start) {
  [lang="zh"] body { text-spacing-trim: trim-start; }
}

/* ── 3. 等宽小标签：去掉为拉丁准备的宽字距 ────────────────────
   ⚠️ 只改**含中文的**那些。纯拉丁的标签（PROPOSAL、FREEZE、
      LIVE·2019 DAYS）保持原样 —— 那个宽字距正是它们的样子。
      靠 :lang() 分不出来，所以按类名逐个列：这些是文案位，
      内容随语言变；数据位（地址、哈希、时间戳）永远是拉丁。 */
[lang="zh"] .status,
[lang="zh"] .inst-grid dt,
[lang="zh"] .ruler-top .k,
[lang="zh"] .stack-tab,
[lang="zh"] .step-facts span,
[lang="zh"] .fan-k,
[lang="zh"] .live-row .what {
  letter-spacing: .04em;
}
/* 汉字没有大小写，uppercase 是空转；但它会让浏览器多做一次
   文本变换，也会让「9」这类字符参与不必要的重排。显式关掉。 */
[lang="zh"] .status, [lang="zh"] .inst-grid dt,
[lang="zh"] .ruler-top .k, [lang="zh"] .stack-tab { text-transform: none; }

/* ── 4. 行宽 ────────────────────────────────────────────────
   ch 是「0」的宽度，一个汉字约等于两个 ch。所以同一个 max-width
   在中文下只有一半的字数 —— 46ch ≈ 23 个汉字，一行没说完就断了。
   中文舒适行宽是 30–40 字，折算回来要放宽。 */
[lang="zh"] .hero .lede { max-width: 34em; }
[lang="zh"] .stack-step p { max-width: 30em; }
[lang="zh"] .sub { max-width: 36em; }

/* ── 5. 数字与代码保持拉丁 ──────────────────────────────────
   账本数字、地址、哈希、合约名在任何语言下都该是等宽拉丁 ——
   它们是**可核对的事实**，换一种字形就不像可以拿去比对的东西了。
   这条不是排版偏好，是这套设计从第一天起的论点。 */
[lang="zh"] .addr, [lang="zh"] .mono, [lang="zh"] code,
[lang="zh"] .inst-grid dd, [lang="zh"] .ruler-top .big,
[lang="zh"] .gap-big, [lang="zh"] table.data td.num,
/* .when 是后来才变成混排的：中文相对时间是「1 天 13 小时前」，两个
   等宽空格摆在 13px 字号上就是两个洞。上面那段注释说「时间戳永远是
   拉丁」，加了中文单位之后这句话就不成立了。 */
[lang="zh"] .when {
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: normal;
  /* ⚠️ 「45.4 分」这类数字＋中文单位的串自带一个空格（同一个字符串也要
     发进 Telegram，那边没有 CSS 可以补间距）。而等宽字体的空格是**一整个
     字身宽** —— 实测 57.6px 字号下英文里是 10px、中文这里是 35px，
     三倍半。收回去，让它看起来还是一个空格而不是一个洞。 */
  word-spacing: -.42em;
}

/* 同一个洞，另外一批位置。上面那条规则连 font-family 和 letter-spacing
   一起设，所以不能把这些塞进去 —— .ruler-top .k / .step-facts span /
   .live-row .what 在前面被特意给了 letter-spacing:.04em，并进去会被
   letter-spacing:normal 顶掉。这条只管 word-spacing，别的一律不碰。
   ⚠️ 之所以还是逐个列而不是给 [lang="zh"] 一刀切：比例字体的空格本来
   就只有 .2em 左右，再收 .42em 会变成负的。CSS 选不出「用了等宽字体」，
   所以只能列。漏了新组件由 test_web_visual.py 的等宽空格宽度检查兜。 */
[lang="zh"] .note,
[lang="zh"] .gap-sub,
[lang="zh"] .dist-k,
[lang="zh"] .bar-row .lbl,
[lang="zh"] .ruler-top .k,
[lang="zh"] .step-facts span,
[lang="zh"] .live-row .what,
[lang="zh"] .live-foot span,
[lang="zh"] .flight-foot span,
[lang="zh"] .prodcard-facts span,
[lang="zh"] .scale-tick span,
[lang="zh"] .scale-median span,
[lang="zh"] .step .idx,
[lang="zh"] :is(table.kv, table.tally, table.data) :is(th, td.fact) {
  word-spacing: -.42em;
}

/* iOS Safari 会在聚焦字号小于 16px 的输入框时**放大整个页面**，而且松开
   焦点不会缩回去 —— 用户从此在一个横向能滑动的页面上继续操作。这不是排版
   偏好，是移动端的硬规矩：触屏设备上一律 16px。
   （用 pointer: coarse 而不是宽度：带触屏的大平板同样会缩放，而窄窗口的
   桌面浏览器不会。）
   页头那个地址输入框本来就是 16px，改的是命令面板和批量筛查这两处。 */
@media (pointer: coarse) {
  /* ⚠️ 得带上 .screen-form：媒体查询不加特指度，裸 textarea（0,0,1）压不过
     .screen-form textarea（0,1,1）。第一版就是这么漏掉批量筛查那一个的。 */
  /* ⚠️ 这一段必须留在文件末尾。等特指度下后写的赢，放在 .screen-form
     textarea（0,1,1）前面的话，它照样是 13.4px —— 上一版就是这样。 */
  input[type="text"], input[type="search"], textarea,
  .screen-form textarea, .q-field input { font-size: 16px; }
