/* ============================================================================
   화성시 버스 수요·공급 미스매칭 대시보드 — 공통 스타일
   ----------------------------------------------------------------------------
   담당: 프론트엔드 디자인
   - 색/여백/타이포는 전부 :root 의 CSS 변수로 정의되어 있습니다.
     변수 값만 바꾸면 전체 화면 톤이 한 번에 바뀝니다. (다크모드 대응 포함)
   - 컴포넌트 클래스는 목적별로 구획을 나눠 두었습니다. 아래 목차 참고.
   목차
     1. 디자인 토큰 (라이트/다크)
     2. 기본 리셋 · 타이포
     3. 레이아웃 (topnav / wrap / grid / card)
     4. KPI 타일
     5. 지도
     6. 우선순위 리스트
     7. 차트 공통 (산점도 · 정류장 프로파일 · 비교차트)
     8. 시뮬레이터 · 시나리오 빌더
     9. 버튼 · 폼
    10. 모달 (AI 보고서)
    11. 표 · 툴팁 · 푸터
    12. 반응형
    ── 아래는 나중에 덧붙인 구획입니다 (기존 규칙은 건드리지 않았습니다) ──
    13. 한 줄 브리핑
    14. 지도 기호 범례
    15. 권역별 요약표
    16. 용어 도움말 버튼
    17. 읍면동 경계선
    18. 추천 배치안
    19. 데이터 품질 · 가정값 표시
   ========================================================================= */

/* ---------------------------------------------------------------- 1. 토큰 */
:root{
  color-scheme:light;
  /* 화성특례시 공식 CI 색을 그대로 씁니다(BI 원본 픽셀에서 추출).
     --brand 파랑 #0054A6 · --brand-2 주황 #F36F21.
     이 둘은 크롬(상단 레일·내비·주요 버튼)에서만 씁니다. 지도·차트의 색은
     값을 나르는 지표라 따로 두며, 크롬과 캔버스가 위치로 갈려 섞이지 않습니다. */
  --brand:#0054A6; --brand-ink:#003C77; --brand-soft:#E7EFF8; --brand-line:#C7DAEE;
  /* 공식 주황은 흰 바탕에서 2.95:1 이라 글자로는 못 씁니다(AA 4.5:1 미달, 큰
     글자 3:1 도 미달). 채움·마커에만 쓰고, 글자가 필요하면 --brand-2-ink
     (5.2:1)를 씁니다. 색만으로 뜻을 나르지 않는다는 원칙과도 맞습니다. */
  --brand-2:#F36F21; --brand-2-ink:#C2410C; --brand-2-soft:#FDEEE3;
  --bg:#EEF2F7; --card:#FFFFFF; --line:#DCE4EE; --grid:#E3E9F1; --axis:#B7C3D4;
  /* --ink3 은 배경 #EEF2F7 위에서 4.6:1 (WCAG AA) */
  --ink:#12233A; --ink2:#41546E; --ink3:#5D6E85;
  --sel:#0054A6; --selbg:rgba(0,84,166,.09);
  --water:#DCE6F1; --land:#F4F1EA; --coast:#9AA9BC;
  /* 미스매칭 MI: 발산형(파랑 ← 중립 → 빨강). 중간값은 반드시 무채색. */
  --mi0:#1c5cab; --mi1:#5598e7; --mi2:#9ec5f4; --mi3:#e9e7e0; --mi4:#f3a68f; --mi5:#e2604f; --mi6:#a62b28;
  /* 수요: 단일 색상 순차형(파랑) */
  --sb0:#dcebfb; --sb1:#9ec5f4; --sb2:#5598e7; --sb3:#2a78d6; --sb4:#184f95;
  /* 공급: 단일 색상 순차형(주황) */
  --so0:#fbe4d4; --so1:#f5b98f; --so2:#ec8c50; --so3:#d95926; --so4:#9c3a12;
  /* 유동인구: 단일 색상 순차형(자주). 예전에는 수요와 --sb 를 같이 썼는데, 두 층을
     번갈아 켜면 색이 똑같아 무엇을 보고 있는지 알 수 없었습니다. 자주를 고른 이유는
     파랑(277°)·주황(52°) 어느 쪽과도 떨어져 있고, 초록과 달리 '좋다/나쁘다' 의미를
     끌고 오지 않기 때문입니다 — 유동인구는 크기지 좋고 나쁨이 아닙니다. */
  --fl0:#f3e7f5; --fl1:#d5badb; --fl2:#b78ec1; --fl3:#9963a8; --fl4:#7a388f;
  /* 범주형 2색(승차/하차) — 파랑·주황: 색각 이상에서도 분리되는 조합 */
  --c-board:#2a78d6; --c-alight:#eb6834;
  --tag-new-bg:#f9dfda; --tag-new-fg:#8f2422; --tag-add-bg:#fce4d5; --tag-add-fg:#93400f;
  --tag-drt-bg:#e5e1f7; --tag-drt-fg:#3b3090; --tag-eff-bg:#ddeafc; --tag-eff-fg:#1c5cab;
  --tag-ok-bg:#e8e7e1; --tag-ok-fg:#52514e;
  --track:#f0e0dc;
  /* 상태색: 좋음/나쁨 — 반드시 부호·라벨과 함께 사용(색 단독 금지) */
  --good:#0f7b4f; --bad:#b2182b;
  --warn:#a86612;
  --shadow:0 4px 14px rgba(0,0,0,.14);
  --shadow-lg:0 18px 48px rgba(0,0,0,.20);
  /* 고정폭은 **한 벌만** 씁니다. 예전에는 .mono 가 "Cascadia Code" 로 시작하고
     나머지 21곳은 Consolas 로 시작해, Windows 에서 서로 다른 고정폭 서체가
     한 화면에 동시에 떴습니다.
     쓰는 자리도 좁힙니다 — body 에 이미 font-variant-numeric:tabular-nums 가
     걸려 있어 숫자 자릿수는 Pretendard 만으로 맞습니다. 고정폭을 덧씌우면
     정렬이 나아지는 게 아니라 본문과 다른 x-height·회색도의 숫자가 박힐 뿐이고,
     공식 CI 가 둥근 끝 모노라인 글자라 각진 고정폭과는 방향도 반대입니다.
     게다가 Consolas 에는 한글 글리프가 없어서, 한글과 숫자가 섞인 줄
     (배치 목록·시나리오 메타·푸터)은 이미 한 줄 안에서 서체가 갈리고 있었습니다.
     그래서 지금은 **격자 ID 같은 식별 코드에만** 남깁니다. */
  --font-mono:ui-monospace,"Cascadia Mono",Consolas,"D2Coding",monospace;
}
@media (prefers-color-scheme:dark){
  :root:where(:not([data-theme="light"])){
    color-scheme:dark;
    --bg:#0F141A; --card:#171E27; --line:rgba(255,255,255,.11); --grid:#242D3A; --axis:#33404F;
    --ink:#F2F6FB; --ink2:#B7C4D4; --ink3:#8B9AAC;
    /* 다크에서는 공식 파랑(#0054A6)이 배경에 묻혀 글자로 못 씁니다(대비 부족).
       같은 계열을 밝게 올린 값을 쓰고, 주황은 다크에서도 6.3:1 이라 그대로 둡니다. */
    --brand:#5AA6EA; --brand-ink:#8CC4F5; --brand-soft:#16283C; --brand-line:#27405C;
    --brand-2:#F36F21; --brand-2-ink:#FFA066; --brand-2-soft:#2C1B10;
    --sel:#5AA6EA; --selbg:rgba(90,166,234,.14);
    --water:#10151c; --land:#232a33; --coast:#3e4a58;
    /* ── 다크의 지표 색은 '심각할수록 진하게' 로 읽히게 만듭니다 ──────────────
       예전에는 심각한 쪽이 파스텔로 빠졌습니다(--sb4 #a4c9f5 는 L* 80 에 채도 26,
       --mi6 #f0928a 는 L* 70 에 채도 40). 옅어질수록 심하다는 뜻이 되어, 보는
       사람의 직관과 반대였습니다.
       이제 단계가 올라갈수록 **채도가 오릅니다**(8 → 52~64). L* 도 22 → 52 로
       오르긴 하지만 폭이 작아, 눈에 먼저 들어오는 변화는 '짙어짐' 입니다.

       ⚠️ 심각한 쪽을 라이트처럼 '더 어둡게' 는 못 합니다. 바탕(--land #232a33)이
          이미 L* 16 이라, 그보다 어두우면 가장 중요한 칸이 배경에 잠깁니다.
          그래서 어둡기가 아니라 짙기로 세기를 나릅니다. 최상단 대비는 3.46:1,
          최하단은 1.18:1 로 빈 곳은 뒤로 물러납니다. 이웃 단계 색차는 ΔE 14 이상. */
    --mi0:#357dd1; --mi1:#3164a5; --mi2:#334c74; --mi3:#333538; --mi4:#753b32; --mi5:#a54438; --mi6:#d64e40;
    --sb0:#303540; --sb1:#364663; --sb2:#395887; --sb3:#376aad; --sb4:#2c7ed5;
    --so0:#3f322c; --so1:#603d2c; --so2:#80492d; --so3:#a2542c; --so4:#c45f2a;
    --fl0:#3a323c; --fl1:#543e5a; --fl2:#6e4a78; --fl3:#8a5599; --fl4:#a661ba;
    --c-board:#3987e5; --c-alight:#d95926;
    --tag-new-bg:rgba(230,103,103,.16); --tag-new-fg:#f0a29b; --tag-add-bg:rgba(217,89,38,.16); --tag-add-fg:#f0a86f;
    --tag-drt-bg:rgba(144,133,233,.16); --tag-drt-fg:#b3aaf2; --tag-eff-bg:rgba(57,135,229,.16); --tag-eff-fg:#86b6ef;
    --tag-ok-bg:rgba(255,255,255,.08); --tag-ok-fg:#c3c2b7;
    --track:#43302c;
    --good:#4ec98d; --bad:#f0928a; --warn:#e0a950;
    --shadow:0 4px 14px rgba(0,0,0,.5);
    --shadow-lg:0 18px 48px rgba(0,0,0,.6);
  }
}
:root[data-theme="dark"]{
  color-scheme:dark;
  --bg:#0F141A; --card:#171E27; --line:rgba(255,255,255,.11); --grid:#242D3A; --axis:#33404F;
  --ink:#F2F6FB; --ink2:#B7C4D4; --ink3:#8B9AAC;
  --brand:#5AA6EA; --brand-ink:#8CC4F5; --brand-soft:#16283C; --brand-line:#27405C;
  --brand-2:#F36F21; --brand-2-ink:#FFA066; --brand-2-soft:#2C1B10;
  --sel:#5AA6EA; --selbg:rgba(90,166,234,.14);
  --water:#10151c; --land:#232a33; --coast:#3e4a58;
  /* 위 @media 블록과 **같은 값이어야 합니다** — 다르면 테마를 손으로 고른 사람과
     시스템 설정을 따르는 사람이 서로 다른 색의 지도를 봅니다. 근거는 위 주석. */
  --mi0:#357dd1; --mi1:#3164a5; --mi2:#334c74; --mi3:#333538; --mi4:#753b32; --mi5:#a54438; --mi6:#d64e40;
  --sb0:#303540; --sb1:#364663; --sb2:#395887; --sb3:#376aad; --sb4:#2c7ed5;
  --so0:#3f322c; --so1:#603d2c; --so2:#80492d; --so3:#a2542c; --so4:#c45f2a;
  --fl0:#3a323c; --fl1:#543e5a; --fl2:#6e4a78; --fl3:#8a5599; --fl4:#a661ba;
  --c-board:#3987e5; --c-alight:#d95926;
  --tag-new-bg:rgba(230,103,103,.16); --tag-new-fg:#f0a29b; --tag-add-bg:rgba(217,89,38,.16); --tag-add-fg:#f0a86f;
  --tag-drt-bg:rgba(144,133,233,.16); --tag-drt-fg:#b3aaf2; --tag-eff-bg:rgba(57,135,229,.16); --tag-eff-fg:#86b6ef;
  --tag-ok-bg:rgba(255,255,255,.08); --tag-ok-fg:#c3c2b7;
  --track:#43302c;
  --good:#4ec98d; --bad:#f0928a; --warn:#e0a950;
  --shadow:0 4px 14px rgba(0,0,0,.5);
  --shadow-lg:0 18px 48px rgba(0,0,0,.6);
}
:root[data-theme="light"]{
  color-scheme:light;
  --brand:#0054A6; --brand-ink:#003C77; --brand-soft:#E7EFF8; --brand-line:#C7DAEE;
  --brand-2:#F36F21; --brand-2-ink:#C2410C; --brand-2-soft:#FDEEE3;
  --bg:#EEF2F7; --card:#FFFFFF; --line:#DCE4EE; --grid:#E3E9F1; --axis:#B7C3D4;
  --ink:#12233A; --ink2:#41546E; --ink3:#5D6E85;
  --sel:#0054A6; --selbg:rgba(0,84,166,.09);
  --water:#DCE6F1; --land:#F4F1EA; --coast:#9AA9BC;
  --mi0:#1c5cab; --mi1:#5598e7; --mi2:#9ec5f4; --mi3:#e9e7e0; --mi4:#f3a68f; --mi5:#e2604f; --mi6:#a62b28;
  --sb0:#dcebfb; --sb1:#9ec5f4; --sb2:#5598e7; --sb3:#2a78d6; --sb4:#184f95;
  --so0:#fbe4d4; --so1:#f5b98f; --so2:#ec8c50; --so3:#d95926; --so4:#9c3a12;
  --fl0:#f3e7f5; --fl1:#d5badb; --fl2:#b78ec1; --fl3:#9963a8; --fl4:#7a388f;
  --c-board:#2a78d6; --c-alight:#eb6834;
  --tag-new-bg:#f9dfda; --tag-new-fg:#8f2422; --tag-add-bg:#fce4d5; --tag-add-fg:#93400f;
  --tag-drt-bg:#e5e1f7; --tag-drt-fg:#3b3090; --tag-eff-bg:#ddeafc; --tag-eff-fg:#1c5cab;
  --tag-ok-bg:#e8e7e1; --tag-ok-fg:#52514e;
  --track:#f0e0dc;
  --good:#0f7b4f; --bad:#b2182b; --warn:#a86612;
  --shadow:0 4px 14px rgba(0,0,0,.14);
  --shadow-lg:0 18px 48px rgba(0,0,0,.20);
}

/* --------------------------------------------------- 2. 리셋 · 타이포 */
*{box-sizing:border-box}
html,body{margin:0;padding:0}
/* Pretendard 는 여기서 부르지 않습니다 — index.html·simulation.html 의
   <link> 가 부릅니다. 예전에는 이 자리에 @import 가 있었는데, @import 는
   @charset·@layer 를 빼면 **모든 스타일 규칙보다 앞서야** 합니다. 바로 위
   :root 토큰 블록들과 *{box-sizing} 뒤에 있어서 파서가 통째로 버렸고,
   그래서 자간(h1 -.03em)·가변 굵기(650)를 Pretendard 기준으로 맞춰 둔
   설정이 전부 맑은 고딕에서 그려지고 있었습니다 — 이 파일이 피하려던
   바로 그 상태입니다. HTML 로 옮기면 순서 함정이 없고, app.css 를 다 받기
   전에 폰트 요청이 시작되므로 대기도 한 단계 줄어듭니다.
   (망 차단·오프라인이면 조용히 시스템 스택으로 떨어지는 것은 그대로입니다) */
body{background:var(--bg);color:var(--ink);
  font-family:"Pretendard Variable",Pretendard,"Segoe UI Variable","Segoe UI",
    "Apple SD Gothic Neo","Malgun Gothic",system-ui,sans-serif;
  font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;
  /* 표·지표가 많은 화면이라 숫자 폭을 고정합니다 — 값이 바뀔 때 자릿수가 흔들리지 않습니다 */
  font-variant-numeric:tabular-nums}
.mono{font-family:var(--font-mono)}
a{color:inherit}
button{font:inherit;color:inherit;background:none;border:none;cursor:pointer;padding:0}
:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:4px}
svg text{font-family:inherit}

/* ── 브라우저가 기본값으로 그리는 표면들 ────────────────────────────────
   선택 영역·캐럿·스크롤바는 손대지 않으면 어느 디자인에도 속하지 않는
   OS 기본색으로 남습니다. 화면이 조립된 게 아니라 만들어졌다는 가장 값싼
   신호라 여기서 한 번에 브랜드 색으로 맞춥니다. */
::selection{background:var(--brand);color:#fff}
:root{caret-color:var(--brand);accent-color:var(--brand);
  scrollbar-color:var(--axis) transparent;scrollbar-width:thin}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--axis);border-radius:999px;   /* 칩과 같은 값 — 둘 다 완전 원형인데 값만 달랐습니다 */
  border:3px solid var(--bg);background-clip:content-box}
::-webkit-scrollbar-thumb:hover{background:var(--ink3);background-clip:content-box}
.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}

/* ------------------------------------------------------ 3. 레이아웃 */
/* 1340px 은 산문 가독 폭 기준인데, 이 화면의 구성물은 지도·산점도·권역표처럼
   정보 밀도가 높은 것들뿐입니다. 1920px 에서 가로 32%(616px)가 놀고 정작 주인공인
   격자 지도는 1440px 일 때와 똑같은 863px 에 머물렀습니다. 산문 문단은 .sub 의
   max-width:70ch 로 따로 묶여 있으므로 래퍼만 넓힙니다.
   ※ 차트 max-width(#scatter 560 / 나머지 760)와 반드시 함께여야 합니다 —
     안 그러면 산점도가 1.24배로 확대돼 축 글자가 카드 제목보다 커집니다. */
.wrap{max-width:1680px;margin:0 auto;padding:14px 18px 30px}

/* 상단 전역 내비게이션 — 관공서 문서의 인장(印章) 자리.
   흰 바탕에 공식 CI 를 얹고, 그 아래 브랜드 파랑 레일 3px 로 화면 전체를
   시가 발행한 문서처럼 묶습니다. 배경을 흐리지 않는 이유: 이 화면 밑에는
   지도가 깔리는데 backdrop-filter 로 비치면 격자 색이 왜곡돼 보입니다. */
.topnav{position:sticky;top:0;z-index:30;background:var(--card);
  border-bottom:3px solid var(--brand)}

/* ── 레일 두 색 ────────────────────────────────────────────────────────────
   공식 CI 는 파랑과 주황 두 색으로 짜인 워드마크인데 레일은 파랑 단색이라, CI 안의
   색 관계가 화면으로 이어지지 않았습니다. 레일 **왼쪽 끝에서 CI 오른쪽 끝까지**만
   주황으로 덮으면 주황이 인장을 받치고 파랑이 나머지 문서를 끌고 가는 관계가 되어,
   길이에 이유가 생깁니다(임의의 지점에서 색이 바뀌면 사고로 보입니다).

   "주황을 글자에 쓰지 않는다"(흰 바탕 2.95:1)는 규칙은 그대로입니다 — 여기서도
   선이지 글자가 아닙니다. 비텍스트 3:1 기준도 적용되지 않습니다. 이 선은 정보를
   나르지 않는 장식이고, 정보를 나르는 '지금 여기' 표식은 여전히 활성 탭 밑줄
   하나뿐입니다.

   끊기는 지점은 눈대중이 아니라 계산입니다. .tn-in 이 .wrap 과 같은 1680px
   중앙정렬이므로, 화면 왼쪽 끝에서 CI 오른쪽 끝까지는
       바깥 여백  max(0px, (100% − 1680px) / 2)
     + 좌측 패딩  --rail-pad (기본 18px · 560px 이하 12px)
     + 햄버거 36px + 간격 16px = 52px
     + CI 폭     --rail-ci  (height 30px × 512/151 ≈ 102px)
     + 다크 보정  --rail-plate (다크에서 CI 뒤에 까는 흰 판의 좌우 패딩 9px×2)
   입니다. 폭이 바뀌어도 CI 끝을 따라갑니다.

   border-bottom 은 한 변에 한 색만 가질 수 있어 ::after 로 덮습니다. 테두리
   자체는 그대로 두므로 상단 레일 높이는 1px 도 바뀌지 않습니다 — 스티키 기준선과
   그 아래 모든 것의 위치가 지금과 같습니다. */
.topnav{--rail-pad:18px;--rail-ci:102px;--rail-plate:0px}
.topnav::after{content:"";position:absolute;left:0;bottom:-3px;height:3px;
  width:calc(max(0px,(100% - 1680px) / 2) + var(--rail-pad) + 52px
             + var(--rail-ci) + var(--rail-plate));
  background:var(--brand-2);pointer-events:none}
.topnav .tn-in{max-width:1680px;   /* .wrap 과 같은 값이어야 좌측 정렬이 맞습니다 */margin:0 auto;padding:10px 18px;display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.brand{display:flex;align-items:center;gap:11px;min-width:0}
/* 공식 CI. 원본 BI 에서 가로형 시그니처를 투명 배경으로 뽑아 둔 것입니다. */
.brand .ci{height:30px;width:auto;display:block;flex:0 0 auto}
/* CI 옆 서비스명. CI 가 기관을 말하므로 여기는 이 도구의 이름만 답니다. */
.brand .svc{font-size:13px;font-weight:700;letter-spacing:-.02em;color:var(--ink);
  white-space:nowrap;padding-left:11px;border-left:1px solid var(--line);line-height:1.2}
.tn-sp{flex:1}
.tn-act{display:flex;gap:6px;align-items:center}

/* 햄버거(작대기 셋) — 막대 맨 왼쪽. CI 는 그 옆으로 한 칸 밀립니다. */
.tn-menu{display:inline-flex;align-items:center;justify-content:center;
  width:36px;height:36px;flex:0 0 auto;border:1px solid var(--line);border-radius:9px;
  background:none;color:var(--ink2);cursor:pointer;transition:background .12s,color .12s}
.tn-menu:hover{background:var(--brand-soft);color:var(--brand-ink)}
.topnav.menu-open .tn-menu{background:var(--brand);border-color:var(--brand);color:#fff}

/* 서랍 메뉴 — 왼쪽에서 스르륵. 상단 내비(30)보다 위, 모달(80)·툴팁(70)보다 아래.
   닫혀 있을 때 visibility 를 함께 끊어야 Tab 키가 안 보이는 링크로 새지 않습니다. */
.tn-scrim{position:fixed;inset:0;z-index:60;background:rgba(15,23,42,.38);
  opacity:0;visibility:hidden;transition:opacity .28s ease,visibility 0s .28s}
.tn-drawer{position:fixed;top:0;left:0;bottom:0;z-index:61;width:248px;
  display:flex;flex-direction:column;gap:3px;padding:16px 12px;
  background:var(--card);border-right:3px solid var(--brand);
  box-shadow:0 0 28px rgba(15,23,42,.18);
  transform:translateX(-102%);   /* 그림자까지 완전히 화면 밖으로 */
  visibility:hidden;transition:transform .28s ease,visibility 0s .28s}
.topnav.menu-open .tn-scrim{opacity:1;visibility:visible;transition:opacity .28s ease}
.topnav.menu-open .tn-drawer{transform:none;visibility:visible;transition:transform .28s ease}
@media (prefers-reduced-motion:reduce){.tn-scrim,.tn-drawer{transition:none}}
.tn-drawer a,.tn-drawer .tn-item{display:flex;align-items:center;gap:8px;
  font-size:13px;font-weight:600;padding:10px 14px;border-radius:9px;
  color:var(--ink2);text-decoration:none;background:none;border:0;cursor:pointer;
  text-align:left;transition:background .12s,color .12s}
.tn-drawer a:hover,.tn-drawer .tn-item:hover{background:var(--brand-soft);color:var(--brand-ink)}
.tn-drawer a[aria-current="page"]{background:var(--brand);color:#fff}
/* 보고서 버튼은 이동 링크와 성격이 달라 괘선 한 줄로 가르고 놓습니다. */
.tn-drawer .tn-cut{height:1px;background:var(--line);margin:8px 2px}
/* ⚠️ .btn 을 셀렉터에 넣어 두세요. 이 버튼은 .btn.sm 이기도 한데, 그 규칙이 이
   블록보다 **뒤(§버튼)** 에 있습니다. `.tn-drawer [data-report-open]` 는 클래스 1 +
   속성 1 로 .btn.sm 과 특이도가 같아(0,2,0) 순서에서 밀렸고, 실제로 여기 적은 값이
   아니라 .btn.sm 의 5px 11px / 11px 로 그려졌습니다. .btn 을 더해 (0,3,0) 으로
   올리면 순서와 무관하게 이깁니다. */
/* ── 발행 버튼 ──────────────────────────────────────────────────────────────
   서랍의 마지막 항목은 이동 링크가 아니라 문서를 만들어 내보내는 동작입니다.
   '멋있게' 를 그라디언트·글로우로 풀지 않습니다 — 이 화면의 세계에 없는 재료이고,
   공무원이 쓰는 도구에서 그 표면은 곧바로 가벼워 보입니다. 대신 관공서 서식의
   발행 버튼이 실제로 갖는 것을 줍니다.
     ① 두 줄 — 무엇을 만드는지(13px/700)와 무엇으로 나오는지(10px). 부제는 제목
        **아래** 입니다. 위에 얹으면 DESIGN.md 가 금지한 eyebrow 가 됩니다.
     ② 윗변 1px 하이라이트 — 빛을 받는 면의 경계지 그림자가 아니라, '그림자는
        모달·툴팁에만' 이라는 규칙과 부딪히지 않습니다. 색을 흰색 반투명으로 두어
        라이트(진한 파랑 바탕)·다크(밝은 파랑 바탕) 어느 쪽에서도 자연스럽습니다.
     ③ 아이콘 18px — 서랍의 다른 아이콘(16px)보다 한 단계 커서 무게가 실립니다.
   ⚠️ 셀렉터에 .btn 을 넣어 두세요. §버튼의 .btn 규칙이 이 블록보다 **뒤**에 있어
      `.tn-drawer .tn-issue`(0,2,0)로는 padding·white-space 를 못 이깁니다.
      .btn 을 더해 (0,3,0) 으로 올리면 순서와 무관하게 이깁니다. */
.tn-drawer .btn.tn-issue{width:100%;justify-content:flex-start;align-items:center;
  gap:10px;padding:11px 13px;border-radius:9px;text-align:left;white-space:normal;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.18)}
/* 같은 함정 하나 더 — §버스의 `.btn .ic{width:14px}` 가 (0,2,0) 이고 이 블록보다
   뒤에 있어, `.tn-issue .ic`(0,2,0)로 쓰면 순서에서 밀려 14px 로 그려집니다.
   svg 에 width="18" 을 찍어 둬도 CSS 가 presentation attribute 를 이깁니다.
   .btn 을 붙여 (0,3,0) 으로 올립니다. */
.btn.tn-issue .ic{width:18px;height:18px;margin-top:0}
.tn-issue .ti-tx{display:flex;flex-direction:column;gap:1px;min-width:0}
.tn-issue .ti-tx b{font-size:13px;font-weight:700;letter-spacing:-.01em;line-height:1.3}
/* 부제는 색을 따로 주지 않고 물려받은 글자색을 흐립니다 — 라이트는 흰 글자,
   다크는 어두운 글자(§22 보정)라 고정 색을 쓰면 한쪽에서 안 보입니다. */
.tn-issue .ti-tx span{font-size:10px;font-weight:500;line-height:1.4;opacity:.74;
  word-break:keep-all}

/* ── 우측 하단 AI 런처 (코리요) ────────────────────────────────────
   다른 사이트의 상담 챗봇과 같은 자리·같은 동작입니다. 처음 오는 사람이 배우지
   않고도 "여기 물어볼 데가 있다"를 알아보는 자리입니다.

   z-index 50 — 상단바(30) 위, 서랍 스크림(60) 아래입니다. 모달(80)보다도
   아래라 실제 모달(보고서·안내)을 열면 그 뒤로 들어갑니다. 도움 패널(.chat-panel,
   55)은 모달이 아니라 대시보드와 함께 쓰는 것이라 그 사이에 둡니다. */
.chat-launch{position:fixed;right:20px;bottom:20px;z-index:50;
  display:flex;align-items:center;gap:9px;pointer-events:none}
.chat-launch>*{pointer-events:auto}
@media (prefers-reduced-motion:reduce){ .cl-btn,.cl-bubble{transition:none} .cl-btn:hover{transform:none} }
/* 바탕이 테마와 무관하게 흰색입니다. 코리요 아트가 흰 배경 기준으로 그려져
   있어(짙은 갈색 외곽선 + 노란 몸통) 어두운 판 위에 놓으면 외곽선이 묻힙니다.
   DESIGN.md 가 공식 CI 에 대해 정해 둔 것과 같은 처리입니다 — 다크에서 로고 뒤에
   흰 판을 깔되, 그림 자체는 고치지 않습니다(마스코트 변형이 되므로). */
.cl-btn{width:72px;height:72px;flex:0 0 auto;border-radius:50%;cursor:pointer;
  border:2px solid var(--brand);background:#fff;padding:0;overflow:hidden;
  display:grid;place-items:center;
  /* 이 화면에서 그림자를 쓰는 것은 실제로 떠 있어야 하는 것뿐입니다(DESIGN.md).
     본문 위를 덮고 스크롤을 따라다니므로 여기가 그 경우입니다. */
  box-shadow:0 4px 14px rgba(15,23,42,.22);
  transition:transform .14s,box-shadow .14s}
.cl-btn img{width:100%;height:100%;object-fit:cover;display:block}
.cl-btn:hover{transform:translateY(-2px);box-shadow:0 7px 18px rgba(15,23,42,.28)}
.cl-btn:active{transform:translateY(0);box-shadow:0 2px 8px rgba(15,23,42,.22)}
.cl-btn:focus-visible{outline:3px solid var(--brand);outline-offset:2px}
/* 이미지를 못 받았을 때만 보이는 대체 아이콘 */
.cl-fallback{display:none;color:var(--brand)}
.cl-btn.noimg .cl-fallback{display:grid}
/* 말풍선 — 버튼 왼쪽, 상시 표시입니다(패널이 열리면 잠깐 숨습니다 — chat.js 의
   showBubble). 꼬리를 오른쪽에 달아 버튼이 하는 말로 읽히게 합니다. 이제
   "닫기"가 없습니다 — 늘 떠 있는 것이 요구사항이라, 개별로 지울 수 있게 두면
   그 뜻과 어긋납니다. */
.cl-bubble{position:relative;display:flex;align-items:center;
  background:var(--card);border:1px solid var(--line);border-radius:999px;
  padding:9px 15px;font-size:13px;font-weight:600;color:var(--ink);
  white-space:nowrap;box-shadow:0 3px 10px rgba(15,23,42,.14);cursor:pointer;
  opacity:0;visibility:hidden;transform:translateX(6px);
  transition:opacity .22s,transform .22s,visibility 0s .22s}
.cl-bubble.show{opacity:1;visibility:visible;transform:none;transition:opacity .22s,transform .22s}
.cl-bubble::after{content:'';position:absolute;right:-5px;top:50%;width:9px;height:9px;
  margin-top:-4.5px;transform:rotate(45deg);background:var(--card);
  border-right:1px solid var(--line);border-top:1px solid var(--line)}
/* 좁은 화면에서는 말풍선이 자리를 많이 먹어 지도를 가립니다. 버튼만 남깁니다. */
@media (max-width:640px){
  .chat-launch{right:14px;bottom:14px}
  .cl-bubble{display:none}
  .cl-btn{width:62px;height:62px}
}

/* ── AI 도움 패널 — 모달이 아니라 도킹된 창 ─────────────────────────
   우측 하단, 런처 바로 위에 뜹니다. 배경을 덮는 것이 없어(.veil 없음) 열려
   있는 동안에도 지도를 클릭하거나 시간대 탭을 누르는 등 대시보드를 그대로
   씁니다 — 요구사항 원문 그대로 "챗봇 화면을 띄어놓고도 동시에 다른 화면
   조작". 그래서 진짜 모달(§보고서·§안내)과 달리 여기는 스크림도, 포커스
   가두기도 두지 않습니다. */
.chat-panel{position:fixed;right:20px;bottom:100px;z-index:55;
  width:min(380px,calc(100vw - 40px));
  max-height:min(560px,calc(100vh - 132px));
  display:flex;flex-direction:column;
  background:var(--card);border:1px solid var(--line);border-radius:16px;
  box-shadow:0 12px 34px rgba(15,23,42,.26);   /* 대시보드 위로 실제로 뜨는 창입니다 */
  transform-origin:bottom right;
  opacity:0;visibility:hidden;transform:translateY(10px) scale(.97);
  transition:opacity .16s,transform .16s,visibility 0s .16s;
  pointer-events:none}
.chat-panel.open{opacity:1;visibility:visible;transform:none;
  transition:opacity .16s,transform .16s;pointer-events:auto}
@media (prefers-reduced-motion:reduce){ .chat-panel{transition:none} }
@media (max-width:640px){
  .chat-panel{right:12px;left:12px;bottom:84px;width:auto;
    max-height:min(72vh,540px)}
}
.cp-head{display:flex;align-items:center;gap:9px;padding:12px 12px 12px 14px;
  border-bottom:1px solid var(--line);flex:0 0 auto}
.cp-head img{width:30px;height:30px;border-radius:50%;flex:0 0 auto;
  background:#fff;border:1px solid var(--line)}
.cp-head b{flex:1;min-width:0;font-size:13px;font-weight:700;color:var(--ink)}
.cp-x{flex:0 0 auto;display:grid;place-items:center;width:26px;height:26px;padding:0;
  border:0;border-radius:50%;background:none;color:var(--ink3);cursor:pointer}
.cp-x:hover{background:var(--brand-soft);color:var(--brand-ink)}
.cp-sub{padding:9px 14px 0;font-size:11px;color:var(--ink3);line-height:1.6;flex:0 0 auto}

/* 토스트가 우측 하단에서 도움 패널과 같은 구석을 씁니다. 패널이 열려 있는
   동안은 왼쪽으로 비켜서 겹치지 않게 합니다(body.chat-open — chat.js 가 겁니다). */
body.chat-open .toasts{right:auto;left:16px}

/* ── AI 채팅 (도움 패널 · 보고서 모달 공용) ────────────────────────
   두 모드가 같은 생김새를 씁니다. 말풍선은 좌우로 갈라 누가 한 말인지 위치로
   구분하고, 색은 크롬 계열(브랜드)로 둡니다 — 지도·차트의 지표 색이 여기로
   넘어오면 "이 색이 값을 뜻하나?" 하는 오해가 생깁니다. */
.cm{display:flex;flex-direction:column;min-height:0}
/* hidden 속성을 되살립니다. 브라우저 기본 규칙 [hidden]{display:none} 은 작성자
   스타일시트의 .cm{display:flex} 에 밀립니다(출처 우선순위: 작성자 > 기본).
   그래서 report.js 가 초안 전까지 hidden 으로 둔 채팅 칸이 그대로 보이고 있었고,
   테두리 선 한 줄이 푸터 위에 떠 있었습니다. */
.cm[hidden]{display:none}
.cm-log{flex:1;min-height:120px;max-height:340px;overflow-y:auto;
  display:flex;flex-direction:column;gap:8px;padding:12px 2px}
.cm-b{max-width:78%;font-size:12.5px;line-height:1.7;border-radius:12px;padding:8px 12px;
  word-break:keep-all}
/* 모델 응답은 textContent 로 넣으므로 줄바꿈은 여기서 살립니다 */
.cm-b .cm-t{white-space:pre-wrap}
.cm-b.me{align-self:flex-end;background:var(--brand);color:#fff;border-bottom-right-radius:4px}
.cm-b.ai{align-self:flex-start;background:var(--bg);color:var(--ink);
  border:1px solid var(--line);border-bottom-left-radius:4px}
/* 화면을 옮겼을 때만 붙는 꼬리표 — 무엇이 바뀌었는지 알려 줍니다 */
.cm-b .cm-n{margin-top:5px;font-size:10.5px;color:var(--brand-ink)}
.cm-bar{display:flex;gap:7px;align-items:center;padding:10px 0 2px;
  border-top:1px solid var(--line)}
.cm-in{flex:1;min-width:0;font:inherit;font-size:12.5px;padding:8px 11px;
  border:1px solid var(--line);border-radius:9px;background:var(--card);color:var(--ink)}
.cm-in:focus{outline:2px solid var(--brand);outline-offset:1px;border-color:var(--brand)}
.cm-in:disabled{opacity:.6}
/* 보내기 버튼 높이를 입력칸에 맞춘다 — .btn.sm 은 다른 화면에서도 쓰는 공용
   버튼이라 거기는 그대로 두고(패딩 5px 대 입력칸 8px 라 버튼이 눈에 띄게 낮았다)
   이 자리에서만 입력칸 높이에 맞춰 늘린다. */
.cm-bar .btn{align-self:stretch}
/* 도움 패널 안에 얹힐 때 — 패널 자체가 높이를 제한하므로(.chat-panel 의
   max-height) 로그는 남는 공간을 그대로 채웁니다. 고정 340px 을 두면 패널이
   낮은 화면(모바일)에서 로그가 입력줄보다 아래로 밀려 잘립니다. */
.chat-panel .cm{padding:0 14px 12px;flex:1;min-height:0}
.chat-panel .cm-log{max-height:none}
/* 보고서 모달 안에 얹힐 때 — 본문과 푸터 사이라 좌우 여백을 본문에 맞춥니다.

   ⚠️ flex:0 0 auto 를 반드시 둡니다. .sheet 는 max-height:88vh 로 묶인 세로 flex
      기둥이고, 그 안의 초안 본문(.body)은 화면보다 깁니다. 이 블록을 줄어들 수
      있게 두면(flex-shrink 기본값 1 + .cm 의 min-height:0) 본문과 줄어듦을 비율로
      나눠 갖습니다. 그런데 .cm-log 는 min-height:120px 에서 더는 안 줄어들어,
      남은 몫이 전부 입력줄로 갑니다 — 입력줄이 블록 밖으로 밀려나 푸터에 덮입니다.
      실측(1440×900): 채팅 블록 124px 인데 로그 120 + 입력줄 50 이 필요해
      입력줄이 785~835px 에 놓이고 푸터가 788px 부터라, 입력창이 DOM 에는 있는데
      화면에 없고 그 자리 클릭은 푸터가 받았습니다. */
.cm-report{flex:0 0 auto;padding:0 18px 4px;border-top:1px solid var(--line)}
/* 로그 높이를 고정합니다 — 대화가 길어져도 로그 안에서만 스크롤되므로, 메시지가
   하나 늘 때마다 문서 미리보기가 다시 접히는 일이 없습니다. */
.cm-report .cm-log{min-height:150px;max-height:150px}
/* 낮은 화면에서는 한 단 줄여 본문에 자리를 넘깁니다 */
@media (max-height:700px){ .cm-report .cm-log{min-height:96px;max-height:96px} }

/* 사용 안내 모달 — 화면 축소판 그림 + 번호 설명 */
/* .sheet 를 함께 붙이는 이유는 바로 위와 같습니다 — .sheet{width:min(920px,100%)} 가
   §모달에 있어 그냥 .guide-sheet 로는 특이도가 같아(0,1,0) 밀립니다. */
.sheet.guide-sheet{width:min(720px,100%)}
.ghero{display:block;width:100%;height:auto;border:1px solid var(--line);border-radius:10px;
  background:var(--card)}
.ghero text{font-family:inherit}
.gsteps{display:grid;grid-template-columns:repeat(2,1fr);gap:12px 18px;margin-top:14px}
.gstep b{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--ink)}
.gstep p{margin:5px 0 0 25px;font-size:11px;line-height:1.65;color:var(--ink2);word-break:keep-all}
.gnum{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:18px;height:18px;border-radius:50%;background:var(--brand);color:#fff;
  font-size:11px;font-weight:700}
.gfoot{margin:14px 0 0;padding-top:10px;border-top:1px dashed var(--line);
  font-size:11px;color:var(--ink3);text-align:center}
@media (max-width:640px){.gsteps{grid-template-columns:1fr}}

header.top{display:flex;justify-content:space-between;align-items:flex-end;gap:18px;flex-wrap:wrap;margin:18px 0 14px}
/* KPI 값(.kpi .vl)이 clamp(19px,1.9vw,26px) 로 뷰포트를 따라 줄어드는데 제목이
   23px 고정이면, 폭이 1,210px 아래로 내려가는 순간 화면의 주인공인 지표값이
   제목보다 작아집니다(1,020px 에서 KPI 19 대 제목 23). 제목도 같은 방식으로
   줄여 위계가 뒤집히지 않게 합니다. 데스크톱(≥1,590px)에서는 23px 그대로라
   지금 보이는 화면은 바뀌지 않습니다. */
h1{font-size:clamp(18px,1.45vw,23px);font-weight:800;margin:0;letter-spacing:-.03em;text-wrap:balance;line-height:1.25}
.sub{margin:7px 0 0;color:var(--ink2);font-size:13px;max-width:70ch;line-height:1.65}

/* 시간대 탭 — 이 화면의 주장이 "시간대를 바꾸면 답이 달라진다" 이므로
   탭은 부가 컨트롤이 아니라 문서의 목차처럼 굵게 섭니다.
   고른 탭만 공식 파랑으로 채우고, 주황은 그 아래 3px 밑줄 한 줄로 '지금 여기'
   만 표시합니다 — 주황은 대비가 2.95:1 이라 글자로는 쓸 수 없기 때문입니다. */
.periods{display:flex;gap:2px;border:1px solid var(--line);border-radius:11px;padding:3px;background:var(--card)}
.pbtn{position:relative;border-radius:8px;padding:7px 15px 8px;text-align:center;color:var(--ink2);
  transition:background .12s,color .12s}
.pbtn b{display:block;font-size:13px;font-weight:700;line-height:1.25;color:inherit;letter-spacing:-.01em}
.pbtn span{display:block;font-size:10px;letter-spacing:.02em;margin-top:1px;opacity:.8}
.pbtn:hover{background:var(--brand-soft);color:var(--brand-ink)}
.pbtn.on,.pbtn.on:hover{background:var(--brand);color:#fff}
.pbtn.on::after{content:"";position:absolute;left:15px;right:15px;bottom:3px;height:2.5px;
  border-radius:2px;background:var(--brand-2)}

/* 한글은 브라우저 기본이 음절 단위 줄바꿈이라 '노선' '수치는' 이 아무 데서나
   쪼개집니다. 산문 요소에만 어절 유지를 겁니다(표의 긴 mono 토큰은 제외). */
h1,.sub,.brief,.empty,.pcard p,.pcard li,.rec-hint,.simhint,.sidefoot{
  word-break:keep-all;overflow-wrap:break-word}
.grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:12px;align-items:stretch}
/* 카드 = 문서의 한 조(條). 띄우지 않고 괘선으로 가릅니다 — 관공서 문서는
   그림자로 떠 있지 않고 선으로 나뉩니다. 그림자는 실제로 떠야 하는 것
   (모달·툴팁)에만 남겨 두어 그 뜻을 지킵니다. */
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;min-width:0}
.span4{grid-column:span 4}.span5{grid-column:span 5}.span6{grid-column:span 6}
.span7{grid-column:span 7}.span8{grid-column:span 8}.span12{grid-column:span 12}
/* 지도(8칸)는 우선순위 목록(4칸)보다 짧아 아래에 180px 쯤 빈 자리가 남았습니다.
   노선 경유 순서 카드를 그 자리에 넣습니다 — 지도에서 격자를 클릭하면 바로
   아래에서 그 격자의 버스 노선을 읽는 흐름이 됩니다.
   우선순위는 두 행에 걸치게 해서 오른쪽 열이 통째로 이어지게 둡니다.

   ⚠️ 행 배치는 .dashgrid(= index.html 의 .grid)에만 겁니다. simulation.html 은
   같은 app.css 를 쓰지만 .routecard 가 없어서, 규칙이 전역이면 사이드카드가
   두 행을 차지한 채 2행 9~12열이 비고 '배치 수단별 사업비' 카드가 한 행
   아래로 밀려 데스크톱 폭에서 구멍이 남습니다. */
.mapcard{grid-column:span 8}
.routecard{grid-column:span 8;display:flex;flex-direction:column;min-height:0}
.sidecard{grid-column:span 4}
.dashgrid .mapcard{grid-row:1}
.dashgrid .routecard{grid-row:2}
.dashgrid .sidecard{grid-row:1 / span 2}
/* 시뮬레이션에는 .routecard 가 없어 사이드카드가 지도 높이(1440px 에서 945px)만큼
   늘어나는데 내용은 448px 에서 끝납니다 — 500px 가까이가 빈 상자로 남습니다.
   한때 align-self:start 로 내용 높이만 차지하게 뒀는데, 그러면 반대로 지도보다
   짧을 때 지도 카드 옆(수단별 소요액 위)에 빈 캔버스가 남습니다 — 오른쪽 열이
   먼저 끝나 버리는 모양입니다. 제보로 사이드카드가 늘 지도와 같은 높이로
   끝나야 합니다 — 내용이 짧으면 그 안이 비어도 되고(오른쪽 열의 경계가 지도와
   나란해지는 게 우선), 내용이 지도보다 길면 카드 밖으로 안 넘치고 안에서만
   스크롤해야 합니다.

   CSS 만으로는 못 만듭니다. align-self:stretch(기본값) + min-height:0 +
   overflow:auto 조합을 실측해 봤는데, min-height:0 은 '이미 정해진 높이보다
   작아지는 것'만 허용할 뿐 auto 트랙 자체의 크기 계산(=애초에 얼마나 커야
   하는지)에서 사이드카드의 내용을 빼주지는 않습니다. 배치를 여러 건 추가하니
   행이 그대로 982 → 1198px 로 늘어났습니다(카드 안이 아니라 행 전체가 늘어나
   '수단별 소요액'을 밀어냄 — 스크롤은 한 번도 안 걸렸습니다).
   그래서 높이는 simulation.js 의 syncSideHeight() 가 지도 카드의 실측 높이를
   재서 인라인 style.height 로 직접 입힙니다(ResizeObserver 로 지도 카드
   크기 변화 — 저장 시나리오 증가·창 크기 변경 — 를 계속 따라갑니다). 여기
   overflow-y:auto 는 그렇게 입혀진 고정 높이 안에서 넘치는 내용을 스크롤시키는
   역할만 합니다. 881px 미만(app.css @media)처럼 한 줄로 쌓이는 폭에서는
   syncSideHeight 가 스스로 높이를 비웁니다(맞출 지도가 옆에 없으므로).

   한때 position:sticky 도 같이 걸어 "지도를 훑는 동안 패널이 화면에 남는다"를
   노렸는데, 실측해 보니 CSS Grid 에서 sticky 의 기준 상자는 그 아이템의
   행(row)이 아니라 그리드 컨테이너 전체입니다 — .simgrid 는 3행(지도+사이드바 ·
   비교+비용 · 정책요약)인데 사이드바가 1행을 지나 3행 끝까지 화면에 눌어붙어
   있었습니다. AI 추천에 배치를 여러 건 더하면(항목당 ~28px) 내용이 지도보다
   커지는데, 그러면 붙어 있는 카드가 스크롤할수록 아래 '수단별 소요액'을 최대
   886px 까지 뒤덮었습니다(실측, AI 추천 10건 기준). 행 하나만을 위한 sticky 를
   CSS Grid 만으로 만들려면 그 행을 별도 컨테이너로 떼어내야 해서 배보다
   배꼽이 큽니다 — 다시 시도하지 않습니다. */
.simgrid .sidecard{overflow-y:auto}
.sc-card{grid-column:span 5}
.st-card{grid-column:span 7}
/* 카드 제목줄. 제목 왼쪽에 브랜드 파랑 막대를 세워 조(條)의 머리를 표시합니다 —
   관공서 문서에서 항목 앞에 붙는 표식과 같은 역할이고, 아이콘 없이도 각 카드가
   무엇의 시작인지 눈이 바로 잡습니다. 아래 1px 괘선으로 제목과 내용을 가릅니다. */
.chead{display:flex;justify-content:space-between;align-items:baseline;gap:10px;
  padding:13px 16px 11px;flex-wrap:wrap;border-bottom:1px solid var(--line);margin-bottom:12px}
.chead h2{font-size:15px;font-weight:700;margin:0;letter-spacing:-.02em;
  display:flex;align-items:center;gap:9px}
.chead h2::before{content:"";width:3px;height:14px;border-radius:2px;
  background:var(--brand);flex:0 0 auto}
.chead .hsub{font-size:11px;color:var(--ink3);line-height:1.5}
/* 계산 중에만 글자가 들어가는데, 비어도 테두리 캡슐이 남아 로드 실패처럼 보입니다 */
.pchip:empty{display:none}
.pchip{font-size:11px;color:var(--ink3);
  border:1px solid var(--line);border-radius:999px;padding:2px 8px;white-space:nowrap}
/* 제목줄은 align-items:baseline 입니다. 제목(15px)과 설명글(11px)은 글자라
   baseline 이 맞지만, 테두리 캡슐·셀렉트처럼 **상자**인 것들은 baseline 에
   걸면 상자 아래쪽이 글자 밑선에 맞춰져 위로 떠 보입니다. 카드마다 제목줄
   오른쪽 구성이 달라(설명글 4곳·캡슐·범례 키·정류장 셀렉트) 그 결과 카드
   머리의 높이 리듬이 조금씩 어긋났습니다. 상자만 중앙에 앉힙니다. */
.chead > .pchip,.chead > .stpick,.chead > .keys{align-self:center}
.cbody{padding:0 14px 14px}

/* --------------------------------------------------------- 4. KPI 타일
   넷을 각각 떠 있는 카드로 두면 화면에 상자가 넷 더 생기고, 정작 주인공인
   지도와 대등해 보입니다. 한 장의 띠로 묶고 사이를 괘선으로만 가릅니다 —
   관공서 문서의 요약표와 같은 구조이고, 상자 수가 넷 줄어 화면이 조용해집니다. */
.kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:0;margin-bottom:12px;
  background:var(--card);border:1px solid var(--line);border-radius:12px;overflow:hidden}
.kpi{padding:13px 16px 12px;min-width:0}
.kpi + .kpi{border-left:1px solid var(--line)}
/* 부팅 직후 자리표시자('–')와 계산된 실제 값이 같은 모양이면 '로딩 중' 인지
   '값이 0' 인지 구분이 안 됩니다. index.html 의 초기 '–' 에 .pending 을 주면
   흐리게 떠서 갈립니다(값이 들어오면 innerHTML 교체로 자동 해제). */
.kpi .vl.pending{opacity:.4}
/* 이 넷(라벨·증감줄·부제)이 --ink3(4.9:1, 최소 AA 통과 수준)라 11px 크기에서
   흐려 보인다는 제보 — 값이 무엇인지 말해 주는 글자라 더 진한 --ink2 로 올린다. */
.kpi .lb{font-size:11px;color:var(--ink2);margin-bottom:3px}
/* 8자리 파생값(18,788,767원/통행)이 들어오면 26px 로는 타일을 넘칩니다.
   tabular-nums 는 배치할 때마다 자릿수가 흔들리지 않게 합니다. */
.kpi .vl{font-size:clamp(19px,1.9vw,26px);font-weight:650;letter-spacing:-.01em;line-height:1.15;
  font-variant-numeric:tabular-nums}
.kpi .vl small{font-size:14px;font-weight:600;color:var(--ink2);margin-left:2px}
.kpi .vl.txt{font-size:19px}
.kpi .dl{font-size:11px;font-weight:600;margin-top:2px;min-height:17px;font-variant-numeric:tabular-nums;color:var(--ink2)}
.kpi .dl.good{color:var(--good)} .kpi .dl.bad{color:var(--bad)}
/* 증감줄(.dl)과 같은 11.5px 이면 위계가 없어 둘이 대등하게 읽힙니다.
   opacity:.9 는 이미 조정해 둔 색을 다시 흐리게 만들 뿐이라 뺀다. */
.kpi .sb{font-size:11px;color:var(--ink2);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* ------------------------------------------------------------- 5. 지도 */
.mapcard{display:flex;flex-direction:column}
.mtools{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:0 14px 8px;flex-wrap:wrap}
.lset{display:flex;gap:4px;flex-wrap:wrap}
.lbtn{font-size:12px;padding:4px 11px;border:1px solid var(--line);border-radius:999px;color:var(--ink2)}
.lbtn:hover{background:var(--selbg)}
.lbtn.on{background:var(--ink);border-color:var(--ink);color:var(--card)}
.tset{display:flex;gap:4px}
/* 캡슐 토글은 .tbtn 과 .simtool 이 같은 11px 인데 좌우 여백만 10 대 11 이었습니다.
   시뮬레이션에서는 둘이 위아래 줄로 붙어 있어 차이가 그대로 보입니다. 11 로 맞춥니다.
   지도 레이어(.lbtn 12px)는 주 컨트롤이라 크기 차이를 그대로 둡니다 — 실선/점선
   테두리와 함께 주·보조를 가르는 의도된 위계입니다. */
.tbtn{font-size:11px;padding:4px 11px;border:1px dashed var(--line);border-radius:999px;color:var(--ink3)}
.tbtn.on{border-style:solid;color:var(--ink2);background:var(--selbg);border-color:var(--sel)}

/* 지도 검색 — 읍면동 이름 또는 격자 ID */
.msearch{display:flex;gap:4px;align-items:center;position:relative}
.msi{font-size:11px;padding:4px 11px 4px 28px;width:196px;max-width:42vw;color:var(--ink2);
     border:1px solid var(--line);border-radius:999px;background:var(--card)}
.msi::placeholder{color:var(--ink3)}
.msi:focus{border-color:var(--sel);background:var(--selbg)}
/* 돋보기 — 입력칸 왼쪽 안쪽에 겹쳐 둡니다. 클릭은 입력칸이 받아야 하므로
   pointer-events 를 끕니다. 포커스가 오면 테두리와 같은 색으로 함께 살아납니다. */
.msi-ico{position:absolute;left:9px;top:50%;transform:translateY(-50%);
  width:13px;height:13px;fill:none;stroke:var(--ink3);stroke-width:1.7;
  stroke-linecap:round;pointer-events:none;transition:stroke .15s}
.msi:focus + .msi-ico{stroke:var(--sel)}
@media (prefers-reduced-motion:reduce){.msi-ico{transition:none}}
/* 검색창 안의 기본 X 버튼이 테마에 따라 안 보이는 경우가 있어 크기만 맞춥니다 */
.msi::-webkit-search-cancel-button{cursor:pointer}

.mapbox{position:relative;flex:1;min-height:0}
/* --zk 기본값을 여기서 선언해 둡니다. stroke-dasharray 처럼 값이 리스트인
   속성에서는 var(--zk,1) 의 폴백 쉼표가 리스트 구분자로 잘못 읽혀 선언이
   통째로 무효가 됩니다 — 그런 곳은 폴백 없이 var(--zk) 만 씁니다. */
.mapsvg{--zk:1;width:100%;height:auto;display:block;border-radius:0 0 9px 9px;position:relative;z-index:1}
.mapbox.arm .cells rect{cursor:crosshair}
/* 카카오맵 배경(선택, map.js 가 키가 있을 때만 만듭니다) — SVG 뒤에 깔리고
   클릭·드래그는 전부 위의 SVG 가 받습니다(지도 자체 조작은 꺼 둠).
   기본 배율에서는 .land/.wtr 가 그대로 덮어 지금처럼 미스매칭 색이 먼저
   읽히고, 충분히 확대(.zdetail, ≈10배 이상)했을 때만 걷혀 실제 지도가
   드러납니다. */
.kakaomap{position:absolute;inset:0;z-index:0;border-radius:0 0 9px 9px;overflow:hidden;pointer-events:none}
/* 카카오 지도는 .kakaomap 이 아니라 이 안쪽 div 에 붙습니다. 정수 줌 레벨
   보정(map.js alignKakao)이 여기에 transform 을 거는데, 바깥 .kakaomap 에
   걸면 자르는 상자까지 같이 커져 배경이 지도 카드 밖으로 넘칩니다
   (.mapbox 에는 overflow 가 없습니다). 틀과 내용을 나눠 둡니다. */
.kakaomap-inner{position:absolute;inset:0;transform-origin:0 0}
/* fill 만 지우면 해안선 stroke 가 남아 실제 지도 위에 굵은 회색 띠로 뜹니다 */
.kkmode.zdetail .wtr,.kkmode.zdetail .land{fill-opacity:0;stroke-opacity:0}
.kkmode.zdetail .c{fill-opacity:.55}
/* 카카오 배경에는 카카오가 그린 버스정류장·상호 아이콘이 이미 타일에 찍혀
   있습니다. 그대로 두면 우리가 찍은 정류장 점과 나란히 보여 무엇이 분석
   대상인지 헷갈립니다. 배경을 탈색해 뒤로 물리고, 데이터 레이어(우리 점·
   노선)만 색을 갖게 합니다. 배경 지도는 맥락이지 주인공이 아닙니다. */
/* .kakaomap 은 svg 의 자식이 아니라 형제입니다(.mapbox 안, svg 앞에 삽입).
   그래서 `.kkmode.zdetail .kakaomap` 은 영원히 매치되지 않았습니다 — 배경만
   원색으로 남고 우리 레이어만 흐려지는 정반대 결과였습니다. map.js 의
   applyZoom 이 부모(.mapbox)에 .kkdetail 을 직접 겁니다. */
.kkdetail .kakaomap{filter:saturate(.35) brightness(1.04) contrast(.92)}
/* 범례 — 예전에는 지도 위에 떠 있어 좌하단(우정·매향·궁평) 격자를 가렸습니다.
   지도 아래 띠로 내려 어떤 화면 폭에서도 데이터를 가리지 않습니다. */
.legendbar{border-top:1px solid var(--line);padding:9px 14px 11px;display:flex;
  align-items:center;gap:12px;flex-wrap:wrap}
.legendbar .lg-head{display:flex;align-items:baseline;gap:6px;white-space:nowrap}
.legendbar .lg-head b{font-size:11px;font-weight:650;color:var(--ink2)}
.legendbar .lg-head span{font-size:10px;color:var(--ink3);}
.legendbar .lg-body{display:flex;align-items:center;gap:8px;flex:1;min-width:230px}
.legendbar .lg-end{font-size:11px;color:var(--ink3);white-space:nowrap}
.legendbar .lg-scale{flex:1;min-width:120px}
.legendbar .lg-sw{display:flex;gap:2px}
.legendbar .lg-sw i{display:block;height:11px;flex:1;border-radius:2px}
.legendbar .lg-tick{display:flex;margin-top:3px;font-size:9px;color:var(--ink3)}
/* 눈금은 색 칸의 경계에 오도록 반 칸씩 밀어 배치합니다 */
.legendbar .lg-tick span{flex:1;text-align:right;transform:translateX(50%);white-space:nowrap}
.legendbar .lg-tick span:last-child{flex:0}
/* 마지막 '값' 눈금(빈 span 앞)까지 절반을 밀면 스케일 밖으로 나가 옆의
   '공급 부족' 글자와 겹칩니다 — 상한값이 못 읽히면 색의 기준이 사라집니다. */
.legendbar .lg-tick span:nth-last-child(2){transform:translateX(0)}
.legendbar .lg-tick5 span{flex:1;text-align:center;transform:none;font-size:9px}
.legendbar .lg-note{width:100%;font-size:11px;color:var(--sel);display:flex;align-items:center;gap:5px}
.legendbar .sw-elig{display:inline-block;width:10px;height:10px;border-radius:2px;
  border:1.2px solid var(--sel);background:var(--selbg)}

.wtr{fill:var(--water)}
.land{fill:var(--land);stroke:var(--coast);stroke-width:calc(1.2px/var(--zk,1));stroke-linejoin:round}
.cells rect{transition:fill .25s,opacity .18s;cursor:pointer}
@media (prefers-reduced-motion:reduce){.cells rect{transition:none}}
/* 마우스를 올린 격자를 즉시 알 수 있게 (툴팁만으로는 어느 칸인지 헷갈립니다).
   호버 = 하이라이터. 칸의 색을 살짝 띄워 '집힌' 느낌을 주되 색상(hue)은
   그대로 둡니다 — 색을 바꾸면 지표를 잘못 읽게 됩니다.
   테두리는 셀에 직접 stroke 를 주지 않고 칸 안쪽에 따로 그립니다(map.js 의
   hov-line). stroke 는 선 중심 기준이라 절반이 밖으로 나가 이웃 칸을 덮는데,
   500m 격자(약 8px)에서는 그게 이웃을 통째로 가립니다. */
/* ⚠️ filter 를 쓰지 마세요. filter 는 그 칸을 별도 페인트 서피스로 승격시켜,
   마우스가 칸을 넘을 때마다 지도 전체가 다시 래스터됩니다 — 실측 호버 프레임
   p95 118.5ms · 50ms 초과 6/72. 밝기는 map.js 가 맨 위 레이어에 깔아 주는
   .hov-fill(반투명 한 장)로 냅니다. opacity 도 같은 함정입니다.

   방향은 테마마다 반대입니다. 다크는 흰 덮개로 띄우고, 라이트는 검은 덮개로
   눌러 잡습니다 — 라이트에서 흰 덮개를 쓰면 이미 밝은 칸이 더 옅어져 강도가
   낮아 보이고(발산형 색 스케일에서 의미가 반대로 읽힙니다) 대비도 거의 안 납니다.
   어느 쪽이든 색상(hue)은 그대로라 위의 계약을 지킵니다. */
.hov-fill{fill:#fff;fill-opacity:.14;stroke:none}
:root[data-theme="light"] .hov-fill{fill:#000;fill-opacity:.07}
@media (prefers-color-scheme:light){
  :root:where(:not([data-theme="dark"])) .hov-fill{fill:#000;fill-opacity:.07}
}
/* 카카오 배경 위에서는 덜 올립니다 — 배경을 탈색해 뒤로 물려 둔 의도를
   호버가 되돌리지 않도록. */
.kkmode.zdetail .hov-fill{fill-opacity:.09}
:root[data-theme="light"] .kkmode.zdetail .hov-fill{fill-opacity:.05}
/* 배치 도구를 든 상태에서 적용 가능한 격자만 또렷하게 */
/* 강조를 '테두리 추가' 가 아니라 '나머지 물리기' 로 합니다.
   예전에는 적격 격자마다 보라 테두리를 둘러 도구를 들면 지도 육지 픽셀의
   19.2% 가 보라가 됐고, 그 색이 '선택한 격자' 링(--sel)과 같아서 선택한 칸을
   굵기 차이로만 구분해야 했습니다. 읍면동 경계·노선에 이어 세 번째 선 체계가
   격자 위에 얹히던 것도 사라집니다. 이제 보라는 선택링 하나뿐입니다. */
.cells rect.inelig{opacity:.14}
.cells rect.elig{stroke:none}
.c.m0{fill:var(--mi0)}.c.m1{fill:var(--mi1)}.c.m2{fill:var(--mi2)}.c.m3{fill:var(--mi3)}
.c.m4{fill:var(--mi4)}.c.m5{fill:var(--mi5)}.c.m6{fill:var(--mi6)}
.c.sb0{fill:var(--sb0)}.c.sb1{fill:var(--sb1)}.c.sb2{fill:var(--sb2)}.c.sb3{fill:var(--sb3)}.c.sb4{fill:var(--sb4)}
.c.so0{fill:var(--so0)}.c.so1{fill:var(--so1)}.c.so2{fill:var(--so2)}.c.so3{fill:var(--so3)}.c.so4{fill:var(--so4)}
.c.fl0{fill:var(--fl0)}.c.fl1{fill:var(--fl1)}.c.fl2{fill:var(--fl2)}.c.fl3{fill:var(--fl3)}.c.fl4{fill:var(--fl4)}
/* --zk 는 map.js 가 확대 배율을 넣어 주는 변수 — 나누면 확대해도 화면 크기가 유지됩니다.
   범례 미니 SVG 처럼 --zk 가 없는 곳은 폴백 1 로 원래 크기 그대로입니다. */
.rlab{fill:var(--ink);font-size:calc(11.5px/var(--zk,1));font-weight:700;paint-order:stroke;stroke:var(--card);
  stroke-width:calc(3.5px/var(--zk,1));stroke-linejoin:round;pointer-events:none;letter-spacing:-.01em}
.rlab.sm{font-size:calc(10px/var(--zk,1));font-weight:600;fill:var(--ink2);stroke-width:calc(3px/var(--zk,1))}
.nlab{fill:var(--ink3);font-size:11px;opacity:.62;pointer-events:none}
.wlab{fill:var(--coast);font-size:10px;letter-spacing:.2em;pointer-events:none}
/* 노선은 맥락이지 주인공이 아닙니다. 진하게 깔면 격자의 미스매칭 색을 덮어
   "어디가 빨간가"가 안 읽힙니다. 흐리게 두고, 선택된 노선만 또렷하게 합니다. */
.rt-casing{fill:none;stroke:var(--card);stroke-width:calc(4px/var(--zk,1));stroke-linecap:round;stroke-linejoin:round;
  opacity:.45;pointer-events:none}
.rt{fill:none;stroke:var(--ink2);stroke-width:calc(1.4px/var(--zk,1));stroke-linecap:round;stroke-linejoin:round;
  opacity:.38;pointer-events:none}
.rt.on{stroke:var(--sel);stroke-width:calc(3px/var(--zk,1));opacity:1}
/* 노선 선은 정류장을 순서대로 이은 모식도(schematic)지 실제 도로 형상이 아닙니다.
   TAGO/경기도 API 가 경유정류소 목록만 주고 폴리라인을 주지 않아서입니다.
   전체 뷰에서는 티가 안 나지만, 확대해 실제 도로가 보이는 상태에서 실선으로
   두면 "버스가 이 길로 간다"는 틀린 주장이 됩니다. 점선으로 바꿔 근사임을
   드러냅니다. (실제 형상이 필요하면 노선 shapefile 을 따로 받아야 합니다) */
.kkmode.zdetail .rt{stroke-dasharray:calc(5px/var(--zk)) calc(4px/var(--zk));opacity:.3}
.kkmode.zdetail .rt-casing{opacity:.25}

/* ── 노선 하나를 골라 본 상태(.rtfocus) ────────────────────────────────
   버스 번호로 찾아 들어온 노선은 맥락이 아니라 **주인공**입니다. 위 규칙은 노선을
   배경으로 두려고 흐리게(opacity .38) 깔아 두는데, 그대로 두면 일부러 찾은 노선이
   회색 실 한 가닥으로 보입니다.

   가는 길과 오는 길을 색으로 가릅니다. 한 노선 기록에 두 방향이 이어 담겨 있어
   (200개 중 185개가 기점=종점) 한 색으로 그리면 선이 제자리로 돌아와 겹칩니다.
   색은 승차/하차에 쓰는 범주형 2색을 그대로 씁니다 — 색각 이상에서도 갈리는
   조합이라 이미 검증돼 있습니다. 방향을 못 가른 노선(turnIdx=null, 18개)은
   dir 클래스가 없어 아래 색이 안 걸리고 기본 색으로 굵게만 나옵니다. */
.rtfocus .rt{opacity:1;stroke-width:calc(3.2px/var(--zk,1))}
.rtfocus .rt-casing{opacity:.92;stroke-width:calc(6.4px/var(--zk,1))}
.rtfocus .rt.dir0{stroke:var(--c-board)}
.rtfocus .rt.dir1{stroke:var(--c-alight)}
/* ⚠️ 확대해 실제 지도가 보이는 상태에서도 **점선은 그대로 둡니다.** 위 주석대로
   이 선은 정류장을 순서대로 이은 모식도지 도로 형상이 아니라서, 실선으로 바꾸면
   "버스가 이 길로 간다"는 틀린 주장이 됩니다. 굵기와 색으로 눈에 띄게 하되 그
   주장은 건드리지 않습니다. (.kkmode.zdetail .rt 가 0,3,0 이라 여기는 0,4,0) */
.rtfocus.kkmode.zdetail .rt{opacity:.95;stroke-width:calc(3.2px/var(--zk,1))}
.rtfocus.kkmode.zdetail .rt-casing{opacity:.5}

/* ── 끄는 동안만 노선을 싸게 그립니다 ─────────────────────────────────
   전체 배율에서 마우스로 끌 때 프레임 간격 중앙값이 200ms 였고, 그 비용은
   노선 층입니다(층을 통째로 끄면 66.6ms). 전체 뷰에서는 화면 밖이 없어
   map.js 의 뷰포트 클리핑이 아무것도 못 자릅니다 — 남는 수단은 둘뿐입니다.

     케이싱 끔 166.6ms(-17%) · optimizeSpeed 183.3ms(-8%) · 둘 다 133.4ms(-33%)

   그런데 둘 다 화면이 하는 주장을 깎습니다. 케이싱은 붉은 칸 위에서 노선이
   끊겨 보이지 않게 하는 대비 장치고, optimizeSpeed 는 안티에일리어싱을 꺼
   가는 선을 계단지게 만듭니다. 33% 를 벌자고 정지 화면(캡처·발표에 쓰는 바로
   그 화면)을 상시로 깎을 이유는 없습니다.

   그래서 **끄는 동안에만** 겁니다. .panning 은 map.js 의 pointermove 가
   4px 이상 움직였을 때 붙였다가 손을 떼면 바로 뗍니다. 움직이는 중에는
   가장자리 계단이나 대비 차이를 눈이 잡지 못하고, 손을 떼는 순간 원래
   품질로 돌아옵니다 — 정지 화면은 픽셀 단위로 이전과 같습니다. */
.panning .rt{shape-rendering:optimizeSpeed}
.panning .rt-casing{display:none}
/* --sr 은 map.js 가 정류장별로 실어 보내는 기본 반지름(일 승하차량 비례, 1.6~4.6px).
   확대 역스케일(--zk)은 여기서 그대로 걸어야 확대해도 점 크기가 유지됩니다. */
.st{fill:var(--card);stroke:var(--ink);stroke-width:calc(1.8px/var(--zk,1));
  r:calc(var(--sr,3.4px)/var(--zk,1));pointer-events:none}
/* 히트 전용 층이 없는(전체 표시) 상태에서는 DOM 이 아니라 기하로 판정합니다
   (map.js nearestStop). 커서만 바꿔 줍니다. */
[data-groutes]:not(.fathit){cursor:default}
.st.hub{fill:#ffd400;stroke-width:calc(2.4px/var(--zk,1));r:calc(var(--sr,4.6px)/var(--zk,1))}
.st.on{fill:var(--sel);stroke:var(--card);stroke-width:calc(2.4px/var(--zk,1))}
.sthit{fill:transparent;cursor:pointer;r:calc(11px/var(--zk,1))}
/* 정류장 이름 — 충분히 확대(약 10배)했을 때만 드러납니다 (map.js 가 .zdetail 토글) */
.stlab{display:none;fill:var(--ink);font-size:calc(9.5px/var(--zk,1));font-weight:600;
  paint-order:stroke;stroke:var(--card);stroke-width:calc(2.5px/var(--zk,1));
  stroke-linejoin:round;pointer-events:none}
.zdetail .stlab{display:block}
/* 격자를 클릭해 그 격자 것만 보는 중에는 몇 개 안 되므로 배율과 무관하게 보입니다 */
.stlab.always{display:block}
/* 배치 모드: 정류장이 격자 클릭을 가로채지 않도록 통과시킵니다 */
[data-groutes].nohit .sthit,
[data-groutes].nohit circle.st{pointer-events:none;cursor:inherit}
.indmark{fill:var(--ink3);opacity:.8;pointer-events:none}
[data-selring],[data-hovring]{pointer-events:none}
/* 호버 테두리 — 굵기·위치는 map.js 가 칸 크기에 맞춰 계산합니다 */
.hov-line{fill:none;stroke:var(--ink);stroke-opacity:.5;stroke-linejoin:round}
/* 굵기는 map.js 가 칸 크기에 맞춰 attribute 로 지정합니다(여기서 정하면 덮어씀).
   불투명하게 두면 링이 주변 격자와 글씨를 가려 읽을 수 없습니다. */
/* 굵기는 map.js 가 칸 크기에 맞춰 attribute 로 지정하지만, 격자를 클릭하면
   2,700% 까지 확대돼 그 값이 화면상 100px 넘는 띠가 됩니다. CSS 는 presentation
   attribute 를 이기므로 확대 상태에서만 화면 고정 굵기로 상한을 겁니다. */
.selring-out{fill:none;stroke:var(--card);stroke-opacity:.5}
.selring-in{fill:none;stroke:var(--sel);stroke-opacity:.8}
.zdetail .selring-out{stroke-width:calc(5px/var(--zk,1))}
.zdetail .selring-in{stroke-width:calc(2.6px/var(--zk,1))}
.placed{pointer-events:none}
/* 배치 기호 — 수단마다 색이 다르고, 모양은 색각 이상·흑백 대비입니다.
   테두리(--card)를 채움 뒤에 깔아 어떤 격자 색 위에서도 뜹니다.
   크기·굵기는 map.js 가 칸 크기에 맞춰 계산합니다. */
 /* .placed 로 스코프하면 안 됩니다 — 범례는 .lg-sym svg 안이라 .placed 의
    자손이 아니고, 같은 markerShape() 로 그려도 fill 이 안 걸려 검정이 됩니다.
    색이 수단을 구분하는 지금은 범례 색 키가 지도와 달라지면 지도를 못 읽습니다. */
.pmk{stroke:var(--card);paint-order:stroke;stroke-linejoin:round}
.pmk-stop{fill:var(--good)}
.pmk-drt{fill:var(--sel)}
.pmk-freq{fill:var(--warn)}
.pmk-n{fill:var(--card);stroke:none;font-weight:700}
/* 지도 위 마커만 — 배경 점(환승거점 지름 9px)과 크기가 비슷해 배치한 위치를
   눈으로 못 찾았습니다. 흰 테두리를 두껍게 하고 그림자로 띄웁니다.
   범례(.lg-sym)에는 배경이 없으므로 .placed 스코프를 씁니다. */
.placed .pmk{stroke-width:calc(3.5px/var(--zk,1))}
.placed .pmk-stop,.placed .pmk-drt,.placed .pmk-freq{
  filter:drop-shadow(0 1px 2px rgba(0,0,0,.35))}
/* 이 하나만 map.js 가 계산하지 않고 CSS 가 정합니다 — SVG 사용자 단위라
   --zk 로 나누지 않으면 확대할수록 같이 부풀어 칸을 덮습니다(120배면 9px 가
   1,080px). 나머지 pmk-* 는 map.js 가 칸 크기 비례로 attribute 를 찍습니다. */
.placed .pmk-more{fill:var(--sel);font-size:calc(9px/var(--zk,1));font-weight:700;paint-order:stroke;
  stroke:var(--card);stroke-width:calc(2.5px/var(--zk,1))}
.compass{fill:none;stroke:var(--ink3);stroke-width:1.2}
.compass-t{fill:var(--ink3);font-size:9px;}
.scalebar{stroke:var(--ink3);stroke-width:1.4}

/* ------------------------------------------------- 6. 우선순위 리스트 */
.sidecard{display:flex;flex-direction:column;min-height:0}
.t10{flex:1;overflow-y:auto;min-height:0;padding:2px 6px 10px}
.sidefoot{border-top:1px solid var(--line);padding:9px 16px;font-size:11px;color:var(--ink3);line-height:1.6}
.trow{display:grid;grid-template-columns:24px minmax(0,1fr) auto;gap:4px 10px;align-items:center;
  width:100%;text-align:left;padding:7px 8px;border-radius:8px}
.trow:hover{background:var(--selbg)}
.trow.sel{background:var(--selbg);box-shadow:inset 0 0 0 1.5px var(--sel)}
.trow .rk{font-size:12px;color:var(--ink3);text-align:right}
.trow .nm{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.trow .nm span{font-family:var(--font-mono);font-weight:400;font-size:11px;color:var(--ink3);margin-left:6px}
/* 순위를 정한 값(1위=100 기준 상대 점수)이 주 숫자입니다. MI 는 sub2 로 내려
   보조가 됐습니다 — 어느 쪽이 정렬 근거인지 크기로 드러나야 합니다. */
.trow .pscore{font-size:13px;font-weight:700;
  color:var(--ink);text-align:right;font-variant-numeric:tabular-nums}
.trow .pscore small{font-size:10px;font-weight:400;color:var(--ink3);margin-left:1px}
.trow .mi{font-size:11px;color:var(--ink2);text-align:right}
.trow .sub2{grid-column:2;font-size:11px;color:var(--ink3);
  display:flex;gap:9px;flex-wrap:wrap;margin-top:-1px}
/* 고령 7% 와 22% 가 똑같이 진빨강이라 값과 무관한 위험 신호처럼 보였습니다.
   기본은 눌러 두고, 임계를 넘을 때만 JS 가 .hi 를 붙여 색을 씁니다. */
.trow .sub2 em{font-style:normal;color:var(--ink2);font-weight:600}
.trow .sub2 em.hi{color:var(--tag-new-fg)}
.trow .bar{grid-column:2;height:4px;border-radius:2px;background:var(--track);position:relative;overflow:hidden}
/* 지도 범례가 --mi5 를 'MI +0.25~+0.7 구간' 이라고 못박아 둔 색이라 여기서는
   안 씁니다(그 색을 순위와 무관하게 상수로 채우면 1위와 9위의 막대 색이
   같아져 버립니다 — 실제로 그랬던 적이 있습니다). 대신 '나쁨' 의미가 이미
   정해진 --bad 를 씁니다 — 시급도 막대라 그 뜻과도 맞고, 지도 범례의 색과
   부딪히지 않습니다. 제보(흐려서 안 보임)에 맞춰 불투명도도 올렸습니다. */
.trow .bar i{position:absolute;inset:0 auto 0 0;border-radius:2px;background:var(--bad);opacity:.85}
.trow .tag{grid-column:3;justify-self:end}
.tag{display:inline-block;font-size:10px;font-weight:600;letter-spacing:.03em;border-radius:999px;padding:2px 8px}
.tag.new{background:var(--tag-new-bg);color:var(--tag-new-fg)}
.tag.add{background:var(--tag-add-bg);color:var(--tag-add-fg)}
.tag.drt{background:var(--tag-drt-bg);color:var(--tag-drt-fg)}
.tag.eff{background:var(--tag-eff-bg);color:var(--tag-eff-fg)}
.tag.ok{background:var(--tag-ok-bg);color:var(--tag-ok-fg)}
.empty{padding:26px 14px;text-align:center;font-size:11px;color:var(--ink3);line-height:1.7}

/* ---------------------------------------------------------- 7. 차트 */
/* viewBox 고정 + width:100% 라 카드가 넓어지면 SVG 안 글자가 통째로 확대됩니다.
   1020px 이하에서 카드가 12칸(984px)이 되면 산점도가 1.84배가 돼 축 눈금이
   카드 제목보다 커집니다 — 위계가 뒤집혀 '깨진 차트' 로 보입니다.
   원본 viewBox 근처로 상한을 걸고 가운데 정렬합니다. 데스크톱(≥1180)에서는
   카드가 이미 상한보다 좁아 렌더 결과가 바뀌지 않습니다. */
.chart{margin-left:auto;margin-right:auto}
#scatter{max-width:560px}
#stchart,#cmpChart,#costChart{max-width:760px}
.chart{width:100%;height:auto;display:block}
/* 점 위에 겹쳐도 읽히도록 후광을 줍니다 — 지도 라벨(.rlab)과 같은 수법.
   그리는 순서는 dashboard.js 에서 점 뒤로 옮겼습니다. */
.qlab{fill:var(--ink3);font-size:10px;paint-order:stroke;
  stroke:var(--card);stroke-width:3px;stroke-linejoin:round}
.qlab.hot{fill:var(--ink2);font-weight:600}
.axlab{fill:var(--ink3);font-size:11px}
.tick{fill:var(--ink3);font-size:10px;}
.gl{stroke:var(--grid);stroke-width:1}
/* --axis(#c3c2b7)는 배경 대비 1.75:1 로 비텍스트 최소 3:1 에 못 미쳐
   4분면 구획 자체가 흐릿했습니다. 격자선이 아니라 '의미를 나누는 선' 입니다. */
.zl{stroke:var(--ink3);stroke-width:1.2;opacity:.75}
.dot{stroke:var(--card);stroke-width:1.4;cursor:pointer}
.scatring{fill:none;stroke:var(--sel);stroke-width:2.2;pointer-events:none}
.stbar-b{fill:var(--c-board)}
.stbar-a{fill:var(--c-alight)}
.pband{fill:var(--ink);opacity:.05;pointer-events:none}
.colhit{fill:transparent;cursor:pointer}
.dl2{fill:var(--ink2);font-size:10px;}
.keys{display:flex;gap:12px;align-items:center;font-size:11px;color:var(--ink2);flex-wrap:wrap}
.keys i{display:inline-block;width:9px;height:9px;border-radius:2px;margin-right:5px;vertical-align:-1px}
.keys i.ghost{background:var(--bad);border-radius:2px}
.stsel{font:inherit;font-size:12px;color:var(--ink);background:var(--card);border:1px solid var(--line);
  border-radius:7px;padding:4px 8px;max-width:170px}
/* 검색을 지도 카드로 옮긴 뒤로 이 칸에는 선택 목록만 남습니다.
   검색 결과가 여러 개일 때 방향(상·하행)을 고르는 용도라 폭을 넉넉히 줍니다. */
.stpick{display:flex;gap:6px;align-items:center;margin-left:auto}
.stpick .stsel{min-width:180px}
.stsearch{font:inherit;font-size:12px;color:var(--ink);background:var(--card);border:1px solid var(--line);
  border-radius:7px;padding:4px 8px;width:120px}
.stsearch::placeholder{color:var(--ink3)}
.ststats{display:flex;gap:18px;padding:2px 16px 12px;font-size:11px;color:var(--ink3);flex-wrap:wrap}
/* 보조 수치(208명/44명/20.7%)가 카드 제목보다 크면 무엇이 주인공인지
   뒤집힙니다 — 제목 15px 아래로 내립니다. */
.ststats b{font-size:13px;font-weight:650;margin-left:5px;color:var(--ink)}

/* 비교 차트: 기준선(배치 전) = 빨강 채움, 시나리오(배치 후) = 파랑 채움.
   한때 기준선을 고스트(점선 외곽선)로 그려 모양으로 구분했는데, 옅어서
   비교가 잘 안 된다는 제보로 그만두고 색으로만 확실히 대비시킵니다.
   두 막대는 2px 간격을 두고 나란히 그려지므로(barUp 호출부 참고) 채워도
   서로 가리지 않습니다. */
.cmp-base{fill:var(--bad)}
.cmp-sim{fill:var(--c-board)}
.cmp-sim.worse{fill:var(--bad)}
.cmp-lab{fill:var(--ink2);font-size:10px;}
.cmp-name{fill:var(--ink2);font-size:11px}
/* 같은 클래스인데 실효 크기가 카드 폭에 따라 달라집니다 — .chart{width:100%} 인데
   viewBox 폭은 고정이라, 7칸 카드의 #cmpChart 는 1.18배로 커지고 5칸 카드의
   #costChart 는 0.84배로 줄어 선언 10px 이 화면에서 8.4px 가 됩니다. 카드의
   존재 이유인 총 소요액이 카드 안에서 가장 작은 글자였습니다. 축소분을 되돌립니다. */
#costChart .cmp-lab{font-size:12px}
#costChart .cmp-name{font-size:13px}
/* 단색 막대는 순차 스케일의 **끝 단계**를 씁니다. 중간 단계(--sb3)를 쓰면 다크에서
   카드 대비가 3.06:1 까지 떨어져 막대가 흐려집니다(--sb4 는 4.04:1). 지도처럼
   여러 단계를 늘어놓는 자리가 아니라, 한 색으로 값을 읽는 자리이기 때문입니다. */
.cost-bar{fill:var(--sb4)}
.cost-bar.dim{fill:var(--sb2)}

/* --------------------------------- 8. 시뮬레이터 · 시나리오 빌더 */
.simbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:0 14px 10px;padding:8px 10px;
  border:1px solid var(--line);border-radius:9px;background:var(--selbg);background:color-mix(in srgb,var(--selbg) 45%,transparent)}
.simbar .sl{font-size:11px;color:var(--ink3);letter-spacing:.02em;margin-right:2px}
.simtool{font-size:11px;padding:4px 11px;border:1px solid var(--line);border-radius:999px;color:var(--ink2);
  background:var(--card);display:inline-flex;align-items:center;gap:5px}
.simtool:hover{border-color:var(--sel)}
.simtool.on{background:var(--sel);border-color:var(--sel);color:#fff;font-weight:600}
.simtool i{font-style:normal;font-size:10px}
.simbar .spacer{flex:1}
.simbar .cnt{font-size:11px;color:var(--ink3);}
.simhint{width:100%;font-size:11px;color:var(--ink3);margin-top:2px}

.plist{list-style:none;margin:0;padding:0 14px 6px;display:flex;flex-direction:column;gap:6px;
  max-height:260px;overflow-y:auto}
.plist li{display:grid;grid-template-columns:22px minmax(0,1fr) auto auto;gap:10px;align-items:center;
  padding:7px 9px;border:1px solid var(--line);border-radius:8px;background:var(--bg)}
.plist .ic{font-size:12px;color:var(--sel);text-align:center}
.plist .tx{min-width:0}
.plist .tx b{display:block;font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.plist .tx span{font-size:11px;color:var(--ink3);}
.plist .cost{font-size:11px;color:var(--ink2);
  font-variant-numeric:tabular-nums;white-space:nowrap}
/* ── 최소 터치·클릭 면적 24x24 (WCAG 2.2 Target Size Minimum) ──────────────
   보이는 크기는 그대로 두고 누를 수 있는 면적만 넓힙니다. 레이아웃을 밀지
   않으려고 .help 와 같은 투명 ::after 확장 상자를 씁니다.
   특히 배치 삭제 ×는 19x19 인데 되돌릴 수 없는 동작이고 바로 옆 금액과 6px
   밖에 안 떨어져 있어, 시연 중 빗나가면 배치가 사라집니다. */
.plist .del,.scen .sdel,.legendbar .lg-clear,.rec-scope .rs-btn,
.rec-tab,.zctl [data-z="reset"]{position:relative}
.plist .del::after,.scen .sdel::after,.legendbar .lg-clear::after,
.rec-scope .rs-btn::after,.rec-tab::after,.zctl [data-z="reset"]::after{
  content:"";position:absolute;left:50%;top:50%;
  min-width:24px;min-height:24px;width:100%;height:100%;
  transform:translate(-50%,-50%)}

/* .xbtn 과 같은 이유 — 글자 × 가 SVG 로 바뀌어 가운데 맞춤이 필요합니다.
   ::after 로 만들어 둔 24px 판정 면적은 그대로 위에 얹힙니다. */
.plist .del{display:inline-flex;align-items:center;justify-content:center;
  width:22px;height:22px;padding:0;color:var(--ink3);border-radius:6px}
.plist .del:hover{background:var(--tag-new-bg);color:var(--tag-new-fg)}

.budget{padding:2px 14px 12px}
.budget .brow{display:flex;justify-content:space-between;align-items:baseline;font-size:11px;
  color:var(--ink3);margin-bottom:5px}
.budget .brow b{font-size:14px;color:var(--ink);font-variant-numeric:tabular-nums}
.budget .btrack{height:8px;border-radius:4px;background:var(--track);overflow:hidden;position:relative}
/* 폭(width)을 애니메이션하면 매 프레임 레이아웃을 다시 계산합니다.
   가로로만 늘어나는 막대라 transform:scaleX 로 같은 그림을 얻으면서
   합성 단계에서만 처리됩니다. JS 는 --p(0~1)만 넣습니다. */
.budget .btrack i{position:absolute;inset:0 auto 0 0;width:100%;border-radius:4px;
  background:var(--sb4);transform-origin:left center;transform:scaleX(0);   /* 끝 단계 — .cost-bar 주석 참고 */
  transition:transform .3s}
.budget .btrack i.over{background:var(--bad)}
@media (prefers-reduced-motion:reduce){.budget .btrack i{transition:none}}
.budget .bnote{font-size:11px;color:var(--ink3);margin-top:5px}
.budget .bnote.over{color:var(--bad);font-weight:600}

.scen-list{display:flex;flex-direction:column;gap:6px;padding:0 14px 12px}
.scen{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 10px;align-items:center;
  width:100%;text-align:left;padding:9px 11px;border:1px solid var(--line);border-radius:9px;background:var(--bg)}
.scen:hover{border-color:var(--sel)}
.scen.on{border-color:var(--sel);box-shadow:inset 0 0 0 1px var(--sel);background:var(--selbg)}
.scen .snm{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.scen .smeta{grid-column:1/-1;font-size:11px;color:var(--ink3);
  display:flex;gap:10px;flex-wrap:wrap}
.scen .sdel{display:inline-flex;align-items:center;justify-content:center;
  width:22px;height:22px;padding:0;color:var(--ink3);border-radius:6px}
.scen .sdel:hover{background:var(--tag-new-bg);color:var(--tag-new-fg)}

.policy{padding:0 14px 14px;display:flex;flex-direction:column;gap:10px}
.pcard{border:1px solid var(--line);border-radius:9px;padding:11px 13px;background:var(--bg)}
/* 소제목 13 · 본문 12 는 1px 차이라 굵기 하나로만 버팁니다 */
.pcard h3{font-size:14px;font-weight:650;margin:0 0 6px}
.pcard p{margin:0;font-size:12px;color:var(--ink2);line-height:1.65}
.pcard ul{margin:6px 0 0;padding-left:17px;font-size:12px;color:var(--ink2);line-height:1.7}
.pcard .num{font-weight:600;color:var(--ink);
  font-variant-numeric:tabular-nums}

/* ------------------------------------------------------- 9. 버튼 · 폼 */
.btn{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;
  padding:6px 13px;border-radius:8px;border:1px solid var(--line);color:var(--ink2);background:var(--card);
  white-space:nowrap}
.btn:hover:not(:disabled){background:var(--selbg);border-color:var(--sel)}
/* .btn 은 .42, .minib 은 .4 로 달랐습니다. 눈에 띄는 차이는 아니지만 같은
   "못 누름" 을 두 값으로 말하고 있었습니다. .45 로 맞춥니다 — 시뮬레이션의
   [되돌리기]·[초기화] 는 대부분의 시간을 비활성으로 보내는데, 그동안 글자가
   무슨 버튼인지 읽기 어려울 만큼 흐렸습니다. */
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn.primary{background:var(--sel);border-color:var(--sel);color:#fff}
/* 예전에는 filter:brightness(1.08) 이었습니다. 채도 높은 파랑을 밝기만 올리면
   색이 바래 보이고(호버가 '비활성처럼' 읽힙니다), filter 는 그 버튼을 별도 페인트
   서피스로 승격시킵니다. 이미 정의해 둔 --brand-ink 로 한 단계 옮깁니다 —
   라이트에서는 더 진하게(#003C77), 다크에서는 더 밝게(#8CC4F5) 가는 값이라
   두 테마 모두 '눌러지는 쪽' 으로 움직입니다. */
.btn.primary:hover:not(:disabled){background:var(--brand-ink);border-color:var(--brand-ink)}
/* 작은 상자 버튼은 .btn.sm 과 .minib 두 벌이었는데 글자 11.5 대 11, 여백 5·11 대
   4·10, 라운드 8 대 7 로 세 값이 전부 조금씩 달랐습니다. 1px 차이는 위계로 읽히지
   않고 안 맞아 보이기만 합니다. 한 치수(11px / 5·11 / 8)로 맞춥니다. */
.btn.sm{font-size:11px;padding:5px 11px;font-weight:600}
.btn i{font-style:normal;font-size:11px}
/* 직접 그린 아이콘(core.js ICONS). currentColor 라 버튼 색을 따라가고,
   글자와 광학 중심을 맞추려고 살짝 올려 둡니다. */
.ic{flex:0 0 auto;margin-top:-1px}
.btn .ic{width:14px;height:14px}
.btn.sm .ic{width:13px;height:13px}
.minib{font-size:11px;padding:5px 11px;border:1px solid var(--line);border-radius:8px;color:var(--ink2);background:var(--card)}
.minib:hover:not(:disabled){background:var(--selbg)}
.minib:disabled{opacity:.45;cursor:not-allowed}
.fld{display:flex;flex-direction:column;gap:4px}
.fld label{font-size:11px;color:var(--ink3)}
.fld input,.fld select,.fld textarea{font:inherit;font-size:12px;color:var(--ink);background:var(--bg);
  border:1px solid var(--line);border-radius:7px;padding:6px 9px;width:100%}
.fld input:focus,.fld select:focus,.fld textarea:focus{border-color:var(--sel)}
.frow{display:flex;gap:10px;flex-wrap:wrap;padding:0 14px 12px}
.frow .fld{flex:1;min-width:130px}

/* --------------------------------------------------- 10. 모달 (보고서) */
.modal{position:fixed;inset:0;z-index:80;display:none;align-items:center;justify-content:center;padding:20px}
.modal.open{display:flex}
.modal .veil{position:absolute;inset:0;background:rgba(0,0,0,.42);backdrop-filter:blur(2px)}
.sheet{position:relative;background:var(--card);border:1px solid var(--line);border-radius:14px;
  box-shadow:var(--shadow-lg);width:min(920px,100%);max-height:88vh;display:flex;flex-direction:column;overflow:hidden}
.sheet header{display:flex;align-items:center;gap:12px;padding:14px 18px;border-bottom:1px solid var(--line);flex-wrap:wrap}
.sheet header h2{margin:0;font-size:15px;font-weight:700}
.sheet header .hs{font-size:11px;color:var(--ink3)}
.sheet header .sp{flex:1}
.sheet .body{overflow-y:auto;padding:16px 18px;min-height:120px}
.sheet footer{display:flex;gap:8px;align-items:center;padding:12px 18px;border-top:1px solid var(--line);flex-wrap:wrap}
.sheet footer .sp{flex:1}
/* 글자 ×(19px)를 담던 시절의 font-size/line-height 대신 SVG 를 가운데 맞춥니다.
   보이는 크기는 그대로인데 누르는 면적이 25 → 30px 로 넓어져 WCAG 2.2 의
   최소 목표 크기(24px)를 여유 있게 넘깁니다. */
.xbtn{display:inline-flex;align-items:center;justify-content:center;
  width:30px;height:30px;padding:0;color:var(--ink3);border-radius:7px}
.xbtn:hover{background:var(--selbg);color:var(--ink)}

.doc{font-size:13px;line-height:1.75;color:var(--ink2)}
.doc h3{margin:18px 0 6px;font-size:13px;font-weight:700;color:var(--ink);
  padding-bottom:4px;border-bottom:1px solid var(--line)}
.doc h3:first-child{margin-top:0}
.doc p{margin:0 0 8px;white-space:pre-wrap}
.doc ul{margin:4px 0 10px;padding-left:19px}
.doc li{margin-bottom:3px}
.doc .dtitle{font-size:17px;font-weight:700;color:var(--ink);text-align:center;margin:0 0 4px}
.doc .dmeta{text-align:center;font-size:11px;color:var(--ink3);
  margin-bottom:16px}
.doc .dnote{margin-top:16px;padding:9px 11px;border:1px dashed var(--line);border-radius:8px;
  font-size:11px;color:var(--ink3);line-height:1.65}
/* 서버가 AI 없이 서식만 채워 보낸 초안일 때(isAiGenerated:false) 문서 맨 위에
   붙습니다. 이 표시가 없으면 얇은 폴백 초안을 AI 가 쓴 글로 오해합니다 — 백엔드가
   그 구분을 위해 플래그를 실어 보내는데(server/main.py _fallback_report) 화면이
   쓰지 않고 있었습니다. */
.doc .dwarn{margin:0 0 14px;padding:10px 12px;border:1px solid var(--warn);border-radius:8px;
  background:color-mix(in srgb,var(--warn) 9%,transparent);
  font-size:11.5px;color:var(--warn);line-height:1.7}
.doc .dwarn b{display:block;margin-bottom:2px}
.doc .dwarn code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}
.doc table{margin:2px 0 12px}
.doc .dcap{font-size:11px;color:var(--ink3);margin:10px 0 3px;font-weight:600}

.loading{display:flex;flex-direction:column;align-items:center;gap:12px;padding:44px 10px;color:var(--ink3);font-size:12px}
.spin{width:26px;height:26px;border:2.5px solid var(--line);border-top-color:var(--sel);border-radius:50%;
  animation:sp .8s linear infinite}
@keyframes sp{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.spin{animation-duration:2.4s}}
.errbox{border:1px solid var(--bad);border-radius:9px;padding:12px 14px;font-size:12px;color:var(--bad);
  background:var(--card);background:color-mix(in srgb,var(--bad) 8%,transparent);line-height:1.6}

/* 토스트 */
/* bottom 이 16 이 아니라 88 인 이유 — 우측 하단에 AI 런처(.chat-launch)가 상시
   떠 있어 같은 구석에서 겹칩니다. 토스트는 잠깐 뜨는 것이라 런처 위로 쌓습니다.
   (이 규칙을 §런처 쪽에 적었다가 파일 뒤인 여기에 밀렸습니다 — 같은 자리에 둡니다) */
.toasts{position:fixed;right:16px;bottom:88px;z-index:90;display:flex;flex-direction:column;gap:8px}
@media (max-width:640px){ .toasts{bottom:76px} }
.toast{background:var(--card);border:1px solid var(--line);border-radius:9px;box-shadow:var(--shadow);
  padding:10px 14px;font-size:12px;color:var(--ink2);max-width:320px;line-height:1.5}
.toast b{color:var(--ink)}
.toast.err{border-color:var(--bad);color:var(--bad)}

/* ----------------------------------------------- 11. 표 · 툴팁 · 푸터 */
details.tbl{border-top:1px solid var(--line);margin-top:4px}
details.tbl summary{cursor:pointer;font-size:11px;color:var(--ink3);padding:8px 16px;user-select:none}
details.tbl summary:hover{color:var(--ink2)}
.tblwrap{overflow-x:auto;padding:0 12px 12px}
table{border-collapse:collapse;width:100%;font-size:11px}
th{font-size:11px;color:var(--ink3);font-weight:600;text-align:right;padding:4px 8px;border-bottom:1px solid var(--line);white-space:nowrap}
th:nth-child(1),th:nth-child(2){text-align:left}
td{padding:3.5px 8px;text-align:right;border-bottom:1px solid var(--grid);color:var(--ink2);
  font-variant-numeric:tabular-nums;white-space:nowrap}
td:nth-child(1),td:nth-child(2){text-align:left}
td:first-child{font-family:var(--font-mono);color:var(--ink3)}

.tt{position:fixed;z-index:70;pointer-events:none;background:var(--card);background:color-mix(in srgb,var(--card) 96%,transparent);
  border:1px solid var(--line);border-radius:8px;padding:7px 10px;font-size:11px;color:var(--ink2);
  box-shadow:var(--shadow);display:none;max-width:250px;line-height:1.45}
.tt b{color:var(--ink);font-size:12px}
.tt .mono{font-size:11px;color:var(--ink3)}

footer.pg{margin-top:16px;border-top:1px solid var(--line);padding-top:12px;color:var(--ink3);font-size:11px}
footer.pg .fx{font-size:11px;line-height:1.9;
  display:flex;flex-wrap:wrap;gap:0 22px}
/* 한 항목이 줄 중간에서 끊기지 않게 nowrap 을 걸어 두었는데, 시뮬레이션
   푸터의 첫 항목이 441px 라 좁은 화면에서는 이게 문서를 통째로 넓혀
   페이지 전체에 가로 스크롤이 생겼습니다(390px 에서 문서 453px 실측).
   항목 하나가 뷰포트보다 길어질 수 있는 폭에서는 줄바꿈을 허용합니다. */
footer.pg .fx span{white-space:nowrap}
@media (max-width:640px){footer.pg .fx span{white-space:normal}}
footer.pg .srcs{margin:7px 0 5px;display:flex;gap:6px;flex-wrap:wrap;align-items:center}
footer.pg .srcs i{font-style:normal;border:1px solid var(--line);border-radius:999px;padding:2px 9px;font-size:11px}
footer.pg .dis{margin-top:6px;line-height:1.7}
/* 발행 표기 — 문서의 말미.
   상단 레일이 브랜드 파랑 3px 로 화면의 머리를 열므로, 여기서 2px 로 닫아
   페이지가 위아래로 묶인 한 장의 문서로 읽히게 합니다. 색은 이미 있는
   --brand 하나뿐이고 새 색·새 장치를 만들지 않습니다.
   내용은 core.js 의 mountIssuer() 가 config 값으로 채웁니다. */
footer.pg .issuer{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  margin:14px 0 0;padding-top:9px;border-top:2px solid var(--brand);
  font-size:11px;color:var(--ink3)}
footer.pg .issuer b{font-weight:650;color:var(--ink2);letter-spacing:-.01em}
footer.pg .issuer .is-sp{flex:1}
footer.pg .issuer:empty{display:none}

/* ------------------------------ 11-2. 노선 경유 순서 (선택한 격자)
   네이버 지도 노선 화면처럼, 노선마다 세로선 하나에 정류장을 순서대로 꿴다.
   가로로 여러 노선을 늘어놓아 "이 동네에 몇 번이 어떤 순서로 지나가는가"를
   한눈에 대조할 수 있게 한다. */
/* 정차가 많은 노선(30개 이상)이 섞이면 카드가 페이지를 삼키므로 높이를 묶고
   안에서 스크롤합니다. 노선이 여럿이면 가로로도 스크롤됩니다. */
/* .rstrip 이 display:flex 라 .empty 가 내용 폭만큼만 차지해 왼쪽에 붙었습니다
   — 865px 카드에서 문구가 좌측 25px 에서 시작하고 오른쪽 400px 이 비어,
   같은 제품 안에서 '비어 있음' 의 생김새가 세 가지가 됐습니다. */
.rstrip .empty{flex:1}
.rstrip{flex:1;min-height:0;max-height:420px;overflow:auto;padding:10px 14px 14px;
  display:flex;gap:14px;align-items:flex-start}
.rstrip .empty{color:var(--ink3);font-size:12px;padding:26px 4px;line-height:1.8}
/* 고정폭 열이라 노선이 2~3개면 카드 폭의 70% 가 빈 채로 남았습니다.
   많으면 예전처럼 좁아지고(가로 스크롤 유지), 적을 때만 벌어집니다. */
.rline{flex:1 1 150px;min-width:132px;max-width:280px}
/* 노선 머리 — 버스 아이콘 + 번호. 유형별 색은 실제 버스 도색 관례를 따른다. */
.rline .rhead{display:flex;align-items:center;gap:6px;margin-bottom:8px;position:sticky;top:0;
  background:var(--card);padding-bottom:4px;z-index:1}
.rline .rno{font-weight:700;font-size:12px;color:var(--ink);letter-spacing:-.01em}
.rline .rkind{font-size:10px;color:var(--ink3);border:1px solid var(--line);
  border-radius:999px;padding:1px 6px;white-space:nowrap}
.rline svg.bus{flex:0 0 auto}
/* 정류장 목록 — 왼쪽 세로선에 점을 꿴 형태 */
.rstops{list-style:none;margin:0;padding:0;position:relative}
.rstops li{position:relative;padding:0 0 0 16px;min-height:26px;display:flex;align-items:center}
/* 세로선: 첫 점부터 마지막 점까지만 (위아래로 삐져나오지 않게) */
.rstops li::before{content:'';position:absolute;left:4.5px;top:0;bottom:0;width:2px;background:var(--line)}
.rstops li:first-child::before{top:13px}
.rstops li:last-child::before{bottom:calc(100% - 13px)}
.rstops li::after{content:'';position:absolute;left:0;top:9px;width:11px;height:11px;border-radius:50%;
  background:var(--card);border:2.5px solid var(--sel);box-sizing:border-box}
.rstops .snm{font-size:11px;color:var(--ink2);line-height:1.35;
  overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
/* 승차량이 많은 정류장은 점을 채워 구분 */
.rstops li.busy::after{background:var(--sel)}
.rstops li.busy .snm{color:var(--ink);font-weight:600}
/* 격자 밖으로 이어지는 구간임을 알리는 꼬리표 */
.rline .rtail{font-size:10px;color:var(--ink3);padding:4px 0 0 16px}

/* 노선 모드 운행정보 — 버스 번호로 검색했을 때만 (기점·종점·첫차·막차·배차·업체).
   전부 routes.csv 실측이고, 없는 값은 0 이 아니라 '자료 없음' 으로 나옵니다. */
/* 바탕은 --bg 입니다. --brand-soft 를 깔았더니 라이트에서 --ink3 라벨 대비가
   4.49:1 로 AA(4.5) 를 아슬하게 못 넘었습니다 — --ink3 는 --bg(#EEF2F7) 위에서
   4.6:1 이 되도록 고른 색이라 그 바탕으로 돌려놓으면 맞습니다. 게다가 이 띠는
   지표를 나르는 캔버스지 크롬이 아니라 브랜드 틴트를 입을 자리도 아닙니다. */
.rinfo{display:flex;flex-wrap:wrap;gap:8px 22px;padding:11px 14px;
  border-bottom:1px solid var(--line);background:var(--bg)}
.rinfo .ri{display:flex;flex-direction:column;gap:2px;min-width:0}
.rinfo .rk{font-size:10px;color:var(--ink3);letter-spacing:.02em}
.rinfo .rv{font-size:12.5px;font-weight:600;color:var(--ink);
  font-variant-numeric:tabular-nums;word-break:keep-all}
/* 값이 없을 때. 숫자와 같은 무게로 쓰면 실측처럼 읽히므로 눌러 둡니다. */
.rinfo .rv.none{font-weight:500;color:var(--ink3)}
/* 번호는 같은데 아예 다른 노선(4쌍). 줄을 끊어 아래에 답니다. */
.rinfo .rsib{flex:0 0 100%;display:flex;flex-wrap:wrap;align-items:center;gap:6px;
  padding-top:8px;border-top:1px dashed var(--line)}
.rinfo .rsib button{font:inherit;font-size:11.5px;font-weight:600;cursor:pointer;
  padding:3px 9px;border-radius:999px;border:1px solid var(--line);
  background:var(--card);color:var(--ink2)}
.rinfo .rsib button:hover{border-color:var(--brand);color:var(--brand-ink)}
/* 노선 한 벌을 여러 열로 쪼갤 때 열마다 붙는 구간 표시(1–20번째, 21–40번째 …).
   ⚠️ .seg 열은 '버스 한 대' 가 아니라 '한 노선의 이어지는 구간' 입니다. 격자
   모드의 노선 열과 같은 무게로 두면 버스 목록처럼 읽혀서("같은 버스인데 왜
   여러 개냐"는 실제 질문을 받았습니다) 머리를 눌러 앉힙니다. */
.rline .rseg{font-size:10px;color:var(--ink3);font-variant-numeric:tabular-nums}
.rline.seg .rhead{font-weight:500;padding-bottom:2px}
/* 정류장 순번. 열이 갈려도 20 다음이 21 로 이어지는 것이 눈에 보여야 합니다. */
.rstops .sno{flex:0 0 auto;min-width:2.1em;font-size:9.5px;color:var(--ink3);
  font-variant-numeric:tabular-nums;text-align:right;margin-right:5px}
.rstops li.busy .sno{color:var(--ink2)}

/* 격자 모드의 노선 머리는 버튼입니다 — 누르면 그 버스 전 구간으로 넘어갑니다.
   button 기본 스타일을 걷어 .rhead 생김새를 그대로 유지합니다. */
button.rhead{width:100%;border:0;background:var(--card);cursor:pointer;
  font:inherit;color:inherit;text-align:left;padding:0 0 4px;border-radius:6px}
button.rhead:hover .rno{color:var(--brand-ink);text-decoration:underline}
button.rhead:hover .rkind{border-color:var(--brand)}

/* 노선 모드에서만 줄을 감쌉니다 — 방향 머리가 줄을 끊어야 하기 때문입니다.
   격자 모드는 종전대로 한 줄에 늘어놓고 가로 스크롤합니다. */
.rstrip.routemode{flex-wrap:wrap}
/* 방향 머리 — flex-basis 100% 로 줄을 끊습니다. 색 조각은 지도의 그 방향 선과
   같은 색이라, 카드와 지도를 눈으로 이어 볼 수 있습니다. */
.rstrip .rleg0,.rstrip .rleg1{flex:0 0 100%;display:flex;align-items:center;gap:7px;
  font-size:11.5px;font-weight:700;color:var(--ink);margin:2px 0 -2px}
.rstrip .rleg0 i,.rstrip .rleg1 i{width:14px;height:3px;border-radius:2px;flex:0 0 auto}
.rstrip .rleg0 i{background:var(--c-board)}
.rstrip .rleg1 i{background:var(--c-alight)}
.rstrip .rleg0 b,.rstrip .rleg1 b{font-weight:600;font-size:10.5px;color:var(--ink3)}
/* 정류장 점도 방향 색을 따릅니다 — 열이 어느 방향 것인지 머리까지 안 올라가도 압니다 */
.rline.d0 .rstops li::after{border-color:var(--c-board)}
.rline.d0 .rstops li.busy::after{background:var(--c-board)}
.rline.d1 .rstops li::after{border-color:var(--c-alight)}
.rline.d1 .rstops li.busy::after{background:var(--c-alight)}

/* ------------------------------------------------------- 12. 반응형 */
@media (max-width:1020px){
  .mapcard,.routecard,.sidecard,.sc-card,.st-card,.span4,.span5,.span6,.span7,.span8{grid-column:span 12}
  /* 한 줄로 쌓이는 폭에서는 명시적 행 배치를 풀어야 순서대로 흐릅니다 */
  .dashgrid .mapcard,.dashgrid .routecard,.dashgrid .sidecard{grid-row:auto}
  /* 시뮬레이션의 조작 루프는 '격자 클릭 → 예산 게이지·배치 목록 확인' 입니다.
     한 줄로 쌓이면 그 둘이 지도 밖으로 나가 클릭할 때마다 스크롤 왕복을 해야
     합니다(대시보드의 Top10 은 참고 목록이라 성격이 다릅니다).
     이 한 쌍만 881px 까지 2열을 유지합니다 — 1020px 에서 지도 569px·패널 403px. */
  .simgrid .sidecard{position:static}
  /* 한 줄로 쌓이면 지도 옆이 아니라 아래에 오므로 붙일 이유가 없습니다 */
  .simgrid .sidecard{position:static}
  /* 2열로 접히면 3·4번째가 아랫줄로 갑니다. 왼쪽 괘선을 1·3번에서 걷어내고
     아랫줄에는 윗줄과 가르는 가로 괘선을 답니다 — 띠 구조가 유지됩니다. */
  .kpis{grid-template-columns:1fr 1fr}
  .kpi:nth-child(odd){border-left:0}
  .kpi:nth-child(n+3){border-top:1px solid var(--line)}
  .t10{max-height:380px}
}
@media (max-width:560px){
  /* 2열로 접히면 3·4번째가 아랫줄로 갑니다. 왼쪽 괘선을 1·3번에서 걷어내고
     아랫줄에는 윗줄과 가르는 가로 괘선을 답니다 — 띠 구조가 유지됩니다. */
  .kpis{grid-template-columns:1fr 1fr}
  .kpi:nth-child(odd){border-left:0}
  .kpi:nth-child(n+3){border-top:1px solid var(--line)}
  h1{font-size:18px}
  .periods{width:100%;justify-content:space-between}
  .wrap{padding:12px 12px 24px}
  .topnav .tn-in{padding:8px 12px}
  .topnav{--rail-pad:12px}   /* 위 패딩과 함께 — 주황 레일이 CI 끝에서 끊기도록 */
  .sheet{max-height:94vh}
}


/* ===========================================================================
   13. 한 줄 브리핑
   숫자만 나열하지 않고 "어디가 문제이고 무엇을 검토할지"를 문장으로 먼저.
   ======================================================================== */
/* 예전에는 왼쪽에 3px 파란 띠를 둘렀는데, 그 '색깔 사이드탭'은 AI 가 만든
   화면의 대표적 표식이라 검출기도 잡습니다. 이 화면의 세계에서 요약문을
   구별하는 방법은 띠가 아니라 바탕입니다 — 관공서 문서의 요약 상자처럼
   브랜드 계열의 옅은 바탕을 깔고 테두리는 다른 카드와 같은 1px 로 둡니다. */
.brief{background:var(--brand-soft);border:1px solid var(--brand-line);
  border-radius:12px;padding:13px 17px;margin-bottom:12px}
.brief:empty{display:none}
.brief .brief-main{font-size:13px;line-height:1.75;color:var(--ink2)}
.brief .brief-main b{color:var(--ink);font-weight:650}
.brief .brief-act{margin-top:7px;padding-top:7px;border-top:1px dashed var(--line);
  font-size:12px;color:var(--ink2);display:flex;align-items:center;gap:7px;flex-wrap:wrap}
/* 배지 계열은 한 치수로 맞춥니다 — 10px / 2·8 / 999.
   .tag · .rec-badge · .rec-edited 가 이미 이 값이고, 여기와 .ai-tag · .dq-badge ·
   .rec-scope-tag 만 11px 이거나 여백이 1·7 / 2·7 이라 나란히 놓일 때 높이가
   어긋났습니다. 굵기(600/700)는 건드리지 않습니다 — 색 위에 흰 글자를 얹는
   배지(.rec-badge · .dq-badge)가 700 인 것은 10px 에서 흰 글자가 얇아 보이는 것을
   막는 장치라, 크기 불일치와 달리 이유가 있습니다. */
.brief .brief-tag{font-size:10px;font-weight:600;letter-spacing:.03em;border-radius:999px;
  padding:2px 8px;background:var(--tag-eff-bg);color:var(--tag-eff-fg)}
.brief .brief-dim{color:var(--ink3);font-size:11px}

/* ===========================================================================
   14. 지도 기호 범례
   색 범례만으로는 흰 점·선·삼각형이 무엇인지 알 수 없어 함께 표기합니다.
   ======================================================================== */
.legendbar .lg-syms{width:100%;display:flex;flex-wrap:wrap;gap:4px 16px;
  margin-top:2px;padding-top:8px;border-top:1px dashed var(--line)}
.legendbar .lg-sym{display:inline-flex;align-items:center;gap:5px;
  font-size:11px;color:var(--ink3);white-space:nowrap}
.legendbar .lg-sym svg{overflow:visible;flex:none}
/* ::after 확장만으로는 형제 요소가 위에 얹혀 판정을 뺏깁니다 — 이 자리는
   여백이 있으므로 패딩으로 직접 24px 높이를 만듭니다. */
.legendbar .lg-clear{font-size:10px;color:var(--sel);text-decoration:underline;
  display:inline-flex;align-items:center;min-height:24px;padding:0 8px;
  border-radius:6px;line-height:1}
.legendbar .lg-clear:hover{background:var(--selbg)}

/* ===========================================================================
   15. 권역별 요약표
   공무원은 격자 번호가 아니라 읍면동 단위로 일합니다.
   ======================================================================== */
.rgtbl{width:100%}
.rgtbl th{cursor:pointer;user-select:none;white-space:nowrap}
.rgtbl th:hover{color:var(--ink2)}
.rgtbl th.on{color:var(--ink);font-weight:700}
.rgtbl tbody tr{cursor:pointer}
.rgtbl tbody tr:hover{background:var(--selbg)}
.rgtbl tbody tr.on{background:var(--selbg);box-shadow:inset 2px 0 0 var(--sel)}
.rgtbl td.rg-name{font-family:inherit;color:var(--ink);font-weight:600;font-size:12px}
.rgtbl td.rg-need{white-space:nowrap}
.rgtbl .rg-bar{display:inline-block;width:44px;height:5px;border-radius:3px;
  background:var(--track);position:relative;overflow:hidden;margin-right:6px;vertical-align:1px}
/* Top10 막대와 같은 이유로 --mi5 대신 --bad 를 씁니다 — 지도 범례의 MI 구간
   색과 안 부딪히면서, 흐려서 안 보인다는 제보에 맞춰 불투명도도 올립니다. */
.rgtbl .rg-bar i{position:absolute;inset:0 auto 0 0;border-radius:2px;background:var(--bad);opacity:.85}
.rgtbl .rg-need b{font-weight:700;color:var(--ink)}
/* 공용 규칙(td:nth-child(2){text-align:left})은 '격자 데이터 표'(2열=권역명)
   기준이라, 2열이 숫자인 이 표에서는 자릿수가 어긋납니다. */
.rgtbl th:nth-child(2),.rgtbl td:nth-child(2){text-align:right}
.rgtbl .rg-zero{color:var(--ink3)}   /* opacity 곱을 걷어냄 — 0 은 '조치 불필요' 라는 의미 있는 값입니다 */

/* ===========================================================================
   16. 용어 도움말 버튼
   지표 이름 옆의 물음표. 눌러도(터치) 마우스를 올려도 설명이 뜹니다.
   ======================================================================== */
.help{position:relative;display:inline-flex;align-items:center;justify-content:center;
  width:14px;height:14px;margin-left:4px;vertical-align:1px;
  border:1px solid var(--line);border-radius:50%;
  font-size:9px;font-weight:700;line-height:1;color:var(--ink3);
  flex:none}
/* 보이는 원은 14px 그대로 두고 **누를 수 있는 면적만** 24px 로 넓힙니다.
   지표 이름 옆에 붙는 자리라 원 자체를 키우면 글줄이 밀립니다.
   WCAG 2.2 의 Target Size(Minimum)가 24×24 CSS px 인데, 14px 이면 마우스로도
   자주 빗나갑니다 — 화면에 ? 가 20개 넘게 깔려 있어 체감이 큽니다. */
.help::after{content:"";position:absolute;left:50%;top:50%;
  width:24px;height:24px;transform:translate(-50%,-50%)}
.help:hover{background:var(--sel);border-color:var(--sel);color:#fff}

@media (max-width:1020px) and (min-width:881px){
  .simgrid .mapcard{grid-column:span 7}
  .simgrid .sidecard{grid-column:span 5}
}

@media (max-width:560px){
  /* 오른쪽에 열이 더 있다는 단서가 없어 '주 조치'(유일한 결론 열)를 못 봅니다 */
  .tblwrap{background:linear-gradient(to left,rgba(0,0,0,.10),transparent 24px) right/24px 100% no-repeat}
  /* 폭에 비례해 통째로 줄면 격자·지명이 판별 불가라, 축소 대신 가로 패닝을 씁니다 */
  .mapbox{overflow-x:auto;overflow-y:hidden}
  .mapsvg{min-width:620px}
  .zctl{position:sticky;left:10px}
  .legendbar .lg-syms{gap:4px 12px}
  .brief .brief-main{font-size:12px}
}


/* ===========================================================================
   17. 읍면동 경계선
   실제 행정경계(SGIS)를 격자 위에 얹어, 어디까지가 어느 동인지 보이게 합니다.
   ======================================================================== */
/* ⚠️ vector-effect:non-scaling-stroke 를 쓰면 stroke-width 가 **이미 화면 픽셀**입니다.
   여기에 calc(1.1px/var(--zk)) 를 또 걸어 두면 두 번 나눠져, 확대할수록 선이
   사라집니다 — 실측 화면 굵기 1배 1.100px · 3.8배 0.288 · 7.5배 0.148 ·
   14.5배 0.076px 이고 여기에 stroke-opacity .5 까지 곱해집니다.
   바로 위 설명("어디까지가 어느 동인지 보이게 합니다")과 정반대로, 들여다보려고
   확대하면 그때 없어졌습니다. 굵기는 고정값으로 둡니다.
   (전체 배율에서는 --zk 가 1 이라 예전과 같은 1.1px 이므로 첫 화면은 안 바뀝니다.
    같은 함정: 아래 .area-box 는 non-scaling-stroke 에 stroke-width:1.5 로 올바릅니다.) */
.dongline{fill:none;stroke:var(--ink2);stroke-width:1.1px;stroke-opacity:.5;
  stroke-linejoin:round;pointer-events:none;vector-effect:non-scaling-stroke}
/* 권역 요약에서 고른 읍면동 — 격자별 테두리 대신 이미 있는 경계선 하나를
   굵게 세운다. --ink 라 라이트에선 검게, 다크에선 밝게 자동으로 맞는다. */
.dongline.active{stroke:var(--ink);stroke-width:2.6px;stroke-opacity:.9}


/* ===========================================================================
   18. 추천 배치안
   위치 선정은 최적화 알고리즘이 하고, 근거 문장을 함께 보여 줍니다.
   ======================================================================== */
.rec-hint{font-size:11px;color:var(--ink3);line-height:1.55;margin-top:6px}
.rec-loading{display:flex;align-items:center;gap:8px;padding:12px 14px;
  font-size:11px;color:var(--ink3)}
.rec-loading .spin{width:14px;height:14px;border-width:2px;flex:none}

.rec-box{margin:0 14px 12px;padding:10px 12px;border:1px solid var(--sel);
  border-radius:9px;background:var(--selbg)}
.rec-head{display:flex;align-items:center;gap:7px;flex-wrap:wrap;
  font-size:11px;color:var(--ink2);font-weight:600}
.rec-badge{font-size:10px;font-weight:700;letter-spacing:.03em;border-radius:999px;
  padding:2px 8px;background:var(--sel);color:#fff}
.rec-edited{font-size:10px;font-weight:600;border-radius:999px;padding:2px 8px;
  background:var(--tag-add-bg);color:var(--tag-add-fg)}
.rec-nums{display:flex;flex-wrap:wrap;gap:4px 14px;margin-top:7px;
  font-size:11px;color:var(--ink3)}
.rec-nums b{color:var(--ink);font-size:13px;font-weight:650;
  font-variant-numeric:tabular-nums}
.rec-note{margin-top:7px;padding-top:7px;border-top:1px dashed var(--line);
  font-size:11px;color:var(--ink3);line-height:1.6}

/* 배치 목록에서 추천 항목 구분 */
.plist li.from-ai{border-color:var(--sel);background:var(--selbg);background:color-mix(in srgb,var(--selbg) 55%,var(--bg))}
.plist .ai-tag{display:inline-block;margin-left:6px;font-size:10px;font-weight:600;
  border-radius:999px;padding:2px 8px;background:var(--sel);color:#fff;vertical-align:1px}
.plist .why{display:block;margin-top:3px;font-size:11px;line-height:1.5;
  color:var(--ink3);font-family:inherit;white-space:normal}
.plist li{align-items:start}
.plist .ic{padding-top:2px}


/* ===========================================================================
   19. 데이터 품질 · 가정값 표시
   무엇이 실측이고 무엇이 추정·가정인지 화면에서 구분되게 합니다.
   실측인 척 내보내면 검증 단계에서 그대로 지적당합니다.
   ======================================================================== */
.dq-badge{display:inline-block;font-size:10px;font-weight:700;letter-spacing:.03em;
  border-radius:999px;padding:2px 8px;margin-right:5px;vertical-align:1px;
  background:var(--tag-add-bg);color:var(--tag-add-fg)}
.dq{display:flex;flex-wrap:wrap;gap:4px 16px;align-items:center;
  margin-bottom:8px;padding-bottom:8px;border-bottom:1px dashed var(--line)}
.dq:empty{display:none}
.dq-item{font-size:11px;color:var(--ink3);display:inline-flex;align-items:center}
.cost-assumed{display:inline-flex;align-items:center;color:var(--tag-add-fg);font-weight:600}
#costNote b{color:var(--ink2);font-weight:600}
#costNote i{font-style:normal;opacity:.75}

/* ===========================================================================
 * [20] 비용 비교 기준 표기 (추천 박스)
 *   추천이 왜 이런 조합인지 묻는 질문에 대한 답을 화면에 남겨 둡니다.
 *   기존 .rec-note 를 그대로 쓰고 톤만 낮춥니다. 새 색은 만들지 않습니다.
 * ======================================================================== */
.rec-basis{color:var(--ink3);font-size:11px;line-height:1.55}
.rec-basis b{color:var(--ink);font-weight:600}
.rec-basis .help{margin-left:5px;vertical-align:baseline}

/* ===========================================================================
 * [21] 추천 대안 비교 (목적별 시나리오)
 *   같은 알고리즘에 목적만 바꿔 끼운 결과를 나란히 보여 줍니다.
 *   기존 .rec-* 규칙과 토큰을 그대로 쓰고 새 색은 만들지 않습니다.
 * ======================================================================== */
.rec-alt{margin-top:9px;padding-top:9px;border-top:1px dashed var(--line)}
.rec-alt-head{font-size:11px;font-weight:600;color:var(--ink);margin-bottom:6px}
/* 사이드 패널은 좁습니다. 표가 넘칠 여지를 남기지 않습니다. */
.rec-alt-wrap{max-width:100%;overflow-x:auto}
.rec-alt-tbl{width:100%;table-layout:fixed;border-collapse:collapse;font-size:11px}
.rec-alt-tbl th{text-align:right;font-weight:500;color:var(--ink2);
  padding:3px 4px;border-bottom:1px solid var(--line);white-space:nowrap}
.rec-alt-tbl th:first-child,.rec-alt-tbl td:first-child{text-align:left;width:42%}
.rec-alt-tbl td{text-align:right;padding:5px 4px;color:var(--ink);
  border-bottom:1px solid var(--line);font-variant-numeric:tabular-nums;
  overflow-wrap:anywhere}
.rec-alt-tbl tbody tr:last-child td{border-bottom:0}
.rec-alt-tbl tr.is-on td{background:var(--selbg);font-weight:600}
.rec-alt-sub{display:block;margin-top:1px;font-size:10px;font-weight:400;
  color:var(--ink2);white-space:normal}
.rec-tab{background:none;border:0;padding:0;font:inherit;color:var(--ink2);
  cursor:pointer;text-align:left;border-bottom:1px dotted var(--line);
  white-space:normal;overflow-wrap:anywhere}
.rec-tab:hover{color:var(--ink)}
.rec-alt-tbl tr.is-on .rec-tab{color:var(--ink);font-weight:600;
  border-bottom:0;cursor:default}
.rec-top{display:block;font-style:normal;font-size:10px;font-weight:500;
  color:var(--ink2)}
.rec-by{color:var(--ink2);font-size:11px;line-height:1.55}
.rec-by b{color:var(--ink);font-weight:600}

/* ===========================================================================
 * [22] 다크모드 대비 보정
 *   --sel 은 라이트에서 공식 파랑(#0054A6), 다크에서 밝은 파랑(#5AA6EA)입니다.
 *   그 위에 흰 글자를 고정해 두면 다크에서 대비가 3.13:1 까지 떨어집니다
 *   (WCAG AA 기준 4.5:1). 밝은 배경에는 어두운 글자를 얹어야 합니다.
 *   기존 규칙은 건드리지 않고 다크에서만 글자색을 덮어씁니다. → 6.03:1
 * ======================================================================== */

/* 공식 CI 는 다크 배경에서 못 읽습니다 — 파랑 #0054A6 이 다크 카드 위에서
   2.25:1 입니다(주황은 5.68:1 로 살아남지만 로고는 한 덩어리라 의미 없음).
   로고 자체를 밝게 고치는 것은 CI 변형이라 하면 안 되므로, 다크에서만
   흰 판을 깔아 규정대로 밝은 바탕 위에 얹습니다. */
/* ⚠️ 아래 흰 판의 좌우 패딩(9px×2 = 18px)만큼 CI 가 넓어지므로, 주황 레일이
   끊기는 지점(--rail-plate)도 같이 밀어야 합니다. 두 값은 반드시 함께 고칩니다 —
   판만 넓히면 레일이 CI 중간에서 끊기고, 레일만 밀면 CI 밖으로 삐져나옵니다. */
:root[data-theme="dark"] .brand .ci{background:#fff;padding:5px 9px;border-radius:7px;box-sizing:content-box}
:root[data-theme="dark"] .topnav{--rail-plate:18px}
@media (prefers-color-scheme:dark){
  :root:where(:not([data-theme="light"])) .brand .ci{
    background:#fff;padding:5px 9px;border-radius:7px;box-sizing:content-box}
  :root:where(:not([data-theme="light"])) .topnav{--rail-plate:18px}
}
/* 추천 박스 안의 설명글은 10.5px 로 작습니다. --ink3 로는 다크에서 3.97:1 이라
   읽기 어렵습니다. 전역 토큰은 그대로 두고 이 박스 안에서만 한 단계 올립니다. */
.rec-box .rec-note{color:var(--ink2)}

:root[data-theme="dark"] .rec-badge,
:root[data-theme="dark"] .plist .ai-tag,
:root[data-theme="dark"] .btn.primary,
:root[data-theme="dark"] .simtool.on,
:root[data-theme="dark"] .cm-b.me,
:root[data-theme="dark"] .help:hover{color:var(--bg)}
/* 카카오 타일은 원래 밝은 지도라, 테마와 무관한 brightness(1.04)를 그대로 두면
   다크에서 확대했을 때 배경만 하얗게 뜹니다(육지·바다는 투명해진 상태라 더 심함). */
:root[data-theme="dark"] .kkdetail .kakaomap{
  filter:saturate(.28) brightness(.55) contrast(1.12)}
@media (prefers-color-scheme:dark){
  :root:where(:not([data-theme="light"])) .rec-badge,
  :root:where(:not([data-theme="light"])) .plist .ai-tag,
  :root:where(:not([data-theme="light"])) .btn.primary,
  :root:where(:not([data-theme="light"])) .simtool.on,
  :root:where(:not([data-theme="light"])) .cm-b.me,
  :root:where(:not([data-theme="light"])) .help:hover{color:var(--bg)}
  /* 위 명시 테마(:root[data-theme="dark"])와 같은 보정을 '자동' 에도 겁니다 */
  :root:where(:not([data-theme="light"])) .kkdetail .kakaomap{
    filter:saturate(.28) brightness(.55) contrast(1.12)}
}

/* ===========================================================================
 * [23] 지도 패널 높이 고정 + 스티키
 *   .grid{align-items:stretch} 때문에 sidecard 가 길어지면 mapcard 도 같이 늘어납니다.
 *   .mapbox{flex:1} 이 늘어난 공간을 차지하지만 SVG 는 height:auto(종횡비 유지)라
 *   아래에 빈 공간이 생깁니다.
 *   align-self:start  — sidecard 높이에 맞춰 늘어나지 않습니다.
 *   flex:0 0 auto     — mapbox 가 flex:1 로 비어 있는 공간을 차지하지 않습니다.
 *   position:sticky   — 추천 목록이 길어져 스크롤해도 지도가 화면에 남습니다.
 * ======================================================================== */
.mapcard{align-self:start}
.mapcard .mapbox{flex:0 0 auto}

/* 저장된 시나리오 — 지도 아래 가로 그리드 */
.scen-floor{border-top:1px solid var(--line);padding:10px 14px 14px}
.scen-floor-hd{display:flex;align-items:baseline;gap:10px;margin-bottom:8px}
.scen-floor-hd b{font-size:13px;font-weight:650}
/* 저장 개수만큼 지도 카드가 통째로 길어져(12개면 지도 카드 945→1,278px)
   아래의 '수단별 소요액'·'정책 판단 요약' 이 화면 밖으로 밀려났습니다.
   두 줄까지만 보이고 그 아래는 이 목록 안에서만 스크롤합니다. */
.scen-floor-list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;
  max-height:216px;overflow-y:auto;overscroll-behavior:contain}
.scen-floor-list .empty{grid-column:1/-1;padding:14px 0;text-align:center}
.scen-floor-list .scen .smeta{white-space:normal}
/* 3열 고정은 좁은 화면에서 카드가 ~100px 로 쪼그라듭니다 */
@media (max-width:1020px){.scen-floor-list{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:560px){.scen-floor-list{grid-template-columns:1fr}}

/* ===========================================================================
 * [24] 시나리오 카드 접근성 구조 + 배치 목록 요약
 *   .scen 이 button 하나였을 때는 삭제 ×(span)가 그 안에 중첩되어
 *   키보드로 삭제할 수 없었습니다. 카드 = div, 불러오기·삭제 = 각각 button.
 *   기존 .scen/.snm/.smeta/.sdel 규칙은 자손 선택자라 그대로 적용됩니다.
 * ======================================================================== */
.scen .sload{display:block;width:100%;min-width:0;text-align:left;padding:0;
  background:none;border:0;font:inherit;color:inherit;cursor:pointer}
.plist-head{display:flex;justify-content:space-between;gap:10px;
  padding:0 14px 6px;font-size:11px;color:var(--ink2)}
.plist-head b{font-variant-numeric:tabular-nums}

/* 배치 마커 수량 표기는 이제 기호 안의 .pmk-n 이고 fill 이 var(--card) 라
   라이트·다크 모두 대비가 확보됩니다 — 별도 다크 보정이 필요 없습니다. */

/* ===========================================================================
 * [25] 추천 범위 칩
 *   대시보드에서 격자를 들고 넘어오면 추천 범위가 그 읍면동으로 좁혀집니다.
 *   어느 범위로 추천할지 버튼 위에 항상 보이게 하고, 한 번에 전환합니다.
 * ======================================================================== */
.rec-scope{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:7px;
  font-size:11px;color:var(--ink2)}
.rec-scope:empty{display:none}
.rec-scope b{font-weight:650;color:var(--ink)}
.rec-scope .rs-btn{font-size:11px;padding:2px 8px;border:1px solid var(--line);
  border-radius:999px;color:var(--ink2)}
.rec-scope .rs-btn:hover{border-color:var(--sel);color:var(--ink)}
.rec-scope-tag{display:inline-block;font-size:10px;font-weight:600;
  border:1px solid var(--sel);color:var(--sel);border-radius:999px;
  padding:2px 8px;margin-left:6px;vertical-align:1px}

/* ===========================================================================
 * [26] 지도 확대·이동 컨트롤
 *   동 단위 추천처럼 좁은 지역은 전체 뷰에서 배치 기호가 겹쳐 안 보입니다.
 *   휠 줌·드래그 이동은 map.js 가 처리하고, 여기는 버튼과 커서만 담당합니다.
 *   우상단에는 나침반이 있어 버튼은 좌상단에 둡니다.
 * ======================================================================== */
.zctl{position:absolute;left:10px;top:10px;display:flex;flex-direction:column;gap:4px;z-index:5}
.zctl button{width:26px;height:26px;border:1px solid var(--line);border-radius:7px;
  background:var(--card);background:color-mix(in srgb,var(--card) 92%,transparent);color:var(--ink2);
  font-size:14px;line-height:1;display:flex;align-items:center;justify-content:center}
.zctl button:hover{border-color:var(--sel);color:var(--ink)}
.zctl [data-z="reset"]{display:none;width:auto;height:22px;padding:0 7px;font-size:11px}
.zctl.zoomed [data-z="reset"]{display:flex}
/* 현재 확대 비율 — 화성시 전체가 보이는 상태가 100%, 최대 12,000% */
/* 세로 flex 의 교차축 stretch 때문에 칩 폭이 +/- 버튼 26px 에 묶여 '100%'가
   겹쳐 뭉갰습니다. 내용 폭만큼 늘어나게 합니다. */
.zctl{align-items:flex-start}
.zpct{min-width:26px;padding:2px 4px;border:1px solid var(--line);border-radius:6px;
  background:color-mix(in srgb,var(--card) 92%,transparent);color:var(--ink3);
  font-size:10px;line-height:1.5;text-align:center;font-variant-numeric:tabular-nums;
  pointer-events:none;white-space:nowrap}
.mapsvg.panning{cursor:grabbing}


/* ===========================================================================
 * [27] 분석 영역 (드래그 선택)
 *   바탕화면에서 파일을 끌어 고르듯 지도에 사각형을 그려, 그 안에서만
 *   배치하고 그 안의 결과만 집계합니다. 영역 밖 격자는 뒤로 물립니다.
 * ======================================================================== */
.mapsvg.areamode{cursor:crosshair}
.mapsvg.areamode .cells rect{cursor:crosshair}
[data-arearect]{pointer-events:none}
.area-box{fill:var(--sel);fill-opacity:.07;stroke:var(--sel);stroke-width:1.5;
  vector-effect:non-scaling-stroke;rx:2}
.area-box.dragging{stroke-dasharray:5 3}
.cells rect.outarea{opacity:.3}
/* 영역 지정 중인 버튼 · 현재 영역 표시 */
.minib.on{background:var(--sel);border-color:var(--sel);color:var(--bg);font-weight:600}
.rec-scope .rs-area{font-size:11px;color:var(--ink2);border:1px solid var(--sel);
  border-radius:999px;padding:1px 8px}
.rec-scope .rs-area b{color:var(--ink)}

/* 영역을 고르는 중 — 고른 칸은 또렷하게(밖은 .outarea 로 물러납니다) */
.cells rect.indraft{filter:saturate(1.45) brightness(1.05)}

/* ===========================================================================
 * [28] 버튼 상태 — 호버 · 눌림 · 포커스
 *   이 화면의 버튼에는 그동안 :active 규칙이 **하나도 없었습니다**(0건).
 *   눌러도 호버 그대로라 손이 "눌렸다"를 확인할 방법이 없었습니다. 세 상태를
 *   다 갖추는 것이 도구가 완성돼 보이는 가장 값싼 조건이고, 반대로 눌림이 없는
 *   것은 시제품의 대표적인 표시입니다.
 *
 *   방법은 두 가지뿐이고 새 색은 만들지 않습니다.
 *     ① 바탕을 한 단계 더 앉힙니다 — --sel 에서 파생하므로 테마를 따라갑니다.
 *     ② 윗변에 얇은 안쪽 그림자를 넣어 면이 들어간 것처럼 보이게 합니다.
 *        띄우는 그림자가 아니라 눌린 면의 경계라 "그림자는 모달·툴팁에만"
 *        이라는 규칙과 부딪히지 않습니다.
 *
 *   이 구획을 파일 맨 끝에 두는 이유: 위의 개별 규칙들과 특이도가 같은 것이
 *   많아, 순서로 이기게 해서 계열 전체가 한 번에 같은 상태를 갖게 합니다.
 *
 *   .help(물음표)는 뺐습니다 — 호버가 이미 --sel 채움이라 눌림이 그보다 약해지고,
 *   동작도 '누름' 이 아니라 설명을 띄우는 것이라 성격이 다릅니다.
 * ======================================================================== */

/* ── 윤곽·투명 버튼 ─────────────────────────────────────────────────────── */
.btn:active:not(:disabled),.minib:active:not(:disabled),
.lbtn:active,.tbtn:active,.simtool:active,.pbtn:active,.tn-menu:active,
.xbtn:active,.zctl button:active,.plist .del:active,.scen .sdel:active,
.legendbar .lg-clear:active,.rec-scope .rs-btn:active,
.tn-drawer a:active,.tn-drawer .tn-item:active{
  background:var(--selbg);
  background:color-mix(in srgb,var(--sel) 20%,transparent);
  box-shadow:inset 0 1px 2px rgba(0,0,0,.13)}

/* ── 채워진 버튼 ────────────────────────────────────────────────────────────
   바탕이 이미 --sel 이라 위 수법이 안 통합니다. 한 단계 앉은 --brand-ink 로
   바꾸고 안쪽 그림자를 조금 더 줍니다. 발행 버튼의 윗변 하이라이트는 눌리는
   동안 사라집니다 — 빛을 받던 면이 들어갔는데 하이라이트가 남으면 안 됩니다. */
.btn.primary:active:not(:disabled),.pbtn.on:active,.simtool.on:active,
.minib.on:active,.topnav.menu-open .tn-menu:active{
  background:var(--brand-ink);border-color:var(--brand-ink);
  box-shadow:inset 0 1px 3px rgba(0,0,0,.22)}
/* 지도 레이어의 활성 상태만 --ink 채움입니다(지도 범례의 파랑과 부딪히지 않게
   일부러 브랜드색을 피한 자리라 그대로 둡니다). 색은 유지하고 눌림만 얹습니다. */
.lbtn.on:active{background:var(--ink);box-shadow:inset 0 1px 3px rgba(0,0,0,.3)}

/* ── 목록 행 ────────────────────────────────────────────────────────────────
   우선순위 Top10 · 권역별 요약 · 저장된 시나리오는 버튼이 아니라 행이라
   면이 들어가는 그림이 어울리지 않습니다. 바탕만 한 단계 앉힙니다. */
.trow:active,.rgtbl tbody tr:active,.scen .sload:active{
  background:var(--selbg);
  background:color-mix(in srgb,var(--sel) 14%,transparent)}

/* ── 상태 전환 ──────────────────────────────────────────────────────────────
   호버·눌림이 즉시 튀지 않고 넘어가게 합니다. 0.12초는 "부드럽다"가 아니라
   "끊기지 않는다" 를 만드는 값입니다 — 이보다 길면 도구가 굼떠 보입니다.
   여기 나열한 것 중 일부(.tn-menu·.pbtn·.tn-drawer a)는 앞에서 이미 비슷한
   전환을 갖고 있었는데, 값과 속성이 제각각이라 한 줄로 모읍니다. */
.btn,.minib,.lbtn,.tbtn,.simtool,.pbtn,.tn-menu,.tn-drawer a,.tn-drawer .tn-item,
.xbtn,.zctl button,.plist .del,.scen .sdel,.legendbar .lg-clear,
.rec-scope .rs-btn,.rec-tab,.trow,.scen,.scen .sload,.rgtbl tbody tr,.rgtbl th{
  transition:background-color .12s ease,border-color .12s ease,
             color .12s ease,box-shadow .12s ease}
@media (prefers-reduced-motion:reduce){
  .btn,.minib,.lbtn,.tbtn,.simtool,.pbtn,.tn-menu,.tn-drawer a,.tn-drawer .tn-item,
  .xbtn,.zctl button,.plist .del,.scen .sdel,.legendbar .lg-clear,
  .rec-scope .rs-btn,.rec-tab,.trow,.scen,.scen .sload,.rgtbl tbody tr,.rgtbl th{
    transition:none}
}
