-
Notifications
You must be signed in to change notification settings - Fork 36
feat: ✨ Add Festive Animations to Hacktoberfest Page #124
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
base: develop
Are you sure you want to change the base?
feat: ✨ Add Festive Animations to Hacktoberfest Page #124
Conversation
WalkthroughAdds multiple festive visual effect components (canvas particles, tsparticles background, 3D falling leaves and leaf mesh), a Hacktoberfest client wrapper, a prefers-reduced-motion hook, a CSS pulse-glow utility, and updates deps/TS config and scripts. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Page as /app/hacktoberfest/page.tsx
participant Client as HacktoberfestClientContent
participant RM as usePrefersReducedMotion
participant Effects as FestiveCanvasAnimation / FestiveBackground / FallingLeaves3D
participant Countdown as CleanCountdown
User->>Page: Request /hacktoberfest
Page->>Client: Render(active: true, eventYear: 2025, hacktoberfestEnd)
Client->>RM: Query reduced-motion
alt reduced-motion = true
Client-->>Effects: Skip visual effects (no mount)
else
Client->>Effects: Initialize canvases, particles, 3D scene, listeners
end
alt active = true
Client->>Countdown: Render countdown
else
Client-->>User: Render "Coming soon" message
end
Note over Effects: Effects run loops, spawn particles/leaves, attach mouse handlers\nand register cleanup on unmount
sequenceDiagram
autonumber
participant FL as FallingLeaves3D
participant RM as usePrefersReducedMotion
participant Canvas as @react-three/fiber Canvas
participant Leaf as Leaf3D (x N)
FL->>RM: Check preference
alt reduced-motion = true
FL-->>FL: return null (do not render)
else
FL->>Canvas: Mount scene (lights, env)
Canvas->>Leaf: Instantiate N leaves (viewport-aware)
loop each frame
Leaf->>Leaf: Apply gravity, wind, rotation
alt y < ground
Leaf->>Leaf: Reset position/rotation/scale/color
end
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧬 Code graph analysis (1)components/effects/falling-leaves-3d.tsx (2)
🔇 Additional comments (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
package.json (1)
25-27: tsparticles packages are mixed across major versions (v2 and v3) — unify or it will breakCode imports v3 engine (@tsparticles/engine) but also uses v2 packages (react-tsparticles, tsparticles-slim). This will cause type/runtime mismatches. Standardize on v3 across code and deps.
Apply this diff to move fully to v3:
"dependencies": { @@ - "@tsparticles/react": "^3.0.0", - "@tsparticles/slim": "^3.9.1", + "@tsparticles/react": "^3.9.1", + "@tsparticles/slim": "^3.9.1", @@ - "react-tsparticles": "^2.12.2", @@ - "tsparticles-slim": "^2.12.0",Then update imports in code to use @tsparticles/react and @tsparticles/slim (see festive-background.tsx comment).
Also applies to: 46-46, 51-51
🧹 Nitpick comments (10)
hooks/use-prefers-reduced-motion.ts (1)
8-20: Hook looks good; consider legacy listener fallback only if you target very old browsersImplementation is correct for modern browsers. If you need legacy Safari support, add addListener/removeListener fallback.
Example fallback:
- mediaQuery.addEventListener("change", listener); - return () => mediaQuery.removeEventListener("change", listener); + if (mediaQuery.addEventListener) { + mediaQuery.addEventListener("change", listener); + return () => mediaQuery.removeEventListener?.("change", listener); + } else { + // @ts-expect-error legacy + mediaQuery.addListener(listener); + // @ts-expect-error legacy + return () => mediaQuery.removeListener(listener); + }components/effects/festive-canvas-animation.tsx (3)
217-220: Spawn particles using the canvas width, not window widthUsing window.innerWidth can misplace particles if the canvas isn’t full-viewport. Use rect.width from the measured canvas.
- const particleCount = window.innerWidth < 768 ? 20 : 40; - particlesRef.current = Array.from({ length: particleCount }, () => new Particle(window.innerWidth)); + const particleCount = rect.width < 768 ? 20 : 40; + particlesRef.current = Array.from({ length: particleCount }, () => new Particle(rect.width));
155-166: Optional: clear the scaled canvas using CSS pixel unitsSince the context is scaled by DPR, clearing with canvas.width/height operates in device pixels and is larger than necessary. Use CSS pixel dimensions to avoid extra work.
- ctx.clearRect(0, 0, canvas.width, canvas.height); + const dpr = window.devicePixelRatio || 1; + ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);Also applies to: 168-176
178-199: DRY: Prefer the shared hook for reduced motionYou already have usePrefersReducedMotion. Consider using it here instead of duplicating matchMedia logic.
components/effects/festive-background.tsx (1)
9-20: Consider reusing existing components/ui/particles.tsxThere’s already a Particles wrapper that initializes the engine and merges options. Reusing it avoids duplicate init logic and keeps styling consistent.
Based on learnings; see components/ui/particles.tsx snippet.
Also applies to: 26-98, 100-112
app/globals.css (1)
352-367: Respect reduced motion and hint compositing for the glow animationAdd a motion-reduced override and will-change to avoid unnecessary work for users who prefer reduced motion.
@keyframes pulse-glow { 0%, 100% { box-shadow: 0 0 20px 0px rgba(255, 165, 0, 0.2); transform: scale(1); } 50% { box-shadow: 0 0 40px 10px rgba(255, 165, 0, 0.4); transform: scale(1.02); } } .animate-pulse-glow { border-radius: 9999px; /* Ensures the glow effect is rounded */ transition: box-shadow 0.5s ease-in-out, transform 0.5s ease-in-out; animation: pulse-glow 4s infinite ease-in-out; + will-change: transform, box-shadow; } + +@media (prefers-reduced-motion: reduce) { + .animate-pulse-glow { + animation: none; + transition: none; + } +}app/hacktoberfest/page.tsx (1)
4-8: Confirm forcing active=true (and consider timezone-safe date parsing)If this is for demo only, fine; otherwise compute active from start/end. Also consider appending “Z” to the ISO strings to avoid locale-dependent parsing.
- const eventYear = 2025; + const eventYear = 2025; const start = new Date(`${eventYear}-10-01T00:00:00`); const end = new Date(`${eventYear}-10-31T23:59:59`); - const active = true; + const now = new Date(); + const active = now >= start && now <= end;Timezone-safe variant:
- const start = new Date(`${eventYear}-10-01T00:00:00`); - const end = new Date(`${eventYear}-10-31T23:59:59`); + const start = new Date(`${eventYear}-10-01T00:00:00Z`); + const end = new Date(`${eventYear}-10-31T23:59:59Z`);components/effects/falling-leaves-3d.tsx (1)
38-46: Optional: cap device pixel ratio to reduce GPU loadClamping DPR can significantly reduce fill rate on high‑DPI screens.
- <Canvas + <Canvas camera={{ position: [0, 0, 15], fov: 75 }} style={{ background: 'transparent' }} - gl={{ antialias: true }} + dpr={[1, 2]} + gl={{ antialias: true, powerPreference: "high-performance" }} >components/hacktoberfest-client-content.tsx (2)
22-38: Remove unused glowControls (dead code) or wire it up
useAnimation()is started but never bound to a motion component;.stop()is called on cleanup only. Either bindanimate={glowControls}to the target element or remove this code.-import { motion, useAnimation } from "framer-motion"; +import { motion } from "framer-motion"; @@ - const glowControls = useAnimation(); @@ - // Gradient glow animation - const animateGlow = async () => { - await glowControls.start({ - opacity: [0.2, 0.5, 0.2], - scale: [1, 1.1, 1], - transition: { duration: 2, repeat: Infinity, ease: "easeInOut" }, - }); - }; - animateGlow(); @@ - glowControls.stop();If you prefer keeping it, attach it:
- <motion.div + <motion.div className="mb-16 flex justify-center relative" - animate={{ + animate={{ boxShadow: [ "0 0 20px rgba(255, 165, 0, 0.3)", "0 0 40px rgba(255, 165, 0, 0.6)", "0 0 20px rgba(255, 165, 0, 0.3)", ], transition: { duration: 2, repeat: Infinity, ease: "easeInOut" }, }} >Also applies to: 214-215
96-102: Consider a single rAF render loop for all effectsRunning three independent loops on the same canvas can cause overdraw/flicker and extra work. Merging updates (particles, confetti, trail) into one loop improves stability and aligns with your PR goal.
Also applies to: 149-158, 198-208
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
app/globals.css(1 hunks)app/hacktoberfest/page.tsx(1 hunks)components/effects/falling-leaves-3d.tsx(1 hunks)components/effects/festive-background.tsx(1 hunks)components/effects/festive-canvas-animation.tsx(1 hunks)components/effects/leaf-3d.tsx(1 hunks)components/hacktoberfest-client-content.tsx(1 hunks)hooks/use-prefers-reduced-motion.ts(1 hunks)package.json(3 hunks)tsconfig.json(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
app/hacktoberfest/page.tsx (1)
components/hacktoberfest-client-content.tsx (1)
HacktoberfestClientContent(17-326)
components/hacktoberfest-client-content.tsx (3)
components/ui/aurora-text.tsx (1)
AuroraText(12-41)components/utils/clean-countdown.tsx (1)
CleanCountdown(24-146)components/utils/icons.tsx (1)
Icons(4-199)
components/effects/falling-leaves-3d.tsx (2)
hooks/use-prefers-reduced-motion.ts (1)
usePrefersReducedMotion(5-23)components/effects/leaf-3d.tsx (1)
Leaf3D(25-84)
components/effects/festive-background.tsx (2)
hooks/use-prefers-reduced-motion.ts (1)
usePrefersReducedMotion(5-23)components/ui/particles.tsx (1)
Particles(125-249)
🪛 Biome (2.1.2)
components/effects/falling-leaves-3d.tsx
[error] 21-21: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
🔇 Additional comments (2)
components/effects/festive-background.tsx (1)
26-98: Options respect reduced motion — looks goodConfig disables movement and reduces count when reduced motion is on. Nice.
Confirm that the “trail” interactivity is also disabled when prefersReducedMotion is true (it is, via onHover.enable). Good.
package.json (1)
22-24: three@^0.180.0 satisfies @react-three/fiber and @react-three/drei peer requirements three@^0.180.0 meets the ≥0.156 (fiber) and ≥0.159 (drei) constraints.
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: 2
🧹 Nitpick comments (1)
components/effects/leaf-3d.tsx (1)
26-30: Add cleanup to dispose geometry and material on unmount.Three.js geometries and materials must be explicitly disposed to prevent GPU memory leaks. Currently, the memoized
materialandgeometryare never disposed when the component unmounts.Add a cleanup effect after the memoized values:
const geometry = useMemo(() => { const shape = new Shape(); const width = 0.5 + Math.random() * 0.3; const height = 0.7 + Math.random() * 0.4; shape.moveTo(0, height / 2); shape.bezierCurveTo(width / 2, height / 2 * 0.8, width / 2, -height / 2 * 0.8, 0, -height / 2); shape.bezierCurveTo(-width / 2, -height / 2 * 0.8, -width / 2, height / 2 * 0.8, 0, height / 2); return new ShapeGeometry(shape); }, []); // Add cleanup effect React.useEffect(() => { return () => { geometry.dispose(); material.dispose(); }; }, [geometry, material]);Also applies to: 32-43
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
components/effects/leaf-3d.tsx(1 hunks)
🔇 Additional comments (1)
components/effects/leaf-3d.tsx (1)
3-4: Previous import and memory leak issues are now resolved.The imports of Three.js classes (Shape, ShapeGeometry, Mesh) are now correct, and the material color is properly updated in place during reset instead of creating new material instances. These fixes address the critical issues from the previous review.
Also applies to: 28-28, 34-34, 42-42, 72-73
|
@wizaye review this |
|
@kuNA78-hub Good work, but it needs some improvement can you do that? |
@wizaye Thanks for the review! I'm happy to make the improvements. Could you please provide some specific details on what needs to be changed? |
|
Sure. |
trailer.mp4@wizaye Thanks for the feedback! I've toned down the animations to be more subtle while keeping the original design intact. Before I proceed further, could you take a look and confirm if this direction works? |
Description
This pull request introduces a suite of dynamic, theme-aware animations to the Hacktoberfest 2025 landing page. The goal is to create a more festive, engaging, and immersive experience for visitors and contributors during the event.
Key Enhancements
🎃 Falling Particles: A lightweight HTML5 canvas animation adds falling pumpkins and autumn leaves that gently drift across the screen.
🎉 Confetti Bursts: Celebratory confetti periodically bursts from the countdown timer, enhancing the festive atmosphere.
✨ Pulsing Glow Effect: The countdown timer now features a subtle, pulsing glow effect using Framer Motion to draw attention and build excitement.
🎨 Interactive Mouse Trail: A light trail follows the user's cursor, adding a fun, interactive element.
♿ Accessibility: All animations fully respect the prefers-reduced-motion browser setting to ensure a comfortable experience for all users.
🚀 Optimized Performance: All effects are rendered in a single, unified requestAnimationFrame loop on a single canvas to ensure smooth performance (stable <60 FPS) and low resource usage.
🌓 Full Theme Support: All text and animation effects automatically adapt to both light and dark modes for perfect visibility and contrast.
Video Demonstration
updated.mp4
Summary by CodeRabbit