/* Static skeleton stage styling, extended with the 04-1-3 dropdown panel. */

:root {
  /* Neutral scale */
  --color-bg: #ffffff;
  --color-text: #222222;
  --color-text-muted: #666666;
  --color-text-faint: #888888;
  --color-border: #e0e0e0;
  --color-border-strong: #333333;
  --color-surface-muted: #f7f7f7;

  /* Accent (tab nav active state, links) */
  --color-accent: #2b6cb0;

  /* COMFA legend/badge scale - blue (cool) to red (hot), 5 bins.
     Deliberately different from app/config.py's COLORMAP_NAME="viridis"
     (raster tile rendering only, see 13-5_작업지시서 1.2절). */
  --color-legend-0: #2b6cb0; /* Very Cool */
  --color-legend-1: #63b3ed; /* Cool */
  --color-legend-2: #f6e05e; /* Moderate */
  --color-legend-3: #ed8936; /* Warm */
  --color-legend-4: #c53030; /* Very Hot */

  /* 19-2: absolute COMFA index palette - distinct from --color-legend-*
     (that one stays the relative color-bar gradient, this one is for
     category badges/index-row/chart reference lines). Neutral is gray
     (#718096), NOT the bar's yellow (#f6e05e) - do not merge these two. */
  --color-comfa-index-0: #2b6cb0; /* Very Cold */
  --color-comfa-index-1: #63b3ed; /* Cold */
  --color-comfa-index-2: #718096; /* Neutral */
  --color-comfa-index-3: #ed8936; /* Hot */
  --color-comfa-index-4: #c53030; /* Very Hot */

  /* 21-5: VISTA 5단계 순서형 열부하 체감 팔레트 - js/legend.js의
     VISTA_LEVEL_COLORS와 반드시 동일하게 유지(JS는 인라인 style로 지도
     도형/범례 칩을 그리고, 여기 CSS 변수는 팝업 배지(.cell-card-badge)에만
     쓰인다 - 두 곳 다 같은 값을 손으로 맞춰야 함, COMFA_INDEX_* 중복 정의와
     동일한 이유). 값이 클수록 열부하가 크다(더움) - 사용자 확인(2026-08-12). */
  --color-vista-level-1: #2b83ba; /* 매우 시원함 */
  --color-vista-level-2: #abdda4; /* 시원함 */
  --color-vista-level-3: #ffffbf; /* 보통 */
  --color-vista-level-4: #fdae61; /* 더움 */
  --color-vista-level-5: #d7191c; /* 매우 더움 */
  --color-vista-missing: #9e9e9e;

  /* 21-6: S-DoT 온도 팔레트 - js/legend.js의 SDOT_TEMP_COLORS와 반드시
     동일하게 유지(위 --color-vista-level-* 주석과 동일한 이유 - JS는
     캔버스 도형/범례 칩을 인라인 style로 그리고, 이 CSS 변수는 팝업
     배지(.cell-card-badge-sdot-flag)의 테두리색에만 쓰인다). */
  --color-sdot-temp-0: #313695; /* 매우 낮음 */
  --color-sdot-temp-1: #74add1; /* 낮음 */
  --color-sdot-temp-2: #ffffbf; /* 보통 */
  --color-sdot-temp-3: #f46d43; /* 높음 */
  --color-sdot-temp-4: #a50026; /* 매우 높음 */
  --color-sdot-missing: #9e9e9e;
  --color-sdot-flag-border: #ffd700;

  /* 26-3: VISTA 히스토그램 선택 막대(bin) 강조 테두리 - 원래 js/vistaHistogram.js에
     "#000000" 리터럴로 하드코딩돼 있던 값(21-7)을 토큰화. 목업 5종(A 검정
     유지/B 강조 회색/C 액센트 블루/D 골드-플래그/E 화이트 헤일로) 검토 후
     사용자가 B(강조 회색) 방향 확정 - 이어서 다크모드 가독성 실측(WCAG
     상대휘도 대비, --color-border-strong의 다크값 #57626e가 VISTA L1
     파랑/L5 빨강 bin에서 대비 1.5 미만으로 약함을 확인)에 따라 회색 톤
     6종을 추가 검토했으나, 최종적으로 **라이트/다크 동일한 단일 톤**을
     사용자가 채택(2026-08-24, 사용자 확인: "회색계열로 적용하는 것이
     과하지 않고 적당히 식별됨") - 그래서 --color-border-strong처럼
     테마별로 분기하지 않고 이 변수 하나만 둔다. js/chartTheme.js의
     getChartThemeColors()가 읽어 selectedBorder 필드로 반환. */
  --color-histogram-selected-border: #6b7684;

  /* Typography scale */
  --font-size-xs: 11px;
  --font-size-sm: 12px;
  --font-size-base: 13px;
  --font-size-md: 14px;
  --font-size-lg: 16px;
  --font-size-xl: 22px;

  /* Spacing scale */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;

  --radius-sm: 4px;
  --radius-pill: 12px;

  /* 26-0: light-mode values for 2 literal-color pairs that need a distinct
     dark counterpart (2.2절) - the other 10 literal spots (2.3절) collapse
     into the 8 core variables above and need no new tokens of their own. */
  --color-warn-bg: #fffbea;      /* cell-card-badge-sdot-flag background */
  --color-warn-text: #7a5b00;    /* cell-card-badge-sdot-flag text */
  --color-accent-tint: #f4f8fc;       /* add-layer-btn background */
  --color-accent-tint-hover: #e8f1fa; /* add-layer-btn:hover background */
}

/* 26-0: dark theme overrides for the 8 UI-chrome variables above plus the 4
   light/dark-pair tokens just above. Values are Cool Slate
   (26_작업계획서_다크테마도입.md 2.1절, user-approved 2026-08-22).
   Data-semantic colors (--color-legend-N, --color-comfa-index-N,
   --color-vista-level-N, --color-sdot-temp-N, --color-sdot-flag-border) are
   deliberately NOT redefined here - they stay identical in both themes
   (계획서 2.1절/2.6절, 26-0_작업지시서 2.1/2.2절). The toggle that actually
   sets data-theme="dark" on <html> does not exist yet (26-1) - until then
   this block is inert (26-0_작업지시서 0절/1.7절).
   --color-histogram-selected-border (26-3, added later than the rest of
   this block) is likewise deliberately NOT redefined here - unlike
   --color-border-strong it was user-confirmed to use one shared tone in
   both themes, see its :root definition above for the full rationale. */
:root[data-theme="dark"] {
  --color-bg: #1f242b;
  --color-surface-muted: #272e37;
  --color-text: #dbe2ea;
  --color-text-muted: #94a1af;
  --color-text-faint: #6b7684;
  --color-border: #3b444f;
  --color-border-strong: #57626e;
  --color-accent: #5a9fe0;

  --color-warn-bg: #3d3320;       /* muted dark amber-brown, avoids glare (계획서 R2) */
  --color-warn-text: #f0c975;
  --color-accent-tint: #25405c;       /* dark tint of --color-accent (#5a9fe0) */
  --color-accent-tint-hover: #2d4d6e;
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  width: 100%;
  /* 26-0 fix (사용자 검증 2026-08-22, 항목 3/5): base text color was never
     tied to the variable system - only components with their own explicit
     "color: var(--color-text-*)" rule were theme-aware. Everything else
     (tab labels, .about-content text, .legend-block-title, the S-DoT
     결측/이상치 chip labels, popup card text with no own color rule) simply
     inherited the browser's default black, which stayed black regardless
     of data-theme. Setting it here once makes all of those inherit the
     theme color automatically. */
  color: var(--color-text);
}

#map {
  height: 100vh;
  width: 100vw;
}

/* Base panel look, shared by both the desktop side-panel and the mobile
   bottom-sheet layouts below. Position/size are set per breakpoint. */
.side-panel {
  z-index: 1000; /* above Leaflet's default pane z-indexes (max ~650) */
  box-sizing: border-box;
  background: var(--color-bg); /* 26-0: was literal #fff */
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.2); /* 26-0_작업지시서 1.3절: 다크에서도 무변경 */
  padding: var(--space-3) var(--space-4);
}

.side-panel .control-group {
  margin-bottom: var(--space-3);
}

.side-panel label {
  display: block;
  font-size: 13px;
  margin-bottom: 4px;
}

.side-panel select {
  width: 100%;
  box-sizing: border-box;
}

.side-panel .radio-inline {
  display: inline-block;
  font-size: 13px;
  font-weight: normal;
  margin-right: 12px;
  margin-bottom: 0;
}

.side-panel .radio-inline input {
  margin-right: 4px;
}

/* Breakpoint 768px is a provisional default (03-1 5.4). Adjust if the
   research lead decides otherwise (03-1 9-3). */
@media (max-width: 768px) {
  .side-panel {
    position: fixed;
    bottom: 0;
    width: 100%;
    max-height: 40vh; /* 18-5: 초기 기본값 - js/panelResize.js가 인라인
                          style.maxHeight로 덮어쓴다(위 769px 규칙의 width와
                          동일한 원칙). */
    overflow-y: auto;
  }
}

@media (min-width: 769px) {
  .side-panel {
    position: absolute;
    top: 0;
    right: 0;
    width: 300px; /* 18-5: 초기 기본값일 뿐 - js/panelResize.js가 인라인
                     style.width로 덮어쓴다(!important 미사용, 인라인 스타일이
                     이 미디어쿼리 규칙보다 자연히 우선하므로 불필요). */
    height: 100%;
    overflow-y: auto; /* 버그 수정(2026-08-03, 사용자 리포트 - "차트를 추가하면
                          화면의 상하 크기가 부족하여 차트가 화면 밖으로 나가게
                          됨"): 데스크톱 규칙에만 이 속성이 빠져 있었다 - 모바일
                          규칙(위 max-width:768px)은 18-3 이전부터 이미
                          overflow-y:auto였는데, 데스크톱은 폭만 리사이즈
                          가능하고 높이는 항상 100%(뷰포트 전체)로 고정이라
                          차트 카드가 여러 개 쌓이면 패널 안쪽 콘텐츠가 뷰포트
                          바닥 아래로 넘쳐도(overflow 기본값 visible) 스크롤할
                          방법이 없었다. 18-5의 리사이즈 핸들(.panel-resize-handle,
                          position:absolute; height:100%)은 패널 박스 자체의
                          높이를 기준으로 계산되므로 내부 콘텐츠가 스크롤되어도
                          핸들 위치/동작에는 영향 없음. */
  }
}

/* 26-0 후속 수정 (사용자 검증, 2026-08-22): attribution 바 "안 보임" 문제의
   진짜 원인은 색상이 아니라 위치였다 — 7.3절의 "글자/배경 대비 부족"
   진단은 틀렸다(이 규칙으로 정정). Leaflet은 attribution 컨트롤을 지도
   컨테이너의 `.leaflet-bottom.leaflet-right` 코너에 기본 배치하는데,
   데스크톱은 `.side-panel`이 바로 그 자리(오른쪽 전체 높이, z-index 1000)를
   덮고, 모바일은 `.side-panel`이 화면 하단 전체 폭(최대 40vh)을 덮어 두
   레이아웃 모두에서 이 코너가 패널에 완전히 가려져 있었다 - 다크/라이트
   테마와 무관하게 항상 그래왔던 구조적 문제. 아래에서 브레이크포인트별로
   비어 있는 다른 코너로 옮긴다: 데스크톱은 왼쪽 하단(오른쪽 패널을 피함),
   모바일은 오른쪽 상단(하단 시트를 피하고, 왼쪽 상단은 이미 Leaflet 줌
   컨트롤이 사용 중이라 겹치지 않는 쪽을 고름). 색상 규칙은 무수정. */
@media (min-width: 769px) {
  .leaflet-control-container .leaflet-bottom.leaflet-right {
    right: auto;
    left: 0;
  }
}
@media (max-width: 768px) {
  .leaflet-control-container .leaflet-bottom.leaflet-right {
    bottom: auto;
    top: 0;
  }
}

/* 18-5: #side-panel 드래그 리사이즈 핸들(js/panelResize.js). 히트 영역(8px)
   이 지도(#map, Leaflet)와 겹치지 않도록 z-index를 .side-panel(1000)보다,
   Leaflet 팬 최대치(약 650)보다도 높게 둔다 - 지도 팬/클릭이 핸들에 가로채
   이지 않고, 반대로 핸들 밖 드래그는 리사이즈에 반응하지 않는다(18-5
   1.3절). 기본은 데스크톱(패널 좌측 경계, 폭 조절) 기준이고, 아래 모바일
   미디어쿼리가 위치/커서만 패널 상단 경계(높이 조절)로 바꾼다. */
.panel-resize-handle {
  position: absolute;
  left: -4px;
  top: 0;
  width: 8px;
  height: 100%;
  cursor: ew-resize;
  z-index: 1001;
  touch-action: none; /* 드래그 중 브라우저 기본 스크롤/핀치줌 제스처와 충돌 방지 */
}

@media (max-width: 768px) {
  .panel-resize-handle {
    left: 0;
    top: -4px;
    width: 100%;
    height: 8px;
    cursor: ns-resize;
  }
}

.hidden {
  display: none;
}

/* 13-0: tab navigation skeleton. Colors/spacing are placeholders -
   final polish (palette, alignment) is deferred to 13-5. */
.tab-nav {
  display: flex;
  border-bottom: 1px solid var(--color-border);
  margin-bottom: 12px;
}

.tab-nav-item {
  flex: 1;
  padding: 8px 4px;
  border: none;
  background: none;
  font-size: var(--font-size-base);
  cursor: pointer;
  /* 26-0 후속 수정 (사용자 재검증 2026-08-22): <button>은 html/body의
     color: var(--color-text) 상속 규칙이 적용되지 않는다 - 브라우저가
     폼 컨트롤(button/select/input/textarea)에는 자체 기본 색을 매겨
     일반 상속 체인을 타지 않기 때문이다(label/p/span 등은 정상 상속됨,
     .tab-nav-item.active는 이미 자기만의 color: var(--color-accent)가
     있어 처음부터 문제없었음). 비활성 탭(Layers/Results/About) 버튼만
     검정으로 남았던 원인 - 명시로 상속을 대신한다. */
  color: var(--color-text);
}

.tab-nav-item.active {
  font-weight: bold;
  border-bottom: 2px solid var(--color-accent);
  color: var(--color-accent);
}

.tab-placeholder {
  font-size: var(--font-size-base);
  color: var(--color-text-muted);
}

/* 13-1-2: cell-click card/badge UI. Structural only - colors/spacing are
   placeholders (13번 계획서 1-1절 원칙), final polish is 13-5. */
.cell-card {
  font-size: var(--font-size-base);
  min-width: 220px;
}

.cell-card-section {
  margin-bottom: var(--space-2);
}

.cell-card-value {
  font-size: var(--font-size-xl);
  font-weight: bold;
  display: inline-block;
  margin-right: 6px;
}

.cell-card-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-muted);
  font-size: var(--font-size-sm);
  margin: 2px 4px 2px 0;
}

.cell-card-badge.legend-bin-0 { background: var(--color-legend-0); color: #fff; }
.cell-card-badge.legend-bin-1 { background: var(--color-legend-1); color: #000; } /* 26-0 후속 수정: 아래 참고 */
.cell-card-badge.legend-bin-2 { background: var(--color-legend-2); color: #000; }
.cell-card-badge.legend-bin-3 { background: var(--color-legend-3); color: #000; }
.cell-card-badge.legend-bin-4 { background: var(--color-legend-4); color: #fff; }
/* 26-0 후속 수정 (html/body에 color: var(--color-text) 추가한 부작용 발견,
   2026-08-22): 위 3곳(그리고 아래 comfa-index-1/3, vista-level-2/3/4)은
   원래 color를 지정하지 않고 브라우저 기본 검정에 기대고 있었다 - 배경이
   옅은/중간 톤(#63b3ed/#f6e05e/#ed8936 등)이라 항상 검정 글자가 맞았는데,
   html/body에 테마 변수를 걸자 다크 모드에서 이 배지들의 글자만 밝은색을
   상속해 옅은 배경 위에 옅은 글자(저대비)가 되는 회귀가 생겼다. 데이터
   의미색 배지는 배경뿐 아니라 글자색도 테마와 무관하게 고정이어야 하므로
   (같은 줄의 #fff 배지들과 동일한 원칙), 리터럴 #000/#fff로 명시해 상속을
   끊는다. */

/* 19-2: absolute-index badge palette (popup.js's renderComfaSectionHtml()).
   Kept separate from .legend-bin-N above (still present, unused - 2.2절/
   4.5절 no-delete-for-regression principle) - do not reuse those classes
   here, see the --color-comfa-index-* comment above for why. */
.cell-card-badge.comfa-index-0 { background: var(--color-comfa-index-0); color: #fff; }
.cell-card-badge.comfa-index-1 { background: var(--color-comfa-index-1); color: #000; } /* 26-0 후속 수정, 292행 근처 참고 */
.cell-card-badge.comfa-index-2 { background: var(--color-comfa-index-2); color: #fff; }
.cell-card-badge.comfa-index-3 { background: var(--color-comfa-index-3); color: #000; }
.cell-card-badge.comfa-index-4 { background: var(--color-comfa-index-4); color: #fff; }

/* 21-5 (3.6절/3.7절): popup.js's VISTA representative-value badge - display:
   block (not inline like the other .cell-card-badge uses) so it sits on its
   own line above the direction tabs, per "VISTA 섹션 최상단에 삽입". */
.cell-card-badge-vista-level-1,
.cell-card-badge-vista-level-2,
.cell-card-badge-vista-level-3,
.cell-card-badge-vista-level-4,
.cell-card-badge-vista-level-5,
.cell-card-badge-vista-missing {
  display: block;
  width: fit-content;
}
.cell-card-badge.cell-card-badge-vista-level-1 { background: var(--color-vista-level-1); color: #fff; }
.cell-card-badge.cell-card-badge-vista-level-2 { background: var(--color-vista-level-2); color: #000; } /* 26-0 후속 수정, 292행 근처 참고 */
.cell-card-badge.cell-card-badge-vista-level-3 { background: var(--color-vista-level-3); color: #000; }
.cell-card-badge.cell-card-badge-vista-level-4 { background: var(--color-vista-level-4); color: #000; }
.cell-card-badge.cell-card-badge-vista-level-5 { background: var(--color-vista-level-5); color: #fff; }
.cell-card-badge.cell-card-badge-vista-missing { background: var(--color-vista-missing); color: #fff; }

/* 21-6 (6절 결정사항 6): S-DoT 팝업의 이상치/도메인평균 제외 표시 배지 -
   결측(회색)/정상 값(무색 배경)과 다른 신호를 주기 위해 노란 계열로 별도
   지정. 지도 위 사각형 테두리(js/sdotLayer.js SDOT_FLAG_BORDER_COLOR)와
   같은 색 계열을 사용해 팝업과 지도 표기가 서로 대응되도록 한다. */
.cell-card-badge.cell-card-badge-sdot-flag {
  background: var(--color-warn-bg); /* 26-0: was literal #fffbea */
  color: var(--color-warn-text); /* 26-0: was literal #7a5b00 */
  border: 1px solid var(--color-sdot-flag-border); /* 데이터 의미색 - 테마 무관 고정, 26-0 무수정 */
}

.cell-card-meta {
  color: var(--color-text-muted);
  font-size: var(--font-size-xs);
}

.cell-card-divider {
  border-top: 1px solid var(--color-border);
  margin: 8px 0;
}

.cell-card-tabs {
  margin: 4px 0;
}

/* 사용자 검증(2026-08-25): Results 탭(js/results.js, .leaflet-container 밖의
   일반 side-panel)의 NSEW 탭이 지도 팝업(js/popup.js, .leaflet-container
   안)의 것과 다른 색으로 보임 - leaflet.css의 `.leaflet-container a {
   color: #0078A8; }`가 팝업 안에서만 적용되고 side-panel에는 적용되지
   않아, 팝업 쪽만 우연히 그 파란색이었을 뿐 명시적 규칙이 없었다. 여기서
   색을 명시하면 두 컨텍스트가 항상 같은 값을 갖는다. `a.` 접두사로
   `.leaflet-container a`(클래스+요소, 0,0,1,1)와 동일한 특정도를 만들고
   style.css가 leaflet.css보다 나중에 로드되므로(index.html) 캐스케이드
   순서로 이겨 팝업 안에서도 이 색이 적용된다(.leaflet-control-attribution a
   가 이미 쓰는 것과 동일한 기법, 위 1088행 근처). */
a.cell-card-tab {
  margin-right: 8px;
  text-decoration: none;
  color: var(--color-accent);
}

a.cell-card-tab.active {
  font-weight: bold;
  text-decoration: underline;
}

.cell-card-badges {
  margin: 4px 0;
}

a.cell-card-more-link {
  font-size: 12px;
  color: var(--color-accent);
}

/* 21-7 (3.1절/3.4절, C0): popup rationale block - same visual idea as
   .results-rationale below (muted background, italic) but sized down for the
   narrow Leaflet popup, plus a "더보기/접기" toggle link next to the text
   when it's been truncated (js/popup.js's renderVistaRationaleHtml()). */
.cell-card-rationale {
  font-size: 11px;
  font-style: italic;
  background: var(--color-surface-muted);
  padding: 4px 6px;
  margin: 4px 0;
  border-radius: 4px;
  white-space: normal;
  word-break: break-word;
}

a.cell-card-rationale-toggle {
  font-size: 11px;
  font-style: normal;
  white-space: nowrap;
  color: var(--color-accent);
}

/* 25-2 (2.9절): floating legend - 2 independent panels (js/legend.js
   buildLegendControl()), each holding a stack of collapsible
   .legend-block elements. Replaces the old single-container
   .map-legend/.map-legend-vista/.map-legend-sdot block (13-1-2/19-2/21-5/
   21-6) - ported from `_docs/25_목업_LayersUI.html` 225~293행 with no value
   changes (토큰 이름이 이미 --color-legend-N, --color-vista-level-N,
   --color-sdot-temp-N 과 일치, 1.6절).

   BUG NOTE (found in verification, fixed same day): an earlier revision of
   this very comment referenced the three token-name prefixes separated by
   forward slashes, each prefix ending in a bare hyphen-asterisk. Written
   that way, "asterisk" was immediately adjacent to the next "forward
   slash" - together those two characters are the CSS block-comment closer,
   so this comment closed itself early. Everything from there up to the
   next real closing sequence (which turned out to be this rule's own,
   right below) was silently swallowed by the browser as one broken
   selector, so the ENTIRE .map-legend-wrap rule was dropped - confirmed via
   devtools: the rule was simply absent from document.styleSheets. Visible
   symptom: the two legend panels rendered as plain stacked divs (no flex),
   i.e. one column instead of side by side. Lesson: never let an
   "asterisk" sit directly before a "forward slash" inside a CSS comment,
   even when only spelling out a list of token names - always separate
   them with a space, comma, or word. */
.map-legend-wrap {
  position: absolute;
  left: var(--space-3);
  bottom: var(--space-3);
  z-index: 5;
  display: flex;
  align-items: flex-end;
  gap: 10px;
}

.map-legend-panel {
  background: var(--color-bg); /* 26-0: was literal #fff */
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18); /* 26-0_작업지시서 1.3절: 다크에서도 무변경 */
  font-size: var(--font-size-xs);
  overflow: hidden;
}

.map-legend-vars { width: 178px; }
.map-legend-fixed { width: 156px; flex: 0 0 auto; }

.legend-col-label {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--color-text-faint);
  padding: 7px 10px 0 10px;
}

.legend-block + .legend-block { border-top: 1px solid var(--color-border); }
.legend-block.is-off { opacity: 0.5; }
.legend-block-header { display: flex; align-items: center; gap: 8px; padding: 7px 10px; cursor: pointer; user-select: none; }
.legend-block-header:hover { background: var(--color-surface-muted); }
.legend-dot { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; border: 1px solid rgba(0, 0, 0, 0.15); }
.legend-block-title { flex: 1 1 auto; min-width: 0; font-weight: 700; font-size: var(--font-size-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.legend-chevron { flex: 0 0 auto; transition: transform 0.15s ease; color: var(--color-text-faint); font-size: 10px; }
.legend-block.open .legend-chevron { transform: rotate(90deg); }

.legend-block-body { display: none; padding: 0 10px 10px 10px; }
.legend-block.open .legend-block-body { display: block; }

.legend-empty-state { font-size: var(--font-size-xs); color: var(--color-text-faint); padding: 8px 10px 10px 10px; }

/* COMFA류(연속값) 블록: 색상띠 + 우측 숫자 눈금만. 카테고리 텍스트 없음
   (구 절대색인 판정 문구는 2.2절 지시대로 삭제). */
.legend-vbar-wrap { display: flex; gap: 10px; }
.legend-vbar.continuous { display: flex; flex-direction: column; width: 16px; height: 118px; flex: 0 0 auto; border-radius: 3px; overflow: hidden; }
.legend-vbar.continuous span { flex: 1; }
.legend-vticks { position: relative; flex: 1 1 auto; height: 118px; }
.legend-vtick { position: absolute; left: 0; transform: translateY(-50%); font-size: 10px; font-weight: 600; color: var(--color-text); white-space: nowrap; }
.legend-vtick:first-child { transform: translateY(0); }
.legend-vtick:last-child { transform: translateY(-100%); }
.legend-unit-caption { font-size: 10px; color: var(--color-text-faint); margin-top: 8px; }

/* VISTA/S-DoT(이산값) 블록: 색상칩 + [수준(위)/값(아래)] 2줄 라벨. */
.legend-vertical { display: flex; align-items: stretch; gap: 8px; }
.legend-vbar.discrete { display: flex; flex-direction: column; width: 16px; flex: 0 0 auto; border-radius: 3px; overflow: hidden; }
.legend-vbar.discrete span { flex: 1; margin-bottom: 3px; border-radius: 2px; }
.legend-vbar.discrete span:last-child { margin-bottom: 0; }
.legend-vlabels { display: flex; flex-direction: column; flex: 1 1 auto; gap: 3px; }
.legend-vrow-multi { flex: 1; display: flex; flex-direction: column; justify-content: center; line-height: 1.25; }
.legend-vrow-level { font-size: 10px; font-weight: 600; color: var(--color-text); }
.legend-vrow-value { font-size: 9px; color: var(--color-text-faint); }

/* 25-2 2.5절: S-DoT 블록 전용 - 결측/이상치 칩 행(VISTA 블록엔 없음, 1.1절
   실측). .vista-legend-chip/.vista-legend-swatch는 구 단일-컨테이너 범례
   시절부터 쓰이던 칩 컴포넌트 CSS를 그대로 재사용(작업지시서 2.5절
   "유지되는 CSS") - 감싸는 flex 컨테이너만 새 이름(.legend-chip-row)으로
   추가. */
.legend-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  font-size: 11px;
  margin-top: 8px;
}

.vista-legend-chip {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}

.vista-legend-swatch {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 2px;
  margin-right: 4px;
  border: 1px solid rgba(0, 0, 0, 0.15);
}

/* 13-2: Results tab detail layout. Structural only - colors/spacing
   finalized in 13-5. */
.results-section {
  margin-bottom: var(--space-4);
}

.results-section h3 {
  font-size: 14px;
  margin: 0 0 8px 0;
}

/* 16-5, 18-3: every chart window's <canvas> (js/cellChart.js, rendered
   inside a .chart-card's containerId div) must not overflow the side
   panel's width. Was scoped to the single #cell-timeseries-chart id before
   18-3 introduced multiple chart windows - .chart-window-group covers all
   of them now, whatever their (dynamically generated) containerId. */
.chart-window-group canvas {
  max-width: 100%;
}

/* 18-4: Results 탭을 출력변수/입력변수 두 구획(.chart-area, data-group으로
   구분)으로 나눈다. 시각적 구분은 구분선(두 번째 이후 구획의 border-top)과
   제목(.chart-area-header h4)만으로 처리 - 새 색상 토큰을 만들지 않고 기존
   --color-border/--color-text-muted를 재사용한다(18-4_작업지시서 1.1절). */
.chart-area {
  margin-bottom: var(--space-4);
}

.chart-area + .chart-area {
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-3);
}

.chart-area-header {
  margin-bottom: var(--space-2);
}

.chart-area-header h4 {
  font-size: var(--font-size-sm);
  font-weight: bold;
  margin: 0;
  color: var(--color-text-muted);
}

/* 18-3: chart workspace (js/chartWorkspace.js) - the "+" button above a
   group's cards, and each card's [gear]/[x] header + settings popover.
   Reuses existing design tokens only - no new color variables. */
.chart-group-header {
  margin-bottom: var(--space-2);
}

.chart-add-btn {
  font-size: var(--font-size-xs);
  padding: 2px var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  color: var(--color-accent);
  cursor: pointer;
}

.chart-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  margin-bottom: var(--space-3);
  position: relative;
}

.chart-card-header {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-1);
  margin-bottom: var(--space-1);
}

.chart-card-settings-btn,
.chart-card-remove-btn {
  font-size: var(--font-size-sm);
  line-height: 1;
  padding: 2px 6px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  color: var(--color-text);
  cursor: pointer;
}

.chart-card-remove-btn:disabled {
  color: var(--color-text-faint);
  cursor: not-allowed;
}

/* Settings popover - variable checkboxes grouped by subgroup (<details>),
   with a search input on top (18-3 1.4절). Hidden unless .open (toggled by
   chartWorkspace.js's _onToggleChartSettings(), a plain class swap with no
   re-render of the chart itself). */
.chart-settings-popover {
  display: none;
  position: absolute;
  right: var(--space-2);
  top: 32px;
  z-index: 10;
  width: 260px;
  max-height: 320px;
  overflow-y: auto;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  padding: var(--space-2);
}

.chart-settings-popover.open {
  display: block;
}

.chart-settings-filter {
  width: 100%;
  box-sizing: border-box;
  font-size: var(--font-size-xs);
  padding: 2px var(--space-1);
  margin-bottom: var(--space-1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.chart-settings-list details {
  margin-bottom: var(--space-1);
}

.chart-settings-list summary {
  font-size: var(--font-size-xs);
  font-weight: bold;
  cursor: pointer;
}

.chart-settings-checkbox {
  display: block;
  font-size: var(--font-size-xs);
  font-weight: normal;
  padding: 2px 0 2px var(--space-2);
  white-space: normal;
}

/* 18-3 1.7절: 단위 3종 이상일 때 정규화(0~1) 안내 문구. */
.chart-normalized-note {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  margin: 0 0 var(--space-1) 0;
}

/* 18-2: per-chart controls bar (monthly/daily toggle + daily-mode date
   <select> + zoom reset button), drawn above the canvas by
   js/cellChart.js's _renderChartControls(). Reuses existing design tokens
   only - no new color variables (18-2_작업지시서 2.1절). */
/* 20-3 (사용자 항목 #3): the controls row was a single-line flex with no
   wrap and no per-item shrink guard, so switching to "일별" - which inserts
   a ~95px date <select> - pushed the row past the side panel's usable width
   (about 230-260px at the 260px minimum panel width) and flex shrank the
   buttons until their labels wrapped mid-word. Allowing the row to wrap and
   pinning each control to its natural width fixes it without touching
   cellChart.js's markup. */
.chart-controls {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
  flex-wrap: wrap;         /* 20-3: 폭이 모자라면 다음 줄로 넘긴다 */
  row-gap: var(--space-1);  /* 20-3: 줄바꿈 시 위아래 간격 */
}

.chart-mode-btn {
  font-size: var(--font-size-xs);
  padding: 2px var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  color: var(--color-text);
  cursor: pointer;
  white-space: nowrap;  /* 20-3: 버튼 안 글자는 절대 개행하지 않는다 */
  flex: 0 0 auto;        /* 20-3: flex가 버튼을 줄이지 못하게 한다 */
}

.chart-mode-btn.active {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-bg);
  font-weight: bold;
}

.chart-date-select {
  font-size: var(--font-size-xs);
  flex: 0 1 auto;   /* 20-3: 버튼과 달리 select는 필요하면 줄어들어도 된다 */
  min-width: 0;      /* 20-3: flex item의 기본 min-width:auto가 축소를 막는 것을 해제 */
  max-width: 100%;   /* 20-3: 한 줄을 통째로 차지하더라도 카드 밖으로 나가지 않게 */
}

/* 20-3 hotfix (사용자 후속 확인, 2026-08-08): "line 85 .side-panel select {
   width: 100% }" is a generic full-width rule meant for form-style dropdowns
   elsewhere in the panel. Its selector (.side-panel select, specificity
   0-1-1) beats plain .chart-date-select (0-1-0), so it silently won the
   `width` property and forced the date <select> to span the entire card
   width on its own line regardless of how short the option text was -
   shrinking "YYYY-MM-DD" to "MM-DD" above had no visible effect because the
   text length was never the actual constraint. Re-declaring `width` here
   with one extra scoping class raises specificity to 0-2-0, which wins, and
   lets the select size to its content again. */
.side-panel .chart-date-select {
  width: auto;
}

.chart-zoom-reset-btn {
  font-size: var(--font-size-xs);
  padding: 2px var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  color: var(--color-text);
  cursor: pointer;
  margin-left: auto;
  /* margin-left: auto 유지 - 한 줄에 다 들어갈 때는 여전히 우측 정렬되고,
     줄바꿈이 일어나면 자연히 새 줄의 왼쪽으로 간다 (20-3) */
  white-space: nowrap;  /* 20-3 */
  flex: 0 0 auto;        /* 20-3 */
}

/* 사용자 수정 요청(2026-08-03, 18-2 사소한 수정 #1): fixed-position hover
   readout row, filled in by cellChart.js's tooltip `external` callback
   instead of Chart.js's default floating tooltip box - reserving this row's
   height up front (min-height) keeps the canvas from shifting position when
   the readout text appears/disappears on hover. */
.chart-tooltip-readout {
  min-height: 1.6em;
  font-size: var(--font-size-xs);
  color: var(--color-text);
  margin-bottom: var(--space-1);
}

.chart-tooltip-title {
  font-weight: bold;
  color: var(--color-text-muted);
}

.results-direction {
  margin-bottom: 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--color-border);
}

.results-direction h4 {
  font-size: 12px;
  margin: 0 0 4px 0;
  color: var(--color-text);
}

.results-rationale {
  font-size: 12px;
  font-style: italic;
  background: var(--color-surface-muted);
  padding: 6px 8px;
  margin: 4px 0;
  border-radius: 4px;
}

/* 13-4: About tab. Structural only - typography finalized in 13-5. */
.about-content h3 {
  margin-top: 0;
}

.about-content p {
  font-size: 13px;
  line-height: 1.5;
}

.about-disclaimer {
  color: var(--color-text-faint);
  font-size: var(--font-size-xs);
}

/* 13-3: Layers tab cards. Structural only - colors/spacing in 13-5. */
.layers-card {
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: var(--space-2) var(--space-3);
  margin-top: var(--space-3);
}

.layers-card h4 {
  font-size: 12px;
  margin: 0 0 6px 0;
}

/* 사용자 요청(2026-07-16, 3차): 변수 52개가 표 하나에 그대로 나열되어
   side-panel 밖으로 벗어나는 문제 - 표를 감싸는 독립 스크롤 컨테이너.
   NESW 탭/rationale은 이 컨테이너 밖(js/results.js)이라 항상 보이고,
   변수 목록만 이 안에서 휠 스크롤된다. */
.results-vars-scroll {
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  margin: var(--space-1) 0;
}

/* 21-7 (3.4절/1.5절, C3): domain histogram canvas at the bottom of the VISTA
   card (js/vistaHistogram.js). Fixed height via CSS + Chart.js
   responsive:true/maintainAspectRatio:false (same combination used
   elsewhere would need a JS-managed width instead - this is the simpler
   option for a single static chart that isn't part of the multi-window
   chart workspace, 작업지시서 3.3절). */
.vista-histogram-container {
  position: relative;
  height: 180px;
  margin: var(--space-2) 0;
}

/* 사용자 요청(2026-07-16): Results 탭 VISTA 방향별 변수 표. 컬럼이 52개라
   폰트를 카드 기본(13px)보다 작게 줄임 - 구조/여백 최종 폴리싱은 13-5. */
.results-vars-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 10px;
  margin-bottom: 6px;
}

.results-vars-table td {
  padding: 2px 4px;
  border-bottom: 1px solid var(--color-border); /* 26-0: was literal #f0f0f0, 1.2절 신규 발견 */
  vertical-align: top;
}

.results-var-name {
  color: var(--color-text); /* 26-0: was literal #444 */
  width: 55%;
  word-break: break-word;
}

.results-var-value {
  text-align: right;
  word-break: break-word;
}

/* 19-3: variable dropdown optgroup spacing (native browser rendering,
   layout-only tweak) */
#variable-select optgroup { font-style: normal; font-weight: 600; }

/* 19-3 (R5): top-of-screen toast, fixed position, fades via opacity */
#toast-container {
  position: fixed;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.8);
  color: #fff;
  padding: 8px 16px;
  border-radius: 4px;
  font-size: 13px;
  z-index: 1000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
  max-width: 80vw;
  text-align: center;
}

/* 25-1 (2.13절): ported verbatim from `_docs/25_목업_LayersUI.html`'s
   <style> (127~223행) - the mockup's :root tokens (--color-accent,
   --color-border, --color-surface-muted, --color-text-muted,
   --color-text-faint, --font-size-*, --space-*, --radius-sm, --radius-pill)
   already match this file's :root (3~74행) by name/value, so no token
   renaming was needed. */

.section-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: var(--space-4) 0 var(--space-2) 0;
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
}
.section-heading h3 { font-size: var(--font-size-md); margin: 0; }
.section-heading .section-note { font-size: 10px; color: var(--color-text-faint); }

.pinned-layers { margin-bottom: var(--space-2); }

/* ---------- Layer card: 드래그 핸들 + 라벨 없는 체크박스/슬라이더를
   한 줄에 압축 (목업 3차 개편, 25 계획서 v2 확정안) ---------- */
.layer-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  margin-bottom: var(--space-2);
  background: var(--color-bg); /* 26-0: was literal #fff */
  transition: opacity 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}
.layer-card.is-off { background: var(--color-surface-muted); opacity: 0.62; }
.layer-card.drag-over { border-color: var(--color-accent); }
.layer-card.dragging { opacity: 0.35; }

.layer-row { display: flex; align-items: center; gap: 6px; }
.layer-drag-handle {
  flex: 0 0 auto;
  width: 14px;
  text-align: center;
  cursor: grab;
  color: var(--color-text-faint);
  font-size: 13px;
  line-height: 1;
  user-select: none;
}
.layer-drag-handle.disabled { cursor: not-allowed; opacity: 0.3; }
.layer-swatch { flex: 0 0 auto; width: 10px; height: 10px; border-radius: 3px; border: 1px solid rgba(0, 0, 0, 0.15); }
.layer-row input[type="checkbox"] { flex: 0 0 auto; margin: 0; }
.layer-row-title {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--font-size-base);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.layer-row input[type="range"] { flex: 0 0 64px; accent-color: var(--color-accent); }
.layer-pin-tag {
  flex: 0 0 auto;
  font-size: 9px;
  color: var(--color-text-faint);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  padding: 1px 6px;
  white-space: nowrap;
}
.layer-remove-btn {
  flex: 0 0 auto;
  font-size: var(--font-size-xs);
  line-height: 1;
  padding: 3px 6px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  color: var(--color-text-muted);
  cursor: pointer;
}
.layer-remove-btn:hover { color: #c53030; border-color: #c53030; }

.layer-meta {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  margin: 3px 0 0 34px; /* handle(14+6) + swatch(10+6) 만큼 들여쓰기 */
}

.add-layer-btn {
  width: 100%;
  padding: 7px var(--space-2);
  border: 1px dashed var(--color-accent);
  border-radius: var(--radius-sm);
  background: var(--color-accent-tint); /* 26-0: was literal #f4f8fc */
  color: var(--color-accent);
  font-size: var(--font-size-base);
  font-weight: 600;
  cursor: pointer;
  margin: var(--space-1) 0 var(--space-3) 0;
}
.add-layer-btn:hover { background: var(--color-accent-tint-hover); } /* 26-0: was literal #e8f1fa */

.layer-add-picker {
  display: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  margin-bottom: var(--space-3);
  background: var(--color-surface-muted);
}
.layer-add-picker.open { display: block; }
.layer-add-picker .control-group { margin-bottom: var(--space-2); }
.layer-add-picker .control-group label { display: block; font-size: var(--font-size-xs); color: var(--color-text-muted); margin-bottom: 2px; }
.layer-add-confirm-row { display: flex; gap: var(--space-2); margin-top: var(--space-1); }
.layer-add-confirm-row button { flex: 1; padding: 5px; font-size: var(--font-size-xs); border-radius: var(--radius-sm); cursor: pointer; }
.btn-confirm { border: 1px solid var(--color-accent); background: var(--color-accent); color: #fff; } /* #fff = accent 버튼 위 흰 글자, 라이트/다크 공통으로 유지 */
.btn-cancel { border: 1px solid var(--color-border); background: var(--color-bg); color: var(--color-text-muted); } /* 26-0: background was literal #fff, 1.2절 신규 발견 */

.empty-hint { font-size: var(--font-size-xs); color: var(--color-text-faint); text-align: center; padding: var(--space-2) 0; }
#toast-container.toast-visible { opacity: 1; }

/* 26-0 (2.4절): Leaflet CDN 기본 컨트롤(줌 버튼/attribution) 다크 오버라이드.
   leaflet.css(index.html 7행, unpkg CDN) 자체는 건드리지 않고, 더 높은
   특정도의 [data-theme="dark"] 스코프로 배경/텍스트/테두리만 덮어쓴다
   (26-0_작업지시서 1.6절 실측: 줌 버튼은 .leaflet-bar a 공통 클래스,
   attribution은 .leaflet-control-attribution 단일 클래스). data-theme
   속성을 실제로 부여하는 토글은 26-1에서 추가되므로 이 블록은 26-0
   단독으로는 아직 발동하지 않는다(0절). */
[data-theme="dark"] .leaflet-bar a {
  background-color: var(--color-surface-muted);
  color: var(--color-text);
  border-bottom-color: var(--color-border);
}
[data-theme="dark"] .leaflet-bar a:hover {
  background-color: var(--color-border);
}
[data-theme="dark"] .leaflet-bar a.leaflet-disabled {
  background-color: var(--color-surface-muted);
  color: var(--color-text-faint);
}
[data-theme="dark"] .leaflet-control-attribution {
  background: rgba(31, 36, 43, 0.7); /* --color-bg(#1f242b)의 반투명 버전 -
                                          rgba()는 CSS 변수에서 채널값만
                                          뽑아 쓸 수 없어 리터럴로 고정 */
  color: var(--color-text-muted);
}
[data-theme="dark"] .leaflet-control-attribution a {
  color: var(--color-accent);
}

/* 26-0 후속 수정 (사용자 검증 2026-08-22, 항목 11): attribution 텍스트가
   다크뿐 아니라 라이트에서도 식별 불가로 보고됨 - 원본 코드에는 이 클래스에
   대한 커스텀 규칙이 전혀 없어(26-0 이전 grep 결과 0건) leaflet.css 기본값
   그대로였다. 이 규칙은 테마 스코프 없이(두 테마 공통) 배경/글자 대비를
   높여 가독성을 확보한다 - 26-0의 원래 범위(다크 전용) 밖이지만 같은 파일,
   같은 조사 영역에서 발견되어 함께 수정한다. */
.leaflet-control-attribution {
  background: rgba(255, 255, 255, 0.85);
  color: var(--color-text);
  font-size: var(--font-size-xs);
}
.leaflet-control-attribution a {
  color: var(--color-accent);
}

/* 26-0 후속 수정 (사용자 검증 2026-08-22, 항목 5): 지도 클릭 팝업
   (js/popup.js가 렌더링하는 .cell-card, Leaflet의 .leaflet-popup-*
   컨테이너 안에 삽입됨)이 26-0_작업지시서 1.6절 실측에서 누락됐다 - 그
   실측은 "Leaflet 기본 컨트롤"만 줌 버튼/attribution으로 좁혀 봤고, 같은
   CDN leaflet.css가 그리는 .leaflet-popup-content-wrapper/-tip은 살펴보지
   않았다(26_작업계획서 0절은 "팝업"을 화면 UI 전반의 대상으로 명시하고
   있어 원래 범위 안이었어야 함 - 이번에 바로잡는다). 팝업 배경을 다크로
   바꾸면 위 html/body의 새 기본 텍스트색과 맞물려 COMFA/S-DoT 배지처럼
   개별 background를 가진 요소만이 아니라 카드 전체(값/구분선/탭/근거
   문구 등 순수 텍스트)가 함께 다크 대응된다 - "VISTA 배지만 적용된 것처럼
   보인다"는 보고의 원인이었다(배지는 원래도 항상 색이 있었고, 나머지
   카드 배경/텍스트가 여태 다크에 대응하지 않아 상대적으로 그렇게 보인
   것). */
[data-theme="dark"] .leaflet-popup-content-wrapper {
  background: var(--color-bg);
  color: var(--color-text);
}
[data-theme="dark"] .leaflet-popup-content {
  color: var(--color-text);
}
[data-theme="dark"] .leaflet-popup-tip {
  background: var(--color-bg);
}
[data-theme="dark"] .leaflet-popup-close-button {
  color: var(--color-text-muted); /* leaflet.css의 단일 클래스 규칙(0,1,0)보다
    이 선택자(속성+클래스, 0,2,0)가 특정도가 높아 !important 불필요 */
}

/* 26-1: manual theme toggle button (Leaflet custom control, topleft
   corner, same as the default zoom control - js/theme.js's
   buildThemeToggleControl()). Circular, ~30px to match Leaflet's own
   30x30 .leaflet-bar a buttons, but deliberately NOT using the
   .leaflet-bar grouping class (계획서 2.3절: standalone circle, not
   joined to the zoom button bar). References the same --color-* vars 26-0
   already flips under :root[data-theme="dark"] - no separate
   [data-theme="dark"] block needed here (1.8절 선례, .btn-cancel 등과
   동일 패턴). */
.theme-toggle-btn {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--color-surface-muted);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); /* leaflet.css .leaflet-bar 그림자와 톤 통일, 리터럴 유지(1.3절과 같은 이유 - rgba 안에서 변수 채널 추출 불가) */
}
.theme-toggle-btn:hover {
  background: var(--color-border);
}
.language-toggle-btn {
  min-width: 30px;
  height: 30px;
  padding: 0 6px;
  border-radius: 15px; /* 텍스트 2글자 수용 - 완전 원형(theme-toggle-btn의 50%)은 비좁음, 27-1_작업지시서 1.5절 */
  background: var(--color-surface-muted);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); /* theme-toggle-btn과 톤 통일 (1.5절과 동일 이유로 리터럴 유지) */
}
.language-toggle-btn:hover {
  background: var(--color-border);
}
