Skip to content

Latest commit

 

History

History
495 lines (364 loc) · 12.1 KB

File metadata and controls

495 lines (364 loc) · 12.1 KB

Portfolio Optimization & Animation Enhancement Plan

Author: Claude (AI Assistant)
Date: December 23, 2025
Project: Nikunj Khitha's Next.js Portfolio


Executive Summary

This document outlines a comprehensive plan for optimizing animations and code quality across the portfolio. The codebase is well-structured with a Next.js 15 + TypeScript + Tailwind CSS + Framer Motion stack. The main areas for improvement include:

  1. Animation Performance - Reduce CPU/GPU overhead and improve frame rates
  2. Bundle Size Optimization - Reduce JavaScript payload
  3. Code Architecture - Improve maintainability and reduce redundancy
  4. Accessibility Enhancements - Better reduced motion support
  5. Mobile Performance - Address heavy effects on low-power devices

1. Animation System Improvements

1.1 Welcome Screen (welcome-screen.tsx)

Current Issues:

  • 15 floating particles with continuous animations running indefinitely
  • Math.random() calls during render cause hydration mismatches
  • No cleanup of intervals when component unmounts

Proposed Changes:

// welcome-screen.tsx

- {[...Array(15)].map((_, i) => (
+ // Pre-compute positions once, reduce to 8 particles
+ const PARTICLES = useMemo(() => 
+   Array.from({ length: 8 }, (_, i) => ({
+     id: i,
+     left: `${(i * 12.5) + Math.random() * 10}%`,
+     top: `${(i * 12.5) + Math.random() * 10}%`,
+     delay: i * 0.15,
+     duration: 3 + (i % 3)
+   })), []);

Benefits:

  • Reduces particle count from 15 → 8 (47% less animation overhead)
  • Eliminates hydration mismatches with SSR-stable positions
  • Memoized calculation prevents re-computation

1.2 Page Transition Animation (page-transition-animation.tsx)

Current State: ✅ Good

  • Already has mobile/low-perf detection
  • Respects prefers-reduced-motion
  • Pauses animations when tab is hidden

Suggested Improvements:

Area Current Proposed
Radiating circles on mobile 2 circles 1 circle
Overlay layers 3 layers 2 layers on mobile
Animation duration on mobile 0.5-0.65s 0.4-0.5s
// page-transition-animation.tsx

- const circleCount = isMobile || isLowPerf ? 2 : 3;
+ const circleCount = isMobile || isLowPerf ? 1 : 3;

// Consider removing tertiary overlay on mobile entirely
+ {!isMobile && (
    <motion.div className="fixed inset-0 z-[50]..." />
+ )}

1.3 Fluid Cursor (useFluidCursor.tsx)

Current Issues:

  • 1,452 lines of WebGL code running continuously
  • Uses @ts-nocheck - no TypeScript safety
  • Runs on ALL devices including mobile where cursor isn't visible
  • No cleanup mechanism visible

Proposed Changes:

// fluid-cursor.tsx - Add device detection

const FluidCursor = () => {
  const [shouldRender, setShouldRender] = useState(false);
  
  useEffect(() => {
    // Only render on desktop with pointer devices
    const hasPointer = window.matchMedia('(pointer: fine)').matches;
    const isDesktop = window.innerWidth >= 1024;
    setShouldRender(hasPointer && isDesktop);
  }, []);

  useEffect(() => {
    if (!shouldRender) return;
    const cleanup = fluidCursor();
    return () => cleanup?.();
  }, [shouldRender]);

  if (!shouldRender) return null;
  
  return (
    <div className="pointer-events-none fixed inset-0 -z-10">
      <canvas id="fluid" className="h-screen w-screen" />
    </div>
  );
};

Benefits:

  • Saves ~300KB of WebGL processing on mobile devices
  • Prevents unnecessary canvas initialization
  • Adds proper cleanup mechanism

1.4 Cursor Trail Canvas (cursor-trail-canvas.tsx)

Current Issues:

  • Running simultaneously with fluid cursor (redundant)
  • Creates new Line objects on every mouse move
  • No object pooling for animations

Proposed Optimizations:

// Implement object pooling for Lines
const LINE_POOL: Line[] = [];

function getLineFromPool(config: LineConfig): Line {
  if (LINE_POOL.length > 0) {
    const line = LINE_POOL.pop()!;
    line.reset(config);
    return line;
  }
  return new Line(config);
}

function returnLineToPool(line: Line): void {
  LINE_POOL.push(line);
}

1.5 FadeUp & FadeRight Animations

Current State: ✅ Good

  • Respects reduced motion preferences
  • Uses animation gate for coordinated timing
  • Has viewport: { once: true }

Minor Improvements:

// fade-up.tsx

- const initial = prefersReducedMotion ? { opacity: 0 } : { y: 80, opacity: 0 };
+ const initial = prefersReducedMotion ? { opacity: 0 } : { y: 40, opacity: 0 };
+ // Reduce y offset from 80 to 40 for subtler, faster animations
// fade-right.tsx

- const initial = prefersReducedMotion ? { opacity: 0 } : { x: -100, opacity: 0 };
+ const initial = prefersReducedMotion ? { opacity: 0 } : { x: -60, opacity: 0 };
+ // Reduce x offset from -100 to -60 for subtler animations

1.6 FlipWords Animation

Current Issues:

  • Uses setTimeout without cleanup
  • Animation runs even when tab is hidden

Proposed Changes:

// flip-words.tsx

useEffect(() => {
  if (!isAnimating) {
    const timeoutId = setTimeout(() => {
      startAnimation();
    }, duration);
    return () => clearTimeout(timeoutId);  // Add cleanup
  }
}, [isAnimating, duration, startAnimation]);

// Add visibility detection
const [isVisible, setIsVisible] = useState(true);

useEffect(() => {
  const handler = () => setIsVisible(!document.hidden);
  document.addEventListener('visibilitychange', handler);
  return () => document.removeEventListener('visibilitychange', handler);
}, []);

2. Bundle Size Optimizations

2.1 Current Bundle Analysis

Run analysis with: npm run analyze

Expected Large Dependencies:

Package Estimated Size Optimization Strategy
framer-motion ~150KB Use lazy loading for AnimatePresence
react-icons Variable Import only needed icons
openai ~50KB Server-only import
@tsparticles/* ~100KB Consider removal (unused?)

2.2 Dynamic Imports for Heavy Components

// pages/_app.tsx

import dynamic from 'next/dynamic';

const FluidCursor = dynamic(
  () => import('@/components/fluid-cursor'),
  { ssr: false }
);

const WelcomeScreen = dynamic(
  () => import('@/components/welcome-screen'),
  { ssr: false }
);

const PageTransitionAnimation = dynamic(
  () => import('@/components/page-transition-animation'),
  { ssr: false }
);

2.3 Remove Unused Dependencies

Check if @tsparticles/* packages are actually used:

grep -r "tsparticles" src/

If unused, remove from package.json:

  • @tsparticles/engine
  • @tsparticles/react
  • @tsparticles/slim

3. Code Architecture Improvements

3.1 Animation Components Consolidation

Current Structure:

src/animation/
├── fade-right.tsx
├── fade-up.tsx
└── flip-words.tsx

Proposed Refactor:

// src/animation/fade.tsx - Unified component

export interface FadeProps {
  children: ReactNode;
  direction: 'up' | 'right' | 'left' | 'down';
  duration?: number;
  delay?: number;
  offset?: number;
  whileInView?: boolean;
  className?: string;
}

export default function Fade({
  direction = 'up',
  offset = 40,
  ...props
}: FadeProps) {
  // Unified implementation
}

3.2 Custom Hook Extraction

Extract animation state management:

// src/hooks/useAnimationConfig.ts

export function useAnimationConfig() {
  const prefersReducedMotion = useReducedMotion();
  const { animationsReady } = useAnimationGate();
  const isMobile = useScreenBreakpoint(640);
  
  return {
    shouldAnimate: animationsReady && !prefersReducedMotion,
    duration: isMobile ? 0.2 : 0.4,
    staggerDelay: isMobile ? 0.04 : 0.06,
  };
}

3.3 Performance Hook

// src/hooks/usePerformanceMode.ts

export function usePerformanceMode() {
  const [mode, setMode] = useState<'high' | 'low' | 'minimal'>('high');
  
  useEffect(() => {
    const cores = navigator.hardwareConcurrency || 4;
    const isMobile = window.innerWidth < 768;
    const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    
    if (prefersReduced) setMode('minimal');
    else if (isMobile || cores <= 4) setMode('low');
    else setMode('high');
  }, []);
  
  return mode;
}

4. Accessibility Enhancements

4.1 Enhanced Reduced Motion Support

Current: ✅ CSS supports prefers-reduced-motion

Add JavaScript support:

// src/contexts/motion-preference.tsx

export function MotionPreferenceProvider({ children }) {
  const prefersReduced = useReducedMotion();
  
  return (
    <MotionConfig reducedMotion={prefersReduced ? 'always' : 'never'}>
      {children}
    </MotionConfig>
  );
}

4.2 Skip Animation Button

Add to WelcomeScreen:

<motion.button
  onClick={handleClose}
  className="absolute right-8 top-8 text-muted-foreground"
  variants={itemVariants}
  aria-label="Skip intro animation and enter portfolio"
>
  Skip Intro
</motion.button>

5. Mobile-Specific Optimizations

5.1 Disable Heavy Effects

Effect Desktop Mobile Rationale
Fluid Cursor ✅ Enabled ❌ Disabled No cursor on touch
Cursor Trail ✅ Enabled ❌ Disabled No cursor on touch
Welcome Particles 15 5 Battery savings
Page Transition 3 layers 1 layer Faster navigation
Backdrop blur ✅ Full 0.5 opacity GPU performance

5.2 Conditional CSS

/* globals.css */

@media (max-width: 768px) {
  .backdrop-blur-lg {
    backdrop-filter: blur(4px);  /* Reduced from default */
  }
  
  .animate-bounce,
  .animate-pulse {
    animation: none;
  }
}

6. Testing & Verification Plan

6.1 Automated Tests

Existing Infrastructure: Jest + React Testing Library configured

# Run existing tests
npm test

# Run with coverage
npm run test:coverage

6.2 Performance Testing

  1. Lighthouse Audit (run in Chrome DevTools)

    • Target: Performance score > 90
    • Target: First Contentful Paint < 1.5s
    • Target: Largest Contentful Paint < 2.5s
  2. Bundle Size Check

    npm run analyze
  3. Animation Frame Rate

    • Open Chrome DevTools → Performance tab
    • Record while navigating
    • Target: Consistent 60fps on desktop, 30fps+ on mobile

6.3 Manual Testing Checklist

  • Page transitions work smoothly
  • Welcome screen can be dismissed
  • Skills pills animate on scroll
  • Project cards hover effects work
  • Theme switching doesn't break animations
  • Mobile navigation is smooth
  • Reduced motion preference is respected

7. Implementation Priority

Priority Task Impact Effort
🔴 High Disable fluid cursor on mobile High Low
🔴 High Add cleanup to FlipWords Medium Low
🟡 Medium Reduce welcome particles Medium Low
🟡 Medium Dynamic import heavy components High Medium
🟡 Medium Consolidate Fade components Low Medium
🟢 Low Object pooling for cursor trail Low High
🟢 Low TypeScript for useFluidCursor Low High

8. Quick Wins (Can implement immediately)

  1. Add next/dynamic imports for FluidCursor, WelcomeScreen
  2. Reduce particle count in WelcomeScreen from 15 → 8
  3. Add device detection to disable cursor effects on mobile
  4. Add clearTimeout cleanup to FlipWords
  5. Reduce animation offsets (y: 80→40, x: -100→-60)

Summary

The portfolio has a strong foundation with thoughtful attention to accessibility (reduced motion support) and performance (mobile detection in page transitions). The main optimization opportunities are:

  1. Cursor effects - Currently running on all devices including mobile
  2. Welcome screen - Too many animated particles
  3. Bundle size - Dynamic imports can improve initial load
  4. Animation cleanup - Missing timeouts/intervals cleanup

Implementing the high-priority items should noticeably improve mobile performance and reduce battery drain without sacrificing the premium visual experience on desktop.