Files
kolaytercih/src/components/animasyonlu-mark.tsx
bilalgursen 30b9b41a22
All checks were successful
Deploy / deploy (push) Successful in 12m16s
refactor: update site header and chat components for improved user experience
Replaced the static logo in the SiteHeader with an animated component for enhanced visual feedback. Updated the PaketSatinAl component to include a dynamic link based on user context. Enhanced the ListeUretici and ListemGovde components with improved layout and messaging features, including the addition of a new initial preference comment display in the SohbetClient. Adjusted styling for consistency across components and improved loading states with skeleton screens.
2026-08-11 12:54:46 +03:00

194 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useRef, useState, type CSSProperties } from "react";
import { motion, useReducedMotion } from "motion/react";
import { cn } from "@/lib/utils";
// public/kolay-tercih-mark.svg'nin inline, animasyonlu karşılığı (Motion).
// İ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).
//
// Not: motion.rect'te `x`/`y` transform değeridir (attribute değil) — kareler
// attribute yerine translate ile konumlanır; scale, transform-box: fill-box
// sayesinde karenin kendi merkezinden uygulanır.
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 = 3.6;
// 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.
const ROTA_GECIKME_ADIMI = 0.2;
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 = 1.8;
const NABIZ_GECIKMELER = [0, 0.6, 1.2] as const;
const SINE = [0.37, 0, 0.63, 1] as const;
/** Kare i 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, eksen: 0 | 1): number[] {
const [p0, p1, p2] = [0, 1, 2].map(
(adim) => POZISYONLAR[(baslangic + adim) % 3][eksen],
);
return [p0, p1, p1, p2, p2, p0, p0];
}
/**
* 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 [hover, setHover] = useState(false);
const svgRef = useRef<SVGSVGElement>(null);
const azHareket = useReducedMotion();
const aktif = !azHareket && (!sadeceHover || hover);
// 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 svg'ye düşer.
useEffect(() => {
if (!sadeceHover) return;
const hedef = svgRef.current?.closest(".group") ?? svgRef.current;
if (!hedef) return;
const ac = () => setHover(true);
const kapat = () => setHover(false);
hedef.addEventListener("mouseenter", ac);
hedef.addEventListener("mouseleave", kapat);
return () => {
hedef.removeEventListener("mouseenter", ac);
hedef.removeEventListener("mouseleave", kapat);
};
}, [sadeceHover]);
return (
<svg
ref={svgRef}
viewBox="0 0 180 180"
aria-hidden="true"
focusable="false"
className={cn("shrink-0", className)}
>
{KARELER.map((k, i) => {
const [px, py] = POZISYONLAR[k.baslangic];
const duruk = { x: px, y: py, scale: 1, opacity: 1 };
const animate = !aktif
? duruk
: varyant === "rota"
? {
x: rota(k.baslangic, 0),
y: rota(k.baslangic, 1),
scale: ROTA_NABIZ_SCALE,
opacity: ROTA_NABIZ_OPACITY,
}
: {
x: px,
y: py,
scale: [1, 0.82, 1],
opacity: [1, 0.55, 1],
};
// Kademeli kalkış: delay yalnızca ilk turdan önce uygulanır, sonraki
// turlarda faz farkı kendiliğinden korunur.
const rotaGecikme = i * ROTA_GECIKME_ADIMI;
const konumGecis = {
duration: ROTA_SURE,
times: KONUM_ZAMANLARI,
ease: "easeInOut" as const,
repeat: Infinity,
delay: rotaGecikme,
};
const rotaNabizGecis = {
duration: ROTA_SURE,
times: ROTA_NABIZ_ZAMANLARI,
ease: "easeInOut" as const,
repeat: Infinity,
delay: rotaGecikme,
};
const nabizGecis = {
duration: NABIZ_SURE,
times: [0, 0.5, 1],
ease: SINE as unknown as [number, number, number, number],
repeat: Infinity,
delay: NABIZ_GECIKMELER[i],
};
const transition = !aktif
? { duration: 0.4, ease: "easeOut" as const }
: varyant === "rota"
? {
x: konumGecis,
y: konumGecis,
scale: rotaNabizGecis,
opacity: rotaNabizGecis,
}
: {
x: { duration: 0 },
y: { duration: 0 },
scale: nabizGecis,
opacity: nabizGecis,
};
return (
<motion.rect
key={k.renk}
width={k.boyut}
height={k.boyut}
rx={13}
fill={k.renk}
className={renk === "turuncu" ? "fill-orange-500" : undefined}
animate={animate}
transition={transition}
style={
{
transformBox: "fill-box",
transformOrigin: "center",
} as CSSProperties
}
/>
);
})}
</svg>
);
}