factory-design
Skill2 starsChanged 4 months agoMITView on GitHub
Apply the Factory.ai visual design system to any UI. Use whenever the user asks for Factory-style, "factory.ai look", agent-native developer tooling aesthetics, or is building landing pages, dashboards, marketing sections, CLI install boxes, terminal mockups, or developer-tool UIs that should feel like Factory.ai / Vercel / Linear-adjacent dev brands. Triggers on phrases like "factory design", "factory style", "droid look", "make it look like factory.ai", or any task in a project using the color tokens `--color-accent-100`, `--color-base-*`, Geist Mono + orange `#ef6f2e`. Produces exact class strings, CSS variables, component shapes, motion, and enforcement rules — never guesses values.
- Pipes a download into a shell
2 other projects use a version of this.
Adopted by
Most recently taken up by
- nikships/factory-designtheir own version, first seen Nov 1, 2026
- nonlinear-xyz/factory-kittheir own version, first seen Nov 1, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- nonlinear-xyz/factory-kit★ 9same name
- nikships/factory-design★ 3same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository25 other files it gives its agents.
- brutal-deepresearchSkill
- brutal-grillSkill
- brutal-idea-evalSkill
- brutal-inf-fix-loopSkill
- brutal-planSkill
- brutal-pr-finding-fixerSkill
- brutal-pr-fix-loopSkill
- brutal-project-reviewSkill
- brutal-pr-reviewSkill
- brutal-realitySkill
- brutal-reviewSkill
- brutal-specSkill
- brutal-swarmSkill
- brutal-ticketsSkill
- brutal-wayfinderSkill
- brutal-workerSkill
- codex-coopSkill
- design-jepa-modelsSkill
- funcvec-dedupe-fixerSkill
- grill-meSkill
- handoffSkill
- opencode-delegateSkill
- rust-architecture-reviewSkill
- rust-pr-reviewSkill
- supply-chain-analyzerSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
31 rules. About 0 are common, 9 are distinctive, and 22 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈9
Distinctive · estimate
This author's own choices. Worth a look.
22
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 15
Dark-first, tokenized: Always author in dark mode first.
- Distinctive: line 20
If a design decision isn't covered below, err toward: mono, dashed, 8-12px radius, var(--color-base-XXX), tight tracking, stripe on hover.
- Very specific: line 26
All tokens live in app/globals.css under :root, html / [data-theme="dark"], and [data-theme="light"].
- Very specific: line 133
Every <section> and the header use this container:
- Very specific: line 141
Never container or max-w-7xl.
- Very specific: line 211
Always use <br /> to enforce specific line breaks in marketing copy — never let the browser wrap.
- Very specific: line 222
Use for: nav links, tab labels, section eyebrow labels, terminal window titles ("01 — TERMINAL / IDE" at text-[12px]).
- Distinctive: line 299
Every primary/secondary button renders an absolute overlay that contains an animated 45° stripe pattern, visible only on hover / …
- Very specific: line 360
Use one Badge at the top of every major section (PLATFORM, PRODUCT, FOOTER, etc.).
- Distinctive: line 556
Never mix: a single container uses one or the other.
- Very specific: line 591
All motion respects prefers-reduced-motion via the rule at the bottom of globals.css which forces animation-duration: 0.01ms and …
- Very specific: line 595
Use the useCharReveal(heroRef) hook.
- Distinctive: line 623
All 250ms eased. Use the ready-made utilities:
- Distinctive: line 653
When scaffolding or extending a Factory-style project, mirror this layout:
- Distinctive: line 701
When generating or editing UI in a Factory-style codebase, do:
- Very specific: line 704
Use font-mono by default; only drop to sans for display/section headings.
- Very specific: line 705
Prefer pixel-exact text-[Npx] values from §3.
- Very specific: line 708
Place one <Badge> at the top of every major section.
- Distinctive: line 709
Use dashed borders for interactive containers, solid for structural.
- Very specific: line 710
Use the btn-stripe-pattern overlay on any hoverable surface that's "primary" in feel.
- Distinctive: line 711
Always provide dark and light via token inversion — don't ship single-theme UI.
- Very specific: line 712
Always guard motion with useReducedMotion / the global media query.
- Very specific: line 713
Keep icons inline, 24×24, stroke-2, currentColor.
- Very specific: line 719
Use rounded-full on buttons or rounded-2xl on cards — the system is 4px button / 8px card / 12px panel.
- Very specific: line 720
Add drop shadows (except shadow-2xl on the product-menu dropdown).
- Very specific: line 721
Import lucide-react / heroicons.
- Very specific: line 722
Use Tailwind's container class or max-w-7xl.
- Distinctive: line 723
Use emoji in UI copy.
- Very specific: line 724
Use bold weights on marketing headlines — they are font-normal.
- Very specific: line 726
Use red, green, yellow, blue, or purple for UI state.
- Very specific: line 761
When in doubt, open app/globals.css, app/page.tsx, components/ui/button.tsx, and components/ui/badge.tsx — those four files are the design …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Factory Design System
A rigorous, pixel-level reproduction of the Factory.ai aesthetic. This skill defines every measurement, variable, class combination, and animation that must be used. When building UI in this system, copy these values verbatim — do not round, re-interpret, or "modernize" them.
Core Philosophy
Factory's look is "agent-native developer tooling". Five non-negotiable rules define the vibe:
- Monospace is the voice.
Geist Monois the default typeface for ~90% of text (nav, labels, body paragraphs, buttons, badges, code).Geist Sansappears only for display/marketing headings and section titles — never for UI chrome. - Dark-first, tokenized. Always author in dark mode first. Never hardcode colors — always
var(--color-...). Light theme is the mirror, not a rewrite. - Micro-typography. Tracking is tight and negative almost everywhere (
tracking-[-0.015rem],tracking-[-0.0175rem],tracking-[-0.04em]on headlines). Font sizes are specific pixels (text-[12px],text-[14px],text-[60px]), not Tailwind presets. - Dashed borders and diagonal stripes. Containers use
border-dashed border-[var(--color-base-700)]. Buttons and hoverable cards reveal a 45° conveyor-belt stripe pattern on hover. - Motion is earned. Three signature animations: character reveal on the hero headline, scramble on section badges, typewriter inside the CLI install box. Everything else is
duration-150/duration-200color and opacity transitions.
If a design decision isn't covered below, err toward: mono, dashed, 8-12px radius, var(--color-base-XXX), tight tracking, stripe on hover.
1. Design Tokens — copy these exactly
All tokens live in app/globals.css under :root, html / [data-theme="dark"], and [data-theme="light"]. Never inline a hex/oklch. Reference via var(--token-name).
1.1 Accent (orange — shared across themes)
--color-accent-100: #ef6f2e; /* primary orange — links, hero word, dots, "Copied" check */
--color-accent-200: #ee6018; /* hover/active */
--color-accent-300: #d15010; /* badge numerals, emphatic accents */Usage rules:
- Exactly one accent word per headline. Example:
<span class="text-[var(--color-accent-100)]">Next-Gen</span> Platform. - Accent dot (
w-2 h-2 rounded-full bg-[var(--color-accent-100)]) precedes "Trusted by" / section intros. ::selectionis always accent-100 on dark-primary.- Nav links hover to
text-[var(--color-accent-100)]viatransition-colors duration-200.
1.2 Base Scale — inverts between themes
The base scale runs 100 (near-foreground) → 1000 (near-background). Dark theme: 100 is almost white, 1000 is near-black. Light theme: inverted. This means every component works in both themes without conditionals — you just reference the scale.
/* DARK (default) */
--background: #020202; /* page bg */
--foreground: #eee; /* primary text */
--color-base-100: #d6d3d2; /* near-foreground */
--color-base-200: #ccc9c7;
--color-base-300: #b8b3b0;
--color-base-400: #a49d9a; /* nav links, subheading body text */
--color-base-500: #8a8380; /* muted body text, "Trusted by" copy, placeholders */
--color-base-600: #5c5855; /* terminal dots, social link separators */
--color-base-700: #4d4947; /* dashed borders, dropdown borders, theme-toggle border */
--color-base-800: #3d3a39; /* solid borders on cards, tab dividers */
--color-base-900: #2e2c2b; /* card backgrounds (CTA block, footer, terminal mockup) */
--color-base-1000: #1f1d1c; /* deepest fill (command box inside terminal) */
/* LIGHT */
--background: #eee;
--foreground: #020202;
--color-base-100: #1f1d1c; /* (inverted) */
--color-base-1000: #d6d3d2;
/* ...the whole scale mirrors */Semantic aliases (prefer these when the role is semantic, not shade-specific):
--color-dark-base-primary: #020202 / #eee; /* background surface */
--color-dark-base-secondary: #101010 / #fafafa;
--color-light-base-primary: #eee / #020202; /* readable text surface */
--color-light-base-secondary:#fafafa / #101010;Button-specific tokens (never hardcode button colors):
/* dark */
--btn-primary-bg: #1f1d1c; --btn-primary-text: #fafafa;
--btn-secondary-bg: #fafafa; --btn-secondary-text: #1f1d1c;
--btn-secondary-border: var(--color-base-700);
/* light (inverted + transparent secondary border) */
--btn-primary-bg: #eeeeee; --btn-primary-text: #101010;
--btn-secondary-bg: #101010; --btn-secondary-text: #eeeeee;
--btn-secondary-border: transparent;1.3 Typography
--font-sans: var(--font-geist-sans), system-ui, sans-serif;
--font-mono: var(--font-geist-mono), monospace;Load both Geists from next/font/google in app/layout.tsx:
import { Geist, Geist_Mono } from "next/font/google";
const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });Body default: font-family: var(--font-sans) + antialiased. All UI chrome explicitly overrides with font-mono.
1.4 Radius, Motion, Spacing
--spacing: 0.25rem; /* 4px base unit */
--radius-xs: 0.125rem; /* 2px */
--radius-sm: 0.25rem; /* 4px — buttons */
--radius-md: 0.375rem; /* 6px */
--radius-lg: 0.5rem; /* 8px — cards, CLI box, terminal mockup, CTA */
--radius-xl: 0.75rem; /* 12px — product dropdown panel */
--radius-2xl: 1rem;
--radius-3xl: 1.5rem;
--ease-reveal: cubic-bezier(0.645, 0.045, 0.355, 1); /* char reveal, fade-in-up */
--ease-default: cubic-bezier(0.4, 0, 0.2, 1);
--duration-fast: 0.15s; /* transition-colors default */
--duration-normal: 0.3s;
--duration-slow: 0.5s;2. Layout Grid — the 4/12 column rule
Every <section> and the header use this container:
<div className="mx-auto grid grid-cols-4 lg:grid-cols-12 gap-x-4 lg:gap-x-6 px-4 lg:px-9">- Mobile: 4 columns,
px-4(16px),gap-x-4. - Desktop (
lg≥ 1024px): 12 columns,px-9(36px),gap-x-6. - Never
containerormax-w-7xl. Horizontal centering comes frommx-auto+ the grid itself.
Column allocations (memorize these):
| Block | Mobile | Desktop |
| Hero left (copy + CLI box) | col-span-4 | col-span-6 |
| Hero right (graphic) | hidden | col-span-6 |
| Product copy | col-span-4 | col-span-5 |
| Product mockup | col-span-4 mt-8 | col-span-7 mt-0 |
| CLI install box | max-width max-w-[480px] inside col | same |
| CTA card | max-w-[520px] | max-w-[520px] |
Vertical rhythm:
- Hero margin:
mt-4 lg:mt-20 mb-20 lg:mb-30. - Section padding:
py-20 lg:py-30. - Header padding:
py-5. - Footer outer:
mx-4 lg:mx-9 mb-4 lg:mb-9(the footer itself is a rounded card, not a full-bleed bar). - Stack gaps inside columns:
gap-y-6 lg:gap-y-8.
3. Typography Scale — exact sizes per role
Rule: always use text-[Npx] bracket values for UI chrome. Tailwind's text-sm / text-base are only acceptable in the <Text> / <Heading> CVA variants.
3.1 Hero headline (marketing display)
<h1
ref={heroTitleRef}
className="text-[40px] lg:text-[60px] 2xl:text-[72px]
font-mono font-normal leading-[100%] tracking-[-0.04em]"
>
<span className="text-[var(--color-accent-100)]">Next-Gen</span>
<span className="text-[var(--foreground)]"> Platform</span>
<br />
<span className="text-[var(--foreground)]">for Developers</span>
</h1>Hero headline is font-mono, font-normal (not bold) with leading-[100%]. useCharReveal(heroTitleRef) splits it into char spans and fades them in on intersection.
3.2 Section headings (h2)
<h2 className="text-[32px] lg:text-[48px] font-normal leading-[100%]
tracking-[-0.04em] text-[var(--foreground)]">
Droids meet you<br />wherever you work.
</h2>Section h2 uses default sans (not mono), font-normal, forced line breaks with <br />.
3.3 CTA headings
<h2 className="text-[28px] lg:text-[36px] font-normal leading-[110%]
tracking-[-0.02em]">3.4 Subheadings / body (mono)
Paragraph body is font-mono text-[14px] lg:text-[16px] leading-[140%] tracking-[-0.0175rem] colored by role:
| Role | Class |
| Primary body | text-[var(--color-base-400)] |
| Muted body | text-[var(--color-base-500)] |
Always use <br /> to enforce specific line breaks in marketing copy — never let the browser wrap.
3.5 Labels (micro-UI)
className="font-mono text-[12px] uppercase tracking-[-0.015rem]
text-[var(--color-base-400)]
hover:text-[var(--foreground)]
transition-colors duration-200"Use for: nav links, tab labels, section eyebrow labels, terminal window titles ("01 — TERMINAL / IDE" at text-[12px]).
3.6 Tab pills inside CLI box
className="px-2 py-0.5 rounded text-[11px] font-mono
uppercase tracking-[-0.01rem] transition-colors"
/* active */ "border border-[var(--color-base-800)] bg-[var(--color-base-1000)] text-[var(--foreground)]"
/* inactive */ "border border-transparent text-[var(--color-base-500)] hover:text-[var(--foreground)]"3.7 Code / command strings
<code className="font-mono text-[14px] text-[var(--foreground)]">...</code>Prefix with an accent caret: <span className="text-[var(--color-accent-100)] font-mono text-[14px]">></span>.
3.8 <Heading> and <Text> CVA variants (for reusable cases)
components/ui/heading.tsx exposes seven variants — use them when a reusable heading is appropriate:
| variant | sizes | weight |
display | 40 / 56 / 72 | semibold |
heading-1 | 30 / 36 / 48 | semibold |
heading-2 | 24 / 28 / 36 | semibold |
subheading-1 | 18 / 20 / 24 | medium |
subheading-2 | 16 / 18 / 20 | medium |
subheading-3 | 14 / 16 | medium |
metrics | 48 / 64 / 80 | bold |
color prop: "default" | "secondary" | "muted" | "accent".
<Text> variants: paragraph / paragraph-lg / paragraph-sm / label-1 / label-1-mono / label-2 / label-2-mono / caption / code.
Note the conflict: display and heading-1 in CVA use font-sans font-semibold. The hero in page.tsx overrides to font-mono font-normal because the marketing hero has its own look. When in doubt for the home hero: mono, normal, tracking -0.04em. For dashboard/product page headings: use the CVA display/heading-1 directly.
4. Components — exact shapes
4.1 <Button> (components/ui/button.tsx)
Built with cva + forwardRef + optional Radix Slot (asChild).
Base classes (always applied):
group relative inline-flex w-max cursor-pointer items-center justify-center
border font-mono uppercase transition-colors duration-150
disabled:cursor-not-allowed disabled:opacity-50
[&_*]:transition-colors [&_*]:duration-150
focus-visible:outline focus-visible:outline-offset-4Variants (exact classes):
| variant | classes |
primary (default) | bg-[var(--btn-primary-bg)] text-[var(--btn-primary-text)] border-[var(--color-base-700)] hover:opacity-80 overflow-hidden rounded-[4px] |
secondary | bg-[var(--btn-secondary-bg)] text-[var(--btn-secondary-text)] border-transparent hover:opacity-80 overflow-hidden rounded-[4px] |
ghost | bg-transparent text-[var(--foreground)] hover:bg-[var(--color-base-900)] hover:text-[var(--color-accent-100)] |
link | bg-transparent text-[var(--foreground)] hover:text-[var(--color-accent-100)] underline-offset-4 hover:underline |
outline | border-[var(--color-base-700)] text-[var(--foreground)] hover:border-[var(--color-accent-100)] hover:text-[var(--color-accent-100)] |
Sizes (exact heights):
| size | classes |
sm | h-[25px] px-3 text-[12px] tracking-[-0.015rem] |
default | h-[31px] px-[14px] text-[12px] tracking-[-0.015rem] |
lg | h-[40px] px-6 text-[14px] tracking-[-0.0175rem] |
icon | h-[31px] w-[31px] p-0 |
The stripe-hover overlay (required for primary/secondary):
Every primary/secondary button renders an absolute overlay that contains an animated 45° stripe pattern, visible only on hover / focus-visible. Copy this verbatim inside the button body:
{showPattern && isPrimaryOrSecondary && (
<div className="pointer-events-none absolute inset-0 opacity-0 will-change-transform
group-hover:opacity-100 group-focus-visible:opacity-100
transition-opacity duration-100 delay-75">
<div
className="btn-stripe-pattern absolute inset-0"
style={{ "--lines-color": linesColor } as React.CSSProperties}
/>
</div>
)}
<span className="relative z-10 flex items-center gap-1">{children}</span>linesColor rule:
variant === "secondary"→var(--color-base-600)(darker lines on light bg)- else →
var(--color-base-500)
The .btn-stripe-pattern CSS lives in globals.css and must not be modified:
.btn-stripe-pattern {
background-image: repeating-linear-gradient(
45deg,
transparent 0px, transparent 2px,
var(--lines-color, var(--color-base-400)) 2px,
var(--lines-color, var(--color-base-400)) 3px,
transparent 3px, transparent 5px
);
background-size: 7.07px 7.07px;
animation: slidePattern 2s linear infinite;
}
@keyframes slidePattern {
0% { background-position: 0 0; }
100% { background-position: 28.28px -28.28px; }
}Children inside a button that should animate (e.g. arrow icons) go after the text with gap-1 spacing: <Button>Learn More <ArrowIcon className="w-3 h-3" /></Button>.
4.2 <Badge> (components/ui/badge.tsx)
An all-caps mono label with either (a) an accent dot or (b) a numbered prefix. Accompanied by a scramble-text animation on the label.
<Badge text="PLATFORM" /> // dot + scrambled text
<Badge text="PRODUCT" variant="numbered" number={1} />Base:
inline-flex items-center uppercase font-mono text-xs tracking-widerVariants:
default:gap-3 text-[var(--color-base-500)]+ prefixed by<span class="w-2 h-2 rounded-full bg-[var(--color-accent-300)]">.numbered:gap-2 text-[var(--color-base-500)]+ prefixed by the number rendered intext-[var(--color-accent-300)] font-semibold.off: identical to default but typically used on inactive states (still dot-prefixed).
Use one Badge at the top of every major section (PLATFORM, PRODUCT, FOOTER, etc.). Always uppercase, ≤ 12 characters.
4.3 CLI Install Box (hero right of copy)
<div className="flex flex-col max-w-[480px] rounded-lg
border border-[var(--color-base-800)] bg-[var(--background)]
overflow-hidden">
{/* tabs row */}
<div className="flex gap-1 px-3 py-2">
<button className={tabClass(active)}>macOS / Linux</button>
<button className={tabClass(active)}>Windows</button>
</div>
<div className="border-t border-[var(--color-base-800)]" />
{/* command row */}
<div className="flex items-center justify-between px-4 py-3 gap-4">
<div className="flex items-center gap-2 min-w-0">
<span className="text-[var(--color-accent-100)] font-mono text-[14px]">></span>
<code className="font-mono text-[14px] text-[var(--foreground)]">
{displayedText}
{isTyping && (
<span className="inline-block w-[8px] h-[16px] bg-[var(--foreground)]
ml-[1px] align-middle translate-y-[1px]" />
)}
</code>
</div>
<button onClick={handleCopy}
className="flex-shrink-0 p-1.5 rounded border border-transparent
hover:border-[var(--color-base-700)] transition-all">
{copied ? <CheckIcon className="w-4 h-4 text-[var(--color-accent-100)]" />
: <CopyIcon className="w-4 h-4 text-[var(--color-base-500)]" />}
</button>
</div>
</div>Typewriter interval is exactly 25 ms / char (see §6.3). Reset + retype on tab switch. Clipboard copy → swap to accent check icon for 2000 ms, then revert.
4.4 Terminal Mockup (product section)
Mac-style traffic-light dots with a text title on the right:
<div className="rounded-lg border border-[var(--color-base-800)] bg-[var(--color-base-900)] overflow-hidden">
<div className="flex items-center justify-between px-4 py-3 border-b border-[var(--color-base-800)]">
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-[var(--color-base-600)]" />
<div className="w-3 h-3 rounded-full bg-[var(--color-base-600)]" />
<div className="w-3 h-3 rounded-full bg-[var(--color-base-600)]" />
</div>
<span className="font-mono text-[12px] text-[var(--color-base-500)]">
01 — TERMINAL / IDE
</span>
</div>
<div className="p-6 min-h-[300px]">{/* content */}</div>
</div>Traffic-light dots are monochromatic grey (never red/yellow/green — that's macOS, not Factory). Title format: NN — CATEGORY.
4.5 Navigation Dropdown ("Product" menu)
Two-column grid of icon-tiles inside a rounded-xl dashed-border panel:
<div className="w-[520px] p-3 rounded-xl border border-dashed
border-[var(--color-base-700)] bg-[var(--background)] shadow-2xl">
<div className="grid grid-cols-2 gap-1">
{/* each tile: */}
<a className="group relative flex items-start gap-3 p-2.5 rounded-lg
border border-dashed border-transparent
hover:border-[var(--color-base-700)]
transition-colors overflow-hidden">
{/* stripe overlay on hover */}
<div className="pointer-events-none absolute inset-0 opacity-0
group-hover:opacity-30 transition-opacity duration-100">
<div className="btn-stripe-pattern absolute inset-0"
style={{ "--lines-color": "var(--color-base-600)" } as React.CSSProperties} />
</div>
{/* icon square */}
<div className="relative z-10 w-11 h-11 flex-shrink-0 rounded-lg
border border-[var(--color-base-700)] bg-[var(--background)]
flex items-center justify-center text-[var(--color-base-500)]">
<TerminalIcon className="w-[18px] h-[18px]" />
</div>
{/* labels */}
<div className="relative z-10">
<div className="font-mono text-[13px] text-[var(--foreground)]">Terminal / IDE</div>
<div className="font-mono text-[11px] text-[var(--color-base-500)]">Code where you work</div>
</div>
</a>
</div>
</div>Wrapper that toggles visibility:
className={`absolute top-full left-1/2 -translate-x-1/2 pt-3 z-50
transition-all duration-200 origin-top ${
open ? "opacity-100 scale-100 translate-y-0"
: "opacity-0 scale-95 -translate-y-2 pointer-events-none"
}`}4.6 CTA Card
<div className="rounded-lg border border-[var(--color-base-800)]
bg-[var(--color-base-900)] p-8 lg:p-12 max-w-[520px]">
<LogoMark className="w-12 h-12 text-[var(--color-base-400)] mb-6" />
<h2 className="text-[28px] lg:text-[36px] font-normal leading-[110%]
tracking-[-0.02em] text-[var(--foreground)] mb-6">
Ready to build the<br />software of the future?
</h2>
<Button size="sm">Start Building <ArrowIcon className="w-3 h-3" /></Button>
</div>4.7 Footer (inset rounded card, not full-bleed)
<footer className="mx-4 lg:mx-9 mb-4 lg:mb-9 rounded-lg
border border-[var(--color-base-800)] bg-[var(--color-base-900)]
p-8 lg:p-12 min-h-[360px] flex flex-col">Footer layout (top → bottom):
<Badge text="FOOTER" />- Link grid (2-3 columns) with section titles
text-[var(--foreground)] font-normal text-[14px] mb-4, itemsfont-mono text-[14px] text-[var(--color-base-500)] hover:text-[var(--foreground)] transition-colors. - Theme toggle (right-aligned pill).
- Bottom bar:
border-t border-[var(--color-base-800)] pt-8withLogoMarkleft, comma-separated social links center, copyright right — allfont-mono text-[14px].
4.8 Theme Toggle
<div className="flex items-center gap-1 rounded-full
border border-[var(--color-base-700)] p-1">
{/* three circular buttons */}
<button className={`p-2 rounded-full transition-colors ${
theme === mode
? "bg-[var(--color-base-800)] text-[var(--foreground)]"
: "text-[var(--color-base-500)] hover:text-[var(--foreground)]"
}`}>
<MoonIcon className="w-4 h-4" />
</button>
{/* SunIcon, MonitorIcon similarly */}
</div>The system button pairs the monitor icon with a font-mono text-[10px] uppercase "System" label.
Theme switching logic (verbatim):
useEffect(() => {
const root = document.documentElement;
root.classList.add("disable-transitions");
if (theme === "system") {
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
root.setAttribute("data-theme", prefersDark ? "dark" : "light");
} else {
root.setAttribute("data-theme", theme);
}
localStorage.setItem("theme", theme);
setTimeout(() => root.classList.remove("disable-transitions"), 100);
}, [theme]);The .disable-transitions class freezes all transitions during the swap to avoid 300ms color animations on every token.
4.9 Hover Underline (nav links)
Apply hover-underline to nav links for the sliding-underline effect. Do not use Tailwind's hover:underline.
.hover-underline { position: relative; }
.hover-underline::after {
content: ''; position: absolute; bottom: -1px; left: 0;
width: 0; height: 1px; background-color: currentColor;
transition: width 0.3s ease-in-out;
}
.hover-underline:hover::after { width: 100%; }4.10 Logo & LogoMark
Logo is word-based (ACME in Geist Sans, fontWeight 600, letter-spacing -0.02em) inside an SVG. LogoMark is a rounded square with a plus sign. Both accept className and use currentColor — drive color through parent text-[var(--color-base-XXX)].
5. Patterns & Surfaces
5.1 Dashed vs solid borders
- Dashed
border-dashed border-[var(--color-base-700)]→ interactive containers (dropdown panel, hoverable tiles, diagonal accent areas). - Solid
border-[var(--color-base-800)]→ structural surfaces (cards, CTA, footer, terminal chrome, CLI box, tab dividers).
Never mix: a single container uses one or the other.
5.2 Stripe pattern — three uses
- Animated
.btn-stripe-pattern→ buttons + nav tiles (hover-revealed overlay). - Static
.btn-pattern(currentColorlines, no animation) → decorative backgrounds when the parent color should drive the lines. - Inline SVG URL for entire section backgrounds:
<section className="relative overflow-hidden bg-[#fafafa]">
<div
className="absolute inset-0 opacity-[0.11]"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='7' height='7' viewBox='0 0 7 7' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0L7 7' stroke='%23888' stroke-width='1.2' fill='none'/%3E%3C/svg%3E")`,
backgroundSize: '7px 7px'
}}
/>
<div className="relative ...grid...">{/* content */}</div>
</section>The product section inverts to a near-white (#fafafa) surface with 11%-opacity diagonal lines. Always wrap content in a relative child so it sits above the absolute inset-0 pattern.
5.3 Marquee (brand strip)
animate-marquee (30s linear infinite, translateX(0) → translateX(-50%)). To loop seamlessly, render the brand list twice inside an overflow-hidden flex row, then apply animate-marquee. .animate-marquee-reverse flips direction.
5.4 Scrollbar
Custom 8px webkit scrollbar: track var(--color-base-900), thumb var(--color-base-600) (hover -500). Do not restyle.
6. Motion & Animation — signatures
All motion respects prefers-reduced-motion via the rule at the bottom of globals.css which forces animation-duration: 0.01ms and transition-duration: 0.01ms.
6.1 Character Reveal — hero headline
Use the useCharReveal(heroRef) hook. It:
- Splits text into
<span class="char">spans viasplitTextIntoChars. - Sets
opacity:0, y:initialY, color:initialColoron all chars. - On
IntersectionObserverhit, GSAPs them toopacity:1, y:0, color:finalColorwith stagger +ease-reveal(cubic-bezier(0.645, 0.045, 0.355, 1)).
Defaults live in ANIMATION_CONFIG.charReveal. Only the hero <h1> gets this. Section h2s stay static.
6.2 Scramble Text — badges
useScrambleText(textRef, iconRef, { text, triggerOnScroll: true, infinite: true }). Characters cycle through XO01 before resolving. Duration 800ms, reveal begins at 50% progress. Every <Badge> uses this — if you want it static, pass animated={false}.
6.3 Typewriter — CLI command
Interval 25ms/char. Cursor is an 8×16px filled <span> block, visible only while isTyping. Triggered on tab change by clearing state and restarting the interval. Keep the Unix command exactly: curl -fsSL https://app.example.com/cli | sh. Windows: irm https://app.example.com/cli/windows | iex.
6.4 Blink — static cursor (alternative)
.animate-blink { animation: blink 1s step-end infinite; }
.typewriter-cursor {
display: inline-block; width: 2px; height: 1em;
background-color: currentColor; margin-left: 2px;
animation: blink 1.06s step-end infinite;
}6.5 Scale / Slide (menus, modals, nav content)
All 250ms eased. Use the ready-made utilities:
.animate-scale-in/.animate-scale-out.animate-enter-from-left/-right,.animate-exit-to-left/-right.animate-accordion-down/-up(bind to--radix-accordion-content-height).animate-fade-in-up(600ms,--ease-reveal)
Radix NavigationMenu content auto-binds via .nav-menu-content[data-state|data-motion] selectors in globals.css.
6.6 Transitions (the default)
For all non-signature motion: transition-colors duration-150 or transition-all duration-200. Never duration-500 for hovers.
6.7 GPU / perf helpers
.gpu-accelerated→will-change: transform; transform: translateZ(0); backface-visibility: hidden;— apply to elements under GSAP control..invisible-animated→ hides pre-animated elements without layout shift (for GSAPautoAlpha)..paused/.running→ controlanimation-play-state.
7. Iconography
Style: stroke-based <svg viewBox="0 0 24 24">, fill="none", stroke="currentColor", strokeWidth="2", strokeLinecap="round", strokeLinejoin="round". Never fill icons. Size with w-3 h-3 (button), w-4 h-4 (theme toggle, copy), w-[18px] h-[18px] (nav tile icons), w-12 h-12 (section LogoMarks), w-16 h-16 (empty-state LogoMarks).
Icons are inline React components in the page, not imported from lucide-react — keep them inline, named exactly: ChevronIcon, ArrowIcon, CopyIcon, CheckIcon, MoonIcon, SunIcon, MonitorIcon, TerminalIcon, SlackIcon, WebIcon, ProjectIcon, CLIIcon. If a new icon is needed, follow the same 24×24 stroke-2 pattern.
8. File Structure
When scaffolding or extending a Factory-style project, mirror this layout:
app/
globals.css ← tokens, keyframes, utility classes (authoritative)
layout.tsx ← Geist fonts, data-theme="dark"
page.tsx ← landing page with inline icons
{route}/page.tsx
components/
ui/
button.tsx badge.tsx heading.tsx text.tsx index.ts
shared/
logo.tsx theme-toggle.tsx
home/
animated-chart.tsx brands-marquee.tsx
hooks/
useCharReveal.ts useScrambleText.ts useReveal.ts
useReducedMotion.ts useTheme.ts index.ts
lib/
utils.ts ← cn(), splitTextIntoChars, scrambleText, typewriterText, debounce, throttle
gsap.ts ← ANIMATION_CONFIG, revealEase, registered pluginsTooling:
- Next.js App Router (≥ 16), React 19, TypeScript strict.
- Tailwind CSS 4 via
@tailwindcss/postcssand@import "tailwindcss"(notailwind.config.js; the@theme inlineblock inglobals.csswires tokens). - class-variance-authority for every reusable component.
- clsx + tailwind-merge behind
cn(). - @radix-ui/react-slot for
asChild, plus@radix-ui/react-accordion/-navigation-menu/-select. - gsap (core only — paid plugins are replaced by
splitTextIntoChars+scrambleTextinlib/utils.ts). - geist package +
next/font/google.
cn utility (copy verbatim)
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }HTML root attributes
<html lang="en" data-theme="dark" suppressHydrationWarning> — always.
9. Authoring Rules (enforcement)
When generating or editing UI in a Factory-style codebase, do:
- Reference colors via
var(--token)— never#xxxxxxoroklch(...)inline except insideglobals.css. - Use
font-monoby default; only drop to sans for display/section headings. - Prefer pixel-exact
text-[Npx]values from §3. - Set
tracking-[-0.015rem]on small mono labels,tracking-[-0.0175rem]on body,tracking-[-0.04em]on display headlines. - Wrap every section in the 4/12 grid.
- Place one
<Badge>at the top of every major section. - Use dashed borders for interactive containers, solid for structural.
- Use the
btn-stripe-patternoverlay on any hoverable surface that's "primary" in feel. - Always provide dark and light via token inversion — don't ship single-theme UI.
- Always guard motion with
useReducedMotion/ the global media query. - Keep icons inline, 24×24, stroke-2,
currentColor. - On route changes or tab swaps that re-trigger typewriters: reset state then restart intervals (don't just mutate text).
Do not:
- Introduce
bg-gray-900,text-slate-500, arbitrary shadcn tokens, ordark:variants — the theme comes fromdata-theme+ CSS vars, not Tailwind'sdarkclass. - Use
rounded-fullon buttons orrounded-2xlon cards — the system is 4px button / 8px card / 12px panel. - Add drop shadows (except
shadow-2xlon the product-menu dropdown). The look is flat with borders, not elevated. - Import
lucide-react/heroicons. Icons are handcrafted inline SVGs. - Use Tailwind's
containerclass ormax-w-7xl. Grid +mx-autoonly. - Use emoji in UI copy.
- Use bold weights on marketing headlines — they are font-normal. Bold is reserved for the
metricsCVA variant. - Animate anything longer than 300ms except the signature reveals.
- Use red, green, yellow, blue, or purple for UI state. Success/confirm is
--color-accent-100(orange check). Error states, if needed, desaturate to--color-base-400or use a new token you add to:root.
10. Quick-Reference Cheat Sheet
BG page: bg-[var(--background)]
Text primary: text-[var(--foreground)]
Text body: text-[var(--color-base-400)]
Text muted: text-[var(--color-base-500)]
Accent: text-[var(--color-accent-100)]
Card bg: bg-[var(--color-base-900)]
Deep fill: bg-[var(--color-base-1000)]
Solid border: border-[var(--color-base-800)]
Dashed border: border-dashed border-[var(--color-base-700)]
Hero h1: text-[40px] lg:text-[60px] 2xl:text-[72px] font-mono font-normal leading-[100%] tracking-[-0.04em]
Section h2:text-[32px] lg:text-[48px] font-normal leading-[100%] tracking-[-0.04em]
CTA h2: text-[28px] lg:text-[36px] font-normal leading-[110%] tracking-[-0.02em]
Body: font-mono text-[14px] lg:text-[16px] leading-[140%] tracking-[-0.0175rem]
Label: font-mono text-[12px] uppercase tracking-[-0.015rem]
Tab pill: px-2 py-0.5 rounded text-[11px] font-mono uppercase tracking-[-0.01rem]
Button sm: h-[25px] px-3 text-[12px] rounded-[4px]
Button md: h-[31px] px-[14px] text-[12px] rounded-[4px]
Button lg: h-[40px] px-6 text-[14px] rounded-[4px]
Card radius: rounded-lg (8px)
Panel radius:rounded-xl (12px)
Icon stroke: stroke="currentColor" strokeWidth="2" fill="none"
Transition: transition-colors duration-150 (hovers)
transition-all duration-200 (menus)When in doubt, open app/globals.css, app/page.tsx, components/ui/button.tsx, and components/ui/badge.tsx — those four files are the design law. Copy their patterns verbatim before inventing new ones.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

