/** * Clone Architect — Static Site Generator * * Reads catalog/index.json → generates public/index.html * Full site: hero + stats + brands directory with search/filter + compare + how it works * * Usage: npx tsx scripts/generate-site.ts */ import { readFile, writeFile, mkdir } from 'fs/promises'; import { existsSync } from 'fs'; import { join } from 'path'; const ROOT = process.cwd(); const CATALOG_PATH = join(ROOT, 'catalog', 'index.json'); const PUBLIC_DIR = join(ROOT, 'public'); const OUT_PATH = join(PUBLIC_DIR, 'index.html'); interface CatalogBrand { domain: string; hasDesignMd: boolean; hasTokens: boolean; hasScreenshots: boolean; hasFrontmatter: boolean; accent: string; description: string; sizeKb: number; category: string; dark: boolean; font: string; completeness: number; extractedAt: string; } interface Catalog { version: string; count: number; brands: CatalogBrand[]; } function gradeColor(score: number): string { if (score >= 90) return '#4ade80'; // green if (score >= 75) return '#a3e635'; // lime if (score >= 60) return '#facc15'; // yellow return '#f87171'; // red } function gradeLabel(score: number): string { if (score >= 90) return 'A'; if (score >= 75) return 'B'; if (score >= 60) return 'C'; return 'D'; } function domainName(domain: string): string { return domain .replace(/^www\./, '') .replace(/\.(com|app|io|dev|net|org|ai|co|xyz|so|design|new)$/, '') .replace(/^(.)/, c => c.toUpperCase()); } function accentHex(accent: string): string { if (!accent) return '#5e6ad2'; if (accent.startsWith('#')) return accent; // parse rgb() const m = accent.match(/\d+/g); if (m && m.length >= 3) { return '#' + m.slice(0, 3).map(v => parseInt(v).toString(16).padStart(2, '0')).join(''); } return '#5e6ad2'; } function showcaseUrl(domain: string): string { // On the public site, showcases are served from /brands/{domain}/ return `brands/${domain}/`; } async function main() { if (!existsSync(CATALOG_PATH)) { console.error('catalog/index.json not found — run: npx tsx scripts/enrich-catalog.ts'); process.exit(1); } const catalogRaw = await readFile(CATALOG_PATH, 'utf-8'); const catalog: Catalog = JSON.parse(catalogRaw); // Read npm package version (not catalog version) for eyebrow const pkgRaw = await readFile(join(ROOT, 'package.json'), 'utf-8'); const pkgVersion: string = JSON.parse(pkgRaw).version; // Sort by completeness desc const brands = [...catalog.brands].sort((a, b) => (b.completeness || 0) - (a.completeness || 0)); const avgScore = Math.round(brands.reduce((s, b) => s + (b.completeness || 0), 0) / brands.length); const darkCount = brands.filter(b => b.dark).length; const categories = [...new Set(brands.map(b => b.category).filter(Boolean))].sort(); // Inline catalog as JSON for client-side filtering const catalogJson = JSON.stringify(brands); await mkdir(PUBLIC_DIR, { recursive: true }); const html = `
Playwright + getComputedStyle()
→ narrative DESIGN.md + tokens.json + screenshots.
The verifiable alternative to manually-written design docs. No catalog. No $39/brand. MIT.
Each extraction ships with: raw-css.json (ground truth), desktop + mobile screenshots, tokens.json, layout analysis, and a 16-section narrative DESIGN.md.
Respect to the VoltAgent team — getdesign.md proved the format. But it's a static, manually-written catalog of 73 brands. Clone Architect extracts any URL in 90 seconds.
| Feature | getdesign.md | Clone Architect |
|---|---|---|
| Extract any URL | 73 pre-written brands | Unlimited · any URL |
| Source of truth | Manually written | getComputedStyle() — pixel-perfect |
| Raw CSS audit | Not shipped | raw-css.json (360 KB for mistral.ai) |
| Visual proof | None | Desktop 1440px + Mobile 390px screenshots |
| Structured tokens | Markdown prose only | tokens.json — import directly in code |
| Component states | Not captured | Hover / focus / pressed via Playwright interaction |
| Page structure skeleton | Not included | §13 — section order + height + layout type |
| Real copy / CTAs | Not included | §14 — h1/h2/h3/nav/CTAs extracted verbatim |
| Re-extract on demand | Static — gets stale | clone-architect update <domain> |
| Completeness score | No confidence signal | 0-100 grade (A/B/C/D) per extraction |
| Price per brand | $39 custom + 2-day wait | Free · MIT · 90 seconds |
No accounts, no API keys, no waiting. Playwright loads the URL, scrapes computed styles, generates everything locally.
Real browser headlessly loads the URL. getComputedStyle() captures every rendered value — not class names, actual pixels. Screenshots at 1440px and 390px.
CSS custom properties (--color-text-*, --color-bg-*) map to semantic roles. OpenType features, variable font axes, translucent borders all surfaced automatically.
16 sections: Visual Theme, Colors, Typography, Components, Layout, Depth, Motion, Do/Don'ts, Responsive, Agent Guide, Page Skeleton, Copy Library, Asset Inventory, Component Templates, CSS Raw Export, Known Gaps.