gendesign/frontend/src/app/site-finder/analysis/[cad]/ptica/ptica.module.css
Light1YT 611c56a549
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Successful in 58s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m59s
fix(ptica): button font-size — :where() so component classes win over reset
The scoped reset `.pticaRoot button { font: inherit }` had specificity (0,1,1),
beating single-class button rules like .navItem (9px), .tab (11px), .detailBtn
(9px), .ptab — so every cockpit button was forced back to the root 13px. The
oversized labels overflowed the 96px left rail (icons/text shoved right of
centre, «неровное») and bloated tabs/buttons across the cockpit.

Wrap the selector in :where(button) → specificity (0,1,0); the per-component
classes (later in source) now win their font-size, matching the prototype's
bare `button {}`. Verified on a live local render: rail labels 13→9px and every
item re-centres at 47.5px; tabs 11px; detail buttons 9px.
2026-06-21 02:35:33 +05:00

2757 lines
55 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:
var(--font-inter), "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%;
}
/* `:where(button)` keeps specificity at (0,1,0) so per-component button classes
(.navItem 9px, .tab 11px, .detailBtn 9px, .ptab …) win their font-size. With a
bare `.pticaRoot button` (0,1,1) the `font: inherit` reset beat those classes
and forced every cockpit button back to the root 13px — overflowing the 96px
rail and oversizing tabs/buttons. Matches the prototype's bare `button {}`. */
.pticaRoot :where(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;
/* Wordmark is the «На главную» link — visibly clickable, inherits cockpit
colors (no default link blue/underline). */
cursor: pointer;
text-decoration: none;
color: inherit;
border-radius: var(--radius-xs);
}
/* Visible focus ring on the home link — matches cockpit pattern
(.drawerClose / .gaugeButton :focus-visible). */
.wordmark:focus-visible {
outline: var(--line-strong) solid var(--accent-cyan);
outline-offset: 3px;
}
.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) {
position: absolute;
inset: 0;
/* Contain Leaflet's panes (tile 200 / overlay 400 / marker 600 / popup 700)
in their OWN stacking context below the cockpit overlay (.mapOverlay /
.mapTools at z-index 4) so the legend is not buried under the tiles. */
z-index: 1;
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);
}
/* Dark-styled zoom +/- control (prototype map controls). */
.mapMount :global(.leaflet-control-zoom) {
border: var(--line) solid var(--border-strong);
border-radius: var(--radius-xs);
overflow: hidden;
box-shadow: 0 0 12px var(--glow);
}
.mapMount :global(.leaflet-control-zoom a) {
background: var(--surface-strong);
color: var(--text);
border-bottom: var(--line) solid var(--border);
width: 28px;
height: 28px;
line-height: 28px;
}
.mapMount :global(.leaflet-control-zoom a:hover) {
background: var(--surface-muted);
color: var(--accent-cyan);
}
.mapMount :global(.leaflet-control-zoom a.leaflet-disabled) {
background: var(--surface-strong);
color: var(--text-soft);
}
/* Metric scale bar. */
.mapMount :global(.leaflet-control-scale-line) {
background: rgba(8, 19, 28, 0.55);
color: var(--text-soft);
border: var(--line) solid var(--border);
border-top: none;
font-size: 9px;
padding: 1px 6px;
backdrop-filter: blur(4px);
}
.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);
flex-shrink: 0;
}
.kvrow .v {
font-weight: 600;
color: var(--text);
text-align: right;
/* Long values (e.g. «Стагнация — рынок остыл») wrap gracefully instead of
overflowing the card. */
min-width: 0;
overflow-wrap: anywhere;
}
.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);
}
/* Recommended Product class-tabs (prototype .product-tabs / .ptab / .active) */
.productTabs {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 12px;
}
.ptab {
padding: 4px 12px;
border: var(--line) solid var(--border);
background: transparent;
color: var(--text-muted);
border-radius: var(--radius-xs);
font-size: 10px;
cursor: pointer;
}
.ptab:hover {
color: var(--text);
border-color: var(--border-strong);
}
.ptabActive,
.ptabActive:hover {
background: var(--accent-blue);
color: var(--text);
border-color: var(--accent-blue);
box-shadow: 0 0 14px var(--glow);
}
/* 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);
/* Keep one row per format — long bucket labels («5-80+ м²») must not wrap to
2 lines and break the name / track / value column alignment across rows. */
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.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 */
/* Ambient «turntable» rotation of the massing-preview cube — gives a sense of
the 3D module without opening the drawer. A constant slight rotateX keeps a
sliver of depth visible as it passes edge-on. */
@keyframes pticaIsoSpin {
from {
transform: rotateX(14deg) rotateY(0deg);
}
to {
transform: rotateX(14deg) rotateY(360deg);
}
}
.isoBoxPreview {
height: 74px;
display: grid;
place-items: center;
margin: 2px 0 10px;
opacity: 0.55;
perspective: 540px;
}
.isoBoxPreview svg {
width: 100%;
height: 100%;
transform-style: preserve-3d;
transform-origin: 50% 50%;
will-change: transform;
animation: pticaIsoSpin 8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.isoBoxPreview svg {
animation: none;
}
}
.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);
}