/* ════════════════════════════════════════════════════════════════════
   L2 component filter  (波长 / 光高 / 口径 / 功率)
   ────────────────────────────────────────────────────────────────────
   Glass bar between the dock title and the push-stack. Collapsed it is a
   single funnel toggle; expanded it slides down a drawer of dual-range
   sliders. Monochrome ink + one accent (the fill track), matching the
   scientific library language (DESIGN.md §8).
   ════════════════════════════════════════════════════════════════════ */

.lib-filter {
  flex: 0 0 auto;
  /* v2.7.12: rounded card on a slightly stronger surface — distinguishes
     the filter from the rest of the left dock (#library is transparent
     glass) and clusters the chip row + drawer into a single visual unit. */
  margin: 6px 8px 8px;
  padding: 4px;
  background: var(--surface-2);
  border: 1px solid var(--line, var(--line-1));
  border-radius: 8px;
  font-family: var(--font-ui);
}
.lib-filter[hidden] { display: none; }

/* ── "Show draggable only" toggle (D-3) ──────────────────────────────
   Sits between the filter drawer and the lib-stack so it's visible at every
   navigation depth. Single-row, monochrome, matches the scientific library
   language; the checkbox itself stays native for accessibility.            */
.lib-draggable-toggle {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  padding: 6px 12px;
  border-bottom: 1px solid var(--line);
  background: transparent;
  font-family: var(--font-ui);
}
.lib-draggable-toggle[hidden] { display: none; }
.lib-draggable-toggle-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  font-size: 11px;
  color: var(--ink-2);
  letter-spacing: 0.02em;
}
.lib-draggable-toggle-label input[type="checkbox"] {
  margin: 0;
  cursor: pointer;
}
.lib-draggable-toggle-text {
  user-select: none;
}

.lf-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 10px;
}

.lf-toggle {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 9px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 4px;
  cursor: pointer;
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--ink);
  transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
}
.lf-toggle:hover { background: var(--surface-2); border-color: var(--ink-1); }
.lf-toggle.is-active {
  border-color: var(--ink-1);
  box-shadow: inset 0 0 0 1px var(--ink-1);
}
.lf-funnel { display: inline-flex; color: var(--ink-2); }
.lf-toggle.is-active .lf-funnel { color: var(--ink-1); }
.lf-toggle-label { flex: 1 1 auto; text-align: left; }

.lf-badge {
  flex: 0 0 auto;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--ink-1);
  color: var(--surface);
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-variant-numeric: tabular-nums;
}

.lf-caret {
  flex: 0 0 auto;
  display: inline-flex;
  color: var(--ink-3);
  transition: transform 0.24s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.lf-caret[data-open="true"] { transform: rotate(90deg); }

.lf-clear {
  flex: 0 0 auto;
  padding: 6px 9px;
  background: none;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  transition: color 0.14s, background 0.14s, border-color 0.14s;
}
.lf-clear:hover { color: var(--sig-error); background: var(--sig-error-bg); }

/* ── Active-filter chips ──────────────────────────────────────────── */
.lf-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  padding: 8px 10px;   /* UX-4：上方 lf-head 行已移除，chips 自带上下留白 */
}
/* UX-4：Clear 移入 chips 行尾。 */
.lf-chips .lf-clear { margin-left: auto; }
.lf-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 4px 2px 7px;
  background: var(--surface-3);
  border: 1px solid var(--line);
  border-radius: 10px;
  font-size: 10px;
}
.lf-chip-k { color: var(--ink-2); font-weight: 600; letter-spacing: 0.02em; }
.lf-chip-v { font-family: var(--font-mono); color: var(--ink); font-variant-numeric: tabular-nums; }
.lf-chip-u { color: var(--ink-3); margin-left: 1px; }
.lf-chip-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.lf-chip-x:hover { background: var(--overlay-2); color: var(--ink-1); }

/* ── Drawer ───────────────────────────────────────────────────────── */
.lf-drawer {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.28s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.lf-drawer[data-open="true"] { grid-template-rows: 1fr; }
.lf-drawer-inner {
  overflow: hidden;
  min-height: 0;
}
.lf-drawer[data-open="true"] .lf-drawer-inner { padding: 6px 10px 8px; }

/* ── Compact single-line filter row (v2.7.09) ─────────────────────────
   v2.7.08 was a 3-col grid with a dual-range slider, but at the 248px
   sidebar width the slider rail was ~50px wide → unusable. v2.7.09 uses
   two compact number inputs (min/max) on a single ~26px line; the
   placeholders show the adaptive [min, max] of the current L1 scope so
   the user sees the data range without dragging. */
.lf-dim {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 0;
}
.lf-dim + .lf-dim { margin-top: 2px; }

.lf-dim-label {
  flex: 0 0 auto;
  min-width: 70px;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: 0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lf-dim-sub {
  margin-left: 4px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 400;
  letter-spacing: 0.03em;
  color: var(--ink-4);
  text-transform: uppercase;
}

.lf-num-pair {
  flex: 1 1 auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}
.lf-num {
  flex: 1 1 0;
  min-width: 0;       /* let the field shrink inside the flex row */
  width: 100%;
  height: 22px;
  padding: 0 5px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-1);
  background: var(--surface, var(--surface-1));
  border: 1px solid var(--line, var(--line-1));
  border-radius: 3px;
  text-align: right;
  appearance: textfield;
  -moz-appearance: textfield;
  transition: border-color 80ms ease, background-color 80ms ease, box-shadow 80ms ease;
}
.lf-num::-webkit-outer-spin-button,
.lf-num::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.lf-num::placeholder { color: var(--ink-4); font-weight: 400; }
.lf-num:hover { border-color: var(--hover-border); background: var(--hover-bg); }
.lf-num:focus {
  outline: none;
  border-color: var(--active-border);
  background: var(--surface, var(--surface-1));
  box-shadow: 0 0 0 2px var(--overlay-2);
}
/* Active dim: solid ink edge + filled cell, mirroring .is-active in topbar
   menu — uniform "this is narrowed" affordance across the app. */
.lf-dim.is-active .lf-num { border-color: var(--active-border); color: var(--ink-1); font-weight: 500; }
.lf-num-dash {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-4);
}
.lf-num-u {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-3);
  margin-left: 2px;
  min-width: 22px;
}
/* Slider styles removed in v2.7.09 — the dual-range slider was unusable at
   the narrow sidebar width; replaced with two number inputs (.lf-num*). */

/* ── Drawer footer (live result count) ────────────────────────────── */
.lf-foot {
  margin-top: 6px;
  padding-top: 5px;
  border-top: 1px solid var(--line-soft);
  display: flex;
  align-items: center;
  min-height: 18px;
}
.lf-result {
  font-size: 10px;
  color: var(--ink-3);
  letter-spacing: 0.01em;
}
.lf-result-n {
  font-family: var(--font-mono);
  font-weight: 600;
  color: var(--ink-1);
  font-variant-numeric: tabular-nums;
}
.lf-result-nd { color: var(--sig-warn); }

/* ── Filter empty state (no matches in a leaf/group) ──────────────── */
.lib-filter-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 6px;
  padding: 34px 22px;
  color: var(--ink-3);
}
.lib-filter-empty-icon { color: var(--ink-4); margin-bottom: 2px; }
.lib-filter-empty-title { font-size: 12px; font-weight: 600; color: var(--ink-2); }
.lib-filter-empty-sub { font-size: 10.5px; line-height: 1.5; max-width: 22ch; }
.lib-filter-empty-clear {
  margin-top: 6px;
  padding: 6px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 4px;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--ink-2);
  transition: background 0.14s, border-color 0.14s, color 0.14s;
}
.lib-filter-empty-clear:hover {
  background: var(--overlay-1);
  border-color: var(--ink-2);
  color: var(--ink-1);
}

/* BOM button — subtraction (2026-06-15)：icon + 实时合计两段，去标签/行数/蓝渐变。
   扁平单色（DESIGN.md §3.5：禁渐变禁蓝），icon 左、金额右。 */
.bom-btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  /* v2 affordance：BOM 主 CTA 保留 --surface 实底（primary 动作不弱化），边并入
     控件座系统 --ctl-line（≥2:1，替代更淡的 --line）—— 玻璃 dock 上静止即清晰（U10/U11）。 */
  background: var(--surface);
  border: 1px solid var(--ctl-line);
  border-radius: 5px;
  cursor: pointer;
  font-family: inherit;
  color: var(--ink);
  outline: none;
  transition: background 0.16s var(--ease-out),
              border-color 0.16s var(--ease-out),
              box-shadow 0.16s var(--ease-out),
              transform 0.16s var(--ease-out);
  box-shadow: 0 1px 2px rgba(20, 24, 31, 0.04);
}

.bom-btn:hover {
  border-color: var(--ink-1);
  background: var(--overlay-1);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05),
              0 6px 14px -4px rgba(0, 0, 0, 0.18);
  transform: translateY(-1px);
}
.bom-btn:focus-visible {
  border-color: var(--ink-1);
  box-shadow: 0 0 0 3px var(--overlay-2);
}

.bom-btn-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-1);   /* 单色描边 icon，去蓝色方块（DESIGN.md §3.5） */
  flex: 0 0 auto;
}

.bom-btn-total {
  /* Running BOM total — a price, so Geist Mono (§4.1). Was italic 22px
     Geist (Instrument-Serif hangover). Now upright mono 16px. */
  font-family: var(--font-mono);
  font-size: 16px;
  font-weight: 500;
  color: var(--ink-1);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

/* ─── Modal backdrop & sheet ──────────────────────────────────────── */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 2000;   /* above floating panels (1000-1099) — was 200, which let pinned panels cover the BOM / DB-status modals (DESIGN.md §8.4.A z-table) */
  /* Neutral scrim (DESIGN.md §8.7 → --overlay-3, was a hardcoded navy
     rgba(14,22,36) that tinted the whole screen cold-blue). One full-screen
     backdrop blur is fine for a transient modal; not animated per-frame. */
  background: var(--overlay-3);
  backdrop-filter: blur(8px) saturate(140%);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  display: flex;
  justify-content: flex-end;
  animation: backdrop-fade var(--dur-enter) var(--ease-out);
}

/* CRITICAL: explicit `display: flex` above overrides UA `[hidden] {display:none}`.
   Re-assert hidden state with higher specificity (`.cls[attr]` > `.cls`). */
.modal-backdrop[hidden] { display: none; }

@keyframes backdrop-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* UX-1 exit — reverse fade on the scrim + slide-back on the sheet.
   ui/motion.js leaveThenHide() adds .is-leaving and hides on animationend. */
.modal-backdrop.is-leaving {
  animation: backdrop-fade var(--dur-exit) var(--ease-out) reverse forwards;
  pointer-events: none;
}
.modal-backdrop.is-leaving .modal-sheet {
  animation: sheet-out var(--dur-exit) var(--ease-out) forwards;
}
@keyframes sheet-out {
  from { transform: translateX(0);    opacity: 1; }
  to   { transform: translateX(24px); opacity: 0; }
}

body.modal-open { overflow: hidden; }

.modal-sheet {
  width: min(680px, 92vw);
  height: 100vh;
  background: var(--surface);
  border-left: 1px solid var(--line);
  box-shadow: -20px 0 60px -10px rgba(14, 22, 36, 0.32);
  display: flex;
  flex-direction: column;
  /* UX-1: was 320ms (0.22,0.61,0.36,1) translateX(20px) from opacity .6 —
     read as sluggish + half-faded. Now full fade + a touch more travel on
     the modal token. (DESIGN.md §15.1 #4 adapted: this sheet is a right-
     side drawer, so the slide stays on the X axis, not translateY.) */
  animation: sheet-in var(--dur-modal) var(--ease-out);
  position: relative;
}

@keyframes sheet-in {
  from { transform: translateX(24px); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}

.modal-header {
  flex: 0 0 auto;
  padding: 28px 32px 24px;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  background:
    radial-gradient(ellipse at top right,
      var(--surface-2) 0%,
      var(--surface) 60%);
}

.modal-eyebrow {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--ink-1);
  margin-bottom: 4px;
}

.modal-title {
  font-family: var(--font-ui);
  /* DESIGN.md §8.7: modal title = 20 / 600. Was 38px/400 — a hero heading
     that ate the top of a 680px drawer and broke §4.2 (28px cap) + P3
     (density). Brought to spec. */
  font-size: 20px;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-bottom: 8px;
}

.modal-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--ink-2);
  letter-spacing: 0.04em;
}

.modal-meta-sep {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--ink-3);
}

/* ── BOM 抬头报错折叠区（user 2026-07-09）──────────────────────────────────
   替换旧 quote-no / line-count meta。折叠条 = 严重度计数 chip；展开体复用
   .alert-summary-* 段样式（顶栏告警面板同源 → 设计一处改两处齐）。 */
.bom-alerts {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
  max-width: 100%;
}

.bom-alerts-toggle {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 3px 8px 3px 5px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--ink-2);
  cursor: pointer;
  font-family: var(--font-ui);
  text-align: left;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}
.bom-alerts-toggle:hover:not(:disabled) { background: var(--overlay-1, var(--surface-2)); }
.bom-alerts-toggle:focus-visible {
  outline: none;
  border-color: var(--line-1, var(--line));
  box-shadow: 0 0 0 2px var(--overlay-2, var(--surface-3));
}
.bom-alerts-toggle:disabled { cursor: default; }

/* Chevron: points right when collapsed, rotates down when expanded. Hidden in
   the clear state (nothing to expand). */
.bom-alerts-caret {
  display: inline-flex;
  color: var(--ink-3);
  transition: transform 0.15s ease;
}
.bom-alerts[data-collapsed="false"] .bom-alerts-caret { transform: rotate(90deg); }
.bom-alerts[data-alert-state="clear"] .bom-alerts-caret { visibility: hidden; }

.bom-alerts-summary {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  min-width: 0;
}

.bom-alerts-clear {
  font-size: 11.5px;
  color: var(--ink-3);
}

/* Severity count chips — same accent tokens as the alert panel sections. */
.bom-alerts-chip {
  display: inline-flex;
  align-items: center;
  height: 18px;
  padding: 0 8px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0;
}
.bom-alerts-chip.is-error   { background: var(--sig-error-bg); color: var(--sig-error); }
.bom-alerts-chip.is-warning { background: var(--sig-warn-bg);  color: var(--sig-warn); }
.bom-alerts-chip.is-advice  { background: var(--surface-3);    color: var(--ink-2); }

/* Expanded body reuses .alert-summary-body (flex column, 8px gap). It is a band
   of its own between the header and the list (2026-07-29) — inside the header it
   inherited the head block's shrink-to-fit width (~125px, messages wrapped into a
   ribbon) and pushed the header to ~360px, i.e. 40% of the sheet. Own band =
   full width, own scroll, header stays one compact row. */
.bom-alerts-body {
  flex: 0 0 auto;
  padding: 12px 32px;
  max-height: min(45vh, 400px);
  overflow-y: auto;
  border-bottom: 1px solid var(--line);
  background: var(--paper-soft);
}
.bom-alerts-body[hidden] { display: none; }
/* 段不许被压缩：.alert-summary-body 是 column flex，段的默认 flex-shrink:1 会在
   本容器 max-height 封顶时把各段压扁，而 .alert-summary-section 自带
   overflow:hidden → 内容被**裁掉**而不是溢出，容器 scrollHeight 恒等于
   clientHeight，滚动条根本不出现（只看得到前几条）。顶栏 popover 里不出问题是
   因为那边的 body 不封顶、滚动在外层 pop 上。 */
.bom-alerts-body > .alert-summary-section { flex: 0 0 auto; }

/* Per-alert action bar (recommended replacement SKUs). Lives inside the alert
   block so every rule that declares `recommend` can be fixed where it is
   reported — previously the whole recommendation area hung off the femtosecond
   banner, so non-fs scenes got no buttons at all. */
.alert-summary-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}

.modal-close {
  width: 36px;   /* ↑ from 32 (DESIGN.md §8.1) */
  height: 36px;
  border: 1px solid var(--ctl-line);   /* v2 affordance：关闭 × 边并入控件座系统（U11）；实底 --surface 保留 */
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink-2);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 0 2px;
  flex: 0 0 auto;
  transition: all 0.15s;
}

.modal-close:hover {
  background: var(--ink);
  color: var(--surface);
  border-color: var(--ink);
}

.modal-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 22px 32px 18px;
}

/* ─── Datasheet modal (product image + raw spec_matrix) ──────────── */

.datasheet-lineage-tag {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--ink-1);
  background: var(--surface-2);
  padding: 2px 7px;
  border-radius: 2px;
}

.datasheet-figure {
  margin: 0 0 22px;
  padding: 14px;
  background: var(--surface-2, #f6f8fb);
  border: 1px solid var(--line);
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 180px;
}

.datasheet-figure img {
  display: block;
  max-width: 100%;
  max-height: 320px;
  object-fit: contain;
}

.datasheet-figure-empty {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  color: var(--ink-3);
}

.datasheet-section-title {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink);
  padding: 6px 0 10px;
  border-bottom: 1px solid var(--ink);
  margin-bottom: 4px;
}

.datasheet-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.4;
}

.datasheet-table th,
.datasheet-table td {
  text-align: left;
  vertical-align: top;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line-soft, #e8edf2);
}

.datasheet-table th {
  width: 38%;
  font-weight: 500;
  color: var(--ink-2);
  font-size: 10.5px;
  letter-spacing: 0.02em;
}

.datasheet-table td {
  color: var(--ink);
  word-break: break-word;
}

.datasheet-table tr:last-child th,
.datasheet-table tr:last-child td {
  border-bottom: none;
}

.datasheet-empty {
  padding: 20px 0;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--ink-3);
  text-align: center;
}

/* ─── BOM sections ────────────────────────────────────────────────── */

.bom-section {
  margin-bottom: 14px;
}

.bom-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 4px 0 7px;
  border-bottom: 1px solid var(--ink);
  margin-bottom: 2px;
}

.bom-section-title {
  font-size: 11px;            /* §4.2 xs（原 10.5 越界） */
  font-weight: 500;           /* §4.3 section = 500 */
  letter-spacing: 0.08em;     /* §4.4 全大写标签统一 0.08em（原 0.22 过宽） */
  text-transform: uppercase;
  color: var(--ink);
}

.bom-section-sub {
  /* per-section subtotal (a price) → Geist Mono（§4.1 价格走 mono）。 */
  font-family: var(--font-mono);
  font-size: 14px;           /* §4.2 md */
  font-weight: 500;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;         /* §4.4 mono 数值 = 0（原 -0.01） */
}

.bom-row {
  display: grid;
  grid-template-columns: 22px 1fr 50px 90px;   /* 件号 · 主 · qty · unit（CAD借鉴③）*/
  gap: 12px;
  align-items: start;
  padding: 9px 0;
  border-bottom: 1px solid var(--line-hard);   /* v2 affordance：数据表格行线 = 唯一线索 → line-hard(≥3:1)（A 辅主战场，U11）；旧 line-soft 隐形 */
}
/* SKU + 型号名同行（2026-06-15 紧排）：型号左、名跟随，超宽换行不挤压 qty/价列。 */
.bom-row-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
/* 件号圆牌 —— 与 3D 气泡同号同源（bom.js bomItemNumbers）*/
.bom-row-num {
  align-self: center;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;            /* §4.3 件号数字 = 500（原 600） */
  color: var(--surface);
  background: var(--ink);
  border-radius: 999px;
}
.bom-row-num-none {
  color: var(--ink-3);
  background: transparent;
  border: 1px dashed var(--line);
}

.bom-row:last-child { border-bottom: none; }

.bom-row-main { min-width: 0; }

.bom-row-sku {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;            /* §4.3 SKU = 500（原 600） */
  color: var(--ink-1);
  letter-spacing: 0;           /* §4.4 mono SKU = 0（原 0.04） */
  white-space: nowrap;
}

.bom-row-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.3;
}

.bom-row-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
  font-size: 10.5px;
  color: var(--ink-3);
}

.bom-vendor {
  font-family: var(--font-ui);   /* 品牌名是文字 → Geist（§4.1 mono 仅给 SKU/数值） */
  font-size: 11px;
  letter-spacing: 0;
  font-weight: 400;
  color: var(--ink-2);
}

.bom-path {
  font-family: var(--font-ui);
  color: var(--ink-3);
}

.bom-row-detail {
  font-family: var(--font-mono);
  font-size: 11px;            /* §4.2 xs（原 10 越界） */
  color: var(--ink-2);
  letter-spacing: 0;          /* §4.4 mono = 0（原 0.02） */
  line-height: 1.45;
  margin-top: 3px;
}

.bom-row-qty {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  text-align: right;
  padding-top: 2px;
}

/* Editable Order Qty input (M1 — 2026-05-29). Minimal styling: matches
   the row's typography, hides spinner UI, surfaces a subtle underline
   on focus. Width tuned for up-to-3-digit values. */
.bom-qty-input {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
  font-variant-numeric: tabular-nums;
  text-align: right;
  width: 48px;
  padding: 1px 4px;
  background: transparent;
  border: 1px solid transparent;
  border-bottom: 1px solid var(--line);
  border-radius: 2px;
  appearance: textfield;
  -moz-appearance: textfield;
  transition: border-color 0.12s ease, background-color 0.12s ease;
}
.bom-qty-input::-webkit-outer-spin-button,
.bom-qty-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.bom-qty-input:hover {
  border-bottom-color: var(--ink-3);
}
.bom-qty-input:focus {
  outline: none;
  border-color: var(--ink-1);
  background: var(--surface-2);
}

/* qty=0 row: faded but still visible so the user can resurrect by typing
   a non-zero value. PDF/Cart consumers filter these out. */
.bom-row-zero {
  opacity: 0.4;
}
.bom-row-zero .bom-qty-input {
  color: var(--ink-3);
}

.bom-row-unit {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  text-align: right;
  padding-top: 3px;
}

/* bom-row-sub removed — subtotal duplicated unit price when qty=1 */

.bom-row-poa { background: var(--paper-soft); }

/* ─── BOM view toggle + assembly groups (grouped view, 2026-06-15) ───── */
/* header 右侧动作组：视图切换 + 关闭按钮（user 2026-06-15：切换挪到 × 左侧，腾出清单空间）。 */
.bom-header-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 0 0 auto;
}
/* BOM 抬头对齐 §4.2 下限（仅覆盖本模态，不改全局 .modal-eyebrow/.modal-meta）：
   eyebrow 10→11 + 字距 0.28→0.08em（§4.4）；meta 10.5→11。 */
#bom-modal .modal-eyebrow { font-size: 11px; letter-spacing: 0.08em; }
#bom-modal .modal-meta { font-size: 11px; }
.bom-view-toggle {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  background: var(--paper-soft);
  border: 1px solid var(--line-soft);
  border-radius: 8px;
}
.bom-view-btn {
  font-family: var(--font-ui);
  font-size: 12px;            /* §4.2 sm（原 11.5 非档） */
  font-weight: 500;
  color: var(--ink-2);
  padding: 5px 12px;
  background: transparent;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: background-color 0.12s ease, color 0.12s ease;
}
.bom-view-btn:hover { color: var(--ink); }
.bom-view-btn.active {
  color: var(--surface);
  background: var(--ink);
}

.bom-asm-list { margin-bottom: 6px; }

.bom-asm-group {
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  margin-bottom: 10px;
  overflow: hidden;
}
.bom-asm-head {
  display: grid;
  grid-template-columns: 16px 22px 1fr 90px;
  gap: 12px;
  align-items: center;
  padding: 10px 14px;
  cursor: pointer;
  background: var(--paper-soft);
}
.bom-asm-head:hover { background: var(--surface-2); }
.bom-asm-head:focus-visible { outline: 2px solid var(--ink-1); outline-offset: -2px; }
.bom-asm-caret {
  font-size: 11px;
  color: var(--ink-3);
  transition: transform 0.15s ease;
  text-align: center;
}
.bom-asm-group.collapsed .bom-asm-caret { transform: rotate(-90deg); }
.bom-asm-group.collapsed .bom-asm-parts { display: none; }
.bom-asm-headmain { min-width: 0; }
.bom-asm-summary {
  font-size: 11px;            /* §4.2 xs（原 10.5 越界） */
  color: var(--ink-3);
  margin-top: 3px;
}

.bom-asm-parts {
  padding: 2px 14px 6px 42px;
  background: var(--surface);
}
.bom-asm-part {
  display: grid;
  grid-template-columns: 84px 1fr 90px;
  gap: 12px;
  align-items: center;
  padding: 7px 0;
  border-top: 1px solid var(--line-soft);
}
.bom-asm-role {
  font-size: 11px;           /* §4.2 xs（原 9.5 越界） */
  font-weight: 500;          /* §4.3 label = 500（原 600） */
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
/* 单行配件行（已去 detail）→ 价格列与 role/型号居中对齐，取消 flat 行的顶距。 */
.bom-asm-part .bom-row-unit { padding-top: 0; }
.bom-asm-itemno {
  color: var(--ink-3);
  font-weight: 500;
}

.bom-asm-toggle-all {
  font-family: var(--font-ui);
  font-size: 11px;            /* §4.2 xs（原 10.5 越界） */
  font-weight: 500;
  color: var(--ink-1);
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0;
}
.bom-asm-toggle-all:hover { text-decoration: underline; }

/* 加入购物车 = 图标按钮（user 2026-06-15：文字 "Add to Cart" → 购物车标志）。保留
   modal-btn-primary 实底醒目样式，收成方形；状态反馈（Loading/Opened）由 cart.js 临时填文字。 */
/* 用 .modal-btn-primary.bom-cart-btn 提特异性，强制覆盖 .modal-btn-primary 的 padding:0 18px
   —— 否则 40px 按钮被左右 18px padding 吃掉、内容区只剩 4px，SVG 被 flex 压成一条竖线（user
   2026-06-15「图标特别小」的真因）。svg flex:none 再防压缩，双保险。 */
.modal-btn-primary.bom-cart-btn {
  width: 40px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.bom-cart-btn svg { display: block; width: 24px; height: 24px; flex: none; }

/* ─── 暗色模式 BOM 可读性（user 2026-06-15）─────────────────────────────────
   亮/暗共用的 --paper-soft 与 --surface 在暗色塌成同一个 #16161A，导致装配卡「卡头」
   与「卡体」同色、行分隔线（--line-soft #1F1F24）几乎隐没。这里按暗色层次 token 显式
   分层（仅覆盖 BOM，不动全局 token，避免与并发主题调整冲突）：
     卡头 surface-3 #1F1F24（标题栏，亮一档） · 卡体 surface #16161A · 轮廓/分隔 line #2A2A30。 */
[data-theme="dark"] .bom-view-toggle { background: var(--surface-3); border-color: var(--line); }
[data-theme="dark"] .bom-asm-group   { border-color: var(--line); }
[data-theme="dark"] .bom-asm-head    { background: var(--surface-3); }
[data-theme="dark"] .bom-asm-head:hover { background: var(--line); }
[data-theme="dark"] .bom-asm-parts   { background: var(--bg); }
[data-theme="dark"] .bom-row         { border-bottom-color: var(--line-hard); }   /* v2 affordance：数据表行线 dark 也达标（U11）*/
[data-theme="dark"] .bom-asm-part    { border-top-color: var(--line); }

.poa-pill {
  display: inline-block;
  padding: 2px 8px;
  background: var(--warn-bg);
  color: var(--warn-ink);
  border: 1px solid var(--warn);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 11px;           /* §4.2 xs（原 9.5 越界） */
  font-weight: 500;          /* §4.3（原 600） */
  letter-spacing: 0.08em;    /* §4.4 全大写标签 0.08em（原 0.18） */
  text-transform: uppercase;
}

/* 装配套件占位母件价格列：中性「Included」（非警示色），表示已按逐件硬件计价。 */
.bom-incl-pill {
  display: inline-block;
  padding: 2px 8px;
  background: var(--overlay-1, rgba(255, 255, 255, 0.04));
  color: var(--ink-3);
  border: 1px solid var(--line-1);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* 装配体零件数量徽标（滑块/镜架/压夹/棱镜 ×2 等）。 */
.bom-asm-qty {
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-weight: 600;
}

.bom-inferred {
  display: inline-block;
  padding: 1px 6px;
  background: var(--surface-2);
  color: var(--ink-1);
  border: 1px solid var(--ink-1);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 500;
  margin-left: 4px;
  cursor: help;
}

.bom-poa-note {
  flex: 0 0 100%;
  width: 100%;
  margin-bottom: 14px;
  padding: 10px 14px;
  background: var(--warn-bg);
  color: var(--warn-ink);
  border: 1px solid var(--warn);
  border-left-width: 3px;
  border-radius: 3px;
  font-family: var(--font-ui);
  font-size: 11px;
  line-height: 1.5;
}

.bom-poa-note strong { font-weight: 600; }

/* §13 BOM 推荐替代型号（2026-07-29 起挂在各自报错块的 .alert-summary-actions
   内，不再是 fs banner 专属；banner 本体退役 —— 那条 scene advice 已在报错区
   里，banner = 同一条的重复渲染）。 */
.bom-fix-btn {
  padding: 2px 8px;
  border: 1px solid var(--line-1, var(--line));
  border-radius: 3px;
  background: var(--surface-1, var(--surface));
  color: var(--ink-1, var(--ink));
  font-family: var(--font-mono);
  font-size: 10px;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease;
}
.bom-fix-btn:hover {
  background: var(--surface-2);
  border-color: var(--ink-3);
}
.bom-fix-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--overlay-2, var(--surface-3));
}
/* 'add' 类推荐（爬高架）= 只读 chip（加配件、非换型 → 不可点击，user 2026-06-26）。 */
.bom-fix-add {
  padding: 2px 8px;
  border: 1px dashed var(--line-1, var(--line));
  border-radius: 3px;
  color: var(--ink-2, var(--ink));
  font-family: var(--font-mono);
  font-size: 10px;
  cursor: default;
}

/* ── 清单行报错角标（2026-07-29）─────────────────────────────────────────
   把折叠条上的「N warnings」落到具体行上：有报错的行/装配卡挂计数角标 +
   行首色条，客户扫清单即知哪几件有问题（此前清单本身零线索）。 */
.bom-row-alert {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 5px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  line-height: 1;
  cursor: help;
}
.bom-row-alert.is-error   { background: var(--sig-error); color: var(--surface); }
.bom-row-alert.is-warning { background: var(--sig-warn);  color: var(--surface); }
.bom-row-alert.is-advice  { background: var(--surface-3); color: var(--ink-2); }

/* 行首色条：一律走 inset box-shadow（不占布局 → 有标记/无标记的行左缘仍对齐；
   .bom-row 是 grid，改 padding/border 会让标记行整体右移 1–8px）。留 8px 通栏
   内缩给色条呼吸，标记与否都一样。装配卡同理：只染既有 1px 左边框的颜色，不改宽。 */
.bom-row { padding-left: 8px; }
.bom-row[data-alert-top="error"]   { box-shadow: inset 2px 0 0 var(--sig-error); }
.bom-row[data-alert-top="warning"] { box-shadow: inset 2px 0 0 var(--sig-warn); }
.bom-row[data-alert-top="advice"]  { box-shadow: inset 2px 0 0 var(--ink-3); }
.bom-asm-group[data-alert-top="error"] {
  border-left-color: var(--sig-error);
  box-shadow: inset 2px 0 0 var(--sig-error);
}
.bom-asm-group[data-alert-top="warning"] {
  border-left-color: var(--sig-warn);
  box-shadow: inset 2px 0 0 var(--sig-warn);
}
.bom-asm-group[data-alert-top="advice"] {
  border-left-color: var(--ink-3);
  box-shadow: inset 2px 0 0 var(--ink-3);
}

/* 报错块点击 → 滚到清单行后的一次性高亮（找得到才算连上）。 */
@keyframes bom-row-flash {
  0%   { background: var(--sig-warn-bg); }
  100% { background: transparent; }
}
.bom-row-flash { animation: bom-row-flash 1.2s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .bom-row-flash { animation: none; background: var(--sig-warn-bg); }
}

/* ─── Modal footer ────────────────────────────────────────────────── */

.modal-footer {
  flex: 0 0 auto;
  padding: 22px 32px 26px;
  border-top: 1px solid var(--line);
  background: var(--paper-soft);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
}

.modal-summary {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 220px;
}

.summary-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 24px;
}

.summary-key {
  font-family: var(--font-ui);   /* label 是文字（§4.1 mono 仅给数值） */
  font-size: 11px;               /* §4.2 xs（原 10 越界） */
  letter-spacing: 0.08em;        /* §4.4 全大写标签 0.08em（原 0.18） */
  text-transform: uppercase;
  color: var(--ink-2);
}

.summary-val {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.summary-row.dimmed .summary-key,
.summary-row.dimmed .summary-val {
  color: var(--ink-3);
  font-size: 11px;          /* §4.2 xs（原 10.5 越界） */
}

/* 税务免责声明：弱化为限宽脚注（user 2026-06-15 UI 反馈）——原本整句占满 footer 左侧、
   挤压按钮、与 Total hero 抢视觉。改为顶对齐、最小最淡、限宽换行，让 Total 价格主导。 */
.bom-tax-disclaimer { align-items: flex-start; }
.bom-tax-disclaimer .summary-disclaimer {
  max-width: 240px;       /* 关键：限宽换行，不再撑满 footer 左侧、不挤按钮 */
  white-space: normal;
  line-height: 1.4;
  font-family: var(--font-ui);   /* 句子文字，覆盖 summary-key 的 mono/uppercase */
  text-transform: none;
  letter-spacing: 0;
}

.summary-row.total {
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--ink);
}

.summary-row.total .summary-key {
  color: var(--ink);
  font-weight: 600;
}

.summary-row.total .summary-val {
  /* "With Tax" grand total — the quotation's hero price → Geist Mono,
     upright, kept at the 28px scale cap (§4.2 2xl), color ink-1 (§4.1). */
  font-family: var(--font-mono);
  font-size: 28px;
  font-weight: 500;
  color: var(--ink-1);
  letter-spacing: 0;          /* §4.4 mono 数值 = 0（原 -0.02） */
  line-height: 1;
}

.modal-actions {
  display: flex;
  gap: 8px;
  align-items: center;
}

.modal-btn-secondary,
.modal-btn-primary {
  height: 40px;   /* ↑ from 36 — modal footer lg (DESIGN.md §8.1) */
  padding: 0 18px;
  border-radius: 3px;
  font-family: var(--font-ui);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
  transition: all 0.18s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.modal-btn-secondary {
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink-2);
}

.modal-btn-secondary:hover {
  border-color: var(--ink-1);
  color: var(--ink-1);
  background: var(--surface-2);
}

.modal-btn-secondary.flash {
  background: var(--good);
  color: var(--surface);   /* theme-aware reverse ink (DESIGN.md §3.3) */
  border-color: var(--good);
}

.modal-btn-primary {
  background: var(--ink-1);
  color: var(--surface);
  border: 1px solid var(--ink-1);
  box-shadow: 0 4px 10px -3px rgba(0, 0, 0, 0.4);
}

.modal-btn-primary:hover {
  background: var(--ink-1);
  border-color: var(--ink-1);
}

