feat(tasarim): bölüm sayfası "şişko ve sade" dile göre
- Başlık bloğu lg altında ortalı, kimlik hapları (düzey + puan türü), dört özet kartı (Program, Devlet, Vakıf, En iyi sıralama), piksel ayraç - OzetKart src/components/ozet-kart.tsx'e çıkarıldı; üniversite ve bölüm sayfası oradan kullanıyor. 6-7 haneli sayı birimle kısalıyor (ölçüldü) - Program listesi §4.15: ad sarar, varyant ayrı satırda, 44 px butonlar, telefonda taban sıra adın altında, butonlar alt alta - Sayfalama 44 px, telefonda tek satır; hover'lar tek turuncu - Denetim: kanonik liste satırı adının mavi hover'ı artık uyarı vermiyor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -222,7 +222,7 @@ shadcn `Input`, `Select`, `Textarea`, `Slider`. Etiket her alanda; hata mesajı
|
|||||||
### 4.7 İkonlar
|
### 4.7 İkonlar
|
||||||
Yalnızca `lucide-react`. Boyut: `size-4` buton/satır içi (varsayılan), `size-5` ikon kutusu, `size-3.5` küçük çip, `size-6` üstü yalnızca boş durum. Dekoratif ikon `aria-hidden`; tek başına ikon buton `aria-label` ya da `sr-only` metin taşır. Emoji ikon olarak kullanılmaz.
|
Yalnızca `lucide-react`. Boyut: `size-4` buton/satır içi (varsayılan), `size-5` ikon kutusu, `size-3.5` küçük çip, `size-6` üstü yalnızca boş durum. Dekoratif ikon `aria-hidden`; tek başına ikon buton `aria-label` ya da `sr-only` metin taşır. Emoji ikon olarak kullanılmaz.
|
||||||
|
|
||||||
### 4.8 Özet kartı — `OzetKart`, `universite-icerik.tsx`
|
### 4.8 Özet kartı — `OzetKart`, `src/components/ozet-kart.tsx`
|
||||||
Apple Fitness özet kartının sitenin ölçüleriyle hâli. Sayfada **en çok dört** tane (harita
|
Apple Fitness özet kartının sitenin ölçüleriyle hâli. Sayfada **en çok dört** tane (harita
|
||||||
yanındaki 2×2).
|
yanındaki 2×2).
|
||||||
|
|
||||||
@@ -239,7 +239,11 @@ yanındaki 2×2).
|
|||||||
ikon kutusu `flex size-8 shrink-0 items-center justify-center rounded-xl bg-primary/10 sm:size-10`.
|
ikon kutusu `flex size-8 shrink-0 items-center justify-center rounded-xl bg-primary/10 sm:size-10`.
|
||||||
- `dl` anlamı için `dt` (başlık) DOM'da önce gelir, görsel sıra `order-*` ile kurulur.
|
- `dl` anlamı için `dt` (başlık) DOM'da önce gelir, görsel sıra `order-*` ile kurulur.
|
||||||
- Telefonda kart iç genişliği ~131 px: en uzun sayı ("13.889", 36 px'te ~90 px) + 32 px ikon
|
- Telefonda kart iç genişliği ~131 px: en uzun sayı ("13.889", 36 px'te ~90 px) + 32 px ikon
|
||||||
sığar. Daha uzun sayı gelecekse önce ölç (§9.2), sonra yaz.
|
sığar. Daha uzun sayı gelecekse önce ölç (§9.2), sonra yaz. Ölçülen sınır: 36 px'te
|
||||||
|
"1.618.988" 123 px — ikonun altına girer. 6–7 haneli sayı birimle kısalır: 245.300 →
|
||||||
|
`245` + birim `bin`, 1.618.988 → `1,62` + birim `milyon` (bölüm sayfasındaki `kartSayisi`).
|
||||||
|
- Kullanıldığı yerler: üniversite ve bölüm sayfası (bölümde dört kart: Program, Devlet,
|
||||||
|
Vakıf, En iyi sıralama; `grid grid-cols-2 gap-4 lg:grid-cols-4`, haritası olmadığı için).
|
||||||
- Tıklanabilir kart (ör. Türkiye sırası → sıralama sayfası): kartın tamamı link olur
|
- Tıklanabilir kart (ör. Türkiye sırası → sıralama sayfası): kartın tamamı link olur
|
||||||
(`after:absolute after:inset-0` gerilmiş link), iç kaynak linki `relative z-10` ile üstte
|
(`after:absolute after:inset-0` gerilmiş link), iç kaynak linki `relative z-10` ile üstte
|
||||||
kalır; hover §2.1.
|
kalır; hover §2.1.
|
||||||
@@ -341,7 +345,14 @@ opaklık.
|
|||||||
adın altındaki satıra iner: `mt-1.5 flex items-center gap-1.5 text-sm text-slate-500 sm:hidden`
|
adın altındaki satıra iner: `mt-1.5 flex items-center gap-1.5 text-sm text-slate-500 sm:hidden`
|
||||||
içinde değer `font-semibold text-slate-900`.
|
içinde değer `font-semibold text-slate-900`.
|
||||||
- Tür çipi: `rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-slate-700`.
|
- Tür çipi: `rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-slate-700`.
|
||||||
- Satır butonları varsayılan `size-11` (44 px); `size-8` gibi küçültme verilmez.
|
- Satır butonları varsayılan `size-11` (44 px); `size-8` gibi küçültme verilmez. Telefonda
|
||||||
|
iki buton alt alta durur (`inline-flex flex-col items-center gap-2 sm:flex-row`, sütun
|
||||||
|
`w-14 sm:w-28`): yan yana 96 px yiyip adı 157 px'e sıkıştırıyordu, alt alta 273 px kalıyor
|
||||||
|
(24 Eyl 2026 ölçümü).
|
||||||
|
- Satırda logo varsa (bölüm sayfası) `UniLogo boy="sm"` adın solunda, `items-start`.
|
||||||
|
- Program varyantı ("… (İngilizce) (Burslu)") adın altında ayrı satır
|
||||||
|
`mt-0.5 block text-sm leading-snug text-slate-600`; tür çipine yapıştırılıp kesilmez.
|
||||||
|
- Tür çipi devlet/vakıf için de aynı slate çip; vakıf violet değildir (§2.2).
|
||||||
- Tablo başlığı `h-12 text-sm font-semibold text-slate-500`; hücre dolgusu `py-4 pl-4 sm:pl-5`.
|
- Tablo başlığı `h-12 text-sm font-semibold text-slate-500`; hücre dolgusu `py-4 pl-4 sm:pl-5`.
|
||||||
|
|
||||||
## 5. Yerleşim
|
## 5. Yerleşim
|
||||||
@@ -378,6 +389,11 @@ kartlarla aynı hizada:
|
|||||||
yorar), form alanları, uzun rehber metni.
|
yorar), form alanları, uzun rehber metni.
|
||||||
- Alt sayfa (§4.10) içeriği her ekranda ortalıdır.
|
- Alt sayfa (§4.10) içeriği her ekranda ortalıdır.
|
||||||
|
|
||||||
|
### 5.2.1 Sayfalama — `Sayfalama`, `sayfalama.tsx`
|
||||||
|
Haplar `h-11` (44 px), hover §2.1. Telefonda (sm altı) tek satır kalsın diye yalnız Önceki ·
|
||||||
|
aktif sayfa · Sonraki görünür; diğer numaralar `hidden sm:inline-flex` ile HTML'de durur
|
||||||
|
(tarayıcı botları tüm sayfaları görür). 375 px'te 7 sayfalık şerit iki satıra kırılıyordu.
|
||||||
|
|
||||||
### 5.3 Verimli ve havadar
|
### 5.3 Verimli ve havadar
|
||||||
- İlişkili öğeler yan yana dizilir (iki sütun) — tek sütunda alt alta uzayan panel yapılmaz.
|
- İlişkili öğeler yan yana dizilir (iki sütun) — tek sütunda alt alta uzayan panel yapılmaz.
|
||||||
- Yan yana dizmek "hepsini tek şeride sıkıştırmak" değildir: 23 Eyl'de denenen yatay "bilgi
|
- Yan yana dizmek "hepsini tek şeride sıkıştırmak" değildir: 23 Eyl'de denenen yatay "bilgi
|
||||||
@@ -470,7 +486,11 @@ Bu dilde sorunların çoğu koddan görünmez (taşma, sığma, hizalanma). Tasa
|
|||||||
### 9.4 Site taraması için denetim uyarıları
|
### 9.4 Site taraması için denetim uyarıları
|
||||||
`pnpm tasarim:denetim` bu dilin eski izlerini **uyarı** olarak listeler (lint'i kırmaz, dönüşüm
|
`pnpm tasarim:denetim` bu dilin eski izlerini **uyarı** olarak listeler (lint'i kırmaz, dönüşüm
|
||||||
listesidir): `minik-yazi` (10–11 px), `buyuk-harf-etiket` (uppercase + tracking-wide),
|
listesidir): `minik-yazi` (10–11 px), `buyuk-harf-etiket` (uppercase + tracking-wide),
|
||||||
`mavi-hover` (§2.1; liste satırı adı istisna), `iri-sayi-tabular` (Cal Sans + tabular-nums).
|
`mavi-hover` (§2.1), `iri-sayi-tabular` (Cal Sans + tabular-nums).
|
||||||
|
`mavi-hover` §4.15 kanonik liste satırı adını (`text-base font-semibold leading-snug` +
|
||||||
|
`hover:text-primary hover:underline` aynı satırda) kendiliğinden atlar — istisna uyarı
|
||||||
|
vermez (Bilal'in kararı, 24 Eyl 2026). Eski `truncate text-sm` adlar yine uyarır; önce §4.15'e
|
||||||
|
çevrilir.
|
||||||
Bir ekranı dönüştüren ajan o ekrandaki uyarıları sıfırlar ya da neden kaldığını yazar.
|
Bir ekranı dönüştüren ajan o ekrandaki uyarıları sıfırlar ya da neden kaldığını yazar.
|
||||||
|
|
||||||
### 9.5 Bitirmeden
|
### 9.5 Bitirmeden
|
||||||
|
|||||||
@@ -191,7 +191,20 @@ const KURALLAR: Kural[] = [
|
|||||||
ad: "mavi-hover",
|
ad: "mavi-hover",
|
||||||
seviye: "uyari",
|
seviye: "uyari",
|
||||||
mesaj: "Hover rengi tek turuncu (orange-500); liste satırındaki ad linkiyse istisna, mavi kalır (DESIGN.md §2.1)",
|
mesaj: "Hover rengi tek turuncu (orange-500); liste satırındaki ad linkiyse istisna, mavi kalır (DESIGN.md §2.1)",
|
||||||
bul: regexKurali(/\b(?:group-)?hover:(?:text|border|shadow)-primary\b/),
|
// §2.1 istisnası: §4.15 kanonik liste satırı adı (sarar, text-base
|
||||||
|
// font-semibold leading-snug + hover:text-primary hover:underline) mavi
|
||||||
|
// kalır. Eski truncate/text-sm adlar istisnaya girmez — önce §4.15'e
|
||||||
|
// çevrilir. (Bilal'in kararı, 24 Eyl 2026: istisna uyarı vermesin.)
|
||||||
|
bul: (icerik) =>
|
||||||
|
regexKurali(/\b(?:group-)?hover:(?:text|border|shadow)-primary\b/)(icerik).filter((e) => {
|
||||||
|
const bas = icerik.lastIndexOf("\n", e.index) + 1;
|
||||||
|
const son = icerik.indexOf("\n", e.index);
|
||||||
|
const satir = icerik.slice(bas, son === -1 ? undefined : son);
|
||||||
|
return !(
|
||||||
|
/\btext-base font-semibold leading-snug\b/.test(satir) &&
|
||||||
|
/\bhover:text-primary hover:underline\b/.test(satir)
|
||||||
|
);
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ad: "iri-sayi-tabular",
|
ad: "iri-sayi-tabular",
|
||||||
|
|||||||
58
src/components/ozet-kart.tsx
Normal file
58
src/components/ozet-kart.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import type { LucideIcon } from "lucide-react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Özet kartı (DESIGN.md §4.8 — Apple Fitness özet kartlarının düzeni,
|
||||||
|
* sitenin ölçüleriyle): kartın en baskın öğesi iri sayı; ikonu sağında,
|
||||||
|
* küçük ikon kutusunda (§4.4 ikon kutusunun küçük hali). Altında başlık ve
|
||||||
|
* tek satır açıklama. Telefonda kart iç genişliği ~131 px: en uzun sayı
|
||||||
|
* ("13.889", text-4xl ≈ 90 px) + size-8 ikon kutusu sığar; sm'den itibaren
|
||||||
|
* text-5xl. Üniversite ve bölüm sayfalarının tek kaynağı. `ek` verilirse
|
||||||
|
* açıklamanın altında durur (ör. Türkiye sırasındaki piksel şeridi). dl
|
||||||
|
* anlamı için dt DOM'da önce gelir, görsel sıra `order` ile kurulur; çağıran
|
||||||
|
* kartları bir `<dl>` içine koyar.
|
||||||
|
*/
|
||||||
|
export function OzetKart({
|
||||||
|
ikon: Ikon,
|
||||||
|
etiket,
|
||||||
|
deger,
|
||||||
|
birim,
|
||||||
|
alt,
|
||||||
|
ek,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
ikon: LucideIcon;
|
||||||
|
etiket: string;
|
||||||
|
deger: ReactNode;
|
||||||
|
birim?: string;
|
||||||
|
alt: ReactNode;
|
||||||
|
ek?: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`flex flex-col rounded-2xl border border-slate-200 bg-white p-4 sm:p-5 ${className ?? ""}`}
|
||||||
|
>
|
||||||
|
<dt className="order-2 mt-2 text-base font-semibold text-slate-900">
|
||||||
|
{etiket}
|
||||||
|
</dt>
|
||||||
|
<dd className="order-1 flex items-start justify-between gap-1.5">
|
||||||
|
<span className="flex min-w-0 flex-wrap items-baseline gap-x-1">
|
||||||
|
<span className="font-heading text-4xl font-bold leading-none tracking-tight text-slate-900 sm:text-5xl">
|
||||||
|
{deger}
|
||||||
|
</span>
|
||||||
|
{birim ? (
|
||||||
|
<span className="text-base font-semibold text-slate-400">
|
||||||
|
{birim}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
<span className="flex size-8 shrink-0 items-center justify-center rounded-xl bg-primary/10 sm:size-10">
|
||||||
|
<Ikon className="size-4 text-primary sm:size-5" aria-hidden />
|
||||||
|
</span>
|
||||||
|
</dd>
|
||||||
|
<dd className="order-3 mt-0.5 text-sm text-slate-500">{alt}</dd>
|
||||||
|
{ek ? <dd className="order-4 mt-3">{ek}</dd> : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,15 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { ChevronRight } from "lucide-react";
|
import {
|
||||||
|
Building2,
|
||||||
|
Calculator,
|
||||||
|
ChevronRight,
|
||||||
|
GraduationCap,
|
||||||
|
Landmark,
|
||||||
|
LayoutList,
|
||||||
|
Target,
|
||||||
|
} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
getAllBolumler,
|
getAllBolumler,
|
||||||
getAllBolumSlugs,
|
getAllBolumSlugs,
|
||||||
@@ -23,6 +31,8 @@ import {
|
|||||||
import { BolumProgramListesi } from "./bolum-program-listesi";
|
import { BolumProgramListesi } from "./bolum-program-listesi";
|
||||||
import { VeriKaynagi } from "./veri-kaynagi";
|
import { VeriKaynagi } from "./veri-kaynagi";
|
||||||
import { NavBaslik } from "./nav-baslik";
|
import { NavBaslik } from "./nav-baslik";
|
||||||
|
import { OzetKart } from "@/components/ozet-kart";
|
||||||
|
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||||
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
|
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
|
||||||
import { SiteFooter } from "@/components/site-footer";
|
import { SiteFooter } from "@/components/site-footer";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
@@ -35,6 +45,32 @@ import {
|
|||||||
|
|
||||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Özet kartında iri sayı telefonda ~94 px'e sığmalı (DESIGN.md §4.8):
|
||||||
|
* 36 px Cal Sans'ta "1.618.988" 123 px ölçüldü, ikonun altına giriyor. 6 ve
|
||||||
|
* 7 haneli sıralar birimle kısalır: 245.300 → "245" + "bin",
|
||||||
|
* 1.618.988 → "1,62" + "milyon".
|
||||||
|
*/
|
||||||
|
function kartSayisi(n: number): { deger: string; birim?: string } {
|
||||||
|
// 999.500+ "1.000 bin" olmasın diye milyona geçer
|
||||||
|
if (n >= 999_500) {
|
||||||
|
return {
|
||||||
|
deger: (n / 1e6).toLocaleString("tr-TR", { maximumFractionDigits: 2 }),
|
||||||
|
birim: "milyon",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (n >= 100_000) return { deger: sayi(Math.round(n / 1000)), birim: "bin" };
|
||||||
|
return { deger: sayi(n) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lisans puan türünün kimlik hapındaki adı (DB: SAYISAL, EŞİT AĞIRLIK…). */
|
||||||
|
const PUAN_TURU_HAP: Record<string, string> = {
|
||||||
|
SAYISAL: "Sayısal puan",
|
||||||
|
"EŞİT AĞIRLIK": "Eşit ağırlık puanı",
|
||||||
|
SÖZEL: "Sözel puan",
|
||||||
|
DİL: "Dil puanı",
|
||||||
|
};
|
||||||
|
|
||||||
export function bolumToplamSayfa(programSayisi: number): number {
|
export function bolumToplamSayfa(programSayisi: number): number {
|
||||||
return Math.max(1, Math.ceil(programSayisi / SAYFA_BOYU));
|
return Math.max(1, Math.ceil(programSayisi / SAYFA_BOYU));
|
||||||
}
|
}
|
||||||
@@ -179,76 +215,126 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
|||||||
// adı geçer (ziyaretçi Google'dan doğrudan /sayfa/N'ye iniyor).
|
// adı geçer (ziyaretçi Google'dan doğrudan /sayfa/N'ye iniyor).
|
||||||
const ctaBaslik = `Sıralaman ${bolum.ad} tabanlarının neresinde?`;
|
const ctaBaslik = `Sıralaman ${bolum.ad} tabanlarının neresinde?`;
|
||||||
|
|
||||||
|
// Kimlik hapları (DESIGN.md §4.12): düzey + lisans puan türleri. TYT
|
||||||
|
// puanı önlisansın kendisi olduğundan ayrı hap açmaz.
|
||||||
|
const puanTurleri = [
|
||||||
|
...new Set(bolum.lisansProgramlari.map((p) => p.tur)),
|
||||||
|
].flatMap((t) => (PUAN_TURU_HAP[t] ? [PUAN_TURU_HAP[t]] : []));
|
||||||
|
const duzeyler = [
|
||||||
|
...(bolum.lisansProgramlari.length > 0 ? ["Lisans"] : []),
|
||||||
|
...(bolum.onlisansProgramlari.length > 0 ? ["Önlisans"] : []),
|
||||||
|
];
|
||||||
|
// Özet kartları: devlet ve vakıf ayrı sayılır; KKTC ve yurt dışı
|
||||||
|
// programları toplamda var ama iki karta da girmez (vakıf sayılmaz).
|
||||||
|
const devletUni = new Set<string>();
|
||||||
|
const vakifUni = new Set<string>();
|
||||||
|
let vakifProgram = 0;
|
||||||
|
for (const p of tumProgramlar) {
|
||||||
|
if (p.unitur === "DEVLET") devletUni.add(p.universite);
|
||||||
|
else if (p.unitur === "VAKIF" || p.unitur === "VAKIF MYO") {
|
||||||
|
vakifProgram += 1;
|
||||||
|
vakifUni.add(p.universite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { stats } = bolum;
|
||||||
|
const hapSinifi =
|
||||||
|
"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";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main className="mx-auto w-full max-w-5xl px-4 py-10 sm:py-14">
|
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
|
||||||
<JsonLd
|
<JsonLd
|
||||||
data={breadcrumbJsonLd([
|
data={breadcrumbJsonLd([
|
||||||
{ name: "Ana Sayfa", path: "/" },
|
{ name: "Ana Sayfa", path: "/" },
|
||||||
{ name: "Bölümler", path: "/bolumler" },
|
{ name: "Bölümler", path: "/bolumler" },
|
||||||
{ name: bolum.ad, path: `/bolum/${slug}` },
|
{ name: bolum.ad, path: `/bolum/${slug}` },
|
||||||
...(ilkSayfa ? [] : [{ name: `Sayfa ${sayfa}`, path: buYol }]),
|
...(ilkSayfa ? [] : [{ name: `Sayfa ${sayfa}`, path: buYol }]),
|
||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<nav
|
<nav
|
||||||
aria-label="İçerik yolu"
|
aria-label="İçerik yolu"
|
||||||
className="mb-5 flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground"
|
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-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
||||||
>
|
>
|
||||||
Ana sayfa
|
<Link
|
||||||
</Link>
|
href="/"
|
||||||
<ChevronRight className="size-3.5" aria-hidden />
|
className="transition-colors hover:text-orange-500 hover:underline"
|
||||||
<Link
|
>
|
||||||
href="/bolumler"
|
Ana Sayfa
|
||||||
className="transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
</Link>
|
||||||
>
|
<ChevronRight className="size-3.5 shrink-0" aria-hidden />
|
||||||
Bölümler
|
<Link
|
||||||
</Link>
|
href="/bolumler"
|
||||||
<ChevronRight className="size-3.5" aria-hidden />
|
className="transition-colors hover:text-orange-500 hover:underline"
|
||||||
{ilkSayfa ? (
|
>
|
||||||
<span className="text-foreground">{bolum.ad}</span>
|
Bölümler
|
||||||
) : (
|
</Link>
|
||||||
<>
|
<ChevronRight className="size-3.5 shrink-0" aria-hidden />
|
||||||
<Link
|
{ilkSayfa ? (
|
||||||
href={`/bolum/${slug}`}
|
<span className="text-slate-700">{bolum.ad}</span>
|
||||||
className="transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
) : (
|
||||||
>
|
<>
|
||||||
{bolum.ad}
|
<Link
|
||||||
</Link>
|
href={`/bolum/${slug}`}
|
||||||
<ChevronRight className="size-3.5" aria-hidden />
|
className="transition-colors hover:text-orange-500 hover:underline"
|
||||||
<span className="text-foreground">Sayfa {sayfa}</span>
|
>
|
||||||
</>
|
{bolum.ad}
|
||||||
)}
|
</Link>
|
||||||
</nav>
|
<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 bölüm adı belirir */}
|
{/* Başlık üst çubuğun altına girince üst çubukta bölüm adı belirir
|
||||||
<NavBaslik baslikId="bolum-baslik" ad={bolum.ad} />
|
(logosu yok — DESIGN.md §4.14). Geniş ekran (lg) altında başlık
|
||||||
<header className="border-b pb-7 sm:pb-9">
|
bloğu ortalı (§5.2). */}
|
||||||
|
<NavBaslik baslikId="bolum-baslik" ad={bolum.ad} />
|
||||||
<h1
|
<h1
|
||||||
id="bolum-baslik"
|
id="bolum-baslik"
|
||||||
className="max-w-3xl font-heading text-3xl font-bold tracking-tight text-foreground sm:text-5xl">
|
className="mt-6 text-center font-heading text-3xl font-bold leading-tight text-slate-900 sm:text-5xl lg:text-left"
|
||||||
|
>
|
||||||
{bolum.ad}
|
{bolum.ad}
|
||||||
{ilkSayfa ? "" : ` — Sayfa ${sayfa}`}
|
{ilkSayfa ? "" : ` — Sayfa ${sayfa}`}
|
||||||
{!ilkSayfa && aralik ? (
|
{!ilkSayfa && aralik ? (
|
||||||
<>
|
<>
|
||||||
{" "}
|
{" "}
|
||||||
<span className="mt-2 block text-lg font-semibold tracking-normal text-muted-foreground sm:text-2xl">
|
<span className="mt-2 block text-lg font-semibold text-slate-500 sm:text-2xl">
|
||||||
{aralik.charAt(0).toLocaleUpperCase("tr-TR") + aralik.slice(1)}
|
{aralik.charAt(0).toLocaleUpperCase("tr-TR") + aralik.slice(1)}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
{duzeyler.length > 0 || puanTurleri.length > 0 ? (
|
||||||
|
<p className="mt-5 flex flex-wrap items-center justify-center gap-2 lg:justify-start">
|
||||||
|
{duzeyler.map((d) => (
|
||||||
|
<span key={d} className={hapSinifi}>
|
||||||
|
<GraduationCap className="size-4 text-primary" aria-hidden />
|
||||||
|
{d}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{puanTurleri.map((t) => (
|
||||||
|
<span key={t} className={hapSinifi}>
|
||||||
|
<Calculator className="size-4 text-primary" aria-hidden />
|
||||||
|
{t}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{ilkSayfa ? (
|
{ilkSayfa ? (
|
||||||
<p className="mt-3 max-w-2xl text-sm leading-6 text-muted-foreground sm:text-base">
|
<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">
|
||||||
{sayi(tumProgramlar.length)} üniversite programının {SON_YIL} taban
|
{bolum.ad} bölümünü açan {sayi(tumProgramlar.length)} üniversite
|
||||||
puanlarını, başarı sıralamalarını ve kontenjanlarını incele.
|
programının {SON_YIL} taban puanlarını, başarı sıralamalarını ve
|
||||||
|
kontenjanlarını incele.{" "}
|
||||||
|
<em>
|
||||||
|
{TERCIH_YILI} tabanları, yerleştirme sonuçları açıklandığında
|
||||||
|
netleşir.
|
||||||
|
</em>
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="mt-3 max-w-2xl text-sm leading-6 text-muted-foreground sm:text-base">
|
<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">
|
||||||
{bolum.ad} taban puanları tablosunun {sayfa}. sayfası (
|
{bolum.ad} taban puanları tablosunun {sayfa}. sayfası (
|
||||||
{sayi(tumProgramlar.length)} programın{" "}
|
{sayi(tumProgramlar.length)} programın{" "}
|
||||||
{sayi((sayfa - 1) * SAYFA_BOYU + 1)}–
|
{sayi((sayfa - 1) * SAYFA_BOYU + 1)}–
|
||||||
@@ -257,54 +343,99 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
|||||||
{aralik ? `: ${aralik}` : ""}. Listenin başı{" "}
|
{aralik ? `: ${aralik}` : ""}. Listenin başı{" "}
|
||||||
<Link
|
<Link
|
||||||
href={`/bolum/${slug}`}
|
href={`/bolum/${slug}`}
|
||||||
className="font-medium text-primary hover:underline"
|
className="font-medium text-primary transition-colors hover:text-orange-500 hover:underline"
|
||||||
>
|
>
|
||||||
ilk sayfada
|
ilk sayfada
|
||||||
</Link>
|
</Link>
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* Tek sayfada çıkan ziyaretçi alttaki karta inmiyor: kompakt sıra girişi
|
{/* Tek sayfada çıkan ziyaretçi alttaki karta inmiyor: kompakt sıra
|
||||||
başlık bloğunun hemen altında, listenin üstünde. Alttaki kart kalır. */}
|
girişi özet paragrafın hemen altında, listenin üstünde. Alttaki
|
||||||
<div className="mt-6">
|
kart kalır. */}
|
||||||
<CtaSiraForm varyant="kompakt" konum="ust" baslik={ctaBaslik} />
|
<div className="mt-8">
|
||||||
</div>
|
<CtaSiraForm varyant="kompakt" konum="ust" baslik={ctaBaslik} />
|
||||||
|
|
||||||
<section aria-labelledby="universite-programlari" className="mt-8">
|
|
||||||
<div className="mb-4 flex items-end justify-between gap-4">
|
|
||||||
<h2
|
|
||||||
id="universite-programlari"
|
|
||||||
className="font-heading text-xl font-bold text-foreground sm:text-2xl"
|
|
||||||
>
|
|
||||||
Üniversite programları
|
|
||||||
</h2>
|
|
||||||
<span className="shrink-0 text-sm text-muted-foreground">
|
|
||||||
{sayi(tumProgramlar.length)} program
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BolumProgramListesi
|
{/* Sayfada en çok dört özet sayı (DESIGN.md §1.1, §4.8). Derin
|
||||||
programlar={tumProgramlar.map((program) => ({
|
sayfalarda tekrar edilmez — üniversite sayfasıyla aynı. */}
|
||||||
...program,
|
{ilkSayfa ? (
|
||||||
universiteSlug: uniSlugFromAd(program.universite),
|
<dl className="mt-8 grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||||
}))}
|
<OzetKart
|
||||||
bazAd={bolum.ad}
|
ikon={LayoutList}
|
||||||
tabanYol={`/bolum/${slug}`}
|
etiket="Program"
|
||||||
sayfa={sayfa}
|
deger={sayi(stats.toplam)}
|
||||||
/>
|
alt={`${sayi(stats.ilSayisi)} ilde`}
|
||||||
<VeriKaynagi className="mt-6" />
|
/>
|
||||||
</section>
|
<OzetKart
|
||||||
|
ikon={Landmark}
|
||||||
|
etiket="Devlet"
|
||||||
|
deger={sayi(stats.devlet)}
|
||||||
|
alt={
|
||||||
|
devletUni.size > 0
|
||||||
|
? `${sayi(devletUni.size)} üniversitede`
|
||||||
|
: "bu bölümde yok"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<OzetKart
|
||||||
|
ikon={Building2}
|
||||||
|
etiket="Vakıf"
|
||||||
|
deger={sayi(vakifProgram)}
|
||||||
|
alt={
|
||||||
|
vakifUni.size > 0
|
||||||
|
? `${sayi(vakifUni.size)} üniversitede`
|
||||||
|
: "bu bölümde yok"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<OzetKart
|
||||||
|
ikon={Target}
|
||||||
|
etiket="En iyi sıralama"
|
||||||
|
{...(stats.minSira != null
|
||||||
|
? kartSayisi(stats.minSira)
|
||||||
|
: { deger: "—" })}
|
||||||
|
alt={`${SON_YIL} taban sırası`}
|
||||||
|
/>
|
||||||
|
</dl>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{/* Üniversite sayfasıyla aynı konum ve aynı kart: liste + sayfalama altı */}
|
<PagePixelDivider seed={83} className="mx-auto mt-12 lg:mx-0" />
|
||||||
<div className="mt-10">
|
|
||||||
<CtaSiraForm
|
<section aria-labelledby="universite-programlari" className="mt-10">
|
||||||
konum="alt"
|
<div className="text-center lg:text-left">
|
||||||
baslik={ctaBaslik}
|
<h2
|
||||||
aciklama={KATALOG_CTA_ACIKLAMA}
|
id="universite-programlari"
|
||||||
/>
|
className="font-heading text-2xl font-bold text-slate-900 sm:text-3xl"
|
||||||
</div>
|
>
|
||||||
|
Üniversite programları
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 text-base text-slate-500">
|
||||||
|
{sayi(tumProgramlar.length)} program, taban sırasına göre
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<BolumProgramListesi
|
||||||
|
programlar={tumProgramlar.map((program) => ({
|
||||||
|
...program,
|
||||||
|
universiteSlug: uniSlugFromAd(program.universite),
|
||||||
|
}))}
|
||||||
|
bazAd={bolum.ad}
|
||||||
|
tabanYol={`/bolum/${slug}`}
|
||||||
|
sayfa={sayfa}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<VeriKaynagi className="mt-6" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Üniversite sayfasıyla aynı konum ve aynı kart: liste + sayfalama altı */}
|
||||||
|
<div className="mt-10">
|
||||||
|
<CtaSiraForm
|
||||||
|
konum="alt"
|
||||||
|
baslik={ctaBaslik}
|
||||||
|
aciklama={KATALOG_CTA_ACIKLAMA}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<SiteFooter />
|
<SiteFooter />
|
||||||
</>
|
</>
|
||||||
@@ -313,16 +444,20 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
|||||||
|
|
||||||
export function BolumIcerikSkeleton() {
|
export function BolumIcerikSkeleton() {
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto w-full max-w-5xl px-4 py-10 sm:py-14">
|
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
|
||||||
<Skeleton className="h-5 w-56" />
|
<Skeleton className="mx-auto h-5 w-56 lg:mx-0" />
|
||||||
<div className="border-b pb-7 sm:pb-9">
|
<Skeleton className="mx-auto mt-6 h-10 w-2/3 max-w-xl lg:mx-0" />
|
||||||
<Skeleton className="mt-5 h-10 w-2/3 max-w-xl" />
|
<Skeleton className="mx-auto mt-5 h-10 w-48 rounded-full lg:mx-0" />
|
||||||
<Skeleton className="mt-3 h-5 w-full max-w-2xl" />
|
<Skeleton className="mx-auto mt-6 h-5 w-full max-w-3xl lg:mx-0" />
|
||||||
|
<div className="mt-8 grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||||
|
{Array.from({ length: 4 }, (_, i) => (
|
||||||
|
<Skeleton key={i} className="h-28 w-full rounded-2xl" />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-8 space-y-3">
|
<div className="mt-16 space-y-3">
|
||||||
<Skeleton className="h-7 w-64" />
|
<Skeleton className="h-7 w-64" />
|
||||||
{Array.from({ length: 5 }, (_, i) => (
|
{Array.from({ length: 5 }, (_, i) => (
|
||||||
<Skeleton key={i} className="h-12 w-full" />
|
<Skeleton key={i} className="h-20 w-full rounded-2xl" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ function BolumLinkleri({ bolumler }: { bolumler: BolumOzet[] }) {
|
|||||||
<li key={b.slug}>
|
<li key={b.slug}>
|
||||||
<Link
|
<Link
|
||||||
href={`/bolum/${b.slug}`}
|
href={`/bolum/${b.slug}`}
|
||||||
className="group inline-flex items-baseline gap-2 text-sm text-slate-700 hover:text-primary"
|
className="group inline-flex items-baseline gap-2 text-sm text-slate-700 transition-colors hover:text-orange-500"
|
||||||
>
|
>
|
||||||
<span className="group-hover:underline">{b.ad}</span>
|
<span className="group-hover:underline">{b.ad}</span>
|
||||||
<span className="text-xs text-slate-400">
|
<span className="text-xs text-slate-400">
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ function SiralamaDugmesi({
|
|||||||
aktif && yon === "artan" ? "azalan" : "artan"
|
aktif && yon === "artan" ? "azalan" : "artan"
|
||||||
} sırala`}
|
} sırala`}
|
||||||
aria-pressed={aktif}
|
aria-pressed={aktif}
|
||||||
className="inline-flex min-h-11 cursor-pointer items-center gap-1 rounded-full px-1.5 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="inline-flex min-h-11 cursor-pointer items-center gap-1 rounded-full px-1.5 text-sm font-semibold text-slate-500 transition-colors hover:text-orange-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
>
|
>
|
||||||
<span>{etiket}</span>
|
<span>{etiket}</span>
|
||||||
<span className="flex flex-col -space-y-1" aria-hidden>
|
<span className="flex flex-col -space-y-1" aria-hidden>
|
||||||
@@ -66,7 +66,7 @@ function SiralamaDugmesi({
|
|||||||
className={`size-3 ${
|
className={`size-3 ${
|
||||||
aktif && yon === "artan"
|
aktif && yon === "artan"
|
||||||
? "text-primary"
|
? "text-primary"
|
||||||
: "text-muted-foreground/40"
|
: "text-slate-300"
|
||||||
}`}
|
}`}
|
||||||
strokeWidth={aktif && yon === "artan" ? 3 : 2}
|
strokeWidth={aktif && yon === "artan" ? 3 : 2}
|
||||||
/>
|
/>
|
||||||
@@ -74,7 +74,7 @@ function SiralamaDugmesi({
|
|||||||
className={`size-3 ${
|
className={`size-3 ${
|
||||||
aktif && yon === "azalan"
|
aktif && yon === "azalan"
|
||||||
? "text-primary"
|
? "text-primary"
|
||||||
: "text-muted-foreground/40"
|
: "text-slate-300"
|
||||||
}`}
|
}`}
|
||||||
strokeWidth={aktif && yon === "azalan" ? 3 : 2}
|
strokeWidth={aktif && yon === "azalan" ? 3 : 2}
|
||||||
/>
|
/>
|
||||||
@@ -154,14 +154,21 @@ export function BolumProgramListesi({
|
|||||||
ref={listeRef}
|
ref={listeRef}
|
||||||
className="scroll-mt-24 overflow-hidden rounded-2xl border border-slate-200 bg-white"
|
className="scroll-mt-24 overflow-hidden rounded-2xl border border-slate-200 bg-white"
|
||||||
>
|
>
|
||||||
|
{/* Şişko ve sade (DESIGN.md §4.15): üniversite adı kesilmez, sarar;
|
||||||
|
satır butonları 44 px. 375 px'te yalnız ad + butonlar sütun
|
||||||
|
olarak kalır; taban sıra adın altındaki satıra iner. Sıralama
|
||||||
|
düğmeleri sütun başlıklarıyla birlikte sm'den itibaren görünür —
|
||||||
|
telefonda liste zaten taban sırasına göre dizili. */}
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow className="hover:bg-transparent">
|
||||||
<TableHead>Üniversite</TableHead>
|
<TableHead className="h-12 pl-4 text-sm font-semibold text-slate-500 sm:pl-5">
|
||||||
<TableHead className="hidden text-right md:table-cell">
|
Üniversite
|
||||||
|
</TableHead>
|
||||||
|
<TableHead className="hidden h-12 text-right text-sm font-semibold text-slate-500 lg:table-cell">
|
||||||
Son {VERI_YIL_SAYISI} yıl
|
Son {VERI_YIL_SAYISI} yıl
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="hidden text-right lg:table-cell">
|
<TableHead className="hidden h-12 text-right lg:table-cell">
|
||||||
<SiralamaDugmesi
|
<SiralamaDugmesi
|
||||||
alan="puanSon"
|
alan="puanSon"
|
||||||
aktifAlan={aktifAlan}
|
aktifAlan={aktifAlan}
|
||||||
@@ -169,7 +176,7 @@ export function BolumProgramListesi({
|
|||||||
onSirala={sirala}
|
onSirala={sirala}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="hidden text-right sm:table-cell">
|
<TableHead className="hidden h-12 text-right md:table-cell">
|
||||||
<SiralamaDugmesi
|
<SiralamaDugmesi
|
||||||
alan="kontenjanSon"
|
alan="kontenjanSon"
|
||||||
aktifAlan={aktifAlan}
|
aktifAlan={aktifAlan}
|
||||||
@@ -177,7 +184,7 @@ export function BolumProgramListesi({
|
|||||||
onSirala={sirala}
|
onSirala={sirala}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="text-right">
|
<TableHead className="hidden h-12 text-right sm:table-cell">
|
||||||
<SiralamaDugmesi
|
<SiralamaDugmesi
|
||||||
alan="siraSon"
|
alan="siraSon"
|
||||||
aktifAlan={aktifAlan}
|
aktifAlan={aktifAlan}
|
||||||
@@ -185,7 +192,7 @@ export function BolumProgramListesi({
|
|||||||
onSirala={sirala}
|
onSirala={sirala}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="w-24 text-right">
|
<TableHead className="h-12 w-14 text-right sm:w-28">
|
||||||
<span className="sr-only">Netler ve listeye ekle</span>
|
<span className="sr-only">Netler ve listeye ekle</span>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -194,12 +201,13 @@ export function BolumProgramListesi({
|
|||||||
{gosterilenProgramlar.map((p) => {
|
{gosterilenProgramlar.map((p) => {
|
||||||
const uniAd = trBaslikDuzeni(p.universite);
|
const uniAd = trBaslikDuzeni(p.universite);
|
||||||
const varyant = p.isim.trim() !== bazAd ? p.isim.trim() : null;
|
const varyant = p.isim.trim() !== bazAd ? p.isim.trim() : null;
|
||||||
const devlet = p.unitur === "DEVLET";
|
const tabanMetni =
|
||||||
|
p.siraSon != null ? `~${sayi(p.siraSon)}.` : "—";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow key={p.id} className="kt-cv-satir">
|
<TableRow key={p.id} className="kt-cv-satir">
|
||||||
<TableCell className="max-w-0 w-full">
|
<TableCell className="w-full whitespace-normal py-4 pl-4 sm:pl-5">
|
||||||
<span className="flex min-w-0 items-center gap-2.5">
|
<span className="flex items-start gap-3">
|
||||||
{p.universiteSlug ? (
|
{p.universiteSlug ? (
|
||||||
<UniLogo slug={p.universiteSlug} ad={uniAd} boy="sm" />
|
<UniLogo slug={p.universiteSlug} ad={uniAd} boy="sm" />
|
||||||
) : null}
|
) : null}
|
||||||
@@ -207,71 +215,71 @@ export function BolumProgramListesi({
|
|||||||
{p.universiteSlug ? (
|
{p.universiteSlug ? (
|
||||||
<Link
|
<Link
|
||||||
href={`/universite/${p.universiteSlug}`}
|
href={`/universite/${p.universiteSlug}`}
|
||||||
className="block truncate text-sm font-semibold hover:text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="block text-base font-semibold leading-snug text-slate-900 transition-colors hover:text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
>
|
>
|
||||||
{uniAd}
|
{uniAd}
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span className="block truncate text-sm font-semibold">
|
<span className="block text-base font-semibold leading-snug text-slate-900">
|
||||||
{uniAd}
|
{uniAd}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="mt-0.5 flex min-w-0 items-center gap-1.5 text-xs text-slate-500">
|
{varyant ? (
|
||||||
|
<span className="mt-0.5 block text-sm leading-snug text-slate-600">
|
||||||
|
{varyant}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
<span className="mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-slate-500">
|
||||||
{p.unitur ? (
|
{p.unitur ? (
|
||||||
<span
|
<span className="shrink-0 rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-slate-700">
|
||||||
className={`shrink-0 rounded px-1 py-px text-[10px] font-medium leading-4 ${
|
|
||||||
devlet
|
|
||||||
? "bg-slate-100 text-slate-600"
|
|
||||||
: "bg-violet-50 text-violet-600"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{UNITUR_ETIKET[p.unitur]?.cip ??
|
{UNITUR_ETIKET[p.unitur]?.cip ??
|
||||||
trBaslikDuzeni(p.unitur)}
|
trBaslikDuzeni(p.unitur)}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="truncate">
|
{p.il ? (
|
||||||
{p.il ? trBaslikDuzeni(p.il) : null}
|
<span className="shrink-0">
|
||||||
{varyant ? `${p.il ? " · " : ""}${varyant}` : ""}
|
{trBaslikDuzeni(p.il)}
|
||||||
</span>
|
|
||||||
{p.sure ? (
|
|
||||||
<span className="shrink-0 text-slate-400">
|
|
||||||
· {p.sure} yıl
|
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
{p.sure ? (
|
||||||
|
<span className="shrink-0">{p.sure} yıl</span>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
<span className="mt-1.5 flex items-center gap-1.5 text-sm text-slate-500 sm:hidden">
|
||||||
|
Taban sıra
|
||||||
|
<span className="font-semibold tabular-nums text-slate-900">
|
||||||
|
{tabanMetni}
|
||||||
|
</span>
|
||||||
|
<ProgramTabanTrendi program={p} />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="hidden text-right text-xs md:table-cell">
|
<TableCell className="hidden text-right text-sm lg:table-cell">
|
||||||
<ProgramSiraGecmisi program={p} />
|
<ProgramSiraGecmisi program={p} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="hidden text-right text-xs tabular-nums text-slate-500 lg:table-cell">
|
<TableCell className="hidden text-right text-sm tabular-nums text-slate-600 lg:table-cell">
|
||||||
{p.puanSon != null
|
{p.puanSon != null
|
||||||
? p.puanSon.toFixed(2).replace(".", ",")
|
? p.puanSon.toFixed(2).replace(".", ",")
|
||||||
: "—"}
|
: "—"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="hidden text-right text-xs tabular-nums text-slate-500 sm:table-cell">
|
<TableCell className="hidden text-right text-sm tabular-nums text-slate-600 md:table-cell">
|
||||||
{p.kontenjanSon != null ? `${sayi(p.kontenjanSon)} kişi` : "—"}
|
{p.kontenjanSon != null ? `${sayi(p.kontenjanSon)} kişi` : "—"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right text-xs tabular-nums text-slate-500">
|
<TableCell className="hidden text-right text-base font-semibold tabular-nums text-slate-900 sm:table-cell">
|
||||||
<span className="inline-flex items-center gap-1">
|
<span className="inline-flex items-center gap-1.5">
|
||||||
<ProgramTabanTrendi program={p} />
|
<ProgramTabanTrendi program={p} />
|
||||||
{p.siraSon != null ? `~${sayi(p.siraSon)}.` : "—"}
|
{tabanMetni}
|
||||||
</span>
|
</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="pr-3 text-right">
|
<TableCell className="py-4 pr-4 text-right sm:pr-5">
|
||||||
<span className="inline-flex items-center gap-1.5">
|
<span className="inline-flex flex-col items-center gap-2 sm:flex-row">
|
||||||
<ProgramNetleriButonu
|
<ProgramNetleriButonu
|
||||||
programId={p.id}
|
programId={p.id}
|
||||||
isim={p.isim.trim()}
|
isim={p.isim.trim()}
|
||||||
universite={uniAd}
|
universite={uniAd}
|
||||||
className="size-8 min-h-8 min-w-8"
|
|
||||||
/>
|
|
||||||
<ProgramEkleButonu
|
|
||||||
program={p}
|
|
||||||
kaynak="bolum"
|
|
||||||
className="size-8 min-h-8 min-w-8"
|
|
||||||
/>
|
/>
|
||||||
|
<ProgramEkleButonu program={p} kaynak="bolum" />
|
||||||
</span>
|
</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function ProgramNetleriButonu({
|
|||||||
type="button"
|
type="button"
|
||||||
title="Son yerleşenin netleri"
|
title="Son yerleşenin netleri"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
className={`inline-flex size-11 min-h-11 min-w-11 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 transition-[background-color,border-color,color,transform] duration-150 hover:border-primary hover:bg-primary/5 hover:text-primary active:scale-[0.92] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${className}`}
|
className={`inline-flex size-11 min-h-11 min-w-11 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 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.92] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${className}`}
|
||||||
>
|
>
|
||||||
<ListChecks className="size-4" aria-hidden />
|
<ListChecks className="size-4" aria-hidden />
|
||||||
<span className="sr-only">{isim} — son yerleşenin netleri</span>
|
<span className="sr-only">{isim} — son yerleşenin netleri</span>
|
||||||
|
|||||||
@@ -95,7 +95,9 @@ export function Sayfalama({
|
|||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
// 7'den çok sayfada pencere: 1 … (c-1) c (c+1) … son
|
// 7'den çok sayfada pencere: 1 … (c-1) c (c+1) … son. Telefonda
|
||||||
|
// (sm altı) tek satır kalsın diye yalnız Önceki · aktif · Sonraki görünür;
|
||||||
|
// diğer numaralar HTML'de durur (tarayıcı botları tüm sayfaları görür).
|
||||||
const numaralar: (number | "...")[] = [];
|
const numaralar: (number | "...")[] = [];
|
||||||
if (toplamSayfa <= 7) {
|
if (toplamSayfa <= 7) {
|
||||||
for (let n = 1; n <= toplamSayfa; n++) numaralar.push(n);
|
for (let n = 1; n <= toplamSayfa; n++) numaralar.push(n);
|
||||||
@@ -116,12 +118,12 @@ export function Sayfalama({
|
|||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
aria-label="Sayfalama"
|
aria-label="Sayfalama"
|
||||||
className="mt-6 flex flex-wrap items-center justify-center gap-1.5"
|
className="mt-8 flex flex-wrap items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
{sayfa > 1
|
{sayfa > 1
|
||||||
? gecis(
|
? gecis(
|
||||||
sayfa - 1,
|
sayfa - 1,
|
||||||
"inline-flex h-9 items-center gap-1 rounded-full border border-slate-200 bg-white px-3.5 text-sm text-slate-700 hover:border-primary/40 hover:text-primary",
|
"inline-flex h-11 items-center gap-1 rounded-full border border-slate-200 bg-white px-4 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]",
|
||||||
<>
|
<>
|
||||||
<ChevronLeft className="size-4" aria-hidden />
|
<ChevronLeft className="size-4" aria-hidden />
|
||||||
Önceki
|
Önceki
|
||||||
@@ -133,7 +135,7 @@ export function Sayfalama({
|
|||||||
n === "..." ? (
|
n === "..." ? (
|
||||||
<span
|
<span
|
||||||
key={`bosluk-${i}`}
|
key={`bosluk-${i}`}
|
||||||
className="px-1.5 text-sm text-slate-400"
|
className="hidden px-1.5 text-sm text-slate-400 sm:inline"
|
||||||
aria-hidden
|
aria-hidden
|
||||||
>
|
>
|
||||||
…
|
…
|
||||||
@@ -142,8 +144,8 @@ export function Sayfalama({
|
|||||||
gecis(
|
gecis(
|
||||||
n,
|
n,
|
||||||
n === sayfa
|
n === sayfa
|
||||||
? "inline-flex h-9 min-w-9 items-center justify-center rounded-full bg-primary px-2 text-sm font-semibold text-primary-foreground"
|
? "inline-flex h-11 min-w-11 items-center justify-center rounded-full bg-primary px-2 text-sm font-semibold text-primary-foreground"
|
||||||
: "inline-flex h-9 min-w-9 items-center justify-center rounded-full border border-slate-200 bg-white px-2 text-sm text-slate-700 hover:border-primary/40 hover:text-primary",
|
: "hidden h-11 min-w-11 items-center justify-center rounded-full border border-slate-200 bg-white px-2 text-sm font-semibold text-slate-700 sm:inline-flex 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]",
|
||||||
n,
|
n,
|
||||||
{ aktif: n === sayfa },
|
{ aktif: n === sayfa },
|
||||||
)
|
)
|
||||||
@@ -152,7 +154,7 @@ export function Sayfalama({
|
|||||||
{sayfa < toplamSayfa
|
{sayfa < toplamSayfa
|
||||||
? gecis(
|
? gecis(
|
||||||
sayfa + 1,
|
sayfa + 1,
|
||||||
"inline-flex h-9 items-center gap-1 rounded-full border border-slate-200 bg-white px-3.5 text-sm text-slate-700 hover:border-primary/40 hover:text-primary",
|
"inline-flex h-11 items-center gap-1 rounded-full border border-slate-200 bg-white px-4 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]",
|
||||||
<>
|
<>
|
||||||
Sonraki
|
Sonraki
|
||||||
<ChevronRight className="size-4" aria-hidden />
|
<ChevronRight className="size-4" aria-hidden />
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import {
|
|||||||
Target,
|
Target,
|
||||||
Users,
|
Users,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { LucideIcon } from "lucide-react";
|
|
||||||
import type { ReactNode } from "react";
|
|
||||||
import {
|
import {
|
||||||
getAllUniversiteler,
|
getAllUniversiteler,
|
||||||
getAllUniversiteSlugs,
|
getAllUniversiteSlugs,
|
||||||
@@ -55,6 +53,7 @@ import { UniLogo } from "@/components/uni-logo";
|
|||||||
import { UrapLogo } from "./urap-logo";
|
import { UrapLogo } from "./urap-logo";
|
||||||
import { AkademikAyrinti } from "./akademik-ayrinti";
|
import { AkademikAyrinti } from "./akademik-ayrinti";
|
||||||
import { NavBaslik } from "./nav-baslik";
|
import { NavBaslik } from "./nav-baslik";
|
||||||
|
import { OzetKart } from "@/components/ozet-kart";
|
||||||
import { uniLogoYolu } from "@/lib/uni-logolar";
|
import { uniLogoYolu } from "@/lib/uni-logolar";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import type { Program } from "@/types/yokatlas";
|
import type { Program } from "@/types/yokatlas";
|
||||||
@@ -313,61 +312,6 @@ function AkademikGostergeler({ uni }: { uni: UniDetay }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Özet kartı (Apple Fitness özet kartlarının düzeni, sitenin ölçüleriyle):
|
|
||||||
* kartın en baskın öğesi iri sayı; ikonu sağında, küçük ikon kutusunda
|
|
||||||
* (DESIGN.md §4.4 ikon kutusunun küçük hali). Altında başlık ve tek satır
|
|
||||||
* açıklama. Telefonda kart iç genişliği ~131 px: en uzun sayı ("13.889",
|
|
||||||
* text-4xl ≈ 90 px) + size-8 ikon kutusu sığar; sm'den itibaren text-5xl.
|
|
||||||
* Harita yanındaki dört hücrenin tek kaynağı. `ek` verilirse açıklamanın
|
|
||||||
* altında durur (ör. Türkiye sırasındaki piksel şeridi). dl anlamı için dt
|
|
||||||
* DOM'da önce gelir, görsel sıra `order` ile kurulur.
|
|
||||||
*/
|
|
||||||
function OzetKart({
|
|
||||||
ikon: Ikon,
|
|
||||||
etiket,
|
|
||||||
deger,
|
|
||||||
birim,
|
|
||||||
alt,
|
|
||||||
ek,
|
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
ikon: LucideIcon;
|
|
||||||
etiket: string;
|
|
||||||
deger: ReactNode;
|
|
||||||
birim?: string;
|
|
||||||
alt: ReactNode;
|
|
||||||
ek?: ReactNode;
|
|
||||||
className?: string;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`flex flex-col rounded-2xl border border-slate-200 bg-white p-4 sm:p-5 ${className ?? ""}`}
|
|
||||||
>
|
|
||||||
<dt className="order-2 mt-2 text-base font-semibold text-slate-900">
|
|
||||||
{etiket}
|
|
||||||
</dt>
|
|
||||||
<dd className="order-1 flex items-start justify-between gap-1.5">
|
|
||||||
<span className="flex min-w-0 flex-wrap items-baseline gap-x-1">
|
|
||||||
<span className="font-heading text-4xl font-bold leading-none tracking-tight text-slate-900 sm:text-5xl">
|
|
||||||
{deger}
|
|
||||||
</span>
|
|
||||||
{birim ? (
|
|
||||||
<span className="text-base font-semibold text-slate-400">
|
|
||||||
{birim}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
<span className="flex size-8 shrink-0 items-center justify-center rounded-xl bg-primary/10 sm:size-10">
|
|
||||||
<Ikon className="size-4 text-primary sm:size-5" aria-hidden />
|
|
||||||
</span>
|
|
||||||
</dd>
|
|
||||||
<dd className="order-3 mt-0.5 text-sm text-slate-500">{alt}</dd>
|
|
||||||
{ek ? <dd className="order-4 mt-3">{ek}</dd> : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dördüncü özet kartı: URAP Türkiye sırası. Açıklamanın altında sıranın
|
* 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
|
* yerini gösteren piksel şeridi (PixelSira) durur. Kartın tamamı sıralama
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function UniversiteProgramTablosu({
|
|||||||
<TableHead className="hidden h-12 text-right text-sm font-semibold text-slate-500 sm:table-cell">
|
<TableHead className="hidden h-12 text-right text-sm font-semibold text-slate-500 sm:table-cell">
|
||||||
Taban sıra
|
Taban sıra
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="h-12 w-28 text-right">
|
<TableHead className="h-12 w-14 text-right sm:w-28">
|
||||||
<span className="sr-only">Netler ve listeye ekle</span>
|
<span className="sr-only">Netler ve listeye ekle</span>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -125,7 +125,7 @@ export function UniversiteProgramTablosu({
|
|||||||
</span>
|
</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="py-4 pr-4 text-right sm:pr-5">
|
<TableCell className="py-4 pr-4 text-right sm:pr-5">
|
||||||
<span className="inline-flex items-center gap-2">
|
<span className="inline-flex flex-col items-center gap-2 sm:flex-row">
|
||||||
<ProgramNetleriButonu
|
<ProgramNetleriButonu
|
||||||
programId={p.id}
|
programId={p.id}
|
||||||
isim={p.isim.trim()}
|
isim={p.isim.trim()}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export function ProgramEkleButonu({
|
|||||||
className={`inline-flex size-11 min-h-11 min-w-11 cursor-pointer items-center justify-center rounded-full border transition-[background-color,border-color,color,transform] duration-150 active:scale-[0.92] disabled:cursor-default focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${
|
className={`inline-flex size-11 min-h-11 min-w-11 cursor-pointer items-center justify-center rounded-full border transition-[background-color,border-color,color,transform] duration-150 active:scale-[0.92] disabled:cursor-default focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${
|
||||||
listede
|
listede
|
||||||
? "border-emerald-200 bg-emerald-50 text-emerald-600"
|
? "border-emerald-200 bg-emerald-50 text-emerald-600"
|
||||||
: "border-slate-200 bg-white text-slate-500 hover:border-primary hover:bg-primary/5 hover:text-primary disabled:opacity-40"
|
: "border-slate-200 bg-white text-slate-500 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 disabled:opacity-40"
|
||||||
} ${className}`}
|
} ${className}`}
|
||||||
>
|
>
|
||||||
{listede ? (
|
{listede ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user