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

@@ -26,6 +26,12 @@ const HOT_COLOR = "#f97316"; // orange-500
const HOT_OPACITY = 0.85;
const HEAT_DURATION = 0.15; // ısınma: anlık geri bildirim
const COOL_DURATION = 1.4; // soğuma: iz gibi yavaşça söner
// Kırpışma: imleç olmadan, rastgele tek piksel ısınıp söner. Yormasın diye
// seyrek ve yumuşak — aynı anda ekranda bir-iki sıcak piksel olur.
const KIRPISMA_BEKLEME = 1500; // ms; sayfa otursun
const KIRPISMA_ARALIK = [500, 1200]; // ms; iki kırpışma arası (rastgele)
const KIRPISMA_ISINMA = 0.5; // imleçtekinden yavaş: göz kırpar, çakmaz
const KIRPISMA_TUTMA = 900; // ms; ısınma + kısa bekleyiş, sonra soğur
type HeatTarget = {
el: SVGRectElement;
@@ -139,18 +145,74 @@ function ayril(inst: HeatInstance) {
}
}
/** Rastgele pikselleri seyrek aralıklarla ısıtıp soğutur; durdurucu döner.
* Fırçayla aynı `calistir`/`hot` üzerinden çalışır: imlecin altındaki
* piksele dokunmaz, fırça da kırpışan pikseli devralabilir. Harita ekran
* dışındayken ya da sekme arka plandayken bekler. */
function kirpismaBaslat(inst: HeatInstance) {
const yanan = new Set<SVGRectElement>();
const bekleyen = new Set<number>();
let gorunur = true;
const io = new IntersectionObserver(([g]) => {
gorunur = g.isIntersecting;
});
io.observe(inst.svg);
let sayac = 0;
const tik = () => {
const [enAz, enCok] = KIRPISMA_ARALIK;
sayac = window.setTimeout(tik, enAz + Math.random() * (enCok - enAz));
if (!gorunur || document.hidden) return;
const p = inst.pixels[Math.floor(Math.random() * inst.pixels.length)];
if (inst.hot.has(p.el) || yanan.has(p.el)) return;
yanan.add(p.el);
calistir(
inst,
p.el,
{ fill: HOT_COLOR, fillOpacity: HOT_OPACITY },
KIRPISMA_ISINMA,
);
const s = window.setTimeout(() => {
bekleyen.delete(s);
yanan.delete(p.el);
if (inst.hot.has(p.el)) return;
calistir(
inst,
p.el,
{ fill: inst.baseColor, fillOpacity: p.baseOpacity },
COOL_DURATION,
);
}, KIRPISMA_TUTMA);
bekleyen.add(s);
};
sayac = window.setTimeout(tik, KIRPISMA_BEKLEME);
return () => {
window.clearTimeout(sayac);
bekleyen.forEach((s) => window.clearTimeout(s));
io.disconnect();
};
}
export function usePixelHeat(
svgRef: RefObject<SVGSVGElement | null>,
{
/** Fırça yarıçapı, SVG (viewBox) biriminde */
radius = 34,
}: { radius?: number } = {},
/** İmleç olmadan da rastgele pikseller seyrekçe ısınıp sönsün (hero) */
kirpisma = false,
}: { radius?: number; kirpisma?: boolean } = {},
) {
useEffect(() => {
const svg = svgRef.current;
if (!svg) return;
// Dokunmatik cihazlarda hover yanlış tetiklenir; efekti imleçli cihazlara sakla
if (!window.matchMedia(FINE_POINTER_QUERY).matches) return;
// Dokunmatik cihazlarda hover yanlış tetiklenir; fırça imleçli cihazlara
// saklanır. Kırpışma imleç istemez ama "hareketi azalt"ta kapalıdır.
const imlecli = window.matchMedia(FINE_POINTER_QUERY).matches;
const kirp =
kirpisma &&
!window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (!imlecli && !kirp) return;
if (!svg.viewBox.baseVal.width) return;
@@ -173,7 +235,11 @@ export function usePixelHeat(
hot: new Set(),
controls: new Map(),
};
kaydol(inst);
return () => ayril(inst);
}, [svgRef, radius]);
if (imlecli) kaydol(inst);
const kirpismaDur = kirp ? kirpismaBaslat(inst) : null;
return () => {
kirpismaDur?.();
ayril(inst);
};
}, [svgRef, radius, kirpisma]);
}