fix(og): rehber kapak başlığı taşmıyor, İ/ş/ğ doğru yazı tipinden geliyor

İki kök neden: rakamlı başlıklarda karışık flex span satır kaydıramıyordu
(başlık artık kelime kelime kutulanıyor); Satori aynı aile adıyla kaydedilen
latin/latin-ext dosyalarından yalnızca birini kullanıyordu (ext alt-kümeleri
'<ad> Ext' adıyla kayıtlı, şablonlar OG_AILE virgüllü aile listesini veriyor).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-24 17:42:29 +03:00
parent 9d90436027
commit 6094066c5e
4 changed files with 400 additions and 370 deletions

View File

@@ -1,5 +1,5 @@
import { ImageResponse } from "next/og";
import { loadSiteOgFonts } from "@/lib/og-fonts";
import { loadSiteOgFonts, OG_AILE } from "@/lib/og-fonts";
export const alt =
"KolayTercih — YKS tercihin, gerçek YÖK Atlas verisiyle. Sıralamanı gir, dengeli 24 tercihlik listeni al.";
@@ -10,7 +10,6 @@ export default async function OpenGraphImage() {
const fonts = await loadSiteOgFonts();
return new ImageResponse(
(
<div
style={{
display: "flex",
@@ -34,7 +33,7 @@ export default async function OpenGraphImage() {
style={{
display: "flex",
alignItems: "center",
fontFamily: "Bricolage Grotesque",
fontFamily: OG_AILE.bricolage,
letterSpacing: "-0.05em",
color: "#0f172a",
}}
@@ -59,7 +58,7 @@ export default async function OpenGraphImage() {
style={{
display: "flex",
marginTop: 28,
fontFamily: "Clarity City",
fontFamily: OG_AILE.clarity,
fontSize: 34,
fontWeight: 600,
lineHeight: 1.25,
@@ -74,7 +73,7 @@ export default async function OpenGraphImage() {
style={{
display: "flex",
marginTop: 16,
fontFamily: "Clarity City",
fontFamily: OG_AILE.clarity,
fontSize: 26,
fontWeight: 300,
lineHeight: 1.3,
@@ -85,8 +84,7 @@ export default async function OpenGraphImage() {
Sıralamanı gir · Dengeli 24 tercihlik listeni al
</div>
</div>
</div>
),
</div>,
{ ...size, fonts },
);
}

View File

@@ -1,6 +1,6 @@
import { ImageResponse } from "next/og";
import { getRehber } from "@/lib/rehber";
import { loadRehberOgFonts } from "@/lib/og-fonts";
import { loadRehberOgFonts, OG_AILE } from "@/lib/og-fonts";
import {
rehberBasliginiBol,
rehberMetniniParcala,
@@ -19,10 +19,30 @@ export default async function RehberOpenGraphImage({
const yazi = getRehber(slug);
const baslik = yazi?.baslik ?? "Tercih Rehberi";
const { vurgu, kalan } = rehberBasliginiBol(baslik);
const kelimeler = [
...vurgu
.split(/\s+/)
.filter(Boolean)
.map((metin) => ({ metin, vurgu: true })),
...kalan
.split(/\s+/)
.filter(Boolean)
.map((metin) => ({ metin, vurgu: false })),
];
// Kademeler sayfadaki RehberKapak ile aynı mantık; 90+ karakter için bir
// kademe daha var — 1000px genişlikte üç satırı aşmasın.
const baslikBoyutu =
baslik.length > 90
? 44
: baslik.length > 70
? 48
: baslik.length > 52
? 54
: 62;
const kelimeAraligi = Math.round(baslikBoyutu * 0.26);
const fonts = await loadRehberOgFonts();
return new ImageResponse(
(
<div
style={{
position: "relative",
@@ -32,18 +52,34 @@ export default async function RehberOpenGraphImage({
overflow: "hidden",
background: "white",
color: "#2563eb",
fontFamily: "Bricolage Grotesque",
fontFamily: OG_AILE.bricolage,
}}
>
{[
[960, 50, 0.16], [980, 50, 0.28], [1020, 50, 0.12],
[940, 70, 0.24], [980, 70, 0.12], [1000, 70, 0.3],
[1060, 70, 0.18], [960, 90, 0.12], [1000, 90, 0.2],
[1040, 90, 0.1], [1080, 90, 0.26], [1080, 110, 0.14],
[1120, 110, 0.22], [1140, 110, 0.12], [60, 490, 0.12],
[100, 490, 0.25], [120, 490, 0.14], [40, 510, 0.24],
[80, 510, 0.12], [120, 510, 0.3], [160, 510, 0.16],
[60, 530, 0.14], [100, 530, 0.22], [140, 530, 0.1],
[960, 50, 0.16],
[980, 50, 0.28],
[1020, 50, 0.12],
[940, 70, 0.24],
[980, 70, 0.12],
[1000, 70, 0.3],
[1060, 70, 0.18],
[960, 90, 0.12],
[1000, 90, 0.2],
[1040, 90, 0.1],
[1080, 90, 0.26],
[1080, 110, 0.14],
[1120, 110, 0.22],
[1140, 110, 0.12],
[60, 490, 0.12],
[100, 490, 0.25],
[120, 490, 0.14],
[40, 510, 0.24],
[80, 510, 0.12],
[120, 510, 0.3],
[160, 510, 0.16],
[60, 530, 0.14],
[100, 530, 0.22],
[140, 530, 0.1],
[180, 530, 0.27],
].map(([left, top, opacity], i) => (
<span
@@ -110,7 +146,7 @@ export default async function RehberOpenGraphImage({
style={{
display: "flex",
alignItems: "center",
fontFamily: "Clarity City",
fontFamily: OG_AILE.clarity,
fontSize: 20,
fontWeight: 700,
letterSpacing: "0.18em",
@@ -121,35 +157,42 @@ export default async function RehberOpenGraphImage({
<span>Tercih Rehberi</span>
</div>
{/*
Satori satır kaydırmayı yalnızca düz metin düğümlerinde yapar;
rakam+metin karışık bir flex span tek satırda kalır ve parçalar
üst üste biner (2026'lı başlıklarda görülen bozulma). Bu yüzden
başlık kelime kelime ayrı kutulara bölünür: kaydırma kelime
sınırında olur, her kelime kendi rengini/yazı tipini taşır.
*/}
<div
style={{
display: "flex",
flexWrap: "wrap",
maxWidth: 980,
fontFamily: "Cal Sans",
fontSize: baslik.length > 70 ? 48 : baslik.length > 52 ? 54 : 62,
fontWeight: 400,
lineHeight: 1.02,
letterSpacing: "-0.025em",
maxWidth: 1000,
fontSize: baslikBoyutu,
lineHeight: 1.06,
color: "#2563eb",
}}
>
<span
style={{
display: "flex",
fontFamily: "Bricolage Grotesque",
color: "#ff5a00",
fontWeight: 600,
letterSpacing: "-0.04em",
}}
>
{rehberMetniniParcala(vurgu).map((parca, i) =>
/^\d/.test(parca) ? (
{kelimeler.map((kelime, i) => (
<span
key={i}
style={{
display: "flex",
fontFamily: "Bricolage Grotesque",
marginRight: kelimeAraligi,
fontFamily: kelime.vurgu ? OG_AILE.bricolage : OG_AILE.calSans,
fontWeight: kelime.vurgu ? 600 : 400,
letterSpacing: kelime.vurgu ? "-0.04em" : "-0.025em",
color: kelime.vurgu ? "#ff5a00" : "#2563eb",
}}
>
{rehberMetniniParcala(kelime.metin).map((parca, j) =>
/^\d/.test(parca) ? (
<span
key={j}
style={{
display: "flex",
fontFamily: OG_AILE.bricolage,
fontStyle: "italic",
fontWeight: 800,
}}
@@ -161,32 +204,7 @@ export default async function RehberOpenGraphImage({
),
)}
</span>
{kalan ? (
<span
style={{
display: "flex",
marginLeft: /^\s/.test(kalan) ? 12 : 0,
}}
>
{rehberMetniniParcala(kalan.trimStart()).map((parca, i) =>
/^\d/.test(parca) ? (
<span
key={i}
style={{
display: "flex",
fontFamily: "Bricolage Grotesque",
fontStyle: "italic",
fontWeight: 800,
}}
>
{parca}
</span>
) : (
parca
),
)}
</span>
) : null}
))}
</div>
<div
@@ -210,7 +228,7 @@ export default async function RehberOpenGraphImage({
style={{
display: "flex",
marginLeft: 2,
fontFamily: "Bricolage Grotesque",
fontFamily: OG_AILE.bricolage,
fontSize: 24,
letterSpacing: "-0.05em",
color: "#2563eb",
@@ -221,8 +239,7 @@ export default async function RehberOpenGraphImage({
</span>
</div>
</div>
</div>
),
</div>,
{ ...size, fonts },
);
}

View File

@@ -21,7 +21,24 @@ type OgFont = {
style: "normal" | "italic";
};
/** latin + latin-ext çiftini aynı aile/ağırlıkta kaydeder (Türkçe glifler için). */
/**
* Satori aynı aile adı + ağırlık için TEK dosya seçer (`get()` en iyi eşleşen
* tek kaydı döndürür); iki alt-kümeyi aynı adla kaydetmek latin-ext dosyasını
* fiilen devre dışı bırakıyordu — İ/ş/ğ gibi glifler başka bir aileden
* (kayıt sırasına göre Clarity City) geliyor, ölçüm ile çizim ayrışıyor ve
* "İ" ile başlayan kelimeden sonraki boşluk yutuluyordu. Bu yüzden ext
* alt-kümesi `<ad> Ext` adıyla ayrı kaydedilir; şablonlar `fontFamily`'ye
* aşağıdaki OG_AILE değerlerini verir (Satori CSS gibi virgüllü METİN ister,
* dizi verilirse `split` hatasıyla çöker); glif ilk dosyada yoksa ikinciye
* geçer.
*/
export const OG_AILE = {
calSans: "Cal Sans, Cal Sans Ext",
bricolage: "Bricolage Grotesque, Bricolage Grotesque Ext",
clarity: "Clarity City",
} as const;
/** latin + latin-ext çiftini kaydeder; ext alt-kümesi `<ad> Ext` adını alır. */
async function latinPair(
name: string,
weight: OgFont["weight"],
@@ -35,7 +52,7 @@ async function latinPair(
]);
return [
{ name, data: latin, weight, style },
{ name, data: latinExt, weight, style },
{ name: `${name} Ext`, data: latinExt, weight, style },
];
}

View File

@@ -1,5 +1,5 @@
import { ImageResponse } from "next/og";
import { loadRehberOgFonts } from "@/lib/og-fonts";
import { loadRehberOgFonts, OG_AILE } from "@/lib/og-fonts";
export const OG_SIZE = { width: 1200, height: 630 };
export const OG_CONTENT_TYPE = "image/png";
@@ -51,7 +51,6 @@ export async function createOgSayfaImage(opts: {
const { eyebrow, title, description, logo } = opts;
return new ImageResponse(
(
<div
style={{
position: "relative",
@@ -61,7 +60,7 @@ export async function createOgSayfaImage(opts: {
overflow: "hidden",
background: "white",
color: "#2563eb",
fontFamily: "Bricolage Grotesque",
fontFamily: OG_AILE.bricolage,
}}
>
{PIXELS.map(([left, top, opacity], i) => (
@@ -129,7 +128,7 @@ export async function createOgSayfaImage(opts: {
style={{
display: "flex",
alignItems: "center",
fontFamily: "Clarity City",
fontFamily: OG_AILE.clarity,
fontSize: 20,
fontWeight: 700,
letterSpacing: "0.18em",
@@ -171,7 +170,7 @@ export async function createOgSayfaImage(opts: {
style={{
display: "flex",
flexWrap: "wrap",
fontFamily: "Cal Sans",
fontFamily: OG_AILE.calSans,
fontSize: baslikBoyutu(title),
fontWeight: 400,
lineHeight: 1.05,
@@ -185,7 +184,7 @@ export async function createOgSayfaImage(opts: {
<div
style={{
display: "flex",
fontFamily: "Clarity City",
fontFamily: OG_AILE.clarity,
fontSize: 26,
fontWeight: 700,
lineHeight: 1.35,
@@ -219,7 +218,7 @@ export async function createOgSayfaImage(opts: {
style={{
display: "flex",
marginLeft: 2,
fontFamily: "Bricolage Grotesque",
fontFamily: OG_AILE.bricolage,
fontSize: 24,
letterSpacing: "-0.05em",
color: "#2563eb",
@@ -230,8 +229,7 @@ export async function createOgSayfaImage(opts: {
</span>
</div>
</div>
</div>
),
</div>,
{ ...OG_SIZE, fonts },
);
}