/* VLAN24 공통 UI — 운영 콘솔과 고객 포털이 함께 쓴다.

   이 파일의 원본은 이 저장소에 있고, 고객 포털(C:\Dev\Portal)에는 scripts/sync-ui.js 가
   복사해 넣는다. 포털 쪽 사본을 손으로 고치지 마세요 — 다음 동기화 때 덮어씁니다.
   두 벌이 어긋나면 tests/ui-sync.test.js 가 잡습니다.

   저장소를 하나로 합치지 않은 이유는, 포털이 콘솔의 CSS 를 <link> 로 가져오게 하면 콘솔이
   내려간 동안 포털이 맨몸으로 뜨기 때문이다. 고객이 보는 화면의 가용성을 직원 화면에
   묶을 수는 없다. 그래서 복사하고, 어긋나면 검사가 잡는 쪽을 골랐다.

   여기에는 토큰과 공통 부품(뼈대·글자·단추·입력·카드·표·배지·빈 화면·모달)만 둔다.
   화면별 규칙은 각자의 파일에 둔다 — 콘솔은 design-system.css, 포털은 portal/styles.css.

   밀도는 부품을 두 벌 만들어서가 아니라 토큰으로 가른다. 콘솔은 --density: 1 로 촘촘하게,
   포털은 1.25 로 여유 있게 — 같은 부품이 두 화면에서 다른 눈금으로 나온다.

   ── 토큰 ────────────────────────────────────────────────────────────────── */
:root {
  /* 글자 크기 단계 — Apple HIG(macOS) 글자 스타일을 웹 밀도에 맞춘 일곱 단계. 이 밖의 크기는 쓰지 않는다.
     micro 11(배지 숫자·작은 표식) · caption 12(보조 글, 표 머리, 라벨) · callout 13(단추·입력·탭)
     · body 14(본문·표 칸·목록) · title3 17(판 제목) · title1 22(화면 제목) · display 28(강조 숫자). */
  --fs-micro: 11px;
  --fs-caption: 12px;
  --fs-callout: 13px;
  --fs-body: 14px;
  --fs-title3: 17px;
  --fs-title1: 22px;
  --fs-display: 28px;
  /* 중립 계열이 화면의 99% 를 덮는다. 단계는 배경 → 선 → 보조 글자 → 본문 순으로 는다. */
  --n-0: #ffffff;
  --n-25: #fcfcfc;
  --n-50: #f5f5f7;
  --n-100: #f1f1f2;
  --n-150: #e9e9eb;
  --n-200: #e2e2e5;
  --n-300: #cfcfd4;
  --n-400: #a5a5ae;
  --n-500: #75757f;
  --n-600: #56565f;
  --n-700: #3d3d45;
  --n-800: #27272d;
  --n-900: #18181b;
  --n-950: #0d0d0f;

  /* 강조는 하나다. 주 행동·선택 상태·진행 중에만 쓴다. */
  --accent: #2f6feb;
  --accent-hover: #2159c9;
  --accent-text: #2159c9;
  --accent-soft: #eaf1fe;
  --accent-border: #c3d8fb;

  /* 상태 색. 장식이 아니라 뜻이 있는 자리에만 쓴다. */
  --danger: #c4373b;
  --danger-soft: #fdeceb;
  --danger-border: #f4cac7;
  --warn: #9a6205;
  --warn-soft: #fdf3e2;
  --warn-border: #f0ddb6;
  --ok: #1f7a4d;
  --ok-soft: #e9f6ef;
  --ok-border: #bfe3d0;

  /* 의미 이름. 화면 규칙은 이쪽만 본다. */
  --bg: var(--n-50);
  --bg-sunken: var(--n-100);
  --surface: var(--n-0);
  --surface-2: var(--n-25);
  --surface-hover: var(--n-50);
  --surface-active: var(--n-100);
  --sidebar-bg: var(--n-100);
  --topbar-bg: rgba(255, 255, 255, 0.86);
  --border: var(--n-200);
  --border-strong: var(--n-300);
  --divider: var(--n-150);
  --text: var(--n-900);
  --text-secondary: var(--n-600);
  /* 밝은 페이지/표 머리 위의 보조 글자. 중립/테두리 계열의 기존 값은 유지한다. */
  --text-muted: #686872;
  --text-faint: #6b6b75;
  --text-invert: var(--n-0);
  --focus-ring: 0 0 0 2px var(--surface), 0 0 0 4px rgba(47, 111, 235, 0.55);

  /* styles.css 가 쓰던 옛 이름들. 남겨 두면 손대지 않은 화면도 새 색을 따라온다. */
  --ink: var(--text);
  --muted: var(--text-muted);
  --line: var(--border);
  --panel: var(--surface);
  --panel-soft: var(--surface-2);
  --navy: var(--sidebar-bg);
  --navy-2: var(--surface-active);
  --blue: var(--accent);
  --blue-hover: var(--accent-hover);
  --critical: var(--danger);
  --high: var(--warn);
  --normal: var(--accent);
  --low: var(--text-muted);
  --success: var(--ok);
  --warning: var(--warn);

  /* 4px 눈금. 운영 화면이라 여백은 넉넉하기보다 규칙적인 쪽을 고른다. */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;
  --s-10: 40px;

  --radius-sm: 4px;
  --radius: 8px;
  --radius-lg: 12px;
  --radius-pill: 999px;

  /* 떠 있는 것에만 쓴다. */
  --shadow-pop: 0 1px 2px rgba(13, 13, 15, 0.06), 0 8px 24px rgba(13, 13, 15, 0.1);
  --shadow-modal: 0 2px 8px rgba(13, 13, 15, 0.08), 0 24px 56px rgba(13, 13, 15, 0.18);
  --shadow: none;

  --sans: Pretendard, 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --mono: ui-monospace, 'JetBrains Mono', 'Cascadia Code', 'SFMono-Regular', Consolas, monospace;

  /* 100~200ms. 장식이 아니라 "지금 눌렸다" 를 알리는 용도다. */
  --t-fast: 120ms cubic-bezier(0.4, 0, 0.2, 1);
  --t: 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
  --t-slow: 240ms cubic-bezier(0.2, 0.8, 0.2, 1);

  /* 밀도. 콘솔은 촘촘하게(1), 고객 포털은 여유 있게(1.25) — 부품을 두 벌 만들지 않고
     같은 부품이 두 화면에서 다른 눈금으로 나오게 하는 자리다. 포털은 portal/styles.css 에서
     이 세 값만 다시 적는다. */
  --density: 1;
  --row-h: 34px;
  --page-max: none;

  --sidebar-w: 236px;
  --topbar-h: 52px;
}

body.theme-dark {
  --n-0: #141417;
  --n-25: #17171a;
  --n-50: #1b1b1f;
  --n-100: #202025;
  --n-150: #26262c;
  --n-200: #2d2d34;
  --n-300: #3a3a43;
  --n-400: #55555f;
  --n-500: #83838e;
  --n-600: #a1a1ab;
  --n-700: #c2c2ca;
  --n-800: #dededf;
  --n-900: #f2f2f3;
  --n-950: #ffffff;

  --accent: #5b8def;
  --accent-hover: #7aa4f5;
  --accent-text: var(--accent);
  --accent-soft: #1b2743;
  --accent-border: #2f4372;

  --danger: #f08b8b;
  --danger-soft: #341d1f;
  --danger-border: #58302f;
  --warn: #e2b264;
  --warn-soft: #302617;
  --warn-border: #4e3e22;
  --ok: #6ec79a;
  --ok-soft: #16281f;
  --ok-border: #24422f;

  /* 어두운 판에서는 카드를 테두리가 아니라 바탕의 밝기 차이로 세운다.

     처음에는 바탕(#0f0f11)과 카드(#17171a)의 차이가 8단계밖에 안 돼, 카드 안쪽이 페이지와
     구분되지 않고 테두리만 남았다. 그러면 카드가 아니라 허공에 뜬 테처럼 보인다 — 근무
     현황 두 장이 딱 그렇게 보였다. 바탕을 더 내리고 카드를 올려 차이를 두 배로 벌리고,
     대신 테두리는 낮춰 선이 혼자 빛나지 않게 한다. */
  --bg: #0a0a0c;
  --bg-sunken: #070708;
  --surface: #17171b;
  --surface-2: #1c1c21;
  --surface-hover: #202026;
  --surface-active: #26262c;
  --sidebar-bg: #111114;
  --topbar-bg: rgba(23, 23, 27, 0.88);
  --border: #26262c;
  --border-strong: #34343c;
  --divider: #202026;
  --text: var(--n-900);
  --text-secondary: var(--n-600);
  /* 보조 글자는 어두운 카드(--surface-2) 위에서 4.5:1 을 넘게 둔다(2026-10-05 측정).
     n-500(#83838e)·n-400(#55555f) 그대로는 4.1:1·2.4:1 이라 «subtext»·묶음 제목이 흐렸다.
     밝기 사다리(n-*)는 테두리·아이콘에도 쓰이므로 건드리지 않고 글자 토큰만 올린다. */
  /* 흰 글자를 얹는 파랑(주 단추·배지). --accent(#5b8def)는 글자·테두리용으로 두고, 흰 글자 위로는 3.2:1 이라 따로 둔다. */
  --accent-fill: #3a6bd6;
  /* 옛 이름 별칭은 :root 에서 한 번 계산되어 다크 값을 따라오지 않는다. 다크에서 다시 적는다
     (Handover 는 design-system.css 가 따로 다시 적어 가려져 있었고, Portal 다크에서 드러났다). */
  --ink: var(--text);
  --muted: var(--text-muted);
  --line: var(--border);
  --panel: var(--surface);
  --panel-soft: var(--surface-2);
  --navy: var(--sidebar-bg);
  --navy-2: var(--surface-active);
  --blue: var(--accent);
  --blue-hover: var(--accent-hover);
  --text-muted: #8e8e96;
  --text-faint: #86868e;
  --text-invert: #0f0f11;
  --focus-ring: 0 0 0 2px var(--surface), 0 0 0 4px rgba(91, 141, 239, 0.65);
  --shadow-pop: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 24px rgba(0, 0, 0, 0.45);
  --shadow-modal: 0 2px 8px rgba(0, 0, 0, 0.5), 0 24px 56px rgba(0, 0, 0, 0.6);
}

/* ── 바탕과 글자 ───────────────────────────────────────────────────────────
   운영 화면은 하루 종일 열려 있다. 본문은 13.5px 로 낮추되 줄 간격을 1.5 로 두어
   표가 빽빽해도 줄이 붙어 보이지 않게 한다. */
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: var(--fs-body);
  line-height: 1.5;
  letter-spacing: -0.006em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
body,
input,
select,
textarea,
button {
  font-family: var(--sans);
}

/* 글자 층위. 크기보다 굵기와 색으로 나눈다 — 크기만으로 나누면 표가 들쭉날쭉해진다. */
.page-head h1,
h1 {
  font-size: var(--fs-title1);
  font-weight: 620;
  line-height: 1.25;
  letter-spacing: -0.021em;
  color: var(--text);
  margin: 0;
}
.panel-head h2,
h2 {
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.012em;
  color: var(--text);
  margin: 0;
}
h3 {
  font-size: var(--fs-callout);
  font-weight: 600;
  letter-spacing: -0.008em;
  margin: 0;
}
.page-head p {
  font-size: var(--fs-callout);
  color: var(--text-muted);
  margin: 4px 0 0;
  letter-spacing: 0;
}
.meta,
.subtext,
.field-hint,
small {
  font-size: var(--fs-caption);
  color: var(--text-muted);
  letter-spacing: 0;
  line-height: 1.45;
}
/* 라틴 문자 라벨만 대문자 자간을 준다. 한글에는 자간을 벌리지 않는다 — 벌리면 읽는 속도가
   오히려 떨어진다. */
.label-caps,
.data-table thead th,
.task-list thead th,
.table thead th {
  font-size: var(--fs-micro);
  font-weight: 560;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.task-id,
.customer-number,
kbd,
code,
.mono,
time {
  font-family: var(--mono);
  font-size: var(--fs-caption);
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
/* 숫자가 줄마다 흔들리지 않게 한다. 자릿수가 다른 값을 세로로 비교하는 화면이 많다. */
td,
.kpi strong,
.stat-value {
  font-variant-numeric: tabular-nums;
}
a {
  color: var(--accent);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}

/* ── 뼈대 ──────────────────────────────────────────────────────────────── */
.shell {
  min-height: 100vh;
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  background: var(--bg);
}
.main {
  min-width: 0;
  background: var(--bg);
}

/* 사이드바를 밝게 바꾼다. 어두운 기둥은 화면 왼쪽에 계속 무게를 실어, 정작 봐야 하는
   본문보다 먼저 눈에 들어온다. */
.sidebar {
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  background: var(--sidebar-bg);
  color: var(--text-secondary);
  border-right: 1px solid var(--border);
  padding: var(--s-3) var(--s-2) var(--s-3);
  gap: 2px;
}
.brand {
  display: flex;
  color: var(--text);
  font-size: var(--fs-body);
  font-weight: 620;
  letter-spacing: -0.015em;
  padding: var(--s-2) var(--s-2) var(--s-4);
  gap: var(--s-2);
}
/* 브랜드는 두 조각이다 — 굵은 제품 이름과 옅은 화면 이름. «VLAN24 Operations» 와
   «VLAN24 Portal» 이 같은 자리에 같은 모양으로 있어야, 두 화면을 번갈아 보는 사람이 왼쪽 위만
   보고 지금 어느 쪽인지 안다. 이 두 줄도 콘솔의 design-system.css 에만 있어서 포털에는 가지
   않았다. */
.brand b {
  color: var(--text);
  font-size: var(--fs-body);
  font-weight: 620;
  letter-spacing: -0.015em;
}
.brand span {
  color: var(--text-muted);
  font-size: var(--fs-caption);
  font-weight: 450;
  letter-spacing: 0;
}

.brand-mark,
.avatar {
  background: var(--n-900);
  color: var(--n-0);
  border-radius: var(--radius-sm);
  box-shadow: none;
}
body.theme-dark .brand-mark,
body.theme-dark .avatar {
  background: var(--n-300);
  color: var(--n-950);
}
.brand-mark {
  width: 24px;
  height: 24px;
  font: 600 var(--fs-micro) var(--mono);
}
.workspace {
  color: var(--text-faint);
  font: var(--fs-micro) var(--mono);
  letter-spacing: 0.08em;
  padding: 0 var(--s-2) var(--s-2);
}
.nav {
  display: grid;
  overflow-y: auto;
  min-height: 0;
  gap: 1px;
  padding-right: 2px;
}
.nav button {
  /* 줄의 뼈대. 지금까지 이 세 줄이 콘솔의 옛 styles.css 에만 있어서, 같은 공통 부품을 쓰는데도
     포털에서는 메뉴가 브라우저 기본 단추(회색 상자에 가운데 정렬)로 그려지고 있었다. 부품을
     나눠 쓰기로 했으면 그 부품이 서는 데 필요한 것도 함께 나눠야 한다. */
  display: flex;
  align-items: center;
  width: 100%;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
  color: var(--text-secondary);
  border-radius: var(--radius);
  padding: 6px var(--s-2);
  gap: var(--s-2);
  font-size: var(--fs-callout);
  font-weight: 450;
  min-height: 30px;
  transition:
    background var(--t-fast),
    color var(--t-fast);
}
.nav button:hover {
  background: var(--surface-active);
  color: var(--text);
  box-shadow: none;
}
/* 지금 어디에 있는지는 배경만으로 말하지 않는다. 글자도 함께 진해진다. */
.nav button.active {
  background: var(--surface-active);
  color: var(--text);
  font-weight: 560;
  box-shadow: none;
}
body.theme-dark .nav button.active {
  background: var(--surface-hover);
}
.nav button .nav-icon,
.nav-icon {
  width: 15px;
  height: 15px;
  opacity: 0.75;
  flex: none;
}
.nav button.active .nav-icon {
  opacity: 1;
}
/* 메뉴 아이콘은 선으로 그린 SVG 다. 글자와 같은 색으로 따라오게 둔다 — 그림글자(emoji)를
   쓰지 않는 이유가 이것이다. 그림글자는 글꼴이 그린 그림이라 색을 물려받지 않아, 고른 메뉴가
   진해져도 아이콘만 제 색으로 남는다.

   이 규칙도 콘솔의 design-system.css 에만 있었다. 포털에서는 선이 없는 채로 fill 이 먹어
   아이콘이 검은 덩어리로 보였다. */
.nav-icon {
  stroke: currentColor;
  fill: none;
  stroke-width: 1.6;
}
.nav .section {
  color: var(--text-faint);
  font-size: var(--fs-micro);
  font-weight: 560;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: var(--s-4) var(--s-2) var(--s-1);
}
.nav .indent {
  padding-left: 30px;
  font-size: var(--fs-callout);
}
.side-user {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--s-2);
  gap: var(--s-2);
  margin-top: auto;
}
.side-user b {
  color: var(--text);
  font-size: var(--fs-callout);
  font-weight: 560;
}
.side-user small,
.side-user span {
  color: var(--text-muted);
  font-size: var(--fs-micro);
}
.avatar {
  width: 26px;
  height: 26px;
  font-size: var(--fs-micro);
  font-weight: 600;
  border-radius: var(--radius-sm);
}

/* 상단 바. 조작만 담고 높이를 낮춘다 — 화면 이름은 바로 아래 본문 머리에 있다. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  height: var(--topbar-h);
  min-height: var(--topbar-h);
  padding: 0 var(--s-5);
  background: var(--topbar-bg);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: var(--s-3);
}
.top-actions {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-left: auto;
}
.local-clock {
  font-family: var(--sans);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-caption);
  color: var(--text-muted);
  white-space: nowrap;
}
/* 검색은 상단 바에서 가장 넓은 자리를 갖는다. 운영 중 이동은 대부분 검색으로 일어난다. */
.global-search-trigger {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 260px;
  max-width: 420px;
  flex: 1 1 320px;
  height: 32px;
  padding: 0 var(--s-2) 0 var(--s-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: var(--fs-callout);
  cursor: pointer;
  transition:
    border-color var(--t-fast),
    background var(--t-fast);
}
.global-search-trigger:hover {
  border-color: var(--border-strong);
  background: var(--surface-hover);
}
.global-search-trigger b {
  font-weight: 400;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.global-search-trigger kbd {
  margin-left: auto;
  padding: 2px 5px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text-faint);
  font-size: 10px;
}

/* ── 본문 ─────────────────────────────────────────────────────────────── */
.page {
  padding: var(--s-5) var(--s-5) var(--s-10);
  max-width: 1560px;
  margin: 0 auto;
}
.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
  padding: 0 0 var(--s-4);
  margin-bottom: var(--s-4);
  border-bottom: 1px solid var(--divider);
}
.page-head-actions {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
}

/* ── 단추 ─────────────────────────────────────────────────────────────── */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 32px;
  padding: 0 var(--s-3);
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--accent);
  color: #fff;
  font-family: inherit;
  font-size: var(--fs-callout);
  font-weight: 540;
  line-height: 1;
  letter-spacing: -0.005em;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: none;
  transition:
    background var(--t-fast),
    border-color var(--t-fast),
    color var(--t-fast);
}
/* 다크 단추: 주 단추는 진한 파랑(--accent-fill) 위 흰 글자(4.9:1), 보조 단추는 본문 글자색.
   전에는 모든 단추 글자를 #0b1020 으로 칠해, 보조 단추가 어두운 면 위에서 1.3:1 로 거의 안 보였다
   (Handover 는 styles.css 가 보조 단추만 다시 덮어 가려져 있었고, Portal 다크 모드에서 드러났다). */
body.theme-dark .button:not(.secondary):not(.ghost):not(.danger):not(.link) {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: #fff;
}
body.theme-dark .button:not(.secondary):not(.ghost):not(.danger):not(.link):hover {
  background: var(--accent);
  border-color: var(--accent);
}
body.theme-dark .button.secondary,
body.theme-dark .button.ghost {
  background: var(--surface-active);
  border-color: transparent;
  color: var(--text);
}
body.theme-dark .button.secondary:hover,
body.theme-dark .button.ghost:hover {
  background: var(--n-200);
  color: var(--text);
}
.button:hover {
  background: var(--accent-hover);
}
.button:active {
  transform: translateY(0.5px);
}
.button.secondary {
  background: var(--surface);
  border-color: var(--border);
  color: var(--text);
}
.button.secondary:hover {
  background: var(--surface-hover);
  border-color: var(--border-strong);
  color: var(--text);
}
.button.secondary.active {
  background: var(--surface-active);
  border-color: var(--border-strong);
  color: var(--text);
  font-weight: 560;
}
.button.ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-secondary);
}
.button.ghost:hover {
  background: var(--surface-active);
  color: var(--text);
}
.button.danger {
  background: var(--danger);
  border-color: transparent;
  color: #fff;
}
.button.danger:hover {
  filter: brightness(0.94);
}
.button.sm {
  height: 27px;
  padding: 0 var(--s-2);
  font-size: var(--fs-caption);
}
.button.lg {
  height: 38px;
  padding: 0 var(--s-4);
  font-size: var(--fs-body);
}
.button:disabled,
.button[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}
.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-secondary);
  font-size: 14px;
  cursor: pointer;
  transition: background var(--t-fast);
}
.icon-button:hover {
  background: var(--surface-active);
  color: var(--text);
}
.icon-button .count {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  border-radius: var(--radius-pill);
  background: var(--danger);
  color: #fff;
  font: 600 9.5px/15px var(--sans);
  text-align: center;
}

/* 누를 수 있는 것은 모두 초점을 드러낸다. 키보드로만 움직이는 사람이 지금 어디에 있는지
   알 수 없으면 그 화면은 쓸 수 없다. */
:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}

/* ── 입력 ─────────────────────────────────────────────────────────────── */
.input,
.select,
input:not([type]),
input[type='text'],
input[type='search'],
input[type='number'],
input[type='date'],
input[type='time'],
input[type='datetime-local'],
input[type='password'],
input[type='email'],
input[type='tel'],
input[type='url'],
input[type='month'],
select,
textarea {
  width: 100%;
  min-width: 0;
  max-width: 100%;
  min-height: 32px;
  padding: 6px var(--s-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-callout);
  line-height: 1.4;
  transition:
    border-color var(--t-fast),
    box-shadow var(--t-fast);
}
textarea {
  min-height: 76px;
  resize: vertical;
}
.input::placeholder,
input::placeholder,
textarea::placeholder {
  color: var(--text-muted);
  opacity: 1;
}
.input:hover,
.select:hover,
select:hover,
textarea:hover {
  border-color: var(--border-strong);
}
.input:focus,
.select:focus,
select:focus,
textarea:focus,
input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
input:disabled,
select:disabled,
textarea:disabled {
  color: var(--text-muted);
  background-color: var(--bg-sunken);
  cursor: not-allowed;
}
select[multiple] {
  background-image: none;
  padding-right: var(--s-2);
}
.select,
select {
  appearance: none;
  padding-right: var(--s-6);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
    linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
  background-position:
    calc(100% - 14px) 14px,
    calc(100% - 9px) 14px;
  background-size:
    5px 5px,
    5px 5px;
  background-repeat: no-repeat;
}
/* 라벨은 자리표시자로 대신하지 않는다. 값을 적고 나면 자리표시자는 사라지고, 그때부터
   그 칸이 무엇이었는지 알 수 없다. */
.field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}
.field > span:first-child,
.field > label,
label {
  font-size: var(--fs-caption);
  font-weight: 540;
  color: var(--text-secondary);
  letter-spacing: 0;
}
.field-hint {
  margin-top: 1px;
}
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: var(--s-3) var(--s-3);
  align-items: start;
}
.form-grid .wide,
.field.wide {
  grid-column: 1 / -1;
}
.form-stack {
  display: grid;
  gap: var(--s-4);
  min-width: 0;
}
.form-stack .field > label,
.form-stack .field > span:first-child {
  display: block;
}
.form-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--s-2);
  padding-top: var(--s-3);
  border-top: 1px solid var(--divider);
}
.form-stack > .form-hint {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-caption);
  line-height: 1.5;
}
/* 긴 폼은 묶음으로 나눈다. 묶음 사이의 선 하나가 제목 열 줄보다 빠르게 읽힌다. */
.form-section {
  padding: var(--s-4) 0;
  border-top: 1px solid var(--divider);
}
.form-section:first-child {
  border-top: 0;
  padding-top: 0;
}
.form-section > h3 {
  margin-bottom: var(--s-3);
  color: var(--text);
}
.form-error {
  color: var(--danger);
  font-size: var(--fs-caption);
}

/* ── 카드 ─────────────────────────────────────────────────────────────── */
.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: none;
  overflow: hidden;
  min-width: 0;
}
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--divider);
  background: transparent;
  min-height: 44px;
}
.panel-head .row {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}
.panel-head-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}
.panel-head-actions > .select {
  width: auto;
  max-width: 100%;
}
@media (max-width: 600px) {
  .panel-head {
    flex-wrap: wrap;
  }
  .panel-head > div:first-child {
    flex: 1 1 100%;
    min-width: 0;
  }
  .panel-head-actions {
    width: 100%;
  }
}
.panel-body {
  padding: var(--s-4);
}
/* 표를 담은 카드는 안쪽 여백을 없앤다. 표 자체가 이미 줄 여백을 갖고 있어, 겹치면
   한 화면에 들어가는 줄 수만 줄어든다. */
.panel-body:has(> .table-wrap),
.panel-body:has(> table) {
  padding: 0;
}
.detail-header {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--s-4);
  margin-bottom: var(--s-4);
}
.detail-header .heading {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
}
/* 상세는 본문과 옆 기둥으로 나눈다. 옆 기둥은 320px 에서 멈춘다 — 더 넓히면 본문의
   대화가 좁아지고, 더 좁히면 담당자 이름이 두 줄로 접힌다. */
.detail-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--s-4);
  align-items: start;
}

/* ── Bento ────────────────────────────────────────────────────────────────
   같은 크기 카드를 늘어놓지 않는다. 12칸 위에서 중요한 것이 넓은 자리를 갖는다. */
.bento {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--s-3);
  align-items: start;
}
.bento > .span-3 {
  grid-column: span 3;
}
.bento > .span-4 {
  grid-column: span 4;
}
.bento > .span-5 {
  grid-column: span 5;
}
.bento > .span-6 {
  grid-column: span 6;
}
.bento > .span-7 {
  grid-column: span 7;
}
.bento > .span-8 {
  grid-column: span 8;
}
.bento > .span-9 {
  grid-column: span 9;
}
.bento > .span-12 {
  grid-column: span 12;
}

/* 숫자 타일. 카드 테두리를 두르지 않고 한 줄에 묶어 한눈에 비교되게 한다. */
.stat-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  gap: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.stat-tile {
  /* Buttons otherwise keep the browser's raised border in the customer portal. */
  appearance: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: var(--s-3) var(--s-4);
  border-left: 1px solid var(--divider);
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
  background: transparent;
  cursor: pointer;
  transition: background var(--t-fast);
  min-width: 0;
}
.stat-tile:first-child {
  border-left: 0;
}
.stat-tile:hover {
  background: var(--surface-hover);
}
.stat-tile:focus-visible {
  /* An inset outline stays visible inside the strip's overflow: hidden. */
  outline: 2px solid var(--accent);
  outline-offset: -3px;
}
.stat-tile.is-selected {
  background: var(--accent-soft);
}
.stat-label {
  font-size: var(--fs-micro);
  font-weight: 540;
  color: var(--text-muted);
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.stat-value {
  font-size: var(--fs-title1);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--text);
}
.stat-tile.is-critical .stat-value {
  color: var(--danger);
}
.stat-note {
  font-size: var(--fs-micro);
  color: var(--text-faint);
}

/* 옛 KPI 이름도 같은 모습으로 맞춘다. */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  gap: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  margin-bottom: var(--s-3);
}
.kpi {
  padding: var(--s-3) var(--s-4);
  border-left: 1px solid var(--divider);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  cursor: pointer;
  transition: background var(--t-fast);
}
.kpi:first-child {
  border-left: 0;
}
.kpi:hover {
  background: var(--surface-hover);
}
.kpi-label {
  font-size: var(--fs-micro);
  font-weight: 540;
  color: var(--text-muted);
}
.kpi strong {
  font-size: var(--fs-title1);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
}
.kpi.critical strong {
  color: var(--danger);
}

/* ── 표 ───────────────────────────────────────────────────────────────────
   운영 화면에서 가장 오래 보는 부품이다. 줄 높이를 낮추고, 머리는 붙여 두고,
   가로로 넘치는 것은 표만 넘치게 한다 — 화면 전체가 흔들리면 안 된다. */
.table-wrap {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  border-radius: var(--radius-lg);
}
.data-table,
.task-list,
.table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--fs-callout);
}
.data-table thead th,
.task-list thead th,
.table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  padding: var(--s-2) var(--s-3);
  text-align: left;
  white-space: nowrap;
}
.data-table tbody td,
.task-list tbody td,
.table tbody td {
  padding: 7px var(--s-3);
  border-bottom: 1px solid var(--divider);
  vertical-align: middle;
  color: var(--text-secondary);
}
.data-table tbody td:first-child,
.task-list tbody td:first-child,
.table tbody td:first-child {
  color: var(--text);
}
.data-table tbody tr:last-child td,
.task-list tbody tr:last-child td,
.table tbody tr:last-child td {
  border-bottom: 0;
}
.data-table tbody tr,
.task-list tbody tr,
.table tbody tr {
  transition: background var(--t-fast);
}
.data-table tbody tr:hover,
.task-list tbody tr:hover,
.table tbody tr:hover {
  background: var(--surface-hover);
}
.data-table tbody tr.is-selected,
.table tbody tr.is-selected {
  background: var(--accent-soft);
}
/* 행동은 오른쪽 끝에 모은다. 눈은 왼쪽에서 정보를 읽고 오른쪽에서 손을 쓴다. */
.data-table td:last-child.actions,
.data-table th:last-child.actions,
.table td.actions,
.table th.actions {
  text-align: right;
  white-space: nowrap;
}
/* 긴 제목 하나가 표 전체를 밀어내지 않게 한다. */
.data-table td .task-title,
.task-list td .task-title,
.cell-truncate {
  display: block;
  max-width: 46ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--divider);
}
/* 표 위의 도구 줄 — 검색·거르개·열 선택이 여기 산다. */
.toolbar {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
  padding: var(--s-2) var(--s-4);
  border-bottom: 1px solid var(--divider);
  background: var(--surface);
}
.toolbar .search,
.toolbar .input {
  max-width: 280px;
}
.toolbar-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--s-2);
}

/* ── 배지 ─────────────────────────────────────────────────────────────────
   색만으로 상태를 말하지 않는다. 글자가 먼저고 점은 거들 뿐이다. 색을 못 보는 사람과
   흑백으로 인쇄된 보고서에서도 같은 것을 읽을 수 있어야 한다. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 20px;
  padding: 0 7px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text-secondary);
  font-size: var(--fs-micro);
  font-weight: 540;
  line-height: 1;
  white-space: nowrap;
  letter-spacing: 0;
}
.badge:before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.85;
  flex: none;
}
.badge.no-dot:before {
  display: none;
}

.badge.status-new,
.badge.status-open {
  background: var(--accent-soft);
  border-color: var(--accent-border);
  color: var(--accent-text);
}
.badge.status-in-progress,
.badge.status-처리중,
.badge.status-sent {
  background: var(--accent-soft);
  border-color: var(--accent-border);
  color: var(--accent-text);
}
.badge.status-waiting,
.badge.status-pending,
.badge.status-scheduled {
  background: var(--warn-soft);
  border-color: var(--warn-border);
  color: var(--warn);
}
.badge.status-completed,
.badge.status-resolved,
.badge.status-received,
.badge.status-active {
  background: var(--ok-soft);
  border-color: var(--ok-border);
  color: var(--ok);
}
.badge.status-cancelled,
.badge.status-closed,
.badge.status-disabled,
.badge.status-draft {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-muted);
}
.badge.priority-critical {
  background: var(--danger-soft);
  border-color: var(--danger-border);
  color: var(--danger);
  font-weight: 600;
}
.badge.priority-high {
  background: var(--warn-soft);
  border-color: var(--warn-border);
  color: var(--warn);
}
.badge.priority-normal {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-secondary);
}
.badge.priority-low {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-muted);
}
.badge.priority-low:before {
  opacity: 0.5;
}

/* ── 알림 줄 ─────────────────────────────────────────────────────────── */
.alert {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  flex-wrap: wrap;
  padding: var(--s-3) var(--s-4);
  margin-bottom: var(--s-3);
  border: 1px solid var(--warn-border);
  border-left: 3px solid var(--warn);
  border-radius: var(--radius);
  background: var(--warn-soft);
  color: var(--text);
  font-size: var(--fs-callout);
}
.alert.critical {
  border-color: var(--danger-border);
  border-left-color: var(--danger);
  background: var(--danger-soft);
}

/* ── 빈 화면·불러오는 중 ────────────────────────────────────────────────── */
.empty {
  padding: var(--s-8) var(--s-4);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--fs-callout);
  background: transparent;
  border: 0;
}
/* 무엇이 없는지만 적고 끝내지 않는다. 비어 있는 것이 고장인지 아직 안 만든 것인지 화면이
   말해 주지 않으면, 보는 사람은 둘 중 하나를 고르려고 사람을 찾는다. 다음에 할 수 있는 일을
   함께 둔다 — 대신 그림은 넣지 않는다. 여기 오는 사람은 대개 일이 생겨서 온 것이라, 명랑한
   삽화가 어울리는 자리가 아니다. 큰 삽화는 빈 화면을 더 비어 보이게도 한다. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-3);
  padding: calc(var(--s-8) * var(--density)) var(--s-4);
  text-align: center;
}
.empty-state b {
  color: var(--text);
  font-size: var(--fs-body);
  font-weight: 600;
}
.empty-state p {
  margin: 0;
  max-width: 34ch;
  color: var(--text-muted);
  line-height: 1.6;
}
.empty-state .button {
  margin-top: var(--s-2);
}

.skeleton {
  background: linear-gradient(90deg, var(--surface-hover), var(--surface-active), var(--surface-hover));
  background-size: 200% 100%;
  animation: ds-shimmer 1.2s linear infinite;
  border-radius: var(--radius-sm);
  height: 12px;
}
@keyframes ds-shimmer {
  to {
    background-position: -200% 0;
  }
}

/* ── 거르개 ───────────────────────────────────────────────────────────────

   한 벌만 둔다. 전에는 콘솔이 밑줄 탭을, 포털이 알약 탭을 각자 만들어 썼다. 같은 일을 하는
   단추가 두 화면에서 다르게 생기면, 같은 티켓을 놓고 전화하는 두 사람이 서로 다른 화면을
   설명하게 된다.

   고른 것을 말하는 방법이 두 가지다 — 콘솔은 .active, 포털은 aria-pressed. 둘 다 받는다.
   한쪽 표시법으로 통일하는 길은 고르지 않았다: 화면 스무 곳의 markup 을 바꾸는 값에 비해
   얻는 것이 선택자 한 줄뿐이고, 그 한 줄이 틀리면 거르개가 통째로 죽는다. */
.tabs {
  display: flex;
  width: auto;
  gap: var(--s-4);
  padding: 0;
  margin: 0 0 var(--s-4);
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  background: transparent;
  overflow-x: auto;
}
.tabs button {
  flex: none;
  min-width: 0;
  padding: calc(var(--s-2) * var(--density)) 2px;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  background: transparent;
  color: var(--text-muted);
  font: 450 var(--fs-callout) var(--sans);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--t-fast);
}
.tabs button:hover {
  color: var(--text);
}
.tabs button.active,
.tabs button[aria-pressed='true'] {
  color: var(--text);
  font-weight: 560;
  border-bottom-color: var(--accent);
}

/* 거르개 단추에 건수를 함께 다는 경우. 고른 탭에서만 강조색을 따라간다. */
.tabs button b {
  margin-left: 4px;
  font-weight: 560;
  color: var(--text-faint);
}
.tabs button.active b,
.tabs button[aria-pressed='true'] b {
  color: var(--accent);
}

/* ── 쪽 넘기기 ───────────────────────────────────────────────────────────── */
.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-4);
  padding: calc(var(--s-3) * var(--density)) var(--s-4);
  border-top: 1px solid var(--divider);
}

/* ── 떠 있는 것 ───────────────────────────────────────────────────────── */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: var(--s-4);
  background: rgba(13, 13, 15, 0.45);
  backdrop-filter: blur(2px);
  animation: ds-fade var(--t) both;
}
.modal {
  width: min(640px, 100%);
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-modal);
  animation: ds-rise var(--t) both;
}
.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-4);
  border-bottom: 1px solid var(--divider);
}
.modal-head h2 {
  font-size: var(--fs-body);
}
.modal-head .close {
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  font-size: var(--fs-title3);
  line-height: 1;
  cursor: pointer;
}
.modal-head .close:hover {
  background: var(--surface-active);
  color: var(--text);
}
.modal-body {
  padding: var(--s-4);
  overflow: auto;
}
.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border-top: 1px solid var(--divider);
  background: var(--surface-2);
}
@keyframes ds-fade {
  from {
    opacity: 0;
  }
}
@keyframes ds-rise {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}
.toast {
  background: var(--n-900);
  color: var(--n-0);
  border: 0;
  border-radius: var(--radius);
  padding: var(--s-2) var(--s-3);
  font-size: var(--fs-callout);
  box-shadow: var(--shadow-pop);
}
body.theme-dark .toast {
  background: var(--n-200);
  color: var(--n-900);
}

/* ── 반응형 ───────────────────────────────────────────────────────────────
   작은 화면에서 데스크톱 화면을 그대로 줄이지 않는다. 기둥을 접고, 카드를 세로로 쌓고,
   표는 가로로만 넘긴다. */
@media (max-width: 1280px) {
  :root {
    --sidebar-w: 204px;
  }
  .detail-layout {
    grid-template-columns: minmax(0, 1fr) 280px;
  }
}
@media (max-width: 1080px) {
  .bento > .span-3,
  .bento > .span-4,
  .bento > .span-5 {
    grid-column: span 6;
  }
  .bento > .span-7,
  .bento > .span-8,
  .bento > .span-9 {
    grid-column: span 12;
  }
  .detail-layout {
    grid-template-columns: minmax(0, 1fr);
  }
  .page {
    padding: var(--s-4) var(--s-4) var(--s-10);
  }
}
@media (max-width: 760px) {
  body {
    font-size: var(--fs-callout);
  }
  .page {
    padding: var(--s-3) var(--s-3) 84px;
  }
  .page-head {
    padding-bottom: var(--s-3);
    margin-bottom: var(--s-3);
  }
  .page-head h1 {
    font-size: var(--fs-title3);
  }
  .bento > [class*='span-'] {
    grid-column: span 12;
  }
  .topbar {
    padding: 0 var(--s-3);
  }
  .global-search-trigger {
    min-width: 0;
    flex: 1 1 auto;
  }
  .global-search-trigger kbd {
    display: none;
  }
  .local-clock {
    display: none;
  }
  .stat-strip,
  .kpi-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .stat-tile:nth-child(odd),
  .kpi:nth-child(odd) {
    border-left: 0;
  }
  .stat-tile,
  .kpi {
    border-top: 1px solid var(--divider);
  }
  .stat-tile:nth-child(-n + 2),
  .kpi:nth-child(-n + 2) {
    border-top: 0;
  }
  .modal {
    max-height: 92vh;
  }
  /* 손가락으로 누르는 곳은 충분히 크게 둔다. */
  .button,
  .icon-button {
    min-height: 34px;
  }
  .button.sm {
    height: 30px;
  }
}

/* 여러 나라 말을 쓴다. 단추·탭·표 머리에 고정 너비를 두지 않고, 넘치면 줄이거나 자른다. */
.button,
.nav button,
.badge,
.tab,
.data-table th {
  max-width: 100%;
}
.nav button span,
.tab span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Short, non-blocking feedback. Route content stays readable; no layout, shadow or count animation.
   The renderer opts in only when navigation changes, never on polling or input. */
@keyframes ui-view-enter {
  from {
    opacity: 0.72;
  }
  to {
    opacity: 1;
  }
}
@keyframes ui-toast-enter {
  from {
    opacity: 0;
    transform: translateY(3px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
@media (prefers-reduced-motion: no-preference) {
  .view-enter > :not(.topbar) {
    animation: ui-view-enter 180ms ease-out;
  }
  .toast {
    animation: ui-toast-enter 160ms ease-out;
  }
  .button,
  .icon-button {
    transition:
      background-color var(--t-fast),
      color var(--t-fast),
      border-color var(--t-fast),
      transform 100ms ease-out;
  }
  .button:not(:disabled):not([aria-disabled='true']):active,
  .icon-button:not(:disabled):not([aria-disabled='true']):active {
    transform: translateY(1px);
  }
  .tabs button,
  .field-work-tabs button {
    transition:
      color var(--t-fast),
      background-color var(--t-fast),
      border-color var(--t-fast);
  }
}
@media print {
  .view-enter > :not(.topbar),
  .toast {
    animation: none !important;
  }
}

/* 움직임을 줄여 달라고 한 사람에게는 줄인다. */
@media (prefers-reduced-motion: reduce) {
  *,
  *:before,
  *:after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}

/* Shared account and notification panels. Neutral controls, bounded mobile width. */
.topbar-menu-wrap {
  position: relative;
  flex: 0 0 auto;
}
.topbar .account-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 34px;
  padding: 4px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-caption);
  line-height: 1.4;
  white-space: nowrap;
  cursor: pointer;
}
.account-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text-muted);
  font-size: var(--fs-micro);
  font-weight: 600;
}
.topbar .account-trigger:hover,
.topbar .account-trigger[aria-expanded='true'] {
  background: var(--surface-hover);
}
.topbar-popup {
  position: absolute;
  right: 0;
  top: calc(100% + 10px);
  z-index: 60;
  width: min(280px, calc(100vw - 24px));
  max-height: calc(100dvh - var(--topbar-h) - 32px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg, 10px);
  text-align: left;
  white-space: normal;
  font-size: var(--fs-callout);
}
.topbar-popup[hidden] {
  display: none !important;
}
.topbar-popup-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}
.account-summary {
  display: grid;
  gap: 4px;
  min-width: 0;
  overflow-wrap: anywhere;
}
.account-summary span {
  color: var(--text-muted);
  font-size: var(--fs-caption);
}
.topbar-popup .popup-close {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  font-size: var(--fs-title1);
  cursor: pointer;
}
.topbar-popup-links {
  display: grid;
  gap: 4px;
  padding: 8px;
}
.topbar-popup-links > button {
  width: 100%;
  padding: 10px 12px;
  border: 0;
  border-radius: var(--radius);
  text-align: left;
  background: transparent;
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.topbar-popup-links > button:hover,
.topbar-popup .popup-close:hover {
  background: var(--surface-hover);
}
.topbar-popup-links > .account-signout {
  margin-top: 4px;
  border-top: 1px solid var(--border);
  border-radius: 0;
  color: var(--text-muted);
}
.topbar .notification-trigger {
  position: relative;
  width: 36px;
  height: 34px;
  padding: 6px;
}
.notification-count {
  position: absolute;
  right: -7px;
  top: -6px;
  min-width: 17px;
  padding: 1px 4px;
  border-radius: 9px;
  background: var(--accent);
  color: #fff;
  font-size: var(--fs-micro);
  line-height: 15px;
}
.notification-popup {
  width: min(400px, calc(100vw - 24px));
}
.notification-row {
  display: grid;
  grid-template-columns: 7px minmax(0, 1fr) 10px;
  align-items: start;
  gap: 10px;
  width: 100%;
  padding: 13px 16px;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  text-align: left;
  font: inherit;
  cursor: pointer;
}
.notification-row:hover {
  background: var(--surface-hover);
}
.notification-row.is-unread {
  background: var(--accent-soft);
}
.notification-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  margin-top: 6px;
  background: var(--border);
}
.is-unread .notification-dot {
  background: var(--accent);
}
.notification-text {
  display: block;
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.notification-row small,
.notification-row time {
  display: inline-block;
  color: var(--text-muted);
  font-size: var(--fs-micro);
  margin: 5px 8px 0 0;
}
.notification-pagination {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  color: var(--text-muted);
  font-size: var(--fs-caption);
}
.top-actions .notification-pagination .button.secondary.sm {
  display: inline-flex;
}
.notification-empty,
.notification-detail {
  padding: 24px 16px;
  margin: 0;
  line-height: 1.6;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.notification-empty {
  color: var(--text-muted);
  text-align: center;
}
.topbar .account-trigger:focus-visible,
.topbar-popup button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.account-profile-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
  padding: 24px;
  margin: 0;
}
.account-profile-grid dt {
  color: var(--text-muted);
  font-size: var(--fs-caption);
  margin-bottom: 8px;
}
.account-profile-grid dd {
  margin: 0;
  overflow-wrap: anywhere;
}
@media (max-width: 600px) {
  .topbar-popup {
    position: fixed;
    top: calc(var(--topbar-h) + 8px);
    right: 12px;
    max-height: calc(100dvh - var(--topbar-h) - 88px);
  }
  .topbar .account-trigger {
    min-height: 36px;
    padding: 4px 8px;
  }
  .account-mark {
    display: none;
  }
  .account-profile-grid {
    grid-template-columns: 1fr;
    padding: 20px;
  }
}

/* 계정 메뉴의 조밀한 기본값 뒤에 둔다. 모바일/터치의 행동 영역만 넓힌다. */
@media (any-pointer: coarse), (max-width: 767px) {
  .button,
  .button.sm,
  .icon-button,
  .topbar .account-trigger,
  .tab,
  .tabs button,
  .report-seg button,
  .task-filter-chip,
  .modal .close {
    min-height: 44px;
    min-width: 44px;
  }
  .topbar .icon-button {
    width: 44px;
    height: 44px;
  }
}

/* 인쇄에는 기둥과 조작을 뺀다. 리포트를 그대로 뽑는 화면이 있다. */
@media print {
  .sidebar,
  .topbar,
  .page-head-actions,
  .mobile-nav {
    display: none !important;
  }
  .shell {
    grid-template-columns: 1fr;
  }
  .panel {
    border-color: #ddd;
    break-inside: avoid;
  }
}

/* 상태 단계 그림. 대기는 칸이 아니라 진행 단계 옆의 작은 표시다 (Handover app.js statusStepper, Portal ticketStepper). 둘이 같은 마크업을 쓴다. */
.status-stepper {
  display: flex;
  list-style: none;
  margin: var(--s-4) 0 var(--s-3);
  padding: 0;
  max-width: 520px;
}

.stepper-step {
  flex: 1;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}

.stepper-step + .stepper-step::before {
  content: '';
  position: absolute;
  top: 13px;
  right: 50%;
  width: 100%;
  height: 2px;
  background: var(--line);
}

.stepper-step.is-done + .stepper-step::before,
.stepper-step.is-done + .stepper-step.is-current::before {
  background: var(--accent);
}

.stepper-dot {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid var(--line);
  background: var(--panel, var(--surface, #fff));
  color: var(--muted);
  font-size: var(--fs-callout);
  font-weight: 650;
}

.is-done .stepper-dot {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
body.theme-dark .is-done .stepper-dot {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
}

.is-current .stepper-dot {
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 18%, transparent);
}

.stepper-label {
  font-size: var(--fs-caption);
  color: var(--muted);
  text-align: center;
  white-space: nowrap;
}

.is-done .stepper-label,
.is-current .stepper-label {
  color: var(--text);
  font-weight: 600;
}

.stepper-wait {
  font-style: normal;
  font-size: var(--fs-micro);
  font-weight: 650;
  padding: 1px 6px;
  border-radius: var(--radius-sm, 4px);
  background: color-mix(in srgb, var(--warning, #d97706) 16%, transparent);
  color: var(--warning, #b45309);
}

.status-stepper.is-cancelled {
  opacity: 0.45;
}

/* ── 2026-10 개편: 상태 반응과 움직임 ─────────────────────────────────────────
   눌렀다·떴다·바뀌었다를 눈으로 따라갈 수 있게 짧게 움직인다. 장식이 아니라 상태가 바뀐 것만
   움직이고, 튕기거나 크게 커지는 효과는 쓰지 않는다. OS 에서 동작 줄이기를 켰거나 인쇄할 때는
   모두 끈다 — 리포트 PDF 에 반쯤 나타난 모달이 찍히면 안 된다. */
.button,
.icon-button,
.tabs button {
  transition:
    background-color var(--t-fast),
    border-color var(--t-fast),
    color var(--t-fast),
    box-shadow var(--t-fast),
    transform var(--t-fast);
}
.button:active:not(:disabled),
.icon-button:active:not(:disabled) {
  transform: scale(0.97);
}
.button:focus-visible,
.icon-button:focus-visible,
.tabs button:focus-visible,
.nav button:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
.table tbody tr,
.data-table tbody tr {
  transition: background-color var(--t-fast);
}
.topbar-popup:not([hidden]) {
  animation: ds-drop var(--t) both;
}
.toast {
  animation: ds-rise var(--t) both;
}
@keyframes ds-drop {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
}
/* 새로 들어온 줄을 잠깐 밝힌다. 대시보드를 모니터에 띄워 둘 때 무엇이 바뀌었는지 보이게. */
.is-fresh {
  animation: ds-fresh 1.6s ease-out both;
}
@keyframes ds-fresh {
  from {
    background-color: var(--accent-soft);
  }
}
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
@media print {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }
}

/* ── 2026-10 개편: 단추와 고르기 ──────────────────────────────────────────────
   보조 단추는 테두리 상자가 아니라 옅은 면으로 한 단계 낮춘다. 테두리 상자가 화면마다
   열 개씩 서 있으면 어느 것이 주 행동인지 먼저 보이지 않았다. */
.button.secondary {
  background: var(--surface-active);
  border-color: transparent;
}
.button.secondary:hover {
  background: var(--n-150);
  border-color: transparent;
}
/* 탭은 밑줄 대신 고르기 단추(segmented)로 그린다. 고른 칸이 흰 면으로 떠서 지금 어디인지가
   글자색이 아니라 모양으로 보인다. 칸이 많으면 가로로 밀린다. */
.tabs {
  display: inline-flex;
  max-width: 100%;
  gap: 2px;
  padding: 2px;
  border: 0;
  border-radius: 9px;
  background: var(--surface-active);
}
.tabs button {
  padding: 5px 12px;
  border: 0;
  border-radius: 7px;
  color: var(--text-secondary);
  font-weight: 500;
}
.tabs button:hover {
  background: rgba(0, 0, 0, 0.04);
  color: var(--text);
}
.tabs button.active,
.tabs button[aria-pressed='true'] {
  background: var(--surface);
  color: var(--text);
  font-weight: 600;
  border: 0;
  box-shadow: 0 1px 2px rgba(13, 13, 15, 0.12);
}
body.theme-dark .tabs button.active,
body.theme-dark .tabs button[aria-pressed='true'] {
  background: var(--n-300);
}

/* ── 사이드바 접기 (2026-10-05) ───────────────────────────────────────────
   맨 아래 단추로 «아이콘만» 과 «글자까지» 를 바꾼다. 상태는 <html class="nav-collapsed"> 로 둔다 —
   화면을 다시 그릴 때마다 사이드바가 새로 만들어지므로, 사이드바에 달면 그릴 때마다 풀린다.
   선택은 브라우저에 기억한다(vlan24.nav-collapsed). 휴대폰 폭은 사이드바 대신 다른 메뉴를 쓰므로
   접기 규칙은 사이드바가 보이는 폭에서만 건다. 접혀도 메뉴 이름은 title·aria-label 로 남는다. */
.sidebar-toggle {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  width: 100%;
  margin-top: auto;
  padding: 6px var(--s-2);
  min-height: 30px;
  border: 0;
  border-radius: var(--radius);
  background: none;
  color: var(--text-muted);
  font: inherit;
  font-size: var(--fs-caption);
  text-align: left;
  cursor: pointer;
}
.sidebar-toggle:hover {
  background: var(--surface-active);
  color: var(--text);
}
.sidebar-toggle svg {
  width: 15px;
  height: 15px;
  flex: none;
  transition: transform var(--t-fast);
}
html.nav-collapsed .sidebar-toggle svg {
  transform: rotate(180deg);
}
@media (min-width: 901px) {
  html.nav-collapsed .shell {
    grid-template-columns: 60px 1fr;
  }
  html.nav-collapsed .sidebar {
    padding-left: var(--s-2);
    padding-right: var(--s-2);
  }
  html.nav-collapsed .brand span,
  html.nav-collapsed .workspace,
  html.nav-collapsed .nav .section,
  html.nav-collapsed .nav button span,
  html.nav-collapsed .nav .fav-star,
  html.nav-collapsed .side-user div,
  html.nav-collapsed .sidebar-toggle span {
    display: none !important;
  }
  html.nav-collapsed .brand {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
    font-size: var(--fs-micro);
    overflow: hidden;
  }
  html.nav-collapsed .nav button,
  html.nav-collapsed .sidebar-toggle {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
  }
  html.nav-collapsed .nav .section + button {
    margin-top: var(--s-3);
  }
}
@media (prefers-reduced-motion: reduce) {
  .sidebar-toggle svg {
    transition: none;
  }
}
@media (max-width: 900px) {
  .sidebar-toggle {
    display: none;
  }
}
/* 사이드바 로고 (2026-10-06). 펼치면 워드마크, 접으면 마크만. 밝은/어두운 테마마다 파일이 따로
   있어(글자 색이 검정/흰색) 테마 × 접힘 네 경우 중 하나만 보인다. 전에는 접힌 막대에 «VLAN24» 가
   다 들어가지 않아 첫 글자 «V» 만 남겼다. 파일 원본은 Handover, Portal 은 sync-ui 로 복사한다. */
/* 두 앱 모두 «워드마크 + 작은 화면 이름» 한 줄. 콘솔의 옛 styles.css 는 이름 두 줄(세로)이었다. */
.brand {
  flex-direction: row;
  align-items: center;
}
.brand-logo {
  display: flex;
  align-items: center;
  flex: none;
}
.brand-logo img {
  display: none;
}
.brand-wordmark {
  width: auto;
  height: 14px;
}
.brand-logomark {
  width: 28px;
  height: 28px;
}
.brand-logo .brand-wordmark.logo-light {
  display: block;
}
body.theme-dark .brand-logo .brand-wordmark.logo-light {
  display: none;
}
body.theme-dark .brand-logo .brand-wordmark.logo-dark {
  display: block;
}
@media (min-width: 901px) {
  html.nav-collapsed .brand-logo .brand-wordmark.logo-light,
  html.nav-collapsed body.theme-dark .brand-logo .brand-wordmark.logo-dark {
    display: none;
  }
  html.nav-collapsed .brand-logo .brand-logomark.logo-light {
    display: block;
  }
  html.nav-collapsed body.theme-dark .brand-logo .brand-logomark.logo-light {
    display: none;
  }
  html.nav-collapsed body.theme-dark .brand-logo .brand-logomark.logo-dark {
    display: block;
  }
}

/* 본문 바로가기 (2026-10-05). 첫 Tab 에만 나타나 메뉴를 건너뛴다. 두 앱 공통. */
.skip-link {
  position: absolute;
  left: var(--s-3);
  top: -48px;
  z-index: 1000;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-pop);
}
.skip-link:focus {
  top: var(--s-3);
}
.main:focus {
  outline: none;
}

/* 불러오는 중·실패 (2026-10-05). 화면마다 다르던 문구·모양을 하나로. 실패에는 다시 시도 단추를 함께 둔다. */
.load-state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--s-2) var(--s-3);
  padding: var(--s-6) var(--s-4);
  color: var(--text-muted);
  font-size: var(--fs-callout);
  text-align: center;
}
.load-state.is-error {
  flex-direction: column;
  color: var(--text-secondary);
}
.load-state.is-error > b {
  color: var(--danger);
  font-weight: 600;
}
.load-spinner {
  width: 14px;
  height: 14px;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: load-spin 0.8s linear infinite;
}
@keyframes load-spin {
  to {
    transform: rotate(360deg);
  }
}
@media (prefers-reduced-motion: reduce) {
  .load-spinner {
    animation: none;
  }
}

/* 계정 메뉴 안 구분선(언어·테마 전환과 로그아웃 사이). 두 앱 공통. */
.topbar-popup-divider {
  height: 1px;
  margin: var(--s-1) 0;
  background: var(--divider);
}
/* 다크의 --danger(#f08b8b)는 글자용이라 흰 글자를 얹으면 2:1 남짓이다. 위험 단추 면은 진한 빨강으로. */
body.theme-dark .button.danger {
  background: #b9373d;
  color: #fff;
}
/* 다크 탭 건수: 고른 탭(밝은 면) 위 강조색·흐린 숫자가 3.5:1·4.2:1 이었다. */
body.theme-dark .tabs button b {
  color: var(--text-muted);
}
body.theme-dark .tabs button.active b,
body.theme-dark .tabs button[aria-pressed='true'] b {
  color: var(--accent-hover);
}

/* ── Cloud 화면 공통 부품 (Handover·Portal 같은 모양) ─────────────────────────
   상태를 숫자가 아니라 문장으로 먼저 말하고(status-callout), 트래픽이 지나는 순서를 그림으로
   보이며(flow), 일어난 일을 시간 순으로 적는다(timeline). 두 앱이 같은 순서·이름을 쓰는 것이
   목적이라, 화면 전용 CSS 에서 다시 정의하지 않는다. */
.status-callout {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin-bottom: var(--s-4);
  padding: calc(var(--s-4) * var(--density)) var(--s-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.status-callout.is-ok {
  border-color: var(--ok-border);
  background: var(--ok-soft);
}
.status-callout.is-warn {
  border-color: var(--warn-border);
  background: var(--warn-soft);
}
.status-callout.is-danger {
  border-color: var(--danger-border);
  background: var(--danger-soft);
}
.status-callout-icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--surface);
  color: var(--text-muted);
}
.status-callout.is-ok .status-callout-icon {
  color: var(--ok);
}
.status-callout.is-warn .status-callout-icon {
  color: var(--warn);
}
.status-callout.is-danger .status-callout-icon {
  color: var(--danger);
}
.status-callout-icon svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.status-callout-text {
  flex: 1;
  min-width: 0;
}
.status-callout h2 {
  margin: 0;
  font-size: var(--fs-title3);
}
.status-callout p {
  margin: 2px 0 0;
  color: var(--text-secondary);
}
.status-callout-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}
@media (max-width: 640px) {
  .status-callout {
    flex-wrap: wrap;
  }
}

.flow {
  display: flex;
  align-items: stretch;
  padding: var(--s-4);
}
.flow-step {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: calc(var(--s-3) * var(--density));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: inherit;
  font: inherit;
  text-align: left;
}
button.flow-step {
  cursor: pointer;
}
button.flow-step:hover {
  border-color: var(--accent-border);
  background: var(--accent-soft);
}
.flow-step.is-static {
  background: var(--surface-2);
}
.flow-step.is-off {
  border-style: dashed;
  background: transparent;
  color: var(--text-muted);
}
.flow-step svg {
  width: 18px;
  height: 18px;
  color: var(--accent);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.flow-step.is-off svg {
  color: var(--text-faint);
}
.flow-step b {
  font-size: var(--fs-callout);
  font-weight: 600;
}
.flow-step small {
  color: var(--text-muted);
  font-size: var(--fs-caption);
  overflow-wrap: anywhere;
}
.flow-arrow {
  pointer-events: none;
  display: grid;
  flex: none;
  place-items: center;
  width: 24px;
  color: var(--text-faint);
}
/* 좁은 화면: 세로로 쌓되 단계마다 한 줄(아이콘 · 이름 · 값). 세 줄씩이면 화면 두 개를 넘는다. */
@media (max-width: 760px) {
  .flow {
    flex-direction: column;
  }
  .flow-step {
    flex-direction: row;
    align-items: center;
    gap: var(--s-2);
  }
  .flow-step small {
    margin-left: auto;
    text-align: right;
  }
  /* 상자를 작게 고정한 채 돌린다. 폭 전체인 상자를 돌리면 세로로 길어져 위아래 단계를 덮어 누를 수 없었다. */
  .flow-arrow {
    align-self: center;
    width: 14px;
    height: 14px;
    font-size: var(--fs-micro);
    transform: rotate(90deg);
  }
}

.timeline {
  margin: 0;
  padding: var(--s-2) var(--s-4) var(--s-4);
  list-style: none;
}
.timeline li {
  position: relative;
  display: grid;
  grid-template-columns: 12px minmax(0, 1fr) auto;
  gap: var(--s-3);
  padding: var(--s-2) 0;
}
.timeline li::before {
  content: '';
  position: absolute;
  top: 22px;
  bottom: -6px;
  left: 5px;
  width: 1px;
  background: var(--divider);
}
.timeline li:last-child::before {
  display: none;
}
.timeline-dot {
  width: 10px;
  height: 10px;
  margin-top: 5px;
  border-radius: 50%;
  background: var(--text-faint);
}
.timeline-dot.is-ok {
  background: var(--ok);
}
.timeline-dot.is-danger {
  background: var(--danger);
}
.timeline-dot.is-info {
  background: var(--accent);
}
.timeline p {
  margin: 0;
  overflow-wrap: anywhere;
}
.timeline small,
.timeline time {
  color: var(--text-muted);
  font-size: var(--fs-caption);
}
.timeline time {
  white-space: nowrap;
}

/* 탭 옆 짧은 꼬리표(«편집 가능» 등) */
.tab-note {
  margin-left: 6px;
  color: var(--ok);
  font-size: var(--fs-micro);
  font-weight: 560;
}

/* ── 방화벽 정책 표 (Handover·Portal 같은 칸) ───────────────────────────────
   «순서 · 정책 · 출발지 → 목적지 · 서비스 · 결과». 대상은 이름을 먼저, IP·포트를 그 아래 작게 쓴다.
   전에는 앱마다 칸이 달랐고(9칸/7칸) 방향·기록·사용이 각자 칸을 차지해 «허용» 같은 글자가 세로로 깨졌다.
   방향·기록·꺼짐은 정책 이름 아래 한 줄로 내렸다. */
.policy-name {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.policy-name b {
  font-weight: 600;
  overflow-wrap: anywhere;
}
.policy-name small {
  color: var(--text-muted);
  font-size: var(--fs-micro);
}
.policy-name .is-off {
  color: var(--warn);
  font-weight: 560;
}
.policy-refs {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.policy-ref {
  display: grid;
  gap: 1px;
  min-width: 0;
}
.policy-ref b {
  font-weight: 540;
  overflow-wrap: anywhere;
}
.policy-ref small {
  color: var(--text-muted);
  font-family: var(--mono);
  font-size: var(--fs-micro);
  overflow-wrap: anywhere;
}
.policy-arrow {
  display: block;
  color: var(--text-faint);
  text-align: center;
}
.verdict {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text-secondary);
  font-size: var(--fs-caption);
  font-weight: 600;
  white-space: nowrap;
}
.verdict.is-accept {
  border-color: var(--ok-border);
  background: var(--ok-soft);
  color: var(--ok);
}
.verdict.is-drop,
.verdict.is-reject {
  border-color: var(--danger-border);
  background: var(--danger-soft);
  color: var(--danger);
}

/* 사용률 막대 (공인 IP 등). 숫자 옆에서 «얼마나 찼나» 를 한눈에. 80% 넘으면 is-warn. */
.meter {
  display: block;
  height: 6px;
  overflow: hidden;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
}
.meter > i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
}
.meter.is-warn > i {
  background: var(--warn);
}
