@varianta/sdk 0.1.8 → 0.1.9

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/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- class N {
1
+ class B {
2
2
  constructor(e) {
3
3
  this.animationFrameId = null, this.needsRender = !0, this.areas = [], this.contents = /* @__PURE__ */ new Map(), this.artboard = null, this.bgImageElement = null, this.loadedImages = /* @__PURE__ */ new Map(), this.loadingImages = /* @__PURE__ */ new Set(), this.zoom = 1, this.pan = { x: 0, y: 0 }, this.selectedAreaId = null, this.hoveredHandle = null, this.eventListeners = /* @__PURE__ */ new Map(), this.safeArea = null, this.safeAreaViolations = /* @__PURE__ */ new Set(), this.showAreaBorders = !0, this.areaSelectionEnabled = !0, this.renderLoop = () => {
4
4
  try {
@@ -150,8 +150,8 @@ class N {
150
150
  const e = 0, { width: t, height: i } = this.artboard, s = window.devicePixelRatio || 1, n = this.canvas.width / s - e * 2, o = this.canvas.height / s - e * 2;
151
151
  if (n <= 0 || o <= 0)
152
152
  return;
153
- const a = n / t, r = o / i, d = Math.min(a, r), l = this.canvas.width / s, c = this.canvas.height / s, p = (l / d - t) / 2, u = (c / d - i) / 2;
154
- this.zoom = d, this.pan = { x: p, y: u }, this.requestRender();
153
+ const a = n / t, r = o / i, l = Math.min(a, r), d = this.canvas.width / s, h = this.canvas.height / s, p = (d / l - t) / 2, c = (h / l - i) / 2;
154
+ this.zoom = l, this.pan = { x: p, y: c }, this.requestRender();
155
155
  }
156
156
  /**
157
157
  * Set the selected area
@@ -188,7 +188,7 @@ class N {
188
188
  * Get content bounds for an area (in artboard coordinates)
189
189
  */
190
190
  getContentBounds(e) {
191
- const t = this.areas.find((d) => d.id === e);
191
+ const t = this.areas.find((l) => l.id === e);
192
192
  if (!t)
193
193
  return null;
194
194
  const i = this.contents.get(e);
@@ -196,32 +196,32 @@ class N {
196
196
  return null;
197
197
  const { location: s } = t, { x: n, y: o, width: a, height: r } = s;
198
198
  if (i.type === "text") {
199
- const d = i.offset || { x: 0, y: 0 }, l = i.scale ?? 1, c = i.rotation ?? 0, p = a * l, u = r * l, g = n + a / 2 + d.x, m = o + r / 2 + d.y;
199
+ const l = i.offset || { x: 0, y: 0 }, d = i.scale ?? 1, h = i.rotation ?? 0, p = a * d, c = r * d, m = n + a / 2 + l.x, f = o + r / 2 + l.y;
200
200
  return {
201
- x: g - p / 2,
202
- y: m - u / 2,
201
+ x: m - p / 2,
202
+ y: f - c / 2,
203
203
  width: p,
204
- height: u,
205
- rotation: c,
206
- centerX: g,
207
- centerY: m
204
+ height: c,
205
+ rotation: h,
206
+ centerX: m,
207
+ centerY: f
208
208
  };
209
209
  } else if (i.type === "image") {
210
- const d = this.loadedImages.get(e);
211
- if (!d)
210
+ const l = this.loadedImages.get(e);
211
+ if (!l)
212
212
  return null;
213
- const l = i.offset || { x: 0, y: 0 }, c = i.scale ?? 1, p = i.rotation ?? 0, u = d.naturalWidth / d.naturalHeight, g = a / r;
214
- let m, v;
215
- u > g ? (m = a, v = a / u) : (v = r, m = r * u);
216
- const b = m * c, w = v * c, y = n + a / 2 + l.x, x = o + r / 2 + l.y;
213
+ const d = i.offset || { x: 0, y: 0 }, h = i.scale ?? 1, p = i.rotation ?? 0, c = l.naturalWidth / l.naturalHeight, m = a / r;
214
+ let f, v;
215
+ c > m ? (f = a, v = a / c) : (v = r, f = r * c);
216
+ const b = f * h, y = v * h, x = n + a / 2 + d.x, C = o + r / 2 + d.y;
217
217
  return {
218
- x: y - b / 2,
219
- y: x - w / 2,
218
+ x: x - b / 2,
219
+ y: C - y / 2,
220
220
  width: b,
221
- height: w,
221
+ height: y,
222
222
  rotation: p,
223
- centerX: y,
224
- centerY: x
223
+ centerX: x,
224
+ centerY: C
225
225
  };
226
226
  }
227
227
  return null;
@@ -258,8 +258,8 @@ class N {
258
258
  * Test if a point is inside rotated bounds
259
259
  */
260
260
  isPointInBounds(e, t, i) {
261
- const { centerX: s, centerY: n, width: o, height: a, rotation: r } = i, d = -r * Math.PI / 180, l = Math.cos(d), c = Math.sin(d), p = e - s, u = t - n, g = p * l - u * c, m = p * c + u * l;
262
- return g >= -o / 2 && g <= o / 2 && m >= -a / 2 && m <= a / 2;
261
+ const { centerX: s, centerY: n, width: o, height: a, rotation: r } = i, l = -r * Math.PI / 180, d = Math.cos(l), h = Math.sin(l), p = e - s, c = t - n, m = p * d - c * h, f = p * h + c * d;
262
+ return m >= -o / 2 && m <= o / 2 && f >= -a / 2 && f <= a / 2;
263
263
  }
264
264
  /**
265
265
  * Get handle positions for the selected content
@@ -274,23 +274,23 @@ class N {
274
274
  const s = this.getContentBounds(e);
275
275
  if (!s)
276
276
  return null;
277
- const { centerX: n, centerY: o, width: a, height: r, rotation: d } = s, l = 6 / this.zoom, c = 30 / this.zoom, p = d * Math.PI / 180, u = Math.cos(p), g = Math.sin(p), m = (b, w) => ({
278
- x: n + b * u - w * g,
279
- y: o + b * g + w * u
277
+ const { centerX: n, centerY: o, width: a, height: r, rotation: l } = s, d = 6 / this.zoom, h = 30 / this.zoom, p = l * Math.PI / 180, c = Math.cos(p), m = Math.sin(p), f = (b, y) => ({
278
+ x: n + b * c - y * m,
279
+ y: o + b * m + y * c
280
280
  }), v = [];
281
281
  if (i.type === "image" && t.imageOptions?.allowScaling || i.type === "text" && t.textOptions?.allowScaling) {
282
- const b = a / 2, w = r / 2, y = m(-b, -w);
283
- v.push({ type: "nw", ...y, radius: l });
284
- const x = m(b, -w);
285
- v.push({ type: "ne", ...x, radius: l });
286
- const C = m(b, w);
287
- v.push({ type: "se", ...C, radius: l });
288
- const M = m(-b, w);
289
- v.push({ type: "sw", ...M, radius: l });
282
+ const b = a / 2, y = r / 2, x = f(-b, -y);
283
+ v.push({ type: "nw", ...x, radius: d });
284
+ const C = f(b, -y);
285
+ v.push({ type: "ne", ...C, radius: d });
286
+ const I = f(b, y);
287
+ v.push({ type: "se", ...I, radius: d });
288
+ const k = f(-b, y);
289
+ v.push({ type: "sw", ...k, radius: d });
290
290
  }
291
291
  if (i.type === "image" && t.imageOptions?.allowRotation || i.type === "text" && t.textOptions?.allowRotation) {
292
- const b = m(0, -r / 2 - c);
293
- v.push({ type: "rotate", ...b, radius: l });
292
+ const b = f(0, -r / 2 - h);
293
+ v.push({ type: "rotate", ...b, radius: d });
294
294
  }
295
295
  return v;
296
296
  }
@@ -344,15 +344,15 @@ class N {
344
344
  checkSafeAreaViolations() {
345
345
  if (!this.safeArea || !this.artboard)
346
346
  return [];
347
- const e = [], { top: t, right: i, bottom: s, left: n } = this.safeArea, o = n, a = t, r = this.artboard.width - i, d = this.artboard.height - s;
348
- for (const l of this.areas) {
349
- if (!this.contents.get(l.id))
347
+ const e = [], { top: t, right: i, bottom: s, left: n } = this.safeArea, o = n, a = t, r = this.artboard.width - i, l = this.artboard.height - s;
348
+ for (const d of this.areas) {
349
+ if (!this.contents.get(d.id))
350
350
  continue;
351
- const c = this.getContentBounds(l.id);
352
- if (!c)
351
+ const h = this.getContentBounds(d.id);
352
+ if (!h)
353
353
  continue;
354
- const { centerX: p, centerY: u, width: g, height: m, rotation: v } = c, b = v * Math.PI / 180, w = Math.abs(Math.cos(b)), y = Math.abs(Math.sin(b)), x = g * w + m * y, C = g * y + m * w, M = p - x / 2, R = u - C / 2, U = p + x / 2, D = u + C / 2;
355
- (M < o || R < a || U > r || D > d) && e.push(l.id);
354
+ const { centerX: p, centerY: c, width: m, height: f, rotation: v } = h, b = v * Math.PI / 180, y = Math.abs(Math.cos(b)), x = Math.abs(Math.sin(b)), C = m * y + f * x, I = m * x + f * y, k = p - C / 2, V = c - I / 2, F = p + C / 2, $ = c + I / 2;
355
+ (k < o || V < a || F > r || $ > l) && e.push(d.id);
356
356
  }
357
357
  return this.safeAreaViolations = new Set(e), this.requestRender(), e;
358
358
  }
@@ -432,11 +432,11 @@ class N {
432
432
  this.renderAreaWithContent(a);
433
433
  this.backgroundImage?.position === "overlay" && this.renderBackgroundImage(), this.renderSafeAreaGuide();
434
434
  for (const a of this.safeAreaViolations) {
435
- const r = this.areas.find((u) => u.id === a);
435
+ const r = this.areas.find((c) => c.id === a);
436
436
  if (!r)
437
437
  continue;
438
- const { x: d, y: l, width: c, height: p } = r.location;
439
- e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(d, l, c, p), e.restore();
438
+ const { x: l, y: d, width: h, height: p } = r.location;
439
+ e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(l, d, h, p), e.restore();
440
440
  }
441
441
  this.selectedAreaId && this.renderSelectionOverlay(this.selectedAreaId), e.restore();
442
442
  }
@@ -446,25 +446,25 @@ class N {
446
446
  renderSelectionOverlay(e) {
447
447
  const t = this.getContentBounds(e);
448
448
  if (!t) {
449
- const l = this.areas.find((v) => v.id === e);
450
- if (!l)
449
+ const d = this.areas.find((v) => v.id === e);
450
+ if (!d)
451
451
  return;
452
- const { ctx: c } = this, { x: p, y: u, width: g, height: m } = l.location;
453
- c.save(), c.strokeStyle = "#3b82f6", c.lineWidth = 2 / this.zoom, c.setLineDash([]), c.strokeRect(p, u, g, m), c.restore();
452
+ const { ctx: h } = this, { x: p, y: c, width: m, height: f } = d.location;
453
+ h.save(), h.strokeStyle = "#3b82f6", h.lineWidth = 2 / this.zoom, h.setLineDash([]), h.strokeRect(p, c, m, f), h.restore();
454
454
  return;
455
455
  }
456
456
  const { ctx: i } = this, { centerX: s, centerY: n, width: o, height: a, rotation: r } = t;
457
457
  i.save(), i.translate(s, n), i.rotate(r * Math.PI / 180), i.strokeStyle = "#3b82f6", i.lineWidth = 2 / this.zoom, i.setLineDash([]), i.strokeRect(-o / 2, -a / 2, o, a), i.restore();
458
- const d = this.getContentHandlePositions(e);
459
- if (d)
460
- for (const l of d) {
458
+ const l = this.getContentHandlePositions(e);
459
+ if (l)
460
+ for (const d of l) {
461
461
  i.save();
462
- const c = this.hoveredHandle === l.type, p = c ? "#dbeafe" : "#ffffff", u = c ? "#2563eb" : "#3b82f6";
463
- if (l.type === "rotate") {
464
- const g = this.getRotatedPoint(s, n - a / 2, s, n, r);
465
- i.beginPath(), i.strokeStyle = u, i.lineWidth = 1 / this.zoom, i.setLineDash([4 / this.zoom, 4 / this.zoom]), i.moveTo(g.x, g.y), i.lineTo(l.x, l.y), i.stroke(), i.setLineDash([]), i.beginPath(), i.fillStyle = p, i.strokeStyle = u, i.lineWidth = 2 / this.zoom, i.arc(l.x, l.y, l.radius, 0, Math.PI * 2), i.fill(), i.stroke(), i.beginPath(), i.strokeStyle = u, i.lineWidth = 1.5 / this.zoom, i.arc(l.x, l.y, l.radius * 0.5, -Math.PI * 0.8, Math.PI * 0.5), i.stroke();
462
+ const h = this.hoveredHandle === d.type, p = h ? "#dbeafe" : "#ffffff", c = h ? "#2563eb" : "#3b82f6";
463
+ if (d.type === "rotate") {
464
+ const m = this.getRotatedPoint(s, n - a / 2, s, n, r);
465
+ i.beginPath(), i.strokeStyle = c, i.lineWidth = 1 / this.zoom, i.setLineDash([4 / this.zoom, 4 / this.zoom]), i.moveTo(m.x, m.y), i.lineTo(d.x, d.y), i.stroke(), i.setLineDash([]), i.beginPath(), i.fillStyle = p, i.strokeStyle = c, i.lineWidth = 2 / this.zoom, i.arc(d.x, d.y, d.radius, 0, Math.PI * 2), i.fill(), i.stroke(), i.beginPath(), i.strokeStyle = c, i.lineWidth = 1.5 / this.zoom, i.arc(d.x, d.y, d.radius * 0.5, -Math.PI * 0.8, Math.PI * 0.5), i.stroke();
466
466
  } else
467
- i.fillStyle = p, i.strokeStyle = u, i.lineWidth = 2 / this.zoom, i.fillRect(l.x - l.radius, l.y - l.radius, l.radius * 2, l.radius * 2), i.strokeRect(l.x - l.radius, l.y - l.radius, l.radius * 2, l.radius * 2);
467
+ i.fillStyle = p, i.strokeStyle = c, i.lineWidth = 2 / this.zoom, i.fillRect(d.x - d.radius, d.y - d.radius, d.radius * 2, d.radius * 2), i.strokeRect(d.x - d.radius, d.y - d.radius, d.radius * 2, d.radius * 2);
468
468
  i.restore();
469
469
  }
470
470
  }
@@ -472,10 +472,10 @@ class N {
472
472
  * Rotate a point around a center
473
473
  */
474
474
  getRotatedPoint(e, t, i, s, n) {
475
- const o = n * Math.PI / 180, a = Math.cos(o), r = Math.sin(o), d = e - i, l = t - s;
475
+ const o = n * Math.PI / 180, a = Math.cos(o), r = Math.sin(o), l = e - i, d = t - s;
476
476
  return {
477
- x: i + d * a - l * r,
478
- y: s + d * r + l * a
477
+ x: i + l * a - d * r,
478
+ y: s + l * r + d * a
479
479
  };
480
480
  }
481
481
  /**
@@ -487,8 +487,8 @@ class N {
487
487
  const { ctx: e } = this, { opacity: t } = this.backgroundImage, { width: i, height: s } = this.artboard;
488
488
  e.save(), e.globalAlpha = t;
489
489
  const n = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = i / s;
490
- let a, r, d, l;
491
- n > o ? (r = s, a = s * n, d = (i - a) / 2, l = 0) : (a = i, r = i / n, d = 0, l = (s - r) / 2), e.drawImage(this.bgImageElement, d, l, a, r), e.restore();
490
+ let a, r, l, d;
491
+ n > o ? (r = s, a = s * n, l = (i - a) / 2, d = 0) : (a = i, r = i / n, l = 0, d = (s - r) / 2), e.drawImage(this.bgImageElement, l, d, a, r), e.restore();
492
492
  }
493
493
  /**
494
494
  * Render safe area guide as dashed green rectangle
@@ -496,8 +496,8 @@ class N {
496
496
  renderSafeAreaGuide() {
497
497
  if (!this.safeArea || !this.artboard)
498
498
  return;
499
- const { ctx: e } = this, { top: t, right: i, bottom: s, left: n } = this.safeArea, o = n, a = t, r = this.artboard.width - n - i, d = this.artboard.height - t - s;
500
- r <= 0 || d <= 0 || (e.save(), e.strokeStyle = "#22c55e", e.lineWidth = 1 / this.zoom, e.setLineDash([6 / this.zoom, 4 / this.zoom]), e.strokeRect(o, a, r, d), e.setLineDash([]), e.restore());
499
+ const { ctx: e } = this, { top: t, right: i, bottom: s, left: n } = this.safeArea, o = n, a = t, r = this.artboard.width - n - i, l = this.artboard.height - t - s;
500
+ r <= 0 || l <= 0 || (e.save(), e.strokeStyle = "#22c55e", e.lineWidth = 1 / this.zoom, e.setLineDash([6 / this.zoom, 4 / this.zoom]), e.strokeRect(o, a, r, l), e.setLineDash([]), e.restore());
501
501
  }
502
502
  /**
503
503
  * Render an area with its content
@@ -505,8 +505,8 @@ class N {
505
505
  renderAreaWithContent(e) {
506
506
  const { ctx: t } = this, { location: i, backgroundColor: s } = e, { x: n, y: o, width: a, height: r } = i;
507
507
  s && (t.fillStyle = s, t.fillRect(n, o, a, r));
508
- const d = this.contents.get(e.id);
509
- d && (d.type === "text" ? this.renderTextContent(e, d) : d.type === "image" && this.renderImageContent(e, d)), this.showAreaBorders && e.showBorder && (t.save(), t.strokeStyle = e.borderColor || "#3b82f6", t.lineWidth = 1 / this.zoom, t.setLineDash([4 / this.zoom, 4 / this.zoom]), t.strokeRect(n, o, a, r), t.setLineDash([]), t.restore());
508
+ const l = this.contents.get(e.id);
509
+ l && (l.type === "text" ? this.renderTextContent(e, l) : l.type === "image" && this.renderImageContent(e, l)), this.showAreaBorders && e.showBorder && (t.save(), t.strokeStyle = e.borderColor || "#3b82f6", t.lineWidth = 1 / this.zoom, t.setLineDash([4 / this.zoom, 4 / this.zoom]), t.strokeRect(n, o, a, r), t.setLineDash([]), t.restore());
510
510
  }
511
511
  /**
512
512
  * Render text content in an area
@@ -514,22 +514,22 @@ class N {
514
514
  renderTextContent(e, t) {
515
515
  if (!t.text.trim())
516
516
  return;
517
- const { ctx: i } = this, { location: s } = e, { x: n, y: o, width: a, height: r } = s, d = t.offset || { x: 0, y: 0 }, l = t.scale ?? 1, c = t.rotation ?? 0, p = n + a / 2 + d.x, u = o + r / 2 + d.y;
518
- i.save(), i.beginPath(), i.rect(n, o, a, r), i.clip(), i.translate(p, u), i.rotate(c * Math.PI / 180), i.scale(l, l), i.font = `${t.size}px ${t.font}, sans-serif`, i.fillStyle = t.color, i.textBaseline = "middle";
519
- let g;
517
+ const { ctx: i } = this, { location: s } = e, { x: n, y: o, width: a, height: r } = s, l = t.offset || { x: 0, y: 0 }, d = t.scale ?? 1, h = t.rotation ?? 0, p = n + a / 2 + l.x, c = o + r / 2 + l.y;
518
+ i.save(), i.beginPath(), i.rect(n, o, a, r), i.clip(), i.translate(p, c), i.rotate(h * Math.PI / 180), i.scale(d, d), i.font = `${t.size}px ${t.font}, sans-serif`, i.fillStyle = t.color, i.textBaseline = "middle";
519
+ let m;
520
520
  switch (t.align) {
521
521
  case "left":
522
- i.textAlign = "left", g = -a / 2 + 10;
522
+ i.textAlign = "left", m = -a / 2 + 10;
523
523
  break;
524
524
  case "right":
525
- i.textAlign = "right", g = a / 2 - 10;
525
+ i.textAlign = "right", m = a / 2 - 10;
526
526
  break;
527
527
  case "center":
528
528
  default:
529
- i.textAlign = "center", g = 0;
529
+ i.textAlign = "center", m = 0;
530
530
  break;
531
531
  }
532
- i.fillText(t.text, g, 0), i.restore();
532
+ i.fillText(t.text, m, 0), i.restore();
533
533
  }
534
534
  /**
535
535
  * Render image content in an area
@@ -538,13 +538,13 @@ class N {
538
538
  const i = this.loadedImages.get(e.id);
539
539
  if (!i)
540
540
  return;
541
- const { ctx: s } = this, { location: n } = e, { x: o, y: a, width: r, height: d } = n, l = t.offset || { x: 0, y: 0 }, c = t.scale ?? 1, p = t.rotation ?? 0;
542
- s.save(), s.beginPath(), s.rect(o, a, r, d), s.clip();
543
- const u = i.naturalWidth / i.naturalHeight, g = r / d;
544
- let m, v;
545
- u > g ? (m = r, v = r / u) : (v = d, m = d * u);
546
- const b = m * c, w = v * c, y = o + r / 2 + l.x, x = a + d / 2 + l.y;
547
- s.translate(y, x), s.rotate(p * Math.PI / 180), s.drawImage(i, -b / 2, -w / 2, b, w), s.restore();
541
+ const { ctx: s } = this, { location: n } = e, { x: o, y: a, width: r, height: l } = n, d = t.offset || { x: 0, y: 0 }, h = t.scale ?? 1, p = t.rotation ?? 0;
542
+ s.save(), s.beginPath(), s.rect(o, a, r, l), s.clip();
543
+ const c = i.naturalWidth / i.naturalHeight, m = r / l;
544
+ let f, v;
545
+ c > m ? (f = r, v = r / c) : (v = l, f = l * c);
546
+ const b = f * h, y = v * h, x = o + r / 2 + d.x, C = a + l / 2 + d.y;
547
+ s.translate(x, C), s.rotate(p * Math.PI / 180), s.drawImage(i, -b / 2, -y / 2, b, y), s.restore();
548
548
  }
549
549
  /**
550
550
  * Draw background image on a specific context (for export)
@@ -555,8 +555,8 @@ class N {
555
555
  const { opacity: s } = this.backgroundImage;
556
556
  e.save(), e.globalAlpha = s;
557
557
  const n = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = t / i;
558
- let a, r, d, l;
559
- n > o ? (r = i, a = i * n, d = (t - a) / 2, l = 0) : (a = t, r = t / n, d = 0, l = (i - r) / 2), e.drawImage(this.bgImageElement, d, l, a, r), e.restore();
558
+ let a, r, l, d;
559
+ n > o ? (r = i, a = i * n, l = (t - a) / 2, d = 0) : (a = t, r = t / n, l = 0, d = (i - r) / 2), e.drawImage(this.bgImageElement, l, d, a, r), e.restore();
560
560
  }
561
561
  /**
562
562
  * Draw area content on a specific context (for export)
@@ -564,38 +564,38 @@ class N {
564
564
  drawAreaContentOnContext(e, t) {
565
565
  const { location: i, backgroundColor: s } = t, { x: n, y: o, width: a, height: r } = i;
566
566
  s && (e.fillStyle = s, e.fillRect(n, o, a, r));
567
- const d = this.contents.get(t.id);
568
- if (d) {
569
- if (d.type === "text") {
570
- if (!d.text.trim())
567
+ const l = this.contents.get(t.id);
568
+ if (l) {
569
+ if (l.type === "text") {
570
+ if (!l.text.trim())
571
571
  return;
572
- const l = d.offset || { x: 0, y: 0 }, c = d.scale ?? 1, p = d.rotation ?? 0, u = n + a / 2 + l.x, g = o + r / 2 + l.y;
573
- e.save(), e.beginPath(), e.rect(n, o, a, r), e.clip(), e.translate(u, g), e.rotate(p * Math.PI / 180), e.scale(c, c), e.font = `${d.size}px ${d.font}, sans-serif`, e.fillStyle = d.color, e.textBaseline = "middle";
574
- let m;
575
- switch (d.align) {
572
+ const d = l.offset || { x: 0, y: 0 }, h = l.scale ?? 1, p = l.rotation ?? 0, c = n + a / 2 + d.x, m = o + r / 2 + d.y;
573
+ e.save(), e.beginPath(), e.rect(n, o, a, r), e.clip(), e.translate(c, m), e.rotate(p * Math.PI / 180), e.scale(h, h), e.font = `${l.size}px ${l.font}, sans-serif`, e.fillStyle = l.color, e.textBaseline = "middle";
574
+ let f;
575
+ switch (l.align) {
576
576
  case "left":
577
- e.textAlign = "left", m = -a / 2 + 10;
577
+ e.textAlign = "left", f = -a / 2 + 10;
578
578
  break;
579
579
  case "right":
580
- e.textAlign = "right", m = a / 2 - 10;
580
+ e.textAlign = "right", f = a / 2 - 10;
581
581
  break;
582
582
  case "center":
583
583
  default:
584
- e.textAlign = "center", m = 0;
584
+ e.textAlign = "center", f = 0;
585
585
  break;
586
586
  }
587
- e.fillText(d.text, m, 0), e.restore();
588
- } else if (d.type === "image") {
589
- const l = this.loadedImages.get(t.id);
590
- if (!l)
587
+ e.fillText(l.text, f, 0), e.restore();
588
+ } else if (l.type === "image") {
589
+ const d = this.loadedImages.get(t.id);
590
+ if (!d)
591
591
  return;
592
- const c = d.offset || { x: 0, y: 0 }, p = d.scale ?? 1, u = d.rotation ?? 0;
592
+ const h = l.offset || { x: 0, y: 0 }, p = l.scale ?? 1, c = l.rotation ?? 0;
593
593
  e.save(), e.beginPath(), e.rect(n, o, a, r), e.clip();
594
- const g = l.naturalWidth / l.naturalHeight, m = a / r;
594
+ const m = d.naturalWidth / d.naturalHeight, f = a / r;
595
595
  let v, b;
596
- g > m ? (v = a, b = a / g) : (b = r, v = r * g);
597
- const w = v * p, y = b * p, x = n + a / 2 + c.x, C = o + r / 2 + c.y;
598
- e.translate(x, C), e.rotate(u * Math.PI / 180), e.drawImage(l, -w / 2, -y / 2, w, y), e.restore();
596
+ m > f ? (v = a, b = a / m) : (b = r, v = r * m);
597
+ const y = v * p, x = b * p, C = n + a / 2 + h.x, I = o + r / 2 + h.y;
598
+ e.translate(C, I), e.rotate(c * Math.PI / 180), e.drawImage(d, -y / 2, -x / 2, y, x), e.restore();
599
599
  }
600
600
  }
601
601
  }
@@ -606,8 +606,8 @@ class N {
606
606
  this.stop(), this.bgImageElement = null, this.loadedImages.clear(), this.loadingImages.clear(), this.eventListeners.clear();
607
607
  }
608
608
  }
609
- function S(f) {
610
- const e = f.textOptions;
609
+ function T(g) {
610
+ const e = g.textOptions;
611
611
  return {
612
612
  type: "text",
613
613
  text: e?.defaultText || "",
@@ -617,14 +617,14 @@ function S(f) {
617
617
  align: e?.defaultAlign || "center"
618
618
  };
619
619
  }
620
- function E(f) {
621
- return f.contentType === "text" || f.contentType === "both";
620
+ function S(g) {
621
+ return g.contentType === "text" || g.contentType === "both";
622
622
  }
623
- function T(f) {
624
- return f.contentType === "image" || f.contentType === "both";
623
+ function R(g) {
624
+ return g.contentType === "image" || g.contentType === "both";
625
625
  }
626
- const V = 50;
627
- class F {
626
+ const q = 50;
627
+ class H {
628
628
  constructor(e) {
629
629
  this.history = [], this.historyIndex = -1, this.listeners = /* @__PURE__ */ new Set(), this.isRestoring = !1, this.state = this.cloneState(e), this.history.push({
630
630
  state: this.cloneState(e),
@@ -675,7 +675,7 @@ class F {
675
675
  state: this.cloneState(e),
676
676
  timestamp: Date.now(),
677
677
  description: t
678
- }), this.history.length > V && this.history.shift(), this.historyIndex = this.history.length - 1;
678
+ }), this.history.length > q && this.history.shift(), this.historyIndex = this.history.length - 1;
679
679
  }
680
680
  restoreFromHistory() {
681
681
  const e = this.history[this.historyIndex];
@@ -705,7 +705,7 @@ class P {
705
705
  buildInitialContents() {
706
706
  const e = /* @__PURE__ */ new Map();
707
707
  for (const t of this.areas)
708
- E(t) && e.set(t.id, S(t));
708
+ S(t) && e.set(t.id, T(t));
709
709
  return e;
710
710
  }
711
711
  getContents() {
@@ -715,7 +715,7 @@ class P {
715
715
  return this.contents.get(e);
716
716
  }
717
717
  setTextContent(e, t) {
718
- const i = this.contents.get(e), s = this.areas.find((o) => o.id === e), n = s ? S(s) : {
718
+ const i = this.contents.get(e), s = this.areas.find((o) => o.id === e), n = s ? T(s) : {
719
719
  type: "text",
720
720
  text: "",
721
721
  font: "Inter",
@@ -743,7 +743,7 @@ class P {
743
743
  }
744
744
  clearContent(e) {
745
745
  const t = this.areas.find((i) => i.id === e);
746
- t && E(t) ? this.contents.set(e, S(t)) : this.contents.delete(e), this.notify();
746
+ t && S(t) ? this.contents.set(e, T(t)) : this.contents.delete(e), this.notify();
747
747
  }
748
748
  setContentOffset(e, t) {
749
749
  const i = this.contents.get(e);
@@ -788,7 +788,7 @@ class P {
788
788
  }
789
789
  }
790
790
  }
791
- class z {
791
+ class M {
792
792
  constructor() {
793
793
  this.listeners = /* @__PURE__ */ new Map();
794
794
  }
@@ -861,7 +861,7 @@ class z {
861
861
  return this.listeners.get(e)?.size ?? 0;
862
862
  }
863
863
  }
864
- class O extends z {
864
+ class Z extends M {
865
865
  constructor() {
866
866
  super(...arguments), this.activeTouches = /* @__PURE__ */ new Map(), this.initialPinchDistance = null, this.lastPinchCenter = null, this.isTwoFingerGesture = !1;
867
867
  }
@@ -924,9 +924,9 @@ class O extends z {
924
924
  };
925
925
  }
926
926
  }
927
- class $ extends z {
927
+ class j extends M {
928
928
  constructor(e, t, i, s) {
929
- super(), this.interactionState = { mode: "idle" }, this.initialPinchZoom = 1, this.canvas = e, this.engine = t, this.getContents = i, this.getSelectedAreaId = s, this.gestureHandler = new O(), this.boundMouseDown = this.handleMouseDown.bind(this), this.boundMouseMove = this.handleMouseMove.bind(this), this.boundMouseUp = this.handleMouseUp.bind(this), this.boundMouseLeave = this.handleMouseLeave.bind(this), this.boundContextMenu = this.handleContextMenu.bind(this), this.boundDoubleClick = this.handleDoubleClick.bind(this), this.boundTouchStart = this.handleTouchStart.bind(this), this.boundTouchMove = this.handleTouchMove.bind(this), this.boundTouchEnd = this.handleTouchEnd.bind(this), e.addEventListener("mousedown", this.boundMouseDown), e.addEventListener("mousemove", this.boundMouseMove), e.addEventListener("mouseup", this.boundMouseUp), e.addEventListener("mouseleave", this.boundMouseLeave), e.addEventListener("contextmenu", this.boundContextMenu), e.addEventListener("dblclick", this.boundDoubleClick), e.addEventListener("touchstart", this.boundTouchStart, { passive: !1 }), e.addEventListener("touchmove", this.boundTouchMove, { passive: !1 }), e.addEventListener("touchend", this.boundTouchEnd), this.gestureHandler.on("pinch", ({ scale: n }) => {
929
+ super(), this.interactionState = { mode: "idle" }, this.initialPinchZoom = 1, this.canvas = e, this.engine = t, this.getContents = i, this.getSelectedAreaId = s, this.gestureHandler = new Z(), this.boundMouseDown = this.handleMouseDown.bind(this), this.boundMouseMove = this.handleMouseMove.bind(this), this.boundMouseUp = this.handleMouseUp.bind(this), this.boundMouseLeave = this.handleMouseLeave.bind(this), this.boundContextMenu = this.handleContextMenu.bind(this), this.boundDoubleClick = this.handleDoubleClick.bind(this), this.boundTouchStart = this.handleTouchStart.bind(this), this.boundTouchMove = this.handleTouchMove.bind(this), this.boundTouchEnd = this.handleTouchEnd.bind(this), e.addEventListener("mousedown", this.boundMouseDown), e.addEventListener("mousemove", this.boundMouseMove), e.addEventListener("mouseup", this.boundMouseUp), e.addEventListener("mouseleave", this.boundMouseLeave), e.addEventListener("contextmenu", this.boundContextMenu), e.addEventListener("dblclick", this.boundDoubleClick), e.addEventListener("touchstart", this.boundTouchStart, { passive: !1 }), e.addEventListener("touchmove", this.boundTouchMove, { passive: !1 }), e.addEventListener("touchend", this.boundTouchEnd), this.gestureHandler.on("pinch", ({ scale: n }) => {
930
930
  const o = Math.max(0.1, Math.min(5, this.initialPinchZoom * n));
931
931
  this.emit("zoom", { zoom: o });
932
932
  }), this.gestureHandler.on("pan", ({ deltaX: n, deltaY: o }) => {
@@ -992,13 +992,13 @@ class $ extends z {
992
992
  if (o) {
993
993
  const a = i.get(t), r = this.engine.getContentBounds(t);
994
994
  if (o === "rotate" && a?.type === "image" && r) {
995
- const d = Math.atan2(e.y - r.centerY, e.x - r.centerX) * (180 / Math.PI);
995
+ const l = Math.atan2(e.y - r.centerY, e.x - r.centerX) * (180 / Math.PI);
996
996
  return this.interactionState = {
997
997
  mode: "rotating",
998
998
  areaId: t,
999
999
  startRotation: a.rotation ?? 0,
1000
1000
  centerPoint: { x: r.centerX, y: r.centerY },
1001
- startAngle: d
1001
+ startAngle: l
1002
1002
  }, !0;
1003
1003
  } else if (o !== "rotate" && a?.type === "image" && r)
1004
1004
  return this.interactionState = {
@@ -1034,8 +1034,8 @@ class $ extends z {
1034
1034
  y: i.y + o
1035
1035
  }, r = this.engine.getAreaLocation(t);
1036
1036
  if (r) {
1037
- const d = r.width / 2 - 10, l = r.height / 2 - 10;
1038
- a.x = Math.max(-d, Math.min(d, a.x)), a.y = Math.max(-l, Math.min(l, a.y));
1037
+ const l = r.width / 2 - 10, d = r.height / 2 - 10;
1038
+ a.x = Math.max(-l, Math.min(l, a.x)), a.y = Math.max(-d, Math.min(d, a.y));
1039
1039
  }
1040
1040
  this.emit("content:drag", { areaId: t, offset: a });
1041
1041
  return;
@@ -1047,8 +1047,8 @@ class $ extends z {
1047
1047
  Math.pow(e.x - n.x, 2) + Math.pow(e.y - n.y, 2)
1048
1048
  );
1049
1049
  if (o > 0) {
1050
- const r = a / o, d = Math.max(0.1, Math.min(5, i * r));
1051
- this.emit("content:scale", { areaId: t, scale: d });
1050
+ const r = a / o, l = Math.max(0.1, Math.min(5, i * r));
1051
+ this.emit("content:scale", { areaId: t, scale: l });
1052
1052
  }
1053
1053
  return;
1054
1054
  }
@@ -1057,9 +1057,9 @@ class $ extends z {
1057
1057
  let a = i + (o - n);
1058
1058
  a = a % 360, a < 0 && (a += 360);
1059
1059
  const r = [0, 90, 180, 270, 360];
1060
- for (const d of r)
1061
- if (Math.abs(a - d) < 5) {
1062
- a = d === 360 ? 0 : d;
1060
+ for (const l of r)
1061
+ if (Math.abs(a - l) < 5) {
1062
+ a = l === 360 ? 0 : l;
1063
1063
  break;
1064
1064
  }
1065
1065
  this.emit("content:rotate", { areaId: t, rotation: a });
@@ -1087,7 +1087,7 @@ class $ extends z {
1087
1087
  this.canvas.removeEventListener("mousedown", this.boundMouseDown), this.canvas.removeEventListener("mousemove", this.boundMouseMove), this.canvas.removeEventListener("mouseup", this.boundMouseUp), this.canvas.removeEventListener("mouseleave", this.boundMouseLeave), this.canvas.removeEventListener("contextmenu", this.boundContextMenu), this.canvas.removeEventListener("dblclick", this.boundDoubleClick), this.canvas.removeEventListener("touchstart", this.boundTouchStart), this.canvas.removeEventListener("touchmove", this.boundTouchMove), this.canvas.removeEventListener("touchend", this.boundTouchEnd), this.gestureHandler.reset(), this.clear();
1088
1088
  }
1089
1089
  }
1090
- class B {
1090
+ class Y {
1091
1091
  constructor(e = "/api") {
1092
1092
  this.baseUrl = e;
1093
1093
  }
@@ -1119,30 +1119,32 @@ class B {
1119
1119
  throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error(`Network error loading product: ${e}`) : t;
1120
1120
  }
1121
1121
  }
1122
- async finalizeDesign(e, t) {
1122
+ async finalizeDesign(e, t, i) {
1123
1123
  try {
1124
- const i = await fetch(`${this.baseUrl}/finalize`, {
1124
+ const s = await fetch(`${this.baseUrl}/finalize`, {
1125
1125
  method: "POST",
1126
1126
  headers: { "Content-Type": "application/json" },
1127
1127
  body: JSON.stringify({
1128
1128
  shopId: e,
1129
1129
  templateId: t.templateId,
1130
- designJson: t
1130
+ designJson: t,
1131
+ ...i?.cartContext && { cartContext: i.cartContext },
1132
+ ...i?.customerEmail && { customerEmail: i.customerEmail }
1131
1133
  })
1132
1134
  });
1133
- if (!i.ok) {
1134
- let s = `Failed to finalize design (${i.status} ${i.statusText})
1135
+ if (!s.ok) {
1136
+ let n = `Failed to finalize design (${s.status} ${s.statusText})
1135
1137
 
1136
1138
  `;
1137
- if (i.status === 400) {
1138
- const n = await i.json().catch(() => ({}));
1139
- s += n.error || "Invalid design data.";
1140
- } else i.status === 429 ? s += "Rate limit exceeded." : i.status >= 500 && (s += "Server error.");
1141
- throw new Error(s);
1139
+ if (s.status === 400) {
1140
+ const o = await s.json().catch(() => ({}));
1141
+ n += o.error || "Invalid design data.";
1142
+ } else s.status === 429 ? n += "Rate limit exceeded." : s.status >= 500 && (n += "Server error.");
1143
+ throw new Error(n);
1142
1144
  }
1143
- return i.json();
1144
- } catch (i) {
1145
- throw i instanceof Error && i.message.includes("Failed to fetch") ? new Error("Network error finalizing design") : i;
1145
+ return s.json();
1146
+ } catch (s) {
1147
+ throw s instanceof Error && s.message.includes("Failed to fetch") ? new Error("Network error finalizing design") : s;
1146
1148
  }
1147
1149
  }
1148
1150
  async finalizeMultiView(e) {
@@ -1252,52 +1254,52 @@ class B {
1252
1254
  }
1253
1255
  }
1254
1256
  }
1255
- function h(f, e, ...t) {
1256
- const i = document.createElement(f);
1257
+ function u(g, e, ...t) {
1258
+ const i = document.createElement(g);
1257
1259
  return e && Object.entries(e).forEach(([s, n]) => {
1258
1260
  s === "class" ? i.className = n : s in i ? i[s] = n : i.setAttribute(s, String(n));
1259
1261
  }), t.forEach((s) => {
1260
1262
  typeof s == "string" ? i.appendChild(document.createTextNode(s)) : i.appendChild(s);
1261
1263
  }), i;
1262
1264
  }
1263
- function q(f, e) {
1265
+ function W(g, e) {
1264
1266
  let t = null, i = null;
1265
1267
  const s = (...n) => {
1266
1268
  i = n, t && clearTimeout(t), t = setTimeout(() => {
1267
- i && f(...i), t = null, i = null;
1269
+ i && g(...i), t = null, i = null;
1268
1270
  }, e);
1269
1271
  };
1270
1272
  return s.flush = () => {
1271
- t && (clearTimeout(t), t = null), i && (f(...i), i = null);
1273
+ t && (clearTimeout(t), t = null), i && (g(...i), i = null);
1272
1274
  }, s.cancel = () => {
1273
1275
  t && (clearTimeout(t), t = null), i = null;
1274
1276
  }, s;
1275
1277
  }
1276
- class H extends z {
1278
+ class _ extends M {
1277
1279
  constructor() {
1278
- super(), this.saveEnabled = !0, this.percentLabel = h("span", { class: "zoom-percent" }, "100%");
1279
- const e = h("button", {
1280
+ super(), this.saveEnabled = !0, this.percentLabel = u("span", { class: "zoom-percent" }, "100%");
1281
+ const e = u("button", {
1280
1282
  class: "zoom-btn",
1281
1283
  title: "Zoom out"
1282
1284
  }, "−");
1283
1285
  e.addEventListener("click", () => this.emit("zoom-out", void 0));
1284
- const t = h("button", {
1286
+ const t = u("button", {
1285
1287
  class: "zoom-btn",
1286
1288
  title: "Fit to view"
1287
1289
  }, "Fit");
1288
1290
  t.addEventListener("click", () => this.emit("zoom-fit", void 0));
1289
- const i = h("button", {
1291
+ const i = u("button", {
1290
1292
  class: "zoom-btn",
1291
1293
  title: "Zoom in"
1292
1294
  }, "+");
1293
1295
  i.addEventListener("click", () => this.emit("zoom-in", void 0)), this.viewSelect = document.createElement("select"), this.viewSelect.className = "view-select", this.viewSelect.title = "Switch view", this.viewSelect.style.display = "none", this.viewSelect.addEventListener("change", () => {
1294
1296
  this.emit("view-change", { viewName: this.viewSelect.value });
1295
- }), this.element = h("div", { class: "zoom-toolbar" }), this.element.appendChild(e), this.element.appendChild(t), this.element.appendChild(i), this.element.appendChild(this.percentLabel), this.element.appendChild(this.viewSelect);
1296
- const s = h("div", { class: "zoom-toolbar-spacer" });
1297
- this.element.appendChild(s), this.saveBtn = h("button", {
1297
+ }), this.element = u("div", { class: "zoom-toolbar" }), this.element.appendChild(e), this.element.appendChild(t), this.element.appendChild(i), this.element.appendChild(this.percentLabel), this.element.appendChild(this.viewSelect);
1298
+ const s = u("div", { class: "zoom-toolbar-spacer" });
1299
+ this.element.appendChild(s), this.saveBtn = u("button", {
1298
1300
  class: "zoom-btn zoom-save-btn",
1299
1301
  title: "Save customization"
1300
- }, "✓"), this.saveBtn.style.display = "none", this.saveBtn.addEventListener("click", () => this.emit("save", void 0)), this.element.appendChild(this.saveBtn), this.closeBtn = h("button", {
1302
+ }, "✓"), this.saveBtn.style.display = "none", this.saveBtn.addEventListener("click", () => this.emit("save", void 0)), this.element.appendChild(this.saveBtn), this.closeBtn = u("button", {
1301
1303
  class: "zoom-btn zoom-close-btn",
1302
1304
  title: "Close editor"
1303
1305
  }, "✕"), this.closeBtn.addEventListener("click", () => this.emit("close", void 0)), this.element.appendChild(this.closeBtn);
@@ -1332,56 +1334,67 @@ class H extends z {
1332
1334
  return this.element;
1333
1335
  }
1334
1336
  }
1335
- const Z = [
1337
+ const J = /[\x00-\x08\x0B\x0C\x0E-\x1F]/g;
1338
+ function X(g) {
1339
+ return typeof g != "string" ? "" : g.replace(J, "");
1340
+ }
1341
+ const G = /^#[0-9a-fA-F]{6}$/;
1342
+ function K(g) {
1343
+ return typeof g == "string" && G.test(g);
1344
+ }
1345
+ function Q(g) {
1346
+ return typeof g != "string" ? "" : g.replace(/[\\/\0]/g, "");
1347
+ }
1348
+ const ee = [
1336
1349
  { label: "Inter", value: "Inter" },
1337
1350
  { label: "Arial", value: "Arial" },
1338
1351
  { label: "Georgia", value: "Georgia" },
1339
1352
  { label: "Times New Roman", value: "Times New Roman" },
1340
1353
  { label: "Courier New", value: "Courier New" }
1341
- ], j = [
1354
+ ], te = [
1342
1355
  { label: "L", value: "left" },
1343
1356
  { label: "C", value: "center" },
1344
1357
  { label: "R", value: "right" }
1345
- ], A = ["image/png", "image/jpeg", "image/webp", "image/svg+xml"], Y = 15 * 1024 * 1024;
1346
- class L extends z {
1358
+ ], L = ["image/png", "image/jpeg", "image/webp", "image/svg+xml"], ie = 15 * 1024 * 1024;
1359
+ class U extends M {
1347
1360
  constructor(e) {
1348
1361
  super(), this.mode = "text", this.isSelected = !1, this.isUploading = !1, this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null, this.area = e;
1349
- const t = E(e), i = T(e);
1350
- this.mode = i && !t ? "image" : "text", this.element = h("div", { class: "area-card" }), this.element.addEventListener("click", (r) => {
1362
+ const t = S(e), i = R(e);
1363
+ this.mode = i && !t ? "image" : "text", this.element = u("div", { class: "area-card" }), this.element.addEventListener("click", (r) => {
1351
1364
  r.target.tagName === "INPUT" || r.target.tagName === "SELECT" || r.target.tagName === "BUTTON" || this.emit("select", { areaId: e.id });
1352
1365
  });
1353
- const s = h("div", { class: "area-card-header" }), n = h("div", { class: "area-card-name-row" }), o = h("span", { class: "area-card-name" }, e.name);
1366
+ const s = u("div", { class: "area-card-header" }), n = u("div", { class: "area-card-name-row" }), o = u("span", { class: "area-card-name" }, e.name);
1354
1367
  if (n.appendChild(o), e.required) {
1355
- const r = h("span", { class: "area-card-badge" }, "Required");
1368
+ const r = u("span", { class: "area-card-badge" }, "Required");
1356
1369
  n.appendChild(r);
1357
1370
  }
1358
1371
  s.appendChild(n);
1359
- const a = h("button", {
1372
+ const a = u("button", {
1360
1373
  class: "area-card-reset-btn",
1361
1374
  title: "Reset content"
1362
1375
  }, "Reset");
1363
1376
  if (a.addEventListener("click", (r) => {
1364
1377
  r.stopPropagation(), this.emit("clear", { areaId: e.id });
1365
1378
  }), s.appendChild(a), this.element.appendChild(s), t && i) {
1366
- const r = h("div", { class: "area-card-mode-switcher" }), d = h("button", {
1379
+ const r = u("div", { class: "area-card-mode-switcher" }), l = u("button", {
1367
1380
  class: "mode-btn mode-btn-active"
1368
1381
  }, "Text");
1369
- d.dataset.mode = "text";
1370
- const l = h("button", { class: "mode-btn" }, "Image");
1371
- l.dataset.mode = "image", d.addEventListener("click", (c) => {
1372
- c.stopPropagation(), this.setMode("text"), d.classList.add("mode-btn-active"), l.classList.remove("mode-btn-active"), this.currentContent?.type === "image" && this.emit("clear", { areaId: e.id });
1373
- }), l.addEventListener("click", (c) => {
1374
- c.stopPropagation(), this.setMode("image"), l.classList.add("mode-btn-active"), d.classList.remove("mode-btn-active"), this.currentContent?.type === "text" && this.emit("clear", { areaId: e.id });
1375
- }), r.appendChild(d), r.appendChild(l), this.element.appendChild(r);
1382
+ l.dataset.mode = "text";
1383
+ const d = u("button", { class: "mode-btn" }, "Image");
1384
+ d.dataset.mode = "image", l.addEventListener("click", (h) => {
1385
+ h.stopPropagation(), this.setMode("text"), l.classList.add("mode-btn-active"), d.classList.remove("mode-btn-active"), this.currentContent?.type === "image" && this.emit("clear", { areaId: e.id });
1386
+ }), d.addEventListener("click", (h) => {
1387
+ h.stopPropagation(), this.setMode("image"), d.classList.add("mode-btn-active"), l.classList.remove("mode-btn-active"), this.currentContent?.type === "text" && this.emit("clear", { areaId: e.id });
1388
+ }), r.appendChild(l), r.appendChild(d), this.element.appendChild(r);
1376
1389
  }
1377
- this.contentContainer = h("div", { class: "area-card-content" }), this.element.appendChild(this.contentContainer), this.transformContainer = h("div", { class: "area-card-transforms" }), this.element.appendChild(this.transformContainer), this.renderContent();
1390
+ this.contentContainer = u("div", { class: "area-card-content" }), this.element.appendChild(this.contentContainer), this.transformContainer = u("div", { class: "area-card-transforms" }), this.element.appendChild(this.transformContainer), this.renderContent();
1378
1391
  }
1379
1392
  setMode(e) {
1380
1393
  this.mode = e, this.renderContent();
1381
1394
  }
1382
1395
  setContent(e) {
1383
1396
  const t = this.currentContent?.type;
1384
- this.currentContent = e, e?.type === "image" ? this.mode = "image" : t === "image" && (this.mode = E(this.area) ? "text" : "image"), this.element.querySelectorAll(".mode-btn").forEach((i) => {
1397
+ this.currentContent = e, e?.type === "image" ? this.mode = "image" : t === "image" && (this.mode = S(this.area) ? "text" : "image"), this.element.querySelectorAll(".mode-btn").forEach((i) => {
1385
1398
  const s = i;
1386
1399
  s.classList.toggle("mode-btn-active", s.dataset.mode === this.mode);
1387
1400
  }), e?.type !== t || e?.type === "image" || e === void 0 ? this.renderContent() : e?.type === "text" && this.updateTextValues(e), this.renderTransforms();
@@ -1397,27 +1410,28 @@ class L extends z {
1397
1410
  }
1398
1411
  renderContent() {
1399
1412
  this.contentContainer.innerHTML = "", this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null;
1400
- const e = E(this.area), t = T(this.area), i = !t || e && t && this.mode === "text", s = !e || e && t && this.mode === "image";
1413
+ const e = S(this.area), t = R(this.area), i = !t || e && t && this.mode === "text", s = !e || e && t && this.mode === "image";
1401
1414
  e && i && this.renderTextControls(), t && s && this.renderImageControls(), this.applyUploadSpinner();
1402
1415
  }
1403
1416
  renderTextControls() {
1404
- const e = this.currentContent, t = e?.type === "text" ? e : S(this.area), i = h("input", {
1417
+ const e = this.currentContent, t = e?.type === "text" ? e : T(this.area), i = u("input", {
1405
1418
  class: "area-input-text",
1406
1419
  type: "text",
1407
1420
  placeholder: this.area.placeholder || "Enter text..."
1408
1421
  });
1409
1422
  i.value = t.text, this.area.textOptions?.maxLength && (i.maxLength = this.area.textOptions.maxLength), i.addEventListener("input", () => {
1410
- this.emit("text:change", { areaId: this.area.id, updates: { text: i.value } });
1423
+ const d = X(i.value);
1424
+ d !== i.value && (i.value = d), this.emit("text:change", { areaId: this.area.id, updates: { text: d } });
1411
1425
  }), this.contentContainer.appendChild(i), this.textInputEl = i;
1412
- const s = h("div", { class: "area-input-row" }), n = h("select", { class: "area-input-font" });
1413
- for (const l of Z) {
1414
- const c = h("option");
1415
- c.value = l.value, c.textContent = l.label, l.value === t.font && (c.selected = !0), n.appendChild(c);
1426
+ const s = u("div", { class: "area-input-row" }), n = u("select", { class: "area-input-font" });
1427
+ for (const d of ee) {
1428
+ const h = u("option");
1429
+ h.value = d.value, h.textContent = d.label, d.value === t.font && (h.selected = !0), n.appendChild(h);
1416
1430
  }
1417
1431
  n.addEventListener("change", () => {
1418
1432
  this.emit("text:change", { areaId: this.area.id, updates: { font: n.value } });
1419
1433
  }), s.appendChild(n), this.fontSelectEl = n;
1420
- const o = h("input", {
1434
+ const o = u("input", {
1421
1435
  class: "area-input-size",
1422
1436
  type: "number"
1423
1437
  });
@@ -1427,49 +1441,49 @@ class L extends z {
1427
1441
  updates: { size: parseInt(o.value, 10) || 24 }
1428
1442
  });
1429
1443
  }), s.appendChild(o), this.sizeInputEl = o, this.contentContainer.appendChild(s);
1430
- const a = h("div", { class: "area-input-row" }), r = h("div", { class: "area-input-align" });
1431
- for (const l of j) {
1432
- const c = h("button", {
1433
- class: `align-btn${t.align === l.value ? " align-btn-active" : ""}`,
1434
- title: l.label === "L" ? "Left" : l.label === "C" ? "Center" : "Right"
1435
- }, l.label);
1436
- c.addEventListener("click", (p) => {
1437
- p.stopPropagation(), this.emit("text:change", { areaId: this.area.id, updates: { align: l.value } }), r.querySelectorAll(".align-btn").forEach((u) => u.classList.remove("align-btn-active")), c.classList.add("align-btn-active");
1438
- }), r.appendChild(c);
1444
+ const a = u("div", { class: "area-input-row" }), r = u("div", { class: "area-input-align" });
1445
+ for (const d of te) {
1446
+ const h = u("button", {
1447
+ class: `align-btn${t.align === d.value ? " align-btn-active" : ""}`,
1448
+ title: d.label === "L" ? "Left" : d.label === "C" ? "Center" : "Right"
1449
+ }, d.label);
1450
+ h.addEventListener("click", (p) => {
1451
+ p.stopPropagation(), this.emit("text:change", { areaId: this.area.id, updates: { align: d.value } }), r.querySelectorAll(".align-btn").forEach((c) => c.classList.remove("align-btn-active")), h.classList.add("align-btn-active");
1452
+ }), r.appendChild(h);
1439
1453
  }
1440
1454
  a.appendChild(r), this.alignGroupEl = r;
1441
- const d = h("input", {
1455
+ const l = u("input", {
1442
1456
  class: "area-input-color",
1443
1457
  type: "color"
1444
1458
  });
1445
- d.value = t.color, d.addEventListener("input", () => {
1446
- this.emit("text:change", { areaId: this.area.id, updates: { color: d.value } });
1447
- }), a.appendChild(d), this.colorInputEl = d, this.contentContainer.appendChild(a);
1459
+ l.value = t.color, l.addEventListener("input", () => {
1460
+ K(l.value) && this.emit("text:change", { areaId: this.area.id, updates: { color: l.value } });
1461
+ }), a.appendChild(l), this.colorInputEl = l, this.contentContainer.appendChild(a);
1448
1462
  }
1449
1463
  renderImageControls() {
1450
1464
  const e = this.currentContent;
1451
1465
  if (e?.type === "image") {
1452
- const t = h("div", { class: "area-image-preview" }), i = h("img", { class: "area-image-thumb" });
1453
- i.src = e.dataUrl, i.alt = e.filename || "Uploaded image", t.appendChild(i);
1454
- const s = h("div", { class: "area-image-info" });
1455
- s.appendChild(h("span", { class: "area-image-name" }, e.filename || "Image"));
1456
- const n = h("button", { class: "area-image-remove-btn" }, "Remove");
1466
+ const t = u("div", { class: "area-image-preview" }), i = u("img", { class: "area-image-thumb" });
1467
+ i.crossOrigin = "anonymous", i.src = e.dataUrl, i.alt = e.filename ? Q(e.filename) : "Uploaded image", t.appendChild(i);
1468
+ const s = u("div", { class: "area-image-info" });
1469
+ s.appendChild(u("span", { class: "area-image-name" }, e.filename || "Image"));
1470
+ const n = u("button", { class: "area-image-remove-btn" }, "Remove");
1457
1471
  n.addEventListener("click", (o) => {
1458
1472
  o.stopPropagation(), this.emit("clear", { areaId: this.area.id });
1459
1473
  }), s.appendChild(n), t.appendChild(s), this.contentContainer.appendChild(t);
1460
1474
  } else {
1461
- const t = h("button", { class: "area-upload-btn" }, "Upload Image"), i = h("input", { type: "file" });
1462
- i.accept = A.join(","), i.style.display = "none";
1463
- const s = h("div", { class: "area-validation-error" });
1475
+ const t = u("button", { class: "area-upload-btn" }, "Upload Image"), i = u("input", { type: "file" });
1476
+ i.accept = L.join(","), i.style.display = "none";
1477
+ const s = u("div", { class: "area-validation-error" });
1464
1478
  s.style.display = "none", i.addEventListener("change", () => {
1465
1479
  const n = i.files?.[0];
1466
1480
  if (!n) return;
1467
- if (!A.includes(n.type)) {
1481
+ if (!L.includes(n.type)) {
1468
1482
  const a = "Only PNG, JPEG, WebP, and SVG images are accepted";
1469
1483
  s.textContent = a, s.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: a }), i.value = "";
1470
1484
  return;
1471
1485
  }
1472
- if (n.size > Y) {
1486
+ if (n.size > ie) {
1473
1487
  const a = "File must be smaller than 15MB";
1474
1488
  s.textContent = a, s.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: a }), i.value = "";
1475
1489
  return;
@@ -1491,44 +1505,44 @@ class L extends z {
1491
1505
  if (!this.isSelected || !e) return;
1492
1506
  const t = e.type === "text" ? this.area.textOptions?.allowPositioning : this.area.imageOptions?.allowPositioning, i = e.type === "image" && this.area.imageOptions?.allowScaling, s = e.type === "image" && this.area.imageOptions?.allowRotation;
1493
1507
  if (!t && !i && !s) return;
1494
- const n = h("div", { class: "area-card-divider" });
1508
+ const n = u("div", { class: "area-card-divider" });
1495
1509
  this.transformContainer.appendChild(n);
1496
- const o = h("div", { class: "transform-header" });
1497
- o.appendChild(h("span", { class: "transform-title" }, "Transform"));
1498
- const a = h("button", { class: "transform-reset-btn" }, "Reset");
1499
- a.addEventListener("click", (d) => {
1500
- d.stopPropagation(), t && this.emit("offset:change", { areaId: this.area.id, offset: { x: 0, y: 0 } }), i && this.emit("scale:change", { areaId: this.area.id, scale: 1 }), s && this.emit("rotation:change", { areaId: this.area.id, rotation: 0 });
1510
+ const o = u("div", { class: "transform-header" });
1511
+ o.appendChild(u("span", { class: "transform-title" }, "Transform"));
1512
+ const a = u("button", { class: "transform-reset-btn" }, "Reset");
1513
+ a.addEventListener("click", (l) => {
1514
+ l.stopPropagation(), t && this.emit("offset:change", { areaId: this.area.id, offset: { x: 0, y: 0 } }), i && this.emit("scale:change", { areaId: this.area.id, scale: 1 }), s && this.emit("rotation:change", { areaId: this.area.id, rotation: 0 });
1501
1515
  }), o.appendChild(a), this.transformContainer.appendChild(o);
1502
1516
  const r = e.offset ?? { x: 0, y: 0 };
1503
1517
  if (t) {
1504
- const d = h("div", { class: "area-input-row" }), l = h("label", { class: "transform-label" }, "X"), c = h("input", { class: "transform-input", type: "number" });
1505
- c.value = String(Math.round(r.x)), c.addEventListener("change", () => {
1518
+ const l = u("div", { class: "area-input-row" }), d = u("label", { class: "transform-label" }, "X"), h = u("input", { class: "transform-input", type: "number" });
1519
+ h.value = String(Math.round(r.x)), h.addEventListener("change", () => {
1506
1520
  this.emit("offset:change", {
1507
1521
  areaId: this.area.id,
1508
- offset: { ...r, x: parseInt(c.value, 10) || 0 }
1522
+ offset: { ...r, x: parseInt(h.value, 10) || 0 }
1509
1523
  });
1510
1524
  });
1511
- const p = h("label", { class: "transform-label" }, "Y"), u = h("input", { class: "transform-input", type: "number" });
1512
- u.value = String(Math.round(r.y)), u.addEventListener("change", () => {
1525
+ const p = u("label", { class: "transform-label" }, "Y"), c = u("input", { class: "transform-input", type: "number" });
1526
+ c.value = String(Math.round(r.y)), c.addEventListener("change", () => {
1513
1527
  this.emit("offset:change", {
1514
1528
  areaId: this.area.id,
1515
- offset: { ...r, y: parseInt(u.value, 10) || 0 }
1529
+ offset: { ...r, y: parseInt(c.value, 10) || 0 }
1516
1530
  });
1517
- }), d.appendChild(l), d.appendChild(c), d.appendChild(p), d.appendChild(u), this.transformContainer.appendChild(d);
1531
+ }), l.appendChild(d), l.appendChild(h), l.appendChild(p), l.appendChild(c), this.transformContainer.appendChild(l);
1518
1532
  }
1519
1533
  if (i && e.type === "image") {
1520
- const d = e.scale ?? 1, l = h("div", { class: "area-input-row transform-slider-row" }), c = h("label", { class: "transform-label" }, `Scale: ${Math.round(d * 100)}%`), p = h("input", { class: "transform-slider", type: "range" });
1521
- p.min = "10", p.max = "500", p.step = "5", p.value = String(Math.round(d * 100)), p.addEventListener("input", () => {
1522
- const u = parseInt(p.value, 10) / 100;
1523
- c.textContent = `Scale: ${Math.round(u * 100)}%`, this.emit("scale:change", { areaId: this.area.id, scale: u });
1524
- }), l.appendChild(c), l.appendChild(p), this.transformContainer.appendChild(l);
1534
+ const l = e.scale ?? 1, d = u("div", { class: "area-input-row transform-slider-row" }), h = u("label", { class: "transform-label" }, `Scale: ${Math.round(l * 100)}%`), p = u("input", { class: "transform-slider", type: "range" });
1535
+ p.min = "10", p.max = "500", p.step = "5", p.value = String(Math.round(l * 100)), p.addEventListener("input", () => {
1536
+ const c = parseInt(p.value, 10) / 100;
1537
+ h.textContent = `Scale: ${Math.round(c * 100)}%`, this.emit("scale:change", { areaId: this.area.id, scale: c });
1538
+ }), d.appendChild(h), d.appendChild(p), this.transformContainer.appendChild(d);
1525
1539
  }
1526
1540
  if (s && e.type === "image") {
1527
- const d = e.rotation ?? 0, l = h("div", { class: "area-input-row transform-slider-row" }), c = h("label", { class: "transform-label" }, `Rotation: ${Math.round(d)}°`), p = h("input", { class: "transform-slider", type: "range" });
1528
- p.min = "0", p.max = "360", p.step = "1", p.value = String(Math.round(d)), p.addEventListener("input", () => {
1529
- const u = parseInt(p.value, 10);
1530
- c.textContent = `Rotation: ${u}°`, this.emit("rotation:change", { areaId: this.area.id, rotation: u });
1531
- }), l.appendChild(c), l.appendChild(p), this.transformContainer.appendChild(l);
1541
+ const l = e.rotation ?? 0, d = u("div", { class: "area-input-row transform-slider-row" }), h = u("label", { class: "transform-label" }, `Rotation: ${Math.round(l)}°`), p = u("input", { class: "transform-slider", type: "range" });
1542
+ p.min = "0", p.max = "360", p.step = "1", p.value = String(Math.round(l)), p.addEventListener("input", () => {
1543
+ const c = parseInt(p.value, 10);
1544
+ h.textContent = `Rotation: ${c}°`, this.emit("rotation:change", { areaId: this.area.id, rotation: c });
1545
+ }), d.appendChild(h), d.appendChild(p), this.transformContainer.appendChild(d);
1532
1546
  }
1533
1547
  }
1534
1548
  setUploading(e) {
@@ -1539,43 +1553,43 @@ class L extends z {
1539
1553
  if (!e) return;
1540
1554
  const t = e.querySelector(".area-upload-spinner-overlay");
1541
1555
  if (this.isUploading && !t) {
1542
- const i = h("div", { class: "area-upload-spinner-overlay" });
1543
- i.appendChild(h("div", { class: "area-upload-spinner" })), e.appendChild(i);
1556
+ const i = u("div", { class: "area-upload-spinner-overlay" });
1557
+ i.appendChild(u("div", { class: "area-upload-spinner" })), e.appendChild(i);
1544
1558
  } else !this.isUploading && t && t.remove();
1545
1559
  }
1546
1560
  getElement() {
1547
1561
  return this.element;
1548
1562
  }
1549
1563
  }
1550
- class J extends z {
1564
+ class se extends M {
1551
1565
  constructor(e) {
1552
1566
  super(), this.cards = /* @__PURE__ */ new Map(), this.isMobile = !1, this.boundEscapeHandler = (s) => {
1553
1567
  s.key === "Escape" && this.emit("dismiss", void 0);
1554
- }, this.backdrop = h("div", { class: "area-panel-backdrop" }), this.backdrop.addEventListener("click", () => {
1568
+ }, this.backdrop = u("div", { class: "area-panel-backdrop" }), this.backdrop.addEventListener("click", () => {
1555
1569
  this.emit("dismiss", void 0);
1556
- }), this.element = h("div", { class: "area-panel" });
1557
- const t = h("div", { class: "area-panel-header" });
1558
- t.appendChild(h("span", { class: "area-panel-title" }, "Customize"));
1559
- const i = h("button", { class: "area-panel-close-btn", title: "Close" }, "×");
1570
+ }), this.element = u("div", { class: "area-panel" });
1571
+ const t = u("div", { class: "area-panel-header" });
1572
+ t.appendChild(u("span", { class: "area-panel-title" }, "Customize"));
1573
+ const i = u("button", { class: "area-panel-close-btn", title: "Close" }, "×");
1560
1574
  if (i.addEventListener("click", () => {
1561
1575
  this.emit("dismiss", void 0);
1562
- }), t.appendChild(i), this.element.appendChild(t), this.panelContent = h("div", { class: "area-panel-content" }), e.length === 0) {
1563
- const s = h("p", { class: "area-panel-empty" }, "No customization areas defined.");
1576
+ }), t.appendChild(i), this.element.appendChild(t), this.panelContent = u("div", { class: "area-panel-content" }), e.length === 0) {
1577
+ const s = u("p", { class: "area-panel-empty" }, "No customization areas defined.");
1564
1578
  this.panelContent.appendChild(s);
1565
1579
  } else
1566
1580
  for (const s of e) {
1567
- const n = new L(s);
1581
+ const n = new U(s);
1568
1582
  this.cards.set(s.id, n), n.on("text:change", (o) => this.emit("text:change", o)), n.on("image:change", (o) => this.emit("image:change", o)), n.on("clear", (o) => this.emit("clear", o)), n.on("select", (o) => this.emit("select", o)), n.on("offset:change", (o) => this.emit("offset:change", o)), n.on("scale:change", (o) => this.emit("scale:change", o)), n.on("rotation:change", (o) => this.emit("rotation:change", o)), n.on("validation:error", (o) => this.emit("validation:error", o)), this.panelContent.appendChild(n.getElement());
1569
1583
  }
1570
1584
  this.element.appendChild(this.panelContent);
1571
1585
  }
1572
1586
  setAreas(e) {
1573
1587
  if (this.cards.clear(), this.panelContent.innerHTML = "", e.length === 0) {
1574
- const t = h("p", { class: "area-panel-empty" }, "No customization areas defined.");
1588
+ const t = u("p", { class: "area-panel-empty" }, "No customization areas defined.");
1575
1589
  this.panelContent.appendChild(t);
1576
1590
  } else
1577
1591
  for (const t of e) {
1578
- const i = new L(t);
1592
+ const i = new U(t);
1579
1593
  this.cards.set(t.id, i), i.on("text:change", (s) => this.emit("text:change", s)), i.on("image:change", (s) => this.emit("image:change", s)), i.on("clear", (s) => this.emit("clear", s)), i.on("select", (s) => this.emit("select", s)), i.on("offset:change", (s) => this.emit("offset:change", s)), i.on("scale:change", (s) => this.emit("scale:change", s)), i.on("rotation:change", (s) => this.emit("rotation:change", s)), i.on("validation:error", (s) => this.emit("validation:error", s)), this.panelContent.appendChild(i.getElement());
1580
1594
  }
1581
1595
  }
@@ -1604,7 +1618,7 @@ class J extends z {
1604
1618
  return this.backdrop;
1605
1619
  }
1606
1620
  }
1607
- class W {
1621
+ class ne {
1608
1622
  constructor(e = window) {
1609
1623
  this.target = e, this.commands = [], this.isEnabled = !0, this.handleKeyDown = (t) => {
1610
1624
  if (!this.isEnabled) return;
@@ -1658,31 +1672,31 @@ class W {
1658
1672
  this.target.removeEventListener("keydown", this.handleKeyDown), this.commands = [];
1659
1673
  }
1660
1674
  }
1661
- function X() {
1675
+ function ae() {
1662
1676
  return /Mac|iPod|iPhone|iPad/.test(navigator.platform);
1663
1677
  }
1664
- function G() {
1665
- return X() ? "meta" : "ctrl";
1678
+ function oe() {
1679
+ return ae() ? "meta" : "ctrl";
1666
1680
  }
1667
- function K(f, e, t, i) {
1668
- const s = f / t, n = e / i;
1681
+ function re(g, e, t, i) {
1682
+ const s = g / t, n = e / i;
1669
1683
  return Math.min(s, n);
1670
1684
  }
1671
- function Q(f, e, t = 8, i = 10, s = 300) {
1672
- const n = K(f, e, t, i), o = n >= s;
1685
+ function le(g, e, t = 8, i = 10, s = 300) {
1686
+ const n = re(g, e, t, i), o = n >= s;
1673
1687
  return {
1674
1688
  actualDPI: Math.round(n),
1675
1689
  targetDPI: s,
1676
- width: f,
1690
+ width: g,
1677
1691
  height: e,
1678
1692
  passed: o,
1679
1693
  warning: o ? void 0 : `Image resolution is ${Math.round(n)} DPI at 100% scale. For best print quality, use images with at least ${s} DPI. This may result in pixelated or blurry prints.`
1680
1694
  };
1681
1695
  }
1682
- const k = ':host{--editor-bg: #f3f4f6;--editor-surface: #ffffff;--editor-border: #e5e7eb;--editor-text: #111827;--editor-text-muted: #6b7280;--editor-primary: #3b82f6;--editor-primary-hover: #2563eb;--editor-danger: #ef4444;--editor-radius: 8px;--editor-radius-sm: 4px;--editor-font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--editor-panel-width: 280px;display:block;width:100%;height:100%;font-family:var(--editor-font);font-size:13px;color:var(--editor-text);line-height:1.4}:host([theme="dark"]){--editor-bg: #1f2937;--editor-surface: #374151;--editor-border: #4b5563;--editor-text: #f9fafb;--editor-text-muted: #9ca3af}.editor-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative;overflow:hidden;background:var(--editor-bg)}.layout-desktop .editor-main{display:flex;flex:1;min-height:0}.layout-desktop .canvas-area{flex:1;min-width:0;position:relative;display:flex;flex-direction:column}.layout-desktop .area-panel{width:var(--editor-panel-width);border-left:1px solid var(--editor-border);background:var(--editor-surface);display:none;flex-direction:column;overflow:hidden}.layout-desktop .area-panel.area-panel-visible{display:flex}.layout-mobile .editor-main{display:flex;flex-direction:column;flex:1;min-height:0}.layout-mobile .canvas-area{flex:1;position:relative;display:flex;flex-direction:column;min-height:0}.layout-mobile .area-panel{position:absolute;bottom:0;left:0;right:0;max-height:50%;background:var(--editor-surface);border-top:1px solid var(--editor-border);border-radius:var(--editor-radius) var(--editor-radius) 0 0;box-shadow:0 -4px 20px #00000026;transform:translateY(100%);transition:transform .25s ease-out;z-index:20;display:flex;flex-direction:column;overflow:hidden}.layout-mobile .area-panel.area-panel-visible{transform:translateY(0)}.area-panel-backdrop{display:none}.layout-mobile .area-panel-backdrop{display:block;position:absolute;inset:0;background:#0000004d;z-index:15;opacity:0;pointer-events:none;transition:opacity .25s ease-out}.layout-mobile .area-panel-backdrop.area-panel-backdrop-visible{opacity:1;pointer-events:auto}.zoom-toolbar{display:flex;align-items:center;gap:4px;padding:8px 12px;background:var(--editor-surface);border-bottom:1px solid var(--editor-border)}.zoom-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:14px;font-family:var(--editor-font);transition:background .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}.zoom-btn:hover{background:var(--editor-bg)}.zoom-btn:active{background:var(--editor-border)}.zoom-percent{margin-left:8px;font-size:12px;color:var(--editor-text-muted);min-width:40px}.view-select{margin-left:8px;padding:4px 8px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);font-family:var(--editor-font);font-size:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.view-select:hover{background:var(--editor-bg)}.view-select:focus{outline:2px solid var(--editor-primary);outline-offset:-1px}.zoom-toolbar-spacer{flex:1}.zoom-close-btn{width:auto;padding:0 12px;font-size:13px;gap:4px}.zoom-save-btn{width:auto;padding:0 16px;margin-left:8px;font-size:13px;font-weight:600;background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.zoom-save-btn:hover{background:var(--editor-primary-hover)}.zoom-save-btn:disabled{opacity:.5;cursor:not-allowed}.canvas-wrapper{flex:1;position:relative;overflow:hidden;min-height:200px}.editor-canvas{position:absolute;top:0;left:0}.area-panel-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--editor-border);flex-shrink:0}.area-panel-title{font-weight:600;font-size:14px}.area-panel-close-btn{width:28px;height:28px;border:none;background:none;color:var(--editor-text-muted);cursor:pointer;font-size:18px;line-height:1;border-radius:var(--editor-radius-sm);display:flex;align-items:center;justify-content:center}.area-panel-close-btn:hover{background:var(--editor-bg);color:var(--editor-text)}.area-panel-content{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:12px}.area-panel-empty{color:var(--editor-text-muted);font-size:12px;text-align:center;padding:20px 0}.area-card{padding:12px;border:1px solid var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-surface);cursor:pointer;transition:border-color .15s}.area-card:hover{border-color:var(--editor-primary)}.area-card-selected{border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.area-card-name-row{display:flex;align-items:center;gap:6px}.area-card-name{font-weight:600;font-size:13px}.area-card-badge{display:inline-block;padding:1px 6px;font-size:10px;font-weight:500;background:#fef3c7;color:#92400e;border-radius:10px}.area-card-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.area-card-reset-btn:hover{background:var(--editor-bg);color:var(--editor-danger);border-color:var(--editor-danger)}.area-card-mode-switcher{display:flex;gap:4px;margin-bottom:8px}.mode-btn{flex:1;padding:4px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-family:var(--editor-font);transition:all .15s}.mode-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-text{width:100%;padding:6px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:13px;color:var(--editor-text);background:var(--editor-surface);box-sizing:border-box;margin-bottom:6px}.area-input-text:focus{outline:none;border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-input-row{display:flex;gap:6px;align-items:center;margin-bottom:6px}.area-input-font{flex:1;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface);min-width:0}.area-input-size{width:56px;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.area-input-align{display:flex;gap:2px}.align-btn{width:28px;height:28px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-weight:600;font-family:var(--editor-font)}.align-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-color{width:32px;height:28px;padding:0;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);cursor:pointer;background:none}.area-upload-btn{width:100%;padding:10px;border:2px dashed var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-bg);color:var(--editor-text-muted);cursor:pointer;font-family:var(--editor-font);font-size:12px;text-align:center;transition:all .15s;min-height:44px}.area-upload-btn:hover{border-color:var(--editor-primary);color:var(--editor-primary)}.area-image-preview{display:flex;gap:8px;align-items:center}.area-image-thumb{width:48px;height:48px;object-fit:cover;border-radius:var(--editor-radius-sm);border:1px solid var(--editor-border)}.area-image-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.area-image-name{font-size:12px;color:var(--editor-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.area-image-remove-btn{padding:2px 8px;border:1px solid var(--editor-danger);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-danger);cursor:pointer;font-size:11px;font-family:var(--editor-font);width:fit-content}.area-image-remove-btn:hover{background:#fef2f2}.area-card-divider{height:1px;background:var(--editor-border);margin:8px 0}.transform-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.transform-title{font-weight:600;font-size:12px}.transform-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.transform-reset-btn:hover{background:var(--editor-bg)}.transform-label{font-size:11px;color:var(--editor-text-muted);white-space:nowrap}.transform-input{width:60px;padding:3px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.transform-slider-row{flex-direction:column;align-items:stretch}.transform-slider{width:100%;accent-color:var(--editor-primary)}.area-image-preview{position:relative}.area-upload-spinner-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#fff9;border-radius:var(--editor-radius-sm);z-index:1}:host([theme="dark"]) .area-upload-spinner-overlay{background:#37415199}.area-upload-spinner{width:16px;height:16px;border:2px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}.loading-container{display:flex;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px}.loading-spinner{width:32px;height:32px;border:3px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.error-container{display:flex;flex-direction:column;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px;padding:20px;text-align:center}.error-title{font-weight:600;font-size:16px;color:var(--editor-danger);margin-bottom:8px}.error-message{font-size:13px;color:var(--editor-text-muted);max-width:400px;white-space:pre-wrap}:host(:focus){outline:2px solid var(--editor-primary);outline-offset:-2px}input:focus,select:focus,button:focus-visible{outline:2px solid var(--editor-primary);outline-offset:-1px}.context-menu{position:absolute;z-index:100;min-width:160px;background:var(--editor-surface);border:1px solid var(--editor-border);border-radius:var(--editor-radius);box-shadow:0 4px 12px #00000026;padding:4px 0;font-size:13px}.context-menu-header{padding:6px 12px;font-weight:600;font-size:12px;color:var(--editor-text-muted);user-select:none}.context-menu-divider{height:1px;background:var(--editor-border);margin:4px 0}.context-menu-item{padding:6px 12px;cursor:pointer;color:var(--editor-text);user-select:none;transition:background .1s}.context-menu-item:hover{background:var(--editor-bg)}.context-menu-item:active{background:var(--editor-border)}.context-menu-item-disabled{color:var(--editor-text-muted);cursor:default;pointer-events:none}.editor-toast{position:absolute;top:16px;left:50%;transform:translate(-50%);padding:10px 20px;border-radius:var(--editor-radius);font-size:13px;font-family:var(--editor-font);line-height:1.4;z-index:200;max-width:400px;text-align:center;animation:toast-in .25s ease-out,toast-out .25s ease-in forwards;animation-delay:0s,var(--toast-duration, 3.75s);pointer-events:none;box-shadow:0 4px 12px #00000026}.editor-toast-warning{background:#fef3c7;color:#92400e;border:1px solid #f59e0b}.editor-toast-error{background:#fef2f2;color:#991b1b;border:1px solid var(--editor-danger)}.editor-toast-info{background:#eff6ff;color:#1e40af;border:1px solid var(--editor-primary)}@keyframes toast-in{0%{opacity:0;transform:translate(-50%) translateY(-10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}@keyframes toast-out{0%{opacity:1;transform:translate(-50%) translateY(0)}to{opacity:0;transform:translate(-50%) translateY(-10px)}}.editor-modal-overlay{position:absolute;inset:0;background:#0006;z-index:300;display:flex;align-items:center;justify-content:center;animation:modal-overlay-in .2s ease-out}@keyframes modal-overlay-in{0%{opacity:0}to{opacity:1}}.editor-modal{background:var(--editor-surface);border-radius:var(--editor-radius);box-shadow:0 8px 32px #0003;max-width:380px;width:calc(100% - 32px);padding:20px;animation:modal-in .2s ease-out}@keyframes modal-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.editor-modal-title{font-weight:600;font-size:15px;margin-bottom:8px;color:var(--editor-text)}.editor-modal-body{font-size:13px;color:var(--editor-text-muted);margin-bottom:16px;line-height:1.5}.editor-modal-actions{display:flex;justify-content:flex-end;gap:8px}.editor-modal-btn{padding:8px 16px;border-radius:var(--editor-radius-sm);font-size:13px;font-family:var(--editor-font);cursor:pointer;border:1px solid var(--editor-border);background:var(--editor-surface);color:var(--editor-text);transition:background .15s}.editor-modal-btn:hover{background:var(--editor-bg)}.editor-modal-btn-primary{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.editor-modal-btn-primary:hover{background:var(--editor-primary-hover)}.area-validation-error{font-size:11px;color:var(--editor-danger);margin-top:4px}@media(pointer:coarse){.zoom-toolbar{padding:6px 8px;gap:2px}.zoom-btn{min-width:44px;min-height:44px}.zoom-percent{display:none}.view-select{max-width:110px;text-overflow:ellipsis;overflow:hidden;min-height:44px;font-size:16px}.zoom-close-btn,.zoom-save-btn{padding:0 8px}.area-input-text,.area-input-font,.area-input-size{min-height:44px;font-size:16px}.align-btn{min-width:44px;min-height:44px}.area-upload-btn{min-height:48px}}', _ = "2.0.0", ee = 768;
1683
- class I extends HTMLElement {
1696
+ const A = ':host{--editor-bg: #f3f4f6;--editor-surface: #ffffff;--editor-border: #e5e7eb;--editor-text: #111827;--editor-text-muted: #6b7280;--editor-primary: #3b82f6;--editor-primary-hover: #2563eb;--editor-danger: #ef4444;--editor-radius: 8px;--editor-radius-sm: 4px;--editor-font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--editor-panel-width: 280px;display:block;width:100%;height:100%;font-family:var(--editor-font);font-size:13px;color:var(--editor-text);line-height:1.4}:host([theme="dark"]){--editor-bg: #1f2937;--editor-surface: #374151;--editor-border: #4b5563;--editor-text: #f9fafb;--editor-text-muted: #9ca3af}.editor-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative;overflow:hidden;background:var(--editor-bg)}.layout-desktop .editor-main{display:flex;flex:1;min-height:0}.layout-desktop .canvas-area{flex:1;min-width:0;position:relative;display:flex;flex-direction:column}.layout-desktop .area-panel{width:var(--editor-panel-width);border-left:1px solid var(--editor-border);background:var(--editor-surface);display:none;flex-direction:column;overflow:hidden}.layout-desktop .area-panel.area-panel-visible{display:flex}.layout-mobile .editor-main{display:flex;flex-direction:column;flex:1;min-height:0}.layout-mobile .canvas-area{flex:1;position:relative;display:flex;flex-direction:column;min-height:0}.layout-mobile .area-panel{position:absolute;bottom:0;left:0;right:0;max-height:50%;background:var(--editor-surface);border-top:1px solid var(--editor-border);border-radius:var(--editor-radius) var(--editor-radius) 0 0;box-shadow:0 -4px 20px #00000026;transform:translateY(100%);transition:transform .25s ease-out;z-index:20;display:flex;flex-direction:column;overflow:hidden}.layout-mobile .area-panel.area-panel-visible{transform:translateY(0)}.area-panel-backdrop{display:none}.layout-mobile .area-panel-backdrop{display:block;position:absolute;inset:0;background:#0000004d;z-index:15;opacity:0;pointer-events:none;transition:opacity .25s ease-out}.layout-mobile .area-panel-backdrop.area-panel-backdrop-visible{opacity:1;pointer-events:auto}.zoom-toolbar{display:flex;align-items:center;gap:4px;padding:8px 12px;background:var(--editor-surface);border-bottom:1px solid var(--editor-border)}.zoom-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:14px;font-family:var(--editor-font);transition:background .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}.zoom-btn:hover{background:var(--editor-bg)}.zoom-btn:active{background:var(--editor-border)}.zoom-percent{margin-left:8px;font-size:12px;color:var(--editor-text-muted);min-width:40px}.view-select{margin-left:8px;padding:4px 8px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);font-family:var(--editor-font);font-size:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.view-select:hover{background:var(--editor-bg)}.view-select:focus{outline:2px solid var(--editor-primary);outline-offset:-1px}.zoom-toolbar-spacer{flex:1}.zoom-close-btn{width:auto;padding:0 12px;font-size:13px;gap:4px}.zoom-save-btn{width:auto;padding:0 16px;margin-left:8px;font-size:13px;font-weight:600;background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.zoom-save-btn:hover{background:var(--editor-primary-hover)}.zoom-save-btn:disabled{opacity:.5;cursor:not-allowed}.canvas-wrapper{flex:1;position:relative;overflow:hidden;min-height:200px}.editor-canvas{position:absolute;top:0;left:0;will-change:transform}.area-panel-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--editor-border);flex-shrink:0}.area-panel-title{font-weight:600;font-size:14px}.area-panel-close-btn{width:28px;height:28px;border:none;background:none;color:var(--editor-text-muted);cursor:pointer;font-size:18px;line-height:1;border-radius:var(--editor-radius-sm);display:flex;align-items:center;justify-content:center}.area-panel-close-btn:hover{background:var(--editor-bg);color:var(--editor-text)}.area-panel-content{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:12px}.area-panel-empty{color:var(--editor-text-muted);font-size:12px;text-align:center;padding:20px 0}.area-card{padding:12px;border:1px solid var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-surface);cursor:pointer;transition:border-color .15s}.area-card:hover{border-color:var(--editor-primary)}.area-card-selected{border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.area-card-name-row{display:flex;align-items:center;gap:6px}.area-card-name{font-weight:600;font-size:13px}.area-card-badge{display:inline-block;padding:1px 6px;font-size:10px;font-weight:500;background:#fef3c7;color:#92400e;border-radius:10px}.area-card-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.area-card-reset-btn:hover{background:var(--editor-bg);color:var(--editor-danger);border-color:var(--editor-danger)}.area-card-mode-switcher{display:flex;gap:4px;margin-bottom:8px}.mode-btn{flex:1;padding:4px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-family:var(--editor-font);transition:all .15s}.mode-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-text{width:100%;padding:6px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:13px;color:var(--editor-text);background:var(--editor-surface);box-sizing:border-box;margin-bottom:6px}.area-input-text:focus{outline:none;border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-input-row{display:flex;gap:6px;align-items:center;margin-bottom:6px}.area-input-font{flex:1;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface);min-width:0}.area-input-size{width:56px;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.area-input-align{display:flex;gap:2px}.align-btn{width:28px;height:28px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-weight:600;font-family:var(--editor-font)}.align-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-color{width:32px;height:28px;padding:0;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);cursor:pointer;background:none}.area-upload-btn{width:100%;padding:10px;border:2px dashed var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-bg);color:var(--editor-text-muted);cursor:pointer;font-family:var(--editor-font);font-size:12px;text-align:center;transition:all .15s;min-height:44px}.area-upload-btn:hover{border-color:var(--editor-primary);color:var(--editor-primary)}.area-image-preview{display:flex;gap:8px;align-items:center}.area-image-thumb{width:48px;height:48px;object-fit:cover;border-radius:var(--editor-radius-sm);border:1px solid var(--editor-border)}.area-image-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.area-image-name{font-size:12px;color:var(--editor-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.area-image-remove-btn{padding:2px 8px;border:1px solid var(--editor-danger);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-danger);cursor:pointer;font-size:11px;font-family:var(--editor-font);width:fit-content}.area-image-remove-btn:hover{background:#fef2f2}.area-card-divider{height:1px;background:var(--editor-border);margin:8px 0}.transform-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.transform-title{font-weight:600;font-size:12px}.transform-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.transform-reset-btn:hover{background:var(--editor-bg)}.transform-label{font-size:11px;color:var(--editor-text-muted);white-space:nowrap}.transform-input{width:60px;padding:3px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.transform-slider-row{flex-direction:column;align-items:stretch}.transform-slider{width:100%;accent-color:var(--editor-primary)}.area-image-preview{position:relative}.area-upload-spinner-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#fff9;border-radius:var(--editor-radius-sm);z-index:1}:host([theme="dark"]) .area-upload-spinner-overlay{background:#37415199}.area-upload-spinner{width:16px;height:16px;border:2px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}.loading-container{display:flex;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px}.loading-spinner{width:32px;height:32px;border:3px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.error-container{display:flex;flex-direction:column;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px;padding:20px;text-align:center}.error-title{font-weight:600;font-size:16px;color:var(--editor-danger);margin-bottom:8px}.error-message{font-size:13px;color:var(--editor-text-muted);max-width:400px;white-space:pre-wrap}:host(:focus){outline:2px solid var(--editor-primary);outline-offset:-2px}input:focus,select:focus,button:focus-visible{outline:2px solid var(--editor-primary);outline-offset:-1px}.context-menu{position:absolute;z-index:100;min-width:160px;background:var(--editor-surface);border:1px solid var(--editor-border);border-radius:var(--editor-radius);box-shadow:0 4px 12px #00000026;padding:4px 0;font-size:13px}.context-menu-header{padding:6px 12px;font-weight:600;font-size:12px;color:var(--editor-text-muted);user-select:none}.context-menu-divider{height:1px;background:var(--editor-border);margin:4px 0}.context-menu-item{padding:6px 12px;cursor:pointer;color:var(--editor-text);user-select:none;transition:background .1s}.context-menu-item:hover{background:var(--editor-bg)}.context-menu-item:active{background:var(--editor-border)}.context-menu-item-disabled{color:var(--editor-text-muted);cursor:default;pointer-events:none}.editor-toast{position:absolute;top:16px;left:50%;transform:translate(-50%);padding:10px 20px;border-radius:var(--editor-radius);font-size:13px;font-family:var(--editor-font);line-height:1.4;z-index:200;max-width:400px;text-align:center;animation:toast-in .25s ease-out,toast-out .25s ease-in forwards;animation-delay:0s,var(--toast-duration, 3.75s);pointer-events:none;box-shadow:0 4px 12px #00000026}.editor-toast-warning{background:#fef3c7;color:#92400e;border:1px solid #f59e0b}.editor-toast-error{background:#fef2f2;color:#991b1b;border:1px solid var(--editor-danger)}.editor-toast-info{background:#eff6ff;color:#1e40af;border:1px solid var(--editor-primary)}@keyframes toast-in{0%{opacity:0;transform:translate(-50%) translateY(-10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}@keyframes toast-out{0%{opacity:1;transform:translate(-50%) translateY(0)}to{opacity:0;transform:translate(-50%) translateY(-10px)}}.editor-modal-overlay{position:absolute;inset:0;background:#0006;z-index:300;display:flex;align-items:center;justify-content:center;animation:modal-overlay-in .2s ease-out}@keyframes modal-overlay-in{0%{opacity:0}to{opacity:1}}.editor-modal{background:var(--editor-surface);border-radius:var(--editor-radius);box-shadow:0 8px 32px #0003;max-width:380px;width:calc(100% - 32px);padding:20px;animation:modal-in .2s ease-out}@keyframes modal-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.editor-modal-title{font-weight:600;font-size:15px;margin-bottom:8px;color:var(--editor-text)}.editor-modal-body{font-size:13px;color:var(--editor-text-muted);margin-bottom:16px;line-height:1.5}.editor-modal-actions{display:flex;justify-content:flex-end;gap:8px}.editor-modal-btn{padding:8px 16px;border-radius:var(--editor-radius-sm);font-size:13px;font-family:var(--editor-font);cursor:pointer;border:1px solid var(--editor-border);background:var(--editor-surface);color:var(--editor-text);transition:background .15s}.editor-modal-btn:hover{background:var(--editor-bg)}.editor-modal-btn-primary{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.editor-modal-btn-primary:hover{background:var(--editor-primary-hover)}.area-validation-error{font-size:11px;color:var(--editor-danger);margin-top:4px}@media(pointer:coarse){.zoom-toolbar{padding:6px 8px;gap:2px}.zoom-btn{min-width:44px;min-height:44px}.zoom-percent{display:none}.view-select{max-width:110px;text-overflow:ellipsis;overflow:hidden;min-height:44px;font-size:16px}.zoom-close-btn,.zoom-save-btn{padding:0 8px}.area-input-text,.area-input-font,.area-input-size{min-height:44px;font-size:16px}.align-btn{min-width:44px;min-height:44px}.area-upload-btn{min-height:48px}}', de = "2.0.0", D = 768;
1697
+ class E extends HTMLElement {
1684
1698
  constructor() {
1685
- super(), this.resizeObserver = null, this.pendingUploads = /* @__PURE__ */ new Map(), this.isReady = !1, this.currentTemplate = null, this.loadingTemplateId = null, this.isMobileLayout = !1, this.productViews = [], this.activeViewName = null, this.perViewContents = /* @__PURE__ */ new Map(), this.subscriptions = [], this.renderAbortController = null, this.contentManagerUnsub = null, this.refitRafId = null, this.storageUsage = null, this.storageUsageLastRefresh = 0, this.emitChange = q(() => {
1699
+ super(), this.resizeObserver = null, this.isScrolling = !1, this.scrollCleanup = null, this.pendingResizeRect = null, this.pendingUploads = /* @__PURE__ */ new Map(), this.isReady = !1, this.currentTemplate = null, this.loadingTemplateId = null, this.isMobileLayout = !1, this.productViews = [], this.activeViewName = null, this.perViewContents = /* @__PURE__ */ new Map(), this.subscriptions = [], this.renderAbortController = null, this.contentManagerUnsub = null, this.storageUsage = null, this.storageUsageLastRefresh = 0, this.emitChange = W(() => {
1686
1700
  this.dispatchEvent(
1687
1701
  new CustomEvent("change", {
1688
1702
  detail: { design: this.getDesign() },
@@ -1713,7 +1727,7 @@ class I extends HTMLElement {
1713
1727
  if (!this.currentTemplate) throw new Error("No design loaded");
1714
1728
  return {
1715
1729
  templateId: this.currentTemplate.metadata.id,
1716
- engineVersion: _,
1730
+ engineVersion: de,
1717
1731
  contents: this.contentManager.toJSON(),
1718
1732
  userData: {}
1719
1733
  };
@@ -1780,7 +1794,7 @@ class I extends HTMLElement {
1780
1794
  if (this.productViews.length > 1) {
1781
1795
  this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON());
1782
1796
  for (const t of this.productViews) {
1783
- const i = this.perViewContents.get(t.viewName), s = i?.some(([, o]) => I.hasEditorContent(o)) ?? !1;
1797
+ const i = this.perViewContents.get(t.viewName), s = i?.some(([, o]) => E.hasEditorContent(o)) ?? !1;
1784
1798
  if (t.isRequired && !s) {
1785
1799
  e.push(`View "${t.viewName}" is required`);
1786
1800
  continue;
@@ -1789,7 +1803,7 @@ class I extends HTMLElement {
1789
1803
  for (const o of n) {
1790
1804
  if (!o.required) continue;
1791
1805
  const a = i?.find(([r]) => r === o.id);
1792
- a && I.hasEditorContent(a[1]) || e.push(`"${o.name}" in view "${t.viewName}" is required`);
1806
+ a && E.hasEditorContent(a[1]) || e.push(`"${o.name}" in view "${t.viewName}" is required`);
1793
1807
  }
1794
1808
  }
1795
1809
  } else {
@@ -1797,27 +1811,27 @@ class I extends HTMLElement {
1797
1811
  for (const s of t) {
1798
1812
  if (!s.required) continue;
1799
1813
  const n = i.get(s.id);
1800
- n && I.hasEditorContent(n) || e.push(`"${s.name}" is required`);
1814
+ n && E.hasEditorContent(n) || e.push(`"${s.name}" is required`);
1801
1815
  }
1802
1816
  }
1803
1817
  return e;
1804
1818
  }
1805
1819
  showValidationErrors(e) {
1806
1820
  return new Promise((t) => {
1807
- const i = h("div", { class: "editor-modal-overlay" }), s = h("div", { class: "editor-modal" });
1808
- s.appendChild(h("div", { class: "editor-modal-title" }, "Required content missing"));
1809
- const n = h("div", { class: "editor-modal-body" }), o = document.createElement("ul");
1821
+ const i = u("div", { class: "editor-modal-overlay" }), s = u("div", { class: "editor-modal" });
1822
+ s.appendChild(u("div", { class: "editor-modal-title" }, "Required content missing"));
1823
+ const n = u("div", { class: "editor-modal-body" }), o = document.createElement("ul");
1810
1824
  o.style.margin = "8px 0", o.style.paddingLeft = "20px";
1811
- for (const d of e) {
1812
- const l = document.createElement("li");
1813
- l.textContent = d, o.appendChild(l);
1825
+ for (const l of e) {
1826
+ const d = document.createElement("li");
1827
+ d.textContent = l, o.appendChild(d);
1814
1828
  }
1815
1829
  n.appendChild(o), s.appendChild(n);
1816
- const a = h("div", { class: "editor-modal-actions" }), r = h("button", { class: "editor-modal-btn editor-modal-btn-primary" }, "OK");
1830
+ const a = u("div", { class: "editor-modal-actions" }), r = u("button", { class: "editor-modal-btn editor-modal-btn-primary" }, "OK");
1817
1831
  r.addEventListener("click", () => {
1818
1832
  i.remove(), t();
1819
- }), a.appendChild(r), s.appendChild(a), i.appendChild(s), i.addEventListener("click", (d) => {
1820
- d.target === i && (i.remove(), t());
1833
+ }), a.appendChild(r), s.appendChild(a), i.appendChild(s), i.addEventListener("click", (l) => {
1834
+ l.target === i && (i.remove(), t());
1821
1835
  }), this.shadow.appendChild(i);
1822
1836
  });
1823
1837
  }
@@ -1827,7 +1841,7 @@ class I extends HTMLElement {
1827
1841
  return await this.showValidationErrors(e), null;
1828
1842
  const t = this.engine.checkSafeAreaViolations();
1829
1843
  if (t.length > 0) {
1830
- const o = this.currentTemplate?.areas || [], a = t.map((r) => o.find((d) => d.id === r)?.name || r).join(", ");
1844
+ const o = this.currentTemplate?.areas || [], a = t.map((r) => o.find((l) => l.id === r)?.name || r).join(", ");
1831
1845
  if (!await this.showConfirmation(
1832
1846
  "Content outside safe area",
1833
1847
  `Content in ${a} extends beyond the safe area. It may be trimmed during printing. Continue?`,
@@ -1840,12 +1854,12 @@ class I extends HTMLElement {
1840
1854
  if (this.pendingUploads.size > 0 && await Promise.allSettled(this.pendingUploads.values()), this.productViews.length > 1) {
1841
1855
  this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON());
1842
1856
  const o = [];
1843
- for (const c of this.productViews) {
1844
- const p = this.perViewContents.get(c.viewName);
1845
- !p || p.length === 0 || !p.some(([, u]) => I.hasEditorContent(u)) || o.push({
1846
- templateId: c.template.id,
1847
- viewName: c.viewName,
1848
- contents: p
1857
+ for (const p of this.productViews) {
1858
+ const c = this.perViewContents.get(p.viewName);
1859
+ !c || c.length === 0 || !c.some(([, m]) => E.hasEditorContent(m)) || o.push({
1860
+ templateId: p.template.id,
1861
+ viewName: p.viewName,
1862
+ contents: c
1849
1863
  });
1850
1864
  }
1851
1865
  if (o.length === 0)
@@ -1853,30 +1867,38 @@ class I extends HTMLElement {
1853
1867
  const a = this.getAttribute("product-id");
1854
1868
  if (!a)
1855
1869
  throw new Error("product-id attribute is required for multi-view finalize");
1856
- const r = await this.apiClient.finalizeMultiView({
1870
+ const r = this.getAttribute("variant-id") || void 0, l = await this.apiClient.finalizeMultiView({
1857
1871
  productId: a,
1858
- designs: o
1859
- }), d = r.views.map((c) => ({
1860
- viewName: c.viewName,
1861
- designId: c.designId,
1862
- proofUrl: c.proofUrl || "",
1863
- status: c.status
1864
- })), l = d[0];
1872
+ designs: o,
1873
+ cartContext: { productId: a, variantId: r },
1874
+ customerEmail: this.getAttribute("customer-email") || void 0
1875
+ }), d = l.views.map((p) => ({
1876
+ viewName: p.viewName,
1877
+ designId: p.designId,
1878
+ proofUrl: p.proofUrl || "",
1879
+ status: p.status
1880
+ })), h = d[0];
1865
1881
  return this.dispatchEvent(
1866
1882
  new CustomEvent("customizer:finalize", {
1867
1883
  detail: {
1868
- designId: l.designId,
1869
- proofUrl: l.proofUrl,
1884
+ designId: h.designId,
1885
+ proofUrl: h.proofUrl,
1870
1886
  status: "processing",
1871
1887
  views: d,
1872
- requestId: r.requestId
1888
+ requestId: l.requestId
1873
1889
  },
1874
1890
  bubbles: !0,
1875
1891
  composed: !0
1876
1892
  })
1877
- ), { ...l, views: d };
1893
+ ), { ...h, views: d };
1878
1894
  }
1879
- const s = this.getDesign(), n = await this.apiClient.finalizeDesign(i, s);
1895
+ const s = this.getDesign(), n = await this.apiClient.finalizeDesign(i, s, {
1896
+ cartContext: {
1897
+ productId: this.getAttribute("product-id") || void 0,
1898
+ variantId: this.getAttribute("variant-id") || void 0
1899
+ },
1900
+ customerEmail: this.getAttribute("customer-email") || void 0
1901
+ });
1880
1902
  return this.dispatchEvent(
1881
1903
  new CustomEvent("customizer:finalize", {
1882
1904
  detail: {
@@ -1897,18 +1919,18 @@ class I extends HTMLElement {
1897
1919
  for (; o < s; ) {
1898
1920
  if (n?.aborted)
1899
1921
  throw new DOMException("Polling aborted", "AbortError");
1900
- await new Promise((d, l) => {
1901
- const c = setTimeout(d, i);
1922
+ await new Promise((l, d) => {
1923
+ const h = setTimeout(l, i);
1902
1924
  n?.addEventListener("abort", () => {
1903
- clearTimeout(c), l(new DOMException("Polling aborted", "AbortError"));
1925
+ clearTimeout(h), d(new DOMException("Polling aborted", "AbortError"));
1904
1926
  }, { once: !0 });
1905
1927
  }), o++;
1906
1928
  let r;
1907
1929
  if (e.length === 1) {
1908
- const d = await this.apiClient.getDesignStatus(e[0]);
1930
+ const l = await this.apiClient.getDesignStatus(e[0]);
1909
1931
  r = {
1910
- designs: [d],
1911
- allCompleted: d.status !== "processing"
1932
+ designs: [l],
1933
+ allCompleted: l.status !== "processing"
1912
1934
  };
1913
1935
  } else
1914
1936
  r = await this.apiClient.pollMultiViewStatus(e);
@@ -1941,17 +1963,17 @@ class I extends HTMLElement {
1941
1963
  // ─── Initialization ───
1942
1964
  renderLoading() {
1943
1965
  const e = document.createElement("style");
1944
- e.textContent = k, this.shadow.appendChild(e);
1945
- const t = h(
1966
+ e.textContent = A, this.shadow.appendChild(e);
1967
+ const t = u(
1946
1968
  "div",
1947
1969
  { class: "loading-container" },
1948
- h("div", { class: "loading-spinner" })
1970
+ u("div", { class: "loading-spinner" })
1949
1971
  );
1950
1972
  this.shadow.appendChild(t);
1951
1973
  }
1952
1974
  async initialize() {
1953
1975
  const e = this.getAttribute("api-url") || "http://localhost:4000";
1954
- this.apiClient = new B(`${e.replace(/\/+$/, "")}/public`);
1976
+ this.apiClient = new Y(`${e.replace(/\/+$/, "")}/public`);
1955
1977
  const t = this.getAttribute("product-id"), i = this.getAttribute("template-id");
1956
1978
  if (!t && !i)
1957
1979
  throw new Error("Either template-id or product-id attribute is required");
@@ -1982,20 +2004,24 @@ class I extends HTMLElement {
1982
2004
  isDirty: !1,
1983
2005
  warnings: []
1984
2006
  };
1985
- this.stateManager = new F(i), this.buildUI(e, t), this.engine = new N(this.canvas), this.engine.setArtboard({
2007
+ this.stateManager = new H(i), this.buildUI(e, t), this.engine = new B(this.canvas), this.engine.setArtboard({
1986
2008
  width: e.artboard.width,
1987
2009
  height: e.artboard.height,
1988
2010
  backgroundColor: e.artboard.backgroundColor ?? "#ffffff"
1989
2011
  }), this.engine.setAreas(t), this.engine.setContents(this.contentManager.getContents()), e.backgroundImage && this.engine.setBackgroundImage(e.backgroundImage);
1990
2012
  const s = e.artboard.safeArea ?? e.safeArea;
1991
- s && this.engine.setSafeArea(s), this.engine.start(), this.scheduleCanvasRefit(), this.interaction = new $(
2013
+ s && this.engine.setSafeArea(s), this.engine.start(), requestAnimationFrame(() => {
2014
+ if (!this.canvasWrapper || !this.engine) return;
2015
+ const o = this.canvasWrapper.getBoundingClientRect();
2016
+ o.width > 0 && o.height > 0 && this.handleCanvasResize(o);
2017
+ }), this.interaction = new j(
1992
2018
  this.canvas,
1993
2019
  this.engine,
1994
2020
  () => this.contentManager.getContents(),
1995
2021
  () => this.stateManager.getState().selectedAreaId
1996
2022
  ), this.wireInteractionEvents(), this.contentManagerUnsub = this.contentManager.subscribe(() => {
1997
2023
  this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents());
1998
- }), this.subscriptions.push(this.contentManagerUnsub), this.keyboard = new W(this), this.setupKeyboardShortcuts(), this.inputPanel.setContents(this.contentManager.getContents());
2024
+ }), this.subscriptions.push(this.contentManagerUnsub), this.keyboard = new ne(this), this.setupKeyboardShortcuts(), this.inputPanel.setContents(this.contentManager.getContents());
1999
2025
  const n = this.getAttribute("store-id");
2000
2026
  n && this.apiClient.getStorageUsage(n).then((o) => {
2001
2027
  this.storageUsage = o, o.usagePercent >= 100 ? this.showToast("Storage quota exceeded. Upgrade your plan for more storage.", "error") : o.usagePercent >= 80 && this.showToast(`Storage ${o.usagePercent}% full. Consider upgrading your plan.`, "warning");
@@ -2048,9 +2074,9 @@ class I extends HTMLElement {
2048
2074
  buildUI(e, t) {
2049
2075
  this.shadow.innerHTML = "";
2050
2076
  const i = document.createElement("style");
2051
- i.textContent = k, this.shadow.appendChild(i), this.container = h("div", { class: "editor-container" }), this.zoomToolbar = new H(), this.subscriptions.push(this.zoomToolbar.on("zoom-in", () => this.handleZoomIn())), this.subscriptions.push(this.zoomToolbar.on("zoom-out", () => this.handleZoomOut())), this.subscriptions.push(this.zoomToolbar.on("zoom-fit", () => this.handleZoomFit())), this.subscriptions.push(this.zoomToolbar.on("view-change", ({ viewName: o }) => {
2052
- const a = this.productViews.find((r) => r.viewName === o);
2053
- a && this.switchToView(a);
2077
+ i.textContent = A, this.shadow.appendChild(i), this.container = u("div", { class: "editor-container" }), this.zoomToolbar = new _(), this.subscriptions.push(this.zoomToolbar.on("zoom-in", () => this.handleZoomIn())), this.subscriptions.push(this.zoomToolbar.on("zoom-out", () => this.handleZoomOut())), this.subscriptions.push(this.zoomToolbar.on("zoom-fit", () => this.handleZoomFit())), this.subscriptions.push(this.zoomToolbar.on("view-change", ({ viewName: d }) => {
2078
+ const h = this.productViews.find((p) => p.viewName === d);
2079
+ h && this.switchToView(h);
2054
2080
  })), this.subscriptions.push(this.zoomToolbar.on("close", () => {
2055
2081
  this.dispatchEvent(new CustomEvent("customizer:close", {
2056
2082
  bubbles: !0,
@@ -2059,35 +2085,77 @@ class I extends HTMLElement {
2059
2085
  })), this.subscriptions.push(this.zoomToolbar.on("save", async () => {
2060
2086
  this.pendingUploads.size > 0 && (this.zoomToolbar.setSaveDisabled(!0, "Uploading..."), await Promise.allSettled(this.pendingUploads.values())), this.zoomToolbar.setSaveDisabled(!0, "Saving...");
2061
2087
  try {
2062
- const o = await this.finalize();
2063
- if (!o) {
2088
+ const d = await this.finalize();
2089
+ if (!d) {
2064
2090
  this.zoomToolbar.setSaveDisabled(!1, "✓");
2065
2091
  return;
2066
2092
  }
2093
+ const h = this.getAttribute("variant-id");
2094
+ if (h)
2095
+ try {
2096
+ const c = d.views && d.requestId || "", m = await fetch("/cart/add.js", {
2097
+ method: "POST",
2098
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
2099
+ body: JSON.stringify({
2100
+ items: [{
2101
+ id: h,
2102
+ quantity: 1,
2103
+ properties: {
2104
+ _design_id: d.designId,
2105
+ ...c && { _request_id: c },
2106
+ Customized: "Yes"
2107
+ }
2108
+ }]
2109
+ })
2110
+ });
2111
+ if (!m.ok) throw new Error("Failed to add to cart");
2112
+ const f = await m.json();
2113
+ this.dispatchEvent(new CustomEvent("customizer:cart-added", {
2114
+ detail: { designId: d.designId, cart: f },
2115
+ bubbles: !0,
2116
+ composed: !0
2117
+ }));
2118
+ } catch (c) {
2119
+ console.error("Customizer: cart add failed", c), this.showToast("Failed to add to cart. Please try again.", "error");
2120
+ }
2067
2121
  this.zoomToolbar.setSaveDisabled(!0, "Saved!"), this.showToast("Design saved successfully!", "info"), this.dispatchEvent(new CustomEvent("customizer:save", {
2068
- detail: o,
2122
+ detail: d,
2069
2123
  bubbles: !0,
2070
2124
  composed: !0
2071
2125
  }));
2072
- const a = o.views ? o.views.map((r) => r.designId) : [o.designId];
2073
- this.renderAbortController?.abort(), this.renderAbortController = new AbortController(), this.waitForResult(a, { signal: this.renderAbortController.signal }).catch((r) => {
2074
- r instanceof DOMException && r.name === "AbortError" || console.error("Customizer: background render polling failed", r);
2126
+ const p = d.views ? d.views.map((c) => c.designId) : [d.designId];
2127
+ this.renderAbortController?.abort(), this.renderAbortController = new AbortController(), this.waitForResult(p, { signal: this.renderAbortController.signal }).catch((c) => {
2128
+ c instanceof DOMException && c.name === "AbortError" || console.error("Customizer: background render polling failed", c);
2075
2129
  }), setTimeout(() => {
2076
2130
  this.zoomToolbar.setSaveDisabled(!1, "✓"), this.zoomToolbar.showSaveButton(!1), this.stateManager.update({ isDirty: !1 });
2077
2131
  }, 2e3);
2078
- } catch (o) {
2079
- console.error("Customizer: finalize failed", o);
2080
- const a = o instanceof Error ? o.message : "Save failed. Please try again.";
2081
- this.showToast(a, "error"), this.zoomToolbar.setSaveDisabled(!1, "✓");
2132
+ } catch (d) {
2133
+ console.error("Customizer: finalize failed", d);
2134
+ const h = d instanceof Error ? d.message : "Save failed. Please try again.";
2135
+ this.showToast(h, "error"), this.zoomToolbar.setSaveDisabled(!1, "✓");
2082
2136
  }
2083
2137
  })), this.getAttribute("show-close-button") === "false" && this.zoomToolbar.setCloseButtonVisible(!1), this.getAttribute("show-save-button") === "false" && this.zoomToolbar.setSaveButtonEnabled(!1);
2084
- const s = h("div", { class: "canvas-area" });
2085
- s.appendChild(this.zoomToolbar.getElement()), this.canvasWrapper = h("div", { class: "canvas-wrapper" }), this.canvas = document.createElement("canvas"), this.canvas.className = "editor-canvas", this.canvasWrapper.appendChild(this.canvas), s.appendChild(this.canvasWrapper), this.inputPanel = new J(t), this.wireInputPanelEvents();
2086
- const n = h("div", { class: "editor-main" });
2087
- n.appendChild(s), n.appendChild(this.inputPanel.getBackdrop()), n.appendChild(this.inputPanel.getElement()), this.container.appendChild(n), this.shadow.appendChild(this.container), this.resizeObserver = new ResizeObserver((o) => {
2088
- for (const a of o)
2089
- a.target === this.canvasWrapper && this.handleCanvasResize(a.contentRect), a.target === this.container && this.handleLayoutResize(a.contentRect);
2138
+ const s = u("div", { class: "canvas-area" });
2139
+ s.appendChild(this.zoomToolbar.getElement()), this.canvasWrapper = u("div", { class: "canvas-wrapper" }), this.canvas = document.createElement("canvas"), this.canvas.className = "editor-canvas", this.canvasWrapper.appendChild(this.canvas), s.appendChild(this.canvasWrapper), this.inputPanel = new se(t), this.wireInputPanelEvents();
2140
+ const n = u("div", { class: "editor-main" });
2141
+ n.appendChild(s), n.appendChild(this.inputPanel.getBackdrop()), n.appendChild(this.inputPanel.getElement()), this.container.appendChild(n), this.shadow.appendChild(this.container);
2142
+ const o = this.getBoundingClientRect().width, a = o > 0 && o < D;
2143
+ this.container.classList.add(a ? "layout-mobile" : "layout-desktop"), this.isMobileLayout = a, this.inputPanel.setMobile(a), this.resizeObserver = new ResizeObserver((d) => {
2144
+ for (const h of d)
2145
+ h.target === this.canvasWrapper && this.handleCanvasResize(h.contentRect), h.target === this.container && this.handleLayoutResize(h.contentRect);
2090
2146
  }), this.resizeObserver.observe(this.canvasWrapper), this.resizeObserver.observe(this.container);
2147
+ let r = null;
2148
+ const l = () => {
2149
+ this.isScrolling = !0, r && clearTimeout(r), r = setTimeout(() => {
2150
+ if (this.isScrolling = !1, this.pendingResizeRect) {
2151
+ const d = this.pendingResizeRect;
2152
+ this.pendingResizeRect = null, this.handleCanvasResize(d);
2153
+ }
2154
+ }, 200);
2155
+ };
2156
+ window.addEventListener("scroll", l, { passive: !0 }), this.scrollCleanup = () => {
2157
+ window.removeEventListener("scroll", l), r && clearTimeout(r);
2158
+ };
2091
2159
  }
2092
2160
  // ─── Event Wiring ───
2093
2161
  wireInteractionEvents() {
@@ -2130,14 +2198,14 @@ class I extends HTMLElement {
2130
2198
  wireInputPanelEvents() {
2131
2199
  this.subscriptions.push(
2132
2200
  this.inputPanel.on("text:change", ({ areaId: e, updates: t }) => {
2133
- this.contentManager.setTextContent(e, t), this.saveContentState();
2201
+ t.text !== void 0 && t.text.length > 1e4 && (t = { ...t, text: t.text.slice(0, 1e4) }), this.contentManager.setTextContent(e, t), this.saveContentState();
2134
2202
  })
2135
2203
  ), this.subscriptions.push(
2136
2204
  this.inputPanel.on("image:change", async ({ areaId: e, dataUrl: t, filename: i }) => {
2137
2205
  const s = this.contentManager.getContents();
2138
2206
  let n = 0;
2139
- for (const [r, d] of s)
2140
- d.type === "image" && r !== e && n++;
2207
+ for (const [r, l] of s)
2208
+ l.type === "image" && r !== e && n++;
2141
2209
  if (n >= 10) {
2142
2210
  this.showToast("Maximum of 10 images per design reached", "error");
2143
2211
  return;
@@ -2150,31 +2218,31 @@ class I extends HTMLElement {
2150
2218
  if (o && this.currentTemplate) {
2151
2219
  const r = this.currentTemplate.print.targetDpi;
2152
2220
  try {
2153
- const d = new Image();
2154
- d.crossOrigin = "anonymous";
2155
- const l = await new Promise((g, m) => {
2156
- d.onload = () => g({ width: d.naturalWidth, height: d.naturalHeight }), d.onerror = () => m(new Error("Failed to load image")), d.src = t;
2157
- }), c = o.location.width / r, p = o.location.height / r, u = Q(
2158
- l.width,
2159
- l.height,
2160
- c,
2221
+ const l = new Image();
2222
+ l.crossOrigin = "anonymous";
2223
+ const d = await new Promise((m, f) => {
2224
+ l.onload = () => m({ width: l.naturalWidth, height: l.naturalHeight }), l.onerror = () => f(new Error("Failed to load image")), l.src = t;
2225
+ }), h = o.location.width / r, p = o.location.height / r, c = le(
2226
+ d.width,
2227
+ d.height,
2228
+ h,
2161
2229
  p,
2162
2230
  r
2163
2231
  );
2164
- if (u.actualDPI < 150) {
2232
+ if (c.actualDPI < 150) {
2165
2233
  if (!await this.showConfirmation(
2166
2234
  "Low resolution detected",
2167
- `This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${r} DPI.`,
2235
+ `This image is ${c.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${r} DPI.`,
2168
2236
  "Cancel",
2169
2237
  "Use Anyway"
2170
2238
  )) return;
2171
- } else u.actualDPI < r && (this.showToast(
2172
- `Image resolution is ${u.actualDPI} DPI (${r} recommended)`,
2239
+ } else c.actualDPI < r && (this.showToast(
2240
+ `Image resolution is ${c.actualDPI} DPI (${r} recommended)`,
2173
2241
  "warning"
2174
2242
  ), this.stateManager.update({
2175
2243
  warnings: [
2176
2244
  ...this.stateManager.getState().warnings,
2177
- { type: "dpi", message: `${u.actualDPI} DPI`, areaId: e }
2245
+ { type: "dpi", message: `${c.actualDPI} DPI`, areaId: e }
2178
2246
  ]
2179
2247
  }));
2180
2248
  } catch {
@@ -2241,28 +2309,19 @@ class I extends HTMLElement {
2241
2309
  handleZoomFit() {
2242
2310
  this.engine.fitToView(), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom());
2243
2311
  }
2244
- /**
2245
- * Schedule a canvas refit after layout settles (double-rAF ensures
2246
- * ResizeObserver callbacks and layout-class changes have been applied).
2247
- */
2248
- scheduleCanvasRefit() {
2249
- this.refitRafId != null && cancelAnimationFrame(this.refitRafId), this.refitRafId = requestAnimationFrame(() => {
2250
- this.refitRafId = requestAnimationFrame(() => {
2251
- if (this.refitRafId = null, !this.canvasWrapper || !this.engine) return;
2252
- const e = this.canvasWrapper.getBoundingClientRect();
2253
- e.width > 0 && e.height > 0 && this.handleCanvasResize(e);
2254
- });
2255
- });
2256
- }
2257
2312
  handleCanvasResize(e) {
2258
2313
  const { width: t, height: i } = e;
2259
2314
  if (t === 0 || i === 0) return;
2260
- const s = window.devicePixelRatio || 1;
2261
- this.canvas.width = t * s, this.canvas.height = i * s, this.canvas.style.width = `${t}px`, this.canvas.style.height = `${i}px`, this.engine && (this.engine.fitToView(), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom()));
2315
+ if (this.isScrolling) {
2316
+ this.pendingResizeRect = e;
2317
+ return;
2318
+ }
2319
+ const s = window.devicePixelRatio || 1, n = Math.round(t * s), o = Math.round(i * s);
2320
+ this.canvas.width === n && this.canvas.height === o || (this.canvas.width = n, this.canvas.height = o, this.canvas.style.width = `${t}px`, this.canvas.style.height = `${i}px`, this.engine && (this.engine.fitToView(), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom())));
2262
2321
  }
2263
2322
  handleLayoutResize(e) {
2264
- const t = e.width < ee;
2265
- this.container.classList.toggle("layout-desktop", !t), this.container.classList.toggle("layout-mobile", t), t !== this.isMobileLayout && (this.isMobileLayout = t, this.inputPanel.setMobile(t), this.scheduleCanvasRefit());
2323
+ const t = e.width < D;
2324
+ this.container.classList.toggle("layout-desktop", !t), this.container.classList.toggle("layout-mobile", t), t !== this.isMobileLayout && (this.isMobileLayout = t, this.inputPanel.setMobile(t));
2266
2325
  }
2267
2326
  // ─── State Management ───
2268
2327
  async refreshStorageUsage() {
@@ -2286,27 +2345,33 @@ class I extends HTMLElement {
2286
2345
  }), n;
2287
2346
  }
2288
2347
  async _doPreUpload(e, t, i, s) {
2289
- const [n, o] = t.split(","), a = n.match(/:(.*?);/)?.[1] || "image/png", r = atob(o), d = new Uint8Array(r.length);
2290
- for (let g = 0; g < r.length; g++) d[g] = r.charCodeAt(g);
2291
- const l = new Blob([d], { type: a }), c = a.split("/")[1] || "png", p = new File([l], `upload.${c}`, { type: a });
2292
- let u;
2348
+ const [n, o] = t.split(","), a = n.match(/:(.*?);/)?.[1] || "image/png", r = atob(o), l = new Uint8Array(r.length);
2349
+ for (let m = 0; m < r.length; m++) l[m] = r.charCodeAt(m);
2350
+ const d = new Blob([l], { type: a }), h = a.split("/")[1] || "png", p = new File([d], `upload.${h}`, { type: a });
2351
+ let c;
2293
2352
  try {
2294
- u = await this.apiClient.uploadAsset(p, i);
2295
- } catch (g) {
2296
- console.error("Customizer: image pre-upload failed", g), this.showToast("Image upload failed. Will retry on save.", "warning");
2353
+ c = await this.apiClient.uploadAsset(p, i);
2354
+ } catch (m) {
2355
+ console.error("Customizer: image pre-upload failed", m), this.showToast("Image upload failed. Will retry on save.", "warning");
2297
2356
  return;
2298
2357
  }
2299
2358
  if (this.activeViewName === s) {
2300
- const g = this.contentManager.getContent(e);
2301
- g?.type === "image" && g.dataUrl === t && this.contentManager.setImageContent(e, u.publicUrl, g.filename, {
2302
- assetId: u.assetId,
2303
- assetUrl: u.publicUrl
2359
+ const m = this.contentManager.getContent(e);
2360
+ m?.type === "image" && m.dataUrl === t && this.contentManager.setImageContent(e, "", m.filename, {
2361
+ assetId: c.assetId,
2362
+ assetUrl: c.publicUrl
2304
2363
  });
2305
2364
  } else if (s) {
2306
- const g = this.perViewContents.get(s);
2307
- if (g) {
2308
- const m = g.map(([v, b]) => v === e && b.type === "image" && b.dataUrl === t ? [v, { ...b, dataUrl: u.publicUrl, assetId: u.assetId, assetUrl: u.publicUrl }] : [v, b]);
2309
- this.perViewContents.set(s, m);
2365
+ const m = this.perViewContents.get(s);
2366
+ if (m) {
2367
+ const f = m.map(([v, b]) => {
2368
+ if (v === e && b.type === "image" && b.dataUrl === t) {
2369
+ const { dataUrl: y, ...x } = b;
2370
+ return [v, { ...x, assetId: c.assetId, assetUrl: c.publicUrl }];
2371
+ }
2372
+ return [v, b];
2373
+ });
2374
+ this.perViewContents.set(s, f);
2310
2375
  }
2311
2376
  }
2312
2377
  }
@@ -2325,7 +2390,7 @@ class I extends HTMLElement {
2325
2390
  }
2326
2391
  // ─── Keyboard ───
2327
2392
  setupKeyboardShortcuts() {
2328
- const e = G();
2393
+ const e = oe();
2329
2394
  this.keyboard.register({
2330
2395
  key: "z",
2331
2396
  [e]: !0,
@@ -2345,22 +2410,22 @@ class I extends HTMLElement {
2345
2410
  }
2346
2411
  // ─── Toast & Modal ───
2347
2412
  showToast(e, t = "info") {
2348
- const i = h("div", { class: `editor-toast editor-toast-${t}` }, e);
2413
+ const i = u("div", { class: `editor-toast editor-toast-${t}` }, e);
2349
2414
  this.shadow.appendChild(i), setTimeout(() => i.remove(), 4e3);
2350
2415
  }
2351
2416
  showConfirmation(e, t, i = "Cancel", s = "Continue") {
2352
2417
  return new Promise((n) => {
2353
- const o = h("div", { class: "editor-modal-overlay" }), a = h("div", { class: "editor-modal" });
2354
- a.appendChild(h("div", { class: "editor-modal-title" }, e)), a.appendChild(h("div", { class: "editor-modal-body" }, t));
2355
- const r = h("div", { class: "editor-modal-actions" }), d = h("button", { class: "editor-modal-btn" }, i);
2356
- d.addEventListener("click", () => {
2418
+ const o = u("div", { class: "editor-modal-overlay" }), a = u("div", { class: "editor-modal" });
2419
+ a.appendChild(u("div", { class: "editor-modal-title" }, e)), a.appendChild(u("div", { class: "editor-modal-body" }, t));
2420
+ const r = u("div", { class: "editor-modal-actions" }), l = u("button", { class: "editor-modal-btn" }, i);
2421
+ l.addEventListener("click", () => {
2357
2422
  o.remove(), n(!1);
2358
2423
  });
2359
- const l = h("button", { class: "editor-modal-btn editor-modal-btn-primary" }, s);
2360
- l.addEventListener("click", () => {
2424
+ const d = u("button", { class: "editor-modal-btn editor-modal-btn-primary" }, s);
2425
+ d.addEventListener("click", () => {
2361
2426
  o.remove(), n(!0);
2362
- }), r.appendChild(d), r.appendChild(l), a.appendChild(r), o.appendChild(a), o.addEventListener("click", (c) => {
2363
- c.target === o && (o.remove(), n(!1));
2427
+ }), r.appendChild(l), r.appendChild(d), a.appendChild(r), o.appendChild(a), o.addEventListener("click", (h) => {
2428
+ h.target === o && (o.remove(), n(!1));
2364
2429
  }), this.shadow.appendChild(o);
2365
2430
  });
2366
2431
  }
@@ -2368,9 +2433,9 @@ class I extends HTMLElement {
2368
2433
  showError(e, t) {
2369
2434
  this.shadow.innerHTML = "";
2370
2435
  const i = document.createElement("style");
2371
- i.textContent = k, this.shadow.appendChild(i);
2372
- const s = h("div", { class: "error-container" });
2373
- s.appendChild(h("div", { class: "error-title" }, e)), s.appendChild(h("div", { class: "error-message" }, t?.message ?? "")), this.shadow.appendChild(s), this.dispatchEvent(
2436
+ i.textContent = A, this.shadow.appendChild(i);
2437
+ const s = u("div", { class: "error-container" });
2438
+ s.appendChild(u("div", { class: "error-title" }, e)), s.appendChild(u("div", { class: "error-message" }, t?.message ?? "")), this.shadow.appendChild(s), this.dispatchEvent(
2374
2439
  new CustomEvent("error", {
2375
2440
  detail: { message: e, error: t },
2376
2441
  bubbles: !0,
@@ -2382,20 +2447,67 @@ class I extends HTMLElement {
2382
2447
  this.renderAbortController?.abort(), this.renderAbortController = null;
2383
2448
  for (const e of this.subscriptions)
2384
2449
  e();
2385
- this.subscriptions = [], this.refitRafId != null && (cancelAnimationFrame(this.refitRafId), this.refitRafId = null), this.resizeObserver && (this.resizeObserver.disconnect(), this.resizeObserver = null), this.engine?.destroy(), this.interaction?.destroy(), this.keyboard?.destroy(), this.isReady = !1, this.currentTemplate = null, this.productViews = [], this.activeViewName = null, this.perViewContents.clear();
2450
+ this.subscriptions = [], this.resizeObserver && (this.resizeObserver.disconnect(), this.resizeObserver = null), this.scrollCleanup && (this.scrollCleanup(), this.scrollCleanup = null), this.engine?.destroy(), this.interaction?.destroy(), this.keyboard?.destroy(), this.isReady = !1, this.currentTemplate = null, this.productViews = [], this.activeViewName = null, this.perViewContents.clear();
2451
+ }
2452
+ }
2453
+ customElements.get("customizer-editor") || customElements.define("customizer-editor", E);
2454
+ class w extends Error {
2455
+ code;
2456
+ details;
2457
+ constructor(e, t, i) {
2458
+ super(t), Object.setPrototypeOf(this, w.prototype), this.name = "CustomizerError", this.code = e, this.details = i;
2386
2459
  }
2387
2460
  }
2388
- customElements.get("customizer-editor") || customElements.define("customizer-editor", I);
2389
- function te(f, e) {
2390
- const t = typeof f == "string" ? document.querySelector(f) : f;
2461
+ const he = /[\x00-\x08\x0B\x0C\x0E-\x1F]/g, N = 1e4;
2462
+ function O(g) {
2463
+ if (typeof g != "string" || g.trim() === "")
2464
+ throw new w("VALIDATION_ERROR", "apiUrl must be a non-empty string");
2465
+ const e = g.trim().toLowerCase();
2466
+ if (!e.startsWith("http://") && !e.startsWith("https://"))
2467
+ throw new w("VALIDATION_ERROR", "apiUrl must use http:// or https:// protocol");
2468
+ }
2469
+ function z(g, e) {
2470
+ if (typeof g != "string" || g.trim() === "")
2471
+ throw new w("VALIDATION_ERROR", `${e} must be a non-empty string`);
2472
+ }
2473
+ function ce(g) {
2474
+ if (typeof g != "string")
2475
+ throw new w("VALIDATION_ERROR", "text must be a string");
2476
+ const e = g.replace(he, "");
2477
+ if (e.length > N)
2478
+ throw new w("VALIDATION_ERROR", `text exceeds maximum length of ${N} characters`);
2479
+ return e;
2480
+ }
2481
+ function ue(g) {
2482
+ if (typeof g != "string" || g.trim() === "")
2483
+ throw new w("VALIDATION_ERROR", "image URL must be a non-empty string");
2484
+ if (!pe(g))
2485
+ throw new w("VALIDATION_ERROR", "image URL uses a disallowed protocol");
2486
+ }
2487
+ function pe(g) {
2488
+ if (typeof g != "string") return !1;
2489
+ const e = g.trim().toLowerCase();
2490
+ return !!(e.startsWith("https://") || e.startsWith("http://") || e.startsWith("blob:") || e.startsWith("data:image/"));
2491
+ }
2492
+ function ge(g) {
2493
+ if (!Array.isArray(g) || g.length === 0)
2494
+ throw new w("VALIDATION_ERROR", "designIds must be a non-empty array");
2495
+ for (let e = 0; e < g.length; e++)
2496
+ if (typeof g[e] != "string" || g[e].trim() === "")
2497
+ throw new w("VALIDATION_ERROR", `designIds[${e}] must be a non-empty string`);
2498
+ }
2499
+ function me(g, e) {
2500
+ const t = typeof g == "string" ? document.querySelector(g) : g;
2391
2501
  if (!t)
2392
- throw new Error(
2393
- `Container not found: ${typeof f == "string" ? f : "provided element is null"}`
2502
+ throw new w(
2503
+ "INIT_ERROR",
2504
+ `Container not found: ${typeof g == "string" ? g : "provided element is null"}`
2394
2505
  );
2395
2506
  if (!e.templateId && !e.productId)
2396
- throw new Error("Either templateId or productId must be provided");
2507
+ throw new w("INIT_ERROR", "Either templateId or productId must be provided");
2397
2508
  if (e.templateId && e.productId)
2398
- throw new Error("Only one of templateId or productId should be provided, not both");
2509
+ throw new w("INIT_ERROR", "Only one of templateId or productId should be provided, not both");
2510
+ e.templateId && z(e.templateId, "templateId"), e.productId && z(e.productId, "productId"), e.apiUrl && O(e.apiUrl);
2399
2511
  const i = document.createElement("customizer-editor");
2400
2512
  e.productId ? i.setAttribute("product-id", e.productId) : i.setAttribute("template-id", e.templateId), e.theme && i.setAttribute("theme", e.theme), e.mode && i.setAttribute("mode", e.mode), e.className && i.classList.add(e.className), e.showCloseButton === !1 && i.setAttribute("show-close-button", "false"), e.showSaveButton === !1 && i.setAttribute("show-save-button", "false"), i.style.width = "100%", i.style.height = "100%";
2401
2513
  const s = [], n = (a, r) => {
@@ -2405,22 +2517,24 @@ function te(f, e) {
2405
2517
  const o = {
2406
2518
  getDesign() {
2407
2519
  if (typeof i.getDesign != "function")
2408
- throw new Error("Editor not ready: getDesign method not available");
2520
+ throw new w("NOT_READY", "Editor not ready: getDesign method not available");
2409
2521
  return i.getDesign();
2410
2522
  },
2411
2523
  setDesign(a) {
2524
+ if (a == null || typeof a != "object")
2525
+ throw new w("VALIDATION_ERROR", "design must be a non-null object");
2412
2526
  if (typeof i.setDesign != "function")
2413
- throw new Error("Editor not ready: setDesign method not available");
2527
+ throw new w("NOT_READY", "Editor not ready: setDesign method not available");
2414
2528
  i.setDesign(a);
2415
2529
  },
2416
2530
  undo() {
2417
2531
  if (typeof i.undo != "function")
2418
- throw new Error("Editor not ready: undo method not available");
2532
+ throw new w("NOT_READY", "Editor not ready: undo method not available");
2419
2533
  i.undo();
2420
2534
  },
2421
2535
  redo() {
2422
2536
  if (typeof i.redo != "function")
2423
- throw new Error("Editor not ready: redo method not available");
2537
+ throw new w("NOT_READY", "Editor not ready: redo method not available");
2424
2538
  i.redo();
2425
2539
  },
2426
2540
  canUndo() {
@@ -2429,116 +2543,128 @@ function te(f, e) {
2429
2543
  canRedo() {
2430
2544
  return typeof i.canRedo != "function" ? !1 : i.canRedo();
2431
2545
  },
2432
- async finalize() {
2433
- const a = e.apiUrl || "https://api.varianta.io";
2546
+ async finalize(a) {
2547
+ const r = e.apiUrl || "https://api.varianta.io";
2548
+ O(r);
2434
2549
  try {
2435
2550
  if (e.productId && typeof i.finalize == "function") {
2436
- const p = await i.finalize(), u = {
2437
- designId: p.designId,
2551
+ const c = await i.finalize(), m = {
2552
+ designId: c.designId,
2438
2553
  status: "processing",
2439
- proofUrl: p.proofUrl ?? null,
2554
+ proofUrl: c.proofUrl ?? null,
2440
2555
  errorMessage: null,
2441
- requestId: p.requestId,
2442
- views: p.views
2556
+ requestId: c.requestId,
2557
+ views: c.views
2443
2558
  };
2444
- return e.onFinalize && e.onFinalize(u), u;
2559
+ return t.dispatchEvent(new CustomEvent("customizer:finalize", {
2560
+ bubbles: !0,
2561
+ detail: m
2562
+ })), e.onFinalize && e.onFinalize(m), m;
2445
2563
  }
2446
- const r = this.getDesign(), d = await fetch(`${a}/public/finalize`, {
2564
+ const l = this.getDesign(), d = await fetch(`${r}/public/finalize`, {
2447
2565
  method: "POST",
2448
2566
  headers: {
2449
2567
  "Content-Type": "application/json"
2450
2568
  },
2451
2569
  body: JSON.stringify({
2452
- templateId: r.templateId || e.templateId,
2453
- designJson: r
2454
- })
2570
+ templateId: l.templateId || e.templateId,
2571
+ designJson: l
2572
+ }),
2573
+ signal: a
2455
2574
  });
2456
2575
  if (!d.ok) {
2457
- const p = await d.json().catch(() => ({
2576
+ const c = await d.json().catch(() => ({
2458
2577
  message: "Finalization failed"
2459
2578
  }));
2460
- throw new Error(p.message || "Finalization failed");
2579
+ throw new w("FINALIZE_ERROR", c.message || "Finalization failed");
2461
2580
  }
2462
- const l = await d.json(), c = {
2463
- designId: l.designId,
2464
- status: l.status,
2465
- proofUrl: l.proofUrl ?? null,
2466
- errorMessage: l.errorMessage ?? null
2467
- };
2468
- return e.onFinalize && e.onFinalize(c), c;
2469
- } catch (r) {
2470
- const d = {
2471
- code: "FINALIZE_ERROR",
2472
- message: r instanceof Error ? r.message : "Unknown error",
2473
- details: r
2581
+ const h = await d.json(), p = {
2582
+ designId: h.designId,
2583
+ status: h.status,
2584
+ proofUrl: h.proofUrl ?? null,
2585
+ errorMessage: h.errorMessage ?? null
2474
2586
  };
2475
- throw e.onError && e.onError(d), r;
2587
+ return t.dispatchEvent(new CustomEvent("customizer:finalize", {
2588
+ bubbles: !0,
2589
+ detail: p
2590
+ })), e.onFinalize && e.onFinalize(p), p;
2591
+ } catch (l) {
2592
+ if (l instanceof DOMException && l.name === "AbortError") {
2593
+ const h = new w("FINALIZE_CANCELLED", "Finalization was cancelled", l);
2594
+ throw e.onError && e.onError(h), h;
2595
+ }
2596
+ const d = l instanceof w ? l : new w(
2597
+ "FINALIZE_ERROR",
2598
+ l instanceof Error ? l.message : "Unknown error",
2599
+ l
2600
+ );
2601
+ throw e.onError && e.onError(d), d;
2476
2602
  }
2477
2603
  },
2478
2604
  async waitForResult(a, r) {
2479
- if (typeof i.waitForResult == "function") {
2480
- const m = await i.waitForResult(a, r);
2481
- return e.onRenderComplete && e.onRenderComplete(m), m;
2605
+ if (ge(a), typeof i.waitForResult == "function") {
2606
+ const f = await i.waitForResult(a, r);
2607
+ return e.onRenderComplete && e.onRenderComplete(f), f;
2482
2608
  }
2483
- const d = e.apiUrl || "https://api.varianta.io", l = r?.pollInterval ?? 1500, c = r?.maxPolls ?? 40, p = r?.signal;
2484
- let u = 0;
2485
- for (; u < c; ) {
2609
+ const l = e.apiUrl || "https://api.varianta.io", d = r?.pollInterval ?? 1500, h = r?.maxPolls ?? 40, p = r?.signal;
2610
+ let c = 0;
2611
+ for (; c < h; ) {
2486
2612
  if (p?.aborted)
2487
2613
  throw new DOMException("Polling aborted", "AbortError");
2488
2614
  await new Promise((v, b) => {
2489
- const w = setTimeout(v, l);
2615
+ const y = setTimeout(v, d);
2490
2616
  p?.addEventListener("abort", () => {
2491
- clearTimeout(w), b(new DOMException("Polling aborted", "AbortError"));
2617
+ clearTimeout(y), b(new DOMException("Polling aborted", "AbortError"));
2492
2618
  }, { once: !0 });
2493
- }), u++;
2494
- let m;
2619
+ }), c++;
2620
+ let f;
2495
2621
  if (a.length === 1) {
2496
- const v = await fetch(`${d}/public/designs/${a[0]}/status`);
2497
- if (!v.ok) throw new Error("Failed to check design status");
2622
+ const v = await fetch(`${l}/public/designs/${a[0]}/status`);
2623
+ if (!v.ok) throw new w("NETWORK_ERROR", "Failed to check design status");
2498
2624
  const b = await v.json();
2499
- m = {
2625
+ f = {
2500
2626
  designs: [b],
2501
2627
  allCompleted: b.status !== "processing"
2502
2628
  };
2503
2629
  } else {
2504
2630
  const v = await fetch(
2505
- `${d}/public/finalize-status?designIds=${a.join(",")}`
2631
+ `${l}/public/finalize-status?designIds=${a.join(",")}`
2506
2632
  );
2507
- if (!v.ok) throw new Error("Failed to check design status");
2508
- m = await v.json();
2633
+ if (!v.ok) throw new w("NETWORK_ERROR", "Failed to check design status");
2634
+ f = await v.json();
2509
2635
  }
2510
- if (m.allCompleted)
2511
- return e.onRenderComplete && e.onRenderComplete(m), m;
2636
+ if (f.allCompleted)
2637
+ return e.onRenderComplete && e.onRenderComplete(f), f;
2512
2638
  }
2513
- const g = {
2514
- designs: a.map((m) => ({
2515
- designId: m,
2639
+ const m = {
2640
+ designs: a.map((f) => ({
2641
+ designId: f,
2516
2642
  status: "failed",
2517
2643
  proofUrl: null,
2518
2644
  errorMessage: "Render timed out"
2519
2645
  })),
2520
2646
  allCompleted: !0
2521
2647
  };
2522
- return e.onRenderComplete && e.onRenderComplete(g), g;
2648
+ return e.onRenderComplete && e.onRenderComplete(m), m;
2523
2649
  },
2524
2650
  addTextLayer(a) {
2525
- if (typeof i.addTextLayer != "function")
2526
- throw new Error("Editor not ready: addTextLayer method not available");
2651
+ if (a !== void 0 && (a = ce(a)), typeof i.addTextLayer != "function")
2652
+ throw new w("NOT_READY", "Editor not ready: addTextLayer method not available");
2527
2653
  i.addTextLayer(a);
2528
2654
  },
2529
2655
  async addImageLayer(a) {
2530
- if (typeof i.addImageLayer != "function")
2531
- throw new Error("Editor not ready: addImageLayer method not available");
2656
+ if (ue(a), typeof i.addImageLayer != "function")
2657
+ throw new w("NOT_READY", "Editor not ready: addImageLayer method not available");
2532
2658
  return i.addImageLayer(a);
2533
2659
  },
2534
2660
  removeLayer(a) {
2535
- if (typeof i.removeLayer != "function")
2536
- throw new Error("Editor not ready: removeLayer method not available");
2661
+ if (z(a, "layerId"), typeof i.removeLayer != "function")
2662
+ throw new w("NOT_READY", "Editor not ready: removeLayer method not available");
2537
2663
  i.removeLayer(a);
2538
2664
  },
2539
2665
  selectLayer(a) {
2540
- if (typeof i.selectLayer != "function")
2541
- throw new Error("Editor not ready: selectLayer method not available");
2666
+ if (a !== null && z(a, "layerId"), typeof i.selectLayer != "function")
2667
+ throw new w("NOT_READY", "Editor not ready: selectLayer method not available");
2542
2668
  i.selectLayer(a);
2543
2669
  },
2544
2670
  getSelectedLayerId() {
@@ -2551,8 +2677,8 @@ function te(f, e) {
2551
2677
  return typeof i.getViews != "function" ? [] : i.getViews();
2552
2678
  },
2553
2679
  setActiveView(a) {
2554
- if (typeof i.setActiveView != "function")
2555
- throw new Error("Editor not ready: setActiveView method not available");
2680
+ if (z(a, "viewName"), typeof i.setActiveView != "function")
2681
+ throw new w("NOT_READY", "Editor not ready: setActiveView method not available");
2556
2682
  i.setActiveView(a);
2557
2683
  },
2558
2684
  setTheme(a) {
@@ -2584,8 +2710,12 @@ function te(f, e) {
2584
2710
  })), e.onLayerUpdate && n("layer:update", ((a) => {
2585
2711
  e.onLayerUpdate?.(a.detail.layerId);
2586
2712
  })), e.onError && n("error", ((a) => {
2587
- const r = a.detail.error;
2588
- e.onError?.(r);
2713
+ const r = a.detail, l = r instanceof w ? r : new w(
2714
+ "UNKNOWN_ERROR",
2715
+ typeof r == "string" ? r : r?.message ?? r?.error?.message ?? "Unknown error",
2716
+ r instanceof Error ? r : r?.error
2717
+ );
2718
+ e.onError?.(l);
2589
2719
  })), e.onViewChange && n("view-change", ((a) => {
2590
2720
  e.onViewChange?.(a.detail.viewName);
2591
2721
  })), e.onRenderComplete && n("customizer:render-complete", ((a) => {
@@ -2603,6 +2733,7 @@ function te(f, e) {
2603
2733
  ), e.debug && (console.log("[Customizer SDK] Initialized with options:", e), console.log("[Customizer SDK] Instance:", o)), o;
2604
2734
  }
2605
2735
  export {
2606
- te as initCustomizer
2736
+ w as CustomizerError,
2737
+ me as initCustomizer
2607
2738
  };
2608
2739
  //# sourceMappingURL=index.esm.js.map