/* ═══ 基础：重置 · 外壳 · 排版 ═══
   色值一律取令牌（vendor/tokens.css）。这一份里出现任何 #hex 都是 bug，
   tools/selfcheck.py 会查出来。
   带 /* GAP */ 注释的数值是令牌里没有、被迫写死的 —— 与 build/verify/signal-feed.html
   记的是同一批缺口，改令牌时一并回收。 */

*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  background: var(--c-neutral-0);
  color: var(--c-neutral-900);
  /* 令牌里的字体栈已含 IBM Plex → 系统中英文回退。
     ⚠️ 绝不从 fonts.googleapis.com 之类的第三方拉字体：那等于每个用户每次打开
     都把 IP 和访问路径发给一个没有写进六项告知的境外接收方（plan 第 20 章）。
     要用 IBM Plex 就把 woff2 自托管进 app/web/fonts/ 并在这里加 @font-face。 */
  font-family: var(--font-sans);
  font-size: var(--size-md);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}
:root { color-scheme: light dark; }

/* 数字一律等宽 + 定宽字形：列表每分钟刷新，数字宽度一变整行就抖。 */
.mono, .n, b.n, .num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
b.n { font-weight: 600; color: var(--c-provenance-measured); font-family: var(--font-mono);
      font-variant-numeric: tabular-nums; }

a { color: inherit; }
a.link { color: var(--c-accent-text); text-decoration: none; border-bottom: 1px solid transparent; }
a.link:hover { border-bottom-color: currentColor; }
a.link.sm { font-size: var(--size-sm); }
button { font-family: inherit; }

:focus-visible {
  outline: var(--a11y-floor-focus-ring) solid var(--a11y-floor-focus-color);
  outline-offset: 1px;                       /* 外扩不内缩：内缩会盖住组件自己的边框 */
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── 外壳 ── */
.shell { display: grid; grid-template-columns: 216px 1fr; height: 100%; }   /* GAP sidebar-w */
@media (max-width: 900px) { .shell { grid-template-columns: 64px 1fr; } .sb .label, .sb .gt { display: none; } }

.sb {
  display: flex; flex-direction: column; height: 100%; overflow: hidden;
  padding: 14px var(--space-2) 0;                                          /* GAP sidebar-pad-top */
  background: var(--c-neutral-25); border-right: 1px solid var(--c-neutral-100);
}
.mark {
  padding: var(--space-1) var(--space-3) var(--space-4); font-size: 18px;   /* GAP wordmark-18 */
  letter-spacing: -.03em; line-height: var(--leading-tight); white-space: nowrap;
  color: var(--c-neutral-900); text-decoration: none; display: block;
}
.mark b { font-weight: 600; }
.mark .sp { display: inline-block; width: 1.8px; }
.mark .spy { font-weight: 500; color: var(--c-neutral-500); }
.sb-scroll { flex: 1; min-height: 0; overflow-y: auto; }
.sb-scroll > div { display: flex; flex-direction: column; gap: 2px; }       /* GAP nav-gap-2 */
.gt {
  font-size: var(--size-11); color: var(--c-neutral-500); padding: 0 var(--space-3);
  margin: 18px 0 var(--space-1); letter-spacing: .04em;                     /* GAP gt-mt-18 */
}
.gt:first-child { margin-top: 0; }

/* 侧栏项：高 36 · 左右内缩 6 · 选中只靠填充深度，不靠彩色、不借左侧竖条
   （竖条是严重度的语言，借来当选中态会让两套语义混掉）。 */
.nav {
  display: flex; align-items: center; gap: var(--space-3); height: var(--control-h-lg);
  padding: 0 var(--space-3); border-radius: var(--radius-md); font-size: var(--size-md);
  color: var(--c-neutral-600); text-decoration: none; flex-shrink: 0; cursor: pointer;
  border: 0; background: none; font-family: inherit; width: 100%; text-align: left;
}
.nav:hover { background: var(--c-neutral-50); }
.nav.on { background: var(--c-neutral-100); color: var(--c-neutral-900); font-weight: 500; }
.nav:focus-visible { outline-offset: -1px; }   /* 侧栏项左右已贴容器，这里内缩 */
.nav svg { flex-shrink: 0; width: var(--control-icon-md); height: var(--control-icon-md); }
.nav .n {
  margin-left: auto; font-family: var(--font-mono); font-size: var(--size-sm);
  color: var(--c-neutral-500); font-variant-numeric: tabular-nums;
}
.nav.on .n { color: var(--c-neutral-900); }
/* 更新日志的红点：它不是一个数，是「有口径改动，去看一眼」。
   用强调色不用严重度红 —— 严重度红是「多急」那一套的最高档，
   一条更新日志够不上，借了就把红的额度用掉了。 */
.nav .n.dot { font-size: 0; width: 6px; height: 6px; border-radius: 50%;
              background: var(--c-accent-base); margin-left: auto; }
.sb-foot {
  flex-shrink: 0; border-top: 1px solid var(--c-neutral-100);
  margin: var(--space-3) var(--space-3) 10px; padding-top: var(--space-2);   /* GAP foot-mb-10 */
}
.sb-foot .nav { height: 30px; font-size: var(--size-base); color: var(--c-neutral-500); padding: 0; }  /* GAP foot-h-30 */

/* ── 内容区 ── */
.main { display: flex; flex-direction: column; height: 100%; overflow: hidden;
        position: relative; background: var(--c-neutral-0); }
.top {
  height: 46px; flex-shrink: 0; display: flex; align-items: center; gap: 11px;  /* GAP top-h-46 */
  padding: 0 18px; border-bottom: 1px solid var(--c-neutral-100);               /* GAP pad-18 */
}
.top .ttl { font-size: var(--size-lg); font-weight: 600; letter-spacing: -.02em; }

/* 心跳：一次呼吸 = 一次「它还活着」。这是承担信息的动效，不是装饰，所以留。 */
.beat { display: inline-flex; align-items: center; gap: var(--space-2);
        font-size: var(--size-base); color: var(--c-neutral-600); }
.beat svg { width: 9px; height: 9px; color: var(--c-severity-mid);
            animation: beat var(--motion-duration-heartbeat) var(--motion-easing-loop) infinite; }
@keyframes beat { 0%, 100% { opacity: .34 } 50% { opacity: 1 } }
.tick { font-size: var(--size-sm); color: var(--c-neutral-500); font-family: var(--font-mono); }
.tick.warn { color: var(--c-severity-high); }

.page { flex: 1; min-height: 0; overflow-y: auto; padding: 14px 18px 18px; }
.page.flush { padding: 0; }
.rowline { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.spacer { margin-left: auto; }

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -.02em; }
h1 { font-size: var(--size-xl); }
h2 { font-size: var(--size-lg); }
h3 { font-size: var(--size-md); }
p  { margin: 0 0 var(--space-3); }
.muted { color: var(--c-neutral-500); }
.sm { font-size: var(--size-sm); }
.xs { font-size: var(--size-xs); }

/* ── 断采横幅 ──
   sticky 是红线不是美观：横幅一滚走，页面上的时间就重新变成假的「现在」。 */
.stall-banner {
  position: sticky; top: 0; z-index: 60; flex-shrink: 0; padding: 9px 18px;
  background: var(--c-severity-high); color: var(--c-neutral-0); font-size: var(--size-base);
}

/* ── 数据来源标（X-ShopSpy-Source）──
   fixture 时常驻可见：前端和后端都能一眼看出这批数是不是编的。 */
.srcmark {
  font-family: var(--font-mono); font-size: var(--size-xs); padding: 1px 6px;
  border-radius: var(--radius-sm); border: 1px dashed var(--border-control); color: var(--c-neutral-600);
}

@media (prefers-reduced-motion: reduce) {
  .beat svg { animation: none; opacity: 1; }   /* 心跳降为静止满亮，不是消失 */
  *, *::before, *::after { transition: none !important; animation: none !important; }
}
/* stub 比 fixture 危险得多：数字假 vs 动作假。所以它不能和「样本数据」长一样。
   用实线框 + 墨色字（异常那一套的「有形无色」），不用红 ——
   红是「多急」那一套的最高档，而这是一个开发期状态，不是用户的急事。 */
.srcmark-stub { border-style: solid; border-color: var(--border-strong);
                color: var(--c-neutral-900); font-weight: 500; }
