Piksel harita ve logo animasyonu iyileştirmeleri
All checks were successful
Deploy / deploy (push) Successful in 7m52s

Şehir/Türkiye piksel haritası görünüme girince oynar; animasyonlu-mark,
console-asr, use-pixel-heat ve cta-sira-form güncellendi.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-10-04 14:00:34 +03:00
parent 9c02b810e7
commit ab0b3a047f
7 changed files with 329 additions and 180 deletions

View File

@@ -195,40 +195,61 @@ function PikselSvg({
const svgRef = useRef<SVGSVGElement>(null);
usePixelHeat(svgRef, { radius: ISI_YARICAPI });
// Yüklenince gecikmeyle odağa yakınlaş. viewBox her karede yazılır; ısı
// fırçası viewBox'ı canlı okuduğu için imleç hep doğru kareyi ısıtır.
// Harita görünüme girince (yüklenince değil) gecikmeyle odağa yakınlaş;
// il karelerinin sırayla belirmesi de o ana kadar bekler (globals.css:
// data-gorundu yokken kt-piksel-belir duraklatılır). Ekranın altında kalan
// harita, kullanıcı oraya inmeden animasyonunu harcamasın. viewBox her
// karede yazılır; ısı fırçası viewBox'ı canlı okuduğu için imleç hep doğru
// kareyi ısıtır.
const { sinir, zoom } = harita;
const hizaLg = odakHiza?.lg ?? ORTA;
const hizaDar = odakHiza?.dar ?? ORTA;
useEffect(() => {
const svg = svgRef.current;
if (!svg || !sinir) return;
const rect = svg.getBoundingClientRect();
const oran = rect.width && rect.height ? rect.width / rect.height : VIEW_W / VIEW_H;
const genis = window.matchMedia("(min-width: 1024px)").matches;
const odak = odakKutusu(sinir, oran, zoom, genis ? hizaLg : hizaDar);
if (!svg) return;
const yaz = (k: Kutu) => svg.setAttribute("viewBox", kutuMetni(k));
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
yaz(odak);
return () => yaz(TAM_KUTU);
}
const bas = TAM_KUTU;
const kontrol = animate(0, 1, {
type: "spring",
bounce: 0,
delay: ODAK_GECIKME,
duration: ODAK_SURE,
onUpdate: (t) =>
yaz([
bas[0] + (odak[0] - bas[0]) * t,
bas[1] + (odak[1] - bas[1]) * t,
bas[2] + (odak[2] - bas[2]) * t,
bas[3] + (odak[3] - bas[3]) * t,
]),
});
let kontrol: { stop: () => void } | null = null;
const basla = () => {
svg.setAttribute("data-gorundu", "");
if (!sinir) return;
const rect = svg.getBoundingClientRect();
const oran = rect.width && rect.height ? rect.width / rect.height : VIEW_W / VIEW_H;
const genis = window.matchMedia("(min-width: 1024px)").matches;
const odak = odakKutusu(sinir, oran, zoom, genis ? hizaLg : hizaDar);
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
yaz(odak);
return;
}
const bas = TAM_KUTU;
kontrol = animate(0, 1, {
type: "spring",
bounce: 0,
delay: ODAK_GECIKME,
duration: ODAK_SURE,
onUpdate: (t) =>
yaz([
bas[0] + (odak[0] - bas[0]) * t,
bas[1] + (odak[1] - bas[1]) * t,
bas[2] + (odak[2] - bas[2]) * t,
bas[3] + (odak[3] - bas[3]) * t,
]),
});
};
// Haritanın dörtte biri, ekranın alt %15'lik şeridinin üstüne çıkınca
const gozlemci = new IntersectionObserver(
(girdiler) => {
if (!girdiler.some((g) => g.isIntersecting)) return;
gozlemci.disconnect();
basla();
},
{ threshold: 0.25, rootMargin: "0px 0px -15% 0px" },
);
gozlemci.observe(svg);
return () => {
kontrol.stop();
yaz(TAM_KUTU);
gozlemci.disconnect();
kontrol?.stop();
svg.removeAttribute("data-gorundu");
if (sinir) yaz(TAM_KUTU);
};
}, [sinir, zoom, hizaLg, hizaDar]);
@@ -237,7 +258,7 @@ function PikselSvg({
ref={svgRef}
viewBox={`0 0 ${VIEW_W} ${VIEW_H}`}
preserveAspectRatio={doldur ? "xMidYMid slice" : "xMidYMid meet"}
className={cn(doldur ? "h-full w-full" : "h-auto w-full", "text-primary", className)}
className={cn(doldur ? "h-full w-full" : "h-auto w-full", "kt-piksel-harita text-primary", className)}
role="img"
aria-label={etiket}
// Statik, deterministik markup — her render'da yeniden kurulmaz