feat(landing): OG-картинка и метаданные
opengraph-image и twitter-image 1200×630 через next/og: знак, заголовок, штамп «ЧТ · обжарка», лента недели с ЧТ/ПТ красным, без фото и дат. Статичные TTF Golos Text и Martian Mono для Satori. Метаданные из site.meta: title, description, openGraph, twitter, metadataBase kvarta.unuchak.com, canonical. Иконка — SVG-знак из семи штрихов.
This commit is contained in:
@@ -552,5 +552,6 @@ const martian = Martian_Mono({ subsets: ['cyrillic', 'latin'], variable: '--font
|
|||||||
│ kvarta.unuchak.com +375 29 000-00-00 │ Martian 20px muted
|
│ kvarta.unuchak.com +375 29 000-00-00 │ Martian 20px muted
|
||||||
└────────────────────────────────────────────────────────────┘
|
└────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
- Тексты из site.ts: заголовок — `site.hero.title`, штамп — `site.hero.stamp`, подписи ленты — `site.freshness.days`, `alt` — `site.meta.ogTitle` (скетч выше — композиция, не текст).
|
||||||
- Поля 64px. Без фото и без дат (картинка кешируется), только дни недели.
|
- Поля 64px. Без фото и без дат (картинка кешируется), только дни недели.
|
||||||
- Satori не поддерживает SVG-фильтры и variable-шрифты: штамп — двойная рамка без «краски», шрифты — статичные TTF (Golos Text 900 и 500, Martian Mono 700), загружаются `fetch` из файлов в `src/app/og-fonts/` (подмножество кириллица+латиница). `alt`: «Кварта — свежая обжарка в Минске, обжарка по четвергам».
|
- Satori не поддерживает SVG-фильтры и variable-шрифты: штамп — двойная рамка без «краски», шрифты — статичные TTF (Golos Text 900 и 500, Martian Mono 700), загружаются `fetch` из файлов в `src/app/og-fonts/` (подмножество кириллица+латиница). `alt`: «Кварта — свежая обжарка в Минске, обжарка по четвергам».
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" fill="#f3ebdd"/><g fill="#2a1810"><rect x="4" y="6" width="2.4" height="20"/><rect x="7.8" y="6" width="2.4" height="20"/><rect x="11.6" y="6" width="2.4" height="20"/><rect x="19.2" y="6" width="2.4" height="20"/><rect x="23" y="6" width="2.4" height="20"/><rect x="26.8" y="6" width="2.4" height="20"/></g><rect x="15.4" y="6" width="2.4" height="20" fill="#b3221b"/></svg>
|
||||||
|
After Width: | Height: | Size: 465 B |
@@ -11,8 +11,23 @@ const golos = Golos_Text({ subsets: ['cyrillic', 'latin'], variable: '--font-gol
|
|||||||
const martian = Martian_Mono({ subsets: ['cyrillic', 'latin'], variable: '--font-martian', axes: ['wdth'], display: 'swap' });
|
const martian = Martian_Mono({ subsets: ['cyrillic', 'latin'], variable: '--font-martian', axes: ['wdth'], display: 'swap' });
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
metadataBase: new URL('https://kvarta.unuchak.com'),
|
||||||
title: site.meta.title,
|
title: site.meta.title,
|
||||||
description: site.meta.description,
|
description: site.meta.description,
|
||||||
|
alternates: { canonical: '/' },
|
||||||
|
openGraph: {
|
||||||
|
type: 'website',
|
||||||
|
locale: 'ru_RU',
|
||||||
|
url: '/',
|
||||||
|
siteName: site.footer.name,
|
||||||
|
title: site.meta.ogTitle,
|
||||||
|
description: site.meta.ogDescription,
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: 'summary_large_image',
|
||||||
|
title: site.meta.ogTitle,
|
||||||
|
description: site.meta.ogDescription,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const viewport: Viewport = {
|
export const viewport: Viewport = {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,101 @@
|
|||||||
|
// OG 1200×630 по DESIGN.md: знак и слово, заголовок, штамп, лента недели ПН–ВС с ЧТ/ПТ красным.
|
||||||
|
// Без фото и без дат: картинка кешируется. Satori не умеет SVG-фильтры и variable-шрифты —
|
||||||
|
// штамп без «краски», шрифты — статичные TTF из ./og-fonts.
|
||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { ImageResponse } from 'next/og';
|
||||||
|
import { CONTACT_PHONE } from '@/content/form';
|
||||||
|
import { site } from '@/content/site';
|
||||||
|
|
||||||
|
export const alt = site.meta.ogTitle;
|
||||||
|
export const size = { width: 1200, height: 630 };
|
||||||
|
export const contentType = 'image/png';
|
||||||
|
|
||||||
|
const C = {
|
||||||
|
cream: '#f3ebdd',
|
||||||
|
ink: '#2a1810',
|
||||||
|
muted: '#5e4636',
|
||||||
|
rule: '#c9b595',
|
||||||
|
cherry: '#b3221b',
|
||||||
|
};
|
||||||
|
|
||||||
|
const DAYS = ['ПН', 'ВТ', 'СР', 'ЧТ', 'ПТ', 'СБ', 'ВС'];
|
||||||
|
|
||||||
|
export default async function OpengraphImage() {
|
||||||
|
const dir = join(process.cwd(), 'src/app/og-fonts');
|
||||||
|
const font = (file: string) => readFile(join(dir, file));
|
||||||
|
const [golos900, golos500, martian700] = await Promise.all([font('golos-900.ttf'), font('golos-500.ttf'), font('martian-700.ttf')]);
|
||||||
|
const [roast, delivery, cup] = site.freshness.days;
|
||||||
|
|
||||||
|
return new ImageResponse(
|
||||||
|
(
|
||||||
|
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', background: C.cream, color: C.ink, padding: 64, fontFamily: 'Golos Text' }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 18 }}>
|
||||||
|
<div style={{ display: 'flex', gap: 7 }}>
|
||||||
|
{DAYS.map((d, i) => (
|
||||||
|
<div key={d} style={{ width: 7, height: 44, background: i === 3 ? C.cherry : C.ink }} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 56, fontWeight: 900, letterSpacing: -1 }}>{site.footer.name}</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 32, fontSize: 76, fontWeight: 900, lineHeight: 0.95, letterSpacing: -2.5, maxWidth: 760 }}>
|
||||||
|
{site.hero.title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
marginTop: 24,
|
||||||
|
padding: 6,
|
||||||
|
border: `5px solid ${C.cherry}`,
|
||||||
|
transform: 'rotate(-6deg)',
|
||||||
|
opacity: 0.92,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', border: `2px solid ${C.cherry}`, padding: '18px 30px', color: C.cherry, fontFamily: 'Martian Mono' }}>
|
||||||
|
<div style={{ fontSize: 30, letterSpacing: 2 }}>{site.hero.stamp.toUpperCase()}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', marginTop: 'auto' }}>
|
||||||
|
<div style={{ display: 'flex', borderBottom: `2px solid ${C.ink}` }}>
|
||||||
|
{DAYS.map((d, i) => {
|
||||||
|
const hot = i === 3 || i === 4;
|
||||||
|
return (
|
||||||
|
<div key={d} style={{ display: 'flex', flex: 1, flexDirection: 'column', paddingBottom: 14, paddingLeft: i === 0 ? 0 : 16, borderLeft: i === 0 ? 'none' : `1px solid ${C.rule}` }}>
|
||||||
|
<div style={{ fontSize: 30, fontWeight: 900, color: hot ? C.cherry : C.ink }}>{d}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', height: 76, fontFamily: 'Martian Mono', fontSize: 18, color: C.cherry, letterSpacing: 1 }}>
|
||||||
|
<div style={{ display: 'flex', flex: 3 }} />
|
||||||
|
<div style={{ display: 'flex', flex: 1, paddingLeft: 16, paddingTop: 14 }}>{roast?.label.toUpperCase()}</div>
|
||||||
|
<div style={{ display: 'flex', flex: 3, flexDirection: 'column', paddingLeft: 16, paddingTop: 14 }}>
|
||||||
|
<div style={{ display: 'flex' }}>{delivery?.label.toUpperCase()}</div>
|
||||||
|
<div style={{ display: 'flex', marginTop: 10, height: 10, border: `2px solid ${C.cherry}`, borderTop: 'none' }} />
|
||||||
|
<div style={{ display: 'flex', marginTop: 8 }}>
|
||||||
|
{cup ? `${cup.day} · ${cup.label}`.toUpperCase() : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 22, fontFamily: 'Martian Mono', fontSize: 20, color: C.muted }}>
|
||||||
|
<div>kvarta.unuchak.com</div>
|
||||||
|
<div>{CONTACT_PHONE.display}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
...size,
|
||||||
|
fonts: [
|
||||||
|
{ name: 'Golos Text', data: golos900, weight: 900, style: 'normal' },
|
||||||
|
{ name: 'Golos Text', data: golos500, weight: 500, style: 'normal' },
|
||||||
|
{ name: 'Martian Mono', data: martian700, weight: 700, style: 'normal' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default, alt, size, contentType } from './opengraph-image';
|
||||||
Reference in New Issue
Block a user