@mindstudio-ai/remy 0.1.34 → 0.1.35

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.
Files changed (54) hide show
  1. package/dist/headless.js +578 -393
  2. package/dist/index.js +652 -385
  3. package/dist/prompt/sources/llms.txt +1618 -0
  4. package/dist/prompt/static/instructions.md +1 -1
  5. package/dist/prompt/static/team.md +1 -1
  6. package/dist/subagents/.notes-background-agents.md +60 -48
  7. package/dist/subagents/browserAutomation/prompt.md +14 -11
  8. package/dist/subagents/designExpert/data/sources/dev/index.html +901 -0
  9. package/dist/subagents/designExpert/data/sources/dev/serve.mjs +244 -0
  10. package/dist/subagents/designExpert/data/sources/dev/specimens-fonts.html +126 -0
  11. package/dist/subagents/designExpert/data/sources/dev/specimens-pairings.html +114 -0
  12. package/dist/subagents/designExpert/data/{fonts.json → sources/fonts.json} +0 -97
  13. package/dist/subagents/designExpert/data/sources/inspiration.json +392 -0
  14. package/dist/subagents/designExpert/prompt.md +36 -12
  15. package/dist/subagents/designExpert/prompts/animation.md +14 -6
  16. package/dist/subagents/designExpert/prompts/color.md +25 -5
  17. package/dist/subagents/designExpert/prompts/{icons.md → components.md} +17 -5
  18. package/dist/subagents/designExpert/prompts/frontend-design-notes.md +17 -122
  19. package/dist/subagents/designExpert/prompts/identity.md +15 -61
  20. package/dist/subagents/designExpert/prompts/images.md +35 -10
  21. package/dist/subagents/designExpert/prompts/layout.md +14 -9
  22. package/dist/subagents/designExpert/prompts/typography.md +39 -0
  23. package/package.json +2 -2
  24. package/dist/actions/buildFromInitialSpec.md +0 -15
  25. package/dist/actions/publish.md +0 -12
  26. package/dist/actions/sync.md +0 -19
  27. package/dist/compiled/README.md +0 -100
  28. package/dist/compiled/auth.md +0 -77
  29. package/dist/compiled/design.md +0 -251
  30. package/dist/compiled/dev-and-deploy.md +0 -69
  31. package/dist/compiled/interfaces.md +0 -238
  32. package/dist/compiled/manifest.md +0 -107
  33. package/dist/compiled/media-cdn.md +0 -51
  34. package/dist/compiled/methods.md +0 -225
  35. package/dist/compiled/msfm.md +0 -222
  36. package/dist/compiled/platform.md +0 -105
  37. package/dist/compiled/scenarios.md +0 -103
  38. package/dist/compiled/sdk-actions.md +0 -146
  39. package/dist/compiled/tables.md +0 -263
  40. package/dist/static/authoring.md +0 -101
  41. package/dist/static/coding.md +0 -29
  42. package/dist/static/identity.md +0 -1
  43. package/dist/static/instructions.md +0 -31
  44. package/dist/static/intake.md +0 -44
  45. package/dist/static/lsp.md +0 -4
  46. package/dist/static/projectContext.ts +0 -160
  47. package/dist/static/team.md +0 -39
  48. package/dist/subagents/designExpert/data/inspiration.json +0 -392
  49. package/dist/subagents/designExpert/prompts/instructions.md +0 -18
  50. /package/dist/subagents/designExpert/data/{compile-font-descriptions.sh → sources/compile-font-descriptions.sh} +0 -0
  51. /package/dist/subagents/designExpert/data/{compile-inspiration.sh → sources/compile-inspiration.sh} +0 -0
  52. /package/dist/subagents/designExpert/data/{inspiration.raw.json → sources/inspiration.raw.json} +0 -0
  53. /package/dist/subagents/designExpert/{prompts/tool-prompts → data/sources/prompts}/design-analysis.md +0 -0
  54. /package/dist/subagents/designExpert/{prompts/tool-prompts → data/sources/prompts}/font-analysis.md +0 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Lightweight dev server for visualizing and managing design expert data.
3
+ *
4
+ * Zero dependencies — uses Node's built-in http module.
5
+ * Reads/writes fonts.json and inspiration.json in the parent directory.
6
+ *
7
+ * Usage: node src/subagents/designExpert/data/dev/serve.mjs
8
+ */
9
+
10
+ import { createServer } from 'node:http';
11
+ import { readFileSync, writeFileSync, renameSync } from 'node:fs';
12
+ import { join, dirname } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { execSync } from 'node:child_process';
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ const dataDir = join(__dirname, '..');
18
+ const fontsPath = join(dataDir, 'fonts.json');
19
+ const inspirationPath = join(dataDir, 'inspiration.json');
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // JSON helpers
23
+ // ---------------------------------------------------------------------------
24
+
25
+ function readJson(path) {
26
+ return JSON.parse(readFileSync(path, 'utf-8'));
27
+ }
28
+
29
+ function writeJson(path, data) {
30
+ const tmp = path + '.tmp';
31
+ writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n');
32
+ renameSync(tmp, path);
33
+ }
34
+
35
+ function parseBody(req) {
36
+ return new Promise((resolve, reject) => {
37
+ let body = '';
38
+ req.on('data', (chunk) => (body += chunk));
39
+ req.on('end', () => {
40
+ try {
41
+ resolve(JSON.parse(body));
42
+ } catch {
43
+ reject(new Error('Invalid JSON'));
44
+ }
45
+ });
46
+ });
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Route helpers
51
+ // ---------------------------------------------------------------------------
52
+
53
+ function json(res, data, status = 200) {
54
+ res.writeHead(status, { 'Content-Type': 'application/json' });
55
+ res.end(JSON.stringify(data));
56
+ }
57
+
58
+ function notFound(res) {
59
+ res.writeHead(404);
60
+ res.end('Not found');
61
+ }
62
+
63
+ function error(res, msg, status = 400) {
64
+ json(res, { error: msg }, status);
65
+ }
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Server
69
+ // ---------------------------------------------------------------------------
70
+
71
+ const server = createServer(async (req, res) => {
72
+ const url = new URL(req.url, `http://${req.headers.host}`);
73
+ const path = url.pathname;
74
+ const method = req.method;
75
+
76
+ // CORS for dev
77
+ res.setHeader('Access-Control-Allow-Origin', '*');
78
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
79
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
80
+ if (method === 'OPTIONS') {
81
+ res.writeHead(204);
82
+ res.end();
83
+ return;
84
+ }
85
+
86
+ try {
87
+ // Serve HTML
88
+ if (path === '/' && method === 'GET') {
89
+ const html = readFileSync(join(__dirname, 'index.html'), 'utf-8');
90
+ res.writeHead(200, { 'Content-Type': 'text/html' });
91
+ res.end(html);
92
+ return;
93
+ }
94
+
95
+ if (path === '/specimens/fonts' && method === 'GET') {
96
+ const html = readFileSync(join(__dirname, 'specimens-fonts.html'), 'utf-8');
97
+ res.writeHead(200, { 'Content-Type': 'text/html' });
98
+ res.end(html);
99
+ return;
100
+ }
101
+
102
+ if (path === '/specimens/pairings' && method === 'GET') {
103
+ const html = readFileSync(join(__dirname, 'specimens-pairings.html'), 'utf-8');
104
+ res.writeHead(200, { 'Content-Type': 'text/html' });
105
+ res.end(html);
106
+ return;
107
+ }
108
+
109
+ // --- Fonts API ---
110
+
111
+ if (path === '/api/fonts' && method === 'GET') {
112
+ return json(res, readJson(fontsPath));
113
+ }
114
+
115
+ if (path === '/api/fonts' && method === 'POST') {
116
+ const font = await parseBody(req);
117
+ const data = readJson(fontsPath);
118
+ if (data.fonts.some((f) => f.slug === font.slug)) {
119
+ return error(res, `Font with slug "${font.slug}" already exists`);
120
+ }
121
+ data.fonts.push(font);
122
+ writeJson(fontsPath, data);
123
+ return json(res, { ok: true, count: data.fonts.length }, 201);
124
+ }
125
+
126
+ const fontDeleteMatch = path.match(/^\/api\/fonts\/(.+)$/);
127
+ if (fontDeleteMatch && method === 'DELETE') {
128
+ const slug = decodeURIComponent(fontDeleteMatch[1]);
129
+ const data = readJson(fontsPath);
130
+ const before = data.fonts.length;
131
+ data.fonts = data.fonts.filter((f) => f.slug !== slug);
132
+ // Also remove pairings that reference this font
133
+ data.pairings = data.pairings.filter(
134
+ (p) => p.heading.slug !== slug && p.body.slug !== slug,
135
+ );
136
+ if (data.fonts.length === before) {
137
+ return error(res, `Font "${slug}" not found`, 404);
138
+ }
139
+ writeJson(fontsPath, data);
140
+ return json(res, { ok: true, count: data.fonts.length });
141
+ }
142
+
143
+ // --- Pairings API ---
144
+
145
+ if (path === '/api/pairings' && method === 'POST') {
146
+ const pairing = await parseBody(req);
147
+ const data = readJson(fontsPath);
148
+ data.pairings.push(pairing);
149
+ writeJson(fontsPath, data);
150
+ return json(res, { ok: true, count: data.pairings.length }, 201);
151
+ }
152
+
153
+ const pairingDeleteMatch = path.match(/^\/api\/pairings\/(\d+)$/);
154
+ if (pairingDeleteMatch && method === 'DELETE') {
155
+ const index = parseInt(pairingDeleteMatch[1], 10);
156
+ const data = readJson(fontsPath);
157
+ if (index < 0 || index >= data.pairings.length) {
158
+ return error(res, 'Pairing index out of range', 404);
159
+ }
160
+ data.pairings.splice(index, 1);
161
+ writeJson(fontsPath, data);
162
+ return json(res, { ok: true, count: data.pairings.length });
163
+ }
164
+
165
+ // --- Inspiration API ---
166
+
167
+ if (path === '/api/inspiration' && method === 'GET') {
168
+ return json(res, readJson(inspirationPath));
169
+ }
170
+
171
+ if (path === '/api/inspiration' && method === 'POST') {
172
+ const entry = await parseBody(req);
173
+ const data = readJson(inspirationPath);
174
+ data.images.push(entry);
175
+ writeJson(inspirationPath, data);
176
+ return json(res, { ok: true, count: data.images.length }, 201);
177
+ }
178
+
179
+ const inspirationDeleteMatch = path.match(/^\/api\/inspiration\/(\d+)$/);
180
+ if (inspirationDeleteMatch && method === 'DELETE') {
181
+ const index = parseInt(inspirationDeleteMatch[1], 10);
182
+ const data = readJson(inspirationPath);
183
+ if (index < 0 || index >= data.images.length) {
184
+ return error(res, 'Image index out of range', 404);
185
+ }
186
+ data.images.splice(index, 1);
187
+ writeJson(inspirationPath, data);
188
+ return json(res, { ok: true, count: data.images.length });
189
+ }
190
+
191
+ if (path === '/api/inspiration/analyze' && method === 'POST') {
192
+ const { url } = await parseBody(req);
193
+ if (!url) return error(res, 'url is required');
194
+
195
+ const promptFile = join(dataDir, '..', 'prompts', 'tool-prompts', 'design-analysis.md');
196
+ const prompt = readFileSync(promptFile, 'utf-8').trim();
197
+
198
+ try {
199
+ const result = execSync(
200
+ `mindstudio analyze-image --prompt ${JSON.stringify(prompt)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`,
201
+ { encoding: 'utf-8', timeout: 120000 },
202
+ ).trim();
203
+ return json(res, { url, analysis: result });
204
+ } catch (err) {
205
+ return error(res, `Analysis failed: ${err.message}`, 500);
206
+ }
207
+ }
208
+
209
+ if (path === '/api/inspiration/dedup' && method === 'POST') {
210
+ const data = readJson(inspirationPath);
211
+ const seen = new Set();
212
+ const deduped = [];
213
+ for (const img of data.images) {
214
+ if (!seen.has(img.url)) {
215
+ seen.add(img.url);
216
+ deduped.push(img);
217
+ }
218
+ }
219
+ const removed = data.images.length - deduped.length;
220
+ data.images = deduped;
221
+ writeJson(inspirationPath, data);
222
+ return json(res, { ok: true, removed, count: deduped.length });
223
+ }
224
+
225
+ notFound(res);
226
+ } catch (err) {
227
+ error(res, err.message, 500);
228
+ }
229
+ });
230
+
231
+ const PORT = parseInt(process.env.PORT || '3333', 10);
232
+ server.listen(PORT, () => {
233
+ console.log(`Design data dev tool: http://localhost:${PORT}`);
234
+ // Try to open in browser
235
+ try {
236
+ const cmd =
237
+ process.platform === 'darwin'
238
+ ? 'open'
239
+ : process.platform === 'win32'
240
+ ? 'start'
241
+ : 'xdg-open';
242
+ execSync(`${cmd} http://localhost:${PORT}`, { stdio: 'ignore' });
243
+ } catch {}
244
+ });
@@ -0,0 +1,126 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=1440" />
6
+ <title>Font Specimens</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
11
+ background: #fff;
12
+ color: #000;
13
+ width: 1440px;
14
+ }
15
+ .row {
16
+ height: 200px;
17
+ width: 1440px;
18
+ border-bottom: 1px solid #ddd;
19
+ padding: 24px 40px;
20
+ display: flex;
21
+ flex-direction: column;
22
+ justify-content: center;
23
+ overflow: hidden;
24
+ }
25
+ .name {
26
+ font-size: 12px;
27
+ font-weight: 600;
28
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
29
+ color: #999;
30
+ margin-bottom: 12px;
31
+ text-transform: uppercase;
32
+ letter-spacing: 0.05em;
33
+ }
34
+ .preview-large {
35
+ font-size: 42px;
36
+ line-height: 1.1;
37
+ margin-bottom: 8px;
38
+ white-space: nowrap;
39
+ overflow: hidden;
40
+ text-overflow: ellipsis;
41
+ }
42
+ .preview-medium {
43
+ font-size: 24px;
44
+ line-height: 1.3;
45
+ color: #333;
46
+ margin-bottom: 8px;
47
+ white-space: nowrap;
48
+ overflow: hidden;
49
+ text-overflow: ellipsis;
50
+ }
51
+ .preview-small {
52
+ font-size: 15px;
53
+ line-height: 1.5;
54
+ color: #666;
55
+ white-space: nowrap;
56
+ overflow: hidden;
57
+ text-overflow: ellipsis;
58
+ }
59
+ </style>
60
+ </head>
61
+ <body>
62
+ <script>
63
+ const CSS_URL_PATTERN = 'https://api.fontshare.com/v2/css?f[]={slug}@{weights}&display=swap';
64
+
65
+ function loadFont(font) {
66
+ let cssUrl;
67
+ if (font.source === 'fontshare') {
68
+ cssUrl = CSS_URL_PATTERN
69
+ .replace('{slug}', font.slug)
70
+ .replace('{weights}', font.weights.join(','));
71
+ } else if (font.cssUrl) {
72
+ cssUrl = font.cssUrl;
73
+ } else {
74
+ return;
75
+ }
76
+ const link = document.createElement('link');
77
+ link.rel = 'stylesheet';
78
+ link.href = cssUrl;
79
+ document.head.appendChild(link);
80
+ }
81
+
82
+ async function init() {
83
+ const params = new URLSearchParams(window.location.search);
84
+ const page = parseInt(params.get('page') || '1', 10);
85
+ const perPage = 40;
86
+
87
+ const res = await fetch('/api/fonts');
88
+ const data = await res.json();
89
+ const allFonts = [...data.fonts].sort((a, b) => a.name.localeCompare(b.name));
90
+ const totalPages = Math.ceil(allFonts.length / perPage);
91
+ const fonts = allFonts.slice((page - 1) * perPage, page * perPage);
92
+
93
+ document.title = `Font Specimens — Page ${page}/${totalPages}`;
94
+
95
+ fonts.forEach(f => loadFont(f));
96
+
97
+ // Wait for fonts to load
98
+ await new Promise(r => setTimeout(r, 3000));
99
+
100
+ fonts.forEach(font => {
101
+ const midWeight = font.weights[Math.floor(font.weights.length / 2)] || 400;
102
+ const lightWeight = font.weights[0] || 400;
103
+ const heavyWeight = font.weights[font.weights.length - 1] || 400;
104
+
105
+ const row = document.createElement('div');
106
+ row.className = 'row';
107
+ row.innerHTML = `
108
+ <div class="name">${font.name} &middot; ${font.category} &middot; ${font.source}</div>
109
+ <div class="preview-large" style="font-family: '${font.name}'; font-weight: ${heavyWeight}">
110
+ The quick brown fox jumps over the lazy dog
111
+ </div>
112
+ <div class="preview-medium" style="font-family: '${font.name}'; font-weight: ${midWeight}">
113
+ Pack my box with five dozen liquor jugs &mdash; 0123456789
114
+ </div>
115
+ <div class="preview-small" style="font-family: '${font.name}'; font-weight: ${lightWeight}">
116
+ A wizard's job is to vex chumps quickly in fog. The five boxing wizards jump quickly. How vexingly quick daft zebras jump!
117
+ </div>
118
+ `;
119
+ document.body.appendChild(row);
120
+ });
121
+ }
122
+
123
+ init();
124
+ </script>
125
+ </body>
126
+ </html>
@@ -0,0 +1,114 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=1440" />
6
+ <title>Pairing Specimens</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
11
+ background: #fff;
12
+ color: #000;
13
+ width: 1440px;
14
+ }
15
+ .row {
16
+ height: 240px;
17
+ width: 1440px;
18
+ border-bottom: 1px solid #ddd;
19
+ padding: 24px 40px;
20
+ display: flex;
21
+ flex-direction: column;
22
+ justify-content: center;
23
+ overflow: hidden;
24
+ }
25
+ .meta {
26
+ font-size: 12px;
27
+ font-weight: 600;
28
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
29
+ color: #999;
30
+ margin-bottom: 16px;
31
+ text-transform: uppercase;
32
+ letter-spacing: 0.05em;
33
+ }
34
+ .heading {
35
+ font-size: 48px;
36
+ line-height: 1.1;
37
+ margin-bottom: 12px;
38
+ }
39
+ .body {
40
+ font-size: 17px;
41
+ line-height: 1.6;
42
+ color: #444;
43
+ max-width: 800px;
44
+ }
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <script>
49
+ const CSS_URL_PATTERN = 'https://api.fontshare.com/v2/css?f[]={slug}@{weights}&display=swap';
50
+ const loaded = new Set();
51
+
52
+ function loadFontBySlug(slug, fonts) {
53
+ if (loaded.has(slug)) return;
54
+ loaded.add(slug);
55
+ const font = fonts.find(f => f.slug === slug);
56
+ if (!font) return;
57
+ let cssUrl;
58
+ if (font.source === 'fontshare') {
59
+ cssUrl = CSS_URL_PATTERN
60
+ .replace('{slug}', font.slug)
61
+ .replace('{weights}', font.weights.join(','));
62
+ } else if (font.cssUrl) {
63
+ cssUrl = font.cssUrl;
64
+ } else {
65
+ return;
66
+ }
67
+ const link = document.createElement('link');
68
+ link.rel = 'stylesheet';
69
+ link.href = cssUrl;
70
+ document.head.appendChild(link);
71
+ }
72
+
73
+ async function init() {
74
+ const params = new URLSearchParams(window.location.search);
75
+ const page = parseInt(params.get('page') || '1', 10);
76
+ const perPage = 33;
77
+
78
+ const res = await fetch('/api/fonts');
79
+ const data = await res.json();
80
+ const fonts = data.fonts;
81
+ const allPairings = data.pairings;
82
+ const totalPages = Math.ceil(allPairings.length / perPage);
83
+ const pairings = allPairings.slice((page - 1) * perPage, page * perPage);
84
+
85
+ document.title = `Pairing Specimens — Page ${page}/${totalPages}`;
86
+
87
+ pairings.forEach(p => {
88
+ loadFontBySlug(p.heading.slug, fonts);
89
+ loadFontBySlug(p.body.slug, fonts);
90
+ });
91
+
92
+ // Wait for fonts to load
93
+ await new Promise(r => setTimeout(r, 3000));
94
+
95
+ pairings.forEach(p => {
96
+ const row = document.createElement('div');
97
+ row.className = 'row';
98
+ row.innerHTML = `
99
+ <div class="meta">${p.heading.font} (${p.heading.weight}) + ${p.body.font} (${p.body.weight})</div>
100
+ <div class="heading" style="font-family: '${p.heading.font}'; font-weight: ${p.heading.weight}">
101
+ The quick brown fox jumps over the lazy dog
102
+ </div>
103
+ <div class="body" style="font-family: '${p.body.font}'; font-weight: ${p.body.weight}">
104
+ Good design is as little design as possible. Less, but better — because it concentrates on the essential aspects, and the products are not burdened with non-essentials. Back to purity, back to simplicity.
105
+ </div>
106
+ `;
107
+ document.body.appendChild(row);
108
+ });
109
+ }
110
+
111
+ init();
112
+ </script>
113
+ </body>
114
+ </html>
@@ -497,41 +497,6 @@
497
497
  "source": "fontshare",
498
498
  "description": "This sans-serif displays monolinear stroke weight with no stress angle and a geometric construction evident in the circular bowls of letters like 'o' and 'd'. The typeface features a high x-height relative to cap height, regular width proportions, and moderately open apertures in characters such as 'c', 'e', and 'a'. Terminals are primarily flat-cut, the 'g' uses a double-story form, and the overall structure shows rationalized geometric forms with subtle optical corrections rather than pure geometric circles."
499
499
  },
500
- {
501
- "name": "Outfit",
502
- "slug": "outfit",
503
- "category": "Sans",
504
- "variable": true,
505
- "weights": [
506
- 100,
507
- 200,
508
- 300,
509
- 400,
510
- 500,
511
- 600,
512
- 700,
513
- 800,
514
- 900
515
- ],
516
- "italics": false,
517
- "source": "fontshare",
518
- "description": "This grotesque sans-serif displays monolinear stroke weight with no stress angle and flat-cut terminals throughout. The typeface features a high x-height relative to cap height, regular width proportions, and moderately open apertures in letters like 'e' and 'a'. Notable characteristics include a single-story 'a', geometric construction with slightly squared curves, and consistent stroke endings that create a sturdy, utilitarian appearance."
519
- },
520
- {
521
- "name": "Lora",
522
- "slug": "lora",
523
- "category": "Serif",
524
- "variable": true,
525
- "weights": [
526
- 400,
527
- 500,
528
- 600,
529
- 700
530
- ],
531
- "italics": true,
532
- "source": "fontshare",
533
- "description": "This serif typeface exhibits moderate stroke contrast with a vertical stress and features thick, unbracketed slab serifs with squared terminals. The construction shows a high x-height relative to cap height, regular width proportions, and moderately open apertures in letters like 'e' and 'a'. Notable characteristics include a double-story 'g', robust slab terminals on ascenders and descenders, and a rational, geometric construction typical of Egyptian or Clarendon-style serifs."
534
- },
535
500
  {
536
501
  "name": "Plus Jakarta Sans",
537
502
  "slug": "plus-jakarta-sans",
@@ -841,22 +806,6 @@
841
806
  "source": "fontshare",
842
807
  "description": "This sans-serif typeface exhibits monolinear stroke weight with no stress angle and a geometric construction characterized by circular bowls and consistent proportions. The apertures are moderately open in letters like 'c' and 'e', while terminals are flat-cut and perpendicular to stroke direction. The x-height is relatively high compared to the cap height, and the overall width is regular with a double-story 'g' and single-story 'a', typical of geometric sans-serifs with rationalized, optically-corrected forms."
843
808
  },
844
- {
845
- "name": "Space Grotesk",
846
- "slug": "space-grotesk",
847
- "category": "Sans",
848
- "variable": true,
849
- "weights": [
850
- 300,
851
- 400,
852
- 500,
853
- 600,
854
- 700
855
- ],
856
- "italics": false,
857
- "source": "fontshare",
858
- "description": "This grotesque sans-serif displays monolinear stroke weight with no stress angle and flat-cut terminals throughout. The typeface features a high x-height relative to cap height, moderate to open apertures in letters like 'c', 'e', and 'a', and regular width proportions. Notable glyphs include a single-story 'a' and 'g', with the construction showing rationalized, geometric tendencies typical of neo-grotesque design, particularly visible in the circular bowls and optically-corrected curves."
859
- },
860
809
  {
861
810
  "name": "Pramukh Rounded",
862
811
  "slug": "pramukh-rounded",
@@ -1344,28 +1293,6 @@
1344
1293
  "cssUrl": "https://fonts.cdnfonts.com/css/reglo",
1345
1294
  "description": "This sans-serif typeface exhibits monolinear stroke weight with no stress angle and flat-cut terminals throughout. The letterforms display a condensed width with a high x-height relative to the cap height, and apertures in c, e, and s are moderately closed. The construction is grotesque with distinctive features including a single-story 'a', closed aperture in the 'g', and compact proportions that emphasize vertical compression over horizontal space."
1346
1295
  },
1347
- {
1348
- "name": "Poppins",
1349
- "slug": "poppins",
1350
- "category": "Sans",
1351
- "source": "google-fonts",
1352
- "googleFontsFamily": "Poppins",
1353
- "weights": [
1354
- 100,
1355
- 200,
1356
- 300,
1357
- 400,
1358
- 500,
1359
- 600,
1360
- 700,
1361
- 800,
1362
- 900
1363
- ],
1364
- "italics": true,
1365
- "cssUrl": "https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap",
1366
- "variable": true,
1367
- "description": "This sans-serif typeface exhibits monolinear stroke weight with no stress angle and a geometric construction characterized by circular bowls and optically-corrected curves. The letterforms feature closed apertures in characters like 'e' and 'a', with a single-story 'a' and 'g', and terminals are flat-cut perpendicular to stroke direction. The x-height is high relative to cap height, proportions are regular width, and distinctive features include perfectly circular dots on 'i' and 'j', and uniform stroke terminals throughout."
1368
- },
1369
1296
  {
1370
1297
  "name": "Oswald",
1371
1298
  "slug": "oswald",
@@ -1915,18 +1842,6 @@
1915
1842
  "weight": 400
1916
1843
  }
1917
1844
  },
1918
- {
1919
- "heading": {
1920
- "font": "Melodrama",
1921
- "slug": "melodrama",
1922
- "weight": 500
1923
- },
1924
- "body": {
1925
- "font": "Nunito",
1926
- "slug": "nunito",
1927
- "weight": 400
1928
- }
1929
- },
1930
1845
  {
1931
1846
  "heading": {
1932
1847
  "font": "Tanker",
@@ -2419,18 +2334,6 @@
2419
2334
  "weight": 400
2420
2335
  }
2421
2336
  },
2422
- {
2423
- "heading": {
2424
- "font": "Syne",
2425
- "slug": "syne",
2426
- "weight": 700
2427
- },
2428
- "body": {
2429
- "font": "Plus Jakarta Sans",
2430
- "slug": "plus-jakarta-sans",
2431
- "weight": 400
2432
- }
2433
- },
2434
2337
  {
2435
2338
  "heading": {
2436
2339
  "font": "Libre Baskerville",