.erp-grid { display: grid; gap: var(--erp-space-4); }
.erp-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.erp-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.erp-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.erp-stack { display: grid; gap: var(--erp-space-4); }
.erp-stack--sm { gap: var(--erp-space-2); }
.erp-stack--lg { gap: var(--erp-space-6); }
.erp-row { display: flex; align-items: center; gap: var(--erp-space-3); }
.erp-row--between { justify-content: space-between; }
.erp-wrap { flex-wrap: wrap; }
.erp-text-muted { color: var(--erp-text-muted) !important; }
.erp-text-secondary { color: var(--erp-text-secondary) !important; }
.erp-text-primary { color: var(--erp-text-primary) !important; }
.erp-text-gold { color: var(--erp-gold-light) !important; }
.erp-mono { font-family: var(--erp-font-mono); }
.erp-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.erp-white-space-pre-line { white-space: pre-line; }

/* P0 hot-fix 2026-05-14 — pagination footer labels were wrapping per-character
   (「每 / 頁」「密 / 度」「分 / 頁 / 樣 / 式」) on narrow containers because parent
   flex row applied no wrap policy and inline <span> labels lacked white-space:nowrap.
   Scope intentionally narrow: only spans whose immediate parent is a horizontal
   pagination/density row (matched by flex+align+gap utility combo). */
.erp-page .row > .d-flex.align-items-center.gap-2 > span,
.erp-page .row > .d-flex.flex-wrap.align-items-center.gap-2 > span { white-space: nowrap; }
.erp-page .row > .d-flex.align-items-center.gap-2 { flex-wrap: wrap; }

/* Wave 1 (P1-A) 2026-05-15 — hide dev-taxonomy kicker shown to end users.
   29 cshtml had `<div class="erp-kicker">OVERVIEW HUB · ADMIN</div>` (etc.)
   inside .page-head — internal navigation taxonomy leaking into user-visible UI.
   Decision (UIUX audit doc §1.2 P1-A): hide via CSS instead of deleting markup.
   Surgical: only `.page-head .erp-kicker` / `.erp-page-header .erp-kicker`.
   Card-level kickers (e.g. Admin/Index L125 "目前焦點") not inside page-head
   are preserved (legitimate UI affordance). */
.page-head > div > .erp-kicker,
.erp-page-header > div > .erp-kicker { display: none; }

/* ===== Bootstrap-mirror atomic utilities — all dimensions via --erp-* tokens
   (per DECISION-20260515-css-trunk-cleanup-architecture §2 Option A — ADR purged
    2026-05-29 mass purge; live SoT = ../CLAUDE.md Rule 1 + tools/qa/css-drift-check.cjs) ===== */

.visually-hidden { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important; }
.d-none { display: none !important; }
.d-grid { display: grid !important; }
.d-flex { display: flex !important; }
.flex-wrap { flex-wrap: wrap !important; }
.align-items-center { align-items: center !important; }
.align-items-start { align-items: flex-start !important; }
.align-items-end { align-items: flex-end !important; }
.justify-content-between { justify-content: space-between !important; }
.justify-content-end { justify-content: flex-end !important; }
.text-start { text-align: left !important; }
.text-end { text-align: right !important; }
.text-center { text-align: center !important; }
.w-100 { width: 100% !important; }
.m-0 { margin: 0 !important; }
.mb-0 { margin-bottom: 0 !important; }
.mb-1 { margin-bottom: var(--erp-space-1) !important; }
.mb-2 { margin-bottom: var(--erp-space-2) !important; }
.mb-3 { margin-bottom: var(--erp-space-3) !important; }
.mb-4 { margin-bottom: var(--erp-space-4) !important; }
.mt-1 { margin-top: var(--erp-space-1) !important; }
.mt-2 { margin-top: var(--erp-space-2) !important; }
.mt-3 { margin-top: var(--erp-space-3) !important; }
.mt-4 { margin-top: var(--erp-space-4) !important; }
.ms-1 { margin-left: var(--erp-space-1) !important; }
.me-1 { margin-right: var(--erp-space-1) !important; }
.me-2 { margin-right: var(--erp-space-2) !important; }
.p-0 { padding: 0 !important; }
.p-3 { padding: var(--erp-space-3) !important; }
.p-4 { padding: var(--erp-space-4) !important; }
.px-3 { padding-left: var(--erp-space-3) !important; padding-right: var(--erp-space-3) !important; }
.px-4 { padding-left: var(--erp-space-4) !important; padding-right: var(--erp-space-4) !important; }
.py-2 { padding-top: var(--erp-space-2) !important; padding-bottom: var(--erp-space-2) !important; }
.gap-1 { gap: var(--erp-space-1) !important; }
.gap-2 { gap: var(--erp-space-2) !important; }
.gap-3 { gap: var(--erp-space-3) !important; }
.gap-4 { gap: var(--erp-space-4) !important; }
.rounded { border-radius: var(--erp-radius-md) !important; }
.rounded-pill { border-radius: var(--erp-radius-pill) !important; }
@media (min-width: 768px) { .erp-grid-md-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .erp-grid-lg-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } .d-lg-none { display: none !important; } .d-lg-flex { display: flex !important; } .d-lg-inline-flex { display: inline-flex !important; } }
@media (max-width: 767px) { .erp-grid--2, .erp-grid--3, .erp-grid--4 { grid-template-columns: 1fr; } }
/* B-2 HOTFIX 2026-05-15：移除原 ERP-owned grid emulation (.row + .col-*) — Bootstrap
   vendor stylesheet 已提供 flex-based grid (.row + .col-lg-4 { width: 33.3333% } 等)。
   原 grid emulation 因兩個 CSS Grid corner case 失效：(a) `grid-column: span N` 簡寫
   被解讀為 grid-column-start: span N + grid-column-end: auto，實際只佔一個 track；
   (b) `repeat(12, minmax(0, 1fr))` + `min-width: 0` 讓 tracks shrink to content。
   讓 vendor Bootstrap 接管，符合 DECISION-20260515-css-auth-bootstrap-first §2
   （ADR purged 2026-05-29 mass purge; live SoT = ../CLAUDE.md §1 Bootstrap-first rules）。
   B-2 HOTFIX REVERSED 2026-06-13（CSS-G3-G4-GUTTER-COLLISION，RC:MI）：原假設
   「保留 .g-N gap utilities（Bootstrap-mirror，不衝突）」錯誤 —— Bootstrap .row 為
   display:flex，ERP 的 .g-3/.g-4 是疊加 flex `gap !important`，與 Bootstrap 原生
   padding-based gutter（--bs-gutter-x/y）相加；當 row 內欄寬總和≈12(100%) 時擠破換行
   （全站 42/43 受影響頁，census 76 HIGH row）。已移除 ERP override，改由 Bootstrap
   原生 .g-N gutter 接管。要 flex/grid gap 請改用本檔上方的 .gap-3/.gap-4（作用於
   d-flex 容器）。禁止再宣告 .g-<digit> { gap } —— 由 tools/qa/css-drift-check.cjs C8 機器擋。 */
.fw-bold { font-weight: var(--erp-weight-bold) !important; }
.fw-semi-bold { font-weight: var(--erp-weight-semibold) !important; }
.text-truncate { overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; }
.align-middle { vertical-align: middle !important; }
.position-relative { position: relative !important; }
.position-absolute { position: absolute !important; }
.top-0 { top: 0 !important; }
.end-0 { right: 0 !important; }
.z-1 { z-index: 1 !important; }
.border-0 { border: 0 !important; }
/* (Removed: @media col-md-* / col-lg-* / col-xl-* @media rules — Bootstrap vendor stylesheet handles them) */
.pt-0 { padding-top: 0 !important; }
.pt-1 { padding-top: var(--erp-space-1) !important; }
.pt-2 { padding-top: var(--erp-space-2) !important; }
.pt-3 { padding-top: var(--erp-space-3) !important; }
.pt-4 { padding-top: var(--erp-space-4) !important; }
.pb-0 { padding-bottom: 0 !important; }
.pb-1 { padding-bottom: var(--erp-space-1) !important; }
.pb-2 { padding-bottom: var(--erp-space-2) !important; }
.pb-3 { padding-bottom: var(--erp-space-3) !important; }
.pb-4 { padding-bottom: var(--erp-space-4) !important; }
.ps-0 { padding-left: 0 !important; }
.ps-3 { padding-left: var(--erp-space-3) !important; }
.pe-0 { padding-right: 0 !important; }
.pe-3 { padding-right: var(--erp-space-3) !important; }
.py-3 { padding-top: var(--erp-space-3) !important; padding-bottom: var(--erp-space-3) !important; }
.py-4 { padding-top: var(--erp-space-4) !important; padding-bottom: var(--erp-space-4) !important; }
.mx-auto { margin-left: auto !important; margin-right: auto !important; }
.ms-auto { margin-left: auto !important; }
.me-auto { margin-right: auto !important; }
.h-100 { height: 100% !important; }
.flex-grow-1 { flex-grow: 1 !important; }
.flex-shrink-0 { flex-shrink: 0 !important; }
.flex-column { flex-direction: column !important; }
.justify-content-center { justify-content: center !important; }
.justify-content-start { justify-content: flex-start !important; }
.erp-mono { font-family: var(--erp-font-mono) !important; }
.tabular-nums { font-variant-numeric: tabular-nums !important; }
.text-num-right { font-family: var(--erp-font-mono); font-variant-numeric: tabular-nums; text-align: right; }

/* ===== Stage B-1.1 (2026-05-15) — token-backed dimensional utilities =====
   Inline-style replacement utilities. All dimensions reference --erp-* tokens
   defined in 00-tokens.css §"Stage B-0 — utility-class token families".
   Per DECISION-20260515-css-trunk-cleanup-architecture §2 Option A (ADR purged
   2026-05-29 mass purge; live SoT = ../CLAUDE.md Rule 1 + tools/qa/css-drift-check.cjs):
   zero px literals in Layer 3 (this file).
   B-1.1 supersedes Stage B-1's literal utility classes (col-min-110..280 etc.). */

/* Table column min-width (6-bucket t-shirt scale) */
.col-min-xs  { min-width: var(--erp-col-min-xs); }
.col-min-sm  { min-width: var(--erp-col-min-sm); }
.col-min-md  { min-width: var(--erp-col-min-md); }
.col-min-lg  { min-width: var(--erp-col-min-lg); }
.col-min-xl  { min-width: var(--erp-col-min-xl); }
.col-min-xxl { min-width: var(--erp-col-min-xxl); }

/* Scrollable container with capped height */
.scroll-y-sm { max-height: var(--erp-scroll-cap-sm); overflow-y: auto; }
.scroll-y-md { max-height: var(--erp-scroll-cap-md); overflow-y: auto; }
.scroll-y-lg { max-height: var(--erp-scroll-cap-lg); overflow-y: auto; }

/* Grid auto-fit (minmax responsive) */
.grid-auto-fit-sm { grid-template-columns: repeat(auto-fit, minmax(var(--erp-grid-autofit-sm), 1fr)); }
.grid-auto-fit-md { grid-template-columns: repeat(auto-fit, minmax(var(--erp-grid-autofit-md), 1fr)); }
.grid-auto-fit-lg { grid-template-columns: repeat(auto-fit, minmax(var(--erp-grid-autofit-lg), 1fr)); }

/* Image clamp (brand logo / icon) */
.img-clamp-sm { max-width: var(--erp-img-clamp-sm); max-height: var(--erp-img-clamp-sm); }
.img-clamp-md { max-width: var(--erp-img-clamp-md); max-height: var(--erp-img-clamp-md); }
.img-clamp-lg { max-width: var(--erp-img-clamp-lg); max-height: var(--erp-img-clamp-lg); }

/* ===== Stage B-2 (2026-05-15) — Purchases inline-style replacement utilities ===== */
/* Popper stack — Element Plus popover internal layering */
.z-popper-base  { z-index: var(--erp-z-popper-base); }
.z-popper-stack { z-index: var(--erp-z-popper-stack); }

/* Tinted card-header backgrounds (replaces hex literals in _PurchaseEditorModal + _WorkstationSupport) */
.bg-info-soft    { background-color: var(--erp-ds-info-bg-soft); }
.bg-success-soft { background-color: var(--erp-ds-success-bg-soft); }

/* Fixed-width column widths (parallel to col-min-* but with exact width) */
.col-w-mini { width: var(--erp-col-w-mini); }
.col-w-md   { width: var(--erp-col-min-md); }
.col-w-lg   { width: var(--erp-col-min-lg); }
.col-w-xl   { width: var(--erp-col-min-xl); }

/* Semantic text color utilities */
.text-info    { color: var(--erp-info); }
.text-success { color: var(--erp-success); }

/* Typography scale shortcuts (semantic, not literal) */
.text-h2   { font-size: var(--erp-text-h2); }
.text-body { font-size: var(--erp-text-body); }

/* Opacity (Bootstrap-mirror) */
.opacity-50 { opacity: 0.5; }
.opacity-75 { opacity: 0.75; }

/* Percentage column widths (table calibration; % is not in Rule 1 forbidden literal list — px/rem/hex/rgba only) */
.col-pct-5  { width: 5%; }
.col-pct-6  { width: 6%; }
.col-pct-7  { width: 7%; }
.col-pct-8  { width: 8%; }
.col-pct-10 { width: 10%; }
.col-pct-12 { width: 12%; }
.col-pct-14 { width: 14%; }
.col-pct-15 { width: 15%; }
.col-pct-18 { width: 18%; }
.col-pct-22 { width: 22%; }
.col-pct-24 { width: 24%; }
.col-pct-25 { width: 25%; }
.col-pct-30 { width: 30%; }
