/* 지앤씨피아 급여관리 — 화면 스타일.
 *
 * 사용자는 급여 담당 사무직이고 매달 반복 작업에 쓴다. 목표는 개성이 아니라 오독 방지다.
 * - 간격은 4px 배수 한 체계(--s1~--s8)만 쓴다. 눈대중 여백 금지.
 * - 색은 정보일 때만 쓴다: 미입력(경고) / 입력됨(정상) / 오류 / 주요 동작. 나머지는 무채색.
 * - 숫자는 tabular-nums + 우측 정렬 — 자릿수가 세로로 맞아야 금액·시간 오독이 준다.
 * - 웹폰트를 외부에서 불러오지 않는다 (사내 배포 환경). 시스템 한글 폰트 스택만 쓴다.
 */

:root {
  --field-error-h: 15px;   /* 검증 메시지 한 줄 자리 — 미리 비워 둔다 (.field-error) */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px;

  --ink: #16181d;         /* 본문 */
  --ink-soft: #5b6270;    /* 보조 텍스트 */
  --line: #d7dae0;
  --line-soft: #e8eaee;
  --bg: #ffffff;
  --bg-soft: #F1F4FA;   /* 화면 바탕 — 흰 카드가 떠 보이도록 옅은 네이비 회색 */

  --accent: #13264E;      /* 주요 동작 — 네이비 */
  --warn-ink: #8a4b00;    /* 미입력 — 이 화면에서 가장 중요한 신호 */
  --warn-bg: #fff4e2;
  --warn-line: #e8a33d;
  --ok-ink: #185c37;
  --ok-bg: #e9f4ec;
  --error-ink: #96261f;
  --error-bg: #fdecea;
  /* 아래 4개는 위 색들과 짝을 이루는 값인데 규칙마다 흩어져 하드코딩되어 있었다.
   * 값은 그대로 두고 정의만 한 곳으로 모은다 (화면에 보이는 색은 달라지지 않는다). */
  --ok-line: #b7dcc4;
  --error-line: #f0b3ae;
  --accent-strong: #0B1730;   /* 진한 네이비 — 주요 버튼 hover·강조 */
  /* ★ "떠 있는 카드"의 그림자는 **한 곳에서만** 정한다. 제목 줄·사이드바 캡슐·조회 조건·
   * 전월 복사·합계가 같은 값을 쓰는데, 규칙마다 적어두면 하나를 바꿀 때 나머지가 옛 값으로
   * 남아 **같은 언어의 카드끼리 미묘하게 달라진다**(색 토큰을 모은 것과 같은 이유).
   * 색은 주색을 섞어 만든다 — 주색을 바꾸면 그림자도 따라온다. */
  --shadow-float: 0 8px 24px color-mix(in srgb, var(--accent) 22%, transparent);
  --accent-bg: #E3E9F5;       /* 연한 네이비 배경 — 확정됨 태그·호버 바탕 */

  /* 상호작용 전환 시간 — **여기 한 곳**에서만 정한다. 화면·규칙마다 적으면 갈린다.
   * 0.18초는 눈에 띄지 않는다는 지적을 받아 0.45초로 늘렸다(요청 구간 0.4~0.5의 가운데).
   * 색·배경만 전환하므로 길어져도 누르는 동작이 늦어지지는 않는다 — 클릭은 즉시 반응하고
   * 색만 뒤따라 바뀐다. */
  --t: .45s;

  /* ★ 글꼴은 **한 스택뿐**이다. 숫자 칸에 따로 고정폭 글꼴을 쓰지 않고
   * `font-variant-numeric: tabular-nums`로 자릿수를 맞춘다 — Pretendard가 그 기능을 갖고 있어
   * 금액·사번이 세로로 어긋나지 않으면서 화면 전체가 같은 글꼴이 된다. */
  --font: 'Pretendard', -apple-system, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
}

/* ── 글꼴 ─────────────────────────────────────────────────────────────
 * ★ CDN이 아니라 **프로젝트에 번들**한다(`app/static/fonts/`, SIL OFL).
 *   사내 서버 배포라 외부 인터넷을 전제할 수 없고, 폰트가 없으면 화면 글꼴이 서버에서만
 *   달라진다 — 로컬에서는 재현되지 않는 종류의 사고다(나눔고딕을 PDF용으로 번들한 것과 같은 이유).
 * ★ 가변 글꼴 한 파일로 굵기 전 구간을 덮는다(45~920). 굵기별로 파일을 나누면 어느 하나를
 *   빠뜨렸을 때 그 굵기만 조용히 다른 글꼴로 대체된다.
 * 출처·버전은 `app/static/VENDOR.md` 참고. PDF(급여명세서·근로계약서)는 나눔고딕 그대로다. */
@font-face {
  font-family: 'Pretendard';
  src: url('/static/fonts/PretendardVariable.woff2') format('woff2-variations');
  font-weight: 45 920;
  font-display: swap;
}

* { box-sizing: border-box; }

/* ── 상호작용 전환 ─────────────────────────────────────────────────────
 * ★ 누를 수 있는 요소 **전부에 한 규칙으로** 건다 — 규칙마다 적게 하면 빠뜨려도 오류가
 *   나지 않아 그 요소만 딱딱하게 바뀐다(공통 처리는 한 곳에서 건다는 원칙 그대로).
 * ★ **색·배경·테두리만** 전환한다. 크기·위치를 움직이면 표가 흔들려 보인다. */
a, button, summary, .home-link, .tablist label {
  transition: background-color var(--t), border-color var(--t), color var(--t);
}
/* 움직임을 줄이도록 설정한 사용자에게는 전환도 등장 애니메이션도 걸지 않는다.
 * ★ `animation: none`만 두면 `both`로 멈춰 있던 첫 프레임(투명)이 그대로 남아 **화면이 빈다.**
 *   그래서 지속시간을 0으로 만들어 끝 상태에서 바로 시작하게 한다. */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation-duration: .01ms !important; animation-delay: 0s !important; }
}

body {
  margin: 0;
  /* ★ 한글은 낱말 안에서 끊으면 안 된다 — 기본값이면 "전월 복사"가 "전월 복 / 사"로 갈린다.
   * `keep-all`이 낱말을 지키고, 칸보다 긴 값(긴 주소·URL)만 `break-word`가 마지막 수단으로 끊는다.
   * ★ 화면마다 붙이지 않고 body 한 곳에서 물려준다 — 붙이는 방식은 빠뜨려도 오류가 나지 않아
   *   그 화면만 조용히 낱말이 깨진다(표 셀·안내 문구·버튼 라벨이 전부 여기에 걸린다). */
  word-break: keep-all;
  overflow-wrap: break-word;
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.5;
  color: var(--ink);
  /* ★ 화면 바탕은 흰색이 아니라 옅은 회색이다 — 그 위에 헤더와 흰 카드가 떠 있는 구조다 */
  background: var(--bg-soft);
}

/* ★ `main`은 **폭을 제한하지 않는다** — 상단 띠가 화면 끝까지 가려면 띠의 부모가 그 폭을
 *   그대로 써야 한다. 대신 안쪽 `.wrap`이 글의 폭을 잡는다(띠는 배경만 넓고 글자는 본문과
 *   같은 자리에서 시작한다 — 일반적인 상단 바 구조).
 * ★ 상한 1440px 근거: 1920 화면에서 표 한 줄이 화면 끝까지 늘어지면 사번(왼쪽)과 금액(오른쪽)이
 *   너무 멀어져 같은 행인지 눈으로 따라가기 어렵다. 1440이면 좌우에 여백이 남아 줄이 끊어 읽힌다.
 *   반대로 1366·1536 노트북에서는 상한에 닿지 않아 화면을 꽉 쓴다(종전 1080은 노트북에서도
 *   양옆이 남아 답답했다). 그 사이 크기에서는 창을 줄이는 대로 함께 줄어든다. */
main { padding: 0 0 var(--s8); }
.wrap { max-width: 1440px; margin: 0 auto; padding: 0 var(--s5); }

/* ── 텍스트 위계 ──────────────────────────────────────────────────── */
h1 { font-size: 27px; margin: 0 0 var(--s2); }
.subhead { color: var(--ink-soft); margin: 0 0 var(--s5); }
.breadcrumb { margin: 0 0 var(--s3); }
/* ★ 링크 기본색을 **한 곳에서** 준다. 규칙이 없으면 클래스가 붙지 않은 링크가 브라우저 기본
 * 파랑(#0000EE)으로 남는데, 오류가 아니라 "그 링크만 색이 다르다"로 나타나 잘 드러나지 않는다
 * — 실제로 급여 화면의 "확인 목록 보기"와 급여 조건 화면의 "현장 목록으로"가 그렇게 빠져 있었다.
 * 화면마다 클래스를 덧붙이게 하면 새 화면에서 또 빠뜨린다. 색을 달리 쓰는 링크(안내 이동·사이드바·
 * 표 머리글·버튼)는 이미 각자 규칙에서 덮어쓰므로 이 줄에 영향받지 않는다. */
a { color: var(--accent); }
.breadcrumb a { color: var(--ink-soft); text-decoration: none; }
.hint { color: var(--ink-soft); font-size: 15px; margin: var(--s2) 0 0; }
.hint-warn { color: var(--warn-ink); }
.sub { color: var(--ink-soft); font-size: 15px; }
/* 날짜(.date)는 숫자 글꼴·자릿수 정렬은 쓰되 **왼쪽 정렬**이다 — 금액처럼 크기를 비교하지
 * 않고 자릿수가 고정이라, 오른쪽으로 밀면 왼쪽에 있는 머리글과 어긋나 보인다. */
.code, .num, .calc, .total, .date { font-variant-numeric: tabular-nums; }
.empty {
  padding: var(--s5); border: 1px dashed var(--line); border-radius: 6px;
  color: var(--ink-soft); background: var(--bg-soft);
}

/* ★ `hidden`은 브라우저 기본 스타일(display:none)이라 **우리가 쓴 display 규칙에 진다.**
 * 실제로 로그인 화면의 빈 안내 상자(.flash + hidden)가 분홍색으로 그려지고 있었다.
 * 규칙을 하나 두어 모든 화면에서 한 번에 막는다 — 상자마다 클래스를 덧붙이면 다음에 또 샌다. */
[hidden] { display: none !important; }

/* ── 플래시 ───────────────────────────────────────────────────────── */
.flash {
  display: flex; flex-wrap: wrap; gap: var(--s2);
  padding: var(--s3) var(--s4); border-radius: 6px; margin: 0 0 var(--s4);
  border: 1px solid var(--line);
}
.flash-ok { background: var(--ok-bg); border-color: var(--ok-line); color: var(--ok-ink); }
.flash-error { background: var(--error-bg); border-color: var(--error-line); color: var(--error-ink); }

/* ── 최상단 요약: 이 화면에서 가장 먼저 알아야 할 것 ───────────────── */
.summary {
  display: flex; align-items: baseline; gap: var(--s4);
  padding: var(--s4); border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg-soft); margin: 0 0 var(--s5);
}
.summary-alert { background: var(--warn-bg); border-color: var(--warn-line); }
.summary-number {
  margin: 0; font-size: 32px; font-weight: 700; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.summary-alert .summary-number { color: var(--warn-ink); }
.summary-text { margin: 0; }

/* ── 폼 ───────────────────────────────────────────────────────────── */
/* ★ 테두리만 있던 상자에 **흰 배경**을 준다 — 화면 바탕이 회색이 되면서 배경이 없는 상자는
 *   바탕과 같은 색이 되어 카드로 읽히지 않는다(조회 조건·폼·표가 전부 여기에 해당한다). */
/* ── 폼 한 줄 (공통 규칙) ──────────────────────────────────────────
 * 라벨+칸을 **한 세트**로 보고, 세트들이 한 줄에 설 때 **칸의 아래쪽 기준선**을 맞춘다.
 * 조회 줄(`.filters`)·입력 줄(`.day-fields`)·작업 줄(`.adjust-form`)·전월 복사 줄이
 * **같은 규칙 하나**를 쓴다 — 화면마다 따로 잡으면 빠뜨려도 오류가 나지 않아 그 화면만 조용히 어긋난다
 * (계약서의 "계약 기간" 줄에서 실제로 그랬다: 필수인 시작일·종료일만 위로 올라가 있었다).
 *
 * ★ 어긋나던 원인은 **검증 메시지 자리**다. 자리가 흐름 안에 있으면 메시지를 가진 칸
 *   (필수 칸)만 라벨이 길어지고, 바닥을 맞추는 순간 그 칸의 입력칸이 그만큼 위로 올라간다.
 *   → 메시지는 흐름에서 빼고(`.field-error`는 absolute), 그 자리는 **줄에 선 모든 것**이
 *     아래 여백으로 똑같이 비워 둔다. 별표·라벨·단위 유무와 무관하게 성립한다.
 * ★ 버튼(`.filter-actions`)·계산 결과(`.calc`)처럼 라벨이 아닌 것도 같은 여백을 받으므로
 *   따로 챙길 필요가 없다.
 * ★ 가로 간격도 한 값(--s4)으로 통일한다 — 줄마다 다르면 같은 화면 안에서 간격이 갈린다. */
.filters, .day-fields, .adjust-form, .copy-panel form {
  display: flex; flex-wrap: wrap; gap: var(--s4); align-items: flex-end;
}
.filters > *, .day-fields > *, .adjust-form > *, .copy-panel form > * {
  padding-bottom: calc(var(--s1) + var(--field-error-h));
}
/* 조회 조건은 제목 줄·사이드바와 **같은 "떠 있는 카드"**다(둥근 모서리 + 같은 그림자).
 * 테두리는 없앤다 — 그림자가 이미 경계를 만들어, 선까지 있으면 카드가 두 번 잘린다.
 * ★ 화면마다 따로 잡지 않고 `.filters` 한 곳에 둔다 — 조회 화면 전부가 이 클래스를 쓰므로
 *   새 화면이 생겨도 같은 모양으로 따라온다. */
.filters {
  padding: var(--s4) var(--s5); border: 0; border-radius: 16px; margin: 0 0 var(--s6);
  background: var(--bg); box-shadow: var(--shadow-float);
}
/* 라벨은 [이름 한 줄][칸] 두 칸으로만 쌓인다 — 이름이 한 줄인 것이 전제이고, 그래서 "필수"를
 * 별표로 바꿔 이름 옆에 붙였다(.req). position은 검증 메시지(absolute)의 기준점이다. */
label { display: inline-flex; flex-direction: column; gap: var(--s1); font-size: 15px;
        color: var(--ink-soft); position: relative; }
/* 줄 밖에 홀로 선 라벨(로그인 등)도 메시지 자리를 비워 둔다 — 자리가 없으면 메시지가
 * 아래 요소를 덮는다. 비워 두는 값은 위 줄 규칙과 **같다**. */
label.has-message { padding-bottom: calc(var(--s1) + var(--field-error-h)); }
/* 줄에 선 **동작 버튼은 이 상자에 담는다**(조회·전월 복사·조정 추가·확정 …).
 * ★ 버튼을 줄에 직접 세우면 `.btn`의 `padding` 한 벌이 줄 규칙을 이겨(같은 특정도인데 뒤에 있다)
 *   버튼만 아래로 내려앉는다. 게다가 버튼에 준 아래 여백은 테두리 안이라 눈에 보인다 —
 *   자리를 비우는 일은 **상자**가 맡아야 한다.
 * ★ 글자 크기는 칸과 같은 15px — 라벨 안 입력칸은 15px를 물려받는데 버튼은 본문 17px이라
 *   그대로 두면 버튼만 높다. */
.filter-actions { display: flex; gap: var(--s3); font-size: 15px; }
/* 상자에 담는 것을 빠뜨려도 **바닥은 어긋나지 않게** 한다 — 자리를 바깥 여백으로 준다
 * (안쪽 여백은 테두리 안이라 버튼 자체가 커진다). 규칙을 기억해야만 맞는 구조를 만들지 않는다. */
.filters > .btn, .day-fields > .btn, .adjust-form > .btn, .copy-panel form > .btn {
  margin-bottom: calc(var(--s1) + var(--field-error-h));
}
input, select, button { font: inherit; }
/* 입력칸은 **한 규칙**으로 모은다.
 * ★ **쓸 타입을 나열하지 않고, 입력칸이 아닌 것만 뺀다.** 나열하는 방식은 새 타입이 생길 때마다
 *   그 칸만 브라우저 기본 모양으로 남는데, 오류가 아니라 "생김새가 다르다"로 나타나 잘 드러나지
 *   않는다 — 실제로 text·date·search가 한 번, password(로그인 화면)가 또 한 번 그렇게 빠졌다.
 *   타입을 적지 않은 `<input>`(기본 text)도 이 규칙에 함께 걸린다. */
input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="hidden"]),
select {
  padding: var(--s2); border: 1px solid var(--line); border-radius: 4px;
  color: var(--ink); background: var(--bg); font-variant-numeric: tabular-nums;
}
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

fieldset { border: 1px solid var(--line); border-radius: 6px; padding: var(--s4); margin: 0 0 var(--s4); background: var(--bg); }
legend { font-weight: 700; padding: 0 var(--s2); font-size: 16px; }
.stack { margin-top: var(--s4); }
/* ★ 아래 여백은 **줄 규칙이 준다** — 여기서 `padding` 한 벌로 덮으면 한 줄에 선 체크박스만
 *   여백이 달라져 옆 입력칸보다 내려앉는다(현장 지급일 줄이 그 경우다). 위쪽만 잡는다. */
.choice { flex-direction: row; align-items: center; gap: var(--s2); font-size: 17px;
          color: var(--ink); padding-top: var(--s1); }
.field-label { font-size: 15px; color: var(--ink-soft); }

.day-toggle { flex-direction: row; align-items: center; gap: var(--s2); color: var(--ink); font-weight: 700; }
.day-fields { flex: 1; }   /* 배치는 위 "폼 한 줄" 공통 규칙 */

.next-day { flex-direction: row; align-items: center; gap: var(--s1); color: var(--ink); }
/* 교대 패턴 — 장식 없이 구분선과 여백으로만 묶는다 */
.shift-pattern { padding: var(--s3) 0; border-top: 1px solid var(--line-soft); }
.shift-pattern:first-child { border-top: 0; padding-top: 0; }
/* 패턴은 항목이 많아 위쪽 정렬이 읽기 쉽다 (요일 행은 기존대로 아래쪽 정렬 유지) */
.shift-pattern .day-fields { align-items: start; }
.shift-pattern input[type=text] { width: 96px; }
.breaks { display: flex; flex-direction: column; gap: var(--s2); }
.break-row { display: flex; align-items: center; gap: var(--s2); }
.calc { margin: 0; font-size: 15px; color: var(--ink-soft); min-width: 88px; }
.total { font-size: 17px; font-weight: 700; margin: 0 0 var(--s4); }
/* 계산할 수 없는 상태 — 숫자가 아니라 안내라는 것이 색과 굵기로 보이게 한다 */
.calc-blocked { font-weight: 400; color: var(--warn-ink); }
/* 유형이 여러 개라 한 줄에 하나씩 — 인라인으로 붙으면 라벨과 단위가 섞여 읽힌다 */
.holiday { display: flex; flex-direction: row; align-items: center; gap: var(--s2);
           color: var(--ink); margin-top: var(--s2); width: fit-content; }
.holiday input { width: 96px; text-align: right; }
.unit { color: var(--ink-soft); font-size: 15px; }
/* 단위는 칸 **오른쪽 같은 줄**에 선다 ("[  ] 원").
 * ★ 라벨 안은 세로 flex라 `<span class="unit">`을 칸의 형제로 두면 **아래 줄로 떨어진다** —
 *   "원"이 홀로 한 줄을 차지해 어색하고, 그만큼 라벨이 높아져 같은 줄의 다른 칸과 바닥이
 *   어긋난다. 칸과 단위를 한 상자에 담아 가로로 세운다(마크업으로 만든다 — `:has()`는 쓰지 않는다). */
.with-unit { display: flex; align-items: center; gap: var(--s2); }
/* 라벨 옆 보조 안내 — 라벨 자체를 가리지 않게 한 단계 작고 옅게 둔다.
 * 별도 줄(.unit)로 두면 그 칸만 높아져 옆 칸과 어긋난다(직종 안내가 실제로 그랬다). */
.label-note { font-size: 12px; font-weight: 400; color: var(--ink-soft); opacity: .8; }
.actions-bar { display: flex; gap: var(--s2); margin-top: var(--s5); }

/* ── 버튼 ─────────────────────────────────────────────────────────── */
.btn {
  display: inline-block; padding: var(--s2) var(--s4); border: 1px solid var(--line);
  border-radius: 4px; background: var(--bg); color: var(--ink);
  text-decoration: none; cursor: pointer; white-space: nowrap;
}
/* 호버는 화면 전체가 **같은 기준**이다: 연보라 바탕 + 진한 보라 글씨.
 * 회색으로 두면 "누를 수 있다"가 아니라 "비활성"으로 읽힌다(비활성 버튼이 회색 바탕이다). */
.btn:not([disabled]):hover { background: var(--accent-bg); border-color: var(--accent); color: var(--accent-strong); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--bg); font-weight: 700; }
/* ★ 주요 버튼은 이미 보라 바탕이라 위 규칙(연보라 바탕 + 진한 보라 글씨)을 그대로 물려받으면
 * **글씨가 바탕에 묻힌다.** 글자색까지 함께 되돌려 준다. */
.btn-primary:not([disabled]):hover { background: var(--accent-strong); border-color: var(--accent-strong); color: var(--bg); }
.btn-quiet { border-color: transparent; color: var(--ink-soft); padding: var(--s2); }
.btn-quiet:not([disabled]):hover { color: var(--accent-strong); }

/* ── 전월 복사 ────────────────────────────────────────────────────── */
/* ★ 조회 조건과 **같은 카드**다(같은 성격의 입력 영역이다 — 시각적으로 갈라놓을 이유가 없다).
 * ★ 위 여백은 목록과 붙지 않을 만큼 준다(--s6 32px) — `.stack`(16px)만으로는 표 마지막 줄에
 *   붙어 다음 섹션이 목록의 일부처럼 읽힌다. */
.copy-panel {
  padding: var(--s5); border: 0; border-radius: 16px; margin: var(--s6) 0 var(--s5);
  background: var(--bg); box-shadow: var(--shadow-float);
}
.copy-summary { margin-top: var(--s3); padding: var(--s3); background: var(--bg-soft); border-radius: 4px; }
.copy-summary p { margin: 0 0 var(--s1); }
.skip-list { margin: var(--s2) 0 0; padding-left: var(--s5); color: var(--ink-soft); font-size: 15px; }
.htmx-indicator { display: none; color: var(--ink-soft); font-size: 15px; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { display: inline; }

/* ── 표 ───────────────────────────────────────────────────────────── */
/* ★ 표는 **흰 카드가 아니다** — 화면 바탕(--bg-soft) 위에 그대로 놓는다. 51명·29항목을
 *   훑는 화면에서 흰 판이 깔리면 목록이 상자에 갇혀 답답하다.
 * ★ 대신 바탕만으로는 행 구분이 사라지므로 세 가지로 읽히게 한다:
 *   머리글은 옅은 네이비 바탕 + 진한 글자, 본문 행은 투명 + 옅은 구분선,
 *   커서를 올린 행만 **흰 배경**이 부드럽게 들어온다(어느 줄을 보고 있는지 표시).
 * ★ `border-collapse`를 `separate`로 바꾼 이유는 **머리글 위 모서리를 둥글게** 하기 위함이다
 *   (collapse 상태에서는 셀의 border-radius가 무시된다). 셀 테두리는 아래쪽 한 줄뿐이라
 *   선이 두 겹으로 보이는 부작용이 없다. */
/* ★ 표에 **최대 폭을 두지 않는다** — 조회 조건 카드·안내 배너·탭은 본문 폭을 그대로 쓰므로,
 *   표만 좁히면 오른쪽 끝이 혼자 어긋나고 그 자리가 휑하게 빈다. 좌우 기준선은 `.wrap` 하나다.
 *   가운데가 비어 보이는 문제는 폭이 아니라 **열 배분**으로 푼다(긴 글 열이 남는 폭을 흡수하고
 *   숫자 열은 좁게 서로 붙는다 — 아래 `.fill`과 각 표의 `<colgroup>`). */
.grid { width: 100%; border-collapse: separate; border-spacing: 0; background: transparent; }
.grid caption { text-align: left; }
/* 행 간격은 간격 체계에서 한 단계 낮춘다(--s3 12px → --s2 8px) — 51명을 훑는 화면이라
 * 한 화면에 들어오는 줄 수가 곧 스크롤 양이다. **글자 크기는 줄이지 않는다**(하루 종일 보는 화면).
 * 표는 전부 .grid 하나를 쓰므로 이 한 줄이 모든 화면에 같은 기준으로 걸린다. */
.grid th, .grid td { padding: var(--s1) var(--s3); border-bottom: 1px solid var(--line-soft); text-align: left; vertical-align: top; }
.grid thead th {
  font-size: 15px; color: var(--accent-strong); font-weight: 700;
  background: var(--accent-bg); border-bottom: 0; padding: var(--s3);
}
/* 위 모서리만 둥글다 — 아래는 본문 행으로 이어지므로 깎으면 머리글이 떠 보인다 */
.grid thead th:first-child { border-top-left-radius: 12px; }
.grid thead th:last-child { border-top-right-radius: 12px; }
/* ★ 행 강조는 **연보라 바탕**이다(0.2초) — 버튼·탭의 hover와 같은 색이라 "커서를 올린 것"으로
 *   읽히고, 경고색(주황)과는 뜻이 섞이지 않는다. 흰색만으로는 51명을 훑을 때 잘 보이지 않았다.
 * ★ 그 경고 행은 hover에서 제외한다 — 덮어쓰면 담당자가 찾는 표시가 사라진다. */
.grid tbody tr { transition: background-color .2s; }
.grid tbody tr:not(.row-missing):not(.row-alert):hover { background: var(--accent-bg); }
.grid .code, .grid .nowrap, .grid .date { white-space: nowrap; }
/* ★ 식별 열(사번·코드·성명)의 너비를 고정한다.
 * 자동 너비는 **그 화면에 담긴 값의 길이**로 정해져서, 탭(입력완료·미입력)을 바꾸거나
 * 현장을 바꾸면 같은 표인데도 사번과 성명 사이가 벌어졌다 좁아진다 — 화면이 흔들려 보인다.
 * 값의 최대 길이가 정해져 있는 열이라(사번 12자 + 접미사) 고정해도 잘리지 않는다.
 * 나머지 열(근무 요약·사유 등)은 자유 문장이라 자동 너비를 그대로 둔다. */
.grid td.code { width: 9.5em; }
.grid .name { width: 6em; white-space: nowrap; }
/* 값의 길이가 화면마다 달라지는 열(금액 자릿수·근무 요약·동작 버튼)까지 고정해야 하는 표는
 * `<colgroup>`으로 폭을 못 박고 table-layout을 고정한다. 이 방식은 **내용과 무관하게**
 * 열이 제자리에 있어, 탭이나 현장을 바꿔도 표가 흔들리지 않는다.
 * 폭을 주지 않은 열 하나가 남는 자리를 전부 가져간다(근태의 근무 요약 등). */
.grid.fixed { table-layout: fixed; }
/* 폭을 못 박지 않은 표(직원·계약서·확인 목록)에서 **남는 폭을 가져갈 글자 열**을 지정한다.
 * 없으면 남는 폭이 모든 열에 조금씩 퍼져 숫자 열과 동작 버튼이 서로 멀어진다. */
.grid .fill { width: 100%; }
.grid .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.grid .actions { text-align: right; white-space: nowrap; }
/* 동작이 둘 이상이면 붙어 있어 한 덩어리로 읽힌다("수정 계약서") — 사이를 벌린다.
 * 조용한 버튼은 테두리가 투명해서 padding만으로는 경계가 보이지 않는다. */
.grid .actions .btn + .btn { margin-left: var(--s3); }

/* ── 요일별 근무 표 (근태·계약서 폼) ──────────────────────────────────
 * ★ 요일이 행, 항목이 열이다 — 요일마다 세로로 쌓으면 7일을 보는 데 화면을 한참 굴려야 하고
 *   요일끼리 값을 견줄 수 없다.
 * ★ 이 블록은 반드시 `.grid`·`.breaks` 규칙 **뒤에** 있어야 한다 — 특정도가 같아 순서가 곧
 *   결과다. 앞에 두었을 때 `.breaks`(세로 상자)가 이겨서 휴게 칸이 표 오른쪽으로 밀렸다. */
.schedule-grid { font-size: 15px; margin-top: var(--s2); }
.schedule-grid td { vertical-align: middle; }
/* 시각 칸은 **고정 폭**이다 — 열을 따라 늘어나면 12시간 표기(09:00 AM)에 견줘 과하게 넓어지고,
 * 시작·종료가 서로 붙어 보인다. 열이 좁아지면 max-width가 받아 잘리지 않게 줄어든다. */
.schedule-grid input[type="time"] { width: 9em; max-width: 100%; }
.schedule-grid .check { text-align: center; }
.schedule-grid .day-cell { color: var(--ink); }
/* ★ 머리글에 바탕색을 두지 않는다 — 목록 표는 훑는 화면이라 머리글이 도드라져야 하지만,
 *   이것은 **입력 폼**이라 색 띠가 칸보다 눈에 먼저 들어온다. 글자를 작고 진하게 하고
 *   아래 선 하나로만 가른다. 목록 표는 종전 그대로다(이 표에만 건다). */
.schedule-grid thead th {
  background: none; color: var(--ink); font-size: 13px;
  padding: var(--s1) var(--s3); border-bottom: 1px solid var(--line);
}
/* ★ 근무 체크가 풀린 요일은 **행을 그대로 두고** 나머지 칸만 흐리게 한다(칸 자체는 스크립트가
 *   잠근다) — 행을 숨기면 어느 날이 빠졌는지 알 수 없다. */
.schedule-grid tr:not(.on) td:not(.day-cell) { opacity: .45; }
/* 휴게 — 구간은 "시작 ~ 종료 [삭제]" 한 줄, 추가 버튼은 **그 바로 아래 왼쪽**이다.
 * 버튼은 테두리 없는 작은 글자 버튼으로 둔다 — 요일마다 둘씩 있어 상자로 그리면 표가
 * 어지러워지고 행 높이도 그만큼 커진다. */
/* ★ 휴게 묶음은 **열 오른쪽에 붙는다** — 남는 폭을 이 열이 가져가므로, 왼쪽에 두면
 *   입력칸과 실근무 사이가 통째로 빈다. 머리글도 같은 오른쪽 정렬이라 값 바로 위에 선다.
 *   묶음 **안쪽**은 왼쪽 정렬이라 "＋ 휴게 추가"는 구간 바로 아래 왼쪽에 그대로 있다
 *   (inline-flex라 상자가 내용만큼만 차지해 오른쪽에 붙을 수 있다). */
.schedule-grid .breaks-cell { text-align: right; }
.breaks-compact { display: inline-flex; flex-direction: column; align-items: flex-start;
  gap: var(--s1); text-align: left; }
.breaks-compact .break-rows { display: flex; flex-direction: column; gap: var(--s1); }
.breaks-compact .break-row { flex-wrap: wrap; gap: var(--s1); }
.breaks-compact input[type="time"] { width: 8em; max-width: 100%; }
.breaks-compact .btn { font-size: 12px; padding: 0; border: 0; background: none; }
/* 잠긴 요일의 추가 버튼도 글자 버튼 그대로 둔다(회색 상자가 생기면 표가 얼룩덜룩해진다) */
.breaks-compact .btn[disabled] { background: none; }
.row-missing { background: var(--warn-bg); }
.row-missing .code, .row-missing td { border-bottom-color: var(--warn-line); }

.tag { display: inline-block; padding: 0 var(--s2); border-radius: 999px; font-size: 13px; font-weight: 700; white-space: nowrap; }
.tag-ok { background: var(--ok-bg); color: var(--ok-ink); }
.tag-missing { background: var(--bg); color: var(--warn-ink); border: 1px solid var(--warn-line); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── 급여 ─────────────────────────────────────────────────────────────
 * 근태 화면의 색 체계를 그대로 쓴다: 사람이 손대야 하는 것(확인 필요) = 경고색,
 * 계산 완료 = 정상색, 확정됨 = 주요 동작색. 새 색을 만들지 않는다.
 */
.flash-warn { background: var(--warn-bg); border-color: var(--warn-line); color: var(--warn-ink); }
.counts { margin: calc(-1 * var(--s3)) 0 var(--s5); color: var(--ink-soft); }
.tag-attention { background: var(--warn-bg); color: var(--warn-ink); border: 1px solid var(--warn-line); }
.tag-confirmed { background: var(--accent-bg); color: var(--accent-strong); }   /* 12px 태그 — 연보라 위에서는 진한 쪽이라야 다른 태그와 대비가 같아진다 */
/* 확인 필요 행 — 근태 미입력 행과 같은 강조 (담당자가 찾는 것이 같은 성격이다) */
.row-alert { background: var(--warn-bg); }
.row-alert td { border-bottom-color: var(--warn-line); }
.grid .strong { font-weight: 700; }
.grid caption { padding: var(--s3) 0 var(--s2); font-weight: 700; }

/* 0인 항목 접기 — JS 없이 체크박스로 (표가 29줄이면 볼 것이 묻힌다).
 *
 * ★ 종전에는 글자만 있어 **누를 수 있는 것으로 보이지 않았고 켜짐/꺼짐도 알 수 없었다.**
 *   스위치를 그려 상태를 눈에 보이게 한다(꺼짐 = 회색 트랙 + 왼쪽 손잡이,
 *   켜짐 = 네이비 트랙 + 오른쪽 손잡이 + 진한 글자).
 * ★ 체크박스 자체는 `.sr-only`로 두고 **라벨을 스위치로 그린다** — 탭(.tablist)과 같은 방식이다.
 *   숨긴 체크박스는 표 위쪽에 형제로 남아야 하므로(`#show-zero ~ .item-tables` 규칙이
 *   그 형제 관계로 0원 행을 접는다) 마크업 위치는 바꾸지 않는다.
 * ★ 자리는 표 오른쪽 위다 — 무엇을 제어하는 스위치인지가 위치로 드러난다. */
.table-tools { display: flex; justify-content: flex-end; margin: 0 0 var(--s2); }
.toggle-zero {
  position: relative; display: inline-flex; flex-direction: row; align-items: center;
  gap: var(--s2); padding: var(--s1) 0 var(--s1) 46px;
  font-size: 15px; color: var(--ink-soft); cursor: pointer;
  transition: color .2s;
}
/* 트랙 */
.toggle-zero::before {
  content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 38px; height: 22px; border-radius: 999px; background: var(--line);
  transition: background-color .2s;
}
/* 손잡이 */
.toggle-zero::after {
  content: ""; position: absolute; left: 3px; top: 50%; transform: translate(0, -50%);
  width: 16px; height: 16px; border-radius: 50%; background: var(--bg);
  transition: transform .2s;
}
.toggle-zero:hover { color: var(--accent-strong); }
.toggle-zero:hover::before { background: var(--accent-bg); }
/* ★ 켜짐 규칙은 hover 뒤에 둔다 — 앞에 두면 켜진 스위치에 커서를 올렸을 때 트랙이 연하게 풀린다 */
#show-zero:checked ~ .table-tools .toggle-zero { color: var(--ink); font-weight: 700; }
#show-zero:checked ~ .table-tools .toggle-zero::before { background: var(--accent); }
#show-zero:checked ~ .table-tools .toggle-zero::after { transform: translate(16px, -50%); }
/* 체크박스를 감췄으므로 키보드 초점 표시는 스위치 쪽에 준다(탭과 같은 처리) */
#show-zero:focus-visible ~ .table-tools .toggle-zero { outline: 2px solid var(--accent); outline-offset: 2px; }
#show-zero:not(:checked) ~ .item-tables .row-zero { display: none; }
.row-zero td { color: var(--ink-soft); }

.item-tables { display: flex; flex-direction: column; gap: var(--s5); }
/* ★ 표에서 걷어낸 흰 카드를 **여기 한 곳에만** 남긴다 — 항목 표가 바탕 위에 평평하게 깔리므로
 *   합계만 떠오르면 시선이 자연히 모인다(전부 카드면 아무것도 강조되지 않는다). */
.totals {
  margin: var(--s6) 0; padding: var(--s5); border: 0;
  border-radius: 16px; background: var(--bg); box-shadow: var(--shadow-float);
}
.totals p { display: flex; justify-content: space-between; gap: var(--s5); margin: 0 0 var(--s2); }
.totals .num { font-size: 19px; }
.totals .net { margin: var(--s3) 0 0; padding-top: var(--s3); border-top: 1px solid var(--line); }
/* 차인지급액이 이 화면의 결론이다 — 가장 크고 굵게, 주색으로 둔다 */
.totals .net .num { font-size: 32px; font-weight: 800; color: var(--accent); }

.panel { margin: var(--s6) 0 0; padding-top: var(--s5); border-top: 1px solid var(--line); }
.panel h2 { font-size: 20px; margin: 0 0 var(--s4); }
.adjust-form input[type="number"] { text-align: right; }
.ack { flex-basis: 100%; color: var(--warn-ink); }
.reason-list { margin: var(--s2) 0 0; padding-left: var(--s5); }
.btn[disabled] { color: var(--ink-soft); background: var(--bg-soft); cursor: not-allowed; }

/* ── 직원 ─────────────────────────────────────────────────────────── */
/* ★ 필수 표시는 라벨 이름 **뒤에 붙는 별표**다. 별도 줄에 "필수"라고 두면 라벨이 두 줄이 되고
 *   (라벨 안은 세로 flex라 글자와 표시가 각각 한 줄을 차지한다), 그러면 같은 줄에 선 버튼과
 *   기준선을 맞출 방법이 없다. 이름과 별표는 `.label-name` 한 상자에 담아 한 줄로 만든다.
 *   읽어 주는 이름은 aria-label="필수"로 남기고, 칸 자체의 `required`도 그대로다. */
.req { color: var(--error-ink); font-size: 12px; font-weight: 700; }
/* 필수 칸 검증 안내 (`static/forms.js`) — 브라우저 기본 말풍선은 문구가 브라우저·OS 언어를
 * 따라가고(영어로 뜬다), 지급월처럼 실제 컨트롤이 1px로 숨어 있는 칸에서는 그 1px 자리에 붙어
 * 필터 박스 위쪽 엉뚱한 곳에 뜬다. 그래서 칸 **바로 아래**에 직접 그린다.
 * ★ **흐름 밖에 둔다**(absolute + 라벨 아래 여백에 자리). 흐름 안에 두면 메시지를 가진 칸만
 *   라벨이 길어져, 한 줄에서 바닥을 맞출 때 그 칸의 입력칸이 위로 올라간다("폼 한 줄" 규칙 참고).
 * ★ 보였다 숨기는 것은 `visibility`다 — display를 껐다 켜면 읽어 주는 순서까지 흔들린다.
 * ★ 라벨의 "필수"(.req)와는 입력칸을 사이에 두고 떨어져 있으므로 굵기만 한 단계 낮춰
 *   두 붉은 글자가 같은 무게로 부딪히지 않게 한다. */
.field-error {
  position: absolute; left: 0; bottom: 0; white-space: nowrap;
  min-height: var(--field-error-h); line-height: 1.2; font-size: 12px; font-weight: 600;
  color: var(--error-ink); visibility: hidden;
}
.field-error.on { visibility: visible; }
/* 어느 칸이 문제인지 테두리로도 보인다. 입력칸 규칙이 :not()을 겹쳐 써 특정도가 높아
   !important 없이는 이 한 줄이 진다. */
.is-invalid { border-color: var(--error-ink) !important; }
/* 주소처럼 긴 값은 한 줄을 채운다 (짧은 칸에 넣으면 앞부분만 보인다) */
.grow { flex: 1 1 280px; }
.grow input, .filters input[type="search"] { width: 100%; }
fieldset input[disabled] { background: var(--bg-soft); color: var(--ink-soft); }
/* 퇴사 처리는 되돌리기 어려우므로 접어두고, 펼치는 것 자체를 첫 확인 단계로 쓴다 */
summary.btn { display: inline-block; list-style: none; }
summary.btn::-webkit-details-marker { display: none; }
details[open] summary.btn { margin-bottom: var(--s3); }

/* ── 반응형: 담당자가 휴대폰으로 확인하는 경우 ──────────────────────── */
@media (max-width: 720px) {
  main { padding: 0 0 var(--s7); }
  .wrap { padding: 0 var(--s3); }
  /* ★ 좁은 화면에서는 표를 **블록으로 편다**. 행이 카드 모양으로 바뀌는데 표 자체가 표로 남으면
   *   `<colgroup>`의 열 너비가 그대로 살아 있어 **화면 밖으로 넘친다**
   *   (글자를 키운 뒤 드러났다 — 당시 근태 표의 고정 폭 합이 47.5em이었다). */
  /* ★ 카드 모양으로 펴진 좁은 화면에서는 **표 자체가 아니라 행**이 흰 카드다.
   *   (넓은 화면에서도 표는 이미 투명이라 여기서 배경을 되돌릴 것이 없다.) */
  .grid { display: block; }
  .grid thead { display: none; }
  /* ★ `tbody`도 함께 펴야 행 카드가 폭을 꽉 채운다 — 표 행 그룹으로 남아 있으면 행이
   *   내용 너비(약 190px)까지만 늘어난다. 종전에는 화면이 흰색이라 드러나지 않았다. */
  .grid tbody { display: block; }
  .grid tr { display: block; border: 1px solid var(--line); border-radius: 6px; margin-bottom: var(--s3); padding: var(--s2); background: var(--bg); }
  .grid td { display: flex; justify-content: space-between; gap: var(--s4); border: 0; padding: var(--s2); text-align: right; }
  .grid td::before { content: attr(data-label); color: var(--ink-soft); font-size: 15px; text-align: left; }
  .grid td:empty { display: none; }
  .row-missing { border-color: var(--warn-line); }
  .summary { flex-direction: column; gap: var(--s2); }
  /* 좁은 화면에서는 줄 간격을 한 단계 좁힌다 — 줄 종류마다 다르면 같은 화면에서 갈린다. */
  .filters, .day-fields, .adjust-form, .copy-panel form { gap: var(--s3); }
}

/* 설정 하위 띠 — 상단 탭을 늘리지 않고 현장·급여규칙을 "설정" 아래에서 가른다 */
.subnav { display: flex; gap: var(--s4); margin: 0 0 var(--s5); border-bottom: 1px solid var(--line); }
.subnav a { color: var(--ink-soft); text-decoration: none; padding: var(--s2) 0; }
.subnav a:hover { color: var(--accent-strong); }
.subnav a.current { color: var(--ink); font-weight: 700; box-shadow: inset 0 -2px 0 var(--accent); }
/* 법정 기준값 이탈 경고 — 값은 저장되었고 무엇이 다른지 알린다 */
.warn-box { margin: var(--s3) 0 0; padding: var(--s3); background: var(--warn-bg);
  border: 1px solid var(--warn-line); border-radius: 4px; color: var(--warn-ink); font-size: 15px; }
.warn-box p, .warn-box ul { margin: 0; }
.warn-box ul { padding-left: var(--s5); }
.fieldset-legal legend { color: var(--warn-ink); }

/* ── 오류 화면 ────────────────────────────────────────────────────────
 * 화면에서 난 오류를 레이아웃 안에서 보여준다. 색·간격은 기존 토큰만 쓴다.
 */
.error-page { max-width: 640px; }
.error-code { margin: 0 0 var(--s1); color: var(--ink-soft); font-size: 15px;
  font-variant-numeric: tabular-nums; }

/* ── 지급월 선택기 ────────────────────────────────────────────────────
 * 브라우저 기본 월 선택기와 같은 형태를 직접 그린다(표시는 항상 한국어).
 * 입력칸은 위 입력칸 규칙과 같은 테두리·여백을 쓰고, 강조는 주요 동작색 하나만 쓴다.
 */
.period-trigger {
  padding: var(--s2); border: 1px solid var(--line); border-radius: 4px;
  background: var(--bg); color: var(--ink); font-variant-numeric: tabular-nums;
  text-align: left; cursor: pointer; min-width: 124px;
}
.period-trigger.placeholder { color: var(--ink-soft); }
/* ★ 패널은 **떠서** 열린다 — 문서 흐름에 들어가면 아래 내용을 밀어내며 펼쳐진다
 *   (브라우저 기본 월 선택기는 겹쳐 뜬다). left/top은 스크립트가 화면 가장자리를 보고 조정한다. */
/* ★ `top: 100%`는 라벨의 **패딩 상자** 바닥이다. 줄 규칙이 메시지 자리만큼(--s1 + 메시지 높이)
 *   아래를 비워 두므로 그만큼 끌어올려야 달력이 입력칸 바로 아래에 붙는다
 *   (종전에는 `:has(.field-error)`로 조건을 걸었는데, 그 선택자를 모르는 브라우저에서는
 *    규칙이 통째로 빠져 달력이 한 줄 떨어져 떴다 — 이제 조건 없이 항상 같은 값이다). */
.period-panel {
  position: absolute; top: 100%; left: 0; z-index: 30;
  margin-top: calc(var(--s1) - (var(--s1) + var(--field-error-h)));
  padding: var(--s3); background: var(--bg);
  border: 1px solid var(--line); border-radius: 6px; box-shadow: 0 2px 8px rgb(0 0 0 / .12);
}
/* 아래에 공간이 없으면 입력칸 위로 뒤집는다 (기본 선택기와 같은 동작) */
.period-panel.up { top: auto; bottom: 100%; margin-top: 0; margin-bottom: var(--s1); }
.period-nav { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); margin-bottom: var(--s2); }
.period-year { font-weight: 700; font-size: 17px; color: var(--ink); }
/* 연도 이동 — 매달 쓰는 버튼이라 누르기 쉬운 크기로 둔다(종전 20×21px은 조준해야 눌렸다) */
.period-step {
  min-width: 36px; min-height: 32px; padding: 0 var(--s2);
  border: 1px solid var(--line); border-radius: 4px; font-size: 22px; line-height: 1;
  background: var(--bg); color: var(--ink); cursor: pointer;
}
.period-step:hover:not(:disabled) { background: var(--accent-bg); border-color: var(--accent); color: var(--accent-strong); }
.period-step:disabled { color: var(--line); cursor: default; }
.period-months { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s1); }
.period-month {
  min-height: 32px; padding: var(--s2) var(--s3); border: 1px solid transparent; border-radius: 4px;
  background: var(--bg); color: var(--ink); cursor: pointer; white-space: nowrap;
}
.period-month:hover:not(:disabled) { background: var(--accent-bg); color: var(--accent-strong); }
.period-month:disabled { color: var(--ink-soft); cursor: default; }
.period-month.on { background: var(--accent); border-color: var(--accent); color: var(--bg); font-weight: 700; }

/* 접어 두는 설정 묶음 — 대부분의 현장은 기본값으로 충분한 값들(현장 등록의 "자세히 설정").
 * 매번 보이면 담당자가 판단해야 할 칸이 두 배가 된다. 기존 간격·색 토큰만 쓴다. */
.panel-details { border: 1px solid var(--line); border-radius: 6px; margin: 0 0 var(--s4); background: var(--bg); }
.panel-details > summary {
  padding: var(--s3) var(--s4); cursor: pointer; font-weight: 700; background: var(--bg-soft);
}
.panel-details[open] > summary { border-bottom: 1px solid var(--line); }
.panel-details > fieldset { border: 0; border-top: 1px solid var(--line-soft); border-radius: 0; margin: 0; }
.panel-details > fieldset:first-of-type { border-top: 0; }

/* ── 탭 ───────────────────────────────────────────────────────────────
 * 라디오 + CSS만 쓴다 (급여 상세의 "0원 항목 접기"와 같은 방식) — 서버를 다시 부르지 않아
 * 조회 조건이 흔들릴 여지가 없고 스크립트도 필요 없다. 라디오라 키보드로도 넘어간다.
 * 색·간격은 기존 토큰만 쓰고, 선택된 탭 표시는 상단 네비게이션과 같은 방식(밑줄)이다. */
.tablist { display: flex; gap: var(--s4); border-bottom: 1px solid var(--line); margin: 0 0 var(--s4); }
.tablist label {
  display: inline-flex; flex-direction: row; align-items: baseline; gap: var(--s2);
  padding: var(--s2) var(--s1); font-size: 17px; color: var(--ink-soft); cursor: pointer;
}
.tablist label strong { font-variant-numeric: tabular-nums; }
.tablist label:hover { color: var(--accent-strong); }
.tabpanel { display: none; }
#tab-done:checked ~ .tablist label[for="tab-done"],
#tab-missing:checked ~ .tablist label[for="tab-missing"] {
  color: var(--ink); font-weight: 700; box-shadow: inset 0 -2px 0 var(--accent);
}
#tab-done:checked ~ .tabpanel-done,
#tab-missing:checked ~ .tabpanel-missing { display: block; }
/* 라디오는 감춰도 포커스가 가야 한다(.sr-only) — 포커스 표시는 탭 이름 쪽에 준다 */
#tab-done:focus-visible ~ .tablist label[for="tab-done"],
#tab-missing:focus-visible ~ .tablist label[for="tab-missing"] { outline: 2px solid var(--accent); outline-offset: 2px; }

/* 정렬 가능한 열 머리글 — 링크지만 머리글로 보여야 한다(파란 링크가 되면 표가 산만해진다).
 * ★ 밑줄 대신 **바탕색**으로 누를 수 있음을 알린다 — 밑줄은 본문 링크의 표시라 머리글에 쓰면
 *   표가 산만해지고, 머리글이 링크처럼 읽힌다. 색·간격은 기존 토큰만 쓴다. */
.grid thead a {
  display: inline-flex; align-items: baseline; gap: var(--s1);
  color: inherit; text-decoration: none; cursor: pointer;
  padding: 0 var(--s1); margin-left: calc(-1 * var(--s1)); border-radius: 4px;
}
/* ★ 머리글 바탕이 연네이비가 되었으므로 hover 바탕도 **흰색**으로 바꾼다 — 종전 값(연네이비)은
 *   이제 머리글 바탕과 같은 색이라 눌러도 아무 변화가 없는 것처럼 보인다. */
.grid thead a:hover { background: var(--bg); color: var(--accent-strong); }
/* ★ 정렬 표시는 **상태가 아니라 조작 안내**라 색을 쓰지 않는다 — 머리글과 같은 무채색을 쓰고,
 *   정렬 중이 아닌 열은 흐리게 둬 지금 어느 열로 정렬 중인지가 한눈에 갈린다.
 *   크기는 머리글 글자에 맞춘다(em) — 고정 px로 두면 글자 크기가 바뀔 때 혼자 커진다. */
.grid thead .arrow { font-size: .8em; color: inherit; opacity: .35; }
.grid thead th[aria-sort] a { color: var(--ink); }
.grid thead th[aria-sort] .arrow { opacity: 1; }

/* ── 사이드바 ─────────────────────────────────────────────────────────
 * 화면 왼쪽에 **떠 있는 네이비 캡슐**이다. 제목 줄 카드와 같은 표현(둥근 모서리·그림자)을 쓴다.
 * ★ 구조는 두 겹이다: `aside`는 **자리만 차지하는 보이지 않는 기둥**(본문을 오른쪽으로 밀어
 *   두는 역할)이고, 실제 캡슐은 그 안의 `nav`가 `position: fixed`로 화면에 붙는다.
 *   이렇게 나눠야 (가) 본문 위치가 종전과 한 픽셀도 달라지지 않고 (나) 스크롤해도 캡슐이
 *   제자리에 남는다. 기둥을 없애고 본문에 여백을 주는 방법은 넓은 화면에서 본문 폭·위치가
 *   달라진다(본문은 이번에 건드리지 않는다).
 */
body.app { display: flex; align-items: flex-start; }
.sidebar { flex: 0 0 180px; }
.sidebar nav {
  /* ★ 제목 줄 **아래 공간의 정중앙**이다(화면 전체 기준이 아니다) — 위를 제목 줄 아래로
   *   잡고 아래를 화면 끝으로 잡은 뒤 auto 여백에 맡긴다. 133px = 위 여백 16 + 제목 줄 높이 93
   *   + 아래 여백 24. 가운데 정렬이라 이 값이 몇 px 어긋나도 절반만 움직인다. */
  position: fixed; left: var(--s4); top: 133px; bottom: var(--s4);
  height: fit-content; margin: auto 0; width: fit-content;
  display: flex; flex-direction: column; gap: var(--s4);
  padding: var(--s4) var(--s3);
  background: var(--accent); border-radius: 28px;
  box-shadow: var(--shadow-float);
}

/* 항목 — 배경(둥근 사각형) · 글자 · 말풍선 세 겹을 겹쳐 놓는 상자 */
.nav-item {
  position: relative; display: flex; align-items: center; justify-content: center;
  min-height: 56px; min-width: 88px; padding: 0 var(--s3);
  color: var(--bg); text-decoration: none; text-align: center;
  transition: color .25s ease-out;
}
/* ★ 배경은 **처음부터 자리를 차지한 채** opacity로만 숨는다 — 나타날 때 항목 높이가 변하면
 *   메뉴 다섯 줄이 함께 들썩인다. 항목을 그대로 덮으므로(inset: 0) 크기를 따로 맞출 것이 없다. */
.nav-shape {
  position: absolute; inset: 0; border-radius: 14px;
  background: var(--bg); opacity: 0; transition: opacity .25s ease-out;
}
/* 글자·아이콘은 배경과 다른 요소라 배경이 바뀌어도 자리가 흔들리지 않는다 */
.nav-label, .nav-icon { position: relative; z-index: 1; }
.nav-icon { width: 1.4em; height: 1.4em; display: block; }
.nav-item:hover, .nav-item:focus-visible, .nav-item.current { color: var(--accent); }
.nav-item:hover .nav-shape,
.nav-item:focus-visible .nav-shape,
.nav-item.current .nav-shape { opacity: 1; }

/* 말풍선 — 캡슐 밖 오른쪽에 뜬다. `absolute`라 본문을 밀지 않는다.
 * ★ 현재 페이지라고 늘 떠 있지는 않는다(hover·키보드 초점일 때만) — 늘 떠 있으면 본문을 가린다. */
.nav-tip {
  position: absolute; left: 100%; top: 50%; margin-left: var(--s4);
  transform: translate(var(--s1), -50%); transform-origin: left center;
  padding: var(--s2) var(--s3); border-radius: 6px;
  background: var(--bg); color: var(--accent); font-size: 15px; font-weight: 700;
  width: max-content; max-width: 240px; text-align: left; word-break: keep-all;
  opacity: 0; pointer-events: none; z-index: 40;
  box-shadow: 0 2px 8px rgb(0 0 0 / .12);
  transition: opacity .25s ease-out, transform .25s ease-out;
}
/* 왼쪽 꼬리 — 삼각형은 테두리로 그린다(도형 파일을 따로 두지 않는다) */
.nav-tip::before {
  content: ""; position: absolute; right: 100%; top: 50%; transform: translateY(-50%);
  border: 6px solid transparent; border-right-color: var(--bg);
}
.nav-item:hover .nav-tip, .nav-item:focus-visible .nav-tip { opacity: 1; transform: translate(0, -50%); }

body.app > main { flex: 1 1 auto; min-width: 0; }

/* ── 로그인 화면 ───────────────────────────────────────────────────────
 * 네비게이션 없이 로고와 두 칸만 있는 단독 화면이다. */
body.login-page { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
.login-box { width: 360px; max-width: 100%; text-align: center; padding: var(--s5); }
/* 로고가 이 화면의 주인공이다 — 아래 글자는 무엇을 여는 곳인지 알려주는 보조 문구다.
   둘의 무게가 비슷하면 로고가 도드라지지 않는다. */
.login-logo { width: 100%; max-width: 300px; height: auto; margin-bottom: var(--s3); }
.login-box h1 { font-size: 16px; color: var(--ink-soft); font-weight: 400; margin: 0 0 var(--s5); }
.login-box form { display: flex; flex-direction: column; gap: var(--s3); text-align: left; }
.login-box .flash { text-align: left; }
.login-box .btn { margin-top: var(--s2); }

/* ── 홈 ──────────────────────────────────────────────────────────────
 * ★ 홈**만** 카드를 화면 세로 가운데에 둔다(body.home-page). 목록이 긴 화면을 가운데
 *   정렬하면 위쪽이 잘려 스크롤로도 닿지 않는다 — 사이드바 메뉴에서 겪은 것과 같은 함정이라
 *   `justify-content: center`가 아니라 auto 여백을 쓴다(내용이 길어지면 저절로 0이 된다).
 */
body.home-page { min-height: 100vh; display: flex; flex-direction: column; }
/* ★ `width: 100%`가 필요하다 — 세로 flex 상자 안에서 `margin: 0 auto`(가운데 정렬)는
 *   가로 늘리기(stretch)를 취소해 **본문이 글자 폭까지 쪼그라든다**(카드가 한 줄로 늘어섰다). */
body.home-page > main { flex: 1 1 auto; width: 100%; display: flex; flex-direction: column; }
/* 본문이 `.wrap` 안으로 들어갔으므로 세로 가운데 정렬도 그 상자가 이어받는다.
 * ★ `width: 100%`가 또 필요하다 — `.wrap`의 `margin: 0 auto`가 세로 flex 안에서는 가로 늘리기를
 *   취소해 **내용 폭까지 쪼그라든다**(카드에 보이는 글자가 제목뿐이라 폭이 눈에 띄게 줄었다). */
body.home-page .page-body { flex: 1 1 auto; width: 100%; display: flex; flex-direction: column; }

/* ★ 카드는 **윗줄 넷 + 아랫줄 설정 하나**다. 매달 하는 일(근태·급여·직원·계약서)과 가끔 하는
 *   설정이 줄로 갈린다(사이드바에서 설정을 따로 묶은 것과 같은 구분). 칸 수를 넷으로 못 박아야
 *   다섯 번째가 윗줄에 끼어들지 않는다 — `auto-fit`은 화면이 넓어지면 다섯을 한 줄에 세운다.
 * ★ 카드 묶음에 폭 상한을 둔다 — 본문이 1440까지 넓어진 뒤 그대로 4등분하면 카드가 340px가
 *   되어 정사각형에서 멀어진다. 상한을 두면 넓은 화면에서도 카드가 정사각형에 가깝게 남고,
 *   가로 가운데 정렬도 같은 `margin: auto`가 함께 해결한다(세로 가운데는 종전 그대로다). */
.home-links {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s3);
  width: 100%; max-width: 960px; margin: auto;
}
/* ★ 높이를 **고정**한다(칸 너비 약 230px과 비슷한 값). 호버에서 설명이 나타날 때 높이가
 *   변하면 카드 넉 장이 함께 흔들린다 — 커서를 옮길 때마다 화면이 들썩인다. */
.home-link {
  position: relative; height: 220px;
  /* 화면 바탕이 회색이 되었으므로 카드에 **흰 배경**을 준다 — 배경이 없으면 바탕과 같은 색이라
   * 테두리만 남아 카드로 읽히지 않는다(본문의 다른 흰 카드와 같은 처리다). */
  padding: var(--s4); border: 1px solid var(--line); border-radius: 6px; background: var(--bg);
  text-decoration: none; color: var(--ink);
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  text-align: center; overflow: hidden;
}
/* ★ 설명은 **흐름에서 빼서**(position: absolute) 카드 높이에 영향을 주지 않는다.
 *   `display` 토글이 아니라 opacity·transform으로만 숨겼다 보여주므로 나타날 때 다시 배치되는
 *   일이 없고, 제목도 평소에 정확히 카드 한가운데에 앉는다(설명이 흐름에 있으면 제목이 늘
 *   위쪽으로 밀려 "호버에서 위로 올라간다"가 성립하지 않는다).
 * 0.3초는 이 등장 동작에만 쓰는 값이다 — 색 전환(--t)과 성격이 다르다. */
.home-link .sub {
  position: absolute; left: var(--s4); right: var(--s4); top: 55%;
  font-size: 16px; opacity: 0; transform: translateY(6px);
  transition: opacity .3s ease-out, transform .3s ease-out;
}
.home-link:hover .sub { opacity: 1; transform: none; }
/* ── 홈 진입 애니메이션 ────────────────────────────────────────────────
 * 로그인 직후 홈이 갑자기 나타나던 것을 부드럽게 띄운다. **CSS만 쓴다.**
 * ★ 제목 줄(네이비 띠)은 **움직이지도 흐려지지도 않는다.** 화면 위쪽을 가득 채운 색면이
 *   올라오면 화면이 통째로 밀리는 것처럼 보인다. 띠는 처음부터 제자리·제 색으로 있고
 *   **그 안의 글자(제목·계정 영역)만** 자리에서 밝아진다(이동 없이 페이드만).
 * ★ 본문 카드만 아래에서 8px 떠오른다. 종전 12px는 진입 순간 내용이 화면 아래로 밀려
 *   **세로 스크롤바가 잠깐 생겼다 사라지면서 화면이 좌우로 흔들렸다.**
 *   ★ 한때 `html { scrollbar-gutter: stable }`로 막았으나 **되돌렸다** — 스크롤이 없는 화면에서도
 *     오른쪽 15px가 비어 띠 옆에 흰 단차가 남았다(전 화면에서 보였다). 흔들림의 원인은 넘침
 *     자체이므로, 이동 거리를 화면에 남는 여백 안(8px)으로 줄여 **애초에 넘치지 않게** 한다.
 *     카드는 `overflow: hidden`이라 안쪽 내용도 밖으로 새지 않는다.
 *     실제 창(스크롤바 15px)에서 확인: 진입 1.4초 동안 본문 폭이 한 값으로 고정되고
 *     세로 스크롤이 한 번도 생기지 않는다.
 * ★ 홈에만 건다(body.home-page). 목록 화면까지 걸면 조회할 때마다 표가 떠올라 거슬린다.
 * 타이밍: 띠 안 글자 0.35초(즉시) → 카드 0.15초 뒤부터 60ms씩 → 마지막 카드가 0.83초에 끝난다. */
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes rise-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
/* ★ 애니메이션은 **본문과 제목 글자에만** 건다. 제목 줄 카드와 사이드바 캡슐은 화면을 옮겨도
 *   같은 자리에 그대로 있어야 하므로 대상이 아니다(움직이면 화면 전체가 다시 그려지는 것처럼 보인다).
 *   계정 영역은 화면이 바뀌어도 내용이 같으므로 홈 진입에서만 밝아진다(매번 깜빡이면 거슬린다). */
.page-body { animation: rise-in .4s ease-out both; }
.page-head h1 { animation: fade-in .35s ease-out both; }
body.home-page .page-user { animation: fade-in .35s ease-out both; }
body.home-page .home-link { animation: rise-in .5s ease-out both; }
body.home-page .home-link:nth-child(1) { animation-delay: 150ms; }
body.home-page .home-link:nth-child(2) { animation-delay: 210ms; }
body.home-page .home-link:nth-child(3) { animation-delay: 270ms; }
body.home-page .home-link:nth-child(4) { animation-delay: 330ms; }

/* 좁은 화면에서는 한 줄에 둘 — 넷을 유지하면 카드가 글자보다 좁아진다.
 * ★ 이 규칙은 위 `.home-links`보다 **뒤**여야 한다(같은 굵기라 앞에 두면 진다). */
@media (max-width: 720px) {
  .home-links { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* 카드에 보이는 것은 **제목뿐**이다 — 크게 두고 카드 정중앙에 놓는다.
 * 색은 제목에만 준다(테두리·바탕까지 물들이면 넉 장이 전부 네이비가 되어 시끄럽다). */
.home-link strong { font-size: 30px; color: var(--accent); transition: transform .3s ease-out; }
/* 커서를 올린 카드만 통째로 주색이 된다 — 어느 카드를 고르는지가 한눈에 보인다.
 * 안의 글자는 색을 따로 주지 않고 상속시킨다(제목·설명이 따로 놀지 않게). */
.home-link:hover { background: var(--accent); border-color: var(--accent); color: var(--bg); }
.home-link:hover strong, .home-link:hover .sub { color: inherit; }
.home-link:hover strong { transform: translateY(-16px); }   /* 설명이 들어갈 자리를 내준다 */

@media (max-width: 900px) {
  /* 좁은 화면에서는 세로 사이드바가 본문을 너무 좁힌다 — 가로 띠로 접는다 */
  body.app { display: block; }
  /* 캡슐을 화면 고정에서 풀어 **가로 막대**로 편다(기둥은 자리를 비운다) */
  .sidebar { flex: none; }
  .sidebar nav {
    position: static; height: auto; width: auto; margin: var(--s3) var(--s4) 0;
    flex-direction: row; flex-wrap: wrap; justify-content: center; gap: var(--s3);
    padding: var(--s2) var(--s3);
  }
  /* ★ 가로로 서면 항목 폭이 글자 폭까지 좁아져 **마름모끼리 겹친다** — 대각선(85px)만큼 벌린다 */
  .nav-item { min-width: 90px; }
  /* ★ 말풍선은 접힌 띠에서 끈다 — 오른쪽에 뜨면 바로 옆 항목을 가린다(세로일 때는 본문 여백이다) */
  .nav-tip { display: none; }
}

/* ── 제목 줄 (모든 화면 공통) ─────────────────────────────────────────
 * 제목·계정·로그아웃을 한 덩어리로 묶은 보라 띠. base.html이 한 곳에서 그린다.
 * ★ 띠는 **배경이 화면 끝까지, 글자는 본문 자리에서** 시작한다 — 띠 자체는 폭을 제한하지 않고
 *   안쪽 `.wrap`이 본문과 같은 상한·여백을 갖는다. 종전에는 띠에 음수 여백을 줘 본문 폭(1080)
 *   만큼만 칠했더니 넓은 화면에서 양옆이 잘린 것처럼 보였다.
 * ★ 띠가 사이드바까지 이어지지는 않는다. 사이드바는 화면 높이에 고정된 별도 기둥이라
 *   (sticky + 100vh) 그 위로 띠를 얹으려면 사이드바를 띠 아래로 내려야 하는데, 그러면
 *   `100vh`가 띠 높이만큼 넘쳐 **모든 화면에 늘 세로 스크롤바가 생긴다.** 사이드바를 그대로 두고
 *   본문 쪽을 화면 끝까지 채우는 편이 구조를 건드리지 않으면서 "잘려 보이는" 문제를 없앤다.
 */
/* ★ 화면 끝에 붙은 띠가 아니라 **떠 있는 카드**다. 위·오른쪽을 띄우고 네 모서리를 둥글게 한다.
 *   왼쪽은 띄우지 않는다 — 사이드바가 그 자리에 있고, 사이드바 쪽 기준은 이번에 건드리지 않는다.
 * ★ 그림자 색은 네이비를 **토큰에서 섞어 쓴다**(`color-mix`) — 같은 값을 두 곳에 적으면 주색을
 *   바꿀 때 그림자만 옛 색으로 남는다(보라 → 네이비 교체가 토큰 세 줄로 끝났던 이유다).
 * ★ 높이를 넉넉히 준다(안쪽 여백 16 → 24px, 약 1.25배) — 모서리가 둥글고 떠 있는 카드는
 *   높이가 낮으면 납작한 띠로 보인다. */
.page-head {
  background: var(--accent); color: var(--bg);
  margin: var(--s4) var(--s4) var(--s5); border-radius: 20px;
  box-shadow: var(--shadow-float);
}
.page-head .wrap {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--s3);
  padding-top: var(--s5); padding-bottom: var(--s5);
}
.page-head h1 { margin: 0; }
/* ★ 제목 줄은 **사이드바를 피하지 않는다** — 화면 좌우 여백 안쪽을 전부 쓰므로 전 화면에서
 *   폭이 같다. 본문은 기둥(180px)만큼 밀려 있으므로 그만큼 되돌려 화면 왼쪽 16px에 맞춘다.
 *   기둥이 없는 홈에서는 되돌릴 것이 없어 기본값(16px)이 그대로 맞는다. */
body.app .page-head { margin-left: calc(var(--s4) - 180px); }
/* 가로 띠로 접히면 기둥이 사라지므로 되돌린다 */
@media (max-width: 900px) { body.app .page-head { margin-left: var(--s4); } }
/* 좁은 화면에서는 띄우는 폭과 모서리를 줄인다 — 여백이 그대로면 본문 폭을 그만큼 더 깎는다 */
@media (max-width: 720px) {
  .page-head { margin: var(--s3) var(--s3) var(--s4); border-radius: 12px; }
  body.app .page-head { margin-left: var(--s3); }
  .page-head .wrap { padding-top: var(--s4); padding-bottom: var(--s4); }
}
/* 제목 안의 사번(.code)·부가 글자도 띠 위에서는 흰색이다 — 보조색(회색)은 보라 위에서 묻힌다 */
.page-head .code, .page-head .sub, .page-head a { color: var(--bg); }
/* 포커스 표시는 주색이라 보라 띠 위에서는 보이지 않는다 — 이 안에서만 흰 테두리로 바꾼다 */
.page-head :focus-visible { outline-color: var(--bg); }

/* ── 로그인한 계정·로그아웃 (모든 화면 공통) ───────────────────────────
 * 홈에만 있던 것을 base.html 한 곳으로 올려 **홈과 나머지 화면이 같은 자리·같은 모양**이 된다.
 *
 * ★ `float`인 이유: 본문 첫 요소가 화면마다 다르다(제목 / "← 돌아가기" 링크 / 알림 줄).
 *   띄우면 어느 경우든 **그 줄의 오른쪽 끝**에 붙고, 제목은 왼쪽에 그대로 남는다.
 *   제자리 배치(absolute)는 알림 줄이 뜬 날 그 위에 겹치고, 별도 행으로 두면 제목 위에
 *   빈 줄이 하나 더 생긴다.
 * ★ 제목과 함께 스크롤된다(화면 고정 아님) — 판단 근거는 커밋 메시지 참고.
 * 색·크기는 보조 정보 수준으로 둔다 — 제목과 무게를 다투면 안 된다. */
.page-user {
  display: flex; align-items: baseline; gap: var(--s2);
  font-size: 15px; color: var(--bg);
}
/* 띠 위의 로그아웃·설정 — 평소에는 흰 글씨, 커서를 올리면 다른 화면과 **같은 기준**
 * (연네이비 바탕 + 진한 네이비 글씨)이다. 흰 글씨를 조금 흐리게 하는 식으로 표현하면
 * 네이비 위에서 변화가 거의 보이지 않는다. */
/* ★ 바탕을 투명으로 되돌린다 — `.btn`의 기본 바탕이 **흰색**이라, 조용한 버튼은 흰 화면에서만
 *   투명해 보였을 뿐이다. 보라 띠 위에서는 흰 상자에 흰 글씨가 되어 글자가 통째로 사라졌다. */
.page-user .btn-quiet { background: transparent; border-color: transparent; color: var(--bg); }
.page-user .btn-quiet:hover { background: var(--accent-bg); color: var(--accent-strong); }
/* 톱니바퀴 — 글자 대신 아이콘 하나만 든 버튼.
 * ★ 크기를 px가 아니라 em으로 준다. 옆의 "로그아웃" 글자와 같은 기준으로 자라야 글자 크기를
 *   바꿨을 때 아이콘만 혼자 남는 일이 없다. 선 색은 `currentColor`라 글자색을 그대로 따라간다.
 * ★ 여백을 글자 버튼과 맞춰 두 버튼의 높이가 같아진다(아이콘만 작아 보이지 않게). */
/* ★ 높이는 옆의 "로그아웃" 버튼에 맞춰 늘린다(`align-self: stretch`) — 아이콘만 든 버튼은
 *   글자 버튼보다 6px 낮아, 커서를 올렸을 때 두 알약의 크기가 눈에 띄게 어긋난다.
 *   숫자를 적어 맞추면 글자 크기를 바꿀 때마다 다시 어긋난다. */
.icon-btn { display: inline-flex; align-items: center; align-self: stretch; padding: 0 var(--s2); }
.icon-btn svg { width: 1.15em; height: 1.15em; display: block; transition: transform var(--t); }
/* 커서를 올리면 톱니가 조금 돈다 — 색 변화(위 규칙)에 더하는 가벼운 반응이다 */
.icon-btn:hover svg { transform: rotate(30deg); }
