@almadar/ui 5.128.0 → 5.130.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.
@@ -0,0 +1,480 @@
1
+ /**
2
+ * Theme Generator from OrbitalSchema
3
+ *
4
+ * Analyzes an .orb schema file and generates a client-specific theme CSS file.
5
+ * The theme is derived from the domain, entity names, and any explicit style hints.
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+
11
+ // Domain-to-theme mappings
12
+ const DOMAIN_THEMES: Record<string, ThemeConfig> = {
13
+ // Agriculture / Garden domains
14
+ garden: {
15
+ primary: '#15803d',
16
+ primaryHover: '#166534',
17
+ secondary: '#fef7ed',
18
+ accent: '#ca8a04',
19
+ muted: '#ecfccb',
20
+ background: '#fefefe',
21
+ foreground: '#1a2e05',
22
+ border: '#bbf7d0',
23
+ metaphor: 'organic growth',
24
+ shadows: 'soft-organic',
25
+ radius: 'organic',
26
+ },
27
+ agriculture: {
28
+ primary: '#15803d',
29
+ primaryHover: '#166534',
30
+ secondary: '#fef7ed',
31
+ accent: '#ca8a04',
32
+ muted: '#ecfccb',
33
+ background: '#fefefe',
34
+ foreground: '#1a2e05',
35
+ border: '#bbf7d0',
36
+ metaphor: 'organic growth',
37
+ shadows: 'soft-organic',
38
+ radius: 'organic',
39
+ },
40
+ // Healthcare domains
41
+ healthcare: {
42
+ primary: '#0891b2',
43
+ primaryHover: '#0e7490',
44
+ secondary: '#f0f9ff',
45
+ accent: '#06b6d4',
46
+ muted: '#e0f2fe',
47
+ background: '#ffffff',
48
+ foreground: '#0c4a6e',
49
+ border: '#bae6fd',
50
+ metaphor: 'clinical trust',
51
+ shadows: 'minimal',
52
+ radius: 'gentle',
53
+ },
54
+ medical: {
55
+ primary: '#0891b2',
56
+ primaryHover: '#0e7490',
57
+ secondary: '#f0f9ff',
58
+ accent: '#06b6d4',
59
+ muted: '#e0f2fe',
60
+ background: '#ffffff',
61
+ foreground: '#0c4a6e',
62
+ border: '#bae6fd',
63
+ metaphor: 'clinical trust',
64
+ shadows: 'minimal',
65
+ radius: 'gentle',
66
+ },
67
+ // Finance domains
68
+ finance: {
69
+ primary: '#1e3a5f',
70
+ primaryHover: '#0f172a',
71
+ secondary: '#f8fafc',
72
+ accent: '#eab308',
73
+ muted: '#f1f5f9',
74
+ background: '#ffffff',
75
+ foreground: '#0f172a',
76
+ border: '#cbd5e1',
77
+ metaphor: 'stability and trust',
78
+ shadows: 'corporate',
79
+ radius: 'sharp',
80
+ },
81
+ banking: {
82
+ primary: '#1e3a5f',
83
+ primaryHover: '#0f172a',
84
+ secondary: '#f8fafc',
85
+ accent: '#eab308',
86
+ muted: '#f1f5f9',
87
+ background: '#ffffff',
88
+ foreground: '#0f172a',
89
+ border: '#cbd5e1',
90
+ metaphor: 'stability and trust',
91
+ shadows: 'corporate',
92
+ radius: 'sharp',
93
+ },
94
+ // E-commerce domains
95
+ ecommerce: {
96
+ primary: '#7c3aed',
97
+ primaryHover: '#6d28d9',
98
+ secondary: '#faf5ff',
99
+ accent: '#f97316',
100
+ muted: '#f3e8ff',
101
+ background: '#ffffff',
102
+ foreground: '#1e1b4b',
103
+ border: '#e9d5ff',
104
+ metaphor: 'vibrant marketplace',
105
+ shadows: 'playful',
106
+ radius: 'rounded',
107
+ },
108
+ retail: {
109
+ primary: '#7c3aed',
110
+ primaryHover: '#6d28d9',
111
+ secondary: '#faf5ff',
112
+ accent: '#f97316',
113
+ muted: '#f3e8ff',
114
+ background: '#ffffff',
115
+ foreground: '#1e1b4b',
116
+ border: '#e9d5ff',
117
+ metaphor: 'vibrant marketplace',
118
+ shadows: 'playful',
119
+ radius: 'rounded',
120
+ },
121
+ // Game domains
122
+ game: {
123
+ primary: '#dc2626',
124
+ primaryHover: '#b91c1c',
125
+ secondary: '#18181b',
126
+ accent: '#facc15',
127
+ muted: '#27272a',
128
+ background: '#09090b',
129
+ foreground: '#fafafa',
130
+ border: '#3f3f46',
131
+ metaphor: 'energetic action',
132
+ shadows: 'dramatic',
133
+ radius: 'sharp',
134
+ },
135
+ gaming: {
136
+ primary: '#dc2626',
137
+ primaryHover: '#b91c1c',
138
+ secondary: '#18181b',
139
+ accent: '#facc15',
140
+ muted: '#27272a',
141
+ background: '#09090b',
142
+ foreground: '#fafafa',
143
+ border: '#3f3f46',
144
+ metaphor: 'energetic action',
145
+ shadows: 'dramatic',
146
+ radius: 'sharp',
147
+ },
148
+ // Education domains
149
+ education: {
150
+ primary: '#2563eb',
151
+ primaryHover: '#1d4ed8',
152
+ secondary: '#eff6ff',
153
+ accent: '#10b981',
154
+ muted: '#dbeafe',
155
+ background: '#ffffff',
156
+ foreground: '#1e3a8a',
157
+ border: '#bfdbfe',
158
+ metaphor: 'clarity and learning',
159
+ shadows: 'soft',
160
+ radius: 'friendly',
161
+ },
162
+ learning: {
163
+ primary: '#2563eb',
164
+ primaryHover: '#1d4ed8',
165
+ secondary: '#eff6ff',
166
+ accent: '#10b981',
167
+ muted: '#dbeafe',
168
+ background: '#ffffff',
169
+ foreground: '#1e3a8a',
170
+ border: '#bfdbfe',
171
+ metaphor: 'clarity and learning',
172
+ shadows: 'soft',
173
+ radius: 'friendly',
174
+ },
175
+ };
176
+
177
+ // Shadow presets
178
+ const SHADOW_PRESETS: Record<string, ShadowConfig> = {
179
+ 'soft-organic': {
180
+ main: '0 4px 12px -2px rgba(21, 128, 61, 0.15), 0 2px 6px -2px rgba(21, 128, 61, 0.1)',
181
+ sm: '0 2px 4px -1px rgba(21, 128, 61, 0.1)',
182
+ lg: '0 16px 32px -8px rgba(21, 128, 61, 0.2), 0 8px 16px -4px rgba(21, 128, 61, 0.1)',
183
+ hover: '0 12px 24px -6px rgba(21, 128, 61, 0.25)',
184
+ },
185
+ minimal: {
186
+ main: '0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06)',
187
+ sm: '0 1px 2px rgba(0, 0, 0, 0.04)',
188
+ lg: '0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)',
189
+ hover: '0 10px 40px -10px rgba(0, 0, 0, 0.15)',
190
+ },
191
+ corporate: {
192
+ main: '0 2px 8px -2px rgba(15, 23, 42, 0.12)',
193
+ sm: '0 1px 3px rgba(15, 23, 42, 0.08)',
194
+ lg: '0 12px 28px -6px rgba(15, 23, 42, 0.18)',
195
+ hover: '0 16px 36px -8px rgba(15, 23, 42, 0.22)',
196
+ },
197
+ playful: {
198
+ main: '0 4px 14px -3px rgba(124, 58, 237, 0.2)',
199
+ sm: '0 2px 6px rgba(124, 58, 237, 0.12)',
200
+ lg: '0 18px 36px -10px rgba(124, 58, 237, 0.25)',
201
+ hover: '0 20px 40px -12px rgba(124, 58, 237, 0.3)',
202
+ },
203
+ dramatic: {
204
+ main: '0 4px 20px -4px rgba(0, 0, 0, 0.5)',
205
+ sm: '0 2px 8px rgba(0, 0, 0, 0.4)',
206
+ lg: '0 20px 50px -12px rgba(0, 0, 0, 0.6)',
207
+ hover: '0 24px 60px -16px rgba(0, 0, 0, 0.7)',
208
+ },
209
+ soft: {
210
+ main: '0 2px 8px -2px rgba(37, 99, 235, 0.15)',
211
+ sm: '0 1px 4px rgba(37, 99, 235, 0.1)',
212
+ lg: '0 12px 28px -6px rgba(37, 99, 235, 0.2)',
213
+ hover: '0 16px 36px -8px rgba(37, 99, 235, 0.25)',
214
+ },
215
+ };
216
+
217
+ // Radius presets
218
+ const RADIUS_PRESETS: Record<string, RadiusConfig> = {
219
+ organic: { sm: '6px', md: '12px', lg: '16px', xl: '24px' },
220
+ gentle: { sm: '4px', md: '8px', lg: '12px', xl: '16px' },
221
+ sharp: { sm: '2px', md: '4px', lg: '6px', xl: '8px' },
222
+ rounded: { sm: '8px', md: '16px', lg: '24px', xl: '32px' },
223
+ friendly: { sm: '6px', md: '10px', lg: '14px', xl: '20px' },
224
+ };
225
+
226
+ interface ThemeConfig {
227
+ primary: string;
228
+ primaryHover: string;
229
+ secondary: string;
230
+ accent: string;
231
+ muted: string;
232
+ background: string;
233
+ foreground: string;
234
+ border: string;
235
+ metaphor: string;
236
+ shadows: string;
237
+ radius: string;
238
+ }
239
+
240
+ interface ShadowConfig {
241
+ main: string;
242
+ sm: string;
243
+ lg: string;
244
+ hover: string;
245
+ }
246
+
247
+ interface RadiusConfig {
248
+ sm: string;
249
+ md: string;
250
+ lg: string;
251
+ xl: string;
252
+ }
253
+
254
+ interface OrbitalSchema {
255
+ name: string;
256
+ version?: string;
257
+ description?: string;
258
+ orbitals?: Array<{
259
+ name: string;
260
+ entity?: {
261
+ name: string;
262
+ fields?: Array<{ name: string; type: string }>;
263
+ };
264
+ }>;
265
+ traits?: Array<{ name: string; category?: string }>;
266
+ }
267
+
268
+ /**
269
+ * Detect domain from schema content
270
+ */
271
+ function detectDomain(schema: OrbitalSchema): string {
272
+ const schemaName = schema.name.toLowerCase();
273
+ const description = (schema.description || '').toLowerCase();
274
+
275
+ // Check schema name first
276
+ for (const domain of Object.keys(DOMAIN_THEMES)) {
277
+ if (schemaName.includes(domain)) {
278
+ return domain;
279
+ }
280
+ }
281
+
282
+ // Check description
283
+ for (const domain of Object.keys(DOMAIN_THEMES)) {
284
+ if (description.includes(domain)) {
285
+ return domain;
286
+ }
287
+ }
288
+
289
+ // Check entity names
290
+ const entityNames = (schema.orbitals || [])
291
+ .map(o => (o.entity?.name || '').toLowerCase())
292
+ .join(' ');
293
+
294
+ // Domain keywords
295
+ const domainKeywords: Record<string, string[]> = {
296
+ garden: ['plant', 'seed', 'garden', 'grow', 'harvest', 'cultivate', 'soil', 'water', 'farm'],
297
+ healthcare: ['patient', 'doctor', 'hospital', 'medical', 'health', 'diagnosis', 'treatment'],
298
+ finance: ['account', 'transaction', 'payment', 'balance', 'transfer', 'bank', 'money'],
299
+ ecommerce: ['product', 'cart', 'order', 'checkout', 'inventory', 'shop', 'store'],
300
+ game: ['player', 'score', 'level', 'enemy', 'character', 'game', 'sprite'],
301
+ education: ['course', 'student', 'lesson', 'quiz', 'learning', 'teacher', 'class'],
302
+ };
303
+
304
+ for (const [domain, keywords] of Object.entries(domainKeywords)) {
305
+ for (const keyword of keywords) {
306
+ if (entityNames.includes(keyword) || schemaName.includes(keyword) || description.includes(keyword)) {
307
+ return domain;
308
+ }
309
+ }
310
+ }
311
+
312
+ // Default to minimalist if no domain detected
313
+ return 'default';
314
+ }
315
+
316
+ /**
317
+ * Generate theme CSS from config
318
+ */
319
+ function generateThemeCSS(themeName: string, config: ThemeConfig): string {
320
+ const shadows = SHADOW_PRESETS[config.shadows] || SHADOW_PRESETS.minimal;
321
+ const radius = RADIUS_PRESETS[config.radius] || RADIUS_PRESETS.gentle;
322
+
323
+ return `/**
324
+ * ${themeName} Theme
325
+ *
326
+ * Auto-generated theme based on domain: ${config.metaphor}
327
+ * Generated at: ${new Date().toISOString()}
328
+ */
329
+
330
+ [data-design-theme="${themeName}"] {
331
+ /* === Color Palette === */
332
+ --color-primary: ${config.primary};
333
+ --color-primary-hover: ${config.primaryHover};
334
+ --color-primary-foreground: #ffffff;
335
+
336
+ --color-secondary: ${config.secondary};
337
+ --color-secondary-hover: ${adjustBrightness(config.secondary, -5)};
338
+ --color-secondary-foreground: ${config.foreground};
339
+
340
+ --color-accent: ${config.accent};
341
+ --color-accent-foreground: #ffffff;
342
+
343
+ --color-muted: ${config.muted};
344
+ --color-muted-foreground: ${adjustBrightness(config.foreground, 30)};
345
+
346
+ --color-background: ${config.background};
347
+ --color-foreground: ${config.foreground};
348
+ --color-card: #ffffff;
349
+ --color-card-foreground: ${config.foreground};
350
+ --color-border: ${config.border};
351
+ --color-input: ${config.border};
352
+ --color-ring: ${config.primary};
353
+
354
+ /* === Shadows === */
355
+ --shadow-main: ${shadows.main};
356
+ --shadow-sm: ${shadows.sm};
357
+ --shadow-lg: ${shadows.lg};
358
+ --shadow-inner: inset 0 2px 4px rgba(0, 0, 0, 0.05);
359
+ --shadow-none: none;
360
+ --shadow-hover: ${shadows.hover};
361
+ --shadow-active: ${shadows.sm};
362
+
363
+ /* === Border Radius === */
364
+ --radius-none: 0px;
365
+ --radius-sm: ${radius.sm};
366
+ --radius-md: ${radius.md};
367
+ --radius-lg: ${radius.lg};
368
+ --radius-xl: ${radius.xl};
369
+ --radius-full: 9999px;
370
+
371
+ /* === Border Width === */
372
+ --border-width: 1px;
373
+ --border-width-thin: 1px;
374
+ --border-width-thick: 2px;
375
+
376
+ /* === Typography === */
377
+ --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
378
+ --font-weight-normal: 400;
379
+ --font-weight-medium: 500;
380
+ --font-weight-bold: 600;
381
+ --letter-spacing: -0.01em;
382
+ --line-height: 1.6;
383
+
384
+ /* === Transitions === */
385
+ --transition-fast: 150ms;
386
+ --transition-normal: 250ms;
387
+ --transition-slow: 400ms;
388
+ --transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
389
+
390
+ /* === Hover/Active Transforms === */
391
+ --hover-scale: 1.02;
392
+ --hover-translate-y: -2px;
393
+ --active-scale: 0.98;
394
+
395
+ /* === Focus Ring === */
396
+ --focus-ring-width: 2px;
397
+ --focus-ring-offset: 2px;
398
+ --focus-ring-color: ${config.primary};
399
+
400
+ /* === Card Specific === */
401
+ --card-hover-shadow: ${shadows.hover};
402
+ --card-hover-transform: translateY(-2px) scale(1.01);
403
+
404
+ /* === Button Specific === */
405
+ --button-active-transform: scale(0.98);
406
+ }
407
+ `;
408
+ }
409
+
410
+ /**
411
+ * Adjust color brightness (simple implementation)
412
+ */
413
+ function adjustBrightness(hex: string, percent: number): string {
414
+ // Simple brightness adjustment - in production use a proper color library
415
+ const num = parseInt(hex.replace('#', ''), 16);
416
+ const amt = Math.round(2.55 * percent);
417
+ const R = Math.max(0, Math.min(255, (num >> 16) + amt));
418
+ const G = Math.max(0, Math.min(255, ((num >> 8) & 0x00ff) + amt));
419
+ const B = Math.max(0, Math.min(255, (num & 0x0000ff) + amt));
420
+ return `#${(0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1)}`;
421
+ }
422
+
423
+ /**
424
+ * Main function to generate theme from schema
425
+ */
426
+ export function generateThemeFromSchema(schemaPath: string, outputPath?: string): string {
427
+ // Read and parse schema
428
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
429
+ const schema: OrbitalSchema = JSON.parse(schemaContent);
430
+
431
+ // Detect domain
432
+ const domain = detectDomain(schema);
433
+ console.log(`Detected domain: ${domain}`);
434
+
435
+ // Get theme config
436
+ const themeConfig = DOMAIN_THEMES[domain] || {
437
+ primary: '#18181b',
438
+ primaryHover: '#27272a',
439
+ secondary: '#fafafa',
440
+ accent: '#18181b',
441
+ muted: '#f4f4f5',
442
+ background: '#ffffff',
443
+ foreground: '#18181b',
444
+ border: '#e4e4e7',
445
+ metaphor: 'neutral professional',
446
+ shadows: 'minimal',
447
+ radius: 'gentle',
448
+ };
449
+
450
+ // Generate theme name from schema name
451
+ const themeName = schema.name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
452
+
453
+ // Generate CSS
454
+ const css = generateThemeCSS(themeName, themeConfig);
455
+
456
+ // Write to file if output path provided
457
+ if (outputPath) {
458
+ fs.writeFileSync(outputPath, css);
459
+ console.log(`Theme generated: ${outputPath}`);
460
+ }
461
+
462
+ return css;
463
+ }
464
+
465
+ // CLI usage
466
+ if (require.main === module) {
467
+ const args = process.argv.slice(2);
468
+ if (args.length < 1) {
469
+ console.error('Usage: npx tsx generate-theme-from-schema.ts <schema.orb> [output.css]');
470
+ process.exit(1);
471
+ }
472
+
473
+ const schemaPath = args[0];
474
+ const outputPath = args[1] || path.join(
475
+ path.dirname(schemaPath),
476
+ `${path.basename(schemaPath, '.orb')}-theme.css`
477
+ );
478
+
479
+ generateThemeFromSchema(schemaPath, outputPath);
480
+ }