Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experiment: animations, full width, smaller headers, more CtaCard padding #94

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

jaybuidl
Copy link
Member

@jaybuidl jaybuidl commented Apr 3, 2025

Summary by CodeRabbit

  • New Features

    • Introduced dynamic visual elements including an animated statistic display, a responsive stats section, a 3D scene feature, and an interactive scroll indicator.
    • Added a captivating animated background with interactive particle effects.
  • Style

    • Refined layouts and visual styling across the landing sections, hero area, partner showcase, footer, and call-to-action areas for a modern, responsive experience.
    • Enhanced background colors and animations for a more engaging user interface.
    • Updated typography with a new primary font for improved readability.

Copy link
Contributor

coderabbitai bot commented Apr 3, 2025

Walkthrough

This pull request introduces multiple enhancements to the frontend. It adds new dependencies for 3D rendering and type definitions while updating JSON formatting. Several new React components—such as AnimatedStat, IcosahedronScene, ScrollIndicator, StatsSection, and AnimatedBackground—are introduced, and existing components like Hero, HowKlerosWorks, TrustedBy, CtaCard, and Footer have been updated with revised layouts and styling. Additionally, the Tailwind CSS configuration receives updates with new colors, animations, text shadow utilities, and a plugin. The RootLayout is also refactored to integrate the animated background.

Changes

File(s) Summary
frontend/package.json Added dependencies: @react-three/drei@^10.0.5, @react-three/fiber@^9.1.1, three@^0.175.0; added dev dependency: @types/three@^0.175.0; fixed JSON formatting.
frontend/src/app/home/components/AnimatedStat.tsx; IcosahedronScene.tsx; ScrollIndicator.tsx; StatsSection.tsx Introduced new components (AnimatedStat, IcosahedronScene, ScrollIndicator, StatsSection) with specific functionalities and animations.
frontend/src/app/home/components/Hero.tsx; HowKlerosWorks.tsx; TrustedBy.tsx; Footer.tsx; CtaCard.tsx Updated existing components (Hero, HowKlerosWorks, TrustedBy, Footer, CtaCard) with revised structure, animations, and styling adjustments.
frontend/src/app/layout.tsx Integrated AnimatedBackground into the layout; removed extra wrapper and updated body/main class settings for improved structure.
frontend/src/components/ui/AnimatedBackground.tsx Added new AnimatedBackground component for dynamic canvas animations.
frontend/tailwind.config.ts Updated configuration with new alpha background colors, revised font size for "3xl", added keyframe animations (slow-bounce, glitch-shift), introduced a new text shadow utility, and incorporated a plugin for neon text shadow.

Sequence Diagram(s)

Landing Page Composition

sequenceDiagram
    participant Browser
    participant RootLayout
    participant AnimatedBackground
    participant Hero
    participant OtherComponents

    Browser->>RootLayout: Request landing page
    RootLayout->>AnimatedBackground: Render animated background
    RootLayout->>Hero: Render Hero section
    Hero->>Hero: Render 3D scene (IcosahedronScene) & scroll indicator (ScrollIndicator)
    RootLayout->>OtherComponents: Render remaining sections/components
Loading

AnimatedBackground Flow

sequenceDiagram
    participant Window
    participant AnimatedBackground
    participant ParticleEngine

    Window->>AnimatedBackground: onMount (initialize canvas)
    AnimatedBackground->>ParticleEngine: Initialize particles with random properties
    loop Animation Loop
        ParticleEngine->>AnimatedBackground: Update positions & handle collisions/mouse interaction
        AnimatedBackground->>Window: Draw updated particle frame
    end
    Window->>AnimatedBackground: onResize – update canvas size and reinitialize particles
Loading

Possibly related PRs

  • Refactor(frontend): add all colors #13: The changes in the main PR, specifically the addition of new colors in the Tailwind CSS configuration, are related to the modifications in the retrieved PR that also involve updates to the color definitions in the Tailwind configuration. Both PRs focus on enhancing the color palette used in the frontend.

Suggested reviewers

  • alcercu

Poem

I'm a rabbit in the code-filled glen,
Hopping through changes time and again.
New scenes and stats dance in my sight,
With animated magic shining bright.
I nibble on code and skip with glee,
Celebrating updates so spry and free!
🐰✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 1895fb1 and f61bcaa.

📒 Files selected for processing (3)
  • frontend/src/app/layout.tsx (2 hunks)
  • frontend/src/components/CtaCard.tsx (2 hunks)
  • frontend/src/styles/globals.css (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/src/styles/globals.css
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/components/CtaCard.tsx
  • frontend/src/app/layout.tsx

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

netlify bot commented Apr 3, 2025

Deploy Preview for kleros-website-v2 ready!

Name Link
🔨 Latest commit f61bcaa
🔍 Latest deploy log https://app.netlify.com/sites/kleros-website-v2/deploys/67efc8bbca1e420008d95e66
😎 Deploy Preview https://deploy-preview-94--kleros-website-v2.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
frontend/src/app/home/components/TrustedBy.tsx (1)

91-102: 💡 Verification agent

🧩 Analysis chain

Check gradient alignment with new background

The BlurredBorders component still uses "from-background-2" and "to-background-2" for its gradients, but the parent container now uses "bg-background-2-alpha". This might create a visual mismatch. Consider updating the gradient colors to match the new background.

-        "bg-gradient-to-r from-background-2 to-transparent",
+        "bg-gradient-to-r from-background-2-alpha to-transparent",
-        "bg-gradient-to-r from-transparent to-background-2",
+        "bg-gradient-to-r from-transparent to-background-2-alpha",

🏁 Script executed:

#!/bin/bash
# Check if background-2-alpha is defined in the tailwind config
rg "background-2-alpha" frontend/tailwind.config.ts

Length of output: 105


Action Required: Update Gradient Colors for Consistency

We verified that the Tailwind configuration defines "background-2-alpha" (with value rgba(34, 0, 80, 0.4)), confirming that the parent container’s background is set correctly. To avoid a visual mismatch, please update the gradient color classes in frontend/src/app/home/components/TrustedBy.tsx to reference "background-2-alpha" instead of "background-2".

  • Update from:
    - "bg-gradient-to-r from-background-2 to-transparent",
    + "bg-gradient-to-r from-background-2-alpha to-transparent",
  • And update from:
    - "bg-gradient-to-r from-transparent to-background-2",
    + "bg-gradient-to-r from-transparent to-background-2-alpha",
🧹 Nitpick comments (11)
frontend/src/app/home/components/StatsSection.tsx (1)

1-15: Well-structured responsive stats component with animation integration

This component properly handles responsive behavior by only rendering on large screens. The integration with the AnimatedStat component provides an engaging visual presentation of statistics.

Consider extracting the hardcoded stats values to props or a configuration object for easier maintenance if these values need to be updated regularly:

-export function StatsSection() {
+interface StatsData {
+  jurorsPaid: string;
+  stakedPercentage: string;
+}
+
+export function StatsSection({ 
+  jurorsPaid = "$200K+", 
+  stakedPercentage = "35%" 
+}: Partial<StatsData> = {}) {
  const screenSize = useScreenSize();
  
  return screenSize === "lg" ? (
    <div className="flex flex-wrap gap-16 pt-28">
-      <AnimatedStat value="$200K+" label="Paid to jurors" />
-      <AnimatedStat value="35%" label="Staked on Court" />
+      <AnimatedStat value={jurorsPaid} label="Paid to jurors" />
+      <AnimatedStat value={stakedPercentage} label="Staked on Court" />
    </div>
  ) : null;
}
frontend/src/app/home/components/ScrollIndicator.tsx (1)

1-24: Well-implemented scroll indicator with animation

The scroll indicator is visually appealing with a clean implementation using an SVG icon and animation. The positioning at the bottom of the viewport is appropriate for this UI element.

Consider adding an accessibility attribute to indicate this is a decorative element:

-    <div className="absolute bottom-[10%] left-1/2 -translate-x-1/2 animate-slow-bounce">
+    <div className="absolute bottom-[10%] left-1/2 -translate-x-1/2 animate-slow-bounce" aria-hidden="true">
frontend/src/app/home/components/IcosahedronScene.tsx (2)

8-65: Well-implemented 3D component with smooth animation logic.

The rotating icosahedron is nicely implemented with smooth animations and layered visual effects. The use of comments to explain each mesh's purpose is excellent.

Consider adding cleanup for Three.js resources when the component unmounts to prevent memory leaks:

function RotatingIcosahedron() {
  const meshRef = useRef<THREE.Mesh>(null);
  const groupRef = useRef<THREE.Group>(null);
+  const materialRef = useRef<THREE.Material>(null);

  useFrame((state) => {
    if (!meshRef.current || !groupRef.current) return;
    
    // Gentle floating motion
    groupRef.current.position.y = Math.sin(state.clock.elapsedTime * 0.5) * 0.2;
    
    // Smooth rotation
    meshRef.current.rotation.x = Math.sin(state.clock.elapsedTime * 0.5) * 0.2;
    meshRef.current.rotation.y += 0.005;
  });

+  // Clean up Three.js resources on unmount
+  useEffect(() => {
+    return () => {
+      if (materialRef.current) {
+        materialRef.current.dispose();
+      }
+    };
+  }, []);

  return (
    // Component JSX remains the same
  );
}

67-83: Consider accessibility and performance optimizations.

The scene looks great, but could benefit from some improvements for accessibility and performance.

Consider these enhancements:

  1. Add prefers-reduced-motion support:
export function IcosahedronScene() {
+  const prefersReducedMotion = useEffect(() => {
+    return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+  }, []);

  return (
    <div className="absolute left-0 top-0 h-full w-full bg-gradient-to-b from-background-1/30 to-background-1/70">
      <Canvas
        camera={{
          position: [0, 0, 6],
          fov: 75,
        }}
+        frameloop={prefersReducedMotion ? 'demand' : 'always'}
      >
        {/* Rest of the component remains the same */}
      </Canvas>
    </div>
  );
}
  1. Add error boundary to handle WebGL context initialization failures:
+import { ErrorBoundary } from 'react-error-boundary';

export function IcosahedronScene() {
  return (
    <div className="absolute left-0 top-0 h-full w-full bg-gradient-to-b from-background-1/30 to-background-1/70">
+      <ErrorBoundary fallback={<div className="hidden">3D scene not available</div>}>
        <Canvas
          camera={{
            position: [0, 0, 6],
            fov: 75,
          }}
        >
          {/* Canvas content */}
        </Canvas>
+      </ErrorBoundary>
    </div>
  );
}
frontend/src/app/home/components/AnimatedStat.tsx (2)

57-69: Consider adding a delay parameter for more flexibility

The animation delay is hardcoded at 500ms. Consider making this configurable through props to allow for more control when using this component in different contexts.

 interface AnimatedStatProps {
   value: string;
   label: string;
   secondaryValue?: string;
+  startDelay?: number;
 }

 export function AnimatedStat({ value, label, secondaryValue }: AnimatedStatProps) {
   // ...
   
   // Start the animation after initial delay
   useEffect(() => {
-    const startDelay = setTimeout(() => {
+    const startDelay = setTimeout(() => {
       setDisplayedChars(prev => {
         const next = [...prev];
         next[0] = value[0]; // Stabilize first character immediately
         return next;
       });
       setCurrentIndex(1); // Start with second character
-    }, 500);
+    }, startDelay || 500);

87-91: Consider extracting inline styles to a Tailwind class

Inline styles for transform could be moved to a Tailwind class for better consistency with the codebase style approach. This would also make the component easier to maintain.

-              style={{
-                transform: index >= currentIndex && isGlitching
-                  ? `translate(${Math.random() * 1 - 0.5}px, ${Math.random() * 1 - 0.5}px)`
-                  : 'none'
-              }}
+              className={
+                index >= currentIndex && isGlitching
+                  ? "glitch-translate"
+                  : ""
+              }

Add the appropriate class in your Tailwind config:

// In your tailwind.config.js
theme: {
  extend: {
    animation: {
      'glitch-translate': 'glitchTranslate 0.1s ease-in-out infinite',
    },
    keyframes: {
      glitchTranslate: {
        '0%, 100%': { transform: 'translate(0px, 0px)' },
        '25%': { transform: 'translate(0.5px, -0.5px)' },
        '50%': { transform: 'translate(-0.5px, 0.5px)' },
        '75%': { transform: 'translate(-0.5px, -0.5px)' },
      },
    },
  },
}
frontend/src/app/home/components/Hero.tsx (1)

50-55: ExternalLink wrapper could be more semantically named

The ExternalLink component with "[&>span]:text-base [&>span]:text-primary-text" styling is used for the arrowLink. Consider using a more specific name or comment to indicate its purpose as a "Learn more" or similar link.

frontend/src/components/ui/AnimatedBackground.tsx (3)

25-33: Consider exposing configuration options as props

The animation configuration is hardcoded in the component. Consider exposing these as props with defaults to make the component more reusable in different contexts.

+interface AnimatedBackgroundProps {
+  particleCount?: number;
+  particleRadius?: { min: number; max: number };
+  particleSpeed?: { min: number; max: number };
+  connectionDistance?: number;
+  mouseRadius?: number;
+  particleColor?: string;
+}
+
-export function AnimatedBackground() {
+export function AnimatedBackground({
+  particleCount = 80,
+  particleRadius = { min: 2, max: 4 },
+  particleSpeed = { min: 0.2, max: 0.5 },
+  connectionDistance = 150,
+  mouseRadius = 150,
+  particleColor = 'rgba(151, 71, 255, 0.8)'
+}: AnimatedBackgroundProps = {}) {
   // ...

   // Configuration
   const config = {
-    particleCount: 80,
-    particleRadius: { min: 2, max: 4 },
-    particleSpeed: { min: 0.2, max: 0.5 },
-    connectionDistance: 150,
-    mouseRadius: 150,
+    particleCount,
+    particleRadius,
+    particleSpeed,
+    connectionDistance,
+    mouseRadius,
+    particleColor,
   };

68-115: Add performance optimization for lower-end devices

The animation loop updates all particles on every frame without considering device performance. Consider adding a performance optimization strategy such as reducing particle count or animation complexity on lower-end devices.

You could add a performance detection mechanism:

 useEffect(() => {
+  // Detect performance capabilities
+  let performanceLevel = 'high';
+  const testStart = performance.now();
+  let counter = 0;
+  for (let i = 0; i < 1000000; i++) counter++;
+  const testEnd = performance.now();
+  
+  // Adjust based on performance test
+  if (testEnd - testStart > 50) performanceLevel = 'low';
+  
+  // Adjust config based on performance
+  const adjustedParticleCount = performanceLevel === 'low' 
+    ? Math.floor(config.particleCount / 2) 
+    : config.particleCount;
   
   // Configuration
   const config = {
-    particleCount: 80,
+    particleCount: adjustedParticleCount,
     particleRadius: { min: 2, max: 4 },
     // ...
   };

117-128: Consider using requestIdleCallback for better performance

The particle rendering might cause performance issues on slower devices. Consider using requestIdleCallback for non-critical updates to avoid affecting main thread performance during scrolling or other interactions.

 // Animation loop
 function animate() {
   if (!ctx || !canvas) return;

   ctx.clearRect(0, 0, canvas.width, canvas.height);

+  // Use requestIdleCallback if available for non-critical updates
+  const updateParticles = () => {
     // Update and draw particles
     particlesRef.current.forEach((particle, i) => {
       // ...existing particle update code...
     });
+  };
+
+  if (window.requestIdleCallback) {
+    window.requestIdleCallback(() => {
+      updateParticles();
+      requestAnimationFrame(animate);
+    }, { timeout: 16 }); // Fallback timeout of ~60fps
+  } else {
+    updateParticles();
     requestAnimationFrame(animate);
+  }
 }
frontend/tailwind.config.ts (1)

76-78: Good addition of text shadow property.

The 'cyberpunk' text shadow effect adds a glow that complements the project's color scheme. Consider adding a few more text shadow variations if this styling will be used extensively throughout the application.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5b1765 and b7f4ef6.

⛔ Files ignored due to path filters (1)
  • frontend/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (13)
  • frontend/package.json (2 hunks)
  • frontend/src/app/home/components/AnimatedStat.tsx (1 hunks)
  • frontend/src/app/home/components/Hero.tsx (2 hunks)
  • frontend/src/app/home/components/HowKlerosWorks.tsx (1 hunks)
  • frontend/src/app/home/components/IcosahedronScene.tsx (1 hunks)
  • frontend/src/app/home/components/ScrollIndicator.tsx (1 hunks)
  • frontend/src/app/home/components/StatsSection.tsx (1 hunks)
  • frontend/src/app/home/components/TrustedBy.tsx (1 hunks)
  • frontend/src/app/layout.tsx (2 hunks)
  • frontend/src/components/CtaCard.tsx (2 hunks)
  • frontend/src/components/Footer.tsx (1 hunks)
  • frontend/src/components/ui/AnimatedBackground.tsx (1 hunks)
  • frontend/tailwind.config.ts (3 hunks)
🧰 Additional context used
🧬 Code Definitions (3)
frontend/src/app/home/components/StatsSection.tsx (1)
frontend/src/app/home/components/AnimatedStat.tsx (1)
  • AnimatedStat (18-104)
frontend/src/app/layout.tsx (1)
frontend/src/components/ui/AnimatedBackground.tsx (1)
  • AnimatedBackground (13-153)
frontend/src/app/home/components/Hero.tsx (3)
frontend/src/app/home/components/IcosahedronScene.tsx (1)
  • IcosahedronScene (67-83)
frontend/src/app/home/components/StatsSection.tsx (1)
  • StatsSection (6-15)
frontend/src/app/home/components/ScrollIndicator.tsx (1)
  • ScrollIndicator (3-24)
🔇 Additional comments (30)
frontend/src/components/CtaCard.tsx (2)

33-33: Improved spacing with increased padding

The padding increase from p-6 to p-8 provides more breathing room around the content, aligning with the PR objective for "more CtaCard padding".


45-45: Consistent text size across breakpoints

The heading now uses the same size (text-lg) for both mobile and desktop views, removing the previously larger desktop size. This aligns with the PR objective for "smaller headers".

frontend/src/app/layout.tsx (3)

8-8: Added import for AnimatedBackground component

Properly imports the new animated background component that will enhance the visual appeal of the application.


47-47: Simplified DOM structure

Removing the unnecessary wrapper div around children helps maintain a cleaner DOM structure.


43-45:

✅ Verification successful

Integrated animated background with proper z-index layering

The changes correctly:

  1. Remove the static background color class
  2. Add the animated background component
  3. Set proper z-index and positioning on the main content container

This implementation ensures the content remains above the animated background while maintaining the visual hierarchy.

Since animated backgrounds can impact performance on low-end devices, consider monitoring performance metrics after deployment.


🌐 Web query:

What are best practices for optimizing canvas animations for performance?

💡 Result:

Optimizing canvas animations requires a combination of efficient rendering techniques, smart resource management, and leveraging browser capabilities. Here are key best practices:

Animation Timing and Control

  • Use requestAnimationFrame instead of setInterval/setTimeout for smoother frame synchronization with browser refresh rates and background tab throttling[1][2][6][9].
  • Throttle FPS using the timestamp parameter in requestAnimationFrame to maintain consistent frame rates (e.g., 30/60 FPS)[9].

Efficient Rendering Techniques

  • Pre-render complex elements:

    // Off-screen canvas setup
    const offscreenCanvas = document.createElement('canvas');
    const offscreenCtx = offscreenCanvas.getContext('2d');
    // Pre-draw complex graphics once
    offscreenCtx.drawImage(...);
    // Reuse in main canvas
    mainCtx.drawImage(offscreenCanvas, 0, 0);

    Reduces per-frame computation for static/repeated elements[1][4][8][10].

  • Batch drawing operations:

    • Group similar drawing commands together
    • Minimize state changes (styles, transformations)[4][10]
    • Combine paths before stroking/filling[9]:
    ctx.beginPath();
    for (const shape of shapes) {
      ctx.moveTo(...);
      ctx.lineTo(...);
    }
    ctx.stroke(); // Single draw call

Minimize Redraw Operations

  • Clear only changed regions with clearRect(x, y, w, h) instead of full canvas clears[1][4][10].
  • Implement dirty rectangle tracking to redraw only modified screen areas[10].
  • Use CSS for static backgrounds instead of canvas drawing when possible[4].

Layer Management

  • Use multiple layered canvases for complex scenes:
    <div class="canvas-stack">
      <canvas id="background" style="z-index: 0"></canvas>
      <canvas id="main" style="z-index: 1"></canvas>
      <canvas id="ui" style="z-index: 2"></canvas>
    </div>
    Separates static elements from dynamic ones[1][4][10].

Optimization Strategies

  • Work with integers for coordinates to avoid subpixel anti-aliasing overhead[4][10].
  • Avoid scaling images during rendering - pre-scale in off-screen buffers[4].
  • Disable alpha channel when transparent backgrounds aren't needed:
    const ctx = canvas.getContext('2d', { alpha: false })[4].
  • Cache complex paths and reuse them with Path2D objects[3].

Advanced Techniques

  • Use CSS transforms for canvas scaling rather than manual resizing[4]:
    canvas.style.transform = `scale(${scaleFactor})`;
  • Implement object pooling to reuse animation objects instead of recreating them[8].
  • Profile with browser tools to identify performance bottlenecks specific to your implementation.

For game development or complex animations, consider libraries like Konva.js that optimize batch drawing and provide caching mechanisms[3]. When combining these techniques, test across target browsers as optimization effectiveness can vary between implementations[6][9].

Citations:


Animated Background Integration Verified

The changes in frontend/src/app/layout.tsx (lines 43–45) are correctly implemented:

  • The static background class has been removed.
  • The <AnimatedBackground /> component has been integrated.
  • The main content container is properly layered above the animated background with correct z-index settings.

As a recommendation, keep an eye on performance metrics post-deployment. For optimizing canvas animations, consider best practices such as using requestAnimationFrame for smoother animations, pre-rendering complex elements on an off-screen canvas, and batching drawing operations to reduce per-frame overhead.

frontend/package.json (1)

15-16: LGTM! Dependencies added to support the 3D animations.

The addition of Three.js and React Three Fiber libraries is consistent with the PR objectives to experiment with animations. All dependencies are properly versioned and formatted.

Also applies to: 25-26, 32-32

frontend/src/components/Footer.tsx (2)

23-56: LGTM! Styling updates for a more modern look.

The changes to make the footer full-width with backdrop blur effects align well with the PR objectives. The transition from solid backgrounds to semi-transparent with blur creates a more modern aesthetic.


57-89: Consistent styling applied to second footer section.

The backdrop blur and alpha transparency are consistently applied across both footer sections, maintaining visual harmony.

frontend/src/app/home/components/HowKlerosWorks.tsx (1)

19-46: LGTM! Consistent styling with the updated design approach.

The changes to make this section full-width with a semi-transparent background and backdrop blur align perfectly with the PR objectives and maintain consistency with other updated components like the Footer.

The addition of the max-width container is a good practice to ensure content remains readable on large screens while the background extends full-width.

frontend/src/app/home/components/AnimatedStat.tsx (3)

18-24: Good component design with clear state management

The component initialization with multiple useState hooks effectively manages the animation state. The initial state of filling the array with placeholder 'X' characters is a good approach for the animation setup.


25-55: Well-implemented animation logic with proper cleanup

The animation logic using useEffect with interval and timeout is well structured. The component properly cleans up timers to prevent memory leaks, and the dependencies array is correctly specified.


80-84:

✅ Verification successful

Potential performance consideration with complex conditional classes

The use of complex conditional classes with clsx is effective, but be aware that for large datasets this can impact rendering performance. Consider extracting the conditional class logic to a separate function if this component will be used for rendering many items simultaneously.


🏁 Script executed:

#!/bin/bash
# Check if this component is used in a list rendering context
rg -A 5 -B 5 "AnimatedStat" --type ts

Length of output: 2896


Performance Consideration – Usage Confirmed Minimal

After verifying, the AnimatedStat component is currently used in a limited context (just a couple of instances in StatsSection), so the current impact of its complex conditional classes is negligible. However, if this component’s usage expands to render many items simultaneously in the future, consider refactoring the conditional logic into a helper function to further optimize performance.

  • Location: frontend/src/app/home/components/AnimatedStat.tsx (Lines 80-84)
frontend/src/app/home/components/Hero.tsx (5)

8-11: Good component restructuring with relevant imports

The addition of IcosahedronScene, ScrollIndicator, and StatsSection improves the modularity of the Hero component. This separation of concerns makes the code more maintainable.


24-28: Well-structured layering with 3D element integration

The use of absolute positioning for the IcosahedronScene alongside relative positioning with z-index creates a good layering effect. This allows for a visually appealing 3D background without affecting content positioning.


29-34: Good responsive text sizing with proper width constraints

The max-width constraints on title and subtitle prevent text from stretching too wide on large screens, improving readability. The responsive text sizing (lg:text-5xl, lg:text-xl) ensures appropriate scaling across devices.


58-58: Good addition of scroll indicator for improved UX

The ScrollIndicator helps users understand that there's more content below the fold, improving user experience especially with the full-height hero section.


35-49:

✅ Verification successful

Verify primary button visibility on desktop

The primary button is hidden on desktop (lg:hidden), but there's no corresponding desktop-only version. This appears to be intentional, but confirm that the primary button is not needed on desktop layouts.


🏁 Script executed:

#!/bin/bash
# Check if there's another instance of primaryButton for desktop view
rg "primaryButton" --type tsx frontend/src/app/home/

Length of output: 80


Below is a revised review comment. Since the original rg --type tsx command failed due to an unrecognized file type, please re-run the search in all TSX files using the updated command and verify that no alternative instance of the primary button exists for desktop layouts.


Confirm Primary Button Omission on Desktop

The primary button is wrapped in a <div className="lg:hidden">, which hides it on desktop. Please verify that this design decision is intentional and that no alternate rendering of the primary button exists for desktop views.

  • To help with this verification, run the following script to search for any occurrences of primaryButton in all TSX files under the frontend/src/app/home/ directory:

    #!/bin/bash
    rg -g '*.tsx' "primaryButton" frontend/src/app/home/
  • If the search returns no desktop-specific instances, confirm that the omission of the primary button on desktop layouts is intentional or document any alternate design decisions.



Below is a revised review comment that suggests running an updated search command to verify that no desktop instance of the primary button exists:


Confirm Primary Button Omission on Desktop

The primary button is wrapped in a <div className="lg:hidden">, which hides it on desktop. Please verify that this design decision is intentional and that there isn’t an alternate rendering for desktop layouts.

  • Run an updated search command for TSX files under frontend/src/app/home/ to check for any occurrences of primaryButton:

    #!/bin/bash
    rg -g '*.tsx' "primaryButton" frontend/src/app/home/
  • If the search returns no desktop-specific references, confirm that the primary button omission on desktop is intentional. Otherwise, document the alternative logic if needed.



Below is an updated review comment that instructs manual verification through a new shell script search for TSX files. This will help confirm that the primary button is intentionally omitted from desktop layouts.


Confirm Primary Button Omission on Desktop

The primary button is enclosed in a <div className="lg:hidden">, which hides it on desktop. To ensure this design decision is intentional, please verify that there is no alternate rendering (i.e., no desktop-specific instance of the primary button) by running the following search command over TSX files in the frontend/src/app/home/ directory:

If the search confirms that the primary button is only used within a mobile-only context, then this implementation is correct. Otherwise, please update or document the desktop behavior accordingly.



🏁 Script executed:

#!/bin/bash
rg -g "*.tsx" "primaryButton" frontend/src/app/home/

Length of output: 581


Primary Button Visibility on Desktop Confirmed

The search results indicate that the only rendered instance of the primary button in the Hero component is within the <div className="lg:hidden">, which restricts its visibility to mobile. The other matches in the queries file pertain to data definitions rather than UI rendering. Please confirm that the primary button’s omission from desktop layouts is intentional.

frontend/src/components/ui/AnimatedBackground.tsx (4)

5-11: Well-defined Particle interface

The Particle interface clearly defines the properties needed for each particle in the animation, making the code more maintainable and type-safe.


13-17: Good use of refs for performance optimization

Using useRef for canvas, particles, and mouse position prevents unnecessary re-renders while maintaining state across animation frames.


34-50: Good handling of canvas resizing with particle reinitialization

The component properly handles window resizing by updating canvas dimensions and reinitializing particles, ensuring the animation looks good at any screen size.


146-152: Good accessibility consideration with aria-hidden

The canvas element is correctly marked with aria-hidden="true" since it's a decorative element, and pointer-events-none ensures it doesn't interfere with user interactions.

frontend/src/app/home/components/TrustedBy.tsx (3)

16-17: Good use of backdrop blur for visual depth

Replacing solid background with semi-transparent background and backdrop blur creates a more modern, layered visual effect. The addition of max-width constraint improves content layout consistency across screen sizes.


21-31: Improved carousel structure with hover-pause functionality

The new grid structure with hover-pause-child-animation class creates a better user experience by allowing users to pause the carousel animation on hover.


32-49: Enhanced responsive layout for institution logos

The changed layout from grid to flexbox with responsive modifiers improves how institution logos are displayed across different screen sizes. The use of flex-col on mobile and flex-row on desktop with appropriate spacing creates a better visual hierarchy.

frontend/tailwind.config.ts (6)

2-2: Good addition of the Tailwind plugin import.

Adding the plugin import enables the custom utility class defined later in the file. This is a necessary first step for extending Tailwind's functionality.


17-17: Font reduction for 3xl size aligns with PR objectives.

Reducing the font size from 4rem to 3.75rem helps achieve the "smaller headers" goal mentioned in the PR objectives while maintaining the same line height for consistency.


37-39: Well-structured alpha color variants.

The addition of alpha variants for your background colors follows good naming conventions and maintains consistent opacity values (0.4). These semi-transparent versions will be useful for layering effects in the new animated components.


46-69: Well-implemented animation keyframes.

The new animation keyframes are well-structured:

  • slow-bounce uses cubic-bezier timing functions for natural movement
  • glitch-shift creates a subtle visual distortion effect with both transform and filter properties

These will pair well with the new animated components mentioned in the PR summary.


73-75: Animation configurations look good.

The animation durations and iteration settings are appropriate:

  • 2s for the slow bounce creates a subtle, non-distracting effect
  • 0.15s for the glitch shift provides the rapid visual distortion typical of glitch effects

Both set to infinite for continuous animation.


81-95: Well-implemented custom utility via plugin.

The text-shadow-neon utility is properly implemented using the plugin API. The multi-layered shadow creates a nice depth effect using the project's color palette. This will pair well with the new animated components mentioned in the PR summary.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (8)
frontend/src/app/home/components/IcosahedronScene.tsx (2)

9-22: Consider adding performance optimizations for the animation.

The animation looks good, but continuously updating the scene can be resource-intensive, especially on lower-end devices.

 useFrame((state) => {
   if (!meshRef.current || !groupRef.current) return;

+  // Skip frames for performance optimization on slower devices
+  if (state.clock.elapsedTime % 2 === 0 && state.clock.delta > 0.1) return;
+
   // Gentle floating motion
   groupRef.current.position.y = Math.sin(state.clock.elapsedTime * 0.5) * 0.2;

   // Smooth rotation
   meshRef.current.rotation.x = Math.sin(state.clock.elapsedTime * 0.5) * 0.2;
   meshRef.current.rotation.y += 0.005;
 });

68-84: Add accessibility features to enhance user experience.

While visually impressive, the 3D scene lacks accessibility features for users who might prefer reduced motion or use screen readers.

 export function IcosahedronScene() {
+  const prefersReducedMotion = 
+    typeof window !== 'undefined' && 
+    window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
+
   return (
-    <div className="absolute left-0 top-0 h-full w-full bg-gradient-to-b from-background-1/30 to-background-1/70">
+    <div 
+      className="absolute left-0 top-0 h-full w-full bg-gradient-to-b from-background-1/30 to-background-1/70"
+      aria-hidden="true"
+    >
       <Canvas
         camera={{
           position: [0, 0, 6],
           fov: 75,
+          far: 100,
         }}
+        frameloop={prefersReducedMotion ? "demand" : "always"}
       >
         <ambientLight intensity={0.4} />
         <pointLight position={[10, 10, 10]} intensity={1.2} />
         <pointLight position={[-10, -10, -10]} intensity={0.6} />
         <RotatingIcosahedron />
       </Canvas>
     </div>
   );
 }
frontend/src/app/home/components/Hero.tsx (2)

16-17: The tokenStats property is destructured from the API but not used.

The tokenStats property from homePageHero is not being used in the component anymore, but it's still being fetched in the GraphQL query.

Consider updating the GraphQL query to remove the tokenStats field if it's no longer needed:

// In ../queries/hero.tsx
export const heroQuery = gql`
  {
    homePageHero {
      title
      subtitle
      primaryButton {
        text
        link {
          url
        }
      }
      secondaryButton {
        text
        link {
          url
        }
      }
      arrowLink {
        text
        link {
          url
        }
      }
-     tokenStats {
-       key
-       primaryValue
-       secondaryValue
-     }
      background {
        url
      }
    }
  }
`;

56-56: Consider conditional StatsSection rendering based on data availability.

The StatsSection is rendered unconditionally but internally only displays on large screens. Consider passing the relevant data as props to make the component more reusable.

- <StatsSection />
+ <StatsSection jurorsPaid="$200K+" stakedPercentage="35%" />
frontend/src/app/home/components/TrustedBy.tsx (1)

90-104: Consider updating gradient references to match the new background color.

The BlurredBorders component still references bg-background-2 in the gradients, but the parent component now uses bg-background-2-alpha.

  <div
    className={clsx(
      "absolute bottom-0 left-0 right-[90%] top-0",
-     "bg-gradient-to-r from-background-2 to-transparent",
+     "bg-gradient-to-r from-background-2-alpha to-transparent",
    )}
  />
  <div
    className={clsx(
      "absolute bottom-0 left-[90%] right-0 top-0",
-     "bg-gradient-to-r from-transparent to-background-2",
+     "bg-gradient-to-r from-transparent to-background-2-alpha",
    )}
  />
frontend/src/components/ui/AnimatedBackground.tsx (3)

18-40: Consider debouncing the resize event handler for better performance.

Resizing the window and reinitializing particles on every resize event can be performance-intensive. Debouncing this event would reduce the computational load.

+import { useEffect, useRef, useCallback } from "react";
+
+function debounce<T extends (...args: any[]) => any>(
+  func: T,
+  wait: number
+): (...args: Parameters<T>) => void {
+  let timeout: ReturnType<typeof setTimeout> | null = null;
+  return (...args: Parameters<T>) => {
+    if (timeout) clearTimeout(timeout);
+    timeout = setTimeout(() => func(...args), wait);
+  };
+}

useEffect(() => {
  const canvas = canvasRef.current;
  if (!canvas) return;

  const ctx = canvas.getContext("2d");
  if (!ctx) return;

  // Configuration
  const config = {
    particleCount: 80,
    particleRadius: { min: 2, max: 4 },
    particleSpeed: { min: 0.2, max: 0.5 },
    connectionDistance: 150,
    mouseRadius: 150,
  };

  // Set canvas size
+  const setCanvasSize = useCallback(() => {
-  const setCanvasSize = () => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    initParticles(); // Reinitialize particles when canvas size changes
+  }, []);
+  
+  const debouncedResize = debounce(setCanvasSize, 200);
  };

67-144: Optimize the animation loop for better performance.

The animation loop is running at full speed and processing every particle on every frame, which can be CPU-intensive. Consider optimizing with frame skipping and reduced processing.

  // Animation loop
+  let frameCount = 0;
  function animate() {
    if (!ctx || !canvas) return;

+   // Skip frames for performance
+   frameCount++;
+   const shouldProcessAll = frameCount % 2 === 0;
+
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Update and draw particles
    particlesRef.current.forEach((particle, i) => {
      // Update position
      particle.x += particle.vx;
      particle.y += particle.vy;

      // Bounce off edges
      if (particle.x < 0 || particle.x > canvas.width) particle.vx *= -1;
      if (particle.y < 0 || particle.y > canvas.height) particle.vy *= -1;

      // Keep particles within bounds
      particle.x = Math.max(0, Math.min(canvas.width, particle.x));
      particle.y = Math.max(0, Math.min(canvas.height, particle.y));

      // Draw connections
+     if (shouldProcessAll) {
        for (let j = i + 1; j < particlesRef.current.length; j++) {
          const other = particlesRef.current[j];
          const dx = other.x - particle.x;
          const dy = other.y - particle.y;
          const distance = Math.sqrt(dx * dx + dy * dy);

          if (distance < config.connectionDistance) {
            const opacity = 1 - distance / config.connectionDistance;
            ctx.beginPath();
            ctx.strokeStyle = `rgba(151, 71, 255, ${opacity * 0.5})`;
            ctx.lineWidth = 1;
            ctx.moveTo(particle.x, particle.y);
            ctx.lineTo(other.x, other.y);
            ctx.stroke();
          }
        }
+     }

145-157: Add throttling for mouse move events.

Mouse move events can fire very frequently, potentially causing performance issues. Consider throttling these events.

+import { useEffect, useRef, useCallback } from "react";
+
+function throttle<T extends (...args: any[]) => any>(
+  func: T,
+  limit: number
+): (...args: Parameters<T>) => void {
+  let inThrottle: boolean = false;
+  return function(this: any, ...args: Parameters<T>): void {
+    if (!inThrottle) {
+      func.apply(this, args);
+      inThrottle = true;
+      setTimeout(() => (inThrottle = false), limit);
+    }
+  };
+}

  // Initialize
  setCanvasSize();
  window.addEventListener("resize", setCanvasSize);
-  canvas.addEventListener("mousemove", handleMouseMove);
+  canvas.addEventListener("mousemove", throttle(handleMouseMove, 30));
  animate();

  // Cleanup
  return () => {
    window.removeEventListener("resize", setCanvasSize);
-    canvas.removeEventListener("mousemove", handleMouseMove);
+    canvas.removeEventListener("mousemove", throttle(handleMouseMove, 30));
  };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between b7f4ef6 and 1895fb1.

📒 Files selected for processing (10)
  • frontend/src/app/home/components/AnimatedStat.tsx (1 hunks)
  • frontend/src/app/home/components/Hero.tsx (1 hunks)
  • frontend/src/app/home/components/HowKlerosWorks.tsx (1 hunks)
  • frontend/src/app/home/components/IcosahedronScene.tsx (1 hunks)
  • frontend/src/app/home/components/ScrollIndicator.tsx (1 hunks)
  • frontend/src/app/home/components/StatsSection.tsx (1 hunks)
  • frontend/src/app/home/components/TrustedBy.tsx (1 hunks)
  • frontend/src/app/layout.tsx (2 hunks)
  • frontend/src/components/Footer.tsx (1 hunks)
  • frontend/src/components/ui/AnimatedBackground.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • frontend/src/app/layout.tsx
  • frontend/src/app/home/components/StatsSection.tsx
  • frontend/src/components/Footer.tsx
  • frontend/src/app/home/components/ScrollIndicator.tsx
  • frontend/src/app/home/components/AnimatedStat.tsx
  • frontend/src/app/home/components/HowKlerosWorks.tsx
🧰 Additional context used
🧬 Code Definitions (1)
frontend/src/app/home/components/Hero.tsx (4)
frontend/src/app/home/queries/hero.tsx (2)
  • HeroQueryType (38-69)
  • heroQuery (3-36)
frontend/src/app/home/components/IcosahedronScene.tsx (1)
  • IcosahedronScene (68-84)
frontend/src/app/home/components/StatsSection.tsx (1)
  • StatsSection (7-16)
frontend/src/app/home/components/ScrollIndicator.tsx (1)
  • ScrollIndicator (3-24)
🔇 Additional comments (13)
frontend/src/app/home/components/IcosahedronScene.tsx (2)

1-7: Well structured imports and client-side directive.

The code correctly uses the "use client" directive since it relies on React hooks and browser-specific features. The imports are organized logically, separating React core from third-party libraries.


24-66: Good component structure with clear separation of concerns.

The component is well-structured with clear separation between the main icosahedron and its glow effects. The comments explain the purpose of each mesh, making the code maintainable.

frontend/src/app/home/components/Hero.tsx (4)

10-12: Good component composition with new visual elements.

The addition of the IcosahedronScene, ScrollIndicator, and StatsSection components enhances the visual appeal while maintaining a clean separation of concerns.


20-23: Good use of absolute positioning for the 3D background.

The IcosahedronScene is correctly positioned absolutely to fill the container while allowing content to be positioned on top with z-index.


59-59: Good addition of the ScrollIndicator for better UX.

The ScrollIndicator component provides a visual cue to users that there's more content below the fold, which can improve engagement.


32-50:

❓ Verification inconclusive

Primary CTA button is hidden on large screens.

The primary button is only visible on screens smaller than 'lg' breakpoint, which could impact conversion rates on desktop. Was this intentional?


🏁 Script executed:

#!/bin/bash
# Check if the primary button is visible elsewhere for large screens
rg -A 5 -B 5 "primaryButton" --glob "*.tsx"

Length of output: 3286


Primary CTA Button Visibility on Large Screens
I verified that the primary CTA button in frontend/src/app/home/components/Hero.tsx is wrapped in a <div className="lg:hidden">, making it visible only on screens smaller than the large breakpoint. Since there’s no other instance rendering this button for desktop, please confirm if hiding it on larger screens is an intentional design choice or if it should be visible to improve desktop conversion rates.

frontend/src/app/home/components/TrustedBy.tsx (4)

16-20: Improved visual design with semi-transparent background and backdrop blur.

The switch from a solid background to a semi-transparent one with backdrop blur creates a more modern, layered visual effect while maintaining readability.


21-31: Enhanced layout structure with proper container maxWidth.

The addition of maxWidth and improved container structure creates a more consistent layout across screen sizes, aligning with design best practices.


33-36: Improved responsive layout with flexbox.

The transition from a less specific grid to a more tailored flexbox layout provides better control over spacing and alignment across different screen sizes.


38-48:

❓ Verification inconclusive

Check if institution logos maintain proportions across screen sizes.

While the code structure looks good, ensure that institution logos maintain their proper aspect ratios when rendered in different container sizes.


🏁 Script executed:

#!/bin/bash
# Check if any width/height constraints are applied to these images elsewhere
rg -A 5 -B 5 "institution.*image" --glob "*.css"
rg -A 5 -B 5 "institution.*image" --glob "*.ts"

Length of output: 100


Attention: Verify That Logos Retain Their Aspect Ratio on All Devices

The updated code looks structurally correct, and automated searches didn’t reveal any overriding width/height constraints applied via CSS or TypeScript. However, since no concrete evidence was found—and the script output was inconclusive—it’s important to manually verify that the institution logos maintain proper proportions across different screen sizes. Please test the responsiveness in your development environment and adjust CSS if needed.

frontend/src/components/ui/AnimatedBackground.tsx (3)

1-12: Well-structured interface definition and client directive.

The code correctly uses the "use client" directive and defines a clean interface for particles with appropriate typing.


13-17: Good use of useRef for performance optimization.

Using refs for the canvas, particles array, and mouse position avoids unnecessary re-renders while maintaining state between animation frames.


159-166: Well-structured canvas element with appropriate attributes.

The canvas element is correctly configured with pointer-events-none to prevent interaction, aria-hidden="true" for accessibility, and proper positioning.

Comment on lines +41 to +66
// Initialize particles
const initParticles = () => {
particlesRef.current = Array.from(
{ length: config.particleCount },
() => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * config.particleSpeed.max,
vy: (Math.random() - 0.5) * config.particleSpeed.max,
radius:
Math.random() *
(config.particleRadius.max - config.particleRadius.min) +
config.particleRadius.min,
}),
);
};

// Update mouse position
const handleMouseMove = (e: MouseEvent) => {
const rect = canvas.getBoundingClientRect();
mouseRef.current = {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
};
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add support for reduced motion preferences.

The animation doesn't respect users' preferences for reduced motion, which could cause issues for those with vestibular disorders or motion sensitivity.

+  // Check user preference for reduced motion
+  const prefersReducedMotion =
+    typeof window !== "undefined" &&
+    window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
+
  // Initialize particles
  const initParticles = () => {
+    // Return fewer, slower particles for reduced motion preference
+    if (prefersReducedMotion) {
+      particlesRef.current = Array.from(
+        { length: Math.floor(config.particleCount / 3) },
+        () => ({
+          x: Math.random() * canvas.width,
+          y: Math.random() * canvas.height,
+          vx: (Math.random() - 0.5) * config.particleSpeed.min,
+          vy: (Math.random() - 0.5) * config.particleSpeed.min,
+          radius: config.particleRadius.min,
+        }),
+      );
+      return;
+    }
+
    particlesRef.current = Array.from(
      { length: config.particleCount },
      () => ({
        x: Math.random() * canvas.width,
        y: Math.random() * canvas.height,
        vx: (Math.random() - 0.5) * config.particleSpeed.max,
        vy: (Math.random() - 0.5) * config.particleSpeed.max,
        radius:
          Math.random() *
            (config.particleRadius.max - config.particleRadius.min) +
          config.particleRadius.min,
      }),
    );
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Initialize particles
const initParticles = () => {
particlesRef.current = Array.from(
{ length: config.particleCount },
() => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * config.particleSpeed.max,
vy: (Math.random() - 0.5) * config.particleSpeed.max,
radius:
Math.random() *
(config.particleRadius.max - config.particleRadius.min) +
config.particleRadius.min,
}),
);
};
// Update mouse position
const handleMouseMove = (e: MouseEvent) => {
const rect = canvas.getBoundingClientRect();
mouseRef.current = {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
};
};
// Check user preference for reduced motion
const prefersReducedMotion =
typeof window !== "undefined" &&
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
// Initialize particles
const initParticles = () => {
// Return fewer, slower particles for reduced motion preference
if (prefersReducedMotion) {
particlesRef.current = Array.from(
{ length: Math.floor(config.particleCount / 3) },
() => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * config.particleSpeed.min,
vy: (Math.random() - 0.5) * config.particleSpeed.min,
radius: config.particleRadius.min,
}),
);
return;
}
particlesRef.current = Array.from(
{ length: config.particleCount },
() => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * config.particleSpeed.max,
vy: (Math.random() - 0.5) * config.particleSpeed.max,
radius:
Math.random() *
(config.particleRadius.max - config.particleRadius.min) +
config.particleRadius.min,
}),
);
};
// Update mouse position
const handleMouseMove = (e: MouseEvent) => {
const rect = canvas.getBoundingClientRect();
mouseRef.current = {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
};
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant