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

35
package-lock.json generated
View File

@@ -14,7 +14,8 @@
"sanitize-html": "^2.17.0" "sanitize-html": "^2.17.0"
}, },
"devDependencies": { "devDependencies": {
"@types/sanitize-html": "^2.16.0" "@types/sanitize-html": "^2.16.0",
"sharp": "^0.34.5"
}, },
"engines": { "engines": {
"node": ">=22.12.0" "node": ">=22.12.0"
@@ -204,6 +205,7 @@
"version": "1.10.0", "version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@@ -630,8 +632,8 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"optional": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -643,6 +645,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -665,6 +668,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -687,6 +691,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -703,6 +708,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -719,6 +725,7 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -735,6 +742,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -751,6 +759,7 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -767,6 +776,7 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -783,6 +793,7 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -799,6 +810,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -815,6 +827,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -831,6 +844,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -847,6 +861,7 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -869,6 +884,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -891,6 +907,7 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -913,6 +930,7 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -935,6 +953,7 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -957,6 +976,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -979,6 +999,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1001,6 +1022,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1023,6 +1045,7 @@
"cpu": [ "cpu": [
"wasm32" "wasm32"
], ],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@@ -1042,6 +1065,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later", "license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1061,6 +1085,7 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later", "license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1080,6 +1105,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later", "license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2090,8 +2116,8 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -4304,9 +4330,9 @@
"version": "0.34.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true,
"dependencies": { "dependencies": {
"@img/colour": "^1.0.0", "@img/colour": "^1.0.0",
"detect-libc": "^2.1.2", "detect-libc": "^2.1.2",
@@ -4541,6 +4567,7 @@
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD", "license": "0BSD",
"optional": true "optional": true
}, },

View File

@@ -14,12 +14,13 @@
"migrate": "node scripts/migrate-wp.mjs" "migrate": "node scripts/migrate-wp.mjs"
}, },
"dependencies": { "dependencies": {
"astro": "^6.3.6",
"@astrojs/rss": "^4.0.12", "@astrojs/rss": "^4.0.12",
"@astrojs/sitemap": "^3.6.0", "@astrojs/sitemap": "^3.6.0",
"astro": "^6.3.6",
"sanitize-html": "^2.17.0" "sanitize-html": "^2.17.0"
}, },
"devDependencies": { "devDependencies": {
"@types/sanitize-html": "^2.16.0" "@types/sanitize-html": "^2.16.0",
"sharp": "^0.34.5"
} }
} }

12
public/logo-mark.svg Normal file
View File

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 64 64" fill="none">
<defs>
<linearGradient id="lm-grad" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#b794ff"/>
<stop offset="1" stop-color="#6c4ed4"/>
</linearGradient>
</defs>
<circle cx="32" cy="32" r="29" stroke="url(#lm-grad)" stroke-width="1.5" opacity="0.45"/>
<path d="M16 40 Q32 18 48 40" stroke="url(#lm-grad)" stroke-width="2" stroke-linecap="round" fill="none"/>
<path d="M16 28 Q32 50 48 28" stroke="url(#lm-grad)" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.6"/>
<circle cx="32" cy="34" r="2" fill="#b794ff"/>
</svg>

After

Width:  |  Height:  |  Size: 706 B

26
public/logo.svg Normal file
View File

@@ -0,0 +1,26 @@
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="160" viewBox="0 0 640 160" fill="none">
<defs>
<linearGradient id="l-grad" x1="0" y1="0" x2="160" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#b794ff"/>
<stop offset="1" stop-color="#6c4ed4"/>
</linearGradient>
<linearGradient id="l-text" x1="0" y1="0" x2="0" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#ffffff"/>
<stop offset="1" stop-color="#b794ff"/>
</linearGradient>
</defs>
<!-- Mark -->
<g transform="translate(28 32) scale(1.5)">
<circle cx="32" cy="32" r="29" stroke="url(#l-grad)" stroke-width="1.5" opacity="0.45"/>
<path d="M16 40 Q32 18 48 40" stroke="url(#l-grad)" stroke-width="2" stroke-linecap="round" fill="none"/>
<path d="M16 28 Q32 50 48 28" stroke="url(#l-grad)" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.6"/>
<circle cx="32" cy="34" r="2" fill="#b794ff"/>
</g>
<!-- Text -->
<text x="160" y="90" font-family="Cormorant Garamond, Lora, Georgia, serif" font-size="48" font-weight="600" fill="url(#l-text)" letter-spacing="0.02em">
Иные Отражения
</text>
<text x="160" y="120" font-family="Inter, sans-serif" font-size="14" fill="#8a93a8" letter-spacing="0.32em">
РОЛЕВОЙ ПРОЕКТ · С 2006
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/og-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

62
public/og-image.svg Normal file
View File

@@ -0,0 +1,62 @@
<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>

After

Width:  |  Height:  |  Size: 2.7 KiB

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`);

View File

@@ -122,7 +122,7 @@ export const WORLDS: World[] = [
{ {
name: 'Главный форум', name: 'Главный форум',
tag: 'Общая площадка', tag: 'Общая площадка',
desc: 'Архивный форум проекта со всеми мирами в одном месте.', desc: 'Действующий форум проекта — общие темы, объявления и обсуждения миров в одном месте.',
url: 'https://forum.anotherreflections.ru/', url: 'https://forum.anotherreflections.ru/',
color: 'var(--c-news)', color: 'var(--c-news)',
}, },

View File

@@ -35,6 +35,14 @@ const year = new Date().getFullYear();
<meta property="og:url" content={canonical} /> <meta property="og:url" content={canonical} />
<meta property="og:site_name" content={SITE_TITLE} /> <meta property="og:site_name" content={SITE_TITLE} />
<meta property="og:locale" content="ru_RU" /> <meta property="og:locale" content="ru_RU" />
<meta property="og:image" content={new URL('/og-image.png', SITE_URL).toString()} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content={`${SITE_TITLE} — ${SITE_DESCRIPTION}`} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={pageTitle} />
<meta name="twitter:description" content={pageDesc} />
<meta name="twitter:image" content={new URL('/og-image.png', SITE_URL).toString()} />
<link rel="alternate" type="application/rss+xml" title={`${SITE_TITLE} — RSS`} href="/feed.xml" /> <link rel="alternate" type="application/rss+xml" title={`${SITE_TITLE} — RSS`} href="/feed.xml" />

View File

@@ -29,7 +29,7 @@ const fmtDate = (d: Date) =>
d.toLocaleDateString('ru-RU', { year: 'numeric', month: 'long', day: 'numeric' }); d.toLocaleDateString('ru-RU', { year: 'numeric', month: 'long', day: 'numeric' });
--- ---
<BaseLayout title={`Категория: ${name}`}> <BaseLayout title={`Категория: ${name}`}>
<section class="hero" style={`padding: 3rem 1rem 2rem; --cat-color: ${catColor};`}> <section class="hero hero-compact" style={`--cat-color: ${catColor};`}>
<span class="hero-eyebrow" style={`color: ${catColor}; border-color: ${catColor};`}>Категория</span> <span class="hero-eyebrow" style={`color: ${catColor}; border-color: ${catColor};`}>Категория</span>
<h1 style={`background: linear-gradient(180deg, #ffffff 0%, ${catColor} 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;`}>{name}</h1> <h1 style={`background: linear-gradient(180deg, #ffffff 0%, ${catColor} 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;`}>{name}</h1>
<p class="hero-tagline">{sorted.length} {plural(sorted.length, ['публикация', 'публикации', 'публикаций'])} · <a href={`/category/${Astro.params.slug}/feed.xml`}>RSS этой категории</a></p> <p class="hero-tagline">{sorted.length} {plural(sorted.length, ['публикация', 'публикации', 'публикаций'])} · <a href={`/category/${Astro.params.slug}/feed.xml`}>RSS этой категории</a></p>

View File

@@ -6,7 +6,7 @@ import { CONTACT_EMAIL, SOCIAL } from '../consts';
title="Контакты" title="Контакты"
description={`Связаться с проектом «Иные Отражения»: ${CONTACT_EMAIL}, Telegram, ВКонтакте.`} description={`Связаться с проектом «Иные Отражения»: ${CONTACT_EMAIL}, Telegram, ВКонтакте.`}
> >
<section class="hero" style="padding: 4rem 1rem 2rem;"> <section class="hero hero-compact">
<span class="hero-eyebrow">На связи</span> <span class="hero-eyebrow">На связи</span>
<h1>Контакты</h1> <h1>Контакты</h1>
<p class="hero-tagline"> <p class="hero-tagline">

View File

@@ -6,7 +6,7 @@ import { WORLDS } from '../consts';
title="Миры" title="Миры"
description="Восемь игровых вселенных проекта «Иные Отражения» — Дозоры, Амбер, Киндрет, Над бездной, Глубина, Ренессанс, Warhammer 40k." description="Восемь игровых вселенных проекта «Иные Отражения» — Дозоры, Амбер, Киндрет, Над бездной, Глубина, Ренессанс, Warhammer 40k."
> >
<section class="hero" style="padding: 4rem 1rem 2.5rem;"> <section class="hero hero-compact">
<span class="hero-eyebrow">Игровые проекты</span> <span class="hero-eyebrow">Игровые проекты</span>
<h1>Миры</h1> <h1>Миры</h1>
<p class="hero-tagline"> <p class="hero-tagline">

View File

@@ -71,8 +71,10 @@ const lastUpdated = '20 мая 2026 г.';
<h2>5. Ваши права</h2> <h2>5. Ваши права</h2>
<p>Вы можете в любой момент:</p> <p>Вы можете в любой момент:</p>
<ul> <ul>
<li>Отозвать согласие на статистику — очистите cookies сайта или удалите <code>ar-consent</code> <li>
в DevTools. При следующем визите снова появится уведомление, выберите «Только необходимые».</li> <strong>Отозвать согласие на статистику</strong> — нажмите кнопку ниже.
Ваш выбор сохранится, при следующих визитах статистика загружаться не будет.
</li>
<li>Запросить удаление ваших данных из систем статистики — обратитесь напрямую к операторам <li>Запросить удаление ваших данных из систем статистики — обратитесь напрямую к операторам
(<a href="https://yandex.ru/support/metrica/" target="_blank" rel="noopener">Яндекс</a>, (<a href="https://yandex.ru/support/metrica/" target="_blank" rel="noopener">Яндекс</a>,
<a href="https://support.google.com/analytics/" target="_blank" rel="noopener">Google</a>).</li> <a href="https://support.google.com/analytics/" target="_blank" rel="noopener">Google</a>).</li>
@@ -80,6 +82,28 @@ const lastUpdated = '20 мая 2026 г.';
по любым вопросам, связанным с данными.</li> по любым вопросам, связанным с данными.</li>
</ul> </ul>
<p style="margin: 1.5em 0 2em;">
<button type="button" class="cookie-btn cookie-btn-primary" id="revoke-consent-btn">
Отозвать согласие на статистику
</button>
<span id="revoke-status" style="margin-left: 1em; color: var(--fg-muted); font-size: .9em;"></span>
</p>
<script is:inline>
(() => {
const btn = document.getElementById('revoke-consent-btn');
const status = document.getElementById('revoke-status');
if (!btn) return;
btn.addEventListener('click', () => {
try { localStorage.setItem('ar-consent', 'deny'); } catch {}
document.cookie = 'ar-consent=deny; expires=' +
new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toUTCString() +
'; path=/; SameSite=Lax';
if (status) status.textContent = '✓ Согласие отозвано. Перезагрузите страницу.';
btn.disabled = true;
});
})();
</script>
<h2>6. Изменения</h2> <h2>6. Изменения</h2>
<p> <p>
Мы можем обновлять эту Политику. Актуальная версия всегда доступна по адресу Мы можем обновлять эту Политику. Актуальная версия всегда доступна по адресу

View File

@@ -223,6 +223,22 @@ pre {
margin: 0 0 3rem; margin: 0 0 3rem;
position: relative; position: relative;
} }
.hero.hero-compact {
padding: 2.2rem 1rem 1.5rem;
margin: 0 0 2rem;
}
.hero.hero-compact h1 {
font-size: clamp(2rem, 4vw, 2.6rem);
}
.hero.hero-compact .hero-tagline {
font-size: 1rem;
margin-bottom: 0;
}
.hero.hero-compact .hero-eyebrow {
margin-bottom: .9rem;
padding: .3em 1em;
font-size: .72rem;
}
.hero-eyebrow { .hero-eyebrow {
display: inline-block; display: inline-block;
font-size: .8rem; font-size: .8rem;