gendesign/frontend/src/app/site-finder/analysis/[cad]/ptica/ptica.module.css
Light1YT fcaa4d197b
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 59s
CI / openapi-codegen-check (pull_request) Successful in 1m57s
fix(ptica): visual parity 1:1 with prototype (incl. ОКС empty-card stretch)
Multi-region audit vs the prototype styles.css/dashboard.html → 22 CSS fixes +
markup. Headline: .lowerGrid/.bottomGrid align-items:start so the sparse ОКС
«нет данных» card no longer stretches into a huge void. Plus restore missing
prototype bits: topbar icon-buttons (Экспорт→Отчёты, Полный экран→fullscreen,
Настройки aria-disabled «скоро»), rail help-dot, app-footer (honest disclaimer,
no fabricated model/timestamp), invest-score sparkline (decorative, aria-hidden),
green/amber value tones, engineering status-row dots (from real nearest_m),
verdict-card gradient, scan-card status-rows, .canvas/.hbar/.mixHead spacing.

All new CSS scoped under .pticaRoot[data-theme=dark] (status-row classes
descendant-scoped under .scanCard — no collision with the drawer's). Environment
card stays honest kvrow placeholders (no fake dots). a11y: aria-labels on icon
buttons. TS strict, no any. tsc/lint/prettier/build green. code-reviewer .
2026-06-21 01:10:36 +05:00

2640 lines
51 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* ПТИЦА cockpit — dark "operator terminal" styles (INCREMENT 1).
*
* Ported from the prototype tokens.css + styles.css. Every design token is
* scoped under `.pticaRoot[data-theme="dark"]` (the wrapper carries BOTH the
* class and the attribute) so these dark vars NEVER leak onto the existing
* light analysis pages. Cockpit class styles reference tokens via var(--…).
*/
/* ===================== SCOPED TOKENS (dark) ===================== */
.pticaRoot[data-theme="dark"] {
--font-ui:
"Inter", "Manrope", -apple-system, "Segoe UI", system-ui, sans-serif;
--font-mono:
var(--font-plex-mono), "IBM Plex Mono", "Roboto Mono", ui-monospace,
monospace;
--radius-xs: 4px;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--line: 1px;
--line-strong: 1.5px;
/* accents */
--accent-blue: #2f6f9f;
--accent-cyan: #7fd0ee;
--accent-cyan-strong: #8ed3ff;
--accent-green: #38c172;
--accent-yellow: #e0a93b;
--accent-red: #d2655b;
--accent-purple: #9b80d6;
--accent-orange: #e46c35;
/* resource (connection-point) accents — ported from prototype tokens.css */
--res-electric: #e6b23c;
--res-water: #4d9bd7;
--res-sewer: #9b80d6;
--res-heat: #e46c35;
--res-gas: #d2655b;
--res-poi: #3fa77d;
--gauge-track: 9px;
/* dark surface palette */
--bg: #060f16;
--bg-2: #08131c;
--grid-line: rgba(120, 165, 190, 0.06);
--surface: rgba(11, 26, 36, 0.72);
--surface-2: rgba(9, 22, 31, 0.62);
--surface-strong: rgba(12, 30, 42, 0.94);
--surface-muted: rgba(26, 52, 67, 0.55);
--surface-inset: rgba(4, 12, 18, 0.6);
--text: #dcebf2;
--text-strong: #f2fafe;
--text-muted: #8ba6b3;
--text-soft: #5f7886;
--border: rgba(150, 192, 214, 0.18);
--border-strong: rgba(150, 192, 214, 0.4);
--border-faint: rgba(150, 192, 214, 0.1);
--map-bg: #0a1820;
--map-filter: saturate(0.7) contrast(1.12) brightness(0.7);
--map-filter-sat: brightness(0.62) saturate(0.8) contrast(1.05);
--glow: rgba(110, 200, 240, 0.28);
--glow-strong: rgba(110, 200, 240, 0.55);
--shadow: 0 18px 70px rgba(0, 0, 0, 0.5);
--gauge-good: var(--accent-green);
/* root paint */
position: relative;
min-height: 100vh;
font-family: var(--font-ui);
color: var(--text);
font-size: 13px;
line-height: 1.4;
font-variant-numeric: tabular-nums;
-webkit-font-smoothing: antialiased;
background:
linear-gradient(90deg, transparent 31px, var(--grid-line) 32px),
linear-gradient(0deg, transparent 31px, var(--grid-line) 32px),
radial-gradient(circle at 78% -10%, var(--glow), transparent 45%), var(--bg);
background-size:
32px 32px,
32px 32px,
100% 100%,
100% 100%;
}
.pticaRoot button {
font: inherit;
cursor: pointer;
color: inherit;
}
.pticaRoot svg {
display: block;
}
.mono {
font-family: var(--font-mono);
}
/* ===================== APP SHELL ===================== */
.shell {
display: grid;
grid-template-columns: 96px 1fr;
min-height: 100vh;
}
/* sidebar rail */
.rail {
border-right: var(--line) solid var(--border);
background: var(--surface-2);
backdrop-filter: blur(8px);
display: flex;
flex-direction: column;
padding: 16px 10px 12px;
gap: 14px;
position: sticky;
top: 0;
height: 100vh;
}
.brand {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding-bottom: 6px;
}
.brandMark {
width: 40px;
height: 40px;
color: var(--accent-cyan);
filter: drop-shadow(0 0 10px var(--glow));
}
.nav {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 4px;
}
.navItem {
position: relative;
display: grid;
place-items: center;
gap: 6px;
min-height: 60px;
padding: 8px 4px;
border: var(--line) solid transparent;
border-radius: var(--radius-sm);
color: var(--text-soft);
background: transparent;
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.05em;
text-align: center;
transition:
color 0.15s,
background 0.15s;
}
.navItem svg {
width: 22px;
height: 22px;
}
.navItem:hover {
color: var(--text-muted);
background: var(--surface);
}
/* Active rail item — cyan accent + inset glow + left accent bar (prototype
.nav-item.active / .nav-item.active::before). */
.navItemActive,
.navItem.navItemActive {
color: var(--accent-cyan);
background: var(--surface);
border-color: var(--border);
box-shadow: inset 0 0 22px var(--glow);
}
.navItemActive::before {
content: "";
position: absolute;
left: -10px;
top: 14px;
bottom: 14px;
width: 2px;
background: var(--accent-cyan);
box-shadow: 0 0 10px var(--accent-cyan);
}
.navFoot {
margin-top: auto;
display: grid;
gap: 8px;
justify-items: center;
color: var(--text-soft);
}
.navFoot .mono {
font-size: 8px;
text-align: center;
line-height: 1.5;
}
/* Rail nav-foot help-dot (?) button (prototype .help-dot). */
.helpDot {
width: 24px;
height: 24px;
border-radius: 50%;
border: var(--line) solid var(--border-strong);
display: grid;
place-items: center;
font-size: 11px;
background: transparent;
color: var(--text-soft);
cursor: pointer;
}
/* main column */
.main {
display: flex;
flex-direction: column;
min-width: 0;
}
/* topbar */
.topbar {
position: sticky;
top: 0;
z-index: 20;
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 24px;
padding: 12px 20px;
border-bottom: var(--line) solid var(--border);
background: var(--surface-2);
backdrop-filter: blur(8px);
}
.wordmark {
display: flex;
align-items: center;
gap: 12px;
}
.bird {
width: 26px;
height: 26px;
color: var(--accent-cyan);
}
.wordmarkTitle {
font-weight: 700;
letter-spacing: 0.42em;
font-size: 18px;
color: var(--text-strong);
}
.wordmarkSub {
color: var(--text-soft);
font-size: 8.5px;
line-height: 1.25;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.tabs {
display: flex;
justify-content: center;
gap: 0;
}
.tab {
padding: 8px 22px;
background: transparent;
border: none;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.14em;
font-size: 11px;
border-bottom: 2px solid transparent;
}
.tab + .tab {
border-left: var(--line) solid var(--border);
}
.tab:hover {
color: var(--text);
}
.tabActive {
color: var(--text-strong);
border-bottom-color: var(--accent-cyan);
}
.sysbar {
display: flex;
align-items: center;
gap: 16px;
color: var(--text-muted);
}
.clock {
font-family: var(--font-mono);
font-size: 12px;
text-align: right;
line-height: 1.3;
}
.clock b {
color: var(--text);
font-weight: 600;
}
/* Topbar sysbar icon-buttons (prototype .icon-btn). */
.iconBtn {
width: 30px;
height: 30px;
border: var(--line) solid transparent;
border-radius: var(--radius-xs);
background: transparent;
display: grid;
place-items: center;
color: var(--text-muted);
}
.iconBtn:hover {
color: var(--text);
background: var(--surface);
border-color: var(--border);
}
.sysbar .iconBtn + .iconBtn {
margin-left: -2px;
}
.iconBtn svg {
width: 16px;
height: 16px;
}
/* canvas (scroll area) */
.canvas {
padding: 14px 18px 8px;
display: flex;
flex-direction: column;
gap: 12px;
}
/* Bottom disclaimer footer bar (prototype .app-footer). */
.appFooter {
display: flex;
justify-content: space-between;
gap: 16px;
padding: 10px 20px 14px;
border-top: var(--line) solid var(--border);
color: var(--text-soft);
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.06em;
font-family: var(--font-mono);
}
/* ===================== PANEL / CARD primitives ===================== */
.panel,
.card,
.mapCard {
position: relative;
background: var(--surface);
border: var(--line) solid var(--border);
border-radius: var(--radius-md);
box-shadow: var(--shadow);
backdrop-filter: blur(6px);
}
.panel,
.card {
padding: 14px 15px;
}
.cardHead {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 12px;
}
.cardTitle {
display: flex;
align-items: baseline;
gap: 8px;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--text-strong);
margin: 0;
}
.cardTitleSub {
font-size: 9px;
font-weight: 500;
letter-spacing: 0.08em;
color: var(--text-soft);
}
.detailBtn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 11px;
border: var(--line) solid var(--border-strong);
background: transparent;
color: var(--text-muted);
border-radius: var(--radius-xs);
text-transform: uppercase;
font-size: 9px;
letter-spacing: 0.08em;
white-space: nowrap;
}
.detailBtn::after {
content: "→";
font-size: 11px;
}
.detailBtn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
/* «Подробнее» pinned to the bottom-left of a flex-column card (scan / score). */
.detailBtnScan {
margin-top: auto;
align-self: flex-start;
}
/* ===================== HERO GRID ===================== */
.hero {
display: grid;
grid-template-columns: 1.7fr 1.02fr 0.62fr;
gap: 12px;
}
/* map card */
.mapCard {
min-height: 358px;
overflow: hidden;
background: var(--map-bg);
}
.mapMount {
position: absolute;
inset: 0;
border-radius: inherit;
overflow: hidden;
}
.mapMount :global(.leaflet-container) {
width: 100%;
height: 100%;
background: transparent;
font-family: var(--font-ui);
}
.mapMount :global(.leaflet-tile-pane) {
filter: var(--map-filter);
}
/* satellite base → its own (darker) filter so markers stay readable */
.mapMount.baseSat :global(.leaflet-tile-pane) {
filter: var(--map-filter-sat);
}
/* dark, glassy Leaflet popups (match the cockpit chrome) */
.mapMount :global(.leaflet-popup-content-wrapper) {
background: var(--surface-strong);
color: var(--text);
border: var(--line) solid var(--border);
border-radius: var(--radius-sm);
}
.mapMount :global(.leaflet-popup-tip) {
background: var(--surface-strong);
}
.mapMount :global(.leaflet-popup-content) {
margin: 9px 11px;
font-family: var(--font-ui);
}
.mapMount :global(.leaflet-container a.leaflet-popup-close-button) {
color: var(--text-muted);
}
/* data-driven divIcon pins (resource + POI markers) */
.mapMount :global(.map-pin) {
width: 22px;
height: 22px;
border-radius: 50%;
display: grid;
place-items: center;
color: #08131c;
border: 1.5px solid rgba(255, 255, 255, 0.85);
box-shadow:
0 0 0 3px rgba(0, 0, 0, 0.25),
0 0 14px currentColor;
}
.mapMount :global(.map-pin svg) {
width: 13px;
height: 13px;
}
.mapMount :global(.map-pin.poi-dot) {
width: 14px;
height: 14px;
border-width: 1.5px;
}
.mapMount :global(.parcel-glow) {
filter: drop-shadow(0 0 6px var(--glow-strong));
}
.mapMount :global(.conn-glow) {
filter: drop-shadow(0 0 5px currentColor);
}
.mapMount :global(.leaflet-control-attribution) {
background: rgba(8, 19, 28, 0.55);
color: var(--text-soft);
font-size: 8px;
padding: 1px 6px;
opacity: 0.6;
backdrop-filter: blur(4px);
}
.mapMount :global(.leaflet-control-attribution a) {
color: var(--text-muted);
}
.mapLoading {
position: absolute;
inset: 0;
display: grid;
place-items: center;
color: var(--text-soft);
font-size: 12px;
background: var(--map-bg);
}
/* passport KV-grid */
.kvGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--line);
background: var(--border-faint);
border: var(--line) solid var(--border-faint);
border-radius: var(--radius-sm);
overflow: hidden;
}
.kv {
background: var(--surface-2);
padding: 9px 11px;
}
.kvK {
display: block;
font-size: 8.5px;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--text-soft);
margin-bottom: 3px;
}
.kvV {
font-size: 12.5px;
font-weight: 600;
color: var(--text);
}
.kvV.mono {
font-family: var(--font-mono);
font-size: 12px;
}
.kvWide {
grid-column: span 2;
}
.kvPlaceholder {
color: var(--text-soft);
font-weight: 500;
}
.kvCaption {
display: block;
margin-top: 2px;
font-size: 8px;
text-transform: none;
letter-spacing: 0.02em;
color: var(--text-soft);
}
/* score panel (hero col 3) */
.scorePanel {
display: flex;
flex-direction: column;
gap: 10px;
}
.gaugePanel {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
/* compact, centred title above a radial gauge. */
.cardTitleGauge {
font-size: 9.5px;
text-align: center;
}
.scoreStack {
display: flex;
flex-direction: column;
gap: 8px;
}
.statLine {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 11px;
}
.statLine .k {
color: var(--text-soft);
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 9px;
}
.statLine .v {
font-weight: 600;
}
.investScore {
display: flex;
align-items: baseline;
gap: 6px;
}
.investBig {
font-size: 26px;
font-weight: 700;
color: var(--text-strong);
font-family: var(--font-mono);
}
.investSmall {
color: var(--text-soft);
font-size: 11px;
}
.investPlaceholder {
color: var(--text-soft);
}
/* Hero stat-line semantic value colors (prototype .v.good / .v.warn). */
.vGood {
color: var(--accent-green);
}
.vWarn {
color: var(--accent-yellow);
}
/* Hero invest-score sparkline (prototype .sparkline). */
.sparkline {
display: flex;
align-items: flex-end;
gap: 3px;
height: 26px;
}
.sparkline span {
flex: 1;
background: linear-gradient(var(--accent-cyan), var(--accent-blue));
border-radius: 1px;
min-height: 3px;
opacity: 0.85;
}
.badge {
display: inline-block;
padding: 2px 9px;
border-radius: 999px;
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.06em;
border: var(--line) solid var(--border-strong);
color: var(--text-soft);
}
.fieldCaption {
font-size: 8px;
color: var(--text-soft);
text-transform: none;
letter-spacing: 0.02em;
}
/* ===================== GAUGE ===================== */
.gauge {
position: relative;
width: 122px;
height: 122px;
margin: 2px auto;
}
.gauge svg {
width: 100%;
height: 100%;
transform: rotate(-90deg);
}
.gaugeTrack {
fill: none;
stroke: var(--surface-muted);
}
.gaugeProg {
fill: none;
stroke-linecap: round;
filter: drop-shadow(0 0 6px var(--glow));
transition: stroke-dashoffset 0.9s ease;
}
.gaugeGood {
stroke: var(--accent-green);
}
.gaugeWarn {
stroke: var(--accent-yellow);
}
.gaugeBad {
stroke: var(--accent-red);
}
.gaugeNeutral {
stroke: var(--accent-cyan);
}
.gaugeCenter {
position: absolute;
inset: 0;
display: grid;
place-content: center;
text-align: center;
}
.gaugeNum {
font-size: 30px;
font-weight: 700;
line-height: 1;
color: var(--text-strong);
font-family: var(--font-mono);
}
.gaugeNumGood {
color: var(--accent-green);
}
.gaugeNumWarn {
color: var(--accent-yellow);
}
.gaugeNumBad {
color: var(--accent-red);
}
.gaugeLab {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--text-muted);
margin-top: 5px;
}
.gaugeFootnote {
font-size: 8px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-soft);
}
/* ===================== SCAN GRID ===================== */
.sectionLabel {
display: flex;
align-items: center;
gap: 10px;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.16em;
color: var(--text-soft);
margin: 2px 2px -2px;
}
.sectionLabel::after {
content: "";
flex: 1;
height: 1px;
background: var(--border);
}
.sectionLabel b {
color: var(--accent-cyan);
font-weight: 600;
letter-spacing: 0.12em;
}
.scanGrid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 10px;
}
.scanCard {
padding: 12px 12px 11px;
min-height: 156px;
display: flex;
flex-direction: column;
}
.scanCard .cardTitle {
margin-bottom: 10px;
font-size: 10px;
}
.kvrow {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
font-size: 11px;
padding: 3px 0;
}
.kvrow .k {
color: var(--text-muted);
}
.kvrow .v {
font-weight: 600;
color: var(--text);
text-align: right;
}
.kvrowPlaceholder .v {
color: var(--text-soft);
font-weight: 500;
}
.scanCard .detailBtn {
margin-top: auto;
align-self: flex-start;
}
/* Scan status-rows (prototype Инженерия / Экология .status-row): dot + label
+ state. Scoped under .scanCard so they never collide with the unrelated
DRAWER .statusRow variant (drDot / drLabel / drState). */
.scanCard .statusRow {
display: flex;
align-items: center;
gap: 7px;
font-size: 11px;
padding: 3px 0;
}
.scanCard .dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--accent-green);
box-shadow: 0 0 6px currentColor;
color: var(--accent-green);
flex: none;
}
.scanCard .dotWarn {
background: var(--accent-yellow);
color: var(--accent-yellow);
}
.scanCard .dotBad {
background: var(--accent-red);
color: var(--accent-red);
}
.scanCard .label {
flex: 1;
color: var(--text-muted);
}
.scanCard .state {
font-size: 10px;
}
.scanCard .stateOk {
color: var(--accent-green);
}
.scanCard .stateWarn {
color: var(--accent-yellow);
}
/* Risks card centres its gauge; title stays left-aligned like the other cards. */
.scanCardCentered {
align-items: center;
}
.scanCardCentered .cardTitle {
align-self: flex-start;
}
.emptyState {
display: grid;
place-items: center;
flex: 1;
text-align: center;
color: var(--text-soft);
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.08em;
}
/* ===================== PLACEHOLDER PANEL ===================== */
.placeholderPanel {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
text-align: center;
min-height: 240px;
border-style: dashed;
}
.placeholderPanel .soon {
font-size: 22px;
letter-spacing: 0.12em;
color: var(--text-soft);
font-weight: 700;
text-transform: uppercase;
}
.placeholderPanel p {
font-size: 10px;
color: var(--text-soft);
margin: 0;
max-width: 280px;
}
/* ===================== STATE SCREENS ===================== */
.stateScreen {
display: grid;
place-items: center;
min-height: 60vh;
padding: 40px 24px;
}
.stateBox {
text-align: center;
color: var(--text-muted);
font-size: 14px;
}
.stateError {
border: var(--line) solid var(--accent-red);
background: rgba(210, 101, 91, 0.08);
color: var(--accent-red);
border-radius: var(--radius-md);
padding: 16px 20px;
}
.stateLink {
display: inline-block;
margin-top: 16px;
color: var(--accent-cyan);
text-decoration: none;
font-size: 13px;
}
/* ===================== SCENARIOS (§22 Прогноз) ===================== */
.scenariosRoot {
display: flex;
flex-direction: column;
gap: 12px;
}
/* view header (title + horizon selector) */
.viewHead {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
}
.viewHead h2 {
margin: 0;
font-size: 15px;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--text-strong);
}
.viewSub {
font-size: 10px;
color: var(--text-soft);
letter-spacing: 0.08em;
margin-left: 8px;
text-transform: none;
}
.horizonSel {
display: flex;
align-items: center;
gap: 8px;
}
.horizonLab {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-soft);
}
.seg {
display: inline-flex;
border: var(--line) solid var(--border);
border-radius: var(--radius-xs);
overflow: hidden;
}
.seg button {
padding: 6px 14px;
background: transparent;
border: none;
color: var(--text-muted);
font-size: 11px;
border-right: var(--line) solid var(--border);
transition:
color 0.15s,
background 0.15s;
}
.seg button:last-child {
border-right: none;
}
.seg button:hover {
color: var(--text);
}
.segActive,
.seg button.segActive {
background: var(--accent-blue);
color: #fff;
}
/* 3 scenario cards */
.scenarioGrid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.scenarioCard {
border-top: 2px solid var(--accent-cyan);
}
.scAccentBase {
border-top-color: var(--accent-blue);
}
.scAccentAggr {
border-top-color: var(--accent-green);
}
.scAccentCons {
border-top-color: var(--accent-yellow);
}
.scTag {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-soft);
}
.scDeficit {
font-size: 30px;
font-weight: 700;
font-family: var(--font-mono);
margin: 4px 0;
line-height: 1.05;
}
.deficitPos {
color: var(--accent-green);
}
.deficitNeg {
color: var(--accent-red);
}
.deficitFlat {
color: var(--text);
}
.scKvRow {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
font-size: 11px;
padding: 3px 0;
}
/* sc-kvrow that sits below a table/segment block — adds top separation. */
.scKvRowSpaced {
margin-top: 8px;
}
.scK {
color: var(--text-muted);
}
.scV {
font-weight: 600;
color: var(--text);
text-align: right;
}
.scNote {
margin-top: 6px;
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-soft);
}
/* forecast grid (chart + table; confidence + product) */
.forecastGrid {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 10px;
}
/* inline legend */
.legendInline {
display: flex;
gap: 14px;
flex-wrap: wrap;
font-size: 10px;
color: var(--text-muted);
}
.legendInline span {
display: inline-flex;
align-items: center;
gap: 6px;
}
.legendInline i {
width: 14px;
height: 3px;
border-radius: 2px;
}
.legendDemand {
background: var(--accent-cyan);
}
.legendSupply {
background: var(--accent-yellow);
}
/* inline SVG line chart */
.chart {
width: 100%;
height: 180px;
}
.chart svg {
width: 100%;
height: 100%;
overflow: visible;
}
.chartGrid {
stroke: var(--border-faint);
stroke-width: 1;
}
.chartDemand {
fill: none;
stroke: var(--accent-cyan);
stroke-width: 2;
}
.chartSupply {
fill: none;
stroke: var(--accent-yellow);
stroke-width: 2;
stroke-dasharray: 4 4;
}
.chartLbl {
fill: var(--text-soft);
font-size: 9px;
font-family: var(--font-mono);
}
/* data table */
.dtable {
width: 100%;
border-collapse: collapse;
font-size: 11px;
}
.dtable th,
.dtable td {
text-align: left;
padding: 8px 9px;
border-bottom: var(--line) solid var(--border-faint);
}
.dtable th {
color: var(--text-soft);
text-transform: uppercase;
font-size: 8.5px;
letter-spacing: 0.06em;
font-weight: 500;
}
.thNum {
text-align: right;
}
.tdNum {
text-align: right;
font-family: var(--font-mono);
}
.dtable tbody tr:hover td {
background: var(--surface-2);
}
.numPos {
color: var(--accent-green);
}
.numNeg {
color: var(--accent-red);
}
.rowTarget td {
background: rgba(47, 111, 159, 0.16);
}
.targetTag {
margin-left: 8px;
font-size: 8px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--accent-cyan);
}
/* confidence rows + pills */
.confRow {
display: flex;
align-items: center;
gap: 10px;
margin: 6px 0;
font-size: 11px;
}
.confName {
flex: 1;
color: var(--text-muted);
}
.confPill {
padding: 2px 9px;
border-radius: 999px;
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.06em;
border: var(--line) solid currentColor;
}
.confPillHigh {
color: var(--accent-green);
}
.confPillMedium {
color: var(--accent-yellow);
}
.confPillLow {
color: var(--accent-red);
}
/* tag (recommended class) */
.tag {
display: inline-block;
padding: 1px 7px;
border-radius: var(--radius-xs);
font-size: 9px;
background: var(--surface-muted);
color: var(--text);
}
/* horizontal bars (квартирография) */
.hbars {
display: grid;
gap: 7px;
}
.hbar {
display: grid;
grid-template-columns: 110px 1fr 56px;
gap: 9px;
align-items: center;
font-size: 11px;
}
.hbarName {
color: var(--text-muted);
}
.hbarTrack {
height: 5px;
background: var(--surface-muted);
border-radius: 3px;
overflow: hidden;
}
.hbarFill {
height: 100%;
border-radius: 3px;
}
.hbarFillGood {
background: var(--accent-green);
}
.hbarFillBad {
background: var(--accent-red);
}
.hbarFillNeutral {
background: linear-gradient(90deg, var(--accent-blue), var(--accent-cyan));
}
.hbarValue {
text-align: right;
font-family: var(--font-mono);
font-weight: 600;
}
/* hints + empty panels */
.panelHint {
margin: 10px 0 0;
font-size: 10px;
line-height: 1.5;
color: var(--text-soft);
}
.emptyPanel {
display: grid;
place-items: center;
min-height: 96px;
text-align: center;
color: var(--text-soft);
font-size: 11px;
}
/* pending / polling skeleton (calm, NOT an error) */
.forecastPending {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
min-height: 260px;
text-align: center;
}
.pendingPulse {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid var(--border-strong);
border-top-color: var(--accent-cyan);
animation: pticaSpin 0.9s linear infinite;
}
@keyframes pticaSpin {
to {
transform: rotate(360deg);
}
}
.pendingTitle {
font-size: 16px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
}
.pendingHint {
margin: 0;
font-size: 10px;
color: var(--text-soft);
max-width: 320px;
line-height: 1.5;
}
/* ===================== REPORTS (Отчёты) ===================== */
.reportsRoot,
.compareRoot {
display: flex;
flex-direction: column;
gap: 12px;
}
.reportGrid {
display: grid;
grid-template-columns: 1.2fr 1fr;
gap: 12px;
}
/* export-format cards */
.exportGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.exportCard {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
text-align: left;
padding: 14px;
border: var(--line) solid var(--border);
border-radius: var(--radius-sm);
background: var(--surface-2);
color: inherit;
transition:
border-color 0.15s,
box-shadow 0.15s,
opacity 0.15s;
}
.exportCard:hover:not(:disabled):not(.exportCardDisabled) {
border-color: var(--accent-cyan);
box-shadow: 0 0 14px var(--glow);
}
.exportCard:disabled {
opacity: 0.55;
cursor: not-allowed;
}
.exportCardDisabled {
opacity: 0.5;
cursor: not-allowed;
}
.fmt {
width: 40px;
height: 40px;
flex: none;
display: grid;
place-items: center;
border-radius: var(--radius-xs);
font-family: var(--font-mono);
font-weight: 700;
font-size: 11px;
color: #fff;
}
.fmtPdf {
background: var(--accent-red);
}
.fmtCsv {
background: var(--accent-green);
}
.fmtDocx {
background: var(--accent-blue);
}
.fmtPptx {
background: var(--accent-orange);
}
.fmtMuted {
background: var(--surface-muted);
color: var(--text-soft);
}
.exportMeta {
display: flex;
flex-direction: column;
gap: 3px;
min-width: 0;
flex: 1;
}
.exportTitle {
display: flex;
align-items: center;
gap: 7px;
font-weight: 600;
font-size: 12px;
color: var(--text);
}
.exportDesc {
font-size: 10px;
color: var(--text-soft);
}
.exportTag {
padding: 1px 6px;
border-radius: 999px;
font-size: 8px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--accent-yellow);
border: var(--line) solid currentColor;
}
.exportTagSoon {
padding: 1px 6px;
border-radius: 999px;
font-size: 8px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-soft);
border: var(--line) solid var(--border);
}
.exportIco {
flex: none;
color: var(--text-soft);
}
.reportError {
margin: 12px 0 0;
font-size: 11px;
color: var(--accent-red);
}
/* report-section checklist */
.tocList {
display: grid;
gap: 2px;
}
.tocItem {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 0;
border-bottom: var(--line) dashed var(--border-faint);
font-size: 11px;
color: var(--text-muted);
}
.tocIndex {
font-family: var(--font-mono);
font-size: 10px;
color: var(--text-soft);
flex: none;
}
.tocLabel {
flex: 1;
color: var(--text);
}
.tocEyebrow {
font-size: 8px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-soft);
}
.sourceLine {
display: flex;
justify-content: space-between;
gap: 10px;
margin-top: 14px;
font-size: 9px;
color: var(--text-soft);
font-family: var(--font-mono);
}
/* ===================== COMPARE (Сравнение) ===================== */
.compareBar {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.compareInput {
flex: 0 1 320px;
padding: 9px 12px;
background: var(--surface-2);
border: var(--line) solid var(--border);
border-radius: var(--radius-xs);
color: var(--text);
font-family: var(--font-mono);
font-size: 12px;
outline: none;
}
.compareInput:focus {
border-color: var(--accent-cyan);
}
.compareInput:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.compareAddBtn {
padding: 9px 16px;
border: var(--line) solid var(--accent-blue);
background: transparent;
color: var(--text);
border-radius: var(--radius-xs);
text-transform: uppercase;
font-size: 10px;
letter-spacing: 0.08em;
white-space: nowrap;
transition:
background 0.15s,
opacity 0.15s;
}
.compareAddBtn:hover:not(:disabled) {
background: var(--accent-blue);
color: #fff;
}
.compareAddBtn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.chipRow {
display: flex;
gap: 6px;
flex-wrap: wrap;
margin-left: 4px;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border: var(--line) solid var(--border);
border-radius: 999px;
font-size: 10px;
}
.chip .mono {
font-size: 10px;
}
.chipPinned {
border-color: var(--accent-cyan);
}
.chipTag {
font-size: 8px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--accent-cyan);
}
.chipRemove {
display: grid;
place-items: center;
border: none;
background: none;
padding: 0;
color: var(--text-soft);
}
.chipRemove:hover {
color: var(--text);
}
.compareInputError {
margin: 10px 0 0;
font-size: 11px;
color: var(--accent-red);
}
.compareHint {
margin: 0;
padding: 8px 2px;
font-size: 11px;
line-height: 1.5;
color: var(--text-soft);
}
.compareTableWrap {
overflow-x: auto;
}
.compareTable {
width: 100%;
border-collapse: collapse;
font-size: 11px;
min-width: 560px;
}
.compareTable th,
.compareTable td {
padding: 9px 11px;
text-align: right;
border-bottom: var(--line) solid var(--border-faint);
font-family: var(--font-mono);
}
.compareTable th:first-child,
.compareTable td:first-child {
text-align: left;
color: var(--text-muted);
font-family: var(--font-ui);
position: sticky;
left: 0;
background: var(--surface);
}
.compareTable thead th {
color: var(--text-strong);
font-family: var(--font-ui);
font-size: 11px;
vertical-align: top;
}
.compareTable thead th .mono {
font-size: 11px;
word-break: break-all;
}
.compareDistrict {
display: block;
margin-top: 3px;
font-family: var(--font-ui);
font-weight: 400;
font-size: 9px;
color: var(--text-soft);
}
.compareBest {
color: var(--accent-green);
font-weight: 600;
}
.compareVerdictRow td {
background: var(--surface-2);
}
.verdictBuy {
color: var(--accent-green);
font-weight: 600;
}
.verdictWatch {
color: var(--accent-yellow);
font-weight: 600;
}
.verdictSkip {
color: var(--accent-red);
font-weight: 600;
}
.cellMuted {
color: var(--text-soft);
}
.cellError {
color: var(--accent-red);
font-size: 10px;
}
/* ===================== DETAIL DRAWER (PR#4) ===================== */
.drawerScrim {
position: fixed;
inset: 0;
background: rgba(2, 8, 12, 0.62);
backdrop-filter: blur(2px);
opacity: 0;
visibility: hidden;
transition:
opacity 0.18s ease,
visibility 0.18s ease;
z-index: 60;
}
.drawerScrimOpen {
opacity: 1;
visibility: visible;
}
.drawer {
position: fixed;
top: 0;
right: 0;
height: 100vh;
width: 480px;
max-width: 100vw;
background: var(--surface-strong);
border-left: var(--line) solid var(--border-strong);
box-shadow: var(--shadow);
transform: translateX(100%);
transition: transform 0.18s ease;
z-index: 61;
display: flex;
flex-direction: column;
}
.drawerOpen {
transform: translateX(0);
}
.drawerHead {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
padding: 16px 18px;
border-bottom: var(--line) solid var(--border);
flex-shrink: 0;
}
.drawerTitle {
margin: 0;
font-size: 15px;
font-weight: 600;
color: var(--text-strong);
}
.drawerSub {
margin-top: 3px;
font-size: 10px;
color: var(--text-soft);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.drawerClose {
flex-shrink: 0;
display: grid;
place-items: center;
width: 30px;
height: 30px;
border: var(--line) solid var(--border);
border-radius: var(--radius-xs);
background: transparent;
color: var(--text-muted);
}
.drawerClose:hover {
color: var(--text-strong);
border-color: var(--border-strong);
}
.drawerClose:focus-visible {
outline: var(--line-strong) solid var(--accent-cyan);
outline-offset: 1px;
}
.drawerBody {
flex: 1;
overflow-y: auto;
padding: 16px 18px 32px;
}
.drawerSection {
margin-bottom: 18px;
}
.drawerSection:last-child {
margin-bottom: 0;
}
.drawerH4 {
margin: 0 0 9px;
font-size: 9.5px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-soft);
}
.summaryBox {
padding: 11px 12px;
background: var(--surface-2);
border: var(--line) solid var(--border-faint);
border-radius: var(--radius-sm);
font-size: 11.5px;
line-height: 1.55;
color: var(--text);
}
.statusRow {
display: flex;
align-items: center;
gap: 9px;
padding: 4px 0;
font-size: 11px;
}
.drDot {
flex-shrink: 0;
width: 7px;
height: 7px;
border-radius: 50%;
}
.drDotOk {
background: var(--accent-green);
}
.drDotWarn {
background: var(--accent-yellow);
}
.drDotBad {
background: var(--accent-red);
}
.drDotMuted {
background: var(--text-soft);
}
.drLabel {
flex: 1;
color: var(--text-muted);
}
.drState {
text-align: right;
font-weight: 600;
}
.drStateOk {
color: var(--accent-green);
}
.drStateWarn {
color: var(--accent-yellow);
}
.drStateBad {
color: var(--accent-red);
}
.drStateMuted {
color: var(--text-soft);
font-weight: 500;
}
.drawerActions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.drawerGaugeRow {
display: flex;
justify-content: center;
margin-bottom: 12px;
}
.drawerNote {
margin: 0 0 8px;
font-size: 11px;
line-height: 1.5;
color: var(--text-muted);
}
.drawerLinks {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;
}
.drawerLink {
font-size: 10px;
color: var(--accent-cyan);
text-decoration: none;
}
.drawerLink:hover {
text-decoration: underline;
}
/* ===================== 3D MASSING SANDBOX (Three.js) ===================== */
.massingWrap {
display: flex;
flex-direction: column;
gap: 12px;
}
.massingViewport {
position: relative;
width: 100%;
height: 380px;
border: var(--line) solid var(--border);
border-radius: var(--radius-md);
background: var(--surface-inset);
overflow: hidden;
}
.massingViewport canvas {
display: block;
}
.massingSkeleton,
.massingFallback {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
padding: 16px;
text-align: center;
color: var(--text-soft);
font-size: 11px;
}
.massingFallbackTitle {
font-size: 12px;
font-weight: 600;
color: var(--accent-yellow);
}
.massingFallback p {
margin: 0;
max-width: 280px;
line-height: 1.5;
}
.massingControls {
display: flex;
flex-direction: column;
gap: 12px;
}
.massingSliders {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
align-items: end;
}
.massingSlider {
display: flex;
flex-direction: column;
gap: 6px;
}
.massingSliderLabel {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 8px;
font-size: 9.5px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-soft);
}
.massingSliderValue {
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0;
text-transform: none;
color: var(--accent-cyan);
}
/* dark range inputs */
.massingControls input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 4px;
border-radius: 2px;
background: var(--border);
outline: none;
cursor: pointer;
}
.massingControls input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 13px;
height: 13px;
border-radius: 50%;
background: var(--accent-cyan);
border: var(--line) solid var(--bg);
cursor: pointer;
}
.massingControls input[type="range"]::-moz-range-thumb {
width: 13px;
height: 13px;
border-radius: 50%;
background: var(--accent-cyan);
border: var(--line) solid var(--bg);
cursor: pointer;
}
.massingControls input[type="range"]:focus-visible {
box-shadow: 0 0 0 2px var(--glow-strong);
}
.massingToggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
color: var(--text-muted);
cursor: pointer;
user-select: none;
}
.massingToggle input[type="checkbox"] {
width: 14px;
height: 14px;
accent-color: var(--accent-cyan);
cursor: pointer;
}
.massingMetrics {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 8px;
padding: 10px 12px;
background: var(--surface-2);
border: var(--line) solid var(--border-faint);
border-radius: var(--radius-sm);
}
.massingMetric {
display: flex;
flex-direction: column;
gap: 3px;
min-width: 0;
}
.massingMetricLabel {
font-size: 8.5px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-soft);
}
.massingMetricValue {
font-family: var(--font-mono);
font-size: 12px;
color: var(--text-strong);
white-space: nowrap;
}
.massingMetricOk {
color: var(--accent-green);
}
.massingMetricOver {
color: var(--accent-yellow);
}
.massingCaption {
font-size: 9px;
line-height: 1.5;
color: var(--text-soft);
font-family: var(--font-mono);
}
/* drawer sub-tabs (.dtabs / .dtab-panel) */
.dtabs {
display: flex;
gap: 4px;
margin-bottom: 11px;
border-bottom: var(--line) solid var(--border-faint);
}
.dtabs button {
padding: 6px 10px;
background: transparent;
border: none;
border-bottom: var(--line-strong) solid transparent;
color: var(--text-soft);
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.dtabs button:hover {
color: var(--text-muted);
}
/* specific enough to beat `.dtabs button` without !important. */
.dtabs button.dtabActive {
color: var(--text-strong);
border-bottom-color: var(--accent-cyan);
}
.dtabPanel {
display: grid;
gap: 2px;
}
/* gauge-as-button (hero Buildability) */
.gaugeButton {
display: block;
width: 100%;
background: transparent;
border: none;
padding: 0;
cursor: pointer;
}
.gaugeButton:focus-visible {
outline: var(--line-strong) solid var(--accent-cyan);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
/* ===================== RESPONSIVE ===================== */
@media (max-width: 1500px) {
.hero {
grid-template-columns: 1.5fr 1fr;
}
.scorePanel {
grid-column: span 2;
flex-direction: row;
justify-content: space-around;
flex-wrap: wrap;
}
}
@media (max-width: 1200px) {
.scanGrid {
grid-template-columns: repeat(3, 1fr);
}
.lowerGrid,
.bottomGrid {
grid-template-columns: 1fr 1fr;
}
.forecastGrid,
.reportGrid {
grid-template-columns: 1fr;
}
}
@media (max-width: 820px) {
.shell {
grid-template-columns: 1fr;
}
.rail {
display: none;
}
.hero,
.scanGrid,
.lowerGrid,
.bottomGrid {
grid-template-columns: 1fr;
}
.scorePanel {
flex-direction: column;
}
.tabs {
display: none;
}
.scenarioGrid,
.forecastGrid,
.exportGrid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.drawer {
width: 100vw;
}
.massingSliders {
grid-template-columns: 1fr;
}
.massingMetrics {
grid-template-columns: repeat(2, 1fr);
}
.massingViewport {
height: 320px;
}
}
/* ===================== MAP OVERLAY (legend + layer toggles + CP list) ===== */
.mapOverlay {
position: absolute;
left: 14px;
top: 14px;
width: 224px;
max-height: calc(100% - 28px);
overflow-y: auto;
padding: 12px 13px;
background: var(--surface-inset);
border: var(--line) solid var(--border);
border-radius: var(--radius-sm);
backdrop-filter: blur(10px);
z-index: 4;
}
.mapOverlay h4 {
margin: 0 0 10px;
font-size: 9.5px;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text);
display: flex;
align-items: center;
gap: 6px;
}
.mapOverlay h4 .live {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent-green);
box-shadow: 0 0 8px var(--accent-green);
}
/* base-layer segmented control (Спутник | Схема) */
.baseToggle {
display: inline-flex;
margin: 0 0 10px;
border: var(--line) solid var(--border);
border-radius: var(--radius-xs);
overflow: hidden;
background: var(--surface-inset);
}
.baseToggle button {
padding: 4px 9px;
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-soft);
background: transparent;
border: none;
}
.baseToggle button + button {
border-left: var(--line) solid var(--border);
}
.baseToggle button:hover {
color: var(--text);
}
.baseToggle button.baseToggleActive {
color: #08131c;
background: var(--accent-cyan);
}
/* layer-toggle list */
.layerList {
display: grid;
gap: 6px;
font-size: 10px;
color: var(--text-muted);
}
.layerRow {
display: grid;
grid-template-columns: 14px 1fr auto;
gap: 7px;
align-items: center;
background: none;
border: none;
padding: 0;
text-align: left;
color: inherit;
font: inherit;
width: 100%;
}
.layerRow:not(.layerRowSoon) {
cursor: pointer;
}
.layerRow .swatch {
width: 9px;
height: 9px;
border-radius: 2px;
}
.layerRow .swatch3d {
background: linear-gradient(135deg, var(--accent-cyan), var(--accent-blue));
}
.layerRow .cnt {
color: var(--text-soft);
font-size: 9px;
}
.layerRowOff {
opacity: 0.4;
}
.layerRowOff .swatch {
background: var(--text-soft) !important;
}
.layerRowSoon {
opacity: 0.55;
cursor: default;
}
.layerRowSoon .swatch {
background: var(--text-soft) !important;
}
.layerRow3d {
cursor: pointer;
}
.openTag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 1px 7px;
border: var(--line) solid var(--accent-cyan);
border-radius: 999px;
font-size: 8px;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--accent-cyan);
}
.openTag::before {
content: "";
width: 5px;
height: 5px;
border-radius: 50%;
background: var(--accent-cyan);
box-shadow: 0 0 6px var(--accent-cyan);
}
/* isochrone sub-legend (5/10/15 мин) — only visible when layer is on */
.isoSublegend {
display: none;
gap: 8px;
margin: 4px 0 2px;
font-size: 8.5px;
color: var(--text-soft);
}
.isoSublegendOn {
display: flex;
}
.isoSublegend i {
display: inline-flex;
align-items: center;
gap: 4px;
font-style: normal;
}
.isoSublegend i::before {
content: "";
width: 8px;
height: 8px;
border-radius: 2px;
background: var(--accent-cyan);
}
.isoSublegend i.b10::before {
opacity: 0.6;
}
.isoSublegend i.b15::before {
opacity: 0.32;
}
.overlayDivider {
margin: 10px 0 8px;
border: none;
border-top: var(--line) solid var(--border);
}
.overlaySub {
font-size: 8.5px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-soft);
margin-bottom: 7px;
}
.resRow {
display: grid;
grid-template-columns: 16px 1fr auto;
gap: 7px;
align-items: center;
font-size: 10px;
margin-bottom: 5px;
}
.resRow .dot {
width: 11px;
height: 11px;
border-radius: 50%;
}
.resRow b {
font-family: var(--font-mono);
font-size: 10px;
color: var(--text);
}
/* map tool buttons (zoom / fit / add-POI) */
.mapTools {
position: absolute;
right: 14px;
top: 14px;
display: grid;
gap: 7px;
z-index: 4;
}
.mapTools .tool {
width: 34px;
height: 34px;
border: var(--line) solid var(--border);
background: var(--surface-strong);
border-radius: var(--radius-xs);
display: grid;
place-items: center;
color: var(--text);
}
.mapTools .tool:hover {
border-color: var(--accent-cyan);
color: var(--accent-cyan);
}
.mapTools .tool svg {
width: 15px;
height: 15px;
}
.mapTools .toolActive {
border-color: var(--accent-yellow);
color: var(--accent-yellow);
box-shadow: 0 0 10px rgba(224, 169, 59, 0.45);
}
.mapHint {
position: absolute;
left: 50%;
bottom: 14px;
transform: translateX(-50%);
z-index: 4;
padding: 4px 12px;
font-size: 9.5px;
letter-spacing: 0.04em;
color: var(--text);
background: var(--surface-strong);
border: var(--line) solid var(--border);
border-radius: 999px;
backdrop-filter: blur(6px);
}
/* ===================== LOWER GRID (cockpit) =====================
Ported from prototype .lower-grid: ОКС · Development Potential ·
Recommended Product · Инсоляционная матрица. */
.lowerGrid {
display: grid;
grid-template-columns: 1.05fr 1.05fr 1.15fr 0.95fr;
gap: 10px;
/* HEADLINE BUG FIX: default align-items:stretch made the sparse/empty ОКС
card (isoBox 84px + emptyState flex:1 + button) stretch to the tallest
sibling, and .emptyState's flex:1 then filled the void → huge empty box.
align-items:start lets each card size to its own content. */
align-items: start;
}
/* Lower-grid квартирография bars match the prototype .bar-row (narrow label /
value columns, 2px track radius) — SCOPED so the shared base .hbar used by
the drawer (110px 1fr 46px, 3px radius) is NOT crushed. */
.lowerGrid .hbars {
gap: 8px;
}
.lowerGrid .hbar {
grid-template-columns: 46px 1fr 38px;
}
.lowerGrid .hbarTrack {
border-radius: 2px;
}
.lowerGrid .hbarFill {
border-radius: 2px;
}
/* shared detail-btn that needs top separation inside flow cards */
.detailBtnSpaced {
margin-top: 10px;
align-self: flex-start;
}
/* ОКС — isometric building visual + empty-state */
.isoBox {
height: 84px;
display: grid;
place-items: center;
margin: 2px 0 10px;
}
.isoBox svg {
width: 100%;
height: 100%;
}
.isoFaceLeft {
fill: rgba(210, 101, 91, 0.22);
stroke: var(--accent-red);
stroke-width: 1;
}
.isoFaceTop {
fill: rgba(210, 101, 91, 0.13);
stroke: var(--accent-red);
stroke-width: 1;
}
.isoFaceRight {
fill: rgba(210, 101, 91, 0.3);
stroke: var(--accent-red);
stroke-width: 1;
}
/* Development Potential — metric tiles */
.potentialGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 9px;
margin-bottom: 10px;
}
/* grid-item wrapper (label + value stack); min-width:0 stops long values
blowing out the grid track. */
.metric {
min-width: 0;
}
.metricLabel {
font-size: 8.5px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-soft);
margin-bottom: 2px;
}
.metricValue {
font-size: 17px;
font-weight: 600;
color: var(--text-strong);
}
.metricValueLg {
font-size: 22px;
}
.metricValuePlaceholder {
color: var(--text-soft);
font-weight: 500;
}
.metricUnit {
font-size: 10px;
color: var(--text-muted);
margin-left: 3px;
}
/* Recommended Product — mix-bar head (квартирография / доля) */
.mixHead {
display: flex;
justify-content: space-between;
font-size: 8.5px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-soft);
margin-bottom: 1px;
}
/* Инсоляционная матрица — faint preview massing + ПРЕВЬЮ placeholder */
.isoBoxPreview {
height: 74px;
display: grid;
place-items: center;
margin: 2px 0 10px;
opacity: 0.55;
}
.isoBoxPreview svg {
width: 100%;
height: 100%;
}
.isoPreviewLeft {
fill: rgba(150, 192, 214, 0.1);
stroke: var(--border-strong);
stroke-width: 1;
}
.isoPreviewTop {
fill: rgba(150, 192, 214, 0.06);
stroke: var(--border-strong);
stroke-width: 1;
}
.isoPreviewRight {
fill: rgba(150, 192, 214, 0.14);
stroke: var(--border-strong);
stroke-width: 1;
}
.isoPreviewEdge {
stroke: var(--border);
stroke-width: 0.6;
}
.placeholder {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 8px;
}
.placeholderSoon {
font-size: 22px;
letter-spacing: 0.12em;
color: var(--text-soft);
font-weight: 600;
}
.placeholder p {
font-size: 10px;
color: var(--text-soft);
margin: 0;
max-width: 180px;
}
/* ===================== BOTTOM GRID (cockpit) =====================
Ported from prototype .bottom-grid: Investment Clearance · Buy Signal ·
Legal Status · Site Verdict. */
.bottomGrid {
display: grid;
grid-template-columns: 1.5fr 0.62fr 0.86fr 1.2fr;
gap: 10px;
/* Same grid-stretch defect as .lowerGrid — sparse Legal Status / cards must
not stretch to the tallest sibling. Pin each card to the top. */
align-items: start;
}
/* Investment Clearance — bignum row */
.bignumRow {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 8px;
}
.bignum {
min-width: 0;
}
.bignumLabel {
font-size: 8.5px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-soft);
margin-bottom: 4px;
}
.bignumValue {
font-size: 30px;
font-weight: 500;
letter-spacing: 0.01em;
color: var(--text-strong);
line-height: 1;
}
.bignumValuePlaceholder {
color: var(--text-soft);
}
.bignumUnit {
display: block;
font-size: 9px;
color: var(--text-muted);
margin-top: 4px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.bignumCaption {
margin: 12px 0 0;
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-soft);
}
/* Buy Signal — centered gauge */
.buySignalCard {
display: grid;
place-items: center;
}
.buySignalTitle {
align-self: flex-start;
}
/* Legal Status — Нет/значение grid */
.legalGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--line);
background: var(--border-faint);
border: var(--line) solid var(--border-faint);
border-radius: var(--radius-sm);
overflow: hidden;
}
.legalCell {
background: var(--surface-2);
padding: 9px 11px;
}
.legalKey {
display: block;
font-size: 8.5px;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--text-soft);
margin-bottom: 3px;
}
.legalValue {
font-size: 12.5px;
font-weight: 600;
color: var(--text);
}
.legalValuePlaceholder {
color: var(--text-soft);
font-weight: 500;
}
/* Site Verdict — headline plaque + checklist */
.verdictCard {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
text-align: center;
/* Prototype .verdict-card plaque gradient — scoped dark cockpit (authoritative
here over the global flat-token rule). */
background: linear-gradient(135deg, var(--surface), var(--surface-2));
}
.verdictSplit {
display: grid;
grid-template-columns: auto 1fr;
gap: 18px;
align-items: center;
}
.verdictLead {
text-align: center;
}
.verdictLeadLabel {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--text-soft);
margin-bottom: 6px;
}
.verdictWord {
font-size: 30px;
font-weight: 700;
letter-spacing: 0.18em;
text-transform: uppercase;
text-shadow: 0 0 22px var(--glow);
}
.verdictWordGood {
color: var(--accent-green);
}
.verdictWordWarn {
color: var(--accent-yellow);
}
.verdictWordBad {
color: var(--accent-red);
}
.checklist {
display: grid;
gap: 8px;
text-align: left;
}
.checkItem {
display: flex;
align-items: flex-start;
gap: 9px;
font-size: 10.5px;
text-transform: uppercase;
letter-spacing: 0.03em;
color: var(--text-muted);
}
.checkIc {
width: 16px;
height: 16px;
flex: none;
}
.checkItemOk .checkIc {
color: var(--accent-green);
border: var(--line-strong) solid currentColor;
border-radius: 50%;
padding: 2px;
}
.checkItemWarn .checkIc {
color: var(--accent-yellow);
/* Prototype removes the icon border ring on warn checklist items. */
border: none;
}
.checkItemBad .checkIc {
color: var(--accent-red);
}