feat(program-ayrinti): açılışta logo kareleri başlığa değil gösterge karelerine dönüşür
Logonun üç karesi içerikteki kart alanlarına (kontenjan, netler, istihdam) uçup onların boyuna büyür; kart kareden belirir. Başlık ilk andan görünür. Hedefi olmayan kare olduğu yerde söner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,9 +9,10 @@
|
||||
// - Kartsız (program-ayrinti.tsx, §4.19 "kart yok"): `[data-kare]` yok,
|
||||
// `[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
|
||||
// `[data-sonra]` bloklar netleşerek gelir. sahnenin kardeşi bir `[data-logo-hedef]` varsa kareler
|
||||
// sönmek yerine küçülerek o yazıya (çekmece başlığı) uçar, yazı onlardan
|
||||
// netleşir (Bilal, 10 Eki 2026).
|
||||
// `[data-sonra]` bloklar netleşerek gelir. İçerikte `[data-logo-hedef]`
|
||||
// kart alanları varsa kareler sönmek yerine aynı adlı karta uçar ve onun
|
||||
// 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
|
||||
// bekleyen çekmecede (`hazir` verilmiş) sahne yalnız veri ilk geldiğinde
|
||||
// (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,
|
||||
1,
|
||||
];
|
||||
// Hedefli kartsız mod: kareler sönmek yerine LOGO_UCUS süresince hedef yazıya
|
||||
// uçar; uçuşun son yarısında solar, yazı o sırada netleşir.
|
||||
// Hedefli kartsız mod: kareler sönmek yerine LOGO_UCUS süresince hedef karta
|
||||
// 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 UCUS_SURE = LOGO_BELIRME + LOGO_DURUS + LOGO_UCUS;
|
||||
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
|
||||
* (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ı
|
||||
* (sahnenin kardeşi, ör. çekmece başlığı). Kareler yazının üç eşit dilimine
|
||||
* uçar; yazı sahne başında görünmez olur, kareler varırken netleşir.
|
||||
* `data-logo-hedef="sol|sag-ust|sag-alt"`: kartsız modda o logo karesinin
|
||||
* dönüşeceği kart alanı (içerikte, ör. gösterge karesi). Kare kartın yerine
|
||||
* 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({
|
||||
children,
|
||||
@@ -224,15 +226,12 @@ export function AcilisSahnesi({
|
||||
// iner ki altında boşluk kalmasın; içerik KARTSIZ_ICERIK_BASI'ndan
|
||||
// itibaren, sönmeyle çaprazlanarak gelir.
|
||||
let sonKare = 0;
|
||||
// Hedef yazının kutusu (blok değil, yazının kendisi: Range).
|
||||
const hedef =
|
||||
kap.parentElement?.querySelector<HTMLElement>("[data-logo-hedef]") ?? null;
|
||||
let hedefKutu: DOMRect | null = null;
|
||||
if (hedef) {
|
||||
const aralik = document.createRange();
|
||||
aralik.selectNodeContents(hedef);
|
||||
hedefKutu = aralik.getBoundingClientRect();
|
||||
}
|
||||
const hedefler = new Map<string, HTMLElement>();
|
||||
kap
|
||||
.querySelectorAll<HTMLElement>("[data-logo-hedef]")
|
||||
.forEach((el) => hedefler.set(el.dataset.logoHedef ?? "", el));
|
||||
// Logo kabı sıfır boya inince kartlar bu kadar yukarı kayar.
|
||||
const logoBoyu = logo!.offsetHeight;
|
||||
logoKareleri.forEach((el) => {
|
||||
const slot = (el.dataset.logoKare ?? "sol") as Slot;
|
||||
const sira = SLOT_SIRASI.indexOf(slot);
|
||||
@@ -240,16 +239,31 @@ export function AcilisSahnesi({
|
||||
sonKare = Math.max(sonKare, gecikme);
|
||||
el.style.opacity = "0";
|
||||
dokunulan.push(el);
|
||||
if (hedefKutu) {
|
||||
const hedef = hedefler.get(slot);
|
||||
if (hedef) {
|
||||
// Kare ile hedef aynı çekmecede: çekmece kayarken fark değişmez.
|
||||
const k = el.getBoundingClientRect();
|
||||
const dx =
|
||||
hedefKutu.left + (hedefKutu.width * (sira + 0.5)) / 3 - (k.left + k.width / 2);
|
||||
const dy = hedefKutu.top + hedefKutu.height / 2 - (k.top + k.height / 2);
|
||||
const h = hedef.getBoundingClientRect();
|
||||
const dx = h.left + h.width / 2 - (k.left + k.width / 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(
|
||||
animate(
|
||||
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,
|
||||
delay: gecikme,
|
||||
@@ -267,6 +281,11 @@ export function AcilisSahnesi({
|
||||
ease: [GUCLU_CIKIS, "linear", "easeIn"],
|
||||
},
|
||||
),
|
||||
animate(
|
||||
hedef,
|
||||
{ opacity: [0, 1] },
|
||||
{ duration: LOGO_UCUS * 0.5, delay: ucusBasi + LOGO_UCUS * 0.4, ease: "easeOut" },
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -288,22 +307,21 @@ export function AcilisSahnesi({
|
||||
});
|
||||
const sonmeBasi = sonKare + LOGO_SURE * LOGO_ZAMANLARI[2];
|
||||
dokunulan.push(logo!);
|
||||
if (hedef && hedefKutu) {
|
||||
netlestir(hedef, sonmeBasi + LOGO_UCUS * 0.4, 0.4);
|
||||
}
|
||||
const kapat = animate(
|
||||
logo!,
|
||||
{ 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(() => {
|
||||
logo!.style.display = "none";
|
||||
});
|
||||
kontroller.push(kapat);
|
||||
|
||||
sonra.forEach((el, i) =>
|
||||
netlestir(el, KARTSIZ_ICERIK_BASI + i * KARTSIZ_BLOK_ADIMI, KARTSIZ_NETLESME),
|
||||
);
|
||||
sonra.forEach((el, i) => {
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -197,15 +197,7 @@ export function ProgramAyrintiButonu({
|
||||
<span className="sr-only">Kapat</span>
|
||||
</AltSayfaKapat>
|
||||
<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
|
||||
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",
|
||||
)}
|
||||
>
|
||||
<AltSayfaBaslik className="px-10 text-center font-heading text-2xl font-bold text-slate-900">
|
||||
{isim}
|
||||
</AltSayfaBaslik>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
// Sıra = AcilisSahnesi'nin `data-sonra` sırası: logo küçülerek başlığa
|
||||
// dönüşür, bloklar yukarıdan aşağıya 0,05 s arayla netleşir.
|
||||
// Sıra = AcilisSahnesi'nin `data-sonra` sırası: logo kareleri gösterge
|
||||
// karelerine dönüşür, bloklar yukarıdan aşağıya 0,05 s arayla netleşir.
|
||||
return (
|
||||
<>
|
||||
{/* Logo kareleri: sahne sırasında görünür, küçülerek başlığa uçar; kap
|
||||
sıfır boya iner. */}
|
||||
{/* Logo kareleri: sahne sırasında görünür, aşağıdaki gösterge karelerine
|
||||
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>
|
||||
{LOGO_SLOTLARI.map((slot) => (
|
||||
<span
|
||||
@@ -589,9 +582,15 @@ function BolumKutulari({ veri }: { veri: ProgramAyrintisi }) {
|
||||
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) => (
|
||||
<motion.button
|
||||
<div
|
||||
key={x.anahtar}
|
||||
data-logo-hedef={LOGO_SLOTLARI[kutular.indexOf(x)]}
|
||||
className="rounded-3xl"
|
||||
>
|
||||
<motion.button
|
||||
ref={(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 />
|
||||
<span className="sr-only"> — {x.baslik}, tamamını göster</span>
|
||||
</motion.button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user