v2 #10
@@ -668,8 +668,9 @@ sihirbaz modalı içindir; yeni bir ayrıntı için `Dialog` açılmaz.
|
|||||||
tek çekmece açar; veri `/api/program-ayrinti`. Sıra: başlık = program adı, bağlam =
|
tek çekmece açar; veri `/api/program-ayrinti`. Sıra: başlık = program adı, bağlam =
|
||||||
üniversite · il · fakülte; **künye hapları** (§4.12 ölçüsü, link değil: süre + tür, dil,
|
üniversite · il · fakülte; **künye hapları** (§4.12 ölçüsü, link değil: süre + tür, dil,
|
||||||
öğretim türü (örgün değilse), burs/ücret, akreditasyon, baraj, öğretim üyesi sayısı,
|
öğretim türü (örgün değilse), burs/ücret, akreditasyon, baraj, öğretim üyesi sayısı,
|
||||||
yalnız kız/erkek koşulu, kampüs ilçesi — yalnız verisi olanlar); "Kontenjan türleri (yıl)" `dl` satırları
|
yalnız kız/erkek koşulu, kampüs ilçesi — yalnız verisi olanlar); "Kontenjan türleri (yıl)" satırları
|
||||||
(yalnız >0 olanlar; özel kontenjanın ayrı taban sırasının elimizde olmadığı notu);
|
(yalnız >0 olanlar; özel kontenjanın ayrı taban sırasının elimizde olmadığı notu; güncel
|
||||||
|
düzen aşağıda, 11 Eki 2026);
|
||||||
"Son yerleşenin netleri" (eski tablo + OBP katsayısı satırı; 0,18 = meslek lisesi ek
|
"Son yerleşenin netleri" (eski tablo + OBP katsayısı satırı; 0,18 = meslek lisesi ek
|
||||||
puanı notu); "Yerleşme koşulları (N)" yerel `details` ile kapalı başlar; "Mezun
|
puanı notu); "Yerleşme koşulları (N)" yerel `details` ile kapalı başlar; "Mezun
|
||||||
istihdamı" ve "Üniversite" tek paragraf; en altta kaynak satırı `border-t border-slate-100
|
istihdamı" ve "Üniversite" tek paragraf; en altta kaynak satırı `border-t border-slate-100
|
||||||
@@ -693,6 +694,37 @@ sihirbaz modalı içindir; yeni bir ayrıntı için `Dialog` açılmaz.
|
|||||||
göstergeler çekmecesi (§4.9) aynı bileşenin kartlı modunu kullanır (her açılışta oynar).
|
göstergeler çekmecesi (§4.9) aynı bileşenin kartlı modunu kullanır (her açılışta oynar).
|
||||||
Gövde kaydırılınca içerik tutamaç ve çarpının altına maskesiz girmesin diye `AltSayfaGovde`
|
Gövde kaydırılınca içerik tutamaç ve çarpının altına maskesiz girmesin diye `AltSayfaGovde`
|
||||||
üst kenarında 20 px saydam → dolu `mask-image` var (§4.10; tüm çekmeceler).
|
üst kenarında 20 px saydam → dolu `mask-image` var (§4.10; tüm çekmeceler).
|
||||||
|
- **11 Eki 2026 — kontenjan türleri (açık panelin içi): önce bütün, sonra parçalar:** Bilal: "daha anlaşılabilir
|
||||||
|
bir UI." Eski hâl (uzun açıklama paragrafı + iki sütunlu `dl`, "70 kontenjan · 72 yerleşen")
|
||||||
|
kimin hangi yere girebildiğini söylemiyordu. Yeni sıra: tek cümle toplam (`text-base
|
||||||
|
text-slate-600`, sayı `font-semibold text-slate-900`: "Toplam 81 kişilik yer açıldı: 70 herkese
|
||||||
|
açık, 11 yalnız şartı taşıyan adaylara") → oran çubuğu (`flex h-3 gap-0.5 rounded-full`,
|
||||||
|
dilim genişliği kontenjanla orantılı, `min-w-1.5`; genel `bg-primary`, özel `bg-slate-300`,
|
||||||
|
`aria-hidden`) → iki grup: "Herkese açık" ve "Yalnız şartı taşıyan adaylara" (grup etiketi
|
||||||
|
`text-sm font-semibold text-slate-500` + çubukla aynı renkte `size-2.5` nokta). Satır tek
|
||||||
|
sütun, `border-b border-slate-100 py-3`: solda ad (`text-base font-semibold`) ve altında
|
||||||
|
kimin girebildiği (`KONTENJAN_TURU_KIME`, `src/lib/ozel-durum.ts`); sağda sayı (`font-heading
|
||||||
|
text-xl font-bold` + `kişi` birimi) ve altında "N yerleşti · M boş kaldı". Renk yalnız
|
||||||
|
açık/şartlı ayrımını taşır, türün adı her satırda yazılıdır. Altta not: özel taban sırası
|
||||||
|
bizde yok; genelde yerleşen kontenjanı aşıyorsa aktarım cümlesi eklenir. Yalnız genel
|
||||||
|
kontenjan varsa çubuk ve grup yok, tek satır. Kart yok kuralı duruyor.
|
||||||
|
- **11 Eki 2026 — üç bölüm dönüşen gösterge karesi:** Bilal: "Kontenjan türleri, son
|
||||||
|
yerleşenin netleri, mezun istihdamı akademik göstergelerdeki gibi kutu olsun; tıklanınca o
|
||||||
|
kutu morph olarak genişlesin ve içeriğin tam hâli gözüksün." Bu üç bölüm artık künye
|
||||||
|
haplarının altında §4.11 gösterge kareleridir (`BolumKutulari`; sınıflar §4.11 ile birebir,
|
||||||
|
masonry: sol sütun Kontenjan `bg-orange-500 min-h-40` + İstihdam `bg-indigo-500 min-h-28`,
|
||||||
|
sağ sütun `mt-12` Son yerleşen `bg-primary min-h-40`). Kare bir düğmedir: etiket + sayı +
|
||||||
|
vurgu satırı, sağ üstte `Maximize2 size-4 text-white/80`, `active:scale-[0.97]`. Dokununca
|
||||||
|
kare **tam genişlikte panele dönüşür** (`motion` `layoutId`, yay `YAY.donusum`; köşe 24 px
|
||||||
|
stilde ki ölçeklenirken bozulmasın): panel `rounded-3xl bg-slate-50 p-4 sm:p-5`, kenarlıksız;
|
||||||
|
karenin rengi panelin üstünde `SURE.standart`'ta söner, içerik 0,14 s gecikmeyle belirir.
|
||||||
|
Panel başlığı: karenin renginde `size-3 rounded-[4px]` kare + `font-heading text-base
|
||||||
|
font-bold` başlık + §4.3 çarpısı ("küçült"). Aynı anda tek panel açık; kalan iki kare panelin
|
||||||
|
altında yan yana durur, dokununca yer değiştirir. Odak dönüşen öğeyle gider (açılınca çarpı,
|
||||||
|
kapanınca kare). "Hareketi azalt"ta dönüşüm yok (`MotionConfig reducedMotion="user"`). Verisi
|
||||||
|
olmayan bölümün karesi çizilmez. 10 Ekim'deki "bu çekmecede kart yok" kuralı bu üç bölüm için
|
||||||
|
kalktı; koşullar, üniversite ve kaynak satırı kartsız kalır. Açılış sahnesi değişmedi (logo →
|
||||||
|
başlık); kareler tek `data-sonra` bloğu olarak netleşir.
|
||||||
- **10 Eki 2026 — büyük logo, logo → başlık (morph):** Bilal: "logo küçücük kalmış; logolar
|
- **10 Eki 2026 — büyük logo, logo → başlık (morph):** Bilal: "logo küçücük kalmış; logolar
|
||||||
küçülürken başlıktaki yazıya morph olsun." Logo kabı `size-44` (akademik göstergelerdeki
|
küçülürken başlıktaki yazıya morph olsun." Logo kabı `size-44` (akademik göstergelerdeki
|
||||||
logoyla aynı ölçek). Kareler sönmek yerine küçülerek (scale 0.2) başlık yazısının üç eşit
|
logoyla aynı ölçek). Kareler sönmek yerine küçülerek (scale 0.2) başlık yazısının üç eşit
|
||||||
@@ -890,43 +922,58 @@ dilindedir, burada kullanılmaz). Kaynak Arc UI "Card stack".
|
|||||||
kullanacağız". Dosyalar duruyor; şu an hiçbir sayfada çizilmiyor.
|
kullanacağız". Dosyalar duruyor; şu an hiçbir sayfada çizilmiyor.
|
||||||
|
|
||||||
### 4.28 Veri akışı — `VeriAkisi`, `src/features/pazarlama/components/veri-akisi.tsx`
|
### 4.28 Veri akışı — `VeriAkisi`, `src/features/pazarlama/components/veri-akisi.tsx`
|
||||||
Ana sayfa "Nasıl çalışır" bölümünde, üç adımın (§4.32) altında: ortada KolayTercih işareti,
|
Ana sayfa "Nasıl çalışır" bölümünde, üç adımın (§4.32) altında. **Üstte öğrencinin üç seçimi**
|
||||||
çevresinde altı girdi; her girdiden merkeze **düz çizgi** iner, üzerinde piksel kare akar. **Solda
|
(turuncu ikon ve kayıt), **altta YÖK Atlas'ın üç verisi** (mavi ikon ve kayıt), **ortada
|
||||||
öğrencinin üç seçimi** (turuncu ikon ve kare), **sağda YÖK Atlas'ın üç verisi** (mavi ikon ve
|
danışman**: "DanışmanlaKonuş" çekirdeği. Her kutudan çekirdeğe kıvrımlı bir yol iner, üzerinde
|
||||||
kare); ayrımı etiket ve yan taşır, renk eşlik eder (§2.2). Akış CSS'tir (`kt-akis`, globals.css),
|
kayıt akar; ayrımı etiket ve sıra taşır, renk eşlik eder (§2.2). Akış CSS'tir (`kt-akis-*`,
|
||||||
yalnız ekrandayken oynar; hareketi azalt'ta kareler durur. Esin React Bits "Center Flow"; kod
|
globals.css), yalnız ekrandayken oynar; hareketi azalt'ta her yolda bir ham kayıt durağan durur.
|
||||||
bizim — parlama ve bulanıklık yok, akış içeri doğru. Yeni bir girdi eklenirse sihirbaz/havuz
|
Yerleşim ve kaydın yaşam döngüsü Arc UI "Data flow"dan (Bilal, 11 Eki 2026: önce "logoya kareler
|
||||||
kurallarıyla aynı olmalı (`/meraklisina` şemasıyla çelişmez).
|
gelen kısım bunun gibi gözükmeli", sonra "ortadaki kısmı dikdörtgen formuna sokup DanışmanlaKonuş
|
||||||
- **Çizgi düz, karolar beyaz** (Bilal, 7 Eki 2026: "aradaki yerler solid çizgi olsun, etrafındaki
|
yazsak, kutular attığım örnekteki gibi"). Arc'ın bileşeni Pro'dur, kaynağı alınmadı (§11.3
|
||||||
kartların bg'si ve görünümü daha iyi olabilir"). Çizgi `var(--color-slate-300)`, 1,5 px
|
md. 7); önizlemedeki davranış DOM kaydıyla ölçülüp bizim dille yeniden yazıldı — parlama ve
|
||||||
(`vectorEffect="non-scaling-stroke"`), karonun merkezinden işaretin merkezine çekilir ve
|
bulanıklık yok. Yeni bir girdi eklenirse sihirbaz/havuz kurallarıyla aynı olmalı
|
||||||
karoların **altında** kalır; kesik/noktalı çizgi yazılmaz. Girdi karosu
|
(`/meraklisina` şemasıyla çelişmez). Eski altıgen yerleşim (ortada kare logo karosu, çevresinde
|
||||||
`size-14 rounded-2xl border border-slate-200 bg-card` + `size-6` ikon; ortadaki karo
|
altı karo, düz çizgiler) kaldırıldı.
|
||||||
`size-24 rounded-3xl border border-slate-200 bg-card`. Renkli soluk zemin (`bg-orange-50/60`,
|
- **Kutu:** `h-20 w-[30%] rounded-2xl border border-slate-200 bg-card`, ikon üstte (`size-5`,
|
||||||
`bg-primary/10`) kaldırıldı: karo dolu olmalı ki çizgi içinden görünmesin. Gölge yok (§6).
|
yanın renginde), etiket altta (`text-xs font-semibold leading-tight text-balance
|
||||||
|
text-slate-700`, en çok iki satır). Etiket kutunun **içindedir**; dışarıda yüzen etiket yok.
|
||||||
|
Üç kutu sütunları kabın %16,7 / %50 / %83,3'ünde. Gölge yok (§6).
|
||||||
|
- **Çekirdek:** `h-16 rounded-2xl border border-slate-200 bg-card`, içinde danışman panelinin
|
||||||
|
başlığı (`DanismanBaslik` deseni, `sohbet-client.tsx`): turuncu `AnimasyonluMark`
|
||||||
|
(`varyant="nabiz" renk="turuncu"`, `size-7`) + `font-bricolage text-xl tracking-tighter`
|
||||||
|
"Danışmanla" (`font-light`) "Konuş" (`font-semibold`), boşluksuz. İşaret ≈3 sn nabız atar,
|
||||||
|
sonra girdilerden çıkana dönüşür (liste → trend → sohbet, `bicimler`, §4.34) ve toplanır.
|
||||||
|
- **Yol:** `var(--color-slate-300)`, 1,5 px, kesiksiz; iki ucu dikey çıkan S kıvrımı
|
||||||
|
(`C x0 ym, x1 ym`). Yollar çekirdeğin kenarına ortadan 4'er birim açılarak varır. Kutudan
|
||||||
|
çıktığı yerde 7 px'lik **port** halkası durur.
|
||||||
|
- **Kayıt ham çıkar, işlenmiş varır.** Ömrü 1,4 sn: porttan küçük ve soluk doğar (ölçek
|
||||||
|
0,6 → 1), arkasında iki küçük noktadan bir **iz** taşır; yol boyunca **içi boş nokta**dır (ham
|
||||||
|
girdi: `bg-card` dolgu, 1,5 px renkli çerçeve). Çekirdeğe yaklaşınca hafifçe şişer (1,2),
|
||||||
|
**dolar ve piksel kareye döner** (işlenmiş), kenarda sönerek içeri alınır. O an **çekirdeğin
|
||||||
|
çerçevesi** kaydın renginde yanar, genişleyip söner; kalkışta da kutunun çerçevesi ve portu
|
||||||
|
kısa bir an yanar (tepe %50). Ham ile işlenmiş yalnız renkle değil **biçimle** ayrılır.
|
||||||
|
- **Ritim:** 6 sn'lik döngüde altı kayıt, 1 sn arayla, bir üstten bir alttan kalkar; yol 1,2 sn
|
||||||
|
sürdüğü için biri varırken sıradaki yola çıkmıştır, akış hiç durmaz.
|
||||||
|
- **Kayıt eğriyi JS'siz izler.** Bu S kıvrımında parametre zamanla doğrusal ilerlerken x tam
|
||||||
|
olarak `cubic-bezier(1/3, 0, 2/3, 1)`, y tam olarak `cubic-bezier(1/3, .5, 2/3, .5)` izler;
|
||||||
|
kayıt iç içe iki öğedir (dıştaki yatay, içteki dikey kayar) ve çizginin tam üstünde yürür. Bu
|
||||||
|
iki eğri hareket zevki değil yolun geometrisidir (`--akis-x`, `--akis-y`); `hareket.ts`'e
|
||||||
|
girmez, yol değişirse onlar da değişir. İz noktaları aynı iki kaymayı 70 ve 140 ms gecikmeyle
|
||||||
|
yapar. Yatay eksen kabın yüzdesidir (kap `@container`, x'ler `cqw`), dikey eksen rem; ölçüm
|
||||||
|
ya da `ResizeObserver` yok. Eğri öğenin `animation` kısayolundadır: tarayıcı `@keyframes`
|
||||||
|
içindeki `animation-timing-function: var(--…)` değerini çözmez, doğrusala düşer.
|
||||||
- **Kartsız, zeminsiz; bilgiler iki yanında, onlar da kartsız** (Bilal, 7 Eki 2026: "kartın
|
- **Kartsız, zeminsiz; bilgiler iki yanında, onlar da kartsız** (Bilal, 7 Eki 2026: "kartın
|
||||||
dışında, arka plansız, sağında solunda da alttaki bilgiler olacak şekilde"; aynı gün: "hero'daki
|
dışında, arka plansız, sağında solunda da alttaki bilgiler olacak şekilde"; aynı gün: "hero'daki
|
||||||
gibi ferah"). Geniş ekranda (lg+) üç sütun (`lg:grid-cols-[minmax(0,1fr)_22rem_minmax(0,1fr)]`,
|
gibi ferah"). Geniş ekranda (lg+) üç sütun (`lg:grid-cols-[minmax(0,1fr)_22rem_minmax(0,1fr)]`,
|
||||||
xl'de orta 24rem, dikeyde ortalı): **solda dürüstlük notu** — `SectionEyebrow` + `h3`
|
xl'de orta 24rem, dikeyde ortalı): **solda dürüstlük notu** — `SectionEyebrow` + `h3`
|
||||||
(`font-heading text-2xl font-bold text-balance`, ikinci yarısı hero başlığındaki vurgu:
|
(`font-heading text-2xl font-bold text-balance`, ikinci yarısı hero başlığındaki vurgu:
|
||||||
`font-bricolage font-bold tracking-tighter text-primary italic`) + `text-base` paragraf, sola
|
`font-bricolage font-bold tracking-tighter text-primary italic`) + `text-base` paragraf, sola
|
||||||
yaslı, `max-w-xs`; **ortada animasyon**; **sağda üç sayı** alt alta, sağa yaslı (YÖK Atlas
|
yaslı, `max-w-xs`; **ortada animasyon**; **sağda üç sayı** alt alta, sağa yaslı (sayılar
|
||||||
girdilerinin yanında — sayılar o verinin sayılarıdır). Sayı §1.1: `font-heading text-4xl
|
alt sıradaki YÖK Atlas verisinin sayılarıdır). Sayı §1.1: `font-heading text-4xl
|
||||||
font-bold leading-none tracking-tight text-primary sm:text-5xl`, birim ("yıl") §3'teki gibi
|
font-bold leading-none tracking-tight text-primary sm:text-5xl`, birim ("yıl") §3'teki gibi
|
||||||
`font-sans text-base font-semibold text-slate-400`, etiket `text-sm font-semibold
|
`font-sans text-base font-semibold text-slate-400`, etiket `text-sm font-semibold
|
||||||
text-slate-500`. Çerçeve, zemin, ayırıcı çizgi ve sayının yanında piksel süs **yok**.
|
text-slate-500`. Çerçeve, zemin, ayırıcı çizgi ve sayının yanında piksel süs **yok**.
|
||||||
lg altında tek sütun, her şey ortalı: animasyon → sayılar (sm'de üçü yan yana) → dürüstlük notu.
|
lg altında tek sütun, her şey ortalı: animasyon → sayılar (sm'de üçü yan yana) → dürüstlük notu.
|
||||||
- **Ortadaki işaret danışmandır: nabız atar, sonra çıktıya dönüşür** (Bilal, 11 Eki 2026:
|
|
||||||
"logo morph ile ikonlara dönüşsün; belirli süre DanışmanlaKonuş'un animasyonu oynayıp morph
|
|
||||||
olsun, şu ankini kaldır"). İşaret ≈3 sn DanışmanlaKonuş başlığındaki gibi yerinde nabız atar
|
|
||||||
(`varyant="nabiz"`: danışman düşünüyor), sonra üç kare girdilerden çıkan şeye dönüşür, ≈1,3 sn
|
|
||||||
durur ve logoya toplanır (toplam 2,5 sn); nabız yerinden sürer, sıra bir sonraki biçime
|
|
||||||
geçer. Sıra paketin verdiğidir: **liste** (deste) → **trend** (yükselen üç çubuk) → **sohbet**
|
|
||||||
(iki balon). Biçimler §4.34'tekilerle aynı dilde, üç kareden ve logonun üç renginden
|
|
||||||
(`AnimasyonluMark` `bicimler`). Yalnız ekrandayken oynar; ekrandan çıkınca kareler yerine
|
|
||||||
oturur. Hareketi azalt'ta durağan logo. Eski "rota" (kareler birbirinin yerine süzülür) bu
|
|
||||||
karodan kaldırıldı; ondan önceki nabız + tüm karoyu döndüren hâl (`kt-akis-donus`) de yanlıştı
|
|
||||||
(Bilal, 7 Eki 2026).
|
|
||||||
|
|
||||||
### 4.29 Program duvarı — `ProgramDuvari`, `src/features/pazarlama/components/program-duvari.tsx`
|
### 4.29 Program duvarı — `ProgramDuvari`, `src/features/pazarlama/components/program-duvari.tsx`
|
||||||
**Şu an sayfada kullanılmıyor:** ana sayfa "Sorun" bölümünden 7 Eki 2026'da çıkarıldı (Bilal:
|
**Şu an sayfada kullanılmıyor:** ana sayfa "Sorun" bölümünden 7 Eki 2026'da çıkarıldı (Bilal:
|
||||||
|
|||||||
@@ -455,39 +455,277 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Veri akışı (veri-akisi.tsx, DESIGN.md §4.28): kare, çizgi boyunca karodan
|
/* Veri akışı (veri-akisi.tsx, DESIGN.md §4.28). Üstte üç kutu, ortada
|
||||||
merkeze akar. Uç noktalar satır içi --x1/--y1/--x2/--y2 ile gelir (SVG
|
çekirdek, altta üç kutu; yollar SVG, kayıtlar HTML (yalnız translate /
|
||||||
içinde px = viewBox birimi). Yalnız ekrandayken oynar. */
|
scale / opacity oynar).
|
||||||
@keyframes kt-akis {
|
|
||||||
|
Geometri: yatay eksen kabın eninin yüzdesi (kap `@container`, x'ler cqw),
|
||||||
|
dikey eksen rem: kutular 5rem, çekirdek 10,5–14,5rem arası, sahne 25rem.
|
||||||
|
Kaydın iki ucu satır içi gelir (--x0/--y0 port, --x1/--y1 çekirdek kenarı).
|
||||||
|
|
||||||
|
Eğri: yol, iki ucu dikey çıkan S kıvrımıdır — C(x0 ym, x1 ym), ym orta
|
||||||
|
yükseklik. Bu eğride parametre zamanla doğrusal ilerlerken x tam olarak
|
||||||
|
cubic-bezier(1/3, 0, 2/3, 1), y tam olarak cubic-bezier(1/3, .5, 2/3, .5)
|
||||||
|
izler. Kayıt bu yüzden iç içe iki öğedir: dıştaki yatay, içteki dikey
|
||||||
|
kayar; ikisi birlikte çizginin tam üstünde yürür, ölçüm ya da JS gerekmez.
|
||||||
|
Bu iki eğri hareket zevki değil yolun geometrisidir (--akis-x / --akis-y);
|
||||||
|
yol değişirse onlar da değişir. İz, aynı iki kaymayı --ek kadar gecikmeyle
|
||||||
|
yapan iki küçük noktadır; böylece o da çizginin üstünde kalır.
|
||||||
|
|
||||||
|
Döngü 6 sn, altı kayıt 1 sn arayla (--g satır içi gecikme). Bir kaydın ömrü:
|
||||||
|
0 → 20 % yol (1,2 sn)
|
||||||
|
0 → 4 % kalkış: porttan küçük ve soluk çıkar (ölçek 0,6 → 1)
|
||||||
|
13 → 17 % işlenir: içi boş nokta şişer (1 → 1,2), dolar ve piksel kareye döner
|
||||||
|
15 → 19 % iz çekirdek kenarına varmadan söner
|
||||||
|
20 → 23 % kayıt kenarda sönerek içeri alınır
|
||||||
|
20 → 32 % çekirdek nabzı: çerçeve kaydın renginde yanar, genişleyip söner
|
||||||
|
0 → 10 % kaynak vurgusu: kutunun çerçevesi ve port kısa bir an yanar
|
||||||
|
Eğri öğenin `animation` kısayolundadır, keyframe'in içinde değil: tarayıcı
|
||||||
|
`@keyframes` içindeki `animation-timing-function: var(--…)` değerini çözmez
|
||||||
|
(doğrusala düşer). Kısayoldaki eğri her durak aralığına ayrı ayrı uygulanır.
|
||||||
|
Yalnız ekrandayken oynar (data-oynuyor). Hareketi azalt'ta her yolda bir
|
||||||
|
ham kayıt durağan durur. */
|
||||||
|
.kt-akis-sahne {
|
||||||
|
height: 25rem;
|
||||||
|
--akis-x: cubic-bezier(0.3333, 0, 0.6667, 1);
|
||||||
|
--akis-y: cubic-bezier(0.3333, 0.5, 0.6667, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kt-akis-kayit-kap {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Port: yolun kutudan çıktığı nokta; kayıt buradan doğar. */
|
||||||
|
.kt-akis-port {
|
||||||
|
position: absolute;
|
||||||
|
top: -3.5px;
|
||||||
|
left: -3.5px;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border: 1px solid var(--color-slate-300);
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: var(--card);
|
||||||
|
translate: var(--x0) var(--y0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kt-akis-port > .kt-akis-vurgu {
|
||||||
|
position: absolute;
|
||||||
|
inset: 1px;
|
||||||
|
border-radius: inherit;
|
||||||
|
background: var(--renk);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kt-akis-x,
|
||||||
|
.kt-akis-y {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kt-akis-x {
|
||||||
|
translate: var(--x0) 0;
|
||||||
|
animation: kt-akis-x 6s var(--akis-x) calc(var(--g, 0s) + var(--ek, 0s)) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kt-akis-y {
|
||||||
|
translate: 0 var(--y0);
|
||||||
|
animation: kt-akis-y 6s var(--akis-y) calc(var(--g, 0s) + var(--ek, 0s)) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* İz noktası: kayıttan küçük, dolu ve soluk. */
|
||||||
|
.kt-akis-iz {
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: var(--renk);
|
||||||
|
opacity: 0;
|
||||||
|
animation: kt-akis-iz 6s var(--egri-standart) calc(var(--g, 0s) + var(--ek, 0s)) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ham kayıt: içi boş nokta. */
|
||||||
|
.kt-akis-kare {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
left: -5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border: 1.5px solid var(--renk);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--card);
|
||||||
|
opacity: 0;
|
||||||
|
scale: 0.6;
|
||||||
|
animation: kt-akis-kare 6s var(--egri-standart) var(--g, 0s) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* İşlenmiş kayıt: aynı kutunun dolusu, üstüne solarak gelir. */
|
||||||
|
.kt-akis-dolgu {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
background: var(--renk);
|
||||||
|
opacity: 0;
|
||||||
|
animation: kt-akis-dolgu 6s var(--egri-standart) var(--g, 0s) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kt-akis-nabiz,
|
||||||
|
.kt-akis-vurgu {
|
||||||
|
border-color: var(--renk);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kt-akis-nabiz {
|
||||||
|
animation: kt-akis-nabiz 6s var(--egri-guclu-cikis) var(--g, 0s) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kt-akis-vurgu {
|
||||||
|
animation: kt-akis-vurgu 6s var(--egri-standart) var(--g, 0s) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes kt-akis-x {
|
||||||
0% {
|
0% {
|
||||||
translate: var(--x1) var(--y1);
|
translate: var(--x0) 0;
|
||||||
|
}
|
||||||
|
20%,
|
||||||
|
100% {
|
||||||
|
translate: var(--x1) 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes kt-akis-y {
|
||||||
|
0% {
|
||||||
|
translate: 0 var(--y0);
|
||||||
|
}
|
||||||
|
20%,
|
||||||
|
100% {
|
||||||
|
translate: 0 var(--y1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes kt-akis-iz {
|
||||||
|
0%,
|
||||||
|
3% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
12%,
|
7%,
|
||||||
82% {
|
15% {
|
||||||
opacity: 1;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
19%,
|
||||||
100% {
|
100% {
|
||||||
translate: var(--x2) var(--y2);
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.kt-akis-kare {
|
@keyframes kt-akis-kare {
|
||||||
translate: var(--x1) var(--y1);
|
0% {
|
||||||
|
scale: 0.6;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
animation: kt-akis 2.4s cubic-bezier(0.45, 0, 0.55, 1) infinite;
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
4%,
|
||||||
|
13% {
|
||||||
|
scale: 1;
|
||||||
|
opacity: 1;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
17% {
|
||||||
|
scale: 1.2;
|
||||||
|
opacity: 1;
|
||||||
|
border-radius: 25%;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
scale: 1;
|
||||||
|
opacity: 1;
|
||||||
|
border-radius: 25%;
|
||||||
|
}
|
||||||
|
23%,
|
||||||
|
100% {
|
||||||
|
scale: 0.8;
|
||||||
|
opacity: 0;
|
||||||
|
border-radius: 25%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes kt-akis-dolgu {
|
||||||
|
0%,
|
||||||
|
13% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
17%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Çekirdek geniş bir dikdörtgen: yatayda az, dikeyde çok büyür ki halka her
|
||||||
|
yanda aynı kadar (≈6 px) açılsın. */
|
||||||
|
@keyframes kt-akis-nabiz {
|
||||||
|
0%,
|
||||||
|
19.9% {
|
||||||
|
scale: 1;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
scale: 1;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
32%,
|
||||||
|
100% {
|
||||||
|
scale: 1.06 1.2;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes kt-akis-vurgu {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
2% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
10%,
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Oynatma durumu `animation` kısayolundan sonra gelmeli (kısayol sıfırlar). */
|
||||||
|
:is(.kt-akis-x, .kt-akis-y, .kt-akis-iz, .kt-akis-kare, .kt-akis-dolgu, .kt-akis-nabiz, .kt-akis-vurgu) {
|
||||||
animation-play-state: paused;
|
animation-play-state: paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-oynuyor] .kt-akis-kare {
|
[data-oynuyor]
|
||||||
|
:is(.kt-akis-x, .kt-akis-y, .kt-akis-iz, .kt-akis-kare, .kt-akis-dolgu, .kt-akis-nabiz, .kt-akis-vurgu) {
|
||||||
animation-play-state: running;
|
animation-play-state: running;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.kt-akis-kare {
|
:is(.kt-akis-x, .kt-akis-y, .kt-akis-iz, .kt-akis-kare, .kt-akis-dolgu, .kt-akis-nabiz, .kt-akis-vurgu) {
|
||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Durağan kare: yolun ortasında ham kayıt — bilgi eksilmez. */
|
||||||
|
.kt-akis-x {
|
||||||
|
translate: calc((var(--x0) + var(--x1)) / 2) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kt-akis-y {
|
||||||
|
translate: 0 calc((var(--y0) + var(--y1)) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kt-akis-kare {
|
||||||
|
opacity: 1;
|
||||||
|
scale: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tema geçişi (DESIGN.md §2.3, lib/tema-gecisi.ts): yeni tema header'daki
|
/* Tema geçişi (DESIGN.md §2.3, lib/tema-gecisi.ts): yeni tema header'daki
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
// türleri, son yerleşenin netleri, yerleşme koşulları, TÜİK mezun istihdamı,
|
// türleri, son yerleşenin netleri, yerleşme koşulları, TÜİK mezun istihdamı,
|
||||||
// üniversite özeti. API'nin `gecmis` (yıllara göre sıra/puan/kontenjan)
|
// üniversite özeti. API'nin `gecmis` (yıllara göre sıra/puan/kontenjan)
|
||||||
// alanı burada gösterilmez (Bilal, 7 Eki 2026: yıl tablosu kaldırıldı;
|
// alanı burada gösterilmez (Bilal, 7 Eki 2026: yıl tablosu kaldırıldı;
|
||||||
// backlog). Açılış: veri ilk gelince logo kareleri belirir, söner, içerik
|
// backlog). Kontenjan türleri, netler ve mezun istihdamı kapalıyken gösterge
|
||||||
|
// karesidir; dokununca kare panele dönüşüp bölümün tamamını açar
|
||||||
|
// (BolumKutulari; Bilal, 11 Eki 2026). Açılış: veri ilk gelince logo kareleri belirir, söner, içerik
|
||||||
// bloklar hâlinde netleşir (AcilisSahnesi kartsız modu); veri önbellekteyken
|
// bloklar hâlinde netleşir (AcilisSahnesi kartsız modu); veri önbellekteyken
|
||||||
// (çekmece yeniden açıldı) sahne yok, içerik 0,25 s solmayla gelir.
|
// (çekmece yeniden açıldı) sahne yok, içerik 0,25 s solmayla gelir.
|
||||||
// Kadın-erkek dağılımı program düzeyinde bilinçli olarak yok: YÖK Atlas 2026
|
// Kadın-erkek dağılımı program düzeyinde bilinçli olarak yok: YÖK Atlas 2026
|
||||||
@@ -18,7 +20,8 @@
|
|||||||
// değiştiği için takılmış görünüyordu (7 Eki 2026).
|
// değiştiği için takılmış görünüyordu (7 Eki 2026).
|
||||||
|
|
||||||
import { SATIR_BASLIK, SATIR_DUGME_BOYU } from "@/components/liste-satiri";
|
import { SATIR_BASLIK, SATIR_DUGME_BOYU } from "@/components/liste-satiri";
|
||||||
import { useState } from "react";
|
import { useEffect, useRef, useState, type ReactNode } from "react";
|
||||||
|
import { LayoutGroup, MotionConfig, motion } from "motion/react";
|
||||||
import {
|
import {
|
||||||
BadgeCheck,
|
BadgeCheck,
|
||||||
Clock,
|
Clock,
|
||||||
@@ -27,17 +30,27 @@ import {
|
|||||||
Info,
|
Info,
|
||||||
Languages,
|
Languages,
|
||||||
MapPin,
|
MapPin,
|
||||||
|
Maximize2,
|
||||||
Monitor,
|
Monitor,
|
||||||
Users,
|
Users,
|
||||||
Wallet,
|
Wallet,
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { ProgramAyrintisi } from "@/app/api/program-ayrinti/route";
|
import type { ProgramAyrintisi } from "@/app/api/program-ayrinti/route";
|
||||||
import type { KontenjanTuruAnahtari, ProgramNetSatiri } from "@/types/yokatlas";
|
import type {
|
||||||
|
KontenjanTuruAnahtari,
|
||||||
|
KontenjanTurleri,
|
||||||
|
ProgramNetSatiri,
|
||||||
|
} from "@/types/yokatlas";
|
||||||
import { olay } from "@/lib/analitik";
|
import { olay } from "@/lib/analitik";
|
||||||
|
import { SURE, YAY } from "@/lib/hareket";
|
||||||
// Yalnız saf modüller: bu bileşen istemcide çalışır, db'ye dokunan
|
// Yalnız saf modüller: bu bileşen istemcide çalışır, db'ye dokunan
|
||||||
// program-ozellik.ts import EDİLMEZ (kadro toplamı API'den gelir)
|
// program-ozellik.ts import EDİLMEZ (kadro toplamı API'den gelir)
|
||||||
import { CINSIYET_KOSULU_METNI, KONTENJAN_TURU_ADI } from "@/lib/ozel-durum";
|
import {
|
||||||
|
CINSIYET_KOSULU_METNI,
|
||||||
|
KONTENJAN_TURU_ADI,
|
||||||
|
KONTENJAN_TURU_KIME,
|
||||||
|
} from "@/lib/ozel-durum";
|
||||||
import { trBaslikDuzeni } from "@/lib/slug";
|
import { trBaslikDuzeni } from "@/lib/slug";
|
||||||
import { UNITUR_ETIKET } from "@/lib/unitur";
|
import { UNITUR_ETIKET } from "@/lib/unitur";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -252,18 +265,8 @@ function AyrintiIcerik({ durum, veri }: { durum: Durum; veri: ProgramAyrintisi |
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Kunye veri={veri} />
|
<Kunye veri={veri} />
|
||||||
<KontenjanTurleriBolumu veri={veri} />
|
<BolumKutulari veri={veri} />
|
||||||
<div data-sonra>
|
|
||||||
<h3 className={BOLUM_BASLIK}>Son yerleşenin netleri</h3>
|
|
||||||
<p className={NOT}>
|
|
||||||
Bölüme en düşük puanla yerleşen adayın sınav netleri (YÖK Atlas).
|
|
||||||
</p>
|
|
||||||
<div className="mt-3">
|
|
||||||
<NetlerIcerik netler={veri.netler} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Kosullar veri={veri} />
|
<Kosullar veri={veri} />
|
||||||
<Istihdam veri={veri} />
|
|
||||||
<UniversiteOzeti veri={veri} />
|
<UniversiteOzeti veri={veri} />
|
||||||
<p data-sonra className="mt-7 border-t border-slate-100 pt-3 text-xs leading-5 text-slate-500">
|
<p data-sonra className="mt-7 border-t border-slate-100 pt-3 text-xs leading-5 text-slate-500">
|
||||||
Kaynaklar: YÖK Atlas (yerleştirme, kontenjan, netler, koşullar; {SON_YIL}{" "}
|
Kaynaklar: YÖK Atlas (yerleştirme, kontenjan, netler, koşullar; {SON_YIL}{" "}
|
||||||
@@ -316,35 +319,364 @@ function Kunye({ veri }: { veri: ProgramAyrintisi }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function KontenjanTurleriBolumu({ veri }: { veri: ProgramAyrintisi }) {
|
/** "2 yerleşti · 1 boş kaldı" — yerleşen kontenjanı aşabilir (aktarım), o zaman yalnız sayı. */
|
||||||
const k = veri.kontenjanTurleri;
|
function yerlesenMetni(kontenjan: number, yerlesen: number | null): string {
|
||||||
if (!k) return null;
|
if (yerlesen == null) return "Yerleşen verisi yok";
|
||||||
const satirlar = KONTENJAN_TURU_SIRASI.filter((t) => (k[t].kontenjan ?? 0) > 0);
|
if (yerlesen === 0) return "Yerleşen olmadı";
|
||||||
if (satirlar.length === 0) return null;
|
const bos = kontenjan - yerlesen;
|
||||||
const ozelVar = satirlar.some((t) => t !== "genel");
|
return bos > 0 ? `${sayi(yerlesen)} yerleşti · ${sayi(bos)} boş kaldı` : `${sayi(yerlesen)} yerleşti`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function KontenjanSatiri({ tur, k }: { tur: KontenjanTuruAnahtari; k: KontenjanTurleri }) {
|
||||||
|
const kontenjan = k[tur].kontenjan ?? 0;
|
||||||
return (
|
return (
|
||||||
<div data-sonra>
|
<li className="flex items-start justify-between gap-4 border-b border-slate-100 py-3">
|
||||||
<h3 className={BOLUM_BASLIK}>Kontenjan türleri ({k.yil})</h3>
|
<div className="min-w-0">
|
||||||
<p className={NOT}>
|
<p className="text-base font-semibold text-slate-900">{KONTENJAN_TURU_ADI[tur]}</p>
|
||||||
{ozelVar
|
<p className="text-sm leading-snug text-slate-500">{KONTENJAN_TURU_KIME[tur]}</p>
|
||||||
? "ÖSYM okul birincileri, şehit ve gazi yakınları, depremzedeler ve 34 yaş üstü kadınlar için ayrı kontenjan ayırır; bu kontenjanlar yalnız o durumdaki adaylara açıktır ve her birinin taban sırası ayrı yayımlanır — o sıra burada yok."
|
</div>
|
||||||
: "Bu programda yalnız genel kontenjan var."}
|
<div className="shrink-0 text-right">
|
||||||
|
<p className="font-heading text-xl font-bold leading-6 text-slate-900">
|
||||||
|
{sayi(kontenjan)}
|
||||||
|
<span className="font-sans text-sm font-semibold text-slate-400"> kişi</span>
|
||||||
|
</p>
|
||||||
|
<p className="text-sm leading-snug tabular-nums text-slate-500">
|
||||||
|
{yerlesenMetni(kontenjan, k[tur].yerlesen)}
|
||||||
</p>
|
</p>
|
||||||
<dl className="mt-3 grid gap-x-6 gap-y-2 sm:grid-cols-2">
|
|
||||||
{satirlar.map((t) => (
|
|
||||||
<div key={t} className="flex items-baseline justify-between gap-3 border-b border-slate-100 py-2">
|
|
||||||
<dt className="text-sm text-slate-700">{KONTENJAN_TURU_ADI[t]}</dt>
|
|
||||||
<dd className="text-right text-sm tabular-nums text-slate-900">
|
|
||||||
<span className="font-semibold">{sayi(k[t].kontenjan ?? 0)}</span>
|
|
||||||
<span className="text-slate-500">
|
|
||||||
{" "}
|
|
||||||
kontenjan · {k[t].yerlesen != null ? sayi(k[t].yerlesen) : "—"} yerleşen
|
|
||||||
</span>
|
|
||||||
</dd>
|
|
||||||
</div>
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const GRUP_ETIKET = "flex items-center gap-2 text-sm font-semibold text-slate-500";
|
||||||
|
|
||||||
|
/** Kontenjan kırılımının özeti: kutudaki sayı ve açık paneldeki satırlar aynı hesaptan. */
|
||||||
|
function kontenjanOzeti(k: KontenjanTurleri) {
|
||||||
|
const satirlar = KONTENJAN_TURU_SIRASI.filter((t) => (k[t].kontenjan ?? 0) > 0);
|
||||||
|
const adet = (t: KontenjanTuruAnahtari) => k[t].kontenjan ?? 0;
|
||||||
|
const ozel = satirlar.filter((t) => t !== "genel");
|
||||||
|
const genelVar = satirlar.includes("genel");
|
||||||
|
const genel = genelVar ? adet("genel") : 0;
|
||||||
|
const ozelToplam = ozel.reduce((a, t) => a + adet(t), 0);
|
||||||
|
return {
|
||||||
|
satirlar,
|
||||||
|
ozel,
|
||||||
|
genelVar,
|
||||||
|
genel,
|
||||||
|
ozelToplam,
|
||||||
|
toplam: genel + ozelToplam,
|
||||||
|
// Dolmayan özel kontenjan genele aktarılır: genelde yerleşen kontenjanı aşar.
|
||||||
|
aktarim: genelVar && (k.genel.yerlesen ?? 0) > genel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Önce bütün (toplam + oran çubuğu), sonra parçalar: herkese açık yer ile
|
||||||
|
// yalnız şartı taşıyana açık yer iki ayrı grup. Çubukta yalnız bu ikili ayrım
|
||||||
|
// renklidir (mavi / nötr); türün adı her satırda yazılı, renk tek başına
|
||||||
|
// anlam taşımaz.
|
||||||
|
function KontenjanGovde({ k }: { k: KontenjanTurleri }) {
|
||||||
|
const o = kontenjanOzeti(k);
|
||||||
|
if (o.ozel.length === 0) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className={NOT}>
|
||||||
|
Bu programda yalnız genel kontenjan var; özel durumlar için ayrı yer ayrılmamış.
|
||||||
|
</p>
|
||||||
|
<ul className="mt-1">
|
||||||
|
<KontenjanSatiri tur="genel" k={k} />
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className="mt-2 text-base leading-relaxed text-slate-600">
|
||||||
|
Toplam <span className="font-semibold text-slate-900">{sayi(o.toplam)} kişilik</span> yer
|
||||||
|
açıldı
|
||||||
|
{o.genelVar
|
||||||
|
? `: ${sayi(o.genel)} herkese açık, ${sayi(o.ozelToplam)} yalnız şartı taşıyan adaylara.`
|
||||||
|
: "; tamamı yalnız şartı taşıyan adaylara."}
|
||||||
|
</p>
|
||||||
|
<div className="mt-3 flex h-3 gap-0.5 overflow-hidden rounded-full" aria-hidden>
|
||||||
|
{o.satirlar.map((t) => (
|
||||||
|
<span
|
||||||
|
key={t}
|
||||||
|
className={cn("min-w-1.5", t === "genel" ? "bg-primary" : "bg-slate-300")}
|
||||||
|
style={{ flexGrow: k[t].kontenjan ?? 0, flexBasis: 0 }}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</dl>
|
|
||||||
</div>
|
</div>
|
||||||
|
{o.genelVar ? (
|
||||||
|
<>
|
||||||
|
<p className={cn(GRUP_ETIKET, "mt-5")}>
|
||||||
|
<span className="size-2.5 rounded-full bg-primary" aria-hidden />
|
||||||
|
Herkese açık
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<KontenjanSatiri tur="genel" k={k} />
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<p className={cn(GRUP_ETIKET, "mt-5")}>
|
||||||
|
<span className="size-2.5 rounded-full bg-slate-300" aria-hidden />
|
||||||
|
Yalnız şartı taşıyan adaylara
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{o.ozel.map((t) => (
|
||||||
|
<KontenjanSatiri key={t} tur={t} k={k} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p className={cn(NOT, "mt-3")}>
|
||||||
|
Özel kontenjanların taban sırası ayrı yayımlanır; o sıra burada yok.
|
||||||
|
{o.aktarim
|
||||||
|
? " Dolmayan özel kontenjan genele aktarılır; genel kontenjanda yerleşenin kontenjanı aşması bundandır."
|
||||||
|
: ""}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gösterge karesi (DESIGN.md §4.11) — akademik göstergelerdeki karelerle aynı
|
||||||
|
// sınıf seti; burada kare bir düğmedir.
|
||||||
|
const KARE = "flex flex-col items-center justify-center rounded-3xl px-3 text-white";
|
||||||
|
const KARE_ETIKET = "text-sm font-bold text-white/90";
|
||||||
|
const KARE_DEV_SAYI = "font-heading text-5xl font-bold leading-none tracking-tight";
|
||||||
|
const KARE_ORTA_SAYI = "font-heading text-3xl font-bold leading-none";
|
||||||
|
const KARE_VURGU = "text-base font-bold text-white/90";
|
||||||
|
const KOSE = 24; // rounded-3xl; motion köşeyi ölçek sırasında düzeltsin diye stilde
|
||||||
|
|
||||||
|
type KutuAnahtari = "kontenjan" | "netler" | "istihdam";
|
||||||
|
type Kutu = {
|
||||||
|
anahtar: KutuAnahtari;
|
||||||
|
baslik: string;
|
||||||
|
/** Karenin zemini (§2 "Gösterge karesi"); anlam taşımaz, etiket yazılı */
|
||||||
|
renk: string;
|
||||||
|
boy: string;
|
||||||
|
ozet: ReactNode;
|
||||||
|
icerik: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
function kutulariKur(veri: ProgramAyrintisi): Kutu[] {
|
||||||
|
const kutular: Kutu[] = [];
|
||||||
|
const k = veri.kontenjanTurleri;
|
||||||
|
const o = k ? kontenjanOzeti(k) : null;
|
||||||
|
if (k && o && o.satirlar.length > 0) {
|
||||||
|
kutular.push({
|
||||||
|
anahtar: "kontenjan",
|
||||||
|
baslik: `Kontenjan türleri (${k.yil})`,
|
||||||
|
renk: "bg-orange-500",
|
||||||
|
boy: "min-h-40 py-4",
|
||||||
|
ozet: (
|
||||||
|
<>
|
||||||
|
<span className={KARE_ETIKET}>Kontenjan</span>
|
||||||
|
<span className={cn("mt-3", KARE_DEV_SAYI)}>{sayi(o.toplam)}</span>
|
||||||
|
<span className={cn("mt-2", KARE_VURGU)}>
|
||||||
|
{o.ozel.length === 0
|
||||||
|
? "Tamamı genel"
|
||||||
|
: o.genelVar
|
||||||
|
? `${sayi(o.genel)} genel · ${sayi(o.ozelToplam)} özel`
|
||||||
|
: "Tamamı özel"}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
icerik: <KontenjanGovde k={k} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (veri.netler.length > 0) {
|
||||||
|
const son = veri.netler.reduce((a, b) => (b.yil > a.yil ? b : a));
|
||||||
|
kutular.push({
|
||||||
|
anahtar: "netler",
|
||||||
|
baslik: "Son yerleşenin netleri",
|
||||||
|
renk: "bg-primary",
|
||||||
|
boy: "min-h-40 py-4",
|
||||||
|
ozet:
|
||||||
|
son.tabanPuan != null ? (
|
||||||
|
<>
|
||||||
|
<span className={KARE_ETIKET}>Son yerleşen</span>
|
||||||
|
<span className={cn("mt-3", KARE_ORTA_SAYI)}>{ondalik(son.tabanPuan)}</span>
|
||||||
|
<span className={cn("mt-2", KARE_VURGU)}>Puan ve netler</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className={KARE_ORTA_SAYI}>{son.yil}</span>
|
||||||
|
<span className={cn("mt-1.5", KARE_ETIKET)}>Son yerleşenin netleri</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
icerik: (
|
||||||
|
<>
|
||||||
|
<p className={NOT}>
|
||||||
|
Bölüme en düşük puanla yerleşen adayın sınav netleri (YÖK Atlas).
|
||||||
|
</p>
|
||||||
|
<div className="mt-3">
|
||||||
|
<NetlerIcerik netler={veri.netler} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const istihdamCumlesi = istihdamMetni(veri);
|
||||||
|
const v = veri.istihdam;
|
||||||
|
if (v && istihdamCumlesi) {
|
||||||
|
kutular.push({
|
||||||
|
anahtar: "istihdam",
|
||||||
|
baslik: "Mezun istihdamı",
|
||||||
|
renk: "bg-indigo-500",
|
||||||
|
boy: "min-h-28 py-3",
|
||||||
|
ozet:
|
||||||
|
v.istihdam != null ? (
|
||||||
|
<>
|
||||||
|
<span className={KARE_ORTA_SAYI}>%{ondalik(v.istihdam)}</span>
|
||||||
|
<span className={cn("mt-1.5", KARE_ETIKET)}>İşe yerleşen mezun</span>
|
||||||
|
</>
|
||||||
|
) : v.isBulmaAy != null ? (
|
||||||
|
<>
|
||||||
|
<span className={KARE_ORTA_SAYI}>{ondalik(v.isBulmaAy)} ay</span>
|
||||||
|
<span className={cn("mt-1.5", KARE_ETIKET)}>İlk işi bulma</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className={KARE_ORTA_SAYI}>{v.kazanc}</span>
|
||||||
|
<span className={cn("mt-1.5", KARE_ETIKET)}>Kazanç grubu</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
icerik: <p className={NOT}>{istihdamCumlesi}</p>,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return kutular;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kontenjan türleri · Son yerleşenin netleri · Mezun istihdamı: kapalıyken
|
||||||
|
* gösterge kareleri (§4.11 masonry), dokununca kare tam genişlikte panele
|
||||||
|
* dönüşür ve bölümün tamamı açılır (Bilal, 11 Eki 2026). Aynı anda tek panel
|
||||||
|
* açıktır; öbür kareler panelin altında yan yana kalır ve dokununca yer
|
||||||
|
* değiştirir. Dönüşüm `layoutId` ile (kare → panel aynı öğe sayılır), yay
|
||||||
|
* `YAY.donusum`; "hareketi azalt"ta dönüşüm yok, içerik doğrudan değişir.
|
||||||
|
*/
|
||||||
|
function BolumKutulari({ veri }: { veri: ProgramAyrintisi }) {
|
||||||
|
const [acik, setAcik] = useState<KutuAnahtari | null>(null);
|
||||||
|
// Az önce kapanan kare: panelden küçülürken içi ezik görünmesin diye solarak gelir.
|
||||||
|
const [kapanan, setKapanan] = useState<KutuAnahtari | null>(null);
|
||||||
|
const kapatRef = useRef<HTMLButtonElement>(null);
|
||||||
|
const kareRef = useRef<Partial<Record<KutuAnahtari, HTMLButtonElement | null>>>({});
|
||||||
|
const ilk = useRef(true);
|
||||||
|
|
||||||
|
// Odak dönüşen öğeyle birlikte gider: açılınca çarpıya, kapanınca kareye.
|
||||||
|
useEffect(() => {
|
||||||
|
if (ilk.current) {
|
||||||
|
ilk.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (acik) kapatRef.current?.focus({ preventScroll: true });
|
||||||
|
else if (kapanan) kareRef.current[kapanan]?.focus({ preventScroll: true });
|
||||||
|
}, [acik, kapanan]);
|
||||||
|
|
||||||
|
const kutular = kutulariKur(veri);
|
||||||
|
if (kutular.length === 0) return null;
|
||||||
|
const acikKutu = kutular.find((x) => x.anahtar === acik) ?? null;
|
||||||
|
const kapali = kutular.filter((x) => x.anahtar !== acik);
|
||||||
|
// Kapalıyken masonry (sol: 1. ve 3., sağ: 2., aşağı kaymış); panel açıkken
|
||||||
|
// kalan kareler yan yana.
|
||||||
|
const sol = kapali.filter((_, i) => i % 2 === 0);
|
||||||
|
const sag = kapali.filter((_, i) => i % 2 === 1);
|
||||||
|
|
||||||
|
function ac(anahtar: KutuAnahtari) {
|
||||||
|
setKapanan(acik);
|
||||||
|
setAcik(anahtar);
|
||||||
|
}
|
||||||
|
function kapat() {
|
||||||
|
setKapanan(acik);
|
||||||
|
setAcik(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const kare = (x: Kutu) => (
|
||||||
|
<motion.button
|
||||||
|
key={x.anahtar}
|
||||||
|
ref={(el) => {
|
||||||
|
kareRef.current[x.anahtar] = el;
|
||||||
|
}}
|
||||||
|
layoutId={`ayrinti-kutu-${x.anahtar}`}
|
||||||
|
type="button"
|
||||||
|
aria-expanded={false}
|
||||||
|
onClick={() => ac(x.anahtar)}
|
||||||
|
style={{ borderRadius: KOSE }}
|
||||||
|
className={cn(
|
||||||
|
KARE,
|
||||||
|
"relative w-full cursor-pointer transition-[scale] duration-150 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
|
x.renk,
|
||||||
|
x.boy,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<motion.span
|
||||||
|
className="flex flex-col items-center"
|
||||||
|
initial={kapanan === x.anahtar ? { opacity: 0 } : false}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: SURE.standart, delay: 0.1 }}
|
||||||
|
>
|
||||||
|
{x.ozet}
|
||||||
|
</motion.span>
|
||||||
|
<Maximize2 className="absolute top-3 right-3 size-4 text-white/80" aria-hidden />
|
||||||
|
<span className="sr-only"> — {x.baslik}, tamamını göster</span>
|
||||||
|
</motion.button>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MotionConfig reducedMotion="user" transition={YAY.donusum}>
|
||||||
|
<LayoutGroup>
|
||||||
|
<div data-sonra className="mt-7">
|
||||||
|
{acikKutu ? (
|
||||||
|
<motion.section
|
||||||
|
key={acikKutu.anahtar}
|
||||||
|
layoutId={`ayrinti-kutu-${acikKutu.anahtar}`}
|
||||||
|
aria-label={acikKutu.baslik}
|
||||||
|
style={{ borderRadius: KOSE }}
|
||||||
|
className="relative overflow-hidden bg-slate-50 p-4 sm:p-5"
|
||||||
|
>
|
||||||
|
{/* Karenin rengi panelin üstünde söner: renk de biçimle birlikte dönüşür. */}
|
||||||
|
<motion.span
|
||||||
|
aria-hidden
|
||||||
|
className={cn("pointer-events-none absolute inset-0", acikKutu.renk)}
|
||||||
|
initial={{ opacity: 1 }}
|
||||||
|
animate={{ opacity: 0 }}
|
||||||
|
transition={{ duration: SURE.standart, ease: "easeOut" }}
|
||||||
|
/>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: SURE.standart, delay: 0.14 }}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<h3 className="flex items-center gap-2.5 font-heading text-base font-bold text-slate-900">
|
||||||
|
<span className={cn("size-3 shrink-0 rounded-[4px]", acikKutu.renk)} aria-hidden />
|
||||||
|
{acikKutu.baslik}
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
ref={kapatRef}
|
||||||
|
type="button"
|
||||||
|
onClick={kapat}
|
||||||
|
className="flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-card text-slate-500 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
|
>
|
||||||
|
<X className="size-4" aria-hidden />
|
||||||
|
<span className="sr-only">{acikKutu.baslik} bölümünü küçült</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{acikKutu.icerik}
|
||||||
|
</motion.div>
|
||||||
|
</motion.section>
|
||||||
|
) : null}
|
||||||
|
{kapali.length > 0 ? (
|
||||||
|
<div className={cn("flex items-start gap-3", acikKutu && "mt-3")}>
|
||||||
|
<div className="flex flex-1 flex-col gap-3">{sol.map(kare)}</div>
|
||||||
|
{sag.length > 0 ? (
|
||||||
|
<div className={cn("flex flex-1 flex-col gap-3", !acikKutu && sol.length > 0 && "mt-12")}>
|
||||||
|
{sag.map(kare)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</LayoutGroup>
|
||||||
|
</MotionConfig>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,7 +812,8 @@ function Kosullar({ veri }: { veri: ProgramAyrintisi }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Istihdam({ veri }: { veri: ProgramAyrintisi }) {
|
/** Mezun istihdamı cümlesi (TÜİK); gösterecek değer yoksa null. */
|
||||||
|
function istihdamMetni(veri: ProgramAyrintisi): string | null {
|
||||||
const v = veri.istihdam;
|
const v = veri.istihdam;
|
||||||
if (!v) return null;
|
if (!v) return null;
|
||||||
const parcalar = [
|
const parcalar = [
|
||||||
@@ -493,15 +826,8 @@ function Istihdam({ veri }: { veri: ProgramAyrintisi }) {
|
|||||||
const birlesik = parcalar.join(", ");
|
const birlesik = parcalar.join(", ");
|
||||||
const cumle = birlesik.charAt(0).toLocaleUpperCase("tr-TR") + birlesik.slice(1);
|
const cumle = birlesik.charAt(0).toLocaleUpperCase("tr-TR") + birlesik.slice(1);
|
||||||
const ort = v.ortalama.istihdam != null ? ` Türkiye ortalaması %${ondalik(v.ortalama.istihdam)}.` : "";
|
const ort = v.ortalama.istihdam != null ? ` Türkiye ortalaması %${ondalik(v.ortalama.istihdam)}.` : "";
|
||||||
return (
|
const duzey = v.duzey === "lisans" ? "lisans" : "ön lisans";
|
||||||
<div data-sonra>
|
return `${cumle} (TÜİK ${v.yil}, ülke geneli, ${duzey} mezunları; üniversiteye göre değişir, kırılım yok).${ort}`;
|
||||||
<h3 className={BOLUM_BASLIK}>Mezun istihdamı</h3>
|
|
||||||
<p className={NOT}>
|
|
||||||
{cumle} (TÜİK {v.yil}, ülke geneli, {v.duzey === "lisans" ? "lisans" : "ön lisans"}{" "}
|
|
||||||
mezunları; üniversiteye göre değişir, kırılım yok).{ort}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function UniversiteOzeti({ veri }: { veri: ProgramAyrintisi }) {
|
function UniversiteOzeti({ veri }: { veri: ProgramAyrintisi }) {
|
||||||
|
|||||||
@@ -162,6 +162,15 @@ export function TercihHaritasi({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Kenar eritme mask'ı yalnızca yakınlaşmış hâlde: tam görünümde Türkiye'nin
|
||||||
|
// uçları (Edirne, Hakkari) rampaya giriyor ve harita kırpılmış gibi
|
||||||
|
// okunuyordu (Bilal, 11 Eki 2026). Yakınlaşırken hemen açılır (ilk karede
|
||||||
|
// kenarlar boş, geçiş görünmez); uzaklaşırken animasyon bitene kadar kalır
|
||||||
|
// ki komşu iller sert kenarla kırpılmış görünmesin.
|
||||||
|
// Uzaklaşma animasyonu sürerken true; rAF içinde yazılır (effect'te senkron
|
||||||
|
// setState değil), bitince düşer.
|
||||||
|
const [uzaklasiyor, setUzaklasiyor] = useState(false);
|
||||||
|
|
||||||
const animateTo = useCallback((hedef: ViewBox) => {
|
const animateTo = useCallback((hedef: ViewBox) => {
|
||||||
cancelAnimationFrame(animRef.current);
|
cancelAnimationFrame(animRef.current);
|
||||||
const kaynak = { ...vbRef.current };
|
const kaynak = { ...vbRef.current };
|
||||||
@@ -175,6 +184,8 @@ export function TercihHaritasi({
|
|||||||
// sf'e bağlı ölçüler tek render'da nihai değere oturur; rAF içinde
|
// sf'e bağlı ölçüler tek render'da nihai değere oturur; rAF içinde
|
||||||
// (asenkron) çağrıldığı için effect'te senkron setState sayılmaz.
|
// (asenkron) çağrıldığı için effect'te senkron setState sayılmaz.
|
||||||
setVb(hedef);
|
setVb(hedef);
|
||||||
|
// Mount'taki tam görünüm → tam görünüm çağrısında açılmasın
|
||||||
|
setUzaklasiyor(hedef === TAM_GORUNUM && kaynak.w !== hedef.w);
|
||||||
}
|
}
|
||||||
const t = Math.min(1, (now - t0) / SURE);
|
const t = Math.min(1, (now - t0) / SURE);
|
||||||
const k = ease(t);
|
const k = ease(t);
|
||||||
@@ -190,6 +201,7 @@ export function TercihHaritasi({
|
|||||||
`${sonraki.x} ${sonraki.y} ${sonraki.w} ${sonraki.h}`,
|
`${sonraki.x} ${sonraki.y} ${sonraki.w} ${sonraki.h}`,
|
||||||
);
|
);
|
||||||
if (t < 1) animRef.current = requestAnimationFrame(adim);
|
if (t < 1) animRef.current = requestAnimationFrame(adim);
|
||||||
|
else setUzaklasiyor(false);
|
||||||
};
|
};
|
||||||
animRef.current = requestAnimationFrame(adim);
|
animRef.current = requestAnimationFrame(adim);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -239,6 +251,7 @@ export function TercihHaritasi({
|
|||||||
// Ekran boyutu sabit kalsın diye SVG içi ölçüler zoom oranıyla çarpılır
|
// Ekran boyutu sabit kalsın diye SVG içi ölçüler zoom oranıyla çarpılır
|
||||||
const sf = vb.w / HARITA_GENISLIK;
|
const sf = vb.w / HARITA_GENISLIK;
|
||||||
const zoomda = Boolean(seciliIl);
|
const zoomda = Boolean(seciliIl);
|
||||||
|
const kenarErit = zoomda || uzaklasiyor;
|
||||||
const seciliPinler = useMemo(
|
const seciliPinler = useMemo(
|
||||||
() => (seciliIl ? (ilPinleri.get(seciliIl) ?? []) : []),
|
() => (seciliIl ? (ilPinleri.get(seciliIl) ?? []) : []),
|
||||||
[ilPinleri, seciliIl],
|
[ilPinleri, seciliIl],
|
||||||
@@ -297,6 +310,28 @@ export function TercihHaritasi({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
{/* Harita arka plandır, kesilmez (DESIGN.md §4.17): il'e yakınlaşınca
|
||||||
|
viewBox komşu illeri dikdörtgenle kırpıyor ve sert kenarlı bir
|
||||||
|
kanvas görünüyordu (Bilal, 11 Eki 2026: "canvas çok keskin").
|
||||||
|
Şehir/üniversite sayfalarındaki piksel haritayla aynı iki katlı
|
||||||
|
mask: dış kat üst-alt, iç kat sol-sağ kenarı eritir; hiçbir
|
||||||
|
kenarda yarım kesilmiş il kalmaz. Yalnız yakınlaşmışken (kenarErit);
|
||||||
|
tam görünümde mask yok, ülke uçları solmaz. "Türkiye" düğmesi
|
||||||
|
mask'ın dışında, erimez. */}
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
kenarErit
|
||||||
|
? "[mask-image:linear-gradient(to_bottom,transparent_0%,rgba(0,0,0,0.35)_7%,black_16%,black_84%,rgba(0,0,0,0.35)_93%,transparent_100%)]"
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
kenarErit
|
||||||
|
? "[mask-image:linear-gradient(to_right,transparent_0%,rgba(0,0,0,0.35)_5%,black_12%,black_88%,rgba(0,0,0,0.35)_95%,transparent_100%)]"
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
ref={svgMount}
|
ref={svgMount}
|
||||||
className="h-auto w-full"
|
className="h-auto w-full"
|
||||||
@@ -485,6 +520,8 @@ export function TercihHaritasi({
|
|||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
</svg>
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{zoomda ? (
|
{zoomda ? (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,24 +1,33 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
// Veri akışı (DESIGN.md §4.28): listenin neyle kurulduğunu tek bakışta
|
// Veri akışı (DESIGN.md §4.28): listenin neyle kurulduğunu tek bakışta
|
||||||
// gösterir. Ortada KolayTercih işareti, çevresinde altı girdi; her girdiden
|
// gösterir. Üstte öğrencinin üç seçimi (turuncu), altta YÖK Atlas'ın üç
|
||||||
// merkeze düz bir çizgi iner, üzerinde piksel kare akar. Soldaki üçü
|
// verisi (mavi), ortada danışman: "DanışmanlaKonuş" çekirdeği. Her kutudan
|
||||||
// öğrencinin seçimi (turuncu), sağdaki üçü YÖK Atlas verisi (mavi) — ayrımı
|
// çekirdeğe kıvrımlı bir yol iner, üzerinde kayıt akar. Ayrımı renk değil,
|
||||||
// renk değil, etiket ve yan taşır; renk yalnız eşlik eder (§2.2).
|
// etiket ve sıra taşır; renk yalnız eşlik eder (§2.2).
|
||||||
//
|
//
|
||||||
// Kartsız ve zeminsizdir (Bilal, 7 Eki 2026): sayfa zemininde açıkta durur,
|
// Kartsız ve zeminsizdir (Bilal, 7 Eki 2026): sayfa zemininde açıkta durur,
|
||||||
// eskiden altında duran sayılar ve dürüstlük notu geniş ekranda iki yanına
|
// sayılar ve dürüstlük notu geniş ekranda iki yanına dizilir (app/page.tsx).
|
||||||
// dizilir (yerleşim app/page.tsx'te).
|
|
||||||
//
|
//
|
||||||
// Ortadaki işaret danışmanın kendisidir (Bilal, 11 Eki 2026): bir süre
|
// Yerleşim ve kaydın yaşam döngüsü Arc UI "Data flow" gibidir (Bilal, 11 Eki
|
||||||
// DanışmanlaKonuş başlığındaki gibi nabız atar (danışman düşünüyor), sonra
|
// 2026: "ortadaki kısmı dikdörtgen formuna sokup DanışmanlaKonuş yazsak,
|
||||||
// üç kare girdilerden çıkan şeye dönüşür, durur ve logoya toplanır — sırayla
|
// kutular attığım örnekteki gibi"; uiarc.dev/components/data-flow — Pro
|
||||||
// liste, trend çubukları, sohbet balonları (`AnimasyonluMark` `bicimler`,
|
// bileşen, kaynağı alınmadı: önizlemedeki davranış DOM kaydıyla ölçülüp bizim
|
||||||
// §4.34). Eski "rota" (kareler birbirinin yerine süzülür) kaldırıldı.
|
// dille yeniden yazıldı, §11.3). Kutu = ikon üstte, etiket altta, tek yüzey.
|
||||||
|
// Kayıt kutunun portundan küçük ve soluk çıkar, arkasında iki noktalık bir iz taşır;
|
||||||
|
// yol boyunca içi boş noktadır (ham girdi). Çekirdeğe yaklaşınca hafifçe
|
||||||
|
// şişer, dolar ve piksel kareye döner (işlenmiş), kenarda sönerek içeri
|
||||||
|
// alınır; o an çekirdeğin çerçevesi kaydın renginde yanıp genişleyerek söner.
|
||||||
|
// Her 1 sn'de bir kayıt kalkar; altı kayıt 6 sn'lik döngüde bir üstten bir
|
||||||
|
// alttan yola çıkar.
|
||||||
//
|
//
|
||||||
// Esin: React Bits "Center Flow" (merkezden çevreye akan çizgiler). Kod bize
|
// Çekirdek danışman panelinin başlığıdır (`DanismanBaslik`, sohbet-client.tsx
|
||||||
// ait: parlama/bulanıklık yok (§2.2), akış yönü içeri doğrudur ve yalnız
|
// ile aynı desen): turuncu işaret nabız atar, yanında "DanışmanlaKonuş".
|
||||||
// ekrandayken oynar.
|
// İşaret aralıklarla girdilerden çıkana dönüşür (liste → trend → sohbet,
|
||||||
|
// `AnimasyonluMark` `bicimler`, §4.34).
|
||||||
|
//
|
||||||
|
// Geometri ve zamanlama globals.css'teki `kt-akis-*` kurallarındadır; buradan
|
||||||
|
// yalnız kaydın iki ucu (--x0/--x1/--y0/--y1), rengi ve gecikmesi gider.
|
||||||
|
|
||||||
import { useEffect, useRef, useState, type CSSProperties } from "react";
|
import { useEffect, useRef, useState, type CSSProperties } from "react";
|
||||||
import { Compass, MapPin, PieChart, TrendingUp, Trophy, Users } from "lucide-react";
|
import { Compass, MapPin, PieChart, TrendingUp, Trophy, Users } from "lucide-react";
|
||||||
@@ -30,41 +39,77 @@ import { cn } from "@/lib/utils";
|
|||||||
// başa döner (animasyonlu-mark.tsx).
|
// başa döner (animasyonlu-mark.tsx).
|
||||||
const CIKTILAR: readonly Bicim[] = ["deste", "trend", "sohbet"];
|
const CIKTILAR: readonly Bicim[] = ["deste", "trend", "sohbet"];
|
||||||
|
|
||||||
// Sağ yarı (−60°, 0°, 60°) veri; sol yarı (120°, 180°, 240°) öğrencinin seçimi.
|
// Üst sıra öğrencinin seçimi, alt sıra veri; `sutun` soldan sağa 0–2.
|
||||||
const DUGUMLER = [
|
const DUGUMLER = [
|
||||||
{ etiket: "Taban sıralamaları", ikon: TrendingUp, aci: -60, kaynak: "veri" },
|
{ etiket: "Senin sıralaman", ikon: Trophy, kaynak: "sen", sutun: 0 },
|
||||||
{ etiket: "Kontenjanlar", ikon: Users, aci: 0, kaynak: "veri" },
|
{ etiket: "İlgi alanların", ikon: Compass, kaynak: "sen", sutun: 1 },
|
||||||
{ etiket: "Doluluk oranları", ikon: PieChart, aci: 60, kaynak: "veri" },
|
{ etiket: "Şehir tercihin", ikon: MapPin, kaynak: "sen", sutun: 2 },
|
||||||
{ etiket: "Şehir tercihin", ikon: MapPin, aci: 120, kaynak: "sen" },
|
{ etiket: "Taban sıralamaları", ikon: TrendingUp, kaynak: "veri", sutun: 0 },
|
||||||
{ etiket: "Senin sıralaman", ikon: Trophy, aci: 180, kaynak: "sen" },
|
{ etiket: "Kontenjanlar", ikon: Users, kaynak: "veri", sutun: 1 },
|
||||||
{ etiket: "İlgi alanların", ikon: Compass, aci: 240, kaynak: "sen" },
|
{ etiket: "Doluluk oranları", ikon: PieChart, kaynak: "veri", sutun: 2 },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
/** viewBox 100×100: düğümlerin merkeze uzaklığı. Çizgi merkezden merkeze
|
// Kalkış sırası (DUGUMLER indeksleri): bir üstten bir alttan, komşu kutular
|
||||||
* çekilir ve karoların altında kalır; akan kare ise karoların dışında
|
// art arda kalkmaz; sahne bir yana yığılmaz.
|
||||||
* kalan aralıkta yürür (iki uçtaki boşluk). */
|
const KALKIS_SIRASI: readonly number[] = [0, 5, 1, 3, 2, 4];
|
||||||
const YARICAP = 36;
|
/** İz noktalarının kayda göre gecikmesi (sn): uzaktaki önce çizilir, küçük ve soluk kalır. */
|
||||||
const DIS_BOSLUK = 9.5;
|
const IZ_GECIKMELERI = [0.14, 0.07] as const;
|
||||||
const IC_BOSLUK = 15.5;
|
/** İki kalkış arası (sn). Döngü 6 kayıt × 1 sn = 6 sn (globals.css). */
|
||||||
/** Akan karenin kenarı (hero noktasının viewBox karşılığı). */
|
const KALKIS_ARALIGI = 1;
|
||||||
const KARE = 2.4;
|
|
||||||
|
|
||||||
// Üç ondalığa yuvarlanır: sunucu (Node) ile tarayıcının sin/cos'u son
|
// Sahne ölçüleri. Yatay eksen kabın eninin yüzdesi (SVG viewBox 0–100, CSS'te
|
||||||
// basamakta ayrışıyor, yuvarlanmazsa hidrasyon uyuşmazlığı çıkıyor.
|
// cqw), dikey eksen rem'in 16'da biri (viewBox 0–400 = 25rem). globals.css'teki
|
||||||
const yuvarla = (v: number) => Math.round(v * 1000) / 1000;
|
// `.kt-akis-sahne` ile aynı sayılar; biri değişirse öbürü de.
|
||||||
const nokta = (aci: number, r: number) => {
|
const SUTUN_X = [16.667, 50, 83.333] as const;
|
||||||
const rad = (aci * Math.PI) / 180;
|
/** Yolların çekirdek kenarına vardığı noktalar: ortadan 4'er birim açılır. */
|
||||||
|
const VARIS_X = [46, 50, 54] as const;
|
||||||
|
const KUTU_BOY = 80;
|
||||||
|
const CEKIRDEK_UST = 168;
|
||||||
|
const CEKIRDEK_ALT = 232;
|
||||||
|
const SAHNE_BOY = 400;
|
||||||
|
|
||||||
|
type Dugum = (typeof DUGUMLER)[number];
|
||||||
|
/** Yolun iki ucu: kutunun çekirdeğe bakan kenarının ortası → çekirdek kenarı. */
|
||||||
|
const uclar = (d: Dugum) => {
|
||||||
|
const ustte = d.kaynak === "sen";
|
||||||
return {
|
return {
|
||||||
x: yuvarla(50 + Math.cos(rad) * r),
|
x0: SUTUN_X[d.sutun],
|
||||||
y: yuvarla(50 + Math.sin(rad) * r),
|
x1: VARIS_X[d.sutun],
|
||||||
|
y0: ustte ? KUTU_BOY : SAHNE_BOY - KUTU_BOY,
|
||||||
|
y1: ustte ? CEKIRDEK_UST : CEKIRDEK_ALT,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/** Arc'ın yolu: iki ucu dikey çıkan S kıvrımı (kontrol noktaları orta yükseklikte). */
|
||||||
|
const yol = (d: Dugum) => {
|
||||||
|
const { x0, x1, y0, y1 } = uclar(d);
|
||||||
|
const ym = (y0 + y1) / 2;
|
||||||
|
return `M${x0} ${y0} C${x0} ${ym} ${x1} ${ym} ${x1} ${y1}`;
|
||||||
|
};
|
||||||
|
/** Kaydın rengi: öğrencinin seçimi turuncu, YÖK Atlas verisi mavi (§2.2). */
|
||||||
|
const renk = (kaynak: Dugum["kaynak"]) =>
|
||||||
|
kaynak === "sen" ? "var(--color-orange-500)" : "var(--primary)";
|
||||||
|
/** Renk ve döngüdeki kalkış anı; kayda, kutu vurgusuna ve çekirdek nabzına gider. */
|
||||||
|
const kayitStili = (i: number) =>
|
||||||
|
({
|
||||||
|
"--renk": renk(DUGUMLER[i].kaynak),
|
||||||
|
"--g": `${KALKIS_SIRASI.indexOf(i) * KALKIS_ARALIGI}s`,
|
||||||
|
}) as CSSProperties;
|
||||||
|
/** Kaydın iki ucu CSS'e: x cqw, y rem. */
|
||||||
|
const ucStili = (d: Dugum) => {
|
||||||
|
const { x0, x1, y0, y1 } = uclar(d);
|
||||||
|
return {
|
||||||
|
"--x0": `${x0}cqw`,
|
||||||
|
"--x1": `${x1}cqw`,
|
||||||
|
"--y0": `${y0 / 16}rem`,
|
||||||
|
"--y1": `${y1 / 16}rem`,
|
||||||
|
} as CSSProperties;
|
||||||
|
};
|
||||||
|
|
||||||
export function VeriAkisi({ className }: { className?: string }) {
|
export function VeriAkisi({ className }: { className?: string }) {
|
||||||
const kapRef = useRef<HTMLDivElement>(null);
|
const kapRef = useRef<HTMLDivElement>(null);
|
||||||
const [oynuyor, setOynuyor] = useState(false);
|
const [oynuyor, setOynuyor] = useState(false);
|
||||||
|
|
||||||
// Ekran dışındayken akış da işaret de durur (kareler CSS'te `data-oynuyor`
|
// Ekran dışındayken akış da işaret de durur (kayıtlar CSS'te `data-oynuyor`
|
||||||
// ile, işaret `oynat` ile).
|
// ile, işaret `oynat` ile).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const kap = kapRef.current;
|
const kap = kapRef.current;
|
||||||
@@ -82,94 +127,112 @@ export function VeriAkisi({ className }: { className?: string }) {
|
|||||||
ref={kapRef}
|
ref={kapRef}
|
||||||
data-oynuyor={oynuyor ? "" : undefined}
|
data-oynuyor={oynuyor ? "" : undefined}
|
||||||
role="img"
|
role="img"
|
||||||
aria-label="Listen altı şeyden kurulur: senin sıralaman, ilgi alanların ve şehir tercihin; YÖK Atlas'ın taban sıralamaları, kontenjanları ve doluluk oranları."
|
aria-label="Listen altı şeyden kurulur ve danışmanda birleşir: senin sıralaman, ilgi alanların ve şehir tercihin; YÖK Atlas'ın taban sıralamaları, kontenjanları ve doluluk oranları."
|
||||||
className="group/akis relative aspect-square w-full"
|
className="kt-akis-sahne group/akis @container relative w-full"
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 100 100" className="absolute inset-0 size-full" aria-hidden>
|
{/* Yollar: x kabın yüzdesi, y rem; çizgi kalınlığı ölçeklenmez */}
|
||||||
{DUGUMLER.map((d, i) => {
|
<svg
|
||||||
const dugum = nokta(d.aci, YARICAP);
|
viewBox={`0 0 100 ${SAHNE_BOY}`}
|
||||||
const dis = nokta(d.aci, YARICAP - DIS_BOSLUK);
|
preserveAspectRatio="none"
|
||||||
const ic = nokta(d.aci, IC_BOSLUK);
|
className="absolute inset-0 size-full"
|
||||||
return (
|
aria-hidden
|
||||||
<g key={d.etiket}>
|
>
|
||||||
{/* Düz çizgi: karodan merkeze; uçları karoların altında */}
|
{DUGUMLER.map((d) => (
|
||||||
<line
|
<path
|
||||||
x1={dugum.x}
|
key={d.etiket}
|
||||||
y1={dugum.y}
|
d={yol(d)}
|
||||||
x2={50}
|
fill="none"
|
||||||
y2={50}
|
|
||||||
stroke="var(--color-slate-300)"
|
stroke="var(--color-slate-300)"
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
vectorEffect="non-scaling-stroke"
|
vectorEffect="non-scaling-stroke"
|
||||||
/>
|
/>
|
||||||
<rect
|
))}
|
||||||
x={-KARE / 2}
|
|
||||||
y={-KARE / 2}
|
|
||||||
width={KARE}
|
|
||||||
height={KARE}
|
|
||||||
rx={0.6}
|
|
||||||
className={cn(
|
|
||||||
"kt-akis-kare",
|
|
||||||
d.kaynak === "sen" ? "fill-orange-500" : "fill-primary",
|
|
||||||
)}
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"--x1": `${dis.x}px`,
|
|
||||||
"--y1": `${dis.y}px`,
|
|
||||||
"--x2": `${ic.x}px`,
|
|
||||||
"--y2": `${ic.y}px`,
|
|
||||||
// Sırayla değil, dağınık kalkış: 0 · 1,1 · 0,5 · 1,6 · 0,3 · 1,3 sn
|
|
||||||
animationDelay: `${[0, 1.1, 0.5, 1.6, 0.3, 1.3][i]}s`,
|
|
||||||
} as CSSProperties
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
{DUGUMLER.map((d) => {
|
{DUGUMLER.map((d, i) => (
|
||||||
const p = nokta(d.aci, YARICAP);
|
// Kutu (Arc "chip"): ikon üstte, etiket altta, tek beyaz yüzey
|
||||||
const ustte = d.aci < 0 || d.aci > 180;
|
|
||||||
return (
|
|
||||||
<div
|
<div
|
||||||
key={d.etiket}
|
key={d.etiket}
|
||||||
className="absolute flex -translate-x-1/2 -translate-y-1/2 flex-col items-center"
|
className={cn(
|
||||||
style={{ left: `${p.x}%`, top: `${p.y}%` }}
|
"absolute flex h-20 w-[30%] -translate-x-1/2 flex-col items-center justify-center gap-1.5 rounded-2xl border border-slate-200 bg-card px-1.5",
|
||||||
|
d.kaynak === "sen" ? "top-0" : "bottom-0",
|
||||||
|
)}
|
||||||
|
style={{ left: `${SUTUN_X[d.sutun]}%` }}
|
||||||
>
|
>
|
||||||
{/* Beyaz, dolu karo (çizgi altından geçer); yanı ikonun rengi söyler */}
|
{/* Kalkış vurgusu: kayıt yola çıkarken çerçeve onun renginde kısa bir an yanar */}
|
||||||
<span className="flex size-14 items-center justify-center rounded-2xl border border-slate-200 bg-card">
|
<span
|
||||||
|
className="kt-akis-vurgu pointer-events-none absolute -inset-px rounded-2xl border"
|
||||||
|
style={kayitStili(i)}
|
||||||
|
/>
|
||||||
<d.ikon
|
<d.ikon
|
||||||
className={cn("size-6", d.kaynak === "sen" ? "text-orange-500" : "text-primary")}
|
className={cn(
|
||||||
|
"size-5 shrink-0",
|
||||||
|
d.kaynak === "sen" ? "text-orange-500" : "text-primary",
|
||||||
|
)}
|
||||||
aria-hidden
|
aria-hidden
|
||||||
/>
|
/>
|
||||||
</span>
|
<span className="text-center text-xs font-semibold leading-tight text-balance text-slate-700">
|
||||||
{/* Üstteki iki düğümde etiket karonun üstünde: altında çizgi merkeze iniyor */}
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"absolute w-20 text-center text-xs font-semibold leading-tight text-slate-700",
|
|
||||||
ustte ? "bottom-full mb-1.5" : "top-full mt-1.5",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{d.etiket}
|
{d.etiket}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
))}
|
||||||
})}
|
|
||||||
|
|
||||||
<div className="absolute top-1/2 left-1/2 flex size-24 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-3xl border border-slate-200 bg-card">
|
{/* Çekirdek: danışman panelinin başlığı (DanismanBaslik deseni) */}
|
||||||
{/* Danışmanın nabzı, aralıklarla çıktıya dönüşür (liste → trend →
|
<div className="absolute top-[10.5rem] left-1/2 flex h-16 -translate-x-1/2 items-center gap-1 rounded-2xl border border-slate-200 bg-card pr-5 pl-4">
|
||||||
sohbet) ve logoya toplanır; yalnız ekrandayken oynar. */}
|
{/* Varış nabzı: kayıt içeri alınınca çerçeve onun renginde yanar,
|
||||||
|
genişleyerek söner. Her kaydın kendi nabzı, aynı gecikmeyle. */}
|
||||||
|
{DUGUMLER.map((d, i) => (
|
||||||
|
<span
|
||||||
|
key={d.etiket}
|
||||||
|
className="kt-akis-nabiz pointer-events-none absolute -inset-px rounded-2xl border"
|
||||||
|
style={kayitStili(i)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
<AnimasyonluMark
|
<AnimasyonluMark
|
||||||
oynat={oynuyor}
|
oynat={oynuyor}
|
||||||
varyant="nabiz"
|
varyant="nabiz"
|
||||||
|
renk="turuncu"
|
||||||
bicimler={CIKTILAR}
|
bicimler={CIKTILAR}
|
||||||
className="size-14"
|
className="size-7"
|
||||||
/>
|
/>
|
||||||
|
<span className="font-bricolage text-xl tracking-tighter whitespace-nowrap text-foreground">
|
||||||
|
<span className="font-light">Danışmanla</span>
|
||||||
|
<span className="font-semibold">Konuş</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Kayıtlar kutuların üstünde: porttan doğar, çekirdek kenarında söner.
|
||||||
|
Yatay ve dikey kayma ayrı öğelerde; ikisi birlikte yolun eğrisini çizer. */}
|
||||||
|
{DUGUMLER.map((d, i) => (
|
||||||
|
<span
|
||||||
|
key={d.etiket}
|
||||||
|
aria-hidden
|
||||||
|
className="kt-akis-kayit-kap"
|
||||||
|
style={{ ...kayitStili(i), ...ucStili(d) }}
|
||||||
|
>
|
||||||
|
<span className="kt-akis-port">
|
||||||
|
<span className="kt-akis-vurgu" />
|
||||||
|
</span>
|
||||||
|
{/* İz: kaydın ardından aynı eğriyi biraz gecikmeyle izleyen iki nokta */}
|
||||||
|
{IZ_GECIKMELERI.map((ek) => (
|
||||||
|
<span key={ek} className="kt-akis-x" style={{ "--ek": `${ek}s` } as CSSProperties}>
|
||||||
|
<span className="kt-akis-y">
|
||||||
|
<span className="kt-akis-iz" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
<span className="kt-akis-x">
|
||||||
|
<span className="kt-akis-y">
|
||||||
|
<span className="kt-akis-kare">
|
||||||
|
<span className="kt-akis-dolgu" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<figcaption className="mt-8 text-center text-sm leading-relaxed text-slate-500">
|
<figcaption className="mt-8 text-center text-sm leading-relaxed text-slate-500">
|
||||||
Solda senin seçimlerin, sağda YÖK Atlas verisi. Yapay zekâ yalnız bu
|
Üstte senin seçimlerin, altta YÖK Atlas verisi. Yapay zekâ yalnız bu
|
||||||
ikisinin kesiştiği programlardan seçer.
|
ikisinin kesiştiği programlardan seçer.
|
||||||
</figcaption>
|
</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|||||||
@@ -68,6 +68,18 @@ export const KONTENJAN_TURU_ADI: Record<KontenjanTuruAnahtari, string> = {
|
|||||||
kadin34: "34 yaş üstü kadın",
|
kadin34: "34 yaş üstü kadın",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kontenjan türü → kimin başvurabildiği (çekmecedeki satırın alt satırı).
|
||||||
|
* Tam şartlar ÖSYM kılavuzundadır; burası tek satırlık tarif.
|
||||||
|
*/
|
||||||
|
export const KONTENJAN_TURU_KIME: Record<KontenjanTuruAnahtari, string> = {
|
||||||
|
genel: "Sıralaması yeten her aday",
|
||||||
|
okulBirincisi: "Lisesini bu yıl birincilikle bitirenler",
|
||||||
|
sehitGaziYakini: "Şehit yakınları, gaziler ve gazi yakınları",
|
||||||
|
depremzede: "6 Şubat 2023 depremlerinden etkilenen adaylar",
|
||||||
|
kadin34: "34 yaşını doldurmuş kadın adaylar",
|
||||||
|
};
|
||||||
|
|
||||||
/** Bilinmeyen değerleri atar, tekrarları birleştirir; sıra sabit listeye göre. */
|
/** Bilinmeyen değerleri atar, tekrarları birleştirir; sıra sabit listeye göre. */
|
||||||
export function ozelDurumDogrula(girdi: unknown): OzelDurum[] {
|
export function ozelDurumDogrula(girdi: unknown): OzelDurum[] {
|
||||||
if (!Array.isArray(girdi)) return [];
|
if (!Array.isArray(girdi)) return [];
|
||||||
|
|||||||
Reference in New Issue
Block a user