feat(katalog+tasarim): devlet/vakıf tür sayfaları, tür çipleri link; kayıtlı-sıra satırı şişko ve sade
All checks were successful
Deploy / deploy (push) Successful in 5m1s
All checks were successful
Deploy / deploy (push) Successful in 5m1s
- /universiteler/devlet ve /universiteler/vakif: şehir sayfası iskeleti, haritada bu türden üniversitesi olan her il turuncu (yakınlaşma yok), üç özet kartı, şehir çipleri (sayfa içi çapa), şehir şehir §4.15 satırları, öteki türe geçiş hapı; metadata + sitemap. - TurCipi tek kaynak: devlet/vakıf çipi tür sayfasına link (sonuç, listem, tadımlık, bölüm, şehir/tür satırları); üniversite sayfası tür hapı ve /universiteler başlıkları da link. UniSatirlari ortak dosyada: ad, tür ve il ayrı linkler. - Kompakt profilli satır (DESIGN.md §10.9): iri kupa kutusu + text-3xl/4xl sıra, text-xl/2xl başlık, 48 px buton; telefonda alt alta, lg'de tek satır. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -352,8 +352,8 @@ aşağı kaymış, kare boyları farklı — ne tepeler ne bitişler hizalı.**
|
||||
Başlığın altında kurumun kimliği; navbar haplarının ölçüsünde:
|
||||
`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`
|
||||
+ başta `size-4 text-primary` ikon (`Landmark` devlet, `Building2` vakıf, `MapPin` il). Tür
|
||||
renkle değil ikonla ayrılır. Rozet (§4.2) değildir. Tür hapı etkileşimsizdir; **il hapı şehir
|
||||
sayfasına link**tir (Bilal, 24 Eyl 2026: "şehirlerin geçtiği her yer tıklanabilir") — aynı
|
||||
renkle değil ikonla ayrılır. Rozet (§4.2) değildir. **Devlet/vakıf hapı tür sayfasına**
|
||||
(§4.18), **il hapı şehir sayfasına link**tir (Bilal, 24 Eyl 2026: "şehirlerin geçtiği her yer tıklanabilir") — aynı
|
||||
sınıf + `hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]`.
|
||||
Kural genel: bir şehir adı nerede geçerse geçsin (`/universiteler` listesi, bölüm/sonuç/listem/
|
||||
tadımlık satırlarındaki tür-il-süre metası) `ilSayfaHref(il)` ile `/universiteler/[il]`'e
|
||||
@@ -493,6 +493,22 @@ bulunulan şehir `border-primary bg-primary text-white` + `aria-current="page"`.
|
||||
üst satır). Kareler büyür, il kadraja gelir; ısı fırçası viewBox'ı
|
||||
canlı okur. Hareketi azalt açıkken kutuya doğrudan atlanır. Sayfa başına tek yakınlaşma.
|
||||
|
||||
### 4.18 Tür sayfaları — `/universiteler/devlet`, `/universiteler/vakif` (`tur-icerik.tsx`)
|
||||
Bilal (24 Eyl 2026): "vakıf ve devlet de tıklanabilir olmalı, özel sayfalar yapabilirsin,
|
||||
kreatif". Şehir sayfasının iskeleti (§4.17) ile: yol satırı, `SehirYuzeyi` başlık yüzeyi,
|
||||
kimlik hapları (tür + öteki türe geçiş hapı), özet paragraf, kompakt `CtaSiraForm`, üç
|
||||
`OzetKart` (üniversite / program / şehir; `grid grid-cols-2 gap-4 lg:grid-cols-3`, üçüncü
|
||||
telefonda tam genişlik), "Şehre göre" çipleri (sayfa içi `#sehir-…` çapaları), şehir şehir
|
||||
§4.15 `UniSatirlari`, altta mavi CTA bandı.
|
||||
- **Kreatif:** başlık yüzeyindeki dot-matrix Türkiye'de bu türden üniversitesi olan **her il**
|
||||
turuncu yanar (`TurkiyePikselHaritasi iller=[…]`), yakınlaşma yok — "devlet üniversiteleri
|
||||
Türkiye'nin neresinde" tek bakışta. Vakıfta harita seyrek yanar; bu bilginin kendisidir.
|
||||
- Tür çipi tek kaynak `TurCipi` (`src/components/tur-cipi.tsx`): devlet/vakıf (ve Vakıf MYO)
|
||||
tür sayfasına link, KKTC/yurt dışı düz çip; `/sonuc`, `/listem`, tadımlık, bölüm sayfası ve
|
||||
şehir/tür satırları bunu kullanır. Satırın tamamı link olan yerde kullanılmaz.
|
||||
- `/universiteler` dizinindeki "Devlet Üniversiteleri" / "Vakıf Üniversiteleri" başlıkları da
|
||||
tür sayfasına link.
|
||||
|
||||
## 5. Yerleşim
|
||||
|
||||
### 5.1 Genel
|
||||
@@ -812,3 +828,14 @@ yok** (Bilal, 24 Eyl 2026: kaldırıldı); `FUNNEL_METINLERI` yalnız arama penc
|
||||
- Çekmece başlığında denenen "gerçek liste" pikseli (12×2, her kare tercihin riski) **kaldırıldı**:
|
||||
başlığı çok dolu gösteriyordu (Bilal, 24 Eyl 2026). Başlık sayı + ilerleme çubuğu + risk
|
||||
dağılımı metniyle kalır.
|
||||
|
||||
### 10.9 Kayıtlı sıra satırı (katalog üstü, profilli) — `cta-sira-form.tsx` kompakt
|
||||
Bilal (24 Eyl 2026): "bu kısım UI kötü, Apple görünümü yok, yazılar küçük". §1.1 "sayı
|
||||
kahramandır" uygulanır: mavi bantta (§10.8 kompakt kabuk + `PikselZemin`) **iri kupa kutusu**
|
||||
(`flex size-14 items-center justify-center rounded-2xl bg-white/15` + `Trophy size-7`), yanında
|
||||
sıra `font-heading text-3xl font-bold leading-none tracking-tight sm:text-4xl` (Cal Sans'ta
|
||||
tabular-nums yok, §3) ve
|
||||
altında `text-sm font-semibold text-white/80` "sıra · Sayısal"; başlık `font-heading text-xl
|
||||
font-bold leading-tight text-balance sm:text-2xl`; buton 48 px turuncu, ok sonda. Telefonda
|
||||
başlık → sayı → buton alt alta ortalı; lg'de tek satır (sayı · başlık · buton). Yükseklik her
|
||||
iki hâlde formu aşmaz (CLS).
|
||||
|
||||
@@ -23,6 +23,18 @@ const staticEntries: MetadataRoute.Sitemap = [
|
||||
priority: 0.8,
|
||||
changeFrequency: "weekly",
|
||||
},
|
||||
{
|
||||
url: `${SITE_URL}/universiteler/devlet`,
|
||||
lastModified: KATALOG_VERI_TARIHI,
|
||||
priority: 0.7,
|
||||
changeFrequency: "monthly",
|
||||
},
|
||||
{
|
||||
url: `${SITE_URL}/universiteler/vakif`,
|
||||
lastModified: KATALOG_VERI_TARIHI,
|
||||
priority: 0.7,
|
||||
changeFrequency: "monthly",
|
||||
},
|
||||
{
|
||||
url: `${SITE_URL}/universite-siralamasi`,
|
||||
priority: 0.7,
|
||||
|
||||
22
src/app/universiteler/devlet/page.tsx
Normal file
22
src/app/universiteler/devlet/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Suspense } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
import {
|
||||
TurIcerik,
|
||||
TurIcerikSkeleton,
|
||||
turSayfaMetadata,
|
||||
} from "@/features/katalog/components/tur-icerik";
|
||||
|
||||
// Statik segment: /universiteler/[il] dinamik rotasından önce eşleşir.
|
||||
export const metadata: Metadata = turSayfaMetadata("devlet");
|
||||
|
||||
export default function DevletPage() {
|
||||
return (
|
||||
<>
|
||||
<Suspense fallback={<TurIcerikSkeleton />}>
|
||||
<TurIcerik tur="devlet" />
|
||||
</Suspense>
|
||||
<SiteFooter />
|
||||
</>
|
||||
);
|
||||
}
|
||||
22
src/app/universiteler/vakif/page.tsx
Normal file
22
src/app/universiteler/vakif/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Suspense } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
import {
|
||||
TurIcerik,
|
||||
TurIcerikSkeleton,
|
||||
turSayfaMetadata,
|
||||
} from "@/features/katalog/components/tur-icerik";
|
||||
|
||||
// Statik segment: /universiteler/[il] dinamik rotasından önce eşleşir.
|
||||
export const metadata: Metadata = turSayfaMetadata("vakif");
|
||||
|
||||
export default function VakifPage() {
|
||||
return (
|
||||
<>
|
||||
<Suspense fallback={<TurIcerikSkeleton />}>
|
||||
<TurIcerik tur="vakif" />
|
||||
</Suspense>
|
||||
<SiteFooter />
|
||||
</>
|
||||
);
|
||||
}
|
||||
41
src/components/tur-cipi.tsx
Normal file
41
src/components/tur-cipi.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import Link from "next/link";
|
||||
import { SATIR_CIP } from "@/components/liste-satiri";
|
||||
import { UNITUR_ETIKET, turSayfaHref } from "@/lib/unitur";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* Liste satırlarındaki tür çipi (DESIGN.md §4.15) — tek kaynak. Devlet ve
|
||||
* vakıf tür sayfasına link (Bilal, 24 Eyl 2026: "vakıf ve devlet de
|
||||
* tıklanabilir olmalı"); KKTC/yurt dışı gibi tür sayfası olmayanlar düz çip.
|
||||
* Hover §2.1 tek turuncu. Satırın tamamı link olan yerde kullanılmaz
|
||||
* (iç içe <a>).
|
||||
*/
|
||||
export function TurCipi({
|
||||
unitur,
|
||||
className,
|
||||
onClick,
|
||||
}: {
|
||||
unitur: string | null | undefined;
|
||||
className?: string;
|
||||
/** Tıklanabilir satırın içinde olayın satıra yayılmasını kesmek için */
|
||||
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
|
||||
}) {
|
||||
if (!unitur) return null;
|
||||
const etiket = UNITUR_ETIKET[unitur]?.cip ?? unitur;
|
||||
const href = turSayfaHref(unitur);
|
||||
if (!href) return <span className={cn(SATIR_CIP, className)}>{etiket}</span>;
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
title={`Tüm ${etiket.toLocaleLowerCase("tr-TR")} üniversiteleri`}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
SATIR_CIP,
|
||||
"transition-colors hover:bg-orange-50/60 hover:text-orange-500",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{etiket}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import Link from "next/link";
|
||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
import type { Program } from "@/types/yokatlas";
|
||||
import { ilSayfaHref, trBaslikDuzeni } from "@/lib/slug";
|
||||
import { TurCipi } from "@/components/tur-cipi";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { UNITUR_ETIKET } from "@/lib/unitur";
|
||||
import { VERI_YIL_SAYISI } from "@/lib/veri-yillari";
|
||||
import { Sayfalama, SAYFA_BOYU } from "./sayfalama";
|
||||
import {
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
SATIR_BUTON_BASLIK,
|
||||
SATIR_BUTON_HUCRE,
|
||||
SATIR_BUTON_KABI,
|
||||
SATIR_CIP,
|
||||
SATIR_GRUP,
|
||||
SATIR_IKINCIL_LINK,
|
||||
SATIR_META,
|
||||
@@ -242,12 +241,7 @@ export function BolumProgramListesi({
|
||||
</span>
|
||||
)}
|
||||
<span className={SATIR_META}>
|
||||
{p.unitur ? (
|
||||
<span className={SATIR_CIP}>
|
||||
{UNITUR_ETIKET[p.unitur]?.cip ??
|
||||
trBaslikDuzeni(p.unitur)}
|
||||
</span>
|
||||
) : null}
|
||||
<TurCipi unitur={p.unitur} />
|
||||
{p.il ? (
|
||||
<Link
|
||||
href={ilSayfaHref(p.il)}
|
||||
|
||||
@@ -2,16 +2,6 @@ import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Building2, ChevronRight, Landmark, MapPin } from "lucide-react";
|
||||
import {
|
||||
SATIR_AD_DUZ,
|
||||
SATIR_CIP,
|
||||
SATIR_GRUP,
|
||||
SATIR_META,
|
||||
} from "@/components/liste-satiri";
|
||||
import { UniLogo } from "@/components/uni-logo";
|
||||
import { trBaslikDuzeni } from "@/lib/slug";
|
||||
import { UNITUR_ETIKET } from "@/lib/unitur";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
getAllIller,
|
||||
getAllIlSlugs,
|
||||
@@ -32,6 +22,7 @@ import { NavBaslik } from "./nav-baslik";
|
||||
import { SehirCipleri, oneCikanIller } from "./sehir-cipleri";
|
||||
import { SehirPikselHaritasi } from "./sehir-piksel-haritasi";
|
||||
import { SehirYuzeyi } from "./sehir-yuzeyi";
|
||||
import { UniSatirlari } from "./uni-satirlari";
|
||||
|
||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||
|
||||
@@ -55,57 +46,6 @@ export function ilSayfaMetadata(slug: string): Metadata {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Şehir sayfasının üniversite satırları — DESIGN.md §4.15 liste satırı
|
||||
* (Bilal, 24 Eyl 2026: "il kısmında bulunan üniler küçük gözüküyor"): 48 px
|
||||
* logo, `text-base font-semibold` ad, altında tür çipi + program sayısı,
|
||||
* satırın tamamı üniversite sayfasına link (44 px'ten iri dokunma hedefi).
|
||||
* Ad hover'ı §2.1 istisnasıyla mavi, satır zemini turuncuya döner, sağdaki
|
||||
* ok kayar. Üniversitenin ana ili bu sayfadan farklıysa (ODTÜ KKTC sayfasında)
|
||||
* il de metada yazar.
|
||||
*/
|
||||
function UniSatirlari({ uniler, buIl }: { uniler: UniOzet[]; buIl: string }) {
|
||||
return (
|
||||
<ol className="mt-4 divide-y divide-slate-100 overflow-hidden rounded-xl border border-slate-200 bg-white text-left">
|
||||
{uniler.map((u) => {
|
||||
const tur = u.unitur ? UNITUR_ETIKET[u.unitur]?.cip : null;
|
||||
const digerIl = u.il && u.il !== buIl ? trBaslikDuzeni(u.il) : null;
|
||||
return (
|
||||
<li key={u.slug}>
|
||||
<Link
|
||||
href={`/universite/${u.slug}`}
|
||||
className="group flex items-start gap-3 px-3 py-4 transition-[background-color,transform] duration-150 hover:bg-orange-50/60 active:scale-[0.99] sm:items-center sm:gap-4 sm:px-5 lg:py-3"
|
||||
>
|
||||
<UniLogo slug={u.slug} ad={u.ad} boy="sm" />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className={SATIR_GRUP}>
|
||||
<span
|
||||
className={cn(
|
||||
SATIR_AD_DUZ,
|
||||
"transition-colors group-hover:text-primary group-hover:underline",
|
||||
)}
|
||||
>
|
||||
{u.ad}
|
||||
</span>
|
||||
<span className={SATIR_META}>
|
||||
{tur ? <span className={SATIR_CIP}>{tur}</span> : null}
|
||||
{digerIl ? <span className="shrink-0">{digerIl}</span> : null}
|
||||
<span className="shrink-0">{sayi(u.programSayisi)} program</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<ChevronRight
|
||||
className="mt-3 size-4 shrink-0 text-slate-400 transition-[color,transform] duration-150 group-hover:translate-x-0.5 group-hover:text-orange-500 sm:mt-0"
|
||||
aria-hidden
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
);
|
||||
}
|
||||
|
||||
function UniGrubu({
|
||||
baslik,
|
||||
uniler,
|
||||
@@ -124,7 +64,7 @@ function UniGrubu({
|
||||
{sayi(uniler.length)}
|
||||
</span>
|
||||
</h2>
|
||||
<UniSatirlari uniler={uniler} buIl={buIl} />
|
||||
<UniSatirlari uniler={uniler} gizliIl={buIl} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -237,14 +237,23 @@ function PikselSvg({
|
||||
|
||||
const TUM_IL_HUCRELERI = Object.values(IL_HUCRELERI).flat();
|
||||
|
||||
/** Türkiye; `il` (DB biçimi, ör. "İZMİR") turuncu karelerle yanar. */
|
||||
/**
|
||||
* Türkiye; `il` (DB biçimi, ör. "İZMİR") turuncu karelerle yanar. `iller`
|
||||
* verilirse hepsi yanar (tür sayfası: devlet üniversitesi olan her il) ve
|
||||
* yakınlaşma yapılmaz — ülkenin tamamı görünür.
|
||||
*/
|
||||
export function TurkiyePikselHaritasi({
|
||||
il,
|
||||
iller,
|
||||
...props
|
||||
}: HaritaProps & { il: string }) {
|
||||
const harita = markup(`tr:${il}`, () =>
|
||||
markupUret(TUM_IL_HUCRELERI, IL_HUCRELERI[il] ?? [], null),
|
||||
);
|
||||
}: HaritaProps & { il?: string; iller?: readonly string[] }) {
|
||||
const liste = iller ?? (il ? [il] : []);
|
||||
const anahtar = `tr:${liste.join("|")}`;
|
||||
const harita = markup(anahtar, () => {
|
||||
const vurgu = liste.flatMap((ad) => IL_HUCRELERI[ad] ?? []);
|
||||
const h = markupUret(TUM_IL_HUCRELERI, vurgu, null);
|
||||
return iller ? { ...h, sinir: null } : h;
|
||||
});
|
||||
return <PikselSvg harita={harita} {...props} />;
|
||||
}
|
||||
|
||||
|
||||
279
src/features/katalog/components/tur-icerik.tsx
Normal file
279
src/features/katalog/components/tur-icerik.tsx
Normal file
@@ -0,0 +1,279 @@
|
||||
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-white 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-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]"
|
||||
>
|
||||
<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'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-white 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>
|
||||
);
|
||||
}
|
||||
67
src/features/katalog/components/uni-satirlari.tsx
Normal file
67
src/features/katalog/components/uni-satirlari.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import Link from "next/link";
|
||||
import {
|
||||
SATIR_AD,
|
||||
SATIR_GRUP,
|
||||
SATIR_IKINCIL_LINK,
|
||||
SATIR_META,
|
||||
} from "@/components/liste-satiri";
|
||||
import { TurCipi } from "@/components/tur-cipi";
|
||||
import { UniLogo } from "@/components/uni-logo";
|
||||
import { ilSayfaHref, trBaslikDuzeni } from "@/lib/slug";
|
||||
import type { UniOzet } from "../katalog-queries";
|
||||
|
||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||
|
||||
/**
|
||||
* Şehir ve tür sayfalarının üniversite satırları — DESIGN.md §4.15 liste
|
||||
* satırı (Bilal, 24 Eyl 2026: "il kısmında üniler küçük gözüküyor"): 48 px
|
||||
* logo, `SATIR_AD` ad (hover mavi, §2.1 istisnası), altında tür çipi
|
||||
* (tür sayfasına link) + il (şehir sayfasına link) + program sayısı. Satırın
|
||||
* tamamı link değildir: tür ve il ayrı hedeflere gider (iç içe <a> olmaz).
|
||||
* `gizliIl`: bulunulan şehir sayfasında aynı il yazılmaz.
|
||||
*/
|
||||
export function UniSatirlari({
|
||||
uniler,
|
||||
gizliIl,
|
||||
}: {
|
||||
uniler: UniOzet[];
|
||||
gizliIl?: string | null;
|
||||
}) {
|
||||
return (
|
||||
<ol className="mt-4 divide-y divide-slate-100 overflow-hidden rounded-xl border border-slate-200 bg-white text-left">
|
||||
{uniler.map((u) => {
|
||||
const il = u.il && u.il !== gizliIl ? u.il : null;
|
||||
return (
|
||||
<li
|
||||
key={u.slug}
|
||||
className="flex items-start gap-3 px-3 py-4 sm:items-center sm:gap-4 sm:px-5 lg:py-3"
|
||||
>
|
||||
<Link href={`/universite/${u.slug}`} tabIndex={-1} aria-hidden>
|
||||
<UniLogo slug={u.slug} ad={u.ad} boy="sm" />
|
||||
</Link>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className={SATIR_GRUP}>
|
||||
<Link href={`/universite/${u.slug}`} className={SATIR_AD}>
|
||||
{u.ad}
|
||||
</Link>
|
||||
<span className={SATIR_META}>
|
||||
<TurCipi unitur={u.unitur} />
|
||||
{il ? (
|
||||
<Link
|
||||
href={ilSayfaHref(il)}
|
||||
className={`shrink-0 ${SATIR_IKINCIL_LINK}`}
|
||||
title={`${trBaslikDuzeni(il)} üniversiteleri`}
|
||||
>
|
||||
{trBaslikDuzeni(il)}
|
||||
</Link>
|
||||
) : null}
|
||||
<span className="shrink-0">{sayi(u.programSayisi)} program</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
bolumSlugFromIsim,
|
||||
} from "../katalog-queries";
|
||||
import { ilSayfaHref, trBaslikDuzeni } from "@/lib/slug";
|
||||
import { UNITUR_ETIKET, uniturDevletMi } from "@/lib/unitur";
|
||||
import { UNITUR_ETIKET, turSayfaHref, uniturDevletMi } from "@/lib/unitur";
|
||||
import {
|
||||
JsonLd,
|
||||
breadcrumbJsonLd,
|
||||
@@ -569,14 +569,31 @@ export function UniversiteIcerik({
|
||||
{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) ? (
|
||||
// Devlet/vakıf hapı tür sayfasına gider (DESIGN.md §4.12); KKTC
|
||||
// ve yurt dışında tür sayfası yok, hap düz kalır.
|
||||
(() => {
|
||||
const href = turSayfaHref(uni.unitur);
|
||||
const ikon = uniturDevletMi(uni.unitur) ? (
|
||||
<Landmark className="size-4 text-primary" aria-hidden />
|
||||
) : (
|
||||
<Building2 className="size-4 text-primary" aria-hidden />
|
||||
)}
|
||||
);
|
||||
return href ? (
|
||||
<Link
|
||||
href={href}
|
||||
title={`Tüm ${turEtiketi.cip.toLocaleLowerCase("tr-TR")} üniversiteleri`}
|
||||
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 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]"
|
||||
>
|
||||
{ikon}
|
||||
{turEtiketi.cip}
|
||||
</Link>
|
||||
) : (
|
||||
<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">
|
||||
{ikon}
|
||||
{turEtiketi.cip}
|
||||
</span>
|
||||
);
|
||||
})()
|
||||
) : null}
|
||||
{uni.il ? (
|
||||
// İl hapı şehir sayfasına gider (Bilal, 24 Eyl 2026: "şehirlerin
|
||||
|
||||
@@ -107,7 +107,12 @@ export function UniversiteKatalogu() {
|
||||
|
||||
<section className="mt-8">
|
||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||
<Link
|
||||
href="/universiteler/devlet"
|
||||
className="transition-colors hover:text-orange-500 hover:underline"
|
||||
>
|
||||
Devlet Üniversiteleri
|
||||
</Link>
|
||||
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||
{sayi(devlet.length)}
|
||||
</span>
|
||||
@@ -117,7 +122,12 @@ export function UniversiteKatalogu() {
|
||||
|
||||
<section className="mt-10">
|
||||
<h2 className="font-heading text-2xl font-bold text-slate-900">
|
||||
<Link
|
||||
href="/universiteler/vakif"
|
||||
className="transition-colors hover:text-orange-500 hover:underline"
|
||||
>
|
||||
Vakıf Üniversiteleri
|
||||
</Link>
|
||||
<span className="ml-2 text-sm font-normal text-slate-400">
|
||||
{sayi(vakif.length)}
|
||||
</span>
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {
|
||||
} from "@/types/yokatlas";
|
||||
import { DILIM_ETIKET, riskHesapla } from "@/lib/risk";
|
||||
import { bolumSayfaSlug, ilSayfaHref, trBaslikDuzeni, uniSayfaSlug } from "@/lib/slug";
|
||||
import { TurCipi } from "@/components/tur-cipi";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { VERI_YIL_SAYISI } from "@/lib/veri-yillari";
|
||||
import {
|
||||
@@ -52,7 +53,6 @@ import {
|
||||
SATIR_BUTON_BASLIK,
|
||||
SATIR_BUTON_HUCRE,
|
||||
SATIR_BUTON_KABI,
|
||||
SATIR_CIP,
|
||||
SATIR_ALT_GRUP,
|
||||
SATIR_IKINCIL_LINK,
|
||||
SATIR_META,
|
||||
@@ -334,7 +334,6 @@ function ProgramSatiri({
|
||||
}) {
|
||||
const taban = programEtkinSira(p);
|
||||
const risk = riskHesapla(taban, adaySira);
|
||||
const devlet = p.unitur === "DEVLET";
|
||||
const tabanMetni = taban != null ? `~${taban.toLocaleString("tr-TR")}.` : "—";
|
||||
const bosKaldi =
|
||||
p.kontenjanSon != null &&
|
||||
@@ -373,7 +372,10 @@ function ProgramSatiri({
|
||||
</Link>
|
||||
</span>
|
||||
<span className={SATIR_META}>
|
||||
<span className={SATIR_CIP}>{devlet ? "Devlet" : "Vakıf"}</span>
|
||||
<TurCipi
|
||||
unitur={p.unitur}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
{p.il ? (
|
||||
<Link
|
||||
href={ilSayfaHref(p.il)}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { bolumSayfaSlug, ilSayfaHref, trBaslikDuzeni, uniSayfaSlug } from "@/lib/slug";
|
||||
import { TurCipi } from "@/components/tur-cipi";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { UNITUR_ETIKET } from "@/lib/unitur";
|
||||
import { RiskEtiketi } from "@/components/risk-etiketi";
|
||||
@@ -38,7 +39,6 @@ import {
|
||||
SATIR_BASLIK,
|
||||
SATIR_BUTON_BASLIK,
|
||||
SATIR_BUTON_HUCRE,
|
||||
SATIR_CIP,
|
||||
SATIR_DUGME_BOYU,
|
||||
SATIR_ALT_GRUP,
|
||||
SATIR_IKINCIL_LINK,
|
||||
@@ -201,7 +201,6 @@ export function RaporListesi({
|
||||
const seri = raporSiraSerisi(p?.siraGecmisi, p?.ilkYil);
|
||||
const taban = p?.efektifSira ?? programEtkinSira(seri);
|
||||
const acikMi = acik.has(t.sira);
|
||||
const devlet = p?.unitur === "DEVLET";
|
||||
const tabanMetni = taban != null ? `~${taban.toLocaleString("tr-TR")}.` : "—";
|
||||
const riskHucresi = analizKilitli ? (
|
||||
<span className="inline-flex items-center gap-1.5 text-xs font-semibold text-slate-500">
|
||||
@@ -263,9 +262,10 @@ export function RaporListesi({
|
||||
</span>
|
||||
) : null}
|
||||
<span className={SATIR_META}>
|
||||
{p?.unitur ? (
|
||||
<span className={SATIR_CIP}>{devlet ? "Devlet" : "Vakıf"}</span>
|
||||
) : null}
|
||||
<TurCipi
|
||||
unitur={p?.unitur}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
{p?.il ? (
|
||||
<Link
|
||||
href={ilSayfaHref(p.il)}
|
||||
|
||||
@@ -26,10 +26,10 @@ import { olay, siraKovasi } from "@/lib/analitik";
|
||||
import { dilimdenRisk, riskHesapla } from "@/lib/risk";
|
||||
import { ilSayfaHref, trBaslikDuzeni, turkishSlugify } from "@/lib/slug";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { TurCipi } from "@/components/tur-cipi";
|
||||
import {
|
||||
SATIR_ALT_AD,
|
||||
SATIR_AD_DUZ,
|
||||
SATIR_CIP,
|
||||
SATIR_ALT_GRUP,
|
||||
SATIR_IKINCIL_LINK,
|
||||
SATIR_META,
|
||||
@@ -123,7 +123,6 @@ export function TadimlikSatiri({
|
||||
const risk = riskHesapla(p.efektifSira, sira) ?? dilimdenRisk(satir.dilim);
|
||||
const seri = raporSiraSerisi(p.siraGecmisi, p.ilkYil);
|
||||
const taban = p.efektifSira ?? programEtkinSira(seri);
|
||||
const devlet = p.unitur === "DEVLET";
|
||||
// Giriş dönüşü kullanıcıyı /sonuc'a geri bırakmaz: callback doğrudan
|
||||
// üretimin koştuğu /listem'e iner, liste ek tık olmadan orada başlar.
|
||||
const girisUrl = `/giris?callback=${encodeURIComponent(listemUretHref("tadimlik"))}`;
|
||||
@@ -164,9 +163,7 @@ export function TadimlikSatiri({
|
||||
{trBaslikDuzeni(p.universite)}
|
||||
</span>
|
||||
<span className={SATIR_META}>
|
||||
{p.unitur ? (
|
||||
<span className={SATIR_CIP}>{devlet ? "Devlet" : "Vakıf"}</span>
|
||||
) : null}
|
||||
<TurCipi unitur={p.unitur} />
|
||||
{p.il ? (
|
||||
<Link
|
||||
href={ilSayfaHref(p.il)}
|
||||
|
||||
@@ -408,17 +408,39 @@ export function CtaSiraForm({
|
||||
<div className="mt-3">{siraFormu}</div>
|
||||
</div>
|
||||
{profil ? (
|
||||
<div className="col-start-1 row-start-1 flex flex-col items-center justify-center gap-3 text-center sm:flex-row sm:justify-between sm:gap-6 sm:text-left">
|
||||
<div className="flex min-w-0 flex-col items-center gap-1 sm:items-start">
|
||||
// Şişko ve sade (DESIGN.md §1.1, §10.9): sayı kahramandır — iri
|
||||
// kupa kutusu + text-3xl/4xl sıra, başlık text-xl/2xl, 48 px buton.
|
||||
// Telefonda başlık → sayı → buton alt alta; lg'de tek satır
|
||||
// (sayı · başlık · buton). Yükseklik formunkini aşmaz (CLS notu).
|
||||
<div className="col-start-1 row-start-1 flex flex-col items-center justify-center gap-4 text-center lg:flex-row lg:gap-6 lg:text-left">
|
||||
<p
|
||||
id={baslikId}
|
||||
className="font-heading text-base font-bold leading-6 text-white"
|
||||
className="order-1 min-w-0 flex-1 font-heading text-xl font-bold leading-tight text-balance text-white sm:text-2xl lg:order-2"
|
||||
>
|
||||
{kompaktBaslik}
|
||||
</p>
|
||||
<KayitliSira sira={profil.sira} tur={profil.tur} beyaz />
|
||||
</div>
|
||||
<Button asChild size="lg" className={cn(PROFILLI_CTA_SINIF, "w-full sm:w-auto")}>
|
||||
<p className="order-2 flex items-center gap-3 lg:order-1 lg:shrink-0">
|
||||
<span className="flex size-14 shrink-0 items-center justify-center rounded-2xl bg-white/15">
|
||||
<Trophy className="size-7 text-white" aria-hidden />
|
||||
</span>
|
||||
<span className="flex flex-col items-start text-left">
|
||||
<span className="sr-only">Sıralaman kayıtlı:</span>
|
||||
<span className="font-heading text-3xl font-bold leading-none tracking-tight text-white sm:text-4xl">
|
||||
{profil.sira.toLocaleString("tr-TR")}.
|
||||
</span>
|
||||
<span className="mt-1 text-sm font-semibold text-white/80">
|
||||
sıra · {turEtiketi(profil.tur)}
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className={cn(
|
||||
PROFILLI_CTA_SINIF,
|
||||
"order-3 w-full active:scale-[0.97] sm:w-auto lg:shrink-0",
|
||||
)}
|
||||
>
|
||||
<Link href={sonucHref()}>
|
||||
Sıralamana uygun programları gör
|
||||
<ArrowRight className="size-4" aria-hidden />
|
||||
|
||||
@@ -35,3 +35,39 @@ export const UNITUR_ETIKET: Record<string, { cip: string; cumle: string }> = {
|
||||
*/
|
||||
export const uniturDevletMi = (unitur: string | null | undefined) =>
|
||||
unitur === "DEVLET";
|
||||
|
||||
/**
|
||||
* Tür sayfaları (/universiteler/devlet, /universiteler/vakif; DESIGN.md
|
||||
* §4.18). Bilal, 24 Eyl 2026: "vakıf ve devlet de tıklanabilir olmalı".
|
||||
* Vakıf MYO'lar vakıf sayfasında sayılır; KKTC ve yurt dışının tür sayfası
|
||||
* yoktur (şehir sayfalarında zaten ayrı öbek).
|
||||
*/
|
||||
export const TUR_SAYFALARI = {
|
||||
devlet: {
|
||||
slug: "devlet",
|
||||
ad: "Devlet Üniversiteleri",
|
||||
tekil: "devlet üniversitesi",
|
||||
uniturler: ["DEVLET"],
|
||||
},
|
||||
vakif: {
|
||||
slug: "vakif",
|
||||
ad: "Vakıf Üniversiteleri",
|
||||
tekil: "vakıf üniversitesi",
|
||||
uniturler: ["VAKIF", "VAKIF MYO"],
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type TurSlug = keyof typeof TUR_SAYFALARI;
|
||||
|
||||
export const turSlugMu = (s: string): s is TurSlug => s in TUR_SAYFALARI;
|
||||
|
||||
/** Ham unitur'dan tür sayfası adresi; tür sayfası olmayanlarda null. */
|
||||
export function turSayfaHref(unitur: string | null | undefined): string | null {
|
||||
if (!unitur) return null;
|
||||
for (const t of Object.values(TUR_SAYFALARI)) {
|
||||
if ((t.uniturler as readonly string[]).includes(unitur)) {
|
||||
return `/universiteler/${t.slug}`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user