Skip to content

Conversation

@Bayselonarrend
Copy link

@Bayselonarrend Bayselonarrend commented Jan 1, 2026

Summary by CodeRabbit

  • Style
    • Enhanced the logo carousel with smoother, continuously looping animation and improved spacing between logos for better visual presentation.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 1, 2026

📝 Walkthrough

Walkthrough

The logo carousel component was refactored from a dual CSS keyframe animation approach to a single-track design driven by JavaScript. The animation now uses requestAnimationFrame with a reactive offset state for smoother, JavaScript-controlled scrolling and seamless looping.

Changes

Cohort / File(s) Summary
Logo Carousel Animation Refactor
frontend/.vitepress/theme/components/LogoCarousel.vue
Replaced dual nested tracks with single animated track; switched from CSS keyframes to requestAnimationFrame-driven animation with reactive offset; introduced Vue 3 Composition API (ref, onMounted, onUnmounted); implemented delta-time-based scrolling for consistent speed; added offset reset at halfway point for seamless looping; expanded baseLogos to allLogos for sufficient content; adjusted gap in responsive breakpoint (2rem → 3rem); removed pointer-events adjustments.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A carousel spins with graceful pace,
No keyframes now, just time and space,
One track, one rhythm, smooth and true,
JavaScript dances the logos through!
thump thump goes the rabbit's delight ✨

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Новая анимация карусели' (New carousel animation) directly and accurately describes the main change - replacing the dual-track animation system with a new single-track continuously animated carousel component.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

Copy link

@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 (2)
frontend/.vitepress/theme/components/LogoCarousel.vue (2)

40-41: Consider making the repetition logic more explicit.

The 6 repetitions create 30 logos, which works with the reset logic that divides scrollWidth / 2. However, this coupling is implicit. If baseLogos changes significantly in count, the repetition factor might need adjustment.

💡 Optional: Make repetition count explicit
-// Repeat enough times to fill and loop seamlessly
-const allLogos = [...baseLogos, ...baseLogos, ...baseLogos, ...baseLogos, ...baseLogos, ...baseLogos];
+// Repeat enough times to fill and loop seamlessly
+// We need an even number of repetitions for the reset logic to work correctly
+const REPETITIONS = 6;
+const allLogos = Array(REPETITIONS).fill(baseLogos).flat();

This makes it clearer that the repetition count matters for the looping logic and makes it easier to adjust if needed.


43-47: Animation state setup looks good.

The reactive references and animation tracking variables are properly initialized.

You could optionally make the speed configurable as a prop or constant at the top of the file if you anticipate needing to adjust it, but the current hardcoded value is reasonable.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 586c202 and 6571b2a.

📒 Files selected for processing (1)
  • frontend/.vitepress/theme/components/LogoCarousel.vue
🔇 Additional comments (4)
frontend/.vitepress/theme/components/LogoCarousel.vue (4)

6-17: LGTM! Clean template structure with proper optimizations.

The template correctly binds the reactive offset, handles dark mode variants, and includes appropriate security attributes (rel="noopener noreferrer") and performance optimizations (loading="lazy").


67-75: Excellent lifecycle management!

The animation is properly initialized on mount and cleaned up on unmount, preventing memory leaks from orphaned animation frames.


112-112: Proper performance optimization.

Using will-change: transform on the animated track is appropriate and helps the browser optimize rendering for the continuous animation.


162-162: Good responsive adjustment.

Increasing the gap on mobile improves readability and touch target spacing.

Comment on lines +49 to +65
const animate = (currentTime) => {
if (!lastTime) lastTime = currentTime;
const delta = currentTime - lastTime;
lastTime = currentTime;
offset.value -= (speed * delta) / 1000;
// Reset when half of the track has scrolled
if (trackRef.value) {
const trackWidth = trackRef.value.scrollWidth / 2;
if (Math.abs(offset.value) >= trackWidth) {
offset.value = 0;
}
}
animationId = requestAnimationFrame(animate);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add accessibility support for reduced motion preference.

The animation logic is well-implemented with proper delta time calculations and seamless looping. However, it doesn't respect the user's prefers-reduced-motion setting, which is an accessibility concern.

🔎 Suggested fix to respect prefers-reduced-motion

Add a check for the reduced motion preference:

+const prefersReducedMotion = ref(false);
+
 const animate = (currentTime) => {
+  if (prefersReducedMotion.value) {
+    animationId = requestAnimationFrame(animate);
+    return;
+  }
+  
   if (!lastTime) lastTime = currentTime;
   const delta = currentTime - lastTime;
   lastTime = currentTime;
   
   offset.value -= (speed * delta) / 1000;
   
   // Reset when half of the track has scrolled
   if (trackRef.value) {
     const trackWidth = trackRef.value.scrollWidth / 2;
     if (Math.abs(offset.value) >= trackWidth) {
       offset.value = 0;
     }
   }
   
   animationId = requestAnimationFrame(animate);
 };
 
 onMounted(() => {
+  const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
+  prefersReducedMotion.value = mediaQuery.matches;
+  mediaQuery.addEventListener('change', (e) => {
+    prefersReducedMotion.value = e.matches;
+  });
+  
   animationId = requestAnimationFrame(animate);
 });

Additionally, consider adding pause-on-hover for better UX, allowing users to interact with logos more easily:

💡 Optional: Add pause-on-hover functionality
+const isPaused = ref(false);
+
 const animate = (currentTime) => {
+  if (isPaused.value || prefersReducedMotion.value) {
     animationId = requestAnimationFrame(animate);
     return;
   }
   
   if (!lastTime) lastTime = currentTime;
   // ... rest of animation logic
 };

Then in the template:

-<div class="logo-carousel">
+<div class="logo-carousel" @mouseenter="isPaused = true" @mouseleave="isPaused = false">

Committable suggestion skipped: line range outside the PR's diff.

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