@efectoapp/mcp-server 0.1.4 → 0.1.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.
@@ -14,7 +14,7 @@
14
14
  import type { Tool } from '@modelcontextprotocol/sdk/types.js';
15
15
  type AspectRatio = '16:9' | '9:16' | '1:1' | '4:3' | 'full';
16
16
  type EffectId = 'none' | 'ascii-standard' | 'ascii-blocks' | 'ascii-braille' | 'ascii-hatching' | 'ascii-matrix' | 'ascii-technical' | 'ascii-dense' | 'ascii-minimal' | 'dither-floyd-steinberg' | 'dither-atkinson' | 'dither-jarvis-judice-ninke' | 'dither-stucki' | 'dither-burkes' | 'dither-sierra' | 'dither-two-row-sierra' | 'dither-sierra-lite' | 'color-separation' | 'halftone-mono' | 'halftone-cmyk' | 'glitch-chromatic' | 'glitch-digital' | 'glitch-vhs' | 'glitch-weird' | 'art-kuwahara' | 'art-crosshatch' | 'art-lineart' | 'art-engraving' | 'art-stipple' | 'special-warp';
17
- type PostProcessType = 'scanlines' | 'vignette' | 'chromatic-aberration' | 'curvature' | 'grain' | 'noise' | 'pixelate' | 'wave' | 'rgb-glitch' | 'brightness-contrast' | 'color-tint' | 'palette' | 'jitter' | 'bloom' | 'dot-screen' | 'sepia' | 'grid' | 'light-beams' | 'warp' | 'motion-blur';
17
+ type PostProcessType = 'scanlines' | 'vignette' | 'chromatic-aberration' | 'curvature' | 'grain' | 'noise' | 'pixelate' | 'wave' | 'rgb-glitch' | 'brightness-contrast' | 'color-tint' | 'palette' | 'jitter' | 'bloom' | 'dot-screen' | 'sepia' | 'grid' | 'light-beams' | 'motion-blur';
18
18
  interface LayerTransform {
19
19
  x: number;
20
20
  y: number;
@@ -31,13 +31,12 @@ interface BaseLayer {
31
31
  locked: boolean;
32
32
  transform: LayerTransform;
33
33
  }
34
- type FontWeight = 'normal' | 'medium' | 'semibold' | 'bold';
35
34
  interface TextLayer extends BaseLayer {
36
35
  type: 'text';
37
36
  content: string;
38
37
  fontFamily: string;
39
38
  fontSize: number;
40
- fontWeight: FontWeight;
39
+ fontWeight: string;
41
40
  color: string;
42
41
  textAlign: 'left' | 'center' | 'right';
43
42
  letterSpacing: number;
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/tools/state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/tools/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAA;AAG9D,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;AAG3D,KAAK,QAAQ,GACT,MAAM,GAEN,gBAAgB,GAAG,cAAc,GAAG,eAAe,GAAG,gBAAgB,GACtE,cAAc,GAAG,iBAAiB,GAAG,aAAa,GAAG,eAAe,GAEpE,wBAAwB,GAAG,iBAAiB,GAAG,4BAA4B,GAC3E,eAAe,GAAG,eAAe,GAAG,eAAe,GAAG,uBAAuB,GAC7E,oBAAoB,GAAG,kBAAkB,GAEzC,eAAe,GAAG,eAAe,GAEjC,kBAAkB,GAAG,gBAAgB,GAAG,YAAY,GAAG,cAAc,GAErE,cAAc,GAAG,gBAAgB,GAAG,aAAa,GAAG,eAAe,GAAG,aAAa,GAEnF,cAAc,CAAA;AAGlB,KAAK,eAAe,GAChB,WAAW,GAAG,UAAU,GAAG,sBAAsB,GAAG,WAAW,GAC/D,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,YAAY,GAAG,qBAAqB,GAC9E,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,YAAY,GAAG,OAAO,GACtE,MAAM,GAAG,aAAa,GAAG,aAAa,CAAA;AAU1C,UAAU,cAAc;IACtB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAGD,UAAU,SAAS;IACjB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,cAAc,CAAA;CAC1B;AAGD,UAAU,SAAU,SAAQ,SAAS;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;IACtC,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;CACnB;AAGD,UAAU,UAAW,SAAQ,SAAS;IACpC,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACnB;AAGD,UAAU,UAAW,SAAQ,SAAS;IACpC,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,OAAO,CAAA;IACb,aAAa,EAAE,MAAM,CAAA;CACtB;AAGD,KAAK,qBAAqB,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,IAAI,GAAG,QAAQ,CAAA;AAG1E,UAAU,eAAgB,SAAQ,SAAS;IACzC,IAAI,EAAE,YAAY,CAAA;IAClB,WAAW,EAAE,qBAAqB,CAAA;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,OAAO,GAAG,OAAO,CAAA;QAC5B,UAAU,EAAE,MAAM,CAAA;QAClB,QAAQ,EAAE,MAAM,CAAA;QAChB,UAAU,EAAE,MAAM,CAAA;QAClB,SAAS,EAAE,OAAO,GAAG,SAAS,CAAA;KAC/B,CAAA;CACF;AAGD,KAAK,KAAK,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,eAAe,GAAG,SAAS,CAAA;AAG9E,UAAU,mBAAmB;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,eAAe,CAAA;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC;AAGD,UAAU,WAAW;IACnB,MAAM,EAAE;QACN,WAAW,EAAE,WAAW,CAAA;QACxB,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;IACD,MAAM,EAAE,KAAK,EAAE,CAAA;IACf,MAAM,CAAC,EAAE;QACP,QAAQ,EAAE,QAAQ,CAAA;QAClB,OAAO,EAAE,OAAO,CAAA;QAEhB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAChC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAClC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACtC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACtC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACnC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAChC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAClC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACpC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACjC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACnC,CAAA;IACD,aAAa,EAAE,mBAAmB,EAAE,CAAA;CACrC;AAMD,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAGD,wBAAgB,eAAe,IAAI,WAAW,GAAG,IAAI,CAEpD;AAgFD,eAAO,MAAM,UAAU,EAAE,IAAI,EAwR5B,CAAA;AAsBD;;GAEG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GACxC,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,CAiuB7D"}
@@ -17,27 +17,12 @@ exports.stateTools = void 0;
17
17
  exports.resetState = resetState;
18
18
  exports.getCurrentState = getCurrentState;
19
19
  exports.handleStateTool = handleStateTool;
20
- const VALID_FONT_WEIGHTS = ['normal', 'medium', 'semibold', 'bold'];
21
- // Helper to validate and normalize fontWeight
22
- function validateFontWeight(weight) {
23
- if (!weight)
24
- return 'bold';
25
- const normalized = weight.toLowerCase().trim();
26
- if (VALID_FONT_WEIGHTS.includes(normalized)) {
27
- return normalized;
28
- }
29
- // Map numeric weights to closest valid weight
30
- const numWeight = parseInt(normalized);
31
- if (!isNaN(numWeight)) {
32
- if (numWeight <= 400)
33
- return 'normal';
34
- if (numWeight <= 500)
35
- return 'medium';
36
- if (numWeight <= 600)
37
- return 'semibold';
38
- return 'bold';
39
- }
40
- return 'bold'; // fallback
20
+ // Helper to clamp values to valid ranges
21
+ function clamp(value, min, max, fallback) {
22
+ const v = value ?? fallback;
23
+ if (isNaN(v))
24
+ return fallback;
25
+ return Math.min(max, Math.max(min, v));
41
26
  }
42
27
  // Current poster state (single poster for now)
43
28
  let currentPoster = null;
@@ -53,6 +38,51 @@ function getCurrentState() {
53
38
  function generateId() {
54
39
  return `layer_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
55
40
  }
41
+ /**
42
+ * Normalize font weight from various formats to our supported weights.
43
+ * Handles numeric weights (100-900) and common name variations.
44
+ * Our supported weights: normal (400), medium (500), semibold (600), bold (700)
45
+ */
46
+ function normalizeFontWeight(input) {
47
+ if (input === undefined || input === null || input === '') {
48
+ return 'bold'; // default for new text layers
49
+ }
50
+ const str = String(input).toLowerCase().trim();
51
+ // Check if it's already a valid weight name
52
+ const validWeights = ['normal', 'medium', 'semibold', 'bold'];
53
+ if (validWeights.includes(str)) {
54
+ return str;
55
+ }
56
+ // Map numeric weights to our supported weights
57
+ const numericWeight = parseInt(str, 10);
58
+ if (!isNaN(numericWeight)) {
59
+ if (numericWeight <= 450)
60
+ return 'normal'; // 100-450 → normal (400)
61
+ if (numericWeight <= 550)
62
+ return 'medium'; // 451-550 → medium (500)
63
+ if (numericWeight <= 650)
64
+ return 'semibold'; // 551-650 → semibold (600)
65
+ return 'bold'; // 651+ → bold (700)
66
+ }
67
+ // Map common font weight name variations
68
+ const weightNameMap = {
69
+ // Thin/Light → normal
70
+ 'thin': 'normal', 'hairline': 'normal', 'ultrathin': 'normal',
71
+ 'extralight': 'normal', 'ultralight': 'normal', 'light': 'normal',
72
+ 'regular': 'normal', 'book': 'normal', 'roman': 'normal',
73
+ // Medium
74
+ 'med': 'medium',
75
+ // Semibold variations
76
+ 'semibold': 'semibold', 'semi-bold': 'semibold', 'demibold': 'semibold',
77
+ 'demi-bold': 'semibold', 'demi': 'semibold',
78
+ // Bold and heavier → bold
79
+ 'extrabold': 'bold', 'ultrabold': 'bold', 'black': 'bold',
80
+ 'heavy': 'bold', 'extrablack': 'bold', 'fat': 'bold',
81
+ };
82
+ // Try lookup with normalized string (remove spaces/hyphens)
83
+ const normalized = str.replace(/[\s-]/g, '');
84
+ return weightNameMap[normalized] || weightNameMap[str] || 'bold';
85
+ }
56
86
  // Create default background layer
57
87
  function createDefaultBackgroundLayer(backgroundColor) {
58
88
  return {
@@ -143,9 +173,8 @@ exports.stateTools = [
143
173
  },
144
174
  name: { type: 'string', description: 'Layer name' },
145
175
  // Transform properties (normalized coordinates relative to canvas)
146
- // NOTE: Y axis is INVERTED from screen coordinates! Use NEGATIVE y for BOTTOM, POSITIVE y for TOP
147
- x: { type: 'number', description: 'X position (-1 to 1, 0 is center). -1 = left edge, +1 = right edge', default: 0 },
148
- y: { type: 'number', description: 'Y position (-1 to 1, 0 is center). IMPORTANT: +1 = TOP, -1 = BOTTOM (opposite of screen coords!)', default: 0 },
176
+ x: { type: 'number', description: 'X position (-1 to 1, 0 is center, positive is right)', default: 0 },
177
+ y: { type: 'number', description: 'Y position (-1 to 1, 0 is center, POSITIVE is UP/TOP, negative is down/bottom)', default: 0 },
149
178
  width: { type: 'number', description: 'Width (0-1 relative to canvas)', default: 1 },
150
179
  height: { type: 'number', description: 'Height (0-1 relative to canvas)', default: 1 },
151
180
  rotation: { type: 'number', description: 'Rotation in degrees', default: 0 },
@@ -154,7 +183,7 @@ exports.stateTools = [
154
183
  content: { type: 'string', description: 'Text content (required for text layers)' },
155
184
  fontFamily: { type: 'string', description: 'Font family name', default: 'DM Sans' },
156
185
  fontSize: { type: 'number', description: 'Font size in pixels', default: 48 },
157
- fontWeight: { type: 'string', enum: ['normal', 'medium', 'semibold', 'bold'], description: 'Font weight (only these 4 values are supported)', default: 'bold' },
186
+ fontWeight: { type: 'string', enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], description: 'Font weight', default: 'bold' },
158
187
  color: { type: 'string', description: 'Text color in hex', default: '#ffffff' },
159
188
  textAlign: { type: 'string', enum: ['left', 'center', 'right'], description: 'Text alignment', default: 'center' },
160
189
  letterSpacing: { type: 'number', description: 'Letter spacing in pixels', default: 0 },
@@ -173,9 +202,9 @@ exports.stateTools = [
173
202
  type: 'object',
174
203
  properties: {
175
204
  layerId: { type: 'string', description: 'ID of the layer to modify' },
176
- // Transform properties - Y axis is INVERTED from screen coordinates!
177
- x: { type: 'number', description: 'X position (-1 to 1). -1 = left, +1 = right' },
178
- y: { type: 'number', description: 'Y position (-1 to 1). +1 = TOP, -1 = BOTTOM (inverted from screen coords!)' },
205
+ // Transform properties (positive Y is UP/TOP, negative Y is down/bottom)
206
+ x: { type: 'number', description: 'X position (-1 to 1, 0 is center)' },
207
+ y: { type: 'number', description: 'Y position (-1 to 1, POSITIVE is UP/TOP, negative is down/bottom)' },
179
208
  width: { type: 'number', description: 'Width' },
180
209
  height: { type: 'number', description: 'Height' },
181
210
  rotation: { type: 'number', description: 'Rotation in degrees' },
@@ -201,7 +230,11 @@ exports.stateTools = [
201
230
  },
202
231
  {
203
232
  name: 'apply_effect',
204
- description: 'Apply or update the main effect (ASCII, dither, halftone, glitch, or art effect). Only one main effect can be active at a time.',
233
+ description: `Apply or update the main effect. Only one effect can be active at a time.
234
+
235
+ IMPORTANT - Two effect pipelines:
236
+ 1. DITHER effects (WebGL): Have BUILT-IN palette and bloom. Use paletteId/colors/bloomEnabled params here. Post-processes do NOT work with dither.
237
+ 2. OTHER effects (WebGPU - ASCII, Glitch, Halftone, Art): Support stackable post-processes. Use add_postprocess for palette/bloom.`,
205
238
  inputSchema: {
206
239
  type: 'object',
207
240
  properties: {
@@ -209,18 +242,18 @@ exports.stateTools = [
209
242
  type: 'string',
210
243
  enum: [
211
244
  'none',
212
- // ASCII
245
+ // ASCII (WebGPU - supports post-processes)
213
246
  'ascii-standard', 'ascii-blocks', 'ascii-braille', 'ascii-hatching',
214
247
  'ascii-matrix', 'ascii-technical', 'ascii-dense', 'ascii-minimal',
215
- // Dither
248
+ // Dither (WebGL - has built-in palette/bloom, NO post-process support)
216
249
  'dither-floyd-steinberg', 'dither-atkinson', 'dither-jarvis-judice-ninke',
217
250
  'dither-stucki', 'dither-burkes', 'dither-sierra', 'dither-two-row-sierra',
218
251
  'dither-sierra-lite', 'color-separation',
219
- // Halftone
252
+ // Halftone (WebGPU - supports post-processes)
220
253
  'halftone-mono', 'halftone-cmyk',
221
- // Glitch
254
+ // Glitch (WebGPU - supports post-processes)
222
255
  'glitch-chromatic', 'glitch-digital', 'glitch-vhs', 'glitch-weird',
223
- // Art
256
+ // Art (WebGPU - supports post-processes)
224
257
  'art-kuwahara', 'art-crosshatch', 'art-lineart', 'art-engraving', 'art-stipple',
225
258
  // Special
226
259
  'special-warp',
@@ -232,21 +265,34 @@ exports.stateTools = [
232
265
  cellSize: { type: 'number', description: 'Cell size for ASCII effects (4-32)', default: 8 },
233
266
  color: { type: 'boolean', description: 'Enable color mode for ASCII', default: true },
234
267
  invert: { type: 'boolean', description: 'Invert ASCII effect', default: false },
235
- // Dither effect settings
236
- pixelation: { type: 'number', description: 'Pixelation level for dither (1-10)', default: 3 },
237
- colors: { type: 'array', items: { type: 'string' }, description: 'Color palette for dither' },
268
+ // Dither effect settings (WebGL - has built-in palette/bloom)
269
+ pixelation: { type: 'number', description: 'Pixelation level for dither (1-20)', default: 3 },
270
+ paletteId: { type: 'string', description: 'DITHER ONLY: Palette ID (e.g., "gameboy", "synthwave", "cyberpunk", "noir"). Use list_palettes to see all 37 options.' },
271
+ colors: { type: 'array', items: { type: 'string' }, description: 'DITHER ONLY: Custom color palette as hex array (used when paletteId is null)' },
272
+ bloomEnabled: { type: 'boolean', description: 'DITHER ONLY: Enable built-in bloom glow effect', default: false },
273
+ bloomIntensity: { type: 'number', description: 'DITHER ONLY: Bloom intensity (0-3)', default: 0.5 },
274
+ bloomRadius: { type: 'number', description: 'DITHER ONLY: Bloom blur radius (1-100)', default: 20 },
238
275
  // Halftone effect settings
239
- dotSize: { type: 'number', description: 'Dot size for halftone effects' },
276
+ dotSize: { type: 'number', description: 'Dot size for halftone effects (0-1, default 0.3)', default: 0.3 },
240
277
  // Glitch effect settings
241
- intensity: { type: 'number', description: 'Glitch intensity (0-1)' },
242
- speed: { type: 'number', description: 'Glitch animation speed' },
278
+ intensity: { type: 'number', description: 'Glitch intensity (0-1)', default: 0.5 },
279
+ speed: { type: 'number', description: 'Glitch animation speed (0-2)', default: 1 },
243
280
  },
244
281
  required: ['effectId'],
245
282
  },
246
283
  },
247
284
  {
248
285
  name: 'add_postprocess',
249
- description: 'Add a post-processing effect that stacks on top of the main effect',
286
+ description: `Add a stackable post-processing effect. Can add multiple post-processes that apply in order.
287
+
288
+ IMPORTANT: Post-processes only work with WebGPU effects (ASCII, Glitch, Halftone, Art).
289
+ They do NOT work with Dither effects (WebGL) - use apply_effect params for dither palette/bloom instead.
290
+
291
+ Common combinations:
292
+ - CRT look: scanlines + curvature + vignette + chromatic-aberration
293
+ - Film look: grain + vignette + brightness-contrast
294
+ - Cyberpunk: bloom + chromatic-aberration + scanlines
295
+ - Vaporwave: palette + bloom + chromatic-aberration`,
250
296
  inputSchema: {
251
297
  type: 'object',
252
298
  properties: {
@@ -256,28 +302,64 @@ exports.stateTools = [
256
302
  'scanlines', 'vignette', 'chromatic-aberration', 'curvature',
257
303
  'grain', 'noise', 'pixelate', 'wave', 'rgb-glitch', 'brightness-contrast',
258
304
  'color-tint', 'palette', 'jitter', 'bloom', 'dot-screen', 'sepia',
259
- 'grid', 'light-beams', 'warp', 'motion-blur',
305
+ 'grid', 'light-beams', 'motion-blur',
260
306
  ],
261
307
  description: 'Post-process type',
262
308
  },
263
309
  enabled: { type: 'boolean', description: 'Whether effect is enabled', default: true },
264
- // Common settings (type-specific settings vary)
265
- intensity: { type: 'number', description: 'Effect intensity' },
310
+ // Shared parameters (used by multiple post-processes)
311
+ intensity: { type: 'number', description: 'Effect intensity - Scanlines: 0-1; Bloom: 0-3 (glow strength); Sepia: 0-1', default: 0.5 },
312
+ radius: { type: 'number', description: 'Effect radius - Vignette: 0.5-1.5; Bloom: 0-1 (blur radius)', default: 0.4 },
266
313
  // Scanlines
267
- count: { type: 'number', description: 'Number of scanlines' },
268
- // Vignette
269
- radius: { type: 'number', description: 'Vignette radius' },
314
+ count: { type: 'number', description: 'Scanlines: number of lines (100-1000)', default: 400 },
270
315
  // Chromatic aberration
271
- strength: { type: 'number', description: 'Aberration strength' },
272
- angle: { type: 'number', description: 'Aberration angle' },
316
+ strength: { type: 'number', description: 'Chromatic aberration: separation strength (0-0.1)', default: 0.01 },
317
+ angle: { type: 'number', description: 'Chromatic aberration: separation direction (0-360)', default: 0 },
273
318
  // Grain
274
- size: { type: 'number', description: 'Grain size' },
275
- speed: { type: 'number', description: 'Animation speed' },
319
+ size: { type: 'number', description: 'Grain: particle size (1-3)', default: 1.5 },
320
+ speed: { type: 'number', description: 'Animation speed for grain/noise/wave/jitter', default: 1 },
321
+ colorAmount: { type: 'number', description: 'Grain: color vs monochrome (0-1)', default: 0 },
276
322
  // Brightness/Contrast
277
- brightness: { type: 'number', description: 'Brightness adjustment' },
278
- contrast: { type: 'number', description: 'Contrast adjustment' },
279
- saturation: { type: 'number', description: 'Saturation adjustment' },
280
- hue: { type: 'number', description: 'Hue rotation' },
323
+ brightness: { type: 'number', description: 'Brightness: adjustment (-1 to 1)', default: 0 },
324
+ contrast: { type: 'number', description: 'Contrast: adjustment (0-2, 1=normal)', default: 1 },
325
+ saturation: { type: 'number', description: 'Saturation: adjustment (0-2, 1=normal)', default: 1 },
326
+ hue: { type: 'number', description: 'Hue: rotation (-0.5 to 0.5)', default: 0 },
327
+ // Bloom (WebGPU post-process version)
328
+ threshold: { type: 'number', description: 'Bloom: luminance threshold (0-1)', default: 0.2 },
329
+ // Palette (WebGPU post-process version)
330
+ paletteId: { type: 'string', description: 'Palette: built-in palette ID (use list_palettes)' },
331
+ colors: { type: 'array', items: { type: 'string' }, description: 'Palette: custom gradient colors as hex array' },
332
+ // Curvature
333
+ curvature: { type: 'number', description: 'Curvature: CRT curve intensity (0-1)', default: 0.3 },
334
+ // Pixelate
335
+ pixelSize: { type: 'number', description: 'Pixelate: block size (1-50)', default: 4 },
336
+ // Wave
337
+ amplitude: { type: 'number', description: 'Wave: height (0-1)', default: 0.1 },
338
+ frequency: { type: 'number', description: 'Wave: frequency (1-20)', default: 5 },
339
+ // Sepia
340
+ // intensity already defined above
341
+ // Grid
342
+ scale: { type: 'number', description: 'Grid: cell size (1-50)', default: 20 },
343
+ lineWidth: { type: 'number', description: 'Grid: line thickness (1-10)', default: 1 },
344
+ color: { type: 'string', description: 'Grid: line color (hex)', default: '#ffffff' },
345
+ rotation: { type: 'number', description: 'Grid: rotation in degrees (0-360)', default: 0 },
346
+ // Noise
347
+ colored: { type: 'boolean', description: 'Noise: color vs monochrome', default: false },
348
+ // Color Tint
349
+ palette: { type: 'string', enum: ['original', 'green', 'amber', 'cyan', 'blue'], description: 'Color-tint: terminal color preset', default: 'green' },
350
+ // Motion Blur
351
+ blurType: { type: 'string', enum: ['linear', 'radial', 'rotational'], description: 'Motion-blur: blur type', default: 'linear' },
352
+ samples: { type: 'number', description: 'Motion-blur: quality (4-48)', default: 16 },
353
+ centerX: { type: 'number', description: 'Motion-blur: center X for radial/rotational (0-1)', default: 0.5 },
354
+ centerY: { type: 'number', description: 'Motion-blur: center Y for radial/rotational (0-1)', default: 0.5 },
355
+ // Light Beams
356
+ lightX: { type: 'number', description: 'Light-beams: source X position (0-1)', default: 0.5 },
357
+ lightY: { type: 'number', description: 'Light-beams: source Y position (0-1)', default: 0.3 },
358
+ exposure: { type: 'number', description: 'Light-beams: ray brightness (0-1)', default: 0.3 },
359
+ decay: { type: 'number', description: 'Light-beams: ray fade (0.9-1.0)', default: 0.96 },
360
+ density: { type: 'number', description: 'Light-beams: ray spread (0.5-2)', default: 1 },
361
+ animated: { type: 'boolean', description: 'Light-beams/Grid: enable animation', default: false },
362
+ particleAmount: { type: 'number', description: 'Light-beams: particle density (0-1)', default: 0.5 },
281
363
  },
282
364
  required: ['type'],
283
365
  },
@@ -459,7 +541,7 @@ async function handleStateTool(name, args) {
459
541
  content: params.content || 'Text',
460
542
  fontFamily: params.fontFamily || 'DM Sans',
461
543
  fontSize: params.fontSize || 48,
462
- fontWeight: validateFontWeight(params.fontWeight),
544
+ fontWeight: normalizeFontWeight(params.fontWeight),
463
545
  color: params.color || '#ffffff',
464
546
  textAlign: params.textAlign || 'center',
465
547
  letterSpacing: params.letterSpacing ?? 0,
@@ -561,7 +643,7 @@ async function handleStateTool(name, args) {
561
643
  if (params.fontSize !== undefined)
562
644
  textLayer.fontSize = params.fontSize;
563
645
  if (params.fontWeight !== undefined)
564
- textLayer.fontWeight = validateFontWeight(params.fontWeight);
646
+ textLayer.fontWeight = normalizeFontWeight(params.fontWeight);
565
647
  if (params.color !== undefined)
566
648
  textLayer.color = params.color;
567
649
  if (params.textAlign !== undefined)
@@ -626,7 +708,7 @@ async function handleStateTool(name, args) {
626
708
  const style = effectId.replace('ascii-', '');
627
709
  effect.ascii = {
628
710
  style,
629
- cellSize: params.cellSize ?? 9,
711
+ cellSize: clamp(params.cellSize, 4, 32, 9),
630
712
  invert: params.invert ?? false,
631
713
  color: params.color ?? true,
632
714
  charRotation: false,
@@ -671,17 +753,21 @@ async function handleStateTool(name, args) {
671
753
  'dither-sierra-lite': 'sierraLite',
672
754
  'color-separation': 'floydSteinberg',
673
755
  };
756
+ // Dither has BUILT-IN palette and bloom (WebGL pipeline)
757
+ // These don't use post-processes - settings are on the effect itself
674
758
  effect.dither = {
675
759
  pattern: patternMap[effectId] || 'floydSteinberg',
676
- pixelation: params.pixelation ?? 3,
677
- paletteId: 'noir',
760
+ pixelation: clamp(params.pixelation, 1, 20, 3),
761
+ // Palette: use paletteId for built-in palette, or custom colors array
762
+ paletteId: params.paletteId ?? null,
678
763
  colors: params.colors ?? ['#000000', '#ffffff'],
679
- brightness: params.brightness ?? 1,
680
- contrast: params.contrast ?? 1.2,
764
+ brightness: clamp(params.brightness, 0, 2, 1),
765
+ contrast: clamp(params.contrast, 0.5, 2, 1.2),
681
766
  threshold: 1.0,
682
- bloomEnabled: false,
683
- bloomIntensity: 0.5,
684
- bloomRadius: 8,
767
+ // Built-in bloom for dither (not a post-process)
768
+ bloomEnabled: params.bloomEnabled ?? false,
769
+ bloomIntensity: clamp(params.bloomIntensity, 0, 3, 0.5),
770
+ bloomRadius: clamp(params.bloomRadius, 1, 100, 20),
685
771
  };
686
772
  }
687
773
  else if (settingsKey === 'monoHalftone') {
@@ -692,7 +778,7 @@ async function handleStateTool(name, args) {
692
778
  gridType: 'square',
693
779
  dotType: 'circle',
694
780
  inverted: false,
695
- size: params.dotSize ?? 0.3,
781
+ size: clamp(params.dotSize, 0, 1, 0.3),
696
782
  radius: 0.5,
697
783
  contrast: 1.2,
698
784
  spread: 0.3,
@@ -707,7 +793,7 @@ async function handleStateTool(name, args) {
707
793
  colorM: '#ec008c',
708
794
  colorY: '#fff200',
709
795
  colorK: '#231f20',
710
- size: params.dotSize ?? 0.3,
796
+ size: clamp(params.dotSize, 0, 1, 0.3),
711
797
  gridNoise: 0.0,
712
798
  type: 'ink',
713
799
  softness: 0.0,
@@ -771,7 +857,7 @@ async function handleStateTool(name, args) {
771
857
  localWarpStrength: 0.14,
772
858
  flipChance: 0.15,
773
859
  // Shared
774
- speed: params.speed ?? 1,
860
+ speed: clamp(params.speed, 0, 2, 1),
775
861
  animated: true,
776
862
  };
777
863
  }
@@ -872,18 +958,58 @@ async function handleStateTool(name, args) {
872
958
  content: [{ type: 'text', text: 'Error: Post-process type is required' }],
873
959
  };
874
960
  }
961
+ // Check if dither effect is active - post-processes don't work with WebGL dither
962
+ const isDitherActive = currentPoster.effect?.effectId?.startsWith('dither-') ||
963
+ currentPoster.effect?.effectId === 'color-separation';
875
964
  const ppId = `pp_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
876
965
  const settings = {};
877
- // Extract relevant settings based on type
878
- const settingKeys = [
966
+ // Extract ALL relevant settings based on type
967
+ // Common settings
968
+ const commonKeys = [
879
969
  'intensity', 'count', 'radius', 'strength', 'angle', 'size', 'speed',
880
970
  'brightness', 'contrast', 'saturation', 'hue',
881
971
  ];
882
- for (const key of settingKeys) {
972
+ // Additional settings for specific post-process types
973
+ const additionalKeys = [
974
+ 'colorAmount', // grain
975
+ 'colored', // noise
976
+ 'threshold', // bloom
977
+ 'paletteId', // palette
978
+ 'colors', // palette
979
+ 'palette', // color-tint
980
+ 'curvature', // curvature
981
+ 'pixelSize', // pixelate
982
+ 'amplitude', // wave
983
+ 'frequency', // wave, rgb-glitch
984
+ 'scale', // grid
985
+ 'lineWidth', // grid
986
+ 'color', // grid
987
+ 'rotation', // grid
988
+ 'animated', // grid, light-beams
989
+ // Motion blur
990
+ 'blurType', // motion-blur (maps to 'type')
991
+ 'samples', // motion-blur
992
+ 'centerX', // motion-blur
993
+ 'centerY', // motion-blur
994
+ // Light beams
995
+ 'lightX', // light-beams
996
+ 'lightY', // light-beams
997
+ 'exposure', // light-beams
998
+ 'decay', // light-beams
999
+ 'density', // light-beams
1000
+ 'particleAmount', // light-beams
1001
+ ];
1002
+ const allSettingKeys = [...commonKeys, ...additionalKeys];
1003
+ for (const key of allSettingKeys) {
883
1004
  if (params[key] !== undefined) {
884
1005
  settings[key] = params[key];
885
1006
  }
886
1007
  }
1008
+ // Special handling: motion-blur uses 'type' internally but we use 'blurType' to avoid schema conflict
1009
+ if (ppType === 'motion-blur' && settings.blurType) {
1010
+ settings.type = settings.blurType;
1011
+ delete settings.blurType;
1012
+ }
887
1013
  const postProcess = {
888
1014
  id: ppId,
889
1015
  type: ppType,
@@ -891,16 +1017,23 @@ async function handleStateTool(name, args) {
891
1017
  settings,
892
1018
  };
893
1019
  currentPoster.postProcesses.push(postProcess);
1020
+ // Build response with warning if dither is active
1021
+ const response = {
1022
+ success: true,
1023
+ message: 'Post-process added',
1024
+ postProcess,
1025
+ totalPostProcesses: currentPoster.postProcesses.length,
1026
+ };
1027
+ if (isDitherActive) {
1028
+ response.warning = 'WARNING: Dither effects use WebGL and do NOT support post-processes. ' +
1029
+ 'This post-process will NOT be visible. For dither, use apply_effect params (paletteId, bloomEnabled) instead. ' +
1030
+ 'Post-processes only work with WebGPU effects (ASCII, Glitch, Halftone, Art).';
1031
+ }
894
1032
  return {
895
1033
  content: [
896
1034
  {
897
1035
  type: 'text',
898
- text: JSON.stringify({
899
- success: true,
900
- message: 'Post-process added',
901
- postProcess,
902
- totalPostProcesses: currentPoster.postProcesses.length,
903
- }, null, 2),
1036
+ text: JSON.stringify(response, null, 2),
904
1037
  },
905
1038
  ],
906
1039
  };