diff --git a/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx b/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx
index 8d4cc818..db414820 100644
--- a/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx
+++ b/frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx
@@ -119,6 +119,18 @@ export function AnalysisPageContent({ cad }: Props) {
{/* Breadcrumb */}
+
+ Анализ участка {cad}
+ {districtName && districtName !== "—" ? ` · ${districtName}` : ""}
+
+
{/* Controls row — horizon selector drives the analyze + forecast queries */}
;
iconBg: string;
}
const COLOR_BY_LABEL: Record = {
- Можно: { bg: "#ecfdf5", border: "#34d399", icon: "✅", iconBg: "#10b981" },
- Нельзя: { bg: "#fef2f2", border: "#f87171", icon: "🚫", iconBg: "#ef4444" },
+ Можно: {
+ bg: "#ecfdf5",
+ border: "#34d399",
+ Icon: CheckCircle2,
+ iconBg: "#10b981",
+ },
+ Нельзя: { bg: "#fef2f2", border: "#f87171", Icon: XCircle, iconBg: "#ef4444" },
"С ограничениями": {
bg: "#fffbeb",
border: "#fbbf24",
- icon: "⚠️",
+ Icon: AlertTriangle,
iconBg: "#f59e0b",
},
"Нужна проверка": {
bg: "#f9fafb",
border: "#9ca3af",
- icon: "❓",
+ Icon: HelpCircle,
iconBg: "#6b7280",
},
};
@@ -55,14 +70,32 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) {
if (!verdict) return null;
const color = COLOR_BY_LABEL[verdict.verdict_label];
+ const Icon = color.Icon;
const hasDetails = verdict.blockers.length > 0 || verdict.warnings.length > 0;
+ const iconSx = { flexShrink: 0 } as const;
const sourceHint =
- verdict.source === "nspd_dump_partial"
- ? "⏱ устарели"
- : verdict.source === "no_data"
- ? "❓ нет данных"
- : "🟢 свежие";
+ verdict.source === "nspd_dump_partial" ? (
+ <>
+
+ устарели
+ >
+ ) : verdict.source === "no_data" ? (
+ <>
+
+ нет данных
+ >
+ ) : (
+ <>
+
+ свежие
+ >
+ );
const summaryParts: string[] = [];
if (verdict.blockers.length > 0)
@@ -94,11 +127,10 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) {
display: "flex",
alignItems: "center",
justifyContent: "center",
- fontSize: 20,
flexShrink: 0,
}}
>
- {color.icon}
+
@@ -107,7 +139,15 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) {
{summaryText}
{" · "}
-
+
{sourceHint}
@@ -142,7 +182,23 @@ export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) {
>
{verdict.blockers.length > 0 && (
-
🚫 Блокеры
+
+
+
+ Блокеры
+
+
0 && (
-
⚠️ Предупреждения
+
+
+
+ Предупреждения
+
+
- 🟡
+
Вечная мерзлота — особые фундаменты
)}
@@ -114,7 +120,12 @@ export function GeotechRiskBlock({ risk }: Props) {
gap: 6,
}}
>
- ⚠
+
{risk.industrial_within_500m} промзон(ы) в <500м (риск
загрязнения почв)
diff --git a/frontend/src/components/site-finder/HydrologyBlock.tsx b/frontend/src/components/site-finder/HydrologyBlock.tsx
index ed32f9d4..7f555320 100644
--- a/frontend/src/components/site-finder/HydrologyBlock.tsx
+++ b/frontend/src/components/site-finder/HydrologyBlock.tsx
@@ -1,16 +1,21 @@
"use client";
+import type { ComponentType } from "react";
+import { AlertTriangle, Droplet, Waves, type LucideProps } from "lucide-react";
import type { Hydrology, HydrologyNearest } from "@/types/site-finder";
interface Props {
hydrology: Hydrology;
}
-const SUBTYPE_ICON: Record = {
- river: "🏞",
- stream: "💦",
- lake_or_pond: "🪷",
- canal: "🚣",
+// Decorative water glyph per subtype — the text label already names the body
+// (река / ручей / озеро / канал), so the icon is aria-hidden. Lucide has no
+// distinct lake/canal glyph; flowing bodies use Waves, the rest use Droplet.
+const SUBTYPE_ICON: Record> = {
+ river: Waves,
+ stream: Droplet,
+ lake_or_pond: Waves,
+ canal: Waves,
};
function subtypeLabel(subtype: HydrologyNearest["subtype"]): string {
@@ -63,7 +68,12 @@ export function HydrologyBlock({ hydrology }: Props) {
gap: 6,
}}
>
- ⚠
+
Возможная пойма — река/канал в <200м
)}
@@ -76,7 +86,7 @@ export function HydrologyBlock({ hydrology }: Props) {
) : (
{top5.map((item, i) => {
- const icon = SUBTYPE_ICON[item.subtype ?? ""] ?? "💧";
+ const Icon = SUBTYPE_ICON[item.subtype ?? ""] ?? Droplet;
const label = subtypeLabel(item.subtype);
const name = item.name ?? "б/н";
return (
@@ -90,7 +100,12 @@ export function HydrologyBlock({ hydrology }: Props) {
color: "#374151",
}}
>
-
{icon}
+
{name} {label} — {Math.round(item.distance_m)} м
diff --git a/frontend/src/components/site-finder/OverviewTab.tsx b/frontend/src/components/site-finder/OverviewTab.tsx
index 595a6e51..ab0f9030 100644
--- a/frontend/src/components/site-finder/OverviewTab.tsx
+++ b/frontend/src/components/site-finder/OverviewTab.tsx
@@ -1,5 +1,6 @@
"use client";
+import { AlertTriangle } from "lucide-react";
import type { FeatureCollection } from "geojson";
import type { ParcelAnalysis } from "@/types/site-finder";
import { SectionLabel } from "@/components/ui/SectionLabel";
@@ -185,13 +186,24 @@ export function OverviewTab({ data, onIsochronesResult }: Props) {
{data.utilities.power_line_охранная_зона_flag && (
- ⚠ Охранная зона ЛЭП — капитальное строительство запрещено
+
+
+ Охранная зона ЛЭП — капитальное строительство запрещено
+
)}
@@ -212,7 +224,12 @@ export function OverviewTab({ data, onIsochronesResult }: Props) {
gap: 8,
}}
>
-
⚠
+
Трамвай в {nearestTram} м — возможный источник шума
)}
diff --git a/frontend/src/components/site-finder/analysis/ForecastChart.tsx b/frontend/src/components/site-finder/analysis/ForecastChart.tsx
index 469d77cf..ab811474 100644
--- a/frontend/src/components/site-finder/analysis/ForecastChart.tsx
+++ b/frontend/src/components/site-finder/analysis/ForecastChart.tsx
@@ -61,11 +61,15 @@ const VIZ: Record
= {
// Single-line fallback colour (ui-tokens: одиночная линия — #374151).
const SINGLE_LINE = "#374151";
-// Prediction / forecast (dashed) — --prediction-line.
-const PREDICTION_LINE = "#0EA5E9";
-// Axis chrome.
-const FG_SECONDARY = "#5B6066";
-const BORDER_STRONG = "#D1D5DB";
+// Chrome colours below are intentionally raw hex, NOT var(--token), for the same
+// reason as VIZ above: ChartShell renders via echarts-for-react with the default
+// CANVAS renderer (no opts.renderer:"svg"), and the canvas 2D context does not
+// resolve CSS custom properties — `var(--x)` would paint as transparent/black.
+// Values mirror ui-tokens.md exactly: --prediction-line, --fg-secondary,
+// --border-strong. Keep in sync with ui-tokens.md by hand.
+const PREDICTION_LINE = "#0EA5E9"; // --prediction-line
+const FG_SECONDARY = "#5B6066"; // --fg-secondary
+const BORDER_STRONG = "#D1D5DB"; // --border-strong
// ── Tooltip row type (echarts-for-react gives loose params; narrow ourselves) ──
interface TooltipParam {
diff --git a/frontend/src/components/site-finder/analysis/Section2NetworksUtilities.tsx b/frontend/src/components/site-finder/analysis/Section2NetworksUtilities.tsx
index 77209674..460a47de 100644
--- a/frontend/src/components/site-finder/analysis/Section2NetworksUtilities.tsx
+++ b/frontend/src/components/site-finder/analysis/Section2NetworksUtilities.tsx
@@ -804,7 +804,8 @@ export function Section2NetworksUtilities({ cad }: Props) {
/>
Участок находится в охранной зоне ЛЭП ≥35 кВ. Капитальное
- строительство в ОЗ запрещено (СП 36.13330, ЗОУИТ тип 5).
+ строительство в охранной зоне (ОЗ) запрещено (СП 36.13330). Участок
+ в зоне с особыми условиями использования территорий (ЗОУИТ), тип 5.
)}
diff --git a/frontend/src/components/site-finder/analysis/Section4Estimate.tsx b/frontend/src/components/site-finder/analysis/Section4Estimate.tsx
index 118dea0a..8cc5be9d 100644
--- a/frontend/src/components/site-finder/analysis/Section4Estimate.tsx
+++ b/frontend/src/components/site-finder/analysis/Section4Estimate.tsx
@@ -219,6 +219,7 @@ export function Section4Estimate({ cad }: Props) {
}}
>