prism-design 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +292 -0
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/bin/clone-architect.mjs +476 -0
- package/bin/prism.mjs +467 -0
- package/catalog/index.json +1155 -0
- package/extractions/airbnb.com/DESIGN.md +1068 -0
- package/extractions/airbnb.com/tokens.json +507 -0
- package/extractions/attio.com/DESIGN.md +1295 -0
- package/extractions/attio.com/tokens.json +438 -0
- package/extractions/auroxdashboard.com/DESIGN.md +724 -0
- package/extractions/auroxdashboard.com/tokens.json +195 -0
- package/extractions/careerexplorer.com/DESIGN.md +1178 -0
- package/extractions/careerexplorer.com/tokens.json +141 -0
- package/extractions/chance.co/DESIGN.md +1209 -0
- package/extractions/chance.co/tokens.json +160 -0
- package/extractions/choisis-ton-avenir.com/DESIGN.md +1265 -0
- package/extractions/choisis-ton-avenir.com/tokens.json +227 -0
- package/extractions/example.com/DESIGN.md +436 -0
- package/extractions/example.com/tokens.json +91 -0
- package/extractions/getdesign.md/DESIGN.md +1009 -0
- package/extractions/getdesign.md/tokens.json +219 -0
- package/extractions/github.com/DESIGN.md +1130 -0
- package/extractions/github.com/tokens.json +2092 -0
- package/extractions/hello-charly.com/DESIGN.md +1146 -0
- package/extractions/hello-charly.com/tokens.json +322 -0
- package/extractions/hyperliquid.xyz/DESIGN.md +779 -0
- package/extractions/hyperliquid.xyz/tokens.json +598 -0
- package/extractions/instagram.com/DESIGN.md +996 -0
- package/extractions/instagram.com/tokens.json +1240 -0
- package/extractions/jobirl.com/DESIGN.md +1160 -0
- package/extractions/jobirl.com/tokens.json +139 -0
- package/extractions/life360.com/DESIGN.md +1133 -0
- package/extractions/life360.com/tokens.json +491 -0
- package/extractions/lifesum.com/DESIGN.md +965 -0
- package/extractions/lifesum.com/tokens.json +170 -0
- package/extractions/linear.app/DESIGN.md +1301 -0
- package/extractions/linear.app/tokens.json +732 -0
- package/extractions/mavoie.org/DESIGN.md +1148 -0
- package/extractions/mavoie.org/tokens.json +128 -0
- package/extractions/miro.com/DESIGN.md +1237 -0
- package/extractions/miro.com/tokens.json +401 -0
- package/extractions/notion.so/DESIGN.md +1319 -0
- package/extractions/notion.so/tokens.json +906 -0
- package/extractions/onetonline.org/DESIGN.md +909 -0
- package/extractions/onetonline.org/tokens.json +280 -0
- package/extractions/posthog.com/DESIGN.md +1024 -0
- package/extractions/posthog.com/tokens.json +197 -0
- package/extractions/revolut.com/DESIGN.md +1080 -0
- package/extractions/revolut.com/tokens.json +401 -0
- package/extractions/stripe.com/DESIGN.md +1272 -0
- package/extractions/stripe.com/tokens.json +794 -0
- package/extractions/switchcollective.com/DESIGN.md +1040 -0
- package/extractions/switchcollective.com/tokens.json +98 -0
- package/extractions/truity.com/DESIGN.md +970 -0
- package/extractions/truity.com/tokens.json +166 -0
- package/extractions/uniquekicks.be/DESIGN.md +1171 -0
- package/extractions/uniquekicks.be/tokens.json +237 -0
- package/package.json +122 -0
- package/scripts/analyze.ts +281 -0
- package/scripts/bank-register.ts +379 -0
- package/scripts/bank.ts +374 -0
- package/scripts/browser-stealth.ts +189 -0
- package/scripts/clone.ts +198 -0
- package/scripts/compare-vs-gd-final.ts +273 -0
- package/scripts/compare-vs-gd.ts +269 -0
- package/scripts/compare.ts +405 -0
- package/scripts/deploy-site.ts +181 -0
- package/scripts/diff-snapshots.ts +340 -0
- package/scripts/enrich-catalog.ts +212 -0
- package/scripts/extract.ts +2038 -0
- package/scripts/extractors/advanced.ts +524 -0
- package/scripts/extractors/widgets.ts +711 -0
- package/scripts/generate-design-md.ts +5775 -0
- package/scripts/generate-final-pdf.ts +274 -0
- package/scripts/generate-og-image.ts +87 -0
- package/scripts/generate-showcase.ts +1588 -0
- package/scripts/generate-site.ts +847 -0
- package/scripts/mass-extract.sh +91 -0
- package/scripts/post-process-all.sh +55 -0
- package/scripts/regen-catalog.ts +203 -0
- package/scripts/shared/cache.ts +149 -0
- package/scripts/shared/css-helpers.ts +263 -0
- package/scripts/shared/logger.ts +57 -0
- package/scripts/shared/named-colors.ts +355 -0
- package/scripts/shared/types.ts +220 -0
- package/scripts/sync-catalog.ts +105 -0
- package/scripts/tokenize.ts +988 -0
- package/templates/layout-template.md +52 -0
- package/templates/tokens-template.json +34 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prism — Catalog Enrichment
|
|
3
|
+
*
|
|
4
|
+
* Reads catalog/index.json and enriches each entry with:
|
|
5
|
+
* - category : brand category classification
|
|
6
|
+
* - dark : dark-mode site flag
|
|
7
|
+
* - hasFrontmatter: DESIGN.md has YAML ---…--- block
|
|
8
|
+
* - hasScreenshots: screenshots/ folder has files
|
|
9
|
+
* - font : primary font extracted
|
|
10
|
+
* - completeness : 0-100 score from YAML frontmatter
|
|
11
|
+
* - extractedAt : ISO date from YAML frontmatter
|
|
12
|
+
*
|
|
13
|
+
* Usage: npx tsx scripts/enrich-catalog.ts
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
17
|
+
import { existsSync, readdirSync, statSync } from 'fs';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
|
|
20
|
+
const ROOT = join(process.cwd());
|
|
21
|
+
const EXTRACTIONS = join(ROOT, 'extractions');
|
|
22
|
+
const CATALOG_PATH = join(ROOT, 'catalog', 'index.json');
|
|
23
|
+
|
|
24
|
+
// ── Category classifier ─────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const CATEGORY_RULES: Array<{ pattern: RegExp; label: string }> = [
|
|
27
|
+
{ pattern: /binance|deribit|hyperliquid|coinbase|kraken|bybit|okx|ftx|bitfinex|gemini/i, label: 'Crypto / Trading' },
|
|
28
|
+
{ pattern: /stripe|paypal|adyen|braintree|square|klarna|checkout|revolut|wise|mercury/i, label: 'Fintech / Payments' },
|
|
29
|
+
{ pattern: /airbnb|booking|expedia|hotels|tripadvisor|vrbo|hostelworld/i, label: 'Travel & Hospitality' },
|
|
30
|
+
{ pattern: /acnestudios|addicts|nike|adidas|zara|hm\.|farfetch|ssense|mrporter|matchesfashion|sneaks/i, label: 'Fashion & Retail' },
|
|
31
|
+
{ pattern: /shopify|commerce|magento|woocommerce|dimension|myshopify/i, label: 'E-commerce' },
|
|
32
|
+
{ pattern: /linear|notion|akiflow|clickup|basecamp|asana|monday|todoist|craft\.do|cron\./i, label: 'Productivity & PM' },
|
|
33
|
+
{ pattern: /figma|framer|sketch|webflow|canva|pitch|miro|loom|comet/i, label: 'Design & Creative Tools' },
|
|
34
|
+
{ pattern: /cursor|bolt\.new|replit|github|gitlab|vercel|netlify|railway|render|supabase|neon/i, label: 'Dev Tools & Cloud' },
|
|
35
|
+
{ pattern: /arc\.net|browser|firefox|chrome/i, label: 'Browser / Platform' },
|
|
36
|
+
{ pattern: /clerk|auth0|okta|jumpcloud|1password|lastpass/i, label: 'Auth & Security' },
|
|
37
|
+
{ pattern: /fabric|attio|hubspot|salesforce|intercom|zendesk|pipedrive/i, label: 'CRM & Business' },
|
|
38
|
+
{ pattern: /ps-apps|ps-tools|aurax|dimension\.ps|lola/i, label: 'PS-Tools Projects' },
|
|
39
|
+
{ pattern: /example\.com/i, label: 'Test / Example' },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
function classifyBrand(domain: string): string {
|
|
43
|
+
for (const { pattern, label } of CATEGORY_RULES) {
|
|
44
|
+
if (pattern.test(domain)) return label;
|
|
45
|
+
}
|
|
46
|
+
return 'SaaS / Product';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Parse YAML frontmatter from DESIGN.md ───────────────────────────
|
|
50
|
+
|
|
51
|
+
interface FrontmatterData {
|
|
52
|
+
hasFrontmatter: boolean;
|
|
53
|
+
completeness: number | null;
|
|
54
|
+
extractedAt: string | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseFrontmatter(content: string): FrontmatterData {
|
|
58
|
+
if (!content.startsWith('---')) {
|
|
59
|
+
return { hasFrontmatter: false, completeness: null, extractedAt: null };
|
|
60
|
+
}
|
|
61
|
+
const end = content.indexOf('\n---\n', 4);
|
|
62
|
+
if (end === -1) {
|
|
63
|
+
return { hasFrontmatter: false, completeness: null, extractedAt: null };
|
|
64
|
+
}
|
|
65
|
+
const block = content.slice(4, end);
|
|
66
|
+
|
|
67
|
+
const completenessMatch = block.match(/^completeness:\s*(\d+)/m);
|
|
68
|
+
const extractedAtMatch = block.match(/^extracted_at:\s*"?([^"\n]+)"?/m);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
hasFrontmatter: true,
|
|
72
|
+
completeness: completenessMatch ? parseInt(completenessMatch[1]) : null,
|
|
73
|
+
extractedAt: extractedAtMatch ? extractedAtMatch[1].trim() : null,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Extract primary font from tokens.json ───────────────────────────
|
|
78
|
+
|
|
79
|
+
function extractFont(tokens: any): string | null {
|
|
80
|
+
const fp = tokens?.typography?.fontFamily?.primary;
|
|
81
|
+
if (fp) return fp;
|
|
82
|
+
const bodyFont = tokens?.typography?.body?.fontFamily;
|
|
83
|
+
if (bodyFont) return bodyFont;
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Detect dark site from tokens.json ───────────────────────────────
|
|
88
|
+
|
|
89
|
+
function isDarkSite(tokens: any): boolean {
|
|
90
|
+
const bg = tokens?.colors?.background?.primary || '';
|
|
91
|
+
// Parse rgb(r,g,b) or hex
|
|
92
|
+
const hexMatch = bg.match(/^#([0-9a-f]{6})/i);
|
|
93
|
+
if (hexMatch) {
|
|
94
|
+
const r = parseInt(hexMatch[1].slice(0, 2), 16);
|
|
95
|
+
const g = parseInt(hexMatch[1].slice(2, 4), 16);
|
|
96
|
+
const b = parseInt(hexMatch[1].slice(4, 6), 16);
|
|
97
|
+
const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
98
|
+
return lum < 0.3;
|
|
99
|
+
}
|
|
100
|
+
const rgbMatch = bg.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
|
|
101
|
+
if (rgbMatch) {
|
|
102
|
+
const [, r, g, b] = rgbMatch.map(Number);
|
|
103
|
+
const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
104
|
+
return lum < 0.3;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── Count screenshots ────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
function hasScreenshotsDir(domain: string): boolean {
|
|
112
|
+
const screenshotsDir = join(EXTRACTIONS, domain, 'screenshots');
|
|
113
|
+
if (!existsSync(screenshotsDir)) return false;
|
|
114
|
+
try {
|
|
115
|
+
const files = readdirSync(screenshotsDir).filter(f => /\.(png|jpg|jpeg|webp)$/i.test(f));
|
|
116
|
+
return files.length > 0;
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Main ─────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
async function main() {
|
|
125
|
+
console.log('🔧 Enriching catalog/index.json...\n');
|
|
126
|
+
|
|
127
|
+
const raw = await readFile(CATALOG_PATH, 'utf-8');
|
|
128
|
+
const catalog = JSON.parse(raw);
|
|
129
|
+
|
|
130
|
+
let enriched = 0;
|
|
131
|
+
let skipped = 0;
|
|
132
|
+
let withScore = 0;
|
|
133
|
+
let withFrontmatter = 0;
|
|
134
|
+
|
|
135
|
+
for (const brand of catalog.brands) {
|
|
136
|
+
const domain: string = brand.domain;
|
|
137
|
+
const extractionDir = join(EXTRACTIONS, domain);
|
|
138
|
+
const designMdPath = join(extractionDir, 'DESIGN.md');
|
|
139
|
+
const tokensPath = join(extractionDir, 'tokens.json');
|
|
140
|
+
|
|
141
|
+
// Category (always computable)
|
|
142
|
+
brand.category = classifyBrand(domain);
|
|
143
|
+
|
|
144
|
+
// Screenshots
|
|
145
|
+
brand.hasScreenshots = hasScreenshotsDir(domain);
|
|
146
|
+
|
|
147
|
+
// Tokens-derived fields
|
|
148
|
+
if (existsSync(tokensPath)) {
|
|
149
|
+
try {
|
|
150
|
+
const tokens = JSON.parse(await readFile(tokensPath, 'utf-8'));
|
|
151
|
+
brand.dark = isDarkSite(tokens);
|
|
152
|
+
const font = extractFont(tokens);
|
|
153
|
+
if (font) brand.font = font;
|
|
154
|
+
} catch {
|
|
155
|
+
// tokens.json malformed — skip token fields
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// DESIGN.md frontmatter fields
|
|
160
|
+
if (existsSync(designMdPath)) {
|
|
161
|
+
try {
|
|
162
|
+
const content = await readFile(designMdPath, 'utf-8');
|
|
163
|
+
const fm = parseFrontmatter(content);
|
|
164
|
+
brand.hasFrontmatter = fm.hasFrontmatter;
|
|
165
|
+
if (fm.hasFrontmatter) withFrontmatter++;
|
|
166
|
+
|
|
167
|
+
if (fm.completeness !== null) {
|
|
168
|
+
brand.completeness = fm.completeness;
|
|
169
|
+
withScore++;
|
|
170
|
+
}
|
|
171
|
+
if (fm.extractedAt) {
|
|
172
|
+
brand.extractedAt = fm.extractedAt;
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
brand.hasFrontmatter = false;
|
|
176
|
+
}
|
|
177
|
+
enriched++;
|
|
178
|
+
} else {
|
|
179
|
+
skipped++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Update metadata
|
|
184
|
+
catalog.enrichedAt = new Date().toISOString();
|
|
185
|
+
catalog.version = bumpVersion(catalog.version || '1.0.0');
|
|
186
|
+
|
|
187
|
+
await writeFile(CATALOG_PATH, JSON.stringify(catalog, null, 2));
|
|
188
|
+
|
|
189
|
+
console.log(`✅ Catalog enriched and saved to catalog/index.json`);
|
|
190
|
+
console.log(` ${enriched} brands processed | ${skipped} skipped (no DESIGN.md)`);
|
|
191
|
+
console.log(` ${withFrontmatter} have YAML frontmatter | ${withScore} have completeness scores`);
|
|
192
|
+
console.log(` Category distribution:`);
|
|
193
|
+
|
|
194
|
+
const catCounts = new Map<string, number>();
|
|
195
|
+
for (const b of catalog.brands) {
|
|
196
|
+
catCounts.set(b.category, (catCounts.get(b.category) || 0) + 1);
|
|
197
|
+
}
|
|
198
|
+
for (const [cat, count] of [...catCounts.entries()].sort((a, b) => b[1] - a[1])) {
|
|
199
|
+
console.log(` ${cat}: ${count}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function bumpVersion(v: string): string {
|
|
204
|
+
const parts = v.split('.').map(Number);
|
|
205
|
+
parts[2] = (parts[2] || 0) + 1;
|
|
206
|
+
return parts.join('.');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
main().catch(err => {
|
|
210
|
+
console.error('Error enriching catalog:', err);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
});
|