"use client"; import { useEffect, useRef } from "react"; import { cn } from "@/lib/utils"; // public/kolay-tercih-mark.svg'nin inline, animasyonlu karşılığı. // İki varyant: // // - "rota" (varsayılan): her kare sıradakinin pozisyonuna süzülür (mavi → // şeftalinin yeri, şeftali → turuncunun, turuncu → mavinin), üç adımda // herkes başlangıç formuna döner. Uçuş sırasında kare hafifçe küçülüp // solar, varışta toparlanır. Site header'daki logo hover'da bunu oynatır. // - "nabiz": kareler yerlerinden ayrılmaz; 120° faz farkıyla dalga gibi // küçülüp büyür (danışman panel başlığının sakin, sürekli hâli). // // Performans notu: kareler SVG değil HTML , animasyon da Web // Animations API ile yalnızca `translate` / `scale` / `opacity` üzerinden // yürür. Bu üçü compositor'da (GPU) oynar: ana iş parçacığı meşgulken // (hidrasyon, sayfa geçişi, kaydırma) bile kare düşmez ve hover React'i // yeniden render etmez. SVG iç elemanlarının transform'u her karede yeniden // boyanır; o yüzden buraya geri dönme. const KUTU = 180; // kaynak SVG'nin viewBox kenarı const POZISYONLAR = [ [27, 56], [91, 29], [91, 95], ] as const; const KARELER = [ { boyut: 57, renk: "#3b82f6", baslangic: 0 }, { boyut: 56, renk: "#fdc7a7", baslangic: 1 }, { boyut: 56, renk: "#ff5a00", baslangic: 2 }, ] as const; // Rota: tam tur 3.6s — 3 etap × (%25 yol = 0.9s + %8.3 varışta soluklanma). // 0.9s'lik yolculuk, nabız varyantının küçül-büyü temposuyla aynı hızda. const ROTA_SURE = 3600; // Kalkışlar kademeli: kareler eş zamanlı değil, danışman başlığındaki gibi // sırayla (mavi → şeftali → turuncu) yola çıkıp küçülür. Gecikme yalnızca ilk // turdan önce uygulanır, sonraki turlarda faz farkı kendiliğinden korunur. const ROTA_GECIKME_ADIMI = 200; const KONUM_ZAMANLARI = [0, 0.25, 1 / 3, 0.583, 2 / 3, 0.917, 1]; // Nabız (rota içi): her yolculuğun ortasında dibe vurur, varışta tam form. const ROTA_NABIZ_ZAMANLARI = [ 0, 0.125, 0.25, 1 / 3, 0.458, 0.583, 2 / 3, 0.792, 0.917, 1, ]; const ROTA_NABIZ_SCALE = [1, 0.84, 1, 1, 0.84, 1, 1, 0.84, 1, 1]; const ROTA_NABIZ_OPACITY = [1, 0.6, 1, 1, 0.6, 1, 1, 0.6, 1, 1]; // Yerinde nabız: kesintisiz tam-sinüs (dinlenme yok), delay'ler döngüyü üçe // böler — biri büyürken sıradaki küçülür, hiçbir kare beklemez. const NABIZ_SURE = 1800; const NABIZ_GECIKMELER = [0, 600, 1200] as const; const SINE = "cubic-bezier(0.37, 0, 0.63, 1)"; // Yolculuk: yumuşak kalkış, uzun ve yumuşak iniş (easeInOutCubic). const YOL = "cubic-bezier(0.65, 0, 0.35, 1)"; // Hover bitince yerine oturma (easeOutQuint): hızlı toparlanır, sarsmadan durur. const DONUS = "cubic-bezier(0.22, 1, 0.36, 1)"; const DONUS_SURE = 500; /** Karenin `translate` değeri: yüzdeler karenin kendi boyutuna göredir, bu * yüzden işaret hangi boyutta çizilirse çizilsin aynı oran korunur. */ function yer(pozisyon: number, boyut: number): string { const [x, y] = POZISYONLAR[pozisyon % 3]; return `${(x / boyut) * 100}% ${(y / boyut) * 100}%`; } /** 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ı). */ function rota(baslangic: number, boyut: number): string[] { const [p0, p1, p2] = [0, 1, 2].map((adim) => yer(baslangic + adim, boyut)); return [p0, p1, p1, p2, p2, p0, p0]; } function donguyuBaslat( el: HTMLElement, i: number, varyant: "rota" | "nabiz", ): Animation[] { const k = KARELER[i]; if (varyant === "nabiz") { return [ el.animate( { scale: ["1", "0.82", "1"], opacity: [1, 0.55, 1], easing: SINE }, { duration: NABIZ_SURE, delay: NABIZ_GECIKMELER[i], iterations: Infinity, }, ), ]; } const secenek = { duration: ROTA_SURE, delay: i * ROTA_GECIKME_ADIMI, iterations: Infinity, }; // Konum ve nabız ayrı animasyonlar: konum etabı tek parça eğriyle akar, // ortasındaki küçülme onu ikiye bölüp duraksatmaz. return [ el.animate( { translate: rota(k.baslangic, k.boyut), offset: KONUM_ZAMANLARI, easing: YOL, }, secenek, ), el.animate( { scale: ROTA_NABIZ_SCALE.map(String), opacity: ROTA_NABIZ_OPACITY, offset: ROTA_NABIZ_ZAMANLARI, easing: SINE, }, secenek, ), ]; } /** * Animasyonlu KolayTercih işareti. Varsayılanda koreografi sürekli döner; * `sadeceHover` ile yalnızca işaretin üzerine gelindiğinde oynar (ör. site * header'daki logo linki) ve hover bitince kareler yumuşakça yerine oturur. */ export function AnimasyonluMark({ className, sadeceHover = false, varyant = "rota", renk = "marka", }: { className?: string; sadeceHover?: boolean; varyant?: "rota" | "nabiz"; /** "marka": logodaki üç renk; "turuncu": tüm kareler sitenin CTA turuncusu * (orange-500) — ör. danışman panel başlığı. */ renk?: "marka" | "turuncu"; }) { const kokRef = useRef(null); useEffect(() => { const kok = kokRef.current; if (!kok || typeof kok.animate !== "function") return; const azHareket = window.matchMedia("(prefers-reduced-motion: reduce)"); const kareler = Array.from(kok.children) as HTMLElement[]; let dongu: Animation[] = []; let donus: Animation[] = []; let istekli = false; const baslat = () => { if (istekli) return; istekli = true; if (azHareket.matches) return; // Kareler hâlâ yerine dönüyorsa (hızlı çık-gir) dönüşün bitmesi // beklenir; döngü hep yerinden başlar, sıçrama olmaz. Promise.all(donus.map((a) => a.finished)) .then(() => { if (!istekli || dongu.length > 0) return; dongu = kareler.flatMap((el, i) => donguyuBaslat(el, i, varyant)); }) .catch(() => {}); }; const durdur = () => { istekli = false; if (dongu.length === 0) return; // Kareler o an neredeyse oradan yerlerine süzülür. const anlik = kareler.map((el) => { const cs = getComputedStyle(el); return { translate: cs.translate, scale: cs.scale, opacity: cs.opacity }; }); dongu.forEach((a) => a.cancel()); dongu = []; donus = kareler.map((el, i) => el.animate( [ anlik[i], { translate: yer(KARELER[i].baslangic, KARELER[i].boyut), scale: "1", opacity: 1, }, ], { duration: DONUS_SURE, easing: DONUS }, ), ); }; if (!sadeceHover) { baslat(); return () => { dongu.forEach((a) => a.cancel()); }; } // Hover hedefi işaretin kendisi değil, en yakın `group` ebeveyni (site // header'da logo + "KolayTercih" yazısını saran Link): yazının üzerine // gelindiğinde de animasyon başlar. `group`'suz kullanımda köke düşer. const hedef = kok.closest(".group") ?? kok; hedef.addEventListener("mouseenter", baslat); hedef.addEventListener("mouseleave", durdur); return () => { hedef.removeEventListener("mouseenter", baslat); hedef.removeEventListener("mouseleave", durdur); [...dongu, ...donus].forEach((a) => a.cancel()); }; }, [sadeceHover, varyant]); return (