/* ============================================================================
   components.css — 태권도 올인원 컴포넌트
   ----------------------------------------------------------------------------
   읽는 순서: tokens.css → base.css → components.css

   규칙 세 가지
   1) 색을 직접 쓰지 않는다. 항상 tokens.css 의 변수를 쓴다.
   2) 상태를 색 하나로만 표시하지 않는다. 색 + 글자 + (필요하면) 모양을 같이 쓴다.
      색각이상인 사람에게 빨강/초록은 같은 회색이다.
   3) 조작 요소는 44px 보다 작아지지 않는다.

   목차
     01 버튼
     02 입력(필드/인풋/셀렉트/텍스트영역)
     03 선택(라디오·체크박스 칩, 복수선택 한도)
     04 반복 블록(지도진)
     05 카드
     06 배지·상태표시
     07 진행률·단계
     08 토스트
     09 표
     10 모달
     11 폼 섹션(설문 한 섹션)
     12 안내 상자(callout)·빈 상태·로딩
     13 점수 근거 막대
     14 민감 정보
     15 레일·고정 바·탭
     16 링크 복사칸

     ── 2차 (2·3단계 화면 — docs/design.md 7~12절) ──────────────────────────
     17 할 일 띠 · 할 일 배지 · 하위 탭
     18 원문 확인 카드 · 다른 답과 맞춰 보기
     19 품질 게이트 — 판정 · 차단(fail) · 경고(warn)
     20 본문 미리보기 · 본문 속 표시(hit) · 번호 핀
     21 검사 목록(checklist) — 리포트 검수 · 게이트 통과 항목
     22 선택 카드 · 비용 표시 · 원고 고르기 · 분량 막대
     23 오래 걸리는 작업(job) · 기본 진행 막대
     24 검색 노출 조회 결과(probe)
     25 원고 격자 · 두 숫자 나란히(count-pair) · 도장(stamp)
     26 원고 빈칸 — 줄 보기(codeview) · 바꾸기 전후(diff)
     27 발행 자리(4단계 자리만)
     28 검토 화면 레이아웃 · 이력

     ── 3차 (4단계 관장 전달 — docs/design.md 14~20절) ─────────────────────
     29 관장 전달 — 받아온 시각(sync-note) · 올렸어요 상태(upload-state) · 발행 자리의 전달 상태
        (관장이 보는 페이지 자체는 여기 없다 → src/delivery/assets/delivery.css)
   ========================================================================== */


/* ==========================================================================
   01 버튼
   --------------------------------------------------------------------------
   .btn                기본(중립). 테두리 있는 흰 버튼
   .btn--primary       그 화면에서 가장 중요한 행동. 한 화면에 하나가 원칙
   .btn--danger        되돌릴 수 없는 행동(삭제·폐기)
   .btn--ghost         테두리 없는 보조 행동
   .btn--link          글자만 있는 행동. 그래도 44px 높이는 지킨다
   .btn--lg / --sm     크기. 관장 화면은 lg, 운영자 표 안은 sm
   .btn--block         가로 꽉 채움
   [aria-busy="true"]  진행 중. 글자는 그대로 두고 회전자를 붙인다
   :disabled           눌러도 아무 일이 없다는 뜻. "왜" 를 옆에 적어라
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-h-sm);
  min-width: var(--tap-min);
  padding: var(--space-2) var(--space-4);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  color: var(--text-strong);
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  line-height: 1.2;
  text-decoration: none;
  text-align: center;
  white-space: nowrap;
  user-select: none;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.btn:hover:not(:disabled):not([aria-disabled="true"]) {
  background: var(--bg-hover);
  border-color: var(--text-muted);
  text-decoration: none;
}
.btn:active:not(:disabled) {
  transform: translateY(1px);
}
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-on-accent);
}
.btn--primary:hover:not(:disabled) {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

.btn--action {
  background: var(--action);
  border-color: var(--action);
  color: var(--text-on-dark);
}
.btn--action:hover:not(:disabled) {
  background: var(--action-hover);
  border-color: var(--action-hover);
}

.btn--danger {
  background: var(--bg-surface);
  border-color: var(--danger-border);
  color: var(--danger);
}
.btn--danger:hover:not(:disabled) {
  background: var(--danger-tint);
  border-color: var(--danger);
}

.btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-muted);
}
.btn--ghost:hover:not(:disabled) {
  background: var(--bg-hover);
  border-color: transparent;
  color: var(--text-strong);
}

.btn--link {
  background: transparent;
  border-color: transparent;
  color: var(--text-link);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  padding-inline: var(--space-2);
}
.btn--link:hover:not(:disabled) {
  background: transparent;
  border-color: transparent;
}

.btn--sm {
  min-height: 36px;
  padding: var(--space-1) var(--space-3);
  font-size: var(--fs-200);
}
/* ⚠️ --sm 은 36px 이라 단독으로 쓰면 터치 기준(44px)에 못 미친다.
   표 안처럼 주변에 여백이 확보되는 곳에서만 쓰고, 모바일 화면에는 쓰지 마라. */

.btn--lg {
  min-height: var(--control-h);
  padding: var(--space-3) var(--space-6);
  font-size: var(--fs-400);
  border-radius: var(--radius-md);
}
.btn--xl {
  min-height: var(--control-h-lg);
  padding: var(--space-4) var(--space-6);
  font-size: var(--fs-500);
  font-weight: var(--fw-heavy);
  border-radius: var(--radius-md);
}
.btn--xl.btn--primary {
  box-shadow: var(--shadow-accent);
}
.btn--block {
  display: flex;
  width: 100%;
}

/* 진행 중 — 글자를 "처리 중…" 으로 바꾸는 건 구현 담당자의 몫이다.
   여기서는 회전자와 잠금만 책임진다. */
.btn[aria-busy="true"] {
  cursor: progress;
  pointer-events: none;
}
.btn[aria-busy="true"]::before {
  content: "";
  width: 1em;
  height: 1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: tkd-spin 700ms linear infinite;
  flex: 0 0 auto;
}
@keyframes tkd-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .btn[aria-busy="true"]::before {
    animation: none;
    border-right-color: currentColor;
    opacity: 0.5;
  }
}

.btn-group {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.btn-group--end { justify-content: flex-end; }


/* ==========================================================================
   02 입력
   --------------------------------------------------------------------------
   구조는 항상 이 순서다. 순서를 바꾸지 마라 —
   화면 낭독기가 라벨 → 설명 → 오류 순으로 읽어야 한다.

   <div class="field">
     <label class="field__label" for="x">도장 이름 <span class="req">필수</span></label>
     <p class="field__hint" id="x-hint">동/구가 지역 키워드로 쓰입니다</p>
     <input class="input" id="x" aria-describedby="x-hint x-err" aria-invalid="true">
     <p class="field__error" id="x-err">도장 이름을 적어주세요</p>
   </div>
   ========================================================================== */

.field {
  display: block;
  margin: 0;
}
.field + .field {
  margin-top: var(--space-5);
}

.field__label {
  display: block;
  font-size: var(--fs-400);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
  line-height: var(--lh-snug);
  margin-bottom: var(--space-2);
}
.field__label .req {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 2px var(--space-2);
  border-radius: var(--radius-xs);
  background: var(--accent-tint);
  border: var(--border-w) solid var(--accent-border);
  color: var(--accent-text);
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  vertical-align: 2px;
}
/* 선택 항목에는 표시를 달지 않는다. 섹션 단위로 한 번만 알린다(11절).
   꼭 문항 단위로 알려야 하면 이걸 쓴다. */
.field__label .opt {
  display: inline-block;
  margin-left: var(--space-2);
  color: var(--text-muted);
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  vertical-align: 2px;
}

.field__hint {
  /* margin-top 을 0 으로 못 박지 않는다 — .stack 안에 놓이면 위 간격을 받아야 한다 */
  margin-inline: 0;
  margin-bottom: var(--space-2);
  font-size: var(--fs-300);
  font-weight: var(--fw-normal);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}

.input,
.textarea,
.select {
  display: block;
  width: 100%;
  min-height: var(--control-h);
  padding: var(--space-3);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  color: var(--text);
  font-size: var(--fs-400);
  line-height: var(--lh-snug);
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
  /* iOS 는 16px 미만 입력칸에서 자동 확대한다. --fs-400(17px) 이라 안전하다. */
}
.textarea {
  min-height: 108px;
  line-height: var(--lh-base);
}
.select {
  appearance: none;
  padding-right: var(--space-10);
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 22px center, right 16px center;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}
.input:hover,
.textarea:hover,
.select:hover {
  border-color: var(--text-muted);
}
.input:focus-visible,
.textarea:focus-visible,
.select:focus-visible {
  outline: var(--focus-w) solid var(--focus);
  outline-offset: 0;
  border-color: var(--focus);
}
.input:disabled,
.textarea:disabled,
.select:disabled {
  background: var(--bg-inset);
  color: var(--text-faint);
  border-color: var(--border);
  cursor: not-allowed;
}
.input[readonly],
.textarea[readonly] {
  background: var(--bg-surface-2);
  border-style: dashed;
}

/* 작은 입력칸 — 운영자 화면의 조밀한 영역 전용 */
.input--sm {
  min-height: var(--control-h-sm);
  padding: var(--space-2) var(--space-3);
  font-size: var(--fs-300);
}

/* 보기에 없을 때 직접 적는 칸. 파선으로 "본 보기의 곁가지"임을 보인다 */
.input--etc {
  margin-top: var(--space-2);
  background: var(--bg-inset);
  border-style: dashed;
  font-size: var(--fs-300);
  min-height: var(--tap-min);
}

/* 오류 — 빨간 테두리 '만' 으로 알리지 않는다. 항상 글과 아이콘을 같이 준다. */
.input[aria-invalid="true"],
.textarea[aria-invalid="true"],
.select[aria-invalid="true"] {
  border-color: var(--danger);
  border-width: var(--border-w-strong);
  background: var(--danger-tint);
}
.field__error {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
  color: var(--danger);
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
}
.field__error::before {
  content: "⚠";
  flex: 0 0 auto;
  font-size: 1em;
  line-height: inherit;
}

/* 잘 들어갔다는 표시 (도장명 중복 확인 등) */
.field__ok {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
  color: var(--ok);
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
}
.field__ok::before { content: "✓"; }

/* 글자 수 카운터 */
.field__count {
  display: block;
  margin-top: var(--space-1);
  text-align: right;
  font-size: var(--fs-200);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.field__count[data-state="over"] {
  color: var(--danger);
  font-weight: var(--fw-bold);
}

/* 검색칸 */
.searchfield {
  position: relative;
  display: block;
}
.searchfield .input {
  padding-left: var(--space-10);
}
.searchfield__icon {
  position: absolute;
  left: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  pointer-events: none;
}


/* ==========================================================================
   03 선택 — 라디오/체크박스 칩
   --------------------------------------------------------------------------
   원본 설문의 알약 모양 칩을 이어받되 세 가지를 고쳤다.
     · 네이티브 input 을 숨기지 않는다 (화면 낭독기·키보드가 그대로 동작)
     · 체크 상태를 색만으로 알리지 않는다 (테두리 2px + 체크표 + 굵은 글씨)
     · 높이 44px 이상

   묶음은 반드시 fieldset/legend 로 감싼다.
     <fieldset class="choices-group">
       <legend class="field__label">주 타깃 수련생 <span class="opt">복수 선택</span></legend>
       <p class="choices__meta">...</p>
       <div class="choices" data-max="3"> ... </div>
     </fieldset>
   ========================================================================== */

.choices-group {
  display: block;
}
.choices-group + .choices-group,
.field + .choices-group,
.choices-group + .field {
  margin-top: var(--space-5);
}

.choices {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
/* 보기 글이 긴 문항은 세로로 쌓는다 (모바일에서 칩이 두 줄로 깨지는 것보다 낫다) */
.choices--stack {
  flex-direction: column;
  flex-wrap: nowrap;
}
.choices--stack .choice {
  width: 100%;
  border-radius: var(--radius-sm);
  justify-content: flex-start;
}

.choice {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-4);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  color: var(--text);
  font-size: var(--fs-300);
  line-height: var(--lh-snug);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.choice:hover {
  border-color: var(--text-muted);
  background: var(--bg-hover);
}
.choice input[type="radio"],
.choice input[type="checkbox"] {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  accent-color: var(--accent);
  cursor: pointer;
  margin: 0;
}
/* 칩 안의 input 이 포커스를 받으면 칩 전체를 표시한다 */
.choice:has(input:focus-visible) {
  outline: var(--focus-w) solid var(--focus);
  outline-offset: 2px;
}
.choice input:focus-visible {
  outline: none;
}

/* 선택됨 — 색 + 굵기 + 2px 테두리. `.is-checked` 는 :has() 미지원 대비 */
.choice:has(input:checked),
.choice.is-checked {
  border-color: var(--accent);
  border-width: var(--border-w-strong);
  background: var(--accent-tint);
  color: var(--accent-text);
  font-weight: var(--fw-bold);
  /* 2px 테두리로 커진 만큼 안쪽을 줄여 줄이 흔들리지 않게 한다 */
  padding: calc(var(--space-2) - 1px) calc(var(--space-4) - 1px);
}

.choice input:disabled {
  cursor: not-allowed;
}
.choice:has(input:disabled) {
  opacity: 0.45;
  cursor: not-allowed;
  background: var(--bg-inset);
}

/* 복수선택 한도 — 칩 위에 항상 보이는 카운터 */
.choices__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  font-size: var(--fs-300);
  color: var(--text-muted);
}
.choices__count {
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}
/* 한도에 닿았을 때 — 카운터가 먼저 말하고, 미선택 칩이 흐려진다.
   ⚠️ 흐려지기만 할 뿐 disabled 를 붙이지 마라. 눌렀을 때 아무 일도 안 일어나면
      고장으로 읽힌다. 눌리면 토스트로 "최대 3개까지예요" 를 띄워라(08절). */
.choices__meta[data-state="maxed"] {
  color: var(--warn);
  font-weight: var(--fw-bold);
}
.choices[data-state="maxed"] .choice:not(:has(input:checked)):not(.is-checked) {
  opacity: 0.5;
  border-style: dashed;
}

/* 줄로 늘어선 형태(운영자 화면 필터) */
.filterbar {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  align-items: center;
}
.filterbar .choice {
  min-height: var(--tap-min);
  font-size: var(--fs-200);
}


/* ==========================================================================
   04 반복 블록 — 지도진 입력
   --------------------------------------------------------------------------
   <div class="repeater">
     <div class="repeater__item">
       <div class="repeater__head">
         <span class="repeater__no">지도진 1</span>
         <button class="repeater__remove" type="button">삭제</button>
       </div>
       … 필드들 …
     </div>
     <button class="repeater__add" type="button">+ 지도진 추가</button>
   </div>

   번호("지도진 1")는 삭제 후 반드시 다시 매긴다. 번호가 2,4,5 로 남으면
   관장님은 자기가 뭔가 잘못했다고 생각한다.
   ========================================================================== */

.repeater {
  display: block;
}
.repeater__item {
  position: relative;
  border: var(--border-w) dashed var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--bg-surface-2);
  padding: var(--space-4);
  margin-bottom: var(--space-3);
}
.repeater__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.repeater__no {
  font-size: var(--fs-300);
  font-weight: var(--fw-heavy);
  color: var(--text-strong);
}
.repeater__remove {
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: var(--fs-300);
  font-weight: var(--fw-medium);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
.repeater__remove:hover {
  color: var(--danger);
  background: var(--danger-tint);
  text-decoration: none;
}
/* 첫 블록은 지울 수 없다면 숨기지 말고 비활성으로 둔다 — 갑자기 사라지면 혼란스럽다 */
.repeater__remove:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.repeater__add {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  min-height: var(--control-h);
  border: var(--border-w-strong) dashed var(--accent-border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  color: var(--accent-text);
  font-size: var(--fs-400);
  font-weight: var(--fw-bold);
}
.repeater__add:hover {
  background: var(--accent-tint);
  border-color: var(--accent);
}
.repeater__add:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
/* 몇 개까지 추가할 수 있는지 미리 알린다 */
.repeater__limit {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-300);
  color: var(--text-muted);
  text-align: center;
}


/* ==========================================================================
   05 카드
   ========================================================================== */

.card {
  background: var(--bg-surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}
.card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: var(--border-w) solid var(--border-muted);
}
.card__title {
  font-size: var(--fs-500);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
  margin: 0;
}
.card__sub {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-300);
  color: var(--text-muted);
}
.card__body {
  padding: var(--space-5);
}
.card__foot {
  padding: var(--space-4) var(--space-5);
  border-top: var(--border-w) solid var(--border-muted);
  background: var(--bg-surface-2);
  border-radius: 0 0 var(--radius-md) var(--radius-md);
}
.card--flat { box-shadow: none; }
.card--accent { border-left: 4px solid var(--accent); }
.card--action { border-left: 4px solid var(--action); }

/* 목록의 한 줄 (도장 목록). 카드보다 얇고, 통째로 누를 수 있다 */
.list-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--bg-surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-2);
  text-decoration: none;
  color: inherit;
}
.list-row:hover {
  border-color: var(--border-strong);
  background: var(--bg-hover);
  text-decoration: none;
}
.list-row__main {
  flex: 1 1 auto;
  min-width: 0;
}
.list-row__name {
  display: block;
  font-size: var(--fs-400);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}
.list-row__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin-top: var(--space-1);
  font-size: var(--fs-200);
  color: var(--text-muted);
}
.list-row__side {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}


/* ==========================================================================
   06 배지 — 상태 표시
   --------------------------------------------------------------------------
   배지는 색 + 글자다. 점(dot)만 있는 배지는 만들지 않는다.
   진행 상태 이름은 계획서 4-3 의 값을 그대로 쓴다.
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 3px var(--space-2);
  border: var(--border-w) solid var(--pending-border);
  border-radius: var(--radius-pill);
  background: var(--pending-tint);
  color: var(--pending);
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  line-height: 1.5;
  white-space: nowrap;
}
.badge--ok      { background: var(--ok-tint);     border-color: var(--ok-border);     color: var(--ok); }
.badge--warn    { background: var(--warn-tint);   border-color: var(--warn-border);   color: var(--warn); }
.badge--danger  { background: var(--danger-tint); border-color: var(--danger-border); color: var(--danger); }
.badge--info    { background: var(--info-tint);   border-color: var(--info-border);   color: var(--info); }
.badge--live    { background: var(--live-tint);   border-color: var(--live-border);   color: var(--live); }
.badge--accent  { background: var(--accent-tint); border-color: var(--accent-border); color: var(--accent-text); }
.badge--secret  { background: var(--secret-tint); border-color: var(--secret-border); color: var(--secret); }
.badge--lg {
  font-size: var(--fs-300);
  padding: var(--space-1) var(--space-3);
}

/* "확인 필요" — 계획서의 일급 값. 빈 값과 구별되어야 한다 */
.badge--unknown {
  background: var(--bg-surface);
  border-style: dashed;
  border-color: var(--border-strong);
  color: var(--text-muted);
}

/* 진행 중인 것은 점이 숨 쉰다 */
.badge--live::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  animation: tkd-pulse 1.6s var(--ease) infinite;
}
@keyframes tkd-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* 임시 저장 표시 — 조용해야 한다. 눈에 띄면 오히려 불안하다 */
.save-status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--fs-200);
  color: var(--text-muted);
  min-height: 20px;   /* 글이 바뀌어도 줄이 튀지 않게 */
}
.save-status[data-state="saving"] { color: var(--text-muted); }
.save-status[data-state="saved"]::before { content: "✓"; color: var(--ok); font-weight: var(--fw-bold); }
.save-status[data-state="error"] { color: var(--danger); font-weight: var(--fw-bold); }
.save-status[data-state="error"]::before { content: "⚠"; }


/* ==========================================================================
   07 진행률 · 단계
   --------------------------------------------------------------------------
   ⚠️ 설문 진행률은 "답한 문항 수 / 전체 문항 수" 로 계산하지 마라.
      선택 문항을 건너뛴 사람의 막대가 영영 차지 않아서, 끝까지 답해도
      "덜 한 것 같은" 기분이 남는다. 반드시 "지금 섹션 / 전체 섹션" 으로 센다.
   ========================================================================== */

.progress {
  display: block;
  height: 8px;
  background: var(--track);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.progress__fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  border-radius: inherit;
  transition: width var(--dur-slow) var(--ease);
}
.progress--thin { height: 5px; }
.progress--ok .progress__fill { background: var(--ok); }

.progress-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
  font-size: var(--fs-300);
}
.progress-head__now {
  font-weight: var(--fw-heavy);
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
}
.progress-head__left {
  color: var(--text-muted);
}

/* 섹션 걸음표 — 가로 스크롤되는 알약들. 모바일에서 7개가 한눈에 들어온다 */
.steps {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: var(--space-1);
  margin: 0;
  list-style: none;
}
.steps::-webkit-scrollbar { display: none; }
.steps__item { flex: 0 0 auto; }
.steps__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  color: var(--text-muted);
  font-size: var(--fs-200);
  font-weight: var(--fw-bold);
  text-decoration: none;
  white-space: nowrap;
}
.steps__link:hover { background: var(--bg-hover); text-decoration: none; }

/* 지금 있는 곳 — 색 + 굵은 테두리 + aria-current 로 세 겹 표시 */
.steps__link[aria-current="step"] {
  background: var(--action);
  border-color: var(--action);
  color: var(--text-on-dark);
}
/* 지나온 곳 — 체크. "다 채웠다"가 아니라 "지나왔다"는 뜻이다 */
.steps__link[data-state="done"] {
  border-color: var(--ok-border);
  background: var(--ok-tint);
  color: var(--ok);
}
.steps__link[data-state="done"]::before { content: "✓"; }
/* 건너뛴 곳 — 나무라지 않는다. 회색 파선일 뿐 */
.steps__link[data-state="skipped"] {
  border-style: dashed;
  color: var(--text-muted);
}

/* 세로 목록형(데스크톱 왼쪽 고정 영역) */
.steps--vertical {
  flex-direction: column;
  overflow: visible;
}
.steps--vertical .steps__link {
  width: 100%;
  justify-content: flex-start;
  border-radius: var(--radius-sm);
  font-size: var(--fs-300);
}


/* ==========================================================================
   08 토스트
   --------------------------------------------------------------------------
   토스트는 "알아도 그만"인 말만 담는다.
   실패·오류는 토스트로 끝내지 마라. 화면에 남는 상자(12절 .callout--danger)로 알린다.

   <div class="toast-region" role="status" aria-live="polite"> … </div>
   오류만큼은 role="alert" aria-live="assertive" 인 별도 영역을 쓴다.
   ========================================================================== */

.toast-region {
  position: fixed;
  left: 50%;
  bottom: calc(var(--space-6) + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: center;
  width: calc(100% - var(--space-8));
  max-width: 420px;
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-pill);
  background: var(--rail-bg);
  color: var(--rail-fg);
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  box-shadow: var(--shadow-lg);
  pointer-events: auto;
  animation: tkd-toast-in var(--dur) var(--ease);
  max-width: 100%;
}
@keyframes tkd-toast-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
.toast--warn   { background: var(--warn-tint);   color: var(--warn);   border: var(--border-w-strong) solid var(--warn-border); }
.toast--ok     { background: var(--ok-tint);     color: var(--ok);     border: var(--border-w-strong) solid var(--ok-border); }
.toast--danger { background: var(--danger-tint); color: var(--danger); border: var(--border-w-strong) solid var(--danger-border); }
.toast__close {
  margin-left: var(--space-2);
  min-width: var(--tap-min);
  min-height: var(--tap-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: inherit;
  opacity: 0.8;
}


/* ==========================================================================
   09 표
   --------------------------------------------------------------------------
   모바일에서 표를 가로 스크롤로 밀게 하지 마라. 관장/운영자 모두 놓친다.
   768px 미만에서는 .table--cards 를 써서 줄 하나가 카드 하나가 되게 한다.
   (각 <td> 에 data-label 을 넣으면 제목이 자동으로 붙는다)
   ========================================================================== */

.table-wrap {
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  overflow: hidden;
}
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-300);
}
.table th,
.table td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  vertical-align: middle;
  border-bottom: var(--border-w) solid var(--border-muted);
}
.table thead th {
  position: sticky;
  top: 0;
  z-index: var(--z-base);
  background: var(--bg-surface-2);
  color: var(--text-muted);
  font-size: var(--fs-200);
  font-weight: var(--fw-bold);
  white-space: nowrap;
  border-bottom: var(--border-w-strong) solid var(--border);
}
.table tbody tr:last-child td {
  border-bottom: 0;
}
.table tbody tr:hover {
  background: var(--bg-hover);
}
.table__num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* 정렬 가능한 머리글 */
.table th[aria-sort] { cursor: pointer; }
.table th[aria-sort="ascending"]::after  { content: " ▲"; color: var(--accent-text); }
.table th[aria-sort="descending"]::after { content: " ▼"; color: var(--accent-text); }

@media (max-width: 767px) {
  .table--cards thead {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden; clip: rect(0, 0, 0, 0);
  }
  .table--cards,
  .table--cards tbody,
  .table--cards tr,
  .table--cards td {
    display: block;
    width: 100%;
  }
  .table--cards tr {
    border: var(--border-w) solid var(--border);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    margin-bottom: var(--space-2);
    padding: var(--space-2) 0;
  }
  .table--cards td {
    display: flex;
    justify-content: space-between;
    gap: var(--space-4);
    border-bottom: 0;
    padding: var(--space-2) var(--space-4);
  }
  .table--cards td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    color: var(--text-muted);
    font-size: var(--fs-200);
    font-weight: var(--fw-bold);
  }
  .table-wrap { border: 0; background: none; }
}


/* ==========================================================================
   10 모달
   --------------------------------------------------------------------------
   <div class="modal-backdrop">
     <div class="modal" role="dialog" aria-modal="true" aria-labelledby="t">
   구현 담당자에게: 열 때 첫 조작 요소로 포커스를 옮기고, 닫을 때 원래 자리로
   돌려놓고, Esc 로 닫히게 하고, 배경 스크롤을 잠가라. CSS 가 해주지 않는다.
   ========================================================================== */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: var(--bg-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  overflow-y: auto;
}
.modal {
  width: 100%;
  max-width: 460px;
  background: var(--bg-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: tkd-modal-in var(--dur) var(--ease);
}
@keyframes tkd-modal-in {
  from { opacity: 0; transform: translateY(10px) scale(0.99); }
  to   { opacity: 1; transform: none; }
}
.modal--wide { max-width: 720px; }
.modal__head {
  padding: var(--space-6) var(--space-6) var(--space-3);
  text-align: center;
}
.modal__icon { font-size: var(--fs-800); line-height: 1; }
.modal__title {
  margin: var(--space-3) 0 0;
  font-size: var(--fs-600);
}
.modal__body {
  padding: 0 var(--space-6) var(--space-5);
  text-align: center;
  color: var(--text);
  line-height: var(--lh-relaxed);
}
.modal__foot {
  padding: var(--space-4) var(--space-6) var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
@media (min-width: 600px) {
  .modal__foot--inline {
    flex-direction: row-reverse;   /* 주 행동이 오른쪽 */
    justify-content: flex-start;
  }
}


/* ==========================================================================
   11 폼 섹션 — 설문의 한 덩어리
   --------------------------------------------------------------------------
   이 컴포넌트가 "긴 설문" 문제를 푸는 자리다.
   섹션 머리에 세 가지를 반드시 같이 보여준다.
     ① 몇 번째 섹션인지        ② 무엇을 묻는지        ③ 필수인지 전부 선택인지
   ③ 이 가장 중요하다. 선택 섹션은 배지 하나로 "안 써도 됩니다"를 못 박는다.
   ========================================================================== */

.form-section {
  background: var(--bg-surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-5) var(--space-4);
  margin-bottom: var(--space-4);
}
@media (min-width: 600px) {
  .form-section { padding: var(--space-6); }
}

.form-section__head {
  margin-bottom: var(--space-5);
  padding-bottom: var(--space-4);
  border-bottom: var(--border-w) solid var(--border-muted);
}
.form-section__eyebrow {
  display: block;
  font-size: var(--fs-200);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-wide);
  color: var(--text-muted);
  margin-bottom: var(--space-1);
  font-variant-numeric: tabular-nums;
}
.form-section__title {
  font-size: var(--fs-600);
  color: var(--text-strong);
  margin: 0;
}
.form-section__desc {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-300);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}

/* 섹션 전체가 선택일 때. 초록이 아니라 회색이다 —
   "잘했다"가 아니라 "부담 갖지 말라"는 말이라서. */
.form-section__optional {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--bg-surface-2);
  border: var(--border-w) dashed var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: var(--fs-300);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
}
/* 필수가 들어 있는 섹션 */
.form-section__required {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--accent-tint);
  border: var(--border-w) solid var(--accent-border);
  border-radius: var(--radius-sm);
  color: var(--accent-text);
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
}

.form-section__body > * + * {
  margin-top: var(--space-5);
}

/* 섹션 아래 이동 줄 — "건너뛰기"를 부끄러운 행동으로 만들지 않는다.
   다음으로 가기와 같은 크기, 같은 줄에 둔다. */
.form-nav {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: var(--border-w) solid var(--border-muted);
  flex-wrap: wrap;
}
.form-nav .btn { flex: 1 1 0; }
.form-nav__hint {
  flex-basis: 100%;
  margin: var(--space-3) 0 0;
  font-size: var(--fs-300);
  color: var(--text-muted);
  text-align: center;
}

/* 하단 고정 바 — 모바일에서 진행률과 다음 버튼이 늘 손 닿는 곳에 있어야 한다 */
.sticky-bar {
  position: sticky;
  bottom: 0;
  z-index: var(--z-sticky);
  background: var(--bg-surface);
  border-top: var(--border-w) solid var(--border);
  box-shadow: 0 -4px 16px rgba(21, 33, 59, 0.08);
  padding: var(--space-3) var(--space-4)
           calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
}
.sticky-bar__inner {
  max-width: var(--container-form);
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  flex-wrap: wrap;
}
.sticky-bar__inner .btn { flex: 1 1 auto; }
/* 진행률 덩어리. 좁은 폰에서는 버튼 위로 한 줄을 통째로 차지한다 —
   375px 에서 막대·글자·버튼 둘을 한 줄에 우겨넣으면 전부 읽히지 않는다. */
.sticky-bar__meter {
  flex: 1 1 100%;
  min-width: 0;
}
.sticky-bar__meter .progress-head { margin-bottom: var(--space-1); }
@media (min-width: 560px) {
  .sticky-bar__meter { flex: 1 1 auto; }
}


/* ==========================================================================
   12 안내 상자 · 빈 상태 · 로딩
   ========================================================================== */

.callout {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-4);
  border: var(--border-w) solid var(--info-border);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  background: var(--info-tint);
  color: var(--info);
  font-size: var(--fs-300);
  line-height: var(--lh-snug);
}
.callout__icon { flex: 0 0 auto; font-size: var(--fs-500); line-height: 1.2; }
.callout__body { flex: 1 1 auto; min-width: 0; }
.callout__title {
  display: block;
  font-size: var(--fs-400);
  font-weight: var(--fw-bold);
  margin-bottom: var(--space-1);
  color: inherit;
}
.callout p { color: inherit; margin-bottom: var(--space-2); }
.callout p:last-child { margin-bottom: 0; }

.callout--warn   { background: var(--warn-tint);   border-color: var(--warn-border);   color: var(--warn); }
.callout--ok     { background: var(--ok-tint);     border-color: var(--ok-border);     color: var(--ok); }
.callout--danger { background: var(--danger-tint); border-color: var(--danger-border); color: var(--danger); }
.callout--quiet  { background: var(--bg-surface-2); border-color: var(--border); color: var(--text-muted); }

/* 제출 실패 전용. 완료 화면과 절대 닮지 않게 생겼다.
   원본은 전송 실패에도 완료 화면을 띄웠다. 그 사고를 모양으로 막는다. */
.submit-error {
  border: var(--border-w-strong) solid var(--danger);
  background: var(--danger-tint);
  border-radius: var(--radius-md);
  padding: var(--space-5);
}
.submit-error__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-500);
  color: var(--danger);
  margin-bottom: var(--space-3);
}
.submit-error__body { color: var(--text); font-size: var(--fs-300); }
.submit-error .btn-group { margin-top: var(--space-4); }

.empty {
  text-align: center;
  padding: var(--space-12) var(--space-4);
  border: var(--border-w) dashed var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--bg-surface-2);
}
.empty__icon { font-size: var(--fs-800); line-height: 1; opacity: 0.6; }
.empty__title {
  margin: var(--space-3) 0 var(--space-2);
  font-size: var(--fs-500);
  color: var(--text-strong);
}
.empty__desc {
  margin: 0 auto var(--space-4);
  max-width: 40ch;
  color: var(--text-muted);
  font-size: var(--fs-300);
}

/* 로딩 자리표시자 — 스피너보다 이쪽이 낫다. 화면이 안 흔들린다 */
.skeleton {
  display: block;
  border-radius: var(--radius-xs);
  background: linear-gradient(90deg,
    var(--skeleton-a) 25%, var(--skeleton-b) 37%, var(--skeleton-a) 63%);
  background-size: 400% 100%;
  animation: tkd-shimmer 1.4s ease infinite;
  color: transparent;
  user-select: none;
}
@keyframes tkd-shimmer {
  from { background-position: 100% 50%; }
  to   { background-position: 0 50%; }
}
.skeleton--line { height: 1em; margin-bottom: var(--space-2); }
.skeleton--title { height: 1.6em; width: 40%; }
.skeleton--block { height: 96px; }
@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; background: var(--skeleton-a); }
}


/* ==========================================================================
   13 점수 근거 막대
   --------------------------------------------------------------------------
   계획서 5-2: 점수는 항목별 근거가 보여야 한다.
   숫자만 크게 띄우지 마라. 무엇으로 몇 점을 얻고 잃었는지가 본문이다.
   ========================================================================== */

.score {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.score__value {
  font-size: var(--fs-900);
  font-weight: var(--fw-heavy);
  color: var(--text-strong);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.score__max { font-size: var(--fs-500); color: var(--text-muted); font-weight: var(--fw-bold); }
.score__label { font-size: var(--fs-300); color: var(--text-muted); margin: var(--space-1) 0 0; }

.score-list { margin: 0; padding: 0; list-style: none; }
.score-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: var(--border-w) solid var(--border-muted);
}
.score-row:last-child { border-bottom: 0; }
.score-row + .score-row { margin-top: 0; }
.score-row__name {
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}
.score-row__points {
  font-size: var(--fs-300);
  font-weight: var(--fw-heavy);
  font-variant-numeric: tabular-nums;
  color: var(--text-strong);
  white-space: nowrap;
}
.score-row__points[data-sign="minus"] { color: var(--danger); }
.score-row__points[data-sign="zero"]  { color: var(--text-muted); }
/* 근거 — 이 줄이 빠지면 점수는 그냥 숫자다 */
.score-row__why {
  grid-column: 1 / -1;
  font-size: var(--fs-200);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}
.score-row__bar {
  grid-column: 1 / -1;
  height: 5px;
  background: var(--track);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.score-row__bar i {
  display: block;
  height: 100%;
  background: var(--action);
  border-radius: inherit;
}
.score-row[data-state="minus"] .score-row__bar i { background: var(--danger); }
.score-row[data-state="unknown"] .score-row__bar i {
  background: repeating-linear-gradient(
    45deg, var(--border-strong) 0 4px, transparent 4px 8px);
}

/* 통계 타일 */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-3);
}
.stat {
  background: var(--bg-surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
}
.stat__label { font-size: var(--fs-200); color: var(--text-muted); }
.stat__value {
  display: block;
  font-size: var(--fs-700);
  font-weight: var(--fw-heavy);
  color: var(--text-strong);
  line-height: 1.15;
  margin: var(--space-1) 0;
  font-variant-numeric: tabular-nums;
}
.stat__sub { font-size: var(--fs-200); color: var(--text-muted); }


/* ==========================================================================
   14 민감 정보
   --------------------------------------------------------------------------
   원본 대시보드는 인증 없이 공개돼 있었고 그 안에 내부 가격·마진 메모가 있었다.
   이 화면 어디에도 "공개돼도 되는 정보"라는 인상을 주면 안 된다.

   · .rail--private  화면 맨 위에 항상 붙는 띠. "운영자 전용" 을 계속 말한다
   · .sensitive      금액·마진·메모를 감싸는 상자. 자주색 + 빗금 + 자물쇠
   · .masked         기본은 가려져 있고, 누르면 보인다 (어깨너머 노출 방지)
   ========================================================================== */

.sensitive {
  position: relative;
  border: var(--border-w-strong) solid var(--secret-border);
  border-radius: var(--radius-sm);
  background: var(--secret-tint);
  padding: var(--space-4);
  padding-top: var(--space-8);
}
.sensitive::before {
  content: "🔒 내부 전용 · 외부 공유 금지";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  padding: 3px var(--space-3);
  background: repeating-linear-gradient(
    45deg,
    var(--secret-border) 0 6px,
    transparent 6px 12px
  );
  color: var(--secret);
  font-size: var(--fs-100);
  font-weight: var(--fw-heavy);
  letter-spacing: 0.02em;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}
.sensitive__note {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-200);
  color: var(--secret);
}

/* 가려진 값. <button class="masked" aria-expanded="false"> 로 만든다 */
.masked {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-1) var(--space-3);
  border: var(--border-w) dashed var(--secret-border);
  border-radius: var(--radius-xs);
  background: var(--bg-surface);
  color: var(--text-muted);
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  text-align: left;
}
.masked[aria-expanded="false"] .masked__value {
  filter: blur(6px);
  user-select: none;
}
.masked[aria-expanded="false"]::after { content: "보기"; color: var(--secret); font-size: var(--fs-200); }
.masked[aria-expanded="true"]::after  { content: "숨기기"; color: var(--secret); font-size: var(--fs-200); }
.masked[aria-expanded="true"] { border-style: solid; color: var(--text-strong); }


/* ==========================================================================
   15 레일 · 탭
   --------------------------------------------------------------------------
   ⚠️ 레일 배경에 --live / --danger / --secret 같은 상태색을 그대로 깔지 마라.
      그 값들은 "연한 배경 위의 글자색"으로 맞춘 색이라, 다크 모드에서는 밝아진다.
      흰 글자가 안 읽힌다. 그래서 레일 전용 짝(--rail-*-bg / --rail-*-fg)이 따로 있다.
   ========================================================================== */

/* 화면 맨 위 띠. 지금 어떤 상태인지를 한 줄로 계속 말한다.
   블로그자동화의 "상태 레일"을 이어받았다 — 색이 곧 신호다. */
.rail {
  position: sticky;
  top: 0;
  z-index: var(--z-rail);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-2) var(--space-4);
  background: var(--rail-bg);
  color: var(--rail-fg);
  font-size: var(--fs-200);
  flex-wrap: wrap;
}
.rail strong { color: inherit; font-weight: var(--fw-heavy); }
.rail--private { background: var(--rail-private-bg); color: var(--rail-private-fg); }
.rail--live    { background: var(--rail-live-bg);    color: var(--rail-live-fg); }
.rail--danger  { background: var(--rail-danger-bg);  color: var(--rail-danger-fg); }
.rail .btn {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.5);
  color: inherit;
}
.rail .btn:hover:not(:disabled) { background: rgba(255, 255, 255, 0.28); }
.rail :focus-visible { outline-color: var(--focus-on-dark); }

/* 페이지 머리 */
.page-head {
  background: var(--bg-dark);
  color: var(--text-on-dark);
  padding: var(--space-6) 0;
}
.page-head h1 { color: var(--text-on-dark); font-size: var(--fs-700); }
.page-head p {
  color: var(--text-on-dark-muted);
  margin: var(--space-2) 0 0;
  font-size: var(--fs-300);
}
.page-head :focus-visible { outline-color: var(--focus-on-dark); }
/* base.css 는 strong/b 를 --text-strong(남색)으로 칠한다. 남색 머리 위에서는 사라진다 */
.page-head strong,
.page-head b { color: var(--text-on-dark); }

/* 탭 — 원본 리포트/대시보드의 탭을 이어받되 키보드 순회를 전제로 한다.
   role="tablist" / role="tab" / aria-selected 를 반드시 붙여라. */
.tabs {
  display: flex;
  gap: var(--space-1);
  overflow-x: auto;
  scrollbar-width: none;
  border-bottom: var(--border-w) solid var(--border);
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  flex: 0 0 auto;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  border-bottom: 3px solid transparent;
  color: var(--text-muted);
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  white-space: nowrap;
  background: none;
}
.tab:hover { color: var(--text-strong); }
.tab[aria-selected="true"] {
  color: var(--text-strong);
  border-bottom-color: var(--accent);
}
.tab__count {
  margin-left: var(--space-2);
  font-size: var(--fs-100);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.tabpanel[hidden] { display: none !important; }


/* ==========================================================================
   16 링크 복사칸 — 설문 링크 발급
   ========================================================================== */

.copyfield {
  display: flex;
  gap: var(--space-2);
  align-items: stretch;
  flex-wrap: wrap;
}
.copyfield .input {
  flex: 1 1 220px;
  font-family: var(--font-mono);
  font-size: var(--fs-300);
  background: var(--bg-surface-2);
}
.copyfield .btn { flex: 0 0 auto; }
/* 복사 직후 — 색만 바꾸지 말고 글자도 "복사됨" 으로 바꿔라 */
.copyfield .btn[data-state="copied"] {
  background: var(--ok);
  border-color: var(--ok);
  color: var(--text-on-accent);
}

/* 키/값 목록 (도장 상세의 설문 응답 보기) */
.kv {
  margin: 0;
  display: grid;
  gap: var(--space-1) var(--space-4);
}
@media (min-width: 640px) {
  .kv { grid-template-columns: minmax(140px, 200px) 1fr; }
}
.kv dt {
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
  padding-top: var(--space-2);
}
.kv dd {
  margin: 0;
  padding-bottom: var(--space-2);
  border-bottom: var(--border-w) solid var(--border-muted);
  font-size: var(--fs-300);
  color: var(--text);
}
@media (max-width: 639px) {
  .kv dt { padding-top: var(--space-3); }
}
/* 정정된 값 — 원본은 남기고 표시만 덮어쓴다(계획서 4-3 설계 원칙 1) */
.kv dd[data-corrected] {
  background: var(--warn-tint);
  border-radius: var(--radius-xs);
  padding-inline: var(--space-2);
}
.kv dd[data-corrected]::after {
  content: " 정정됨";
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  color: var(--warn);
}
.kv dd:empty::before,
.kv dd[data-value="unknown"]::before {
  content: "확인 필요";
  color: var(--text-muted);
  font-size: var(--fs-200);
  border: var(--border-w) dashed var(--border-strong);
  border-radius: var(--radius-xs);
  padding: 0 var(--space-2);
}



/* ##########################################################################
   ── 2차 컴포넌트 (2·3단계 운영자 화면) ──────────────────────────────────────
   쓰는 법과 상태별 표현은 docs/design.md 7~12절. 견본은 docs/mockups/
   confirm.html · content.html · report.html · enrich.html.

   이 묶음이 지키는 약속 세 가지
   1) fail(차단)과 warn(경고)은 색 말고도 **네 가지**로 갈린다 —
      아이콘(⛔/⚠) · 낱말("차단"/"경고") · 모양(채운 딱지/테두리 딱지, 굵은 막대/얇은 막대)
      · 자리(따로 묶인 목록). 흑백으로 봐도 구별된다.
   2) "확인 필요"(조회 못 함)는 실패처럼 생기지 않는다. 회색 파선 + "?" 다.
      빨강·⚠ 를 쓰지 않는다. 1차의 .badge--unknown 과 같은 말투다.
   3) 사람이 안 하면 영영 안 되는 일(원문 확인)은 호박색 굵은 띠로 화면 맨 위에 둔다.
      오류(빨강)가 아니다 — 고장이 아니라 **해야 할 일**이다.
   ########################################################################## */


/* ==========================================================================
   17 할 일 띠 · 할 일 배지 · 하위 탭
   --------------------------------------------------------------------------
   .task-banner   도장 상세 맨 위. 원문 확인 대기가 있을 때만 그린다.
                  하위 탭 **위**에 둔다 — 어느 탭을 보고 있어도 사라지지 않는다.
   .badge--task   도장 목록의 도장명 바로 아래. "✋ 원문 확인 3"
   .stat--task    요약 타일. 누르면 "원문 확인 대기" 필터가 걸린다.
   .tabs--sub     도장 상세 안의 하위 탭. .tab / .tab__count 를 그대로 쓴다.
   ========================================================================== */

.task-banner {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2) var(--space-4);
  align-items: start;
  padding: var(--space-4) var(--space-5);
  border: var(--border-w-strong) solid var(--warn-border);
  border-left: 8px solid var(--warn);
  border-radius: var(--radius-md);
  background: var(--warn-tint);
  color: var(--text);
}
.task-banner__icon {
  font-size: var(--fs-700);
  line-height: 1;
}
.task-banner__title {
  margin: 0;
  font-size: var(--fs-500);
  font-weight: var(--fw-heavy);
  color: var(--text-strong);
  line-height: var(--lh-snug);
}
.task-banner__desc {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-300);
  color: var(--text);
  line-height: var(--lh-snug);
}
/* 확인 안 하면 무엇이 빠지는지 — 추상적인 "확인하세요"보다 이 줄이 사람을 움직인다 */
.task-banner__loss {
  margin: var(--space-2) 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.task-banner__loss > li + li { margin-top: 0; }
.task-banner__actions {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  align-items: center;
  margin-top: var(--space-2);
}
@media (min-width: 600px) {
  .task-banner__actions { grid-column: 2; }
}
.task-banner__note {
  font-size: var(--fs-300);
  color: var(--text);
}

/* 목록의 "해야 할 일" 배지. .badge--warn(검토 필요 상태)과 헷갈리지 않게
   2px 실선 테두리 + 손바닥 아이콘(글자로 넣는다)으로 갈라 둔다. */
.badge--task {
  background: var(--warn-tint);
  border: var(--border-w-strong) solid var(--warn);
  color: var(--warn);
  font-weight: var(--fw-heavy);
}

/* 폐기된 것. 회색 + 가운뎃줄. 지워진 게 아니라 "쓰지 않기로 한 것" 이다 */
.badge--void {
  text-decoration: line-through;
  text-decoration-thickness: 2px;
}

/* 요약 타일을 링크로 쓸 때 */
a.stat {
  display: block;
  color: inherit;
  text-decoration: none;
}
a.stat:hover {
  border-color: var(--border-strong);
  text-decoration: none;
}
.stat--task {
  border: var(--border-w-strong) solid var(--warn-border);
  border-left: 6px solid var(--warn);
  background: var(--warn-tint);
}
.stat--task .stat__label,
.stat--task .stat__sub { color: var(--text); }
/* 동의 필요 — 원문 확인(호박)보다 강하게. 내보내기가 전부 막힌 상태다(재검사 9번). */
.stat--danger {
  border-color: var(--danger-border);
  border-left-color: var(--danger);
  background: var(--danger-tint);
}

/* 하위 탭 — 도장 상세 카드 안에서 쓴다 */
.tabs--sub {
  padding-inline: var(--space-3);
  background: var(--bg-surface-2);
  /* 가로 스크롤(스크롤바 숨김)이면 폰에서 뒤쪽 탭(리포트·검색 노출)이 안 보인다.
     하위 탭은 다섯 개뿐이라 줄을 바꿔 전부 보이게 한다. */
  flex-wrap: wrap;
  overflow: visible;
}
.tabs--sub .tab { font-size: var(--fs-300); }
/* 사람이 해야 할 일이 남은 탭의 숫자. 회색 숫자(.tab__count)와 모양부터 다르다 */
.tab__count--alert {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.7em;
  padding: 0 var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--rail-live-bg);
  color: var(--rail-live-fg);
  font-weight: var(--fw-heavy);
  line-height: 1.6;
}


/* ==========================================================================
   18 원문 확인 카드 · 다른 답과 맞춰 보기
   --------------------------------------------------------------------------
   <article class="quote-check" data-state="pending">
     .quote-check__head      항목 이름 · 왜 확인이 필요한가 · 상태 배지
     .quote-check__body
       .quote-check__original   관장님이 적은 글 **그대로**. 줄바꿈도 그대로
         mark.flag              설문으로 확인할 수 없는 낱말(서버가 flags[] 를 줄 때)
       .crosscheck              설문의 다른 답을 옆에 놓는다
     .quote-check__actions   원문 그대로 쓰기 · 고쳐서 쓰기 · 쓰지 않기

   data-state : pending(확인대기) · changed(값변경됨) · confirmed(확인됨)
                · operator(운영자입력) · empty(값없음) · rejected(쓰지 않음 — 서버 추가 필요)

   ⚠️ 이 카드는 **자기 넓이에 따라** 원문과 맞춰 보기를 옆으로 놓는다(컨테이너 쿼리).
      도장 상세의 오른쪽 칸(약 650px)에서는 위아래로, 넓은 자리에서는 좌우로.
   ========================================================================== */

.quote-check {
  container-type: inline-size;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  overflow: hidden;
}
.quote-check + .quote-check { margin-top: var(--space-4); }

.quote-check[data-state="pending"],
.quote-check[data-state="changed"] {
  border: var(--border-w-strong) solid var(--warn-border);
  border-left: 6px solid var(--warn);
}
.quote-check[data-state="confirmed"] { border-left: 6px solid var(--ok); }
.quote-check[data-state="operator"]  { border-left: 6px solid var(--action-border); }
.quote-check[data-state="rejected"]  { border-left: 6px solid var(--border-strong); }
.quote-check[data-state="empty"] {
  border-style: dashed;
  border-color: var(--border-strong);
  background: var(--bg-surface-2);
}

.quote-check__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-bottom: var(--border-w) solid var(--border-muted);
  background: var(--bg-surface-2);
}
.quote-check[data-state="pending"] .quote-check__head,
.quote-check[data-state="changed"] .quote-check__head { background: var(--warn-tint); }
.quote-check__label {
  margin: 0;
  font-size: var(--fs-500);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}
.quote-check__why {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-300);
  color: var(--text);
  line-height: var(--lh-snug);
  max-width: 60ch;
}

.quote-check__body {
  display: grid;
  gap: var(--space-4);
  padding: var(--space-5);
}
@container (min-width: 760px) {
  .quote-check__body { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }
}

.quote-check__original-label {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  margin: 0 0 var(--space-2);
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}
.quote-check__original-label small {
  font-size: var(--fs-200);
  font-weight: var(--fw-medium);
}
/* 원문 — 본문 크기 그대로(17px). 작게 줄이면 안 읽고 넘긴다.
   파선 테두리 = "우리가 손대지 않은 남의 글"이라는 표시. */
.quote-check__original {
  margin: 0;
  padding: var(--space-4) var(--space-5);
  border: var(--border-w) dashed var(--border-strong);
  border-left: 4px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-inset);
  color: var(--text-strong);
  font-size: var(--fs-400);
  line-height: var(--lh-relaxed);
  white-space: pre-wrap;        /* 관장님이 친 줄바꿈을 그대로 */
}
/* 확인 뒤 관장이 고쳐서 다시 물어야 하는 값 */
.quote-check__changed {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  color: var(--warn);
}
.quote-check__meta {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-200);
  color: var(--text-muted);
}

/* 설문으로 확인할 수 없는 낱말. 굵게 + 연빨강 + 아래 막대 — 색이 없어도 굵기와 막대가 남는다 */
mark.flag {
  padding: 0 2px;
  border-radius: 3px;
  background: var(--danger-tint);
  color: var(--text-strong);
  font-weight: var(--fw-bold);
  box-shadow: inset 0 -3px 0 var(--danger);
}

.quote-check__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  padding: var(--space-4) var(--space-5);
  border-top: var(--border-w) solid var(--border-muted);
  background: var(--bg-surface-2);
}
.quote-check__hint {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--fs-300);
  color: var(--text);
}

/* 다른 답과 맞춰 보기 */
.crosscheck {
  min-width: 0;
}
.crosscheck__title {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}
.crosscheck__list {
  margin-inline: 0;
  margin-bottom: 0;   /* 위쪽은 비워 둔다 — .stack 안에서 간격을 받게 */
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-2);
}
.crosscheck__list > li + li { margin-top: 0; }
.crosscheck__row {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-3);
  border: var(--border-w) solid var(--border);
  border-left: 4px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  font-size: var(--fs-300);
  line-height: var(--lh-snug);
}
.crosscheck__verdict {
  font-size: var(--fs-300);
  font-weight: var(--fw-heavy);
}
.crosscheck__claim,
.crosscheck__answer { margin: 0; color: var(--text); }
.crosscheck__answer { color: var(--text-muted); }
.crosscheck__answer b { color: var(--text-strong); }

/* 어긋남 — 이 카드에서 가장 먼저 눈에 걸려야 하는 줄 */
.crosscheck__row[data-state="conflict"] {
  border: var(--border-w-strong) solid var(--danger);
  border-left-width: 6px;
  background: var(--danger-tint);
}
.crosscheck__row[data-state="conflict"] .crosscheck__verdict { color: var(--danger); }
.crosscheck__row[data-state="conflict"] .crosscheck__answer { color: var(--text); }
/* 설문에 문항이 없어 대조할 수 없음 — 실패가 아니다. 회색 파선 */
.crosscheck__row[data-state="unverifiable"] {
  border-style: dashed;
  border-color: var(--border-strong);
  border-left-style: solid;
}
.crosscheck__row[data-state="unverifiable"] .crosscheck__verdict { color: var(--text-muted); }
.crosscheck__row[data-state="match"] { border-left-color: var(--ok); }
.crosscheck__row[data-state="match"] .crosscheck__verdict { color: var(--ok); }
/* 판정 없이 "참고할 답"만 놓을 때 (서버가 flags[] 를 주기 전의 1차 구현) */
.crosscheck__row[data-state="reference"] { border-left-color: var(--action-border); }
.crosscheck__row[data-state="reference"] .crosscheck__verdict { color: var(--text-muted); }


/* ==========================================================================
   19 품질 게이트 — 판정 · 차단(fail) · 경고(warn)
   --------------------------------------------------------------------------
   .gate__verdict[data-result="fail|warn|pass|stale"]  맨 위 한 줄 결론
   .gate__group                                        차단 묶음 / 경고 묶음 / 통과 묶음
   .issue[data-level="fail|warn"]                      걸린 것 하나
     .pin            본문 속 표시와 같은 번호
     .issue__level   "차단"(채운 딱지) / "경고"(테두리 딱지)
     .issue__found   **본문 어디에서** 걸렸는지 — 서버의 found 문장
     .issue__ack     경고에만. "읽었습니다" 체크

   ⚠️ qualityScore(10점 만점)를 이 자리에 크게 쓰지 마라.
      점수는 fail 급 검사만 센다. 경고가 다섯 개여도 10/10 이 나온다.
      감사팀의 허위 이력 캡션이 "10/10 승인"으로 나간 게 정확히 그 모양이었다.
   ========================================================================== */

.gate {
  display: grid;
  gap: var(--space-5);
}

.gate__verdict {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-1) var(--space-3);
  align-items: start;
  padding: var(--space-4);
  border: var(--border-w-strong) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
}
.gate__verdict-icon {
  grid-row: span 2;
  font-size: var(--fs-700);
  line-height: 1.1;
}
.gate__verdict-title {
  margin: 0;
  font-size: var(--fs-500);
  font-weight: var(--fw-heavy);
  line-height: var(--lh-snug);
  color: inherit;
}
.gate__verdict-sub {
  margin: 0;
  font-size: var(--fs-300);
  color: var(--text);
  line-height: var(--lh-snug);
}
.gate__verdict[data-result="fail"] {
  border-color: var(--danger);
  border-left: 8px solid var(--danger);
  background: var(--danger-tint);
  color: var(--danger);
}
.gate__verdict[data-result="warn"] {
  border-color: var(--warn);
  border-left: 8px solid var(--warn);
  background: var(--warn-tint);
  color: var(--warn);
}
.gate__verdict[data-result="pass"] {
  border-color: var(--ok-border);
  background: var(--ok-tint);
  color: var(--ok);
}
/* 본문을 고쳤는데 아직 재검사 전 — 판정이 낡았다 */
.gate__verdict[data-result="stale"] {
  border-style: dashed;
  border-color: var(--border-strong);
  background: var(--bg-surface-2);
  color: var(--text-strong);
}

.gate__group-title {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  margin: 0 0 var(--space-1);
  font-size: var(--fs-400);
  font-weight: var(--fw-heavy);
  color: var(--text-strong);
}
/* 이 묶음이 승인에 무슨 일을 하는지. 제목 바로 아래 한 줄 — 빼지 마라 */
.gate__group-rule {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-300);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}
.gate__list {
  margin-inline: 0;
  margin-bottom: 0;   /* 위쪽은 비워 둔다 — .stack 안에서 간격을 받게 */
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-3);
}
.gate__list > li + li { margin-top: 0; }

.issue {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2) var(--space-3);
  padding: var(--space-4);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  color: var(--text);
}
.issue[data-level="fail"] {
  border: var(--border-w-strong) solid var(--danger);
  border-left-width: 8px;
  background: var(--danger-tint);
}
.issue[data-level="warn"] {
  border: var(--border-w) solid var(--warn-border);
  border-left: 4px solid var(--warn);
  background: var(--warn-tint);
}
.issue__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  min-width: 0;
}
.issue__level {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 1px var(--space-2);
  border-radius: var(--radius-xs);
  font-size: var(--fs-200);
  font-weight: var(--fw-heavy);
  white-space: nowrap;
}
/* 차단 = 채운 딱지 / 경고 = 테두리 딱지. 흑백에서도 모양이 다르다 */
.issue[data-level="fail"] .issue__level {
  background: var(--rail-danger-bg);
  color: var(--rail-danger-fg);
  border: var(--border-w-strong) solid var(--rail-danger-bg);
}
.issue[data-level="warn"] .issue__level {
  background: var(--bg-surface);
  color: var(--warn);
  border: var(--border-w-strong) solid var(--warn);
}
.issue__title {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--fs-400);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
  line-height: var(--lh-snug);
}
.issue__code {
  font-family: var(--font-mono);
  font-size: var(--fs-200);
  color: var(--text-muted);
}
.issue__msg {
  grid-column: 1 / -1;
  margin: 0;
  font-size: var(--fs-300);
  line-height: var(--lh-base);
}
.issue__found {
  grid-column: 1 / -1;
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border: var(--border-w) solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius-xs);
  background: var(--bg-surface);
  color: var(--text);
  font-size: var(--fs-300);
  line-height: var(--lh-base);
}
.issue__found-label {
  display: block;
  margin-bottom: var(--space-1);
  font-size: var(--fs-200);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
}
.issue__fix {
  grid-column: 1 / -1;
  margin: 0;
  font-size: var(--fs-300);
  color: var(--text);
}
.issue__fix b { color: var(--text-strong); }
.issue__actions {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}
/* 경고를 "읽었다"고 남기는 칸. 체크하면 초록으로 바뀐다 — 글자도 같이 바꿔라 */
.issue__ack {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-1) var(--space-3);
  border: var(--border-w-strong) solid var(--warn);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  color: var(--text-strong);
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  cursor: pointer;
}
.issue__ack input {
  width: 20px;
  height: 20px;
  margin: 0;
  flex: 0 0 auto;
  accent-color: var(--ok);
  cursor: pointer;
}
.issue__ack:has(input:focus-visible) {
  outline: var(--focus-w) solid var(--focus);
  outline-offset: 2px;
}
.issue__ack input:focus-visible { outline: none; }
.issue__ack:has(input:checked),
.issue__ack.is-checked {
  border-color: var(--ok);
  background: var(--ok-tint);
  color: var(--ok);
}
.issue[data-level="warn"]:has(.issue__ack input:checked) {
  background: var(--bg-surface);
  border-left-color: var(--ok);
}

/* 번호 핀 — 본문 속 표시와 목록의 한 줄을 잇는다.
   차단 = 네모 채움 / 경고 = 동그라미 테두리 */
.pin {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.75em;
  height: 1.75em;
  padding: 0 0.3em;
  font-size: var(--fs-200);
  font-weight: var(--fw-heavy);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  text-decoration: none;
  vertical-align: 0.1em;
  white-space: nowrap;
}
.pin[data-level="fail"] {
  border-radius: 4px;
  background: var(--rail-danger-bg);
  color: var(--rail-danger-fg);
  border: var(--border-w-strong) solid var(--rail-danger-bg);
}
.pin[data-level="warn"] {
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  color: var(--warn);
  border: var(--border-w-strong) solid var(--warn);
}
a.pin:hover { text-decoration: none; }
a.pin:visited { color: inherit; }
a.pin[data-level="fail"]:visited { color: var(--rail-danger-fg); }
a.pin[data-level="warn"]:visited { color: var(--warn); }


/* ==========================================================================
   20 본문 미리보기 · 본문 속 표시(hit)
   --------------------------------------------------------------------------
   ARTICLE_SCHEMA({title, summary, tags[], blocks[]})를 **글자로만** 그린다.
   innerHTML 금지 — 블록마다 el() 로 만든다(public/js/dom.js).

   mark.hit[data-level]  게이트가 걸린 문장. id 를 붙여 목록에서 건너온다.
     fail = 연빨강 + 굵은 실선 밑줄 / warn = 연호박 + 물결 밑줄
   ========================================================================== */

.article {
  max-width: 68ch;
  color: var(--text);
  font-size: var(--fs-400);
  line-height: var(--lh-relaxed);
}
.article__title {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-700);
  line-height: var(--lh-tight);
  color: var(--text-strong);
}
.article__summary {
  margin: 0 0 var(--space-6);
  padding: var(--space-3) var(--space-4);
  border-left: 4px solid var(--action-border);
  background: var(--bg-surface-2);
  color: var(--text);
  font-size: var(--fs-400);
}
.article__h {
  margin: var(--space-8) 0 var(--space-3);
  font-size: var(--fs-500);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}
.article__p { margin: 0 0 var(--space-4); }
/* 사진 자리 — 사진이 아니라 "여기 사진이 들어간다"는 안내다 */
.article__slot {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: var(--border-w) dashed var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-surface-2);
  color: var(--text-muted);
  font-size: var(--fs-300);
  line-height: var(--lh-snug);
}
.article__slot b { color: var(--text-strong); }
.article__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-6) 0 0;
  padding: 0;
  list-style: none;
}
.article__tags > li + li { margin-top: 0; }
.tag {
  display: inline-block;
  padding: 2px var(--space-3);
  border: var(--border-w) solid var(--action-border);
  border-radius: var(--radius-pill);
  background: var(--action-tint);
  color: var(--text);
  font-size: var(--fs-200);
}

mark.hit {
  padding: 0 2px;
  border-radius: 3px;
  background: transparent;
  color: inherit;
  scroll-margin-top: calc(var(--space-16) + var(--space-8));
}
mark.hit[data-level="fail"] {
  background: var(--danger-tint);
  box-shadow: inset 0 -3px 0 var(--danger);
}
mark.hit[data-level="warn"] {
  background: var(--warn-tint);
  text-decoration: underline wavy var(--warn);
  text-decoration-thickness: 2px;
  text-underline-offset: 5px;
}
/* 목록에서 "본문에서 보기"로 건너왔을 때 — 포커스 테두리로 한 번 더 */
mark.hit:target,
mark.hit.is-current {
  outline: var(--focus-w) solid var(--focus);
  outline-offset: 2px;
}


/* ==========================================================================
   21 검사 목록 — 리포트 검수(verify.checks) · 게이트 checks
   --------------------------------------------------------------------------
   <li class="check" data-result="pass|fail|warn">
     .check__mark    ✓ / ✕ / !     (::before 로 그린다)
     .check__name    검사 이름
     .check__result  "통과" / "차단" / "경고" — 글자로 한 번 더
     .check__detail  서버가 준 detail 그대로
   ⚠️ 색을 **검사 이름으로 정하지 마라.** 같은 분량 검사가 원고 출처에 따라
      fail 도 되고 warn 도 된다(api.md 9-4). 늘 level 과 pass 로 정한다:
        pass:true → pass / pass:false + level:fail → fail / pass:false + level:warn → warn
   ========================================================================== */

.checklist {
  margin-inline: 0;
  margin-bottom: 0;   /* 위쪽은 비워 둔다 — .stack 안에서 간격을 받게 */
  padding: 0;
  list-style: none;
}
.checklist > li + li { margin-top: 0; }
.check {
  display: grid;
  grid-template-columns: 1.6em minmax(0, 1fr) auto;
  gap: var(--space-1) var(--space-3);
  align-items: baseline;
  padding: var(--space-3) 0;
  border-bottom: var(--border-w) solid var(--border-muted);
  font-size: var(--fs-300);
}
.check:last-child { border-bottom: 0; }
.check__mark {
  font-weight: var(--fw-heavy);
  text-align: center;
}
.check__mark::before { content: "✓"; }
.check__name {
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}
.check__result {
  font-size: var(--fs-200);
  font-weight: var(--fw-heavy);
  white-space: nowrap;
}
.check__detail {
  grid-column: 2 / -1;
  color: var(--text-muted);
  font-size: var(--fs-300);
  line-height: var(--lh-snug);
}
.check[data-result="pass"] .check__mark,
.check[data-result="pass"] .check__result { color: var(--ok); }
.check[data-result="fail"] {
  margin-inline: calc(var(--space-2) * -1);
  padding-inline: var(--space-2);
  background: var(--danger-tint);
  border-radius: var(--radius-xs);
}
.check[data-result="fail"] .check__mark::before { content: "✕"; }
.check[data-result="fail"] .check__mark,
.check[data-result="fail"] .check__result { color: var(--danger); }
.check[data-result="fail"] .check__detail { color: var(--text); }
.check[data-result="warn"] .check__mark::before { content: "!"; }
.check[data-result="warn"] .check__mark,
.check[data-result="warn"] .check__result { color: var(--warn); }
.check[data-result="warn"] .check__detail { color: var(--text); }

/* 접는 상자 — 통과 항목처럼 "있다는 것만 알면 되는" 것을 접어 둔다 */
.fold {
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
}
.fold > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
  list-style: none;
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::before {
  content: "▸";
  color: var(--text-muted);
  transition: transform var(--dur-fast) var(--ease);
}
.fold[open] > summary::before { transform: rotate(90deg); }
.fold > summary:focus-visible {
  outline: var(--focus-w) solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.fold__body {
  padding: 0 var(--space-4) var(--space-3);
}


/* ==========================================================================
   22 선택 카드 · 비용 표시 · 원고 고르기 · 분량 막대
   --------------------------------------------------------------------------
   .option-card   라디오 하나를 카드 하나로. 만드는 방법(템플릿/AI/캡션), AI 모드
   .cost--free / .cost--paid   **유료는 누르기 전에** 보여야 한다
   .tpl           범용 원고 한 편. data-usable="false" 면 흐리고 이유를 적는다
   .charmeter     <progress class="pbar"> 를 감싸 75% 자리에 기준선을 긋는다
                  → max 를 (기준 ÷ 0.75) 로 주면 기준선이 정확히 기준 글자 수 자리에 선다
   ========================================================================== */

.option-cards {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  margin-inline: 0;
  margin-bottom: 0;   /* 위쪽은 비워 둔다 — .stack 안에서 간격을 받게 */
  padding: 0;
  border: 0;
}
.option-card {
  position: relative;
  display: grid;
  align-content: start;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-4);
  padding-right: calc(var(--space-4) + 28px);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  color: var(--text);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.option-card:hover { border-color: var(--text-muted); background: var(--bg-hover); }
.option-card input[type="radio"],
.option-card input[type="checkbox"] {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  width: 20px;
  height: 20px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.option-card:has(input:focus-visible) {
  outline: var(--focus-w) solid var(--focus);
  outline-offset: 2px;
}
.option-card input:focus-visible { outline: none; }
.option-card:has(input:checked),
.option-card.is-checked {
  border: var(--border-w-strong) solid var(--accent);
  padding: calc(var(--space-4) - 1px);
  padding-right: calc(var(--space-4) + 27px);
  background: var(--accent-tint);
}
/* 잠긴 선택지 — 숨기지 않는다. 왜 잠겼는지(.option-card__why)를 늘 같이 쓴다 */
.option-card:has(input:disabled),
.option-card.is-disabled {
  cursor: not-allowed;
  background: var(--bg-inset);
  border-style: dashed;
}
.option-card:has(input:disabled) .option-card__name { color: var(--text-muted); }
.option-card__name {
  font-size: var(--fs-400);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
  line-height: var(--lh-snug);
}
.option-card__desc {
  font-size: var(--fs-300);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}
.option-card__why {
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  color: var(--text);
}

.cost {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  justify-self: start;
  padding: 2px var(--space-2);
  border: var(--border-w) solid var(--pending-border);
  border-radius: var(--radius-pill);
  background: var(--pending-tint);
  color: var(--pending);
  font-size: var(--fs-200);
  font-weight: var(--fw-bold);
  white-space: nowrap;
}
.cost--free { background: var(--ok-tint);   border-color: var(--ok-border); color: var(--ok); }
.cost--paid { background: var(--warn-tint); border: var(--border-w-strong) solid var(--warn); color: var(--warn); }

.tpl-list {
  margin-inline: 0;
  margin-bottom: 0;   /* 위쪽은 비워 둔다 — .stack 안에서 간격을 받게 */
  padding: 0;
  list-style: none;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  overflow: hidden;
}
.tpl-list > li + li { margin-top: 0; }
.tpl {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-1) var(--space-3);
  align-items: start;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  border-bottom: var(--border-w) solid var(--border-muted);
  cursor: pointer;
}
.tpl-list > li:last-child .tpl { border-bottom: 0; }
.tpl:hover { background: var(--bg-hover); }
.tpl input {
  width: 20px;
  height: 20px;
  margin: 3px 0 0;
  accent-color: var(--accent);
}
.tpl:has(input:checked) {
  background: var(--accent-tint);
  box-shadow: inset 4px 0 0 var(--accent);
}
.tpl:has(input:focus-visible) {
  outline: var(--focus-w) solid var(--focus);
  outline-offset: -3px;
}
.tpl input:focus-visible { outline: none; }
.tpl__main { min-width: 0; }
.tpl__title {
  display: block;
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
  line-height: var(--lh-snug);
}
.tpl__no {
  font-family: var(--font-mono);
  color: var(--text-muted);
  margin-right: var(--space-1);
}
.tpl__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  margin-top: var(--space-1);
  font-size: var(--fs-200);
  color: var(--text-muted);
}
.tpl__why {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--fs-300);
  color: var(--text);
}
.tpl[data-usable="false"] {
  background: var(--bg-inset);
  cursor: not-allowed;
}
.tpl[data-usable="false"] .tpl__title { color: var(--text-muted); font-weight: var(--fw-medium); }

.charmeter {
  position: relative;
  display: block;
  margin-top: var(--space-2);
}
.charmeter::after {
  content: "";
  position: absolute;
  top: -3px;
  bottom: -3px;
  left: 75%;
  width: 2px;
  background: var(--text-strong);
  pointer-events: none;
}


/* ==========================================================================
   23 오래 걸리는 작업(job) · 기본 진행 막대(.pbar)
   --------------------------------------------------------------------------
   AI 생성은 2~4분이다. 회전자 하나로 버티게 하지 마라. 세 가지를 같이 보인다:
     ① 무엇을 하는 중인지  ② 얼마나 지났는지(초 단위로 늘어나는 숫자)
     ③ 보통 얼마나 걸리는지 — "보통 2~4분"
   퍼센트를 지어내지 마라. 서버가 진행률을 주지 않는다. value 없는 <progress> 를 쓴다.
   ========================================================================== */

.job {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border: var(--border-w-strong) solid var(--live-border);
  border-left: 6px solid var(--live);
  border-radius: var(--radius-md);
  background: var(--live-tint);
  color: var(--text);
}
.job__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-1) var(--space-4);
}
.job__title {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-400);
  font-weight: var(--fw-heavy);
  color: var(--text-strong);
}
.job__title::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--live);
  animation: tkd-pulse 1.6s var(--ease) infinite;
}
.job__elapsed {
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--text-strong);
}
.job__note {
  margin: 0;
  font-size: var(--fs-300);
  line-height: var(--lh-snug);
}

/* 기본 진행 막대 — native <progress>. value 가 없으면 "끝을 모름"이 저절로 된다.
   style="width:…" 가 필요 없어서 CSP 를 건드리지 않는다. */
.pbar {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  height: 10px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--track);
  color: var(--action);
  overflow: hidden;
}
.pbar::-webkit-progress-bar { background: var(--track); border-radius: var(--radius-pill); }
.pbar::-webkit-progress-value { background: var(--action); border-radius: var(--radius-pill); }
.pbar::-moz-progress-bar { background: var(--action); border-radius: var(--radius-pill); }
.pbar--ok::-webkit-progress-value { background: var(--ok); }
.pbar--ok::-moz-progress-bar { background: var(--ok); }
.pbar--short::-webkit-progress-value { background: var(--warn); }
.pbar--short::-moz-progress-bar { background: var(--warn); }
/* 끝을 모르는 진행 — 빗금이 흘러간다. 움직임을 줄이면 빗금만 남는다(base.css) */
.pbar:indeterminate,
.pbar:indeterminate::-webkit-progress-bar {
  background: repeating-linear-gradient(45deg, var(--live) 0 10px, var(--track) 10px 20px);
  background-size: 28px 28px;
  animation: tkd-stripes 900ms linear infinite;
}
.pbar:indeterminate::-moz-progress-bar { background: transparent; }
@keyframes tkd-stripes {
  to { background-position: 28px 0; }
}


/* ==========================================================================
   24 검색 노출 조회 결과(probe)
   --------------------------------------------------------------------------
   <article class="probe" data-tone="good|weak|bad|unknown|none">
   판정(verdict) → tone 대응은 docs/design.md 10-2절 표.

   ⚠️ unknown(확인필요)은 **실패가 아니다.** 조회를 못 한 것뿐이다.
      회색 파선 + "?" + 이유 한 줄. 빨강·⚠ 금지.
   ⚠️ none(아직 조회 안 함, verdict:null)과 unknown 을 같은 모양으로 그리지 마라.
      none 은 실선 회색 면 + "—".
   ========================================================================== */

.probe-grid {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
}
.probe {
  display: grid;
  align-content: start;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-4);
  border: var(--border-w) solid var(--border);
  border-top: 5px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
}
.probe__kind {
  margin: 0;
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
}
.probe__verdict {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-600);
  font-weight: var(--fw-heavy);
  line-height: var(--lh-tight);
  color: var(--text-strong);
}
.probe__verdict::before {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.4em;
  height: 1.4em;
  border-radius: 50%;
  font-size: 0.75em;
}
.probe__why {
  margin: 0;
  font-size: var(--fs-300);
  color: var(--text);
  line-height: var(--lh-snug);
}
.probe__facts {
  margin: 0;
  font-size: var(--fs-300);
  color: var(--text);
}
.probe__facts dt {
  font-size: var(--fs-200);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
}
.probe__facts dd {
  margin: 0 0 var(--space-2);
  overflow-wrap: anywhere;
}
.probe__foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin-top: var(--space-1);
  padding-top: var(--space-2);
  border-top: var(--border-w) solid var(--border-muted);
  font-size: var(--fs-200);
  color: var(--text-muted);
}

.probe[data-tone="good"] { border-top-color: var(--ok); }
.probe[data-tone="good"] .probe__verdict { color: var(--ok); }
.probe[data-tone="good"] .probe__verdict::before {
  content: "✓"; content: "✓" / "";
  background: var(--ok-tint); border: 2px solid var(--ok);
}

.probe[data-tone="weak"] { border-top-color: var(--warn); }
.probe[data-tone="weak"] .probe__verdict { color: var(--warn); }
.probe[data-tone="weak"] .probe__verdict::before {
  content: "◐"; content: "◐" / "";
  background: var(--warn-tint); border: 2px solid var(--warn);
}

/* 안보임·확인안됨·미확인 — 조회는 됐고 결과가 나쁜 것. 관장님께 해 드릴 말이 있는 칸이다 */
.probe[data-tone="bad"] { border-top-color: var(--danger); }
.probe[data-tone="bad"] .probe__verdict { color: var(--danger); }
.probe[data-tone="bad"] .probe__verdict::before {
  content: "✕"; content: "✕" / "";
  background: var(--danger-tint); border: 2px solid var(--danger);
}

/* 확인필요 — 조회를 못 했다. 나쁜 결과가 아니라 "모름"이다 */
.probe[data-tone="unknown"] {
  border: var(--border-w) dashed var(--border-strong);
  border-top: 5px dashed var(--border-strong);
}
.probe[data-tone="unknown"] .probe__verdict { color: var(--text-muted); }
.probe[data-tone="unknown"] .probe__verdict::before {
  content: "?"; content: "?" / "";
  border: 2px dashed var(--border-strong);
}

/* 아직 조회 안 함 (verdict: null) */
.probe[data-tone="none"] {
  background: var(--bg-surface-2);
  border-top-color: var(--border);
}
.probe[data-tone="none"] .probe__verdict { color: var(--text-muted); font-size: var(--fs-500); }
.probe[data-tone="none"] .probe__verdict::before { content: "—"; content: "—" / ""; }

/* 제3자(네이버)가 준 글자라는 꼬리표 — 우리가 쓴 문장과 섞이지 않게 */
.third-party {
  display: inline-block;
  padding: 0 var(--space-1);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius-xs);
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
  vertical-align: 1px;
  white-space: nowrap;
}


/* ==========================================================================
   25 원고 격자 · 두 숫자 나란히 · 도장(stamp)
   ========================================================================== */

/* 리포트 원고 22종. 있는 것(실선) / 없는 것(파선) / 출처(기존·새로) */
.doc-grid {
  margin-inline: 0;
  margin-bottom: 0;   /* 위쪽은 비워 둔다 — .stack 안에서 간격을 받게 */
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-2);
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
}
.doc-grid > li + li { margin-top: 0; }
.doc {
  display: grid;
  align-content: start;
  gap: 2px;
  height: 100%;
  padding: var(--space-2) var(--space-3);
  border: var(--border-w) solid var(--border);
  border-left: 4px solid var(--ok);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  font-size: var(--fs-200);
  line-height: var(--lh-snug);
}
.doc__no {
  font-family: var(--font-mono);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}
.doc__title { color: var(--text); }
.doc__meta { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.doc[data-state="missing"] {
  border: var(--border-w) dashed var(--border-strong);
  border-left: 4px dashed var(--border-strong);
  background: var(--bg-surface-2);
}
.doc[data-state="missing"] .doc__title { color: var(--text-muted); }
/* 새로 쓴 원고(generated)는 분량 미달이 배포를 막는다 — 막대 색과 글자로 표시해 둔다 */
.doc[data-origin="generated"] { border-left-color: var(--action); }

/* 두 숫자 나란히 — "고칠 곳 3군데 / 검수 4건" 처럼 **같다고 오해하면 안 되는** 숫자 */
.count-pair {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}
.count-pair__item {
  padding: var(--space-3) var(--space-4);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
}
.count-pair__num {
  display: block;
  font-size: var(--fs-800);
  font-weight: var(--fw-heavy);
  line-height: 1.1;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
}
.count-pair__label {
  display: block;
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}
.count-pair__hint {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--fs-300);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}

/* 도장(stamp) — "드라이런" · "견본" 처럼 결과물의 성격을 못 박는 딱지 */
.stamp {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-3);
  border: var(--border-w-strong) dashed var(--border-strong);
  border-radius: var(--radius-xs);
  background: var(--bg-surface);
  color: var(--text-strong);
  font-size: var(--fs-200);
  font-weight: var(--fw-heavy);
  letter-spacing: 0.04em;
  white-space: nowrap;
}


/* ==========================================================================
   26 원고 빈칸 — 줄 보기(codeview) · 바꾸기 전후(diff)
   --------------------------------------------------------------------------
   <article class="blank"> 한 자리 = 한 카드. **서버 목록이 준 자리만** 그린다.
   화면에서 대괄호를 직접 세지 마라(api.md 11-3).
   ========================================================================== */

.blank {
  border: var(--border-w) solid var(--border);
  border-left: 6px solid var(--danger);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  overflow: hidden;
}
.blank + .blank { margin-top: var(--space-4); }
.blank[data-state="done"] { border-left-color: var(--ok); }
.blank__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: var(--border-w) solid var(--border-muted);
  background: var(--bg-surface-2);
}
.blank__where {
  margin: 0;
  font-size: var(--fs-400);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}
.blank__where small { font-size: var(--fs-300); font-weight: var(--fw-medium); }
.blank__body {
  display: grid;
  gap: var(--space-4);
  padding: var(--space-4);
}
.blank__reason {
  margin: 0;
  font-size: var(--fs-300);
  color: var(--text);
  line-height: var(--lh-base);
}
.blank__shown {
  margin: 0;
  font-size: var(--fs-300);
  color: var(--text-muted);
}
.blank__shown code {
  color: var(--text-strong);
  font-family: var(--font-sans);   /* 고정폭 글꼴은 한글 자간이 벌어진다(26절 .codeview 참고) */
}
.blank__fill {
  padding: var(--space-4);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-surface-2);
}

/* 원고 줄 보기. 줄번호 + 글. 가로 스크롤 대신 줄을 접는다(폰에서도 읽히게) */
.codeview {
  margin-inline: 0;
  margin-bottom: 0;   /* 위쪽은 비워 둔다 — .stack 안에서 간격을 받게 */
  padding: var(--space-2) 0;
  list-style: none;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-inset);
  /* 고정폭 글꼴을 쓰지 않는다. 윈도우의 고정폭 글꼴에는 한글이 없어서 대체 글꼴로 넘어가며
     글자 사이가 벌어진다(실측). 원고는 한글이 대부분이라 본문 글꼴이 훨씬 잘 읽힌다. */
  font-size: var(--fs-300);
  line-height: var(--lh-base);
}
.codeview > li + li { margin-top: 0; }
.codeview__line {
  display: grid;
  grid-template-columns: 3.5em minmax(0, 1fr);
}
.codeview__no {
  padding-right: var(--space-3);
  text-align: right;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  user-select: none;
}
.codeview__text {
  padding-right: var(--space-3);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--text);
}
.codeview__line[data-target] {
  background: var(--bg-surface);
  box-shadow: inset 4px 0 0 var(--danger);
}
.codeview__line[data-target] .codeview__no {
  color: var(--danger);
  font-weight: var(--fw-heavy);
}

/* 바꾸기 전 / 바꾼 뒤. 색만으로 가르지 않는다 — 앞에 글자 딱지가 붙는다 */
.diff {
  margin-inline: 0;
  margin-bottom: 0;   /* 위쪽은 비워 둔다 — .stack 안에서 간격을 받게 */
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-1);
  font-size: var(--fs-300);
}
.diff > li + li { margin-top: 0; }
.diff__row {
  display: grid;
  grid-template-columns: 5.5em minmax(0, 1fr);
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-xs);
  color: var(--text);
}
.diff__row[data-side="before"] { background: var(--danger-tint); }
.diff__row[data-side="after"]  { background: var(--ok-tint); }
.diff__label {
  font-size: var(--fs-200);
  font-weight: var(--fw-heavy);
  white-space: nowrap;
}
.diff__row[data-side="before"] .diff__label { color: var(--danger); }
.diff__row[data-side="after"]  .diff__label { color: var(--ok); }
.diff__text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}


/* ==========================================================================
   27 발행 자리 — 4단계 자리만 잡는다
   --------------------------------------------------------------------------
   [운영자 결정] 자동 발행은 네이버 블로그 '비공개'까지. 공개 전환은 사람이 블로그에서.
   이 컴포넌트에는 **"공개로 올리기" 같은 버튼 자리가 없다.** 만들지 마라.
   data-state : none(아직) · queued(대기) · private(비공개로 올라감) · failed(실패)
   ========================================================================== */

.pub-slot {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-4);
  border: var(--border-w) dashed var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--bg-surface-2);
  color: var(--text);
}
.pub-slot__label {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-200);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
}
.pub-slot__state {
  margin: 0;
  font-size: var(--fs-400);
  font-weight: var(--fw-heavy);
  color: var(--text-strong);
  line-height: var(--lh-snug);
}
.pub-slot__todo {
  margin: 0;
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  color: var(--text);
}
.pub-slot__url {
  font-family: var(--font-mono);
  font-size: var(--fs-300);
  overflow-wrap: anywhere;
}
.pub-slot__note {
  margin: 0;
  font-size: var(--fs-300);
  color: var(--text-muted);
}
.pub-slot[data-state="private"] {
  border: var(--border-w-strong) solid var(--info-border);
  border-left: 6px solid var(--info);
  background: var(--info-tint);
}
.pub-slot[data-state="private"] .pub-slot__state { color: var(--info); }
.pub-slot[data-state="private"] .pub-slot__note { color: var(--text); }
.pub-slot[data-state="queued"] {
  border-style: solid;
  border-color: var(--border);
  background: var(--bg-surface);
}
.pub-slot[data-state="failed"] {
  border: var(--border-w-strong) solid var(--danger);
  background: var(--danger-tint);
}
.pub-slot[data-state="failed"] .pub-slot__state { color: var(--danger); }


/* ==========================================================================
   28 검토 화면 레이아웃 · 이력
   --------------------------------------------------------------------------
   글 하나를 검토할 때는 도장 상세의 오른쪽 칸(≈650px)이 좁다.
   목록에서 글을 누르면 **넓은 작업 화면**으로 바꾼다(주소도 바꿔라: #/content/12).

   .review-head      뒤로 · 제목 · 상태 · **판정 한 줄**(차단 N · 경고 N)
   .review           1024px 이상에서 2단 — 본문(왼쪽) / 게이트·조작(오른쪽)
   .review__side     오른쪽 칸. 화면에 붙어 따라오되 **자기 안에서 스크롤**된다
                     (1차에서 sticky 상세가 아래를 못 보게 막았던 사고를 max-height 로 막는다)
   .review__actions  승인·폐기. 오른쪽 칸 맨 아래 — 문제 목록을 **다 지나서** 닿는다
   ========================================================================== */

.review-head {
  display: grid;
  gap: var(--space-2);
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-5);
  border-bottom: var(--border-w) solid var(--border);
}
.review-head__title {
  margin: 0;
  font-size: var(--fs-600);
  line-height: var(--lh-snug);
  color: var(--text-strong);
}
.review-head__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-300);
  color: var(--text-muted);
}

.review {
  display: grid;
  gap: var(--space-6);
  align-items: start;
}
.review__main {
  min-width: 0;
  padding: var(--space-5);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
}
.review__side {
  min-width: 0;
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 1024px) {
  .review { grid-template-columns: minmax(0, 1fr) minmax(22rem, 28rem); }
  .review__main { padding: var(--space-8); }
  .review__side {
    position: sticky;
    top: calc(var(--space-12) + var(--space-2));   /* .rail--private 높이만큼 */
    max-height: calc(100dvh - var(--space-12) - var(--space-4));
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 0 var(--space-1) var(--space-4) 0;
  }
}
.review__actions {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
}
.review__actions-why {
  margin: 0;
  font-size: var(--fs-300);
  color: var(--text);
  line-height: var(--lh-snug);
}

/* 상태·발행 이력 */
.timeline {
  margin-inline: 0;
  margin-bottom: 0;   /* 위쪽은 비워 둔다 — .stack 안에서 간격을 받게 */
  padding: 0;
  list-style: none;
  border-left: var(--border-w-strong) solid var(--border);
}
.timeline > li + li { margin-top: 0; }
.timeline__item {
  position: relative;
  padding: 0 0 var(--space-3) var(--space-4);
  font-size: var(--fs-300);
  color: var(--text);
  line-height: var(--lh-snug);
}
.timeline__item::before {
  content: "";
  position: absolute;
  left: -6px;
  top: 0.45em;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--bg-surface);
  border: var(--border-w-strong) solid var(--border-strong);
}
.timeline__time {
  display: block;
  font-size: var(--fs-200);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}


/* ##########################################################################
   ── 3차 컴포넌트 (4단계 관장 전달 — 운영자 쪽) ──────────────────────────────
   쓰는 법은 docs/design.md 19절. 견본은 docs/mockups/delivery-admin.html.
   관장이 보는 페이지는 이 파일을 읽지 않는다(src/delivery/assets/delivery.css).
   ########################################################################## */


/* ==========================================================================
   29 관장 전달 — 받아온 시각 · 올렸어요 상태 · 발행 자리의 전달 상태
   --------------------------------------------------------------------------
   .sync-note[data-state]   "올렸어요는 10분마다 받아옵니다 · 마지막 14:20 (6분 전)"
                            fresh(조용히) · syncing(받아오는 중) · stale(오래됨 — 호박 ⚠)
                            · failed(받아오지 못함 — 빨강 ✕. 우리 쪽 고장이라 빨강이다)
                            ⚠️ 이 줄이 없으면 운영자는 "관장님이 방금 눌렀다는데 왜 안 바뀌지?"에 답할 수 없다.
   .upload-state[data-state]  원고 하나에 대한 관장의 상태. 글자 + 아이콘 + 시각
                            done(✓ 올렸어요) · waiting(⏳ 아직) · undone(↺ 되돌림 — 아직으로 센다)
                            · unknown(? 받아온 적 없음 — 파선) · none(— 전달 전)
   .pub-slot[data-state="delivered" | "uploaded"]   27절 발행 자리의 새 상태(방향 변경 2026-09-19)
   ========================================================================== */

.sync-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  margin: 0;
  font-size: var(--fs-300);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}
.sync-note__text::before {
  content: "↻ ";
  font-weight: var(--fw-heavy);
}
.sync-note__time {
  font-weight: var(--fw-bold);
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
}
.sync-note[data-state="syncing"] .sync-note__text::before { content: "… "; }
.sync-note[data-state="stale"] { color: var(--warn); }
.sync-note[data-state="stale"] .sync-note__text { font-weight: var(--fw-bold); }
.sync-note[data-state="stale"] .sync-note__text::before { content: "⚠ "; }
.sync-note[data-state="failed"] { color: var(--danger); }
.sync-note[data-state="failed"] .sync-note__text { font-weight: var(--fw-bold); }
.sync-note[data-state="failed"] .sync-note__text::before { content: "✕ "; }

.upload-state {
  display: inline-grid;
  gap: 2px;
  line-height: var(--lh-snug);
}
.upload-state__label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--fs-300);
  font-weight: var(--fw-heavy);
  color: var(--text-muted);
}
.upload-state__label::before { content: "—"; }
.upload-state__time {
  font-size: var(--fs-200);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.upload-state[data-state="done"] .upload-state__label { color: var(--ok); }
.upload-state[data-state="done"] .upload-state__label::before { content: "✓"; }
.upload-state[data-state="waiting"] .upload-state__label { color: var(--warn); }
.upload-state[data-state="waiting"] .upload-state__label::before { content: "⏳"; }
/* 되돌림 — 관장이 눌렀다가 거뒀다. 미업로드로 센다. 가운뎃줄 없이 ↺ 로 */
.upload-state[data-state="undone"] .upload-state__label { color: var(--warn); }
.upload-state[data-state="undone"] .upload-state__label::before { content: "↺"; }
.upload-state[data-state="unknown"] .upload-state__label {
  padding: 0 var(--space-2);
  border: var(--border-w) dashed var(--border-strong);
  border-radius: var(--radius-xs);
}
.upload-state[data-state="unknown"] .upload-state__label::before { content: "?"; }

/* 27 발행 자리 — 방향 변경 후의 두 상태 */
.pub-slot[data-state="delivered"] {
  border: var(--border-w-strong) solid var(--warn-border);
  border-left: 6px solid var(--warn);
  background: var(--warn-tint);
}
.pub-slot[data-state="delivered"] .pub-slot__state { color: var(--warn); }
.pub-slot[data-state="delivered"] .pub-slot__note { color: var(--text); }
.pub-slot[data-state="uploaded"] {
  border: var(--border-w-strong) solid var(--ok-border);
  border-left: 6px solid var(--ok);
  background: var(--ok-tint);
}
.pub-slot[data-state="uploaded"] .pub-slot__state { color: var(--ok); }
.pub-slot[data-state="uploaded"] .pub-slot__note { color: var(--text); }
