/**
 * 주짓수메이트 - Martial Arts Mastery UI System
 * 전문적이고 세련된 도장 관리 시스템
 *
 * ⚠ hover 규약 (2026-08-11 사용자 확정 A안) — 모든 `:hover` 규칙은 `@media (hover: hover)` 안에 둔다.
 *   마우스 없는 기기는 탭한 요소에 :hover 를 걸고 **다음 탭까지 유지**한다(sticky hover) — 무가드로 두면
 *   누르지도 않은 셀렉트·칩·버튼에 emerald 테두리가 남아 「선택됨」으로 읽힌다(실사용 신고).
 *   신규 hover 도 예외 없이 가드 안에. 무가드 `:hover` 재도입 ❌ (검사 = frontend-guide §28).
 */

/* ========================================
   1. DESIGN TOKENS - Color System
   ========================================
   Brand Mono (Strict): Emerald(var(--color-primary-600)) + Soft Dark(var(--color-slate-600)) 2색 기본.
   시맨틱 Error(#ef4444)는 Toast/Form error 등 최소 사용.
   본문 Warning/Info 토큰은 모두 Slate neutral 매핑(amber/indigo 폐지).
   Toast 는 --status-success/-error 2색만 사용 (--toast-* 토큰 2026-05 제거).
   2026-08-09 C안: Toast 면은 흰 면 + hairline — -bg tint 소비 종료, 상태색은 dot 한 점.
   ======================================== */
/* 다크테마 폐지 (2026-08-23 UI 감사 사용자 확정) — `[data-theme="dark"]` 2블록(중복 선언)을 제거했다.
   4개 layout 이 `data-theme=${theme ?: 'light'}` 를 내리지만 **theme 를 dark 로 만드는 경로가 코드에 없다**
   (서버 모델 속성 0 · JS 토글 0). 되살리려면 컨트롤러·토글 UI 부터 새로 만들어야 하므로 CSS 복원만으로는 ❌ */
/* `.section-header` 삭제 (2026-08-23 UI 감사) — 사용처는 랜딩 5페이지뿐인데 랜딩은 style.css 를
   로드하지 않는다(2026-08-12 링크 폐지, landing.css 가 자기 것을 소유). 재도입 ❌ */
/* 키오스크 레거시 3규칙 삭제 (2026-08-23 UI 감사) — `.attendance-clock`/`-date`/`-digit.filled`.
   클래스는 kiosk.html 에서 살아 있으나 이 선언들은 전부 사문이었다(page-local `.aurora-*` 가 !important 로 전부 덮음).
   남은 값이 폐지된 다크테마 전제(rgba(255,255,255,.05)·--text-tertiary)라 다른 화면이 재사용하면 흰 면 위 흰 글자가 된다.
   기본 스타일이 필요해지면 kiosk page-local 을 참조해 새로 세울 것 — 이 값 복원 ❌ */
:root {
  /* ── Brand Primary - Emerald ── */
  --color-primary-50: #ecfdf5;
  --color-primary-100: #d1fae5;
  --color-primary-200: #a7f3d0;
  --color-primary-300: #6ee7b7;
  --color-primary-400: #34d399;
  --color-primary-500: #10b981;
  --color-primary-600: #059669;
  --color-primary-700: #047857;
  --color-primary-800: #065f46;
  --color-primary-900: #064e3b;

  /* ── Brand Secondary - Emerald Light (Primary 와 동일, 호환 alias) ── */

  /* ── 이용권 뱃지 dot 4-shade 램프 SSOT (2026-07-04, 구 6곳 scoped 중복 통합) ──
     소비처: .badge-*::before · .detail-pass-type-chip--* · pass-extension .pt-badge ·
     payments/index .pay-type-badge · plan-form-modal.js TYPE_META. 램프 변경은 여기 한 곳만. */
  --pass-dot-period: var(--color-primary-300);
  --pass-dot-count: var(--color-primary-500);
  --pass-dot-weekly: var(--color-primary-700);
  --pass-dot-day: var(--color-primary-900);

  /* ── Brand Base - Soft Dark (Slate) ── */
  --color-slate-50:  #f8fafc;
  --color-slate-100: #f1f5f9;
  --color-slate-200: #e2e8f0;
  --color-slate-300: #cbd5e1;
  --color-slate-400: #94a3b8;
  --color-slate-500: #64748b;
  --color-slate-600: #475569;
  --color-slate-700: #334155;
  --color-slate-800: #1e293b;
  --color-slate-900: #0f172a;

  /* Level Colors - 띠 색상 팔레트 (slate 외엔 도메인 전용 hex) */

  /* Gradient - 수평 방향 (Emerald 톤) */

  /* Background Colors — 시맨틱 alias → palette ref */
  --bg-primary: #ffffff;
  --bg-secondary: var(--color-slate-50);
  --bg-tertiary: var(--color-slate-100);
  --bg-hover: #f0fdf4;

  /* Text Colors — 시맨틱 alias → palette ref */
  /* 주 잉크 = slate-900 단일 (2026-08-31 톤 수렴 — 구 slate-800): 값·제목이 900, 본문이 800 으로
     갈려 있었는데 두 단 차이가 위계로 읽히기엔 미세해 잡음이었다(사용처 800 계열 134 vs 900 계열 185 실측).
     landing.css :root 사본도 같은 값 — 한쪽만 바꾸기 ❌ (signup-complete 가 두 시트를 다 로드). 800 복귀 ❌ */
  --text-primary: var(--color-slate-900);
  --text-secondary: var(--color-slate-600);
  --text-tertiary: var(--color-slate-400);
  --text-inverse: #ffffff;

  /* Border Colors */
  --border-default: var(--color-slate-200);
  --border-subtle: rgba(0, 0, 0, 0.04);
  --border-strong: var(--color-slate-300);
  --border-hairline: #eaecef;  /* D.MONO CONCIERGE 카드/모달 1px 외곽선 */
  /* D.MONO 박제값 2종 — hairline 과 같은 등급인데 변수가 없어 24곳에 생짜 hex 로 흩어져 있던 것을
     승격 (2026-08-12 UI 감사). 값 불변 = 시각 변화 0. 램프 변경은 이제 여기 한 곳. */
  --border-divider: #f4f5f7;   /* 카드 헤더 divider · 카드 안쪽 행 선 (외곽 hairline 보다 옅음) */
  --bg-row-hover: #fafbfc;     /* 테이블 행 · 클릭 가능한 행 hover */

  /* auth 단일 컬럼 폭 (ADR 0025 카드리스) — 5곳 하드코딩 통합 (2026-07-30) */
  --auth-col-width: 460px;

  /* Accent - Primary (Emerald) — 시맨틱 alias → palette ref */
  --accent-primary: var(--color-primary-600);
  --accent-primary-hover: var(--color-primary-700);
  --accent-primary-subtle: rgba(5, 150, 105, 0.08);

  /* Status Colors — Emerald palette ref */
  /* 텍스트 신호 전용 토큰 — 흰 면 위 emerald-600 은 대비 3.77:1 로 AA 미달이라 700(5.48)로 내렸다
     (ADR 0495, 2026-08-26 사용자 확정 A안 · 시안 `brand-color-contrast`).
     사용처가 `color:` 2곳뿐이라 토큰째 바꿔도 «면»에는 안 닿는다 — 버튼 채움·칩 배경의 emerald-600 은 불변.
     600 복귀 ❌ */
  --status-success: var(--color-primary-700);

  /* Brand Mono Strict (2026-04 재설계, 2026-05 Toast 2색 정리):
     본문(뱃지/배너/모달/캘린더 등)에서는 amber/indigo 폐지 → Slate neutral.
     Toast 도 4색 → 2색 정리 (success+info=Emerald, error+warning=Red).
     "좋음/나쁨" 두 축. warning/info 클래스는 API 호환만 유지 — 색은 success/error 와 동일. */

  --status-error: #ef4444;
  --status-error-bg: #fef2f2;
  --status-error-border: #fee2e2;
  /* 구 지정 자리 = 「옅은 red 면(`--status-error-bg`) «위»의 글자」 — 그런데 §시맨틱 색이
     「red tint 면 = 앱 전체 0곳」으로 박제해 그 면이 «존재하지 않는다».
     2026-09-04 UI 감사에서 흰 면 위에 이 토큰을 쓰던 12곳을 [[0495]] 지정대로 `-ink` 로 옮겼고,
     **남은 유일한 자리 = hover 진하게**(`-ink` → 이 값. 3곳: `.card-action-link.danger:hover` ·
     `.att-late-badge:hover` · `.promo-unresolved-jump:hover`). 흰 면 «기본» 글자에 재도입 ❌ */
  --status-error-text: #991b1b;
  /* 흰 면 위 red 글자 전용 — `--status-error`(#ef4444)는 흰 면에서 3.76:1 로 AA 미달인데
     border-color·background·box-shadow 에도 쓰여(실측 11+2+3곳) 토큰째 못 바꾼다.
     그래서 «흰 면 글자»만 분리해 red-600(4.83:1) 로 내렸다 (ADR 0495 · 2026-08-26 사용자 확정 A안).
     ⚠ 세 red 토큰의 자리가 다르다 — 면·테두리 = `--status-error` / 옅은 면 위 글자 = `--status-error-text`
     / **흰 면 위 글자 = 이것**. 면에 이 토큰 쓰지 ❌ */
  --status-error-ink: #dc2626;


  /* Typography */
  --font-sans: 'Wanted Sans Variable', 'Wanted Sans', 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;

  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5rem;
  --text-3xl: 1.875rem;
  --text-4xl: 2.25rem;

  --font-normal: 400;
  --font-medium: 500;
  --font-semibold: 600;
  --font-bold: 700;
  --font-extrabold: 800;

  /* Spacing */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-8: 2rem;

  /* Border Radius - Clean Scale */
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-full: 9999px;

  /* Shadow Scale - 미니멀 */
  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.1);

  /* Transitions - 부드러운 */
  --transition-fast: 0.15s ease;
  --transition-base: 0.2s ease;
  --transition-smooth: 0.25s ease;

  /* Layout */
  --top-nav-height: 64px;
  /* 읽기 컬럼 상한 (구 --container-max 1400px 승계 — 값 동일 = 시각 회귀 0).
     이름이 바뀐 이유 = 소유 대상이 「페이지 컨테이너」에서 「본문 읽기 컬럼」으로 옮겨졌다.
     페이지는 이제 창 폭을 그대로 쓰고(.page-container max-width 없음), 이 값은 .page-content
     격자의 **첫 트랙**만 잡는다. 가로로 늘어나서 이득인 블록만 `.is-wide` 로 둘째 트랙까지 스팬한다. */
  --reading-max: 1400px;
  --content-padding: 24px; /* 32→24 (2026-06-15) — admin/system 바깥 여백 압축. page-container + top-nav 좌우 정렬 동시 적용 */
  --sidebar-width: 240px;
  --sidebar-header-height: 75px;

  /* Borders & Backgrounds */
  --border-light: #f1f5f9;
  --bg-card: #ffffff;

  /* Z-Index */
  --z-raised: 10;
  --z-dropdown: 100;
  --z-sticky: 200;
  --z-overlay: 300;
  --z-modal: 400;
  --z-toast: 500;

  /* ── Premium Redesign Tokens ── */

  /* Glass Morphism */

  /* Glow Effects Extended */

  /* Animation Tokens */
  --ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
  --ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
  --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);   /* 토스트·바텀시트 감속 (오버슈트 없음) */
  --ease-vt: cubic-bezier(0.32, 0.72, 0, 1);          /* View Transition push/pop (iOS 곡선, 값 박제) */
  --ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);   /* 강한 오버슈트 (transition-bounce 짝) */
  --duration-medium: 0.4s;
  --stagger-delay: 0.06s;

  /* Surface Colors (Glass layers) */

  /* Sidebar Premium */
  --sidebar-bg: #ffffff;
  --sidebar-border: #e5e7eb;

  /* Card Premium */
  --card-bg: #ffffff;
  --card-border: #e5e7eb;
}

/* ========================================
   1.1 DARK THEME - 출석체크 등 몰입형 페이지
   ======================================== */


/* ========================================
   2. Reset & Base
   ======================================== */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* hidden 속성은 display 를 지정하는 클래스(.btn flex 등)보다 항상 우선.
   author 클래스(.btn{display:inline-flex})가 UA [hidden]{display:none} 을 덮어쓰는 footgun 차단. */
[hidden] {
  display: none !important;
}

html {
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* 모바일 텍스트 자동 확대(iOS Safari text autosizing · Chrome Android font boosting) 차단.
     기본값 auto 는 블록 폭·텍스트 양으로 배율을 스스로 정하고, DOM 이 바뀌어 리플로우가 일어나면
     (스텝 전환·모달 open 시 body overflow:hidden 등) 배율을 다시 계산한다 → "버튼 눌렀더니 갑자기
     글자만 커짐". 우리 타이포는 전부 px 박제라 자동 배율은 이득 없이 위계만 깨뜨린다.
     100% (none ❌ — 사용자 확대 접근성 보존) */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* body 와 동일 배경 — 페이지 이동 시 html 기본 흰색이 잠깐 비쳐 번쩍이는 것 방지 */
  background: var(--bg-secondary);
  /* 스크롤바 자리 항상 확보 — 짧은(스크롤 없음)↔긴(스크롤 있음) 페이지 이동 시 스크롤바가
     생겼다 사라지며 본문이 가로로 출렁이는 점프 차단. (macOS 오버레이 스크롤바엔 무해, Windows/항상표시에 효과) */
  scrollbar-gutter: stable;
}

/* ============================================================
   안전영역 · 브라우저 바 토큰 (2026-09-14 사용자 확정 A안 · 시안 ios-install-keep-or-block)

   layout 들이 `viewport-fit=cover` 를 켠 순간 문서는 노치·홈 인디케이터 «아래»까지 깔린다.
   그 자리를 피하는 몫 중 **여러 화면이 같은 값을 읽어야 하는 둘**을 여기서 소유한다 —
   소비처마다 다시 조립하면 같은 값이 화면마다 갈린다(이 앱이 반복해서 없애 온 page-local 사본 패턴).
   단발 인셋(셸 자기 여백 등)은 소비처가 `env()` 를 직접 쓴다.

   ① `--stage-top` / `--stage-h` = 무대(common.js §1.4 OverlayStage)의 **위 인셋 반영판**.
      무대를 «직접» 지는 풀스크린 패널(top: var(--vvt) + height: var(--vvh))은 화면 맨 위에서
      시작하므로 cover 를 켜면 헤더(취소·저장)가 상태바 밑으로 들어간다. 배경 딤(`.modal-overlay`
      등)은 **그대로 `--vvt` 를 쓴다** — 딤까지 내리면 노치 띠만 안 덮인 채 남는다.
   ⚠️ **바텀네비를 브라우저 바 «위»로 띄우는 `--shell-lift` 는 넣지 않는다** ([[0733]] C안 = 보류) —
      iOS 26 의 하단 바는 콘텐츠 «위에 떠 있는» 플로팅이라 `visualViewport` 가 높이를 못 재는
      경우가 있고([[0635]] 가 같은 함정으로 한 번 틀렸다), 창이 뜨던 원인 자체는 [[0733]] 의
      앱 영역 로그인이 없앴다. 필요해지면 **실기기로 정확히 재서** 넣는다 — 추정값 ❌.
   ============================================================ */
:root {
  /* 무대가 «이미» 인셋 아래에서 시작했으면(키보드가 떠 화면이 밀려 올라간 경우 `--vvt` > 0)
     그만큼은 빼고 «모자란 몫»만 더한다 — 무조건 더하면 키보드가 뜬 순간 패널이 노치 높이만큼
     한 번 더 내려가 보이는 영역을 놓친다 */
  --stage-inset-top: max(0px, calc(env(safe-area-inset-top, 0px) - var(--vvt, 0px)));
  --stage-top: calc(var(--vvt, 0px) + var(--stage-inset-top));
  --stage-h: calc(var(--vvh, 100vh) - var(--stage-inset-top));
  /* ② `--shell-header-h` = **모바일 헤더의 실제 높이**. 베이스 56px 은 cover 를 켠 순간 거짓이 된다
     (헤더가 노치 몫을 자기 안에 받아 56 + 인셋이 된다). 헤더 «아래»에 붙는 sticky 툴바·저장바가
     상수 56px 을 쓰면 그 차이만큼 헤더 밑으로 파고든다 — 자리를 재는 쪽은 전부 이 토큰을 쓴다.
     `56px` 상수 복귀 ❌ (2026-09-14 사용자 확정 A안 · 시안 ios-install-keep-or-block) */
  --shell-header-h: calc(56px + env(safe-area-inset-top, 0px));
}

/* 가로 모드 좌우 인셋 — `viewport-fit=cover` 는 «옆»도 연다. 노치가 있는 폰을 눕히면 한쪽
   변 59px 이 카메라 컷아웃 밑으로 들어가므로 문서 본문이 그만큼 비켜 앉는다. 세로 모드·노치 없는
   기기는 인셋 0 이라 렌더 변화 0. 전폭 셸(바텀네비 등)은 면이 이어져야 해서 대상 ❌ (2026-09-14) */
@media (orientation: landscape) {
  body {
    padding-left: env(safe-area-inset-left, 0px);
    padding-right: env(safe-area-inset-right, 0px);
  }
}

/* ============================================================
   페이지 전환 — Cross-document View Transitions
   서버사이드 렌더(MPA) 네비게이션 = 전체 리로드 → 흰 화면 번쩍.
   지속 셸(사이드바·헤더·네비)은 제자리 고정, 본문만 부드럽게 전환.
   미지원 브라우저(Firefox 등)는 기존 즉시 이동으로 graceful degrade.
   ============================================================ */
@view-transition {
  navigation: auto;
}

/* 지속 셸 — 전환 그룹에서 분리(named)해 본문(root)에서 떼어냄.
   관리자: sidebar / mobile-header / mobile-bottom-nav
   회원:   top-nav / member-bottom-nav  (페이지당 각 1개 → 이름 충돌 없음) */
.sidebar           { view-transition-name: vt-admin-sidebar; }
.mobile-header     { view-transition-name: vt-mobile-header; }
.mobile-bottom-nav { view-transition-name: vt-mobile-nav; }
.top-nav           { view-transition-name: vt-member-topnav; }
.member-bottom-nav { view-transition-name: vt-member-nav; }

/* 드로어 모드(≤1024px)에선 사이드바를 지속 셸에서 제외 — 닫힌 드로어는
   transform: translateX(-100%) 로 화면 밖인데, 이름을 유지하면 스냅샷 그룹이
   뷰포트 왼쪽(0,0 240×844)에 잡혀 잉크(그림자 등)가 전환 내내 왼쪽 세로 띠로
   노출된다. 드로어는 화면 밖이라 '지속 셸 고정' 이득도 없음 — root 에 포함시켜
   자연스럽게 본문과 함께 전환 (2026-07-17 모바일 전환 회색 띠 버그) */
@media (max-width: 1024px) {
  .sidebar { view-transition-name: none; }
}

/* 회원 PWA 모바일(≤768px)에선 상단 바를 지속 셸에서 제외 — 세 탭의 상단 바가 서로 다르다:
   홈=.top-nav 64px / 내 정보=.mobile-header 56px(top-nav 숨김) / 결제=.pay-head(이름 없음).
   짝이 없는 쪽에서 animation:none 은 '고정'이 아니라 하드컷으로 작동했다 — 도착 바가 0ms 에
   통째로 즉시 등장하고, 떠나는 바는 320ms 얼어붙어 있다가 마지막 프레임에 소멸(상단 밴드만
   하드컷 2회). 게다가 본문 시작선이 56↔64px 라 슬라이드해 들어오는 본문이 8px 어긋났다.
   → 이름을 주지 않아 상단 바가 root 스냅샷에 포함 = 화면이 한 덩어리로 push/pop.
   iOS 와 같은 분담(네비 바는 화면과 함께 이동, 탭 바만 고정)이고, 이미 그렇게 동작하던
   .pay-head 에 세 탭이 정렬된다. 드로어 모드 .sidebar 와 같은 계열 예외.
   데스크톱은 세 페이지 모두 .top-nav 라 짝이 맞으므로 불변(즉시 스왑 유지).
   관리자(admin)는 .mobile-header 가 전 페이지 공통이라 대상 아님 — .member-portal 스코프 필수.
   (2026-08-11 사용자 확정 A안, 시안 static/mockups/member-tab-transition-motion.html) */
@media (max-width: 768px) {
  .member-portal .top-nav,
  .member-portal .mobile-header { view-transition-name: none; }
}

/* 섹션 탭 바(.tabs)는 지속 셸이 아니다 — 2026-08-04 잔상 수정으로 vt-section-tabs 폐지.
   구 설계(2026-06-23)는 "탭 클릭 = 페이지 이동" 전제로 탭 바를 사이드바처럼 고정했으나,
   ① payments/history 가 index 라디오 탭으로 흡수되며 라우팅 탭(<a class="tab-item">)이
      코드베이스에서 0개가 됐고(.tabs 4곳 전부 인페이지 radio 필터) 고정의 목적이 소멸
   ② 고정된 탭 바는 animation:none 으로 0ms 즉시 스왑돼, 본문(root)의 순차 페이드
      (110ms out → 150ms in)와 어긋나 **새 탭 바가 아직 살아있는 옛 본문 위에 겹쳐** 그려졌다
   ③ 양쪽 다 탭이 있으면(결제 2탭 → 이용권 5탭) 옛 스냅샷이 새 그룹 폭으로 늘어나
      4배 확대된 흐릿한 유령으로 전환 내내 남았다. 이를 막으려던
      ::view-transition-old(vt-section-tabs):not(:only-child){display:none} 은 실효 없음(실측).
   → 이름을 주지 않아 탭 바가 root 스냅샷에 자연 포함 = 본문과 같은 순차 페이드(겹침 0).
   라우팅 탭이 다시 생기면 섹션별 이름(vt-tabs-{section})으로 재도입할 것 — 전역 단일 이름 ❌. */

/* 셸은 애니메이션 없이 즉시 스왑 — 흔들림 없이 단단하게 고정.
   (active 메뉴 하이라이트만 즉시 바뀜) */
::view-transition-old(vt-admin-sidebar),
::view-transition-new(vt-admin-sidebar),
::view-transition-old(vt-mobile-header),
::view-transition-new(vt-mobile-header),
::view-transition-old(vt-mobile-nav),
::view-transition-new(vt-mobile-nav),
::view-transition-old(vt-member-topnav),
::view-transition-new(vt-member-topnav),
::view-transition-old(vt-member-nav),
::view-transition-new(vt-member-nav) {
  animation: none;
}

/* 본문(root) — 겹침 제로 순차 페이드. 셸(named)은 고정.
   이전 화면(old)이 '완전히' 사라진 뒤(110ms out)에야 새 화면(new)이 페이드인(110ms 지연).
   두 화면이 동시에 반투명으로 노출되는 구간이 0이라, 구조가 다른 두 페이지가
   함께 비치던 잔상(ghosting)을 '완화'가 아니라 원천 제거. 갭 순간은 html 배경=페이지색
   이라 흰 번쩍임 없이 페이지색 한 겹만 스침(즉시 스왑의 '탁' 거슬림도 페이드라 완화).
   (2026-07-06, 구 '겹침 최소화' 크로스페이드 supersede — 잔상 원천 제거) */
::view-transition-old(root) {
  animation: 110ms ease-in both vt-root-out;
}
::view-transition-new(root) {
  animation: 170ms var(--ease-out-quint) 110ms both vt-root-in;
}
@keyframes vt-root-out { to { opacity: 0; } }
/* 새 화면 = 페이드 + 8px 상향 착지 (ADR 0542, 2026-08-31 사용자 확정 — 구 「페이드만」 supersede).
   모바일 슬라이드 전환과 같은 가족의 절제판. 타입 미지정(reload·판정 불가)도 동일 —
   방향 슬라이드 타입이 얹히면 아래 모바일 규칙이 이긴다. 잔상 원천 제거(순차, 겹침 0)는 불변. */
@keyframes vt-root-in  { from { opacity: 0; transform: translateY(8px); } }

/* 모바일 방향 슬라이드 — iOS Push/Pop (2026-07-15, A안 사용자 확정)
   fragments/vt-direction.html 이 pageswap/pagereveal 에서 네비 방향을 감지해
   transition types(slide-forward/slide-back + tab-forward/tab-back)를 얹는다.
   매칭 selector 가 여기(모바일)뿐이라
   데스크톱·타입 미지정(reload·판정 불가)·미지원 브라우저는 위 순차 페이드 그대로.
   두 스냅샷 모두 불투명(body bg 포함)이라 크로스페이드 잔상 없음 — 잔상 원천 제거 설계 유지.
   mix-blend-mode: normal 필수 — UA 기본 plus-lighter 는 불투명 두 장이 겹치면 백화(白化). */
@media (max-width: 768px) {
  /* 앞으로(push) — 새 화면이 오른쪽에서 시트처럼 진입, 이전 화면은 28% 물러나며 디밍.
     셸 네비(tab-forward)도 동일 push — 전 구간 push/pop 단일 문법 (2026-07-16 사용자 확정
     A안, ADR 0054 — 구 "셸 네비=30px 마이크로 슬라이드" ADR 0052 supersede.
     tab-* 타입 이름은 vt-direction 판정·data-vt-dir 힌트 호환 위해 유지, 모션만 동일). */
  html:active-view-transition-type(slide-forward)::view-transition-old(root),
  html:active-view-transition-type(tab-forward)::view-transition-old(root) {
    animation: 340ms var(--ease-vt) both vt-push-recede;
    mix-blend-mode: normal;
  }
  html:active-view-transition-type(slide-forward)::view-transition-new(root),
  html:active-view-transition-type(tab-forward)::view-transition-new(root) {
    animation: 340ms var(--ease-vt) both vt-push-enter;
    mix-blend-mode: normal;
    box-shadow: -10px 0 28px rgba(15, 23, 42, 0.16);
  }
  /* 뒤로(traverse back·tab-back) — 현재 화면이 오른쪽으로 퇴장, 이전 화면이 복귀 (pop).
     z-index: 나가는 화면(old)이 위 — 기본 페인트 순서(new 위)의 역전 필요 */
  html:active-view-transition-type(slide-back)::view-transition-old(root),
  html:active-view-transition-type(tab-back)::view-transition-old(root) {
    animation: 320ms var(--ease-vt) both vt-pop-exit;
    mix-blend-mode: normal;
    z-index: 1;
    box-shadow: -10px 0 28px rgba(15, 23, 42, 0.16);
  }
  html:active-view-transition-type(slide-back)::view-transition-new(root),
  html:active-view-transition-type(tab-back)::view-transition-new(root) {
    animation: 320ms var(--ease-vt) both vt-pop-return;
    mix-blend-mode: normal;
  }
}
@keyframes vt-push-enter  { from { transform: translateX(100%); } }
@keyframes vt-push-recede { to   { transform: translateX(-28%); filter: brightness(0.92); } }
@keyframes vt-pop-exit    { to   { transform: translateX(100%); } }
@keyframes vt-pop-return  { from { transform: translateX(-28%); filter: brightness(0.92); } }

/* 모션 최소화 설정 — 전환 애니메이션 전부 끄고 즉시 스왑 */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* ============================================================
   스와이프 뒤로가기 — 손가락 추종 레이어 (2026-07-19)
   제어 = static/js/swipe-back.js · 스냅샷 = fragments/swipe-back.html
   값(-28% · brightness .92 · 320ms · cubic-bezier · box-shadow)은
   위 pop 모션(vt-pop-return/exit)과 동일 — 탭 이동과 손 이동이 같아 보여야 한다.
   ============================================================ */
.swipe-back-layer {
  position: fixed;
  left: 0;
  top: 0;
  /* top/width/height = JS 가 레이아웃 뷰포트 실측으로 지정 (셸 높이만큼 인셋).
     left+right 로 늘리지 않는 이유 = position:fixed 기준 박스가 레이아웃 뷰포트와
     갈리는 환경이 있어(본문과 좌표계 불일치) 픽셀로 못 박는다. */
  z-index: 150;              /* 셸(--z-sticky 200) 아래, 본문 위 */
  overflow: hidden;          /* iframe 의 셸 영역을 잘라낸다 */
  visibility: hidden;
  pointer-events: none;
  transform: translateX(-28%);
  filter: brightness(0.92);
  will-change: transform, filter;
  background: var(--bg-primary, #fff);
}
.swipe-back-layer.is-visible { visibility: visible; }

.swipe-back-frame {
  position: absolute;
  left: 0;
  width: 100%;
  border: 0;
  display: block;
  background: var(--bg-primary, #fff);
  /* height/top = JS (원래 뷰포트 크기 그대로 렌더 후 셸만큼 끌어올림) */
}

/* 드래그 중인 현재 화면 — 레이어 위에서 오른쪽으로 퇴장 (pop 의 old 역할) */
html.swipe-back-active { overflow-x: hidden; }
html.swipe-back-active #main-content {
  position: relative;
  z-index: 160;
  will-change: transform;
  box-shadow: -10px 0 28px rgba(15, 23, 42, 0.16);
}

/* 회원 PWA 상단 바 = 본문과 한 덩어리로 퇴장 (ADR 0182 로 지속 셸에서 빠졌고, 제스처도 같은 문법 —
   transform 은 swipe-back.js `movingTopBar()` 가 본문과 같은 값으로 건다). 좌측 그림자를 함께 줘야
   본문 그림자가 상단 56px 에서 끊기지 않는다. admin 헤더는 대상 아님(전 페이지 공통 = 고정이 옳다). */
html.swipe-back-active body.member-portal .mobile-header,
html.swipe-back-active body.member-portal .top-nav {
  will-change: transform;
  box-shadow: -10px 0 28px rgba(15, 23, 42, 0.16);
}

/* 정착(커밋/취소) 구간에만 트랜지션 — 드래그 중엔 손가락에 즉시 붙어야 한다.
   시간은 swipe-back.js 의 SETTLE_MS 와 동일해야 함 */
.swipe-back-layer.is-settling,
html.swipe-back-settling #main-content,
html.swipe-back-settling body.member-portal .mobile-header,
html.swipe-back-settling body.member-portal .top-nav {
  transition: transform 320ms var(--ease-vt),
              filter 320ms var(--ease-vt);
}

@media (prefers-reduced-motion: reduce) {
  .swipe-back-layer.is-settling,
  html.swipe-back-settling #main-content,
  html.swipe-back-settling body.member-portal .mobile-header,
  html.swipe-back-settling body.member-portal .top-nav {
    transition: none;
  }
}

body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: var(--font-normal);
  line-height: 1.5;
  color: var(--text-primary);
  background: var(--bg-secondary);
  /* 화면 높이 = vh fallback + dvh override (2026-08-12 「헛스크롤」 제거).
     모바일에서 100vh = 주소창이 숨은 「큰 뷰포트」라, 주소창이 보이는 동안 문서가 화면보다
     60~90px 더 길어진다 → 콘텐츠가 짧아 스크롤할 게 없는 페이지(관원 프로필·결제·회원목록 등)
     에서도 "볼 것 없이 화면만 스르륵 내려가는" 헛스크롤이 항상 생김. dvh 는 현재 뷰포트를
     그대로 따라가 헛여백이 0. 아래 화면높이 컨테이너 전부 같은 규칙 (frontend-guide §모바일 시트 높이 동일 패턴). */
  min-height: 100vh;
  min-height: 100dvh;
  /* 한국어 줄바꿈 — 어절(공백) 단위로 줄바꿈하여 조사·의존명사가 분리되지 않도록.
     긴 영어 단어/URL은 overflow-wrap으로 컨테이너 넘침 방지. (frontend-guide §22) */
  word-break: keep-all;
  overflow-wrap: break-word;
}

a {
  color: var(--accent-primary);
  text-decoration: none !important;
}

a:focus,
a:active,
a:visited {
  text-decoration: none !important;
}
@media (hover: hover) {
  a:hover {
    text-decoration: none !important;
  }
}

@media (hover: hover) {
  a:hover {
    color: var(--accent-primary-hover);
  }
}

/* Screen reader only (a11y) — 시각적으로 숨기고 스크린리더에만 노출 */
.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;
}

button {
  font-family: inherit;
  cursor: pointer;
  border: none;
  background: none;
}

input, textarea, select {
  font-family: inherit;
  font-size: inherit;
}

img {
  max-width: 100%;
  height: auto;
  display: block;
}

/* 키보드 focus 박제 — 페이지 인라인 .X:focus { outline:none } 가 키보드 진입 시 덮어쓰지 못하도록
   접근성 보장. 마우스/터치 focus 는 :focus-visible 매칭 안 되어 영향 없음.
   ※ input/select/textarea 는 :focus-visible 휴리스틱이 마우스 클릭에도 matches 되어
   자체 :focus (border + box-shadow ring) 와 outline 이 동시에 표시되는 이중 테두리 버그 → 제외.
   텍스트 컨트롤의 a11y indicator 는 각 컨트롤 :focus 규칙(.form-input/.form-select/.form-textarea/
   .float-field input/.ios-item:has(:focus-visible) 등) 이 책임.
   ※ [tabindex] 는 input/textarea/select 와 .custom-select-trigger 를 명시 제외 — flatpickr 가
   date-picker input 에 tabindex="0" 을 주입해 [tabindex] 에 걸리면 자체 ring + outline 이
   동시에 표시되는 이중 테두리 회귀가 발생한다 (frontend-guide §27). */
button:focus-visible,
a:focus-visible,
[role="button"]:focus-visible,
[tabindex]:not(input):not(textarea):not(select):not(.custom-select-trigger):focus-visible {
  outline: 2px solid var(--accent-primary) !important;
  outline-offset: 2px;
}

/* 본문 앵커 안전판 — `#main-content` 는 swipe-back.js 의 transform 대상이자 common.js 의 스크롤 컨테이너
   폴백이다(포커스 타깃 아님). skip-link 는 미사용이고(ADR 0072) 고아 `tabindex="-1"` 도 2026-07-31 에
   6개 레이아웃에서 제거해 지금은 위 [tabindex] 규칙에 걸리지 않는다. 이 규칙은 누군가 스크립트로
   `.focus()` 를 걸거나 tabindex 를 되살렸을 때 본문 전체(수천 px)에 emerald 테두리가 그려지는 것만 막는다. */
#main-content:focus,
#main-content:focus-visible {
  outline: none !important;
}

/* 프로그램으로 «세워 두는» 포커스 자리 = 키보드 링 ❌ (2026-09-07)
   `Modal._focusFirst` 는 터치(≤768px)에서 텍스트 입력 자동 포커스가 키보드로 화면을 가리는 걸 피하려
   **패널**에 `tabindex="-1"` + `focus()` 를 건다. 탭으로는 갈 수 없는 자리라 「탭해서 여기 왔다」는
   링이 거짓 신호이고, 데스크톱은 애초에 이 경로를 안 탄다(첫 입력란/액션을 잡는다).
   바로 위 `#main-content` 와 ADR 0227 의 `#payHeadTitle` 이 이미 같은 답을 낸 자리의 **셋째 사례**다 —
   폰 풀스크린 모달에서는 그 2px 이 화면 네 변을 통째로 그린다(ADR 0627 대가 절이 「전 모달에 걸리는
   별건」으로 남겨 둔 그것 · 2026-09-07 문자 보내기 풀스크린 전환으로 표면화).
   포커스 «진입» 자체는 불변 — 스크린리더 낭독·Escape·포커스 복귀 경로는 그대로다.
   ⚠️ `[tabindex="-1"]` 을 **전역으로** 제외하지 ❌ — roving tabindex 위젯은 -1 자리로 키보드로 실제 이동한다.
   여기서 끄는 것은 «모달 패널»이라는 그 park 자리 하나뿐이다. */
.modal-overlay .modal[tabindex="-1"]:focus-visible,
.modal-overlay .modal-content[tabindex="-1"]:focus-visible,
.modal-overlay[tabindex="-1"]:focus-visible {
  outline: none !important;
}

/* ========================================
   3. Typography
   ======================================== */
h1, h2, h3, h4, h5, h6 {
  font-weight: var(--font-semibold);
  line-height: 1.3;
  color: var(--text-primary);
}

h1, .h1 { font-size: var(--text-4xl); }
h2, .h2 { font-size: var(--text-3xl); }
h3, .h3 { font-size: var(--text-2xl); }
h4, .h4 { font-size: var(--text-xl); }
h5 { font-size: var(--text-lg); }
h6, .h6 { font-size: var(--text-base); }








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












/* ========================================
   4. Layout
   ======================================== */


.main-content {
  flex: 1;
  margin-left: var(--sidebar-width);
  /* min-height 제거 — §102 와 동일 이유 (짧은 페이지 하단 빈 공백 차단). bg 는 §102 가 책임. */
  background: var(--bg-secondary);
}

/* 본문 = 「읽기 컬럼 + 남는 폭」 2트랙 격자 (2026-08-23 사용자 확정 B안, 시안 desktop-wide-page-width).
   구 `max-width: 1400px` 단일 캡은 27인치(2560)에서 오른쪽 920px 을 통째로 죽였고, 그렇다고 캡을
   그냥 풀면 앱의 지배적 어휘인 「라벨 좌 · 값 우」 행(ios-item·내 계정 요약·구독 내역)이 2200px 로
   벌어져 라벨과 값이 마주 보지 않는다 (실측 스크린샷 근거).

   → 기본 자식은 첫 트랙(= 오늘과 같은 1400px)에 그대로 앉고, **가로로 늘어나서 이득인 블록만**
   `.is-wide` 로 둘째 트랙까지 스팬한다. 첫 트랙 상한이 구 캡과 같은 값이라 `.is-wide` 를 안 붙인
   모든 화면의 렌더는 **바이트 단위로 동일**(1440px 10페이지 기하 대조 = 변화 0).

   ⚠️ `max-width` 로 자식을 캡하는 구현 ❌ — `.page-content` 직계에 `position: fixed` 모달 오버레이가
   흔하다(관원 목록 5개·설정 4개). max-width 는 out-of-flow 여부와 무관하게 걸려 `inset: 0` 오버레이를
   1400px 로 줄인다. 격자는 fixed 자식을 아이템으로 잡지 않아 이 함정이 구조적으로 없다.
   ⚠️ `grid-template-columns` 를 `1fr 1fr` 류로 바꾸기 ❌ — 첫 트랙은 상한이지 비율이 아니다. */
.page-content {
  /* padding 제거 — page-container가 이미 padding 책임 (이중 padding 방지) */
  margin: 0;
}

/* ⚠️ 격자는 **데스크톱(≥769px) 한정** — 모바일 뷰포트는 늘 읽기 컬럼보다 좁아 격자가 줄 이득이 0인데,
   대가는 있다: 격자 아이템은 독립 서식 문맥이라 **자식의 margin 이 밖으로 collapse 되지 않는다**.
   `.page-container` 모바일 padding 을 음수 마진으로 상쇄하는 풀블리드 요소(members/list
   `.mobile-member-search` `margin: -12px -16px 0` · §모바일 첫 콘텐츠 시작선의 「상쇄값 동기 의무」)가
   그 collapse 에 기대고 있어, 전역으로 켜면 관원 목록 모바일이 12px 아래로 밀렸다 (2026-08-23 실측).
   미디어쿼리 제거 ❌ · 모바일까지 확대 ❌ */
@media (min-width: 769px) {
  .page-content {
    display: grid;
    grid-template-columns: minmax(0, var(--reading-max)) minmax(0, 1fr);
    align-content: start;
  }

  .page-content > * {
    grid-column: 1;
  }

  /* 전폭 옵트인 — 「칸이 넓어지면 더 많이 보이는」 블록만. 붙이기 전 기준:
     ① 폭을 먹으면 정보가 늘어나는가(표 컬럼·KPI 칸) → O
     ② 라벨 좌 · 값 우 행이거나 문단인가 → ❌ (넓어질수록 읽기 나빠진다)
     현재 사용처 5곳 = admin/dashboard `.home-glance`(KPI 밴드)·`.home-cards`(전폭 밴드 아래 2열 카드 래퍼) ·
     admin/members/list 데스크톱 표 래퍼 · fragments/member-bulk-register 루트(고정폭 2242px 표) ·
     admin/settings/index `.ios-settings-page`(남는 폭을 다단으로).
     결제 확인·이용권 상품·출석 목록은 ①을 만족하지 않아 의도적으로 제외 (탭 전환 시 폭 출렁임 포함). */
  .page-content > .is-wide {
    grid-column: 1 / -1;
  }
}



/* 페이지 상단 저장 액션 (frontend-guide §21 단일 표준)
   admin 편집 페이지 데스크톱 전용 — breadcrumb 다음 / 카드 위에 위치.
   모바일은 .mobile-header-save (mobile-shell.js MobileHeaderAction) 가 헤더에 inject.

   사이드바 톱 메뉴 편집 페이지(settings/index.html 등):
   - 데스크톱: position:fixed top:var(--setup-banner-h,0) 로 setup-wrapper 바로 아래 고정
     (sidebar 우측에서 viewport 끝까지 stretch). 스크롤과 무관한 절대 고정.
   - 태블릿(769~1024px): position:sticky; top:var(--setup-banner-h,0) — mobile-header 미노출 구간.
   - 모바일(≤768px): position:fixed; top:calc(56px + var(--setup-banner-h,0)) — mobile-header + setup-wrapper 아래.
     sticky 가 환경(iOS Safari 등)에서 의도대로 안 붙는 케이스 박제 차단 + 스크롤해도 헤더 + 액션바 두 줄 항상 노출.
   .is-dirty 시 backdrop-filter + emerald accent 강조.

   ── setup-wrapper 와 수직 스택 (2026-05-14) ──
   .setup-wrapper(sticky top:0 z:20)와 같은 top:0 자리를 점유하면 데스크톱은 setup-wrapper 가,
   태블릿(action bar z:15 < setup-wrapper z:20)은 action bar 가 가려졌다. setup-guide.js 가
   실측 높이를 --setup-banner-h 로 박제하므로 top 을 그 값만큼 오프셋 → 둘 다 항상 노출.
   setup-wrapper 가 없거나 dismiss 된 페이지에서는 fallback 0 으로 기존 top:0 동작 유지. */
.ios-settings-actions {
  position: fixed;
  top: var(--setup-banner-h, 0px);
  left: var(--sidebar-width);
  right: 0;
  /* setup-wrapper(sticky, z:20)와 같은 paint level 에서 sticky 부착 순간 같이 repaint 되며
     1px 흔들리던 현상 차단 → z:30 으로 명확히 우위에 두고 자체 stacking context 분리.
     will-change: transform 은 의도와 반대로 GPU compositor sub-pixel jitter 를 유발하므로 사용 금지. */
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 10px 20px;
  background: #ffffff;
  border-bottom: 1px solid var(--border-light, #eef0f2);
  /* reveal-on-dirty(아래) 접힘 애니메이션 기준 — 펼친 상태의 상한값. */
  max-height: 200px;
  /* setup-wrapper 높이 변화(expand/collapse) 시 top 이 매끄럽게 따라가도록 트랜지션.
     스크롤 중 jitter 유발 없도록 짧고 가볍게. max-height/padding/opacity 는 dirty 토글 시에만
     변하므로 스크롤 jitter 와 무관. */
  transition: top 0.15s ease-out, max-height 0.28s ease, padding 0.28s ease,
    opacity 0.2s ease, border-color 0.28s ease, visibility 0s;
}
.ios-settings-actions .btn-primary { min-width: 88px; justify-content: center; }
.ios-settings-actions .btn-secondary { min-width: 88px; justify-content: center; }
/* 인페이지 푸터 compact 하한은 두 어휘 공통 (§액션 푸터 어휘 = 「btn-sm · min-width 88px · gap 8」).
   구현이 .ios-settings-actions 에만 있어, 같은 성격의 .card-footer-actions(일괄 등록 카드 푸터)는
   「취소」가 자연 폭 50px 로 서서 확정(96px)과 2배 가까이 벌어져 있었다 (2026-08-05 실측).
   문서↔구현 불일치 해소 — 두 인페이지 푸터가 같은 하한을 공유한다. */
.card-footer-actions .btn { min-width: 88px; justify-content: center; }

/* sidebar 접힘 시 좌측 offset 자동 보정 (.sidebar.collapsed + .main-content 패턴 따라가기) */


/* 태블릿(769~1024px): sticky top:--setup-banner-h — mobile-header 미노출 구간.
   setup-wrapper(sticky z:20) 바로 아래에 stick 하도록 top 오프셋 (없으면 0 fallback).
   z-index 는 setup-wrapper(z:20) 보다 위(30)로 두어 height 전환 race 시에도 가림 차단. */
@media (max-width: 1024px) {
  .ios-settings-actions {
    position: sticky;
    top: var(--setup-banner-h, 0px);
    left: auto;
    right: auto;
    padding: 10px 16px;
    margin: 0 -16px 16px;
    z-index: 30;
  }
}

/* 모바일(≤768px): position:fixed top:56px+--setup-banner-h — mobile-header(sticky 56px) +
   setup-wrapper(sticky top:56, 2026-05-15) 아래에 부착. 이전엔 setup-wrapper 가 static 이라
   스크롤 시 banner 가 사라지면서 action-bar 가 header 와 분리되어 빈 공간 발생 → setup-wrapper
   를 sticky 로 변경하여 항상 banner + action-bar 가 일관된 수직 스택 유지.
   sticky 가 일부 환경(iOS Safari + 긴 페이지 조합 등)에서 의도대로 안 붙는 케이스 박제 차단.
   본문 시작점은 .ios-settings-page mobile padding-top 으로 보정.
   z-index 는 mobile-header(--z-sticky 200) 보다 1단 낮춰 헤더 우선 노출. */
@media (max-width: 768px) {
  .ios-settings-actions {
    position: fixed;
    top: calc(var(--shell-header-h, 56px) + var(--setup-banner-h, 0px));
    left: 0;
    right: 0;
    margin: 0;
    padding: 8px 16px;
    z-index: 190;
  }
}

.ios-settings-status {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-right: auto;
  font-size: 13px;
  color: var(--text-tertiary);
  transition: color 0.2s ease;
  /* 상태 텍스트는 항상 1줄 — 2줄로 꺾이면 바 높이가 본문 padding-top 보정값을 초과해 첫 카드를 가림 */
  white-space: nowrap;
}
/* 모바일 축약 라벨 (2026-07-22, 1안 사용자 확정): 375px(iPhone SE)에서
   「저장되지 않은 변경사항」 + 취소·저장(min-width 88px×2)이 가용폭 초과 → 2줄 꺾임.
   모바일은 축약 스팬(「변경사항 있음」/「저장됨」)으로 스왑해 1줄 유지 — 바 높이가 본문
   보정값(settings/index.html 61px = 버튼 44px 터치 타깃 기준)과 일치.
   full/short 두 스팬은 settings/index.html _setActionBarDirty 가 함께 갱신. */
.ios-settings-status .status-text--short { display: none; }
@media (max-width: 768px) {
  .ios-settings-status .status-text--full { display: none; }
  .ios-settings-status .status-text--short { display: inline; }
}
.ios-settings-status .status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-slate-300);
  transition: background 0.2s ease, box-shadow 0.2s ease;
}

/* Dirty 상태 (settings/index.html JS 가 토글) — backdrop-filter + accent.
   position 은 평소와 동일(fixed/static) — 분기 없음. */
.ios-settings-actions.is-dirty {
  background: rgba(255, 255, 255, 0.92);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  backdrop-filter: saturate(180%) blur(12px);
  box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
  border-bottom-color: var(--color-slate-200);
}
.ios-settings-actions.is-dirty .ios-settings-status {
  color: var(--color-primary-700);
  font-weight: 600;
}
.ios-settings-actions.is-dirty .status-dot {
  background: var(--color-primary-500);
  box-shadow: 0 0 0 3px var(--color-primary-100);
}
/* Reveal-on-dirty (2026-06-01): status indicator 가 있는 dirty-aware 페이지는 clean 상태에서
   액션 바를 접어 숨기고, 변경 발생 시(.is-dirty) 에만 슬라이드인. 변경할 게 없을 때 상단을
   영구 점유하던 "떠 있는 빈 바" 위화감 제거. position(fixed/sticky)·jitter 차단값은 그대로 —
   가시성만 토글한다. status 없는 페이지(향후 단독 폼 등)는 기존대로 항상 노출.
   본문 예약 공간(padding-top)은 index.html .ios-settings-page:has(...is-dirty) 가 연동. */
.ios-settings-actions:has(.ios-settings-status):not(.is-dirty) {
  max-height: 0;
  padding-top: 0;
  padding-bottom: 0;
  margin-bottom: 0;
  border-bottom-color: transparent;
  opacity: 0;
  overflow: hidden;
  pointer-events: none;
  /* 접힌 바의 취소/저장이 Tab 포커스 정지로 남지 않게 (clean 상태 Enter 저장 차단).
     hidden 은 접힘 완료 후, 노출은 base 의 visibility 0s 가 즉시 뒤집음. */
  visibility: hidden;
  transition: top 0.15s ease-out, max-height 0.28s ease, padding 0.28s ease,
    opacity 0.2s ease, border-color 0.28s ease, visibility 0s linear 0.28s;
}

/* 저장 성공 순간만 접힘을 유예 — `.is-saved-flash` (settings/index `_flashSavedBar`, 1.6s).
   저장 성공 신호 = 저장 버튼 체크 morph 하나로 정리하면서(2026-08-16 사용자 확정 A안, 구 성공 Toast 폐지)
   이 규칙이 그 짝이 됐다: clearDirty 직후 그대로 접히면 **바가 300ms 만에 사라져 morph 를 아무도 못 본다**
   (실측 +150ms opacity 0.13 · +300ms 0). 접힘 유예를 지우면 저장 성공에 남는 신호가 0 이 된다.
   `:not(.is-dirty)` 유지 = 유예 중 다시 수정하면 dirty 어휘가 즉시 이긴다. */
.ios-settings-actions.is-saved-flash:has(.ios-settings-status):not(.is-dirty) {
  max-height: 200px;
  padding-top: 10px;
  padding-bottom: 10px;
  opacity: 1;
  visibility: visible;
  border-bottom-color: var(--border-light, #eef0f2);
}
/* 유예 중 좌측 상태(dot + 「사업자·정산 정보 저장됨」)는 숨긴다 — 버튼이 이미 「✓ 저장됨」이라
   같은 낱말이 한 바 안에 두 번 찍힌다(2026-08-16 실측 스크린샷). 신호는 누른 버튼 하나.
   `display:none` ❌ — 전역 접힘 규칙이 `:has(.ios-settings-status)` 로 이 요소의 존재를 본다. */
.ios-settings-actions.is-saved-flash:not(.is-dirty) .ios-settings-status {
  visibility: hidden;
}
/* 모바일 세로 padding 은 8px (위 ≤768px 블록) — 접힘 유예 규칙이 특이도로 이기므로 같은 값을 다시 준다.
   빼면 유예 1.6s 동안만 바가 4px 두꺼워져 본문 보정값(61px)과 어긋난다. */
@media (max-width: 768px) {
  .ios-settings-actions.is-saved-flash:has(.ios-settings-status):not(.is-dirty) {
    padding-top: 8px;
    padding-bottom: 8px;
  }
}

/* ========================================
   4.5. iOS Settings Row Pattern (Global)
   2026-05-15 settings/index, admins, pass-extension, settings/payment 4개 페이지
   인라인 정의를 통합. 페이지별 prefix(.px-page/.admins-page) 제거.
   D. MONO CONCIERGE — hairline + emerald accent 적용.
   ======================================== */

/* Group wrapper — 카드형 그룹 */
.ios-group {
  margin-bottom: 24px;
}
/* 그룹 라벨 = 마이크로 라벨 박제 톤 (12px/800/slate-500/0.04em — 2026-08-09 ②-A 정합, 구 600/0.05em 폐지) */
.ios-group-label {
  font-size: 12px;
  font-weight: 800;
  color: var(--color-slate-500);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0 16px;
  margin-bottom: 8px;
}
.ios-group-card {
  background: var(--bg-primary);
  border-radius: 14px;
  border: 1px solid var(--border-default);
  overflow: hidden;
}
.ios-group-hint {
  font-size: 12.5px;
  color: var(--color-slate-500);
  margin-top: 6px;
  padding: 0 16px;
  line-height: 1.55;
}
.ios-group-hint strong {
  font-weight: 700;
  color: var(--color-slate-700);
}

/* Row — 14px 18px hairline divider */
.ios-item {
  display: flex;
  align-items: center;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border-subtle);
}
.ios-item:last-child {
  border-bottom: none;
}
/* 모바일 — 좌우 16px + 터치 타깃 52px (2026-08-03 글로벌 승격: 구 settings/index·admins
   페이지 사본 2개 통합, 사본 없던 late.html 만 패딩이 갈리던 드리프트 해소) */
@media (max-width: 768px) {
  .ios-item {
    padding: 14px 16px;
    min-height: 52px;
  }
  /* 무면 인라인 입력이 행 높이를 다 먹게 — 히트영역만 확대, 시각 불변
     (2026-08-24 사용자 확정 A안, 시안 settings-row-input-hit-area).
     이 입력은 `border: none` + `background: transparent` + `padding: 0` 이라 박스 높이가
     **글자 한 줄(19px)** 그대로였다 — 위 행의 min-height 52px 이 「터치 타깃」으로 잡아 둔 높이 중
     **19px 만 실제로 눌렸고** 위 16 · 아래 17px 은 눌러도 아무 반응이 없었다(실측).
     투명 패딩으로 박스를 행 높이까지 키우고 같은 값의 음수 마진으로 상쇄한다 —
     글자 위치·행 높이·정렬 모두 불변(`.lt-class-add`·`.qs-mm-row-x` 히트박스 확대 idiom).
     ⚠ `input` 은 **대체 요소라 `::after` 오버레이가 렌더되지 않는다** — 다른 컨트롤에 쓰는
     오버레이 방식을 여기 그대로 옮기면 조용히 무동작이다. 이 자리는 padding + 음수 margin 이 유일한 수단.
     값 16/17 = 행 min-height 52 − 글자 19 = 33 을 반씩(16.5) 나눠 정수 픽셀로 떨어뜨린 것 —
     `.ios-item` 의 mobile min-height 가 바뀌면 이 두 줄도 같이 바꾼다.
     데스크톱은 min-height 가 없어(행 = 14+19+14) 확장 대상이 아니다 — 이 블록 밖으로 내보내기 ❌ */
  .ios-item-input {
    padding: 16px 0 17px;
    margin: -16px 0 -17px;
  }
}
.ios-item-clickable {
  cursor: pointer;
  transition: background 0.15s;
}
@media (hover: hover) {
  .ios-item-clickable:hover {
    background: var(--color-slate-50);
  }
}

/* Leading icon(.ios-item-icon 4색 패밀리) = 사용처 0 으로 삭제 (2026-08-09 초록 배경 정리) */

/* Label / Value — 좌측 14px medium, 우측 14px tertiary.
   행 focus(`.ios-item:has(:focus-visible)`) 시 weight 500 → 700 + color slate-900 (active 단서 보조). */
.ios-item-label {
  font-size: 14px;
  font-weight: 500;
  flex: 1;
  color: var(--text-primary);
  transition: font-weight 0.15s ease, color 0.15s ease;
}
.ios-item-value {
  font-size: 14px;
  /* slate-500 — 흰 면 slate-400 은 ≈2.85:1. 이 값은 행에서 「그 자리의 유일한 신호」(요약 수치·상태 값)라
     승격 대상 (2026-08-10 UI 감사 C안). placeholder·disabled(.ios-item-input.is-locked)는 「빈 칸」·「잠김」을
     색으로 전달하므로 slate-400 유지 — 함께 올리지 ❌ */
  color: var(--color-slate-500);
  margin-right: 8px;
}
/* 8px 는 「값 ↔ 뒤에 오는 컨트롤(셰브론 등)」 간격 — 값이 행의 마지막이면 죽은 여백이라
   그 행만 값이 8px 안쪽에서 끝나 우측 rail 이 둘로 갈린다 (late 「예외가 있는 관원 N명」 실측).
   pass-extension 이 page-local `margin-right: 0` 로 이미 우회하던 것을 전역 가드로 승격. */
.ios-item-value:last-child {
  margin-right: 0;
}

/* 텔레포트 행 셰브론 — 다른 페이지로 넘기는 ios-item 우측 끝 (설정 「지각 설정」 등).
   .ios-item 이 <a> 일 때 글로벌 `a { text-decoration: none }` 로 밑줄 없음 (§5). */
/* 행 끝 드릴다운 표지 — 「이 행을 누르면 넘어간다」 단일 원자 (ADR 0371, 2026-08-21 사용자 확정 A안 ·
   시안 chevron-unify). 구 `.ios-item-chevron`(설정 행 8곳) · `.q-row-chev` · `.qd-answer-chev` ·
   `.qa-chev` · `.pay-plan-chev` · `.history-chip-chev` 6벌 통합 — 같은 역할인데 기하 2계열(heroicons/lucide) ·
   크기 15/16/18 · stroke 2/2.5 · 색 slate-300/400 · hover 유무로 갈려 있었다. 「관원 상세」 한 페이지
   안에서만도 3종이 섞였다.
   아이콘 = `fragments/icons :: chevronRight(cls='row-chevron')` — 인라인 SVG ❌ · page-local 사본 ❌.
   크기·굵기는 여기서 결정(프래그먼트 폴백 2.25 를 CSS 가 이긴다). 15px·18px 복귀 ❌ */
.row-chevron {
  width: 16px;
  height: 16px;
  stroke-width: 2.5;
  color: var(--text-tertiary);
  flex-shrink: 0;
  transition: color 0.15s ease, transform 0.15s ease;
}
/* hover 반응(초록 + 2px 전진)은 **행이 트리거**라 행 클래스를 나열할 수밖에 없다 — 원자에 :hover 를
   달면 아이콘 위에 커서가 올라갔을 때만 반응해 「행 전체가 눌린다」는 신호와 어긋난다.
   드릴다운 행을 새로 만들면 이 목록에 추가한다(page-local hover 사본 ❌). */
@media (hover: hover) {
  .ios-item-clickable:hover .row-chevron,
  .q-row:hover .row-chevron,
  .qa-item:hover .row-chevron,
  .qd-answer:not(.is-deleted):hover .row-chevron,
  .history-chip--tappable:hover .row-chevron,
  .sys-row-clickable:hover .row-chevron,
  .qs-choice:hover .row-chevron,
  .pay-plan-card:hover .row-chevron,
  .mm-row:hover .row-chevron,
  .msg-config-btn:hover .row-chevron,
  .msg-banner--pending:hover .row-chevron,
  .msg-banner--notify:hover .row-chevron,
  .dash-att-row:hover .row-chevron,
  .auth-role-card:hover .row-chevron,
  .settlement-rejected-banner .srb-next:hover .row-chevron {
    color: var(--color-primary-600);
    transform: translateX(2px);
  }
}

/* 2-line stack — 제목 + 부제목 수직 */
/* `flex: 1` = 「행의 마지막 요소가 늘 행 padding(18px)에서 끝난다」 (project-frontend-guide
   §iOS Settings Row 「저장 배지는 트레일링 요소 앞 — 값이 우측 rail 을 지킨다」, 2026-08-19 사용자 확정).
   stack 이 남는 폭을 안 먹으면 셰브론·값·버튼이 **문장 끝에 붙어** 그 행만 rail 밖에서 끝난다
   (실측 2026-09-20: public-page 「시간표 고치기」 셰브론이 우측 rail 에서 383px 안쪽 · 같은 페이지
   「문의 버튼」 항상 켜짐 331px · settings 「도장 공개 페이지」 230px).
   구 상태 = `-toggle`·`-action` 변형에만 주고 맨 `.ios-item` 은 빠져 있어, 같은 결함을 만난 페이지가
   각자 사본을 만들었다(admins `#adminList` · messages `.msg-compose-entry` — 2026-09-20 글로벌로 흡수).
   page-local 사본 ❌ · `-toggle`/`-action` 한정으로 되돌리기 ❌ */
.ios-item-stack {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}
.ios-item-stack .title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-primary);
}
.ios-item-sub {
  font-size: 12px;
  /* §slate-400 승격 기준 「읽어야 하는 텍스트 = 그 자리의 유일한 신호」 — 부제 6곳(설정 4·지각 2)은
     전부 그 토글·행이 무엇을 하는지 말하는 유일한 문장. 대비 2.85 → 4.76:1. admins.html 이 같은
     역할을 이미 slate-500 으로 써 계열 안에서 두 벌로 갈려 있던 것을 통일 (2026-08-18 사용자 확정 B안,
     시안 ios-item-sub-contrast). slate-400 되돌리기 ❌ */
  color: var(--color-slate-500);
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

/* Trailing chevron */


/* Inline edit input — 우측 정렬 transparent.
   Focus indication 은 row(.ios-item) 의 좌측 rail 로 처리 (아래 `.ios-item:has(:focus-visible)` 참조).
   .is-invalid 의 좌측 red rail 과 동일 위치·두께로 일관 체계 — valid=slate-900 rail / invalid=red rail (색만 다름). */
.ios-item-input {
  border: none;
  text-align: right;
  font-size: 14px;
  color: var(--text-primary);
  outline: none;
  /* 라벨(flex:1) 대비 2배 — 긴 이메일·계좌번호가 잘리지 않도록 입력 영역에 더 넓은 비중 부여 */
  flex: 2;
  background: transparent;
  min-width: 0;
  font-family: inherit;
  transition: color 0.15s ease, font-weight 0.15s ease;
}
.ios-item-input:focus {
  color: var(--color-slate-900);
  font-weight: 600;
}

/* ── 숫자 + 단위 인라인 (설정 행 안 무면형) ─────────────────────────────
   2026-08-27 사용자 확정 A안 (시안 `numeric-unit-input-unify`) — 구 page-local 사본 2벌
   (`pass-extension .ios-days-wrap` · `settings/late .lt-window-wrap`) 승격. 두 사본은 값 토큰
   (입력 16/700 tabular · 단위 14/500)이 이미 같았고 `.lt-window-wrap` 주석이 스스로
   「선례 .ios-days-wrap 과 동일 — 세 번째 사용처가 생기면 승격 검토(SSOT), 그때 기준값이 한 벌이도록」
   이라 예고해 뒀다. 이름이 둘이라 한쪽을 고치면 다른 쪽이 조용히 뒤처지던 자리다.

   ⚠ 이 원자는 **자기 면을 갖지 않는다** — 몸통은 `.ios-item` 행이고 focus 신호도 행 좌측 rail 에
   위임한다(§27 단일 ring). 박스(면·테두리·radius)를 주면 설정 행 어휘가 깨지고 ring 이 둘이 된다.
   면이 필요한 자리는 `.stepper`(박스형) 쪽 — 판정 = 「설정 행 안인가, 폼·모달 안인가」.

   승격 시 수렴한 2축(구 사본이 갈려 있던 값): gap 4↔7 → **6** · 입력 폭 80↔64 → **64**
   (16/700 에서 3자리 + 여유. 우정렬이라 값 위치는 불변, 넓히면 단위와 사이가 벌어진다). */
.ios-item-input--with-unit {
  /* 라벨(flex:1) 대비 2배 — 글로벌 .ios-item-input 과 같은 비율 */
  flex: 2;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  min-width: 0;
}
.ios-item-input--with-unit input {
  width: 64px;
  border: none;
  background: transparent;
  text-align: right;
  padding: 0;
  outline: none;
  font-family: inherit;
  /* 16px = iOS 포커스 줌 방지 (frontend-guide §23) — 베어 input 이라 글로벌 `.form-input` 모바일
     강제가 안 닿는다. 이 원자가 직접 소유해야 한다. */
  font-size: 16px;
  font-weight: 700;
  /* 강조 숫자 = tabular-nums + slate-900 (D.MONO — emerald 는 신호 위치에만) */
  font-variant-numeric: tabular-nums;
  color: var(--color-slate-900);
}
/* 이 슬롯만 「강조 숫자」(16/700)라 placeholder 가 그 톤을 물려받아 입력된 값처럼 읽혔다
   (2026-08-06 UI 감사). 힌트는 형제 텍스트 필드와 같은 14/400 톤 — §필수·선택 표기
   「placeholder 는 형식·예시만」의 취지 = 값 자리를 차지하지 않기. */
.ios-item-input--with-unit input::placeholder {
  font-size: 14px;
  font-weight: 400;
}
.ios-item-input--with-unit .unit {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
}
/* 히트영역만 행 높이까지 — 글로벌 `.ios-item-input` 과 같은 처리·같은 값
   (2026-08-24 사용자 확정 A안, 시안 settings-row-input-hit-area). 근거·계산은 그 선언 위 주석 참조.
   ⚠ input 은 대체 요소라 `::after` 오버레이가 안 먹는다 — padding + 동값 음수 margin 이 유일한 수단. */
@media (max-width: 768px) {
  .ios-item-input--with-unit input {
    padding: 16px 0 17px;
    margin: -16px 0 -17px;
  }
}

/* Row focus — 좌측 2px slate-900 rail. 배경 tint 없음 (D.MONO active=slate-900 정책 일관).
   행 안의 focus 가 row 까지 전파. 라벨도 weight up.

   ⚠ `:focus-within` ❌ (ADR 0616 · 2026-09-05 사용자 신고 — member/pay 결제 세그먼트·`.selection-card--radio` 와 같은 결함):
   토글 스위치·행 우측 버튼·인라인 셀렉트를 «마우스로» 누르면 그 컨트롤이 포커스를 계속 쥐고 있어
   좌측 세로 rail 이 다른 곳을 누를 때까지 남는다 (신고 자리 = 관원 일괄 「알림 설정」 모달 `.optout-master`
   — 토글을 켜면 행 왼쪽에 세로선이 그어진 채 안 사라진다).
   `:has(:focus-visible)` = 텍스트 입력(`.ios-item-input`)은 :focus-visible 휴리스틱이 «마우스 클릭에도»
   matches 되어 rail 유지(§글로벌 키보드 focus 박제 주석), 토글·버튼·셀렉트는 키보드 탭에만 켜짐.
   선례 = `.pay-method-item` · `.selection-card--radio` · `.issuer-card` (frontend-guide §27).
   `.ios-item:focus-visible` 를 함께 두는 이유 = 행 자체가 `<a>`(`.ios-item-clickable`)인 진입형 행은
   포커스를 «자기»가 받아 `:has()` 로 안 잡힌다 (구 `:focus-within` 은 self 를 포함했다). */
.ios-item:focus-visible,
.ios-item:has(:focus-visible) {
  box-shadow: inset 2px 0 0 0 var(--color-slate-900);
}
.ios-item:focus-visible .ios-item-label,
.ios-item:has(:focus-visible) .ios-item-label {
  font-weight: 700;
  color: var(--color-slate-900);
}

/* Row invalid — 좌측 2px red rail (focus rail 과 같은 자리·두께, 색만 다름). FormValidator.is-invalid 발동 시.
   FormValidator 는 is-invalid 를 내부 input 에 부여하므로 :has() 로 row 까지 전파 (input event 시 자동 해제와 동기).
   :has(input) 범위 = .ios-item-input 뿐 아니라 숫자+단위 인라인 입력(.ios-item-input--with-unit) 등 row 내 모든 data-validate input. */
.ios-item.is-invalid,
.ios-item:has(input.is-invalid) {
  box-shadow: inset 2px 0 0 0 var(--status-error);
  animation: shakeInvalid 0.4s ease;
}
/* invalid + focus 동시 → invalid red rail 우선 (오류 상태가 활성 상태보다 시각 우선). 배경은 status-error-bg 미세 tint 유지(오류는 시각 무게 ↑). */
.ios-item.is-invalid:has(:focus-visible),
.ios-item:has(input.is-invalid):has(:focus-visible) {
  background: var(--status-error-bg);
  box-shadow: inset 2px 0 0 0 var(--status-error);
}
.ios-item.is-invalid:has(:focus-visible) .ios-item-label,
.ios-item:has(input.is-invalid):has(:focus-visible) .ios-item-label {
  font-weight: 700;
  color: var(--status-error);
}

/* Pill badge(.ios-item-badge) = 사용처 0 으로 삭제 (2026-08-09 초록 배경 정리) */

/* 가격 (15px 700 emerald) */


/* ===========================================================================
   Toggle switch — 44×24 iOS-style (글로벌 표준)
   ---------------------------------------------------------------------------
   사용처: 단일 on/off 의미의 모든 컨트롤 (checkbox 대체).
   다중 선택(요일·필터) 은 토글 금지 → 칩 패턴 사용.

   기본:        <input type="checkbox" class="toggle-switch">
   ios-item 안:  <input type="checkbox" class="ios-item-toggle-switch"> (alias)

   "토글 + 설명 + 의존 필드" 패턴은 .toggle-row 또는 .toggle-group 헬퍼 사용.
   ========================================================================== */
.toggle-switch,
.ios-item-toggle-switch {
  width: 44px; height: 24px;
  appearance: none; -webkit-appearance: none;
  background: var(--color-slate-300);
  border-radius: 12px;
  position: relative;
  cursor: pointer;
  transition: background 0.2s;
  margin: 0;
  flex-shrink: 0;
}
.toggle-switch::after,
.ios-item-toggle-switch::after {
  content: '';
  position: absolute;
  top: 2px; left: 2px;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: white;
  /* 스프링 바운스 (2026-07-16 모션 시스템) — knob 이 살짝 오버슈트 후 안착 */
  transition: transform 0.35s var(--ease-spring);
  box-shadow: 0 1px 2px rgba(0,0,0,0.15);
}
.toggle-switch:checked,
.ios-item-toggle-switch:checked { background: var(--color-primary-600); }
.toggle-switch:checked::after,
.ios-item-toggle-switch:checked::after { transform: translateX(20px); }
/* 잠긴 토글 — 트랙 색을 직접 지정 (§잠긴 값 칸 토큰과 같은 축, opacity ❌ — 구 .45).
   켜짐/꺼짐은 잠겨서도 읽혀야 하므로 두 상태를 각각 한 단계씩 죽인다
   (꺼짐 slate-100 / 켜짐 slate-300) — 한 값으로 합치면 「지금 켜져 있나」가 사라진다. */
.toggle-switch:disabled,
.ios-item-toggle-switch:disabled {
  background: var(--color-slate-100);
  cursor: not-allowed;
}
.toggle-switch:disabled:checked,
.ios-item-toggle-switch:disabled:checked { background: var(--color-slate-300); }
.toggle-switch:disabled::after,
.ios-item-toggle-switch:disabled::after { box-shadow: none; background: var(--color-slate-50); }

/* 히트영역 44px — 보이는 크기(44×24)는 그대로, 세로만 넓힌다 (2026-08-26 사용자 확정 A안 ·
   시안 `ios-toggle-touch-target`). 모바일 한정 — 데스크톱은 마우스라 24px 로 충분하다.
   ⚠ 이 자리는 히트박스 확대 idiom 의 전제 ①(「보이는 상자가 없을 것」, [[0317]])을 «깨는» 예외다 —
   스위치는 면·radius 를 가진 컨트롤이다. 그럼에도 쓰는 근거 = **면을 키우는 게 아니라 판정만 키우기**
   때문이고, 같은 성격의 선례가 `.toast-close::after`(보이는 24px 불변 + 히트 44px)다.
   ⚠ knob 은 `::after` 가 이미 쓰고 있으므로 확장은 **`::before`** 가 진다 — 둘을 바꿔 쓰지 ❌.
   대안이던 「행 전체 탭」은 미채택: 설명문을 짚기만 해도 서버 저장이 즉시 일어나고,
   같은 `.ios-item` 어휘의 토글 아닌 행(값 선택·링크)과 규칙이 갈린다. 재도입 ❌ */
@media (max-width: 768px) {
  .toggle-switch::before,
  .ios-item-toggle-switch::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    height: 44px;
  }
}

/* ios-item layout helpers (ios-group-card 안에서만 사용) */
.ios-item-toggle { gap: 12px; }
/* `display: flex` = 스위치를 감싸개의 인라인 라인박스에서 꺼낸다. 안 꺼내면 `<input>` 이
   `inline-block` + `vertical-align: baseline` 이라 baseline 에 앉고 그 «아래» 디센더 자리 7px 이
   감싸개에 더해져(24 → 31px) 스위치가 감싸개 중심보다 3.5px 위로 뜬다 — 행이 감싸개를 중앙 정렬해도
   «보이는 스위치»는 라벨 덩어리보다 3.5px 위였다(2026-09-05 UI 감사 R · 전 토글 14곳 예외 없이 동일 실측).
   결제 내역 상태 뱃지(`.detail-pay-amt-col .badge`, ADR 0587)와 같은 계열의 함정 — 그쪽은 뱃지가,
   여기는 스위치가 인라인이었다. `flex-shrink: 0` 유지 의무(라벨이 길어져도 스위치는 안 줄어든다). */
.ios-item-toggle-control { flex-shrink: 0; display: flex; align-items: center; }

/* ios-item 우측 액션 버튼 행 — stack(제목+설명) 좌 + 버튼 우 (토글 행과 동일 배치) */
.ios-item-action { gap: 12px; }
.ios-item-action .btn { flex-shrink: 0; }

/* ios-item 내부 인라인 select — row 우측 값 표시 + 펼침 (iOS Settings 패턴).
   §23 form-select 박제 룰에서 row inline 변형으로 명시 박제.
   사용: <div class="ios-item"><span>라벨</span><div class="ios-inline-select-wrapper"><select class="form-select">...</select></div></div> */
.ios-item .ios-inline-select-wrapper {
  flex: 1;
  min-width: 0;
  display: flex;
  justify-content: flex-end;
}
.ios-item .ios-inline-select-wrapper .custom-select-wrapper { width: auto; }
.ios-item .ios-inline-select-wrapper .custom-select-trigger {
  background: transparent;
  border: none;
  padding: 0 4px 0 0;
  height: auto;
  min-height: 0;
  color: var(--text-secondary);
  font-size: 14px;
  box-shadow: none;
  justify-content: flex-end;
}
/* 글로벌 트리거 링이 `:focus-visible` 로 옮겨졌으므로 중화 규칙도 같은 상태로 (안 맞추면 안 이긴다) */
.ios-item .ios-inline-select-wrapper .custom-select-trigger:focus,
.ios-item .ios-inline-select-wrapper .custom-select-trigger:focus-visible {
  background: transparent;
  box-shadow: none;
  color: var(--color-primary-600);
}
@media (hover: hover) {
  .ios-item .ios-inline-select-wrapper .custom-select-trigger:hover {
    background: transparent;
    box-shadow: none;
    color: var(--color-primary-600);
  }
}
.ios-item .ios-inline-select-wrapper .custom-select-value {
  color: inherit;
  font-weight: 500;
}
.ios-item .ios-inline-select-wrapper .custom-select-arrow { color: var(--color-slate-400); }
.ios-item .ios-inline-select-wrapper .custom-select-dropdown {
  min-width: 240px;
  right: 0;
  left: auto;
}

/* toggle-row — 카드 안 단독 라인 (텍스트 + 토글 좌우 분리).
   label.toggle-row 권장 — 라벨 클릭으로 토글 가능. */
.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  background: white;
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  cursor: pointer;
  user-select: none;
}
.toggle-row-text { min-width: 0; }
/* title·sub 는 <span> 이라 display 를 안 주면 inline 으로 이어져 「제목 부제」가 한 문장으로 읽히고
   (실측 390px), sub 의 margin-top 도 inline 에선 무효라 2줄 구조라는 원자 의도가 통째로 죽는다. */



/* toggle-group — 토글 head + 의존 fields 묶음 (product-modal pause-group 등) */
.toggle-group {
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  overflow: hidden;
}
.toggle-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  background: white;
  cursor: pointer;
  user-select: none;
}
.toggle-group-head-title {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: -0.01em;
}
.toggle-group-head-sub {
  font-size: 12.5px;
  color: var(--text-tertiary);
  margin-top: 2px;
  line-height: 1.5;
}
.toggle-group-fields {
  padding: 12px 14px 14px;
  border-top: 1px solid var(--border-hairline);
  background: var(--color-slate-50);
}
.toggle-group-fields .form-group:last-child { margin-bottom: 0; }

/* Section divider — 무면 마이크로 라벨, 카드 안에서 sub-section 구분.
   2026-08-09 사용자 확정 ②-A: 구 slate-50 채움 스트립 + 하단선 폐지 → 섹션 머리 어휘는
   무면 마이크로 라벨(12px/800/0.04em) + 여백 한 벌로 수렴 (회색면·선 재도입 ❌).
   pass-extension 의 .sub modifier 는 right-aligned 가 필요해 별도 변형. */
.ios-section-divider {
  padding: 22px 18px 8px;
  font-size: 12px;
  font-weight: 800;
  color: var(--color-slate-500);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.ios-section-divider:first-child {
  padding-top: 16px;
}
/* 모바일 좌우 16px — `.ios-item` 모바일 rail(14px 16px)과 같은 시작선 (2026-09-04 UI 감사 R):
   오버라이드가 없어 섹션 머리만 18 로 남아 바로 아래 행·각주보다 2px 오른쪽에서 시작했다.
   소비처(pass-extension·settings/index·settings/payment) 전부 같은 방향. page-local 사본 ❌ */
@media (max-width: 768px) {
  .ios-section-divider {
    padding-left: 16px;
    padding-right: 16px;
  }
}
/* .sub = slate-500 — 이 슬롯의 두 소비처가 「적용 N명 · 제외 M명」(pass-extension, `.history-count`
   동반)과 「— 토스 정산 심사 제출용」(settings/payment)이라 둘 다 «읽어야 하는 텍스트»다.
   구 `--text-tertiary`(slate-400)는 (0,2,0) 특이도로 `.history-count`(0,1,0) 의 slate-500 박제를
   이겨 「slate-400 복귀 ❌」를 무효화하고 있었다 (§slate-400 승격 기준 · [[0495]] · 2.85→4.76:1).
   같은 역할의 글로벌 `.card-subtitle` 도 slate-500. page-local 로 덮기 ❌ (두 소비처가 다시 갈린다). */
.ios-section-divider .sub {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 500;
  color: var(--color-slate-500);
  margin-left: 6px;
}

/* ========================================
   5. CLEAN LIGHT SIDEBAR — 삭제 (2026-08-31 사용자 확정, 보류 항목 정리)
   .sidebar 계열이 §5 / §100 / R4 세 벌로 갈려 있던 것을 §100 한 벌로 통합.
   §5 에서만 살아 있던 값(display:flex·overflow-y:auto·logo hover translateX·header
   justify-content 등)은 전부 §100 으로 이관 — 계산값 전후 diff 0 검증 완료. 재분산 ❌ */












/* 구 .nav-icon 삭제 (2026-08-31) — 소비처 0 (마지막 4곳 cls='nav-icon' 은 같은 날 감사에서 제거).
   opacity .9 가 같은 버튼 안 다른 아이콘과 밝기를 가르던 원인이기도 했다. 재도입 ❌ */











/* 구 §5 .sidebar-footer 삭제 (2026-08-31) — §100 통합본이 소유 */

/* Logout Button */
.btn-logout {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 12px;
  margin-top: 12px;
  background: rgba(239, 68, 68, 0.06);
  border: 1px solid rgba(239, 68, 68, 0.15);
  border-radius: 12px;
  color: var(--status-error);
  font-size: 14px;
  font-weight: var(--font-medium);
  transition: all var(--transition-smooth);
  cursor: pointer;
}

@media (hover: hover) {
  .btn-logout:hover {
    background: rgba(239, 68, 68, 0.12);
    border-color: rgba(239, 68, 68, 0.25);
    color: #b91c1c;
  }
}

/* Collapsed sidebar */











/* ========================================
   6. D. MONO CONCIERGE CARDS (2026-05-15, accent 갱신 2026-05-26)
   project-frontend-guide § D. MONO CONCIERGE
   - 1px slate-200 hairline (fingerprint = typography + 외곽 hairline)
   - shadow 제거
   - card-header 흰 배경 + .card-title 14px 800 (장식 없음)
   2026-05-26: 상단 emerald ::before accent 폐지 — §「Carded surface — color rail 일반 금지」
   ======================================== */
.card {
  background: var(--bg-primary);
  border-radius: 14px;
  box-shadow: none;
  border: 1px solid #eaecef;
  overflow: hidden;
  transition: border-color 0.2s ease;
  position: relative;
}

/* hover 리프트는 MOTION SYSTEM `body[data-motion] .card:hover`(-2px + hover 그림자)가 소유 —
   여기 base 에 리프트 사본을 두지 ❌ (ADR 0542 검증에서 항상 지는 죽은 규칙임을 실측, 2026-08-31) */
@media (hover: hover) {
  .card:hover {
    border-color: #d8dde3;
  }
}

.card-header {
  padding: 18px 22px 14px;
  border-bottom: 1px solid var(--border-divider);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  background: #ffffff;
  border-radius: 13px 13px 0 0;
}

/* frontend-guide §21 저장 액션 표준: 카드 헤더 우측 액션 그룹 */
.card-header .card-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

/* 카드 헤더 텔레포트 어휘 — 박스 없이 emerald 텍스트 + 화살표.
 * 사용 조건: 카드의 CTA 가 다른 페이지로의 텔레포트(href)이고 진짜 액션은 그 페이지가 owns 할 때.
 * 박스 어휘(btn-* btn-sm)는 같은 페이지에서 모달/상태전환을 owns 하는 카드 헤더에만.
 * fingerprint(폰트 12px / weight 800) = D.MONO 마이크로 라벨족 (kpi-label·sub-badge 11~11.5px/800)
 * 과 한 가족. title 13/700 → action 12/800 → badge 11/800 1px 계단형 위계로 헤더가 단일 meta strip 으로 읽힘.
 * emerald-600 컬러의 luminance 가벼움은 weight 800 으로 보정 (D.MONO §타이포 "800 = 라벨족" 원칙). */
.card-action-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 2px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.01em;
  /* 흰 면 위 12px 글자 → primary-700 (3.77 → 5.48:1, ADR 0495). 600 복귀 ❌ */
  color: var(--color-primary-700);
  text-decoration: none;
  border-radius: 4px;
  background: none;
  border: none;
  cursor: pointer;
  transition: color 0.15s ease;
}

/* 히트영역 44px — 보이는 크기(34)는 그대로, 세로만 넓힌다 (히트박스 확대 idiom [[0317]]).
   두 전제 충족: ① 보이는 상자가 없다(면 없음 · 테두리 0 — radius 4px 는 포커스 링 몫)
   ② 세로 확장이라 같은 줄 형제(카드 제목 span)와는 x 범위가 겹치지 않는다.
   확장분은 `.card-header`(61px) 안 — 헤더 높이 불변. 모바일 한정(데스크톱은 마우스). */
@media (max-width: 768px) {
  .card-action-link {
    position: relative;
  }
  .card-action-link::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    height: 44px;
  }
}

@media (hover: hover) {
  .card-action-link:hover {
    color: var(--color-primary-700);
  }
}
/* danger variant — BILLING_OVERDUE / RENEWAL_FAILED 등 위기 상태.
 * push 신호는 .danger-card 외곽 + .sub-badge.danger 가 책임, 액션은 한 차원만(red 텍스트). */
.card-action-link.danger {
  color: var(--status-error-ink);
}
@media (hover: hover) {
  .card-action-link.danger:hover {
    color: var(--status-error-text);
  }
}

/* 모바일 hit area 보강 — 시각 fingerprint 유지(font 12.5px) 하면서 탭 영역만 확장 */
@media (max-width: 768px) {
  .card-action-link {
    padding: 8px 6px;
  }
}

/* ── 사범 「관장님께 요청하기」 = 무면 텍스트 버튼 (2026-09-06 UI 감사 R · 값 소유자 여기 한 곳) ──
   목적지가 OWNER 전용이라 링크를 주면 빈손 착지하는 자리에서, 링크 대신 «요청 문구 복사»를 내주는
   어휘. 셋 다 같은 낱말·같은 행동·같은 성공 토스트(「관장님께 보낼 문구를 복사했어요.」)다 —
   ① 홈 정산 반려 배너 `.srb-ask` ② 홈 「다음 단계」 카드 `.next-step-card-ask`
   ③ 온보딩 허브 잠긴 줄 `.qs-hub-ask`.

   합친 이유 = **page-local 세 벌이 「한 벌」이라 적어 두고 값이 갈려 있었다** — ②만 13px·`padding: 0`
   이라 폰 히트 판정이 ≈16px 였고(①은 ≈31px), ③만 2026-08-31 감사에서 `::after` 44px 를 받아
   뒤에 생긴 ①② 는 그 처리를 못 받았다. 사범 세션의 «유일한» 조치 동선인데 폰에서 못 누르는 셈.
   §원자 승격 기준의 「3번째 사용처」 트리거를 넘겼고, 선례 = `.md-today-cta` 의 `.md-pb-btn` 흡수.

   면·테두리·알약 ❌ (행·배너 안 인라인 액션 어휘) · page-local 값 사본 재도입 ❌
   — 그 자리에만 필요한 차이(전폭 스택·좌측 padding 등)만 page-local 이 갖는다. */
.srb-ask,
.next-step-card-ask,
.qs-hub-ask {
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 800;
  color: var(--color-primary-700);
  white-space: nowrap;
  padding: 8px 0;
  text-align: left; /* `<button>` UA 기본 center — 전폭이 되는 순간 글자가 가운데로 밀린다 */
  flex-shrink: 0;
}
@media (hover: hover) {
  .srb-ask:hover,
  .next-step-card-ask:hover,
  .qs-hub-ask:hover { color: var(--color-primary-800); }
}
/* 모바일 히트영역 44px — 보이는 크기(≈31px)는 그대로, 세로만 넓힌다 (히트박스 확대 idiom [[0317]]).
   두 전제 충족: ① 보이는 상자가 없다(면·테두리 0) ② 셋 다 자기 줄에 단독으로 선다.
   확장분이 닿는 위쪽은 전부 «탭 대상이 아닌 글자»(각주·설명문)라 겹칠 판정이 없다([[0486]] 짝 규칙). */
@media (max-width: 768px) {
  .srb-ask,
  .next-step-card-ask,
  .qs-hub-ask {
    position: relative;
  }
  .srb-ask::after,
  .next-step-card-ask::after,
  .qs-hub-ask::after {
    content: '';
    position: absolute;
    inset: -7px 0; /* ≈31 → 45 */
  }
}

/* 2026-05-26 V2 Confident — 좌측 4×14 emerald 막대 폐지, typography 강화로 fingerprint.
   §「Carded surface — color rail 일반 금지」.
   기존: 13px 700 letter-spacing 0.02em + ::before 4×14 emerald
   현재: 14px 800 letter-spacing -0.01em (장식 없음)
   .card-title 은 더 이상 flex 컨테이너가 아니므로 옆 요소(sub-badge 등)는 부모 .card-title-row 에서 gap 으로 배치. */
.card-title {
  font-size: 14px;
  font-weight: 800;
  color: var(--color-slate-900);
  letter-spacing: -0.01em;
  margin: 0;
}

/* 카드 헤더 hero 변형 (2026-06-24) — 헤더에 액션 버튼(.card-actions)이 있어
   타이틀(14px)이 btn-sm(14px) 옆에서 작아 보이는 카드용.
   card-title 14→15px(버튼과 1px 계단)로 타이틀을 헤더의 주인으로 올리고, 헤더 하단 여백은 압축.
   (구 「secondary 13px 와 2px 계단」 서술은 .btn-sm 이 두 벌 선언되던 시절의 죽은 값 —
    2026-08-16 실측 정정. title 16px 로 2px 계단 "복원" ❌, project-frontend-guide §헤더 hero)
   액션 없는 정보 카드는 기본 14px 유지 — "헤더에 액션이 있으면 hero" 단일 규칙. */
.card-header.is-hero {
  padding: 15px 22px 8px;
}
.card-header.is-hero .card-title {
  font-size: 15px;
}

.card-body {
  padding: 18px 20px; /* 20px 22px→18px 20px (2026-06-15) — 카드 내부 여백 압축(V1 density). 모바일 16px override 유지 */
  overflow: visible;
}

.card-footer {
  padding: 14px 22px;
  border-top: 1px solid var(--border-divider);
  background: #ffffff;
  border-radius: 0 0 13px 13px;
}

/* frontend-guide §21 — 카드 푸터 액션 (D안).
   단일 컬럼 폼 페이지(members/form.html, member/profile.html)의 저장/취소 액션.
   (members/detail 은 폼+read-only 카드 다수라 §21 A안 상단 고정 바로 흡수 — 구 .card-footer--slim 폐지 2026-06-26)
   modal-footer 의 페이지 버전 — "위에서 채우고 아래에서 저장" 흐름.
   모바일은 헤더 인라인 액션을 쓰므로 .desktop-only 와 함께 사용. */
.card-footer-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}

/* D — 카드 헤더 보조 라인 */
.card-subtitle {
  font-size: 12.5px;
  color: var(--color-slate-500);
  margin-top: 4px;
  line-height: 1.5;
  font-weight: 500;
}

/* ========================================
   7. CLEAN STAT CARDS (Dojo Style)
   ======================================== */


/* Compact Stat Chips (Member Dashboard) */



















/* Stats List (Vertical Layout) */













/* ========================================
   7.5. BUTTON ICONS (인라인 SVG)
   ======================================== */
.btn svg,
.btn-icon svg {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  transition: all 0.2s ease;
}

/* Icon sizes in context */
.nav-link svg {
  width: 20px;
  height: 20px;
}

/* 구 `.btn-lg svg { 20px }` = 2026-08-16 삭제. 6개월간 **죽은 선언**이었다 — 뒤에 오는 `.btn svg`(18px)와
   특이도가 같아(0,1,1) 늘 밀렸고, `.btn-lg` 를 `.btn` 없이 쓰는 마크업은 0곳이라 이길 길이 없었다.
   btn-lg 아이콘의 실제·유일 소유자 = `.btn svg`(18px). 20px 로 되살리기 = 아이콘이 실제로 커지는
   시각 변경이므로 사용자 확정 없이 ❌ */

/* System logout button icons */


/* Mobile nav button icons */


/* Secondary button icon color */
.btn-secondary svg {
  color: var(--text-tertiary);
  transition: color 0.2s;
}

@media (hover: hover) {
  .btn-secondary:hover:not(:disabled) svg {
    color: var(--color-slate-700);
  }
}

/* Ghost button icon */
.btn-ghost svg {
  color: var(--text-secondary);
}

@media (hover: hover) {
  .btn-ghost:hover:not(:disabled) svg {
    color: var(--text-primary);
  }
}

/* Emoji in buttons */







/* Navigation emoji */





/* ========================================
   8. PREMIUM BUTTONS - Modern Minimal Style
   ======================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 22px;
  font-size: 14px;
  /* line-height 명시 = 태그별 높이 불일치 차단 (2026-08-05 실측).
     `<button>` 은 UA 가 `line-height: normal`(14px 기준 16px)을 주지만 `<a class="btn">` 은 본문
     line-height(1.5 → 21px)를 상속해, **같은 `btn-sm` 인데 39px ↔ 34px** 로 5px 어긋났다
     (관원 일괄 등록 푸터 「취소」(a) ↔ 「등록하기」(button) 실발현). 두 태그가 한 행에 섞이는 자리는
     앱 전역에 흩어져 있어 태그별 개별 보정 ❌ — 여기 한 곳에서 normal 로 고정한다.
     값이 `normal` 인 이유 = 현재 button 높이가 곧 기준선이라, 앵커만 그 위로 수렴하고 회귀가 0. */
  line-height: normal;
  font-weight: var(--font-bold);
  border-radius: 10px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: all 0.2s var(--ease-smooth);
  text-decoration: none;
  white-space: nowrap;
  position: relative;
  overflow: hidden;
  letter-spacing: -0.01em;
}

@media (hover: hover) {
  .btn:hover {
    text-decoration: none;
  }
}

/* Focus ring for accessibility */
.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.3);
}

.btn:disabled,
.btn[disabled] {
  background: transparent !important;
  color: var(--text-tertiary) !important;
  border-color: var(--border-hairline) !important;
  cursor: not-allowed !important;
  box-shadow: none !important;
  transform: none !important;
}

@media (hover: hover) {
  .btn:disabled:hover,
  .btn[disabled]:hover {
    background: transparent !important;
    transform: none !important;
    box-shadow: none !important;
  }
}

.btn:active:not(:disabled) {
  transform: scale(0.97);
}

.btn svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* btn-sm 아이콘 = 14px **단일 선언** (2026-08-16 중복 정리 — 구 3벌: §7.5 16 · 여기 16 · 구 §72 14).
   **자리가 곧 값이다** — `.btn svg`(18px)와 특이도가 같아(0,1,1) 반드시 그 뒤에 와야 이긴다.
   위쪽 §7.5 아이콘 블록으로 올리면 아이콘이 조용히 18px 로 커진다(2026-08-16 실측 회귀).
   위로 이동 ❌ · 재분산 ❌ */
.btn-sm svg {
  width: 14px;
  height: 14px;
}

/* Raised Hierarchy 버튼 (2026-06-23, Hollow supersede — ADR 0034)
   흰 면 + 얇은 hairline + 미세 그림자로 "눌리는 버튼" 어포던스 복원.
   primary/dark/danger = 흰 bg + tier 색 hairline + tier 색 글자 + raised shadow(+hover lift),
   secondary = 순수 텍스트(면·테두리·그림자 없음). hollow 가 잃은 "여기 눌러" 신호를 raised 로 보강.
   초록·어두운 배경 위 CTA 는 page-local 클래스(.md-pb-btn·.landing-btn-*) 라 영향 없음. */

/* Primary — 흰 면 + emerald hairline + emerald 글자 + raised */
.btn-primary {
  background: #fff;
  color: var(--color-primary-700);
  border: 1px solid var(--color-primary-200);
  font-weight: var(--font-extrabold);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06), 0 2px 6px -2px rgba(5, 150, 105, 0.18);
}

@media (hover: hover) {
  .btn-primary:hover:not(:disabled) {
    background: var(--color-primary-50);
    border-color: var(--color-primary-300);
    color: var(--color-primary-800);
    box-shadow: 0 2px 4px rgba(16, 24, 40, 0.08), 0 6px 14px -4px rgba(5, 150, 105, 0.28);
    transform: translateY(-1px);
  }
}

.btn-primary:active:not(:disabled) {
  transform: translateY(0) scale(0.98);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
}

.btn-primary:focus-visible {
  box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.3);
}

.btn-primary svg {
  color: var(--color-primary-600);
}

/* Secondary — 흰 면 + slate hairline + 미세 그림자 (raised 정합, 보조 액션)
   2026-07-06 개정 (ADR 0047, secondary=순수 텍스트 supersede): primary/dark 와 동일한
   "떠 있는 흰 면" 몸통 + 중립 slate 테두리·글자로 보조 tier 유지(저장=emerald 앵커).
   모달 푸터 취소/저장 등 한 쌍 액션의 비대칭(한쪽만 테두리) 해소. */
.btn-secondary {
  background: #fff;
  color: var(--color-slate-700);
  border: 1px solid var(--color-slate-200);
  font-weight: var(--font-bold);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
}

@media (hover: hover) {
  .btn-secondary:hover:not(:disabled) {
    border-color: var(--color-slate-300);
    color: var(--color-slate-900);
    background: #fff;
    box-shadow: 0 2px 6px rgba(16, 24, 40, 0.1);
    transform: translateY(-1px);
  }
}

.btn-secondary:active:not(:disabled) {
  background: #fff;
  transform: translateY(0) scale(0.98);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
}

.btn-secondary:focus-visible {
  box-shadow: 0 0 0 3px rgba(15, 23, 42, 0.12);
}

/* Dark — 흰 면 + slate hairline + slate-900 글자 + raised (D. MONO 3-tier 모노 강조 액션)
   용도: 도장 카드 "관리하기" · 토글 짝 · 주의 확정. project-frontend-guide § 버튼 3-tier */
.btn-dark {
  background: #fff;
  color: var(--color-slate-900);
  border: 1px solid var(--color-slate-200);
  font-weight: var(--font-extrabold);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
}

@media (hover: hover) {
  .btn-dark:hover:not(:disabled) {
    background: #fff;
    border-color: var(--color-slate-300);
    color: var(--color-slate-900);
    box-shadow: 0 2px 6px rgba(16, 24, 40, 0.1);
    transform: translateY(-1px);
  }
}

.btn-dark:active:not(:disabled) {
  transform: translateY(0) scale(0.98);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
}

.btn-dark:focus-visible {
  box-shadow: 0 0 0 3px rgba(15, 23, 42, 0.25);
}

.btn-dark svg {
  color: var(--color-slate-700);
}

/* Ghost - Minimal and clean */
.btn-ghost {
  background: transparent;
  color: var(--text-secondary);
  border: 1.5px solid transparent;
}

@media (hover: hover) {
  .btn-ghost:hover:not(:disabled) {
    background: var(--bg-secondary);
    color: var(--text-primary);
    border-color: var(--border-default);
  }
}

.btn-ghost:active:not(:disabled) {
  background: var(--bg-tertiary);
}

/* 2026-05-27: .btn-success 폐지 — templates 0건 사용 dead. D.MONO 3-tier
   (btn-primary / btn-dark / btn-secondary) 와 .btn-danger 만으로 충분. */

/* Danger — 흰 면 + red hairline + 빨강 글자 + raised */
.btn-danger {
  background: #fff;
  color: var(--status-error);
  border: 1px solid #fecaca;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
}

@media (hover: hover) {
  .btn-danger:hover:not(:disabled) {
    border-color: var(--status-error);
    background: var(--status-error-bg);
    box-shadow: 0 2px 6px rgba(239, 68, 68, 0.18);
    transform: translateY(-1px);
  }
}

.btn-danger:active:not(:disabled) {
  transform: translateY(0) scale(0.98);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
}

.btn-danger:focus-visible {
  box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.15);
}

.btn-full {
  width: 100%;
  margin-top: 8px;
}

/* Sizes - Refined proportions */
/* btn-sm = **여기가 단일 선언** (2026-08-16 중복 정리). 구 상태는 이 블록(padding 7/14 · 13px)과
   구 §72 BUTTON SIZES(padding 8/12 · var(--text-sm))가 같은 특이도로 겹쳐 **뒤엣것만 살아 있었다**
   — 실측 35px/14px 이 앱 전역의 진짜 값이고 이 블록의 13px 은 6개월째 죽은 값이었다(2026-02 리디자인
   잔재). 값은 실제 적용값을 그대로 승계 — 「원안 복원」(13px/7px14px)은 높이가 31.5px 이 돼
   frontend-guide §23 「-sm = 32~36px」 밴드를 벗어나므로 ❌. 재분산·두 벌 선언 ❌ */
.btn-sm {
  padding: 8px 12px;
  font-size: var(--text-sm);
  border-radius: 8px;
}

.btn-lg {
  padding: 14px 28px;
  font-size: 15px;
  font-weight: var(--font-extrabold);
  border-radius: 12px;
}


/* Block and Wide buttons */




/* Button Group - Clean spacing */


/* Loading state with spinner */
.btn-loading {
  position: relative;
  pointer-events: none;
}
/* ⚠ 라벨 숨김은 «`.btn` 을 붙여» 선언해야 한다 — `Loading.setButton` 이 `btn-loading` 과
   `disabled` 를 **항상 함께** 걸기 때문(common.js). 구 무스코프 `.btn-loading`(0,1,0)은
   `.btn:disabled`(0,2,0)의 `color: var(--text-tertiary) !important` 에 져서
   **라벨이 안 숨겨지고 스피너가 글자 위에 겹쳐 그려졌다** — 둘 다 !important 라 특이도가
   갈랐고, 실측 글자색이 투명이 아니라 slate-400 이었다(2026-08-28 사용자 확정 A안 ·
   시안 `btn-loading-label-overlap`). 호출처 173곳 전부에 발현하던 자리.
   무스코프로 되돌리기 ❌ · `!important` 제거 ❌(비활성 규칙이 !important 라 진다).
   ⚠ `.btn-loading` 선언이 이 파일에 2벌(여기 + §인라인 스피너)이라 스피너 치수·애니메이션은
   **뒤엣 선언이 이긴다**(16px·`spin`). 정리는 「어느 스피너가 맞는가」를 정하는 별건으로 보류. */
.btn.btn-loading {
  color: transparent !important;
}

.btn-loading::after {
  content: '';
  position: absolute;
  width: 18px;
  height: 18px;
  top: 50%;
  left: 50%;
  margin-left: -9px;
  margin-top: -9px;
  border: 2px solid transparent;
  border-top-color: currentColor;
  border-radius: 50%;
  animation: btn-spin 0.6s linear infinite;
}

/* Raised Hierarchy — 로딩 스피너는 tier 색(흰 면이라 흰 스피너 보이지 않음) */
.btn-primary.btn-loading::after {
  border-top-color: var(--color-primary-600);
}
.btn-danger.btn-loading::after {
  border-top-color: var(--status-error);
}
.btn-dark.btn-loading::after {
  border-top-color: var(--color-slate-900);
}
.btn-secondary.btn-loading::after {
  border-top-color: var(--color-slate-600);
}
/* tier 클래스가 없는 컨트롤 — 뒤엣 `.btn-loading::after`(§인라인 스피너)가 `border-top-color: transparent` 를
   덮어써서 네 변이 모두 투명해지고, 짝인 `color: transparent !important` 까지 걸려 **컨트롤이 통째로 빈다**.
   위 tier 4벌과 같은 방식으로 색 소유자만 명시한다 — `.btn-loading` 2벌 정리는 별건 보류 그대로.
   (`.md-id-switch` 는 자기 page-local 스타일이 같은 처방을 든다 — 2026-09-04 UI 감사) */
.pwa-row-destructive.btn-loading::after {
  border-top-color: var(--color-slate-500);
}

@keyframes btn-spin {
  to { transform: rotate(360deg); }
}

/* ========================================
   9. PREMIUM FORMS
   ======================================== */
.form-row-2col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.form-row-2col + .form-row-2col {
  margin-top: 14px;
}

@media (max-width: 768px) {
  .form-row-2col {
    grid-template-columns: 1fr;
    gap: 0;
  }
  /* row 사이 모바일 표준 8px (frontend-guide §24) */
  .form-row-2col + .form-row-2col,
  .form-row-2col + .form-group,
  .form-group + .form-row-2col {
    margin-top: 8px;
  }
  /* iOS Safari 자동 줌 방지 (input font-size < 16px 시 포커스 줌 발생) */
  .form-input,
  .form-input-sm,
  .form-textarea,
  .form-select,
  .float-field input {
    font-size: 16px !important;
  }
  /* 입력 컨트롤 높이 모바일 표준 48px (frontend-guide §23) */
  .form-input,
  .form-textarea,
  .form-select,
  .custom-select-trigger {
    padding: 13px 16px;
    min-height: 48px;
  }
  .form-textarea { min-height: 96px; }
  .form-select { padding-right: 44px; }
  /* 터치 타겟 44px+ (WCAG / Apple HIG 권장) */
  .btn {
    min-height: 44px;
  }
  /* 모바일 폼 간격 표준 (frontend-guide §24) — 이 블록이 소유하는 것은 `card-body 16` 하나뿐.
     `row gap 8` 은 위 `.form-row-2col + *` 묶음이 갖는다(베이스가 이 블록 «앞»이라 유효).
     ⚠ `form-group` · `form-label` 의 모바일 값을 여기 두지 ❌ — 베이스 선언(`.form-group` 아래 2행 /
     `.form-label` 그 아래)이 이 미디어 블록 «뒤»에 «같은 특이도»로 서 있어 조용히 덮인다. 구 `14px`/`6px`
     두 줄이 정확히 그렇게 죽어 있었고(빌드·렌더 에러 0), 폰에서도 데스크톱 값 24 / 10 이 서 왔다.
     2026-09-17 UI 감사에서 사용자가 «그 렌더값을 정답으로» 확정 → §24 표를 24 / 10 으로 갱신하고
     죽은 두 줄을 걷었다. 14 / 6 재도입 ❌ (되살리려면 베이스 «뒤»의 새 미디어 블록이어야 한다). */
  .card-body { padding: 16px; }
  /* `.form-group:last-child { margin-bottom: 0 }` 은 아래 전역 선언이 담당 (데스크톱·모바일 공통) */
}

.form-group {
  margin-bottom: 24px;
  overflow: visible;
  position: relative;
}

/* 마지막 폼 그룹의 아래 margin = 죽은 여백 — 바닥 간격은 컨테이너 padding 이 소유한다 (§24 표: 데스크톱·모바일 둘 다 0).
   구현이 모바일 미디어쿼리 안에만 있어 데스크톱 카드·폼 10곳이 24 + 컨테이너 24 = 48px, 위(24)의 두 배로 벌어져 있었다.
   2열 격자 셀에도 걸리지만 행 높이는 더 큰 칸이 정하고 데스크톱에선 셀의 아래 margin 이 「행 ↔ 다음 요소」 간격을
   담당하므로(왼쪽 칸 유지) 시각 회귀는 없다 — 셀만 예외로 되살리지 ❌ (규칙이 둘로 갈린다). */
.form-group:last-child {
  margin-bottom: 0;
}

.form-label {
  display: block;
  font-size: 14px;
  font-weight: var(--font-bold);
  color: var(--text-primary);
  margin-bottom: 10px;
  letter-spacing: -0.2px;
}

/* 필수 신호 SSOT = 라벨 옆 <span class="required">*</span> 하나 (§필수·선택 표기).
   2026-08-06 UI 감사: .ios-item-label 추가 — 설정 행 계열이 「무표기」로 굳어 필수 필드가
   선택으로 읽히던 것을 계열 무관 한 벌로 통일. 스코프를 계열별로 나열하는 이유 =
   .required 를 무스코프 전역으로 풀면 모디파이어로 쓰는 자리(signup-complete
   .sc-terms-row-tag.required 등)의 자체 톤까지 덮는다. */
.form-label .required,
.ios-item-label .required,
.td-gift-label .required,   /* system 도장 상세 구독 무료 연장 (2026-08-07 UI 감사) */
.float-field .required {    /* auth 폼 (2026-08-16 UI 감사 — 구 §float-field 색만 있던 중복 선언 흡수) */
  /* 흰 면 위 red 글자 → --status-error-ink (3.76 → 4.83:1, ADR 0495) */
  color: var(--status-error-ink);
  margin-left: 2px;
  font-weight: var(--font-semibold);
  font-size: 14px;
}

/* 입력 컨트롤 높이 표준 (frontend-guide §23)
   데스크톱 44px / 모바일 48px — .form-input · .form-select · .custom-select-trigger 통일 */
.form-input,
.form-textarea {
  width: 100%;
  padding: 11px 16px;
  font-size: 14px;
  line-height: 1.4;
  min-height: 44px;
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1.5px solid var(--border-default);
  border-radius: var(--radius-lg);
  transition: all 0.25s var(--ease-smooth);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02);
}

/* Select 기본 appearance 제거 - 상세 스타일은 섹션 65에서 정의 */

/* 날짜 입력은 Flatpickr(.date-picker) 가 담당 — 브라우저 input[type=date] 는 미사용 (frontend-guide §10) */

@media (hover: hover) {
  .form-input:hover,
  .form-textarea:hover {
    border-color: var(--border-strong);
    background: #fcfcfd;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  }
}

.form-input:focus,
.form-textarea:focus {
  outline: none;
  border-color: var(--accent-primary);
  box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.12), 0 2px 8px rgba(16, 185, 129, 0.08);
  background: var(--bg-primary);
}

/* .form-select hover/focus 스타일은 섹션 65에서 정의 */

.form-input::placeholder,
.form-textarea::placeholder {
  color: var(--text-tertiary);
  font-weight: var(--font-normal);
}

.form-input.is-invalid,
.form-textarea.is-invalid,
.float-field input.is-invalid,
.add-float-field input.is-invalid {
  border-color: var(--status-error);
  background-color: var(--status-error-bg);
}

.form-input.is-invalid:focus,
.form-textarea.is-invalid:focus,
.float-field input.is-invalid:focus,
.add-float-field input.is-invalid:focus {
  border-color: var(--status-error);
  box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.12);
}

.float-field input.is-invalid + label,
.add-float-field input.is-invalid + label {
  color: var(--status-error) !important;
}

/* ────────────────────────────────────────────────────────────────
   잠긴 값 칸 — 「지금은 못 쓰는 칸」 단일 토큰 (2026-08-17 사용자 확정 C안, 시안 locked-control-signal)
   구 상태 = 입력·셀렉트에 :disabled 스타일이 아예 없어서 브라우저 기본 회색 글자만 남았고,
   흰 면·1.5px 테두리·그림자가 그대로라 「비어 있는 칸」과 「잠긴 칸」이 화면에서 구분되지 않았다
   (실발현 = /admin/members/new 일괄 표에서 상태를 「일시정지」로 바꾸면 만료일이 잠기는데 티가 안 남).
   토큰 = slate-50 면 + slate-400 글자 + 그림자 제거 + not-allowed. opacity ❌ —
   구 잠금 표현이 컴포넌트마다 .45/.5/.55/.65 로 갈린 원인이라 면·글자색으로만 말한다.
   C안 = 여기에 더해 잠긴 빈 칸의 placeholder 를 「왜 잠겼는지」로 갈아끼운다 (각주를 못 두는
   표 셀에서 이유를 그 자리에서 답한다). 갈아끼우는 주체는 잠금을 켜는 JS — 사유가 마땅치 않은
   자리는 placeholder 를 비워 「예시값이 잠긴 칸에 남는」 혼란만 막는다.
   readonly 는 대상 아님 — .date-picker 가 키보드 차단용으로 쓰는 속성이라 잠금과 다른 축. */
.form-input:disabled,
.form-textarea:disabled,
.form-select:disabled,
.float-field input:disabled {
  background: var(--bg-secondary);
  color: var(--text-tertiary);
  border-color: var(--border-default);
  box-shadow: none;
  cursor: not-allowed;
}

/* 사유 문구(C안)는 placeholder 자리를 쓰므로 잠긴 칸에서도 읽혀야 한다 —
   placeholder 를 숨기는 처리 ❌ (사유가 안 보이면 A안으로 되돌아간다). */
.form-input:disabled::placeholder,
.form-textarea:disabled::placeholder {
  color: var(--text-tertiary);
}

/* 이미 slate-50 인 면 위에서는 잠금 면을 한 단계 더 죽인다 — 같은 색이면 신호가 0이 된다
   (사용처 = .toggle-group-fields 이용권 폼 정지 정책 2칸). 새 회색면 컨테이너를 만들 때도 같은 짝. */
.toggle-group-fields .form-input:disabled,
.toggle-group-fields .form-select:disabled,
.toggle-group-fields .form-textarea:disabled {
  background: var(--color-slate-100);
}

/* FormValidator 인라인 에러 문구 (ADR 0041) — blur/저장 검증 실패 시 필드 아래 상주,
   입력 재개 시 자동 제거. FormValidator._showMessage 가 생성·부착 (페이지 수동 마크업 ❌). */
.form-error {
  margin-top: 7px;
  font-size: 12px;
  font-weight: 600;
  color: var(--status-error-ink);
  line-height: 1.5;
}

/* ios-row 변형 — 행 다음 형제로 부착돼 행의 hairline 을 인계 (행 자체는 가로 flex 라 내부 부착 ❌).
   invalid 행의 red rail 과 짝: 행은 rail, 이유 문구는 이 줄이 담당. */
.form-error--row {
  margin-top: 0;
  padding: 0 18px 12px;
  text-align: right;
  border-bottom: 1px solid var(--border-subtle);
}
.ios-item:has(+ .form-error--row) {
  border-bottom: none;
}
.form-error--row:last-child {
  border-bottom: none;
}

/* .form-select.is-invalid 스타일은 섹션 65 이후 정의 */


/* Form Section */






/* Checkbox & Radio */


.form-check-input {
  width: 18px;
  height: 18px;
  accent-color: var(--accent-primary);
  cursor: pointer;
}



/* Form Row (for inline forms) */
.form-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}

@media (max-width: 768px) {
  .form-row {
    grid-template-columns: 1fr;
    gap: 0;
  }
  .form-row + .form-row,
  .form-row + .form-group,
  .form-group + .form-row {
    margin-top: 8px;
  }
}

/* ========================================
   10. PREMIUM TABLES
   ======================================== */
.table-container {
  overflow-x: auto;
  overflow-y: hidden; /* §이중 스크롤 금지 — overflow-x 만 선언하면 y 가 auto 로 계산돼 세로 스크롤 컨테이너가 된다 (member-bulk-register 선례) */
  border-radius: var(--radius-xl);
  background: var(--bg-primary);
}

/* 가로 스크롤 = 「있다는 걸 화면이 말한다」 — ADR 0484 어휘를 «세 번째 가로 스크롤 표»에 적용
   (2026-09-15 사용자 확정 A안 · 시안 `ipad-table-scroll-signal`).
   0484 가 「세 번째 가로 스크롤 표가 생기면 그것도 이 헬퍼를 쓴다」로 예고한 자리다 — 관원 목록 표는
   열 폭 예산이 1440px 기준([[0262]])이라 데스크톱에선 안 넘쳐 여태 안 드러났고, 아이패드에서만 나온다
   (실측 세로 820px 숨은 폭 185px · 가로 1180px 65px · 데스크톱 1440px 넘침 0).
   ⚠ `::-webkit-scrollbar` 를 **선언하는 것 자체**가 크롬의 오버레이 모드를 끄고 클래식 바로 되돌린다 —
   두께만 키우려고 `height` 만 적고 트랙·썸 색을 빼면 바가 다시 사라진다(셋이 한 벌).
   ⚠ 넘치지 않는 표는 실측 0px 라 결제·콘솔 표 등 같은 클래스를 쓰는 나머지 자리의 지금 모습은 안 바뀐다. */
.table-container::-webkit-scrollbar { height: 14px; }
.table-container::-webkit-scrollbar-track { background: var(--color-slate-100); border-radius: 999px; }
/* 썸 테두리 = 트랙과 같은 색 3px — 보이는 두께는 8px 이면서 잡히는 판정은 14px (히트영역 확대 idiom 의 스크롤바 판) */
.table-container::-webkit-scrollbar-thumb {
  background: var(--color-slate-300); border-radius: 999px; border: 3px solid var(--color-slate-100);
}
@media (hover: hover) {
  .table-container::-webkit-scrollbar-thumb:hover { background: var(--color-slate-400); }
}

/* 우측 페이드 = 「여기서 잘린 게 아니라 더 있다」를 **헤더 눈높이에서** 말한다.
   스크롤바는 표 맨 아래라 행이 20줄이면 세로로 한참 내려야 닿는다 — 그 공백을 이 34px 이 메운다.
   ⚠ 스크롤 컨테이너(.table-container) 자신에 붙이면 내용과 함께 흘러가므로 **바깥 프레임**이 소유한다.
   `bottom:16px` = 스크롤바 높이(14) + 여유 — 페이드가 바 위를 덮으면 트랙이 끊겨 보인다.
   is-scrollable/at-end 는 `Utils.watchScrollEdge` 가 실측으로 토글 — 넘치지 않는 폭에선 아예 안 뜨고,
   끝까지 밀면 사라져 **「끝」까지 말한다**(신호가 한 방향으로만 거짓말하지 않게).
   좌측 페이드 ❌ — 넣으면 이름 열이 늘 흐려 보인다(0484 가 좌측을 뺀 것과 같은 축). */
.table-scroll-frame { position: relative; }
.table-scroll-frame::after {
  content: ''; position: absolute; top: 0; right: 0; bottom: 16px; width: 34px; pointer-events: none;
  opacity: 0; transition: opacity .18s var(--ease-smooth, ease);
  background: linear-gradient(to left, rgba(255,255,255,.98), rgba(255,255,255,0));
}
.table-scroll-frame.is-scrollable:not(.at-end)::after { opacity: 1; }

.table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
}

/* D. MONO CONCIERGE — 테이블 헤더 = 마이크로 라벨 절충 승격 (2026-07-29 사용자 확정 C안).
   weight 800 + slate-500 잉크는 마이크로 라벨과 통일, 자간만 0.03em 유지(2026-05-15 한글 가독 보정 —
   0.08em 은 한글 머리글에서 과도). billing 계열 scoped th 3곳은 이 글로벌로 흡수·제거.
   배경은 흰색 유지 (회색 그라데이션 제거 의도 유지). */
.table th {
  padding: 14px 20px;
  text-align: left;
  font-size: 12px;
  font-weight: 800;
  color: var(--color-slate-500);
  letter-spacing: 0.03em;
  text-transform: none;
  background: #ffffff;
  border-bottom: 1px solid #eaecef;
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: var(--z-raised);
}

.table th:first-child {
  border-radius: var(--radius-lg) 0 0 0;
}

.table th:last-child {
  border-radius: 0 var(--radius-lg) 0 0;
}

/* Sortable header — th[role="button"] 은 cursor pointer (인라인 style 제거 - 2026-05-20) */
.table th[role="button"] {
  cursor: pointer;
  user-select: none;
}
@media (hover: hover) {
  .table th[role="button"]:hover {
    color: var(--color-primary-700);
  }
}

/* 활성 정렬 표시 (2026-07-28 UI 감사) — aria-sort 는 JS 가 이미 정확히 세팅하는데 대응 CSS 가
   없어 "어느 컬럼이 어느 방향으로 정렬 중인지"가 화면에 전혀 안 나왔다. 어휘 = §탭/필터
   Text Hierarchy (emerald 텍스트 + 800, 채움·밑줄 없음) + 방향은 caret 로 — 색만으로 정보 전달 ❌ 충족. */
.table th[aria-sort="ascending"],
.table th[aria-sort="descending"] {
  color: var(--color-primary-700);
  font-weight: 800;
}
.table th[aria-sort="ascending"]::after,
.table th[aria-sort="descending"]::after {
  margin-left: 4px;
  font-size: 0.85em;
  line-height: 1;
}
.table th[aria-sort="ascending"]::after { content: "▲"; }
.table th[aria-sort="descending"]::after { content: "▼"; }

/* Toolbar utilities — 검색바/필터 옆 액션 묶음 우측 정렬 (인라인 style 제거 - 2026-05-20) */
.toolbar-actions-end {
  margin-left: auto;
  display: flex;
  gap: 8px;
  align-items: center;
}

/* Dropdown anchor — relative+inline-block 위치 컨텍스트 (드롭다운 메뉴의 부모) */
.dropdown-anchor {
  position: relative;
  display: inline-block;
}

.table td {
  padding: 14px 20px;
  font-size: 13px;
  color: var(--text-primary);
  border-bottom: 1px solid var(--border-divider);
  vertical-align: middle;
  transition: background-color 0.15s ease;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* D. MONO CONCIERGE — 테이블 셀 위계: 부차 컬럼(.muted) tabular-nums slate-500 */
.table td.muted {
  color: var(--color-slate-500);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.table tbody tr:last-child td {
  border-bottom: none;
}

/* Premium Table Row Hover */
.table tbody tr {
  transition: all 0.25s ease;
}

/* D. MONO CONCIERGE — row hover 모노톤 미세 강조 (emerald 그라데이션 제거) */
@media (hover: hover) {
  .table tbody tr:hover {
    background: var(--bg-row-hover);
  }
}

@media (hover: hover) {
  .table tbody tr:hover td {
    color: var(--text-primary);
  }
}

/* Clickable Row Enhancement */
.table-clickable tbody tr {
  cursor: pointer;
}

@media (hover: hover) {
  .table-clickable tbody tr:hover {
    background: var(--bg-row-hover);
  }
}

.table-clickable tbody tr:active {
  transform: scale(0.998);
}



/* .data-table — 2026-07-29 삭제: 사용처 0 죽은 CSS (표는 글로벌 .table 단일 어휘) */

/* ========================================
   11. PREMIUM BADGES
   ======================================== */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 6px 14px;
  font-size: 12px;
  font-weight: var(--font-bold);
  border-radius: var(--radius-full);
  letter-spacing: 0.3px;
  text-transform: uppercase;
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;
}

.badge::before {
  display: none;
}

/* Badges — 순수 텍스트 통일 (2026-06-26 개정, Hairline Pill supersede): 배경·테두리 0,
   색 + 800 굵기로만 신호. 탭/필터 Text Hierarchy 와 동일 어휘 — 라벨이 1차 의미,
   텍스트 색이 보조 신호(success=emerald · danger=red · 그 외 slate).
   (이용권 뱃지 badge-period/-count/-day-specific/-weekly-count·badge-none 은 dot pill 유지 — 아래 별도.)
   project-frontend-guide §상태 뱃지 참조. */
.badge-success,
.badge-pending,
.badge-danger,
.badge-secondary,
.badge-neutral,
.badge-cancelled,
.badge-waiting,
.badge-expired {
  background: transparent;
  border: none;
  padding: 0;
  border-radius: 0;
  letter-spacing: 0;
  text-transform: none;
  font-weight: 800;
}

.badge-success {
  color: var(--status-success);
}

/* Brand Mono: warning/info → slate neutral. 색 대신 텍스트로 의미 전달
   (예: "일시정지" · "심사 중" · "설정 필요"). */
.badge-pending {
  color: var(--color-slate-600);
}

/* 뱃지 = 12px/800 순수 텍스트라 흰 면 위 «기본 글자» — red 토큰 셋 중 `-ink` 자리다
   ([[0495]]·[[0597]] 「흰 면 위 기본 글자 = `--status-error-ink` 하나 / `--status-error` 는
   면·테두리 자리」). 구 #ef4444 는 3.76:1 로 AA 미달이었다. `--status-error` 복귀 ❌ */
.badge-danger {
  color: var(--status-error-ink);
}

.badge-secondary,
.badge-neutral,
.badge-cancelled {
  color: var(--color-slate-500);
}



/* WAITING_FIRST_ATTENDANCE(첫 출석 대기) = emerald — 이용권을 취득했고 곧 시작할 관원이라
   「이용종료」·「일시정지」 회색과 구분돼야 한다. 관원 상세 .detail-identity-status·회원 PWA
   .md-id-status--waiting 가 이미 emerald 로 박제 — 목록만 slate 면 같은 상태가 표면마다 다른 색.
   (emerald = 성공이 아니라 "진행/신호 위치" — project-frontend-guide §디자인 톤 Emerald 유지 라인) */
.badge-waiting {
  color: var(--status-success);
}

/* EXPIRED(이용종료) 는 read-only 상태이므로 slate 중립
   (project-frontend-guide §시맨틱 색 "이용권 만료 뱃지 → slate", member/dashboard .md-status--expired 정합) */
.badge-expired {
  color: var(--color-slate-500);
}

/* Pass Type Badges — Brand Mono · D.MONO CONCIERGE 정합 (2026-05 재설계 v3).
   흰 bg + slate-200 hairline + slate-700 텍스트 + ::before emerald 3-shade dot.
   project-frontend-guide §이용권 뱃지 참조. */
.badge-period,
.badge-count,
.badge-day-specific,
.badge-weekly-count {
  background: #fff;
  color: var(--color-slate-700);
  border: 1px solid var(--border-light);
  font-weight: 700;
  letter-spacing: 0.01em;
  text-transform: none;
  gap: 7px;
  /* .badge 베이스(padding 6px 14px) 위에 dot 까지 들어가면 답답 → 한글+dot 비율에 맞춰 보정.
     .plan-type-chip / .pay-type-badge / .pt-badge 등 자체 padding 베이스에는 그쪽이 우선. */
  padding: 4px 11px 4px 10px;
}

/* dot — .badge::before { display:none } 보다 뒤에 있어 자연 override */
.badge-period::before,
.badge-count::before,
.badge-day-specific::before,
.badge-weekly-count::before {
  display: inline-block;
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
}

/* 이용권 dot 4-shade 균등 램프 — SSOT = :root --pass-dot-* (2026-07-04 통합) */
.badge-period::before { background: var(--pass-dot-period); }
.badge-count::before { background: var(--pass-dot-count); }
.badge-day-specific::before { background: var(--pass-dot-day); }
.badge-weekly-count::before { background: var(--pass-dot-weekly); }

/* 이용권 타입 뱃지 — 결제·만료일 연장 계열 공유 지오메트리 (2026-08-03 UI 감사 통일).
   payments 검증값(radius 6 · 12px) 승격 — 드릴다운 쌍(연장 선택 → 확정)의 모양 점프 방지.
   dot 표시·색 = 위 .badge-*::before(--pass-dot-* SSOT) 그대로. .badge-period 뒤 선언이라
   같은 특이도의 알약 지오메트리(4px 11px·radius-full)를 소스 순서로 이긴다 — 위로 올리지 ❌ */
.pay-type-badge,
.pt-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 8px 2px 7px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
  flex-shrink: 0;
  background: #fff;
  color: var(--color-slate-700);
  border: 1px solid var(--border-light);
  text-transform: none;
}

.badge-none {
  background: #fff;
  color: var(--color-slate-500);
  border: 1px solid var(--border-light);
  font-weight: 600;
}

/* Pass Template Grid */





.pass-template-card.selected {
  border-color: var(--color-primary-500);
  background: var(--color-primary-50);
  box-shadow: 0 0 0 3px var(--color-primary-100);
}







/* Light badges (alternative style) — Hairline Pill 통일 (2026-06-26): 채움 폐지 */
/* 순수 텍스트 통일 (2026-06-26): status 패밀리와 동일 어휘 — 면·테두리 0 + 800 */





/* ========================================
   15. Modal — D.MONO Concierge (표준)
   ----------------------------------------
   페이지 인라인 재정의 금지:
     .modal-header/-title/-close/-body/-footer 의
     padding/height/font-size/bg/border 를 페이지에서 덮어쓰지 않는다.
   스크롤은 .modal-body 한 곳만 — 내부 list/scroll wrapper 에
     별도 max-height/overflow 거는 패턴 금지 (min-height:0 박제로 자식 스크롤 보장).
   modifier: 폭 변형(.modal-sm/-md/-lg/-xl)만.
     구 .modal-dmono·.modal-compact 는 2026-07-28 제거 (사용처 0 · 기본 .modal 이 D.MONO 흡수). 재도입 ❌
   ======================================== */
.modal {
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 14px;
  box-shadow: 0 20px 60px -20px rgba(15, 23, 42, 0.25);
  width: 100%;
  max-width: 480px;
  /* 무대의 90% — 오버레이가 「보이는 영역」이라 화면 전체(dvh)가 아니라 그 높이를 나눠 쓴다
     (common.js §1.4 OverlayStage · 2026-09-07 사용자 확정 A안). 구 `90dvh` 는 키보드가 떠도
     화면 전체의 90% 라 확정 푸터를 키보드 밑에 두는 값이었다. `90dvh` 복귀 ❌ */
  max-height: 90vh;
  max-height: calc(var(--vvh, 100vh) * 0.9);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transform: scale(0.93) translateY(24px);
  /* 스프링 팝인 (2026-07-16 모션 시스템) — overshoot bezier 로 열림 순간 살짝 튐 */
  transition: transform 0.42s var(--ease-spring);
  position: relative;
}

.modal-sm { max-width: 380px; }
.modal-md { max-width: 480px; }



.modal-header {
  padding: 0 22px;
  height: 56px;
  background: #fff;
  border-bottom: 1px solid var(--border-hairline);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-shrink: 0;
  position: relative;
}

/* 2026-05-26: .modal-header::before 상단 emerald hairline 및 .modal-title::before
   좌측 4×14 emerald 막대 폐지 — project-frontend-guide §「Carded surface — color rail 일반 금지」.
   fingerprint = 외곽 hairline + shadow + .modal-title 15px 800 typography. */

.modal-title {
  font-size: 15px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--color-slate-900);
  margin: 0;
}

/* 닫기 ✕ 기본값 — 프래그먼트가 항상 달고 나오는 클래스라 **어떤 컨텍스트에서도 크기가 auto 로
   깨지지 않는다**(구 인라인 width/height 를 CSS 로 옮기며 규칙을 빠뜨리면 아이콘이 사라지는 경로).
   컨텍스트별 값은 아래·각 페이지 규칙이 덮는다. */
.icon-close { width: 16px; height: 16px; stroke-width: 2; flex-shrink: 0; }

/* CTA·행 안 작은 전진 셰브론 12px — 아이콘은 `icons :: chevronRight`(ADR 0374).
   구 인라인 14벌은 같은 도형을 `<polyline points="9 18 15 12 9 6">` 와 `M9 18l6-6-6-6` **두 표기**로
   적고 있었다(렌더 결과는 동일). 굵기는 구 값 그대로 보존 — 2.5(관장·auth 8곳) / 3(랜딩·PWA CTA 6곳)은
   표면이 달라 지금 통일하지 않는다(§아이콘 굵기 표준은 미결 — 별도 판단). 인라인 크기 속성 복귀 ❌ */
.icon-cta-chev { width: 12px; height: 12px; stroke-width: 2.5; flex-shrink: 0; }
/* 랜딩(`.lf-pb-btn`)은 이 시트를 안 읽는다 — 같은 값이 landing.css 이식 구역에 따로 있다 */
.md-pb-btn .icon-cta-chev,
.md-today-cta .icon-cta-chev { stroke-width: 3; }

/* 모달 닫기 ✕ — 아이콘은 `icons :: close`(ADR 0374). 구 인라인 34벌이 **한 클래스 안에서**
   16px/2(22개)·14px/2.5(9개)·14px/2.4(3개) 세 벌로 갈려 있었다 → 다수값 16px/2 로 통일.
   인라인 width/height/stroke-width 복귀 ❌ */
.modal-close svg { width: 16px; height: 16px; stroke-width: 2; }
.modal-close {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-slate-400);
  background: transparent;
  border: none;
  cursor: pointer;
  transition: all 0.18s ease;
  flex-shrink: 0;
}

@media (hover: hover) {
  .modal-close:hover {
    background: var(--color-slate-100);
    color: var(--color-slate-700);
  }
}

.modal-body {
  padding: 20px 22px;
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}

/* 본문 마지막 요소의 아래 margin = 죽은 여백 — 하단 간격은 .modal-body padding(20) 이 소유한다.
   위 `.form-group:last-child` 전역 선언과 짝 — 그건 폼 그룹만 덮으므로, 마지막 요소가 데이터 카드·안내
   박스·섹션 무엇이든 상·하 20/20 대칭이 되게 여기서 한 번 더 못 박는다.
   통과시키는 것은 **자기 면이 없는 레이아웃 래퍼**뿐 — 본문 전체를 감싸는 `<form>`(이용권 폼 모달·카드
   등록·요금 설정)과 2열 행 격자. 이 자리에선 body 의 마지막 자식이 래퍼라, 실제 마지막 필드까지 규칙이
   닿지 않으면 같은 44px 이 그대로 남는다(카드 등록 모달 실측).
   무차별 자손(`.modal-body *:last-child`)으로 넓히지 ❌ — 면을 가진 중첩 블록의 내부 여백까지 죽인다. */
.modal-body > *:last-child,
.modal-body > form:last-child > *:last-child,
.modal-body > .form-row-2col:last-child > *:last-child,
.modal-body > form:last-child > .form-row-2col:last-child > *:last-child {
  margin-bottom: 0;
}

/* 액션 푸터 어휘 통일 (2026-07-30 사용자 확정 C안) — "화면을 덮는 것은 전폭, 페이지에 붙어 있는 것은 우측 compact".
   오버레이(.modal-footer · .alert-modal-actions · .bottom-sheet-actions) = 전폭 split /
   인페이지(.ios-settings-actions · .card-footer-actions) = 우측 정렬 btn-sm.
   구 「우측 정렬 + height 56px 고정 + min-width 88px」 supersede — 같은 성격의 확인(관원 삭제=Alert ↔ 이용권 해지=모달)이
   표면에 따라 다르게 보이던 문제 해소. 높이 고정은 풀되 flex-shrink:0 유지(단일 스크롤 패턴 불변). */
.modal-footer {
  padding: 16px 22px 20px;
  background: #fff;
  border-top: 1px solid var(--border-hairline);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}

.modal-footer .btn {
  flex: 1;
  min-width: 0;
}

/* modal-footer-clean — 이제 hairline 제거만 담당 (전폭 균등은 기본으로 승격, 2026-07-30) */
.modal-footer-clean {
  border-top: none;
}

/* modal-footer--toolbar — 「취소+확정」 쌍이 아니라 동등 액션 3개+ 를 늘어놓는 툴바 푸터.
   전폭 균등(flex:1 + min-width:0)을 그대로 적용하면 좁은 화면에서 각 버튼이 1/3 로 눌려
   아이콘+라벨이 잘린다(2026-07-30 회원 초대 모달에서 실측). 내용 폭 + 필요 시 줄바꿈으로 분리.
   닫기는 헤더 ✕ 가 담당하는 모달 전용 — 확인 쌍에는 쓰지 ❌ */
/* .modal-overlay 스코프까지 함께 선언 — 위 `.modal-overlay .modal-footer .btn`(특이성 0,3,0)이
   변형만으로는 이기지 못한다(2026-07-30 실측: 툴바인데도 1/3 로 눌림) */
.modal-footer--toolbar,
.modal-overlay .modal-footer--toolbar {
  flex-wrap: wrap;
  justify-content: flex-end;
}
.modal-footer--toolbar .btn,
.modal-overlay .modal-footer--toolbar .btn {
  flex: 0 0 auto;
  min-width: 88px;
}

/* 파괴+취소+확정 3버튼 푸터의 좌측 분리 변형(.btn-destructive-left)은 §9700 전폭 규칙 뒤에 선언
   (같은 특이성 0,3,0 — 순서 의존, 툴바 변형과 같은 이유. 여기로 옮기면 다시 눌린다) */

/* modal-footer-compact — 2026-07-31 폐지. 존재 이유였던 "기본 56px 고정 해제"가
   §액션 푸터 어휘(2026-07-30)에서 height 고정 자체와 함께 사라졌고, 남은 작은 padding 은
   오버레이 = 전폭 split 표준(16/22/20 · gap 10)과 어긋나는 유일 사용처만 만들었다. */

/* .modal-icon 배지 계열 — 2026-08-07 폐지 (사용처 0 죽은 CSS).
   ADR 0015 가 amber 원을 폐지한 뒤 primary/danger/success 변형도 사용처가 소멸.
   모달 본문 장식 아이콘 자체가 금지 어휘 — 재도입 ❌ (§15.5 데이터 카드가 대체). */

/* ========================================
   15.5 Modal Data Card — 주의 확정·파괴 모달 본문 어휘 (ADR 0015 · 2026-05-25)
   ----------------------------------------
   amber 솔리드 원 (`.modal-icon-warning`) 같은 추상적 경고 대신
   *변경의 실체와 결과를 데이터로* 표현. 마이크로 라벨 + 변경 카드 + 영향 dot.
   적용: 주의 확정 (btn-dark) · 파괴 (btn-danger outline) · 정산·구독·결제 모달.
   ======================================== */

/* 모달 본문 리드 — 데이터 카드 위 안내 한 줄 */
.modal-body .modal-lead {
  font-size: 13.5px;
  color: var(--color-slate-700);
  line-height: 1.65;
  margin: 0 0 14px;
}

.modal-data-card {
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  background: var(--color-slate-50);
  padding: 14px 16px;
}

/* 변경 행 — 라벨 + 값 양끝 정렬 */
.modal-data-card .data-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
}
/* 행 사이 hairline — border-top 으로 그린다. border-bottom + :last-of-type 방식은
   숨김(display:none) 행이 DOM상 마지막이면 보이는 마지막 행에 잔여 선이 남아
   impact-list 상단 선과 평행선 2줄이 생긴다. border-top + 인접선택자는 보이는
   행 사이에만 선이 들어가고, 데이터 영역↔영향 리스트 구분은 .impact-list 상단
   hairline 1줄이 담당한다. (admin/members/detail.html 정지 모달 더블라인 버그) */
.modal-data-card .data-row + .data-row {
  border-top: 1px solid var(--border-hairline);
}
/* 값 열 축소 허용 — flex item 기본 min-width:auto 는 무공백 긴 값(계좌번호·사업자번호 등)의
   전체 폭을 최소 폭으로 잡아 행이 모달 밖으로 넘친다(break-word 는 min-content 계산에 반영되지
   않아 단독으론 무력). 라벨을 뺀 *모든* 값 열에 걸어야 한다 — 값이 클래스 없는 래퍼 span 안에
   들어가는 모달(settings 정산 변경 확인)은 `.data-value` 만으로는 안 닿는다. */
.modal-data-card .data-row > *:not(.data-label) {
  min-width: 0;
}

/* 마이크로 라벨 — 11px 800 UPPERCASE 0.08em */
.modal-data-card .data-label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-slate-500);
  flex-shrink: 0;
}

/* 값 — slate-900 700 tabular-nums */
.modal-data-card .data-value {
  font-size: 13px;
  font-weight: 700;
  color: var(--color-slate-900);
  text-align: right;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.005em;
  word-break: keep-all;
  overflow-wrap: break-word;
  /* min-width:0 은 위 `.data-row > *:not(.data-label)` 이 담당 — 래퍼 span 까지 함께 덮는다 */
}

/* 이전 값 — line-through slate-500 500 */
.modal-data-card .data-value-prev {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--color-slate-500);
  text-decoration: line-through;
  margin-right: 6px;
}

/* 값 뒤 보조 텍스트 — 잔여 quota "/ N회" · 단위 · 부가 설명 */
.modal-data-card .data-value-suffix {
  font-weight: 500;
  color: var(--color-slate-500);
  margin-left: 2px;
}

/* 영향 리스트 — margin-top + top hairline */
.modal-data-card .impact-list {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border-hairline);
}
/* 영향 리스트 only (data-row 없는 모달) — 상단 공백/hairline 제거 */
.modal-data-card > .impact-list:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

/* 영향 라벨 — 마이크로 라벨 + display block */
.modal-data-card .impact-label {
  display: block;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-slate-500);
  margin-bottom: 6px;
}

/* 영향 항목 — 좌측 emerald dot + line-height 1.65 */
.modal-data-card .impact-item {
  font-size: 12.5px;
  color: var(--color-slate-700);
  line-height: 1.65;
  padding-left: 14px;
  position: relative;
}
.modal-data-card .impact-item::before {
  content: '';
  position: absolute;
  left: 0;
  top: 8px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--color-primary-600);
}
.modal-data-card .impact-item + .impact-item {
  margin-top: 4px;
}

/* 인접 데이터 카드 — 14px 간격 */
.modal-data-card + .modal-data-card {
  margin-top: 14px;
}

/* 데이터 카드 안 inline 액션 (예: 복사 버튼 단일 행) */


/* 환불 금액 강조 행 — 데이터 카드 surface(slate-50 + hairline) 위에서
   금액 숫자만 emerald 신호. carded surface emerald fill/border 금지 정책 정합. */
.modal-refund-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 14px;
  padding: 14px 16px;
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  background: var(--color-slate-50);
}
.modal-refund-row .refund-label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-slate-500);
}
.modal-refund-row .refund-sub {
  font-size: 12px;
  color: var(--color-slate-500);
  margin-top: 3px;
}
.modal-refund-row .refund-amount {
  flex-shrink: 0;
  font-size: 22px;
  font-weight: 800;
  color: var(--text-primary); /* ADR 0075 — 금액 값 슬롯 slate, 강조는 크기·800 weight */
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
}

/* 모달 본문 보조 캡션 — 데이터 카드 아래 안내 1줄 */
.modal-note {
  margin: 14px 0 0;
  font-size: 12.5px;
  color: var(--color-slate-500);
  text-align: center;
  line-height: 1.55;
}

/* ========================================
   15.6 Modal Inline Highlight — 일반 확인 모달 emerald 강조 어휘 (ADR 0015)
   ----------------------------------------
   본문 산문 안 핵심 어휘를 emerald-50 underline highlight.
   `<strong class="modal-highlight">` 또는 `.modal-body .modal-highlight`.
   ======================================== */

.modal-body .modal-highlight {
  font-weight: 800;
  color: var(--color-primary-800);
  background: linear-gradient(transparent 60%, var(--color-primary-50) 60%);
  padding: 0 2px;
}

/* Modal meta strip — 하단 메타 라벨 2~3 (Inline 강조 모달 짝) */





/* 모바일 — 메타 strip 세로로 적층 + gap 축소 */
@media (max-width: 768px) {
  
  .modal-data-card {
    padding: 12px 14px;
  }
}

/* ========================================
   16. Toast
   ======================================== */
.toast-container {
  position: fixed;
  /* 인셋 포함 — 아이패드 standalone·가로 모드에서 토스트가 상태바·노치에 물리지 않게 (2026-09-14) */
  top: calc(var(--space-4) + env(safe-area-inset-top, 0px));
  right: calc(var(--space-4) + env(safe-area-inset-right, 0px));
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  pointer-events: none;
}

/* 설정 저장바(.ios-settings-actions) 펼침(dirty) 시에만 토스트를 바 아래로 비켜 우상단 저장/취소
   버튼을 가리지 않게 한다. reveal-on-dirty 라 평소(접힘)엔 매칭 안 됨 → 일반 페이지 영향 0.
   데스크톱: 바가 fixed top:0 (~56px) → top-nav-height(64) + 여백으로 클리어. (2026-06-04) */
body:has(.ios-settings-actions.is-dirty) .toast-container {
  top: calc(var(--top-nav-height) + 12px);
}

/* 모바일: 기본 = 최상단(헤더 위 오버레이). 헤더에 조작 버튼이 있는 화면만 헤더 아래로 회피 —
   데스크톱 dirty 저장바 회피 규칙과 같은 문법 (2026-08-09 사용자 확정 C안, 구 「전 화면 헤더 아래 64px」 supersede) */
@media (max-width: 768px) {
  .toast-container {
    top: calc(env(safe-area-inset-top, 0px) + var(--space-2));
    right: var(--space-3);
    left: var(--space-3);
  }
  /* 편집 헤더(뒤로·저장 인라인, 56px) 활성 화면 → 헤더 아래로 (저장 직후 연속 조작 보호) */
  body.has-mobile-header-action .toast-container {
    top: calc(var(--shell-header-h, 56px) + var(--space-2));
  }
  /* 모바일 저장바는 sticky top:56px(헤더 아래) + 높이 ~48px → 헤더(56) + 바(48) 아래로 비킴 */
  body:has(.ios-settings-actions.is-dirty) .toast-container {
    top: calc(var(--shell-header-h, 56px) + 48px + var(--space-2));
  }
  .toast {
    min-width: 0;
    max-width: 100%;
    width: 100%;
  }
}

.toast {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 14px;
  box-shadow: 0 20px 60px -20px rgba(15,23,42,0.25);
  pointer-events: auto;
  position: relative;
  overflow: hidden;
  animation: toastIn 450ms var(--ease-out-quint);
  min-width: 300px;
  max-width: 380px;
  /* 닫기 어휘 3종 = 면 전체 탭 · ✕ · 위로 스와이프 (2026-08-11 사용자 확정 B안, 시안 toast-dismiss-gesture).
     토스트는 액션 없는 순수 알림이라 면 어디를 눌러도 뜻이 하나 — 탭 목표가 화면 폭 전체가 돼
     ✕ 24px 조준 문제가 원인째 사라진다. ✕ 는 「닫을 수 있다」는 눈에 보이는 표지로 남는다.
     touch-action: pan-up = 손가락을 아래로 끄는 팬(= 스크롤 방향은 위)만 브라우저에 넘기고,
     위로 향하는 제스처는 JS 가 가져간다 — 토스트가 상단 풀폭 오버레이라 이걸 안 가르면
     그 위에서 시작한 페이지 스크롤이 통째로 막힌다(`pan-down` 은 값의 방향이 반대라
     아래로 끌어도 스크롤이 안 됨 — 2026-08-11 실측). `pan-y`·`auto` 도 지금은 동작하지만
     컴포지터가 먼저 스크롤을 시작하면 `cancelable=false` 로 preventDefault 가 무시되는
     레이스가 남는다 — 방향을 미리 선언해 그 창을 없앤다.
     JS 축 판정(_bindDismiss)은 미지원 브라우저 폴백 + 가로 제스처 양보를 겸한다.
     user-select: none = 탭·드래그 때 문구가 하이라이트되지 않게. */
  cursor: pointer;
  touch-action: pan-up;
  user-select: none;
  -webkit-user-select: none;
}

/* 모바일 진입 = 위에서 내려옴 (2026-08-11 사용자 확정 B안, 시안 toast-enter-direction).
   상단 앵커 풀폭이라 진입·퇴장이 같은 축이어야 「들어온 길 = 치우는 길」이 되고,
   스와이프 방향을 모션이 미리 가르쳐 준다. 구 가로 진입(translateX 80%)은 우상단 380px
   카드 전제의 모션이라 풀폭에서는 화면 폭만큼 옆으로 밀려 들어왔다.
   오버슈트는 축만 바꿔 유지(4px) — 데스크톱 진입(toastIn)은 불변.
   **선언 위치 = `.toast-drag`/`.toast-settle`/`.toast-exit` 보다 위 의무** — 셀렉터 특정도가
   같아(클래스 1개) 뒤에 두면 드래그 중에 진입 애니가 되살아나고 퇴장도 덮인다. */
@media (max-width: 768px) {
  .toast {
    animation: toastInDown 450ms var(--ease-out-quint);
  }
}

/* 스와이프 추종 중 = 진입 애니·전환 정지(손가락 좌표가 유일한 입력) / 임계 미달 복귀 = 스프링 */
.toast-drag {
  animation: none;
  transition: none;
}

.toast-settle {
  animation: none;
  transition: transform 220ms var(--ease-out-quint), opacity 220ms ease-out;
}

/* 자리 메움 = 위 토스트 퇴장과 동시에 아래 토스트가 따라 올라옴 (2026-08-13 사용자 확정 B안,
   시안 toast-stack-collapse). Toast._collapseSiblings 가 닫기 시작 시점에 다음 형제들을
   shift 만큼 인라인 transform 으로 올리고, remove() 프레임에 이 클래스와 함께 걷는다 —
   제거 시점 순간이동 방지. 닫힘 경로 전부(자동 소멸·✕·탭·스와이프) 공통. */
.toast-collapse {
  transition: transform 200ms var(--ease-out-quint);
}

/* 2026-05-27: Toast 좌측 4px color rail 폐지 — frontend-guide §0 AI slop test
   "좌측 컬러 rail = AI slop tell 1순위" 박제 정신.
   2026-08-07: 상태 아이콘(.toast-icon/.toast-icon-wrap) 폐지 — 아이콘 재도입 ❌ 유지.
   2026-08-09 사용자 확정 C안: tint 면 폐지 → 흰 면 + hairline(카드·모달과 같은 재질).
   상태 신호 = 문구(1차) + 좌측 6px dot(보조). 진행바는 중립 slate 로 내려 "시간"만 말한다.
   구조는 3자식(content · close ✕ · progress) 유지 — dot 은 ::before 라 자식이 늘지 않는다. */
.toast::before {
  content: "";
  flex: 0 0 6px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
}



/* 스프링 진입 (2026-07-16 모션 시스템 — overshoot 는 키프레임이 담당, easing 은 ease-out) */
@keyframes toastIn {
  0% {
    opacity: 0;
    transform: translateX(80%) scale(0.96);
  }
  60% {
    opacity: 1;
    transform: translateX(-6px) scale(1.01);
  }
  100% {
    opacity: 1;
    transform: translateX(0) scale(1);
  }
}

@keyframes toastInDown {
  0% {
    opacity: 0;
    transform: translateY(-120%) scale(0.98);
  }
  60% {
    opacity: 1;
    transform: translateY(4px) scale(1.005);
  }
  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.toast-exit {
  animation: toastOut 200ms ease-out forwards;
}

@keyframes toastOut {
  to {
    opacity: 0;
    transform: translateX(100%);
  }
}

/* 모바일 퇴장 = 위로 (2026-08-11 B안): 상단 앵커 풀폭이라 사용자가 날리는 방향과 같아야 한다.
   자동 소멸·✕·탭·스와이프 어느 경로로 닫혀도 같은 방향으로 나간다.
   데스크톱은 우상단 카드라 가로 퇴장(toastOut) 유지 — 위로 빼면 헤더 뒤로 파고든다. */
@media (max-width: 768px) {
  .toast-exit {
    animation: toastOutUp 200ms ease-out forwards;
  }
}

/* 스와이프 확정 퇴장 = 놓은 지점 이어달리기 (2026-08-13 사용자 확정 A안, 시안 toast-swipe-exit).
   toastOut* 키프레임은 원위치(0)에서 다시 출발해, 끌어올려 둔 토스트가 릴리즈 순간
   제자리로 '툭' 복귀한 뒤 날아가는 불연속이 보였다. 스와이프로 닫힐 때만 이 클래스가
   드래그 인라인 transform/opacity 를 이어받아 180ms 감속으로 마저 내보낸다
   (Toast._closeFromDrag — 자동 소멸·✕·탭 닫힘은 기존 toastOut* 그대로).
   .toast-exit 와 함께 붙는다 — 특정도가 같아(클래스 1개) animation: none 이 이기려면
   **.toast-exit(위 모바일 오버라이드 포함)보다 아래 선언 의무**. */
.toast-exit-follow {
  animation: none;
  transition: transform 180ms var(--ease-out-quint), opacity 180ms ease-out;
}

@keyframes toastOutUp {
  to {
    opacity: 0;
    transform: translateY(-110%);
  }
}

/* 흰 면 전환(2026-08-09 C안)으로 문구가 상태의 1차 신호 — 600 → 700, slate-900 */
.toast-content {
  flex: 1;
  font-size: 15px;
  font-weight: 700;
  color: var(--color-slate-900);
}

.toast-close {
  width: 24px;
  height: 24px;
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-tertiary);
  transition: all var(--transition-fast);
  position: relative;
}

/* 히트영역 44px — 보이는 크기(24)는 그대로 두고 터치 최소치만 확보(히트박스 확대 idiom).
   모바일 한정: 데스크톱은 마우스라 24px + hover 면으로 충분하고, 여기서 44px 를 깔면
   토스트 우측 20px 이 ✕ 판정이 돼 「면 전체 탭」과 구분이 없어진다. */
@media (max-width: 768px) {
  .toast-close::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 44px;
    height: 44px;
  }
}

@media (hover: hover) {
  .toast-close:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
  }
}

/* ========================================
   17. Dropdown
   ======================================== */
/* 구 `.dropdown` · `.dropdown-menu` 삭제 (2026-08-25 사용자 확정 A안, UI 감사) —
   짝인 common.js `Dropdown` 모듈과 함께 제거. 템플릿 소비처 0 이었다.
   살아 있는 드롭다운은 각자 다른 어휘를 쓴다: `.tenant-switcher-dropdown`(top-nav) ·
   `.dropdown-anchor`/`.download-menu`(관원 목록 내보내기) · `.custom-select-dropdown`. 재도입 ❌ */








/* ========================================
   18. Tabs — Text Hierarchy (2026-06-18, 구 slate-900 솔리드 pill·Underline rail 폐지)
   ========================================
   활성 = emerald-700 글자 + 800 weight (채움·테두리·밑줄·rail 전면 없음).
   정책 상세 = 아래 .tab-item 블록 주석 참조.
   스코프 = 화면 전환만 (2026-08-09 사용자 확정 「탭=화면 전환·칩=거르기」 —
   필터 목적 탭은 .filter-chips 로 전환, 현존 사용처 = admin/payments/index 뷰 전환 뿐).

   마크업: <div class="tabs"> 안에 <a class="tab-item active"> 또는
   <label class="tab-item"><input type="radio" checked> ...</label> 두 패턴 모두 지원.
   카운트: 알람/주의(결제 대기 등) = .tab-badge / 단순 분류 = .tab-count */
.tabs {
  display: inline-flex;
  gap: 6px;
  /* 음수 상·좌 마진 = tab-item 히트박스 패딩(상 8px·좌 12px) 상쇄 (2026-08-14 사용자 확정 C안,
     시안 payments-tab-top-gap) — 탭 텍스트가 페이지 시작선(카드 모서리)에 앉는다. 패딩 자체를 깎으면
     터치·클릭 영역이 줄므로 상쇄만. 상쇄 없이 두면 탭 페이지만 첫 콘텐츠가 8px 더 내려앉고
     좌측 시작선도 12px 안쪽에서 시작해 다른 페이지와 어긋난다. */
  margin: -8px 0 var(--space-5) -12px;
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
  scroll-snap-type: x proximity;
}
.tabs::-webkit-scrollbar { display: none; }

/* ---- 탭 + 툴바 한 행 `.tabs-row` (2026-09-16 사용자 확정 A안 · 시안 `tabs-toolbar-row`) ----
   탭 바(화면 전환)와 그 화면의 툴바(칩·검색·버튼)를 **세로로 두 줄 쌓지 않고 한 행**에 둔다.
   구 상태는 탭 줄(37px) 아래 툴바 줄(35px)이 따로 서서 목록이 122px 에서 시작했고 탭 줄 오른쪽
   ≈860px 이 통째로 비어 있었다 — products · payments · system tenants/accounts 네 화면이 같은 모양.
   구조 = `.tabs-row > .tabs + .tabs-row-slot…` — 슬롯은 `display: contents` 로 안의 툴바를 행에
   흘려보내 **서버 조각 교체 단위·`hidden` 토글 단위는 그대로** 둔다(products 부분 갱신 · payments 뷰 토글).
   - 시작선 상쇄(-8/-12)는 `.tabs` 의 것을 승계하되 **위는 행이, 왼쪽은 탭 아이템이** 진다 — 행의 왼쪽을 -12 로
     당기면 아래 줄로 접힌 전폭 아이템(칩 줄·필터 패널)까지 12px 왼쪽에서 시작한다(2026-09-16 실측 900px).
     탭만 음수 마진이면 탭 텍스트는 페이지 시작선에 앉고(2026-08-14 C안) 나머지는 콘텐츠 폭 그대로
   - `container-type` = 폭이 모자랄 때 «칩 줄만» 아래로 접는 판정축. 뷰포트 ❌ — 사이드바·드로어에 따라
     같은 뷰포트의 콘텐츠 폭이 갈린다(§액션 행 「판정축이 뷰포트가 아니라 카드 폭」). 임계는 페이지가
     자기 칩·버튼 폭으로 정한다(행 구성이 바뀌면 재측정)
   - 폰(≤768)은 탭이 제 줄을 갖고 툴바가 아래로 — 층 «순서»는 페이지 `order` 가 정한다
   - ⚠ `.tabs-row-slot { display: contents }` 무조건 선언 ❌ — `[hidden]`(UA, 0,1,0)과 동특이도라 뒤
     선언이 이겨 숨긴 슬롯이 그대로 보인다. `:not([hidden])` 게이트 의무(`.msg-auto-grid` 와 같은 함정) */
.tabs-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--space-4);
  row-gap: 10px;
  margin: -8px 0 var(--space-4);
  container-type: inline-size;
}
.tabs-row > .tabs { margin: 0 0 0 -12px; flex: 0 0 auto; }
.tabs-row-slot:not([hidden]) { display: contents; }
@media (max-width: 768px) {
  .tabs-row > .tabs { flex: 0 0 100%; }
}

/* 오버플로 페이드 마스크 — Tabs.init() 이 scroll 위치 기반으로 토글 (common.js).
   양쪽 모두 오버플로 시 좌·우 페이드. 데스크톱(컨텐츠 폭 충분)에서는 mask 미적용 → 마지막 탭 fade 회귀 차단. */
.tabs.tabs-overflow-right:not(.tabs-overflow-left) {
  -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 24px), transparent 100%);
  mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 24px), transparent 100%);
}
.tabs.tabs-overflow-left:not(.tabs-overflow-right) {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 24px, #000 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 24px, #000 100%);
}
.tabs.tabs-overflow-left.tabs-overflow-right {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 24px, #000 calc(100% - 24px), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 24px, #000 calc(100% - 24px), transparent 100%);
}

/* Text Hierarchy 탭 (2026-06-18, slate-900 솔리드 pill supersede)
   활성 = emerald-700 글자 + 800 weight (채움·테두리·밑줄 없음)
   비활성 = slate-400 + 500 weight, hover = slate-700
   위계를 색+굵기로만. 밑줄형 indicator 는 여전히 금지(링크 혼동) — 활성은 글자색·굵기로만 표현. */
.tab-item {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  /* slate-500 승격 — 탭 이름이 「이 탭이 무엇인가」의 유일한 신호다 (§slate-400 승격 기준 · ADR 0495).
     대비 2.45 → 4.55:1. 활성 탭과의 구분 축은 «색이 아니라 굵기·밑줄»이라 승격해도 안 무너진다.
     slate-400 복귀 ❌ */
  color: var(--color-slate-500);
  text-decoration: none !important;
  cursor: pointer;
  letter-spacing: -0.01em;
  white-space: nowrap;
  transition: color 0.15s;
  background: transparent;
  border: 0;
  border-radius: 0;
  scroll-snap-align: start;
}

.tab-item input[type="radio"] {
  display: none;
}

@media (hover: hover) {
  .tab-item:hover:not(.active):not(:has(input:checked)) {
    color: var(--color-slate-700);
    background: transparent;
  }
}

.tab-item.active,
.tab-item:has(input:checked) {
  color: var(--color-primary-700);
  font-weight: 800;
  background: transparent;
}

/* .tab-badge / .tab-count — 인라인 숫자 (Text Hierarchy 갱신 2026-06-18, Hairline Pill supersede)
   캡슐·테두리 제거 → 라벨 옆 작은 tabular 숫자.
   count·badge 모두 비활성 slate-500 / 활성 emerald-600 — 지금은 «시각이 같고» 이름이 «역할»만 가른다
   (count=분류 · badge=알람). 구 「count 비활성 slate-300」 서술은 ADR 0495 승격(아래 `.tab-count` 주석)
   이후 거짓이라 정정 — 시각을 다시 가르려면 그 승격을 되돌리지 말고 badge 쪽 «활성 변형»으로 벌릴 것. */
.tab-badge,
.tab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: auto;
  height: auto;
  padding: 0;
  font-size: 12.5px;
  font-weight: 700;
  background: transparent;
  border: 0;
  border-radius: 0;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  margin-left: 0;
}

/* slate-500 승격 — 탭 옆 개수는 «읽어야 하는 값»이다. 구 slate-300 은 흰/slate-50 면에서
   대비 1.42:1 로 §slate-400 승격 기준보다도 낮았다 (ADR 0495). → 4.76:1.
   구분은 유지된다 — 실렌더에서 `.tab-badge`(알람/주의)는 emerald, `.tab-count`(단순 분류)는 slate 라
   **축이 「emerald ↔ 무채색」**이고 승격은 무채색 쪽의 «농도»만 올린다. slate-300 복귀 ❌ */
.tab-count {
  color: var(--color-slate-500);
}

/* 주의 카운트 = emerald 계열 상태 신호(`--status-success` 경유)와 같은 축이라 흰 면 위 대비 요건도 같다.
   기본값 slate-500 은 그대로 두고, emerald 로 뜨는 변형은 `--status-success`(=primary-700) 를 탄다 (ADR 0495) */
.tab-badge {
  color: var(--color-slate-500);
}

.tab-item.active .tab-badge,
.tab-item.active .tab-count,
.tab-item:has(input:checked) .tab-badge,
.tab-item:has(input:checked) .tab-count {
  background: transparent;
  border-color: transparent;
  color: var(--color-primary-600);
}

@media (max-width: 768px) {
  .tab-item {
    padding: 9px 13px;
    font-size: 13px;
  }
}

/* ========================================
   19. Pagination
   ======================================== */
.pagination {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}






/* §잠긴 값 칸 토큰과 같은 축 — opacity ❌, 색으로만 (구 .5) */


/* ========================================
   20. Empty State (Typography-only — D.MONO)
   - 모든 페이지 빈 상태 통일 토큰. 페이지별 인라인 style 오버라이드 금지.
   - 2026-06-24: 장식 아이콘 + emerald ambient wash 폐지 (§아이콘 장식 SVG 최소화 ·
     frontend-guide §17 운영 surface ambient 초록 워시 제거). 빈 상태 정체성 = 800 타이틀 typography.
   - 변형은 padding 강도(intensity)로만 구분.
   ======================================== */
/* 시스템 콘솔 breadcrumb(구 .sys-crumb — 2026-07-20 에 .td-crumb/.sd-crumb/.smi-back 4곳을 흡수했던
   토큰)는 2026-08-20 사용자 확정 B안(시안 back-link-unify)에서 글로벌 .back-link 로 한 번 더 통합됐다.
   admin 드릴다운과 같은 역할(복귀 액션)인데 아이콘 15px·hover 600·모바일 히트박스 없음으로 갈려 있었다.
   정의 = §드릴다운 복귀 back 링크(.back-link). .sys-crumb 재도입 ❌ */

/* ── system 목록 공유 어휘 — tenants/list · settlements/list 페이지 사본 승격 (2026-07-27 UI 감사) ── */
/* Toolbar — 좌 상태 필터 칩 / 우 검색 단일 행 (2026-08-09 「칩=거르기」 통일 — 구 .tabs 필터 supersede) */
.sys-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: var(--space-4);
}
.sys-toolbar .filter-chips {
  flex: 1 1 auto;
  min-width: 0;
}
/* 검색 기본 폭 240 — 구 320 은 칩 7개(ADR 0417)와 함께 1280px(콘솔 최소 폭)에서 17px 넘쳐
   「시스템 정지」 칩 하나가 홀로 아랫줄로 떨어졌다(툴바 35 → 78px, 2026-08-23 실측:
   콘텐츠 977 vs 칩 자연폭 682 + gap 12 + 검색 320 = 1014). 240 이면 여유 43px 라 도장이 늘어
   카운트가 세 자리가 돼도 한 줄을 지킨다 — placeholder 「도장명, 대표자 검색」 실측 108px 이라 잘림 0.
   320 복귀 ❌ · 칩이 더 늘면 이 값이 아니라 칩 구성을 다시 잰다. */
.sys-search-box { flex: 0 1 240px; }
.sys-search-box .search-input { width: 100%; }

/* 축 탭이 있는 두 목록(tenants · accounts)은 툴바가 탭 행(`.tabs-row`, §Tabs)의 아이템이 된다
   (2026-09-16 사용자 확정 A안 · 시안 `tabs-toolbar-row`) — [도장 | 계정] · [칩] · [검색] 한 줄.
   래퍼는 `display: contents` 라 칩·검색이 행에 직접 놓이고, 검색은 `margin-left: auto` 로 오른쪽 끝.
   폭이 모자라면 «칩 줄만» 아래로 — 임계는 칩 집합에 종속이라 **페이지가 소유**(tenants 7칩 1060 · accounts 2칩 640,
   각 템플릿 `@container` 주석 참조). 여기에 한 값으로 두면 칩 둘뿐인 accounts 가 1280px 에서 괜히 두 줄이 된다(2026-09-16 실측).
   settlements · aligo-reconcile 는 축 탭이 없어 대상 밖 — 위 한 행 규칙 그대로. */
.tabs-row > .sys-toolbar { display: contents; }
.tabs-row .sys-search-box { margin-left: auto; }

/* ── 시스템 콘솔 모바일 (≤768) — 2026-08-26 사용자 확정 B·B안 · 시안 `system-console-mobile` ──
   콘솔은 여기까지 「데스크톱 전용」이었다([[0413]] ③ 이 `/system/*` 을 모바일 안내 대상에서 뺀 근거).
   그 전제를 접고 폰을 지원한다 — 그 결과 이 파일이 지는 몫은 셋:
   ① 바텀 네비가 «없는» 셸의 하단 여백  ② 툴바 세로 재배치  ③ 목록 카드 어휘.
   드로어(≤1024)·모바일 헤더(≤768) 자체는 기존 규칙·mobile-shell.css 그대로 — 신설 0. */
@media (max-width: 768px) {
  /* mobile-shell.css 는 바텀 네비(64px)가 있다는 전제로 `.main-content` 하단을 비운다.
     콘솔엔 그 네비가 없어(메뉴 6 / 슬롯 4 → 진입점 분열) 그대로 두면 스크롤 끝에 빈 64px 이 남는다.
     같은 파일이 꼬리 블록 margin 을 0 으로 눌러 두므로 여백 소유자는 여기 하나뿐 — 24px = 그
     주석이 말하는 「네비 64 + 여백 24」의 여백분과 같은 값. `.page-container` 로 되돌리기 ❌
     (그쪽 하단 0 은 「하단 여백은 main-content 단독 책임」 규약이다). */
  body.system-admin .main-content { padding-bottom: 24px; }

  /* 툴바 = 세로 2층. 구 가로 1행은 폰에서 «둘 다» 깨졌다 (390px 실측): 칩이 4줄로 접혀 툴바가
     200px 을 먹고, 남은 폭 87px 에 눌린 검색칸은 placeholder 「도장명, 대표자 검색」(108px)이 잘렸다.
     검색을 전폭 한 줄로 올려 폭 다툼을 없애고, 칩은 아래에서 줄바꿈으로 일곱 개 전부 보인다 (→ 96px).
     칩 축소·「+N 더보기」 ❌ — 카운트가 곧 운영 지표라 접으면 화면의 목적이 깎인다. */
  .sys-toolbar {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
  /* 검색이 먼저 — 데스크톱 시각 순서(좌 칩 / 우 검색)는 마크업이 소유하고, 폰의 층 순서만 CSS 가 뒤집는다 */
  /* 히트영역 44px — 보이는 것(글자 + 1.5px 밑줄)은 40px 그대로 (2026-08-26 사용자 확정 A안 ·
     시안 `boxed-touch-targets`). `<input>` 은 replaced element 라 `::after` 오버레이를 못 쓴다 →
     래퍼(`label`)가 44 를 지고 동값 음수 마진이 지오메트리를 되돌린다 = **툴바 높이 불변**.
     ⚠ 짝 = 마크업이 `label` 이어야 한다(`div` 면 확장 밴드 클릭이 포커스로 안 간다). 40px 로 되돌리기 ❌ */
  .sys-search-box {
    flex: 0 0 auto; order: -1;
    display: flex; align-items: center;
    min-height: 44px; margin-top: -2px; margin-bottom: -2px;
  }
  /* 탭 행 안에서는 층이 셋 — 탭(0) / 검색(1) / 칩(2). 위 `order: -1` 은 래퍼가 column 일 때의 값이라
     행에선 검색이 탭 «위»로 올라간다 → 순서를 다시 준다. 검색은 전폭(래퍼 stretch 가 사라져 자기 폭이 된다). */
  .tabs-row .sys-search-box { order: 1; flex: 0 0 100%; }
  .tabs-row .sys-toolbar .filter-chips { order: 2; flex: 0 0 100%; }
}

/* Table — D.MONO td 강조 13px 700 slate-900 */
.sys-tenant-name { font-weight: 700; color: var(--color-slate-900); }

/* ── 콘솔 목록 모바일 카드 (≤768) — 데스크톱=표 / 모바일=카드 (§모바일 표현 분리 미러:
   관원 목록 `fragments/member-list-cards` · 결제 `.payment-cards-mobile` 과 같은 어휘).
   근거는 실측 가림 폭이다 — 도장 관리 237px · 정산 정보 165px · 구독 이상 현황 **427px**(그 화면의
   존재 이유인 «실패 사유»가 통째로 화면 밖이었다). 열 숨김 ❌ = 화면에서 정보가 사라지고,
   특히 「마지막 접속」은 [[0356]] 이 일부러 세운 열이라 숨기면 그 결정을 조용히 되돌린다.
   구조 = `.sys-card`(한 행) > 첫 줄 `.sys-card-head`(이름 + 뱃지 + 보조) + `.sys-card-kv`(라벨 값 나열).
   페이지별 사본 ❌ — 세 목록이 이 원자를 공유한다. */
.sys-cards { display: none; }

@media (max-width: 768px) {
  /* 표만 접는다 — 페이지네이션(.table-footer)은 두 표현이 공유하는 «같은» 컨트롤이라 함께 살아 있다 */
  .sys-list-card > .table-container { display: none; }
  .sys-cards { display: flex; flex-direction: column; gap: 8px; }

  /* 목록 카드의 «면»을 폰에서만 걷는다 — 표를 담던 흰 카드 안에 다시 카드가 들어가면 면이 두 겹이 된다.
     DOM 을 재배치하는 대신 면만 투명하게 하는 이유 = 데스크톱 렌더를 픽셀 단위로 보존하기 위해서다
     (카드 밖으로 옮기면 페이지네이션이 표 카드에서 떨어져 나가 데스크톱이 함께 바뀐다). */
  .sys-list-card {
    background: none;
    border: 0;
    border-radius: 0;
    overflow: visible;
  }
  .sys-list-card > .table-footer {
    background: none;
    border-top: 0;
    padding: 16px 0 0;
  }
  /* 빈 상태 = 콘솔 폰에서만 압축 (2026-08-26 사용자 확정 B안 · 시안 `system-console-mobile-2`).

     ⚠️ 이 규칙은 `mobile-shell.css` 의 **의도된 「빈 상태가 뷰포트를 채운다」 규칙을 끈다** —
     `.empty-state:not(.empty-state-sm) { min-height: clamp(280px, 100dvh - 356px, 520px) }`.
     그쪽 목적은 「콘텐츠가 짧을 때 하단에 휑한 공백이 남지 않게」이고, 356 은
     **관장 셸 기준 계산**이다(mobile-header 56 + **bottom-nav 64** + main-pb 64 + page-padding 40 + 상단 콘텐츠 132).

     콘솔에는 그 셸이 없다 — 바텀네비 0 · main-pb 24 라 가용 높이가 약 100px 더 크고,
     실측 결과 그 규칙을 그대로 받아도 488px 로 **바닥에 안 닿아 회색 공백이 남았다**(목적 미달성).
     즉 콘솔에서는 「채우기」가 성립하지 않아, 두 줄 안내에 화면 58%(488/844)만 쓰고 있었다.
     → 채우기를 포기하고 압축을 고른다. **아래 회색 공백은 인지된 대가** — 이 화면은 위에 툴바
     (검색 + 칩 7개, 140px)가 있어 「덜 그려진 화면」이 아니라 「목록이 여기서 끝」으로 읽힌다.

     ⚠️ 되살리려면 `min-height: 0` 대신 **콘솔 셸로 재계산한 값**(≈ `100dvh - 232px`)을 줄 것 —
     356 을 그대로 쓰는 것은 관장 셸 전제라 어느 쪽 목적도 못 채운다.
     줄이는 것은 **여백뿐** — 카드 면·문구·CTA·가운데 정렬·[[0419]] 리듬(gap 6 / actions mt 16) 불변.
     스코프 = `body.system-admin` (관장 화면 확장 ❌ — 거기선 카드 목록이 자기 빈 상태를 그려 이 요소가 안 뜬다).
     데스크톱 불변 · 「폰↔PC 여백이 갈린다」·「하단 공백」 감사 재지적 ❌ (인지된 대가) */
  body.system-admin .empty-state {
    min-height: 0;
    padding: 36px 24px 32px;
  }

  /* 페이저 = 모바일 카드 목록의 44px 어휘 (`.pagination-bar .pagination-btn` 값 그대로).
     그쪽은 관장 목록이 «JS 로 새로 그리는» 모바일 전용 바라 이 마크업(데스크톱 `.table-footer` 공유)에는
     안 닿는데, 카드 목록이 된 이상 손가락으로 누르는 것은 같다 — 값이 갈리면 같은 페이저가
     셸마다 다른 크기가 된다. 데스크톱 32px 불변 (2026-08-26 · ADR 0466 후속) */
  .sys-list-card .pagination-btn {
    min-width: 44px;
    min-height: 44px;
    font-size: 16px;
  }
  /* 면을 걷은 카드의 헤더(구독 이상 현황 「결제 실패 유예 구독 · 총 5건」)는 그대로 «섹션 머리글»이 된다 —
     좌우 padding 을 페이지 rail 에 맞추고 경계선을 뗀다. 제목·건수를 지우기 ❌ (두 표를 가르는 유일한 낱말) */
  .sys-list-card > .card-header {
    padding-left: 0;
    padding-right: 0;
    border-bottom: 0;
  }
  /* 0건 본문도 같은 rail — 면이 없는데 카드 padding 만 남으면 제목보다 16px 안쪽에서 시작한다 */
  .sys-list-card > .card-body {
    padding-left: 0;
    padding-right: 0;
  }
  /* 「총 N건」 머리 행도 같은 처리 — `.card-header` 와 같은 «섹션 머리글»인데 이 목록에 없어서
     면을 걷은 카드 위에 흰 띠 하나만 남고 시작선이 16px 안으로 밀렸다 (message-topups 실측,
     accounts `.acc-scope-note` 와 같은 함정). min-height 50 은 불변 — morph 자리 예약이다.
     데스크톱 렌더 불변 · 콘솔 목록에 `.list-card-head` 를 새로 쓰면 여기가 이미 받는다 (2026-09-04 UI 감사) */
  .sys-list-card > .list-card-head {
    padding-left: 0;
    padding-right: 0;
    background: none;
    border-bottom: 0;
  }

  /* 카드 = 행 하나. 전체가 링크라 `<a>` 가 소유자 — 표의 `.sys-row-clickable` 위임 JS 는
     여기서 필요 없다(진입이 이미 실제 링크다 = 새 탭·prefetch·키보드 그대로). */
  .sys-card {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 13px 14px;
    background: var(--bg-primary);
    /* 카드 토큰 = 글로벌 `.card` 와 한 벌 (2026-08-30 UI 감사) — 구 `--border-default`(slate-200) +
       radius 12 는 D.MONO 카드 hairline 보다 진해 카드가 표처럼 읽혔다. 선례 = `mobile-shell.css`
       `.card-list-item` 이 같은 이유로 교정된 자리(2026-08-12). 8~12 재도입 ❌ */
    border: 1px solid var(--border-hairline);
    border-radius: 14px;
    text-decoration: none;
    color: inherit;
  }
  .sys-card-main { flex: 1; min-width: 0; }
  .sys-card-head {
    display: flex;
    align-items: center;
    gap: 7px;
    flex-wrap: wrap;
  }
  .sys-card-name {
    font-size: 14px;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--color-slate-900);
  }
  /* 보조 줄(「0일째」·「9일 남음」)은 표에서 쓰던 `.sys-setup-elapsed` 를 그대로 재사용 —
     카드 전용 사본을 만들면 임계 강조(is-stalled red)가 두 곳으로 갈린다 */

  /* 라벨 값 나열 — 표의 나머지 열 전부. `column-gap 14px` 로 한 줄에 두세 쌍이 들어가고,
     안 들어가면 줄바꿈한다(잘림 0). 값은 숫자·날짜가 많아 tabular-nums 의무(§숫자). */
  .sys-card-kv {
    display: flex;
    flex-wrap: wrap;
    gap: 2px 14px;
    margin-top: 8px;
  }
  /* 라벨·값 톤 = 데스크톱 같은 표와 «쌍»으로 맞춘다 (2026-08-30 UI 감사) — 구 slate-400/600 은
     th slate-500 · td `--text-primary` 인 데스크톱과 갈려 «같은 값이 폭에 따라 두 위계»였다.
     라벨만 올리면 값과 100pt 차만 남아 위계가 얇아지므로 한쪽만 되돌리기 ❌.
     (§slate-400 승격 기준 3행 「옆에 값이 있는 라벨」 + `.sys-la-rel` 승격 선례와 같은 축) */
  .sys-card-kv > span {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-slate-500);
    white-space: nowrap;
  }
  .sys-card-kv b {
    margin-left: 5px;
    font-weight: 700;
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
  }
  /* 금액만 강조 = 데스크톱 `td.sub-amt`(800/slate-900/-0.025em) 와 «같은 토큰»
     (2026-08-26 사용자 확정 2-B안 · 시안 `system-console-card-polish`).
     그 셀 주석이 「한쪽만 고치면 콘솔 두 표가 다시 갈린다」로 못박은 자리라, 카드도 함께 올린다 —
     구독 이상 현황은 「얼마가 언제 빠지나」를 보는 화면이고 금액이 1차 값인데 카드에선
     이웃 날짜와 같은 무게였다. 단위 「원」 분리(`.pay-amount-currency`)는 미채택 —
     12px 값 안에서 11px 단위는 폰에서 차이가 안 보이고 kv 값이 3원자가 된다(2-C 배제). */
  .sys-card-kv b.sub-amt {
    font-weight: 800;
    color: var(--color-slate-900);
    letter-spacing: -0.025em;
  }

  /* 보조 라벨(「0일째」·「9일 남음」)은 표 셀 인라인용 `margin-left: 6px` 를 갖고 오는데,
     flex head 의 `gap: 7px` 에 더해져 13px 이 되어 옆 「이름↔뱃지」 7px 과 어긋났다 —
     카드 스코프에서만 중화한다 (2026-08-26 사용자 확정 1-B안 · 시안 `system-console-card-polish`).
     원자 재사용은 그대로 = 카드 전용 사본 신설 ❌ (임계 강조 `is-stalled` red 가 두 곳으로 갈린다).
     표 셀은 불변 — 거기선 gap 이 없어 그 6px 이 유일한 간격이다. */
  .sys-card-head .sys-setup-elapsed { margin-left: 0; }

  /* 자유 문장 줄 — 구독 이상 현황의 실패 사유처럼 «칸에 안 들어가는» 값 전용.
     `.sys-card-kv` 의 nowrap 과 다른 자리라 별도 원자 (한 줄에 억지로 넣으면 다시 잘린다) */
  .sys-card-note {
    display: block;   /* 소비처가 <p>·<span> 두 벌 — 인라인이면 아래 margin-top 이 죽는다 */
    margin: 7px 0 0;
    font-size: 12px;
    line-height: 1.6;
    color: var(--text-secondary);
  }
  /* 카드 안 액션 (구독 이상 현황 「지금 재결제」) — 표의 액션 열이 접히는 자리.
     좌측 정렬 = 카드 본문 좌측 시작선과 한 축 (푸터 우정렬 어휘는 모달·바가 소유) */
  .sys-card-action { margin-top: 10px; }

  /* 카드 도장명 링크 = 탭 판정 44px (2026-08-26 사용자 확정 A안 · 시안 `system-console-mobile-2`).
     §히트박스 확대 idiom 의 「보이는 상자 없음」 갈래 — 면·테두리가 없는 인라인 링크라 `::after`
     오버레이로 **보이는 크기 불변**(실측 72×21 → 판정 45px). 구독 이상 현황은 카드에 실행 버튼이 있어
     **카드 전체가 링크가 될 수 없는 유일한 목록**이고, 이 링크가 도장 상세로 가는 유일한 길이다.
     아래로 겹치는 것은 클릭 대상이 아닌 kv 텍스트라 판정 충돌 0 · padding 확대 ❌(첫 줄이 밀린다) */
  .sys-card-head .sys-row-link {
    position: relative;
    /* 카드 제목 체급 되돌리기 — `.sys-row-link { font-weight: inherit }`(표 셀 톤 상속용, 이 블록
       «뒤» 선언)이 `.sys-card-name`(800)과 동특이도라 소스 순서로 이겨, 폰 카드에서 **클릭 가능한**
       도장명만 400 으로 빠졌다. 링크가 없는 「A도장 외 N곳」은 800 그대로라 위계가 뒤집혔다
       (실측 fw 400 ↔ 800, 2026-09-07 UI 감사). 특이도 (0,2,0) 이라 순서 무관하게 이긴다.
       `.sys-row-link` 쪽을 800 으로 올려 고치기 ❌ — 그건 표 셀에서 td 톤 상속을 깬다 */
    font-weight: 800;
  }
  .sys-card-head .sys-row-link::after {
    content: "";
    position: absolute;
    inset: -12px -6px;
  }
}

/* 온보딩 경과일 — 초기설정 배지 옆 관측값 (ADR 0178).
   30일 이상은 red 텍스트 — 시맨틱 색 등재 예외(운영자 콘솔 한정, 관장 대면 아님):
   「어느 도장을 먼저 볼지」 고르려면 훑을 대상이 한 눈에 잡혀야 하고, 여기 red 는
   지각 신호와 같은 경계를 지킨다 — 텍스트만, 면 채움·테두리·아이콘 ❌.
   자동 동작은 붙지 않는다 (ADR 0175 = 온보딩 전엔 과금·잠금 없음). */
.sys-setup-elapsed {
  margin-left: 6px;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-slate-900);
  font-variant-numeric: tabular-nums;
}
/* 흰 표 셀·흰 .sys-card 위 12px 글자라 `--status-error-ink` ([[0495]] 3.76→4.83:1).
   CSS 주석의 「시맨틱 색 등재 예외(운영자 콘솔 한정)」는 red 를 «쓰는 것»의 예외이지 농도 규정이 아니다. */
.sys-setup-elapsed.is-stalled { color: var(--status-error-ink); }

/* Row affordance — 행 자체가 클릭 타겟임을 chevron 으로 시각화 (iOS settings 패턴) */
.sys-action-cell {
  text-align: right;
  width: 32px;
}
/* 구 .sys-row-arrow(18px/stroke 2/slate-300)는 글로벌 .row-chevron 에 흡수 (ADR 0371).
   표 셀 안이라 baseline 이 아닌 셀 중앙에 앉아야 한다 — 그 보정만 여기 남는다. */
.sys-action-cell .row-chevron {
  display: inline-block;
  vertical-align: middle;
}

/* 진입 링크 — 셀 톤 상속(도장명 700 slate-900), hover 만 emerald 신호 */
.sys-row-link {
  color: inherit;
  font-weight: inherit;
}
.sys-row-link:focus-visible {
  color: var(--color-primary-700);
}
@media (hover: hover) {
  /* 링크 직접 hover 도 같은 신호 — 행 전체가 클릭이 아닌 표(subscriptions/list 도장명)에서도 어포던스 유지 */
  .sys-row-clickable:hover .sys-row-link,
  .sys-row-link:hover {
    color: var(--color-primary-700);
  }
}

/* 제목 옆 건수 카운트 — 순수 텍스트 12px 회색. 관장 「예약 이용권」 헤더 어휘를 글로벌 승격해
   system 콘솔(정산·구독·관원 관리·이용권 목록)과 단일 어휘 (2026-07-27 UI 감사 사용자 확정, 구 800/700 방언 폐지) */
/* slate-500 승격 — 개수는 «읽어야 하는 값»이고 그 자리의 유일한 신호 (§slate-400 승격 기준 · ADR 0495).
   대비 2.56:1 → 4.76:1. slate-400 복귀 ❌ */
.history-count { font-size: 12px; color: var(--color-slate-500); font-variant-numeric: tabular-nums; }

/* 세로 리듬 = 부모 gap 이 소유 (2026-08-23 사용자 확정 A안, 시안 `empty-state-rhythm`).
   구 상태는 간격을 자식 margin-bottom 이 나눠 가져(제목 6 = 제목→설명 · 설명 22 = 설명→버튼)
   **셋 다 있을 때만** 계산이 맞았다 — 조각이 빠지면 그 조각이 들고 있던 간격도 같이 사라지거나
   갈 곳 없는 유령 마진으로 남는다 (실측 24곳 중 10곳 붕괴):
   제목+버튼(passes/products) 제목→버튼 **6px** · 제목+설명(5곳) 하단 78 · 제목만(3곳) 하단 54 ·
   제목+설명+숨겨진 버튼칸(pass-extension) 하단 70. 「위 64 / 아래 56」 약속이 절반에서만 지켜졌다.
   gap 6 + .empty-state-actions margin-top 16 = 22 → **구 설명문이 들고 있던 값 그대로**라
   셋 다 있는 14개 화면은 픽셀 변화 0. 자식 margin-bottom 으로 되돌리기 ❌ */
.empty-state {
  padding: 64px 24px 56px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.empty-state-title {
  font-size: 19px;
  font-weight: 800;
  color: var(--text-primary);
  letter-spacing: -0.015em;
  /* 구 margin-bottom: 6px → 부모 gap 이 인계 (위 주석). 설명문이 없는 빈 상태에서
     이 값이 곧 제목↔버튼 간격이 돼 버튼이 제목에 달라붙었다 */
}

/* 본문 설명 — empty-state-description 단일 클래스.
   구 주석의 「레거시 .empty-state-text / .empty-state-desc 는 동일 스타일로 호환 매핑」은
   사실이 아니었다 (2026-08-23 UI 감사 정정): 그 두 이름은 **CSS 선언도 0건 · 템플릿 사용처도 0건**으로,
   호환 매핑이 있다고 믿고 새로 쓰면 스타일 없이 렌더된다. 별칭 신설 ❌ — 이 클래스 하나만 쓴다. */
.empty-state-description {
  font-size: 14px;
  color: var(--text-secondary);
  line-height: 1.6;
  max-width: 380px;
  margin: 0 auto; /* 구 하단 22px → .empty-state-actions margin-top 이 인계 (액션이 없으면 여백도 없다) */
  text-wrap: balance; /* 두 줄 이상 시 줄 길이 균형 (<br> 금지 정책 — CSS 로만 줄바꿈 제어) */
}

.empty-state-description strong {
  color: var(--text-primary);
  font-weight: 700;
}

/* CTA 영역 — 여러 액션이 들어갈 수 있음.
   margin-top 16 = 부모 gap 6 과 합쳐 「본문 → 액션」 22px (구 설명문 하단 마진 승계).
   액션 앞에서만 한 단 벌어지므로 제목+버튼 조합도 22px 로 같은 리듬을 받는다. */
.empty-state-actions {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 16px;
}

/* 안이 전부 숨겨진 버튼칸은 자리 자체를 비운다 — pass-extension 「다시 시도」는 로드 실패 때만
   노출돼(`style.display` 토글) 평소엔 높이 0 의 빈 flex item 으로 남는다. 가드가 없으면
   위 margin-top 이 그 빈 칸 앞에 22px 을 깔아 하단 여백이 다시 부푼다.
   `:empty` 는 공백 텍스트 노드 때문에 안 걸리므로 `:has()` 로 「보이는 자식 유무」를 본다. */
.empty-state-actions:not(:has(> *:not([hidden]):not([style*="display:none"]):not([style*="display: none"]))) {
  display: none;
}

/* CTA 2개 이상 = 모바일 전폭 스택 (2026-08-05 사용자 확정 E2안).
   가운데 정렬 자연 폭 2버튼은 좁은 폭에서 라벨·아이콘 차이가 그대로 폭 차이로 남는다 —
   375px 실측 「✕ 필터 초기화」135 ↔ 「관원 등록」97 (편차 38px). 같은 무게의 선택지처럼 보이는
   자리라 어긋남이 도드라진다. 에러 페이지 `.error-actions` 가 ≤480px 에서 쓰는 것과 같은 문법.
   `:has(.btn + .btn)` 로 2개 이상일 때만 — 단일 CTA(「첫 관원 등록하기」·「다시 시도」)는
   자연 폭 유지가 맞다(전폭으로 늘리면 빈 화면에서 과하게 무거워진다). */
@media (max-width: 768px) {
  .empty-state-actions:has(.btn + .btn) {
    display: flex;
    flex-direction: column;
    width: 100%;
    /* 상한 = 바로 위 .empty-state-description 과 같은 380px 컬럼.
       빈 상태는 카드 폭을 그대로 받아서, 상한이 없으면 768px 에서 버튼 하나가 688px 이 된다
       (2026-08-05 실측) — 전폭 스택은 폰에서 「터치 타깃 최대」라 고른 것이지 면적을 키우려던 게 아니다.
       375px 에선 가용 폭(295px)이 상한보다 좁아 값이 걸리지 않는다 = 확정한 모습 그대로. */
    max-width: 380px;
    margin-left: auto;
    margin-right: auto;
  }
  .empty-state-actions:has(.btn + .btn) .btn {
    width: 100%;
  }
}

/* 변형 1: filter — 필터·검색 결과 0. 기본 .empty-state 와 동일 (wash 폐지로 차이 없음, 의미 클래스로 유지) */

/* 변형 2: neutral — 조용한 안내·에러 빈 상태 (payments·pass-extension 의
   「다시 시도」 동반 에러 상태 포함 — 실효는 padding 축소뿐이라 액션 유무 무관. 2026-08-07 UI 감사 주석 정정.
   구 셋째 사용처 billing-history 페이지는 ADR 0376 폐지) */
.empty-state.empty-state--neutral {
  padding: 56px 24px 48px;
}

/* Compact 변형 — 카드 안 좁은 영역 (회원상세 이용권 미등록 등).
   admins.html 의 .admins-page .empty-state-sm 같은 페이지 scoped override 는 그대로 동작 (later wins). */
.empty-state-sm {
  padding: 28px 16px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.empty-state-sm > p {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-secondary);
  margin: 0;
}
/* 빈 상태 각주 = §타이포 보조 각주/힌트 토큰(12.5/400/slate-500/1.55). 위 `> p` 가 직접 자식
   선택자라 span 에 안 닿아 page-local 사본이 쌓였다 — pricing `.ph-empty-note`·dashboard
   `.trend-empty-note`(2곳)·test-tools `.tt-empty-note` 로 **네 번째 사본**이 생기며
   pricing 주석이 걸어 둔 승격 트리거에 도달(2026-08-30 UI 감사). 시각 변화 0.
   ⚠️ dashboard `.trend-kpi-note` 는 «KPI 타일 안»이라 이 슬롯이 아니다 — 흡수 대상 ❌
   (같은 토큰이지만 `.empty-state-sm` 밖이라 셀렉터가 못 닿는다). page-local 사본 재도입 ❌ */
.empty-state-sm > span {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);
  line-height: 1.55;
}


/* ========================================
   21. Avatar
   ======================================== */








/* ========================================
   22. Loading
   ======================================== */
.spinner {
  width: 20px;
  height: 20px;
  border: 2px solid var(--border-default);
  border-top-color: var(--accent-primary);
  border-radius: var(--radius-full);
  animation: spin 0.6s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* 인라인 스피너 (버튼 안 텍스트와 함께 표시 — 처리 중 피드백) */


.btn-loading {
  position: relative;
  color: transparent !important;
}

.btn-loading::after {
  content: '';
  position: absolute;
  width: 16px;
  height: 16px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: var(--radius-full);
  animation: spin 0.6s linear infinite;
}

.skeleton {
  background: linear-gradient(90deg, var(--bg-tertiary) 25%, var(--bg-hover) 50%, var(--bg-tertiary) 75%);
  background-size: 200% 100%;
  animation: skeleton 1.5s ease-in-out infinite;
  border-radius: var(--radius-sm);
}

@keyframes skeleton {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.skeleton-text {
  height: 16px;
  margin-bottom: var(--space-2);
}

.skeleton-title {
  height: 24px;
  width: 60%;
  margin-bottom: var(--space-3);
}

/* 응답 대기 표 스켈레톤 막대 — 데이터 셀(13~14px 텍스트) 자리표시.
   payments·pass-extension 공유 글로벌 (2026-07-30 UI 감사 3페이지 승격 — 구 .billing-skeleton-bar /
   .pay-skeleton-bar 페이지 사본 통합. billing-history 페이지는 ADR 0376 폐지). 치수만 — 애니·색은 .skeleton 소유. */
.skeleton-bar { height: 14px; }

/* fetch-swap 목록 갱신 중 신호 — 직전 결과 위 재조회 시 「결과 있음」과 구분 (system 콘솔 3곳, 2026-07-30 UI 감사).
   빈 표에서 시작하는 로딩은 스켈레톤(.skeleton-bar)이 담당 — 이건 이미 채워진 표의 재조회 전용. */
.is-refreshing { opacity: 0.55; pointer-events: none; transition: opacity 0.15s; }

/* ========================================
   23. Auth Pages
   --------------------------------------------------------------------
   구 2단 레이아웃(좌 폼 / 우 emerald photo-panel) 잔재 일괄 삭제 (2026-08-15 UI 감사).
   auth 는 [[0025]] 로 카드리스 단일 컬럼이 됐고 헤딩은 auth-hero 가 흡수해,
   .auth-page/-left/-right/-card/-header/-logo{,-icon,-text}/-title/-subtitle/-form/
   -illustration{,-icon,h2,p} · .pricing-card{,-title} · .pricing-item · .test-login-section
   전부 templates·static/js 참조 0건이었다 (선례 = 같은 파일 §Auth Hero 의 2026-08-05 정리).
   삭제 이유는 「안 쓰여서」만이 아니다 — 폐지된 어휘(emerald 솔리드 photo-panel · 흰 카드 래퍼 ·
   gradient 로고 타일)가 선언으로 남아 있으면 그게 재도입 근거로 읽힌다.
   ※ 랜딩 요금 카드는 별개 클래스(.pricing-card-single, landing.css) — 이 삭제와 무관.
   ======================================== */
.auth-container {
  width: 100%;
  max-width: 400px;
}

@media (max-width: 480px) {
  .auth-container {
    max-width: 100%;
  }
}
/* ⚠️ 위 두 블록은 «현재 사문» — 화면에 나온 적이 없다 (2026-09-04 UI 감사 실측).
   사용처는 `auth/add-tenant`·`auth/setup-quickstart` 둘뿐이고 둘 다 `[data-page="auth"]` 안이라
   §58 의 `[data-page="auth"] .auth-container`(0,2,0)가 `max-width: none` 으로 이긴다
   (미디어쿼리는 특이도를 올리지 않으므로 ≤480 블록도 마찬가지).
   남겨 두는 이유 = auth «밖» 신규 사용처가 생기면 이 기본값이 되살아나는 자리다.
   여기 값을 고쳐 auth 폭을 조정하려 하지 ❌ — auth 폭 SSOT 는 §58 + `--auth-col-width` 한 곳
   (§auth 카드리스 「조정은 여기 한 곳만」). 사용자 확정 = 삭제 대신 주석 박제. */

/* ========================================
   24. Member Card
   ======================================== */





.member-name {
  font-weight: var(--font-semibold);
  color: var(--text-primary);
  margin-bottom: 2px;
}



/* ========================================
   25. Settings
   ======================================== */
















/* ========================================
   26. Utilities
   ======================================== */
/* Flex */
.flex { display: flex; }
.d-flex { display: flex; }

.items-center { align-items: center; }







.flex-wrap { flex-wrap: wrap; }

/* Gap */


.gap-3 { gap: var(--space-3); }
.gap-4 { gap: var(--space-4); }



/* Margin */









.mb-3 { margin-bottom: var(--space-3); }
.mb-4 { margin-bottom: var(--space-4); }





/* Padding */



.p-4 { padding: var(--space-4); }






/* 구 .section-divider/.section-divider-sm = 2026-08-09 삭제 (사용처 0 dead CSS — ②-A 섹션 머리 통일) */







/* Width */


/* Display */
.hidden { display: none; }
.block { display: block; }
.inline-block { display: inline-block; }
.inline-flex { display: inline-flex; }

/* Text */



.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Cursor */


/* ========================================
   27. Search & Filter Components
   ======================================== */
/* Search Box */
.search-box {
  position: relative;
  display: flex;
  align-items: center;
}

/* 크기 = 쓰는 쪽 CSS 소유 (2026-08-21 아이콘 프래그먼트 승격). 지우면 width:auto 로 아이콘 소실 ❌ */
.search-box svg {
  position: absolute;
  left: 2px;
  width: 20px;
  height: 20px;
  color: var(--text-tertiary);
  pointer-events: none;
}

/* Underline 검색 (Text Hierarchy 2026-06-18, 박스 채움 supersede)
   탭/필터 텍스트 어휘와 일관 — 박스·라운드 제거, 밑줄 1.5px 만.
   focus = slate-900 밑줄 (단일 indicator, §27 정합). 데스크톱 글로벌. */
.search-input {
  width: 240px;
  height: 40px;
  padding: 0 8px 0 28px;
  font-size: 14px;
  font-weight: 500;
  border: 0;
  border-bottom: 1.5px solid var(--color-slate-200);
  border-radius: 0;
  background: transparent;
  color: var(--color-slate-900);
  transition: border-color var(--transition-fast);
}

.search-input::placeholder {
  color: var(--text-tertiary);
  font-weight: 500;
}

.search-input:focus {
  outline: none;
  border-bottom-color: var(--color-slate-900);
  box-shadow: none;
}

/* Filter Panel */
.filter-panel {
  padding: var(--space-4);
  background: var(--bg-secondary);
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-4);
}

/* 필터 패널이 열릴 때(display:none→block) 부드럽게 등장 — fade + slide-down. 닫힘은 즉시.
   JS 토글(인라인 display)·모바일 BottomSheet DOM 이동 모두 그대로 두고 등장 시점에만 재생 */
@media (prefers-reduced-motion: no-preference) {
  .filter-panel { animation: filterPanelReveal 0.22s ease; }
}
@keyframes filterPanelReveal {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}

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

.filter-chip {
  display: flex;
  align-items: center;
  cursor: pointer;
  position: relative;
}

/* display:none 은 키보드 포커스 제거 — 시각만 숨기고 탭 도달 유지 (frontend-guide §4, mqr-day-chip 선례) */
.filter-chip input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.filter-chip-label {
  padding: 6px 14px;
  font-size: var(--text-sm);
  font-weight: 700;
  background: var(--bg-primary);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-full);
  transition: all var(--transition-fast);
  color: var(--text-secondary);
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
}

@media (max-width: 768px) {
  .filter-chip-label {
    min-height: 44px;
    padding: 8px 16px;
  }
}

/* 활성 필터 칩 = emerald hairline (2026-08-09 사용자 확정 E안 — 구 Slate-900 솔리드 폐지)
   「활성 = emerald」 학습을 탭·활성 토큰·선택 카드와 한 줄로 통일. 채움 없음.
   .is-active = JS imperative 재렌더 칩 (input 없이 button 으로 쓸 때) */
.filter-chip input:checked + .filter-chip-label,
.filter-chip-label.is-active {
  border-color: var(--color-primary-300);
  color: var(--color-primary-700);
  font-weight: 800;
}

@media (hover: hover) {
  .filter-chip-label:hover {
    border-color: var(--color-slate-400);
    color: var(--color-slate-900);
  }
}

/* 활성 칩은 hover 시에도 emerald 유지 — 동일 명시도 hover 가 color 를 덮어
   활성 신호가 사라지는 회귀 차단. 구 사유였던 「모바일 탭 후 sticky :hover」는
   2026-08-11 hover 가드(§0 상단 주석)로 원인째 소멸 — 이 규칙은 이제 마우스 전용 방어. */
@media (hover: hover) {
  .filter-chip input:checked + .filter-chip-label:hover,
  .filter-chip-label.is-active:hover {
    border-color: var(--color-primary-300);
    color: var(--color-primary-700);
  }
}


/* ── 필터 토글 텍스트 버튼 — 목록 페이지 공통 (2026-07-27 UI 감사: members·payments 페이지 사본 글로벌 추출) ──
   탭/필터 Text Hierarchy 어휘: 채움·테두리 없음, 활성(.has-active/.has-filter/.is-open)=emerald 텍스트 + 인라인 카운트 뱃지 */
.filter-toggle {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: transparent;
  border: 0;
  padding: 0 10px;
  height: 40px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  color: var(--color-slate-500);
  cursor: pointer;
  transition: color 0.15s ease;
}
/* 크기 = 쓰는 쪽 CSS 소유 (2026-08-21 아이콘 프래그먼트 승격). 지우면 width:auto 로 아이콘 소실 ❌ */
.filter-toggle svg { width: 18px; height: 18px; color: var(--color-slate-400); transition: color 0.15s ease; }
@media (hover: hover) {
  .filter-toggle:hover:not(:disabled) { color: var(--color-slate-700); }
}
@media (hover: hover) {
  .filter-toggle:hover:not(:disabled) svg { color: var(--color-slate-500); }
}
.filter-toggle.has-active,
.filter-toggle.has-filter,
.filter-toggle.is-open { color: var(--color-primary-700); }
.filter-toggle.has-active svg,
.filter-toggle.has-filter svg,
.filter-toggle.is-open svg { color: var(--color-primary-600); }
.filter-toggle-badge {
  display: inline-flex;
  align-items: center;
  margin-left: 2px;
  /* 흰/slate-50 면 위 12.5px 글자 → primary-700 (3.60 → 5.24:1, ADR 0495). 600 복귀 ❌ */
  color: var(--color-primary-700);
  font-size: 12.5px;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 768px) {
  .filter-toggle { padding: 0 12px; height: 44px; }
}

/* 폼 옵션 변형 — 폼 "입력값" 선택 칩은 emerald 채움 (SSOT 「폼 옵션 선택 = emerald」, 2026-07-27 사용자 확정).
   목록 "필터" 칩 활성(emerald hairline, 채움 없음 — 2026-08-09 E안)과 달리 emerald-50 면으로 한 단계 강함.
   적용 = 이용권 타입·보너스 프리셋·결제수단 등 폼 안 선택. */
.filter-chips--form .filter-chip input:checked + .filter-chip-label,
.filter-chips--form .filter-chip-label.is-active {
  background: var(--color-primary-50);
  border-color: var(--color-primary-600);
  color: var(--color-primary-700);
}
@media (hover: hover) {
  .filter-chips--form .filter-chip input:checked + .filter-chip-label:hover,
  .filter-chips--form .filter-chip-label.is-active:hover {
    background: var(--color-primary-50);
    border-color: var(--color-primary-600);
    color: var(--color-primary-700);
  }
}

/* button 래퍼 칩(JS imperative 재렌더)의 UA 기본 padding 제거 — label 래퍼와 같은 지오메트리.
   `.filter-chip` 은 padding 미선언이고 글로벌 button 리셋은 border/background/font-family 만
   지워서, `<button class="filter-chip">` 로 쓰면 좌우 6px·세로 1px 이 더 붙었다. */
button.filter-chip { padding: 0; }

/* button.filter-chip-label 리셋 (form 디폴트 폰트/마진 제거)
   + cursor — 커서는 래퍼(.filter-chip)만 갖고 있어서, 래퍼 없이 `<button class="filter-chip-label">` 로
   쓰는 «베어 버튼» 경로는 UA 기본이 `default` 이라 hover 색은 반응하는데 커서가 안 바뀐다.
   page-local 사본이 3벌(system/tenants/detail · system/aligo-result-codes/list · fragments/message-topup-modal)
   나온 뒤 승격 (§원자 승격 기준 「3번째에서 검토」). <label>·<a> 경로는 이미 커서가 있어 중복 0. */
button.filter-chip-label {
  font-family: inherit;
  line-height: 1.2;
  margin: 0;
  cursor: pointer;
}

.filter-chip-label .level-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  margin-right: 6px;
  vertical-align: middle;
  border: 1px solid rgba(0, 0, 0, 0.15);
}

/* 필터 칩 카운트 — 칩(②) 자체 어휘, 탭 .tab-count 와 같은 인라인 tabular 숫자
   (2026-08-09 E안: 활성 칩이 흰 면 emerald hairline 이 되며 구 캡슐(slate-100 면 + 활성 흰 글자)의
   존재 이유였던 「솔리드 위 대비 확보」가 소멸 — 인라인으로 수렴).
   2026-07-20 members/list.html page-local 정의에서 승격 — 페이지별 칩 클래스 신설 ❌ */
/* slate-500 승격 — 개수가 값이다. 구 slate-300 은 대비 1.48:1 로 §slate-400 기준보다도 낮았다 (ADR 0495).
   → 4.76:1. slate-300 복귀 ❌ · 선택 상태(:checked)의 emerald 는 별개 축이라 불변 */
.filter-chip-count {
  margin-left: 4px;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-slate-500);
  font-variant-numeric: tabular-nums;
}

/* 선택 상태 개수 = 흰 면 위 12px 글자 → primary-700 (3.77 → 5.48:1, ADR 0495). 600 복귀 ❌ */
.filter-chip input:checked ~ .filter-chip-label .filter-chip-count,
.filter-chip-label.is-active .filter-chip-count {
  color: var(--color-primary-700);
}

/* 단독 필터 칩 행 = 모바일 컴팩트 칩 (2026-08-09 사용자 확정 B안 — 목록 위 단독 칩 행 전용).
   글로벌 모바일 칩(44px 알약 + wrap)이면 타입 5종에서 블록이 플랜 카드 한 장보다 커진다 →
   **시각 34px 컴팩트 + 칩 상하 패딩 5px 로 44px 터치 히트박스 보전**(히트박스 확대 idiom).
   필터 패널·모바일 시트 내부 칩·--form 폼 옵션 칩은 대상 아님 (글로벌 44px wrap 유지).

   **흐름 = `--compact` 줄바꿈 단일** (2026-08-25 사용자 확정 B안, 시안 `mobile-filter-chip-overflow`·
   `member-pay-type-filter-wrap`) — 소비처 2곳(admin/passes/products 툴바 · member/pay 타입 필터)
   동시 전환. 「고르는 칩」은 **전부 보이는 게 원칙**인데 구 한 줄 레일에서는 종류 하나(주N회권)가
   화면 밖이었다(자연 폭 394px / 390px 폰 가용 358px). 두 줄이 되는 건 종류를 4종 다 쓰는 도장뿐이고,
   두 행 모두 sticky 가 아니라 스크롤하면 지나가는 자리라 상시 점유가 0 이다.
   「두 줄이 되면 라벨·개수를 줄여 한 줄로」 = 2026-08-25 비교 후 **미채택**(A안 유지) — 종류 이름·개수가
   뱃지·이력·엑셀과 갈린다. 레일 복귀 ❌

   구 `--rail`(한 줄 `overflow-x:auto` 변형)은 그 전환으로 사용처 0 이 돼 **2026-08-25 삭제**했다 —
   되살리려면 「그 화면은 왜 칩이 잘려도 되는가」가 먼저 서야 한다. 무근거 재도입 ❌ */
@media (max-width: 768px) {
  .filter-chips--compact .filter-chip { flex-shrink: 0; padding: 5px 0; }
  .filter-chips--compact .filter-chip-label { min-height: 34px; padding: 6px 12px; }
  .filter-chips--compact { flex-wrap: wrap; row-gap: 0; }
}

/* 활성 칩 줄은 sticky 툴바 «밖» 형제라 데스크톱에선 스스로 접힌다 (2026-08-25 C안) —
   구조상 툴바 래퍼(`.mobile-member-search`·`.payment-mobile-search`) 안에 있을 때는 그 래퍼의
   `display:none` 이 대신 접어 줬다. JS 가 인라인 `display:''`(=CSS 위임)로 켜므로 기본값이 필요하다. */
.mobile-filter-chips-row { display: none; }

/* ── 모바일 목록 툴바 원자 — 컴팩트 검색바 + 아이콘 버튼 + 활성 칩 줄 (ADR 0223, 2026-08-14 글로벌 승격) ──
   members/list page-local(2026-08-11 B안 확정 어휘)에서 추출 — payments 완료 뷰가 두 번째 소비자.
   (.filter-toggle 의 2026-07-27 members·payments 사본 글로벌 추출과 같은 경로.)
   래퍼(sticky 오프셋·풀블리드 마진·하단 경계)는 페이지 소유 — members = .mobile-member-search /
   payments = .payment-mobile-search. 원자만 여기. 페이지별 원자 사본 재도입 ❌ */
@media (max-width: 768px) {
  .mobile-search-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 36px;
    background: var(--bg-secondary);
    border: 1px solid var(--border-default);
    border-radius: 8px;
    padding: 0 10px;
  }
  /* §27 focus 단일화 — 베어 input(outline:none) 대신 컨테이너가 ring 담당 (글로벌 .search-input:focus 와 동일 토큰) */
  /* `:focus-within` ❌ (ADR 0616 · 2026-09-05, `.ios-item` rail 과 같은 결함) — 바 «안»에 필터 버튼이 있어
     필터를 누르면 검색칸이 포커스된 것처럼 ring 이 켜진 채 남는다. */
  .mobile-search-bar:has(:focus-visible) {
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 3px var(--accent-primary-subtle);
  }
  /* 크기 = 쓰는 쪽 CSS 소유 (2026-08-21 승격). 지우면 아이콘 소실 ❌ */
  .mobile-search-bar svg {
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    color: var(--text-tertiary);
  }
  .mobile-search-input {
    flex: 1;
    min-width: 0; /* flex 최소폭 = input 고유폭(~180px) 차단 — 320px 폭에서 우측 버튼이 화면 밖으로 밀리며 가로 스크롤 유발 */
    border: none;
    background: transparent;
    font-size: 16px;
    line-height: 1.2;
    padding: 0;
    outline: none;
    color: var(--text-primary);
  }
  .mobile-search-input::placeholder {
    color: var(--text-tertiary);
  }
  .mobile-filter-btn {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border: none;
    background: transparent;
    color: var(--text-secondary);
    border-radius: 6px;
    cursor: pointer;
  }
  /* fragment 아이콘(userPlus 등)은 width/height 무지정 — 버튼 안 18px 통일 */
  .mobile-filter-btn svg { width: 18px; height: 18px; }
  .mobile-filter-btn.has-filter {
    color: var(--color-primary-600);
  }

  /* 활성 필터 칩 줄 — [줄바꿈 칩 목록] + [우측 끝 고정 슬롯(조회 건수 등)] 2단.
     고정 슬롯을 목록 안에 넣으면 칩과 함께 밀려 안 보인다 → 목록 밖 형제로 두고 flex-shrink:0.

     **줄바꿈 = 2026-08-25 사용자 확정 C안** (시안 `mobile-filter-chip-overflow`) — 구
     `.mobile-filter-chips-rail`(한 줄 `overflow-x:auto`) supersede. 조건 5개면 내용 폭 866px /
     화면 390px 이라 **뒤쪽 칩과 「모두 지우기」가 화면 밖**이었다(밀어야 보이고, ×도 두 동작).
     「잘린 칩 = 더 있음 힌트」(ADR 0193·0305)는 **걸린 조건을 한눈에 못 읽는 대가**가 더 컸다.
     짝 규칙 = 이 줄을 **sticky 툴바 «밖»으로 이동**(페이지 마크업 소유) — 줄이 두꺼워져도
     상시 점유가 0 이다(`.mobile-entry-row` 분리와 같은 처리). 한 줄 레일 재도입 ❌ */
  .mobile-filter-chips-row {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-top: 8px;
    padding-bottom: 4px;
  }
  .mobile-filter-chips-list {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    column-gap: 6px;
    /* row-gap 18 = 아래 히트영역 확장분(`inset: -9px 0`)의 2배 — §히트박스 확대 idiom
       「wrap 격자는 row-gap 을 확장분 2배와 동치로 함께 벌린다」. 좁히면 윗줄 판정이 아랫줄을 덮는다. */
    row-gap: 18px;
    flex: 1;
    min-width: 0; /* flex 자식 기본 min-width:auto 면 칩이 안 줄고 우측 슬롯을 화면 밖으로 밀어낸다 */
  }
  .mobile-filter-chips-list .filter-active-chip { flex-shrink: 0; }
  .mobile-filter-chips-list .filter-active-chip-clear-all { flex-shrink: 0; }

  /* 활성 칩 탭 영역 = 칩 전체 44px (2026-08-21 사용자 확정 A안 · 시안 active-filter-chip-touch).
     실측 ✕ 18×18 · 「모두 지우기」 80×24 로 44px 터치 기준 미달이었다 — 같은 줄 필터 칩은 44 를
     보전하는데 활성 칩 두 컨트롤만 빠져 한 줄 안에서 탭 난이도가 갈렸다.
     ① 구 레일 시절의 세로 패딩 상쇄(`padding: 9px 0; margin: -9px 0`)는 **줄바꿈 전환으로 소멸** —
        `overflow-x: auto`(⇒ 세로도 clip)가 판정을 잘라내던 문제 자체가 없어졌고, 이제 줄 사이 여백은
        `row-gap: 18px` 이 지고 판정이 서로 겹치지 않는다. 세로 패딩·음수 마진 재도입 ❌
     ② `::after` 의 컨테이닝 블록 = `.filter-active-chip`(여기서만 `position: relative`) —
        **✕ 버튼에는 position 을 주지 ❌** (주는 순간 판정이 18px 버튼 기준으로 되돌아간다).
        `inset: -9px 0` = 칩 폭 전체 × (26+18) → 라벨 어디를 눌러도 그 필터가 풀린다.
     마크업은 그대로(중첩 인터랙티브 회피) · 데스크톱 확장 ❌ (마우스 — ADR 0317 과 같은 조항). */
  .mobile-filter-chips-list .filter-active-chip { position: relative; }
  .mobile-filter-chips-list .filter-active-chip-remove::after {
    content: '';
    position: absolute;
    inset: -9px 0;
  }
  .mobile-filter-chips-list .filter-active-chip-clear-all {
    position: relative;
  }
  .mobile-filter-chips-list .filter-active-chip-clear-all::after {
    content: '';
    position: absolute;
    inset: -10px 0; /* 24 + 20 = 44 */
  }

  /* 콘솔 대시보드 활성 칩도 같은 판정 — 위 확장은 `.mobile-filter-chips-list` 스코프 안에만 있어
     `.active-filter-chips.trend-active-row`(system/dashboard) 에는 안 닿아 ✕ 가 18px 로 남아 있었다.
     콘솔은 [[0466]] 이후 폰 지원 표면이다. ✕ 버튼 자신에 `position` 부여 ❌ (위 ② 조항 — 주는 순간
     판정이 18px 버튼 기준으로 되돌아간다) */
  body.system-admin .filter-active-chip { position: relative; }
  body.system-admin .filter-active-chip-remove::after {
    content: '';
    position: absolute;
    inset: -9px 0;
  }

  /* 조회 건수 = 데스크톱 .result-count-row 토큰 미러 (12px/800/slate-500/0.04em + 강조 13px/800/slate-900).
     두 표면이 같은 사실을 같은 톤으로 말하도록 — 모바일 전용 방언 신설 ❌ */
  .mobile-filter-count {
    flex-shrink: 0;
    font-size: 12px;
    font-weight: 800;
    color: var(--color-slate-500);
    letter-spacing: 0.04em;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }
  .mobile-filter-count strong {
    font-size: 13px;
    font-weight: 800;
    color: var(--color-slate-900);
    letter-spacing: -0.01em;
  }
}

/* ========================================
   27.5 Active Filter Chips — 글로벌 박제
   ========================================
   목록 페이지 검색바 아래 노출되는 "활성 필터" pill (× 개별 제거 + 모두 지우기).
   D. MONO CONCIERGE: Slate-900 솔리드 칩 + dashed 둘레 UPPERCASE clear-all.
   회원·결제·정산·테넌트 목록 공통 사용 — project-frontend-guide §목록 페이지 필터 패턴. */
.active-filter-chips {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}

/* Text Hierarchy 활성 필터 칩 (2026-06-18, slate-900 솔리드 supersede)
   채움 제거 → hairline + emerald 텍스트 (탭/필터 토글과 같은 신호 어휘).
   token 어포던스(pill + ×)는 유지, 무게만 덜어냄. */
.filter-active-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 4px 3px 10px;
  background: var(--bg-primary);
  border: 1px solid var(--border-hairline, #eaecef);
  border-radius: 20px;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-primary-700);
  white-space: nowrap;
  letter-spacing: 0.01em;
}

/* 테두리는 패널 칩(.filter-chip-label .level-dot)과 «같은 값» — 없으면 화이트벨트(#FFFFFF)가
   흰 칩 면(--bg-primary) 위에서 통째로 사라져 17색 중 그 한 줄만 점 없는 칩이 된다
   (2026-09-18 실측: 활성 칩만 점 소실). box-sizing:border-box 라 8px 는 그대로. 제거 ❌ */
.filter-active-chip .level-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
  flex-shrink: 0;
  border: 1px solid rgba(0, 0, 0, 0.15);
}

.filter-active-chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: none;
  background: transparent;
  /* slate-500 승격 — ✕ 가 「이 칩을 뗀다」의 유일한 신호 (§slate-400 승격 기준 · ADR 0495).
     대비 2.56:1 → 4.76:1 · 치수 18px 불변. slate-400 복귀 ❌ */
  color: var(--color-slate-500);
  cursor: pointer;
  border-radius: 50%;
  font-size: 14px;
  font-weight: 400;
  line-height: 1;
  transition: all 0.15s;
}

@media (hover: hover) {
  .filter-active-chip-remove:hover {
    background: var(--color-slate-100);
    color: var(--color-slate-700);
  }
}

/* slate-500 승격 — 버튼 라벨이라 이 글자가 기능의 유일한 신호 (§slate-400 승격 기준 · ADR 0495).
   대비 2.45:1 → 4.76:1 · 크기·굵기 불변. slate-400 복귀 ❌ */
.filter-active-chip-clear-all {
  padding: 4px 10px;
  border: 1px dashed var(--border-default, #eaecef);
  background: transparent;
  color: var(--color-slate-500);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  border-radius: 999px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  transition: all 0.15s;
}

@media (hover: hover) {
  .filter-active-chip-clear-all:hover {
    border-color: var(--color-slate-400);
    color: var(--color-slate-900);
    background: transparent;
  }
}


/* ========================================
   선택 액션바 「전부 취소」 ✕ — 2026-08-18 사용자 확정 C안 (시안 selbar-clear-button)
   어휘 SSOT = project-frontend-guide 「목록 일괄 정정 — 헤더 morph 액션바」
   ========================================
   개별 해제가 체크박스로 되므로 이 컨트롤이 하는 일은 「여러 개를 한 번에 지우기」 하나다.
   그 하나를 실행 버튼(출석으로·승급 등) 옆 raised 버튼으로 두면 취소가 실행과 같은 체급·인접
   자리라 오탭 한 번에 고른 걸 통째로 잃는다 → 기능은 지키고 자리와 체급만 낮춘다.
   자리 = **카운트 옆(좌측 라벨 묶음)** — 우측 실행 클러스터 안이 아니다. 2026-08-16 확정
   (시안 bulk-clear-tier)이 무면 텍스트 버튼을 금지한 근거는 「같은 행 실행 버튼과 높이가
   23↔34px 로 갈린다」였고, 그건 이 컨트롤이 그 클러스터에 있을 때의 이야기다. 라벨 묶음으로
   옮기면 비교 대상이 카운트 텍스트라 체급이 맞는다 — 실행 클러스터 안에 무면 텍스트 재도입 ❌.
   「지금 걸린 것을 되돌리는 ✕」는 활성 필터 칩이 이미 쓰는 문법이라 새 어휘가 아니다. */
.sel-clear-x {
  border: none;
  background: none;
  padding: 6px 4px;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 800;
  line-height: normal;
  color: var(--color-slate-500);
  letter-spacing: -0.01em;
  cursor: pointer;
  flex-shrink: 0;
}

@media (hover: hover) {
  .sel-clear-x:hover { color: var(--color-slate-900); }
}

/* 모바일 히트영역 44px — 보이는 글자는 그대로, 히트박스만 키운다 (토스트 ✕ 와 같은 idiom).
   행 높이 예약값(출석 52px · 관원 44px)을 넘지 않아 morph 때 목록이 밀리지 않는다. */
@media (max-width: 768px) {
  .sel-clear-x { min-width: 44px; min-height: 44px; }
}

/* ========================================
   27.6 목록 필터 — 통일 어휘 (SSOT, 2026-06-25)
   ========================================
   데스크톱 = Label-Left Rows(B): .filter-panel--rows > .filter-rows > .filter-row(라벨열 + 콘텐츠).
   모바일   = FilterSheet(Stacked Sections): .fsheet > .fsheet-sec(라벨 위 + 콘텐츠 아래), 푸터 A(초기화/N건 보기).
   같은 "라벨 + 콘텐츠 per 필터" 모델을 폭에 맞춰 회전(데스크톱 좌측 / 모바일 상단).
   회원·결제 목록 공통. project-frontend-guide §목록 페이지 필터 패턴. */

/* ---- 무면 목록 툴바 `.list-toolbar` (2026-08-14 사용자 확정 C안, 시안 `list-toolbar-surface`) ----
   검색 + 필터 토글(+ 우측 액션)이 앉는 면 = **흰 카드 ❌, 페이지 면에 직접**.
   구 「card > card-body 로 감싸기」는 밑줄형 검색 한 줄(40px)에 카드 크롬 38px 을 쓰던 자리 —
   `.filter-panel--rows` 자체가 원래부터 「투명 배경 + 상단 hairline」이라 카드 없이 그대로 성립한다
   (카드 쪽이 나중에 덧씌워진 껍데기였다). 2026-08-14 모바일 컴팩트 툴바와 같은 문법으로 수렴.
   - `padding: 0 2px` = `.filter-row`/`.filter-foot` 의 2px rail 과 같은 시작선
   - `position/z-index` = 관원 목록 내보내기 드롭다운이 뒤따르는 표 카드 위로 펼쳐지게
     (구 `.members-toolbar-card { overflow: visible }` 우회는 카드가 사라져 불필요)
   적용 = `admin/members/list`·`admin/payments/index`. 카드 래핑 재도입 ❌ · 페이지별 무면 툴바 사본 ❌ */
.list-toolbar {
  position: relative;
  z-index: 20;
  padding: 0 2px;
  margin-bottom: var(--space-4);
}

/* ---- 목록 카드 머리 행 `.list-card-head` (2026-08-14 사용자 확정 A안, 시안 `list-head-grouping`) ----
   결과 건수는 **표 카드 안 첫 행**. 무면 툴바 전환 뒤 「검색줄·활성칩줄·건수줄」 얇은 3층이 배경 위에
   흩어져 허전했고, 건수만 카드로 들여보내면 배경은 2층으로 줄고 카드가 「머리 → 표」 한 덩어리가 된다.
   같은 자리가 §목록 일괄 정정의 **morph 슬롯** — 선택 시 이 행이 그대로 액션바가 된다(출석 명단 선례,
   새 바 추가 ❌). 흰 면 의무: 회색 면(`#fafafa`/slate-50)은 D.MONO 카드 헤더 금지 + 흰 raised 버튼과
   면이 겹쳐 탁해진다. 톤 = 마이크로 라벨(12/800/slate-500) + 강조 숫자.
   적용 = `admin/members/list`·`admin/payments/index`·`admin/pass-extension-batch`(구 `.pxb-count-row`).
   **노출 조건은 페이지 소유** — members·payments = 결과 1건 이상이면 상시 / pass-extension-batch =
   검색 중 + 1건 이상(평상시 전체 수는 위 카드 메타 「총 N명」이 답한다, 2026-08-08 확정).
   배경 위 무면 건수 줄 복귀 ❌ · 우측 정렬 회색 텍스트 복귀 ❌ */
.list-card-head {
  display: flex;
  align-items: center;
  gap: 12px;
  /* 높이 = **가장 큰 상태를 처음부터 예약** (2026-08-16 사용자 확정 A안, 시안 `list-head-morph-height`).
     50 = btn-sm 35 + padding 7×2 + border-bottom 1. 구 48px 은 담을 수 있는 높이가 33px 뿐이라
     morph 로 버튼이 들어오는 순간 행이 48→50 으로 밀려 자라고 **표 전체가 2px 아래로 점프**했다
     (1440px 실측 — 구 주석의 「48→49」는 `.btn-sm` 이 padding 7→8px 로 뒤에서 재선언된 뒤 어긋난 값).
     padding 은 얇게 유지 — 13px 로 키우면 63px 로 뛰어 표가 통째로 밀린다(2026-08-14 실측).
     48px 복귀 ❌ · 「평소 행이 왜 50px 인가」 감사 재지적 ❌ (morph 자리 예약이 목적) */
  min-height: 50px;
  padding: 7px 16px;
  background: var(--bg-primary);
  border-bottom: 1px solid var(--border-light);
  font-size: 12px;
  font-weight: var(--font-extrabold);
  letter-spacing: 0.04em;
  color: var(--color-slate-500);
}
.list-card-head strong {
  color: var(--color-slate-900);
  font-weight: var(--font-extrabold);
  font-size: 13px;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

/* ---- 데스크톱 B: Label-Left Rows ---- */
.filter-panel--rows {
  background: transparent;
  border-radius: 0;
  padding: 0;
  margin-bottom: 0;
  margin-top: 12px;
  border-top: 1px solid var(--border-hairline);
}
.filter-rows {
  display: flex;
  flex-direction: column;
}
/* 좌우 rail 은 래퍼 `.list-toolbar`(0 2px)가 단독 소유 — 행에 다시 2px 을 주면 검색줄(266)과
   패널 행(268)이 2px 어긋난다(2026-08-14 실측, 구 카드 시절부터 잠재하던 어긋남). 좌우 padding 재도입 ❌ */
.filter-row {
  display: grid;
  grid-template-columns: 84px 1fr;
  gap: 16px;
  align-items: center;
  padding: 13px 0;
  border-bottom: 1px solid var(--border-hairline);
}
.filter-row:last-child { border-bottom: 0; }
.filter-row.is-tall { align-items: start; }
.filter-row-label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-slate-500);
  line-height: 1.2;
}
.filter-row.is-tall .filter-row-label { padding-top: 6px; }

/* 패널 안 칩 = 컴팩트 (2026-08-14 글로벌 승격 — 구 `.members-filter-panel` page-local 사본 폐지).
   패널은 목록 위에 얹히는 조작 면이라 글로벌 칩(14px/6·14 = 35px)이면 행 하나가 그만큼 두꺼워진다.
   스코프가 `.filter-panel--rows` 인 이유 = 모바일 FilterSheet(.fsheet-*)·단독 필터 칩 행은 대상 아님
   (무스코프 재정의는 시트 칩까지 덮어 44px 터치 규칙을 깬다). 페이지별 사본 재도입 ❌ */
.filter-panel--rows .filter-chips { gap: 6px; }
.filter-panel--rows .filter-chip-label {
  padding: 4px 10px;
  font-size: 12px;
}
@media (max-width: 768px) {
  /* 데스크톱 패널은 모바일에서 노출되지 않지만, 글로벌 모바일 칩 룰(min-height 44px) 침범 방지 */
  .filter-panel--rows .filter-chip-label {
    padding: 8px 16px;
    font-size: 13px;
  }
}
/* 벨트 하위(주니어/성인) 라인 — 데스크톱/모바일 공유 어휘 */
.filter-belt { display: flex; flex-direction: column; gap: 9px; }
.filter-belt-line { display: flex; align-items: flex-start; gap: 10px; }
.filter-belt-line-label {
  font-size: 12px;
  font-weight: 700;
  color: var(--color-slate-400);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  min-width: 34px;
  padding-top: 7px;
  flex-shrink: 0;
}

/* ---- 데스크톱 패널 푸터: [라이브 카운트 · 초기화 · 확인] (2026-07-03, 모바일 FilterSheet 푸터 A 데스크톱 이식) ----
   필터는 기존대로 즉시 적용 — 확인 버튼은 재조회 트리거가 아니라 "패널 닫기" (라벨이 실제 동작과 일치).
   카운트는 좌측 라이브 텍스트("지금 조건으로 N명 표시 중")가 담당 — 조건 변경마다 갱신 + 펄스.
   모바일 'N건 보기'와 라벨이 다른 이유: 시트는 목록을 가리고 있어 '보기'가 성립, 데스크톱 패널은 목록이 이미 보임.
   확인 버튼은 dark raised (페이지 primary 1개 규칙 — 페이지 앵커와 공존 회피). */
.filter-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  /* 좌우 0 = `.filter-row` 와 같은 근거 (rail 은 `.list-toolbar` 단독 소유) */
  padding: 14px 0 2px;
}
.filter-foot-live {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-right: auto;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--color-slate-600);
}
.filter-foot-live b {
  color: var(--color-primary-700);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.filter-foot-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-primary-600);
  flex-shrink: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .filter-foot-live.pulse b { display: inline-block; animation: filterFootPulse 0.35s ease; }
}
@keyframes filterFootPulse {
  0% { transform: scale(1); }
  40% { transform: scale(1.14); }
  100% { transform: scale(1); }
}
.filter-foot-reset {
  background: transparent;
  border: 0;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-slate-500);
  padding: 8px 10px;
  border-radius: 8px;
  cursor: pointer;
  transition: all var(--transition-fast);
}
@media (hover: hover) {
  .filter-foot-reset:hover { color: var(--color-slate-900); background: var(--color-slate-100); }
}

/* ---- 모바일 FilterSheet: Stacked Sections ---- */
.fsheet { display: flex; flex-direction: column; }
.fsheet-sec {
  padding: 16px 0;
  border-bottom: 1px solid var(--border-hairline);
}
.fsheet-sec:first-child { padding-top: 4px; }
.fsheet-sec:last-child { border-bottom: 0; }
.fsheet-label-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 11px;
}
.fsheet-label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-slate-500);
}
.fsheet-hint {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);
}
/* 앞 형제가 있을 때의 상단 여백 — 소비처(members/list FilterSheet 빌더 3곳)가 매번
   인라인 style.marginTop 으로 메우던 것을 승격 (2026-08-12 UI 감사, 아래 belt-line 선례) */
* + .fsheet-hint { margin-top: 10px; }
.fsheet-belt-line + .fsheet-belt-line { margin-top: 12px; }
.fsheet-belt-label {
  font-size: 12px;
  font-weight: 700;
  color: var(--color-slate-400);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 8px;
}

/* stepper (직접 입력) */
/* 모바일 필터 시트 숫자 칸 — 「거르기」 축이라 `.stepper`(정하기) 로 흡수하지 ❌
   (2026-08-27 사용자 확정 A안, §숫자 + 단위 «박스형» 판정 한 문장).
   h46 은 시트 터치 규격이고 «± 가 없다» — 구 이름 `.fsheet-stepper` 는 스테퍼가 아닌데
   스테퍼라 불려 다음 사람이 ± 를 찾게 만들던 자리라 개명했다. `stepper` 로 되돌리기 ❌ */
.fsheet-numfield {
  display: flex;
  align-items: center;
  height: 46px;
  border: 1px solid var(--color-slate-200);
  border-radius: 11px;
  background: #fff;
  padding: 0 14px;
  transition: all 0.15s;
}
/* `:focus-within` 유지 — 자식이 텍스트 입력뿐이라 `:has(:focus-visible)` 과 동작이 같다 (ADR 0616).
   버튼·토글을 넣는 순간 같은 결함이 되므로 그때 함께 옮길 것. */
.fsheet-numfield:focus-within {
  border-color: var(--color-primary-300);
  box-shadow: 0 0 0 3px var(--color-primary-50);
}
.fsheet-numfield input {
  flex: 1;
  border: 0;
  outline: 0;
  background: transparent;
  font-size: 16px;
  font-weight: 600;
  color: var(--color-slate-900);
  min-width: 0;
}
.fsheet-numfield input::-webkit-outer-spin-button,
.fsheet-numfield input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.fsheet-numfield input[type="number"] { -moz-appearance: textfield; }
.fsheet-numfield-suffix {
  font-size: 13px;
  color: var(--color-slate-500);
  font-weight: 600;
  flex-shrink: 0;
}
.fsheet-numfield-prefix {
  font-size: 13px;
  color: var(--color-slate-500);
  font-weight: 600;
  flex-shrink: 0;
  margin-right: 6px;
}
/* 값+단위 «붙임» 변형 — 단위가 값에 달라붙어 「출석률 80% 이상」 한 문장으로 읽힌다
   (2026-09-05 사용자 확정 C안, 시안 `attend-rate-placeholder` · 데스크톱 `.input-with-suffix.is-glued` 짝).
   `unit` 을 준 numField 만 걸린다 — 나이(세)·미출석(일) 칸은 기본형 유지(값과 떨어져 읽는 단위). */
.fsheet-numfield.is-glued input { text-align: right; }
.fsheet-numfield-unit { margin-left: 1px; }
.fsheet-numfield.is-glued .fsheet-numfield-suffix:not(.fsheet-numfield-unit) { margin-left: 5px; }
/* 토글 (단일 boolean — 승급 후보 등) */
.fsheet-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  height: 48px;
  padding: 0 16px;
  border: 1px solid var(--color-slate-200);
  border-radius: 12px;
  background: #fff;
  cursor: pointer;
}
.fsheet-toggle-label {
  font-size: 14px;
  font-weight: 600;
  color: var(--color-slate-700);
}
.fsheet-toggle.is-on .fsheet-toggle-label { color: var(--color-primary-700); }
.fsheet-toggle-switch {
  width: 44px;
  height: 26px;
  border-radius: 9999px;
  background: var(--color-slate-200);
  position: relative;
  flex-shrink: 0;
  transition: background 0.2s;
}
/* knob = 글로벌 토글 어휘 미러 (.ios-item-toggle-switch::after — transform + --ease-spring).
   구 `left 0.15s` 는 같은 목적 모션의 page-local 재구현이라 ① 스프링 바운스가 없어 같은 시트 안
   칩·체크 pop(motionChipPop)보다 딱딱하고 ② left 애니메이션이라 매 프레임 레이아웃을 다시 잡았다.
   이동량 18px = 트랙 44 − knob 20 − 좌우 여백 3×2 (구 left 3 → 21 과 같은 좌표). ADR 0104 LV1 */
.fsheet-toggle-switch::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  transition: transform 0.35s var(--ease-spring);
}
.fsheet-toggle.is-on .fsheet-toggle-switch { background: var(--color-primary-600); }
.fsheet-toggle.is-on .fsheet-toggle-switch::after { transform: translateX(18px); }
.fsheet-grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 10px;
}
/* 푸터 A (초기화 + N건 보기) — BottomSheet .bottom-sheet-actions 안에 삽입 */
.fsheet-foot-btn {
  height: 48px;
  border-radius: 12px;
  font-size: 14px;
  font-weight: 800;
  font-family: inherit;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.fsheet-foot-reset {
  flex: 0 0 auto;
  padding: 0 20px;
  background: #fff;
  border: 1px solid var(--border-hairline);
  color: var(--color-slate-600);
  font-weight: 700;
}
@media (hover: hover) {
  .fsheet-foot-reset:hover { border-color: var(--color-slate-400); color: var(--color-slate-900); }
}
.fsheet-foot-apply {
  flex: 1;
  background: #fff;
  border: 1px solid var(--color-primary-300);
  color: var(--color-primary-700);
  box-shadow: 0 1px 3px rgba(5, 150, 105, 0.12);
  font-variant-numeric: tabular-nums;
}
@media (hover: hover) {
  .fsheet-foot-apply:hover { background: var(--color-primary-50); }
}

/* ========================================
   28. Table Enhancements
   ======================================== */
/* Table Clickable Rows — §2 「Clickable Row Enhancement」가 cursor·hover(--bg-row-hover)·active 를
   전부 소유. 여기 있던 중복 선언은 동특이도 소스 순서로 hover 를 emerald 워시로 덮어 D.MONO
   중립 row hover 를 깨뜨려 삭제 (2026-08-17 UI 감사) — 재도입 ❌ */

/* Member Avatar */


/* Level Display */








/* Table Footer & Pagination */
.table-footer {
  padding: var(--space-4);
  border-top: 1px solid var(--border-subtle);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  background: var(--bg-primary);
}
/* 구 .table-footer--mid (인-카드 중간 푸터 변형) 는 2026-08-07 제거 — 유일 사용처였던
   pass-extension 적용 버튼이 카드 밖으로 이동해 「푸터 아래 콘텐츠와 구분」 근거가 소멸. */

.pagination-btn {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--bg-primary);
  color: var(--text-secondary);
  cursor: pointer;
  transition: all var(--transition-fast);
}
/* 아이콘 크기·굵기는 여기서 결정 — 마크업은 `icons :: chevronLeft/Right` 프래그먼트만 심는다
   (구 인라인 SVG 18벌 하드코딩 폐지, 2026-08-21. §SSOT 「인라인 SVG 아이콘 = fragments/icons.html」).
   stroke-width 는 프래그먼트의 presentation attribute(2.25)를 CSS 가 이긴다 — 페이저는 2 로 유지
   (구 값 그대로, 시각 변화 0). 인라인 width/height 복귀 ❌ */
.pagination-btn svg { width: 16px; height: 16px; stroke-width: 2; }

@media (hover: hover) {
  .pagination-btn:hover:not(:disabled) {
    border-color: var(--accent-primary);
    color: var(--accent-primary);
    background: var(--accent-primary-subtle);
  }
}

/* §잠긴 값 칸 토큰과 같은 축 — opacity ❌, 면·글자·테두리로 (구 .4) */
.pagination-btn:disabled {
  background: var(--bg-secondary);
  color: var(--text-tertiary);
  border-color: var(--border-hairline);
  cursor: not-allowed;
}

.pagination-info {
  padding: 0 var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/* 모바일 카드 리스트 페이지네이션 바 (JS 렌더, .pagination-btn/.pagination-info 재사용) */
.pagination-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
.pagination-bar .pagination-btn {
  min-width: 44px;
  min-height: 44px;
  font-size: 16px;
}

/* 표 푸터 페이저도 폰에서 같은 44px — payments 완료 뷰·pass-extension 미리보기·batch 명단 세 페이저가
   `desktop-only` 밖이라 폰에 그대로 렌더되는데 글로벌 `.pagination-btn` 은 32px 고정이었다.
   같은 관장이 「관원 목록(`.pagination-bar`) 44px ↔ 결제·연장 32px」을 오간다 — 값이 갈리면
   같은 페이저가 셸마다 다른 크기가 된다(ADR 0466 후속 주석이 `.sys-list-card` 에 적어 둔 그 근거).
   마크업이 prev + info + next «2버튼»뿐이라 320px 에서도 안 넘친다. 데스크톱 32px 불변 (2026-09-04 UI 감사) */
@media (max-width: 768px) {
  .table-footer .pagination-btn {
    min-width: 44px;
    min-height: 44px;
    font-size: 16px;
  }
}

/* 이력 카드 「최근 N건 + 더보기」 (2026-08-09 사용자 확정 A안)
   목록 컨테이너 data-list-more 를 common.js ListMore 가 소유 — 초과 행 접기 + 더보기 행 부착.
   버튼 = 글로벌 btn-secondary raised 그대로 (페이지별 더보기 버튼 재구현 ❌).
   마지막 보이는 행의 hairline 이 구분선 역할이라 행에 별도 border 없음. */
.list-more-hidden { display: none !important; }
.list-more-row {
  display: flex;
  justify-content: center;
  padding-top: 12px;
}
.list-more-row .list-more-btn { min-width: 200px; justify-content: center; }
/* 표 안 ListMore — 더보기 행이 table 자식으로 삽입되면 anonymous cell 로 1열에 끼어 좌측으로
   치우친다(common.js `list.parentNode.insertBefore`, list = tbody). 하단 캡션으로 승격해 표 전폭
   중앙 정렬. 2026-08-18 UI 감사: system/settings/pricing 이 두 번째 사용처가 되며 page-local 2벌
   → 글로벌 승격. page-local 사본 재도입 ❌ */
.table .list-more-row { display: table-caption; caption-side: bottom; padding-bottom: 16px; text-align: center; }

/* Empty State Enhancement — 통합되어 R20 으로 이전 (Soft Hero) */

/* Member Specific Avatar */




/* ========================================
   30. Responsive
   ======================================== */
/* 구 ≤1024 .sidebar/.main-content 사본 삭제 (2026-08-31) — §100 뒤 드로어 블록이 같은 선언 소유,
   transition 줄은 R4 통합 transition 에 늘 지던 죽은 선언이었다 */

@media (max-width: 768px) {
  

  

  .table-container {
    border-radius: 0;
    border-left: none;
    border-right: none;
  }

  /* 폰 모달 바깥 여백 = 한쪽 16px 단일 (2026-09-10 사용자 확정 A안 · 시안 `mobile-modal-gutter`).
     구 `margin: var(--space-4)` 는 오버레이 padding 20px 위에 «겹쳐» 한쪽 36px 가 됐다 —
     360px 폰에서 본문 글자 폭 242px(화면 67%)라 충전 금액 칩 4개가 2개씩 쪼개졌다.
     여백 소유자는 이제 오버레이 padding 하나(아래 §67 `.modal-overlay` 모바일 규칙) — 모달 margin 재도입 ❌ */
  .modal {
    max-width: 100%;
    /* iOS Safari 주소창 동적 높이 반영 (100vh → 100dvh) */
    max-height: calc(100dvh - var(--space-8));
  }
}

/* ========================================
   28. Dark Theme (Optional - disabled)
   ======================================== */
/* Dark theme is disabled for clean design */

/* ========================================
   29. Print Styles
   ======================================== */
@media print {
  .sidebar,
  .toast-container,
  .modal-overlay {
    display: none !important;
  }

  .main-content {
    margin-left: 0;
  }

  .card {
    box-shadow: none;
    border: 1px solid #ccc;
  }
}

/* ========================================
   30. Animations (Minimal)
   ======================================== */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}



/* ========================================
   32. Sidebar Overlay (Mobile)
   ======================================== */
/* 구 `.sidebar-overlay` base 사본 + 죽은 `.show` 변형 삭제 (2026-08-25 사용자 확정, UI 감사).
   base 는 아래 「Sidebar Overlay (mobile)」 블록과 «완전 동일값» 이었고, 가시 토글만 `.show` ↔ `.active`
   로 갈려 있었다 — JS(`toggleSidebar`)와 템플릿은 `active` 만 쓴다(`.show` 매칭 0).
   남겨두면 「오버레이는 `.show` 로도 열린다」는 거짓 계약이 베끼기 사고를 만든다. 재도입 ❌ */

/* Mobile menu button */
/* 아이콘 크기 = CSS 소유 (§화면 전환 「인라인 SVG … CSS 가 크기·색 결정」). 규칙이 없어 SVG 가
   버튼 40px 를 그대로 채우면서, 같은 fragments/icons::menu 를 쓰는 모바일 헤더(22px)와
   획 두께가 3.3px ↔ 1.8px 로 갈렸다 (2026-08-15 UI 감사 실측) */
.mobile-menu-btn svg {
  width: 22px;
  height: 22px;
}
.mobile-menu-btn {
  display: none;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
}

@media (max-width: 1024px) {
  .mobile-menu-btn {
    display: flex;
  }
}

/* 태블릿 밴드(769~1024) 전용 — 이 구간만 «모바일 헤더 없이» 햄버거가 <main> 앞 in-flow 로 선다
   (mobile-shell 헤더·바텀네비는 전부 ≤768). position·margin 이 없으면 버튼은 뷰포트 좌상단(0,0)에
   붙고 바로 아래 .page-container 는 --content-padding 만큼 들여써서 «좌측 시작선이 둘»이 된다.
   경계를 ≤1024 로 넓히는 안은 바텀네비 4슬롯·hide-on-mobile 9메뉴·--shell-sticky-h·저장 바 태블릿
   규칙을 전부 재측정해야 해서 미채택 — 여기선 버튼만 페이지 rail 에 맞춘다 (2026-08-25 UI 감사).
   ≤768 은 mobile-shell.css 가 `display:none !important` 라 무영향 */
@media (min-width: 769px) and (max-width: 1024px) {
  .mobile-menu-btn {
    margin: var(--content-padding) 0 0 var(--content-padding);
  }
}

/* ---- 태블릿 밴드 도장 줄 (ADR 0472, 2026-08-26 사용자 확정 A안 · 시안 `tablet-band-tenant-identity`) ----
   위 주석의 「여기선 버튼만 페이지 rail 에 맞춘다」를 잇는 자리 — 그 rail 에 **도장 신원**을 함께 세운다.
   폰(≤768)은 모바일 헤더가, 데스크톱(≥1025)은 사이드바가 같은 일을 하므로 이 행은 그 사이에서만 산다. */
.tablet-tenant-row {
  display: none;
}

@media (min-width: 769px) and (max-width: 1024px) {
  .tablet-tenant-row {
    display: flex;
    align-items: center;
    gap: 10px;
    /* 좌측 rail = 햄버거가 쓰던 값과 동일 — 행이 padding 을 지고 버튼은 마진을 반납한다.
       상단 margin 을 padding 으로 옮겨도 .page-container 시작선(64px)은 그대로다 */
    padding: var(--content-padding) var(--content-padding) 0;
  }

  .tablet-tenant-row .mobile-menu-btn {
    margin: 0;
    flex-shrink: 0;
  }

  /* 드롭다운의 앵커 — 행이 아니라 **트리거 폭**에 맞물려야 페이지 전폭으로 늘어나지 않는다 */
  .tablet-tenant-switcher {
    position: relative;
    min-width: 0;
  }

  /* 무면 텍스트 트리거 — 이 밴드엔 헤더 면이 없어 상자를 만들면 없던 크롬이 하나 생긴다.
     히트 판정 44px 은 «패딩 + 음수 마진」이 아니라 `::after` 오버레이로 넓힌다 (§히트박스 확대 idiom):
     이 트리거는 한 줄에 «단독이 아니라» 햄버거와 나란히 서고, 패딩으로 키우면 행에서 가장 큰 자식이
     되어 **행 높이 40 → 44px · 본문 시작선 64 → 68px** 이 함께 밀린다. 형제 `.mobile-menu-btn::after`
     (같은 밴드, 보이는 40px 불변 + 판정 44px)와 **같은 수단**이다.
     **세로로만** 넓힌다 — 가로로 벌리면 옆 햄버거의 44px 판정과 겹친다(gap 10px). */
  .tablet-tenant-trigger {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    padding: 4px 0;
    border: none;
    background: transparent;
    font-family: inherit;
    cursor: pointer;
  }

  .tablet-tenant-trigger::after {
    content: "";
    position: absolute;
    inset: -11px 0;
  }

  .tablet-tenant-static {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    min-width: 0;
  }

  /* 도장명 = 사이드바 트리거 이름과 같은 체급(15/800/slate-900). 자격 배지는
     `.tenant-switcher-trigger-role`(12/800/slate-500) 재사용 — 새 원자 신설 ❌ */
  .tablet-tenant-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 15px;
    font-weight: var(--font-extrabold);
    letter-spacing: -0.015em;
    color: var(--color-slate-900);
  }

  .tablet-tenant-trigger .tenant-switcher-trigger-role,
  .tablet-tenant-static .tenant-switcher-trigger-role {
    flex-shrink: 0;
    margin-left: 0;
  }

  .tablet-tenant-trigger .tenant-switcher-arrow {
    flex-shrink: 0;
  }

  /* 좌측 정렬 트리거라 드롭다운도 왼쪽에서 자란다(사이드바 짝은 좌우 12px 스트레치, 관원 셸은 우측 기준).
     면·최대 높이·그림자는 style.css R16 원자가 소유 — 여기선 앵커만 */
  .tablet-tenant-switcher .tenant-switcher-dropdown {
    top: calc(100% + 8px);
    left: 0;
    right: auto;
    min-width: 260px;
    transform-origin: top left;
  }
}

/* ========================================
   33. Form Grid
   ======================================== */


/* Static form value (read-only display) */


/* ========================================
   34. Search Input
   ======================================== */






/* ========================================
   35. Filter Bar
   ======================================== */




/* ========================================
   36. Header Actions
   ======================================== */





/* ========================================
   37. Wizard Steps
   ======================================== */
/* ========================================
   38. MEMBER DASHBOARD (Dojo Style)
   ======================================== */



































/* Profile Header - Mobile */
/* 2026-05-27: .member-welcome-banner + .welcome-* 그룹 폐지 — "Legacy" 주석 명시.
   templates 0건 사용 dead. emerald gradient + box-shadow + glass border + backdrop-filter
   모두 D.MONO 박제 (flat + hairline + 박제 글래스 금지) 와 정면 충돌. */

/* 회원 리스트 벨트 컬럼 — belt-sm + "블루벨트 · 2그랄" 인라인 1줄 (mb-belt-line 과 동일 패턴) */
.level-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  line-height: 1.4;
}
.level-badge-name {
  font-weight: 600;
  color: var(--text-primary);
}
/* `--text-muted` 는 정의된 적 없는 토큰이라 선언이 통째로 무효화돼 부모 색을 상속했다
   → 페이지 미러 2곳(`.px-target-lvl-sep`·`.pxb-lvl-sep`)이 쓰는 slate-300 으로 정렬 (2026-08-12 UI 감사) */
.level-badge-sep {
  color: var(--color-slate-300);
  font-weight: 400;
  margin: 0 -2px;
}
/* 그랄 = 라벨이 아니라 **데이터 실수치** → §slate-400 승격 기준 ①(그 자리의 유일한 신호) 승격 대상
   (2026-08-12 사용자 확정). ADR 0128 이 「0그랄도 값으로 적는다」를 박제했고, 스트라이프가 0개인
   관원에겐 이 글자가 진급 단계의 유일한 단서다. 구 slate-400 은 흰 면 13px 에서 대비 ≈2.85:1 로 AA 미달.
   페이지 미러 2곳(`pass-extension` `.px-target-lvl-grau` · `pass-extension-batch` `.pxb-lvl-grau`)도
   같은 값 — 한쪽만 되돌리면 표면 간 색이 갈린다. slate-400 복귀 ❌ */
.level-badge-grau {
  font-weight: 500;
  color: var(--color-slate-500);
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   .belt — 통합 띠(BJJ belt) 시각화 컴포넌트
   사용처: 회원 리스트/상세, 출석 키오스크, 회원 프로필/대시보드,
           레벨 색 설정. 모든 띠 시각화는 이 컴포넌트로 통일.
   구조: main(메인 컬러) + tape(랭크바 — 검정/블랙벨트는 빨강) + strips(그랄 줄)
         **그랄은 랭크바 바깥쪽 끝에서 시작해 안쪽으로 채워지고, 바가 차면 벨트 면으로 이어진다**
         ([[0779]], 2026-09-19 사용자 확정 C2·D2안 · 시안 `belt-grau-on-rankbar`·`belt-grau-stripe-weight`)
   사이즈: --xs(36×8) / --sm(55×12) / --md(70×14) / --lg(80×16) / --xl(112×22)
   ⚠️ 2026-09-15 전 벨트 폭 통일 — 키즈 11그랄(흰4·빨강4·검정3)이 들어가야 하는 값이고
      **키즈·성인 폭이 갈리면 명단 이름 시작선이 두 벌**이 된다(사용자 확정). 구 24/36/48/56/80 복귀 ❌
   ============================================================ */
.belt {
  /* 면직 노이즈 1장(베이크된 feTurbulence data-URI)을 모든 띠가 background 로 공유 → 100개여도 텍스처 1장 비용 */
  --belt-grain: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='30'%20height='30'%3E%3Cfilter%20id='b'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.9%200.4'%20numOctaves='2'%20stitchTiles='stitch'/%3E%3CfeColorMatrix%20type='saturate'%20values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR%20type='linear'%20slope='0.2'%20intercept='0.4'/%3E%3CfeFuncG%20type='linear'%20slope='0.2'%20intercept='0.4'/%3E%3CfeFuncB%20type='linear'%20slope='0.2'%20intercept='0.4'/%3E%3CfeFuncA%20type='linear'%20slope='0'%20intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect%20width='30'%20height='30'%20filter='url(%23b)'/%3E%3C/svg%3E");
  --belt-color: var(--color-slate-300);
  /* 두 톤 벨트(키즈 8색)의 가운데 밴드 — 색은 .belt--acc-* 가, 두께는 아래 두 지점이 정한다.
     기본 28%(md 3.9px). xs·sm 은 비율 그대로 두면 2px 미만이라 서브픽셀로 뭉개져 솔리드와
     구분이 안 돼서 사이즈 변형에서 넓힌다 (2026-08-27 실측). */
  --belt-color-2: transparent;
  --belt-band-a: 36%;
  --belt-band-b: 64%;
  --belt-tape: #16171d;
  --belt-stitch: rgba(255,255,255,0.20);
  --belt-w: 70px;
  --belt-h: 14px;
  /* 랭크바 폭 = 그 사이즈에서 «블랙 7단이 규격대로 들어가는» 값이다
     (2026-09-18 사용자 확정 C안 · 시안 `belt-black-highdan-rankbar`).
     식 = 7×--belt-stripe-w + 6×--belt-stripe-gap + 좌우 padding.
     ⚠️ 구 11/16/21/24/33 복귀 ❌ — 그 값들은 5단까지만 담아서 나머지를 «줄 쪽»이 flex 로
        찌그러뜨렸다(랭크바는 규격을 지키고 줄만 얇아진다): 실측 xs 7단 0.44px · sm 7단 0.66px ·
        md 7단 1.16px. 바로 아래 3색 밴드가 근거로 쓰는 「색 면 1px 미만이면 회색으로 뭉갠다」
        선을 블랙 6·7단만 넘고 있었다.
     ⚠️ 블랙벨트만 넓히는 특례 ❌ — 같은 사이즈 안에서 성인 5색의 랭크바 폭이 두 벌로 갈린다.
        벨트 «전체» 폭(--belt-w)은 불변이라 명단 이름 시작선은 그대로다. */
  --belt-tape-w: 27px;
  /* 그랄 줄 규격 — **전 벨트 한 벌**(구 성인 2px / 키즈 3px 두 벌을 2026-09-19 통합, 사용자 확정 D2안).
     값은 키즈 쪽(3px 계열)을 택했다: 색 면이 1px 미만이면 흰·빨강이 회색으로 뭉개고(2026-09-15 실측)
     성인 규격(md 2px)으로 합치면 **xs 에서 1px** 이 되어 검정 3줄이 한 덩어리로 뭉친다(2026-09-19 10배 확대 실측).
     ⚠️ 2px 복귀 ❌ — 그 값은 랭크바가 줄을 «품던» 시절의 규격이고, 지금은 넘치면 면으로 이어지므로
        랭크바 폭이 줄 개수를 가두지 않는다. */
  --belt-stripe-w: 3px;
  --belt-stripe-gap: 1.5px;
  --belt-stripe-edge: 0.65px;
  display: inline-flex;
  align-items: stretch;
  width: var(--belt-w);
  height: var(--belt-h);
  border-radius: 3px;
  overflow: hidden;
  box-shadow: 0 1px 2px rgba(15,23,42,0.20);
  flex-shrink: 0;
  vertical-align: middle;
  /* 3색 밴드가 절대배치로 앉는 기준 — 빼면 줄이 벨트를 탈출해 **화면 왼쪽에 세로 띠**로 붙는다
     (2026-09-15 실측 발현). position 제거 ❌ */
  position: relative;
}
.belt__main {
  flex: 1;
  background-color: var(--belt-color);
  /* 트윈 스티칭(상·하 dashed) + 매트 sheen + 면직 grain(soft-light) */
  background-image:
    repeating-linear-gradient(90deg, var(--belt-stitch) 0 1.6px, transparent 1.6px 3.6px),
    repeating-linear-gradient(90deg, var(--belt-stitch) 0 1.6px, transparent 1.6px 3.6px),
    linear-gradient(180deg, rgba(255,255,255,0.16), rgba(255,255,255,0) 42%, rgba(0,0,0,0.18)),
    var(--belt-grain);
  background-size: 100% 1px, 100% 1px, 100% 100%, 30px 30px;
  background-position: 0 31%, 0 69%, 0 0, 0 0;
  background-repeat: repeat-x, repeat-x, no-repeat, repeat;
  background-blend-mode: normal, normal, normal, soft-light;
}
/* 두 톤: 베이스 위를 길이 방향으로 관통하는 가운데 밴드 (IBJJF 키즈 실물 형태).
   grain 아래에 한 장 더 깔아 soft-light 결이 줄 위에도 얹히게 한다 — 위로 올리면 밴드만
   결 없는 스티커처럼 뜬다. 좌우 반반 분할 ❌ (실물에 없고 검은 절반이 랭크바와 뭉친다). */
.belt--duo .belt__main {
  background-image:
    repeating-linear-gradient(90deg, var(--belt-stitch) 0 1.6px, transparent 1.6px 3.6px),
    repeating-linear-gradient(90deg, var(--belt-stitch) 0 1.6px, transparent 1.6px 3.6px),
    linear-gradient(180deg, rgba(255,255,255,0.16), rgba(255,255,255,0) 42%, rgba(0,0,0,0.18)),
    var(--belt-grain),
    linear-gradient(180deg, transparent 0 var(--belt-band-a),
                    var(--belt-color-2) var(--belt-band-a) var(--belt-band-b),
                    transparent var(--belt-band-b) 100%);
  background-size: 100% 1px, 100% 1px, 100% 100%, 30px 30px, 100% 100%;
  background-position: 0 31%, 0 69%, 0 0, 0 0, 0 0;
  background-repeat: repeat-x, repeat-x, no-repeat, repeat, no-repeat;
  background-blend-mode: normal, normal, normal, soft-light, normal;
}
/* 액센트 톤 — 서버는 의미색(#FFFFFF/#000000)만 넘기고 실제 색은 여기가 정한다.
   .belt--white/.belt--black 이 베이스에 하던 일과 같은 문법. */
.belt--acc-white { --belt-color-2: #fbfaf5; }
.belt--acc-black { --belt-color-2: #14151a; }
/* 랭크바 = 벨트 끝에 감아 꿰맨 천. **줄을 품지 않는다** — 줄은 .belt__strips 가 절대배치로
   그 위에 얹고, 바를 다 채우면 벨트 면으로 이어진다. flex 로 줄을 담던 구 문법(display/justify/gap/
   padding) 복귀 ❌ — 그 문법은 줄 개수가 바 폭에 갇혀 6·7단을 «줄 쪽»이 찌그러뜨렸다. */
.belt__tape {
  width: var(--belt-tape-w);
  background-color: var(--belt-tape);
  /* 입체 wrap 랭크바: 좌측 seam 그림자 + 상하 음영 */
  background-image: linear-gradient(180deg, rgba(255,255,255,0.12), rgba(0,0,0,0.22));
  box-shadow: inset 1.5px 0 0 rgba(0,0,0,0.30);
  flex-shrink: 0;
}
/* 그랄 줄 — 랭크바 바깥쪽(오른쪽) 끝에서 시작해 안쪽으로 채워진다.
   row-reverse 라 **DOM 순서 1..n 이 오른쪽→왼쪽**으로 놓인다: 1그랄이 바 위, 번호가 커질수록 면 쪽.
   그래서 키즈 9~11그랄의 검정 줄은 «항상 밝은 벨트 면 위»에 서고, 구 「검정 랭크바 위 검정 줄」
   대비 1.02:1 문제가 자리 자체로 풀린다 (전 사이즈에서 6번째 줄부터 면 — md 바 27px 기준 5.1칸). */
.belt__strips {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 2px;
  display: flex;
  flex-direction: row-reverse;
  align-items: center;
  gap: var(--belt-stripe-gap);
}
.belt__stripe {
  width: var(--belt-stripe-w);
  height: 100%;
  border-radius: 1.5px;
  flex-shrink: 0;
}
/* 줄 색 — 성인 색벨트·블랙은 전부 흰(--w) · 화이트·키즈만 IBJJF 키즈 도표 3색([[0746]]).
   외곽선이 이 방식의 본체다: 옐로우 면 위 흰 줄은 대비 1.70:1 로 색만으론 구분되지 않는다.
   ⚠️ **검정 줄에는 외곽선을 주지 않는다** — 주면 9~11그랄 3개가 한 덩어리로 뭉친다(2026-09-15 실측). */
.belt__stripe--w { background: #fff; box-shadow: 0 0 0 var(--belt-stripe-edge) rgba(15,23,42,0.72); }
.belt__stripe--r { background: #e0242f; box-shadow: 0 0 0 var(--belt-stripe-edge) rgba(15,23,42,0.72); }
.belt__stripe--k { background: #14151a; }
/* 두 톤 벨트(키즈 8색) 중 «/블랙»: 액센트 밴드가 검정 줄 뒤를 지나가면 줄 사이 간격이 검정으로
   채워져 9~11그랄 3개가 한 덩어리로 읽힌다 ([[0746]] 후속, 2026-09-18 사용자 확정 A안).
   구 문법은 줄 묶음 «전체»에 면색 패치를 깔았지만, 지금은 묶음이 랭크바 위까지 걸쳐 있어
   패치를 깔면 바가 지워진다 — 그래서 **검정 줄 각각에 면색 후광**으로 같은 일을 한다.
   ⚠️ 액센트 밴드를 얇게 ❌ (sm·xs 에서 2px 미만이 되어 솔리드와 구분이 사라진다) ·
      검정 줄에 밝은 외곽선 ❌ (솔리드 키즈 벨트의 검정 줄까지 함께 바뀐다). */
.belt--duo.belt--acc-black .belt__stripe--k {
  box-shadow: 0 0 0 var(--belt-stripe-gap) var(--belt-color);
}

/* 흰 벨트: 따뜻한 아이보리 + 보더 + 어두운 스티칭 */
.belt--white {
  --belt-color: #f7f5ef;
  --belt-stitch: rgba(70,58,34,0.16);
}
.belt--white .belt__main {
  box-shadow: inset 0 0 0 1px #cfc8b6;
}
/* 블랙 벨트: 본체 + 옥스블러드 랭크바 + 흰색 그랄 */
.belt--black {
  --belt-color: #14151a;
  --belt-tape: #cf2233;
}

/* 사이즈 변형 */
.belt--xs {
  --belt-w: 36px;
  --belt-h: 8px;
  --belt-tape-w: 15px;
  --belt-stripe-w: 1.6px;
  --belt-stripe-gap: 0.8px;
  --belt-stripe-edge: 0.4px;
  --belt-band-a: 33%;
  --belt-band-b: 67%;
}
.belt--sm {
  --belt-w: 55px;
  --belt-h: 12px;
  --belt-tape-w: 22px;
  --belt-stripe-w: 2.4px;
  --belt-stripe-gap: 1.2px;
  --belt-stripe-edge: 0.55px;
  --belt-band-a: 35%;
  --belt-band-b: 65%;
}
.belt--md {
  --belt-w: 70px;
  --belt-h: 14px;
  --belt-tape-w: 27px;
  --belt-stripe-w: 3px;
  --belt-stripe-gap: 1.5px;
  --belt-stripe-edge: 0.65px;
}
.belt--lg {
  --belt-w: 80px;
  --belt-h: 16px;
  --belt-tape-w: 28.5px;
  --belt-stripe-w: 3.4px;
  --belt-stripe-gap: 1.7px;
  --belt-stripe-edge: 0.7px;
}
.belt--xl {
  --belt-w: 112px;
  --belt-h: 22px;
  --belt-tape-w: 37px;
  --belt-stripe-w: 4.4px;
  --belt-stripe-gap: 2.4px;
  --belt-stripe-edge: 0.9px;
  border-radius: 4px;
}
/* xs 는 높이 8px 라 줄 모서리를 더 눕힌다 */
.belt--xs .belt__stripe { border-radius: 1px; }

/* 2026-05-27: .welcome-meta .divider + .welcome-streak 잔여 폐지 (welcome-* legacy 그룹). */

/* Weekly Calendar */
.weekly-calendar {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 8px;
}

.day-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 12px 8px;
  border-radius: 10px;
  background: var(--bg-secondary);
  transition: all 0.2s ease;
}

.day-cell.today {
  background: var(--color-primary-50);
  border: 2px solid var(--color-primary-300);
}

.day-cell.attended {
  background: var(--color-primary-100);
}

.day-cell.attended .day-indicator {
  color: var(--color-primary-600);
}

.day-name {
  font-size: 12px;
  font-weight: var(--font-medium);
  color: var(--text-tertiary);
  text-transform: uppercase;
}

.day-indicator {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}



.today-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--color-primary-400);
}

/* Current Pass Card */


























/* Notice List */

















/* Grid Layouts */


.p-0 { padding: 0 !important; }
.h4 {
  font-size: 16px;
  font-weight: var(--font-semibold);
  color: var(--text-primary);
  margin: 0;
}

/* Mobile Responsive for Welcome Banner */
@media (max-width: 768px) {
  

  

  

  

  

  .weekly-calendar {
    gap: 4px;
  }

  .day-cell {
    padding: 8px 4px;
  }
}

/* ========================================
   39. SYSTEM DASHBOARD (Dojo Style)
   ======================================== */

/* Stat Icon - Violet */


/* Banner Styles */
.banner {
  padding: 20px 24px;
  border-radius: 12px;
  border: 1px solid var(--card-border);
  background: var(--bg-primary);
}



.banner-content {
  display: flex;
  align-items: center;
  gap: 16px;
}



.banner-text h4 {
  font-size: 14px;
  font-weight: var(--font-semibold);
  margin: 0 0 4px;
  color: rgba(255, 255, 255, 0.9);
}

.banner-text p {
  font-size: 18px;
  font-weight: var(--font-bold);
  margin: 0;
  color: white;
}

/* List View */







/* Tenant Avatar */


/* Inquiry Icon */








/* Quick Actions Grid */


/* Ripple Effect Placeholder */


/* ========================================
   40. TOP NAVIGATION (Dojo Unique Layout)
   ======================================== */
.top-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* 인셋을 높이에 더하고 그만큼 위로 민다 — `viewport-fit=cover` 뒤로 이 바는 화면 «맨 위»에서
     시작하므로, 안 받으면 로고·이름이 상태바·노치 밑으로 들어간다. mobile-shell `.mobile-header`
     와 같은 calc 문법 (2026-09-14 사용자 확정 A안). 인셋 0 인 자리(데스크톱·안드로이드)는 불변 */
  height: calc(var(--top-nav-height) + env(safe-area-inset-top, 0px));
  padding: 0 var(--content-padding);
  padding-top: env(safe-area-inset-top, 0px);
  background: var(--bg-primary);
  border-bottom: 1px solid var(--border-default);
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
}

.nav-brand {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
}

.brand-name {
  font-size: 22px;
  font-weight: var(--font-bold);
  color: var(--color-slate-900); /* 2026-08-31 톤 수렴 — 주 잉크 900 단일 */
  letter-spacing: -0.02em;
}

/* Sport Badge */


.nav-menu {
  display: flex;
  align-items: center;
  gap: 6px;
}

.nav-link {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border-radius: 10px;
  font-size: 14px;
  font-weight: var(--font-medium);
  color: var(--text-secondary);
  text-decoration: none;
  transition: all var(--transition-fast);
}

@media (hover: hover) {
  .nav-link:hover {
    background: var(--color-primary-50);
    color: var(--color-primary-600);
  }
}

.nav-link.active {
  background: var(--color-primary-600);
  color: white;
}

.nav-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}





.nav-icon-btn {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  color: var(--text-secondary);
  background: transparent;
  border: none;
  cursor: pointer;
  position: relative;
  transition: all var(--transition-fast);
}

@media (hover: hover) {
  .nav-icon-btn:hover {
    background: var(--bg-secondary);
    color: var(--text-primary);
  }
}

@media (hover: hover) {
  .nav-member-logout:hover {
    background: var(--status-error-bg);
    color: var(--status-error);
  }
}

.nav-icon-btn svg {
  width: 18px;
  height: 18px;
}

/* 회원 top-nav 로그아웃: 데스크톱에선 아이콘+텍스트, 모바일에선 아이콘만 (프로필은 바텀 탭으로 이관 — C안 2026-06-01) */
.nav-member-logout {
  gap: 6px;
  width: auto;
  padding: 0 12px;
  text-decoration: none;
}
.nav-member-logout-label {
  font-size: 13px;
  font-weight: 600;
}
@media (max-width: 768px) {
  /* 라벨을 숨기면 36×40 이라 44 미달 — 회원 PWA 는 「폰 전용」이라 마우스 폴백이 없다.
     확대 수단 = `::after` 오버레이 (`.toast-close::after` 선례, 2026-08-21 UI 감사):
     이 버튼은 `.nav-icon-btn` 이 width/height 를 고정하고 `box-sizing: border-box` 라
     padding 이 안쪽으로 먹혀 히트가 커지지 않는다 — padding + 음수 마진 idiom 은 여기선 무효.
     보이는 36×40·아이콘 18px 불변, 탭 판정만 44×44. 고정 크기만 두고 끝내기 ❌ */
  .nav-member-logout {
    width: 36px;
    padding: 0;
    position: relative;
  }
  .nav-member-logout::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 44px;
    height: 44px;
  }
  .nav-member-logout-label { display: none; }
}












/* Mobile Menu Button */


@media (max-width: 1024px) {
  .nav-menu {
    display: none;
  }

  
}

@media (max-width: 768px) {
  .top-nav {
    /* 좌우만 바꾸는 자리지만 `padding` 단축이라 베이스의 `padding-top: env(...)` 가 함께 지워진다 —
       그러면 바 높이는 인셋만큼 큰데 내용만 가운데로 올라가 상태바 밑으로 들어간다 (2026-09-15 교정) */
    padding: 0 16px;
    padding-top: env(safe-area-inset-top, 0px);
  }

  

  .brand-name {
    font-size: 18px;
  }
}

/* ========================================
   41. MAIN CONTENT WRAPPER (Top Nav Layout)
   ======================================== */
.main-wrapper {
  /* 상단 인셋도 함께 뺀다 — `viewport-fit=cover` 뒤로 `.top-nav` 실높이가 `--top-nav-height + 인셋`
     이라, 안 빼면 그 인셋만큼 문서가 화면보다 길어져 헛스크롤이 되살아난다 (2026-09-15 교정) */
  min-height: calc(100vh - var(--top-nav-height) - env(safe-area-inset-top, 0px));
  min-height: calc(100dvh - var(--top-nav-height) - env(safe-area-inset-top, 0px)); /* vh fallback + dvh override — 헛스크롤 제거 (body 규칙 참고) */
  /* 중립 배경(slate-50) — 구 초록 그라데이션 워시 폐지(2026-06-15, admin .main-content 와 통일).
     emerald 는 신호 위치에만. */
  background: var(--bg-secondary);
}

.page-container {
  /* max-width 없음 — 폭 상한은 .page-content 격자의 첫 트랙이 소유한다 (§본문 읽기 컬럼).
     여기에 캡을 되돌리면 `.is-wide` 가 늘어날 자리가 사라진다. */
  margin: 0;
  padding: var(--content-padding);
}

@media (max-width: 768px) {
  .page-container {
    /* 하단 패딩 제거 — 모바일 바텀 여백은 .main-content 의 padding-bottom(88px = 바텀네비 64 + 여백 24)이
       단독 책임. page-container 하단 20px 이 중첩되면 콘텐츠 끝과 바텀네비 사이 공백이 과해진다.
       상단 12px = 모바일 첫 콘텐츠 시작선 전역 압축 (2026-08-14 사용자 확정 C안, 시안 payments-tab-top-gap —
       구 20px). 이 값을 상쇄하는 풀블리드 요소(members/list .mobile-member-search 음수 마진)와 동기 의무. */
    padding: 12px 16px 0;
  }
}

/* ========================================
   Page Head Pattern (.pg-head-*)
   브레드크럼/인트로 있는 서브 라우팅 페이지용 공통 헤더
   ======================================== */


.pg-head-breadcrumb {
  font-size: 12px;
  color: var(--text-tertiary);
  margin-bottom: 10px;
}
.pg-head-breadcrumb a {
  color: var(--text-secondary);
  text-decoration: none;
}
@media (hover: hover) {
  .pg-head-breadcrumb a:hover { color: var(--color-primary-700); }
}
.pg-head-breadcrumb .sep {
  margin: 0 6px;
  color: var(--border-default);
}
.pg-head-breadcrumb .current {
  color: var(--text-primary);
  font-weight: 600;
}

/* 드릴다운 복귀 back 링크 — §서브페이지 복귀 어휘 「목록→상세 드릴다운 = back 링크(모바일 포함)」.
   2026-08-20 사용자 확정 B안 (시안 back-link-unify): 구 .pxb-back(admin 2곳) · .qd-back(questions/detail
   page-local 사본) · .sys-crumb(system 2곳) 3벌 → 역할 이름 단일 원자. 클래스명이 페이지 유래
   (pxb = pass-extension-batch)면 다음 드릴다운 저자가 「내 페이지 것이 아니다」로 읽고 또 사본을 뜬다 —
   .qd-back 이 정확히 그 사고였고, 베끼는 과정에서 아이콘 13→14px 과 모바일 히트박스가 함께 어긋났다.
   페이지 유래 이름 재도입 ❌ · page-local 사본 ❌ (같은 원자 2벌).
   라벨 = 도착지 정체성 3채널(사이드바·모바일 헤더·title)과 같은 글자.
   아이콘 = fragments/icons :: chevronLeft 1벌 — 인라인 SVG 하드코딩 ❌ (구 3벌이 stroke 2/2.5 로 갈렸다).
   값 중재: 아이콘 14px(구 13·15 중간 — 13px 텍스트 x-height 정합) · hover 700(admin 다수결) ·
   margin-bottom 16px(구 sys-crumb 값). align-self 는 system 상세의 flex 부모(td-/sd-detail)에서
   링크가 전폭으로 늘어나는 것을 막는다 — 제거 ❌ */
.back-link {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 13px; color: var(--color-slate-500);
  align-self: flex-start;
  /* 짝 = `justify-self` (2026-09-08 UI 감사 실측). `align-self` 하나만으로는 **격자 부모에서 안 막힌다** —
     `.page-content` 는 2트랙 grid(§페이지 폭)라 이 링크가 곧 grid 아이템이고, ① `inline-flex` 가
     blockify 되어 `flex` 로 계산되고 ② `align-self` 는 격자에서 «블록 축»(세로)을 맡아 가로를 안 잡는다
     → 실측 **폭 1152px**, 줄 오른쪽 빈 자리에 마우스만 올려도 글자가 emerald 로 물들고 클릭이 먹었다.
     `justify-self` 는 flex 부모(system 상세)에서는 무시되므로 두 부모를 한 선언으로 덮는다.
     보이는 크기·색·간격 불변(폭만 내용 크기로 수렴) — 둘 중 하나만 남기기 ❌ */
  justify-self: start;
  margin-bottom: 16px; transition: color .15s;
}
@media (hover: hover) {
  .back-link:hover { color: var(--color-primary-700); }
}
.back-link svg { width: 14px; height: 14px; flex-shrink: 0; }
/* 모바일 = admin mobile-header 에 back 버튼이 없어 이 링크가 유일 가시 복귀 표지인데
   13px 텍스트라 히트가 ≈20px 였다 → §액션 행 히트박스 확대 idiom(padding + 동값 음수 마진,
   지오메트리 불변 — 12px 패딩 + 4px 마진 = 데스크톱 16px 과 같은 시각 간격).
   데스크톱 확장 ❌ — 마우스 조준엔 불필요하고 인접 여백이 링크 판정이 된다. */
@media (max-width: 768px) {
  .back-link { padding: 12px 8px; margin: -12px -8px 4px -8px; }
  /* 모바일 복귀를 헤더가 지는 페이지의 본문 링크 (2026-09-08 사용자 확정 A안 · 시안 `back-nav-unify-2`).
     한 화면에 복귀 표지가 둘이면 어느 쪽이 진짜인지 묻게 된다 — 헤더가 있는 폭에서만 접는다.
     ⚠️ 유틸 `.desktop-only` 로 대체 ❌ — 그건 `display: revert !important` 라 데스크톱의
     `inline-flex` 까지 UA 기본값(`inline`)으로 되돌려 셰브론·라벨 정렬과 `align-self` 가 함께 죽는다
     (`.pg-head-breadcrumb` 는 display 선언이 없어 무사했을 뿐이다). 여기는 «숨김만» 한다.
     클래스는 서버 렌더 — JS 가 `has-mobile-header-action` 을 붙기 전 한 프레임 깜빡임 차단. */
  .back-link--desktop-only { display: none; }
}





/* ========================================
   42. DASHBOARD GRID (3-Column Layout)
   ======================================== */










/* 2026-05-27: §43 METRIC CARD 그룹 전체 폐지 — templates 0건 사용 dead.
   기존 .metric-card / .metric-value / .metric-number(gradient text) /
   .metric-badge / .metric-bar / .metrics-row 등 — radius 16px + box-shadow lift
   + gradient text 가 D.MONO 박제와 정면 충돌이라 정리 시점에 함께 제거. */

/* ========================================
   46. ACTION TILES (Quick Actions)
   ======================================== */


/* 2026-05-27: .action-tile + .action-tile-icon/label/hint 그룹 폐지 — templates 0건 사용 dead. */

/* ========================================
   47. BELT INDICATOR (Martial Arts Theme)
   ======================================== */
















/* ========================================
   48. STREAK BANNER (연속 출석)
   ======================================== */


/* ========================================
   49. MOBILE SLIDE MENU
   ======================================== */
























/* ========================================
   50. SECTION HEADER
   ======================================== */
/* 2026-08-16: §51 ACTIVITY LIST 그룹 폐지 — `.activity-dot`(+attendance/payment/member/warning) ·
   `.activity-list`·`.activity-item*`·`.activity-empty` 전부 templates·js 0건 사용 dead (ADR 0260 후속 dot 정리).
   폐지된 `--status-info`·`--status-warning` 토큰을 참조하던 마지막 자리이기도 하다. 재도입 ❌ */

/* 2026-05-27: §52 METRIC CARD EXTENSIONS 그룹 폐지 — templates 0건 사용 dead (§43 본체와 함께 정리). */

/* ========================================
   53. CARD HEADER LINK
   ======================================== */



/* ========================================
   54. SYSTEM NAV COLORS (Emerald - same as admin)
   ======================================== */
/* System admin now uses the same Emerald color scheme as regular admin for consistency */

/* ========================================
   55. MEMBER PORTAL (uses same styles as admin)
   ======================================== */
/* Member portal now uses the same navigation styles as admin for consistency */

/* ========================================
   57. RESPONSIVE DASHBOARD GRID
   ======================================== */
/* ========================================
   58. AUTH PAGES (로그인, 회원가입 등)
   ======================================== */

/* Auth Page Container */
[data-page="auth"] {
  min-height: 100vh;
  min-height: 100dvh; /* vh fallback + dvh override — 헛스크롤 제거 (body 규칙 참고) */
  display: flex;
  flex-direction: column;
  /* 배경 SSOT = 하단 "Auth page background" 블록 (2026-06-30: 랜딩 연결감 위해 옅은 emerald 워시.
     admin·PWA·error 운영 surface 는 중립 유지 — auth 한정 의도적 예외). */
  overflow-y: auto;
}

[data-page="auth"] > main {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 상하만 인셋 최대값 — `viewport-fit=cover`([[0734]])로 인셋이 실값이 된 뒤, 내용이 뷰포트보다
     긴 auth 화면(`join` 6칸 · `signup-complete` 약관 체크리스트)에서 스크롤 최상단의 워드마크가
     상태바 아래로, 맨 아래 `.auth-footer` 링크(히트영역 44px)가 홈 인디케이터 자리로 들어갔다.
     좌우는 가로 모드 `body` 인셋이 이미 담당(§안전영역) — 여기서 또 더하지 ❌.
     인셋 0 인 기기(데스크톱·안드로이드·노치 없음)는 렌더 변화 0. */
  padding: max(20px, env(safe-area-inset-top, 0px)) 20px max(20px, env(safe-area-inset-bottom, 0px));
}

[data-page="auth"] .auth-container {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: none;
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px 20px;
  min-height: min-content;
}

/* 카드리스 래퍼 무력화 — ADR 0025 SSOT (2026-07-28 인라인 사본 통합,
   2026-07-30 signup-complete·add-tenant 마지막 2곳 흡수 + 컬럼 폭 변수화).
   auth 전 페이지 공통: --auth-col-width 단일 컬럼 + 흰 카드 제거 → 배경 워시 위 직접 floating.
   페이지 인라인 재선언 ❌ — 조정은 여기 한 곳만. */
[data-page="auth"] .login-card-container,
[data-page="auth"] .signup-complete-wrap,
[data-page="auth"] .add-tenant-wrap,
[data-page="auth"] .invite-card-container {
  width: 100%;
  max-width: var(--auth-col-width);
  z-index: 1;
  position: relative;
}
[data-page="auth"] .login-wrapper,
[data-page="auth"] .sc-card,
[data-page="auth"] .add-tenant-card,
[data-page="auth"] .invite-card {
  width: 100%;
  background: transparent;
  border: none;
  padding: 0;
  box-shadow: none;
  position: relative;
}
[data-page="auth"] .login-form-panel {
  padding: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* 구 auth 레거시(.animated-bg·.auth-brand·@keyframes fadeInDown) = 2026-08-05 삭제.
   사용처 0 + SSOT 가 폐지한 어휘(흰 카드 래퍼·로고 아이콘 타일). 재도입 ❌ — hero 는 §auth-hero. */

/* 구 @keyframes fadeInUp = 2026-08-11 삭제 — `.animate-in` 이 MOTION SYSTEM 의 `motionEnterUp` 을
   공유하도록 통일(ADR 0104 LV1 「같은 목적 모션 = 공용 키프레임 하나」). 진입 페이드업 키프레임이
   `fadeInUp`(20px, .animate-in) / `motionEnterUp`(16px, .motion-enter) 둘로 갈려 있었다. 재도입 ❌ */

/* 구 .form-floating+.input-icon · .auth-divider · .test-account-* = 2026-08-05 삭제 (사용처 0).
   SSOT 가 .float-field 로 대체한 폐지 어휘 — 스타일시트에 남겨두면 신규 폼이 복사한다. 재도입 ❌ */

/* .auth-eyebrow/-dot — 2026-07-30 삭제 (실사용 템플릿 0, system-login .sys-eyebrow 는 별개 어휘) */

/* System eyebrow — 시스템 콘솔 관문 3화면(system-login·system-approve·system-approved)의 wordmark 자리.
   slate-900 배지 = 시스템 관리자 전용 신호. page-local 3벌이던 것을 §원자 승격 기준(「3번째 사용처가
   생길 때 승격을 검토」)에 따라 승격 — 같은 관문의 연속 3화면이라 한쪽만 고치면 그 자리에서 갈린다.
   시각 변화 0. (구 `.auth-eyebrow` 와는 별개 어휘 — 위 삭제 주석 참조) */
.sys-eyebrow {
  display: inline-flex;
  align-items: center;
  padding: 5px 11px;
  border-radius: 999px;
  background: var(--color-slate-900, #0f172a);
  color: #fff;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.08em; /* 영문 마이크로 라벨 표준값 수렴 (ADR 0113, 2026-08-03 UI 감사) */
  margin-bottom: 14px;
}

/* Auth Footer — SSOT (구 중복 선언 병합: font-size/color 는 링크 아닌 자식용.
   예: signup-complete .sc-acct-line "N 계정으로 가입 중") */
.auth-footer {
  margin-top: 28px;
  text-align: center;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.auth-footer a {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  text-decoration: none;
  transition: color var(--transition-fast);
}

@media (hover: hover) {
  .auth-footer a:hover {
    color: var(--accent-primary);
  }
}

/* 모바일 히트영역 44px — [[0317]] 히트박스 확대 idiom(보이는 상자 없음 · 한 줄에 단독).
   auth 6화면의 유일한 이탈·복귀 경로인데 자연 높이가 ~21px 이었다. 음수 마진이 지오메트리를
   되돌리므로 타이포·색·아이콘 16px 은 불변. `.back-link` 로 흡수 ❌ ([[0365]] C안 기각 — 위계가 다르다) */
@media (max-width: 768px) {
  .auth-footer a {
    display: inline-flex;
    align-items: center;
    /* inline → flex 로 갈리면서 셰브론·어절 사이 인라인 공백이 붕괴한다(익명 flex 아이템 선두 공백).
       형제 어휘와 같은 값(`.back-link` gap 5)으로 복원 — 데스크톱 규칙 불변 */
    gap: 5px;
    /* 12px 은 «44 를 노린 값»이었지만 실측 43.5px 였다 — 글자 행높이가 19.5px 라
       12×2 + 19.5 = 43.5. 12.25px 로 맞춰 정확히 44.0 (2026-08-26 실측 교정).
       보이는 크기·색·아이콘 불변(음수 마진이 지오메트리를 되돌린다). 12px 복귀 ❌ */
    padding: 12.25px 10px;
    margin: -12.25px -10px;
  }
}

/* auth·invitation footer 복귀 링크 아이콘 — 마크업은 `icons :: chevronLeft` 프래그먼트
   (구 인라인 SVG 5벌 폐지, 2026-08-21). 16px/stroke 2 = 구 인라인 값 그대로 — 시각 변화 0.
   이 링크는 「폼 아래 이탈 링크」라 드릴다운 복귀 `.back-link`(13px/14px 아이콘)와 위계가 다르다 —
   `.back-link` 로 흡수 ❌ ([[0365]] C안 기각). */
.auth-footer a svg {
  width: 16px;
  height: 16px;
  stroke-width: 2;
  vertical-align: -2px;
}

/* Auth Page Responsive */
/* ⚠️ 아래 블록도 «사문» — `[data-page="auth"] .auth-container`(0,2,0)의 `padding: 40px 20px` 에 진다
   (2026-09-04 UI 감사 실측). 그 40/20 조차 두 사용처에서는 `add-tenant.css`
   `[data-page="auth"]:has(.add-tenant-wrap) .auth-container`(0,3,0) `padding: 16px 0` 에 다시 진다.
   auth 여백을 실제로 바꾸려면 그 두 곳을 볼 것 — 여기서 고치면 아무 일도 안 일어난다.
   사용자 확정 = 삭제 대신 주석 박제 (auth 밖 사용처가 생기면 살아나는 자리라 통삭제가 판단을 하나 더 만든다). */
@media (max-width: 480px) {
  .auth-container {
    padding: 24px 16px;
  }

}

/* ========================================
   Auth Common - Background Blobs
   ======================================== */
.login-blob {
  position: fixed;
  border-radius: 50%;
  filter: blur(60px);
  opacity: 0.4;
  animation: blobFloat 8s ease-in-out infinite;
  pointer-events: none;
  z-index: 0;
}

/* 2026-06-30: slate blob → 옅은 emerald 워시 (랜딩 연결감 통일, auth 한정 예외).
   랜딩 .landing-page 의 radial-gradient(좌상단 a7f3d0 0.55 · 우하단 a7f3d0 0.5)와 동일 톤·위치.
   transparent edge 로 칙칙함 방지, blur 60px 로 ambient wash. 전 auth 페이지 공유 (10개 동시 적용). */
.login-blob-1 {
  top: -120px;
  left: -120px;
  width: 400px;
  height: 400px;
  background: radial-gradient(circle, rgba(167, 243, 208, 0.55) 0%, rgba(167, 243, 208, 0.32) 50%, transparent 100%);
}

.login-blob-2 {
  bottom: -120px;
  right: -120px;
  width: 350px;
  height: 350px;
  background: radial-gradient(circle, rgba(167, 243, 208, 0.5) 0%, rgba(167, 243, 208, 0.28) 50%, transparent 100%);
  animation-delay: -4s;
}

@keyframes blobFloat {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33% { transform: translate(20px, -20px) scale(1.05); }
  66% { transform: translate(-15px, 15px) scale(0.95); }
}

/* ========================================
   Auth Common - Float Field
   ======================================== */
.float-field {
  position: relative;
  margin-bottom: 18px;
  /* input 높이 박제 — label·액션 버튼(.float-field-action)의 세로 중앙 앵커.
     top:50% (컨테이너 기준) 은 .form-error 내부 부착 시 컨테이너가 커져 label/버튼이
     input 을 벗어나 밀림 → input 높이 절반 고정 앵커로 대체 (2026-07-14) */
  --float-field-input-h: 54px;
}

.float-field input {
  width: 100%;
  height: var(--float-field-input-h);
  padding: 24px 14px 10px;
  border: 1.5px solid var(--border-default);
  border-radius: 10px;
  font-size: 14px;
  font-family: inherit;
  outline: none;
  transition: all .2s;
  background: transparent;
  color: var(--text-primary);
}

.float-field input:focus {
  border-color: var(--color-primary-600);
  box-shadow: 0 0 0 3px rgba(5, 150, 105, .1);
}

/* 카드리스 auth (2026-06-05): 폼 흰 카드 제거 → 입력 필드가 중립(slate-50) 배경 위에 떠도
   또렷하도록 흰 면 + 미세 그림자 박제. 카드 그룹핑을 입력 자체 면이 대체한다. */
[data-page="auth"] .float-field input {
  background: #fff;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
}
[data-page="auth"] .float-field input:focus {
  box-shadow: 0 0 0 3px rgba(5, 150, 105, .1);
}

/* 조건부 항목(생년월일·성별·주소)이 켜짐에 따라 늘고 주는 auth 폼 = `.form-group` 라벨-위 어휘
   (`join.html`). 플로팅 라벨은 select·달력과 구조가 충돌해 전환 ❌ — 대신 **치수·링만** 형제
   `.float-field` 와 맞춘다 (2026-08-21 UI 감사 사용자 확정 B안, 시안 `join-form-vocab`).
   라벨 위치가 갈리는 것은 의도된 분기 · 「form-input 을 float-field 로 바꾸라」 재지적 ❌
   `.custom-select-trigger` 포함 필수 — select 는 CustomSelect 가 트리거로 업그레이드하므로
   빠지면 성별 칸만 글로벌 44px/radius 12 로 서고, FOUC 가드의 「동일 높이」 전제도 깨진다 */
[data-page="auth"] .form-input,
[data-page="auth"] .form-select,
[data-page="auth"] .custom-select-trigger {
  min-height: 54px;
  border-radius: 10px;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
}
[data-page="auth"] .form-input:focus,
[data-page="auth"] .form-select:focus {
  border-color: var(--color-primary-600);
  box-shadow: 0 0 0 3px rgba(5, 150, 105, .1);
}

.float-field label {
  position: absolute;
  left: 14px;
  top: calc(var(--float-field-input-h) / 2);
  transform: translateY(-50%);
  font-size: 14px;
  color: var(--text-tertiary);
  transition: all .2s;
  pointer-events: none;
}

.float-field input:focus + label,
.float-field input:not(:placeholder-shown) + label {
  top: 7px;
  transform: translateY(0);
  font-size: 12px;
  color: var(--color-primary-600);
  font-weight: 600;
}

/* .float-field .required = §필수·선택 표기 열거 그룹(위 2290대)에 편입 — 중복 선언 ❌ (2026-08-16 UI 감사) */

/* 모바일 — input 폰트 16px 강제(iOS 줌 방지, §전역 768 블록)만큼 높이 상승.
   base .float-field 선언(위)보다 뒤에 있어야 cascade 에서 이김 (동일 특이도) */
@media (max-width: 768px) {
  .float-field {
    --float-field-input-h: 56px;
  }
}

/* 위 auth 폼 어휘의 모바일 짝 — `.float-field` 56px 와 같은 체급 */
@media (max-width: 768px) {
  [data-page="auth"] .form-input,
  [data-page="auth"] .form-select,
  [data-page="auth"] .custom-select-trigger {
    min-height: 56px;
  }
}

/* ========================================
   59. DARK THEME (출석 체크 페이지용)
   ======================================== */


/* ========================================
   60. ATTENDANCE PAGE (출석 체크)
   ======================================== */





.attendance-digit {
  width: 72px;
  height: 88px;
  background: rgba(255, 255, 255, 0.05);
  border: 2px solid rgba(255, 255, 255, 0.1);
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 36px;
  font-weight: 600;
  color: var(--text-tertiary);
  transition: all var(--transition-fast);
}

/* `.keypad` 삭제 (2026-08-23 UI 감사) — 키오스크는 `.aurora-keypad` 를 쓴다(다른 토큰). 재도입 ❌ */
/* "초기화"처럼 거의 사용되지 않는 액션은 시각 약화 — 자동 4자리 제출이라 빈도 매우 낮음 */





/* ===== Attendance Overlay — 화이트 D.MONO 멤버십 카드 (2026-07-16 리디자인) =====
   구 "검은 백드롭 + 흰 카드 + stripe + confetti" 폐지 — 밝은 aurora 무대(키오스크 hero 톤 연속)
   위로 raised 화이트 카드가 착지(cardLand) + 광택 스윕(sheen) + 이름 심머 + 체크 드로우.
   벨트 = 미니 벨트 칩(Belt.create fragment 어휘) + 텍스트. 닫기 = 화면 탭 + 자동(성공 5s/에러 7s). */
.attendance-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: var(--z-modal);
  cursor: pointer;
  animation: fadeIn 0.35s ease-out;      /* 글로벌 키프레임 공유 (구 overlayFadeIn 중복 삭제) */
  overflow: hidden;
}

/* 성공·에러 공통 — 밝은 emerald 워시 무대 (키오스크 aurora 와 같은 계열, 어두워지지 않음) */
.attendance-overlay.success,
.attendance-overlay.error {
  background:
    radial-gradient(ellipse 90% 70% at 30% 8%, rgba(167, 243, 208, 0.34), transparent 58%),
    radial-gradient(ellipse 80% 60% at 78% 102%, rgba(16, 185, 129, 0.1), transparent 60%),
    linear-gradient(180deg, #fbfefd 0%, #f2faf6 100%);
}

/* 카드 아래 바닥 그림자 워시 */
.attendance-overlay.success::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 5%;
  width: 480px;
  max-width: 80vw;
  height: 56px;
  background: radial-gradient(ellipse at center, rgba(15, 23, 42, 0.12), transparent 70%);
  transform: translateX(-50%);
  filter: blur(14px);
  pointer-events: none;
}

/* 구 @keyframes overlayFadeIn = 2026-08-11 삭제 — 글로벌 `fadeIn`(opacity 0→1)과 정의가 동일한
   중복이었다. 소비처(.attendance-overlay)는 duration·easing 을 그대로 두고 fadeIn 참조 (ADR 0104 LV1) */

/* ===== 성공 — 멤버십 카드 =====
   전면 확대 B안 (2026-09-01 사용자 확정 · 시안 kiosk-scale-and-stats — 카드 560→720 · 이름 56→76 ·
   로고 136→168 · 스탯 값 26→38). 1024×600 가로 탭 실측 = 카드 상 18px · 하 19px 여유.
   세로 탭(≥600×1000 portrait)은 아래 보강 블록이 한 단계 더 키운다. */
.att-card {
  position: relative;
  width: min(720px, 92vw);
  min-height: 380px;
  border-radius: 26px;
  overflow: visible;
  /* 오버레이가 flex column 이라 카드는 세로 축 flex 아이템 — 기본 `flex-shrink: 1` 이면 내용보다
     짧게 눌려 «흰 카드 밖으로 내용이 새어 나간다»(맨 아래 카운트다운 선이 잘렸다, 2026-09-02 실측).
     높이를 화면에 맞추는 일은 JS `Attendance.fitCard` 의 zoom 이 하고, 박스는 늘 내용과 같게 둔다.
     `flex-shrink` 를 되살리지 ❌ — 그 순간 잘림이 그대로 돌아온다. */
  flex-shrink: 0;
  margin-top: 84px; /* 탑 메달리온 돌출량 — 로고 절반이 카드 위로 걸침 */
  background:
    radial-gradient(ellipse 130% 100% at 88% -12%, rgba(209, 250, 229, 0.5), transparent 52%),
    linear-gradient(160deg, #ffffff 0%, #fcfefd 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 1),
    inset 0 0 0 1px rgba(15, 23, 42, 0.05),
    0 44px 90px -30px rgba(15, 23, 42, 0.3),
    0 18px 36px -18px rgba(5, 150, 105, 0.12);
  transform-origin: 50% 100%;
  animation: attCardLand 0.9s cubic-bezier(0.22, 0.9, 0.24, 1) 0.15s both;
}

@keyframes attCardLand {
  from { opacity: 0; transform: translateY(52px) rotateX(14deg) scale(0.94); }
  to { opacity: 1; transform: translateY(0) rotateX(0) scale(1); }
}

/* 탑 메달리온 — 카드 상단 모서리에 걸친 도장 로고 (D-3 구조 유지, 136→168 확대 B안)

   ⚠️ 이 한 칸이 «두 가지»를 담는다 (2026-09-17 확정 — 사진 있으면 관원 사진, 없으면 도장 로고.
   src 스왑 = `renderCardIdentity` 한 곳). 둘은 담기는 법이 반대다:
     · 로고  = `contain` — 통째로 보여야 한다. `cover` 는 **정사각이 아닌 로고의 좌우를 잘라낸다**
               (가로형 워드마크에서 「격투창고」→「격투창ㄱ」 실렌더 확인 2026-09-22). [[0858]] 이
               hero 로고 판에 내린 답과 같은 답이고, 한쪽만 고치면 **같은 화면 안에서 같은 로고가
               한 자리는 온전하고 한 자리는 잘린다**
     · 사진  = `cover` — 원을 꽉 채워야 한다. `contain` 이면 인물 사진이 흰 여백에 둘러싸여 작아진다
   → 기본을 로고(`contain`)로 두고, 사진일 때만 `.att-card-logo--photo` 가 되돌린다.
   서버 렌더 초기 상태가 로고라 기본값이 맞다. 클래스 토글을 지우면 사진이 letterbox 가 된다.

   안여백이 필요한 이유 = **원판이라 `contain` 만으로는 부족하다** — 이미지가 상자 «모서리»까지 닿으면
   `border-radius: 50%` 가 그 끝을 다시 잘라낸다(168 원에서 가로 로고 양끝 각 5px). 판의 약 6% 를 주면
   760×260 로고가 147.8×50.6 으로 들어와 모서리 거리 78.1 < 반지름 84 로 **완전히 내접**한다.

   ⚠️⚠️ **여기서는 `%` 를 쓰면 안 된다 — `px` 이 정답이고 밴드마다 다시 적는다** (2026-09-22 실측).
   hero 로고 판(`.aurora-logo img`)은 `padding: 6%` 가 맞는데 여기만 반대인 이유는 **이 칸이
   `position: absolute` 라서**다: 백분율 padding 은 «자기 폭»이 아니라 «포함 블록(`.att-card`) 폭»으로
   풀린다. 실측 = 168px 판에 `6%` 를 줬더니 안여백이 **43.19px** 로 잡혀(카드 폭 ≈720 의 6%) 로고가
   82px 짜리로 쪼그라들었다. 두 자리가 다른 단위를 쓰는 것이 **의도다** — 「hero 는 %인데 여기는 px」
   통일 ❌ · `%` 복귀 ❌.
   짝 규칙 = **판 크기를 바꾸는 밴드는 안여백도 «같은 줄에서» 함께 바꾼다**(168→10 · 200→12 · 160→10 ·
   100→6 · kiosk.html page-local 80→5). 한쪽만 고치면 그 밴드만 안여백 비율이 어긋난다.
   판 지름 자체는 [[0558]] 확정값이라 불변 — 바뀐 건 «담기는 법»뿐이다. */
.att-card-logo {
  position: absolute;
  top: -84px;
  left: 50%;
  transform: translateX(-50%);
  width: 168px;
  height: 168px;
  border-radius: 50%;
  object-fit: contain;
  padding: 10px;
  background: #ffffff;
  box-shadow: 0 0 0 6px #ffffff, 0 16px 34px -14px rgba(15, 23, 42, 0.38);
  z-index: 1;
}
/* 관원 사진일 때만 — 원을 꽉 채운다 (위 주석 참조). `renderCardIdentity` 가 src 와 «함께» 토글한다 */
.att-card-logo--photo {
  object-fit: cover;
  padding: 0;
}

/* 로고가 카드 밖으로 돌출하므로 sheen 클리핑은 내부 래퍼가 담당 */
.att-card-clip {
  position: relative;
  min-height: 380px;
  border-radius: inherit;
  overflow: hidden;
}

/* 카드 표면을 한 번 스치는 광택 */
.att-card-sheen {
  position: absolute;
  inset: -40%;
  background: linear-gradient(115deg, transparent 42%, rgba(5, 150, 105, 0.05) 50%, rgba(167, 243, 208, 0.14) 54%, transparent 62%);
  transform: translateX(-70%);
  animation: attSheenSweep 1.6s var(--ease-smooth) 0.95s both;
  pointer-events: none;
}

@keyframes attSheenSweep {
  to { transform: translateX(70%); }
}

.att-card-inner {
  position: relative;
  min-height: 380px;
  padding: 104px 44px 30px; /* 상단 = 메달리온 돌출(84px) + 여백 */
  display: flex;
  flex-direction: column;
}

.att-card-top {
  display: flex;
  flex-direction: column; /* 로고 → 도장명 → 상태 세로 브랜드 락업 (2026-07-16) */
  align-items: center;
  justify-content: center;
  position: relative;
  gap: 10px;
  animation: attFadeIn 0.7s ease-out 0.55s both;
}

/* 도장명 — 로고 아래 독립 중앙 줄로 승격 (21→28 확대 B안) */
.att-card-dojo {
  font-size: 28px;
  font-weight: 800;
  letter-spacing: -0.015em;
  color: var(--color-slate-900);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 90%;
}

/* 출석 완료 — 면 없는 emerald 체크 + 텍스트 (순수 텍스트 상태 신호) */
.att-card-check {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 23px;
  font-weight: 800;
  letter-spacing: 0.01em;
  color: var(--color-primary-600);
  flex-shrink: 0;
}

.att-card-check svg { width: 22px; height: 22px; }

/* 지각 — 회원 대면 결과 카드의 상태 뱃지. 지각은 「나쁨」이 아니라 사실 고지라
   red 는 뱃지 텍스트에만 쓰고 카드 면·테두리는 건드리지 않는다 (Brand Mono). */
.att-card-check.late { color: var(--status-error); }

/* 지각 상세 — 판정 기준 수업 + 몇 분 늦었나 (키오스크 자연어 예외) */
/* 주N회권 초과 줄(.att-card-over*) = 지각 줄과 같은 조판에 합류 (ADR 0527) — 축이 다를 뿐
   「이름 아래 한 줄로 사실을 적는다」는 같은 자리다. 별도 값 신설 ❌ (둘이 동시에 뜰 수 있어 리듬이 갈리면 안 된다) */
.att-card-late,
.att-card-over {
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: center;
  margin-top: 10px;
}
.att-card-late-when,
.att-card-over-when {
  font-size: 23px;
  font-weight: 700;
  color: var(--color-slate-500);
  font-variant-numeric: tabular-nums;
}
.att-card-late-msg,
.att-card-over-msg {
  font-size: 28px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--color-slate-900);
}

.att-card-check svg path {
  /* 23 = icons :: check 실측 path 길이 22.63 바로 위 (2026-08-22 UI 감사 — 구 26 은 폐지된
     heroicons path(≈25.4) 기준이라 새 path 에선 draw 의 마지막 13% 가 아무것도 안 그리는 빈 구간이었다).
     path 를 바꾸면 이 값도 함께 재측정할 것 — getTotalLength() 로 확인 */
  stroke-dasharray: 23;
  stroke-dashoffset: 23;
  animation: attCheckDraw 0.5s ease-out 1.1s forwards;
}

/* 중복 출석 — 배지를 도장 이름 아래 독립 중앙 줄(slate 알약)로 내림 (A안, 절대배치 겹침 방지) */
.att-card-top:has(.att-card-check.dup) { flex-direction: column; gap: 9px; }
.att-card-top:has(.att-card-check.dup) .att-card-dojo { max-width: 90%; }

/* 중복 출석 — 형제 2상태(완료 emerald · 지각 red)와 같은 무면 텍스트 (2026-08-19 UI 감사).
   구 slate-100 채움 알약은 frontend-guide §16 「상태 뱃지 = 배경·테두리 0」 위반이었고,
   같은 슬롯 3상태 중 이것만 면을 가져 뱃지가 아니라 버튼처럼 읽혔다.
   크기 12.5 는 유지 (base 14/800 승격 무효화 — 원래 의도). 채움 재도입 ❌
   ⚠️ 사용처 2곳 — 중복 출석 배지 + **하원 확인 카드의 등원 시각 줄**(`kiosk.html` `#leaveCheckedAt`).
   값 변경 시 두 자리 동시 확인 (2026-08-30 UI 감사 등재). */
.att-card-check.dup {
  position: static;
  font-size: 18px;
  font-weight: 800;
  color: var(--color-slate-600);
  letter-spacing: 0.04em;
}

/* svg 아이콘이 없거나 JS 로 숨겨진 자리라 좌측 slate dot 을 ::before 로 대체
   (중복 출석 = JS 가 숨김 · 하원 확인 카드 = 애초에 svg 자식이 없음) */
.att-card-check.dup::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-slate-400);
  flex-shrink: 0;
}

@keyframes attCheckDraw {
  to { stroke-dashoffset: 0; }
}

.att-card-center {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  padding: 18px 0;
}

/* 이름 — 단색 slate-900 (gradient text 폐기 2026-08-19 UI 감사).
   frontend-guide §0 이 gradient text 를 「AI 티 패턴」으로, §컬러가 시맨틱 그라데이션을 ❌ 로 두고 있고,
   **같은 키오스크 화면의 시계(.aurora-clock)가 2026-05-25 에 이미 같은 이유로 단색 전환**을 마쳤다.
   등재된 키오스크 예외는 `.aurora-hero` 워시뿐 — 이름은 그 목록에 없다.
   축하 delight 는 카드 sheen · 등장 모션(attRiseIn) · 체크 draw 3종이 이미 담당한다.
   gradient·shimmer 재도입 ❌ */
.att-card-name {
  font-size: 76px; /* 56→76 — 확대 B안 (2026-09-01 사용자 확정) */
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.08;
  color: var(--color-slate-900);
  animation: attRiseIn 0.8s cubic-bezier(0.22, 0.9, 0.24, 1) 0.5s both;
  margin-bottom: 13px;
}

@keyframes attRiseIn {
  from { opacity: 0; transform: translateY(18px); }
  to { opacity: 1; transform: translateY(0); }
}


@keyframes attFadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* 벨트 행 — Belt.create 미니 칩 + 텍스트 */
.att-card-belt {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 26px;
  font-weight: 600;
  color: var(--color-slate-500);
  animation: attFadeIn 0.7s ease-out 0.8s both;
}

/* 출결 지표 줄 — 관원 상세·관원 앱 공유 지표(최근 N개월)의 키오스크 문장형 (2026-09-01 사용자 확정,
   시안 kiosk-scale-and-stats). 값(b)만 800/slate-900, 캡션·라벨은 600/slate-500 — 벨트 줄과 같은 톤 층.
   attendanceRate null(계산 불가)이면 JS 가 줄 통째 숨김 · lateRate null 이면 지각률 조각만 숨김(ADR 0120 게이트). */
/* 하원 «질문» (ADR 0558 갱신 12) — 버튼 바로 위. 이 줄이 없으면 관원은 묻지 않은 질문에 답한다.
   출석 카드의 「출석 완료」와 같은 체급이되 색은 중립(slate-900) — emerald 는 «완료» 신호 자리다. */
.att-leave-ask {
  margin-top: 16px;
  font-size: 19px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--color-slate-900);
  animation: attFadeIn 0.7s ease-out 1s both;
}

/* 출결 지표 = «두 칸 스탯» (ADR 0558 갱신 11, 2026-09-02 사용자 확정 B안 · 시안 kiosk-rates-lines).
   칸 안 글자가 짧아 **어느 폭에서도 안 꺾인다** — 구 문장형(`.att-card-rates`+`.att-card-frac`)은
   큰 줄이 「최근 N개월 · 출석률 · 지각률」 셋을 져서 좁은 화면에서 3줄이 되는 경로가 있었다.
   구 문장형 재도입 ❌ · 비율 줄에 분수를 괄호로 합치기 ❌(갱신 10 에서 실측으로 배제).
   ⚠️ **인지된 대가 = 바로 아래 `.att-card-bottom`(남은 기간·만료일)과 모양이 같다** —
   한 카드에 스탯 밴드가 둘로 보이는 것을 알고 고른 값이다. 「밴드가 두 개」 감사 재지적 ❌. */
.att-card-stats {
  margin-top: 14px;
  animation: attFadeIn 0.7s ease-out 0.9s both;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.att-stats-cap {
  font-size: 20px;
  font-weight: 600;
  /* 기간을 말하는 유일한 글자 → slate-500 (§slate-400 승격 기준 · 공용 fragment `.attst-cap` 과 한 값) */
  color: var(--color-slate-500);
  letter-spacing: -0.01em;
}

.att-stats-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, auto));
  gap: 0 30px;
  text-align: center;
}

/* 지각 칸이 빠지면(관원 노출 OFF) 남은 한 칸이 가운데 선다 */
.att-stats-grid:has(.att-stats-col[style*="none"]) { grid-template-columns: minmax(0, auto); }

.att-stats-label {
  font-size: 18px;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--color-slate-500);
}

.att-stats-value {
  font-size: 28px;
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.15;
  color: var(--color-slate-900);
  font-variant-numeric: tabular-nums;
}

.att-stats-sub {
  font-size: 20px;
  font-weight: 600;
  color: var(--color-slate-500);
  font-variant-numeric: tabular-nums;
  min-height: 1em;                 /* 분수가 없어도 두 칸 높이가 같게 */
}

.att-card-bottom {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 64px;
  border-top: 1px solid var(--border-hairline, #eaecef);
  padding-top: 18px;
  animation: attFadeIn 0.7s ease-out 1s both;
}

.att-stat { text-align: center; }

/* 스탯 라벨 — 마이크로 라벨 토큰으로 색·굵기 수렴 (2026-08-19 UI 감사).
   구 700/slate-400 은 §디자인 톤 마이크로 라벨(12/800/slate-500)에서 두 축이 벗어나 있었고,
   slate-400 은 「옆에 값이 있는 라벨 = slate-500 승격」(2026-08-12 확정) 대상이기도 하다.
   자간 0.16em 만 유지 — 키오스크는 관원이 **떨어져서 읽는** 화면이라 자간이 판독 거리를 만든다
   (앱 표면으로 이 값 확장 ❌ · 0.04em 로 조이는 재지적 ❌). */
.att-stat-label {
  font-size: 21px;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-slate-500);
  margin-bottom: 5px;
}

/* 값은 «한 줄»로 — 칸이 좁아지면 「1시간 22분」이 꺾이고 그때 라벨만 위로 튀어
   칸끼리 기준선이 어긋난다(2026-09-02 실측). 꺾일 바에는 칸을 넓히거나 값을 줄인다. */
.att-stat-value {
  font-size: 38px;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--color-slate-900);
  white-space: nowrap;
}

.att-stat-value.hot { color: var(--color-primary-600); }

/* 만료 임박 — 축하 화면이라 red 금지, slate 로만 톤다운 (Brand Mono 시맨틱 색 컨텍스트) */
.att-stat-value.warning,
.att-stat-value.danger { color: var(--color-slate-600); }

/* 중복 출석 — 기존 출석 시각 스탯 (강조값 아님) */
.att-stat-value.muted { color: var(--color-slate-600); }

/* ===== 에러 — 같은 무대 위 조용한 활자 ===== */
.att-err {
  position: relative;
  text-align: center;
  padding: 0 24px;
  animation: attRiseIn 0.7s cubic-bezier(0.22, 0.9, 0.24, 1) 0.15s both;
}

.att-err-msg {
  font-size: 60px;
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.15;
  color: var(--color-slate-900);
  margin-bottom: 14px;
}

.att-err-sub {
  font-size: 25px;
  font-weight: 500;
  color: var(--color-slate-500);
}

/* `.att-err-rule` 폐기 (2026-08-19 UI 감사) — 정보 0의 장식 선이었다.
   §타이포 「각주에 dot·hairline·아이콘 장식 ❌」 + 같은 오버레이가 아이콘을 이미 빼고
   「조용한 활자」 어휘로 통일돼 있어 선 하나만 남는 게 어긋났다. 재도입 ❌ */


/* ===== 하단 미니 진행바 (자동 닫힘 잔여 시간 — 풀폭 ❌, 중앙 140px) ===== */
/* ===== 자동 닫힘 카운트다운 — 카드 하단을 가로지르는 선 =====
   (2026-09-01 사용자 확정 C안 + 문구 제거 · 시안 kiosk-leave-countdown)
   구 `.att-mini-progress`(화면 맨 아래 140px·2px)는 카드에서 200px 넘게 떨어진 데다 얇아
   **아무도 못 봤고**, 그래서 하원 카드는 「5초 뒤 자동으로 닫혀요」 문구로 한 번 더 말하고 있었다.
   → 선을 카드 «안» 맨 아래 전폭으로 들여오고 그 문구는 삭제(재도입 ❌ — 선이 그 말을 한다).
   성공·하원 확인·에러 **세 화면 같은 어휘** — 한 화면만 바꾸면 신호가 갈린다.
   카드 안이라 `.att-card-clip` 의 overflow 가 radius 를 맞춰 준다 — 오버레이 직속 복귀 ❌ */
.att-countdown {
  height: 10px;
  overflow: hidden;
  background: var(--color-slate-200);
  animation: attFadeIn 0.6s ease-out 1.2s both;
}

/* 에러는 카드가 없어 글 블록 하단이 그 자리 — 폭은 블록을 따르되 radius 는 자기가 갖는다.
   트랙 페이드를 성공(1.2s 지연)보다 «앞당긴다» — 번호 오입력은 3초만 머무는데(ERROR_RETRY_MS)
   1.2s 는 선이 이미 절반 지난 뒤에 나타나 「선이 다 지나가면 닫힌다」는 유일한 신호가 무너진다.
   0.5s + 0.5s = 1.0s 에 완전히 보이고, 그 시점이 곧 선이 «출발»하는 시점이다(bar delay 1s). */
.att-countdown--err {
  margin-top: 26px;
  border-radius: 999px;
  animation: attFadeIn 0.5s ease-out 0.5s both;
}

.overlay-progress-bar {
  display: block;
  height: 100%;
  animation: progressShrink linear forwards;
}

/* delay(0.6s) + duration 이 JS `RESULT_AUTO_CLOSE_MS`(7000ms)와 «같아야» 선이 다 지나가는 순간
   카드가 닫힌다 — 구 5s 는 0.6s 만큼 넘겨 카드가 닫힐 때 선이 11% 남아 있었다 (2026-09-01) */
.success-progress-bar {
  background: var(--color-primary-500);
  animation-duration: 6.4s;
  animation-delay: 0.6s;
}

/* 성공과 달리 duration 이 «두 값»(번호 3초 · 안내 7초)이라 JS `showError` 가 넣는다 —
   여기 delay 는 JS `ERROR_BAR_DELAY_MS` 와 같아야 하고 `delay + duration = hold` 가 짝 규칙([[0561]]).
   duration 기본값 6s = 안내 7초 − delay 1s — JS 가 못 넣었을 때 선이 즉시 사라지지 않게 하는 바닥값. */
.error-progress-bar {
  background: var(--color-slate-400);
  animation-delay: 1s;
  animation-duration: 6s;
}

@keyframes progressShrink {
  from { width: 100%; }
  to { width: 0%; }
}

/* 모바일·좁은 키오스크 — 카드 압축. 폰 구간은 높이 예산이 없어 확대 B안을 받지 않고 기존 실측값 유지 —
   베이스가 커진 만큼 눌러야 하는 축(도장명·상태·지각 줄·스탯 라벨·간격)을 함께 되돌린다 (2026-09-01) */
@media (max-width: 768px) {
  .att-card { min-height: 0; margin-top: 50px; }
  .att-card-logo { width: 100px; height: 100px; top: -50px; padding: 6px; box-shadow: 0 0 0 5px #ffffff, 0 14px 30px -12px rgba(15, 23, 42, 0.38); }
  .att-card-clip { min-height: 0; }
  .att-card-inner { min-height: 0; padding: 64px 24px 20px; }
  .att-card-center { padding: 22px 0; }
  .att-card-dojo { font-size: 22px; }
  /* 15px = 18.66 미만 → emerald 글자는 600 → 700 (ADR 0495). `.late`·`.dup` 는 아래 자기 색이 이긴다 */
  .att-card-check { font-size: 15px; color: var(--color-primary-700); }
  /* 베이스 23px 은 「큰 글자」라 --status-error 로 3:1 을 통과하지만 여기서 15px 로 줄어
     4.5:1 기준에 걸린다 → ADR 0495 「18.66px 미만은 red-500 → --status-error-ink」.
     짝 표면(`admin/attendance/list` `.att-mobile-stats-late`)이 같은 사유로 이미 승격된 자리다.
     베이스 선언(6731)은 불변 — 토큰째 내리면 면·테두리용 --status-error 축과 섞인다.
     (2026-09-08 UI 감사) */
  .att-card-check.late { color: var(--status-error-ink); }
  .att-card-check svg { width: 17px; height: 17px; }
  .att-card-check.dup { font-size: 15px; }
  .att-card-name { font-size: 40px; }
  .att-card-belt { font-size: 17px; }
  /* 폰 — 12px 바닥([[0038]]) 때문에 라벨·분수는 더 못 내린다 */
  .att-card-stats { margin-top: 10px; gap: 4px; }
  .att-leave-ask { font-size: 15px; margin-top: 11px; }
  .att-stats-cap { font-size: 18px; }
  .att-stats-grid { gap: 0 20px; }
  .att-stats-label { font-size: 18px; }
  .att-stats-value { font-size: 21px; }
  .att-stats-sub { font-size: 18px; }
  .att-card-late-when, .att-card-over-when { font-size: 17px; }
  .att-card-late-msg, .att-card-over-msg { font-size: 20px; }
  .att-card-bottom { gap: 40px; }
  .att-stat-label { font-size: 18px; }
  .att-stat-value { font-size: 22px; }
  .att-err-msg { font-size: 32px; }
  .att-err-sub { font-size: 17px; }
}

/* 세로 탭 결과 카드 보강 (2026-09-01 사용자 확정 · 시안 kiosk-scale-and-stats) — 세로 1280px 급에서
   B안 카드(높이 ≈510px)가 화면 40%만 차 위아래가 비어 보였다(사용자 신고). 폭 94vw(≤736)·로고 200·
   이름 88 로 한 단계 더 — 실측 카드 높이 636px(메달리온 포함 736px < 880 뷰포트에 안착). 조건은
   키오스크 태블릿 세로 입력 밴드와 같은 축 — **임계 880 = 사파리 크롬 실측** (ADR 0558 갱신: 구 1000 은
   기기 해상도 기준이라 주소창을 뺀 실뷰포트(iPad 세로 929~1006px)가 못 들어왔다. 되올리지 ❌).
   ≤768 압축 블록보다 뒤에 둬야 744~768px 폭 태블릿을 덮는다 — 위로 옮기지 ❌ */
@media (orientation: portrait) and (min-width: 600px) and (min-height: 880px) {
  .att-card { width: min(736px, 94vw); min-height: 380px; margin-top: 100px; }
  .att-card-logo { width: 200px; height: 200px; top: -100px; padding: 12px; box-shadow: 0 0 0 6px #ffffff, 0 16px 34px -14px rgba(15, 23, 42, 0.38); }
  .att-card-clip { min-height: 380px; }
  .att-card-inner { min-height: 380px; padding: 124px 48px 40px; }
  /* 「출석에 관한 글씨 전반 크게」 (2026-09-01 사용자 확정 「더 키우기」 · 시안 kiosk-portrait-hero) —
     이름만 큰 카드가 아니라 상태 배지·벨트·지표·스탯 라벨까지 한 체급 위로. 되돌리지 ❌ */
  .att-card-top { gap: 14px; }
  .att-card-dojo { font-size: 40px; }
  .att-card-check { font-size: 34px; }
  .att-card-check svg { width: 38px; height: 38px; }
  /* dup = 중복 출석 배지 + **하원 확인 카드의 등원 시각 줄**(사용처 2곳 — style.css 원 주석 참조).
     하원 카드에서는 「16:40에 등원했어요」가 판단 근거라 30 (2026-09-01 사용자 확정) */
  .att-card-check.dup { font-size: 36px; }
  .att-card-center { padding: 30px 0; }
  .att-card-name { font-size: 98px; margin-bottom: 18px; }
  .att-card-belt { font-size: 36px; }
  .att-card-stats { margin-top: 18px; gap: 9px; }
  .att-leave-ask { font-size: 34px; margin-top: 22px; }
  .att-stats-cap { font-size: 30px; }
  .att-stats-grid { gap: 0 44px; }
  .att-stats-label { font-size: 30px; }
  .att-stats-value { font-size: 44px; }
  .att-stats-sub { font-size: 30px; }
  .att-card-late-when, .att-card-over-when { font-size: 32px; }
  .att-card-late-msg, .att-card-over-msg { font-size: 38px; }
  .att-card-bottom { padding-top: 28px; gap: 80px; }
  .att-stat-label { font-size: 30px; }
  .att-stat-value { font-size: 56px; }
  .att-err-msg { font-size: 72px; }
  .att-err-sub { font-size: 36px; }
}

/* 좁은 태블릿 세로 (폭 600~767px) — 결과 카드도 한 계단 낮춤.
   위 보강 값(이름 98·「출석 완료」 34)은 폭 768 이상 전제 — 620px 실측에서 카드가 화면을 넘겼다.
   실기기 = iPad mini 세로(744) 계열. 위 880 밴드 **뒤**에 둬야 덮는다 — 위로 옮기지 ❌ */
@media (orientation: portrait) and (min-width: 600px) and (max-width: 767px) and (min-height: 880px) {
  .att-card { margin-top: 80px; min-height: 340px; }
  .att-card-logo { width: 160px; height: 160px; top: -80px; padding: 10px; }
  .att-card-clip { min-height: 340px; }
  .att-card-inner { min-height: 340px; padding: 100px 36px 32px; }
  .att-card-dojo { font-size: 32px; }
  .att-card-check { font-size: 27px; }
  .att-card-check svg { width: 30px; height: 30px; }
  .att-card-check.dup { font-size: 30px; }
  .att-card-center { padding: 22px 0; }
  .att-card-name { font-size: 76px; margin-bottom: 14px; }
  .att-card-belt { font-size: 30px; }
  .att-card-stats { margin-top: 14px; gap: 7px; }
  .att-leave-ask { font-size: 26px; margin-top: 18px; }
  .att-stats-cap { font-size: 24px; }
  .att-stats-grid { gap: 0 34px; }
  .att-stats-label { font-size: 23px; }
  .att-stats-value { font-size: 34px; }
  .att-stats-sub { font-size: 23px; }
  .att-card-late-when, .att-card-over-when { font-size: 25px; }
  .att-card-late-msg, .att-card-over-msg { font-size: 30px; }
  .att-card-bottom { padding-top: 22px; gap: 56px; }
  .att-stat-label { font-size: 24px; }
  .att-stat-value { font-size: 44px; }
  .att-err-msg { font-size: 56px; }
  .att-err-sub { font-size: 29px; }
}

/* ===== Member Selection Overlay (뒤 4자리 충돌 — 풀스크린 선택, 성공/에러 오버레이와 톤 통일) ===== */
.attendance-overlay.select {
  background:
    radial-gradient(ellipse 80% 60% at 70% 90%, rgba(16, 185, 129, 0.1), transparent 60%),
    linear-gradient(160deg, #ffffff 0%, var(--color-primary-50) 100%);
  padding: 44px 40px;
  overflow-y: auto;
}

.select-panel {
  width: 100%;
  max-width: 600px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 30px;
}

.select-head { text-align: center; }

/* `.select-badge` 폐기 (2026-08-19 UI 감사) — 「출석 체크」 알약이 정보를 더하지 않았다.
   화면 전체가 키오스크고 바로 아래 제목이 「누구신가요?」라 축이 이미 명확하며,
   §0 「섹션마다 대문자 eyebrow」 + §섹션 머리 「무면 마이크로 라벨 단일」 + 제3 자간값(0.1em) 3중 위반이었다.
   초단신 가로에서 3명째를 화면 밖으로 밀어내던 자리이기도 하다. 재도입 ❌ */

.select-title {
  font-size: 34px;
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--color-slate-900);
  line-height: 1.2;
  margin-bottom: 10px;
}

.select-sub {
  font-size: 17px;
  color: var(--color-slate-500);
}

.select-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 100%;
}

.select-item {
  display: flex;
  align-items: center;
  gap: 20px;
  width: 100%;
  padding: 24px 28px;
  background: rgba(255, 255, 255, 0.85);
  border: 1.5px solid var(--color-slate-200);
  border-radius: 18px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  transition: all 0.15s var(--ease-out-expo);
}

/* 동명이인 고르기 항목의 관원 사진 — 사진이 있는 후보만 (2026-09-16 사용자 확정 A안 ·
   시안 member-photo-kiosk). 치수는 **전 화면 한 벌 112**(ADR 0825 · 구 72 supersede) —
   `.member-photo` 가 이미 그 값을 주므로 여기서 폭·높이를 다시 적지 ❌ (두 벌이 되면 한쪽만 움직인다).
   좁은 밴드는 kiosk.html page-local 이 한 계단씩 줄인다(그 파일이 밴드의 SSOT) — 가로 압축 화면은 카드가 넘친다.
   ⚠️ 출석 완료 결과 카드에는 사진을 넣지 ❌ — §관원 사진 아바타 주석 참조. */

.select-item:focus-visible {
  border-color: var(--color-primary-500);
  background: #fff;
  transform: translateY(-2px);
  box-shadow: 0 10px 28px -10px rgba(5, 150, 105, 0.3);
  outline: none;
}
@media (hover: hover) {
  .select-item:hover {
    border-color: var(--color-primary-500);
    background: #fff;
    transform: translateY(-2px);
    box-shadow: 0 10px 28px -10px rgba(5, 150, 105, 0.3);
    outline: none;
  }
}

.select-num {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: 12px;
  background: var(--color-slate-900);
  color: #fff;
  font-size: 20px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  display: flex;
  align-items: center;
  justify-content: center;
}

.select-item:focus-visible .select-num {
  background: var(--color-primary-600);
}
@media (hover: hover) {
  .select-item:hover .select-num {
    background: var(--color-primary-600);
  }
}

.select-item-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.select-item-top {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 7px;
}

.select-item-name {
  font-size: 28px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--color-slate-900);
}

.select-item-meta {
  font-size: 16px;
  color: var(--color-slate-500);
  font-variant-numeric: tabular-nums;
}

.select-pass-badge {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 800;
  color: var(--color-slate-700);
  background: #fff;
  border: 1px solid var(--color-slate-200);
  border-radius: 999px;
  padding: 5px 12px 5px 10px;
}

.select-pass-badge::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-primary-600);
}

.select-pass-badge.none { color: var(--color-slate-500); }   /* 뱃지의 유일한 글자 → slate-500 (출석 명단 「이용권 없음」과 한 값). dot 300 유지 */
.select-pass-badge.none::before { background: var(--color-slate-300); }

/* 상태 병기(일시정지 등) — 중립 신호라 dot 도 slate (정지=중립, emerald ❌) */
.select-pass-badge.has-status { color: var(--color-slate-600); }
.select-pass-badge.has-status::before { background: var(--color-slate-400); }

/* 판정만 세로로 넓힌다(::after) — 압축 밴드(≤768·가로 ≤560h·≤380h)에서 27~39px 이던 관원 대면 버튼.
   보이는 크기·밴드별 높이 예산 불변 (§히트박스 확대 idiom). */
.select-cancel::after { content: ''; position: absolute; inset: -8px 0; }
.select-cancel {
  position: relative;
  font-size: 16px;
  font-weight: 700;
  color: var(--color-slate-500);
  background: transparent;
  border: none;
  padding: 10px 20px;
  border-radius: 999px;
  cursor: pointer;
  transition: all 0.15s;
}

@media (hover: hover) {
  .select-cancel:hover {
    background: rgba(15, 23, 42, 0.05);
    color: var(--color-slate-700);
  }
}

/* 모바일·좁은 키오스크 — 세로 압축 */
@media (max-width: 768px) {
  .attendance-overlay.select { padding: 28px 18px; }
  .select-panel { gap: 22px; }
  .select-title { font-size: 26px; }
  .select-sub { font-size: 14px; }
  .select-item { padding: 18px; gap: 14px; }
  /* 96 = 한 벌 112 에서 한 계단 (ADR 0825 · 구 64 supersede — 112 → 96 → 80 사다리의 가운데).
     폰 밴드는 번호 원(38) + 사진 + 이름이 한 행이라 112 를 그대로 쓰면 행이 부푼다.
     ⚠️ 셀렉터가 `.member-photo.select-item-photo` 인 이유 = **치수를 `.member-photo` 가 갖게 됐기 때문**(ADR 0825).
     구 `.select-item-photo` 단독(0,1,0)은 아래쪽 §관원 사진 `.member-photo`(같은 0,1,0 · 파일에서 **뒤**)에
     순서로 져서 폰에서 112 가 그대로 섰다(2026-09-20 실측). 한쪽만 되돌리지 ❌ */
  .member-photo.select-item-photo { width: 96px; height: 96px; }
  .select-num { width: 38px; height: 38px; font-size: 17px; }
  .select-item-name { font-size: 22px; }
  .select-item-meta { font-size: 14px; }
  .select-item-top { gap: 10px; }
}

/* 세로 탭 동명이인 목록 확대 (2026-09-01 사용자 확정 A안 · 시안 kiosk-portrait-fill 「전반 큼직」) —
   결과 카드 보강과 같은 밴드 축(portrait ≥600w ≥880h). ≤768 압축 블록보다 뒤에 둬야
   744~768px 폭 태블릿을 덮는다 — 위로 옮기지 ❌ */
@media (orientation: portrait) and (min-width: 600px) and (min-height: 880px) {
  .select-panel { max-width: 700px; }
  .select-title { font-size: 46px; }
  .select-sub { font-size: 27px; }
  .select-item { padding: 30px; }
  .select-num { width: 60px; height: 60px; font-size: 30px; }
  .select-item-name { font-size: 40px; }
  /* 메타·배지는 [[0558]] 갱신 3 에서 누락된 자리 (2026-09-01 사용자 확정 · 시안 kiosk-leave-select-scale) —
     이 화면은 «연락처 뒷자리·벨트·이용권»으로 본인을 가리는 곳이라 그 줄이 실제 판단 근거다.
     배지는 배포 전 13px 그대로였다. 13/23 복귀 ❌ */
  .select-item-meta { font-size: 28px; }
  .select-pass-badge { font-size: 22px; gap: 8px; }
  .select-pass-badge::before { width: 9px; height: 9px; }
  .select-cancel { font-size: 26px; padding: 14px 26px; }
}

/* 좁은 태블릿 세로 (폭 600~767px) — 동명이인 목록도 한 계단 낮춤 (위 밴드 뒤 선언 의무) */
@media (orientation: portrait) and (min-width: 600px) and (max-width: 767px) and (min-height: 880px) {
  .select-title { font-size: 38px; }
  .select-sub { font-size: 22px; }
  .select-item { padding: 24px; }
  .select-num { width: 52px; height: 52px; font-size: 25px; }
  .select-item-name { font-size: 34px; }
  .select-item-meta { font-size: 23px; }
  .select-pass-badge { font-size: 19px; }
  .select-cancel { font-size: 22px; padding: 12px 22px; }
}

/* ========================================
   61. (TABS dead-code 제거 2026-05-18) — §18 Underline 단일 박제로 통합
   ======================================== */

/* ========================================
   62. PLANS GRID (이용권 관리)
   ======================================== */
.plans-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 20px;
  /* 카드는 «자기 내용만큼만» 선다 ([[0757]], 2026-09-17 사용자 확정 C 방향 · 시안 `passes-products-unified`).
     기본값 stretch 는 한 줄에서 가장 긴 카드(횟수권 3행)에 맞춰 옆 카드를 늘려, 1개월권 아래
     빈 면이 ≈60px 남았다(1440px 실측). stretch 복귀 ❌ — 그 빈 면이 「구리다」의 절반이었다. */
  align-items: start;
}

/* D. MONO CONCIERGE — 이용권 카드: hairline + 상단 emerald accent + shadow 제거 (2026-05-15) */
.plan-card {
  background: var(--bg-primary);
  border: 1px solid #eaecef;
  border-radius: 14px;
  overflow: hidden;
  transition: border-color 0.2s ease;
  position: relative;
}

/* 2026-05-26: .plan-card::before 상단 emerald gradient hairline 폐지 —
   project-frontend-guide §「Carded surface — color rail 일반 금지」. */

@media (hover: hover) {
  .plan-card:hover {
    border-color: #d8dde3;
    box-shadow: none;
  }
}

/* D — 이용권 카드 헤더: 흰 배경, divider 없음.
   [[0757]] (2026-09-17 사용자 확정 C 방향 · 시안 `passes-products-unified`): 구 divider 는 «칩 한 줄»을 위해
   카드를 위아래 두 덩이로 쪼개고 있었다 — 칩·액션 → 이름 → 조건 → 가격이 한 면에서 위에서 아래로
   읽히도록 선을 하나로 줄인다. 카드 안에 남는 유일한 선 = 아래 `.plan-card-price` 상단선(값이
   바뀌는 자리를 가른다). **헤더 divider 재도입 ❌** · 좌우 20 은 본문과 같은 시작선(갈리면 칩과
   이름이 어긋난다). quickstart 카드 그리드와 공유하는 선언이라 한쪽만 고치지 ❌. */
.plan-card-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 16px 20px 0;
  background: #ffffff;
}

/* 이용권 카드 — 종류 chip (D.MONO CONCIERGE v3 2026-05).
   글로벌 .badge-period/-count/-day-specific 가 흰 bg + slate-700 + dot 으로 색을 책임지고,
   .plan-type-chip 은 padding·radius·font-size 등 chip 형태만 정의. */
.plan-type-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 2px 9px 2px 8px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.005em;
  line-height: 1.5;
  white-space: nowrap;
}

/* gap 8 = 「수정」과 「삭제」 사이 오탭 완충 (2026-08-11 사용자 확정 A안, 시안 `plan-card-icon-gap`).
   32px 표적 둘이 4px 로 붙어 있어 터치에서 수정하려다 삭제를 누르기 쉬웠다 — 삭제는 이름을 보여주는
   danger 확인 모달이 막아 주지만 그 모달을 닫는 헛수고가 남는다. 버튼 치수·아이콘·카드 높이는 불변
   (표적 확대 B안은 터치 기기에서만 헤더가 6px 높아져 액션이 이용권 이름보다 먼저 눈에 들어 미채택). */
.plan-card-actions {
  margin-left: auto;
  display: flex;
  gap: 8px;
}

/* 이용권 카드 수정·삭제 아이콘 — 32px 버튼 대비 또렷하게 (글로벌 16px 오버라이드) */
.plan-card-actions .btn-icon svg {
  width: 22px;
  height: 22px;
}

/* 위 헤더가 아래 padding 을 0 으로 넘겨주므로 칩 ↔ 이름 간격은 여기 top 12 가 소유.
   좌우·하단은 기존 20/18 (divider 를 뺀 만큼 하단을 2px 조여 카드 높이를 현행 근처로 유지). */
.plan-card-body {
  padding: 12px 20px 18px;
}

.plan-name {
  font-size: var(--text-lg);
  /* 800 (구 600 ❌ — 2026-09-04 UI 감사 R): 카드 헤더에 제목이 없어 이 h3 가 카드 제목인데 weight 사다리
     (800/700/500/400) 밖 값이었다. §카드/모달 타이틀 800 통일 — 크기 18 은 유지(체급 불변, 굵기만 사다리로). */
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  margin: 0 0 8px;
}

.plan-description {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin: 0 0 16px;
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.plan-details {
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* 간격 소유자를 «항상 있는» 이 요소로 (2026-09-04 UI 감사 R): 구 `margin-bottom: 16px` 는 body(padding 20)
     마지막 자식이라 카드 아래 여백만 36 이었고, 이름↔상세 간격은 조건부 `.plan-description`(있으면 16 / 없으면
     `.plan-name` 8)이 소유해 같은 그리드 안에서 리듬이 두 벌이었다. margin-top 16 + 형제 마진 상쇄로 설명
     유무와 무관하게 16 고정, 하단은 padding 20 대칭. quickstart 카드 그리드 동시 적용(같은 클래스). */
  margin-top: 16px;
  margin-bottom: 0;
}

.plan-detail-item {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-sm);
}

.detail-label {
  /* KV 카드 라벨 = slate-500 승격 (§slate-400 승격 기준, 2026-08-12 사용자 확정) */
  color: var(--color-slate-500);
}

.detail-value {
  font-weight: var(--font-medium);
  color: var(--text-primary);
}

.detail-value.price {
  color: var(--text-primary);
  font-weight: var(--font-semibold);
  /* 금액 값 슬롯 = tabular ([[0075]] 스코프 · §D.MONO 「숫자 tabular-nums 누락 ❌」). 굵기·크기는 카드 위계라 현행. */
  font-variant-numeric: tabular-nums;
}

/* 가격 행 카드가 병기 — 현금가와 다를 때만 렌더 (quickstart·passes/products 공통) */
.price-card-inline {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--color-slate-500);
  margin-left: 6px;
}

/* 이용권 카드 «바닥 가격 슬롯» — [[0757]] (2026-09-17 사용자 확정 C 방향 · 시안 `passes-products-unified`).
   구 상태는 가격이 `.plan-detail-item` 한 줄이라 「이용 기간」과 완전히 같은 무게였다 — 상품 카드에서
   가장 중요한 숫자인데 위계가 0이었다. 상세 행에서 빼 카드 바닥에 세우고, 강조는 **색이 아니라
   무게·크기·자리**로만 한다(§금액 값 슬롯 = slate, emerald ❌).
   값 토큰 = §강조 숫자(800 · tabular · -0.025em). 라벨 = §마이크로 라벨(12/800/0.04em, 한글 자간).
   ⚠️ 이 선언은 `passes/products` 와 `setup/quickstart` 가 **함께** 쓴다 — 한쪽만 고치지 ❌. */
.plan-card-price {
  margin-top: 14px;
  padding-top: 13px;
  border-top: 1px solid var(--border-divider);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.plan-card-price-label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--color-slate-500);
  flex-shrink: 0;
}

.plan-card-price-value {
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -0.025em;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* 카드가는 «아래 줄»로 — 280px 카드에서 「220,000원 · 카드 240,000원」(17px + 12.5px ≈ 200px)이
   라벨(28)과 gap(10)까지 더하면 카드 안쪽 240px 을 넘겨 두 값이 붙어 읽힌다(실측).
   인라인 병기 자체는 `.detail-value.price`·용품 행에서 그대로 유지 — 여기만 줄을 바꾼다. */
.plan-card-price .price-card-inline {
  display: block;
  margin-left: 0;
  margin-top: 3px;
}






/* 이용권 추가 dashed 타일 — grid 안 "행 추가" idiom (1.5px dashed, tier 버튼 아님).
   장식 원형 아이콘 폐지(2026-07-03) — 작은 + SVG + 라벨만. quickstart Step 1 카드 그리드 사용. */
.plan-card-add {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 180px;
  border: 1.5px dashed var(--color-slate-300);
  border-radius: 14px;
  background: transparent;
  color: var(--color-slate-500);
  font-size: 13px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}

@media (hover: hover) {
  .plan-card-add:hover {
    border-color: var(--color-primary-300);
    color: var(--color-primary-600);
    background: var(--color-primary-50);
  }
}

.plan-card-add svg {
  width: 18px;
  height: 18px;
  stroke-width: 2.5; /* icons::plus(기본 2) 오버라이드 — 구 인라인 heroicons 2.5 유지 (2026-08-21 lucide 통일) */
}

@media (max-width: 768px) {
  .plan-card-add {
    min-height: 0;
    flex-direction: row;
    padding: 16px;
  }
}

/* ===== 이용권 폼 모달 (fragments/plan-form-modal) — products·quickstart 공유 컴포넌트 ===== */
/* 수정 모드 타입 배너: D.MONO 이용권 뱃지 어휘 (흰 bg + slate-200 hairline + 좌측 emerald dot) */
.product-modal-banner {
  margin-bottom: 16px;
}

.product-modal-banner-inner {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-radius: 999px;
  background: #fff;
  border: 1px solid var(--color-slate-200);
}

.product-modal-banner-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-primary-600);
  flex-shrink: 0;
}

.product-modal-banner-label {
  font-size: 13px;
  font-weight: 700;
  color: var(--color-slate-700);
  letter-spacing: -0.01em;
}

.product-modal-banner-desc {
  font-size: 12px;
  color: var(--color-slate-500);
}

.product-modal-banner-desc::before {
  content: "·";
  margin: 0 4px;
  color: var(--color-slate-300);
}

/* 셀프 정지 정책 — toggle-group 안 fields grid */
#pausePolicyFields {
  transition: opacity 0.18s;
}

/* 기간 단위 세그먼트 (일/개월) — 2026-08-22 개월 단위 도입, 시안 plan-duration-month-unit A안.
   숫자 칸 하나에 단위를 붙여 「30 일」·「1 개월」을 같은 자리에서 고른다. 프리셋 칩 줄을 따로 두면
   같은 결정이 두 층으로 갈리고 4개 타입 모달이 모두 길어진다 (B안 반려 사유).
   checked 어휘 = .radio-card 와 동일(primary 테두리 + primary-50 면) — 새 강조 어휘 발명 ❌ */
.duration-unit-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
}

.duration-unit-row .form-input {
  flex: 1 1 auto;
  min-width: 0;
}

/* 「기한 없음」 토글 행 — 횟수권 전용 (2026-09-16 사용자 확정 · ADR 0751 · 시안 count-pass-no-expiry-input B안,
   컨트롤 모양은 같은 날 시안 count-pass-no-expiry-control A안). 기간(또는 만료일) 칸 힌트 아래 한 행.
   원자는 글로벌 `.toggle-group` + `.toggle-group-head` + `.toggle-switch` 그대로(같은 모달의 「관원 앱에서 판매」와 한 벌)
   — 이 클래스는 위 칸과의 간격 10px 만 소유한다. 구 네이티브 체크박스(`.form-check-input` 18px + 13.5/600 글자)는
   앱 규칙 「단일 on/off = 토글, 체크박스 ❌」 밖이라 폐지 — 재도입 ❌. 사용처 3곳(plan-form-modal·members/form·members/detail). */
.plan-no-expiry {
  margin-top: 10px;
}

/* 잠긴 기간 칸·단위 세그먼트 = §잠긴 값 칸 어휘 (박스 입력이라 면까지 — 무면 인라인 축과 다르다).
   값을 지우지 않으므로 「적어 둔 숫자가 회색으로 남아 있다」가 곧 되돌릴 수 있다는 신호다. */
.unit-seg input:disabled + span {
  color: var(--text-tertiary);
}

.unit-seg:has(input:disabled) {
  background: var(--bg-secondary);
  cursor: not-allowed;
}

.unit-seg {
  flex: 0 0 auto;
  display: inline-flex;
  border: 1.5px solid var(--border-default);
  border-radius: var(--radius-lg);
  background: var(--bg-primary);
  overflow: hidden;
}

.unit-seg-opt {
  position: relative;
  display: inline-flex;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.unit-seg-opt input {
  position: absolute;
  opacity: 0;
}

.unit-seg-opt > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 48px;
  padding: 0 12px;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text-tertiary);
  transition: background .15s, color .15s;
}

.unit-seg-opt + .unit-seg-opt > span {
  border-left: 1.5px solid var(--border-default);
}

.unit-seg-opt input:checked + span {
  background: var(--color-primary-50);
  color: var(--color-primary-700);
  font-weight: 800;
}

/* 키보드 진입 신호 — 라디오가 시각적으로 숨겨져 있어 :focus-visible 을 라벨 면이 대신 받는다 */
.unit-seg-opt input:focus-visible + span {
  box-shadow: inset 0 0 0 2px var(--color-primary-600);
}

/* ══ 숫자 + 단위 «박스형» — `.stepper` ══════════════════════════════════
   2026-08-27 사용자 확정 A안 (시안 `numeric-unit-input-unify`) — page-local 사본 3벌 승격
   (`system/tenants/detail .td-stepper` · `members/detail .pass-adj-stepper` ·
   `members/list .promo-grau-step`) + 프리셋 칩 뒤 「직접 입력」 칸 4곳.
   `.td-stepper` 주석이 「세 번째 사용처가 생기면 `.stepper` 로 승격 검토」로 예고해 둔 자리이고,
   실제 스테퍼는 3벌 + 값 글자가 13/700 · 15/700 두 벌로 갈려 있었다.

   ── 가르는 축 = 「값을 정하는 칸인가, 목록을 거르는 칸인가」 한 문장 ──
   **정하기** = 이 원자(박스형) 또는 `.ios-item-input--with-unit`(설정 행 안 무면형).
   **거르기** = `.input-with-suffix`(데스크톱 필터 · h36 은 옆 날짜 입력과 키를 맞춘 2026-08-03 확정값)
              · `.fsheet-numfield`(모바일 시트 · h46 은 시트 터치 규격). **이 둘을 흡수 ❌**
              — 끌어오면 그 두 확정값이 깨지고, 앱이 이미 쓰는 「탭=전환 / 칩=거르기」 이분과도 결이 맞다.
   **고르기** = `.filter-chips--form` 프리셋 칩(별개 어휘 — 칩 뒤에 열리는 «입력 칸»만 이 원자).
   **단위가 갈리는 자리** = `.unit-seg` 를 옆에 세운다(위 블록).

   ── 토큰 기준 = 「가장 최근 확정본」(`.pass-adj-stepper`, 2026-08-26) ──
   면 흰색 + hairline + raised 그림자(앱 버튼 어휘와 정합 · slate-50 면은 §잠긴 값 칸 어휘와 겹친다) ·
   h40(모바일 44) · radius 10 · 값 15/700 tabular slate-900(모바일 16 = iOS 포커스 줌 방지 §23) ·
   단위 13/500 slate-500 **박스 안**(밖에 두면 값과 단위 사이에 테두리가 낀다) ·
   focus = 컨테이너 한 곳(`:has(:focus-visible)`, §27 단일 ring — 내부 input 은 무테두리).

   ── ± 는 «자식 유무»지 변형 클래스가 아니다 ──
   몸통·값·단위 토큰은 하나이고, ± 를 넣을지는 **「한두 칸 움직이는 작은 정수인가」** 가 정한다
   (무료 연장 1~24 · 그랄 0~11 · 잔여 ±30 = 있음 / 보너스·정지 직접 입력 1~365 = 없음).
   `.stepper--plain` 류 변형 클래스 신설 ❌ */
.stepper {
  display: inline-flex;
  align-items: stretch;
  height: 40px;
  flex: 0 0 auto;
  border: 1px solid var(--color-slate-200);
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
  /* overflow:hidden = ± 버튼 hover 면이 모서리를 넘지 않게. 자식 focus 아웃라인은
     §27 대로 컨테이너 ring 에 위임하므로 잘릴 것이 없다(구 `.td-stepper` 가 outline 때문에
     overflow 를 못 쓰고 버튼별 radius 로 우회하던 것과 갈리는 지점) */
  overflow: hidden;
}
/* `:focus-within` ❌ (ADR 0616 · 2026-09-05, `.ios-item` rail 과 같은 결함) — ± 버튼을 «마우스로» 누르면
   버튼이 포커스를 쥐어 ring 이 다른 곳을 누를 때까지 남는다. 안의 input 은 텍스트 컨트롤이라
   `:focus-visible` 이 마우스 클릭에도 matches → 「편집 중」 ring 은 그대로. */
.stepper:has(:focus-visible) {
  border-color: var(--color-primary-500);
  box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.12);
}
/* 검증 실패 = 컨테이너가 red ring 을 진다 — 안의 input 은 테두리가 없어 `.is-invalid` 가 보이지 않는다.
   값·ring 은 `.form-input.is-invalid:focus` 와 같은 토큰(§27 단일 ring). focus ring 뒤 선언 = 빨강이 초록을 이긴다 */
.stepper:has(input.is-invalid) {
  border-color: var(--status-error);
  box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.12);
}
.stepper-btn {
  width: 38px;
  border: none;
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-slate-700);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
  flex-shrink: 0;
  font-family: inherit;
}
@media (hover: hover) {
  .stepper-btn:hover:not(:disabled) {
    background: var(--color-slate-50);
    color: var(--color-slate-900);
  }
}
.stepper-btn:disabled { color: var(--color-slate-300); cursor: not-allowed; }
/* 아이콘 = `icons :: minus/plus` (lucide). 크기·굵기는 «쓰는 쪽»이 소유 —
   빼면 프래그먼트가 `width:auto` 로 사라진다 (ADR 0374 실측 함정) */
.stepper-btn svg { width: 16px; height: 16px; stroke-width: 2.5; }
/* 값 칸 — 좌우 hairline 이 ± 와 값을 가른다. ± 가 없으면 그 쪽 선도 없다 */
.stepper-field {
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 0 11px;
  border-left: 1px solid var(--border-hairline);
  border-right: 1px solid var(--border-hairline);
}
.stepper-field:first-child { border-left: 0; }
.stepper-field:last-child { border-right: 0; }
.stepper-field input {
  width: 38px;
  border: none;
  outline: none;
  padding: 0;
  background: transparent;
  text-align: right;
  font-size: 15px;
  font-weight: 700;
  font-family: inherit;
  color: var(--color-slate-900);
  font-variant-numeric: tabular-nums;
}
/* 값이 읽기 전용 텍스트인 자리(그랄 스테퍼처럼 input 이 아닌 경우) */
.stepper-value {
  min-width: 38px;
  text-align: center;
  font-size: 15px;
  font-weight: 700;
  color: var(--color-slate-900);
  font-variant-numeric: tabular-nums;
}
.stepper-value.is-empty { color: var(--color-slate-400); font-weight: 500; }
.stepper-unit {
  font-size: 13px;
  font-weight: 500;
  color: var(--color-slate-500);
  flex-shrink: 0;
}
/* 값 칸이 넓어야 하는 자리(3자리 + 단위) — 폭만 오버라이드 */
.stepper--wide .stepper-field input,
.stepper--wide .stepper-value { width: 62px; min-width: 62px; }
/* 모바일 = 터치 히트 44px + 입력 16px(iOS 포커스 줌 방지 §23).
   베어 input 이라 글로벌 `.form-input` 모바일 강제가 안 닿는다 — 이 원자가 직접 소유.
   ⚠ 선언 위치는 위 세 원자가 «전부 선언된 뒤» 의무 — 동특이도라 앞에 두면 base 가
   소스 순서로 이겨 조용히 무효가 된다 (2026-08-26 콘솔 스테퍼 실측) */
@media (max-width: 768px) {
  .stepper { height: 44px; }
  .stepper-btn { width: 44px; }
  .stepper-field input,
  .stepper-value { font-size: 16px; }
}

/* 이름 프리뷰 타일(.product-modal-name-preview*) + 「직접 입력」 링크(.product-modal-name-edit) =
   2026-08-26 A안 확정으로 «폐지». 이름 칸이 상시 노출 .form-input 이 되어 마크업이 사라졌다
   (ADR 0457 A안 개정 · 시안 plan-name-default-editable). 되살리려면 그 결정부터 뒤집을 것 —
   프리뷰 타일 재도입 ❌ */

.product-modal-pause-fields-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

/* 셀프 정지 안내 — 비대칭 리드 노트 (관장 무제한 전제 + 관원 셀프 정지, 2026-07-06) */
.product-modal-pause-info {
  margin-top: 10px;
  padding: 12px 14px;
  background: var(--color-slate-50);
  /* 모달 내부 타일 = 10 (형제 .modal-data-card 정렬, 2026-08-04 UI 감사 A안) */
  border-radius: 10px;
}

.product-modal-pause-info-lead {
  margin: 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--color-slate-700);
}

.product-modal-pause-info-lead .em {
  font-weight: 700;
  color: var(--color-primary-700);
}

/* 관장 기본 기능 고정 줄 (2026-08-22 사용자 확정 A안 · 시안 plan-pause-owner-explain) —
   토글 상태와 무관하게 항상 표시. 리드(관원 축 13px slate-700)보다 한 톤 낮춰 부연으로. */
.product-modal-pause-info-owner {
  margin: 5px 0 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--color-slate-500);
}

@media (max-width: 768px) {
  .product-modal-pause-fields-grid {
    grid-template-columns: 1fr;
    gap: 10px;
  }

  /* 긴 폼 모달 모바일 한정 풀스크린 (모달 표준 예외 — members/detail #addPassModal 과 동일 패턴).
     데스크톱은 표준 .modal modal-md 유지. */
  #productModal .modal {
    /* 무대 = 지금 보이는 영역 (common.js §1.4 OverlayStage) — 이 패널은 `position:fixed` 라
       오버레이 박스를 벗어나므로 무대를 «직접» 진다. `inset:0` + `100dvh` 복귀 ❌ */
    position: fixed;
    /* `viewport-fit=cover` 뒤로 무대는 화면 «맨 위»에서 시작한다 — 헤더가 상태바·노치 밑으로
       들어가지 않도록 위 인셋 반영판(`--stage-top`/`--stage-h`)을 쓴다. 배경 딤은 `--vvt` 그대로
       (딤까지 내리면 노치 띠만 안 덮인 채 남는다). style.css §안전영역 토큰 ① (2026-09-14) */
    top: var(--stage-top, 0px);
    left: 0;
    right: 0;
    bottom: auto;
    max-width: 100%;
    max-height: 100%;
    height: var(--stage-h, 100vh);
    width: 100%;
    border-radius: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
  }

  #productModal .modal-body {
    flex: 1;
    overflow-y: auto;
    max-height: none;
    min-height: 0;
    -webkit-overflow-scrolling: touch;
  }

  /* 구 sticky .modal-footer 재정의는 modal--head-save 전환(§76.5 — 모바일에서 푸터 자체를 숨김)으로
     dead 라 제거 (2026-08-17 UI 감사 — members/detail 동일 정리의 공유 fragment 짝) */
}

/* 이용권 카드 헤더 — 모바일 컴팩트 (project-frontend-guide §디자인 톤).
   데스크톱 baseline(padding 14px 20px / icon 32px)은 유지, 모바일에서만 헤더 비중 축소.
   ~60px → ~42px (-30%). chip 12.5px / icon 28px(svg 18px) / padding 10px 20px 0 (아래 규칙의 실값과 한 벌 —
   구 「chip 10.5px / svg 14px / padding 8px 14px」 는 12px 바닥·20px 시작선 확정 이전 값이라 복원 ❌). */
@media (max-width: 768px) {
  /* 좌우 20 = 본문과 같은 시작선. divider 를 없앤 뒤로는 좌우가 갈리면 칩과 이름이 6px 어긋난 채
     한 면에 놓인다 — 구 14px 재도입 ❌. 컴팩트는 «세로»(10/0)와 chip·아이콘 치수가 담당. */
  .plan-card-header {
    padding: 10px 20px 0;
    gap: 8px;
  }
  /* gap 은 컴팩트 대상에서 **제외** — 「수정 ↔ 삭제」 오탭 완충이라 좁힐수록 위험해진다
     (2026-08-11 사용자 확정 A안, 시안 `plan-card-icon-gap`). 헤더 비중 축소는 padding·chip·아이콘
     치수(28px)가 담당하고, 이 8px 만 baseline 과 같은 값을 유지한다 — 구 2px 재도입 ❌.
     대가 = 액션 묶음이 6px 넓어짐(58→64px), 그래도 폰 카드 헤더 폭에 여유 충분(실측). */
  .plan-card-actions {
    gap: 8px;
  }
  .plan-card-actions .btn-icon {
    width: 28px;
    height: 28px;
    /* ::after 히트영역 오버레이 앵커 — 글로벌 `.btn-icon` 은 `position` 이 없다 */
    position: relative;
  }
  /* 히트영역만 44px 세로 확보 — 보이는 상자·간격·정렬은 불변 (2026-08-24 사용자 확정 A안,
     시안 icon-hit-area). 실측 실효 히트가 28×29 뿐이라 세로가 44 대비 15px 부족했다.
     담긴 헤더가 45px(위 8·아래 9)이라 세로는 -8 로 44 가 나온다.
     ⚠ 가로는 **바깥쪽만** 벌린다 — 위 gap 8px 은 2026-08-11 사용자 확정
     「수정 ↔ 삭제 오탭 완충」이라 ±4 로 안쪽을 먹으면 그 완충이 0 이 된다.
     수정 왼쪽(타입 칩까지 ~196px)·삭제 오른쪽(카드 안쪽 여백 14px)은 비어 있어 -4 가 안전.
     안쪽으로 확장 ❌ · `padding` 확대 구현 ❌(보이는 상자가 커진다 — ADR 0317 「보이는 알약」 규칙) */
  .plan-card-actions .btn-icon::after {
    content: '';
    position: absolute;
  }
  .plan-card-actions .btn-icon:first-child::after { inset: -8px 0 -8px -4px; }
  .plan-card-actions .btn-icon:last-child::after  { inset: -8px -4px -8px 0; }
  .plan-card-actions .btn-icon svg {
    width: 18px;
    height: 18px;
  }
  .plan-type-chip {
    font-size: 12.5px;
    padding: 2px 8px 2px 7px;
    gap: 6px;
  }
  /* dot 은 6px 유지 — §이용권 뱃지 「좌측 6px」 에 모바일 예외 없음. 구 5px 축소는 폰트
     13→12.5(-4%) 대비 -17% 로 비례도 아니었다 (2026-08-06 UI 감사). */
}

/* ========================================
   63. SUMMARY CARDS (요약 카드)
   ======================================== */


















/* ========================================
   64. PAYMENT PENDING LIST (결제 대기 목록)
   ======================================== */
.payment-pending-list {
  display: flex;
  flex-direction: column;
}

/* 큐 카드 — 낱장 카드 폐지 (2026-08-09 사용자 확정 B안): 대기 전체를 카드 하나에 담고
   행은 hairline 으로만 구분 — 완료 뷰 모바일 카드·이력 카드와 같은 「컨테이너 + 행」 문법. */
.payment-pending-card {
  background: var(--bg-card, #fff);
  border: 1px solid var(--border-hairline);
  border-radius: 14px;
  overflow: hidden;
}

.payment-pending-item {
  /* 데스크톱 한 줄 그리드 [관원 | 벨트 | 이용권 | 신청 시각 | 금액 | 액션] — 시각·금액 세로 열 정렬이
     같은 관원 다건의 입금 대조를 담당 (구 순번 배지 대체). 입금자 각주는 2행 1~3열 span.
     ⚠️ 열은 «카드»가 소유하고 행은 subgrid 로 물려받는다 (ADR 0613) — 행마다 제 격자를 두면
     각자 max-content 를 계산해 **열 머리와 행이 어긋난다**. 구 `grid-template-columns` 직접 선언 재도입 ❌.
     열 배치·모바일 morph 는 admin/payments/index.html page-local CSS 소유. */
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: center;
  gap: 3px 16px;
  padding: 8px 16px;
  border-bottom: 1px solid var(--border-hairline);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.payment-pending-item:last-child {
  border-bottom: none;
}

.member-info-compact {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 160px;
}





.member-name {
  font-weight: var(--font-medium);
  color: var(--text-primary);
}



/* 구 .payment-plan-name/-amount/-date 글로벌 정의 삭제 (2026-07-30 UI 감사) —
   유일 사용처 payments/index 가 page-local D.MONO 값으로 소유. SSOT 이원화 해소. */

.payment-pending-actions {
  display: flex;
  gap: 8px;
}

/* ========================================
   65. CUSTOM FORM SELECT (커스텀 셀렉트)
   ======================================== */
/* 입력 컨트롤 높이 표준 (frontend-guide §23)
   데스크톱 44px / 모바일 48px — .form-input · .custom-select-trigger 와 통일 */
.form-select,
select.form-select {
  appearance: none !important;
  -webkit-appearance: none !important;
  -moz-appearance: none !important;
  box-sizing: border-box;
  width: 100%;
  height: auto;
  min-height: 44px;
  padding: 11px 44px 11px 16px;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text-primary);
  background-color: white;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%23059669' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 18px;
  border: 1.5px solid var(--border-default);
  border-radius: var(--radius-lg);
  cursor: pointer;
  transition: all 0.2s ease;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}

/* FOUC 가드 — CustomSelect(common.js) 업그레이드 전, 네이티브 select 의 브라우저 기본 크롬이
   잠깐 보였다가 커스텀 트리거로 교체되는 깜빡임/점프 차단. JS 가 upgrade 후
   data-custom-select-initialized 를 부여하면 가드 해제(이후 inline display:none 으로 전환).
   data-custom-select="false"(브라우저 기본 노출 의도)는 제외. 높이 동일(44px)이라 레이아웃 시프트 없음. */
select.form-select:not([data-custom-select-initialized]):not([data-custom-select="false"]),
select.form-input:not([data-custom-select-initialized]):not([data-custom-select="false"]) {
  visibility: hidden;
}

/* IE/Edge 기본 화살표 숨김 */
.form-select::-ms-expand {
  display: none;
}

/* Firefox 포커스 링 보정 */
.form-select:-moz-focusring {
  color: transparent;
  text-shadow: 0 0 0 var(--text-primary);
}

@media (hover: hover) {
  .form-select:hover {
    border-color: var(--color-primary-300);
    background-color: #f0fdf9;
    box-shadow: 0 2px 8px rgba(5, 150, 105, 0.08);
  }
}

.form-select:focus {
  outline: none;
  border-color: var(--color-primary-500);
  background-color: white;
  box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.15), 0 2px 8px rgba(5, 150, 105, 0.1);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%23047857' stroke-width='2.5'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
}

.form-select option {
  padding: 12px 16px;
  line-height: 1.6;
  background: white;
  color: var(--text-primary);
}

.form-select option:checked {
  background: linear-gradient(0deg, var(--color-primary-200) 0%, var(--color-primary-200) 100%);
  background-color: var(--color-primary-200);
  color: var(--color-primary-800);
  font-weight: 600;
}

/* sm/lg 변형 — frontend-guide §23 입력 컨트롤 높이 표준 (변형 표) */
.form-select-sm {
  padding: 8px 40px 8px 12px;
  font-size: 13px;
  min-height: 36px;
  border-radius: var(--radius-md);
}

.form-select-lg {
  padding: 14px 48px 14px 18px;
  font-size: 15px;
  min-height: 50px;
}

/* Invalid 상태 */
.form-select.is-invalid {
  border-color: var(--status-error);
  background-color: var(--status-error-bg);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%23dc2626' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
}

.form-select.is-invalid:focus {
  border-color: var(--status-error);
  box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.15);
}

/* ========================================
   65-1. CUSTOM SELECT DROPDOWN (커스텀 셀렉트 드롭다운)
   ======================================== */

/* 래퍼 */
.custom-select-wrapper {
  position: relative;
  display: inline-block;
  width: 100%;
}

/* 트리거 버튼 — 입력 컨트롤 높이 표준 (frontend-guide §23)
   데스크톱 44px / 모바일 48px — .form-input · .form-select 와 통일 */
.custom-select-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  min-height: 44px;
  padding: 11px 16px;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text-primary);
  background: white;
  border: 1.5px solid var(--border-default);
  border-radius: var(--radius-lg);
  cursor: pointer;
  transition: all 0.2s ease;
  text-align: left;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}

@media (hover: hover) {
  .custom-select-trigger:hover {
    border-color: var(--color-primary-300);
    background-color: #f0fdf9;
    box-shadow: 0 2px 8px rgba(5, 150, 105, 0.08);
  }
}

/* 트리거 포커스 링 = `:focus-visible` (2026-08-11 사용자 확정 A안 — 구 일반 `:focus` supersede).
   트리거는 <button> 이라 탭하면 DOM 포커스를 계속 쥔다 — 일반 `:focus` 로 두면 값을 고르고
   드롭다운을 닫아도 emerald 링이 「선택됨」처럼 남는다(모바일 실사용 신고). hover 가드(§0)로는
   안 없어지는 나머지 절반. 「열려 있다」 신호는 아래 `.open` 규칙이 따로 소유하므로 잃는 것 없음.
   frontend-guide §27·§28. 일반 `:focus` 로 되돌리기 ❌ */
.custom-select-trigger:focus {
  outline: none;
}

.custom-select-trigger:focus-visible {
  outline: none;
  border-color: var(--color-primary-500);
  box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.15), 0 2px 8px rgba(5, 150, 105, 0.1);
}

.custom-select-wrapper.open .custom-select-trigger {
  border-color: var(--color-primary-500);
  box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.15);
}

.custom-select-value {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.custom-select-arrow {
  flex-shrink: 0;
  color: var(--color-primary-500);
  transition: transform 0.2s ease;
}

.custom-select-wrapper.open .custom-select-arrow {
  transform: rotate(180deg);
  color: var(--color-primary-600);
}

/* 드롭다운 */
.custom-select-dropdown {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: var(--z-dropdown);
  background: white;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 10px 15px -3px rgba(0, 0, 0, 0.08),
    0 20px 25px -5px rgba(0, 0, 0, 0.05);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px) scale(0.98);
  transition: all 0.2s ease-out;
  overflow: hidden;
}

.custom-select-wrapper.open .custom-select-dropdown {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
}

/* 검색 */
.custom-select-search {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border-light);
  background: var(--bg-secondary);
}

/* 크기 = 쓰는 쪽 CSS 소유 (2026-08-21 아이콘 프래그먼트 승격). 지우면 width:auto 로 아이콘 소실 ❌ */
.custom-select-search svg {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  color: var(--text-tertiary);
}

.custom-select-search input {
  flex: 1;
  border: none;
  background: transparent;
  font-size: 14px;
  color: var(--text-primary);
  outline: none;
}

.custom-select-search input::placeholder {
  color: var(--text-tertiary);
}

/* 옵션 목록 — 상한 440px (2026-08-28 사용자 확정 B안 · 시안 `select-dropdown-height`).
   구 240px 은 «화면에 공간이 남아도» 목록을 자르는 고정값이라, 벨트 셀렉트(옵션 18개·목록 814px)가
   1440×900 에서 5.6개까지만 보였다. 440 은 그 화면에서 10.4개 + 「키즈」 그룹 머리가 한눈에 들어오고
   노트북(900px) 화면 절반을 안 넘는 값 — **화면이 더 좁으면 JS 가 가용 공간까지 더 줄인다**
   (CustomSelect.open 의 뷰포트 클램프 · portal·인라인 두 경로 모두).
   옵션이 적은 셀렉트는 내용만큼만 커지므로 이 값과 무관하다.
   구 240 재도입 ❌ · 상한 없이 풀기 ❌(큰 모니터에서 목록이 화면을 거의 덮는다 — 미채택 C안). */
.custom-select-options {
  max-height: 440px;
  overflow-y: auto;
  padding: 6px;
}

/* 옵션 항목 */
.custom-select-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px;
  font-size: 14px;
  color: var(--text-primary);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all 0.15s ease;
}

.custom-select-option.highlighted {
  background: var(--color-primary-50);
  color: var(--color-primary-700);
}
@media (hover: hover) {
  .custom-select-option:hover {
    background: var(--color-primary-50);
    color: var(--color-primary-700);
  }
}

.custom-select-option.selected {
  background: var(--color-primary-100);
  color: var(--color-primary-700);
  font-weight: 600;
}

.custom-select-option.disabled {
  /* 잠금 = 면·글자·색만 (§잠긴 값 칸 「opacity 전 컨트롤 금지」 — 구 .65/.6 재도입 ❌).
     신호는 slate-400 글자 + not-allowed + 아래 :hover 중화 3중이 진다 */
  color: var(--text-tertiary);
  cursor: not-allowed;
}

@media (hover: hover) {
  .custom-select-option.disabled:hover {
    background: transparent;
  }
}

.custom-select-option-text {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 옵션 2줄 — data-desc: 이름 + 한 줄 설명 (이용권 종류 등). 이름은 option-text 어휘 그대로 */
.custom-select-option-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.custom-select-option-body .custom-select-option-text {
  flex: none;
}

.custom-select-option-desc {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-tertiary);
  line-height: 1.4;
  white-space: nowrap;
}

/* 2줄 옵션은 dot·체크를 첫 줄(이름)에 정렬 */
.custom-select-option:has(.custom-select-option-body) {
  align-items: flex-start;
}

.custom-select-option:has(.custom-select-option-body) .custom-select-check {
  margin-top: 2px;
}

/* 체크 아이콘 */
.custom-select-check {
  flex-shrink: 0;
  color: var(--color-primary-600);
  opacity: 0;
  transform: scale(0.8);
  transition: all 0.15s ease;
}

.custom-select-option.selected .custom-select-check {
  opacity: 1;
  transform: scale(1);
}

/* 그룹 */
.custom-select-group {
  margin-bottom: 4px;
}

.custom-select-group:last-child {
  margin-bottom: 0;
}

.custom-select-group-label {
  padding: 8px 12px 4px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.custom-select-group .custom-select-option {
  padding-left: 20px;
}

/* 빈 상태 */
.custom-select-empty {
  padding: 20px;
  text-align: center;
  font-size: 14px;
  color: var(--text-tertiary);
}

/* 크기 변형 - Small */
.custom-select-wrapper.sm {
  width: auto;
  display: inline-block;
}

.custom-select-wrapper.sm .custom-select-trigger {
  padding: 8px 14px;
  font-size: 13px;
  width: auto;
  height: 36px;
  min-height: 36px;
  display: inline-flex;
  white-space: nowrap;
  border-radius: 8px;
  gap: 6px;
  box-shadow: none;
  border-width: 1px;
}

@media (hover: hover) {
  .custom-select-wrapper.sm .custom-select-trigger:hover {
    border-color: var(--color-primary-500);
    color: var(--color-primary-600);
    background: var(--color-primary-50);
  }
}

.custom-select-wrapper.sm .custom-select-arrow {
  width: 14px;
  height: 14px;
  background: transparent;
  padding: 0;
  border-radius: 0;
}

.custom-select-wrapper.sm .custom-select-dropdown {
  min-width: 160px;
}

.custom-select-wrapper.sm .custom-select-option {
  padding: 8px 12px;
  font-size: 13px;
  border-radius: 6px;
}

/* 테이블 행용 컴팩트 변형 (member-table-row 내부) */


.member-table-row .custom-select-wrapper.open .custom-select-trigger {
  border-color: var(--color-primary-500);
  box-shadow: none;
}





.member-table-row .custom-select-wrapper.open .custom-select-dropdown {
  transform: translateY(0) scale(1);
}







.member-table-row .custom-select-option.selected {
  background: var(--color-primary-50);
  color: var(--color-primary-600);
}

/* 회원 등록 테이블 — 컴팩트 행 포커스: solid border 1개만, halo 제거 */


/* CustomSelect — 비활성(.disabled) 상태: 벨트 미선택 시 그랄 잠금.
   값·화살표 색을 직접 지정한다 — 구 opacity(.65/.4)는 §잠긴 값 칸 토큰(2026-08-17)이 없애기로 한
   파편이고, 반투명은 뒤 배경에 따라 세기가 흔들려 같은 잠금이 표면마다 다르게 읽혔다. */
.custom-select-wrapper.disabled .custom-select-trigger {
  background: var(--bg-secondary);
  color: var(--text-tertiary);
  border-color: var(--border-default);
  box-shadow: none;
  cursor: not-allowed;
  pointer-events: none;
}
.custom-select-wrapper.disabled .custom-select-arrow {
  color: var(--color-slate-300);
}
@media (hover: hover) {
  .custom-select-wrapper.disabled .custom-select-trigger:hover {
    border-color: var(--border-default);
    background: var(--color-slate-50);
    box-shadow: none;
  }
}

/* 색상 dot (CustomSelect) */
.custom-select-color-dot {
  display: inline-block;
  width: 28px;
  height: 10px;
  border-radius: 3px;
  flex-shrink: 0;
  box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}

.custom-select-option .custom-select-color-dot {
  margin-right: 6px;
}

.custom-select-trigger .custom-select-color-dot {
  margin-right: 6px;
}

/* 크기 변형 - Large */
.custom-select-wrapper.lg .custom-select-trigger {
  padding: 14px 18px;
  font-size: 15px;
}

.custom-select-wrapper.lg .custom-select-option {
  padding: 12px 14px;
  font-size: 15px;
}

/* 유효성 오류 상태 */
.custom-select-wrapper.is-invalid .custom-select-trigger {
  border-color: var(--status-error);
  background-color: var(--status-error-bg);
}

.custom-select-wrapper.is-invalid .custom-select-trigger:focus-visible,
.custom-select-wrapper.is-invalid.open .custom-select-trigger {
  border-color: var(--status-error);
  box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.15);
}

/* 스크롤바 스타일 */
.custom-select-options::-webkit-scrollbar {
  width: 6px;
}

.custom-select-options::-webkit-scrollbar-track {
  background: transparent;
}

.custom-select-options::-webkit-scrollbar-thumb {
  background: var(--border-default);
  border-radius: 3px;
}

@media (hover: hover) {
  .custom-select-options::-webkit-scrollbar-thumb:hover {
    background: var(--text-tertiary);
  }
}

/* === 은행 선택 모달 picker (select[data-bank-list] + BankPicker, common.js §18.4) ===
   결제·정산 폼의 은행 선택 전용. 구 인라인 드롭다운(bank-list 1열 리스트)은 폐지 (2026-07-03) —
   작은 텍스트 트리거에서 320×480px 패널이 카드 밖으로 돌출 + 아래 입력 행(계좌번호·예금주)을
   덮는 문제. 47개 옵션을 표준 모달(.modal modal-md) 안 로고 타일 그리드로 렌더
   (토스·카카오페이 계좌 등록 문법). 마크업은 BankPicker 가 JS 로 생성. */

/* 트리거 — ios row 우측 텍스트 (ios-inline-select-wrapper 인라인 트리거와 동일 톤,
   chevron-right 가 "모달 열림" 신호. 값 선택 시 로고 + 은행명). */
.bank-picker-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: none;
  padding: 6px 4px 6px 0;
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  transition: color 0.15s ease;
}

.bank-picker-trigger:focus-visible {
  color: var(--color-primary-600);
}
/* 히트영역 44px — 보이는 크기(28)는 그대로, 세로만 넓힌다 (히트박스 확대 idiom [[0317]]).
   두 전제 충족: ① 보이는 상자가 없다(background transparent · border none)
   ② 형제 `select.form-select` 는 모바일에서 미렌더라 이 줄에 단독이다.
   확장분은 부모 `.ios-item`(57px) 안 — 행 높이 불변. 모바일 한정(데스크톱은 마우스). */
@media (max-width: 768px) {
  .bank-picker-trigger {
    position: relative;
  }
  .bank-picker-trigger::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    height: 44px;
  }
}
@media (hover: hover) {
  .bank-picker-trigger:hover {
    color: var(--color-primary-600);
  }
}

.bank-picker-trigger.has-value {
  color: var(--color-slate-900);
  font-weight: 600;
}

/* 잠긴 은행 트리거 — 면이 없는 인라인 트리거라 글자·로고 색이 신호 (§잠긴 값 칸 토큰, opacity ❌) */
.bank-picker-trigger:disabled {
  pointer-events: none;
  color: var(--text-tertiary);
  cursor: not-allowed;
}
.bank-picker-trigger:disabled .bank-picker-trigger-logo {
  filter: grayscale(1);
}

.bank-picker-trigger-logo {
  width: 20px;
  height: 20px;
  border-radius: 5px;
  border: 1px solid var(--border-light);
  background: #fff;
  padding: 1px;
  object-fit: contain;
}

.bank-picker-trigger-arrow {
  color: var(--color-slate-400);
  flex-shrink: 0;
}

/* 모달 본문 — 검색 (§27: 자체 :focus-within border + ring 단일 indicator) */
.bank-picker-search {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 12px;
  margin-bottom: 14px;
  background: #fff;
  border: 1px solid var(--color-slate-200);
  border-radius: 10px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* `:focus-within` 유지 — 자식이 텍스트 입력뿐 (ADR 0616 · 위 `.fsheet-numfield` 와 같은 사유) */
.bank-picker-search:focus-within {
  border-color: var(--color-primary-500);
  box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.12);
}

.bank-picker-search svg {
  color: var(--color-slate-400);
  flex-shrink: 0;
}

.bank-picker-search input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font-family: inherit;
  font-size: 13px;
  color: var(--color-slate-900);
}

.bank-picker-search input::placeholder {
  color: var(--color-slate-400);
}

/* 섹션 (은행/증권사) + 타일 그리드 */
.bank-picker-section + .bank-picker-section {
  margin-top: 16px;
}

.bank-picker-section-label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-slate-500);
  margin-bottom: 8px;
}

.bank-picker-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}

.bank-picker-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 12px 4px 10px;
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  color: var(--color-slate-700);
  cursor: pointer;
  transition: border-color 0.12s ease, background 0.12s ease;
}

@media (hover: hover) {
  .bank-picker-tile:hover {
    border-color: var(--color-primary-200);
    background: var(--color-primary-50);
  }
}

/* 선택 = 폼 옵션 선택 어휘 (emerald ✅ — day-chip·mode-switcher 계열) */
.bank-picker-tile.selected {
  border-color: var(--color-primary-600);
  background: var(--color-primary-50);
  color: var(--color-primary-700);
  font-weight: 700;
}

.bank-picker-tile-logo {
  width: 30px;
  height: 30px;
  object-fit: contain;
}

.bank-picker-tile-name {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bank-picker-empty {
  padding: 24px 0 12px;
  text-align: center;
  font-size: 13px;
  color: var(--color-slate-500);
}

@media (max-width: 768px) {
  .bank-picker-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* 모바일 — Bottom Sheet 모드 (frontend-guide §13)
   .custom-select-wrapper.open 에서 dropdown 을 viewport 하단 시트로 전환 */
@media (max-width: 768px) {
  .custom-select-trigger {
    font-size: 16px;
    gap: 8px;
  }

  /* Backdrop */
  .custom-select-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.5);
    z-index: 1040;
    opacity: 0;
    transition: opacity 0.2s ease-out;
  }

  .custom-select-backdrop.show {
    opacity: 1;
  }

  /* Sheet 변환
     `.cs-portal-sheet` 는 dropdown 이 body 로 portal 됐을 때 (ancestor transform
     containing-block 회피용 — common.js CustomSelect.open 참조) sheet 스타일을 유지하기 위함. */
  .custom-select-wrapper.open .custom-select-dropdown,
  .custom-select-dropdown.cs-portal-sheet {
    position: fixed;
    top: auto;
    left: 0;
    right: 0;
    /* 무대 바닥에 앉는다 (common.js §1.4 OverlayStage) — 키보드가 떠 있는 채로 열리면
       `bottom:0` 은 화면 바닥이라 시트가 키보드 뒤에 깔린다. `bottom:0` 복귀 ❌.
       몫은 `--vvb` 하나 — `100vh` 는 iOS 에서 레이아웃 뷰포트보다 커서 산식이 틀어진다 */
    bottom: var(--vvb, 0px);
    /* 무대의 92% — 시트가 「보이는 영역」을 나눠 쓴다 (common.js §1.4 OverlayStage ·
       2026-09-07 사용자 확정 A안). 구 `92dvh` 는 키보드가 떠도 화면 전체의 92% 라
       시트 바닥이 키보드 뒤로 들어가는 값이었다. `92dvh` 복귀 ❌ */
    max-height: 92vh;
    max-height: calc(var(--vvh, 100vh) * 0.92);
    /* 시트 곡률 = 16 단일 (2026-08-15 사용자 확정 A안, 시안 sheet-corner-radius) —
       `.bottom-sheet` 와 이미 높이(92dvh)·모션(0.45s ease-out-quint)을 맞춘 짝인데 곡률만 22 로 갈려,
       필터 시트 위에 이 시트가 열리면 두 곡률이 나란히 보였다. 카드·모달 14 와의 계단도 완만해진다.
       22 재도입 ❌ (§0 「카드 radius 32px+ = AI 티」 축에 다가간다).
       **그림자는 통일하지 않는다** — 겹칠 때 위 시트가 더 떠 보이는 건 장식이 아니라 층 신호 */
    border-radius: 16px 16px 0 0;
    border: none;
    box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.15);
    z-index: 1050;
    transform: translateY(0);
    display: flex;
    flex-direction: column;
    animation: customSelectSheetUp 0.45s var(--ease-out-quint); /* 바텀시트(.bottom-sheet 0.45s) 어휘 정렬 (2026-07-30) */
    opacity: 1;
    visibility: visible;
  }

  @keyframes customSelectSheetUp {
    from { transform: translateY(100%); }
    to { transform: translateY(0); }
  }

  /* Sheet 핸들 */
  .custom-select-sheet-handle {
    width: 38px;
    height: 4px;
    background: var(--border-default);
    border-radius: 999px;
    margin: 11px auto 6px;
    flex-shrink: 0;
  }

  /* Sheet 헤더 (제목 + 닫기) */
  .custom-select-sheet-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 20px 14px;
    border-bottom: 1px solid var(--border-light);
    flex-shrink: 0;
  }

  .custom-select-sheet-header h4 {
    font-size: 16px;
    font-weight: 700;
    color: var(--text-primary);
    margin: 0;
  }

  .custom-select-sheet-close {
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 4px;
    line-height: 0;
  }

  /* 검색 — sheet 모드: 회색 둥근 컨테이너 안에 svg + input flex */
  .custom-select-wrapper.open .custom-select-search,
  .custom-select-dropdown.cs-portal-sheet .custom-select-search {
    padding: 12px 20px;
    background: white;
    border-bottom: 1px solid var(--border-light);
  }

  .custom-select-wrapper.open .custom-select-search > svg,
  .custom-select-dropdown.cs-portal-sheet .custom-select-search > svg {
    display: none; /* 둥근 컨테이너 안 svg 로 대체 — 아래 ::before */
  }

  .custom-select-wrapper.open .custom-select-search input,
  .custom-select-dropdown.cs-portal-sheet .custom-select-search input {
    width: 100%;
    background: var(--bg-tertiary);
    border-radius: 12px;
    padding: 11px 14px 11px 40px;
    font-size: 16px; /* iOS 줌 방지 */
    border: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'><path stroke-linecap='round' stroke-linejoin='round' d='M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z'/></svg>");
    background-repeat: no-repeat;
    background-position: 14px center;
  }

  .custom-select-wrapper.open .custom-select-search input::placeholder {
    color: var(--text-tertiary);
  }

  /* 옵션 목록 — flex 안에서 스크롤 가능 영역 차지.
     ⚠️ `.cs-portal-sheet` 를 함께 매칭하는 것이 짝 규칙 — 시트는 dropdown 을 **body 로 portal** 하므로
     `.custom-select-wrapper.open` 종속 셀렉터가 그 순간 통째로 미매치가 된다(`.cs-portal-sm` 마커가
     `.sm` 컴팩트 어휘를 되살리는 것과 같은 함정). 빠뜨리면 92dvh 시트인데 목록만 base
     `max-height`(440px)에 갇혀 **시트 아래가 비는데 옵션은 잘린다**(2026-08-28 실측: 시트 512px /
     화면 844px). wrapper 셀렉터만으로 되돌리기 ❌ */
  .custom-select-wrapper.open .custom-select-options,
  .custom-select-dropdown.cs-portal-sheet .custom-select-options {
    max-height: none;
    flex: 1;
    overflow-y: auto;
    /* 이 시트는 푸터가 없어 «목록»이 시트 바닥까지 내려온다 — `viewport-fit=cover` 뒤로 그 바닥은
       화면 바닥이라 마지막 옵션이 홈 인디케이터에 깔린다 — `.bottom-sheet--no-actions` 와 같은 축
       (2026-09-14 사용자 확정 A안) */
    padding: 8px 12px calc(24px + env(safe-area-inset-bottom, 0px));
  }

  .custom-select-wrapper.open .custom-select-option,
  .custom-select-dropdown.cs-portal-sheet .custom-select-option {
    padding: 16px 12px;
    font-size: 15px;
    border-radius: 10px;
  }

}

/* === 데스크톱 floating portal — cs-portal-sheet 의 데스크톱 변형 어휘
   common.js CustomSelect.open() 에서 overflow clip 컨테이너 안일 때 발동:
   ① 일괄 등록 표(.mbr-table-wrap)
   ② iOS Settings 카드(.ios-group-card — overflow:hidden 이 하단 row inline dropdown 을 자르는 문제).
   dropdown 을 body 로 portal + viewport 좌표(fixed) 로 trigger 옆 펼침.
   모달 내부 overflow clip 탈출용 (modal-body 의 single-scroll 박제는
   frontend-guide §7, 풀지 않음). top/left/width 는 JS inline style.
   모바일은 cs-portal-sheet 가 우선이라 이 어휘 미적용. */
.custom-select-dropdown.cs-portal-floating {
  position: fixed;
  z-index: 9999;
  /* base .custom-select-dropdown 의 left:0;right:0 (= wrapper 폭 stretch) 해제 —
     좁은 표 셀(.sm trigger 는 width:auto 라 내용폭)에서 dropdown 이 트리거 폭으로 눌려
     옵션 라벨이 …로 잘리던 문제 차단. min-width(JS=트리거폭) 보장 + 내용폭으로 확장. */
  right: auto;
  width: max-content;
  max-width: min(360px, calc(100vw - 16px));
  /* 펼침 트랜지션 — wrapper 의존 셀렉터가 portal 후 매치 안 되므로 자체 visible 박제 */
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
  /* base .custom-select-dropdown 의 transition:all 0.2s 해제 — JS 가 잡는 top/left/width/max-height
     가 애니메이션되면 dropdown 이 calc(100%)=뷰포트 바닥에서 슬라이드하며 잼·잘림 유발(특히 위로
     flip 시 바닥→상단 이동이 보임). 포털 좌표는 즉시 적용돼야 함. */
  transition: none;
}

/* portal 된 .sm 셀렉트(일괄 등록 표 셀 등) — body 로 옮겨지면 wrapper 종속 컴팩트 셀렉터가
   매치 안 돼 옵션이 기본 크기로 커진다. 마커(.cs-portal-sm)로 .sm 컴팩트 어휘를 재적용. */
.custom-select-dropdown.cs-portal-floating.cs-portal-sm .custom-select-options {
  padding: 4px;
}
.custom-select-dropdown.cs-portal-floating.cs-portal-sm .custom-select-option {
  padding: 7px 10px;
  font-size: 13px;
  border-radius: 8px;
}

/* ========================================
   66. RADIO CARDS (라디오 카드)
   ======================================== */
.radio-group {
  display: flex;
  background: var(--bg-tertiary);
  border-radius: 10px;
  padding: 4px;
  gap: 0;
}

.radio-card {
  flex: 1;
  min-width: 0;
  position: relative;
  cursor: pointer;
  display: flex;
}

.radio-card input {
  position: absolute;
  opacity: 0;
}

/* 키보드 진입 신호 — 라디오가 시각적으로 숨겨져 있어 :focus-visible 을 라벨 면이 대신 받는다.
   선례 `.unit-seg-opt input:focus-visible + span` 과 같은 어휘(§27 단일 ring) — 이게 없으면
   글로벌 아웃라인이 `[tabindex]:not(input)` 으로 input 을 제외해 탭 이동에 화면 변화가 0이다.
   `:focus-visible` = 키보드 탭에만 켜짐(탭한 뒤 링이 「선택됨」으로 남는 회귀 차단, §27). */
.radio-card input:focus-visible + .radio-card-content {
  box-shadow: inset 0 0 0 2px var(--color-primary-600);
}

/* 선택 카드 어휘 통일 (2026-06-05): 세그먼트 알약 → 테두리 카드형.
   .selection-card 와 같은 호버 리프트·checked 강조 토큰 공유. */
.radio-card-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 12px 8px;
  background: #fff;
  border: 1px solid var(--border-default);
  border-radius: 12px;
  text-align: center;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
  transition: transform .12s ease, border-color .2s, background .15s, box-shadow .2s;
  -webkit-tap-highlight-color: transparent;
}

@media (hover: hover) {
  .radio-card-content:hover {
    border-color: var(--color-primary-600);
    box-shadow: 0 8px 24px -10px rgba(5, 150, 105, .28);
    transform: translateY(-2px);
  }
}

.radio-card input:checked + .radio-card-content {
  border-color: var(--color-primary-600);
  background: var(--color-primary-50);
  box-shadow: inset 0 0 0 1px var(--color-primary-600);
}

@media (hover: hover) {
  .radio-card input:checked + .radio-card-content:hover {
    background: var(--color-primary-50);
    transform: none;
  }
}



.radio-card-label {
  font-weight: var(--font-semibold);
  font-size: 14px;
  color: var(--text-primary);
  margin-bottom: 2px;
}

.radio-card input:checked + .radio-card-content .radio-card-label {
  color: var(--color-primary-600);
}

/* 설명문이 「이 옵션이 무슨 뜻인가」를 지는 «그 자리의 유일한 신호» 라 slate-500
   (§slate-400 승격 기준 1행 — 흰 면 위 slate-400 12px = 2.85:1 AA 미달 → 4.76:1).
   라벨(14 slate-900)과의 위계는 크기·굵기가 이미 지므로 색을 올려도 위계 손해 0.
   소비처 5면(설정 키즈·도장 알림 2곳·문자 작성·이용권 폼·관원 상세) 공통 — page-local 사본 ❌.
   `--text-tertiary` 복귀 ❌ (400 유지 축 = placeholder·잠긴 값·행 카운터 셋으로 닫혀 있다) */
.radio-card-desc {
  font-size: 12px;
  color: var(--color-slate-500);
}

/* radio-card 컴팩트 변형 — `.radio-group.compact` 사용 시.
   기존 패딩 12px 8px → 8px 6px, 폰트 14/11 → 13/10.5.
   (구 `.modal-dmono` 동반 셀렉터는 2026-07-28 제거 — 클래스 사용처 0.) */
.radio-group.compact .radio-card-content {
  padding: 12px 8px;
  /* 10 = §디자인 톤 「모달 내부 타일 radius 10 단일」 — 구 11 은 어느 축(선택 카드 12·타일 10)에도
     없던 표류값 (2026-08-16 UI 감사). 베이스 12 는 선택 카드 어휘라 대상 아님 */
  border-radius: 10px;
}
.radio-group.compact .radio-card-label {
  font-size: 13px;
}
.radio-group.compact .radio-card-desc {
  font-size: 12px;
}
.radio-group.compact {
  padding: 4px;
  gap: 6px;
}

/* 모바일: 4-in-row 는 카드 폭이 좁아 desc 줄바꿈이 카드마다 달라짐(높이 들쭉날쭉)
   → 2×2 그리드. grid-auto-rows 1fr 로 행 간 높이도 통일. */
@media (max-width: 768px) {
  .radio-group {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    grid-auto-rows: 1fr;
    gap: 6px;
  }
}

/* ========================================
   66-B. 메시지 선택지 (`.msg-method`) — 「누구에게」(4택 · ADR 0730)·「보내는 방법」(2택)
   ========================================
   2026-09-08 ADR 0654 로 «페이지 로컬 → 글로벌» 승격. 소비처가 둘이 됐다:
   `admin/messages/index`(보내는 방법 카드 + 행 모달의 「누구에게」) ·
   `fragments/message-compose-modal`(문자 보내기 모달의 「누구에게」 — «다른 페이지»에서 로드된다).
   ⚠️ 승격은 «자리»만 바꾼 것이고 스코프는 그대로 `.msg-method` 안뿐 —
   글로벌 `.radio-group` 트레이 어휘를 쓰는 다른 화면은 여전히 불변이다.
   ⚠️ 페이지 로컬 사본을 다시 만들지 ❌ (사본이 갈림의 뿌리 — 그래서 승격했다).
   여백은 소비처가 정한다: 카드 안이면 기본값, 모달 안이면 `padding: 0` 으로 덮는다. */

/* 「보내는 방법」·「누구에게」 라디오 = 카드 위 «면 1겹» (ADR 0626 ①②③ · 2026-09-06 사용자 확정 · 시안 `msg-choice-unify`). */
/* 좌우 18 = 같은 카드 형제 `.ios-item` 축. 구 16 은 트레이 padding 4 와 겹쳐 타일 모서리가 «20px» 이었고
   바로 아래 토글 행 텍스트(18px)와 2px 어긋나 있었다 (2026-09-06 실측). 16 되돌리기 ❌ */
.msg-method { padding: 14px 18px 10px; }
/* ① 회색 트레이(slate-100 면 + padding 4) 제거 — 흰 카드 안에 색 면을 하나 더 만들던 구 세그먼트 알약 시절의 잔재다.
   남은 역할이 6px 홈뿐이라 gap 8 이 대신한다. 트레이 재도입 ❌ (카드 → 트레이 → 타일 = 면 3겹) */
.msg-method .radio-group.compact { background: none; padding: 0; gap: 8px; }
/* ① resting 그림자 제거 — §디자인 톤 「카드 box-shadow 제거」.
   ⚠ 특이도 주의: 이 선택자는 **(0,2,0) 이어야** 한다 — 글로벌 checked(`.radio-card input:checked + .radio-card-content`
   = 0,2,1)보다 약해야 선택 타일의 inset emerald 링이 산다. 클래스를 하나 더 얹어 (0,3,0) 으로 올리면
   링이 조용히 죽는다(선택 신호가 틴트 면 하나로 줄어듦). 아래 정렬 규칙과 «합치지 ❌» */
.msg-method .radio-card-content { box-shadow: none; }
/* hover lift 는 글로벌 어휘라 되살린다(§28 hover 가드 안). `:not(:checked)` = 선택된 타일은 글로벌 링 유지 — 구 동작과 동일 */
@media (hover: hover) {
  .msg-method .radio-card input:not(:checked) + .radio-card-content:hover {
    box-shadow: 0 8px 24px -10px rgba(5, 150, 105, .28);
  }
}
/* ② 왼쪽 정렬 — 이 타일의 desc 는 칩 라벨이 아니라 «두세 줄짜리 완성 문장»이고, 바로 아래 형제
   (`.ios-item` 토글 행 · 카드 밖 `.ios-group-hint`)가 전부 왼쪽이라 한 카드 안에 정렬축이 둘이었다.
   padding 은 글로벌 `.radio-group.compact .radio-card-content`(0,3,0 · 12px 8px)를 이겨야 해 여기만 한 단계 올린다.
   가운데 정렬 되돌리기 ❌ */
.msg-method .radio-group .radio-card-content {
  align-items: flex-start; justify-content: flex-start; text-align: left; padding: 12px;
}
/* ③ 값(원·건) = 「마크·라벨·값」 «첫 줄» 괄호 슬롯 (2026-09-07 사용자 확정 ①·㉯·㉠ ·
   시안 `msg-method-labelrow`·`msg-method-sms-price-tail`·`msg-scope-value-grammar`).
   ADR 0626 ③ 「셋째 줄 고정」을 supersede — 그때 ③-B(값을 라벨 줄로)를 배제한 근거는
   「19원 · 45자 넘으면 39원」이 길어 좁은 칸에서 라벨을 압박한다는 것이었고, 그 «조건절»을
   카드 밖 각주로 내린 뒤에야 이 자리가 성립한다. 조건절을 값 옆에 되돌리면 0626 이 실측한
   라벨 압박이 그대로 재발한다 ❌. 구 셋째 줄(`margin-top:5px`)·`small` 슬롯 재도입 ❌.
   §금액 값 슬롯 = slate + 800 (emerald ❌ — 강조는 색이 아니라 무게).
   괄호는 «글자»로 적는다 — `::before/::after` content 로 그리면 복사·낭독에서 값만 떨어져 나온다.
   두 그룹(보내는 방법·누구에게)이 «같은» 괄호 문법을 쓴다 — 한 화면에 값 문법이 둘이 되면
   0626 ③ 이 없애려던 상태로 되돌아간다. 한쪽만 셋째 줄로 되돌리기 ❌ */
.msg-opt-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  width: 100%; margin-bottom: 3px; }
.msg-opt-head .radio-card-label { margin-bottom: 0; }
.msg-opt-val { font-size: 12px; font-weight: 800;
  color: var(--color-slate-500); font-variant-numeric: tabular-nums; }

/* ④ 「누구에게」가 **네 값**이 된 뒤의 조판 = 2×2 (ADR 0730 · 2026-09-14 사용자 확정 A안 ·
   시안 `msg-scope-four-values`). 모달은 480px 고정(`.modal-md`)이라 한 줄에 넷을 세우면 카드당
   ~104px — 「보호자에게만」이 이름부터 줄바꿈되고, 잘라내야 하는 설명이 하필 「우선」과 「에게만」의
   차이를 지는 문장이다. 2×2 면 카드당 ~208px 로 지금 문장을 **한 글자도 안 줄이고** 세운다.
   ⚠ 새 어휘가 아니다 — 바로 위 `@media (max-width:768px)` 가 라디오를 이미 2×2 로 세운다
   (「4-in-row 는 카드 폭이 좁아…」). 이 클래스는 그 모양을 **데스크톱까지** 끌어올린 것이라
   폰↔PC 가 같은 그림이 된다. 한 줄 4개로 되돌리기 ❌
   ⚠ 형제 「보내는 방법」(2택)에는 안 붙인다 — 2택은 한 줄이 맞고, 붙여도 repeat(2) 라 같은 그림이지만
   «왜 붙었나»가 안 읽힌다. 붙이는 자리는 4택 둘뿐(설정 모달 · 문자 보내기 모달). */
.msg-method .radio-group.compact.is-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: 1fr;
}

/* 「보호자에게만」 경고 — 고르는 그 자리에서 «조용히 빠질 인원»을 숫자로 말한다 (ADR 0730).
   이 사람들은 건별 행이 안 생겨(ADR 0697) 발송 내역·대기함 어디에도 안 뜬다. 그래서 이 한 자리가
   그 사실이 드러나는 **유일한 창구**다 — 지우지 ❌.
   §디자인 톤 — 경고라고 노란 면을 깔지 ❌(솔리드 색 면·채움 배지 ❌). 위계는 색이 아니라
   테두리 1겹 + 숫자의 무게다. 형제 `.form-hint`(각주)와 같은 좌우 축(16px)에 선다. */
/* 아래 10 = 위와 같은 값 — 뒤따르는 형제 `.form-hint` 의 `margin-top: 4px` 와 **겹쳐** max(10,4)=10 이 된다
   (인접 형제 세로 마진은 합산이 아니라 겹친다 — 6 으로 두면 실렌더 간격이 6px 이라 위아래가 안 맞는다,
   2026-09-14 실측 교정). 0 으로 두면 아래 간격만 4px 이라 상시 각주가 조건부 경고에 붙어
   «경고의 부연»처럼 읽히고, 같은 화면이 고른 값에 따라 다른 묶음으로 보인다.
   ⚠ 모달 오버라이드(`admin/messages/index` `#msgConfigModal .msg-scope-warn`)는 좌우만 건드리므로
   이 값을 바꿔도 그쪽은 그대로 따라온다 — 한쪽만 고치지 ❌ */
.msg-scope-warn {
  margin: 10px 16px 10px;
  padding: 10px 12px;
  border: 1px solid var(--color-slate-300);
  border-radius: 10px;
  background: var(--bg-primary);
}
/* ⚠ 아이콘을 붙이지 ❌ — icons.html 에 정보/경고 글리프가 없고, 그 자리를 위해 장식 SVG 를 새로 들이면
   §디자인 톤 「장식 제거」에 어긋난다. 이 블록의 위계는 색도 아이콘도 아닌 **테두리 1겹 + 숫자의 무게**다. */
.msg-scope-warn p {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-secondary);
}
.msg-scope-warn b { color: var(--text-primary); font-weight: 700; }

/* ── 요일 선택 칩 — 글로벌 단일 컴포넌트 (2026-07-27 UI 감사 사용자 확정 풀 통합) ──
   구 3벌 폐지: plan-form-modal .day-checkbox(28px 원형·솔리드) · quickstart .qs-mm-day-chip(솔리드) ·
   members/form .mqr-day-chip(틴트) + system plans .pm-day 도 흡수. 활성 = 폼 옵션 emerald 틴트 어휘
   (§디자인 톤 — 선택 칩 솔리드 채움 ❌). JS 값 읽기는 input 기능 클래스(.mqr-day 등)·컨테이너 id 가 담당 — 불변.
   2026-08-06 복원: 커밋 2cc412bc dead-CSS 정리가 plan-wizard 폐기 블록 사이에 낀 이 살아있는
   블록(사용처 5템플릿)까지 함께 삭제 — 생 체크박스 노출 회귀. 재정리 시 이 블록은 live. */
.day-chip-group {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}

/* 체크박스 그룹 미선택 신호 — 그룹 red 외곽(box-shadow = 레이아웃 시프트 0) + 인라인 .form-error + 범용 Toast
   (2026-07-30 사용자 확정 — 필드/그룹별 인라인 구체 문구 + Toast 는 범용 1회) */
.day-chip-group.is-invalid,
.radio-group.is-invalid,
.sc-terms-checklist.is-invalid,
.jn-consent.is-invalid,
.jn-exp-choices.is-invalid,
.q-choice-rows.is-invalid,
.lx-rows.is-invalid,
.qsheet-choices.is-invalid,
.card-issuer-grid.is-invalid {
  /* padding 추가 ❌ — invalid 시점에만 생기는 padding 은 레이아웃 시프트 (box-shadow 외곽선 취지 위배).
     radio-group 확장 = 라디오 그룹도 같은 그룹 invalid 어휘 (2026-07-30 확정 모델 — 이용권 등록 모달 종류 선택).
     sc-terms-checklist(약관 동의) 확장 = 2026-07-30 사용자 확정 — page-local 재구현 흡수.
     lx-rows(지각 예외 요일 행 표) 확장 = 2026-08-11 — 요일 행 표도 같은 그룹 invalid 어휘 (ADR 0190).
     qsheet-choices(관원 PWA 설문 선택지) 확장 = 2026-08-23 UI 감사 — page-local 재구현(링 2px·틴트 면 없음)
     흡수. 같은 설문 기능의 관장 쪽 짝 `.q-choice-rows` 와 신호가 갈려 있었다.
     card-issuer-grid(구독 카드사 선택) 확장 = 2026-08-30 UI 감사 — page-local 재구현(shake 만 + 타일
     11개 각각 red border) 흡수. 그룹 링·틴트 면이 없어 같은 「그룹 미선택」이 약관·요일 칩과 다른
     세기·다른 형태로 보였다. page-local 사본 재도입 ❌
     jn-exp-choices(가입 신청 「주짓수가 처음이세요?」 칩 2열) 확장 = 2026-09-17 — 같은 폼의 .jn-consent 선례. */
  box-shadow: 0 0 0 1px var(--status-error);
  background: var(--status-error-bg);
  border-radius: 10px;
  /* 평소 테두리를 가진 그룹(약관 박스 1px slate-200)이 링과 겹쳐 선 두 겹이 되는 것 차단 —
     테두리 없는 칩/라디오 그룹엔 무동작이라 어휘가 한 곳으로 수렴한다. */
  border-color: transparent;
  animation: shakeInvalid 0.4s ease;
}
.day-chip {
  position: relative;
  cursor: pointer;
  user-select: none;
  margin: 0;
}
/* display:none 은 키보드 포커스 제거 — 시각만 숨기고 탭 도달 유지 (frontend-guide §4) */
.day-chip input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.day-chip span {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 40px;
  font-size: 13px;
  font-weight: 600;
  background: #fff;
  border: 1px solid var(--border-default);
  border-radius: 8px;
  color: var(--text-secondary);
  transition: background-color 0.15s ease,
              border-color 0.15s ease,
              color 0.15s ease;
}
@media (hover: hover) {
  .day-chip:hover span {
    border-color: var(--color-primary-400);
    color: var(--color-primary-600);
  }
}
/* 선택 = emerald 틴트 + 테두리 + 800 weight — 2026-08-11 사용자 확정(시안 day-chip-dot A안,
   구 「위계 2단(800 + dot)」 2026-08-06 확정 supersede). 좌측 dot 은 `left: 9px` 절대배치인데
   칩 폭이 modal-md 기준 ≈59px·글자는 중앙(≈29px)이라 **글자와 9px 떨어진 낱개 문장부호**로 읽혔다
   (7칩 중 일부만 켜지면 점이 좌측에 흩어져 정렬 리듬까지 깨짐).
   색만으로 정보 전달 ❌ 는 **font-weight 600 → 800** 비색상 신호가 그대로 충족한다.
   ❌ 좌측·코너·하단 dot 재도입 (검토·기각 = ADR 0188) */
.day-chip input:checked + span {
  background: var(--color-primary-50);
  border-color: var(--color-primary-600);
  color: var(--color-primary-700);
  font-weight: 800;
}

/* ========================================
   67. MODAL OVERLAY (모달) - Premium Design
   ======================================== */
/* 무대 = 지금 «보이는» 영역 (common.js §1.4 OverlayStage · 2026-09-07 사용자 확정 A안 ·
   시안 `msg-compose-mobile-keyboard`·`modal-keyboard-scope`).
   구 `top:0 … bottom:0` supersede — iOS 는 키보드가 떠도 레이아웃 뷰포트를 안 줄여 `inset:0` 이
   계속 화면 전체(844px)를 뜻했고, 그래서 **입력이 있는 모달 전부**가 확정 푸터를 키보드 밑
   (실측 y 507~690 · 키보드 상단 508)에 두고 있었다. 무대를 갈아 끼우면 가운데 정렬·max-height 가
   전부 「보이는 영역」 기준으로 다시 계산돼 **모달을 하나도 안 고치고** 20개가 같이 낫는다.
   ⚠️ 폴백은 `100vh`(= 현행 기하) — `100dvh` 로 쓰지 ❌ (미지원 브라우저에서 툴바만큼 줄어 기하가 갈린다).
   `inset:0`·`bottom:0` 복귀 ❌ */
.modal-overlay {
  position: fixed;
  top: var(--vvt, 0px);
  left: 0;
  right: 0;
  bottom: auto;
  height: var(--vvh, 100vh);
  background: rgba(15, 23, 42, 0.6);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: var(--z-modal);
  padding: 20px;
  opacity: 0;
  visibility: hidden;
  /* visibility 를 전환에서 분리 (2026-08-02 UI 감사) — 구 `transition: all` 은 visibility 까지 전환해
     `.show` 직후 150ms 동안 computed visibility 가 hidden 으로 남았고, 그 사이 동기 실행되는
     `Modal.focusInto` 의 `.focus()` 가 통째로 무시돼 **모든 모달에서 포커스 진입이 죽어 있었다**
     (모달 표준 §포커스 진입·복귀 박제 위반). 닫을 때만 페이드가 끝난 뒤 숨기도록 0.3s 지연. */
  transition: opacity 0.3s var(--ease-smooth), visibility 0s linear 0.3s;
}

.modal-overlay.show {
  display: flex;
  opacity: 1;
  visibility: visible;
  /* 열 때는 즉시 visible — 지연 0 (위 주석) */
  transition: opacity 0.3s var(--ease-smooth), visibility 0s;
}

/* 폰 모달 바깥 여백 = 한쪽 16px (2026-09-10 사용자 확정 A안 · 시안 `mobile-modal-gutter`) —
   폰 화면 본문(`.page-container` 좌우 16)과 모달 테두리가 같은 세로선에 선다.
   위 기본 `padding: 20px` 과 특이도가 같아 «반드시 그 뒤»에 둔다(앞에 두면 소스 순서로 진다).
   §30 의 구 `.modal { margin: 16px }` 겹침은 같은 날 삭제 — 여백 소유자는 이 규칙 하나.
   풀스크린 모달(약관 `#termsModal` 등 ID 특이도)은 자기 `padding: 0` 이 그대로 이긴다 */
@media (max-width: 768px) {
  .modal-overlay { padding: 16px; }
}

/* D.MONO Modal Container (inside overlay) — §15 와 동일 톤 */
.modal-overlay .modal {
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 14px;
  box-shadow: 0 20px 60px -20px rgba(15, 23, 42, 0.25);
  width: 100%;
  /* 무대의 90% — 오버레이가 「보이는 영역」이라 화면 전체(dvh)가 아니라 그 높이를 나눠 쓴다
     (common.js §1.4 OverlayStage · 2026-09-07 사용자 확정 A안). 구 `90dvh` 는 키보드가 떠도
     화면 전체의 90% 라 확정 푸터를 키보드 밑에 두는 값이었다. `90dvh` 복귀 ❌ */
  max-height: 90vh;
  max-height: calc(var(--vvh, 100vh) * 0.9);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transform: scale(0.93) translateY(24px);
  /* 스프링 팝인 (2026-07-16 모션 시스템) — overshoot bezier 로 열림 순간 살짝 튐 */
  transition: transform 0.42s var(--ease-spring);
  position: relative;
}

.modal-overlay.show .modal {
  transform: scale(1) translateY(0);
}

.modal-overlay .modal.modal-sm { max-width: 380px; }
.modal-overlay .modal.modal-md { max-width: 480px; }



.modal-overlay .modal-header {
  padding: 0 22px;
  height: 56px;
  background: #fff;
  border-bottom: 1px solid var(--border-hairline);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-shrink: 0;
  position: relative;
}

/* 2026-05-26: .modal-overlay 의 ::before 액센트(상단 hairline + 좌측 막대) 폐지 —
   project-frontend-guide §「Carded surface — color rail 일반 금지」. */

.modal-overlay .modal-title {
  font-size: 15px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--color-slate-900);
  margin: 0;
}

.modal-overlay .modal-close {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-slate-400);
  background: transparent;
  border: none;
  cursor: pointer;
  transition: all 0.18s ease;
  flex-shrink: 0;
}

@media (hover: hover) {
  .modal-overlay .modal-close:hover {
    background: var(--color-slate-100);
    color: var(--color-slate-700);
  }
}

.modal-overlay .modal-body {
  padding: 20px 22px;
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}

/* 전폭 split (2026-07-30 C안) — 위 .modal-footer 와 동일 규칙. 우측 정렬·56px 고정·min-width 88px supersede. */
.modal-overlay .modal-footer {
  padding: 16px 22px 20px;
  background: #fff;
  border-top: 1px solid var(--border-hairline);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}

.modal-overlay .modal-footer .btn {
  flex: 1;
  min-width: 0;
}

/* 폰 모달 안쪽 좌우 여백 = 16px — 제목줄·본문·버튼줄 셋이 한 선 (2026-09-10 사용자 확정 「폰 모달 전부 통일」
   · 시안 `modal-body-side-padding`). 폰 화면 카드 본문(`.card-body` 16, frontend-guide §24)과 같은 값이라
   모달이 뜨든 안 뜨든 글자 시작선 여백이 하나다. 데스크톱 22 는 불변 — 값이 폭에 따라 갈리는 것은 인지된 대가.
   위 세 규칙(0,2,0)과 특이도가 같아 «반드시 그 뒤»에 둔다. 좌우만 덮는다 — 위아래(20/16·20)와
   모바일 safe-area 푸터(§「모바일은 safe-area 만」)는 그대로. 한 모달만 22 로 되돌리기 ❌(시작선이 둘이 된다) */
@media (max-width: 768px) {
  .modal-overlay .modal-header,
  .modal-overlay .modal-body,
  .modal-overlay .modal-footer {
    padding-left: 16px;
    padding-right: 16px;
  }
}

/* 툴바 변형은 위 전폭 균등을 되돌린다 — 같은 특이성(0,3,0)이라 선언 순서상 여기(뒤)에 있어야 이긴다.
   §2900 의 변형 선언과 짝 (분리돼 있는 이유 = 순서 의존, 합치면 다시 눌린다) */
.modal-overlay .modal-footer--toolbar .btn {
  flex: 0 0 auto;
  min-width: 88px;
}

/* 파괴+취소+확정 3버튼 푸터 — 파괴는 좌측 분리 (2026-08-03 사용자 확정).
   전폭 split(취소+확정 쌍)은 그대로 두고, 파괴 액션만 margin-right:auto 로 좌측 compact —
   확정과 같은 폭·인접 배치(오탭 사거리)를 피한다. 사용처 = late.html 수업 모달 ·
   members/detail 지각 예외 모달. 위 전폭 규칙(0,3,0) 뒤 선언 의무 — 순서 의존, 앞으로 옮기면 눌린다. */
.modal-footer .btn-destructive-left,
.modal-overlay .modal-footer .btn-destructive-left {
  flex: 0 0 auto;
  min-width: 0;
  margin-right: auto;
  padding-left: 16px;
  padding-right: 16px;
}

/* .modal-dmono / .modal-compact — 2026-07-28 제거 (사용처 0, 규칙 전부 dead).
   기본 .modal 톤이 D.MONO 를 흡수해 변형 자체가 불필요. 신규 사용 ❌ (project-frontend-guide §모달 표준). */

/* Mobile: constrain wide modals to viewport and respect iOS safe-area.
   modal-sm 포함 — 380px 고정 폭이 sub-380px 화면에서 오버플로 (member dashboard·members/detail 의 modal-sm 모두 보호). */
@media (max-width: 768px) {
  .modal-sm,
.modal-overlay .modal.modal-sm,
.modal-md,
.modal-overlay .modal.modal-md {
    max-width: calc(100vw - 32px);
  }

  /* 모바일은 safe-area 만 얹는다 — 여백 자체는 데스크톱과 같은 16/22/20 (ADR 0107 액션 푸터 어휘).
     구 규칙은 height 56px 고정을 전제로 padding 을 8px 로 줄였는데, 그 height 가 폐지되면서
     같은 확인 푸터가 데스크톱 ≈80px / 모바일 ≈60px 로 갈렸다. 「표면마다 다른 확인 푸터」를
     없애려던 결정이 브레이크포인트 축으로 되살아난 것이라 오버라이드를 safe-area 로만 좁힌다. */
  .modal-footer,
  .modal-overlay .modal-footer {
    padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  }
}

/* 폰(≤480px) 모달 푸터 버튼 = 좌우 패딩 회수 (22 → 12px). 2026-08-12 사용자 확정 C안
   (시안 static/mockups/modal-confirm-label-fit.html).
   푸터 버튼은 `flex:1` 이라 **박스 폭은 화면이 정하고 라벨은 고정 길이**인데, `.btn` 이
   `overflow:hidden` + `text-overflow:clip` 이라 넘쳐도 말줄임 없이 조용히 잘린다 —
   좌우 여백이 0이 된 뒤엔 글자 획이 테두리를 파고든다(실폰트 실측 3건, 라벨 축약과 함께 수정).
   패딩만 줄이면 박스 크기는 그대로고 글자가 쓸 수 있는 안쪽 폭만 20px 늘어 앞으로 라벨이
   한두 글자 길어져도 다시 터지지 않는다. 라벨 축약 단독으로는 320px 이 1~13px 모자랐다.
   상한 480px = 폰 한정(§액션 행 폭·높이 「폰 전용 규칙에 상한 의무」) — 데스크톱 22px 불변.
   `.btn-destructive-left` 는 제외 — 자연 폭(flex:0 0 auto)이라 라벨이 잘릴 일이 없고,
   좌측 compact 분리 padding 16px 은 ADR 0107 이 박제한 값이다. */
@media (max-width: 480px) {
  .modal-footer .btn:not(.btn-destructive-left),
  .modal-overlay .modal-footer .btn:not(.btn-destructive-left) {
    padding-left: 12px;
    padding-right: 12px;
  }
}

/* 폰(≤480px) 툴바 푸터 = 2행 위계 — primary 전폭(위) + 나머지 균등(아래). 2026-08-11 사용자 확정 A안
   (시안 static/mockups/invite-modal-footer.html).
   375px 실측: 3버튼 자연 폭 283 + gap 20 = 303 > 푸터 안쪽 297 → 6px 이 모자라 주 액션
   「링크 복사」만 둘째 줄 우측에 홀로 떨어졌다(푸터 81→135px). 접힌 결과가 우연이라 사고로 읽힌다.
   접히는 것을 막는 대신 「접히는 모양」을 위계로 규정한다 — 미채택 대안 2종:
   ① 3등분 균등(폭 74px)은 라벨이 잘린다(위 §modal-footer--toolbar 주석의 2026-07-30 실측)
   ② 모바일만 아이콘 제거는 같은 버튼의 어휘가 화면 폭으로 갈리고 360px 여유가 3px 뿐.
   상한 480px = 폰 한정 (§액션 행 폭·높이 「폰 전용 규칙에 상한 의무」) — 태블릿·데스크톱은 한 줄 그대로.
   primary 는 DOM 마지막이라 order:-1 로 첫 줄에 올린다(마크업 순서 = 데스크톱 시각 순서 유지). */
@media (max-width: 480px) {
  .modal-footer--toolbar .btn,
  .modal-overlay .modal-footer--toolbar .btn {
    flex: 1 1 0;
    min-width: 0;
  }
  .modal-footer--toolbar .btn-primary,
  .modal-overlay .modal-footer--toolbar .btn-primary {
    flex: 1 1 100%;
    order: -1;
  }
}

/* ========================================
   68. EMPTY STATE — 통합되어 R20 으로 이전 (Soft Hero)
   ======================================== */

/* ========================================
   69. DATA TABLE — 2026-07-29 삭제: 사용처 0 죽은 CSS (§10 사본과 함께 제거, 표는 글로벌 .table 단일 어휘)
   ======================================== */

/* ========================================
   71. BUTTON ICON
   ======================================== */
.btn-icon {
  width: 32px;
  height: 32px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 6px;
  color: var(--text-tertiary);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all var(--transition-fast);
}

@media (hover: hover) {
  .btn-icon:hover {
    background: var(--bg-secondary);
    color: var(--text-primary);
  }
}

/* ========================================
   72. BUTTON SIZES — 2026-08-16 삭제: §Sizes(Refined proportions) 로 병합
   이 블록이 앞의 `.btn-sm` 을 같은 특이도로 덮어 **실제 값과 문서가 6개월간 어긋나 있었다**
   (문서·주석은 13px/34px, 실제는 14px/35px). 값은 §Sizes 에 그대로 살아 있다 — 재도입 ❌
   ======================================== */


/* ========================================
   72-1. REJECT REASON OPTIONS
   ======================================== */











/* ========================================
   72-2. LEVEL SETTINGS
   ======================================== */



















/* ========================================
   72-3. SUBSCRIPTION INFO
   ======================================== */




























/* ========================================
   73. ENHANCED BACKGROUND (배경 개선)
   ======================================== */
/* body 배경은 §1 본 규칙(var(--bg-secondary) slate-50) 유지.
   그라데이션은 .main-content 가 단독으로 책임진다. 짧은 페이지에서 main-content 가 collapse 되면
   viewport 잔여 영역은 body→canvas propagate 된 slate-50 으로 깔끔하게 마감 (2026-05-18). */

.main-wrapper {
  background: transparent;
}

/* Subtle pattern overlay */
body::before {
  content: '';
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-image: radial-gradient(circle at 1px 1px, rgba(0, 0, 0, 0.02) 1px, transparent 0);
  background-size: 24px 24px;
  pointer-events: none;
  z-index: -1;
}

/* ========================================
   74. BACK BUTTON
   ======================================== */





/* 75. ANIMATED BACKGROUND (Dark theme용) = 2026-08-05 삭제 — .animated-bg 본체와 함께 사용처 0.
   auth 배경은 §Auth Common 의 .login-blob 이 담당한다. 재도입 ❌ */

/* ========================================
   76. SLUG PREVIEW COMPONENT
   ======================================== */




















/* ========================================
   76.5 모바일 풀스크린 편집 모달 = 상단 헤더 「취소 · 제목 · 저장」
   (2026-08-16 사용자 확정 B안, 시안 mobile-save-placement)
   화면을 다 덮는 편집 모달이 하단 저장이라 상단 저장인 페이지 편집(관원 등록 헤더 인라인)과
   갈려 보이던 자리 — 모바일(≤768px)에서만 mobile-header-edit 문법(취소 500 좌 · 제목 중앙 ·
   저장 800 emerald 우)으로 morph 한다. 데스크톱 센터 팝업은 표준(.modal-footer 하단) 그대로.
   적용 = .modal 에 .modal--head-save 를 단 풀스크린 모달 3곳
   (#addPassModal · #lateExemptionModal · #productModal). 헤더 버튼은 푸터의 같은 핸들러를
   그대로 부른다(경로 이원화 ❌) — 푸터는 모바일에서 통째 숨김.
   ======================================== */
.modal-head-cancel,
.modal-head-save,
.modal-body-destructive {
  display: none;
}

@media (max-width: 768px) {
  .modal--head-save .modal-head-cancel,
  .modal--head-save .modal-head-save {
    display: inline-flex;
    align-items: center;
    height: 44px;
    padding: 0 4px;
    background: none;
    border: none;
    cursor: pointer;
    font-size: 15px;
    flex-shrink: 0;
  }

  .modal--head-save .modal-head-cancel {
    color: var(--text-secondary, #64748b);
    font-weight: 500;
    padding-left: 0;
  }

  .modal--head-save .modal-head-save {
    color: var(--color-primary-600, #059669);
    font-weight: 800;
    padding-right: 0;
  }

  .modal--head-save .modal-head-save:disabled {
    color: var(--color-slate-300, #cbd5e1);
  }

  .modal--head-save .modal-title {
    flex: 1;
    text-align: center;
    min-width: 0;
  }

  .modal--head-save .modal-close,
  .modal--head-save .modal-footer {
    display: none;
  }

  /* 부가 파괴(지각 허용 시간 「없애기」)는 숨긴 푸터에서 본문 맨 아래 전폭 red 버튼으로 이동 —
     관장 표면 파괴 = red 버튼 한 문법 (§액션 푸터 「파괴 액션 배치」 ②의 자리 이동판) */
  .modal--head-save .modal-body-destructive {
    display: flex;
    width: 100%;
    justify-content: center;
    margin-top: 14px;
  }
}

/* ========================================
   77. CUSTOM ALERT/CONFIRM MODAL (구 섹션 78)
   - .form-select option 스타일은 섹션 65로 통합됨
   ======================================== */
/* 무대 = 보이는 영역 (common.js §1.4 OverlayStage) — `Alert.prompt` 가 입력칸을 싣는다 */
.alert-modal-backdrop {
  position: fixed;
  top: var(--vvt, 0px);
  left: 0;
  right: 0;
  bottom: auto;
  height: var(--vvh, 100vh);
  background: rgba(15, 23, 42, 0.5);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--z-toast);
  opacity: 0;
  visibility: hidden;
  transition: all 0.2s ease;
}

.alert-modal-backdrop.active {
  opacity: 1;
  visibility: visible;
}

.alert-modal {
  background: white;
  border-radius: 20px;
  box-shadow: 0 20px 50px -12px rgba(0, 0, 0, 0.16);
  width: 100%;
  max-width: 320px;
  margin: 20px;
  overflow: hidden;
  transform: scale(0.92) translateY(-24px);
  /* 스프링 팝인 (2026-07-16 모션 시스템) — 표준 .modal 과 동일 어휘 */
  transition: transform 0.42s var(--ease-spring);
}

.alert-modal-backdrop.active .alert-modal {
  transform: scale(1) translateY(0);
}

/* 아이콘 제거 후 좌측 정렬 — 표준 .modal 어휘와 통일 (장식 없이 typography fingerprint) */
.alert-modal-body {
  padding: 26px 24px 0;
  text-align: left;
}

.alert-modal-title {
  font-size: 16px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  margin: 0 0 6px;
}

.alert-modal-message {
  font-size: 13px;
  color: var(--text-secondary);
  margin: 0;
  line-height: 1.55;
}

/* 이름 칩 한 벌 (Alert options.names — 2026-09-08 사용자 확정 B안 · 시안 `msg-optout-ask-names`).
   「N명」이 «누구»인지를 확인창이 말하는 자리 — 명단은 칩 한 벌이 소유한다([[0638]]).
   무면 pill (§탭/필터 ② Hairline Pill 어휘) — 누르는 물건이 아니라 «확인»이라 hover 없음.

   ⚠️ 문자 모달 헤더 `.cp-chip`(message-compose-modal page-local)과 «같은 어휘, 다른 원자»다 —
   저쪽은 이름 + 벨트 락업을 담고 여기는 이름만 담는다. 사용처 2곳이라 글로벌 승격은 아직 ❌
   (§원자 승격 기준 「세 번째 사용처에서 검토」). 다만 **면·테두리·radius 가 갈리면 두 화면이
   이어져 보이던 이유가 사라진다** — 값을 바꿀 땐 두 곳을 함께 볼 것. 칩 여백만 이쪽이 반 단계 좁다
   (320px 상자라 한 줄에 드는 수가 곧 이 창의 정보량이다). */
.alert-modal-names {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 14px;   /* 모달 body 섹션 간격 = 14 단일 */
}

.alert-modal-name {
  display: inline-flex;
  align-items: center;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--color-slate-900);
  background: #fff;
  border: 1px solid var(--color-slate-200);
  border-radius: 999px;
  padding: 5px 10px;
  word-break: keep-all;   /* 이름은 중간에서 끊지 ❌ (`.alert-modal-meta-value.stack` 과 같은 판단) */
}

/* 넘침 칩 — 접힘에서 못 든 «나머지 수». 사람이 아니라 수라 글자색만 한 단계 낮춘다.
   점선 ❌ — 이 앱에서 dashed 는 「누르면 늘어나는 행 추가 슬롯」 전용 어휘라 가짜 손잡이로 읽힌다. */
.alert-modal-name--more {
  color: var(--color-slate-500);
}

/* 라벨/값 메타 카드 = ADR 0015 데이터 카드 어휘 (2026-08-09 사용자 확정 A안 — 구 emerald tint 폐지).
   .modal-data-card 와 같은 slate-50 + hairline + 행 hairline + UPPERCASE 라벨 — 앱 확인 UI 어휘 1종 수렴.
   danger 는 면 그대로 + 값만 red (구 red tint 면 재도입 ❌ — 신호는 텍스트·btn-danger 가 담당) */
.alert-modal-meta {
  background: var(--color-slate-50);
  border: 1px solid var(--border-hairline);
  border-radius: 10px; /* 모달 내부 타일 10 단일 */
  padding: 8px 14px;
  margin-top: 16px;
  text-align: left;
  font-size: 13px;
  line-height: 1.6;
}

.alert-modal-meta-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 0;
}

.alert-modal-meta-row + .alert-modal-meta-row {
  border-top: 1px solid var(--border-hairline);
}

/* meta 캡션 행 — 라벨/값 쌍이 아닌 «행들의 읽는 법» 한 줄 (2026-09-05, 시안 topup-count-exclusive).
   라벨보다 한 단계 흐리고 작다: 그 자체가 사실이 아니라 아래 사실들의 조건이라 값과 경쟁하면 안 된다. */
.alert-modal-meta-caption {
  padding: 8px 0 2px;
  font-size: 12.5px; /* §보조 각주/힌트 토큰 — 11.5px 신설 ❌ ([[0038]] 12px 바닥) */
  font-weight: 400;
  line-height: 1.55;
  color: var(--color-slate-500);
}
/* 캡션이 «구분선 역할»을 겸한다 — 위 행과는 hairline 으로 갈리고, 캡션이 이끄는 첫 행은
   선 없이 붙는다(그 행은 캡션의 «내용»이지 다음 사실이 아니다). 행+행 규칙만 두면
   캡션 뒤 첫 행이 선을 잃고 캡션 자신은 위 행과 붙어 소속이 뒤집힌다. */
.alert-modal-meta-row + .alert-modal-meta-caption {
  border-top: 1px solid var(--border-hairline);
  margin-top: 7px;
}
.alert-modal-meta.danger .alert-modal-meta-caption { color: var(--color-slate-500); }

.alert-modal-meta-label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-slate-500);
  flex-shrink: 0;
}

/* 값 = slate (ADR 0075 금액 값 슬롯 slate 통일 — 모달 데이터 카드 .data-value 와 같은 어휘, 2026-07-28).
   accent 강조는 색이 아니라 800 weight 가 담당 — emerald 재도입 ❌ */
.alert-modal-meta-value {
  color: var(--text-primary);
  font-weight: 700;
  text-align: right;
  word-break: break-all;
  font-variant-numeric: tabular-nums;
}

.alert-modal-meta-value.accent {
  color: var(--color-slate-900);
  font-weight: 800;
}

/* 값이 여럿인 행 — 한 항목 한 줄 (2026-08-29 사용자 확정 B안 · 시안 dup-phone-multi-name).
   「이미 등록된 관원」처럼 상대가 N명일 수 있는 자리용. 이름은 중간에서 끊지 않는다
   (기본 break-all 은 번호·금액용 규칙이라 사람 이름엔 「남궁 / 민서」 같은 절단이 생긴다). */
.alert-modal-meta-value.stack {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  word-break: keep-all;
}

.alert-modal-meta.danger .alert-modal-meta-value,
.alert-modal-meta.danger .alert-modal-meta-value.accent {
  color: var(--status-error-ink);
}

/* 값 옆 부가 회색 글씨 (예: "4명 [제외 1명]") — 데이터 카드 .data-value-suffix 와 같은 토큰
   (2026-08-09 A안 수렴 때 구 emerald-700 → slate-500 정합) */
.alert-modal-meta-value .alert-modal-meta-sub {
  font-weight: 500;
  color: var(--color-slate-500);
  font-size: 12px;
  margin-left: 6px;
}

.alert-modal-meta.danger .alert-modal-meta-value .alert-modal-meta-sub {
  color: var(--status-error);
}

/* 값 앞 이전 값 (취소선) — ADR 0015 데이터 카드 `.data-value-prev` 와 같은 토큰.
   "확정 금액 4,900원 → 7,900원" 처럼 확정 전에 변화를 보여주는 모달용. */
.alert-modal-meta-value .alert-modal-meta-prev {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--color-slate-500);
  text-decoration: line-through;
  margin-right: 6px;
}

/* 보조 경고 텍스트 (메타 아래 — "되돌릴 수 없습니다" 류) */
.alert-modal-warn {
  font-size: 12.5px;
  color: var(--text-tertiary);
  margin: 12px 0 0;
  line-height: 1.5;
}

/* danger 확인 모달 한정 — 돈이 잘못 나가는 급 경고가 각주 톤이면 모달에서 가장 흐린 글자가 된다.
   .alert-modal-meta.danger 와 같은 라인의 수식자 (2026-07-24 UI 감사). */
.alert-modal-warn.danger {
  color: var(--status-error-ink);
  font-weight: 700;
}

/* darkConfirm(주의 확정) 모달의 warning — red 는 아니지만 각주 톤보다 한 단계 위 (경고성 신호 텍스트 라인). */
.alert-modal-warn.strong {
  color: var(--color-slate-600);
  font-weight: 600;
}

/* note = 안심·부연 각주 (Alert options.note). warn 과 달리 모달 타입에 따라 승격되지 않는다 —
   「지우지 않아도 된다」 류가 danger 모달에서 red 700 으로 뜨면 뜻이 반대로 읽힌다.
   값 = 보조 각주/힌트 토큰(12.5 / 400 / slate-500 / 1.55) · 모달 body 섹션 간격 14. */
.alert-modal-note {
  margin: 14px 0 0;
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);
  line-height: 1.55;
}

.alert-modal-input {
  margin-top: 16px;
}

.alert-modal-input input,
.alert-modal-input textarea {
  width: 100%;
  padding: 12px 14px;
  border: 2px solid var(--border-default);
  border-radius: 10px;
  font-size: var(--text-sm);
  transition: all var(--transition-fast);
  box-sizing: border-box;
}

.alert-modal-input input:focus,
.alert-modal-input textarea:focus {
  outline: none;
  border-color: var(--accent-primary);
  box-shadow: 0 0 0 3px var(--accent-primary-subtle);
}

/* 두 칸 프롬프트 (Alert.promptPair) — 등원·하원처럼 «한 덩어리로 아는» 두 값 (ADR 0532).
   라벨은 새 방언이 아니라 마이크로 라벨 토큰(12/800/0.04em UPPERCASE slate-500) 그대로 —
   같은 두 값이 바로 위 `.modal-data-card .data-label` 로도 서므로 여기서 갈리면 한 화면에 두 라벨이 된다.
   is-single = 하원을 끈 도장(등원 한 칸) — 호출부가 아니라 이 클래스가 폭을 접는다. */
.alert-modal-field-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 16px;
}

.alert-modal-field-pair.is-single {
  grid-template-columns: 1fr;
}

/* 칸이 이미 라벨 아래 붙어 있어 입력 슬롯 자체의 상단 여백은 0 — 안 지우면 라벨과 칸이 벌어진다 */
.alert-modal-field-pair .alert-modal-input {
  margin-top: 0;
}

.alert-modal-field-label {
  display: block;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-slate-500);
  margin-bottom: 6px;
}

/* 시각 값 — 명단·데이터 카드와 같은 tabular-nums (자릿수가 흔들리면 두 칸이 서로 안 맞아 보인다) */
.alert-modal-field-pair input {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

/* match 확인 프롬프트 (Alert.prompt { match }) — 구독 취소 cancel-typing-guard 미러 (2026-08-19).
   일치 = emerald 테두리 + 연면, 불일치 문구가 「확정이 왜 안 켜지나」를 그 자리에서 답한다.
   대상 칩 = 별도 줄 (2026-08-19 사용자 확정 A안, 시안 transfer-name-presentation) —
   placeholder 에 정답을 넣으면 타이핑 시작 순간 소실. .cancel-typing-name 토큰 미러. */
.alert-modal-target-line {
  margin-top: 10px;
}

.alert-modal-target-name {
  display: inline-block;
  padding: 1px 7px;
  background: var(--color-slate-100);
  border-radius: 6px;
  font-size: 13.5px;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--color-slate-900);
  user-select: all;
}

.alert-modal-input input.match {
  border-color: var(--color-primary-600);
  background: var(--color-primary-50);
}

.alert-modal-match-status {
  margin-top: 6px;
  min-height: 16px;
  font-size: 12px;
  color: var(--color-slate-500);
}

.alert-modal-match-status.match {
  color: var(--color-primary-700);
  font-weight: 700;
}

.alert-modal-actions {
  display: flex;
  gap: 10px;
  padding: 20px 24px 24px;
}

.alert-modal-actions .btn {
  flex: 1;
  padding: 12px 16px;
  font-size: 14px;
  font-weight: 700;
  border-radius: 12px;
  border: none;
  cursor: pointer;
  transition: all 0.15s ease;
}

/* Cancel — Raised secondary(흰 면 + 중립 slate hairline + 미세 그림자). ADR 0047 정합.
   짝인 .btn-confirm 은 이미 1px + raised 인데 여기만 1.5px hollow + emerald hover 로 남아,
   ADR 0107 로 같은 전폭 split 이 된 뒤 한 쌍 안에서 테두리 두께·그림자·hover 색계열이 갈렸다.
   secondary 에 emerald ❌ (§디자인 톤) · 1.5px+ hollow 재도입 ❌ (§11). */
.alert-modal-actions .btn-cancel {
  background: #fff;
  color: var(--color-slate-700);
  border: 1px solid var(--color-slate-200) !important;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
}

@media (hover: hover) {
  .alert-modal-actions .btn-cancel:hover {
    border-color: var(--color-slate-300) !important;
    color: var(--color-slate-900);
    background: #fff;
    box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06), 0 2px 6px -2px rgba(15, 23, 42, 0.12);
  }
}

/* Confirm (info·success·warning) — Raised(흰 면 + emerald hairline + 미세 그림자). ADR 0034 정합, 솔리드 폐지. */
.alert-modal-actions .btn-confirm,
.alert-modal-actions .btn-confirm.info,
.alert-modal-actions .btn-confirm.success,
.alert-modal-actions .btn-confirm.warning {
  background: #fff;
  color: var(--color-primary-700);
  border: 1px solid var(--color-primary-200) !important;
  font-weight: 800;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06), 0 2px 6px -2px rgba(5, 150, 105, 0.18);
}

@media (hover: hover) {
  .alert-modal-actions .btn-confirm:hover,
  .alert-modal-actions .btn-confirm.info:hover,
  .alert-modal-actions .btn-confirm.success:hover,
  .alert-modal-actions .btn-confirm.warning:hover {
    background: var(--color-primary-50);
    border-color: var(--color-primary-300) !important;
    color: var(--color-primary-800);
    box-shadow: 0 2px 4px rgba(16, 24, 40, 0.08), 0 6px 14px -4px rgba(5, 150, 105, 0.28);
    transform: translateY(-1px);
  }
}

/* Dark(주의 확정) — Raised(흰 면 + slate hairline + slate-900 글자). §7 「주의 확정 = btn-dark」 미러 —
   비가역이지만 파괴가 아닌 확정(만료일 일괄 연장 등). red 는 진짜 파괴 확정 전용 (2026-08-04 UI 감사). */
.alert-modal-actions .btn-confirm.dark {
  background: #fff;
  color: var(--color-slate-900);
  border: 1px solid var(--color-slate-200) !important;
  font-weight: 800;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06), 0 2px 6px -2px rgba(15, 23, 42, 0.14);
}

@media (hover: hover) {
  .alert-modal-actions .btn-confirm.dark:hover {
    background: #fff;
    border-color: var(--color-slate-300) !important;
    box-shadow: 0 2px 4px rgba(16, 24, 40, 0.08), 0 6px 14px -4px rgba(15, 23, 42, 0.22);
    transform: translateY(-1px);
  }
}

/* Destructive — Raised(흰 면 + 1px red hairline + 미세 그림자). ADR 0034 정합(.btn-danger 미러), solid red·1.5px hollow 금지 */
.alert-modal-actions .btn-confirm.danger {
  background: #fff;
  color: var(--status-error-ink);
  border: 1px solid var(--status-error-border) !important;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
}

@media (hover: hover) {
  .alert-modal-actions .btn-confirm.danger:hover {
    background: var(--status-error-bg);
    border-color: var(--status-error) !important;
    color: var(--status-error);
    box-shadow: 0 2px 6px rgba(239, 68, 68, 0.18);
    transform: translateY(-1px);
  }
}

/* 2026-05-27: §79 ACTION TILES GRID 그룹 폐지 — templates 0건 사용 dead.
   .action-tile / -icon(+variant) / -content / -arrow + grid 전체 정리. */

/* ========================================
   80. SECTION HEADER
   ======================================== */
/* 2026-08-16: §81 FOCUS CARD SUPPLEMENTS 그룹 폐지 — `.focus-card-list`·`.focus-status`(+pending/warning/
   info/danger/done)·`.focus-text`·`.focus-action`·`.focus-date`·`.focus-card-cta` 전부 templates·js 0건
   사용 dead (ADR 0260 후속 정리). `.focus-card-header`/`-body` 도 같은 근거로 제거. 재도입 ❌ */

/* 2026-05-27: §82 METRIC CARD SUPPLEMENTS 폐지 — templates 0건 사용 dead. */

/* 2026-08-16: §83·§84 ACTIVITY 그룹 폐지 — `.activity-empty`·`.activity-item-icon/-content/-text/-time`
   전부 templates·js 0건 사용 dead (§51 `.activity-dot` 과 한 벌). 재도입 ❌ */

/* ========================================
   85. NAV USER DROPDOWN (Top Nav)
   ======================================== */













/* System Admin 아바타 - 에메랄드 테마 */


/* System Admin 간소화된 네비게이션 */





/* User Dropdown Menu */



























/* ========================================
   87. STREAK BANNER SUPPLEMENTS
   ======================================== */












/* 2026-08-16: §88 FOCUS CARD HEADER 폐지 — `.focus-card-header`(+h3)·`.focus-card-body`·
   `.focus-status.done` templates·js 0건 사용 dead (§81 focus 가족과 한 벌). 재도입 ❌ */
/* 2026-05-27: §89 METRIC ICON VARIANTS + .metric-content 그룹 폐지 — templates 0건 사용 dead. */

/* 2026-08-10: §90 ALERT PANEL STRUCTURE 폐지 — .alert-panel 가족 templates 0건 사용 dead (red tint 톤다운 동반 정리) */

/* ========================================
   91. FILTER TABS (System Pages)
   ======================================== */











/* ========================================
   92. SEARCH BAR
   ======================================== */








/* ========================================
   93. TABLE ENHANCEMENTS
   ======================================== */





/* ========================================
   94. BUTTON GROUP
   ======================================== */
.btn-group {
  display: flex;
  gap: 4px;
}

.btn-group .btn {
  border-radius: 6px;
}

/* ========================================
   96. MOBILE SLIDE MENU
   ======================================== */















/* ========================================
   97. NAV BRAND VARIANTS
   ======================================== */








/* ========================================
   98. STATS AREA / ALERTS AREA LAYOUT
   ======================================== */




/* 2026-05-26: 부모 .metric-card::before 가 폐지되어 이 override 도 무효 → 제거. */





/* Section 99 ERROR PAGES moved to R13 */

/* ========================================
   UPLOAD DROPZONE
   ======================================== */





















/* ========================================
   COLOR PICKER & BELT ORDER
   ======================================== */








.btn-icon-sm {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: none;
  background: transparent;
  color: var(--text-tertiary);
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: all var(--transition-fast);
}

@media (hover: hover) {
  .btn-icon-sm:hover {
    background: var(--bg-tertiary);
    color: var(--text-primary);
  }
}

/* .level-setting-item - 스타일은 섹션 72-2에서 정의됨 */

/* SortableJS 스타일 */






/* ========================================
   KAKAO BUTTON
   ======================================== */





/* ========================================
   MEMBER DETAIL PAGE STYLES
   ======================================== */

/* ============================================================
   Detail Layout — D. MONO Premium (magazine)
   회원 상세 페이지 전용 (정체성+기본정보 통합 2026-06-26):
     .detail-top-grid (좌: 프로필 카드[정체성 헤더+폼]+이력 3종 · 우: spotlight+예약+캘린더+소셜 + 「관원 삭제」 단일 행 카드[ADR 0544])
   구 하단 .detail-history-rail(2×2) 폐지 (2026-07-20) — 좌우 카드가 같은 높이에서 시작하며
   짧은 컬럼 아래 공백이 생기던 문제. 전 카드를 두 컬럼 stack 으로 흡수해 공백 없이 이어 흐름. */
.detail-top-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  align-items: start;
}
.detail-left-col,
.detail-right-col {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

/* ---- Profile head (프로필 카드 정체성 헤더, hero left 흡수 2026-06-26) ----
   기본정보 폼 카드 상단 = 이름(entity h1) + 상태 배지 + 벨트 라인. card-header 대체. */
.detail-profile-head {
  padding: 18px 22px;
  border-bottom: 1px solid var(--border-light);
}
.detail-profile-name {
  font-size: 23px;
  font-weight: 800;
  letter-spacing: -0.025em;
  color: var(--color-slate-900);
  line-height: 1.15;
  margin: 0 0 7px 0;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.detail-profile-belt {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-secondary);
}
/* Hairline Pill 통일 (2026-06-26): 솔리드 채움 폐지 → 흰 bg + slate-200 hairline + 컬러 텍스트. */
/* 순수 텍스트 통일 (2026-06-26): 면·테두리 0, 색 + 800 굵기로만 (.badge-* status 패밀리와 동일 어휘) */
.detail-identity-status {
  font-size: 12px;
  font-weight: 800;
  color: var(--color-primary-700);
  letter-spacing: 0.04em;
}
.detail-identity-status.detail-identity-status--paused {
  color: var(--color-slate-600);
}
.detail-identity-status.detail-identity-status--expired {
  color: var(--color-slate-500);
}
/* 첫 출석 대기 — emerald 신호 (PWA .md-id-status--waiting 미러, 순수 텍스트 패밀리라 색만. 2026-07-29) */
.detail-identity-status.detail-identity-status--waiting {
  color: var(--color-primary-700);
}
/* 이용중(정상)은 배지 숨김 — 예외 상태만 노출. 요소는 부분갱신 앵커로 DOM 유지. */
.detail-identity-status.detail-identity-status--none {
  display: none;
}
/* 키즈 태그 — 관원 목록(이름 칸·모바일 카드) + 관원 상세 헤더가 «같은 두 얼굴» ([[0752]] ② ·
   2026-09-17 사용자 확정 A안 · 시안 `kids-surface-scope`: 목록에서 본 태그가 상세 헤더에서도 서야
   「키즈 · 보호자 연락처 없음」을 보고 들어온 관장이 스크롤 없이 그 사실을 다시 본다).
   두 페이지가 쓰게 되며 list.html 페이지 로컬에서 승격 — 형제 `.member-notify-tag` 는 목록 전용이라 그대로.
   순수 텍스트 · 무게로 가름 (`is-on` slate-900/800 · `is-need` slate-500/600) · 채움 배지 ❌ · 점선 밑줄 ❌ ·
   amber/red 승격 ❌ · `nowrap` ❌ — 근거는 list.html 의 태그 주석이 소유. */
.member-kids-tag { font-size: 12px; font-weight: 700; margin-left: 6px; }
.member-kids-tag.is-on { color: var(--color-slate-900); font-weight: 800; }
.member-kids-tag.is-need { color: var(--color-slate-500); font-weight: 600; }
/* 헤더에선 flex gap(10px)이 간격을 소유 — 목록의 인라인 margin 은 뺀다. 형제 상태 라벨과 같은 12px 라 줄 높이 불변. */
.detail-profile-name .member-kids-tag { margin-left: 0; }
/* ---- Unified Row list (회원 상세 — D-2 Caption variant) ----
   read-only / 편집 / 진입형 진입을 하나의 row 패턴으로 통합.
   focus rail (slate-900 inset 2px, `:has(:focus-visible)`) 로 "지금 편집 중" 시각 피드백.
   라벨 톤은 13px sentence-case 단일 — D.MONO 마이크로 라벨/폼 라벨 충돌 zone 제거. */
.ur-body {
  padding: 0 !important;
}
.ur-item {
  display: grid;
  grid-template-columns: 160px 1fr;
  gap: 12px;
  align-items: center;
  padding: 12px 20px;
  border-bottom: 1px solid var(--border-light);
  min-height: 52px;
  transition: background 0.15s;
}
.ur-item:last-child {
  border-bottom: 0;
}
/* `:focus-within` ❌ (ADR 0616 · 2026-09-05, `.ios-item` rail 과 같은 결함) — 행 우측 버튼(벨트 「변경」)이나
   셀렉트 트리거를 «마우스로» 누르면 그 컨트롤이 포커스를 쥐어 행 배경 + 좌측 세로 rail 이
   다른 곳을 누를 때까지 남는다. 텍스트 입력(`.ur-input`)의 「지금 편집 중」 신호는 그대로. */
.ur-item:has(:focus-visible) {
  background: var(--color-slate-50);
  box-shadow: inset 2px 0 0 var(--color-slate-900);
}
.ur-item-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 0;
}
.ur-item:has(:focus-visible) .ur-item-label {
  font-weight: 700;
  color: var(--color-slate-900);
}
.ur-input {
  width: 100%;
  padding: 6px 10px;
  height: 32px;
  font-size: 14px;
  font-weight: 600;
  color: var(--color-slate-900);
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  transition: all 0.15s;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-family: inherit;
}
@media (hover: hover) {
  .ur-input:hover {
    background: #fff;
    border-color: var(--color-slate-200);
  }
}
.ur-input:focus {
  outline: none;
  background: #fff;
  border-color: var(--color-primary-500);
  box-shadow: 0 0 0 3px var(--color-primary-100);
}
.ur-input.is-invalid {
  border-color: var(--status-error);
  background: var(--status-error-bg);
}
/* FormValidator 인라인 에러 — grid row 3번째 자식으로 부착되면 라벨 컬럼(160px)에 끼어
   깨짐 → 입력 컬럼 아래로 정렬 (입력이 우측 정렬이라 문구도 우측). */
.ur-item > .form-error {
  grid-column: 2;
  margin-top: 0;
  text-align: right;
}
.ur-readonly {
  font-size: 14px;
  font-weight: 700;
  color: var(--color-slate-900);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.ur-readonly small {
  /* 12px 바닥 명시 (ADR 0038) — 선언이 없으면 `<small>` 의 UA 기본 `font-size: smaller`(0.8333em)가
     부모 14px 을 받아 **11.67px** 로 렌더된다(2026-08-24 실렌더 측정). 그 규칙은 「전역 clamp 가 없어
     선언 단위로 12px 미만을 두지 않는 방식으로 지킴」이라 적어 뒀는데, 상속으로 내려가는 이 자리는
     선언이 0 이라 정적 grep(`font-size:` 선언만 검사)에 안 걸리는 구멍이었다.
     같은 짝 = 아래 `.cum-sum small`(members/detail page-local) — 한쪽만 고치면 같은 행에서 단위 크기가 갈린다.
     margin-left 2px 은 숫자↔단위 간격 박제값이라 불변 */
  font-size: 12px;
  font-weight: 500;
  color: var(--color-slate-500);
  margin-left: 2px;
}
/* select 은 CustomSelect 가 .form-select 에 자동 적용 — ur-select 는 row 안 정렬만 담당 */

.ur-item .custom-select-trigger {
  height: 32px;
  padding: 0 10px;
  font-size: 14px;
  border: 1px solid transparent;
  background: transparent;
  /* 글로벌 `.custom-select-trigger` 의 미세 그림자도 함께 해제 — 이 행의 어휘는 「평소 무면, hover/focus 때만
     흰 박스」(같은 행의 `.ur-input` 과 짝)인데 그림자만 남아 셀렉트 행에만 빈 박스 윤곽이 떠 있었다.
     값을 우측 정렬한 뒤로는 그 박스의 왼쪽 절반이 비어 더 도드라진다. hover/focus 박스는 border+bg 가 담당. */
  box-shadow: none;
  /* 값 정렬 = 오른쪽, 셰브론은 값 바로 뒤 동반 (2026-08-05 사용자 확정 A안 · 데스크톱 확장 B안).
     이 카드의 다른 값(이름·연락처·생년월일 `.ur-input`)은 전부 오른쪽 정렬인데 셀렉트만 왼쪽에서 시작해,
     짧은 값(「화이트벨트」)일 때 모바일 154px · 데스크톱 222px 어긋나 보였다. 셰브론 오른쪽 끝은
     `.ur-input` 글자 끝(= 셀 오른쪽 − 11px)과 이미 같은 선이라, 값을 셰브론에 붙이면 rail 이 하나가 된다.
     값 글자 자체는 셰브론 폭만큼(28px) 안쪽에서 끝나는 것을 감수 — 셰브론 제거·행 padding 예외는 미채택. */
  justify-content: flex-end;
}
.ur-item .custom-select-value {
  flex: 0 1 auto;
  text-align: right;
}
/* 닫힌 트리거의 벨트 색 칩 제거 — 바로 위 신원 줄의 벨트 그림 + 「화이트벨트」 텍스트가 이미 같은 정보를
   전달(§아이콘 「같은 정보를 라벨이 이미 전달하는데 중복 아이콘 ❌」). 모바일 선례를 데스크톱까지 확장해
   두 화면의 행 어휘를 하나로 통일 (2026-08-05 사용자 확정 B안).
   열린 옵션 목록의 dot 은 유지 — 거기선 색이 옵션 간 구분 신호다. */
.ur-item .custom-select-trigger .custom-select-color-dot {
  display: none;
}
/* `.ur-readonly` 는 padding 이 0 이라 혼자 11px 바깥에서 끝났다 — `.ur-input`(padding 10 + border 1)에 맞춤.
   한쪽만 적용하면 rail 이 다시 둘이 된다. */
.ur-readonly {
  padding-right: 11px;
}
@media (hover: hover) {
  .ur-item .custom-select-trigger:hover {
    background: #fff;
    border-color: var(--color-slate-200);
  }
}
.ur-item .custom-select.open .custom-select-trigger,
.ur-item .custom-select-trigger:focus-visible {
  background: #fff;
  border-color: var(--color-primary-500);
  box-shadow: 0 0 0 3px var(--color-primary-100);
}
/* 그룹 캡션 (D-2) — D.MONO 마이크로 라벨 톤 재사용 */
/* 섹션 머리 = 무면 마이크로 라벨 단일 (§섹션 머리, 2026-08-19 UI 감사) —
   구 slate-50 채움 + 하단선은 `.ios-section-divider` 가 무면으로 재정의된 바로 그 형태다.
   「회색 채움 스트립·섹션 머리용 구분선 재도입 ❌」. 색도 토큰(slate-500)으로 정렬.
   padding 은 이 카드의 행 리듬(20px 시작선)이 소유하므로 현행 유지. */
.ur-caption {
  padding: 14px 20px 8px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-slate-500);
}
/* 메모 row — 마지막 row, 전폭 textarea */
.ur-item--memo {
  grid-template-columns: 1fr;
  align-items: stretch;
  padding: 14px 20px 16px;
}
/* 높이 = 내용만큼 자동으로 펼침, 15줄에서 멈춤 (2026-08-22 사용자 확정 B안, 시안 member-memo-autogrow).
   메모는 이 카드에서 유일하게 길이가 사람마다 다른 칸이라 3줄 고정이면 대부분 안 보이는 채로 놓인다.
   상한 358px = 15줄(14 × 1.6 = 22.4) + padding 20 + border 2 — 넘치는 만큼만 칸 안 스크롤.
   짧은 창에서는 45vh 가 먼저 걸린다(min) — 상한이 화면보다 크면 「멈춘다」는 약속이 깨진다.
   `resize` 제거 필수 — 실제 높이는 JS(growMemo)가 매 입력마다 덮어써서, 손잡이로 끈 높이가
   다음 타이핑에 즉시 되돌아간다(손잡이가 고장난 것처럼 읽힌다). min-height 72px 은 유지 —
   빈 메모까지 얇아지면 클릭 표적이 줄고 위 행들의 리듬이 깨진다. */
/* 상한에 닿았을 때만 칸 아래를 흰색으로 옅힌다 — 「이 아래로 이어진다」를 글자 없이 말하는 신호
   (2026-08-22 사용자 확정 B안 · 시안 `memo-cap-affordance`). textarea 는 가상 요소를 못 가져
   래퍼가 필요하고, 격자 칸을 차지하는 것도 이 래퍼다(`min-width:0` = 1fr 트랙 수축 허용).
   끝 색이 토큰이 아니라 `#fff` 인 이유 = 바로 아래 `.ur-memo` 의 background 가 하드코딩 #fff 라,
   토큰으로 적으면 두 값이 갈리는 순간 페이드만 칸 색과 어긋난다 (같은 값을 같은 자리에서 읽는다).
   우측은 스크롤바 폭(`--memo-sbw`, JS 실측)만큼 비켜 덮는다 — 상시 표시 스크롤바 환경에서 thumb 을
   흐리면 「굴릴 수 있다」는 신호를 오히려 지운다. `is-capped` 토글 = syncMemoCap 단독 소유. */
.ur-memo-wrap {
  position: relative;
  min-width: 0;
}
.ur-memo-wrap.is-capped::after {
  content: '';
  position: absolute;
  left: 1px;
  right: calc(1px + var(--memo-sbw, 0px));
  bottom: 1px;
  height: 34px;
  border-radius: 0 0 7px 7px;   /* 칸 radius 8 − 테두리 1 */
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0), #fff);
}
.ur-memo {
  /* inline 기본값이면 래퍼 바닥에 baseline 여백이 생겨 페이드가 테두리 위로 뜬다 */
  display: block;
  width: 100%;
  padding: 10px 12px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--color-slate-900);
  border: 1px solid var(--color-slate-200);
  border-radius: 8px;
  background: #fff;
  resize: none;
  min-height: 72px;
  /* 화면 높이 = vh fallback + dvh override (앱 전역 규약) — vh 는 주소창이 숨은 큰 뷰포트 기준이라
     주소창이 떠 있는 동안 상한이 실제 가시영역의 45% 를 넘는다. 위 주석의 「상한이 화면보다 크면
     『멈춘다』는 약속이 깨진다」가 정확히 그 상태. */
  max-height: min(358px, 45vh);
  max-height: min(358px, 45dvh);
  font-family: inherit;
  transition: all 0.15s;
}
.ur-memo:focus {
  outline: none;
  border-color: var(--color-primary-500);
  box-shadow: 0 0 0 3px var(--color-primary-100);
}
.ur-memo.is-invalid {
  border-color: var(--status-error);
  background: var(--status-error-bg);
}
@media (max-width: 768px) {
  /* 라벨 56px = 가장 긴 라벨 「한달 출석」(13px/600 실측 48.1px) 기준 — 라벨 열 폭은 늘 최장 라벨이 정한다.
     구 84px(「최근 한달 출석」 73.8px 기준)은 360px 화면에서 벨트 값 여유를 8px 밖에 안 남겨,
     벨트 이름이 한 글자만 길어져도 다시 말줄임으로 잘렸다. 56px + gap 8 로 값 자리를 32px 회수
     (구 120px → 84px → 56px, 2026-08-05 사용자 확정 C안 + 벨트 옵션 성인 접두 제거 병행). */
  .ur-item {
    grid-template-columns: 56px 1fr;
    gap: 8px;
    padding: 10px 16px;
    min-height: 48px;
  }
  /* grid 1fr 은 min-width:auto 라 nowrap 컨트롤이 트랙을 밀어낸다 — 잘림의 직접 원인. 값 슬롯만 수축 허용 */
  .ur-item > *:not(.ur-item-label) {
    min-width: 0;
  }
  /* 정렬(값 우측 + 셰브론 동반) · 색 칩 제거 · `.ur-readonly` 11px 보정은 **전역** 선언으로 올라갔다
     (데스크톱도 같은 어긋남이라 2026-08-05 B안으로 확장) — 여기 모바일 사본 재도입 ❌ */
  .ur-caption {
    padding: 12px 16px 6px;
  }
  .ur-item--memo {
    grid-template-columns: 1fr;
    padding: 12px 16px 14px;
  }
  .ur-input,
.ur-item .custom-select-trigger {
    font-size: 16px; /* iOS Safari zoom 방지 */
  }
  .ur-memo {
    font-size: 16px; /* iOS Safari zoom 방지 */
    /* 같은 15줄이라도 글자가 16px 이라 줄 높이가 25.6px — 상한도 그만큼 올라간다(15 × 25.6 + 22).
       실제로는 좁은 화면에서 45vh 가 먼저 걸리는 경우가 대부분이다. */
    max-height: min(406px, 45vh);
    max-height: min(406px, 45dvh);
  }
}

/* ---- Pass spotlight (hero right) ---- */
.detail-pass-spotlight {
  /* 2026-06-04: 카드 배경 emerald wash(흰→primary-50) 제거 → #fff.
     project-frontend-guide §「Carded surface — color rail 일반 금지」 ·「D.MONO」
     carded surface fingerprint 로 emerald 강제 ❌. emerald 신호는 내부 dot/숫자가 담당.
     paused 변형은 slate-50(neutral dimming) 유지 — 활성=흰 / 일시정지=slate veil 상태 구분. */
  background: #fff;
  border: 1px solid var(--border-hairline, #eaecef);
  /* 카드 radius 14 단일 (§디자인 톤) — 구 16px 은 등재 예외(8·12) 어디에도 없고 같은 컬럼의
     .detail-reserved-card·.card·.plan-card 가 전부 14 (2026-08-06 UI 감사). */
  border-radius: 14px;
  padding: 20px 22px;
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
/* 2026-05-26: .detail-pass-spotlight::before 상단 emerald hairline 폐지 —
   project-frontend-guide §「Carded surface — color rail 일반 금지」. */
.detail-pass-eyebrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
/* eyebrow 는 상태 신호가 아닌 정적 라벨이라 emerald ❌ — attendance hero eyebrow 판정(2026-07-28)과 동일.
   자간 = 한글 마이크로 라벨 0.04em (2026-07-30 사용자 확정 B안). */
.detail-pass-eyebrow-label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-slate-500);
}
.detail-pass-type-line {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}
/* 이용권 뱃지 어휘 = 흰 bg + slate-200 hairline + slate-700 텍스트 + 좌 emerald dot
   (§이용권 뱃지 적용 목록에 .detail-pass-type-chip--* 명시). 구 emerald-200 테두리 +
   emerald-800 텍스트는 .badge-period 계열과 어긋나 slate 로 환원 (2026-08-06 UI 감사).
   emerald 는 dot 이 담당 — 테두리·글자로 확장 ❌ */
.detail-pass-type-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  background: #fff;
  border: 1px solid var(--border-light);
  border-radius: 5px;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-slate-700);
}
.detail-pass-type-chip::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-primary-600);
}
.detail-pass-type-chip.detail-pass-type-chip--count::before {
  background: var(--pass-dot-count);
}
.detail-pass-type-chip.detail-pass-type-chip--day::before {
  background: var(--pass-dot-day);
}
.detail-pass-type-chip.detail-pass-type-chip--period::before {
  background: var(--pass-dot-period);
}
.detail-pass-type-chip.detail-pass-type-chip--weekly::before {
  background: var(--pass-dot-weekly);
}
.detail-pass-type-name {
  font-size: 12px;
  color: var(--text-secondary);
  font-weight: 600;
}
/* 주N회권 주당 한도 — 타입 칩 옆 info 알약 (ADR 0028 읽기 경로 보완).
   주N회권은 기간 기반(일수)이라 한도가 큰 숫자 슬롯에 못 들어가 칩 라인에서 한도를 전달.
   emerald = 정보 신호 위치 허용 (project-frontend-guide 「Carded surface」 정보 박스 emerald ✅). */
.detail-pass-weekly-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 9px;
  border-radius: 5px;
  background: #fff;
  border: 1px solid var(--color-slate-200);
  font-size: 12px;
  font-weight: 700;
  color: var(--color-primary-700);
}
.detail-pass-big {
  font-size: 40px;
  font-weight: 800;
  color: var(--color-slate-900);
  letter-spacing: -0.035em;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  display: flex;
  align-items: baseline;
  gap: 4px;
}
/* 임박(D-7) — Brand Mono: slate-900 유지 (red/amber/emerald 시맨틱 확장 금지). 긴급도는 D-N 숫자가 전달. */
.detail-pass-big.detail-pass-big--urgent {
  color: var(--color-slate-900);
}
/* 무기한(만료일 없음) — 숫자가 아니라 텍스트라 큰 KPI 슬롯에서 한 단계 축소 */
.detail-pass-big .detail-pass-unlimited {
  font-size: 26px;
  letter-spacing: -0.02em;
}
/* 14/700 = 회원 PWA hero 단위(.md-pb-num small 15/600)와 의도적 분기 (2026-08-09 사용자 확정 C안) —
   PWA 는 폰 단일 컬럼이라 한 단계 큰 단위. 값 "통일" 재지적 ❌ (숫자 본체 40px 스펙은 두 표면 동치 유지).
   margin-left 3px = PWA hero 와 같은 날 확정한 미러 값 (구 「붙임 0px」 supersede, 5px 재도입 ❌) */
.detail-pass-big small {
  font-size: 14px;
  font-weight: 700;
  color: var(--text-secondary);
  letter-spacing: -0.01em;
  margin-left: 3px;
}
.detail-pass-meta {
  font-size: 12.5px;
  color: var(--color-slate-500);   /* 만료·정지 종료일 = 읽어야 하는 값 (§slate-400 승격 기준) */
  margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
/* 주N회권 초과 출석 (ADR 0527) — 이용권 spotlight 바닥에 hairline 한 겹 아래로 붙는 기록 줄.
   숫자 줄은 데이터 값(13.5/700 slate-900), 각주는 §보조 각주 토큰(12.5/400/slate-500) 그대로.
   면·테두리·아이콘 ❌ — 경고가 아니라 기록이라 이 카드에서 가장 조용한 층에 앉는다. */
.detail-pass-over {
  margin-top: 11px;
  padding-top: 11px;
  border-top: 1px solid var(--border-light);
}
.detail-pass-over-line {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text-primary);
}
.detail-pass-over-line b {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.detail-pass-over-note {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);
  line-height: 1.55;
  margin-top: 4px;
}

/* meta 줄 안 인라인 편집 진입 — 값 옆에서 그 값을 고친다 (ADR 0295 일시정지 종료일).
   카드 헤더 텔레포트 링크(.card-action-link)와 같은 12px/800 emerald 어휘의 인라인판이라
   면·테두리 없음. 모바일 히트영역은 상하 padding 으로 확보(글꼴 크기 불변 — §카드 헤더 hit area 문법).

   ⚠️ 값 소유자는 «여기 한 곳» — `.promo-inline-edit`(관원 목록 승급식 예정일·「다음 벨트」 칸)이
   2026-09-06 UI 감사에서 합류했다. 그 page-local 선언은 주석이 스스로 「글로벌 미러」라 적어 두고
   **같은 값을 바이트째 다시 선언**하고 있었고, 이미 한 축이 갈려 있었다 — 글로벌은 `margin-left: 4px`
   를 무조건 갖는데 사본은 `.members-table` 안에서만 6px 이라 **승급식 바 인스턴스만 좌측 여백 0**.
   사용처가 셋(스트립·표 셀·모바일 카드 JS)이라 §원자 승격 기준의 「3번째 사용처」 트리거를 넘겼다.
   선례 = `.md-today-cta` 를 `.md-pb-btn` 선언으로 흡수(2026-08-04).
   **page-local 값 사본 재도입 ❌** — 그 자리에만 필요한 차이(`white-space`·표 셀 margin)만 남긴다. */
/* ⚠️ 구 셋째 소비처 `.pubpage-url-copy`(「도장 페이지」 관리 화면의 인라인 「복사」)는 [[0820]] 이
   그 행을 「홍보에 쓰기」 그룹의 `btn btn-secondary btn-sm` 으로 올리며 사라졌다 — 선언도 함께
   걷었다(2026-09-20 사용자 확정). 지금 사용처는 둘이라 §원자 승격 기준의 「3번째」를 다시
   밑돈다 — 그래도 **쪼개지 ❌**(같은 롤이고, 쪼개면 2026-08-30 이 합친 값이 다시 갈린다). */
.detail-pass-meta-edit,
.promo-inline-edit {
  font-family: inherit;
  font-size: 12px;
  font-weight: 800;
  color: var(--color-primary-700);
  background: none;
  border: none;
  padding: 2px 4px;
  margin-left: 4px;
  cursor: pointer;
  border-radius: 6px;
}
@media (hover: hover) {
  .detail-pass-meta-edit:hover,
  .promo-inline-edit:hover { background: var(--color-primary-50); }
}
/* 값이 아직 없다 — placeholder 성격이라 slate-400 유지 (§slate-400 승격 기준의 「빈 칸」 축) */
.detail-pass-meta-none { color: var(--color-slate-400); }
@media (max-width: 768px) {
  .detail-pass-meta-edit,
  .promo-inline-edit { padding: 6px 8px; }
}
/* 구매 시점 설명 스냅샷 각주 — spotlight 는 단독 hero 라 전문 (클램프 없음, 목록 표면과 구분) */
.detail-pass-plandesc {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);   /* 이용권 설명 4곳 한 값 — 구 tertiary 복귀 ❌ (2026-09-11 사용자 확정) */
  line-height: 1.55;
  margin-top: 8px;
  max-width: 460px;
}
.detail-pass-actions {
  display: flex;
  /* 이 행은 「지금 이 이용권」의 행동만 담는다 (ADR 0203) — 「이용권 등록」은 새 이용권 생성이라
     카드 머리(.detail-pass-eyebrow)로 이관. 활성 4개(정지·시작일 조정·기간횟수 보정·해지)
     · 첫 출석 대기 1개(시작일 조정). 좁은 2컬럼 폭에서 wrap 없이는 라벨이 말줄임 없이 잘림 (2026-07-30 UI 감사) */
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 16px;
  padding-top: 14px;
  /* solid hairline — dashed 는 행 추가 슬롯 idiom 전용 (2026-07-28 UI 감사, pay-toss-note 선례) */
  border-top: 1px solid var(--border-hairline);
}
.detail-pass-actions .btn {
  /* basis 0 + grow — 일상 액션 3개가 라벨 길이와 무관하게 완전 균등 (2026-08-04 사용자 확정 B안).
     구 basis auto 는 "남는 공간만 균등 배분"이라 출발점(라벨 길이) 차이가 결과 폭까지 그대로 남아
     「이용권 등록」이 「시작일 조정」보다 20px 넓은 어긋남이 상시 노출됐다.
     min-width: max-content = 구 주석이 경고한 "basis 0 이 자연 폭 아래로 눌러 라벨을 자름"의 방지턱. */
  flex: 1 1 0;
  min-width: max-content;
  justify-content: center;
}
/* 액션이 하나만 남는 상태(첫 출석 대기 = 「시작일 조정」 단독)는 자연 폭 — flex:1 1 0 이면
   버튼 하나가 카드 안쪽 전폭(≈700px)을 먹어 「빈 상태 CTA 2개 이상」 규칙이 막으려던 폭발이 그대로 난다.
   `<p class="detail-pass-action-note">` 는 다른 타입이라 정지 상태(버튼 4개)에는 매칭되지 않는다. (ADR 0203) */
.detail-pass-actions .btn:only-of-type {
  flex: 0 0 auto;
  /* 격자 모드(@container ≤460px)에서도 같은 뜻 — 한 칸(절반 폭)을 채우지 말고 자연 폭 좌측 정렬.
     flex 모드에선 무효라 한 선언으로 두 모드를 덮는다. */
  justify-self: start;
}
/* 해지(파괴) = 우측 끝 분리 — 일상 액션 스트레치에서 제외 + 여백 한 단계 (2026-07-28 사용자 확정 A안) */
.detail-pass-actions .btn-danger {
  flex: 0 0 auto;
  margin-left: 12px;
}
/* 비활성 액션의 이유 각주 (정지 중 「시작일 조정」 — 2026-08-05 사용자 확정 B안).
   버튼을 지우지 않고 자리를 지키므로 "왜 지금 안 되는지 + 다음에 뭘 하면 되는지"를 이 줄이 잇는다.
   flex 행에서는 전폭 wrap, ≤480px 격자에서는 두 칸 span — 두 레이아웃 모두 한 줄 통짜. */
.detail-pass-action-note {
  flex: 0 0 100%;
  grid-column: 1 / -1;
  margin: 9px 0 0;
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.55;
  color: var(--color-slate-500);
}
.detail-pass-action-note strong {
  font-weight: 700;
  color: var(--color-slate-700);
}

/* Pass spotlight — Empty state (등록 안내) */
/* `min-height: 200px` 제거 — 2026-08-24 사용자 확정 A안 (시안 pass-spotlight-empty-height).
   내용이 81px(제목 + CTA)인데 200px 을 예약하고 `justify-content: center` 라, 패딩 32 위에
   **죽은 공백 43px** 이 더 얹혀 제목 위가 75px 비어 있었다 — 사용자가 신고한 출석 빈 상태와 같은 패턴이고
   같은 처방(늘어남을 끊어 중앙 정렬할 남는 높이를 0 으로)으로 수렴시킨다.
   ⚠ 「등록 시 레이아웃 점프 방지」로 되돌리지 ❌ — 그 근거는 실측으로 성립하지 않았다:
   채워진 상태는 `min-height: auto` 라 **이용중 235 · 예약 동반 254 · 일시정지 313px** 로 갈리고,
   200 은 그 어느 것과도 안 맞아 예약을 둬도 점프는 남아 있었다(오히려 145→235 와 200→235 의 차이일 뿐).
   등록은 모달 확정 뒤 부분 갱신이라 화면이 바뀌는 게 이미 기대되는 순간 — 인지된 대가.
   `justify-content: center` 는 유지(내용 높이만 남아 무동작이지만, 카피가 늘면 다시 중앙이 맞다) */
.detail-pass-spotlight--empty {
  background: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 32px 22px;
}
/* 빈 상태 desc 는 2026-07-30 미니멀 카피로 삭제 (CTA 라벨이 이미 다음 행동을 말함) —
   title 하단 여백이 CTA 와의 간격을 담당 */
.detail-pass-empty-title {
  font-size: 15px;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  margin-bottom: 14px;
}

/* Pass spotlight — Paused state (slate-tinted) */
.detail-pass-spotlight.detail-pass-spotlight--paused {
  background: linear-gradient(135deg, #fff 0%, var(--color-slate-50) 100%);
}
.detail-pass-spotlight.detail-pass-spotlight--paused .detail-pass-actions {
  border-top-color: var(--color-slate-200);
}

/* Pass spotlight — 첫 출석 대기 (활성 이용권 없이 예약 이용권 보유, 첫 출석 시작 모델) */
.detail-pass-spotlight.detail-pass-spotlight--waiting .detail-pass-big span {
  font-size: 22px;
  letter-spacing: -0.02em;
}

/* (Reserved passes 는 detail-pass-column 안 별도 카드로 분리 — Split Cards 시안 2026-05-20.
    상세 스타일은 .detail-reserved-card 시리즈 참조) */

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 1024px) {
  .detail-top-grid {
    grid-template-columns: 1fr;
  }
  /* rail 폐지(2026-07-20) 후에도 단일 컬럼에선 프로필 → 이용권 → 캘린더 → 이력 3종 → 소셜 → 삭제
     읽기 순서 유지 — 컬럼 wrapper 를 풀고 order 로 재배열.
     (wrapper 를 그대로 두면 좌측 이력들이 현재 이용권보다 위로 끼어듦) */
  .detail-left-col,
  .detail-right-col {
    display: contents;
  }
  #memberForm { order: 1; }
  .detail-pass-column { order: 2; }
  .detail-right-col > .card { order: 3; } /* 출석 캘린더·지각 (설문·소셜은 아래 ID order 가 우선) */
  #detailPassHistoryZone { order: 4; }
  /* 결제 내역 = 이용권 이력 «바로 다음» (ADR 0579) — 데스크톱 좌측 컬럼 자리와 같은 순서.
     이 카드가 끼면서 뒤 카드 order 가 한 칸씩 밀렸다(승급 5→6 …) — 새 카드를 이 사이에 넣을 땐
     뒤 전부를 함께 밀 것(order 는 정수라 사이값이 없다). */
  #detailPaymentZone { order: 5; }
  #detailLevelHistoryZone { order: 6; }
  #detailPauseHistoryZone { order: 7; }
  /* 설문 답변 = 이력 3종 다음. 데스크톱에서는 **우측 컬럼 하단**(지각 ↔ 소셜 사이)인데
     모바일은 이 ID order 가 `.detail-right-col > .card { order: 3 }` 을 특이도로 이겨 9번째를 지킨다
     (2026-08-21 사용자 확정 B안 = 「모바일 순서 변화 0」이 채택 근거 — 이 선언을 지우면 설문이 4번째로 튄다).
     **새 카드에 order 를 안 주면 기본값 0 이라 프로필(1)보다 위로 튄다** — 2026-08-21 실측:
     빈 「설문 답변」 카드가 관원 이름 카드보다 먼저 떴다. 이 블록에 카드를 추가할 때 order 부여 의무. */
  #detailQuestionAnswerZone { order: 8; }
  #detailSocialZone { order: 9; }
  /* 관원 삭제 = 단일 행 카드(ADR 0544 — 구 0505 꼬리 링크 supersede). 카드가 되며
     `.detail-right-col > .card { order: 3 }` 에도 걸리지만 이 ID 선언(1,0,0)이 특이도로 이겨
     여전히 순서를 소유한다 — 지우면 order 3 으로 떨어져 캘린더·설문 카드 사이로 끼어든다. */
  #detailDeleteZone { order: 10; }
}

@media (max-width: 768px) {
  .detail-top-grid {
    gap: 12px;
  }
  .detail-left-col,
  .detail-right-col {
    gap: 12px;
  }
  .detail-pass-spotlight {
    padding: 16px 18px;
    border-radius: 14px;
  }
  .detail-profile-head {
    padding: 16px 18px;
  }
  .detail-profile-name {
    font-size: 20px;
  }
  .detail-pass-big {
    font-size: 34px;
  }
}

/* 이용권 액션 = 2열 균등 그리드 — **판정축 = 뷰포트가 아니라 카드 자신의 폭** (2026-08-12 사용자 확정 C안).
   flex wrap 스트레치는 좁은 폭에서 "행마다 남는 공간"이 달라 격자가 무너진다 —
   375px 실측: 1행 150/149 인데 2행은 「이용권 등록」 243 · 「해지」 50 으로 극단 대비.
   그리드는 줄바꿈 지점이 상태(액션 5개/4개/3개)와 무관하게 고정되고 전 버튼이 같은 폭.
   해지는 grid-column 2 고정 — 액션이 3개든 5개든 늘 우측 하단이라 위치 근육기억이 깨지지 않는다.
   파괴 액션 분리는 데스크톱처럼 여백(margin)이 아니라 이 고정 위치가 담당 — 해지는 별도 행이 아니라
   마지막 행 우측 칸이라, margin-top 을 주면 같은 행 짝이 그만큼 늘어나(44→50px) 두 버튼의
   높이·기준선만 어긋난다 (2026-08-04 실측). 격자 간격은 6px 균일.
   액션 5개면 마지막 행 좌측 칸이 비는데, 이는 해지를 늘 우측 하단에 두기 위한 의도된 빈 칸이다
   (해지 전폭 = 파괴가 화면 최대 타깃이 되는 위계 역전이라 ❌ — 2026-08-12 시안 pass-action-row-5 D안 기각).

   ── 구 뷰포트 경계(≤480px)를 컨테이너 쿼리로 대체한 이유 (2026-08-12 실측) ──
   구 규칙의 근거는 「481~768px 은 자연 폭 합계 ≈392px 라 한 행에 들어간다」였는데 그건 **액션 4개** 기준이다.
   「기간·횟수 보정」이 추가돼 5개가 되면서 한 행 필요 폭이 **442.8px**(일시정지 73.8 + 시작일 조정 89.1 +
   기간·횟수 보정 104.9 + 이용권 등록 89.1 + 해지 49.9 + gap 24 + 해지 margin 12)로 올라, 뷰포트가 아니라
   **카드 안쪽 폭**이 이 값을 밑도는 구간 전부가 깨졌다 — 실측 2밴드:
     · 뷰포트 1025~1300px : 1025px 이상은 .detail-top-grid 가 2컬럼이라 카드가 화면 절반만 받는다.
       1025px→안쪽 315px(2행: 1행 99/99/105 · 2행 「이용권 등록」 **246.6** — 2.5배 대비) ·
       1280px→안쪽 442px(0.8px 모자라 해지만 아랫줄에 홀로)
     · 뷰포트 481~510px : 500px→안쪽 430px (같은 증상)
   뷰포트 미디어쿼리로 잡으면 경계가 2벌 필요하고 **사이드바 접힘(240→80px)으로 카드가 넓어지는 경우를
   못 본다**. 컨테이너 쿼리는 판정을 실제 제약(카드 폭)에 직접 걸어 두 밴드와 폰을 한 규칙으로 덮는다.
   임계 = 필요 폭 + 여유 17 (라벨 드리프트분).
   **현재값 370px** — ADR 0203 으로 「이용권 등록」이 카드 머리로 빠져 4버튼이 되며 재측정:
     · 활성   = 일시정지 73.8 + 시작일 조정 89.1 + 기간·횟수 보정 104.9 + 해지 49.9 + gap 18 + margin 12 = 347.7
     · 정지   = 「다시 이용」 77.1 (일시정지보다 3.3 넓은 최장 조합) → **351.0** ← 이쪽이 기준
     351.0 + 19 = 370. 구 460 은 **5버튼 시절 값**(위 식에 이용권 등록 89.1 + gap 6 을 더한 442.8 기준)이라
     4버튼에선 1280px(카드 안쪽 442px)처럼 한 줄이 들어가는 폭까지 격자로 접었다.
   측정법 = 화면 밖 클론 probe(`flex:0 0 auto; min-width:max-content`) — 원본 행에 `width:max-content` 를 걸면
   정지 상태의 각주 `<p>`(flex 0 0 100%)가 컨테이너 폭을 잡아 버튼이 늘어난 값이 잡힌다.
   ❌ 뷰포트 미디어쿼리로 되돌리기 · 임계를 351 로 조여 여유 0 으로 두기 · 해지를 전폭/자연 폭으로 바꾸기 */
.detail-pass-spotlight {
  container-type: inline-size;
}
@container (max-width: 370px) {
  .detail-pass-actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
  }
  .detail-pass-actions .btn {
    flex: none;
    min-width: 0;
  }
  .detail-pass-actions .btn-danger {
    grid-column: 2;
    margin-left: 0;
  }
}

/* Member Profile Header — 실제 선언은 §38 (2026-08-21 고아 CSS 스윕 잔여 주석 정리) */









/* Info Grid */










/* ──────────────────────────────────────────────────────────────
 * Pass Card (글로벌)
 * 회원 대시보드(member/dashboard.html)와 관리자 회원 상세
 * (admin/members/detail.html)가 공유하는 이용권 카드.
 * "Hero D-day" 시각 언어 — 큰 잔여 표시 + 만료 메타.
 * ────────────────────────────────────────────────────────────── */

















/* D-7 이하 만료 임박 — 잔여 숫자만 빨강으로 강조 */




/* ============================================================
   Reserved Passes — Split Cards 시안 (2026-05-20)
   detail-pass-column 안에서 spotlight 다음 sibling 으로 위치.
   예약 N건이 늘어도 spotlight 위치/높이 영향 없음.
   ============================================================ */
.detail-pass-column {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

/* zone wrapper — 항상 존재 (partial refresh 가 빈 상태로 갱신 가능) */
.detail-reserved-zone:empty {
  display: none;
}

.detail-reserved-card {
  background: #fff;
  border: 1px solid var(--border-hairline, #eaecef);
  border-radius: 14px;
  overflow: hidden;
}

/* 헤더 — 표준 카드 헤더 어휘 미러 (card-title 14/800 + card-subtitle 각주 + 우측 카운트).
   구 회색 스트립 + UPPERCASE 마이크로 라벨 + 스트립 안 힌트는 페이지 유일 어휘라 폐지 (2026-07-20 A안). */
.detail-reserved-card-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 8px;
  padding: 15px 18px 13px;
  border-bottom: 1px solid var(--border-light);
  background: #fff;
}

.detail-reserved-card-head .history-count {
  margin-top: 2px;
  white-space: nowrap;
}

.detail-reserved-card-list {
  padding: 6px 8px;
}

/* 행 = B안 2축 (2026-07-21 사용자 확정): 좌=상품(제목·타입·기간 메타·메모) / 우=돈(가격·결제수단·구매일).
   가격이 세로 정렬(tabular)돼 여러 건 비교 스캔이 빠르다 — 결제 완료 목록과 같은 문법. */
.detail-reserved-card-row {
  display: grid;
  grid-template-columns: 20px 1fr auto 28px;
  align-items: start;
  gap: 12px;
  padding: 12px 8px;
  border-radius: 8px;
  transition: background 0.12s;
}

.detail-reserved-card-row + .detail-reserved-card-row {
  border-top: 1px solid var(--border-light);
}

@media (hover: hover) {
  .detail-reserved-card-row:hover {
    background: var(--color-slate-50);
  }
}

/* 순번 — 읽히는 명도 (slate-400), 채움·세로선 없음. align-start 라 첫 텍스트 줄에 시각 정렬 */
.detail-reserved-card-order {
  font-size: 12.5px;
  font-weight: 800;
  color: var(--color-slate-400);
  text-align: center;
  font-variant-numeric: tabular-nums;
  margin-top: 2px;
}

.detail-reserved-card-info {
  min-width: 0;
}

.detail-reserved-card-name {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-primary);
}

.detail-reserved-card-detail {
  font-size: 12px;
  color: var(--color-slate-500);
  margin-top: 2px;
}

/* 메모 — 보조 각주 토큰 (12.5/400/slate-500) */
.detail-reserved-card-memo {
  font-size: 12.5px;
  font-weight: 400;
  /* 관장이 직접 적은 운영 메모 = 그 자리의 유일한 신호 (§slate-400 승격 기준 「읽어야 하는 텍스트」).
     바로 아래 .detail-reserved-card-plandesc 의 tertiary 는 구매 시점 설명 스냅샷이라 각주 톤 유지 (ADR 0094). */
  color: var(--color-slate-500);
  line-height: 1.55;
  margin-top: 5px;
}

/* 메모 줄 — 메모 텍스트 + 수정/추가 링크 (등록 후 정정 경로, 2026-08-14 시안 컨펌).
   링크 = 12px/800 emerald 텍스트 (신호 위치 — 편집 가능 affordance), 히트박스는 padding 으로 확보 */
.detail-reserved-card-memoline {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: 5px;
}
.detail-reserved-card-memoline .detail-reserved-card-memo { margin-top: 0; }
.detail-reserved-memo-edit {
  background: none;
  border: none;
  padding: 2px 2px;
  cursor: pointer;
  font-size: 12px;
  font-weight: 800;
  color: var(--color-primary-700);
  white-space: nowrap;
  flex-shrink: 0;
}

/* 구매 시점 설명 스냅샷 — 반복 행 표면이라 2줄 클램프 (긴 설명이 가격 세로 비교 문법을 밀지 않게) */
.detail-reserved-card-plandesc {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);   /* 이용권 설명 4곳 한 값 — 관장이 쓴 «읽는» 글자 (2026-09-11 UI 감사 사용자 확정 A안) */
  line-height: 1.55;
  margin-top: 4px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* 우측 돈 축 — 가격(tabular 세로 정렬) + 결제수단·구매일 */
.detail-reserved-card-right {
  text-align: right;
}

.detail-reserved-card-price {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

/* 이관 건·가격 미상 = 실제 오간 돈이 아닌 자리라 흐린 500 (이력 행 --none 미러, 2026-08-20) */
.detail-reserved-card-price.is-none {
  font-weight: 500;
  color: var(--text-tertiary);
}

.detail-reserved-card-pay {
  font-size: 12px;
  color: var(--color-slate-500);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* 취소 버튼 — Brand Mono: subtle, hover 시 red */
.detail-reserved-card-cancel {
  width: 26px;
  height: 26px;
  border-radius: 6px;
  border: 1px solid var(--border-subtle);
  background: transparent;
  color: var(--text-tertiary);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: all 0.15s;
  flex-shrink: 0;
  margin-top: 2px; /* align-start 행에서 첫 텍스트 줄 기준 정렬 */
}
@media (hover: hover) {
  .detail-reserved-card-cancel:hover {
    border-color: var(--status-error);
    color: var(--status-error);
    background: var(--status-error-bg);
  }
}
/* 폰 판정만 44px — 보이는 26px 상자는 불변 (§히트박스 확대 idiom). 메모 링크 = padding + 같은 값 음수 마진 */
@media (max-width: 768px) {
  .detail-reserved-card-cancel { position: relative; }
  .detail-reserved-card-cancel::after { content: ''; position: absolute; inset: -9px; }
  .detail-reserved-memo-edit { padding: 13px 4px; margin: -13px -4px; }
}
.detail-reserved-card-cancel:focus-visible {
  outline: 2px solid var(--status-error);
  outline-offset: 2px;
}

/* Level History Chips — 무면 hairline 행 (2026-07-23 이력 카드 어휘 통일, 구 slate 면+테두리 칩 supersede.
   이용권 이력 ADR 0074 hairline 리스트와 동일 어휘).

   2026-08-21 사용자 확정 — 골격 C안(시안 promo-history-row) + 문장 3안(시안 promo-history-copy).
   「1줄 가로 전환」에서 「2줄 스택」으로 전환.
   구 어휘 재도입 ❌: 좌측 날짜 rail(min-width:60) · 벨트 그래픽 2개 · 결과값 압축 라벨(newShortLabel)이
   출발값 풀라벨과 갈리던 비대칭. 폐지 이유 3종 —
     (1) 같은 벨트 진급이면 색이 같은 띠가 한 행에 두 번 그려져 시각 비중이 실제 차이(스트라이프 1개)의 2배였다.
     (2) 좌 풀라벨(8자) ↔ 우 압축(3자) 비대칭으로 「무엇이 무엇으로」 대비가 행 안에서 깨졌다.
     (3) 벨트가 바뀐 행(「화이트벨트 4그랄 → 블루벨트 0그랄」)은 flex-wrap·min-width:0 이 둘 다 없어
         360px 카드 폭에서 잘렸다.
   현행 구조 = 벨트 그래픽 1개 + 제목 줄(**벨트 풀라벨 하나**) + 메타 줄(날짜) — 2026-08-27 벨트 기록
   모델 전환으로 출발값·화살표가 사라졌다(구 전환 문장 어휘 재도입 ❌).
   PWA 미러 `.md-lh-*`(member/dashboard) 동시 적용 의무 — 한쪽만 수정 ❌ (ADR 0079) */
.history-chips {
  display: flex;
  flex-direction: column;
}

.history-chip {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 2px;
  border-bottom: 1px solid var(--border-hairline);
}

.history-chip:last-child { border-bottom: none; }

/* 벨트 그래픽 = 그 기록의 벨트. 제목 첫 줄 기준 정렬 (align-start 행) */
.history-chips .belt { margin-top: 4px; }

/* 구 `.history-chip-belt-none`(「벨트 해제」 행의 점선 자리)은 벨트 기록 모델 전환(2026-08-27)으로
   사용처 0 이 돼 삭제 — 한 행이 「이 벨트가 된 날」이라 결과 벨트가 없는 행 자체가 없다.
   PWA 미러 `.md-lh-belt-none`(member/dashboard) 도 같은 날 동반 삭제. 재도입 ❌ */

.history-chip-main { flex: 1; min-width: 0; }

/* 날짜 = 이 행의 메타 줄 전체 (구 좌측 rail 셀 supersede) → slate-500
   (§slate-400 승격 기준, 2026-08-11 UI 감사 A안 유지). PWA 미러 `.md-lh-date` 동시 적용 의무 */
.history-chip-date {
  font-size: 12px;
  color: var(--color-slate-500);
  font-weight: 500;
  line-height: 1.5;
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}

/* Calendar */
.attendance-calendar {
  margin: 0 auto;
  max-width: 300px;
  padding: 4px 0;
}

.calendar-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 4px 10px 4px;
  margin-bottom: 6px;
}

.calendar-nav-btn {
  width: 38px;
  height: 38px;
  padding: 0;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  border: none;
  background: transparent;
  line-height: 1;
  color: var(--text-secondary);
  cursor: pointer;
  transition: all 0.2s ease;
}
/* 월 네비 아이콘 = icons :: chevronLeft/Right (ADR 0371, 2026-08-21 확정 A안).
   크기는 여기가 소유 — 지우면 width:auto 로 아이콘이 사라진다.
   38px 버튼이라 28px 버튼(.att-desk-datenav-arrow 14px)보다 한 체급 크게 16px.
   구 `font-size: 18px` 텍스트 글리프 재도입 ❌ */
.calendar-nav-btn svg { width: 16px; height: 16px; stroke-width: 2.25; }

@media (hover: hover) {
  .calendar-nav-btn:hover {
    background: var(--bg-tertiary);
    color: var(--text-primary);
    transform: scale(1.05);
  }
}

.calendar-month {
  font-size: 16px;
  font-weight: 600;
  color: var(--text-primary);
}

.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}

.calendar-day-header {
  text-align: center;
  font-size: 12px;
  /* 요일 헤더 = 표의 열 머리 → §마이크로 라벨/테이블 th 토큰(12/800/slate-500, 자간 0.03em).
     PWA `.md-cal-dow` 가 이미 이 값인데 관장만 12/600/slate-400 이라 같은 캘린더가 두 토큰이었다
     (나머지 축 — 지각 dot·주말색·셀 배경·radius — 은 이미 미러). [[0318]] 「같은 라벨이 폭에 따라
     다른 토큰이 되지 ❌」와 같은 축. 2026-08-18 사용자 확정 B안, 시안 calendar-dow-token.
     600/slate-400 되돌리기 ❌ */
  font-weight: 800;
  color: var(--color-slate-500);
  letter-spacing: 0.03em;
  height: 36px;
  line-height: 36px;
  margin-bottom: 4px;
}

/* 출석 캘린더 — 회원/관리자 통합 어휘(C안): 은은한 타일 + 주말 숫자색 + 오늘 ring */
.calendar-day {
  height: 36px;
  line-height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 600;
  color: var(--color-slate-700);
  background: #f7f8fa;
  border-radius: 8px;
  transition: all 0.15s ease;
}

.calendar-day.other-month {
  background: transparent;
  color: var(--color-slate-300);
}

/* 출석 채움 = emerald-700 — 흰 숫자 대비 3.77 → 5.48:1 (2026-08-27 사용자 확정 A안 ·
   시안 `legal-toc-and-fill-contrast`). 관원 PWA `.md-cal-day.is-att` 와 «같은 출석은 같은 색»
   이라 세 표면(관장 캘린더 · PWA · 범례 `.cal-legend-fill`)이 한 벌로 움직인다. 600 복귀 ❌ */
.calendar-day.attended {
  background: var(--color-primary-700);
  color: white !important;
  font-weight: 700;
}

.calendar-day.today {
  background: var(--color-primary-50);
  color: var(--color-primary-700);
  font-weight: 800;
  box-shadow: inset 0 0 0 1.5px var(--color-primary-400);
}

.calendar-day.today.attended {
  background: var(--color-primary-700);
  color: white !important;
  box-shadow: none;
}

/* 지각 = 채움 위 우상단 빨간 점. 「채워진 칸 = 출석」 규칙을 깨지 않고 한 축을 더 얹는다.
   (member PWA .md-cal-day 미러 — 두 표면 동일 어휘) */
.calendar-day.is-late { position: relative; }
.calendar-day.is-late::after {
  content: '';
  position: absolute;
  top: 4px;
  right: 4px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--status-error);
}

/* 고칠 수 있는 날짜 (오늘 이전) — 누르면 날짜 시트 */
.calendar-day.is-editable { cursor: pointer; }
@media (hover: hover) {
  .calendar-day.is-editable:hover { box-shadow: inset 0 0 0 1.5px var(--color-primary-400); }
}

/* 캘린더 범례 — 채움(출석)과 점(지각)이 다른 축이라 둘 다 설명해야 읽힌다 */
.cal-legend {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--border-hairline);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-secondary);
}
.cal-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.cal-legend-fill {
  width: 12px; height: 12px; border-radius: 4px;
  background: var(--color-primary-700);
}
.cal-legend-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--status-error);
}

/* 출석 날짜 시트 — 옵션 행 (무면 hairline, 이력 카드 문법) */
.att-day-state {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-primary);
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border-hairline);
}
.att-day-options { display: flex; flex-direction: column; }
.att-day-option {
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: flex-start;
  width: 100%;
  padding: 13px 2px;
  border: none;
  border-bottom: 1px solid var(--border-hairline);
  background: none;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  transition: background 0.15s ease;
}
.att-day-option:last-child { border-bottom: none; }
@media (hover: hover) {
  .att-day-option:hover { background: var(--color-slate-50); }
}
.att-day-option-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--color-primary-700);
}
.att-day-option.is-danger .att-day-option-title { color: var(--status-error-ink); } /* 흰 면 14px 글자 = -ink ([[0495]]) */
.att-day-option-desc {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);
  line-height: 1.55;
}

/* 주말색 = 흰 면 위 작은 글자(헤더 12px · 날짜 14px, 둘 다 18.66 미만)라 한 단계 진하게
   ([[0495]] — red-500 → `--status-error-ink`(#dc2626, 3.76→4.83) · blue-500 → #2563eb(3.68→5.17)).
   PWA 짝 `.md-cal-dow--sun/--sat`·`.md-cal-day`(member/dashboard)는 2026-08-26 에 승격됐는데 관장
   미러만 남아 **같은 캘린더가 두 값**이었다 — 위 `.calendar-day-header` 주석이 「주말색은 이미 미러」라
   적어 둔 자리가 그 뒤로 갈린 것. #ef4444 / #3b82f6 복귀 ❌ · 한쪽만 고치기 ❌.
   면(`.calendar-day.is-late::after`·`.cal-legend-dot` 의 `--status-error`)은 불변 — 바뀌는 건 «글자»뿐 */

/* 일요일 헤더 */
.calendar-day-header:first-child {
  color: var(--status-error-ink);
}

/* 토요일 헤더 */
.calendar-day-header:nth-child(7) {
  color: #2563eb;
}

/* 일요일 날짜 숫자색 (출석/타월/오늘 제외) */
.calendar-day:nth-child(7n+1):not(.other-month):not(.attended):not(.today) {
  color: var(--status-error-ink);
}

/* 토요일 날짜 숫자색 (출석/타월/오늘 제외) */
.calendar-day:nth-child(7n):not(.other-month):not(.attended):not(.today) {
  color: #2563eb;
}

/* 출석한 주말은 요일색으로 덮지 않는다 (2026-07-23 사용자 확정 A안) — 채움 = 출석 신호라
   요일색(빨강·파랑) 채움을 얹으면 빨강이 오류·환불 신호와 충돌하고 회원 PWA 캘린더
   (member/dashboard `.md-cal-day.is-att`)와 같은 출석이 다른 색으로 보인다. 주말 요일색은
   위 미출석 셀 숫자색 규칙(:not(.attended))에만 적용. */

/* Attendance Summary */


/* Quick Actions */








/* Payment List */










.payment-date {
  font-size: 12px;
  color: var(--text-tertiary);
}

.payment-amount {
  font-size: 14px;
  font-weight: var(--font-semibold);
  color: var(--text-primary);
}





/* 2026-08-16: 구 Activity Log 잔재(`.activity-list`·`.activity-item`·`.activity-dot`(+payment/level)) 폐지 —
   templates·js 0건 사용 dead. 재도입 ❌ */
/* Badge Level Style */





/* 월 선택 오버레이 */
.month-picker-overlay {
  position: absolute;
  top: 60px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--bg-primary);
  border: 1px solid var(--border-default);
  border-radius: 12px;
  padding: 16px;
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
  z-index: var(--z-raised);
  display: none;
  width: 220px;
}

.month-picker-overlay.active {
  display: block;
  animation: fadeInScale 0.15s ease;
}

.month-picker-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.month-picker-item {
  padding: 10px 8px;
  text-align: center;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
  background: transparent;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.15s ease;
}

@media (hover: hover) {
  .month-picker-item:hover {
    background: var(--bg-tertiary);
    color: var(--text-primary);
  }
}

.month-picker-item.current {
  background: var(--color-primary-50);
  color: var(--color-primary-600);
}

.month-picker-item.selected {
  background: var(--color-primary-600);
  color: #ffffff;
}

/* 범위(min·maxDate) 밖 월 — 달력 본문 .flatpickr-day.flatpickr-disabled 와 같은 어휘.
   hover 블록보다 뒤에 와야 이긴다 (같은 특이도 0,2,0 → 소스 순서 승부). */
.month-picker-item:disabled,
.month-picker-item:disabled:hover {
  color: var(--color-slate-300);
  background: transparent;
  cursor: not-allowed;
}

@keyframes fadeInScale {
  from {
    opacity: 0;
    transform: translateX(-50%) scale(0.95);
  }
  to {
    opacity: 1;
    transform: translateX(-50%) scale(1);
  }
}

/* ===== 삭제됨: SETUP WIZARD · Plan Wizard Tabbed Cards · Plan Type Tabs ·
   Plan Option Cards · CONTENT ACTIONS (2026-08-05, 총 1151줄) =====
   templates/ · static/js/ 사용처 0 확인 후 제거 (wizard-container·wizard-step·plan-type-tab·
   plan-option·plan-card-list·plan-field-·plan-add-btn·content-actions·celebration·
   progress-step·card-header-icon·level-item·plan-setup-item 전부 0건).
   단순 dead 정리가 아니라 **현행 금지 어휘를 통째로 보존하고 있어서** 지웠다 —
   카드 radius 20 + box-shadow(카드 shadow ❌) · 1.5~2px 테두리 hollow(§11) ·
   .form-input/.form-select page 재정의(§23) · focus box-shadow 2겹(§27) ·
   self-rolled segmented(§탭/필터) · 48px 그라데이션 원형 아이콘 타일(§아이콘) ·
   emerald linear-gradient 면(시맨틱 그라데이션 ❌).
   특히 `.card-header-icon`·`.plan-card-*` 는 무스코프라 새 마크업에 우연히 걸릴 수 있었다.
   이용권 편집 UI 는 fragments/plan-form-modal + style.css §이용권 폼 모달이 SSOT — 재도입 ❌ */

/* ==========================================
   MEMBER REGISTRATION - Guide Banner
   ========================================== */












/* ==========================================
   MEMBER REGISTRATION (Wizard Step 4)
   ========================================== */











/* Excel Upload Panel */


/* Excel step cards */


/* .excel-step-num(emerald 원형 번호) = 사용처 0 으로 삭제 (2026-08-09 초록 배경 정리) */







/* .file-drop-zone·.drop-zone-content·.drop-zone-text·.drop-zone-hint = 삭제 (2026-08-21 UI 감사).
   앞 둘은 폐지된 system member-import(ADR 0163) 잔재로 소비자 0. 뒤 둘은 logo-crop 이 쓰는 클래스와
   **같은 이름**인데 구 토큰(15px/500 · 13px/tertiary)이라, 소비 페이지 2곳이 add-tenant.css 를 뒤에
   로드해 정본(13/700 · 12.5/slate-500)이 이기고 있을 뿐이었다 — 로드 순서 하나에 기댄 지뢰.
   어휘 SSOT = add-tenant.css 단독. style.css 재도입 ❌ */

/* .selected-file(emerald tint 선택 파일 패널) = 사용처 0 으로 삭제 (2026-08-09 초록 배경 정리) */

/* Manual Registration Panel */


/* 필드 안내 */




/* 테이블 형태 입력 */


.member-table-wrapper:has(.custom-select-wrapper.open) {
  overflow: visible;
}



.member-table {
  width: 100%;
  min-width: fit-content;
}



.member-table-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 0;
}






































/* 이용권 타입별 필수 항목 영역 너비: col widths + gaps */
/* PERIOD: name(120) + phone(140) + expiry(140) + 2*gap(8) = 416px */

/* COUNT: name(120) + phone(140) + expiry(140) + remaining(80) + 3*gap(8) = 504px */

/* WEEKLY_COUNT: name(120) + phone(140) + expiry(140) + weekly(80) + 3*gap(8) = 504px */

/* NONE: name(120) + phone(140) + gap(8) = 268px */






/* 에러 발생 시 행 정렬 상단 기준으로 전환 */





/* 하단 총계 행 — 입력 표와 구분하는 hairline + 간격 */






/* 회원 등록 페이지 — 테이블 내부 스크롤 제거 */




/* 회원 등록 페이지 — 모바일: 행 wrap + 2-column grid 스택 */
@media (max-width: 768px) {
  
  
  
  
  /* 긴 입력 필드는 full-width로 배치 */
  
  /* 이름/연락처는 한 줄 2-col */
  
  /* 횟수 필드는 만료일과 같은 줄에서 자연스러운 분할 */
  
  /* 세로 구분선은 모바일에서 숨김 */
  
  /* 삭제 버튼은 행 우상단 absolute */
  
}

/* 이용권 타입 탭 — 라운드 칩 스타일 */
/* 이용권 종류 선택 = 표준 .tab-item (Text Hierarchy). 컨테이너만 flex-wrap 으로 5-way 줄바꿈 허용 */




/* 패널 표시/숨김 */




/* 기존 폼 스타일 (하위 호환) */








/* Added Members List */


















.member-name {
  font-weight: 500;
  color: var(--text-primary);
  font-size: 14px;
}



/* Responsive */
/* Date Input 스타일 */
.date-picker {
  cursor: pointer;
}

.date-picker[readonly] {
  background: var(--bg-primary);
}

/* 커스텀 연도 선택기 */
.year-picker-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--bg-primary);
  border-radius: 16px;
  z-index: var(--z-raised);
  display: flex;
  flex-direction: column;
  padding: 20px;
  opacity: 0;
  visibility: hidden;
  transition: all 0.2s ease;
}

.year-picker-overlay.active {
  opacity: 1;
  visibility: visible;
}

.year-picker-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 0 16px 0;
  border-bottom: 1px solid var(--border-default);
  margin-bottom: 16px;
}

.year-picker-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--text-primary);
}

.year-picker-nav {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  cursor: pointer;
  color: var(--text-secondary);
  font-size: 18px;
  transition: all 0.2s;
  background: transparent;
  border: none;
}

@media (hover: hover) {
  .year-picker-nav:hover {
    background: var(--bg-tertiary);
    color: var(--text-primary);
  }
}

.year-picker-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  flex: 1;
}

.year-picker-item {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  border-radius: 10px;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  transition: all 0.15s ease;
  border: none;
  background: transparent;
}

@media (hover: hover) {
  .year-picker-item:hover {
    background: var(--bg-tertiary);
    color: var(--text-primary);
  }
}

.year-picker-item.current {
  background: var(--accent-primary-subtle);
  color: var(--accent-primary);
  font-weight: 600;
}

.year-picker-item.selected {
  background: var(--accent-primary);
  color: var(--text-inverse);
  font-weight: 600;
}

@media (hover: hover) {
  .year-picker-item.selected:hover {
    background: var(--accent-primary-hover);
  }
}

/* 범위 밖 연도 — 월 선택기와 같은 어휘 */
.year-picker-item:disabled,
.year-picker-item:disabled:hover {
  color: var(--color-slate-300);
  background: transparent;
  cursor: not-allowed;
  font-weight: 500;
}


/* 구 「COMPLETION STEP」 섹션(.completion-* 12개 · .celebration-icon · @keyframes fade-up ·
   @keyframes icon-appear · 반응형·reduced-motion 보정) = 2026-08-11 삭제 — templates·js·java 전수
   grep 에서 completion·celebration 참조 0(사용처 0 dead CSS). 진입 페이드업 어휘가 fade-up 까지
   3종으로 갈려 있던 파편도 함께 소멸 — 현재 어휘는 motionEnterUp/motionEnterSpring 뿐. 재도입 ❌ */

/* ========================================
   SETTINGS PAGE LAYOUT (설정 페이지 레이아웃)
   ======================================== */


/* 구독 정보 배너 */























/* 문의 섹션 컴팩트 */




/* 설정 토글 아이템 */






/* 레벨 설정 input 필드 최적화 */


/* 반응형 */
/* ========================================
   KAKAO CHAT FLOATING BUTTON
   ======================================== */
.kakao-chat-fab {
  /* 글리프 색 소유 — icons :: kakao 는 fill: currentColor 라 여기가 카카오 규정색을 준다
     (2026-08-22 UI 감사 A안. 구 인라인 fill="#3C1E1E" 대체)
     **랜딩은 이 파일을 안 읽는다** — `landing.css` 에 같은 블록의 쌍둥이가 있고, 2026-08-22 에
     이 줄만 넣고 사본을 빠뜨려 랜딩 전 페이지의 말풍선이 body emerald 를 상속해 초록이 됐다
     (2026-08-23 복구). 이 블록 건드릴 때 landing.css `.kakao-chat-fab` 동반 수정 의무 */
  color: #3C1E1E;
  position: fixed;
  bottom: 24px;
  right: 24px;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: #FEE500;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 8px 24px rgba(254, 229, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.08);
  transition: transform 0.2s ease, box-shadow 0.2s ease, bottom 0.3s ease;
  z-index: var(--z-dropdown);
  text-decoration: none;
}

@media (hover: hover) {
  .kakao-chat-fab:hover {
    transform: translateY(-3px) scale(1.05);
    box-shadow: 0 12px 32px rgba(254, 229, 0, 0.5), 0 6px 16px rgba(0, 0, 0, 0.1);
  }
}

.kakao-chat-fab:active {
  transform: translateY(-1px) scale(1.02);
}

/* 본문에 같은 목적지의 상담 CTA 가 있으면 FAB 을 내린다 — 2026-08-17 사용자 확정 B안
   (시안 auth-kakao-fab-scope). 대상 = 복구 링크 만료(상담이 page primary) + 에러 상태 login·recover.
   훅을 `body:has([data-inline-chat])` 로 잡는 이유 = **노출 조건의 소유자를 하나로** 두기 위함이다 —
   login·recover 의 상담 링크는 errorMessage/param.error 안에서만 렌더되므로, 마크업이 사라지면
   FAB 이 저절로 돌아온다(레이아웃과 페이지가 같은 조건식을 각자 들고 갈리는 일이 원천 봉쇄).
   invitation(사범 초대)의 FAB 상시 노출은 **의도** — 초대 수락 실패는 정당한 문의 사유다.
   ⚠ **「layout 조건부 렌더로 되돌리기 ❌」의 스코프 = «본문에 상담 CTA 가 있는» 이 훅의 대상 화면뿐**
   (2026-09-14 개정). 구 문장은 그 금지를 auth 전체로 읽히게 썼고 `member-link` 를 상시 노출 «의도»로
   못박아 뒀는데, 2026-09-14 사용자 확정 ㄴ안(런북 `kakao-channel-split.md` §6)이 상담 채널을 관장
   전용으로 가르면서 **관원 전용 화면은 숨기는 것**으로 갈렸다. 구 문장을 그대로 두면 다음 감사가
   그 숨김을 결함으로 읽고 되돌린다 — 실제로 이번 diff 가 런북 §6 표 누락 때문에 스코프가 좁게
   확정됐던 자리를 교정한 것이다.

   **FAB 을 내리는 수단은 셋이고, 고르는 기준은 「무엇이 조건을 소유하는가」다**:
   ① 본문에 같은 목적지 상담 CTA 가 선다 → `body:has([data-inline-chat])` (아래 규칙 · 마크업이 소유)
   ② 화면 자체가 «관원 전용»이라 관장용 창구 진입이 없어야 한다 → `hideKakaoChat` 모델 속성
      (`layout/default` 가 읽는다 · 대상 목록은 런북 `kakao-channel-split.md` §6 표가 소유)
   ③ 관원 대면 풀스크린 → page-local `display:none` (`admin/attendance/kiosk.html`)
   새 화면을 더할 땐 셋 중 하나를 고르고 «그 소유자»에 등재할 것 — 넷째 수단 신설 ❌ */
body:has([data-inline-chat]) .kakao-chat-fab {
  display: none;
}

.kakao-chat-fab svg {
  width: 30px;
  height: 30px;
}

@media (max-width: 768px) {
  /* 화면 하단 적층 = 토큰 2개, 값의 출처는 한 줄(아래 `:has` 분기)뿐.
     ① `--bottom-dock` = **전폭으로 붙는 층**(하단 고정 액션바 등)의 바닥 y — 셸 하단 크롬 바로 위,
        간격 0. 셸 크롬이 없으면 홈 인디케이터만 피한다.
     ② `--fab-anchor` = **원형 FAB 스택 최하단** y — dock 위로 16px 띄운 자리.
        .kakao-chat-fab(상시) 가 스택 최하단, .fab(페이지 액션) 이 그 위로 적층 → 절대 겹침 방지.
        소비처 = mobile-shell.css `.fab`·`.kakao-chat-fab`.
     둘 다 바텀네비(64px)·랜딩 sticky CTA(72px) 유무에 따라 자동 보정 — **하단 크롬이 늘면 이 분기에
     추가**(소비처마다 높이를 재계산 ❌). `--fab-anchor` 를 dock 파생으로 적은 것은 두 값이 「같은
     바닥 + 16px」 관계라서다 — 계산 결과는 구 상수식과 완전히 동일(회귀 0). */
  body {
    --bottom-dock: env(safe-area-inset-bottom, 0px);
    /* dock 파생 의무 — 아래 두 분기와 같은 식. 상수 `16px` 로 두면 `viewport-fit=cover` 뒤
       **바텀네비가 없는 화면**(인증 전 10면 — 카카오 상담 FAB 상시)에서만 인셋을 안 먹어
       FAB 이 홈 막대에 깔린다 (2026-09-15 실측 박스 782~836 · 안전선 818).
       인셋 0 이던 시절엔 결과가 `16px` 과 같아 안 드러나던 자리 (2026-09-15 교정) */
    --fab-anchor: calc(var(--bottom-dock) + 16px);
  }
  body:has(.mobile-bottom-nav) {
    --bottom-dock: calc(64px + env(safe-area-inset-bottom, 0px));
    --fab-anchor: calc(var(--bottom-dock) + 16px);
  }
  body:has(.landing-mobile-cta.show) {
    --bottom-dock: calc(72px + env(safe-area-inset-bottom, 0px));
    --fab-anchor: calc(var(--bottom-dock) + 16px);
  }

  .kakao-chat-fab {
    bottom: var(--fab-anchor, 16px);
    right: 16px;
    width: 54px;
    height: 54px;
  }

  .kakao-chat-fab svg {
    width: 26px;
    height: 26px;
  }

  /* .fab(회원추가 등) 과 함께 노출될 때 카카오를 스택 최하단으로 맞춰 중앙선 정렬
     (.fab 56px vs 카카오 mobile-only 52px → 우측 +2px 보정으로 세로 라인 일치) */
  body:has(.fab) .kakao-chat-fab--mobile-only {
    right: 18px;
  }
}

/* 모바일 한정 변형: **admin 레이아웃 전용** (`layout/admin.html` 단독 소비처) — 데스크톱은 사이드바에
   상시 노출이라 숨김, 모바일 52px. 관원 PWA 는 이 변형을 쓰지 ❌ (`layout/member` 는 FAB 자체를 안 그린다).
   구 「admin/member 보완용」 서술은 2026-08-23 UI 감사가 fragment 쪽만 정정하고 이 짝 주석을 놓친 것 —
   그 말을 믿고 관원 PWA 에 붙이면 관원 대면 화면에 운영팀 채널이 새로 생긴다 (2026-09-04 UI 감사) */
@media (min-width: 769px) {
  .kakao-chat-fab--mobile-only {
    display: none !important;
  }
}

@media (max-width: 768px) {
  .kakao-chat-fab--mobile-only {
    width: 52px;
    height: 52px;
  }

  .kakao-chat-fab--mobile-only svg {
    width: 26px;
    height: 26px;
  }
}

/* ========================================
   POSTCODE MODAL (다음 우편번호 검색)
   fragment: fragments/postcode-modal.html
   ======================================== */
/* 무대 = 보이는 영역 (common.js §1.4 OverlayStage) — 우편번호 검색창이 키보드를 부른다 */
.postcode-modal {
  position: fixed;
  top: var(--vvt, 0px); left: 0; right: 0; bottom: auto;
  height: var(--vvh, 100vh);
  background: rgba(15, 23, 42, 0.6);
  backdrop-filter: blur(8px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: var(--z-modal);
  padding: 20px;
}
.postcode-modal.active {
  display: flex;
  animation: fadeIn 0.2s ease-out;
}
/* chrome = §모달 표준 토큰 (radius 14 · hairline · 모달 그림자 · header 56px · title 15/800 ·
   close 32×32). 짝인 .crop-modal 은 2026-07-27 에 정렬됐고 postcode 만 구 토큰이 남아 있었다.
   SSOT 가 이 fragment 에 준 예외는 「.active 토글」 하나뿐 — 시각 토큰 예외는 없다. */
.postcode-container {
  background: var(--bg-primary);
  border-radius: 14px;
  border: 1px solid var(--border-hairline);
  max-width: 540px;
  width: 100%;
  overflow: hidden;
  box-shadow: 0 20px 60px -20px rgba(15, 23, 42, 0.25);
  animation: motionModalPop 0.42s var(--ease-spring); /* 표준 .modal 스프링과 동일 곡선 (2026-07-30 통일) */
  display: flex;
  flex-direction: column;
  /* 무대의 90% — 오버레이가 「보이는 영역」이라 화면 전체(dvh)가 아니라 그 높이를 나눠 쓴다
     (common.js §1.4 OverlayStage · 2026-09-07 사용자 확정 A안). 구 `90dvh` 는 키보드가 떠도
     화면 전체의 90% 라 확정 푸터를 키보드 밑에 두는 값이었다. `90dvh` 복귀 ❌ */
  max-height: 90vh;
  max-height: calc(var(--vvh, 100vh) * 0.9);
}
.postcode-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 56px;
  flex-shrink: 0;
  padding: 0 22px;
  border-bottom: 1px solid var(--border-subtle);
}
.postcode-header h2 {
  font-size: 15px;
  font-weight: 800;
  letter-spacing: -0.02em;
  margin: 0;
  color: var(--text-primary);
}
.postcode-close-btn {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: transparent;
  color: var(--color-slate-400);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s;
  border: none;
  cursor: pointer;
}
@media (hover: hover) {
  .postcode-close-btn:hover {
    background: var(--color-slate-100);
    color: var(--text-primary);
  }
}
.postcode-embed {
  /* 확정 height 필수 — 컨테이너가 max-height 뿐이라 flex:1 사이즈가 불확정이고,
     Kakao SDK 레이어(height:100%)가 0으로 해소돼 데스크톱에서 백지가 됨.
     calc 의 64px = postcode-header 56px + 하단 인셋 8px (짧은 창에서 모달 잘림 방지 — iframe 이 바닥에 붙지 않게) */
  flex: none;
  height: min(480px, calc(90vh - 64px));
  height: min(480px, calc(90dvh - 64px));
  width: 100%;
  overflow: hidden;
}
@media (max-width: 768px) {
  .postcode-modal { padding: 0; }
  .postcode-container {
    max-width: 100%;
    /* 무대를 꽉 채운다 (common.js §1.4 OverlayStage) — `.postcode-modal` 이 이미 보이는 영역이라
       화면 전체(dvh)를 쓰면 키보드만큼 넘친다. `100dvh` 복귀 ❌ */
    height: 100vh;
    height: var(--stage-h, 100vh);
    max-height: 100vh;
    max-height: var(--stage-h, 100vh);
    /* 무대가 화면 맨 위에서 시작하므로 컨테이너도 그만큼 내려 앉힌다 — 안 하면 검색창 헤더가
       상태바 밑 (2026-09-14 cover 도입) */
    margin-top: env(safe-area-inset-top, 0px);
    border-radius: 0;
  }
  /* 모바일은 컨테이너가 100dvh 확정이라 flex:1 로 잔여 공간 채움 */
  .postcode-embed { flex: 1; height: auto; min-height: 0; }
}

/* Form Textarea: §23 표준 (.form-input 합본)에 textarea-only 속성만 추가 */
textarea.form-textarea {
  min-height: 120px;
  font-family: inherit;
  line-height: 1.5;
  resize: vertical;
}

.form-hint {
  font-size: 12.5px;
  color: var(--color-slate-500);
  margin-top: 4px;
  line-height: 1.55;
}
/* 각주 강조 토큰(§타이포 「보조 각주/힌트」 = strong 700 + slate-700) — 이 규칙이 없던 동안
   .form-hint 안 strong 이 색 없이 굵기만 달라져, 같은 문장을 쓰는 홈 KPI 각주(slate-700)와
   갈렸다 (ADR 0257 B 각주에서 실발현, 2026-08-16 사용자 확정 A안 — 전역 채움). */
.form-hint strong {
  font-weight: 700;
  color: var(--color-slate-700);
}

/* 「PC에서 열면 …」 권유 각주 — 모바일 화면 본문 **맨 아래** 한 줄 (ADR 0496 · 문장 = ADR 0528).
   ⚠ ADR 0413 의 「PC에서만 돼요」(미지원 안내)와 **다른 축**이다: 이 줄이 붙는 화면은
   폰에서 이미 다 된다. 큰 화면이 «더» 낫다는 권유라, 문형은 「PC에서 **열면** …볼 수 있어요」로
   고정한다 — 「PC에서 **해야**」로 읽히면 모바일 편집 기본 허용(ADR 0004)을 스스로 뒤집는다.
   **문장 = 고정 골격 + 슬롯 한 조각, 한 줄** (ADR 0528 이 0496 10항을 supersede):
   「PC에서 열면 <strong>〔그 화면이 더 주는 것〕</strong>까지 … 볼 수 있어요.」 — 4면이 골격을 공유하고
   `strong` 은 **슬롯만** 감싼다(문장 첫머리를 통째로 굵게 하던 구 「굵은 결론 + 근거 두 문장」 ❌).
   미지원 축(`.mbr-pc-note`·`.qs-mm-pc-note`)은 같은 한 줄 골격에 **낱말 하나로 갈린다** —
   「PC에서 **열면**」(권유) ↔ 「<strong>PC에서만</strong>」(미지원).
   원자 = `.form-hint` 재사용(page-local 사본 ❌). 이 클래스는 **여백·구분선만** 소유 —
   값은 미지원 각주 짝(`.mbr-pc-note`·`.qs-mm-pc-note`)과 같은 16/14/divider 로 맞춘다.
   **링크·버튼 ❌** — 갈 곳이 같은 URL 의 데스크톱 반쪽이라 누를 것을 주면 순수 헛걸음(ADR 0413 4항과 같은 이유).
   슬롯에는 그 화면에서 **PC 가 실제로 더 주는 것**만 적는다 — 폰에도 있는 기능을 PC 것으로 적으면 거짓 약속이 된다.
   하루 동안 **세 번** 걸렸다: ① 관원 「여러 명 한 번에 승급」(모바일 선택 액션바 ADR 0372) ② 홈 「매출·관원 수를
   그래프로」(홈에 canvas 0건) ③ 결제 「금액이 세로로 줄 맞춰 서서」(모바일 카드도 flex-end + tabular-nums).
   **데스크톱에 있는 걸 봤을 뿐 «폰에 없는지»를 안 본 것**이 셋의 공통 원인 — 새 슬롯은 반드시 양쪽을 대조할 것.
   ⚠ 홈은 ADR 0545(2026-08-31 사용자 확정)로 이 가족에서 **분리** — admin/dashboard page-local `.pc-home-sig`
   (중앙 한 줄 시그니처 · 「큰 화면으로 볼 땐 PC에서 · bjjmate.com」)가 대신 선다(근거 = 그 마크업 주석).
   이 클래스의 사용처는 **관원·출석·결제 3면뿐**이고, 주소는 여전히 홈 그 한 줄만 진다(ADR 0496 9항 불변). */
.pc-invite-note {
  /* 아래 20px = 이 각주가 모바일 본문 마지막 요소인데 .main-content 하단 패딩이 바텀네비 64px
     «정확히»(여유 0)라 글줄이 네비에 밀착하던 것의 해소분 (ADR 0545, 홈 .pc-home-sig 와 동값).
     여백은 각주 자신이 소유 — 전역 .main-content 패딩 확대 ❌ (ADR 0505 「오버플로를 만든 쪽이 갚는다」). */
  margin: 16px 0 20px;
  padding-top: 14px;
  border-top: 1px solid var(--border-divider);
}
/* 결과 0건·조회 실패에서 끄는 스위치 — 이 각주는 `.mobile-only` 와 함께 쓰이는데 그쪽이
   `display: revert !important` 라 **인라인 `style.display='none'` 으로는 안 꺼진다**(2026-08-28 UI 감사에서
   실측으로 잡힌 함정). 같은 !important 층위의 클래스라야 이긴다. 인라인 style 토글로 되돌리기 ❌ */
.pc-invite-note.is-off { display: none !important; }

/* ========================================
   Level Preset Selection (Setup Wizard)
   ======================================== */









.preset-card.selected .preset-card-content {
  border-color: var(--color-primary-500);
  background: var(--color-primary-50);
  box-shadow: 0 0 0 3px var(--color-primary-100);
}



.preset-card.selected .preset-icon {
  color: var(--color-primary-600);
}





/* Custom Level Type Section */















/* Level Preview */












/* Responsive */
/* ========================================
   로고 업로드 컴포넌트
   ======================================== */

/* 로고 업로드 영역 */


/* 설정 페이지용 로고 영역 */


/* 로고 드롭존 */











/* 로고 미리보기 */




/* wizard 페이지용 삭제 버튼 */



/* 설정 페이지용 액션 버튼 */




/* 출석 화면 로고 */






/* 출석 화면 좌우 분할 레이아웃 */














/* 반응형 - 태블릿 이하에서 세로 배치 */
/* 반응형 */
/* ========================================
   Number Input 스피너 숨기기
   ======================================== */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

/* ========================================
   이용권 타입별 그룹 스타일
   ======================================== */












/* ========================================
   100. SIDEBAR NAVIGATION
   ======================================== */
/* 사이드바 단일 선언 (2026-08-31 통합 — 구 §5·R4 사본 흡수, 계산값 전후 diff 0):
   display:flex/overflow-y 는 구 §5, background(--sidebar-bg 솔리드 흰색 — 구 §100 그라데이션은
   R4 가 늘 덮어 렌더된 적 없는 값)·border 색·transition 은 구 R4 최종값. */
.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  width: var(--sidebar-width);
  height: 100vh;
  height: 100dvh; /* vh fallback + dvh override — 모바일 드로어 하단 잘림 방지 (body 규칙 참고) */
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  /* 안전영역 — `viewport-fit=cover` 뒤로 드로어는 화면 «맨 위»에서 시작하고 가로 모드에선
     노치 쪽 변까지 닿는다. 첫 항목(도장 이름)·마지막 항목이 상태바·홈 인디케이터에 깔리지
     않도록 셸이 직접 받는다. 인셋 0 인 자리(데스크톱·안드로이드)는 렌더 변화 0 (2026-09-14) */
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  padding-left: env(safe-area-inset-left, 0px);
  background: var(--sidebar-bg);
  border-right: 1px solid var(--sidebar-border);
  z-index: var(--z-sticky);
  /* transform 필수 — 드로어 슬라이드가 이 한 줄에 산다(빼면 스냅, 2026-08-21 실측). width 는 죽은
     접힘 기능 잔재지만 R4 통합값 보존(제거는 별건). */
  transition: transform 0.3s ease, width 0.4s var(--ease-smooth), background 0.3s ease;
}

.sidebar-inner {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* Sidebar Header (Logo) */
.sidebar-header {
  padding: 20px 20px 16px;
  /* 도장명 2줄 노출 시 헤더가 자연 증가하도록 min-height (고정 height 면 2줄이 잘림/넘침) */
  min-height: var(--sidebar-header-height);
  display: flex;
  align-items: center;
  justify-content: space-between; /* 구 §5 생존값 (2026-08-31 통합) */
  background: var(--sidebar-bg);
  border-bottom: 1px solid var(--sidebar-border); /* 색 = 구 R4 최종값 */
}

.sidebar-logo {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  overflow: hidden;
  color: var(--text-primary);              /* 구 §5 생존값 */
  transition: all var(--transition-smooth); /* 구 §5 생존값 — hover 밀림 전이 */
}

@media (hover: hover) {
  .sidebar-logo:hover {
    transform: translateX(2px);
  }
}

.sidebar-logo-img {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--bg-secondary);
}

.sidebar-logo-text {
  /* 15px = 한 줄 ~10자 → 2줄 클램프로 긴 도장명(약 20자)까지 잘림 없이 수용 (컴팩트안, 2026-06-12).
     로고 40px 는 collapsed(80px) 헤더 콘텐츠 폭(80-padding 40)과 일치해 유지 — 텍스트만 축소 */
  font-size: 15px;
  font-weight: var(--font-bold);
  color: var(--color-slate-900); /* 2026-08-31 톤 수렴 — 주 잉크 900 단일 */
  letter-spacing: -0.02em;
  overflow: hidden;
  min-width: 0;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  word-break: keep-all;
  text-overflow: ellipsis; /* 구 §5 생존값 — 클램프 마지막 줄 말줄임 */
}

/* 무면 마이크로 라벨 — 셸에서 면을 가진 유일한 emerald pill 이었다.
   형제 신호(.tenant-switcher-item-role · 그룹 라벨)가 전부 무면 텍스트라
   이 자리만 채움이면 위계가 아니라 어긋남으로 읽힌다 (D.MONO 「emerald 는 신호 위치에만 · 면 채움 ❌」,
   2026-08-23 UI 감사). 채움 pill 복귀 ❌ */
.sidebar-sport-badge {
  color: var(--color-primary-700);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
}

.sidebar-logo-wordmark {
  height: 36px;
  width: auto;
  display: block;
  flex-shrink: 0;
}

/* Sidebar Navigation */
.sidebar-nav {
  flex: 1;
  padding: 16px 12px;
  overflow-y: auto;
  scrollbar-width: none; /* 스크롤은 유지, 바만 숨김 */
}

.sidebar-nav::-webkit-scrollbar {
  display: none;
}

.sidebar-nav-item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 11px 16px;
  border-radius: 10px;
  font-size: 14px;
  font-weight: var(--font-medium);
  color: var(--text-secondary);
  text-decoration: none;
  transition: all var(--transition-fast);
  margin-bottom: 2px;
  border-left: 3px solid transparent;
}

@media (hover: hover) {
  .sidebar-nav-item:hover {
    background: var(--bg-secondary);
    color: var(--text-primary);
    transform: translateX(2px);
  }
}

.sidebar-nav-item.active {
  background: var(--color-primary-50);
  color: var(--color-primary-700);
  font-weight: var(--font-semibold);
  border-left-color: var(--color-primary-500);
}

@media (hover: hover) {
  .sidebar-nav-item.active:hover {
    transform: none;
  }
}

/* 사이드바 상태 뱃지 — 사용처 2곳: ① 메시지 잔액 「곧 바닥」 상태어 (ADR 0548 갱신 2026-09-03 사용자
   확정 C안) ② 「알리고 대조」 처리 전 «건수» ([[0702]], 2026-09-12/13 사용자 확정 — 0건이면 모델에
   값이 없어 아예 안 그린다).
   ⚠ «잔액» 뱃지는 상태어만 적는다 — 거기에 숫자(남은 건수·원)를 넣지 ❌. 상시로 붙는 값이 아니라
   «신호가 있을 때만» 나타나는 물건이라, 값이 바뀌는 걸 지켜보게 만들면 사이드바가 상태판이 된다.
   ⚠ 구 서술 「숫자(잔액·건수)를 넣지 ❌」는 [[0702]] 가 건수 뱃지를 확정하기 «전» 문장이다 — 그 말을
   믿고 알리고 대조 건수를 상태어로 되돌리지 ❌. 금지의 범위는 «추적하게 만드는 값»(잔액)이지
   «처리할 일의 개수»가 아니다 (개수는 0 이 되면 스스로 사라져 지켜볼 것이 남지 않는다).
   면 = slate-100 + slate-800 글자 — [[0512]] DIVERGE 배지와 같은 무채색 어휘(2026-09-04 사용자 확정 A안 · 시안
   `msg-amber-tier`). 구 amber(#fffbeb/#fde68a/#92400e)는 SSOT 에 없는 「주의 tier」였다(§컬러 Amber 금지 ·
   [[0512]] 「amber 복귀 ❌」) — 재도입 ❌. 아직 벌어지지 않은 일이라 red 도 ❌. 12px 바닥 준수([[0038]]). */
.sidebar-nav-badge {
  margin-left: auto;
  flex-shrink: 0;
  font-size: 12px;
  font-weight: var(--font-extrabold);
  color: var(--color-slate-800);
  background: var(--color-slate-100);
  padding: 2px 7px;
  border-radius: 6px;
  white-space: nowrap;
}

.sidebar-nav-icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}

/* Sidebar Section Label(「출결」·「관원」·「설정」) — 2026-08-23 사용자 확정 1-A안 재도입
   (시안 sidebar-groups-and-admin-name, 같은 날 구 「메인」·「도장 관리」 삭제를 supersede).
   구 라벨이 죽은 건 그룹이라는 형식이 아니라 **이름** 탓이었다 — 「메인」은 내용이 없는 말이고
   「도장 관리」는 위 그룹에도 참이라 가르지 못했다. 새 3개는 전부 「무엇에 관한 것인가」 한 축.
   톤 = §섹션 머리 무면 마이크로 라벨(12px/800/slate-500/0.04em) — 구 11px/0.4px/tertiary 는
   §폰트 최소 크기 12px 바닥 위반이라 복원 ❌.
   **hide-on-mobile 부여 ❌** — 구 「메인」이 그걸 달고 있어 폰 드로어엔 라벨 하나만 떴다.
   그룹 여백(margin)·구분선 ❌ (라벨의 위 padding 이 그 일을 한다). */
.sidebar-section-label {
  font-size: 12px;
  font-weight: var(--font-extrabold);
  color: var(--color-slate-500);
  letter-spacing: 0.04em;
  padding: 14px 16px 6px;
}
/* 구 `.sidebar-nav > .sidebar-section-label:first-child { padding-top: 4px }` 는 부활시키지 않았다 —
   홈이 `th:if` 없이 **항상 첫 자식**이라 라벨이 첫 자식이 될 수 없어 매칭 0인 고아 규칙이 된다.
   폰 드로어에서 홈이 `hide-on-mobile` 로 빠져도 요소 자체는 남아 첫 자식 자리를 지킨다. */

/* Sidebar Footer 카카오 상담 — `.sidebar-nav-item` 상속 + 오버라이드 3: 카카오 브랜드 아이콘 ·
   아래 여백 8px · **13px**(푸터 유틸이라 nav 항목 14px 보다 한 단계 아래 — `sidebar-user-*` 와 같은 결).
   14px 통일 ❌ (로그아웃 카드와의 위계가 사라진다) */
.sidebar-consult-item {
  margin-bottom: 8px;
  font-size: 13px;
}

.sidebar-consult-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  background: #FEE500;
  border-radius: 50%;
  color: #3C1E1E;
}

.sidebar-consult-icon svg {
  width: 12px;
  height: 12px;
}

/* Sidebar Footer */
.sidebar-footer {
  padding: 16px; /* 구 R4 최종값 (구 §100 12 16 16 은 늘 지던 죽은 값) */
  border-top: 1px solid var(--sidebar-border);
  background: transparent;
}

.sidebar-user-card {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--bg-secondary);
  border-radius: 10px;
}

.sidebar-user-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}

.sidebar-user-name {
  font-size: 13px;
  font-weight: var(--font-semibold);
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* slate-500 승격 — §slate-400 승격 기준 「그 자리의 «유일한 신호»인가」 (ADR 0495, 2026-08-26).
   대비 2.45:1 → 4.76:1. 크기·굵기 불변이라 위계 손해 0. slate-400/300 복귀 ❌ */
.sidebar-user-meta {
  font-size: 12px;
  color: var(--color-slate-500);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}











.sidebar-power-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text-tertiary);
  cursor: pointer;
  transition: all var(--transition-fast);
}

.sidebar-power-btn svg {
  width: 16px;
  height: 16px;
}

@media (hover: hover) {
  .sidebar-power-btn:hover {
    background: var(--status-error-bg);
    color: var(--status-error);
  }
}

/* Sidebar mobile close */
.sidebar-close-mobile {
  display: none;
  /* 드로어 **안** 우측 상단 (2026-08-21 사용자 확정 B안, 시안 `drawer-close-and-dead-css`).
     구 `right: -44px` 는 사이드바 박스 밖이라 `overflow-y: auto` 스크롤 컨테이너에 **잘려서**
     375px·900px 어디서도 보이지도 탭되지도 않았다 — 마크업·CSS 는 있는데 사용자는 본 적이 없는 죽은 UI.
     바깥 배치 재도입 ❌ (overflow 를 걷어 스크롤을 포기하는 해법도 ❌). */
  position: absolute;
  /* `viewport-fit=cover` 뒤로 드로어는 화면 «맨 위»에서 시작한다 — 부모의 `padding-top` 은
     absolute 자식을 안 밀어주므로 인셋을 이 값이 직접 진다. 안 더하면 ✕ 가 상태바(시계·배터리)
     밑에 깔려 보이지도 눌리지도 않는다 (2026-09-15 실측 ink 16~52 · 인셋 59). 인셋 0 인 자리는 불변 */
  top: calc(16px + env(safe-area-inset-top, 0px));
  right: 12px;
  z-index: 1;
  width: 36px;
  height: 36px;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: white;
  border: none;
  cursor: pointer;
  color: var(--text-secondary);
  box-shadow: var(--shadow-md);
}

.sidebar-close-mobile svg {
  width: 20px;
  height: 20px;
  stroke-width: 2;
}

/* 아래 4종 = 닫기/취소 ✕ 의 나머지 컨텍스트. 아이콘은 `icons :: close` 공통, 크기·굵기는 구 인라인 값
   그대로 옮긴 것(시각 변화 0). 모달 헤더 ✕ 와 달리 각자 다른 컨트롤이라 `.modal-close` 값으로 강제 ❌ */
.invite-link-modal-close svg { width: 15px; height: 15px; stroke-width: 2.2; }
.postcode-close-btn svg { width: 14px; height: 14px; stroke-width: 2; }
.detail-reserved-card-cancel svg { width: 14px; height: 14px; stroke-width: 2; }
.btn-icon-sm svg { width: 15px; height: 15px; stroke-width: 2; }

/* Sidebar Overlay (mobile) */
.sidebar-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: calc(var(--z-sticky) - 1);
  opacity: 0;
  visibility: hidden;
  transition: all var(--transition-base);
}

.sidebar-overlay.active {
  opacity: 1;
  visibility: visible;
}

/* ========================================
   101. TOP HEADER (Sidebar Layout) — 폐지 (사용처 0, 2026-08-09 죽은 CSS 제거.
   페이지 헤더 채널 = 사이드바 active·<title>·mobile-header-title 3분담,
   project-frontend-guide 「페이지 헤더 — 콘텐츠 영역 h1 금지」)
   ======================================== */

/* ========================================
   102. MAIN CONTENT (Sidebar Layout)
   ======================================== */
/* `margin-left` 재선언 ❌ — 이 절은 **배경만** 소유한다 (2026-08-16 UI 감사 실측).
   좌측 오프셋 SSOT = §41 `.main-content`(위) + `@media (max-width:1024px)` 드로어 보정 한 쌍.
   여기서 `margin-left: var(--sidebar-width)` 를 다시 선언하면 **미디어쿼리 밖 + 소스 순서 뒤**라
   그 1024px 보정을 통째로 덮어, 사이드바가 `translateX(-100%)` 로 화면 밖인 **769~1024px 구간에서
   본문이 240px 를 빈 채로 예약**한다(아래 103절이 ≤768px 만 되돌려 폰에서만 정상이었다).
   실측: KPI 5칸 합계가 769/800/900/1024px 전부에서 「뷰포트 − 242px」로 상수 — 마진이 한 번도
   해제되지 않았다. 그 눌림이 12px 라벨을 2줄로 꺾은 직접 원인(769px 에서 5칸 중 3개).
   margin-left 재도입 ❌ · 「§102 에 오프셋이 없다」 감사 재지적 ❌ (§41 이 소유) */
.main-content {
  /* 중립 배경(slate-50) — emerald 는 신호 위치(CTA·dot·active)에만 노출 (project-frontend-guide
     「Carded surface — color rail 일반 금지」·D.MONO CONCIERGE). 구 초록 viewport 고정 그라데이션 워시는
     ambient emerald 라 신호-위치 원칙과 충돌 → 폐지(2026-06-15). body bg 와 동일 톤이라 짧은 페이지
     하단 잔여 영역도 자연스러운 중립으로 이어진다. */
  background: var(--bg-secondary);
}

/* ========================================
   103. SIDEBAR RESPONSIVE (드로어 모드 = ≤1024px)
   ======================================== */
/* 경계 = 1024 (§화면 전환 「.sidebar 는 드로어 모드(≤1024px)」 박제와 일치, 2026-08-21 UI 감사).
   구 768 이던 동안 769~1024 는 **햄버거(≤1024 노출)를 눌러도 드로어가 안 열렸다** — JS 는 `.open` 을
   토글하는데 그 규칙이 ≤768 안에만 있어, 오버레이 + ScrollLock 만 걸리고 사이드바는 화면 밖에 남았다.
   그 구간엔 바텀네비·모바일 헤더(≤768 전용)도 없어 사이드바 전용 메뉴(이용권 관리·설문·사범 관리·
   지각 설정·설정) 도달 경로가 0 이 되고 스크롤까지 잠겼다(900px 실측). 768 로 되돌리기 ❌ */
@media (max-width: 1024px) {
  .sidebar {
    transform: translateX(-100%);
    z-index: var(--z-overlay);
  }

  /* 그림자는 열린 드로어에만 — 닫힘(translateX(-100%)) 상태에 두면 blur 20px 가
     뷰포트 왼쪽 가장자리로 상시 스며들고, 페이지 전환 시 old/new 스냅샷 두 장이
     겹쳐 2배 진한 회색 세로 띠로 보인다 (2026-07-17 모바일 전환 회색 띠 버그) */
  .sidebar.open {
    transform: translateX(0);
    box-shadow: 4px 0 20px rgba(0, 0, 0, 0.1);
  }

  /* 드로어 모드 = 터치 입력 → 컨트롤 3종 탭 판정 44px (2026-08-21 UI 감사).
     수단은 `::after` 오버레이 — 셋 다 보이는 면(배경·radius)을 가진 버튼이라 padding 을 키우면
     모양 자체가 커진다(§히트박스 확대 idiom 의 「보이는 상자 있음」 갈래, `.toast-close::after` 선례).
     보이는 32/36/40px·아이콘 불변, 판정만 44. 데스크톱(>1024)은 마우스라 미적용 — 확장 ❌ */
  /* `.sidebar-close-mobile` 은 이미 `position: absolute`(top 20 / right -44) 라 여기서 relative 를
     주면 **흐름으로 떨어져 사이드바 맨 아래로 내려간다**(2026-08-21 실측 회귀). positioned 컨텍스트가
     없는 둘만 relative — ::after 는 세 버튼 모두에 건다. */
  .sidebar-power-btn,
  .mobile-menu-btn { position: relative; }
  /* `.sidebar-close-mobile` 도 포함 — 드로어 **안**으로 옮겨(2026-08-21 B안) 더 이상 overflow 에
     잘리지 않으므로 오버레이가 실제 탭 판정을 넓힌다. 구 바깥 배치 때는 넓혀도 안 닿는 죽은 코드였다. */
  .sidebar-power-btn::after,
  .sidebar-close-mobile::after,
  .mobile-menu-btn::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 44px;
    height: 44px;
  }

  .sidebar-close-mobile {
    display: flex;
  }

  .sidebar-overlay.active {
    display: block;
  }

  .main-content {
    margin-left: 0;
  }

}

/* ╔══════════════════════════════════════════╗
   ║  PREMIUM REDESIGN - Visual Upgrade      ║
   ║  글래스모피즘 · 마이크로인터랙션 · 다크모드  ║
   ╚══════════════════════════════════════════╝ */

/* ========================================
   R1. ANIMATION KEYFRAMES
   ======================================== */

@keyframes toastProgress {
  from { width: 100%; }
  to { width: 0%; }
}

/* ========================================
   R2. ANIMATION UTILITY CLASSES
   ======================================== */
/* 진입 페이드업 = MOTION SYSTEM `motionEnterUp` 공유 (ADR 0104 LV1 — 구 전용 `fadeInUp` 삭제).
   duration 은 슬롯별 유지 — .animate-in 0.4s / .motion-enter 0.5s (0.5s 로 맞추면 느려짐 = 강도 상향 ❌)

   ⚠ fill-mode 는 **backwards 고정** — `.motion-enter`(2026-07-22) 와 같은 처방·같은 이유.
   구 상태 `forwards` + base `opacity:0; transform:translateY(10px)` 는 애니가 끝난 «뒤에도»
   transform(identity matrix)을 영구 잔류시켜 `.animate-in` 블록 전부를 **영구 stacking context** 로 만든다.
   그러면 그 안의 `z-index` 가 블록 «안»에 갇혀, 뒤에 오는 형제 `.animate-in` 이 위로 덮는다 —
   `/join` 에서 「어떻게 알고 오셨어요?」 셀렉트 드롭다운(z 100) 위로 footer 「이미 등록된 관원이신가요?」가
   그대로 비쳐 보였다 (2026-09-18 실측: 1440×900 · `elementFromPoint` 로 확인).
   `backwards` 는 delay 구간만 from-상태로 채우고 종료 후 자연 상태(opacity 1 · transform none)로 돌아오므로
   보이는 모션은 동일하고 잔류만 사라진다 — base `opacity:0`·`transform` 선언은 그래서 함께 뺀다
   (남겨두면 종료 후 그 값으로 «되돌아가» 화면이 비거나 10px 어긋난다).
   `forwards` + base 선언 재도입 ❌ */
.animate-in {
  animation: motionEnterUp var(--duration-medium) var(--ease-out-quint) backwards; /* 곡선 quint 동기 (ADR 0542) */
}

/* 제목 마스크 리빌 — `.hero-ln` = «창» 원자 (ADR 0439, auth 5화면 + 온보딩 첫 화면).
   겉이 창(overflow hidden), 안쪽 span 이 그 안에서 밀려 올라온다. `.animate-in`(블록 페이드)과
   **역할이 다르다** — 페이드는 「나타남」, 마스크는 「올라옴」이라 제목에만 쓴다.
   ⚠️ **landing.css 에 같은 이름·같은 값이 있다** (랜딩은 style.css 를 로드하지 않는다 — `.icon-cta-chev` 선례).
   값을 바꾸면 **두 시트 동시** · 두 번째 마스크 원자(`.feat-ln` 등) 신설 ❌ (같은 목적 모션 = 공용 어휘 하나, ADR 0104).
   `signup-complete` 는 두 시트를 다 로드하므로 값이 어긋나면 그 화면만 갈린다. */
@keyframes motionLineUp {
  from { transform: translateY(108%); }
  to   { transform: none; }
}
.hero-ln { display: block; overflow: hidden; }
.hero-ln > span { display: block; }
/* 제목은 워드마크 «다음» 계단 — 지연은 글로벌 --stagger-delay 를 그대로 쓴다(새 상수 ❌) */
.auth-hero-title .hero-ln > span,
.qs-w-title .hero-ln > span {
  animation: motionLineUp 0.66s var(--ease-out-expo) both;
  animation-delay: calc(var(--stagger-delay) * 2);
}
/* 부제는 제목 «다음» 계단 — [[0439]] 표의 「부제 ×3」을 이행하는 한 선언 (2026-09-13 UI 감사).
   `.stagger` 는 «직계 자식»만 계단을 놓는데 부제는 `.auth-hero` «안»이라 안 닿는다 — 그래서 0439 가
   인라인 delay 를 「인지된 대가」로 등재했던 자리다(등재 당시 6곳 → 15곳으로 불었다).
   위 액션 블록과 «같은 처방»으로 수렴한다: 값이 15곳 전부 동일하므로 선언 하나가 SSOT 다.
   온보딩 첫 화면 부제 `.qs-w-sub` 도 같은 ×3 이라 같은 선언에 담는다(2026-09-13 fast-analyze — 마지막 인라인 흡수).
   ⚠ 인라인 `style="animation-delay"` 재도입 ❌ — 화면이 하나 늘 때마다 값을 다시 적게 된다. */
.auth-hero-sub.animate-in,
.qs-w-sub.animate-in {
  animation-delay: calc(var(--stagger-delay) * 3);
}
/* 온보딩 체험 카드 = 환영 계단(anchor ×1 → hero ×2 → choices ×3) «뒤». `#setupHub` 는 직계 자식이 둘뿐이라
   `.stagger` 를 못 준다(quickstart 주석) — 부제와 같은 이유로 값은 템플릿 인라인이 아니라 여기서 소유한다 */
.qs-demo-card.animate-in {
  animation-delay: calc(var(--stagger-delay) * 4);
}

/* 히어로 «뒤» 블록은 전부 부제 «다음» 계단에 선다 — [[0439]] 표의 「액션 ×3~」을 실제로 이행하는 선언
   (2026-09-09 UI 감사 사용자 확정 C안 · 시안 `auth-entry-order-0439` → 2026-09-13 fast-analyze 로 «계단 +2 밀기» 일반화).
   구 상태: `.stagger` 계단이 `nth-child` 기반이라 히어로 다음 블록이 ×2 로 떨어져 **제목 마스크(×2)와 동시에** 뜨고
   부제(×3)를 추월했다 — 0439 가 「지금은 제목과 액션이 동시에 떴다」로 지목해 없애려던 바로 그 상태.
   구 처방 `:nth-child(2):is(form, .social-login-group, .invite-action-group, .sys-approve-group)` → ×4 는
   «두 번째 자식이 액션일 때»만 잡아, 앞에 안내·대상 카드가 서는 화면을 남겼다(2026-09-13 감사):
   member-link OAuth 진입 `.ml-acct-line` ×2 · 폼 ×3(부제와 동시) · login 오류 `.auth-notice` ×2 ·
   recover `.auth-target` ×2 · signup-complete 푸터 ×3. 목록에 클래스를 더하는 안은 «두 번째가 아닌» 자식을 여전히 못 잡는다.

   ⚠ 계단을 통째로 +2 민다 — 블록끼리의 순서는 그대로 두고 부제 뒤로만 옮기므로 «당겨지는» 블록이 없다
      (구 조건 ① 이 경계한 `invitation/accept` 액션 그룹 앞당김이 구조적으로 안 생긴다 — 오히려 ×2 늦어진다).
   ⚠ `:has(> .auth-hero)` = auth 히어로가 여는 화면만. `.stagger` 는 앱 전역 20곳이 쓴다.
   범위는 기본 계단과 같은 1~10번째. `:last-child`·클래스 목록으로 잡지 ❌ · 인라인 `style="animation-delay"` 로 흩뿌리기 ❌.
   위 revisit `!important` 가드는 이 선언보다 뒤·`!important` 라 그대로 이긴다(뒤로가기 = 즉시 완료). */
.stagger:has(> .auth-hero) > :nth-child(2)  { animation-delay: calc(var(--stagger-delay) * 4); }
.stagger:has(> .auth-hero) > :nth-child(3)  { animation-delay: calc(var(--stagger-delay) * 5); }
.stagger:has(> .auth-hero) > :nth-child(4)  { animation-delay: calc(var(--stagger-delay) * 6); }
.stagger:has(> .auth-hero) > :nth-child(5)  { animation-delay: calc(var(--stagger-delay) * 7); }
.stagger:has(> .auth-hero) > :nth-child(6)  { animation-delay: calc(var(--stagger-delay) * 8); }
.stagger:has(> .auth-hero) > :nth-child(7)  { animation-delay: calc(var(--stagger-delay) * 9); }
.stagger:has(> .auth-hero) > :nth-child(8)  { animation-delay: calc(var(--stagger-delay) * 10); }
.stagger:has(> .auth-hero) > :nth-child(9)  { animation-delay: calc(var(--stagger-delay) * 11); }
.stagger:has(> .auth-hero) > :nth-child(10) { animation-delay: calc(var(--stagger-delay) * 12); }

/* 진입 애니 = 첫 방문 화면 전용 (§모션 revisit 게이트) — 뒤로가기·모바일 슬라이드 VT·prerender 도착은
   즉시 완료(0.01ms)로 "새로고침 반짝"·이중 모션 차단. 판정 SSOT = fragments/vt-direction. */
html[data-vt-revisit] .animate-in,
html[data-vt-revisit] .stagger > *,
html[data-vt-revisit] .hero-ln > span {
  /* !important = 위 진입 순서 선언들(`.auth-hero-title .hero-ln > span` ×2 · `.auth-hero-sub` ×3 ·
     `.stagger:has(> .auth-hero) > :nth-child(n)` ×(n+2))의 지연만 살아남던 것을 누른다 —
     아래 reduced-motion 블록과 같은 이유·같은 어휘. 구 근거였던 「인라인 delay 6곳」은
     2026-09-13 UI 감사로 전부 CSS 선언에 흡수돼 사라졌다(인라인 재도입 ❌). */
  animation-duration: 0.01ms !important;
  animation-delay: 0ms !important;
}

/* Stagger delays for children */
.stagger > *:nth-child(1) { animation-delay: calc(var(--stagger-delay) * 1); }
.stagger > *:nth-child(2) { animation-delay: calc(var(--stagger-delay) * 2); }
.stagger > *:nth-child(3) { animation-delay: calc(var(--stagger-delay) * 3); }
.stagger > *:nth-child(4) { animation-delay: calc(var(--stagger-delay) * 4); }
.stagger > *:nth-child(5) { animation-delay: calc(var(--stagger-delay) * 5); }
.stagger > *:nth-child(6) { animation-delay: calc(var(--stagger-delay) * 6); }
.stagger > *:nth-child(7) { animation-delay: calc(var(--stagger-delay) * 7); }
.stagger > *:nth-child(8) { animation-delay: calc(var(--stagger-delay) * 8); }
.stagger > *:nth-child(9) { animation-delay: calc(var(--stagger-delay) * 9); }
.stagger > *:nth-child(10) { animation-delay: calc(var(--stagger-delay) * 10); }

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .animate-in {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  /* 마스크 리빌도 정지 — 창 안 글자를 제자리로 (ADR 0439) */
  .hero-ln > span {
    animation: none !important;
    transform: none !important;
  }
  .stagger > * { animation-delay: 0s !important; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ========================================
   R3. GLASS MORPHISM UTILITIES
   ======================================== */




/* ========================================
   R4. ENHANCED SIDEBAR (Glassmorphism)
   ======================================== */
/* 구 R4 .sidebar/.sidebar-header/.sidebar-footer 오버라이드 삭제 (2026-08-31) — 최종값은 §100
   통합 선언이 그대로 소유(transform transition 보존 포함). 여기 재분산 ❌ */

/* R5. ENHANCED TOP HEADER — 폐지 (사용처 0 .top-header 잔재, §101 과 함께 2026-08-09 제거) */

/* ========================================
   R6. ENHANCED CARDS (D. MONO CONCIERGE 통합 2026-05-15)
   .card-footer 회색 배경 override 제거 — D 는 흰 footer + hairline divider.
   ======================================== */

/* Stat cards — D. MONO CONCIERGE: hairline + accent. lift/shadow 제거 */






/* ========================================
   R8. ENHANCED TABLES (D. MONO CONCIERGE 통합 2026-05-15)
   기존 그라데이션 th + emerald row hover override 제거 — 섹션 28(.table)의 D 정의가 단일 진실.
   ======================================== */
.table tbody tr {
  transition: background-color 0.15s ease;
  position: relative;
}

/* ========================================
   R9. ENHANCED FORMS (Focus Glow)
   ======================================== */
.form-input:focus,
.form-textarea:focus {
  border-color: var(--accent-primary);
  box-shadow: 0 0 0 4px rgba(5, 150, 105, 0.12), 0 2px 8px rgba(5, 150, 105, 0.08);
}

.form-input.shake-invalid,
.form-textarea.shake-invalid,
.form-select.shake-invalid {
  animation: shakeInvalid 0.4s ease;
}

@keyframes shakeInvalid {
  0%, 100% { transform: translateX(0); }
  15% { transform: translateX(-4px); }
  30% { transform: translateX(4px); }
  45% { transform: translateX(-3px); }
  60% { transform: translateX(3px); }
  75% { transform: translateX(-2px); }
  90% { transform: translateX(2px); }
}

/* ========================================
   R10. ENHANCED MODALS (Glass + Anim)
   ======================================== */
.modal-overlay .modal {
  background: var(--card-bg);
  transition: transform 0.35s var(--ease-spring), opacity 0.35s ease;
}

.modal-overlay.show .modal {
  transform: scale(1) translateY(0);
}

/* 2026-05-27: project-frontend-guide §모달 표준 박제 — header/footer 흰 bg 고정.
   이전 emerald-50 → #fff 다색 gradient 헤더 / #fafafa footer 어휘 폐지. */
.modal-overlay .modal-header {
  background: var(--card-bg);
  border-bottom-color: var(--card-border);
}

.modal-overlay .modal-footer {
  background: var(--card-bg);
  border-top-color: var(--card-border);
}

/* ========================================
   R11. ENHANCED TOASTS (Progress Bar)
   ======================================== */
/* left: 4px 는 2026-05 폐지된 좌측 color rail 의 잔재였다 — 바가 면 왼쪽 끝에 닿지 않아
   어긋나 보였다. 0 으로 정렬 (2026-08-09 C안).
   색은 중립 slate — 상태 신호는 dot·문구가 소유하고 이 바는 "남은 시간"만 말한다.
   모바일 3px 보강도 제거: 상태색일 때만 필요했던 가시성 보강이라, 중립 바에서는
   전폭 divider 처럼 읽힌다. 데스크톱·모바일 2px 단일. */
.toast-progress {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--color-slate-200);
  animation: toastProgress 10s linear forwards;
}

/* ========================================
   R12. ENHANCED BADGES
   ======================================== */

/* ========================================
   R13. ERROR PAGES - Pastel Number Card
   ======================================== */
.error-page {
  min-height: 100vh;
  min-height: 100dvh; /* vh fallback + dvh override — 헛스크롤 제거 (body 규칙 참고) */
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-secondary); /* 중립(slate-50). 구 초록 그라데이션 폐지(2026-06-15, 전 영역 통일) */
  /* 동값 토큰 사용(32px) — :root 밖 raw rem 리터럴은 이 파일에서 여기 하나뿐이었다.
     2026-08-22 스윕이 font-size(0.9rem→14px)만 훑어 남은 잔재 (2026-08-23 UI 감사). 리터럴 복귀 ❌ */
  padding: var(--space-8);
}

/* 2026-05-27: static error page 카드 radius 24px + box-shadow 폐지 —
   D.MONO 박제 (radius 14px + 1px hairline + flat) 통일. */
.error-page .error-card {
  background: var(--card-bg);
  border: 1px solid #eaecef;
  border-radius: 14px;
  padding: 56px 44px 44px;
  max-width: 440px;
  width: 100%;
  text-align: center;
  /* `position: relative` + `overflow: hidden` 제거 (2026-08-23 UI 감사 사용자 확정 A안):
     둘 다 아래 주석이 폐지를 명시한 `::before` 4px 막대를 받치던 잔재라 받칠 대상이 없었다.
     `overflow: hidden` 은 `.card` 에서 겪은 「자식이 조용히 잘림」과 같은 성질이기도 하다.
     ::before 막대와 함께 재도입 ❌ */
}

/* 2026-05-26: .error-page .error-card::before 4px solid 상단 막대 (404/403/500/generic
   variant 색 분기) 폐지 — project-frontend-guide §「Carded surface — color rail 일반 금지」.
   에러 페이지 시각 식별은 .error-number(96px 900) + 텍스트 어휘로 충분. */

/* 2026-05-27: error 페이지 hero 96/80px gradient text 폐지.
   frontend-guide §0 AI slop test 1순위 "그라데이션 텍스트" 박제 — solid slate 로 통일.
   페이지 식별은 .error-title("페이지를 찾을 수 없어요") 텍스트가 담당,
   위계는 96px / weight 900 / letter-spacing -3px 자체로 충분. */
.error-number {
  /* 900 = weight 사다리(상한 800) 예외 — SSOT §폰트 최소 크기 절에 등재 (2026-08-22 UI 감사).
     static/css 전체 유일한 900. 800 강등 ❌ — 96px 숫자가 제목과 같은 굵기가 되어 hero 가 죽는다 */
  font-size: 96px;
  font-weight: 900;
  line-height: 1;
  /* 색 선언 제거 (2026-08-23 UI 감사 사용자 확정 A안) — 템플릿 6종이 **전부**
     `error-number-generic`(slate-400)을 병기해 이 slate-300 은 도달한 적이 없다.
     색 소유자 = `.error-number-generic` 단일. 여기에 색 재선언 ❌ ·
     **`.error-number-generic` 클래스 자체는 유지**(2026-08-21 「generic 단일 수렴,
     코드별 셀렉터 재도입 ❌」 재개봉 방지) */
  margin-bottom: 4px;
  letter-spacing: -3px;
}

/* .error-text(80px 문자 hero) 삭제 (2026-08-12 A안) — 루트 폴백이 숫자 hero 로 통일되며
   문자 hero 사용처 0. 문자 hero 어휘 재도입 ❌ */

/* 코드별 색 구분 폐지 (2026-07-28 UI 감사) — 96px 는 화면 최대 면적인데 404 만 emerald 라
   의미를 전달하지 않는 색이 가장 크게 깔렸다 (404 는 성공·진행이 아니다). 심각도 구분은
   .error-title 텍스트가 담당. frontend-guide §17 「error surface 중립 유지, emerald 는 신호 위치에만」. */
/* 코드별 색 구분은 2026-07-28 UI 감사로 폐지 — 넷 다 같은 선언이라 클래스 이름만 「코드마다 다르다」고
   약속하는 잔여 훅이었다(템플릿도 409·400 은 이미 generic 을 쓰던 혼용 상태). 2026-08-21 UI 감사에서
   generic 단일로 수렴 — 렌더 변화 0. 코드별 셀렉터 재도입 ❌ */
.error-number-generic {
  color: var(--color-slate-400);
}

/* 마이크로 라벨 박제 = 12px / 800 / slate-500 (§디자인 톤).
   구 `letter-spacing: 3px` 는 고정 px 라 크기 변경 시 비율이 깨진다 — em 으로 전환.
   자간 0.08em 유지 = 라벨 값이 영문 대문자(ACCESS DENIED·CONFLICT·SERVER ERROR)라
   한글 보정(0.04em) 대상이 아니다 — 2026-08-02 전면 수렴에서 의도적으로 제외. 0.04em 으로 낮추지 ❌ */
.error-page .error-sub {
  font-size: 12px;
  font-weight: 800;
  color: var(--color-slate-500);
  letter-spacing: 0.08em;
  margin-bottom: 20px;
}

/* 단일 hero 관문 어휘 통일 — .auth-hero-title(22/800/-0.02em)·.invite-title 과 같은 위계 */
.error-title {
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  margin-bottom: 10px;
}

.error-message {
  /* 14px — 구 0.9rem(=14.4px) 은 style.css 전체 유일한 rem font-size 였다 (2026-08-22 UI 감사).
     토큰 체계가 px 단일이라 어떤 체급과도 안 맞았다. 렌더 차 0.4px */
  font-size: 14px;
  color: var(--text-secondary);
  line-height: 1.7;
  margin-bottom: 28px;
}

.error-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}

/* 좁은 모바일에서 버튼 가로 배치 시 터치 타겟 좁아짐 방지 */
@media (max-width: 480px) {
  /* 좁은 폭 여유 — `.auth-container`(≤480px = 24px 16px) 선례와 같은 문법 (2026-08-19 UI 감사).
     구 56/44/44 고정은 실측으로 본문 폭을 360px→206px · 320px→190px 로 조여
     320px 에서 제목(`.error-title`)이 두 줄로 꺾였다. 44px 복원 ❌
     ※ 인용한 24/16 은 «선언» 기준 — 그 규칙(0,1,0)은 `[data-page="auth"] .auth-container`(0,2,0)에 져
     실제로 렌더된 적이 없다. 빌려온 것은 문법(좁은 폭에서 여유를 준다)이지 값이 아니다
     (2026-09-04 UI 감사 — 「매칭 0인 규칙을 근거로 읽히는 주석」은 이 파일이 회귀 원인으로 등재한 함정). */
  .error-page .error-card { padding: 40px 22px 28px; }
  .error-actions { flex-direction: column; }
  .error-actions .btn { width: 100%; }
}

.error-footer {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--border-light);
}

/* 푸터 문의 안내 = 보조 각주 토큰 12.5/400/slate-500/1.55 (§타이포).
   fragments/error 가 렌더하는 건 <p> 인데 규칙이 `a` 기준뿐이라 body 기본값(--text-base·--text-primary)을
   상속해 바로 위 .error-message(0.9rem·slate-600)보다 크고 진했다 — 각주가 본문을 이기던 위계 역전.
   구 `.error-footer a` 규칙은 사용처 0(푸터는 텍스트 <p> 한 줄)이라 제거. */
.error-footer p {
  margin: 0;
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.55;
  color: var(--color-slate-500);
}

/* 에러 ID — 예상치 못한 500/DataIntegrity 에러에서만 노출 (문의용 추적 번호) */
.error-id {
  margin-top: 20px;
  padding: 14px 16px;
  background: var(--bg-secondary);
  border: 1px solid var(--border-light);
  /* 카드 내부 타일도 radius 14 단일 (§디자인 톤 — 구 8/10/12 다단 재도입 ❌).
     .error-card 가 radius 14 라 안쪽 타일만 10 이면 모서리 곡률이 갈린다 */
  border-radius: 14px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  font-size: 13px;
  color: var(--text-secondary);
}

.error-id-label {
  font-weight: 800;
  color: var(--color-slate-500);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* 추적번호 칩 — 중립 slate. error 는 운영 surface 라 emerald 는 신호 위치에만(§17),
   게다가 실패 화면 식별자에 success 색이 붙으면 의미가 충돌한다. */
.error-id-value {
  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
  font-size: 13px;
  font-weight: 700;
  color: var(--color-slate-700);
  background: var(--color-slate-100);
  padding: 3px 8px;
  border-radius: 6px;
  letter-spacing: 0.5px;
  user-select: all;
}

/* 오류 ID 사용법을 알리는 유일한 문장 = 「읽어야 하는 텍스트」라 slate-500 승격
   (§slate-400 승격 기준 — 유지 축은 placeholder·disabled 2행뿐). 같은 블록 .error-id-label 과 동값 */
.error-id-hint {
  /* 문장이라 각주/힌트 토큰 12.5/400/slate-500/1.55 (§타이포 — 12px·13px 즉석 발명 ❌).
     12px 는 같은 블록 .error-id-label(마이크로 라벨)의 값이지 산문의 값이 아니다 */
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.55;
  color: var(--color-slate-500);
  flex-basis: 100%;
}

/* ========================================
   R13.5 LINK SHARE MODAL — 토큰/링크 공유 모달 공통 컴포넌트
   ----------------------------------------
   사용처: 사범 초대 링크(admin/admins.html), 관장 OAuth 복구(system/tenants/detail.html)
   기타 미래의 "토큰 발급 → 링크 표시 → 복사 → 닫기" 플로우 일체에 재사용.
   2026-07-21 B안 개편 (사용자 컨펌): 표준 .modal 셸 미러(header 56px + title 15/800 + close ✕)
   + 링크 1줄 ellipsis 캡슐 + full-width 복사 primary 히어로 + 각주.
   footer 없음 — 모달 표준 §7 "성공 유형 = 단일 확인 / footer 없음" 어휘.
   ======================================== */
/* 무대 = 보이는 영역 (common.js §1.4 OverlayStage) — 복사용 링크 input 이 포커스를 받는다 */
.invite-link-modal-overlay {
  position: fixed;
  top: var(--vvt, 0px); left: 0; right: 0; bottom: auto;
  height: var(--vvh, 100vh);
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  opacity: 0;
  transition: opacity 0.2s;
  pointer-events: none;
}
.invite-link-modal-overlay.active {
  opacity: 1;
  pointer-events: auto;
}
/* 스프링 팝인 (2026-07-16 모션 시스템) — 표준 .modal 과 동일 어휘 */
.invite-link-modal-overlay .invite-link-modal {
  transform: scale(0.93) translateY(24px);
  transition: transform 0.42s var(--ease-spring);
}
.invite-link-modal-overlay.active .invite-link-modal {
  transform: scale(1) translateY(0);
}
.invite-link-modal {
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 14px;
  box-shadow: 0 20px 60px -20px rgba(15, 23, 42, 0.25);
  width: 90%;
  max-width: 440px;
  overflow: hidden;
}
.invite-link-modal-header {
  height: 56px;
  padding: 0 22px;
  background: #fff;
  border-bottom: 1px solid var(--border-hairline);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.invite-link-modal-title {
  font-size: 15px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--color-slate-900);
}
.invite-link-modal-close {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-slate-400);
  background: transparent;
  border: none;
  cursor: pointer;
  transition: all 0.18s ease;
  flex-shrink: 0;
}
@media (hover: hover) {
  .invite-link-modal-close:hover {
    background: var(--color-slate-100);
    color: var(--color-slate-700);
  }
}
.invite-link-modal-body {
  padding: 20px 22px 22px;
}
.invite-link-box {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--bg-secondary);
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  padding: 11px 14px;
  min-width: 0;
}
/* 크기 = 쓰는 쪽 CSS 소유 (2026-08-21 아이콘 프래그먼트 승격). 지우면 width:auto 로 아이콘 소실 ❌ */
.invite-link-box-icon {
  width: 14px;
  height: 14px;
  color: var(--color-slate-400);
  flex-shrink: 0;
}
/* 토큰 꼬리는 CSS ellipsis 로만 생략 — textContent 는 전체 URL 유지 (복사 원본) */
.invite-link-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12.5px;
  color: var(--text-secondary);
  font-family: 'SF Mono', Monaco, monospace;
}
/* 복사 히어로 CTA — .btn.btn-primary 베이스에 폭·체급만 확장 */
.invite-link-copy-btn {
  width: 100%;
  min-height: 44px;
  margin-top: 12px;
  font-size: 14px;
}
/* 각주 — 보조 각주/힌트 토큰 (12.5/400/slate-500, strong 700 slate-700) */
.invite-link-note {
  margin-top: 12px;
  font-size: 12.5px;
  font-weight: 400;
  /* slate-500 — 각주 토큰 값이자 §slate-400 승격 기준 「그 자리의 유일한 신호」
     (만료 60분·1회용·번호 주인은 이 줄에만 있다). 400 복귀 ❌ */
  color: var(--color-slate-500);
  line-height: 1.55;
}
.invite-link-note strong {
  font-weight: 700;
  color: var(--color-slate-700);
}

@media (max-width: 768px) {
  .invite-link-modal {
    max-width: calc(100vw - 32px);
    margin: 16px;
  }
}

/* ========================================
   R14. LANDING PAGE ENHANCEMENTS
   ======================================== */


/* 2026-05-27: .gradient-text / .gradient-text-gold 유틸 클래스 폐지.
   templates 0건 사용 dead, frontend-guide §0 AI slop test 1순위 박제와도 충돌. */

/* 2026-08-15: R15 「AUTH PAGE ENHANCEMENTS」 폐지 — 유일 규칙이던 `.auth-right .geo-pattern`
   (2축 radial-gradient 장식 격자)은 부모 `.auth-right` 가 §23 구 2단 레이아웃과 함께 삭제돼
   도달 불가였고, 장식용 배경 격자 자체가 frontend-guide §0 금지 목록 항목이다. */

/* ========================================
   R16. TENANT SWITCHER (Sidebar)
   --------------------------------------------------------------------
   Variant A (2026-05-25): 헤더 = 스위처 트리거 통합 (Linear/Vercel 패턴).
   .sidebar-tenant-switcher 는 admin 사이드바의 헤더 역할을 그대로 흡수.
   홈 진입은 사이드바 nav 첫 항목이 담당.
   ======================================== */
.sidebar-tenant-switcher {
  position: relative;
}

/* 갈 데가 없으면 트리거를 «표시»로 강등한다 (2026-08-26 사용자 확정 A안 ·
   시안 `role-shell-switch-consistency`). 도장 1곳 사범은 「도장 전환」을 눌러도 지금 있는 도장
   한 줄만 만났다 — 관리 셸의 「1곳이어도 상시」는 «＋ 도장 추가»가 근거인데(ADR 0239) 도장 추가는
   관장 전용이라 사범에겐 그 근거가 없다. 도장명·자격은 그대로 읽히고 죽은 컨트롤만 사라진다.
   지오메트리는 `.tenant-switcher-trigger` 미러(마크·gap·padding) — hover/focus 는 없다. */
.sidebar-tenant-static {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px;
}

/* 라벨 줄이 없으므로 도장명이 트리거 이름 자리를 그대로 받는다 (15px = 트리거 이름과 동값) */
.sidebar-tenant-static-name {
  flex: 1;
  min-width: 0;
  font-size: 15px;
  font-weight: var(--font-extrabold);
  letter-spacing: -0.015em;
  color: var(--color-slate-900);
  line-height: 1.3;
  word-break: keep-all;
}

.tenant-switcher-trigger {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px;
  background: transparent;
  border: none;
  cursor: pointer;
  transition: background var(--transition-fast);
  font-family: inherit;
  text-align: left;
}

.tenant-switcher-trigger:focus-visible {
  background: var(--bg-secondary);
  outline: none;
}
@media (hover: hover) {
  .tenant-switcher-trigger:hover {
    background: var(--bg-secondary);
    outline: none;
  }
}

.tenant-switcher-trigger-img,
.tenant-switcher-trigger-initial {
  width: 36px;
  height: 36px;
  border-radius: 9px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  object-fit: cover;
}

.tenant-switcher-trigger-img {
  background: var(--bg-tertiary);
}

/* 자식은 `fragments/icons :: building2` SVG 하나뿐 (텍스트 노드 0) — 구 「이름 첫글자」 타이포
   3선언(font-size 14 · extrabold · -0.02em)은 §6 「첫글자 ❌ → SVG」 전환 뒤 매칭 0 이라 걷었다.
   `color: #fff` 는 SVG `currentColor` 소유자라 유지. 첫글자 아바타 재도입 ❌ */
.tenant-switcher-trigger-initial {
  background: linear-gradient(135deg, #059669, #047857);
  color: #fff;
}
/* 폴백 아이콘 크기 = CSS 소유 (마크업이 fragments/icons 참조라 인라인 width/height 없음) */
.tenant-switcher-trigger-initial svg {
  width: 18px;
  height: 18px;
}

.tenant-switcher-trigger-stack {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.tenant-switcher-trigger-label {
  font-size: 12px;
  font-weight: var(--font-extrabold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  /* `--text-muted` 는 레포 전체에 정의가 0인 미정의 토큰이라 이 선언이 통째로 무효가 되고
     body 까지 상속이 올라가 **순검정 #000** 으로 렌더됐다 (2026-08-23 UI 감사 R 실측).
     순검정은 D.MONO 팔레트에 없는 값(팔레트 최댓값 = slate-900). 마이크로 라벨 토큰
     (12px/800/0.04em)의 색은 slate-500 — `.list-card-head`·`.td-stat-k` 와 같은 벌.
     `--text-muted` 재도입 ❌ */
  color: var(--color-slate-500);
  line-height: 1.2;
}

.tenant-switcher-trigger-name {
  font-size: 15px;
  font-weight: var(--font-extrabold);
  letter-spacing: -0.015em;
  color: var(--color-slate-900);
  overflow: hidden;
  /* 도장명 너무 길지 않으면 다 보이게 — 최대 2줄 클램프 (sidebar-logo-text 와 동일 어휘) */
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  word-break: keep-all;
}

.tenant-switcher-arrow {
  /* 인라인 width/height 18 을 이관 (2026-08-22 UI 감사 — ADR 0374 「크기는 쓰는 쪽 CSS 가 소유」).
     굵기도 같은 조항 대상이라 함께 이관 (2026-08-22 UI 감사 2차 — 구 상태는 `<path>` 에
     stroke-width="2" 만 남은 반쪽 이관이었다). stroke-width 는 상속되는 SVG presentation
     property 라 root 에 걸면 path 까지 닿는다 — 렌더 변화 0. 인라인 재선언 ❌ */
  width: 18px;
  height: 18px;
  stroke-width: 2;
  /* 위 라벨과 같은 미정의 토큰 사고 — 구 `--text-muted` 로 순검정이었다 (2026-08-23 UI 감사 R).
     쉼 상태 셰브론은 장식 축이라 slate-400, hover·열림에서만 accent 로 올라간다 */
  color: var(--color-slate-400);
  flex-shrink: 0;
  transition: color var(--transition-fast), transform var(--transition-fast);
}

.tenant-switcher-trigger:focus-visible .tenant-switcher-arrow,
.member-tenant-switcher-trigger:focus-visible .tenant-switcher-arrow {
  color: var(--accent-primary);
}
@media (hover: hover) {
  .tenant-switcher-trigger:hover .tenant-switcher-arrow,
  .member-tenant-switcher-trigger:hover .tenant-switcher-arrow {
    color: var(--accent-primary);
  }
}

.tenant-switcher-dropdown.show ~ .tenant-switcher-trigger .tenant-switcher-arrow,
.tenant-switcher-trigger[aria-expanded="true"] .tenant-switcher-arrow,
.member-tenant-switcher-trigger[aria-expanded="true"] .tenant-switcher-arrow {
  color: var(--accent-primary);
}

.tenant-switcher-dropdown {
  display: none;
  position: absolute;
  top: calc(100% + 4px);
  left: 12px;
  right: 12px;
  background: var(--bg-primary);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  z-index: var(--z-dropdown);
  padding: 4px;
  /* 겸임 계정(관장 + 사범 + 관원 + 같은 도장 겸임 + 샘플)이면 내용이 ≈350px 라 구 240px 에서
     잘렸다 — 이 기능이 가장 필요한 사람만 스크롤을 만나던 자리 (2026-08-26).
     dvh 라 모바일 주소창 개폐에도 화면 밖으로 안 넘친다. 도장이 많으면 여전히 스크롤(정상). */
  max-height: min(calc(100dvh - 140px), 420px);
  overflow-y: auto;
}

.tenant-switcher-dropdown.show {
  display: block;
  transform-origin: top left;
  animation: motionPopoverIn 0.16s ease; /* 팝오버 공용 리빌 (2026-07-30 통일) */
}

.tenant-switcher-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  background: transparent;
  border-radius: 6px;
  font-size: var(--text-xs);
  color: var(--text-secondary);
  cursor: pointer;
  transition: all var(--transition-fast);
  text-decoration: none;
  font-family: inherit;
}

@media (hover: hover) {
  .tenant-switcher-item:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
    text-decoration: none;
  }
}

.tenant-switcher-item.active {
  background: var(--accent-primary-subtle);
  color: var(--accent-primary);
  font-weight: var(--font-semibold);
}

.tenant-switcher-item-name {
  flex: 1;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tenant-switcher-divider {
  height: 1px;
  background: var(--border-default);
  margin: 4px 0;
}

/* 도장별 역할 배지 — 겸임 계정(관장·사범·관원)의 「어디 · 무슨 자격」을 한 줄에 (ADR 0407).
   §상태 뱃지 어휘: 면·테두리 0 + 색 + 800 만. 클래스는 정적 3벌 — 값에서 조립 ❌ (고아 CSS 판정 전제). */
.tenant-switcher-item-role {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: var(--font-extrabold);
}

.tenant-switcher-item-role--owner { color: var(--color-primary-700); }
.tenant-switcher-item-role--sabeom { color: var(--color-slate-600); }
.tenant-switcher-item-role--member { color: var(--color-slate-500); }

/* .active 항목은 이름이 emerald 로 뜨므로 배지까지 emerald 면 한 줄이 통째로 초록이 된다 —
   배지는 중립으로 눌러 「어느 도장인가」와 「무슨 자격인가」가 서로 다른 신호로 남게 한다. */
.tenant-switcher-item.active .tenant-switcher-item-role--owner { color: var(--color-slate-600); }

/* 스위처 «한 행» = 이름 버튼 하나 (ADR 0533 — 구 「이름 + 반대 자격 칩」 supersede).
   겸임 도장은 칩이 아니라 «줄»이 둘이라 행 안에 목적지가 하나뿐이다 — 그 결과 구 칩의
   히트영역 34×26(44px 미달, ADR 0514 가 「리듬이 깨져서」 감수했던 자리)이 원인째 사라졌다.
   flex 래퍼는 남긴다: 자식이 하나라 렌더는 같고, 행에 무언가를 더할 때 기준선이 이미 서 있다. */
.tenant-switcher-row {
  display: flex;
  align-items: center;
  gap: 4px;
}

.tenant-switcher-row .tenant-switcher-item {
  flex: 1;
  min-width: 0;
}

/* 스위처 목록 검색 (ADR 0514) — 항목 8개 이상일 때만 JS 가 붙인다(TenantSwitcherFilter).
   드롭다운이 스크롤되는 길이라 sticky — 아래로 내려간 뒤에도 지운 뒤 되돌아올 자리가 보여야 한다.
   ⚠️ 음수 `top`·`margin` 은 한 쌍 — 드롭다운이 `padding: 4px` 라 기본값이면 검색줄이 content box
   상단에 붙어 **그 4px 띠 위로 행이 비쳐 지나간다**(검색은 늘 스크롤되는 목록에서만 뜬다).
   면이 층을 온전히 덮게 위로 4px 끌어올리고 폭도 좌우 4px 씩 넓힌다. 하나만 되돌리면 반대쪽에 틈. */
.tenant-switcher-search {
  position: sticky;
  top: -4px;
  z-index: 1;
  margin: -4px -4px 0;
  padding: 8px 8px 6px;
  background: var(--bg-primary);
}

.tenant-switcher-search-input {
  width: 100%;
  height: 34px;
  padding: 0 10px;
  border: 1px solid var(--border-default);
  border-radius: 10px;
  background: var(--bg-primary);
  font-family: inherit;
  font-size: var(--text-xs);
  color: var(--text-primary);
}

/* iOS Safari 는 font-size < 16px 인 입력에 포커스하면 화면을 확대한다 (§23). 글로벌 16px 강제는
   `.form-input/-select/.custom-select-trigger` 스코프라 이 클래스엔 안 걸린다 — 이 검색창은
   모바일 헤더 스위처([[0472]])에도 그대로 뜨므로 여기서 따로 건다. 높이 34px 는 불변. */
@media (max-width: 768px) {
  .tenant-switcher-search-input { font-size: 16px; }
}

/* focus = border + ring 한 벌 (§27) — 형제 검색 입력(.bank-picker-search:focus-within)과 같은 값.
   `outline: none` 은 이중 테두리 방지용이고, 그래서 ring 을 여기서 대신 그려 줘야 한다. */
.tenant-switcher-search-input:focus {
  outline: none;
  border-color: var(--accent-primary);
  box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.12);
}

/* 검색 결과 0건 안내 — 「검색 결과가 없어요」 한 줄 (2026-08-27 사용자 확정 A안).
   §타이포 보조 각주 토큰(12.5 / 400 / slate-500 / 1.55) 그대로 — 새 어휘 0.
   구 상태는 거른 뒤 드롭다운이 검색창만 남고 통째로 비어 「없음」과 「고장」이 구분되지 않았다.
   검색어 인용·「지우기」 버튼 재도입 ❌ (근거 = common.js `TenantSwitcherFilter._syncEmpty`). */
.tenant-switcher-empty {
  margin: 0;
  padding: 14px 10px;
  text-align: center;
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.55;
  color: var(--color-slate-500);
}

/* ⚠️ 필터가 쓰는 `hidden` 속성은 여기서 **이겨 줘야** 한다 — `[hidden]{display:none}`(UA)과
   `.tenant-switcher-item{display:flex}` 는 명시도가 같고 뒤에 오는 이쪽이 이겨서, 이 한 줄이 없으면
   검색해도 행이 그대로 보인다. 삭제 ❌ */
.tenant-switcher-dropdown [hidden] {
  display: none !important;
}

/* 「내가 다니는 도장」 머리말 = §섹션 머리 무면 마이크로 라벨 (회색 채움 스트립·양옆 선 ❌) */
.tenant-switcher-group-label {
  padding: 8px 10px 4px;
  font-size: 12px;
  font-weight: var(--font-extrabold);
  letter-spacing: 0.04em;
  color: var(--color-slate-500);
}

/* 「누르면 관원 화면으로 이동해요」 = §보조 각주 토큰 (12.5 / 400 / slate-500 / 1.55) */
.tenant-switcher-group-note {
  margin: 2px 0 4px;
  padding: 0 10px;
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.55;
  color: var(--color-slate-500);
}

/* 트리거의 「지금 내 자격」 — 관장은 기본값이라 서버가 라벨을 안 내려보낸다 (배지 미렌더). */
.tenant-switcher-trigger-role {
  margin-left: 6px;
  font-size: 12px;
  font-weight: var(--font-extrabold);
  color: var(--color-slate-500);
}

/* ─── 관원 화면 도장 스위처 (ADR 0407) ────────────────────────────────────────
   관리 화면과 «같은 목록·같은 배지»를 쓰되 트리거만 PWA 상단 바에 맞춘 무면 텍스트다.
   갈 데가 2곳 이상일 때만 렌더되므로(템플릿 게이트) 대다수 관원에겐 이 자리가 아예 없다. */
.member-tenant-switcher {
  position: relative;
}

.member-tenant-switcher-trigger {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 8px;
  /* 히트박스 확대 idiom — 보이는 상자가 없고 한 줄에 단독이라 「패딩 + 동값 음수 마진」이 성립 */
  margin: -6px -8px;
  border: none;
  background: transparent;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: var(--font-extrabold);
  color: var(--color-slate-600);
  cursor: pointer;
}

@media (hover: hover) {
  .member-tenant-switcher-trigger:hover {
    color: var(--color-slate-900);
  }
}

/* 상단 바 우측 끝 트리거라 오른쪽 정렬로 «왼쪽으로» 자란다 — 좌우 12px 스트레치(관리 화면 사이드바
   기준값)를 그대로 쓰면 480px 컬럼 밖으로 넘친다. */
.member-tenant-switcher-dropdown {
  top: calc(100% + 8px);
  left: auto;
  right: 0;
  min-width: 216px;
  transform-origin: top right;
}

/* 모바일 터치 타깃 44px — 보이는 크기는 그대로 두고 «판정만» 넓힌다(히트박스 확대 idiom).
   세로로만 벌린다: 가로로 벌리면 옆 로그아웃 버튼 판정과 겹친다. */
@media (max-width: 768px) {
  .member-tenant-switcher-trigger {
    padding-top: 13px;
    padding-bottom: 13px;
    margin-top: -13px;
    margin-bottom: -13px;
  }
}




/* "+ 도장 추가" — emerald positive action (progression 어휘). dashboard 와 동일 톤 + gap 정합.
   아이콘+라벨은 한 묶음이므로 좌측 정렬 (space-between 이 +↔텍스트를 양 끝으로 분리하는 것 방지). */
.tenant-switcher-add {
  justify-content: flex-start;
  color: var(--accent-primary);
  gap: 6px;
  font-weight: var(--font-semibold);
}
/* 아이콘 크기는 CSS 소유 — 마크업이 fragments/icons 참조로 바뀌며 인라인 width/height 가 사라진다
   (th:replace 는 placeholder 태그 속성을 버린다). 파일 내 다른 10곳의 .sidebar-nav-icon 과 같은 문법 */
.tenant-switcher-add svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

@media (hover: hover) {
  .tenant-switcher-add:hover {
    color: var(--accent-primary-hover);
    background: var(--accent-primary-subtle);
  }
}

/* "샘플 도장 둘러보기" — 「내 도장」이 아닌 탐색 진입점이라 중립 slate.
   emerald 는 바로 위 "도장 추가"(progression 어휘)가 소유한다 — 한 드롭다운에 emerald 신호가
   둘이면 무엇이 주 액션인지 흐려진다. 샘플을 보고 있을 때만 .active 가 emerald 로 켠다
   (형제 도장 항목과 같은 활성 어휘 — 별도 규칙 신설 ❌). 지오메트리는 .tenant-switcher-add 미러. */
.tenant-switcher-demo {
  justify-content: flex-start;
  color: var(--text-secondary);
  gap: 6px;
  font-weight: var(--font-semibold);
}
.tenant-switcher-demo svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

@media (hover: hover) {
  .tenant-switcher-demo:hover {
    color: var(--text-primary);
    background: var(--bg-secondary);
  }
}



/* ===== Subscription Expiry Warning Banner (Brand Mono — Slate neutral) =====
   셸 배너 = 흰 면 + border-bottom 농도 계단 한 어휘 (2026-08-12 사용자 확정 A안).
   구독 경고 3단계를 색이 아니라 **무게**로 올린다 — ① 예고(hairline · 제목 500)
   ② 자동갱신 실패(slate-300 · 제목 700, dashboard `.renewal-failure-banner`)
   ③ 정지(slate-900 2px · 제목 800 + btn-dark). 구 solid 면 재도입 ❌
   (① slate-700 흰 글자 · ③ #dc2626 red · SYSTEM slate-600 전부 폐지) —
   셸에 다크·red 블록이 뜨면 그 줄만 다른 앱처럼 읽혔다. 지원 모드 배너(흰 면 + hairline)와 한 어휘. */
/* 좌측 정렬 + dot = 셸 배너 단일 문법 (2026-08-23 사용자 확정 A안 · 시안 shell-banner-two-models).
   구 중앙 정렬 `-content` 래퍼 재도입 ❌. 제목 굵기 500 은 위 ① 계단값이라 유지 —
   형제 배너(700)에 맞춰 올리면 ②(자동갱신 실패)와 구분이 사라진다. */
.subscription-expiry-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  background: #fff;
  border-bottom: 1px solid var(--border-hairline);
  color: var(--color-slate-700);
  padding: 10px 20px;
}

/* 만료 「예고」는 아직 정상 운영 중이라 중립 신호 — 정지(slate-900 dot)와 갈리는 게 의도 */
.subscription-expiry-banner-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-primary-600);
  flex-shrink: 0;
}

.subscription-expiry-banner-text {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--color-slate-700);
}

/* 상태 알림 surface 규칙: bg 색 + 텍스트만 (아이콘·rail ❌ — project-frontend-guide §아이콘).
   구 경고 삼각형 SVG 3종(구독 정지 BILLING·SYSTEM·만료 임박)은 2026-08-05 제거. 재도입 ❌ —
   상태 신호는 배너 bg 색 + <strong> title 텍스트가 담당한다(§4 색만으로 정보 전달 ❌ 충족). */

/* CTA = 글로벌 btn tier 그대로 (2026-08-05 사용자 확정 A안). 구 page-local 재정의는 반투명 흰
   채움 + weight 600 + radius 6 으로 마크업의 btn-primary 를 통째로 무효화했다 — frontend-guide §11
   「버튼 솔리드/채움 재도입 ❌」·「신규 버튼에 임의 500/600 지정 ❌」 위반이고, slate-700 면 위
   반투명은 오히려 흰 면 raised 보다 대비가 낮았다. page-local 재정의 재도입 ❌. */
.subscription-expiry-banner .btn-sm {
  white-space: nowrap;
}

/* ===== Impersonation Banner (운영자 대리 접속 지원 모드) =====
   시스템관리자가 콘솔 「관장 화면으로 보기」로 진입한 세션 — 전 admin 페이지 상단 상시 노출.
   상태 알림 surface 규칙: bg 색 + 텍스트만 (아이콘·rail ❌), emerald dot 는 신호 위치 허용.

   면 = slate 중립 (2026-08-06 사용자 확정). 구 emerald-50 전폭 채움 재도입 ❌ — 지원 모드는 긍정도
   경고도 아닌 *중립 사실*인데, 셸 배너가 이미 slate(중립: 구독 만료 예고·SYSTEM 정지) / red(위험:
   BILLING 정지) 2축으로 정리돼 있어 emerald 를 세 번째 축으로 신설하면 「지금 무슨 상태인가」의 색
   학습이 흐려진다(admin 셸 유일한 emerald 전폭 면이기도 했다). 신호는 emerald dot + 800 라벨이 담당 —
   frontend-guide §17 「admin 운영 surface 는 중립 유지, emerald 는 신호 위치에만」과 정합. */
.impersonation-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 20px;
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-hairline);
}

.impersonation-banner-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-primary-600);
  flex-shrink: 0;
}

.impersonation-banner-text {
  flex: 1;
  min-width: 0;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--color-slate-700);
}

.impersonation-banner-text strong {
  font-weight: 800;
}

/* ===== 보기 모드 잠금 (관장·사범이 연 관원 화면 — [[0534]]) =====
   잠긴 «그 컨트롤»만 신호를 진다 (구역 통째 opacity ❌ — §잠긴 값 칸의 금지 축).
   버튼은 글로벌 `.btn:disabled` 가 이미 면·글자를 지므로 여기선 커서만 얹고, 버튼이 아닌
   조작 행은 강조 글자(CTA)만 slate-400 으로 내린다 — 행에 담긴 «읽을거리»(질문 내용·상태)는
   톤을 안 내린다(잠긴 건 조작이지 정보가 아니다). */
.is-view-locked {
  cursor: not-allowed;
}

.is-view-locked .md-today-cta,
.is-view-locked .icon-cta-chev {
  color: var(--text-tertiary);
}

/* 잠긴 이유를 그 자리에서 말하는 각주 — 관장 설정 화면 `.lt-gate-note` 와 같은 토큰
   (§타이포 보조 각주 12.5/400/slate-500). 배너가 스크롤로 사라지므로 잠긴 자리마다 반복하는 것이
   의도다 — 「왜 여긴 있고 저긴 없나」가 생기지 않게. */
.view-locked-note {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);
  line-height: 1.55;
  margin-top: 8px;
}

/* ===== Demo Banner (샘플 도장 둘러보기 — 샘플 도장 전 관장 화면 상시) =====
   2026-08-21 사용자 확정. 지원 모드 배너와 «성격이 같다» — 「지금 보는 게 당신 도장이 아니다」라
   면·dot·본문 톤을 그대로 미러한다(slate-50 면 + hairline + emerald dot + 12.5/700).
   셸 배너에 다크·red 블록을 두지 않는 규칙은 ADR 0201.
   CTA = btn-secondary — 「지원 종료」와 같은 성격(배너를 끝내지만 위험하지 않은 이탈).
   닫기 ✕ ❌ — 닫히면 이 배너가 막으려던 오해가 그대로 되살아난다. */
.demo-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 20px;
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-hairline);
}

.demo-banner-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-primary-600);
  flex-shrink: 0;
}

.demo-banner-text {
  flex: 1;
  min-width: 0;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--color-slate-700);
}

.demo-banner-text strong {
  font-weight: 800;
}

/* ===== Owner Transfer Banner (관장 인수 요청 — 지목된 사범 전 admin 페이지 상시) =====
   2026-08-19 사용자 확정 A안 (시안 owner-transfer-visibility) — 구 「홈에서만 모달」은 홈을
   안 거치거나 「나중에」 후 재진입점이 0 이라 만료 7일짜리 요청이 조용히 사라졌다.
   면 = 셸 배너 한 어휘(흰 면 + 하단 hairline). 중립 사실 + 처리할 일이라 red ❌,
   신호는 emerald dot(지원 모드 배너 문법) + 700 본문. CTA = btn-dark(모달을 여는 확정 진입 —
   확인형 배너의 「확인이 배너를 끝내는 액션」 tier). 닫기 ✕ 없음 — 수락·거절만이 배너를 끝낸다. */
.owner-transfer-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 20px;
  background: #fff;
  border-bottom: 1px solid var(--border-hairline);
}

.owner-transfer-banner-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-primary-600);
  flex-shrink: 0;
}

.owner-transfer-banner-text {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  font-weight: 700;
  color: var(--color-slate-900);
}

.owner-transfer-banner-text strong {
  font-weight: 800;
}

.owner-transfer-banner-meta {
  font-weight: 500;
  color: var(--color-slate-500);
}

.owner-transfer-banner .btn-sm {
  white-space: nowrap;
}

/* ===== Subscription Suspended Banner (결제 실패 / 운영자 강제 정지) =====
   두 갈래 모두 흰 면 한 어휘 — 구 「Red/Slate 그라데이션」은 ADR 0201 이 폐지했다 (아래 주석이 근거).
   reasonType=BILLING: 기본 .subscription-suspended-banner + "결제수단 재등록" CTA
   reasonType=SYSTEM:  .subscription-suspended-banner--system + 안내만
*/
/* 정지 = 3단계 중 가장 무거운 자리라 **가장 진한 하단 rule + 800 제목 + btn-dark** 로 표시
   (2026-08-12 사용자 확정 A안 — 구 red 솔리드 #dc2626 폐지, 앱 내 red 솔리드 면 0곳).
   구 근거였던 「톤다운하면 ②와 구분이 사라진다」는 색을 뺀 대신 **무게 3축**(테두리 농도·제목
   굵기·버튼 tier)이 그대로 계단을 만들어 성립하지 않는다.
   BILLING ↔ SYSTEM 은 색이 아니라 **CTA 유무**가 가른다 — 전자는 관장이 풀 수 있어
   「결제수단 재등록」이 서고, 후자는 문의만 가능해 액션이 없다(색보다 강한 구분).
   box-shadow 는 D.MONO 무광 원칙에 따라 없음. red·다크 솔리드 재도입 ❌ */
/* 셸 배너 = 좌측 정렬 + dot 단일 문법 (2026-08-23 사용자 확정 A안 · 시안 shell-banner-two-models).
   구 중앙 정렬 `-content` 래퍼 + 2줄 스택 `-text` 는 지원·샘플·인수 배너(좌측 + dot 1줄)와 두 문법이라,
   겹쳐 뜨면 한 화면에 시작선이 둘이 됐다. 중앙 정렬 래퍼·세로 스택 재도입 ❌.
   ⚠️ 위 ADR 0201 계단(테두리 농도·제목 굵기·버튼 tier)은 **정렬과 무관**이라 그대로 유지 —
   정렬은 그 계단의 축이 아니다. 굵기를 형제 배너(700)에 맞춰 "통일" ❌ */
.subscription-suspended-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  background: #fff;
  border-bottom: 2px solid var(--color-slate-900);
  color: var(--color-slate-700);
  padding: 12px 20px;
}

/* dot 색 = 경고 계열만 slate-900 — emerald 는 신호 위치라 「좋은 소식」으로 읽힌다 */
.subscription-suspended-banner-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-slate-900);
  flex-shrink: 0;
}

.subscription-suspended-banner-text {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--color-slate-900);
}

.subscription-suspended-banner-meta {
  font-weight: 500;
  color: var(--color-slate-500);
}

/* 구 `.btn-danger-solid` page-local 버튼 = 사용처 0 으로 삭제 (2026-08-12 UI 감사) —
   실제 CTA 는 layout/admin.html 이 글로벌 `btn btn-sm` tier 를 쓴다. 솔리드 채움 재도입 ❌ */

/* 폰에서는 텍스트와 CTA 를 세로로 — 좌측 시작선은 유지한다(중앙 정렬 복귀 ❌) */
@media (max-width: 640px) {
  .subscription-suspended-banner {
    align-items: flex-start;
    flex-wrap: wrap;
  }
  .subscription-suspended-banner-dot {
    margin-top: 6px;
  }
}

/* ========================================
   STATUS BANNERS
   4 tones: success / warning / error / info
   Usage: <div class="status-banner status-banner--{tone}">...</div>
   Link color auto-follows banner tone via currentColor.
   ======================================== */










/* Loading Overlay (Loading.show/hide) */
.loading-overlay {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(15, 23, 42, 0.35);
  backdrop-filter: blur(2px);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.2s ease, visibility 0.2s;
}

.loading-overlay.show {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.loading-overlay .loading-spinner {
  width: 42px;
  height: 42px;
  border: 3px solid rgba(255, 255, 255, 0.35);
  border-top-color: #fff;
  border-radius: 50%;
  animation: loadingSpin 0.8s linear infinite;
}

@keyframes loadingSpin {
  to { transform: rotate(360deg); }
}


/* 죽은 상태 변형(--success/--warning/--error) 삭제 — 사용처 0 (2026-08-10 UI 감사, red tint 톤다운 동반 정리). 실사용은 --info 뿐 */



/* Download / Export 드롭다운 — D.MONO (project-frontend-guide 「다운로드·내보내기 어휘」)
   hairline + radius 14 + 모달 그림자 + emerald hover + 800 마이크로 라벨 + stroke 다운로드 아이콘.
   양식 다운로드(btn-secondary + 다운로드 아이콘)와 같은 어휘. 포맷 선택만 드롭다운으로 분기. */
/* 구 `.members-toolbar-card`(overflow:visible + position/z-index) = 2026-08-14 C안으로 소멸 —
   툴바가 무면 `.list-toolbar` 가 되며 전역 `.card { overflow: hidden }` 클리핑 자체가 없어졌고,
   표 카드 위 stacking 은 `.list-toolbar` 가 승계. 툴바 카드 클래스 재도입 ❌ */

/* 메뉴 폭 = 캡션 한 줄이 안 접히는 하한 (2026-08-22 A안, ADR 0388).
   구 규칙은 「트리거 버튼 폭 정합」(left/right:0 스트레치)이었는데, 버튼이 「다운로드」 한 낱말이라
   폭이 ~120px 이고 거기에 캡션(「명단 + 이용권·승급·결제 내역 시트」)을 넣으면 3줄로 접힌다.
   right:0 고정 + min-width 로 **왼쪽으로** 자란다 — 툴바 오른쪽 끝 액션이라 오른쪽으로 키우면
   화면 밖으로 나간다. 이미 있던 transform-origin: top right 와 같은 방향. */
.download-menu {
  display: none;
  position: absolute;
  right: 0;
  min-width: 232px;
  top: calc(100% + 6px);
  background: var(--bg-primary);
  border: 1px solid var(--border-hairline);
  border-radius: 14px;
  box-shadow: 0 20px 60px -20px rgba(15, 23, 42, 0.25);
  padding: 6px;
  z-index: var(--z-dropdown);
}

/* 드롭다운이 열릴 때(display:none→block) 우상단 기준으로 살짝 떠오르며 등장. 닫힘은 즉시 */
@media (prefers-reduced-motion: no-preference) {
  .download-menu { transform-origin: top right; animation: motionPopoverIn 0.16s ease; }
}

.download-menu-label {
  font-size: 12px;
  font-weight: var(--font-extrabold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-slate-500);
  padding: 8px 12px 6px;
}

.download-menu-item {
  /* 캡션 1줄이 붙으면서 flex row → block (2026-08-22 A안, ADR 0388) — 이름·확장자 짝은
     안쪽 .download-menu-item-top 이 row 로 계속 쥔다. 여기를 다시 flex 로 되돌리면
     캡션이 확장자 옆으로 붙어 한 줄이 된다. */
  display: block;
  width: 100%;
  padding: 10px 12px;
  border: none;
  border-radius: 9px;
  background: none;
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
  transition: background var(--transition-fast);
}

.download-menu-item-top {
  display: flex;
  align-items: center;
  gap: 5px;
}

/* 캡션 = 「무엇이 담기나」 한 줄. 두 항목이 같은 문법으로 서야 차이가 스스로 드러난다.
   토큰 = §보조 각주/힌트 (12.5 / 400 / slate-500 / 1.55) — 각주 롤이라 즉석 크기 발명 ❌
   (구 11.5/500 은 12px 바닥 [[0038]] 위반이자 힌트 토큰 이탈이었다).
   이름(13.5/700/slate-900)과의 위계는 크기 1px 차가 아니라 **무게·색**이 쥔다.
   nowrap 을 상속하면 좁은 폭에서 잘리므로 normal 로 되돌린다 (부모 .download-menu-item 이 nowrap). */
.download-menu-cap {
  display: block;
  margin-top: 2px;
  font-size: 12.5px;
  font-weight: var(--font-normal);
  color: var(--color-slate-500);
  white-space: normal;
  line-height: 1.55;
}

.download-menu-name {
  font-size: 13.5px;
  font-weight: var(--font-bold);
  color: var(--color-slate-900);
  transition: color var(--transition-fast);
}

.download-menu-ext {
  font-size: 12px;
  font-weight: var(--font-semibold);
  color: var(--color-slate-400);
  font-variant-numeric: tabular-nums;
}

@media (hover: hover) {
  .download-menu-item:hover {
    background: var(--color-primary-50);
  }
}

@media (hover: hover) {
  .download-menu-item:hover .download-menu-name {
    color: var(--color-primary-700);
  }
}


/* Form Input Small — frontend-guide §23 입력 컨트롤 높이 표준 (변형 표) */
.form-input-sm {
  padding: 8px 12px !important;
  font-size: var(--text-sm) !important;
  min-height: 36px !important;
  border-radius: var(--radius-md) !important;
}

/* Mobile: prevent iOS Safari auto-zoom on focus (requires >= 16px font-size) */
@media (max-width: 768px) {
  .form-input-sm,
  .ios-item-input,
  input.form-input-sm,
  input.ios-item-input,
  select.form-input-sm,
  textarea.form-input-sm,
  .search-input,
  .ur-input,
  .custom-select-search input,
  .bank-picker-search input,
  .tenant-switcher-search-input,
  .alert-modal-input input,
  .alert-modal-input textarea,
  .flatpickr-current-month input.cur-year {
    font-size: 16px !important;
  }
}

/* ========================================
   MEMBER PORTAL - Mobile App Frame
   ======================================== */
.member-portal {
  background: var(--color-slate-100);
}

.member-portal .top-nav {
  max-width: 480px;
  margin: 0 auto;
  border-bottom: none;
  background: var(--bg-primary);
}

/* 멤버 헤더 도장 로고 — admin sidebar-logo-img 원형 패턴 미러 (데스크톱 52px / 모바일 52px, C안 2026-06-05) */
.member-portal .nav-brand {
  gap: 10px;
  /* 히트영역 44px — 보이는 크기(76×27)는 그대로, 세로만 넓힌다 (히트박스 확대 idiom [[0317]]).
     두 전제 충족: ① 보이는 상자가 없다(면·테두리·radius 0)
     ② 세로 확장이라 같은 줄 형제 `.nav-actions`(36×40)와는 x 범위가 겹치지 않는다.
     확장분은 `header.top-nav`(64px) 안 — 헤더 높이 불변. 관원 앱은 «폰 전용»이라 마우스 폴백이 없다.
     ⚠ 가로로 넓히지 ❌ — 우측 `.nav-actions` 와 판정이 겹친다. */
  position: relative;
  min-width: 0;
}

.member-portal .nav-brand::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 44px;
}


.member-portal .nav-brand-logo {
  width: 52px;
  height: 52px;
  border-radius: 12px;
  object-fit: contain;
  flex-shrink: 0;
  background: var(--bg-secondary);
}

.member-portal .brand-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.member-portal .main-wrapper {
  max-width: 480px;
  margin: 0 auto;
  background: var(--bg-primary);
  min-height: calc(100vh - var(--top-nav-height) - env(safe-area-inset-top, 0px));
  min-height: calc(100dvh - var(--top-nav-height) - env(safe-area-inset-top, 0px)); /* vh fallback + dvh override — 헛스크롤 제거 (body 규칙 참고) */
}

.member-portal .page-container {
  max-width: 100%;
  /* 하단: 바텀 탭바(60px) + safe-area + 여백 만큼 확보해 콘텐츠가 가려지지 않도록 */
  padding: 20px 20px calc(60px + env(safe-area-inset-bottom, 0px) + 24px);
}

@media (min-width: 481px) {
  .member-portal .main-wrapper {
    box-shadow: 0 0 40px rgba(0, 0, 0, 0.08);
  }
}

@media (max-width: 480px) {
  .member-portal .top-nav {
    /* 인셋은 베이스 `.top-nav` 가 이미 진다 — `padding` 단축·고정 `height` 로 덮으면 그 처리가
       통째로 무효가 돼 cover 뒤 도장명·로그아웃이 상태바 밑으로 들어간다 (2026-09-15 교정).
       ⚠ 좌우만 바꿀 때도 `padding-top` 을 함께 다시 적을 것 — 단축 선언이 인셋을 지운다 */
    padding: 0 14px;
    padding-top: env(safe-area-inset-top, 0px);
    height: calc(64px + env(safe-area-inset-top, 0px));
  }
  .member-portal .brand-name {
    font-size: 18px;
  }
  .member-portal .nav-brand-logo {
    width: 52px;
    height: 52px;
  }
  .member-portal .main-wrapper {
    min-height: calc(100vh - 64px - env(safe-area-inset-top, 0px));
    min-height: calc(100dvh - 64px - env(safe-area-inset-top, 0px)); /* vh fallback + dvh override — 헛스크롤 제거 (body 규칙 참고) · 인셋은 위 top-nav 실높이 몫 */
  }
  .member-portal .page-container {
    padding: 16px 14px calc(60px + env(safe-area-inset-bottom, 0px) + 20px);
  }
}

/* ============================================================
   Member Bottom Tab Bar (C안 2026-06-01)
   설치형 PWA 상시 네비. 홈·결제·내 정보 3슬롯.
   480px 컬럼에 정렬 — 데스크톱 centered / 모바일 full-width (둘 다 fixed bottom).
   admin 의 .mobile-bottom-nav 와 달리 모든 폭에서 노출 (관원 앱은 폰 전용 PWA).
   ============================================================ */
.member-bottom-nav {
  position: fixed;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  /* 인셋을 높이에도 더한다 — 베이스 60px 고정이면 padding-bottom 인셋만큼 콘텐츠 박스가 눌려
     아이콘·라벨이 통째로 잘린다 (mobile-shell.css `.mobile-header` 와 같은 calc 문법).
     `viewport-fit=cover` 도입(2026-09-14)으로 이 인셋이 «실제 값»이 됐다 — 구 「인셋 0 = 렌더
     변화 0」 서술은 그 전의 사실이다. */
  height: calc(60px + env(safe-area-inset-bottom, 0px));
  display: flex;
  background: var(--bg-primary);
  border-top: 1px solid var(--border-default);
  box-shadow: 0 -1px 3px rgba(15, 23, 42, 0.04);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  z-index: var(--z-sticky);
}

.member-bottom-nav-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  border: none;
  background: transparent;
  text-decoration: none;
  /* slate-500 승격 — 네비 라벨이 「이 칸이 어디로 가나」의 유일한 신호 (§slate-400 승격 기준 · ADR 0495).
     대비 2.56 → 4.76:1. 활성(emerald-700)과의 구분은 그대로. slate-400 복귀 ❌ */
  color: var(--color-slate-500);
  font-size: 12px;
  /* 셸 네비 비활성 = 500 (§타이포 Weight 「.tab-item 비활성 = 500」에 편입, 2026-08-15 사용자 확정 A안,
     시안 bottomnav-label-weight). 구 700 은 admin 셸(mobile-shell.css `.mobile-bottom-nav-item` 500)과
     같은 자리에서 굵기만 갈려 겸임 계정이 두 셸을 오갈 때 무게가 바뀌었다.
     700 재도입 ❌ — 비활성이 700 이면 활성과의 차이가 색 하나뿐이라 §4 「색만으로 정보 전달」에 가까워진다 */
  font-weight: 500;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.15s;
}

.member-bottom-nav-item svg {
  width: 22px;
  height: 22px;
  stroke-width: 1.8;
}

/* 활성 라벨 = 흰 면 위 12px → primary-700 (3.77 → 5.48:1, ADR 0495).
   비활성(slate)과의 «색 대비»는 그대로 유지된다 — 둘 다 한 단계씩 진해진다. 600 복귀 ❌ */
.member-bottom-nav-item.active {
  color: var(--color-primary-700);
}

.member-bottom-nav-item.active svg {
  stroke-width: 2.2;
}

/* ============================================================
   Settlement status banner (admin/settings/index.html)
   ============================================================ */

/* ================================================================
   R17. BILLING SNAPSHOT (구독 결제 공유 토큰)
   - 구 「구독 결제 내역」 페이지(/admin/account/billing-history)는 ADR 0376 으로 폐지 —
     전량·산정 근거는 홈 「구독 내역」 타임라인이 소유. 페이지 전용 표·모바일 카드 규칙은 함께 삭제.
   - 잔존 토큰 = 설정 「결제 내역」 미리보기 + system 콘솔 tenants/detail 공유
     (.billing-tier-badge · .billing-row-status · .billing-period-detail · .pay-amount-currency)
   ================================================================ */



/* 상태 뱃지 — 확정(결제 예정). Hairline Pill 통일 (2026-06-26): 채움 폐지 */
/* 순수 텍스트 통일 (2026-06-26): 면·테두리 0, dot + 800 텍스트만 (.badge-* status 패밀리와 동일 어휘) */

/* 미리보기 카드 내부 — 카드 자체가 컨테이너. inner 박스 별도 배경/테두리 없음 */


/* 구간 뱃지 (결제 내역 · 미리보기 공용) */
.billing-tier-badge {
  display: inline-block;
  padding: 3px 8px;
  border-radius: 5px;
  font-size: 12px;
  font-weight: 700;
  /* 분류 뱃지 어휘 (2026-07-05 판정): 구간=status 아닌 가격 구간 분류라 hairline pill 유지
     (이용권 dot pill·payments 타입 뱃지와 같은 라인 — 순수 텍스트 전환은 .billing-row-status 등 status 패밀리만) */
  background: #fff;
  border: 1px solid var(--color-slate-200);
  color: var(--color-slate-700);
}

/* 구 페이지 전용 표 규칙(.billing-history-table·.billing-history-amount)은 ADR 0376 폐지로 삭제 —
   강조 숫자 어휘(800/-0.025em/tabular)의 SSOT 는 ADR 0316 (콘솔 td.td-bill-amount·홈 .sub-tl-amt 가 미러). */
/* 통화 '원' 약화 분리 — D.MONO 강조 숫자 어휘 (payments/history 와 일치).
   크기는 표면별 2슬롯: 데스크톱 표 12px / 모바일 카드 13px — payments/index 의
   `.payment-table-desktop`(12) ↔ `.payment-card-list`(13) 짝과 같은 축 (2026-08-12 UI 감사).
   2026-08-18 UI 감사: system 콘솔 금전 표 2곳이 세 번째 사용처가 되며 **베이스를 무스코프로 승격**
   (구 `.billing-history-amount` 스코프 단독 → 데스크톱 표 값이 기본, 모바일 카드만 13px 오버라이드).
   page-local 사본 재도입 ❌ */
.pay-amount-currency {
  font-size: 12px;
  font-weight: 700;
  color: var(--color-slate-500);
  margin-left: 2px;
  letter-spacing: 0;
}
/* 모바일 카드 슬롯 = 13px (2026-08-18 UI 감사: payments/index page-local 사본을 여기로 흡수.
   구 짝 .billing-card-amount 는 ADR 0376 페이지 폐지로 소멸. page-local 재도입 ❌) */
.payment-card-list .pay-amount-currency {
  font-size: 13px;
}

/* 「전체 N명 · 이용종료 M명 제외」 각주는 홈 타임라인 메타(.sub-tl-meta)로 흡수 (ADR 0376) —
   구 페이지 전용 .billing-member-sub·.billing-card-sub 삭제. */

/* 결제 내역 상태 뱃지 — 순수 텍스트 통일 (2026-06-26): 면·테두리(취소 점선 포함) 0,
   색 + 800 굵기로만 (.badge-* status 패밀리와 동일 어휘) */
.billing-row-status {
  display: inline-flex;
  align-items: center;
  font-size: 12px;
  font-weight: 800;
  color: var(--color-slate-600);
}
.billing-row-status.is-applied {
  color: var(--color-slate-600);
}

.billing-row-status.is-free {
  color: var(--color-primary-700);
}
/* 기간 각주 — 이 행이 커버하는 기간의 끝 「~까지」 (ADR 0282 — system tenants/detail 소유.
   구 관장 billing-history 페이지 공유는 ADR 0376 폐지, 홈 타임라인은 metaParts 「~까지」가 같은 축).
   유료·무료 전 행 공통, 보조 각주 토큰(12.5/400/slate-500). */
.billing-period-detail {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* 구 페이지 전용 스켈레톤·모바일 카드 리스트(.billing-status-skeleton-detail·.billing-history-desktop/
   -cards-mobile·.billing-card-*)는 ADR 0376 폐지로 삭제 — 모바일 표현은 홈 타임라인 행이 그대로 담당. */

/* ========================================
   R14. (폐지 2026-05-26) BRAND MONO SIGNATURE ACCENT — 어휘 자체 폐지
   project-frontend-guide §「Carded surface — color rail 일반 금지」 일반화로
   .subsection-title / .form-section-title 좌측 4×14 emerald 막대 (alias of .card-title::before)
   + .pg-head 하단 emerald fade line 모두 폐지. 어차피 templates 미사용 (dead).
   섹션 어휘 (subsection/form-section) 가 필요하면 .card-title 14px 800 어휘 그대로 재사용.
   ======================================== */

/* ========================================================================
   Setup Guide — 도장 오픈 준비 배너 (온보딩 위젯)
   이전: fragments/setup-guide-banner.html 의 inline <style> 480줄.
   admin layout <head> 에 무조건 inject되어 사실상 글로벌이라 박제로 이동.
   모든 클래스는 .setup-* 로 스코프되어 전역 스타일과 충돌하지 않음.
   ======================================================================== */
.setup-wrapper {
  position: sticky;
  top: 0;
  z-index: 20;
  border-bottom: 1px solid var(--border-default);
  background: #fff;
}

@media (max-width: 768px) {
  .setup-wrapper {
    position: sticky;
    top: var(--shell-header-h, 56px);
    z-index: 150;
    border-top: 1px solid var(--border-default);
  }
}

/* 단일 줄 banner (2026-05-28) — 1 item 만 남아 펼침/접힘 step 폐지.
   구조: [dot] title — desc [CTA pill] [✕ dismiss]
   emerald 신호 = dot · CTA pill · focus ring 에만. carded surface fill ❌ (D.MONO 정합). */
.setup-banner-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 20px;
  background: #fff;
}

.setup-banner-action {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  text-decoration: none;
  color: inherit;
  padding: 2px 4px;
  margin: -2px -4px;
  border-radius: 8px;
}
.setup-banner-action:focus-visible {
  outline: 2px solid var(--color-primary-600);
  outline-offset: 2px;
}

.setup-banner-dot {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--color-primary-500);
  flex-shrink: 0;
  position: relative;
}
.setup-banner-dot::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 999px;
  border: 2px solid var(--color-primary-400);
  opacity: 0;
  animation: setup-banner-ring 2.4s ease-out infinite;
}
@keyframes setup-banner-ring {
  0%   { opacity: 0.5; transform: scale(0.8); }
  100% { opacity: 0;   transform: scale(2); }
}
@media (prefers-reduced-motion: reduce) {
  .setup-banner-dot::after { animation: none; }
}

.setup-banner-title {
  font-size: 13.5px;
  font-weight: 800;
  color: var(--color-slate-900);
  letter-spacing: -0.01em;
  white-space: nowrap;
  flex-shrink: 0;
}
.setup-banner-em {
  font-size: 13px;
  color: var(--color-slate-300);
  flex-shrink: 0;
  font-weight: 500;
}
.setup-banner-desc {
  flex: 1;
  font-size: 12.5px;
  color: var(--color-slate-600);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 500;
}

/* Raised(흰 면 + emerald hairline + 미세 그림자) — ADR 0034 정합. 솔리드 채움 폐지. */
.setup-banner-cta {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: #fff;
  color: var(--color-primary-700);
  border: 1px solid var(--color-primary-200);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06), 0 2px 6px -2px rgba(5, 150, 105, 0.18);
  padding: 6px 13px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: -0.005em;
  flex-shrink: 0;
  transition: background 0.15s, border-color 0.15s, box-shadow 0.15s, transform 0.15s, gap 0.2s;
}
@media (hover: hover) {
  .setup-banner-action:hover .setup-banner-cta {
    background: var(--color-primary-50);
    border-color: var(--color-primary-300);
    box-shadow: 0 2px 4px rgba(16, 24, 40, 0.08), 0 6px 14px -4px rgba(5, 150, 105, 0.28);
    transform: translateY(-1px);
    gap: 6px;
  }
}
/* viewBox(0 0 24 24) 내장 좌측 여백(~4px) 상쇄 — 텍스트와 체감 간격 정렬 */
.setup-banner-cta svg {
  width: 11px;
  height: 11px;
  margin-left: -2px;
}

/* 배너 끄기 = **라벨 있는 텍스트 액션** (ADR 0475).
   구 `.setup-banner-dismiss`(28×28 무라벨 ✕ + svg 14px)는 0475 가 마크업을 `.setup-banner-off`
   「다시 안 보기」로 바꾸면서 **이름 이관이 반쪽으로 끝나** 고아 4규칙으로 남았고, 새 클래스는
   규칙이 0건이라 배너에서 **브라우저 기본 회색 버튼**으로 렌더됐다 (2026-08-27 UI 감사).
   값 = 같은 일을 하는 짝 `.qs-hub-off`(온보딩 허브 「이 안내 다시 안 보기」)와 한 어휘 —
   읽는 텍스트 액션이라 slate-500(§slate-400 승격 기준 1행). 무라벨 ✕ 복귀 ❌ · 400 복귀 ❌ */
.setup-banner-off {
  flex-shrink: 0;
  border: none;
  background: none;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--color-slate-500);
  white-space: nowrap;
  padding: 6px 4px;
  border-radius: 8px;
  cursor: pointer;
  transition: color 0.15s;
  position: relative;
}
/* 히트영역 = `::after` 오버레이 (보이는 27px 불변 · 판정 45px). [[0475]] 가 이 버튼을 모바일에도
   세우며(구 무라벨 ✕ 는 ≤768px 에서 숨어 「폰에서 끌 방법 자체가 0」이었다) 폰의 «유일한 해제 수단»이
   됐는데 히트영역 축만 지나갔다 — 2026-09-04 UI 감사.
   ⚠ SSOT 기본 처방인 「패딩 + 동값 음수 마진」은 여기서 ❌ — 박스가 배너 «밖»으로 나가는데 바로 아래가
   `top: calc(56px + var(--shell-sticky-h))` sticky 툴바라 [[0481]] 의 「보이지 않는 판정이 이웃을 먹는」
   자리가 그대로 재현된다. 오버레이는 바 안쪽(padding 12px)에 머문다.
   선례 = `.toast-close::after` · `.tablet-tenant-trigger::after`(새 어휘 0).
   ⚠ 값은 실측으로 확인할 것 — 라벨·폰트가 바뀌면 다시 잰다 */
.setup-banner-off::after {
  content: "";
  position: absolute;
  inset: -9px 0;
}
@media (hover: hover) {
  .setup-banner-off:hover { color: var(--color-slate-700); }
}
.setup-banner-off:focus-visible {
  outline: 2px solid var(--color-primary-600);
  outline-offset: 2px;
}

/* 도장 오픈 준비 매칭 칩(.setup-link-chip) = 사용처 0 으로 삭제 (2026-08-09 초록 배경 정리) */

/* Nav Pill (dismissed state)
   배너를 닫은 뒤 남는 재진입점 — 신호는 emerald hairline + emerald 텍스트로만 (2026-07-23 UI 감사 D안 확정).
   구 체크 SVG 는 폐지: 배너 본체가 아이콘 0 어휘라 pill 에만 픽토그램이 남아 어긋났고,
   ✓ 가 "이미 끝남"으로 오독될 여지도 있었다 (실제로는 미완료일 때만 노출). dot 신호도 미채택(사용자 결정). */
/* (삭제됨) .setup-nav-pill — dismiss 상태에서 사이드바 푸터에 뜨던 「도장 오픈 준비」 알약.
   2026-08-23 사용자 확정 A안(시안 `quickstart-reentry`)으로 폐지 — quickstart 재진입은
   사이드바 「시작하기」 메뉴가 상시로 갖는다. 마크업·JS(showPill/undismiss)도 함께 제거. */
/* 모바일 ≤768px — bar padding 축소.
   좁은 폭에서 desc 가 길어 잘리지 않도록 title + em-dash 도 숨김(아래 540px 블록).
   ⚠ **끄기 버튼은 모바일에도 선다 (ADR 0475) — 여기서 숨기지 ❌.**
   구 `.setup-banner-dismiss { display:none }` 3줄은 2026-08-28 사용자 확정 A안으로 삭제:
   0475 가 버튼을 `.setup-banner-off` 로 개명한 뒤 이 한 줄만 옛 이름으로 남아 매칭이 0 이었는데,
   함께 남은 주석(「✕ dismiss 숨김 · 모바일 dismiss 불가」)이 «0475 를 되돌리라고 지시하는» 형태라
   그 말을 믿고 새 클래스를 이 블록에 넣으면 **폰에서 배너를 끌 방법이 다시 0** 이 된다
   (빌드·렌더가 안 깨져 조용히 통과한다). 규칙·주석 어느 쪽도 재도입 ❌ */
@media (max-width: 768px) {
  .setup-banner-bar {
    padding: 12px 16px;
  }
}
@media (max-width: 540px) {
  .setup-banner-title,
  .setup-banner-em {
    display: none;
  }
  /* title·em 이 빠져 desc 가 전폭을 받는 구간 — 320px 가용 폭(≈171px)이 문구 실폭(≈175px)보다 좁아
     마지막 어절이 말줄임됐다. 여기서만 줄바꿈 허용(데스크톱 한 줄 어휘는 불변).
     배너 높이는 setup-guide.js 의 ResizeObserver 가 --setup-banner-h/--shell-sticky-h 로 실측 반영하므로
     2줄이 돼도 하위 sticky 스택이 따라온다. §22 「nowrap 남용 ❌ · 글로벌 keep-all 의존」 */
  .setup-banner-desc {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }
  .setup-banner-cta {
    padding: 6px 11px;
  }
}

/* ========================================================================
   Invitation Cards — 초대/복구 진입점 공통 카드 어휘
   사용처: invitation/accept.html · invitation/expired.html · auth/recover-invalid.html
   이전: 각 페이지 inline <style>. recover-invalid 가 accept.html CSS 의존하던
   잠재적 스타일 깨짐 버그도 함께 해소.
   ======================================================================== */
/* Auth page background — 랜딩 연결감 위해 옅은 emerald 워시로 통일 (2026-06-30).
   2026-06-15 "운영 surface 초록 제거"의 auth 한정 의도적 예외:
   auth = 랜딩(마케팅)→제품 진입 관문이라, 랜딩 body 와 동일한 #f8fdfb + a7f3d0 emerald 라디얼로
   첫인상 핸드오프를 매끄럽게. admin·PWA·error 운영 surface 는 중립 유지(예외 확장 ❌).
   워시는 .login-blob(좌상단·우하단, 랜딩 ellipse 위치와 동일)이 담당 — 전 auth 페이지 공유. */
[data-page="auth"] {
  background: #f8fdfb;
  min-height: 100vh;
  min-height: 100dvh; /* vh fallback + dvh override — 헛스크롤 제거 (body 규칙 참고) */
}

/* 컬럼 폭·z-index·position 은 ADR 0025 SSOT 블록이 형제 3종과 함께 소유 (§auth 카드리스).
   여기 단독 재선언 ❌ — 조정은 그 한 곳만 (2026-08-12 UI 감사) */

/* ============================================================
   auth-hero — auth 카드 안 통일 hero 어휘 (SSOT, 2026-05-27)
   적용 페이지: login · recover · select-tenant · signup-complete
              · setup-quickstart · add-tenant
   구조: <div class="auth-hero">
           <img class="auth-hero-img" src=".../logo-wordmark.png" alt="주짓수메이트">
           <h1 class="auth-hero-title">{컨텍스트별 title}</h1>
           <p class="auth-hero-sub">{컨텍스트별 sub, optional}</p>
         </div>
   기존 7개 클래스(.login-logo-img·.select-logo-img·.sc-brand-img
   ·.at-brand-img·.auth-page-brand-img·.login-logo-sub·.sc-sub·.sc-title
   ·.at-title) 통합. project-frontend-guide §auth-hero 참조.
   ============================================================ */
.auth-hero {
  text-align: center;
  margin-bottom: 22px;
}
.auth-hero-img {
  height: 76px;
  width: auto;
  display: inline-block;
  margin-bottom: 14px;
}
.auth-hero-title {
  font-size: 22px;
  font-weight: 800;
  color: var(--color-slate-900);
  letter-spacing: -0.02em;
  line-height: 1.25;
  margin: 0 0 6px;
}
.auth-hero-sub {
  font-size: 13px;
  color: var(--color-slate-500);
  line-height: 1.5;
  margin: 0;
}

/* 콜라보 락업 — 서비스 워드마크(맨 모양) × 도장 로고(원형). auth-hero 공용 어휘.
   login·member-link 공유 (구 .login-collab/.ml-collab 평행 클래스 통합, 2026-06-02).
   도장 로고는 항상 원형 크롭. project-frontend-guide §회원 연결 참조. */
.auth-collab {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 15px;
  margin-bottom: 16px;
}
.auth-collab-wm { height: 52px; width: auto; }
.auth-collab-x {
  color: var(--color-slate-400);
  font-size: 34px;
  font-weight: 600;
  flex-shrink: 0;
  line-height: 1;
}
.auth-collab-dojo {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid #eaecef;
  background: #fff;
  flex-shrink: 0;
}

/* Brand wordmark above invite-card (recover-invalid, invitation/expired) —
   에러 hero 픽토 폐지(2026-06-18, §아이콘 상태 surface 아이콘 금지) 후 wordmark + .auth-hero-title 가 hero.
   (auth-hero 와 동일 height 76px 로 시스템 통일, 위치는 카드 외부) */
.auth-page-brand {
  text-align: center;
  margin-bottom: 16px;
}
.auth-page-brand-img {
  height: 76px;
  width: auto;
  display: inline-block;
}

/* 카드리스 무력화(면·테두리·padding·position)는 ADR 0025 SSOT 블록이 형제 3종과 함께 소유
   (§auth 카드리스). 여기는 이 페이지 고유값만 (2026-08-12 UI 감사) */
.invite-card {
  text-align: center;
}

/* 구 .invite-title/.invite-desc 폐지 (2026-08-03 UI 감사) — 에러 두 페이지(recover-invalid·
   invitation/expired)도 .auth-hero-title/-sub 단일 락업으로 흡수. 재도입 ❌ */
.auth-hero-sub strong {
  color: var(--color-slate-700);
  font-weight: 700;
}

/* 초대 수락 — 지점 명단 + 권한 각주 (2026-08-19 사용자 확정 A안 · 시안 invite-accept-facelift,
   같은 날 invite-scope-two-boxes 의 「라벨 2 + slate-50 박스 2」 supersede) — 핵심 정보(어느 지점을
   맡나)와 보일러플레이트(권한 안내)가 같은 회색 면·같은 dot 이라 화면 중앙이 납작하게 읽혔다.
   명단 = 흰 면 + hairline 행(.auth-target 과 같은 「액션의 대상」 어휘)으로 승격,
   권한 안내 = 무면 각주로 강등. 구 .invite-scope slate-50 박스 재도입 ❌ */
/* 섹션 머리 = 무면 마이크로 라벨 단일 (§섹션 머리 — .ios-group-label 과 같은 토큰). 면·구분선 부여 ❌ */
.invite-scope-label {
  margin: 0 0 7px;
  padding: 0 2px;
  text-align: left;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-slate-500);
}
.invite-tenants {
  margin: 0 0 20px;
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 10px; /* auth 비카드 박스 10px 통일 (2026-08-03 사용자 확정) */
  text-align: left;
  overflow: hidden;
}
.invite-tenant-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 16px;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}
.invite-tenant-row + .invite-tenant-row { border-top: 1px solid var(--border-light); }
.invite-tenant-row::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-primary-500);
  flex-shrink: 0;
}
/* 권한 안내 = 보조 각주 토큰(12.5/400/slate-500) — dot·면·hairline 장식 ❌ (§타이포 보조 각주) */
.invite-perm-note {
  margin: 0 0 22px;
  padding: 0 2px;
  text-align: left;
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);
  line-height: 1.55;
}

/* 「이 화면의 대상」 카드 (2026-08-11 사용자 확정 C안 → 2026-08-12 A안으로 recover 와 공유,
   구 .invite-acct 에서 중립명 승격) — 액션 직전에 「무엇을 대상으로 하는가」를 라벨+값으로 박는
   확인 슬롯. 초대 수락 = 수락할 계정, 계정 복구 = 복구할 도장. auth 비카드 박스 radius 10px 통일,
   면은 흰 카드 + hairline — 지점 명단(.invite-tenants)과 같은 「대상」 어휘.
   provider 마크(.pv-mark) 대신 제공자를 **텍스트**로 싣는다 — §아이콘 최소화 + 마크는
   사용처 3번째라 글로벌 승격이 선행돼야 하고, 이름·이메일·제공자 3줄이면 식별에 충분. */
.auth-target {
  margin: 0 0 20px;
  padding: 13px 16px;
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  text-align: left;
}
/* 색 = 마이크로 라벨 표준 slate-500 — 이 라벨이 「수락할 계정」↔「지금 로그인한 계정」 두 상태를
   가르는 유일한 신호라 §slate-400 승격 기준의 「옆에 값이 있는 라벨(유지)」이 아니라 승격 대상 */
.auth-target-lab {
  margin: 0 0 8px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--color-slate-500);
}
.auth-target-name {
  margin: 0;
  font-size: 14.5px;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}
.auth-target-sub {
  margin: 3px 0 0;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

/* 계정 교체 링크 — 주 액션(수락)보다 한 체급 아래 텍스트 링크.
   관원 계정 분기의 btn-secondary 「다른 계정으로 로그인」은 그 분기의 유일 액션이라 체급 유지 */
.invite-switch-link {
  display: block;
  width: 100%;
  padding: 4px 0;
  background: none;
  border: none;
  font-family: inherit;
  /* 13px/700 = auth 단독 텍스트 링크 공통 토큰 (signup-complete `.sc-acct-link`·join `.jn-cross-link`
     와 한 벌 — 2026-08-12 UI 감사, 구 12.5/800 재도입 ❌. 구 짝 login `.login-cross-link` 는
     ADR 0771 폐지) */
  font-size: 13px;
  font-weight: 700;
  color: var(--color-primary-700);
  cursor: pointer;
}
@media (hover: hover) {
  .invite-switch-link:hover {
    color: var(--color-primary-600);
  }
}

/* 모바일 히트영역 44px — 자연 높이 ≈27px 인데 `.invite-action-group`(gap 16) 안에서 수락 btn-lg(48px) 바로
   아래 서는 보조 액션이라 손가락이 위 버튼으로 흘러든다. 음수 마진이 기존 `padding: 4px 0` 몫을 되돌려
   행 간격·타이포·색 불변 (ADR 0317 히트박스 확대 idiom · 값 문법은 `.auth-footer a` 와 한 벌).
   `btn-secondary` 승격 ❌ — 위 주석이 「주 액션보다 한 체급 아래 텍스트 링크」로 위계를 박제했다. */
@media (max-width: 768px) {
  .invite-switch-link {
    padding-top: 13px;
    padding-bottom: 13px;
    margin-top: -9px;
    margin-bottom: -9px;
  }
}

/* Action group */
.invite-action-group {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* expired/recover-invalid 의 단순 홈 복귀 액션은 gap 더 작게 (override) */
.invite-action-group:has(.btn-lg) {
  gap: 10px;
}
/* accept 로그인 분기는 위 override 대상이 아니다 — `.invite-switch-link` 히트박스(음수 마진 -9px)가
   근거로 삼은 전제가 `.invite-action-group`(gap 16) 이라, gap 10 이면 위 btn-lg 와 여유가 1px 만 남는다.
   `.invite-switch-link` 는 accept 로그인 분기 단독이라 expired/recover-invalid 는 10px 그대로 */
.invite-action-group:has(.invite-switch-link) {
  gap: 16px;
}
/* 전화 CTA 아래 번호 각주 — 번호를 버튼 라벨에 결합하면 320px 에서 .btn(nowrap+overflow hidden)에
   ~11px 조용히 잘린다 (2026-08-09 UI 감사 실측). 번호는 읽는 정보라 각주 톤으로 분리 */
.invite-phone-note {
  margin: -2px 0 0;
  text-align: center;
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);
  line-height: 1.55;
}

/* auth 중립 안내 박스 — login OAuth/세션·recover 재요청·invitation 수락 불가 공용
   (2026-07-30 통합 — 구 .login-error-message/.recover-error/.invite-error-msg 3벌 폐지).
   사용자 잘못 아닌 안내라 slate 중립 (§시맨틱 색) */
.auth-notice {
  margin-bottom: 14px;
  padding: 12px 16px;
  background: var(--color-slate-50, #f8fafc);
  border: 1px solid var(--color-slate-200, #e2e8f0);
  border-radius: 10px; /* auth 비카드 박스 10px 통일 (2026-08-03 사용자 확정 — .btn/.form-input 정합) */
  color: var(--color-slate-600, #475569);
  font-size: 13px; /* auth 중립 안내 박스 13px 통일 (2026-07-22) */
  font-weight: 500;
  line-height: 1.5;
  text-align: left;
}
/* --center 변형 폐지 (2026-08-09 UI 감사 사용자 확정 A안 — 3 사용처 좌측 통일로 사용처 0. 재도입 ❌) */
/* 안내 박스 «둘째 줄» 여백 — 「사실 ↔ 그래서 어떻게 되나」 두 덩어리를 가르는 자리.
   구 사본 2벌(setup-quickstart 인라인 `style` · signup-complete `line2.style.marginTop`)을 흡수한다
   (2026-09-07 UI 감사 — 같은 값이 마크업·JS 로 갈려 있으면 한쪽만 고쳐진다. `system-approve` 가
   같은 상황에서 「인라인 `style` ❌ — 여백 주인이 클래스와 갈린다」로 이미 박제한 축).
   ⚠ `> div + div` 류 구조 셀렉터 ❌ — (0,1,2) 라 `.auth-notice-action{margin-top:8px}`(0,1,0)을 이겨
   login·member-link·recover 3곳의 층 구분 여백을 조용히 6px 로 깎는다. 여백 주인은 «이름»이다. */
.auth-notice-line2 { margin-top: 6px; }

/* 시스템 로그인 «대조 코드» 조판 — 두 화면의 숫자를 «눈으로 맞추는 것»이 이 값의 유일한 용도라
   (ADR 0614 ②) 값이 갈리면 그 기능이 통째로 깨진다. 구 상태 = page-local 2벌이 7선언을 손으로
   미러하고, 대기 화면 주석이 스스로 「승인 화면 `.sys-code` 와 «같은 값이어야 한다»」로 미러 의무를
   사람에게 지우고 있었다 (§원자 승격 — 주석이 「한 벌」이라 말하는데 선언이 둘이면 그 자체가 결함 신호.
   같은 근거의 바로 위 `.auth-notice-line2` 선례). 클래스명 둘 다 유지 = 마크업 변경 0 · 시각 변화 0.
   ⚠ 자리 값(`.sys-code` 가운데 정렬 · `.sys-wait-code` margin)은 화면마다 달라 page-local 이 계속 소유.
   page-local 로 이 7선언 재복제 ❌ */
.sys-code,
.sys-wait-code {
  font-size: 34px;
  font-weight: 800;
  letter-spacing: 0.22em;
  text-indent: 0.22em; /* 마지막 글자 뒤 자간이 만드는 우측 쏠림 상쇄 */
  font-variant-numeric: tabular-nums;
  color: var(--text-primary, #0f172a);
  line-height: 1.2;
}

/* 시스템 승인 «실패» 분기의 복귀 링크 자리 (2026-09-14 사용자 확정 A-2안 · 시안 sys-approve-dead-end).
   `auth/system-approve`(approvable=false)·`auth/system-approved`(approved=false) 두 화면이 같은 값을
   써야 해서 처음부터 글로벌 (`.sys-code` 승격 선례와 같은 근거 — 주석이 「한 벌」이라 말하는데 선언이
   둘이면 그 자체가 결함 신호). 링크 자체는 글로벌 `.card-action-link` 어휘를 그대로 쓴다.
   ⚠ 성공 분기에 이 래퍼를 달지 ❌ — 「결과 화면에 CTA ❌」 박제는 성공 영수증이 대상이다 */
.sys-relogin-wrap {
  margin-top: 20px;
  text-align: center;
}

/* 안내 박스 안 「본문 ↔ 복구 액션」 층 구분 행 (2026-08-10 UI 감사 — page-local 3벌 승격:
   login `.login-error-recover` · member-link `.ml-owner-recover`(ADR 0174) · recover `.recover-error-chat`).
   박스 *내부* 층 구분선이라 「각주에 hairline 장식 ❌」 대상 아님 (2026-08-09 사용자 확정). page-local 재복제 ❌ */
.auth-notice-action {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--color-slate-200, #e2e8f0);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-secondary, #475569);
}
.auth-notice-action a {
  color: var(--color-primary-700, #047857);
  font-weight: 700;
}
/* 크기는 부모(12.5px) 상속 — 문장 속 인라인 링크라 단독 링크 13px 토큰 대상 아님.
   hover 방향만 4슬롯 공통(밝아짐)으로 정렬 (2026-08-12 UI 감사) */
@media (hover: hover) {
  .auth-notice-action a:hover {
    color: var(--color-primary-600, #059669);
  }
}

/* 「넘어설 수 없는 거절」 변형 — 공개 가입 폼이 같은 박스로 «두 가지 다른 말»을 하기 때문에 필요하다
   ([[0769]], 2026-09-18 사용자 확정 B안): 번호만 겹침 = 「그대로 신청할 수 있어요」 1차 확인(중립 유지) ↔
   동일인·이미 신청함 = 컨펌으로 못 넘는 거절. 실측(test 서버)에서 둘이 같은 회색이라 거절이 바로 위 폼 힌트와
   같은 체급으로 읽혔고, 같은 409 가 3번 연달아 나갔다.
   신호 = 면이 아니라 **문구 굵기 + 6px dot + red hairline** — Toast 「흰 면 + hairline, 상태는 문구 + 좌측 dot」과 같은 어휘라
   tint 면 재도입이 아니다. hairline·dot 값은 `.btn-danger` 와 한 벌(#fecaca / --status-error).
   ⚠ 중립 `.auth-notice` 자체를 빨갛게 하지 ❌ — login·recover·invitation 이 공유하는 「사용자 잘못 아닌 안내」 박스다. */
.auth-notice--blocked {
  position: relative;
  padding-left: 30px; /* dot(6px) + 좌 16 + 간격 8 */
  background: #fff;
  border-color: #fecaca;
  color: var(--text-primary, #0f172a);
  font-weight: 700;
}
.auth-notice--blocked::before {
  content: '';
  position: absolute;
  left: 16px;
  top: 18px; /* 첫 줄 시각 중앙 (padding 12 + line-height 19.5/2 - dot 3) */
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--status-error);
}
/* 둘째 줄·액션 줄은 중립 톤으로 되돌린다 — 「사실 ↔ 그래서 어떻게」 층 구분이 굵기로도 서게 */
.auth-notice--blocked .auth-notice-line2,
.auth-notice--blocked .auth-notice-action {
  font-weight: 500;
  color: var(--text-secondary, #475569);
}
.auth-notice--blocked .auth-notice-action {
  border-top-color: #fecaca;
}

/* 13px = auth 안내 한 줄 계열 공통(`.auth-notice` 와 한 벌). 구 14px 은 그룹 유일값이라
   같은 역할 문장이 페이지마다 다른 체급으로 읽혔다 (2026-08-12 UI 감사) */
/* 간격 소유자 = 부모 `.invite-action-group` gap 하나 — 구 margin-bottom:4px 는 gap 16 과 겹쳐
   실효 20px 을 만들었고, 두 규칙이 한 간격을 나눠 가지면 한쪽만 바뀔 때 조용히 어긋난다
   (2026-08-18 사용자 확정 B안, 시안 confirm-spacing-pair). margin 재도입 ❌ */
/* 좌측 고정 — `invitation/accept` 은 `.invite-card { text-align: center }` 아래라 선언이 없으면
   같은 카드 안에서 이 줄만 중앙으로 갈린다. 형제 `.auth-notice`·`.invite-perm-note`·`.invite-scope-label`·
   `.auth-target`·`.social-recent-hint` 가 이미 좌측 선언이고, 비로그인 실렌더가
   `.invite-perm-note`(좌) → 여기(중앙) → 소셜 버튼 → `.social-recent-hint`(좌) 라
   읽기 시작점이 한 번 튀었다가 돌아온다 (2026-09-13 UI 감사 R — 2026-09-04 `.social-recent-hint` 와 같은 근거) */
.invite-login-guide {
  font-size: 13px;
  color: var(--text-secondary);
  margin: 0;
  padding: 0 2px;
  text-align: left;
}

/* invite 액션 버튼 = 표준 btn-lg btn-full 체급 (2026-07-20 page-local 재정의 폐지) */

/* Social Login Buttons (OAuth 외부 채널 마크 컨텍스트) */
.social-login-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.social-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 13px 16px;
  /* auth 비카드 박스 radius 10px 통일 편입 (2026-08-04 사용자 확정 — OAuth 예외는 색 한정, 지오메트리는 auth 면 정합) */
  border-radius: 10px;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  transition: filter 0.15s ease;
  text-decoration: none;
  border: 1px solid transparent;
  font-family: inherit;
}
@media (hover: hover) {
  .social-btn:hover {
    text-decoration: none;
    filter: brightness(0.96);
  }
}
.social-btn.loading {
  pointer-events: none;
  opacity: 0.7;
}

/* 「최근 사용」 힌트 — 마지막 로그인 성공 수단 1개에만 ([[0507]]).
   배지를 버튼 *밖* 우측에 두는 이유 = .social-btn 은 아이콘+라벨 중앙 정렬(justify-content:center)이라
   안에 넣으면 그 축이 깨지고, provider 3종 배경색(카카오 노랑·네이버 초록·구글 흰) 위 대비를
   각각 새로 정해야 한다. 행 래퍼가 그룹의 flex 자식이 되므로 gap 소유자는 .social-login-group 그대로. */
.social-recent-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.social-recent-row .social-btn {
  flex: 1;
  min-width: 0;
}
.social-recent-badge {
  flex: none;
  padding: 3px 8px;
  border-radius: 6px;
  background: var(--color-slate-900, #0f172a);
  color: #fff;
  /* 12px 바닥 준수 — 배지라고 더 줄이지 않는다 (frontend-guide 최소 본문 체급). */
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
/* 13px = auth 안내 한 줄 계열 공통(.auth-notice 와 한 벌, 2026-08-12 UI 감사)보다 한 단 아래 —
   버튼에 붙는 각주라 본문 안내와 같은 체급이면 배지와 경쟁한다. */
.social-recent-hint {
  margin: -2px 0 0 2px;
  /* 좌측 고정 — `invitation/accept` 은 `.invite-card { text-align: center }` 아래라 선언이 없으면
     같은 원자가 login 은 좌측, accept 은 중앙으로 갈린다. 위 `margin-left: 2px` 자체가 버튼 글자
     시작선에 맞추는 값이라 중앙 정렬에서는 축이 어긋난 채 뜻도 잃는다 (2026-09-04 UI 감사).
     같은 카드의 `.auth-notice`·`.invite-perm-note`·`.invite-scope-label`·`.auth-target` 이 이미 좌측 선언. */
  text-align: left;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--color-slate-500, #64748b);
}
.social-kakao { background: #FEE500; color: #191919; }
/* 마크 크기 소유자 — `icons :: kakao` 프래그먼트는 치수를 안 들고 나온다 ([[0374]] 「아이콘 소실」 함정).
   20px = 구 인라인 값 그대로라 시각 변화 0. 색은 버튼 `color: #191919` 상속(신원 축) */
.social-kakao svg { width: 20px; height: 20px; }
@media (hover: hover) {
  .social-kakao:hover { background: #FEE500; color: #191919; }
}
.social-naver { background: #03C75A; color: #fff; }
/* 마크 크기 소유자 — `icons :: naver` 프래그먼트는 치수를 안 들고 나온다 ([[0374]] 「아이콘 소실」 함정).
   15px = 구 인라인 `20px × scale(0.75)` 실렌더값 승계라 시각 변화 0. 색은 버튼 `color: #fff` 상속 */
.social-naver svg { width: 15px; height: 15px; }
/* 구글 = 다색 고정 fill 이라 조각이 색을 들고 나온다 — 치수만 소비처가 소유
   (provider 별 개별 소유자 = 카카오 20 · 네이버 15 · 구글 20, 실렌더 치수가 달라 공통 규칙 ❌) */
.social-google svg { width: 20px; height: 20px; }
@media (hover: hover) {
  .social-naver:hover { background: #03C75A; color: #fff; }
}
.social-google { background: #fff; color: var(--color-slate-900, #0f172a); border-color: var(--border-default); }
@media (hover: hover) {
  .social-google:hover { background: #fff; color: var(--color-slate-900, #0f172a); border-color: var(--border-strong); }
}

@media (max-width: 480px) {
  /* 구 .invite-card padding:0 잔재 제거 — 카드리스 SSOT 블록이 전 폭에서 이미 0 (2026-08-12 UI 감사) */
  .social-btn {
    font-size: 14px;
    padding: 12px 14px;
  }
}

/* ============================================================================
   R18. SETUP WIZARD STEPPER (.qs-stepper)
   admin/setup/quickstart 2단계 stepper (이용권 → 회원) · D.MONO hairline · B+1 톤
   진행 신호 = 라벨 점(dots)만 단일화. STEP 카운터·게이지 bar(accent rail) 폐지(2026-06-11)
   ============================================================================ */
.qs-stepper {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 0 14px;
  margin: 0 0 20px;
  border-bottom: 1px solid #eaecef;
}
.qs-stepper-dots {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.qs-dot-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  min-width: 44px;
}
.qs-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-slate-200);
  transition: all 0.2s;
}
.qs-dot-wrap.done .qs-dot {
  background: var(--color-primary-600);
}
.qs-dot-wrap.active .qs-dot {
  width: 24px;
  border-radius: 999px;
  background: var(--color-primary-600);
}
.qs-dot-label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em; /* 한글 라벨 자간 — ADR 0113 (제3값 ❌) */
  text-transform: uppercase;
  color: var(--color-slate-600);
  transition: color 0.2s;
}
.qs-dot-wrap.done .qs-dot-label,
.qs-dot-wrap.active .qs-dot-label {
  color: var(--color-slate-900);
}

@media (max-width: 768px) {
  .qs-stepper {
    gap: 10px;
    padding-bottom: 12px;
    margin-bottom: 16px;
  }
  .qs-stepper-dots { gap: 6px; }
  .qs-dot-wrap { min-width: 36px; gap: 4px; }
  .qs-dot-label { font-size: 12px; }
}

/* ============================================================
   Selection Card — 공용 "선택/이동 카드" 통일 어휘 (2026-06-05 추출)
   사용처: auth/select-tenant(.tenant-card), admin/setup/quickstart(.qs-mode-card),
           member/pay(.pay-plan-card·.pay-method-item), 등.
   각 페이지는 .selection-card 위에 콘텐츠 전용 클래스를 얹어 사용한다.
   장식 아이콘은 넣지 않음 — 텍스트(+실제 로고/이미지) 중심.
   ============================================================ */
.selection-card {
  display: flex;
  align-items: center;
  gap: 15px;
  padding: 16px 18px;
  background: #fff;
  border: 1px solid var(--border-default, #e2e8f0);
  /* radius 14 — §PWA radius 14 단일 (2026-08-10 UI 감사 A안, 구 16 은 미등재 잔존값).
     같은 화면 주문서·대기 카드가 14 라 Step 1→2 재질이 연속된다. 사용처 = member/pay 플랜 카드 1곳 */
  border-radius: 14px;
  cursor: pointer;
  text-align: left;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
  transition: transform .12s ease, border-color .2s, box-shadow .2s, background .15s;
  -webkit-tap-highlight-color: transparent;
  font: inherit;
  color: inherit;
}
.selection-card:focus-visible {
  border-color: var(--color-primary-600);
  box-shadow: 0 8px 24px -10px rgba(5, 150, 105, .28);
  transform: translateY(-2px);
  outline: none;
}
@media (hover: hover) {
  .selection-card:hover {
    border-color: var(--color-primary-600);
    box-shadow: 0 8px 24px -10px rgba(5, 150, 105, .28);
    transform: translateY(-2px);
    outline: none;
  }
}
/* 터치(호버 없는 환경) 탭 피드백 */
.selection-card:active {
  transform: scale(.985);
  background: var(--color-primary-50, #ecfdf5);
  border-color: var(--color-primary-600);
}

/* 진입 chevron 모션 (선택 카드 우측 화살표에 .selection-card-arrow 부여) */


/* radio 변형 — 선택(checked) 상태 강조. native radio(:has) / .selected / .checked 모두 지원 */
.selection-card--radio:has(input:checked),
.selection-card--radio.selected,
.selection-card--radio.checked {
  border-color: var(--color-primary-600);
  background: var(--color-primary-50, #ecfdf5);
  box-shadow: inset 0 0 0 1px var(--color-primary-600);
}
/* :focus-within ❌ (2026-08-14 사용자 신고 — member/pay 결제 세그먼트와 같은 결함):
   카드를 탭하면 안의 라디오가 포커스를 계속 쥐어 선택 신호 위에 초록 링이 겹쳐 남고
   다른 곳을 누를 때까지 안 사라진다. :has(input:focus-visible) = 키보드 탭에만 켜짐
   (선례 = .issuer-card·.custom-select-trigger — frontend-guide §27). */
.selection-card--radio:has(input:focus-visible) {
  box-shadow: 0 0 0 3px rgba(5, 150, 105, .15);
  outline: none;
}

@media (max-width: 480px) {
  .selection-card { padding: 16px; }
}

/* ===== 관원 초대 모달 (fragments/member-invite-modal) — 관원 목록·설정 공유 컴포넌트 ===== */
/* 관원 로그인 진입 QR·링크. 인쇄(현장 부착) + 링크 복사(단톡·문자) 두 경로.
   QR 데이터 = 설정 API 재사용, JS = member-invite-modal.js */

.mi-qr-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 6px 0 2px;
}

/* 모달 내부 타일 = radius 10 단일 (§디자인 톤 — 표준 .modal-data-card 가 기준값).
   구 --radius-xl(16)·img 12 는 한 모달에 16/12/9 세 값이 섞이고 정작 10 이 없던 상태 */
.mi-qr-frame {
  background: var(--color-slate-50);
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  padding: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 230px;
  min-height: 230px;
}

.mi-qr-frame img {
  width: 200px;
  height: auto;
  border-radius: 10px;
  /* 안쪽 hairline 재도입 ❌ — 바깥 `.mi-qr-frame` 이 같은 선을 이미 갖고 있어 동심 2겹이 됐다
     (carded surface = hairline 1겹). QR 흰 면은 slate-50 타일 위 톤 차로 분리된다.
     2026-08-23 UI 감사 R — 소비처 2곳(member-invite-modal · member-join-qr-modal) 공통 */
  display: none;
}

.mi-qr-frame img.is-loaded { display: block; }

/* 로그인 링크 표시 — URL 토큰이라 무어절, break-all 오버라이드 (frontend-guide §22) */
.mi-link-chip {
  background: var(--color-slate-50);
  border: 1px solid var(--border-hairline);
  /* 모달 내부 타일 radius 10 단일 — 구 9px 는 즉석 발명값 */
  border-radius: 10px;
  padding: 9px 14px;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-slate-700);
  word-break: break-all;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* 「관원이 직접 봐요」 dot 리스트 카드 — 앞 형제가 .mi-qr-wrap 이라 인접 카드 14px 규칙이 안 닿아
   자기 margin-top 소유 (모달 본문 섹션 간격 14 단일). 구 .mi-qr-hint(목적지만 말하던 한 줄)는
   2026-08-17 이 카드로 대체 — 재도입 ❌ */
.mi-value-card {
  margin-top: 14px;
}

/* 자기신고 층 — 「관원에게 보냈어요」(허브 전용). 푸터 3버튼은 «내보내기», 이 버튼은 «끝냈다는 보고»라
   축이 달라 층으로 나눈다 (2026-08-28 사용자 확정 A안 · 시안 `invite-modal-primary-hierarchy`,
   선례 = ADR 0271 「부가 액션은 본문 맨 아래 전폭」). 푸터로 되돌리면 toolbar primary 가 둘이 돼
   ≤480px 규칙이 전폭 줄을 두 개 만든다(실측 375px 3행) — 재도입 ❌.
   위 간격 14 = 모달 본문 섹션 간격 단일. 버튼 자체 여백은 글로벌 `.btn-full`(margin-top 8)이 소유. */
.mi-selfreport {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border-hairline);
}
/* 「이걸 누르면 무엇이 되는가」 — 라벨만으로는 허브 체크리스트와의 연결이 안 보인다.
   §보조 각주 토큰(12.5/slate-500) 재사용 · 중앙 정렬은 전폭 버튼 아래 단독 줄이라 */
.mi-selfreport-hint {
  margin-top: 7px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--color-slate-500);
  text-align: center;
}

@media (max-width: 768px) {
  .mi-qr-frame {
    padding: 12px;
    min-width: 186px;
    min-height: 186px;
  }
  .mi-qr-frame img { width: 160px; }
}

/* 인쇄 — 인쇄 시점(beforeprint)에 JS(member-invite-modal.js)가 body 직속에 QR 사본 #qrPrintRoot 를
   심고 afterprint 에 제거한다. 모달 내부를 직접 인쇄 표면으로 쓰지 ❌ —
   ① 전역 print 규칙(§29 .modal-overlay display:none !important)이 조상째 죽여 visibility 로 못 뚫고
   ② .modal 의 transform(스프링 팝인)이 남아 fixed 기준점이 페이지가 아니라 모달이 되며
   ③ 본문을 visibility 로만 숨기면 레이아웃 높이가 남아 빈 페이지가 양산된다.
   body 직속 사본 + 형제 display:none = 정확히 1페이지 중앙에 QR 만. */
#qrPrintRoot { display: none; }

@media print {
  body:has(#qrPrintRoot) > *:not(#qrPrintRoot) { display: none !important; }
  #qrPrintRoot {
    display: block;
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
  }
  #qrPrintRoot img { width: 300px; height: auto; }
}

/* ── 가입 신청 승인 큐 (관원 목록 상단 카드 — fragments/join-request-queue) ──────────────
   무통장 입금 대기 큐와 같은 「카드 하나 + hairline 행」 자기 소진 큐 (2026-08-20,
   시안 member-signup-approval-compare 1안). 데스크톱·모바일 한 프래그먼트 공유. */
.jq-card { margin-bottom: 16px; }
.jq-row {
  display: flex;
  align-items: center;
  gap: 14px;                   /* 한 줄 행 — 구 「캡션 2행 낙하」용 flex-wrap·세로 gap 은 2026-08-21 캡션 폐지로 소멸 */
  padding: 13px 22px;
  border-top: 1px solid var(--border-hairline);
}
.jq-list > .jq-row:first-child { border-top: none; }
.jq-name { font-size: 13.5px; font-weight: 700; color: var(--text-primary); flex-shrink: 0; }
.jq-phone { font-size: 13px; font-weight: 500; color: var(--color-slate-600); font-variant-numeric: tabular-nums; }
.jq-time { font-size: 12px; color: var(--color-slate-500); font-variant-numeric: tabular-nums; }
/* 「번호 중복 · {이름} 관원」 — 오류가 아니라 확인할 사실이라 warning 톤(slate-600 800 순수 텍스트,
   §상태 뱃지 「배경·테두리 0」). 등록 확인 모달이 같은 사실을 meta 행으로 받아 컨펌한다 (2026-08-22 보호자 번호 공유). */
.jq-dup { font-size: 12px; font-weight: 800; color: var(--color-slate-600); flex-shrink: 0; }
.jq-actions { margin-left: auto; display: flex; gap: 8px; flex-shrink: 0; }
/* 구 .jq-meta 캡션(생년월일·성별·주소 둘째 줄, 2026-08-21 오전 확정)은 같은 날 사용자 재확정으로 폐지 —
   행은 이름·연락처·시각·액션 한 줄만, 상세는 등록 확인 모달 meta 행이 전담(JoinQueue.buildMeta). 재도입 ❌. */

/* ── 새로 온 관원 카드 (관원 목록 상단 — fragments/new-member-card) ──────────────
   가입 신청 큐(.jq-*)의 «형제» — 같은 「카드 하나 + hairline 행」 골격을 쓰고 0명이면 카드째 미렌더.
   다른 점은 비는 방식뿐: 신청 큐는 «처리하면», 이 카드는 «7일이 지나면» 저절로 빈다
   (창·접기 하한 SSOT = MemberConstants.NEW_MEMBER_WINDOW_DAYS · NEW_MEMBER_BULK_FOLD_MIN).
   지오메트리를 .jq-* 와 «같은 값»으로 두는 것이 요점 — 두 카드가 나란히 서므로 한쪽만 달라지면
   행 높이·시작선이 어긋나 「다른 물건」으로 읽힌다. 값을 바꿀 땐 두 블록을 함께 볼 것. */
.nm-card { margin-bottom: 16px; }
.nm-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 22px;
  border-top: 1px solid var(--border-hairline);
  text-decoration: none;
}
/* 첫 자식이 낱낱 행이든 접힌 묶음(`.nm-fold`)이든 카드 헤더 divider 와 hairline 이 겹치지 않게 —
   낱낱 행 0명 + 묶음만 있는 날(이관 당일)엔 첫 자식이 묶음이다 (2026-09-17 fast-analyze) */
.nm-list > :first-child { border-top: none; }
.nm-name { font-size: 13.5px; font-weight: 700; color: var(--text-primary); flex-shrink: 0; }
.nm-phone { font-size: 13px; font-weight: 500; color: var(--color-slate-600); font-variant-numeric: tabular-nums; }
/* 이용권이 아직 없다 = 그 행의 유일한 「안 끝난 일」이라 이 조각만 진하다.
   red ❌ (오류가 아니라 다음에 할 일) · 채움 배지 ❌ — 가르는 것은 색이 아니라 무게다. */
.nm-need { font-size: 12px; font-weight: 800; color: var(--color-primary-700); flex-shrink: 0; }
.nm-pass { font-size: 12px; font-weight: 500; color: var(--color-slate-500); flex-shrink: 0; }
.nm-when { margin-left: auto; font-size: 12px; color: var(--color-slate-500); flex-shrink: 0; }
@media (hover: hover) {
  .nm-row:hover { background: var(--bg-row-hover); }
}
/* 접힌 묶음 한 줄 — 「9월 16일에 53명 한꺼번에 등록」. 무면 텍스트 행(버튼 면 ❌ — 카드 안 행들과
   같은 층이어야 목록으로 읽힌다). 셰브론은 펼침 상태에서 90° 돈다. */
.nm-fold {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 13px 22px;
  border: none;
  border-top: 1px solid var(--border-hairline);
  background: none;
  font-size: 13px;
  font-weight: 500;
  color: var(--color-slate-600);
  text-align: left;
  cursor: pointer;
}
.nm-fold strong { font-weight: 800; color: var(--text-primary); }
.nm-fold-chev {
  width: 16px;
  height: 16px;
  margin-left: auto;
  color: var(--text-tertiary);
  flex-shrink: 0;
  transition: transform 0.2s var(--ease-smooth);
}
.nm-fold[aria-expanded="true"] .nm-fold-chev { transform: rotate(90deg); }
@media (hover: hover) {
  .nm-fold:hover { background: var(--bg-row-hover); }
  .nm-fold:hover .nm-fold-chev { color: var(--color-primary-600); }
}

@media (max-width: 768px) {
  /* 폰은 연락처를 접는다 — 관원 목록 카드가 이미 쓰는 방식(이름·상태·등록일 세 조각이면 320px 까지 한 줄).
     사진·상태·등록일은 남긴다: 「누구였더라」에 답하는 조각들이다. */
  .nm-row, .nm-fold { padding: 12px 16px; gap: 8px; }
  .nm-phone { display: none; }
  /* 상태 요약은 벨트 «정식 이름»을 쓴다(「기간권 · 키즈 오렌지/블랙벨트」) — 320px 에서 이름·등록일까지 더하면
     넘치는데 `.card{overflow:hidden}` 이라 접히지 않고 잘린다. 이 조각만 줄바꿈을 허용한다(이름·등록일은 그대로).
     `.nm-row{flex-wrap}` ❌ — 등록일이 둘째 줄로 떨어져 「누구·언제」 짝이 갈린다 (2026-09-17 fast-analyze) */
  .nm-pass, .nm-need { flex-shrink: 1; min-width: 0; }
}

@media (max-width: 768px) {
  .jq-row { flex-wrap: wrap; gap: 6px 10px; padding: 12px 16px; }
  /* 액션은 아랫줄 전폭 — 등록 우위(무통장 대기 큐 모바일과 같은 위계), 상한 420px (폰 전용 규칙 상한 의무).
     구 order:2(캡션보다 뒤)는 캡션 폐지로 소멸 — DOM 순서(정보 → 액션)가 곧 표시 순서. */
  .jq-actions {
    flex-basis: 100%;
    max-width: 420px;
    margin-left: 0;
  }
  /* 실제 tier = 등록 **primary** · 반려 danger (ADR 0358 ③ 「등록 = `btn-primary btn-sm` 은 불변,
     구 secondary 전환은 되돌렸다」 2026-08-22 사용자 재확정 — 관원 목록 툴바 primary 와 공존).
     ⚠️ 셀렉터가 tier 를 따라오지 않으면 매칭 0이라 두 버튼이 자연 폭으로 몰려 「등록 우위」가 실현되지 않는다
     — 구 `.btn-primary`(tier 가 secondary 이던 시절) → `.btn-secondary`(tier 가 primary 로 되돌아간 뒤)로
     **정확히 반대 방향으로 두 번 어긋났다**(2026-08-22 UI 감사 실측). tier 를 바꾸면 이 줄도 함께 바꿀 것.
     미러 원본(payments 대기 큐 `.payment-approve-btn{flex:1}` / `.payment-reject-btn{flex-shrink:0}`)과 같은 문법 */
  .jq-actions .btn-primary { flex: 1 1 0; min-width: max-content; }
  .jq-actions .btn-danger { flex-shrink: 0; }
}

/* 가입 신청 QR 모달 — 「신청 폼 항목」 인라인 토글 (2026-08-21 사용자 확정 B안, 시안 join-qr-form-fields).
   설정 「가입 신청」 그룹과 같은 값·같은 즉시 저장 API — 링크를 보내는 자리(관원 목록 진입 포함)에서
   폼이 뭘 받는지 확인·변경까지 되게 한다. 모달 내부 타일 radius 10 단일(§디자인 톤). */
.mj-fields {
  margin-top: 14px; /* 모달 본문 섹션 간격 14 단일 — 앞 형제 .mi-qr-wrap 이라 자기 margin 소유 */
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  overflow: hidden;
}

.mj-fields-head {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em; /* 한글 마이크로 라벨 자간 (§디자인 톤) */
  color: var(--color-slate-500);
  padding: 10px 14px 8px;
  /* 섹션 머리 = 무면 마이크로 라벨 (§섹션 머리) — slate-50 채움 스트립 재도입 ❌.
     하단선은 행/카드 구조 어휘라 대상 아님 (같은 조항 명시 예외). */
  border-bottom: 1px solid var(--border-light);
}

.mj-field-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
}

.mj-field-row + .mj-field-row { border-top: 1px solid var(--border-light); }

.mj-field-name {
  flex: 1;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-primary);
}

/* 보조 각주 토큰 (§타이포) — 설정 그룹 힌트와 같은 문장·같은 톤 */
.mj-fields-note {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);
  line-height: 1.55;
  padding: 10px 14px;
  background: var(--color-slate-50);
  border-top: 1px solid var(--border-light);
}

/* QR 포스터 인쇄 — #qrPrintRoot 와 같은 원리(body 직속 사본 + 형제 display:none),
   내용만 A4 포스터(도장 로고 + 제목 + 안내 + QR + URL + 워드마크 — 시안 member-signup-qr-delivery 3안).

   ⚠️ 소비처 «둘»이 이 선언을 공유한다 — 제목과 안내 문장만 다르고 기하는 완전히 같다:
     · #joinPosterPrintRoot = 가입 신청 QR (member-join-qr-modal.js) — 「{도장명} 가입 신청」
     · #gymPosterPrintRoot  = 도장 페이지 QR (gym-page-qr-modal.js) — 도장명 하나
   선언을 복사해 갈라 놓지 ❌ — 갈리면 같은 프린터에서 나온 두 포스터의 여백·글자 크기가 달라진다.
   셋째가 생기면 그때 `.poster-print-root` 클래스로 승격할 것(§원자 승격 기준 「세 번째에서 검토」). */
#joinPosterPrintRoot,
#gymPosterPrintRoot { display: none; }

@media print {
  body:has(#joinPosterPrintRoot) > *:not(#joinPosterPrintRoot),
  body:has(#gymPosterPrintRoot) > *:not(#gymPosterPrintRoot) { display: none !important; }
  #joinPosterPrintRoot,
  #gymPosterPrintRoot {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding-top: 110px;
  }
  #joinPosterPrintRoot .jp-logo,
  #gymPosterPrintRoot .jp-logo {
    width: 76px; height: 76px; border-radius: 50%; object-fit: cover;
    margin-bottom: 22px;
  }
  #joinPosterPrintRoot .jp-title,
  #gymPosterPrintRoot .jp-title {
    font-size: 30px; font-weight: 800; letter-spacing: -0.02em; color: var(--color-slate-900);
    margin-bottom: 10px;
  }
  #joinPosterPrintRoot .jp-sub,
  #gymPosterPrintRoot .jp-sub {
    font-size: 15px; color: var(--color-slate-600); line-height: 1.6;
    margin-bottom: 34px;
  }
  #joinPosterPrintRoot .jp-qr,
  #gymPosterPrintRoot .jp-qr { width: 320px; height: auto; }
  #joinPosterPrintRoot .jp-url,
  #gymPosterPrintRoot .jp-url {
    font-size: 13px; color: var(--color-slate-500); font-variant-numeric: tabular-nums;
    margin-top: 26px; word-break: break-all;
  }
  #joinPosterPrintRoot .jp-brand,
  #gymPosterPrintRoot .jp-brand {
    font-size: 14px; font-weight: 800; color: var(--color-primary-700);
    margin-top: 46px;
  }
}

/* ╔══════════════════════════════════════════════════════════╗
   ║  MOTION SYSTEM — 하이브리드 rich/delight (2026-07-16)      ║
   ║  body[data-motion="rich"]    = admin·member 운영·system    ║
   ║  body[data-motion="delight"] = landing·auth·quickstart·    ║
   ║                                kiosk·member PWA 홈        ║
   ║  스코프 클래스는 Motion.autoStagger()(common.js)가 부여     ║
   ╚══════════════════════════════════════════════════════════╝ */

/* ── 진입 스태거 ── */
@keyframes motionEnterUp {
  /* 이동 거리 16 → 10px (ADR 0542 모션 패키지 — 덜 출렁이는 착지. .animate-in·.motion-enter·행 스태거 공유 값) */
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

@keyframes motionEnterSpring {
  0%   { opacity: 0; transform: translateY(24px) scale(0.98); }
  60%  { opacity: 1; transform: translateY(-3px) scale(1.004); }
  100% { opacity: 1; transform: none; }
}

/* fill-mode 는 backwards 고정 (2026-07-22) — both 는 종료 후에도 transform(identity)을 영구
   잔류시켜 내부 position:fixed 요소(.ios-settings-actions 등)의 containing block 을 페이지로
   바꿔버림(뷰포트 고정 상실). backwards 는 delay 구간 from-상태만 채우고 종료 후 자연 상태 복귀. */
body[data-motion] .motion-enter {
  /* quint + 시차 0.07 → 0.09s (ADR 0542 — 「차례로 놓이는」 리듬. 8블록 캡이라 마지막 지연 0.72s) */
  animation: motionEnterUp 0.5s var(--ease-out-quint) backwards;
  animation-delay: calc(var(--motion-i, 0) * 0.09s);
}

body[data-motion="delight"] .motion-enter {
  animation-name: motionEnterSpring;
  animation-duration: 0.6s;
  animation-timing-function: var(--ease-spring);
}

/* ── 목록 행 스태거 (Motion.autoStagger 가 --motion-i·--motion-step 부여 — 캡은 행 개수가
   아니라 총 길이: 스텝이 행수에 반비례해 좁혀지므로 마지막 행도 항상 0.70s 안에 끝난다) ── */
@keyframes motionRowIn {
  from { opacity: 0; transform: translateX(-12px); }
  to   { opacity: 1; transform: none; }
}

body[data-motion] .motion-row {
  animation: motionRowIn 0.45s var(--ease-out-expo) backwards;
  animation-delay: calc(0.15s + var(--motion-i, 0) * var(--motion-step, 0.05s));
}

/* ── 카드 hover 리프트 (hover 시점만 — D.MONO static shadow 금지는 유지, ADR 개정) ── */
body[data-motion] .card {
  transition: transform 0.22s var(--ease-out-expo), box-shadow 0.22s ease;
}

@media (hover: hover) {
  body[data-motion] .card:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 24px -14px rgba(15, 23, 42, 0.16);
  }
}

/* ── 갭 채우기 (2026-07-30 LV1 통일) — .card 를 안 쓰는 page-local 카드에 동일 리프트 어휘 확장 ── */
body[data-motion] .att-group-card,
body[data-motion] .att-tl-card,
body[data-motion] .md-card,
body[data-motion] .md-passcard,
body[data-motion] .detail-reserved-card,
body[data-motion] .td-stat,
body[data-motion] .next-step-card {
  transition: transform 0.22s var(--ease-out-expo), box-shadow 0.22s ease;
}
/* .pxb-card 제외 (2026-08-15 UI 감사) — 2026-08-12 「모바일 목록 행 3면 통일」로 면·radius 를 잃고
   padding + border-bottom hairline 만 남은 *행* 이 됐다. 리프트가 걸리면 부모 .card 리프트와 겹쳐
   카드 안에서 행이 또 뜨고, 페이지가 이미 가진 background hover 와 hover 문법이 2벌 발화한다.
   같은 목록의 데스크톱 행 .pxb-row 는 아래 「클릭 가능한 행 hover」 그룹 소속 — 그쪽이 정답 어휘.

   구 .attc-card(출석 빈 상태 월 달력, ADR 0628)는 ADR 0648 으로 달력이 날짜칸 안으로 들어가며
   **사라졌다** — 그 카드를 이 목록에서 뺐던 이유(안에 누르는 것이 42개 들어찬 카드라 카드째 뜨면
   hover 문법이 2벌 발화)는 새 달력에도 그대로 걸린다. 팝오버 달력에 리프트 부여 ❌ */

@media (hover: hover) {
  body[data-motion] .att-group-card:hover,
  body[data-motion] .att-tl-card:hover,
  body[data-motion] .md-card:hover,
  body[data-motion] .md-passcard:hover,
  body[data-motion] .detail-reserved-card:hover,
  body[data-motion] .td-stat:hover,
  body[data-motion] .next-step-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 24px -14px rgba(15, 23, 42, 0.16);
  }
}

/* ── 모달 스프링 팝인 (키프레임판) — transition 기반 .modal(§15) 과 동일 곡선.
   .active 토글 특수 모달(postcode·crop)의 공용 어휘 (2026-07-30 통일) ── */
@keyframes motionModalPop {
  from { opacity: 0; transform: scale(0.93) translateY(24px); }
  to   { opacity: 1; transform: none; }
}

/* ── 소형 팝오버 리빌 — 드롭다운·스위처 메뉴 공용 (구 downloadMenuReveal 흡수) ── */
@keyframes motionPopoverIn {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}

/* ── 인페이지 스텝 전환 — 모바일 VT push/pop 문법의 같은 문서 안 미러 (member/pay 2-step 등).
   JS 가 표시 직후 클래스 부여 (재부여 시 제거→reflow→추가로 재생) ── */
@keyframes motionStepInFwd {
  from { opacity: 0; transform: translateX(28px); }
  to   { opacity: 1; transform: none; }
}
@keyframes motionStepInBack {
  from { opacity: 0; transform: translateX(-28px); }
  to   { opacity: 1; transform: none; }
}
body[data-motion] .motion-step-in-fwd { animation: motionStepInFwd 0.32s var(--ease-vt); }
body[data-motion] .motion-step-in-back { animation: motionStepInBack 0.32s var(--ease-vt); }

/* ── 부분 갱신 페이드 — replaceWith/innerHTML 존 교체 직후 JS 가 부여 (스냅 교체 방지) ── */
body[data-motion] .motion-swap-in {
  animation: fadeIn 0.25s ease;
}

/* 빈 상태 진입 페이드 — display 토글로 나타날 때마다 재생 */
body[data-motion] .empty-state {
  animation: fadeIn 0.25s ease;
}

/* 클릭 가능한 행 hover — 테이블 row hover(#fafbfc) 와 동일 어휘 */
body[data-motion] .att-row,
body[data-motion] .pxb-row,
body[data-motion] .msg-un-row.is-pick,
body[data-motion] .ios-item-clickable {
  transition: background-color 0.15s ease;
}

@media (hover: hover) {
  body[data-motion] .att-row:hover,
  body[data-motion] .pxb-row:hover,
  body[data-motion] .msg-un-row.is-pick:hover,
  body[data-motion] .ios-item-clickable:hover {
    background-color: var(--bg-row-hover);
  }
}

/* hover 색전환 부드럽게 — transition 누락 베이스 일괄 (색·배경·테두리·그림자만, transform 어휘 보유 컨트롤 제외) */
a { transition: color 0.15s ease; }

.pg-head-breadcrumb a,
.fsheet-foot-reset,
.nav-member-logout,
.tenant-switcher-add,
.social-kakao,
.social-naver,
.social-google,
.table th[role="button"],
.alert-modal-actions .btn-cancel,
.alert-modal-actions .btn-confirm,
.filter-active-chip-clear-all {
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}

/* 구 「라이브 pulse dot」(motionLivePulse · .motion-live-dot · .kpi-stat-dot:not(.warn)) = 폐지 (ADR 0260).
   유일 소비처였던 홈 KPI 밴드 라벨 dot 이 사라져 사용처 0 — rich tier 의 상시 반복 모션도 0이 됐다.
   신호점 pulse 재도입 ❌ (되살릴 자리가 생기면 그때 새로 설계). */

/* ── 저장 버튼 체크 morph (Loading.setButton saved 옵션) ── */
@keyframes motionSavedPop {
  0%   { transform: scale(0.9); }
  55%  { transform: scale(1.06); }
  100% { transform: scale(1); }
}

/* 오버레이 방식 — 버튼 내부 마크업(SVG·span) 비파괴. 원 콘텐츠 숨기고 ::after 로 "✓ 저장됨" */
.btn.btn-saved {
  position: relative;
  color: transparent !important; /* 직접 텍스트 노드 숨김 (::after 오버레이가 대체) */
  border-color: var(--color-primary-300) !important;
}

.btn.btn-saved > * {
  visibility: hidden;
}

.btn.btn-saved::after {
  content: '✓ 저장됨';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-primary-700);
  font-weight: 800;
  animation: motionSavedPop 0.4s var(--ease-spring);
}

/* 구 `[data-saved-label]` morph 문구 오버라이드 = 사용처 0 으로 삭제 (2026-08-16 사용자 확정 A안).
   유일 소비처였던 복사 버튼 2곳(사범 초대 링크·도장 복구 링크)이 Toast 단일 신호로 바뀌며 소멸.
   복사 성공에 버튼 morph 재도입 ❌ — 한 동작의 신호는 하나. */

/* ── 즉시 저장 인라인 배지 (common.js SaveSignal, ADR 0178) ──
   즉시 저장 화면의 성공 신호. 「저장 버튼 morph」가 눌린 버튼 자리에서 답하듯,
   이쪽은 고친 행 자리에서 답한다 — 같은 체크 어휘, 같은 1.6s.

   폭을 상시 점유(width 고정 + visibility 토글)하는 게 핵심 — 뜨고 질 때마다
   행 폭이 바뀌면 옆 입력칸이 저장할 때마다 출렁인다. 빈 자리 56px 은 라벨 여백에 묻힌다.

   ios-item 행에서의 자리 = 트레일링 요소(값·입력·토글·셰브론) **앞** (2026-08-19 사용자
   확정 B안, 시안 ios-value-rail): 값 뒤에 세우면 예약분 64px 이 값 우측에 남아, 배지 없는
   저장 바 행(상호명 등)과 값 끝(rail)이 64px 갈린다. 배지가 앞이면 예약분이 라벨 여백에
   묻히고 행의 마지막 요소가 늘 행 padding(18px)을 지킨다 — 주소·토글 행이 원래 쓰던 순서로
   입력 행까지 수렴. 배지를 값 뒤로 되돌리기 ❌ (SaveSignal 은 [data-save-signal] 로 찾으므로
   DOM 순서 무관). ur-item(관원 상세)은 별도 계열 — 우측 배지 열 전 행 예약(ADR 0196) 유지. */
.save-signal {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  flex-shrink: 0;
  width: 56px;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-primary-700);
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  /* visibility 를 전환에서 분리 — 사라진 뒤에야 숨겨야 페이드가 보인다 (§모달 visibility 분리와 같은 이유) */
  transition: opacity 0.2s var(--ease-smooth), visibility 0s linear 0.2s;
}

.save-signal.is-on {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.2s var(--ease-smooth), visibility 0s;
}

.save-signal svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}

.ios-item > .save-signal {
  margin-left: 8px;
}
/* 뒤에 컨트롤(토글 등)이 더 서는 행은 그쪽과도 띄운다 — 행 끝에 붙는 경우는 행 padding 이 담당 */
.ios-item > .save-signal:not(:last-child) {
  margin-right: 8px;
}

/* ── delight 전용: ambient 오브 (마크업 <i class="motion-orb">) ── */
@keyframes motionOrbDrift {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50%      { transform: translate(-16px, 14px) scale(1.08); }
}

body[data-motion="delight"] .motion-orb {
  /* fixed = 소비처 6곳이 인라인으로 쓰던 실제 값. 구 `absolute` 는 전 소비처가 덮어
     한 번도 적용된 적 없는 죽은 선언이었다 (2026-08-23 UI 감사 R, 렌더 변화 0).
     면·모션·position 은 CSS 소유 · 크기·오프셋은 자리마다 달라 소비처 인라인 소유 */
  position: fixed;
  border-radius: 50%;
  pointer-events: none;
  filter: blur(34px);
  opacity: 0.5;
  background: radial-gradient(circle, rgba(167, 243, 208, 0.8), rgba(167, 243, 208, 0) 70%);
  animation: motionOrbDrift 9s ease-in-out infinite;
  z-index: 0;
}

body[data-motion="rich"] .motion-orb { display: none; }

/* ── delight 전용: hero 글로우 펄스 ── */




/* ── delight 전용: 그라데이션 숫자 (hero 강조 숫자만) ── */
@keyframes motionGradientShift {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}

body[data-motion="delight"] .motion-gradient-num {
  background: linear-gradient(90deg, #047857, #10b981, #047857);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: motionGradientShift 4s ease infinite;
}

/* ── 3D 틸트 대상 (transform 은 Motion.initTilt JS 가 관리) ── */
[data-tilt] {
  transition: transform 0.25s var(--ease-out-expo);
}
/* will-change 는 hover 중에만 — 상시 선언하면 틸트가 붙지 않는 터치 기기(initTilt 는 pointer:fine 만)
   에서도 합성 레이어를 영구 점유한다. hover 가 곧 틸트 시작 시점이라 승격 타이밍도 정확하다. */
@media (hover: hover) {
  [data-tilt]:hover {
    will-change: transform;
  }
}

/* ── reduced motion — 모션 시스템 전체 무효 ── */
@media (prefers-reduced-motion: reduce) {
  body[data-motion] .motion-enter,
body[data-motion] .motion-row,
body[data-motion="delight"] .motion-orb,
body[data-motion="delight"] .motion-gradient-num,
.btn.btn-saved {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  @media (hover: hover) {
    body[data-motion] .card:hover { transform: none; }
  }
  [data-tilt] { transition: none; }
}

/* ── 칩 선택 pop (2026-07-16 모션 시스템) — 필터/요일 칩 체크 순간 스프링 팝 ── */
@keyframes motionChipPop {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.08); }
  100% { transform: scale(1); }
}

.filter-chip input:checked + .filter-chip-label,
.filter-chip-label.is-active,
.day-chip input:checked + span {
  animation: motionChipPop 0.3s var(--ease-spring);
}

/* 숫자 변경 pop — JS 가 클래스 재부여로 재생 (결제 대기 뱃지 등) */
.motion-count-pop {
  display: inline-block;
  animation: motionChipPop 0.35s var(--ease-spring);
}

@media (prefers-reduced-motion: reduce) {
  .filter-chip input:checked + .filter-chip-label,
  .filter-chip-label.is-active,
  .day-chip input:checked + span,
  .motion-count-pop {
    animation: none !important;
  }
}

/* ── 게이지 draw-in (2026-07-16 모션 시스템) — 로드 시 0→값 차오름 ── */




/* ── 선택 카드 pop = 폐지 (2026-08-14 사용자 확정 B안 — 시안 pay-select-pop, 구 2026-07-16 모션 시스템 supersede).
   구 선언은 `.selection-card--radio:has(input:checked)` **상태 셀렉터**라, 스텝 전환의 `hidden` 토글
   (display:none → 표시)마다 애니메이션이 처음부터 재생됐다 — member/pay 에서 결제(Step 2) → ‹ 복귀 때마다
   이미 고른 카드가 복귀 슬라이드인 위에서 한 번 더 튀었다(선택은 한 번인데 팝은 복귀마다).
   유일한 사용처인 그 플랜 카드는 「탭 = 선택 + 즉시 Step 2」(ADR 0078)라 **선택 순간엔 목록이 곧바로 숨겨져
   팝이 애초에 안 보인다** — 이 팝이 눈에 띈 유일한 순간이 그 복귀 재생이었다. 그래서 제거해도 잃는 화면이 0.
   선택 신호는 emerald 채움·테두리·가격 색(.selection-card--radio:has(input:checked))이 그대로 담당.
   ❌ 재도입 · ❌ 원샷 클래스(.motion-select-pop 류)로 되살리기 — 이 동선에선 아무도 못 보는 모션이다.
   목록이 남는 새 동선에 .selection-card 를 쓰게 되면 그때 새로 설계할 것. ── */

/* ── 검색 밑줄 grow (2026-07-16 모션 시스템) — focus 시 slate-900 바가 좌→우로 자람 ── */
.search-box::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1.5px;
  background: var(--color-slate-900);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s var(--ease-out-expo);
  pointer-events: none;
}

/* `:focus-within` 유지 — 자식이 검색 입력뿐 (ADR 0616 · 같은 사유) */
.search-box:focus-within::after { transform: scaleX(1); }

/* 즉시 색전환은 grow 바가 대체 — 래핑된 인스턴스만 베이스 hairline 유지 (§27 단일 indicator 정합) */
.search-box .search-input:focus { border-bottom-color: var(--color-slate-200); }

@media (prefers-reduced-motion: reduce) {
  /* .selection-card--radio 항목은 선택 카드 pop 폐지(2026-08-14 B안)와 함께 제거 — 끌 애니메이션이 없다 */
  
  .search-box::after { transition: none; }
}

/* ── 새 행 추가 플래시 (2026-07-16 모션 시스템) — slide-in + emerald 잔광 (일괄 등록 행 추가 등) ── */
@keyframes motionRowFlash {
  0%   { background-color: var(--color-primary-50); }
  100% { background-color: transparent; }
}

body[data-motion] .motion-row-flash {
  animation: motionRowIn 0.4s var(--ease-out-expo),
             motionRowFlash 1.4s ease-out 0.4s backwards;
}

/* ── plan-card hover 리프트 (2026-07-16 모션 시스템) — .card 밖 카드 어휘 보충. [data-tilt] 인라인 transform 이 우선 ── */
body[data-motion] .plan-card {
  transition: transform 0.22s var(--ease-out-expo), box-shadow 0.22s ease;
}

@media (hover: hover) {
  body[data-motion] .plan-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 24px -14px rgba(15, 23, 42, 0.16);
  }
}

@media (prefers-reduced-motion: reduce) {
  body[data-motion] .motion-row-flash { animation: none !important; }
  @media (hover: hover) {
    body[data-motion] .plan-card:hover { transform: none; }
  }
}

/* ── 행 삭제 exit (2026-07-16 모션 시스템) — 표준 삭제 애니 (opacity 0 + scale 0.95 + 0.3s) ── */
@keyframes motionRowExit {
  to { opacity: 0; transform: scale(0.95); }
}

.motion-row-exit {
  animation: motionRowExit 0.3s ease forwards;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .motion-row-exit { animation-duration: 0.01ms !important; }
}

/* =====================================================
   관원 목록 — 승급 훑기 (출결 필터 + 지표 셀)
   「기준은 자르지 말고 표시」 — 미달 관원을 지우지 않고 셀이 스스로 말하게 한다.
   ===================================================== */

/* 조건 미달 행 — 옅은 빨강. 통과 행은 흰색 그대로 */
.members-table tbody tr.is-below { background: #fffbfb; }
@media (hover: hover) {
  .members-table tbody tr.is-below:hover { background: #fff5f5; }
}

/* 지표 셀 — 값 + (미달이면) 차이 캡션 / (통과면) 분수 근거 */
.metric-cell { white-space: nowrap; }
.metric-value {
  display: block;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
/* 통과 값 emerald(`.is-ok`) = 폐지 (2026-08-17 사용자 확정 B안, 시안 metric-cell-signal).
   ① 기준을 넣는 목적이 「누가 못 미치나」 찾기인데 통과 행이 대부분이라 emerald 가 화면을 덮고 red 가 묻혔다
   ② 모바일 카드 렌더러는 원래부터 `is-short` 만 붙여 두 표면의 신호 단계가 갈려 있었다
   ③ 구 조건이 「기준 설정 여부」가 아니라 「값 존재」만 봐서, 기간만 넣고 출석률 기준을 비우면
      비교 대상 없이 전 행에 합격색이 켜졌다 — is-ok 소멸로 원인째 해결.
   emerald 통과 신호 재도입 ❌ · 「통과 표시 누락」 감사 재지적 ❌ (미달만 red 가 정책) */
/* 미달 red = `--status-error-ink` — 흰 면(미달 행 tint #fffbfb 포함) 위 18.66px 미만 글자라
   ADR 0495 「red-500 → -ink」 · ADR 0597 「흰 면 위 red 기본 글자는 -ink 하나」 그대로.
   모바일 카드 짝(.attendance-badge .metric-value.is-short)이 이미 -ink 라 같은 신호가 화면마다
   두 빨강으로 갈려 있던 자리다 (2026-09-05 UI 감사 A). `--status-error`(#ef4444) 복귀 ❌ */
.metric-cell.is-short .metric-value { color: var(--status-error-ink); font-weight: 800; }
.metric-gap {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  font-weight: 700;
  color: var(--status-error-ink);
  font-variant-numeric: tabular-nums;
}
/* 분수는 값의 근거일 뿐이라 후퇴 톤 — 「12 / 24일」. **통과·미달 셀 모두** 그린다 (ADR 0599 —
   구 「통과 셀만」은 근거가 가장 필요한 미달 행에서 분모·분자를 지워, 표본이 작아 우연히 낮은 값과
   진짜 결석이 잦은 값이 같은 빨강으로 보였다). 미달 행에서도 **회색 유지** — 빨강은 값·부족 캡션이
   이미 진다(§slate-400 승격 기준의 「값을 완성하는 보조」 축). 미달 셀 분수에 red 부여 ❌ */
.metric-frac {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

/* 나이 필터 — 범위 두 칸 */
.age-filter-row,
.attend-filter-rates,
.attend-filter-period {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.age-filter-row { gap: 8px; }
.filter-range-sep { font-size: 13px; font-weight: 600; color: var(--text-tertiary); }
.age-filter-hint:empty { display: none; }
.age-filter-hint,
.attend-filter-hint { width: 100%; margin-top: 6px; }

/* 출결 필터 — 기간 한 줄 + 비율 한 줄 */
.attend-filter-row { display: flex; flex-direction: column; gap: 8px; }
.attend-filter-row .attend-date { max-width: 150px; }
/* 모바일 FilterSheet 로 «옮겨진» 출결 기간 — 시작·끝이 한 줄 (payments `.payment-date-range` 선례).
   위 규칙은 조상이 `.attend-filter-row` 라 시트로 이동하는 순간 매칭이 끊기고, 글로벌
   `.form-input{width:100%}` 이 되살아나 날짜 «한 칸»이 한 줄을 통째로 먹었다(`~` 도 홀로 한 줄,
   블록 145px). 시트 안에서는 두 칸을 1fr 로 나눠 담는다 — 일수(`N일`)만 다음 줄로 접힌다. */
@media (max-width: 768px) {
  .attend-filter-period .attend-date { flex: 1 1 0; width: auto; max-width: none; min-width: 0; }
  .attend-filter-period .filter-range-sep { flex-shrink: 0; }
  .attend-filter-days { flex-basis: 100%; }
}
.attend-filter-days {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}
.attend-filter-days:empty { display: none; } /* 무효·역순 기간이면 flex 유령 간격까지 접음 (.age-filter-hint 짝) */
/* 보조 각주 = 표준 .form-hint 톤 그대로 (12.5/400/slate-500 — 각주에 dot·emerald·700 ❌.
   같은 문장이 모바일 fsheet-hint 와 두 표면 공유라 무게가 갈리면 안 된다 — 구 `.promotion-filter-hint` 는 ADR 0267 로 소멸) */

/* =====================================================
   지각 예외 — 관원 상세 카드
   지정·해제는 관원 상세 한 곳 (「진짜 예외인 몇 명」이라 일괄 UI 없음).
   「누가 받고 있나」는 설정 › 지각 › 관원 예외 목록이 답한다 — 관원 목록 행 표시 ❌
   (드문 속성이라 행마다 자리를 주지 않는다. 구 .member-exempt-mark 재도입 ❌)
   ===================================================== */

/* 관원 상세 카드 — 무면 hairline 행 (이력 카드 문법) */
.lx-empty {
  font-size: 12.5px;
  font-weight: 400;
  /* slate-500 — 빈 상태 문구는 그 영역의 유일한 내용 (2026-08-10 UI 감사, §slate-400 승격 기준) */
  color: var(--color-slate-500);
  line-height: 1.55;
}
.lx-list { display: flex; flex-direction: column; }
.lx-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 0;
  border-bottom: 1px solid var(--border-hairline);
}
.lx-row:last-of-type { border-bottom: none; }
.lx-day { font-size: 13px; font-weight: 700; color: var(--text-primary); }
.lx-time {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--color-primary-700);
  font-variant-numeric: tabular-nums;
}
.lx-note {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border-hairline);
  font-size: 12.5px;
  font-weight: 400;
  /* slate-500 — 보조 각주 토큰은 12.5/400/slate-500/1.55 (§타이포). tertiary 복귀 ❌ —
     형제 `.lx-empty` 는 2026-08-10 감사에서 이미 승격됐고, 이 각주는 지각 판정 규칙을
     말하는 그 자리의 유일한 문장이라 읽어야 하는 텍스트다 (관장 상세·PWA 미러 동시 소비) */
  color: var(--color-slate-500);
  line-height: 1.55;
}

/* 판정 전제(기록 on + 시간표 존재) 미충족 잠금 — A안(자리 유지 + disabled 버튼 + 각주,
   2026-08-07 사용자 확정, 시안 late-exemption-gate). 각주가 이유 + 해결 동선(설정 › 지각 링크)을
   말하고, 기존 허용 시간 목록은 지우지 않고 그대로 보존한다.
   ⚠️ 흐리기 ❌ (2026-08-17 확정 B안, 시안 locked-section-signal): 명단은 「읽을거리」라 톤을 안 내린다 —
   잠긴 건 조작(버튼)이지 정보가 아니다. 구 `.lx-gated .lx-list { opacity: .45 }` 재도입 ❌. */
.lx-gate-note {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-600);
  line-height: 1.62;
}
.lx-gate-note strong { font-weight: 700; color: var(--color-slate-700); }
.lx-gate-note a { color: var(--color-primary-700); font-weight: 700; }
.lx-gated .lx-list {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border-light);
}

/* 예외 시각이 그 요일 판정을 통째로 무력화할 때의 안내 — 저장은 막지 않으므로 에러가 아니라
   「알고 저장하세요」 신호다. 경고성 텍스트라 힌트 400 이 아닌 600/slate-700 (타이포 예외). */
.lx-warn {
  margin-top: 7px;
  font-weight: 600;
  color: var(--color-slate-700);
}
.lx-warn b { font-weight: 800; color: var(--text-primary); }

/* 모달 — 요일 행 표 (ADR 0190 B안, 시안 late-exemption-per-day-time)
   행 = 요일 하나. 칩을 켜면 그 행 시각 칸이 열린다 — 모드 전환 없이 늘 요일별이라
   「전부 같게」와 「요일마다 다르게」가 같은 화면의 두 결과일 뿐이다.
   수업 없는 요일은 렌더 단계에서 꺼진 행 + 「수업 없음」 — ADR 0152 차단을 구조가 표현한다. */
.lx-rows {
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.lx-row-edit {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border-light);
}
.lx-row-edit:last-child { border-bottom: none; }
.lx-row-edit.is-closed { background: var(--bg-secondary); }

/* 요일 칩 — 글로벌 .day-chip 의 행 배치판. 선택 신호 = 면 + emerald 테두리 + 800 (ADR 0188 — dot ❌) */
.lx-day-chip {
  position: relative;
  flex: 0 0 44px;
  margin: 0;
  cursor: pointer;
  user-select: none;
}
.lx-day-chip input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.lx-day-chip span {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 36px;
  font-size: var(--text-sm);
  font-weight: 600;
  background: var(--bg-primary);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
@media (hover: hover) {
  .lx-day-chip:not(.is-off):hover span {
    border-color: var(--color-primary-400);
    color: var(--color-primary-600);
  }
}
.lx-day-chip input:checked + span {
  background: var(--color-primary-50);
  border-color: var(--color-primary-600);
  color: var(--color-primary-700);
  font-weight: 800;
}
.lx-day-chip.is-off { cursor: default; }
.lx-day-chip.is-off span {
  background: var(--bg-secondary);
  border-color: var(--border-light);
  color: var(--color-slate-300);
}

.lx-time-input { flex: 1 1 auto; }
/* 시각 칸 잠금(disabled) 스타일 없음 — 켜짐의 SSOT 가 시각 값이라(ADR 0055 문법, 2026-08-14)
   수업 있는 요일의 시각 칸은 늘 열려 있다. 빈 칸 신호는 placeholder(slate-400)가 담당 */
/* 그 요일 마지막 수업 — 「몇 시로 잡아야 하나」를 그 자리에서 판단하게 하는 참고값 */
.lx-lastclass,
.lx-closed-note {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 400;
  color: var(--color-slate-500);
  font-variant-numeric: tabular-nums;
}
.lx-closed-note { flex: 1 1 auto; }

/* 그룹 검증 신호는 글로벌 .day-chip-group.is-invalid 셀렉터에 .lx-rows 를 편입해 위임 —
   page-local 재구현 ❌ (§18 「체크박스 그룹도 인라인 문구 + 범용 Toast」 단일 어휘) */

/* =====================================================
   출결 지표 KPI — 관장 관원 상세 · 관원 PWA 공유 어휘
   (fragments/attendance-stats). 두 표면이 같은 모양이어야 같은 숫자로 읽힌다.
   prefix = attst- 전용 — .att-stat/.att-stat-label 은 키오스크 결과 오버레이가 선점한
   이름이라 재사용 ❌ (여기 선언이 소스 뒤라 키오스크 라벨을 조용히 덮었었다, 2026-08-03).
   ===================================================== */
.attst {
  position: relative;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border-hairline);
  margin-bottom: 14px;
}
/* 기간 캡션 — fragment 소유 (두 표면 자동 동일). 라벨 행 우측 끝 정렬 (2026-08-09 사용자 확정 A안).
   구 짝이던 카드 헤더 「이번 달 N회」 pill 은 같은 날 B안으로 폐지 — 우측 정렬은 라벨 행 균형 자체로 유지.
   구 좌측 고아 힌트 줄(12.5/400 독립 행) 재도입 ❌ */
/* 「최근 N개월」 = 이 통계의 기간을 알리는 유일한 문장 (없으면 50% 가 언제 기준인지 알 수 없다)
   → 같은 축으로 slate-500 (§slate-400 승격 기준) */
.attst-cap {
  position: absolute;
  top: 0;
  right: 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--color-slate-500);
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
}
.attst-grid { display: flex; gap: 30px; }
.attst-item { flex: 1; min-width: 0; }
/* 마이크로 라벨 — 한글이라 자간 0.04em (project-frontend-guide) */
.attst-label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--color-slate-500);
  margin-bottom: 5px;
}
/* 강조 숫자 — 지표는 「돈 값」이 아니라 신호라 emerald 유지 */
.attst-num {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  color: var(--color-primary-600);
}
/* 지각률 = 중립 사실 — 값과 무관한 브랜드 긍정색이면 「좋은 숫자」로 읽힌다 (2026-08-03 확정) */
.attst-num--neutral { color: var(--color-slate-900); }
.attst-num small {
  font-size: 15px;
  font-weight: 700;
  margin-left: 1px;
}
/* 단위 글자 = 15px 라 「작은 글자」 축 → primary-700 (ADR 0495, 3.77→5.48:1). 짝 `.md-att-total-num small`
   이 같은 근거로 이미 700 인데 이 선언만 색이 없어 부모 600 을 상속하던 자리. 600 복귀 ❌
   ⚠ `:not(--neutral)` 필수 — 지각률은 숫자가 slate-900 중립(2026-08-03 확정)이라 그 `%` 도
   같이 중립이어야 한다. 무스코프로 올리면 「숫자는 검정, % 만 emerald」가 된다. */
.attst-num:not(.attst-num--neutral) small { color: var(--color-primary-700); }
/* 분수 근거 — 두 비율의 분모가 다르다는 걸 이 줄이 직접 말한다 */
/* 분수 보조줄(「출석 가능 24일 중 12일」)은 데이터 실수치 = 그 자리의 유일한 신호라 slate-500
   (§slate-400 승격 기준 — 유지 축은 placeholder·disabled 2행뿐). 관장·PWA 공유 fragment 라 동시 이동 */
.attst-sub {
  font-size: 12px;
  font-weight: 500;
  color: var(--color-slate-500);
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
}
/* 시간표가 없으면 분모가 0 — 0% 라고 적으면 「한 번도 안 왔다」는 거짓이 된다.
   힌트 셰이프지만 grid 를 통째로 대체하는 이 블록의 유일한 콘텐츠라 slate-500 (각주 취급 ❌). */
.attst-none {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--color-slate-500);
  line-height: 1.55;
  /* 캡션(.attst-cap)은 absolute 라 흐름 밖 — 이 블록만 전폭으로 흘러 첫 줄이 캡션 밑을 지나
     글자가 겹쳤다(2026-08-21 UI 감사, PWA 375px 실측: 카드 안쪽 298px 에서 41px 겹침).
     정상 분기는 첫 요소가 짧은 .attst-label 이라 무충돌 — 이 분기만 캡션 자리를 비운다.
     68px = 최대 캡션 「최근 12개월」 60px + 여백 8px. 캡션을 흐름 안으로 내리는 해법 ❌
     (「캡션 = 라벨 행 우측 끝 정렬」 2026-08-09 사용자 확정을 분기별로 갈라놓는다). */
  padding-right: 68px;
}

/* PWA — 카드 본문 상단 밴드. 캡션은 fragment 소유(.attst-cap)라 여기선 체급만 줄인다 */
.md-stats-band { margin-bottom: 4px; }
.md-stats-band .attst-num { font-size: 23px; }
/* 단위 글자(%·회)는 14px 라 §대비 「작은 글자」 축 — 본체 숫자(23~26px)는 큰 글자라 대상 아님 (ADR 0495).
   ⚠ color 는 위 `.attst-num:not(--neutral) small` 이 소유 — 여기서 재선언하면 `small` 을 직접
   겨냥해 지각률의 중립 상속을 이기고 「숫자 slate-900 / % emerald」로 갈린다(실발현). 재도입 ❌ */
.md-stats-band .attst-num small { font-size: 14px; }
.md-stats-band .attst-grid { gap: 22px; }

/* ── PWA 누적 출석 hero — 출석 카드 본문 최상단 전용 층 (2026-08-19 사용자 확정 C안,
   시안 total-attendance-display). 값 = 등록 이관값 + 출석 COUNT (member/dashboard.html 주석 참조).
   층 골격(padding-bottom 14 + hairline + margin-bottom 14)은 .attst 와 같은 어휘 — 두 층이
   같은 리듬으로 쌓인다. 라벨 = 마이크로 라벨 한글 0.04em (.attst-label 미러).
   숫자 30px = 스탯 밴드(23px)보다 한 체급 위 — 카드의 첫인상 앵커. 단위 간격 3px = .md-pb-num small
   (「숫자↔단위 간격 3px」 확정값). 각주 = 데이터 실수치라 slate-500 (.attst-sub 와 같은 톤). */
.md-att-total {
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border-hairline);
  margin-bottom: 14px;
}
.md-att-total-label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--color-slate-500);
  margin-bottom: 5px;
}
/* 숫자 emerald = .attst-num 과 같은 축(「지표는 신호라 emerald 유지」) — 확정 시안 값 그대로 */
.md-att-total-num {
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  color: var(--color-primary-600);
}
.md-att-total-num small {
  font-size: 15px;
  font-weight: 700;
  margin-left: 3px;
  /* 흰 면 위 작은 글자 → primary-700 (3.77 → 5.48:1, ADR 0495). 600 복귀 ❌ */
  color: var(--color-primary-700);
}
.md-att-total-sub {
  font-size: 12px;
  font-weight: 500;
  color: var(--color-slate-500);
  margin-top: 4px;
  font-variant-numeric: tabular-nums;
}

/* ── PWA 카드 행 파괴 액션 = 조용한 slate 텍스트 링크 (2026-08-05 사용자 확정 · 어휘 1종화) ──
   §액션 푸터 어휘 「파괴 액션 배치 4분류」 ④ 의 구현 SSOT. 구 member/pay page-local
   `.pay-pending-cancel` 을 여기로 승격 — 입금 요청 취소(가역)·소셜 연결 해제(비가역) 둘 다 같은 어휘.
   위험 신호는 뒤의 Alert.confirm({type:'danger'}) + 결과 명시 카피가 담당한다 (red 버튼화 ❌).
   히트박스 확대 = .pay-hero-change idiom (시각 크기 불변, padding/margin 상쇄). 오른쪽 끝 요소라
   우측 확장을 버리고 왼쪽·세로로만 확보한다 — 우측 음수 margin 은 무면 카드에서 화면 밖으로 밀린다.
   관장 표면으로 이 텍스트 어휘 확장 ❌ (관장 파괴 = 항상 red 버튼 한 문법). */
/* 히트박스 = 세로 14.5px 패딩 + 동값 음수 마진 → **44px** (보이는 글자 12.5px 는 불변).
   구 13px 은 41px 로 목표치에 3px 모자랐다 (2026-08-26 사용자 확정 A안 · 시안 `pwa-destructive-touch-gap`).
   ⚠ **짝 규칙** — 이 토큰을 다시 키울 땐 «위아래 이웃 탭 대상과의 간격»을 확장분 2배와 동치로 함께 벌릴 것
   (§히트박스 확대 idiom). 홈 설문 시트가 정확히 그 자리라 `.qsheet-skip-row` 의 margin-top 을 함께 올렸다 —
   한쪽만 바꾸면 파괴 액션의 판정이 바로 위 확인 버튼과 2.5px 로 붙는다. */
.pwa-row-destructive {
  flex-shrink: 0; background: none; border: none; font-family: inherit;
  font-size: 12.5px; font-weight: 800; color: var(--color-slate-500); cursor: pointer;
  padding: 14.5px 0 14.5px 12px; margin: -14.5px 0 -14.5px -12px;
}
/* 잠긴 조작 = 글자색 + 커서 (§잠긴 값 칸 「opacity 전 컨트롤 금지」 · 면 축은 «박스 입력» 전용이라
   무면 인라인 컨트롤은 글자색만 — 글로벌 `.bank-picker-trigger:disabled` 와 같은 합류, ADR 0314).
   ⚠ 이 클래스는 `.btn` 계열이 아니라 글로벌 `.btn:disabled` 가 «닿지 않는다» — 없으면 보기 모드
   ([[0534]]) 로 잠긴 3곳(결제 취소 2 · 연결 해제)이 평상시와 픽셀 동일해 「눌리는 버튼」으로 읽힌다. */
.pwa-row-destructive:disabled {
  color: var(--text-tertiary);
  cursor: not-allowed;
}
@media (hover: hover) {
  .pwa-row-destructive:hover:not(:disabled) { color: var(--color-slate-700); }
}

/* ── PWA 설치 안내 시트 본문 (ADR 0251, 2026-08-16 사용자 확정 C안) ──────────────────
   `pwa-install.js` 가 BottomSheet 본문으로 조립하는 슬롯. 진입점이 둘(홈 자동 시트 ·
   내 정보 상시 행)인데 시트를 그리는 코드는 하나라, CSS 도 page-local 두 벌이 될 수 없다.
   번호 마커(1/2/3)는 §0 「번호 마커 남발 ❌」의 등재 허용 케이스 — 아이폰 「공유 → 홈 화면에
   추가 → 추가」는 순서가 곧 정보인 진짜 3단계 절차다. */
.pwa-guide-lead {
  font-size: 12.5px; font-weight: 400; color: var(--color-slate-500);
  line-height: 1.55; margin: 0 0 4px;
}
/* 마지막 단계 아래 hairline 제거 — 시트 푸터 경계와 겹쳐 두 줄로 보인다 */
.pwa-guide-step {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 10px 0; border-bottom: 1px solid var(--border-light);
}
.pwa-guide-step:last-child { border-bottom: none; padding-bottom: 0; }
/* 단계 번호 = hairline 원형 (emerald 채움 ❌ — 신호가 아니라 순서 표기).
   12px = §폰트 최소 크기 바닥 ([[0038]]). 원은 22px — 20px 에 12px/800 숫자를 넣으면
   위아래 여백이 3px 뿐이라 글자가 테두리에 닿아 보인다. 11.5px 로 줄이기 ❌ */
.pwa-guide-n {
  width: 22px; height: 22px; border-radius: 999px; flex-shrink: 0; margin-top: 1px;
  border: 1px solid var(--color-primary-200); color: var(--color-primary-700);
  font-size: 12px; font-weight: 800; font-variant-numeric: tabular-nums;
  display: flex; align-items: center; justify-content: center;
}
.pwa-guide-t { font-size: 13px; font-weight: 700; color: var(--color-slate-900); line-height: 1.4; }
.pwa-guide-m { font-size: 12px; color: var(--color-slate-500); margin-top: 2px; line-height: 1.45; }
/* 공유 아이콘 = 「어느 버튼인지」를 지목하는 기능 신호(장식 픽토 ❌). iOS 시스템 파랑 유지 —
   화면에서 실제로 그 색인 버튼을 찾아야 하므로 Brand Mono 로 중화하면 단서가 사라진다 */
.pwa-guide-ico { display: inline-flex; vertical-align: -3px; margin: 0 2px; color: #007aff; }

/* ── PWA hero 라벨 원자 (2026-08-22 UI 감사 사용자 확정 A안) ──────────────────────
   관원 앱 카드 머리의 마이크로 라벨 — 구 page-local 5벌(`.md-pb-lab`·`.qsheet-label`·
   `.pay-pending-label`·`.pay-hero-label`·`.bank-confirm-label`)이 2파일에 걸쳐 값이 완전히
   같은 채로 병존했다(§원자 승격 기준 「3번째 사용처에서 검토」 트리거를 5벌로 넘긴 상태).
   토큰을 바꿀 때 5곳을 동시에 고쳐야 하던 자리 — page-local 재선언 ❌ */
.pwa-hero-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 800;
  /* 자간 = 한글 라벨 표준 0.04em ([[0113]]). 구 0.06em 은 흡수한 page-local 5벌의 옛 값이
     그대로 따라온 것이고 «왜 이 값인가»가 어디에도 없었다 — 하필 [[0113]] 이 「제3값 신설 ❌」의
     예시로 든 값이 정확히 0.06em 이다. 2026-08-26 사용자 확정 A안 · 시안 micro-label-tracking-drift.
     라벨이 「내 이용권 · 30일권」·「설문」·「입금 금액」 = 전부 한글이라 영문 보정(0.08em) 대상도 아니다.
     0.06em 복귀 ❌ */
  letter-spacing: 0.04em;
  color: var(--color-primary-700);
}
/* dot = 링 있는 기본형(구 `.md-pb-lab-dot`·`.pay-pending-dot` 동값). 설문 시트의 민짜 dot
   (`.qsheet-dot`)은 링이 없는 의도적 변형이라 흡수 대상 아님 — 링 부여 ❌ */
.pwa-hero-label-dot {
  width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
  background: var(--color-primary-600);
  box-shadow: 0 0 0 3px var(--color-primary-100);
}

/* ── 네비게이션 진행 바 (2026-08-19 사용자 확정 — 모바일 속도 개선 1번) ──────────────
   MPA 특성상 탭 → 다음 페이지 도착까지 화면이 무반응인 구간을 상단 2.5px emerald 바가
   메운다 — 실제 속도가 아니라 "진행 중" 인지가 목적. 소유 = common.js §13.7 NavProgress
   (문서 위임 한 곳 — 페이지별 진행 바 재구현 ❌).
   - 노출 지연 120ms 는 JS 소유 — prerender 즉시 전환에는 바가 아예 안 보인다
   - z = --z-toast: 모달 위 네비게이션(모달 안 링크)에서도 보여야 해서 --z-modal 위.
     토스트(top 8px+)와 같은 층이지만 바는 최상단 2.5px 라 겹침 없음
   - 진행 곡선(10s 감속 → 92% 정지)은 JS 인라인 transition 소유 — 여기 keyframe 신설 ❌
   - 완료 애니 없음: 문서가 통째로 교체되며 바도 함께 사라진다 (VT 스냅샷에 담겨 자연 소멸) */
.nav-progress {
  /* 상단 인셋만큼 내려 앉힌다 — cover 뒤로 `top: 0` 은 상태바·노치 «밑»이라 2.5px 바가
     통째로 안 보인다 (2026-09-14) */
  position: fixed; top: env(safe-area-inset-top, 0px); left: 0; right: 0; height: 2.5px;
  z-index: var(--z-toast); pointer-events: none;
  opacity: 0; transition: opacity .15s ease;
}
.nav-progress.is-active { opacity: 1; }
.nav-progress-fill {
  height: 100%; width: 100%;
  background: var(--color-primary-600);
  transform: scaleX(0); transform-origin: left;
  border-radius: 0 2px 2px 0;
}

/* =====================================================
   이름 길이 — 문자 미리보기 (2026-09-14 사용자 확정 A안 · 시안 sms-name-hint-copy)
   도장 이름·이용권 이름 칸이 「문자로 가면 이렇게 찍힌다」를 그 자리에서 보여주는 조각.
   소비처 3곳 = 이용권 폼 모달 · 설정 도장 이름 · 도장 만들 때 → page-local 사본 ❌
   (한 곳만 고치면 같은 사실이 화면마다 다른 모양이 된다).

   면·테두리·radius 없는 무면 블록이다 — 말풍선 자체가 이미 면이라 감싸면 「카드인카드」가 되고,
   곡률 단일화(모달 안 10 ↔ 카드 안 14)에서 한 원자가 두 값을 가질 수 없다. 회색 박스 재도입 ❌.
   ===================================================== */
.sms-limit-peek {
  margin-top: 8px;
}
/* 마이크로 라벨 롤 — 한글이라 자간 0.04em (§마이크로 라벨) */
.sms-limit-peek-lab {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--color-slate-500);
  margin-bottom: 6px;
}
/* 면(#e9e9eb)·곡률·폭 300 = 「도장 알림」 미리보기 .sms-bubble 미러 (admin/messages/index).
   실물 재현이 목적이라 Brand Mono 중화 ❌ — 중화하면 「어느 채널로 가는지」가 그림에서 사라진다.
   값을 바꿀 땐 그 원본과 함께 볼 것 (사본이 아니라 미러). */
.sms-limit-bubble {
  display: inline-block;
  max-width: 300px;
  background: #e9e9eb;
  border-radius: 16px 16px 16px 4px;
  padding: 10px 13px;
  font-size: 13px;
  line-height: 1.55;
  color: #111;
  white-space: pre-line;
  word-break: keep-all;
}

/* =====================================================
   관장 셀프 「무료 체험 시작」 — 2026-09-15 사용자 확정 (시안 owner-trial-start-*)
   소비처 2곳 = 홈 「다음 단계」 슬롯 카드(.ts-card) · 「시작하기」 허브 바닥(.ts-panel).
   한 프래그먼트(fragments/trial-start.html)가 두 자리에 서므로 page-local 사본 ❌.

   옷 = **홈 「다음 단계」 카드(.next-step-card) 그대로** (확정 가2안) — 흰 면 + hairline + radius 14,
   emerald 는 CTA 신호 위치에만. 강조를 올리려고 카드에 emerald 테두리·색 면 주기 ❌
   (시안 가3안 미채택 — 홈에서 «면»이 색을 갖는 자리는 지금 하나도 없다).
   ===================================================== */
.ts-card {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 18px;
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 14px;
  margin-bottom: 16px;
}
.ts-body { flex: 1; min-width: 0; }
.ts-actions { display: flex; align-items: center; flex-shrink: 0; }

/* 허브 바닥 — 카드인카드라 면·테두리 없이 위 divider 한 줄로만 갈린다 (.qs-hub-foot 어휘). */
.ts-panel {
  padding: 16px 0 6px;
  border-top: 1px solid var(--border-hairline);
  margin-top: 2px;
}

/* 라벨·제목·본문 체급 = 홈 배너 스택 형제(.next-step-card-*)와 한 토큰 — 값 갈리기 ❌ */
.ts-label {
  display: block;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--color-slate-500);
  margin-bottom: 6px;
}
.ts-title {
  font-size: 14px;
  font-weight: 800;
  letter-spacing: -0.015em;
  color: var(--text-primary);
  margin-bottom: 4px;
}
.ts-desc {
  font-size: 12.5px;
  color: var(--color-slate-600);
  line-height: 1.6;
}
.ts-desc strong { font-weight: 800; color: var(--text-primary); }
/* 둘째 줄(「대신 무료 N개월은 …」) = 혜택 줄보다 한 단 뒤 — 같은 크기, 색만 후퇴시켜 짝을 이룬다. */
.ts-sub {
  font-size: 12.5px;
  color: var(--color-slate-500);
  line-height: 1.6;
  margin-top: 3px;
}

/* 숫자 두 칸 — 훑어도 「얼마 · 몇 개월」이 남는 자리. 값 = 강조 숫자 어휘(800 · tabular · 단위 분리). */
.ts-facts {
  display: flex;
  gap: 26px;
  margin: 12px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--border-divider);
}
.ts-facts dt {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--color-slate-500);
}
.ts-facts dd {
  margin: 0;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: -0.025em;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.ts-facts dd .unit {
  font-size: 12px;
  font-weight: 700;
  color: var(--color-slate-500);
  margin-left: 2px;
}
.ts-fact-sub {
  display: block;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--color-slate-500);
  margin-top: 1px;
}

/* 구간 조건 각주 — 「그대로」가 금액이 아니라 요금표라는 사실을 못박는 줄 (보조 각주 토큰 12.5/400). */
.ts-tier-note {
  margin: 10px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--border-divider);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--color-slate-500);
}
.ts-tier-note strong { font-weight: 700; color: var(--color-slate-700); } /* §보조 각주 토큰 강조 = 700 (2026-09-17 UI 감사 — 800 은 각주 위계 이탈) */

.ts-btn-block { width: 100%; }
.ts-panel .ts-btn { margin-top: 14px; }

/* 모바일 — 본문/액션 세로 스택, CTA 전폭 (.next-step-card 와 같은 break·같은 규칙). */
@media (max-width: 768px) {
  .ts-card {
    flex-direction: column;
    align-items: stretch;
    gap: 14px;
  }
  .ts-card .ts-btn { width: 100%; }
}

/* =====================================================
   관원 사진 아바타 — ADR 0756 · 0761 · 0763 · 0768 → **치수는 ADR 0825 으로 한 벌**
   (시안 member-photo-scope · member-photo-atom · member-photo-sparse · member-photo-upload · member-photo-kiosk ·
    member-photo-detail-size · member-photo-circle-entry · **member-photo-bigger-inline**)

   ⚠️ **사진이 없는 관원에게는 이 원자를 렌더하지 않는다 — 자리(폭)도 예약하지 않는다.**
   회색 이니셜 원·실루엣 아이콘·투명 빈 원 폴백 **전부 재도입 ❌** (ADR 0756 확정 1).
   **예외 = «누르는 자리» 셋** — 관원 상세 헤더(ADR 0761) · 가입 신청 폼 · **관원 앱 홈**(ADR 0825 으로 합류):
   거긴 사진이 없어도 점선 원(`.member-photo-btn.is-empty`)이 선다. 그 원은 이 원자가 아니라 버튼이다.
   홈이 예외가 된 이유 = **내 정보에서 사진이 빠져(0825) 홈이 유일한 등록 자리**가 됐기 때문 —
   빈 원을 안 세우면 사진을 «넣을 길 자체»가 사라진다. 보는 자리(키오스크 «고르기»)는 그대로 「사진 있을 때만」.

   **조건부 예외 1곳 = 키오스크 «이름(초성) 후보 목록»** (ADR 0856 ②-1 · 2026-09-22 사용자 확정 C안):
   그 목록만 **사진을 한 장이라도 쓰는 도장에서** 성 한 글자 원(`.aurora-cand-initial` — page-local)을 세운다.
   0756 확정 1 이 배제한 B안(「옅은 이니셜로 자리를 맞춘다」)을 **조건부로만** 연 것이고, 배제 근거였던
   비용(「사진을 한 명만 올린 도장이 나머지 전원의 행에 이니셜 원을 얻는다」)은 그대로 지켜진다 —
   판정이 **도장 단위**(`memberPhotoInUse`)라 사진 0장 도장에서는 **원이 한 개도 안 선다**.
   ⚠️ 이 원자(`.member-photo`)에 폴백을 넣는 것이 **아니다** · 「항상 세운다」로 되돌리지 ❌ ·
   같은 페이지의 동명이인 «고르기» 오버레이는 **여전히 「사진 있을 때만」**(두 목록이 다른 것이 의도) ·
   후보 목록 단위 판정 ❌(초성을 더 누를 때마다 이름이 좌우로 튄다) · 다른 보는 자리로 확장 ❌.

   **치수 = 112 한 벌 (ADR 0825 · 2026-09-20 사용자 확정 B안 · 시안 `member-photo-bigger-inline`)** —
   구 두 체급(관원 72 / 관장 96·모바일 80 · ADR 0768)은 **폐지**. 모바일 전용 축소값도 없다 — 한 값이 모든 기기에 선다.
   사용처 = 관원 앱 홈 락업 · 키오스크 「누구신가요」 고르기 · 크롭 모달 미리보기 · 관장 상세 헤더 · 가입 신청 폼.
   («내 정보» 는 ADR 0825 으로 사진 자체가 빠졌다 — 사용처 아님)

   **112 가 상한인 근거 = 실측 4제약 중 셋이 여유, 하나가 딱 걸린다** (최소 지원 폭 320px 기준):
   ① 관원 앱 신원 락업 — 사진을 이름 줄 «위»로 올려(0825) 폭 경쟁 자체를 없앴다. 인라인이면 96 이 상한이었다
   ② 크롭 모달 줌 슬라이더 — 320px 에서 72→82px · 96→60 · **112→45** · 128→30. 45px 이 한계라
      **좁은 폭(≤360px)에서 줌 줄을 미리보기 «아래»로 내린다**(member-photo-crop 조각 소유). 이 보정 없이 112 ❌
   ③ 가입 신청 폼 설명 칸 — 320px 에서 149px(3줄 유지) · ④ 관장 상세 헤더 — 320~1440 가로 넘침 0
   **128 이상 ❌** — 320px 에서 사진이 화면 폭의 40%+ 라 «프로필 사진»이 아니라 «표지 사진»이 된다.
   **구 72·80·96 복귀 ❌ · 모바일 전용 축소값 부활 ❌ · 두 체급 재도입 ❌**(0768 의 「폰 480 vs 관장 1440」 축은
   사진을 락업에서 들어내는 순간 근거가 사라졌다 — 되돌리려면 락업 구조부터 되돌려야 한다).
   구 3벌(56·64·80)·`--sm`/`--md` 는 ADR 0761·0768 로 이미 소멸 — 목록에 재도입 ❌.
   키오스크 결과 카드 탑 메달리온(100~200)은 **카드 높이에 종속된 다른 축**이라 이 값 밖 — 함께 움직이지 ❌.
   키오스크 「누구신가요」의 **좁은 밴드 축소(kiosk.html page-local)는 유지** — 가로 압축 화면은 카드가 넘친다.

   모양 = **원형** (도장 로고 원형 크롭과 같은 어휘 — 앱에 모양을 하나 더 늘리지 않는다).
   증명사진 비율 4:5 사각 재도입 ❌ (미채택 B안 — 로고 원형과 어휘가 둘이 된다).

   `object-fit: cover` 필수 — 저장은 정사각 400×400 이지만 크롭 모달을 우회한 사진은 비율이 남는다.
   400×400 원본이라 112 에서도 2배 DPR(224px)까지 원본 화소를 다 쓴다 — 화질은 이 치수의 제약이 아니다.
   ===================================================== */
.member-photo {
  flex-shrink: 0;
  /* 치수 = 한 벌 (ADR 0825). 변형 클래스(`--lg`·`--app`)는 같은 날 **삭제** — 값이 하나뿐이라 고를 것이 없다.
     크기를 달리해야 하는 자리가 생기면 변형을 되살리지 말고 **왜 그 자리만 다른지**부터 ADR 에 적을 것. */
  width: 112px; height: 112px;
  border-radius: 50%;
  object-fit: cover;
  /* 흰 면 위 흰 배경 사진(증명사진)이 떠 보이지 않게 하는 최소 경계 — 테두리가 아니라 그림자라
     레이아웃 폭을 안 건드린다. */
  box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.08);
  /* 로딩 전·깨진 파일에서도 자리가 안 무너지게 — 비어 보이는 원 하나로 끝낸다(문구·아이콘 ❌) */
  background-color: var(--bg-secondary);
}

/* ── 사진 «넣는» 원 버튼 = 글로벌 원자 (2026-09-17 승격 · 사용처 = 관장 상세 · 가입 신청 폼 · **관원 앱 홈**).
   크기는 짝 아바타(`.member-photo`)와 «같은 값» — 비어 있을 땐 안에 img 가 없어 물려받을 상대가 없다.
   아바타 치수를 바꾸면 여기도 함께.
   빈 원 = 「행 추가」 dashed idiom(.plan-card-add 토큰: 1.5px dashed slate-300 · 흰 면 · hover emerald) —
   회색 이니셜·실루엣 폴백 ❌ (ADR 0756 확정 1 의 금지는 그대로, 여기 서는 건 «누르는 자리»다). */
.member-photo-btn {
  position: relative; flex-shrink: 0;
  width: 112px; height: 112px;
  padding: 0; border: 0; background: none; border-radius: 50%; cursor: pointer;
}
.member-photo-btn .member-photo { width: 100%; height: 100%; display: block; }
.member-photo-btn.is-empty { border: 1.5px dashed var(--color-slate-300); background: #fff; }
/* 카메라 배지 = 원 우하단 흰 원 + hairline — 「누르면 사진」이라는 유일한 가시 표지(데스크톱·모바일 공통, hover 의존 ❌).
   112 원에 32 (원 폭의 1/3.5 근처). 구 2벌(96 → 30 · 72 → 28)은 치수가 한 벌이 되며 **한 값으로 합쳐졌다** —
   원 크기가 한 값인데 배지만 갈리면 자리마다 다른 모양이 된다. 기기별 배지 규칙 신설 ❌ */
.member-photo-badge {
  position: absolute; right: -2px; bottom: -2px;
  width: 32px; height: 32px; border-radius: 50%;
  background: #fff; border: 1px solid var(--color-slate-200);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
  display: flex; align-items: center; justify-content: center;
  color: var(--color-slate-600);
}
.member-photo-badge svg { width: 16px; height: 16px; stroke-width: 2; }
/* 링 «색»은 글로벌 `button:focus-visible{outline:… !important}` 소유 — 밖으로 나온 배지(-2px)에 링이 닿지 않게 offset 만 넓힌다 */
.member-photo-btn:focus-visible { outline-offset: 3px; }
/* 잠김(보기 모드 · 제출 중) = 낱말 없는 도형이라 slate-300 축 (§잠긴 값 칸 「글리프 전용이면 300」) */
.member-photo-btn:disabled { cursor: not-allowed; }
.member-photo-btn:disabled .member-photo-badge { color: var(--color-slate-300); }
@media (hover: hover) {
  .member-photo-btn:not(:disabled):hover .member-photo-badge { color: var(--color-primary-700); border-color: var(--color-primary-200); }
  .member-photo-btn.is-empty:not(:disabled):hover { border-color: var(--color-primary-600); background: var(--color-primary-50); }
}

/* =====================================================
   호버 값 상자 — `.val-tip` (ADR 0805 · 커서 추종·조판 개정 [[0813]])
   =====================================================
   「그림을 가리키면 그 칸이 말한다」의 «그릇». 통계 카드 전부 + 콘솔 그래프 2장이 한 상자를 나눠 쓴다.
   JS = common.js §HoverTip (위치·뒤집기·닫기). 마크업은 `data-tip`(윗줄)·`data-tip-sub`(아랫줄)만 단다.

   ⚠️ 브라우저 기본 `title` 로 되돌리지 ❌ (2026-09-20 사용자 확정 B안 · 시안 `stats-hover-vocabulary`) —
   기본 툴팁은 0.5~1초 지연 + 한 줄뿐이라 카드 18장을 «훑으며 비교»하는 이 페이지의 동작을 막았다.
   ADR 0792 ① 이 「커스텀 ❌」로 닫아 둔 자리를 그 조건(「하려면 콘솔 2장과 함께」)을 채워서 연 것이라,
   통계만 되돌리거나 콘솔만 되돌리면 두 화면 어휘가 다시 갈린다.

   면 어휘 = 관원 목록 `.notify-tip`([[0594]])과 «같은 값» — 흰 면 + hairline + --shadow-lg + radius 14.
   다크 툴팁 ❌ (앱에 다크 블록이 0이라 그 자리만 다른 앱처럼 보인다).
   ⚠️ 꼬리는 «없다» (2026-09-20 사용자 확정 가안 · 시안 `stats-hover-anchor` · ADR 0805 ④ 개정) —
   상자가 커서를 따라가므로 출처를 커서가 이미 가리키고 있다. `.notify-tip` 은 요소 기준이라 꼬리를 쥔다
   (위치 계산도 그쪽만 `HoverTip.place()` 를 탄다). `.val-tip-caret` 재도입 ❌ — 되살리면 커서 옆에서
   «아무것도 안 가리키는» 삼각형이 따라다닌다. 두 상자의 위치 규칙이 다른 것이 의도다 */
.val-tip {
  position: fixed; z-index: var(--z-dropdown); display: none; background: #fff;
  border: 1px solid var(--border-hairline); border-radius: 14px; box-shadow: var(--shadow-lg);
  padding: 11px 14px 12px; pointer-events: none;
}
/* 등장 = 팝오버 공용 리빌 `motionPopoverIn` ([[0104]] LV1 「같은 목적 모션 = 공용 키프레임 하나」 —
   떠 있는 작은 면이 나타나는 자리라 드롭다운·스위처와 같은 목적이다. 전용 키프레임 신설 ❌).
   JS 가 «칸이 갈릴 때만» `is-in` 을 재부여한다 — 한 칸 «안»에서 커서를 움직이는 동안 다시 재생하면
   상자가 매 프레임 깜빡인다. 상시 반복 모션이 아니다(1회 전이) · reduced-motion 은 글로벌 가드가 먹는다 */
.val-tip.is-in { animation: motionPopoverIn 0.14s var(--ease-out-quint); }
/* 윗줄 = «수» (2026-09-20 사용자 확정 가안 · 시안 `stats-hover-sentence`) — 가리킨 이유가 「얼마야?」라
   굵은 잉크가 먼저 답한다. 아랫줄과 자리를 맞바꾸지 ❌ (그러면 수가 회색 보조 줄로 내려간다).
   ⚠️ 15/800 은 2026-09-20 사용자 확정 나안(시안 `stats-hover-skin`) — 구 13/700 은 아랫줄(12.5/400)과
   0.5px 차라 두 줄이 «같은 무게»로 읽혔다. 위계는 색이 아니라 크기·굵기가 진다. 13/700 복귀 ❌ */
.val-tip-value {
  font-size: 15px; font-weight: 800; letter-spacing: -0.02em; color: var(--text-primary);
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
/* 아랫줄 = 그 수가 선 자리(분모·기간·지난 칸 대비) — 윗줄이 커진 만큼 한 단계 내려 간격을 벌린다.
   12.5 복귀 ❌ (윗줄과의 차이가 다시 좁아진다) */
.val-tip-sub {
  margin-top: 4px; font-size: 12px; font-weight: 400; color: var(--color-slate-500);
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
/* 셋째 블록 = 그 칸을 «한 번 더» 쪼갠 줄들 — 지금 다는 카드는 알림 발송 하나다 ([[0816]]).
   `data-tip-rows` 가 없으면 JS 가 통째로 접으므로 나머지 카드 전부는 모양이 안 바뀐다.
   ⚠️ 글자 크기를 새로 들이지 ❌ — 상자 안 크기는 윗줄 15 · 그 밖 12 «두 단계»뿐이고, 이 줄들도
   아랫줄과 같은 12 를 쓴다(수만 굵기로 올린다). 위계는 색이 아니라 굵기가 진다.
   블록끼리는 hairline 한 겹으로만 가른다 — 면·색 추가 ❌ (§컬러 Brand Mono).
   라벨 왼쪽 · 수는 오른쪽 끝 = 세 줄이 자릿수로 정렬돼 «세로로 비교»된다 (이 상자를 여는 이유) */
.val-tip-rows {
  display: flex; flex-direction: column; gap: 5px;
  margin-top: 9px; padding-top: 9px; border-top: 1px solid var(--border-divider);
  min-width: 148px;
}
.val-tip-rows > div {
  display: flex; align-items: baseline; gap: 14px;
  font-size: 12px; font-weight: 400; color: var(--color-slate-500); white-space: nowrap;
}
.val-tip-rows b {
  margin-left: auto; font-weight: 700; color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
