shipshots-mcp 0.2.2 → 0.2.5

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.
@@ -1,16 +1,16 @@
1
1
  import { z } from "zod";
2
2
  export function registerExportTools(server, api) {
3
- server.tool("get_screen_preview", `Get a visual preview of a screen as an image. Returns the rendered screenshot at 50% resolution (630×1368 for standard screens). Requires a Pro plan.
3
+ server.tool("get_screen_preview", `Get a visual preview of a screen as an image. Requires a Pro plan.
4
4
 
5
5
  WORKFLOW: position layers → get_screen_preview → inspect the image → adjust → preview again.
6
6
  This is the recommended feedback loop for iterative layout design.
7
7
  Rate limit: 60 previews per hour. Use validate_screen (free, no rate limit) for quick layout checks without rendering.`, {
8
8
  project_id: z.string().describe("Project ID"),
9
9
  screen_id: z.string().describe("Screen ID to preview"),
10
- }, async ({ project_id, screen_id }) => {
11
- // Fetch preview image and project data in parallel
10
+ scale: z.number().min(0.25).max(1.0).optional().describe("Preview scale: 0.5 (default, 630×1368), 0.75 (945×2052), 1.0 (full 1260×2736). Higher = better detail but slower."),
11
+ }, async ({ project_id, screen_id, scale }) => {
12
12
  const [result, project] = await Promise.all([
13
- api.getScreenPreview(project_id, screen_id),
13
+ api.getScreenPreview(project_id, screen_id, scale),
14
14
  api.getProject(project_id),
15
15
  ]);
16
16
  // Build layer position summary from project data
@@ -30,9 +30,55 @@ Rate limit: 60 previews per hour. Use validate_screen (free, no rate limit) for
30
30
  text: JSON.stringify({
31
31
  width: result.width,
32
32
  height: result.height,
33
- scale: 0.5,
33
+ scale: scale ?? 0.5,
34
34
  layers,
35
- message: `Preview of screen "${screen_id}" (${result.width}×${result.height})`,
35
+ message: `Preview of screen "${screen_id}" (${result.width}×${result.height} at ${scale ?? 0.5}x)`,
36
+ }, null, 2),
37
+ },
38
+ ],
39
+ };
40
+ });
41
+ server.tool("get_strip_preview", `Get a panoramic preview of connected screens rendered as a single wide image. Use this after connect_screens to see the cross-screen layout. Requires a Pro plan.
42
+ Rate limit: 60 previews per hour (shared with get_screen_preview).`, {
43
+ project_id: z.string().describe("Project ID"),
44
+ screen_ids: z.array(z.string()).min(2).describe("Ordered screen IDs to render as a strip (must be connected)"),
45
+ scale: z.number().min(0.25).max(1.0).optional().describe("Preview scale (default 0.5). Lower = faster, higher = more detail."),
46
+ }, async ({ project_id, screen_ids, scale }) => {
47
+ const [result, project] = await Promise.all([
48
+ api.getStripPreview(project_id, screen_ids, scale),
49
+ api.getProject(project_id),
50
+ ]);
51
+ const offsets = [];
52
+ let cumX = 0;
53
+ for (const sid of screen_ids) {
54
+ offsets.push(cumX);
55
+ const s = project.screens.find((s) => s.id === sid);
56
+ cumX += s?.width ?? 0;
57
+ }
58
+ const layers = screen_ids.flatMap((sid, i) => {
59
+ const screen = project.screens.find((s) => s.id === sid);
60
+ if (!screen)
61
+ return [];
62
+ return screen.layers
63
+ .filter((l) => l.visible !== false)
64
+ .map((l) => ({ id: l.id, type: l.type, screen_id: sid, x: l.x + offsets[i], y: l.y, width: l.width, height: l.height }));
65
+ });
66
+ return {
67
+ content: [
68
+ {
69
+ type: "image",
70
+ data: result.base64,
71
+ mimeType: result.mimeType,
72
+ },
73
+ {
74
+ type: "text",
75
+ text: JSON.stringify({
76
+ width: result.width,
77
+ height: result.height,
78
+ scale: scale ?? 0.5,
79
+ screen_ids,
80
+ layers,
81
+ message: `Strip preview (${result.width}×${result.height} at ${scale ?? 0.5}x)`,
36
82
  }, null, 2),
37
83
  },
38
84
  ],
@@ -101,5 +147,31 @@ Rate limit: 60 previews per hour. Use validate_screen (free, no rate limit) for
101
147
  ],
102
148
  };
103
149
  });
150
+ server.tool("export_screenshot", `Export a screen as a full-resolution image. Returns a base64 JPEG at native canvas size (1260×2736 for standard screens). Requires a Pro plan.
151
+ Rate limit: shares the 60/hour limit with get_screen_preview.
152
+
153
+ For different export sizes (iPhone 6.5", iPad, etc.), use the web editor's export feature.`, {
154
+ project_id: z.string().describe("Project ID"),
155
+ screen_id: z.string().describe("Screen ID to export"),
156
+ }, async ({ project_id, screen_id }) => {
157
+ const result = await api.getScreenPreview(project_id, screen_id, 1.0);
158
+ return {
159
+ content: [
160
+ {
161
+ type: "image",
162
+ data: result.base64,
163
+ mimeType: result.mimeType,
164
+ },
165
+ {
166
+ type: "text",
167
+ text: JSON.stringify({
168
+ width: result.width,
169
+ height: result.height,
170
+ message: `Exported screen "${screen_id}" at full resolution (${result.width}×${result.height})`,
171
+ }, null, 2),
172
+ },
173
+ ],
174
+ };
175
+ });
104
176
  }
105
177
  //# sourceMappingURL=export.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/tools/export.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/tools/export.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/tools/layers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AA2BlD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,GAAG,IAAI,CA0qB1E"}
1
+ {"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/tools/layers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AA6BlD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,GAAG,IAAI,CAk7B1E"}
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { createTextLayer, createDeviceLayer, createImageLayer, createShapeLayer, createBadgeLayer, createArrowLayer, autoPosition, } from "../defaults.js";
2
+ import { createTextLayer, createDeviceLayer, createImageLayer, createShapeLayer, createBadgeLayer, createArrowLayer, autoPosition, H_MARGIN, V_MARGIN, } from "../defaults.js";
3
3
  import { getProjectAndScreen, unescapeNewlines } from "../helpers.js";
4
4
  import { positionEnum, sizeEnum, tiltEnum, resolveSemanticPosition } from "../semantic-position.js";
5
5
  /** Extract position/dimension info from a layer for inclusion in tool responses. */
@@ -56,7 +56,11 @@ Use fontWeight contrast (800 vs 400) within the same font family for hierarchy.
56
56
  rotation: z.number().optional().describe("2D rotation in degrees (default: 0)"),
57
57
  rotateX: z.number().optional().describe("3D tilt on X axis, -90 to 90 (default: 0). Creates perspective depth effect."),
58
58
  rotateY: z.number().optional().describe("3D tilt on Y axis, -90 to 90 (default: 0). Creates perspective depth effect."),
59
- }, async ({ project_id, screen_id, text, position, size, tilt, x, y, width, height, fontSize, fontFamily, fontWeight, color, textAlign, letterSpacing, lineHeight, fillType, gradientColor2, gradientDirection, strokeEnabled, strokeColor, strokeWidth, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, rotation, rotateX, rotateY }) => {
59
+ blurEnabled: z.boolean().optional().describe("Enable Gaussian blur (default: false)"),
60
+ blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
61
+ backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur (default: false). Layer needs low opacity to see the effect."),
62
+ backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50"),
63
+ }, async ({ project_id, screen_id, text, position, size, tilt, x, y, width, height, fontSize, fontFamily, fontWeight, color, textAlign, letterSpacing, lineHeight, fillType, gradientColor2, gradientDirection, strokeEnabled, strokeColor, strokeWidth, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, rotation, rotateX, rotateY, blurEnabled, blurAmount, backdropBlurEnabled, backdropBlurAmount }) => {
60
64
  const result = await getProjectAndScreen(api, project_id, screen_id);
61
65
  if ("error" in result) {
62
66
  return { content: [{ type: "text", text: result.error }], isError: true };
@@ -93,6 +97,10 @@ Use fontWeight contrast (800 vs 400) within the same font family for hierarchy.
93
97
  ...(rotation !== undefined ? { rotation } : semantic.rotation !== undefined ? { rotation: semantic.rotation } : {}),
94
98
  ...(rotateX !== undefined ? { rotateX } : {}),
95
99
  ...(rotateY !== undefined ? { rotateY } : {}),
100
+ ...(blurEnabled !== undefined ? { blurEnabled } : {}),
101
+ ...(blurAmount !== undefined ? { blurAmount } : {}),
102
+ ...(backdropBlurEnabled !== undefined ? { backdropBlurEnabled } : {}),
103
+ ...(backdropBlurAmount !== undefined ? { backdropBlurAmount } : {}),
96
104
  });
97
105
  screen.layers.push(layer);
98
106
  await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
@@ -136,7 +144,11 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
136
144
  shadowBlur: z.number().optional().describe("Shadow blur radius in pixels (default: 20)"),
137
145
  shadowOffsetX: z.number().optional().describe("Shadow horizontal offset (default: 0)"),
138
146
  shadowOffsetY: z.number().optional().describe("Shadow vertical offset (default: 10)"),
139
- }, async ({ project_id, screen_id, deviceType, screenshotUrl, position, size, tilt, x, y, width, rotation, rotateX, rotateY, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY }) => {
147
+ blurEnabled: z.boolean().optional().describe("Enable Gaussian blur (default: false)"),
148
+ blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
149
+ backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur (default: false). Layer needs low opacity to see the effect."),
150
+ backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50"),
151
+ }, async ({ project_id, screen_id, deviceType, screenshotUrl, position, size, tilt, x, y, width, rotation, rotateX, rotateY, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, blurEnabled, blurAmount, backdropBlurEnabled, backdropBlurAmount }) => {
140
152
  const result = await getProjectAndScreen(api, project_id, screen_id);
141
153
  if ("error" in result) {
142
154
  return { content: [{ type: "text", text: result.error }], isError: true };
@@ -160,6 +172,10 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
160
172
  ...(shadowBlur !== undefined ? { shadowBlur } : {}),
161
173
  ...(shadowOffsetX !== undefined ? { shadowOffsetX } : {}),
162
174
  ...(shadowOffsetY !== undefined ? { shadowOffsetY } : {}),
175
+ ...(blurEnabled !== undefined ? { blurEnabled } : {}),
176
+ ...(blurAmount !== undefined ? { blurAmount } : {}),
177
+ ...(backdropBlurEnabled !== undefined ? { backdropBlurEnabled } : {}),
178
+ ...(backdropBlurAmount !== undefined ? { backdropBlurAmount } : {}),
163
179
  });
164
180
  screen.layers.push(layer);
165
181
  await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
@@ -191,7 +207,11 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
191
207
  shadowBlur: z.number().optional().describe("Shadow blur radius in pixels (default: 20)"),
192
208
  shadowOffsetX: z.number().optional().describe("Shadow horizontal offset (default: 0)"),
193
209
  shadowOffsetY: z.number().optional().describe("Shadow vertical offset (default: 10)"),
194
- }, async ({ project_id, screen_id, imageUrl, position, size, tilt, x, y, width, height, objectFit, borderRadius, rotation, rotateX, rotateY, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY }) => {
210
+ blurEnabled: z.boolean().optional().describe("Enable Gaussian blur (default: false)"),
211
+ blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
212
+ backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur (default: false). Layer needs low opacity to see the effect."),
213
+ backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50"),
214
+ }, async ({ project_id, screen_id, imageUrl, position, size, tilt, x, y, width, height, objectFit, borderRadius, rotation, rotateX, rotateY, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, blurEnabled, blurAmount, backdropBlurEnabled, backdropBlurAmount }) => {
195
215
  const result = await getProjectAndScreen(api, project_id, screen_id);
196
216
  if ("error" in result) {
197
217
  return { content: [{ type: "text", text: result.error }], isError: true };
@@ -217,6 +237,10 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
217
237
  ...(shadowBlur !== undefined ? { shadowBlur } : {}),
218
238
  ...(shadowOffsetX !== undefined ? { shadowOffsetX } : {}),
219
239
  ...(shadowOffsetY !== undefined ? { shadowOffsetY } : {}),
240
+ ...(blurEnabled !== undefined ? { blurEnabled } : {}),
241
+ ...(blurAmount !== undefined ? { blurAmount } : {}),
242
+ ...(backdropBlurEnabled !== undefined ? { backdropBlurEnabled } : {}),
243
+ ...(backdropBlurAmount !== undefined ? { backdropBlurAmount } : {}),
220
244
  });
221
245
  screen.layers.push(layer);
222
246
  await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
@@ -228,7 +252,9 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
228
252
 
229
253
  QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size ("small", "medium", "large", "full-width"), and tilt ("slight-left", "dramatic-right", ...) for semantic positioning. Explicit x/y/width/height/rotation override these.
230
254
 
231
- TIP: Use large semi-transparent gradient circles (opacity 0.3-0.5) as decorative orbs behind device frames for visual depth.`, {
255
+ TIP: Use large semi-transparent gradient circles (opacity 0.3-0.5) as decorative orbs behind device frames for visual depth.
256
+
257
+ DEPTH EFFECTS: Use gradientType "radial" for spotlight/glow orbs. Add blurEnabled + blurAmount for depth-of-field. Use backdropBlurEnabled + backdropBlurAmount on semi-transparent shapes for frosted glass panels.`, {
232
258
  project_id: z.string().describe("Project ID"),
233
259
  screen_id: z.string().describe("Screen ID"),
234
260
  shapeType: z.enum(["rect", "circle", "rounded-rect"]).describe("Shape type"),
@@ -239,6 +265,7 @@ TIP: Use large semi-transparent gradient circles (opacity 0.3-0.5) as decorative
239
265
  fillType: z.enum(["solid", "gradient", "image"]).optional().describe('Fill type (default: "solid")'),
240
266
  gradientColor2: z.string().optional().describe('Second gradient color hex (default: "#000000")'),
241
267
  gradientDirection: z.number().optional().describe("Gradient angle in degrees 0-360 (default: 135)"),
268
+ gradientType: z.enum(["linear", "radial"]).optional().describe('Gradient interpolation type. "radial" creates spotlight/glow effects centered in the shape. Default: "linear"'),
242
269
  fillImageUrl: z.string().optional().describe("Image URL or local file path for image fill"),
243
270
  fillImageSize: z.enum(["cover", "contain", "fill"]).optional().describe('Image fill size mode (default: "cover")'),
244
271
  x: z.number().optional().describe("X position"),
@@ -257,7 +284,11 @@ TIP: Use large semi-transparent gradient circles (opacity 0.3-0.5) as decorative
257
284
  shadowBlur: z.number().optional().describe("Shadow blur radius in pixels (default: 20)"),
258
285
  shadowOffsetX: z.number().optional().describe("Shadow horizontal offset (default: 0)"),
259
286
  shadowOffsetY: z.number().optional().describe("Shadow vertical offset (default: 10)"),
260
- }, async ({ project_id, screen_id, shapeType, position, size, tilt, fill, fillType, gradientColor2, gradientDirection, fillImageUrl, fillImageSize, x, y, width, height, borderRadius, stroke, strokeWidth, rotation, rotateX, rotateY, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY }) => {
287
+ blurEnabled: z.boolean().optional().describe("Enable Gaussian blur (default: false)"),
288
+ blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
289
+ backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur (default: false). Layer needs low opacity to see the effect."),
290
+ backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50. Blurs content behind this layer — great for frosted glass panels."),
291
+ }, async ({ project_id, screen_id, shapeType, position, size, tilt, fill, fillType, gradientColor2, gradientDirection, gradientType, fillImageUrl, fillImageSize, x, y, width, height, borderRadius, stroke, strokeWidth, rotation, rotateX, rotateY, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, blurEnabled, blurAmount, backdropBlurEnabled, backdropBlurAmount }) => {
261
292
  const result = await getProjectAndScreen(api, project_id, screen_id);
262
293
  if ("error" in result) {
263
294
  return { content: [{ type: "text", text: result.error }], isError: true };
@@ -272,6 +303,7 @@ TIP: Use large semi-transparent gradient circles (opacity 0.3-0.5) as decorative
272
303
  ...(fillType !== undefined ? { fillType } : {}),
273
304
  ...(gradientColor2 !== undefined ? { gradientColor2 } : {}),
274
305
  ...(gradientDirection !== undefined ? { gradientDirection } : {}),
306
+ ...(gradientType !== undefined ? { gradientType } : {}),
275
307
  ...(resolvedFillImageUrl !== undefined ? { fillImageUrl: resolvedFillImageUrl } : {}),
276
308
  ...(fillImageSize !== undefined ? { fillImageSize } : {}),
277
309
  ...(x !== undefined ? { x } : semantic.x !== undefined ? { x: semantic.x } : {}),
@@ -290,6 +322,10 @@ TIP: Use large semi-transparent gradient circles (opacity 0.3-0.5) as decorative
290
322
  ...(shadowBlur !== undefined ? { shadowBlur } : {}),
291
323
  ...(shadowOffsetX !== undefined ? { shadowOffsetX } : {}),
292
324
  ...(shadowOffsetY !== undefined ? { shadowOffsetY } : {}),
325
+ ...(blurEnabled !== undefined ? { blurEnabled } : {}),
326
+ ...(blurAmount !== undefined ? { blurAmount } : {}),
327
+ ...(backdropBlurEnabled !== undefined ? { backdropBlurEnabled } : {}),
328
+ ...(backdropBlurAmount !== undefined ? { backdropBlurAmount } : {}),
293
329
  });
294
330
  screen.layers.push(layer);
295
331
  await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
@@ -336,7 +372,11 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
336
372
  rotation: z.number().optional().describe("2D rotation in degrees (default: 0)"),
337
373
  rotateX: z.number().optional().describe("3D tilt on X axis, -90 to 90 (default: 0). Creates perspective depth effect."),
338
374
  rotateY: z.number().optional().describe("3D tilt on Y axis, -90 to 90 (default: 0). Creates perspective depth effect."),
339
- }, async ({ project_id, screen_id, badgeStyle, text, position, size, tilt, x, y, width, height, fontSize, fontFamily, fontWeight, color, backgroundColor, backgroundOpacity, borderRadius, iconType, iconColor, rating, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, rotation, rotateX, rotateY }) => {
375
+ blurEnabled: z.boolean().optional().describe("Enable Gaussian blur (default: false)"),
376
+ blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
377
+ backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur (default: false). Layer needs low opacity to see the effect."),
378
+ backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50"),
379
+ }, async ({ project_id, screen_id, badgeStyle, text, position, size, tilt, x, y, width, height, fontSize, fontFamily, fontWeight, color, backgroundColor, backgroundOpacity, borderRadius, iconType, iconColor, rating, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, rotation, rotateX, rotateY, blurEnabled, blurAmount, backdropBlurEnabled, backdropBlurAmount }) => {
340
380
  const result = await getProjectAndScreen(api, project_id, screen_id);
341
381
  if ("error" in result) {
342
382
  return { content: [{ type: "text", text: result.error }], isError: true };
@@ -371,6 +411,10 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
371
411
  ...(rotation !== undefined ? { rotation } : semantic.rotation !== undefined ? { rotation: semantic.rotation } : {}),
372
412
  ...(rotateX !== undefined ? { rotateX } : {}),
373
413
  ...(rotateY !== undefined ? { rotateY } : {}),
414
+ ...(blurEnabled !== undefined ? { blurEnabled } : {}),
415
+ ...(blurAmount !== undefined ? { blurAmount } : {}),
416
+ ...(backdropBlurEnabled !== undefined ? { backdropBlurEnabled } : {}),
417
+ ...(backdropBlurAmount !== undefined ? { backdropBlurAmount } : {}),
374
418
  });
375
419
  screen.layers.push(layer);
376
420
  await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
@@ -408,7 +452,11 @@ POSITIONING TIP:
408
452
  rotation: z.number().optional().describe("2D rotation in degrees (default: 0)"),
409
453
  rotateX: z.number().optional().describe("3D tilt on X axis, -90 to 90 (default: 0). Creates perspective depth effect."),
410
454
  rotateY: z.number().optional().describe("3D tilt on Y axis, -90 to 90 (default: 0). Creates perspective depth effect."),
411
- }, async ({ project_id, screen_id, fromX, fromY, toX, toY, strokeColor, strokeWidth, headStyle, curvature, dashed, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, rotation, rotateX, rotateY }) => {
455
+ blurEnabled: z.boolean().optional().describe("Enable Gaussian blur (default: false)"),
456
+ blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
457
+ backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur (default: false). Layer needs low opacity to see the effect."),
458
+ backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50"),
459
+ }, async ({ project_id, screen_id, fromX, fromY, toX, toY, strokeColor, strokeWidth, headStyle, curvature, dashed, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, rotation, rotateX, rotateY, blurEnabled, blurAmount, backdropBlurEnabled, backdropBlurAmount }) => {
412
460
  const result = await getProjectAndScreen(api, project_id, screen_id);
413
461
  if ("error" in result) {
414
462
  return { content: [{ type: "text", text: result.error }], isError: true };
@@ -434,6 +482,10 @@ POSITIONING TIP:
434
482
  ...(rotation !== undefined ? { rotation } : {}),
435
483
  ...(rotateX !== undefined ? { rotateX } : {}),
436
484
  ...(rotateY !== undefined ? { rotateY } : {}),
485
+ ...(blurEnabled !== undefined ? { blurEnabled } : {}),
486
+ ...(blurAmount !== undefined ? { blurAmount } : {}),
487
+ ...(backdropBlurEnabled !== undefined ? { backdropBlurEnabled } : {}),
488
+ ...(backdropBlurAmount !== undefined ? { backdropBlurAmount } : {}),
437
489
  });
438
490
  screen.layers.push(layer);
439
491
  await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
@@ -500,6 +552,11 @@ QUICK PLACEMENT: Use position/size/tilt for quick repositioning. Explicit x/y/wi
500
552
  shadowBlur: z.number().optional().describe("Shadow blur radius in pixels"),
501
553
  shadowOffsetX: z.number().optional().describe("Shadow horizontal offset"),
502
554
  shadowOffsetY: z.number().optional().describe("Shadow vertical offset"),
555
+ gradientType: z.enum(["linear", "radial"]).optional().describe('Gradient type for shape layers: "linear" or "radial"'),
556
+ blurEnabled: z.boolean().optional().describe("Enable Gaussian blur"),
557
+ blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
558
+ backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur"),
559
+ backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50"),
503
560
  animation_entrance_type: z.string().optional().describe('Override entrance animation type. Valid: "fade-rise", "slide-right", "zoom-pop", "fade", "bounce", "parallax", "drop-bounce", "flip", "typewriter", "blur-in", "wipe", "elastic". Set to "" to clear.'),
504
561
  animation_entrance_duration: z.number().optional().describe("Override entrance duration in frames"),
505
562
  animation_entrance_delay: z.number().optional().describe("Override entrance delay in frames"),
@@ -604,5 +661,189 @@ QUICK PLACEMENT: Use position/size/tilt for quick repositioning. Explicit x/y/wi
604
661
  content: [{ type: "text", text: JSON.stringify({ message: "Layers reordered.", order: layer_ids }, null, 2) }],
605
662
  };
606
663
  });
664
+ // ── move_layer ──
665
+ server.tool("move_layer", `Move a layer's z-order position. Use "to-front"/"to-back" for extremes, or "in-front-of"/"behind" with a reference_layer_id for relative positioning. Simpler than reorder_layers when you just need to adjust one layer.`, {
666
+ project_id: z.string().describe("Project ID"),
667
+ screen_id: z.string().describe("Screen ID"),
668
+ layer_id: z.string().describe("Layer ID to move"),
669
+ placement: z.enum(["in-front-of", "behind", "to-front", "to-back"]).describe("Where to place the layer"),
670
+ reference_layer_id: z.string().optional().describe("Reference layer ID (required for 'in-front-of' and 'behind')"),
671
+ }, async ({ project_id, screen_id, layer_id, placement, reference_layer_id }) => {
672
+ const result = await getProjectAndScreen(api, project_id, screen_id);
673
+ if ("error" in result) {
674
+ return { content: [{ type: "text", text: result.error }], isError: true };
675
+ }
676
+ const { project, screenIndex } = result;
677
+ const screen = project.screens[screenIndex];
678
+ const sorted = [...screen.layers].sort((a, b) => a.zIndex - b.zIndex);
679
+ const targetIndex = sorted.findIndex((l) => l.id === layer_id);
680
+ if (targetIndex === -1) {
681
+ const ids = screen.layers.map((l) => l.id).join(", ");
682
+ return { content: [{ type: "text", text: `Error: Layer "${layer_id}" not found. Available: ${ids}` }], isError: true };
683
+ }
684
+ const [target] = sorted.splice(targetIndex, 1);
685
+ if (placement === "to-front") {
686
+ sorted.push(target);
687
+ }
688
+ else if (placement === "to-back") {
689
+ sorted.unshift(target);
690
+ }
691
+ else {
692
+ if (!reference_layer_id) {
693
+ return { content: [{ type: "text", text: `Error: reference_layer_id is required for "${placement}".` }], isError: true };
694
+ }
695
+ const refIndex = sorted.findIndex((l) => l.id === reference_layer_id);
696
+ if (refIndex === -1) {
697
+ const ids = screen.layers.map((l) => l.id).join(", ");
698
+ return { content: [{ type: "text", text: `Error: Reference layer "${reference_layer_id}" not found. Available: ${ids}` }], isError: true };
699
+ }
700
+ const insertAt = placement === "in-front-of" ? refIndex + 1 : refIndex;
701
+ sorted.splice(insertAt, 0, target);
702
+ }
703
+ // Re-number all zIndex values
704
+ for (let i = 0; i < sorted.length; i++) {
705
+ const layer = screen.layers.find((l) => l.id === sorted[i].id);
706
+ if (layer)
707
+ layer.zIndex = i + 1;
708
+ }
709
+ await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
710
+ const order = sorted.map((l) => l.id);
711
+ return {
712
+ content: [{ type: "text", text: JSON.stringify({ message: "Layer moved.", layer_id, placement, order }, null, 2) }],
713
+ };
714
+ });
715
+ // ── align_layer ──
716
+ server.tool("align_layer", `Align a layer relative to the canvas boundaries. Uses standard margins (${H_MARGIN}px horizontal, ${V_MARGIN}px vertical) for edge alignments.`, {
717
+ project_id: z.string().describe("Project ID"),
718
+ screen_id: z.string().describe("Screen ID"),
719
+ layer_id: z.string().describe("Layer ID to align"),
720
+ alignment: z.enum([
721
+ "center-horizontal", "center-vertical", "center",
722
+ "left", "right", "top", "bottom",
723
+ ]).describe("Alignment relative to the canvas"),
724
+ }, async ({ project_id, screen_id, layer_id, alignment }) => {
725
+ const result = await getProjectAndScreen(api, project_id, screen_id);
726
+ if ("error" in result) {
727
+ return { content: [{ type: "text", text: result.error }], isError: true };
728
+ }
729
+ const { project, screenIndex } = result;
730
+ const screen = project.screens[screenIndex];
731
+ const layer = screen.layers.find((l) => l.id === layer_id);
732
+ if (!layer) {
733
+ const ids = screen.layers.map((l) => l.id).join(", ");
734
+ return { content: [{ type: "text", text: `Error: Layer "${layer_id}" not found. Available: ${ids}` }], isError: true };
735
+ }
736
+ if (alignment === "center-horizontal" || alignment === "center") {
737
+ layer.x = Math.round((screen.width - layer.width) / 2);
738
+ }
739
+ if (alignment === "center-vertical" || alignment === "center") {
740
+ layer.y = Math.round((screen.height - layer.height) / 2);
741
+ }
742
+ if (alignment === "left")
743
+ layer.x = H_MARGIN;
744
+ if (alignment === "right")
745
+ layer.x = screen.width - layer.width - H_MARGIN;
746
+ if (alignment === "top")
747
+ layer.y = V_MARGIN;
748
+ if (alignment === "bottom")
749
+ layer.y = screen.height - layer.height - V_MARGIN;
750
+ await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
751
+ return {
752
+ content: [{ type: "text", text: JSON.stringify({ message: `Layer aligned (${alignment}).`, ...layerPosition(layer) }, null, 2) }],
753
+ };
754
+ });
755
+ // ── align_layers ──
756
+ server.tool("align_layers", `Align multiple layers relative to each other, or distribute them evenly.`, {
757
+ project_id: z.string().describe("Project ID"),
758
+ screen_id: z.string().describe("Screen ID"),
759
+ layer_ids: z.array(z.string()).min(2).describe("Layer IDs to align"),
760
+ alignment: z.enum([
761
+ "left", "right", "top", "bottom",
762
+ "center-horizontal", "center-vertical",
763
+ "distribute-horizontal", "distribute-vertical",
764
+ ]).describe("How to align the layers relative to each other"),
765
+ }, async ({ project_id, screen_id, layer_ids, alignment }) => {
766
+ const result = await getProjectAndScreen(api, project_id, screen_id);
767
+ if ("error" in result) {
768
+ return { content: [{ type: "text", text: result.error }], isError: true };
769
+ }
770
+ const { project, screenIndex } = result;
771
+ const screen = project.screens[screenIndex];
772
+ const layers = layer_ids.map((id) => screen.layers.find((l) => l.id === id)).filter(Boolean);
773
+ if (layers.length < 2) {
774
+ return { content: [{ type: "text", text: `Error: Need at least 2 valid layer IDs. Found ${layers.length}.` }], isError: true };
775
+ }
776
+ switch (alignment) {
777
+ case "left": {
778
+ const minX = Math.min(...layers.map((l) => l.x));
779
+ for (const l of layers)
780
+ l.x = minX;
781
+ break;
782
+ }
783
+ case "right": {
784
+ const maxRight = Math.max(...layers.map((l) => l.x + l.width));
785
+ for (const l of layers)
786
+ l.x = maxRight - l.width;
787
+ break;
788
+ }
789
+ case "top": {
790
+ const minY = Math.min(...layers.map((l) => l.y));
791
+ for (const l of layers)
792
+ l.y = minY;
793
+ break;
794
+ }
795
+ case "bottom": {
796
+ const maxBottom = Math.max(...layers.map((l) => l.y + l.height));
797
+ for (const l of layers)
798
+ l.y = maxBottom - l.height;
799
+ break;
800
+ }
801
+ case "center-horizontal": {
802
+ const avgCx = layers.reduce((s, l) => s + l.x + l.width / 2, 0) / layers.length;
803
+ for (const l of layers)
804
+ l.x = Math.round(avgCx - l.width / 2);
805
+ break;
806
+ }
807
+ case "center-vertical": {
808
+ const avgCy = layers.reduce((s, l) => s + l.y + l.height / 2, 0) / layers.length;
809
+ for (const l of layers)
810
+ l.y = Math.round(avgCy - l.height / 2);
811
+ break;
812
+ }
813
+ case "distribute-horizontal": {
814
+ const sorted = [...layers].sort((a, b) => a.x - b.x);
815
+ const first = sorted[0];
816
+ const last = sorted[sorted.length - 1];
817
+ const totalSpan = (last.x + last.width) - first.x;
818
+ const totalLayerWidth = sorted.reduce((s, l) => s + l.width, 0);
819
+ const gap = (totalSpan - totalLayerWidth) / (sorted.length - 1);
820
+ let cx = first.x + first.width;
821
+ for (let i = 1; i < sorted.length - 1; i++) {
822
+ sorted[i].x = Math.round(cx + gap);
823
+ cx = sorted[i].x + sorted[i].width;
824
+ }
825
+ break;
826
+ }
827
+ case "distribute-vertical": {
828
+ const sorted = [...layers].sort((a, b) => a.y - b.y);
829
+ const first = sorted[0];
830
+ const last = sorted[sorted.length - 1];
831
+ const totalSpan = (last.y + last.height) - first.y;
832
+ const totalLayerHeight = sorted.reduce((s, l) => s + l.height, 0);
833
+ const gap = (totalSpan - totalLayerHeight) / (sorted.length - 1);
834
+ let cy = first.y + first.height;
835
+ for (let i = 1; i < sorted.length - 1; i++) {
836
+ sorted[i].y = Math.round(cy + gap);
837
+ cy = sorted[i].y + sorted[i].height;
838
+ }
839
+ break;
840
+ }
841
+ }
842
+ await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
843
+ const positions = layers.map((l) => ({ id: l.id, ...layerPosition(l) }));
844
+ return {
845
+ content: [{ type: "text", text: JSON.stringify({ message: `Layers aligned (${alignment}).`, layers: positions }, null, 2) }],
846
+ };
847
+ });
607
848
  }
608
849
  //# sourceMappingURL=layers.js.map