zark-design 1.0.0 → 3.0.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.
Files changed (66) hide show
  1. package/bin/cli.js +364 -90
  2. package/package.json +2 -2
  3. package/templates/README.md.hbs +264 -0
  4. package/templates/_shared/ASSETS-README.md.hbs +39 -0
  5. package/templates/_shared/tokens.css.hbs +202 -0
  6. package/templates/_shared/tokens.js.hbs +34 -0
  7. package/templates/html/components.css +740 -0
  8. package/templates/html/index.html +135 -0
  9. package/templates/html/showcase.html +3550 -0
  10. package/templates/jsx/App.example.jsx +229 -0
  11. package/templates/jsx/components/AlertCritical.jsx +43 -0
  12. package/templates/jsx/components/Avatar.jsx +41 -0
  13. package/templates/jsx/components/Badge.jsx +12 -0
  14. package/templates/jsx/components/Banner.jsx +42 -0
  15. package/templates/jsx/components/Button.jsx +43 -0
  16. package/templates/jsx/components/Chip.jsx +28 -0
  17. package/templates/jsx/components/CodeBlock.jsx +42 -0
  18. package/templates/jsx/components/EmptyState.jsx +27 -0
  19. package/templates/jsx/components/Funnel.jsx +55 -0
  20. package/templates/jsx/components/Input.jsx +53 -0
  21. package/templates/jsx/components/KanbanColumn.jsx +51 -0
  22. package/templates/jsx/components/Kbd.jsx +11 -0
  23. package/templates/jsx/components/LeadCard.jsx +79 -0
  24. package/templates/jsx/components/Modal.jsx +57 -0
  25. package/templates/jsx/components/Panel.jsx +25 -0
  26. package/templates/jsx/components/Section.jsx +28 -0
  27. package/templates/jsx/components/Segmented.jsx +26 -0
  28. package/templates/jsx/components/Sidebar.jsx +49 -0
  29. package/templates/jsx/components/Spec.jsx +19 -0
  30. package/templates/jsx/components/StatCard.jsx +44 -0
  31. package/templates/jsx/components/TableActions.jsx +34 -0
  32. package/templates/jsx/components/Tag.jsx +21 -0
  33. package/templates/jsx/components/TagDot.jsx +26 -0
  34. package/templates/jsx/components/Toast.jsx +25 -0
  35. package/templates/jsx/components/Toggle.jsx +29 -0
  36. package/templates/jsx/components.css +740 -0
  37. package/templates/{icons.jsx → jsx/icons.jsx} +20 -9
  38. package/templates/jsx/index.js +31 -0
  39. package/templates/presets/zark/preset.json +26 -0
  40. package/templates/REFERENCE.md +0 -376
  41. package/templates/SHOWCASE.html +0 -254
  42. package/templates/brand.jsx +0 -89
  43. package/templates/components.jsx +0 -385
  44. package/templates/design-canvas.jsx +0 -789
  45. package/templates/foundations.jsx +0 -363
  46. package/templates/layouts.jsx +0 -232
  47. package/templates/patterns.jsx +0 -268
  48. package/templates/primitives.jsx +0 -306
  49. package/templates/tokens.css +0 -306
  50. package/templates/visual-references/icon-zark.png +0 -0
  51. package/templates/visual-references/logo-zark-principal.png +0 -0
  52. package/templates/visual-references/pasted-1777605750385-0.png +0 -0
  53. package/templates/visual-references/pasted-1777605766298-0.png +0 -0
  54. package/templates/visual-references/pasted-1777605775820-0.png +0 -0
  55. package/templates/visual-references/pasted-1777605789833-0.png +0 -0
  56. package/templates/visual-references/pasted-1777605802420-0.png +0 -0
  57. package/templates/visual-references/pasted-1777605812470-0.png +0 -0
  58. package/templates/visual-references/pasted-1777605817688-0.png +0 -0
  59. package/templates/visual-references/pasted-1777605828485-0.png +0 -0
  60. package/templates/visual-references/pasted-1777605837137-0.png +0 -0
  61. package/templates/visual-references/pasted-1777605849789-0.png +0 -0
  62. package/templates/visual-references/pasted-1777605864942-0.png +0 -0
  63. package/templates/visual-references/pasted-1777605877920-0.png +0 -0
  64. package/templates/visual-references/pasted-1777605897353-0.png +0 -0
  65. /package/templates/{assets/zark-logo.png → presets/zark/assets/logo-zark-laranja.png} +0 -0
  66. /package/templates/{assets → presets/zark/assets}/zark-icon.png +0 -0
package/bin/cli.js CHANGED
@@ -3,10 +3,13 @@
3
3
  import { promises as fs } from 'node:fs';
4
4
  import { existsSync } from 'node:fs';
5
5
  import path from 'node:path';
6
+ import readline from 'node:readline';
6
7
  import { fileURLToPath } from 'node:url';
7
8
 
8
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
10
  const TEMPLATES_DIR = path.resolve(__dirname, '../templates');
11
+ const PRESETS_DIR = path.join(TEMPLATES_DIR, 'presets');
12
+ const SHARED_DIR = path.join(TEMPLATES_DIR, '_shared');
10
13
  const PKG_PATH = path.resolve(__dirname, '../package.json');
11
14
 
12
15
  const c = {
@@ -27,146 +30,417 @@ function banner(title) {
27
30
 
28
31
  async function getVersion() {
29
32
  try {
30
- const pkg = JSON.parse(await fs.readFile(PKG_PATH, 'utf-8'));
31
- return pkg.version;
32
- } catch {
33
- return 'unknown';
33
+ return JSON.parse(await fs.readFile(PKG_PATH, 'utf-8')).version;
34
+ } catch { return 'unknown'; }
35
+ }
36
+
37
+ // ─── Color utils: hex ↔ HSL, palette generation ──────────────────────────
38
+
39
+ function hexToRgb(hex) {
40
+ const h = hex.replace('#', '').trim();
41
+ const s = h.length === 3 ? h.split('').map(x => x + x).join('') : h;
42
+ if (s.length !== 6 || !/^[0-9a-f]{6}$/i.test(s)) {
43
+ throw new Error(`Hex inválido: "${hex}" (esperado #RRGGBB ou #RGB)`);
34
44
  }
45
+ return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];
35
46
  }
36
47
 
37
- async function copyDir(src, dest) {
38
- await fs.mkdir(dest, { recursive: true });
39
- const entries = await fs.readdir(src, { withFileTypes: true });
40
- for (const entry of entries) {
41
- const sp = path.join(src, entry.name);
42
- const dp = path.join(dest, entry.name);
43
- if (entry.isDirectory()) {
44
- await copyDir(sp, dp);
45
- } else if (entry.isFile()) {
46
- await fs.copyFile(sp, dp);
48
+ function rgbToHex(r, g, b) {
49
+ const cl = (v) => Math.max(0, Math.min(255, Math.round(v)));
50
+ return '#' + [cl(r), cl(g), cl(b)].map(v => v.toString(16).padStart(2, '0')).join('');
51
+ }
52
+
53
+ function rgbToHsl(r, g, b) {
54
+ r /= 255; g /= 255; b /= 255;
55
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
56
+ let h = 0, s = 0; const l = (max + min) / 2;
57
+ if (max !== min) {
58
+ const d = max - min;
59
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
60
+ switch (max) {
61
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
62
+ case g: h = (b - r) / d + 2; break;
63
+ case b: h = (r - g) / d + 4; break;
47
64
  }
65
+ h /= 6;
48
66
  }
67
+ return [h * 360, s * 100, l * 100];
49
68
  }
50
69
 
51
- async function appendNoteToContextFiles(targetRoot, designDirName) {
52
- const note = `\n\n## Design system\nEste projeto usa o **ZARK Design System**. Antes de qualquer trabalho de UI/frontend, leia \`${designDirName}/REFERENCE.md\` (descreve tokens, primitivos, layouts e patterns). Os arquivos \`.jsx\` em \`${designDirName}/\` são REFERÊNCIA visual e estrutural — adapte pra stack atual do projeto (React/Vue/Svelte/Blade/Django templates/etc.), não copie literal. Os PNGs em \`${designDirName}/visual-references/\` são ground truth visual.\n`;
70
+ function hslToRgb(h, s, l) {
71
+ h /= 360; s /= 100; l /= 100;
72
+ if (s === 0) { const v = l * 255; return [v, v, v]; }
73
+ const hue2rgb = (p, q, t) => {
74
+ if (t < 0) t += 1; if (t > 1) t -= 1;
75
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
76
+ if (t < 1 / 2) return q;
77
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
78
+ return p;
79
+ };
80
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
81
+ const p = 2 * l - q;
82
+ return [hue2rgb(p, q, h + 1 / 3) * 255, hue2rgb(p, q, h) * 255, hue2rgb(p, q, h - 1 / 3) * 255];
83
+ }
53
84
 
54
- const candidates = [
55
- path.join(targetRoot, '.ai-context', 'CONTEXT.md'),
56
- path.join(targetRoot, 'CLAUDE.md'),
85
+ // Gera escala 50→900 a partir de uma cor base (assumida como 500/primary).
86
+ // Mantém o matiz, varia luminosidade e ajusta saturação por tier.
87
+ function generatePalette(baseHex) {
88
+ const [h, s, l] = rgbToHsl(...hexToRgb(baseHex));
89
+ // Tabela de target lightness por tier; saturação ajustada nos extremos
90
+ const tiers = [
91
+ { k: 50, L: 95, sScale: 0.55 },
92
+ { k: 100, L: 88, sScale: 0.70 },
93
+ { k: 200, L: 78, sScale: 0.85 },
94
+ { k: 300, L: 68, sScale: 0.95 },
95
+ { k: 400, L: 58, sScale: 1.00 },
96
+ { k: 500, L: Math.round(l), sScale: 1.00 }, // mantém a cor base
97
+ { k: 600, L: Math.max(l - 8, 38), sScale: 1.00 },
98
+ { k: 700, L: Math.max(l - 18, 28), sScale: 1.00 },
99
+ { k: 800, L: Math.max(l - 28, 20), sScale: 0.95 },
100
+ { k: 900, L: Math.max(l - 38, 12), sScale: 0.85 },
57
101
  ];
102
+ const out = {};
103
+ for (const t of tiers) {
104
+ if (t.k === 500) { out[500] = baseHex.toLowerCase(); continue; }
105
+ const tS = Math.min(100, s * t.sScale);
106
+ const [r, g, b] = hslToRgb(h, tS, t.L);
107
+ out[t.k] = rgbToHex(r, g, b);
108
+ }
109
+ // Rings (rgba)
110
+ const [r5, g5, b5] = hexToRgb(out[500]);
111
+ const [r4, g4, b4] = hexToRgb(out[400]);
112
+ out.ring = `rgba(${r5}, ${g5}, ${b5}, 0.20)`;
113
+ out.ringSoft = `rgba(${r5}, ${g5}, ${b5}, 0.12)`;
114
+ out.ringDark = `rgba(${r4}, ${g4}, ${b4}, 0.30)`;
115
+ out.ringSoftDark = `rgba(${r4}, ${g4}, ${b4}, 0.16)`;
116
+ return out;
117
+ }
58
118
 
59
- for (const file of candidates) {
60
- if (!existsSync(file)) continue;
61
- try {
62
- const stat = await fs.lstat(file);
63
- const realFile = stat.isSymbolicLink()
64
- ? path.resolve(path.dirname(file), await fs.readlink(file))
65
- : file;
66
- const current = await fs.readFile(realFile, 'utf-8');
67
- if (current.includes('ZARK Design System')) {
68
- return realFile + ' (já tem nota — pulando)';
119
+ // ─── Template engine: tiny Handlebars subset ─────────────────────────────
120
+ // Suporta {{var}}, {{var.nested}}, {{#if x}}...{{/if}}, {{#if x}}A{{else}}B{{/if}}
121
+
122
+ function renderTemplate(src, ctx) {
123
+ const get = (path) => path.split('.').reduce((o, k) => (o == null ? undefined : o[k]), ctx);
124
+ // Process {{#if cond}}...{{else}}...{{/if}} blocks (no nesting needed for our cases)
125
+ let out = src;
126
+ let prev;
127
+ do {
128
+ prev = out;
129
+ out = out.replace(
130
+ /\{\{#if\s+([\w.]+)\s*\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g,
131
+ (_, cond, a, b) => {
132
+ const v = get(cond);
133
+ return v ? a : (b || '');
134
+ }
135
+ );
136
+ } while (out !== prev);
137
+ // Replace {{var}}
138
+ out = out.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, key) => {
139
+ const v = get(key);
140
+ return v == null ? '' : String(v);
141
+ });
142
+ return out;
143
+ }
144
+
145
+ // ─── FS helpers ──────────────────────────────────────────────────────────
146
+
147
+ async function copyDir(src, dest, ctx, opts = {}) {
148
+ await fs.mkdir(dest, { recursive: true });
149
+ const entries = await fs.readdir(src, { withFileTypes: true });
150
+ for (const e of entries) {
151
+ const sp = path.join(src, e.name);
152
+ const dp = path.join(dest, e.name);
153
+ if (e.isDirectory()) await copyDir(sp, dp, ctx, opts);
154
+ else if (e.isFile()) {
155
+ if (sp.endsWith('.hbs')) {
156
+ const content = await fs.readFile(sp, 'utf-8');
157
+ const rendered = renderTemplate(content, ctx);
158
+ const finalPath = dp.replace(/\.hbs$/, '');
159
+ await fs.writeFile(finalPath, rendered, 'utf-8');
160
+ opts.written?.push(path.relative(opts.targetRoot, finalPath));
161
+ } else {
162
+ await fs.copyFile(sp, dp);
163
+ opts.written?.push(path.relative(opts.targetRoot, dp));
69
164
  }
70
- await fs.writeFile(realFile, current + note, 'utf-8');
71
- return realFile + ' (nota adicionada)';
72
- } catch {
73
- // continue
74
165
  }
75
166
  }
76
- return null;
77
167
  }
78
168
 
169
+ // ─── Prompts (readline nativo) ───────────────────────────────────────────
170
+
171
+ function makePrompt() {
172
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
173
+ return {
174
+ ask: (q, def) => new Promise(resolve => {
175
+ const prompt = def ? ` ${q} ${c.dim(`(${def})`)} ` : ` ${q} `;
176
+ rl.question(prompt, (a) => resolve(a.trim() || def || ''));
177
+ }),
178
+ yn: (q, def = true) => new Promise(resolve => {
179
+ const d = def ? 'S/n' : 's/N';
180
+ rl.question(` ${q} ${c.dim(`(${d})`)} `, (a) => {
181
+ const v = a.trim().toLowerCase();
182
+ if (v === '') return resolve(def);
183
+ resolve(v.startsWith('s') || v.startsWith('y'));
184
+ });
185
+ }),
186
+ close: () => rl.close(),
187
+ };
188
+ }
189
+
190
+ function slugify(s) {
191
+ return s.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '')
192
+ .replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
193
+ }
194
+
195
+ // ─── Help + List ─────────────────────────────────────────────────────────
196
+
79
197
  function printHelp(version) {
80
198
  banner(`zark-design v${version}`);
81
199
  console.log(` ${c.bold('Comandos:')}\n`);
82
- console.log(` ${c.green('init')} ${c.dim('[pasta]')} instala o design system (default: ./design-system)`);
83
- console.log(` ${c.green('help')} mostra esta ajuda`);
84
- console.log(` ${c.green('version')} mostra a versão\n`);
85
- console.log(` ${c.bold('Uso:')}`);
86
- console.log(` npx zark-design init ${c.dim('# instala em ./design-system')}`);
87
- console.log(` npx zark-design init ./ui ${c.dim('# instala em ./ui')}`);
88
- console.log(` npx zark-design init --force ${c.dim('# sobrescreve se existe')}\n`);
89
- console.log(` ${c.bold('Filosofia:')}`);
90
- console.log(` Os arquivos JSX são ${c.bold('REFERÊNCIA')} pra IA, não pra runtime.`);
91
- console.log(` A IA REFERENCE.md + visual-references/ e adapta pra stack atual`);
92
- console.log(` do projeto (React, Vue, Svelte, Blade, Django, HTML puro, etc.).\n`);
200
+ console.log(` ${c.green('init')} ${c.dim('[flags]')} cria design system (preset ou custom)`);
201
+ console.log(` ${c.green('presets')} lista presets disponíveis`);
202
+ console.log(` ${c.green('help')} mostra esta ajuda`);
203
+ console.log(` ${c.green('version')} mostra a versão\n`);
204
+ console.log(` ${c.bold('Modos:')}`);
205
+ console.log(` ${c.green('preset ZARK')} paleta + logos ZARK prontos (Liquid Lava #F56F10)`);
206
+ console.log(` ${c.green('custom')} sua cor primária + suas logos\n`);
207
+ console.log(` ${c.bold('Uso interativo:')}`);
208
+ console.log(` npx zark-design init ${c.dim('# pergunta tudo')}`);
209
+ console.log(` npx zark-design init --preset zark ${c.dim('# usa ZARK direto')}`);
210
+ console.log(` npx zark-design init --preset zark --yes ${c.dim('# sem confirms')}\n`);
211
+ console.log(` ${c.bold('Uso custom (flags):')}`);
212
+ console.log(` npx zark-design init --name "ALLSEC" --primary "#E74C3C" --yes`);
213
+ console.log(` npx zark-design init --palette ./minha-paleta.json --yes\n`);
214
+ console.log(` ${c.bold('Output:')}`);
215
+ console.log(` design-system/ ${c.dim('# default; mude com [pasta]')}`);
216
+ console.log(` ├── README.md`);
217
+ console.log(` ├── html/ (HTML puro)`);
218
+ console.log(` ├── jsx/ (React)`);
219
+ console.log(` └── assets/ (logos)\n`);
220
+ }
221
+
222
+ async function listPresets() {
223
+ banner('Presets disponíveis');
224
+ const entries = await fs.readdir(PRESETS_DIR, { withFileTypes: true });
225
+ for (const e of entries) {
226
+ if (!e.isDirectory()) continue;
227
+ const cfgPath = path.join(PRESETS_DIR, e.name, 'preset.json');
228
+ if (existsSync(cfgPath)) {
229
+ const cfg = JSON.parse(await fs.readFile(cfgPath, 'utf-8'));
230
+ console.log(` ${c.green('•')} ${c.bold(e.name.padEnd(10))} ${c.dim(`${cfg.name} · brand ${cfg.brand['500']}`)}`);
231
+ }
232
+ }
233
+ console.log('');
234
+ console.log(c.dim(' Uso: npx zark-design init --preset <nome>'));
235
+ }
236
+
237
+ // ─── Init logic ──────────────────────────────────────────────────────────
238
+
239
+ function parseArgs(argv) {
240
+ const flags = {}; const positional = [];
241
+ for (let i = 0; i < argv.length; i++) {
242
+ const a = argv[i];
243
+ if (a.startsWith('--')) {
244
+ const key = a.slice(2);
245
+ const next = argv[i + 1];
246
+ if (next === undefined || next.startsWith('--')) { flags[key] = true; }
247
+ else { flags[key] = next; i++; }
248
+ } else positional.push(a);
249
+ }
250
+ return { flags, positional };
251
+ }
252
+
253
+ async function loadPreset(name) {
254
+ const cfgPath = path.join(PRESETS_DIR, name, 'preset.json');
255
+ if (!existsSync(cfgPath)) {
256
+ const available = (await fs.readdir(PRESETS_DIR)).filter(x => !x.startsWith('.'));
257
+ throw new Error(`Preset "${name}" não existe. Disponíveis: ${available.join(', ')}`);
258
+ }
259
+ return JSON.parse(await fs.readFile(cfgPath, 'utf-8'));
260
+ }
261
+
262
+ async function loadCustomPalette(jsonPath) {
263
+ const data = JSON.parse(await fs.readFile(jsonPath, 'utf-8'));
264
+ if (!data.brand || !data.brand['500']) {
265
+ throw new Error('Paleta JSON precisa ter pelo menos { "name": ..., "brand": { "500": "#..." } }');
266
+ }
267
+ // Se faltam tiers, gera os ausentes a partir do 500
268
+ const filled = generatePalette(data.brand['500']);
269
+ data.brand = { ...filled, ...data.brand };
270
+ data.name = data.name || 'Custom';
271
+ data.slug = data.slug || slugify(data.name);
272
+ data.fonts = data.fonts || { ui: 'Inter', mono: 'JetBrains Mono', display: 'Space Grotesk' };
273
+ return data;
274
+ }
275
+
276
+ async function buildCtx({ flags, prompt }) {
277
+ // 1) Preset path
278
+ if (flags.preset) {
279
+ const preset = await loadPreset(flags.preset);
280
+ if (!flags.yes && !prompt) return preset;
281
+ return preset;
282
+ }
283
+ // 2) Palette JSON path
284
+ if (flags.palette) {
285
+ const data = await loadCustomPalette(path.resolve(flags.palette));
286
+ data.preset = null;
287
+ return data;
288
+ }
289
+ // 3) Flags directos (custom não-interativo)
290
+ if (flags.name || flags.primary) {
291
+ const name = flags.name || 'Custom';
292
+ const slug = flags.slug || slugify(name);
293
+ const primary = flags.primary;
294
+ if (!primary) throw new Error('--primary <hex> é obrigatório quando usa --name sem --palette');
295
+ return {
296
+ name, slug, preset: null,
297
+ brand: generatePalette(primary),
298
+ fonts: { ui: 'Inter', mono: 'JetBrains Mono', display: 'Space Grotesk' },
299
+ };
300
+ }
301
+ // 4) Interativo
302
+ if (!prompt) throw new Error('Modo interativo precisa de TTY. Use --preset zark ou --primary <hex>.');
303
+ console.log(c.dim(' Pressione Enter pra usar o default em parênteses.\n'));
304
+ const usePreset = await prompt.yn('Usar preset ZARK pronto?', true);
305
+ if (usePreset) return await loadPreset('zark');
306
+
307
+ const name = await prompt.ask('Nome do projeto/empresa:', 'Custom');
308
+ const slug = await prompt.ask('Slug (lowercase, sem espaços):', slugify(name));
309
+ const primary = await prompt.ask('Cor primária (hex, ex: #E74C3C):');
310
+ if (!primary) throw new Error('Cor primária é obrigatória.');
311
+ const palette = generatePalette(primary);
312
+ return {
313
+ name, slug, preset: null,
314
+ brand: palette,
315
+ fonts: { ui: 'Inter', mono: 'JetBrains Mono', display: 'Space Grotesk' },
316
+ };
93
317
  }
94
318
 
95
319
  async function runInit(args) {
96
- const force = args.includes('--force');
97
- const positional = args.filter((a) => !a.startsWith('--'));
98
- const target = path.resolve(positional[0] ?? './design-system');
320
+ const { flags, positional } = parseArgs(args);
321
+ const target = path.resolve(positional[0] || './design-system');
99
322
  const designDirName = path.basename(target);
100
- const projectRoot = path.dirname(target);
323
+ const force = !!flags.force;
324
+ const yes = !!flags.yes;
101
325
 
102
326
  banner('🎨 zark-design — install');
103
327
 
104
328
  if (existsSync(target)) {
105
329
  if (!force) {
106
- console.error(c.red(`✗ Pasta '${target}' já existe.`));
107
- console.error(c.dim(` Use --force pra sobrescrever, ou escolha outro nome.`));
330
+ console.error(c.red(`✗ Pasta '${target}' já existe. Use --force pra sobrescrever.`));
108
331
  process.exit(1);
109
332
  }
110
333
  console.log(c.yellow(`⚠ Sobrescrevendo ${target} (--force)`));
111
334
  await fs.rm(target, { recursive: true, force: true });
112
335
  }
113
336
 
114
- console.log(c.dim(` origem: ${TEMPLATES_DIR}`));
115
- console.log(c.dim(` destino: ${target}\n`));
337
+ // Build context (preset OR custom)
338
+ const interactive = !yes && !flags.preset && !flags.palette && !flags.name && process.stdin.isTTY;
339
+ const prompt = interactive ? makePrompt() : null;
340
+ let ctx;
341
+ try {
342
+ ctx = await buildCtx({ flags, prompt });
343
+ } finally { prompt?.close(); }
116
344
 
117
- await copyDir(TEMPLATES_DIR, target);
345
+ // Defaults
346
+ ctx.date = new Date().toISOString().slice(0, 10);
347
+ ctx.nameDesc = ctx.preset === 'zark'
348
+ ? 'Sistema de design para apps e sistemas internos da ZARK'
349
+ : `Sistema de design para apps e sistemas do(a) **${ctx.name}**`;
118
350
 
119
- const fileList = [];
120
- async function walk(dir, base = '') {
121
- for (const e of await fs.readdir(dir, { withFileTypes: true })) {
122
- const sub = path.join(base, e.name);
123
- if (e.isDirectory()) await walk(path.join(dir, e.name), sub);
124
- else fileList.push(sub);
125
- }
126
- }
127
- await walk(target);
351
+ console.log(c.dim(' Contexto:'));
352
+ console.log(c.dim(` Nome: ${ctx.name} (slug: ${ctx.slug})`));
353
+ console.log(c.dim(` Modo: ${ctx.preset ? 'preset ' + ctx.preset : 'custom'}`));
354
+ console.log(c.dim(` Brand: ${ctx.brand['500']} (paleta 50→900 gerada)`));
355
+ console.log(c.dim(` Fontes: ${ctx.fonts.ui} / ${ctx.fonts.mono} / ${ctx.fonts.display}`));
356
+ console.log(c.dim(` Destino: ${target}\n`));
128
357
 
129
- for (const f of fileList) {
130
- console.log(` ${c.green('✓')} ${f}`);
131
- }
358
+ const written = [];
359
+ const copyOpts = { targetRoot: target, written };
132
360
 
133
- console.log('');
134
- const noteResult = await appendNoteToContextFiles(projectRoot, designDirName);
135
- if (noteResult) {
136
- console.log(` ${c.green('✓')} ${noteResult}`);
361
+ // 1. Copia html/ e jsx/ (estáticos, com substituição de .hbs)
362
+ await copyDir(path.join(TEMPLATES_DIR, 'html'), path.join(target, 'html'), ctx, copyOpts);
363
+ await copyDir(path.join(TEMPLATES_DIR, 'jsx'), path.join(target, 'jsx'), ctx, copyOpts);
364
+
365
+ // 2. Renderiza tokens.css.hbs e tokens.js.hbs em html/ e jsx/
366
+ const tokensCss = renderTemplate(await fs.readFile(path.join(SHARED_DIR, 'tokens.css.hbs'), 'utf-8'), ctx);
367
+ await fs.writeFile(path.join(target, 'html/tokens.css'), tokensCss);
368
+ await fs.writeFile(path.join(target, 'jsx/tokens.css'), tokensCss);
369
+ written.push('html/tokens.css', 'jsx/tokens.css');
370
+ const tokensJs = renderTemplate(await fs.readFile(path.join(SHARED_DIR, 'tokens.js.hbs'), 'utf-8'), ctx);
371
+ await fs.writeFile(path.join(target, 'jsx/tokens.js'), tokensJs);
372
+ written.push('jsx/tokens.js');
373
+
374
+ // 3. README.md
375
+ const readme = renderTemplate(await fs.readFile(path.join(TEMPLATES_DIR, 'README.md.hbs'), 'utf-8'), ctx);
376
+ await fs.writeFile(path.join(target, 'README.md'), readme);
377
+ written.push('README.md');
378
+
379
+ // 4. Assets
380
+ await fs.mkdir(path.join(target, 'assets'), { recursive: true });
381
+ if (ctx.preset && existsSync(path.join(PRESETS_DIR, ctx.preset, 'assets'))) {
382
+ // Preset traz assets prontos
383
+ await copyDir(path.join(PRESETS_DIR, ctx.preset, 'assets'), path.join(target, 'assets'), ctx, copyOpts);
137
384
  } else {
138
- console.log(c.dim(` (sem .ai-context/CONTEXT.md ou CLAUDE.md no projeto — sem auto-nota)`));
385
+ // Custom: pasta vazia + README instrutivo
386
+ const assetsReadme = renderTemplate(await fs.readFile(path.join(SHARED_DIR, 'ASSETS-README.md.hbs'), 'utf-8'), ctx);
387
+ await fs.writeFile(path.join(target, 'assets/README.md'), assetsReadme);
388
+ written.push('assets/README.md (pasta pra você colar seus logos)');
139
389
  }
140
390
 
391
+ // Log
392
+ for (const f of written.sort()) console.log(` ${c.green('✓')} ${f}`);
393
+
394
+ // Auto-note em CLAUDE.md / .ai-context/CONTEXT.md se existir
141
395
  console.log('');
142
- console.log(c.green(`✓ Design system instalado em ${designDirName}/`));
396
+ const noteResult = await appendNoteToContextFiles(path.dirname(target), designDirName, ctx);
397
+ if (noteResult) console.log(` ${c.green('✓')} ${noteResult}`);
398
+ else console.log(c.dim(` (sem CLAUDE.md ou .ai-context/CONTEXT.md no projeto — sem auto-nota)`));
399
+
400
+ console.log('');
401
+ console.log(c.green(`✓ Design system "${ctx.name}" instalado em ${designDirName}/`));
143
402
  console.log('');
144
403
  console.log(c.bold('Próximo passo:'));
145
- console.log(` Diga pra IA: ${c.dim(`"use o design system em ${designDirName}/ como referência exata pra UI"`)}`);
146
- console.log(` Ou abra ${designDirName}/SHOWCASE.html no browser pra ver o sistema.`);
404
+ if (!ctx.preset) {
405
+ console.log(` ${c.yellow('•')} Cole seus logos em ${designDirName}/assets/ (veja assets/README.md)`);
406
+ }
407
+ console.log(` ${c.dim('•')} Diga pra IA: "use o design system em ${designDirName}/ como referência (leia README.md)"`);
408
+ console.log(` ${c.dim('•')} Abra ${designDirName}/html/showcase.html no browser pra ver tudo renderizado.`);
147
409
  }
148
410
 
411
+ async function appendNoteToContextFiles(targetRoot, designDirName, ctx) {
412
+ const note = `\n\n## Design system\nEste projeto usa o **${ctx.name} Design System** (gerado por \`zark-design\`${ctx.preset ? `, preset ${ctx.preset}` : ', modo custom'}). Antes de qualquer trabalho de UI/frontend, leia \`${designDirName}/README.md\`.\n\n2 implementações lado a lado consumindo \`tokens.css\` + \`components.css\` idênticos:\n- \`${designDirName}/html/\` — HTML puro (Blade/Django/Rails/HTML)\n- \`${designDirName}/jsx/\` — React (24 componentes individuais)\n\nVue/Svelte/Solid: use \`tokens.css\` + \`components.css\` direto, adapte os JSX mantendo as classes.\n\nCor de marca: \`var(--brand-500)\` = ${ctx.brand['500']}. Abra \`${designDirName}/html/showcase.html\` no browser pra inspeção visual.\n`;
413
+ const candidates = [
414
+ path.join(targetRoot, '.ai-context', 'CONTEXT.md'),
415
+ path.join(targetRoot, 'CLAUDE.md'),
416
+ ];
417
+ for (const file of candidates) {
418
+ if (!existsSync(file)) continue;
419
+ try {
420
+ const stat = await fs.lstat(file);
421
+ const realFile = stat.isSymbolicLink()
422
+ ? path.resolve(path.dirname(file), await fs.readlink(file)) : file;
423
+ const current = await fs.readFile(realFile, 'utf-8');
424
+ if (current.includes('Design System') && current.includes('design-system')) return realFile + ' (já tem nota — pulando)';
425
+ await fs.writeFile(realFile, current + note, 'utf-8');
426
+ return realFile + ' (nota adicionada)';
427
+ } catch { /* continue */ }
428
+ }
429
+ return null;
430
+ }
431
+
432
+ // ─── Main ────────────────────────────────────────────────────────────────
433
+
149
434
  async function main() {
150
435
  const argv = process.argv.slice(2);
151
- const cmd = argv[0] ?? 'help';
436
+ const cmd = argv[0] || 'help';
152
437
  const version = await getVersion();
153
-
154
438
  try {
155
439
  switch (cmd) {
156
- case 'init':
157
- await runInit(argv.slice(1));
158
- return;
159
- case 'version':
160
- case '--version':
161
- case '-v':
162
- console.log(version);
163
- return;
164
- case 'help':
165
- case '--help':
166
- case '-h':
167
- default:
168
- printHelp(version);
169
- return;
440
+ case 'init': return await runInit(argv.slice(1));
441
+ case 'presets': return await listPresets();
442
+ case 'version': case '--version': case '-v': return console.log(version);
443
+ case 'help': case '--help': case '-h': default: return printHelp(version);
170
444
  }
171
445
  } catch (e) {
172
446
  console.error(c.red(`✗ ${e.message ?? e}`));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zark-design",
3
- "version": "1.0.0",
4
- "description": "ZARK SAAS Design System install via `npx zark-design init` to drop the design system as REFERENCE for AI in any project (any stack).",
3
+ "version": "3.0.0",
4
+ "description": "Design system scaffolder seguindo método ZARK `npx zark-design init` em modo preset (ZARK pronto) ou custom (cor e logos suas, paleta 50→900 gerada). HTML + JSX side-by-side. Dark mode nativo. Sem dependência runtime.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "zark-design": "bin/cli.js"