From 17c2a3603f05405527c6790bcb7a9a6fe3ac58ef Mon Sep 17 00:00:00 2001 From: Nicholai Date: Fri, 12 Dec 2025 14:23:40 -0700 Subject: [PATCH 1/3] Refactor components for performance and accessibility improvements - Removed unused Lucide CDN script from BaseHead component to reduce network costs. - Enhanced CustomCursor component with requestAnimationFrame for smoother animations and respect for user motion preferences. - Updated Hero section styles for faster fade-out transitions and improved responsiveness to user preferences. - Optimized clock functionality in Hero section to reduce drift and improve performance. - Streamlined mousemove event handling in Hero section for better performance and reduced resource usage. - Lazy-loaded markdown renderer in contact page to keep initial JavaScript lighter. - Added will-change property to global CSS for improved rendering performance. --- src/components/BaseHead.astro | 2 +- src/components/CustomCursor.tsx | 77 ++++++++++-- src/components/sections/Hero.astro | 180 ++++++++++++++++++----------- src/layouts/BaseLayout.astro | 113 ++++++++++++++---- src/pages/contact.astro | 8 +- src/styles/global.css | 1 + 6 files changed, 274 insertions(+), 107 deletions(-) diff --git a/src/components/BaseHead.astro b/src/components/BaseHead.astro index e675332..d143b4e 100644 --- a/src/components/BaseHead.astro +++ b/src/components/BaseHead.astro @@ -137,7 +137,7 @@ const professionalServiceSchema = { - + diff --git a/src/components/CustomCursor.tsx b/src/components/CustomCursor.tsx index b14a43b..d12f0d3 100644 --- a/src/components/CustomCursor.tsx +++ b/src/components/CustomCursor.tsx @@ -10,25 +10,78 @@ const CustomCursor = () => { if (!dot || !outline) return; - const onMouseMove = (e: MouseEvent) => { - const posX = e.clientX; - const posY = e.clientY; + // Respect user preferences + const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches; + if (reduceMotion) return; - // Dot follows instantly - dot.style.left = `${posX}px`; - dot.style.top = `${posY}px`; + // rAF-driven cursor animation (single loop, no per-event Web Animations allocations) + let targetX = 0; + let targetY = 0; + let dotX = 0; + let dotY = 0; + let outlineX = 0; + let outlineY = 0; - // Outline follows with animation - outline.animate({ - left: `${posX}px`, - top: `${posY}px` - }, { duration: 500, fill: "forwards" }); + let hasInit = false; + let rafId: number | null = null; + let lastMoveTs = performance.now(); + + const DOT_LERP = 0.65; // tighter = closer to 1 + const OUTLINE_LERP = 0.18; // tighter = closer to 1 + const STOP_AFTER_MS = 140; + const STOP_EPS_PX = 0.35; + + const applyTransforms = () => { + dot.style.transform = `translate3d(${dotX}px, ${dotY}px, 0) translate(-50%, -50%)`; + outline.style.transform = `translate3d(${outlineX}px, ${outlineY}px, 0) translate(-50%, -50%)`; }; - window.addEventListener('mousemove', onMouseMove); + const tick = (ts: number) => { + // Lerp towards the target + dotX += (targetX - dotX) * DOT_LERP; + dotY += (targetY - dotY) * DOT_LERP; + outlineX += (targetX - outlineX) * OUTLINE_LERP; + outlineY += (targetY - outlineY) * OUTLINE_LERP; + + applyTransforms(); + + const idle = ts - lastMoveTs > STOP_AFTER_MS; + const dx = Math.abs(targetX - outlineX); + const dy = Math.abs(targetY - outlineY); + const settled = dx < STOP_EPS_PX && dy < STOP_EPS_PX; + + if (idle && settled) { + rafId = null; + return; + } + + rafId = window.requestAnimationFrame(tick); + }; + + const onMouseMove = (e: MouseEvent) => { + targetX = e.clientX; + targetY = e.clientY; + lastMoveTs = performance.now(); + + if (!hasInit) { + hasInit = true; + dotX = targetX; + dotY = targetY; + outlineX = targetX; + outlineY = targetY; + applyTransforms(); + } + + if (rafId === null) { + rafId = window.requestAnimationFrame(tick); + } + }; + + window.addEventListener('mousemove', onMouseMove, { passive: true }); return () => { window.removeEventListener('mousemove', onMouseMove); + if (rafId !== null) window.cancelAnimationFrame(rafId); }; }, []); diff --git a/src/components/sections/Hero.astro b/src/components/sections/Hero.astro index 5273ab2..0bf2b27 100644 --- a/src/components/sections/Hero.astro +++ b/src/components/sections/Hero.astro @@ -91,7 +91,8 @@ const { headlineLine1, headlineLine2, portfolioYear, location, locationLabel, bi } /* Fade out */ .grid-cell { - transition: opacity 0.8s ease-out, background-color 0.8s ease-out; + /* Slightly faster fade-out for a snappier feel */ + transition: opacity 0.6s ease-out, background-color 0.6s ease-out; } /* Initial Loaded State Classes */ @@ -112,17 +113,40 @@ const { headlineLine1, headlineLine2, portfolioYear, location, locationLabel, bi diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 1ad9aca..10ea3c7 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -42,7 +42,8 @@ const { - + + @@ -53,14 +54,9 @@ const {