Этап 1b: сайт

- Страницы обоих брендов из блоков Payload (D-029): главная, цены,
  локации, поддержка, 4 юридические, 404 и 500. Коллекции pages
  (черновики и версии) и faq с местами показа, 12 типов блоков; поля
  sites — почта и Telegram поддержки, часы работы, SEO по умолчанию.
- Карта и глобус рисуются на сервере (d3-geo), контуры во внешнем
  SVG через <use>; выбор локации синхронизирован между картой и сеткой.
- Тексты сайта и юридические черновики на 5 языках: de, fr, es —
  перевод с глоссарием; seed создаёт страницы и FAQ, только если их
  нет, SEED_CONTENT=reset пересоздаёт.
- SEO: title и описания, canonical, hreflang с x-default,
  sitemap.xml и robots.txt на каждый домен, картинки для соцсетей.
- Статическая сборка и пересборка через revalidatePath из хуков
  Payload (D-030); /svc/internal/revalidate для проверки.
- 404 — статичная страница со статусом 404 через proxy; на сайте
  только опубликованные страницы.
- Производительность: Client Hints темы Payload только в админке,
  предзагрузка 2 файлов шрифтов вместо 6; Lighthouse mobile 97–99.
- Контраст --ink-3 по WCAG AA, радиокнопки вместо div role=button.
- Документация: итоги 1b, глоссарий de/fr/es, трактовки юридических
  черновиков подтверждены, переносимые вопросы — в этапах 3e–3h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
andreypr207andClaude Opus 5.5 committed 2026-10-22 04:53:15 +02:00
1 parent 515acfb69d
commit 20491d1ba4
134 files changed
+11232 -212

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
/**
* Подстановки в текстах блоков: {priceFirst}, {countries} и т. п. (D-029).
* Неизвестная подстановка остаётся как есть — так ошибку в админке видно на странице.
*/
export type Placeholders = Partial<Record<PlaceholderName, string>>
export const PLACEHOLDER_NAMES = [
'priceFirst',
'priceRenewal',
'priceMonth',
'priceYear',
'pricePerMonth',
'savings',
'countries',
'devices',
] as const
export type PlaceholderName = (typeof PLACEHOLDER_NAMES)[number]
export const fillPlaceholders = (text: string, values: Placeholders): string =>
text.replace(/\{(\w+)\}/g, (match, name: string) => values[name as PlaceholderName] ?? match)