feat: OG-image, логотип, компактный hero, кнопка отзыва consent

- public/logo-mark.svg (знак, 512×512) и public/logo.svg (знак+надпись,
  640×160). Копии для пользователя — E:/Projects/anotherreflections-logo*.{svg,png}
- public/og-image.png 1200×630 — тематический баннер с лого, заголовком
  градиентом и подписью. og:image + twitter:summary_large_image в meta,
  расшаривание в Telegram/VK/WhatsApp/Twitter получит превью
- scripts/build-og-image.mjs — пересоздание баннера через sharp (devdep)
- .hero.hero-compact — внутренние страницы /miry/, /kontakty/, /privacy/,
  /category/* перешли на компактный hero (меньше padding, без курсивного
  tagline). На главной hero остался прежний — entry point
- На /privacy/ кнопка «Отозвать согласие» — ставит ar-consent=deny
  одним кликом (152-ФЗ: отозвать должно быть так же просто, как дать)
- Описание Главного форума: «Архивный» → «Действующий форум проекта»
This commit is contained in:
2026-05-21 02:14:49 +03:00
parent 6f2cfdd63d
commit 4319759d88
14 changed files with 277 additions and 12 deletions

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env node
// Один раз: генерирует public/og-image.png 1200x630 для расшаривания
// в Telegram/VK/WhatsApp/Twitter. Требует `npm i -D sharp` (один раз).
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import sharp from 'sharp';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, '..');
const SVG = `
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">
<defs>
<radialGradient id="bg" cx="50%" cy="30%" r="70%">
<stop offset="0" stop-color="#1a1430"/>
<stop offset="0.7" stop-color="#07090f"/>
<stop offset="1" stop-color="#000000"/>
</radialGradient>
<linearGradient id="mark" x1="0" y1="0" x2="100%" y2="100%">
<stop offset="0" stop-color="#b794ff"/>
<stop offset="1" stop-color="#6c4ed4"/>
</linearGradient>
<linearGradient id="title" x1="0" y1="0" x2="0" y2="100%">
<stop offset="0" stop-color="#ffffff"/>
<stop offset="1" stop-color="#b794ff"/>
</linearGradient>
<!-- Звёзды -->
<radialGradient id="star" cx="50%" cy="50%" r="50%">
<stop offset="0" stop-color="#ffffff" stop-opacity="0.9"/>
<stop offset="1" stop-color="#ffffff" stop-opacity="0"/>
</radialGradient>
</defs>
<rect width="1200" height="630" fill="url(#bg)"/>
<!-- Stars -->
<circle cx="180" cy="110" r="1.5" fill="url(#star)"/>
<circle cx="320" cy="180" r="1" fill="url(#star)"/>
<circle cx="480" cy="80" r="1.2" fill="url(#star)"/>
<circle cx="720" cy="140" r="1" fill="url(#star)"/>
<circle cx="900" cy="90" r="1.6" fill="url(#star)"/>
<circle cx="1080" cy="200" r="1.2" fill="url(#star)"/>
<circle cx="160" cy="460" r="1" fill="url(#star)"/>
<circle cx="380" cy="540" r="1.2" fill="url(#star)"/>
<circle cx="620" cy="500" r="1" fill="url(#star)"/>
<circle cx="860" cy="540" r="1.6" fill="url(#star)"/>
<circle cx="1040" cy="460" r="1.2" fill="url(#star)"/>
<!-- Mark в центре -->
<g transform="translate(600 195) scale(3.6)">
<g transform="translate(-32 -32)">
<circle cx="32" cy="32" r="29" stroke="url(#mark)" stroke-width="1.2" opacity="0.45" fill="none"/>
<path d="M16 40 Q32 18 48 40" stroke="url(#mark)" stroke-width="1.6" stroke-linecap="round" fill="none"/>
<path d="M16 28 Q32 50 48 28" stroke="url(#mark)" stroke-width="1.6" stroke-linecap="round" fill="none" opacity="0.6"/>
<circle cx="32" cy="34" r="2" fill="#b794ff"/>
</g>
</g>
<!-- Title -->
<text x="600" y="430" font-family="Cormorant Garamond, Lora, Georgia, serif" font-size="92" font-weight="600" text-anchor="middle" fill="url(#title)" letter-spacing="-0.01em">
Иные Отражения
</text>
<!-- Tagline -->
<text x="600" y="490" font-family="Cormorant Garamond, Lora, Georgia, serif" font-size="28" font-style="italic" text-anchor="middle" fill="#a89bc4">
Ролевой проект по современной фантастике
</text>
<!-- Eyebrow -->
<text x="600" y="560" font-family="Inter, sans-serif" font-size="18" text-anchor="middle" fill="#8a93a8" letter-spacing="0.4em">
С 2006 ГОДА
</text>
</svg>
`;
const outPng = path.join(ROOT, 'public/og-image.png');
const outSvg = path.join(ROOT, 'public/og-image.svg');
fs.writeFileSync(outSvg, SVG.trim() + '\n', 'utf8');
console.log(`wrote ${outSvg}`);
await sharp(Buffer.from(SVG))
.resize(1200, 630)
.png({ compressionLevel: 9 })
.toFile(outPng);
const stat = fs.statSync(outPng);
console.log(`wrote ${outPng}${(stat.size / 1024).toFixed(1)} KB`);