Enhance application performance and user experience by enabling component caching in next.config.ts, optimizing database queries in various pages, and implementing lazy loading for heavy components. Updated styles for smoother transitions and improved visual clarity in multiple components, including globals.css and landing sections.
All checks were successful
Deploy / deploy (push) Successful in 2m30s

This commit is contained in:
bilalgursen
2026-08-01 14:00:22 +03:00
parent acb3b9d557
commit 569b9e5da2
29 changed files with 458 additions and 126 deletions

View File

@@ -8,6 +8,11 @@
* SVG katmanları çoğunlukla pointer-events-none olduğundan imleç window
* üzerinden izlenir ve viewBox koordinatına çevrilir. Beklenen veri
* öznitelikleri: data-px, data-cx, data-cy, data-op (taban opaklık).
*
* Landing'de 16 örnek yaşar: her örneğin kendi window dinleyicisi yerine
* modül seviyesinde TEK pointermove dinleyicisi + rAF birleştirmesi kullanılır;
* kare başına en fazla bir tarama yapılır, imleçten uzak örnekler hiç
* piksel iterasyonuna girmez.
*/
import { useEffect, type RefObject } from "react";
@@ -26,6 +31,99 @@ type HeatTarget = {
baseOpacity: number;
};
type HeatInstance = {
svg: SVGSVGElement;
viewW: number;
radius: number;
baseColor: string;
pixels: HeatTarget[];
hot: Set<SVGRectElement>;
controls: Map<SVGRectElement, AnimationPlaybackControls>;
};
const instances = new Set<HeatInstance>();
let sonOlay: PointerEvent | null = null;
let rafId = 0;
function calistir(
inst: HeatInstance,
el: SVGRectElement,
to: { fill: string; fillOpacity: number },
duration: number,
) {
inst.controls.get(el)?.stop();
inst.controls.set(el, animate(el, to, { duration, ease: "easeOut" }));
}
function tara() {
rafId = 0;
const e = sonOlay;
if (!e) return;
for (const inst of instances) {
const rect = inst.svg.getBoundingClientRect();
if (rect.width === 0) continue;
const scale = rect.width / inst.viewW;
const pad = inst.radius * scale;
// İmleç fırça menzilinin tamamen dışındaysa ve sıcak piksel yoksa
// bu örnek için hiç hesap yapma
if (
inst.hot.size === 0 &&
(e.clientX < rect.left - pad ||
e.clientX > rect.right + pad ||
e.clientY < rect.top - pad ||
e.clientY > rect.bottom + pad)
) {
continue;
}
const x = (e.clientX - rect.left) / scale;
const y = (e.clientY - rect.top) / scale;
for (const p of inst.pixels) {
const d2 = (p.cx - x) ** 2 + (p.cy - y) ** 2;
const isHot = d2 <= inst.radius * inst.radius;
if (isHot && !inst.hot.has(p.el)) {
inst.hot.add(p.el);
calistir(
inst,
p.el,
{ fill: HOT_COLOR, fillOpacity: HOT_OPACITY },
HEAT_DURATION,
);
} else if (!isHot && inst.hot.has(p.el)) {
inst.hot.delete(p.el);
calistir(
inst,
p.el,
{ fill: inst.baseColor, fillOpacity: p.baseOpacity },
COOL_DURATION,
);
}
}
}
}
function onMove(e: PointerEvent) {
sonOlay = e;
if (!rafId) rafId = requestAnimationFrame(tara);
}
function kaydol(inst: HeatInstance) {
if (instances.size === 0) {
window.addEventListener("pointermove", onMove, { passive: true });
}
instances.add(inst);
}
function ayril(inst: HeatInstance) {
instances.delete(inst);
inst.controls.forEach((c) => c.stop());
if (instances.size === 0) {
window.removeEventListener("pointermove", onMove);
if (rafId) cancelAnimationFrame(rafId);
rafId = 0;
sonOlay = null;
}
}
export function usePixelHeat(
svgRef: RefObject<SVGSVGElement | null>,
{
@@ -42,7 +140,6 @@ export function usePixelHeat(
const viewW = svg.viewBox.baseVal.width;
if (!viewW) return;
const baseColor = getComputedStyle(svg).color;
const pixels: HeatTarget[] = Array.from(
svg.querySelectorAll<SVGRectElement>("[data-px]"),
).map((el) => ({
@@ -53,61 +150,16 @@ export function usePixelHeat(
}));
if (pixels.length === 0) return;
const hot = new Set<SVGRectElement>();
const controls = new Map<SVGRectElement, AnimationPlaybackControls>();
const run = (
el: SVGRectElement,
to: { fill: string; fillOpacity: number },
duration: number,
) => {
controls.get(el)?.stop();
controls.set(el, animate(el, to, { duration, ease: "easeOut" }));
};
const onMove = (e: PointerEvent) => {
const rect = svg.getBoundingClientRect();
if (rect.width === 0) return;
const scale = rect.width / viewW;
const pad = radius * scale;
// İmleç fırça menzilinin tamamen dışındaysa ve sıcak piksel yoksa
// hiç hesap yapma (sayfada birden çok örnek dinlediği için önemli)
if (
hot.size === 0 &&
(e.clientX < rect.left - pad ||
e.clientX > rect.right + pad ||
e.clientY < rect.top - pad ||
e.clientY > rect.bottom + pad)
) {
return;
}
const x = (e.clientX - rect.left) / scale;
const y = (e.clientY - rect.top) / scale;
for (const p of pixels) {
const d2 = (p.cx - x) ** 2 + (p.cy - y) ** 2;
const isHot = d2 <= radius * radius;
if (isHot && !hot.has(p.el)) {
hot.add(p.el);
run(
p.el,
{ fill: HOT_COLOR, fillOpacity: HOT_OPACITY },
HEAT_DURATION,
);
} else if (!isHot && hot.has(p.el)) {
hot.delete(p.el);
run(
p.el,
{ fill: baseColor, fillOpacity: p.baseOpacity },
COOL_DURATION,
);
}
}
};
window.addEventListener("pointermove", onMove, { passive: true });
return () => {
window.removeEventListener("pointermove", onMove);
controls.forEach((c) => c.stop());
const inst: HeatInstance = {
svg,
viewW,
radius,
baseColor: getComputedStyle(svg).color,
pixels,
hot: new Set(),
controls: new Map(),
};
kaydol(inst);
return () => ayril(inst);
}, [svgRef, radius]);
}