whale-igniter 1.3.4 → 1.3.6

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.
@@ -32,6 +32,8 @@ const CATEGORY_TEMPLATES = {
32
32
  */
33
33
  export async function createComponentCommand(name, options = {}) {
34
34
  const target = resolveTarget();
35
+ const shouldRegister = options.register !== false && !options.noRegister;
36
+ const shouldSync = options.sync !== false && !options.noSync;
35
37
  if (!name || !/^[A-Z][A-Za-z0-9]*$/.test(name)) {
36
38
  console.log(ui.fail("Component name must be PascalCase (e.g. `Button`, `UserCard`)."));
37
39
  process.exitCode = 1;
@@ -82,7 +84,7 @@ export async function createComponentCommand(name, options = {}) {
82
84
  if (variants && variants.length > 0)
83
85
  console.log(` ${ui.kv("variants", variants.join(", "), { keyWidth: 8 })}`);
84
86
  // Auto-register in the catalog unless explicitly skipped.
85
- if (!options.noRegister) {
87
+ if (shouldRegister) {
86
88
  await upsertComponent(target, {
87
89
  name,
88
90
  category,
@@ -92,7 +94,7 @@ export async function createComponentCommand(name, options = {}) {
92
94
  });
93
95
  console.log(ui.muted(` registered in intelligence/components.json`));
94
96
  }
95
- if (!options.noSync && !options.noRegister) {
97
+ if (shouldSync && shouldRegister) {
96
98
  await generateWiki(target);
97
99
  console.log(ui.muted(`${ui.glyph.check} AI context updated`));
98
100
  }
@@ -5,12 +5,15 @@ import { loadConfig } from "../utils/config.js";
5
5
  import { upsertComponent } from "../utils/components.js";
6
6
  import { generateWiki } from "../generators/wikiGenerator.js";
7
7
  import { ui } from "../ui/index.js";
8
+ import { loadUiTheme, DEFAULT_UI_THEME } from "../themes/registry.js";
8
9
  const DEFAULT_SECTIONS = ["hero", "features", "proof", "contact"];
9
10
  const VALID_SECTIONS = new Set(DEFAULT_SECTIONS);
10
11
  export async function createLandingCommand(options = {}) {
11
12
  const target = resolveTarget();
12
13
  const config = await loadConfig(target);
13
14
  const sections = parseSections(options.sections);
15
+ const shouldRegister = options.register !== false && !options.noRegister;
16
+ const shouldSync = options.sync !== false && !options.noSync;
14
17
  if (sections.length === 0) {
15
18
  console.log(ui.fail("No valid sections selected. Use hero,features,proof,contact."));
16
19
  process.exitCode = 1;
@@ -35,24 +38,27 @@ export async function createLandingCommand(options = {}) {
35
38
  console.log(ui.warn(`Project stack is ${ui.code(config.stack)}; generating portable HTML/CSS anyway.`));
36
39
  }
37
40
  await fs.ensureDir(path.dirname(htmlAbs));
38
- const grid = config.foundations?.grid ?? 8;
39
- const controlRadius = config.foundations?.radius?.control ?? 2;
40
- const containerRadius = config.foundations?.radius?.container ?? 4;
41
41
  const projectName = config.projectName ?? "Whale Landing";
42
+ // Resolve theme: CLI flag → config.theme → DEFAULT_UI_THEME
43
+ const themeName = options.theme ?? config.theme ?? DEFAULT_UI_THEME;
44
+ const theme = await loadUiTheme(themeName);
45
+ if (!theme) {
46
+ console.log(ui.fail(`Unknown theme '${themeName}'. Run \`whale themes list\`.`));
47
+ process.exitCode = 1;
48
+ return;
49
+ }
42
50
  await fs.writeFile(htmlAbs, renderHtml(projectName, sections), "utf8");
43
- await fs.writeFile(cssAbs, renderCss({
44
- grid,
45
- controlRadius,
46
- containerRadius,
51
+ await fs.writeFile(cssAbs, renderCss(theme, {
47
52
  includeProof: sections.includes("proof"),
48
53
  includeContact: sections.includes("contact")
49
54
  }), "utf8");
50
55
  console.log(ui.ok(`Created ${ui.path(htmlRel)}`));
51
56
  console.log(ui.ok(`Created ${ui.path(cssRel)}`));
52
57
  console.log(` ${ui.kv("sections", sections.join(", "), { keyWidth: 8 })}`);
53
- console.log(` ${ui.kv("grid", `${grid}px`, { keyWidth: 8 })}`);
54
- console.log(` ${ui.kv("radius", `${controlRadius}px controls, ${containerRadius}px containers`, { keyWidth: 8 })}`);
55
- if (!options.noRegister) {
58
+ console.log(` ${ui.kv("theme", theme.label, { keyWidth: 8 })}`);
59
+ console.log(` ${ui.kv("grid", `${theme.structural.grid}px`, { keyWidth: 8 })}`);
60
+ console.log(` ${ui.kv("radius", `${theme.structural.radiusControl}px controls, ${theme.structural.radiusContainer}px containers`, { keyWidth: 8 })}`);
61
+ if (shouldRegister) {
56
62
  for (const section of sections) {
57
63
  await upsertComponent(target, {
58
64
  name: sectionName(section),
@@ -66,7 +72,7 @@ export async function createLandingCommand(options = {}) {
66
72
  }
67
73
  console.log(ui.muted(` registered ${sections.length} section(s) in intelligence/components.json`));
68
74
  }
69
- if (!options.noSync && !options.noRegister) {
75
+ if (shouldSync && shouldRegister) {
70
76
  await generateWiki(target);
71
77
  console.log(ui.muted(`${ui.glyph.check} AI context updated`));
72
78
  }
@@ -214,17 +220,45 @@ function renderSection(section, title, sections) {
214
220
  </section>`;
215
221
  }
216
222
  }
217
- function renderCss(args) {
218
- const { grid, controlRadius, containerRadius, includeProof, includeContact } = args;
223
+ function renderCss(theme, opts) {
224
+ const { includeProof, includeContact } = opts;
225
+ const { identity, typography, structural, interaction } = theme;
226
+ const g = structural.grid;
227
+ const focusStyles = interaction.focus === "ring"
228
+ ? `box-shadow: 0 0 0 3px var(--color-accent);
229
+ outline: none;`
230
+ : `outline: var(--border-width) solid var(--color-accent);
231
+ outline-offset: ${g / 2}px;`;
219
232
  return `:root {
220
- --space-grid: ${grid}px;
221
- --color-bg: #f7fafc;
222
- --color-text: #111827;
223
- --color-muted: #4b5563;
224
- --color-surface: #ffffff;
225
- --color-accent: #0891b2;
226
- --color-accent-strong: #0e7490;
227
- --color-border: #d1d5db;
233
+ /* Identity */
234
+ --color-bg: ${identity.bg};
235
+ --color-surface: ${identity.surface};
236
+ --color-text: ${identity.text};
237
+ --color-muted: ${identity.muted};
238
+ --color-border: ${identity.border};
239
+ --color-accent: ${identity.accent};
240
+ --color-accent-strong: ${identity.accentStrong};
241
+ --color-on-accent: ${identity.onAccent};
242
+
243
+ /* Typography */
244
+ --font-display: ${typography.display};
245
+ --font-body: ${typography.body};
246
+ --font-mono: ${typography.mono};
247
+
248
+ /* Structure */
249
+ --space-grid: ${structural.grid}px;
250
+ --radius-control: ${structural.radiusControl}px;
251
+ --radius-container: ${structural.radiusContainer}px;
252
+ --border-width: ${structural.borderWidth}px;
253
+ --shadow: ${structural.shadow};
254
+
255
+ /* Motion */
256
+ --motion-duration: ${interaction.duration}ms;
257
+ --motion-ease: ${interaction.easing};
258
+ }
259
+
260
+ @media (prefers-reduced-motion: reduce) {
261
+ * { transition: none !important; }
228
262
  }
229
263
 
230
264
  * {
@@ -233,7 +267,7 @@ function renderCss(args) {
233
267
 
234
268
  body {
235
269
  margin: 0;
236
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
270
+ font-family: var(--font-body);
237
271
  color: var(--color-text);
238
272
  background: var(--color-bg);
239
273
  }
@@ -243,17 +277,17 @@ a {
243
277
  }
244
278
 
245
279
  .section {
246
- padding: ${grid * 8}px ${grid * 3}px;
280
+ padding: calc(var(--space-grid) * 8) calc(var(--space-grid) * 3);
247
281
  }
248
282
 
249
283
  .section__inner {
250
284
  width: 100%;
251
- max-width: ${grid * 140}px;
285
+ max-width: calc(var(--space-grid) * 140);
252
286
  margin: 0 auto;
253
287
  }
254
288
 
255
289
  .hero {
256
- min-height: ${grid * 80}px;
290
+ min-height: calc(var(--space-grid) * 80);
257
291
  display: grid;
258
292
  align-items: center;
259
293
  }
@@ -262,13 +296,13 @@ a {
262
296
  .contact__grid,
263
297
  .proof__grid {
264
298
  display: grid;
265
- grid-template-columns: minmax(0, 1.2fr) minmax(${grid * 36}px, 0.8fr);
266
- gap: ${grid * 6}px;
299
+ grid-template-columns: minmax(0, 1.2fr) minmax(calc(var(--space-grid) * 36), 0.8fr);
300
+ gap: calc(var(--space-grid) * 6);
267
301
  align-items: center;
268
302
  }
269
303
 
270
304
  .eyebrow {
271
- margin: 0 0 ${grid * 2}px;
305
+ margin: 0 0 calc(var(--space-grid) * 2);
272
306
  color: var(--color-accent-strong);
273
307
  font-size: 0.875rem;
274
308
  font-weight: 700;
@@ -283,27 +317,29 @@ p {
283
317
  }
284
318
 
285
319
  h1 {
286
- max-width: ${grid * 88}px;
287
- margin-bottom: ${grid * 3}px;
320
+ max-width: calc(var(--space-grid) * 88);
321
+ margin-bottom: calc(var(--space-grid) * 3);
322
+ font-family: var(--font-display);
288
323
  font-size: clamp(2.5rem, 6vw, 4.5rem);
289
324
  line-height: 1;
290
325
  }
291
326
 
292
327
  h2 {
293
- max-width: ${grid * 80}px;
294
- margin-bottom: ${grid * 4}px;
328
+ max-width: calc(var(--space-grid) * 80);
329
+ margin-bottom: calc(var(--space-grid) * 4);
330
+ font-family: var(--font-display);
295
331
  font-size: clamp(2rem, 4vw, 3rem);
296
332
  line-height: 1.1;
297
333
  }
298
334
 
299
335
  h3 {
300
- margin-bottom: ${grid}px;
336
+ margin-bottom: var(--space-grid);
301
337
  font-size: 1.125rem;
302
338
  }
303
339
 
304
340
  .lede {
305
- max-width: ${grid * 72}px;
306
- margin-bottom: ${grid * 4}px;
341
+ max-width: calc(var(--space-grid) * 72);
342
+ margin-bottom: calc(var(--space-grid) * 4);
307
343
  color: var(--color-muted);
308
344
  font-size: 1.125rem;
309
345
  line-height: 1.6;
@@ -311,18 +347,19 @@ h3 {
311
347
 
312
348
  .button {
313
349
  display: inline-flex;
314
- min-height: ${grid * 6}px;
350
+ min-height: calc(var(--space-grid) * 6);
315
351
  align-items: center;
316
352
  justify-content: center;
317
- padding: ${grid}px ${grid * 3}px;
353
+ padding: var(--space-grid) calc(var(--space-grid) * 3);
318
354
  border: 0;
319
- border-radius: ${controlRadius}px;
355
+ border-radius: var(--radius-control);
320
356
  background: var(--color-accent);
321
- color: var(--color-surface);
357
+ color: var(--color-on-accent);
322
358
  font: inherit;
323
359
  font-weight: 700;
324
360
  text-decoration: none;
325
361
  cursor: pointer;
362
+ transition: background var(--motion-duration) var(--motion-ease);
326
363
  }
327
364
 
328
365
  .button:hover {
@@ -332,24 +369,23 @@ h3 {
332
369
  .button:focus-visible,
333
370
  input:focus-visible,
334
371
  textarea:focus-visible {
335
- outline: ${controlRadius}px solid var(--color-accent);
336
- outline-offset: ${grid / 2}px;
372
+ ${focusStyles}
337
373
  }
338
374
 
339
375
  .hero__panel,
340
376
  .card,
341
377
  blockquote,
342
378
  .contact-form {
343
- border: 1px solid var(--color-border);
344
- border-radius: ${containerRadius}px;
379
+ border: var(--border-width) solid var(--color-border);
380
+ border-radius: var(--radius-container);
345
381
  background: var(--color-surface);
346
- box-shadow: 0 ${grid}px ${grid * 3}px rgba(17, 24, 39, 0.08);
382
+ box-shadow: var(--shadow);
347
383
  }
348
384
 
349
385
  .hero__panel {
350
386
  display: grid;
351
- gap: ${grid * 2}px;
352
- padding: ${grid * 4}px;
387
+ gap: calc(var(--space-grid) * 2);
388
+ padding: calc(var(--space-grid) * 4);
353
389
  }
354
390
 
355
391
  .hero__panel strong {
@@ -359,15 +395,15 @@ blockquote,
359
395
  .feature-grid {
360
396
  display: grid;
361
397
  grid-template-columns: repeat(3, minmax(0, 1fr));
362
- gap: ${grid * 3}px;
398
+ gap: calc(var(--space-grid) * 3);
363
399
  }
364
400
 
365
401
  .card {
366
- padding: ${grid * 3}px;
402
+ padding: calc(var(--space-grid) * 3);
367
403
  }
368
- ${includeProof ? proofCss(grid) : ""}
369
- ${includeContact ? contactCss(grid) : ""}
370
- @media (max-width: ${grid * 96}px) {
404
+ ${includeProof ? proofCss() : ""}
405
+ ${includeContact ? contactCss() : ""}
406
+ @media (max-width: calc(var(--space-grid) * 96)) {
371
407
  .hero__grid,
372
408
  .contact__grid,
373
409
  .proof__grid,
@@ -376,12 +412,12 @@ ${includeContact ? contactCss(grid) : ""}
376
412
  }
377
413
 
378
414
  .section {
379
- padding: ${grid * 6}px ${grid * 2}px;
415
+ padding: calc(var(--space-grid) * 6) calc(var(--space-grid) * 2);
380
416
  }
381
417
  }
382
418
  `;
383
419
  }
384
- function proofCss(grid) {
420
+ function proofCss() {
385
421
  return `
386
422
  .proof {
387
423
  background: var(--color-surface);
@@ -389,11 +425,11 @@ function proofCss(grid) {
389
425
 
390
426
  blockquote {
391
427
  margin: 0;
392
- padding: ${grid * 3}px;
428
+ padding: calc(var(--space-grid) * 3);
393
429
  }
394
430
 
395
431
  blockquote p {
396
- margin-bottom: ${grid * 2}px;
432
+ margin-bottom: calc(var(--space-grid) * 2);
397
433
  font-size: 1.25rem;
398
434
  line-height: 1.5;
399
435
  }
@@ -404,26 +440,26 @@ cite {
404
440
  }
405
441
  `;
406
442
  }
407
- function contactCss(grid) {
443
+ function contactCss() {
408
444
  return `
409
445
  .contact-form {
410
446
  display: grid;
411
- gap: ${grid * 2}px;
412
- padding: ${grid * 3}px;
447
+ gap: calc(var(--space-grid) * 2);
448
+ padding: calc(var(--space-grid) * 3);
413
449
  }
414
450
 
415
451
  label {
416
452
  display: grid;
417
- gap: ${grid}px;
453
+ gap: var(--space-grid);
418
454
  font-weight: 700;
419
455
  }
420
456
 
421
457
  input,
422
458
  textarea {
423
459
  width: 100%;
424
- padding: ${grid}px ${grid * 2}px;
425
- border: 1px solid var(--color-border);
426
- border-radius: 2px;
460
+ padding: var(--space-grid) calc(var(--space-grid) * 2);
461
+ border: var(--border-width) solid var(--color-border);
462
+ border-radius: var(--radius-control);
427
463
  font: inherit;
428
464
  }
429
465
  `;
@@ -10,6 +10,7 @@ import { getAiAvailability } from "../utils/aiAvailability.js";
10
10
  import { mapWizardAnswers, suggestUiCategories } from "../utils/wizardMapping.js";
11
11
  import { ui } from "../ui/index.js";
12
12
  import { PACKAGE_VERSION } from "../version.js";
13
+ import { splash } from "../ui/splash.js";
13
14
  const OPINIONATED_PACKS = ["lighthouse", "forge", "scribe"];
14
15
  const MINIMAL_PACKS = ["lighthouse"];
15
16
  export async function igniteCommand(projectName = "whale-project", options = {}) {
@@ -19,7 +20,8 @@ export async function igniteCommand(projectName = "whale-project", options = {})
19
20
  ? "minimal"
20
21
  : "opinionated";
21
22
  console.log();
22
- console.log(ui.header("Whale Igniter", `ignite • ${mode}`));
23
+ console.log(splash());
24
+ console.log(ui.muted(`ignite • ${mode}`));
23
25
  console.log();
24
26
  // ---- Resolve config based on mode -----------------------------------------
25
27
  let config;
@@ -0,0 +1,20 @@
1
+ import { ui } from "../ui/index.js";
2
+ import { listUiThemes } from "../themes/registry.js";
3
+ export async function themesListCommand() {
4
+ const themes = await listUiThemes();
5
+ if (themes.length === 0) {
6
+ console.log(ui.warn("No UI themes found."));
7
+ return;
8
+ }
9
+ for (const theme of themes) {
10
+ console.log();
11
+ console.log(ui.section(theme.label));
12
+ console.log(ui.muted(theme.description));
13
+ console.log(` ${ui.kv("accent", theme.identity.accent, { keyWidth: 10 })}`);
14
+ console.log(` ${ui.kv("radius", `${theme.structural.radiusControl}px / ${theme.structural.radiusContainer}px`, { keyWidth: 10 })}`);
15
+ console.log(` ${ui.kv("mode", theme.identity.mode, { keyWidth: 10 })}`);
16
+ console.log(` ${ui.kv("slug", theme.name, { keyWidth: 10 })}`);
17
+ }
18
+ console.log();
19
+ console.log(ui.muted(`Use --theme <slug> with \`whale create landing\` to apply a theme.`));
20
+ }
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ import { adoptReviewCommand, adoptStatusCommand } from "./commands/adoptReview.j
15
15
  import { insightsCommand, insightsDriftReviewCommand } from "./commands/insights.js";
16
16
  import { createComponentCommand } from "./commands/createComponent.js";
17
17
  import { createLandingCommand } from "./commands/createLanding.js";
18
+ import { themesListCommand } from "./commands/themes.js";
18
19
  import { updateTokensCommand } from "./commands/updateTokens.js";
19
20
  import { seleneDescribeCommand, seleneAuditCommand, seleneSuggestCommand, seleneApplyCommand, seleneStatusCommand, seleneCacheClearCommand } from "./commands/selene.js";
20
21
  import { mcpServeCommand, mcpConfigCommand } from "./commands/mcp.js";
@@ -182,6 +183,7 @@ create
182
183
  .option("--force", "Overwrite generated files if they already exist")
183
184
  .option("--no-register", "Don't add generated sections to intelligence/components.json")
184
185
  .option("--no-sync", "Don't regenerate CLAUDE.md / wiki after creating")
186
+ .option("--theme <name>", "Visual theme for the generated UI (see `whale themes list`)")
185
187
  .action((opts) => createLandingCommand(opts));
186
188
  const adopt = program
187
189
  .command("adopt [target]")
@@ -294,4 +296,11 @@ program
294
296
  .description("Show what changed in intelligence stores since a git ref.")
295
297
  .option("--since <ref>", "Git ref to compare against (default: HEAD~1)", "HEAD~1")
296
298
  .action((target, opts) => changesCommand({ since: opts.since, target }));
299
+ const themes = program
300
+ .command("themes")
301
+ .description("Browse local UI themes for generated output.");
302
+ themes
303
+ .command("list")
304
+ .description("List available UI themes.")
305
+ .action(() => themesListCommand());
297
306
  program.parse();
@@ -0,0 +1,49 @@
1
+ import path from "node:path";
2
+ import fs from "fs-extra";
3
+ import { fileURLToPath } from "node:url";
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+ // Resolve themes/ relative to compiled output (dist/themes/ → ../../themes)
7
+ const THEMES_DIR = path.resolve(__dirname, "../../themes");
8
+ export const DEFAULT_UI_THEME = "atlas";
9
+ function isValidUiTheme(obj) {
10
+ if (!obj || typeof obj !== "object")
11
+ return false;
12
+ const t = obj;
13
+ return (typeof t.name === "string" &&
14
+ typeof t.label === "string" &&
15
+ typeof t.description === "string" &&
16
+ typeof t.identity === "object" && t.identity !== null &&
17
+ typeof t.typography === "object" && t.typography !== null &&
18
+ typeof t.structural === "object" && t.structural !== null &&
19
+ typeof t.interaction === "object" && t.interaction !== null);
20
+ }
21
+ export async function listUiThemes() {
22
+ const files = await fs.readdir(THEMES_DIR).catch(() => []);
23
+ const themes = [];
24
+ for (const file of files) {
25
+ if (!file.endsWith(".json"))
26
+ continue;
27
+ try {
28
+ const raw = await fs.readJson(path.join(THEMES_DIR, file));
29
+ if (isValidUiTheme(raw))
30
+ themes.push(raw);
31
+ }
32
+ catch {
33
+ // skip malformed packs
34
+ }
35
+ }
36
+ return themes.sort((a, b) => a.label.localeCompare(b.label));
37
+ }
38
+ export async function loadUiTheme(name) {
39
+ const filePath = path.join(THEMES_DIR, `${name}.json`);
40
+ if (!(await fs.pathExists(filePath)))
41
+ return null;
42
+ try {
43
+ const raw = await fs.readJson(filePath);
44
+ return isValidUiTheme(raw) ? raw : null;
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ /**
2
+ * splash.ts — capability-gated welcome banner for `whale ignite`.
3
+ *
4
+ * Three tiers:
5
+ * 1. Full (color + unicode) — wordmark with accent color + glyphs
6
+ * 2. Unicode, no color — same layout, no color
7
+ * 3. Plain — single ASCII line, no glyphs
8
+ *
9
+ * Rules: no chalk imports, no direct env var reads, no "premium".
10
+ * Uses `capabilities()` from capabilities.js and `ui` atoms.
11
+ */
12
+ import { capabilities } from "./capabilities.js";
13
+ import { accent, muted, emphasis } from "./atoms.js";
14
+ import { PACKAGE_VERSION } from "../version.js";
15
+ const TAGLINE = "map the project before the agent moves";
16
+ export function splash() {
17
+ const { color, unicode } = capabilities();
18
+ const isPlain = !unicode;
19
+ // Tier 3 — plain ASCII, single line
20
+ if (isPlain) {
21
+ return `WHALE IGNITER v${PACKAGE_VERSION} — ${TAGLINE}`;
22
+ }
23
+ // Tier 1 & 2 — unicode layout (color applied via atom functions when available)
24
+ const whale = "◆ ~";
25
+ const wordmarkWhale = color ? accent("WHALE") : "WHALE";
26
+ const wordmarkIgniter = color ? emphasis("IGNITER") : "IGNITER";
27
+ const version = color ? muted(`v${PACKAGE_VERSION}`) : `v${PACKAGE_VERSION}`;
28
+ const tag = color ? muted(TAGLINE) : TAGLINE;
29
+ const rule = "─".repeat(36);
30
+ return [
31
+ `${whale}`,
32
+ `${wordmarkWhale} ${wordmarkIgniter} ${version}`,
33
+ tag,
34
+ rule
35
+ ].join("\n");
36
+ }
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const PACKAGE_VERSION = "1.3.4";
1
+ export const PACKAGE_VERSION = "1.3.6";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whale-igniter",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "CLI-first operational intelligence. Bootstraps and adopts AI-readable project context so agents like Claude Code, Codex and Cursor understand your design system from the first commit. Includes an MCP server, a file watcher, deterministic insights, and an opt-in AI bridge (Selene).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,6 +11,7 @@
11
11
  "dist",
12
12
  "brand",
13
13
  "packs",
14
+ "themes",
14
15
  "ui-references",
15
16
  "README.md",
16
17
  "LICENSE",
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "atlas",
3
+ "label": "Atlas",
4
+ "description": "Enterprise minimal: dense, neutral, hairline borders.",
5
+ "identity": {
6
+ "mode": "light",
7
+ "bg": "#FFFFFF",
8
+ "surface": "#FFFFFF",
9
+ "text": "#0F172A",
10
+ "muted": "#475569",
11
+ "border": "#E2E8F0",
12
+ "accent": "#334155",
13
+ "accentStrong": "#0F172A",
14
+ "onAccent": "#FFFFFF"
15
+ },
16
+ "typography": {
17
+ "display": "Inter, ui-sans-serif, system-ui, -apple-system, sans-serif",
18
+ "body": "Inter, ui-sans-serif, system-ui, -apple-system, sans-serif",
19
+ "mono": "ui-monospace, SFMono-Regular, monospace",
20
+ "scaleRatio": 1.2
21
+ },
22
+ "structural": {
23
+ "grid": 8,
24
+ "radiusControl": 2,
25
+ "radiusContainer": 4,
26
+ "borderWidth": 1,
27
+ "shadow": "none"
28
+ },
29
+ "interaction": {
30
+ "duration": 120,
31
+ "easing": "cubic-bezier(0.4, 0, 0.2, 1)",
32
+ "focus": "ring"
33
+ }
34
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "aurora",
3
+ "label": "Aurora",
4
+ "description": "Modern SaaS: soft, airy, friendly, generous whitespace.",
5
+ "identity": {
6
+ "mode": "light",
7
+ "bg": "#FBFAFF",
8
+ "surface": "#FFFFFF",
9
+ "text": "#26215C",
10
+ "muted": "#534AB7",
11
+ "border": "#E4DFFA",
12
+ "accent": "#6D5AE6",
13
+ "accentStrong": "#534AB7",
14
+ "onAccent": "#FFFFFF"
15
+ },
16
+ "typography": {
17
+ "display": "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif",
18
+ "body": "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif",
19
+ "mono": "ui-monospace, SFMono-Regular, monospace",
20
+ "scaleRatio": 1.25
21
+ },
22
+ "structural": {
23
+ "grid": 8,
24
+ "radiusControl": 6,
25
+ "radiusContainer": 8,
26
+ "borderWidth": 1,
27
+ "shadow": "0 8px 24px rgba(109, 90, 230, 0.12)"
28
+ },
29
+ "interaction": {
30
+ "duration": 200,
31
+ "easing": "cubic-bezier(0.16, 1, 0.3, 1)",
32
+ "focus": "ring"
33
+ }
34
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "harbor",
3
+ "label": "Harbor",
4
+ "description": "The Whale brand: confident, editorial, royal blue on paper.",
5
+ "identity": {
6
+ "mode": "light",
7
+ "bg": "#F2ECE1",
8
+ "surface": "#FFFFFF",
9
+ "text": "#003087",
10
+ "muted": "#3A4D7A",
11
+ "border": "#C9D6F2",
12
+ "accent": "#0034D3",
13
+ "accentStrong": "#003087",
14
+ "onAccent": "#F2ECE1"
15
+ },
16
+ "typography": {
17
+ "display": "\"Special Elite\", ui-monospace, SFMono-Regular, monospace",
18
+ "body": "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif",
19
+ "mono": "\"JetBrains Mono\", ui-monospace, monospace",
20
+ "scaleRatio": 1.25
21
+ },
22
+ "structural": {
23
+ "grid": 8,
24
+ "radiusControl": 2,
25
+ "radiusContainer": 4,
26
+ "borderWidth": 1,
27
+ "shadow": "0 8px 24px rgba(0, 48, 135, 0.10)"
28
+ },
29
+ "interaction": {
30
+ "duration": 160,
31
+ "easing": "cubic-bezier(0.2, 0, 0, 1)",
32
+ "focus": "outline"
33
+ }
34
+ }