-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: master
Are you sure you want to change the base?
Conversation
WalkthroughThis 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
Sequence Diagram(s)Landing Page CompositionsequenceDiagram
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
AnimatedBackground FlowsequenceDiagram
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
Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
✅ Deploy Preview for kleros-website-v2 ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
There was a problem hiding this 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.tsLength of output: 105
Action Required: Update Gradient Colors for Consistency
We verified that the Tailwind configuration defines
"background-2-alpha"
(with valuergba(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 infrontend/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 integrationThis 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 animationThe 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:
- 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> ); }
- 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 flexibilityThe 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 classInline 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 namedThe 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 propsThe 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 devicesThe 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 performanceThe 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
⛔ 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 paddingThe padding increase from
p-6
top-8
provides more breathing room around the content, aligning with the PR objective for "more CtaCard padding".
45-45
: Consistent text size across breakpointsThe 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 componentProperly imports the new animated background component that will enhance the visual appeal of the application.
47-47
: Simplified DOM structureRemoving 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:
- Remove the static background color class
- Add the animated background component
- 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 ofsetInterval
/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 callMinimize 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:
Separates static elements from dynamic ones[1][4][10].<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>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:
- 1: https://gist.github.com/jaredwilli/5469626
- 2: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations
- 3: https://konvajs.org/docs/performance/Optimize_Animation.html
- 4: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas
- 5: https://gsap.com/community/forums/topic/23062-questions-about-performance-best-practices/
- 6: https://devhammer.net/2012/09/18/exploring-html5-canvas-part-7-optimizing-animations/
- 7: https://gsap.com/community/forums/topic/18097-performance-and-visual-optimizations/
- 8: https://cloudinary.com/blog/creating_html5_animations
- 9: https://remysharp.com/2015/07/13/optimising-a-canvas-animation
- 10: https://web.dev/articles/canvas-performance
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 managementThe 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 cleanupThe 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 tsLength 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 inStatsSection
), 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 importsThe 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 integrationThe 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 constraintsThe 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 UXThe 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 thefrontend/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 ofprimaryButton
:#!/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 thefrontend/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 interfaceThe 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 optimizationUsing 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 reinitializationThe 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-hiddenThe 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 depthReplacing 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 functionalityThe 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 logosThe 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 movementglitch-shift
creates a subtle visual distortion effect with both transform and filter propertiesThese 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.
There was a problem hiding this 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 usesbg-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)
📒 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 infrontend/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.
// 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, | ||
}; | ||
}; | ||
|
There was a problem hiding this comment.
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.
// 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, | |
}; | |
}; |
Summary by CodeRabbit
New Features
Style