Merge pull request 'feat(mera/b2c): лэндинг v3 доведён до макета 29.08 — лента, игра, два пути, возражения, статьи' (#3215) from feat/b2c-landing-v3-full into main
Some checks are pending
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / build-frontend (push) Blocked by required conditions
Deploy Trade-In / build-browser (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 13s
Deploy Trade-In / test (push) Has been skipped
Some checks are pending
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / build-frontend (push) Blocked by required conditions
Deploy Trade-In / build-browser (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 13s
Deploy Trade-In / test (push) Has been skipped
This commit is contained in:
commit
2f91b4b34f
9 changed files with 1279 additions and 10 deletions
|
|
@ -41,6 +41,9 @@ const V3_SOURCES = [
|
||||||
"_components/v3/AccuracyV3.tsx",
|
"_components/v3/AccuracyV3.tsx",
|
||||||
"_components/v3/FreeCheckCard.tsx",
|
"_components/v3/FreeCheckCard.tsx",
|
||||||
"_components/v3/FreeResultV3.tsx",
|
"_components/v3/FreeResultV3.tsx",
|
||||||
|
// Макет 29.08: лента и игра тоже стоят на реквизитных величинах.
|
||||||
|
"_components/v3/DealsTickerV3.tsx",
|
||||||
|
"_components/v3/GuessGameV3.tsx",
|
||||||
];
|
];
|
||||||
|
|
||||||
const PLACEHOLDER_EXPORTS = [
|
const PLACEHOLDER_EXPORTS = [
|
||||||
|
|
@ -48,6 +51,7 @@ const PLACEHOLDER_EXPORTS = [
|
||||||
"COST_OF_ERROR_PLACEHOLDER",
|
"COST_OF_ERROR_PLACEHOLDER",
|
||||||
"FREE_SAMPLE_PLACEHOLDER",
|
"FREE_SAMPLE_PLACEHOLDER",
|
||||||
"PROOF_ROWS_PLACEHOLDER",
|
"PROOF_ROWS_PLACEHOLDER",
|
||||||
|
"GAME_ROUNDS_PLACEHOLDER",
|
||||||
];
|
];
|
||||||
|
|
||||||
function placeholdersInUse(): string[] {
|
function placeholdersInUse(): string[] {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
/**
|
||||||
|
* ArticlesV3 — «Разборы на данных» (макет 29.08, секция «V3 — статьи»).
|
||||||
|
*
|
||||||
|
* Раздела статей в продукте НЕ СУЩЕСТВУЕТ (страницы «МЕРА статьи»/«МЕРА
|
||||||
|
* статья» из макета — следующий шард). Поэтому карточки рендерятся <article>,
|
||||||
|
* а НЕ <a>: ссылка «в никуда» на превью читалась бы как сломанная навигация.
|
||||||
|
* Ссылка «Все статьи →» по той же причине не рендерится вовсе — появится
|
||||||
|
* вместе с разделом. Темы и рубрики карточек — из макета дословно.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styles from "../../landing-v3.module.css";
|
||||||
|
|
||||||
|
interface ArticleCard {
|
||||||
|
readonly rubric: string;
|
||||||
|
readonly title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ARTICLES: readonly ArticleCard[] = [
|
||||||
|
{
|
||||||
|
rubric: "ЦЕНООБРАЗОВАНИЕ · 6 МИН",
|
||||||
|
title: "Как оценить квартиру для продажи — без риелтора и «на глазок»",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rubric: "СРОКИ · 5 МИН",
|
||||||
|
title: "Сколько продаётся квартира в 2026 году и что «подвешивает» объявление",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rubric: "ТОРГ · 7 МИН",
|
||||||
|
title: "Завысить и торговаться или поставить в рынок: что выгоднее",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function ArticlesV3() {
|
||||||
|
return (
|
||||||
|
<section className={styles.artSection} aria-labelledby="articles-v3-title">
|
||||||
|
<div className={styles.artHead}>
|
||||||
|
<h2 id="articles-v3-title" className={styles.artTitle}>
|
||||||
|
Разборы на данных
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className={styles.artGrid}>
|
||||||
|
{ARTICLES.map((a) => (
|
||||||
|
<article key={a.title} className={styles.artCard}>
|
||||||
|
<div className={styles.artRubric}>{a.rubric}</div>
|
||||||
|
<div className={styles.artCardTitle}>{a.title}</div>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* DealsTickerV3 — бегущая строка «ПРОГНОЗ → ФАКТ» (макет 29.08, секция
|
||||||
|
* «V3 — лента сделок»): тёмная полоса сразу под шапкой.
|
||||||
|
*
|
||||||
|
* Данные — ТЕ ЖЕ строки, что в таблице сверок (`PROOF_ROWS_PLACEHOLDER`):
|
||||||
|
* лента и таблица иллюстрируют один и тот же будущий контур «прогноз → факт»,
|
||||||
|
* которого в продукте пока нет — заводить второй набор выдуманных сделок
|
||||||
|
* значило бы удвоить сущности под одним и тем же гейтом (#2904). Список
|
||||||
|
* дублируется дважды подряд — стандартный приём бесшовного CSS-marquee
|
||||||
|
* (анимация уводит ровно на −50%).
|
||||||
|
*
|
||||||
|
* `aria-hidden` на второй копии и `role="marquee"`-семантика намеренно НЕ
|
||||||
|
* используются: полоса декоративна, весь её смысл продублирован таблицей
|
||||||
|
* сверок ниже по странице — скринридеру достаточно одного источника.
|
||||||
|
* `prefers-reduced-motion` глушится глобальным правилом из css-модуля.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PROOF_ROWS_PLACEHOLDER } from "../../marketing-v3";
|
||||||
|
import styles from "../../landing-v3.module.css";
|
||||||
|
|
||||||
|
export function DealsTickerV3() {
|
||||||
|
const items = [...PROOF_ROWS_PLACEHOLDER, ...PROOF_ROWS_PLACEHOLDER];
|
||||||
|
return (
|
||||||
|
<div className={styles.tickerRoot}>
|
||||||
|
<div className={styles.tickerBadge}>
|
||||||
|
<span className={styles.tickerDot} aria-hidden="true" />
|
||||||
|
<span className={styles.tickerBadgeText}>ПРОГНОЗ → ФАКТ</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.tickerTrack} aria-hidden="true">
|
||||||
|
{items.map((r, i) => (
|
||||||
|
<div key={`${r.addr}-${i}`} className={styles.tickerItem}>
|
||||||
|
<span className={styles.tickerObj}>{r.addr}</span>
|
||||||
|
<span className={styles.tickerPair}>
|
||||||
|
<span className={styles.tickerLabelMuted}>ПРОГНОЗ</span>
|
||||||
|
<span className={styles.tickerSaid}>{r.said}</span>
|
||||||
|
</span>
|
||||||
|
<span className={styles.tickerArrow}>→</span>
|
||||||
|
<span className={styles.tickerPair}>
|
||||||
|
<span className={styles.tickerLabelAccent}>ФАКТ</span>
|
||||||
|
<span className={styles.tickerFact}>{r.fact}</span>
|
||||||
|
</span>
|
||||||
|
<span className={styles.tickerErr}>{r.err}</span>
|
||||||
|
<span className={styles.tickerGain}>
|
||||||
|
<span className={styles.tickerGainCaret}>▲</span>
|
||||||
|
<span>{r.gain}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,214 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GuessGameV3 — мини-игра «Сыграйте против МЕРЫ на реальных сделках»
|
||||||
|
* (макет 29.08, секция `#game` «V3 — игра "Угадай цену"»).
|
||||||
|
*
|
||||||
|
* Клиентский компонент: три раунда «слайдер → ответ → сверка», итоговый экран
|
||||||
|
* со средней ошибкой игрока против ошибки МЕРЫ. Раунды — реквизит из
|
||||||
|
* `GAME_ROUNDS_PLACEHOLDER` (см. шапку marketing-v3.ts: контура сверки
|
||||||
|
* «прогноз → факт» в продукте нет, «реальные сделки» здесь — макетные, и
|
||||||
|
* страница закрыта noindex до появления вычисляемых).
|
||||||
|
*
|
||||||
|
* Ошибка игрока считается медианой |ответ − факт| / факт по трём раундам —
|
||||||
|
* та же метрика, которой описана ошибка МЕРЫ (GAME_MERA_ERROR_LABEL).
|
||||||
|
* Финальная CTA ведёт на `/estimate` (решение StickyCtaV3 вместо макетной
|
||||||
|
* модалки).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
GAME_MERA_ERROR_LABEL,
|
||||||
|
GAME_ROUNDS_PLACEHOLDER,
|
||||||
|
} from "../../marketing-v3";
|
||||||
|
import styles from "../../landing-v3.module.css";
|
||||||
|
|
||||||
|
import { PublicLink } from "../PublicLink";
|
||||||
|
|
||||||
|
type Phase = "playing" | "revealed" | "finished";
|
||||||
|
|
||||||
|
const fmtMln = (v: number) =>
|
||||||
|
`${v.toFixed(2).replace(".", ",")} млн ₽`;
|
||||||
|
|
||||||
|
function median(xs: number[]): number {
|
||||||
|
const s = [...xs].sort((a, b) => a - b);
|
||||||
|
const mid = Math.floor(s.length / 2);
|
||||||
|
return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GuessGameV3() {
|
||||||
|
const rounds = GAME_ROUNDS_PLACEHOLDER;
|
||||||
|
const [idx, setIdx] = useState(0);
|
||||||
|
const [phase, setPhase] = useState<Phase>("playing");
|
||||||
|
const [guess, setGuess] = useState(rounds[0].start);
|
||||||
|
const [errors, setErrors] = useState<number[]>([]);
|
||||||
|
|
||||||
|
const r = rounds[idx];
|
||||||
|
const isLast = idx === rounds.length - 1;
|
||||||
|
|
||||||
|
const answer = () => {
|
||||||
|
setErrors((prev) => [...prev, Math.abs(guess - r.fact) / r.fact]);
|
||||||
|
setPhase("revealed");
|
||||||
|
};
|
||||||
|
const next = () => {
|
||||||
|
if (isLast) {
|
||||||
|
setPhase("finished");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const n = idx + 1;
|
||||||
|
setIdx(n);
|
||||||
|
setGuess(rounds[n].start);
|
||||||
|
setPhase("playing");
|
||||||
|
};
|
||||||
|
const restart = () => {
|
||||||
|
setIdx(0);
|
||||||
|
setGuess(rounds[0].start);
|
||||||
|
setErrors([]);
|
||||||
|
setPhase("playing");
|
||||||
|
};
|
||||||
|
|
||||||
|
const userErrPct = errors.length
|
||||||
|
? `${(median(errors) * 100).toFixed(1).replace(".", ",")}%`
|
||||||
|
: "—";
|
||||||
|
const roundErrPct = errors.length
|
||||||
|
? `${(errors[errors.length - 1] * 100).toFixed(1).replace(".", ",")}%`
|
||||||
|
: "—";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="game" className={styles.gameSection} aria-labelledby="game-v3-title">
|
||||||
|
<div className={styles.gameHead}>
|
||||||
|
<div className={styles.accEyebrow}>
|
||||||
|
ПРОВЕРЬТЕ СЕБЯ · РАУНД {Math.min(idx + 1, rounds.length)} ИЗ {rounds.length}
|
||||||
|
</div>
|
||||||
|
<h2 id="game-v3-title" className={styles.accTitle}>
|
||||||
|
Сыграйте против МЕРЫ на реальных сделках
|
||||||
|
</h2>
|
||||||
|
<p className={styles.gameLead}>
|
||||||
|
Три квартиры, которые уже проданы. Назовите свою цифру — потом
|
||||||
|
увидите, что сказала МЕРА и чем всё закончилось.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.gameCard}>
|
||||||
|
<div className={styles.gameGrid}>
|
||||||
|
<div className={styles.gameMap}>
|
||||||
|
<div className={styles.gameMapGrid} aria-hidden="true" />
|
||||||
|
<div className={styles.gameMapRoadH} aria-hidden="true" />
|
||||||
|
<div className={styles.gameMapRoadV} aria-hidden="true" />
|
||||||
|
<div
|
||||||
|
className={styles.gameMapDot}
|
||||||
|
style={{ left: `${r.dotX}%`, top: `${r.dotY}%` }}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<span className={styles.gameMapPing} />
|
||||||
|
<span className={styles.gameMapCore} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.gameMapLabel}>
|
||||||
|
ОБЪЕКТ НА КАРТЕ · {r.district}
|
||||||
|
</div>
|
||||||
|
<div className={styles.gameMapBottom}>
|
||||||
|
<div className={styles.gameAddr}>{r.addr}</div>
|
||||||
|
<div className={styles.gameMeta}>{r.meta}</div>
|
||||||
|
<div className={styles.gameTags}>
|
||||||
|
{r.tags.map((t) => (
|
||||||
|
<span key={t} className={styles.gameTag}>
|
||||||
|
{t}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.gamePanel}>
|
||||||
|
{phase === "playing" && (
|
||||||
|
<div className={styles.gameFade}>
|
||||||
|
<div className={styles.gameQuestion}>{r.question}</div>
|
||||||
|
<div className={styles.gameHint}>{r.hint}</div>
|
||||||
|
<div className={styles.gameGuess}>{fmtMln(guess)}</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={r.min}
|
||||||
|
max={r.max}
|
||||||
|
step={r.step}
|
||||||
|
value={guess}
|
||||||
|
onChange={(e) => setGuess(Number(e.target.value))}
|
||||||
|
className={styles.gameRange}
|
||||||
|
aria-label="Ваша цена, млн ₽"
|
||||||
|
/>
|
||||||
|
<div className={styles.gameScale}>
|
||||||
|
<span>{fmtMln(r.min)}</span>
|
||||||
|
<span>{fmtMln(r.max)}</span>
|
||||||
|
</div>
|
||||||
|
<button type="button" onClick={answer} className={styles.gameBtnAccent}>
|
||||||
|
Ответить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{phase === "revealed" && (
|
||||||
|
<div className={styles.gameFade}>
|
||||||
|
<div className={styles.gameResultEyebrow}>РЕЗУЛЬТАТ РАУНДА</div>
|
||||||
|
<div className={styles.gameRows}>
|
||||||
|
<div className={styles.gameRowLight}>
|
||||||
|
<span className={styles.gameRowLabel}>Ваш ответ</span>
|
||||||
|
<span className={styles.gameRowValue}>{fmtMln(guess)}</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.gameRowSoft}>
|
||||||
|
<span className={styles.gameRowLabelAccent}>МЕРА сказала</span>
|
||||||
|
<span className={styles.gameRowValueAccent}>{fmtMln(r.mera)}</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.gameRowDark}>
|
||||||
|
<span className={styles.gameRowLabelMuted}>Факт сделки</span>
|
||||||
|
<span className={styles.gameRowValueLight}>{fmtMln(r.fact)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.gameVerdict}>
|
||||||
|
Вы разошлись с фактом на {roundErrPct}
|
||||||
|
{" · "}МЕРА — на{" "}
|
||||||
|
{`${(Math.abs(r.mera - r.fact) / r.fact * 100)
|
||||||
|
.toFixed(1)
|
||||||
|
.replace(".", ",")}%`}
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
<button type="button" onClick={next} className={styles.gameBtnDark}>
|
||||||
|
{isLast ? "Показать итог" : "Следующая квартира →"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{phase === "finished" && (
|
||||||
|
<div className={`${styles.gameFade} ${styles.gameFinal}`}>
|
||||||
|
<div className={styles.gameResultEyebrow}>ИТОГ ТРЁХ РАУНДОВ</div>
|
||||||
|
<div className={styles.gameFinalGrid}>
|
||||||
|
<div className={styles.gameFinalTileLight}>
|
||||||
|
<div className={styles.gameFinalValue}>{userErrPct}</div>
|
||||||
|
<div className={styles.gameFinalLabel}>ваша ошибка по цене</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.gameFinalTileSoft}>
|
||||||
|
<div className={styles.gameFinalValueAccent}>
|
||||||
|
{GAME_MERA_ERROR_LABEL}
|
||||||
|
</div>
|
||||||
|
<div className={styles.gameFinalLabelAccent}>
|
||||||
|
ошибка МЕРЫ по цене
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.gameVerdict}>
|
||||||
|
Рынок сложно угадать на глаз — для этого и существует расчёт
|
||||||
|
по данным.
|
||||||
|
</div>
|
||||||
|
<PublicLink to="estimate" className={styles.gameBtnAccent}>
|
||||||
|
Узнать цифру для своей квартиры
|
||||||
|
</PublicLink>
|
||||||
|
<button type="button" onClick={restart} className={styles.gameRestart}>
|
||||||
|
Сыграть заново
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
/**
|
||||||
|
* ObjectionsV3 — «Что обычно спрашивают перед оплатой» (макет 29.08, секция
|
||||||
|
* «V3 — возражения»): двухколоночный FAQ с CTA слева.
|
||||||
|
*
|
||||||
|
* Аккордеон — нативные <details>/<summary>, как в Faq старого лэндинга:
|
||||||
|
* макетный клиентский toggle не нужен, поведение и клавиатурная доступность
|
||||||
|
* бесплатны. Иконка +/− рисуется через CSS по [open].
|
||||||
|
*
|
||||||
|
* Ответы НЕ из макета (там реквизит рендера): собраны из уже выверенной
|
||||||
|
* честной копии продукта — content.ts (FAQ), oferta/refund. Вопрос про факт
|
||||||
|
* сделки отвечаем тем, что в продукте ЕСТЬ (сделки Росреестра + снятие
|
||||||
|
* объявлений с публикации), а не макетной формулировкой про «отметки
|
||||||
|
* продавцов», механики которых не существует.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styles from "../../landing-v3.module.css";
|
||||||
|
|
||||||
|
import { PublicLink } from "../PublicLink";
|
||||||
|
|
||||||
|
interface Objection {
|
||||||
|
readonly q: string;
|
||||||
|
readonly a: readonly string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const OBJECTIONS: readonly Objection[] = [
|
||||||
|
{
|
||||||
|
q: "Откуда вы знаете факт сделки?",
|
||||||
|
a: [
|
||||||
|
"К объявлениям мы добавляем зарегистрированные сделки Росреестра и отслеживаем снятие объявлений с публикации. По этим данным считается расхождение прогноза с реальностью — и честно показывается, на скольких объектах построен каждый расчёт.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Почему так дёшево — это точно работает?",
|
||||||
|
a: [
|
||||||
|
"Дёшево, потому что расчёт автоматический: без выезда, без оценщика, без комиссии. Вы платите за данные, а не за часы специалиста.",
|
||||||
|
"Точность прямо зависит от того, сколько нашлось сопоставимых объектов, — поэтому мы всегда пишем, на скольких объектах и сделках построен расчёт, и честно сообщаем, если данных по дому мало.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Чем это лучше бесплатных калькуляторов?",
|
||||||
|
a: [
|
||||||
|
"Калькулятор площадки считает по объявлениям этой же площадки — то есть по ценам, которые продавцы просят, а не получают.",
|
||||||
|
"Мы смотрим сразу несколько площадок и добавляем зарегистрированные сделки, чтобы была видна разница между запрашиваемой и реальной ценой.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Это подписка? Спишут ли ещё раз?",
|
||||||
|
a: [
|
||||||
|
"Нет. Это разовый платёж за один отчёт — без подписки, автопродлений и скрытых списаний. Отчёт остаётся у вас по ссылке.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Мне начнут звонить после проверки?",
|
||||||
|
a: [
|
||||||
|
"Нет. Для расчёта нужны адрес и параметры квартиры — телефон мы не спрашиваем. Он появляется, только если вы сами решите оставить заявку, и всегда с отдельной галочкой согласия.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Это официальная оценка для банка?",
|
||||||
|
a: [
|
||||||
|
"Нет. Для ипотеки, суда, опеки и нотариуса нужен отчёт аккредитованного оценщика по 135-ФЗ — это отдельная процедура с выездом. МЕРА отвечает на другой вопрос: за сколько квартира реально продаётся на рынке сегодня.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function ObjectionsV3() {
|
||||||
|
return (
|
||||||
|
<section className={styles.objSection} aria-labelledby="obj-v3-title">
|
||||||
|
<div className={styles.objGrid}>
|
||||||
|
<div>
|
||||||
|
<div className={styles.accEyebrow}>СОМНЕНИЯ</div>
|
||||||
|
<h2 id="obj-v3-title" className={styles.objTitle}>
|
||||||
|
Что обычно спрашивают перед оплатой
|
||||||
|
</h2>
|
||||||
|
<PublicLink to="estimate" className={styles.objCtaBtn}>
|
||||||
|
Проверить за 30 секунд
|
||||||
|
</PublicLink>
|
||||||
|
</div>
|
||||||
|
<div className={styles.objList}>
|
||||||
|
{OBJECTIONS.map((o) => (
|
||||||
|
<details key={o.q} className={styles.objItem}>
|
||||||
|
<summary className={styles.objSummary}>
|
||||||
|
<span className={styles.objQuestion}>{o.q}</span>
|
||||||
|
<span className={styles.objIcon} aria-hidden="true" />
|
||||||
|
</summary>
|
||||||
|
<div className={styles.objAnswer}>
|
||||||
|
{o.a.map((p) => (
|
||||||
|
<p key={p.slice(0, 24)}>{p}</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
/**
|
||||||
|
* TwoPathsV3 — «С отчётом справитесь сами. Не уверены — сделаем за вас»
|
||||||
|
* (макет 29.08, секция `#turnkey` «V3 — два пути»).
|
||||||
|
*
|
||||||
|
* Кнопка пути 1 ведёт на `/estimate` — то же решение, что у StickyCtaV3:
|
||||||
|
* макетный `openModal` в коде заменён отдельным экраном проверки.
|
||||||
|
*
|
||||||
|
* Кнопка пути 2 («Оставить заявку») ведёт в Telegram поддержки: формы
|
||||||
|
* партнёрской заявки и договора «продажи под ключ» в продукте НЕТ — до их
|
||||||
|
* появления честный путь заявки один, живой канал. Текст пути 2 из макета
|
||||||
|
* сохранён дословно: он аккуратно не обещает ничего сверх «узнайте условия».
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SUPPORT_TELEGRAM_URL } from "../../content";
|
||||||
|
import { COST_OF_ERROR_PLACEHOLDER } from "../../marketing-v3";
|
||||||
|
import styles from "../../landing-v3.module.css";
|
||||||
|
|
||||||
|
import { PublicLink } from "../PublicLink";
|
||||||
|
|
||||||
|
const REPORT_PRICE = COST_OF_ERROR_PLACEHOLDER[2].value;
|
||||||
|
|
||||||
|
export function TwoPathsV3() {
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
id="turnkey"
|
||||||
|
className={styles.pathsSection}
|
||||||
|
aria-labelledby="paths-v3-title"
|
||||||
|
>
|
||||||
|
<div className={styles.pathsHead}>
|
||||||
|
<div className={styles.accEyebrow}>ВЫБЕРИТЕ ПУТЬ</div>
|
||||||
|
<h2 id="paths-v3-title" className={styles.accTitle}>
|
||||||
|
С отчётом справитесь сами. Не уверены — сделаем за вас
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className={styles.pathsGrid}>
|
||||||
|
<div className={styles.pathCard}>
|
||||||
|
<div className={styles.pathEyebrow}>ПУТЬ 1 · САМОСТОЯТЕЛЬНО</div>
|
||||||
|
<div className={styles.pathTitle}>Отчёт за {REPORT_PRICE}</div>
|
||||||
|
<p className={styles.pathBody}>
|
||||||
|
Цена, срок, аналоги, факторы. Дальше вы ведёте продажу сами — на
|
||||||
|
руках все цифры, чтобы уверенно держать свою цену в переговорах.
|
||||||
|
</p>
|
||||||
|
<PublicLink to="estimate" className={styles.pathBtnLight}>
|
||||||
|
Проверить квартиру
|
||||||
|
</PublicLink>
|
||||||
|
</div>
|
||||||
|
<div className={styles.pathCardDark}>
|
||||||
|
<div className={styles.pathEyebrowAccent}>
|
||||||
|
ПУТЬ 2 · ЕСЛИ НУЖНА ПОМОЩЬ
|
||||||
|
</div>
|
||||||
|
<div className={styles.pathTitle}>
|
||||||
|
Можем продать за вас — но только если захотите
|
||||||
|
</div>
|
||||||
|
<p className={styles.pathBodyDark}>
|
||||||
|
Отчёта достаточно, чтобы справиться самому. Но если нет времени или
|
||||||
|
не хочется вести показы и торг — передайте продажу нашим
|
||||||
|
специалистам: объявление, фото, показы, переговоры, сделка. Работаем
|
||||||
|
по цене из вашего отчёта, стоимость фиксированная и известна
|
||||||
|
заранее. Ни к чему не обязывает — просто узнайте условия.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href={SUPPORT_TELEGRAM_URL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.pathBtnAccent}
|
||||||
|
>
|
||||||
|
Оставить заявку
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1482,3 +1482,685 @@
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: var(--b2c-surface-soft);
|
background: var(--b2c-surface-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
Макет 29.08 («B2C модуль», Макс): лента сделок, игра, два пути,
|
||||||
|
возражения, статьи. Та же палитра b2c-tokens, что и выше.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/* --- Лента «ПРОГНОЗ → ФАКТ» ---------------------------------------------- */
|
||||||
|
.tickerRoot {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--b2c-surface-dark);
|
||||||
|
}
|
||||||
|
.tickerBadge {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 auto 0 0;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0 18px 0 clamp(20px, 5vw, 56px);
|
||||||
|
background: var(--b2c-surface-dark);
|
||||||
|
box-shadow: 18px 0 24px rgba(18, 26, 29, 0.9);
|
||||||
|
}
|
||||||
|
.tickerDot {
|
||||||
|
display: block;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--b2c-accent);
|
||||||
|
animation: tickerPulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.tickerBadgeText {
|
||||||
|
font-family: var(--b2c-font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
color: var(--b2c-accent);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.tickerTrack {
|
||||||
|
display: flex;
|
||||||
|
width: max-content;
|
||||||
|
padding: 12px 0;
|
||||||
|
animation: tickerMarquee 90s linear infinite;
|
||||||
|
}
|
||||||
|
.tickerItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 0 30px;
|
||||||
|
border-right: 1px solid #2c3a3f;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.tickerObj {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.tickerPair {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.tickerLabelMuted {
|
||||||
|
font-family: var(--b2c-font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
color: var(--b2c-muted);
|
||||||
|
}
|
||||||
|
.tickerLabelAccent {
|
||||||
|
composes: tickerLabelMuted;
|
||||||
|
color: var(--b2c-accent-text);
|
||||||
|
}
|
||||||
|
.tickerSaid {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #9fb0b4;
|
||||||
|
}
|
||||||
|
.tickerArrow {
|
||||||
|
color: #2c3a3f;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
.tickerFact {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--b2c-accent);
|
||||||
|
}
|
||||||
|
.tickerErr {
|
||||||
|
font-family: var(--b2c-font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: #9fb0b4;
|
||||||
|
}
|
||||||
|
.tickerGain {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
font-family: var(--b2c-font-mono);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #3fd1a6;
|
||||||
|
}
|
||||||
|
.tickerGainCaret {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
@keyframes tickerMarquee {
|
||||||
|
from { transform: translateX(0); }
|
||||||
|
to { transform: translateX(-50%); }
|
||||||
|
}
|
||||||
|
@keyframes tickerPulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.45; }
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.tickerTrack { animation: none; }
|
||||||
|
.tickerDot { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Игра «Угадай цену» --------------------------------------------------- */
|
||||||
|
.gameSection {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: clamp(48px, 6vw, 80px) clamp(20px, 5vw, 56px);
|
||||||
|
}
|
||||||
|
.gameHead {
|
||||||
|
max-width: 700px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
.gameLead {
|
||||||
|
margin: 14px 0 0;
|
||||||
|
font-size: 16.5px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--b2c-muted);
|
||||||
|
text-wrap: pretty;
|
||||||
|
}
|
||||||
|
.gameCard {
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--b2c-surface);
|
||||||
|
border: 1px solid var(--b2c-line);
|
||||||
|
border-radius: 14px;
|
||||||
|
box-shadow: 0 16px 44px rgba(18, 26, 29, 0.07);
|
||||||
|
}
|
||||||
|
.gameGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
|
||||||
|
}
|
||||||
|
.gameMap {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 24px;
|
||||||
|
min-height: 300px;
|
||||||
|
padding: clamp(24px, 3vw, 34px);
|
||||||
|
background: var(--b2c-surface-dark);
|
||||||
|
}
|
||||||
|
.gameMapGrid {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0.16;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(var(--b2c-accent) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, var(--b2c-accent) 1px, transparent 1px);
|
||||||
|
background-size: 44px 44px;
|
||||||
|
}
|
||||||
|
.gameMapRoadH {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 38%;
|
||||||
|
height: 14px;
|
||||||
|
background: #2c3a3f;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.gameMapRoadV {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 62%;
|
||||||
|
width: 12px;
|
||||||
|
background: #2c3a3f;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.gameMapDot {
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: -8px 0 0 -8px;
|
||||||
|
}
|
||||||
|
.gameMapPing {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: block;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--b2c-accent);
|
||||||
|
animation: gamePing 2.4s ease-out infinite;
|
||||||
|
}
|
||||||
|
.gameMapCore {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: block;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--b2c-accent);
|
||||||
|
border: 3px solid var(--b2c-surface-dark);
|
||||||
|
}
|
||||||
|
@keyframes gamePing {
|
||||||
|
0% { transform: scale(1); opacity: 0.55; }
|
||||||
|
70%, 100% { transform: scale(2.6); opacity: 0; }
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.gameMapPing { animation: none; }
|
||||||
|
}
|
||||||
|
.gameMapLabel {
|
||||||
|
position: relative;
|
||||||
|
font-family: var(--b2c-font-mono);
|
||||||
|
font-size: 9.5px;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
color: var(--b2c-accent);
|
||||||
|
}
|
||||||
|
.gameMapBottom {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.gameAddr {
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: clamp(19px, 2.2vw, 24px);
|
||||||
|
line-height: 1.2;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.gameMeta {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #9fb0b4;
|
||||||
|
}
|
||||||
|
.gameTags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.gameTag {
|
||||||
|
font-family: var(--b2c-font-mono);
|
||||||
|
font-size: 10.5px;
|
||||||
|
color: #9fb0b4;
|
||||||
|
border: 1px solid #2c3a3f;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
.gamePanel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: clamp(24px, 3vw, 34px);
|
||||||
|
}
|
||||||
|
.gameFade {
|
||||||
|
animation: gameFadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
@keyframes gameFadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(8px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.gameFade { animation: none; }
|
||||||
|
}
|
||||||
|
.gameQuestion {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: clamp(17px, 1.8vw, 20px);
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
.gameHint {
|
||||||
|
margin-bottom: 26px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.55;
|
||||||
|
color: var(--b2c-muted);
|
||||||
|
}
|
||||||
|
.gameGuess {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
font-size: clamp(30px, 3.6vw, 40px);
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
line-height: 1;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.gameRange {
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
margin: 0 0 10px;
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--b2c-line);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.gameRange::-webkit-slider-thumb {
|
||||||
|
appearance: none;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--b2c-accent);
|
||||||
|
border: 3px solid var(--b2c-surface);
|
||||||
|
box-shadow: 0 2px 8px rgba(18, 26, 29, 0.25);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.gameRange::-moz-range-thumb {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--b2c-accent);
|
||||||
|
border: 3px solid var(--b2c-surface);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.gameScale {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-family: var(--b2c-font-mono);
|
||||||
|
font-size: 10.5px;
|
||||||
|
color: #9fb0b4;
|
||||||
|
}
|
||||||
|
.gameBtnAccent {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--b2c-accent-deep);
|
||||||
|
color: var(--b2c-on-accent);
|
||||||
|
font-family: var(--b2c-font-sans);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.gameBtnAccent:hover {
|
||||||
|
background: var(--b2c-ink);
|
||||||
|
}
|
||||||
|
.gameBtnDark {
|
||||||
|
composes: gameBtnAccent;
|
||||||
|
background: var(--b2c-surface-dark);
|
||||||
|
}
|
||||||
|
.gameBtnDark:hover {
|
||||||
|
background: var(--b2c-accent-deep);
|
||||||
|
}
|
||||||
|
.gameResultEyebrow {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-family: var(--b2c-font-mono);
|
||||||
|
font-size: 9.5px;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
color: var(--b2c-accent-text);
|
||||||
|
}
|
||||||
|
.gameRows {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.gameRowLight {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--b2c-page-bg);
|
||||||
|
}
|
||||||
|
.gameRowSoft {
|
||||||
|
composes: gameRowLight;
|
||||||
|
background: var(--b2c-surface-soft);
|
||||||
|
}
|
||||||
|
.gameRowDark {
|
||||||
|
composes: gameRowLight;
|
||||||
|
background: var(--b2c-surface-dark);
|
||||||
|
}
|
||||||
|
.gameRowLabel {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--b2c-muted);
|
||||||
|
}
|
||||||
|
.gameRowLabelAccent {
|
||||||
|
composes: gameRowLabel;
|
||||||
|
color: var(--b2c-accent-text);
|
||||||
|
}
|
||||||
|
.gameRowLabelMuted {
|
||||||
|
composes: gameRowLabel;
|
||||||
|
color: #9fb0b4;
|
||||||
|
}
|
||||||
|
.gameRowValue {
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 19px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.gameRowValueAccent {
|
||||||
|
composes: gameRowValue;
|
||||||
|
color: var(--b2c-accent-text);
|
||||||
|
}
|
||||||
|
.gameRowValueLight {
|
||||||
|
composes: gameRowValue;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.gameVerdict {
|
||||||
|
margin-bottom: 22px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #3a4a4f;
|
||||||
|
}
|
||||||
|
.gameFinal {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.gameFinalGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.gameFinalTileLight {
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--b2c-page-bg);
|
||||||
|
}
|
||||||
|
.gameFinalTileSoft {
|
||||||
|
composes: gameFinalTileLight;
|
||||||
|
background: var(--b2c-surface-soft);
|
||||||
|
}
|
||||||
|
.gameFinalValue {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.gameFinalValueAccent {
|
||||||
|
composes: gameFinalValue;
|
||||||
|
color: var(--b2c-accent-text);
|
||||||
|
}
|
||||||
|
.gameFinalLabel {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--b2c-muted);
|
||||||
|
}
|
||||||
|
.gameFinalLabelAccent {
|
||||||
|
composes: gameFinalLabel;
|
||||||
|
color: var(--b2c-accent-text);
|
||||||
|
}
|
||||||
|
.gameRestart {
|
||||||
|
margin-top: 14px;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: var(--b2c-muted);
|
||||||
|
font-family: var(--b2c-font-sans);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.gameRestart:hover {
|
||||||
|
color: var(--b2c-accent-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Два пути ------------------------------------------------------------- */
|
||||||
|
.pathsSection {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: clamp(48px, 6vw, 80px) clamp(20px, 5vw, 56px);
|
||||||
|
}
|
||||||
|
.pathsHead {
|
||||||
|
max-width: 680px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
.pathsGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.pathCard {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: clamp(24px, 3vw, 34px);
|
||||||
|
background: var(--b2c-surface);
|
||||||
|
border: 1px solid var(--b2c-line);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.pathCardDark {
|
||||||
|
composes: pathCard;
|
||||||
|
background: var(--b2c-surface-dark);
|
||||||
|
border-color: var(--b2c-surface-dark);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.pathEyebrow {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
font-family: var(--b2c-font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
color: var(--b2c-muted);
|
||||||
|
}
|
||||||
|
.pathEyebrowAccent {
|
||||||
|
composes: pathEyebrow;
|
||||||
|
color: var(--b2c-accent);
|
||||||
|
}
|
||||||
|
.pathTitle {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 21px;
|
||||||
|
}
|
||||||
|
.pathBody {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--b2c-muted);
|
||||||
|
}
|
||||||
|
.pathBodyDark {
|
||||||
|
composes: pathBody;
|
||||||
|
color: #9fb0b4;
|
||||||
|
}
|
||||||
|
.pathBtnLight {
|
||||||
|
display: block;
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
border: 1px solid var(--b2c-line);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--b2c-page-bg);
|
||||||
|
color: var(--b2c-ink);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.pathBtnLight:hover {
|
||||||
|
border-color: var(--b2c-accent);
|
||||||
|
color: var(--b2c-accent-text);
|
||||||
|
}
|
||||||
|
.pathBtnAccent {
|
||||||
|
display: block;
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--b2c-accent-deep);
|
||||||
|
color: var(--b2c-on-accent);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.pathBtnAccent:hover {
|
||||||
|
background: var(--b2c-accent);
|
||||||
|
color: var(--b2c-on-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Возражения (FAQ) ----------------------------------------------------- */
|
||||||
|
.objSection {
|
||||||
|
background: var(--b2c-surface);
|
||||||
|
border-top: 1px solid var(--b2c-line);
|
||||||
|
border-bottom: 1px solid var(--b2c-line);
|
||||||
|
}
|
||||||
|
.objGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
|
||||||
|
gap: clamp(28px, 4vw, 56px);
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: clamp(48px, 6vw, 80px) clamp(20px, 5vw, 56px);
|
||||||
|
}
|
||||||
|
.objTitle {
|
||||||
|
margin: 0 0 20px;
|
||||||
|
font-size: clamp(26px, 3.4vw, 38px);
|
||||||
|
line-height: 1.12;
|
||||||
|
letter-spacing: -0.015em;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
.objCtaBtn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 13px 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--b2c-surface-dark);
|
||||||
|
color: var(--b2c-on-accent);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14.5px;
|
||||||
|
}
|
||||||
|
.objCtaBtn:hover {
|
||||||
|
background: var(--b2c-accent-deep);
|
||||||
|
color: var(--b2c-on-accent);
|
||||||
|
}
|
||||||
|
.objList {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.objItem {
|
||||||
|
border-bottom: 1px solid var(--b2c-line);
|
||||||
|
}
|
||||||
|
.objSummary {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 20px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.objSummary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.objQuestion {
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 16.5px;
|
||||||
|
color: var(--b2c-ink);
|
||||||
|
}
|
||||||
|
.objIcon {
|
||||||
|
position: relative;
|
||||||
|
flex: none;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
.objIcon::before,
|
||||||
|
.objIcon::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 50% auto auto 0;
|
||||||
|
width: 16px;
|
||||||
|
height: 2px;
|
||||||
|
margin-top: -1px;
|
||||||
|
border-radius: 1px;
|
||||||
|
background: var(--b2c-accent);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
.objIcon::after {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
.objItem[open] .objIcon::after {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
.objAnswer {
|
||||||
|
max-width: 720px;
|
||||||
|
padding: 0 0 22px;
|
||||||
|
}
|
||||||
|
.objAnswer p {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.65;
|
||||||
|
color: var(--b2c-muted);
|
||||||
|
}
|
||||||
|
.objAnswer p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Статьи --------------------------------------------------------------- */
|
||||||
|
.artSection {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: clamp(48px, 6vw, 80px) clamp(20px, 5vw, 56px);
|
||||||
|
}
|
||||||
|
.artHead {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 24px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
.artTitle {
|
||||||
|
margin: 0;
|
||||||
|
font-size: clamp(24px, 3vw, 32px);
|
||||||
|
line-height: 1.15;
|
||||||
|
letter-spacing: -0.015em;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
.artGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.artCard {
|
||||||
|
padding: 26px;
|
||||||
|
background: var(--b2c-surface);
|
||||||
|
border: 1px solid var(--b2c-line);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.artRubric {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
font-family: var(--b2c-font-mono);
|
||||||
|
font-size: 9.5px;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
color: var(--b2c-muted);
|
||||||
|
}
|
||||||
|
.artCardTitle {
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 17.5px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -164,3 +164,86 @@ export const PROOF_ROWS_PLACEHOLDER: readonly ProofRow[] = [
|
||||||
gainPct: "+3,4%",
|
gainPct: "+3,4%",
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Дополнение по макету 29.08 («B2C модуль для МЕРА», Макс) — секции «лента
|
||||||
|
// сделок», «игра "Угадай цену"», «возражения», «статьи». Тот же контракт, что
|
||||||
|
// у величин выше: ничего из этого НЕ подтверждено замером, контура сверки
|
||||||
|
// «прогноз → факт» в продукте не существует (см. шапку модуля и гейт-тест) —
|
||||||
|
// поэтому данные живут здесь, под noindex, до появления вычисляемых.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Раунд мини-игры «Угадай цену»: реквизит из макета, НЕ реальная сделка. */
|
||||||
|
export interface GameRound {
|
||||||
|
readonly district: string;
|
||||||
|
readonly addr: string;
|
||||||
|
readonly meta: string;
|
||||||
|
readonly tags: readonly string[];
|
||||||
|
readonly question: string;
|
||||||
|
readonly hint: string;
|
||||||
|
/** Слайдер: млн ₽. */
|
||||||
|
readonly min: number;
|
||||||
|
readonly max: number;
|
||||||
|
readonly step: number;
|
||||||
|
readonly start: number;
|
||||||
|
/** «МЕРА сказала» и «факт сделки», млн ₽ — числа из макета. */
|
||||||
|
readonly mera: number;
|
||||||
|
readonly fact: number;
|
||||||
|
/** Позиция точки на декоративной карте, в процентах. */
|
||||||
|
readonly dotX: number;
|
||||||
|
readonly dotY: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GAME_ROUNDS_PLACEHOLDER: readonly GameRound[] = [
|
||||||
|
{
|
||||||
|
district: "ЦЕНТРАЛЬНЫЙ Р-Н",
|
||||||
|
addr: "1-комнатная, 34 м²",
|
||||||
|
meta: "Центральный р-н · 4/9 этаж · панель",
|
||||||
|
tags: ["ХОРОШЕЕ СОСТОЯНИЕ", "9 АНАЛОГОВ", "БЛИЗКО К МЕТРО"],
|
||||||
|
question: "Какую цену поставить, чтобы продать за 30 дней?",
|
||||||
|
hint: "Продано в марте 2026. Ваш ответ — в миллионах рублей.",
|
||||||
|
min: 4.5,
|
||||||
|
max: 7.5,
|
||||||
|
step: 0.05,
|
||||||
|
start: 5.4,
|
||||||
|
mera: 5.55,
|
||||||
|
fact: 5.5,
|
||||||
|
dotX: 32,
|
||||||
|
dotY: 42,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
district: "ЮГО-ЗАПАД",
|
||||||
|
addr: "2-комнатная, 52 м²",
|
||||||
|
meta: "Юго-Западный р-н · 7/16 этаж · монолит",
|
||||||
|
tags: ["КОСМЕТИКА", "14 АНАЛОГОВ", "ДВОР БЕЗ МАШИН"],
|
||||||
|
question: "За сколько её реально купили?",
|
||||||
|
hint: "Продано в мае 2026. Ваш ответ — в миллионах рублей.",
|
||||||
|
min: 5.5,
|
||||||
|
max: 9.5,
|
||||||
|
step: 0.05,
|
||||||
|
start: 7.2,
|
||||||
|
mera: 7.4,
|
||||||
|
fact: 7.55,
|
||||||
|
dotX: 58,
|
||||||
|
dotY: 61,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
district: "УРАЛМАШ",
|
||||||
|
addr: "3-комнатная, 74 м²",
|
||||||
|
meta: "Орджоникидзевский р-н · 2/5 этаж · кирпич",
|
||||||
|
tags: ["ТРЕБУЕТ РЕМОНТА", "6 АНАЛОГОВ", "ТИХИЙ ДВОР"],
|
||||||
|
question: "Какая цена ушла бы за 45 дней?",
|
||||||
|
hint: "Продано в июне 2026. Ваш ответ — в миллионах рублей.",
|
||||||
|
min: 4.0,
|
||||||
|
max: 8.0,
|
||||||
|
step: 0.05,
|
||||||
|
start: 6.0,
|
||||||
|
mera: 5.85,
|
||||||
|
fact: 5.95,
|
||||||
|
dotX: 44,
|
||||||
|
dotY: 28,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Ошибка МЕРЫ в финале игры — та же величина, что в бейдже героя. */
|
||||||
|
export const GAME_MERA_ERROR_LABEL = "1,8%";
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
import { AccuracyV3 } from "../_components/v3/AccuracyV3";
|
import { AccuracyV3 } from "../_components/v3/AccuracyV3";
|
||||||
|
import { ArticlesV3 } from "../_components/v3/ArticlesV3";
|
||||||
import { CostOfErrorV3 } from "../_components/v3/CostOfErrorV3";
|
import { CostOfErrorV3 } from "../_components/v3/CostOfErrorV3";
|
||||||
|
import { DealsTickerV3 } from "../_components/v3/DealsTickerV3";
|
||||||
import { FooterV3 } from "../_components/v3/FooterV3";
|
import { FooterV3 } from "../_components/v3/FooterV3";
|
||||||
import { HeaderV3 } from "../_components/v3/HeaderV3";
|
import { HeaderV3 } from "../_components/v3/HeaderV3";
|
||||||
|
import { GuessGameV3 } from "../_components/v3/GuessGameV3";
|
||||||
import { HeroV3 } from "../_components/v3/HeroV3";
|
import { HeroV3 } from "../_components/v3/HeroV3";
|
||||||
|
import { ObjectionsV3 } from "../_components/v3/ObjectionsV3";
|
||||||
import { StepsV3 } from "../_components/v3/StepsV3";
|
import { StepsV3 } from "../_components/v3/StepsV3";
|
||||||
import { StickyCtaV3 } from "../_components/v3/StickyCtaV3";
|
import { StickyCtaV3 } from "../_components/v3/StickyCtaV3";
|
||||||
|
import { TwoPathsV3 } from "../_components/v3/TwoPathsV3";
|
||||||
import styles from "../landing.module.css";
|
import styles from "../landing.module.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -19,15 +24,18 @@ import styles from "../landing.module.css";
|
||||||
* на одном и том же контенте. Переключение — отдельным решением, когда
|
* на одном и том же контенте. Переключение — отдельным решением, когда
|
||||||
* версия принята.
|
* версия принята.
|
||||||
*
|
*
|
||||||
* ЧЕГО ЗДЕСЬ ПОКА НЕТ (секции макета, требующие данных или контуров,
|
* ОБНОВЛЕНИЕ 29.08 (макет «B2C модуль», Макс): добавлены лента
|
||||||
* которых нет в коде — каждая заведена отдельной задачей):
|
* «прогноз → факт», игра «Угадай цену», «Два пути», «Возражения» и тизер
|
||||||
* - лента «прогноз → факт» и мини-игра «Проверьте себя» — нужен контур
|
* статей. Данные ленты/игры — реквизит из marketing-v3.ts под тем же гейтом
|
||||||
* сверки прогноза с фактом сделки;
|
* #2904 (контура сверки прогноза с фактом в продукте по-прежнему нет).
|
||||||
* - «Продажа под ключ» — противоречит текущему утверждению лэндинга
|
*
|
||||||
* «мы не берём квартиры на продажу», требует отдельного договора;
|
* ЧЕГО ЗДЕСЬ ПО-ПРЕЖНЕМУ НЕТ:
|
||||||
* - блок статей — раздела не существует;
|
* - страницы статей («МЕРА статьи»/«МЕРА статья») — карточки тизера
|
||||||
* - модалка проверки и рабочая оплата — нет анонимного периметра и
|
* рендерятся без ссылок до появления раздела;
|
||||||
* платёжного контура.
|
* - модалка проверки — сознательно заменена экраном /estimate (см.
|
||||||
|
* StickyCtaV3), все CTA макетного `openModal` ведут туда;
|
||||||
|
* - партнёрская заявка «продажи под ключ» — кнопка ведёт в Telegram
|
||||||
|
* поддержки до появления формы и договора.
|
||||||
*
|
*
|
||||||
* ЧИСЛА в секции «Точность» и «Цена ошибки» — плейсхолдеры из макета,
|
* ЧИСЛА в секции «Точность» и «Цена ошибки» — плейсхолдеры из макета,
|
||||||
* см. `marketing-v3.ts`: они обязаны быть заменены вычисляемыми до снятия
|
* см. `marketing-v3.ts`: они обязаны быть заменены вычисляемыми до снятия
|
||||||
|
|
@ -44,10 +52,15 @@ export default function MeraPublicV3Page() {
|
||||||
<>
|
<>
|
||||||
<HeaderV3 />
|
<HeaderV3 />
|
||||||
<main id="content" className={styles.main} tabIndex={-1}>
|
<main id="content" className={styles.main} tabIndex={-1}>
|
||||||
<HeroV3 />
|
<DealsTickerV3 />
|
||||||
|
<HeroV3 />
|
||||||
<StepsV3 />
|
<StepsV3 />
|
||||||
<AccuracyV3 />
|
<AccuracyV3 />
|
||||||
|
<GuessGameV3 />
|
||||||
<CostOfErrorV3 />
|
<CostOfErrorV3 />
|
||||||
|
<TwoPathsV3 />
|
||||||
|
<ObjectionsV3 />
|
||||||
|
<ArticlesV3 />
|
||||||
</main>
|
</main>
|
||||||
<FooterV3 />
|
<FooterV3 />
|
||||||
<StickyCtaV3 />
|
<StickyCtaV3 />
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue