merge(main): 2.0.1 üzerine Turnstile bot koruması (→ 2.0.2)
All checks were successful
Deploy / deploy (push) Successful in 12m39s
All checks were successful
Deploy / deploy (push) Successful in 12m39s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -723,8 +723,13 @@ sihirbaz modalı içindir; yeni bir ayrıntı için `Dialog` açılmaz.
|
|||||||
altında yan yana durur, dokununca yer değiştirir. Odak dönüşen öğeyle gider (açılınca çarpı,
|
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
|
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
|
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 →
|
kalktı; koşullar, üniversite ve kaynak satırı kartsız kalır.
|
||||||
başlık); kareler tek `data-sonra` bloğu olarak netleşir.
|
- **11 Eki 2026 — logo → gösterge kareleri:** Bilal: "logonun dönüştüğü başlık olmamalı, içerikteki
|
||||||
|
kart alanları olmalı." Logonun üç karesi artık başlığa değil, aşağıdaki gösterge karelerine uçar
|
||||||
|
ve onların boyuna büyür (`data-logo-hedef="sol|sag-ust|sag-alt"`, `AcilisSahnesi` sözleşmesi;
|
||||||
|
kareler sırayla sol / sağ üst / sağ alt logo karesini alır); kare uçuşun son yarısında solar,
|
||||||
|
kart o sırada belirir. Hedefi olmayan logo karesi (verisi olmayan bölüm) olduğu yerde söner.
|
||||||
|
Başlık ilk andan görünür. 10 Ekim'deki logo → başlık dönüşümü kalktı.
|
||||||
- **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
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kolaytercih",
|
"name": "kolaytercih",
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
@@ -27,12 +27,20 @@ const SURUMLER: {
|
|||||||
arayuz?: string[];
|
arayuz?: string[];
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
surum: "2.0.1",
|
surum: "2.0.2",
|
||||||
tarih: "11 Ekim 2026",
|
tarih: "11 Ekim 2026",
|
||||||
maddeler: [
|
maddeler: [
|
||||||
"Geri bildirim kutusu otomatik gönderimlere karşı korunuyor; çoğu zaman fark etmezsin, nadiren tek tıkla onay istenir.",
|
"Geri bildirim kutusu otomatik gönderimlere karşı korunuyor; çoğu zaman fark etmezsin, nadiren tek tıkla onay istenir.",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
surum: "2.0.1",
|
||||||
|
tarih: "11 Ekim 2026",
|
||||||
|
maddeler: [],
|
||||||
|
arayuz: [
|
||||||
|
"Program ayrıntısı açılırken logo kareleri başlığa değil, kontenjan, netler ve mezun istihdamı karelerine dönüşür.",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
surum: "2.0.0",
|
surum: "2.0.0",
|
||||||
tarih: "11 Ekim 2026",
|
tarih: "11 Ekim 2026",
|
||||||
|
|||||||
@@ -9,9 +9,10 @@
|
|||||||
// - Kartsız (program-ayrinti.tsx, §4.19 "kart yok"): `[data-kare]` yok,
|
// - Kartsız (program-ayrinti.tsx, §4.19 "kart yok"): `[data-kare]` yok,
|
||||||
// `[data-logo]` kabındaki üç `[data-logo-kare]` belirir, kısa durur, sonra
|
// `[data-logo]` kabındaki üç `[data-logo-kare]` belirir, kısa durur, sonra
|
||||||
// sönerek kaybolur (scale 0.6 + opacity 0; Bilal, 7 Eki 2026) ve
|
// sönerek kaybolur (scale 0.6 + opacity 0; Bilal, 7 Eki 2026) ve
|
||||||
// `[data-sonra]` bloklar netleşerek gelir. sahnenin kardeşi bir `[data-logo-hedef]` varsa kareler
|
// `[data-sonra]` bloklar netleşerek gelir. İçerikte `[data-logo-hedef]`
|
||||||
// sönmek yerine küçülerek o yazıya (çekmece başlığı) uçar, yazı onlardan
|
// kart alanları varsa kareler sönmek yerine aynı adlı karta uçar ve onun
|
||||||
// netleşir (Bilal, 10 Eki 2026).
|
// boyuna büyür; kart kareden belirir (Bilal, 11 Eki 2026; önce çekmece
|
||||||
|
// başlığına dönüşüyordu).
|
||||||
// "Hareketi azalt"ta logo ve büyüme yok, yalnız 0,25 s solma (§6). Veri
|
// "Hareketi azalt"ta logo ve büyüme yok, yalnız 0,25 s solma (§6). Veri
|
||||||
// bekleyen çekmecede (`hazir` verilmiş) sahne yalnız veri ilk geldiğinde
|
// bekleyen çekmecede (`hazir` verilmiş) sahne yalnız veri ilk geldiğinde
|
||||||
// (false → true) oynar; önbellekli yeniden açılışta aynı 0,25 s solma
|
// (false → true) oynar; önbellekli yeniden açılışta aynı 0,25 s solma
|
||||||
@@ -58,8 +59,8 @@ const LOGO_ZAMANLARI = [
|
|||||||
(LOGO_BELIRME + LOGO_DURUS) / LOGO_SURE,
|
(LOGO_BELIRME + LOGO_DURUS) / LOGO_SURE,
|
||||||
1,
|
1,
|
||||||
];
|
];
|
||||||
// Hedefli kartsız mod: kareler sönmek yerine LOGO_UCUS süresince hedef yazıya
|
// Hedefli kartsız mod: kareler sönmek yerine LOGO_UCUS süresince hedef karta
|
||||||
// uçar; uçuşun son yarısında solar, yazı o sırada netleşir.
|
// uçup onun boyuna büyür; uçuşun son yarısında solar, kart o sırada belirir.
|
||||||
const LOGO_UCUS = 0.45;
|
const LOGO_UCUS = 0.45;
|
||||||
const UCUS_SURE = LOGO_BELIRME + LOGO_DURUS + LOGO_UCUS;
|
const UCUS_SURE = LOGO_BELIRME + LOGO_DURUS + LOGO_UCUS;
|
||||||
const UCUS_ZAMANLARI = [
|
const UCUS_ZAMANLARI = [
|
||||||
@@ -127,9 +128,10 @@ export function logoKareStili(slot: Slot, kapBoyu = LOGO_KAP_BOYU) {
|
|||||||
* çekmece daha ekrana kaymadan oynuyor, gövde boş kalıyordu. Verilmezse
|
* çekmece daha ekrana kaymadan oynuyor, gövde boş kalıyordu. Verilmezse
|
||||||
* (içerik sunucudan hazır gelen çekmece) sahne her montajda oynar.
|
* (içerik sunucudan hazır gelen çekmece) sahne her montajda oynar.
|
||||||
*
|
*
|
||||||
* `data-logo-hedef`: kartsız modda logo karelerinin küçülerek dönüşeceği yazı
|
* `data-logo-hedef="sol|sag-ust|sag-alt"`: kartsız modda o logo karesinin
|
||||||
* (sahnenin kardeşi, ör. çekmece başlığı). Kareler yazının üç eşit dilimine
|
* dönüşeceği kart alanı (içerikte, ör. gösterge karesi). Kare kartın yerine
|
||||||
* uçar; yazı sahne başında görünmez olur, kareler varırken netleşir.
|
* uçup onun boyuna büyür; kart sahne başında görünmez olur, kare varırken
|
||||||
|
* belirir. Hedefi olmayan kare olduğu yerde söner.
|
||||||
*/
|
*/
|
||||||
export function AcilisSahnesi({
|
export function AcilisSahnesi({
|
||||||
children,
|
children,
|
||||||
@@ -224,15 +226,12 @@ export function AcilisSahnesi({
|
|||||||
// iner ki altında boşluk kalmasın; içerik KARTSIZ_ICERIK_BASI'ndan
|
// iner ki altında boşluk kalmasın; içerik KARTSIZ_ICERIK_BASI'ndan
|
||||||
// itibaren, sönmeyle çaprazlanarak gelir.
|
// itibaren, sönmeyle çaprazlanarak gelir.
|
||||||
let sonKare = 0;
|
let sonKare = 0;
|
||||||
// Hedef yazının kutusu (blok değil, yazının kendisi: Range).
|
const hedefler = new Map<string, HTMLElement>();
|
||||||
const hedef =
|
kap
|
||||||
kap.parentElement?.querySelector<HTMLElement>("[data-logo-hedef]") ?? null;
|
.querySelectorAll<HTMLElement>("[data-logo-hedef]")
|
||||||
let hedefKutu: DOMRect | null = null;
|
.forEach((el) => hedefler.set(el.dataset.logoHedef ?? "", el));
|
||||||
if (hedef) {
|
// Logo kabı sıfır boya inince kartlar bu kadar yukarı kayar.
|
||||||
const aralik = document.createRange();
|
const logoBoyu = logo!.offsetHeight;
|
||||||
aralik.selectNodeContents(hedef);
|
|
||||||
hedefKutu = aralik.getBoundingClientRect();
|
|
||||||
}
|
|
||||||
logoKareleri.forEach((el) => {
|
logoKareleri.forEach((el) => {
|
||||||
const slot = (el.dataset.logoKare ?? "sol") as Slot;
|
const slot = (el.dataset.logoKare ?? "sol") as Slot;
|
||||||
const sira = SLOT_SIRASI.indexOf(slot);
|
const sira = SLOT_SIRASI.indexOf(slot);
|
||||||
@@ -240,16 +239,31 @@ export function AcilisSahnesi({
|
|||||||
sonKare = Math.max(sonKare, gecikme);
|
sonKare = Math.max(sonKare, gecikme);
|
||||||
el.style.opacity = "0";
|
el.style.opacity = "0";
|
||||||
dokunulan.push(el);
|
dokunulan.push(el);
|
||||||
if (hedefKutu) {
|
const hedef = hedefler.get(slot);
|
||||||
|
if (hedef) {
|
||||||
// Kare ile hedef aynı çekmecede: çekmece kayarken fark değişmez.
|
// Kare ile hedef aynı çekmecede: çekmece kayarken fark değişmez.
|
||||||
const k = el.getBoundingClientRect();
|
const k = el.getBoundingClientRect();
|
||||||
const dx =
|
const h = hedef.getBoundingClientRect();
|
||||||
hedefKutu.left + (hedefKutu.width * (sira + 0.5)) / 3 - (k.left + k.width / 2);
|
const dx = h.left + h.width / 2 - (k.left + k.width / 2);
|
||||||
const dy = hedefKutu.top + hedefKutu.height / 2 - (k.top + k.height / 2);
|
const dy = h.top + h.height / 2 - logoBoyu - (k.top + k.height / 2);
|
||||||
|
const sx = h.width / k.width;
|
||||||
|
const sy = h.height / k.height;
|
||||||
|
// Ölçek köşeyi de büyütür; kartın köşesi için ters ölçekli elips.
|
||||||
|
const kose = getComputedStyle(el).borderTopLeftRadius;
|
||||||
|
const kartKose = parseFloat(getComputedStyle(hedef).borderTopLeftRadius) || 0;
|
||||||
|
const ucusBasi = gecikme + LOGO_BELIRME + LOGO_DURUS;
|
||||||
|
hedef.style.opacity = "0";
|
||||||
|
dokunulan.push(hedef);
|
||||||
kontroller.push(
|
kontroller.push(
|
||||||
animate(
|
animate(
|
||||||
el,
|
el,
|
||||||
{ x: [0, 0, 0, dx], y: [0, 0, 0, dy], scale: [0.4, 1, 1, 0.2] },
|
{
|
||||||
|
x: [0, 0, 0, dx],
|
||||||
|
y: [0, 0, 0, dy],
|
||||||
|
scaleX: [0.4, 1, 1, sx],
|
||||||
|
scaleY: [0.4, 1, 1, sy],
|
||||||
|
borderRadius: [kose, kose, kose, `${kartKose / sx}px / ${kartKose / sy}px`],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
duration: UCUS_SURE,
|
duration: UCUS_SURE,
|
||||||
delay: gecikme,
|
delay: gecikme,
|
||||||
@@ -267,6 +281,11 @@ export function AcilisSahnesi({
|
|||||||
ease: [GUCLU_CIKIS, "linear", "easeIn"],
|
ease: [GUCLU_CIKIS, "linear", "easeIn"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
animate(
|
||||||
|
hedef,
|
||||||
|
{ opacity: [0, 1] },
|
||||||
|
{ duration: LOGO_UCUS * 0.5, delay: ucusBasi + LOGO_UCUS * 0.4, ease: "easeOut" },
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -288,22 +307,21 @@ export function AcilisSahnesi({
|
|||||||
});
|
});
|
||||||
const sonmeBasi = sonKare + LOGO_SURE * LOGO_ZAMANLARI[2];
|
const sonmeBasi = sonKare + LOGO_SURE * LOGO_ZAMANLARI[2];
|
||||||
dokunulan.push(logo!);
|
dokunulan.push(logo!);
|
||||||
if (hedef && hedefKutu) {
|
|
||||||
netlestir(hedef, sonmeBasi + LOGO_UCUS * 0.4, 0.4);
|
|
||||||
}
|
|
||||||
const kapat = animate(
|
const kapat = animate(
|
||||||
logo!,
|
logo!,
|
||||||
{ height: [logo!.offsetHeight, 0] },
|
{ height: [logo!.offsetHeight, 0] },
|
||||||
{ duration: hedefKutu ? LOGO_UCUS : LOGO_SONME, delay: sonmeBasi, ease: YERLESME },
|
{ duration: hedefler.size > 0 ? LOGO_UCUS : LOGO_SONME, delay: sonmeBasi, ease: YERLESME },
|
||||||
);
|
);
|
||||||
kapat.then(() => {
|
kapat.then(() => {
|
||||||
logo!.style.display = "none";
|
logo!.style.display = "none";
|
||||||
});
|
});
|
||||||
kontroller.push(kapat);
|
kontroller.push(kapat);
|
||||||
|
|
||||||
sonra.forEach((el, i) =>
|
sonra.forEach((el, i) => {
|
||||||
netlestir(el, KARTSIZ_ICERIK_BASI + i * KARTSIZ_BLOK_ADIMI, KARTSIZ_NETLESME),
|
// Hedef kartları taşıyan blok kendi netleşmez: kartlar karelerden belirir.
|
||||||
);
|
if (el.querySelector("[data-logo-hedef]")) return;
|
||||||
|
netlestir(el, KARTSIZ_ICERIK_BASI + i * KARTSIZ_BLOK_ADIMI, KARTSIZ_NETLESME);
|
||||||
|
});
|
||||||
return temizle;
|
return temizle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,15 +197,7 @@ export function ProgramAyrintiButonu({
|
|||||||
<span className="sr-only">Kapat</span>
|
<span className="sr-only">Kapat</span>
|
||||||
</AltSayfaKapat>
|
</AltSayfaKapat>
|
||||||
<AltSayfaGovde className="px-4 pt-3 pb-[max(1.25rem,env(safe-area-inset-bottom))] sm:px-6">
|
<AltSayfaGovde className="px-4 pt-3 pb-[max(1.25rem,env(safe-area-inset-bottom))] sm:px-6">
|
||||||
{/* Başlık veri gelene dek görünmez: logo kareleri küçülüp buraya
|
<AltSayfaBaslik className="px-10 text-center font-heading text-2xl font-bold text-slate-900">
|
||||||
uçar, yazı onlardan netleşir (AcilisSahnesi `data-logo-hedef`). */}
|
|
||||||
<AltSayfaBaslik
|
|
||||||
data-logo-hedef
|
|
||||||
className={cn(
|
|
||||||
"px-10 text-center font-heading text-2xl font-bold text-slate-900",
|
|
||||||
(durum === "bekliyor" || durum === "yukleniyor") && "opacity-0",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{isim}
|
{isim}
|
||||||
</AltSayfaBaslik>
|
</AltSayfaBaslik>
|
||||||
<AltSayfaAciklama className="mx-auto mt-1 max-w-md px-4 text-center text-sm text-slate-500">
|
<AltSayfaAciklama className="mx-auto mt-1 max-w-md px-4 text-center text-sm text-slate-500">
|
||||||
@@ -248,12 +240,13 @@ function AyrintiIcerik({ durum, veri }: { durum: Durum; veri: ProgramAyrintisi |
|
|||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Sıra = AcilisSahnesi'nin `data-sonra` sırası: logo küçülerek başlığa
|
// Sıra = AcilisSahnesi'nin `data-sonra` sırası: logo kareleri gösterge
|
||||||
// dönüşür, bloklar yukarıdan aşağıya 0,05 s arayla netleşir.
|
// karelerine dönüşür, bloklar yukarıdan aşağıya 0,05 s arayla netleşir.
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Logo kareleri: sahne sırasında görünür, küçülerek başlığa uçar; kap
|
{/* Logo kareleri: sahne sırasında görünür, aşağıdaki gösterge karelerine
|
||||||
sıfır boya iner. */}
|
uçup onlara dönüşür (BolumKutulari `data-logo-hedef`); kap sıfır boya
|
||||||
|
iner. */}
|
||||||
<div data-logo className="relative mx-auto size-44" aria-hidden>
|
<div data-logo className="relative mx-auto size-44" aria-hidden>
|
||||||
{LOGO_SLOTLARI.map((slot) => (
|
{LOGO_SLOTLARI.map((slot) => (
|
||||||
<span
|
<span
|
||||||
@@ -589,9 +582,15 @@ function BolumKutulari({ veri }: { veri: ProgramAyrintisi }) {
|
|||||||
setAcik(null);
|
setAcik(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sarmalayıcı: açılış sahnesinde bu kareye dönüşen logo karesinin hedefi
|
||||||
|
// (AcilisSahnesi). Düz öğe; sahne `motion` öğesinin stiline dokunmaz.
|
||||||
const kare = (x: Kutu) => (
|
const kare = (x: Kutu) => (
|
||||||
<motion.button
|
<div
|
||||||
key={x.anahtar}
|
key={x.anahtar}
|
||||||
|
data-logo-hedef={LOGO_SLOTLARI[kutular.indexOf(x)]}
|
||||||
|
className="rounded-3xl"
|
||||||
|
>
|
||||||
|
<motion.button
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
kareRef.current[x.anahtar] = el;
|
kareRef.current[x.anahtar] = el;
|
||||||
}}
|
}}
|
||||||
@@ -618,6 +617,7 @@ function BolumKutulari({ veri }: { veri: ProgramAyrintisi }) {
|
|||||||
<Maximize2 className="absolute top-3 right-3 size-4 text-white/80" aria-hidden />
|
<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>
|
<span className="sr-only"> — {x.baslik}, tamamını göster</span>
|
||||||
</motion.button>
|
</motion.button>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user