/* ============================================================================
   TransitLab 컴포넌트 스타일 — 정적 갤러리 1차

   설계 근거
     · 화면 구조·통일성 규칙 UNI-1~7 : R12_정보아키텍처_화면설계.md §C.1.2
     · 접근성 강제 항목               : R12_접근성_별표3_대응표.md
     · 색·간격·서체 토큰              : R13_디자인시스템_색체계.md §E-6 → tokens.css
     · 셸 표현 방식                   : ADR-013 (역할은 R12, 표현은 플로팅)

   ★ 이 파일이 지키는 «타협 불가» 3가지
     ① 원시 색값(hex)을 직접 쓰지 않는다. 반드시 tokens.css 의 역할 토큰을 참조한다.
        REQ-11 이 요구한 «색을 바꾸면 연동되어 함께 움직인다»가 여기서 결정된다.
        한 곳이라도 hex 를 박으면 그 지점에서 연동이 끊긴다.
     ② `backdrop-filter`·반투명 패널을 쓰지 않는다.
        배경지도 휘도가 0.001~0.996 전 구간이라 반투명 위에서 명도대비 4.5:1
        ([별표 3] 1.8)을 보장할 수 없다. «떠 있는 느낌»은 그림자로만 낸다. (ADR-013)
     ③ `outline: none` 을 쓰지 않는다. 포커스 표시는 법적 요구다([별표 3] 2.2).
   ============================================================================ */

/* ===========================================================================
   0. 갤러리 문서 자체의 뼈대 (실제 앱 셸이 아니라 «전시장» 스타일)
   =========================================================================== */
.doc {
  max-width: 1180px;
  margin: 0 auto;
  padding: var(--sp-6) var(--sp-5) var(--sp-8);
}
.doc__title { font-size: var(--fs-display); line-height: var(--lh-display);
              letter-spacing: var(--ls-tight); margin: 0 0 var(--sp-2); }
.doc__lead  { color: var(--text-secondary); margin: 0 0 var(--sp-6); max-width: 68ch; }

.section { margin: 0 0 var(--sp-8); }
.section__head { display: flex; align-items: baseline; gap: var(--sp-3);
                 border-bottom: var(--border-w) solid var(--border);
                 padding-bottom: var(--sp-2); margin-bottom: var(--sp-4); }
.section__no { font: 700 var(--fs-sm)/1 var(--font-mono); color: var(--text-muted); }
.section__title { font-size: var(--fs-h2); line-height: var(--lh-h2); margin: 0; }
/* 근거 표기 — 이 프로젝트는 모든 서술에 근거를 단다(ADR-006). 갤러리도 예외가 아니다. */
.section__src { margin-left: auto; font-size: var(--fs-xs); color: var(--text-muted); }

.grid { display: grid; gap: var(--sp-4); }
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1024px) { .grid--2, .grid--3 { grid-template-columns: 1fr; } }

/* 각 예시를 담는 «전시대» */
.demo { background: var(--bg-surface); border: var(--border-w) solid var(--border);
        border-radius: var(--radius-lg); padding: var(--sp-4);
        box-shadow: var(--shadow-sm); }
.demo__label { font-size: var(--fs-xs); color: var(--text-muted);
               text-transform: none; margin: 0 0 var(--sp-3); }
.demo__row { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }

/* ===========================================================================
   1. 플로팅 셸 (ADR-013)
      R12 UNI-1 이 정한 «⓪상단바 / 좌패널 / 지도 / 우패널 / 하단 안내바» 의
      역할·순서·위치는 그대로 두고, 고정 분할 대신 지도 위에 떠 있는 카드로 그린다.
      UNI-1 이 존재하는 이유는 «정보의 위치를 예측 가능하게» 하는 것이지
      분할선을 긋는 것이 아니다.
   =========================================================================== */
.shell {
  position: relative;
  height: 560px;                      /* 갤러리 전시용 고정 높이 */
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: var(--border-w) solid var(--border);
  background: var(--bg-subtle);

  /* ── 하단 쌓임 순서 (아래에서 위로) ─────────────────────────────────
     안내바 → 출처 표기 → 축척·줌.
     ⚠예전에는 각 요소에 bottom 값을 «따로» 박아 두었더니 겹쳤다.
        안내바가 아래에서 12~50px 대역을 차지하는데 출처 표기를 34px 에 두어
        그 안에 들어가 있었다(2026-08-04 발주자 지적).
        높이가 조금만 바뀌어도 다시 겹치므로, 이제 «앞 요소 + 높이 + 간격»으로
        계산해 쌓는다. 한 곳만 고치면 위가 전부 따라 올라간다. */
  --guide-bottom: var(--sp-3);                  /* 안내바가 바닥에서 띄는 거리 */
  --guide-h: 40px;                              /* 안내바 높이 (패딩 8+8 + 본문 ~22 + 테두리) */
  --stack-credit: calc(var(--guide-bottom) + var(--guide-h) + var(--sp-2));
  --credit-h: 20px;                             /* 출처 표기 한 줄 높이 */
  --stack-tools: calc(var(--stack-credit) + var(--credit-h) + var(--sp-2));
}

/* 지도 캔버스 — 풀블리드. 갤러리에서는 «플레이스홀더»다.
   RK-11(VWorld 영리목적 이용 제약)이 미해소라 특정사 타일을 넣지 않는다.
   지도 라이브러리도 미확정(R12 ❓D-7)이므로 어댑터로 갈아끼울 수 있게 비워 둔다. */
.map-canvas {
  position: absolute; inset: 0;
  background:
    repeating-linear-gradient(0deg,  transparent 0 39px, var(--border) 39px 40px),
    repeating-linear-gradient(90deg, transparent 0 39px, var(--border) 39px 40px),
    var(--bg-subtle);
}
.map-canvas__note {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  text-align: center; color: var(--text-muted); font-size: var(--fs-sm);
  background: var(--bg-surface); border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md); padding: var(--sp-3) var(--sp-4);
  box-shadow: var(--shadow-sm); max-width: 44ch;
}

/* 지도 위에 얹히는 모든 표면의 공통 규칙.
   ⚠불투명(bg-surface)이다. 반투명·블러를 쓰지 않는 이유는 파일 머리말 ② 참조. */
.float {
  position: absolute;
  background: var(--bg-surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

/* ⓪ 전역 컨텍스트 바 — 전 화면의 모든 수치를 지배하는 조건이 여기 모인다.
   화면별 전용 필터를 여기에 두면 «어느 조건에서 나온 수치인가»가 흐려진다(UNI-7). */
.ctxbar {
  top: var(--sp-3); left: var(--sp-3); right: var(--sp-3);
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-full);
  overflow-x: auto;
}
.ctxbar__brand { font-weight: 700; white-space: nowrap; }
.ctxbar__sep { width: var(--border-w); align-self: stretch; background: var(--border); }
.ctxbar__spacer { margin-left: auto; }
/* 재현성 스탬프 — 「이 화면은 언제 기준인가」. P1(분석가)이 양보하지 않는 것이다. */
.stamp { font-size: var(--fs-xs); color: var(--text-muted); white-space: nowrap; }

/* 좌측 워크스페이스 레일 — 아이콘만 두지 않고 한글 라벨을 병기한다.
   [별표 3] 2.14(레이블과 네임): 표시 텍스트가 접근성 네임에 포함되어야 한다. */
.rail {
  /* 아래쪽은 안내바 위에서 멈춘다 — 같은 쌓임 계산을 쓴다(위 .shell 주석 참조).
     #12(2026-08-08): top 을 --ovl-top 토큰으로 — ctxbar 역산 높이 기반(tokens.css --ctxbar-h 참조). */
  top: var(--ovl-top); left: var(--sp-3); bottom: var(--stack-credit);
  width: var(--rail-w); padding: var(--sp-2);   /* --rail-w: tokens.css 단일출처(#12) */
  display: flex; flex-direction: column; gap: var(--sp-1);
  overflow-y: auto;
}
.rail__item {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: var(--sp-2) var(--sp-1);
  border: 0; background: transparent; color: var(--text-secondary);
  border-radius: var(--radius-md); cursor: pointer;
  font: inherit; font-size: var(--fs-xs); line-height: var(--lh-xs);
  width: 100%;
}
/* 호버 스타일은 «진짜 호버 가능한 포인터»에서만 — 터치기기 sticky-hover 방지(R37 FX-01) */
@media (hover: hover) { .rail__item:hover { background: var(--bg-subtle); color: var(--text-primary); } }
.rail__item[aria-current="page"] {
  background: var(--color-primary); color: var(--text-on-accent); font-weight: 700;
}
/* rail__icon 크기 보정 (§A F5 시정, 2026-08-08):
   레일 버튼의 font-size 는 --fs-xs(12px). .ico { width:var(--icon-size)=1.25em } 이 적용되면
   1.25em × 12px = 15px 로 종전(1em=12px)보다 커지는 무의도적 회귀가 발생한다.
   ⚠ «레일 20px»는 잘못된 주석이었음 — 실측 근거 없음(F5 §A 지적).
      종전 값은 1em = 12px(fs-xs 맥락)이 정확하다.
   시정: .ico.rail__icon 으로 특이성을 .ico 보다 높여 1em×1em 을 고정.
     결과: 12px × 12px — 레일 fs-xs 맥락에서 종전과 동일.
     이렇게 하면 --icon-size 토큰 값이 바뀌어도 레일 아이콘은 영향을 받지 않는다.
   추후: 레일 font-size 를 --fs-body 로 올리면 이 덮어쓰기 없이 --icon-size 직접 사용 가능. */
.ico.rail__icon { width: 1em; height: 1em; }   /* 1em = 12px(레일 fs-xs 맥락) — 종전값 유지 */

/* ── 아이콘 (인라인 SVG) ───────────────────────────────────────────────
   ★ 유니코드 이모지를 쓰지 않는다. 세 가지 이유가 있다.
     ① **색을 제어할 수 없다.** 이모지는 OS 이모지 폰트가 그리는 그림이라
        집·버스·차트 모양은 다색으로, 거미줄·화살 모양은 단색으로 나온다. R13 §0.2 의 전제
        「모든 색상값은 실제 계산으로 검증되었다」 밖에 있는 통제 불능 유채색이며,
        「유채색은 데이터·상태 전용, UI 는 중성」 원칙과도 어긋난다.
        (2026-08-04 발주자 지적: 「어떤 건 색감이 많이 들어가 있고 어떤 건 검정 png 같다」)
     ② **접근성 네임을 오염시킨다.** 보조기기가 집 모양 이모지를 「집」으로 읽어 버튼 이름이
        「집 개요」가 된다([별표 3] 1.1·2.14).
     ③ **고대비 모드에서 재색상되지 않는다.** 컬러 이모지는 시스템 색으로 바뀌지 않는다.
   → `currentColor` 를 상속하는 단색 SVG 로 그린다. 그러면 글자색을 그대로 따라가므로
     선택 상태·다크 테마·강제 색상 모드까지 전부 자동으로 통일된다.
   ⚠장식이므로 `aria-hidden="true"` 를 붙이고, 뜻은 옆의 한글 라벨이 담당한다.
   근거: R12 는 이모지를 «와이어프레임 삽화»로 썼을 뿐 아이콘을 지정한 적이 없다
        (F5-ICON 교차검증 2026-08-04). 규격은 R13 §E-8 에 신설해 기록한다. */
/* ── 아이콘 공통 규칙 (2-1·2-11 하이브리드, 2026-08-08) ──────────────────
   인라인 SVG(.ico)와 Material Symbols(.ico-ms) 두 가지가 공존한다.
   크기는 --icon-size 토큰 하나로 통일 — 발주자: «기준점 하나로 크기 변환».
   색은 currentColor 상속 — 선택·다크·강제색상 모드 모두 자동 대응(기존 .ico 규칙 계승).

   ★ Material Symbols 로드 실패(오프라인·CDN 차단) 시 fallback (§B F5 시정, 2026-08-08):
     폰트 로드 성공: ligature 텍스트("keyboard_arrow_up" 등)는 단일 글리프로 합성
       → width:var(--icon-size) 안에 정확히 들어옴. overflow:hidden 은 실질 무영향.
     폰트 로드 실패: ligature 미합성으로 원문 텍스트(최대 17자)가 그대로 렌더됨.
       → .ico-ms 의 width + overflow:hidden 으로 버튼 밖 오버플로를 차단한다.
       → 텍스트가 잘려 보이지만, 소스 보기로 아이콘 이름(«keyboard_arrow_up»)을 확인 가능.
       → 레이아웃(버튼 팽창·오버플로)은 이 가드로 방지.
     ⚠ 종전 «span 이라 레이아웃 안 깨진다» 서술은 max-width/overflow 가드 없던 시절의
        잘못된 기술이었다(F5 §B 지적). 이 가드로 정정. */
.ico {
  /* 크기: --icon-size 토큰 기준. 기존 1em 은 맥락에 따라 달라져 2-1·2-11 «한 줄 차지» 원인.
     토큰으로 통일해 줄높이(lh-body 1.65) 안에 맞게 고정한다. */
  width: var(--icon-size); height: var(--icon-size); flex: none;
  fill: none; stroke: currentColor;
  stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round;
}

/* Material Symbols 아이콘 헬퍼 클래스 (.ico-ms)
   용도: MS 로 교체한 아이콘(map·grid_on·photo_camera·keyboard_arrow_up/down 등).
   인라인 SVG 유지 아이콘(i-xfer·i-overview 등)은 기존 .ico 규칙을 그대로 쓴다.

   font-variation-settings 근거 (Material Symbols 가변폰트 축):
     opsz 20  — 작은 크기(≤20px)에서 획이 너무 가늘어지는 것 방지. 20px 최적화.
     wght 400 — 기본 굵기. .btn--sm 등 소형 컨텍스트에서 300 으로 낮춰도 됨.
     FILL 0   — 아웃라인(채움 없음) — 인라인 SVG fill:none 과 동일 느낌.
     GRAD 0   — 기본 농도. 다크모드에서 -25 로 낮추면 번짐 감소(선택 적용 가능).
   user-select:none — ligature 텍스트가 복사되지 않도록(접근성 텍스트는 aria-hidden 이 담당).
   pointer-events:none — 클릭 이벤트를 부모 버튼이 받도록.
   aria-hidden="true" — 의미는 부모 버튼의 aria-label 또는 옆 텍스트가 담당.
                         (components.css .ico 규칙과 동일 접근성 원칙) */
.ico-ms {
  font-family: "Material Symbols Outlined";
  font-size: var(--icon-size);    /* --icon-size 토큰으로 .ico 와 크기 통일 */
  /* §B 오버플로 가드 (F5 시정, 2026-08-08):
     width = .ico 와 동일 --icon-size — 두 종류 아이콘의 크기 단일 기준점.
     overflow:hidden — 폰트 미로드 시 ligature 미합성 텍스트(최대 17자)가
       부모 버튼 밖으로 팽창하는 것을 차단. 폰트 로드 성공 시 실질 무영향. */
  width: var(--icon-size);
  overflow: hidden;
  font-style: normal;
  font-weight: normal;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "liga";
  font-variation-settings: "opsz" 20, "wght" 400, "FILL" 0, "GRAD" 0;
  color: currentColor;            /* .ico 와 동일: 글자색 자동 상속 */
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  user-select: none;
  pointer-events: none;
}

/* 우측 도구 스택 — 배경지도·레이어·비교 등 «지도를 조작하는» 컨트롤.
   #12(2026-08-08): top 을 --ovl-top 토큰으로 통일(rail·ovl--legend 와 동일 기준). */
.dock { top: var(--ovl-top); right: var(--sp-3); width: 232px;
        display: flex; flex-direction: column; gap: var(--sp-3);
        background: transparent; border: 0; box-shadow: none; }

/* 하단 안내바 — 상시 존재한다(UNI-6).
   ⚠문구에 «우측 패널에서»·«빨간 구간» 같은 위치·색 의존 표현을 쓰지 않는다
      ([별표 3] 1.5 지시사항 — 모양·색·위치 비의존). */
.guidebar {
  left: var(--sp-3); right: var(--sp-3); bottom: var(--sp-3);
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-full);
  font-size: var(--fs-sm); color: var(--text-secondary);
}

/* 지도 오버레이 4종의 «자리» — UNI-3 이 위치를 고정해 두었다.
   범례 좌상단 안쪽 / 축척·좌표 우하단 / 줌 좌하단.
   지도 캡처 한 장만으로 의미가 통해야 하므로 캡처 영역 안에 있어야 한다. */
.ovl { position: absolute; z-index: 2; }
/* #14 범례 너비 토큰화 (2026-08-08):
   기존 width:216px 하드코딩이 «격자 500m» 줄바꿈의 근본 원인.
   실측 없이 박힌 px 수치가 «흔들림»(발주자 #20)의 전형 사례다.
   width 를 제거하고 min-width 를 토큰으로 대체한다.
   → 범례 내용이 더 길어져도 토큰 한 곳만 올려 대응할 수 있다. */
/* #12(2026-08-08): top·left 하드코딩(64px·128px)을 토큰으로 교체.
   근거: tokens.css --ovl-top(ctxbar 역산)·--ovl-left-after-rail(rail 소스값) 참조.
   이 두 토큰이 플로팅 오버레이의 «단일 출처»다 — 여기서만 바꾸면 전체 반영. */
.ovl--legend { top: var(--ovl-top); left: var(--ovl-left-after-rail); min-width: var(--legend-min-w); }
.ovl--zoom   { left: var(--ovl-left-after-rail); bottom: var(--stack-tools); }
.ovl--scale  { right: var(--sp-3); bottom: var(--stack-tools); text-align: right; }
/* 출처 표시 — 배경지도 약관상 «필수»다(VWorld 약관 제13조).
   표기 규격은 미확인(질의 중)이라 문구 슬롯만 잡아 둔다.
   안내바 바로 위에 둔다 — 지도 캡처 한 장에 출처가 반드시 함께 담겨야 하기 때문이다. */
.ovl--credit { right: var(--sp-3); bottom: var(--stack-credit); font-size: var(--fs-xs);
               line-height: var(--credit-h); color: var(--text-secondary); text-align: right; }
.ovl__box { background: var(--bg-surface); border: var(--border-w) solid var(--border);
            border-radius: var(--radius-md); box-shadow: var(--shadow-sm);
            padding: var(--sp-2) var(--sp-3); }

/* ===========================================================================
   2. 패널 · 카드
   =========================================================================== */
.panel { background: var(--bg-surface); border: var(--border-w) solid var(--border);
         border-radius: var(--radius-lg); box-shadow: var(--shadow-md);
         overflow: hidden; }
/* 모든 콘텐츠 블록에는 제목이 있어야 한다 ([별표 3] 2.9 — 블록 단위 제목 제공).
   범례·표·패널 각각이 대상이다. 「OD TRAFFIC」 같은 영문 약어는 한글 제목으로 바꾼다. */
.panel__head { display: flex; align-items: center; gap: var(--sp-2);
               padding: var(--sp-3) var(--sp-3);
               border-bottom: var(--border-w) solid var(--border); }
.panel__title { font-size: var(--fs-h3); line-height: var(--lh-h3); margin: 0; font-weight: 700; }
.panel__tools { margin-left: auto; display: flex; gap: var(--sp-1); }
.panel__body { padding: var(--sp-3); }
/* 우패널은 상단=결과 / 하단=필터 로 역할이 고정된다 (UNI-2, 원 BusPlan 계승) */
.panel__section + .panel__section { border-top: var(--border-w) solid var(--border); }
.panel__sectionhead { font-size: var(--fs-xs); color: var(--text-muted);
                      padding: var(--sp-2) var(--sp-3) 0; margin: 0; }

/* ===========================================================================
   3. 버튼
   =========================================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-1);
  min-height: 32px; padding: 0 var(--sp-3);
  font: inherit; font-size: var(--fs-sm);
  border-radius: var(--radius-md);
  border: var(--border-w) solid var(--border-strong);   /* 기능 경계 → strong (SC 1.4.11) */
  background: var(--bg-surface); color: var(--text-primary);
  cursor: pointer;
}
@media (hover: hover) { .btn:hover { background: var(--bg-subtle); } }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn--primary { background: var(--color-primary); color: var(--text-on-accent);
                border-color: var(--color-primary); font-weight: 700; }
@media (hover: hover) { .btn--primary:hover { background: var(--color-primary-hover); border-color: var(--color-primary-hover); } }
/* 파괴적 동작 — 색만으로 위험을 알리지 않는다. 아이콘·문구를 함께 쓰고,
   실행 전 확인 다이얼로그를 둔다 ([별표 3] 2.13 / §5-㉓). */
.btn--danger { color: var(--color-danger); border-color: var(--color-danger); }
@media (hover: hover) { .btn--danger:hover { background: var(--bg-subtle); } }
/* 아이콘 전용 버튼은 «반드시» 접근성 네임을 갖는다(aria-label). 없으면 스크린리더에
   빈 버튼으로 읽힌다 ([별표 3] 2.14 / 1.1). */
.btn--icon { width: 32px; padding: 0; font-size: var(--fs-body); }
.btn--sm { min-height: 26px; font-size: var(--fs-xs); padding: 0 var(--sp-2); }
/* 토글 버튼의 «켜짐» 상태 — 색만으로 알리지 않는다(고시 제11조①).
   배경·글자색과 함께 굵기까지 바꿔서, 색을 빼도 눌린 것이 보이게 한다. */
.btn[aria-pressed="true"] {
  background: var(--color-primary); color: var(--text-on-accent);
  border-color: var(--color-primary); font-weight: 700;
}
@media (hover: hover) { .btn[aria-pressed="true"]:hover { background: var(--color-primary-hover); border-color: var(--color-primary-hover); } }
/* 작은 아이콘 버튼(레이어 순서 ▲▼ 등) — «클릭 영역»과 «글리프 크기»를 분리한다.
   ⚠글리프만 줄이고 클릭 영역은 24×24 CSS px 아래로 내리지 않는다.
      WCAG 2.2 SC 2.5.8(최소 타깃 크기, AA)이 24×24 를 요구하며,
      이 프로젝트는 AA 를 최소 기준으로 삼는다(R13 리서치 행150).
      즉 «작게 보이게»는 글리프로 해결하고 누를 수 있는 면적은 지킨다.
   발주자 지적(2026-08-04): 「▲▼ 글자 크기가 너무 커서 유독 이것만 눈에 들어온다.
   옆의 체크박스·라벨과 같거나 작았으면 좋겠다」 — 체크박스 16px·라벨 13px 과 어울리도록
   글리프를 10px 로 낮추고, 색도 중성(secondary)으로 물린다. */
.btn--icon.btn--sm {
  width: 24px; min-height: 24px; height: 24px;
  font-size: 10px; line-height: 1;
  color: var(--text-secondary);
  /* 목록 안에서 조용히 물러나도록 테두리·배경을 두지 않는다.
     ⚠테두리를 --border(장식 전용, 대비 1.43)로 «낮추는» 것은 금지다 — 그건 기능적 경계를
        SC 1.4.11(비텍스트 3:1) 미달로 만드는 것이다. 대신 **테두리 자체를 없애고**
        컨트롤의 식별을 글리프가 담당하게 한다. 글리프 색 --text-secondary 는 표면 대비
        8.74:1 로 3:1 을 크게 넘는다(R13 §E-6 실측). 즉 대비를 낮춘 것이 아니라
        «경계 대신 아이콘으로 식별»하는 방식으로 바꾼 것이다.
     ⚠크기는 24×24 아래로 내리지 않는다 — WCAG 2.2 SC 2.5.8(AA) 준용 자체 기준(R13 §E-8-4).
        「더 작게」는 글리프로만 해결한다. (발주자 지적 2026-08-04, 2회차) */
  border-color: transparent;
  background: transparent;
}
/* 소형 아이콘 버튼 안의 아이콘 — .ico(SVG)와 .ico-ms(MS) 모두 12px 로 통일.
   12px 근거: 버튼 클릭 영역(24px)의 절반. 글리프만 줄여 클릭 영역은 유지(R13 §E-8-4). */
.btn--icon.btn--sm .ico    { width: 12px; height: 12px; stroke-width: 2; }
.btn--icon.btn--sm .ico-ms { font-size: 12px; }   /* .ico 와 동일 px — 소형 컨텍스트 */
@media (hover: hover) { .btn--icon.btn--sm:hover { color: var(--text-primary); background: var(--bg-subtle); } }

.btngroup { display: inline-flex; }
.btngroup .btn { border-radius: 0; margin-left: calc(var(--border-w) * -1); }
.btngroup .btn:first-child { border-radius: var(--radius-md) 0 0 var(--radius-md); margin-left: 0; }
.btngroup .btn:last-child  { border-radius: 0 var(--radius-md) var(--radius-md) 0; }

/* ===========================================================================
   4. 폼 컨트롤
   =========================================================================== */
/* 가시 레이블은 필수다. placeholder 로 대체하지 않는다 ([별표 3] 3.5). */
.field { display: flex; flex-direction: column; gap: var(--sp-1); }
.field__label { font-size: var(--fs-sm); color: var(--text-secondary); }
.input, .select {
  min-height: 32px; padding: 0 var(--sp-2);
  font: inherit; font-size: var(--fs-sm);
  color: var(--text-primary); background: var(--bg-surface);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius-sm);
}
.check { display: inline-flex; align-items: center; gap: var(--sp-2);
         font-size: var(--fs-sm); cursor: pointer; }
.check input { width: 16px; height: 16px; accent-color: var(--color-primary); margin: 0; }

/* 슬라이더 — ⚠여기가 이 프로젝트의 «최상» 접근성 항목 중 하나다.
   [별표 3] 2.12(단일 포인터): 드래그로만 되는 조작은 클릭 대체 수단이 «반드시» 있어야 한다.
   키보드 대체(←→)로는 충족되지 않는다 — 마우스만 쓰는 사용자를 위한 것이기 때문이다.
   그래서 −/+ 버튼과 숫자 입력을 항상 함께 둔다. */
.slider { display: flex; align-items: center; gap: var(--sp-2); }
.slider input[type="range"] { flex: 1; accent-color: var(--color-primary); }
.slider__val { width: 56px; }

/* ===========================================================================
   5. 탭
      ⚠탭 전환은 `Ctrl+Tab` 이다. `Tab` 은 초점 이동 전용으로 예약한다.
         (V5 §4 F-8 정정 — [별표 3] 2.2 와 충돌해 확정된 사항)
   =========================================================================== */
.tabs { display: flex; gap: var(--sp-1); border-bottom: var(--border-w) solid var(--border); }
.tab {
  padding: var(--sp-2) var(--sp-3); border: 0; background: transparent;
  font: inherit; font-size: var(--fs-sm); color: var(--text-secondary);
  cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
@media (hover: hover) { .tab:hover { color: var(--text-primary); } }
/* 선택 상태 — 색만으로 표시하지 않는다(고시 제11조①). 밑줄(모양)이 색 이외 부호를 담당한다.
   ⚠font-weight:700 은 «쓰지 않는다» — 글자 폭이 바뀌어 탭 라벨 폭이 흔들린다(리플로우, R36 IS-1).
   밑줄은 border-bottom(2px transparent 로 공간 예약됨)의 색만 바꾸므로 레이아웃 불변이다. */
.tab[aria-selected="true"] { color: var(--color-primary); border-bottom-color: var(--color-primary); }

/* ===========================================================================
   6. 레이어 트리 (R6 §1.4 요구 L-01~L-09)
   =========================================================================== */
.tree { list-style: none; margin: 0; padding: 0; font-size: var(--fs-sm); }
.tree ul { list-style: none; margin: 0; padding-left: var(--sp-4); }
.tree__row { display: flex; align-items: center; gap: var(--sp-2);
             padding: var(--sp-1) var(--sp-1); border-radius: var(--radius-sm); }
@media (hover: hover) { .tree__row:hover { background: var(--bg-subtle); } }
.tree__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tree__group > .tree__row { font-weight: 700; }
/* 순서 변경 — 드래그만 두면 [별표 3] 2.12 위반이다. ↑↓ 버튼을 «항상» 함께 둔다.
   순서 변경은 그룹 «안에서만» 허용된다(그룹 간 이동 금지 — 렌더 순서가 무너진다). */
.tree__order { display: flex; gap: 2px; }
/* 미확보 레이어는 숨기지 않는다. 회색 비활성 + 확보 방법 툴팁으로 남긴다 —
   «없는 것»과 «아직 못 받은 것»을 구분해야 다음 사람이 헛짚지 않는다. */
.tree__row--unavailable .tree__name { color: var(--text-muted); }

/* ===========================================================================
   7. 배지 · 칩
   =========================================================================== */
.badge { display: inline-flex; align-items: center; gap: 4px;
         font-size: var(--fs-xs); line-height: 1.6;
         padding: 0 var(--sp-2); border-radius: var(--radius-full);
         border: var(--border-w) solid var(--border-strong); color: var(--text-secondary);
         background: var(--bg-surface); white-space: nowrap; }
/* 추정 단계 배지 — 하차는 76.8% 가 추정값이다. 어느 단계 추정인지 밝히지 않으면
   사용자가 실측과 구별할 수 없다(P3 요구 C-3). */
.badge--est { border-color: var(--color-warning); color: var(--color-warning); }
/* 활성 필터 칩 — «지금 무엇이 걸려 있는가»는 항상 보여야 한다. */
.chip { display: inline-flex; align-items: center; gap: var(--sp-1);
        font-size: var(--fs-xs); padding: 2px var(--sp-2);
        border-radius: var(--radius-full); background: var(--bg-subtle);
        border: var(--border-w) solid var(--border-strong); }

/* ===========================================================================
   8. 범례
      값 범위를 «숫자로» 병기한다. 색 견본만 두면 색 단독 전달이 된다(고시 제11조①).
      분류 방식(등간격/분위수/자연구간/사용자정의)도 화면에 드러내야 한다 —
      원 BusPlan 은 이것을 숨겨서 «어느 잣대인지» 알 수 없었다(R12 F-9).
   =========================================================================== */
.legend__list { list-style: none; margin: 0; padding: 0;
                display: flex; flex-direction: column; gap: 2px; }
/* 3-3 수정(2026-08-08): white-space:nowrap 추가.
   이유: 아이콘(.ico/.ico-ms)과 텍스트 사이에서 줄바꿈이 일어나면
   아이콘이 혼자 남아 의미 전달이 끊긴다(발주자 3-3).
   nowrap 으로 «아이콘+텍스트» 쌍을 한 줄로 유지하고,
   줄바꿈이 필요하면 컨테이너의 flex-wrap 이 항목 단위로 내린다. */
.legend__item { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-xs);
                white-space: nowrap; }
.legend__swatch { width: 18px; height: 12px; border-radius: 2px; flex: none;
                  border: 1px solid var(--halo-dark); }   /* 헤일로 = 배경 대비 확보 (규칙 M-1) */
/* #14 범례 라벨 nowrap (2026-08-08):
   범례 항목(.legend__item) 안의 라벨이 줄바꿈되면 swatch 와 어긋난다.
   nowrap 으로 한 줄 강제하고, 범례 컨테이너(.ovl--legend)의 min-width 가 이를 수용한다. */
.legend__label { color: var(--text-secondary); white-space: nowrap; }
/* .legend__method 는 갤러리 전역 설명 문단 클래스(index.html 에 10곳+).
   전역 nowrap 은 장문 문단이 카드 밖으로 넘치는 회귀를 유발한다(F5 재지적, 2026-08-08).
   «격자 500m» 줄바꿈 방지는 해당 span 에 .nowrap 유틸을 직접 적용한다(index.html 202행). */
.legend__method { margin-top: var(--sp-2); font-size: var(--fs-xs); color: var(--text-muted); }

/* 인라인 줄바꿈 방지 유틸 — 특정 어구(«격자 500m» 등)가 단어 중간에서 꺾이지 않게 (#14).
   전역 nowrap 대신 필요한 span 에만 직접 적용해 범위를 최소화한다. */
.nowrap { white-space: nowrap; }
/* 값 없음(no data) — 색이 아니라 «빗금»으로 표시해 0 과 구분한다. */
.legend__swatch--nodata {
  background:
    repeating-linear-gradient(45deg, var(--text-muted) 0 2px, transparent 2px 5px),
    var(--bg-subtle);
}

/* ── (2) 범례 맥락 환승 아이콘 축소 (2026-08-08) ────────────────────────────────
   발주자 지적: 노선402 범례의 환승거점 .ico(--icon-size 1.25em≈18.75px)가
     옆 배지(출/도/경, --fs-xs 12px)·텍스트보다 커서 튄다.
   처방: .legend__item 안의 .ico 에 한해 --icon-size-mark(12px) 로 덮어쓴다.
     스코프: .legend__item .ico — 다른 맥락의 .ico 는 영향 없음(특이성 비교로 안전).
     width/height 만 덮어쓰고 stroke 등 나머지 규칙은 전역 .ico 가 담당.
   근거: --icon-size-mark = 12px = --fs-xs — 배지·텍스트와 동일 기준점(tokens.css). */
.legend__item .ico {
  width: var(--icon-size-mark);    /* 12px — 배지·텍스트와 동일 크기로 통일 */
  height: var(--icon-size-mark);
  flex: none;
}

/* ===========================================================================
   9. 지표 카드 · KPI 타일
      ⚠굴곡도·중복도는 «산식이 원 보고서에 미기재»라 값을 넣을 수 없다(R12 D-2).
         직선굴곡도는 울산 실측상 무용하다(순환노선 186개가 NULL, 63개가 100 초과, R5).
         갤러리에서도 값을 지어내지 않고 «산식 미확정»으로 표시한다.
   =========================================================================== */
.kpi { background: var(--bg-surface); border: var(--border-w) solid var(--border);
       border-radius: var(--radius-md); padding: var(--sp-3); box-shadow: var(--shadow-sm); }
.kpi__label { font-size: var(--fs-xs); color: var(--text-muted); margin: 0 0 var(--sp-1); }
.kpi__value { font-size: var(--fs-h1); line-height: var(--lh-h1); font-weight: 700;
              font-variant-numeric: tabular-nums; }
.kpi__unit { font-size: var(--fs-sm); color: var(--text-secondary); font-weight: 400;
             margin-left: 2px; }
/* 증감 표기 — 두 가지를 «분리»해서 다룬다. 붙이면 틀린다.
     ① 방향(증가/감소) → 기호가 담당한다. 원 보고서 표기 계승: 증가 `↑` / 감소 `△`.
        ⚠일반 관례(△=증가)와 반대다. 헷갈리므로 기호 옆에 값을 반드시 병기한다.
     ② 개선/악화     → 색이 담당한다.
   ★ 색을 «방향»에 묶으면 안 된다. R12 §D.4(686행)가 명시한다 —
     「지표마다 증가가 좋은가가 다르다(연장 감소=개선, 이용객 증가=개선).
      개선/악화를 값이 아니라 **지표 정의로 판정**하여 표기」.
     즉 감소를 일률적으로 빨강으로 칠하면 «노선 연장이 줄어 좋아진 것»을 나쁨으로 보여 준다.
     그래서 모디파이어 이름을 방향(up/down)이 아니라 «better/worse» 로 둔다.
     (2026-08-04 F5-GALLERY 교차검증 R-2 지적 반영) */
.kpi__delta { font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.kpi__delta--better { color: var(--color-success); }
.kpi__delta--worse  { color: var(--color-danger); }
/* 개선/악화 판정이 없는 지표(중립)는 색을 쓰지 않는다 — 근거 없이 가치를 부여하지 않는다 */
.kpi__delta--neutral { color: var(--text-secondary); }

/* ===========================================================================
   10. 표
       열 헤더 연결·자릿수 정렬·선택 행 표식. 선택을 «색»만으로 표시하지 않는다.
   =========================================================================== */
/* ⚠table-layout: fixed — 열이 «값·정렬·강조로 재계산되지 않게» 고정한다 (R36 IS-2).
   fixed 는 첫 행(colgroup/thead)의 너비로 열을 확정하고, 이후 셀 내용이 바뀌어도 열 폭을
   다시 계산하지 않는다. 이것이 호버·연동·상하행 토글에서 표가 흔들리지 않는 «근본» 장치다.
   숫자열은 tabular-nums(.num)로 자릿수 폭까지 고정한다. 넘치는 셀은 말줄임(아래 규칙). */
.table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); table-layout: fixed; }
/* 정렬 규칙 갱신 2026-08-16 (발주자 결정·.claude/rules/ui-coding.md 「표를 만들 때(정렬)」·F5 R-1~R-3):
   왜: 사람이 «기호부터» 읽으므로 증감(▲▼+)은 좌측이 스캔 쉽고(기호가 한 줄 정렬), 순수 숫자는
       자릿수·소수점 정렬을 위해 우측이 국가표준(KRDS)에 맞다. 한글 라벨은 좌측이 가독적이다.
   th → center 유지(발주자 08-16 은 헤더 정렬 미결정 → 종전 유지·헤더는 열 대표). td 기본 → center.
   순수 정수 .td--num → right · 소수 .td--decimal → right · 증감/기호값 .td--sign → left · 한글 .td--text → left.
   .num(중앙) 은 식별자 컬럼(노선번호·순번)·짧은 라벨·«—» 전용(정수인데 중앙은 «컬럼 균일» opt-in).
   ⚠ 정렬 «단위는 컬럼»이다(F5 R-1): 한 컬럼은 한 정렬로 통일한다 — fillTable 은 TLTools.colClass 다수결로,
      수동 빌더는 컬럼별 확정 클래스로. «—»·null 도 그 컬럼 정렬을 따른다(우/중 혼재 금지).
   (종전 항목 2 규칙[2026-08-08·정수=중앙]을 발주자 08-16 결정으로 대체. 헤더 정렬은 불변.)
   항목 4 (2026-08-08): .chart-card__unit 위치 이력 및 현재 상태:
   · 초기: 제목 옆 · 3-6: 헤더 오른쪽 · 재지시: stamp 아래 별도 p
   · 항목 4 최종: stamp 와 같은 줄 맨 오른쪽 — stamp p 가 flex 컨테이너,
     unit 이 그 안의 span(margin-left:auto). components.css .chart-card__stamp 참조. */
.table th, .table td { padding: var(--sp-2) var(--sp-2); text-align: center;
                       border-bottom: var(--border-w) solid var(--border);
                       /* fixed 에서 내용이 열 폭을 밀지 않도록 넘침을 말줄임 처리(폭 불변) */
                       overflow: hidden; text-overflow: ellipsis; }
/* 식별자·라벨·«—» 셀 — 중앙(기본) + tabular-nums(자릿수 폭 고정). ⚠ 종전엔 .num 규칙이 «없어»
   기본 center 에만 의존하고 tabular-nums 가 실제로 안 걸리던 결함이 있었다(실측·F5 Q5) → 명시 신설. */
.table td.num { font-variant-numeric: tabular-nums; }
/* 순수 정수 셀 — right 정렬로 자릿수를 열 내에서 정렬(신 규칙 기본·종전 중앙 뒤집음). tabular-nums 병행. */
.table td.td--num { text-align: right; font-variant-numeric: tabular-nums; }
/* 소수 셀 — 소수점 이하가 있는 수치. right 정렬로 소수점 위치를 열 내에서 정렬. tabular-nums 병행. */
.table td.td--decimal { text-align: right; font-variant-numeric: tabular-nums; }
/* 증감·기호값 셀(▲▼+) — left 정렬로 기호를 한 줄로 세워 스캔을 쉽게 한다. tabular-nums 병행. */
.table td.td--sign { text-align: left; font-variant-numeric: tabular-nums; }
/* 텍스트 셀 — 한글 포함 문자열(길이 무관·F5 R-2). left 정렬로 가독성 확보 */
.table td.td--text { text-align: left; }
/* 열 너비는 «명시»한다(IS-2). 가장 정확한 방법은 각 표의 <colgroup> 이며(정적 표는 HTML,
   차트 데이터표는 fillTable 이 생성), col[style=width] 가 fixed 레이아웃의 열 폭을 확정한다.
   colgroup 이 없는 표를 위한 fallback: 첫 열(행 헤더/라벨) 8.5em, 나머지는 균등. */
.table:not(:has(col)) th:first-child,
.table:not(:has(col)) td:first-child { width: 8.5em; }
.table thead th { font-size: var(--fs-xs); color: var(--text-secondary);
                  background: var(--bg-subtle); font-weight: 700; white-space: nowrap; }
@media (hover: hover) { .table tbody tr:hover { background: var(--bg-subtle); } }
/* 선택 행 강조 — ⚠«레이아웃 시프트 0» 이 요건이다(발주자 지적 2026-08-07: 호버·토글 시
   아래 표가 미세하게 흔들린다). 원인은 font-weight:700(글자 폭 변화)과 ::before "▶ "(폭 추가)가
   리플로우를 일으킨 것. → 굵기·삽입문자를 쓰지 않고 background + inset box-shadow(좌측 강조 바)
   로만 표시한다. box-shadow 는 페인트만 바꾸고 레이아웃을 건드리지 않는다.
   좌측 바는 색 이외에 «위치»로도 선택을 알린다(색 단독 아님). */
.table tbody tr[aria-selected="true"] {
  background: var(--bg-subtle);
  box-shadow: inset 3px 0 0 0 var(--color-primary);
}
/* ── 호버 강조 (R40 R-1·스니펫 E) — 차트↔표 연동의 «일시」 강조 ──────────────
   .is-hover 는 배경만 바꾼다(비레이아웃, R36 IS-1). 선택(aria-selected)과 «별개»이며
   더 약하다 — 선택은 좌측 바까지 있어 겹쳐도 선택이 이긴다. 이탈(globalout) 시 제거된다. */
.table tbody tr.is-hover { background: var(--bg-subtle); }
/* 히트맵 셀↔표 연동(발주자 2026-08-09): 차트 셀 호버 시 표의 대응 셀을 점선 테두리로 표시한다.
   차트 emphasis(검정 점선 테두리 — charts-demo.js heatmap)와 형태 통일. outline 이라 비레이아웃(R36 IS-1). */
.table tbody td.is-hover-cell { outline: 2px dashed var(--text-primary); outline-offset: -2px; background: var(--bg-subtle); }
/* transition 화이트리스트(R36 IS-3) — background-color 만. 열·행 폭에 영향 없는 속성만 애니메이트. */
.table tbody tr { transition: background-color 150ms ease-out; }
@media (prefers-reduced-motion: reduce) { .table tbody tr { transition-duration: 0.01ms; } }
/* 표 «내부» 스크롤 컨테이너 (R40 R-3b) — 긴 표를 이 래퍼가 스크롤한다(페이지 아님).
   연동 호버 시 강조 행을 이 래퍼 안에서만 보이게 한다. */
.table-scroll { max-height: 22rem; overflow: auto; }
.table__caption { text-align: left; font-size: var(--fs-xs); color: var(--text-muted);
                  padding-bottom: var(--sp-2); }

/* ===========================================================================
   11. 빈 상태 4종 — ★ 이 갤러리에서 가장 중요한 컴포넌트
       [별표 3] §5-⑧ 최우선 항목이자, 이 프로젝트가 실제로 당한 «조용한 오류»
       (RK-1 정류소 ID 매칭)와 직결된다.
       0 / 값없음(null) / 미확보 / 필터 0건 은 «전부 다른 뜻»인데
       넷을 모두 빈칸이나 회색으로 그리면 사용자가 구분할 방법이 없다.
   =========================================================================== */
.empty { display: flex; align-items: flex-start; gap: var(--sp-2);
         padding: var(--sp-3); border-radius: var(--radius-md);
         background: var(--bg-subtle); border: var(--border-w) dashed var(--border-strong);
         font-size: var(--fs-sm); }
.empty__mark { font-weight: 700; flex: none; }
.empty__body { min-width: 0; }
.empty__title { font-weight: 700; margin: 0 0 2px; }
.empty__desc { margin: 0; color: var(--text-secondary); font-size: var(--fs-xs); }

/* ===========================================================================
   12. 배너 · 토스트
       오류 토스트는 «자동으로 사라지지 않는다» ([별표 3] §5-⑰).
       읽는 데 시간이 걸리는 사용자가 놓치면 안 되기 때문이다.
   =========================================================================== */
.banner { display: flex; align-items: flex-start; gap: var(--sp-2);
          padding: var(--sp-3); border-radius: var(--radius-md);
          border: var(--border-w) solid var(--border-strong);
          background: var(--bg-subtle); font-size: var(--fs-sm); }
/* ★ [hidden] 이 실제로 숨기게 명시도를 맞춘다 — author `.banner{display:flex}` 가 UA `[hidden]{display:none}` 를
   덮어, `hidden` 속성을 준 배너(예: #charts-offline·#ws-chart-offline 오프라인 안내)가 온라인에서도 보이던
   선재 버그(2026-08-15 workspace 5b 육안 발견). `.chart-table[hidden]`·`.slot__ph[hidden]` 와 동일 수법. */
.banner[hidden] { display: none; }
.banner--warning { border-color: var(--color-warning); }
.banner--danger  { border-color: var(--color-danger); }
.banner__close { margin-left: auto; }

/* ===========================================================================
   13. 색 견본 (갤러리 전용 — 토큰을 눈으로 확인하기 위한 것)
   =========================================================================== */
.swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
            gap: var(--sp-2); }
.swatch { border: var(--border-w) solid var(--border); border-radius: var(--radius-sm);
          overflow: hidden; background: var(--bg-surface); }
.swatch__chip { height: 40px; }
.swatch__meta { padding: var(--sp-1) var(--sp-2); font-size: var(--fs-xs);
                color: var(--text-secondary); }
.swatch__name { display: block; font-family: var(--font-mono); color: var(--text-primary); }
/* 램프(순차형·발산형)는 «순서»가 핵심이라 붙여서 보여준다 */
.ramp { display: flex; border-radius: var(--radius-sm); overflow: hidden;
        border: var(--border-w) solid var(--border); }
.ramp__step { flex: 1; height: 36px; }
/* 흑백 판독 검사 — 고시 제11조②는 «흑백에서도 의미가 같을 것»을 요구한다.
   색을 회색으로 바꿔도 순서가 보이는지 눈으로 확인하기 위한 필터다. */
.gray { filter: grayscale(1); }

/* ===========================================================================
   14. 로그인 화면 (발주자 요구 — 2차 갤러리 신설)
       ⚠여기는 «UI 껍데기»만이다. 실제 인증 로직·세션·CSRF·비밀번호 정책은
         전부 서버측 보안설계(R3x_웹보안_서버설계.md)의 소관이며, 이 파일에
         자바스크립트 인증을 넣지 않는다. 자리표시자에는 그 문서를 가리키는 주석만 둔다.
       근거: R13 색·간격 토큰 그대로 사용. 상태색은 S-1(아이콘+라벨) 준수.
   =========================================================================== */
/* 로그인은 «지도 우선» 원칙의 예외다 — 지도가 없는 유일한 전역 화면이므로
   중앙 정렬 카드로 그린다. canvas 바탕 위에 그림자로 띄운 불투명 카드(ADR-013). */
.login {
  min-height: 520px;                    /* 갤러리 전시용 높이 */
  display: grid; place-items: center;
  padding: var(--sp-6);
  background: var(--bg-canvas);
  border-radius: var(--radius-lg);
  border: var(--border-w) solid var(--border);
}
.login__card {
  width: 100%; max-width: 360px;
  background: var(--bg-surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);         /* 부유감은 그림자만 (ADR-013) */
  padding: var(--sp-6) var(--sp-5) var(--sp-5);
}
.login__brand { display: flex; align-items: center; gap: var(--sp-2);
                margin-bottom: var(--sp-2); }
.login__logo { width: 32px; height: 32px; border-radius: var(--radius-md);
               background: var(--color-primary); color: var(--text-on-accent);
               display: grid; place-items: center; font-weight: 800;
               font-size: var(--fs-h3); flex: none; }
.login__title { font-size: var(--fs-h2); line-height: var(--lh-h2); margin: 0; font-weight: 700; }
.login__sub { color: var(--text-muted); font-size: var(--fs-sm); margin: 0 0 var(--sp-5); }
.login__form { display: grid; gap: var(--sp-4); }
/* 로그인 입력은 전폭이라 .input 위에 폭만 덮어쓴다 */
.login .input { width: 100%; box-sizing: border-box; min-height: 38px; }
.login__row { display: flex; align-items: center; justify-content: space-between;
              font-size: var(--fs-sm); }
.login .btn--primary { width: 100%; min-height: 40px; }
/* 오류 상태 — 색만으로 알리지 않는다(S-1). 아이콘+라벨을 붙인 배너를 재사용한다.
   실제 «어떤 경우에 뜨는가»는 서버 응답에 달렸으므로 여기서는 형태만 전시한다. */
.login__error { border-color: var(--color-danger); }
/* 잘못된 입력칸 테두리 — border-strong 대비를 danger 로 승격(비텍스트 3:1은 유지) */
.input--invalid { border-color: var(--color-danger); }
.login__foot { margin-top: var(--sp-5); padding-top: var(--sp-4);
               border-top: var(--border-w) solid var(--border);
               font-size: var(--fs-xs); color: var(--text-muted); text-align: center; }

/* ===========================================================================
   15. Leaflet 지도 데모 오버레이 (2차 갤러리 신설)
       Leaflet + OSM 타일을 CDN 으로 불러와 «지도 우선 + 플로팅» 을 실제 지도 위에서 시연한다.
       ⚠OSM 은 R6 상 «대안»이다. 실제 배경지도(VWorld)는 RK-11(영리이용 약관) 미해소라
         데모에서는 API 키가 필요 없는 OSM 을 쓴다. 출처 표기는 Leaflet 기본 attribution 이 담당한다.
       지도 위 플로팅 표면은 여전히 «불투명 + 그림자»다(ADR-013).
   =========================================================================== */
.leaflet-shell { position: relative; height: 560px;
                 border-radius: var(--radius-lg); overflow: hidden;
                 border: var(--border-w) solid var(--border); }
/* Leaflet 이 그리는 실제 지도 컨테이너 */
#map-live { position: absolute; inset: 0; z-index: 0; background: var(--bg-subtle); }
/* Leaflet 컨트롤·attribution 이 우리 플로팅 카드보다 위로 올라오지 않게 눌러 둔다 */
.leaflet-shell .leaflet-top,
.leaflet-shell .leaflet-bottom { z-index: 400; }
/* 지도 위 플로팅 카드는 z 를 그보다 높인다 */
.leaflet-shell .float,
.leaflet-shell .ovl { z-index: 500; }
/* attribution 글씨를 토큰 서체로 맞춘다(장식적 조정) */
.leaflet-shell .leaflet-container {
  font-family: var(--font-sans);
  /* 🔴 타일이 없는 영역의 바탕(2026-08-20 육안 확인 후 추가).
     Leaflet 기본값은 #ddd 라, 어두운 테마에서 지도 위·아래 «밝은 회색 띠»가 남아
     페이지와 따로 놀았다(status 화면 스크린샷에서 확인). 토큰으로 테마를 따라가게 한다. */
  background: var(--bg-subtle);
}

/* ── Leaflet 셸 안의 우리 오버레이 위치 (구 1차 갤러리 UNI-3 배치를 병합) ──────
   자리표시자 셸(.shell)은 --stack-* 로컬변수로 쌓임을 계산했지만, Leaflet 셸에는
   그 변수가 없다(별도 셸). 여기서는 Leaflet 기본 컨트롤과 겹치지 않게 값을 직접 준다.
   · Leaflet 줌 = 좌하단(기본) / attribution = 우하단 최하단(약 18px)
   · 우리 안내바 = 하단 sp-3 띄우고 높이 역산 44px → 그 위(역산 70px)에 축척을 둔다.
     (#12 F5 시정: 기존 ~40px·약 64px 는 역산 없는 추측값이었음. 2026-08-08) */
/* #12·#14(2026-08-08): Leaflet 셸 범례도 동일 토큰 적용 — 전역 .ovl--legend 와 동일 기준.
   3-1 수정(2026-08-08): rail(.float, right-edge=104px) 의 오른쪽 border 와
   ovl--legend(.ovl__box 의 왼쪽 border, left=112px) 가 8px 간격으로 붙어
   두 박스 테두리가 시각적으로 간섭해 보인다는 발주자 지적(3-1).
   left 를 --sp-2(8px) 더 띄워 16px 여백으로 분리한다.
   이유: 두 border 간 여백이 --sp-2(8px) 이상이어야 시각적으로 분리되어 보인다
   (토큰 --sp-2 = 8px 는 컴포넌트 간 최소 여백 기준, tokens.css 참조). */
.leaflet-shell .ovl--legend {
  top: var(--ovl-top);
  left: calc(var(--ovl-left-after-rail) + var(--sp-2));  /* rail border 와 분리: 112+8=120px */
  min-width: var(--legend-min-w);
}
.leaflet-shell .ovl--scale-live {
  right: var(--sp-3);
  /* 2-12 수정(2026-08-08): 44px 하드코딩을 --guidebar-h 토큰으로 교체.
     근거: tokens.css --guidebar-h = 상하패딩(sp-2×2=16)+버튼(26)+border(2) = 44px.
     단일 출처 — guidebar 구성이 바뀌면 토큰 한 곳만 고친다. */
  bottom: calc(var(--sp-3) + var(--guidebar-h) + var(--sp-2));
  text-align: right;
}

/* ── 2-12 Leaflet 줌 컨트롤 재배치 (2026-08-08) ─────────────────────────────
   문제: Leaflet 줌 컨트롤(bottomleft, Leaflet 기본 bottom:10px left:10px) 이
         .guidebar(bottom:sp-3=12px, 높이 44px → 하단 56px 점유) 와 겹친다.
         또한 attribution(bottomleft 이동) 과도 수직으로 겹칠 수 있다.
   해결: Leaflet 컨트롤 DOM(.leaflet-bottom.leaflet-left) 의 bottom 을
         --leaflet-zoom-bottom 토큰(= sp-3+guidebar-h+sp-2 = 64px) 으로 올린다.
         CSS 로 Leaflet 컨트롤 컨테이너 위치를 재지정하는 방식(Leaflet 공식 권고).
         left 는 --ovl-left-after-rail 로 rail 오른쪽에 배치해 rail 과 겹침 방지.
   ⚠ Leaflet attribution 은 index.html 초기화에서 bottomleft 로 이동해 두었다(#12).
      attribution(.leaflet-control-attribution) 은 줌(.leaflet-control-zoom) 보다
      아래에 쌓인다 — Leaflet 1.9 는 컨트롤을 DOM insertBefore 방식으로 삽입·정렬하므로
      «flex-direction:column-reverse 자동 처리» 는 사실이 아니다(F5 권고-4, 2026-08-08 정정).
      결과(attribution 이 줌 아래) 는 맞으나, 근거는 Leaflet 의 DOM 삽입 순서이다.
      따라서 CSS 로 attribution 위치를 별도로 조정할 필요가 없다. */
.leaflet-shell .leaflet-bottom.leaflet-left {
  /* guidebar 위로 올린다 — --leaflet-zoom-bottom(=64px) 역산 근거는 tokens.css 참조 */
  bottom: var(--leaflet-zoom-bottom);
  /* 3-2 수정(2026-08-08): left 를 --ovl-left-after-rail(112px) → --sp-3(12px) 으로 변경.
     이유: 줌 컨트롤은 영역선택 버튼(guidebar left:--sp-3)과 같은 좌측 여백을 써야 한다는
     발주자 지적(3-2). rail 은 z-index:500(.float) 이므로 줌(z:400) 과 겹쳐도
     rail 이 항상 위에 오고, 줌 버튼은 rail 에 가려지지 않는다. */
  left: var(--sp-3);
}

/* ── 지도 경계 라벨 `.cr-blabel` (2026-08-20 승격) ───────────────────────────
   왜 여기로 올렸나: 이 클래스는 circuity-review.html 의 «페이지 로컬 <style>» 에만 있었는데,
   status.js 가 같은 클래스명(className:'cr-blabel')으로 행정동 경계 라벨을 붙이면서
   «이름만 계승하고 스타일은 못 받는» 상태가 됐다(2026-08-20 계승감사 §5-E).
   그대로 두면 status 화면에서 행정동 토글 시 Leaflet 기본 «흰 박스 말풍선» 이 뜬다.
   ⇒ 공용 컴포넌트로 승격해 두 화면이 같은 모양을 쓰게 한다.

   설계 의도(원문 유지): 기본 툴팁 말풍선(배경·화살표·테두리)을 없애고 «지도 위 글자»만 남긴다(카카오식).
   글자색은 --text-primary 단일 — 레이어 구분은 경계선/채움 색과 범례 스와치가 담당한다.
   후광은 --bg-surface 3중 text-shadow 로 밑지도 위 가독을 확보한다. */
.cr-blabel.leaflet-tooltip {
  background: transparent; border: 0; box-shadow: none; padding: 0;
  color: var(--text-primary); font-size: var(--fs-xs); font-weight: 600;
  white-space: nowrap; pointer-events: none;
  text-shadow: 0 0 2px var(--bg-surface), 0 0 2px var(--bg-surface), 0 0 3px var(--bg-surface);
}
.cr-blabel.leaflet-tooltip::before { display: none; }   /* 말풍선 꼬리 제거 */
/* 줌 임계 미만에서 리·격자 라벨 숨김(라벨 뭉침 방지·발주자 클러터 우려) — JS 가 지도 컨테이너에 클래스 토글. */
.cr--hidelbl-ri .cr-blabel--ri { display: none; }

/* ── #12 Leaflet attribution 위치 조정 (2026-08-08) ──────────────────────────
   문제: Leaflet 기본 attribution(.leaflet-bottom.leaflet-right, 우하단)이
         .guidebar(bottom:sp-3, z:500)에 완전히 가려진다.
         OSM attribution 은 배경지도 약관상 필수 표기이므로 가려지면 안 된다.
   Leaflet attribution 기본 position: 'bottomright'(Leaflet 1.x 소스 확인).
   해결: index.html Leaflet 초기화에서 attributionControl:false + 수동으로
         L.control.attribution({position:'bottomleft'}) 추가 — 줌 컨트롤과 같은 코너.
         CSS 단독으로 position 이동 불가(Leaflet 이 DOM 코너별로 직접 배치하므로).
         (index.html Leaflet 초기화 블록 참조)
   ⚠ .leaflet-attribution-flag 규칙은 삽입하지 않는다:
      prefix:false 옵션 하에서 Leaflet 이 해당 요소를 렌더하지 않으므로
      그 셀렉터에 CSS 를 걸면 대상 없는 죽은 규칙이 된다.(F5 시정 2026-08-08) */

/* ===========================================================================
   16. 노선 프로파일 (RT-18) — 선형 스트립 다이어그램 + 연동 표 병치
       발주자 직접 요구: "버스 노선 중간에 표기와 함께 표로 표현".
       근거: R12 §C.10 (3영역 수직 스택). 여기서는 지도 없이 «스트립 + 표»만 전시한다
             (스트립은 좌표 없이도 그려진다 — R12 §C.10.8 이 명시한 이 뷰의 장점).
       더미 데이터. 값은 형태 확인용이며 실제 분석값이 아니다.
   =========================================================================== */
.profile { background: var(--bg-surface); border: var(--border-w) solid var(--border);
           border-radius: var(--radius-lg); box-shadow: var(--shadow-md); overflow: hidden; }
/* ③ 지표 스트립 — 노선 요약 한 줄 (R12 §C.10.2 상단) */
.profile__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
                 padding: var(--sp-3); border-bottom: var(--border-w) solid var(--border); }
.profile__name { font-size: var(--fs-h3); font-weight: 700; }
.profile__stats { display: flex; flex-wrap: wrap; gap: var(--sp-3);
                  font-size: var(--fs-sm); color: var(--text-secondary);
                  font-variant-numeric: tabular-nums; }
.profile__stats b { color: var(--text-primary); }

/* ── 선형 스트립 다이어그램 ──────────────────────────────────────────────
   승차 막대(위) / 구간 띠(가운데, 굵기=재차·색=재차율 등급) / 하차 막대(아래).
   구간 띠는 «이중 부호화»다 — 굵기와 색을 동시에 쓴다(R12 별표3 대응표 193행).
   가로축은 정류장 순번 등간격(기본). 정류장 노드는 원. */
.strip { padding: var(--sp-4) var(--sp-3); overflow-x: auto; }
.strip__scale { font-size: var(--fs-xs); color: var(--text-muted); margin-bottom: var(--sp-2); }
/* SVG 자체는 반응형 폭. 정류장이 많으면 최소폭을 넘겨 가로 스크롤(R12 §C.10.3 길이대응) */
.strip svg { display: block; min-width: 640px; width: 100%; height: auto; }
/* 스트립 구간 띠 색 — 재차율 등급. 순차형 지도용 램프를 재사용한다(값=크기 부호화).
   ⚠ 유채색은 데이터 전용 원칙에 맞다. 등급 경계는 범례에 숫자로 병기한다. */
.seg--1 { stroke: var(--seqmap-2); }
.seg--2 { stroke: var(--seqmap-3); }
.seg--3 { stroke: var(--seqmap-4); }
.seg--4 { stroke: var(--seqmap-5); }
.seg--5 { stroke: var(--seqmap-6); }
/* 스트립 구간·노드는 클릭으로 표 행을 선택할 수 있다(3-4 재확인 2026-08-08) — 클릭 어포던스. */
.strip__seg, .strip__node { cursor: pointer; }
.strip__node { fill: var(--bg-surface); stroke: var(--border-strong); stroke-width: 1.5; }
/* 선택·호버된 구간/노드 — UNI-4(전 화면 동일 하이라이트). 색만이 아니라 굵기로도 표시 */
.strip__seg.is-active { stroke: var(--color-primary); }
.strip__node.is-active { stroke: var(--color-primary); stroke-width: 2.5; fill: var(--color-primary); }

/* ── 스트립 선택 표시 (#1, 2026-08-08) ─────────────────────────────────────
   발주자 요구: «선택했는지 그래프상 표시 안 됨». 표 행 aria-selected 와 스트립을 동기화한다.
   is-active(호버)와 is-selected(선택)는 별개 클래스 — 함께 적용되면 선택이 시각 우선이다.
   색: --color-select(빨간 점선 계열, R41 §2.2 선택 규약). 굵기+stroke-dasharray 로 «점선» 표현.
   이유: is-active 는 실선+primary 파랑, is-selected 는 점선+select 빨강으로 구분해
     «호버(일시)»와 «선택(지속)»을 색+모양으로 동시에 구별(색 단독 아님, 고시 제11조①).
   ⚠ stroke-dasharray 는 SVG line/circle 에만 유효 — HTML DOM 에는 쓰지 않는다.
   ⚠ is-selected + is-active 동시 — is-selected 규칙이 더 뒤에 있어 stroke-dasharray 가 유지된다.
      (CSS cascade: 동일 specificity 에서 뒤 규칙이 이긴다) */
.strip__seg.is-selected {
  stroke: var(--color-select);
  stroke-dasharray: 4 3;          /* 점선: 4px 실선 + 3px 공백. 선택=지속임을 형태로 표현 */
}
.strip__node.is-selected {
  /* 발주자 «선택은 안쪽 채움이 아니라 바깥쪽 테두리 점선」(F5 R-2). fill 은 원래 배경색을 유지하고
     stroke 를 «점선 링」으로 준다 — seg(구간)의 점선(stroke-dasharray:4 3)과 형태가 통일된다. */
  fill: var(--bg-surface);
  stroke: var(--color-select);
  stroke-width: 2;
  stroke-dasharray: 2 2;          /* 바깥 점선 링 */
}
.bar--board { fill: var(--bar-board); }   /* 승차 막대(위) — --bar-board 토큰(파랑, tokens.css §스트립) */
.bar--alight { fill: var(--bar-alight); }  /* 하차 막대(아래) — --bar-alight 토큰(회색, tokens.css §스트립) */
.strip__axislabel { fill: var(--text-muted); font-size: 10px;
                    font-family: var(--font-sans); }
/* 환승거점 i-xfer / 주요시설 표식 — 색이 아니라 «형태»로 구분.
   #4 수정(F5-C): .ico { fill:none } 이 전역 기본값이나 fill:... 이 이를 덮어 형태 구분 소실.
   → fill 은 .ico 의 none 을 유지하고, stroke 만 currentColor 로 윤곽선을 보이게 한다.
   i-xfer 안쪽 십자는 stroke, i-facility 는 MS 교체(place 심볼).

   2-1·2-11 하이브리드 확정 (2026-08-08):
   ─ i-xfer(환승거점): 도메인 특화(원+십자 = 환승·연결 의미) → 인라인 SVG 유지.
   ─ i-facility(주요시설): Material Symbols "place" 로 교체 — 시설·장소를 표준 아이콘으로.
   크기: --icon-size-mark 토큰(12px = --fs-xs 동일값). SVG 맥락이라 em 대신 px 토큰 사용.
   이유: 스트립 SVG 는 viewBox 720×200 내부라 em 맥락이 외부 본문과 다르다.
         막대(승차/하차) 위에 표식이 놓이므로 본문 --icon-size(1.25em≈18.75px)보다 작아야 한다. */
.strip__mark {
  /* SVG <use> 방식(i-xfer): width/height 로 크기 지정(JS 코드 참조).
     HTML <span> 방식(i-facility → .ico-ms): font-size 로 크기 지정. */
  font-size: var(--icon-size-mark);   /* --icon-size-mark: 12px(tokens.css) — «한 줄 차지» 해소 */
}
/* <svg class="ico strip__mark"> 안의 <use> — fill/stroke 을 심볼 의도대로 설정.
   fill:none 은 바깥 테두리용(i-xfer 원). stroke 는 --text-primary 로 테마 대응.
   ★ (#2, 2026-08-08) currentColor → var(--text-primary) 로 변경.
     이유: currentColor 는 부모 요소의 color 값을 상속한다. 스트립 SVG 안에서 상속원이
     --text-muted(회색) 계열로 내려와 라이트=회색·다크=회색이 되어 테마 전환이 없었다.
     --text-primary 는 라이트=#141a21(검정 잉크)·다크=#f2f5f8(흰 계열)로 테마별 정의되어
     발주자 요구(라이트=검정/다크=흰)를 정확히 충족한다.
     표 셀(htmlMark)의 SVG use 도 이 규칙을 상속하므로 별도 처리 불필요. */
.strip__mark use { fill: none; stroke: var(--text-primary); }
/* .strip__mark 안의 .ico-ms — MS 아이콘(i-facility 교체분)에 적용.
   font-size 는 .strip__mark 에서 상속. 수직 정렬은 부모 flex 또는 SVG y 오프셋으로 맞춘다. */
.strip__mark.ico-ms { font-size: var(--icon-size-mark); vertical-align: middle; }

/* ── 정류장 역할 배지 (출/도/경, #3, 2026-08-08) ────────────────────────────
   발주자 요구: «카카오맵 출발=파란 깃발·도착=빨간 깃발처럼 작게 출/도/경».
   HTML 표 셀용: <span class="stop-badge stop-badge--origin">출</span> 형태.
   색 토큰: --stop-origin(파랑)·--stop-dest(빨강)·--stop-via(회색) — tokens.css 라이트/다크 정의.
   글자(출/도/경) 자체가 의미를 담아 색맹 무관(색은 보조). 흰 글자로 배경 대비 확보.
   크기: --fs-xs(12px) 기준. 배지는 inline-flex 로 정렬 — 정류장명과 같은 줄에 자연스럽게 붙는다.
   min-width: 1.4em — 한 글자 원형에 가깝게. border-radius:--radius-full 로 완전 둥근 형태.
   ⚠ 스트립 SVG 배지(rect+text)는 JS 코드에서 직접 그리며, 이 클래스는 HTML 표 셀 전용. */
.stop-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.4em;
  height: 1.4em;
  padding: 0 0.2em;
  font-size: var(--fs-xs);          /* 12px — 정류장명 옆 보조 정보 크기 */
  font-weight: 700;
  line-height: 1;
  /* 흰 글자는 출/도/경 3색 배지(파랑·빨강·회색) 배경 대비용·테마 무관 고정이라, 테마 반전 토큰
     (--text-on-accent 다크=검정)을 쓰면 대비가 깨진다. 고정 흰색이 정당하다(R13 근거). */
  color: #ffffff; /* ui-allow: UI001 3색 배지 배경 대비용 고정 흰색·테마 반전 토큰 부적합 */
  border-radius: var(--radius-full);
  vertical-align: middle;
  flex: none;
}
.stop-badge--origin { background: var(--stop-origin);   }  /* 출발: 파랑 */
.stop-badge--dest   { background: var(--stop-dest);     }  /* 도착: 빨강 */
.stop-badge--via    { background: var(--stop-via);      }  /* 경유: 회색 */
.stop-badge--xfer   { background: var(--stop-transfer); }  /* 환승: 녹 (i-xfer 화살표 폐기·통일, F5 R-1) */

/* ── (1b) 정류장명 셀 고정폭 아이콘칸 (2026-08-08) ──────────────────────────────
   발주자 지적: 아이콘 유무에 따라 정류장명 시작 위치가 흔들림.
   구조: .stop-cell(flex) > .stop-cell__icon(고정폭) + .stop-cell__name(나머지).
   아이콘 없는 행도 .stop-cell__icon 칸 자체는 항상 존재(빈 span) → 이름 시작 x 일정.
   --icon-size-mark(12px) + 좌우 여유(4px) = 16px 고정. 배지(.stop-badge) 높이와 맞춤.
   .stop-cell__icon 은 inline-flex + justify-content:center 로 배지를 중앙 정렬.
   .stop-cell__name 은 min-width:0 + overflow:hidden + text-overflow:ellipsis
     (table-layout:fixed 환경에서 셀 밖 넘침 방지 — .table th/td 의 overflow:hidden 와 이중 가드). */
.stop-cell {
  display: inline-flex;
  align-items: center;
  width: 100%;           /* 셀 전체 폭 사용 */
  gap: 0;
}
.stop-cell__icon {
  flex: none;
  width: 18px;           /* --icon-size-mark(12px) + 좌우 여유(3px×2) = 18px 고정폭 */
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.stop-cell__name {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 항목 3 (2026-08-08): 정류장명 열 헤더 텍스트를 데이터 행 이름 시작 x 에 정렬.
   .stop-cell__icon 고정폭(18px) 만큼 padding-left 추가 — 배지 유무 무관하게 이름이
   항상 18px 위치에서 시작하므로 헤더도 동일 x 에서 시작해야 정렬이 맞는다.
   .table th 규칙(specificity 0-1-1)을 덮으려면 .table .stop-col-head(0-2-0)로 높여야 한다. */
.table .stop-col-head {
  padding-left: calc(var(--sp-2) + 18px);  /* 셀 기본 padding(--sp-2=8px) + 아이콘칸(18px) = 26px */
}

/* 연동 표 — 스트립 바로 아래. 스트립과 «동일 데이터»이며 표가 곧 대체 표현이다
   (R12 별표3 대응표 105행: 표가 스트립의 접근성 대체 표현). 세로 스크롤.
   3-7 수정(2026-08-08): padding-top 미정의라 기존 padding-top 없음 확인.
   thead(position:sticky;top:0)와 스크롤 컨테이너 상단이 바짝 붙어야 틈이 없다.
   overflow-y:auto 가 스크롤 컨테이너를 형성하므로 thead top:0 은 이 컨테이너 기준이다.
   chart-table 과 달리 border-top·padding-top 이 없는 구조라 별도 조치 불필요.
   단, 차트표(chart-table) 와 동일한 스티키 thead 규칙을 components.css 아래에서 공유한다. */
.profile__table-wrap { max-height: 240px; overflow-y: auto; }
/* 재차율 등급 점 — 표 안에서도 색만 쓰지 않도록 «등급 숫자»를 함께 적는다 */
.grade-dot { display: inline-block; width: 10px; height: 10px; border-radius: 2px;
             margin-right: var(--sp-1); vertical-align: -1px;
             border: 1px solid var(--halo-dark); }
/* 추정 신뢰도 배지(1~4단계) — 하차 76.8%가 추정. 1단계와 4단계를 같은 굵기로
   그리면 «거짓 정밀»이 된다(R12 §C.10.3). 숫자로 단계를 명시. */
.est-badge { display: inline-block; min-width: 16px; text-align: center;
             font-size: var(--fs-xs); font-weight: 700; border-radius: var(--radius-sm);
             padding: 0 4px; border: var(--border-w) solid var(--border-strong);
             color: var(--text-secondary); }
.est-badge[data-level="1"] { border-color: var(--color-success); color: var(--color-success); }
.est-badge[data-level="4"] { border-color: var(--color-warning); color: var(--color-warning); }

/* ===========================================================================
   17. 데이터 시각화 팔레트 스와치 확충 (2차 갤러리 신설)
       순차형(단색 6등급)·발산형(양끝+중립회색)·범주형(고정순서) 전시.
       각 스와치에 등급 라벨을 병기해 «색 단독 전달»을 피한다(고시 제11조①).
   =========================================================================== */
.viz-block + .viz-block { margin-top: var(--sp-4); }
.viz-block__label { font-size: var(--fs-xs); color: var(--text-muted); margin: 0 0 var(--sp-2); }
/* 라벨이 달린 램프 — 각 칸 아래 등급 숫자/부호를 적는다 */
.ramp--labeled { border: 0; gap: 2px; }
.ramp--labeled .ramp__step { border-radius: var(--radius-sm);
                             display: flex; align-items: flex-end; justify-content: center;
                             height: 44px; padding-bottom: 2px;
                             border: 1px solid var(--halo-dark); }
.ramp__tag { font-size: var(--fs-xs); font-weight: 700;
             font-family: var(--font-mono); }

/* ===========================================================================
   18. 차트 (ECharts) — 섹션 §12
       ECharts 가 SVG 를 이 컨테이너 안에 그린다. 색은 charts-demo.js 가 R13 토큰에서
       읽어 주입한다(원시 hex 없음). 카드는 불투명 surface + 그림자(ADR-013).
   =========================================================================== */
.chart-card { display: flex; flex-direction: column; }
/* 차트 그리기 영역 — ECharts 가 이 크기에 맞춰 렌더한다. 반응형(폭 100%). */
.chart { width: 100%; height: 260px; }
.chart--tall { height: 320px; }               /* 히트맵·발산막대는 세로 여유 */
.chart-tools { display: flex; align-items: center; justify-content: space-between;
               gap: var(--sp-2); margin-top: var(--sp-2); flex-wrap: wrap; }
/* 「표로 보기」로 펼치는 데이터표(CH-A1). 세로 스크롤 — 긴 표가 카드를 밀지 않게.
   3-7 수정(2026-08-08): padding-top 을 제거하고 margin-top 으로 대체한다.
   이유: padding-top 이 있으면 thead(position:sticky;top:0) 와 스크롤 컨테이너 상단 사이에
   «여백 슬롯»이 생겨 스크롤 시 본문 행이 그 틈으로 비쳐 보인다(발주자 3-7).
   border-top 은 margin 경계 위에 있으므로 시각 구분선은 유지된다.
   표 상단 여백은 margin-top 으로 대신해 레이아웃 효과는 동일하게 보존한다. */
.chart-table { margin-top: var(--sp-3); max-height: 240px; overflow: auto;
               border-top: var(--border-w) solid var(--border); }
.chart-table[hidden] { display: none; }
/* 데이터표 안의 표는 기존 .table 규격을 그대로 쓴다(charts-demo.js 가 .table 로 생성) */

/* ── 차트 툴바 (R41 §5) — [전체선택·해제·PNG·CSV·XLSX] ──────────────────────
   charts-demo.js 가 각 차트 카드의 .chart-tools 에 «생성」해 넣는다(하드코딩 아님).
   버튼은 기존 .btn.btn--sm 재사용(S-1: 텍스트 라벨). 좁은 화면에서 줄바꿈. */
.chart-toolbar { display: inline-flex; flex-wrap: wrap; gap: var(--sp-1); align-items: center; }
/* 「표로 보기」 + 툴바가 한 줄에 다 들어가도록 .chart-tools 를 왼쪽 정렬로(기존 space-between 대신). */
.chart-tools { justify-content: flex-start; }
/* XLSX 로딩 중 표시(aria-busy) — 커서·투명도로 «불러오는 중」을 알린다(레이아웃 불변). */
.chart-toolbar button[aria-busy="true"] { opacity: .6; cursor: progress; }

/* ===========================================================================
   C그룹 — 차트 인터랙션 재설계 (#5·#16·#17·#2·#6·#7·#21, 2026-08-08)
   =========================================================================== */

/* ── 차트 카드 헤더 (#5·#16·#17) ────────────────────────────────────────────
   툴바를 캔버스 «밖» 카드 상단 제목 줄에 배치한다.
   제목(.chart-card__title)과 툴바(.chart-card__toolbar)를 한 줄에 배치.
   stamp(.stamp)는 헤더 아래 별도 줄에 항상 표시(#16: 설명글 상시 유지).

   #17 자동 해결 근거:
     ECharts 의 getDataURL(svg) 는 차트 인스턴스 내부 SVG 만 직렬화한다.
     .chart-card__toolbar 는 ECharts 캔버스 바깥 DOM 요소이므로
     PNG 에 버튼이 처음부터 포함되지 않는다 — 별도 처리가 필요 없다.

   chart-card 에 position:relative 추가 — 툴바 absolute 배치의 기준점. */
.chart-card { position: relative; }

/* 카드 헤더 컨테이너 — 제목 | 표로보기 | 툴바 를 겹치지 않게 배치한다.
   2-2 수정 (2026-08-08):
   원인: 제목(flex:1 1 0)·「표로 보기」 버튼·툴바(flex-shrink:0) 가 한 줄에 들어가면
     버튼+툴바 합산 너비가 제목 가용 폭을 초과해 제목이 0 으로 수축하고 잘린다.
     발주자 «이대로는 못 씀» 지적의 근본 원인.
   처방: flex-wrap:wrap 으로 좁아지면 툴바 행을 다음 줄로 내린다.
     제목 행(첫 줄): 제목(flex:1 1 60%) + 「표로 보기」 버튼(flex-shrink:0).
     툴바(두 번째 줄, 필요 시): width:100% 로 전폭 차지.
     이렇게 하면 제목은 항상 가용 너비의 60% 이상을 보장받고,
     툴바가 넘칠 때는 행 아래로 내려가 겹침이 0 이 된다.
   ⚠ 「표로 보기」 버튼은 카드 너비에 상관없이 제목 옆에 유지된다(상시 표시 요건, #16).
      툴바(.chart-card__toolbar)만 둘째 줄로 내려간다. */
.chart-card__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;          /* 좁아지면 툴바를 다음 줄로 내려 겹침 방지(2-2) */
  gap: var(--sp-1) var(--sp-2);
  padding: var(--sp-2) var(--sp-2) var(--sp-1);
  min-height: 36px;         /* .btn--sm min-height 26px + 상하 패딩 여유. 정방향 계산 */
}

/* 카드 제목 (demo__label 대체) — 왼쪽 고정. 줄바꿈 없이 넘치면 말줄임. */
.chart-card__title {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1 1 60%;            /* 제목은 60% 이상 차지. 툴바 너비가 40% 이내면 한 줄에 공존.
                               초과하면 툴바가 wrap 되어 다음 줄로(2-2 핵심). */
  min-width: 0;             /* flex 항목이 overflow:hidden 를 존중하게 (flex 기본값 auto 를 덮음) */
  margin: 0;
}

/* ── 툴바 컨테이너 (3-5, 2026-08-08) ──────────────────────────────────────
   3-5 이전: opacity:0/1 호버 노출로 ⋮ 안의 모든 버튼을 숨겼다 (#16).
   3-5 이후: ⋮(케밥) 버튼 하나만 상시 표시하고, 내용은 드롭다운으로.
   · opacity·pointer-events 제어를 제거한다(발주자 «쓸데없는 애니메이션 단순화»).
   · .chart-card__toolbar 는 이제 «케밥 버튼+드롭다운의 컨테이너» 역할만 한다.
   · 위치 정렬: flex-shrink:0, 내용 너비 유지(flex-basis:auto).

   2-2 wrap 대응(2026-08-08)은 그대로 유지 —
   툴바+제목이 한 줄에 안 들어오면 flex-wrap:wrap 으로 다음 줄에 내려간다. */
.chart-card__toolbar {
  display: inline-flex;
  flex-shrink: 0;
  flex-basis: auto;
  align-items: center;
  position: relative;       /* 드롭다운 .chart-card__menu 의 absolute 기준점 */
}

/* 「표로 보기」 버튼 — .chart-card__head 안에서 수축 방지 (2-2, 2026-08-08).
   .btn 은 flex-shrink 기본값(1)이라 제목·단위·툴바 압박 시 텍스트가 잘릴 수 있다.
   헤더 안 토글 버튼만 flex-shrink:0 으로 고정한다.
   셀렉터: .chart-card__head 직계 자식 [data-table-toggle] 버튼. */
.chart-card__head > [data-table-toggle] {
  flex-shrink: 0;   /* 「표로 보기」 텍스트 잘림 방지 — 제목(flex:1 1 60%)이 먼저 줄어듦 */
}

/* ── 단위 라벨 (#2-3, 2026-08-08) ──────────────────────────────────────────
   buildToolbar 가 chartRegistry 의 unit 필드를 읽어 이 클래스의 p 를 삽입한다.
   위치 이력:
     · 초기(2-3): 제목 바로 옆
     · 3-6(2026-08-08): 헤더 맨 오른쪽(margin-left:auto)
     · 4차 재지시(2026-08-08): «표로 보기» 아래 별도 줄 — 헤더 밖 카드 직속 자식(p 요소)
     · 항목 4 최종(2026-08-08): stamp 와 같은 줄 맨 오른쪽 — stamp p(flex) 안의 span.
   margin-left:auto 로 stamp flex 컨테이너 오른쪽 끝에 붙는다.
   white-space:nowrap — 단위 텍스트가 줄바꿈되지 않게. */
.chart-card__unit {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin-left: auto;       /* stamp flex 컨테이너 맨 오른쪽 — 항목 4 */
  white-space: nowrap;     /* «(단위: 명)» 등이 줄바꿈되지 않게 */
  padding-left: var(--sp-2);  /* stamp 텍스트와 최소 간격 */
}

/* ── 케밥(⋮) 버튼 (3-5, 2026-08-08) ────────────────────────────────────────
   상시 표시. 기존 호버 opacity 전환 없음(단순화).
   크기: .btn--icon(width:32px) 상속 — 내부 ⋮ SVG 는 4×16 viewBox (F5 L-1).
   ⚠ .btn--icon 의 width:32px + padding:0 을 상속한다. 더 좁은 ⋮ SVG 를 담으므로
     시각 크기보다 클릭 영역이 크지만 이는 WCAG 2.2 SC 2.5.8(최소 24px) 에 유리하다.
   색: currentColor 상속 — 글자색 따라감. 다크/라이트 자동 대응. */
.chart-card__kebab {
  /* .btn.btn--sm.btn--icon 을 베이스로 가져다 쓴다(JS에서 클래스 지정).
     여기서는 추가 조정만 한다. */
  flex-shrink: 0;
}
@media (hover: hover) {
  .chart-card__kebab:hover {
    background: var(--bg-subtle);
    color: var(--text-primary);
  }
}

/* ── 케밥 드롭다운 메뉴 (3-5, 2026-08-08) ───────────────────────────────────
   절대위치 — .chart-card__toolbar(position:relative) 기준 우측 정렬.
   기본 hidden. JS 가 hidden 속성을 토글한다.

   배경: --bg-surface(불투명, ADR-013 — 반투명·blur 금지).
   테두리: --border(장식선), 그림자: --shadow-md 계열.
   z-index: 200 — 차트 SVG(z:1)·chart-table(z:1) 위, 다른 플로팅 요소 아래.

   ⚠ top:calc(100% + var(--sp-1)) — 케밥 버튼 바로 아래 --sp-1(4px) 간격.
     right:0 — 오른쪽 끝 정렬(발주자 «표로 보기 옆 ⋮, 오른쪽에서 열림»).
   매직 px 없음 — 간격은 토큰, z-index 200 은 «차트 z:1 위, 전역 모달 z:1000 아래»
     로 역할 기준 숫자이며 한글 주석으로 근거를 기재한다. */
.chart-card__menu {
  position: absolute;
  top: calc(100% + var(--sp-1));   /* 케밥 버튼 바로 아래 sp-1(4px) 간격 */
  right: 0;                         /* 오른쪽 끝 정렬 */
  z-index: 200;                     /* 차트 SVG(z:1) 위, 전역 모달(z:1000) 아래 */
  min-width: 9rem;                  /* 가장 긴 라벨 «전체 선택/해제» 기준 최소 너비 */
  background: var(--bg-surface);   /* 불투명 — ADR-013 반투명 금지 */
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  /* 그림자: --shadow-md 는 테마별로 다르다(라이트 .10 / 다크 .50). var 로 참조해야 다크에서도
     부유감이 유지된다(F5 M-1: rgba 하드코딩은 다크에서 그림자가 옅어지는 회귀·단일출처 위반). */
  box-shadow: var(--shadow-md);
  padding: var(--sp-1) 0;          /* 메뉴 상하 여백 — 항목과 테두리 사이 숨공간 */
  display: flex;
  flex-direction: column;
}
/* [hidden] 속성이 있으면 완전히 숨긴다(JS 토글 대상). */
.chart-card__menu[hidden] { display: none; }

/* ── 메뉴 항목([role=menuitem]) (3-5, 2026-08-08) ──────────────────────────
   전폭 버튼 — 메뉴 너비를 꽉 채운다.
   텍스트 왼쪽 정렬. 호버 시 --bg-subtle 배경.
   .btn.btn--sm 을 베이스로 쓰되(JS에서 클래스 지정) 여기서 전폭·정렬을 덮어쓴다.
   테두리·radius 제거 — 메뉴 안에서는 항목 경계선 없이 배경만으로 구분. */
.chart-card__menu [role="menuitem"] {
  width: 100%;
  border-radius: 0;             /* 메뉴 안 항목은 각진 배경(메뉴 컨테이너가 radius 담당) */
  border: 0;                    /* 항목 간 경계선 없음 — 배경 호버로만 구분 */
  background: transparent;
  text-align: left;
  justify-content: flex-start;  /* .btn 의 center 를 왼쪽 정렬로 덮어씀 */
  padding: var(--sp-1) var(--sp-3);
  color: var(--text-primary);
}
@media (hover: hover) {
  .chart-card__menu [role="menuitem"]:hover {
    background: var(--bg-subtle);
  }
}
/* 포커스 — :focus-visible 로 키보드 포커스만 표시(마우스 클릭 시 outline 없음). */
.chart-card__menu [role="menuitem"]:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;   /* 메뉴 안쪽에서 outline 이 잘리지 않도록 안쪽으로 */
}

/* stamp 는 상시 표시 — 헤더 아래 별도 줄. (#16: 설명글은 항상 보임) */
/* 항목 4 (2026-08-08): stamp 와 unit 을 같은 줄에 배치.
   stamp p 를 flex row 컨테이너로 만들고, 안의 span.chart-card__unit 이 margin-left:auto 로
   맨 오른쪽에 붙는다. white-space:nowrap 은 stamp 텍스트에만 적용(flex 아이템 개별 지정). */
.chart-card__stamp {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  display: flex;
  align-items: baseline;
  padding: 0 var(--sp-2) var(--sp-1);
}

/* 노선402 카드에도 동일 헤더 구조를 적용한다(통일성 #5·#15).
   .profile 컨테이너가 chart-card 와 동일한 hover 맥락을 제공하도록.
   profile__head 안의 툴바는 chart-card__toolbar 와 동일 규칙을 공유한다. */
.profile__head .chart-card__toolbar {
  margin-left: auto;
}

/* ── 표 «같은 공간에서 필터»(A-2 자동 필터, R47 §4 P0, 2026-08-08) ──────────
   [정정 이력·CLAUDE §9] 종전(#2·#6·#7 초안)에는 선택 행을 cloneNode 로 복제해 thead
   바로 아래 sticky 로 고정(.is-pinned)했다. 발주자 재확정(대장 FEEDBACK_반영대장.md:50)으로
   «같은 공간에서 필터»(A-2)로 전환한다: 선택된 행만 남기고 비선택 행을 .is-hidden 으로
   숨기며, 전체 해제 시 전원 복원한다. 복제행이 사라지므로 #21(스크롤 리셋)의 원인이
   구조적으로 소멸한다(R47 §2 R1 «보너스»). 신규 토큰 0 — 호버(.is-hover 색만)·선택강조
   (aria-selected 좌측 바)는 아래 기존 규칙을 그대로 재사용한다. */

/* thead sticky — 필터/스크롤 중에도 머리글이 사라지지 않게(원 pinned 도입분, 필터에도 유효).
   배경 불투명 필수 — 투명이면 스크롤 중 본문 행이 비쳐 머리글이 읽히지 않는다. */
.chart-table thead,
.profile__table-wrap thead,
.cr__tablewrap thead {                              /* 굴곡도 검토 뷰 표도 같은 스티키 규칙 계승(발주자 2026-08-16) */
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg-surface);                    /* 불투명 배경 — 스크롤 중 본문 행 비침 방지 */
}

/* A-2 필터: 비선택 행 숨김. display:none 이라 AT·탭 순서에서도 제외된다(R47 §2 R2 접근성).
   «레이아웃 변화」지만 호버 강조가 아니라 «선택의 명시적 결과»라 R36 IS-1 흔들림이 아니다. */
.is-hidden { display: none; }

/* 「표로 보기」로 접힌 데이터표 숨김 — hidden 속성 + 명시 규칙(§13 ③ rd-hourly-table).
   UA 의 [hidden]{display:none} 이 .table 의 요소기본(display:table)에 밀리지 않도록 명시한다. */
.table[hidden] { display: none; }

/* 시각 숨김 유틸(스크린리더 전용) — 필터 상태 aria-live status 에 쓴다(R47 §4 ⑥, WCAG 4.1.3).
   표준 클립 패턴(관례값 — 매직넘버 아님, 시각 숨김의 정석). */
.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;
}

/* ── 노선 상세 4블록 카드 (김해 표준 슬라이드 이식, §13) ────────────────────
   근거: 08. 김해교류/07_다원_보고서_표그래프_분석.md §1(고정 틀)·§1-2(4블록 역할·색)·§6-A.
   김해 다원 보고서의 노선검토 슬라이드는 «좌 60% 대형 노선도 + 우 40% 4블록»이 고정 틀이고
   노선 슬라이드 26장이 전부 이 한 화면의 인스턴스다. 좌측 노선도는 현황/개편안 경로 2벌이
   필요해(R50 시나리오 op·§6-A 2 후속) 여기서는 자리만 두고, 우측 4블록을 A-2 route_tier·
   A-3 v_od_emd 실측으로 채운다. 신규 원시 hex 0 — 역할 토큰만 참조(R13). */
.rcard {
  display: grid;
  grid-template-columns: 3fr 2fr;                   /* 좌 60% 노선도 · 우 40% 4블록(§1 틀) */
  gap: var(--sp-3);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-2, 8px);
  background: var(--bg-surface);
  padding: var(--sp-3);
}
/* 좁은 화면에서는 세로로 쌓는다 — 4블록이 못 읽히면 안 된다(R36 반응형). */
@media (max-width: 800px) { .rcard { grid-template-columns: 1fr; } }

/* 좌측 대형 노선도 «자리» — 경로 2벌 미확보라 지어내지 않고 안내로 둔다(정직, §6-A 2). */
.rcard__map {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: 320px; padding: var(--sp-4);
  border: var(--border-w) dashed var(--border-strong);
  border-radius: var(--radius-2, 8px);
  background: var(--bg-subtle, #f5f7fa);
  color: var(--text-secondary); text-align: center;
}

/* 우측 4블록 스택(고정 순서 ①노선특징 ②연관표 ③시간대 ④개편방향, §1-2). */
.rcard__blocks { display: flex; flex-direction: column; gap: var(--sp-3); }
.rblock { border: var(--border-w) solid var(--border); border-radius: var(--radius-2, 8px); padding: var(--sp-3); }
/* «>> 제목» 블록 헤더 — 원 보고서의 파란 볼드 소제목 관례(§1-2 ①④). */
.rblock__title {
  margin: 0 0 var(--sp-2); font-size: var(--fs-sm); font-weight: 700;
  color: var(--color-primary);
}
.rblock__title::before { content: "» "; }         /* 원 보고서 «>>» 관례를 겹화살표로 표기 */
.rblock ul { margin: 0; padding-left: 1.1em; color: var(--text-secondary); font-size: var(--fs-sm); }
.rblock li { margin: 0 0 var(--sp-1); }
/* 진단 핵심어 강조: 파랑=지표·수준, 빨강=개편 결정(§1-2 색 규칙). */
.rblock b.hl-blue { color: var(--color-primary); }
.rblock b.hl-red  { color: var(--color-danger); }

/* ③ 시간대별 미니막대(5~23시). 값 그라데이션 + 피크만 노랑(§1-2 ③).
   막대 높이는 인라인 style(정적 실측값·2025-10-23 노선134)로 준다 — JS 불필요(render-check 안전). */
.minibars { display: flex; align-items: flex-end; gap: 2px; height: 96px; margin-top: var(--sp-1); }
.minibar {
  flex: 1 1 0; min-width: 0; border-radius: 2px 2px 0 0;
  background: var(--seqmap-3);                       /* 기본 톤(값 그라데이션은 아래 변형) */
}
.minibar--lo   { background: var(--seqmap-2); }      /* 저이용 시간대 */
.minibar--hi   { background: var(--seqmap-4); }      /* 고이용 시간대 */
/* 피크만 강조색(§1-2 ③ «피크만 노랑»). 역할 토큰 --color-warning 사용 — 라이트 앰버(#8a5a00)·
   다크 골드(#e0a52a). 순수 노랑 채움은 원시 hex 신설이라 R13 팔레트 절차(verify_palette.py) 없이
   두지 않는다(components.css 규약: 역할 토큰만). 파랑 그라데이션과 색상 대비가 커 피크 식별에 충분. */
.minibar--peak { background: var(--color-warning); }
/* ③ 미니막대↔데이터표 brushing 강조(발주자 2026-08-16) — 호버·연동 시 대응 막대를 테두리로 부각.
   outline 은 페인트만 바꿔 레이아웃을 건드리지 않는다(R36 IS-1 흔들림 0). 색은 역할 토큰(--color-primary). */
.minibar.is-hover { outline: 2px solid var(--color-primary); outline-offset: 1px; }
.minibars__axis {
  display: flex; gap: 2px; margin-top: 2px;
  font-size: var(--fs-xs); color: var(--text-muted);
}
.minibars__axis span { flex: 1 1 0; text-align: center; }

/* §13 ② 연관표 — 김해 ②는 «행정구역» 또는 «연관노선» 판이 병존한다(07 §1-2 ②).
   갤러리는 «틀»을 보이는 자리라 두 판을 한 블록에 싣게 되어, 표 위 소제목이 필요해졌다.
   신규 원시 hex 0 — 역할 토큰만 쓴다(components.css 규약·R13). */
.rblock__sub {
  margin: 0 0 var(--sp-1);
  display: flex; align-items: baseline; gap: var(--sp-1); flex-wrap: wrap;
  font-size: var(--fs-sm); font-weight: 700; color: var(--text-secondary);
}
/* §13 소제목·제목에 케밥 내보내기 툴바를 «우측» 정렬 — 텍스트 좌·⋮ 우(발주자 2026-08-16 index 계승).
   .rblock__sub 는 이미 flex 이고, .rblock__title 은 여기서 flex 를 얻는다. nowrap 으로 케밥 줄내림 방지.
   ★ justify-content:space-between 대신 케밥에 margin-left:auto — 제목의 «»(::before)·텍스트가
     각각 flex item 이라 space-between 이면 3분할로 텍스트가 중앙으로 밀린다(2026-08-16 render-check 실측).
     margin-left:auto 는 케밥만 오른쪽으로 밀고 나머지는 자연 좌측 정렬한다(FEEDBACK 검증 패턴). */
/* ★ position:relative — .chart-card__menu 는 absolute(top:100%·right:0)라 «positioned 조상(앵커)»이
   필요하다. 없으면 메뉴가 초기 컨테이닝 블록(body) 기준으로 페이지 최상단에 열린다
   (F5 CRITICAL C-1·좌표 실측 케밥 y=9063 vs 메뉴 y=904). 이 1줄이 메뉴를 케밥 바로 아래에 앵커한다. */
.rblock__sub--tools { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: nowrap; position: relative; }
.rblock__sub--tools .chart-card__kebab { flex: 0 0 auto; align-self: center; margin-left: auto; }
/* 표 셀 안의 «2022 원번호» 같은 부가 표기 — 주 번호(현재번호)와 시각적으로 구분한다.
   개편후 번호는 (추정)이라(R30 §2) 원번호를 함께 보여야 근거를 추적할 수 있다. */
.t-muted { color: var(--text-muted); font-size: var(--fs-xs); font-weight: 400; }

/* ── 지도 컨트롤 «좌하단 클러스터» `.tl-mapctl` (2026-08-20 승격) ─────────────
   왜 올렸나: 이 배치는 circuity-review.html 의 «페이지 로컬 <style>» 에만 있었다. 그래서
   현황 화면(status)은 확대·축소만 우상단에 덩그러니 두고 «축척 바가 아예 없는» 상태로 만들어졌다
   (발주자 2026-08-20 지적: 「확대 축소 버튼과 행정동 경계는 circuity 처럼 해야 하고 스케일(축척)이 빠져 있다」).
   `.cr-blabel` 을 승격했던 것과 같은 이유다 — 복사하면 또 «반쪽 계승»이 된다.

   배치 결정 이력(발주자 2026-08-16·2026-08-17 · 원문 취지 보존):
     · +/- 는 왼쪽에 2행 span, 오른쪽 위에 「스케일(Zoom)」 드롭다운, 그 아래 축척 바, 맨 밑에 출처(©).
     · col1=min-content(+/- 고정) · col2=1fr(잔여 고정) — 축척 바 폭은 줌마다 바뀌는데,
       auto auto 로 두면 그 변화가 열 폭을 재계산해 «왼쪽이 흔들린다»(발주자 「왼쪽 흔들림」·R36 IS-2).
     · 가장자리에 붙이지 않고 여백만큼 안쪽으로 — 다른 Leaflet 카드(기본 10px)와 선을 맞추기 위함.
   쓰는 법: 지도 컨테이너에 `tl-mapctl` 클래스를 준다. 줌 드롭다운은 `.tl-zoomctl`. */
.tl-mapctl .leaflet-bottom.leaflet-left {
  display: grid; grid-template-columns: min-content 1fr;
  grid-template-areas: "zbtn zsel" "zbtn sbar" "attr attr";
  align-items: end; justify-items: start; gap: var(--sp-1) var(--sp-2);
  left: var(--sp-2); bottom: var(--sp-2);
}
.tl-mapctl .leaflet-bottom.leaflet-left .leaflet-control-zoom { grid-area: zbtn; margin: 0; align-self: stretch; }
/* 스케일(줌) 드롭다운은 row1 «상단»에 붙여 + 버튼 상단과 같은 선에 둔다(발주자 2026-08-17).
   클러스터가 align-items:end 라 기본은 바닥에 붙어 +/- 보다 아래로 내려간다. */
.tl-mapctl .leaflet-bottom.leaflet-left .tl-zoomctl,
.tl-mapctl .leaflet-bottom.leaflet-left .cr-zoomctl { grid-area: zsel; margin: 0; align-self: start; }
.tl-mapctl .leaflet-bottom.leaflet-left .leaflet-control-scale { grid-area: sbar; margin: 0; }
.tl-mapctl .leaflet-bottom.leaflet-left .leaflet-control-attribution {
  grid-area: attr; margin: 0; background: transparent;
  text-shadow: 0 0 2px var(--halo-light), 0 0 2px var(--halo-light);
}
/* 축척 바 — 검정 반투명 배경·흰 글자·흰 브래킷(발주자 2026-08-16). 밑지도 위에 얹히는 표시라
   테마와 무관하게 고정색을 쓴다(그래서 UI001 예외를 같은 줄에 명시한다). */
.tl-mapctl .leaflet-control-scale-line {
  background: rgba(33, 37, 41, .75);   /* ui-allow: UI001 발주자 지정 축척 배경(밑지도 위 고정색·테마무관) */
  color: var(--halo-light);
  border: var(--border-w) solid rgba(248, 249, 250, .75); border-top: none;   /* ui-allow: UI001 발주자 지정 축척 테두리 고정색 */
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  padding: 0 var(--sp-1); font-size: var(--fs-xs); line-height: 1.4; font-weight: 700;
  text-shadow: none; box-shadow: none;
}
/* 「스케일(Zoom)」 드롭다운 — 지금 줌이 몇인지 늘 보이게 하고, 원하는 줌으로 바로 가게 한다
   (발주자 2026-08-16 「스케일이 몇인지 모르겠다·특정 스케일만 이상하다고 말을 못 하겠다」). */
.tl-zoomctl { background: var(--bg-surface); border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-md);
  padding: var(--sp-1) var(--sp-2); font-size: var(--fs-xs); color: var(--text-secondary); }
.tl-zoomctl select { margin-left: var(--sp-1); font: inherit; color: var(--text-primary);
  background: var(--bg-subtle); border: var(--border-w) solid var(--border);
  border-radius: var(--radius-sm); padding: 0 var(--sp-1); }

/* ── 필터 바 `.cr-filterbar` (2026-08-20 승격) ──────────────────────────────
   왜 올렸나: 이 스타일도 circuity-review.html 로컬에만 있었다. 그래서 현황 화면이 같은 클래스명을
   써도 «이름만 계승하고 모양은 못 받는» 상태가 된다(`.cr-blabel` 과 똑같은 사고).
   설계 의도(원문 유지): 필터를 메뉴 안에 숨기지 않고 «패널 상단에 항상» 둔다
   — 숨겨 두면 「필터 기능도 없다」로 읽힌다(발주자 2026-08-17). 토큰색만(UI001)·흔들림0(R36). */
.cr-filterbar { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  padding: var(--sp-1) var(--sp-2); border-bottom: var(--border-w) solid var(--border);
  background: var(--bg-subtle); font-size: var(--fs-sm); }
.cr-filterbar__f { display: inline-flex; align-items: center; gap: var(--sp-1); color: var(--text-secondary); white-space: nowrap; }
.cr-filterbar select { font: inherit; font-size: var(--fs-sm); color: var(--text-primary);
  background: var(--bg-surface); border: var(--border-w) solid var(--border); border-radius: var(--radius-sm); padding: 1px var(--sp-1); }
.cr-filterbar__status { margin-left: auto; color: var(--text-muted); font-size: var(--fs-xs); }
.cr-filterbar__reset { font: inherit; font-size: var(--fs-sm); color: var(--text-secondary); background: var(--bg-surface);
  border: var(--border-w) solid var(--border); border-radius: var(--radius-sm); padding: 1px var(--sp-2); cursor: pointer; margin-left: auto; }
.cr-filterbar__reset:hover { background: var(--bg-subtle); }
/* 초기화 버튼 «뒤»에 상태 문구가 오면 둘 다 margin-left:auto 라 붙어 버린다 → 상태 문구는 여백을 뗀다. */
.cr-filterbar__reset + .cr-filterbar__status { margin-left: var(--sp-2); }

/* ── 지도 위 «표시 메뉴» `.tl-mapmenu` (2026-08-20 신설) ──────────────────────
   왜 만들었나 — 발주자 2026-08-20:
     「트리플 콜론(⋮)은 «마우스를 올려놓지 않으면 사라지게» 되어 있고 배치도 제각각이라
       통일성이 전혀 없다. **호버라는 걸 일반 사람들은 모른다.** 일반 사람도 알 수 있게 표현되면 된다.」
   그래서 이 메뉴는 ① «항상» 보이고 ② 아이콘만 두지 않고 «글자 라벨»을 함께 둔다.
   ⋮ 만 있으면 「누를 수 있는 것」인지조차 모른다 — 제안서를 보는 분들은 이 화면을 처음 본다.
   흔들림0(R36): 호버는 배경만 바뀐다(굵기·여백·폭 불변). 팝업은 불투명(ADR-013 — 반투명·블러 금지). */
.tl-mapmenu { position: relative; }
.tl-mapmenu__btn { display: inline-flex; align-items: center; gap: var(--sp-1);
  background: var(--bg-surface); border: var(--border-w) solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow-md); color: var(--text-primary); cursor: pointer;
  font: inherit; font-size: var(--fs-xs); font-weight: 600; padding: var(--sp-1) var(--sp-2); }
.tl-mapmenu__btn:hover { background: var(--bg-subtle); }
.tl-mapmenu__pop { position: absolute; top: calc(100% + var(--sp-1)); right: 0; z-index: 700; min-width: 200px;
  background: var(--bg-surface); border: var(--border-w) solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow-md); padding: var(--sp-1); }
.tl-mapmenu__pop[hidden] { display: none; }
.tl-mapmenu__sec { font-size: var(--fs-xs); color: var(--text-muted); padding: var(--sp-1) var(--sp-1) 2px; }
.tl-mapmenu__item { display: flex; align-items: center; gap: var(--sp-1); padding: 2px var(--sp-1);
  font-size: var(--fs-sm); color: var(--text-primary); cursor: pointer; border-radius: var(--radius-sm); }
.tl-mapmenu__item input { flex: none; }   /* 체크박스는 줄지 않게(라벨이 두 줄로 밀리던 것) */
.tl-mapmenu__item:hover { background: var(--bg-subtle); }
.tl-mapmenu__item select { margin-left: auto; font: inherit; font-size: var(--fs-xs); color: var(--text-primary);
  background: var(--bg-subtle); border: var(--border-w) solid var(--border); border-radius: var(--radius-sm); padding: 0 var(--sp-1); }

/* ── 도움말 «?» 버튼 + 아래쪽 설명 `.tl-help` (2026-08-20 신설) ───────────────
   왜 만들었나 — 발주자 2026-08-20:
     「`?` 버튼을 만들어 **컴포넌트 연계 시 아래쪽에 나오게끔** 해 줘야 한다.
       차후 사용방법에 대해서 표시되면서 설명되는 기능들도 추가될 것이다.」
     그리고 같은 날: 「호버라는 걸 **일반 사람들은 모른다**. 일반 사람도 알 수 있게 표현되면 된다.」

   그래서 이 부품은
     ① 버튼이 «항상» 보이고(호버로 나타나지 않는다),
     ② 눌렀는지 아닌지가 `aria-expanded` 로 드러나며,
     ③ 설명이 그 컴포넌트 «바로 아래»에 펼쳐진다(딴 데 뜨는 팝업이 아니다).

   기존 지표 도움말(metrics-help.js)은 «화면에 떠 있는 카드»다. 그것은 지표 하나를 깊게 읽는 용도이고,
   이 부품은 «지금 보고 있는 표·차트가 무엇인가»를 그 자리에서 밝히는 용도다. 둘은 자리가 다르다.

   흔들림0(R36): 열고 닫을 때 버튼의 굵기·여백·폭이 변하지 않는다(배경만 바뀐다).
   펼침이 아래 내용을 밀어내는 것은 «레이아웃 변경»이 아니라 사용자가 «의도한» 동작이라 무방하다. */
.tl-help { display: inline-flex; align-items: center; }
.tl-help__btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-1);
  min-width: 1.5em; height: 1.5em; padding: 0 var(--sp-1);
  background: var(--bg-subtle); border: var(--border-w) solid var(--border);
  border-radius: var(--radius-full); color: var(--text-secondary);
  font: inherit; font-size: var(--fs-xs); font-weight: 700; line-height: 1; cursor: pointer;
}
.tl-help__btn:hover { background: var(--bg-surface); color: var(--text-primary); }
.tl-help__btn[aria-expanded="true"] { background: var(--color-primary); color: var(--text-on-accent); border-color: var(--color-primary); }
/* 설명 본문 — 컴포넌트 «아래»에 붙는 띠. 불투명 배경(ADR-013)·좌측에 강조선. */
.tl-help__body {
  border: var(--border-w) solid var(--border); border-left: 3px solid var(--color-primary);
  border-radius: var(--radius-sm); background: var(--bg-subtle);
  padding: var(--sp-2) var(--sp-3); margin: 0 var(--sp-3) var(--sp-2);
  font-size: var(--fs-xs); color: var(--text-secondary); line-height: 1.6;
}
.tl-help__body[hidden] { display: none; }
.tl-help__body dl { display: grid; grid-template-columns: max-content 1fr; gap: 2px var(--sp-2); margin: 0; }
.tl-help__body dt { color: var(--text-primary); font-weight: 700; }
.tl-help__body dd { margin: 0; }
.tl-help__body p { margin: 0 0 var(--sp-1); }
.tl-help__body p:last-child { margin-bottom: 0; }

/* ── HTML `hidden` 속성은 «항상» 숨긴다 (2026-08-20 신설) ────────────────────
   함정: `hidden` 은 브라우저 기본 스타일의 display:none 인데, 작성자 CSS 의 display 가
   그것을 «덮어쓴다». 그래서 `.hm__top { display:flex }` 같은 규칙이 걸린 요소는
   hidden 을 줘도 그대로 보이고 자리를 차지한다.
   실제 사고: 허브에서 숨긴 머리줄이 80px 을 차지해 로그인 카드가 화면 중앙보다
   아래로 밀렸다(발주자 2026-08-20 「로그인 화면은 중간 중심적으로」·실측 오차 +84px).
   숨기라고 표시한 것이 안 숨겨지는 것은 «조용히 틀리는» 부류라 전역으로 못 박는다.
   !important 를 쓰는 이유: display 를 지정한 모든 컴포넌트 규칙을 이겨야 하기 때문이다.
   (요소를 보이게 하려면 hidden 을 «떼면» 된다 — 이 규칙과 다툴 일이 없다.) */
[hidden] { display: none !important; }
