fix(mera/b2c): вернуть потерянные вопросы FAQ, починить адрес превью и честность двух комментариев
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m0s

Правки по трём ревью ветки «v3 становится корнем».

FAQ. Из шести вопросов `content.ts` возражения v3 брали два, остальные
четыре (`how-accurate`, `vs-marketplace`, `personal-data`, `bank-report`)
не рендерились нигде: `Faq.tsx`, который выводил все шесть, удалён вместе
с лэндингом v1. Макетные возражения их тематически закрывали, но
пересказом — а оригиналы проходили юр-ревью. Содержательно пропали два
утверждения: «Имя, паспорт и документы на квартиру мы не спрашиваем» с
отсылкой к странице ПДн (152-ФЗ) и «показываем диапазон, а не одно число»
как прямой ответ про точность (рамка диапазона осталась только в
`AccuracyV3`, рядом с плейсхолдерными числами).

Теперь в секции один порядок чтения: все шесть вопросов из `FAQ` по id
плюс три макетных возражения, которым в `FAQ` пары нет («факт сделки»,
«почему так дёшево», «подписка»). Три макетных пересказа удалены в пользу
оригиналов; у «почему так дёшево» убран второй абзац — почти дословный
повтор второго абзаца `how-accurate`, который теперь стоит следующим.
Новых формулировок не добавлено. Отсылка к странице ПДн стала живой
ссылкой через `PublicLink` (обычный `<a>`, basePath не течёт); текст
ссылки — слова самого `content.ts`, найденные по вхождению.

Гейт стал двусторонним: тест перебирает `FAQ` и требует, чтобы КАЖДЫЙ id
был отрисован. Прежняя версия перечисляла два id руками и потому молчала
ровно в тот момент, когда четыре ответа исчезли со страницы. Проверено
мутацией: снятие вопроса, переименование id и переформулировка фразы про
ПДн красят тест по отдельности.

CADDY. `/trade-in/mera-public/v3` выбыл из @meraLongPages вместе со
страницей и никуда не попал — длинный адрес превью падал в catch-all 404,
хотя раньше вёл на страницу. Обе его формы добавлены в @meraV3Gone. Там же
редирект перестал терять query: было `redir * /`, стало `redir * {uri}`
со срезанием пути — как у @meraLongPages, где перенос UTM и обоснован.
Проверено на живом Caddy 2.11 по настоящему site-блоку: все четыре формы
дают 301 на `/` с сохранённой query, без параметров — чистый `/` без
хвоста `?`, соседние матчеры не задеты, лишний сегмент по-прежнему 404.

КОММЕНТАРИИ, ОПИСЫВАЮЩИЕ НЕСУЩЕСТВУЮЩЕЕ. Строка `page.tsx` в гейте
плейсхолдеров убрана: комментарий над ней утверждал, что устаревший путь
«перестал бы что-либо сканировать», но ни в корневом `page.tsx`, ни в
прежнем `v3/page.tsx` нет ни одного имени из `PLACEHOLDER_EXPORTS` —
страница только собирает секции. Замер подтверждает: множество найденных
плейсхолдеров со строкой и без неё совпадает, а сама по себе она не даёт
ничего. Держать её значило держать ложное ощущение охвата.

Дескриптор «Оценка вторичного жилья по рыночным данным · <регион>» на
внутренние страницы не возвращается — причина записана в докстринге
`InnerHeader`: эту шапку носят и `/estimate`, `/articles`, `/docs`, где
дескриптора не было никогда; требование юриста было про первый экран
лэндинга и там выполняется `HeroV3`; на самих юр-страницах то же сказано
сильнее — в тексте оферты (п. 1.3, 6.2) и в `FooterV3` на каждой странице.
This commit is contained in:
bot-backend 2026-08-29 19:35:57 +05:00
parent 6976ca8915
commit 49652f4413
7 changed files with 242 additions and 68 deletions

View file

@ -372,12 +372,35 @@ meraocenka.ru {
# двух недель: владелец сравнивал по нему версии, и ссылка почти наверняка
# осталась в закладках и в переписке. Поэтому 301 на корень, а не молчание
# белого списка: человек по старой ссылке попадает ровно на то, что по ней
# и смотрел, только теперь это главная. Обе формы — со слэшем и без:
# `/v3/` вышел из @meraShortSlash вместе с остальным превью и иначе упал бы
# в catch-all 404.
@meraV3Gone path /v3 /v3/
# и смотрел, только теперь это главная.
#
# ЧЕТЫРЕ ФОРМЫ, А НЕ ДВЕ. Превью раздавалось и коротким `/v3`, и длинным
# `/trade-in/mera-public/v3` — вторая пара до 29.08.2026 жила в
# @meraLongPages и оттуда выбыла вместе со страницей. Короткие формы её
# не подхватывают (матчер `path` требует точного совпадения), поэтому
# длинный адрес превью падал в catch-all 404 — тот же класс дефекта,
# ради которого длинные адреса вообще оставлены живыми. Все четыре формы
# ведут в один корень, поэтому и матчер один.
#
# QUERY ПЕРЕНОСИТСЯ — `redir * {uri}`, как в @meraLongPages, а не
# `redir * /`, как было здесь до этой правки. Причина ровно та же, что
# записана у @meraLongPages: по разосланной ссылке приходят с UTM-метками,
# и `/v3?utm=…` не должен терять атрибуцию на редиректе. Оба
# `uri strip_prefix` срезают путь целиком (для `/v3` — до пустого, для
# `/v3/` — до `/`), а `{uri}` при пустом пути даёт `/`, поэтому запрос без
# параметров отдаёт чистый `Location: /` без хвоста `?`. Обёртка `route`
# обязательна по той же причине, что у @meraLongPages: внутри `handle`
# порядок директив определяет Caddy, и без неё `redir` выполнится раньше
# `uri`. Проверено на живом Caddy 2.11 (прод — `caddy:2`): все четыре формы
# с параметрами и без дают 301 на `/` с сохранённой query, а лишний
# сегмент (`/trade-in/mera-public/v3/evil`) по-прежнему падает в 404.
@meraV3Gone path /v3 /v3/ /trade-in/mera-public/v3 /trade-in/mera-public/v3/
handle @meraV3Gone {
redir * / permanent
route {
uri strip_prefix /trade-in/mera-public
uri strip_prefix /v3
redir * {uri} permanent
}
}
# Длинные адреса поддерева → 301 на короткие. Один канонический адрес у

View file

@ -34,13 +34,21 @@ function read(rel: string): string {
return readFileSync(join(ROOT, rel), "utf8");
}
/** Компоненты лэндинга v3, которые могут ссылаться на плейсхолдеры. */
/**
* Компоненты лэндинга v3, которые могут ссылаться на плейсхолдеры.
*
* САМОЙ СТРАНИЦЫ (`page.tsx`) ЗДЕСЬ НЕТ, И ЭТО НЕ ПРОПУСК. 29.08.2026 лэндинг
* переехал из `v3/page.tsx` в корень, и путь в этом списке обновили вслед за
* файлом с обоснованием «иначе строка перестала бы что-либо сканировать».
* Обоснование звучало как проверенный факт, но проверено не было: ни в
* `page.tsx`, ни в прежнем `v3/page.tsx` нет ни одного имени из
* `PLACEHOLDER_EXPORTS` страница только собирает секции из импортов, а числа
* живут в самих секциях. То есть строка не давала покрытия ни до переезда, ни
* после, и держать её значило держать ложное ощущение охвата. Появится величина
* прямо в разметке страницы строку сюда вернуть (и тогда она будет что-то
* значить).
*/
const V3_SOURCES = [
// 29.08.2026: лэндинг v3 переехал из `v3/page.tsx` в корень. Путь обязан
// ехать вместе с ним — `placeholdersInUse()` молча пропускает ненайденный
// файл (это правильно для переименований), поэтому устаревшая строка не
// покраснела бы, а просто перестала бы что-либо сканировать.
"page.tsx",
"_components/v3/HeroV3.tsx",
"_components/v3/AccuracyV3.tsx",
"_components/v3/FreeCheckCard.tsx",

View file

@ -196,6 +196,11 @@ describe("короткие адреса публичного домена", () =
*
* Правильный ответ ровно один 301 на корень, и он проверяется здесь, а не
* держится на памяти о том, почему в конфиге появился ещё один матчер.
*
* ФОРМ ЧЕТЫРЕ, А НЕ ДВЕ. Превью раздавалось и коротким `/v3`, и длинным
* `/trade-in/mera-public/v3`; вторая пара выбыла из @meraLongPages вместе со
* страницей и никуда не попала длинный адрес превью отдавал 404, ровно тот
* же дефект, что проверка выше ловит для страниц.
*/
it("адрес выбывшего превью /v3 отдаёт 301 на корень, а не 404", () => {
const matcher = meraBlock.match(/@meraV3Gone path ([^\n]+)/)?.[1] ?? "";
@ -203,14 +208,32 @@ describe("короткие адреса публичного домена", () =
expect(
paths,
"нет матчера @meraV3Gone — старые ссылки на превью упадут в catch-all 404",
).toEqual(["/v3", "/v3/"]);
).toEqual([
"/v3",
"/v3/",
"/trade-in/mera-public/v3",
"/trade-in/mera-public/v3/",
]);
// Тело `handle` содержит вложенный `route { … }`, поэтому берём до
// закрывающей скобки НА УРОВНЕ handle (четыре пробела), а не до первой.
const handler =
meraBlock.match(/handle @meraV3Gone \{([^}]*)\}/)?.[1] ?? "";
meraBlock.match(/handle @meraV3Gone \{([\s\S]*?)\n {4}\}/)?.[1] ?? "";
expect(
handler,
"матчер есть, а редиректа в нём нет — зонд выше прошёл бы вхолостую",
).toMatch(/redir\s+\*\s+\/\s+permanent/);
).toMatch(/redir\s+\*\s+\{uri\}\s+permanent/);
/**
* Query обязана переноситься иначе `/v3?utm=…` теряет атрибуцию у уже
* разосланных ссылок. Один `redir * {uri}` этого не гарантирует: без
* срезания пути он вернул бы Location, равный исходному адресу (цикл).
* Поэтому проверяется вся связка, а не только форма редиректа.
*/
expect(
handler,
"путь не срезается — {uri} вернёт исходный адрес, редирект закольцуется",
).toContain("uri strip_prefix /v3");
expect(handler).toContain("uri strip_prefix /trade-in/mera-public");
});
});

View file

@ -11,7 +11,7 @@ import { FAQ } from "../content";
* Новый дизайн жил на /v3 и 29.08.2026 стал корнем meraocenka.ru. Перед этим
* разведка нашла блоки лэндинга v1, которых в v3 не было вовсе: реестр
* источников, «что вы получите» с дисклеймером 135-ФЗ, плашка покрытия и
* строка «мы не покупаем квартиры» в герое, два вопроса из FAQ, город в форме.
* строка «мы не покупаем квартиры» в герое, вопросы FAQ, город в форме.
* Они перенесены здесь проверяется, что перенос ЖИВОЙ: секции подключены к
* странице, а компоненты читают те же константы, а не копии строк.
*
@ -70,19 +70,62 @@ describe("секции читают константы, а не копии ст
});
});
describe("два вопроса из FAQ v1 живут в возражениях v3", () => {
describe("КАЖДЫЙ вопрос FAQ живёт в возражениях v3", () => {
const objections = read("_components/v3/ObjectionsV3.tsx");
/**
* Проверка ДВУСТОРОННЯЯ: id должен и существовать в FAQ, и упоминаться в
* секции. Односторонняя пропустила бы главную ошибку переименование id, от
* которого `FAQ.find` вернёт undefined, и вопрос молча исчезнет со страницы.
* Список вопросов НЕ ЗАХАРДКОЖЕН он перебирается из самого `FAQ`. В этом
* вся разница с прежней версией проверки: она перечисляла два id вручную и
* потому была зелёной ровно в тот момент, когда `Faq.tsx` (единственный, кто
* рендерил все шесть) удалили вместе с лэндингом v1, а в возражения v3
* перенесли только эти два. Четыре ответа, прошедшие юр-ревью, не
* рендерились нигде, и ни один гейт этого не сказал.
*
* Перебор по `FAQ` краснеет в обе стороны:
* - вопрос убрали из ObjectionsV3 его id в файле нет красный;
* - id переименовали в content.ts в файле остался СТАРЫЙ id, `FAQ.find`
* молча вернёт undefined и вопрос исчезнет со страницы красный;
* - в content.ts добавили седьмой вопрос он обязан быть либо отрисован,
* либо осознанно исключён (тогда правится и этот тест), а не потерян.
*
* Ищем `...fromFaq("id")` со спредом, а не просто id: спред это то, что
* кладёт вопрос в `ITEMS`. Объявление без спреда собралось бы, тайпчек бы
* промолчал, а на странице вопроса не было бы.
*/
it.each(["how-do-you-know", "why-region"])("%s", (id) => {
it("в FAQ вообще есть вопросы — иначе перебор ниже пустой", () => {
expect(FAQ.length).toBeGreaterThan(0);
});
it.each(FAQ.map((f) => f.id))("%s отрисован", (id) => {
expect(
FAQ.map((f) => f.id),
`id пропал из FAQ — ObjectionsV3 отрендерит на один вопрос меньше и никто не заметит`,
).toContain(id);
expect(objections).toContain(`"${id}"`);
objections,
`вопрос "${id}" не попадает в ITEMS — на странице его не будет, и заметить это может только человек`,
).toContain(`...fromFaq("${id}")`);
});
/**
* Отсылка к странице про обработку персональных данных обязана быть ЖИВОЙ
* ССЫЛКОЙ. В `content.ts` она лежит текстом, ObjectionsV3 линкует её по
* дословному совпадению значит переформулировка предложения в content.ts
* тихо превращает ссылку обратно в текст. Проверка сверяет обе стороны.
*/
it("отсылка к странице ПДн отдаётся ссылкой, а не текстом", () => {
const phrase =
objections.match(/const PRIVACY_PHRASE = "([^"]+)"/)?.[1] ?? "";
expect(
phrase,
"зонд сломан: в ObjectionsV3 не найдена константа связующей фразы",
).not.toBe("");
const answer = FAQ.find((f) => f.id === "personal-data")?.a.join(" ") ?? "";
expect(
answer,
"фраза в content.ts переформулирована — ссылка на страницу ПДн молча станет обычным текстом",
).toContain(phrase);
expect(
objections,
"ссылка обязана идти через PublicLink (обычный <a>): next/link подставит basePath",
).toContain('<PublicLink to="privacy"');
});
});

View file

@ -15,6 +15,32 @@ import { PublicLink } from "./PublicLink";
*
* Логотип отсюда ссылка на главную: это единственный обратный путь на
* лэндинг, и он же то, что человек инстинктивно нажимает.
*
* ДЕСКРИПТОРА «ОЦЕНКА ВТОРИЧНОГО ЖИЛЬЯ ПО РЫНОЧНЫМ ДАННЫМ · <РЕГИОН>» ЗДЕСЬ
* НЕТ И ЭТО РЕШЕНИЕ, А НЕ ПОТЕРЯ. Строка жила в `SiteHeader.tsx` (шапка
* лэндинга v1) с формулировкой, о которой просил юрист 14.08.2026: слова «по
* рыночным данным» то, чем сервис отличается от оценки по 135-ФЗ, и они
* должны стоять в заметном дескрипторе, а не только в дисклеймерах внизу.
* 29.08.2026 `SiteHeader` удалён, юр-страницы переведены на эту шапку, и
* строка с них ушла.
*
* Почему она сюда не возвращается:
* - ЭТО ШАПКА НЕ ТОЛЬКО ЮР-СТРАНИЦ. Её же носят `/estimate`, `/articles`,
* `/docs`, у которых дескриптора не было никогда: возврат «на место»
* означал бы новую строку на трёх страницах, которых правка не касается.
* - ТРЕБОВАНИЕ ЮРИСТА БЫЛО ПРО ЛЭНДИНГ про первое, что видит посетитель.
* На корне оно выполняется и сейчас, только не шапкой: `HeroV3` первым же
* экраном пишет «Мера сервис оценки вторичного жилья по рыночным
* данным», и там же стоит граница покрытия.
* - НА САМИХ ЮР-СТРАНИЦАХ ЭТО СКАЗАНО СИЛЬНЕЕ, ЧЕМ В ДЕСКРИПТОРЕ. Оферта
* говорит то же в тексте договора (п. 1.3 «индикативный расчёт
* ориентировочной рыночной стоимости», п. 6.2 «не является отчётом об
* оценке по 135-ФЗ»), а `FooterV3` на каждой из этих страниц несёт и
* рыночную природу оценки с регионом («Оценка квартир на вторичном рынке
* по сделкам и объявлениям. <регион>»), и прямой отказ («не является
* офертой или отчётом об оценке»).
* Появится страница, где ни то ни другое не выполняется, дескриптор нужно
* вернуть ей, а не этой общей шапке.
*/
export function InnerHeader() {
return (

View file

@ -12,17 +12,39 @@
* объявлений с публикации), а не макетной формулировкой про «отметки
* продавцов», механики которых не существует.
*
* ДВА ВОПРОСА ИЗ v1 (`FAQ` в content.ts) ИМПОРТИРУЮТСЯ, А НЕ ПЕРЕПИСАНЫ.
* Макетные шесть возражений про оплату и доверие; в v1 было два вопроса,
* которых среди них нет вовсе, и оба несут границы продукта:
* - `how-do-you-know` как вообще получается цена именно этой квартиры;
* - `why-region` почему одна область, и чем «полное покрытие» отличается
* от «данных меньше».
* Первый стоит ПЕРВЫМ (это вопрос до всяких сомнений об оплате), второй
* последним (география то, с чем человек уходит). Берём их из `FAQ` по id, а
* не копией строк: копия разъедется с ответом на странице /docs и в v1, где
* тот же текст уже прошёл юр-ревью. Если id переименуют, вопрос молча
* исчезнет это гейтится `__tests__/v3-ported-copy.test.ts`.
* ВЕСЬ `FAQ` ИЗ content.ts ИМПОРТИРУЕТСЯ, А НЕ ПЕРЕПИСЫВАЕТСЯ. Так было не
* сразу: при переносе брались только `how-do-you-know` и `why-region`, а
* остальные четыре вопроса v1 (`how-accurate`, `vs-marketplace`,
* `personal-data`, `bank-report`) не рендерились НИГДЕ старый компонент
* `Faq.tsx`, который выводил все шесть, удалён вместе с лэндингом v1.
* Тематически их закрывали макетные возражения, но это были ПЕРЕСКАЗЫ, а
* оригиналы проходили юр-ревью. Две потери были содержательными:
* - `personal-data` единственное место на сайте, где сказано «Имя, паспорт
* и документы на квартиру мы не спрашиваем», и единственная отсылка к
* странице про обработку персональных данных (152-ФЗ). Макетная версия
* оставляла только «отдельную галочку согласия», без ссылки;
* - `how-accurate` «показываем диапазон, а не одно число» как прямой ответ
* на вопрос о точности. После потери рамка диапазона осталась только в
* `AccuracyV3`, рядом с плейсхолдерными числами.
*
* ЧТО СДЕЛАНО. Список ниже один порядок чтения, в который вплетены и все
* шесть вопросов `FAQ` (по id, а не копией строк), и те макетные возражения,
* которым в `FAQ` пары нет вовсе: «откуда вы знаете факт сделки», «почему так
* дёшево», «это подписка». Три остальных макетных возражения про
* калькуляторы, звонки и отчёт для банка УДАЛЕНЫ как пересказы
* `vs-marketplace` / `personal-data` / `bank-report`: держать рядом два ответа
* на один вопрос хуже, чем один, а выбор между пересказом и выверенным
* оригиналом очевиден. По той же причине у «почему так дёшево» убран второй
* абзац он был почти дословным повтором второго абзаца `how-accurate`,
* который теперь стоит следующим вопросом и отвечает на «это точно работает?»
* целиком.
*
* Ни одной строки не написано здесь заново: тексты либо приходят из
* `content.ts`, либо это уже стоявшие в этом файле макетные возражения.
*
* Если id переименуют или вопрос выпадет из списка, он молча исчезнет со
* страницы гейтится `__tests__/v3-ported-copy.test.ts` двусторонне: тест
* перебирает `FAQ` и требует, чтобы КАЖДЫЙ его id был отрисован здесь.
*/
import { FAQ } from "../../content";
@ -35,7 +57,20 @@ interface Objection {
readonly a: readonly string[];
}
const OBJECTIONS: readonly Objection[] = [
/** Вопрос из `FAQ` по id — `[]`, если id переименовали (тест это ловит). */
function fromFaq(id: string): readonly Objection[] {
const item = FAQ.find((f) => f.id === id);
return item ? [{ q: item.q, a: item.a }] : [];
}
/**
* Порядок чтения: как получается цена откуда факт сколько стоит и почему
* столько насколько точно чем отличается от бесплатного как списывают
* деньги что с данными годится ли для банка почему одна область.
* Сомнения про деньги стоят посередине, границы продукта по краям.
*/
const ITEMS: readonly Objection[] = [
...fromFaq("how-do-you-know"),
{
q: "Откуда вы знаете факт сделки?",
a: [
@ -46,48 +81,50 @@ const OBJECTIONS: readonly Objection[] = [
q: "Почему так дёшево — это точно работает?",
a: [
"Дёшево, потому что расчёт автоматический: без выезда, без оценщика, без комиссии. Вы платите за данные, а не за часы специалиста.",
"Точность прямо зависит от того, сколько нашлось сопоставимых объектов, — поэтому мы всегда пишем, на скольких объектах и сделках построен расчёт, и честно сообщаем, если данных по дому мало.",
],
},
{
q: "Чем это лучше бесплатных калькуляторов?",
a: [
"Калькулятор площадки считает по объявлениям этой же площадки — то есть по ценам, которые продавцы просят, а не получают.",
"Мы смотрим сразу несколько площадок и добавляем зарегистрированные сделки, чтобы была видна разница между запрашиваемой и реальной ценой.",
],
},
...fromFaq("how-accurate"),
...fromFaq("vs-marketplace"),
{
q: "Это подписка? Спишут ли ещё раз?",
a: [
"Нет. Это разовый платёж за один отчёт — без подписки, автопродлений и скрытых списаний. Отчёт остаётся у вас по ссылке.",
],
},
{
q: "Мне начнут звонить после проверки?",
a: [
"Нет. Для расчёта нужны адрес и параметры квартиры — телефон мы не спрашиваем. Он появляется, только если вы сами решите оставить заявку, и всегда с отдельной галочкой согласия.",
],
},
{
q: "Это официальная оценка для банка?",
a: [
"Нет. Для ипотеки, суда, опеки и нотариуса нужен отчёт аккредитованного оценщика по 135-ФЗ — это отдельная процедура с выездом. МЕРА отвечает на другой вопрос: за сколько квартира реально продаётся на рынке сегодня.",
],
},
];
/** Вопрос v1 по id — `[]`, если id переименовали (тест это ловит). */
function fromFaq(id: string): readonly Objection[] {
const item = FAQ.find((f) => f.id === id);
return item ? [{ q: item.q, a: item.a }] : [];
}
const ITEMS: readonly Objection[] = [
...fromFaq("how-do-you-know"),
...OBJECTIONS,
...fromFaq("personal-data"),
...fromFaq("bank-report"),
...fromFaq("why-region"),
];
/**
* Отсылка к странице про обработку персональных данных внутри ответа
* `personal-data`. В `content.ts` она лежит обычным текстом (там нет разметки),
* и в старом `Faq.tsx` так текстом и рендерилась то есть 152-ФЗ-документ
* назывался, но открыть его отсюда было нельзя.
*
* Фраза вынесена константой, а не зашита в разметку: это КОПИЯ куска текста из
* `content.ts`, и разъехаться она может молча переформулируют предложение, и
* `indexOf` не найдёт ничего, а ссылка тихо станет обычным текстом. Ровно это
* и проверяет `v3-ported-copy.test.ts`: фраза обязана дословно встречаться в
* ответе `personal-data`.
*/
const PRIVACY_PHRASE = "странице про обработку персональных данных";
/** Абзац ответа: фраза выше отдаётся ссылкой, остальное — как есть. */
function renderAnswer(text: string) {
const at = text.indexOf(PRIVACY_PHRASE);
if (at < 0) return text;
return (
<>
{text.slice(0, at)}
<PublicLink to="privacy" className={styles.objAnswerLink}>
{PRIVACY_PHRASE}
</PublicLink>
{text.slice(at + PRIVACY_PHRASE.length)}
</>
);
}
export function ObjectionsV3() {
return (
<section className={styles.objSection} aria-labelledby="obj-v3-title">
@ -110,7 +147,7 @@ export function ObjectionsV3() {
</summary>
<div className={styles.objAnswer}>
{o.a.map((p) => (
<p key={p.slice(0, 24)}>{p}</p>
<p key={p.slice(0, 24)}>{renderAnswer(p)}</p>
))}
</div>
</details>

View file

@ -2127,6 +2127,20 @@
margin-bottom: 0;
}
/* Ссылка ВНУТРИ предложения (отсылка к странице про обработку персональных
данных в ответе `personal-data`) то же inline-исключение из правила зоны
нажатия, что у .ftrInlineLink: раздувать строку текста было бы хуже. */
.objAnswerLink {
color: var(--b2c-accent-text);
font-weight: 600;
text-decoration: underline;
text-underline-offset: 3px;
}
.objAnswerLink:hover {
color: var(--b2c-ink);
}
/* --- Статьи --------------------------------------------------------------- */
.artSection {
max-width: 1280px;