fix(story): restore single-card AnimatedFeatureGrid story#463
Conversation
- Create production-ready hero section with glassmorphic floating cards - Implement smooth entrance, floating, and parallax animations - Add mouse-tracking parallax effect for depth illusion - Support dark mode with gradient accents and glassmorphism - Include accessibility features (prefers-reduced-motion support) - Add responsive design for mobile, tablet, and desktop - Create comprehensive Storybook stories with variants - Add detailed MDX documentation with examples and API reference - Implement customizable props for title, subtitle, CTAs, and theme - Optimize for production with GPU-accelerated animations Features: - 5 floating cards: Analytics, Notification, Code Editor, Dashboard, Stats - CTA buttons with hover glow and lift effects - Animated scroll indicator - Keyboard accessible interactions - Respects user motion preferences - Full TypeScript support
📝 WalkthroughWalkthroughThis PR introduces six new animated React components to the Animata library alongside comprehensive landing page integration, component preview infrastructure improvements, and accessibility enhancements. The changes add StateActionCard, SwipeDeck, DropdownMenu, PricingComparison, FloatingProductHero, and AnimatedFeatureGrid with full Storybook support, documentation, and theme-aware animations. ChangesNew Components, Landing Integration, and Infrastructure
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR restores and expands Storybook + docs previews for interactive components (notably AnimatedFeatureGrid), adds several new component implementations/stories, and updates the docs/landing experience to surface them.
Changes:
- Adds a reusable
components/animated-feature-gridpackage with demo + Animata wrapper + Storybook story. - Introduces new Animata components/stories (Pricing Comparison, Dropdown Menu, Swipe Deck, Floating Product Hero, State Action Card) and corresponding MDX docs pages.
- Updates MDX rendering/component-preview and the homepage to support richer embedded previews and a “Featured” section in docs nav.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| content/docs/section/pricing-comparison.mdx | Adds docs page for Pricing Comparison section preview + install steps. |
| content/docs/section/animated-feature-grid.mdx | Adds docs page for Animated Feature Grid section preview + usage/props. |
| content/docs/overlay/dropdown-menu.mdx | Adds docs page and inline preview usage for Dropdown Menu. |
| content/docs/hero/floating-product-hero.mdx | Adds docs page for Floating Product Hero preview + props/examples. |
| content/docs/carousel/swipe-deck.mdx | Adds docs page for Swipe Deck preview + install steps. |
| content/docs/card/state-action-card.mdx | Adds docs page for State Action Card preview + install steps. |
| config/docs.ts | Adds “Featured” sidebar section linking Animated Feature Grid docs. |
| components/mdx-components.tsx | Extends MDX component mapping + refactors codeblock post-processing. |
| components/component-preview.tsx | Refactors props editor controls + adjusts preview container sizing/loading UI. |
| components/animated-feature-grid/README.md | Documents the new reusable Animated Feature Grid package. |
| components/animated-feature-grid/index.ts | Barrel exports for the Animated Feature Grid package. |
| components/animated-feature-grid/feature-card.tsx | Implements FeatureCard (spotlight/hover/fallback + metric variant). |
| components/animated-feature-grid/demo.tsx | Single-card demo used for publishable preview. |
| components/animated-feature-grid/animated-feature-grid.tsx | Implements AnimatedFeatureGrid container/layout component. |
| app/(main)/page.tsx | Adds FeaturedComponents section and embeds AnimatedFeatureGrid + Expandable previews. |
| animata/widget/shopping-list.tsx | Makes widget focusable with hover/focus elevation styling. |
| animata/widget/notes.tsx | Makes widget focusable with hover/focus elevation styling. |
| animata/section/pricing-comparison.tsx | Adds Pricing Comparison section component implementation. |
| animata/section/pricing-comparison.stories.tsx | Adds Storybook stories for Pricing Comparison (3-plan and 4-plan). |
| animata/section/animated-feature-grid.tsx | Adds Animata wrapper re-export for reusable Animated Feature Grid package. |
| animata/section/animated-feature-grid.stories.tsx | Restores single-card Animated Feature Grid story. |
| animata/overlay/dropdown-menu.tsx | Adds Dropdown Menu component implementation (keyboard nav + hover/click trigger). |
| animata/overlay/dropdown-menu.stories.tsx | Adds Storybook stories for Dropdown Menu (click + hover trigger). |
| animata/hero/product-features.tsx | Replaces Balancer usage in header copy. |
| animata/hero/floating-product-hero.tsx | Adds Floating Product Hero implementation (parallax + floating cards). |
| animata/hero/floating-product-hero.stories.tsx | Adds multiple Storybook stories/variants for Floating Product Hero. |
| animata/hero/floating-product-hero-screen.tsx | Adds alternate “screen” variant implementation of Floating Product Hero. |
| animata/feature-card/metric.stories.tsx | Adds FeatureCard metric-only Storybook story. |
| animata/carousel/swipe-deck.tsx | Adds Swipe Deck carousel implementation (snap + drag + parallax + arrows/indicators). |
| animata/carousel/swipe-deck.stories.tsx | Adds Swipe Deck Storybook story with sample items. |
| animata/card/state-action-card.tsx | Adds interactive State Action Card implementation (actions + confetti). |
| animata/card/state-action-card.stories.tsx | Adds Storybook stories for State Action Card presets. |
| animata/card/card-spread.tsx | Refactors CardSpread behavior/layout and adds keyboard interaction. |
| .github/workflows/deploy.yml | Updates action versions/env in preview deployment workflow. |
| .github/workflows/deploy-v3.yml | Updates action versions/env in v3 deployment workflow. |
Comments suppressed due to low confidence (1)
content/docs/overlay/dropdown-menu.mdx:60
- There is a stray
]at the end of the file which will cause MDX parsing/build failures. Remove the extra bracket so the document renders correctly.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (match) { | ||
| (node as UnistNode & { __event__?: string }).__event__ = match[1]; | ||
| (codeEl as UnistNode & { data?: { meta?: string } }).data!.meta = meta.replace(regex, ""); | ||
| (codeEl as UnistNode & { data?: { meta?: string } }).data!.meta = meta.replaceAll( |
| <div className="h-px w-full bg-white/8" /> | ||
| </div> | ||
|
|
||
| <div className="mt-3 text-xs font-medium text-slate-400" /> |
| <div | ||
| role="group" | ||
| aria-expanded={isExpanded} | ||
| className={cn( |
| <div | ||
| key={index} | ||
| onClick={(e) => { | ||
| setExpanded(!isExpanded); | ||
| e.preventDefault(); | ||
| }} | ||
| className={cn( | ||
| "transition duration-500 ease-in-out", | ||
| { | ||
| absolute: !isExpanded, | ||
| "origin-bottom": !isExpanded, | ||
| }, | ||
| !isExpanded && item.rotationClass, | ||
| isExpanded && item.revealClass, | ||
| )} | ||
| tabIndex={0} | ||
| onKeyDown={(e) => { | ||
| if (e.key === "Enter" || e.key === " ") { | ||
| setExpanded(!isExpanded); | ||
| e.preventDefault(); | ||
| } | ||
| }} |
| <div | ||
| role="group" | ||
| tabIndex={0} | ||
| className="h-64 w-48 rounded-3xl border bg-white p-4 font-sans shadow-lg transition-transform duration-200 ease-[cubic-bezier(0.2,0.9,0.2,1)] hover:-translate-y-1 focus:outline-none focus:ring-2 focus:ring-indigo-200" | ||
| > |
| <div className="h-64 w-48 rounded-3xl border bg-[#fced99] p-4 font-sans text-zinc-950 shadow-sm"> | ||
| <div | ||
| role="group" | ||
| tabIndex={0} |
| <Check | ||
| className="mx-auto h-4 w-4 text-emerald-600 dark:text-emerald-400" | ||
| aria-hidden="true" | ||
| /> | ||
| ) : ( | ||
| <Minus className="mx-auto h-4 w-4 text-zinc-400 dark:text-zinc-500" aria-hidden="true" /> |
| <h1 className="text-3xl font-black text-orange-600">Pots of Joy: Ceramic Chic!</h1> | ||
| <Balancer className="block text-lg text-neutral-500"> | ||
| <div className="block text-lg text-neutral-500"> | ||
| Quirky ceramics for happy spaces. From sleek vases to funky mugs, we've got your | ||
| shelves covered. | ||
| </Balancer> | ||
| </div> | ||
| </motion.header> |
There was a problem hiding this comment.
Actionable comments posted: 18
♻️ Duplicate comments (1)
.github/workflows/deploy.yml (1)
14-15:⚠️ Potential issue | 🟠 MajorSame action version concerns apply to this workflow.
This workflow has the same GitHub Actions version updates as
deploy-v3.yml. Please verify the versions exist and review their changelogs as noted in the previous file's comments.Also applies to: 18-18, 22-22, 28-28
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/deploy.yml around lines 14 - 15, This workflow references updated GitHub Action versions (same concern as deploy-v3.yml) and sets FORCE_JAVASCRIPT_ACTIONS_TO_NODE24; verify each referenced action identifier in this workflow exists at the pinned version, confirm no breaking changes by reviewing their changelogs, and update to a valid stable tag/commit if necessary; specifically check the action pins used in this file (the same ones you changed in deploy-v3.yml) and ensure the environment flag FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 remains correct for those action versions or adjust/remove it accordingly.
🧹 Nitpick comments (2)
.github/workflows/deploy-v3.yml (1)
12-13: 💤 Low valueNode version configuration is correct but may benefit from clarification.
The workflow sets
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true(line 13) while configuring Node 20 for build steps (line 20). This is actually correct because:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24controls the Node.js version used by the GitHub Actions runner itself- The
node-version: "20"controls the Node.js version used for your build commandsHowever, this distinction might not be immediately clear to maintainers. Consider adding a comment to clarify the purpose of each Node version configuration.
Also applies to: 19-20
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/deploy-v3.yml around lines 12 - 13, Add a clarifying comment explaining the two different Node settings: note that the env variable FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 controls the Node runtime used by GitHub Actions JavaScript actions on the runner, while the job step setting node-version: "20" (used in setup-node) controls the Node version available to your build commands; update the workflow near the env block and the node-version step to include these brief comments referencing FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 and node-version: "20".app/(main)/page.tsx (1)
92-96: 💤 Low valueSimplify icon rendering.
The IIFE wrapper is unnecessary. You can render the icon directly.
♻️ Simplified version
- {(() => { - const GitHubIcon = Icons.gitHub; - - return <GitHubIcon className="h-4 w-4" />; - })()} + <Icons.gitHub className="h-4 w-4" />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/`(main)/page.tsx around lines 92 - 96, The code uses an unnecessary IIFE to render the GitHub icon: locate the block that declares const GitHubIcon = Icons.gitHub inside the immediately-invoked function and replace it by rendering the icon directly (use Icons.gitHub with the same props/className instead of the wrapper and const GitHubIcon variable) so the JSX is simplified and the IIFE removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@animata/card/card-spread.tsx`:
- Around line 42-44: The element with role="group" is currently receiving
aria-expanded (using isExpanded) which is invalid; remove aria-expanded from the
role="group" wrapper in card-spread.tsx and instead apply
aria-expanded={isExpanded} to the actual interactive toggle control — i.e., the
clickable card control/button element that handles expansion (the element with
the onClick/toggle handler). Keep role="group" on the wrapper for grouping
semantics and ensure the interactive control is keyboard-focusable (button or
role="button") so aria-expanded is correctly exposed.
- Around line 57-69: Replace the interactive div with a semantic <button
type="button"> element (instead of the current div keyed by index) and add
aria-expanded={isExpanded} to it; remove manual tabIndex/onKeyDown keyboard
handling and leave click handling to the button, and change the onClick handler
to use a functional state update like setExpanded(prev => !prev) (referencing
setExpanded and isExpanded in the component) so toggling is safe and accessible.
In `@animata/card/state-action-card.tsx`:
- Around line 129-245: StateActionCard uses fixed light colors; update the JSX
className strings to include dark-mode variants so the card adapts to both
themes. Replace hardcoded light classes in the root motion.article (bg-white,
border-zinc-200, shadow, text-zinc-900), the badge/labels (bg-zinc-100,
text-zinc-700, text-zinc-500, text-zinc-600), the separator and subtle
backgrounds (bg-linear-to-r from-transparent via-zinc-200 to-transparent), and
the status pill defaults so each has a dark:... counterpart (e.g.,
dark:bg-zinc-800, dark:border-zinc-700, dark:text-zinc-100, dark:text-zinc-400,
dark:via-zinc-700). Update any inline classes used on CardIcon wrapper, meta
text, and the ActionButton container similarly; ensure confetti pieces colors
remain visible in dark mode or add alternate dark variants. Locate these edits
around the motion.article, the CardIcon span, the "Interactive Card" text,
preset.title/preset.description/preset.meta, status mapping (statuses.map), the
separator div, and the ActionButton container to apply the dark: class variants
consistently.
In `@animata/carousel/swipe-deck.tsx`:
- Around line 184-208: The card markup in SwipeDeck (the <article> with
className starting "relative w-[82%]...") uses hardcoded light-mode classes
(bg-stone-100, border-black/10, text-stone-900, text-stone-700, badge
bg-amber-300, and the dark overlay from-black/65) and needs dark-mode variants;
update the className strings and the overlay/badge classes to include Tailwind
dark: variants (e.g., dark:bg-*, dark:border-*, dark:text-*,
dark:bg-gradient-to-t/dark:from-*, dark:from-*, dark:badge-bg) so the article
element, inner overlay div (data-parallax-layer and bg-linear-to-t), heading
(item.title), description (item.description) and badge rendering (item.badge)
render appropriate colors in dark theme. Ensure you only add dark: prefixed
classes and not remove existing light classes so both themes remain supported.
In `@animata/hero/floating-product-hero-screen.tsx`:
- Around line 314-428: FloatingProductHero currently toggles container
background via the theme prop but leaves many text and UI components hardcoded
to white (e.g., the h1 and subtitle elements inside FloatingProductHero, plus
children like TopBar, LogoStrip, HeroCtaButton, PromptPanel, and PreviewCard
still assuming dark text colors), causing unreadable text in light mode; update
the component to apply theme-aware classes (conditional classNames based on the
theme prop) to the root and all internal text elements and pass the theme down
into child components (or expose a reduceMotion/theme prop) so they render
appropriate text and accent colors in both "dark" and "light" modes—specifically
modify FloatingProductHero (h1, subtitle p, motion wrappers, and where
HeroCtaButton/TopBar/LogoStrip/PromptPanel/PreviewCard are rendered) to use
conditional class strings instead of hardcoded "text-white".
In `@animata/hero/floating-product-hero.tsx`:
- Around line 311-421: FloatingProductHero currently toggles background via the
theme prop but leaves many text/UI pieces hardcoded to white (e.g., h1, p with
text-white/70, PromptPanel, LogoStrip and child components), causing invisible
text in light mode; update FloatingProductHero to pass the theme prop down to
TopBar, PromptPanel, LogoStrip, PreviewCard, HeroCtaButton and NavLinkRow and
replace hardcoded "text-white" / "text-white/70" classes in the h1, p and any
internal components with conditional classNames that switch between dark and
light styles (e.g., theme === "dark" ? "text-white" : "text-slate-950" or
appropriate muted variants), ensuring all child components consume the theme
prop and use it to choose their own text/icon/button color classes.
In `@animata/section/animated-feature-grid.stories.tsx`:
- Around line 6-15: demoItems currently contains only one FeatureGridItem so the
"Animated Feature Grid" story doesn't demonstrate a multi-card layout; add 2–4
more FeatureGridItem objects to the demoItems array to showcase grid behavior,
spacing, and interactions. For each new item, include unique properties (icon,
title, description, metric, metricCaption, tone) matching the FeatureGridItem
shape used in animated-feature-grid.stories.tsx (use the same Gauge component or
other available icon components for icon instances) so the story renders
multiple cards and visibly exercises the grid layout.
In `@animata/widget/notes.tsx`:
- Around line 3-7: The NotesCard wrapper (the div with role="group" and
className containing focus:ring-*) should not be keyboard-focusable if it has no
action: remove tabIndex={0} and any focus-only styles (e.g., the
"focus:outline-none focus:ring-2 focus:ring-indigo-200" classes) from the
container; alternatively convert the container into an interactive element
(e.g., a button/link with an onClick handler and appropriate ARIA attributes) if
you intend it to be focusable and actionable. Ensure changes reference the
NotesCard container element (the div with role="group") and update tests/styles
accordingly.
- Line 6: The focus ring color in the Notes widget className uses a hard-coded
Tailwind token (`focus:ring-indigo-200`) — update the className in
animata/widget/notes.tsx (the string assigned to className for the Notes
component) to use the repo accent theme color instead, e.g. replace
`focus:ring-indigo-200` with the theme accent token form
(`focus:ring-[hsl(var(--accent))]` or your project's equivalent token like
`focus:ring-accent`) so the focus ring follows `hsl(var(--accent))`.
In `@animata/widget/shopping-list.tsx`:
- Line 65: The focus ring uses a hardcoded Tailwind color; update the className
on the shopping list card in animata/widget/shopping-list.tsx (the JSX element
containing className="... focus:ring-indigo-200") to use the theme accent token
instead of indigo—replace the hardcoded focus:ring-indigo-200 with a focus:ring
that references the accent token (hsl(var(--accent)) or your project's
equivalent utility/class) so the focus ring uses the theme accent color
consistently.
- Around line 62-66: The div with role="group" that currently has tabIndex={0}
is a non-interactive container and should not be a keyboard focus target; remove
the tabIndex={0} from that element (and also strip the focus-related classes
like focus:outline-none and focus:ring-2 focus:ring-indigo-200) in the
ShoppingList component so it no longer creates an unnecessary tab stop, or if
you intend it to be interactive convert it into a proper control with keyboard
handlers instead.
In `@components/animated-feature-grid/animated-feature-grid.tsx`:
- Around line 21-22: The spotlightClassName prop is declared but not applied;
update the AnimatedFeatureGrid component to forward spotlightClassName into the
spotlight layer's className (the JSX element that renders the visual "spotlight"
— search for the element/variable rendering the spotlight layer in this file,
also around the 42-60 region), merging it with any existing classes (use
existing className logic or a utility like clsx/template string) so consumers
can style the spotlight; ensure the prop remains optional and does not overwrite
existing internal classes.
- Around line 98-100: The list key for the mapped items uses item.title which
can collide; update the key on the <li> in the items.map in
animated-feature-grid (where FeatureCard is rendered) to use a unique,
collision-resistant identifier (preferably item.id if available), or fall back
to a stable compound key (e.g., `${item.id ?? item.title}-${index}`) so React
reconciliation is safe; ensure the same unique property is present on the data
source or generate/stamp stable ids before mapping.
In `@components/animated-feature-grid/feature-card.tsx`:
- Around line 61-66: The violet color entries in the colors map (the "violet"
object with keys glow, border, icon, accent inside
components/animated-feature-grid/feature-card.tsx) are hardcoded; replace their
hardcoded purple values with the theme accent token using hsl(var(--accent))
(and use that token in related CSS utility strings for glow, border, icon, and
accent). Update both the violet object near the top and the duplicate violet
usage around lines 218–220 to reference hsl(var(--accent)) so all purple/violet
styling uses the theme accent variable consistently.
- Around line 225-249: The metric card currently renders an empty footer div so
item.metricCaption is never shown; update the metric-style branch in the
FeatureCard component (where item.metric is checked and the empty <div
className="mt-3 text-xs font-medium text-slate-400" /> is returned) to render
the caption text (e.g., item.metricCaption) inside that div, optionally guarding
with a conditional render (item.metricCaption && ...) to avoid rendering when
absent and preserving the existing styling class names.
- Around line 173-181: The inline style is using the shorthand background which
is overriding tone-specific styles (tone.border); change the style to set
backgroundImage (e.g., backgroundImage: "linear-gradient(...)" ) instead of
background so it doesn't clobber other background/border-related CSS from
tone.border; update the style object in the FeatureCard component (the block
that currently sets opacity: smoothOpacity and background: "...") to use
backgroundImage and keep smoothOpacity unchanged.
In `@config/docs.ts`:
- Around line 84-93: The code currently accesses sidebarNav[2] after sidebarNav
is alphabetically sorted, which will break when the "Featured" section position
changes; update the code to locate the "Featured" category by searching the
sidebarNav array (e.g., use Array.prototype.find to match item.title ===
"Featured" or another unique property) instead of using the hardcoded index, and
handle the case where the find returns undefined (fallback or no-op) so
references like the one that previously used sidebarNav[2] safely target the
correct object.
In `@content/docs/overlay/dropdown-menu.mdx`:
- Line 59: There is a stray closing bracket character (']') after the markdown
link that breaks MDX parsing; open the dropdown-menu.mdx content around the link
and remove the extra ']' so the link syntax is valid (i.e., ensure only the
intended closing parenthesis or bracket for the link remains and no standalone
']' follows the link).
---
Duplicate comments:
In @.github/workflows/deploy.yml:
- Around line 14-15: This workflow references updated GitHub Action versions
(same concern as deploy-v3.yml) and sets FORCE_JAVASCRIPT_ACTIONS_TO_NODE24;
verify each referenced action identifier in this workflow exists at the pinned
version, confirm no breaking changes by reviewing their changelogs, and update
to a valid stable tag/commit if necessary; specifically check the action pins
used in this file (the same ones you changed in deploy-v3.yml) and ensure the
environment flag FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 remains correct for those
action versions or adjust/remove it accordingly.
---
Nitpick comments:
In @.github/workflows/deploy-v3.yml:
- Around line 12-13: Add a clarifying comment explaining the two different Node
settings: note that the env variable FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 controls
the Node runtime used by GitHub Actions JavaScript actions on the runner, while
the job step setting node-version: "20" (used in setup-node) controls the Node
version available to your build commands; update the workflow near the env block
and the node-version step to include these brief comments referencing
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 and node-version: "20".
In `@app/`(main)/page.tsx:
- Around line 92-96: The code uses an unnecessary IIFE to render the GitHub
icon: locate the block that declares const GitHubIcon = Icons.gitHub inside the
immediately-invoked function and replace it by rendering the icon directly (use
Icons.gitHub with the same props/className instead of the wrapper and const
GitHubIcon variable) so the JSX is simplified and the IIFE removed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 91b535f8-5b8f-4c6b-9a12-9bdf582b02ab
📒 Files selected for processing (35)
.github/workflows/deploy-v3.yml.github/workflows/deploy.ymlanimata/card/card-spread.tsxanimata/card/state-action-card.stories.tsxanimata/card/state-action-card.tsxanimata/carousel/swipe-deck.stories.tsxanimata/carousel/swipe-deck.tsxanimata/feature-card/metric.stories.tsxanimata/hero/floating-product-hero-screen.tsxanimata/hero/floating-product-hero.stories.tsxanimata/hero/floating-product-hero.tsxanimata/hero/product-features.tsxanimata/overlay/dropdown-menu.stories.tsxanimata/overlay/dropdown-menu.tsxanimata/section/animated-feature-grid.stories.tsxanimata/section/animated-feature-grid.tsxanimata/section/pricing-comparison.stories.tsxanimata/section/pricing-comparison.tsxanimata/widget/notes.tsxanimata/widget/shopping-list.tsxapp/(main)/page.tsxcomponents/animated-feature-grid/README.mdcomponents/animated-feature-grid/animated-feature-grid.tsxcomponents/animated-feature-grid/demo.tsxcomponents/animated-feature-grid/feature-card.tsxcomponents/animated-feature-grid/index.tscomponents/component-preview.tsxcomponents/mdx-components.tsxconfig/docs.tscontent/docs/card/state-action-card.mdxcontent/docs/carousel/swipe-deck.mdxcontent/docs/hero/floating-product-hero.mdxcontent/docs/overlay/dropdown-menu.mdxcontent/docs/section/animated-feature-grid.mdxcontent/docs/section/pricing-comparison.mdx
| role="group" | ||
| aria-expanded={isExpanded} | ||
| className={cn( |
There was a problem hiding this comment.
aria-expanded is applied to the wrong element/role.
role="group" does not support aria-expanded. Move aria-expanded to the actual interactive toggle element (the clickable card control).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@animata/card/card-spread.tsx` around lines 42 - 44, The element with
role="group" is currently receiving aria-expanded (using isExpanded) which is
invalid; remove aria-expanded from the role="group" wrapper in card-spread.tsx
and instead apply aria-expanded={isExpanded} to the actual interactive toggle
control — i.e., the clickable card control/button element that handles expansion
(the element with the onClick/toggle handler). Keep role="group" on the wrapper
for grouping semantics and ensure the interactive control is keyboard-focusable
(button or role="button") so aria-expanded is correctly exposed.
| <div | ||
| key={index} | ||
| onClick={(e) => { | ||
| setExpanded(!isExpanded); | ||
| e.preventDefault(); | ||
| }} | ||
| className={cn( | ||
| "transition duration-500 ease-in-out", | ||
| { | ||
| absolute: !isExpanded, | ||
| "origin-bottom": !isExpanded, | ||
| }, | ||
| !isExpanded && item.rotationClass, | ||
| isExpanded && item.revealClass, | ||
| )} | ||
| tabIndex={0} | ||
| onKeyDown={(e) => { | ||
| if (e.key === "Enter" || e.key === " ") { | ||
| setExpanded(!isExpanded); | ||
| e.preventDefault(); | ||
| } | ||
| }} |
There was a problem hiding this comment.
Use a semantic button for card toggles and functional state updates.
Interactive behavior on a div requires manual keyboard/a11y plumbing and is currently flagged by a11y lint. Use <button type="button"> with aria-expanded, and switch toggles to functional updates.
Suggested fix
- <div
+ <button
+ type="button"
key={index}
- onClick={(e) => {
- setExpanded(!isExpanded);
- e.preventDefault();
- }}
- tabIndex={0}
- onKeyDown={(e) => {
- if (e.key === "Enter" || e.key === " ") {
- setExpanded(!isExpanded);
- e.preventDefault();
- }
- }}
+ aria-expanded={isExpanded}
+ onClick={() => setExpanded((prev) => !prev)}
className={cn("relative min-w-52 cursor-pointer", {
"origin-bottom": !isExpanded,
})}
style={{
transform: isExpanded
? `translateX(${offset}px) translateY(-22px) rotate(${rotate}deg)`
: `rotate(${rotate / 2}deg)`,
boxShadow: isExpanded
? "0 22px 56px rgba(2,6,23,0.16)"
: "0 8px 22px rgba(2,6,23,0.07)",
transition: `transform 520ms cubic-bezier(0.22,1,0.36,1) ${delay}ms, box-shadow 520ms ease ${delay}ms`,
}}
>
<item.component />
- </div>
+ </button>📝 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.
| <div | |
| key={index} | |
| onClick={(e) => { | |
| setExpanded(!isExpanded); | |
| e.preventDefault(); | |
| }} | |
| className={cn( | |
| "transition duration-500 ease-in-out", | |
| { | |
| absolute: !isExpanded, | |
| "origin-bottom": !isExpanded, | |
| }, | |
| !isExpanded && item.rotationClass, | |
| isExpanded && item.revealClass, | |
| )} | |
| tabIndex={0} | |
| onKeyDown={(e) => { | |
| if (e.key === "Enter" || e.key === " ") { | |
| setExpanded(!isExpanded); | |
| e.preventDefault(); | |
| } | |
| }} | |
| <button | |
| type="button" | |
| key={index} | |
| aria-expanded={isExpanded} | |
| onClick={() => setExpanded((prev) => !prev)} | |
| className={cn("relative min-w-52 cursor-pointer", { | |
| "origin-bottom": !isExpanded, | |
| })} | |
| style={{ | |
| transform: isExpanded | |
| ? `translateX(${offset}px) translateY(-22px) rotate(${rotate}deg)` | |
| : `rotate(${rotate / 2}deg)`, | |
| boxShadow: isExpanded | |
| ? "0 22px 56px rgba(2,6,23,0.16)" | |
| : "0 8px 22px rgba(2,6,23,0.07)", | |
| transition: `transform 520ms cubic-bezier(0.22,1,0.36,1) ${delay}ms, box-shadow 520ms ease ${delay}ms`, | |
| }} | |
| > | |
| <item.component /> | |
| </button> |
🧰 Tools
🪛 Biome (2.4.15)
[error] 63-63: The HTML element div is non-interactive. Do not use tabIndex.
(lint/a11y/noNoninteractiveTabindex)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@animata/card/card-spread.tsx` around lines 57 - 69, Replace the interactive
div with a semantic <button type="button"> element (instead of the current div
keyed by index) and add aria-expanded={isExpanded} to it; remove manual
tabIndex/onKeyDown keyboard handling and leave click handling to the button, and
change the onClick handler to use a functional state update like
setExpanded(prev => !prev) (referencing setExpanded and isExpanded in the
component) so toggling is safe and accessible.
| <motion.article | ||
| initial={{ opacity: 0, y: 16 }} | ||
| animate={{ opacity: 1, y: 0 }} | ||
| transition={{ duration: 0.35 }} | ||
| className={cn( | ||
| "group relative w-full max-w-sm overflow-hidden rounded-2xl border border-zinc-200 bg-white p-5 shadow-[0_16px_45px_-24px_rgba(0,0,0,0.45)]", | ||
| className, | ||
| )} | ||
| > | ||
| <div className="absolute inset-x-0 top-0 h-1 bg-linear-to-r from-cyan-500 via-emerald-500 to-fuchsia-500" /> | ||
|
|
||
| <div className="mb-4 flex items-start justify-between gap-3"> | ||
| <div className="flex items-center gap-3"> | ||
| <span className="rounded-xl bg-zinc-100 p-2 text-zinc-700"> | ||
| <CardIcon className="size-4" /> | ||
| </span> | ||
| <p className="text-xs font-semibold uppercase tracking-[0.2em] text-zinc-500"> | ||
| Interactive Card | ||
| </p> | ||
| </div> | ||
| <span className="inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2.5 py-1 text-xs font-medium text-emerald-700"> | ||
| <Sparkles className="size-3" /> | ||
| Live State | ||
| </span> | ||
| </div> | ||
|
|
||
| <h3 className="text-xl font-semibold text-zinc-900">{preset.title}</h3> | ||
| <p className="mt-2 text-sm leading-relaxed text-zinc-600">{preset.description}</p> | ||
|
|
||
| <div className="mt-4 flex flex-wrap gap-2"> | ||
| {statuses.map((status) => ( | ||
| <span | ||
| key={status.label} | ||
| className={cn("rounded-full px-2.5 py-1 text-xs font-medium", status.className)} | ||
| > | ||
| {status.label} | ||
| </span> | ||
| ))} | ||
| </div> | ||
|
|
||
| <div className="mt-5 flex items-center justify-between"> | ||
| <p className="text-sm font-medium text-zinc-500">{preset.meta}</p> | ||
|
|
||
| <AnimatePresence mode="wait" initial={false}> | ||
| {lastAction && ( | ||
| <motion.div | ||
| key={lastAction} | ||
| initial={{ opacity: 0, y: 8, scale: 0.95 }} | ||
| animate={{ opacity: 1, y: 0, scale: 1 }} | ||
| exit={{ opacity: 0, y: -8, scale: 0.95 }} | ||
| transition={{ duration: 0.18 }} | ||
| className="inline-flex items-center gap-1 rounded-full bg-emerald-600 px-2.5 py-1 text-xs font-semibold text-white" | ||
| > | ||
| <Check className="size-3.5" /> | ||
| Action saved | ||
| </motion.div> | ||
| )} | ||
| </AnimatePresence> | ||
| </div> | ||
|
|
||
| <div className="pointer-events-none mt-4 h-0.5 bg-linear-to-r from-transparent via-zinc-200 to-transparent" /> | ||
|
|
||
| <div | ||
| className={cn( | ||
| "mt-4 flex items-center gap-2 transition-all duration-300", | ||
| "opacity-100 translate-y-0 sm:translate-y-3 sm:opacity-0 sm:group-hover:translate-y-0 sm:group-hover:opacity-100 sm:group-focus-within:translate-y-0 sm:group-focus-within:opacity-100", | ||
| )} | ||
| > | ||
| <ActionButton | ||
| icon={Heart} | ||
| onClick={onFavorite} | ||
| label={isFavorite ? "Favorited" : "Add to favorites"} | ||
| active={isFavorite} | ||
| /> | ||
|
|
||
| <ActionButton | ||
| icon={CheckCircle2} | ||
| onClick={onComplete} | ||
| label={isCompleted ? "Completed" : "Mark complete"} | ||
| active={isCompleted} | ||
| /> | ||
|
|
||
| <ActionButton | ||
| icon={Share2} | ||
| onClick={onShare} | ||
| label={isShared ? "Shared" : "Share"} | ||
| active={isShared} | ||
| /> | ||
| </div> | ||
|
|
||
| <AnimatePresence> | ||
| {showConfetti && ( | ||
| <motion.div | ||
| className="pointer-events-none absolute left-1/2 top-[52%]" | ||
| initial={{ opacity: 0 }} | ||
| animate={{ opacity: 1 }} | ||
| exit={{ opacity: 0 }} | ||
| > | ||
| {confettiPieces.map((piece) => ( | ||
| <motion.span | ||
| key={piece.id} | ||
| className={cn("absolute h-2 w-1.5 rounded-sm", piece.color)} | ||
| initial={{ x: 0, y: 0, rotate: 0, opacity: 1 }} | ||
| animate={{ | ||
| x: piece.x, | ||
| y: piece.y, | ||
| rotate: piece.rotate, | ||
| opacity: 0, | ||
| }} | ||
| transition={{ duration: 0.75, ease: "easeOut" }} | ||
| /> | ||
| ))} | ||
| </motion.div> | ||
| )} | ||
| </AnimatePresence> | ||
| </motion.article> | ||
| ); |
There was a problem hiding this comment.
Missing dark mode support for card component.
The StateActionCard uses fixed light theme colors (white, zinc-200, zinc-900, etc.) without dark mode variants. The component will not adapt properly to dark themes.
As per coding guidelines, all new components must be theme-responsive (light + dark mode support).
💡 Example dark mode fixes
<motion.article
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35 }}
className={cn(
- "group relative w-full max-w-sm overflow-hidden rounded-2xl border border-zinc-200 bg-white p-5 shadow-[0_16px_45px_-24px_rgba(0,0,0,0.45)]",
+ "group relative w-full max-w-sm overflow-hidden rounded-2xl border border-zinc-200 bg-white p-5 shadow-[0_16px_45px_-24px_rgba(0,0,0,0.45)] dark:border-zinc-700 dark:bg-zinc-900",
className,
)}
>
{/* ... */}
- <h3 className="text-xl font-semibold text-zinc-900">{preset.title}</h3>
+ <h3 className="text-xl font-semibold text-zinc-900 dark:text-zinc-100">{preset.title}</h3>
- <p className="mt-2 text-sm leading-relaxed text-zinc-600">{preset.description}</p>
+ <p className="mt-2 text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">{preset.description}</p>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@animata/card/state-action-card.tsx` around lines 129 - 245, StateActionCard
uses fixed light colors; update the JSX className strings to include dark-mode
variants so the card adapts to both themes. Replace hardcoded light classes in
the root motion.article (bg-white, border-zinc-200, shadow, text-zinc-900), the
badge/labels (bg-zinc-100, text-zinc-700, text-zinc-500, text-zinc-600), the
separator and subtle backgrounds (bg-linear-to-r from-transparent via-zinc-200
to-transparent), and the status pill defaults so each has a dark:... counterpart
(e.g., dark:bg-zinc-800, dark:border-zinc-700, dark:text-zinc-100,
dark:text-zinc-400, dark:via-zinc-700). Update any inline classes used on
CardIcon wrapper, meta text, and the ActionButton container similarly; ensure
confetti pieces colors remain visible in dark mode or add alternate dark
variants. Locate these edits around the motion.article, the CardIcon span, the
"Interactive Card" text, preset.title/preset.description/preset.meta, status
mapping (statuses.map), the separator div, and the ActionButton container to
apply the dark: class variants consistently.
| className="relative w-[82%] shrink-0 snap-center overflow-hidden rounded-2xl border border-black/10 bg-stone-100 shadow-[0_12px_30px_-16px_rgba(0,0,0,0.45)] sm:w-[72%] lg:w-[64%]" | ||
| > | ||
| <div className="relative h-56 overflow-hidden sm:h-64"> | ||
| <div | ||
| data-parallax-layer | ||
| className="absolute inset-0 bg-cover bg-center transition-transform duration-200 ease-out will-change-transform" | ||
| style={{ backgroundImage: `url(${item.image})` }} | ||
| /> | ||
| <div className="absolute inset-0 bg-linear-to-t from-black/65 via-black/15 to-transparent" /> | ||
| {item.badge && ( | ||
| <span className="absolute left-4 top-4 rounded-full bg-amber-300 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-amber-950"> | ||
| {item.badge} | ||
| </span> | ||
| )} | ||
| </div> | ||
|
|
||
| <div className="space-y-2 p-5 sm:p-6"> | ||
| <h3 className="text-balance text-xl font-semibold leading-tight text-stone-900 sm:text-2xl"> | ||
| {item.title} | ||
| </h3> | ||
| <p className="text-sm leading-relaxed text-stone-700 sm:text-base"> | ||
| {item.description} | ||
| </p> | ||
| </div> | ||
| </article> |
There was a problem hiding this comment.
Missing dark mode support for card styling.
The card article uses fixed stone and black color values without dark mode variants. The component does not adapt to dark themes.
As per coding guidelines, all new components must be theme-responsive (light + dark mode support).
💡 Example dark mode implementation
<article
key={item.id}
ref={(node) => {
cardRefs.current[index] = node;
}}
- className="relative w-[82%] shrink-0 snap-center overflow-hidden rounded-2xl border border-black/10 bg-stone-100 shadow-[0_12px_30px_-16px_rgba(0,0,0,0.45)] sm:w-[72%] lg:w-[64%]"
+ className="relative w-[82%] shrink-0 snap-center overflow-hidden rounded-2xl border border-black/10 bg-stone-100 shadow-[0_12px_30px_-16px_rgba(0,0,0,0.45)] dark:border-white/10 dark:bg-stone-800 sm:w-[72%] lg:w-[64%]"
>
<div className="relative h-56 overflow-hidden sm:h-64">
{/* ... */}
</div>
<div className="space-y-2 p-5 sm:p-6">
- <h3 className="text-balance text-xl font-semibold leading-tight text-stone-900 sm:text-2xl">
+ <h3 className="text-balance text-xl font-semibold leading-tight text-stone-900 dark:text-stone-100 sm:text-2xl">
{item.title}
</h3>
- <p className="text-sm leading-relaxed text-stone-700 sm:text-base">
+ <p className="text-sm leading-relaxed text-stone-700 dark:text-stone-300 sm:text-base">
{item.description}
</p>
</div>
</article>📝 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.
| className="relative w-[82%] shrink-0 snap-center overflow-hidden rounded-2xl border border-black/10 bg-stone-100 shadow-[0_12px_30px_-16px_rgba(0,0,0,0.45)] sm:w-[72%] lg:w-[64%]" | |
| > | |
| <div className="relative h-56 overflow-hidden sm:h-64"> | |
| <div | |
| data-parallax-layer | |
| className="absolute inset-0 bg-cover bg-center transition-transform duration-200 ease-out will-change-transform" | |
| style={{ backgroundImage: `url(${item.image})` }} | |
| /> | |
| <div className="absolute inset-0 bg-linear-to-t from-black/65 via-black/15 to-transparent" /> | |
| {item.badge && ( | |
| <span className="absolute left-4 top-4 rounded-full bg-amber-300 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-amber-950"> | |
| {item.badge} | |
| </span> | |
| )} | |
| </div> | |
| <div className="space-y-2 p-5 sm:p-6"> | |
| <h3 className="text-balance text-xl font-semibold leading-tight text-stone-900 sm:text-2xl"> | |
| {item.title} | |
| </h3> | |
| <p className="text-sm leading-relaxed text-stone-700 sm:text-base"> | |
| {item.description} | |
| </p> | |
| </div> | |
| </article> | |
| className="relative w-[82%] shrink-0 snap-center overflow-hidden rounded-2xl border border-black/10 bg-stone-100 shadow-[0_12px_30px_-16px_rgba(0,0,0,0.45)] dark:border-white/10 dark:bg-stone-800 sm:w-[72%] lg:w-[64%]" | |
| > | |
| <div className="relative h-56 overflow-hidden sm:h-64"> | |
| <div | |
| data-parallax-layer | |
| className="absolute inset-0 bg-cover bg-center transition-transform duration-200 ease-out will-change-transform" | |
| style={{ backgroundImage: `url(${item.image})` }} | |
| /> | |
| <div className="absolute inset-0 bg-linear-to-t from-black/65 via-black/15 to-transparent" /> | |
| {item.badge && ( | |
| <span className="absolute left-4 top-4 rounded-full bg-amber-300 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-amber-950"> | |
| {item.badge} | |
| </span> | |
| )} | |
| </div> | |
| <div className="space-y-2 p-5 sm:p-6"> | |
| <h3 className="text-balance text-xl font-semibold leading-tight text-stone-900 dark:text-stone-100 sm:text-2xl"> | |
| {item.title} | |
| </h3> | |
| <p className="text-sm leading-relaxed text-stone-700 dark:text-stone-300 sm:text-base"> | |
| {item.description} | |
| </p> | |
| </div> | |
| </article> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@animata/carousel/swipe-deck.tsx` around lines 184 - 208, The card markup in
SwipeDeck (the <article> with className starting "relative w-[82%]...") uses
hardcoded light-mode classes (bg-stone-100, border-black/10, text-stone-900,
text-stone-700, badge bg-amber-300, and the dark overlay from-black/65) and
needs dark-mode variants; update the className strings and the overlay/badge
classes to include Tailwind dark: variants (e.g., dark:bg-*, dark:border-*,
dark:text-*, dark:bg-gradient-to-t/dark:from-*, dark:from-*, dark:badge-bg) so
the article element, inner overlay div (data-parallax-layer and bg-linear-to-t),
heading (item.title), description (item.description) and badge rendering
(item.badge) render appropriate colors in dark theme. Ensure you only add dark:
prefixed classes and not remove existing light classes so both themes remain
supported.
| export function FloatingProductHero({ | ||
| title = "Turn your ideas into apps", | ||
| subtitle = "What will you create? The possibilities are endless.", | ||
| primaryCtaText = "Start building with AI", | ||
| secondaryCtaText = "Write a prompt", | ||
| onPrimaryCta, | ||
| onSecondaryCta, | ||
| theme = "dark", | ||
| animationEnabled = true, | ||
| className, | ||
| minHeight = "100vh", | ||
| navLinks = defaultNavLinks, | ||
| logos = defaultLogos, | ||
| cards = defaultCards, | ||
| showAllCards = true, | ||
| }: Readonly<FloatingProductHeroProps>) { | ||
| const reduceMotion = useReducedMotion(); | ||
| const shouldAnimate = animationEnabled && !reduceMotion; | ||
| const mouseX = useMotionValue(0); | ||
| const mouseY = useMotionValue(0); | ||
| const springX = useSpring(mouseX, { stiffness: 90, damping: 18, mass: 0.15 }); | ||
| const springY = useSpring(mouseY, { stiffness: 90, damping: 18, mass: 0.15 }); | ||
|
|
||
| const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => { | ||
| if (!shouldAnimate) return; | ||
| const bounds = event.currentTarget.getBoundingClientRect(); | ||
| const x = ((event.clientX - bounds.left) / bounds.width - 0.5) * 20; | ||
| const y = ((event.clientY - bounds.top) / bounds.height - 0.5) * 12; | ||
| mouseX.set(x); | ||
| mouseY.set(y); | ||
| }; | ||
|
|
||
| const resetPointer = () => { | ||
| mouseX.set(0); | ||
| mouseY.set(0); | ||
| }; | ||
|
|
||
| return ( | ||
| <section | ||
| aria-label="Floating product hero" | ||
| className={cn( | ||
| "relative w-full overflow-hidden", | ||
| theme === "dark" ? "bg-[#0b1117] text-white" : "bg-white text-slate-950", | ||
| className, | ||
| )} | ||
| style={{ minHeight }} | ||
| > | ||
| <div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_18%,rgba(55,65,81,0.32),transparent_30%),radial-gradient(circle_at_50%_34%,rgba(15,23,42,0.76),transparent_52%),linear-gradient(to_bottom,#0b1117,#0b1117)]" /> | ||
| <div className="absolute inset-0 opacity-[0.08] [background-image:linear-gradient(rgba(255,255,255,0.22)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.22)_1px,transparent_1px)] [background-size:72px_72px]" /> | ||
|
|
||
| <div className="relative mx-auto flex w-full max-w-[1240px] flex-col px-4 py-4 sm:px-6 lg:px-8"> | ||
| <TopBar navLinks={navLinks} reduceMotion={!!reduceMotion} onPrimaryCta={onPrimaryCta} /> | ||
|
|
||
| <div className="relative flex min-h-[calc(100vh-80px)] flex-col items-center justify-center pt-10"> | ||
| <motion.div | ||
| initial={shouldAnimate ? { opacity: 0, y: 12 } : false} | ||
| animate={shouldAnimate ? { opacity: 1, y: 0 } : undefined} | ||
| transition={{ duration: 0.7, ease: "easeOut" }} | ||
| className="mx-auto max-w-3xl text-center" | ||
| > | ||
| <h1 className="text-balance text-[clamp(2.7rem,6vw,5.2rem)] font-semibold tracking-[-0.06em] text-white"> | ||
| {title} | ||
| </h1> | ||
| <p className="mx-auto mt-5 max-w-2xl text-pretty text-lg leading-8 text-white/65 sm:text-xl"> | ||
| {subtitle} | ||
| </p> | ||
| </motion.div> | ||
|
|
||
| <div | ||
| onPointerMove={handlePointerMove} | ||
| onPointerLeave={resetPointer} | ||
| className="relative mt-10 w-full px-4 sm:px-6 lg:px-0" | ||
| > | ||
| <motion.div | ||
| style={shouldAnimate ? { x: springX, y: springY } : undefined} | ||
| className="relative mx-auto w-full max-w-[780px]" | ||
| > | ||
| <PromptPanel reduceMotion={!!reduceMotion} animationEnabled={animationEnabled} /> | ||
|
|
||
| {showAllCards ? ( | ||
| <div className="pointer-events-none absolute inset-0 hidden md:block"> | ||
| {cards.map((card, index) => ( | ||
| <PreviewCard | ||
| key={card.title} | ||
| card={card} | ||
| index={index} | ||
| reduceMotion={!!reduceMotion} | ||
| animationEnabled={animationEnabled} | ||
| /> | ||
| ))} | ||
| </div> | ||
| ) : null} | ||
| </motion.div> | ||
| </div> | ||
|
|
||
| <LogoStrip logos={logos} /> | ||
|
|
||
| <motion.div | ||
| initial={shouldAnimate ? { opacity: 0, y: 10 } : false} | ||
| animate={shouldAnimate ? { opacity: 1, y: 0 } : undefined} | ||
| transition={{ delay: 0.25, duration: 0.6 }} | ||
| className="mt-8 flex flex-col items-center gap-3 sm:flex-row" | ||
| > | ||
| <HeroCtaButton variant="primary" reduceMotion={!!reduceMotion} onClick={onPrimaryCta}> | ||
| {primaryCtaText} | ||
| </HeroCtaButton> | ||
| <HeroCtaButton variant="secondary" reduceMotion={!!reduceMotion} onClick={onSecondaryCta}> | ||
| {secondaryCtaText} | ||
| </HeroCtaButton> | ||
| </motion.div> | ||
| </div> | ||
| </div> | ||
| </section> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Incomplete theme implementation (duplicate issue).
This file has the same theme implementation issue as floating-product-hero.tsx: the component accepts a theme prop and switches background colors (line 356), but most text elements remain white-colored. In light mode, white text (lines 374, 377) on a white background (line 356) would be invisible.
💡 Apply theme-aware colors
<h1 className="text-balance text-[clamp(2.7rem,6vw,5.2rem)] font-semibold tracking-[-0.06em] text-white">
+ <h1 className={cn(
+ "text-balance text-[clamp(2.7rem,6vw,5.2rem)] font-semibold tracking-[-0.06em]",
+ theme === "dark" ? "text-white" : "text-slate-950"
+ )}>
{title}
</h1>
- <p className="mx-auto mt-5 max-w-2xl text-pretty text-lg leading-8 text-white/65 sm:text-xl">
+ <p className={cn(
+ "mx-auto mt-5 max-w-2xl text-pretty text-lg leading-8 sm:text-xl",
+ theme === "dark" ? "text-white/65" : "text-slate-600"
+ )}>
{subtitle}
</p>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@animata/hero/floating-product-hero-screen.tsx` around lines 314 - 428,
FloatingProductHero currently toggles container background via the theme prop
but leaves many text and UI components hardcoded to white (e.g., the h1 and
subtitle elements inside FloatingProductHero, plus children like TopBar,
LogoStrip, HeroCtaButton, PromptPanel, and PreviewCard still assuming dark text
colors), causing unreadable text in light mode; update the component to apply
theme-aware classes (conditional classNames based on the theme prop) to the root
and all internal text elements and pass the theme down into child components (or
expose a reduceMotion/theme prop) so they render appropriate text and accent
colors in both "dark" and "light" modes—specifically modify FloatingProductHero
(h1, subtitle p, motion wrappers, and where
HeroCtaButton/TopBar/LogoStrip/PromptPanel/PreviewCard are rendered) to use
conditional class strings instead of hardcoded "text-white".
| violet: { | ||
| glow: "rgba(167, 139, 250, 0.52)", | ||
| border: "from-violet-400/0 via-violet-300/70 to-fuchsia-400/0", | ||
| icon: "bg-violet-500/15 text-violet-200 ring-violet-400/15", | ||
| accent: "from-violet-400/12 via-transparent to-transparent", | ||
| }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Replace hardcoded violet values with the accent token.
Violet styling is hardcoded instead of using the theme accent variable.
Proposed fix
violet: {
- glow: "rgba(167, 139, 250, 0.52)",
- border: "from-violet-400/0 via-violet-300/70 to-fuchsia-400/0",
- icon: "bg-violet-500/15 text-violet-200 ring-violet-400/15",
- accent: "from-violet-400/12 via-transparent to-transparent",
+ glow: "hsl(var(--accent) / 0.52)",
+ border: "from-[hsl(var(--accent)/0)] via-[hsl(var(--accent)/0.7)] to-[hsl(var(--accent)/0)]",
+ icon: "bg-[hsl(var(--accent)/0.15)] text-[hsl(var(--accent))] ring-[hsl(var(--accent)/0.15)]",
+ accent: "from-[hsl(var(--accent)/0.12)] via-transparent to-transparent",
},As per coding guidelines, use theme accent color hsl(var(--accent)) for purple/violet elements.
Also applies to: 218-220
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/animated-feature-grid/feature-card.tsx` around lines 61 - 66, The
violet color entries in the colors map (the "violet" object with keys glow,
border, icon, accent inside components/animated-feature-grid/feature-card.tsx)
are hardcoded; replace their hardcoded purple values with the theme accent token
using hsl(var(--accent)) (and use that token in related CSS utility strings for
glow, border, icon, and accent). Update both the violet object near the top and
the duplicate violet usage around lines 218–220 to reference hsl(var(--accent))
so all purple/violet styling uses the theme accent variable consistently.
| "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-500 group-hover:opacity-100 group-focus-visible:opacity-100", | ||
| "bg-linear-to-r", | ||
| tone.border, | ||
| )} | ||
| style={{ | ||
| opacity: smoothOpacity, | ||
| background: | ||
| "linear-gradient(135deg, rgba(255,255,255,0.02) 0%, rgba(255,255,255,0.12) 18%, rgba(255,255,255,0.02) 38%, rgba(255,255,255,0.18) 50%, rgba(255,255,255,0.02) 70%, rgba(255,255,255,0.08) 100%)", | ||
| }} |
There was a problem hiding this comment.
Preserve tone-specific border styling.
Line 180 sets inline background, which overrides tone.border classes and makes tones look the same.
Proposed fix
style={{
opacity: smoothOpacity,
- background:
- "linear-gradient(135deg, rgba(255,255,255,0.02) 0%, rgba(255,255,255,0.12) 18%, rgba(255,255,255,0.02) 38%, rgba(255,255,255,0.18) 50%, rgba(255,255,255,0.02) 70%, rgba(255,255,255,0.08) 100%)",
}}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/animated-feature-grid/feature-card.tsx` around lines 173 - 181,
The inline style is using the shorthand background which is overriding
tone-specific styles (tone.border); change the style to set backgroundImage
(e.g., backgroundImage: "linear-gradient(...)" ) instead of background so it
doesn't clobber other background/border-related CSS from tone.border; update the
style object in the FeatureCard component (the block that currently sets
opacity: smoothOpacity and background: "...") to use backgroundImage and keep
smoothOpacity unchanged.
| {item.metric ? ( | ||
| // Metric-style compact card (styled to match the screenshot) | ||
| <div className="mx-auto flex h-full w-full max-w-[252px] flex-col justify-between"> | ||
| <div className="flex h-full min-h-[224px] flex-col justify-between rounded-[28px] bg-slate-900/95 p-5 ring-1 ring-black/30 shadow-[0_8px_22px_rgba(2,6,23,0.35)]"> | ||
| <div className="flex items-center justify-between"> | ||
| <div className={cn("inline-flex h-11 w-11 items-center justify-center rounded-2xl border border-white/10 bg-white/5 ring-1 ring-white/10", tone.icon)}> | ||
| {item.icon} | ||
| </div> | ||
|
|
||
| <div className="inline-flex items-center rounded-full border border-white/10 bg-white/5 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.24em] text-white/45"> | ||
| Insight | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="flex flex-1 items-center justify-center"> | ||
| <div className="text-[3.45rem] font-extrabold leading-none tracking-[-0.08em] text-white sm:text-[3.75rem]"> | ||
| {item.metric} | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="h-px w-full bg-white/8" /> | ||
| </div> | ||
|
|
||
| <div className="mt-3 text-xs font-medium text-slate-400" /> | ||
| </div> |
There was a problem hiding this comment.
Render metricCaption in the metric card footer.
Line 248 renders an empty element, so metricCaption is never displayed.
Proposed fix
- <div className="mt-3 text-xs font-medium text-slate-400" />
+ <div className="mt-3 text-xs font-medium text-slate-400">
+ {item.metricCaption ?? item.description}
+ </div>📝 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.
| {item.metric ? ( | |
| // Metric-style compact card (styled to match the screenshot) | |
| <div className="mx-auto flex h-full w-full max-w-[252px] flex-col justify-between"> | |
| <div className="flex h-full min-h-[224px] flex-col justify-between rounded-[28px] bg-slate-900/95 p-5 ring-1 ring-black/30 shadow-[0_8px_22px_rgba(2,6,23,0.35)]"> | |
| <div className="flex items-center justify-between"> | |
| <div className={cn("inline-flex h-11 w-11 items-center justify-center rounded-2xl border border-white/10 bg-white/5 ring-1 ring-white/10", tone.icon)}> | |
| {item.icon} | |
| </div> | |
| <div className="inline-flex items-center rounded-full border border-white/10 bg-white/5 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.24em] text-white/45"> | |
| Insight | |
| </div> | |
| </div> | |
| <div className="flex flex-1 items-center justify-center"> | |
| <div className="text-[3.45rem] font-extrabold leading-none tracking-[-0.08em] text-white sm:text-[3.75rem]"> | |
| {item.metric} | |
| </div> | |
| </div> | |
| <div className="h-px w-full bg-white/8" /> | |
| </div> | |
| <div className="mt-3 text-xs font-medium text-slate-400" /> | |
| </div> | |
| {item.metric ? ( | |
| // Metric-style compact card (styled to match the screenshot) | |
| <div className="mx-auto flex h-full w-full max-w-[252px] flex-col justify-between"> | |
| <div className="flex h-full min-h-[224px] flex-col justify-between rounded-[28px] bg-slate-900/95 p-5 ring-1 ring-black/30 shadow-[0_8px_22px_rgba(2,6,23,0.35)]"> | |
| <div className="flex items-center justify-between"> | |
| <div className={cn("inline-flex h-11 w-11 items-center justify-center rounded-2xl border border-white/10 bg-white/5 ring-1 ring-white/10", tone.icon)}> | |
| {item.icon} | |
| </div> | |
| <div className="inline-flex items-center rounded-full border border-white/10 bg-white/5 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.24em] text-white/45"> | |
| Insight | |
| </div> | |
| </div> | |
| <div className="flex flex-1 items-center justify-center"> | |
| <div className="text-[3.45rem] font-extrabold leading-none tracking-[-0.08em] text-white sm:text-[3.75rem]"> | |
| {item.metric} | |
| </div> | |
| </div> | |
| <div className="h-px w-full bg-white/8" /> | |
| </div> | |
| <div className="mt-3 text-xs font-medium text-slate-400"> | |
| {item.metricCaption ?? item.description} | |
| </div> | |
| </div> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/animated-feature-grid/feature-card.tsx` around lines 225 - 249,
The metric card currently renders an empty footer div so item.metricCaption is
never shown; update the metric-style branch in the FeatureCard component (where
item.metric is checked and the empty <div className="mt-3 text-xs font-medium
text-slate-400" /> is returned) to render the caption text (e.g.,
item.metricCaption) inside that div, optionally guarding with a conditional
render (item.metricCaption && ...) to avoid rendering when absent and preserving
the existing styling class names.
| { | ||
| title: "Featured", | ||
| items: [ | ||
| { | ||
| title: "Animated Feature Grid", | ||
| href: "/docs/section/animated-feature-grid", | ||
| items: [], | ||
| }, | ||
| ], | ||
| }, |
There was a problem hiding this comment.
Hardcoded array index may break after sorting.
The new "Featured" section is inserted at position 2 in the sidebarNav array, but line 230 references sidebarNav[2] after the array has been sorted alphabetically (lines 202-220). After sorting, index 2 will point to the first alphabetically sorted category after "Getting Started" and "Contributing", which may not be "Featured" or the originally intended category.
🔧 Proposed fix
Replace the hardcoded index with a find operation:
export const docsConfig: DocsConfig = {
mainNav: [
{
title: "Docs",
href: "/docs",
},
{
title: "Components",
- href: sidebarNav[2].items?.[0]?.href ?? sidebarNav[2]?.href,
+ href: sidebarNav.find((nav) => nav.title === "Featured")?.items?.[0]?.href ?? "/docs",
},
{
title: "Blog",
href: "/blog",
},
],
sidebarNav,
};Also applies to: 230-230
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@config/docs.ts` around lines 84 - 93, The code currently accesses
sidebarNav[2] after sidebarNav is alphabetically sorted, which will break when
the "Featured" section position changes; update the code to locate the
"Featured" category by searching the sidebarNav array (e.g., use
Array.prototype.find to match item.title === "Featured" or another unique
property) instead of using the hardcoded index, and handle the case where the
find returns undefined (fallback or no-op) so references like the one that
previously used sidebarNav[2] safely target the correct object.
| ## Credits | ||
|
|
||
| Built by [Ujjwal Basnet](https://github.com/leazi99) | ||
| ] |
There was a problem hiding this comment.
Remove stray closing bracket.
Line 59 has an extra ] character after the markdown link that will break MDX parsing.
🐛 Fix the syntax error
Built by [Ujjwal Basnet](https://github.com/leazi99)
-]📝 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.
| ] | |
| Built by [Ujjwal Basnet](https://github.com/leazi99) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@content/docs/overlay/dropdown-menu.mdx` at line 59, There is a stray closing
bracket character (']') after the markdown link that breaks MDX parsing; open
the dropdown-menu.mdx content around the link and remove the extra ']' so the
link syntax is valid (i.e., ensure only the intended closing parenthesis or
bracket for the link remains and no standalone ']' follows the link).
Summary
Restore the single-card Storybook preview for AnimatedFeatureGrid and keep the publishable metric-card presentation.
What changed
Validation
Notes
Summary by CodeRabbit
New Features
Improvements