Skip to content

feat: add AI section to homepage#2768

Open
chenparnasa wants to merge 4 commits intomainfrom
feat-ai-section
Open

feat: add AI section to homepage#2768
chenparnasa wants to merge 4 commits intomainfrom
feat-ai-section

Conversation

@chenparnasa
Copy link

@chenparnasa chenparnasa commented Feb 24, 2026

What does this PR do?

This PR adds a new AI-focused section below the "Build like a team of hundreds" section on the homepage.
The section showcases AI-related features like MCP, agent skills, agent rules, Supported IDEs.

The section is fully responsive and supports mobile behavior.

**On first commit, local build showed a 500 error on /threads/data.json, but I'm not aware of changes made to it. Tested in the dev server and preview works as expected.

Screen.Recording.2026-02-26.at.14.01.38.mov

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)

Related PRs and Issues

(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)

Have you read the Contributing Guidelines on issues?

(Write your answer here.)

Summary by CodeRabbit

  • New Features
    • Added an AI section to the marketing page with a responsive two-column layout showcasing MCP and Skills visual animations.
    • Included an interactive ecosystem strip of tool icons with accessible hover tooltips and subtle hover background effects.
    • Animations respond to hover and in-view triggers for desktop and mobile, enhancing engagement without changing data flows.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 390b22e and 2c099ba.

📒 Files selected for processing (3)
  • src/routes/(marketing)/(components)/(ai-animations)/mcp.svelte
  • src/routes/(marketing)/(components)/(ai-animations)/skills.svelte
  • src/routes/(marketing)/(components)/ai.svelte
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/routes/(marketing)/(components)/(ai-animations)/mcp.svelte
  • src/routes/(marketing)/(components)/(ai-animations)/skills.svelte

Walkthrough

Adds a new Svelte component at src/routes/(marketing)/(components)/ai.svelte and mounts it in the marketing page. The component composes two new SVG animation components (mcp.svelte and skills.svelte) that use GridPaper backdrops, requestAnimationFrame-driven easing animations, and hover/in-view triggers with different desktop/mobile behaviors. Also adds a full-width ecosystem strip rendering masked tool icons with hover tooltips and Noise hover effects. All rendering is driven by local state and static arrays; no network or data-fetching changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 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 'feat: add AI section to homepage' directly and clearly summarizes the main change—adding a new AI section to the homepage. It follows conventional commit format and accurately reflects the primary modification across all files in the changeset.
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 unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-ai-section

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
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/routes/`(marketing)/(components)/ai.svelte:
- Around line 63-85: The two Svelte each-blocks are missing stable keys; update
the first each "{`#each` categories as category, i}" to include a key such as
"(category.id ?? category.label ?? i)" and update the second each to include an
index (e.g., "{`#each` categories[active].features as feature, j}") and a key such
as "(feature.id ?? feature.title ?? j)" so Svelte can track items reliably and
satisfy ESLint.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81c6e0d and eb7f465.

📒 Files selected for processing (2)
  • src/routes/(marketing)/(components)/ai.svelte
  • src/routes/(marketing)/+page.svelte

Comment on lines 63 to 85
{#each categories as category, i}
<button
class="text-caption h-[30px] w-full rounded-full px-3 font-medium whitespace-nowrap transition-colors min-[512px]:w-auto {active ===
i
? 'bg-greyscale-800 text-primary'
: 'text-secondary hover:text-primary bg-white/2 min-[512px]:bg-transparent'}"
onclick={() => (active = i)}
>
{category.label}
</button>
{/each}
</div>
</div>

<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{#each categories[active].features as feature}
<div
class="border-smooth flex flex-col gap-1.5 rounded-2xl border bg-white/2 px-6 py-4"
>
<h3 class="font-aeonik-pro text-label text-primary">{feature.title}</h3>
<p class="text-sub-body text-secondary font-medium">{feature.description}</p>
</div>
{/each}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add keys to each-blocks to satisfy linting and stabilize updates.

ESLint reports missing keys for both loops; add stable keys to prevent warnings and potential diffing glitches.

🛠️ Proposed fix
-                {`#each` categories as category, i}
+                {`#each` categories as category, i (category.label)}
                     <button
                         class="text-caption h-[30px] w-full rounded-full px-3 font-medium whitespace-nowrap transition-colors min-[512px]:w-auto {active ===
                         i
                             ? 'bg-greyscale-800 text-primary'
                             : 'text-secondary hover:text-primary bg-white/2 min-[512px]:bg-transparent'}"
                         onclick={() => (active = i)}
                     >
                         {category.label}
                     </button>
                 {/each}
-            {`#each` categories[active].features as feature}
+            {`#each` categories[active].features as feature (feature.title)}
                 <div
                     class="border-smooth flex flex-col gap-1.5 rounded-2xl border bg-white/2 px-6 py-4"
                 >
                     <h3 class="font-aeonik-pro text-label text-primary">{feature.title}</h3>
                     <p class="text-sub-body text-secondary font-medium">{feature.description}</p>
                 </div>
             {/each}
📝 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
{#each categories as category, i}
<button
class="text-caption h-[30px] w-full rounded-full px-3 font-medium whitespace-nowrap transition-colors min-[512px]:w-auto {active ===
i
? 'bg-greyscale-800 text-primary'
: 'text-secondary hover:text-primary bg-white/2 min-[512px]:bg-transparent'}"
onclick={() => (active = i)}
>
{category.label}
</button>
{/each}
</div>
</div>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{#each categories[active].features as feature}
<div
class="border-smooth flex flex-col gap-1.5 rounded-2xl border bg-white/2 px-6 py-4"
>
<h3 class="font-aeonik-pro text-label text-primary">{feature.title}</h3>
<p class="text-sub-body text-secondary font-medium">{feature.description}</p>
</div>
{/each}
{`#each` categories as category, i (category.label)}
<button
class="text-caption h-[30px] w-full rounded-full px-3 font-medium whitespace-nowrap transition-colors min-[512px]:w-auto {active ===
i
? 'bg-greyscale-800 text-primary'
: 'text-secondary hover:text-primary bg-white/2 min-[512px]:bg-transparent'}"
onclick={() => (active = i)}
>
{category.label}
</button>
{/each}
</div>
</div>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{`#each` categories[active].features as feature (feature.title)}
<div
class="border-smooth flex flex-col gap-1.5 rounded-2xl border bg-white/2 px-6 py-4"
>
<h3 class="font-aeonik-pro text-label text-primary">{feature.title}</h3>
<p class="text-sub-body text-secondary font-medium">{feature.description}</p>
</div>
{/each}
🧰 Tools
🪛 ESLint

[error] 63-73: Each block should have a key

(svelte/require-each-key)


[error] 78-85: Each block should have a key

(svelte/require-each-key)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(marketing)/(components)/ai.svelte around lines 63 - 85, The two
Svelte each-blocks are missing stable keys; update the first each "{`#each`
categories as category, i}" to include a key such as "(category.id ??
category.label ?? i)" and update the second each to include an index (e.g.,
"{`#each` categories[active].features as feature, j}") and a key such as
"(feature.id ?? feature.title ?? j)" so Svelte can track items reliably and
satisfy ESLint.

Copy link
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

♻️ Duplicate comments (1)
src/routes/(marketing)/(components)/ai.svelte (1)

54-87: ⚠️ Potential issue | 🟠 Major

Add a stable key to the #each block.

The loop is unkeyed (Line 54), which triggers svelte/require-each-key and can cause unstable DOM updates.

🛠️ Suggested fix
-                    {`#each` tools as tool, i}
+                    {`#each` tools as tool, i (tool.name)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(marketing)/(components)/ai.svelte around lines 54 - 87, The
`#each` block iterating "tools" is unkeyed (in the {`#each` tools as tool, i} loop)
causing unstable DOM updates; update the each block to include a stable unique
key (preferably tool.id, falling back to tool.name or another immutable unique
field) so Svelte can track items reliably—locate the loop around the
Tooltip.Root/Tooltip.Trigger/Tooltip.Content components and add the key
expression to the {`#each`} statement.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/routes/`(marketing)/(components)/(ai-animations)/mcp.svelte:
- Around line 7-14: The SVG is decorative and should be hidden from assistive
tech: update the <svg> element in mcp.svelte to include accessibility attributes
such as aria-hidden="true" and focusable="false" (and remove any role or title
that would expose it) so screen readers ignore the graphic; locate the <svg> tag
in the component and add those attributes to apply AT hiding.

In `@src/routes/`(marketing)/(components)/(ai-animations)/skills.svelte:
- Around line 7-14: The SVG at the top of the component is decorative and should
be hidden from assistive tech; update the opening <svg> element (the SVG with
viewBox="0 0 541 273") to include aria-hidden="true", role="presentation" and
focusable="false" so screen readers ignore it and it is not keyboard-focusable.

In `@src/routes/`(marketing)/(components)/ai.svelte:
- Around line 60-72: Tooltip.Trigger is currently icon-only and lacks an
accessible name; add an accessible label so screen readers can identify each
control by setting an aria-label (or title) on Tooltip.Trigger using the tool's
descriptive property (e.g., tool.name or tool.label) and, for robustness,
include a visually-hidden fallback span (class="sr-only") inside the trigger
with the same text; ensure the trigger remains keyboard-focusable (add
tabindex="0" if Tooltip.Trigger doesn't render a native focusable element).
- Line 2: The module imports the Icon symbol but never uses it; remove the
unused import statement "import Icon from
'$lib/components/ui/icon/icon.svelte';" at the top of the file (or alternatively
use the Icon component where intended) so the unused-import lint error is
resolved; ensure no other references to Icon remain after removal.

---

Duplicate comments:
In `@src/routes/`(marketing)/(components)/ai.svelte:
- Around line 54-87: The `#each` block iterating "tools" is unkeyed (in the {`#each`
tools as tool, i} loop) causing unstable DOM updates; update the each block to
include a stable unique key (preferably tool.id, falling back to tool.name or
another immutable unique field) so Svelte can track items reliably—locate the
loop around the Tooltip.Root/Tooltip.Trigger/Tooltip.Content components and add
the key expression to the {`#each`} statement.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb7f465 and dcd4151.

📒 Files selected for processing (3)
  • src/routes/(marketing)/(components)/(ai-animations)/mcp.svelte
  • src/routes/(marketing)/(components)/(ai-animations)/skills.svelte
  • src/routes/(marketing)/(components)/ai.svelte

Comment on lines +7 to +14
<svg
width="100%"
height="100%"
viewBox="0 0 541 273"
fill="none"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet"
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hide decorative SVG from screen readers.

This graphic is decorative; add explicit AT hiding to reduce accessibility noise (Line 7).

♿ Suggested fix
     <svg
         width="100%"
         height="100%"
         viewBox="0 0 541 273"
         fill="none"
         xmlns="http://www.w3.org/2000/svg"
         preserveAspectRatio="xMidYMid meet"
+        aria-hidden="true"
+        focusable="false"
     >
📝 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
<svg
width="100%"
height="100%"
viewBox="0 0 541 273"
fill="none"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet"
>
<svg
width="100%"
height="100%"
viewBox="0 0 541 273"
fill="none"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet"
aria-hidden="true"
focusable="false"
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(marketing)/(components)/(ai-animations)/mcp.svelte around lines
7 - 14, The SVG is decorative and should be hidden from assistive tech: update
the <svg> element in mcp.svelte to include accessibility attributes such as
aria-hidden="true" and focusable="false" (and remove any role or title that
would expose it) so screen readers ignore the graphic; locate the <svg> tag in
the component and add those attributes to apply AT hiding.

Comment on lines +7 to +14
<svg
width="100%"
height="100%"
viewBox="0 0 541 273"
fill="none"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet"
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Mark decorative SVG as hidden from assistive tech.

This SVG appears purely decorative; without explicit hiding, screen readers may announce it unnecessarily (Line 7).

♿ Suggested fix
     <svg
         width="100%"
         height="100%"
         viewBox="0 0 541 273"
         fill="none"
         xmlns="http://www.w3.org/2000/svg"
         preserveAspectRatio="xMidYMid meet"
+        aria-hidden="true"
+        focusable="false"
     >
📝 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
<svg
width="100%"
height="100%"
viewBox="0 0 541 273"
fill="none"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet"
>
<svg
width="100%"
height="100%"
viewBox="0 0 541 273"
fill="none"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet"
aria-hidden="true"
focusable="false"
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(marketing)/(components)/(ai-animations)/skills.svelte around
lines 7 - 14, The SVG at the top of the component is decorative and should be
hidden from assistive tech; update the opening <svg> element (the SVG with
viewBox="0 0 541 273") to include aria-hidden="true", role="presentation" and
focusable="false" so screen readers ignore it and it is not keyboard-focusable.

@@ -0,0 +1,92 @@
<script lang="ts">
import Icon from '$lib/components/ui/icon/icon.svelte';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused Icon import.

Icon is imported but never used (Line 2), which currently fails lint.

🧹 Suggested fix
-    import Icon from '$lib/components/ui/icon/icon.svelte';
     import Noise from '$lib/components/fancy/noise.svelte';
📝 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
import Icon from '$lib/components/ui/icon/icon.svelte';
🧰 Tools
🪛 ESLint

[error] 2-2: 'Icon' is defined but never used.

(@typescript-eslint/no-unused-vars)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(marketing)/(components)/ai.svelte at line 2, The module imports
the Icon symbol but never uses it; remove the unused import statement "import
Icon from '$lib/components/ui/icon/icon.svelte';" at the top of the file (or
alternatively use the Icon component where intended) so the unused-import lint
error is resolved; ensure no other references to Icon remain after removal.

Comment on lines 60 to 72
<Tooltip.Trigger
class="border-smooth group relative flex h-16 w-full items-center justify-center border-r border-dashed {i === 0 ? 'border-l' : ''}"
>
<div
style="mask-image: url('{tool.src}'); -webkit-mask-image: url('{tool.src}'); mask-size: contain; mask-repeat: no-repeat; mask-position: center;"
class="h-9 w-9 bg-white/40 transition-colors duration-500 group-hover:bg-[var(--primary-color)]"
></div>
<div
class="absolute inset-0 opacity-0 transition-opacity group-hover:opacity-100 bg-gradient-to-tl from-(--primary-color)/4 to-(--secondary-color)/10"
>
<Noise opacity={0.1} />
</div>
</Tooltip.Trigger>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Provide an accessible name for icon-only tooltip triggers.

Tooltip.Trigger is visual-only here; add an accessible label so screen reader users can identify each control (Line 60).

♿ Suggested fix
                                 <Tooltip.Trigger
+                                    aria-label={tool.name}
                                     class="border-smooth group relative flex h-16 w-full items-center justify-center border-r border-dashed {i === 0 ? 'border-l' : ''}"
                                 >
📝 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
<Tooltip.Trigger
class="border-smooth group relative flex h-16 w-full items-center justify-center border-r border-dashed {i === 0 ? 'border-l' : ''}"
>
<div
style="mask-image: url('{tool.src}'); -webkit-mask-image: url('{tool.src}'); mask-size: contain; mask-repeat: no-repeat; mask-position: center;"
class="h-9 w-9 bg-white/40 transition-colors duration-500 group-hover:bg-[var(--primary-color)]"
></div>
<div
class="absolute inset-0 opacity-0 transition-opacity group-hover:opacity-100 bg-gradient-to-tl from-(--primary-color)/4 to-(--secondary-color)/10"
>
<Noise opacity={0.1} />
</div>
</Tooltip.Trigger>
<Tooltip.Trigger
aria-label={tool.name}
class="border-smooth group relative flex h-16 w-full items-center justify-center border-r border-dashed {i === 0 ? 'border-l' : ''}"
>
<div
style="mask-image: url('{tool.src}'); -webkit-mask-image: url('{tool.src}'); mask-size: contain; mask-repeat: no-repeat; mask-position: center;"
class="h-9 w-9 bg-white/40 transition-colors duration-500 group-hover:bg-[var(--primary-color)]"
></div>
<div
class="absolute inset-0 opacity-0 transition-opacity group-hover:opacity-100 bg-gradient-to-tl from-(--primary-color)/4 to-(--secondary-color)/10"
>
<Noise opacity={0.1} />
</div>
</Tooltip.Trigger>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(marketing)/(components)/ai.svelte around lines 60 - 72,
Tooltip.Trigger is currently icon-only and lacks an accessible name; add an
accessible label so screen readers can identify each control by setting an
aria-label (or title) on Tooltip.Trigger using the tool's descriptive property
(e.g., tool.name or tool.label) and, for robustness, include a visually-hidden
fallback span (class="sr-only") inside the trigger with the same text; ensure
the trigger remains keyboard-focusable (add tabindex="0" if Tooltip.Trigger
doesn't render a native focusable element).

Copy link
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: 2

🧹 Nitpick comments (1)
src/routes/(marketing)/(components)/(ai-animations)/skills.svelte (1)

22-24: Consider extracting shared animation utilities.

The easeOut function is identical in both mcp.svelte and skills.svelte. Consider extracting it to a shared utility module if more animation components are planned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(marketing)/(components)/(ai-animations)/skills.svelte around
lines 22 - 24, The easeOut function is duplicated between the skills.svelte and
mcp.svelte components; extract this shared animation utility into a single
module (e.g., an animations or easing utility file) and import it in both
components. Replace the local function declarations of easeOut in skills.svelte
and mcp.svelte with an import of the shared function, ensure the exported
function signature matches (easeOut(t: number)), and run the build/tests to
confirm no behavior changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/routes/`(marketing)/(components)/(ai-animations)/mcp.svelte:
- Around line 41-57: The $effect currently registers hover(container, ...) and
inView(container, ...) and starts an animation via slideTo/tick using rafId but
does not return a cleanup; update the $effect to return a single cleanup
function that (1) calls the unsubscribe/cleanup functions returned by hover(...)
and inView(...), (2) cancels any pending frame with cancelAnimationFrame(rafId),
and (3) unsubscribes any motion/spring subscriptions that slideTo/tick may have
touched so tick cannot access DOM nodes (leftBlock, rightCluster) after unmount;
locate the setup in the $effect and add this combined teardown to avoid orphaned
raf and subscriptions.

In `@src/routes/`(marketing)/(components)/(ai-animations)/skills.svelte:
- Around line 47-63: The effect registered with $effect must return a teardown
that unsubscribes hover and inView and cancels any in-flight animation frames so
RAF callbacks don't touch an unmounted element; modify the block around $effect
to capture the cleanup handles returned by hover(container, ...) and
inView(container, ...), and capture the cancel function returned by
animateTo(DOCK_X, DOCK_Y) (and animateTo(0,0)) so you can call them on cleanup;
ensure animateTo is updated (if not already) to return a cancel function for its
RAF loop or guard its RAF callback with an alive flag, then return a single
cleanup function from $effect that calls the hover/inView unsubscribers and
cancels any pending animations (so references like container/piece are never
accessed after unmount).

---

Nitpick comments:
In `@src/routes/`(marketing)/(components)/(ai-animations)/skills.svelte:
- Around line 22-24: The easeOut function is duplicated between the
skills.svelte and mcp.svelte components; extract this shared animation utility
into a single module (e.g., an animations or easing utility file) and import it
in both components. Replace the local function declarations of easeOut in
skills.svelte and mcp.svelte with an import of the shared function, ensure the
exported function signature matches (easeOut(t: number)), and run the
build/tests to confirm no behavior changes.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb7f465 and 390b22e.

📒 Files selected for processing (3)
  • src/routes/(marketing)/(components)/(ai-animations)/mcp.svelte
  • src/routes/(marketing)/(components)/(ai-animations)/skills.svelte
  • src/routes/(marketing)/(components)/ai.svelte

Comment on lines +41 to +57
$effect(() => {
hover(container, () => {
if (isMobile()) return;
slideTo(1);
return () => slideTo(0);
});

inView(
container,
() => {
if (!isMobile()) return;
slideTo(1);
return () => slideTo(0);
},
{ amount: 'all' }
);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing cleanup for animation frame and motion subscriptions on unmount.

The $effect sets up hover and inView observers but doesn't return a cleanup function. If the component unmounts mid-animation, rafId won't be cancelled, and the tick function may attempt to access unmounted DOM elements (leftBlock, rightCluster), causing errors.

🛠️ Proposed fix to add cleanup
     $effect(() => {
-        hover(container, () => {
+        const stopHover = hover(container, () => {
             if (isMobile()) return;
             slideTo(1);
             return () => slideTo(0);
         });

-        inView(
+        const stopInView = inView(
             container,
             () => {
                 if (!isMobile()) return;
                 slideTo(1);
                 return () => slideTo(0);
             },
             { amount: 'all' }
         );
+
+        return () => {
+            stopHover();
+            stopInView();
+            cancelAnimationFrame(rafId);
+        };
     });
📝 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
$effect(() => {
hover(container, () => {
if (isMobile()) return;
slideTo(1);
return () => slideTo(0);
});
inView(
container,
() => {
if (!isMobile()) return;
slideTo(1);
return () => slideTo(0);
},
{ amount: 'all' }
);
});
$effect(() => {
const stopHover = hover(container, () => {
if (isMobile()) return;
slideTo(1);
return () => slideTo(0);
});
const stopInView = inView(
container,
() => {
if (!isMobile()) return;
slideTo(1);
return () => slideTo(0);
},
{ amount: 'all' }
);
return () => {
stopHover();
stopInView();
cancelAnimationFrame(rafId);
};
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(marketing)/(components)/(ai-animations)/mcp.svelte around lines
41 - 57, The $effect currently registers hover(container, ...) and
inView(container, ...) and starts an animation via slideTo/tick using rafId but
does not return a cleanup; update the $effect to return a single cleanup
function that (1) calls the unsubscribe/cleanup functions returned by hover(...)
and inView(...), (2) cancels any pending frame with cancelAnimationFrame(rafId),
and (3) unsubscribes any motion/spring subscriptions that slideTo/tick may have
touched so tick cannot access DOM nodes (leftBlock, rightCluster) after unmount;
locate the setup in the $effect and add this combined teardown to avoid orphaned
raf and subscriptions.

Comment on lines +47 to +63
$effect(() => {
hover(container, () => {
if (isMobile()) return;
animateTo(DOCK_X, DOCK_Y);
return () => animateTo(0, 0);
});

inView(
container,
() => {
if (!isMobile()) return;
animateTo(DOCK_X, DOCK_Y);
return () => animateTo(0, 0);
},
{ amount: 'all' }
);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing cleanup for animation frame and motion subscriptions on unmount.

Same issue as in mcp.svelte: the $effect doesn't return a cleanup function. If the component unmounts mid-animation, the RAF callback may try to access the unmounted piece element.

🛠️ Proposed fix to add cleanup
     $effect(() => {
-        hover(container, () => {
+        const stopHover = hover(container, () => {
             if (isMobile()) return;
             animateTo(DOCK_X, DOCK_Y);
             return () => animateTo(0, 0);
         });

-        inView(
+        const stopInView = inView(
             container,
             () => {
                 if (!isMobile()) return;
                 animateTo(DOCK_X, DOCK_Y);
                 return () => animateTo(0, 0);
             },
             { amount: 'all' }
         );
+
+        return () => {
+            stopHover();
+            stopInView();
+            cancelAnimationFrame(rafId);
+        };
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(marketing)/(components)/(ai-animations)/skills.svelte around
lines 47 - 63, The effect registered with $effect must return a teardown that
unsubscribes hover and inView and cancels any in-flight animation frames so RAF
callbacks don't touch an unmounted element; modify the block around $effect to
capture the cleanup handles returned by hover(container, ...) and
inView(container, ...), and capture the cancel function returned by
animateTo(DOCK_X, DOCK_Y) (and animateTo(0,0)) so you can call them on cleanup;
ensure animateTo is updated (if not already) to return a cancel function for its
RAF loop or guard its RAF callback with an alive flag, then return a single
cleanup function from $effect that calls the hover/inView unsubscribers and
cancels any pending animations (so references like container/piece are never
accessed after unmount).

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