Full reference: docs/design-system.md. This skill gives you the fast rules.
Color -- always use semantic tokens
- Background surfaces:
bg-background>bg-card>bg-elevated>bg-muted(lightest to most elevated) - Text:
text-foreground(primary) /text-muted-foreground(secondary) /text-subtle(tertiary) - Brand / accent:
bg-accentfor CTAs;hover:bg-accent-hoveron hover;text-accentfor inline highlights - Borders:
border-border(default hairline);border-border-strong(emphasis, button outlines) - Never use raw hex. Dark mode is the default (
darkclass on<html>).
Typography
- Headings:
font-serif(Fraunces). Display:text-[clamp(2rem,5vw,2.75rem)] font-medium tracking-[-0.02em]. Section h2:text-2xl md:text-[1.75rem]. - Body:
font-sans(Inter). Default sizetext-base, secondary text intext-muted-foreground. - Labels / eyebrows / code:
font-mono(JetBrains Mono). Label component:text-[11px] uppercase tracking-[0.16em] text-subtle. - No em-dashes in copy. Restructure sentences instead.
Section pattern
Every major section follows this structure:
tsx<Section number="01" label="Label" title="Serif section title" width="reading"> {/* content */} </Section> <Divider />
Sectionhandlespy-10 md:py-14vertical rhythm and theContainerwrapper.Dividergoes between every section (faded hairline, matches homepage).- Number is
text-accent-hover; title isfont-serif text-2xl md:text-[1.75rem].
Layout primitives (all in components/layout/)
| Primitive | When to use |
|---|---|
Container width="reading" | Prose / single-column content (760px max) |
Container width="wide" | Full layouts, hero sections (1080px max) |
Section | Any major content block needing the numbered eyebrow + title |
Bento | Grid of feature cards with hairline borders |
Divider | Between every section |
Label | Eyebrow text above headings |
Reveal | Wrap elements for fade-up animation on scroll |
Bento pattern
tsx<Bento className="grid-cols-1 md:grid-cols-3"> <div className="bg-card p-6">Cell</div> <div className="bg-card p-6">Cell</div> </Bento>
Each cell needs bg-card so the 1px bg-border gap is visible as a hairline separator.
Component patterns
- Primary button:
rounded-full bg-accent text-accent-foreground hover:bg-accent-hover - Ghost button:
rounded-full border border-border-strong hover:bg-muted - Card:
rounded-2xl border border-border bg-card p-6 - Badge:
rounded-full bg-accent/15 text-accent font-mono text-[11px] uppercase tracking-[0.12em] - Inline link:
underline decoration-accent/50 underline-offset-4 hover:decoration-accent - StackIcon:
<StackIcon name="react" />-- use for brand/tech icons;lucide-reactfor UI icons
Motion rules
- Easing:
--ease-out=cubic-bezier(0.22, 1, 0.36, 1)for entrances. - Duration: 200ms hover micro / 300-400ms reveal / ~60ms stagger per item.
- Wrap elements in
<Reveal>for scroll-triggered fade-up. - Hover lift:
whileHover={{ y: -2 }}on cards. - Always respect
prefers-reduced-motion(handled globally inglobals.css).
Icons
- UI icons:
lucide-react(outlined). - Brand logos:
simple-iconsviaStackIconcomponent. Do not importsimple-iconsdirectly.
Copy rules
- No em-dashes. Use commas, colons, or restructure.
- Section eyebrow labels are ALL CAPS via CSS -- write lowercase in JSX.
- Keep explanations short and direct.