refactor: update site header and chat components for improved user experience
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.
This commit is contained in:
bilalgursen
2026-08-11 12:54:46 +03:00
parent 951caa3c93
commit 30b9b41a22
9 changed files with 437 additions and 108 deletions

View File

@@ -0,0 +1,193 @@
"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>
);
}