Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Animata — Claude Code Instructions

## Project overview

Animata is a free, open-source library of animated React components built with Next.js 16, Tailwind CSS v4, and TypeScript. Components live in `animata/` and are documented via MDX in `content/docs/`. The site itself is the primary showcase — the landing page at `app/(main)/page.tsx` and the docs at `app/(main)/docs/`.

## Key conventions

- Components go in `animata/<category>/<name>.tsx` with a matching `<name>.stories.tsx`
- Use `cn()` from `@/lib/utils` for class merging — never raw string concatenation
- CSS animations belong in inline `<style>` blocks inside the component (see `marquee.tsx`)
- No CSS modules, no styled-components — Tailwind only
- Fonts: `--font-display` = Young Serif (headings/display), `--font-sans` = IBM Plex Sans (body), `--font-mono` = Lilex
- Brand yellow: `#ffcc00` (from logo) — use for highlights and badges
- shadcn registry URL format: `https://animata.design/r/{category}/{name}.json` — always use this in docs/changelog, never bare `npx shadcn add component-name`
- Theme accent: `hsl(var(--accent))` = purple/violet
- All new components must be theme-responsive (light + dark)

## Changelog rule — ALWAYS update this

The changelog lives at `content/docs/changelog/` — one MDX file per month plus an index.

### Structure

```text
content/docs/changelog/
index.mdx # overview page — shows 2 most recent months + table linking all others
2026-04.mdx # April 2026 (newest first)
2026-03.mdx
...
```

### When adding a new month

1. Create `content/docs/changelog/YYYY-MM.mdx` with this frontmatter:
```yaml
---
title: Month YYYY
description: One-line summary of the main themes.
date: YYYY-MM-DD
---
```
2. Write the content as prose, not bullet lists. Each new component gets a short paragraph — what it is, when you'd use it, anything notable about the implementation.
3. Add the new month to `config/docs.ts` under the Changelog items array (newest first).
4. Update `content/docs/changelog/index.mdx`: promote the new month to "Recent releases" and move the oldest of the current two into the table.

### When updating an existing month

Add to the relevant `YYYY-MM.mdx` file. No need to touch the index unless the summary line there is now misleading.

### What warrants a changelog entry

| Action | Update changelog? |
|---|---|
| New component | Yes — new section in the current month file |
| Landing/docs UI update | Yes — brief paragraph |
| Major dependency upgrade | Yes — explain what changed and why it matters |
| User-visible bug fix | Yes — one sentence is fine |
| Dependency patch bumps | No |
| CI / GitHub Actions only | No |
| Typo fix in docs | No |

### Writing style

- Past tense. "Added X" not "We're excited to announce X."
- No emojis, no prefix symbols (no ✨ 🛠 🐛).
- Each component gets a description of what it does and when you'd use it — not just its name.
- If a fix has a root cause worth knowing, say so briefly.

## File map (quick reference)

```text
animata/ # Component source (copy-paste friendly)
container/ # Layout wrappers (marquee, dock, ribbon…)
text/ # Text animation effects
button/ # Button variants
card/ # Card components
widget/ # Complex interactive widgets
app/(main)/
page.tsx # Landing page
_landing/ # Landing page sections
docs/ # Docs app shell
components/
site-header.tsx # Top nav
icons.tsx # SVG icon set (logo is here — brand yellow #ffcc00)
content/docs/
changelog/
index.mdx # overview + table of all months — ← KEEP THIS UPDATED
YYYY-MM.mdx # one file per month (e.g. 2026-04.mdx)
contributing/ # Contributor guides
config/
docs.ts # Sidebar nav config — register new component categories here
styles/globals.css # Tailwind v4 theme tokens
```

## Running locally

```bash
yarn dev # Next.js dev server
yarn storybook # Component workbench
yarn build # Production build
```
4 changes: 2 additions & 2 deletions animata/bento-grid/gradient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function BentoCard({
return (
<MovingGradient
animated={false}
className={cn("rounded-md", className)}
className={className}
gradientClassName={cn("opacity-10", gradient)}
>
<section className="flex h-full flex-col gap-2 p-4">
Expand All @@ -57,7 +57,7 @@ function GetGradient() {
className="sm:col-span-1 sm:row-span-2"
gradient="from-cyan-900 via-60% via-sky-600 to-indigo-600"
>
<div className="group/grid relative flex cursor-pointer flex-col justify-end rounded-md bg-zinc-950 p-2 text-2xl tracking-tight text-gray-100 md:text-4xl">
<div className="group/grid relative flex cursor-pointer flex-col justify-end bg-zinc-950 p-2.5 text-2xl tracking-tight text-gray-100 md:text-4xl">
<div className="font-light">Get</div>
<div className="-mt-2 font-bold">Gradients</div>
<div className="flex h-6 w-6 items-center justify-center rounded-full border bg-white transition duration-700 group-hover/grid:rotate-[360] md:h-8 md:w-8">
Expand Down
2 changes: 1 addition & 1 deletion animata/bento-grid/three.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function FeatureThree() {

export default function Three() {
return (
<div className="grid w-full min-w-0 grid-cols-1 grid-rows-1 gap-3 sm:grid-cols-2 sm:grid-rows-2">
<div className="grid w-fit min-w-0 grid-cols-1 grid-rows-1 gap-3 sm:grid-cols-2 sm:grid-rows-2">
<FeatureOne />
<FeatureTwo />
<FeatureThree />
Expand Down
52 changes: 52 additions & 0 deletions animata/container/announcement-ribbon.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Package, Sparkles, Zap } from "lucide-react";
import AnnouncementRibbon from "@/animata/container/announcement-ribbon";

const meta = {
title: "Container/Announcement Ribbon",
component: AnnouncementRibbon,
parameters: {
layout: "fullscreen",
},
tags: ["autodocs"],
argTypes: {
badge: { control: "text" },
ctaText: { control: "text" },
repeat: { control: { type: "number", min: 2, max: 8 } },
pauseOnHover: { control: "boolean" },
},
} satisfies Meta<typeof AnnouncementRibbon>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {
args: {
pauseOnHover: true,
},
};

export const NoControls: Story = {
args: {
badge: null,
ctaText: null,
pauseOnHover: true,
},
};

export const CustomMessage: Story = {
args: {
badge: "V2",
ctaText: "Read the docs",
message: (
<span className="flex items-center gap-2 whitespace-nowrap">
<Zap className="h-3 w-3 shrink-0 text-black/75" aria-hidden />
<span className="text-black/75">
Animata <strong className="font-medium">2.0</strong> is here — faster animations, smaller
bundles, zero config
</span>
</span>
),
pauseOnHover: true,
},
};
116 changes: 116 additions & 0 deletions animata/container/announcement-ribbon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"use client";

import Link from "next/link";

import Marquee from "@/animata/container/marquee";
import { cn } from "@/lib/utils";

interface AnnouncementRibbonProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* Content to scroll in the ribbon. Accepts any React node.
* Defaults to the shadcn registry announcement.
*/
message?: React.ReactNode;

/**
* Label shown in the static left badge. Pass `null` to hide it.
* @default "NEW"
*/
badge?: string | null;

/**
* Text for the right-side CTA link. Pass `null` to hide it.
* @default "Learn more"
*/
ctaText?: string | null;

/**
* URL for the CTA link.
* @default "/docs/changelog/2026-04"
*/
ctaHref?: string;

/**
* Number of times the message is repeated to fill the track.
* @default 5
*/
repeat?: number;

/**
* Pause scrolling when the user hovers over the ribbon.
* @default true
*/
pauseOnHover?: boolean;
}

function DefaultMessage() {
return (
<span>
<span className="whitespace-nowrap px-12 font-(family-name:--font-display) font-light text-neutral-900">
shadcn registry is live
</span>
<span className="text-neutral-900">&middot;</span>
</span>
);
}

export default function AnnouncementRibbon({
message,
badge = "NEW",
ctaText = "Learn more",
ctaHref = "/docs/changelog/2026-04",
repeat = 5,
pauseOnHover = true,
className,
...props
}: AnnouncementRibbonProps) {
const content = message ?? <DefaultMessage />;

return (
<div
className={cn(
"relative flex h-11 w-full items-center overflow-hidden",
"bg-[#ffcc00]",
"border-b border-black/8",
className,
)}
{...props}
>
{/* Badge */}
{badge && (
<div className="relative z-30 flex bg-[#ffcc00] shrink-0 items-center self-stretch border-r border-black/8 px-4">
<span className="rounded-full bg-black/10 px-2.5 py-px font-mono text-[10px] font-semibold uppercase tracking-widest text-neutral-900">
{badge}
</span>
</div>
)}

<div className="flex-1 overflow-hidden">
<Marquee repeat={repeat} pauseOnHover={pauseOnHover} applyMask={false}>
{content}
</Marquee>
</div>

{/* CTA */}
{ctaText && ctaHref && (
<Link
href={ctaHref}
className="group/cta relative bg-[#ffcc00] z-30 flex shrink-0 items-center gap-1.5 self-stretch border-l border-black/8 px-4 font-mono text-[10px] font-semibold uppercase tracking-widest text-neutral-800/60 transition-colors hover:text-neutral-900"
>
{ctaText}
<svg
className="h-3 w-3 transition-transform group-hover/cta:translate-x-0.5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
aria-hidden
role="presentation"
>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg>
</Link>
)}
</div>
);
}
9 changes: 6 additions & 3 deletions animata/hero/hero-section-text-hover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const HeroSectionTextHover: React.FC<HeroCardProps> = ({ className }) => {
<span
key={index}
className={cn(
"pointer-events-none absolute transform text-lg transition-transform duration-500 group-hover/hero:scale-110 sm:text-2xl md:text-4xl",
"pointer-events-none absolute transform text-lg transition-transform duration-300 ease-[cubic-bezier(0.5,1.8,0.4,1)] group-hover/hero:scale-110 sm:text-2xl md:text-4xl",
dest.position,
)}
>
Expand All @@ -104,7 +104,7 @@ const HeroSectionTextHover: React.FC<HeroCardProps> = ({ className }) => {
<span
key={index}
className={cn(
"pointer-events-none absolute transform text-lg transition-transform duration-500 group-hover/hero:scale-110 sm:text-2xl md:text-4xl",
"pointer-events-none absolute transform text-lg transition-transform duration-300 ease-[cubic-bezier(0.5,1.8,0.4,1)] group-hover/hero:scale-110 sm:text-2xl md:text-4xl",
gem.position,
)}
>
Expand All @@ -115,7 +115,10 @@ const HeroSectionTextHover: React.FC<HeroCardProps> = ({ className }) => {
</div>
</div>
</div>
<button className="cursor-pointer rounded-3xl bg-orange-400 px-4 py-2 font-mono tracking-tighter hover:bg-orange-500">
<button
className="cursor-pointer rounded-3xl bg-yellow-400 px-4 py-2 font-mono tracking-tighter hover:bg-yellow-500"
type="button"
>
Begin your journey
</button>
</div>
Expand Down
5 changes: 2 additions & 3 deletions animata/hero/product-features.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { type HTMLMotionProps, motion, useSpring, useTransform } from "motion/react";
import type { ReactNode } from "react";
import Balancer from "react-wrap-balancer";

import { cn } from "@/lib/utils";

Expand Down Expand Up @@ -89,10 +88,10 @@ export default function ProductFeatures() {
className="flex max-w-md flex-col items-center gap-2 text-center"
>
<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-balance text-lg text-neutral-500">
Quirky ceramics for happy spaces. From sleek vases to funky mugs, we&apos;ve got your
shelves covered.
</Balancer>
</div>
</motion.header>

<motion.div
Expand Down
2 changes: 1 addition & 1 deletion animata/text/animated-gradient-text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default function AnimatedGradientText({
return (
<div
className={cn(
"bg-size animate-bg-position bg-linear-to-r from-yellow-500 from-30% via-yellow-700 via-50% to-pink-500 to-80% bg-[length:200%_auto] bg-clip-text text-transparent",
"bg-size animate-bg-position bg-linear-to-r from-yellow-500 from-30% via-yellow-700 via-50% to-pink-500 to-80% bg-size-[200%_auto] bg-clip-text text-transparent",
className,
)}
>
Expand Down
22 changes: 22 additions & 0 deletions animata/text/blur-out-up.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Meta, StoryObj } from "@storybook/react";
import BlurOutUp from "@/animata/text/blur-out-up";

const meta = {
title: "Text/Blur Out Up",
component: BlurOutUp,
parameters: { layout: "centered" },
tags: ["autodocs"],
} satisfies Meta<typeof BlurOutUp>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {
args: {
speed: 0.72,
holdMs: 550,
gapMs: 320,
className:
"h-80 w-[32rem] rounded-lg border border-zinc-200 bg-white text-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-100",
},
};
Loading