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
```
82 changes: 82 additions & 0 deletions animata/container/announcement-ribbon.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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-sky-400" aria-hidden />
<span className="text-white/75">
Animata <strong className="font-semibold text-sky-300">2.0</strong> is here — faster
animations, smaller bundles, zero config
</span>
</span>
),
pauseOnHover: true,
},
};

export const RegistryRelease: Story = {
name: "Registry Release (Full Page Preview)",
args: {},
render: () => (
<div className="flex min-h-screen flex-col bg-background">
<AnnouncementRibbon />
<div className="flex flex-1 flex-col items-center justify-center gap-6 px-4 text-center">
<div className="flex items-center gap-2 rounded-full border border-amber-300/40 bg-amber-100/60 px-3 py-1 dark:border-amber-700/30 dark:bg-amber-950/30">
<Package className="h-3.5 w-3.5 text-amber-600 dark:text-amber-400" />
<span className="text-xs font-medium text-amber-700 dark:text-amber-400">
shadcn registry
</span>
</div>
<h1 className="text-4xl font-bold tracking-tight text-foreground md:text-6xl">
Beautiful UI components
<br />
<span className="text-muted-foreground">ready to install</span>
</h1>
<p className="max-w-md text-sm text-muted-foreground">
Copy, paste, and own every component — no black boxes, no abstractions you can't control.
</p>
<div className="flex items-center gap-1.5 rounded-lg border border-border bg-muted px-4 py-2 font-mono text-sm text-muted-foreground">
<Sparkles className="h-3.5 w-3.5 text-amber-500" />
npx shadcn add
</div>
</div>
</div>
),
};
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>
);
}
2 changes: 1 addition & 1 deletion app/(main)/_landing/open-source-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export default function OpenSourceSection() {

{/* Stats */}
<div className="mx-auto grid max-w-3xl grid-cols-2 gap-8 px-6 py-10 sm:grid-cols-4 sm:py-12">
<Stat value={2392} label="Stars" delay={0} />
<Stat value={2506} label="Stars" delay={0} />
<Stat value={196} label="Forks" delay={100} />
<Stat value={44} label="Contributors" delay={200} />
<Stat value={194} label="Components" delay={300} />
Expand Down
2 changes: 1 addition & 1 deletion app/(main)/_landing/stats-bento.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export default function StatsBento() {
<strong className="font-(family-name:--font-mono) text-2xl font-bold tabular-nums text-foreground sm:text-3xl lg:text-4xl">
<RemountOnMouseIn>
<Ticker
value="2,392+"
value="2,506+"
className="font-(family-name:--font-mono) font-bold tabular-nums"
/>
</RemountOnMouseIn>
Expand Down
4 changes: 2 additions & 2 deletions app/(main)/blog/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ export default async function BlogPage({ params }: BlogPageProps) {
</div>
{blog.toc && (
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 pt-4">
<div className="sticky top-[6.25rem] -mt-10 pt-4">
<ScrollArea className="pb-10">
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] py-12">
<div className="sticky top-[6.25rem] -mt-10 h-[calc(100vh-6.25rem)] py-12">
<DashboardTableOfContents toc={toc} />
</div>
</ScrollArea>
Expand Down
2 changes: 1 addition & 1 deletion app/(main)/blog/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function BlogLayout({ children }: BlogLayoutProps) {
return (
<div className="border-b border-border">
<div className="container flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-4 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-6">
<aside className="fixed top-14 z-30 -ml-2 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 md:sticky md:block">
<aside className="fixed top-[6.25rem] z-30 -ml-2 hidden h-[calc(100vh-6.25rem)] w-full shrink-0 md:sticky md:block">
<ScrollArea className="h-full py-6 pr-6 lg:py-8">
<DocsSidebarNav items={blogSidebarNav} />
</ScrollArea>
Expand Down
Loading