/* ============================================================================
   GenAI AEO 컴포넌트 레이어 (정본).

   tokens.css 의 변수만 쓴다. 화면별 CSS 를 새로 만들지 않고 여기 컴포넌트를 조합한다.
   마크업은 templates/_ui.html 의 Jinja 매크로가 만든다 — 클래스 이름을 화면에서 직접
   적기 전에 매크로가 있는지 먼저 본다.

   접두사 u- 는 이 파일이 소유하는 클래스라는 표시다. 기존 관리자 화면이 쓰던
   .st-*(상태 배지)·.mode-*(요금제 배지)·.panel·.card·.table-wrap·.btn 은 그대로 둔다
   (admin.css 가 정의한다). 새 상태 배지 계열을 따로 만들지 않는다.

   모든 움직임은 prefers-reduced-motion: reduce 에서 멈춘다(파일 맨 끝 한 곳에서 끈다).
   ============================================================================ */

/* ── 단추 (제품에 모양 하나) ────────────────────────────────────────────
   회원 화면의 알약+보라 그러데이션과 관리자 화면의 사각+단색 파랑을 하나로 합쳤다.
   모난 네모(10px), 단색 강조 바탕, 굵은 14px 글자다. 그러데이션을 버린 까닭은
   밝은 바탕에서 흰 글자 대비를 보장할 수 없어서이고, 알약을 버린 까닭은 표 안
   행 단추가 가로로 너무 벌어져서다. 자세한 설명은 docs/4.design-system.md.
   회원·공개 화면의 .btn-primary/.btn-ghost, 관리자의 .btn/.actions button,
   로그인 화면의 .auth-submit 이 모두 이 값을 읽는다. */
.u-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--btn-min-h);
  padding: var(--btn-pad-y) var(--btn-pad-x);
  border-radius: var(--btn-radius);
  border: 1px solid var(--surface-line-strong);
  background: var(--surface-panel);
  color: var(--surface-ink);
  font: inherit;
  font-size: var(--btn-fs);
  font-weight: var(--btn-fw);
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-inout), border-color var(--dur-base) var(--ease-inout),
              box-shadow var(--dur-base) var(--ease-inout), transform var(--dur-fast) var(--ease-inout);
}
.u-btn:hover { background: var(--surface-sunken); border-color: var(--surface-accent); text-decoration: none; }
.u-btn:active { transform: translateY(1px); }
.u-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.u-btn[disabled], .u-btn.is-disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }

.u-btn-primary { background: var(--surface-accent); border-color: var(--surface-accent); color: var(--accent-on); }
.u-btn-primary:hover { background: var(--surface-accent-2); border-color: var(--surface-accent-2); color: var(--accent-on); }
.u-btn-secondary { background: var(--surface-accent-soft); border-color: var(--surface-accent); color: var(--surface-accent); }
.u-btn-secondary:hover { background: var(--surface-accent-soft); border-color: var(--surface-accent-2); color: var(--surface-accent-2); }
.u-btn-ghost { background: transparent; border-color: transparent; color: var(--surface-muted); }
.u-btn-ghost:hover { background: var(--surface-sunken); color: var(--surface-ink); border-color: var(--surface-line); }
.u-btn-danger { background: var(--danger); border-color: var(--danger); color: var(--n-0); }
.u-btn-danger:hover { background: var(--danger-deep); border-color: var(--danger-deep); color: var(--n-0); }

.u-btn-sm { min-height: 0; padding: var(--btn-pad-y-sm) var(--btn-pad-x-sm); font-size: var(--btn-fs-sm); }
.u-btn-lg { padding: var(--btn-pad-y-lg) var(--btn-pad-x-lg); font-size: var(--btn-fs-lg); }

/* 진행 중 버튼: 글자를 그대로 두고 앞에 작은 회전 표시를 넣는다. */
.u-btn.is-loading { pointer-events: none; opacity: 0.85; }
.u-btn.is-loading::before {
  content: '';
  width: 13px; height: 13px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-right-color: transparent;
  animation: u-spin 700ms linear infinite;
}

/* ── 입력·폼 ──────────────────────────────────────────────────────────── */
.u-field { display: grid; gap: var(--sp-1); min-width: 0; }
.u-label { font-size: var(--fs-12); font-weight: var(--fw-bold); color: var(--surface-muted); }
.u-input, .u-select, .u-textarea {
  width: 100%;
  padding: 7px 10px;
  border: 1px solid var(--surface-line-strong);
  border-radius: var(--r-sm);
  background: var(--surface-panel);
  color: var(--surface-ink);
  font: inherit;
  font-size: var(--fs-13);
  transition: border-color var(--dur-base) var(--ease-inout), box-shadow var(--dur-base) var(--ease-inout);
}
.u-textarea { min-height: 72px; resize: vertical; }
.u-input:focus, .u-select:focus, .u-textarea:focus {
  outline: none;
  border-color: var(--surface-accent);
  box-shadow: var(--sh-ring);
}
.u-input:focus-visible, .u-select:focus-visible, .u-textarea:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.u-input[disabled], .u-select[disabled], .u-textarea[disabled] { background: var(--surface-sunken); color: var(--surface-muted); cursor: not-allowed; }
.u-input[aria-invalid="true"], .u-select[aria-invalid="true"], .u-textarea[aria-invalid="true"] { border-color: var(--danger); }
.u-hint { font-size: var(--fs-12); color: var(--surface-muted); margin: 0; }
/* 안내 글이 둘 이어지면 문단 사이를 조금 띄운다(줄 간격과 문단 간격이 같으면 한 문단으로 읽힌다). */
.u-hint + .u-hint { margin-top: var(--sp-2); }
/* 안내 한 줄 바로 뒤에 오는 카드 묶음·검색 칸이 글자에 붙지 않게 띄운다. */
.u-hint + .u-cards, .u-hint + .filters { margin-top: var(--sp-3); }
.u-error { font-size: var(--fs-12); color: var(--danger-ink); margin: 0; font-weight: var(--fw-medium); }

/* ── 패널·구역 머리 ───────────────────────────────────────────────────── */
.u-panel {
  background: var(--surface-panel);
  border: 1px solid var(--surface-line);
  border-radius: var(--r-panel);
  box-shadow: var(--sh-1);
  padding: var(--panel-pad);
  margin-bottom: var(--sp-5);
  min-width: 0;
}
.u-panel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-4);
  margin-bottom: var(--sp-3);
}
.u-panel-title { margin: 0; font-size: var(--h2-size); font-weight: var(--h2-weight); color: var(--surface-ink-strong); }
.u-panel-sub { margin: 2px 0 0; font-size: var(--fs-12); color: var(--surface-muted); }
.u-panel-note { margin: 0; font-size: var(--fs-12); color: var(--surface-muted); }
/* 패널 안 소제목. 같은 본문이 화면마다 다른 제목 단계로 들어가므로 크기는 태그가 아니라 클래스가 정한다. */
.u-panel .is-h2 { margin: var(--sp-6) 0 var(--sp-3); font-size: var(--h2-size); font-weight: var(--h2-weight); color: var(--surface-ink-strong); }
.u-panel .is-h3 { margin: var(--sp-5) 0 var(--sp-2); font-size: var(--h3-size); font-weight: var(--h3-weight); color: var(--surface-ink-strong); }

/* ── 줄 목록 ──────────────────────────────────────────────────────────────
   칸이 고르지 않아 표로 만들기 어려운 항목(점검 항목·질문·검색어)에 쓴다. */
.u-list { list-style: none; margin: 0 0 var(--sp-3); padding: 0; display: grid; gap: var(--sp-2); }
.u-list-item {
  min-width: 0;
  padding: var(--sp-3);
  border: 1px solid var(--surface-line);
  border-left: 3px solid var(--surface-line-strong);
  border-radius: var(--r-md);
  background: var(--surface-sunken);
}
.u-list-item.is-danger { border-left-color: var(--danger); }
.u-list-item.is-warning { border-left-color: var(--warning); }
.u-list-item.is-success { border-left-color: var(--success); }
.u-list-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.u-list-title { font-weight: var(--fw-bold); color: var(--surface-ink-strong); }
.u-list-item p { margin: 4px 0 0; font-size: var(--fs-13); }
.u-list.is-tight > .u-list-item { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); padding: 8px 12px; }
.u-list.is-tight > .u-list-item p { margin: 0; }
.u-grow { flex: 1 1 auto; min-width: 0; }

/* 접어 두는 자세한 내용(설치 안내·모델 목록). 기본은 접힌 채다. */
.u-details { border: 1px solid var(--surface-line); border-radius: var(--r-md); padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-3); }
.u-details > summary { cursor: pointer; font-size: var(--fs-13); font-weight: var(--fw-medium); color: var(--surface-ink); }
.u-details > summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.u-details[open] > summary { margin-bottom: var(--sp-2); }

/* 차례대로 하는 일(설치 안내처럼 순서가 있는 안내). */
.u-steps { margin: var(--sp-3) 0 0; padding-left: 20px; display: grid; gap: 4px; font-size: var(--fs-13); color: var(--surface-ink); }

/* ── 화면 맨 위 제목 줄 ───────────────────────────────────────────────── */
.u-page-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--sp-2) var(--sp-5); margin: 0 0 var(--sp-4); }
.u-page-eyebrow { margin: 0 0 4px; font-size: var(--fs-12); font-weight: var(--fw-bold); letter-spacing: 0.06em; color: var(--surface-accent); }
.u-page-title { margin: 0; font-size: var(--h1-size); font-weight: var(--h1-weight); line-height: var(--lh-tight); color: var(--surface-ink-strong); word-break: keep-all; }
.u-page-sub { margin: 4px 0 0; font-size: var(--fs-13); color: var(--surface-muted); }
.u-page-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* ── 통계 타일 ────────────────────────────────────────────────────────── */
.u-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--sp-3);
  margin-bottom: var(--sp-5);
}
/* 타일 묶음: 열한 개를 한 줄에 늘어놓으면 읽히지 않는다. 하는 일로 묶고 묶음마다 이름을 단다. */
.u-tile-group { margin-bottom: var(--sp-5); min-width: 0; }
.u-tile-group > .u-tiles { margin-bottom: 0; }
.u-tile-group-label {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-12);
  font-weight: var(--fw-bold);
  letter-spacing: 0.04em;
  color: var(--surface-muted);
}
.u-tile-group-note { font-weight: var(--fw-normal); }
/* 묶음 안 타일 수를 알면 칸 수를 고정해 줄이 고르게 끊기게 한다(마지막 줄이 남지 않는다). */
.u-tiles[data-cols] { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 768px) {
  .u-tiles[data-cols="2"], .u-tiles[data-cols="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .u-tiles[data-cols="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1280px) {
  .u-tiles[data-cols="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.u-tile {
  display: grid;
  grid-template-rows: auto auto 1fr;
  gap: var(--sp-1);
  padding: var(--sp-4);
  background: var(--surface-panel);
  border: 1px solid var(--surface-line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
  min-width: 0;
  color: inherit;
  text-decoration: none;
  transition: border-color var(--dur-base) var(--ease-inout), box-shadow var(--dur-base) var(--ease-inout),
              transform var(--dur-base) var(--ease-out);
}
a.u-tile:hover, a.u-tile:focus-visible {
  border-color: var(--surface-accent);
  box-shadow: var(--sh-2);
  transform: translateY(-1px);
  text-decoration: none;
}
.u-tile-label { font-size: var(--fs-12); color: var(--surface-muted); font-weight: var(--fw-medium); order: -1; }
.u-tile-value {
  font-size: var(--fs-28);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  color: var(--surface-ink-strong);
  font-variant-numeric: tabular-nums;
  display: flex;
  align-items: baseline;
  gap: 4px;
}
.u-tile-unit { font-size: var(--fs-13); font-weight: var(--fw-medium); color: var(--surface-muted); }
.u-tile-foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); font-size: var(--fs-12); }
.u-tile-period { color: var(--surface-muted); }
.u-tile-spark { display: block; width: 100%; height: 28px; margin-top: 2px; }

/* 증감. 색만으로 구분하지 않게 ▲▼ 글리프와 숫자를 함께 적는다. */
.u-delta { display: inline-flex; align-items: center; gap: 3px; font-weight: var(--fw-bold); font-variant-numeric: tabular-nums; }
.u-delta-up { color: var(--success-ink); }
.u-delta-down { color: var(--danger-ink); }
.u-delta-flat { color: var(--surface-muted); }

/* ── 표 ───────────────────────────────────────────────────────────────── */
.u-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--surface-line);
  border-radius: var(--r-md);
  background: var(--surface-panel);
  max-height: none;
  /* 옆으로 더 있다는 표시: 넘칠 때만 양 끝에 그림자가 생기고 끝까지 밀면 사라진다.
     (바탕색 두 장은 칸을 따라 움직이고 그림자 두 장은 제자리에 머무는 고전 수법) */
  background-image:
    linear-gradient(to right, var(--surface-panel), transparent),
    linear-gradient(to left, var(--surface-panel), transparent),
    linear-gradient(to right, rgba(16, 24, 40, 0.18), transparent),
    linear-gradient(to left, rgba(16, 24, 40, 0.18), transparent);
  background-position: left center, right center, left center, right center;
  background-repeat: no-repeat;
  background-size: 28px 100%, 28px 100%, 12px 100%, 12px 100%;
  background-attachment: local, local, scroll, scroll;
}
/* 줄 수 고정 표(ui.table scroll_rows, 2026-09-20 사장님: 목록이 길면 20줄 높이로 고정하고 나머지는 표 안에서 스크롤).
   높이 어림값은 머리 한 줄 + 줄 수 × 한 줄이고, shell.js 가 실제 N번째 줄의 아래 끝으로 다시 맞춘다. 줄이 적으면
   max-height 에 닿지 않으므로 표가 저절로 짧아진다. 스크롤바는 마우스를 올렸거나 초점이 있을 때만 색을 입힌다
   (자리는 늘 잡아 두어 올릴 때 글자가 밀리지 않는다). */
.u-table-wrap.is-scroll {
  overflow-y: auto;
  overscroll-behavior: contain;
  max-height: calc(2.75rem + var(--u-scroll-rows, 20) * 2.75rem);
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
}
.u-table-wrap.is-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
.u-table-wrap.is-scroll::-webkit-scrollbar-thumb { background: transparent; border-radius: var(--r-pill); }
.u-table-wrap.is-scroll:hover,
.u-table-wrap.is-scroll:focus-visible { scrollbar-color: var(--surface-line-strong, var(--surface-line)) transparent; }
.u-table-wrap.is-scroll:hover::-webkit-scrollbar-thumb,
.u-table-wrap.is-scroll:focus-visible::-webkit-scrollbar-thumb { background: var(--surface-line-strong, var(--surface-line)); }
.u-table-wrap.is-scroll:focus-visible { outline: 2px solid var(--surface-accent); outline-offset: 2px; }
.u-table-wrap.is-scroll thead th { position: sticky; top: 0; z-index: 1; background: var(--surface-panel); }
/* 좁은 화면에서는 글자로도 알린다(그림자를 못 보는 경우가 있다). */
.u-table-hint { display: none; margin: var(--sp-1) 0 0; font-size: var(--fs-12); color: var(--surface-muted); }
/* 표 바로 아래 설명 문단이 표 테두리에 붙지 않게 띄운다(표 밑 안내 한 줄은 좁은 화면에서만 보인다). */
.u-table-wrap + .u-table-hint + p, .u-table-wrap + p { margin-top: var(--sp-3); }
@media (max-width: 767px) {
  .u-table-hint { display: block; }
}
.u-table { width: 100%; border-collapse: collapse; font-size: var(--fs-13); white-space: nowrap; }
.u-table th, .u-table td { padding: 9px 10px; text-align: left; border-bottom: 1px solid var(--surface-line); }
.u-table thead th {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  background: var(--surface-sunken);
  color: var(--surface-muted);
  font-size: var(--fs-12);
  font-weight: var(--fw-bold);
  border-bottom: 1px solid var(--surface-line-strong);
}
.u-table tbody tr { transition: background var(--dur-fast) var(--ease-inout); }
.u-table tbody tr:hover { background: var(--surface-sunken); }
.u-table tbody tr:last-child > :is(td, th) { border-bottom: 0; }  /* 행 머리(th) 칸만 밑줄이 남아 반쯤 끊긴 줄로 보였다 */
/* 지금 보고 있는 행(목록과 본문이 한 화면에 있을 때 어느 줄을 열었는지 눈으로 보인다). */
/* 합계 줄. 마지막 줄 밑줄 지우기와 겹치지 않게 위쪽에 굵은 선을 둔다. */
.u-table tbody tr.is-total > :is(td, th) {
  border-top: 2px solid var(--surface-line-strong, var(--surface-line));
  font-weight: var(--fw-bold);
  color: var(--surface-ink-strong);
  background: var(--surface-sunken);
}
.u-table tbody tr.is-current { background: var(--accent-soft); }
.u-table tbody tr.is-current th[scope="row"] { color: var(--surface-ink-strong); }
.u-table .is-num { text-align: right; font-variant-numeric: tabular-nums; }
/* 행 링크 칸은 오른쪽에 붙여 둔다 — 표가 옆으로 넘쳐도 [상세] 가 잘리지 않는다. */
.u-table .is-actions {
  position: sticky;
  right: 0;
  text-align: right;
  background: var(--surface-panel);
  border-left: 1px solid var(--surface-line);
}
.u-table thead .is-actions { background: var(--surface-sunken); }
.u-table tbody tr:hover .is-actions { background: var(--surface-sunken); }
.u-table .is-actions a + a { margin-left: var(--sp-2); }
/* 동작 칸 안 폼(내리기·해제)은 링크 옆에 나란히 둔다. 폼은 블록이라 그대로 두면 링크 아래로 떨어져 행이 두 줄이 된다. */
.u-table .is-actions > form { display: inline-block; margin: 0; vertical-align: middle; }
.u-table .is-actions > * + form, .u-table .is-actions > form + * { margin-left: var(--sp-2); }
.u-table-empty { padding: var(--sp-6) var(--sp-4); text-align: center; color: var(--surface-muted); }
/* 표 위 건수. 쪽 나누기가 없는 표도 "전체 몇 건"을 안다. */
.u-table-count { margin: 0 0 var(--sp-2); font-size: var(--fs-12); color: var(--surface-muted); font-variant-numeric: tabular-nums; }
/* 정렬 가능한 열 머리. aria-sort 는 th 가 가지고(표준 자리) 화살표는 그 안 링크가 그린다. */
.u-th-sort { display: inline-flex; align-items: center; gap: 4px; color: inherit; text-decoration: none; white-space: nowrap; }
/* 화살표는 제 폭(1em)을 늘 차지한다: 정렬 중인 열로 바뀌어도 머리 폭이 흔들리지 않는다. */
.u-th-sort::after { content: '\2195'; flex: none; width: 1em; text-align: center; opacity: 0.45; font-size: var(--fs-11); }
/* 화면 읽기용 ", 정렬" 은 보이지 않고, 머리 글자를 긁어 복사할 때도 딸려 오지 않는다. */
.u-th-sort .sr-only { user-select: none; }
.u-th-sort:hover { color: var(--surface-ink); }
.u-table th[aria-sort="ascending"] .u-th-sort::after { content: '\25B2'; opacity: 1; }
.u-table th[aria-sort="descending"] .u-th-sort::after { content: '\25BC'; opacity: 1; }
.u-table th[aria-sort="ascending"] .u-th-sort,
.u-table th[aria-sort="descending"] .u-th-sort { color: var(--surface-ink); }

/* ── 열 종류(폭·정렬·줄바꿈) ─────────────────────────────────────────────
   ui.table 이 열 머리의 kind 를 표 클래스 c<N>-<종류> 로 옮기고, 여기서 N 번째 칸 전체(머리와 본문)에 적용한다.
   화면은 칸마다 클래스를 적지 않는다. :where() 로 감싸 우선순위를 0 으로 두어, 빈 줄(.u-table-empty)·
   휴대폰 카드(.is-cards) 규칙이 늘 이긴다. 폭은 "내용에 맞춤"(width: 1%) 과 "남는 폭"(text) 두 가지뿐이다.
     num      좁게, 오른쪽 정렬, 고정폭 숫자       date    내용 폭, 줄 바꾸지 않음
     status   내용 폭(알약·짧은 이름표)            actions 내용 폭, 오른쪽에 붙음
     id       표 폭의 일정 비율(최소 폭 있음)에서 말줄임(admin.js 가 잘린 칸에 전체 값을 title 로 단다)
     text     남는 폭을 쓰고 줄을 바꿈(최소 폭 아래로는 좁아지지 않는다)
   칸 안 보조 글(.u-cell-note)은 한 줄로 최대 폭에서 말줄임하고 전체 글은 title 로 단다. */
/* 열 머리는 종류와 관계없이 한 줄이다. 머리가 두세 줄로 꺾이면 행보다 머리가 높아지고, 좁은 숫자 열이
   "진단 / 횟수" 처럼 읽힌다. 머리 글자는 짧게 짓고(4~6자), 넘치면 표가 옆으로 밀린다(동작 칸은 붙박이). */
.u-table thead th { white-space: nowrap; }
.u-table thead th.is-num { text-align: right; }
:where(.u-table.c1-num) > * > tr > :nth-child(1),
:where(.u-table.c2-num) > * > tr > :nth-child(2),
:where(.u-table.c3-num) > * > tr > :nth-child(3),
:where(.u-table.c4-num) > * > tr > :nth-child(4),
:where(.u-table.c5-num) > * > tr > :nth-child(5),
:where(.u-table.c6-num) > * > tr > :nth-child(6),
:where(.u-table.c7-num) > * > tr > :nth-child(7),
:where(.u-table.c8-num) > * > tr > :nth-child(8),
:where(.u-table.c9-num) > * > tr > :nth-child(9),
:where(.u-table.c10-num) > * > tr > :nth-child(10),
:where(.u-table.c11-num) > * > tr > :nth-child(11),
:where(.u-table.c12-num) > * > tr > :nth-child(12),
:where(.u-table.c13-num) > * > tr > :nth-child(13),
:where(.u-table.c14-num) > * > tr > :nth-child(14) {
  width: 1%;
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
:where(.u-table.c1-date) > * > tr > :nth-child(1),
:where(.u-table.c2-date) > * > tr > :nth-child(2),
:where(.u-table.c3-date) > * > tr > :nth-child(3),
:where(.u-table.c4-date) > * > tr > :nth-child(4),
:where(.u-table.c5-date) > * > tr > :nth-child(5),
:where(.u-table.c6-date) > * > tr > :nth-child(6),
:where(.u-table.c7-date) > * > tr > :nth-child(7),
:where(.u-table.c8-date) > * > tr > :nth-child(8),
:where(.u-table.c9-date) > * > tr > :nth-child(9),
:where(.u-table.c10-date) > * > tr > :nth-child(10),
:where(.u-table.c11-date) > * > tr > :nth-child(11),
:where(.u-table.c12-date) > * > tr > :nth-child(12),
:where(.u-table.c13-date) > * > tr > :nth-child(13),
:where(.u-table.c14-date) > * > tr > :nth-child(14) {
  width: 1%;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
:where(.u-table.c1-status) > * > tr > :nth-child(1),
:where(.u-table.c2-status) > * > tr > :nth-child(2),
:where(.u-table.c3-status) > * > tr > :nth-child(3),
:where(.u-table.c4-status) > * > tr > :nth-child(4),
:where(.u-table.c5-status) > * > tr > :nth-child(5),
:where(.u-table.c6-status) > * > tr > :nth-child(6),
:where(.u-table.c7-status) > * > tr > :nth-child(7),
:where(.u-table.c8-status) > * > tr > :nth-child(8),
:where(.u-table.c9-status) > * > tr > :nth-child(9),
:where(.u-table.c10-status) > * > tr > :nth-child(10),
:where(.u-table.c11-status) > * > tr > :nth-child(11),
:where(.u-table.c12-status) > * > tr > :nth-child(12),
:where(.u-table.c13-status) > * > tr > :nth-child(13),
:where(.u-table.c14-status) > * > tr > :nth-child(14) {
  width: 1%;
  white-space: nowrap;
}
:where(.u-table.c1-actions) > * > tr > :nth-child(1),
:where(.u-table.c2-actions) > * > tr > :nth-child(2),
:where(.u-table.c3-actions) > * > tr > :nth-child(3),
:where(.u-table.c4-actions) > * > tr > :nth-child(4),
:where(.u-table.c5-actions) > * > tr > :nth-child(5),
:where(.u-table.c6-actions) > * > tr > :nth-child(6),
:where(.u-table.c7-actions) > * > tr > :nth-child(7),
:where(.u-table.c8-actions) > * > tr > :nth-child(8),
:where(.u-table.c9-actions) > * > tr > :nth-child(9),
:where(.u-table.c10-actions) > * > tr > :nth-child(10),
:where(.u-table.c11-actions) > * > tr > :nth-child(11),
:where(.u-table.c12-actions) > * > tr > :nth-child(12),
:where(.u-table.c13-actions) > * > tr > :nth-child(13),
:where(.u-table.c14-actions) > * > tr > :nth-child(14) {
  width: 1%;
  white-space: nowrap;
  text-align: right;
  position: sticky;
  right: 0;
  background: var(--surface-panel);
  border-left: 1px solid var(--surface-line);
}
:where(.u-table.c1-actions) > tbody > tr:hover > :nth-child(1),
:where(.u-table.c2-actions) > tbody > tr:hover > :nth-child(2),
:where(.u-table.c3-actions) > tbody > tr:hover > :nth-child(3),
:where(.u-table.c4-actions) > tbody > tr:hover > :nth-child(4),
:where(.u-table.c5-actions) > tbody > tr:hover > :nth-child(5),
:where(.u-table.c6-actions) > tbody > tr:hover > :nth-child(6),
:where(.u-table.c7-actions) > tbody > tr:hover > :nth-child(7),
:where(.u-table.c8-actions) > tbody > tr:hover > :nth-child(8),
:where(.u-table.c9-actions) > tbody > tr:hover > :nth-child(9),
:where(.u-table.c10-actions) > tbody > tr:hover > :nth-child(10),
:where(.u-table.c11-actions) > tbody > tr:hover > :nth-child(11),
:where(.u-table.c12-actions) > tbody > tr:hover > :nth-child(12),
:where(.u-table.c13-actions) > tbody > tr:hover > :nth-child(13),
:where(.u-table.c14-actions) > tbody > tr:hover > :nth-child(14) {
  background: var(--surface-sunken);
}
:where(.u-table.c1-id) > * > tr > :nth-child(1),
:where(.u-table.c2-id) > * > tr > :nth-child(2),
:where(.u-table.c3-id) > * > tr > :nth-child(3),
:where(.u-table.c4-id) > * > tr > :nth-child(4),
:where(.u-table.c5-id) > * > tr > :nth-child(5),
:where(.u-table.c6-id) > * > tr > :nth-child(6),
:where(.u-table.c7-id) > * > tr > :nth-child(7),
:where(.u-table.c8-id) > * > tr > :nth-child(8),
:where(.u-table.c9-id) > * > tr > :nth-child(9),
:where(.u-table.c10-id) > * > tr > :nth-child(10),
:where(.u-table.c11-id) > * > tr > :nth-child(11),
:where(.u-table.c12-id) > * > tr > :nth-child(12),
:where(.u-table.c13-id) > * > tr > :nth-child(13),
:where(.u-table.c14-id) > * > tr > :nth-child(14) {
  width: var(--table-col-id);
  min-width: var(--table-col-id-min);
  max-width: 0;              /* 내용 길이가 칸 폭을 밀어내지 않게 한다(폭은 width 비율이 정한다) */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
:where(.u-table.c1-text) > * > tr > :nth-child(1),
:where(.u-table.c2-text) > * > tr > :nth-child(2),
:where(.u-table.c3-text) > * > tr > :nth-child(3),
:where(.u-table.c4-text) > * > tr > :nth-child(4),
:where(.u-table.c5-text) > * > tr > :nth-child(5),
:where(.u-table.c6-text) > * > tr > :nth-child(6),
:where(.u-table.c7-text) > * > tr > :nth-child(7),
:where(.u-table.c8-text) > * > tr > :nth-child(8),
:where(.u-table.c9-text) > * > tr > :nth-child(9),
:where(.u-table.c10-text) > * > tr > :nth-child(10),
:where(.u-table.c11-text) > * > tr > :nth-child(11),
:where(.u-table.c12-text) > * > tr > :nth-child(12),
:where(.u-table.c13-text) > * > tr > :nth-child(13),
:where(.u-table.c14-text) > * > tr > :nth-child(14) {
  min-width: var(--table-col-text-min);
  white-space: normal;
  word-break: keep-all;
  overflow-wrap: anywhere;
}

/* 열이 10개 이상인 표(is-dense)는 칸 좌우 여백을 줄여 폭을 돌려준다. 날짜·시각은 여기서도 꺾지 않는다
   (예전에는 두 줄로 접었더니 "2026- / 09-17 / 18:10" 세 줄이 되어 행이 세 배로 높아졌다). */
.u-table.is-dense th, .u-table.is-dense td { padding-left: 8px; padding-right: 8px; }
/* 행 동작 칸은 한 줄이다. 동작이 둘이면 나란히 두고, 셋 이상이면 ui.row_actions 가 첫 동작만 남기고 나머지를
   [더 보기] 메뉴로 접는다(예전에는 위아래로 쌓아 한 행이 네 줄이 되었다). */
.u-table .row-links { display: inline-flex; flex-direction: row; flex-wrap: nowrap; align-items: center; justify-content: flex-end; gap: var(--sp-3); white-space: nowrap; }
.u-table .row-links form { display: inline; margin: 0; }

/* [더 보기] 메뉴. details·summary 라 스크립트 없이 열리고 키보드(Enter·Space)로도 연다. admin.js 가 바깥을 누르거나
   Esc 를 누르면 닫고, 연 단추로 초점을 돌려준다. 메뉴는 칸 위에 떠서 행 높이를 바꾸지 않는다. */
.u-row-more { position: relative; display: inline-block; }
.u-row-more > summary {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  list-style: none;
  cursor: pointer;
  color: var(--surface-accent-text);
  border-radius: var(--r-xs);
}
.u-row-more > summary::-webkit-details-marker { display: none; }
.u-row-more > summary::after { content: '\25BE'; font-size: var(--fs-11); }
.u-row-more > summary:hover { text-decoration: underline; }
.u-row-more > summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.u-row-more-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: var(--z-sticky);
  min-width: max-content;
  margin: 0;
  padding: var(--sp-1) 0;
  list-style: none;
  text-align: left;
  background: var(--surface-panel);
  border: 1px solid var(--surface-line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--sh-3);
}
.u-row-more-menu a, .u-row-more-menu button { display: block; width: 100%; padding: 6px var(--sp-3); text-align: left; white-space: nowrap; }
.u-row-more-menu a:hover { background: var(--surface-sunken); }
/* 붙박이(sticky) 동작 칸은 칸마다 쌓임 맥락을 만든다. 메뉴를 연 칸을 한 층 올려 아래 행의 칸이 메뉴를 덮지 않게 한다. */
.u-table :is(td, th):has(.u-row-more[open]) { z-index: var(--z-sticky); }
/* 표 틀은 옆으로 밀리는 틀이라(overflow) 넘친 메뉴를 자른다. 아래쪽 세 행에서 메뉴를 열면 그만큼 틀 아래를 늘린다. */
.u-table-wrap:has(tbody > tr:nth-last-child(-n+3) .u-row-more[open]) { padding-bottom: var(--table-menu-room); }

/* 칸 안 보조 글: 상태 알약 아래 거절 사유, 시각 아래 결과 이름처럼 짧은 칸에 딸린 한 줄. */
.u-table .u-cell-note {
  display: block;
  max-width: var(--table-cell-note-max);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--surface-muted);
  font-size: var(--fs-12);
}
/* 두 열을 한 칸에 합친 요약(동의 시각 아래 경로·문구 버전, 해지 시각 아래 방법·사유)은 조금 더 넓게 둔다. */
.u-table .u-cell-note.is-wide { max-width: var(--table-cell-note-wide); }

/* ── 휴대폰 폭에서 표를 카드로(cards=true 인 표만) ─────────────────────────
   열이 열 개가 넘는 목록 표는 390px 에서 두세 칸만 보인다. 옆으로 밀 수는 있지만
   "고장 난 표"로 읽힌다. 그래서 S 폭에서는 행 하나를 카드 한 장으로 세워 모든 칸을
   보여 준다. 칸 이름은 표 머리에서 온다 — ui.table 이 열 이름을 --u-col-N 에 담아
   주고 여기서 ::before 가 읽는다(화면이 칸마다 data-label 을 다시 적지 않는다). */
@media (max-width: 559px) {
  .u-table-wrap.is-cards { overflow-x: visible; background-image: none; }
  .u-table-wrap.is-cards + .u-table-hint { display: none; }
  .u-table.is-cards { white-space: normal; }
  .u-table.is-cards thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .u-table.is-cards, .u-table.is-cards tbody, .u-table.is-cards tr,
  .u-table.is-cards td, .u-table.is-cards tbody th {
    display: block;
    width: 100%;
    min-width: 0;
    max-width: none;
    overflow: visible;
    white-space: normal;
    position: static;
  }
  /* 칸 글은 어디서나 끊어 넘치지 않게 하지만(anywhere), 보조 글의 날짜는 "2026-09- / 16" 처럼 가르지 않는다. */
  .u-table.is-cards .u-cell-note { max-width: none; white-space: normal; overflow-wrap: break-word; }
  /* 카드에서는 동작 줄이 왼쪽에서 시작하므로 [더 보기] 메뉴도 왼쪽 끝에 맞춰 연다(오른쪽 맞춤이면 화면 밖으로 나간다). */
  .u-table.is-cards .row-links { justify-content: flex-start; }
  .u-table.is-cards .u-row-more-menu { right: auto; left: 0; }
  /* 행 머리(th scope=row)는 카드 제목이 된다 — 칸 이름 없이 굵게 맨 위에 둔다. */
  .u-table.is-cards tbody th {
    padding: 0 0 var(--sp-2);
    border: 0;
    text-align: left;
    font-size: var(--fs-14);
    font-weight: var(--fw-bold);
    color: var(--surface-ink-strong);
    overflow-wrap: anywhere;
  }
  .u-table.is-cards tbody th::before { content: none; }
  .u-table.is-cards tr { padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--surface-line); }
  .u-table.is-cards tr:last-child { border-bottom: 0; }
  /* 칸 이름과 값을 두 기둥처럼 보이게 하되 값은 그대로 흐르게 둔다(내어쓰기).
     칸을 flex 로 만들면 한 칸 안 배지 여러 개가 각각 줄바꿈 없는 항목이 되어 카드를 넘친다. */
  .u-table.is-cards td {
    display: block;
    padding: 3px 0 3px 96px;
    text-indent: -96px;
    border: 0;
    text-align: left;
    overflow-wrap: anywhere;
  }
  .u-table.is-cards td::before {
    display: inline-block;
    width: 88px;
    margin-right: 8px;
    text-indent: 0;
    vertical-align: top;
    color: var(--surface-muted);
    font-size: var(--fs-12);
  }
  /* 내어쓰기는 물려받는 값이다. 칸 안 알약·배지가 제 글자를 왼쪽으로 끌어당겨 칸 이름과
     겹치지 않게 자식에서 되돌린다. */
  .u-table.is-cards td > * { text-indent: 0; }
  /* 칸 이름은 열 차례를 따라간다(열 14개까지). */
  .u-table.is-cards td:nth-child(1)::before { content: var(--u-col-1); }
  .u-table.is-cards td:nth-child(2)::before { content: var(--u-col-2); }
  .u-table.is-cards td:nth-child(3)::before { content: var(--u-col-3); }
  .u-table.is-cards td:nth-child(4)::before { content: var(--u-col-4); }
  .u-table.is-cards td:nth-child(5)::before { content: var(--u-col-5); }
  .u-table.is-cards td:nth-child(6)::before { content: var(--u-col-6); }
  .u-table.is-cards td:nth-child(7)::before { content: var(--u-col-7); }
  .u-table.is-cards td:nth-child(8)::before { content: var(--u-col-8); }
  .u-table.is-cards td:nth-child(9)::before { content: var(--u-col-9); }
  .u-table.is-cards td:nth-child(10)::before { content: var(--u-col-10); }
  .u-table.is-cards td:nth-child(11)::before { content: var(--u-col-11); }
  .u-table.is-cards td:nth-child(12)::before { content: var(--u-col-12); }
  .u-table.is-cards td:nth-child(13)::before { content: var(--u-col-13); }
  .u-table.is-cards td:nth-child(14)::before { content: var(--u-col-14); }
  .u-table.is-cards .is-num { text-align: left; }
  /* 행 링크 칸은 카드 안에서는 붙박지 않고 맨 아래 한 줄이 된다. */
  .u-table.is-cards .is-actions {
    position: static;
    border-left: 0;
    background: transparent;
    padding: var(--sp-2) 0 0;
    text-indent: 0;
  }
  .u-table.is-cards .is-actions::before { content: none; }
  /* 열 종류가 actions 인 칸(칸에 is-actions 를 적지 않은 표)도 같다. */
  .u-table.is-cards.c1-actions td:nth-child(1),
  .u-table.is-cards.c2-actions td:nth-child(2),
  .u-table.is-cards.c3-actions td:nth-child(3),
  .u-table.is-cards.c4-actions td:nth-child(4),
  .u-table.is-cards.c5-actions td:nth-child(5),
  .u-table.is-cards.c6-actions td:nth-child(6),
  .u-table.is-cards.c7-actions td:nth-child(7),
  .u-table.is-cards.c8-actions td:nth-child(8),
  .u-table.is-cards.c9-actions td:nth-child(9),
  .u-table.is-cards.c10-actions td:nth-child(10),
  .u-table.is-cards.c11-actions td:nth-child(11),
  .u-table.is-cards.c12-actions td:nth-child(12),
  .u-table.is-cards.c13-actions td:nth-child(13),
  .u-table.is-cards.c14-actions td:nth-child(14) {
    position: static;
    border-left: 0;
    background: transparent;
    padding: var(--sp-2) 0 0;
    text-indent: 0;
  }
  .u-table.is-cards.c1-actions td:nth-child(1)::before,
  .u-table.is-cards.c2-actions td:nth-child(2)::before,
  .u-table.is-cards.c3-actions td:nth-child(3)::before,
  .u-table.is-cards.c4-actions td:nth-child(4)::before,
  .u-table.is-cards.c5-actions td:nth-child(5)::before,
  .u-table.is-cards.c6-actions td:nth-child(6)::before,
  .u-table.is-cards.c7-actions td:nth-child(7)::before,
  .u-table.is-cards.c8-actions td:nth-child(8)::before,
  .u-table.is-cards.c9-actions td:nth-child(9)::before,
  .u-table.is-cards.c10-actions td:nth-child(10)::before,
  .u-table.is-cards.c11-actions td:nth-child(11)::before,
  .u-table.is-cards.c12-actions td:nth-child(12)::before,
  .u-table.is-cards.c13-actions td:nth-child(13)::before,
  .u-table.is-cards.c14-actions td:nth-child(14)::before { content: none; }
  /* 빈 표는 카드가 아니라 안내 한 줄 그대로 둔다. */
  .u-table.is-cards td.u-table-empty { display: block; text-align: center; padding: var(--sp-6) var(--sp-4); text-indent: 0; }
  .u-table.is-cards td.u-table-empty::before { content: none; }
}

/* ── 배지·상태 알약 ───────────────────────────────────────────────────── */
.u-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: var(--fs-12);
  font-weight: var(--fw-bold);
  line-height: 1.5;
  border: 1px solid transparent;
  white-space: nowrap;
}
.u-badge-neutral { background: var(--neutral-soft); color: var(--neutral-ink); border-color: var(--neutral-line); }
.u-badge-success { background: var(--success-soft); color: var(--success-ink); border-color: var(--success-line); }
.u-badge-warning { background: var(--warning-soft); color: var(--warning-ink); border-color: var(--warning-line); }
.u-badge-danger { background: var(--danger-soft); color: var(--danger-ink); border-color: var(--danger-line); }
.u-badge-info { background: var(--info-soft); color: var(--info-ink); border-color: var(--info-line); }

/* 살아 있는 상태 점(대기열·작업 처리기). 흐릿한 맥박 하나만 쓴다. */
.u-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--surface-muted); margin-right: 6px; }
.u-dot-live { background: var(--success); animation: u-pulse 2s var(--ease-inout) infinite; }
.u-dot-warn { background: var(--warning); }
.u-dot-down { background: var(--danger); }

/* ── 탭 ───────────────────────────────────────────────────────────────── */
.u-tabs { display: flex; flex-wrap: wrap; gap: var(--sp-1); border-bottom: 1px solid var(--surface-line); margin-bottom: var(--sp-4); }
.u-tab {
  padding: 8px 14px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--surface-muted);
  font: inherit;
  font-size: var(--fs-13);
  font-weight: var(--fw-medium);
  text-decoration: none;
  cursor: pointer;
  transition: color var(--dur-base) var(--ease-inout), border-color var(--dur-base) var(--ease-inout);
}
.u-tab:hover { color: var(--surface-ink); text-decoration: none; }
.u-tab[aria-current="page"], .u-tab.is-on { color: var(--surface-accent); border-bottom-color: var(--surface-accent); font-weight: var(--fw-bold); }
.u-tab:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }

/* ── 이동 경로 ────────────────────────────────────────────────────────── */
.u-crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; list-style: none; margin: 0 0 var(--sp-3); padding: 0; font-size: var(--fs-12); color: var(--surface-muted); }
.u-crumbs li + li::before { content: '\203A'; margin-right: 6px; color: var(--surface-line-strong); }
.u-crumbs a { color: var(--surface-muted); text-decoration: none; }
.u-crumbs a:hover { color: var(--surface-accent); text-decoration: underline; }

/* ── 쪽 넘김 ────────────────────────────────────────────────────────────
   번호를 눌러 바로 그 쪽으로 간다(_ui.html 의 pager·list_pager). 줄 구성은
   [전체 건수·쪽 수] … [처음][이전][번호들][다음][끝] … [쪽 번호로 이동]. */
.u-pager { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); font-size: var(--fs-13); }
.u-pager-now { color: var(--surface-muted); font-variant-numeric: tabular-nums; }
.u-pager-total { margin-right: auto; color: var(--surface-muted); font-variant-numeric: tabular-nums; }
.u-pager-list { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; list-style: none; margin: 0; padding: 0; }
.u-pager-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2rem;
  padding: var(--btn-pad-y-sm) 6px;
  border-radius: var(--btn-radius);
  border: 1px solid transparent;
  color: var(--surface-muted);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}
a.u-pager-num:hover { background: var(--surface-sunken); border-color: var(--surface-line-strong); color: var(--surface-ink); text-decoration: none; }
a.u-pager-num:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.u-pager-num.is-now { background: var(--surface-accent); border-color: var(--surface-accent); color: var(--accent-on); font-weight: var(--fw-bold); }
.u-pager-gap { color: var(--surface-line-strong); padding: 0 2px; }
.u-pager-jump { display: flex; align-items: center; gap: 6px; color: var(--surface-muted); }
.u-pager-jump input[type=number] {
  width: 5rem;
  min-height: 0;
  padding: var(--btn-pad-y-sm) var(--btn-pad-x-sm);
  border-radius: var(--btn-radius);
  border: 1px solid var(--surface-line-strong);
  background: var(--surface-panel);
  color: var(--surface-ink);
  font: inherit;
  font-size: var(--btn-fs-sm);
  font-variant-numeric: tabular-nums;
}
.u-pager-jump input[type=number]:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* 좁은 화면(휴대폰)에서는 지금 쪽에서 먼 번호와 [처음]·[끝] 단추를 감춰 한 줄에 들어가게 한다.
   가운데로 가는 길은 '쪽 번호로 이동' 칸이 맡는다. */
@media (max-width: 559px) {
  .u-pager { justify-content: center; }
  .u-pager-total { margin-right: 0; width: 100%; text-align: center; }
  .u-pager-list > .is-far, .u-pager-list > .u-pager-edge { display: none; }
  .u-pager-jump { width: 100%; justify-content: center; }
}

/* ── 알림 줄·토스트 ───────────────────────────────────────────────────── */
.u-alert {
  display: flex;
  gap: var(--sp-2);
  align-items: flex-start;
  padding: 10px 14px;
  border-radius: var(--r-sm);
  border: 1px solid var(--info-line);
  background: var(--info-soft);
  color: var(--info-ink);
  font-size: var(--fs-13);
  margin-bottom: var(--sp-3);
}
.u-alert-success { border-color: var(--success-line); background: var(--success-soft); color: var(--success-ink); }
.u-alert-warning { border-color: var(--warning-line); background: var(--warning-soft); color: var(--warning-ink); }
.u-alert-danger { border-color: var(--danger-line); background: var(--danger-soft); color: var(--danger-ink); }
.u-alert-title { font-weight: var(--fw-bold); margin: 0 0 2px; }
.u-alert p { margin: 0; }

.u-toast {
  padding: 10px 14px;
  border-radius: var(--r-sm);
  background: var(--n-800);
  color: var(--n-0);
  font-size: var(--fs-13);
  box-shadow: var(--sh-3);
  animation: u-rise var(--dur-slow) var(--ease-out);
}

/* ── 겹침창 ───────────────────────────────────────────────────────────── */

/* ── 도움말 풍선 ──────────────────────────────────────────────────────── */
.u-tip { position: relative; display: inline-flex; align-items: center; border-bottom: 1px dotted var(--surface-line-strong); cursor: help; }
.u-tip-text {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-header);
  width: max-content;
  max-width: 260px;
  padding: 6px 10px;
  border-radius: var(--r-sm);
  background: var(--n-800);
  color: var(--n-0);
  font-size: var(--fs-12);
  font-weight: var(--fw-normal);
  line-height: 1.5;
  white-space: normal;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-base) var(--ease-inout), visibility var(--dur-base);
}
.u-tip:hover .u-tip-text, .u-tip:focus-visible .u-tip-text { opacity: 1; visibility: visible; }

/* ── 값 풍선(data-tip) ────────────────────────────────────────────────────
   그래프 조각·표 칸처럼 "이 조각이 무슨 값인가"를 알려 주는 짧은 풍선.
   브라우저 기본 title 을 쓰지 않는 까닭(2026-09-20 사장님 지적):
     - 1초쯤 늦게 뜬다. 막대 위를 훑을 때 따라오지 못해 답답하다.
     - 꾸밀 수 없다. 운영체제 기본 모양이라 화면과 따로 논다.
   여는 요소에 data-tip="글" 만 붙이면 된다. 글이 길면 u-tip(도움말 풍선)을 쓴다. */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 7px);
  left: 50%;
  transform: translateX(-50%) translateY(3px);
  z-index: var(--z-header);
  width: max-content;
  max-width: 220px;
  padding: 5px 9px;
  border-radius: var(--r-sm);
  background: var(--n-800);
  color: var(--n-0);
  font-size: var(--fs-12);
  font-weight: var(--fw-normal);
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  /* 빠르게 뜨고 빠르게 사라진다 - 막대 위를 훑을 때 풍선이 따라와야 한다. */
  transition: opacity var(--dur-fast, 120ms) var(--ease-out), transform var(--dur-fast, 120ms) var(--ease-out), visibility var(--dur-fast, 120ms);
}
/* 꼬리 */
[data-tip]::before {
  content: '';
  position: absolute;
  bottom: calc(100% + 2px);
  left: 50%;
  transform: translateX(-50%) translateY(3px);
  z-index: var(--z-header);
  border: 5px solid transparent;
  border-top-color: var(--n-800);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: inherit;
}
[data-tip]:hover::after, [data-tip]:focus-visible::after,
[data-tip]:hover::before, [data-tip]:focus-visible::before {
  opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0);
}
/* 표 맨 윗줄은 위로 띄울 자리가 없다(머리글을 가린다). 아래로 편다.
   손으로 붙이는 is-tip-below 와, 표 첫 줄에 자동으로 걸리는 규칙 둘 다 둔다. */
[data-tip].is-tip-below::after,
.u-table tbody tr:first-child [data-tip]::after { bottom: auto; top: calc(100% + 7px); }
[data-tip].is-tip-below::before,
.u-table tbody tr:first-child [data-tip]::before {
  bottom: auto; top: calc(100% + 2px); border-top-color: transparent; border-bottom-color: var(--n-800);
}

/* ── 진행률 막대 ──────────────────────────────────────────────────────── */
/* 칸이 좁으면 막대가 줄어야 한다. 예전에는 min-width 가 칸보다 커서 47px 넘쳤다. */
.u-progress { display: grid; gap: 4px; min-width: 0; max-width: 100%; }
.u-progress-head { display: flex; justify-content: space-between; gap: var(--sp-2); font-size: var(--fs-12); color: var(--surface-muted); }
.u-progress-head strong { color: var(--surface-ink); font-variant-numeric: tabular-nums; }
.u-progress-track { height: 8px; max-width: 100%; border-radius: var(--r-pill); background: var(--surface-line); overflow: hidden; }
.u-progress-bar {
  height: 100%;
  width: var(--u-pct, 0%);
  border-radius: inherit;
  background: var(--surface-accent);
  transition: width var(--dur-slow) var(--ease-out);
}
.u-progress-bar.is-warning { background: var(--warning); }
.u-progress-bar.is-danger { background: var(--danger); }
.u-progress-bar.is-success { background: var(--success); }
/* 얼마나 남았는지 알 수 없을 때만 쓴다. */
.u-progress.is-indeterminate .u-progress-bar { width: 35%; animation: u-slide 1.3s var(--ease-inout) infinite; }

/* ── 회전 표시 ────────────────────────────────────────────────────────── */
.u-spinner {
  display: inline-block;
  width: 16px; height: 16px;
  border-radius: 50%;
  border: 2px solid var(--surface-line-strong);
  border-top-color: var(--surface-accent);
  animation: u-spin 700ms linear infinite;
  vertical-align: -3px;
}
.u-spinner-lg { width: 28px; height: 28px; border-width: 3px; }

/* ── 뼈대(불러오는 중) ────────────────────────────────────────────────── */
.u-skeleton {
  display: block;
  height: 12px;
  border-radius: var(--r-xs);
  background: linear-gradient(90deg, var(--surface-line) 25%, var(--surface-sunken) 37%, var(--surface-line) 63%);
  background-size: 400% 100%;
  animation: u-shimmer 1.4s ease-in-out infinite;
}
.u-skeleton + .u-skeleton { margin-top: var(--sp-2); }
.u-skeleton-row { height: 16px; }

/* ── 비어 있는 상태 ───────────────────────────────────────────────────── */
.u-empty {
  display: grid;
  justify-items: center;
  gap: var(--sp-2);
  padding: var(--sp-7) var(--sp-4);
  text-align: center;
  color: var(--surface-muted);
  border: 1px dashed var(--surface-line-strong);
  border-radius: var(--r-md);
  background: var(--surface-sunken);
}
.u-empty-title { margin: 0; font-size: var(--fs-14); font-weight: var(--fw-bold); color: var(--surface-ink); }
.u-empty p { margin: 0; font-size: var(--fs-13); max-width: var(--measure-body); }

/* ── 위험한 작업 구역 ─────────────────────────────────────────────────────
   되돌리기 어려운 동작(이용 정지·키 재발급)은 보통 폼과 같은 자리에 두지 않는다. */
.u-danger-zone {
  margin-top: var(--sp-5);
  padding: var(--sp-4);
  border: 1px solid var(--danger);
  border-radius: var(--r-panel);
  background: var(--danger-soft);
}
.u-danger-zone-title { margin: 0 0 4px; font-size: var(--fs-13); font-weight: var(--fw-bold); color: var(--danger-ink); }
.u-danger-zone-body { margin: 0 0 var(--sp-3); font-size: var(--fs-12); color: var(--danger-ink); }
.u-danger-zone .actions { margin: 0; }

/* ── 값 목록(이름 + 값) ───────────────────────────────────────────────── */
.u-kv { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--sp-3); margin: 0 0 var(--sp-3); }
.u-kv > div { display: grid; gap: 2px; min-width: 0; }
.u-kv dt { font-size: var(--fs-12); font-weight: var(--fw-medium); color: var(--surface-muted); }
.u-kv dd { margin: 0; font-size: var(--fs-14); color: var(--surface-ink-strong); }
.u-kv dd .u-badge { vertical-align: 1px; }

/* ── 코드 상자(복사 단추 포함) ────────────────────────────────────────── */
.u-code { border: 1px solid var(--surface-line); border-radius: var(--r-md); background: var(--surface-sunken); overflow: hidden; }
.u-code-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
               padding: 6px 10px; border-bottom: 1px solid var(--surface-line); font-size: var(--fs-12); color: var(--surface-muted); }
.u-code pre { margin: 0; padding: var(--sp-3); overflow-x: auto; font-size: var(--fs-12); line-height: 1.6; color: var(--surface-ink); }
.u-code.is-wrap pre { white-space: pre-wrap; word-break: break-word; }
.u-code .copy.is-copied { border-color: var(--success); color: var(--success); }

/* ── 점수 등급 알약 ───────────────────────────────────────────────────── */
/* 등급 key(config/criteria.yaml 의 grades)를 구간 색으로 잇는다. 점수를 그리는 모든 것
   (등급 알약·게이지·영역 막대)이 이 한 줄을 통해 같은 색을 쓴다. */
.grade-poor { --band-color: var(--band-poor); }
.grade-fair { --band-color: var(--band-fair); }
.grade-good { --band-color: var(--band-good); }
.grade-excellent { --band-color: var(--band-excellent); }
.u-grade {
  display: inline-flex;
  align-items: center;
  padding: 1px 10px;
  border-radius: var(--r-pill);
  font-size: var(--fs-12);
  font-weight: var(--fw-bold);
  white-space: nowrap;
  color: var(--band-color, var(--surface-accent));
  background: color-mix(in srgb, var(--band-color, var(--surface-accent)) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--band-color, var(--surface-accent)) 40%, transparent);
}

/* ── 점수 카드 목록 ──────────────────────────────────────────────────────
   사이트 하나가 카드 한 장이다(명예의 전당 /hall). 390px 에서는 한 줄에 한 장이고, 넓어지면
   칸이 늘어 화면 폭을 다 쓴다. 카드 윗변 색은 등급 key 가 정한다(.grade-* → --band-color). */
.u-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: var(--sp-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.u-card {
  display: grid;
  align-content: start;
  gap: var(--sp-3);
  min-width: 0;
  padding: var(--panel-pad);
  border: 1px solid var(--surface-line);
  border-top: 3px solid var(--band-color, var(--surface-accent));
  border-radius: var(--r-panel);
  background: var(--surface-panel);
  box-shadow: var(--sh-1);
  transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.u-card:hover { transform: translateY(-2px); box-shadow: var(--sh-2); }
.u-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); min-width: 0; }
/* 등수 알약(K5). 여기 있는 카드는 모두 기준을 넘긴 사이트라 등수를 크게 외치지 않는다.
   구간 색을 쓰지 않고 테두리 하나로 조용히 둔다. */
.u-card-rank {
  flex: none;
  margin: 0;
  padding: 2px var(--sp-2);
  border: 1px solid var(--surface-line-strong);
  border-radius: var(--r-pill);
  font-size: var(--fs-12);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--surface-ink-strong);
  background: var(--surface-sunken);
}
.u-card-site { display: grid; gap: 2px; margin: 0; min-width: 0; }
.u-card-name { font-size: var(--fs-16); font-weight: var(--fw-bold); color: var(--surface-ink-strong); overflow-wrap: anywhere; }
.u-card-host { font-size: var(--fs-12); color: var(--surface-muted); overflow-wrap: anywhere; }
.u-card-score { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--sp-2); margin: 0; }
.u-card-num {
  font-size: var(--fs-34);
  font-weight: var(--fw-black);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--band-color, var(--surface-accent));
}
.u-card-max { font-size: var(--fs-12); color: var(--surface-muted); }
.u-card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0;
  font-size: var(--fs-12);
  color: var(--surface-muted);
}
.u-card-links { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.u-card-link { font-weight: var(--fw-semibold); color: var(--surface-accent); }
/* 그 사이트가 제 진단에서 가장 잘한 영역 한 줄(K5). 눌러 볼 까닭을 카드가 스스로 준다. */
.u-card-best {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0;
  padding-top: var(--sp-2);
  border-top: 1px solid var(--surface-line);
  font-size: var(--fs-12);
}
.u-card-best-label { color: var(--surface-muted); }
.u-card-best-value { font-weight: var(--fw-semibold); color: var(--surface-ink-strong); overflow-wrap: anywhere; }

/* ── 배지 미리보기(K5) ───────────────────────────────────────────────────
   남의 홈페이지에 붙는 그림이라 우리 화면 바탕이 아니라 밝은·어두운 바탕을 각각 깔고 보여 준다. */
.u-badge-previews { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin: 0; padding: 0; list-style: none; }
.u-badge-preview { display: grid; gap: var(--sp-2); justify-items: start; }
.u-badge-preview-frame {
  display: flex;
  align-items: center;
  padding: var(--sp-3);
  border: 1px solid var(--surface-line);
  border-radius: var(--r-inner);
}
.u-badge-preview-frame.is-light { background: var(--n-0); }
.u-badge-preview-frame.is-dark { background: var(--d-800); }
.u-badge-preview-label { font-size: var(--fs-12); color: var(--surface-muted); }

/* ── 한 줄 비중 막대(100%) ────────────────────────────────────────────── */
/* overflow:hidden 을 쓰지 않는다 - 조각 위 풍선(data-tip)이 잘린다. 대신 양끝 조각만 둥글린다. */
.u-sharebar { display: flex; height: 14px; border-radius: var(--r-pill); background: var(--surface-line); }
.u-sharebar span { display: block; height: 100%; width: var(--u-w, 0%); transition: width var(--dur-slow) var(--ease-out); }
.u-sharebar span:first-child { border-start-start-radius: var(--r-pill); border-end-start-radius: var(--r-pill); }
.u-sharebar span:last-child { border-start-end-radius: var(--r-pill); border-end-end-radius: var(--r-pill); }
/* 조각이 아주 좁아도 마우스를 올릴 수 있게 최소 너비를 준다(1px 조각은 잡히지 않는다). */
.u-sharebar span[data-tip] { min-width: 3px; cursor: default; }

/* ── 점수 구간(도넛 + 구간 표) ────────────────────────────────────────────
   반쪽짜리 상자에 작은 원 하나만 두지 않는다. 넓은 화면에서는 도넛 옆에 구간별
   건수·비중·막대를 함께 놓고, 좁은 화면에서는 도넛 아래로 쌓는다. */
.u-score-split { display: grid; gap: var(--sp-4); align-items: center; min-width: 0; }
@media (min-width: 1280px) {
  .u-score-split { grid-template-columns: minmax(0, 240px) minmax(0, 1fr); }
}
.u-band-table { width: 100%; border-collapse: collapse; font-size: var(--fs-13); white-space: normal; }
.u-band-table th, .u-band-table td { padding: 7px 8px; text-align: left; border-bottom: 1px solid var(--surface-line); }
.u-band-table thead th { font-size: var(--fs-12); font-weight: var(--fw-bold); color: var(--surface-muted); }
.u-band-table tbody tr:last-child th, .u-band-table tbody tr:last-child td { border-bottom: 0; }
.u-band-table .is-num { text-align: right; font-variant-numeric: tabular-nums; color: var(--surface-ink-strong); white-space: nowrap; min-width: 3.5em; }
/* 구간 이름은 좁은 화면에서 줄을 바꾸고(숫자 칸이 한 글자씩 접히지 않게), 막대 칸은 남는 폭을 쓴다. */
.u-band-table tbody th[scope="row"] { min-width: 6em; }
.u-band-name { display: inline-flex; align-items: center; gap: 6px; word-break: keep-all; color: var(--surface-ink); font-weight: var(--fw-medium); }
.u-band-swatch { width: 10px; height: 10px; border-radius: var(--r-xs); background: var(--u-band, var(--surface-line)); flex: none; }
.u-band-track { display: block; height: 8px; min-width: 48px; border-radius: var(--r-pill); background: var(--surface-line); overflow: hidden; }
.u-band-bar { display: block; height: 100%; width: var(--u-pct, 0%); border-radius: inherit; background: var(--u-band, var(--surface-accent));
              transition: width var(--dur-slow) var(--ease-out); }

/* ── 그래프 공통 ──────────────────────────────────────────────────────── */
.u-chart { display: grid; gap: var(--sp-2); min-width: 0; }
.u-chart svg { display: block; width: 100%; height: auto; overflow: visible; }
/* 원형 그래프는 폭을 다 쓰면 터무니없이 커진다. 읽기 좋은 크기에서 멈추고 가운데 둔다. */
.u-chart.is-donut svg { max-width: 200px; margin: 0 auto; }
.u-chart.is-gauge svg { max-width: 280px; margin: 0 auto; }
.u-chart-note { font-size: var(--fs-12); color: var(--surface-muted); margin: 0; }
.u-legend { display: flex; flex-wrap: wrap; gap: 4px var(--sp-4); list-style: none; margin: 0; padding: 0; font-size: var(--fs-12); color: var(--surface-muted); }
.u-legend li { display: inline-flex; align-items: center; gap: 6px; }
.u-legend-swatch { width: 10px; height: 10px; border-radius: var(--r-xs); display: inline-block; }
.u-axis { fill: var(--chart-ink); font-size: 10px; }
/* 측정 조건이 바뀐 지점에 세우는 세로 점선. 범례 대신 아래 설명 문장이 뜻을 적는다. */
.u-mark-line { stroke: var(--surface-line-strong); stroke-width: 1; stroke-dasharray: 3 3; }
.u-grid-line { stroke: var(--chart-grid); stroke-width: 1; }
.u-series-line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.u-series-dot { stroke: var(--surface-panel); stroke-width: 1.5; }
.u-gauge-track { fill: none; stroke: var(--surface-line); stroke-linecap: round; }
.u-gauge-value { fill: none; stroke: var(--surface-accent); stroke-linecap: round; }
.u-gauge-num { font-size: 20px; font-weight: 800; fill: var(--surface-ink-strong); }
.u-gauge-cap { font-size: 7px; fill: var(--surface-muted); }
.u-donut-hole-num { font-size: 20px; font-weight: 800; fill: var(--surface-ink-strong); }
.u-donut-hole-cap { font-size: 9px; fill: var(--surface-muted); }
.u-rank { display: grid; gap: var(--sp-2); margin: 0; padding: 0; list-style: none; }
.u-rank li { display: grid; grid-template-columns: minmax(80px, 30%) minmax(0, 1fr) auto; align-items: center; gap: var(--sp-2); font-size: var(--fs-12); }
/* 긴 이름은 두 줄까지 줄을 바꾸고 그 뒤는 말줄임한다(전체 이름은 title). 좁은 화면에서 한 글자만 남지 않게 한다. */
.u-rank-name {
  color: var(--surface-ink);
  overflow: hidden;
  word-break: keep-all;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  line-height: 1.35;
}
.u-rank-track { display: block; height: 10px; border-radius: var(--r-xs); background: var(--surface-line); overflow: hidden; }
.u-rank-bar { display: block; height: 100%; width: var(--u-pct, 0%); background: var(--surface-accent); border-radius: inherit; transition: width var(--dur-slow) var(--ease-out); }
.u-rank-num { font-variant-numeric: tabular-nums; color: var(--surface-muted); white-space: nowrap; }
.u-heat { display: flex; flex-wrap: wrap; gap: 3px; margin: 0; padding: 0; list-style: none; }
.u-heat li { width: 13px; height: 13px; border-radius: var(--r-xs); background: var(--surface-line); }
.u-heat li[data-level="1"] { background: var(--brand-100); }
.u-heat li[data-level="2"] { background: var(--brand-300); }
.u-heat li[data-level="3"] { background: var(--brand-500); }
.u-heat li[data-level="4"] { background: var(--brand-700); }

/* ── 상태 신호등(ui.status_lamp) ─────────────────────────────────────────
   분석 에이전트처럼 "지금 켜져서 제대로 도는가"를 한눈에 보이는 부품. 색은 성공·주의·중립·위험 역할 토큰만 읽는다.
   색만으로 뜻을 전하지 않도록 불빛 안에 모양이 다른 아이콘을 넣고, 옆에 이름표 글자를 둔다. 초록만 맥박이 뛴다. */
.u-lamp {
  --u-lamp: var(--surface-dim);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--surface-line);
  border-left: 3px solid var(--u-lamp);
  border-radius: var(--r-inner);
  background: var(--surface-sunken);
  min-width: 0;
}
.u-lamp.is-green { --u-lamp: var(--success); }
.u-lamp.is-yellow { --u-lamp: var(--warning); }
.u-lamp.is-red { --u-lamp: var(--danger); }
.u-lamp.is-gray { --u-lamp: var(--surface-dim); }
.u-lamp-light {
  position: relative;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin-top: 1px;
  border-radius: var(--r-round);
  background: var(--u-lamp);
  color: var(--surface-panel);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--u-lamp) 22%, transparent);
}
.u-lamp.is-gray .u-lamp-light { background: transparent; color: var(--surface-muted); box-shadow: inset 0 0 0 2px var(--surface-dim); }
.u-lamp-light.is-pulse::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: var(--r-round);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--u-lamp) 55%, transparent);
  animation: u-lamp-pulse 2.4s var(--ease-inout) infinite;
}
.u-lamp-body { flex: 1 1 14rem; min-width: 0; display: grid; gap: 2px; }
.u-lamp-body p { margin: 0; }
.u-lamp-label { font-size: var(--fs-14); font-weight: var(--fw-bold); color: var(--surface-ink-strong); line-height: var(--lh-tight); }
.u-lamp-title { font-weight: var(--fw-medium); color: var(--surface-muted); }
.u-lamp-meta { font-size: var(--fs-12); color: var(--surface-muted); font-variant-numeric: tabular-nums; }
.u-lamp-meta time { color: var(--surface-ink); font-weight: var(--fw-semibold); }
.u-lamp-detail { font-size: var(--fs-13); color: var(--surface-ink); line-height: var(--lh-normal); word-break: keep-all; }
.u-lamp-action { flex: none; margin-left: auto; align-self: center; }
.u-lamp-action form { margin: 0; }
.u-lamp.is-compact { padding: var(--sp-2) var(--sp-3); align-items: center; }
.u-lamp.is-compact .u-lamp-body { flex-basis: auto; }
@keyframes u-lamp-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--u-lamp) 55%, transparent); }
  70%, 100% { box-shadow: 0 0 0 9px color-mix(in srgb, var(--u-lamp) 0%, transparent); }
}

/* ── 화면 안 고르기(ui.tabs variant='segmented'): 기간·감시 주소 같은 값 고르기. 화면 이동 탭과 모양을 가른다. */
.u-tabs.is-segmented { display: inline-flex; flex-wrap: wrap; gap: 2px; padding: 3px; border: 1px solid var(--surface-line); border-radius: var(--r-md); background: var(--surface-sunken); margin-bottom: var(--sp-3); }
.u-tabs.is-segmented .u-tab { padding: 5px 12px; border: 0; border-radius: var(--r-sm); font-size: var(--fs-13); }
.u-tabs.is-segmented .u-tab[aria-current="page"], .u-tabs.is-segmented .u-tab.is-on { background: var(--surface-accent-soft); color: var(--surface-accent-text); border: 0; }

/* ── 설정 폼 한 줄(ui.form_row): 이름·설명 | 입력 ─────────────────────── */
.u-form-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-2) var(--sp-6); padding: var(--sp-4) 0; border-top: 1px solid var(--surface-line); min-width: 0; }
.u-form-row:first-child { border-top: 0; padding-top: 0; }
.u-form-row-head { min-width: 0; }
.u-form-row-head .u-hint { margin: var(--sp-1) 0 0; }
.u-form-row-label { margin: 0; font-size: var(--fs-14); font-weight: var(--fw-bold); color: var(--surface-ink-strong); }
.u-form-row-body { display: grid; gap: var(--sp-2); min-width: 0; align-content: start; }
.u-form-row-body > .u-input, .u-form-row-body > select, .u-form-row-body > textarea { max-width: var(--form-max); }
.u-form-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); padding-top: var(--sp-4); border-top: 1px solid var(--surface-line); }
@media (min-width: 768px) {
  .u-form-row { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); }
}

/* 숫자는 어디서나 자릿수가 흔들리지 않게 고정폭으로. */
.u-num, .u-count { font-variant-numeric: tabular-nums; }

/* ── 움직임 끄기 ──────────────────────────────────────────────────────── */
@keyframes u-spin { to { transform: rotate(360deg); } }
@keyframes u-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(22, 163, 74, 0.45); } 60% { box-shadow: 0 0 0 6px rgba(22, 163, 74, 0); } }
@keyframes u-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@keyframes u-slide { 0% { transform: translateX(-110%); } 100% { transform: translateX(310%); } }
@keyframes u-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .u-btn, .u-tile, .u-card, .u-input, .u-select, .u-textarea, .u-tab, .u-table tbody tr, .u-tip-text,
  .u-progress-bar, .u-rank-bar, .u-band-bar, .u-sharebar span { transition: none !important; }
  .u-card:hover { transform: none; }
  .u-btn.is-loading::before, .u-dot-live, .u-skeleton, .u-progress.is-indeterminate .u-progress-bar,
  .u-spinner, .u-toast, .u-lamp-light.is-pulse::after { animation: none !important; }
  .u-progress.is-indeterminate .u-progress-bar { width: 100%; opacity: 0.4; }
}

@media print {
  .u-btn, .u-pager { display: none !important; }
  .u-panel, .u-tile, .u-table-wrap { box-shadow: none; }
}


/* ── 목록 표 검색 양식(ui.list_search → form.filters) 기본 모양 ──────────────────────────
   공개·회원·관리자 어디서 불러도 같은 모양이 나오게 공용 부품에 둔다. 예전에는 admin.css 와 app.css(.mb-col)에만
   있어 공개 화면(관측소)에서는 브라우저 기본 모양으로 나왔다(2026-09-19). 두 화면의 세부 조정은 각 파일이 덮어쓴다. */
.filters { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; margin-bottom: var(--sp-3); }
.filters select, .filters input[type=text] {
  min-height: var(--btn-min-h); padding: 6px 12px; border: 1px solid var(--surface-line-strong); border-radius: var(--r-sm);
  background: var(--surface-panel); color: var(--surface-ink); font: inherit; font-size: var(--fs-13);
}
.filters[role="search"] input[type=text] { flex: 0 1 20rem; min-width: 0; max-width: 100%; }
.filters select:focus, .filters input[type=text]:focus { outline: none; border-color: var(--surface-accent); box-shadow: var(--sh-ring); }
.filters button {
  display: inline-flex; align-items: center; justify-content: center; min-height: var(--btn-min-h);
  padding: var(--btn-pad-y) var(--btn-pad-x); border: 1px solid var(--surface-accent); background: var(--surface-accent);
  color: var(--accent-on); border-radius: var(--btn-radius); cursor: pointer; font: inherit;
  font-size: var(--btn-fs); font-weight: var(--btn-fw); transition: background var(--dur-base) var(--ease-inout);
}
.filters button:hover { background: var(--surface-accent-2); border-color: var(--surface-accent-2); }
.filters a { font-size: var(--fs-13); }
