@@ -98,6 +98,37 @@ function yer(pozisyon: number, boyut: number): string {
return ` ${ ( x / boyut ) * 100 } % ${ ( y / boyut ) * 100 } % ` ;
}
/** Karenin merkezini 180'lik kutuda (cx, cy) noktası na taşı yan `translate`. */
function merkezde ( cx : number , cy : number , boyut : number ) : string {
return ` ${ ( ( cx - boyut / 2 ) / boyut ) * 100 } % ${ ( ( cy - boyut / 2 ) / boyut ) * 100 } % ` ;
}
// Tema geçişi biçimi (DESIGN.md §2.3): geçiş logodan yayı lı rken kareler
// neye geçildiğini gösterir, kı sa bir an durur ve yeniden logoya toplanı r.
// - Koyuya: şeftali kare hilale, mavi ve turuncu kareler iki küçük yı ldı za.
// - Açı ğa: turuncu kare güneşe, şeftali kare arkası ndaki haleye; mavi söner.
// Toplam süre ve duraklar: %30'da biçim tamam, %70'e kadar durur, sonra döner.
const SEKIL_SURE = 1700 ;
const SEKIL_DURAKLAR = [ 0 , 0.3 , 0.7 , 1 ] ;
type SekilHedefi = { translate : string ; scale : string ; opacity : number } ;
/** Kare `i` için hedef biçim. Kareler sı rayla mavi, şeftali, turuncu. */
function sekilHedefi ( i : number , koyu : boolean ) : SekilHedefi {
const b = KARELER [ i ] . boyut ;
if ( koyu ) {
return [
{ translate : merkezde ( 40 , 50 , b ) , scale : "0.3" , opacity : 1 } , // yı ldı z
{ translate : merkezde ( 100 , 90 , b ) , scale : "1.75" , opacity : 1 } , // hilal
{ translate : merkezde ( 56 , 132 , b ) , scale : "0.2" , opacity : 1 } , // yı ldı z
] [ i ] ;
}
return [
{ translate : merkezde ( 90 , 90 , b ) , scale : "0.3" , opacity : 0 } , // söner
{ translate : merkezde ( 90 , 90 , b ) , scale : "2.15" , opacity : 0.45 } , // hale
{ translate : merkezde ( 90 , 90 , b ) , scale : "1.35" , opacity : 1 } , // güneş
] [ i ] ;
}
/** Kare için durak koordinatları : kendi yeri → sı radaki → ondan sonraki →
* kendi yeri. Duraklarda bekleme için her hedef iki kez tekrarlanı r
* (KONUM_ZAMANLARI ile hizalı ). */
@@ -185,6 +216,7 @@ export function AnimasyonluMark({
const kokRef = useRef < HTMLSpanElement > ( null ) ;
const kontrolRef = useRef < {
ayarla : ( hedef : Varyant | null , cevik? : boolean ) = > void ;
temaSekli : ( koyu : boolean ) = > void ;
} | null > ( null ) ;
// Animasyonları n sahibi: bileşen yaşadı kça bir kez kurulur. Prop değişimi
@@ -198,6 +230,8 @@ export function AnimasyonluMark({
let dongu : Animation [ ] = [ ] ;
let donus : Animation [ ] = [ ] ;
/** Tema geçişi biçimi (güneş/ay) animasyonları ; bkz. `temaSekli`. */
let sekil : Animation [ ] = [ ] ;
/** Şu an dönen döngü ve istenen döngü (`null`: durağan). */
let aktif : Varyant | null = null ;
let hedef : Varyant | null = null ;
@@ -237,6 +271,11 @@ export function AnimasyonluMark({
const ayarla = ( yeni : Varyant | null , cevik = false ) = > {
if ( yeni === hedef ) return ;
hedef = yeni ;
// Hover döngüsü biçimin üstüne binmesin: güneş/ay yarı da kesilir.
if ( yeni !== null ) {
sekil . forEach ( ( a ) = > a . cancel ( ) ) ;
sekil = [ ] ;
}
if ( aktif !== null ) yerles ( cevik ) ;
if ( yeni === null || azHareket . matches ) return ;
// Kareler hâlâ yerine dönüyorsa dönüşün bitmesi beklenir; döngü hep
@@ -250,12 +289,65 @@ export function AnimasyonluMark({
. catch ( ( ) = > { } ) ;
} ;
const kontrol = { ayarla } ;
/**
* Tema geçişi biçimi: kareler güneşe ya da aya dönüşür, durur, logoya
* döner. Döngü oynarken (hover) ya da hareket azaltı lmı şken çalı şmaz.
* Hilal, kareyi kesen ikinci bir şekille değil karenin kendi iç gölgesiyle
* çizilir (zemin şeffaf, renk iç gölgede): arkadaki zemin ne olursa olsun
* doğru görünür.
*/
const temaSekli = ( koyu : boolean ) = > {
if ( aktif !== null || hedef !== null || azHareket . matches ) return ;
sekil . forEach ( ( a ) = > a . cancel ( ) ) ;
sekil = kareler . flatMap ( ( el , i ) = > {
const k = KARELER [ i ] ;
const ev = {
translate : yer ( k . baslangic , k . boyut ) ,
scale : "1" ,
opacity : 1 ,
borderRadius : ` ${ ( 13 / k . boyut ) * 100 } % ` ,
} ;
const bicim = { . . . sekilHedefi ( i , koyu ) , borderRadius : "50%" } ;
const secenek = { duration : SEKIL_SURE } ;
const animasyonlar = [
el . animate (
[
{ . . . ev , easing : DONUS } ,
{ . . . bicim , easing : "linear" } ,
{ . . . bicim , easing : YERLES } ,
ev ,
] . map ( ( kare , d ) = > ( { . . . kare , offset : SEKIL_DURAKLAR [ d ] } ) ) ,
secenek ,
) ,
] ;
// Hilal: yalnı z şeftali kare, yalnı z koyuya geçerken.
if ( koyu && i === 1 ) {
const renk = getComputedStyle ( el ) . backgroundColor ;
const w = el . offsetWidth ;
const dolu = ` inset 0 0 0 ${ w } px ${ renk } ` ;
const hilal = ` inset ${ - 0.36 * w } px ${ 0.1 * w } px 0 0 ${ renk } ` ;
animasyonlar . push (
el . animate (
[
{ boxShadow : dolu , backgroundColor : "transparent" , easing : DONUS } ,
{ boxShadow : hilal , backgroundColor : "transparent" , easing : "linear" } ,
{ boxShadow : hilal , backgroundColor : "transparent" , easing : YERLES } ,
{ boxShadow : dolu , backgroundColor : "transparent" } ,
] . map ( ( kare , d ) = > ( { . . . kare , offset : SEKIL_DURAKLAR [ d ] } ) ) ,
secenek ,
) ,
) ;
}
return animasyonlar ;
} ) ;
} ;
const kontrol = { ayarla , temaSekli } ;
kontrolRef . current = kontrol ;
return ( ) = > {
sokuldu = true ;
if ( kontrolRef . current === kontrol ) kontrolRef . current = null ;
[ . . . dongu , . . . donus ] . forEach ( ( a ) = > a . cancel ( ) ) ;
[ . . . dongu , . . . donus , . . . sekil ]. forEach ( ( a ) = > a . cancel ( ) ) ;
} ;
} , [ ] ) ;
@@ -277,24 +369,18 @@ export function AnimasyonluMark({
const cik = ( ) = > kontrolRef . current ? . ayarla ( null , true ) ;
hoverHedefi . addEventListener ( "mouseenter" , gir ) ;
hoverHedefi . addEventListener ( "mouseleave" , cik ) ;
// Tema geçişi başlarken (lib/tema-gecisi.ts) kareler bir etap yürür ve
// yerine oturur: geçiş logodan yayı ldı ğı için onu logo başlatmı ş olur .
// İmleç zaten üzerindeyse hover döngüsü sürer, dokunulmaz .
le t temaZamanlayici : number | undefined ;
const temaGecti = ( ) = > {
if ( hoverHedefi . matches ( ":hover" ) ) return ;
gir ( ) ;
window . clearTimeout ( temaZamanlayici ) ;
temaZamanlayici = window . setTimeout ( ( ) = > {
if ( ! hoverHedefi . matches ( ":hover" ) ) cik ( ) ;
} , 1000 ) ;
} ;
// Tema geçişi başlarken (lib/tema-gecisi.ts) kareler yeni temanı n
// biçimine dönüşür: koyuya geçişte ay ve yı ldı zlar, açı ğa geçişte güneş .
// Olay anı nda <html> yeni temanı n sı nı fı nı taşı r .
cons t temaGecti = ( ) = >
kontrolRef . current ? . temaSekli (
document . documentElement . classList . contains ( "dark" ) ,
) ;
window . addEventListener ( TEMA_GECIS_OLAYI , temaGecti ) ;
return ( ) = > {
hoverHedefi . removeEventListener ( "mouseenter" , gir ) ;
hoverHedefi . removeEventListener ( "mouseleave" , cik ) ;
window . removeEventListener ( TEMA_GECIS_OLAYI , temaGecti ) ;
window . clearTimeout ( temaZamanlayici ) ;
} ;
} , [ oynat , sadeceHover , varyant ] ) ;