From 22accb124108f42a52d95e35f8ae6a73899af1d9 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Thu, 24 Sep 2026 00:01:40 +0300 Subject: [PATCH 1/3] =?UTF-8?q?fix(sonuc,liste):=20tad=C4=B1ml=C4=B1k=20li?= =?UTF-8?q?stesi=20olanda=20tablonun=20alt=C4=B1na=20iner,=20=C3=A7ekmeced?= =?UTF-8?q?e=20risk=20etiketi=20metinle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /sonuc: kayıtlı listesi olan ziyaretçide harita ile ekleme tablosu arasına tadımlık CTA'sı girmez, tablonun altına iner (karar sunucudaki liste sayısı çerezinden, hidrasyonda zıplamaz) - Liste çekmecesi: risk noktası tek başına değil RISK_ETIKET metniyle - Sıralama formu: kompakt başlık düzeni --- src/app/sonuc/page.tsx | 21 +++- .../liste/components/liste-cekmecesi.tsx | 11 +- .../rapor/components/tadimlik-satiri.tsx | 10 +- .../sihirbaz/components/cta-sira-form.tsx | 114 ++++++++++++------ 4 files changed, 104 insertions(+), 52 deletions(-) diff --git a/src/app/sonuc/page.tsx b/src/app/sonuc/page.tsx index dbd7600..2cc4b0b 100644 --- a/src/app/sonuc/page.tsx +++ b/src/app/sonuc/page.tsx @@ -106,14 +106,19 @@ export default function SonucPage({ ); } + const tadimlik = ; + return (
- {/* Sayfa hiyerarşisi: manuel listenin haritası → tadımlık → ham - tablo. İki farklı ziyaretçi, iki farklı ilk ekran: + {/* Sayfa hiyerarşisi: manuel listenin haritası → ham tablo → + tadımlık (listesi yoksa tadımlık → tablo). İki farklı + ziyaretçi, iki farklı ilk ekran: • Listesi OLAN ziyaretçi ekranda önce kendi kurduğu listeyi - görür — o kişi "bu ürün ne?" aşamasını geçmiştir. + görür — o kişi "bu ürün ne?" aşamasını geçmiştir. Haritanın + hemen altında ekleme alanı (tablo) durur; tadımlık CTA'sı + ikisinin arasına girmez, tablonun altına iner. • Listesi OLMAYAN ziyaretçide harita DOM'a hiç girmez (manuel-harita.tsx), sıra bugünküyle birebir aynı kalır: tadımlık → tablo. Tadımlık, ödeme öncesi gösterebildiğimiz @@ -131,8 +136,12 @@ export default function SonucPage({ {/* Girişsiz tadımlık: gerçek rapor satırı formatında tek örnek. - Havuzda içerik yoksa bileşen hiç render edilmez. */} - + Havuzda içerik yoksa bileşen hiç render edilmez. Listesi + OLAN ziyaretçide harita ile tablo (+ ile ekleme alanı) + arasına CTA girmesin diye tadımlık tablonun altına iner; + karar sunucudaki liste sayısı çerezinden verilir, böylece + hidrasyonda yer değiştirip zıplamaz. */} + {haritayaYerAyir ? null : tadimlik} {/* Yapay Zeka'dan bağımsız ham tablo — sayfanın ana gövdesi; puan türü navbar'daki sıralama rozetinden değiştirilir. @@ -145,6 +154,8 @@ export default function SonucPage({ uniturGrubu={uniturGrubu} /> + {haritayaYerAyir ? tadimlik : null} + {/* Sihirbaz akışı (CTA + modal) — üretim ve liste /listem'de yaşar, burada yalnızca funnel kartları render edilir */} ) : null} {tercih.risk ? ( - + + + {RISK_ETIKET[tercih.risk]} + ) : null}
) : null} @@ -390,22 +426,24 @@ export function CtaSiraForm({ if (profil) { return ( -
-

+
+

{baslik ?? "Sıralamana yakın programlar hangileri? Saniyede gör."}

-

- Sıralaman kayıtlı ({profil.sira.toLocaleString("tr-TR")}. sıra) — - gerçek YÖK Atlas verisiyle hayal, dengeli ve güvenli dilimlerindeki +

+ +
+

+ Gerçek YÖK Atlas verisiyle hayal, dengeli ve güvenli dilimlerindeki programların hazır.

- - Sıralamana uygun programları gör - - + +
); } -- 2.49.1 From 5df0af0d38bfb61680e6c99a39e427a1bf4fefa1 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Thu, 24 Sep 2026 00:01:41 +0300 Subject: [PATCH 2/3] =?UTF-8?q?feat(katalog):=20Y=C3=96K=20=C4=B0statistik?= =?UTF-8?q?=20=C3=B6=C4=9Frenci/=C3=B6=C4=9Fretim=20=C3=BCyesi=20verisi=20?= =?UTF-8?q?ve=20T=C3=BCrkiye=20s=C4=B1ralamas=C4=B1=20sayfas=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/lib/yok-istatistik.ts (scripts/yok-istatistik-uret.py ile üretilir): üniversite başına örgün öğrenci ve öğretim üyesi, öğrenci/üye oranı ve Türkiye medyanı; katalog detayına `istatistik` alanı - /universite-siralamasi: URAP Türkiye sıralaması sayfası (URAP logosu ve zorunlu urapcenter.org atfıyla), sitemap'e eklendi --- public/urap-logo.svg | 74 ++++++ scripts/yok-istatistik-uret.py | 142 ++++++++++ src/app/sitemap.ts | 5 + src/app/universite-siralamasi/page.tsx | 34 +++ .../katalog/components/turkiye-siralamasi.tsx | 123 +++++++++ src/features/katalog/components/urap-logo.tsx | 28 ++ src/lib/katalog.ts | 4 + src/lib/yok-istatistik.ts | 242 ++++++++++++++++++ 8 files changed, 652 insertions(+) create mode 100644 public/urap-logo.svg create mode 100644 scripts/yok-istatistik-uret.py create mode 100644 src/app/universite-siralamasi/page.tsx create mode 100644 src/features/katalog/components/turkiye-siralamasi.tsx create mode 100644 src/features/katalog/components/urap-logo.tsx create mode 100644 src/lib/yok-istatistik.ts diff --git a/public/urap-logo.svg b/public/urap-logo.svg new file mode 100644 index 0000000..3c9aa5d --- /dev/null +++ b/public/urap-logo.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/yok-istatistik-uret.py b/scripts/yok-istatistik-uret.py new file mode 100644 index 0000000..209bae7 --- /dev/null +++ b/scripts/yok-istatistik-uret.py @@ -0,0 +1,142 @@ +"""YÖK İstatistik Excel'lerinden src/lib/yok-istatistik.ts sabitini üretir. + +Kullanım (yılda bir, YÖK İstatistik Mart'ta yayımlayınca): + python3 scripts/yok-istatistik-uret.py 2025-2026 + +Girdi: data/kaynak/yok-istatistik-{ogrenci,ogretim-elemani}-sayilari-.xls +(istatistik.yok.gov.tr'den tarayıcıyla indirilir, API yok). Bağımlılık: xlrd. + +Kurallar: +- Öğrenci = örgün öğrenci (BİRİNCİ Ö. + İKİNCİ Ö.), tüm düzeyler. UZAKTAN Ö. + ve AÇIK Ö. dışarıda: açıköğretimli üç büyük üniversitede oranı anlamsız + kılar ve kampüste okuyan öğrenciyi anlatmaz. +- Öğretim üyesi = Profesör + Doçent + Doktor Öğretim Üyesi (YÖK tanımı). +- Anahtar: /universite/[slug] sayfa slug'ı; yalnız yokatlas.db'de sayfası + olan üniversiteler yazılır, eşleşmeyenler ekrana basılır. +""" + +import sqlite3 +import sys +import unicodedata +import re +from pathlib import Path + +import xlrd + +KOK = Path(__file__).resolve().parent.parent +DONEM = sys.argv[1] if len(sys.argv) > 1 else "2025-2026" + + +def slugify(s: str) -> str: + # src/lib/slug.ts turkishSlugify ile aynı kural. + s = s.replace("I", "ı").replace("İ", "i").lower() + for a, b in [("ı", "i"), ("ş", "s"), ("ğ", "g"), ("ü", "u"), ("ö", "o"), + ("ç", "c"), ("â", "a"), ("î", "i"), ("û", "u")]: + s = s.replace(a, b) + s = unicodedata.normalize("NFKD", s) + s = re.sub(r"[̀-ͯ]", "", s) + s = re.sub(r"[^a-z0-9]+", "-", s) + return s.strip("-") + + +def sayfa_slug(ad: str) -> str: + return slugify(re.sub(r"\s*\([^)]*\)\s*$", "", ad).strip()) + + +def tamsayi(v) -> int: + return int(float(v)) if v not in ("", None) else 0 + + +def sayfa(ad: str): + return xlrd.open_workbook( + KOK / "data" / "kaynak" / f"yok-istatistik-{ad}-sayilari-{DONEM}.xls" + ).sheet_by_index(0) + + +# Öğrenci: sütun 1 ad, 4 öğrenim türü, 19 genel toplam (T). +ogrenci: dict[str, int] = {} +s = sayfa("ogrenci") +for r in range(4, s.nrows): + v = s.row_values(r) + ad, tur = str(v[1]).strip(), str(v[4]).strip() + if not ad or ad == "TOPLAM": + continue + if tur in ("BİRİNCİ Ö.", "İKİNCİ Ö."): + k = sayfa_slug(ad) + ogrenci[k] = ogrenci.get(k, 0) + tamsayi(v[19]) + +# Akademisyen: T sütunları Prof 6, Doç 9, Dr. Öğr. Üyesi 12, genel toplam 21. +akademik: dict[str, tuple[int, int]] = {} +s = sayfa("ogretim-elemani") +for r in range(4, s.nrows): + v = s.row_values(r) + ad = str(v[1]).strip() + if not ad or ad == "TOPLAM": + continue + uye = tamsayi(v[6]) + tamsayi(v[9]) + tamsayi(v[12]) + akademik[sayfa_slug(ad)] = (uye, tamsayi(v[21])) + +db = sqlite3.connect(KOK / "data" / "yokatlas.db") +sayfalar = {sayfa_slug(a) for (a,) in db.execute("SELECT DISTINCT universite FROM programs")} + +satirlar = [] +for k in sorted(sayfalar): + if k not in ogrenci or k not in akademik: + continue + uye, toplam = akademik[k] + if ogrenci[k] == 0 or uye == 0: + continue + satirlar.append(f' "{k}": {{ ogrenci: {ogrenci[k]}, ogretimUyesi: {uye}, akademisyen: {toplam} }},') + +oranlar = sorted(ogrenci[k] / akademik[k][0] for k in sayfalar + if ogrenci.get(k) and akademik.get(k, (0, 0))[0]) +medyan = round(oranlar[len(oranlar) // 2]) + +eksik = sorted(sayfalar - {l.split('"')[1] for l in satirlar}) +print(f"{len(satirlar)}/{len(sayfalar)} sayfa eşleşti. Eşleşmeyen {len(eksik)}:", file=sys.stderr) +for k in eksik: + print(" -", k, file=sys.stderr) +for k in sorted(set(ogrenci) - sayfalar): + print(" Excel'de var, sayfası yok:", k, file=sys.stderr) + +cikti = f'''// YÖK İstatistik {DONEM}: üniversite başına örgün öğrenci ve öğretim üyesi sayısı. +// +// ÜRETİLMİŞ DOSYA — elle düzenleme. Kaynak Excel'ler data/kaynak/ altında; +// yeniden üretmek için: python3 scripts/yok-istatistik-uret.py {DONEM} +// +// Öğrenci = örgün (birinci + ikinci öğretim), tüm düzeyler; uzaktan ve açık +// öğretim dahil değil. Öğretim üyesi = Prof. + Doç. + Dr. Öğr. Üyesi. +// Anahtar: /universite/[slug] sayfa slug'ı. Burada olmayan üniversitenin +// YÖK İstatistik satırı yok (KKTC/yurt dışı ya da öğrencisi/üyesi 0). + +export const YOK_ISTATISTIK_DONEM = "{DONEM}"; + +export const YOK_ISTATISTIK_KAYNAK_URL = "https://istatistik.yok.gov.tr/"; + +/** Sayfası olan üniversitelerde öğretim üyesi başına öğrencinin medyanı — kıyas çizgisi. */ +export const OGRENCI_BASINA_UYE_MEDYAN = {medyan}; + +export type UniIstatistik = {{ + /** Örgün öğrenci (ön lisans + lisans + yüksek lisans + doktora) */ + ogrenci: number; + /** Profesör + doçent + doktor öğretim üyesi */ + ogretimUyesi: number; + /** Tüm akademik personel (öğretim görevlisi ve araştırma görevlisi dahil) */ + akademisyen: number; +}}; + +export const YOK_ISTATISTIK: Record = {{ +{chr(10).join(satirlar)} +}}; + +/** Üniversite sayfa slug'ının YÖK İstatistik satırı; yoksa null. */ +export function uniIstatistikBul(slug: string): UniIstatistik | null {{ + return YOK_ISTATISTIK[slug] ?? null; +}} + +/** Öğretim üyesi başına örgün öğrenci, tam sayıya yuvarlanmış. */ +export function ogrenciBasinaUye(i: UniIstatistik): number {{ + return Math.round(i.ogrenci / i.ogretimUyesi); +}} +''' +(KOK / "src" / "lib" / "yok-istatistik.ts").write_text(cikti, encoding="utf-8") diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 7864436..ce731ff 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -19,6 +19,11 @@ const staticEntries: MetadataRoute.Sitemap = [ priority: 0.8, changeFrequency: "weekly", }, + { + url: `${SITE_URL}/universite-siralamasi`, + priority: 0.7, + changeFrequency: "yearly", + }, { url: `${SITE_URL}/paket`, priority: 0.7, changeFrequency: "monthly" }, { url: `${SITE_URL}/meraklisina`, priority: 0.6, changeFrequency: "monthly" }, { url: `${SITE_URL}/iletisim`, priority: 0.3, changeFrequency: "yearly" }, diff --git a/src/app/universite-siralamasi/page.tsx b/src/app/universite-siralamasi/page.tsx new file mode 100644 index 0000000..4b5b6b7 --- /dev/null +++ b/src/app/universite-siralamasi/page.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { JsonLd, breadcrumbJsonLd, sayfaMetadata } from "@/lib/seo"; +import { SiteFooter } from "@/components/site-footer"; +import { TurkiyeSiralamasi } from "@/features/katalog/components/turkiye-siralamasi"; +import { URAP_DONEM, URAP_TOPLAM } from "@/lib/urap-siralama"; + +const BASLIK = `Türkiye Üniversite Sıralaması ${URAP_DONEM}`; + +export const metadata: Metadata = sayfaMetadata({ + title: BASLIK, + description: `Türkiye'deki ${URAP_TOPLAM} üniversitenin URAP ${URAP_DONEM} akademik performans sıralaması — sıra, puan ve üniversite sayfaları.`, + path: "/universite-siralamasi", +}); + +export default function UniversiteSiralamasiPage() { + return ( + <> +
+ +

+ {BASLIK} +

+ +
+ + + ); +} diff --git a/src/features/katalog/components/turkiye-siralamasi.tsx b/src/features/katalog/components/turkiye-siralamasi.tsx new file mode 100644 index 0000000..be25374 --- /dev/null +++ b/src/features/katalog/components/turkiye-siralamasi.tsx @@ -0,0 +1,123 @@ +import Link from "next/link"; +import { getAllUniversiteler } from "../katalog-queries"; +import { trBaslikDuzeni } from "@/lib/slug"; +import { UNITUR_ETIKET } from "@/lib/unitur"; +import { UniLogo } from "@/components/uni-logo"; +import { PagePixelDivider } from "@/components/pixel-decor"; +import { UrapLogo } from "./urap-logo"; +import { + URAP_DONEM, + URAP_KAYNAK_URL, + URAP_SIRA, + URAP_TOPLAM, +} from "@/lib/urap-siralama"; + +const sayi = (n: number) => n.toLocaleString("tr-TR"); +const puanYaz = (n: number) => + n.toLocaleString("tr-TR", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); + +/** + * URAP atfı: izin "urapcenter.org adresine aktif bağlantı" şartına bağlı — + * gerçek , yeni sekme, rel="noopener", nofollow YOK. Sayfanın başında + * ve sonunda tekrar edilir, liste uzun olduğundan sıra sayısıyla aynı ekranda + * kalsın. + */ +function UrapAtfi() { + return ( + + urapcenter.org + + ); +} + +export function TurkiyeSiralamasi() { + const ozetler = new Map(getAllUniversiteler().map((u) => [u.slug, u])); + const satirlar = Object.entries(URAP_SIRA) + .map(([slug, u]) => ({ slug, ...u, ozet: ozetler.get(slug) })) + .sort((a, b) => a.sira - b.sira); + const enYuksek = satirlar[0]?.puan ?? 1; + + return ( + <> +
+

+ Türkiye'deki {sayi(URAP_TOPLAM)} üniversitenin URAP (University + Ranking by Academic Performance) {URAP_DONEM} genel sıralaması. URAP, + üniversiteleri yayın, atıf, proje ve doktora gibi{" "} + akademik yayın ve araştırma performansı{" "} + göstergeleriyle puanlar; eğitim kalitesini ya da mezunların iş bulma + oranını ölçmez. Bölüm seçerken tek başına ölçüt değil, taban + sıralamayla birlikte bakılacak bir bilgi. Kaynak: . +

+ +
+ + + +
    + {satirlar.map((u) => { + const ad = u.ozet?.ad ?? trBaslikDuzeni(u.slug.replace(/-/g, " ")); + const tur = u.ozet?.unitur ? UNITUR_ETIKET[u.ozet.unitur]?.cip : null; + const alt = [u.ozet?.il ? trBaslikDuzeni(u.ozet.il) : null, tur] + .filter(Boolean) + .join(" · "); + return ( +
  1. + + + {u.sira} + + + + + {ad} + + {alt ? ( + {alt} + ) : null} + + + + + + + {puanYaz(u.puan)} + + + +
  2. + ); + })} +
+ +
+ +

+ Sağdaki sayı URAP toplam puanıdır. Listede olmayan üniversite + sıralamaya girmemiştir — KKTC/yurt dışı kurumlar ve araştırma çıktısı + henüz oluşmamış yeni üniversiteler listede yer almaz; bu, + "kötü" demek değildir. URAP Türkiye Sıralaması {URAP_DONEM}, + URAP Araştırma Laboratuvarı'nın izniyle yayımlanmıştır. Kaynak:{" "} + . +

+
+ + ); +} diff --git a/src/features/katalog/components/urap-logo.tsx b/src/features/katalog/components/urap-logo.tsx new file mode 100644 index 0000000..625fafe --- /dev/null +++ b/src/features/katalog/components/urap-logo.tsx @@ -0,0 +1,28 @@ +import Image from "next/image"; +import { URAP_KAYNAK_URL } from "@/lib/urap-siralama"; + +/** + * URAP logosu, urapcenter.org'a aktif bağlantı olarak. Atıf şartı gereği + * gerçek , yeni sekme, rel="noopener", nofollow YOK. Logo resmî + * PNG'den (urapcenter.org/img/urap-logo/logo.png) vektörleştirildi — + * `public/urap-logo.svg`, oran 819×261. + */ +export function UrapLogo({ className = "h-8" }: { className?: string }) { + return ( + + URAP — University Ranking by Academic Performance + + ); +} diff --git a/src/lib/katalog.ts b/src/lib/katalog.ts index fee4430..7669d89 100644 --- a/src/lib/katalog.ts +++ b/src/lib/katalog.ts @@ -11,6 +11,7 @@ import { kategoriBul, type Kategori } from "./kategoriler"; import { uniAdiNormalize } from "./harita"; import { bolumBazAdi, turkishSlugify, trBaslikDuzeni } from "./slug"; import { urapSirasiBul, type UrapSira } from "./urap-siralama"; +import { uniIstatistikBul, type UniIstatistik } from "./yok-istatistik"; /** * Programatik SEO sayfalarının veri katmanı (/bolum, /universite, hub'lar). @@ -230,6 +231,8 @@ export type UniDetay = UniOzet & { * Gösterildiği her yüzeyde URAP_KAYNAK_URL'ye aktif atıf zorunludur. */ urap: UrapSira | null; + /** YÖK İstatistik örgün öğrenci / öğretim üyesi sayısı; KKTC ve yurt dışında null. */ + istatistik: UniIstatistik | null; fakulteler: { fakulte: string; programlar: Program[] }[]; stats: { minSira: number | null; @@ -400,6 +403,7 @@ export function getUniversiteBySlug(slug: string): UniDetay | null { return { ...ozet, urap: urapSirasiBul(slug), + istatistik: uniIstatistikBul(slug), fakulteler: [...gruplar.entries()] .map(([fakulte, programlar]) => ({ fakulte, programlar })) .sort((a, b) => a.fakulte.localeCompare(b.fakulte, "tr-TR")), diff --git a/src/lib/yok-istatistik.ts b/src/lib/yok-istatistik.ts new file mode 100644 index 0000000..fef2c36 --- /dev/null +++ b/src/lib/yok-istatistik.ts @@ -0,0 +1,242 @@ +// YÖK İstatistik 2025-2026: üniversite başına örgün öğrenci ve öğretim üyesi sayısı. +// +// ÜRETİLMİŞ DOSYA — elle düzenleme. Kaynak Excel'ler data/kaynak/ altında; +// yeniden üretmek için: python3 scripts/yok-istatistik-uret.py 2025-2026 +// +// Öğrenci = örgün (birinci + ikinci öğretim), tüm düzeyler; uzaktan ve açık +// öğretim dahil değil. Öğretim üyesi = Prof. + Doç. + Dr. Öğr. Üyesi. +// Anahtar: /universite/[slug] sayfa slug'ı. Burada olmayan üniversitenin +// YÖK İstatistik satırı yok (KKTC/yurt dışı ya da öğrencisi/üyesi 0). + +export const YOK_ISTATISTIK_DONEM = "2025-2026"; + +export const YOK_ISTATISTIK_KAYNAK_URL = "https://istatistik.yok.gov.tr/"; + +/** Sayfası olan üniversitelerde öğretim üyesi başına öğrencinin medyanı — kıyas çizgisi. */ +export const OGRENCI_BASINA_UYE_MEDYAN = 35; + +export type UniIstatistik = { + /** Örgün öğrenci (ön lisans + lisans + yüksek lisans + doktora) */ + ogrenci: number; + /** Profesör + doçent + doktor öğretim üyesi */ + ogretimUyesi: number; + /** Tüm akademik personel (öğretim görevlisi ve araştırma görevlisi dahil) */ + akademisyen: number; +}; + +export const YOK_ISTATISTIK: Record = { + "abdullah-gul-universitesi": { ogrenci: 3936, ogretimUyesi: 107, akademisyen: 300 }, + "acibadem-mehmet-ali-aydinlar-universitesi": { ogrenci: 6279, ogretimUyesi: 375, akademisyen: 646 }, + "adana-alparslan-turkes-bilim-ve-teknoloji-universitesi": { ogrenci: 6220, ogretimUyesi: 236, akademisyen: 446 }, + "adiyaman-universitesi": { ogrenci: 16659, ogretimUyesi: 548, akademisyen: 862 }, + "afyon-kocatepe-universitesi": { ogrenci: 29711, ogretimUyesi: 699, akademisyen: 1109 }, + "afyonkarahisar-saglik-bilimleri-universitesi": { ogrenci: 7072, ogretimUyesi: 325, akademisyen: 620 }, + "agri-ibrahim-cecen-universitesi": { ogrenci: 15239, ogretimUyesi: 390, akademisyen: 574 }, + "akdeniz-universitesi": { ogrenci: 55992, ogretimUyesi: 1339, akademisyen: 2658 }, + "aksaray-universitesi": { ogrenci: 18151, ogretimUyesi: 626, akademisyen: 963 }, + "alanya-alaaddin-keykubat-universitesi": { ogrenci: 15322, ogretimUyesi: 410, akademisyen: 696 }, + "alanya-universitesi": { ogrenci: 1207, ogretimUyesi: 51, akademisyen: 100 }, + "altinbas-universitesi": { ogrenci: 12055, ogretimUyesi: 227, akademisyen: 334 }, + "amasya-universitesi": { ogrenci: 16656, ogretimUyesi: 543, akademisyen: 748 }, + "anadolu-universitesi": { ogrenci: 25934, ogretimUyesi: 900, akademisyen: 1470 }, + "ankara-bilim-universitesi": { ogrenci: 3626, ogretimUyesi: 72, akademisyen: 134 }, + "ankara-haci-bayram-veli-universitesi": { ogrenci: 27559, ogretimUyesi: 867, akademisyen: 1221 }, + "ankara-medipol-universitesi": { ogrenci: 14035, ogretimUyesi: 270, akademisyen: 458 }, + "ankara-muzik-ve-guzel-sanatlar-universitesi": { ogrenci: 1457, ogretimUyesi: 86, akademisyen: 136 }, + "ankara-sosyal-bilimler-universitesi": { ogrenci: 5879, ogretimUyesi: 274, akademisyen: 477 }, + "ankara-universitesi": { ogrenci: 56128, ogretimUyesi: 2225, akademisyen: 3702 }, + "ankara-yildirim-beyazit-universitesi": { ogrenci: 26177, ogretimUyesi: 1019, akademisyen: 1568 }, + "antalya-belek-universitesi": { ogrenci: 2553, ogretimUyesi: 44, akademisyen: 94 }, + "antalya-bilim-universitesi": { ogrenci: 8597, ogretimUyesi: 180, akademisyen: 315 }, + "ardahan-universitesi": { ogrenci: 7355, ogretimUyesi: 236, akademisyen: 380 }, + "artvin-coruh-universitesi": { ogrenci: 12358, ogretimUyesi: 332, akademisyen: 566 }, + "atasehir-adiguzel-meslek-yuksekokulu": { ogrenci: 1476, ogretimUyesi: 2, akademisyen: 32 }, + "ataturk-universitesi": { ogrenci: 58279, ogretimUyesi: 1800, akademisyen: 2758 }, + "atilim-universitesi": { ogrenci: 9885, ogretimUyesi: 331, akademisyen: 594 }, + "avrasya-universitesi": { ogrenci: 7495, ogretimUyesi: 119, akademisyen: 238 }, + "aydin-adnan-menderes-universitesi": { ogrenci: 43686, ogretimUyesi: 1232, akademisyen: 1924 }, + "bahcesehir-universitesi": { ogrenci: 21181, ogretimUyesi: 560, akademisyen: 838 }, + "balikesir-universitesi": { ogrenci: 28673, ogretimUyesi: 890, akademisyen: 1313 }, + "bandirma-onyedi-eylul-universitesi": { ogrenci: 18949, ogretimUyesi: 456, akademisyen: 653 }, + "bartin-universitesi": { ogrenci: 15615, ogretimUyesi: 452, akademisyen: 723 }, + "baskent-universitesi": { ogrenci: 17784, ogretimUyesi: 976, akademisyen: 1920 }, + "batman-universitesi": { ogrenci: 14701, ogretimUyesi: 360, akademisyen: 584 }, + "bayburt-universitesi": { ogrenci: 13883, ogretimUyesi: 314, akademisyen: 499 }, + "beykoz-universitesi": { ogrenci: 5251, ogretimUyesi: 94, akademisyen: 154 }, + "bezm-i-alem-vakif-universitesi": { ogrenci: 4076, ogretimUyesi: 231, akademisyen: 605 }, + "bilecik-seyh-edebali-universitesi": { ogrenci: 19612, ogretimUyesi: 495, akademisyen: 824 }, + "bingol-universitesi": { ogrenci: 18558, ogretimUyesi: 494, akademisyen: 744 }, + "biruni-universitesi": { ogrenci: 15154, ogretimUyesi: 347, akademisyen: 449 }, + "bitlis-eren-universitesi": { ogrenci: 14635, ogretimUyesi: 342, akademisyen: 549 }, + "bogazici-universitesi": { ogrenci: 13839, ogretimUyesi: 503, akademisyen: 1031 }, + "bolu-abant-izzet-baysal-universitesi": { ogrenci: 30443, ogretimUyesi: 965, akademisyen: 1491 }, + "burdur-mehmet-akif-ersoy-universitesi": { ogrenci: 34361, ogretimUyesi: 799, akademisyen: 1209 }, + "bursa-teknik-universitesi": { ogrenci: 12346, ogretimUyesi: 350, akademisyen: 601 }, + "bursa-uludag-universitesi": { ogrenci: 58341, ogretimUyesi: 1368, akademisyen: 2331 }, + "cag-universitesi": { ogrenci: 3667, ogretimUyesi: 63, akademisyen: 127 }, + "canakkale-onsekiz-mart-universitesi": { ogrenci: 42341, ogretimUyesi: 1254, akademisyen: 2058 }, + "cankaya-universitesi": { ogrenci: 6401, ogretimUyesi: 148, akademisyen: 260 }, + "cankiri-karatekin-universitesi": { ogrenci: 17291, ogretimUyesi: 488, akademisyen: 740 }, + "cukurova-universitesi": { ogrenci: 44430, ogretimUyesi: 1187, akademisyen: 2116 }, + "demiroglu-bilim-universitesi": { ogrenci: 2633, ogretimUyesi: 179, akademisyen: 231 }, + "dicle-universitesi": { ogrenci: 42368, ogretimUyesi: 1011, akademisyen: 1641 }, + "dogus-universitesi": { ogrenci: 18442, ogretimUyesi: 217, akademisyen: 459 }, + "dokuz-eylul-universitesi": { ogrenci: 56833, ogretimUyesi: 1849, akademisyen: 2951 }, + "duzce-universitesi": { ogrenci: 28561, ogretimUyesi: 765, akademisyen: 1230 }, + "ege-universitesi": { ogrenci: 49852, ogretimUyesi: 1744, akademisyen: 2983 }, + "erciyes-universitesi": { ogrenci: 42735, ogretimUyesi: 1237, akademisyen: 2195 }, + "erzincan-binali-yildirim-universitesi": { ogrenci: 23617, ogretimUyesi: 764, akademisyen: 1070 }, + "erzurum-teknik-universitesi": { ogrenci: 5876, ogretimUyesi: 268, akademisyen: 389 }, + "eskisehir-osmangazi-universitesi": { ogrenci: 26667, ogretimUyesi: 1043, akademisyen: 1695 }, + "eskisehir-teknik-universitesi": { ogrenci: 13840, ogretimUyesi: 467, akademisyen: 735 }, + "fatih-sultan-mehmet-vakif-universitesi": { ogrenci: 7676, ogretimUyesi: 169, akademisyen: 328 }, + "fenerbahce-universitesi": { ogrenci: 9741, ogretimUyesi: 195, akademisyen: 311 }, + "firat-universitesi": { ogrenci: 39181, ogretimUyesi: 1167, akademisyen: 1924 }, + "galatasaray-universitesi": { ogrenci: 4398, ogretimUyesi: 162, akademisyen: 310 }, + "gazi-universitesi": { ogrenci: 38023, ogretimUyesi: 1877, akademisyen: 3115 }, + "gaziantep-islam-bilim-ve-teknoloji-universitesi": { ogrenci: 5934, ogretimUyesi: 167, akademisyen: 248 }, + "gaziantep-universitesi": { ogrenci: 45986, ogretimUyesi: 906, akademisyen: 1689 }, + "gebze-teknik-universitesi": { ogrenci: 10152, ogretimUyesi: 354, akademisyen: 820 }, + "giresun-universitesi": { ogrenci: 24556, ogretimUyesi: 820, akademisyen: 1165 }, + "gumushane-universitesi": { ogrenci: 19558, ogretimUyesi: 425, akademisyen: 664 }, + "hacettepe-universitesi": { ogrenci: 45592, ogretimUyesi: 2015, akademisyen: 3536 }, + "hakkari-universitesi": { ogrenci: 5255, ogretimUyesi: 216, akademisyen: 332 }, + "halic-universitesi": { ogrenci: 15757, ogretimUyesi: 256, akademisyen: 470 }, + "harran-universitesi": { ogrenci: 28476, ogretimUyesi: 740, akademisyen: 1249 }, + "hasan-kalyoncu-universitesi": { ogrenci: 7697, ogretimUyesi: 206, akademisyen: 311 }, + "hatay-mustafa-kemal-universitesi": { ogrenci: 18471, ogretimUyesi: 685, akademisyen: 1146 }, + "hitit-universitesi": { ogrenci: 18433, ogretimUyesi: 492, akademisyen: 821 }, + "ibn-haldun-universitesi": { ogrenci: 1877, ogretimUyesi: 110, akademisyen: 171 }, + "igdir-universitesi": { ogrenci: 13947, ogretimUyesi: 314, akademisyen: 491 }, + "ihsan-dogramaci-bilkent-universitesi": { ogrenci: 12244, ogretimUyesi: 367, akademisyen: 707 }, + "inonu-universitesi": { ogrenci: 30854, ogretimUyesi: 948, akademisyen: 1584 }, + "isik-universitesi": { ogrenci: 5999, ogretimUyesi: 108, akademisyen: 189 }, + "iskenderun-teknik-universitesi": { ogrenci: 13168, ogretimUyesi: 263, akademisyen: 388 }, + "isparta-uygulamali-bilimler-universitesi": { ogrenci: 22373, ogretimUyesi: 415, akademisyen: 739 }, + "istanbul-29-mayis-universitesi": { ogrenci: 5492, ogretimUyesi: 129, akademisyen: 187 }, + "istanbul-arel-universitesi": { ogrenci: 14076, ogretimUyesi: 276, akademisyen: 519 }, + "istanbul-atlas-universitesi": { ogrenci: 10704, ogretimUyesi: 376, akademisyen: 501 }, + "istanbul-aydin-universitesi": { ogrenci: 47200, ogretimUyesi: 777, akademisyen: 1200 }, + "istanbul-beykent-universitesi": { ogrenci: 26693, ogretimUyesi: 501, akademisyen: 824 }, + "istanbul-bilgi-universitesi": { ogrenci: 19216, ogretimUyesi: 310, akademisyen: 568 }, + "istanbul-esenyurt-universitesi": { ogrenci: 14673, ogretimUyesi: 150, akademisyen: 320 }, + "istanbul-galata-universitesi": { ogrenci: 4213, ogretimUyesi: 125, akademisyen: 192 }, + "istanbul-gedik-universitesi": { ogrenci: 10243, ogretimUyesi: 175, akademisyen: 306 }, + "istanbul-gelisim-universitesi": { ogrenci: 36730, ogretimUyesi: 472, akademisyen: 775 }, + "istanbul-kent-universitesi": { ogrenci: 15262, ogretimUyesi: 231, akademisyen: 405 }, + "istanbul-kultur-universitesi": { ogrenci: 13322, ogretimUyesi: 268, akademisyen: 459 }, + "istanbul-medeniyet-universitesi": { ogrenci: 15978, ogretimUyesi: 746, akademisyen: 1141 }, + "istanbul-medipol-universitesi": { ogrenci: 49066, ogretimUyesi: 975, akademisyen: 1647 }, + "istanbul-nisantasi-universitesi": { ogrenci: 42789, ogretimUyesi: 485, akademisyen: 783 }, + "istanbul-okan-universitesi": { ogrenci: 17022, ogretimUyesi: 560, akademisyen: 823 }, + "istanbul-rumeli-universitesi": { ogrenci: 10118, ogretimUyesi: 165, akademisyen: 257 }, + "istanbul-sabahattin-zaim-universitesi": { ogrenci: 8740, ogretimUyesi: 261, akademisyen: 392 }, + "istanbul-saglik-ve-teknoloji-universitesi": { ogrenci: 5136, ogretimUyesi: 350, akademisyen: 434 }, + "istanbul-sisli-meslek-yuksekokulu": { ogrenci: 10446, ogretimUyesi: 19, akademisyen: 80 }, + "istanbul-teknik-universitesi": { ogrenci: 33900, ogretimUyesi: 1253, akademisyen: 2524 }, + "istanbul-ticaret-universitesi": { ogrenci: 8384, ogretimUyesi: 167, akademisyen: 270 }, + "istanbul-topkapi-universitesi": { ogrenci: 24477, ogretimUyesi: 237, akademisyen: 424 }, + "istanbul-universitesi": { ogrenci: 52770, ogretimUyesi: 2125, akademisyen: 3379 }, + "istanbul-universitesi-cerrahpasa": { ogrenci: 29490, ogretimUyesi: 1318, akademisyen: 2053 }, + "istanbul-yeni-yuzyil-universitesi": { ogrenci: 20511, ogretimUyesi: 271, akademisyen: 424 }, + "istinye-universitesi": { ogrenci: 17697, ogretimUyesi: 525, akademisyen: 745 }, + "izmir-bakircay-universitesi": { ogrenci: 8576, ogretimUyesi: 340, akademisyen: 502 }, + "izmir-demokrasi-universitesi": { ogrenci: 11641, ogretimUyesi: 321, akademisyen: 450 }, + "izmir-ekonomi-universitesi": { ogrenci: 11623, ogretimUyesi: 356, akademisyen: 626 }, + "izmir-katip-celebi-universitesi": { ogrenci: 19148, ogretimUyesi: 778, akademisyen: 1215 }, + "izmir-konak-meslek-yuksekokulu": { ogrenci: 3816, ogretimUyesi: 28, akademisyen: 116 }, + "izmir-tinaztepe-universitesi": { ogrenci: 4685, ogretimUyesi: 199, akademisyen: 232 }, + "izmir-yuksek-teknoloji-enstitusu": { ogrenci: 7109, ogretimUyesi: 234, akademisyen: 685 }, + "kadir-has-universitesi": { ogrenci: 5237, ogretimUyesi: 163, akademisyen: 257 }, + "kafkas-universitesi": { ogrenci: 18892, ogretimUyesi: 712, akademisyen: 989 }, + "kahramanmaras-istiklal-universitesi": { ogrenci: 4080, ogretimUyesi: 104, akademisyen: 186 }, + "kahramanmaras-sutcu-imam-universitesi": { ogrenci: 30728, ogretimUyesi: 867, akademisyen: 1335 }, + "kapadokya-universitesi": { ogrenci: 10946, ogretimUyesi: 149, akademisyen: 276 }, + "karabuk-universitesi": { ogrenci: 37533, ogretimUyesi: 808, akademisyen: 1179 }, + "karadeniz-teknik-universitesi": { ogrenci: 28957, ogretimUyesi: 1198, akademisyen: 2144 }, + "karamanoglu-mehmetbey-universitesi": { ogrenci: 19098, ogretimUyesi: 641, akademisyen: 908 }, + "kastamonu-universitesi": { ogrenci: 25308, ogretimUyesi: 639, akademisyen: 980 }, + "kayseri-universitesi": { ogrenci: 14389, ogretimUyesi: 174, akademisyen: 315 }, + "kilis-7-aralik-universitesi": { ogrenci: 13370, ogretimUyesi: 304, akademisyen: 478 }, + "kirikkale-universitesi": { ogrenci: 29779, ogretimUyesi: 775, akademisyen: 1209 }, + "kirklareli-universitesi": { ogrenci: 22377, ogretimUyesi: 560, akademisyen: 915 }, + "kirsehir-ahi-evran-universitesi": { ogrenci: 20182, ogretimUyesi: 670, akademisyen: 990 }, + "koc-universitesi": { ogrenci: 8596, ogretimUyesi: 432, akademisyen: 838 }, + "kocaeli-saglik-ve-teknoloji-universitesi": { ogrenci: 6271, ogretimUyesi: 115, akademisyen: 152 }, + "kocaeli-universitesi": { ogrenci: 53036, ogretimUyesi: 1191, akademisyen: 2174 }, + "konya-gida-ve-tarim-universitesi": { ogrenci: 1614, ogretimUyesi: 36, akademisyen: 57 }, + "konya-teknik-universitesi": { ogrenci: 10688, ogretimUyesi: 312, akademisyen: 521 }, + "kto-karatay-universitesi": { ogrenci: 9768, ogretimUyesi: 211, akademisyen: 341 }, + "kutahya-dumlupinar-universitesi": { ogrenci: 38491, ogretimUyesi: 652, akademisyen: 1007 }, + "kutahya-saglik-bilimleri-universitesi": { ogrenci: 6757, ogretimUyesi: 326, akademisyen: 521 }, + "lokman-hekim-universitesi": { ogrenci: 5137, ogretimUyesi: 241, akademisyen: 331 }, + "malatya-turgut-ozal-universitesi": { ogrenci: 12370, ogretimUyesi: 355, akademisyen: 510 }, + "maltepe-universitesi": { ogrenci: 6288, ogretimUyesi: 370, akademisyen: 577 }, + "manisa-celal-bayar-universitesi": { ogrenci: 42218, ogretimUyesi: 986, akademisyen: 1647 }, + "mardin-artuklu-universitesi": { ogrenci: 16705, ogretimUyesi: 456, akademisyen: 722 }, + "marmara-universitesi": { ogrenci: 56733, ogretimUyesi: 2168, akademisyen: 3410 }, + "mef-universitesi": { ogrenci: 3708, ogretimUyesi: 115, akademisyen: 181 }, + "mersin-universitesi": { ogrenci: 37444, ogretimUyesi: 914, akademisyen: 1749 }, + "mimar-sinan-guzel-sanatlar-universitesi": { ogrenci: 8956, ogretimUyesi: 457, akademisyen: 631 }, + "mudanya-universitesi": { ogrenci: 3040, ogretimUyesi: 98, akademisyen: 158 }, + "mugla-sitki-kocman-universitesi": { ogrenci: 39185, ogretimUyesi: 991, akademisyen: 1727 }, + "munzur-universitesi": { ogrenci: 9826, ogretimUyesi: 318, akademisyen: 511 }, + "mus-alparslan-universitesi": { ogrenci: 14993, ogretimUyesi: 418, akademisyen: 647 }, + "necmettin-erbakan-universitesi": { ogrenci: 36239, ogretimUyesi: 1164, akademisyen: 1954 }, + "nevsehir-haci-bektas-veli-universitesi": { ogrenci: 21073, ogretimUyesi: 471, akademisyen: 822 }, + "nigde-omer-halisdemir-universitesi": { ogrenci: 26201, ogretimUyesi: 746, akademisyen: 1149 }, + "nuh-naci-yazgan-universitesi": { ogrenci: 2902, ogretimUyesi: 98, akademisyen: 162 }, + "ondokuz-mayis-universitesi": { ogrenci: 44852, ogretimUyesi: 1253, akademisyen: 2305 }, + "ordu-universitesi": { ogrenci: 17767, ogretimUyesi: 598, akademisyen: 973 }, + "orta-dogu-teknik-universitesi": { ogrenci: 30055, ogretimUyesi: 757, akademisyen: 2058 }, + "osmaniye-korkut-ata-universitesi": { ogrenci: 16460, ogretimUyesi: 365, akademisyen: 609 }, + "ostim-teknik-universitesi": { ogrenci: 8469, ogretimUyesi: 148, akademisyen: 257 }, + "ozyegin-universitesi": { ogrenci: 8369, ogretimUyesi: 255, akademisyen: 465 }, + "pamukkale-universitesi": { ogrenci: 43013, ogretimUyesi: 1238, akademisyen: 2113 }, + "piri-reis-universitesi": { ogrenci: 6741, ogretimUyesi: 121, akademisyen: 217 }, + "recep-tayyip-erdogan-universitesi": { ogrenci: 18693, ogretimUyesi: 703, akademisyen: 1252 }, + "sabanci-universitesi": { ogrenci: 5093, ogretimUyesi: 212, akademisyen: 363 }, + "saglik-bilimleri-universitesi": { ogrenci: 25070, ogretimUyesi: 3992, akademisyen: 4397 }, + "sakarya-universitesi": { ogrenci: 44027, ogretimUyesi: 1088, akademisyen: 1678 }, + "sakarya-uygulamali-bilimler-universitesi": { ogrenci: 29466, ogretimUyesi: 374, akademisyen: 698 }, + "samsun-universitesi": { ogrenci: 8287, ogretimUyesi: 363, akademisyen: 515 }, + "sanko-universitesi": { ogrenci: 1724, ogretimUyesi: 115, akademisyen: 167 }, + "selcuk-universitesi": { ogrenci: 61768, ogretimUyesi: 1486, akademisyen: 2594 }, + "siirt-universitesi": { ogrenci: 16165, ogretimUyesi: 482, akademisyen: 689 }, + "sinop-universitesi": { ogrenci: 13767, ogretimUyesi: 421, akademisyen: 626 }, + "sirnak-universitesi": { ogrenci: 6543, ogretimUyesi: 211, akademisyen: 330 }, + "sivas-bilim-ve-teknoloji-universitesi": { ogrenci: 940, ogretimUyesi: 81, akademisyen: 177 }, + "sivas-cumhuriyet-universitesi": { ogrenci: 46095, ogretimUyesi: 1184, akademisyen: 1921 }, + "suleyman-demirel-universitesi": { ogrenci: 33222, ogretimUyesi: 1097, akademisyen: 1847 }, + "tarsus-universitesi": { ogrenci: 7088, ogretimUyesi: 161, akademisyen: 285 }, + "ted-universitesi": { ogrenci: 5454, ogretimUyesi: 188, akademisyen: 331 }, + "tekirdag-namik-kemal-universitesi": { ogrenci: 28761, ogretimUyesi: 661, akademisyen: 1125 }, + "tobb-ekonomi-ve-teknoloji-universitesi": { ogrenci: 5597, ogretimUyesi: 242, akademisyen: 378 }, + "tokat-gaziosmanpasa-universitesi": { ogrenci: 32387, ogretimUyesi: 964, akademisyen: 1617 }, + "toros-universitesi": { ogrenci: 4319, ogretimUyesi: 96, akademisyen: 155 }, + "trabzon-universitesi": { ogrenci: 11965, ogretimUyesi: 432, akademisyen: 665 }, + "trakya-universitesi": { ogrenci: 35922, ogretimUyesi: 933, akademisyen: 1710 }, + "turk-alman-universitesi": { ogrenci: 4574, ogretimUyesi: 126, akademisyen: 364 }, + "turk-hava-kurumu-universitesi": { ogrenci: 4144, ogretimUyesi: 73, akademisyen: 162 }, + "ufuk-universitesi": { ogrenci: 4280, ogretimUyesi: 181, akademisyen: 284 }, + "usak-universitesi": { ogrenci: 22113, ogretimUyesi: 495, akademisyen: 930 }, + "uskudar-universitesi": { ogrenci: 23805, ogretimUyesi: 506, akademisyen: 702 }, + "van-yuzuncu-yil-universitesi": { ogrenci: 28497, ogretimUyesi: 1195, akademisyen: 1744 }, + "yalova-universitesi": { ogrenci: 16528, ogretimUyesi: 451, akademisyen: 758 }, + "yasar-universitesi": { ogrenci: 10081, ogretimUyesi: 226, akademisyen: 414 }, + "yeditepe-universitesi": { ogrenci: 19371, ogretimUyesi: 602, akademisyen: 1126 }, + "yildiz-teknik-universitesi": { ogrenci: 36618, ogretimUyesi: 1072, akademisyen: 1801 }, + "yozgat-bozok-universitesi": { ogrenci: 23118, ogretimUyesi: 656, akademisyen: 1028 }, + "yuksek-ihtisas-universitesi": { ogrenci: 3654, ogretimUyesi: 197, akademisyen: 272 }, + "zonguldak-bulent-ecevit-universitesi": { ogrenci: 29666, ogretimUyesi: 713, akademisyen: 1354 }, +}; + +/** Üniversite sayfa slug'ının YÖK İstatistik satırı; yoksa null. */ +export function uniIstatistikBul(slug: string): UniIstatistik | null { + return YOK_ISTATISTIK[slug] ?? null; +} + +/** Öğretim üyesi başına örgün öğrenci, tam sayıya yuvarlanmış. */ +export function ogrenciBasinaUye(i: UniIstatistik): number { + return Math.round(i.ogrenci / i.ogretimUyesi); +} -- 2.49.1 From 6a9af19ef2ec7f39bcb045c5cd9fb9aa15128023 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Thu, 24 Sep 2026 00:01:41 +0300 Subject: [PATCH 3/3] =?UTF-8?q?feat(tasarim):=20=C3=BCniversite=20sayfas?= =?UTF-8?q?=C4=B1=20"=C5=9Fi=C5=9Fko=20ve=20sade"=20dile=20g=C3=B6re=20ba?= =?UTF-8?q?=C5=9Ftan,=20DESIGN.md=20know-how'=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Üniversite sayfası (canlı referans ekran /universite/ankara-universitesi): - Program listesi: ad kesilmez/sarar, telefonda taban sıra alt satıra iner, satır butonları 44 px, tür çipi okunur boyda - Başlık bloğu lg altında ortalı (logo başlığın üstünde), kimlik hapları (Devlet/Ankara) navbar ölçüsünde, ikon + metin - Harita yanında 4 özet kartı (OzetKart): sayı solda iri, ikon sağda; 4. kart URAP Türkiye sırası + piksel sıra şeridi (PixelSira) - Akademik göstergeler (URAP + YÖK İstatistik) sayfadan "Akademik göstergeler ›" satırının açtığı alt sayfaya taşındı: kartsız, ortalı, masonry gösterge kareleri, tek ekrana sığar - Hover tek turuncu (orange-500); liste satırı adı mavi kalır - Üst çubuk başlık geçişi (NavBaslik): h1 kaydırılınca üst çubukta logo + ad belirir; bölüm sayfasında da (yalnız ad) Tasarım dili: - DESIGN.md: temel his (§1.1), hover kuralı (§2.1), yazı alt sınırları, yeni tek kaynak parçalar (§4.8–4.15), lg altı ortala (§5.2), reddedilen denemeler (§7), ölç-tahmin-etme ve tuzaklar (§9) - Denetim: gösterge karesi renk istisnası (bg-indigo-500, bg-green-600) ve site taraması için dört uyarı (minik-yazi, buyuk-harf-etiket, mavi-hover, iri-sayi-tabular) Not: universite-icerik.tsx'teki akademik göstergeler bölümü (URAP/YÖK kartları) Bilal'in commit'lenmemiş işiydi; tasarım değişiklikleriyle aynı dosyada iç içe olduğu için bu commit'e girdi. Co-Authored-By: Claude Opus 5.5 --- .agents/skills/kolaytercih-tasarim/SKILL.md | 2 +- docs/tasarim/DESIGN.md | 375 +++++++++++-- scripts/tasarim-denetim.ts | 30 +- src/components/pixel-decor.tsx | 59 ++ src/components/site-header.tsx | 43 +- .../katalog/components/akademik-ayrinti.tsx | 72 +++ .../katalog/components/bolum-icerik.tsx | 7 +- .../katalog/components/nav-baslik.tsx | 116 ++++ .../katalog/components/universite-icerik.tsx | 522 ++++++++++++++---- .../components/universite-konum-haritasi.tsx | 2 +- .../components/universite-program-tablosu.tsx | 66 ++- 11 files changed, 1092 insertions(+), 202 deletions(-) create mode 100644 src/features/katalog/components/akademik-ayrinti.tsx create mode 100644 src/features/katalog/components/nav-baslik.tsx diff --git a/.agents/skills/kolaytercih-tasarim/SKILL.md b/.agents/skills/kolaytercih-tasarim/SKILL.md index ce8a87b..58ec3af 100644 --- a/.agents/skills/kolaytercih-tasarim/SKILL.md +++ b/.agents/skills/kolaytercih-tasarim/SKILL.md @@ -10,7 +10,7 @@ yön zaten belli. Kaynak tek: `docs/tasarim/DESIGN.md`. Bu dosya onu nasıl uygu ## 1. Başlamadan önce (zorunlu) -1. `docs/tasarim/DESIGN.md` dosyasını **tamamını** oku (181 satır). Özellikle §4 tek kaynaklar ve §7 yap/yapma. +1. `docs/tasarim/DESIGN.md` dosyasını **tamamını** oku (477 satır). Özellikle §1.1 temel his, §4 tek kaynaklar, §7 yap/yapma ve §9.2 ölç-tahmin-etme. Canlı referans ekran: `/universite/ankara-universitesi`. 2. Dört satırlık brief'i doldur (DESIGN.md §9): amaç · referans ekran · tek kaynak parçalar · durumlar. 3. Referans ekranı bul ve aç; kopyalayacağın sınıf dizilerini oradan al: diff --git a/docs/tasarim/DESIGN.md b/docs/tasarim/DESIGN.md index 60aa7bc..25187ed 100644 --- a/docs/tasarim/DESIGN.md +++ b/docs/tasarim/DESIGN.md @@ -3,23 +3,57 @@ Bu dosya sitenin görsel dilinin **tek yazılı kaynağıdır**. Yeni ekran, bileşen, modal, kart ya da buton çizmeden önce okunur; mevcut arayüz değiştirilirken buna bakılır. Değerler koddan çıkarılmıştır (`src/app/globals.css`, `src/app/layout.tsx`, `src/components/pixel-decor.tsx`, -`src/components/ui/*`, `src/app/page.tsx`); kodla çelişirse kod değil, bu dosya düzeltilir ve -çelişki raporlanır. Kural ihlallerini `pnpm tasarim:denetim` yakalar. +`src/components/ui/*`, `src/app/page.tsx`, `src/features/katalog/components/universite-icerik.tsx`); +kodla çelişirse kod değil, bu dosya düzeltilir ve çelişki raporlanır. Kural ihlallerini +`pnpm tasarim:denetim` yakalar. Biçim: Google Stitch'in DESIGN.md düzeni (9 bölüm). Sabit değerler Tailwind sınıfı olarak verilir; kodda da sınıf kullanılır, hex/oklch elle yazılmaz. +**Canlı referans ekran:** `/universite/ankara-universitesi` (23 Eyl 2026'da Bilal'le birlikte +bu belgedeki "şişko ve sade" dile göre baştan tasarlandı). Bir kural belirsiz kalırsa o sayfanın +kodu (`universite-icerik.tsx`, `universite-program-tablosu.tsx`, `akademik-ayrinti.tsx`, +`nav-baslik.tsx`) ölçüdür. Site genelinde dönüşüm bu ekranı örnek alır. + --- ## 1. Görsel tema ve atmosfer -**Bir cümle:** Beyaz, bol nefesli, mavi bilgi + turuncu aksiyon; her yüzeyde küçük bir -**piksel imzası** (hero'daki dot-matrix Türkiye haritasının dili) dolaşır. +**Bir cümle:** Beyaz, bol nefesli, **şişko ve sade** — kullanıcıya "basit bir şey +kullanıyorum" hissi veren, Apple uygulamaları kadar kolay anlaşılır bir arayüz; mavi bilgi + +turuncu aksiyon; her yüzeyde küçük bir **piksel imzası** (hero'daki dot-matrix Türkiye +haritasının ve logodaki üç karenin dili) dolaşır. + +### 1.1 Temel his: şişko ve sade + +Referans: header (`src/components/site-header.tsx` ve içindeki arama/kullanıcı hapları), hero +ve üniversite sayfası. Bu hissi veren şeyler, ekrandan bağımsız: + +- **İri dokunma alanı.** Butonlar ve haplar 44–48 px (`h-11`/`h-12`), tam yuvarlak. +- **İri yazı, az eleman.** Bir ekranda az şey var ve her biri büyük. Küçük gri yazı yığını yok. +- **Sayı kahramandır.** Bir veri gösteriliyorsa sayı kartın en büyük öğesidir; etiket ve + açıklama onun altında ikinci planda. +- **Sık bakılan önde, ayrıntı bir dokunuş derinde** (Apple'ın "önce ortak yol, gelişmiş + seçenekler bir seviye aşağıda" ilkesi). Sayfada en çok dört özet sayı; fazlası ayrıntı + satırının açtığı alt sayfaya (§4.10). +- **Nefes alanı korunur.** Harita gibi görsel dinlenme alanları küçültülmez, sıkıştırılmaz, + bilgiyle doldurulmaz. Sadeleştirmek = bu alanları silmek değil, etraflarındaki kalabalığı + azaltmaktır. +- **Verimli ama havadar.** Alt sayfa ve paneller dikine uzamaz; ilişkili öğeler yan yana + dizilir, aralarında boşluk kalır (§5.3). +- **Doğal duruş.** Eşit, hizalı ızgaralar yerine logodaki üç kare gibi hafif kaymış yerleşim + (§4.11). Her şey birbirine cetvelle hizalıysa ekran "tablo" gibi durur. +- **Basınca tepki.** Her tıklanabilir öğe basınca hafifçe küçülür (`active:scale-[0.97]`, + geniş satırlarda `[0.99]`), üzerine gelince turuncuya döner (§2.1). + +### 1.2 Genel kurallar - Zemin her zaman beyaz; koyu zemin yalnızca CTA bandı ve çekmece başlığı gibi tek bir vurgu yüzeyinde (`PixelField` ile). - İmza dekoratiftir ama rastgele değildir: deterministik ızgara (hücre 10, nokta 7.4, rx 2), imleç yaklaşınca turuncuya ısınan pikseller (`usePixelHeat`), uzaklaşınca yavaş soğuma. + Piksel **veri taşıyorsa** (ör. `PixelSira`) kalır; iki bölüm arasına **süs olarak** konmuş + piksel ayraç alt sayfalarda ve kompakt panellerde kullanılmaz. - Ton: öğrenciye "sen" diyen, sakin, vaatsiz. Süs yok, gösteriş yok; hareket yalnızca bir şey söylüyorsa var. - **Karanlık mod yoktur.** `dark:` sınıfı yazılmaz (`.dark` token'ları shadcn kalıntısıdır). @@ -28,31 +62,66 @@ verilir; kodda da sınıf kullanılır, hex/oklch elle yazılmaz. | Rol | Sınıf | Nerede | |---|---|---| -| Bilgi/vurgu mavisi (`primary`) | `text-primary`, `bg-primary/10`, `border-primary/30` | Rozet metni, ikon kutusu, hero'daki italik vurgu, linkler, kart hover kenarlığı | +| Bilgi/vurgu mavisi (`primary`) | `text-primary`, `bg-primary/10`, `border-primary/30` | Rozet metni, ikon kutusu, hero'daki italik vurgu, linklerin durağan rengi | | **Aksiyon turuncusu** | `bg-orange-500 hover:bg-orange-600 text-white` | Funnel CTA butonları. Bir ekranda **tek** turuncu buton | +| **Hover turuncusu** | `hover:text-orange-500`, `hover:border-orange-500`, `hover:bg-orange-50/60`, `group-hover:text-orange-500` | Link, kart, satır ve ikincil butonların üzerine gelme hâli (§2.1); liste satırı adı hariç (mavi kalır) | | Piksel vurgusu | `fill-orange-500` (opaklık 0.55–0.85) | Yalnızca piksel süslerinin "sıcak" noktaları | -| Başlık metni | `text-slate-900` | h1–h3, kart başlıkları | +| Başlık metni | `text-slate-900` | h1–h3, kart başlıkları, iri sayılar | | Gövde metni | `text-slate-600` | Paragraf, açıklama | -| İkincil metin | `text-slate-500` | Meta bilgi, yardımcı satır | -| Silik metin | `text-slate-400` | İpucu, devre dışı, placeholder | +| İkincil metin | `text-slate-500` | Meta bilgi, yardımcı satır, kart açıklaması | +| Silik metin | `text-slate-400` | İpucu, devre dışı, placeholder, sayının yanındaki birim (`/ 198`) | | Kenarlık | `border-slate-200` (landing kartlarında `border-slate-200/70`) | Kart, rozet, çarpı, giriş alanı | | İç zemin | `bg-slate-50` | Kartın içindeki ikincil kutu, tablo başlığı | -| Hover zemini | `hover:bg-slate-100` | Çarpı, ghost buton | +| Nötr hover zemini | `hover:bg-slate-100` | Yalnız kapatma çarpısı ve ghost buton | | Risk: güvenli | `emerald-500` / `text-emerald-600` | **Her zaman "Güvenli" metniyle** | | Risk: az riskli | `amber-500` / `text-amber-600` | **Her zaman "Az riskli" metniyle** | | Risk: riskli | `red-500` / `text-red-600` | **Her zaman "Riskli" metniyle** | | Uyarı kutusu | `border-amber-200 bg-amber-50 text-amber-900` | Bilgilendirme notu | +| Gösterge karesi | Dolu zemin + beyaz yazı: `bg-orange-500`, `bg-primary`, `bg-indigo-500`, `bg-green-600`; `rounded-3xl` | Yalnız sayı gösterge kareleri (§4.11). Renk sempati katar, anlam taşımaz: etiket her karede yazılı | | Hata | `text-destructive`, `text-red-600` | Form hatası | -Kurallar: +### 2.1 Hover: tek turuncu + +Bilal'in kararı (23 Eyl 2026): üzerine gelme hâlinde **sitenin turuncusu** kullanılır ve bu +turuncu **tek tondur: `orange-500`** — CTA butonunun turuncusuyla birebir aynı değer. + +- Link / metin: `transition-colors hover:text-orange-500` (altı çizili linklerde + `hover:underline` kalır). +- Tıklanabilir kart / satır: `hover:border-orange-500 hover:bg-orange-50/60`; içindeki iri + sayı ya da başlık `group-hover:text-orange-500`; sağdaki ok + `group-hover:translate-x-0.5 group-hover:text-orange-500`. +- İkincil (outline) buton: `hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500`. +- **İstisna — liste satırındaki adlar mavi kalır:** program/üniversite listelerinde satırın + adı (bölüm ya da üniversite sayfasına giden link) `hover:text-primary hover:underline`. + Bilal'in kararı (23 Eyl 2026): uzun bir listede imleç satırdan satıra geçerken yanıp sönen + turuncu adlar yorucu; mavi sakin kalır. Satırın butonları ve listedeki diğer linkler §2.1'e + uyar. +- **Yapılmaz:** hover'da `text-primary`/`border-primary` (eski mavi hover — yukarıdaki + istisna dışında), `orange-600` yazı, `orange-300` kenarlık — aynı ekranda iki turuncu tonu + göze "farklı turuncu" diye batar. + Tek istisna: turuncu CTA'nın kendi zemininin hover'ı (`hover:bg-orange-600`), kanonik. +- Geçiş notu: kodda hâlâ `hover:text-primary` / `hover:border-primary/30` kullanan yerler var + (landing kartı dahil); tarama sırasında bu kurala çevrilir. Denetim bunları `mavi-hover` + uyarısıyla listeler; liste satırı adındaki uyarı istisnadır, dokunulmaz. + +### 2.2 Palet kuralları + - Nötr yalnızca **slate**. `gray`, `zinc`, `neutral`, `stone` yazılmaz. - `indigo`, `purple`, `violet`, `fuchsia`, `pink`, `cyan`, `teal`, `lime`, `green`, `yellow` - yeni kullanım açmaz. (`bg-violet-50 text-violet-600` tek bir mevcut durum çipidir; yeni yere taşıma.) + yeni kullanım açmaz. Tek istisna "Gösterge karesi" satırındaki dolu `bg-indigo-500` ve + `bg-green-600` zeminleri (Bilal'in kararı, 23 Eyl 2026); yazı, kenarlık ya da başka + ton/opaklıkta bu renkler yine yasak. (`bg-violet-50 text-violet-600` tek bir mevcut durum + çipidir; yeni yere taşıma — üniversite sayfasında "Vakıf" çipi bu yüzden hap + ikonla + değiştirildi, §4.12.) - Gradyan metin (`bg-clip-text text-transparent`) ve parlama gölgesi (`shadow-[0_0_…]`) yok. - Hex/oklch değeri sınıf içine yazılmaz (`text-[#…]`); token ya da Tailwind tonu kullanılır. Risk renklerinin SVG karşılığı `src/lib/risk.ts` içindeki `RISK_PIN_RENK`'ten okunur. - Renk tek başına anlam taşımaz: risk rengi her zaman metin etiketiyle birlikte gider - (`RISK_ETIKET`, `DILIM_ETIKET` — üçüncü dilimin görünen adı "Güvenli"). + (`RISK_ETIKET`, `DILIM_ETIKET` — üçüncü dilimin görünen adı "Güvenli"). Tür ayrımı (devlet / + vakıf) da renkle değil ikon + metinle yapılır. +- Renkli dolu zeminde beyaz yazı: turuncu ve yeşil zeminde küçük beyaz yazı WCAG AA'nın + altında kalır (sitenin turuncu CTA'sı da aynı ikiliyi kullanır). Bu zeminlerde etiket + `font-bold`, sayı en az `text-3xl`; uzun metin konmaz. ## 3. Tipografi @@ -60,27 +129,51 @@ Dört yazı tipi, her birinin tek bir işi var: | Sınıf | Yazı tipi | İş | |---|---|---| -| `font-heading` | Cal Sans | Tüm başlıklar (h1–h3, kart başlığı). Her zaman `font-bold` ile | +| `font-heading` | Cal Sans | Tüm başlıklar (h1–h3, kart başlığı) ve **iri sayılar**. Her zaman `font-bold` ile | | `font-sans` (varsayılan) | Clarity City 400–800 | Gövde, buton, form, tablo | -| `font-bricolage` | Bricolage Grotesque (opsz ekseni) | Vurgu: hero'da italik mavi span, küçük etiketler/çipler, sayısal rozetler | +| `font-bricolage` | Bricolage Grotesque (opsz ekseni) | Vurgu: hero'da italik mavi span, logo yazısı, üst çubuktaki sayfa adı (§4.14), küçük etiketler/çipler | | `font-mono` | Geist Mono | Yalnızca kod/markdown blokları. Arayüzde kullanılmaz | Ölçek (kopyala, uydurma): - Hero h1: `font-heading text-5xl font-bold tracking-[0.015em] sm:text-6xl`; vurgu kelimesi `` -- Bölüm h2: `font-heading text-3xl font-bold sm:text-4xl` (istenirse `text-slate-900`) -- Panel/kart h3: `font-heading text-xl font-bold` · liste satırı başlığı: `font-heading text-lg font-bold text-slate-900` +- Alt sayfa h1 (katalog, rehber): `font-heading text-3xl font-bold leading-tight text-slate-900 sm:text-5xl` +- Bölüm h2: `font-heading text-3xl font-bold sm:text-4xl` (istenirse `text-slate-900`); + alt sayfada liste bölümü başlığı `font-heading text-2xl font-bold text-slate-900 sm:text-3xl` +- Panel/kart h3: `font-heading text-xl font-bold` · grup başlığı (fakülte vb.): + `font-heading text-xl font-bold text-slate-900 sm:text-2xl` - Küçük başlık: `font-heading text-base font-bold text-slate-900` - Bölüm giriş paragrafı: `max-w-xl text-lg leading-relaxed text-slate-600` -- Gövde: `text-sm` (tablo, form, kart içi) ya da `text-base leading-relaxed` (açıklama) +- Sayfa özet paragrafı: `text-base leading-relaxed text-slate-600 sm:text-lg` +- Gövde: `text-base leading-relaxed` (açıklama, liste satırı adı) ya da `text-sm` (kart + açıklaması, tablo hücresi, meta) - Etiket/rozet metni: `text-xs font-semibold` -- Yasak: Inter, Roboto, Arial, sistem fontu, serif, `font-[…]` keyfi font; tek kelimelik - büyük harf etiketler (uppercase yok, rozet metni büyük-küçük harfli yazılır). + +**İri sayı ölçeği** (§1.1 "sayı kahramandır"): + +| Yer | Sınıf | +|---|---| +| Özet kartı sayısı | `font-heading text-4xl font-bold leading-none tracking-tight text-slate-900 sm:text-5xl` | +| Gösterge karesi, büyük | `font-heading text-5xl font-bold leading-none tracking-tight` | +| Gösterge karesi, orta | `font-heading text-3xl font-bold leading-none` | +| Sayının birimi (`/ 198`, `kişi`) | `text-base font-semibold text-slate-400` (renkli zeminde `text-white/80`) | + +**Alt sınırlar:** +- Gövde metni telefonda 16 px'in (`text-base`) altına inmez; `text-sm` yalnız ikincil + bilgide, `text-xs` yalnız ince yazıda (kaynak satırı, eksen etiketi, çip). +- **12 px altı yok:** `text-[10px]`, `text-[11px]` yazılmaz (denetim uyarır). +- **Büyük harfli minik etiket yok:** `uppercase tracking-wide text-xs` kalıbı ("PROGRAM", + "TOPLAM KONTENJAN") yazılmaz; etiket büyük-küçük harfli, `text-sm font-semibold`. +- **İri sayıda `tabular-nums` yok:** Cal Sans'ta sabit genişlikli rakam "daktilo" gibi + aralıklı durur ("13.88 9"). `tabular-nums` yalnız `font-sans` tablo sütunlarında. +- Yasak: Inter, Roboto, Arial, sistem fontu, serif, `font-[…]` keyfi font. ## 4. Bileşen stilleri (tek kaynaklar) Bir eleman iki yerde görünüyorsa stili tek kaynaktan gelir. Aşağıdakiler yeniden tasarlanmaz. +§4.8–§4.15 üniversite sayfasında doğdu; ikinci bir ekranda gerektiğinde **kopyalanmaz**, ortak +bir dosyaya (`src/components/…`) çıkarılıp iki yer de oradan kullanır. ### 4.1 Buton — `src/components/ui/button.tsx` - Her buton `rounded-full`; `h-9` varsayılan, `h-11`/`h-12` funnel CTA'ları (44 px dokunma hedefi), `size-9` ikon butonu. @@ -93,24 +186,32 @@ Bir eleman iki yerde görünüyorsa stili tek kaynaktan gelir. Aşağıdakiler y ``` Metin önce, ok sonda. Başta ikon yok. `Sparkles` yok. İstenirse `active:scale-[0.97]`. -- İkincil: `variant="outline"`; sessiz: `variant="ghost"`; link görünümü: `variant="link"`. +- **İkincil gezinme butonu** ("Tüm sıralamayı gör →"): + ```tsx + + ``` +- Sessiz: `variant="ghost"`; link görünümü: `variant="link"`. ### 4.2 Rozet (bölüm başlığı üstündeki kapsül) — `SectionEyebrow`, `src/components/pixel-decor.tsx` ```tsx Nasıl çalışır? ``` Sınıfı: `inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white px-3.5 py-1.5 text-xs font-semibold text-primary` + başta `PixelMark`. -`@/components/ui/badge` **kullanılmaz**; elle pill span yazılmaz. Filtre çipleri ve nav butonları bu kuralın dışındadır (onlar etkileşimlidir). +`@/components/ui/badge` **kullanılmaz**; elle pill span yazılmaz. Filtre çipleri, nav butonları +ve kimlik hapları (§4.12) bu kuralın dışındadır. ### 4.3 Modal kapatma çarpısı — `DialogContent` varsayılanı, `src/components/ui/dialog.tsx` `absolute top-3 right-3 z-10 flex size-9 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-popover text-slate-500 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96]` + `` + `sr-only "Kapat"`. Özel yerleşim gerekiyorsa `showCloseButton={false}` ver ve **aynı sınıf setini** kopyala. +Alt sayfada (`Drawer`) aynı sınıf seti `DrawerClose` üstünde kullanılır (§4.10). ### 4.4 Kart ve kutu -- Landing/öne çıkan kart: `` + sağ üstte ``. +- Landing/öne çıkan kart: `` + sağ üstte ``. (Kodda hâlâ `hover:border-primary/30 hover:shadow-primary/5`; §2.1 geçiş notu.) - Sade kutu (form, panel, liste satırı): `rounded-2xl border border-slate-200 bg-white` (+ `p-5`/`p-6`). Küçük iç kutu: `rounded-xl border border-slate-200 bg-slate-50`. Not kutusu: `rounded-lg`. -- İkon kutusu: `flex size-11 items-center justify-center rounded-xl bg-primary/10` içinde `size-5 text-primary` ikon. -- Gölge: `shadow-sm` varsayılan; hover'da `shadow-lg shadow-primary/5`. Başka gölge yok. +- İkon kutusu: `flex size-11 items-center justify-center rounded-xl bg-primary/10` içinde `size-5 text-primary` ikon. Kart içinde sayının yanında küçük hâli: `size-8 … sm:size-10` + `size-4 sm:size-5` ikon (§4.8). +- Gölge: `shadow-sm` varsayılan; hover'da `shadow-lg shadow-orange-500/5`. Başka gölge yok. ### 4.5 Risk çipi / etiketi Nokta + metin: `` yanında `text-xs font-semibold text-emerald-600`; metin `RISK_ETIKET`'ten. Renk tek başına asla. @@ -121,8 +222,131 @@ shadcn `Input`, `Select`, `Textarea`, `Slider`. Etiket her alanda; hata mesajı ### 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. +### 4.8 Özet kartı — `OzetKart`, `universite-icerik.tsx` +Apple Fitness özet kartının sitenin ölçüleriyle hâli. Sayfada **en çok dört** tane (harita +yanındaki 2×2). + +``` +┌──────────────────────┐ +│ 13.889 [👥] │ ← sayı solda, iri; ikon kutusu sağda, küçük +│ Kontenjan │ ← başlık text-base font-semibold text-slate-900 +│ 2026 toplamı │ ← açıklama text-sm text-slate-500, tek satır +└──────────────────────┘ +``` + +- Kap: `flex flex-col rounded-2xl border border-slate-200 bg-white p-4 sm:p-5`. +- Sayı satırı: `flex items-start justify-between gap-1.5`; sayı §3 "Özet kartı sayısı"; + 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. +- 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. +- 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 + kalır; hover §2.1. +- Açıklama altında veri görseli gerekiyorsa (ör. `PixelSira`) `ek` yuvasına konur. + +### 4.9 Ayrıntı satırı — `AkademikAyrinti`, `akademik-ayrinti.tsx` +Özet kartlarının altında "fazlası burada" satırı (Apple Sağlık'taki "Tüm verileri göster"). + +- `mt-4 flex w-full items-center gap-4 rounded-2xl border border-slate-200 bg-white p-4 sm:p-5` + + hover §2.1 + `active:scale-[0.99]`. +- Solda ikon kutusu (`size-11`), ortada iki satır: başlık `font-heading text-lg font-bold`, + altında **önizleme** `truncate text-sm text-slate-500` — içeride ne olduğunu söyler ("Hoca + başına 25 öğrenci"), boş "Daha fazla" yazmaz. Sağda `ChevronRight size-5 text-slate-400`. +- Dokununca alt sayfa açılır (§4.10). + +### 4.10 Alt sayfa (Drawer) içeriği +Ayrıntı her zaman **alttan açılan sayfada** (`Drawer handleOnly`, tutamaçlı); ekranın ortasında +açılan modal değil. Çekmece kabı ve çarpı `cta-sira-form.tsx`'teki çekmeceyle birebir: +`DrawerContent className="max-h-[92dvh] bg-white sm:mx-auto sm:max-w-2xl sm:border-x sm:border-slate-200 sm:shadow-2xl"`. + +İçerik kuralları (Apple ayrıntı sayfası): +- **Her ekranda ortalı:** başlık `px-10 text-center font-heading text-2xl font-bold`, altında + bağlam (`text-sm text-slate-500`, ör. üniversite adı). +- **Kart yok.** Çekmece zaten bir yüzey; içine çerçeveli kart koymak "pencere içinde pencere" + olur. Gösterge kareleri (§4.11) bu kuralın istisnası değildir — onlar çerçevesiz, dolu renk. +- **Süs ayraç yok.** Bölümler arasına piksel ayraç ya da çizgi konmaz; ayrım boşlukla ve + yan yana dizilişle yapılır. Yalnız en alttaki kaynak satırı `border-t border-slate-100`. +- **Tek ekrana sığar:** 375×812'de kaydırmasız. Dikine uzuyorsa öğeler yan yana dizilir, + kareler kısaltılır, not kısaltılır — içerik silinmez (§9.2 ölçüm). +- Sıra: gösterge kareleri → tek cümlelik not (`text-sm text-slate-500`) → ikincil buton (§4.1) + → kaynaklar (logo solda, `text-xs` iki satır). +- **Arama motoru uyarısı:** çekmece içeriği sayfa açılırken HTML'de yoktur (kapalıyken + çizilmez). Google'ın görmesi gereken metin ve **zorunlu atıflar** (ör. URAP sırası + payda + + dönem + aktif bağlantı) çekmeceye taşınmaz, sayfada da görünür kalır. + +### 4.11 Gösterge kareleri (masonry) +Birden çok sayıyı alt sayfada göstermenin yolu. Logodaki üç kare gibi: **iki sütun, sağdaki +aşağı kaymış, kare boyları farklı — ne tepeler ne bitişler hizalı.** + +``` +┌────────┐ +│ Türkiye│ ┌────────┐ +│ sırası │ │ Hoca │ +│ 5. │ │ başına │ +│ İlk %3 │ │ 25 │ +└────────┘ └────────┘ +┌────────┐ ┌────────┐ +│ 56.128 │ │ 2.225 │ +└────────┘ │ │ + └────────┘ +``` + +- Kap: `flex w-full max-w-md items-start gap-3`; iki sütun `flex flex-1 flex-col gap-3`, + sağdaki `mt-12`. +- Kare: `flex flex-col items-center justify-center rounded-3xl px-3 text-white` + zemin (§2 + "Gösterge karesi") + `min-h-*` ile farklı boy (ör. 40 / 24 / 32 / doğal). +- **Ölçüyle doğrula:** komşu kareler arasında tepe ve bitiş farkı **en az 24 px**. 8 px fark + "yanlışlıkla hizalanmış" görünür (§9.2). +- Etiket `text-sm font-bold text-white/90`; sayı §3 gösterge ölçeği; vurgu satırı + `text-base font-bold`. +- Renk sırası: en önemli gösterge turuncu, ikincisi mavi, sonra indigo, yeşil. + +### 4.12 Kimlik hapları (tür, il) +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. Etkileşimli değildir; rozet (§4.2) değildir. + +### 4.13 Piksel sıra şeridi — `PixelSira`, `src/components/pixel-decor.tsx` +Bir sıranın (ör. 198 içinde 5.) yerini tek satır piksel şeridinde gösterir; sıranın düştüğü +dilim turuncu yanar, geri kalanı hero dokusunda. **Veri taşıyan** piksel olduğu için süs +ayraç yasağına girmez. +- Açık zeminde: `` (8 hücre varsayılan). +- Renkli dolu zeminde: `zemin="koyu"` — işaret beyaz yanar, ısı fırçası kapanır. +- Anlamı her zaman yanındaki metin taşır ("5. / 198", "İlk %3"); şerit `aria-hidden`. + +### 4.14 Üst çubuk başlık geçişi — `NavBaslik`, `nav-baslik.tsx` +iOS'taki büyük başlık davranışı: sayfa h1'i üst çubuğun altına girince üst çubuktaki +"KolayTercih" yazısı yukarı kayıp söner, yerine sayfanın logosu + adı gelir; dokununca sayfa +başına döner. Katalog detay sayfalarında (üniversite, bölüm) zorunlu; yeni detay sayfası +eklenince aynısı yapılır: + +```tsx +} /> +

…

+``` +Logosu olmayan sayfada `logo` verilmez; üst çubukta **yalnız ad** görünür (bölüm sayfası — +Bilal'in kararı: yerine ikon konmaz, üst çubukta süs ikona gerek yok). Logo sunucuda üretilip +verilir (logo listesi istemciye inmez). +Hareket: yay `bounce: 0, duration: 0.35`, giriş ve çıkış aynı yoldan; "hareketi azalt"ta yalnız +opaklık. + +### 4.15 Liste satırı (program/üniversite listeleri) — `universite-program-tablosu.tsx` +- **Ad kesilmez, sarar:** `whitespace-normal` + `block text-base font-semibold leading-snug`. + `truncate` ile "İngiliz Dili ve E…" yazılmaz. +- Ad linkinin hover'ı **mavi**: `transition-colors hover:text-primary hover:underline` + (§2.1 istisnası). +- Telefonda yalnız ad + satır butonları sütun olarak kalır; sayısal sütunlar (taban sıra vb.) + 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`. +- 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. +- 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.1 Genel - Sayfa kabı: `mx-auto max-w-6xl px-4`. Metin sütunu: `max-w-xl` (paragraf) / `max-w-2xl`–`max-w-3xl` (uzun içerik). - Landing bölümü: `
`; alt sayfalar `py-12`–`py-16`. - Bölüm başlık bloğu (kanonik): @@ -137,45 +361,118 @@ Yalnızca `lucide-react`. Boyut: `size-4` buton/satır içi (varsayılan), `size - Alt sayfa başlığının altında `PagePixelDivider` (`mt-6`/`mt-8`), her sayfada **farklı seed**. - Landing bölümleri arasında çizgi yok; `PixelDivider` (`mx-auto w-56`, farklı seed). - Boşluk ölçeği: 4 → 6 → 8 → 12 → 16/20 → 24. Ara değer (`mt-7`, `gap-5`) yeni yerde açılmaz. -- Mobil önce (trafiğin ~%90'ı 375 px): uzun üniversite/program adları `min-w-0` + sarma ya da `truncate`; tablo yatay kaydırmaz, kart olur; sabit alt çubuk klavyeyle çakışmaz; `vh` yerine `dvh`. +- Mobil önce (trafiğin ~%90'ı 375 px): uzun adlar sarar (§4.15); tablo yatay kaydırmaz, + dar ekranda sütunlar satır altına iner; sabit alt çubuk klavyeyle çakışmaz; `vh` yerine `dvh`. -## 6. Derinlik ve katman +### 5.2 Geniş ekran dışında ortala +Alt sayfaların (katalog, detay) başlık bloğu **`lg` (1024 px) altında ortalı**, `lg`'de sola +yaslıdır — mobil uygulama gibi telefonda ve tablette her şey ortada, masaüstünde tablo ve +kartlarla aynı hizada: +- Yol satırı: `flex flex-wrap items-center justify-center gap-1.5 text-sm text-slate-500 lg:justify-start` +- Logo + h1: `flex flex-col items-center gap-4 text-center lg:flex-row lg:text-left` — logo + telefonda **başlığın üstünde, ortada**. +- Kimlik hapları: `flex flex-wrap items-center justify-center gap-2 lg:justify-start` +- Özet paragraf: `mx-auto … text-center lg:mx-0 lg:text-left` +- Sayfa ayracı: `mx-auto … lg:mx-0`; liste bölümü ve grup başlıkları `text-center lg:text-left`. +- **Ortalanmaz:** liste/tablo satırlarının içeriği (göz satırdan satıra kayarken ortalı liste + yorar), form alanları, uzun rehber metni. +- Alt sayfa (§4.10) içeriği her ekranda ortalıdır. + +### 5.3 Verimli ve havadar +- İ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 + şeridi" (harita dahil altı değer tek satırda, yana kayan) reddedildi — harita küçüldü, + sayılar ezildi, ekran kalabalıklaştı. + +## 6. Derinlik, katman ve hareket Derinlik gölgeyle değil **piksel katmanı ve hareket farkıyla** verilir: başlık bloğu gövdeden hafif hızlı akar (`Parallax strength={16}` başlık, `7` içerik, `26` ayraç). Kartlar hover'da 4 px yükselir. Modal arka planı `bg-black/10` + hafif blur. Yığın: içerik < sabit bar < çekmece/modal (`z-50`). +Hareket kuralları (Apple, `motion/react`): +- Varsayılan yay: `{ type: "spring", bounce: 0, duration: 0.35 }` — sekmesiz, sakin. Sekme + yalnızca kullanıcı bir şeyi fırlattıysa (sürükle-bırak). +- Giriş ve çıkış **aynı yoldan** (aşağıdan geldiyse aşağı gider). +- Basınca anında tepki: `active:scale-[0.97]` (buton), `[0.99]` (geniş satır), `[0.96]` (çarpı). +- Hover'da ok `translate-x-0.5` kayar; kart yükselmesi yalnız landing kartında. +- Kaydırmaya bağlı tetik `IntersectionObserver` ile (kaydırma dinleyicisi değil). +- "Hareketi azalt": kayma/yay yok, yalnız opaklık (`useReducedMotion`, + `motion-reduce:transition-opacity`). + ## 7. Yap / Yapma | Yap | Yapma (AI izi) | |---|---| | Rozet için `SectionEyebrow` | `Badge` import'u, elle `rounded-full border … px-3.5 py-1.5` span | | CTA: metin + sondaki `ArrowRight` | Başta `Sparkles`/yıldız ikonu, "✨" | -| Nötr = slate; vurgu = primary mavi; aksiyon = orange-500 | `indigo/purple` primary, mor→mavi gradyan, `gray/zinc` nötr | +| Nötr = slate; vurgu = primary mavi; aksiyon ve hover = orange-500 | `indigo/purple` primary, mor→mavi gradyan, `gray/zinc` nötr | +| Hover'da tek turuncu (`orange-500`); liste satırı adı mavi (§2.1 istisnası) | Liste dışında hover'da mavi; aynı ekranda `orange-600` yazı + `orange-300` kenarlık | | Kart: `rounded-2xl border-slate-200 bg-white shadow-sm` | Her şeyde `rounded-3xl`, camsı `backdrop-blur` kartlar, parlama gölgesi | -| Hareket: `Reveal` (bölüm girişi), piksel ısısı, `kt-unblur`; `animate-ping` yalnızca "şu anda canlı" noktasında | Her elemanda `fade-up`, `whileHover scale 1.05`, `animate-bounce` | -| Başlık `font-heading`, vurgu `font-bricolage` | Inter/Roboto/serif, tek kelimelik BÜYÜK HARF etiket | -| Risk rengi + metin etiketi | Renk tek başına, "yerleşirsin" gibi vaat cümlesi | +| Sayı kartın en iri öğesi, ikon sağda küçük | İkon üstte büyük, sayı küçük; BÜYÜK HARFLİ `text-xs` etiket | +| Sayfada ≤4 özet sayı, fazlası ayrıntı satırı → alt sayfa | 6+ kartı sayfaya dizmek ya da hepsini yatay şeride sıkıştırmak | +| Harita gibi nefes alanını olduğu gibi bırakmak | Nefes alanını küçültüp bilgi şeridine gömmek | +| Alt sayfada kartsız, ortalı, tek ekrana sığan içerik | Çekmece içinde çerçeveli kart; bölümler arasında süs piksel ayraç | +| Gösterge kareleri masonry (kaymış sütun, farklı boy) | Hizalı eşit ızgara; 8 px'lik "neredeyse hizalı" kaymalar | +| Liste satırında ad sarar, sayı alt satıra iner | `truncate` ile yarım ad; 32 px satır butonu | +| Hareket: `Reveal`, piksel ısısı, `kt-unblur`, sekmesiz yay; `animate-ping` yalnızca "şu anda canlı" noktasında | Her elemanda `fade-up`, `whileHover scale 1.05`, `animate-bounce` | +| Başlık `font-heading`, vurgu `font-bricolage` | Inter/Roboto/serif, tek kelimelik BÜYÜK HARF etiket, 10–11 px yazı | +| Risk rengi + metin etiketi; tür ayrımı ikonla | Renk tek başına, "yerleşirsin" gibi vaat cümlesi | | Üç kart yalnızca üç gerçek öğe varsa | Hero + üç kart iskeleti refleksi | | Beyaz zemin, tek koyu vurgu yüzeyi | İstenmemiş karanlık mod, `dark:` sınıfı | | Kapatma çarpısı `DialogContent` varsayılanı | Başka boyut/renkte çarpı | ## 8. Duyarlılık -- Kırılma noktaları: 375 (temel), 640 `sm:`, 768 `md:`, 1024 `lg:`. Her bileşen önce 375'te tasarlanır. +- Kırılma noktaları: 375 (temel), 640 `sm:`, 768 `md:`, 1024 `lg:`. Her bileşen önce 375'te + tasarlanır. Ortalı ↔ sola yaslı geçişi `lg`'dedir (§5.2). - Dokunma hedefi en az 44 px (`h-11`, `size-11`); satır içi küçük buton `h-9` yalnızca masaüstü tablo aksiyonlarında. -- Başlık ölçeği bir kademe düşer (`text-3xl` → `sm:text-4xl`), gövde 16 px altına inmez. -- Modal genişliği `max-w-[calc(100%-2rem)]`; çekmece (`Drawer`) mobilde alttan açılır. -- `prefers-reduced-motion`: her animasyonun sade karşılığı var (`motion-reduce:animate-none`, `.kt-unblur-sade`). +- Başlık ölçeği bir kademe düşer (`text-3xl` → `sm:text-5xl` alt sayfa h1), gövde 16 px altına inmez. +- Modal genişliği `max-w-[calc(100%-2rem)]`; ayrıntı her zaman çekmecede (`Drawer`), alttan açılır. +- Çekmece içeriği 375×812'de kaydırmasız sığar (§4.10). +- `prefers-reduced-motion`: her animasyonun sade karşılığı var (`motion-reduce:animate-none`, `.kt-unblur-sade`, `useReducedMotion`). -## 9. Ajan için brief şablonu +## 9. Ajan için brief şablonu ve iş akışı -Yeni bir UI işi istenince önce şu dört satır doldurulur, sonra kod yazılır: +### 9.1 Brief +Yeni bir UI işi istenince önce şu beş satır doldurulur, sonra kod yazılır: ``` Amaç: Kim, hangi ekranda, ne yapacak? (ör. veli, /paket, fiyatı karşılaştırıp satın alacak) -Referans: Sitedeki en yakın mevcut ekran/bileşen (dosya yolu). Yoksa "yok" yaz ve ana sayfa bölüm bloğunu al. -Tek kaynak: Kullanılacak kanonik parçalar (SectionEyebrow, Button+ArrowRight, Card+PixelCorner, PagePixelDivider…) +Referans: Sitedeki en yakın mevcut ekran/bileşen (dosya yolu). Yoksa üniversite sayfası. +Tek kaynak: Kullanılacak kanonik parçalar (SectionEyebrow, Button+ArrowRight, OzetKart, AkademikAyrinti, NavBaslik…) Durumlar: boş / yükleniyor / hata / başarılı / girişsiz / kredisiz — hangileri var? +His: Ekranın tek ana işi ne? Hangi 4 sayı önde, ne bir dokunuş derinde? ``` "Modern", "temiz", "şık" gibi yön belirtmeyen kelimeler kullanılmaz; yön = bu dosya. -İş bitince: `pnpm tasarim:denetim` sıfır hata + 375 px'te tek bakış (taşma, üst üste binme, dokunma hedefi). + +### 9.2 Ölç, tahmin etme +Bu dilde sorunların çoğu koddan görünmez (taşma, sığma, hizalanma). Tasarım işinde: +- **Sığma:** metin genişliğini tarayıcıda ölç (gizli `span` + `getBoundingClientRect`); + "sanırım sığar" ile yazma. Örnek: özet kartında "13.889" 36 px'te 95 px. +- **Hizasızlık:** masonry'de karelerin `top`/`bottom` değerlerini ölç; komşu fark ≥ 24 px. +- **Tek ekran:** çekmecede kaydırma kabının `scrollHeight` ≤ `clientHeight` (375×812). +- **Genişlikler:** 375 / 768 / 1280 px'te bak; ortalı ↔ sola yaslı geçişini `lg`'de doğrula. +- **Uç veri:** en uzun ad ("Adana Alparslan Türkeş Bilim ve Teknoloji Üniversitesi"), en + büyük sayı, verisi eksik kurum (URAP'ta olmayan, istatistiği olmayan). + +### 9.3 Tuzaklar (23 Eyl 2026'da yaşandı) +- **Prettier'ı toplu çalıştırma.** Depo prettier düzeninde değil; bir dosyayı prettier'dan + geçirmek yüzlerce satırlık biçim farkı üretir ve başkasının kaydedilmemiş işine karışır. + Yalnız kendi yazdığın satırları elle düzenle. +- **Geliştirme sunucusunda ilk tık.** Kod değişikliğinden hemen sonra sunucu derleyip sayfayı + tazeleyebilir; o anda yapılan tıklama boşa gider. "Çekmece açılmıyor" demeden önce sayfa + hazır olduktan sonra tekrar dene. +- **Sayfada ikinci `
`.** Bölüm sayfasında içerik `
`'ı var; site üst çubuğunu + `querySelector("header")` ile değil, bilinen bir öğenin `closest("header")`'ı ile bul. +- **"use client" dosyasından sabit import etme.** Sunucu bileşeni o sabiti değer olarak + değil istemci referansı olarak alır; paylaşılan sabit ayrı dosyada durur ya da elle yazılır. + +### 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 +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). +Bir ekranı dönüştüren ajan o ekrandaki uyarıları sıfırlar ya da neden kaldığını yazar. + +### 9.5 Bitirmeden +`pnpm tasarim:denetim` sıfır hata (uyarılar raporda listelenir) + 375 px'te tek bakış (taşma, +üst üste binme, dokunma hedefi, çekmece tek ekran) + §9.2 ölçümleri. diff --git a/scripts/tasarim-denetim.ts b/scripts/tasarim-denetim.ts index a898b57..810ead0 100644 --- a/scripts/tasarim-denetim.ts +++ b/scripts/tasarim-denetim.ts @@ -115,8 +115,12 @@ const KURALLAR: Kural[] = [ ad: "yabanci-renk", seviye: "hata", mesaj: "Paletin dışında ton; primary mavi / orange-500 / slate / risk renkleri kullanılır (DESIGN.md §2)", + // İstisna (DESIGN.md §2 "Gösterge karesi"): yalnız dolu bg-indigo-500 ve + // bg-green-600 zeminleri serbest; başka ton/opaklık/özellik yine hata. bul: regexKurali( - new RegExp(`\\b${RENK_ONEK}-(?:indigo|purple|fuchsia|pink|cyan|teal|lime|green|yellow)-\\d+`), + new RegExp( + `\\b${RENK_ONEK}-(?:indigo|purple|fuchsia|pink|cyan|teal|lime|green|yellow)-\\d+(?!\\d)(?!(?<=\\bbg-(?:indigo-500|green-600))(?![\\w/-]))`, + ), ), }, { @@ -171,6 +175,30 @@ const KURALLAR: Kural[] = [ muaf: /src\/features\/rehber\/components\/rehber-kapak\.tsx$/, bul: regexKurali(/\[#[0-9a-fA-F]{3,8}\]/), }, + { + ad: "minik-yazi", + seviye: "uyari", + mesaj: "12 px altı yazı yok; en küçük text-xs, yalnız ince yazıda (DESIGN.md §3 alt sınırlar)", + bul: regexKurali(/\btext-\[(?:[0-9]|1[01])px\]/), + }, + { + ad: "buyuk-harf-etiket", + seviye: "uyari", + mesaj: "Büyük harfli minik etiket yok; büyük-küçük harfli text-sm font-semibold (DESIGN.md §3 alt sınırlar)", + bul: regexKurali(/\buppercase\b[^"'`\n]*\btracking-(?:wide|wider|widest)\b|\btracking-(?:wide|wider|widest)\b[^"'`\n]*\buppercase\b/), + }, + { + ad: "mavi-hover", + seviye: "uyari", + 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/), + }, + { + ad: "iri-sayi-tabular", + seviye: "uyari", + mesaj: "Cal Sans iri sayıda tabular-nums 'daktilo' gibi durur; yalnız font-sans tablo sütunlarında (DESIGN.md §3)", + bul: regexKurali(/\bfont-heading\b[^"'`\n]*\btabular-nums\b|\btabular-nums\b[^"'`\n]*\bfont-heading\b/), + }, { ad: "emoji-ikon", seviye: "uyari", diff --git a/src/components/pixel-decor.tsx b/src/components/pixel-decor.tsx index 61fb7e8..fd16b2a 100644 --- a/src/components/pixel-decor.tsx +++ b/src/components/pixel-decor.tsx @@ -298,6 +298,65 @@ export function PixelCorner({ className }: { className?: string }) { ); } +/** + * Bir sıranın yerini tek satır piksel şeridinde gösteren sessiz gösterge + * (ör. URAP'ta 198 üniversite içinde 5.). Şerit soldan (1.) sağa + * (sonuncu) dilimlere bölünür; sıranın düştüğü dilim hero'daki şehirler + * gibi turuncu yanar, geri kalanı hero dokusunda durur ve imleç fırçasına + * katılır. Dekoratiftir: anlamı yanındaki metin taşır (renk tek başına + * anlam taşımaz), bu yüzden aria-hidden. + */ +export function PixelSira({ + sira, + toplam, + hucre = 8, + zemin = "acik", + className, +}: { + sira: number; + toplam: number; + hucre?: number; + /** "koyu": renkli dolu zemin üstünde (ör. turuncu gösterge karesi) — + * işaret beyaz yanar, doku zeminin üstünde beyaz kalır. */ + zemin?: "acik" | "koyu"; + className?: string; +}) { + const svgRef = useRef(null); + // Isı fırçası turuncuya ısıtır; turuncu zeminde görünmez, orada kapalı. + usePixelHeat(svgRef, { radius: zemin === "koyu" ? 0 : 28 }); + const yer = Math.min( + hucre - 1, + Math.max(0, Math.floor(((sira - 1) / toplam) * hucre)), + ); + + return ( + + ); +} + /** Bölüm başlıklarının üstünde duran piksel imzalı eyebrow rozeti. */ export function SectionEyebrow({ children, diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx index 6c54015..be3a6e6 100644 --- a/src/components/site-header.tsx +++ b/src/components/site-header.tsx @@ -10,23 +10,34 @@ export function SiteHeader() {
{/* lg altında 2 satır: logo+UserNav / sıralama+ara. lg+: tek satır, ortada nav. */}
- - {/* Inline animasyonlu mark: Link'teki `group` sayesinde hover'da - kareler danışman panelindeki nabız animasyonuyla canlanır. */} - - - - Kolay + {/* Sayfa başlığı yuvası (NavBaslik): bir sayfa 'e + data-nav-baslik koyunca yazı logosu yukarı kayıp söner, yuvaya + sayfanın başlığı (ör. üniversite logosu + adı) gelir. Yuva Link'in + dışında: içine gelen düğme bağlantı içinde bağlantı olmasın. + lg'de ortadaki nav hapına taşmasın diye genişliği sınırlı. */} +
+ + {/* Inline animasyonlu mark: Link'teki `group` sayesinde hover'da + kareler danışman panelindeki nabız animasyonuyla canlanır. */} + + + + Kolay + + + Tercih + - - Tercih - - - + + {/* UserNav session+DB bekler; Suspense dışına taşarsa tüm kabuk bloklanır */} }> diff --git a/src/features/katalog/components/akademik-ayrinti.tsx b/src/features/katalog/components/akademik-ayrinti.tsx new file mode 100644 index 0000000..37d7636 --- /dev/null +++ b/src/features/katalog/components/akademik-ayrinti.tsx @@ -0,0 +1,72 @@ +"use client"; + +import type { ReactNode } from "react"; +import { ChevronRight, GraduationCap, X } from "lucide-react"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer"; + +/** + * Üniversite sayfasında özet kartlarının altındaki "Akademik göstergeler ›" + * satırı (Apple Sağlık'taki "Tüm verileri göster" gibi): sık bakılan sayılar + * kartlarda, ayrıntı bir dokunuş derinde. İkinci satır içeride ne olduğunu + * önceden söyler. Ayrıntı alttan açılan çekmecede; çekmece ve kapatma + * çarpısı cta-sira-form.tsx'teki çekmeceyle birebir aynı sınıf setinde. + * Kartlar sunucuda üretilip `children` olarak gelir. + */ +export function AkademikAyrinti({ + universite, + onizleme, + children, +}: { + universite: string; + onizleme: string; + children: ReactNode; +}) { + return ( + + + + + + + + Kapat + +
+ + Akademik göstergeler + + + {universite} + +
{children}
+
+
+
+ ); +} diff --git a/src/features/katalog/components/bolum-icerik.tsx b/src/features/katalog/components/bolum-icerik.tsx index a49608b..1459913 100644 --- a/src/features/katalog/components/bolum-icerik.tsx +++ b/src/features/katalog/components/bolum-icerik.tsx @@ -22,6 +22,7 @@ import { } from "./sayfalama"; import { BolumProgramListesi } from "./bolum-program-listesi"; import { VeriKaynagi } from "./veri-kaynagi"; +import { NavBaslik } from "./nav-baslik"; import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form"; import { SiteFooter } from "@/components/site-footer"; import { Skeleton } from "@/components/ui/skeleton"; @@ -224,8 +225,12 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) { )} + {/* Başlık üst çubuğun altına girince üst çubukta bölüm adı belirir */} +
-

+

{bolum.ad} {ilkSayfa ? "" : ` — Sayfa ${sayfa}`} {!ilkSayfa && aralik ? ( diff --git a/src/features/katalog/components/nav-baslik.tsx b/src/features/katalog/components/nav-baslik.tsx new file mode 100644 index 0000000..edd09d7 --- /dev/null +++ b/src/features/katalog/components/nav-baslik.tsx @@ -0,0 +1,116 @@ +"use client"; + +import { + type ReactNode, + useEffect, + useState, + useSyncExternalStore, +} from "react"; +import { createPortal } from "react-dom"; +import { AnimatePresence, motion, useReducedMotion } from "motion/react"; + +/** SiteHeader'daki yuva; logo yazısının hemen sağında. */ +export const NAV_BASLIK_YUVASI = "nav-baslik-yuvasi"; + +const subscribe = () => () => {}; + +/** + * iOS'taki büyük başlık davranışı: sayfa başlığı (h1) üst çubuğun altına + * girince üst çubukta "KolayTercih" yazısı yukarı kayıp söner (SiteHeader'da + * `in-data-[nav-baslik]` ile), yerine üniversitenin logosu ve adı gelir. + * Dokununca sayfanın başına döner. Tetik IntersectionObserver'la: kaydırma + * dinleyicisi yok, üst sınır üst çubuğun alt kenarı. + * + * Hareket Apple'ın varsayılanı: sekmesiz yay (bounce 0, 0,35 sn), giriş ve + * çıkış aynı yoldan. "Hareketi azalt" açıksa yalnız opaklık değişir. + * Logo sunucuda üretilip `logo` olarak gelir (logo listesi istemciye inmez); + * logosu olmayan sayfada verilmez, yalnız ad görünür. + */ +export function NavBaslik({ + baslikId, + ad, + logo, +}: { + baslikId: string; + ad: string; + /** Verilmezse yalnız ad görünür (ör. bölüm sayfası). */ + logo?: ReactNode; +}) { + const [goster, setGoster] = useState(false); + const azHareket = useReducedMotion(); + const yuva = useSyncExternalStore( + subscribe, + () => document.getElementById(NAV_BASLIK_YUVASI), + () => null, + ); + + useEffect(() => { + const baslik = document.getElementById(baslikId); + // Sayfada başka
olabilir (bölüm sayfası): yuvanın kendi + // üst çubuğu alınır. + const ustCubuk = document + .getElementById(NAV_BASLIK_YUVASI) + ?.closest("header"); + if (!baslik || !ustCubuk) return; + + let gozlemci: IntersectionObserver | null = null; + const kur = () => { + gozlemci?.disconnect(); + const alt = Math.round(ustCubuk.getBoundingClientRect().bottom); + gozlemci = new IntersectionObserver( + ([giris]) => { + // Görünmüyor ve üst çubuğun üstünde kaldıysa (aşağıda değil) + setGoster( + !giris.isIntersecting && giris.boundingClientRect.top < alt, + ); + }, + { rootMargin: `-${alt}px 0px 0px 0px` }, + ); + gozlemci.observe(baslik); + }; + kur(); + window.addEventListener("resize", kur); + return () => { + window.removeEventListener("resize", kur); + gozlemci?.disconnect(); + }; + }, [baslikId]); + + useEffect(() => { + const kok = document.documentElement; + if (goster) kok.dataset.navBaslik = ""; + else delete kok.dataset.navBaslik; + return () => { + delete kok.dataset.navBaslik; + }; + }, [goster]); + + if (!yuva) return null; + + const kayma = azHareket ? 0 : 10; + return createPortal( + + {goster ? ( + + window.scrollTo({ top: 0, behavior: azHareket ? "auto" : "smooth" }) + } + aria-label={`${ad} — sayfanın başına dön`} + initial={{ opacity: 0, y: kayma }} + animate={{ opacity: 1, y: 0 }} + exit={{ opacity: 0, y: kayma }} + transition={{ type: "spring", bounce: 0, duration: 0.35 }} + className="pointer-events-auto flex min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-full py-1 pr-2 pl-1 transition-colors duration-150 hover:text-orange-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring active:scale-[0.97]" + > + {logo ?? null} + + {ad} + + + ) : null} + , + yuva, + ); +} diff --git a/src/features/katalog/components/universite-icerik.tsx b/src/features/katalog/components/universite-icerik.tsx index ef5f258..dbeb805 100644 --- a/src/features/katalog/components/universite-icerik.tsx +++ b/src/features/katalog/components/universite-icerik.tsx @@ -1,7 +1,19 @@ import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; -import { ChevronRight } from "lucide-react"; +import { + ArrowRight, + Building2, + ChevronRight, + Landmark, + LayoutList, + Medal, + MapPin, + Target, + Users, +} from "lucide-react"; +import type { LucideIcon } from "lucide-react"; +import type { ReactNode } from "react"; import { getAllUniversiteler, getAllUniversiteSlugs, @@ -26,17 +38,23 @@ import { sayfaDilimi, siraYiliEtiketi, } from "./sayfalama"; -import { PagePixelDivider } from "@/components/pixel-decor"; +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 { - URAP_DONEM, - URAP_KAYNAK_URL, - URAP_TOPLAM, -} from "@/lib/urap-siralama"; + 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 { uniLogoYolu } from "@/lib/uni-logolar"; import { Skeleton } from "@/components/ui/skeleton"; import type { Program } from "@/types/yokatlas"; @@ -124,8 +142,8 @@ function fakulteOzeti({ * Atıf pazarlık dışı: URAP'ın izni "urapcenter.org adresine aktif bağlantı" * şartına bağlı, o yüzden gerçek , yeni sekme, rel="noopener" ve * nofollow YOK. Sıra payda ("198 üniversite arasında") ve dönem olmadan - * gösterilmez. URAP listesinde olmayan üniversitede sayı yoktur; eksiklik - * gizlenmez, nedeni tek cümleyle yazılır. + * 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) { @@ -139,18 +157,288 @@ function UrapKaynakSatiri({ uni }: { uni: UniDetay }) {

); } + // 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
, 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 ? ( +
+
Türkiye sırası
+
+ {sayi(urap.sira)}. + + / {sayi(URAP_TOPLAM)} + +
+
+ {dilim <= 50 ? `İlk %${dilim}` : `${URAP_DONEM} listesi`} +
+
+ +
+
+ ) : null; + const ogrenciKaresi = oranVar ? ( +
+
{sayi(i.ogrenci)}
+
Örgün öğrenci
+
+ ) : null; + const oranKaresi = oranVar ? ( +
+
Hoca başına öğrenci
+
{sayi(oran)}
+
+ Türkiye: {sayi(OGRENCI_BASINA_UYE_MEDYAN)} +
+
+ ) : null; + const uyeKaresi = oranVar ? ( +
+
{sayi(i.ogretimUyesi)}
+
Öğretim üyesi
+
+ ) : null; + const ikiSutun = urap != null && oranVar; + + return ( +
+ ); +} + +/** + * Ö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 ( +
+
+ {etiket} +
+
+ + + {deger} + + {birim ? ( + + {birim} + + ) : null} + + + + +
+
{alt}
+ {ek ?
{ek}
: null} +
+ ); +} + +/** + * 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 ( + } + etiket="Türkiye sırası" + deger={ + + {sayi(sira)}. + + } + birim={`/ ${sayi(URAP_TOPLAM)}`} + alt={ + <> + {dilim <= 50 ? ( + + İlk %{dilim} + + ) : null} + + URAP {URAP_DONEM} + + + } + /> + ); +} + +/** "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 (

- URAP Türkiye Sıralaması {URAP_DONEM} ({sayi(URAP_TOPLAM)} üniversite - arasında) — akademik yayın ve araştırma performansını ölçer; eğitim - kalitesini ya da iş bulma oranını ölçmez. Kaynak:{" "} + Öğ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:{" "} - urapcenter.org + istatistik.yok.gov.tr .

@@ -231,7 +519,7 @@ export function UniversiteIcerik({ // ö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 = ( -
+
); @@ -276,71 +564,88 @@ export function UniversiteIcerik({ -
+ {/* Başlık üst çubuğun altına girince üst çubukta logo + ad belirir */} + } + /> + {/* 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. */} +
-

+

{uni.ad} Taban Puanları {BASLIK_YILI} {ilkSayfa ? "" : ` — Sayfa ${sayfa}`}

{/* Kurumun kimliği (tür + il): ızgaradaki kartlar sayısal ve - karşılaştırılabilir metriklerdir, tür ise kategoridir — sitenin - geri kalanında da başlığın altında çip olarak duruyor. Çipin sınıf - seti program-tablosu.tsx:329-333'ten birebir kopyadır (AGENTS.md - "ortak elemanlar tek kaynaktan"); yeni varyant üretilmez. - Sayfalama sayfalarında da yazar: ziyaretçi Google'dan doğrudan - /sayfa/N'ye inebiliyor, kimlik orada da aynı. `unitur` NULL ise - satırın tamamı düşer, kopuk boşluk kalmaz. */} - {turEtiketi ? ( -

- - {turEtiketi.cip} - + 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 ? ( +

+ {turEtiketi ? ( + + {uniturDevletMi(uni.unitur) ? ( + + ) : ( + + )} + {turEtiketi.cip} + + ) : null} {uni.il ? ( - {trBaslikDuzeni(uni.il)} + + + {trBaslikDuzeni(uni.il)} + ) : null}

) : null} {ilkSayfa ? ( <> -

+

{uni.ad} {uni.il ? ` (${trBaslikDuzeni(uni.il)})` : ""} {turCumlesi ? `, ${turCumlesi};` : ","} {SON_YIL} YKS yerleştirme @@ -370,82 +675,68 @@ export function UniversiteIcerik({ {ustSiraSatiri} -

+
+ {/* Ş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. */}
-
-
- Program -
-
- {sayi(uni.programSayisi)} -
-
-
-
- Fakülte / Birim -
-
- {sayi(uni.fakulteSayisi)} -
-
- {/* Şablon tam 4 hücre bırakıyor: masaüstünde harita 3. sütunu - iki satır boyunca kaplıyor (lg:grid-rows-2 + row-span-2), 5. - kart ızgarayı bozar. Bu yüzden 4. hücre tek: URAP'ta olan - üniversitede URAP sırası, olmayan 31 sayfada toplam - kontenjan. Toplam kontenjanı 0 olan üniversite yok - (yokatlas.db: GROUP BY universite HAVING - SUM(COALESCE(kontenjan2026,0))=0 → 0 satır), yani hiçbir - sayfada "—" çıkmaz. URAP'ta payda kartın içindedir: "18." - tek başına yanıltır. */} -
- {uni.urap ? ( - <> -
- URAP Türkiye -
-
- {sayi(uni.urap.sira)} / {sayi(URAP_TOPLAM)} - - {URAP_DONEM} - -
- - ) : ( - <> -
- Toplam kontenjan -
-
- {sayi(uni.stats.toplamKontenjan)} -
- - )} -
-
-
- En iyi sıralama -
-
- {uni.stats.minSira != null ? sayi(uni.stats.minSira) : "—"} -
-
+ + + + {uni.urap ? ( + + ) : ( + + )}
+ {uni.urap || uni.istatistik ? ( + + + + ) : null} ) : ( <> -

+

{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{" "} + {birimler ? ` Bu sayfadaki birimler: ${birimler}.` : ""}{" "} + Üniversite özeti ve istatistikler{" "} ilk sayfada @@ -455,13 +746,13 @@ export function UniversiteIcerik({ )} - + -

-

+
+

Üniversite programları

-

+

Fakülte ve birimlere göre

@@ -474,6 +765,7 @@ export function UniversiteIcerik({ {ilkSayfa ? : null} + {ilkSayfa ? : null} - -
+ +
-
+
{Array.from({ length: 5 }, (_, i) => ( - + ))}
{Array.from({ length: 5 }, (_, i) => ( - + ))}
diff --git a/src/features/katalog/components/universite-konum-haritasi.tsx b/src/features/katalog/components/universite-konum-haritasi.tsx index 8d1eb9f..f50311f 100644 --- a/src/features/katalog/components/universite-konum-haritasi.tsx +++ b/src/features/katalog/components/universite-konum-haritasi.tsx @@ -27,7 +27,7 @@ export function UniversiteKonumHaritasi({ return ( -

+

{yurtdisi.ulkeAd} {yurtdisi.sehir ? ` · ${yurtdisi.sehir}` : ""}

diff --git a/src/features/katalog/components/universite-program-tablosu.tsx b/src/features/katalog/components/universite-program-tablosu.tsx index eb7408d..88c2060 100644 --- a/src/features/katalog/components/universite-program-tablosu.tsx +++ b/src/features/katalog/components/universite-program-tablosu.tsx @@ -36,29 +36,36 @@ export function UniversiteProgramTablosu({ }: { gruplar: { fakulte: string; programlar: UniProgramSatiri[] }[]; }) { + // Şişko ve sade: program adı kesilmez (sarar), sıra ve butonlar iri. + // 375 px'te yalnız ad + butonlar sütun olarak kalır; taban sıra adın + // altındaki satıra iner, böylece ad bütün genişliği kullanır. return ( <> {gruplar.map(({ fakulte, programlar }, i) => ( -
-

+
+

{fakulte}

- - Program - + + + Program + + Son {VERI_YIL_SAYISI} yıl - + {SON_YIL} puan - + Kontenjan - Taban sıra - + + Taban sıra + + Netler ve listeye ekle @@ -66,61 +73,64 @@ export function UniversiteProgramTablosu({ {programlar.map((p) => { const taban = programEtkinSira(p); + const tabanMetni = taban != null ? `~${sayi(taban)}.` : "—"; return ( - + {p.bolumSlug ? ( {p.isim.trim()} ) : ( - + {p.isim.trim()} )} - - + + {TUR_ETIKET[p.tur] ?? p.tur} {p.sure ? ( {p.sure} yıl ) : null} + + Taban sıra + + {tabanMetni} + + + - + - + {p.puanSon != null ? p.puanSon.toFixed(2).replace(".", ",") : "—"} - + {p.kontenjanSon != null ? `${sayi(p.kontenjanSon)} kişi` : "—"} - - + + - {taban != null ? `~${sayi(taban)}.` : "—"} + {tabanMetni} - - + + - + -- 2.49.1