@nexart/ui-renderer 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -4
- package/dist/compiler.d.ts +18 -2
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +25 -11
- package/dist/index.d.ts +19 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -7
- package/dist/presets/backgrounds.d.ts +14 -0
- package/dist/presets/backgrounds.d.ts.map +1 -0
- package/dist/presets/backgrounds.js +222 -0
- package/dist/presets/index.d.ts +7 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +6 -0
- package/dist/presets/primitives.d.ts +16 -0
- package/dist/presets/primitives.d.ts.map +1 -0
- package/dist/presets/primitives.js +282 -0
- package/dist/presets/sketch-wrapper.d.ts +14 -0
- package/dist/presets/sketch-wrapper.d.ts.map +1 -0
- package/dist/presets/sketch-wrapper.js +70 -0
- package/dist/preview/code-renderer.d.ts +25 -0
- package/dist/preview/code-renderer.d.ts.map +1 -0
- package/dist/preview/code-renderer.js +651 -0
- package/dist/preview/primitives/sketch.d.ts +14 -0
- package/dist/preview/primitives/sketch.d.ts.map +1 -0
- package/dist/preview/primitives/sketch.js +407 -0
- package/dist/preview/renderer.d.ts +1 -1
- package/dist/preview/renderer.d.ts.map +1 -1
- package/dist/preview/renderer.js +23 -13
- package/dist/preview/unified-renderer.d.ts +16 -0
- package/dist/preview/unified-renderer.d.ts.map +1 -0
- package/dist/preview/unified-renderer.js +270 -0
- package/dist/system.d.ts +7 -3
- package/dist/system.d.ts.map +1 -1
- package/dist/system.js +187 -11
- package/dist/types.d.ts +125 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -3
- package/package.json +2 -2
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Renderer - Handles background, primitive, and sketch elements
|
|
3
|
+
* Dispatches rendering per element type in order: background → primitive → sketch
|
|
4
|
+
* All elements render to the same canvas/context with shared timing
|
|
5
|
+
*/
|
|
6
|
+
import { compileBackgroundPreset, getPaletteColors } from '../presets/backgrounds';
|
|
7
|
+
import { compilePrimitive } from '../presets/primitives';
|
|
8
|
+
import { wrapSketch, validateSketchSafety } from '../presets/sketch-wrapper';
|
|
9
|
+
import { createP5Runtime } from './code-renderer';
|
|
10
|
+
function validateSketchElement(element) {
|
|
11
|
+
const { safe, warnings } = validateSketchSafety(element.code);
|
|
12
|
+
if (!safe) {
|
|
13
|
+
throw new Error(`Unsafe sketch code: ${warnings.join(', ')}`);
|
|
14
|
+
}
|
|
15
|
+
if (warnings.length > 0) {
|
|
16
|
+
console.warn('[SDK] Sketch warnings:', warnings);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function inferPalette(elements) {
|
|
20
|
+
for (const el of elements) {
|
|
21
|
+
if (el.type === 'background' && el.palette) {
|
|
22
|
+
return el.palette;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return 'offwhite-dark';
|
|
26
|
+
}
|
|
27
|
+
function extractFunctionBody(code, funcName) {
|
|
28
|
+
const pattern = new RegExp(`function\\s+${funcName}\\s*\\(\\s*\\)\\s*\\{`);
|
|
29
|
+
const match = code.match(pattern);
|
|
30
|
+
if (!match || match.index === undefined)
|
|
31
|
+
return '';
|
|
32
|
+
const startIndex = match.index + match[0].length;
|
|
33
|
+
let braceCount = 1;
|
|
34
|
+
let i = startIndex;
|
|
35
|
+
while (i < code.length && braceCount > 0) {
|
|
36
|
+
if (code[i] === '{')
|
|
37
|
+
braceCount++;
|
|
38
|
+
if (code[i] === '}')
|
|
39
|
+
braceCount--;
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
return code.slice(startIndex, i - 1).trim();
|
|
43
|
+
}
|
|
44
|
+
function extractDrawCode(code) {
|
|
45
|
+
const body = extractFunctionBody(code, 'draw');
|
|
46
|
+
return body || code.trim();
|
|
47
|
+
}
|
|
48
|
+
function extractSetupCode(code) {
|
|
49
|
+
return extractFunctionBody(code, 'setup');
|
|
50
|
+
}
|
|
51
|
+
function compileBackgroundElement(element, palette) {
|
|
52
|
+
const code = compileBackgroundPreset(element.preset, element.palette || palette, element.loop || { duration: 120 });
|
|
53
|
+
return {
|
|
54
|
+
setup: extractSetupCode(code),
|
|
55
|
+
draw: extractDrawCode(code),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function compilePrimitiveElement(element, foreground) {
|
|
59
|
+
const primitiveCode = compilePrimitive({
|
|
60
|
+
name: element.name,
|
|
61
|
+
count: element.count,
|
|
62
|
+
strokeWeight: element.strokeWeight,
|
|
63
|
+
motion: element.motion,
|
|
64
|
+
color: element.color,
|
|
65
|
+
opacity: element.opacity,
|
|
66
|
+
}, foreground);
|
|
67
|
+
return { setup: '', draw: primitiveCode };
|
|
68
|
+
}
|
|
69
|
+
function compileSketchElement(element) {
|
|
70
|
+
validateSketchElement(element);
|
|
71
|
+
const wrapped = wrapSketch({
|
|
72
|
+
code: element.code,
|
|
73
|
+
normalize: element.normalize !== false,
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
setup: extractSetupCode(wrapped),
|
|
77
|
+
draw: extractDrawCode(wrapped),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function renderUnifiedSystem(system, canvas, options = {}) {
|
|
81
|
+
const { showBadge = true, onPreview, onComplete, onError } = options;
|
|
82
|
+
canvas.width = system.width;
|
|
83
|
+
canvas.height = system.height;
|
|
84
|
+
const ctx = canvas.getContext('2d');
|
|
85
|
+
let animationId = null;
|
|
86
|
+
let isRunning = false;
|
|
87
|
+
let isDestroyed = false;
|
|
88
|
+
const palette = inferPalette(system.elements);
|
|
89
|
+
const colors = getPaletteColors(palette);
|
|
90
|
+
const totalFrames = system.loop?.duration ?? 120;
|
|
91
|
+
const sortedElements = [...system.elements].sort((a, b) => {
|
|
92
|
+
const order = { background: 0, primitive: 1, sketch: 2 };
|
|
93
|
+
return (order[a.type] ?? 3) - (order[b.type] ?? 3);
|
|
94
|
+
});
|
|
95
|
+
const backgroundElements = [];
|
|
96
|
+
const primitiveElements = [];
|
|
97
|
+
const sketchElements = [];
|
|
98
|
+
for (const element of sortedElements) {
|
|
99
|
+
switch (element.type) {
|
|
100
|
+
case 'background':
|
|
101
|
+
backgroundElements.push(compileBackgroundElement(element, palette));
|
|
102
|
+
break;
|
|
103
|
+
case 'primitive':
|
|
104
|
+
primitiveElements.push(compilePrimitiveElement(element, colors.foreground));
|
|
105
|
+
break;
|
|
106
|
+
case 'sketch':
|
|
107
|
+
sketchElements.push(compileSketchElement(element));
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const hasElements = system.elements.length > 0;
|
|
112
|
+
const hasRenderers = backgroundElements.length > 0 || primitiveElements.length > 0 || sketchElements.length > 0;
|
|
113
|
+
console.log('[UIRenderer] Compiled elements:', {
|
|
114
|
+
background: backgroundElements.length,
|
|
115
|
+
primitive: primitiveElements.length,
|
|
116
|
+
sketch: sketchElements.length,
|
|
117
|
+
backgroundDrawEmpty: backgroundElements.map(e => !e.draw.trim()),
|
|
118
|
+
primitiveDrawEmpty: primitiveElements.map(e => !e.draw.trim()),
|
|
119
|
+
sketchDrawEmpty: sketchElements.map(e => !e.draw.trim()),
|
|
120
|
+
});
|
|
121
|
+
if (hasElements && !hasRenderers) {
|
|
122
|
+
throw new Error('[UIRenderer] No renderers executed despite having elements');
|
|
123
|
+
}
|
|
124
|
+
const drawBadge = () => {
|
|
125
|
+
if (!showBadge)
|
|
126
|
+
return;
|
|
127
|
+
const text = '⚠️ Preview Renderer (Non-Canonical)';
|
|
128
|
+
ctx.font = '12px -apple-system, sans-serif';
|
|
129
|
+
const metrics = ctx.measureText(text);
|
|
130
|
+
const padding = 8;
|
|
131
|
+
const badgeWidth = metrics.width + padding * 2;
|
|
132
|
+
const badgeHeight = 24;
|
|
133
|
+
const x = system.width - badgeWidth - 10;
|
|
134
|
+
const y = 10;
|
|
135
|
+
ctx.fillStyle = 'rgba(255, 100, 100, 0.15)';
|
|
136
|
+
ctx.strokeStyle = 'rgba(255, 100, 100, 0.4)';
|
|
137
|
+
ctx.lineWidth = 1;
|
|
138
|
+
ctx.beginPath();
|
|
139
|
+
ctx.roundRect(x, y, badgeWidth, badgeHeight, 4);
|
|
140
|
+
ctx.fill();
|
|
141
|
+
ctx.stroke();
|
|
142
|
+
ctx.fillStyle = '#ff9999';
|
|
143
|
+
ctx.fillText(text, x + padding, y + 16);
|
|
144
|
+
};
|
|
145
|
+
const executeCode = (p, code, frame, t) => {
|
|
146
|
+
if (!code.trim())
|
|
147
|
+
return;
|
|
148
|
+
try {
|
|
149
|
+
const wrappedCode = new Function('p', 'frameCount', 't', 'time', 'tGlobal', `with(p) { ${code} }`);
|
|
150
|
+
wrappedCode(p, frame, t, t * (totalFrames / 30), t);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
console.error('[UIRenderer] Code execution error:', err);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const runSetup = (p) => {
|
|
157
|
+
let setupRan = false;
|
|
158
|
+
for (const el of backgroundElements) {
|
|
159
|
+
if (el.setup) {
|
|
160
|
+
executeCode(p, el.setup, 0, 0);
|
|
161
|
+
setupRan = true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
for (const el of primitiveElements) {
|
|
165
|
+
if (el.setup) {
|
|
166
|
+
executeCode(p, el.setup, 0, 0);
|
|
167
|
+
setupRan = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const el of sketchElements) {
|
|
171
|
+
if (el.setup) {
|
|
172
|
+
executeCode(p, el.setup, 0, 0);
|
|
173
|
+
setupRan = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!setupRan) {
|
|
177
|
+
p.strokeWeight(1.5);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const runDraw = (p, frame, t) => {
|
|
181
|
+
let elementsRendered = 0;
|
|
182
|
+
const hasBackground = backgroundElements.length > 0;
|
|
183
|
+
if (!hasBackground) {
|
|
184
|
+
p.background(colors.background);
|
|
185
|
+
}
|
|
186
|
+
for (const el of backgroundElements) {
|
|
187
|
+
if (el.draw) {
|
|
188
|
+
console.log('[UIRenderer] background rendered');
|
|
189
|
+
executeCode(p, el.draw, frame, t);
|
|
190
|
+
elementsRendered++;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const el of primitiveElements) {
|
|
194
|
+
if (el.draw) {
|
|
195
|
+
console.log('[UIRenderer] primitive rendered');
|
|
196
|
+
executeCode(p, el.draw, frame, t);
|
|
197
|
+
elementsRendered++;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
for (const el of sketchElements) {
|
|
201
|
+
if (el.draw) {
|
|
202
|
+
console.log('[UIRenderer] sketch rendered');
|
|
203
|
+
executeCode(p, el.draw, frame, t);
|
|
204
|
+
elementsRendered++;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (hasElements && elementsRendered === 0) {
|
|
208
|
+
throw new Error('[UIRenderer] Zero renderers executed in frame despite having elements');
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const renderLoop = () => {
|
|
212
|
+
if (isDestroyed)
|
|
213
|
+
return;
|
|
214
|
+
try {
|
|
215
|
+
let frame = 0;
|
|
216
|
+
const p = createP5Runtime(canvas, system.width, system.height, system.seed);
|
|
217
|
+
runSetup(p);
|
|
218
|
+
const loop = () => {
|
|
219
|
+
if (!isRunning || isDestroyed)
|
|
220
|
+
return;
|
|
221
|
+
const t = frame / totalFrames;
|
|
222
|
+
p.randomSeed(system.seed);
|
|
223
|
+
p.noiseSeed(system.seed);
|
|
224
|
+
runDraw(p, frame, t);
|
|
225
|
+
drawBadge();
|
|
226
|
+
frame = (frame + 1) % totalFrames;
|
|
227
|
+
animationId = requestAnimationFrame(loop);
|
|
228
|
+
};
|
|
229
|
+
isRunning = true;
|
|
230
|
+
animationId = requestAnimationFrame(loop);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
234
|
+
onError?.(err);
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const render = () => {
|
|
239
|
+
if (isDestroyed)
|
|
240
|
+
return;
|
|
241
|
+
renderLoop();
|
|
242
|
+
};
|
|
243
|
+
const start = () => {
|
|
244
|
+
if (isDestroyed)
|
|
245
|
+
return;
|
|
246
|
+
stop();
|
|
247
|
+
renderLoop();
|
|
248
|
+
};
|
|
249
|
+
const stop = () => {
|
|
250
|
+
isRunning = false;
|
|
251
|
+
if (animationId !== null) {
|
|
252
|
+
cancelAnimationFrame(animationId);
|
|
253
|
+
animationId = null;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const destroy = () => {
|
|
257
|
+
isDestroyed = true;
|
|
258
|
+
stop();
|
|
259
|
+
ctx.clearRect(0, 0, system.width, system.height);
|
|
260
|
+
};
|
|
261
|
+
const renderer = {
|
|
262
|
+
render,
|
|
263
|
+
start,
|
|
264
|
+
stop,
|
|
265
|
+
destroy,
|
|
266
|
+
isCanonical: false,
|
|
267
|
+
isArchival: false,
|
|
268
|
+
};
|
|
269
|
+
return renderer;
|
|
270
|
+
}
|
package/dist/system.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.
|
|
2
|
+
* @nexart/ui-renderer v0.4.0 - System Creation & Validation
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Opinionated generative design system with aesthetic guardrails.
|
|
5
|
+
* Supports background, primitive, and sketch element types.
|
|
5
6
|
*/
|
|
6
|
-
import type { NexArtSystemInput, NexArtSystem, ValidationResult } from './types';
|
|
7
|
+
import type { NexArtSystemInput, NexArtSystem, DeclarativeSystem, NexArtCodeSystem, UnifiedSystem, ValidationResult } from './types';
|
|
7
8
|
export declare function validateSystem(input: NexArtSystemInput): ValidationResult;
|
|
8
9
|
export declare function createSystem(input: NexArtSystemInput): NexArtSystem;
|
|
10
|
+
export declare function isCodeModeSystem(system: NexArtSystem): system is NexArtCodeSystem;
|
|
11
|
+
export declare function isUnifiedModeSystem(system: NexArtSystem): system is UnifiedSystem;
|
|
12
|
+
export declare function isDeclarativeSystem(system: NexArtSystem): system is DeclarativeSystem;
|
|
9
13
|
//# sourceMappingURL=system.d.ts.map
|
package/dist/system.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../src/system.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../src/system.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EAEZ,iBAAiB,EAEjB,gBAAgB,EAEhB,aAAa,EAGb,gBAAgB,EAKjB,MAAM,SAAS,CAAC;AAiTjB,wBAAgB,cAAc,CAAC,KAAK,EAAE,iBAAiB,GAAG,gBAAgB,CAiBzE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CA6DnE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,IAAI,gBAAgB,CAEjF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,IAAI,aAAa,CAEjF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,IAAI,iBAAiB,CAErF"}
|
package/dist/system.js
CHANGED
|
@@ -1,12 +1,97 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.
|
|
2
|
+
* @nexart/ui-renderer v0.4.0 - System Creation & Validation
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Opinionated generative design system with aesthetic guardrails.
|
|
5
|
+
* Supports background, primitive, and sketch element types.
|
|
5
6
|
*/
|
|
6
|
-
const CURRENT_VERSION = '0.
|
|
7
|
+
const CURRENT_VERSION = '0.4';
|
|
8
|
+
function isCodeSystem(input) {
|
|
9
|
+
return 'type' in input && input.type === 'code';
|
|
10
|
+
}
|
|
11
|
+
function isUnifiedSystem(input) {
|
|
12
|
+
if (!('elements' in input) || !Array.isArray(input.elements)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const elements = input.elements;
|
|
16
|
+
return elements.length > 0 && elements.some((el) => el.type === 'background' || el.type === 'primitive' || el.type === 'sketch');
|
|
17
|
+
}
|
|
18
|
+
function isDeclarativeInput(input) {
|
|
19
|
+
return 'elements' in input && 'background' in input && Array.isArray(input.elements);
|
|
20
|
+
}
|
|
7
21
|
const VALID_ELEMENT_TYPES = ['dots', 'lines', 'waves', 'grid', 'flowField', 'orbits'];
|
|
8
22
|
const VALID_MOTION_SOURCES = ['none', 'time', 'seed'];
|
|
9
23
|
const VALID_TEXTURES = ['none', 'noise', 'grain'];
|
|
24
|
+
const VALID_BACKGROUND_PRESETS = ['layered-waves', 'soft-noise-field', 'orbital-lines', 'flowing-stripes', 'minimal-grid'];
|
|
25
|
+
const VALID_PRIMITIVE_NAMES = ['waves', 'dots', 'lines', 'grid', 'flow', 'orbits', 'circles', 'stripes'];
|
|
26
|
+
const VALID_PALETTES = ['offwhite-dark', 'midnight', 'warm-neutral', 'ocean', 'sunset', 'forest'];
|
|
27
|
+
function validateUnifiedElement(el, index) {
|
|
28
|
+
const errors = [];
|
|
29
|
+
const prefix = `Element[${index}]`;
|
|
30
|
+
if (!el.type) {
|
|
31
|
+
errors.push(`${prefix}: type is required`);
|
|
32
|
+
return errors;
|
|
33
|
+
}
|
|
34
|
+
switch (el.type) {
|
|
35
|
+
case 'background':
|
|
36
|
+
if (!el.preset || !VALID_BACKGROUND_PRESETS.includes(el.preset)) {
|
|
37
|
+
errors.push(`${prefix}: preset must be one of: ${VALID_BACKGROUND_PRESETS.join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
if (el.palette && !VALID_PALETTES.includes(el.palette)) {
|
|
40
|
+
errors.push(`${prefix}: palette must be one of: ${VALID_PALETTES.join(', ')}`);
|
|
41
|
+
}
|
|
42
|
+
if (el.loop) {
|
|
43
|
+
if (typeof el.loop.duration !== 'number' || el.loop.duration < 1 || el.loop.duration > 600) {
|
|
44
|
+
errors.push(`${prefix}: loop.duration must be between 1 and 600`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
case 'primitive':
|
|
49
|
+
if (!el.name || !VALID_PRIMITIVE_NAMES.includes(el.name)) {
|
|
50
|
+
errors.push(`${prefix}: name must be one of: ${VALID_PRIMITIVE_NAMES.join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
if (el.count !== undefined && (el.count < 1 || el.count > 100)) {
|
|
53
|
+
errors.push(`${prefix}: count must be between 1 and 100`);
|
|
54
|
+
}
|
|
55
|
+
if (el.opacity !== undefined && (el.opacity < 0 || el.opacity > 1)) {
|
|
56
|
+
errors.push(`${prefix}: opacity must be between 0 and 1`);
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
case 'sketch':
|
|
60
|
+
if (!el.code || typeof el.code !== 'string' || el.code.trim().length === 0) {
|
|
61
|
+
errors.push(`${prefix}: code is required and must be a non-empty string`);
|
|
62
|
+
}
|
|
63
|
+
if (el.totalFrames !== undefined && (el.totalFrames < 1 || el.totalFrames > 600)) {
|
|
64
|
+
errors.push(`${prefix}: totalFrames must be between 1 and 600`);
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
default:
|
|
68
|
+
errors.push(`${prefix}: type must be "background", "primitive", or "sketch"`);
|
|
69
|
+
}
|
|
70
|
+
return errors;
|
|
71
|
+
}
|
|
72
|
+
function validateUnifiedSystem(input) {
|
|
73
|
+
const errors = [];
|
|
74
|
+
if (!Array.isArray(input.elements) || input.elements.length === 0) {
|
|
75
|
+
errors.push('elements array is required and must not be empty');
|
|
76
|
+
return errors;
|
|
77
|
+
}
|
|
78
|
+
input.elements.forEach((el, i) => {
|
|
79
|
+
errors.push(...validateUnifiedElement(el, i));
|
|
80
|
+
});
|
|
81
|
+
if (input.width !== undefined && (input.width < 1 || input.width > 4096)) {
|
|
82
|
+
errors.push('width must be between 1 and 4096');
|
|
83
|
+
}
|
|
84
|
+
if (input.height !== undefined && (input.height < 1 || input.height > 4096)) {
|
|
85
|
+
errors.push('height must be between 1 and 4096');
|
|
86
|
+
}
|
|
87
|
+
if (!input.loop || typeof input.loop.duration !== 'number') {
|
|
88
|
+
errors.push('loop.duration is required for unified systems');
|
|
89
|
+
}
|
|
90
|
+
else if (input.loop.duration < 1 || input.loop.duration > 600) {
|
|
91
|
+
errors.push('loop.duration must be between 1 and 600');
|
|
92
|
+
}
|
|
93
|
+
return errors;
|
|
94
|
+
}
|
|
10
95
|
function validateBackground(bg) {
|
|
11
96
|
const errors = [];
|
|
12
97
|
if (!bg.color || typeof bg.color !== 'string') {
|
|
@@ -112,7 +197,40 @@ function validateMotion(motion) {
|
|
|
112
197
|
}
|
|
113
198
|
return errors;
|
|
114
199
|
}
|
|
115
|
-
|
|
200
|
+
function validateCodeSystem(input) {
|
|
201
|
+
const errors = [];
|
|
202
|
+
if (input.type !== 'code') {
|
|
203
|
+
errors.push('type must be "code"');
|
|
204
|
+
}
|
|
205
|
+
if (!input.source || typeof input.source !== 'string') {
|
|
206
|
+
errors.push('source is required and must be a string');
|
|
207
|
+
}
|
|
208
|
+
if (input.source && input.source.trim().length === 0) {
|
|
209
|
+
errors.push('source cannot be empty');
|
|
210
|
+
}
|
|
211
|
+
if (!['static', 'loop'].includes(input.mode)) {
|
|
212
|
+
errors.push('mode must be "static" or "loop"');
|
|
213
|
+
}
|
|
214
|
+
if (typeof input.width !== 'number' || input.width < 1 || input.width > 4096) {
|
|
215
|
+
errors.push('width is required and must be between 1 and 4096');
|
|
216
|
+
}
|
|
217
|
+
if (typeof input.height !== 'number' || input.height < 1 || input.height > 4096) {
|
|
218
|
+
errors.push('height is required and must be between 1 and 4096');
|
|
219
|
+
}
|
|
220
|
+
if (input.mode === 'loop') {
|
|
221
|
+
if (typeof input.totalFrames !== 'number' || input.totalFrames < 1 || input.totalFrames > 600) {
|
|
222
|
+
errors.push('totalFrames is required for loop mode and must be between 1 and 600');
|
|
223
|
+
}
|
|
224
|
+
if (input.seed === undefined) {
|
|
225
|
+
errors.push('seed is required for loop mode to ensure determinism');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (input.seed !== undefined && typeof input.seed !== 'number') {
|
|
229
|
+
errors.push('seed must be a number');
|
|
230
|
+
}
|
|
231
|
+
return errors;
|
|
232
|
+
}
|
|
233
|
+
function validateDeclarativeSystem(input) {
|
|
116
234
|
const errors = [];
|
|
117
235
|
if (typeof input.seed !== 'number') {
|
|
118
236
|
errors.push('seed is required and must be a number');
|
|
@@ -134,6 +252,22 @@ export function validateSystem(input) {
|
|
|
134
252
|
if (input.motion) {
|
|
135
253
|
errors.push(...validateMotion(input.motion));
|
|
136
254
|
}
|
|
255
|
+
return errors;
|
|
256
|
+
}
|
|
257
|
+
export function validateSystem(input) {
|
|
258
|
+
const errors = [];
|
|
259
|
+
if (isCodeSystem(input)) {
|
|
260
|
+
errors.push(...validateCodeSystem(input));
|
|
261
|
+
}
|
|
262
|
+
else if (isUnifiedSystem(input)) {
|
|
263
|
+
errors.push(...validateUnifiedSystem(input));
|
|
264
|
+
}
|
|
265
|
+
else if (isDeclarativeInput(input)) {
|
|
266
|
+
errors.push(...validateDeclarativeSystem(input));
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
errors.push('Invalid system input: must be a code system, unified system, or declarative system');
|
|
270
|
+
}
|
|
137
271
|
return {
|
|
138
272
|
valid: errors.length === 0,
|
|
139
273
|
errors,
|
|
@@ -144,22 +278,64 @@ export function createSystem(input) {
|
|
|
144
278
|
if (!validation.valid) {
|
|
145
279
|
throw new Error(`Invalid system: ${validation.errors.join('; ')}`);
|
|
146
280
|
}
|
|
281
|
+
if (isCodeSystem(input)) {
|
|
282
|
+
const codeSystem = {
|
|
283
|
+
protocol: 'nexart',
|
|
284
|
+
systemType: 'code',
|
|
285
|
+
systemVersion: CURRENT_VERSION,
|
|
286
|
+
source: input.source,
|
|
287
|
+
mode: input.mode,
|
|
288
|
+
width: input.width,
|
|
289
|
+
height: input.height,
|
|
290
|
+
seed: input.seed ?? Math.floor(Math.random() * 2147483647),
|
|
291
|
+
totalFrames: input.totalFrames,
|
|
292
|
+
deterministic: true,
|
|
293
|
+
createdAt: new Date().toISOString(),
|
|
294
|
+
};
|
|
295
|
+
return codeSystem;
|
|
296
|
+
}
|
|
297
|
+
if (isUnifiedSystem(input)) {
|
|
298
|
+
const unifiedSystem = {
|
|
299
|
+
protocol: 'nexart',
|
|
300
|
+
systemType: 'unified',
|
|
301
|
+
systemVersion: CURRENT_VERSION,
|
|
302
|
+
seed: input.seed ?? Math.floor(Math.random() * 2147483647),
|
|
303
|
+
width: input.width ?? 800,
|
|
304
|
+
height: input.height ?? 800,
|
|
305
|
+
elements: input.elements,
|
|
306
|
+
loop: input.loop ?? { duration: 120 },
|
|
307
|
+
deterministic: true,
|
|
308
|
+
createdAt: new Date().toISOString(),
|
|
309
|
+
};
|
|
310
|
+
return unifiedSystem;
|
|
311
|
+
}
|
|
312
|
+
const declarativeInput = input;
|
|
147
313
|
const system = {
|
|
148
314
|
protocol: 'nexart',
|
|
149
|
-
|
|
150
|
-
|
|
315
|
+
systemType: 'declarative',
|
|
316
|
+
systemVersion: declarativeInput.version || CURRENT_VERSION,
|
|
317
|
+
seed: declarativeInput.seed,
|
|
151
318
|
background: {
|
|
152
|
-
color:
|
|
153
|
-
texture:
|
|
154
|
-
...(
|
|
319
|
+
color: declarativeInput.background.color,
|
|
320
|
+
texture: declarativeInput.background.texture || 'none',
|
|
321
|
+
...(declarativeInput.background.gradient && { gradient: declarativeInput.background.gradient }),
|
|
155
322
|
},
|
|
156
|
-
elements:
|
|
323
|
+
elements: declarativeInput.elements.map((el) => ({
|
|
157
324
|
...el,
|
|
158
325
|
opacity: el.opacity ?? 1,
|
|
159
326
|
})),
|
|
160
|
-
motion:
|
|
327
|
+
motion: declarativeInput.motion || { source: 'none' },
|
|
161
328
|
deterministic: true,
|
|
162
329
|
createdAt: new Date().toISOString(),
|
|
163
330
|
};
|
|
164
331
|
return system;
|
|
165
332
|
}
|
|
333
|
+
export function isCodeModeSystem(system) {
|
|
334
|
+
return 'systemType' in system && system.systemType === 'code';
|
|
335
|
+
}
|
|
336
|
+
export function isUnifiedModeSystem(system) {
|
|
337
|
+
return 'systemType' in system && system.systemType === 'unified';
|
|
338
|
+
}
|
|
339
|
+
export function isDeclarativeSystem(system) {
|
|
340
|
+
return !('systemType' in system) || system.systemType === 'declarative';
|
|
341
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,10 +1,107 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.
|
|
2
|
+
* @nexart/ui-renderer v0.4.0 - Type Definitions
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Opinionated generative design system with aesthetic guardrails.
|
|
5
5
|
* This SDK is non-canonical and for authoring/preview only.
|
|
6
6
|
*/
|
|
7
|
+
export declare const SDK_VERSION = "0.4.0";
|
|
8
|
+
export declare const AESTHETIC_DEFAULTS: {
|
|
9
|
+
readonly background: {
|
|
10
|
+
readonly r: 246;
|
|
11
|
+
readonly g: 245;
|
|
12
|
+
readonly b: 242;
|
|
13
|
+
};
|
|
14
|
+
readonly foreground: {
|
|
15
|
+
readonly r: 45;
|
|
16
|
+
readonly g: 45;
|
|
17
|
+
readonly b: 45;
|
|
18
|
+
};
|
|
19
|
+
readonly strokeWeight: {
|
|
20
|
+
readonly min: 0.5;
|
|
21
|
+
readonly max: 4;
|
|
22
|
+
readonly default: 1.5;
|
|
23
|
+
};
|
|
24
|
+
readonly density: {
|
|
25
|
+
readonly min: 3;
|
|
26
|
+
readonly max: 50;
|
|
27
|
+
readonly default: 12;
|
|
28
|
+
};
|
|
29
|
+
readonly motion: {
|
|
30
|
+
readonly speed: 0.5;
|
|
31
|
+
readonly easing: "sinusoidal";
|
|
32
|
+
};
|
|
33
|
+
readonly margins: {
|
|
34
|
+
readonly ratio: 0.08;
|
|
35
|
+
};
|
|
36
|
+
readonly loop: {
|
|
37
|
+
readonly defaultFrames: 120;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
export type MotionSpeed = 'slow' | 'medium' | 'fast';
|
|
41
|
+
export type StrokeWeightAuto = 'auto' | 'thin' | 'medium' | 'thick' | number;
|
|
42
|
+
export interface LoopConfig {
|
|
43
|
+
duration: number;
|
|
44
|
+
fps?: number;
|
|
45
|
+
}
|
|
46
|
+
export type BackgroundPreset = 'layered-waves' | 'soft-noise-field' | 'orbital-lines' | 'flowing-stripes' | 'minimal-grid';
|
|
47
|
+
export type ColorPalette = 'offwhite-dark' | 'midnight' | 'warm-neutral' | 'ocean' | 'sunset' | 'forest';
|
|
48
|
+
export interface BackgroundElement {
|
|
49
|
+
type: 'background';
|
|
50
|
+
preset: BackgroundPreset;
|
|
51
|
+
palette?: ColorPalette;
|
|
52
|
+
loop?: LoopConfig;
|
|
53
|
+
seed?: number;
|
|
54
|
+
}
|
|
55
|
+
export type PrimitiveName = 'waves' | 'dots' | 'lines' | 'grid' | 'flow' | 'orbits' | 'circles' | 'stripes';
|
|
56
|
+
export interface PrimitiveElement {
|
|
57
|
+
type: 'primitive';
|
|
58
|
+
name: PrimitiveName;
|
|
59
|
+
count?: number;
|
|
60
|
+
strokeWeight?: StrokeWeightAuto;
|
|
61
|
+
motion?: MotionSpeed;
|
|
62
|
+
color?: string;
|
|
63
|
+
opacity?: number;
|
|
64
|
+
loop?: LoopConfig;
|
|
65
|
+
seed?: number;
|
|
66
|
+
}
|
|
67
|
+
export interface SketchElement {
|
|
68
|
+
type: 'sketch';
|
|
69
|
+
code: string;
|
|
70
|
+
totalFrames?: number;
|
|
71
|
+
seed?: number;
|
|
72
|
+
normalize?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export type UnifiedElement = BackgroundElement | PrimitiveElement | SketchElement;
|
|
75
|
+
export interface UnifiedSystemInput {
|
|
76
|
+
version?: string;
|
|
77
|
+
seed?: number;
|
|
78
|
+
width?: number;
|
|
79
|
+
height?: number;
|
|
80
|
+
elements: UnifiedElement[];
|
|
81
|
+
loop?: LoopConfig;
|
|
82
|
+
}
|
|
83
|
+
export interface UnifiedSystem {
|
|
84
|
+
protocol: 'nexart';
|
|
85
|
+
systemType: 'unified';
|
|
86
|
+
systemVersion: string;
|
|
87
|
+
seed: number;
|
|
88
|
+
width: number;
|
|
89
|
+
height: number;
|
|
90
|
+
elements: UnifiedElement[];
|
|
91
|
+
loop: LoopConfig;
|
|
92
|
+
deterministic: true;
|
|
93
|
+
createdAt: string;
|
|
94
|
+
}
|
|
7
95
|
export type BackgroundTexture = 'none' | 'noise' | 'grain';
|
|
96
|
+
export interface CodeSystem {
|
|
97
|
+
type: 'code';
|
|
98
|
+
source: string;
|
|
99
|
+
mode: 'static' | 'loop';
|
|
100
|
+
width: number;
|
|
101
|
+
height: number;
|
|
102
|
+
seed?: number;
|
|
103
|
+
totalFrames?: number;
|
|
104
|
+
}
|
|
8
105
|
export interface BackgroundConfig {
|
|
9
106
|
color: string;
|
|
10
107
|
gradient?: {
|
|
@@ -65,21 +162,23 @@ export interface OrbitsElement {
|
|
|
65
162
|
color?: string;
|
|
66
163
|
opacity?: number;
|
|
67
164
|
}
|
|
68
|
-
export type
|
|
165
|
+
export type DeclarativeElement = DotsElement | LinesElement | WavesElement | GridElement | FlowFieldElement | OrbitsElement;
|
|
166
|
+
export type SystemElement = DeclarativeElement;
|
|
69
167
|
export type MotionSource = 'none' | 'time' | 'seed';
|
|
70
168
|
export interface MotionConfig {
|
|
71
169
|
source: MotionSource;
|
|
72
170
|
speed?: number;
|
|
73
171
|
}
|
|
74
|
-
export interface
|
|
172
|
+
export interface DeclarativeSystemInput {
|
|
75
173
|
version?: string;
|
|
76
174
|
seed: number;
|
|
77
175
|
background: BackgroundConfig;
|
|
78
176
|
elements: SystemElement[];
|
|
79
177
|
motion?: MotionConfig;
|
|
80
178
|
}
|
|
81
|
-
export interface
|
|
179
|
+
export interface DeclarativeSystem {
|
|
82
180
|
protocol: 'nexart';
|
|
181
|
+
systemType: 'declarative';
|
|
83
182
|
systemVersion: string;
|
|
84
183
|
seed: number;
|
|
85
184
|
background: BackgroundConfig;
|
|
@@ -88,9 +187,30 @@ export interface NexArtSystem {
|
|
|
88
187
|
deterministic: boolean;
|
|
89
188
|
createdAt: string;
|
|
90
189
|
}
|
|
190
|
+
export interface NexArtCodeSystem {
|
|
191
|
+
protocol: 'nexart';
|
|
192
|
+
systemType: 'code';
|
|
193
|
+
systemVersion: string;
|
|
194
|
+
source: string;
|
|
195
|
+
mode: 'static' | 'loop';
|
|
196
|
+
width: number;
|
|
197
|
+
height: number;
|
|
198
|
+
seed: number;
|
|
199
|
+
totalFrames?: number;
|
|
200
|
+
deterministic: boolean;
|
|
201
|
+
createdAt: string;
|
|
202
|
+
}
|
|
203
|
+
export type NexArtSystemInput = DeclarativeSystemInput | CodeSystem | UnifiedSystemInput;
|
|
204
|
+
export type NexArtSystem = DeclarativeSystem | NexArtCodeSystem | UnifiedSystem;
|
|
91
205
|
export interface PreviewOptions {
|
|
92
206
|
mode?: 'static' | 'loop';
|
|
93
207
|
showBadge?: boolean;
|
|
208
|
+
onPreview?: (canvas: HTMLCanvasElement) => void;
|
|
209
|
+
onComplete?: (result: {
|
|
210
|
+
type: 'image' | 'video';
|
|
211
|
+
blob: Blob;
|
|
212
|
+
}) => void;
|
|
213
|
+
onError?: (error: Error) => void;
|
|
94
214
|
}
|
|
95
215
|
export interface ValidationResult {
|
|
96
216
|
valid: boolean;
|