AGENTS.md

Everything an AI agent needs to work effectively in this codebase. This is a copy of the project's CLAUDE.md file.

Package Manager

This project uses pnpm. Always use pnpm for all commands — never npm or yarn.

pnpm dev        # start dev server
pnpm build      # production build
pnpm lint       # lint
pnpm install    # install dependencies

Folder Structure

src/
  app/              Next.js App Router  routing only, no business logic
    layout.tsx      Root layout: fonts, Providers
    (home)/page.tsx Home page
    Providers.tsx   Client wrapper for Tooltip.Provider (Base UI)
    global.css      Tailwind v4 theme + design tokens
  base/             All starter code  the fork boundary lives here
    layout/         App-level layout (Navbar, NavbarCentered, NavbarStatic, Footer, MobileMenu)
    components/     Shared components (PageHero, etc.)
    features/       Feature modules  add one folder per feature
    ui/             Design system primitives  generic, no business logic
      icons/        Icon components
    constants/      Site-wide constants (brand, nav, contact, social)
    types/          Project-specific TypeScript types
    lib/            Utilities (cn, etc.)

Import Rules

  • ui/ components have no knowledge of the app — no imports from constants, features, etc.
  • layout/ can import from constants, ui, and components but not features
  • components/ can import from constants and ui but not features or layout
  • features/ can import from anywhere except other features
  • Pages (app/) orchestrate features and sections — keep them thin
  • Nothing outside app/ should import from app/ — it is the top of the dependency graph

File Rules

  • Always create a barrel index.ts in each feature folder
  • Never import directly from third-party UI libraries — all primitives must go through @/ui wrappers
  • No inline style props except for truly dynamic values
  • One component per file (tightly coupled sub-components are the only exception)
  • ui/ components accept className for layout overrides — they never hardcode margins or positioning
  • Feature components own their spacing and do not accept className

Color System

Defined in global.css using Tailwind v4 @theme. All Tailwind built-in colors are disabled — only the custom tokens are available.

TokenUsage
rootPage background
root-100Section / card surface
root-200Elevated surface
root-contrastPrimary text on root backgrounds
root-contrast-subtleSecondary / muted text
primaryBrand accent background
secondarySecond brand color
dividerLines, borders
overlaySemi-opaque dark layer for modals

Color Rules

  • All colors must be defined as named tokens in global.css — never use raw hex values
  • Pair every background token with its -contrast companion — bg-primary → text-primary-contrast
  • Use Tailwind opacity modifier for transparency (bg-root-contrast/50)
  • Dark mode is opt-in via .dark class — tokens flip automatically

Naming Conventions

  • Feature folders and route segments use kebab-case, component files use PascalCase, utility files use camelCase
  • Named exports only — no default exports
  • Event handler props prefixed with on, handler functions with handle
  • Boolean props prefixed with is, has, or can

TypeScript

  • No any — use unknown and narrow
  • No as type casting unless there is no alternative
  • Component props interfaces are defined in the same file as the component

Tailwind

This project uses Tailwind v4 with the @theme directive in global.css. No tailwind.config.ts — configuration lives in global.css.

  • Data attribute variants use the v4 syntax: data-open:opacity-100
  • All custom tokens are available as Tailwind utilities automatically
  • cn() from @/lib merges class names with clsx + tailwind-merge

UI Imports

Always import from @/ui, never from individual files. Icons are the exception — import those from @/ui/icons.

// correct
import { Button, Heading, Text } from "@/ui";
import { MenuIcon, ArrowForwardIcon } from "@/ui/icons";

// wrong
import { Button } from "@/ui/Button";
import { MenuIcon } from "@/ui";

Server vs Client Components

  • Default to server components — no "use client" unless needed
  • Add "use client" only when using: useState, useEffect, useRef, event handlers, browser APIs
  • Layout components (Navbar, Footer) are client components
  • Children passed into a client component remain server-rendered

Adding a New Page

  1. Create src/app/[route]/page.tsx
  2. Render the page's sections directly — layout chrome (navbar, footer) comes from the root layout
  3. Add the route to NAV_LINKS in constants/navigation.ts
  4. Add the route to sitemap.ts
  5. Create a feature folder if the page has significant content
export default function NewPage() {
  return (
    <>
      {/* page sections */}
    </>
  );
}

Adding a New Feature

src/base/features/my-feature/
  MyComponent.tsx
  index.ts            <- export everything from here
import { MyComponent } from "@/features/features/my-feature";

Full CLAUDE.md

Copy and paste this into your project's CLAUDE.md file.

# Marketing Starter  Agent Guide

A Next.js 15 marketing site starter. This document covers everything an agent needs to work effectively in this codebase.

## Package Manager

This project uses **pnpm**. Always use `pnpm` for all commands  never `npm` or `yarn`.

```bash
pnpm dev        # start dev server
pnpm build      # production build
pnpm lint       # lint
pnpm install    # install dependencies
```

---

## Folder Structure

```
src/
  app/                    # Next.js App Router — routing only, no business logic
    layout.tsx            # Root layout: fonts, Providers (html/body shell only)
    (home)/page.tsx       # Home page
    about/page.tsx
    services/page.tsx
    contact/page.tsx
    Providers.tsx         # Client wrapper for Tooltip.Provider (Base UI)
    global.css            # Tailwind v4 theme + design tokens
  base/                   # All starter code — the "fork boundary" lives here
    layout/               # App-level layout (Navbar, NavbarCentered, NavbarStatic, Footer, MobileMenu)
    components/           # Shared components (PageHero, etc.)
    features/             # Feature modules — add one folder per feature
      example/
        ExampleComponent.tsx
        index.ts
    ui/                   # Design system primitives — generic, no business logic
      icons/              # Icon components
    constants/            # Site-wide constants (brand, nav, contact, social)
    types/                # Project-specific TypeScript types
    lib/                  # Utilities (cn, etc.)
```

Everything you'd keep in a fork lives under `src/base/`. The root `layout.tsx` stays in `src/app/` because Next.js requires it there.

**Rules:**
- `base/ui` components have no knowledge of the app — no imports from `@/constants`, `@/features/features`, etc.
- `base/layout` can import from `@/constants`, `@/ui`, and `@/features/components` but not `@/features/features`
- `base/components` can import from `@/constants` and `@/ui` but not `@/features/features` or `@/features/layout`
- `base/features` components can import from anywhere in `@/base` except other features
- Pages (`app/`) orchestrate features and sections — keep them thin
- Always create a barrel `index.ts` in each feature folder
- Never import directly from third-party UI libraries (Base UI, Radix, Headless UI, etc.) in `features/`, `components/`, or pages — all primitives must be consumed through `@/ui` wrappers only
- Always import features through their barrel (`@/features/features/hero`, never `@/features/features/hero/HeroSection`)
- Nothing outside `app/` should import from `app/` — it is the top of the dependency graph
- `lib/` utilities must be pure functions — no React imports, no side effects, no app-specific knowledge
- `constants/` is static data only — no functions, no logic; use `as const` for type safety
- `types/` is for shared types only — feature-specific types stay colocated in the feature folder and are not exported via `@/types`
- No inline `style={{}}` props except for truly dynamic values that cannot be expressed in Tailwind (e.g., a JS-calculated pixel height)
- No one-off Tailwind arbitrary values (`w-[347px]`) — if a value recurs, add it as a token in `global.css`
- Exception: z-index values beyond the default scale (`z-60`, `z-70`, etc.) are allowed without brackets — Tailwind v4 accepts any numeric z-index directly
- Prefer canonical Tailwind classes over arbitrary values when equivalent — e.g. `-top-1` instead of `top-[-4px]`
- `ui/` components accept `className` as a prop to allow layout overrides from outside — they never hardcode their own margins or positioning
- Feature components own their own spacing/layout and do not accept `className` — they are not generic
- `components/` and `features/` components must use `<Container>` from `@/ui` as their outer wrapper for consistent max-width and responsive padding
- One component per file — tightly coupled sub-components are the only exception
- Each page's sections should be discrete components, not one large JSX block in the page file

---

## Color System

Defined in `src/app/global.css` using Tailwind v4 `@theme`. All Tailwind built-in colors are disabled (`--color-*: initial`)  only the tokens below are available.

### Semantic tokens
Every background token has a guaranteed-contrast `-contrast` foreground companion.

| Token | Usage |
|-------|-------|
| `root` | Page background |
| `root-100` | Section / card surface (slightly different from root) |
| `root-200` | Elevated surface |
| `root-contrast` | Primary text on root backgrounds |
| `root-contrast-subtle` | Secondary / muted text |
| `primary` | Brand accent background |
| `primary-contrast` | Text on primary backgrounds |
| `secondary` | Second brand color |
| `secondary-contrast` | Text on secondary backgrounds |
| `info` / `info-contrast` | Informational state |
| `success` / `success-contrast` | Success state |
| `warning` / `warning-contrast` | Warning state |
| `danger` / `danger-contrast` | Danger / destructive state |
| `divider` | Lines, borders |
| `overlay` | Semi-opaque dark layer (80%) for modals |

**Dark mode** is opt-in via `.dark` class on any ancestor. Tokens flip automatically.

**Rules:**
- All Tailwind built-in colors are disabled  only theme tokens are available as utilities
- All colors must be defined as named tokens in `global.css`  never use raw hex values or CSS color names
- If a new color is needed, add it to `global.css` first  never introduce a color directly in a component
- Pair every background token with its `-contrast` companion  `bg-primary`  `text-primary-contrast`
- Use Tailwind's opacity modifier for transparency (`bg-root-contrast/50`) — do not add separate opacity tokens
- Never set color via inline `style={{color: '...'}}` — all color must go through Tailwind utility classes

---

## Naming Conventions

- Feature folders and route segments use kebab-case (`my-feature`), component files use PascalCase (`MyComponent.tsx`), utility files use camelCase (`formatDate.ts`)
- Named exports only — no default exports
- Event handler props are prefixed with `on` (`onSubmit`), handler functions with `handle` (`handleSubmit`)
- Boolean props are prefixed with `is`, `has`, or `can` (`isOpen`, `hasError`)

---

## TypeScript

- No `any` — if a type is unknown, use `unknown` and narrow it
- No `as` type casting unless there is no alternative
- Component props interfaces are defined in the same file as the component, not in `types/`
- When a component prop conflicts with a legacy HTML attribute (e.g. `color`), use `Omit<React.HTMLAttributes<...>, "color">` before extending

---

## Tailwind

This project uses **Tailwind v4** with the `@theme` directive in `global.css`.

- No `tailwind.config.ts` — configuration lives in `global.css`
- Data attribute variants use the v4 syntax: `data-open:opacity-100` (not `data-[open]:opacity-100`)
- All custom tokens are available as Tailwind utilities automatically: `bg-root`, `text-primary`, `border-divider`, etc.
- `cn()` from `@/lib` merges class names with `clsx` + `tailwind-merge`

---

## UI Components (`@/ui`)

Always import from `@/ui`, never from individual files. Icons are the exception — import those from `@/ui/icons`:

```tsx
// correct
import { Button, Heading, Text } from "@/ui";
import { MenuIcon, ArrowForwardIcon } from "@/ui/icons";

// wrong
import { Button } from "@/ui/Button";
import { MenuIcon } from "@/ui";
```

---

## Accessibility (a11y)

- `IconButton` automatically wraps in a `Tooltip` when `aria-label` is provided — always pass `aria-label` on icon-only buttons
- Use semantic HTML: `<nav aria-label="...">`, `<footer aria-label="...">`, `<main>`, `<section>`
- `Heading` renders `h2` by default — pass `as="h1"` on page-level headings
- Interactive elements that aren't `<button>` or `<a>` need `role` + `tabIndex` + keyboard handlers
- Focus styles use `focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none`
- Images always require meaningful `alt` text (empty string only for decorative images)

---

## Server vs Client Components

- **Default to server components**  no `"use client"` unless needed
- Add `"use client"` only when using: useState, useEffect, useRef, event handlers, browser APIs
- `children` passed into a client component remain server-rendered
- Layout components (Navbar, Footer) are client components (scroll listeners, pathname)
- Feature components that manage local UI state are client components

---

## SEO

- Every page must export a `metadata` object  no page ships without `title` and `description`
- Set `openGraph.images` on any page likely to be shared or linked externally

---

## Constants

Update `src/base/constants/brand.ts` with your company info before building features:

```ts
export const BRAND = {
  name: "Company Name",
  initials: "CO",
  tagline: "Your tagline here.",
  year: 2025,
  location: "City, State",
};
```

All constants are imported via `@/constants`:
```tsx
import { BRAND, CONTACT, NAV_LINKS } from "@/constants";
```

---

## Adding a New Page

1. Create `src/app/[route]/page.tsx`
2. Render the page's sections directly — layout concerns (navbar, footer) come from the root layout
3. Add the route to `NAV_LINKS` in `src/base/constants/navigation.ts`
4. Add the route to `sitemap.ts`
5. Create a feature folder if the page has significant content: `src/base/features/[name]/`

```tsx
export default function NewPage() {
  return (
    <>
      {/* page sections */}
    </>
  );
}
```

---

## Adding a New Feature

```
src/base/features/my-feature/
  MyComponent.tsx
  index.ts            <- export everything from here
```

Import in pages as:
```tsx
import { MyComponent } from "@/features/features/my-feature";
```