Skip to content

Icons/anthropic midjourney ollama aistudio xai lmstudio#71

Merged
Abhijit-Jha merged 5 commits intoitshover:masterfrom
itstheanurag:icons/anthropic-midjourney-ollama-aistudio-xai-lmstudio
Jan 17, 2026
Merged

Icons/anthropic midjourney ollama aistudio xai lmstudio#71
Abhijit-Jha merged 5 commits intoitshover:masterfrom
itstheanurag:icons/anthropic-midjourney-ollama-aistudio-xai-lmstudio

Conversation

@itstheanurag
Copy link
Copy Markdown
Contributor

@itstheanurag itstheanurag commented Jan 11, 2026

added icons for

anthropic
midjourney
ollam
xai
aistudio (google)
lmStudio

these are all kinda llm related, here's the video.

anthropic-aistudio-lmstudio-midjourney-ollama-xai.mp4

Summary by CodeRabbit

Release Notes

  • New Features
    • Added six new animated brand icons: Anthropic, AI Studio, XAI, LM Studio, Midjourney, and Ollama icons are now available for use throughout the application.
    • Brand icons now feature interactive hover animations that bring them to life when users interact with them.

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

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Jan 11, 2026

@itstheanurag is attempting to deploy a commit to the itshover's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 11, 2026

📝 Walkthrough

Walkthrough

The PR introduces six new animated brand icon components (Anthropic, AI Studio, xAI, LM Studio, Midjourney, Ollama) with hover-triggered animations and imperative animation APIs, registered in icon catalogs and made available through the icon index.

Changes

Cohort / File(s) Summary
Icon Index & Registry Configuration
icons/index.ts, lib/icons.ts, registry.json, public/r/registry.json
Updated ICON_LIST with six new brand icon entries and their keywords; extended ICONS constant with twelve entries (six with "-icon" suffix, six without); added registry definitions for all six icons across public and root registry files.
Animated Icon Components
icons/brand-anthropic-icon.tsx, icons/brand-aistudio-icon.tsx, icons/brand-xai-icon.tsx, icons/brand-lmstudio-icon.tsx, icons/brand-midjourney-icon.tsx, icons/brand-ollama-icon.tsx
Six new forwardRef-based React components exposing imperative startAnimation and stopAnimation methods via custom handles. Each implements unique SVG animations using motion/react, with hover-triggered animation lifecycle and distinct visual effects (e.g., scaling for Anthropic, sparks for AI Studio, explosions for xAI, boat motion for Midjourney).
Type Definitions
icons/types.ts
Exports shared AnimatedIconProps interface (size, color, strokeWidth, className) and AnimatedIconHandle interface (startAnimation, stopAnimation methods) used across all new icon components.
Registry Item Descriptors
public/r/brand-anthropic-icon.json, public/r/brand-aistudio-icon.json, public/r/brand-xai-icon.json, public/r/brand-lmstudio-icon.json, public/r/brand-midjourney-icon.json, public/r/brand-ollama-icon.json
Six new UI registry entries documenting component files, dependencies (motion), and public types for each brand icon.
Formatting Adjustment
public/r/brand-bags-fm-icon.json
Minor whitespace normalization; no functional changes.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

  • PR #65: Modifies the same icon registries and ICON_LIST exports by adding new icon entries with consistent patterns.
  • PR #54: Adds new icon components and updates the same registry files (registry.json, icons/index.ts, lib/icons.ts) with brand-prefixed icon entries.
  • PR #83: Adds forwardRef animated brand icon components using identical patterns and updates the ICON_LIST export structure.

Poem

🐰 Six brand mascots hopped into town,
With Anthropic, xAI's renown!
Ollama dances, LM Studio glows,
Midjourney sails where the animation flows,
AI Studio sparks—a colorful sight,
Now your icons shimmer and animate bright! ✨

🚥 Pre-merge checks | ✅ 3
✅ 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 lists six brand icons (anthropic, midjourney, ollama, aistudio, xai, lmstudio) being added, directly matching the PR's main objective of introducing these new branded icon components with animations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
icons/index.ts (1)

1-1490: Fix Prettier formatting issues.

The CI pipeline detected code style issues in this file. Run Prettier to auto-format the code before merging.

#!/bin/bash
# Run Prettier to fix formatting
npx prettier --write icons/index.ts
🤖 Fix all issues with AI agents
In @icons/index.ts:
- Around line 1477-1489: Update the keywords for the "midjourney-icon" entry
(icon: MidJourneyIcon) to remove misleading and irrelevant terms: delete "llm",
"ships", and "boat" from the keywords array and replace/add more accurate tags
such as "image-generation", "text-to-image", and "generative-ai" so the keywords
reflect MidJourney's image-generation domain.
- Around line 1452-1465: The keywords array for the "xai-icon" entry (object
with name "xai-icon" and icon symbol XaiIcon) incorrectly contains "studio";
remove the "studio" token from that keywords list so searches won't associate
xAI with Google's AI Studio, leaving the other keywords unchanged.

In @icons/lmstudio-icon.tsx:
- Around line 12-19: The constant array lines is defined inside the component
causing missing dependency warnings for useCallback; move the lines declaration
to module scope (outside the component) so it is not recreated on each render
and update references in the component (e.g., in any useCallback that currently
closes over lines) to use the top-level lines constant; this keeps the array
immutable, satisfies exhaustive-deps, and removes the lint warnings for the
component that uses lines.

In @public/r/xai-icon.json:
- Line 10: The SVG title text "Grok" does not match the React component name
XaiIcon; update the <title> element inside the XaiIcon component (the JSX
returned by motion.svg) to use a consistent name — either change "Grok" to
"XaiIcon" (or "xAI") or include both (e.g., "xAI (Grok)") so the component name
XaiIcon and the SVG title align for discoverability.
🧹 Nitpick comments (7)
icons/ollama-icon.tsx (1)

38-41: Add dependency array to useImperativeHandle for consistency.

The useImperativeHandle hook is missing its dependency array. While this works, other icons in this PR (e.g., lmstudio-icon.tsx, xai-icon.tsx) include [start, stop] as dependencies. Adding it ensures the handle updates if the callbacks change and maintains consistency across the codebase.

Suggested fix
     useImperativeHandle(ref, () => ({
       startAnimation: start,
       stopAnimation: stop,
-    }));
+    }), [start, stop]);
icons/midjourney-icon.tsx (1)

26-29: Add dependency array to useImperativeHandle for consistency.

Same as noted for OllamaIcon — add [start, stop] as the dependency array to match the pattern used in LmStudioIcon and XaiIcon.

Suggested fix
     useImperativeHandle(ref, () => ({
       startAnimation: start,
       stopAnimation: stop,
-    }));
+    }), [start, stop]);
public/r/xai-icon.json (1)

10-10: SVG path uses both fill and stroke with identical color.

The path applies both fill={color} and stroke={color} with the same value, creating a heavier visual weight. Most icon libraries use either fill or stroke, not both, unless intentionally creating an outlined effect.

Suggested refinement

If the intent is a simple filled icon:

           fill={color}
-          stroke={color}
-          strokeWidth=\"1.5\"
           strokeLinecap=\"round\"

Or if the intent is a stroked icon:

-          fill={color}
+          fill=\"none\"
           stroke={color}
           strokeWidth=\"1.5\"
public/r/aistudio-icon.json (1)

10-10: Consider adding accessibility attributes to the SVG.

The icon includes a <title> element, which is good, but for better screen reader support, consider adding role="img" and aria-labelledby (or aria-label) attributes to the motion.svg element. This ensures assistive technologies properly announce the icon.

♿ Suggested accessibility improvements
     return (
       <motion.svg
         ref={scope}
         width={size}
         height={size}
         viewBox="0 0 24 24"
         fill={color}
         className={className}
         xmlns="http://www.w3.org/2000/svg"
+        role="img"
+        aria-label="Google AI Studio"
         onHoverStart={start}
         onHoverEnd={stop}
       >
-        <title>Google AI Studio</title>
+        <title id="aistudio-title">Google AI Studio</title>
public/r/ollama-icon.json (1)

10-10: Consider adding accessibility attributes to the SVG.

Similar to other icons in this PR, the Ollama icon should include role="img" and proper ARIA attributes for better screen reader support.

icons/anthropic-icon.tsx (1)

40-78: Consider adding accessibility attributes.

For consistency with accessibility best practices, add role="img" and aria-label to the SVG element, similar to the recommendation for other icons in this PR.

public/r/midjourney-icon.json (1)

10-10: Consider adding accessibility attributes.

For consistency with other icons, add role="img" and proper ARIA attributes to the SVG element for better screen reader support.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e1239d and 5b182fe.

📒 Files selected for processing (16)
  • icons/aistudio-icon.tsx
  • icons/anthropic-icon.tsx
  • icons/index.ts
  • icons/lmstudio-icon.tsx
  • icons/midjourney-icon.tsx
  • icons/ollama-icon.tsx
  • icons/xai-icon.tsx
  • lib/icons.ts
  • public/r/aistudio-icon.json
  • public/r/anthropic-icon.json
  • public/r/lmstudio-icon.json
  • public/r/midjourney-icon.json
  • public/r/ollama-icon.json
  • public/r/registry.json
  • public/r/xai-icon.json
  • registry.json
🧰 Additional context used
🪛 GitHub Actions: CI
icons/index.ts

[warning] 1-1: Code style issues found in icons/index.ts. Run Prettier with --write to fix.

🪛 GitHub Check: build
icons/lmstudio-icon.tsx

[warning] 45-45:
React Hook useCallback has a missing dependency: 'lines'. Either include it or remove the dependency array


[warning] 37-37:
React Hook useCallback has a missing dependency: 'lines'. Either include it or remove the dependency array

🔇 Additional comments (17)
icons/ollama-icon.tsx (1)

1-102: Well-structured animated icon component.

The OllamaIcon implementation follows the established pattern with proper forwardRef usage, imperative handle exposure, and hover-triggered animations. The animation effects (body breathing, eye blinking, nose wiggle) are creative and well-implemented.

public/r/lmstudio-icon.json (1)

1-20: Registry entry is correctly structured.

The registry item follows the shadcn schema and correctly declares the motion dependency and file paths.

icons/lmstudio-icon.tsx (1)

56-106: LGTM on the SVG structure and animation setup.

The SVG paths with individual class names for animation targeting and the hover event wiring are well implemented.

lib/icons.ts (1)

694-717: Icon registry entries are correctly added.

The new icon entries follow the established naming convention and path structure consistent with existing icons in the registry.

icons/midjourney-icon.tsx (1)

1-62: Well-implemented sailing boat animation.

The MidJourneyIcon component correctly implements the animated icon pattern with a creative bobbing boat effect on the sails.

icons/xai-icon.tsx (2)

45-45: Verify the SVG title: "Grok" vs "xAI".

The component is named XaiIcon but the <title> is set to "Grok". Note that grok-icon already exists in the registry. If this icon represents xAI (the company), the title should likely be "xAI". If it's meant to be another Grok icon, consider the naming to avoid confusion.


1-64: Implementation follows the established pattern correctly.

The XaiIcon properly implements the animated icon pattern with dependency arrays included in useImperativeHandle.

registry.json (1)

41-57: Registry entries for new icons are correctly structured.

All six new icon entries (aistudio-icon, anthropic-icon, lmstudio-icon, midjourney-icon, ollama-icon, xai-icon) follow the established registry schema with proper dependencies and file references. The entries are correctly positioned in alphabetical order.

Also applies to: 177-193, 2030-2046, 2149-2165, 2251-2267, 3407-3423

public/r/anthropic-icon.json (1)

10-10: LGTM! Clean animation implementation.

The component correctly uses getBBox() to calculate dynamic spacing between SVG elements and animates them smoothly. The null checks and ref handling are appropriate.

public/r/registry.json (1)

2-2: LGTM! Auto-generated registry entries are correctly structured.

All six new icon entries (aistudio-icon, anthropic-icon, lmstudio-icon, midjourney-icon, ollama-icon, xai-icon) follow the correct structure and are properly integrated into the registry. The file is auto-generated as indicated by the header comment.

icons/aistudio-icon.tsx (1)

1-120: LGTM! Well-implemented animated icon component.

The component follows best practices:

  • Proper TypeScript typing with forwardRef
  • Efficient getBBox() usage (only called during animation, not on every render)
  • Clean animation logic with useCallback
  • Appropriate null checks for refs
  • Consistent with the pattern used in other icon components
icons/anthropic-icon.tsx (2)

12-19: LGTM! Proper null safety for getBBox() calls.

The getMergeX function correctly guards against null refs before calling getBBox(). Since this is only invoked during animation start (after component mount and ref assignment), the timing is safe.


21-29: Animation implementation looks solid.

The animation callbacks are properly memoized with correct dependencies, and the approach of measuring actual SVG bounding boxes ensures accurate positioning regardless of viewport size or scaling.

public/r/midjourney-icon.json (2)

10-10: Verify cleanup of infinite animations on unmount.

Like OllamaIcon, this component uses repeat: Infinity for the continuous bobbing animation. Verify that motion/react properly cleans up the animation when the component unmounts.


10-10: Nice animation design with appropriate duration.

The 1.2-second duration for the boat bobbing animation is well-chosen—it creates a smooth, natural wave-like motion without being too fast or jarring. The combination of scale, x, and y transformations effectively simulates realistic boat movement.

public/r/aistudio-icon.json (1)

4-4: No action needed.

The useAnimate hook and motion.svg component are fully compatible with motion v12.23.26, the version currently in use. The aistudio-icon.tsx implementation correctly uses these APIs with proper animation configurations.

public/r/ollama-icon.json (1)

10-10: No issues found. The component correctly uses useAnimate() from motion/react, which automatically cleans up all animations—including infinite ones—when the component unmounts. No explicit cleanup code or additional verification is needed.

Comment thread icons/index.ts
Comment thread icons/index.ts Outdated
Comment on lines +1477 to +1489
name: "midjourney-icon",
icon: MidJourneyIcon,
keywords: [
"midjourney",
"midjourney-icon",
"ai",
"llm",
"ai-agents",
"journey",
"ships",
"boat",
],
},
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.

⚠️ Potential issue | 🟡 Minor

Misleading keywords for midjourney-icon.

MidJourney is an image generation AI, not an LLM. The keywords include "llm" which is incorrect. Additionally, "ships" and "boat" keywords seem unusual for an AI tool.

Suggested fix
     keywords: [
       "midjourney",
       "midjourney-icon",
       "ai",
-      "llm",
-      "ai-agents",
-      "journey",
-      "ships",
-      "boat",
+      "image",
+      "generation",
+      "art",
+      "design",
     ],
🤖 Prompt for AI Agents
In @icons/index.ts around lines 1477 - 1489, Update the keywords for the
"midjourney-icon" entry (icon: MidJourneyIcon) to remove misleading and
irrelevant terms: delete "llm", "ships", and "boat" from the keywords array and
replace/add more accurate tags such as "image-generation", "text-to-image", and
"generative-ai" so the keywords reflect MidJourney's image-generation domain.

Comment thread icons/lmstudio-icon.tsx
Comment on lines +12 to +19
const lines = [
".lm-line-1",
".lm-line-2",
".lm-line-3",
".lm-line-4",
".lm-line-5",
".lm-line-6",
];
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.

⚠️ Potential issue | 🟡 Minor

Move lines array outside the component to fix missing dependency warnings.

The static analysis correctly flags that lines is missing from the useCallback dependency arrays. Since lines is a constant array of class selectors that never changes, move it outside the component to avoid recreating it on every render and to satisfy the exhaustive-deps rule cleanly.

Suggested fix
 import { forwardRef, useImperativeHandle, useCallback } from "react";
 import type { AnimatedIconHandle, AnimatedIconProps } from "./types";
 import { motion, useAnimate } from "motion/react";

+const lines = [
+  ".lm-line-1",
+  ".lm-line-2",
+  ".lm-line-3",
+  ".lm-line-4",
+  ".lm-line-5",
+  ".lm-line-6",
+];
+
 const LmStudioIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(
   (
     { size = 24, color = "currentColor", strokeWidth = 1, className = "" },
     ref,
   ) => {
     const [scope, animate] = useAnimate();

-    const lines = [
-      ".lm-line-1",
-      ".lm-line-2",
-      ".lm-line-3",
-      ".lm-line-4",
-      ".lm-line-5",
-      ".lm-line-6",
-    ];
-
     const start = useCallback(() => {
📝 Committable suggestion

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

Suggested change
const lines = [
".lm-line-1",
".lm-line-2",
".lm-line-3",
".lm-line-4",
".lm-line-5",
".lm-line-6",
];
const lines = [
".lm-line-1",
".lm-line-2",
".lm-line-3",
".lm-line-4",
".lm-line-5",
".lm-line-6",
];
const LmStudioIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(
(
{ size = 24, color = "currentColor", strokeWidth = 1, className = "" },
ref,
) => {
const [scope, animate] = useAnimate();
const start = useCallback(() => {
🤖 Prompt for AI Agents
In @icons/lmstudio-icon.tsx around lines 12 - 19, The constant array lines is
defined inside the component causing missing dependency warnings for
useCallback; move the lines declaration to module scope (outside the component)
so it is not recreated on each render and update references in the component
(e.g., in any useCallback that currently closes over lines) to use the top-level
lines constant; this keeps the array immutable, satisfies exhaustive-deps, and
removes the lint warnings for the component that uses lines.

Comment thread public/r/xai-icon.json Outdated
"files": [
{
"path": "icons/xai-icon.tsx",
"content": "import { forwardRef, useImperativeHandle, useCallback } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst XaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = useCallback(() => {\n animate(\n \".xai-icon\",\n {\n x: [0, -1, 1, -1, 0],\n y: [0, 1, -1, 1, 0],\n },\n { duration: 0.25, repeat: 1 },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\".xai-icon\", { x: 0, y: 0 }, { duration: 0.15 });\n }, [animate]);\n\n useImperativeHandle(\n ref,\n () => ({\n startAnimation: start,\n stopAnimation: stop,\n }),\n [start, stop],\n );\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={className}\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>Grok</title>\n\n <motion.path\n className=\"xai-icon\"\n d=\"M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z\"\n fill={color}\n stroke={color}\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n />\n </motion.svg>\n );\n },\n);\n\nXaiIcon.displayName = \"XaiIcon\";\nexport default XaiIcon;\n",
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.

⚠️ Potential issue | 🟡 Minor

Title and component name mismatch.

The SVG title is "Grok" but the component is named "XaiIcon". While xAI created Grok, this inconsistency may confuse users searching for icons. Consider using either "xAI" or "Grok" consistently.

Suggested fix for consistency

Option 1: Change title to match component name

-        <title>Grok</title>
+        <title>xAI</title>

Option 2: Or use both for clarity

-        <title>Grok</title>
+        <title>xAI (Grok)</title>
📝 Committable suggestion

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

Suggested change
"content": "import { forwardRef, useImperativeHandle, useCallback } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst XaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = useCallback(() => {\n animate(\n \".xai-icon\",\n {\n x: [0, -1, 1, -1, 0],\n y: [0, 1, -1, 1, 0],\n },\n { duration: 0.25, repeat: 1 },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\".xai-icon\", { x: 0, y: 0 }, { duration: 0.15 });\n }, [animate]);\n\n useImperativeHandle(\n ref,\n () => ({\n startAnimation: start,\n stopAnimation: stop,\n }),\n [start, stop],\n );\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={className}\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>Grok</title>\n\n <motion.path\n className=\"xai-icon\"\n d=\"M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z\"\n fill={color}\n stroke={color}\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n />\n </motion.svg>\n );\n },\n);\n\nXaiIcon.displayName = \"XaiIcon\";\nexport default XaiIcon;\n",
"content": "import { forwardRef, useImperativeHandle, useCallback } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst XaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = useCallback(() => {\n animate(\n \".xai-icon\",\n {\n x: [0, -1, 1, -1, 0],\n y: [0, 1, -1, 1, 0],\n },\n { duration: 0.25, repeat: 1 },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\".xai-icon\", { x: 0, y: 0 }, { duration: 0.15 });\n }, [animate]);\n\n useImperativeHandle(\n ref,\n () => ({\n startAnimation: start,\n stopAnimation: stop,\n }),\n [start, stop],\n );\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={className}\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>xAI</title>\n\n <motion.path\n className=\"xai-icon\"\n d=\"M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z\"\n fill={color}\n stroke={color}\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n />\n </motion.svg>\n );\n },\n);\n\nXaiIcon.displayName = \"XaiIcon\";\nexport default XaiIcon;\n",
Suggested change
"content": "import { forwardRef, useImperativeHandle, useCallback } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst XaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = useCallback(() => {\n animate(\n \".xai-icon\",\n {\n x: [0, -1, 1, -1, 0],\n y: [0, 1, -1, 1, 0],\n },\n { duration: 0.25, repeat: 1 },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\".xai-icon\", { x: 0, y: 0 }, { duration: 0.15 });\n }, [animate]);\n\n useImperativeHandle(\n ref,\n () => ({\n startAnimation: start,\n stopAnimation: stop,\n }),\n [start, stop],\n );\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={className}\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>Grok</title>\n\n <motion.path\n className=\"xai-icon\"\n d=\"M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z\"\n fill={color}\n stroke={color}\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n />\n </motion.svg>\n );\n },\n);\n\nXaiIcon.displayName = \"XaiIcon\";\nexport default XaiIcon;\n",
"content": "import { forwardRef, useImperativeHandle, useCallback } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst XaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = useCallback(() => {\n animate(\n \".xai-icon\",\n {\n x: [0, -1, 1, -1, 0],\n y: [0, 1, -1, 1, 0],\n },\n { duration: 0.25, repeat: 1 },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\".xai-icon\", { x: 0, y: 0 }, { duration: 0.15 });\n }, [animate]);\n\n useImperativeHandle(\n ref,\n () => ({\n startAnimation: start,\n stopAnimation: stop,\n }),\n [start, stop],\n );\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={className}\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>xAI (Grok)</title>\n\n <motion.path\n className=\"xai-icon\"\n d=\"M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z\"\n fill={color}\n stroke={color}\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n />\n </motion.svg>\n );\n },\n);\n\nXaiIcon.displayName = \"XaiIcon\";\nexport default XaiIcon;\n",
🤖 Prompt for AI Agents
In @public/r/xai-icon.json at line 10, The SVG title text "Grok" does not match
the React component name XaiIcon; update the <title> element inside the XaiIcon
component (the JSX returned by motion.svg) to use a consistent name — either
change "Grok" to "XaiIcon" (or "xAI") or include both (e.g., "xAI (Grok)") so
the component name XaiIcon and the SVG title align for discoverability.

@Abhijit-Jha
Copy link
Copy Markdown
Member

Looks cool overall 👍
Here are a few ideas and suggestions:
LLM Studio: We can follow the same interaction pattern we used for Gemini.
Anthropic: On hover, animate the \ into an I to form AI, and revert it back to the original A\ when the hover ends.
Midjourney: Make it feel more realistic by subtly animating the waves and the boat.

xAI: Let’s explore a different and more unique interaction for this logo.

One more thing:
Please prefix all these icons with brand-.
For example: brand-xAi and .

@itstheanurag
Copy link
Copy Markdown
Contributor Author

Sure will do.

@itstheanurag
Copy link
Copy Markdown
Contributor Author

I think LMStudio animation is proper. see there are just 6 lines in that. Currently, they are animating by increasing and decreasing in size. Gemini has a rotation animation that will not work for the LMStudio animation.

What do you think?

@itstheanurag
Copy link
Copy Markdown
Contributor Author

So I have added animations for

Anthropic.
Midjourney - along with sails, waves, and bot will also go up and down, giving a subtle effect of being at sea.
Xai. animation is also changed

The brand is prefixed ahead of the icons.

anthropic-xai-midjourney.mp4

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In @icons/brand-lmstudio-icon.tsx:
- Around line 4-44: The start() function currently calls animate(...) with
repeat: Infinity for each selector but never stores the returned animation
controls, so infinite animations stack; update BrandLmStudioIcon to capture the
controls returned by animate for each selector (from the lines array) into a
persistent ref (or state) and, before starting, explicitly stop any existing
controls to avoid stacking; change stop() to iterate those stored controls and
call .stop() (then clear the storage) instead of only animating scaleX back to
1, and ensure start() also uses that stop-step at its beginning to prevent
overlapping runs.

In @icons/brand-midjourney-icon.tsx:
- Around line 36-39: The useImperativeHandle call for ref is missing its
dependency array, so update the hook invocation to include [start, stop] as the
dependency array to ensure the imperative handle (startAnimation/stopAnimation)
stays in sync when the start or stop callbacks change; locate the
useImperativeHandle(ref, () => ({ startAnimation: start, stopAnimation: stop }))
and add the dependency array [start, stop].

In @icons/brand-ollama-icon.tsx:
- Around line 38-41: The useImperativeHandle call in the component currently
omits the dependency array; update the useImperativeHandle(ref, () => ({
startAnimation: start, stopAnimation: stop })) invocation to include the
dependency array [start, stop] so that the imperative handle updates when start
or stop change (match the pattern used in brand-midjourney-icon.tsx and other
brand icons).

In @public/r/brand-aistudio-icon.json:
- Around line 1-20: The getSparkCenterOffset function can throw when calling
element.getBBox() on rendered-but-not-layouted SVG nodes; wrap both getBBox()
calls in a try/catch inside getSparkCenterOffset and return { x: 0, y: 0 } on
any exception so startAnimation (exposed by useImperativeHandle) is safe to call
even if SVG parts are hidden or not yet laid out; update the
getSparkCenterOffset implementation (referenced by frameRef, sparkRef, and
start) to catch errors from getBBox() and fall back to zeros.

In @public/r/brand-ollama-icon.json:
- Around line 1-20: The start callback launches three infinite animations via
animate(...) but never keeps the returned animation controls, so repeated starts
stack loops; update BrandOllamaIcon to store each animate(...) return (for
".ollama-body", ".ollama-eye", ".ollama-nose") in persistent refs (e.g., useRef)
when start runs and in stop call .cancel() on each stored control before calling
animate(...) to reset properties; ensure start replaces/refills the refs and
stop clears them after cancelling so repeated hover/imperative toggles do not
accumulate animation loops.
🧹 Nitpick comments (10)
lib/icons.ts (1)

694-717: Consider inserting new brand icons in alphabetical order.

The existing icons in the ICONS array follow alphabetical ordering. The new brand icons are appended at the end rather than inserted near the existing brand-google and brand-nextjs entries (around lines 134-141). Consider placing them alphabetically for consistency and easier maintenance.

icons/brand-xai-icon.tsx (1)

33-54: Consider cleaning up the setTimeout to avoid stale callbacks.

The setTimeout inside start is not cleared if the component unmounts or if start is called again rapidly. For a short hover animation this is unlikely to cause visible issues, but for robustness you could store the timeout ID in a ref and clear it in stop or on unmount.

Optional cleanup pattern
+    const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+
     const start = useCallback(() => {
+      // Clear any pending merge animation
+      if (timeoutRef.current) clearTimeout(timeoutRef.current);
+
       // Explode: all 4 parts move outward...
       ...
 
       // Then merge back
-      setTimeout(() => {
+      timeoutRef.current = setTimeout(() => {
         ...
       }, 250);
     }, [animate]);
 
     const stop = useCallback(() => {
+      if (timeoutRef.current) clearTimeout(timeoutRef.current);
       ...
     }, [animate]);
icons/brand-lmstudio-icon.tsx (1)

55-67: Consider dropping strokeWidth (or add stroke) to avoid a misleading prop.

This icon is fill={color} and doesn’t set stroke, so strokeWidth is likely a no-op. Either remove it from props for this icon, or add stroke={color} and set fill="none" if you intended strokes.

public/r/brand-ollama-icon.json (1)

1-20: Add deps to useImperativeHandle for consistency with other icons.

This file uses useImperativeHandle(ref, () => ({ ... })) without the deps array; consider adding [start, stop] to match the pattern used elsewhere and avoid stale-handle surprises if callbacks ever change.

public/r/brand-lmstudio-icon.json (1)

1-20: Keep embedded TSX content in lockstep with icons/brand-lmstudio-icon.tsx (avoid drift).

Since the registry item embeds the TSX as a string, it’s easy for it to diverge from the real file unless it’s always generated from source. If this is generated, consider adding a CI check that fails on diffs after running npm run registry:build.

public/r/brand-xai-icon.json (2)

7-13: Avoid setTimeout-queued animations that can stack / fire after hover end (or unmount).
start() schedules a merge-back via setTimeout, but stop() doesn’t cancel it—rapid hover in/out can cause delayed “merge” to run after you’ve already reset, and repeated hovers can stack timers. Consider tracking/clearing the timer (and ideally sequencing via Motion’s animation primitives if available).

Proposed change (in icons/brand-xai-icon.tsx)
 import { forwardRef, useImperativeHandle, useCallback } from "react";
+import { useRef, useEffect } from "react";
 import type { AnimatedIconHandle, AnimatedIconProps } from "./types";
 import { motion, useAnimate } from "motion/react";

 const BrandXaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(
   ({ size = 24, color = "currentColor", className = "" }, ref) => {
     const [scope, animate] = useAnimate();
+    const mergeTimeoutRef = useRef<number | null>(null);
+
+    useEffect(() => {
+      return () => {
+        if (mergeTimeoutRef.current != null) window.clearTimeout(mergeTimeoutRef.current);
+      };
+    }, []);

     const start = useCallback(() => {
+      if (mergeTimeoutRef.current != null) window.clearTimeout(mergeTimeoutRef.current);
       // Explode...
       animate(".xai-part-1", { x: -4, y: -2 }, { duration: 0.25, ease: "easeOut" });
       animate(".xai-part-2", { x: -3, y: 3 }, { duration: 0.25, ease: "easeOut" });
       animate(".xai-part-3", { x: 4, y: -3 }, { duration: 0.25, ease: "easeOut" });
       animate(".xai-part-4", { x: 3, y: 2 }, { duration: 0.25, ease: "easeOut" });

       // Then merge back
-      setTimeout(() => {
+      mergeTimeoutRef.current = window.setTimeout(() => {
         animate(".xai-part-1", { x: 0, y: 0 }, { duration: 0.3, ease: "easeInOut" });
         animate(".xai-part-2", { x: 0, y: 0 }, { duration: 0.3, ease: "easeInOut" });
         animate(".xai-part-3", { x: 0, y: 0 }, { duration: 0.3, ease: "easeInOut" });
         animate(".xai-part-4", { x: 0, y: 0 }, { duration: 0.3, ease: "easeInOut" });
       }, 250);
     }, [animate]);

     const stop = useCallback(() => {
+      if (mergeTimeoutRef.current != null) window.clearTimeout(mergeTimeoutRef.current);
       animate(".xai-part-1", { x: 0, y: 0 }, { duration: 0.2 });
       animate(".xai-part-2", { x: 0, y: 0 }, { duration: 0.2 });
       animate(".xai-part-3", { x: 0, y: 0 }, { duration: 0.2 });
       animate(".xai-part-4", { x: 0, y: 0 }, { duration: 0.2 });
     }, [animate]);

13-18: icons/types.ts duplication across registry items can drift or overwrite.
Since multiple brand registry items appear to embed the same icons/types.ts, ensure it’s identical everywhere (or better: ship it once via a shared registry dependency and reference it).

public/r/brand-anthropic-icon.json (3)

7-13: Verify SVG rotateX/3D transforms behave consistently (or prefer 2D transforms).
rotateX on an SVG <g> can be spotty depending on how Motion applies transforms to SVG elements; consider switching to a 2D transform (rotate/scale/skew) unless you’ve verified this across your target browsers.


7-13: Optional: align Anthropic hover interaction with the “AI” (\ → I) suggestion.
Current hover is a nice pulse, but it doesn’t match the requested “\ becomes I” interaction. If that feedback is still desired, consider implementing it (path swap/morph, or a subtle translate/scale that visually reads as “I”).


13-18: Same icons/types.ts registry duplication concern here.
If this is intentional, keep the file content 100% identical across items (or extract into a shared registry dependency).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b182fe and 7e7eec6.

📒 Files selected for processing (16)
  • icons/brand-aistudio-icon.tsx
  • icons/brand-anthropic-icon.tsx
  • icons/brand-lmstudio-icon.tsx
  • icons/brand-midjourney-icon.tsx
  • icons/brand-ollama-icon.tsx
  • icons/brand-xai-icon.tsx
  • icons/index.ts
  • lib/icons.ts
  • public/r/brand-aistudio-icon.json
  • public/r/brand-anthropic-icon.json
  • public/r/brand-lmstudio-icon.json
  • public/r/brand-midjourney-icon.json
  • public/r/brand-ollama-icon.json
  • public/r/brand-xai-icon.json
  • public/r/registry.json
  • registry.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • registry.json
🔇 Additional comments (6)
icons/brand-aistudio-icon.tsx (1)

1-120: LGTM!

The component is well-structured with proper use of forwardRef, useImperativeHandle, and the motion library. The getBBox-based centering logic for the spark animation is a nice approach for dynamic positioning. The hover interaction and imperative API are correctly wired.

icons/brand-anthropic-icon.tsx (1)

1-70: LGTM!

The component follows the established pattern consistently. The 3D transform animation (rotateX, skewX, scaleY) on the backslash creates a subtle hover effect. The imperative API and hover handlers are correctly implemented.

public/r/brand-midjourney-icon.json (1)

1-20: Registry file will need regeneration after source fix.

This registry JSON embeds the source code. Once the useImperativeHandle dependency array is added to the source file (icons/brand-midjourney-icon.tsx), regenerate this registry file to keep it in sync.

icons/brand-xai-icon.tsx (1)

64-71: LGTM on imperative handle.

The useImperativeHandle correctly includes the [start, stop] dependency array, consistent with BrandAiStudioIcon.

public/r/registry.json (1)

687-822: Registry additions are correctly structured and ordered.

All six new brand-icon entries (aistudio, anthropic, lmstudio, midjourney, ollama, xai) follow the established pattern with motion dependency, proper file references, and alphabetical placement. All referenced icon files and icons/types.ts exist in the repository.

icons/index.ts (1)

198-203: Imports look consistent with the “brand-*” naming objective.

Comment thread icons/brand-lmstudio-icon.tsx Outdated
Comment on lines +36 to +39
useImperativeHandle(ref, () => ({
startAnimation: start,
stopAnimation: stop,
}));
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.

⚠️ Potential issue | 🟡 Minor

Add dependency array to useImperativeHandle.

The useImperativeHandle hook is missing its dependency array. Other brand icon components in this PR (e.g., BrandAiStudioIcon, BrandXaiIcon) include [start, stop] as dependencies. This ensures the imperative handle stays in sync if the callbacks change.

Proposed fix
-    useImperativeHandle(ref, () => ({
-      startAnimation: start,
-      stopAnimation: stop,
-    }));
+    useImperativeHandle(
+      ref,
+      () => ({
+        startAnimation: start,
+        stopAnimation: stop,
+      }),
+      [start, stop],
+    );
📝 Committable suggestion

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

Suggested change
useImperativeHandle(ref, () => ({
startAnimation: start,
stopAnimation: stop,
}));
useImperativeHandle(
ref,
() => ({
startAnimation: start,
stopAnimation: stop,
}),
[start, stop],
);
🤖 Prompt for AI Agents
In @icons/brand-midjourney-icon.tsx around lines 36 - 39, The
useImperativeHandle call for ref is missing its dependency array, so update the
hook invocation to include [start, stop] as the dependency array to ensure the
imperative handle (startAnimation/stopAnimation) stays in sync when the start or
stop callbacks change; locate the useImperativeHandle(ref, () => ({
startAnimation: start, stopAnimation: stop })) and add the dependency array
[start, stop].

Comment on lines +38 to +41
useImperativeHandle(ref, () => ({
startAnimation: start,
stopAnimation: stop,
}));
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.

⚠️ Potential issue | 🟡 Minor

Add dependency array to useImperativeHandle.

Same issue as in brand-midjourney-icon.tsx — the dependency array is missing. Add [start, stop] for consistency with other brand icons.

Proposed fix
-    useImperativeHandle(ref, () => ({
-      startAnimation: start,
-      stopAnimation: stop,
-    }));
+    useImperativeHandle(
+      ref,
+      () => ({
+        startAnimation: start,
+        stopAnimation: stop,
+      }),
+      [start, stop],
+    );
📝 Committable suggestion

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

Suggested change
useImperativeHandle(ref, () => ({
startAnimation: start,
stopAnimation: stop,
}));
useImperativeHandle(
ref,
() => ({
startAnimation: start,
stopAnimation: stop,
}),
[start, stop],
);
🤖 Prompt for AI Agents
In @icons/brand-ollama-icon.tsx around lines 38 - 41, The useImperativeHandle
call in the component currently omits the dependency array; update the
useImperativeHandle(ref, () => ({ startAnimation: start, stopAnimation: stop }))
invocation to include the dependency array [start, stop] so that the imperative
handle updates when start or stop change (match the pattern used in
brand-midjourney-icon.tsx and other brand icons).

Comment thread icons/index.ts
Comment on lines +1 to +20
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "brand-aistudio-icon",
"dependencies": ["motion"],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "icons/brand-aistudio-icon.tsx",
"content": "import { forwardRef, useImperativeHandle, useCallback, useRef } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst BrandAiStudioIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const frameRef = useRef<SVGGElement | null>(null);\n const sparkRef = useRef<SVGGElement | null>(null);\n\n const getSparkCenterOffset = () => {\n if (!frameRef.current || !sparkRef.current) return { x: 0, y: 0 };\n\n const frame = frameRef.current.getBBox();\n const spark = sparkRef.current.getBBox();\n\n const frameCenterX = frame.x + frame.width / 2;\n const frameCenterY = frame.y + frame.height / 2;\n\n const sparkCenterX = spark.x + spark.width / 2;\n const sparkCenterY = spark.y + spark.height / 2;\n\n return {\n x: frameCenterX - sparkCenterX,\n y: frameCenterY - sparkCenterY,\n };\n };\n\n const start = useCallback(() => {\n const { x, y } = getSparkCenterOffset();\n\n // Move Gemini into the IDE\n animate(\n \".google-spark\",\n { x, y, scale: 0.9 },\n { duration: 0.35, ease: \"easeInOut\" },\n );\n\n // Pull the frame inward slightly so it \"closes\"\n animate(\n \".google-frame\",\n { scale: 0.94 },\n { duration: 0.35, ease: \"easeInOut\" },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\n \".google-spark\",\n { x: 0, y: 0, scale: 1 },\n { duration: 0.3, ease: \"easeInOut\" },\n );\n\n animate(\n \".google-frame\",\n { scale: 1 },\n { duration: 0.3, ease: \"easeInOut\" },\n );\n }, [animate]);\n\n useImperativeHandle(\n ref,\n () => ({\n startAnimation: start,\n stopAnimation: stop,\n }),\n [start, stop],\n );\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill={color}\n className={className}\n xmlns=\"http://www.w3.org/2000/svg\"\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>Google AI Studio</title>\n\n {/* Frame */}\n <motion.g\n ref={frameRef}\n className=\"google-frame\"\n style={{\n transformBox: \"fill-box\",\n transformOrigin: \"center\",\n }}\n >\n <path\n fillRule=\"evenodd\"\n d=\"M9.921 4.196H6.328A2.705 2.705 0 003.623 6.9v11.362a2.705 2.705 0 002.705 2.705h11.363a2.705 2.705 0 002.705-2.705v-4.756l1.623-1.113v5.87a4.329 4.329 0 01-4.328 4.328H6.328A4.329 4.329 0 012 18.263V6.901a4.328 4.328 0 014.328-4.329h4.545l-.952 1.624z\"\n />\n </motion.g>\n\n {/* Gemini sparkle */}\n <motion.g\n ref={sparkRef}\n className=\"google-spark\"\n style={{\n transformBox: \"fill-box\",\n transformOrigin: \"center\",\n }}\n >\n <path\n fillRule=\"evenodd\"\n d=\"M17.82 0c.145 0 .268.104.299.246a7 7 0 001.9 3.484 7 7 0 003.485 1.901c.142.031.246.154.246.3a.308.308 0 01-.246.298A7 7 0 0020.02 8.13a7 7 0 00-1.912 3.535.297.297 0 01-.288.238.297.297 0 01-.288-.238A7 7 0 0015.62 8.13a7 7 0 00-3.535-1.912.297.297 0 01-.238-.288c0-.14.1-.26.238-.288A7 7 0 0015.62 3.73 7.001 7.001 0 0017.521.246.308.308 0 0117.82 0z\"\n />\n </motion.g>\n </motion.svg>\n );\n },\n);\n\nBrandAiStudioIcon.displayName = \"BrandAiStudioIcon\";\nexport default BrandAiStudioIcon;\n",
"type": "registry:ui"
},
{
"path": "icons/types.ts",
"content": "export interface AnimatedIconProps {\n /** Icon size in pixels or CSS string */\n size?: number | string;\n /** Icon color (defaults to currentColor) */\n color?: string;\n /** SVG stroke width */\n strokeWidth?: number;\n /** Additional CSS classes */\n className?: string;\n}\n\nexport interface AnimatedIconHandle {\n startAnimation: () => void;\n stopAnimation: () => void;\n}\n",
"type": "registry:ui"
}
],
"type": "registry:ui"
}
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

getBBox() throws exception SVG element not rendered

💡 Result:

Cause

  • getBBox() requires the element to be "rendered" (connected and not display:none or inside a non-rendered ancestor). If the element is detached, has display:none, or is a descendant of a non‑rendered element, some UAs throw or return invalid results. [1][2]

How to fix (pick one that fits your case)

  1. Make sure the element is in the document and visible before calling getBBox:

    • append it to the DOM (or to an that is in the DOM) and ensure CSS display ≠ none.
    • call getBBox after the element is inserted and the browser had a chance to render (e.g. in requestAnimationFrame or after load). [1][2]

    Example:
    el = document.querySelector('svg > g');
    requestAnimationFrame(() => { const bb = el.getBBox(); /* use bb */ });

  2. If you must measure while hidden, temporarily make a measuring SVG that is visible (but offscreen or 0‑opacity) and clone the element into it, measure, then remove the clone.

  3. Use getBoundingClientRect() when you need viewport coordinates (works only for rendered/painted elements) as an alternative for layout/position needs.

  4. For elements in or elements that are never rendered, compute geometry manually or clone them into a rendered container first—those elements do not contribute to rendering and getBBox may not apply. [2]

References

  • MDN: SVGGraphicsElement.getBBox(). [1]
  • SVG spec: Rendered vs non‑rendered elements (when elements do/don't contribute to geometry/getBBox). [2]

If you want, tell me which browser and code snippet you're using and I’ll suggest the minimal change.


🌐 Web query:

SVG getBBox() can throw TypeError display none

💡 Result:

Yes — calling SVGElement.getBBox() can throw (or return empty/zero) when the element isn’t rendered (for example it or an ancestor uses display: none, or the element has zero geometry). Browsers differ: Chrome is more tolerant, but Firefox/Edge will treat display:none as non‑existent and can throw or return invalid results. (developer.mozilla.org)

Workarounds

  • Avoid calling getBBox on elements with display: none. Check first:
  • Use visibility: hidden (keeps layout/rendering) instead of display: none, then getBBox works. (stackoverflow.com)
  • If you must measure while hidden, temporarily insert a copy into a visible (or visibility:hidden) SVG in the DOM, measure, then remove. Example pattern:
    • clone the node, append to an offscreen SVG container with visibility:hidden (not display:none), call getBBox(), then remove.
  • As alternatives, for viewport-based sizes use getBoundingClientRect(), or for paths use path.getTotalLength()/getPointAtLength to compute extents.

References: MDN getBBox (behavior/spec) and community reports/solutions. (developer.mozilla.org)

Citations:


🌐 Web query:

SVG getBBox() specification browser behavior exceptions

💡 Result:

Short summary — spec vs actual browser behavior

  • Spec (SVG 2): getBBox(options) must return a DOMRect for the computed bounding box. If called on a non‑rendered element and the user agent is not able to compute the geometry, the UA must throw an InvalidStateError. [1]

  • Reality / interop exceptions:

    • Implementations differ for malformed or non‑rendered shapes (e.g. negative/missing width on , detached elements). Some browsers return zeros (x=0,y=0,width=0,height=0) instead of throwing. Others return partial/derived values (Chrome historically returned the element’s x/y but zero width for an invalid rect). These inconsistencies are visible in WPT/issue discussion and tests. [2][3]
    • Chromium has code/commits and issue discussion showing behavior was adjusted to avoid throwing when the UA can compute geometry from computed styles (i.e., implementations have been hardened/changed for interoperability). [4]

Sources

  1. SVG 2 specification (getBBox / rendered vs non‑rendered element rules). [1]
  2. WPT / svgwg issue demonstrating interop differences for invalid/non‑rendered shapes (examples and per‑browser table). [2]
  3. MDN entry for SVGGraphicsElement.getBBox (behavior notes & compatibility). [3]
  4. Chromium commit/discussion referencing change to avoid throwing when geometry can be computed. [4]

If you want, I can paste the exact WPT test cases and a tiny repro showing the differing outputs per browser.


Wrap getBBox() calls in try/catch—null refs don't guarantee rendered elements.

getSparkCenterOffset() checks for null refs but doesn't guard against exceptions when getBBox() is called on SVG elements that exist in the DOM but aren't rendered (e.g., display: none, hidden ancestor, or detached). The SVG spec allows (and some browsers including Firefox/Edge do) throw InvalidStateError in these cases. Since startAnimation() is exposed via imperative handle, callers can trigger it before layout/visibility is guaranteed.

Wrap both getBBox() calls in a try/catch block and return { x: 0, y: 0 } on exception:

Suggested fix
const getSparkCenterOffset = () => {
  if (!frameRef.current || !sparkRef.current) return { x: 0, y: 0 };

  try {
    const frame = frameRef.current.getBBox();
    const spark = sparkRef.current.getBBox();

    const frameCenterX = frame.x + frame.width / 2;
    const frameCenterY = frame.y + frame.height / 2;

    const sparkCenterX = spark.x + spark.width / 2;
    const sparkCenterY = spark.y + spark.height / 2;

    return {
      x: frameCenterX - sparkCenterX,
      y: frameCenterY - sparkCenterY,
    };
  } catch {
    return { x: 0, y: 0 };
  }
};
🤖 Prompt for AI Agents
In @public/r/brand-aistudio-icon.json around lines 1 - 20, The
getSparkCenterOffset function can throw when calling element.getBBox() on
rendered-but-not-layouted SVG nodes; wrap both getBBox() calls in a try/catch
inside getSparkCenterOffset and return { x: 0, y: 0 } on any exception so
startAnimation (exposed by useImperativeHandle) is safe to call even if SVG
parts are hidden or not yet laid out; update the getSparkCenterOffset
implementation (referenced by frameRef, sparkRef, and start) to catch errors
from getBBox() and fall back to zeros.

Comment on lines +1 to +20
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "brand-ollama-icon",
"dependencies": ["motion"],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "icons/brand-ollama-icon.tsx",
"content": "import { forwardRef, useImperativeHandle, useCallback } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst BrandOllamaIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = useCallback(() => {\n // Body breathing effect - subtle scale\n animate(\n \".ollama-body\",\n { scale: [1, 1.03, 1] },\n { duration: 0.8, repeat: Infinity, ease: \"easeInOut\" },\n );\n\n // Eyes blink - immediate and noticeable\n animate(\n \".ollama-eye\",\n { scaleY: [1, 0.1, 1] },\n { duration: 0.2, delay: 0.3, repeat: Infinity, repeatDelay: 2 },\n );\n\n // Nose wiggle - playful\n animate(\n \".ollama-nose\",\n { y: [0, -1, 1, 0] },\n { duration: 0.3, repeat: Infinity, repeatDelay: 1 },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\".ollama-body\", { scale: 1 }, { duration: 0.2 });\n animate(\".ollama-eye\", { scaleY: 1 }, { duration: 0.2 });\n animate(\".ollama-nose\", { y: 0 }, { duration: 0.2 });\n }, [animate]);\n\n useImperativeHandle(ref, () => ({\n startAnimation: start,\n stopAnimation: stop,\n }));\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill={color}\n fillRule=\"evenodd\"\n className={className}\n xmlns=\"http://www.w3.org/2000/svg\"\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>Ollama</title>\n\n {/* Outer body/shell (without ears) */}\n <motion.path\n className=\"ollama-body\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center bottom\" }}\n d=\"M7.905 1.09c.216.085.411.225.588.41.295.306.544.744.734 1.263.191.522.315 1.1.362 1.68a5.054 5.054 0 012.049-.636l.051-.004c.87-.07 1.73.087 2.48.474.101.053.2.11.297.17.05-.569.172-1.134.36-1.644.19-.52.439-.957.733-1.264a1.67 1.67 0 01.589-.41c.257-.1.53-.118.796-.042.401.114.745.368 1.016.737.248.337.434.769.561 1.287.23.934.27 2.163.115 3.645l.053.04.026.019c.757.576 1.284 1.397 1.563 2.35.435 1.487.216 3.155-.534 4.088l-.018.021.002.003c.417.762.67 1.567.724 2.4l.002.03c.064 1.065-.2 2.137-.814 3.19l-.007.01.01.024c.472 1.157.62 2.322.438 3.486l-.006.039a.651.651 0 01-.747.536.648.648 0 01-.54-.742c.167-1.033.01-2.069-.48-3.123a.643.643 0 01.04-.617l.004-.006c.604-.924.854-1.83.8-2.72-.046-.779-.325-1.544-.8-2.273a.644.644 0 01.18-.886l.009-.006c.243-.159.467-.565.58-1.12a4.229 4.229 0 00-.095-1.974c-.205-.7-.58-1.284-1.105-1.683-.595-.454-1.383-.673-2.38-.61a.653.653 0 01-.632-.371c-.314-.665-.772-1.141-1.343-1.436a3.288 3.288 0 00-1.772-.332c-1.245.099-2.343.801-2.67 1.686a.652.652 0 01-.61.425c-1.067.002-1.893.252-2.497.703-.522.39-.878.935-1.066 1.588a4.07 4.07 0 00-.068 1.886c.112.558.331 1.02.582 1.269l.008.007c.212.207.257.53.109.785-.36.622-.629 1.549-.673 2.44-.05 1.018.186 1.902.719 2.536l.016.019a.643.643 0 01.095.69c-.576 1.236-.753 2.252-.562 3.052a.652.652 0 01-1.269.298c-.243-1.018-.078-2.184.473-3.498l.014-.035-.008-.012a4.339 4.339 0 01-.598-1.309l-.005-.019a5.764 5.764 0 01-.177-1.785c.044-.91.278-1.842.622-2.59l.012-.026-.002-.002c-.293-.418-.51-.953-.63-1.545l-.005-.024a5.352 5.352 0 01.093-2.49c.262-.915.777-1.701 1.536-2.269.06-.045.123-.09.186-.132-.159-1.493-.119-2.73.112-3.67.127-.518.314-.95.562-1.287.27-.368.614-.622 1.015-.737.266-.076.54-.059.797.042z\"\n />\n\n {/* Face outline */}\n <motion.path\n className=\"ollama-face\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n d=\"M12.021 10.18c.936 0 1.8.313 2.446.855.63.527 1.005 1.235 1.005 1.94 0 .888-.406 1.58-1.133 2.022-.62.375-1.451.557-2.403.557-1.009 0-1.871-.259-2.493-.734-.617-.47-.963-1.13-.963-1.845 0-.707.398-1.417 1.056-1.946.668-.537 1.55-.849 2.485-.849zm0 .896a3.07 3.07 0 00-1.916.65c-.461.37-.722.835-.722 1.25 0 .428.21.829.61 1.134.455.347 1.124.548 1.943.548.799 0 1.473-.147 1.932-.426.463-.28.7-.686.7-1.257 0-.423-.246-.89-.683-1.256-.484-.405-1.14-.643-1.864-.643z\"\n />\n\n {/* Nose */}\n <motion.path\n className=\"ollama-nose\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n d=\"M12.683 12.286l.004.004c.12.151.095.37-.056.49l-.292.23v.446a.375.375 0 01-.376.373.375.375 0 01-.376-.373v-.46l-.271-.218a.347.347 0 01-.052-.49.353.353 0 01.494-.051l.215.172.22-.174a.353.353 0 01.49.051z\"\n />\n\n {/* Left eye */}\n <motion.circle\n className=\"ollama-eye\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n cx=\"7.643\"\n cy=\"11.238\"\n r=\"0.868\"\n />\n\n {/* Right eye */}\n <motion.circle\n className=\"ollama-eye\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n cx=\"16.349\"\n cy=\"11.238\"\n r=\"0.868\"\n />\n </motion.svg>\n );\n },\n);\n\nBrandOllamaIcon.displayName = \"BrandOllamaIcon\";\nexport default BrandOllamaIcon;\n",
"type": "registry:ui"
},
{
"path": "icons/types.ts",
"content": "export interface AnimatedIconProps {\n /** Icon size in pixels or CSS string */\n size?: number | string;\n /** Icon color (defaults to currentColor) */\n color?: string;\n /** SVG stroke width */\n strokeWidth?: number;\n /** Additional CSS classes */\n className?: string;\n}\n\nexport interface AnimatedIconHandle {\n startAnimation: () => void;\n stopAnimation: () => void;\n}\n",
"type": "registry:ui"
}
],
"type": "registry:ui"
}
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

motion/react animate function cancellation infinite repeat

💡 Result:

Short answer: animate() returns playback controls you can call to stop/cancel an infinite repeat. Use animation.stop() to commit the current value, or animation.cancel() to revert to the initial state. If you start animations with useAnimate(), those animations are automatically cleaned up when the component unmounts. [1][2][3]

Example:

  • standalone animate():
    const animation = animate(x, [0, 100], { repeat: Infinity, duration: 2 });
    // stop (commits current value)
    animation.stop();
    // or cancel (reverts to initial)
    animation.cancel();. [1]

  • useAnimate within a component (automatic cleanup on unmount):
    const [scope, animate] = useAnimate();
    useEffect(() => {
    const a = animate(scope.current, { opacity: 1 }, { repeat: Infinity });
    return () => a.cancel?.() /* optional explicit cleanup, but useAnimate auto-cleans on unmount */;
    }, []);. [2]

Also: motion values expose stop() and jump() for fine-grained control of running animations. [3]

Sources:
[1] animate() API (controls, stop, cancel).
[2] useAnimate() hook (scoped animate + automatic cleanup).
[3] MotionValue API (stop(), jump()).


🌐 Web query:

framer motion useAnimate cancel stop repeat Infinity

💡 Result:

Short answer: store the animation object returned by animate() (or use AnimationControls) and call its cancel()/stop() method.

Examples:

  • useAnimate (motion.animate): const animation = await animate(el, { x: 100 }, { repeat: Infinity }); animation.cancel(); // or animation.stop();. The animate() return provides control methods like cancel(), complete(), play(), pause(), stop(). [1][2]

  • useAnimationControls / manual controls: const controls = useAnimationControls(); controls.start({ x: 100, transition:{ repeat: Infinity } }); controls.stop(); // stops the running animation. [3]

References

  1. useAnimate docs (Motion): https://motion.dev/motion/use-animate/ [7].
  2. framer-motion release notes (animate() added cancel/complete/stop/play/pause): framer-motion changelog/package notes. [8].
  3. useAnimationControls docs (stop method): motion docs for useAnimationControls. [5].

🏁 Script executed:

cd /tmp && npm info motion 2>/dev/null | grep -A 5 "description\|version" | head -20

Repository: itshover/itshover

Length of output: 331


Store and cancel animation controls to prevent infinite repeat stacking.

start() launches three repeat: Infinity animations but doesn't store the returned animation controls. stop() calls animate() again to reset values, but this doesn't cancel the running loops—rapid hover cycles or repeated imperative calls will stack animations. Refactor to store the animation controls returned by each animate() call, then call .cancel() on them in stop() to properly halt the loops before resetting.

🤖 Prompt for AI Agents
In @public/r/brand-ollama-icon.json around lines 1 - 20, The start callback
launches three infinite animations via animate(...) but never keeps the returned
animation controls, so repeated starts stack loops; update BrandOllamaIcon to
store each animate(...) return (for ".ollama-body", ".ollama-eye",
".ollama-nose") in persistent refs (e.g., useRef) when start runs and in stop
call .cancel() on each stored control before calling animate(...) to reset
properties; ensure start replaces/refills the refs and stop clears them after
cancelling so repeated hover/imperative toggles do not accumulate animation
loops.

@Abhijit-Jha
Copy link
Copy Markdown
Member

Hey @itstheanurag are you working on this?

@itstheanurag
Copy link
Copy Markdown
Contributor Author

hey @Abhijit-Jha i did work on it, added a new video for the latest changes above, never heard your views on the new one.

So I have added animations for

Anthropic. Midjourney - along with sails, waves, and bot will also go up and down, giving a subtle effect of being at sea. Xai. animation is also changed

The brand is prefixed ahead of the icons.

anthropic-xai-midjourney.mp4

@Abhijit-Jha
Copy link
Copy Markdown
Member

LGTM @itstheanurag.
There are merge conflicts, please resolve them.

@itstheanurag itstheanurag force-pushed the icons/anthropic-midjourney-ollama-aistudio-xai-lmstudio branch from 233ec0c to 01073eb Compare January 15, 2026 06:25
@itstheanurag
Copy link
Copy Markdown
Contributor Author

@Abhijit-Jha i have resolved the merge conflicts. After this is merged, there will be 217 icons here.

please have it merged before other changes are merged.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@icons/brand-xai-icon.tsx`:
- Around line 33-54: The setTimeout in start() creates an untracked timer that
can fire after unmount or after stop(); store its ID in a scoped variable or
class/Ref (e.g., mergeTimeoutId) and before scheduling a new timeout clear any
existing one to avoid stacking; call clearTimeout(mergeTimeoutId) inside stop()
and in the component unmount/useEffect cleanup so the animate(...) callbacks for
".xai-part-1"…".xai-part-4" never run on an unmounted component.
♻️ Duplicate comments (4)
icons/brand-lmstudio-icon.tsx (1)

21-45: Infinite animations stack on rapid hover cycles—store and cancel animation controls.

The start() function schedules repeat: Infinity animations without storing the returned controls. The stop() function only animates properties back to defaults but doesn't cancel the running infinite loops. Rapid hover triggers will stack animations, causing CPU overhead and visual glitches.

🔧 Recommended fix
+import { forwardRef, useImperativeHandle, useCallback, useRef } from "react";
-import { forwardRef, useImperativeHandle, useCallback } from "react";
 import type { AnimatedIconHandle, AnimatedIconProps } from "./types";
 import { motion, useAnimate } from "motion/react";

 // ... inside component:
+    const controlsRef = useRef<Array<{ stop?: () => void }> | null>(null);

     const start = useCallback(() => {
+      // Prevent stacking if already running
+      if (controlsRef.current) return;
+      const controls: Array<{ stop?: () => void }> = [];
       lines.forEach((line, i) => {
         const strength = 1 + i * 0.06;
-        animate(
+        const c = animate(
           line,
           { scaleX: [1, strength, 0.9, strength * 0.95, 1] },
           { duration: 1.4, delay: i * 0.08, ease: "easeInOut", repeat: Infinity },
         );
+        controls.push(c);
       });
+      controlsRef.current = controls;
     }, [animate]);

     const stop = useCallback(() => {
+      controlsRef.current?.forEach((c) => c.stop?.());
+      controlsRef.current = null;
       animate(
         lines.join(", "),
         { scaleX: 1 },
         { duration: 0.2, ease: "easeOut" },
       );
     }, [animate]);
public/r/brand-ollama-icon.json (1)

8-12: Embedded component has the same infinite animation stacking issue.

The embedded BrandOllamaIcon code in this registry file has the same problem as brand-lmstudio-icon.tsx: start() launches repeat: Infinity animations without storing controls, and stop() doesn't cancel running loops. Apply the same fix pattern—store animation controls in a ref and call .stop() on them before resetting.

public/r/brand-lmstudio-icon.json (1)

8-12: Embedded component code mirrors the source file issues.

The embedded BrandLmStudioIcon code has the same infinite animation stacking issue flagged in icons/brand-lmstudio-icon.tsx. Once the source file is fixed, regenerate this registry JSON to keep them in sync.

icons/index.ts (1)

1545-1549: Inaccurate keywords for brand-midjourney-icon.

MidJourney is an image generation AI, not a chatbot. The keywords "chatbot" and "conversation" are misleading for this icon.

Suggested fix
   {
     name: "brand-midjourney-icon",
     icon: BrandMidJourneyIcon,
-    keywords: ["midjourney", "ai", "chatbot", "conversation", "brand"],
+    keywords: ["midjourney", "ai", "image", "generation", "art", "brand"],
   },
🧹 Nitpick comments (2)
icons/brand-lmstudio-icon.tsx (1)

12-19: Hoist lines array outside the component to stabilize references.

The lines array is recreated on every render. Since it's a constant, move it outside the component to avoid unnecessary allocations and to satisfy the ESLint hook dependency warning.

♻️ Proposed fix
 import { forwardRef, useImperativeHandle, useCallback } from "react";
 import type { AnimatedIconHandle, AnimatedIconProps } from "./types";
 import { motion, useAnimate } from "motion/react";

+const lines = [
+  ".lm-line-1",
+  ".lm-line-2",
+  ".lm-line-3",
+  ".lm-line-4",
+  ".lm-line-5",
+  ".lm-line-6",
+];
+
 const BrandLmStudioIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(
   (
     { size = 24, color = "currentColor", strokeWidth = 1, className = "" },
     ref,
   ) => {
     const [scope, animate] = useAnimate();

-    const lines = [
-      ".lm-line-1",
-      ".lm-line-2",
-      ".lm-line-3",
-      ".lm-line-4",
-      ".lm-line-5",
-      ".lm-line-6",
-    ];
-
     const start = useCallback(() => {
public/r/brand-anthropic-icon.json (1)

9-10: Misleading comment in the embedded component code.

The comment says "Simple scale pulse on the slash" but the animation applies rotateX, skewX, and scaleY transforms, which create a 3D perspective effect rather than a pulse. Consider updating the comment to accurately describe the transform effect.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e7eec6 and 01073eb.

📒 Files selected for processing (17)
  • icons/brand-aistudio-icon.tsx
  • icons/brand-anthropic-icon.tsx
  • icons/brand-lmstudio-icon.tsx
  • icons/brand-midjourney-icon.tsx
  • icons/brand-ollama-icon.tsx
  • icons/brand-xai-icon.tsx
  • icons/index.ts
  • lib/icons.ts
  • public/r/brand-aistudio-icon.json
  • public/r/brand-anthropic-icon.json
  • public/r/brand-bags-fm-icon.json
  • public/r/brand-lmstudio-icon.json
  • public/r/brand-midjourney-icon.json
  • public/r/brand-ollama-icon.json
  • public/r/brand-xai-icon.json
  • public/r/registry.json
  • registry.json
✅ Files skipped from review due to trivial changes (1)
  • public/r/brand-bags-fm-icon.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • icons/brand-ollama-icon.tsx
  • icons/brand-midjourney-icon.tsx
  • public/r/brand-xai-icon.json
  • registry.json
  • public/r/brand-aistudio-icon.json
  • icons/brand-anthropic-icon.tsx
🧰 Additional context used
🪛 GitHub Check: build
icons/brand-lmstudio-icon.tsx

[warning] 45-45:
React Hook useCallback has a missing dependency: 'lines'. Either include it or remove the dependency array


[warning] 37-37:
React Hook useCallback has a missing dependency: 'lines'. Either include it or remove the dependency array

🔇 Additional comments (12)
icons/brand-lmstudio-icon.tsx (1)

47-112: Imperative handle and SVG structure look good.

The useImperativeHandle correctly exposes the animation API, hover handlers are properly wired, and the SVG paths have appropriate transform-origin settings for the scale animation.

public/r/brand-ollama-icon.json (1)

1-7: Registry metadata is correctly structured.

The schema reference, dependencies, and file paths follow the expected shadcn registry format.

icons/brand-aistudio-icon.tsx (2)

30-69: Clean finite animation implementation.

Unlike the other icons using repeat: Infinity, this component uses finite-duration animations that complete naturally. No animation stacking concerns here.


12-28: No action needed—the concern is invalid.

getSparkCenterOffset() is called only via the onHoverStart event handler, which cannot fire before the SVG is rendered and painted. By the time a user can hover, the refs are guaranteed to be attached and getBBox() will return correct values. The guard check is defensive but not addressing a real issue here.

Likely an incorrect or invalid review comment.

public/r/brand-lmstudio-icon.json (1)

1-7: Registry entry structure is correct.

Dependencies and file references are properly configured.

icons/brand-xai-icon.tsx (2)

57-71: Stop and imperative handle implementation is correct.

The stop() function properly resets all parts, and the imperative handle correctly exposes the animation API. Just ensure the timeout cleanup is added as noted above.


73-120: SVG structure and hover wiring look good.

The four-part X logo decomposition with individual transform origins enables the explode/merge effect nicely.

public/r/brand-midjourney-icon.json (1)

1-20: LGTM!

The registry entry is well-structured. The embedded component correctly implements the animated icon pattern with forwardRef, useAnimate, and useImperativeHandle for the imperative API. The sail and wave animations provide a nice nautical effect.

icons/index.ts (2)

210-218: LGTM!

The imports for the six new brand icons are correctly added and follow the existing naming conventions.


1525-1529: LGTM on remaining brand icon entries.

The keywords for Anthropic, AI Studio, xAI, LM Studio, and Ollama are appropriate for their respective AI tools.

Also applies to: 1530-1534, 1535-1539, 1540-1544, 1550-1554

lib/icons.ts (1)

738-785: LGTM!

The new icon entries correctly provide both the -icon suffixed names and shorter aliases, all pointing to the correct paths. This maintains consistency with the existing pattern in the file.

public/r/registry.json (1)

687-720: LGTM!

The six new brand icon registry entries are correctly structured with proper dependencies and file references. Since this is an auto-generated file, ensure these entries are regenerated via npm run registry:build if any source files change.

Also applies to: 823-856, 891-907, 1095-1111

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread icons/brand-xai-icon.tsx
Comment on lines +33 to +54
setTimeout(() => {
animate(
".xai-part-1",
{ x: 0, y: 0 },
{ duration: 0.3, ease: "easeInOut" },
);
animate(
".xai-part-2",
{ x: 0, y: 0 },
{ duration: 0.3, ease: "easeInOut" },
);
animate(
".xai-part-3",
{ x: 0, y: 0 },
{ duration: 0.3, ease: "easeInOut" },
);
animate(
".xai-part-4",
{ x: 0, y: 0 },
{ duration: 0.3, ease: "easeInOut" },
);
}, 250);
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.

⚠️ Potential issue | 🟠 Major

setTimeout is not cleaned up—can cause stale callbacks and memory leaks.

The setTimeout in start() is not stored or cleared. If the component unmounts or stop() is called within 250ms, the merge animation callback will still execute on a potentially unmounted component or cause visual conflicts. Rapid start() calls will also stack multiple timeouts.

🔧 Recommended fix
+import { forwardRef, useImperativeHandle, useCallback, useRef, useEffect } from "react";
-import { forwardRef, useImperativeHandle, useCallback } from "react";
 // ...

 const BrandXaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(
   ({ size = 24, color = "currentColor", className = "" }, ref) => {
     const [scope, animate] = useAnimate();
+    const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

     const start = useCallback(() => {
+      // Clear any pending merge animation
+      if (timeoutRef.current) {
+        clearTimeout(timeoutRef.current);
+      }
+
       // Explode animations...
       animate(".xai-part-1", { x: -4, y: -2 }, { duration: 0.25, ease: "easeOut" });
       // ... other explode animations

-      setTimeout(() => {
+      timeoutRef.current = setTimeout(() => {
         // Merge animations...
+        timeoutRef.current = null;
       }, 250);
     }, [animate]);

     const stop = useCallback(() => {
+      if (timeoutRef.current) {
+        clearTimeout(timeoutRef.current);
+        timeoutRef.current = null;
+      }
       animate(".xai-part-1", { x: 0, y: 0 }, { duration: 0.2 });
       // ... other stop animations
     }, [animate]);

+    // Cleanup on unmount
+    useEffect(() => {
+      return () => {
+        if (timeoutRef.current) {
+          clearTimeout(timeoutRef.current);
+        }
+      };
+    }, []);
📝 Committable suggestion

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

Suggested change
setTimeout(() => {
animate(
".xai-part-1",
{ x: 0, y: 0 },
{ duration: 0.3, ease: "easeInOut" },
);
animate(
".xai-part-2",
{ x: 0, y: 0 },
{ duration: 0.3, ease: "easeInOut" },
);
animate(
".xai-part-3",
{ x: 0, y: 0 },
{ duration: 0.3, ease: "easeInOut" },
);
animate(
".xai-part-4",
{ x: 0, y: 0 },
{ duration: 0.3, ease: "easeInOut" },
);
}, 250);
import { forwardRef, useImperativeHandle, useCallback, useRef, useEffect } from "react";
// ... other imports
const BrandXaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(
({ size = 24, color = "currentColor", className = "" }, ref) => {
const [scope, animate] = useAnimate();
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const start = useCallback(() => {
// Clear any pending merge animation
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// Explode animations...
animate(".xai-part-1", { x: -4, y: -2 }, { duration: 0.25, ease: "easeOut" });
animate(".xai-part-2", { x: 4, y: -2 }, { duration: 0.25, ease: "easeOut" });
animate(".xai-part-3", { x: -4, y: 2 }, { duration: 0.25, ease: "easeOut" });
animate(".xai-part-4", { x: 4, y: 2 }, { duration: 0.25, ease: "easeOut" });
timeoutRef.current = setTimeout(() => {
animate(
".xai-part-1",
{ x: 0, y: 0 },
{ duration: 0.3, ease: "easeInOut" },
);
animate(
".xai-part-2",
{ x: 0, y: 0 },
{ duration: 0.3, ease: "easeInOut" },
);
animate(
".xai-part-3",
{ x: 0, y: 0 },
{ duration: 0.3, ease: "easeInOut" },
);
animate(
".xai-part-4",
{ x: 0, y: 0 },
{ duration: 0.3, ease: "easeInOut" },
);
timeoutRef.current = null;
}, 250);
}, [animate]);
const stop = useCallback(() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
animate(".xai-part-1", { x: 0, y: 0 }, { duration: 0.2 });
animate(".xai-part-2", { x: 0, y: 0 }, { duration: 0.2 });
animate(".xai-part-3", { x: 0, y: 0 }, { duration: 0.2 });
animate(".xai-part-4", { x: 0, y: 0 }, { duration: 0.2 });
}, [animate]);
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
// ... rest of component (useImperativeHandle and SVG)
},
);
🤖 Prompt for AI Agents
In `@icons/brand-xai-icon.tsx` around lines 33 - 54, The setTimeout in start()
creates an untracked timer that can fire after unmount or after stop(); store
its ID in a scoped variable or class/Ref (e.g., mergeTimeoutId) and before
scheduling a new timeout clear any existing one to avoid stacking; call
clearTimeout(mergeTimeoutId) inside stop() and in the component
unmount/useEffect cleanup so the animate(...) callbacks for
".xai-part-1"…".xai-part-4" never run on an unmounted component.

@Abhijit-Jha Abhijit-Jha merged commit 157ccb8 into itshover:master Jan 17, 2026
2 of 3 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Mar 15, 2026
@coderabbitai coderabbitai bot mentioned this pull request Mar 27, 2026
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.

2 participants