Files
kolaytercih/src/features/katalog/components/universite-icerik.tsx
bilalgursen 6690d3b779
All checks were successful
Deploy / deploy (push) Successful in 4m51s
fix(tasarim): üniversite haritası dört kartın arkasına sarkar; yakınlaşma sıçramasız yay, mask rampaları uzun
- Özet ızgarası relative isolate; konum haritası absolute katman (-inset-x-4
  -inset-y-8) olarak ızgaranın tamamını kaplar, üçüncü sütun yalnız yer tutar.
  lg'de sağdan sola, telefonda üst satırdan aşağı uzun rampalı mask.
- Yakınlaşma ease-out yerine spring bounce 0 (0,6 s gecikme, 1,6 s): sert
  başlangıç gitti. Şehir yüzeyi mask'ları da uzatıldı.
- DESIGN.md §4.17 güncellendi.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 15:37:10 +03:00

765 lines
30 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 { notFound } from "next/navigation";
import {
ArrowRight,
Building2,
ChevronRight,
Landmark,
LayoutList,
Medal,
MapPin,
Target,
Users,
} from "lucide-react";
import {
getAllUniversiteler,
getAllUniversiteSlugs,
getUniversiteBySlug,
bolumSlugFromIsim,
} from "../katalog-queries";
import { ilSayfaHref, trBaslikDuzeni } from "@/lib/slug";
import { UNITUR_ETIKET, uniturDevletMi } from "@/lib/unitur";
import {
JsonLd,
breadcrumbJsonLd,
sayfaMetadata,
SITE_URL,
ustSegmentOgGorseli,
} from "@/lib/seo";
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
import { SiteFooter } from "@/components/site-footer";
import {
KATALOG_CTA_ACIKLAMA,
Sayfalama,
SAYFA_BOYU,
sayfaDilimi,
siraYiliEtiketi,
} from "./sayfalama";
import { PagePixelDivider, PixelSira } from "@/components/pixel-decor";
import { Button } from "@/components/ui/button";
import { UniversiteKonumHaritasi } from "./universite-konum-haritasi";
import { uniUlkeKonum } from "@/lib/harita-ulkeler";
import { UniversiteProgramTablosu } from "./universite-program-tablosu";
import { VeriKaynagi } from "./veri-kaynagi";
import { URAP_DONEM, URAP_KAYNAK_URL, URAP_TOPLAM } from "@/lib/urap-siralama";
import {
YOK_ISTATISTIK_DONEM,
YOK_ISTATISTIK_KAYNAK_URL,
OGRENCI_BASINA_UYE_MEDYAN,
ogrenciBasinaUye,
} from "@/lib/yok-istatistik";
import { UniLogo } from "@/components/uni-logo";
import { UrapLogo } from "./urap-logo";
import { AkademikAyrinti } from "./akademik-ayrinti";
import { NavBaslik } from "./nav-baslik";
import { OzetKart } from "@/components/ozet-kart";
import { uniLogoYolu } from "@/lib/uni-logolar";
import { Skeleton } from "@/components/ui/skeleton";
import type { Program } from "@/types/yokatlas";
import { BASLIK_YILI, SON_YIL, TERCIH_YILI } from "@/lib/veri-yillari";
const sayi = (n: number) => n.toLocaleString("tr-TR");
export function uniToplamSayfa(programSayisi: number): number {
return Math.max(1, Math.ceil(programSayisi / SAYFA_BOYU));
}
// Sayfaların generateStaticParams'ı için — sayfalar *-queries import etmez.
export function uniStaticSlugs(): string[] {
return getAllUniversiteSlugs();
}
// 2..N sayfaları; 1. sayfa taban rota (/universite/[slug]) olduğundan üretilmez.
export function uniSayfaStaticParams(): { slug: string; no: string }[] {
return getAllUniversiteler().flatMap((u) => {
const toplam = uniToplamSayfa(u.programSayisi);
return Array.from({ length: Math.max(0, toplam - 1) }, (_, i) => ({
slug: u.slug,
no: String(i + 2),
}));
});
}
type UniDetay = NonNullable<ReturnType<typeof getUniversiteBySlug>>;
/**
* Sayfanın satır dilimi + o dilimde geçen fakülte/birim adları (tablo
* sırasıyla, tekrarsız). Liste fakülteye göre dizili olduğundan her derin
* sayfa belirli birimleri kapsar. Metadata ve gövde aynı hesabı buradan alır.
* Fakültesi boş satırlar tabloda "Diğer" grubuna düşer; ad listesine girmez.
*/
function uniSayfaDilimi(uni: UniDetay, sayfa: number) {
// Sayfalama fakülte sıralı düz liste üzerinden; dilim içi yeniden gruplanır
const tumProgramlar = uni.fakulteler.flatMap((f) => f.programlar);
const dilim = sayfaDilimi(tumProgramlar, sayfa);
const fakulteAdlari: string[] = [];
for (const p of dilim) {
if (p.fakulte && !fakulteAdlari.includes(p.fakulte)) {
fakulteAdlari.push(p.fakulte);
}
}
return { tumProgramlar, dilim, fakulteAdlari };
}
/**
* İlk 2 ad + "ve N birim daha"; ad yoksa null (çağıran eski metne düşer).
* Dilim tek birimden ibaretse (50'den çok programlı MYO'lar art arda iki
* sayfayı doldurabiliyor) sayfalar ayırt edilsin diye birim içi taban sırası
* aralığı eklenir — birim içinde satırlar zaten taban sırasına göre dizili.
*/
function fakulteOzeti({
dilim,
fakulteAdlari: adlar,
}: ReturnType<typeof uniSayfaDilimi>): string | null {
if (adlar.length === 0) return null;
if (adlar.length === 1) {
const siralar = dilim
.map((p) => p.efektifSira)
.filter((n): n is number => n != null);
if (siralar.length === 0) return adlar[0];
const min = Math.min(...siralar);
const max = Math.max(...siralar);
const yil = siraYiliEtiketi(
dilim.filter((p) => p.siraSon == null && p.efektifSira != null).length,
siralar.length,
);
return min === max
? `${adlar[0]} (${yil} taban sırası ${sayi(min)})`
: `${adlar[0]} (${yil} taban sırası ${sayi(min)}–${sayi(max)} arası)`;
}
if (adlar.length === 2) return `${adlar[0]} ve ${adlar[1]}`;
return `${adlar[0]}, ${adlar[1]} ve ${adlar.length - 2} birim daha`;
}
/**
* Tablonun altındaki AYRI ikinci kaynak satırı. `VeriKaynagi` kullanılmaz ve
* kopyalanmaz: o satır YÖK Atlas'ın yerleştirme yılına ve son güncelleme
* tarihine bağlı, URAP'ın dönemi başka — aynı satırda birleşirlerse URAP
* verisi YÖK Atlas tarihiyle sunulmuş olur.
*
* Atıf pazarlık dışı: URAP'ın izni "urapcenter.org adresine aktif bağlantı"
* şartına bağlı, o yüzden gerçek <a href>, yeni sekme, rel="noopener" ve
* nofollow YOK. Sıra payda ("198 üniversite arasında") ve dönem olmadan
* gösterilmez. Sıra ve atıf artık AkademikGostergeler kartında; bu satır
* yalnız URAP listesinde olmayan üniversitede nedeni tek cümleyle yazar.
*/
function UrapKaynakSatiri({ uni }: { uni: UniDetay }) {
if (!uni.urap) {
const yurtDisi =
uni.unitur === "KKTC" || uni.unitur?.startsWith("YURTDISI");
return (
<p className="mt-2 text-xs leading-5 text-slate-500">
{yurtDisi
? "Bu üniversite URAP Türkiye sıralamasında yer almıyor — URAP yalnız Türkiye'deki üniversiteleri sıralıyor."
: `Bu üniversite URAP Türkiye Sıralaması ${URAP_DONEM} listesinde yer almıyor.`}
</p>
);
}
// Sıra ve zorunlu atıf AkademikGostergeler kartında.
return null;
}
/**
* Akademik göstergeler: URAP sırası + YÖK İstatistik öğrenci/öğretim üyesi.
* Sayfada değil, özet kartlarının altındaki "Akademik göstergeler ›"
* satırının açtığı alt sayfada (AkademikAyrinti) durur; sıra sayısı ayrıca
* dördüncü özet kartında.
*
* Apple ayrıntı sayfası düzeni (Hava Durumu gibi): kart ve süs ayraç yok,
* her şey ortalı; iki dev sayı yan yana ince çizgiyle ayrılır, altında ham
* sayılar, tek satır not, buton ve en altta ince yazıyla kaynaklar. Alt
* sayfa ekranı doldurmasın diye dikey değil yan yana dizilir.
*
* URAP: sıra payda ve dönem olmadan gösterilmez; sıranın yeri piksel
* şeridinde (PixelSira) turuncu yanar. Atıf pazarlık dışı — URAP'ın izni
* urapcenter.org'a aktif bağlantı şartına bağlı: gerçek <a href>, yeni
* sekme, rel="noopener", nofollow YOK. Oran kıyası sayfası olan
* üniversitelerin medyanıyla yapılır; renk kullanılmaz.
*/
function AkademikGostergeler({ uni }: { uni: UniDetay }) {
const { urap, istatistik: i } = uni;
const oran = i ? ogrenciBasinaUye(i) : null;
const oranVar = i != null && oran != null;
if (!urap && !oranVar) return null;
const dilim = urap ? Math.ceil((urap.sira / URAP_TOPLAM) * 100) : 0;
const kaynakLinki =
"font-medium text-primary transition-colors hover:text-orange-500 hover:underline";
// Dört gösterge dört canlı karede, logodaki kareler gibi yerleşir: iki
// sütun, sağdaki aşağı kaymış, kare boyları farklı — ne tepeler ne
// bitişler hizalı (masonry). Renk sempati katar, anlamı etiket taşır;
// yazı beyaz (turuncu/mavi zeminde sitenin CTA'larıyla aynı ikili).
const kare =
"flex flex-col items-center justify-center rounded-3xl px-3 text-white";
const etiket = "text-sm font-bold text-white/90";
const devSayi =
"font-heading text-5xl font-bold leading-none tracking-tight";
const orta = "font-heading text-3xl font-bold leading-none";
const siraKaresi = urap ? (
<div className={`${kare} min-h-40 bg-orange-500 py-4`}>
<dt className={etiket}>Türkiye sırası</dt>
<dd className="mt-3 flex items-baseline justify-center gap-1">
<span className={devSayi}>{sayi(urap.sira)}.</span>
<span className="text-base font-bold text-white/80">
/ {sayi(URAP_TOPLAM)}
</span>
</dd>
<dd className="mt-2 text-base font-bold">
{dilim <= 50 ? `İlk %${dilim}` : `${URAP_DONEM} listesi`}
</dd>
<dd className="mt-3 text-white">
<PixelSira
sira={urap.sira}
toplam={URAP_TOPLAM}
hucre={10}
zemin="koyu"
className="h-3"
/>
</dd>
</div>
) : null;
const ogrenciKaresi = oranVar ? (
<div className={`${kare} min-h-24 bg-indigo-500 py-3`}>
<dd className={orta}>{sayi(i.ogrenci)}</dd>
<dt className={`mt-1.5 ${etiket}`}>Örgün öğrenci</dt>
</div>
) : null;
const oranKaresi = oranVar ? (
<div className={`${kare} min-h-32 bg-primary py-4`}>
<dt className={etiket}>Hoca başına öğrenci</dt>
<dd className={`mt-3 ${devSayi}`}>{sayi(oran)}</dd>
<dd className="mt-2 text-base font-bold text-white/90">
Türkiye: {sayi(OGRENCI_BASINA_UYE_MEDYAN)}
</dd>
</div>
) : null;
const uyeKaresi = oranVar ? (
<div className={`${kare} bg-green-600 py-5`}>
<dd className={orta}>{sayi(i.ogretimUyesi)}</dd>
<dt className={`mt-1.5 ${etiket}`}>Öğretim üyesi</dt>
</div>
) : null;
const ikiSutun = urap != null && oranVar;
return (
<div className="flex flex-col items-center text-center">
<dl className="flex w-full max-w-md items-start gap-3">
<div className="flex flex-1 flex-col gap-3">
{siraKaresi}
{ogrenciKaresi}
</div>
{ikiSutun || (!urap && oranVar) ? (
<div className={`flex flex-1 flex-col gap-3 ${ikiSutun ? "mt-12" : ""}`}>
{oranKaresi}
{uyeKaresi}
</div>
) : null}
</dl>
{urap ? (
<>
<p className="mt-4 max-w-md text-sm leading-relaxed text-slate-500">
Türkiye sırası araştırma performansını ölçer; eğitim kalitesini
ya da iş bulma oranını ölçmez.
</p>
<Button
asChild
size="lg"
variant="outline"
className="mt-4 cursor-pointer px-6 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]"
>
<Link href="/universite-siralamasi">
Tüm sıralamayı gör
<ArrowRight className="size-4" aria-hidden />
</Link>
</Button>
</>
) : null}
<div className="mt-5 flex w-full max-w-md items-center justify-center gap-3 border-t border-slate-100 pt-4 text-left text-xs leading-5 text-slate-500">
{urap ? <UrapLogo className="h-7 shrink-0" /> : null}
<div>
{urap ? (
<p>
URAP {URAP_DONEM} ·{" "}
<a
href={URAP_KAYNAK_URL}
target="_blank"
rel="noopener"
className={kaynakLinki}
>
urapcenter.org
</a>
</p>
) : null}
{i ? (
<p>
YÖK {YOK_ISTATISTIK_DONEM} ·{" "}
<a
href={YOK_ISTATISTIK_KAYNAK_URL}
target="_blank"
rel="noopener"
className={kaynakLinki}
>
istatistik.yok.gov.tr
</a>
</p>
) : null}
</div>
</div>
</div>
);
}
/**
* Dördüncü özet kartı: URAP Türkiye sırası. Açıklamanın altında sıranın
* yerini gösteren piksel şeridi (PixelSira) durur. Kartın tamamı sıralama
* sayfasına gider (after: ile gerilmiş link); hover kenarlığı bunu belli
* eder. Atıf pazarlık dışı: sıra payda ve dönem olmadan gösterilmez;
* urapcenter.org'a aktif bağlantı (yeni sekme, rel="noopener", nofollow YOK)
* z-10 ile kart linkinin üstünde kalır.
*/
function TurkiyeSirasiKarti({ sira }: { sira: number }) {
const dilim = Math.ceil((sira / URAP_TOPLAM) * 100);
return (
<OzetKart
className="group relative transition-[background-color,border-color] duration-150 hover:border-orange-500 hover:bg-orange-50/60"
ikon={Medal}
ek={<PixelSira sira={sira} toplam={URAP_TOPLAM} className="h-3" />}
etiket="Türkiye sırası"
deger={
<Link
href="/universite-siralamasi"
aria-label={`Türkiye sıralamasında ${sayi(sira)}. — tüm üniversite sıralamasını gör`}
className="transition-colors after:absolute after:inset-0 after:rounded-2xl group-hover:text-orange-500 focus-visible:outline-none focus-visible:after:ring-2 focus-visible:after:ring-primary"
>
{sayi(sira)}.
</Link>
}
birim={`/ ${sayi(URAP_TOPLAM)}`}
alt={
<>
{dilim <= 50 ? (
<span className="block font-semibold text-slate-700">
İlk %{dilim}
</span>
) : null}
<a
href={URAP_KAYNAK_URL}
target="_blank"
rel="noopener"
className="relative z-10 font-medium text-primary transition-colors hover:text-orange-500 hover:underline"
>
URAP {URAP_DONEM}
</a>
</>
}
/>
);
}
/** "Akademik göstergeler ›" satırının ikinci satırı: içeride ne var. */
function akademikOnizleme(uni: UniDetay): string {
const oran = uni.istatistik ? ogrenciBasinaUye(uni.istatistik) : null;
if (oran != null) return `Hoca başına ${sayi(oran)} öğrenci`;
return "Türkiye sıralaması ayrıntısı";
}
/** YÖK İstatistik'in ayrı kaynak satırı — dönemi YÖK Atlas'tan ve URAP'tan farklı. */
function IstatistikKaynakSatiri({ uni }: { uni: UniDetay }) {
if (!uni.istatistik) return null;
return (
<p className="mt-2 text-xs leading-5 text-slate-500">
Öğrenci ve öğretim üyesi sayıları: YÖK Yükseköğretim İstatistikleri{" "}
{YOK_ISTATISTIK_DONEM}. Örgün öğrenci birinci ve ikinci öğretimdeki tüm
düzeyleri (ön lisans–doktora) kapsar, uzaktan ve açık öğretim dahil
değildir; öğretim üyesi profesör, doçent ve doktor öğretim üyesidir.
Kaynak:{" "}
<a
href={YOK_ISTATISTIK_KAYNAK_URL}
target="_blank"
rel="noopener"
className="font-medium text-primary transition-colors hover:text-orange-500 hover:underline"
>
istatistik.yok.gov.tr
</a>
.
</p>
);
}
export function uniSayfaMetadata(slug: string, sayfa: number): Metadata {
const uni = getUniversiteBySlug(slug);
if (!uni) return {};
if (sayfa > 1) {
// Üniversite adları uzun: <title> sayfa numarasıyla kalır (~60 karakter),
// farklılaştırıcı (o sayfadaki birimler) açıklamaya ve gövdeye gider.
const birimler = fakulteOzeti(uniSayfaDilimi(uni, sayfa));
return sayfaMetadata({
title: `${uni.ad} Taban Puanları ${BASLIK_YILI} (Sayfa ${sayfa})`,
description: birimler
? `${uni.ad} taban puanları, sayfa ${sayfa}: ${birimler}. Toplam ${uni.programSayisi} programın ${SON_YIL} yerleştirme verileri — taban puanı, başarı sıralaması ve kontenjan.`
: `${uni.ad} taban puanları ve başarı sıralamaları tablosunun ${sayfa}. sayfası — toplam ${uni.programSayisi} programın ${SON_YIL} yerleştirme verileri.`,
path: `/universite/${slug}/sayfa/${sayfa}`,
openGraph: {
images: ustSegmentOgGorseli(
`/universite/${slug}`,
"Üniversite taban puanları ve sıralamaları — KolayTercih",
),
},
});
}
return sayfaMetadata({
title: `${uni.ad} Taban Puanları ve Başarı Sıralamaları ${BASLIK_YILI}`,
description: `${uni.ad} ${SON_YIL} yerleştirme verileri: ${uni.programSayisi} programın taban puanları, başarı sıralamaları ve kontenjanları${uni.il ? ` (${trBaslikDuzeni(uni.il)})` : ""}. ${TERCIH_YILI} tercih rehberi.`,
path: `/universite/${slug}`,
});
}
export function UniversiteIcerik({
slug,
sayfa,
}: {
slug: string;
sayfa: number;
}) {
const uni = getUniversiteBySlug(slug);
if (!uni) notFound();
const sayfaDilim = uniSayfaDilimi(uni, sayfa);
const { tumProgramlar, dilim } = sayfaDilim;
const toplamSayfa = uniToplamSayfa(tumProgramlar.length);
if (sayfa < 1 || sayfa > toplamSayfa) notFound();
const birimler = fakulteOzeti(sayfaDilim);
const gruplar: {
fakulte: string;
programlar: (Program & { bolumSlug: string | null })[];
}[] = [];
for (const p of dilim) {
const anahtar = p.fakulte ?? "Diğer";
const satir = { ...p, bolumSlug: bolumSlugFromIsim(p.isim) };
const son = gruplar[gruplar.length - 1];
if (son && son.fakulte === anahtar) son.programlar.push(satir);
else gruplar.push({ fakulte: anahtar, programlar: [satir] });
}
const ilkSayfa = sayfa === 1;
// Tür iki yerde geçer ve bu bilinçli: H1'in altındaki çip taranmak,
// özet paragrafın ilk virgülündeki öbek okunmak içindir. Izgarada "Tür"
// kartı yok (şablon 4 hücre, bkz. aşağıdaki ızgara yorumu). Haritada
// olmayan ya da NULL bir `unitur`'da öbek hiç yazılmaz: cümlede kopuk
// virgül/boşluk kalmaz ve ham DB değeri cümlenin içine sızmaz.
const turEtiketi = uni.unitur ? UNITUR_ETIKET[uni.unitur] : undefined;
const turCumlesi = turEtiketi?.cumle;
const buYol = ilkSayfa
? `/universite/${slug}`
: `/universite/${slug}/sayfa/${sayfa}`;
const yurtdisi = uniUlkeKonum(uni.ad, uni.il);
// Üst satır ve alt kart aynı soruyu sorar; sayfalama sayfalarında da
// üniversite adı geçer (ziyaretçi Google'dan doğrudan /sayfa/N'ye iniyor).
const ctaBaslik = `Sıralaman ${uni.ad} tabanlarının neresinde?`;
// Tek sayfada çıkan ziyaretçi alttaki karta inmiyor: kompakt sıra girişi
// özet paragrafının hemen altında, tablonun (ve ilk sayfadaki istatistik
// ızgarasının) üstünde durur. Alttaki kart yerinde kalır.
const ustSiraSatiri = (
<div className="mt-8">
<CtaSiraForm varyant="kompakt" konum="ust" baslik={ctaBaslik} />
</div>
);
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: uni.ad, path: `/universite/${slug}` },
...(ilkSayfa ? [] : [{ name: `Sayfa ${sayfa}`, path: buYol }]),
])}
/>
{ilkSayfa ? (
<JsonLd
data={{
"@context": "https://schema.org",
"@type": "CollegeOrUniversity",
name: uni.ad,
url: `${SITE_URL}/universite/${slug}`,
...(uniLogoYolu(slug)
? { logo: `${SITE_URL}${uniLogoYolu(slug)}` }
: {}),
...(uni.il || yurtdisi
? {
address: {
"@type": "PostalAddress",
...(yurtdisi?.sehir
? { addressLocality: yurtdisi.sehir }
: uni.il
? { addressLocality: trBaslikDuzeni(uni.il) }
: {}),
addressCountry: yurtdisi ? yurtdisi.iso : "TR",
},
}
: {}),
}}
/>
) : null}
<nav
aria-label="breadcrumb"
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 />
{ilkSayfa ? (
<span className="text-slate-700">{uni.ad}</span>
) : (
<>
<Link
href={`/universite/${slug}`}
className="transition-colors hover:text-orange-500 hover:underline"
>
{uni.ad}
</Link>
<ChevronRight className="size-3.5 shrink-0" aria-hidden />
<span className="text-slate-700">Sayfa {sayfa}</span>
</>
)}
</nav>
{/* Başlık üst çubuğun altına girince üst çubukta logo + ad belirir */}
<NavBaslik
baslikId="uni-baslik"
ad={uni.ad}
logo={<UniLogo slug={slug} ad={uni.ad} boy="xs" />}
/>
{/* Geniş ekran (lg) altında başlık bloğu ortalı: logo başlığın
üstünde, yol satırı, çipler ve özet paragraf ortada. */}
<div className="mt-6 flex flex-col items-center gap-4 text-center lg:flex-row lg:text-left">
<UniLogo slug={slug} ad={uni.ad} boy="lg" />
<h1
id="uni-baslik"
className="font-heading text-3xl font-bold leading-tight text-slate-900 sm:text-5xl"
>
{uni.ad} Taban Puanları {BASLIK_YILI}
{ilkSayfa ? "" : ` — Sayfa ${sayfa}`}
</h1>
</div>
{/* Kurumun kimliği (tür + il): ızgaradaki kartlar sayısal ve
karşılaştırılabilir metriklerdir, tür ise kategoridir — başlığın
altında iki iri hap olarak durur (navbar'daki hap butonların
ölçüsü: h-10, tam yuvarlak, ikon + tek kelime). Tür renkle değil
ikon + metinle ayrılır. Sayfalama sayfalarında da yazar:
ziyaretçi Google'dan doğrudan /sayfa/N'ye inebiliyor. `unitur`
NULL ise tür hapı düşer, kopuk boşluk kalmaz. */}
{turEtiketi || uni.il ? (
<p className="mt-5 flex flex-wrap items-center justify-center gap-2 lg:justify-start">
{turEtiketi ? (
<span className="inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-800">
{uniturDevletMi(uni.unitur) ? (
<Landmark className="size-4 text-primary" aria-hidden />
) : (
<Building2 className="size-4 text-primary" aria-hidden />
)}
{turEtiketi.cip}
</span>
) : null}
{uni.il ? (
// İl hapı şehir sayfasına gider (Bilal, 24 Eyl 2026: "şehirlerin
// geçtiği her yer tıklanabilir"); hover §2.1 tek turuncu.
<Link
href={ilSayfaHref(uni.il)}
title={`${trBaslikDuzeni(uni.il)} üniversiteleri`}
className="inline-flex h-10 min-w-0 items-center gap-2 rounded-full border border-slate-200 bg-white 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]"
>
<MapPin className="size-4 shrink-0 text-primary" aria-hidden />
<span className="truncate">{trBaslikDuzeni(uni.il)}</span>
</Link>
) : null}
</p>
) : null}
{ilkSayfa ? (
<>
<p className="mx-auto mt-6 max-w-3xl text-center text-base leading-relaxed text-slate-600 sm:text-lg lg:mx-0 lg:text-left">
{uni.ad}
{uni.il ? ` (${trBaslikDuzeni(uni.il)})` : ""}
{turCumlesi ? `, ${turCumlesi};` : ","} {SON_YIL} YKS yerleştirme
sonuçlarına göre{" "}
<strong>{sayi(uni.programSayisi)} programda</strong> öğrenci aldı
{uni.fakulteSayisi > 1
? `; programlar ${sayi(uni.fakulteSayisi)} fakülte/yüksekokula dağılıyor`
: ""}
.
{uni.stats.minSira != null ? (
<>
{" "}
En iyi taban başarı sıralaması{" "}
<strong>{sayi(uni.stats.minSira)}</strong>.
</>
) : null}
{uni.stats.toplamKontenjan > 0 ? (
<> Toplam kontenjan {sayi(uni.stats.toplamKontenjan)}.</>
) : null}{" "}
Aşağıdaki tablolar {TERCIH_YILI} tercihleri için rehber
niteliğindedir;{" "}
<em>
{TERCIH_YILI} taban puanları ve sıralamaları, yerleştirme
sonuçları açıklandığında netleşir.
</em>
</p>
{ustSiraSatiri}
{/* relative isolate: konum haritası ızgaranın arkasına serbest
katman olarak yayılır, dört kartın altına sarkar (Bilal, 24 Eyl
2026); üçüncü sütun yalnız yer tutar. */}
<div className="relative isolate mt-8 grid grid-cols-2 gap-4 lg:grid-cols-[1fr_1fr_1.35fr] lg:grid-rows-2">
<UniversiteKonumHaritasi universite={uni.ad} il={uni.il} />
{/* Şablon tam 4 hücre bırakıyor: masaüstünde harita 3. sütunu
iki satır boyunca kaplıyor, 5. kart ızgarayı bozar. Fakülte
sayısı Program kartının alt satırında; boşalan hücrede
Türkiye sırası. Geri kalan akademik ayrıntı alttaki
"Akademik göstergeler ›" penceresinde. */}
<dl className="contents">
<OzetKart
ikon={LayoutList}
etiket="Program"
deger={sayi(uni.programSayisi)}
alt={`${sayi(uni.fakulteSayisi)} birimde`}
/>
<OzetKart
ikon={Users}
etiket="Kontenjan"
deger={sayi(uni.stats.toplamKontenjan)}
alt={`${SON_YIL} toplamı`}
/>
<OzetKart
ikon={Target}
etiket="En iyi sıralama"
deger={
uni.stats.minSira != null ? sayi(uni.stats.minSira) : "—"
}
alt={`${SON_YIL} taban sırası`}
/>
{uni.urap ? (
<TurkiyeSirasiKarti sira={uni.urap.sira} />
) : (
<OzetKart
ikon={Building2}
etiket="Fakülte / birim"
deger={sayi(uni.fakulteSayisi)}
alt="programların dağıldığı"
/>
)}
</dl>
</div>
{uni.urap || uni.istatistik ? (
<AkademikAyrinti
universite={uni.ad}
onizleme={akademikOnizleme(uni)}
>
<AkademikGostergeler uni={uni} />
</AkademikAyrinti>
) : null}
</>
) : (
<>
<p className="mx-auto mt-6 max-w-3xl text-center text-base leading-relaxed text-slate-600 sm:text-lg lg:mx-0 lg:text-left">
{uni.ad} taban puanları tablosunun {sayfa}. sayfası (
{sayi(tumProgramlar.length)} programın{" "}
{sayi((sayfa - 1) * SAYFA_BOYU + 1)}–
{sayi(Math.min(sayfa * SAYFA_BOYU, tumProgramlar.length))}.
satırları, fakülte sırasıyla).
{birimler ? ` Bu sayfadaki birimler: ${birimler}.` : ""}{" "}
Üniversite özeti ve istatistikler{" "}
<Link
href={`/universite/${slug}`}
className="font-medium text-primary transition-colors hover:text-orange-500 hover:underline"
>
ilk sayfada
</Link>
.
</p>
{ustSiraSatiri}
</>
)}
<PagePixelDivider seed={57} className="mx-auto mt-12 lg:mx-0" />
<div id="programlar" className="mt-10 text-center lg:text-left">
<h2 className="font-heading text-2xl font-bold text-slate-900 sm:text-3xl">
Üniversite programları
</h2>
<p className="mt-2 text-base text-slate-500">
Fakülte ve birimlere göre
</p>
</div>
<UniversiteProgramTablosu gruplar={gruplar} />
<VeriKaynagi>
&quot;—&quot;: o yıl için yerleştirme verisi bulunmuyor (yeni açılan
veya o yıl yerleşen çıkmayan program).{" "}
</VeriKaynagi>
{ilkSayfa ? <UrapKaynakSatiri uni={uni} /> : null}
{ilkSayfa ? <IstatistikKaynakSatiri uni={uni} /> : null}
<Sayfalama
tabanYol={`/universite/${slug}`}
sayfa={sayfa}
toplamSayfa={toplamSayfa}
/>
<div className="mt-10">
<CtaSiraForm
konum="alt"
baslik={ctaBaslik}
aciklama={KATALOG_CTA_ACIKLAMA}
/>
</div>
</main>
<SiteFooter />
</>
);
}
export function UniversiteIcerikSkeleton() {
return (
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
<Skeleton className="h-5 w-64" />
<div className="mt-6 flex items-center gap-4">
<Skeleton className="size-14 rounded-lg" />
<Skeleton className="h-10 w-2/3 max-w-xl" />
</div>
<Skeleton className="mt-4 h-5 w-full max-w-3xl" />
<div className="mt-8 grid grid-cols-2 gap-4 lg:grid-cols-[1fr_1fr_1.35fr] lg:grid-rows-2">
{Array.from({ length: 5 }, (_, i) => (
<Skeleton key={i} className="h-28 w-full rounded-2xl" />
))}
</div>
<div className="mt-16 space-y-3">
<Skeleton className="h-7 w-64" />
{Array.from({ length: 5 }, (_, i) => (
<Skeleton key={i} className="h-20 w-full rounded-2xl" />
))}
</div>
</main>
);
}