feat(mera/b2c): лэндинг v3 доведён до макета 29.08 — лента, игра, два пути, возражения, статьи #3215
9 changed files with 1279 additions and 10 deletions
|
|
@ -41,6 +41,9 @@ const V3_SOURCES = [
|
|||
"_components/v3/AccuracyV3.tsx",
|
||||
"_components/v3/FreeCheckCard.tsx",
|
||||
"_components/v3/FreeResultV3.tsx",
|
||||
// Макет 29.08: лента и игра тоже стоят на реквизитных величинах.
|
||||
"_components/v3/DealsTickerV3.tsx",
|
||||
"_components/v3/GuessGameV3.tsx",
|
||||
];
|
||||
|
||||
const PLACEHOLDER_EXPORTS = [
|
||||
|
|
@ -48,6 +51,7 @@ const PLACEHOLDER_EXPORTS = [
|
|||
"COST_OF_ERROR_PLACEHOLDER",
|
||||
"FREE_SAMPLE_PLACEHOLDER",
|
||||
"PROOF_ROWS_PLACEHOLDER",
|
||||
"GAME_ROUNDS_PLACEHOLDER",
|
||||
];
|
||||
|
||||
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;
|
||||
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%",
|
||||
},
|
||||
] 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 { AccuracyV3 } from "../_components/v3/AccuracyV3";
|
||||
import { ArticlesV3 } from "../_components/v3/ArticlesV3";
|
||||
import { CostOfErrorV3 } from "../_components/v3/CostOfErrorV3";
|
||||
import { DealsTickerV3 } from "../_components/v3/DealsTickerV3";
|
||||
import { FooterV3 } from "../_components/v3/FooterV3";
|
||||
import { HeaderV3 } from "../_components/v3/HeaderV3";
|
||||
import { GuessGameV3 } from "../_components/v3/GuessGameV3";
|
||||
import { HeroV3 } from "../_components/v3/HeroV3";
|
||||
import { ObjectionsV3 } from "../_components/v3/ObjectionsV3";
|
||||
import { StepsV3 } from "../_components/v3/StepsV3";
|
||||
import { StickyCtaV3 } from "../_components/v3/StickyCtaV3";
|
||||
import { TwoPathsV3 } from "../_components/v3/TwoPathsV3";
|
||||
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`: они обязаны быть заменены вычисляемыми до снятия
|
||||
|
|
@ -44,10 +52,15 @@ export default function MeraPublicV3Page() {
|
|||
<>
|
||||
<HeaderV3 />
|
||||
<main id="content" className={styles.main} tabIndex={-1}>
|
||||
<HeroV3 />
|
||||
<DealsTickerV3 />
|
||||
<HeroV3 />
|
||||
<StepsV3 />
|
||||
<AccuracyV3 />
|
||||
<GuessGameV3 />
|
||||
<CostOfErrorV3 />
|
||||
<TwoPathsV3 />
|
||||
<ObjectionsV3 />
|
||||
<ArticlesV3 />
|
||||
</main>
|
||||
<FooterV3 />
|
||||
<StickyCtaV3 />
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue