All checks were successful
Deploy / deploy (push) Successful in 12m16s
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.
194 lines
6.6 KiB
TypeScript
194 lines
6.6 KiB
TypeScript
"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>
|
||
);
|
||
}
|