/* 三个多户页共用的页面层样式。
 *
 * 这一整块原本是 prism.html 头部的内联 <style>，逐字节搬到这里（搬的时候没有改任何一行：
 * 不缩进、不重排、不合并）。搬出来的理由是拆页：三个多户页需要同一套页面层规则
 * （reset / i18n 显隐 / body / .main / .section-label / footer / 两个断点），复制三份必然漂移，
 * 而漂移的样式表比没有样式表更难查 —— 三页会各自「差一点」，且没有任何一处能看出差在哪。
 *
 * ⚠️ 站2（可比成交）自己的规则也在这份文件里，量还不小。**没有按站切开**：切开要判断
 * 一千多行里每一条归谁，而判断错的代价是「某一页少一条规则」——这种缺陷肉眼看不出来，
 * 测试也抓不到。逐字节搬一份共用的，是这一步里唯一能被证明做对了的做法。
 * 真要瘦身，得先有渲染普查做前后对照，那是另一件事。
 *
 * ⚠️ <link> 必须排在 tokens.css / roles.css / prism-ds.css / mf-*.css **之后** ——
 * 它原本是内联块，位置就在所有样式表的最后，层叠顺序靠的就是这个。
 */
        * { margin: 0; padding: 0; box-sizing: border-box; }
        html, body { overflow-x: hidden; }
        body { overflow-wrap: break-word; word-wrap: break-word; }

        /* i18n toggle */
        .i18n-zh { display: none; }
        html[lang="zh"] .i18n-en { display: none; }
        html[lang="zh"] .i18n-zh { display: inline; }

        /* .lang-toggle / nav / .logo / .logo-sub / nav a.nav-link / .nav-links removed here:
           this page has no static <nav> — the top shell is injected entirely by
           /js/prism-nav.js (its own div-based markup: .prism-shell/.ps-crumb/…, not
           nav/.logo/.nav-links), confirmed by grep: none of these six selectors ever matched
           an element in this file. Zero rendered difference; also clears their font-size/
           letter-spacing literals from the guard's worklist for free instead of converting
           dead declarations. */

        body {
            background: var(--canvas);
            color: var(--body);
            font-family: var(--font-body);
            font-weight: var(--fw-body);
            line-height: var(--lh-reset);
            min-height: 100vh;
        }

        /* Main content */
        .main {
            padding: 140px 48px 120px /* spacing: page-level hero clearance, not a component gap. 140px and 120px are past the 10-step ladder max of 64px -- see the 768px/480px breakpoint rules below for the same rhythm scaled down */;
            max-width: var(--w-wide);
            margin: 0 auto;
        }

        /* sub-label role: mono, uppercase, tracked. family/size/weight/text-transform now come
           from the r-label role class (added on the element below) -- r-label's default weight
           is 400 (roles.css fix round 2), which is exactly what this rule already rendered at
           (no font-weight override here), so the role is a clean fit; only letter-spacing
           (2px vs r-label's 1px) and colour stay as page overrides. */
        .section-label {
            letter-spacing: 2px /* tracking: eyebrow label tracking, wider than r-label's default 1px */;
            color: var(--accent-strong);
            margin-bottom: 16px;
        }

        .page-title {
            font-family: var(--font-display);
            /* 2026-08-31 从 --fs-display(40px) 降到 --fs-h1(28px)（ Eddie 定 A 案）：
               三个站是平级的工具， 站2 比另外两个大一圈， 视觉上在暗示它更重要。
               ⚠️ 手机端本来就是 28px（ 下面两条响应式改写）， 所以这是把桌面对齐到
               它自己的手机端， 三个断点从此一致 —— 那两条改写因此成了同值重复， 一并删掉。 */
            font-size: var(--fs-h1);
            font-weight: 500;
            color: var(--ink);
            line-height: 1.15 /* line-height: page display heading, weight 500 doesn't match r-display's 400 so this stays property-only */;
            margin-bottom: 16px;
        }

        .page-title em {
            font-style: italic;
            color: var(--accent-strong);
        }

        .page-desc {
            font-size: var(--fs-body);
            color: var(--body);
            max-width: var(--w-read);
            margin-bottom: 32px;
        }

        /* Search */
        .search-box {
            display: flex;
            gap: 12px;
            margin-bottom: 48px;
        }

        .search-box input {
            flex: 1;
            padding: 16px 20px;
            background: var(--surface);
            border: 1px solid var(--border);
            color: var(--ink);
            font-family: var(--font-body);
            font-size: var(--fs-small);
            font-weight: var(--fw-body);
            outline: none;
            transition: border-color var(--dur-slow);
        }

        .search-box input::placeholder {
            color: var(--muted-strong);
        }

        .search-box input:focus {
            border-color: var(--accent);
        }

        .search-box select {
            appearance: none;
            padding: 16px 32px 16px 16px;
            background: var(--surface);
            border: 1px solid var(--border);
            color: var(--body);
            font-family: var(--font-body);
            font-size: var(--fs-small);
            outline: none;
            cursor: pointer;
            transition: border-color var(--dur-slow);
            background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='var(--surface)'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: right 14px center;
        }

        .search-box select option {
            background: var(--canvas);
            color: var(--ink);
        }

        .search-box select:focus {
            border-color: var(--border);
        }

        /* .btn-analyze adopts .a-btn.a-btn--accent (added on #btnAnalyze below) -- same trade
           the brief calls out and index.html's hero-cta already made: the tracked-uppercase
           look and the --ink-on-accent text colour (a real, pre-existing low-contrast pairing
           the shared role's --on-accent white text corrects) are replaced by the shared
           button's normal-case label and padding. white-space:nowrap kept since the role
           doesn't set it and this button sits in a flex row next to the input. */
        #btnAnalyze { white-space: nowrap; }

        /* .btn-locate stays its own component, NOT .a-btn -- same call Task 8 made for
           .et-play: it's a 40x40 icon-only square button (a location pin, no label), and
           .a-btn's pill padding/shape doesn't fit a square icon control any better here than
           it did there. It carries no GUARDED (font-size/family/line-height/letter-spacing)
           properties at all, so it was never part of the guard's worklist. */
        .btn-locate {
            padding: 12px;
            background: var(--surface);
            border: 1px solid var(--border);
            color: var(--muted-strong);
            cursor: pointer;
            transition: all var(--dur-slow);
            display: flex;
            align-items: center;
            justify-content: center;
            flex-shrink: 0;
        }

        .btn-locate:hover { border-color: var(--accent); color: var(--accent-strong); }
        .btn-locate:disabled { opacity: 0.3; cursor: not-allowed; }

        /* Loading — multi-step */
        .loading { display: none; padding: 64px 0; }
        .loading.active { display: block; }

        .loading-steps {
            display: flex;
            flex-direction: column;
            gap: 0;
        }

        .loading-step {
            display: flex;
            align-items: center;
            gap: 12px;
            padding: 12px 0;
            opacity: 0.25;
            transition: opacity 0.4s;
        }

        .loading-step.active {
            opacity: 1;
        }

        .loading-step.done {
            opacity: 0.45;
        }

        .loading-step .step-indicator {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            border: 1.5px solid var(--border);
            display: flex;
            align-items: center;
            justify-content: center;
            flex-shrink: 0;
        }

        .loading-step.active .step-indicator {
            border-color: var(--accent);
            animation: pulse-ring 1.2s ease infinite;
        }

        .loading-step.active .step-indicator::after {
            content: '';
            width: 6px;
            height: 6px;
            background: var(--accent);
            border-radius: 50%;
        }

        .loading-step.done .step-indicator {
            border-color: var(--accent-line);
            background: var(--accent-soft);
        }

        .loading-step.done .step-indicator::after {
            content: '✓';
            font-size: var(--fs-caption);
            color: var(--accent-strong);
        }

        @keyframes pulse-ring {
            0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 35%, transparent); }
            50% { box-shadow: 0 0 0 6px rgba(47,132,103,0); } /* frozen-literal: --accent at alpha 0 — color-mix(in srgb, var(--accent) 0%, transparent) resolves to transparent BLACK not transparent accent (verified live), and this keyframe interpolates against the previous 35% accent stop, so the pulse would visibly flash through black at the edge. Literal renders correctly. */
        }

        .loading-step span {
            font-family: var(--font-mono);
            font-size: var(--fs-small);
            color: var(--muted-strong);
            letter-spacing: 0.5px /* tracking: loading-step label tracking, no token matches 0.5px */;
        }

        .loading-step.active span {
            color: var(--body);
        }

        /* Market Context Section */
        .market-context {
            margin-bottom: 48px;
        }

        .market-context-header {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            margin-bottom: 20px;
        }

        /* family/weight/size/letter-spacing now come from the r-h2 role class (added on every
           .mc-title/.sm-title/.map-title/.comps-title element) -- all four matched this rule's
           own values exactly. Line-height was not previously set here (inherited the page's
           1.6), so adopting the role also tightens it to r-h2's 1.25 -- the same "role adoption
           tightens leading" corollary Task 7/8 already documented, not a separate decision. */
        .market-context-header .mc-title {
            text-transform: none;
            color: var(--ink);

            display: flex;
            align-items: center;
            gap: 12px;
        }

        .market-context-header .mc-period {
            font-size: var(--fs-caption);
            line-height: var(--lh-panel);
            color: var(--muted-strong);
        }

        .mc-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 1px /* spacing: NOT a spacing choice -- this is a hairline-divider trick. The grid's own --surface background shows through this gap between the --canvas-painted cells, drawing a thin dividing line the same weight as the 1px border below. Snapping to 4px would make the internal dividers 4x thicker than the outer border, an actual visible regression, not an invisible one */;
            background: var(--surface);
            border: 1px solid var(--border);
            margin-bottom: 16px;
        }

        .mc-cell {
            padding: 16px 16px;
            background: var(--canvas);
        }

        /* C7 批次一（2026-08-31）：字号与字距交给 .r-label--body。
           ⚠️ 原注释「no token matches 1.5px」是 .r-label--body 存在**之前**的话 —— 它有 2px。
              1.5px 与 2px 的差别，就是「各自发明一个」与「用共用的那一档」的差别。 */
        .mc-cell-label {
            text-transform: uppercase;
            color: var(--muted-strong);
            margin-bottom: 8px;
        }

        /* C7 批次二（2026-08-31，Eddie 看过对照图后批准）：排版交给 .r-metric。
           这是这一批**唯一看得见**的变化：字重 400 → 500，卡片主数值明显变重。
           理由：原来 400 在等宽字里偏轻，跟上面那行标签的分量拉不开；改完「标签轻、数值重」
           的层级才出来。行高同时从 1 收到 1.2（--lh-tight），并拿到 r-metric 自带的
           tabular-nums —— 这一格是数字网格，等宽数位让四张卡的数字上下对齐。 */
        .mc-cell-value {
            color: var(--ink);
        }

        /* C7 批次一：字族与字号交给 .a-source（同为等宽 11px，行高从 17.6 收到 16.5）。 */
        .mc-cell-yoy {
            margin-top: 8px;
            color: var(--muted-strong);
        }

        .mc-cell-yoy .up { color: var(--up); }
        .mc-cell-yoy .down { color: var(--down); }

        .mc-rent-row {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 1px /* spacing: same hairline-divider trick as .mc-grid above, not a spacing choice */;
            background: var(--surface);
            border: 1px solid var(--border);
            border-top: none;
        }

        .mc-rent-cell {
            padding: 12px 16px;
            background: var(--canvas);
        }

        .mc-rent-cell .mc-cell-label {
            margin-bottom: 4px;
        }

        .mc-rent-cell .mc-cell-value {
            font-size: var(--fs-small);
        }

        .mc-summary {
            margin-top: 16px;
            font-size: var(--fs-small);
            color: var(--muted-strong);
            line-height: 1.7 /* line-height: multi-line italic summary paragraph, looser than r-panel's 1.55 */;
            font-style: italic;
            border-left: 2px solid var(--accent-line);
            padding-left: 16px;
        }

        /* Submarket Context */
        .submarket-context {
            margin-bottom: 48px;
        }

        .sm-header {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            margin-bottom: 20px;
        }

        /* family/weight/size/letter-spacing now come from the r-h2 role class; see the
           .market-context-header .mc-title comment above for the identical reasoning. */
        .sm-header .sm-title {
            text-transform: none;
            color: var(--ink);

            display: flex;
            align-items: center;
            gap: 12px;
        }

        .sm-header .sm-count {
            font-size: var(--fs-caption);
            line-height: var(--lh-panel);
            color: var(--muted-strong);
        }

        .sm-compare {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 1px /* spacing: same hairline-divider trick as .mc-grid above, not a spacing choice */;
            background: var(--surface);
            border: 1px solid var(--border);
            margin-bottom: 20px;
        }

        .sm-compare-col {
            padding: 16px 20px;
            background: var(--canvas);
        }

        .sm-compare-col.subject {
            border-right: 1px solid var(--accent-line);
        }

        .sm-col-label {
            font-size: var(--fs-caption);
            letter-spacing: 2px /* tracking: submarket column label tracking, no token matches 2px */;
            color: var(--accent-strong);
            margin-bottom: 16px;
            font-weight: 500;
        }
        /* Task 18(design-language-unification): text-transform:uppercase removed — this label
           wraps escapeField(s.name), a real submarket name (e.g. "Downtown Los Angeles"), not a
           fixed caption. Same bug shape Task 8 found in a breadcrumb ("Los Angeles" -> "LOS
           ANGELES"); the "Metro Average" sibling column keeps working fine without it since it
           was never relying on the transform to read correctly. */

        .sm-metric {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            padding: 8px 0;
        }

        .sm-metric:not(:last-child) {
            border-bottom: 1px solid var(--border);
        }

        .sm-metric-label {
            font-size: var(--fs-caption);
            letter-spacing: var(--track-label);
            text-transform: uppercase;
            color: var(--muted-strong);
        }

        .sm-metric-value {
            font-family: var(--font-mono);
            font-size: var(--fs-body);
            color: var(--ink);
        }

        .sm-metric-delta {
            font-family: var(--font-mono);
            font-size: var(--fs-caption);
            margin-left: 8px;
        }

        .sm-metric-delta.up { color: var(--up); }
        .sm-metric-delta.down { color: var(--down); }

        .sm-ranking {
            border: 1px solid var(--border);
        }

        .sm-ranking-row {
            display: grid;
            grid-template-columns: 2fr 1fr 1fr 1fr;
            padding: 12px 16px;
            font-size: var(--fs-caption);
            border-bottom: 1px solid var(--border);
            background: var(--canvas);
        }

        .sm-ranking-row.header {
            font-size: var(--fs-caption);
            letter-spacing: 1.5px /* tracking: ranking table header tracking, no token matches 1.5px */;
            text-transform: uppercase;
            color: var(--muted-strong);
            background: var(--surface);
            font-weight: 400;
        }

        .sm-ranking-row.highlight {
            background: var(--accent-soft);
            border-left: 2px solid var(--accent);
        }

        .sm-ranking-row > div {
            color: var(--body);
        }

        .sm-ranking-row > div:not(:first-child) {
            text-align: right;
            font-family: var(--font-mono);
        }

        .sm-ranking-row > div:first-child {
            color: var(--body);
        }

        .sm-ranking-row.highlight > div:first-child {
            color: var(--accent-strong);
            font-weight: 500;
        }

        /* family/size/weight/text-transform now come from the r-label role class; see the
           .section-label comment above for the identical reasoning. */
        .sm-section-label {
            letter-spacing: 2px /* tracking: eyebrow label tracking, wider than r-label's default 1px */;
            color: var(--accent-strong);
            margin: 24px 0 8px;
            padding-left: 4px;
        }

        .sm-section-label:first-of-type {
            margin-top: 0;
        }

        /* AI Insights Section */
        .scatter-card{border:1px solid var(--border);border-radius:var(--radius);padding:16px 16px 12px;margin:0 0 24px}
        .scatter-title{font-family:var(--font-mono,monospace);font-size:var(--fs-small);font-weight:500;line-height: var(--lh-label);color:var(--ink,#1a1a1a)}
        /* Task 13(design-language-unification): was .scatter-sub — font-size/line-height/color
           dropped, .a-caption (styles/roles.css) already supplies the same font-size and color
           and a line-height close enough (1.5 vs the old 1.55) that no documented reason was
           found to keep the literal override. Margin is layout, scoped to #scatterCard so it
           doesn't reach the page's other .a-caption use (.stat-card). */
        #scatterCard .a-caption{margin:4px 0 12px}
        .scatter-svg{width:100%;display:block}
        .scatter-input{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin:0 0 12px;
             font-family:var(--font-mono,monospace);font-size:var(--fs-caption);font-weight:500;line-height: var(--lh-caption);color:var(--muted-strong,#6b6b6b)}
        .scatter-input label{display:inline-flex;align-items:center;gap:4px}
        .scatter-input input{width:92px;padding:4px 8px;border:1px solid var(--border);border-radius:var(--radius);
             font-family:var(--font-mono,monospace);font-size:var(--fs-small);font-weight:500;background:var(--surface,#fff);color:var(--ink,#1a1a1a)}
        .si-label{color:var(--ink,#1a1a1a)}
        .si-unit{color:var(--muted-strong)}
        .si-clear{border:1px solid var(--border);background:none;border-radius:var(--radius);padding:4px 8px;
             cursor:pointer;font-family:var(--font-mono,monospace);font-size:var(--fs-caption);font-weight:500;color:var(--muted-strong,#6b6b6b)}
        .si-out{flex-basis:100%;color:var(--ink,#1a1a1a)}
        .scatter-legend{display:flex;flex-wrap:wrap;gap:16px;font-family:var(--font-mono,monospace);font-size:var(--fs-caption);font-weight:500;line-height: var(--lh-caption);
             color:var(--muted-strong,#6b6b6b);padding:8px 0 4px}
        .scatter-legend i{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:4px;vertical-align:-1px}
        .ai-insights {
            margin-bottom: 48px;
            border: 1px solid var(--accent-line);
            background: var(--accent-soft);
            padding: 24px 24px 24px;
        }

        .ai-insights-header {
            display: flex;
            align-items: center;
            gap: 12px;
            margin-bottom: 20px;
        }

        .ai-pulse {
            width: 8px;
            height: 8px;
            background: var(--accent);
            border-radius: 50%;
            animation: ai-blink 2s ease infinite;
        }

        @keyframes ai-blink {
            0%, 100% { opacity: 1; box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 40%, transparent); }
            50% { opacity: 0.5; box-shadow: 0 0 8px 2px color-mix(in srgb, var(--accent) 22%, transparent); }
        }

        .ai-insights-header span {
            font-size: var(--fs-caption);
            letter-spacing: 2px /* tracking: AI-insights eyebrow tracking, no token matches 2px */;
            text-transform: uppercase;
            color: var(--accent-strong);
        }

        .ai-insight-item {
            padding: 12px 0;
            border-bottom: 1px solid var(--border);
            display: flex;
            gap: 12px;
            align-items: flex-start;
        }

        .ai-insight-item:last-child {
            border-bottom: none;
            padding-bottom: 0;
        }

        .ai-insight-icon {
            flex-shrink: 0;
            width: 28px;
            height: 28px;
            display: flex;
            align-items: center;
            justify-content: center;
            border: 1px solid var(--accent-line);
            background: var(--accent-soft);
        }

        .ai-insight-icon svg {
            width: 14px;
            height: 14px;
            stroke: var(--accent);
            fill: none;
            stroke-width: 1.5;
            stroke-linecap: round;
            stroke-linejoin: round;
        }

        .ai-insight-text {
            font-size: var(--fs-small);
            color: var(--body);
            line-height: var(--lh-body);
        }

        .ai-insight-text strong {
            color: var(--ink);
            font-weight: 500;
        }

        .ai-insight-text .ai-highlight {
            color: var(--accent-strong);
            font-family: var(--font-mono);
            font-size: var(--fs-small);
        }

        .ai-cursor {
            display: inline-block;
            width: 2px;
            height: 14px;
            background: var(--accent);
            margin-left: 4px;
            vertical-align: middle;
            animation: cursor-blink 0.6s step-end infinite;
        }

        @keyframes cursor-blink {
            0%, 100% { opacity: 1; }
            50% { opacity: 0; }
        }

        /* Error */
        .error-msg { display: none; padding: 32px 0; }
        .error-msg.active { display: block; }
        .error-msg p {
            font-size: var(--fs-small);
            color: var(--danger);
        }

        /* Results */
        .results {
            display: none;
            opacity: 0;
            background: var(--surface);
            border: 1px solid var(--border);
            padding: 40px 32px;
            margin: 0 -32px /* spacing: full-bleed pair with the padding above -- negative margin pulls .results flush to its ancestor's edge, matching padding keeps content visually inset by the same amount. Moved to -32px together with the padding's 36->32 ladder snap so the pair stays symmetric instead of drifting apart */;
        }
        .results.active { display: block; animation: fadeIn 0.6s ease forwards; }

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

        .result-header {
            border-left: 3px solid var(--accent);
            padding: 0 0 0 20px;
            margin-bottom: 40px;
        }

        /* family/size/weight/letter-spacing now come from the r-h1 role class (added on the
           #resultAddress element below) -- all four matched this rule's own values exactly, so
           adopting the role changes nothing except line-height (kept here as a page override,
           same specificity as the role class, page <style> loads after roles.css so it wins). */
        .result-address {
            color: var(--ink);
            line-height: 1.3 /* line-height: address heading, looser than r-h1's 1.2 for a long street address */;
            margin-bottom: 12px;
        }

        .result-meta {
            display: flex;
            gap: 24px;
            font-family: var(--font-mono);
            font-size: var(--fs-caption);
            color: var(--muted-strong);
            letter-spacing: 0 /* tracking: result-meta row is not tracked/uppercase (bed/bath/sqft), unlike r-label */;
        }

        .result-meta .highlight {
            color: var(--accent-strong);
        }

        /* Stats Grid */
        .stats-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 12px;
            margin-bottom: 48px;
        }

        .stat-card {
            padding: 24px 20px;
            background: var(--surface);
            border: 1px solid var(--border);
            border-top: 2px solid var(--accent);
            transition: background var(--dur-slow), border-color var(--dur-slow);
        }

        .stat-card:hover {
            background: var(--surface);
            border-color: var(--border);
            border-top-color: var(--up);
        }

        /* C7（2026-08-31）：字号与字距交给 .r-label--body —— 实测逐项相同
           （Inter / 11px / 400 / 17.6px / 2px）。⚠️ 原注释写「no token matches 2px」，
           那是 .r-label--body 存在**之前**的话，已经过期。 */
        .stat-label {
            text-transform: uppercase;
            color: var(--muted-strong);
            margin-bottom: 12px;
        }

        .stat-value {
            font-family: var(--font-mono);
            font-size: var(--fs-h1);
            font-weight: 400;
            color: var(--ink);
            line-height: var(--lh-flush);
        }

        /* Task 13(design-language-unification): was .stat-sub — font-size/color dropped, .a-caption
           (styles/roles.css) already supplies the same values. font-family:mono had no documented
           reason to differ from a-caption's font-body, so it's dropped too (this is a sub-line of
           prose — "Avg: $450,000" — not a standalone metric like .stat-value/.r-value, which keep
           mono deliberately). Scoped to .stat-card so it doesn't reach the page's other .a-caption
           use (#scatterCard). */
        .stat-card .a-caption {
            margin-top: 12px;
        }

        /* Map */
        .map-section {
            margin-bottom: 48px;
        }

        /* family/weight/size/letter-spacing now come from the r-h2 role class; see the
           .market-context-header .mc-title comment above for the identical reasoning. */
        .map-title, .comps-title {
            text-transform: none;
            color: var(--ink);

            margin-bottom: 16px;
            display: flex;
            align-items: center;
            gap: 12px;
        }

        .map-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 16px;
        }

        .map-header .map-title {
            margin-bottom: 0;
        }

        .map-style-btns {
            display: flex;
            gap: 4px;
        }

        /* .map-style-btns button adopts .a-btn.a-btn--quiet (added on each <button> below) --
           font-family/font-size/letter-spacing/padding/border/colour/hover all now come from
           the shared role. .active (the selected map style) has no equivalent in the two role
           variants, so it stays a page-specific state layered on top, same pattern as
           .amenity-toggles button.on below it. */
        .map-style-btns button.active {
            background: var(--accent-soft);
            border-color: var(--accent);
            color: var(--accent-strong);
        }

        .map-controls-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 12px;
        }

        .amenity-toggles {
            display: flex;
            gap: 4px;
            flex-wrap: wrap;
        }

        .amenity-toggles button {
            padding: 4px 12px;
            background: transparent;
            border: 1px solid var(--border);
            color: var(--muted-strong);
            font-family: var(--font-body);
            font-size: var(--fs-caption);
            letter-spacing: 0.5px /* tracking: amenity toggle-button tracking, no token matches 0.5px -- not in this task's button-role list */;
            cursor: pointer;
            transition: all var(--dur);
            display: flex;
            align-items: center;
            gap: 4px;
        }

        .amenity-toggles button .amenity-dot {
            width: 6px;
            height: 6px;
            border-radius: 50%;
            opacity: 0.5;
            transition: opacity var(--dur);
        }

        .amenity-toggles button:hover {
            border-color: var(--border);
            color: var(--body);
        }

        .amenity-toggles button.on {
            background: var(--surface);
            border-color: var(--border);
            color: var(--ink);
        }

        .amenity-toggles button.on .amenity-dot {
            opacity: 1;
        }

        .amenity-toggles button.loading {
            opacity: 0.5;
            pointer-events: none;
        }

        .amenity-label {
            font-size: var(--fs-caption);
            letter-spacing: 1.5px /* tracking: amenity-filter row label tracking, no token matches 1.5px */;
            text-transform: uppercase;
            color: var(--muted-strong);
            margin-right: 8px;
        }

        /* Amenity markers */
        .marker-amenity {
            width: 18px;
            height: 18px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: var(--fs-caption);
            line-height: var(--lh-flush);
            box-shadow: var(--shadow-sm);
            border: 1px solid var(--border);
        }

        .leaflet-popup-content .amenity-popup-name {
            font-weight: 500;
            font-size: var(--fs-small);
            color: var(--ink);
        }

        .leaflet-popup-content .amenity-popup-type {
            font-size: var(--fs-caption);
            color: var(--muted-strong);
            text-transform: uppercase;
            letter-spacing: 0.5px /* tracking: map-popup amenity type tracking, no token matches 0.5px */;
        }

        #compMap {
            width: 100%;
            height: var(--h-map);
            border: 1px solid var(--border);
        }

        /* Custom map markers */
        .marker-subject {
            background: var(--accent);
            border: 2px solid var(--ink);
            border-radius: 50%;
            width: 16px;
            height: 16px;
            box-shadow: var(--ring-strong), var(--shadow); /* Task 16: was a hand-written copy of --ring-strong's exact value (--accent at 22%, 4px spread) — reused the token instead of re-deriving it */
        }

        .marker-comp {
            background: var(--surface);
            border: 1.5px solid var(--accent);
            border-radius: 50%;
            width: 26px;
            height: 26px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: var(--font-mono);
            font-size: var(--fs-caption);
            font-weight: 500;
            color: var(--accent-strong);
            line-height: var(--lh-flush);
            box-shadow: var(--shadow);
            transition: transform var(--dur), box-shadow var(--dur);
        }

        .marker-comp:hover {
            transform: scale(1.2);
            box-shadow: var(--ring), var(--shadow); /* Task 16: was a hand-written copy of --ring's exact value (--accent at 30%, 3px spread) — reused the token instead of re-deriving it */
        }

        .leaflet-popup-content-wrapper {
            background: var(--surface);
            color: var(--ink);
            border-radius: 0;
            border: 1px solid var(--border);
            box-shadow: var(--shadow-lg);
        }

        .leaflet-popup-tip {
            background: var(--surface);
            border: 1px solid var(--border);
            border-top: none;
            border-left: none;
        }

        .leaflet-popup-content {
            margin: 12px 16px;
            font-family: var(--font-body);
            font-size: var(--fs-small);
            font-weight: var(--fw-body);
            line-height: var(--lh-reset);
        }

        .leaflet-popup-content .popup-name {
            font-weight: 500;
            font-size: var(--fs-small);
            color: var(--ink);
            margin-bottom: 4px;
        }

        .leaflet-popup-content .popup-price {
            font-family: var(--font-mono);
            color: var(--accent-strong);
            font-size: var(--fs-small);
            font-weight: 500;
        }

        .leaflet-popup-content .popup-detail {
            color: var(--muted-strong);
            font-size: var(--fs-caption);
        }

        .leaflet-popup-content .popup-metrics {
            font-family: var(--font-mono);
            font-size: var(--fs-caption);
            color: var(--body);
            margin: 4px 0;
            padding: 4px 0;
            border-top: 1px solid var(--border);
            border-bottom: 1px solid var(--border);
        }

        .leaflet-popup-content .popup-sv {
            display: inline-block;
            margin-top: 8px;
            font-size: var(--fs-caption);
            letter-spacing: var(--track-label);
            text-transform: uppercase;
            color: var(--accent-strong);
            text-decoration: none;
            transition: color var(--dur);
        }

        .leaflet-popup-content .popup-sv:hover {
            color: var(--ink);
        }

        .leaflet-popup-close-button {
            color: var(--muted-strong) !important;
        }

        /* Dark map tiles adjustments — only for OSM */
        .map-dark-tiles .leaflet-tile-pane { filter: saturate(0.4) brightness(0.82) contrast(1.05); }
        .leaflet-control-zoom a {
            background: var(--canvas) !important;
            color: var(--body) !important;
            border-color: var(--border) !important;
        }
        .leaflet-control-attribution {
            background: var(--canvas) !important;
            color: var(--muted-strong) !important;
            font-size: var(--fs-caption) !important;
        }
        .leaflet-control-attribution a { color: var(--muted-strong) !important; }

        /* Comps Table */
        .comps-section {
            margin-bottom: 48px;
        }


/* Task 13(design-language-unification): was .comps-sample-note — collapsed into .a-scope
   (styles/roles.css); font-family/weight/color dropped, no documented reason found for the old
   mono/500/muted-strong choice (a-scope's font-body/400/muted-strong is the same colour anyway).
   Dashed-box treatment is structural, kept scoped to .comps-section — the only .a-scope use in
   this file, so no leak risk, but scoped on principle rather than left bare. */
.comps-section .a-scope{background:var(--surface);border:1px dashed var(--border);
            border-radius:var(--radius);padding:8px 12px;margin:12px 0 0}
        .comps-table-wrap {
            overflow-x: auto;
            margin: 0 -16px /* spacing: full-bleed horizontal-scroll trick, paired with padding:16px below -- negative margin pulls this wrapper flush to its ancestor's edge so the scrollbar/clip runs edge to edge, matching padding keeps the table content visually inset by the same amount. Both sides already equal 16px (on the ladder), so nothing to snap -- kept exactly as-is rather than touched */;
            padding: 0 16px;
        }

        .comps-table {
            width: 100%;
            border-collapse: collapse;
            min-width: 800px;
        }

        .comps-table th {
            font-size: var(--fs-caption);
            letter-spacing: 1.5px /* tracking: comps-table header tracking, no token matches 1.5px */;
            text-transform: uppercase;
            color: var(--muted-strong);
            text-align: right;
            padding: 12px 16px;
            border-bottom: 1px solid var(--border);
            background: var(--surface);
            font-weight: 400;
            white-space: nowrap;
        }

        .comps-table th:first-child {
            text-align: center;
            width: 36px;
        }

        .comps-table th:nth-child(2) {
            text-align: left;
        }

        .comps-table td {
            padding: 16px 16px;
            font-size: var(--fs-small);
            color: var(--body);
            border-bottom: 1px solid var(--border);
            text-align: right;
            vertical-align: top;
            white-space: nowrap;
            font-variant-numeric: tabular-nums; /* Task 18(design-language-unification): table of
              figures (price/sqft/date columns) -- digits should line up down the column. Applies
              harmlessly to the left-aligned address column (:nth-child(2)) too, which has no digits
              to affect. */
        }

        .comps-table td:first-child {
            text-align: center;
        }

        .comps-table td:nth-child(2) {
            text-align: left;
            white-space: normal;
        }

        .comps-table td.price {
            font-family: var(--font-mono);
            color: var(--accent-strong);
            font-weight: 400;
            font-size: var(--fs-small);
        }

        .comps-table tr {
            transition: background var(--dur);
        }

        .comps-table tr:hover {
            background: var(--surface);
        }

        .comps-table tr:hover td {
            color: var(--ink);
        }

        .comps-table tr:hover td.price {
            color: var(--ink);
        }

        .comp-stats-header {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            margin-bottom: 20px;
            border-top: 1px solid var(--border);
            padding-top: 24px;
        }

        /* family/weight/size/letter-spacing now come from the r-h2 role class; see the
           .market-context-header .mc-title comment above for the identical reasoning. */
        .comp-stats-header .mc-title {
            text-transform: none;
            color: var(--ink);

            display: flex;
            align-items: center;
            gap: 12px;
        }

        .comp-stats-header .mc-period {
            font-size: var(--fs-caption);
            line-height: var(--lh-panel);
            color: var(--muted-strong);
        }

        /* (Full Report "locked-section" / "cta-bar" styles removed — block extracted to Prism hub) */

        /* Footer */
        .a-disclosure { margin-top: 40px; }   /* look comes from the shared role in styles/roles.css (Task 13: moved off tokens.css's retired .disclosure) */

        footer {
            border-top: 1px solid var(--border);
            padding: 20px 24px;
            text-align: center;
            font-size: var(--fs-small);
            color: var(--muted-strong);
        }

        footer span {
            font-size: var(--fs-small);
            color: var(--muted-strong);
        }

        footer a {
            color: var(--muted-strong);
            text-decoration: none;
            font-size: var(--fs-small);
            transition: color var(--dur-slow);
        }

        footer a:hover { color: var(--ink); }

        /* Responsive */
        @media (max-width: 768px) {
            .main { padding: 110px 20px 72px /* spacing: 768px breakpoint of the same page-level hero clearance, see .main above */; }
            .page-desc { font-size: var(--fs-small); }
            .result-meta { flex-wrap: wrap; gap: 12px; }
            .stat-value { font-size: var(--fs-h2); }
            .stats-grid { grid-template-columns: repeat(2, 1fr); }
            .stat-card { border-bottom: 1px solid var(--border); }
            .stat-card:nth-child(2) { border-right: none; }
            .search-box { flex-direction: column; }
            .mc-grid, .mc-rent-row { grid-template-columns: repeat(2, 1fr); }
            .sm-compare { grid-template-columns: 1fr; }
            .sm-ranking-row { grid-template-columns: 1.5fr 1fr 0.8fr 1fr; font-size: var(--fs-caption); }
            footer { padding: 24px 20px; flex-direction: column; gap: 12px; }
        }

        /* 🔴 2026-09-01 补两条窄口规则 —— 两处**既有**缺陷，390px 实测抓到，
           拆页前的树（da6707a）逐条复现同样三条，所以不是拆页带出来的。

           ① .results 带着 `margin: 0 -32px` 做出血效果（与它自己的 32px 内边距配对）。
              桌面上那是对的；390px 上父容器只剩 16px 内边距，负边距把它左右各拉出 16px，
              实测 left=−16 / right=406。窄口不需要出血 —— 页面本来就窄。
           ② .map-header 是一行 flex：标题占 125px，底图切换那一排按钮 276px 宽，
              剩下 231px 放不下，实测 right=401。让它在窄口换成两行即可，按钮一个都不用删。

           ⚠️ 这两条都**只有在真浏览器里量才看得见**：页面的 scrollWidth 始终是 390
              （祖先没让它撑开），所以「页面能不能横向滚动」这个判据对它们永远是绿的。 */
        @media (max-width: 480px) {
            .results { padding: 24px 16px; margin: 0; }
            .map-header { flex-direction: column; align-items: flex-start; gap: 12px; }
            .map-style-btns { flex-wrap: wrap; }
            .main { padding: 100px 16px 56px /* spacing: 480px breakpoint of the same page-level hero clearance as the desktop/768px rules above -- 100px and 56px are part of that scaled-down rhythm, not independent component paddings, so 56px is kept even though its magnitude alone would fall inside the 10-step ladder */; }
            .stat-value { font-size: var(--fs-h2); }
            .stat-card { padding: 16px 12px; }
            .sm-ranking-row { font-size: var(--fs-caption); grid-template-columns: 1.5fr 1fr 0.8fr 1fr; }
            .mc-grid, .mc-rent-row { grid-template-columns: 1fr; }
            footer { padding: 20px 16px; }
        }
