From 2e82b0810f6b1de3749dca4f492dc89ad7574f04 Mon Sep 17 00:00:00 2001
From: bot-backend
Date: Sat, 29 Aug 2026 20:28:10 +0500
Subject: [PATCH] =?UTF-8?q?fix(mera/b2c):=20=D1=81=D0=BD=D1=8F=D1=82=D1=8C?=
=?UTF-8?q?=20=D1=81=20=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D1=8B=20=D0=BE?=
=?UTF-8?q?=D0=B1=D0=B5=D1=89=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=80=D0=B5?=
=?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B8=20=D0=B8=20=D0=BF=D0=BE=D0=B4=D0=BF?=
=?UTF-8?q?=D0=B8=D1=81=D0=B0=D1=82=D1=8C=20=D0=B2=D0=B5=D0=BB=D0=B8=D1=87?=
=?UTF-8?q?=D0=B8=D0=BD=D1=8B=20=D0=B8=D0=B7=D0=BC=D0=B5=D1=80=D0=B5=D0=BD?=
=?UTF-8?q?=D0=BD=D1=8B=D0=BC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
«30 секунд» ушло из бейджа шага 1 как незамеренное, но осталось видимым
посетителю в четырёх других местах той же страницы: подпись под кнопкой формы,
CTA шапки, CTA блока возражений и липкая нижняя панель, которая висит на экране
всегда. Гейт витринных чисел этого не видел по построению — «сек» не было среди
единиц, а трёх файлов не было в списке сканируемых (обе дыры закрыты отдельно).
Замена — по смыслу: число полей формы (STEP1_FIELDS_LABEL) проверяется глазами,
и оба места берут его из landing-facts.ts, а не пишут у себя.
Медиана аналогов стояла под общей шапкой «пример по рынку Екатеринбурга», но
городом не ограничена: _ANALOGS_SQL идёт по всей таблице trade_in_estimates,
фильтр по city стоит только у экспозиции. Плюс Hero печатал у неё лишь размер
выборки и выбрасывал note «только расчёты, где аналоги нашлись» — отбор
выживших. Теперь у каждой величины своя подпись со своей границей и своя
оговорка рядом со значением; правило зафиксировано двумя проверками рендера
(обе краснеют на возврате к одной строке и на выброшенном note).
price_cut_share_pct считает долю ОБЪЯВЛЕНИЙ и только по Домклику — подпись
«столько продавцов снижают цену» приписывала величине единицу, которой её не
мерили. Один продавец ведёт несколько объявлений.
В ReportResult оставались три литерала «150 ₽» при живой SERVICE_PRICE_RUB:
цена обязана иметь один источник с офертой и политикой возврата.
Обещание «прогноз срока продажи» приведено к тому, что считается:
_estimate_days_on_market — медиана days_on_market аналогов, то есть сколько
похожие объявления УЖЕ висят. deals.days_on_market заполнен 0 раз из 108 623,
сверять прогноз срока не с чем (по этой же причине в ветке уже снято «±6 дн»).
---
.../__tests__/landing-v3-render.test.tsx | 41 ++++++++++++++-
.../mera-public/_components/InnerHeader.tsx | 2 +-
.../_components/estimate/ReportResult.tsx | 37 +++++++++----
.../_components/v3/CostOfErrorV3.tsx | 9 +++-
.../_components/v3/FreeCheckCard.tsx | 10 +++-
.../_components/v3/FreeResultV3.tsx | 11 ++--
.../mera-public/_components/v3/HeaderV3.tsx | 2 +-
.../app/mera-public/_components/v3/HeroV3.tsx | 52 ++++++++++++++-----
.../_components/v3/ObjectionsV3.tsx | 2 +-
.../mera-public/_components/v3/StepsV3.tsx | 6 +--
.../_components/v3/StickyCtaV3.tsx | 12 ++++-
.../mera-public/_components/v3/TwoPathsV3.tsx | 9 +++-
.../mera-public/articles/articles-content.ts | 4 +-
.../articles/kak-ocenit-kvartiru/page.tsx | 2 +-
.../src/app/mera-public/articles/page.tsx | 2 +-
.../src/app/mera-public/landing-facts.ts | 21 ++++++--
16 files changed, 173 insertions(+), 49 deletions(-)
diff --git a/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-v3-render.test.tsx b/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-v3-render.test.tsx
index 51c6559f..ed59741d 100644
--- a/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-v3-render.test.tsx
+++ b/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-v3-render.test.tsx
@@ -11,6 +11,7 @@ import { describe, expect, it } from "vitest";
import { AccuracyV3 } from "../_components/v3/AccuracyV3";
import { CostOfErrorV3 } from "../_components/v3/CostOfErrorV3";
+import { HeroV3 } from "../_components/v3/HeroV3";
import type { LandingStat, ShowcaseResponse } from "../public-api";
const stat = (value: number, sample_n: number | null, note: string): LandingStat => ({
@@ -28,6 +29,16 @@ const STATS = {
price_cut_median_pct_per_month: stat(-2.17, 3018, "Медианное изменение цены за 30 дней среди снижавших"),
};
+/** Hero печатает ещё и медиану аналогов — величину БЕЗ фильтра по городу. */
+const HERO_STATS = {
+ ...STATS,
+ analogs_median: stat(
+ 14,
+ 1123,
+ "Медиана числа аналогов на расчёт (только расчёты, где аналоги нашлись)",
+ ),
+};
+
const SHOWCASE: ShowcaseResponse = {
computed_at: "2026-08-29T03:00:00+00:00",
deals: [
@@ -88,7 +99,35 @@ describe("витрина лэндинга v3 без данных", () => {
it("«Цена ошибки»: без /stats остаётся только цена — она из оферты, а не из замера", () => {
render();
- expect(screen.queryByText(/столько продавцов снижают цену/)).toBeNull();
+ expect(screen.queryByText(/столько объявлений на Домклике снижали цену/)).toBeNull();
expect(screen.getByText("150 ₽")).toBeTruthy();
});
+
+ /**
+ * Величина не должна стоять под подписью, к которой не привязана. Экспозиция
+ * считается с фильтром по городу (`_LISTING_AGE_SQL`), медиана аналогов — по
+ * всей таблице `trade_in_estimates`; под общей шапкой «пример по рынку
+ * Екатеринбурга» вторая была бы подписана чужой границей. Проверяем не
+ * наличие двух строк, а СОДЕРЖИМОЕ каждой: слить их обратно в одну и
+ * оставить тест зелёным невозможно.
+ */
+ it("HeroV3: аналоги подписаны своей границей и своей оговоркой, а не Екатеринбургом", () => {
+ render();
+ const ekbLine = screen.getByText(/Пример по рынку Екатеринбурга/).parentElement;
+ expect(ekbLine?.textContent).toMatch(/активное объявление висит\s+26\s+дн\./u);
+ expect(ekbLine?.textContent, "экспозиция и аналоги снова в одной строке").not.toMatch(/аналог/);
+
+ const allLine = screen.getByText(/По всем расчётам МЕРЫ/).parentElement;
+ expect(allLine?.textContent).toMatch(/медиана аналогов на расчёт/);
+ // Отбор выживших: расчёты без аналогов в знаменатель не попали.
+ expect(allLine?.textContent, "note бэкенда выброшен").toMatch(
+ /только расчёты, где аналоги нашлись/,
+ );
+ });
+
+ it("HeroV3: без /stats обе строки примера исчезают, а не показывают подпись без числа", () => {
+ render();
+ expect(screen.queryByText(/Пример по рынку Екатеринбурга/)).toBeNull();
+ expect(screen.queryByText(/По всем расчётам МЕРЫ/)).toBeNull();
+ });
});
diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/InnerHeader.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/InnerHeader.tsx
index 398f140a..be973f8f 100644
--- a/tradein-mvp/frontend/src/app/mera-public/_components/InnerHeader.tsx
+++ b/tradein-mvp/frontend/src/app/mera-public/_components/InnerHeader.tsx
@@ -8,7 +8,7 @@ import { PublicLink } from "./PublicLink";
* (`v3/HeaderV3.tsx`).
*
* Из неё убраны навигация по секциям лэндинга, селектор города и CTA
- * «Проверить за 30 секунд». Причина у всех трёх одна: человек уже ушёл с
+ * «Проверить квартиру». Причина у всех трёх одна: человек уже ушёл с
* лэндинга. Якоря вроде `#accuracy` тут просто не на что навести (секций нет
* на этих страницах), город выбирается прямо в форме проверки — а второй
* селектор города в шапке означал бы два места, задающих одно значение.
diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/estimate/ReportResult.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/estimate/ReportResult.tsx
index 13dbf946..8e9d73c4 100644
--- a/tradein-mvp/frontend/src/app/mera-public/_components/estimate/ReportResult.tsx
+++ b/tradein-mvp/frontend/src/app/mera-public/_components/estimate/ReportResult.tsx
@@ -23,11 +23,24 @@
* «возраст активного объявления», а НЕ макетное «срок продажи по дому» —
* выборка цензурирована, честное имя величины важнее красоты.
*
- * Возврат 150 ₽ при ненадёжном расчёте — политика с /refund; в тизере она
+ * Возврат при ненадёжном расчёте — политика с /refund; в тизере она
* упоминается той же строкой, что в макете, со ссылкой на полный текст.
+ *
+ * ЦЕНА — ТОЛЬКО `SERVICE_PRICE_RUB`. Здесь оставались три литерала «150 ₽»
+ * (плашка тизера, текст и строка возврата). Цена стоит в оферте и в политике
+ * возврата; вписанная в экран копия разъехалась бы с ними молча, и человек
+ * увидел бы одну цифру на витрине, а другую в документе, который его
+ * обязывает.
+ *
+ * СРОК. Строка тизера обещала «прогноз срока продажи»: платный расчёт отдаёт
+ * `est_days_on_market` — медиану `days_on_market` аналогов, то есть сколько
+ * похожие объявления УЖЕ висят. `deals.days_on_market` заполнен 0 раз из
+ * 108 623, поэтому сверить прогноз срока не с чем и прогнозом эта величина
+ * не является. Названа тем, что считается, — ровно как в пункте 5 выше про
+ * бесплатные плитки.
*/
-import { SUPPORT_TELEGRAM_LABEL, SUPPORT_TELEGRAM_URL } from "../../content";
+import { SERVICE_PRICE_RUB, SUPPORT_TELEGRAM_LABEL, SUPPORT_TELEGRAM_URL } from "../../content";
import type { CoverageVerdict } from "../../coverage-copy";
import styles from "../../landing-v3.module.css";
@@ -129,7 +142,7 @@ function FreeCards({ verdict }: { verdict: CoverageVerdict }) {
const PAID_ROWS = [
{ label: "РЕКОМЕНДОВАННАЯ ЦЕНА", w: "60%" },
- { label: "ПРОГНОЗ СРОКА ПРОДАЖИ ПРИ ЭТОЙ ЦЕНЕ", w: "45%" },
+ { label: "СКОЛЬКО УЖЕ ВИСЯТ ПОХОЖИЕ ОБЪЯВЛЕНИЯ", w: "45%" },
{ label: "ДИАПАЗОН И ПОЛОЖЕНИЕ НА РЫНКЕ", w: "70%" },
{ label: "АНАЛОГИ С АДРЕСАМИ И ЦЕНАМИ", w: "88%" },
] as const;
@@ -139,7 +152,9 @@ function PaidTeaser() {
return (
- ПОЛНЫЙ ОТЧЁТ · 150 ₽
+
+ ПОЛНЫЙ ОТЧЁТ · {SERVICE_PRICE_RUB} ₽
+
@@ -159,17 +174,17 @@ function PaidTeaser() {
- Цена и срок продажи — следующий шаг
+ Ваша цена — следующий шаг
- Полный расчёт стоит 150 ₽: диапазон цены, цена за квадратный
- метр и список квартир, на которых он построен. Один платёж, без
- подписки. Приём оплаты сейчас подключается — как только заработает,
- кнопка появится здесь.
+ Полный расчёт стоит {SERVICE_PRICE_RUB} ₽: диапазон цены, цена
+ за квадратный метр и список квартир, на которых он построен. Один
+ платёж, без подписки. Приём оплаты сейчас подключается — как только
+ заработает, кнопка появится здесь.
- Если надёжный расчёт не получится — вернём 150 ₽:{" "}
- условия возврата.
+ Если надёжный расчёт не получится — вернём {SERVICE_PRICE_RUB}
+ ₽: условия возврата.
diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/CostOfErrorV3.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/CostOfErrorV3.tsx
index 1421ad1d..c90201f6 100644
--- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/CostOfErrorV3.tsx
+++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/CostOfErrorV3.tsx
@@ -9,6 +9,13 @@
* снижал»: одна подпись на две величины была бы подменой знаменателя.
* Цена — `SERVICE_PRICE_RUB` (content.ts), тот же источник, что у оферты.
*
+ * ОБЪЯВЛЕНИЯ, А НЕ ПРОДАВЦЫ. `price_cut_share_pct` — доля ОБЪЯВЛЕНИЙ, и
+ * только по Домклику (единственный источник, где триггер пишет стартовую
+ * цену, см. шапку `landing_stats.py`). Один продавец ведёт несколько
+ * объявлений, поэтому «столько продавцов снижают цену» приписывало величине
+ * единицу, которой её не мерили: подпись приведена к измеренному, а источник
+ * назван прямо в ней, а не только в `note` под плиткой.
+ *
* ЧЕГО ЗДЕСЬ БОЛЬШЕ НЕТ. Плитки «×2,4 дольше продаётся квартира с завышенной
* ценой»: замер даёт ×1,04 (79 дней против 76), и это уже с цензурой в пользу
* заявления. Её место заняла доля снижающих цену — измеренная величина про то
@@ -51,7 +58,7 @@ export function CostOfErrorV3({ stats }: { stats: LandingStats }) {
tiles.push({
key: "share",
value: share.text,
- title: "столько продавцов снижают цену",
+ title: "столько объявлений на Домклике снижали цену",
text: [share.note, share.sample].filter(Boolean).join(" · "),
});
}
diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/FreeCheckCard.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/FreeCheckCard.tsx
index c97ad1ac..bd045ed1 100644
--- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/FreeCheckCard.tsx
+++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/FreeCheckCard.tsx
@@ -4,7 +4,7 @@
* FreeCheckCard — карточка «Бесплатная проверка» на первом экране (макет v3,
* `МЕРА для себя v3.dc.html` строки ~129-170).
*
- * ЧТО ОНА ДЕЛАЕТ СЕГОДНЯ. Проверяет пять полей и уводит на `/estimate`, где
+ * ЧТО ОНА ДЕЛАЕТ СЕГОДНЯ. Проверяет шесть полей и уводит на `/estimate`, где
* живёт сама проверка. Макетный сценарий «спиннер 1100 мс → готовый
* результат прямо в карточке» не воспроизводится и не будет: результату нужно
* место рядом с полями, а автокомплиту — место под список; в карточке первого
@@ -39,6 +39,11 @@
* не использует (см. `CoverageProbeInput` на бэке), но выбросить уже
* полученный ответ и спросить его второй раз на платном шаге — хуже, чем
* донести его в `sessionStorage` до того шага.
+ *
+ * ПОДПИСЬ ПОД КНОПКОЙ. В ней стояло «30 СЕКУНД» — замера времени заполнения
+ * формы нет и не было. Его место занял `STEP1_FIELDS_LABEL`: число полей —
+ * проверяемый факт, и оно приезжает из `landing-facts.ts`, а не вписано сюда
+ * второй раз (иначе с бейджем шага 1 они разъехались бы молча).
*/
import { useId, useRef, useState } from "react";
@@ -54,6 +59,7 @@ import {
SECONDARY_CITIES,
} from "../../content";
import { saveDraft } from "../../estimate-draft";
+import { STEP1_FIELDS_LABEL } from "../../landing-facts";
import styles from "../../landing-v3.module.css";
/** Значение
- Ваша цена и срок продажи готовы
+ Ваша цена готова
diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/HeroV3.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/HeroV3.tsx
index 3e5ad3a7..42435255 100644
--- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/HeroV3.tsx
+++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/HeroV3.tsx
@@ -14,6 +14,20 @@
* конкретного человека. Цена — `SERVICE_PRICE_RUB` (content.ts), один
* источник с офертой и политикой возврата.
*
+ * ДВЕ СТРОКИ ПРИМЕРА, А НЕ ОДНА. Екатеринбургом ограничена только экспозиция
+ * (`_LISTING_AGE_SQL` фильтрует `listings.city`); медиана аналогов считается
+ * по всей таблице `trade_in_estimates` без фильтра по городу. Стоя под общей
+ * шапкой «пример по рынку Екатеринбурга», она была бы подписана городом, к
+ * которому не привязана, — поэтому у каждой величины своя подпись со своей
+ * границей. Рядом с аналогами печатается их `note` бэкенда («только расчёты,
+ * где аналоги нашлись»): это отбор выживших, расчёты без аналогов в
+ * знаменатель не попали, и без оговорки медиана читается выше, чем есть.
+ *
+ * СРОК. В тексте нет «узнаете срок продажи»: платный отчёт отдаёт
+ * `est_days_on_market` — медиану `days_on_market` аналогов, то есть сколько
+ * похожие объявления УЖЕ висят (`deals.days_on_market` пуст, сверять прогноз
+ * срока не с чем). Обещание приведено к этой величине.
+ *
* ЧЕГО В ТЕКСТЕ БОЛЬШЕ НЕТ: обещания «каждый такой прогноз мы потом сверяем с
* фактом сделки». Контура сверки прогноза клиента с его сделкой в продукте не
* существует (`trade_in_leads` — 4 строки без полей исхода), и обещать его
@@ -53,12 +67,22 @@ export function HeroV3({ stats }: { stats: LandingStats }) {
digits: 0,
sampleWord: "объявлениям",
});
- const marketExample = [
- analogs && `медиана аналогов на расчёт — ${analogs.text}${analogs.sample ? ` (${analogs.sample})` : ""}`,
- age && `активное объявление висит ${age.text}${age.sample ? ` (медиана ${age.sample})` : ""}`,
- ]
- .filter((line): line is string => Boolean(line))
- .join("; ");
+ const marketLines = [
+ age && {
+ key: "age",
+ label: "Пример по рынку Екатеринбурга, а не расчёт по вашему адресу:",
+ text: [`активное объявление висит ${age.text}`, age.sample && `медиана ${age.sample}`]
+ .filter(Boolean)
+ .join(" · "),
+ },
+ analogs && {
+ key: "analogs",
+ label: "По всем расчётам МЕРЫ, а не только по Екатеринбургу:",
+ text: [`медиана аналогов на расчёт — ${analogs.text}`, analogs.sample, analogs.note]
+ .filter(Boolean)
+ .join(" · "),
+ },
+ ].filter((line): line is { key: string; label: string; text: string } => Boolean(line));
return (
- Не зависнуть: узнаете, за сколько дней уйдёт
- квартира по вашей цене
+ Не зависнуть: увидите, сколько времени уже
+ висят похожие квартиры рядом
@@ -133,20 +157,20 @@ export function HeroV3({ stats }: { stats: LandingStats }) {
За{" "}
{`${SERVICE_PRICE_RUB} ₽`}:
{" "}
- ваша цена, срок продажи и аналоги, на которых построен расчёт
+ ваша цена, экспозиция похожих объявлений и аналоги, на которых
+ построен расчёт
- {marketExample !== "" && (
-
+ {marketLines.map((line) => (
+
≈
- Пример по рынку Екатеринбурга, а не расчёт по вашему адресу:{" "}
- {marketExample}
+ {line.label} {line.text}
- )}
+ ))}
{/* Покрытие подано неравномерно намеренно — см. COVERED_CITIES в
diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/ObjectionsV3.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/ObjectionsV3.tsx
index 9b1d736b..e5cc5b1e 100644
--- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/ObjectionsV3.tsx
+++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/ObjectionsV3.tsx
@@ -135,7 +135,7 @@ export function ObjectionsV3() {
Что обычно спрашивают перед оплатой
- Проверить за 30 секунд
+ Проверить квартиру
diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/StepsV3.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/StepsV3.tsx
index 9bd6afa0..a5cd6e92 100644
--- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/StepsV3.tsx
+++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/StepsV3.tsx
@@ -30,11 +30,11 @@ const STEPS_V3: readonly StepV3[] = [
},
{
title: "Мы собираем рынок вокруг",
- text: "Похожие квартиры рядом: сколько их продаётся сейчас и за сколько дней они уходят.",
+ text: "Похожие квартиры рядом: сколько их продаётся сейчас и как давно они висят.",
},
{
- title: "Вы получаете цену и срок",
- text: "Конкретная цифра для объявления, прогноз срока продажи и все аналоги, на которых построен расчёт.",
+ title: "Вы получаете цену и картину рынка",
+ text: "Конкретная цифра для объявления, сколько уже висят похожие объявления и все аналоги, на которых построен расчёт.",
accent: true,
},
] as const;
diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/StickyCtaV3.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/StickyCtaV3.tsx
index eb957cf4..0b95e7c4 100644
--- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/StickyCtaV3.tsx
+++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/StickyCtaV3.tsx
@@ -14,6 +14,14 @@
* компоненте — так `StickyCtaV3` остаётся самодостаточным независимо от
* того, куда именно в дерево страницы его вставят (ожидаемое место — сразу
* после подвала, как Header/Footer в текущем `layout.tsx`).
+ *
+ * НА КНОПКЕ НЕТ СРОКА. Здесь стояло «Проверить за 30 секунд» — замера времени
+ * заполнения формы нет и не было (та же причина, по которой «30 СЕКУНД» ушло
+ * из бейджа первого шага, см. `landing-facts.ts`). Панель висит на экране
+ * ВСЕГДА, поэтому именно с неё обещание, которого никто не мерил, читалось
+ * чаще всего. В тексте панели по той же причине нет и «срока продажи»:
+ * платный отчёт отдаёт медиану экспозиции похожих объявлений, а не прогноз
+ * срока (`_estimate_days_on_market`, см. HeroV3).
*/
import styles from "../../landing-v3.module.css";
@@ -26,10 +34,10 @@ export function StickyCtaV3() {
- Узнайте цену и срок продажи своей квартиры
+ Узнайте цену своей квартиры и что происходит с похожими рядом
- Проверить за 30 секунд
+ Проверить квартиру
>
diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/TwoPathsV3.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/TwoPathsV3.tsx
index 542ed9bc..2642138b 100644
--- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/TwoPathsV3.tsx
+++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/TwoPathsV3.tsx
@@ -13,6 +13,12 @@
* Цена — `SERVICE_PRICE_RUB` (content.ts): один источник с офертой и политикой
* возврата. Раньше она приезжала сюда из плейсхолдерной плитки «цена ошибки»,
* и разъехаться с офертой могла молча.
+ *
+ * СОСТАВ ОТЧЁТА НАЗВАН ПО ФАКТУ. В перечне пути 1 стоял «срок»: платный отчёт
+ * действительно отдаёт `est_days_on_market`, но это медиана `days_on_market`
+ * аналогов — сколько похожие объявления УЖЕ висят, а не срок до сделки
+ * (`deals.days_on_market` заполнен 0 раз из 108 623, сверять прогноз срока не
+ * с чем). Слово заменено на измеренную величину.
*/
import { SERVICE_PRICE_RUB, SUPPORT_TELEGRAM_URL } from "../../content";
@@ -38,7 +44,8 @@ export function TwoPathsV3() {
ПУТЬ 1 · САМОСТОЯТЕЛЬНО
{`Отчёт за ${SERVICE_PRICE_RUB} ₽`}
- Цена, срок, аналоги, факторы. Дальше вы ведёте продажу сами — на
+ Цена, экспозиция похожих объявлений, аналоги, факторы. Дальше вы
+ ведёте продажу сами — на
руках все цифры, чтобы уверенно держать свою цену в переговорах.
diff --git a/tradein-mvp/frontend/src/app/mera-public/articles/articles-content.ts b/tradein-mvp/frontend/src/app/mera-public/articles/articles-content.ts
index e4941909..7f0bfba8 100644
--- a/tradein-mvp/frontend/src/app/mera-public/articles/articles-content.ts
+++ b/tradein-mvp/frontend/src/app/mera-public/articles/articles-content.ts
@@ -149,8 +149,8 @@ export const HOWTO = {
},
cta: {
eyebrow: "ЕСЛИ СЧИТАТЬ ВРУЧНУЮ НЕКОГДА",
- title: "МЕРА делает всё это за 30 секунд",
- body: `Мы находим аналоги, считаем медиану, применяем поправки и проверяем цену сроком продажи. Бесплатно покажем рынок вокруг, за ${SERVICE_PRICE_RUB} ₽ — вашу цену и прогноз.`,
+ title: "МЕРА делает всё это за вас",
+ body: `Мы находим аналоги, считаем медиану, применяем поправки и смотрим, сколько похожие объявления уже висят. Бесплатно покажем рынок вокруг, за ${SERVICE_PRICE_RUB} ₽ — вашу цену и аналоги, на которых она построена.`,
},
s5: {
id: "s5",
diff --git a/tradein-mvp/frontend/src/app/mera-public/articles/kak-ocenit-kvartiru/page.tsx b/tradein-mvp/frontend/src/app/mera-public/articles/kak-ocenit-kvartiru/page.tsx
index 055587d0..1e468270 100644
--- a/tradein-mvp/frontend/src/app/mera-public/articles/kak-ocenit-kvartiru/page.tsx
+++ b/tradein-mvp/frontend/src/app/mera-public/articles/kak-ocenit-kvartiru/page.tsx
@@ -124,7 +124,7 @@ export default function HowToArticlePage() {
{HOWTO.cta.title}
{HOWTO.cta.body}
- Проверить за 30 секунд
+ Проверить квартиру
diff --git a/tradein-mvp/frontend/src/app/mera-public/articles/page.tsx b/tradein-mvp/frontend/src/app/mera-public/articles/page.tsx
index 646bdcc1..cee546f8 100644
--- a/tradein-mvp/frontend/src/app/mera-public/articles/page.tsx
+++ b/tradein-mvp/frontend/src/app/mera-public/articles/page.tsx
@@ -88,7 +88,7 @@ export default function ArticlesPage() {
- Проверить за 30 секунд
+ Проверить квартиру
diff --git a/tradein-mvp/frontend/src/app/mera-public/landing-facts.ts b/tradein-mvp/frontend/src/app/mera-public/landing-facts.ts
index b718b2c1..a73b1bb2 100644
--- a/tradein-mvp/frontend/src/app/mera-public/landing-facts.ts
+++ b/tradein-mvp/frontend/src/app/mera-public/landing-facts.ts
@@ -87,8 +87,21 @@ export const BACKTEST_CORRIDOR_LABEL = "±37 %";
export const BACKTEST_PERIOD_LABEL = "сделки с июня 2025 года";
/**
- * Бейдж первого шага. Раньше здесь стояло «30 секунд» — замера времени
- * заполнения формы нет и не было. Число полей формы, наоборот, проверяется
- * глазами в `FreeCheckCard.tsx`: адрес, площадь, комнаты, этаж, состояние.
+ * Бейдж первого шага И подпись под кнопкой формы (`FreeCheckCard.tsx`).
+ * Раньше в обоих местах стояло «30 секунд» — замера времени заполнения формы
+ * нет и не было. Число полей формы, наоборот, проверяется глазами там же:
+ * город, адрес, площадь, комнаты, этаж, состояние.
+ *
+ * ШЕСТЬ, А НЕ ПЯТЬ. Пока эта подпись делалась, соседняя ветка вернула в форму
+ * ВЫБОР ГОРОДА (#2576: без него «Ленина, 1» давало жителю Нижнего Тагила цену
+ * по одноимённой улице в ЕКБ). Число полей — не константа продукта, а счёт
+ * того, что стоит в разметке рядом; после слияния двух веток «5 полей» было бы
+ * ровно тем враньём на витрине, ради снятия которого подпись и заводилась.
+ * Меняется форма — пересчитать здесь; проверяется по `heroFieldLabel` в
+ * `FreeCheckCard.tsx`.
+ *
+ * Оба места берут строку ОТСЮДА, а не пишут её у себя: «30 секунд» пережило
+ * снятие из бейджа ровно потому, что было вписано в четыре компонента
+ * независимо, и правка одного не касалась остальных.
*/
-export const STEP1_FIELDS_LABEL = "5 полей";
+export const STEP1_FIELDS_LABEL = "6 полей";