Skip to content

refactor: README for clarity, enhance theme toggle accessibility, and…#5

Merged
nabobery merged 8 commits into
mainfrom
feat/v1-improvements
Oct 4, 2025
Merged

refactor: README for clarity, enhance theme toggle accessibility, and…#5
nabobery merged 8 commits into
mainfrom
feat/v1-improvements

Conversation

@nabobery

@nabobery nabobery commented Oct 3, 2025

Copy link
Copy Markdown
Owner

… improve particle canvas performance with reduced motion support.

… improve particle canvas performance with reduced motion support.
@vercel

vercel Bot commented Oct 3, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
project-protico Ready Ready Preview Comment Oct 4, 2025 6:29am

@coderabbitai

coderabbitai Bot commented Oct 3, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Theme-aware particle background adapts to light/dark modes and device pixel ratio.
    • Honors reduced-motion preferences by disabling non-essential animations.
    • Improved hero readability with a subtle contrast overlay.
  • Bug Fixes

    • Eliminates theme flash and hydration mismatches; visual transitions occur only after mount.
    • Stabilizes hero, CTA, and indicator behavior under reduced-motion.
  • Documentation

    • Expanded Next.js benefits, added Tailwind CSS overview, and standardized Vercel note.
    • Refined Project Structure formatting for clarity.
  • Style

    • Added canvas-related CSS variables for consistent colors and opacity across themes.

Walkthrough

Adds CSS variables and canvas theming, injects a pre-hydration theme script, expands README content, makes Hero respect reduced-motion with an overlay, makes ThemeToggle hydration-safe, and rewrites ParticleCanvas to be theme-, DPR-, and motion-aware.

Changes

Cohort / File(s) Summary of Changes
Docs content and formatting
README.md
Expanded Next.js bullets into a detailed benefits list, added a Tailwind CSS entry, removed duplicate Next.js bullets, simplified Vercel note, and normalized Project Structure indentation.
Global CSS: canvas variables
app/globals.css
Added CSS custom properties: --canvas-line-rgb, --canvas-particle-rgb, and --canvas-opacity for light and dark themes (distinct values per theme).
Hero section: reduced-motion + overlay
components/sections/hero.tsx
Uses useReducedMotion, gates motion durations/delays on reduced-motion, disables arrow animation when reduced, and inserts an overlay div between ParticleCanvas and content to improve contrast.
Hydration-safe theme toggle
components/theme/theme-toggle.tsx
Adds mounted state/effect, derives isDark post-mount via resolvedTheme, centralizes toggle helper, defers ARIA/title/animation behavior until mounted to avoid hydration mismatch.
Theme-aware, DPR & motion-aware ParticleCanvas
components/ui/particle-canvas.tsx
Reads resolvedTheme and CSS variables, caches css vars, applies client-only canvas styling, adds DPR-aware sizing and CSS-pixel particle logic, supports reduced-motion static render, and updates particle/line rendering to use CSS-driven colors and dimensions.
Root layout: pre-hydration script & ThemeProvider props
app/layout.tsx
Adds a pre-hydration script to apply preferred color scheme before hydration; configures ThemeProvider with enableSystem and disableTransitionOnChange; includes a descriptive docblock.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant B as Browser
  participant RL as RootLayout (pre-hydrate script)
  participant CSS as CSS Vars
  participant TT as ThemeToggle
  participant PC as ParticleCanvas
  participant H as Hero

  U->>B: Request page
  B->>RL: Run pre-hydration script (apply preferred color / dark class)
  B->>CSS: Resolve theme CSS vars (`--primary-rgb`, `--canvas-*`)
  B->>TT: Mount ThemeToggle
  TT-->>TT: set mounted = true (defer ARIA/animations)

  B->>PC: Mount ParticleCanvas
  alt Prefers Reduced Motion
    PC-->>PC: Render single static frame (no listeners/loop)
  else Full Motion
    PC-->>PC: Read CSS vars, apply canvas styles, start animation loop + listeners (DPR-aware)
  end

  B->>H: Mount Hero
  alt Prefers Reduced Motion
    H-->>H: Disable motion (durations=0, no arrow anim)
  else Full Motion
    H-->>H: Run motion transitions and arrow animation
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I twitch my nose when themes align,
I hush my hop for motion kind—
Particles settle, colors sing,
Dark and light in gentle ring.
I code a hop, I leave a trail—🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title lists multiple significant changes but ends with an ellipsis, leaving the scope unclear and preventing a concise summary of the primary change in a single sentence. It does reference real parts of the changeset but is overly broad and incomplete due to the trailing “and…”. A clear, focused title without the ellipsis would better meet expectations. Please rewrite the title to remove the ellipsis and focus on the single most important change or combine key elements into a concise statement (for example, “refactor: improve particle canvas performance with reduced-motion support”).
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed The description directly references improving particle canvas performance with reduced motion support, which aligns with the changes in both the particle-canvas component and related motion handling. It is succinct but clearly relates to a core objective of the pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/v1-improvements

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between b3f865f and bdd5409.

📒 Files selected for processing (1)
  • components/ui/particle-canvas.tsx (7 hunks)

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
README.md (1)

14-18: Optional: Consider markdown linter preferences.

The 4-space indentation for nested list items is valid Markdown and improves readability. However, markdownlint expects 2-space indentation by default. You can either:

  1. Keep the current 4-space indentation (recommended for clarity)
  2. Adjust to 2-space indentation to satisfy the linter
  3. Configure markdownlint to accept 4-space indentation via .markdownlint.json

Based on static analysis hints.

Also applies to: 29-32

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 3f78d06 and d867052.

📒 Files selected for processing (5)
  • README.md (1 hunks)
  • app/globals.css (2 hunks)
  • components/sections/hero.tsx (8 hunks)
  • components/theme/theme-toggle.tsx (1 hunks)
  • components/ui/particle-canvas.tsx (7 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
README.md

14-14: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


15-15: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


16-16: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


17-17: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


18-18: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


29-29: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


30-30: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


31-31: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


32-32: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🔇 Additional comments (10)
app/globals.css (1)

23-27: LGTM!

The new canvas-specific CSS variables follow the existing naming conventions and provide appropriate theme-aware values. The opacity difference (1.0 for light, 0.95 for dark) is subtle and appropriate. The inline comments clearly document the purpose.

Also applies to: 56-58

README.md (1)

13-20: LGTM!

The expanded tech stack descriptions significantly improve clarity and professionalism. The structured bullet points with emoji indicators make the content more scannable, and the addition of the Tailwind CSS entry completes the technology overview.

components/theme/theme-toggle.tsx (2)

11-21: LGTM!

The hydration-safe pattern correctly addresses SSR/CSR mismatches. The mounted state ensures theme-dependent attributes and animations only apply after hydration, preventing console errors and visual flickers.


29-43: Excellent accessibility improvements!

The mounted-gated ARIA attributes ensure screen readers receive accurate state information after hydration. The conditional animations prevent hydration mismatches while maintaining the polished UX. The use of aria-pressed is semantically correct for a toggle button.

Also applies to: 46-62

components/sections/hero.tsx (2)

14-14: Excellent accessibility implementation!

The reduced-motion support comprehensively addresses accessibility concerns. By checking useReducedMotion() and conditionally setting durations to 0 and removing delays, you ensure users who prefer reduced motion experience an instant, non-animated interface while preserving the polished UX for others.

Also applies to: 64-66, 78-81, 93-96, 108-111, 128-147


46-55: LGTM!

The overlay layer improves text contrast without affecting accessibility or interactivity. The aria-hidden and pointer-events-none attributes are correctly applied, and the responsive approach (gradient on desktop, stronger layer on mobile) is appropriate for different screen sizes.

components/ui/particle-canvas.tsx (4)

29-33: LGTM!

The reduced-motion implementation correctly detects user preferences and renders a single static frame when motion is reduced, bypassing the animation loop and event listeners. This provides an accessible experience while maintaining visual presence.

Also applies to: 175-190


42-55: Excellent hydration-safe styling!

The applyCanvasStyles function correctly reads CSS variables client-side to prevent SSR/CSR mismatches. The fallback logic aligns with the values defined in app/globals.css, ensuring consistent behavior even if variables are not available.

Also applies to: 180-181


92-124: LGTM!

The theme-aware color computation robustly reads CSS variables with sensible fallbacks. The trim() checks and theme-based defaults ensure the canvas renders correctly across themes and handles cases where CSS variables might not be available.


138-164: LGTM!

The theme-aware rendering logic provides appropriate visual contrast across themes. The differential line widths and alpha values ensure connections are visible but not overwhelming in both light and dark modes. The use of primary accent for mouse connections provides good affordance.

Also applies to: 210-227

coderabbitai Bot added a commit that referenced this pull request Oct 3, 2025
Docstrings generation was requested by @nabobery.

* #5 (comment)

The following files were modified:

* `components/sections/hero.tsx`
* `components/theme/theme-toggle.tsx`
* `components/ui/particle-canvas.tsx`
@coderabbitai

coderabbitai Bot commented Oct 3, 2025

Copy link
Copy Markdown
Contributor

Note

Generated docstrings for this pull request at #6

… performance with improved CSS variable handling
Docstrings generation was requested by @nabobery.

* #5 (comment)

The following files were modified:

* `app/layout.tsx`
* `components/sections/hero.tsx`
* `components/theme/theme-toggle.tsx`
* `components/ui/particle-canvas.tsx`
@coderabbitai

coderabbitai Bot commented Oct 3, 2025

Copy link
Copy Markdown
Contributor

Note

Generated docstrings for this pull request at #7

📝 Add docstrings to `feat/v1-improvements`
@nabobery nabobery marked this pull request as ready for review October 3, 2025 16:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
components/sections/hero.tsx (1)

23-43: Honor reduced-motion for the Typed.js animation.

useReducedMotion disables the Framer Motion transitions, but the Typed.js effect still runs, so users who prefer reduced motion see continuous typing animation. That contradicts the stated goal and accessibility preference. Make the effect conditional (or render static text) when shouldReduceMotion is true.

-    useEffect(() => {
-        if (typedRef.current) {
+    useEffect(() => {
+        if (shouldReduceMotion) {
+            return
+        }
+        if (typedRef.current) {
             const options = {
@@
-        return () => {
-            typedInstance.current?.destroy()
-        }
-    }, [])
+        return () => {
+            typedInstance.current?.destroy()
+        }
+    }, [shouldReduceMotion])
components/ui/particle-canvas.tsx (2)

24-71: Re-run effect when resolvedTheme changes.

When the user runs in "system" mode and the OS theme flips, theme stays "system" so this effect never re-executes. The cached CSS variables (and canvasParticleRgb fallback) remain stuck on the old theme, so the canvas colors don’t update. Add resolvedTheme to the dependency list (and ensure readCssVars runs) so system-driven theme changes refresh colors.

-    const { theme, resolvedTheme } = useTheme()
+    const { theme, resolvedTheme } = useTheme()
@@
-    }, [theme])
+    }, [theme, resolvedTheme])

Also make sure any theme-conditioned fallbacks inside the effect use resolvedTheme || theme after this change.


288-312: Fix boundary handling to prevent teleporting particles.

When a particle crosses the left/top boundary, this.x/this.y get clamped to 0 and the subsequent ternary always picks the “else” branch, snapping the particle to the far edge. Because vx/vy are never inverted, particles stick to borders. Switch vx/vy to let and reflect them when bounds are hit instead of reassigning position after clamping.

-    const vx = (Math.random() - 0.5) * 0.2
-    const vy = (Math.random() - 0.5) * 0.2
+    let vx = (Math.random() - 0.5) * 0.2
+    let vy = (Math.random() - 0.5) * 0.2
@@
-            // Bounce off edges
-            if (this.x < 0 || this.x > cssWidth) {
-                this.x = Math.max(0, Math.min(this.x, cssWidth))
-                this.x = this.x < 0 ? radius : cssWidth - radius
-            }
-
-            if (this.y < 0 || this.y > cssHeight) {
-                this.y = Math.max(0, Math.min(this.y, cssHeight))
-                this.y = this.y < 0 ? radius : cssHeight - radius
-            }
+            // Bounce off edges
+            if (this.x - radius < 0 || this.x + radius > cssWidth) {
+                this.x = Math.max(radius, Math.min(this.x, cssWidth - radius))
+                vx = -vx
+            }
+
+            if (this.y - radius < 0 || this.y + radius > cssHeight) {
+                this.y = Math.max(radius, Math.min(this.y, cssHeight - radius))
+                vy = -vy
+            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between d867052 and a9f4f61.

📒 Files selected for processing (4)
  • app/layout.tsx (3 hunks)
  • components/sections/hero.tsx (7 hunks)
  • components/theme/theme-toggle.tsx (1 hunks)
  • components/ui/particle-canvas.tsx (7 hunks)
🧰 Additional context used
🪛 ast-grep (0.39.5)
app/layout.tsx

[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
app/layout.tsx

[error] 112-112: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

Comment thread components/theme/theme-toggle.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between a9f4f61 and ebf1604.

📒 Files selected for processing (1)
  • components/theme/theme-toggle.tsx (1 hunks)

Comment thread components/theme/theme-toggle.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
components/ui/particle-canvas.tsx (2)

147-194: Use the resolved theme for light/dark heuristics.

When the stored theme is "system" and the OS is dark, theme !== 'dark' evaluates to true, so isLight stays true: connection opacity and line widths use light-mode tuning against a dark canvas. Base this decision on the effective theme.

-            const primaryRgb = cssVarsRef.current.primaryRgb
-            const canvasLineRgb = cssVarsRef.current.canvasLineRgb
-            const isLight = theme !== 'dark'
+            const primaryRgb = cssVarsRef.current.primaryRgb
+            const canvasLineRgb = cssVarsRef.current.canvasLineRgb
+            const effectiveTheme = resolvedTheme ?? theme ?? 'light'
+            const isLight = effectiveTheme !== 'dark'

285-312: Invert particle velocity on wall collisions.

After hitting an edge the particle is clamped to the boundary but its velocity never flips, so it sticks in place. Make the velocities mutable and multiply by -1 when you hit a wall.

-    const vx = (Math.random() - 0.5) * 0.2
-    const vy = (Math.random() - 0.5) * 0.2
+    let vx = (Math.random() - 0.5) * 0.2
+    let vy = (Math.random() - 0.5) * 0.2
@@
-            if (this.x < 0 || this.x > cssWidth) {
-                this.x = Math.max(0, Math.min(this.x, cssWidth))
-                this.x = this.x < 0 ? radius : cssWidth - radius
-            }
+            if (this.x - radius <= 0 || this.x + radius >= cssWidth) {
+                this.x = Math.max(radius, Math.min(this.x, cssWidth - radius))
+                vx *= -1
+            }
@@
-            if (this.y < 0 || this.y > cssHeight) {
-                this.y = Math.max(0, Math.min(this.y, cssHeight))
-                this.y = this.y < 0 ? radius : cssHeight - radius
-            }
+            if (this.y - radius <= 0 || this.y + radius >= cssHeight) {
+                this.y = Math.max(radius, Math.min(this.y, cssHeight - radius))
+                vy *= -1
+            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between ebf1604 and b3f865f.

📒 Files selected for processing (4)
  • app/layout.tsx (2 hunks)
  • components/sections/hero.tsx (7 hunks)
  • components/theme/theme-toggle.tsx (1 hunks)
  • components/ui/particle-canvas.tsx (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/sections/hero.tsx
🧰 Additional context used
🪛 ast-grep (0.39.5)
app/layout.tsx

[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
app/layout.tsx

[error] 112-112: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

Comment thread components/ui/particle-canvas.tsx
Comment thread components/ui/particle-canvas.tsx Outdated
…epresentation based on resolved theme and user preferences
@nabobery nabobery merged commit a45f378 into main Oct 4, 2025
4 checks passed
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