agentleFS
sssemilsssemil/SKILLS/skills/misc/factory-design/SKILL.md

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

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
  1. Very specific:

    Dark-first, tokenized: Always author in dark mode first.

    line 15
  2. Distinctive:

    If a design decision isn't covered below, err toward: mono, dashed, 8-12px radius, var(--color-base-XXX), tight tracking, stripe on hover.

    line 20
  3. Very specific:

    All tokens live in app/globals.css under :root, html / [data-theme="dark"], and [data-theme="light"].

    line 26
  4. Very specific:

    Every <section> and the header use this container:

    line 133
  5. Very specific:

    Never container or max-w-7xl.

    line 141
  6. Very specific:

    Always use <br /> to enforce specific line breaks in marketing copy — never let the browser wrap.

    line 211
  7. Very specific:

    Use for: nav links, tab labels, section eyebrow labels, terminal window titles ("01 — TERMINAL / IDE" at text-[12px]).

    line 222
  8. Distinctive:

    Every primary/secondary button renders an absolute overlay that contains an animated 45° stripe pattern, visible only on hover / …

    line 299
  9. Very specific:

    Use one Badge at the top of every major section (PLATFORM, PRODUCT, FOOTER, etc.).

    line 360
  10. Distinctive:

    Never mix: a single container uses one or the other.

    line 556
  11. Very specific:

    All motion respects prefers-reduced-motion via the rule at the bottom of globals.css which forces animation-duration: 0.01ms and …

    line 591
  12. Very specific:

    Use the useCharReveal(heroRef) hook.

    line 595
  13. Distinctive:

    All 250ms eased. Use the ready-made utilities:

    line 623
  14. Distinctive:

    When scaffolding or extending a Factory-style project, mirror this layout:

    line 653
  15. Distinctive:

    When generating or editing UI in a Factory-style codebase, do:

    line 701
  16. Very specific:

    Use font-mono by default; only drop to sans for display/section headings.

    line 704
  17. Very specific:

    Prefer pixel-exact text-[Npx] values from §3.

    line 705
  18. Very specific:

    Place one <Badge> at the top of every major section.

    line 708
  19. Distinctive:

    Use dashed borders for interactive containers, solid for structural.

    line 709
  20. Very specific:

    Use the btn-stripe-pattern overlay on any hoverable surface that's "primary" in feel.

    line 710
  21. Distinctive:

    Always provide dark and light via token inversion — don't ship single-theme UI.

    line 711
  22. Very specific:

    Always guard motion with useReducedMotion / the global media query.

    line 712
  23. Very specific:

    Keep icons inline, 24×24, stroke-2, currentColor.

    line 713
  24. Very specific:

    Use rounded-full on buttons or rounded-2xl on cards — the system is 4px button / 8px card / 12px panel.

    line 719
  25. Very specific:

    Add drop shadows (except shadow-2xl on the product-menu dropdown).

    line 720
  26. Very specific:

    Import lucide-react / heroicons.

    line 721
  27. Very specific:

    Use Tailwind's container class or max-w-7xl.

    line 722
  28. Distinctive:

    Use emoji in UI copy.

    line 723
  29. Very specific:

    Use bold weights on marketing headlines — they are font-normal.

    line 724
  30. Very specific:

    Use red, green, yellow, blue, or purple for UI state.

    line 726
  31. Very specific:

    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 …

    line 761

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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:

  1. Monospace is the voice. Geist Mono is the default typeface for ~90% of text (nav, labels, body paragraphs, buttons, badges, code). Geist Sans appears only for display/marketing headings and section titles — never for UI chrome.
  2. Dark-first, tokenized. Always author in dark mode first. Never hardcode colors — always var(--color-...). Light theme is the mirror, not a rewrite.
  3. 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.
  4. 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.
  5. 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-200 color 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.
  • ::selection is always accent-100 on dark-primary.
  • Nav links hover to text-[var(--color-accent-100)] via transition-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 container or max-w-7xl. Horizontal centering comes from mx-auto + the grid itself.

Column allocations (memorize these):

BlockMobileDesktop
Hero left (copy + CLI box)col-span-4col-span-6
Hero right (graphic)hiddencol-span-6
Product copycol-span-4col-span-5
Product mockupcol-span-4 mt-8col-span-7 mt-0
CLI install boxmax-width max-w-[480px] inside colsame
CTA cardmax-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:

RoleClass
Primary bodytext-[var(--color-base-400)]
Muted bodytext-[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]">&gt;</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:

variantsizesweight
display40 / 56 / 72semibold
heading-130 / 36 / 48semibold
heading-224 / 28 / 36semibold
subheading-118 / 20 / 24medium
subheading-216 / 18 / 20medium
subheading-314 / 16medium
metrics48 / 64 / 80bold

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-4

Variants (exact classes):

variantclasses
primary (default)bg-[var(--btn-primary-bg)] text-[var(--btn-primary-text)] border-[var(--color-base-700)] hover:opacity-80 overflow-hidden rounded-[4px]
secondarybg-[var(--btn-secondary-bg)] text-[var(--btn-secondary-text)] border-transparent hover:opacity-80 overflow-hidden rounded-[4px]
ghostbg-transparent text-[var(--foreground)] hover:bg-[var(--color-base-900)] hover:text-[var(--color-accent-100)]
linkbg-transparent text-[var(--foreground)] hover:text-[var(--color-accent-100)] underline-offset-4 hover:underline
outlineborder-[var(--color-base-700)] text-[var(--foreground)] hover:border-[var(--color-accent-100)] hover:text-[var(--color-accent-100)]

Sizes (exact heights):

sizeclasses
smh-[25px] px-3 text-[12px] tracking-[-0.015rem]
defaulth-[31px] px-[14px] text-[12px] tracking-[-0.015rem]
lgh-[40px] px-6 text-[14px] tracking-[-0.0175rem]
iconh-[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-wider

Variants:

  • 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 in text-[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]">&gt;</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):

  1. <Badge text="FOOTER" />
  2. Link grid (2-3 columns) with section titles text-[var(--foreground)] font-normal text-[14px] mb-4, items font-mono text-[14px] text-[var(--color-base-500)] hover:text-[var(--foreground)] transition-colors.
  3. Theme toggle (right-aligned pill).
  4. Bottom bar: border-t border-[var(--color-base-800)] pt-8 with LogoMark left, comma-separated social links center, copyright right — all font-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

  1. Animated .btn-stripe-pattern → buttons + nav tiles (hover-revealed overlay).
  2. Static .btn-pattern (currentColor lines, no animation) → decorative backgrounds when the parent color should drive the lines.
  3. 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:

  1. Splits text into <span class="char"> spans via splitTextIntoChars.
  2. Sets opacity:0, y:initialY, color:initialColor on all chars.
  3. On IntersectionObserver hit, GSAPs them to opacity:1, y:0, color:finalColor with 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 GSAP autoAlpha).
  • .paused / .running → control animation-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 plugins

Tooling:

  • Next.js App Router (≥ 16), React 19, TypeScript strict.
  • Tailwind CSS 4 via @tailwindcss/postcss and @import "tailwindcss" (no tailwind.config.js; the @theme inline block in globals.css wires 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 + scrambleText in lib/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 #xxxxxx or oklch(...) inline except inside globals.css.
  • Use font-mono by 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-pattern overlay 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, or dark: variants — the theme comes from data-theme + CSS vars, not Tailwind's dark class.
  • Use rounded-full on buttons or rounded-2xl on cards — the system is 4px button / 8px card / 12px panel.
  • Add drop shadows (except shadow-2xl on the product-menu dropdown). The look is flat with borders, not elevated.
  • Import lucide-react / heroicons. Icons are handcrafted inline SVGs.
  • Use Tailwind's container class or max-w-7xl. Grid + mx-auto only.
  • Use emoji in UI copy.
  • Use bold weights on marketing headlines — they are font-normal. Bold is reserved for the metrics CVA 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-400 or 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.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.