diff --git a/app/globals.css b/app/globals.css index 48cd3025..efaa11a0 100644 --- a/app/globals.css +++ b/app/globals.css @@ -339,3 +339,32 @@ pre[class*="language-"] { pre[class*="language-"] { color: var(--prism-text) !important; } + +/* Performance optimization utilities */ +@layer utilities { + .contain-layout { + contain: layout; + } + + .contain-paint { + contain: paint; + } + + .contain-strict { + contain: strict; + } + + .contain-content { + contain: content; + } + + .gpu-accelerated { + transform: translateZ(0); + will-change: transform; + } + + .optimize-animation { + contain: layout paint; + will-change: transform, opacity; + } +} diff --git a/bun.lock b/bun.lock index 5a99575c..b5235489 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "eternalcodev3", diff --git a/components/docs/search/search-modal.tsx b/components/docs/search/search-modal.tsx index c4d8370f..666e6a15 100644 --- a/components/docs/search/search-modal.tsx +++ b/components/docs/search/search-modal.tsx @@ -239,14 +239,14 @@ export function SearchModal({ isOpen, onClose, triggerRef }: SearchModalProps) { {/* Modal Container with Enhanced Glassmorphism */} -
+
{/* Search Input Section with Enhanced Gradient */}
{/* Search Icon with Animation */} diff --git a/components/docs/sidebar/sidebar-wrapper.tsx b/components/docs/sidebar/sidebar-wrapper.tsx index da4a11f1..1d07f77d 100644 --- a/components/docs/sidebar/sidebar-wrapper.tsx +++ b/components/docs/sidebar/sidebar-wrapper.tsx @@ -32,7 +32,19 @@ const SidebarWrapper: FC = ({ sidebarStructure }) => { lenisRef.current = lenis; + // Throttle RAF to 120Hz (8.33ms per frame) for optimal performance + const targetFPS = 120; + const frameTime = 1000 / targetFPS; + let lastTime = 0; + function raf(time: number) { + // Skip frame if not enough time has passed + if (time - lastTime < frameTime) { + requestAnimationFrame(raf); + return; + } + + lastTime = time; lenis.raf(time); requestAnimationFrame(raf); } @@ -64,7 +76,7 @@ const SidebarWrapper: FC = ({ sidebarStructure }) => {
setIsSearchOpen(true)} />
diff --git a/components/docs/view/animations.ts b/components/docs/view/animations.ts index 7b37772a..f225526a 100644 --- a/components/docs/view/animations.ts +++ b/components/docs/view/animations.ts @@ -9,11 +9,10 @@ export const containerVariants: Variants = { }; export const itemVariants: Variants = { - hidden: { y: 20, opacity: 0, filter: "blur(4px)" }, + hidden: { y: 20, opacity: 0 }, visible: { y: 0, opacity: 1, - filter: "blur(0px)", transition: { type: "spring", stiffness: 200, diff --git a/components/hero/navbar.tsx b/components/hero/navbar.tsx index e2c32858..5e3dbc2c 100644 --- a/components/hero/navbar.tsx +++ b/components/hero/navbar.tsx @@ -146,7 +146,8 @@ export default function Navbar() { const handleScroll = () => { if (!ticking) { window.requestAnimationFrame(() => { - setScrolled(window.scrollY > 20); + const isScrolled = window.scrollY > 20; + setScrolled((prev) => (prev === isScrolled ? prev : isScrolled)); ticking = false; }); ticking = true; @@ -210,7 +211,7 @@ export default function Navbar() { aria-label="Main navigation" className={`fixed inset-x-0 top-0 z-50 border-transparent border-b transition-all duration-300 ${ scrolled || isMenuOpen - ? "border-gray-200/50 bg-white/80 backdrop-blur-xl dark:border-white/5 dark:bg-[#0a0a0a]/80" + ? "border-gray-200/50 bg-white/95 backdrop-blur-sm dark:border-white/5 dark:bg-[#0a0a0a]/95" : "bg-white/0 dark:bg-black/0" }`} initial="hidden" diff --git a/components/home/about/poland-map.tsx b/components/home/about/poland-map.tsx index 6812850d..57343d5b 100644 --- a/components/home/about/poland-map.tsx +++ b/components/home/about/poland-map.tsx @@ -12,14 +12,14 @@ export default function PolandMap() { ); diff --git a/components/home/projects/projects-section.tsx b/components/home/projects/projects-section.tsx index 9f8bde18..5b683007 100644 --- a/components/home/projects/projects-section.tsx +++ b/components/home/projects/projects-section.tsx @@ -1,9 +1,9 @@ "use client"; -import { motion } from "framer-motion"; +import { motion, useAnimationControls } from "framer-motion"; import Image from "next/image"; import Link from "next/link"; -import { useEffect, useRef, useState } from "react"; +import { useState } from "react"; interface Project { name: string; @@ -52,41 +52,11 @@ const projects: Project[] = [ ]; export default function Projects() { - const scrollRef = useRef(null); const [isHovered, setIsHovered] = useState(false); - - // Auto-scroll logic - useEffect(() => { - const scrollContainer = scrollRef.current; - if (!scrollContainer) { - return; - } - - let animationFrameId: number; - let scrollPos = scrollContainer.scrollLeft; - const speed = 0.6; // Slightly slower for better viewing of images - - const scroll = () => { - if (!isHovered && scrollContainer) { - scrollPos += speed; - - if (scrollPos >= scrollContainer.scrollWidth / 3) { - scrollPos = 0; - } - - scrollContainer.scrollLeft = scrollPos; - } else if (isHovered && scrollContainer) { - scrollPos = scrollContainer.scrollLeft; - } - animationFrameId = requestAnimationFrame(scroll); - }; - - animationFrameId = requestAnimationFrame(scroll); - - return () => cancelAnimationFrame(animationFrameId); - }, [isHovered]); + const controls = useAnimationControls(); const displayProjects = [...projects, ...projects, ...projects]; + const singleSetWidth = projects.length * (420 + 24); return (
@@ -123,23 +93,39 @@ export default function Projects() { {/* biome-ignore lint/a11y/noNoninteractiveElementInteractions: Pause on hover feature */}
setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} + onMouseEnter={() => { + setIsHovered(true); + controls.stop(); + }} + onMouseLeave={() => { + setIsHovered(false); + controls.start({ + x: [`${-singleSetWidth}px`, "0px"], + transition: { + duration: 60, + ease: "linear", + repeat: Number.POSITIVE_INFINITY, + }, + }); + }} > -
-
+
+ {displayProjects.map((project, index) => ( ))} -
+
{/* Subtle fade masks inside the container */}