/*
 * Base styles — typography, layout, TopNav, common components.
 * Every value comes from tokens.css (frontend §2.x). No raw hex/px here.
 */

/* ─── Reset ────────────────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }
html, body, h1, h2, h3, h4, h5, h6, p, ul, ol, dl, figure, blockquote, pre, hr {
  margin: 0;
}
ul, ol { padding: 0; list-style: none; }

html {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: var(--fw-regular);
  line-height: var(--lh-body);
  color: var(--color-text-primary);
  background: var(--color-bg-base);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a {
  color: var(--color-link);
  text-decoration: none;
}
a:hover {
  color: var(--color-link-hover);
  text-decoration: underline;
}

button {
  font: inherit;
  cursor: pointer;
  background: none;
  border: none;
  color: inherit;
  padding: 0;
}

input, select, textarea { font: inherit; }

/* ─── Typography helpers (frontend §2.1) ──────────────────────────── */

.heading-h1 { font-size: var(--fs-h1); font-weight: var(--fw-bold);   line-height: 1.3; }
.heading-h2 { font-size: var(--fs-h2); font-weight: var(--fw-bold);   line-height: 1.3; }
.heading-h3 { font-size: var(--fs-h3); font-weight: var(--fw-medium); line-height: 1.4; }

.text-body  { font-size: var(--fs-body);  }
.text-table { font-size: var(--fs-table); line-height: var(--lh-table); }
.text-label { font-size: var(--fs-label); font-weight: var(--fw-medium); }
.text-meta  { font-size: var(--fs-meta);  color: var(--color-text-muted); }
.text-mono  { font-family: var(--font-mono); font-size: var(--fs-mono); }

.text-primary   { color: var(--color-text-primary); }
.text-secondary { color: var(--color-text-secondary); }
.text-muted     { color: var(--color-text-muted); }
.text-inverse   { color: var(--color-text-inverse); }

/* ─── Site version footer ────────────────────────────────────────── */
/* Faint, centered build-version line at the bottom of every page. Low
   visual weight on purpose — a quiet "which build am I on?" reference
   (handy when Sheens staff report a bug), not a real UI element. */
.site-footer {
  padding: var(--space-6) var(--space-4) var(--space-8);
  text-align: center;
  font-size: var(--fs-meta);
  color: var(--color-text-muted);
  letter-spacing: 0.02em;
}

/* ─── Layout helpers ─────────────────────────────────────────────── */

.container {
  max-width: var(--layout-content-width);
  margin: 0 auto;
  padding: var(--layout-page-pad-top) var(--layout-side-margin);
}

.page {
  min-height: calc(100vh - var(--layout-topnav-height));
}

.stack > * + * { margin-top: var(--space-4); }
.stack-sm > * + * { margin-top: var(--space-2); }
.stack-lg > * + * { margin-top: var(--space-6); }

/* ─── TopNav (frontend §6.3) ─────────────────────────────────────── */

.topnav {
  position: sticky;
  top: 0;
  z-index: 100;
  height: var(--layout-topnav-height);
  background: var(--color-primary);
  color: var(--color-text-inverse);
  box-shadow: var(--shadow-sm);
}

.topnav[data-disabled="true"] {
  pointer-events: none;
  opacity: 0.55;
}

.topnav__inner {
  max-width: var(--layout-canvas-width);
  margin: 0 auto;
  height: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-6);
  padding: 0 var(--space-6);
}
/* Tighter gap + padding on narrow phones so the right cluster (ERP · ⚙ · 이름 · 로그아웃) fits
 * without the page scrolling sideways. */
@media (max-width: 480px) {
  .topnav__inner {
    gap: var(--space-3);
    padding: 0 var(--space-3);
  }
}

.topnav__logo {
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  color: var(--color-text-inverse);
  text-decoration: none;
  flex-shrink: 0;
}
.topnav__logo:hover { text-decoration: none; }

.topnav__links {
  display: flex;
  gap: var(--space-1);
}

.topnav__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--color-text-inverse);
  font-weight: var(--fw-medium);
  text-decoration: none;
  transition: background 0.15s;
}
.topnav__link:hover {
  background: rgba(255, 255, 255, 0.08);
  text-decoration: none;
}
.topnav__link.is-active {
  background: rgba(255, 255, 255, 0.14);
}
/* Per-link unread badge — info-blue circle in the topnav 공지사항 link. */
.topnav__badge {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--color-info);
  color: var(--color-text-inverse);
  font-size: 11px;
  font-weight: var(--fw-bold);
  border-radius: 9px;
  min-width: 18px; height: 18px;
  padding: 0 5px;
}

/* Bell icon badge — small red counter pinned to the top-right corner of
 * the bell button. Different from .topnav__badge (which sits inline in
 * the link); this one is absolutely positioned so it overlaps the icon. */
.topnav__icon-btn--with-badge { position: relative; }
.topnav__icon-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-danger);
  color: var(--color-text-inverse);
  font-size: 10px;
  font-weight: var(--fw-bold);
  border-radius: 9px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border: 2px solid var(--color-primary);  /* matches topnav bg so the badge "pops" */
  line-height: 1;
}

.topnav__cluster {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-left: auto;
}

.topnav__icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-md);
  color: var(--color-text-inverse);
  transition: background 0.15s;
}
.topnav__icon-btn:hover {
  background: rgba(255, 255, 255, 0.08);
}
.topnav__icon-btn svg { display: block; }

/* Account menu — the [name ▾] button + its dropdown. The button lives on the navy nav
 * (inverse text); the dropdown is a white popover (surface tokens) anchored below-right. */
.topnav__account {
  position: relative;
  padding-left: var(--space-3);
  border-left: 1px solid rgba(255, 255, 255, 0.12);
}
.topnav__account-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2) var(--space-1) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--color-text-inverse);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  transition: background 0.15s;
}
.topnav__account-btn:hover,
.topnav__account-btn[aria-expanded="true"] {
  background: rgba(255, 255, 255, 0.12);
}
.topnav__account-caret {
  transition: transform 0.15s;
}
.topnav__account-btn[aria-expanded="true"] .topnav__account-caret {
  transform: rotate(180deg);
}
.topnav__account-menu {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  min-width: 210px;
  padding: var(--space-1);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  z-index: 100;
}
.topnav__account-menu[hidden] {
  display: none;
}
.topnav__account-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-2) var(--space-3) var(--space-3);
  margin-bottom: var(--space-1);
  border-bottom: 1px solid var(--color-border-subtle);
}
.topnav__account-fullname {
  font-size: var(--fs-body);
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
}
.topnav__account-role {
  font-size: var(--fs-caption);
  color: var(--color-text-secondary);
}
.topnav__account-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--fs-body);
  font-weight: var(--fw-regular);
  color: var(--color-text-primary);
  white-space: nowrap;
  transition: background 0.12s;
}
.topnav__account-item:hover,
.topnav__account-item:focus-visible {
  background: var(--color-bg-hover);
}
.topnav__account-item--danger {
  color: var(--color-danger);
}
.topnav__account-sep {
  height: 1px;
  margin: var(--space-1) var(--space-2);
  background: var(--color-border-subtle);
}
.topnav__account-logout {
  margin: 0;
}

/* ─── Buttons (frontend §3) ──────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: 40px;
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--fs-body);
  font-weight: var(--fw-medium);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s, box-shadow 0.15s;
  border: 1px solid transparent;
  white-space: nowrap;
}
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }

.btn-primary {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}
.btn-primary:hover { background: var(--color-primary-hover); text-decoration: none; }

.btn-secondary {
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
  border-color: var(--color-border);
}
.btn-secondary:hover { background: var(--color-bg-base); text-decoration: none; }

.btn-ghost {
  background: transparent;
  color: var(--color-text-secondary);
}
.btn-ghost:hover { background: var(--color-bg-base); color: var(--color-text-primary); text-decoration: none; }
/* Compact 28px-tall ghost — used for inline tool buttons (e.g. the sketch
 * crop / reset controls). Pairs naturally with `.btn-ghost--danger`. */
.btn-ghost--sm {
  height: 28px;
  padding: 0 10px;
  font-size: 12px;
  border-radius: var(--radius-sm);
}

/* Red ghost — destructive actions (delete order, etc.). Not aggressive
 * by default, but turns clearly red on hover to signal danger. */
.btn-ghost--danger { color: var(--color-danger); }
.btn-ghost--danger:hover {
  background: rgba(179, 38, 30, 0.08);
  color: var(--color-danger);
}

.btn--block { width: 100%; }

/* ─── Cards ──────────────────────────────────────────────────────── */

.card {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-6);
}

/* ─── Forms ──────────────────────────────────────────────────────── */

.form-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
/* Between-field spacing comes from the container gap (.form-row / .form-stack, components.css),
 * NOT a sibling margin. The legacy `.form-field + .form-field { margin-top }` rule was removed
 * here (ADR/CLAUDE convention: form rhythm via grid/flex gap + tokens). */

.form-label {
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
}

.form-input {
  width: 100%;
  height: 40px;
  padding: 0 var(--space-3);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text-primary);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.form-input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(31, 58, 95, 0.12);
}
.form-input.has-error {
  border-color: var(--color-danger);
}

.form-help {
  font-size: var(--fs-meta);
  color: var(--color-text-muted);
}

.form-error {
  font-size: var(--fs-meta);
  color: var(--color-danger);
}

/* ─── Inline alerts (frontend §3) ────────────────────────────────── */

.alert {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--fs-body);
  border: 1px solid transparent;
  /* Force breathing room before whatever follows (page header, table,
   * form). Per-page templates were inconsistently adding bottom
   * spacing, so the alert sometimes sat flush against the next H1. */
  margin-bottom: var(--space-6);
}
.alert-danger {
  background: var(--color-bg-danger-tint);
  color: var(--color-danger);
  border-color: rgba(179, 38, 30, 0.2);
}
.alert-info {
  background: rgba(47, 111, 179, 0.06);
  color: var(--color-info);
  border-color: rgba(47, 111, 179, 0.18);
}

/* ─── Auth pages (login 1.1, password change 1.2 — frontend §5.1, §5.2) ── */

.auth-page {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: var(--color-bg-base);
  padding: var(--space-8);
}

/* When TopNav is present (1.2 password change), reduce top of auth-page so card sits naturally */
.topnav + .page .auth-page { min-height: calc(100vh - var(--layout-topnav-height)); }

.auth-card {
  width: 380px;                    /* frontend §5.1 — 카드 폭 380px */
  max-width: 100%;
  padding: var(--space-8);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

.auth-logo {
  text-align: center;
  margin-bottom: var(--space-8);
}
.auth-logo__wordmark {
  font-size: var(--fs-h1);
  font-weight: var(--fw-bold);
  color: var(--color-primary);
  letter-spacing: -0.01em;
}

.auth-title {
  text-align: center;
  margin-bottom: var(--space-2);
}

.auth-subtitle {
  text-align: center;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-6);
}

.auth-form { display: flex; flex-direction: column; gap: 0; }

.auth-actions { margin-top: var(--space-6); }

.auth-footer {
  margin-top: var(--space-6);
  text-align: center;
  font-size: var(--fs-meta);
  color: var(--color-text-muted);
}

/* ─── Empty state ────────────────────────────────────────────────── */

.empty-state {
  margin: var(--space-12) auto;
  text-align: center;
  color: var(--color-text-muted);
  padding: var(--space-8);
  background: var(--color-bg-surface);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  max-width: 600px;
}
.empty-state p + p { margin-top: var(--space-2); }

/* ─── Page header (h1 + meta on the right) ───────────────────────── */

.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}
/* Cap the header to a record view's width so its back button lines up with the record-form's right
 * edge below it (record-form / create pages). Full-width grid/detail pages omit this so the header
 * spans the same width as their full-width content. */
.page-header--capped {
  max-width: var(--record-width);
}
/* Right-side control cluster — keeps the header's buttons together on the right (the ← back button
 * is last = rightmost) instead of space-between spreading a lone middle button. */
.page-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 1 auto;
  /* WRAPS. A document toolbar can now hold six controls (승인·반려·미결로·저장·삭제·목록) and at 375px
   * a rigid `flex: 0 0 auto` row pushed straight through the page edge. Wrapping costs a second line
   * only on a phone, where there is vertical room; horizontal page scroll is never acceptable. */
  flex-wrap: wrap;
  justify-content: flex-end;
  row-gap: var(--space-1);
}
/* Soft-delete toggle form sitting in the header's action row — no default form margin. */
.page-header__toggle {
  margin: 0;
}
/* 결재 (승인/반려/미결로) as ONE cluster in the toolbar rather than a band of its own below it —
 * three small buttons were costing a 28px row plus a 24px gap next to a status badge that already
 * says the same thing. The trailing gap keeps them off the navigation buttons. */
/* A blocked destructive action: the wrapper is what the pointer hits, because a disabled <button>
 * dispatches no mouse events (so a tooltip on the button itself would never fire). */
.page-header__blocked { display: inline-flex; }
.page-header__blocked > .btn[disabled] { pointer-events: none; }
.page-header__approval {
  display: flex;
  margin: 0;
  gap: var(--space-1);
  margin-right: var(--space-2);
}
.page-header__approval-choice {
  position: relative;
}
.page-header__approval-choice input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.page-header__approval-choice.is-selected,
.page-header__approval-choice:has(input:checked) {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-text-inverse);
}
/* Merged toolbar (ADR 0045): title (the H1) + optional subtitle, inline on the left of the header
 * row. The title is compact (h3-sized, not a 28px h1) so the whole toolbar is one dense line; the
 * subtitle is muted context. min-width:0 lets a long title truncate instead of pushing the page
 * wider. (The 관리 콘솔 context comes from the sidebar brand, so there's no breadcrumb prefix.) */
.page-header__heading {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  flex: 1 1 auto;
}
.page-header__title {
  margin: 0;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  line-height: var(--lh-heading);
  color: var(--color-text-primary);
}
.page-header__sub {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-meta);
  color: var(--color-text-muted);
}
.context-strip__link {
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ─── Dashboard (1.3 — frontend §5.3) ────────────────────────────── */

.dashboard-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
  margin-top: var(--space-6);
}

.dashboard-card {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-6);
  min-height: 140px;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* Linkable variant — when the whole card is an <a> wrapping content */
a.dashboard-card,
.dashboard-card--linkable {
  text-decoration: none;
  color: inherit;
  transition: border-color 0.1s, background 0.1s;
}
a.dashboard-card:hover,
.dashboard-card--linkable:hover {
  border-color: var(--color-primary);
  background: var(--color-bg-base);
  text-decoration: none;
}

.dashboard-card__title {
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
}

.dashboard-card__stat {
  font-size: 32px;
  font-weight: var(--fw-bold);
  line-height: 1;
  color: var(--color-text-primary);
}
.dashboard-card__stat--muted { color: var(--color-text-muted); }

.dashboard-card__list,
.dashboard-card__text {
  font-size: var(--fs-body);
  color: var(--color-text-primary);
}
.dashboard-card__text--muted { color: var(--color-text-muted); }

.dashboard-card__list > * + * { margin-top: var(--space-1); }

.dashboard-card__meta {
  font-size: var(--fs-meta);
  color: var(--color-text-muted);
  margin-top: auto;
}

/* Activity card — spans 2 columns of the 3-column dashboard grid so the
 * cross-order activity stream has room for timestamp + summary on each
 * row without truncating. Drops to 1 column on narrow viewports. */
.dashboard-card--span-2 { grid-column: span 2; }
@media (max-width: 900px) {
  .dashboard-card--span-2 { grid-column: span 1; }
}

/* Each activity row inside the dashboard card — clickable, hovers,
 * styled as a denser variant of the 2.2-H activity-card pattern. */
.dashboard-activity {
  display: flex;
  flex-direction: column;
  margin-top: var(--space-2);
  /* Inset rows to the card's edge by undoing the card's horizontal padding */
  margin-left: calc(-1 * var(--space-6));
  margin-right: calc(-1 * var(--space-6));
}
.dashboard-activity__row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-3) var(--space-6);
  border-top: 1px solid var(--color-border);
  text-decoration: none;
  color: inherit;
  transition: background 0.1s;
}
.dashboard-activity__row:hover {
  background: var(--color-bg-base);
  text-decoration: none;
}
.dashboard-activity__row:first-child {
  border-top: none;
}
.dashboard-activity__avatar { flex-shrink: 0; }
.dashboard-activity__body { min-width: 0; }
.dashboard-activity__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 13px;
  margin-bottom: 2px;
}
.dashboard-activity__title strong { color: var(--color-text-primary); }
.dashboard-activity__meta {
  font-size: 11px;
  color: var(--color-text-secondary);
}
.dashboard-activity__time {
  font-size: 11px;
  color: var(--color-text-secondary);
  white-space: nowrap;
  flex-shrink: 0;
}

/* ─── Filter bar (used on 1.6, 2.1, 3.1, 4.1 — frontend §3) ──────── */

.filter-bar {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  /* nowrap (NOT wrap): the whole bar must stay on ONE row. The search
   * input shrinks (flex 0 1 220px → 180px min) and the action cluster is
   * pinned right with flex-shrink:0, so everything fits at the locked
   * 1280px width. With wrap, sub-pixel width rounding differs per browser
   * (Chrome wrapped the 검색/기본 필터 buttons to a 2nd row, Safari didn't). */
  flex-wrap: nowrap;
  margin-top: var(--space-4);
  padding: var(--space-3);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  /* IMPORTANT: do NOT set overflow on this element. Chip filter
   * dropdowns use position:absolute and need to extend below the bar
   * — any overflow rule here clips them (same bug family as the
   * chip-widget clip on 4.2 earlier). */
}

/* Pin the 검색 + 초기화 action group to the far right of the filter bar.
 * margin-left: auto eats the remaining horizontal space, pushing the
 * group to the right edge regardless of how many filter dropdowns are
 * shown. Stays consistent as the user toggles filter visibility on/off
 * via the profile prefs. */
.filter-bar__actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  margin-left: auto;
  /* With .filter-bar { flex-wrap: nowrap }, the action cluster must
   * never shrink — otherwise the buttons would compress their labels
   * before the chips do. Pin it to its natural width. */
  flex-shrink: 0;
}

.filter-bar__search {
  /* 2026-05-25: was `flex: 1` — caused the search input to greedily
   * absorb leftover row space whenever the action buttons wrapped
   * to a second row, ending up "enormous" (manager feedback).
   * Trimmed to 220px basis and allowed shrinking to 180px when the
   * row is tight. Combined with .filter-bar { flex-wrap: nowrap },
   * this keeps the entire bar on a single row. */
  flex: 0 1 220px;
  min-width: 180px;
  height: 36px;
  padding: 0 var(--space-3);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font: inherit;
}
.filter-bar__search:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(31, 58, 95, 0.12);
}

.filter-bar__select {
  height: 36px;
  padding: 0 var(--space-3);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font: inherit;
  cursor: pointer;
}

/* ─── Filter chips (used on 1.4, 1.7, 2.1) ───────────────────────── */

.filter-chips {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-4);
  flex-wrap: wrap;
}

.filter-chip {
  padding: var(--space-2) var(--space-4);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.filter-chip:hover { background: var(--color-bg-base); }
.filter-chip.is-active {
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border-color: var(--color-primary);
}

/* ─── Data tables (used on 1.6, 2.1, 2.2-H, 3.1, 4.x — frontend §2.4) ── */

.data-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  margin-top: var(--space-4);
}
.data-table thead { background: var(--color-bg-base); }
.data-table th {
  text-align: left;
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}
.data-table td {
  font-size: var(--fs-table);
  line-height: var(--lh-table);
  color: var(--color-text-primary);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-border);
  height: 40px;     /* default row height per §2.4 */
  vertical-align: middle;
}
.data-table tbody tr:hover { background: var(--color-bg-base); }
.data-table tbody tr:last-child td { border-bottom: none; }
/* Whole-row click cursor for the order list (spec §6 row-click → 2.2).
   Navigation itself is handled in order_list.js — modifier-aware so
   Cmd/Ctrl+click and middle-click open the order in a new tab. The
   item-name cell additionally holds a real <a.row-link> for native
   keyboard / right-click "open in new tab" on the link text itself. */
.data-table__row--clickable { cursor: pointer; }
.data-table__row--clickable td.col-checkbox { cursor: default; }

.empty-state-table {
  text-align: center;
  padding: var(--space-12) var(--space-6);
  color: var(--color-text-muted);
}

/* ─── Avatar initials (table cells, profile) ─────────────────────── */

.avatar-initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  margin-right: var(--space-2);
  border-radius: 50%;
  background: var(--color-primary);
  color: var(--color-text-inverse);
  font-size: 11px;
  font-weight: var(--fw-medium);
  vertical-align: middle;
}

/* ─── Pagination (frontend §3 + backend §7.1) ────────────────────── */

.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  flex-wrap: wrap;
}

.pagination__total {
  font-size: var(--fs-meta);
  color: var(--color-text-secondary);
}

.pagination__pages {
  display: flex;
  gap: var(--space-1);
  align-items: center;
}

.pagination__btn,
.pagination__page {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  text-decoration: none;
  transition: background 0.15s;
}
.pagination__btn:hover,
.pagination__page:hover {
  background: var(--color-bg-base);
  text-decoration: none;
}
.pagination__page.is-active {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}

.pagination__size {
  display: flex;
  gap: var(--space-1);
  align-items: center;
  font-size: var(--fs-meta);
  color: var(--color-text-secondary);
}
.pagination__size-option {
  padding: 0 var(--space-2);
  border-radius: var(--radius-xs);
  color: var(--color-text-secondary);
  text-decoration: none;
  height: 24px;
  display: inline-flex;
  align-items: center;
}
.pagination__size-option:hover {
  background: var(--color-bg-base);
  color: var(--color-text-primary);
  text-decoration: none;
}
.pagination__size-option.is-active {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}

/* ─── Profile (1.5) ──────────────────────────────────────────────── */

.profile-grid {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: var(--space-6);
  margin-top: var(--space-6);
}

.profile-info {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-4);
}

.profile-avatar {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--color-primary);
  color: var(--color-text-inverse);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  flex-shrink: 0;
}

.profile-name__main {
  font-size: var(--fs-h3);
  font-weight: var(--fw-medium);
}
.profile-name__sub {
  font-size: var(--fs-meta);
  color: var(--color-text-secondary);
  margin-top: var(--space-1);
}

.profile-details {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-6);
  padding-top: var(--space-6);
  border-top: 1px solid var(--color-border);
}
.profile-details dt {
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
}
.profile-details dd {
  font-size: var(--fs-body);
  margin: 0;
}

/* ─── Profile · 화면 환경설정 (display preferences) ────────────────
 * Two side-by-side cards below the identity grid — left card configures
 * the order list (2.1) filter bar, right card configures the 꼬마딱지
 * modal (2.1-P) sort buttons. Each card is self-contained with a polished
 * header (icon-style accent + title + intro), a clean toggle list, and a
 * save button anchored to the card bottom.
 *
 * IMPORTANT: nginx serves CSS from a host bind-mount populated by
 * `manage.py collectstatic`. After ANY git pull on the VM, run:
 *   docker compose -f docker-compose.prod.yml run --rm web \
 *     python manage.py collectstatic --noinput
 * Without this, browsers see the OLD CSS and toggles render as default
 * checkboxes. The deploy.sh script handles this automatically (step 6).
 */
/* Prefs grid sits directly inside .container, same horizontal extent
 * as the identity grid above it. No wrapper background — the cards
 * themselves carry the visual weight, and the accent bars on each
 * card distinguish them as a separate concern from 내 정보 above. */
.prefs-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
  margin-top: var(--space-6);
  align-items: start;
}
@media (max-width: 900px) {
  .prefs-grid { grid-template-columns: 1fr; }
}

/* Each card has stacked sections: header / list / footer. The card
 * itself comes from the global .card style (padding, shadow, etc.). */
.prefs-card {
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}
/* Accent bar at the top — visual "ID badge" so the two cards are
 * instantly distinguishable from the surrounding identity cards. */
.prefs-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--color-primary) 0%, #4a6da3 100%);
}
.prefs-card__header {
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-4);
}
.prefs-card__header .heading-h3 {
  margin: 0;
}
.prefs-card__intro {
  margin: var(--space-2) 0 0 0;
  font-size: 13px;
  color: var(--color-text-secondary);
  line-height: 1.55;
}

/* Each card may contain MULTIPLE prefs-section blocks (e.g. "Filter bar"
 * + "Table columns"). Sections are separated by 32px of breathing room
 * with a clearly-spaced subheading above each list. The previous version
 * referenced --space-5 / --space-7 which don't exist in tokens.css, so
 * the spacing collapsed to zero — this round uses real tokens (6=24px,
 * 8=32px) to keep the rhythm intact. */
.prefs-section + .prefs-section {
  margin-top: var(--space-8);  /* 32px gap between sections */
}
.prefs-section__title {
  margin: 0 0 var(--space-3) 0;  /* 12px below title, before list */
  font-size: 12px;
  font-weight: 700;
  color: var(--color-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* Toggle-row list. Bordered, rounded, with dividers + hover state. */
.prefs-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  overflow: hidden;
  background: var(--color-bg-surface);
}
.prefs-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 14px 18px;
  border-bottom: 1px solid var(--color-border);
  transition: background 0.12s ease;
}
.prefs-row:last-child { border-bottom: none; }
.prefs-row:hover     { background: var(--color-bg-base); }
.prefs-row__label {
  font-size: 14px;
  color: var(--color-text-primary);
  font-weight: var(--fw-medium, 500);
  flex: 1;
  cursor: pointer;
}

/* iOS-style toggle switch — bigger, softer, with a smooth thumb glide.
 * Independent from the global `.toggle` so we don't disturb other
 * consumers (the 꼬마딱지 modal toggles stay compact at 36×20). */
.prefs-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  user-select: none;
  flex-shrink: 0;
}
.prefs-switch input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 0;
  height: 0;
}
.prefs-switch__track {
  position: relative;
  width: 46px;
  height: 26px;
  border-radius: 999px;
  background: #d6d6d6;
  transition: background 0.2s ease;
  flex-shrink: 0;
}
.prefs-switch__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.06);
  transition: left 0.2s cubic-bezier(0.3, 1.2, 0.7, 1);
}
.prefs-switch input:checked + .prefs-switch__track {
  background: var(--color-primary);
}
.prefs-switch input:checked + .prefs-switch__track .prefs-switch__thumb {
  left: 22px;
}
.prefs-switch input:focus-visible + .prefs-switch__track {
  box-shadow: 0 0 0 3px rgba(26, 46, 79, 0.18);
}

/* Footer pin — separated by a top border so the save button is its own
 * action zone, anchored to card bottom for consistent height. */
.prefs-card__footer {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}

/* ─── TopNav: sub-link, active states for icon buttons + user name ─ */

.topnav__sub-link {
  color: rgba(255, 255, 255, 0.7);
  font-size: var(--fs-meta);
  text-decoration: none;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.topnav__sub-link:hover {
  color: var(--color-text-inverse);
  background: rgba(255, 255, 255, 0.08);
  text-decoration: none;
}

.topnav__icon-btn.is-active {
  background: rgba(255, 255, 255, 0.14);
}

.topnav__user-name {
  text-decoration: none;
}
.topnav__user-name:hover {
  text-decoration: underline;
}
.topnav__user-name.is-active {
  text-decoration: underline;
}

/* ─── Status badges (frontend §3) ────────────────────────────────── */

.status-badge {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-2);
  border-radius: var(--radius-xs);
  font-size: 11px;
  font-weight: var(--fw-medium);
  white-space: nowrap;
  line-height: 1.4;
}

/* Order status (in_progress / done) */
.status-badge--in_progress { background: rgba(47, 111, 179, 0.12); color: var(--color-info); }
.status-badge--done        { background: rgba(46, 125, 87, 0.12);  color: var(--color-success); }

/* Generic semantic variants */
.status-badge--info     { background: rgba(47, 111, 179, 0.12); color: var(--color-info); }
.status-badge--success  { background: rgba(46, 125, 87, 0.12);  color: var(--color-success); }
.status-badge--warning  { background: rgba(194, 133, 12, 0.14); color: var(--color-warning); }
.status-badge--danger   { background: rgba(179, 38, 30, 0.12);  color: var(--color-danger); }
.status-badge--muted    { background: var(--color-bg-base);     color: var(--color-text-muted); }

/* Spacing between two adjacent badges in the same cell */
.status-badge + .status-badge { margin-left: var(--space-1); }

/* ─── Chip-dropdown multi-select filter (Batch D.1, 2026-05-13) ───────
 * Replaces native <select multiple> for filter-bar pickers on screen 2.1.
 * The hidden native <select> is the data source — these styles dress
 * the chip widget JS builds around it.
 *
 * Visual logic:
 *  - Trigger button uses the same height/border as filter-bar__select
 *    so the row of filters stays uniform.
 *  - Chips inside the trigger render as small rounded rectangles —
 *    same shape language as status-badge.
 *  - Dropdown panel sits below the trigger with a subtle shadow so
 *    it doesn't feel like a modal — it's an inline picker.
 */
.chip-filter__source--hidden,
/* Pre-hide the raw <select multiple> BEFORE chip_filter.js enhances it, so
 * the un-styled native list boxes never flash on load. Scoped to `.js`
 * (set synchronously in <head>) so a no-JS client still gets a usable
 * native multi-select. Same visually-hidden technique as `--hidden`. */
.js .chip-filter__source {
  /* Visually hidden but still in the form so submission picks up the
   * `selected` options. Hiding via display:none would exclude it. */
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.chip-filter {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  /* Pattern B (count-only): triggers stay narrow regardless of how many
   * values are selected, so the filter bar fits on one row. The chip
   * styles further down are dormant — flip back to Pattern A by
   * restoring the chipsBox rendering in chip_filter.js. */
  min-width: 110px;
}

.chip-filter__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-height: 34px;
  padding: 4px 10px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 13px;
  color: var(--color-text-primary);
  text-align: left;
  transition: border-color 0.12s, box-shadow 0.12s;
}
/* Dense-row modifier — matches form-input--sm (30px) so the chip trigger
 * lines up with the other dense fields in the order-detail basic-info
 * grid. Propagated by chip_filter.js when the source <select> carries
 * `form-input--sm`. */
.chip-filter--sm .chip-filter__trigger {
  min-height: 30px;
  padding: 2px 10px;
  font-size: var(--fs-body);
}
.chip-filter__trigger:hover {
  border-color: var(--color-text-muted);
}
.chip-filter[data-open=true] .chip-filter__trigger {
  border-color: var(--color-info);
  box-shadow: 0 0 0 2px rgba(47, 111, 179, 0.15);
}

.chip-filter__chips {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
  min-width: 0;
}

.chip-filter__placeholder {
  color: var(--color-text-muted);
  font-size: 13px;
}

/* Filter chip — neutral, low-noise styling.
 * Status badges (status-badge--*) already use color to convey meaning
 * (샘플 amber, 진행중 blue, etc.). Filter chips must NOT compete with
 * those — they signal "selected" via shape + dismiss affordance, not
 * color. This is the consistent pattern across Material 3, GitHub
 * Primer, Linear, IBM Carbon, and Notion. */
.chip-filter__chip {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px 4px 2px 8px;
  background: var(--color-bg-base);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  font-size: 12px;
  font-weight: var(--fw-medium);
  white-space: nowrap;
  line-height: 1.4;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chip-filter__chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  color: var(--color-text-muted);
  cursor: pointer;
  font-size: 11px;
  line-height: 1;
  border-radius: 50%;
  transition: background 0.12s, color 0.12s;
}
.chip-filter__chip-remove:hover {
  background: rgba(0, 0, 0, 0.06);
  color: var(--color-text-primary);
}

/* "+N" overflow indicator inside the trigger — shows how many
 * selections are not visible as chips. Not a button; clicking the
 * trigger (which contains it) opens the dropdown. Tooltip lists the
 * hidden names so the user can preview without opening. */
.chip-filter__overflow {
  display: inline-flex;
  align-items: center;
  padding: 2px 6px;
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
  background: var(--color-bg-base);
  border-radius: var(--radius-xs);
  white-space: nowrap;
  cursor: default;
}

.chip-filter__caret {
  color: var(--color-text-muted);
  font-size: 12px;
  flex-shrink: 0;
}

.chip-filter__panel {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 20;
  min-width: 240px;
  max-width: 360px;
  max-height: 360px;
  display: flex;
  flex-direction: column;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
  overflow: hidden;
}
/* The HTML `hidden` attribute would normally hide the panel, but our
 * own `display: flex` above outranks the browser default. This selector
 * has higher specificity and brings the hide-by-default back. */
.chip-filter__panel[hidden] {
  display: none;
}

.chip-filter__search {
  margin: 8px;
  padding: 6px 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  font-size: 13px;
}

.chip-filter__options {
  list-style: none;
  margin: 0;
  padding: 4px 0;
  overflow-y: auto;
  flex: 1;
}

/* Section header inside a grouped chip dropdown (팀 / 직원 on the order
   담당 picker). Sticky so the group stays labeled while scrolling. */
.chip-filter__group {
  position: sticky;
  top: 0;
  background: #f0f2f5;
  color: #5b6770;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 4px 10px;
  border-top: 1px solid #e3e6ea;
  border-bottom: 1px solid #e3e6ea;
}
.chip-filter__group:first-child {
  border-top: 0;
}
.chip-filter__option {
  padding: 0;
}
.chip-filter__option label {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  cursor: pointer;
  font-size: 13px;
  color: var(--color-text-primary);
}
.chip-filter__option label:hover {
  background: var(--color-bg-base);
}
.chip-filter__option input[type=checkbox] {
  margin: 0;
}

.chip-filter__footer {
  border-top: 1px solid var(--color-border);
  padding: 6px 8px;
  text-align: right;
}
.chip-filter__clear {
  background: none;
  border: 0;
  padding: 4px 8px;
  font-size: 12px;
  color: var(--color-text-muted);
  cursor: pointer;
  border-radius: var(--radius-xs);
}
.chip-filter__clear:hover {
  background: var(--color-bg-base);
  color: var(--color-text-primary);
}


/* Order type — 샘플 vs 본생산 column on screen 2.1.
 * Same chip shape as status-badge but tuned colors so the two badge
 * families don't look identical when rendered next to each other.
 * Sample = soft amber (it's the exception, draws the eye); 본생산 =
 * neutral muted (the default, recedes). */
.order-type-badge {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-2);
  border-radius: var(--radius-xs);
  font-size: 11px;
  font-weight: var(--fw-medium);
  white-space: nowrap;
  line-height: 1.4;
}
.order-type-badge--sample { background: rgba(194, 133, 12, 0.14); color: var(--color-warning); }
.order-type-badge--normal { background: var(--color-bg-base);     color: var(--color-text-muted); }

/* Team cell on the orders list (2.1) — first team name + small "+N"
 * pill when the order has more than one team. CSS tooltip via
 * `data-tooltip` attribute shows the full comma-joined team list
 * INSTANTLY on hover (native `title=` has a ~1.5s browser delay).
 * The native title is also set for keyboard / screen-reader access. */
.team-cell {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
/* Only show the help cursor when there's actually a tooltip to reveal —
 * i.e. multi-team rows, which carry data-tooltip. A single-team cell has
 * nothing to pop up, so the help cursor (the arrow + "?") was misleading:
 * you hover, see the "?", but no list appears. Flagged by Julian
 * 2026-06-10. The tooltip ::after below is already gated to [data-tooltip],
 * so now the cursor and the popup appear together or not at all. */
.team-cell[data-tooltip] {
  cursor: help;
}
.team-cell__primary {
  white-space: nowrap;
}
.team-cell__extra {
  display: inline-flex;
  align-items: center;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--color-bg-base);
  color: var(--color-text-muted);
  font-size: 11px;
  font-weight: var(--fw-medium);
  line-height: 1.4;
}
/* Instant CSS tooltip — fires on `:hover` with zero delay. Medium-grey
 * background (not the near-black --color-text-primary) so it reads as
 * a soft tooltip rather than a heavy modal-style chip.
 *
 * Same rule applies to `.alias-tooltip` wrapper on 4.2 (aliases input)
 * — both surfaces use a wrapper span with `data-tooltip` and the
 * pseudo-element bubble. <input> elements can't host ::after directly
 * (HTML replaced elements have no pseudo-elements), hence the wrapper. */
.team-cell[data-tooltip]:hover::after,
.alias-tooltip[data-tooltip]:hover::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 4px);
  left: 0;
  background: var(--color-text-secondary);
  color: #fff;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: var(--fw-regular);
  /* Wrap long lists (many teams + 직원) into a constrained block instead of
     running off the right screen edge — the 팀 column sits far right. */
  white-space: normal;
  line-height: 1.35;
  z-index: 10;
  pointer-events: none;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
  width: max-content;
  max-width: 320px;
}
/* The 팀 column is near the right edge, so anchor its tooltip to the cell's
   right and let it grow leftward (keeps it on-screen). */
.team-cell[data-tooltip]:hover::after {
  left: auto;
  right: 0;
}
.alias-tooltip {
  position: relative;
  display: inline-block;
  width: 100%;
}

/* Editable variant — used as the order_status dropdown on screen 2.2.
 * Looks like a chip, behaves like a <select>: small caret on hover,
 * focus ring matches the rest of the form inputs. The underlying
 * `<select>` keeps its color from `.status-badge--<value>`. */
.status-badge--editable {
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid transparent;
  cursor: pointer;
  padding-right: 22px;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(-45deg, transparent 50%, currentColor 50%);
  background-position:
    calc(100% - 12px) 55%,
    calc(100% - 8px)  55%;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
  font-family: inherit;
  font-size: 11px;
  font-weight: var(--fw-medium);
  line-height: 1.4;
  height: auto;
}
.status-badge--editable:hover {
  border-color: rgba(0, 0, 0, 0.10);
}
.status-badge--editable:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(31, 58, 95, 0.15);
}
.order-status-form .bom-saved-badge {
  margin-left: 6px;
  vertical-align: middle;
}

/* ─── Read-only mode for orders the user can't edit ───────────────────
 * The basic-info card and BOM container both get `data-readonly="true"`
 * when `can_edit` is False (cross-team designer / team_lead). Inputs
 * are visually muted AND disabled via pointer-events so accidental
 * clicks don't fire the auto-save XHRs (which would 403 anyway after
 * the server-side guard, but no point firing them).
 */
[data-readonly="true"] .bom-cell-input,
[data-readonly="true"] .bom-stack-input,
[data-readonly="true"] .bom-memo__input,
[data-readonly="true"] input[type="text"],
[data-readonly="true"] input[type="number"],
[data-readonly="true"] input[type="date"],
[data-readonly="true"] textarea,
[data-readonly="true"] select {
  pointer-events: none;
  background: var(--color-bg-base);
  color: var(--color-text-secondary);
  cursor: not-allowed;
}
/* Red asterisk used to mark required fields on admin forms. */
.form-required-mark {
  color: var(--color-danger);
  margin-left: 2px;
  font-weight: var(--fw-medium);
}

/* ─── Formula help (?) on computed BOM column headers ───────────────
 * Small circle next to a column label; hover shows the formula in a
 * dark bubble (CSS) plus the browser-native tooltip from `title`. */
.bom-formula-help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-left: 4px;
  border: 1px solid var(--color-text-secondary);
  border-radius: 50%;
  font-size: 9px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  cursor: help;
  background: transparent;
  vertical-align: middle;
  position: relative;
}
.bom-formula-help:hover {
  color: var(--color-bg-surface);
  background: var(--color-text-secondary);
}
.bom-formula-help:hover,
.bom-formula-help:focus {
  z-index: 200;
}
.bom-formula-help:hover::after,
.bom-formula-help:focus::after {
  content: attr(title);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  padding: 10px 14px;
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border);
  font-size: 13px;
  font-weight: var(--fw-medium);
  white-space: nowrap;
  border-radius: var(--radius-sm);
  box-shadow: 0 4px 12px rgba(0,0,0,0.10), 0 1px 3px rgba(0,0,0,0.08);
  z-index: 200;
  pointer-events: none;
}
/* Small caret pointing down at the icon. Same surface color so it
 * looks like a continuation of the bubble. Border on two sides
 * matches the bubble's outline. */
.bom-formula-help:hover::before,
.bom-formula-help:focus::before {
  content: "";
  position: absolute;
  bottom: calc(100% + 2px);
  left: 50%;
  transform: translateX(-50%) rotate(45deg);
  width: 8px;
  height: 8px;
  background: var(--color-bg-surface);
  border-right: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
  z-index: 199;
  pointer-events: none;
}
/* The column header (.bom-table__h) and the BOM container have
 * `overflow: hidden` / `overflow-x: auto` to keep long labels and wide
 * tables in check — that also clips the absolute-positioned tooltip
 * pseudo-element above. Lift the clip ONLY while the help icon is the
 * hover/focus target via `:has()`. Modern Chrome/Safari/Firefox all
 * support this; Sheens' browser context is recent enough.
 */
.bom-table__h:has(.bom-formula-help:hover),
.bom-table__h:has(.bom-formula-help:focus) {
  overflow: visible;
  position: relative;
  z-index: 200;
}
.bom-table__head:has(.bom-formula-help:hover),
.bom-table__head:has(.bom-formula-help:focus) {
  overflow: visible;
}
.bom-subsection__body:has(.bom-formula-help:hover),
.bom-subsection__body:has(.bom-formula-help:focus) {
  overflow: visible;
}

[data-readonly="true"] button[type="submit"],
[data-readonly="true"] .bom-add-link,
[data-readonly="true"] .bom-mini-btn,
[data-readonly="true"] .bom-row-delete,
[data-readonly="true"] .bom-card-tab,
[data-readonly="true"] .bom-section-toggle-btn {
  pointer-events: none;
  opacity: 0.5;
}

/* ─── Order list table specifics (2.1 — frontend §5.9) ───────────── */

.data-table--orders .col-checkbox  { width: 40px; text-align: center; }
.data-table--orders .col-thumbnail { width: 64px; }

/* Post-demo (2026-05-25): 연도 / 시즌 columns added. Pin them to tight
 * widths — the values are short ("2026", "26FW") — and force the date
 * column to a single line. Without this, adding two new columns
 * squeezed the 업데이트 column so hard the date wrapped across 3
 * lines, which the manager (correctly) flagged as broken design. */
.data-table--orders .col-year     { width: 60px; white-space: nowrap; }
.data-table--orders .col-season   { width: 72px; white-space: nowrap; }
.data-table--orders .col-updated  { white-space: nowrap; }
/* Owner cell — Korean names should NEVER wrap mid-character. The default
 * cell behavior was breaking "이소리" into "이소 / 리" because the column
 * got squeezed by adding 연도+시즌. nowrap keeps the avatar + full name on
 * a single line. .col-owner__name wraps the text node so the avatar
 * (an inline-flex circle) and the name stay glued together. */
.data-table--orders .col-owner    { white-space: nowrap; }
.data-table--orders .col-owner__name { display: inline-block; vertical-align: middle; }
/* Brand cell — Korean brand names like "무신사 스탠다드" or "(미확인 브랜드)"
 * were wrapping across two lines, which hurts row scanability on the
 * 2.1 list. Keep them on one line. Staging review 2026-05-28. */
.data-table--orders .col-brand    { white-space: nowrap; }

.thumb {
  display: block;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-sm);
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  object-fit: cover;
}
.thumb--empty {
  background-image:
    linear-gradient(45deg, var(--color-border) 25%, transparent 25%),
    linear-gradient(-45deg, var(--color-border) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--color-border) 75%),
    linear-gradient(-45deg, transparent 75%, var(--color-border) 75%);
  background-size: 8px 8px;
  background-position: 0 0, 0 4px, 4px -4px, -4px 0;
  opacity: 0.5;
}

/* Batch L — spinning thumbnail for orders whose AI extraction is still
 * running. Replaces the empty checkered placeholder so admins can scan
 * the column and immediately spot pending work. Inherits the 40x40 box
 * size from `.thumb`; the spinner sits centered with a ring that
 * rotates 360 degrees once per ~0.9 seconds. */
.thumb--spinning {
  display: flex;
  align-items: center;
  justify-content: center;
}
.thumb--spinning::after {
  content: "";
  width: 20px;
  height: 20px;
  border: 2px solid var(--color-border);
  border-top-color: var(--color-info);
  border-radius: 50%;
  animation: thumb-spin 0.9s linear infinite;
}
@keyframes thumb-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  /* Honor system preference — kill the rotation but keep the ring as a
   * static "in progress" indicator. */
  .thumb--spinning::after { animation: none; }
}

.row-link {
  color: var(--color-text-primary);
  text-decoration: none;
}
.row-link:hover {
  color: var(--color-primary);
  text-decoration: underline;
}

input[type="checkbox"].row-checkbox,
input[type="checkbox"].select-all-checkbox {
  width: 16px;
  height: 16px;
  cursor: pointer;
  accent-color: var(--color-primary);
}

/* ─── Selection-mode banner (frontend §5.9) ──────────────────────── */

.selection-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  margin-top: var(--space-4);
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border-radius: var(--radius-md);
  flex-wrap: wrap;
}

.selection-banner__count {
  font-size: var(--fs-body);
  color: var(--color-text-inverse);
}
.selection-banner__count strong { font-weight: var(--fw-bold); }

.selection-banner__actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
/* Same `display:flex` outranks the HTML `hidden` attribute as the
 * chip-filter panel did — explicit override brings hide-by-default back. */
.selection-banner[hidden] {
  display: none;
}

/* Default-filter picker grid on the profile page (Batch D.2).
 * One row per filter dimension: a label above the chip-filter button.
 * Vertical stack on narrow screens; the chip widget itself handles
 * width sizing. */
.prefs-default-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-3);
}
.prefs-default-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.prefs-default-field__label {
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
}
/* Flat checkbox grid used on the profile page's 꼬마딱지 prefs card
 * for 표시 항목 + 옵션. No dropdowns — all options visible at once
 * because we have the vertical space here that the orders-page
 * filter bar doesn't. Auto-flows to multiple columns on wider
 * screens via auto-fill. */
.prefs-checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 8px 16px;
  margin-top: 4px;
}
.prefs-checkbox {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  font-size: 13px;
  color: var(--color-text-primary);
}
.prefs-checkbox input[type="checkbox"] {
  margin: 0;
}

/* Radio group for the single-choice 정렬 기준 default on the profile
 * 꼬마딱지 prefs card. Horizontal layout — wraps on narrow screens. */
.prefs-radio-group {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 4px;
}
.prefs-radio {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  font-size: 13px;
  color: var(--color-text-primary);
}
.prefs-radio input[type="radio"] {
  margin: 0;
}

/* Applied-filter pills — sit BELOW the filter bar (per manager request,
 * 2026-05-13). Each pill shows "<filter_label>: <value> ✕" and links
 * to a URL with just that value removed. Server-side rendered so it
 * works without JS.
 *
 * NOTE on the class name: we deliberately use `.applied-filter` (NOT
 * `.filter-chip`) because two unrelated pieces of the existing UI
 * already own `.filter-chip` further up + down in this stylesheet
 * (announcement filter pills around L616, profile pills around L4231).
 * Sharing the class would cascade-clobber these solid-navy styles. */
.applied-filters-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-top: var(--space-3);
}
/* Solid brand-navy pill — same dark blue as the top nav + the
 * selection banner so an "active filters" state feels visually
 * coherent across the page. White text for contrast. */
.applied-filter {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 6px 5px 12px;
  height: 28px;
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border: 0;
  border-radius: 999px;
  font-size: 12px;
  font-weight: var(--fw-medium);
  text-decoration: none;
  white-space: nowrap;
  transition: filter 0.12s, box-shadow 0.12s;
}
.applied-filter:hover {
  filter: brightness(1.18);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.applied-filter__label {
  color: var(--color-text-inverse);
  font-weight: var(--fw-normal);
  opacity: 0.7;
}
.applied-filter__x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  color: var(--color-text-inverse);
  font-size: 11px;
  line-height: 1;
  border-radius: 50%;
  opacity: 0.7;
  transition: opacity 0.12s, background 0.12s;
}
.applied-filter:hover .applied-filter__x {
  opacity: 0.95;
}
.applied-filter__x:hover {
  opacity: 1;
  background: rgba(255, 255, 255, 0.18);
}
/* "전체 지우기" — the destructive sibling. Transparent + red so it
 * reads as "clear EVERYTHING" rather than another applied filter. */
.applied-filter--clear {
  background: transparent;
  color: var(--color-text-muted);
  padding: 5px 14px;
  margin-left: 4px;
  height: 28px;
}
.applied-filter--clear:hover {
  background: rgba(179, 38, 30, 0.08);
  color: var(--color-danger);
  box-shadow: none;
  filter: none;
}

/* Buttons placed ON the navy banner — need inverted colors */
.btn--on-primary {
  background: var(--color-text-inverse);
  color: var(--color-primary);
  border-color: transparent;
}
.btn--on-primary:hover {
  background: rgba(255, 255, 255, 0.92);
  color: var(--color-primary-hover);
  text-decoration: none;
}
.btn--on-primary-ghost {
  background: rgba(255, 255, 255, 0.08);
  color: var(--color-text-inverse);
  border-color: transparent;
}
.btn--on-primary-ghost:hover {
  background: rgba(255, 255, 255, 0.16);
  color: var(--color-text-inverse);
  text-decoration: none;
}

/* ─── Breadcrumb ─────────────────────────────────────────────────── */

.breadcrumb {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-meta);
  color: var(--color-text-secondary);
  margin-bottom: var(--space-3);
  flex-wrap: wrap;
}
.breadcrumb a {
  color: var(--color-text-secondary);
  text-decoration: none;
}
.breadcrumb a:hover {
  color: var(--color-primary);
  text-decoration: underline;
}
.breadcrumb__sep {
  color: var(--color-text-muted);
}
.breadcrumb__current {
  color: var(--color-text-primary);
  font-weight: var(--fw-medium);
}

/* ─── Recent change summary row (2.2 — frontend §5.11) ───────────── */

.recent-change {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  margin-top: var(--space-4);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.recent-change--empty {
  justify-content: center;
}

.recent-change__avatar { flex-shrink: 0; }

.recent-change__body { flex: 1; min-width: 0; }
.recent-change__title { font-size: var(--fs-body); }
.recent-change__meta { margin-top: var(--space-1); }

.recent-change__action {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}

.avatar-initials--system {
  background: var(--color-info);
  font-size: 10px;
}

/* ─── Info grid (basic-info card on 2.2) ─────────────────────────── */

.info-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4) var(--space-6);
  margin: var(--space-4) 0 0;
}

.info-grid__item dt,
.info-grid__label {
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  margin: 0 0 var(--space-1);
}

.info-grid__item dd,
.info-grid__value {
  font-size: var(--fs-body);
  color: var(--color-text-primary);
  margin: 0;
}

/* ─── Timeline (2.2-H — frontend §5.12) ──────────────────────────── */

.timeline {
  margin-top: var(--space-4);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.timeline-row {
  display: grid;
  grid-template-columns: auto 140px 1fr auto;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  border-bottom: 1px solid var(--color-border);
}
.timeline-row:last-child { border-bottom: none; }

.timeline-row--agent {
  background: var(--color-bg-base);
}

.timeline-row__avatar { flex-shrink: 0; }
.timeline-row__time { color: var(--color-text-secondary); white-space: nowrap; }
.timeline-row__title { font-size: var(--fs-body); }
.timeline-row__email-meta { margin-top: var(--space-1); }

/* Field-level diff dropdown — collapsed by default, expands inline below
 * the row title to show every leaf change without dumping raw JSON. */
.timeline-row__diff {
  margin-top: var(--space-2);
  font-size: 12px;
}
.timeline-row__diff > summary {
  cursor: pointer;
  color: var(--color-text-secondary);
  user-select: none;
  padding: 2px 0;
  list-style: none;
}
.timeline-row__diff > summary::-webkit-details-marker { display: none; }
.timeline-row__diff > summary::before {
  content: "▸ ";
  display: inline-block;
  transition: transform 0.15s;
}
.timeline-row__diff[open] > summary::before {
  transform: rotate(90deg);
}
.timeline-row__diff > summary:hover {
  color: var(--color-text-primary);
}
.timeline-row__diff-table {
  margin-top: var(--space-2);
  border-collapse: collapse;
  font-size: 12px;
  width: 100%;
  max-width: 720px;
  /* Lock column widths so consecutive diff tables align column-for-column.
   * Without `table-layout: fixed`, each table's first column auto-sizes
   * to its content, causing rows with long field names ("협의 · 관리부
   * 담당자") to push their later columns farther right than rows with
   * short names ("협의 · 과장"). */
  table-layout: fixed;
}
.timeline-row__diff-table col.col-field { width: 45%; }
.timeline-row__diff-table col.col-old   { width: 25%; }
.timeline-row__diff-table col.col-arrow { width: 24px; }
.timeline-row__diff-table col.col-new   { width: auto; }
.timeline-row__diff-table th,
.timeline-row__diff-table td {
  padding: 4px 8px;
  border: 1px solid var(--color-border);
  vertical-align: top;
  word-break: break-word;
}
.timeline-row__diff-table th {
  background: var(--color-bg-base);
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  text-align: left;
}
.timeline-row__diff-table .diff-old {
  color: var(--color-text-secondary);
  text-decoration: line-through;
}
.timeline-row__diff-table .diff-arrow {
  color: var(--color-text-secondary);
  text-align: center;
  width: 1em;
}
.timeline-row__diff-table .diff-new {
  color: var(--color-success);
  font-weight: var(--fw-medium);
}

.timeline-row__source { white-space: nowrap; }

/* ─── Activity stream (2.2-H bundled change-history view) ─────────
 * Each card is a `<details>` element wrapping one editing session
 * (multiple OrderHistory entries grouped by user+source within 30 min).
 * Closed by default — user clicks the summary to expand and see the
 * leaf-level diff of every save in that session.
 *
 * Visual style: stacked cards with subtle borders, hover state, accent
 * color via left edge for AI / agent bundles so the source is scannable
 * without reading the chip. Compact summary line; expanded detail uses
 * the same diff-table styling as the legacy flat timeline.
 */
.activity-stream {
  margin-top: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.activity-card {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.activity-card:hover {
  border-color: var(--color-text-secondary);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.activity-card[open] {
  border-color: var(--color-primary);
}

/* Subtle source-tinted left edge so AI / agent activity stands out */
.activity-card--ai    { border-left: 3px solid var(--color-info, #3b82f6); }
.activity-card--agent { border-left: 3px solid var(--color-warning, #c08400); background: var(--color-bg-base); }

.activity-card__summary {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  user-select: none;
  list-style: none;
}
.activity-card__summary::-webkit-details-marker { display: none; }

.activity-card__avatar {
  flex-shrink: 0;
}

.activity-card__body {
  min-width: 0;  /* allow children to shrink in grid */
}

.activity-card__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: 2px;
}
.activity-card__title strong {
  font-size: var(--fs-body);
  color: var(--color-text-primary);
}

.activity-card__source-chip {
  font-size: 11px;
}

.activity-card__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--color-text-secondary);
}
.activity-card__sep {
  opacity: 0.5;
}
.activity-card__duration {
  color: var(--color-text-secondary);
}

.activity-card__fields {
  margin-top: 4px;
  font-size: 12px;
  color: var(--color-text-primary);
  font-weight: var(--fw-medium, 500);
}

.activity-card__chevron {
  color: var(--color-text-secondary);
  font-size: 14px;
  transition: transform 0.2s;
}
.activity-card[open] .activity-card__chevron {
  transform: rotate(180deg);
}

/* Expanded body — list of every individual save in this bundle, each
 * with its own field-level diff table. Pads beneath the summary so the
 * card still reads as one unit when open. */
.activity-card__detail {
  border-top: 1px solid var(--color-border);
  background: var(--color-bg-base);
  padding: var(--space-3) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.activity-detail-row {
  display: grid;
  grid-template-columns: 80px 1fr;
  gap: var(--space-3);
  align-items: start;
}
.activity-detail-row__time {
  color: var(--color-text-secondary);
  white-space: nowrap;
  padding-top: 4px;
}
.activity-detail-row__body {
  min-width: 0;
}

/* Agent avatar — tinted variant */
.avatar-initials--agent {
  background: var(--color-warning, #c08400);
  color: white;
}


/* Source chips — used on 2.2-H timeline rows */
.source-chip {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-2);
  border-radius: var(--radius-xs);
  font-size: 11px;
  font-weight: var(--fw-medium);
  background: var(--color-bg-base);
  color: var(--color-text-secondary);
  border: 1px solid var(--color-border);
  white-space: nowrap;
}
.source-chip--agent {
  background: rgba(47, 111, 179, 0.10);
  color: var(--color-info);
  border-color: rgba(47, 111, 179, 0.18);
}
.source-chip--ai_extraction {
  background: rgba(58, 124, 165, 0.10);
  color: var(--color-accent);
  border-color: rgba(58, 124, 165, 0.20);
}
.source-chip--manual_entry,
.source-chip--order_detail,
.source-chip--material_list {
  /* Default neutral chip — uses base styles above */
}
/* git-style change id — monospace, muted, the version-history "commit" tag. */
.source-chip--id {
  background: var(--color-bg-subtle, var(--color-bg-base));
  color: var(--color-text-tertiary, var(--color-text-secondary));
  letter-spacing: 0.02em;
}
/* "복원 → #id" marker on a restore card — points at the version restored to. */
.source-chip--restore-mark {
  background: rgba(47, 111, 179, 0.10);
  color: var(--color-info);
  border-color: rgba(47, 111, 179, 0.18);
}

/* ═════════════════════════════════════════════════════════════════
 * 2.2 발주 상세 — BOM editor
 * Frontend §5.11 + Figma frame 18:45.
 * Geometry below comes from Figma metadata (verified):
 *   page gutter 80px · sidebar 220px · main 1040px · gap 20px
 *   Section 1 card: padding 24px · sketch 300×340 · grid 3 col, 16px gap col, 12px row
 *   inputs 30px tall · BOM sub-card header 44px · 24px padding inside BOM container
 * ═════════════════════════════════════════════════════════════════ */

/* 2-col body: sidebar + main. 1280 = 220 + 20gap + 1040. */
/* ─── Sticky top panel (order detail 2.2 — ITEM 10) ──────────────── */
/* Pins the title bar (전체 저장 / Excel / Worksheet buttons), any
 * extraction alerts, and the recent-change row (전체 이력 → history link)
 * to the top while the BOM scrolls underneath. Offset by the global
 * sticky topnav height so it sits just below the navbar with no overlap.
 * Solid background + z-index below the navbar (100) but above the BOM
 * content so cards scroll under it cleanly. Negative side margins + matching
 * padding let the solid background bleed to the container edges so scrolling
 * content doesn't peek past the panel's sides. */
.order-sticky-top {
  position: sticky;
  top: var(--layout-topnav-height);
  z-index: 90;
  background: var(--color-bg-base);
  margin: 0 calc(-1 * var(--space-4));
  padding: var(--space-4) var(--space-4) var(--space-3);
}

.detail-body {
  display: grid;
  grid-template-columns: 220px 1040px;
  gap: 20px;
  margin-top: var(--space-4);
  align-items: flex-start;
}

/* ─── Section nav (left sidebar) ───────────────────────────────── */
/* Per Figma: short standalone block, NOT stretching the body. Sticky
 * so it follows scroll, with offset for the TopNav. */
.section-nav {
  position: sticky;
  /* Offset clears the sticky topnav PLUS the order-detail sticky top panel
   * (ITEM 10) so the section nav pins below it instead of disappearing
   * behind it. --order-sticky-h is the panel's measured height, set by
   * bom_editor.js (syncStickyTopHeight) — robust to DEBUG banners / alerts /
   * flash messages that change the panel height. Falls back to 160px. */
  top: calc(var(--layout-topnav-height) + var(--order-sticky-h, 160px) + var(--space-3));
  display: flex;
  flex-direction: column;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 16px 0 8px;
}
.section-nav__header {
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  padding: 0 16px 12px;
  border-bottom: 1px solid var(--color-border);
  margin-bottom: 4px;
}
.section-nav__item {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 8px 16px;
  font-size: var(--fs-body);
  color: var(--color-text-secondary);
  text-decoration: none;
  border-left: 3px solid transparent;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.section-nav__item:hover {
  background: var(--color-bg-base);
  color: var(--color-text-primary);
  text-decoration: none;
  border-left-color: var(--color-primary);
}
/* Scroll-spy active state — the section currently in view. Deliberately
 * SUBTLE: a light accent-tinted background + accent left-border + slightly
 * bolder text, so it reads as "you are here" without shouting. Set by the
 * IntersectionObserver in bom_editor.js (sectionScrollSpy). */
.section-nav__item.is-active {
  background: rgba(58, 124, 165, 0.08);
  color: var(--color-text-primary);
  border-left-color: var(--color-accent);
  font-weight: var(--fw-medium);
}
.section-nav__item.is-active .section-nav__num {
  color: var(--color-accent);
}
.section-nav__item.is-active .section-nav__label {
  font-weight: var(--fw-medium);
}
.section-nav__num {
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
  font-weight: var(--fw-medium);
  font-size: 13px;
  flex-shrink: 0;
}
.section-nav__label {
  font-weight: var(--fw-regular);
}
.section-nav__item:hover .section-nav__num {
  color: var(--color-primary);
}

/* ─── Main column — stacked cards ──────────────────────────────── */
.section-main {
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-width: 0;
}

/* ─── Generic detail card (Section 1 wrapper, BOM container) ──── */
/* Reuses the existing `.card` class for surface/border/radius. We override
 * the global .card padding (24px) since the BOM container holds its own
 * inner cards which already have padding. */
.detail-card {
  padding: 24px;
  scroll-margin-top: calc(var(--layout-topnav-height) + var(--space-4));
}
.detail-card__header {
  margin-bottom: 20px;
}
.detail-card__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--color-border);
}

/* ─── Section 1 — merged sketch + 12-field grid ───────────────── */
/* Selector also matches `.basic-info-card.bom-form` so the grid display
 * wins over `.bom-form { display: flex; flex-direction: column }`, which
 * is defined later in this file (last rule otherwise wins on equal
 * specificity — but `.basic-info-card.bom-form` has higher specificity).
 */
.basic-info-card,
.basic-info-card.bom-form {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 20px;
  align-items: flex-start;
}
/* Footer ("저장" button row) spans the full width below the sketch+grid
 * instead of falling into its own grid cell. */
.basic-info-card > .detail-card__footer {
  grid-column: 1 / -1;
}
/* Django's CSRF token renders as <input type="hidden">. Hidden inputs
 * still count as grid items and steal a cell unless we exclude them. */
.basic-info-card > input[type="hidden"] {
  display: none;
}
.basic-info-card__sketch {
  width: 300px;
  height: 340px;
  background: var(--color-bg-base);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  overflow: hidden;
}
.basic-info-card__sketch img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.basic-info-card__sketch-empty {
  padding: var(--space-4);
}
/* Sketch container needs `position: relative` so the absolutely-positioned
 * tool overlay (`.sketch-tools`) anchors to it. */
.basic-info-card__sketch[data-sketch-cell] {
  position: relative;
}

/* ─── Sketch tools overlay (✂ 이미지 편집 / 원본 복귀 / ✓ 크롭됨) ──── */
/* Bottom-right corner of the sketch box. Faint until hover so it doesn't
 * overpower the image preview. */
.sketch-tools {
  position: absolute;
  right: 6px;
  bottom: 6px;
  display: flex;
  align-items: center;
  gap: 6px;
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 3px 4px;
  backdrop-filter: blur(2px);
}
.sketch-tools__indicator {
  font-size: 11px;
  color: var(--color-success);
  font-weight: var(--fw-medium);
  padding: 0 4px;
}

/* ─── Crop modal (Section 1 sketch editor) ──────────────────────────
 * Same shell as the 꼬마딱지 modal (2.1-P) so the two feel like a pair.
 * Cropper.js renders its workspace inside `.crop-modal__stage`, which
 * has a definite height so the canvas can compute its layout. */
.crop-modal[hidden] { display: none; }
.crop-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.crop-modal__bg {
  position: absolute;
  inset: 0;
  background: rgba(26, 29, 33, 0.42);
}
/* Modal panel — slightly bigger than the 꼬마딱지 one so the cropping
 * workspace is comfortable. 960×720 → 532px-tall stage. */
.crop-modal__panel {
  position: relative;
  z-index: 1;
  width: 960px;
  max-width: 95vw;
  max-height: 90vh;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.crop-modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 64px;
  padding: 0 24px;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}
.crop-modal__title {
  margin: 0;
  font-size: 18px;
  font-weight: var(--fw-bold);
  color: var(--color-text-primary);
}
.crop-modal__x {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg-base);
  border: none;
  border-radius: 4px;
  color: var(--color-text-secondary);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.crop-modal__x:hover {
  color: var(--color-text-primary);
  background: var(--color-border);
}
.crop-modal__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px 24px;
  min-height: 0;
}
.crop-modal__stage {
  flex: 1;
  min-height: 0;
  background: var(--color-bg-base);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.crop-modal__stage img {
  display: block;
  max-width: 100%;
  height: auto;
}
.crop-modal__hint {
  margin: 0;
  font-size: 12px;
  color: var(--color-text-secondary);
}
.crop-modal__footer {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  height: 76px;
  padding: 0 24px;
  border-top: 1px solid var(--color-border);
  flex-shrink: 0;
}

/* 3 col × 4 row grid. Per Figma: 16px col gap, 12px row gap. */
.basic-info-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px 16px;
}
.basic-info-grid__field {
  display: flex;
  flex-direction: column;
  gap: 4px;  /* per Figma: 4px label-to-input gap */
  min-width: 0;
}

/* Required-field marking (order detail basic-info). A red asterisk on each
   mandatory label + a single "* 필수 항목" legend spanning the grid top;
   aria-required on the inputs carries the same meaning to screen readers. */
.form-label__req {
  color: var(--color-danger);
  font-weight: var(--fw-bold);
  margin-left: 1px;
}
.required-legend {
  grid-column: 1 / -1;
  margin: 0;
  font-size: var(--fs-meta);
  color: var(--color-text-muted);
}
.required-legend__star {
  color: var(--color-danger);
  font-weight: var(--fw-bold);
  margin-right: 2px;
}

/* Compact 30px input height to match Figma. The default `.form-input`
 * is 40px; we use a `--sm` modifier for the basic-info grid where Figma
 * specifies a denser layout. */
.form-input--sm {
  height: 30px;
  padding: 0 10px;
  font-size: var(--fs-body);
  border-radius: var(--radius-sm);
}

/* Read-only field — visually matches the input but with bg/base + no border highlight */
.form-readonly {
  display: flex;
  align-items: center;
  height: 30px;
  padding: 0 10px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-text-primary);
  font-size: var(--fs-body);
}

/* ─── BOM container (holds toolbar + 10 sub-section cards) ─────── */
.bom-container {
  /* Reuses .card → bg-surface, border, radius, padding 24px */
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Toolbar — first child of .bom-container */
.bom-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--color-border);
}
.bom-toolbar__title {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.bom-toolbar__meta {
  font-size: var(--fs-meta);
  color: var(--color-text-muted);
  padding: 2px 8px;
  background: var(--color-bg-base);
  border-radius: var(--radius-xs);
}
.bom-toolbar__actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* ─── BOM sub-section card (sections 1-10 inside BOM container) ── */
/* Per Figma: 6px border-radius (not 8) — tighter than the big page-level cards. */
.bom-subsection {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  overflow: hidden;
  scroll-margin-top: calc(var(--layout-topnav-height) + var(--space-4));
}
.bom-subsection__header {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 44px;
  padding: 0 16px;
  background: var(--color-bg-base);
  border-bottom: 1px solid var(--color-border);
}
.bom-subsection__chevron {
  font-size: 12px;
  color: var(--color-text-secondary);
  user-select: none;
}
.bom-subsection__title {
  margin: 0;
  font-size: 14px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
}
.bom-subsection__body {
  padding: 14px 8px;              /* tighter horizontal padding so wide tables (packing) have more usable room */
  /* Safety: if a section's table is wider than the container (e.g. very many
   * columns or long content), let it scroll horizontally instead of breaking
   * the layout of the surrounding card. */
  overflow-x: auto;
}

/* "✓ 저장됨" badge that fades in next to a section header after auto-save.
 * NOTE: deliberately NOT `margin-left: auto`. The section's toggle button
 * already eats remaining space with its own `margin-left: auto`; if the
 * badge did the same, the two auto-margins would split the space and push
 * the toggle button to the middle of the header. The badge instead sits
 * flush against whatever's to its left (toggle button or title). */
.bom-saved-badge {
  margin-left: 8px;
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-success);
  opacity: 0;
  transition: opacity 0.18s, color 0.18s;
}
/* When the section header has no toggle button (single-card sections),
 * push the badge to the right edge with its own auto-margin. */
.bom-subsection__header:not(:has(.bom-section-toggle-btn)) .bom-saved-badge {
  margin-left: auto;
}
.bom-saved-badge.is-visible { opacity: 1; }

/* Three sticky save states — replaces the old transient 1.8s flash. With
 * a 2-minute reset-timer autosave the user needs persistent feedback so
 * they know whether their last change reached the server. */
.bom-saved-badge--dirty  { color: var(--color-warning, #c47900); }   /* amber dot */
.bom-saved-badge--saving { color: var(--color-text-muted, #757575); } /* grey */
.bom-saved-badge--saved  { color: var(--color-success); }              /* green check */

/* Placeholder body (legacy — kept for any read-only blocks we still want) */
.placeholder-section {
  padding: 16px;
  background: var(--color-bg-base);
  border: 1px dashed var(--color-border);
  border-radius: 6px;
}
.placeholder-section p { margin: 0; }
.placeholder-section p + p { margin-top: 6px; }

/* ═══════════════════════════════════════════════════════════════════
 * BOM editor — table / matrix / fabric stack / packing / QC grid
 * Geometry & colors from Figma frames 53:163, 53:234, 53:350, 54:191, 54:450.
 * ═══════════════════════════════════════════════════════════════════ */

/* Form container — common to every editable section. */
.bom-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.bom-form__hint {
  margin: 0;
  font-size: 12px;
  color: var(--color-text-secondary);
}
.bom-form__footer {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-top: 8px;
}
.bom-form__footer--right {
  justify-content: flex-end;
}
.bom-form__footer--split {
  justify-content: space-between;
}

/* ─── Generic table (sections 2, 6, 7, 11) ───────────────────────── */
.bom-table {
  display: flex;
  flex-direction: column;
}
.bom-table__head,
.bom-table__row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 8px 0 0;       /* no left padding — first cell sits flush with the row's left edge for max content width */
  height: 34px;  /* head height */
  /* Size to children, then never below the parent's width. With many cols
   * (e.g. Section 8 packing's 12-col main row) the natural width exceeds
   * the card body — `min-width: 100%` keeps the row's white background
   * stretched the full visible width even when content overflows the
   * card; `width: max-content` lets the row extend WITH its overflowed
   * cells so the bg keeps going under them too instead of stopping at
   * the card edge. The `.bom-subsection__body`'s `overflow-x: auto`
   * provides the horizontal scrollbar. */
  width: max-content;
  min-width: 100%;
}
.bom-table__head {
  background: var(--color-bg-base);
  border-radius: var(--radius-xs);
  flex-shrink: 0;
}
.bom-table__row {
  height: 40px;
  background: var(--color-bg-surface);
  border-bottom: 1px solid var(--color-border);
}
.bom-table__row:last-child { border-bottom: 1px solid var(--color-border); }

.bom-table__h {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* 9px = .bom-cell-input's 1px border + 8px horizontal padding.
   * Aligns the header label exactly above the input text below it. */
  padding-left: 9px;
  /* Match `.bom-table__cell { flex-shrink: 0 }` so headers and value
   * cells stay the SAME declared width even when the row overflows
   * the parent card. Without this the headers shrink while the cells
   * keep their full width — the two strips drift apart and labels no
   * longer sit above their inputs. */
  flex-shrink: 0;
}
.bom-table__h--del { width: 40px; flex-shrink: 0; padding-left: 0; }
/* Numeric/money columns formerly had a right-aligned header (--num) so
 * the label sat above the right-aligned digits. Reverted on user
 * request — they prefer ALL column labels to share a consistent
 * left-aligned position above their inputs, regardless of whether the
 * value inside the input is left- or right-aligned. The class is kept
 * (templates still apply it conditionally) but is now a no-op so
 * future styling tweaks are still possible without re-touching every
 * template. */

.bom-table__cell {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  position: relative;        /* anchor for the overflow tooltip below */
  /* No padding — keeps the input flush with the header column boundary
   * so the input frame visually sits exactly under its column label. */
}

/* ─── Overflow tooltip (cell with truncated content) ─────────────── */
/* JS sets data-overflow="true" + data-tooltip="<full value>" on any
 * cell whose input scroll-width exceeds its visible width. Hovering
 * the cell reveals a small dark bubble with the full text. Disappears
 * automatically once the cell is wide enough or the input is shorter. */
.bom-table__cell[data-overflow="true"]:hover::before,
.bom-stack__field[data-overflow="true"]:hover::before {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%);
  padding: 4px 8px;
  background: rgba(26, 29, 33, 0.92);
  color: var(--color-text-inverse);
  font-size: 11px;
  font-weight: var(--fw-medium);
  border-radius: var(--radius-xs);
  white-space: nowrap;
  z-index: 50;
  pointer-events: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Tooltip arrow (small triangle pointing down at the cell) */
.bom-table__cell[data-overflow="true"]:hover::after,
.bom-stack__field[data-overflow="true"]:hover::after {
  content: "";
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 4px solid transparent;
  border-top-color: rgba(26, 29, 33, 0.92);
  z-index: 50;
  pointer-events: none;
}
/* The fabric stack uses .bom-stack__field instead of .bom-table__cell — give
 * it the same anchor so its tooltip positions correctly. */
.bom-stack__field {
  position: relative;
}
.bom-table__cell--del {
  width: 40px;
  justify-content: center;
}
/* Pin the delete (×) column to the right edge of the scroll viewport so it's
   always reachable without scrolling a wide row sideways. Before this, the ×
   sat after the LAST data column inside .bom-scroll, so on column-heavy
   sections (완성자재 labels, 부자재 materials, PACKING) you had to scroll right
   to find it — which read as "there's no delete option" (Sheens 2026-06-18). */
.bom-table__h--del,
.bom-table__cell--del {
  position: sticky;
  right: 0;
  z-index: 2;
  /* White everywhere (including the PACKING main row) so the pinned × matches
     the input cells beside it — no grey block, no shadow smudge. The white bg
     also cleanly masks columns scrolling underneath it. */
  background: var(--color-bg-surface);
}

/* Bottom 합계 (totals) row — Excel-style "ledger rule" treatment.
 * Reverted to navy-rule version 2026-05-11 per Julian's request to
 * keep the current style while discussing alternatives with manager.
 *
 * Visual intent: the row sits on the SAME white surface as the data
 * rows above it. What signals "this is the totals row" is a single
 * 2px primary-color rule line across the top, plus bolder typography
 * on the row itself.
 *
 * Server-rendered via the `sum_by_cols` filter; JS overwrites the
 * values live as the user types. Numeric cells right-align under
 * their column inputs (mirrors `.bom-cell-input--num`).
 */
.bom-table__row--totals {
  height: 42px;
  border-top: 2px solid var(--color-primary);
  margin-top: 2px;
}
.bom-table__row--totals .bom-table__cell {
  justify-content: flex-end;
  padding: 0 8px;
}
.bom-table__row--totals .bom-table__cell:first-child {
  justify-content: flex-start;
  padding-left: 9px;
}
.bom-table__row--totals .bom-totals-label {
  color: var(--color-primary);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
}
.bom-table__row--totals .bom-totals-value {
  color: var(--color-text-primary);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Section-level totals strip for KIND_PACKING_NESTED — packs all main
 * rows + sub-rows into one bottom summary because per-card footers don't
 * make sense when the card itself contains two tables. */
.bom-section-totals {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  align-items: center;
  margin-top: 12px;
  padding: 10px 14px;
  background: var(--color-grey-50, #f7f7f7);
  border: 1px solid var(--color-border, #d6d6d6);
  border-radius: 6px;
  font-size: 13px;
}
.bom-section-totals .bom-totals-label {
  font-size: 13px;
  font-weight: 700;
  color: var(--color-text-strong, #1a1a1a);
}
.bom-totals-stat {
  color: var(--color-text-muted, #6a6a6a);
  font-weight: 500;
}
.bom-totals-stat strong {
  color: var(--color-text-strong, #1a1a1a);
  font-weight: 700;
  margin-left: 4px;
}

/* Inputs inside a table cell — visible bordered fields, Section-3 style.
 *
 * Every editable cell shows a 1px gray border + white background by default,
 * so users always see where they can click. Hover deepens the border;
 * focus turns it navy with a soft shadow. Browser-default spinner arrows
 * and select chevrons are stripped so the look is identical across browsers.
 */
.bom-cell-input {
  width: 100%;
  height: 30px;
  padding: 0 8px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  font: inherit;
  font-size: 13px;
  color: var(--color-text-primary);
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;

  /* Reset native chrome so Chrome/Safari/Firefox look identical. */
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
}

/* Hover → deeper border so the click target is obvious. */
.bom-cell-input:hover {
  border-color: var(--color-text-muted);
}

/* Strip the native ↑↓ spinner buttons on number inputs.
 * They're ugly, take up width, and double up with our own number formatting. */
.bom-cell-input[type="number"]::-webkit-outer-spin-button,
.bom-cell-input[type="number"]::-webkit-inner-spin-button,
.bom-stack-input[type="number"]::-webkit-outer-spin-button,
.bom-stack-input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.bom-cell-input[type="number"],
.bom-stack-input[type="number"] {
  -moz-appearance: textfield;     /* Firefox equivalent */
}

/* Custom dropdown chevron for selects — subtle gray, only visible on
 * hover/focus to keep populated cells clean. SVG inlined as data URI. */
select.bom-cell-input,
select.bom-stack-input {
  background-image: none;
  padding-right: 8px;
}
select.bom-cell-input:hover,
select.bom-cell-input:focus,
select.bom-stack-input:hover,
select.bom-stack-input:focus {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%238a93a1' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='3 5 6 8 9 5'/></svg>");
  background-repeat: no-repeat;
  background-position: right 6px center;
  background-size: 12px 12px;
  padding-right: 22px;
}

/* Status-chip style for approval-status selects. Activated by JS adding
 * `.bom-cell-input--chip-success / --warning / --danger / --muted` based
 * on the current value (e.g. "승인" → success). Matches the Figma green
 * pill in section 1 결재. */
.bom-cell-input--chip-success,
.bom-cell-input--chip-warning,
.bom-cell-input--chip-danger,
.bom-cell-input--chip-muted {
  font-weight: var(--fw-medium);
  font-size: 11px;
  text-align: center;
  text-align-last: center;        /* centre the visible select option */
  height: 24px;
  width: auto;
  min-width: 60px;
  padding: 0 10px;
  border-radius: var(--radius-xs);
  color: var(--color-text-inverse);
  border: none;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
}
.bom-cell-input--chip-success { background: var(--color-success); }
.bom-cell-input--chip-warning { background: var(--color-warning); }
.bom-cell-input--chip-danger  { background: var(--color-danger);  }
.bom-cell-input--chip-muted   {
  background: var(--color-bg-base);
  color: var(--color-text-secondary);
  border: 1px solid var(--color-border);
}
.bom-cell-input:focus {
  outline: none;
  background: var(--color-bg-surface);
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(31, 58, 95, 0.10);
}
.bom-cell-input--num {
  font-family: var(--font-mono);
  font-size: 12px;
  text-align: right;
}
.bom-cell-input--mono {
  font-family: var(--font-mono);
  font-size: 13px;
}

/* Delete `×` button per row */
.bom-row-delete {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-xs);
  font-size: 16px;
  color: var(--color-text-muted);
  cursor: pointer;
  background: transparent;
  border: 1px solid transparent;
  transition: color 0.12s, background 0.12s, border-color 0.12s;
}
.bom-row-delete:hover {
  color: var(--color-danger);
  background: rgba(179, 38, 30, 0.06);
  border-color: rgba(179, 38, 30, 0.2);
}

/* Card-level delete for user-added fabric/lab cards (ITEM 6). Sits just
 * left of the absolute-positioned collapse toggle (.bom-card-toggle is at
 * right: 8px, width 26px → place this at right: 40px). Matches the toggle's
 * 22px height so the two controls line up. */
.bom-card-delete {
  position: absolute;
  top: 6px;
  right: 40px;
  z-index: 2;
  width: 24px;
  height: 22px;
  font-size: 15px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* When a card is collapsed the toggle expands to fill the bar; hide the
 * floating delete so it doesn't overlap the collapsed name. */
.bom-stack__card.is-collapsed .bom-card-delete { display: none; }

/* "+ 추가" link — accent text, no surrounding box */
.bom-add-link {
  background: none;
  border: 1px solid transparent;
  color: var(--color-accent);
  font-size: 12px;
  font-weight: var(--fw-medium);
  padding: 6px 10px;
  border-radius: var(--radius-xs);
  cursor: pointer;
}
.bom-add-link:hover {
  background: rgba(58, 124, 165, 0.06);
}
.bom-add-link--centered {
  margin: 0 auto;
}
.bom-add-link--outlined {
  border: 1px solid var(--color-accent);
  border-radius: 6px;
  padding: 9px 14px;
  font-size: 13px;
}
.bom-add-link--block-dashed {
  width: 100%;
  border: 1px dashed var(--color-accent);
  border-radius: 6px;
  padding: 12px;
  font-size: 13px;
  text-align: center;
  margin-top: 4px;
}

/* ─── 3 inline fields (section 3 협의) ───────────────────────────── */
.bom-fields-row {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}
.bom-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
  min-width: 180px;
}

/* ─── Shared horizontal-scroll viewport for wide BOM tables ─────── */
/* Generalizes the color-matrix scroll pattern (ITEM 2) to every wide
 * section editor (table / fabric-stack / packing). The inner table keeps
 * `width: max-content; min-width: 100%`, so head + body + totals share
 * identical fixed column tracks and scroll sideways together as ONE unit
 * when the card is narrower than the table — columns never compress or
 * overlap. Each section's table is wrapped in this class; .bom-matrix-scroll
 * is kept as an alias so the section-4 markup is untouched. */
.bom-scroll,
.bom-matrix-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  /* When the inner table is wider than the card, overflow-x:auto adds a
   * horizontal scrollbar along the BOTTOM edge of this viewport. Without
   * room for it, the bar overlaps the last visible row — the 합계 (totals)
   * row, which sits at the bottom of the table — leaving it half-clipped.
   * Reserve a strip of bottom padding so the scrollbar sits BELOW the
   * totals row instead of on top of it. Harmless when no scrollbar shows
   * (the table simply gains a little bottom breathing room). */
  padding-bottom: 10px;
}
/* Top-field scroll viewport (fabric/lab cards): wraps the ITEM/부위/폭/…
 * strip so it scrolls sideways instead of spilling past the card border.
 * It has no totals row below it to protect, and the card's own `gap`
 * already separates it from the 컬러별 수량 table, so skip the extra
 * bottom padding to keep the strip visually tight. */
.bom-scroll--top {
  padding-bottom: 0;
  overflow-x: visible;
  /* TWO stacked rows: the main strip (ITEM/부위/폭/단위/업체) on top, and the
   * optional 시장 원단 strip (호수 + 연락처) together on its OWN line below.
   * Earlier these shared one wrap flow so 호수/연락처 filled the empty space
   * next to 업체 — but once 요척 moved to the per-color row that empty space
   * grew and 호수 reflowed UP next to 업체, splitting it from 연락처. Stacking
   * the strips keeps 호수 + 연락처 paired (Sheens feedback 2026-06-23). */
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.bom-matrix {
  display: flex;
  flex-direction: column;
  gap: 0;
  /* Fit-content keeps the matrix at exactly the sum of its fixed column
   * widths so head, body, totals and the template row all share the same
   * tracks. Combined with non-shrinking cells (below) this guarantees the
   * columns never misalign — the .bom-matrix-scroll wrapper handles the
   * overflow instead. */
  width: max-content;
  min-width: 100%;
}
.bom-matrix__head,
.bom-matrix__row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 8px;
}
/* Header cells must NOT shrink — otherwise they desync from the body
 * cells (which already have flex-shrink: 0) when the viewport narrows. */
.bom-matrix__h {
  flex-shrink: 0;
}
.bom-matrix__head {
  height: 34px;
  background: var(--color-bg-base);
  border-radius: var(--radius-xs);
}
.bom-matrix__row {
  height: 56px;
  border-bottom: 1px solid var(--color-border);
}
.bom-matrix__h {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  white-space: nowrap;
}
/* Size column header — label CENTERED to align with the centered numeric
 * inputs below; the × delete button is absolute-positioned at the right
 * edge so it doesn't push the label off-center. */
.bom-matrix__h--size {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* 합계 (total) column header — value cell is centered (.bom-matrix__cell--total
 * has justify-content: center), so the header needs to center too. */
.bom-matrix__h--center {
  display: flex;
  align-items: center;
  justify-content: center;
}
.bom-matrix__h-delete {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: 1px solid transparent;
  border-radius: 50%;
  width: 16px;
  height: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  line-height: 1;
  color: var(--color-text-muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s, background 0.12s, color 0.12s;
}
.bom-matrix__h--size:hover .bom-matrix__h-delete { opacity: 1; }
.bom-matrix__h-delete:hover {
  background: rgba(179, 38, 30, 0.10);
  color: var(--color-danger);
}
.bom-matrix__cell {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.bom-matrix__cell--total {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: var(--fw-medium);
  color: var(--color-accent);
  justify-content: center;
}

/* 합계 footer row (ITEM 3) — mirrors .bom-table__row--totals: 2px primary
 * rule line on top, bold label, mono per-column sums + grand total. */
.bom-matrix__row--totals {
  border-bottom: none;
  border-top: 2px solid var(--color-primary);
  background: var(--color-bg-base);
}
.bom-matrix-totals-label {
  color: var(--color-primary);
  font-size: 13px;
  font-weight: 700;
}
.bom-matrix__row--totals .bom-matrix__cell--total {
  color: var(--color-text-primary);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.bom-matrix__row--totals .bom-matrix__cell--grand {
  color: var(--color-accent);
}

/* Color swatch — 14px square, rounded 3px */
.bom-swatch {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 3px;
  border: 1px solid var(--color-border);
  flex-shrink: 0;
}

/* Inputs in matrix cells — slightly taller (36px vs 30px default) to
 * match the Figma color-matrix design. Border + bg already match defaults. */
.bom-matrix__cell .bom-cell-input {
  height: 36px;
  padding: 0 10px;
}
.bom-matrix__cell .bom-cell-input--num {
  text-align: center;
}

/* ─── Card collapse/expand toggle (Sections 4-Fabric, 5-Materials,
       7-Packing, 8-Lab) ─────────────────────────────────────────────
   Each card-stack item has a header bar with a chevron + the live ITEM
   value. Click the bar to toggle between expanded (full card visible)
   and collapsed (only the bar visible). A section-level "전체 접기 /
   전체 펼치기" button toggles every card in the section at once.
   Helps when an order has many materials/fabrics and the page gets
   long to scroll. */
/* Section header toggle button — sits to the right of the section title.
 * .bom-subsection__header is `display: flex` already; margin-left: auto
 * pushes the button against the right edge. */
.bom-section-toggle-btn {
  margin-left: auto;
}

/* ─── Per-card tabs (기본 / 선적) ──────────────────────────────────
   Each card-stack item renders a tiny tab nav at the top with two
   tabs: "기본 정보" (main inputs) and "선적 정보" (1차/2차/3차 ship
   strips with 계 balance). Toggling tabs flips the
   `data-active-tab` attribute on the card root; CSS uses that to
   show/hide the right child elements. Existing per-row .bom-shipments
   strips stay in place inside each row-group — we just hide them on
   the main tab and hide everything else on the ship tab.
   The right-padding clears the absolute-positioned card-toggle
   (chevron) so they don't visually collide. */
.bom-card-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--color-border);
  margin: 0 0 10px 0;
  padding-right: 36px;
}
.bom-card-tab {
  background: transparent;
  border: none;
  padding: 6px 10px;
  font: inherit;
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 0.12s, border-bottom-color 0.12s;
}
.bom-card-tab:hover:not(.is-active) {
  color: var(--color-text-primary);
}
.bom-card-tab.is-active {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}
.bom-card-tab:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* Visibility flip — driven by data-active-tab on card root.
   - MAIN tab (default): hide every shipment strip in the card.
   - SHIP tab: hide the heavy chrome (top fields strip / sub-table
     header / packing-detail panel / del-cells / column headers)
     and most of the row's input cells, KEEPING just the first cell
     of each row (which holds the item/color/SPEC identifier) plus
     the .bom-shipments strip. The card-tabs nav itself stays
     visible in both states. */
.bom-stack__card[data-active-tab="main"] .bom-shipments,
.bom-packing-group[data-active-tab="main"] .bom-shipments {
  display: none;
}
.bom-stack__card[data-active-tab="ship"] .bom-stack__top,
.bom-stack__card[data-active-tab="ship"] .bom-scroll--top,
.bom-stack__card[data-active-tab="ship"] .bom-stack__sub > .bom-stack__sub-header,
.bom-stack__card[data-active-tab="ship"] .bom-table__head,
.bom-packing-group[data-active-tab="ship"] .bom-table__head,
.bom-packing-group[data-active-tab="ship"] .bom-packing-detail {
  display: none;
}
/* Inside ship tab, hide all cells EXCEPT the first one (the item/color/
   SPEC identifier — provides context for which row's shipment you're
   editing). Also hide the delete-cell on the right. */
.bom-stack__card[data-active-tab="ship"] .bom-table-row-group .bom-table__row > .bom-table__cell:not(:first-of-type),
.bom-packing-group[data-active-tab="ship"] .bom-table-row-group .bom-table__row > .bom-table__cell:not(:first-of-type) {
  display: none;
}

/* ── Section-level tabs for KIND_TABLE sections with ship_cols ────
   Labels (section 7) is a flat row table — one card for the whole
   section with a 기본 정보 / 선적 정보 tab pair at the top.
   - 기본 정보: column-header strip + every row's main cells, ship
     strips hidden, per-row identifier hidden.
   - 선적 정보: column-header + main row cells hidden; each row shows
     its identifier (item — part) above its 1차/2차/3차 ship strip
     so the user knows which row's shipment they're editing. */
.bom-card-tabs--section {
  margin: 0 0 12px 0;
  padding-right: 0;
}
.bom-ship-row-label {
  display: none;
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  padding: 8px 8px 4px;
  border-top: 1px solid var(--color-border);
  background: var(--color-bg-surface);
}
.bom-form--table[data-active-tab="main"] .bom-shipments {
  display: none;
}
.bom-form--table[data-active-tab="ship"] .bom-table__head,
.bom-form--table[data-active-tab="ship"] .bom-table__strip,
.bom-form--table[data-active-tab="ship"] .bom-table-row-group .bom-table__row {
  display: none;
}
.bom-form--table[data-active-tab="ship"] .bom-ship-row-label {
  display: block;
}
.bom-form--table[data-active-tab="ship"] .bom-shipments {
  border-top: none;
  border-bottom: 1px solid var(--color-border);
}
.bom-form--table[data-active-tab="ship"] .bom-table-row-group:first-child .bom-ship-row-label {
  border-top: none;
}

/* ── Section-level 메모 (memo) tab — chem / QC-PP / 가공료 ────────────
   Free-text textarea that maps 1-to-1 to the merged white R-X cell on
   the Excel template. Reuses the existing `data-active-tab` flip
   pattern. The 메모 tab hides the section's data editor; non-memo
   tabs hide the memo block. */
.bom-memo {
  margin-top: 16px;
}
.bom-memo__input {
  width: 100%;
  min-height: 110px;
  padding: 10px 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  font: inherit;
  font-size: 13px;
  line-height: 1.5;
  color: var(--color-text-primary);
  resize: vertical;
}
.bom-memo__input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(31, 58, 95, 0.10);
}
/* Hide the memo block by default, show only on memo tab */
.bom-form .bom-memo { display: none; }
.bom-form[data-active-tab="memo"] .bom-memo { display: block; }
/* On memo tab, hide the data editors of all three section kinds */
.bom-form[data-active-tab="memo"] .bom-stack,
.bom-form[data-active-tab="memo"] [data-cards-target],
.bom-form[data-active-tab="memo"] .bom-qc-grid,
.bom-form[data-active-tab="memo"] .bom-table,
.bom-form[data-active-tab="memo"] [data-add-card],
.bom-form[data-active-tab="memo"] .bom-add-link,
.bom-form[data-active-tab="memo"] .bom-tests {
  display: none;
}

/* ── Linked test results panel (시험 결과 tab on chem section) ──────
   Read-only list of TestReports linked to this order via
   TestReportStyleLink. Visible only when the section's tab is "tests";
   hidden on every other tab. */
.bom-tests {
  display: none;
  margin-top: 14px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.bom-form[data-active-tab="tests"] .bom-tests { display: block; }
.bom-form[data-active-tab="tests"] .bom-stack,
.bom-form[data-active-tab="tests"] [data-cards-target],
.bom-form[data-active-tab="tests"] .bom-add-link,
.bom-form[data-active-tab="tests"] [data-add-card],
.bom-form[data-active-tab="tests"] .bom-memo {
  display: none;
}
.bom-tests__head,
.bom-tests__row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--color-border);
}
.bom-tests__head {
  background: var(--color-bg-base);
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
}
.bom-tests__row:last-child { border-bottom: none; }
.bom-tests__cell {
  font-size: 13px;
  color: var(--color-text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bom-tests__empty {
  padding: 24px 14px;
  text-align: center;
  font-size: 13px;
  color: var(--color-text-secondary);
}
.bom-tests__empty a {
  color: var(--color-primary);
  margin-left: 4px;
  text-decoration: underline;
}
/* Small count chip on the 시험 결과 tab button. */
.bom-card-tab__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 6px;
  margin-left: 4px;
  border-radius: 9px;
  background: var(--color-bg-base);
  font-size: 10px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
}
.bom-card-tab.is-active .bom-card-tab__count {
  background: rgba(31, 58, 95, 0.10);
  color: var(--color-primary);
}
/* Per-card collapse/expand toggle — when EXPANDED, just a tiny
 * chevron at the top-right corner of the card (no name text — the
 * ITEM input already shows the name). When COLLAPSED, the card
 * shrinks to a single bar and the toggle expands to fill it,
 * showing both the item name + chevron. Saves a full row of vertical
 * space per expanded card vs. the old always-visible bar design.
 *
 * Implementation: button is `position: absolute` in the expanded
 * state (so it floats over the card's top-right padding zone without
 * pushing content down) and `position: static` when the card is
 * collapsed (so it lays out as the only visible element). */
.bom-card-toggle {
  position: absolute;
  top: 6px;
  right: 8px;
  z-index: 2;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0;
  width: 26px;
  height: 22px;
  border-radius: 4px;
  font: inherit;
  color: var(--color-text-muted);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease;
}
.bom-card-toggle:hover {
  background: var(--color-bg-base);
  color: var(--color-text-primary);
  border-color: var(--color-text-muted);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
}
.bom-card-toggle:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}
.bom-card-toggle__name {
  display: none;     /* hidden when expanded — ITEM input shows the name */
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bom-card-toggle__chevron {
  font-size: 11px;
  transition: transform 0.15s ease;
  flex-shrink: 0;
}
/* The card itself needs position: relative so the absolute-positioned
 * chevron anchors to the card. Already set elsewhere for tooltip
 * anchors, but spell it out here for clarity. */
.bom-stack__card,
.bom-packing-group {
  position: relative;
}
/* Collapsed state — card shrinks to a single bar; toggle expands to
 * fill it as a static (in-flow) element with the name visible. */
.bom-stack__card.is-collapsed,
.bom-packing-group.is-collapsed {
  padding: 4px 8px;
}
.bom-stack__card.is-collapsed > *:not(.bom-card-toggle),
.bom-packing-group.is-collapsed > *:not(.bom-card-toggle) {
  display: none;
}
.bom-stack__card.is-collapsed .bom-card-toggle,
.bom-packing-group.is-collapsed .bom-card-toggle {
  position: static;
  width: 100%;
  padding: 4px 8px;
  height: 32px;
}
.bom-stack__card.is-collapsed .bom-card-toggle__name,
.bom-packing-group.is-collapsed .bom-card-toggle__name {
  display: block;
  flex: 1;
  text-align: left;
}
.bom-stack__card.is-collapsed .bom-card-toggle__chevron,
.bom-packing-group.is-collapsed .bom-card-toggle__chevron {
  transform: rotate(-90deg);
}

/* ─── Scroll-to-top floating action button ─────────────────────────
   Fixed pill at the bottom-right of the viewport. Hidden until the
   user has scrolled past ~half a viewport, then fades in. Click =
   smooth-scroll back to the top. Plain SVG arrow icon, navy fill,
   subtle elevation. Lives in core/base.html so it appears on every
   page that has enough content to scroll. */
.scroll-to-top {
  position: fixed;
  bottom: 24px;
  right: 24px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.08);
  z-index: 100;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.2s ease, transform 0.2s ease, box-shadow 0.15s ease, background 0.15s ease;
  pointer-events: none;
}
.scroll-to-top.is-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.scroll-to-top:hover {
  background: #142844;
  box-shadow: 0 10px 22px rgba(0, 0, 0, 0.18), 0 3px 6px rgba(0, 0, 0, 0.10);
  transform: translateY(-2px);
}
.scroll-to-top:active {
  transform: translateY(0);
}
.scroll-to-top:focus-visible {
  outline: 3px solid rgba(31, 58, 95, 0.35);
  outline-offset: 2px;
}
.scroll-to-top svg {
  display: block;
}

/* ─── Fabric / Lab stack (sections 5, 9) ─────────────────────────── */
.bom-stack {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.bom-stack__card {
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* ── Fixed lab cards (이화학성적서) ───────────────────────────────────
   3 non-removable, pre-labeled cards. No editable ITEM input — the
   read-only label doubles as a full-width collapse toggle so the user
   can minimize each card (they get long: label + 업체 + color/date table). */
.bom-stack__card--fixed {
  gap: 8px;
}
/* Full-width in-flow header bar (overrides the absolute-positioned
   chevron-only .bom-card-toggle used by dynamic cards). */
.bom-card-toggle--fixed {
  position: static;
  width: 100%;
  height: auto;
  justify-content: space-between;
  padding: 2px 4px 6px;
  border: none;
  border-bottom: 1px solid var(--color-border);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
.bom-card-toggle--fixed:hover {
  background: var(--color-bg-base);
  border-color: var(--color-border);
  box-shadow: none;
}
/* The label is the card's identity — keep it visible when expanded
   (the dynamic-card .bom-card-toggle__name is display:none when open). */
.bom-card-toggle--fixed .bom-card-toggle__name--static {
  display: block;
  flex: 1;
  text-align: left;
}
/* Collapsed: the card is a single bar — drop the label's separator line. */
.bom-stack__card.is-collapsed .bom-card-toggle--fixed {
  border-bottom: none;
}
.bom-stack__top {
  display: flex;
  flex-wrap: nowrap;       /* Keep all 6 top fields on one line. The subsection
                              body has overflow-x:auto as a safety net if a
                              future addition pushes the row wider than the card. */
  gap: 8px;
  width: 100%;
}
.bom-stack__field {
  flex-shrink: 0;          /* Don't let the browser squish individual fields. */
}
.bom-stack__field {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.bom-stack__label {
  font-size: 10px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
}
.bom-stack-input {
  width: 100%;
  height: 28px;
  padding: 0 8px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  font: inherit;
  font-size: 12px;
  color: var(--color-text-primary);
}
.bom-stack-input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(31, 58, 95, 0.10);
}

/* ── Optional secondary top row (Q-012) ────────────────────────────────────
   Always-visible secondary fields below the main top-fields strip — used
   for 시장 원단 (market fabric) info on Section 5: 호수 + 연락처. Visually
   identical to the main row except the labels render in muted gray to
   signal "optional / secondary." No border, no background, no header
   label — just the two inputs sitting cleanly underneath. UI only;
   never written to the BOM Excel.
*/
.bom-stack__top--secondary {
  margin-top: 6px;
}
.bom-stack__label--muted {
  color: var(--color-text-muted);
  opacity: 0.85;
}

.bom-stack__sub {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.bom-stack__sub-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.bom-stack__sub-title {
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
}
.bom-stack__sub-actions {
  display: flex;
  gap: 6px;
  margin-left: auto;
}

/* In-table action strip — sits between the column-header row and the data
 * rows, visually attached to the table on both edges. Used for sections
 * that have `auto_fill_colors: True` (Materials, etc.) so the add/auto-fill
 * /clear buttons feel like part of the table chrome, not floating above it.
 * Right-aligned to match the fabric sub-header pattern. */
.bom-table__strip {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 6px;
  padding: 6px 4px;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-bg-surface);
}

/* ─── Mini-buttons (used in the fabric/lab sub-table header) ───── */
/* Three of them, per Figma: "+ 컬러 추가", "↓ 컬러 자동채우기", "× 초기화"
 * Smaller than .btn but larger than .bom-add-link — they sit on the
 * right side of the sub-table header and trigger DOM-only actions. */
.bom-mini-btn {
  display: inline-flex;
  align-items: center;
  padding: 4px 8px;
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 3px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.bom-mini-btn:hover {
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
  border-color: var(--color-text-secondary);
}
.bom-mini-btn--accent {
  background: var(--color-accent);
  color: var(--color-text-inverse);
  border-color: var(--color-accent);
}
.bom-mini-btn--accent:hover {
  background: var(--color-accent);
  color: var(--color-text-inverse);
  filter: brightness(1.08);
}

/* ─── Packing nested (section 8) ─────────────────────────────────── */
/* Visual pattern mirrors Materials (.bom-stack__card / .bom-stack__sub):
 * the WHOLE packing item lives in one grey card, and the nested
 * "세부 — SPEC별 배분" sub-table sits inside as a clean white sub-panel.
 * Without this the main row floated bare while the sub-table had its own
 * grey, making the section look patchy (Sheens feedback May 6). */
.bom-packing-group {
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 12px 14px 12px 8px;     /* tighter left padding for more horizontal room */
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* The CARD is the SINGLE horizontal scroller for the whole packing item:
   * one scrollbar moves the main row AND the nested SPEC detail together
   * (Sheens feedback 2026-06-18). The inner .bom-scroll wrappers are
   * neutralized to overflow-x:visible below so they don't add their own bars,
   * and the SPEC panel's old fixed width:958px (which forced a 2nd bar even
   * when collapsed) is gone — so it's one bar instead of two. */
  width: 100%;
  overflow-x: auto;
}
/* Inside packing, the per-table .bom-scroll viewports defer to the card's
 * single scrollbar above — they don't scroll independently. This is what makes
 * the SPEC detail scroll in lockstep with the main row under one bar.
 * NOTE: must be `overflow: visible` (BOTH axes). The base .bom-scroll rule sets
 * overflow-y: hidden; if we only set overflow-x: visible, CSS forces it back to
 * `auto` (a visible axis paired with a non-visible axis computes to auto), so
 * the inner bars would never disappear. */
.bom-packing-group .bom-scroll {
  overflow: visible;
  padding-bottom: 0;
}
/* Space adjacent packing items vertically. */
.bom-packing-group + .bom-packing-group {
  margin-top: 10px;
}
/* Neutralize the main row's white background + bottom-border inside
 * the grey card — otherwise we get a white strip that fights the card
 * surface and a stray border line above the sub-panel. */
.bom-packing-group > .bom-table__row {
  background: transparent;
  border-bottom: none;
}
.bom-packing-detail {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  padding: 10px 12px;
  margin: 0;
  /* Grow to wrap the SPEC sub-table so the panel's border encloses the WHOLE
   * row. `align-self: start` defeats the flex-column stretch (which would pin
   * the panel to the visible width and let a wider sub-table spill past its
   * right border — "rows stretch over the container border"); width:max-content
   * sizes it to the sub-table; min-width:100% keeps it at least the visible
   * card width. The card's single scrollbar still scrolls the whole item.
   * (Was a fixed width:958px, which forced a 2nd scrollbar even when collapsed.) */
  align-self: start;
  width: max-content;
  min-width: 100%;
}
/* Collapsed state — only the "+ SPEC 추가" button is visible. Click the
 * button to expand: the JS handler removes this attribute and adds the
 * first sub-row in one go (see bom_editor.js add-sub-row handler).
 * For empty packing items (no SPEC variants yet) this hides the empty
 * sub-table behind a single button — much cleaner. */
.bom-packing-detail[data-spec-collapsed] {
  background: transparent;
  border: none;
  padding: 0;
}
.bom-packing-detail[data-spec-collapsed] .bom-packing-detail__label,
.bom-packing-detail[data-spec-collapsed] .bom-table {
  display: none;
}
.bom-packing-detail__label {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
  margin-bottom: 8px;
}
/* Inside the sub-panel, the table-head stripe inherits its own bg from
 * .bom-table__head's default — leave it alone (no override). */

/* ─── QC / PP grid (section 10) ──────────────────────────────────── */
.bom-qc-grid {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.bom-qc-row {
  display: flex;
  gap: 16px;
  align-items: flex-end;
}
.bom-qc-label {
  width: 200px;
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  flex-shrink: 0;
  padding-bottom: 6px;
}
.bom-qc-cell {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.bom-qc-stage {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
}

/* ─── Status chips (used in 결재 'status' col + 9 '추정/확인 결과') ─ */
.bom-status-chip {
  display: inline-flex;
  align-items: center;
  padding: 3px 8px;
  border-radius: var(--radius-xs);
  font-size: 11px;
  font-weight: var(--fw-medium);
  white-space: nowrap;
}
.bom-status-chip--success { background: var(--color-success); color: var(--color-text-inverse); }
.bom-status-chip--warning { background: var(--color-warning); color: var(--color-text-inverse); }
.bom-status-chip--danger  { background: var(--color-danger);  color: var(--color-text-inverse); }
.bom-status-chip--muted   { background: var(--color-bg-base); color: var(--color-text-secondary); border: 1px solid var(--color-border); }

/* ─── Computed cells (read-only result of a formula) ─────────────── */
/* Used for `amount` cells where amount = order_qty × price. Visually
 * distinct from inputs (no border, slight italic) so users know they
 * can't type into them — the value updates as they type elsewhere. */
.bom-cell-computed {
  display: flex;
  align-items: center;
  width: 100%;
  /* Don't clip large calculated numbers (e.g. 필요수량 / 금액 like
   * "1,514,499") on the left when the column is narrow. nowrap + a min-width
   * floor keeps normal-size numbers fully visible WITHOUT needing to scroll;
   * the .bom-scroll wrapper handles the case where the whole table is too
   * wide for the card. Only ever applies to read-only computed cells. */
  min-width: 88px;
  height: 30px;
  padding: 0 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--color-accent);
  font-style: italic;
  font-weight: var(--fw-medium);
  background: rgba(58, 124, 165, 0.04);
  border: 1px dashed transparent;
  border-radius: var(--radius-xs);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  justify-content: flex-end;
}
/* Give every NUMERIC column a width floor so large computed values aren't
 * clipped without scrolling — and apply it to the header, the value/computed
 * body cells, AND the totals cell IDENTICALLY so the three strips keep the
 * same per-column track and stay aligned inside the .bom-scroll wrapper.
 * (A schema column's inline `width: Npx` acts as the flex-basis; this floor
 * only ever widens a column, never narrows it, so head/body never desync.)
 * Numeric headers carry .bom-table__h--num; numeric body/total cells are
 * detected by their .bom-cell-input--num / .bom-cell-computed child. */
.bom-table__h--num,
.bom-table__cell:has(> .bom-cell-input--num),
.bom-table__cell:has(> .bom-cell-computed),
.bom-table__row--totals .bom-table__cell:has(> .bom-totals-value),
.bom-table__cell--num {
  min-width: 88px;
}
/* Totals-row numeric values never wrap either. */
.bom-table__row--totals .bom-totals-value {
  white-space: nowrap;
}

/* ─── Shipments strip (1차 / 2차 / 3차 + 계 below a data row) ────── */
.bom-table-row-group {
  display: flex;
  flex-direction: column;
  border-bottom: 1px solid var(--color-border);
}
.bom-table-row-group:last-child {
  border-bottom: none;
}
.bom-table-row-group > .bom-table__row {
  border-bottom: none;
}
.bom-shipments {
  display: flex;
  align-items: stretch;
  gap: 8px;
  padding: 6px 8px 10px;
  background: rgba(253, 255, 177, 0.20);  /* matches Excel template's pale yellow ship fill */
  border-top: 1px dashed var(--color-border);
}
.bom-shipment-cell {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1;
  min-width: 140px;
}
.bom-shipment-label {
  font-size: 10px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
}
.bom-cell-input--ship {
  height: 26px;
  padding: 0 8px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  font-size: 12px;
}
.bom-cell-input--ship:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(31, 58, 95, 0.10);
}
.bom-shipment-cell input + input {
  margin-top: 4px;
}
.bom-shipment-balance {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 80px;
  flex-shrink: 0;
}
.bom-shipment-balance__value {
  height: 26px;
  padding: 0 8px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-accent);
  background: rgba(58, 124, 165, 0.06);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
}

/* ─── Cost summary block (read-only, below all 11 sections) ──────── */
.bom-summary {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-primary);  /* slightly stronger to signal "important totals" */
}
.bom-summary .bom-subsection__header {
  background: rgba(31, 58, 95, 0.04);
}
.bom-summary__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px 24px;
  margin: 0;
}
.bom-summary__cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 12px;
  background: var(--color-bg-base);
  border-radius: var(--radius-xs);
  border-left: 3px solid var(--color-accent);
}
.bom-summary__label {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  margin: 0;
}
.bom-summary__value {
  font-size: 18px;
  font-weight: var(--fw-bold);
  color: var(--color-text-primary);
  margin: 0;
}
@media (max-width: 960px) {
  .bom-summary__grid { grid-template-columns: repeat(2, 1fr); }
}

/* ─── Narrower viewports ───────────────────────────────────────── */
@media (max-width: 1280px) {
  .detail-body {
    /* Allow main column to flex below the design width */
    grid-template-columns: 220px 1fr;
  }
}
@media (max-width: 960px) {
  .detail-body {
    grid-template-columns: 1fr;
  }
  .section-nav {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    overflow-x: auto;
    padding: 12px;
  }
  .section-nav__header { display: none; }
  .basic-info-card {
    grid-template-columns: 1fr;
  }
  .basic-info-card__sketch {
    width: 100%;
    max-width: 300px;
  }
  .basic-info-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ═════════════════════════════════════════════════════════════════
 * Screen 2.4 발주 추가 — PDF upload + AI extraction review
 * Figma frame 124:522.
 * ═════════════════════════════════════════════════════════════════ */

.add-card {
  padding: 24px 28px;
}
.add-card__header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
.add-card__step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  background: var(--color-primary);
  color: var(--color-text-inverse);
  font-size: 12px;
  font-weight: var(--fw-bold);
  border-radius: 12px;
}
.add-card__title {
  margin: 0;
  font-size: 16px;
  font-weight: var(--fw-bold);
  color: var(--color-text-primary);
}
.add-card__hint {
  margin: 0 0 12px;
  font-size: 12px;
  color: var(--color-text-secondary);
}

/* ── Empty upload dropzone ──────────────────────────────────────── */
.add-card__upload-empty { width: 100%; }
.add-card__dropzone {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 32px 24px;
  background: var(--color-bg-base);
  border: 2px dashed var(--color-border);
  border-radius: 6px;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.add-card__dropzone:hover {
  border-color: var(--color-accent);
  background: var(--color-bg-surface);
}
/* Drag-over state — active while user is dragging files OVER the
 * dropzone (before they release the mouse). Stronger visual than
 * hover so it's obvious the drop will land. */
.add-card__dropzone--dragover {
  border-color: var(--color-info);
  background: rgba(47, 111, 179, 0.06);
  box-shadow: 0 0 0 4px rgba(47, 111, 179, 0.10);
}
.add-card__dropzone-icon { font-size: 28px; }
.add-card__dropzone-text {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.add-card__dropzone-text strong {
  font-size: 14px;
  color: var(--color-text-primary);
}

/* ── Bulk-upload selected files list (Batch E, 2026-05-15) ─────────
 * Sits between the dropzone and the 업로드 시작 button. One row per
 * file the user has picked, with a ✕ to drop individual files before
 * committing to upload. */
.selected-files {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 280px;
  overflow-y: auto;
}
.selected-files__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 8px 12px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: 13px;
}
.selected-files__name {
  flex: 1;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.selected-files__size {
  flex-shrink: 0;
}
.selected-files__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--color-text-muted);
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
  transition: background 0.12s, color 0.12s;
}
.selected-files__remove:hover {
  background: rgba(0, 0, 0, 0.08);
  color: var(--color-danger);
}

.upload-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* ── Uploaded file row ──────────────────────────────────────────── */
.add-card__file {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
}
.add-card__file--success { border-color: var(--color-success); }
.add-card__file--error   { border-color: var(--color-danger); }
.add-card__file-icon { font-size: 24px; }
.add-card__file-meta {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

/* ── Review-form field grid ─────────────────────────────────────── */
.add-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px 16px;
  margin-top: 12px;
}
.add-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.add-field__label-row {
  display: flex;
  align-items: center;
  gap: 6px;
}
.add-field__label {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
}
.add-field__ai-chip,
.add-card__ai-chip {
  display: inline-flex;
  align-items: center;
  padding: 1px 5px;
  font-size: 9px;
  font-weight: var(--fw-medium);
  color: var(--color-accent);
  background: var(--color-bg-base);
  border: 1px solid var(--color-accent);
  border-radius: 3px;
  white-space: nowrap;
}
.add-card__ai-chip { font-size: 10px; padding: 1px 6px; }
.add-field__value {
  display: flex;
  align-items: center;
  height: 36px;
  padding: 0 12px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-size: 13px;
  color: var(--color-text-primary);
}

/* ── Team auto-assign info banner ────────────────────────────────── */
.add-card__team-banner {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  margin-top: 12px;
  background: var(--color-bg-base);
  border-radius: 6px;
}
.add-card__team-icon {
  font-size: 14px;
  color: var(--color-text-muted);
  flex-shrink: 0;
}

/* ── Color/size preview table ───────────────────────────────────── */
.add-color-preview {
  display: flex;
  flex-direction: column;
  margin-top: 12px;
}
.add-color-preview__head,
.add-color-preview__row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 16px;
}
.add-color-preview__head {
  height: 36px;
  background: var(--color-bg-base);
  border-radius: 6px;
}
.add-color-preview__row {
  height: 44px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
}
.add-color-preview__row + .add-color-preview__row { border-top: none; }
.add-color-preview__h {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
}
.add-color-preview__cell {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--color-text-primary);
}
.add-color-preview__cell--total {
  font-weight: var(--fw-medium);
  color: var(--color-accent);
  font-size: 14px;
}

@media (max-width: 1100px) {
  .add-grid { grid-template-columns: repeat(2, 1fr); }
}

/* ═════════════════════════════════════════════════════════════════
 * 꼬마딱지 (Overview) modal — Screen 2.1-P
 * Figma frame 17:240. Triggered from the order list selection banner.
 * ═════════════════════════════════════════════════════════════════ */

.overview-modal[hidden] { display: none; }
.overview-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.overview-modal__bg {
  position: absolute;
  inset: 0;
  background: rgba(26, 29, 33, 0.42);
}
/* Modal panel — exact dimensions from Figma 17:255 (880×620). Content
 * scrolls only if the order list overflows the 480px body. */
.overview-modal__panel {
  position: relative;
  z-index: 1;
  width: 880px;
  max-width: 95vw;
  max-height: 90vh;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
/* Header — Figma: 64px tall, 24px h-padding, bottom border. */
.overview-modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 64px;
  padding: 0 24px;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}
.overview-modal__title {
  margin: 0;
  font-size: 18px;
  font-weight: var(--fw-bold);
  color: var(--color-text-primary);
}
/* Close ✕ — Figma: 28×28 squircle on bg/base, 13px secondary glyph. */
.overview-modal__x {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg-base);
  border: none;
  border-radius: 4px;
  color: var(--color-text-secondary);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.overview-modal__x:hover {
  color: var(--color-text-primary);
  background: var(--color-border);
}
/* Body — Figma: 480px tall, 340 left + 540 right (no gap). */
.overview-modal__body {
  display: grid;
  grid-template-columns: 340px 540px;
  height: 480px;
  flex-shrink: 0;
}
/* Left column — Figma: 24px h-padding, 20px v-padding, 12px gap. */
.overview-modal__left {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 20px 24px;
  background: var(--color-bg-base);
  overflow-y: auto;
}
/* "3건 선택됨" — Figma: 13px Medium, secondary text. */
.overview-modal__count {
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
}
/* Brand list ("MLB Kids · Matin Kim · CGP") — Figma: 12px Regular muted,
 * sits directly under the count with no extra margin so the 12px gap
 * comes from the parent flex. */
.overview-modal__brands {
  font-size: 12px;
  color: var(--color-text-muted);
  margin: 0;
}
.overview-modal__orders {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* Order preview card — Figma: 292×96, 12px padding, 10px gap, 6px radius. */
.overview-modal__order-card {
  display: flex;
  gap: 10px;
  padding: 12px;
  height: 96px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  align-items: flex-start;
}
.overview-modal__order-thumb {
  width: 48px;
  height: 64px;
  flex-shrink: 0;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  overflow: hidden;
}
/* Batch E.1 fix: the modal preview card now contains an <img> when
   the order has a sketch (cropped > raw). Cover the whole 48×64 thumb
   wrapper so we get a consistent crop. Empty case (no image) shows
   the wrapper's grey background, same as before. */
.overview-modal__order-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.overview-modal__order-meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.overview-modal__order-style {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
}
.overview-modal__order-name {
  font-size: 11px;
  color: var(--color-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.overview-modal__order-owner {
  font-size: 11px;
  color: var(--color-text-muted);
}
/* Right column — Figma: 24px h-padding, 20px v-padding, 14px gap between
 * sections (which alternate between section-title text + content block). */
.overview-modal__right {
  padding: 20px 24px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow-y: auto;
}
.overview-modal__section-title {
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  /* Figma: section title sits flush; spacing comes from the parent flex gap.
   * The right column has gap:14px between section-title and its content. */
  margin: 0;
}

/* ─── Chip-style field checkboxes (표시 항목) ──────────────────────
 * Per Figma 17:284: flex-wrap row, 8px gap, two visual states.
 *   Unchecked → bg/base, 1px border, empty 14×14 box
 *   Checked   → bg/surface (white), 1.5px primary border, primary-filled box
 * The native <input> is hidden; clicks toggle the `is-checked` class via JS
 * so the chip itself becomes the click target. */
.overview-modal__chip-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.chip-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 6px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  cursor: pointer;
  user-select: none;
  /* Reserve 0.5px for the checked state's thicker border to avoid layout
   * shift when toggling. */
  margin: 0.5px;
}
.chip-check.is-checked {
  background: var(--color-bg-surface);
  border: 1.5px solid var(--color-primary);
  margin: 0;
}
.chip-check input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 0;
  height: 0;
}
/* The visual checkbox box. Unchecked = white inside default-bordered chip;
 * checked = primary-filled. */
.chip-check__box {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 3px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  flex-shrink: 0;
}
.chip-check.is-checked .chip-check__box {
  background: var(--color-primary);
  border-color: var(--color-border);
}
.chip-check__label {
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  line-height: 1;
  white-space: nowrap;
}

/* ─── Segmented sort-key buttons (정렬 기준) ──────────────────────
 * Per Figma 17:322: row of 4 pill buttons, 8px gap, exclusive selection.
 *   Inactive → white bg, 1px default border, primary text
 *   Active   → primary bg + border, white text
 * Behaves like a radio group; JS toggles `is-active` on click. */
.seg-group {
  display: flex;
  gap: 8px;
}
.seg-btn {
  padding: 8px 12px;
  border-radius: 6px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  font-family: inherit;
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  cursor: pointer;
  line-height: 1;
  transition: background 0.1s, color 0.1s, border-color 0.1s;
}
.seg-btn:hover:not(.is-active) {
  border-color: var(--color-text-secondary);
}
.seg-btn.is-active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-text-inverse);
  cursor: default;
}

/* ─── Toggle switches (옵션) ─────────────────────────────────────
 * Per Figma 17:332-340: 36×20 track + 16×16 thumb. ON = primary-filled track
 * with thumb at right. OFF = bg/base track with thumb at left. */
.overview-modal__toggles {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}
.toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
}
.toggle input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 0;
  height: 0;
}
.toggle__track {
  display: inline-block;
  position: relative;
  width: 36px;
  height: 20px;
  border-radius: 10px;
  background: var(--color-text-muted);  /* OFF state */
  transition: background 0.15s ease;
  flex-shrink: 0;
}
.toggle__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 8px;
  background: var(--color-bg-surface);
  transition: left 0.15s ease;
}
.toggle input:checked + .toggle__track {
  background: var(--color-primary);
}
.toggle input:checked + .toggle__track .toggle__thumb {
  left: 18px;
}
.toggle__label {
  font-size: 13px;
  color: var(--color-text-primary);
  line-height: 1;
}

/* Footer — Figma 17:341: 76px tall, 24px h-padding, 8px gap, right-aligned. */
.overview-modal__footer {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  height: 76px;
  padding: 0 24px;
  border-top: 1px solid var(--color-border);
  flex-shrink: 0;
}

/* ═════════════════════════════════════════════════════════════════
 * Screens 3.x — Test results (KOTITI / FITI / KATRI)
 * Figma 19:2 (3.1 list), 19:242 (3.2 KOTITI), 35:116 (3.2 FITI),
 * 35:278 (3.2 KATRI), 124:884 (3.4 add).
 * ═════════════════════════════════════════════════════════════════ */

/* ── 3.1 list — filter bar ──────────────────────────────────────── */
.test-filter-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 56px;
  padding: 0 16px;
  margin-top: 20px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 8px;
}
.filter-chip {
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 7px 10px 7px 12px;
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  font-family: inherit;
  cursor: pointer;
  height: 32px;
}
.filter-chip:focus {
  outline: 2px solid var(--color-primary);
  outline-offset: -1px;
}
.filter-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  cursor: pointer;
  user-select: none;
}
.filter-toggle input[type="checkbox"] {
  width: 14px;
  height: 14px;
  margin: 0;
  accent-color: var(--color-primary);
}
.filter-bar__spacer { flex: 1 0 0; min-width: 8px; }
.filter-search {
  width: 220px;
  height: 36px;
  font-size: 12px;
  background: var(--color-bg-base);
  padding: 0 12px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
}

/* ── 3.1 list — data table ──────────────────────────────────────── */
.test-table {
  margin-top: 20px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  overflow: hidden;
}
.test-table__head {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 40px;
  padding: 0 20px;
  background: var(--color-bg-base);
  border-bottom: 1px solid var(--color-border);
}
.test-table__h {
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  white-space: nowrap;
}
.test-table__row {
  display: flex;
  align-items: center;
  gap: 16px;
  position: relative;
  height: 56px;
  padding: 0 20px;
  background: var(--color-bg-surface);
  border-bottom: 1px solid var(--color-border);
  text-decoration: none;
  color: inherit;
  transition: background 0.1s;
}
.test-table__row:hover {
  background: var(--color-bg-base);
  text-decoration: none;
}
.test-table__row--fail {
  background: #fdf1f0;
  border-left: 3px solid var(--color-danger);
  padding-left: 17px;  /* offset for the 3px border so columns don't shift */
}
.test-table__row--fail:hover {
  background: #f9e6e4;
}

/* Order list — a row missing any mandatory first-7 field (브랜드·품번·팀·상품명·
   생산처·납품창고·원산지) is flagged red, same visual language as a failed test
   row. Hover the row to see which fields are missing (title tooltip). */
.data-table__row--missing > td {
  background: #fdf1f0;
}
.data-table__row--missing:hover > td {
  background: #f9e6e4;
}
.data-table__row--missing > td:first-child {
  box-shadow: inset 3px 0 0 0 var(--color-danger);
}

/* Header dropdown menus (Excel ▾ / Worksheet ▾) — native <details>, no JS.
   Groups the file/export/print actions so the order header isn't a long
   row of buttons. */
.hdr-dropdown {
  position: relative;
}
.hdr-dropdown > summary {
  list-style: none;
  cursor: pointer;
}
.hdr-dropdown > summary::-webkit-details-marker {
  display: none;
}
.hdr-dropdown__panel {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  z-index: 50;
  min-width: 200px;
  max-width: 90vw;
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* The Worksheet menu carries the richer replace/version UI → wider. */
.hdr-dropdown--wide .hdr-dropdown__panel {
  width: 380px;
  padding: 12px;
}
/* Menu links/buttons render as full-width left-aligned rows. */
.hdr-dropdown__item {
  width: 100%;
  justify-content: flex-start;
  text-align: left;
}
/* Current worksheet filename shown faint in brackets next to the download
   link, so you can see WHAT the current document is without downloading it.
   Truncates with … (long Korean names) — full name is in the title tooltip. */
.hdr-dropdown__filename {
  display: inline-block;
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
  color: var(--color-text-muted);
  font-weight: var(--fw-regular);
  font-size: var(--fs-meta);
}
.hdr-dropdown__divider {
  height: 1px;
  background: #eef1f4;
  margin: 6px 2px;
}
.worksheet-tools__replace {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 2px;
}
.worksheet-tools__replace-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.worksheet-tools__file {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 12px;
  color: #5b6770;
}
.worksheet-tools__file::file-selector-button {
  margin-right: 8px;
  padding: 4px 10px;
  font-size: 12px;
  border: 1px solid #c9d2dc;
  border-radius: 6px;
  background: #f7f8fa;
  color: #1f2933;
  cursor: pointer;
}
.worksheet-tools__file::file-selector-button:hover {
  background: #eef2f7;
}
.worksheet-tools__replace .btn {
  flex: 0 0 auto;
}
.worksheet-tools__title {
  display: block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 0 0 2px 2px;
  color: #9aa5b1;
}
.worksheet-tools__empty {
  margin: 4px 2px 0;
}
.worksheet-tools__version {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 0;
  border-bottom: 1px solid #f3f5f7;
}
.worksheet-tools__version:last-child {
  border-bottom: 0;
}
.worksheet-tools__vinfo {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.worksheet-tools__vname {
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.worksheet-tools__vactions {
  display: flex;
  gap: 4px;
  flex: 0 0 auto;
}
.test-table__cell {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
.test-table__cell--links {
  gap: 6px;
  flex-wrap: wrap;
}
.test-table__lab {
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
}
.test-table__connect {
  position: absolute;
  right: 20px;
  top: 50%;
  transform: translateY(-50%);
}
.test-table__empty {
  text-align: center;
  padding: 60px 20px;
  color: var(--color-text-muted);
}

/* Style-no chip used in linked-품번 column AND the 연결 품번 panel */
.style-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 3px 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--color-accent);
  white-space: nowrap;
}
.style-chip--more {
  background: var(--color-bg-surface);
  color: var(--color-text-secondary);
  font-family: inherit;
  font-weight: var(--fw-medium);
  padding: 3px 8px;
}
.style-chip__x {
  background: none;
  border: none;
  color: var(--color-text-muted);
  cursor: pointer;
  padding: 0;
  font-size: 14px;
  line-height: 1;
}
.style-chip__x:hover { color: var(--color-danger); }

/* ── Status badges (5 states) ───────────────────────────────────── */
.status-badge--pass {
  background: var(--color-success);
  color: var(--color-text-inverse);
}
.status-badge--fail {
  background: var(--color-danger);
  color: var(--color-text-inverse);
}
.status-badge--review,
.status-badge--sample {
  background: var(--color-warning);
  color: var(--color-text-inverse);
}
.status-badge--pending {
  background: var(--color-border);
  color: var(--color-text-secondary);
}

/* AI 추출 chip — small accent-colored tag */
.ai-chip {
  display: inline-flex;
  align-items: center;
  background: var(--color-bg-base);
  border: 1px solid var(--color-accent);
  border-radius: 3px;
  padding: 1px 5px;
  font-size: 9px;
  font-weight: var(--fw-medium);
  color: var(--color-accent);
  letter-spacing: 0.3px;
}

/* ── 3.2 detail — 2-col body (840 + 420) ────────────────────────── */
.test-detail__body {
  display: grid;
  grid-template-columns: 840px 420px;
  gap: 20px;
  margin-top: 20px;
  align-items: flex-start;
}
.test-detail__left {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}
.test-detail__right {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.test-card {
  padding: 18px 24px;
}
.test-card .heading-h3 {
  margin: 0 0 12px;
  font-size: 16px;
  font-weight: var(--fw-bold);
}
.test-card__row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}
/* Header row inside a test-card whose right-hand side holds an inline
   indicator (e.g. the "● 변경사항 있음" dirty badge on 확인 결과).
   Used on screen 3.2 — see test_detail.html and test_detail_save.js. */
.test-card__header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.test-card__header-row .heading-h3 { margin: 0; }
.test-dirty-badge {
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: #b45309;
  padding: 2px 8px;
  border-radius: 999px;
  background: #fef3c7;
  white-space: nowrap;
}
.test-dirty-badge[hidden] { display: none; }
.test-card__provenance {
  display: flex;
  align-items: center;
  gap: 8px;
}
.test-card__hint {
  display: flex;
  gap: 6px;
  align-items: flex-start;
  background: var(--color-bg-base);
  border-radius: 6px;
  padding: 8px 12px;
  font-size: 11px;
  color: var(--color-text-secondary);
  margin-top: 12px;
}
.test-card__hint span:first-child { color: var(--color-text-muted); }

/* ── Confirm result form (radio-pill + memo) ────────────────────── */
.confirm-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.confirm-form__radios {
  display: flex;
  gap: 12px;
}
.radio-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  cursor: pointer;
  user-select: none;
}
.radio-pill input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 0;
  height: 0;
}
.radio-pill__dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  flex-shrink: 0;
}
.radio-pill.is-selected,
.radio-pill:has(input:checked) {
  background: var(--color-bg-base);
  border: 2px solid var(--color-primary);
  padding: 9px 13px;  /* offset for the +1px border so layout doesn't shift */
}
.radio-pill.is-selected .radio-pill__dot,
.radio-pill:has(input:checked) .radio-pill__dot {
  background: var(--color-primary);
  position: relative;
}
.radio-pill.is-selected .radio-pill__dot::after,
.radio-pill:has(input:checked) .radio-pill__dot::after {
  content: '';
  position: absolute;
  inset: 4px;
  background: var(--color-bg-surface);
  border-radius: 50%;
}
.radio-pill__label {
  font-size: 13px;
  color: var(--color-text-primary);
}
.confirm-form .form-input--memo {
  height: auto;
  min-height: 72px;
  padding: 12px;
  font-size: 12px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  resize: vertical;
}
.confirm-form__actions {
  display: flex;
  justify-content: flex-end;
}
.confirm-form--readonly .radio-pill {
  cursor: not-allowed;
  opacity: 0.7;
}
.confirm-form--readonly .form-input--memo {
  opacity: 0.7;
}

/* ── Lab meta card — header strip + 5×2 field grid ──────────────── */
.lab-meta__strip {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 48px;
  padding: 0 16px;
  background: var(--color-bg-base);
  border-radius: 6px;
  margin-bottom: 10px;
}
.lab-meta__strip-name {
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
}
.lab-meta__strip-title {
  font-size: 14px;
  font-weight: var(--fw-bold);
  color: var(--color-text-primary);
  letter-spacing: 2px;
}
.lab-meta__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px 16px;
}
.lab-meta__field {
  display: flex;
  height: 34px;
  align-items: stretch;
}
.lab-meta__label {
  display: flex;
  align-items: center;
  width: 140px;
  padding: 0 10px;
  background: var(--color-bg-base);
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  flex-shrink: 0;
}
.lab-meta__value {
  display: flex;
  align-items: center;
  flex: 1 0 0;
  padding: 0 10px;
  border: 1px solid var(--color-border);
  font-size: 12px;
  color: var(--color-text-primary);
}
/* Lab-meta header: stack the section title and the "관리자만 편집…" caption
   with breathing room before the gray strip. Without this margin-bottom,
   the strip sits flush against the caption. */
.lab-meta__header {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 14px;
}
/* The h3 inside the header uses the wrapper's gap, not its own margin. */
.lab-meta__header .heading-h3 {
  margin-bottom: 0;
}
/* Lab-meta form: separates the grid from the save button row. Without this
   the 저장 button sat directly underneath the last field row. */
.lab-meta__form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.lab-meta__actions {
  display: flex;
  justify-content: flex-end;
}
/* Any subtitle/explainer paragraph that sits directly under a test-card
   heading needs space below before the next block begins. Hits the
   "담당 디자이너·팀장·관리과가 …" copy on the 확인 결과 card. */
.test-card .heading-h3 + .text-meta {
  margin: 0 0 14px;
}

/* ── PDF preview placeholder ────────────────────────────────────── */
/* 원본 성적서 card body — holds the iframe-embedded PDF.js viewer + filename
   caption underneath. Per doc §5.15: "PDF.js 뷰어 자리 + 원본 다운로드". */
.test-pdf-preview {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* Clickable thumbnail showing the top of the PDF. Overflow:hidden hides
   the browser's PDF viewer scrollbar; the embed inside is sized taller
   than the wrapper so no scrollbar appears within the visible window. */
.test-pdf-preview__crop {
  position: relative;
  width: 100%;
  height: 240px;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-bg-base);
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.test-pdf-preview__crop:hover,
.test-pdf-preview__crop:focus-visible {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 1px var(--color-primary);
  outline: none;
}
.test-pdf-preview__image {
  display: block;
  width: 70%;
  max-width: 560px;
  height: auto;
  margin: 12px auto 0;   /* center horizontally + small top inset so it looks like a page on the wrapper */
  /* The image is naturally taller than the 240px wrapper; overflow:hidden
     on .test-pdf-preview__crop crops it to show only the top of the PDF. */
  pointer-events: none;  /* clicks pass through to the wrapper to open the modal */
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.test-pdf-preview__hint {
  position: absolute;
  bottom: 8px;
  right: 8px;
  padding: 4px 10px;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 11px;
  border-radius: 4px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.test-pdf-preview__crop:hover .test-pdf-preview__hint,
.test-pdf-preview__crop:focus-visible .test-pdf-preview__hint {
  opacity: 1;
}
/* Empty state — no PDF attached: keep the centered-placeholder look. */
.test-pdf-preview:not(:has(.test-pdf-preview__crop)) {
  height: 200px;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
}

/* ── PDF modal (full document, opened from preview thumbnail) ───── */
.pdf-modal {
  /* override <dialog> defaults so we control sizing + appearance */
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: #fff;
  width: min(960px, 90vw);
  height: min(900px, 90vh);
  max-width: 90vw;
  max-height: 90vh;
  overflow: hidden;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.pdf-modal::backdrop {
  background: rgba(0, 0, 0, 0.55);
}
.pdf-modal__close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.95);
  color: var(--color-text-primary);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  z-index: 2;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
  transition: background 0.15s ease;
}
.pdf-modal__close:hover {
  background: var(--color-bg-base);
}
.pdf-modal__embed {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* ── Change history rows ────────────────────────────────────────── */
.test-history { display: flex; flex-direction: column; }
.test-history__row {
  display: flex;
  gap: 12px;
  align-items: center;
  height: 56px;
  padding: 12px;
  border-bottom: 1px solid var(--color-border);
}
.test-history__row:first-child { background: var(--color-bg-base); }
.test-history__row:last-child  { border-bottom: none; }
.test-history__time { width: 140px; font-size: 11px; }
.test-history__who  { width: 100px; font-size: 13px; font-weight: var(--fw-medium); color: var(--color-text-primary); }
.test-history__summary {
  flex: 1;
  font-size: 12px;
  color: var(--color-text-primary);
}

/* ── 연결 품번 panel ─────────────────────────────────────────────── */
.test-link-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 12px;
}

/* ── 신비서 source-email card ───────────────────────────────────── */
.email-card-field {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 8px;
}
.email-card-field__label {
  font-size: 11px;
  color: var(--color-text-muted);
}
.email-card-field__value {
  font-size: 13px;
  color: var(--color-text-primary);
}

/* ── Link modal — chrome matches 꼬마딱지/crop modals ───────────── */
.link-modal[hidden] { display: none; }
.link-modal {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
}
.link-modal__bg {
  position: absolute; inset: 0;
  background: rgba(26, 29, 33, 0.42);
}
.link-modal__panel {
  position: relative; z-index: 1;
  width: 720px; max-width: 95vw; max-height: 80vh;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  display: flex; flex-direction: column;
  overflow: hidden;
}
.link-modal__header {
  display: flex; align-items: center; justify-content: space-between;
  height: 64px; padding: 0 24px;
  border-bottom: 1px solid var(--color-border);
}
.link-modal__title {
  margin: 0; font-size: 18px; font-weight: var(--fw-bold);
  color: var(--color-text-primary);
}
.link-modal__x {
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--color-bg-base);
  border: none; border-radius: 4px;
  color: var(--color-text-secondary);
  font-size: 13px; cursor: pointer;
}
.link-modal__x:hover { background: var(--color-border); color: var(--color-text-primary); }
.link-modal__body {
  flex: 1;
  display: flex; flex-direction: column; gap: 12px;
  padding: 16px 24px;
  min-height: 0;
}
.link-modal__search {
  width: 100%;
  height: 36px;
  font-size: 13px;
}
.link-modal__results {
  flex: 1;
  overflow-y: auto;
  display: flex; flex-direction: column; gap: 4px;
  padding: 4px 0;
}
.link-modal__row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  width: 100%;
}
.link-modal__row:hover {
  background: var(--color-bg-base);
  border-color: var(--color-primary);
}
.link-modal__row-style {
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--color-accent);
  flex-shrink: 0;
  width: 180px;
}
.link-modal__row-meta {
  display: flex; gap: 12px; align-items: center;
  font-size: 12px;
  color: var(--color-text-primary);
}
.link-modal__hint {
  font-size: 11px;
  color: var(--color-text-muted);
}
.link-modal__footer {
  display: flex; justify-content: flex-end;
  height: 64px; padding: 0 24px; align-items: center;
  border-top: 1px solid var(--color-border);
}

/* ── 3.4 add — step cards + upload + estimate preview ───────────── */
.test-add-card {
  padding: 24px 28px;
  margin-top: 20px;
}
.step-header {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 14px;
}
.step-circle {
  width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border-radius: 12px;
  font-size: 12px; font-weight: var(--fw-bold);
  flex-shrink: 0;
}
.test-add-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px 16px;
}
.test-add-field {
  display: flex; flex-direction: column; gap: 4px;
}
.test-add-upload {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 18px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  min-height: 70px;
}
.test-add-upload__icon { font-size: 22px; }
.test-add-upload__body {
  flex: 1; display: flex; flex-direction: column; gap: 3px;
  min-width: 0;
}
.test-add-upload__input {
  position: absolute; opacity: 0; width: 0; height: 0;
  pointer-events: none;
}
.test-add-upload__label {
  font-size: 14px; font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--color-border);
  text-underline-offset: 3px;
}
.test-add-upload__label:hover {
  text-decoration-color: var(--color-primary);
}
.test-add-preview {
  display: flex; gap: 14px; align-items: center;
  background: var(--color-bg-base);
  padding: 18px 20px;
  border-radius: 6px;
}
.test-add-preview__body {
  flex: 1; display: flex; flex-direction: column; gap: 4px;
  min-width: 0;
}
.test-add-preview__title {
  margin: 0; font-size: 13px; font-weight: var(--fw-medium);
  color: var(--color-text-primary);
}
.test-add-hint {
  display: flex; gap: 12px; align-items: flex-start;
  background: var(--color-bg-base);
  padding: 14px 16px;
  border-radius: 6px;
  margin-top: 20px;
}
.test-add-hint > span {
  color: var(--color-text-muted);
  font-size: 14px;
}

/* Mid-screen variants of test-add-grid for narrower viewports */
@media (max-width: 1100px) {
  .test-add-grid { grid-template-columns: repeat(2, 1fr); }
  .test-detail__body { grid-template-columns: 1fr; }
}

/* ═════════════════════════════════════════════════════════════════
 * Screens 1.8a / 1.8b — Email
 * Figma 124:182 (1.8a list), 140:329 (1.8b detail).
 * ═════════════════════════════════════════════════════════════════ */

/* ── ScanFreshness chip (top-right of 1.8a) ─────────────────────── */
.scan-freshness {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 12px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 6px;
}
.scan-freshness__dot {
  width: 8px; height: 8px;
  border-radius: 4px;
  background: var(--color-success);
  flex-shrink: 0;
}
.scan-freshness__label {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
}
.scan-freshness__time {
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
}
.scan-freshness__next {
  font-size: 11px;
  color: var(--color-text-muted);
}

/* ── Pill-shape filter chips (1.8a tabs) ─────────────────────────── */
.email-filter-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 56px;
  padding: 0 16px;
  margin-top: 20px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 8px;
}
.pill-chip {
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 16px;
  padding: 8px 14px;
  font-family: inherit;
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  line-height: 1;
  cursor: pointer;
  transition: background 0.1s, color 0.1s, border-color 0.1s;
}
.pill-chip:hover:not(.is-active) {
  border-color: var(--color-text-secondary);
}
.pill-chip.is-active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-text-inverse);
  cursor: default;
}
.email-search {
  width: 280px;
  height: 36px;
  font-size: 12px;
  background: var(--color-bg-base);
  padding: 0 12px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
}

/* ── Email list table ───────────────────────────────────────────── */
.email-table {
  margin-top: 20px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  overflow: hidden;
}
.email-table__head {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 40px;
  padding: 0 16px;
  background: var(--color-bg-base);
  border-bottom: 1px solid var(--color-border);
}
.email-table__h {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  white-space: nowrap;
}
.email-table__row {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 60px;
  padding: 0 16px;
  background: var(--color-bg-surface);
  border-bottom: 1px solid var(--color-border);
  text-decoration: none;
  color: inherit;
  transition: background 0.1s;
}
.email-table__row:hover {
  background: var(--color-bg-base);
  text-decoration: none;
}
.email-table__row--unread {
  background: var(--color-bg-base);
  border-left: 3px solid var(--color-primary);
  padding-left: 13px; /* offset for the 3px border so cells don't shift */
}
.email-table__row--unread:hover {
  background: var(--color-border);
}
.email-table__cell {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
.email-table__cell--main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: flex-start;
  justify-content: center;
}
.email-table__time {
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
}
.email-table__row--unread .email-table__time {
  color: var(--color-text-primary);
}
.email-table__unread-dot {
  display: inline-block;
  width: 7px; height: 7px;
  border-radius: 4px;
  background: var(--color-danger);
  margin-right: 6px;
  flex-shrink: 0;
}

/* 1.8a per-user 상태 column badge — derived from EmailRead, not global state. */
.email-status-badge {
  display: inline-block;
  font-size: 12px;
  font-weight: var(--fw-medium);
  padding: 3px 10px;
  border-radius: 12px;
  line-height: 1.4;
}
.email-status-badge--unread {
  color: var(--color-danger);
  background: rgba(220, 53, 69, 0.10);  /* light red tint */
}
.email-status-badge--read {
  color: var(--color-text-secondary);
  background: var(--color-surface-secondary);
}
/* 처리완료 badge — distinct from "읽음" so users can tell at a glance
   which emails they've finished processing. Greenish tint to match
   the "done" semantics; readable on hover row backgrounds. */
.email-status-badge--done {
  color: #166534;          /* dark green */
  background: #dcfce7;     /* light green tint */
}
.email-table__sender-row {
  display: flex; align-items: center; gap: 6px;
  font-size: 11px;
}
.email-table__attach { font-size: 12px; }
.email-table__subject {
  font-size: 13px;
  color: var(--color-text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 600px;
}
.email-table__subject--unread {
  font-weight: var(--fw-medium);
}
.email-table__empty {
  text-align: center;
  padding: 60px 20px;
  color: var(--color-text-muted);
}

/* ── Owner chip (1.8a table + 1.8b detail) ──────────────────────── */
.owner-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 6px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 3px;
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  white-space: nowrap;
  margin-right: 4px;
}
.owner-chip__agent-dot {
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 2px;
  background: var(--color-info, #2f6fb3);
}
.owner-chip--lg {
  padding: 6px 8px 6px 10px;
  border-radius: 4px;
  font-size: 12px;
  gap: 6px;
}
.owner-chip__hint {
  font-size: 10px;
  font-weight: var(--fw-regular);
  color: var(--color-text-muted);
}
.owner-chip__x {
  background: none; border: none;
  color: var(--color-text-muted);
  cursor: pointer;
  padding: 0; font-size: 12px;
  line-height: 1;
}
.owner-chip__x:hover { color: var(--color-danger); }

/* ── 1.8b detail layout ─────────────────────────────────────────── */
.email-detail__body {
  display: grid;
  grid-template-columns: 840px 420px;
  gap: 20px;
  margin-top: 20px;
  align-items: flex-start;
}
.email-detail__left,
.email-detail__right {
  display: flex; flex-direction: column; gap: 16px;
  /* Grid items default to `min-width: auto`, which lets an unshrinkable
     child (e.g. a 1500px lab webzine image) push this column past its
     840px / 420px track width and into the sibling column. Forcing
     `min-width: 0` here lets the child's `max-width: 100%` / overflow-x
     finally kick in. Caught on 2026-05-27 staging review — the KOTITI
     Webzine email body was still bleeding past 담당자 even though
     `.email-card__body` had `overflow-x: auto`. */
  min-width: 0;
}
/* Three-state segmented control on the 1.8b 상태 card.
   Manager-restored 2026-05-28 — now per-user (EmailRead.state) instead
   of global. Visual design mirrors the pre-#94 production look:
   full-width row with three equal-split buttons, 36px tall, chunky
   click targets. Active state is dark-blue (color-primary). */
.state-toggle {
  display: flex;
  height: 36px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  overflow: hidden;
}
.state-toggle__btn {
  flex: 1 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg-base);
  border: none;
  border-right: 1px solid var(--color-border);
  font-family: inherit;
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.state-toggle__btn:last-child { border-right: none; }
.state-toggle__btn:hover:not(.is-active) {
  background: var(--color-border);
}
.state-toggle__btn.is-active {
  background: var(--color-primary);
  color: var(--color-text-inverse, #fff);
  cursor: default;
}

.email-card {
  padding: 18px 24px;
}
.email-card__title {
  margin: 0 0 10px;
  font-size: 16px;
  font-weight: var(--fw-bold);
  color: var(--color-text-primary);
}
.email-card__meta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 12px;
  margin-bottom: 12px;
  font-size: 11px;
}
.email-card__sender {
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
}
.email-card__body {
  background: var(--color-bg-base);
  padding: 16px;
  border-radius: 6px;
  font-size: 14px;
  color: var(--color-text-primary);
  line-height: 1.7;
  min-height: 200px;
  /* Lab emails (KOTITI / FITI / KATRI / Hometax) use fixed-width
     HTML tables and inline width="800"-style attributes. Without
     constraining the body, these tables push the layout past the
     right sidebar — which the 2026-05-27 staging review caught.
     Allow the body to scroll horizontally instead, and force long
     URLs / Korean strings to wrap rather than expand the container. */
  overflow-x: auto;
  word-break: break-word;
}
/* Override inline width/height attrs from incoming email HTML so the
   raw lab template doesn't override our container width. The !important
   is unfortunate but the only reliable way to win against inline
   attributes baked into the lab's email template. Scoped strictly to
   email body so it can't affect other parts of the app. */
.email-card__body img,
.email-card__body iframe {
  max-width: 100% !important;
  height: auto !important;
}
/* Tables get DIFFERENT treatment than images.
 *
 * Lab notification emails (FITI / KOTITI / KATRI) routinely embed
 * 15-20 column tables with `white-space:nowrap` on every cell. If we
 * force `max-width: 100%` on those, the browser can't honor BOTH the
 * 840px parent width AND the nowrap on cells, and it falls back to
 * one-character-per-line text wrapping (caught 2026-05-31 on email
 * 474, a FITI 접수내역 with 17 columns).
 *
 * Better: let the table grow to its content width and rely on the
 * parent's `overflow-x: auto` for horizontal scroll. The reader gets
 * a horizontal scrollbar on the email body — readable rather than
 * unreadable.
 */
.email-card__body table {
  max-width: none;
  table-layout: auto;
}
/* Placeholder shown when both body_text + body_html are empty
 * (e.g. 신비서 synced metadata only). Centered + muted so it
 * reads as intentional, not broken. */
.email-card__body-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  min-height: inherit;
  text-align: center;
  color: var(--color-text-muted);
}
.email-card__body-empty a {
  color: var(--color-accent);
  text-decoration: underline;
}

/* Attachment row inside the 첨부 card */
.email-attachment {
  display: flex; align-items: center; gap: 12px;
  height: 60px;
  padding: 12px 14px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  margin-bottom: 8px;
}
.email-attachment:last-child { margin-bottom: 0; }
.email-attachment__icon { font-size: 22px; flex-shrink: 0; }
.email-attachment__meta {
  flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0;
}
.email-attachment__name {
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* State change history rows */
.email-history { display: flex; flex-direction: column; }
.email-history__row {
  display: flex; align-items: center; gap: 12px;
  height: 44px;
  padding: 10px 12px;
  background: var(--color-bg-base);
  border-bottom: 1px solid var(--color-border);
  font-size: 12px;
}
.email-history__row:last-child {
  background: var(--color-bg-surface);
  border-bottom: none;
}
.email-history__time {
  font-size: 12px;
  color: var(--color-text-secondary);
}
.email-history__who {
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
}
.email-history__detail {
  font-size: 12px;
  color: var(--color-text-primary);
}

/* ── 담당자 chips area (right card) ─────────────────────────────── */
.owner-chips {
  display: flex; flex-wrap: wrap; gap: 6px 8px; align-items: center;
  margin-top: 14px;
  margin-bottom: 8px;
}

/* + 담당자 추가 button — accent outlined, used inline below the chips */
.owner-add-btn {
  display: inline-flex; align-items: center;
  padding: 8px 12px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-accent);
  border-radius: 6px;
  font-family: inherit;
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-accent);
  cursor: pointer;
}
.owner-add-btn:hover {
  background: var(--color-bg-base);
}

/* Inline owner picker (search → click to add) */
.owner-picker[hidden] { display: none; }
.owner-picker {
  margin-top: 8px;
  padding: 10px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
}
.owner-picker__search {
  width: 100%;
  height: 32px;
  font-size: 12px;
  margin-bottom: 6px;
}
.owner-picker__results {
  display: flex; flex-direction: column; gap: 2px;
  max-height: 220px;
  overflow-y: auto;
}
.owner-picker__row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  font-family: inherit;
  font-size: 12px;
  color: var(--color-text-primary);
  cursor: pointer;
  text-align: left;
  width: 100%;
}
.owner-picker__row:hover {
  border-color: var(--color-primary);
}
.owner-picker__name {
  font-weight: var(--fw-medium);
  flex-shrink: 0;
}

/* ── State segmented toggle (안읽음 / 읽음 / 처리완료) ─────────── */
.state-toggle-form { margin: 0; }
.state-toggle {
  display: flex;
  height: 36px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  overflow: hidden;
}
.state-toggle__seg {
  flex: 1 0 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--color-bg-base);
  border: none;
  border-right: 1px solid var(--color-border);
  font-family: inherit;
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  cursor: pointer;
  transition: background 0.1s, color 0.1s;
}
.state-toggle__seg:last-child { border-right: none; }
.state-toggle__seg:hover:not(.is-active) {
  background: var(--color-border);
}
.state-toggle__seg.is-active {
  background: var(--color-primary);
  color: var(--color-text-inverse);
  cursor: default;
}

/* Mid-screen variants for narrower viewports */
@media (max-width: 1100px) {
  .email-detail__body { grid-template-columns: 1fr; }
}

/* ═════════════════════════════════════════════════════════════════
 * Screen 1.7 — Announcements
 * Figma 120:115 (list), 120:210 (detail), 120:306 (compose).
 * ═════════════════════════════════════════════════════════════════ */

/* ── 1.7 list — card stack ──────────────────────────────────────── */
.announcement-list {
  margin-top: 12px;
  display: flex; flex-direction: column; gap: 12px;
}
.announcement-card {
  display: flex; align-items: center; gap: 16px;
  height: 88px;
  padding: 18px 24px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.1s, box-shadow 0.1s;
}
.announcement-card:hover {
  border-color: var(--color-primary);
  text-decoration: none;
}
.announcement-card__pin-col {
  width: 32px; height: 56px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.announcement-card__pin {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  border-radius: 16px;
  background: var(--color-warning);
  font-size: 14px;
}
.announcement-card__body {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; gap: 6px;
}
.announcement-card__title {
  font-size: 16px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.announcement-card__meta {
  display: flex; align-items: center; gap: 12px;
  font-size: 12px;
  color: var(--color-text-secondary);
}
.announcement-card__status {
  width: 120px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.announcement-list__empty {
  text-align: center;
  padding: 80px 20px;
  color: var(--color-text-muted);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 8px;
}

/* ── Acknowledgment chips (list "확인 필요" / "✓ 확인 완료") ──── */
.ack-chip {
  display: inline-flex; align-items: center;
  padding: 5px 10px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: var(--fw-medium);
}
.ack-chip--pending {
  background: var(--color-bg-base);
  border: 1px solid var(--color-warning);
  color: var(--color-warning);
}
.ack-chip--done {
  background: var(--color-success);
  border: 1px solid var(--color-success);
  color: var(--color-text-inverse);
}

/* ── 1.7 detail — title row + meta ──────────────────────────────── */
.announcement-detail__header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px;
  margin-top: 12px;
  margin-bottom: 16px;
}
.announcement-detail__title-row {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 8px;
}
.announcement-detail__title-row .heading-h1 {
  font-size: 24px;
  margin: 0;
}
.announcement-detail__meta {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  font-size: 13px;
  color: var(--color-text-secondary);
}
.announcement-detail__actions {
  display: flex; gap: 8px; flex-shrink: 0;
}

/* "📌 상단 고정" pill in the detail title */
.pin-badge {
  display: inline-flex; align-items: center;
  padding: 3px 8px;
  background: var(--color-warning);
  color: var(--color-text-inverse);
  border-radius: 4px;
  font-size: 11px;
  font-weight: var(--fw-medium);
}

/* ── Detail card wrappers ──────────────────────────────────────── */
.announcement-card-detail {
  padding: 24px 28px;
  margin-bottom: 16px;
}
.announcement-body {
  font-size: 14px;
  color: var(--color-text-primary);
  line-height: 1.8;
}
.announcement-body p { margin: 0 0 14px; }
.announcement-body p:last-child { margin-bottom: 0; }
.announcement-body ul, .announcement-body ol {
  padding-left: 24px;
  margin: 0 0 14px;
}
.announcement-body li { margin-bottom: 6px; }

/* Attachments inside detail/compose cards */
.announcement-attach__header {
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  margin-bottom: 10px;
}
.announcement-attach {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  margin-bottom: 8px;
}
.announcement-attach:last-child { margin-bottom: 0; }
.announcement-attach__icon {
  font-size: 18px;
  flex-shrink: 0;
}
.announcement-attach__meta {
  flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.announcement-attach__name {
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* "✓ 확인" big center button + "확인 완료" success state */
.announcement-ack-row {
  display: flex; align-items: center; justify-content: center;
  height: 60px;
  margin: 0 0 16px;
}
.btn-success {
  background: var(--color-success);
  color: var(--color-text-inverse);
  border: 1px solid var(--color-success);
}
.btn--ack-done {
  cursor: default;
}
.btn--ack-done:disabled {
  opacity: 1;  /* override the global .btn:disabled — green should stay vivid */
}

/* Acknowledgment panel — 2-column grid (확인 / 미확인) */
.announcement-ack__header {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 12px;
}
.ack-scope-chip {
  display: inline-flex; align-items: center;
  padding: 2px 6px;
  background: var(--color-bg-base);
  border-radius: 3px;
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
}
.announcement-ack__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  align-items: start;
}
.announcement-ack__col {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px 14px;
  background: var(--color-bg-base);
  border-radius: 6px;
  min-height: 120px;
}
.announcement-ack__title {
  font-size: 13px;
  font-weight: var(--fw-medium);
  margin-bottom: 4px;
}
.announcement-ack__title--done    { color: var(--color-success); }
.announcement-ack__title--pending { color: var(--color-warning); }
.announcement-ack__user {
  font-size: 12px;
  color: var(--color-text-primary);
}

/* ── 1.7 compose ────────────────────────────────────────────────── */
.announcement-compose-card {
  padding: 24px 28px;
  margin-top: 20px;
  display: flex; flex-direction: column; gap: 16px;
}
.announcement-compose__label {
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  margin: 0;
}
.announcement-compose__title-input {
  height: 44px;
  padding: 0 14px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-size: 14px;
  color: var(--color-text-primary);
  width: 100%;
  font-family: inherit;
}
.announcement-compose__title-input:focus {
  outline: 2px solid var(--color-primary);
  outline-offset: -1px;
}
.announcement-compose__pin-row {
  display: flex; align-items: center; gap: 10px;
}

/* Decorative rich-text toolbar */
.rt-toolbar {
  display: flex; align-items: center; gap: 4px;
  height: 40px;
  padding: 6px 8px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
}
.rt-toolbar__btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  cursor: default;  /* decorative in v1 */
}
.rt-toolbar__btn:hover { background: var(--color-border); }
.rt-toolbar__sep {
  width: 1px; height: 20px;
  background: var(--color-border);
  margin: 0 2px;
}

.announcement-compose__body {
  height: 280px;
  padding: 14px;
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-size: 14px;
  color: var(--color-text-primary);
  font-family: inherit;
  resize: vertical;
}
.announcement-compose__body:focus {
  outline: 2px solid var(--color-primary);
  outline-offset: -1px;
}

/* Dashed dropzone for file attach */
.announcement-compose__dropzone {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px;
  height: 130px;
  background: var(--color-bg-base);
  border: 1px dashed var(--color-accent);
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.1s;
}
.announcement-compose__dropzone:hover {
  background: var(--color-border);
}
.announcement-compose__dropzone-icon {
  font-size: 28px;
}
.announcement-compose__dropzone-title {
  font-size: 14px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
}
.announcement-compose__existing {
  margin-top: 8px;
  padding-top: 12px;
  border-top: 1px solid var(--color-border);
}

.text-danger {
  color: var(--color-danger);
  font-size: 12px;
  margin-top: -8px;
}

/* ═════════════════════════════════════════════════════════════════
 * Screens 4.1 / 4.2 / 4.3 — Admin
 * Figma 20:2 (사용자 관리), 20:291 (팀·브랜드), 20:464 (신비서 활동).
 * ═════════════════════════════════════════════════════════════════ */

/* ── Admin dropdown (topnav 관리자 button) ─────────────────────── */
.topnav__admin {
  position: relative;
}
.topnav__admin .admin-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  width: 240px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  padding: 6px;
  display: none;
  z-index: 100;
}

/* Invisible hit-area bridge filling the 6px visual gap between the
   icon and the dropdown. Without this, the cursor briefly leaves both
   elements while traveling from icon to menu and the :hover state is
   lost — making the dropdown flicker / disappear unpredictably. */
.topnav__admin .admin-dropdown::before {
  content: "";
  position: absolute;
  top: -8px;          /* slightly more than the 6px gap, with overlap */
  left: 0;
  right: 0;
  height: 8px;
  background: transparent;
}

.topnav__admin:hover .admin-dropdown,
.topnav__admin:focus-within .admin-dropdown { display: block; }
.admin-dropdown__header {
  font-size: 10px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
  padding: 8px 10px;
  border-bottom: 1px solid var(--color-border);
  margin-bottom: 2px;
}
.admin-dropdown__item {
  display: flex; gap: 10px; align-items: center;
  padding: 10px;
  border-radius: 6px;
  text-decoration: none;
  color: inherit;
  margin-bottom: 2px;
}
.admin-dropdown__item:last-child { margin-bottom: 0; }
.admin-dropdown__item:hover {
  background: var(--color-bg-base);
  text-decoration: none;
}
.admin-dropdown__item.is-active {
  background: var(--color-bg-base);
}
.admin-dropdown__num {
  display: inline-flex; align-items: center;
  background: var(--color-border);
  color: var(--color-text-secondary);
  border-radius: 3px;
  padding: 2px 6px;
  font-size: 10px;
  font-weight: var(--fw-medium);
}
.admin-dropdown__item.is-active .admin-dropdown__num {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}
.admin-dropdown__body {
  display: flex; flex-direction: column; gap: 2px;
}
.admin-dropdown__title {
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
}
.admin-dropdown__item.is-active .admin-dropdown__title {
  color: var(--color-primary);
}
.admin-dropdown__desc {
  font-size: 11px;
  color: var(--color-text-muted);
}

/* ── Generic admin DataTable ─────────────────────────────────────── */
.admin-table {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  /* `overflow: visible` so the per-row ⋯ dropdown can extend below the
   * row's edge. The 8px radius is preserved by the `border-radius` rule
   * itself; we don't need overflow:hidden for visual clipping. */
  overflow: visible;
}
/* Wide log-style tables (e.g. 4.3 신비서 활동 로그) that can't fit the
 * narrower 관리 콘솔 content pane: scroll horizontally INSIDE their own
 * card instead of spilling past it / forcing the whole page (and the
 * sidebar) to scroll. Only safe on tables with NO below-row dropdown or
 * hover bubble — overflow:auto would clip those. */
.admin-table--scroll {
  overflow-x: auto;
}
.admin-table__head {
  display: flex;
  align-items: center;
  gap: 16px;
  height: var(--table-header-height); /* one header height for every table (== AG grid header) */
  padding: 0 20px;
  background: var(--table-header-bg); /* one header background for every table (== AG grid header) */
  border-bottom: 1px solid var(--color-border);
}
.admin-table__h {
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-secondary);
  white-space: nowrap;
}
.admin-table__row {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 52px;
  padding: 0 20px;
  background: var(--color-bg-surface);
  border-bottom: 1px solid var(--color-border);
  border-left: none;
  border-right: none;
  border-top: none;
  /* Buttons-as-rows reset */
  text-align: left;
  font-family: inherit;
  width: 100%;
  cursor: default;
}
.admin-table__row--clickable {
  cursor: pointer;
  transition: background 0.1s;
}
.admin-table__row--clickable:hover { background: var(--color-bg-base); }
.admin-table__row--inactive {
  background: var(--color-bg-surface);
}
.admin-table__row--inactive .admin-table__name,
.admin-table__row--inactive .admin-table__mono,
.admin-table__row--inactive .admin-table__cell { color: var(--color-text-muted); }
.admin-table__cell {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  /* Prevent long content (e.g. test-report subjects in 4.3 신비서 활동 로그)
     from wrapping vertically and overlapping the next row. */
  min-width: 0;
  overflow: hidden;
}
/* Single-line clamp + ellipsis for inline text inside cells. */
.admin-table__cell > .text-mono,
.admin-table__cell > .text-meta {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  max-width: 100%;
}
.admin-table__cell--menu {
  position: relative;
  justify-content: flex-end;
  /* Override `.admin-table__cell { overflow: hidden }` so the row's
   * dropdown menu can extend beyond the cell's box. Without this, the
   * menu gets clipped to the cell's 60px width × 52px height and
   * appears as a tiny sliver — Playwright catches this as "cell
   * intercepts pointer events" because the visible portion is too
   * small to click. Bug pinned 2026-05-24 by Julian. */
  overflow: visible;
}

/* Same overflow-clipping bug as `--menu` above, different cell: the
 * chip-filter widget on 4.2 팀·브랜드 매핑 puts an absolutely-positioned
 * dropdown panel below its trigger. Without this override, the panel
 * is clipped at the row's bottom edge → click registers, aria-expanded
 * flips to true, but visually nothing appears. Bug surfaced 2026-05-25
 * when Julian repeatedly couldn't open the team chip widget.
 *
 * The row's flexbox + 52px height creates a clipping context. Add
 * `--chip` modifier class on cells that host the chip widget (set
 * explicitly in admin_brand_team_list.html) and override here. */
.admin-table__cell--chip {
  overflow: visible;
}

/* Same overflow-clipping bug as `--chip` above — the aliases input on
 * 4.2 hosts a CSS `::after` tooltip (positioned absolute, ABOVE the
 * input via `bottom: calc(100% + 4px)`). Without this override the
 * default `.admin-table__cell { overflow: hidden }` clips the bubble
 * and Julian (correctly) reports that the hover effect doesn't work.
 *
 * I shipped this fix once on 2026-05-25 without browser-verifying —
 * the wrapper was added but the cell modifier was missed. Per CLAUDE.md
 * "Browser-verify UI changes" rule, this is now covered by a Playwright
 * E2E test in tests_e2e/test_brand_team_mapping.py that hovers the
 * input and asserts the tooltip is visible. */
.admin-table__cell--alias {
  overflow: visible;
  /* Floor width so the flex:1 alias cell can't collapse to 0 when the
   * table is wider than its container (e.g. inside the narrower 관리 콘솔
   * content pane) — without it the input shrank to zero and vanished. */
  min-width: 200px;
}

/* Single-line clamp for free text inside an admin-table cell (e.g. the
 * 별칭 / 담당 팀 lists on 4.2). Keeps rows a uniform height; the full text
 * shows in the .alias-tooltip bubble on hover and on the detail page. */
.admin-table__ellipsis {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Action-button cells (e.g. 보관함의 보기/복구/영구 삭제 cluster).
 * Default `.admin-table__cell { overflow: hidden }` clips long button
 * groups — the bug was Julian seeing "영구 삭" with the last character
 * sliced off on the archive page. Right-align the cluster so the most
 * destructive action sits flush with the row's right edge (consistent
 * with the kebab-menu cell, which already uses `--menu`). */
.admin-table__cell--actions {
  overflow: visible;
  justify-content: flex-end;
  flex-wrap: nowrap;
}
.admin-table__name {
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
}
.admin-table__mono {
  font-size: 12px;
  color: var(--color-text-primary);
}
/* …but a mono value that is a LINK keeps the link colour. `.admin-table__mono` is a class and `a` is
 * an element, so the class wins on specificity and a 발주번호 rendered as an anchor came out looking
 * exactly like the plain text beside it. Element+class (0,1,1) beats the class alone (0,1,0). */
a.admin-table__mono { color: var(--color-link); }
a.admin-table__mono:hover { color: var(--color-link-hover); }
.admin-table__summary {
  font-size: 12px;
  color: var(--color-text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: block;
  width: 100%;
}
.admin-table__empty {
  padding: 60px 20px;
  text-align: center;
  color: var(--color-text-muted);
}

/* Per-row ⋯ menu trigger + dropdown */
.row-menu__trigger {
  /* z-index higher than .row-menu (100) so that an open menu on one
   * row doesn't block clicks on the trigger of the next row. Without
   * this, opening a menu makes the next row's ⋯ unclickable because
   * the dropdown physically overlaps it (Playwright caught this
   * 2026-05-24 via test_admin_user_row_menu). */
  position: relative;
  z-index: 200;
  background: none;
  border: none;
  color: var(--color-text-secondary);
  font-size: 18px;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 4px;
}
.row-menu__trigger:hover { background: var(--color-bg-base); }
.row-menu {
  position: absolute;
  top: 100%;
  right: 0;
  /* z-index must beat the row triggers (200) so an open dropdown covers
   * rather than is-poked-through-by the ⋯ buttons of rows below it.
   * 2026-05-24 the original design had triggers > dropdown so a user
   * could quickly switch between adjacent menus, but staging review
   * 2026-05-28 reported this as confusing (peer triggers visible AND
   * clickable through the open dropdown panel). The click-outside-
   * closes listener in admin_user_list.html template handles the
   * "switch menus" UX path now: click on row 2's trigger area closes
   * row 1's dropdown via document.addEventListener, then a second click
   * opens row 2's. Modals overlay everything at higher z-index. */
  z-index: 300;
  min-width: 160px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* Higher-specificity override of `display: flex` so the [hidden] attribute
 * actually hides the menu. Without this `.row-menu` wins on specificity and
 * every per-row menu is permanently visible. */
.row-menu[hidden] { display: none; }
.row-menu__item {
  display: block;
  padding: 8px 12px;
  background: var(--color-bg-surface);
  border: none;
  border-radius: 4px;
  font-family: inherit;
  font-size: 12px;
  color: var(--color-text-primary);
  text-align: left;
  cursor: pointer;
  width: 100%;
}
.row-menu__item:hover { background: var(--color-bg-base); }
.row-menu__item--danger { color: var(--color-danger); }
.row-menu__item--danger:hover { background: rgba(179, 38, 30, 0.06); }

/* + 계정 생성 modal grid (2-col) */
.user-create-form {
  display: flex; flex-direction: column;
}
.user-create-form__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px 16px;
  padding: 18px 24px;
}
/* In the 관리 콘솔 detail panels the card already supplies padding, so drop
 * the grid's own inset — otherwise the fields sit indented from the panel
 * title + buttons (which align to the card edge). Roomier row/column gaps so
 * the stacked settings don't feel crammed. */
.console-panel .user-create-form__grid {
  padding: 0;
  gap: 22px 24px;
}
.console-panel .user-create-form .form-label {
  margin-bottom: 7px;
}
/* A touch more separation before the save/cancel actions. */
.console-panel .user-create-form > div:last-child {
  margin-top: 26px !important;
}
.user-create-form .form-label {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
  margin-bottom: 4px;
  display: block;
}

/* btn-secondary--sm — for the inline 저장 button on 4.2 */
.btn-secondary--sm {
  height: 30px;
  padding: 0 12px;
  font-size: 12px;
  border-radius: 4px;
}

/* Activity detail modal */
.activity-modal__body {
  padding: 18px 24px;
  display: flex; flex-direction: column; gap: 12px;
  max-height: 70vh;
  overflow-y: auto;
}
.activity-modal__row {
  display: flex; gap: 16px; align-items: baseline;
}
.activity-modal__label {
  width: 120px;
  font-size: 12px;
  font-weight: var(--fw-medium);
  color: var(--color-text-muted);
  flex-shrink: 0;
}
.activity-modal__section {
  display: flex; flex-direction: column; gap: 4px;
  margin-top: 4px;
}
.activity-modal__pre {
  background: var(--color-bg-base);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 10px 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--color-text-primary);
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
}

/* ─── Alert list (1.4 — frontend §5.4) ────────────────────────────
 *
 * Per Figma node 15:81: each row is a 72px-tall horizontal flex
 *   [unread dot] [colored kind icon 32x32] [title + subtitle stacked] [time]
 * Background swaps light-gray ↔ white based on read state.
 * Icon block color encodes kind (red = test fail, blue = info/agent, etc.).
 */

.alert-list {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  margin-top: var(--space-4);
}

.alert-row-form { margin: 0; }

.alert-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  height: 72px;
  padding: 0 20px;
  background: var(--color-bg-surface);  /* read = white */
  border: 0;
  border-bottom: 1px solid var(--color-border);
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
  transition: background 120ms ease;
}
.alert-row:hover { background: var(--color-bg-base); }
.alert-row:last-child { border-bottom: 0; }

.alert-row--unread {
  background: var(--color-bg-base);  /* unread = light gray */
}

/* Red dot — only visible on unread rows. The spacer keeps row alignment
 * consistent regardless of read state (so titles don't jitter left/right). */
.alert-row__dot,
.alert-row__dot-spacer {
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-radius: 4px;
}
.alert-row__dot {
  background: var(--color-danger);
}
.alert-row__dot-spacer {
  background: transparent;
}

/* Colored icon block — color encodes notification kind */
.alert-row__icon {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  background: var(--color-info);  /* default (mention / fallback) */
}
.alert-row--test_failure   .alert-row__icon { background: var(--color-danger);  }
.alert-row--email_assigned .alert-row__icon { background: var(--color-info);    }
.alert-row--mention        .alert-row__icon { background: var(--color-info);    }

/* Title + subtitle stack */
.alert-row__main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  overflow: hidden;
}

.alert-row__title {
  font-size: var(--fs-body);
  font-weight: var(--fw-medium);
  color: var(--color-text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.alert-row__body {
  font-size: var(--fs-meta);
  font-weight: var(--fw-regular);
  color: var(--color-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.alert-row__time {
  flex-shrink: 0;
  font-size: var(--fs-meta);
  color: var(--color-text-muted);
}

/* ─── Utility ────────────────────────────────────────────────────── */

.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;
}

/* ─── Leave-confirmation modal ───────────────────────────────────────
 *
 * Shown on screens with editable forms (order detail 2.2, test detail
 * 3.2) when the user attempts in-app navigation with unsaved changes.
 * Appended to <body> by JS, not rendered server-side.
 *
 * Uses the existing design system colors: surface white, primary navy
 * (--color-primary), secondary text muted. No yellow/warm tones — the
 * modal matches the rest of the app palette. */

.leave-modal {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: none;
  align-items: center;
  justify-content: center;
}
.leave-modal.is-visible { display: flex; }
.leave-modal__backdrop {
  position: absolute;
  inset: 0;
  /* Strong dim so the form behind doesn't read through. */
  background: rgba(15, 23, 42, 0.62);
  /* Optional blur on supported browsers — keeps the dimmed area readable
     while obscuring everything underneath. Falls back gracefully. */
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}
.leave-modal__panel {
  position: relative;
  /* Explicit solid white — never relies on a var that might be tinted. */
  background: #ffffff;
  border-radius: 14px;
  box-shadow:
    0 1px 2px rgba(15, 23, 42, 0.08),
    0 16px 40px rgba(15, 23, 42, 0.30),
    0 24px 60px rgba(15, 23, 42, 0.18);
  padding: 32px 32px 24px;
  max-width: 460px;
  width: calc(100% - 48px);
  text-align: left;
}
.leave-modal__title {
  margin: 0 0 14px;
  font-size: 19px;
  font-weight: var(--fw-bold);
  color: var(--color-text-primary);
  letter-spacing: -0.01em;
}
.leave-modal__body {
  margin: 0 0 26px;
  font-size: 14px;
  line-height: 1.7;
  color: var(--color-text-secondary);
}
.leave-modal__actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  flex-wrap: wrap;
}
.leave-modal__actions .btn {
  min-width: 96px;
}

/* ─── Print ────────────────────────────────────────────────────────────
   Order-detail "인쇄" button calls window.print(). Strip the app chrome so
   the printout is just the order content. Anything tagged .no-print (e.g.
   the order action bar) is also dropped. */
@media print {
  .topnav,
  .site-footer,
  .scroll-to-top,
  .section-nav,
  .no-print {
    display: none !important;
  }
  /* Unpin the frozen top panel so it prints inline, not as a fixed overlay. */
  .order-sticky-top {
    position: static !important;
  }
  /* Use the full page width once the sidebar is gone. */
  .page {
    max-width: none !important;
    margin: 0 !important;
    padding: 0 !important;
  }
  body {
    background: #fff !important;
  }
}

/* ─── Order comments (댓글 section, 2.2) — avatar + chat-bubble layout ── */
.comments-card { margin-top: 16px; }

/* New-message composer: current-user avatar + textarea + button */
.comment-new {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin: 6px 0 18px;
}
.comment-new > .avatar-initials { flex: 0 0 auto; margin: 4px 0 0; }
.comment-new textarea { flex: 1; }

.comment-list { display: flex; flex-direction: column; gap: 16px; }
.comment-empty {
  text-align: center;
  padding: 18px 0;
}

/* A comment = avatar column + content column (Slack/Reddit-style) */
.comment {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
.comment__avatar { flex: 0 0 auto; margin: 1px 0 0; }
.comment--reply > .comment__avatar {
  width: 20px;
  height: 20px;
  font-size: 10px;
  background: var(--color-text-secondary);
}
.comment__main { flex: 1; min-width: 0; }
.comment--reply { margin-top: 12px; }

.comment__head {
  display: flex;
  gap: 8px;
  align-items: baseline;
  margin-bottom: 3px;
}
.comment__author { font-weight: 700; font-size: 13px; }
.comment__time { color: var(--color-text-secondary); font-size: 11px; }

/* Body bubble — light grey for top-level, white-bordered for replies */
.comment__body {
  font-size: 13px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  background: #f3f5f7;
  border-radius: 10px;
  padding: 8px 12px;
}
.comment--reply .comment__body {
  background: #fff;
  border: 1px solid #eef1f4;
}
.comment__body--deleted { color: #9aa5b1; font-style: italic; }

.comment__actions {
  display: flex;
  gap: 14px;
  align-items: center;
  margin: 5px 0 0 2px;
}
.comment__tool { position: relative; }
.comment__tool > summary { list-style: none; cursor: pointer; }
.comment__tool > summary::-webkit-details-marker { display: none; }
.comment__action {
  border: 0;
  background: none;
  padding: 0;
  font-size: 12px;
  color: var(--color-text-secondary);
  cursor: pointer;
}
.comment__action:hover { color: var(--color-primary); text-decoration: underline; }
.comment__action--danger { color: #b3261e; }
.comment-reply-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
  max-width: 520px;
}
.comment-reply-form .btn { align-self: flex-start; }

/* 원가 요약 — manual inputs (feedback #12) live in their own "수동 입력" strip
   below the computed totals (option 2 layout), divided off so computed vs
   hand-entered read as two distinct groups. The strip holds all three inputs
   in one even row; a grey left accent marks each as editable. */
.bom-summary__manual {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px dashed var(--color-border);
}
.bom-summary__manual-label {
  margin: 0 0 10px;
  font-size: 11px;
  font-weight: var(--fw-medium);
  letter-spacing: 0.04em;
  color: var(--color-text-secondary);
}
.bom-summary__cell--input {
  border-left-color: #9aa5b1;
}
.bom-summary__input {
  margin: 0;
}
.bom-summary__input input {
  width: 100%;
}

/* ===================================================================
 * 관리 콘솔 (settings console) — the consolidated 4.x admin environment.
 * A lighter-navy sidebar (sections 4.1–4.5) + a content pane, replacing
 * the old topnav 관리자 dropdown. Sidebar navy is intentionally lighter
 * than the app top-nav (--color-primary #1F3A5F) so the console reads as
 * a distinct "you've stepped into settings" environment.
 * =================================================================== */
:root {
  --console-navy:       #4A6FA8;   /* lighter than top-nav navy */
  --console-navy-dark:  #3E5F92;   /* hover wash */
  --console-nav-w:      248px;
}

/* Drop the default <main class="page"> max-width/padding so the console
 * spans the full app width like its own screen. */
body.has-console main.page {
  max-width: none;
  margin: 0;
  padding: 0;
}

.console {
  display: flex;
  align-items: stretch;
  min-height: 100vh;
}

/* ── Sidebar ─────────────────────────────────────────────────────── */
.console__nav {
  flex: 0 0 var(--console-nav-w);
  width: var(--console-nav-w);
  background: var(--console-navy);
  color: #fff;
  padding: 0 0 24px;
  display: flex;
  flex-direction: column;
}

.console__brand {
  padding: 18px 20px 16px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}
.console__back {
  display: inline-block;
  font-size: 12px;
  color: rgba(255, 255, 255, 0.75);
  text-decoration: none;
  margin-bottom: 12px;
}
.console__back:hover { color: #fff; text-decoration: underline; }
.console__brand-title {
  font-size: 18px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.01em;
}
.console__brand-sub {
  margin-top: 3px;
  font-size: 10px;
  font-weight: var(--fw-medium);
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.55);
}
/* App version - relocated out of the content footer (ADR 0045); sits quietly under the brand. */
.console__brand-ver {
  margin-top: 8px;
  font-size: 10px;
  letter-spacing: 0.04em;
  color: rgba(255, 255, 255, 0.38);
}

.console__menu { padding: 12px 12px 0; }
.console__group {
  margin: 14px 8px 6px;
  font-size: 10px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.08em;
  color: rgba(255, 255, 255, 0.5);
}
.console__group:first-child { margin-top: 4px; }

.console__item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 12px;
  border-radius: 8px;
  color: rgba(255, 255, 255, 0.88);
  font-size: 14px;
  text-decoration: none;
  transition: background 0.12s ease;
}
.console__item:hover { background: var(--console-navy-dark); text-decoration: none; }
.console__item.is-active {
  background: #fff;
  color: var(--color-primary);
  font-weight: var(--fw-medium);
}
/* Colored icon chip — tint comes from the inline --ico custom property.
 * Off-state icons sit in a soft translucent square so they read against
 * the navy; the active (white) row shows the icon in its own color. */
.console__ico {
  flex: 0 0 28px;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
}
.console__item.is-active .console__ico {
  background: color-mix(in srgb, var(--ico) 16%, transparent);
  color: var(--ico);
}
.console__num {
  font-family: var(--font-mono);
  font-size: 11px;
  opacity: 0.7;
  margin-right: 4px;
}
.console__item.is-active .console__num { opacity: 0.85; }

/* ── Content pane ────────────────────────────────────────────────── */
.console__main {
  flex: 1 1 auto;
  min-width: 0;
  /* Dense shell: a 16px gutter on every side (ERP = information first). On the desktop
   * fixed shell the inline gutter is split with .console__scroll's clip-protection
   * padding (components.css) so the NET gutter stays 16px there too. */
  padding: var(--space-4);
  background: var(--color-bg-base);
}
.console__crumb {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--color-text-secondary);
  margin-bottom: 18px;
}
.console__crumb-root { color: var(--color-text-secondary); text-decoration: none; }
.console__crumb-root:hover { color: var(--color-primary); text-decoration: underline; }
.console__crumb-sep { color: var(--color-border); }
.console__crumb-cur { color: var(--color-text-primary); font-weight: var(--fw-medium); }
.console__messages { margin-bottom: 16px; }
.console__messages .alert { margin-bottom: 8px; }

/* ── Detail page (4.1 user detail; reused by later sections) ─────── */
.console-back-link {
  font-size: 13px;
  color: var(--color-text-secondary);
  text-decoration: none;
}
.console-back-link:hover { color: var(--color-primary); text-decoration: underline; }

.console-detail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 24px;
  align-items: start;
}
.console-panel {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  padding: 28px 32px;
}
.console-panel__title {
  margin: 0 0 22px;
  font-size: 12px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
}
.console-meta { margin: 0 0 18px; }
.console-meta dt {
  font-size: 11px;
  color: var(--color-text-muted);
  margin-top: 10px;
}
.console-meta dt:first-child { margin-top: 0; }
.console-meta dd { margin: 2px 0 0; font-size: 13px; color: var(--color-text-primary); }
.console-actions { display: flex; flex-direction: column; gap: 8px; }
.console-actions form { margin: 0; }
.console-actions .btn { width: 100%; justify-content: center; }

/* Clickable admin-table rows (4.1 list → detail). The row navigates;
 * the ⋯ menu cell and any link/form/button inside it are excluded by the
 * row's inline onclick guard. */
.admin-table__row--link { cursor: pointer; }
.admin-table__row--link:hover { background: var(--color-bg-base); }
.admin-table__name--link { color: var(--color-primary); text-decoration: none; font-weight: var(--fw-medium); }
.admin-table__name--link:hover { text-decoration: underline; }
