﻿/* ============================================================================
   아파트손품 (AptSonpoom) — 글로벌 디자인 토큰 + 유틸리티
   CLAUDE.md "디자인 시스템 (CSS 토큰)" 정본 — 새 토큰 정의 X
   ========================================================================== */

:root {
    /* 컬러 — 한국 부동산 정보 사이트 (호갱노노·아실·아파트미) 톤 neutral + 단일 accent */
    --color-bg: #fafbfc;
    --color-surface: #ffffff;
    --color-surface-alt: #f5f6f8;
    --color-text: #1a1d23;
    --color-text-muted: #6b7280;
    --color-accent: #2563eb;
    --color-accent-hover: #1d4ed8;
    --color-accent-soft: #dbeafe;
    --color-border: #e5e7eb;

    /* 보조 accent — 청록 (2026-05-26 사용자 명시 승인). hue 가 파랑 accent 와 멀고
       --color-up (빨강) 과 절대 안 헷갈림. context 계열 (호재·관심·뉴스) chrome 한정. */
    --color-accent-2: #0d9488;
    --color-accent-2-hover: #0f766e;
    --color-accent-2-soft: #ccfbf1;

    /* 따뜻한 surface — narrative 섹션 배경 리듬용 (2026-05-26 승인). */
    --color-bg-warm: #fbf9f5;

    /* 한국 컨벤션 — 상승 빨강 / 하락 파랑, 채도 낮춤 */
    --color-up: #c0392b;
    --color-down: #1e6091;

    /* 실거래 핸드오프 (2026-06-03 사용자 승인) — 거래가·신고가 코랄레드 신호색.
       기존 --color-up (벽돌 빨강) 과 별개 토큰 (additive — 기존 토큰 0건 변경).
       거래가/상승칩/신고가(신) 에만 사용. 한국 상승=빨강 컨벤션 유지 (코랄도 빨강 계열). */
    --color-price: oklch(0.55 0.2 27);        /* 코랄레드 */
    --color-price-soft: oklch(0.96 0.045 27);  /* 상승 칩 배경 */

    /* 카드 그림자 (핸드오프 톤 — radius 16 카드 + hairline 외곽 결합). additive. */
    --shadow-card: 0 2px 8px rgba(20, 30, 50, 0.06), 0 0 0 1px rgba(20, 30, 50, 0.05);

    /* 반경 */
    --radius-sm: 4px;
    --radius-md: 8px;
    --radius-lg: 12px;

    /* 그림자 (Material 의 떠 있는 톤 회피 — 정보 dense hairline 위주) */
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
    --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.06);
    --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.08);

    /* 레이아웃 — header-h 66px (2026-06-03 실거래 핸드오프 nav 높이). sticky 오프셋 토큰 일관. */
    --header-h: 66px;
    --container: 1280px;
}

/* ============================================================================
   기본 / Reset
   ========================================================================== */

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
}

body {
    margin: 0;
    background: var(--color-bg);
    color: var(--color-text);
    /* 한국어 폰트 스택 — 2026-06-03 사용자 승인으로 Pretendard 웹폰트 첫 순위 (App.razor 에서 CDN 로드).
       미로드 환경은 system-ui 자연 폴백 (기존 시스템 폰트 스택 그대로 보존). */
    font-family: "Pretendard", system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif;
    font-size: 16px;
    line-height: 1.7;
    word-break: keep-all;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

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

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

h1, h2, h3, h4, h5, h6 {
    margin: 0;
    font-weight: 700;
    color: var(--color-text);
    line-height: 1.35;
}

p {
    margin: 0;
}

button {
    font-family: inherit;
}

/* ============================================================================
   공용 유틸리티
   ========================================================================== */

.container {
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: 24px;
}

.tabular {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
}

.signal-up {
    color: var(--color-up);
}

.signal-down {
    color: var(--color-down);
}

.signal-flat {
    color: var(--color-text-muted);
}

.disclaimer {
    color: var(--color-text-muted);
    font-size: 0.875rem;
    line-height: 1.7;
}

.eyebrow {
    display: inline-block;
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    color: var(--color-text-muted);
    text-transform: uppercase;
    font-weight: 600;
}

.source-line {
    font-size: 0.85rem;
    color: var(--color-text-muted);
    padding-left: 12px;
    line-height: 1.7;
}

.visually-hidden,
.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;
}

/* ============================================================================
   포커스 표준 (WCAG 2.2 §2.4.11 / §2.4.13)
   ========================================================================== */

:focus {
    outline: none;
}

:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* ============================================================================
   MudBlazor 톤 오버라이드 (CLAUDE.md MudBlazor 톤 오버라이드 룰 §§1-6)
   - MudThemeProvider 로 1차 매핑하고, 글로벌 CSS 변수도 우리 토큰으로 덮어씀
   - 새 토큰 추가 X — 기존 우리 토큰만 사용
   ========================================================================== */

:root {
    /* MudBlazor 핵심 변수 강제 매핑 */
    --mud-palette-primary: var(--color-accent);
    --mud-palette-primary-darken: var(--color-accent-hover);
    --mud-palette-primary-lighten: var(--color-accent-soft);
    --mud-palette-secondary: var(--color-accent-2);
    --mud-palette-secondary-darken: var(--color-accent-2-hover);
    --mud-palette-secondary-lighten: var(--color-accent-2-soft);
    --mud-palette-background: var(--color-bg);
    --mud-palette-surface: var(--color-surface);
    --mud-palette-background-gray: var(--color-surface-alt);
    --mud-palette-text-primary: var(--color-text);
    --mud-palette-text-secondary: var(--color-text-muted);
    --mud-palette-text-disabled: var(--color-text-muted);
    --mud-palette-divider: var(--color-border);
    --mud-palette-divider-light: var(--color-border);
    --mud-palette-lines-default: var(--color-border);
    --mud-palette-lines-inputs: var(--color-border);
    --mud-palette-action-default: var(--color-text);
    --mud-palette-action-disabled: var(--color-text-muted);

    /* 타이포 — Roboto 제거, system-ui 계열 */
    --mud-typography-default-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif;
    --mud-typography-h1-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif;
    --mud-typography-h2-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif;
    --mud-typography-h3-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif;
    --mud-typography-body1-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif;
    --mud-typography-body2-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif;
    --mud-typography-button-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif;
    --mud-typography-input-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif;
    --mud-typography-caption-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif;
    --mud-typography-overline-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif;

    /* 반경 — 우리 토큰에 맞춤 */
    --mud-default-borderradius: var(--radius-md);
}

/* MudBlazor 폰트 패밀리 글로벌 적용 */
.mud-typography,
.mud-input,
.mud-input-label,
.mud-button,
.mud-table,
.mud-data-grid {
    font-family: "Pretendard", system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif !important;
}

/* MudDataGrid hairline 톤 강제 — Material 그림자 제거 */
.mud-data-grid {
    box-shadow: none !important;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    overflow: hidden;
}

    .mud-data-grid .mud-table-head {
        background: var(--color-surface-alt);
    }

    .mud-data-grid .mud-table-cell {
        border-bottom: 1px solid var(--color-border);
    }

    .mud-data-grid .mud-table-cell.tabular,
    .mud-data-grid .mud-table-cell--tabular {
        font-variant-numeric: tabular-nums;
        font-feature-settings: "tnum";
    }

/* MudChip 활성 톤 */
.mud-chip.mud-chip-color-primary {
    background-color: var(--color-accent-soft);
    color: var(--color-accent);
}

/* ============================================================================
   Blazor 기본 (보존)
   ========================================================================== */

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "오류가 발생했습니다.";
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

/* ============================================================================
   모바일 글로벌 케이스 — content-mobile-dev 추가
   - breakpoint 43.75em (≈ 700px). 컨테이너 padding 축소 + 가로 overflow 차단.
   - 페이지 scoped CSS 가 .page-container 같은 자체 컨테이너에서 따로 처리하면
     해당 페이지는 본 글로벌 룰 영향 받지 X (scope 격리).
   ========================================================================== */

@media (max-width: 43.75em) {

    .container {
        padding-inline: 16px;
    }

    /* 모바일 가로 overflow 차단 — 부동산 사이트 dense 영역 보호 */
    html, body {
        overflow-x: hidden;
    }
}

/* ============================================================================
   info-chip — 인라인 라벨/태그 (단지명 옆, 차트 메타 라인, 거래 카드 등 재사용)
   참고 톤: InvestmentABWeb 의 Discovery 카드 칩 (옅은 회색 배경 + 작은 둥근 모서리 + 작은 padding + 1줄 인라인).
   ========================================================================== */
.info-chip {
    display: inline-flex;
    align-items: center;
    padding: 2px 8px;
    font-size: 0.72rem;
    line-height: 1.5;
    font-weight: 500;
    border-radius: var(--radius-sm);
    background: var(--color-surface-alt);
    color: var(--color-text-muted);
    white-space: nowrap;
    border: 1px solid transparent;
}

/* accent (파랑) — 관심·강조 라벨 */
.info-chip--accent {
    color: var(--color-accent);
    background: var(--color-accent-soft);
}

/* 보조 accent (청록) — 호재·뉴스·context */
.info-chip--accent-2 {
    color: var(--color-accent-2);
    background: var(--color-accent-2-soft);
}

/* 상승 (한국 컨벤션 — 빨강) */
.info-chip--up {
    color: var(--color-up);
    background: #fdebe7;
}

/* 하락 (한국 컨벤션 — 파랑) */
.info-chip--down {
    color: var(--color-down);
    background: #e3eef5;
}

/* ============================================================================
   /deals/daily — 네이티브 input[type=date] segment 시각화 차단 (2026-05-28)
   캘린더 popup 만으로 날짜 수정 정책 — 키보드 직접 편집은 razor 측 onkeydown 으로
   차단 + 시각화 (caret · text selection · segment focus 파란 배경) 도 차단해서
   "수정 가능" 처럼 보이는 오해 제거.

   ※ 글로벌 영역에 박은 사유 — Blazor scoped CSS (`*.razor.css`) 는 빌드 시 selector
      마지막에 `[b-hash]` attribute 가 박혀 input 자체에는 매칭되지만 input 의 shadow
      DOM 안 pseudo element (`::-webkit-datetime-edit-*-field`) 에는 attribute 가
      전파 안 돼 매칭이 깨졌음. 글로벌 CSS 에서 박으면 attribute 없는 selector 라 정상 작동.
   ========================================================================== */
.deals-filter__date {
    caret-color: transparent;
    user-select: none;
    -webkit-user-select: none;
}

/* Chrome/Edge — segment 영역(연/월/일) 안 caret 추가 차단. shadow DOM 안 pseudo 라 별도. */
.deals-filter__date::-webkit-datetime-edit {
    caret-color: transparent;
    user-select: none;
}

/* Chrome/Edge — segment focus 시 브라우저 기본 파란 highlight 무력화.
   직접 편집 불가한데 selected 시각으로 보이면 "수정 가능" 인상 → 배경 투명 + 본문 색 유지. */
.deals-filter__date::-webkit-datetime-edit-year-field:focus,
.deals-filter__date::-webkit-datetime-edit-month-field:focus,
.deals-filter__date::-webkit-datetime-edit-day-field:focus {
    background-color: transparent;
    color: var(--color-text);
    outline: none;
}

/* Firefox — pseudo 다르므로 별도 처리. ::selection 자체는 input 안 텍스트엔 안 먹지만
   segment 영역 외 우발적 텍스트 선택 차단용 안전망. */
.deals-filter__date::selection {
    background: transparent;
}

/* ============================================================
   M2-1.3 — 지도 탐색 (/explore) 마커 / 클러스터.
   카카오맵 CustomOverlay 의 content HTML 이라 Blazor scoped CSS 의
   [b-xxx] attribute selector 가 못 잡음 → 글로벌 정의 필수.

   WCAG 1.4.1 다중 감각 = 색 + 텍스트 (단지명·평균가) + (필요 시 테두리·아이콘).
   호갱노노식 "모두 빨강" 채택 X — 기본은 검정 텍스트, 표본·거래 상태로만 시각 분리.
   ============================================================ */

/* 단지 마커 (level <= 5, 단지 단위) — 디자인 핸드오프 (2026-06-02, design_handoff_apt_map_markers).
   구조: .em-marker(래퍼) > .em-fanrow(겹친 상태 깃발, 조건부) + .em-marker__card(흰 카드).
   카드 = 상단 가격대 컬러 라인(rail) + 단지명 + (평수 칩 · 가격 · 변동 badge) + 흰 꼬리.
   깃발(신고가/신축/거래활발)은 카드 위에 책장처럼 겹쳐 표시 + hover 시 리프트 + 전체 이름 툴팁.
   가변폭(내용 맞춤) — 단지명 길면 넓고 짧으면 좁음. 직방·호갱노노식. */

/* 마커 래퍼 — 깃발 행 + 카드를 세로로. 배경·그림자는 카드가 가짐. 좌표 앵커는 하단 중앙(카드 꼬리 끝). */
.em-marker {
    display: inline-flex;
    flex-direction: column;
    align-items: stretch;  /* 깃발 행·카드 wrap 같은 폭으로 — 깃발은 행 안에서 왼쪽 정렬 */
    font-family: inherit;
    color: var(--color-text);
    cursor: pointer;
    /* 드래그/더블클릭으로 마커 글씨(단지명·가격)가 텍스트 선택되지 않게 (사용자 요청 2026-06-01). */
    user-select: none;
    -webkit-user-select: none;
    transform-origin: bottom center;
    position: relative;
    line-height: 1.15;
    /* 흰 꼬리 높이(7px)만큼 위로 띄움 — yAnchor:1(하단=좌표)에서 꼬리 끝이 좌표를 가리키게. */
    margin-bottom: 7px;
    box-sizing: border-box;
    /* 래퍼 자체는 클릭 통과 — 깃발 행의 빈 여백(카드 폭으로 stretch된 투명 바)이 위 단지 카드를 덮어
       클릭을 가로채던 것 차단(2026-06-09 사용자 지적). 실제 클릭 대상(카드·깃발)만 아래에서 auto 로 복원.
       closest('.em-marker[data-kapt]') 는 DOM 탐색이라 pointer-events 무관 — 카드 클릭해도 마커 식별 정상. */
    pointer-events: none;
}
/* 카드(흰 박스)·깃발만 실제 클릭 대상 — 나머지(깃발행 여백·말풍선·꼬리)는 none 상속 → 뒤 마커로 통과. */
.em-marker__cardwrap {
    pointer-events: auto;
    cursor: pointer;
}
.em-fanflag {
    pointer-events: auto;
}

/* ===== 상태 깃발 — 카드 위 겹친 깃발 + hover 툴팁 (디자인 핸드오프 2026-06-02) ===== */
/* 깃발 행 — 카드 위, 왼쪽 정렬. 책장처럼 겹침(margin-left 음수). */
.em-fanrow {
    display: flex;
    align-items: flex-end;
    padding-left: 3px;
    /* 깃발-카드 거리(4px) 를 margin 이 아닌 padding 으로 — 그 빈틈도 이 마커 hit 영역에 포함시켜, 깃발↔카드
       이동 중 빈틈에서 호버가 풀려 뒤 단지가 올라오며 깃발이 가려지던 것 차단(2026-06-09 사용자 지적).
       height 27 = 깃발 23 + padding 4 (border-box) → 깃발·카드 시각 위치 불변. */
    height: 27px;
    padding-bottom: 4px;
    /* 깃발 묶음 폭만 차지(카드 폭 stretch 해제) → 깃발 오른쪽 빈 영역은 위 단지로 클릭 통과(위 단지 클릭 가능). */
    align-self: flex-start;
    /* 깃발 묶음 영역은 충돌박스 살림(none 상속 무시) — 깃발 누르러 이동하는 동안 이 마커 호버 유지. */
    pointer-events: auto;
}
/* 깃발 한 개 래퍼 — 툴팁(.em-fantip)을 clip-path 걸린 flagbody 바깥에 둬야 안 잘림. */
.em-fanflag {
    position: relative;
    margin-left: -6px;  /* 두 번째부터 책장 겹침 (앞 깃발이 위 레이어) */
    cursor: pointer;
    transition: transform 0.16s;
}
.em-fanflag:first-child {
    margin-left: 0;
}
.em-fanflag:hover {
    z-index: 30;  /* hover 깃발이 최상단 */
}
/* 깃발 몸체 — 스왈로테일(제비꼬리) 모양 + 흰 아이콘. clip-path 라 일반 border 안 먹어
   흰 외곽선(페이지 구분)은 4방향 drop-shadow 로 (디자인 핸드오프 ⚠️ — ::before z-index 방식 금지). */
.em-flagbody {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 22px;
    color: #fff;
    background: var(--fc);
    clip-path: polygon(0 0, 100% 0, calc(100% - 5px) 50%, 100% 100%, 0 100%);
    filter: drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff) drop-shadow(0 2px 2px rgba(20, 30, 50, 0.22));
    transition: transform 0.16s cubic-bezier(0.2, 0.85, 0.3, 1.1), filter 0.16s;
}
/* hover — 그 깃발이 위로 튀어오르며 그림자 강해짐 (흰 외곽선 유지). */
.em-fanflag:hover .em-flagbody {
    transform: translateY(-6px) scale(1.06);
    filter: drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff) drop-shadow(0 5px 5px rgba(20, 30, 50, 0.32));
}
/* 툴팁 — 깃발 위 다크 버블 + 아래쪽 삼각 꼬리. 평상시 숨김, hover 시 등장 + 살짝 위로 슬라이드. */
.em-fantip {
    position: absolute;
    bottom: 100%;
    left: 50%;
    margin-bottom: 7px;
    background: oklch(0.24 0.02 255);
    color: #fff;
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: -0.01em;
    padding: 3px 8px;
    border-radius: 6px;
    white-space: nowrap;
    z-index: 40;
    opacity: 0;
    visibility: hidden;
    transform: translate(-50%, 3px);
    transition: opacity 0.15s, transform 0.15s;
    pointer-events: none;
}
.em-fantip::after {
    content: '';
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    border: 4px solid transparent;
    border-top-color: oklch(0.24 0.02 255);
}
.em-fanflag:hover .em-fantip {
    opacity: 1;
    visibility: visible;
    transform: translate(-50%, -5px);
}

/* ===== 카드 wrap + 뒤 컬러 패널 (디자인 핸드오프 최종 2026-06-02) ===== */
/* cardwrap — backpanel(뒤) + card(앞)를 겹치는 기준 컨테이너. */
.em-marker__cardwrap {
    position: relative;
}

/* 뒤 컬러 패널 — 신고가·신축 단지(.em-marker--special)면 카드 뒤에 컬러 카드가 4·5px 어긋나게 겹침.
   색 = --sc (신고가 우선, JS 인라인 style 로 주입). 종이 겹침 효과로 시각 강조. */
.em-marker__backpanel {
    position: absolute;
    inset: 0;
    background: var(--sc);
    border-radius: 11px;
    transform: translate(4px, 5px);
    box-shadow: 0 3px 8px color-mix(in oklch, var(--sc), transparent 45%);
    z-index: 0;
}

/* ===== 흰 카드 (디자인 핸드오프 .scard) ===== */
.em-marker__card {
    position: relative;
    z-index: 1;  /* 뒤 패널(z-index 0) 위로 */
    background: #fff;
    border-radius: 11px;
    /* 평상시 그림자: 떠 있는 카드 + 1px ring (디자인 핸드오프 값 그대로). */
    box-shadow: 0 2px 6px rgba(20, 30, 50, 0.13), 0 0 0 1px rgba(20, 30, 50, 0.07);
    transition: box-shadow 0.2s;
    white-space: nowrap;
    /* 상단 컬러 라인(rail)이 카드 radius 따라 깔끔히 잘리게 overflow hidden. 꼬리는 .em-marker::after 로 분리. */
    overflow: hidden;
    min-width: 100px;
}

/* 말풍선 꼬리 (아래쪽 흰 삼각형) — 카드 하단 중앙. 카드가 overflow:hidden 이라 마커 래퍼에 박음.
   z-index 3 으로 뒤 패널·카드 위. 카드 본체와 같은 흰색으로 자연 연결. */
.em-marker::after {
    content: '';
    position: absolute;
    bottom: -6px;
    left: 50%;
    transform: translateX(-50%);
    width: 0;
    height: 0;
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
    border-top: 7px solid #fff;
    z-index: 3;
}

/* 키보드 포커스 — 접근성 ring (마우스 hover 시 카드 그림자 변화는 디자인상 없음, 선택만 ring). */
.em-marker:focus-visible .em-marker__card {
    box-shadow: 0 2px 6px rgba(20, 30, 50, 0.13), 0 0 0 2px var(--color-accent);
    outline: none;
}

/* 상단 가격대 컬러 라인 (rail) — 카드 맨 위 가로 풀폭, height 4px, 배경 = 가격대 색(--c).
   가격대 modifier (.em-marker--price-1~9) 가 --c 분기. 디폴트는 hairline 회색 (noSample/fallback). */
.em-marker__catbar {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background: var(--c, var(--color-border));
    border-radius: 11px 11px 0 0;
}

/* 마커 본문 (카드 padding 안 — 단지명·평수칩·가격). 디자인 핸드오프 padding 9px 11px 8px. */
.em-marker__body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    box-sizing: border-box;
    padding: 9px 11px 8px;
}

/* 단지명 — 디자인 핸드오프 11px / 600, muted, 아래 3px 간격. 긴 이름은 최대폭 넘으면 말줄임(…).
   전체 이름은 마커 클릭 시 우측 패널에 노출되므로 정보 손실 없음. */
.em-marker__name {
    white-space: nowrap;
    max-width: 90px;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-bottom: 3px;
    font-size: 11px;
    font-weight: 600;
    color: oklch(0.5 0.01 250);
    letter-spacing: -0.01em;
}

/* 평수 칩 — 디자인 핸드오프 10px / 600, 회색 배경 박스 radius 5. flex:none(가격 행에서 안 줄어듦). */
.em-marker__pyeong {
    flex: none;
    font-size: 10px;
    font-weight: 600;
    color: oklch(0.5 0.01 250);
    background: oklch(0.95 0.004 250);
    padding: 1px 5px;
    border-radius: 5px;
    letter-spacing: -0.01em;
}

/* 가격 — 디자인 핸드오프 15px / 800. */
.em-marker__price {
    font-size: 15px;
    font-weight: 800;
    color: oklch(0.26 0.02 255);
    letter-spacing: -0.02em;
}

/* 신고가 단지 — 가격 빨강 (디자인 핸드오프 최종). 깃발(↑) + 뒤 패널 + 빨강 가격 3중 강조. */
.em-marker__price--high {
    color: oklch(0.55 0.22 27);
}

/* 평수 칩 · 가격 한 행 (디자인 핸드오프 m1-row) — baseline 정렬 gap 6px. */
.em-marker__price-row {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
}

/* M2-1.7 — fallback (마지막 거래일 기준 6개월 평균) 마커. 정직성 신호는 평형 칩 "· 이전" 꼬리표만. */
.em-marker--fallback .em-marker__pyeong::after {
    content: ' · 이전';
    color: var(--color-text-muted);
    font-size: 9px;
}

/* 거래 없음 — 회색 톤. 마커 자체는 표시 (단지 위치 알림). */
.em-marker--empty .em-marker__card {
    background: var(--color-surface-alt);
    box-shadow: 0 2px 6px rgba(20, 30, 50, 0.1), 0 0 0 1px var(--color-text-muted);
    opacity: 0.85;
}
.em-marker--empty .em-marker__name {
    color: var(--color-text-muted);
}
.em-marker--empty .em-marker__price {
    color: var(--color-text-muted);
    font-weight: 600;
    font-size: 11px;
}

/* 선택(클릭)된 단지 — 디자인 핸드오프 최종: 가격대 색 2.5px 테두리 + 같은 색 소프트 그림자.
   확대(scale) 안 함 (깃발·뒤 패널까지 커지면 어색). focus-visible 카드 규칙(위)보다 소스 뒤라 동시에도 ring 유지. */
.em-marker--selected .em-marker__card {
    box-shadow: 0 10px 22px color-mix(in oklch, var(--c, var(--color-accent)), transparent 55%), 0 0 0 2.5px var(--c, var(--color-accent));
}
/* 특별 단지(신고가·신축) 선택 시 테두리·그림자 = 특별색(--sc, 신고가 우선) — 가격대 색 대신. */
.em-marker--selected.em-marker--special .em-marker__card {
    box-shadow: 0 10px 22px color-mix(in oklch, var(--sc), transparent 50%), 0 0 0 2.5px var(--sc);
}
.em-marker--selected {
    z-index: 50;
}

/* 가격대 색 9단계 — 디자인 핸드오프 (oklch base + ink).
   평균가 낮을수록 밝게(노랑·연두·하늘), 높을수록 어둡게(빨강·보라·남색·검정). oklch 로 밝기·채도 매끄럽게 진행.
   --c = 상단 컬러 라인 + 선택 시 2.5px ring 색. --ink = 선택 시 가격 텍스트(그 가격대 진한 톤).
   noSample / fallback 마커는 --c 미정의 → 컬러 라인 회색 (가격 신뢰 낮아 분류 X).
   tier 판정 = price < max 첫 구간 (exploreMap.js buildMarkerHtml 와 경계 일치). */
.em-marker--price-1 { --c: oklch(0.88 0.15 98);  --ink: oklch(0.58 0.13 80);  }  /* ~3억 — 노랑 */
.em-marker--price-2 { --c: oklch(0.84 0.17 132); --ink: oklch(0.55 0.15 132); }  /* 3~5억 — 연두 */
.em-marker--price-3 { --c: oklch(0.78 0.12 226); --ink: oklch(0.52 0.13 240); }  /* 5~7억 — 하늘 */
.em-marker--price-4 { --c: oklch(0.74 0.16 58);  --ink: oklch(0.55 0.15 52);  }  /* 7~10억 — 주황 */
.em-marker--price-5 { --c: oklch(0.66 0.18 38);  --ink: oklch(0.54 0.18 36);  }  /* 10~15억 — 코랄 */
.em-marker--price-6 { --c: oklch(0.57 0.21 27);  --ink: oklch(0.52 0.21 27);  }  /* 15~20억 — 빨강 */
.em-marker--price-7 { --c: oklch(0.48 0.19 318); --ink: oklch(0.46 0.19 318); }  /* 20~30억 — 보라 */
.em-marker--price-8 { --c: oklch(0.38 0.14 268); --ink: oklch(0.40 0.15 268); }  /* 30~50억 — 남색 */
.em-marker--price-9 { --c: oklch(0.26 0.02 270); --ink: oklch(0.30 0.02 270); }  /* 50억+ — 검정 */

/* 시·군·구 클러스터 (level >= 6, 카운트만). 도전자 §2 룰 — 동/구 평균 표시 X.
   같은 토큰의 accent 강조로 시각적으로 "단지 마커보다 큰 묶음 단위" 라는 신호. */
.em-cluster {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1px;
    min-width: 64px;
    padding: 8px 12px;
    background: var(--color-surface);
    border: 2px solid var(--color-accent);
    border-radius: 999px;
    box-shadow: var(--shadow-md);
    color: var(--color-text);
    font-family: inherit;
    white-space: nowrap;
    pointer-events: none; /* 클러스터는 클릭 X — 줌인하면 단지로 전환 */
}

.em-cluster__name {
    font-size: 0.7rem;
    font-weight: 500;
    color: var(--color-text);
}

.em-cluster__count {
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--color-accent);
}

/* 줌아웃 지역 평균가 버블 (2026-06-08 디자인 핸드오프 design_handoff_region_markers — A·에어리어 캡슐 "값 그대로").
   [가격대 컬러 도트][이름(위)/평균가(아래)] 가로 캡슐. 9단계 가격대 컬러 램프(oklch, --c/--soft/--ink 인라인 주입,
   JS REGION_TIERS 단일 진실원)로 도트·후광·가격 글자색이 가격대별로 변함 — 단지 마커와 같은 가족 톤.
   줌 레벨별 3단계 치수(동<시·군·구<시·도, 줌아웃할수록 큼). 지역 고정 좌표에 박혀 드래그 시 안 따라다님.
   클릭 시 그 지역 중심으로 한 단계 아래 레벨 줌인(시·도→시·군·구→동→단지, JS onRegionClick). hover 시 핸드오프대로 살짝 떠오름.
   oklch·치수 직접값은 이 버블 한정 핸드오프 승인 예외(글로벌 토큰·다른 페이지 영향 0). 네이버 마커 content 는 scoped CSS 못 받아 글로벌 정의. */
/* 인구 레이어 버블 (2026-06-09 핸드오프 design_handoff_region_population_panel — "값 그대로" 승인) */
/* 원형 글로우 버블: 크기·색 = 인구수(시안 H~200 → 인디고 H~292 램프). 지역명(위)+인구값(아래) 내부 배치. */
/* 반투명 방사형 그라데이션(가장자리 투명 → 지도 비침) + 글래스 엣지. oklch·치수는 JS 인라인 주입(popBubbleStyle) — 이 버블 한정 핸드오프 승인 예외. 네이버 마커라 글로벌. */
.em-pop {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    line-height: 1;
    text-align: center;
    border-radius: 50%;
    pointer-events: auto;
    cursor: pointer;
    font-family: inherit;
    background: var(--fill, #0d9488);
    box-shadow: var(--glow), inset 0 1.5px 1px rgba(255, 255, 255, .7), inset 0 0 0 1px var(--edge, rgba(13, 148, 136, .4));
    backdrop-filter: blur(1.5px);
    -webkit-backdrop-filter: blur(1.5px);
    transition: transform .18s cubic-bezier(.2, .85, .3, 1.1), box-shadow .2s;
}
.em-pop__nm {
    font-weight: 600;
    color: var(--txt, #0f766e);
    opacity: .82;
    letter-spacing: -.03em;
    white-space: nowrap;
    margin-bottom: 1px;
    font-size: var(--nsize, 10px);
}
.em-pop__val {
    font-weight: 800;
    color: var(--txt, #0f766e);
    letter-spacing: -.035em;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    text-shadow: 0 1px 1px rgba(255, 255, 255, .35);
    font-size: var(--vsize, 13px);
}
.em-pop:hover { transform: scale(1.07); box-shadow: var(--glowHover), inset 0 1.5px 1px rgba(255, 255, 255, .8), inset 0 0 0 1px var(--edge); z-index: 4; }
.em-pop:focus-visible { outline: none; transform: scale(1.07); }
/* 선택(패널 표시 중) — 펄스 링 + 부유 모션. */
.em-pop.sel { animation: emPopFloat 3.4s ease-in-out infinite; }
.em-pop.sel::after {
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: 50%;
    box-shadow: 0 0 0 2.6px var(--ring, rgba(13, 148, 136, .5));
    animation: emPopPing 2.6s cubic-bezier(.2, .6, .3, 1) infinite;
}
@keyframes emPopFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
@keyframes emPopPing { 0% { opacity: .55; transform: scale(1); } 70%, 100% { opacity: 0; transform: scale(1.32); } }
@media (prefers-reduced-motion: reduce) {
    .em-pop { transition: none; }
    .em-pop:hover, .em-pop:focus-visible { transform: none; }
    .em-pop.sel { animation: none; }
    .em-pop.sel::after { animation: none; }
}

/* 인구 패널 피라미드 행 hover 말풍선 (2026-06-09 핸드오프) — body 에 JS 가 append(position:fixed). 마우스 X + 행 상단. */
.em-poptip {
    position: fixed;
    z-index: 9999;
    pointer-events: none;
    background: oklch(0.24 0.02 255);
    color: #fff;
    border-radius: 9px;
    padding: 8px 11px;
    font-size: 11px;
    letter-spacing: -.01em;
    line-height: 1.5;
    white-space: nowrap;
    box-shadow: 0 6px 16px rgba(20, 30, 50, .3);
    opacity: 0;
    transform: translate(-50%, -100%);
    transition: opacity .12s;
    font-family: 'Pretendard', system-ui, sans-serif;
}
.em-poptip.show { opacity: 1; }
.em-poptip b { font-weight: 800; font-variant-numeric: tabular-nums; }
.em-poptip__age { font-weight: 800; display: block; margin-bottom: 3px; }
.em-poptip__m { color: oklch(0.82 0.09 256); }
.em-poptip__f { color: oklch(0.84 0.1 350); }

/* 인구 이동 레이어 버블 (2026-06-09, 핸드오프 2026-06-09 재디자인) — 시군구/시도 순이동.
   원 버블(크기 ∝ |순이동|) 안에 지역명 + 순이동값 2줄. 핸드오프 마커 룩: 연한 틴트 fill(--fill) + 채도 컬러 링(--ring) + 흰 후광 + 본문색(--txt).
   순유입(+)=빨강 hue25 / 순유출(−)=파랑 hue256, |순이동| 클수록 채도↑·명도↓ (markerVars). 호갱노노 톤 — 승인 예외(인구이동 방향축이라 한국 신호색 의미와 별개). 네이버 마커라 글로벌.
   선택(허브) = 흰 디스크 + 진한 외곽 + ping 펄스. 원 중심 = 좌표라 화살표가 원 가장자리에서 정확히 멈춤. */
.em-mig {
    position: relative;
    width: var(--d, 48px);
    height: var(--d, 48px);
    border-radius: 50%;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1px;
    background: var(--fill, #fff);
    box-shadow: 0 0 0 1.6px var(--ring, #888), 0 0 0 4px rgba(255, 255, 255, .92), 0 5px 12px rgba(20, 30, 50, .18);
    white-space: nowrap;
    pointer-events: auto;
    cursor: pointer;
    font-family: inherit;
    transition: transform .18s cubic-bezier(.2, .85, .3, 1.1), box-shadow .2s;
}
.em-mig__nm { font-size: 9px; font-weight: 600; color: var(--txt, #374151); opacity: .85; line-height: 1.05; letter-spacing: -.03em; max-width: calc(var(--d, 48px) - 10px); overflow: hidden; text-overflow: ellipsis; margin-bottom: 1px; }
.em-mig__v { font-size: 12.5px; font-weight: 800; color: var(--txt, #374151); line-height: 1.1; letter-spacing: -.04em; font-variant-numeric: tabular-nums; }
.em-mig--lv1 .em-mig__nm { font-size: 9.5px; }
.em-mig--lv1 .em-mig__v { font-size: 13.5px; }
.em-mig:hover { transform: scale(1.07); z-index: 6; box-shadow: 0 0 0 2px var(--ring, #888), 0 0 0 4.4px #fff, 0 10px 20px rgba(20, 30, 50, .22); }
.em-mig:focus-visible { outline: none; transform: scale(1.07); }
/* 선택(허브) — 흰 디스크 + 진한 외곽 + ping 펄스 (지금 보는 주체 강조) */
.em-mig--sel { background: #fff; box-shadow: 0 0 0 2.4px oklch(0.21 0.02 258), 0 0 0 5.4px #fff, 0 12px 26px rgba(20, 30, 50, .28); }
.em-mig--sel .em-mig__nm { color: oklch(0.21 0.02 258); opacity: 1; font-weight: 700; }
.em-mig--sel .em-mig__v { color: oklch(0.21 0.02 258); }
.em-mig__ping { position: absolute; inset: -4px; border-radius: 50%; pointer-events: none;
    box-shadow: 0 0 0 2.4px oklch(0.21 0.02 258 / .45); animation: emMigPing 2.8s cubic-bezier(.2, .6, .3, 1) infinite; }
@keyframes emMigPing { 0% { opacity: .5; transform: scale(1); } 70%, 100% { opacity: 0; transform: scale(1.3); } }
@media (prefers-reduced-motion: reduce) {
    .em-mig { transition: none; }
    .em-mig:hover, .em-mig:focus-visible { transform: none; }
    .em-mig__ping { animation: none; }
}

/* 인구 이동 흐름 라벨 — 곡선 위 목적지 근처 알약 칩(핸드오프 .flab). ▲(전입·파랑)/▼(전출·빨강) + N명 + %. 클릭 시만 표시(clearFlows 로 제거). 네이버 마커라 글로벌. */
.em-flowval {
    position: absolute;
    left: 0;
    top: 0;
    transform: translate(-50%, -50%);
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 23px;
    padding: 0 9px;
    border-radius: 999px;
    background: #fff;
    line-height: 1;
    box-shadow: 0 3px 9px rgba(20, 30, 50, .16), 0 0 0 1px rgba(20, 30, 50, .05);
    white-space: nowrap;
    pointer-events: none;
}
/* 흐름 화살표 선 — SVG path 마커(`addFlowArrow`)라 글로벌. 흐르는 점선(dashoffset 애니, 핸드오프 flowMove).
   점 = 길이 0 + round cap(지름=선두께) / 간격(--gap)은 선 두께 비례로 JS 인라인 주입 → 두께 무관 일정 리듬(굵은 선 점 붙고 가는 선 벌어지던 것 fix). 화살촉 path 는 애니 없음(fill). */
.em-flowline { fill: none; stroke-linecap: round; stroke-opacity: .72; stroke-dasharray: 0 var(--gap, 11); animation: emFlowMove 1.1s linear infinite; }
@keyframes emFlowMove { to { stroke-dashoffset: calc(var(--gap, 11) * -2); } }
@media (prefers-reduced-motion: reduce) {
    .em-flowline { animation: none; stroke-dasharray: 0 var(--gap, 11); stroke-opacity: .62; }
}

.em-flowval__tri { font-size: 9px; line-height: 1; }
.em-flowval b { font-size: 12px; font-weight: 800; letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
.em-flowval i { font-size: 10.5px; font-style: normal; font-weight: 700; color: #9aa3b2; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.em-flowval--in b, .em-flowval--in .em-flowval__tri { color: #2f6fe0; }   /* 전입 파랑 */
.em-flowval--out b, .em-flowval--out .em-flowval__tri { color: #e0392f; }  /* 전출 빨강 */

.em-region {
    position: relative;            /* B48 — 절대배치 "N명 보는중" 버블(.em-viewer--abs)의 앵커. */
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 6px 11px 6px 9px;
    background: #fff;
    border-radius: 13px;
    box-shadow: 0 2px 7px rgba(20, 30, 50, .14), 0 0 0 1px rgba(20, 30, 50, .06);
    font-family: inherit;
    white-space: nowrap;
    pointer-events: auto;
    cursor: pointer;
    transform-origin: center;
    transition: transform .18s cubic-bezier(.2, .85, .3, 1.1), box-shadow .18s;
}
.em-region:hover {
    transform: translateY(-2px);
    box-shadow: 0 9px 18px rgba(20, 30, 50, .2), 0 0 0 1px rgba(20, 30, 50, .06);
}
.em-region:focus-visible {
    outline: none;
    transform: translateY(-2px);
    box-shadow: 0 9px 18px rgba(20, 30, 50, .2), 0 0 0 2px var(--c, var(--color-accent));
}
@media (prefers-reduced-motion: reduce) {
    .em-region {
        transition: none;
    }
    .em-region:hover,
    .em-region:focus-visible {
        transform: none;
    }
}
.em-region__dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--c, var(--color-border));
    flex: none;
    box-shadow: 0 0 0 3px var(--soft, transparent);
}
.em-region__txt {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    line-height: 1.12;
}
.em-region__name {
    font-size: 10.5px;
    font-weight: 600;
    color: oklch(0.52 0.01 250);
    letter-spacing: -.01em;
    white-space: nowrap;
}
.em-region__price {
    font-size: 15px;
    font-weight: 800;
    color: var(--ink, var(--color-text));
    letter-spacing: -.02em;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
/* 시·군·구 (lv-gu) */
.em-region--lv-gu {
    padding: 8px 14px 8px 11px;
    gap: 9px;
    border-radius: 15px;
}
.em-region--lv-gu .em-region__dot {
    width: 11px;
    height: 11px;
}
.em-region--lv-gu .em-region__name {
    font-size: 12px;
}
.em-region--lv-gu .em-region__price {
    font-size: 18px;
}
/* 시·도 (lv-do) */
.em-region--lv-do {
    padding: 10px 17px 10px 13px;
    gap: 11px;
    border-radius: 18px;
}
.em-region--lv-do .em-region__dot {
    width: 13px;
    height: 13px;
    box-shadow: 0 0 0 4px var(--soft, transparent);
}
.em-region--lv-do .em-region__name {
    font-size: 13.5px;
}
.em-region--lv-do .em-region__price {
    font-size: 22px;
}
/* 표본 부족·거래 없음 — 채도 낮춰 회색 (호도 차단, 가격대 컬러 안 씀). */
.em-region--nodata .em-region__dot {
    background: var(--color-text-muted);
    box-shadow: none;
}
.em-region--nodata .em-region__price {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-text-muted);
}
.em-region--lv-gu.em-region--nodata .em-region__price,
.em-region--lv-do.em-region--nodata .em-region__price {
    font-size: 13px;
}

/* ============================================================
   SideRail 도구 마커 (2026-06-03) — POI / 거리재기.
   카카오맵 CustomOverlay content 는 scoped CSS 못 받아 글로벌(app.css)에 정의.
   단지 마커(.em-marker)와 시각 분리 — 작고 채도 낮게, 정보 위계 절제.
   ============================================================ */

/* 주변 POI 라벨 (지하철·학교·편의점) — 단지 마커보다 작고 neutral, 클릭 X. */
.em-poi {
    display: inline-block;
    max-width: 120px;
    padding: 2px 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-sm);
    color: var(--color-text-muted);
    font-family: inherit;
    font-size: 0.66rem;
    font-weight: 500;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
}

/* 거리재기 — 클릭 지점 작은 원. */
.em-measure-dot {
    display: block;
    width: 9px;
    height: 9px;
    background: var(--color-surface);
    border: 2px solid var(--color-accent);
    border-radius: 999px;
    box-shadow: var(--shadow-sm);
}

/* 거리재기 — 마지막 점 옆 누적 직선 거리 라벨. tabular-nums 로 자릿수 정렬. */
.em-measure-label {
    display: inline-block;
    padding: 2px 7px;
    background: var(--color-accent);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-sm);
    color: #fff;
    font-family: inherit;
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1.3;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* 사용자 환경 reduced-motion — 마커 transition 0 (CLAUDE.md 접근성 룰). */
@media (prefers-reduced-motion: reduce) {
    .em-marker__card,
    .em-fanflag,
    .em-flagbody,
    .em-fantip {
        transition: none;
    }
}

/* M2-1.8 모바일 — 변동 mini badge 가 길어 마커 폭 부풀고 overlap 잘 일어남 → 폰트 축소 + 단지명 max-width 축소.
   white-space: nowrap 는 보존 (마커 내부 줄바꿈 차단).
   M2-1.9 — padding 은 .em-marker 가 아니라 .em-marker__body 에 박힘 (catbar 분리 마크업). */
@media (max-width: 700px) {
    /* 가변폭 유지 — 단, 좁은 화면에서 긴 단지명이 마커를 과하게 늘리지 않게 단지명만 max-width + 말줄임. */
    .em-marker__body {
        padding: 7px 9px 7px;
    }
    .em-marker__name {
        font-size: 10.5px;
        max-width: 110px;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .em-marker__pyeong {
        font-size: 9.5px;
    }
    .em-marker__price {
        /* 모바일에서도 가격 위계 유지 — PC 15px 대비 14px (작아도 가장 큼) */
        font-size: 14px;
    }
    .em-marker__price-row {
        gap: 5px;
    }
    /* 깃발 모바일 약간 축소 (마커 자체가 작아 깃발도 비례 축소). 깃발-카드 거리도 반(4→2px). */
    .em-fanrow {
        /* 데스크톱과 동형 — 간격을 padding 으로(margin 0) 흡수해 hit 영역 연결. height 23 = 깃발 21 + padding 2. */
        height: 23px;
        padding-bottom: 2px;
    }
    .em-flagbody {
        width: 26px;
        height: 20px;
    }
    /* 클러스터 폰트 약간 축소 */
    .em-cluster {
        min-width: 56px;
        padding: 7px 10px;
    }
    .em-cluster__name {
        font-size: 0.68rem;
    }
    .em-cluster__count {
        font-size: 0.78rem;
    }
    /* 지역 평균가 버블 — 핸드오프 치수가 최종값이라 모바일 별도 축소 없음(값 그대로). */
}

/* ============================================================
   M2-2.1 — 풀-블리드 레이아웃 분기 (2026-05-30).
   BlankLayout 전용 site-main 변형 — SiteHeader / SiteFooter 자체가 안 박힘.

   사용:
     Components/Layout/BlankLayout.razor 의
       <main class="site-main site-main--blank">@Body</main>

   왜 .site-main--blank 단일 클래스로 갈음 가능한가:
   BlankLayout 안에는 SiteHeader / SiteFooter 가 없으므로 옛 body.explore-fullscreen
   기반 토글 (지도 진입 시 .site-main 패딩 무력화 + .site-footer display:none) 자체가 불필요.
   레이아웃 진입 = 풀-블리드, 떠남 = MainLayout 자동 복귀 (Blazor @layout 표준).

   /explore 의 호갱노노식 풀스크린 지도 + 좌하단 .explore-disclosure 접이식 칩이
   SiteFooter 의 표준 면책 5줄 갈음 (CLAUDE.md §3-1 층 분리 룰 보존 — 「표시광고법」·YMYL 가드).
   ============================================================ */
.site-main.site-main--blank {
    padding: 0;
    margin: 0;
    max-width: none;
    min-height: 0;
    /* 풀-블리드 지도 무대 — viewport 끝까지 100dvh. 모바일 주소창 자연 축소도 100dvh 가 대응. */
    height: 100dvh;
    /* 자식 (.explore) 이 absolute 오버레이를 박을 수 있게 — z-index stacking context 격리. */
    position: relative;
    overflow: hidden;
}

/* ============================================================
   M2-2.7 (2026-06-01) — /explore 필터 모달 평형 이중 손잡이 슬라이더 thumb.
   scoped CSS (.razor.css) 는 ::-webkit-slider-thumb 등 pseudo-element 에 [b-xxx] attr 을
   못 붙여 작동 X (메모리 reference_native_input_blazor_pitfalls) → thumb/track 은 여기 글로벌에.
   컨테이너·track·fill·scale 은 FilterRow.razor.css (scoped) 가 owns.
   ============================================================ */
.range-slider__range::-webkit-slider-runnable-track {
    height: 32px;
    background: transparent;
    border: none;
}

.range-slider__range::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    pointer-events: auto;      /* track 은 none, thumb 만 잡음 — 겹친 input 2개 각자 thumb 드래그 */
    margin-top: 7px;           /* (track 32 - thumb 18) / 2 — 시각 track 중앙 정렬 */
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #fff;
    border: 2px solid var(--color-accent);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
    cursor: pointer;
}

.range-slider__range::-moz-range-track {
    height: 32px;
    background: transparent;
    border: none;
}

.range-slider__range::-moz-range-thumb {
    pointer-events: auto;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #fff;
    border: 2px solid var(--color-accent);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
    cursor: pointer;
}

.range-slider__range:focus-visible::-webkit-slider-thumb {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
}

.range-slider__range:focus-visible::-moz-range-thumb {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
}

/* ============================================================
   분양 단지 마커 (BY3 — 레드 헤더 투톤). 외부 핸드오프 design_handoff_bunyang_marker "값 그대로"
   (2026-06-06 사용자 승인). 일반 마커 골격 + 상단 레드 헤더. 레드는 신고가/액센트 레드 재사용(새 색 X).
   /explore 분양 레이어 전용 (exploreMapNaver.js buildSaleMarkerHtml 가 .mk.by.by3 DOM 렌더).
   ⚠️ 2026-06-07 사용자 요청으로 크기를 일반 마커(.em-marker)와 동일 치수로 조정 — 헤더 슬림(padding 1px/svg 9px),
   본문 flex 통일(line-height 1.15·display flex·padding 9/11/8), min-width 100, 단지명 max-width 90px + 말줄임(…).
   핸드오프 원본의 큰 치수(110px·name 12px·두꺼운 헤더)로 되돌리지 X — 점검 시 "핸드오프 값과 다름" 오인 금지.
   ============================================================ */
.by { --r: oklch(0.585 0.215 27); --rd: oklch(0.50 0.205 27); --rdr: oklch(0.44 0.19 27);
      --rs: oklch(0.955 0.045 27); --rk: oklch(0.45 0.18 27); }
.mk { position: relative; display: flex; flex-direction: column; align-items: stretch;
      font-family: 'Pretendard', system-ui, sans-serif; transform-origin: bottom center;
      line-height: 1.15;  /* 일반 마커 .em-marker 와 동일 — 줄 높이 차이로 본문이 두꺼워지던 것 해소 */
      transition: transform .2s cubic-bezier(.2,.85,.3,1.1);
      pointer-events: none; }  /* 래퍼 클릭 통과 — 깃발행 여백이 위 마커 덮는 것 차단(일반 마커 동형). 카드(.by3)만 auto 복원 */
.mk .by3 { pointer-events: auto; cursor: pointer; }
.tail { position: absolute; left: 50%; bottom: -6px; transform: translateX(-50%);
        width: 0; height: 0; border-left: 6px solid transparent; border-right: 6px solid transparent;
        border-top: 7px solid #fff; z-index: 3; }
.by-tail-w { border-top-color: #fff; bottom: -6px; }
.by3 { background: #fff; border-radius: 11px; overflow: hidden; min-width: 100px; text-align: left;
       box-shadow: 0 2px 6px rgba(20,30,50,.13), 0 0 0 1px rgba(20,30,50,.07);
       transition: transform .2s cubic-bezier(.2,.85,.3,1.1), box-shadow .2s; }
.by3-head { display: flex; align-items: center; gap: 3px; background: var(--r); color: #fff;
            padding: 1px 9px; font-size: 9px; line-height: 1.3; font-weight: 800; letter-spacing: .03em; }
.by3-body { display: flex; flex-direction: column; align-items: flex-start; gap: 0; padding: 9px 11px 8px; }  /* 일반 마커 .em-marker__body 와 레이아웃·여백 동일 — 흰 본문 높이 일치 */
.by3-name { font-size: 11px; font-weight: 600; color: oklch(0.5 0.01 250); letter-spacing: -.01em;
            white-space: nowrap; max-width: 90px; overflow: hidden; text-overflow: ellipsis; margin-bottom: 3px; }
.by3-row { display: inline-flex; align-items: baseline; gap: 6px; }  /* 일반 .em-marker__price-row 와 동일 */
.by3-py { font-size: 10px; font-weight: 600; color: var(--rk); background: var(--rs);
          padding: 1px 5px; border-radius: 5px; flex: none; }
.by3-price { font-size: 15px; font-weight: 800; color: var(--rk); letter-spacing: -.02em; }
.by3-soon { font-size: 11px; font-weight: 700; color: var(--rk); background: var(--rs); padding: 1px 7px; border-radius: 5px; letter-spacing: -.01em; }
.by3.on { transform: scale(1.04); box-shadow: 0 10px 22px color-mix(in oklch, var(--r), transparent 55%), 0 0 0 2.5px var(--r); }

/* ============================================================
   B48 (2026-06-08) "N명 보는중" 말풍선 + 선택 호흡 — 외부 핸드오프 design_handoff_apt_map_markers
   (viewerbubble.jsx) "값 그대로" 적용. 단지(.em-marker)·분양(.mk.by)·지역(.em-region) 3종 공통.
   - 다크 네이비 버블(깃발 툴팁과 동일 oklch(0.24 0.02 255)) + 좌측 펄스 점 + 숫자 오도미터 "드르륵" 롤업 + 꼬리.
   - 평소엔 버블만 Y축 호흡(floatY), 선택 마커는 카드+버블이 부모 타고 동기 호흡(버블 자체 애니 off — 이중 움직임 방지).
   - 지역 버블은 절대배치(--abs, 핸드오프 vb-abs) — 지역은 클릭=줌인이라 선택 상태 없음 → 인기 지역에 상시 호흡.
   - oklch·치수 직접값은 이 버블 한정 핸드오프 승인 예외(글로벌 토큰·다른 페이지 영향 0). 네이버 마커 content 는
     scoped CSS 못 받아 글로벌(app.css)에 정의 — 기존 .em-marker/.em-region 패턴 동형.
   ============================================================ */
/* B48 (2026-06-09 fix) — 단지·분양·지역 버블 모두 절대배치(마커 위 중앙앵커)로 통일.
   in-flow(inline-flex)였던 단지·분양 버블은 마커 박스를 위로 늘려 네이버 마커의 투명 hit-box(충돌박스)를
   키웠고, 그 박스가 이웃 마커 클릭/호버를 가로챘음(사용자 지적). 절대배치로 빼면 마커 box 는 깃발+카드만 →
   버블은 box 밖에서 떠 있을 뿐 hit-box 에 안 잡힘(애초에 pointer-events:none 이라 클릭도 통과). */
.em-viewer {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    position: absolute;            /* flow 에서 빼서 마커 hit-box 안 키움 (충돌박스 제거) */
    left: 50%;
    bottom: 100%;                  /* 마커(깃발+카드) 위에 띄움 */
    transform: translateX(-50%);
    margin-bottom: 6px;
    z-index: 4;
    padding: 4px 9px 4px 7px;
    background: oklch(0.24 0.02 255);
    color: #fff;
    border-radius: 9px;
    box-shadow: 0 0 0 1.5px #fff, 0 4px 10px rgba(20, 30, 50, .28);
    white-space: nowrap;
    font-family: inherit;
    pointer-events: none;          /* 클릭은 아래 카드로 통과 (버블 자체는 정보 표시용) */
    /* translateX 와 floatY(translateY) 동시 적용 위해 floatY 를 translate 양축으로 정의한 abs 전용 키프레임 */
    animation: vb-floatY-abs 2.8s ease-in-out infinite;
}
/* 지역 버블 — 이제 base 와 동일(절대배치). 명시적 별칭으로 유지(지역 마커 마크업이 붙임). */
.em-viewer--abs {
    position: absolute;
}
/* 좌측 라이브 펄스 점 (ping) */
.vb-pulse {
    position: relative;
    width: 7px;
    height: 7px;
    flex: none;
}
.vb-pulse-core {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: oklch(0.72 0.18 25);
}
.vb-pulse-core::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: oklch(0.72 0.18 25);
    animation: vb-ping 1.8s cubic-bezier(0, 0, 0.2, 1) infinite;
}
.vb-count {
    display: inline-flex;
    align-items: baseline;
}
.vb-odo {
    display: inline-flex;
    font-size: 12px;
    font-weight: 800;
    letter-spacing: -.01em;
    font-variant-numeric: tabular-nums;
}
.vb-digit {
    display: inline-block;
    height: 16px;
    overflow: hidden;
}
.vb-col {
    display: flex;
    flex-direction: column;
    /* 첫 등장 시 0→목표값 "드르륵" — 셀 16px 고정, translateY(-320px)=20칸으로 착지. both=delay 중엔 from 유지. */
    animation: vb-roll 1.05s cubic-bezier(.16, .84, .28, 1) both;
}
/* 이미 한 번 롤업한 단지·분양·지역 — 재렌더(선택·pan·줌)엔 정적 최종값(롤업 1회만, 사용자 지적 2026-06-08). */
.vb-col--done {
    animation: none;
    transform: translateY(-320px);
}
.vb-n {
    height: 16px;
    line-height: 16px;
    text-align: center;
}
.vb-suffix {
    font-size: 11px;
    font-weight: 600;
    opacity: .82;
    margin-left: 1px;
}
.vb-tail {
    position: absolute;
    left: 50%;
    bottom: -5px;
    transform: translateX(-50%);
    width: 0;
    height: 0;
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-top: 6px solid oklch(0.24 0.02 255);
    filter: drop-shadow(0 1.5px 0 #fff);
}
@keyframes vb-floatY {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-3.5px); }
}
@keyframes vb-floatY-abs {
    0%, 100% { transform: translate(-50%, 0); }
    50% { transform: translate(-50%, -3.5px); }
}
@keyframes vb-roll {
    from { transform: translateY(0); }
    to { transform: translateY(-320px); }
}
@keyframes vb-ping {
    0% { transform: scale(1); opacity: .55; }
    100% { transform: scale(2.9); opacity: 0; }
}
/* 선택 시 동기 호흡 — 단지: 마커 전체가 호흡 + 버블 자체 애니 off (부모 타고 같이 움직임). */
.em-marker--selected {
    animation: vb-floatY 2.8s ease-in-out infinite;
}
.em-marker--selected .em-viewer {
    animation: none;
}
/* 분양: .by3.on(선택) 포함하면 .mk 전체 호흡 + 버블 애니 off. */
.mk.by:has(.by3.on) {
    animation: vb-floatY 2.8s ease-in-out infinite;
}
.mk.by:has(.by3.on) .em-viewer {
    animation: none;
}
/* reduced-motion — floatY·롤업·ping 정지. 숫자는 최종값(translateY -320px)으로 즉시 고정. */
@media (prefers-reduced-motion: reduce) {
    .em-viewer,
    .em-viewer--abs,
    .em-marker--selected,
    .mk.by:has(.by3.on) {
        animation: none;
    }
    .em-viewer--abs {
        transform: translateX(-50%);
    }
    .vb-col {
        animation: none;
        transform: translateY(-320px);
    }
    .vb-pulse-core::after {
        animation: none;
        opacity: 0;
    }
}

/* ── 재연결 띠배너 (Blazor Server SignalR 회로 끊김 시) ───────────────────────────
   기본 풀스크린 모달을 대신함 — 상단 얇은 띠로만 알리고 보던 콘텐츠(글·표·지도)는 안 가림.
   Blazor 가 #components-reconnect-modal 요소에 상태 클래스를 토글:
     .components-reconnect-show    재연결 시도 중
     .components-reconnect-failed  자동 재시도 소진 → '다시 연결' 버튼
     .components-reconnect-rejected 회로 만료 → '새로고침' 버튼
     .components-reconnect-hide    복구됨 (띠 사라짐)
   마크업은 App.razor body 의 #components-reconnect-modal. */
#components-reconnect-modal {
    position: fixed;
    inset: 0 0 auto 0; /* top/left/right = 0, 화면 상단 가로 전체 */
    z-index: 12000; /* sticky 헤더보다 위 */
    display: flex;
    justify-content: center;
    padding: 8px 12px;
    transform: translateY(-130%); /* 평소엔 화면 밖 위로 숨김 */
    transition: transform .3s cubic-bezier(0.16, 1, 0.3, 1);
    pointer-events: none; /* 띠 바깥(=보던 화면) 클릭/스크롤 그대로 통과 */
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    transform: translateY(0);
}

.reconnect-bar__inner {
    pointer-events: auto; /* 띠 안의 버튼은 클릭 가능 */
    display: inline-flex;
    align-items: center;
    gap: 10px;
    max-width: var(--container, 1200px);
    padding: 8px 16px;
    background: var(--color-surface, #fff);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: var(--radius-md, 10px);
    box-shadow: 0 4px 16px rgba(0, 0, 0, .12);
    font-size: 13.5px;
    line-height: 1.4;
    color: var(--color-text, #1f2937);
}

/* 로딩 로고 — 핸드오프 '튀어나오는 빌드온' 모션을 반복 재생.
   세 원이 가격대 순서(하늘→코랄→남색)로 scale(.4)→1 팝업. 좌표·delay·cubic-bezier 는 핸드오프 값 그대로. */
.reconnect-bar__logo { width: 26px; height: 26px; flex: none; }

.reconnect-bar__logo .rcl {
    transform-box: view-box; /* transform-origin 을 viewBox 좌표(24,24)로 해석 */
    transform-origin: 24px 24px; /* 핸드오프: viewBox 중심 */
    animation: reconnectLogoPop 1.8s cubic-bezier(.2, 1.1, .3, 1) infinite;
}
.reconnect-bar__logo .rcl--1 { animation-delay: 0s; } /* 하늘 */
.reconnect-bar__logo .rcl--2 { animation-delay: .13s; } /* 코랄 */
.reconnect-bar__logo .rcl--3 { animation-delay: .26s; } /* 남색 */

@keyframes reconnectLogoPop {
    0%        { opacity: 0; transform: scale(.4); } /* 핸드오프 pop from */
    24%       { opacity: 1; transform: scale(1); }  /* 팝업 완성 (cubic-bezier 오버슈트) */
    64%       { opacity: 1; transform: scale(1); }  /* 유지 */
    84%, 100% { opacity: 0; transform: scale(.4); } /* 사라짐 → 다음 사이클 */
}

.reconnect-bar__text { display: none; }
.components-reconnect-show .reconnect-bar__text--retrying { display: inline; }
.components-reconnect-failed .reconnect-bar__text--failed { display: inline-flex; align-items: center; gap: 8px; }
.components-reconnect-rejected .reconnect-bar__text--rejected { display: inline-flex; align-items: center; gap: 8px; }

/* 자동 재시도가 끝난(failed/rejected) 상태에선 모션을 멈추고 완성 마크를 정적 표시 ('대기 중' 신호) */
.components-reconnect-failed .reconnect-bar__logo .rcl,
.components-reconnect-rejected .reconnect-bar__logo .rcl {
    animation: none;
    transform: none;
    opacity: 1;
}

.reconnect-bar__inner button {
    font: inherit;
    padding: 4px 12px;
    border: 0;
    border-radius: var(--radius-sm, 6px);
    background: var(--color-accent, #2563eb);
    color: #fff;
    cursor: pointer;
}

.reconnect-bar__inner button:hover { background: var(--color-accent-hover, #1d4ed8); }

@media (prefers-reduced-motion: reduce) {
    #components-reconnect-modal { transition: none; }
    /* 핸드오프 폴백: 모션 미적용 시 마크는 완성 상태(보임)가 기본 */
    .reconnect-bar__logo .rcl { animation: none; opacity: 1; transform: none; }
}
