Files
kolaytercih/src/features/katalog/components/tur-icerik.tsx
bilalgursen 78ad84922a
All checks were successful
Deploy / deploy (push) Successful in 6m23s
feat(tema): açık / koyu / sistem teması
- TemaSaglayici (next-themes) + alt bilgide TemaSecici
- globals.css: .dark bloğunda palet token'ları çevrilir; bileşenlere
  dark: sınıfı yazılmaz
- yüzeyler bg-white → bg-card, koyu haplar text-slate-50, SVG'lerde
  sabit renk yerine değişken
- DESIGN.md §2.3 + tasarim-denetim kuralları (yuzey-beyaz,
  koyu-hap-beyaz-yazi, dark-sinif), AGENTS.md karanlık mod bölümü
- docs: koyu tema denetimi raporu

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-05 10:20:02 +03:00

280 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Metadata } from "next";
import Link from "next/link";
import {
Building2,
ChevronRight,
Landmark,
LayoutList,
MapPin,
} from "lucide-react";
import { getAllUniversiteler, type UniOzet } from "../katalog-queries";
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
import { PagePixelDivider } from "@/components/pixel-decor";
import { OzetKart } from "@/components/ozet-kart";
import { Skeleton } from "@/components/ui/skeleton";
import {
JsonLd,
breadcrumbJsonLd,
sayfaMetadata,
ustSegmentOgGorseli,
} from "@/lib/seo";
import { ilSayfaHref, trBaslikDuzeni, turkishSlugify } from "@/lib/slug";
import { TUR_SAYFALARI, type TurSlug } from "@/lib/unitur";
import { BASLIK_YILI, SON_YIL, TERCIH_YILI } from "@/lib/veri-yillari";
import { NavBaslik } from "./nav-baslik";
import { TurkiyePikselHaritasi } from "./sehir-piksel-haritasi";
import { SehirYuzeyi } from "./sehir-yuzeyi";
import { UniSatirlari } from "./uni-satirlari";
const sayi = (n: number) => n.toLocaleString("tr-TR");
function turUnileri(tur: TurSlug): UniOzet[] {
const set = new Set<string>(TUR_SAYFALARI[tur].uniturler);
return getAllUniversiteler().filter((u) => u.unitur && set.has(u.unitur));
}
export function turSayfaMetadata(tur: TurSlug): Metadata {
const t = TUR_SAYFALARI[tur];
const uniler = turUnileri(tur);
const program = uniler.reduce((a, u) => a + u.programSayisi, 0);
return sayfaMetadata({
title: `${t.ad} Taban Puanları ve Sıralamaları ${BASLIK_YILI}`,
description: `Türkiye'deki ${uniler.length} ${t.tekil}nin ${SON_YIL} YKS yerleştirme verileri: ${sayi(program)} programın taban puanları, başarı sıralamaları ve kontenjanları, şehir şehir. ${TERCIH_YILI} tercih rehberi.`,
path: `/universiteler/${t.slug}`,
openGraph: {
images: ustSegmentOgGorseli(
"/universiteler",
"Üniversitelerin taban puanları ve sıralamaları — KolayTercih",
),
},
});
}
/**
* /universiteler/devlet ve /universiteler/vakif (DESIGN.md §4.18). Şehir
* sayfasıyla aynı iskelet; kreatif fark: başlık yüzeyindeki dot-matrix
* Türkiye'de bu türden üniversitesi olan **her il** turuncu yanar, yakınlaşma
* yok — "devlet üniversiteleri Türkiye'nin neresinde" tek bakışta. Altında
* üç özet kartı, şehir çipleri (bu türün il başına sayısı) ve şehir şehir
* §4.15 liste satırları.
*/
export function TurIcerik({ tur }: { tur: TurSlug }) {
const t = TUR_SAYFALARI[tur];
const uniler = turUnileri(tur);
const program = uniler.reduce((a, u) => a + u.programSayisi, 0);
// Şehir öbekleri: üniversitenin ana ili (UniOzet.il); ili boş olan sona.
const ilOzet = new Map<string, { il: string; ad: string; uniler: UniOzet[] }>();
const ilsiz: UniOzet[] = [];
for (const u of uniler) {
if (!u.il) {
ilsiz.push(u);
continue;
}
let k = ilOzet.get(u.il);
if (!k) {
k = { il: u.il, ad: trBaslikDuzeni(u.il), uniler: [] };
ilOzet.set(u.il, k);
}
k.uniler.push(u);
}
const sehirler = [...ilOzet.values()].sort((a, b) =>
a.ad.localeCompare(b.ad, "tr-TR"),
);
const cipSehirler = [...sehirler].sort(
(a, b) =>
b.uniler.length - a.uniler.length || a.ad.localeCompare(b.ad, "tr-TR"),
);
const Ikon = tur === "devlet" ? Landmark : Building2;
const DigerIkon = tur === "devlet" ? Building2 : Landmark;
const ctaBaslik = `Sıralaman ${t.tekil} tabanlarının neresinde?`;
const digerTur = tur === "devlet" ? TUR_SAYFALARI.vakif : TUR_SAYFALARI.devlet;
return (
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
<JsonLd
data={breadcrumbJsonLd([
{ name: "Ana Sayfa", path: "/" },
{ name: "Üniversiteler", path: "/universiteler" },
{ name: t.ad, path: `/universiteler/${t.slug}` },
])}
/>
<nav
aria-label="İçerik yolu"
className="flex flex-wrap items-center justify-center gap-1.5 text-sm text-slate-500 lg:justify-start"
>
<Link
href="/"
className="transition-colors hover:text-orange-500 hover:underline"
>
Ana Sayfa
</Link>
<ChevronRight className="size-3.5 shrink-0" aria-hidden />
<Link
href="/universiteler"
className="transition-colors hover:text-orange-500 hover:underline"
>
Üniversiteler
</Link>
<ChevronRight className="size-3.5 shrink-0" aria-hidden />
<span className="text-slate-700">{t.ad}</span>
</nav>
<NavBaslik baslikId="tur-baslik" ad={t.ad} />
<SehirYuzeyi
harita={
<TurkiyePikselHaritasi
iller={sehirler.map((s) => s.il)}
etiket={`${t.ad} Türkiye haritasında: ${sehirler.length} il`}
doldur
/>
}
metin={
<div className="text-center lg:text-left">
<h1
id="tur-baslik"
className="font-heading text-3xl font-bold leading-tight text-slate-900 sm:text-5xl"
>
{t.ad} Taban Puanları {BASLIK_YILI}
</h1>
{/* Kimlik hapları (§4.12): tür + öteki türe geçiş */}
<p className="mt-5 flex flex-wrap items-center justify-center gap-2 lg:justify-start">
<span className="inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-card px-4 text-sm font-semibold text-slate-800">
<Ikon className="size-4 text-primary" aria-hidden />
{tur === "devlet" ? "Devlet" : "Vakıf"}
</span>
<Link
href={`/universiteler/${digerTur.slug}`}
className="inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-card px-4 text-sm font-semibold text-slate-800 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]"
>
<DigerIkon className="size-4 text-primary" aria-hidden />
{digerTur.ad}
</Link>
</p>
<p className="mx-auto mt-6 max-w-2xl text-base leading-relaxed text-slate-600 sm:text-lg lg:mx-0">
Türkiye&apos;de {SON_YIL} YKS yerleştirmesinde öğrenci alan{" "}
<strong>
{sayi(uniler.length)} {t.tekil}
</strong>
, <strong>{sayi(sehirler.length)} şehirde</strong> toplam{" "}
<strong>{sayi(program)} program</strong> açtı. Haritada turuncu
yanan her ilde en az bir {t.tekil} var. Üniversiteye tıklayınca
programlarının taban puanlarını, başarı sıralamalarını ve
kontenjanlarını fakülte fakülte görürsün; {TERCIH_YILI} tercihleri
için rehber niteliğindedir.
</p>
</div>
}
/>
<div className="mt-8">
<CtaSiraForm varyant="kompakt" konum="ust" baslik={ctaBaslik} />
</div>
<dl className="mt-8 grid grid-cols-2 gap-4 lg:grid-cols-3">
<OzetKart
ikon={Ikon}
etiket="Üniversite"
deger={sayi(uniler.length)}
alt={`${SON_YIL}'de öğrenci alan`}
/>
<OzetKart
ikon={LayoutList}
etiket="Program"
deger={sayi(program)}
alt="lisans ve önlisans"
/>
<OzetKart
ikon={MapPin}
etiket="Şehir"
deger={sayi(sehirler.length)}
alt="en az bir kampüs"
className="col-span-2 lg:col-span-1"
/>
</dl>
<PagePixelDivider seed={73} className="mx-auto mt-8 lg:mx-0" />
<section
className="mt-8 text-center lg:text-left"
aria-labelledby="tur-sehirler"
>
<h2
id="tur-sehirler"
className="font-heading text-2xl font-bold text-slate-900"
>
Şehre göre
<span className="ml-2 text-sm font-normal text-slate-400">
{sayi(sehirler.length)} şehir
</span>
</h2>
<ul className="mt-4 flex flex-wrap items-center justify-center gap-2 lg:justify-start">
{cipSehirler.map((s) => (
<li key={s.il}>
<a
href={`#sehir-${turkishSlugify(s.il)}`}
className="inline-flex h-10 cursor-pointer items-center gap-1.5 rounded-full border border-slate-200 bg-card px-3.5 text-sm font-semibold text-slate-700 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]"
>
{s.ad}
<span className="font-normal tabular-nums text-slate-400">
{sayi(s.uniler.length)}
</span>
</a>
</li>
))}
</ul>
</section>
{sehirler.map((s) => (
<section
key={s.il}
id={`sehir-${turkishSlugify(s.il)}`}
className="mt-10 scroll-mt-24 text-center lg:text-left"
>
<h2 className="font-heading text-2xl font-bold text-slate-900">
<Link
href={ilSayfaHref(s.il)}
className="transition-colors hover:text-orange-500 hover:underline"
>
{s.ad}
</Link>
<span className="ml-2 text-sm font-normal text-slate-400">
{sayi(s.uniler.length)}
</span>
</h2>
<UniSatirlari uniler={s.uniler} gizliIl={s.il} />
</section>
))}
{ilsiz.length > 0 ? (
<section className="mt-10 text-center lg:text-left">
<h2 className="font-heading text-2xl font-bold text-slate-900">
Diğer
</h2>
<UniSatirlari uniler={ilsiz} />
</section>
) : null}
<div className="mt-12">
<CtaSiraForm
baslik={ctaBaslik}
aciklama={`YKS başarı sıralamanı gir; ${t.tekil} programları dahil sıralamana uygun hayal, dengeli ve güvenli dilimlerindeki programları anında listele. Ücretsiz, üyelik gerekmez.`}
/>
</div>
</main>
);
}
export function TurIcerikSkeleton() {
return (
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
<Skeleton className="mx-auto h-4 w-64 lg:mx-0" />
<Skeleton className="mt-6 h-72 w-full rounded-3xl" />
<div className="mt-8 grid grid-cols-2 gap-4 lg:grid-cols-3">
<Skeleton className="h-28 rounded-2xl" />
<Skeleton className="h-28 rounded-2xl" />
<Skeleton className="col-span-2 h-28 rounded-2xl lg:col-span-1" />
</div>
</main>
);
}