diff --git a/frontend/public/trade-in-v2/building.png b/frontend/public/trade-in-v2/building.png
new file mode 100644
index 00000000..466bff5d
Binary files /dev/null and b/frontend/public/trade-in-v2/building.png differ
diff --git a/frontend/src/app/trade-in/v2/layout.tsx b/frontend/src/app/trade-in/v2/layout.tsx
new file mode 100644
index 00000000..532ca966
--- /dev/null
+++ b/frontend/src/app/trade-in/v2/layout.tsx
@@ -0,0 +1,46 @@
+import type { Metadata } from "next";
+import { IBM_Plex_Mono, Manrope } from "next/font/google";
+
+import { pageBg } from "@/components/trade-in/v2/tokens";
+
+// Manrope — primary sans typeface of the МЕРА HUD. next/font is bundled
+// (no package.json change). Cyrillic + latin so RU labels render correctly.
+const manrope = Manrope({
+ subsets: ["latin", "cyrillic"],
+ weight: ["200", "300", "400", "500", "600", "700"],
+ variable: "--font-manrope",
+ display: "swap",
+});
+
+// IBM Plex Mono — cockpit numerals / mono labels.
+const plexMono = IBM_Plex_Mono({
+ subsets: ["latin", "cyrillic"],
+ weight: ["300", "400", "500"],
+ variable: "--font-plex-mono",
+ display: "swap",
+});
+
+export const metadata: Metadata = {
+ title: "МЕРА · Оценка v2",
+};
+
+export default function TradeInV2Layout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/frontend/src/app/trade-in/v2/page.tsx b/frontend/src/app/trade-in/v2/page.tsx
new file mode 100644
index 00000000..4bd6fd4e
--- /dev/null
+++ b/frontend/src/app/trade-in/v2/page.tsx
@@ -0,0 +1,148 @@
+"use client";
+
+import { useState } from "react";
+import type { CSSProperties } from "react";
+
+import { tokens } from "@/components/trade-in/v2/tokens";
+import TopNav from "@/components/trade-in/v2/TopNav";
+import HeroBar from "@/components/trade-in/v2/HeroBar";
+import ParamsPanel from "@/components/trade-in/v2/ParamsPanel";
+import ResultPanel from "@/components/trade-in/v2/ResultPanel";
+import { ObjectSummary } from "@/components/trade-in/v2/ObjectSummary";
+import { Footer } from "@/components/trade-in/v2/Footer";
+import SectionOverlay from "@/components/trade-in/v2/SectionOverlay";
+import { LocationDrawer } from "@/components/trade-in/v2/LocationDrawer";
+
+// OUTER HUD FRAME + 4 corner brackets (design lines 31-37). Decorative,
+// non-interactive overlay drawn over the artboard gradient. The frame has
+// chamfered octagonal corners (clip-path polygon) + 4px radius; the brackets
+// sit just outside it at the artboard corners.
+const FRAME_INSET = 14;
+const BRACKET_INSET = 6;
+const BRACKET_ARM = 22;
+const BRACKET_W = 1.5;
+
+const FRAME_CLIP =
+ "polygon(18px 0,calc(100% - 18px) 0,100% 18px,100% calc(100% - 18px),calc(100% - 18px) 100%,18px 100%,0 calc(100% - 18px),0 18px)";
+
+const brackets: { key: string; style: CSSProperties }[] = [
+ {
+ key: "tl",
+ style: {
+ top: BRACKET_INSET,
+ left: BRACKET_INSET,
+ borderTop: `${BRACKET_W}px solid ${tokens.bracket2}`,
+ borderLeft: `${BRACKET_W}px solid ${tokens.bracket2}`,
+ },
+ },
+ {
+ key: "tr",
+ style: {
+ top: BRACKET_INSET,
+ right: BRACKET_INSET,
+ borderTop: `${BRACKET_W}px solid ${tokens.bracket2}`,
+ borderRight: `${BRACKET_W}px solid ${tokens.bracket2}`,
+ },
+ },
+ {
+ key: "bl",
+ style: {
+ bottom: BRACKET_INSET,
+ left: BRACKET_INSET,
+ borderBottom: `${BRACKET_W}px solid ${tokens.bracket2}`,
+ borderLeft: `${BRACKET_W}px solid ${tokens.bracket2}`,
+ },
+ },
+ {
+ key: "br",
+ style: {
+ bottom: BRACKET_INSET,
+ right: BRACKET_INSET,
+ borderBottom: `${BRACKET_W}px solid ${tokens.bracket2}`,
+ borderRight: `${BRACKET_W}px solid ${tokens.bracket2}`,
+ },
+ },
+];
+
+export default function TradeInV2Page() {
+ const [nav, setNav] = useState(0);
+ const [drawerOpen, setDrawerOpen] = useState(false);
+
+ return (
+
+ {/* OUTER HUD FRAME */}
+
+ {/* 4 corner brackets */}
+ {brackets.map((b) => (
+
+ ))}
+
+ {/* CONTENT WRAP */}
+
+
+
setDrawerOpen(true)} />
+
+
+
+
+
+
+ {nav !== 0 && (
+
setNav(0)}
+ onNavigate={setNav}
+ />
+ )}
+ setDrawerOpen(false)} />
+
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/AnalyticsView.tsx b/frontend/src/components/trade-in/v2/AnalyticsView.tsx
new file mode 100644
index 00000000..50408d28
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/AnalyticsView.tsx
@@ -0,0 +1,551 @@
+"use client";
+
+// Overlay 06 "АНАЛИТИКА ДОМА" — markup port from the МЕРА Оценка pixel design.
+// Data comes from fixtures (no API); the price-history dots navigate to the
+// "Аналогичные объявления" overlay via onNavigate?.(2).
+
+import { useState } from "react";
+import { tokens } from "./tokens";
+import { analytics } from "./fixtures";
+import type { SellTimeTier } from "./types";
+
+interface AnalyticsViewProps {
+ onNavigate?: (i: number) => void;
+}
+
+// ---- price-history geometry -----------------------------------------------
+// Parse the SVG polyline point strings into coordinates so the markers and
+// the transparent hover targets can be derived from the same source.
+
+interface Pt {
+ x: number;
+ y: number;
+}
+
+function parsePoints(s: string): Pt[] {
+ return s
+ .trim()
+ .split(/\s+/)
+ .map((pair) => {
+ const [x, y] = pair.split(",").map(Number);
+ return { x, y };
+ });
+}
+
+const avitoPts = parsePoints(analytics.priceHistory.avito);
+const yandexPts = parsePoints(analytics.priceHistory.yandex);
+// Яндекс only draws dots where its series diverges from Avito (последние 3 года).
+const yandexDots = yandexPts.filter(
+ (p, i) => !avitoPts[i] || p.x !== avitoPts[i].x || p.y !== avitoPts[i].y,
+);
+const hoverPts: Pt[] = [...avitoPts, ...yandexDots];
+
+// X positions of the year axis labels (chart scaffolding, not in fixtures).
+const histYearX = [50, 250, 460, 670, 860];
+
+// ---- sell-time tier tints --------------------------------------------------
+// One-off decorative tints per variant (design lines 553-556). The soft border
+// hex (#bfe3d2 / #e3d2ad / #e6c3c3) and the rgba background fills are documented
+// one-off design tints with no token equivalent; the label text colour reuses
+// semantic tokens (success / accent / warn / danger).
+
+const tierStyles: Record<
+ SellTimeTier["variant"],
+ { bg: string; border: string; label: string }
+> = {
+ success: {
+ bg: "rgba(27,170,107,.07)",
+ border: "1px solid #bfe3d2",
+ label: tokens.success,
+ },
+ accent: {
+ bg: "rgba(46,139,255,.08)",
+ border: `1.5px solid ${tokens.accent}`,
+ label: tokens.accent,
+ },
+ gold: {
+ bg: "rgba(201,154,62,.09)",
+ border: "1px solid #e3d2ad",
+ label: tokens.warn,
+ },
+ danger: {
+ bg: "rgba(214,90,90,.07)",
+ border: "1px solid #e6c3c3",
+ label: tokens.danger,
+ },
+};
+
+const cardStyle = {
+ background: tokens.surface.w55,
+ border: `1px solid ${tokens.line2}`,
+ borderRadius: 8,
+ padding: "16px 18px",
+} as const;
+
+export default function AnalyticsView({ onNavigate }: AnalyticsViewProps) {
+ const [hoverHist, setHoverHist] = useState(false);
+
+ return (
+
+ {/* header */}
+
+
+ Аналитика дома
+
+
+ {analytics.header.ads} · {analytics.header.radius}
+
+
+
+ {/* KPI row */}
+
+ {analytics.kpis.map((kpi, i) => (
+
+
+ {kpi.label}
+
+
+ {kpi.value}
+ {kpi.unit ? (
+ <>
+ {" "}
+
+ {kpi.unit}
+
+ >
+ ) : null}
+
+
{kpi.sub}
+
+ ))}
+
+
+ {/* срок продажи в зависимости от цены */}
+
+
+ Срок продажи в зависимости от цены
+
+
+ {analytics.sellTimeNote}
+
+
+ {analytics.sellTime.map((tier, i) => {
+ const v = tierStyles[tier.variant];
+ return (
+
+
+ {tier.tier}
+
+
+ {tier.days}
+
+
+ {tier.range}
+
+ {tier.count}
+
+
+ );
+ })}
+
+
+
+ {/* история цен */}
+
+
+
+
+ История цен в этом доме
+
+
+ {analytics.priceHistory.note}
+
+
+
+
+
+ Avito
+
+
+
+ Яндекс
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 200k
+
+
+ 150k
+
+
+ 100k
+
+
+ 50k
+
+
+ 0k
+
+
+
+ {analytics.priceHistory.years.map((yr, i) => (
+
+ {yr}
+
+ ))}
+
+
+
+ {/* white dot fill on an accent ring — documented one-off, no token (design l.568) */}
+
+ Архивные объявления Avito · нажмите, чтобы открыть
+ {avitoPts.map((p, i) => (
+
+ ))}
+
+
+ {yandexDots.map((p, i) => (
+
+ ))}
+
+ setHoverHist(true)}
+ onMouseLeave={() => setHoverHist(false)}
+ onClick={() => onNavigate?.(2)}
+ >
+ {hoverPts.map((p, i) => (
+
+ ))}
+
+
+ {hoverHist ? (
+ // Dark navy tooltip surface + white text — documented one-off literals
+ // matching the design (l.572); no token equivalent.
+
+
+ Архивные объявления этого дома
+
+
+ Нажмите, чтобы открыть список →
+
+
+ ) : null}
+
+
+ {/* цена × срок продажи (детально) */}
+
+
+
+
+ Цена × срок продажи
+
+
+ Зависимость цены от срока экспозиции ·{" "}
+ {analytics.scatterDetail.note}
+
+
+
+
+
+ аналоги
+
+
+
+ сделки
+
+
+
+ объект
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {analytics.scatterDetail.deals.map((p, i) => (
+
+ ))}
+
+
+ {analytics.scatterDetail.analogs.map((p, i) => (
+
+ ))}
+
+
+
+
+ {analytics.scatterDetail.yTicks.map((t) => (
+
+ {t.label}
+
+ ))}
+
+
+ {analytics.scatterDetail.xTicks.map((t) => (
+
+ {t.label}
+
+ ))}
+
+
+ СРОК ПРОДАЖИ, ДН
+
+
+ ЦЕНА ПРОДАЖИ, ₽
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/CacheView.tsx b/frontend/src/components/trade-in/v2/CacheView.tsx
new file mode 100644
index 00000000..1d8bd778
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/CacheView.tsx
@@ -0,0 +1,147 @@
+// OVERLAY 07: ПРЕДЫДУЩИЕ ОЦЕНКИ (cache view).
+// Faithful markup port of МЕРА Оценка.dc.html lines 593-613.
+// Data from fixtures (cacheKpi + cacheRows); no API, no local state.
+
+import { tokens } from "./tokens";
+import { cacheKpi, cacheRows } from "./fixtures";
+
+const GRID_COLUMNS = "2.4fr 1fr 1fr 1fr";
+
+export function CacheView() {
+ return (
+
+ {/* 3 KPI cards */}
+
+ {cacheKpi.map((kpi) => (
+
+
+ {kpi.label}
+
+
+ {kpi.value}
+ {kpi.unit && (
+ <>
+ {kpi.unit !== "%" && " "}
+
+ {kpi.unit}
+
+ >
+ )}
+
+
{kpi.sub}
+
+ ))}
+
+
+ {/* Последние оценки table */}
+
+
+ Последние оценки
+
+
+ АДРЕС
+ ВРЕМЯ
+ ИСТОЧНИКОВ
+ СТАТУС
+
+ {cacheRows.map((r, i) => (
+
+ {r.addr}
+
+ {r.time}
+
+
+ {r.src}
+
+
+
+ {r.status}
+
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/Footer.tsx b/frontend/src/components/trade-in/v2/Footer.tsx
new file mode 100644
index 00000000..e4e05182
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/Footer.tsx
@@ -0,0 +1,123 @@
+// Report footer for the /trade-in/v2 "МЕРА Оценка" design port.
+// Faithful markup port of the design footer (МЕРА Оценка.dc.html, lines 426-439):
+// report id / date / valid-until on the left, a decorative centre line, and the
+// МЕРА v2.0.6 wordmark on the right. Static markup, data from fixtures.
+
+import { tokens } from "./tokens";
+import { report, version } from "./fixtures";
+
+export function Footer() {
+ return (
+
+
+
+ ОТЧЁТ {report.id}
+
+
+ ДАТА {report.date}
+
+
+ ДЕЙСТВИТЕЛЕН ДО{" "}
+ {report.validUntil}
+
+
+
+
+
+
+
+
+ МЕРА
+
+
+ {version}
+
+
+
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/HeroBar.tsx b/frontend/src/components/trade-in/v2/HeroBar.tsx
new file mode 100644
index 00000000..ef512725
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/HeroBar.tsx
@@ -0,0 +1,484 @@
+"use client";
+
+import { tokens } from "./tokens";
+import { object, report } from "./fixtures";
+
+interface HeroBarProps {
+ onOpenInfo: () => void;
+}
+
+export default function HeroBar({ onOpenInfo }: HeroBarProps) {
+ return (
+
+
+
+ {/* LEFT: meta + buttons */}
+
+
+
+
+ ОТЧЁТ
+
+
+ {report.id}
+
+
+
+
+ ДАТА
+
+
+ {report.date}
+
+
+
+
+ ДЕЙСТВИТЕЛЕН ДО
+
+
+ {report.validUntil}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ СКАЧАТЬ PDF-ОТЧЁТ
+
+
+
+ КАК РАССЧИТАНО
+
+
+
+
+ {/* RIGHT: photo + overlays */}
+
+
+
+
+
+
+ {/* address card */}
+
+
+ АДРЕС
+
+
+ {object.address}
+
+
+ {object.city}
+
+
+
+
+ {object.streetView}
+
+
+
+
+ КОЭФ. ЛОКАЦИИ
+
+
+
+ {object.locationCoef}
+
+
+ ?
+
+
+
+
+
+ {/* compass */}
+
+
+
+
+
+
+ {object.compass}
+
+
+
+ {/* distance scale */}
+
+
+
+
+
+
+
+
+
+ 0
+ 25
+ 50
+ 75
+ 100м
+
+
+
+ {/* corner brackets */}
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/HistoryView.tsx b/frontend/src/components/trade-in/v2/HistoryView.tsx
new file mode 100644
index 00000000..ca0fbeed
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/HistoryView.tsx
@@ -0,0 +1,359 @@
+// Overlay 04 "ПРОДАЖИ В ДОМЕ" body for the /trade-in/v2 "МЕРА Оценка" port.
+// Faithful markup port of the design's showHistory block (МЕРА Оценка.dc.html,
+// lines 457-489): "История продаж в этом доме" card (house sales) +
+// "ДКП-сделки на улице" card (3 KPI + dealine table + footer count).
+// Static markup — data comes from fixtures, no fetch / no local state.
+
+import { tokens } from "./tokens";
+import { history, dkpRows } from "./fixtures";
+
+// Shared grid templates (kept verbatim from the design column tracks).
+const houseGrid = "2fr 1.4fr 1fr 1fr";
+const dkpGrid = "2.2fr 1fr 1.3fr 1fr .9fr 2fr";
+
+export default function HistoryView() {
+ return (
+
+ {/* ── История продаж в этом доме ───────────────────────────── */}
+
+
+
+ История продаж в этом доме
+
+
+ {history.houseSales.length} ЛОТА
+
+
+
+
+ ЛОТ
+ ЦЕНА НАЧ. → ПОСЛ.
+ ДАТА
+ ЭКСПОЗИЦИЯ
+
+
+ {history.houseSales.map((row, i) => (
+
+ {row.lot}
+
+ {row.priceFrom} →{" "}
+ {row.priceTo}
+
+
+ {row.date}
+
+
+ {row.exposure}
+
+
+ ))}
+
+
+ {/* ── ДКП-сделки на улице ──────────────────────────────────── */}
+
+
+
+
+
+ ПОХОЖИЕ КВАРТИРЫ · ПО ВАШЕЙ УЛИЦЕ
+
+
+ {history.dkpHeader.title}
+
+
+
+ Период:{" "}
+ {history.dkpHeader.period}
+
+ Источник: Росреестр + объявления
+
+
+
+ {history.dkpHeader.note}
+
+
+
+ {/* KPI band */}
+
+
+
+ СДЕЛОК ЗА 24 МЕС
+
+
+ {history.dkpKpi.count}
+
+
+
+
+ МЕДИАНА
+
+
+ {history.dkpKpi.median}{" "}
+
+ ₽/м²
+
+
+
+
+
+ ДИАПАЗОН
+
+
+ {history.dkpKpi.range}{" "}
+
+ млн ₽
+
+
+
+
+
+ {/* Table header */}
+
+ АДРЕС
+ ПЛОЩАДЬ
+ ЦЕНА СДЕЛКИ
+ ₽/М²
+ ДАТА
+ ОБЪЯВЛЕНИЕ ДО СДЕЛКИ
+
+
+ {dkpRows.map((r, i) => (
+
+
+ Екатеринбург, Академика Ландау
+
+
+ {r.area}
+
+
+ {r.price}
+
+
+ {r.ppm}
+
+
+ 01.01.26
+
+
+
+ Яндекс
+
+ {r.ask}
+ {r.delta}
+
+
+ ))}
+
+
+ Показано {dkpRows.length} из {history.dkpKpi.count} сделок
+
+
+
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/LocationDrawer.tsx b/frontend/src/components/trade-in/v2/LocationDrawer.tsx
new file mode 100644
index 00000000..290ece6a
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/LocationDrawer.tsx
@@ -0,0 +1,330 @@
+"use client";
+
+// "КОЭФФИЦИЕНТ ЛОКАЦИИ" right-side drawer for the /trade-in/v2 "МЕРА Оценка"
+// design port. Faithful markup port of the design drawer (МЕРА Оценка.dc.html,
+// lines 618-660): scrim + 452px right slider that slides in via translateX, a
+// big location coefficient, intro, base×coef formula, the factors that raise /
+// lower price, and a geo-source footer. Static markup, data from fixtures.
+// Open/close is driven entirely by props (no internal state, no fetch).
+
+import { tokens } from "./tokens";
+import type { LocationFactor } from "./types";
+import { locationFactors } from "./fixtures";
+
+interface LocationDrawerProps {
+ open: boolean;
+ onClose: () => void;
+}
+
+function FactorRow({
+ factor,
+ variant,
+}: {
+ factor: LocationFactor;
+ variant: "pos" | "neg";
+}) {
+ const isPos = variant === "pos";
+ return (
+
+
+
+ {isPos ? "+" : "−"}
+
+ {factor.label}
+
+
+ {factor.delta}
+
+
+ );
+}
+
+export function LocationDrawer({ open, onClose }: LocationDrawerProps) {
+ return (
+ <>
+
+
+ {/* scrim */}
+
+
+ {/* drawer */}
+
+ {/* left accent rail */}
+
+ {/* corner brackets */}
+
+
+
+ {/* header */}
+
+
+
+ ПОЯСНЕНИЕ К РАСЧЁТУ
+
+
+ КОЭФФИЦИЕНТ ЛОКАЦИИ
+
+
+
+ ✕
+
+
+
+ {/* big coefficient */}
+
+
+ {locationFactors.coef}
+
+
+ множитель к базовой
+
+ цене по городу
+
+
+
+ {/* intro */}
+
+ Показывает, как адрес корректирует стоимость относительно медианы по
+ Екатеринбургу. 1.00 — средний
+ уровень. 0.87 означает, что
+ локация снижает цену примерно на{" "}
+ 13% из-за баланса факторов ниже.
+
+
+ {/* formula */}
+
+
+ {locationFactors.formula.base}
+
+ ×
+
+ {locationFactors.formula.coef}
+
+ =
+
+ {locationFactors.formula.result}
+
+
+
+ {/* positives */}
+
+ ЧТО ПОВЫШАЕТ ЦЕНУ
+
+
+ {locationFactors.positives.map((f, i) => (
+
+ ))}
+
+
+ {/* negatives */}
+
+ ЧТО СНИЖАЕТ ЦЕНУ
+
+
+ {locationFactors.negatives.map((f, i) => (
+
+ ))}
+
+
+ {/* footer source */}
+
+ {locationFactors.footer}
+
+
+ >
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/ObjectSummary.tsx b/frontend/src/components/trade-in/v2/ObjectSummary.tsx
new file mode 100644
index 00000000..7c58c339
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/ObjectSummary.tsx
@@ -0,0 +1,302 @@
+// 03 СВОДКА ОБЪЕКТА — right-rail summary card for the /trade-in/v2 "МЕРА Оценка"
+// design port. Faithful markup port of МЕРА Оценка.dc.html lines 397-422:
+// address + "В РАСЧЁТЕ" badge, four clickable per-section totals, and a
+// "КАЧЕСТВО ДАННЫХ" block with a donut. Data from fixtures; clicks fire
+// onNavigate(row.nav) only — no API, no fetching.
+
+"use client";
+
+import { tokens } from "./tokens";
+import { object, summary } from "./fixtures";
+
+interface ObjectSummaryProps {
+ onNavigate: (i: number) => void;
+}
+
+export function ObjectSummary({ onNavigate }: ObjectSummaryProps) {
+ return (
+
+
+
+ {/* HUD corner bracket */}
+
+
+ {/* Header: 03 СВОДКА ОБЪЕКТА + В РАСЧЁТЕ badge */}
+
+
+
+ 03
+
+
+ СВОДКА ОБЪЕКТА
+
+
+
+
+ В РАСЧЁТЕ
+
+
+
+
+ ИТОГИ ПО РАЗДЕЛАМ
+
+
+ {/* Address */}
+
+
+
+
+
+
+
{object.address}
+
+ {object.city}
+
+
+
+
+
+
+
+ {/* Per-section totals (clickable) */}
+
+ {summary.rows.map((row, i) => (
+
onNavigate(row.nav)}
+ style={{
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ padding: "9px 6px",
+ borderBottom:
+ i < summary.rows.length - 1
+ ? `1px dotted ${tokens.lineGradient}`
+ : undefined,
+ cursor: "pointer",
+ borderRadius: 5,
+ }}
+ >
+
+
+ {row.label}
+
+
+
+ {row.value}
+
+ ↗
+
+
+ ))}
+
+
+ {/* КАЧЕСТВО ДАННЫХ */}
+
+
+
+
+
+
+
+
+
+ КАЧЕСТВО ДАННЫХ
+
+
+ Источников
+
+ {summary.quality.sources}
+
+
+
+ Достоверность
+
+ {summary.quality.confidence}
+
+
+
+ CV
+
+ {summary.quality.cv}
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/ParamsPanel.tsx b/frontend/src/components/trade-in/v2/ParamsPanel.tsx
new file mode 100644
index 00000000..04f068f8
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/ParamsPanel.tsx
@@ -0,0 +1,958 @@
+"use client";
+
+// 01 ПАРАМЕТРЫ КВАРТИРЫ — left card of the /trade-in/v2 "МЕРА Оценка" port.
+// Faithful markup port (МЕРА Оценка.dc.html lines 146-240). Markup-first: all
+// values come from fixtures (no API). Dropdowns / balcony toggle drive local UI
+// state only. Hover/active + @keyframes live in a pp-prefixed local
+
+ {/* HUD corner brackets */}
+
+
+
+ {/* header */}
+
+
+
+ 01
+
+
+ ПАРАМЕТРЫ КВАРТИРЫ
+
+
+
+ ШАГ 1/1
+
+
+
+ {/* MAP */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* center pin */}
+
+
+
+
+ {object.address}
+
+
+
+
+
+ {/* analog price pins */}
+
+ 12,7 млн ₽
+ 54 м²
+
+
+ 7,6 млн ₽
+ 44 м²
+
+
+ 22,4 млн ₽
+ 55 м²
+
+
+ 15,9 млн ₽
+ 55 м²
+
+
+ 14,9 млн ₽
+ 56 м²
+
+
+ {/* analog dots */}
+
+
+
+
+ {/* map controls */}
+
+
+ +
+
+
+ −
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* map corner bracket */}
+
+
+
+ {/* radius row */}
+
+
+ РАДИУС АНАЛИЗА
+
+
+
toggle("radius")}
+ style={{
+ display: "flex",
+ alignItems: "center",
+ gap: 8,
+ background: tokens.surface.w60,
+ border: `1px solid ${tokens.line}`,
+ borderRadius: 5,
+ padding: "6px 10px",
+ fontSize: 12,
+ fontFamily: tokens.font.mono,
+ cursor: "pointer",
+ }}
+ >
+ {radius}
+ ▼
+
+ {openDd === "radius" && (
+
+ {dropdownOptions.radius.map((o) => (
+
pick(setRadius)(o)}
+ style={{
+ padding: "7px 10px",
+ fontSize: 13,
+ fontFamily: tokens.font.mono,
+ borderRadius: 4,
+ cursor: "pointer",
+ color: o === radius ? tokens.accent : tokens.ink,
+ fontWeight: o === radius ? 600 : 400,
+ }}
+ >
+ {o}
+
+ ))}
+
+ )}
+
+
+
+
+ Объявления
+
+
+
+ Сделки
+
+
+
+
+ {/* INPUTS */}
+
+
+
АДРЕС (YANDEX / NOMINATIM)
+
+
+ {object.address} · Куйбышева, 48…
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ПЛОЩАДЬ, М²
+
{object.area}
+
+
+
КОМНАТ
+
toggle("rooms")}
+ value={rooms}
+ options={dropdownOptions.rooms}
+ onSelect={pick(setRooms)}
+ mono
+ triggerFontSize={14}
+ optionFontSize={13}
+ />
+
+
+
+
+
+
+ ЭТАЖ
+ если знаешь
+
+
{object.floor}
+
+
+
+ ВСЕГО ЭТАЖЕЙ
+ если знаешь
+
+
{object.totalFloors}
+
+
+
+
+ 1-й и последний этаж снижают цену на 5–10% — заполни если знаешь
+
+
+
+
+
+ ГОД ПОСТРОЙКИ
+ опц.
+
+
{object.year}
+
+
+
+ ТИП ДОМА
+ опц.
+
+
toggle("houseType")}
+ value={houseType}
+ options={dropdownOptions.houseType}
+ onSelect={pick(setHouseType)}
+ mono={false}
+ triggerFontSize={13}
+ optionFontSize={12}
+ />
+
+
+
+
+
+
+ СОСТОЯНИЕ РЕМОНТА
+ опц.
+
+
toggle("repair")}
+ value={repair}
+ options={dropdownOptions.repair}
+ onSelect={pick(setRepair)}
+ mono={false}
+ triggerFontSize={13}
+ optionFontSize={12}
+ />
+
+
+
БАЛКОН / ЛОДЖИЯ
+
+
setBalcony(false)}
+ style={{ ...balBase, ...(balcony ? balOff : balOn) }}
+ >
+ НЕТ
+
+
setBalcony(true)}
+ style={{ ...balBase, ...(balcony ? balOn : balOff) }}
+ >
+ ДА
+
+
+
+
+
+
+
+ CRM — ДЛЯ МЕНЕДЖЕРА
+ опц.
+
+
toggle("crm")}
+ value={crm}
+ options={dropdownOptions.crm}
+ onSelect={pick(setCrm)}
+ mono={false}
+ triggerFontSize={12}
+ optionFontSize={12}
+ variant="dashed"
+ openUp
+ triggerHeight={34}
+ triggerBg={tokens.surface.w50}
+ triggerColor={tokens.hint}
+ caretColor={tokens.muted4}
+ />
+
+
+
+ {/* button */}
+
+
+ ОЦЕНИТЬ КВАРТИРУ
+
+
+ →
+
+
+
+ {/* cache status */}
+
+ КЭШ ПО АДРЕСУ — 24 Ч
+
+
+ ГОТОВ
+
+
+
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/ResultPanel.tsx b/frontend/src/components/trade-in/v2/ResultPanel.tsx
new file mode 100644
index 00000000..c8020449
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/ResultPanel.tsx
@@ -0,0 +1,880 @@
+"use client";
+
+import { Fragment } from "react";
+import { tokens } from "./tokens";
+import {
+ ranges,
+ resultCards,
+ resultMeta,
+ scatterMini,
+ sources,
+} from "./fixtures";
+
+const {
+ accent,
+ onAccent,
+ accentGlow,
+ ink,
+ muted,
+ muted2,
+ muted3,
+ line,
+ line2,
+ line3,
+ bracket,
+ barLo,
+ barMid,
+ track,
+ success,
+ hint,
+ scatterDeal,
+ disabledBorder,
+ disabledValue,
+ disabledLabel,
+ surface,
+ font,
+} = tokens;
+
+interface ResultPanelProps {
+ onNavigate: (i: number) => void;
+}
+
+// Mini-histogram column tint: peak column = accent, its neighbours = mid, rest = lo.
+function barColor(i: number, bars: number[]): string {
+ const peak = bars.indexOf(Math.max(...bars));
+ if (i === peak) return accent;
+ if (i === peak - 1 || i === peak + 1) return barMid;
+ return barLo;
+}
+
+// Render a string[] with between lines (faithful to the design line breaks).
+function lines(parts: string[]) {
+ return parts.map((part, i) => (
+
+ {i > 0 && }
+ {part}
+
+ ));
+}
+
+// Split a source name on its parenthetical so the design's forced
+// "РОСРЕЕСТР / (ВНУТР.)" two-line break (line-height 1.3) is reproduced; plain
+// names without " (" stay on a single line.
+function sourceName(name: string) {
+ const idx = name.indexOf(" (");
+ if (idx === -1) return name;
+ return (
+ <>
+ {name.slice(0, idx)}
+
+ {name.slice(idx + 1)}
+ >
+ );
+}
+
+export default function ResultPanel({ onNavigate }: ResultPanelProps) {
+ return (
+
+
+
+ {/* header */}
+
+
+
+ 02
+
+
+ РЕЗУЛЬТАТ ОЦЕНКИ
+
+
+
+
+ ИСТОЧНИКОВ:{" "}
+
+ {resultMeta.sources}
+
+
+
+ ДОСТОВЕРНОСТЬ: {resultMeta.confidence}
+
+
+ CV{" "}
+
+ {resultMeta.cv}
+
+
+
+
+
+ {/* result cards */}
+
+ {resultCards.map((card, ci) => (
+
+
+
+ {lines(card.title)}
+
+
+
+ {card.value}
+
+
+ {card.unit}
+
+
+
+ {card.range}
+
+
+ {card.ppm}
+
+
+ {card.bars ? (
+
+
+ {card.bars.map((h, bi) => (
+
+ ))}
+
+
onNavigate(card.nav)}
+ style={{
+ display: "flex",
+ alignItems: "center",
+ gap: 4,
+ cursor: "pointer",
+ fontSize: 9,
+ letterSpacing: 0.3,
+ color: accent,
+ whiteSpace: "nowrap",
+ }}
+ >
+ Подробнее →
+
+
+ ) : (
+ <>
+
onNavigate(card.nav)}
+ style={{
+ marginTop: 16,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "flex-end",
+ gap: 4,
+ cursor: "pointer",
+ fontSize: 9,
+ letterSpacing: 0.3,
+ color: accent,
+ }}
+ >
+ Подробнее →
+
+
+
+
+
+
+
+
+ {card.delta}
+
+
+ {card.deltaLabel}
+
+
+
+ >
+ )}
+
+ ))}
+
+
+ {/* ranges + radar */}
+
+
+
+ {/* left range — ads */}
+
+
+ {lines(ranges.ads.label)}
+
+
+ медиана · {ranges.ads.median}
+
+
+
+ {ranges.ads.lo}
+ {ranges.ads.median}
+ {ranges.ads.hi}
+
+
+
+ {/* radar — price × sell-time scatter */}
+
+
+ ЦЕНА × СРОК ПРОДАЖИ
+
+
+
+
+
+
+
+
+
+
+
+ {scatterMini.deals.map((p, i) => (
+
+ ))}
+
+
+ {scatterMini.analogs.map((p, i) => (
+
+ ))}
+
+
+
+
+ {scatterMini.yTicks.map((t, i) => (
+
+ {t.label}
+
+ ))}
+
+
+ {scatterMini.xTicks.map((t, i) => (
+
+ {t.label}
+
+ ))}
+
+
+ СРОК ПРОДАЖИ, ДН
+
+
+ ЦЕНА ПРОДАЖИ
+
+
+
+
+
+ аналоги
+
+
+
+ сделки
+
+
+
+ объект
+
+
+
onNavigate(3)}
+ style={{
+ display: "flex",
+ alignItems: "center",
+ gap: 4,
+ cursor: "pointer",
+ fontSize: 9,
+ letterSpacing: 0.3,
+ color: accent,
+ marginTop: 2,
+ }}
+ >
+ Подробнее →
+
+
+
+ {/* right range — deals */}
+
+
+ {lines(ranges.deals.label)}
+
+
+ медиана · {ranges.deals.median}
+
+
+
+ {ranges.deals.lo}
+ {ranges.deals.median}
+ {ranges.deals.hi}
+
+
+
+
+ {/* sources */}
+
+
+
+ ИСТОЧНИКИ ДАННЫХ
+
+ onNavigate(2)}
+ style={{
+ display: "flex",
+ alignItems: "center",
+ gap: 4,
+ cursor: "pointer",
+ fontSize: 9.5,
+ letterSpacing: 0.3,
+ color: accent,
+ }}
+ >
+ Подробнее →
+
+
+
+ {sources.map((s, si) =>
+ s.active ? (
+
+
+ {sourceName(s.name)}
+
+
+ {s.count}
+
+
+
+ {s.label}
+
+
+ ) : (
+
+
+ {s.name}
+
+
+ {s.count}
+
+
+ {s.label}
+
+
+ ),
+ )}
+
+
+
+ При недоступности источника частичный результат не блокируется
+
+
+ ПОСТРОЕНО ПО 6 АНАЛОГАМ И 10 СДЕЛКАМ ЗА 6 МЕСЯЦЕВ
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/SectionOverlay.tsx b/frontend/src/components/trade-in/v2/SectionOverlay.tsx
new file mode 100644
index 00000000..7f7e465f
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/SectionOverlay.tsx
@@ -0,0 +1,150 @@
+"use client";
+
+// Overlay wrapper for the nav-driven section panels of the /trade-in/v2
+// "МЕРА Оценка" design. Faithful port of the design overlay container
+// (МЕРА Оценка.dc.html lines 443-453 + 615-616): a glass/blur absolute layer
+// with HUD corner brackets, a header (section number + title + "← К ОЦЕНКЕ"
+// button) and a scrollable body that swaps the active view.
+
+import { tokens } from "./tokens";
+import { overlayNums, overlayTitles } from "./fixtures";
+import HistoryView from "./HistoryView";
+import SourcesView from "./SourcesView";
+import AnalyticsView from "./AnalyticsView";
+import { CacheView } from "./CacheView";
+
+interface SectionOverlayProps {
+ active: number;
+ onClose: () => void;
+ onNavigate: (i: number) => void;
+}
+
+export default function SectionOverlay({
+ active,
+ onClose,
+ onNavigate,
+}: SectionOverlayProps) {
+ return (
+
+
+
+ {/* HUD corner brackets */}
+
+
+
+ {/* overlay header */}
+
+
+
+ {overlayNums[active]}
+
+
+ {overlayTitles[active]}
+
+
+
+ ←
+
+ К ОЦЕНКЕ
+
+
+
+
+ {/* overlay body */}
+
+ {active === 1 &&
}
+ {active === 2 &&
}
+ {active === 3 &&
}
+ {active === 4 &&
}
+
+
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/SourcesView.tsx b/frontend/src/components/trade-in/v2/SourcesView.tsx
new file mode 100644
index 00000000..9ea730a7
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/SourcesView.tsx
@@ -0,0 +1,401 @@
+import type { CSSProperties } from "react";
+
+import { tokens } from "./tokens";
+import { adRows, dealRows, marketAds, marketDeals } from "./fixtures";
+import type { AdRow, DealRow } from "./types";
+
+// Overlay 05 — РЫНОК · АНАЛОГИ И СДЕЛКИ.
+// Faithful markup port from "МЕРА Оценка.dc.html" (lines 492-537).
+// Card A: объявления в продаже (3 KPI + filter chips + adRows table).
+// Card B: фактические сделки (3 KPI + dealRows table).
+// Static markup: data from fixtures, no fetching, no interactive state.
+
+// One-off tints not present in the v2 palette (kept literal, faithful to design).
+const KPI_BG = "rgba(238,244,250,.5)";
+const BADGE_BG = "#eaf1f8";
+const AVITO_DOT = "#e8743b";
+
+const AD_GRID = "2.4fr 1fr 1fr 1.2fr .7fr 1fr";
+const DEAL_GRID = "2.6fr 1.2fr 1fr 1.2fr 1fr";
+
+const card: CSSProperties = {
+ background: tokens.surface.w55,
+ border: `1px solid ${tokens.line2}`,
+ borderRadius: 8,
+ overflow: "hidden",
+};
+
+const cardHeader: CSSProperties = {
+ display: "flex",
+ alignItems: "flex-start",
+ justifyContent: "space-between",
+ padding: "14px 18px",
+ borderBottom: `1px solid ${tokens.lineSoft}`,
+};
+
+const eyebrow: CSSProperties = {
+ fontSize: "8.5px",
+ letterSpacing: "2px",
+ color: tokens.muted2,
+ marginBottom: 5,
+};
+
+const cardTitle: CSSProperties = {
+ fontSize: "12px",
+ fontWeight: 600,
+ color: tokens.ink2,
+};
+
+const headerRight: CSSProperties = {
+ textAlign: "right",
+ fontSize: "9.5px",
+ color: tokens.muted2,
+ lineHeight: 1.6,
+};
+
+const kpiRow: CSSProperties = {
+ display: "grid",
+ gridTemplateColumns: "repeat(3,1fr)",
+ borderBottom: `1px solid ${tokens.lineSoft}`,
+ background: KPI_BG,
+};
+
+const kpiLabel: CSSProperties = {
+ fontSize: "9px",
+ letterSpacing: "1.5px",
+ color: tokens.muted2,
+};
+
+const tableHead: CSSProperties = {
+ gap: "10px",
+ padding: "9px 18px",
+ fontSize: "9px",
+ letterSpacing: "1px",
+ color: tokens.muted2,
+ borderBottom: `1px solid ${tokens.lineSoft2}`,
+};
+
+const tableRow: CSSProperties = {
+ gap: "10px",
+ padding: "11px 18px",
+ fontSize: "11px",
+ borderBottom: `1px solid ${tokens.lineSoft3}`,
+ alignItems: "center",
+};
+
+const metaSpan: CSSProperties = {
+ color: tokens.muted2,
+ fontFamily: tokens.font.mono,
+ fontSize: "9.5px",
+};
+
+const badge: CSSProperties = {
+ fontSize: "9px",
+ background: BADGE_BG,
+ border: `1px solid ${tokens.line2}`,
+ borderRadius: 3,
+ padding: "2px 8px",
+ color: tokens.muted,
+};
+
+const chip: CSSProperties = {
+ fontSize: "10px",
+ color: tokens.muted,
+ border: `1px solid ${tokens.line2}`,
+ borderRadius: 20,
+ padding: "3px 10px",
+};
+
+const monoMuted: CSSProperties = {
+ fontFamily: tokens.font.mono,
+ color: tokens.muted,
+};
+
+const monoInk: CSSProperties = {
+ fontFamily: tokens.font.mono,
+ color: tokens.ink2,
+};
+
+const monoMuted2: CSSProperties = {
+ fontFamily: tokens.font.mono,
+ color: tokens.muted2,
+};
+
+function KpiCell({
+ label,
+ value,
+ unit,
+ valueColor = tokens.ink2,
+ unitColor = tokens.muted,
+ last = false,
+}: {
+ label: string;
+ value: string;
+ unit: string;
+ valueColor?: string;
+ unitColor?: string;
+ last?: boolean;
+}) {
+ return (
+
+
{label}
+
+ {value}{" "}
+ {unit}
+
+
+ );
+}
+
+export default function SourcesView() {
+ return (
+
+
+
+ {/* объявления */}
+
+
+
+
ПОХОЖИЕ КВАРТИРЫ · В ПРОДАЖЕ
+
+ Объявления — аналогичные квартиры в продаже
+
+
+
+ Обновлено: —
+ Источников: 2 / 7
+
+
+
+
+
+
+
+
+
+
+
+ ФИЛЬТР
+
+ {marketAds.filters.map((f, i) => (
+
+ {f}
+
+ ))}
+
+
+ Avito · 10
+
+
+
+
+ АДРЕС
+ ИСТОЧНИК
+ ₽/М²
+ СТОИМОСТЬ
+ ДИСТ.
+
+
+
+ {adRows.map((r: AdRow, i) => (
+
+
+ {r.addr}
+
+ {" · "}
+ {r.meta}
+
+
+
+ Avito
+
+ {r.ppm}
+ {r.price}
+ —
+
+ Росреестр ↗
+
+
+ ))}
+
+
+ {/* фактические сделки */}
+
+
+
+
ПОХОЖИЕ КВАРТИРЫ · СДЕЛКИ
+
+ Фактические сделки по аналогичным квартирам
+
+
+
+ Период: за 12 мес.
+
+ проверенные данные · подтверждено Росреестром
+
+
+
+
+
+
+
+
+
+
+ АДРЕС
+ ИСТОЧНИК
+ ₽/М²
+ ЦЕНА СДЕЛКИ
+ ДАТА
+
+
+ {dealRows.map((r: DealRow, i) => (
+
+
+ {r.addr}
+
+ {" · "}
+ {r.meta}
+
+
+
+ Росреестр {" "}
+
+ по улице
+
+
+ {r.ppm}
+ {r.price}
+ 01.01.26
+
+ ))}
+
+
+ Показано 10 фактических сделок
+
+
+
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/TopNav.tsx b/frontend/src/components/trade-in/v2/TopNav.tsx
new file mode 100644
index 00000000..c0b768df
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/TopNav.tsx
@@ -0,0 +1,407 @@
+"use client";
+
+// Top navigation bar for the /trade-in/v2 "МЕРА Оценка" design port.
+// Faithful markup port of the design header (МЕРА Оценка.dc.html, lines 42-90):
+// inline SVG logo + version + 5 nav tabs (active underline/triangle) + user menu.
+// Tabs change only local UI state via onNavigate; the user dropdown owns its
+// own useState. No data fetching — labels/user come from fixtures, colours from
+// tokens.
+
+import { useState } from "react";
+import type { CSSProperties } from "react";
+
+import { tokens } from "./tokens";
+import { navLabels, user, version } from "./fixtures";
+
+interface TopNavProps {
+ active: number;
+ onNavigate: (i: number) => void;
+}
+
+const menuItemStyle: CSSProperties = {
+ display: "flex",
+ alignItems: "center",
+ gap: "10px",
+ padding: "9px 10px",
+ borderRadius: "6px",
+ cursor: "pointer",
+ fontSize: "12.5px",
+ color: tokens.body,
+ transition: "background .12s",
+};
+
+export default function TopNav({ active, onNavigate }: TopNavProps) {
+ const [userOpen, setUserOpen] = useState(false);
+
+ return (
+ <>
+
+
+
+ {/* Logo + version */}
+
+
+
+
+
+
+
+
+
+
+ МЕРА
+
+
+ ОЦЕНКА НЕДВИЖИМОСТИ
+
+
+
+
+ {version}
+
+
+
+ {/* Nav tabs */}
+
+ {navLabels.map((label, i) => (
+
onNavigate(i)}
+ className={`tnav-tab${active === i ? " tnav-tab--active" : ""}`}
+ style={{ position: "relative", whiteSpace: "nowrap" }}
+ >
+ {label}
+
+ {i === 0 && (
+
+ )}
+
+ ))}
+
+
+ {/* User menu */}
+
+
setUserOpen((o) => !o)}
+ className="tnav-userbtn"
+ style={{
+ display: "flex",
+ alignItems: "center",
+ gap: "10px",
+ cursor: "pointer",
+ padding: "5px 8px 5px 6px",
+ borderRadius: "9px",
+ transition: "background .15s",
+ }}
+ >
+
+ {user.initials}
+
+
+
+ {user.name}
+
+
+ {user.org}
+
+
+
▾
+
+
+ {userOpen && (
+
+
+
+ {user.initials}
+
+
+
+ {user.name}
+
+
+ {user.email}
+
+
+
+
+
+
+
+
+
+
+
+ Мои отчёты{" "}
+
+ {user.reports}
+
+
+
+
+
+
+
+
+
+
setUserOpen(false)}
+ className="tnav-logout"
+ style={{ ...menuItemStyle, color: tokens.danger }}
+ >
+
+
+
+ Выйти
+
+
+ )}
+
+
+ >
+ );
+}
diff --git a/frontend/src/components/trade-in/v2/fixtures.ts b/frontend/src/components/trade-in/v2/fixtures.ts
new file mode 100644
index 00000000..3f310437
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/fixtures.ts
@@ -0,0 +1,665 @@
+// Sample/markup data for the /trade-in/v2 "МЕРА Оценка" design port.
+// Extracted verbatim from the pixel design (МЕРА Оценка.dc.html). This is the
+// data source for the markup — components must NOT fetch; they read from here.
+// Colour fields reference tokens so the palette stays the single source of truth.
+
+import { tokens } from "./tokens";
+import type {
+ Analytics,
+ CacheKpi,
+ CacheRow,
+ DkpRow,
+ AdRow,
+ DealRow,
+ DropdownOptions,
+ History,
+ LocationFactors,
+ MarketAds,
+ MarketDeals,
+ ObjectInfo,
+ Ranges,
+ Report,
+ ResultCard,
+ ResultMeta,
+ ScatterMini,
+ SourceCard,
+ Summary,
+ User,
+} from "./types";
+
+export const report: Report = {
+ id: "9b9e2737",
+ date: "20.06.2026",
+ validUntil: "21.06.2026",
+};
+
+export const object: ObjectInfo = {
+ address: "ул. Малышева, 30",
+ city: "Екатеринбург, Россия",
+ area: "54",
+ rooms: "2",
+ floor: "5",
+ totalFloors: "17",
+ year: "1985",
+ houseType: "Панельный",
+ repair: "Хороший",
+ balcony: true,
+ locationCoef: "0.87",
+ streetView: "Street View · май 2024",
+ compass: "СЕВЕРО-ЗАПАД",
+};
+
+// ---- 02 RESULT ------------------------------------------------------------
+
+export const resultCards: ResultCard[] = [
+ {
+ title: ["РЕКОМЕНДОВАННАЯ ЦЕНА", "В ОБЪЯВЛЕНИИ"],
+ value: "11,29",
+ unit: "млн ₽",
+ range: "7,72 — 14,46 млн ₽",
+ ppm: "225 801 ₽/м² · по объявлениям",
+ bars: [30, 55, 80, 100, 70, 45, 25, 35],
+ nav: 2,
+ },
+ {
+ title: ["ОЖИДАЕМАЯ СДЕЛКА", "(ПОЛУЧИТЕ)"],
+ value: "10,00",
+ unit: "млн ₽",
+ range: "6,83 — 12,81 млн ₽",
+ ppm: "199 973 ₽/м²",
+ delta: "−11%",
+ deltaLabel: "К РЫНКУ",
+ nav: 2,
+ },
+ {
+ title: ["ДКП · РОСРЕЕСТР", "(ФАКТИЧЕСКИЕ СДЕЛКИ)"],
+ value: "9,43",
+ unit: "млн ₽",
+ range: "6,10 — 12,72 млн ₽",
+ ppm: "188 672 ₽/м² · 44 сделки",
+ bars: [25, 45, 70, 100, 85, 55, 30, 20],
+ nav: 1,
+ },
+];
+
+export const resultMeta: ResultMeta = {
+ sources: "3 / 7",
+ confidence: "СРЕДНЯЯ",
+ cv: "59.7%",
+};
+
+export const ranges: Ranges = {
+ ads: {
+ label: ["ДИАПАЗОН ЦЕН В ОБЪЯВЛЕНИЯХ", "(БЕЗ УЧЁТА РЕМОНТА)"],
+ median: "11,29 млн ₽",
+ lo: "7,72 млн",
+ hi: "14,46 млн",
+ marker: { fillLeft: "8%", fillRight: "18%", dotLeft: "58%" },
+ },
+ deals: {
+ label: ["ДИАПАЗОН ЦЕН ПО", "ФАКТИЧЕСКИМ СДЕЛКАМ"],
+ median: "6,80 млн ₽",
+ lo: "4,30 млн",
+ hi: "19,70 млн",
+ marker: { fillLeft: "4%", fillRight: "55%", dotLeft: "33%" },
+ },
+};
+
+// Mini "ЦЕНА × СРОК ПРОДАЖИ" scatter inside the ranges card (viewBox 168×140).
+export const scatterMini: ScatterMini = {
+ deals: [
+ { x: 52, y: 96, r: 2.4 },
+ { x: 64, y: 88, r: 2.4 },
+ { x: 58, y: 101, r: 2.4 },
+ { x: 74, y: 85, r: 2.4 },
+ { x: 47, y: 103, r: 2.4 },
+ { x: 88, y: 79, r: 2.4 },
+ { x: 70, y: 93, r: 2.4 },
+ { x: 55, y: 90, r: 2.4 },
+ { x: 82, y: 99, r: 2.4 },
+ { x: 44, y: 97, r: 2.4 },
+ ],
+ analogs: [
+ { x: 118, y: 48, r: 3 },
+ { x: 100, y: 60, r: 3 },
+ { x: 132, y: 40, r: 3 },
+ { x: 92, y: 66, r: 3 },
+ { x: 112, y: 54, r: 3 },
+ { x: 142, y: 34, r: 3 },
+ ],
+ subject: { x: 96, y: 70, r: 3.6 },
+ yTicks: [
+ { label: "15М", y: 13 },
+ { label: "10М", y: 77 },
+ { label: "4М", y: 112 },
+ ],
+ xTicks: [
+ { label: "0", x: 34 },
+ { label: "90", x: 96 },
+ { label: "180", x: 158 },
+ ],
+};
+
+export const sources: SourceCard[] = [
+ { name: "ЦИАН", count: "7", label: "лотов", active: true },
+ { name: "Я.НЕДВИЖИМОСТЬ", count: "1", label: "лот", active: true },
+ { name: "РОСРЕЕСТР (ВНУТР.)", count: "10", label: "лотов", active: true },
+ { name: "АВИТО ОЦЕНКА", count: "—", label: "нет данных", active: false },
+ { name: "ДОМКЛИК ПРАЙС", count: "—", label: "нет данных", active: false },
+ { name: "RESTATE", count: "—", label: "нет данных", active: false },
+ { name: "N1.RU", count: "—", label: "нет данных", active: false },
+];
+
+// ---- 03 SUMMARY -----------------------------------------------------------
+
+export const summary: Summary = {
+ rows: [
+ { label: "Продажи в доме", value: "2 · 32 дн", dot: "accent", nav: 1 },
+ { label: "Аналоги", value: "23 · 6,13 млн", dot: "accent", nav: 2 },
+ { label: "Сделки", value: "10 · 5,95 млн", dot: "muted", nav: 2 },
+ { label: "Аналитика", value: "67 дн · −3%", dot: "accent", nav: 3 },
+ ],
+ quality: { sources: "3 / 7", confidence: "средняя", cv: "59.7%" },
+};
+
+// ---- OVERLAY 04: ПРОДАЖИ В ДОМЕ -------------------------------------------
+
+export const dkpRows: DkpRow[] = [
+ {
+ area: "33.9 м²",
+ price: "5 950 000 ₽",
+ ppm: "175 516",
+ ask: "5 272 800 ₽ за 3 дн до",
+ delta: "+12.8%",
+ deltaColor: tokens.danger,
+ },
+ {
+ area: "35.4 м²",
+ price: "4 900 000 ₽",
+ ppm: "138 418",
+ ask: "5 186 700 ₽ за 3 дн до",
+ delta: "−5.5%",
+ deltaColor: tokens.success,
+ },
+ {
+ area: "36.2 м²",
+ price: "4 200 000 ₽",
+ ppm: "116 022",
+ ask: "5 186 700 ₽ за 3 дн до",
+ delta: "−19.0%",
+ deltaColor: tokens.success,
+ },
+ {
+ area: "37.0 м²",
+ price: "7 503 227 ₽",
+ ppm: "202 790",
+ ask: "5 272 800 ₽ за 3 дн до",
+ delta: "+42.3%",
+ deltaColor: tokens.danger,
+ },
+ {
+ area: "36.8 м²",
+ price: "7 140 243 ₽",
+ ppm: "194 028",
+ ask: "5 272 800 ₽ за 3 дн до",
+ delta: "+35.4%",
+ deltaColor: tokens.danger,
+ },
+ {
+ area: "42.1 м²",
+ price: "6 535 050 ₽",
+ ppm: "155 227",
+ ask: "5 272 800 ₽ за 3 дн до",
+ delta: "+23.9%",
+ deltaColor: tokens.danger,
+ },
+ {
+ area: "42.0 м²",
+ price: "6 650 000 ₽",
+ ppm: "158 333",
+ ask: "5 781 600 ₽ за 3 дн до",
+ delta: "+15.0%",
+ deltaColor: tokens.danger,
+ },
+ {
+ area: "32.5 м²",
+ price: "5 362 650 ₽",
+ ppm: "165 005",
+ ask: "5 325 600 ₽ за 3 дн до",
+ delta: "+0.7%",
+ deltaColor: tokens.danger,
+ },
+ {
+ area: "38.8 м²",
+ price: "4 380 000 ₽",
+ ppm: "112 887",
+ ask: "5 325 600 ₽ за 3 дн до",
+ delta: "−17.8%",
+ deltaColor: tokens.success,
+ },
+ {
+ area: "34.9 м²",
+ price: "5 000 000 ₽",
+ ppm: "143 266",
+ ask: "5 254 500 ₽ за 3 дн до",
+ delta: "−4.8%",
+ deltaColor: tokens.success,
+ },
+];
+
+export const history: History = {
+ houseSales: [
+ {
+ lot: "2-к. квартира, 50 м², 6/16 эт.",
+ priceFrom: "6,25",
+ priceTo: "5,98 млн ₽",
+ date: "11.07.2025",
+ exposure: "32 дн.",
+ },
+ {
+ lot: "2-к. квартира, 60 м², 6/16 эт.",
+ priceFrom: "7,29",
+ priceTo: "7,09 млн ₽",
+ date: "11.07.2025",
+ exposure: "31 дн.",
+ },
+ ],
+ dkpHeader: {
+ title: "ДКП-сделки на улице Академика Ландау",
+ period: "24 мес",
+ note: "78% сделок имеют историческую цену в объявлении · медианный торг −36.0%. Данные по улице, не по дому.",
+ },
+ dkpKpi: { count: "95", median: "140 066", range: "3,1 – 7,5" },
+};
+
+// ---- OVERLAY 05: АНАЛОГИЧНЫЕ ОБЪЯВЛЕНИЯ -----------------------------------
+
+export const adRows: AdRow[] = [
+ {
+ addr: "ул. Академика Ландау, 39",
+ meta: "32.5 м² · 1-к · этаж 12/15",
+ ppm: "170 769",
+ price: "5 550 000",
+ },
+ {
+ addr: "ул. Академика Ландау, 35А/2",
+ meta: "33.5 м² · 1-к · этаж 13/25",
+ ppm: "174 626",
+ price: "5 850 000",
+ },
+ {
+ addr: "ул. Академика Ландау, 38",
+ meta: "32.7 м² · 1-к · этаж 10/16",
+ ppm: "171 253",
+ price: "5 600 000",
+ },
+ {
+ addr: "ул. Академика Ландау, 35",
+ meta: "32.7 м² · 1-к · этаж 6/12",
+ ppm: "178 999",
+ price: "5 853 300",
+ },
+ {
+ addr: "ул. Академика Ландау, 35",
+ meta: "37.4 м² · 1-к · этаж 8/12",
+ ppm: "149 732",
+ price: "5 600 000",
+ },
+ {
+ addr: "ул. Академика Парина, 29",
+ meta: "40.5 м² · 1-к · этаж 22/23",
+ ppm: "160 493",
+ price: "6 500 000",
+ },
+ {
+ addr: "ул. Академика Ландау, 39",
+ meta: "32.5 м² · 1-к · этаж 15/15",
+ ppm: "146 153",
+ price: "4 750 000",
+ },
+ {
+ addr: "ул. Академика Ландау, 39",
+ meta: "34.9 м² · 1-к · этаж 9/15",
+ ppm: "143 266",
+ price: "5 000 000",
+ },
+ {
+ addr: "ул. Академика Парина, 33",
+ meta: "37.1 м² · 1-к · этаж 6/14",
+ ppm: "160 916",
+ price: "5 970 000",
+ },
+ {
+ addr: "ул. Академика Ландау, 34",
+ meta: "41.7 м² · 1-к · этаж 22/23",
+ ppm: "167 865",
+ price: "6 999 999",
+ },
+];
+
+export const dealRows: DealRow[] = [
+ {
+ addr: "Екатеринбург, Академика Парина",
+ meta: "38.1 м² · 1-к",
+ ppm: "161 417",
+ price: "6 150 000",
+ },
+ {
+ addr: "Екатеринбург, Академика Ландау",
+ meta: "33.9 м² · 1-к",
+ ppm: "175 516",
+ price: "5 950 000",
+ },
+ {
+ addr: "Екатеринбург, Академика Ландау",
+ meta: "39.1 м² · 1-к",
+ ppm: "148 338",
+ price: "5 800 000",
+ },
+ {
+ addr: "Екатеринбург, Академика Парина",
+ meta: "33.8 м² · 1-к",
+ ppm: "147 929",
+ price: "5 000 000",
+ },
+ {
+ addr: "Екатеринбург, Академика Сахарова",
+ meta: "37.1 м² · 1-к",
+ ppm: "167 116",
+ price: "6 200 000",
+ },
+ {
+ addr: "Екатеринбург, Академика Вавилова",
+ meta: "38.8 м² · 1-к",
+ ppm: "165 437",
+ price: "6 418 972",
+ },
+ {
+ addr: "Екатеринбург, Академика Ландау",
+ meta: "42.0 м² · 1-к",
+ ppm: "158 333",
+ price: "6 650 000",
+ },
+ {
+ addr: "Екатеринбург, Академика Ландау",
+ meta: "34.9 м² · 1-к",
+ ppm: "143 266",
+ price: "5 000 000",
+ },
+ {
+ addr: "Екатеринбург, Академика Парина",
+ meta: "32.9 м² · 1-к",
+ ppm: "159 574",
+ price: "5 250 000",
+ },
+ {
+ addr: "Екатеринбург, Академика Парина",
+ meta: "41.2 м² · 1-к",
+ ppm: "101 942",
+ price: "4 200 000",
+ },
+];
+
+export const marketAds: MarketAds = {
+ kpi: {
+ count: "23",
+ median: "6,13",
+ ppm: "161 351",
+ range: "4,7–7,5",
+ cv: "45.9%",
+ },
+ filters: ["Расстояние ≤ 2 км", "Планировка 1-к", "Площадь 32.5 – 41.7 м²"],
+};
+
+export const marketDeals: MarketDeals = {
+ kpi: {
+ count: "10",
+ median: "5,95",
+ delta: "−3% к рынку",
+ range: "4,2 – 6,7",
+ },
+};
+
+// ---- OVERLAY 06: АНАЛИТИКА ДОМА -------------------------------------------
+
+export const analytics: Analytics = {
+ header: { ads: "615 объявлений", radius: "в радиусе 300 м" },
+ kpis: [
+ {
+ label: "СРЕДНЯЯ ЭКСПОЗИЦИЯ",
+ value: "67",
+ unit: "дн.",
+ sub: "по архивным объявлениям",
+ },
+ {
+ label: "СРЕДНИЙ ТОРГ",
+ value: "−3.0%",
+ color: tokens.success,
+ sub: "от начальной до итоговой цены",
+ },
+ {
+ label: "ДОЛЯ СНЯТЫХ",
+ value: "69%",
+ sub: "421 из 615",
+ },
+ ],
+ sellTimeNote: "Медиана экспозиции по архивным лотам · эталон 177 394 ₽/м²",
+ sellTime: [
+ {
+ tier: "−5% от рынка",
+ days: "~63 дн.",
+ range: "обычно 18–180 дн.",
+ count: "69 аналогов",
+ variant: "success",
+ },
+ {
+ tier: "По медиане",
+ days: "~65 дн.",
+ range: "обычно 18–182 дн.",
+ count: "79 аналогов",
+ variant: "accent",
+ },
+ {
+ tier: "+5%",
+ days: "~78 дн.",
+ range: "обычно 16–212 дн.",
+ count: "61 аналог",
+ variant: "gold",
+ },
+ {
+ tier: "+10%",
+ days: "~67 дн.",
+ range: "обычно 26–260 дн.",
+ count: "57 аналогов",
+ variant: "danger",
+ },
+ ],
+ priceHistory: {
+ note: "Медиана ₽/м² по годам · только этот дом · Avito + Яндекс (596 лотов)",
+ years: ["2022", "2023", "2024", "2025", "2026"],
+ avito: "58,75 260,82 470,69 680,69 885,73",
+ yandex: "58,75 260,82 470,51 680,42 885,24",
+ },
+ scatterDetail: {
+ note: "16 сделок и 6 аналогов в радиусе 300 м",
+ deals: [
+ { x: 150, y: 210, r: 4.5 },
+ { x: 200, y: 196, r: 4.5 },
+ { x: 175, y: 222, r: 4.5 },
+ { x: 250, y: 188, r: 4.5 },
+ { x: 220, y: 204, r: 4.5 },
+ { x: 300, y: 176, r: 4.5 },
+ { x: 270, y: 200, r: 4.5 },
+ { x: 330, y: 166, r: 4.5 },
+ { x: 360, y: 182, r: 4.5 },
+ { x: 195, y: 214, r: 4.5 },
+ { x: 285, y: 190, r: 4.5 },
+ { x: 160, y: 200, r: 4.5 },
+ { x: 345, y: 198, r: 4.5 },
+ { x: 240, y: 172, r: 4.5 },
+ { x: 315, y: 186, r: 4.5 },
+ { x: 180, y: 232, r: 4.5 },
+ ],
+ analogs: [
+ { x: 600, y: 96, r: 5.5 },
+ { x: 540, y: 124, r: 5.5 },
+ { x: 670, y: 84, r: 5.5 },
+ { x: 500, y: 142, r: 5.5 },
+ { x: 640, y: 104, r: 5.5 },
+ { x: 730, y: 70, r: 5.5 },
+ ],
+ subject: { x: 445, y: 150, r: 6 },
+ yTicks: [
+ { label: "15М", y: 24 },
+ { label: "12М", y: 81 },
+ { label: "9М", y: 138 },
+ { label: "6М", y: 195 },
+ { label: "4М", y: 252 },
+ ],
+ xTicks: [
+ { label: "0", x: 60 },
+ { label: "45", x: 265 },
+ { label: "90", x: 470 },
+ { label: "135", x: 675 },
+ { label: "180", x: 880 },
+ ],
+ },
+};
+
+// ---- OVERLAY 07: ПРЕДЫДУЩИЕ ОЦЕНКИ ----------------------------------------
+
+export const cacheRows: CacheRow[] = [
+ {
+ addr: "ул. Малышева, 30",
+ time: "12:45:02",
+ src: "3 / 7",
+ status: "свежий",
+ statusColor: tokens.success,
+ },
+ {
+ addr: "ул. Куйбышева, 48",
+ time: "12:31:50",
+ src: "4 / 7",
+ status: "свежий",
+ statusColor: tokens.success,
+ },
+ {
+ addr: "ул. Академика Ландау, 35",
+ time: "11:58:14",
+ src: "2 / 7",
+ status: "свежий",
+ statusColor: tokens.success,
+ },
+ {
+ addr: "ул. 8 Марта, 120",
+ time: "09:12:40",
+ src: "3 / 7",
+ status: "устаревает",
+ statusColor: tokens.warn,
+ },
+ {
+ addr: "пр. Ленина, 5",
+ time: "вчера 18:04",
+ src: "5 / 7",
+ status: "устарел",
+ statusColor: tokens.danger,
+ },
+ {
+ addr: "ул. Белинского, 86",
+ time: "вчера 14:22",
+ src: "2 / 7",
+ status: "устарел",
+ statusColor: tokens.danger,
+ },
+];
+
+export const cacheKpi: CacheKpi[] = [
+ { label: "ВСЕГО ОЦЕНОК", value: "128", sub: "за последние 30 дней" },
+ {
+ label: "СРЕДНЯЯ ЦЕНА ОЦЕНОК",
+ value: "9,6",
+ unit: "млн ₽",
+ sub: "по проведённым оценкам",
+ },
+ { label: "ПОВТОРНЫЕ АДРЕСА", value: "18", unit: "%", sub: "доля переоценок" },
+];
+
+// ---- INPUTS / DROPDOWNS ---------------------------------------------------
+
+export const dropdownOptions: DropdownOptions = {
+ rooms: ["1", "2", "3", "4", "5+"],
+ houseType: [
+ "Панельный",
+ "Кирпичный",
+ "Монолитный",
+ "Блочный",
+ "Монолитно-кирпичный",
+ ],
+ repair: [
+ "Без отделки",
+ "Требует ремонта",
+ "Удовлетворительный",
+ "Хороший",
+ "Отличный",
+ ],
+ radius: ["300 м", "500 м", "1000 м", "2000 м"],
+ crm: [
+ "Не заполнять",
+ "Создать сделку",
+ "Привязать к контакту",
+ "Экспорт в CRM",
+ ],
+};
+
+// ---- КОЭФ. ЛОКАЦИИ DRAWER -------------------------------------------------
+
+export const locationFactors: LocationFactors = {
+ coef: "0.87",
+ intro:
+ "Показывает, как адрес корректирует стоимость относительно медианы по Екатеринбургу. 1.00 — средний уровень. 0.87 означает, что локация снижает цену примерно на 13% из-за баланса факторов ниже.",
+ formula: { base: "11,29 млн", coef: "0.87", result: "9,82 млн ₽" },
+ positives: [
+ { label: "Центр города, пешая доступность ключевых точек", delta: "+0.06" },
+ { label: "Транспортные узлы и остановки рядом", delta: "+0.05" },
+ { label: "Набережная и парк в 10 минутах", delta: "+0.04" },
+ { label: "Развитая торговая инфраструктура", delta: "+0.03" },
+ ],
+ negatives: [
+ { label: "Оживлённая магистраль, шумовая нагрузка", delta: "−0.07" },
+ { label: "Дефицит парковочных мест", delta: "−0.05" },
+ { label: "Возраст жилого фонда района (1985)", delta: "−0.04" },
+ { label: "Износ инженерных сетей квартала", delta: "−0.02" },
+ ],
+ footer:
+ "Источник геоданных: OpenStreetMap POI, транспортная доступность, шумовые и экологические слои. Коэффициент пересчитывается при смене адреса.",
+};
+
+// ---- NAV / CHROME ---------------------------------------------------------
+
+export const navLabels: string[] = [
+ "ОЦЕНКА",
+ "ПРОДАЖИ В ДОМЕ",
+ "АНАЛОГИЧНЫЕ ОБЪЯВЛЕНИЯ",
+ "АНАЛИТИКА",
+ "ПРЕДЫДУЩИЕ ОЦЕНКИ",
+];
+
+export const overlayTitles: string[] = [
+ "",
+ "ПРОДАЖИ В ДОМЕ",
+ "АНАЛОГИЧНЫЕ ОБЪЯВЛЕНИЯ",
+ "АНАЛИТИКА ДОМА",
+ "ПРЕДЫДУЩИЕ ОЦЕНКИ",
+];
+
+export const overlayNums: string[] = ["", "04", "05", "06", "07"];
+
+export const user: User = {
+ name: "Андрей Петров",
+ org: "Брусника",
+ email: "a.petrov@mera.ru",
+ initials: "АП",
+ reports: 128,
+};
+
+export const version = "v2.0.6";
diff --git a/frontend/src/components/trade-in/v2/tokens.ts b/frontend/src/components/trade-in/v2/tokens.ts
new file mode 100644
index 00000000..0345e857
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/tokens.ts
@@ -0,0 +1,115 @@
+// Design tokens for the /trade-in/v2 "МЕРА Оценка" HUD/blueprint port.
+// Faithful colour palette extracted from the pixel design (МЕРА Оценка.dc.html).
+// This file is the shared colour contract for every v2 component — import from
+// here instead of hardcoding hex literals (one-off SVG stroke/opacity may stay
+// inline). NOTE: this is an isolated design port, so it intentionally does NOT
+// reuse the global GenDesign analytics tokens (different visual language).
+
+// Page background behind the 1536×1024 artboard.
+export const pageBg = "#dde6ef";
+
+export const tokens = {
+ // Brand / accent
+ accent: "#2e8bff",
+ accentDeep: "#0d6fd6",
+ accentLite: "#46a8ff",
+ onAccent: "#fff", // opaque white text/glyph on accent-coloured surfaces (avatar initials)
+
+ // Ink / headings
+ ink: "#1c2c40",
+ ink2: "#17263a",
+
+ // Body text
+ body: "#39506a",
+ body2: "#46586c",
+
+ // Muted text (light → lighter)
+ muted: "#6f8195",
+ muted2: "#91a1b4",
+ muted3: "#9aaabb",
+ muted4: "#b3c0cf",
+ hint: "#8a9aac", // small helper / caption text
+
+ // Borders & hairlines (strong → soft)
+ line: "#c2d3e3",
+ line2: "#c8d7e6",
+ line3: "#b8c8d8", // photo/card edge, chart axes
+ lineSoft: "#d3dfeb",
+ lineSoft2: "#e2e9f1",
+ lineSoft3: "#eef3f8",
+ lineSoft4: "#eaf0f6", // pale-blue hairline — house-sales row separator (design line 462)
+ lineGradient: "#cbd7e3", // decorative gradient line fade stops (footer hairline)
+
+ // HUD corner brackets
+ bracket: "#9bbce0",
+ bracket2: "#8fb4dd",
+
+ // Semantic
+ success: "#1baa6b",
+ successBorder: "#b6e0cc", // soft green border for success badges (В РАСЧЁТЕ)
+ warn: "#b58a3e",
+ gold: "#c79a3e",
+ danger: "#cd6868",
+
+ // Info-soft panel (data-quality box)
+ infoSoftBg: "rgba(233,242,252,.5)", // translucent blue panel fill
+ infoSoftBorder: "#d3e0ec", // soft blue panel border
+ surfaceTint: "rgba(238,244,250,.5)", // translucent blue-tinted surface — KPI band + table footer (design lines 471, 487)
+
+ // Data-viz tints (mini histograms / range bars / scatter)
+ barLo: "#cfddec",
+ barMid: "#a9cef5",
+ track: "#dbe6f1", // range track + donut track
+ scatterDeal: "#9aafc4", // grey "сделки" dots (price×time scatter charts)
+ dealDot: "#8fa3b8", // grey "сделки" marker — map dot + legend (design 186, 203); darker than scatterDeal
+ dotStroke: "#fff", // white outline ring around map marker dots (design 185-187)
+
+ // Solid backgrounds
+ photoBg: "#e7eef6", // hero photo container fill behind building image
+ mapBg: "#e4ebf3", // map container fill behind blueprint SVG (design 156)
+ badgeTint: "#eaf1f8", // opaque pale-blue fill — source badge ("Яндекс") chip (design line 484)
+
+ // Disabled / no-data source card (02 RESULT · inactive source tile, design lines 366-384)
+ disabledBorder: "#d8e2ec", // inactive source card edge
+ disabledValue: "#c2cdd9", // greyed-out "—" count value
+ disabledLabel: "#aab7c5", // greyed-out "нет данных" caption
+
+ // Accent glow (range-marker dot halo, design lines 303/341)
+ accentGlow: "rgba(46,139,255,.5)",
+
+ // Translucent white surfaces (glass cards / overlays)
+ surface: {
+ w25: "rgba(255,255,255,.25)",
+ w40: "rgba(255,255,255,.4)",
+ w50: "rgba(255,255,255,.5)",
+ w55: "rgba(255,255,255,.55)",
+ w60: "rgba(255,255,255,.6)",
+ w62: "rgba(255,255,255,.62)",
+ w65: "rgba(255,255,255,.65)",
+ w70: "rgba(255,255,255,.7)",
+ w72: "rgba(255,255,255,.72)",
+ w80: "rgba(255,255,255,.8)",
+ w85: "rgba(255,255,255,.85)",
+ w98: "rgba(255,255,255,.98)",
+ w99: "rgba(255,255,255,.99)",
+ },
+
+ // Font CSS variables (provided by app/trade-in/v2/layout.tsx via next/font)
+ font: {
+ sans: "var(--font-manrope), sans-serif",
+ mono: "var(--font-plex-mono), monospace",
+ },
+
+ // Artboard background (radial glows + blueprint grid + base gradient).
+ // Ported verbatim from the design root div (lines 23-29).
+ gradientBg: [
+ "radial-gradient(620px 320px at 78% 16%, rgba(46,139,255,.07), transparent 60%)",
+ "radial-gradient(560px 360px at 24% 64%, rgba(46,139,255,.055), transparent 60%)",
+ "radial-gradient(480px 300px at 60% 92%, rgba(46,139,255,.04), transparent 60%)",
+ "repeating-linear-gradient(0deg, rgba(120,150,180,.045) 0 1px, transparent 1px 38px)",
+ "repeating-linear-gradient(90deg, rgba(120,150,180,.045) 0 1px, transparent 1px 38px)",
+ "linear-gradient(160deg,#f4f8fc 0%,#eaf1f8 55%,#e6eef7 100%)",
+ ].join(", "),
+
+ pageBg,
+};
diff --git a/frontend/src/components/trade-in/v2/types.ts b/frontend/src/components/trade-in/v2/types.ts
new file mode 100644
index 00000000..61f0e5fc
--- /dev/null
+++ b/frontend/src/components/trade-in/v2/types.ts
@@ -0,0 +1,284 @@
+// TypeScript shapes for the /trade-in/v2 design fixtures.
+// All values are sample/markup data (see fixtures.ts) — no API types here.
+
+export interface Report {
+ id: string;
+ date: string;
+ validUntil: string;
+}
+
+export interface ObjectInfo {
+ address: string;
+ city: string;
+ area: string;
+ rooms: string;
+ floor: string;
+ totalFloors: string;
+ year: string;
+ houseType: string;
+ repair: string;
+ balcony: boolean;
+ locationCoef: string;
+ streetView: string;
+ compass: string;
+}
+
+// ---- 02 RESULT ------------------------------------------------------------
+
+export interface ResultCard {
+ /** Header label, one entry per visual line. */
+ title: string[];
+ /** Big mono number, e.g. "11,29". */
+ value: string;
+ /** Unit suffix, e.g. "млн ₽". */
+ unit: string;
+ /** Low — high range line, e.g. "7,72 — 14,46 млн ₽". */
+ range: string;
+ /** ₽/м² sub-line (may include a "· N сделки" tail). */
+ ppm: string;
+ /** Mini-histogram column heights in % (cards 1 & 3). */
+ bars?: number[];
+ /** Donut delta value, e.g. "−11%" (card 2). */
+ delta?: string;
+ /** Donut caption, e.g. "К РЫНКУ" (card 2). */
+ deltaLabel?: string;
+ /** Nav index opened by the card's "Подробнее →" link. */
+ nav: number;
+}
+
+export interface ResultMeta {
+ sources: string;
+ confidence: string;
+ cv: string;
+}
+
+export interface RangeMarker {
+ /** Filled segment offsets / dot position, as CSS % strings. */
+ fillLeft: string;
+ fillRight: string;
+ dotLeft: string;
+}
+
+export interface RangeBar {
+ /** Header label, one entry per visual line. */
+ label: string[];
+ median: string;
+ lo: string;
+ hi: string;
+ marker: RangeMarker;
+}
+
+export interface Ranges {
+ ads: RangeBar;
+ deals: RangeBar;
+}
+
+export interface ScatterPoint {
+ x: number;
+ y: number;
+ r: number;
+}
+
+export interface AxisTickX {
+ label: string;
+ x: number;
+}
+
+export interface AxisTickY {
+ label: string;
+ y: number;
+}
+
+export interface ScatterMini {
+ deals: ScatterPoint[];
+ analogs: ScatterPoint[];
+ subject: ScatterPoint;
+ yTicks: AxisTickY[];
+ xTicks: AxisTickX[];
+}
+
+export interface SourceCard {
+ name: string;
+ /** Lot count, e.g. "7" or "—" when no data. */
+ count: string;
+ /** Count caption: "лотов" / "лот" / "нет данных". */
+ label: string;
+ active: boolean;
+}
+
+// ---- 03 SUMMARY -----------------------------------------------------------
+
+export interface SummaryRow {
+ label: string;
+ value: string;
+ dot: "accent" | "muted";
+ nav: number;
+}
+
+export interface SummaryQuality {
+ sources: string;
+ confidence: string;
+ cv: string;
+}
+
+export interface Summary {
+ rows: SummaryRow[];
+ quality: SummaryQuality;
+}
+
+// ---- OVERLAY: ПРОДАЖИ В ДОМЕ / ИСТОРИЯ ------------------------------------
+
+export interface DkpRow {
+ area: string;
+ price: string;
+ ppm: string;
+ ask: string;
+ delta: string;
+ /** Colour for the delta (success / danger token value). */
+ deltaColor: string;
+}
+
+export interface HouseSaleRow {
+ lot: string;
+ priceFrom: string;
+ priceTo: string;
+ date: string;
+ exposure: string;
+}
+
+export interface History {
+ houseSales: HouseSaleRow[];
+ dkpHeader: { title: string; period: string; note: string };
+ dkpKpi: { count: string; median: string; range: string };
+}
+
+// ---- OVERLAY: АНАЛОГИЧНЫЕ ОБЪЯВЛЕНИЯ --------------------------------------
+
+export interface AdRow {
+ addr: string;
+ meta: string;
+ ppm: string;
+ price: string;
+}
+
+export interface DealRow {
+ addr: string;
+ meta: string;
+ ppm: string;
+ price: string;
+}
+
+export interface MarketAds {
+ kpi: {
+ count: string;
+ median: string;
+ ppm: string;
+ range: string;
+ cv: string;
+ };
+ filters: string[];
+}
+
+export interface MarketDeals {
+ kpi: { count: string; median: string; delta: string; range: string };
+}
+
+// ---- OVERLAY: АНАЛИТИКА ДОМА ----------------------------------------------
+
+export interface AnalyticsKpi {
+ label: string;
+ value: string;
+ unit?: string;
+ /** Optional value colour override (token value). */
+ color?: string;
+ sub: string;
+}
+
+export interface SellTimeTier {
+ tier: string;
+ days: string;
+ range: string;
+ count: string;
+ variant: "success" | "accent" | "gold" | "danger";
+}
+
+export interface PriceHistory {
+ note: string;
+ years: string[];
+ /** SVG polyline points string (viewBox 0 0 900 220). */
+ avito: string;
+ yandex: string;
+}
+
+export interface ScatterDetail {
+ note: string;
+ deals: ScatterPoint[];
+ analogs: ScatterPoint[];
+ subject: ScatterPoint;
+ yTicks: AxisTickY[];
+ xTicks: AxisTickX[];
+}
+
+export interface Analytics {
+ header: { ads: string; radius: string };
+ kpis: AnalyticsKpi[];
+ sellTime: SellTimeTier[];
+ /** Sub-line above the sell-time tiers (carries the эталон ₽/м² number). */
+ sellTimeNote: string;
+ priceHistory: PriceHistory;
+ scatterDetail: ScatterDetail;
+}
+
+// ---- OVERLAY: ПРЕДЫДУЩИЕ ОЦЕНКИ (CACHE) -----------------------------------
+
+export interface CacheRow {
+ addr: string;
+ time: string;
+ src: string;
+ status: string;
+ /** Status dot/text colour (success / warn / danger token value). */
+ statusColor: string;
+}
+
+export interface CacheKpi {
+ label: string;
+ value: string;
+ unit?: string;
+ sub: string;
+}
+
+// ---- INPUTS / DROPDOWNS ---------------------------------------------------
+
+export interface DropdownOptions {
+ rooms: string[];
+ houseType: string[];
+ repair: string[];
+ radius: string[];
+ crm: string[];
+}
+
+// ---- КОЭФ. ЛОКАЦИИ DRAWER -------------------------------------------------
+
+export interface LocationFactor {
+ label: string;
+ delta: string;
+}
+
+export interface LocationFactors {
+ coef: string;
+ intro: string;
+ formula: { base: string; coef: string; result: string };
+ positives: LocationFactor[];
+ negatives: LocationFactor[];
+ footer: string;
+}
+
+// ---- USER / CHROME --------------------------------------------------------
+
+export interface User {
+ name: string;
+ org: string;
+ email: string;
+ initials: string;
+ reports: number;
+}