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 { ImageResponse } from "next/og";
import { loadSiteOgFonts } from "@/lib/og-fonts"; import { loadSiteOgFonts, OG_AILE } from "@/lib/og-fonts";
export const alt = export const alt =
"KolayTercih — YKS tercihin, gerçek YÖK Atlas verisiyle. Sıralamanı gir, dengeli 24 tercihlik listeni al."; "KolayTercih — YKS tercihin, gerçek YÖK Atlas verisiyle. Sıralamanı gir, dengeli 24 tercihlik listeni al.";
@@ -10,83 +10,81 @@ export default async function OpenGraphImage() {
const fonts = await loadSiteOgFonts(); const fonts = await loadSiteOgFonts();
return new ImageResponse( return new ImageResponse(
( <div
style={{
display: "flex",
width: "100%",
height: "100%",
alignItems: "center",
justifyContent: "center",
background: "white",
}}
>
<div <div
style={{ style={{
display: "flex", display: "flex",
width: "100%", flexDirection: "column",
height: "100%",
alignItems: "center", alignItems: "center",
justifyContent: "center", maxWidth: 920,
background: "white", padding: "0 48px",
}} }}
> >
<div <div
style={{ style={{
display: "flex", display: "flex",
flexDirection: "column",
alignItems: "center", alignItems: "center",
maxWidth: 920, fontFamily: OG_AILE.bricolage,
padding: "0 48px", letterSpacing: "-0.05em",
color: "#0f172a",
}} }}
> >
<div <svg
style={{ width="64"
display: "flex", height="64"
alignItems: "center", viewBox="0 0 180 180"
fontFamily: "Bricolage Grotesque", style={{ display: "flex", marginRight: 4 }}
letterSpacing: "-0.05em",
color: "#0f172a",
}}
> >
<svg <rect x="27" y="56" width="57" height="57" rx="13" fill="#3b82f6" />
width="64" <rect x="91" y="29" width="56" height="56" rx="13" fill="#fdc7a7" />
height="64" <rect x="91" y="95" width="56" height="56" rx="13" fill="#ff5a00" />
viewBox="0 0 180 180" </svg>
style={{ display: "flex", marginRight: 4 }} <span style={{ display: "flex", fontSize: 72, lineHeight: 1 }}>
> <span style={{ fontWeight: 300 }}>Kolay</span>
<rect x="27" y="56" width="57" height="57" rx="13" fill="#3b82f6" /> <span style={{ fontWeight: 600 }}>Tercih</span>
<rect x="91" y="29" width="56" height="56" rx="13" fill="#fdc7a7" /> </span>
<rect x="91" y="95" width="56" height="56" rx="13" fill="#ff5a00" /> </div>
</svg>
<span style={{ display: "flex", fontSize: 72, lineHeight: 1 }}>
<span style={{ fontWeight: 300 }}>Kolay</span>
<span style={{ fontWeight: 600 }}>Tercih</span>
</span>
</div>
<div <div
style={{ style={{
display: "flex", display: "flex",
marginTop: 28, marginTop: 28,
fontFamily: "Clarity City", fontFamily: OG_AILE.clarity,
fontSize: 34, fontSize: 34,
fontWeight: 600, fontWeight: 600,
lineHeight: 1.25, lineHeight: 1.25,
textAlign: "center", textAlign: "center",
color: "#1e293b", color: "#1e293b",
}} }}
> >
YKS tercihin, gerçek YÖK Atlas verisiyle YKS tercihin, gerçek YÖK Atlas verisiyle
</div> </div>
<div <div
style={{ style={{
display: "flex", display: "flex",
marginTop: 16, marginTop: 16,
fontFamily: "Clarity City", fontFamily: OG_AILE.clarity,
fontSize: 26, fontSize: 26,
fontWeight: 300, fontWeight: 300,
lineHeight: 1.3, lineHeight: 1.3,
textAlign: "center", textAlign: "center",
color: "#64748b", color: "#64748b",
}} }}
> >
Sıralamanı gir · Dengeli 24 tercihlik listeni al Sıralamanı gir · Dengeli 24 tercihlik listeni al
</div>
</div> </div>
</div> </div>
), </div>,
{ ...size, fonts }, { ...size, fonts },
); );
} }

View File

@@ -1,6 +1,6 @@
import { ImageResponse } from "next/og"; import { ImageResponse } from "next/og";
import { getRehber } from "@/lib/rehber"; import { getRehber } from "@/lib/rehber";
import { loadRehberOgFonts } from "@/lib/og-fonts"; import { loadRehberOgFonts, OG_AILE } from "@/lib/og-fonts";
import { import {
rehberBasliginiBol, rehberBasliginiBol,
rehberMetniniParcala, rehberMetniniParcala,
@@ -19,137 +19,180 @@ export default async function RehberOpenGraphImage({
const yazi = getRehber(slug); const yazi = getRehber(slug);
const baslik = yazi?.baslik ?? "Tercih Rehberi"; const baslik = yazi?.baslik ?? "Tercih Rehberi";
const { vurgu, kalan } = rehberBasliginiBol(baslik); 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(); const fonts = await loadRehberOgFonts();
return new ImageResponse( return new ImageResponse(
( <div
style={{
position: "relative",
display: "flex",
width: "100%",
height: "100%",
overflow: "hidden",
background: "white",
color: "#2563eb",
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],
[180, 530, 0.27],
].map(([left, top, opacity], i) => (
<span
key={i}
style={{
position: "absolute",
display: "flex",
left,
top,
width: 14,
height: 14,
borderRadius: 4,
background: "#2563eb",
opacity,
}}
/>
))}
<span
style={{
position: "absolute",
display: "flex",
left: 1040,
top: 50,
width: 14,
height: 14,
borderRadius: 4,
background: "#ff5a00",
opacity: 0.8,
}}
/>
<span
style={{
position: "absolute",
display: "flex",
left: 80,
top: 530,
width: 14,
height: 14,
borderRadius: 4,
background: "#ff5a00",
opacity: 0.8,
}}
/>
<div
style={{
position: "absolute",
width: "100%",
height: "100%",
border: "2px solid rgba(37,99,235,0.15)",
}}
/>
<div <div
style={{ style={{
position: "relative", position: "relative",
display: "flex", display: "flex",
flexDirection: "column",
justifyContent: "space-between",
width: "100%", width: "100%",
height: "100%", padding: "58px 68px 52px",
overflow: "hidden",
background: "white",
color: "#2563eb",
fontFamily: "Bricolage Grotesque",
}} }}
> >
{[
[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
key={i}
style={{
position: "absolute",
display: "flex",
left,
top,
width: 14,
height: 14,
borderRadius: 4,
background: "#2563eb",
opacity,
}}
/>
))}
<span
style={{
position: "absolute",
display: "flex",
left: 1040,
top: 50,
width: 14,
height: 14,
borderRadius: 4,
background: "#ff5a00",
opacity: 0.8,
}}
/>
<span
style={{
position: "absolute",
display: "flex",
left: 80,
top: 530,
width: 14,
height: 14,
borderRadius: 4,
background: "#ff5a00",
opacity: 0.8,
}}
/>
<div <div
style={{ style={{
position: "absolute",
width: "100%",
height: "100%",
border: "2px solid rgba(37,99,235,0.15)",
}}
/>
<div
style={{
position: "relative",
display: "flex", display: "flex",
flexDirection: "column", alignItems: "center",
justifyContent: "space-between", fontFamily: OG_AILE.clarity,
width: "100%", fontSize: 20,
padding: "58px 68px 52px", fontWeight: 700,
letterSpacing: "0.18em",
textTransform: "uppercase",
color: "#2563eb",
}} }}
> >
<div <span>Tercih Rehberi</span>
style={{ </div>
display: "flex",
alignItems: "center",
fontFamily: "Clarity City",
fontSize: 20,
fontWeight: 700,
letterSpacing: "0.18em",
textTransform: "uppercase",
color: "#2563eb",
}}
>
<span>Tercih Rehberi</span>
</div>
<div {/*
style={{ Satori satır kaydırmayı yalnızca düz metin düğümlerinde yapar;
display: "flex", rakam+metin karışık bir flex span tek satırda kalır ve parçalar
flexWrap: "wrap", üst üste biner (2026'lı başlıklarda görülen bozulma). Bu yüzden
maxWidth: 980, başlık kelime kelime ayrı kutulara bölünür: kaydırma kelime
fontFamily: "Cal Sans", sınırında olur, her kelime kendi rengini/yazı tipini taşır.
fontSize: baslik.length > 70 ? 48 : baslik.length > 52 ? 54 : 62, */}
fontWeight: 400, <div
lineHeight: 1.02, style={{
letterSpacing: "-0.025em", display: "flex",
color: "#2563eb", flexWrap: "wrap",
}} maxWidth: 1000,
> fontSize: baslikBoyutu,
lineHeight: 1.06,
color: "#2563eb",
}}
>
{kelimeler.map((kelime, i) => (
<span <span
key={i}
style={{ style={{
display: "flex", display: "flex",
fontFamily: "Bricolage Grotesque", marginRight: kelimeAraligi,
color: "#ff5a00", fontFamily: kelime.vurgu ? OG_AILE.bricolage : OG_AILE.calSans,
fontWeight: 600, fontWeight: kelime.vurgu ? 600 : 400,
letterSpacing: "-0.04em", letterSpacing: kelime.vurgu ? "-0.04em" : "-0.025em",
color: kelime.vurgu ? "#ff5a00" : "#2563eb",
}} }}
> >
{rehberMetniniParcala(vurgu).map((parca, i) => {rehberMetniniParcala(kelime.metin).map((parca, j) =>
/^\d/.test(parca) ? ( /^\d/.test(parca) ? (
<span <span
key={i} key={j}
style={{ style={{
display: "flex", display: "flex",
fontFamily: "Bricolage Grotesque", fontFamily: OG_AILE.bricolage,
fontStyle: "italic", fontStyle: "italic",
fontWeight: 800, fontWeight: 800,
}} }}
@@ -161,68 +204,42 @@ export default async function RehberOpenGraphImage({
), ),
)} )}
</span> </span>
{kalan ? ( ))}
<span </div>
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 <div
style={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
<svg
width="38"
height="38"
viewBox="0 0 180 180"
style={{ display: "flex" }}
>
<rect x="27" y="56" width="57" height="57" rx="13" fill="#3b82f6" />
<rect x="91" y="29" width="56" height="56" rx="13" fill="#fdc7a7" />
<rect x="91" y="95" width="56" height="56" rx="13" fill="#ff5a00" />
</svg>
<span
style={{ style={{
display: "flex", display: "flex",
alignItems: "center", marginLeft: 2,
justifyContent: "flex-end", fontFamily: OG_AILE.bricolage,
fontSize: 24,
letterSpacing: "-0.05em",
color: "#2563eb",
}} }}
> >
<svg <span style={{ fontWeight: 300 }}>Kolay</span>
width="38" <span style={{ fontWeight: 600 }}>Tercih</span>
height="38" </span>
viewBox="0 0 180 180"
style={{ display: "flex" }}
>
<rect x="27" y="56" width="57" height="57" rx="13" fill="#3b82f6" />
<rect x="91" y="29" width="56" height="56" rx="13" fill="#fdc7a7" />
<rect x="91" y="95" width="56" height="56" rx="13" fill="#ff5a00" />
</svg>
<span
style={{
display: "flex",
marginLeft: 2,
fontFamily: "Bricolage Grotesque",
fontSize: 24,
letterSpacing: "-0.05em",
color: "#2563eb",
}}
>
<span style={{ fontWeight: 300 }}>Kolay</span>
<span style={{ fontWeight: 600 }}>Tercih</span>
</span>
</div>
</div> </div>
</div> </div>
), </div>,
{ ...size, fonts }, { ...size, fonts },
); );
} }

View File

@@ -21,7 +21,24 @@ type OgFont = {
style: "normal" | "italic"; 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( async function latinPair(
name: string, name: string,
weight: OgFont["weight"], weight: OgFont["weight"],
@@ -35,7 +52,7 @@ async function latinPair(
]); ]);
return [ return [
{ name, data: latin, weight, style }, { 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 { 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_SIZE = { width: 1200, height: 630 };
export const OG_CONTENT_TYPE = "image/png"; export const OG_CONTENT_TYPE = "image/png";
@@ -51,187 +51,185 @@ export async function createOgSayfaImage(opts: {
const { eyebrow, title, description, logo } = opts; const { eyebrow, title, description, logo } = opts;
return new ImageResponse( return new ImageResponse(
( <div
style={{
position: "relative",
display: "flex",
width: "100%",
height: "100%",
overflow: "hidden",
background: "white",
color: "#2563eb",
fontFamily: OG_AILE.bricolage,
}}
>
{PIXELS.map(([left, top, opacity], i) => (
<span
key={i}
style={{
position: "absolute",
display: "flex",
left,
top,
width: 14,
height: 14,
borderRadius: 4,
background: "#2563eb",
opacity,
}}
/>
))}
<span
style={{
position: "absolute",
display: "flex",
left: 1040,
top: 50,
width: 14,
height: 14,
borderRadius: 4,
background: "#ff5a00",
opacity: 0.8,
}}
/>
<span
style={{
position: "absolute",
display: "flex",
left: 80,
top: 530,
width: 14,
height: 14,
borderRadius: 4,
background: "#ff5a00",
opacity: 0.8,
}}
/>
<div
style={{
position: "absolute",
width: "100%",
height: "100%",
border: "2px solid rgba(37,99,235,0.15)",
}}
/>
<div <div
style={{ style={{
position: "relative", position: "relative",
display: "flex", display: "flex",
flexDirection: "column",
justifyContent: "space-between",
width: "100%", width: "100%",
height: "100%", padding: "58px 68px 52px",
overflow: "hidden",
background: "white",
color: "#2563eb",
fontFamily: "Bricolage Grotesque",
}} }}
> >
{PIXELS.map(([left, top, opacity], i) => (
<span
key={i}
style={{
position: "absolute",
display: "flex",
left,
top,
width: 14,
height: 14,
borderRadius: 4,
background: "#2563eb",
opacity,
}}
/>
))}
<span
style={{
position: "absolute",
display: "flex",
left: 1040,
top: 50,
width: 14,
height: 14,
borderRadius: 4,
background: "#ff5a00",
opacity: 0.8,
}}
/>
<span
style={{
position: "absolute",
display: "flex",
left: 80,
top: 530,
width: 14,
height: 14,
borderRadius: 4,
background: "#ff5a00",
opacity: 0.8,
}}
/>
<div <div
style={{ style={{
position: "absolute", display: "flex",
width: "100%", alignItems: "center",
height: "100%", fontFamily: OG_AILE.clarity,
border: "2px solid rgba(37,99,235,0.15)", fontSize: 20,
fontWeight: 700,
letterSpacing: "0.18em",
textTransform: "uppercase",
color: "#2563eb",
}} }}
/> >
<span>{eyebrow}</span>
{logo ? (
<span
style={{
display: "flex",
marginLeft: "auto",
alignItems: "center",
justifyContent: "center",
width: 108,
height: 108,
borderRadius: 24,
background: "white",
border: "2px solid rgba(37,99,235,0.15)",
boxShadow: "0 8px 24px rgba(15,23,42,0.08)",
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={logo} alt="" width={84} height={84} />
</span>
) : null}
</div>
<div <div
style={{ style={{
position: "relative",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
justifyContent: "space-between", maxWidth: 980,
width: "100%", gap: 18,
padding: "58px 68px 52px",
}} }}
> >
<div <div
style={{ style={{
display: "flex", display: "flex",
alignItems: "center", flexWrap: "wrap",
fontFamily: "Clarity City", fontFamily: OG_AILE.calSans,
fontSize: 20, fontSize: baslikBoyutu(title),
fontWeight: 700, fontWeight: 400,
letterSpacing: "0.18em", lineHeight: 1.05,
textTransform: "uppercase", letterSpacing: "-0.025em",
color: "#2563eb", color: "#2563eb",
}} }}
> >
<span>{eyebrow}</span> {title}
{logo ? (
<span
style={{
display: "flex",
marginLeft: "auto",
alignItems: "center",
justifyContent: "center",
width: 108,
height: 108,
borderRadius: 24,
background: "white",
border: "2px solid rgba(37,99,235,0.15)",
boxShadow: "0 8px 24px rgba(15,23,42,0.08)",
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={logo} alt="" width={84} height={84} />
</span>
) : null}
</div> </div>
{description ? (
<div
style={{
display: "flex",
flexDirection: "column",
maxWidth: 980,
gap: 18,
}}
>
<div <div
style={{ style={{
display: "flex", display: "flex",
flexWrap: "wrap", fontFamily: OG_AILE.clarity,
fontFamily: "Cal Sans", fontSize: 26,
fontSize: baslikBoyutu(title), fontWeight: 700,
fontWeight: 400, lineHeight: 1.35,
lineHeight: 1.05, color: "#64748b",
letterSpacing: "-0.025em", maxWidth: 860,
color: "#2563eb",
}} }}
> >
{title} {description}
</div> </div>
{description ? ( ) : null}
<div </div>
style={{
display: "flex",
fontFamily: "Clarity City",
fontSize: 26,
fontWeight: 700,
lineHeight: 1.35,
color: "#64748b",
maxWidth: 860,
}}
>
{description}
</div>
) : null}
</div>
<div <div
style={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
<svg
width="38"
height="38"
viewBox="0 0 180 180"
style={{ display: "flex" }}
>
<rect x="27" y="56" width="57" height="57" rx="13" fill="#3b82f6" />
<rect x="91" y="29" width="56" height="56" rx="13" fill="#fdc7a7" />
<rect x="91" y="95" width="56" height="56" rx="13" fill="#ff5a00" />
</svg>
<span
style={{ style={{
display: "flex", display: "flex",
alignItems: "center", marginLeft: 2,
justifyContent: "flex-end", fontFamily: OG_AILE.bricolage,
fontSize: 24,
letterSpacing: "-0.05em",
color: "#2563eb",
}} }}
> >
<svg <span style={{ fontWeight: 300 }}>Kolay</span>
width="38" <span style={{ fontWeight: 600 }}>Tercih</span>
height="38" </span>
viewBox="0 0 180 180"
style={{ display: "flex" }}
>
<rect x="27" y="56" width="57" height="57" rx="13" fill="#3b82f6" />
<rect x="91" y="29" width="56" height="56" rx="13" fill="#fdc7a7" />
<rect x="91" y="95" width="56" height="56" rx="13" fill="#ff5a00" />
</svg>
<span
style={{
display: "flex",
marginLeft: 2,
fontFamily: "Bricolage Grotesque",
fontSize: 24,
letterSpacing: "-0.05em",
color: "#2563eb",
}}
>
<span style={{ fontWeight: 300 }}>Kolay</span>
<span style={{ fontWeight: 600 }}>Tercih</span>
</span>
</div>
</div> </div>
</div> </div>
), </div>,
{ ...OG_SIZE, fonts }, { ...OG_SIZE, fonts },
); );
} }