vibrant-tool 0.1.3 → 0.1.4

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.
@@ -8368,9 +8368,15 @@
8368
8368
  var Sh = Object.freeze({ __proto__: null, BaseFilter: Va, BlackWhite: nh, BlendColor: Ga, BlendImage: Ua, Blur: qa, Brightness: Ka, Brownie: $a$1, ColorMatrix: Qa, Composed: oh, Contrast: ah, Convolute: ch, Gamma: uh, Grayscale: gh, HueRotation: ph, Invert: mh, Kodachrome: eh, Noise: vh, Pixelate: yh, Polaroid: ih, RemoveColor: _h, Resize: xh, Saturation: Ch, Sepia: rh, Technicolor: sh, Vibrance: bh, Vintage: th });
8369
8369
  function createOverlays(canvas2, workarea) {
8370
8370
  canvas2.getObjects().filter((o2) => o2.name?.startsWith("overlay-") || o2.name?.startsWith("guideline-")).forEach((o2) => canvas2.remove(o2));
8371
- const zoom = canvas2.getZoom();
8372
- const cvW = canvas2.getWidth() / zoom;
8373
- const cvH = canvas2.getHeight() / zoom;
8371
+ const vt2 = canvas2.viewportTransform;
8372
+ const zoom = vt2 ? vt2[0] : canvas2.getZoom() || 1;
8373
+ const panX = vt2 ? vt2[4] : 0;
8374
+ const panY = vt2 ? vt2[5] : 0;
8375
+ const vX0 = -panX / zoom;
8376
+ const vY0 = -panY / zoom;
8377
+ const vX1 = (canvas2.getWidth() - panX) / zoom;
8378
+ const vY1 = (canvas2.getHeight() - panY) / zoom;
8379
+ const cvW = vX1 - vX0;
8374
8380
  const waL = workarea.left ?? 0;
8375
8381
  const waT = workarea.top ?? 0;
8376
8382
  const waW = workarea.getScaledWidth();
@@ -8384,79 +8390,81 @@
8384
8390
  });
8385
8391
  const topOverlay = makeOverlay({
8386
8392
  name: "overlay-top",
8387
- left: 0,
8388
- top: 0,
8393
+ left: vX0,
8394
+ top: vY0,
8389
8395
  width: cvW,
8390
- height: Math.max(0, waT)
8396
+ height: Math.max(0, waT - vY0)
8391
8397
  });
8392
8398
  const bottomOverlay = makeOverlay({
8393
8399
  name: "overlay-bottom",
8394
- left: 0,
8400
+ left: vX0,
8395
8401
  top: waT + waH,
8396
8402
  width: cvW,
8397
- height: Math.max(0, cvH - (waT + waH))
8403
+ height: Math.max(0, vY1 - (waT + waH))
8398
8404
  });
8399
8405
  const leftOverlay = makeOverlay({
8400
8406
  name: "overlay-left",
8401
- left: 0,
8407
+ left: vX0,
8402
8408
  top: waT,
8403
- width: Math.max(0, waL),
8409
+ width: Math.max(0, waL - vX0),
8404
8410
  height: waH
8405
8411
  });
8406
8412
  const rightOverlay = makeOverlay({
8407
8413
  name: "overlay-right",
8408
8414
  left: waL + waW,
8409
8415
  top: waT,
8410
- width: Math.max(0, cvW - (waL + waW)),
8416
+ width: Math.max(0, vX1 - (waL + waW)),
8411
8417
  height: waH
8412
8418
  });
8413
- const printDPI = workarea.__printDPI || 72;
8414
- const bleedArea = workarea.__bleedArea !== void 0 ? Number(workarea.__bleedArea) : 0.125;
8415
- const safetyArea = workarea.__safetyArea !== void 0 ? Number(workarea.__safetyArea) : 0.125;
8416
- const bleedInsetX = Math.max(4, (bleedArea > 0 ? bleedArea : 0.0125) * printDPI * 1);
8417
- const bleedInsetY = Math.max(4, (bleedArea > 0 ? bleedArea : 0.0125) * printDPI * 1);
8418
- const safeInsetX = Math.max(6, (safetyArea > 0 ? safetyArea : 0.125) * printDPI * 1);
8419
- const safeInsetY = Math.max(6, (safetyArea > 0 ? safetyArea : 0.125) * printDPI * 1);
8420
- const trimLine = new jr({
8421
- name: "guideline-trim",
8422
- left: waL + bleedInsetX,
8423
- top: waT + bleedInsetY,
8424
- width: Math.max(10, waW - bleedInsetX * 2),
8425
- height: Math.max(10, waH - bleedInsetY * 2),
8426
- fill: "transparent",
8427
- stroke: "#ef4444",
8428
- // Red dashed trim line
8429
- strokeWidth: 1.2,
8430
- strokeDashArray: [4, 4],
8431
- strokeUniform: true,
8432
- selectable: false,
8433
- evented: false,
8434
- excludeFromExport: true
8435
- });
8436
- const safeLine = new jr({
8437
- name: "guideline-safe",
8438
- left: waL + bleedInsetX + safeInsetX,
8439
- top: waT + bleedInsetY + safeInsetY,
8440
- width: Math.max(10, waW - (bleedInsetX + safeInsetX) * 2),
8441
- height: Math.max(10, waH - (bleedInsetY + safeInsetY) * 2),
8442
- fill: "transparent",
8443
- stroke: "#10b981",
8444
- // Green dashed safe line
8445
- strokeWidth: 1.2,
8446
- strokeDashArray: [4, 4],
8447
- strokeUniform: true,
8448
- selectable: false,
8449
- evented: false,
8450
- excludeFromExport: true
8451
- });
8452
- canvas2.add(trimLine, safeLine, topOverlay, bottomOverlay, leftOverlay, rightOverlay);
8419
+ const isTextTemplate = workarea.__isTextTemplate === true || workarea.__hideGuidelines === true || workarea.__bleedArea === 0 && workarea.__safetyArea === 0 || typeof window !== "undefined" && ["text", "texttemplate", "text-template"].includes(
8420
+ (new URLSearchParams(window.location.search).get("designType") || new URLSearchParams(window.location.search).get("designtype") || new URLSearchParams(window.location.search).get("type") || "")?.toLowerCase()
8421
+ );
8422
+ const objectsToAdd = [topOverlay, bottomOverlay, leftOverlay, rightOverlay];
8423
+ if (!isTextTemplate) {
8424
+ const printDPI = workarea.__printDPI || 72;
8425
+ const bleedArea = workarea.__bleedArea !== void 0 ? Number(workarea.__bleedArea) : 0.125;
8426
+ const safetyArea = workarea.__safetyArea !== void 0 ? Number(workarea.__safetyArea) : 0.125;
8427
+ const bleedInsetX = Math.max(4, (bleedArea > 0 ? bleedArea : 0.0125) * printDPI * 1);
8428
+ const bleedInsetY = Math.max(4, (bleedArea > 0 ? bleedArea : 0.0125) * printDPI * 1);
8429
+ const safeInsetX = Math.max(6, (safetyArea > 0 ? safetyArea : 0.125) * printDPI * 1);
8430
+ const safeInsetY = Math.max(6, (safetyArea > 0 ? safetyArea : 0.125) * printDPI * 1);
8431
+ const trimLine = new jr({
8432
+ name: "guideline-trim",
8433
+ left: waL + bleedInsetX,
8434
+ top: waT + bleedInsetY,
8435
+ width: Math.max(10, waW - bleedInsetX * 2),
8436
+ height: Math.max(10, waH - bleedInsetY * 2),
8437
+ fill: "transparent",
8438
+ stroke: "#ef4444",
8439
+ // Red dashed trim line
8440
+ strokeWidth: 1.2,
8441
+ strokeDashArray: [4, 4],
8442
+ strokeUniform: true,
8443
+ selectable: false,
8444
+ evented: false,
8445
+ excludeFromExport: true
8446
+ });
8447
+ const safeLine = new jr({
8448
+ name: "guideline-safe",
8449
+ left: waL + bleedInsetX + safeInsetX,
8450
+ top: waT + bleedInsetY + safeInsetY,
8451
+ width: Math.max(10, waW - (bleedInsetX + safeInsetX) * 2),
8452
+ height: Math.max(10, waH - (bleedInsetY + safeInsetY) * 2),
8453
+ fill: "transparent",
8454
+ stroke: "#10b981",
8455
+ // Green dashed safe line
8456
+ strokeWidth: 1.2,
8457
+ strokeDashArray: [4, 4],
8458
+ strokeUniform: true,
8459
+ selectable: false,
8460
+ evented: false,
8461
+ excludeFromExport: true
8462
+ });
8463
+ objectsToAdd.unshift(trimLine, safeLine);
8464
+ }
8465
+ canvas2.add(...objectsToAdd);
8453
8466
  canvas2.sendObjectToBack(workarea);
8454
- canvas2.bringObjectToFront(trimLine);
8455
- canvas2.bringObjectToFront(safeLine);
8456
- canvas2.bringObjectToFront(topOverlay);
8457
- canvas2.bringObjectToFront(bottomOverlay);
8458
- canvas2.bringObjectToFront(leftOverlay);
8459
- canvas2.bringObjectToFront(rightOverlay);
8467
+ objectsToAdd.forEach((obj) => canvas2.bringObjectToFront(obj));
8460
8468
  canvas2.requestRenderAll();
8461
8469
  }
8462
8470
  function bringOverlaysToFront(canvas2) {
@@ -98808,15 +98816,18 @@ end`);
98808
98816
  };
98809
98817
  }
98810
98818
  function cmykToRgb(c2, m2, y2, k2) {
98811
- c2 = c2 / 100;
98812
- m2 = m2 / 100;
98813
- y2 = y2 / 100;
98814
- k2 = k2 / 100;
98815
- return {
98816
- r: Math.round(255 * (1 - c2) * (1 - k2)),
98817
- g: Math.round(255 * (1 - m2) * (1 - k2)),
98818
- b: Math.round(255 * (1 - y2) * (1 - k2))
98819
- };
98819
+ const cn2 = c2 / 100;
98820
+ const mn2 = m2 / 100;
98821
+ const yn2 = y2 / 100;
98822
+ const kn2 = k2 / 100;
98823
+ const base = 1 - kn2;
98824
+ const rTrans = (1 - cn2 * 0.9 - mn2 * 0.05 - yn2 * 0.02) * base;
98825
+ const gTrans = (1 - mn2 * 0.85 - cn2 * 0.12 - yn2 * 0.03) * base;
98826
+ const bTrans = (1 - yn2 * 0.92 - mn2 * 0.22 - cn2 * 0.02) * base;
98827
+ const r2 = Math.round(Math.max(0, Math.min(255, rTrans * 255)));
98828
+ const g2 = Math.round(Math.max(0, Math.min(255, gTrans * 255)));
98829
+ const b2 = Math.round(Math.max(0, Math.min(255, bTrans * 255)));
98830
+ return { r: r2, g: g2, b: b2 };
98820
98831
  }
98821
98832
  function rgbToHex(r2, g2, b2) {
98822
98833
  const toHex = (n2) => {
@@ -98841,16 +98852,6 @@ end`);
98841
98852
  const rgb2 = cmykToRgb(c2, m2, y2, k2);
98842
98853
  return rgbToHex(rgb2.r, rgb2.g, rgb2.b);
98843
98854
  }
98844
- function isOutOfCmykGamut(hex, threshold = 8) {
98845
- const rgb2 = hexToRgb(hex);
98846
- const cmyk2 = rgbToCmyk(rgb2.r, rgb2.g, rgb2.b);
98847
- const roundTrip = cmykToRgb(cmyk2.c, cmyk2.m, cmyk2.y, cmyk2.k);
98848
- const dr2 = rgb2.r - roundTrip.r;
98849
- const dg = rgb2.g - roundTrip.g;
98850
- const db = rgb2.b - roundTrip.b;
98851
- const distance = Math.sqrt(dr2 * dr2 + dg * dg + db * db);
98852
- return distance > threshold;
98853
- }
98854
98855
  function convertSvgColorsToCmyk(svg) {
98855
98856
  if (!svg) return svg;
98856
98857
  const hexToCmykString = (hex) => {
@@ -98953,6 +98954,7 @@ end`);
98953
98954
  return pdfDoc.save({ useObjectStreams: false });
98954
98955
  }
98955
98956
  const BASE_URL = "https://www.api.vibrantprintshop.com";
98957
+ const IMAGE_UPLOAD_URL = "https://upload.vibrantprint.com";
98956
98958
  async function apiFetch(endpoint, options = {}) {
98957
98959
  const token = localStorage.getItem("token");
98958
98960
  const response = await fetch(`${BASE_URL}${endpoint}`, {
@@ -98973,6 +98975,30 @@ end`);
98973
98975
  }
98974
98976
  return response.json();
98975
98977
  }
98978
+ async function apiUploadImage(endpoint, options = {}) {
98979
+ const token = localStorage.getItem("token");
98980
+ const isFormData = options.body instanceof FormData;
98981
+ const headers = {
98982
+ ...token && { Authorization: `Bearer ${token}` },
98983
+ ...options.headers
98984
+ };
98985
+ if (!isFormData && !("Content-Type" in (options.headers || {}))) {
98986
+ headers["Content-Type"] = "application/json";
98987
+ }
98988
+ const response = await fetch(`${IMAGE_UPLOAD_URL}${endpoint}`, {
98989
+ method: options.method ?? "POST",
98990
+ headers,
98991
+ body: isFormData ? options.body : options.body ? JSON.stringify(options.body) : void 0
98992
+ });
98993
+ if (!response.ok) {
98994
+ const message = await response.text();
98995
+ throw {
98996
+ status: response.status,
98997
+ message: message || "Something went wrong"
98998
+ };
98999
+ }
99000
+ return response.json();
99001
+ }
98976
99002
  const ProductService = {
98977
99003
  async getProductOptions(id) {
98978
99004
  const res = await apiFetch(
@@ -99472,7 +99498,7 @@ end`);
99472
99498
  {
99473
99499
  value: fileType,
99474
99500
  onChange: (e2) => setFileType(e2.target.value),
99475
- className: "w-full bg-[#1e1e1e] border border-gray-600 rounded-lg px-3 py-2 text-sm mb-5 focus:outline-none focus:ring-2 focus:ring-blue-500",
99501
+ className: "w-full bg-[#1e1e1e] border border-gray-600 rounded-lg px-3 py-2 text-sm mb-5 focus:outline-none focus:ring-2 focus:ring-[#02bc71]",
99476
99502
  children: [
99477
99503
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "png", children: "PNG" }),
99478
99504
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "jpeg", children: "JPEG" }),
@@ -99856,8 +99882,13 @@ end`);
99856
99882
  const TemplateDesignService = {
99857
99883
  async getTemplates(data) {
99858
99884
  try {
99885
+ const params = new URLSearchParams();
99886
+ if (data.product) params.append("product", data.product);
99887
+ params.append("status", "true");
99888
+ params.append("page", String(data.page));
99889
+ params.append("limit", String(data.limit));
99859
99890
  const res = await apiFetch(
99860
- `/api/template-designs?product=${data.product}&status=true&page=${data.page}&limit=${data.limit}`,
99891
+ `/api/template-designs?${params.toString()}`,
99861
99892
  { method: "GET" }
99862
99893
  );
99863
99894
  return res || null;
@@ -99894,6 +99925,100 @@ end`);
99894
99925
  }
99895
99926
  }
99896
99927
  };
99928
+ const ShapeService = {
99929
+ async getAll(data) {
99930
+ const res = await apiFetch(
99931
+ `/api/cliparts?page=${data.page}&limit=${data.limit}&category=${data.category}&status=true`,
99932
+ {
99933
+ method: "GET"
99934
+ }
99935
+ );
99936
+ return {
99937
+ items: res.data || []
99938
+ };
99939
+ },
99940
+ async getAllExternal(data) {
99941
+ const res = await apiFetch(
99942
+ `/api/external/images?page=${data.page}&limit=${data.limit}&keyType=${data.keyType}&status=true${data.query ? `&query=${data.query}` : ""}`,
99943
+ {
99944
+ method: "GET"
99945
+ }
99946
+ );
99947
+ return {
99948
+ items: res.data || []
99949
+ };
99950
+ },
99951
+ async getTextTemplates(data) {
99952
+ const res = await apiFetch(
99953
+ `/api/text-templates?page=${data.page}&limit=${data.limit}&status=true`,
99954
+ {
99955
+ method: "GET"
99956
+ }
99957
+ );
99958
+ return {
99959
+ items: res.data || []
99960
+ };
99961
+ },
99962
+ async getTextTemplateById(id) {
99963
+ const res = await apiFetch(
99964
+ `/api/text-templates/${id}`,
99965
+ {
99966
+ method: "GET"
99967
+ }
99968
+ );
99969
+ return res.data || null;
99970
+ },
99971
+ async updateTextTemplate(id, body) {
99972
+ const res = await apiFetch(`/api/text-templates/${id}`, {
99973
+ method: "PUT",
99974
+ body
99975
+ });
99976
+ return res;
99977
+ },
99978
+ async convertImages(url) {
99979
+ const res = await apiFetch(`/api/convert-images`, {
99980
+ method: "POST",
99981
+ body: { file: url }
99982
+ });
99983
+ return res;
99984
+ }
99985
+ };
99986
+ function dataUrlToFile(dataUrl, filename = `design-${Date.now()}.png`) {
99987
+ const arr = dataUrl.split(",");
99988
+ const mime = arr[0].match(/:(.*?);/)?.[1] || "image/png";
99989
+ const bstr = atob(arr[1]);
99990
+ let n2 = bstr.length;
99991
+ const u8arr = new Uint8Array(n2);
99992
+ while (n2--) {
99993
+ u8arr[n2] = bstr.charCodeAt(n2);
99994
+ }
99995
+ return new File([u8arr], filename, { type: mime });
99996
+ }
99997
+ const UploadService = {
99998
+ async uploadFileApi(file, filename = `design-${Date.now()}.png`) {
99999
+ const formData = new FormData();
100000
+ formData.append("file", file, file.name || filename);
100001
+ formData.append("name", "vibrantprint");
100002
+ const res = await apiUploadImage(`/upload`, {
100003
+ method: "POST",
100004
+ body: formData
100005
+ });
100006
+ const data = res?.data || res;
100007
+ const url = data?.name || data?.Location || data?.url || data?.data?.name || data?.data?.Location || data?.data?.url || (typeof data === "string" ? data : "");
100008
+ if (!url) {
100009
+ throw new Error(res?.message || "Failed to upload file");
100010
+ }
100011
+ return url;
100012
+ },
100013
+ async uploadDataUrl(dataUrl, filename) {
100014
+ if (!dataUrl) return "";
100015
+ if (dataUrl.startsWith("http://") || dataUrl.startsWith("https://")) {
100016
+ return dataUrl;
100017
+ }
100018
+ const file = dataUrlToFile(dataUrl, filename || `design-${Date.now()}.png`);
100019
+ return await this.uploadFileApi(file, filename);
100020
+ }
100021
+ };
99897
100022
  function Topbar() {
99898
100023
  const [open2, setOpen] = React.useState(false);
99899
100024
  const [previewOpen, setPreviewOpen] = React.useState(false);
@@ -99901,8 +100026,40 @@ end`);
99901
100026
  const [saveSuccess, setSaveSuccess] = React.useState(false);
99902
100027
  const [saveError, setSaveError] = React.useState(false);
99903
100028
  const resetTour = useTourStore((s2) => s2.reset);
99904
- const templateId = getQueryParam("templateId");
99905
- const designType = getQueryParam("design");
100029
+ const templateId = getQueryParam("templateId") || getQueryParam("id");
100030
+ const designType = (getQueryParam("designType") || getQueryParam("designtype") || getQueryParam("type"))?.toLowerCase();
100031
+ const handleSaveTextTemplate = async () => {
100032
+ const canvas2 = useEditorStore.getState().canvas;
100033
+ if (!templateId || !canvas2) return;
100034
+ try {
100035
+ setIsSaving(true);
100036
+ useLoaderStore.getState().startLoading();
100037
+ const currentJson = canvas2.toObject(["name"]);
100038
+ const currentImage = getWorkareaDataUrl(canvas2);
100039
+ const uploadedImageUrl = await UploadService.uploadDataUrl(
100040
+ currentImage,
100041
+ `text-template-${templateId}-${Date.now()}.png`
100042
+ );
100043
+ const res = await ShapeService.updateTextTemplate(templateId, {
100044
+ jsonData: currentJson,
100045
+ image: uploadedImageUrl
100046
+ });
100047
+ if (res && (res.success || res.status || res.data || res._id)) {
100048
+ setSaveSuccess(true);
100049
+ setTimeout(() => setSaveSuccess(false), 3e3);
100050
+ window.location.href = "/design-configuration/text-templates";
100051
+ } else {
100052
+ throw new Error("Save failed");
100053
+ }
100054
+ } catch (err) {
100055
+ console.error("Save text template error:", err);
100056
+ setSaveError(true);
100057
+ setTimeout(() => setSaveError(false), 3e3);
100058
+ } finally {
100059
+ setIsSaving(false);
100060
+ useLoaderStore.getState().stopLoading();
100061
+ }
100062
+ };
99906
100063
  const handleSaveTemplate = async () => {
99907
100064
  const canvas2 = useEditorStore.getState().canvas;
99908
100065
  if (!templateId || !canvas2) return;
@@ -99920,26 +100077,33 @@ end`);
99920
100077
  const designData = isCurrent ? currentJson : editorPages[side] || historyPages[side]?.history?.at(-1) || null;
99921
100078
  if (!designData) return null;
99922
100079
  const sideImage = isCurrent ? currentImage : await renderPageDataUrl(designData, canvas2);
100080
+ const uploadedImageUrl = await UploadService.uploadDataUrl(
100081
+ sideImage,
100082
+ `template-${templateId}-${side}-${Date.now()}.png`
100083
+ );
99923
100084
  return {
99924
100085
  side,
99925
100086
  designData,
99926
- image: sideImage,
100087
+ image: uploadedImageUrl,
99927
100088
  order: i2 + 1
99928
100089
  };
99929
100090
  })
99930
100091
  );
99931
100092
  const validPages = pages.filter(Boolean);
99932
- const mainImage = validPages[0]?.image || currentImage;
100093
+ const primaryImage = validPages[0]?.image || "";
99933
100094
  const res = await TemplateDesignService.updateTemplateDesign(templateId, {
99934
- pages: validPages
100095
+ pages: validPages,
100096
+ image: primaryImage
99935
100097
  });
99936
100098
  if (res && res.success) {
99937
100099
  setSaveSuccess(true);
99938
100100
  setTimeout(() => setSaveSuccess(false), 3e3);
100101
+ window.location.href = "/design-configuration/template-designs";
99939
100102
  } else {
99940
100103
  throw new Error("Save failed");
99941
100104
  }
99942
- } catch {
100105
+ } catch (err) {
100106
+ console.error("Save template error:", err);
99943
100107
  setSaveError(true);
99944
100108
  setTimeout(() => setSaveError(false), 3e3);
99945
100109
  } finally {
@@ -99965,17 +100129,20 @@ end`);
99965
100129
  ]
99966
100130
  }
99967
100131
  ),
99968
- templateId && designType == "template" && /* @__PURE__ */ jsxRuntime.jsx(
100132
+ templateId && (designType === "template" || designType === "text") && /* @__PURE__ */ jsxRuntime.jsx(
99969
100133
  "button",
99970
100134
  {
99971
100135
  id: "save-template-button",
99972
- onClick: handleSaveTemplate,
100136
+ onClick: designType === "text" ? handleSaveTextTemplate : handleSaveTemplate,
99973
100137
  disabled: isSaving,
99974
100138
  className: `flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg text-xs font-semibold cursor-pointer transition-all shadow-sm ${saveSuccess ? "bg-[#02bc71] text-white" : saveError ? "bg-rose-600 text-white" : "bg-[#02bc71] hover:bg-[#03945a] text-white"}`,
99975
100139
  children: isSaving ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1.5", children: [
99976
100140
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-3 w-3 border-2 border-white border-t-transparent rounded-full animate-spin" }),
99977
100141
  "Saving..."
99978
- ] }) : saveSuccess ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "✓ Saved!" }) : saveError ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Save Failed" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Save Template" })
100142
+ ] }) : saveSuccess ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "✓ Saved!" }) : saveError ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Save Failed" }) : /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
100143
+ "Save ",
100144
+ designType === "text" ? "Text Template" : "Template"
100145
+ ] })
99979
100146
  }
99980
100147
  ),
99981
100148
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center", children: [
@@ -100277,20 +100444,39 @@ end`);
100277
100444
  function deriveType(objects) {
100278
100445
  if (objects.length === 0) return "none";
100279
100446
  if (objects.length > 1) return "multiple";
100280
- const t2 = objects[0].type;
100281
- if (t2 === "group") return "group";
100447
+ const obj = objects[0];
100448
+ const t2 = obj.type?.toLowerCase();
100449
+ if (t2 === "activeselection") return "multiple";
100450
+ if (t2 === "group") {
100451
+ if (obj.name === "qr") return "qr";
100452
+ if (obj.name === "barcode") return "barcode";
100453
+ return "group";
100454
+ }
100282
100455
  if (t2 === "textbox" || t2 === "i-text" || t2 === "text") return "textbox";
100283
100456
  if (t2 === "image") return "image";
100284
- return t2;
100457
+ return t2 || "none";
100285
100458
  }
100286
100459
  const useSelectionStore = create((set) => ({
100287
100460
  type: "none",
100288
100461
  objects: [],
100462
+ activeColor: null,
100463
+ activeColorIndex: 0,
100289
100464
  setSelection: (objects) => set({
100290
100465
  objects,
100291
- type: deriveType(objects)
100466
+ type: deriveType(objects),
100467
+ activeColor: null,
100468
+ activeColorIndex: 0
100292
100469
  }),
100293
- clearSelection: () => set({ type: "none", objects: [] })
100470
+ setActiveColor: (color, index = 0) => set({
100471
+ activeColor: color,
100472
+ activeColorIndex: index
100473
+ }),
100474
+ clearSelection: () => set({
100475
+ type: "none",
100476
+ objects: [],
100477
+ activeColor: null,
100478
+ activeColorIndex: 0
100479
+ })
100294
100480
  }));
100295
100481
  function TemplatesPanel() {
100296
100482
  const { productData } = useProductStore();
@@ -100852,27 +101038,23 @@ end`);
100852
101038
  });
100853
101039
  qrGroup.canvas?.requestRenderAll();
100854
101040
  }
100855
- function extractSVGColors(svg) {
100856
- if (svg.type !== "group") return [];
100857
- const colors = /* @__PURE__ */ new Set();
100858
- svg.getObjects().forEach((obj) => {
100859
- if (typeof obj.fill === "string" && obj.fill !== "none" && obj.fill !== "" && obj.fill !== "transparent") {
100860
- try {
100861
- const hex = new Ie(obj.fill).toHex();
100862
- colors.add(`#${hex}`);
100863
- } catch (e2) {
100864
- }
100865
- }
100866
- if (typeof obj.stroke === "string" && obj.stroke !== "none" && obj.stroke !== "" && obj.stroke !== "transparent") {
100867
- try {
100868
- const hex = new Ie(obj.stroke).toHex();
100869
- colors.add(`#${hex}`);
100870
- } catch (e2) {
100871
- }
101041
+ const MAX_RECENT_ELEMENTS = 15;
101042
+ const useRecentElementsStore = create()(
101043
+ persist(
101044
+ (set) => ({
101045
+ recentItems: [],
101046
+ addRecentItem: (item) => set((state) => ({
101047
+ recentItems: [
101048
+ item,
101049
+ ...state.recentItems.filter((recent) => recent.image !== item.image)
101050
+ ].slice(0, MAX_RECENT_ELEMENTS)
101051
+ }))
101052
+ }),
101053
+ {
101054
+ name: "vibrant-recent-elements"
100872
101055
  }
100873
- });
100874
- return [...colors];
100875
- }
101056
+ )
101057
+ );
100876
101058
  function AllElements({
100877
101059
  elements: elements2,
100878
101060
  onBack,
@@ -100881,6 +101063,7 @@ end`);
100881
101063
  }) {
100882
101064
  const [search, setSearch] = React.useState("");
100883
101065
  const { isLoading } = useLoaderStore.getState();
101066
+ const { addRecentItem } = useRecentElementsStore();
100884
101067
  const clearSearch = () => {
100885
101068
  setSearch("");
100886
101069
  onSearch("");
@@ -100957,6 +101140,7 @@ end`);
100957
101140
  } else {
100958
101141
  addSVGToCanvas("svg", shape.image);
100959
101142
  }
101143
+ addRecentItem(shape);
100960
101144
  if (window.innerWidth < 768) {
100961
101145
  useSidePanelStore.getState().setActivePanel(null);
100962
101146
  }
@@ -100979,50 +101163,6 @@ end`);
100979
101163
  ] }) }) })
100980
101164
  ] });
100981
101165
  }
100982
- const ShapeService = {
100983
- async getAll(data) {
100984
- const res = await apiFetch(
100985
- `/api/cliparts?page=${data.page}&limit=${data.limit}&category=${data.category}&status=true`,
100986
- {
100987
- method: "GET"
100988
- }
100989
- );
100990
- return {
100991
- items: res.data || []
100992
- };
100993
- },
100994
- async getAllExternal(data) {
100995
- const res = await apiFetch(
100996
- `/api/external/images?page=${data.page}&limit=${data.limit}&keyType=${data.keyType}&status=true${data.query ? `&query=${data.query}` : ""}`,
100997
- {
100998
- method: "GET"
100999
- }
101000
- );
101001
- return {
101002
- items: res.data || []
101003
- };
101004
- },
101005
- async getTextTemplates(data) {
101006
- const res = await apiFetch(
101007
- `/api/text-templates?page=${data.page}&limit=${data.limit}&status=true`,
101008
- {
101009
- method: "GET"
101010
- }
101011
- );
101012
- return {
101013
- items: res.data || []
101014
- };
101015
- },
101016
- async getTextTemplateById(id) {
101017
- const res = await apiFetch(
101018
- `/api/text-templates/${id}`,
101019
- {
101020
- method: "GET"
101021
- }
101022
- );
101023
- return res.data || null;
101024
- }
101025
- };
101026
101166
  const PixabayIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
101027
101167
  "svg",
101028
101168
  {
@@ -101047,6 +101187,7 @@ end`);
101047
101187
  const FlatIcon = "data:image/webp;base64,UklGRhAEAABXRUJQVlA4TAMEAAAvX8AXEFfHoG0bQS5/0Lm7fxAQ2kiSJGX1Dn+Iy2Lv7Uo7bNtGkpi7/vv9LSaSJEXK/38MjSeQOCTsvS0kSKoJb6QA1AFJDYEC0UiSGiAkSEDSRtKmzkf5RO0kGlIhCSGQgKQ7JdAOkIRAYlOnHSCknWCnph0CJe7UEE0CdTTdAdKd7kBGUthgjsE/hLQhDv1HAxJDG/9TQwASAp7T5wcItCEElDYEbPqbMhKAb28hwBP8OpTAT6BIQM5ylM8jkUABYI4cB08Mhr2eDM4xeI5hzzFnz+kVcYhXDBFxiiIiqohoIqKLOCanjJ7er6WUGBxcAOSQj0xAASAFcPCT5cHQZn/0M5KCRAJqt22bkqRTttm2bdu2bdu6+TLqlW3bbHetiOhg27aN+zPinRe452N9iOj/BHi1WizkMDNXKL3h66xkMTdbSfNeHpPz7ypvYvZbCe9h+Ns1ecte995XML3ifda2rK9ifLVoXbFgXSFnXY4tWrt4XDPHJMXjYhfM1OV3X3n5FQ2//OpRNWMfuOrupVMC+fDs3tLU/TYB0cudRXqd+1EQ80+W5nZYFgFrDpPaU+YFMOJRafLpM4H4MUl+dETrzTqmSYM+c8APOynHzGq9FXs26ZrxwMRLRd1reettOrA5u/wCuPf7agcvaL0JFzbn6Qww5yTRrxnXeu6LQc04YhUQLRR92+8IcNwtKfa57do6LxvtgE/2S/FwHAK/7qrJs1PGpR/rgNF3in7QxwSZeTzF7l/TwJ+20Tq91h4GK47W5Ppx9U05X/RT5xBotKiHNvCfWM8kuN+7aQO+dKEw/RxNTn7w/uT77srUbDhG9OvGE+5/Q7W0R84GMk+LvvNvBDz69gb0WtcGfLZHikfikPh0r/oumAKMvVn0w1cTdPtzHerZ+l+AvwdqXZdEYbHp+HruHQnMOFP0s6YTuPure7qDPgaixV21wX8T/KSLUnV6KQLWHCb6rWPD49vt0pw2G4gfFH3PzzBw9D0p+v0PsHLnFM9kLOCjAzR5aCQw+Wzt2A2YGL2QYocfAff18KRufzobmHOSJhdPAsbeknT+FIx03/TX+qx1wFd71mzzM2ZOvESTE+YC7Ztr7hxtBz/sqMmLEbDxaJF9P8HQ+MkUe38KtK3vKc9HlrDmUE3uGA1MO+O4OZgavdJF2+ongDFjMHbmGZqcNwWL3fdDtG5/OIsYd5Mmx2wwiV930+SpjEmZJ1LsscEkVhylnTjDJvfBsKTtv3c20b54fxHpeMjnbVgdrdx84w2vbnAYHo0ZHbGF7Zx1uYJ1haJ1xap1VZ+1Let9xbaK9z5vWd5779+z7L0a/6Zdb/rkd/I25d/zKctZe7IVX2e1WMjZkSsUq14FAA==";
101048
101188
  function ElementsPanel() {
101049
101189
  const { startLoading, stopLoading, isLoading } = useLoaderStore();
101190
+ const { recentItems, addRecentItem } = useRecentElementsStore();
101050
101191
  const [elementsSVG, setElementsSVG] = React.useState([]);
101051
101192
  const [page, setPage] = React.useState(1);
101052
101193
  const [category, setCategory] = React.useState(null);
@@ -101156,45 +101297,96 @@ end`);
101156
101297
  ] })
101157
101298
  ] }),
101158
101299
  /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px]" }),
101159
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100dvh-180px)] overflow-auto", children: elements.map((item, i2) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-4", children: [
101160
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-5", children: [
101161
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
101162
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold mb-3", children: item.category }),
101163
- /* @__PURE__ */ jsxRuntime.jsx(
101164
- "div",
101300
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-[calc(100dvh-180px)] overflow-auto", children: [
101301
+ recentItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-4", children: [
101302
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-5", children: [
101303
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
101304
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold mb-3", children: "Recent items" }),
101305
+ recentItems.length > 3 && /* @__PURE__ */ jsxRuntime.jsx(
101306
+ "div",
101307
+ {
101308
+ className: "underline cursor-pointer",
101309
+ onClick: () => {
101310
+ setElementsSVG(recentItems);
101311
+ setCategory("Recent items");
101312
+ setHasMore(false);
101313
+ },
101314
+ children: "View all"
101315
+ }
101316
+ )
101317
+ ] }),
101318
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3", children: recentItems.slice(0, 3).map((shape, i2) => /* @__PURE__ */ jsxRuntime.jsx(
101319
+ "button",
101165
101320
  {
101166
- className: "underline cursor-pointer",
101167
- onClick: () => getSVG(null, item?.category, 1),
101168
- children: "View all"
101169
- }
101170
- )
101171
- ] }),
101172
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3", children: item.data.map((shape, j2) => /* @__PURE__ */ jsxRuntime.jsx(
101173
- "button",
101174
- {
101175
- onClick: () => {
101176
- addSVGToCanvas("svg", shape.image);
101321
+ onClick: () => {
101322
+ addSVGToCanvas("svg", shape.image);
101323
+ addRecentItem(shape);
101324
+ },
101325
+ className: "w-full cursor-pointer flex items-center justify-center",
101326
+ children: /* @__PURE__ */ jsxRuntime.jsx(
101327
+ "img",
101328
+ {
101329
+ src: shape.image,
101330
+ alt: shape.category,
101331
+ width: 150,
101332
+ height: 150,
101333
+ className: "w-full h-24 object-contain"
101334
+ }
101335
+ )
101177
101336
  },
101178
- className: "w-full cursor-pointer flex items-center justify-center",
101179
- children: /* @__PURE__ */ jsxRuntime.jsx(
101180
- "img",
101337
+ shape._id || shape.image || i2
101338
+ )) })
101339
+ ] }),
101340
+ /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px]" })
101341
+ ] }),
101342
+ elements.map((item, i2) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-4", children: [
101343
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-5", children: [
101344
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
101345
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold mb-3", children: item.category }),
101346
+ /* @__PURE__ */ jsxRuntime.jsx(
101347
+ "div",
101181
101348
  {
101182
- src: shape.image,
101183
- alt: item.category,
101184
- width: 150,
101185
- height: 150,
101186
- className: "w-full h-24 object-contain"
101349
+ className: "underline cursor-pointer",
101350
+ onClick: () => getSVG(null, item?.category, 1),
101351
+ children: "View all"
101187
101352
  }
101188
101353
  )
101189
- },
101190
- j2
101191
- )) })
101192
- ] }),
101193
- /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px]" })
101194
- ] }, i2)) })
101354
+ ] }),
101355
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3", children: item.data.map((shape, j2) => /* @__PURE__ */ jsxRuntime.jsx(
101356
+ "button",
101357
+ {
101358
+ onClick: () => {
101359
+ addSVGToCanvas("svg", shape.image);
101360
+ addRecentItem({
101361
+ _id: shape.image,
101362
+ status: true,
101363
+ category: item.category,
101364
+ image: shape.image,
101365
+ largeImage: shape.image,
101366
+ tags: []
101367
+ });
101368
+ },
101369
+ className: "w-full cursor-pointer flex items-center justify-center",
101370
+ children: /* @__PURE__ */ jsxRuntime.jsx(
101371
+ "img",
101372
+ {
101373
+ src: shape.image,
101374
+ alt: item.category,
101375
+ width: 150,
101376
+ height: 150,
101377
+ className: "w-full h-24 object-contain"
101378
+ }
101379
+ )
101380
+ },
101381
+ j2
101382
+ )) })
101383
+ ] }),
101384
+ /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px]" })
101385
+ ] }, i2))
101386
+ ] })
101195
101387
  ] });
101196
101388
  }
101197
- const isTextbox = (obj) => obj.type === "textbox";
101389
+ const isTextbox = (obj) => obj.type === "textbox" || obj.type === "i-text" || obj.type === "text";
101198
101390
  async function addTextToCanvas(fontSize, width, textName) {
101199
101391
  const canvas2 = useEditorStore.getState().canvas;
101200
101392
  if (!canvas2) return;
@@ -101275,13 +101467,10 @@ end`);
101275
101467
  page,
101276
101468
  limit: 30
101277
101469
  }).then((res) => {
101278
- const newItems = res.items.filter(
101279
- (x2) => x2.status && x2.image
101280
- );
101281
101470
  setTextElements(
101282
- (prev) => page === 1 ? newItems : [...prev, ...newItems]
101471
+ (prev) => page === 1 ? res.items : [...prev, ...res.items]
101283
101472
  );
101284
- if (newItems.length < 20) setHasMore(false);
101473
+ if (res?.items?.length < 20) setHasMore(false);
101285
101474
  }).finally(() => stopLoading());
101286
101475
  }, [page]);
101287
101476
  React.useEffect(() => {
@@ -101715,10 +101904,19 @@ end`);
101715
101904
  handleFacebookAuth,
101716
101905
  handleInstagramAuth
101717
101906
  } = useSocialAuth({
101718
- onImageSelect: (url) => {
101719
- addImageToCanvas(url);
101720
- if (window.innerWidth < 768) {
101721
- useSidePanelStore.getState().setActivePanel(null);
101907
+ onImageSelect: async (url) => {
101908
+ try {
101909
+ startLoading();
101910
+ const image = await ShapeService.convertImages(url);
101911
+ const imageUrl = image?.success ? image?.url : null;
101912
+ if (imageUrl) addImageToCanvas(imageUrl);
101913
+ } catch (error2) {
101914
+ console.error("Failed to convert image:", error2);
101915
+ } finally {
101916
+ stopLoading();
101917
+ if (window.innerWidth < 768) {
101918
+ useSidePanelStore.getState().setActivePanel(null);
101919
+ }
101722
101920
  }
101723
101921
  }
101724
101922
  });
@@ -101843,10 +102041,20 @@ end`);
101843
102041
  "button",
101844
102042
  {
101845
102043
  className: "relative aspect-square rounded overflow-hidden group cursor-pointer",
101846
- onClick: () => {
101847
- addImageToCanvas(item.largeImage);
101848
- if (window.innerWidth < 768) {
101849
- useSidePanelStore.getState().setActivePanel(null);
102044
+ onClick: async () => {
102045
+ try {
102046
+ startLoading();
102047
+ const image = await ShapeService.convertImages(item.largeImage);
102048
+ const imageUrl = image?.success ? image?.url : null;
102049
+ if (!imageUrl) return;
102050
+ addImageToCanvas(imageUrl);
102051
+ if (window.innerWidth < 768) {
102052
+ useSidePanelStore.getState().setActivePanel(null);
102053
+ }
102054
+ } catch (error2) {
102055
+ console.error("Failed to convert image:", error2);
102056
+ } finally {
102057
+ stopLoading();
101850
102058
  }
101851
102059
  },
101852
102060
  children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -101913,319 +102121,281 @@ end`);
101913
102121
  workarea.set("fill", color);
101914
102122
  canvas2.requestRenderAll();
101915
102123
  }
102124
+ function normalizeHexColor(c2) {
102125
+ if (!c2 || typeof c2 !== "string" || c2 === "none" || c2 === "transparent" || c2.trim() === "") return null;
102126
+ const trimmed = c2.trim();
102127
+ try {
102128
+ const hex = new Ie(trimmed).toHex();
102129
+ if (hex && hex.length === 6) {
102130
+ return `#${hex.toLowerCase()}`;
102131
+ }
102132
+ } catch {
102133
+ }
102134
+ if (trimmed.startsWith("#")) {
102135
+ let raw = trimmed.replace(/^#/, "");
102136
+ if (raw.length === 3) raw = raw[0] + raw[0] + raw[1] + raw[1] + raw[2] + raw[2];
102137
+ if (raw.length === 6) return `#${raw.toLowerCase()}`;
102138
+ }
102139
+ return null;
102140
+ }
102141
+ function extractAllTargetColors(targets) {
102142
+ if (!targets || !targets.length) return [];
102143
+ const colors = [];
102144
+ const seen = /* @__PURE__ */ new Set();
102145
+ const addColor = (raw) => {
102146
+ const norm = normalizeHexColor(raw);
102147
+ if (norm && !seen.has(norm)) {
102148
+ seen.add(norm);
102149
+ colors.push(norm);
102150
+ }
102151
+ };
102152
+ const traverse = (obj) => {
102153
+ if (!obj || obj.name === "workarea") return;
102154
+ if (obj.type === "image" && obj.name !== "qr" && obj.name !== "barcode" && !obj._objects) {
102155
+ return;
102156
+ }
102157
+ if (obj.type === "group" && obj.name === "qr") {
102158
+ if (obj.__qrDark) addColor(obj.__qrDark);
102159
+ if (obj.__qrLight) addColor(obj.__qrLight);
102160
+ return;
102161
+ }
102162
+ if (obj.type === "group" && obj.name === "barcode") {
102163
+ if (obj.__barcodeForeground) addColor(obj.__barcodeForeground);
102164
+ if (obj.__barcodeBackground) addColor(obj.__barcodeBackground);
102165
+ return;
102166
+ }
102167
+ if (obj.type === "group" || obj._objects) {
102168
+ const children = obj.getObjects ? obj.getObjects() : obj._objects || [];
102169
+ children.forEach(traverse);
102170
+ return;
102171
+ }
102172
+ const fill2 = obj.get?.("fill") ?? obj.fill;
102173
+ if (typeof fill2 === "string" && fill2 !== "none" && fill2 !== "transparent" && fill2 !== "") {
102174
+ addColor(fill2);
102175
+ }
102176
+ const stroke2 = obj.get?.("stroke") ?? obj.stroke;
102177
+ const strokeWidth = obj.get?.("strokeWidth") ?? obj.strokeWidth;
102178
+ if (typeof stroke2 === "string" && stroke2 !== "none" && stroke2 !== "transparent" && stroke2 !== "" && (typeof strokeWidth !== "number" || strokeWidth > 0)) {
102179
+ addColor(stroke2);
102180
+ }
102181
+ };
102182
+ targets.forEach(traverse);
102183
+ return colors;
102184
+ }
101916
102185
  function extractTargetColor(targets) {
101917
102186
  if (!targets.length) return {};
102187
+ const allColors = extractAllTargetColors(targets);
102188
+ const primaryColor = allColors[0] || void 0;
101918
102189
  const obj = targets[0];
101919
- let color;
101920
- let cmyk2 = obj.__cmyk;
101921
- if (obj.type === "group") {
101922
- if (obj.name === "qr" && obj.__qrDark) {
101923
- color = obj.__qrDark;
101924
- if (obj.__qrDarkCmyk) cmyk2 = obj.__qrDarkCmyk;
101925
- } else if (obj.name === "barcode" && obj.__barcodeForeground) {
101926
- color = obj.__barcodeForeground;
101927
- if (obj.__barcodeFgCmyk) cmyk2 = obj.__barcodeFgCmyk;
101928
- } else {
101929
- const colors = extractSVGColors(obj);
101930
- if (colors.length > 0) color = colors[0];
101931
- }
101932
- } else {
101933
- const fill2 = obj.get?.("fill") ?? obj.fill;
101934
- if (typeof fill2 === "string") color = fill2;
102190
+ let cmyk2 = obj?.__cmyk;
102191
+ if (primaryColor && !cmyk2) {
102192
+ cmyk2 = hexToCmyk(primaryColor);
101935
102193
  }
101936
- if (cmyk2 && !color) {
101937
- color = cmykToHex(cmyk2.c, cmyk2.m, cmyk2.y, cmyk2.k);
101938
- } else if (color && !cmyk2) {
101939
- cmyk2 = hexToCmyk(color);
101940
- }
101941
- const opacity = typeof obj.opacity === "number" ? Math.round(obj.opacity * 100) : void 0;
101942
- return { color, opacity, cmyk: cmyk2 };
102194
+ const opacity = typeof obj?.opacity === "number" ? Math.round(obj.opacity * 100) : void 0;
102195
+ return { color: primaryColor, opacity, cmyk: cmyk2 };
101943
102196
  }
101944
- function applyFillToFabricObjects(objects, newColor, alpha, currentColor, canvas2, cmykValues) {
102197
+ function applyFillToFabricObjects(objects, newColor, alpha, targetOldColor, canvas2, cmykValues) {
101945
102198
  if (!canvas2 || !objects.length) return;
101946
- const resolvedCmyk = cmykValues || hexToCmyk(newColor);
102199
+ const targetNewHex = normalizeHexColor(newColor) || newColor.toLowerCase();
102200
+ const targetOldHex = targetOldColor ? normalizeHexColor(targetOldColor) : null;
102201
+ const resolvedCmyk = cmykValues || hexToCmyk(targetNewHex);
101947
102202
  objects.forEach((obj) => {
101948
- obj.set("opacity", alpha / 100);
101949
- obj.set("__cmyk", { ...resolvedCmyk });
101950
- if (obj.type === "textbox" || obj.type === "i-text") {
101951
- obj.set("fill", newColor);
101952
- } else if (obj.type === "group" && obj.name === "qr") {
101953
- updateQRObjectColors(obj, newColor, obj.__qrLight || "#ffffff");
101954
- } else if (obj.type === "group" && obj.name === "barcode") {
101955
- updateBarcodeObjectColors(obj, newColor, obj.__barcodeBackground || "#ffffff");
101956
- } else if (obj.type === "group") {
101957
- const applyColorRecursive = (child) => {
101958
- child.set("fill", newColor);
101959
- if (child.type === "group" && child._objects) {
101960
- child._objects.forEach((grandchild) => applyColorRecursive(grandchild));
102203
+ if (typeof alpha === "number") {
102204
+ obj.set("opacity", alpha / 100);
102205
+ }
102206
+ if (obj.type === "group" && obj.name === "qr") {
102207
+ const dark = normalizeHexColor(obj.__qrDark) || "#000000";
102208
+ const light = normalizeHexColor(obj.__qrLight) || "#ffffff";
102209
+ let newDark = dark;
102210
+ let newLight = light;
102211
+ if (targetOldHex) {
102212
+ if (dark === targetOldHex) newDark = targetNewHex;
102213
+ if (light === targetOldHex) newLight = targetNewHex;
102214
+ } else {
102215
+ newDark = targetNewHex;
102216
+ }
102217
+ updateQRObjectColors(obj, newDark, newLight);
102218
+ return;
102219
+ }
102220
+ if (obj.type === "group" && obj.name === "barcode") {
102221
+ const fg = normalizeHexColor(obj.__barcodeForeground) || "#000000";
102222
+ const bg = normalizeHexColor(obj.__barcodeBackground) || "#ffffff";
102223
+ let newFg = fg;
102224
+ let newBg = bg;
102225
+ if (targetOldHex) {
102226
+ if (fg === targetOldHex) newFg = targetNewHex;
102227
+ if (bg === targetOldHex) newBg = targetNewHex;
102228
+ } else {
102229
+ newFg = targetNewHex;
102230
+ }
102231
+ updateBarcodeObjectColors(obj, newFg, newBg);
102232
+ return;
102233
+ }
102234
+ if (obj.type === "textbox" || obj.type === "i-text" || obj.type === "text") {
102235
+ obj.set("fill", targetNewHex);
102236
+ obj.set("__cmyk", { ...resolvedCmyk });
102237
+ obj.setCoords();
102238
+ return;
102239
+ }
102240
+ if (obj.type === "group" || obj._objects) {
102241
+ const replaceRecursive = (child) => {
102242
+ if (!child) return;
102243
+ if (typeof child.fill === "string") {
102244
+ const childFillHex = normalizeHexColor(child.fill);
102245
+ if (!targetOldHex || childFillHex === targetOldHex) {
102246
+ child.set("fill", targetNewHex);
102247
+ child.__cmyk = { ...resolvedCmyk };
102248
+ }
102249
+ }
102250
+ if (typeof child.stroke === "string") {
102251
+ const childStrokeHex = normalizeHexColor(child.stroke);
102252
+ if (targetOldHex && childStrokeHex === targetOldHex) {
102253
+ child.set("stroke", targetNewHex);
102254
+ child.__cmyk = { ...resolvedCmyk };
102255
+ }
102256
+ }
102257
+ if (child.type === "group" || child._objects) {
102258
+ const sub = child.getObjects ? child.getObjects() : child._objects || [];
102259
+ sub.forEach(replaceRecursive);
101961
102260
  }
101962
102261
  };
101963
- if (obj._objects) {
101964
- obj._objects.forEach((child) => applyColorRecursive(child));
101965
- }
102262
+ const children = obj.getObjects ? obj.getObjects() : obj._objects || [];
102263
+ children.forEach(replaceRecursive);
101966
102264
  } else {
101967
- obj.set("fill", newColor);
102265
+ const currentFillHex = normalizeHexColor(obj.fill);
102266
+ if (!targetOldHex || currentFillHex === targetOldHex) {
102267
+ obj.set("fill", targetNewHex);
102268
+ obj.set("__cmyk", { ...resolvedCmyk });
102269
+ }
102270
+ const currentStrokeHex = normalizeHexColor(obj.stroke);
102271
+ if (targetOldHex && currentStrokeHex === targetOldHex) {
102272
+ obj.set("stroke", targetNewHex);
102273
+ obj.set("__cmyk", { ...resolvedCmyk });
102274
+ }
101968
102275
  }
101969
102276
  obj.setCoords();
101970
102277
  });
101971
102278
  canvas2.requestRenderAll();
101972
102279
  }
101973
- const useDraggable = (onDrag, onEnd) => {
101974
- return (e2) => {
101975
- const startX = e2.clientX;
101976
- const startY = e2.clientY;
101977
- onDrag(startX, startY);
101978
- const handleMove = (e22) => onDrag(e22.clientX, e22.clientY);
101979
- const handleUp = () => {
101980
- document.removeEventListener("mousemove", handleMove);
101981
- document.removeEventListener("mouseup", handleUp);
101982
- onEnd?.();
101983
- };
101984
- document.addEventListener("mousemove", handleMove);
101985
- document.addEventListener("mouseup", handleUp);
101986
- };
101987
- };
101988
- function GradientSelector({
101989
- hsv,
101990
- hueColor,
101991
- refEl,
101992
- onChange
101993
- }) {
102280
+ const STEP$1 = 1;
102281
+ const STEP_LARGE = 10;
102282
+ function ChannelSlider({ value, min, max, gradient, ariaLabel, onChange, onCommit, className, thumbClassName }) {
102283
+ const trackRef = React.useRef(null);
102284
+ function pick(event) {
102285
+ const element = trackRef.current;
102286
+ if (!element) return;
102287
+ const bounds = element.getBoundingClientRect();
102288
+ const positionRatio = Math.max(0, Math.min(1, (event.clientX - bounds.left) / bounds.width));
102289
+ onChange(Math.round(min + positionRatio * (max - min)));
102290
+ }
102291
+ function nudge(event) {
102292
+ let next = value;
102293
+ switch (event.key) {
102294
+ case "ArrowRight":
102295
+ case "ArrowUp":
102296
+ next += event.shiftKey ? STEP_LARGE : STEP$1;
102297
+ break;
102298
+ case "ArrowLeft":
102299
+ case "ArrowDown":
102300
+ next -= event.shiftKey ? STEP_LARGE : STEP$1;
102301
+ break;
102302
+ case "PageUp":
102303
+ next += STEP_LARGE;
102304
+ break;
102305
+ case "PageDown":
102306
+ next -= STEP_LARGE;
102307
+ break;
102308
+ case "Home":
102309
+ next = min;
102310
+ break;
102311
+ case "End":
102312
+ next = max;
102313
+ break;
102314
+ default:
102315
+ return;
102316
+ }
102317
+ event.preventDefault();
102318
+ next = Math.max(min, Math.min(max, next));
102319
+ if (next !== value) onChange(next);
102320
+ }
102321
+ const percent = (value - min) / (max - min) * 100;
101994
102322
  return /* @__PURE__ */ jsxRuntime.jsx(
101995
102323
  "div",
101996
102324
  {
101997
- ref: refEl,
101998
- className: "w-full h-48 mb-3 rounded cursor-crosshair relative",
101999
- style: {
102000
- background: `linear-gradient(to bottom, transparent, black), linear-gradient(to right, white, ${hueColor})`
102325
+ ref: trackRef,
102326
+ className: className ? `pqc-slider ${className}` : "pqc-slider",
102327
+ style: { ["--pqc-_grad"]: gradient },
102328
+ role: "slider",
102329
+ tabIndex: 0,
102330
+ "aria-label": ariaLabel,
102331
+ "aria-valuemin": min,
102332
+ "aria-valuemax": max,
102333
+ "aria-valuenow": value,
102334
+ onKeyDown: nudge,
102335
+ onPointerDown: (event) => {
102336
+ event.currentTarget.setPointerCapture(event.pointerId);
102337
+ pick(event);
102001
102338
  },
102002
- onMouseDown: useDraggable((x2, y2) => {
102003
- if (!refEl.current) return;
102004
- const rect = refEl.current.getBoundingClientRect();
102005
- const s2 = Math.max(0, Math.min(x2 - rect.left, rect.width)) / rect.width * 100;
102006
- const v2 = 100 - Math.max(0, Math.min(y2 - rect.top, rect.height)) / rect.height * 100;
102007
- onChange({ ...hsv, s: s2, v: v2 });
102008
- }),
102009
- children: /* @__PURE__ */ jsxRuntime.jsx(
102010
- "div",
102011
- {
102012
- className: "absolute w-4 h-4 border-2 border-white rounded-full shadow-lg pointer-events-none",
102013
- style: {
102014
- left: `${hsv.s}%`,
102015
- top: `${100 - hsv.v}%`,
102016
- transform: "translate(-50%, -50%)"
102017
- }
102018
- }
102019
- )
102020
- }
102021
- );
102022
- }
102023
- function HueSlider({ hsv, refEl, onChange }) {
102024
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(
102025
- "div",
102026
- {
102027
- ref: refEl,
102028
- className: "w-full h-4 rounded-full cursor-pointer relative",
102029
- style: {
102030
- background: "linear-gradient(to right, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)"
102339
+ onPointerMove: (event) => {
102340
+ if (event.buttons === 0) return;
102341
+ pick(event);
102031
102342
  },
102032
- onMouseDown: useDraggable((x2) => {
102033
- if (!refEl.current) return;
102034
- const rect = refEl.current.getBoundingClientRect();
102035
- const h2 = Math.max(0, Math.min(x2 - rect.left, rect.width)) / rect.width * 360;
102036
- onChange({ ...hsv, h: h2 });
102037
- }),
102038
- children: /* @__PURE__ */ jsxRuntime.jsx(
102039
- "div",
102040
- {
102041
- className: "absolute top-1/2 w-5 h-5 bg-white border-2 border-gray-400 rounded-full shadow-lg pointer-events-none",
102042
- style: {
102043
- left: `${hsv.h / 360 * 100}%`,
102044
- transform: "translate(-50%, -50%)"
102045
- }
102046
- }
102047
- )
102343
+ onPointerUp: () => onCommit?.(),
102344
+ onBlur: () => onCommit?.(),
102345
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: thumbClassName ? `pqc-slider-thumb ${thumbClassName}` : "pqc-slider-thumb", style: { ["--pqc-_x"]: `${percent}%` } })
102048
102346
  }
102049
- ) });
102050
- }
102051
- function AlphaSlider({
102052
- alpha,
102053
- color,
102054
- refEl,
102055
- setAlpha,
102056
- canvas: canvas2,
102057
- targets,
102058
- saveState
102059
- }) {
102060
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
102061
- "div",
102062
- {
102063
- ref: refEl,
102064
- className: "w-full h-4 rounded-full cursor-pointer relative overflow-hidden",
102065
- onMouseDown: useDraggable(
102066
- (x2) => {
102067
- if (!refEl.current) return;
102068
- const rect = refEl.current.getBoundingClientRect();
102069
- const newAlpha = Math.round(
102070
- Math.max(0, Math.min(x2 - rect.left, rect.width)) / rect.width * 100
102071
- );
102072
- setAlpha(newAlpha);
102073
- if (canvas2 && targets.length) {
102074
- targets.forEach((obj) => {
102075
- obj.set("opacity", newAlpha / 100);
102076
- obj.setCoords();
102077
- });
102078
- canvas2.requestRenderAll();
102079
- }
102080
- },
102081
- saveState
102082
- ),
102083
- children: [
102084
- /* @__PURE__ */ jsxRuntime.jsx(
102085
- "div",
102086
- {
102087
- className: "absolute inset-0",
102088
- style: {
102089
- backgroundImage: `
102090
- linear-gradient(45deg, #ccc 25%, transparent 25%),
102091
- linear-gradient(-45deg, #ccc 25%, transparent 25%),
102092
- linear-gradient(45deg, transparent 75%, #ccc 75%),
102093
- linear-gradient(-45deg, transparent 75%, #ccc 75%)
102094
- `,
102095
- backgroundSize: "8px 8px",
102096
- backgroundPosition: "0 0, 0 4px, 4px -4px, -4px 0px"
102097
- }
102098
- }
102099
- ),
102100
- /* @__PURE__ */ jsxRuntime.jsx(
102101
- "div",
102102
- {
102103
- className: "absolute inset-0",
102104
- style: {
102105
- background: `linear-gradient(to right, transparent, ${color})`
102106
- }
102107
- }
102108
- ),
102109
- /* @__PURE__ */ jsxRuntime.jsx(
102110
- "div",
102111
- {
102112
- className: "absolute top-1/2 w-5 h-5 bg-white border-2 border-gray-400 rounded-full shadow-lg pointer-events-none",
102113
- style: {
102114
- left: `${alpha}%`,
102115
- transform: "translate(-50%, -50%)"
102116
- }
102117
- }
102118
- )
102119
- ]
102120
- }
102121
- ) });
102122
- }
102123
- function CMYKInputs({
102124
- cmyk: cmyk2,
102125
- alpha,
102126
- setAlpha,
102127
- updateColor,
102128
- canvas: canvas2,
102129
- targets,
102130
- saveState
102131
- }) {
102132
- const inputClass = "w-full px-1.5 py-1.5 text-xs border border-gray-300 rounded text-center focus:outline-none focus:ring-1 focus:ring-cyan-500 focus:border-cyan-500";
102133
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3", children: [
102134
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 mb-2", children: [
102135
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-bold tracking-wider text-gray-500 uppercase", children: "CMYK" }),
102136
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-gray-400", children: "Print Values (0–100%)" })
102137
- ] }),
102138
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-5 gap-1.5", children: [
102139
- ["c", "m", "y", "k"].map((key) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
102140
- /* @__PURE__ */ jsxRuntime.jsx(
102141
- "input",
102142
- {
102143
- type: "number",
102144
- min: 0,
102145
- max: 100,
102146
- value: cmyk2[key],
102147
- onChange: (e2) => {
102148
- const val = Math.max(0, Math.min(100, +e2.target.value || 0));
102149
- updateColor({ ...cmyk2, [key]: val });
102150
- },
102151
- className: inputClass
102152
- }
102153
- ),
102154
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-[10px] text-gray-500 block mt-0.5 text-center font-semibold", children: key.toUpperCase() })
102155
- ] }, key)),
102156
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
102157
- /* @__PURE__ */ jsxRuntime.jsx(
102158
- "input",
102159
- {
102160
- type: "number",
102161
- min: 0,
102162
- max: 100,
102163
- value: alpha,
102164
- onChange: (e2) => {
102165
- const newAlpha = Math.max(0, Math.min(100, +e2.target.value));
102166
- setAlpha(newAlpha);
102167
- if (canvas2 && targets.length) {
102168
- targets.forEach((obj) => {
102169
- obj.set("opacity", newAlpha / 100);
102170
- obj.setCoords();
102171
- });
102172
- canvas2.requestRenderAll();
102173
- saveState();
102174
- }
102175
- },
102176
- className: inputClass
102177
- }
102178
- ),
102179
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-[10px] text-gray-500 block mt-0.5 text-center font-semibold", children: "A" })
102180
- ] })
102181
- ] })
102182
- ] });
102347
+ );
102183
102348
  }
102184
- function ColorPreviewBox({ color, alpha }) {
102185
- const outOfGamut = isOutOfCmykGamut(color);
102186
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-start gap-3 mb-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-12 h-12 rounded border border-gray-300 flex-shrink-0", children: [
102187
- /* @__PURE__ */ jsxRuntime.jsx(
102188
- "div",
102189
- {
102190
- className: "absolute inset-0 rounded",
102191
- style: {
102192
- backgroundImage: `
102193
- linear-gradient(45deg, #ccc 25%, transparent 25%),
102194
- linear-gradient(-45deg, #ccc 25%, transparent 25%),
102195
- linear-gradient(45deg, transparent 75%, #ccc 75%),
102196
- linear-gradient(-45deg, transparent 75%, #ccc 75%)
102197
- `,
102198
- backgroundSize: "8px 8px",
102199
- backgroundPosition: "0 0, 0 4px, 4px -4px, -4px 0px"
102200
- }
102201
- }
102202
- ),
102349
+ function ChannelRow({ label, labelColor, ariaLabel, value, min, max, gradient, onChange, onCommit, sliderClassName, sliderThumbClassName }) {
102350
+ const [rawInput, setRawInput] = React.useState(String(value));
102351
+ React.useEffect(() => {
102352
+ setRawInput(String(value));
102353
+ }, [value]);
102354
+ function commit(rawValue) {
102355
+ const parsedValue = parseInt(rawValue, 10);
102356
+ if (!isNaN(parsedValue)) onChange(Math.max(min, Math.min(max, parsedValue)));
102357
+ setRawInput(String(value));
102358
+ onCommit?.("input");
102359
+ }
102360
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pqc-channel-row", children: [
102361
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pqc-channel-label", style: { color: labelColor }, "aria-hidden": "true", children: label }),
102362
+ /* @__PURE__ */ jsxRuntime.jsx(ChannelSlider, { value, min, max, gradient, ariaLabel, onChange, onCommit: () => onCommit?.("slider"), className: sliderClassName, thumbClassName: sliderThumbClassName }),
102203
102363
  /* @__PURE__ */ jsxRuntime.jsx(
102204
- "div",
102205
- {
102206
- className: "absolute inset-0 rounded",
102207
- style: {
102208
- background: color,
102209
- opacity: alpha / 100
102210
- }
102211
- }
102212
- ),
102213
- outOfGamut && /* @__PURE__ */ jsxRuntime.jsx(
102214
- "div",
102364
+ "input",
102215
102365
  {
102216
- className: "absolute -top-1.5 -right-1.5 w-4 h-4 bg-amber-500 rounded-full flex items-center justify-center text-white text-[8px] font-bold shadow-sm cursor-help",
102217
- title: "This color is outside the CMYK gamut. It may look different when printed.",
102218
- children: "!"
102366
+ type: "text",
102367
+ inputMode: "numeric",
102368
+ "aria-label": ariaLabel,
102369
+ value: rawInput,
102370
+ onChange: (event) => {
102371
+ setRawInput(event.target.value);
102372
+ },
102373
+ onBlur: (event) => commit(event.target.value),
102374
+ onKeyDown: (event) => {
102375
+ if (event.key === "Enter") commit(event.target.value);
102376
+ },
102377
+ className: "pqc-channel-input"
102219
102378
  }
102220
102379
  )
102221
- ] }) });
102380
+ ] });
102222
102381
  }
102382
+ const DEFAULT_COLORS = ["#f97316", "#ef4444", "#22c55e", "#3b82f6", "#a855f7", "#ec4899", "#000000", "#ffffff"];
102383
+ const CMYK_CHANNELS$1 = [
102384
+ { key: "c", label: "C", color: "#009fe3", name: "Cyan" },
102385
+ { key: "m", label: "M", color: "#e6007e", name: "Magenta" },
102386
+ { key: "y", label: "Y", color: "#dcb000", name: "Yellow" },
102387
+ { key: "k", label: "K", color: "#222222", name: "Black" }
102388
+ ];
102223
102389
  function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102224
102390
  const canvas2 = useEditorStore((s2) => s2.canvas);
102225
102391
  const saveState = useHistoryStore((s2) => s2.saveState);
102226
- const { objects } = useSelectionStore();
102227
- const targets = objects.filter((o2) => o2.name !== "workarea");
102392
+ const { objects, activeColor, activeColorIndex, setActiveColor } = useSelectionStore();
102393
+ const targets = React.useMemo(() => objects.filter((o2) => o2.name !== "workarea"), [objects]);
102228
102394
  const isControlled = typeof onChange === "function";
102395
+ const allTargetColors = React.useMemo(() => {
102396
+ if (isControlled) return [];
102397
+ return extractAllTargetColors(targets);
102398
+ }, [isControlled, targets, objects]);
102229
102399
  const initialCmyk = typeof value === "object" && value !== null ? value : typeof value === "string" ? hexToCmyk(value) : { c: 0, m: 100, y: 100, k: 0 };
102230
102400
  const initialHex = typeof value === "string" ? value : cmykToHex(initialCmyk.c, initialCmyk.m, initialCmyk.y, initialCmyk.k);
102231
102401
  const [color, setColor] = React.useState(initialHex);
@@ -102235,17 +102405,32 @@ end`);
102235
102405
  return rgbToHsv(rgb2.r, rgb2.g, rgb2.b);
102236
102406
  });
102237
102407
  const [alpha, setAlpha] = React.useState(100);
102408
+ const [mode2, setMode] = React.useState("HEX");
102238
102409
  const [open2, setOpen] = React.useState(false);
102410
+ const [copied, setCopied] = React.useState(false);
102411
+ const [recentColors, setRecentColors] = React.useState([
102412
+ "#e11d48",
102413
+ "#9f1239",
102414
+ "#881337",
102415
+ "#a855f7",
102416
+ "#c084fc",
102417
+ "#475569",
102418
+ "#334155",
102419
+ "#22c55e",
102420
+ "#16a34a",
102421
+ "#6366f1",
102422
+ "#d97706"
102423
+ ]);
102239
102424
  const [coords, setCoords] = React.useState({ top: 0, left: 0 });
102240
102425
  const containerRef = React.useRef(null);
102241
102426
  const popoverRef = React.useRef(null);
102242
- const gradientRef = React.useRef(null);
102427
+ const svRef = React.useRef(null);
102243
102428
  const hueRef = React.useRef(null);
102244
102429
  const alphaRef = React.useRef(null);
102245
102430
  const updatePosition = React.useCallback(() => {
102246
102431
  if (!containerRef.current) return;
102247
102432
  const rect = containerRef.current.getBoundingClientRect();
102248
- const pickerW = 300, pickerH = 440;
102433
+ const pickerW = 270, pickerH = 430;
102249
102434
  let left = Math.max(10, Math.min(rect.left, window.innerWidth - pickerW - 10));
102250
102435
  let top = rect.bottom + 6;
102251
102436
  if (top + pickerH > window.innerHeight - 10) {
@@ -102256,29 +102441,23 @@ end`);
102256
102441
  React.useEffect(() => {
102257
102442
  if (!open2) return;
102258
102443
  updatePosition();
102444
+ const close = (e2) => {
102445
+ const t2 = e2.target;
102446
+ if (containerRef.current?.contains(t2) || popoverRef.current?.contains(t2)) return;
102447
+ setOpen(false);
102448
+ };
102259
102449
  window.addEventListener("resize", updatePosition);
102260
- window.addEventListener("scroll", updatePosition, true);
102450
+ document.addEventListener("mousedown", close, { capture: true });
102261
102451
  return () => {
102262
102452
  window.removeEventListener("resize", updatePosition);
102263
- window.removeEventListener("scroll", updatePosition, true);
102453
+ document.removeEventListener("mousedown", close, { capture: true });
102264
102454
  };
102265
102455
  }, [open2, updatePosition]);
102266
- React.useEffect(() => {
102267
- if (!open2) return;
102268
- const handleClickOutside = (e2) => {
102269
- const t2 = e2.target;
102270
- if (containerRef.current?.contains(t2) || popoverRef.current?.contains(t2)) return;
102271
- setOpen(false);
102272
- };
102273
- document.addEventListener("mousedown", handleClickOutside, { capture: true });
102274
- return () => document.removeEventListener("mousedown", handleClickOutside, { capture: true });
102275
- }, [open2]);
102276
102456
  React.useEffect(() => {
102277
102457
  if (isControlled && value) {
102278
102458
  if (typeof value === "object") {
102279
102459
  const rgb2 = cmykToRgb(value.c, value.m, value.y, value.k);
102280
- const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
102281
- setColor(hex);
102460
+ setColor(rgbToHex(rgb2.r, rgb2.g, rgb2.b));
102282
102461
  setCmyk(value);
102283
102462
  setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102284
102463
  } else if (typeof value === "string") {
@@ -102295,35 +102474,32 @@ end`);
102295
102474
  setOpen(false);
102296
102475
  return;
102297
102476
  }
102298
- const { color: targetColor, opacity: targetOpacity, cmyk: targetCmyk } = extractTargetColor(targets);
102299
- if (targetCmyk) {
102300
- setCmyk(targetCmyk);
102301
- const rgb2 = cmykToRgb(targetCmyk.c, targetCmyk.m, targetCmyk.y, targetCmyk.k);
102302
- const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
102303
- setColor(hex);
102477
+ const { color: tColor, opacity: tOpacity, cmyk: tCmyk } = extractTargetColor(targets);
102478
+ if (tCmyk) {
102479
+ setCmyk(tCmyk);
102480
+ const rgb2 = cmykToRgb(tCmyk.c, tCmyk.m, tCmyk.y, tCmyk.k);
102481
+ setColor(rgbToHex(rgb2.r, rgb2.g, rgb2.b));
102304
102482
  setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102305
- } else if (targetColor && targetColor !== color) {
102306
- const rgb2 = hexToRgb(targetColor);
102307
- setColor(targetColor);
102483
+ } else if (tColor && tColor !== color) {
102484
+ const rgb2 = hexToRgb(tColor);
102485
+ setColor(tColor);
102308
102486
  setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102309
102487
  setCmyk(rgbToCmyk(rgb2.r, rgb2.g, rgb2.b));
102310
102488
  }
102311
- if (typeof targetOpacity === "number" && targetOpacity !== alpha) {
102312
- setAlpha(targetOpacity);
102313
- }
102489
+ if (typeof tOpacity === "number" && tOpacity !== alpha) setAlpha(tOpacity);
102314
102490
  }, [isControlled, objects]);
102315
- const applyColor = React.useCallback(
102491
+ const applyControlledColor = React.useCallback(
102316
102492
  (newColor, newCmyk) => {
102317
102493
  const finalCmyk = newCmyk || cmyk2;
102318
102494
  setColor(newColor);
102319
102495
  if (isControlled && onChange) {
102320
102496
  onChange(newColor, finalCmyk);
102321
102497
  } else if (canvas2 && targets.length) {
102322
- applyFillToFabricObjects(targets, newColor, alpha, color, canvas2, finalCmyk);
102498
+ applyFillToFabricObjects(targets, newColor, alpha, activeColor || color, canvas2, finalCmyk);
102323
102499
  saveState();
102324
102500
  }
102325
102501
  },
102326
- [isControlled, onChange, canvas2, targets, alpha, color, cmyk2, saveState]
102502
+ [isControlled, onChange, canvas2, targets, alpha, color, activeColor, cmyk2, saveState]
102327
102503
  );
102328
102504
  const updateFromHsv = (newHsv) => {
102329
102505
  const rgb2 = hsvToRgb(newHsv.h, newHsv.s, newHsv.v);
@@ -102331,25 +102507,76 @@ end`);
102331
102507
  const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
102332
102508
  setHsv(newHsv);
102333
102509
  setCmyk(newC);
102334
- applyColor(hex, newC);
102510
+ applyControlledColor(hex, newC);
102335
102511
  };
102336
102512
  const updateFromCmyk = (newCmyk) => {
102337
102513
  const rgb2 = cmykToRgb(newCmyk.c, newCmyk.m, newCmyk.y, newCmyk.k);
102338
102514
  const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
102339
102515
  setCmyk(newCmyk);
102340
102516
  setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102341
- applyColor(hex, newCmyk);
102517
+ applyControlledColor(hex, newCmyk);
102518
+ };
102519
+ const updateFromHex = (inputHex) => {
102520
+ let clean = inputHex.trim().replace(/^#/, "");
102521
+ if (clean.length === 3) clean = clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2];
102522
+ if (/^[0-9a-fA-F]{6}$/.test(clean)) {
102523
+ const hex = `#${clean.toLowerCase()}`;
102524
+ const rgb2 = hexToRgb(hex);
102525
+ const newC = rgbToCmyk(rgb2.r, rgb2.g, rgb2.b);
102526
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102527
+ setCmyk(newC);
102528
+ applyControlledColor(hex, newC);
102529
+ setRecentColors((prev) => [hex, ...prev.filter((c2) => c2.toLowerCase() !== hex)].slice(0, 16));
102530
+ }
102342
102531
  };
102343
- if (!isControlled && !targets.length) return null;
102532
+ const handleCopy = () => {
102533
+ if (navigator.clipboard) {
102534
+ navigator.clipboard.writeText(color);
102535
+ setCopied(true);
102536
+ setTimeout(() => setCopied(false), 2e3);
102537
+ }
102538
+ };
102539
+ const handleEyeDropper = async () => {
102540
+ if (typeof window === "undefined" || !("EyeDropper" in window)) return;
102541
+ try {
102542
+ const result = await new window.EyeDropper().open();
102543
+ if (result?.sRGBHex) updateFromHex(result.sRGBHex);
102544
+ } catch {
102545
+ }
102546
+ };
102547
+ if (!isControlled) {
102548
+ if (!targets.length || !allTargetColors.length) return null;
102549
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex items-center gap-1.5 ${className}`, children: allTargetColors.map((c2, idx2) => {
102550
+ const isSelected = activeColor ? activeColor.toLowerCase() === c2.toLowerCase() : activeColorIndex === idx2 || idx2 === 0 && !activeColor;
102551
+ return /* @__PURE__ */ jsxRuntime.jsx(
102552
+ "button",
102553
+ {
102554
+ type: "button",
102555
+ onClick: () => {
102556
+ setActiveColor(c2, idx2);
102557
+ const { activePanel, setActivePanel } = useSidePanelStore.getState();
102558
+ if (activePanel !== "color") {
102559
+ setActivePanel("color");
102560
+ }
102561
+ },
102562
+ className: `w-7 h-7 rounded-md border shadow-xs transition-transform hover:scale-105 cursor-pointer relative ${isSelected ? "border-white ring-2 ring-[#02bc71] scale-105 z-10" : "border-neutral-600 hover:border-neutral-400"}`,
102563
+ style: { backgroundColor: c2 },
102564
+ title: `Edit color ${c2.toUpperCase()}`
102565
+ },
102566
+ c2 + "-" + idx2
102567
+ );
102568
+ }) });
102569
+ }
102344
102570
  const hueRgb = hsvToRgb(hsv.h, 100, 100);
102345
102571
  const hueColor = rgbToHex(hueRgb.r, hueRgb.g, hueRgb.b);
102346
102572
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${className}`, ref: containerRef, children: [
102347
102573
  /* @__PURE__ */ jsxRuntime.jsx(
102348
102574
  "div",
102349
102575
  {
102350
- className: "cursor-pointer w-7 h-7 rounded-md border border-neutral-600 shadow-sm",
102576
+ className: "cursor-pointer w-7 h-7 rounded-md border border-neutral-600 shadow-sm transition-transform hover:scale-105",
102351
102577
  style: { background: color },
102352
- onClick: () => setOpen((v2) => !v2)
102578
+ onClick: () => setOpen((v2) => !v2),
102579
+ title: "Color Picker"
102353
102580
  }
102354
102581
  ),
102355
102582
  open2 && reactDom.createPortal(
@@ -102358,46 +102585,202 @@ end`);
102358
102585
  {
102359
102586
  ref: popoverRef,
102360
102587
  style: { position: "fixed", top: `${coords.top}px`, left: `${coords.left}px`, zIndex: 999999 },
102361
- className: "bg-white border border-gray-300 rounded-lg shadow-2xl p-3 w-[290px] text-gray-800",
102588
+ className: "bg-white rounded-2xl shadow-2xl p-3.5 w-[270px] text-gray-800 border border-gray-100 select-none font-sans",
102362
102589
  children: [
102363
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center mb-3", children: [
102364
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
102365
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-gray-800 font-semibold text-sm", children: "Color Picker" }),
102366
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] font-bold tracking-wider text-cyan-600 bg-cyan-50 px-1.5 py-0.5 rounded uppercase", children: "CMYK" })
102367
- ] }),
102368
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setOpen(false), className: "text-gray-400 hover:text-gray-600 text-lg leading-none", children: "✕" })
102369
- ] }),
102370
- /* @__PURE__ */ jsxRuntime.jsx(GradientSelector, { hsv, hueColor, refEl: gradientRef, onChange: updateFromHsv }),
102371
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
102372
- /* @__PURE__ */ jsxRuntime.jsx(ColorPreviewBox, { color, alpha }),
102373
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
102374
- /* @__PURE__ */ jsxRuntime.jsx(HueSlider, { hsv, refEl: hueRef, onChange: updateFromHsv }),
102375
- !isControlled && /* @__PURE__ */ jsxRuntime.jsx(
102376
- AlphaSlider,
102590
+ /* @__PURE__ */ jsxRuntime.jsx(
102591
+ "div",
102592
+ {
102593
+ ref: svRef,
102594
+ className: "w-full h-[135px] rounded-xl relative cursor-crosshair overflow-hidden touch-none",
102595
+ style: { background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, ${hueColor})` },
102596
+ onPointerDown: (e2) => {
102597
+ e2.currentTarget.setPointerCapture(e2.pointerId);
102598
+ const rect = e2.currentTarget.getBoundingClientRect();
102599
+ updateFromHsv({
102600
+ ...hsv,
102601
+ s: Math.round(Math.max(0, Math.min(1, (e2.clientX - rect.left) / rect.width)) * 100),
102602
+ v: Math.round((1 - Math.max(0, Math.min(1, (e2.clientY - rect.top) / rect.height))) * 100)
102603
+ });
102604
+ },
102605
+ onPointerMove: (e2) => {
102606
+ if (e2.buttons === 0) return;
102607
+ const rect = e2.currentTarget.getBoundingClientRect();
102608
+ updateFromHsv({
102609
+ ...hsv,
102610
+ s: Math.round(Math.max(0, Math.min(1, (e2.clientX - rect.left) / rect.width)) * 100),
102611
+ v: Math.round((1 - Math.max(0, Math.min(1, (e2.clientY - rect.top) / rect.height))) * 100)
102612
+ });
102613
+ },
102614
+ children: /* @__PURE__ */ jsxRuntime.jsx(
102615
+ "div",
102377
102616
  {
102378
- alpha,
102379
- color,
102380
- refEl: alphaRef,
102381
- setAlpha,
102382
- canvas: canvas2,
102383
- targets,
102384
- saveState
102617
+ className: "w-4 h-4 rounded-full border-2 border-white shadow-md absolute pointer-events-none -translate-x-1/2 -translate-y-1/2",
102618
+ style: { left: `${hsv.s}%`, top: `${100 - hsv.v}%`, backgroundColor: color }
102385
102619
  }
102386
102620
  )
102387
- ] })
102388
- ] }),
102621
+ }
102622
+ ),
102389
102623
  /* @__PURE__ */ jsxRuntime.jsx(
102390
- CMYKInputs,
102624
+ "div",
102391
102625
  {
102392
- cmyk: cmyk2,
102393
- alpha,
102394
- setAlpha,
102395
- updateColor: updateFromCmyk,
102396
- canvas: canvas2,
102397
- targets,
102398
- saveState
102626
+ ref: hueRef,
102627
+ className: "w-full h-3 rounded-full relative cursor-pointer mt-3 mb-2 touch-none shadow-inner",
102628
+ style: { background: "linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%)" },
102629
+ onPointerDown: (e2) => {
102630
+ e2.currentTarget.setPointerCapture(e2.pointerId);
102631
+ const rect = e2.currentTarget.getBoundingClientRect();
102632
+ updateFromHsv({ ...hsv, h: Math.round(Math.max(0, Math.min(1, (e2.clientX - rect.left) / rect.width)) * 360) % 360 });
102633
+ },
102634
+ onPointerMove: (e2) => {
102635
+ if (e2.buttons === 0) return;
102636
+ const rect = e2.currentTarget.getBoundingClientRect();
102637
+ updateFromHsv({ ...hsv, h: Math.round(Math.max(0, Math.min(1, (e2.clientX - rect.left) / rect.width)) * 360) % 360 });
102638
+ },
102639
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 rounded-full border-2 border-white shadow-md absolute top-1/2 -translate-y-1/2 -translate-x-1/2 pointer-events-none", style: { left: `${hsv.h / 360 * 100}%`, backgroundColor: hueColor } })
102399
102640
  }
102400
- )
102641
+ ),
102642
+ /* @__PURE__ */ jsxRuntime.jsxs(
102643
+ "div",
102644
+ {
102645
+ ref: alphaRef,
102646
+ className: "w-full h-3 rounded-full relative cursor-pointer mb-3 touch-none pqc-checkerboard shadow-inner overflow-hidden",
102647
+ onPointerDown: (e2) => {
102648
+ e2.currentTarget.setPointerCapture(e2.pointerId);
102649
+ const rect = e2.currentTarget.getBoundingClientRect();
102650
+ const newAlpha = Math.round(Math.max(0, Math.min(1, (e2.clientX - rect.left) / rect.width)) * 100);
102651
+ setAlpha(newAlpha);
102652
+ if (canvas2 && targets.length) {
102653
+ targets.forEach((obj) => obj.set("opacity", newAlpha / 100));
102654
+ canvas2.requestRenderAll();
102655
+ }
102656
+ },
102657
+ onPointerMove: (e2) => {
102658
+ if (e2.buttons === 0) return;
102659
+ const rect = e2.currentTarget.getBoundingClientRect();
102660
+ const newAlpha = Math.round(Math.max(0, Math.min(1, (e2.clientX - rect.left) / rect.width)) * 100);
102661
+ setAlpha(newAlpha);
102662
+ if (canvas2 && targets.length) {
102663
+ targets.forEach((obj) => obj.set("opacity", newAlpha / 100));
102664
+ canvas2.requestRenderAll();
102665
+ }
102666
+ },
102667
+ children: [
102668
+ /* @__PURE__ */ jsxRuntime.jsx(
102669
+ "div",
102670
+ {
102671
+ className: "absolute inset-0",
102672
+ style: {
102673
+ background: `linear-gradient(to right, transparent, ${color})`
102674
+ }
102675
+ }
102676
+ ),
102677
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 rounded-full border-2 border-white shadow-md absolute top-1/2 -translate-y-1/2 -translate-x-1/2 pointer-events-none", style: { left: `${alpha}%` } })
102678
+ ]
102679
+ }
102680
+ ),
102681
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-1.5 mb-3 bg-gray-50 p-1 rounded-xl border border-gray-100", children: [
102682
+ /* @__PURE__ */ jsxRuntime.jsx(
102683
+ "button",
102684
+ {
102685
+ onClick: () => setMode("HEX"),
102686
+ className: `flex-1 py-1 text-xs font-semibold rounded-lg transition-all ${mode2 === "HEX" ? "bg-white text-gray-900 shadow-sm" : "text-gray-500 hover:text-gray-900"}`,
102687
+ children: "HEX"
102688
+ }
102689
+ ),
102690
+ /* @__PURE__ */ jsxRuntime.jsx(
102691
+ "button",
102692
+ {
102693
+ onClick: () => setMode("CMYK"),
102694
+ className: `flex-1 py-1 text-xs font-semibold rounded-lg transition-all ${mode2 === "CMYK" ? "bg-white text-gray-900 shadow-sm" : "text-gray-500 hover:text-gray-900"}`,
102695
+ children: "CMYK"
102696
+ }
102697
+ )
102698
+ ] }),
102699
+ mode2 === "HEX" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 mb-3", children: [
102700
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex items-center bg-gray-50 border border-gray-200 rounded-xl px-2.5 py-1.5", children: [
102701
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 text-xs font-mono", children: "#" }),
102702
+ /* @__PURE__ */ jsxRuntime.jsx(
102703
+ "input",
102704
+ {
102705
+ type: "text",
102706
+ value: color.replace("#", "").toUpperCase(),
102707
+ onChange: (e2) => updateFromHex(e2.target.value),
102708
+ className: "w-full bg-transparent text-xs font-mono font-medium focus:outline-none uppercase pl-1 text-gray-800"
102709
+ }
102710
+ )
102711
+ ] }),
102712
+ /* @__PURE__ */ jsxRuntime.jsx(
102713
+ "button",
102714
+ {
102715
+ onClick: handleCopy,
102716
+ className: "p-2 rounded-xl bg-gray-50 hover:bg-gray-100 border border-gray-200 text-gray-600 transition-colors cursor-pointer text-xs",
102717
+ title: "Copy HEX",
102718
+ children: copied ? "✓" : "Copy"
102719
+ }
102720
+ ),
102721
+ typeof window !== "undefined" && "EyeDropper" in window && /* @__PURE__ */ jsxRuntime.jsx(
102722
+ "button",
102723
+ {
102724
+ onClick: handleEyeDropper,
102725
+ className: "p-2 rounded-xl bg-gray-50 hover:bg-gray-100 border border-gray-200 text-gray-600 hover:text-cyan-600 transition-colors cursor-pointer",
102726
+ title: "Eyedropper",
102727
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
102728
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m14 7 3 3" }),
102729
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 10 9 20l-4 1 1-4 10-10a2.828 2.828 0 0 1 4 4Z" }),
102730
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m15 4 2-2 4 4-2 2" })
102731
+ ] })
102732
+ }
102733
+ )
102734
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5 mb-3", children: CMYK_CHANNELS$1.map(({ key, label, color: lColor, name }) => /* @__PURE__ */ jsxRuntime.jsx(
102735
+ ChannelRow,
102736
+ {
102737
+ label,
102738
+ labelColor: lColor,
102739
+ ariaLabel: name,
102740
+ value: cmyk2[key],
102741
+ min: 0,
102742
+ max: 100,
102743
+ gradient: `linear-gradient(to right, ${cmykToHex(
102744
+ key === "c" ? 0 : cmyk2.c,
102745
+ key === "m" ? 0 : cmyk2.m,
102746
+ key === "y" ? 0 : cmyk2.y,
102747
+ key === "k" ? 0 : cmyk2.k
102748
+ )}, ${cmykToHex(
102749
+ key === "c" ? 100 : cmyk2.c,
102750
+ key === "m" ? 100 : cmyk2.m,
102751
+ key === "y" ? 100 : cmyk2.y,
102752
+ key === "k" ? 100 : cmyk2.k
102753
+ )})`,
102754
+ onChange: (val) => updateFromCmyk({ ...cmyk2, [key]: val })
102755
+ },
102756
+ key
102757
+ )) }),
102758
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-2 border-t border-gray-100", children: [
102759
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-bold text-gray-400 uppercase tracking-wider block mb-1.5", children: "Preset Swatches" }),
102760
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-8 gap-1.5", children: DEFAULT_COLORS.map((c2) => /* @__PURE__ */ jsxRuntime.jsx(
102761
+ "button",
102762
+ {
102763
+ className: "w-6 h-6 rounded-md border border-gray-200 shadow-xs cursor-pointer hover:scale-110 transition-transform",
102764
+ style: { backgroundColor: c2 },
102765
+ onClick: () => updateFromHex(c2),
102766
+ title: c2
102767
+ },
102768
+ c2
102769
+ )) })
102770
+ ] }),
102771
+ recentColors.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-2 mt-2 border-t border-gray-100", children: [
102772
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-bold text-gray-400 uppercase tracking-wider block mb-1.5", children: "Recent Colors" }),
102773
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-8 gap-1.5", children: recentColors.map((c2, i2) => /* @__PURE__ */ jsxRuntime.jsx(
102774
+ "button",
102775
+ {
102776
+ className: "w-6 h-6 rounded-md border border-gray-200 shadow-xs cursor-pointer hover:scale-110 transition-transform",
102777
+ style: { backgroundColor: c2 },
102778
+ onClick: () => updateFromHex(c2),
102779
+ title: c2
102780
+ },
102781
+ c2 + i2
102782
+ )) })
102783
+ ] })
102401
102784
  ]
102402
102785
  }
102403
102786
  ),
@@ -102493,11 +102876,26 @@ end`);
102493
102876
  hex
102494
102877
  ))
102495
102878
  ] }),
102496
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100vh-122px)] overflow-auto", ref: scrollRef, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-3 mx-4 pb-5", children: backgroundImages.map((item, i2) => /* @__PURE__ */ jsxRuntime.jsx(
102879
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100vh-165px)] overflow-auto", ref: scrollRef, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-3 mx-4 pb-5", children: backgroundImages.map((item, i2) => /* @__PURE__ */ jsxRuntime.jsx(
102497
102880
  "button",
102498
102881
  {
102499
102882
  className: "relative aspect-square rounded overflow-hidden group cursor-pointer",
102500
- onClick: () => handleApplyBackground("image", item.largeImage),
102883
+ onClick: async () => {
102884
+ try {
102885
+ startLoading();
102886
+ const image = await ShapeService.convertImages(item.largeImage);
102887
+ const imageUrl = image?.success ? image?.url : null;
102888
+ if (!imageUrl) return;
102889
+ handleApplyBackground("image", imageUrl);
102890
+ if (window.innerWidth < 768) {
102891
+ useSidePanelStore.getState().setActivePanel(null);
102892
+ }
102893
+ } catch (error2) {
102894
+ console.error("Failed to convert image:", error2);
102895
+ } finally {
102896
+ stopLoading();
102897
+ }
102898
+ },
102501
102899
  children: /* @__PURE__ */ jsxRuntime.jsx(
102502
102900
  "img",
102503
102901
  {
@@ -102515,14 +102913,40 @@ end`);
102515
102913
  }
102516
102914
  function UploadPanel() {
102517
102915
  const [assets, setAssets] = React.useState([]);
102518
- function handleFileChange(e2) {
102916
+ const { startLoading, stopLoading } = useLoaderStore();
102917
+ async function handleFileChange(e2) {
102519
102918
  const file = e2.target.files?.[0];
102520
102919
  if (!file) return;
102521
102920
  if (!file.type.startsWith("image/")) return;
102522
- const url = URL.createObjectURL(file);
102523
- setAssets((prev) => [...prev, { id: crypto.randomUUID(), url }]);
102921
+ const previewUrl = URL.createObjectURL(file);
102922
+ const newAsset = { id: crypto.randomUUID(), url: previewUrl, file };
102923
+ setAssets((prev) => [newAsset, ...prev]);
102524
102924
  e2.target.value = "";
102525
102925
  }
102926
+ async function handleAssetClick(asset) {
102927
+ try {
102928
+ startLoading();
102929
+ let targetUrl = asset.uploadedUrl;
102930
+ if (!targetUrl && asset.file) {
102931
+ targetUrl = await UploadService.uploadFileApi(asset.file);
102932
+ setAssets(
102933
+ (prev) => prev.map((a2) => a2.id === asset.id ? { ...a2, uploadedUrl: targetUrl } : a2)
102934
+ );
102935
+ }
102936
+ const finalUrl = targetUrl || asset.url;
102937
+ if (finalUrl) {
102938
+ addImageToCanvas(finalUrl);
102939
+ if (window.innerWidth < 768) {
102940
+ useSidePanelStore.getState().setActivePanel(null);
102941
+ }
102942
+ }
102943
+ } catch (error2) {
102944
+ console.error("Failed to add image to canvas:", error2);
102945
+ addImageToCanvas(asset.url);
102946
+ } finally {
102947
+ stopLoading();
102948
+ }
102949
+ }
102526
102950
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-4", children: [
102527
102951
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold mb-4 mt-2", children: "Upload your assets" }),
102528
102952
  /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "w-full bg-[#02bc71] border border-neutral-700 hover:bg-[#03945a] py-2 rounded-md font-medium flex items-center justify-center gap-2 cursor-pointer", children: [
@@ -102541,17 +102965,12 @@ end`);
102541
102965
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-3 mt-3", children: assets.map((asset) => /* @__PURE__ */ jsxRuntime.jsx(
102542
102966
  "button",
102543
102967
  {
102544
- onClick: () => {
102545
- addImageToCanvas(asset.url);
102546
- if (window.innerWidth < 768) {
102547
- useSidePanelStore.getState().setActivePanel(null);
102548
- }
102549
- },
102968
+ onClick: () => handleAssetClick(asset),
102550
102969
  className: "rounded-md overflow-hidden transition cursor-pointer",
102551
102970
  children: /* @__PURE__ */ jsxRuntime.jsx(
102552
102971
  "img",
102553
102972
  {
102554
- src: asset.url,
102973
+ src: asset.uploadedUrl || asset.url,
102555
102974
  alt: "asset",
102556
102975
  height: 300,
102557
102976
  width: 300,
@@ -104837,7 +105256,7 @@ end`);
104837
105256
  <path fill="${dark}" d="${pathData.trim()}" />
104838
105257
  </svg>`;
104839
105258
  }
104840
- const inputStyle$1 = "w-full bg-neutral-800 border border-neutral-600 rounded-md px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition";
105259
+ const inputStyle$1 = "w-full bg-neutral-800 border border-neutral-600 rounded-md px-3 py-2 text-sm outline-none focus:border-[#02bc71] focus:ring-1 focus:ring-[#02bc71] transition";
104841
105260
  const FormField$1 = ({
104842
105261
  label,
104843
105262
  error: error2,
@@ -105642,17 +106061,38 @@ END:VCARD`;
105642
106061
  canvas2.requestRenderAll();
105643
106062
  }, [canvas2, active, blur.enabled, blur.value]);
105644
106063
  }
105645
- function useBrightnessEffect(canvas2, enabled) {
105646
- const { brightness } = useEffectStore();
106064
+ function useBrightnessEffect(canvas2, active) {
106065
+ const brightness = useEffectStore((s2) => s2.brightness);
105647
106066
  React.useEffect(() => {
105648
- if (!enabled) return;
105649
- if (!canvas2) return;
105650
- if (!brightness.enabled) return;
105651
- const target = getActiveEffectTarget(canvas2);
105652
- if (!target) return;
105653
- target.set("opacity", 1 + brightness.value / 100);
106067
+ if (!canvas2 || !active) return;
106068
+ const obj = getActiveEffectTarget(canvas2);
106069
+ if (!obj) return;
106070
+ if (obj.type !== "image" && obj.type !== "FabricImage") return;
106071
+ const image = obj;
106072
+ image.filters = image.filters || [];
106073
+ const BRIGHTNESS_INDEX = image.filters.findIndex(
106074
+ (f) => f && (f.type === "Brightness" || f instanceof Sh.Brightness)
106075
+ );
106076
+ if (!brightness.enabled || brightness.value === 0) {
106077
+ if (BRIGHTNESS_INDEX !== -1) {
106078
+ image.filters.splice(BRIGHTNESS_INDEX, 1);
106079
+ image.applyFilters();
106080
+ canvas2.requestRenderAll();
106081
+ }
106082
+ return;
106083
+ }
106084
+ const strength = Math.max(-1, Math.min(1, brightness.value / 100));
106085
+ const brightnessFilter = new Sh.Brightness({
106086
+ brightness: strength
106087
+ });
106088
+ if (BRIGHTNESS_INDEX !== -1) {
106089
+ image.filters[BRIGHTNESS_INDEX] = brightnessFilter;
106090
+ } else {
106091
+ image.filters.push(brightnessFilter);
106092
+ }
106093
+ image.applyFilters();
105654
106094
  canvas2.requestRenderAll();
105655
- }, [canvas2, enabled, brightness.enabled, brightness.value]);
106095
+ }, [canvas2, active, brightness.enabled, brightness.value]);
105656
106096
  }
105657
106097
  function EffectSection({
105658
106098
  label,
@@ -106246,7 +106686,6 @@ END:VCARD`;
106246
106686
  const { toggleShadow, setShadow } = useEffectStore();
106247
106687
  useBlurEffect(canvas2, isOpen);
106248
106688
  useBrightnessEffect(canvas2, isOpen);
106249
- useBrightnessEffect(canvas2, isOpen);
106250
106689
  useTemperatureEffect(canvas2, isOpen);
106251
106690
  useContrastEffect(canvas2, isOpen);
106252
106691
  useShadowEffect(canvas2, isOpen);
@@ -107094,7 +107533,7 @@ END:VCARD`;
107094
107533
  )
107095
107534
  ] }),
107096
107535
  /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px]" }),
107097
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100vh-125px)] overflow-auto", ref: scrollRef, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-4 py-5", children: fontData.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-400 text-sm text-center", children: "No results found" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107536
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100vh-126px)] overflow-auto", ref: scrollRef, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-4 py-5", children: fontData.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-400 text-sm text-center", children: "No results found" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107098
107537
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-4", children: fontData.map((item, i2) => /* @__PURE__ */ jsxRuntime.jsx(
107099
107538
  "button",
107100
107539
  {
@@ -152464,82 +152903,99 @@ ${elements2.join("\n")}
152464
152903
  )
152465
152904
  ] }) });
152466
152905
  }
152467
- const QUOTES = [
152468
- {
152469
- content: "There are no absolutes in this world, and there will always be mistakes that are made within brokerage firms; there will always be people who set out to deceive the regulators and even deceive their own senior management.",
152470
- author: "Mary Schapiro"
152471
- },
152472
- {
152473
- content: "Things will get better if you just hold out long enough.",
152474
- author: "Desmond Dekker"
152475
- },
152476
- {
152477
- content: "The thing I am attracted to is just good writing and stories that are based somewhat in reality.",
152478
- author: "Jennifer Aniston"
152479
- },
152480
- {
152481
- content: "Tax reform and expanded trade are going to be so important to the economy of Illinois.",
152482
- author: "Bill Foster"
152483
- },
152484
- {
152485
- content: "In the middle of every difficulty lies opportunity.",
152486
- author: "Albert Einstein"
152487
- },
152488
- {
152489
- content: "It does not matter how slowly you go as long as you do not stop.",
152490
- author: "Confucius"
152491
- },
152492
- {
152493
- content: "Life is what happens when you're busy making other plans.",
152494
- author: "John Lennon"
152495
- },
152496
- {
152497
- content: "The future belongs to those who believe in the beauty of their dreams.",
152498
- author: "Eleanor Roosevelt"
152499
- },
152500
- {
152501
- content: "Success is not final, failure is not fatal: it is the courage to continue that counts.",
152502
- author: "Winston Churchill"
152503
- },
152504
- {
152505
- content: "You miss 100% of the shots you don't take.",
152506
- author: "Wayne Gretzky"
152507
- },
152508
- {
152509
- content: "Whether you think you can or you think you can't, you're right.",
152510
- author: "Henry Ford"
152511
- },
152512
- {
152513
- content: "I have not failed. I've just found 10,000 ways that won't work.",
152514
- author: "Thomas Edison"
152906
+ const QuoteService = {
152907
+ async getAllQuotes(params = {}) {
152908
+ try {
152909
+ const query = new URLSearchParams();
152910
+ if (params.page) query.append("page", String(params.page));
152911
+ if (params.limit) query.append("limit", String(params.limit));
152912
+ if (params.search) query.append("search", params.search);
152913
+ query.append("status", "true");
152914
+ const res = await apiFetch(`/api/quotes?${query.toString()}`, {
152915
+ method: "GET"
152916
+ });
152917
+ return Array.isArray(res?.data) ? res.data : [];
152918
+ } catch (err) {
152919
+ console.error("Failed to fetch quotes:", err);
152920
+ return [];
152921
+ }
152515
152922
  }
152516
- ];
152923
+ };
152517
152924
  function QuotesPanel() {
152925
+ const [quotes, setQuotes] = React.useState([]);
152926
+ const [page, setPage] = React.useState(1);
152927
+ const [hasMore, setHasMore] = React.useState(true);
152928
+ const [isLoading, setIsLoading] = React.useState(false);
152929
+ const scrollRef = React.useRef(null);
152930
+ const fetchQuotes = React.useCallback(async (pageNo) => {
152931
+ setIsLoading(true);
152932
+ try {
152933
+ const data = await QuoteService.getAllQuotes({ page: pageNo, limit: 20 });
152934
+ if (data && data.length > 0) {
152935
+ setQuotes((prev) => pageNo === 1 ? data : [...prev, ...data]);
152936
+ if (data.length < 20) {
152937
+ setHasMore(false);
152938
+ }
152939
+ } else {
152940
+ setHasMore(false);
152941
+ }
152942
+ } catch (err) {
152943
+ console.error("Failed to load quotes:", err);
152944
+ } finally {
152945
+ setIsLoading(false);
152946
+ }
152947
+ }, []);
152948
+ React.useEffect(() => {
152949
+ fetchQuotes(1);
152950
+ }, [fetchQuotes]);
152951
+ React.useEffect(() => {
152952
+ const el = scrollRef.current;
152953
+ if (!el) return;
152954
+ const onScroll = () => {
152955
+ if (!hasMore || isLoading) return;
152956
+ if (el.scrollTop + el.clientHeight >= el.scrollHeight - 150) {
152957
+ setPage((prev) => {
152958
+ const next = prev + 1;
152959
+ fetchQuotes(next);
152960
+ return next;
152961
+ });
152962
+ }
152963
+ };
152964
+ el.addEventListener("scroll", onScroll);
152965
+ return () => el.removeEventListener("scroll", onScroll);
152966
+ }, [hasMore, isLoading, fetchQuotes]);
152518
152967
  const addQuote = async (content, author) => {
152519
- await addTextToCanvas(18, 300, `${content}
152968
+ await addTextToCanvas(18, 300, author ? `${content}
152520
152969
 
152521
- — ${author}`);
152970
+ — ${author}` : content);
152971
+ if (window.innerWidth < 768) {
152972
+ useSidePanelStore.getState().setActivePanel(null);
152973
+ }
152522
152974
  };
152523
152975
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-full", children: [
152524
152976
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pb-3", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-base font-semibold text-white", children: "Quotes" }) }),
152525
152977
  /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "border-neutral-800 mx-4 mb-3" }),
152526
- /* @__PURE__ */ jsxRuntime.jsx(
152978
+ /* @__PURE__ */ jsxRuntime.jsxs(
152527
152979
  "div",
152528
152980
  {
152981
+ ref: scrollRef,
152529
152982
  className: "flex-1 min-h-0 overflow-y-auto flex flex-col gap-3 px-4 py-2",
152530
- children: QUOTES.map((q2, i2) => /* @__PURE__ */ jsxRuntime.jsxs(
152531
- "button",
152532
- {
152533
- onClick: () => addQuote(q2.content, q2.author),
152534
- title: "Click to add to canvas",
152535
- className: "\n w-full text-left rounded-lg\n bg-neutral-800 hover:bg-neutral-700\n border border-neutral-700 hover:border-[#02bc71]/40\n px-4 py-3.5 cursor-pointer\n transition-all duration-150 active:scale-[0.98]\n group\n ",
152536
- children: [
152537
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-neutral-200 text-[13px] leading-relaxed mb-2 group-hover:text-white transition-colors", children: q2.content }),
152538
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-right text-[12px] font-semibold text-[#02bc71]", children: q2.author })
152539
- ]
152540
- },
152541
- i2
152542
- ))
152983
+ children: [
152984
+ quotes.map((q2, i2) => /* @__PURE__ */ jsxRuntime.jsxs(
152985
+ "button",
152986
+ {
152987
+ onClick: () => addQuote(q2.content, q2.author),
152988
+ title: "Click to add to canvas",
152989
+ className: "\n w-full text-left rounded-lg\n bg-neutral-800 hover:bg-neutral-700\n border border-neutral-700 hover:border-[#02bc71]/40\n px-4 py-3.5 cursor-pointer\n transition-all duration-150 active:scale-[0.98]\n group\n ",
152990
+ children: [
152991
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-neutral-200 text-[13px] leading-relaxed mb-2 group-hover:text-white transition-colors", children: q2.content }),
152992
+ q2.author && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-right text-[12px] font-semibold text-[#02bc71]", children: q2.author })
152993
+ ]
152994
+ },
152995
+ q2._id || i2
152996
+ )),
152997
+ isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-4 text-xs text-neutral-500", children: "Loading more quotes..." })
152998
+ ]
152543
152999
  }
152544
153000
  )
152545
153001
  ] });
@@ -152567,6 +153023,559 @@ ${elements2.join("\n")}
152567
153023
  ) })
152568
153024
  }
152569
153025
  );
153026
+ const ColorService = {
153027
+ async getAll(params) {
153028
+ try {
153029
+ const searchParams = new URLSearchParams();
153030
+ if (params?.search) searchParams.append("search", params.search);
153031
+ if (params?.status !== void 0) searchParams.append("status", String(params.status));
153032
+ searchParams.append("limit", String(params?.limit || 100));
153033
+ if (params?.page) searchParams.append("page", String(params.page));
153034
+ const res = await apiFetch(`/colors?${searchParams.toString()}`, {
153035
+ method: "GET"
153036
+ });
153037
+ if (Array.isArray(res)) return res;
153038
+ if (Array.isArray(res?.data)) return res.data;
153039
+ return [];
153040
+ } catch (err) {
153041
+ console.error("Failed to fetch colors:", err);
153042
+ return [];
153043
+ }
153044
+ }
153045
+ };
153046
+ const DEFAULT_PRESET_COLORS = [
153047
+ "#ffffff",
153048
+ "#000000",
153049
+ "#1e293b",
153050
+ "#475569",
153051
+ "#94a3b8",
153052
+ "#e2e8f0",
153053
+ "#ef4444",
153054
+ "#f97316",
153055
+ "#f59e0b",
153056
+ "#eab308",
153057
+ "#84cc16",
153058
+ "#22c55e",
153059
+ "#10b981",
153060
+ "#06b6d4",
153061
+ "#0ea5e9",
153062
+ "#3b82f6",
153063
+ "#6366f1",
153064
+ "#8b5cf6",
153065
+ "#a855f7",
153066
+ "#d946ef",
153067
+ "#ec4899",
153068
+ "#f43f5e"
153069
+ ];
153070
+ const CMYK_CHANNELS = [
153071
+ { key: "c", label: "C", color: "#009fe3", name: "Cyan" },
153072
+ { key: "m", label: "M", color: "#e6007e", name: "Magenta" },
153073
+ { key: "y", label: "Y", color: "#dcb000", name: "Yellow" },
153074
+ { key: "k", label: "K", color: "#222222", name: "Black" }
153075
+ ];
153076
+ function ColorPanel() {
153077
+ const canvas2 = useEditorStore((s2) => s2.canvas);
153078
+ const { objects, activeColor, activeColorIndex, setActiveColor } = useSelectionStore();
153079
+ const { setActivePanel } = useSidePanelStore();
153080
+ const saveState = useHistoryStore((s2) => s2.saveState);
153081
+ const targets = React.useMemo(
153082
+ () => objects.filter((o2) => o2.name !== "workarea"),
153083
+ [objects]
153084
+ );
153085
+ const objectColors = React.useMemo(() => extractAllTargetColors(targets), [targets, objects]);
153086
+ const targetInfo = React.useMemo(() => extractTargetColor(targets), [targets]);
153087
+ const initialColor = activeColor || (objectColors.length > 0 ? objectColors[0] : targetInfo.color || "#000000");
153088
+ const [currentColor, setCurrentColor] = React.useState(initialColor);
153089
+ const [currentCmyk, setCurrentCmyk] = React.useState(() => hexToCmyk(initialColor));
153090
+ const [hsv, setHsv] = React.useState(() => {
153091
+ const rgb2 = hexToRgb(initialColor);
153092
+ return rgbToHsv(rgb2.r, rgb2.g, rgb2.b);
153093
+ });
153094
+ const [alpha, setAlpha] = React.useState(() => targetInfo.opacity ?? 100);
153095
+ const [colorMode, setColorMode] = React.useState("HEX");
153096
+ const [dbColors, setDbColors] = React.useState([]);
153097
+ const [isLoadingColors, setIsLoadingColors] = React.useState(false);
153098
+ const [copied, setCopied] = React.useState(false);
153099
+ const svRef = React.useRef(null);
153100
+ const hueRef = React.useRef(null);
153101
+ const alphaRef = React.useRef(null);
153102
+ React.useEffect(() => {
153103
+ if (targets.length > 0) {
153104
+ const colors = extractAllTargetColors(targets);
153105
+ if (colors.length > 0) {
153106
+ let chosenColor = activeColor;
153107
+ if (!chosenColor || !colors.includes(chosenColor)) {
153108
+ chosenColor = colors[activeColorIndex] || colors[0];
153109
+ }
153110
+ setCurrentColor(chosenColor);
153111
+ const c2 = hexToCmyk(chosenColor);
153112
+ setCurrentCmyk(c2);
153113
+ const rgb2 = hexToRgb(chosenColor);
153114
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
153115
+ const info = extractTargetColor(targets);
153116
+ if (info.opacity !== void 0) setAlpha(info.opacity);
153117
+ if (activeColor !== chosenColor) {
153118
+ const idx2 = colors.indexOf(chosenColor);
153119
+ setActiveColor(chosenColor, idx2 >= 0 ? idx2 : 0);
153120
+ }
153121
+ }
153122
+ }
153123
+ }, [targets, activeColor, activeColorIndex]);
153124
+ React.useEffect(() => {
153125
+ let isMounted = true;
153126
+ const fetchColors = async () => {
153127
+ setIsLoadingColors(true);
153128
+ try {
153129
+ const data = await ColorService.getAll({ status: true, limit: 100 });
153130
+ if (isMounted) setDbColors(data);
153131
+ } finally {
153132
+ if (isMounted) setIsLoadingColors(false);
153133
+ }
153134
+ };
153135
+ fetchColors();
153136
+ return () => {
153137
+ isMounted = false;
153138
+ };
153139
+ }, []);
153140
+ const documentColors = React.useMemo(() => {
153141
+ if (!canvas2) return [];
153142
+ const allObjs = canvas2.getObjects ? canvas2.getObjects() : [];
153143
+ return extractAllTargetColors(allObjs);
153144
+ }, [canvas2, objects]);
153145
+ const applyColor = React.useCallback(
153146
+ (newHex, cmykVal, opacityVal = alpha) => {
153147
+ const resolvedCmyk = cmykVal || hexToCmyk(newHex);
153148
+ const oldTargetColor = currentColor;
153149
+ setCurrentColor(newHex);
153150
+ setCurrentCmyk(resolvedCmyk);
153151
+ if (targets.length > 0) {
153152
+ applyFillToFabricObjects(targets, newHex, opacityVal, oldTargetColor, canvas2, resolvedCmyk);
153153
+ setActiveColor(newHex, activeColorIndex);
153154
+ } else {
153155
+ addBackgroundColor(newHex);
153156
+ }
153157
+ saveState();
153158
+ },
153159
+ [targets, canvas2, alpha, currentColor, activeColorIndex, setActiveColor, saveState]
153160
+ );
153161
+ const updateFromHsv = React.useCallback(
153162
+ (newHsv) => {
153163
+ const rgb2 = hsvToRgb(newHsv.h, newHsv.s, newHsv.v);
153164
+ const newC = rgbToCmyk(rgb2.r, rgb2.g, rgb2.b);
153165
+ const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
153166
+ setHsv(newHsv);
153167
+ setCurrentCmyk(newC);
153168
+ applyColor(hex, newC, alpha);
153169
+ },
153170
+ [alpha, applyColor]
153171
+ );
153172
+ const updateFromCmyk = React.useCallback(
153173
+ (newCmyk) => {
153174
+ const rgb2 = cmykToRgb(newCmyk.c, newCmyk.m, newCmyk.y, newCmyk.k);
153175
+ const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
153176
+ setCurrentCmyk(newCmyk);
153177
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
153178
+ applyColor(hex, newCmyk, alpha);
153179
+ },
153180
+ [alpha, applyColor]
153181
+ );
153182
+ const updateFromHex = React.useCallback(
153183
+ (inputHex) => {
153184
+ let clean = inputHex.trim().replace(/^#/, "");
153185
+ if (clean.length === 3) clean = clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2];
153186
+ if (/^[0-9a-fA-F]{6}$/.test(clean)) {
153187
+ const hex = `#${clean.toLowerCase()}`;
153188
+ const rgb2 = hexToRgb(hex);
153189
+ const newC = rgbToCmyk(rgb2.r, rgb2.g, rgb2.b);
153190
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
153191
+ setCurrentCmyk(newC);
153192
+ applyColor(hex, newC, alpha);
153193
+ }
153194
+ },
153195
+ [alpha, applyColor]
153196
+ );
153197
+ const handleSelectObjectColor = (hex, idx2) => {
153198
+ setActiveColor(hex, idx2);
153199
+ setCurrentColor(hex);
153200
+ const c2 = hexToCmyk(hex);
153201
+ setCurrentCmyk(c2);
153202
+ const rgb2 = hexToRgb(hex);
153203
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
153204
+ };
153205
+ const handleCopyHex = () => {
153206
+ navigator.clipboard.writeText(currentColor);
153207
+ setCopied(true);
153208
+ setTimeout(() => setCopied(false), 1500);
153209
+ };
153210
+ const handleEyeDropper = async () => {
153211
+ if (typeof window !== "undefined" && "EyeDropper" in window) {
153212
+ try {
153213
+ const eyeDropper = new window.EyeDropper();
153214
+ const result = await eyeDropper.open();
153215
+ if (result?.sRGBHex) {
153216
+ updateFromHex(result.sRGBHex);
153217
+ }
153218
+ } catch (err) {
153219
+ console.error("EyeDropper error:", err);
153220
+ }
153221
+ }
153222
+ };
153223
+ const groupedDbColors = React.useMemo(() => {
153224
+ const map = /* @__PURE__ */ new Map();
153225
+ dbColors.forEach((c2) => {
153226
+ const group = c2.colorGroup || "Custom Palettes";
153227
+ if (!map.has(group)) map.set(group, []);
153228
+ map.get(group).push(c2);
153229
+ });
153230
+ return map;
153231
+ }, [dbColors]);
153232
+ const hueRgb = hsvToRgb(hsv.h, 100, 100);
153233
+ const hueColor = rgbToHex(hueRgb.r, hueRgb.g, hueRgb.b);
153234
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full h-full bg-neutral-900 border-r border-neutral-800 flex flex-col select-none text-white text-xs", children: [
153235
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 flex flex-col gap-3 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
153236
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
153237
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-2 h-2 rounded-full bg-[#02bc71] animate-pulse" }),
153238
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "font-semibold text-sm text-neutral-100", children: "Color Palette" })
153239
+ ] }),
153240
+ /* @__PURE__ */ jsxRuntime.jsx(
153241
+ "button",
153242
+ {
153243
+ onClick: () => setActivePanel(null),
153244
+ className: "p-1 rounded-md text-neutral-400 hover:text-white hover:bg-neutral-800 transition-colors cursor-pointer",
153245
+ title: "Close Panel",
153246
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) })
153247
+ }
153248
+ )
153249
+ ] }) }),
153250
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto space-y-5 p-4 pr-3", children: [
153251
+ objectColors.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
153252
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
153253
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[11px] font-bold text-neutral-400 uppercase tracking-wider", children: [
153254
+ "Object Colors (",
153255
+ objectColors.length,
153256
+ ")"
153257
+ ] }),
153258
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-neutral-500", children: "Select to customize" })
153259
+ ] }),
153260
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-2 p-2.5 bg-neutral-950 border border-neutral-800 rounded-xl", children: objectColors.map((hex, idx2) => {
153261
+ const isSelected = currentColor.toLowerCase() === hex.toLowerCase();
153262
+ return /* @__PURE__ */ jsxRuntime.jsx(
153263
+ "button",
153264
+ {
153265
+ type: "button",
153266
+ onClick: () => handleSelectObjectColor(hex, idx2),
153267
+ className: `w-8 h-8 rounded-lg border transition-all cursor-pointer hover:scale-105 shadow-xs relative ${isSelected ? "border-white ring-2 ring-[#02bc71] scale-105 z-10" : "border-neutral-700 hover:border-neutral-500"}`,
153268
+ style: { backgroundColor: hex },
153269
+ title: `Object color ${idx2 + 1}: ${hex.toUpperCase()}`,
153270
+ children: isSelected && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 flex items-center justify-center text-xs font-bold mix-blend-difference text-white", children: "✓" })
153271
+ },
153272
+ hex + "-" + idx2
153273
+ );
153274
+ }) })
153275
+ ] }),
153276
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3.5 bg-neutral-950 border border-neutral-800 rounded-2xl shadow-xl space-y-3", children: [
153277
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between pb-1", children: [
153278
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
153279
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-bold text-neutral-300", children: "Custom Color" }),
153280
+ /* @__PURE__ */ jsxRuntime.jsx(
153281
+ "span",
153282
+ {
153283
+ className: "w-3.5 h-3.5 rounded-full border border-neutral-700 shadow-inner",
153284
+ style: { backgroundColor: currentColor }
153285
+ }
153286
+ )
153287
+ ] }),
153288
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex bg-neutral-900 rounded-lg p-0.5 border border-neutral-800", children: [
153289
+ /* @__PURE__ */ jsxRuntime.jsx(
153290
+ "button",
153291
+ {
153292
+ type: "button",
153293
+ onClick: () => setColorMode("HEX"),
153294
+ className: `px-2 py-0.5 rounded text-[10px] font-bold transition-colors cursor-pointer ${colorMode === "HEX" ? "bg-[#02bc71] text-white" : "text-neutral-400 hover:text-white"}`,
153295
+ children: "HEX"
153296
+ }
153297
+ ),
153298
+ /* @__PURE__ */ jsxRuntime.jsx(
153299
+ "button",
153300
+ {
153301
+ type: "button",
153302
+ onClick: () => setColorMode("CMYK"),
153303
+ className: `px-2 py-0.5 rounded text-[10px] font-bold transition-colors cursor-pointer ${colorMode === "CMYK" ? "bg-[#02bc71] text-white" : "text-neutral-400 hover:text-white"}`,
153304
+ children: "CMYK"
153305
+ }
153306
+ )
153307
+ ] })
153308
+ ] }),
153309
+ /* @__PURE__ */ jsxRuntime.jsx(
153310
+ "div",
153311
+ {
153312
+ ref: svRef,
153313
+ className: "relative w-full h-36 rounded-xl overflow-hidden cursor-crosshair select-none border border-neutral-800 touch-none",
153314
+ style: {
153315
+ background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, ${hueColor})`
153316
+ },
153317
+ onPointerDown: (e2) => {
153318
+ e2.currentTarget.setPointerCapture(e2.pointerId);
153319
+ const rect = e2.currentTarget.getBoundingClientRect();
153320
+ updateFromHsv({
153321
+ ...hsv,
153322
+ s: Math.round(Math.max(0, Math.min(1, (e2.clientX - rect.left) / rect.width)) * 100),
153323
+ v: Math.round((1 - Math.max(0, Math.min(1, (e2.clientY - rect.top) / rect.height))) * 100)
153324
+ });
153325
+ },
153326
+ onPointerMove: (e2) => {
153327
+ if (e2.buttons === 0) return;
153328
+ const rect = e2.currentTarget.getBoundingClientRect();
153329
+ updateFromHsv({
153330
+ ...hsv,
153331
+ s: Math.round(Math.max(0, Math.min(1, (e2.clientX - rect.left) / rect.width)) * 100),
153332
+ v: Math.round((1 - Math.max(0, Math.min(1, (e2.clientY - rect.top) / rect.height))) * 100)
153333
+ });
153334
+ },
153335
+ children: /* @__PURE__ */ jsxRuntime.jsx(
153336
+ "div",
153337
+ {
153338
+ className: "absolute w-4 h-4 rounded-full border-2 border-white shadow-md -translate-x-1/2 -translate-y-1/2 pointer-events-none ring-1 ring-black/40",
153339
+ style: {
153340
+ left: `${hsv.s}%`,
153341
+ top: `${100 - hsv.v}%`,
153342
+ backgroundColor: currentColor
153343
+ }
153344
+ }
153345
+ )
153346
+ }
153347
+ ),
153348
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
153349
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-[10px] text-neutral-400 font-medium", children: [
153350
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Hue" }),
153351
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
153352
+ Math.round(hsv.h),
153353
+ "°"
153354
+ ] })
153355
+ ] }),
153356
+ /* @__PURE__ */ jsxRuntime.jsx(
153357
+ "div",
153358
+ {
153359
+ ref: hueRef,
153360
+ className: "relative h-4 rounded-lg cursor-pointer select-none border border-neutral-800 touch-none",
153361
+ style: {
153362
+ background: "linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%)"
153363
+ },
153364
+ onPointerDown: (e2) => {
153365
+ e2.currentTarget.setPointerCapture(e2.pointerId);
153366
+ const rect = e2.currentTarget.getBoundingClientRect();
153367
+ updateFromHsv({
153368
+ ...hsv,
153369
+ h: Math.round(Math.max(0, Math.min(1, (e2.clientX - rect.left) / rect.width)) * 360) % 360
153370
+ });
153371
+ },
153372
+ onPointerMove: (e2) => {
153373
+ if (e2.buttons === 0) return;
153374
+ const rect = e2.currentTarget.getBoundingClientRect();
153375
+ updateFromHsv({
153376
+ ...hsv,
153377
+ h: Math.round(Math.max(0, Math.min(1, (e2.clientX - rect.left) / rect.width)) * 360) % 360
153378
+ });
153379
+ },
153380
+ children: /* @__PURE__ */ jsxRuntime.jsx(
153381
+ "div",
153382
+ {
153383
+ className: "absolute top-1/2 w-4 h-4 -translate-y-1/2 -translate-x-1/2 rounded-full border-2 border-white shadow-md pointer-events-none ring-1 ring-black/30",
153384
+ style: { left: `${hsv.h / 360 * 100}%`, backgroundColor: hueColor }
153385
+ }
153386
+ )
153387
+ }
153388
+ )
153389
+ ] }),
153390
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
153391
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-[10px] text-neutral-400 font-medium", children: [
153392
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Opacity" }),
153393
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
153394
+ alpha,
153395
+ "%"
153396
+ ] })
153397
+ ] }),
153398
+ /* @__PURE__ */ jsxRuntime.jsxs(
153399
+ "div",
153400
+ {
153401
+ ref: alphaRef,
153402
+ className: "relative h-4 rounded-lg cursor-pointer select-none border border-neutral-800 overflow-hidden touch-none pqc-checkerboard",
153403
+ onPointerDown: (e2) => {
153404
+ e2.currentTarget.setPointerCapture(e2.pointerId);
153405
+ const rect = e2.currentTarget.getBoundingClientRect();
153406
+ const newAlpha = Math.round(Math.max(0, Math.min(1, (e2.clientX - rect.left) / rect.width)) * 100);
153407
+ setAlpha(newAlpha);
153408
+ applyColor(currentColor, currentCmyk, newAlpha);
153409
+ },
153410
+ onPointerMove: (e2) => {
153411
+ if (e2.buttons === 0) return;
153412
+ const rect = e2.currentTarget.getBoundingClientRect();
153413
+ const newAlpha = Math.round(Math.max(0, Math.min(1, (e2.clientX - rect.left) / rect.width)) * 100);
153414
+ setAlpha(newAlpha);
153415
+ applyColor(currentColor, currentCmyk, newAlpha);
153416
+ },
153417
+ children: [
153418
+ /* @__PURE__ */ jsxRuntime.jsx(
153419
+ "div",
153420
+ {
153421
+ className: "absolute inset-0",
153422
+ style: {
153423
+ background: `linear-gradient(to right, transparent, ${currentColor})`
153424
+ }
153425
+ }
153426
+ ),
153427
+ /* @__PURE__ */ jsxRuntime.jsx(
153428
+ "div",
153429
+ {
153430
+ className: "absolute top-1/2 w-4 h-4 -translate-y-1/2 -translate-x-1/2 rounded-full border-2 border-white shadow-md bg-white pointer-events-none ring-1 ring-black/30",
153431
+ style: { left: `${alpha}%` }
153432
+ }
153433
+ )
153434
+ ]
153435
+ }
153436
+ )
153437
+ ] }),
153438
+ colorMode === "HEX" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2 pt-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
153439
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1", children: [
153440
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute left-2.5 top-2 text-xs font-mono text-neutral-500", children: "#" }),
153441
+ /* @__PURE__ */ jsxRuntime.jsx(
153442
+ "input",
153443
+ {
153444
+ type: "text",
153445
+ maxLength: 7,
153446
+ value: currentColor.replace("#", "").toUpperCase(),
153447
+ onChange: (e2) => {
153448
+ const val = e2.target.value.replace(/[^0-9A-Fa-f]/g, "");
153449
+ if (val.length <= 6) {
153450
+ const newHex = `#${val}`;
153451
+ setCurrentColor(newHex);
153452
+ if (val.length === 6) {
153453
+ updateFromHex(newHex);
153454
+ }
153455
+ }
153456
+ },
153457
+ className: "w-full bg-neutral-900 border border-neutral-800 rounded-lg pl-6 pr-2 py-1.5 text-xs text-white font-mono uppercase focus:outline-none focus:border-[#02bc71]"
153458
+ }
153459
+ )
153460
+ ] }),
153461
+ /* @__PURE__ */ jsxRuntime.jsx(
153462
+ "button",
153463
+ {
153464
+ type: "button",
153465
+ onClick: handleCopyHex,
153466
+ className: "px-2.5 py-1.5 bg-neutral-900 hover:bg-neutral-800 border border-neutral-800 hover:border-neutral-700 rounded-lg text-[11px] text-neutral-300 font-medium flex items-center gap-1 transition-colors cursor-pointer",
153467
+ title: "Copy Hex Code",
153468
+ children: copied ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[#02bc71]", children: "Copied!" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Copy" })
153469
+ }
153470
+ ),
153471
+ typeof window !== "undefined" && "EyeDropper" in window && /* @__PURE__ */ jsxRuntime.jsx(
153472
+ "button",
153473
+ {
153474
+ type: "button",
153475
+ onClick: handleEyeDropper,
153476
+ className: "p-1.5 bg-neutral-900 hover:bg-neutral-800 border border-neutral-800 hover:border-neutral-700 rounded-lg text-neutral-300 hover:text-white transition-colors cursor-pointer",
153477
+ title: "Eyedropper Tool",
153478
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
153479
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m14 2 8 8-4 4-8-8 4-4Z" }),
153480
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m18 10-4-4" }),
153481
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6.5 13.5-3 3c-.8.8-.8 2.2 0 3 .8.8 2.2.8 3 0l3-3" })
153482
+ ] })
153483
+ }
153484
+ )
153485
+ ] }) }),
153486
+ colorMode === "CMYK" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5 pt-1", children: CMYK_CHANNELS.map(({ key, label, color: lColor, name }) => /* @__PURE__ */ jsxRuntime.jsx(
153487
+ ChannelRow,
153488
+ {
153489
+ label,
153490
+ labelColor: lColor,
153491
+ ariaLabel: name,
153492
+ value: currentCmyk[key],
153493
+ min: 0,
153494
+ max: 100,
153495
+ gradient: `linear-gradient(to right, ${cmykToHex(
153496
+ key === "c" ? 0 : currentCmyk.c,
153497
+ key === "m" ? 0 : currentCmyk.m,
153498
+ key === "y" ? 0 : currentCmyk.y,
153499
+ key === "k" ? 0 : currentCmyk.k
153500
+ )}, ${cmykToHex(
153501
+ key === "c" ? 100 : currentCmyk.c,
153502
+ key === "m" ? 100 : currentCmyk.m,
153503
+ key === "y" ? 100 : currentCmyk.y,
153504
+ key === "k" ? 100 : currentCmyk.k
153505
+ )})`,
153506
+ onChange: (val) => updateFromCmyk({ ...currentCmyk, [key]: val })
153507
+ },
153508
+ key
153509
+ )) })
153510
+ ] }),
153511
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
153512
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
153513
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-bold text-neutral-400 uppercase tracking-wider", children: "Document Colors" }),
153514
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-neutral-500", children: [
153515
+ documentColors.length,
153516
+ " colors"
153517
+ ] })
153518
+ ] }),
153519
+ documentColors.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3 text-center rounded-xl bg-neutral-900/40 border border-dashed border-neutral-800", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-neutral-500", children: "No colors detected in document yet" }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-2", children: documentColors.map((hex) => /* @__PURE__ */ jsxRuntime.jsx(
153520
+ "button",
153521
+ {
153522
+ onClick: () => updateFromHex(hex),
153523
+ className: `w-8 h-8 rounded-lg border transition-all cursor-pointer hover:scale-105 shadow-xs relative ${currentColor.toLowerCase() === hex.toLowerCase() ? "border-white ring-2 ring-[#02bc71]" : "border-neutral-700/80"}`,
153524
+ style: { backgroundColor: hex },
153525
+ title: hex.toUpperCase(),
153526
+ children: currentColor.toLowerCase() === hex.toLowerCase() && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 flex items-center justify-center text-xs font-bold mix-blend-difference text-white", children: "✓" })
153527
+ },
153528
+ hex
153529
+ )) })
153530
+ ] }),
153531
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
153532
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-bold text-neutral-400 uppercase tracking-wider block mb-2", children: "Default Colors" }),
153533
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-6 gap-2", children: DEFAULT_PRESET_COLORS.map((hex) => /* @__PURE__ */ jsxRuntime.jsx(
153534
+ "button",
153535
+ {
153536
+ type: "button",
153537
+ onClick: () => updateFromHex(hex),
153538
+ className: `w-full aspect-square rounded-lg border transition-all cursor-pointer hover:scale-105 shadow-xs relative ${currentColor.toLowerCase() === hex.toLowerCase() ? "border-white ring-2 ring-[#02bc71]" : "border-neutral-800"}`,
153539
+ style: { backgroundColor: hex },
153540
+ title: hex,
153541
+ children: currentColor.toLowerCase() === hex.toLowerCase() && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 flex items-center justify-center text-xs font-bold mix-blend-difference text-white", children: "✓" })
153542
+ },
153543
+ hex
153544
+ )) })
153545
+ ] }),
153546
+ isLoadingColors ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-4 text-xs text-neutral-500", children: "Loading color library..." }) : Array.from(groupedDbColors.entries()).map(([groupName, colors]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
153547
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
153548
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-bold text-neutral-400 uppercase tracking-wider", children: groupName }),
153549
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-neutral-500", children: colors.length })
153550
+ ] }),
153551
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-6 gap-2", children: colors.map((c2) => /* @__PURE__ */ jsxRuntime.jsx(
153552
+ "button",
153553
+ {
153554
+ onClick: () => updateFromHex(c2.hexCode),
153555
+ className: `w-full aspect-square rounded-lg border transition-all cursor-pointer hover:scale-105 shadow-xs relative group ${currentColor.toLowerCase() === c2.hexCode.toLowerCase() ? "border-white ring-2 ring-[#02bc71]" : "border-neutral-800"}`,
153556
+ style: { backgroundColor: c2.hexCode },
153557
+ title: `${c2.colorName} (${c2.hexCode})`,
153558
+ children: currentColor.toLowerCase() === c2.hexCode.toLowerCase() && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 flex items-center justify-center text-xs font-bold mix-blend-difference text-white", children: "✓" })
153559
+ },
153560
+ c2._id || c2.hexCode
153561
+ )) })
153562
+ ] }, groupName))
153563
+ ] })
153564
+ ] });
153565
+ }
153566
+ const ColorPickerIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
153567
+ "svg",
153568
+ {
153569
+ xmlns: "http://www.w3.org/2000/svg",
153570
+ viewBox: "0 0 512 512",
153571
+ width: "80",
153572
+ height: "80",
153573
+ fill: "currentColor",
153574
+ "aria-hidden": "true",
153575
+ style: { mixBlendMode: "difference" },
153576
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M430.1 347.9c-6.6-6.1-16.3-7.6-24.6-9-11.5-1.9-15.9-4-22.6-10-14.3-12.7-14.3-31.1 0-43.8l30.3-26.9c46.4-41 46.4-108.2 0-149.2-34.2-30.1-80.1-45-127.8-45-55.7 0-113.9 20.3-158.8 60.1-83.5 73.8-83.5 194.7 0 268.5 41.5 36.7 97.5 55 152.9 55.4h1.7c55.4 0 110-17.9 148.8-52.4 14.4-12.7 12-36.6.1-47.7zM120 216c0-17.7 14.3-32 32-32s32 14.3 32 32-14.3 32-32 32-32-14.3-32-32zm40 126c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm64-161c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm72 219c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48zm24-208c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z" })
153577
+ }
153578
+ );
152570
153579
  const DEFAULT_SECTIONS = [
152571
153580
  {
152572
153581
  id: "templates",
@@ -152628,6 +153637,13 @@ ${elements2.join("\n")}
152628
153637
  Tab: /* @__PURE__ */ jsxRuntime.jsx(QRCodeIcon, {}),
152629
153638
  Panel: /* @__PURE__ */ jsxRuntime.jsx(BarcodePanel, {})
152630
153639
  },
153640
+ {
153641
+ id: "color",
153642
+ name: "Color",
153643
+ Tab: /* @__PURE__ */ jsxRuntime.jsx(ColorPickerIcon, {}),
153644
+ Panel: /* @__PURE__ */ jsxRuntime.jsx(ColorPanel, {}),
153645
+ visibleInList: false
153646
+ },
152631
153647
  {
152632
153648
  id: "effects",
152633
153649
  name: "Effects",
@@ -152821,6 +153837,18 @@ ${elements2.join("\n")}
152821
153837
  }
152822
153838
  );
152823
153839
  }
153840
+ const STROKE_WIDTH_OPTIONS = [0, 1, 2, 3, 4, 5];
153841
+ const getStrokeDashArray = (style) => {
153842
+ switch (style) {
153843
+ case "Dashed":
153844
+ return [10, 5];
153845
+ case "Dotted":
153846
+ return [2, 4];
153847
+ case "Line":
153848
+ default:
153849
+ return null;
153850
+ }
153851
+ };
152824
153852
  function TextFontSize({
152825
153853
  value,
152826
153854
  onChange
@@ -152862,6 +153890,10 @@ ${elements2.join("\n")}
152862
153890
  const [charSpacing, setCharSpacing] = React.useState(0);
152863
153891
  const [align, setAlign] = React.useState("left");
152864
153892
  const [showTextPopup, setShowTextPopup] = React.useState(false);
153893
+ const [showStrokeControls, setShowStrokeControls] = React.useState(false);
153894
+ const [strokeColor, setStrokeColor] = React.useState("#000000");
153895
+ const [strokeSize, setStrokeSize] = React.useState(0);
153896
+ const [strokeStyle, setStrokeStyle] = React.useState("Line");
152865
153897
  const text = React.useMemo(() => {
152866
153898
  if (type !== "textbox") return null;
152867
153899
  return isTextbox(objects[0]) ? objects[0] : null;
@@ -152872,6 +153904,11 @@ ${elements2.join("\n")}
152872
153904
  setLineHeight2((text.lineHeight ?? 1) * 100);
152873
153905
  setCharSpacing(text.charSpacing ?? 0);
152874
153906
  setAlign(text.textAlign ?? "left");
153907
+ const dashArray = text.strokeDashArray;
153908
+ const currentStrokeStyle = Array.isArray(dashArray) && dashArray.length === 2 && dashArray[0] === 2 && dashArray[1] === 4 ? "Dotted" : Array.isArray(dashArray) && dashArray.length === 2 && dashArray[0] === 10 && dashArray[1] === 5 ? "Dashed" : "Line";
153909
+ setStrokeColor(typeof text.stroke === "string" && text.stroke ? text.stroke : "#000000");
153910
+ setStrokeSize(Math.max(0, Number(text.strokeWidth ?? 0)));
153911
+ setStrokeStyle(currentStrokeStyle);
152875
153912
  }, [text]);
152876
153913
  const updateText = React.useCallback(
152877
153914
  (props) => {
@@ -152884,6 +153921,24 @@ ${elements2.join("\n")}
152884
153921
  },
152885
153922
  [text]
152886
153923
  );
153924
+ const applyTextStroke = React.useCallback(
153925
+ (nextColor, nextSize, nextStyle) => {
153926
+ if (!text) return;
153927
+ const canvas2 = text.canvas;
153928
+ if (!canvas2) return;
153929
+ const normalizedSize = Number.isFinite(nextSize) ? Math.min(5, Math.max(0, Math.round(nextSize))) : 0;
153930
+ text.set({
153931
+ stroke: normalizedSize > 0 ? nextColor : null,
153932
+ strokeWidth: normalizedSize,
153933
+ strokeDashArray: normalizedSize === 0 ? null : getStrokeDashArray(nextStyle),
153934
+ strokeUniform: true,
153935
+ paintFirst: "stroke"
153936
+ });
153937
+ canvas2.requestRenderAll();
153938
+ useHistoryStore.getState().saveState();
153939
+ },
153940
+ [text]
153941
+ );
152887
153942
  const applyTextCase = (mode2) => {
152888
153943
  if (!text?.text) return;
152889
153944
  const anyText = text;
@@ -152965,7 +154020,9 @@ ${elements2.join("\n")}
152965
154020
  /* @__PURE__ */ jsxRuntime.jsx(
152966
154021
  "button",
152967
154022
  {
152968
- className: "cursor-pointer p-1 hover:bg-neutral-700 rounded",
154023
+ type: "button",
154024
+ "aria-expanded": showTextPopup,
154025
+ className: `cursor-pointer rounded p-1 transition-colors duration-150 ${showTextPopup ? "bg-neutral-700 text-white ring-1 ring-neutral-600 shadow-sm" : "hover:bg-neutral-700"}`,
152969
154026
  onClick: () => setShowTextPopup((prev) => !prev),
152970
154027
  children: /* @__PURE__ */ jsxRuntime.jsx(
152971
154028
  "svg",
@@ -152984,10 +154041,9 @@ ${elements2.join("\n")}
152984
154041
  showTextPopup && /* @__PURE__ */ jsxRuntime.jsxs(
152985
154042
  "div",
152986
154043
  {
152987
- className: "fixed md:absolute z-[9999] md:z-50 bg-neutral-900 text-white shadow-xl rounded-lg p-4 top-[112px] md:top-[40px] left-1/2 md:left-auto md:right-0 -translate-x-1/2 md:-translate-x-0",
154044
+ className: "fixed md:absolute z-[9999] md:z-50 bg-[#1d1d1d] text-white shadow-[0_12px_30px_rgba(0,0,0,0.35)] rounded-lg p-4 top-[112px] md:top-[40px] left-1/2 md:left-auto md:right-0 -translate-x-1/2 md:-translate-x-0 border border-neutral-700",
152988
154045
  style: {
152989
- width: "230px",
152990
- border: "1px solid #333"
154046
+ width: "230px"
152991
154047
  },
152992
154048
  children: [
152993
154049
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between mb-2", children: [
@@ -153076,7 +154132,75 @@ ${elements2.join("\n")}
153076
154132
  },
153077
154133
  children: "Aa"
153078
154134
  }
153079
- )
154135
+ ),
154136
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
154137
+ /* @__PURE__ */ jsxRuntime.jsx(
154138
+ "button",
154139
+ {
154140
+ type: "button",
154141
+ onClick: () => setShowStrokeControls((prev) => !prev),
154142
+ className: `text-left text-[15px] font-semibold ${text?.fontWeight === "bold" ? "text-blue-400" : ""} transition hover:text-blue-400`,
154143
+ children: "Stroke"
154144
+ }
154145
+ ),
154146
+ showStrokeControls && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute left-0 top-full z-50 mt-2 flex items-center gap-3 rounded-md border border-neutral-700 bg-[#1d1d1d] px-3 py-2 text-white shadow-[0_12px_30px_rgba(0,0,0,0.35)]", children: [
154147
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 text-[13px] font-medium text-white", children: [
154148
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Color" }),
154149
+ /* @__PURE__ */ jsxRuntime.jsx(
154150
+ "input",
154151
+ {
154152
+ type: "color",
154153
+ value: strokeColor,
154154
+ onChange: (e2) => {
154155
+ const nextColor = e2.target.value;
154156
+ setStrokeColor(nextColor);
154157
+ applyTextStroke(nextColor, strokeSize, strokeStyle);
154158
+ },
154159
+ className: "h-8 w-10 cursor-pointer rounded border border-neutral-600 bg-transparent p-0",
154160
+ "aria-label": "Text stroke color"
154161
+ }
154162
+ )
154163
+ ] }),
154164
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 text-[13px] font-medium text-white", children: [
154165
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Size" }),
154166
+ /* @__PURE__ */ jsxRuntime.jsx(
154167
+ "select",
154168
+ {
154169
+ value: String(strokeSize),
154170
+ onChange: (e2) => {
154171
+ const nextSize = Math.min(5, Number(e2.target.value));
154172
+ setStrokeSize(nextSize);
154173
+ applyTextStroke(strokeColor, nextSize, strokeStyle);
154174
+ },
154175
+ className: "h-8 min-w-[72px] rounded border border-neutral-600 bg-neutral-800 px-2 text-[13px] text-white outline-none",
154176
+ "aria-label": "Text stroke size",
154177
+ children: STROKE_WIDTH_OPTIONS.map((size) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: String(size), className: "bg-neutral-800 text-white", children: size }, size))
154178
+ }
154179
+ )
154180
+ ] }),
154181
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 text-[13px] font-medium text-white", children: [
154182
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Style" }),
154183
+ /* @__PURE__ */ jsxRuntime.jsxs(
154184
+ "select",
154185
+ {
154186
+ value: strokeStyle,
154187
+ onChange: (e2) => {
154188
+ const nextStyle = e2.target.value;
154189
+ setStrokeStyle(nextStyle);
154190
+ applyTextStroke(strokeColor, strokeSize, nextStyle);
154191
+ },
154192
+ className: "h-8 min-w-[90px] rounded border border-neutral-600 bg-neutral-800 px-2 text-[13px] text-white outline-none",
154193
+ "aria-label": "Text stroke style",
154194
+ children: [
154195
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "Dotted", className: "bg-neutral-800 text-white", children: "Dotted" }),
154196
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "Line", className: "bg-neutral-800 text-white", children: "Line" }),
154197
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "Dashed", className: "bg-neutral-800 text-white", children: "Dashed" })
154198
+ ]
154199
+ }
154200
+ )
154201
+ ] })
154202
+ ] })
154203
+ ] })
153080
154204
  ] }) });
153081
154205
  }
153082
154206
  function CropIcon() {
@@ -153194,7 +154318,7 @@ ${elements2.join("\n")}
153194
154318
  stroke: "#2196F3",
153195
154319
  strokeWidth: 2,
153196
154320
  cornerColor: "#ffffff",
153197
- cornerStrokeColor: "#202020",
154321
+ cornerStrokeColor: "#2196F3",
153198
154322
  cornerSize: 12,
153199
154323
  cornerStyle: "circle",
153200
154324
  transparentCorners: false,
@@ -153530,7 +154654,7 @@ ${elements2.join("\n")}
153530
154654
  /* @__PURE__ */ jsxRuntime.jsxs(
153531
154655
  "button",
153532
154656
  {
153533
- className: "hover:text-blue-400 flex",
154657
+ className: "hover:text-emerald-400 flex items-center gap-1.5 cursor-pointer",
153534
154658
  onClick: () => setActivePanel("effects"),
153535
154659
  children: [
153536
154660
  /* @__PURE__ */ jsxRuntime.jsx(EffectIcon, {}),
@@ -153557,7 +154681,7 @@ ${elements2.join("\n")}
153557
154681
  /* @__PURE__ */ jsxRuntime.jsx(
153558
154682
  "button",
153559
154683
  {
153560
- className: "hover:text-blue-400",
154684
+ className: "hover:text-emerald-400 cursor-pointer",
153561
154685
  onClick: () => setActivePanel("animate"),
153562
154686
  children: "Animate"
153563
154687
  }
@@ -153670,7 +154794,7 @@ ${elements2.join("\n")}
153670
154794
  const duplicate = async () => {
153671
154795
  const active = canvas2.getActiveObject();
153672
154796
  if (!active) return;
153673
- if (active.type === "activeselection") {
154797
+ if (active.type?.toLowerCase() === "activeselection") {
153674
154798
  const selection = active;
153675
154799
  const originals = selection.getObjects().filter((o2) => o2.name !== "workarea");
153676
154800
  if (!originals.length) return;
@@ -153797,7 +154921,7 @@ ${elements2.join("\n")}
153797
154921
  const getTargets = () => {
153798
154922
  const active = canvas2.getActiveObject();
153799
154923
  if (!active) return [];
153800
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
154924
+ const objects = active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
153801
154925
  return objects.filter((o2) => o2.name !== "workarea");
153802
154926
  };
153803
154927
  const handleFlip = (flip) => {
@@ -153942,7 +155066,7 @@ ${elements2.join("\n")}
153942
155066
  const getTargets = () => {
153943
155067
  const active = canvas2?.getActiveObject();
153944
155068
  if (!active) return [];
153945
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
155069
+ const objects = active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
153946
155070
  return objects.filter((o2) => o2.name !== "workarea");
153947
155071
  };
153948
155072
  React.useEffect(() => {
@@ -154035,12 +155159,12 @@ ${elements2.join("\n")}
154035
155159
  const getTargets = () => {
154036
155160
  const active = getActive();
154037
155161
  if (!active) return [];
154038
- return active.type === "activeselection" ? active.getObjects() : [active];
155162
+ return active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
154039
155163
  };
154040
155164
  const readOpacityFromSelection = () => {
154041
155165
  const active = getActive();
154042
155166
  if (!active) return 100;
154043
- if (active.type === "activeselection") {
155167
+ if (active.type?.toLowerCase() === "activeselection") {
154044
155168
  const objects = active.getObjects();
154045
155169
  return Math.round((objects[0]?.opacity ?? 1) * 100);
154046
155170
  }
@@ -154160,7 +155284,7 @@ ${elements2.join("\n")}
154160
155284
  const getTargets = () => {
154161
155285
  const active = getActive();
154162
155286
  if (!active) return [];
154163
- return active.type === "activeselection" ? active.getObjects() : [active];
155287
+ return active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
154164
155288
  };
154165
155289
  const getWorkarea2 = () => canvas2.getObjects().find((o2) => o2.name === "workarea");
154166
155290
  const normalizeOrigin = (obj) => {
@@ -154208,7 +155332,7 @@ ${elements2.join("\n")}
154208
155332
  const handleAlign = (align) => {
154209
155333
  const active = getActive();
154210
155334
  if (!active) return;
154211
- if (active.type === "activeselection") {
155335
+ if (active.type?.toLowerCase() === "activeselection") {
154212
155336
  alignInsideSelection(align);
154213
155337
  } else {
154214
155338
  applyAlign(align);
@@ -154375,7 +155499,7 @@ ${elements2.join("\n")}
154375
155499
  const getTargets = () => {
154376
155500
  const active = canvas2?.getActiveObject();
154377
155501
  if (!active) return [];
154378
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
155502
+ const objects = active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
154379
155503
  return objects.filter((o2) => o2.name !== "workarea");
154380
155504
  };
154381
155505
  const deleteObjects = () => {
@@ -154449,7 +155573,7 @@ ${elements2.join("\n")}
154449
155573
  const getTargets = () => {
154450
155574
  const active = canvas2.getActiveObject();
154451
155575
  if (!active) return [];
154452
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
155576
+ const objects = active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
154453
155577
  return objects.filter((o2) => o2.name !== "workarea");
154454
155578
  };
154455
155579
  const handleRotate = (dir, step = 45) => {
@@ -154538,13 +155662,19 @@ ${elements2.join("\n")}
154538
155662
  const handleGroup = () => {
154539
155663
  if (!canvas2) return;
154540
155664
  const active = canvas2.getActiveObject();
154541
- if (!active || active.type !== "activeselection") return;
155665
+ if (!active || active.type?.toLowerCase() !== "activeselection") return;
154542
155666
  const selection = active;
154543
155667
  const objects2 = [...selection.getObjects()];
155668
+ const canvasObjects = canvas2.getObjects();
155669
+ const minZIndex = Math.min(...objects2.map((o2) => canvasObjects.indexOf(o2)));
154544
155670
  canvas2.discardActiveObject();
154545
155671
  objects2.forEach((obj) => canvas2.remove(obj));
154546
155672
  const group = new Ur(objects2);
154547
- canvas2.add(group);
155673
+ if (minZIndex !== -1 && minZIndex < canvas2.getObjects().length) {
155674
+ canvas2.insertAt(minZIndex, group);
155675
+ } else {
155676
+ canvas2.add(group);
155677
+ }
154548
155678
  canvas2.setActiveObject(group);
154549
155679
  canvas2.requestRenderAll();
154550
155680
  useSelectionStore.getState().setSelection([group]);
@@ -154593,6 +155723,14 @@ ${elements2.join("\n")}
154593
155723
  </g>
154594
155724
  </svg>`;
154595
155725
  let rotateImg = null;
155726
+ const SMALL_OBJECT_THRESHOLD = 40;
155727
+ const SMALL_CONTROL_SIZE = 6;
155728
+ const NORMAL_CONTROL_SIZE = 10;
155729
+ const ROTATION_GAP = 6;
155730
+ const isTextObject = (obj) => obj.type === "textbox" || obj.type === "i-text" || obj.type === "text";
155731
+ function normalizeAngle(angle = 0) {
155732
+ return (Math.round(angle) % 360 + 360) % 360;
155733
+ }
154596
155734
  function renderRotateIcon(ctx, left, top, fabricObject) {
154597
155735
  if (!rotateImg) {
154598
155736
  rotateImg = new Image();
@@ -154601,7 +155739,7 @@ ${elements2.join("\n")}
154601
155739
  fabricObject.canvas?.requestRenderAll();
154602
155740
  };
154603
155741
  }
154604
- const size = 24;
155742
+ const size = fabricObject.__rotationControlSize || 24;
154605
155743
  ctx.save();
154606
155744
  ctx.translate(left, top);
154607
155745
  if (rotateImg.complete && rotateImg.naturalHeight !== 0) {
@@ -154609,11 +155747,13 @@ ${elements2.join("\n")}
154609
155747
  }
154610
155748
  ctx.restore();
154611
155749
  }
154612
- function createRotateControl() {
155750
+ function createRotateControl(size = 24, offsetY = -30) {
154613
155751
  return new ai({
154614
155752
  x: 0,
154615
155753
  y: -0.5,
154616
- offsetY: -30,
155754
+ offsetY,
155755
+ sizeX: size,
155756
+ sizeY: size,
154617
155757
  cursorStyle: "crosshair",
154618
155758
  actionHandler: Fa.rotationWithSnapping,
154619
155759
  actionName: "rotate",
@@ -154621,23 +155761,147 @@ ${elements2.join("\n")}
154621
155761
  withConnection: true
154622
155762
  });
154623
155763
  }
155764
+ function renderRotationAngle(ctx, left, top, _styleOverride, fabricObject) {
155765
+ if (!fabricObject.__showDegree) {
155766
+ return;
155767
+ }
155768
+ const angle = normalizeAngle(fabricObject.angle);
155769
+ const label = `${angle}°`;
155770
+ const paddingX = 8;
155771
+ const height = 22;
155772
+ const radius = 6;
155773
+ ctx.save();
155774
+ ctx.font = "bold 12px sans-serif";
155775
+ ctx.textAlign = "center";
155776
+ ctx.textBaseline = "middle";
155777
+ const textWidth = ctx.measureText(label).width;
155778
+ const width = Math.max(34, textWidth + paddingX * 2);
155779
+ const x2 = left - width / 2;
155780
+ const y2 = top - height / 2;
155781
+ ctx.beginPath();
155782
+ ctx.fillStyle = "rgba(15, 23, 42, 0.9)";
155783
+ ctx.strokeStyle = "#02bc71";
155784
+ ctx.lineWidth = 1.5;
155785
+ if (typeof ctx.roundRect === "function") {
155786
+ ctx.roundRect(x2, y2, width, height, radius);
155787
+ } else {
155788
+ ctx.rect(x2, y2, width, height);
155789
+ }
155790
+ ctx.fill();
155791
+ ctx.stroke();
155792
+ ctx.fillStyle = "#ffffff";
155793
+ ctx.fillText(label, left, top + 0.5);
155794
+ ctx.restore();
155795
+ }
155796
+ function createRotationAngleControl() {
155797
+ return new ai({
155798
+ x: -0.5,
155799
+ y: 0,
155800
+ offsetX: -28,
155801
+ offsetY: 0,
155802
+ sizeX: 1,
155803
+ sizeY: 1,
155804
+ cursorStyle: "default",
155805
+ render: renderRotationAngle
155806
+ });
155807
+ }
155808
+ const isImageObject = (obj) => obj.type === "image" || obj.type === "FabricImage" || typeof obj.isType === "function" && (obj.isType("image") || obj.isType("FabricImage"));
154624
155809
  function configureObjectControls(obj) {
155810
+ const zoom = obj.canvas ? obj.canvas.getZoom() : 1;
155811
+ const scaledWidth = obj.getScaledWidth() * zoom;
155812
+ const scaledHeight = obj.getScaledHeight() * zoom;
155813
+ const renderedSize = Math.min(scaledWidth, scaledHeight);
155814
+ const isSmall = renderedSize < SMALL_OBJECT_THRESHOLD;
155815
+ const cornerSize = isSmall ? Math.min(SMALL_CONTROL_SIZE, Math.max(4, renderedSize * 0.3)) : NORMAL_CONTROL_SIZE;
155816
+ const rotationSize = isSmall ? Math.min(16, Math.max(12, renderedSize * 0.4)) : 24;
155817
+ const rotationOffset = -(rotationSize / 2 + ROTATION_GAP);
154625
155818
  obj.set({
154626
155819
  transparentCorners: false,
154627
155820
  cornerColor: "#ffffff",
154628
155821
  cornerStrokeColor: "#02bc71",
154629
155822
  borderColor: "#02bc71",
154630
- cornerSize: 10,
155823
+ cornerSize,
154631
155824
  cornerStyle: "rect",
154632
155825
  borderScaleFactor: 1
154633
155826
  });
154634
- obj.controls.mtr = createRotateControl();
155827
+ obj.__rotationControlSize = rotationSize;
155828
+ obj.controls.mtr = createRotateControl(rotationSize, rotationOffset);
155829
+ const textObject = isTextObject(obj);
155830
+ if (!obj.controls.mb) {
155831
+ obj.controls.mb = new ai({
155832
+ x: 0,
155833
+ y: 0.5,
155834
+ cursorStyleHandler: Fa.scaleSkewCursorStyleHandler,
155835
+ actionHandler: textObject ? Fa.changeWidth : Fa.scalingYOrSkewingX,
155836
+ actionName: "resizing"
155837
+ });
155838
+ }
155839
+ const rotationControl = createRotationAngleControl();
155840
+ obj.controls.rotationAngle = rotationControl;
155841
+ delete obj.controls.bmc;
155842
+ delete obj.controls.textAngle;
155843
+ if (isSmall) {
155844
+ obj.setControlsVisibility({
155845
+ mtr: true,
155846
+ br: true,
155847
+ tl: false,
155848
+ tr: false,
155849
+ bl: false,
155850
+ mb: false,
155851
+ ml: false,
155852
+ mr: false,
155853
+ mt: false,
155854
+ bmc: false,
155855
+ rotationAngle: true
155856
+ });
155857
+ return;
155858
+ }
155859
+ if (isImageObject(obj)) {
155860
+ obj.setControlsVisibility({
155861
+ mtr: true,
155862
+ tl: true,
155863
+ tr: true,
155864
+ bl: true,
155865
+ br: true,
155866
+ ml: false,
155867
+ mr: false,
155868
+ mb: true,
155869
+ mt: false,
155870
+ bmc: false,
155871
+ rotationAngle: true
155872
+ });
155873
+ return;
155874
+ }
155875
+ if (!obj.controls.ml) {
155876
+ obj.controls.ml = new ai({
155877
+ x: -0.5,
155878
+ y: 0,
155879
+ cursorStyleHandler: Fa.scaleSkewCursorStyleHandler,
155880
+ actionHandler: textObject ? Fa.changeWidth : Fa.scalingXOrSkewingY,
155881
+ actionName: "resizing"
155882
+ });
155883
+ }
155884
+ if (!obj.controls.mr) {
155885
+ obj.controls.mr = new ai({
155886
+ x: 0.5,
155887
+ y: 0,
155888
+ cursorStyleHandler: Fa.scaleSkewCursorStyleHandler,
155889
+ actionHandler: textObject ? Fa.changeWidth : Fa.scalingXOrSkewingY,
155890
+ actionName: "resizing"
155891
+ });
155892
+ }
154635
155893
  obj.setControlsVisibility({
154636
155894
  mtr: true,
154637
- ml: false,
154638
- mr: false,
155895
+ tl: true,
155896
+ tr: true,
155897
+ bl: true,
155898
+ br: true,
155899
+ ml: true,
155900
+ mr: true,
155901
+ mb: true,
154639
155902
  mt: false,
154640
- mb: false
155903
+ bmc: false,
155904
+ rotationAngle: true
154641
155905
  });
154642
155906
  }
154643
155907
  function initializeRotateImage() {
@@ -154654,20 +155918,38 @@ ${elements2.join("\n")}
154654
155918
  const handleUngroup = () => {
154655
155919
  if (!canvas2) return;
154656
155920
  const active = canvas2.getActiveObject();
154657
- if (!active || active.type !== "group") return;
155921
+ if (!active || active.type?.toLowerCase() !== "group") return;
154658
155922
  const group = active;
154659
- const objects2 = [...group.getObjects()];
154660
- group.remove(...objects2);
155923
+ const groupIndex = canvas2.getObjects().indexOf(group);
155924
+ const childObjects = [...group.getObjects()];
155925
+ canvas2.discardActiveObject();
155926
+ group.remove(...childObjects);
154661
155927
  canvas2.remove(group);
154662
- objects2.forEach((obj) => canvas2.add(obj));
154663
- const sel = new Qo(objects2, { canvas: canvas2 });
154664
- configureObjectControls(sel);
154665
- canvas2.setActiveObject(sel);
154666
- canvas2.renderAll();
154667
- useSelectionStore.getState().setSelection(objects2);
155928
+ if (typeof group.dispose === "function") {
155929
+ group.dispose();
155930
+ }
155931
+ childObjects.forEach((obj, idx2) => {
155932
+ obj.set({
155933
+ selectable: true,
155934
+ evented: true,
155935
+ hasControls: true,
155936
+ hasBorders: true
155937
+ });
155938
+ obj.setCoords();
155939
+ if (groupIndex !== -1) {
155940
+ canvas2.insertAt(groupIndex + idx2, obj);
155941
+ } else {
155942
+ canvas2.add(obj);
155943
+ }
155944
+ configureObjectControls(obj);
155945
+ });
155946
+ canvas2.discardActiveObject();
155947
+ useSelectionStore.getState().clearSelection();
155948
+ canvas2.requestRenderAll();
154668
155949
  saveState();
154669
155950
  };
154670
- if (selectionType !== "multiple" || objects.length !== 1) return null;
155951
+ const isGroupSelected = selectionType === "group" || objects.length === 1 && objects[0]?.type?.toLowerCase() === "group" && objects[0]?.name !== "qr" && objects[0]?.name !== "barcode";
155952
+ if (!isGroupSelected) return null;
154671
155953
  return /* @__PURE__ */ jsxRuntime.jsx(
154672
155954
  "button",
154673
155955
  {
@@ -155013,6 +156295,9 @@ ${elements2.join("\n")}
155013
156295
  initialZoom = 1;
155014
156296
  }
155015
156297
  const initialScale = baseScale * initialZoom;
156298
+ const isTextTemplate = productData?.isTextTemplate || productData?.hideGuidelines || typeof window !== "undefined" && ["text", "texttemplate", "text-template"].includes(
156299
+ (new URLSearchParams(window.location.search).get("designType") || new URLSearchParams(window.location.search).get("designtype") || new URLSearchParams(window.location.search).get("type") || "")?.toLowerCase()
156300
+ );
155016
156301
  const workarea = new jr({
155017
156302
  width: fullWidth,
155018
156303
  height: fullHeight,
@@ -155028,6 +156313,8 @@ ${elements2.join("\n")}
155028
156313
  workarea.__printDPI = printDPI;
155029
156314
  workarea.__baseScale = baseScale;
155030
156315
  workarea.__initialZoom = initialZoom;
156316
+ workarea.__hideGuidelines = isTextTemplate || productData?.hideGuidelines;
156317
+ workarea.__isTextTemplate = isTextTemplate;
155031
156318
  workarea.__bleedArea = productData?.bleedArea ?? 0.125;
155032
156319
  workarea.__safetyArea = productData?.safetyArea ?? 0.125;
155033
156320
  workarea.__trimArea = productData?.trimArea ?? 0;
@@ -155075,27 +156362,63 @@ ${elements2.join("\n")}
155075
156362
  clearSelection();
155076
156363
  return;
155077
156364
  }
155078
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
156365
+ const isMulti = active.type?.toLowerCase() === "activeselection";
156366
+ const objects = isMulti ? active.getObjects() : [active];
155079
156367
  setSelection(objects);
155080
156368
  };
155081
- const configureSelectionControls = (e2) => {
156369
+ const configureSelectionControls = () => {
155082
156370
  const activeObject = canvas2.getActiveObject();
155083
156371
  if (!activeObject) return;
155084
156372
  configureObjectControls(activeObject);
155085
156373
  canvas2.requestRenderAll();
155086
156374
  };
155087
156375
  canvas2.on("selection:created", (e2) => {
156376
+ if (e2.selected) {
156377
+ e2.selected.forEach((o2) => {
156378
+ o2.__showDegree = false;
156379
+ });
156380
+ }
155088
156381
  updateSelection();
155089
156382
  configureSelectionControls();
155090
156383
  });
155091
156384
  canvas2.on("selection:updated", (e2) => {
156385
+ if (e2.selected) {
156386
+ e2.selected.forEach((o2) => {
156387
+ o2.__showDegree = false;
156388
+ });
156389
+ }
155092
156390
  updateSelection();
155093
156391
  configureSelectionControls();
155094
156392
  });
155095
- canvas2.on("selection:cleared", clearSelection);
156393
+ canvas2.on("object:scaling", configureSelectionControls);
156394
+ canvas2.on("object:rotating", (e2) => {
156395
+ if (e2.target) {
156396
+ e2.target.__showDegree = true;
156397
+ }
156398
+ configureSelectionControls();
156399
+ });
156400
+ const hideDegree = (e2) => {
156401
+ if (e2?.target) {
156402
+ e2.target.__showDegree = false;
156403
+ }
156404
+ canvas2.getObjects().forEach((o2) => {
156405
+ o2.__showDegree = false;
156406
+ });
156407
+ configureSelectionControls();
156408
+ };
156409
+ canvas2.on("object:modified", hideDegree);
156410
+ canvas2.on("mouse:up", hideDegree);
156411
+ canvas2.on("selection:cleared", () => {
156412
+ hideDegree();
156413
+ clearSelection();
156414
+ });
155096
156415
  return () => {
155097
156416
  canvas2.off("selection:created");
155098
156417
  canvas2.off("selection:updated");
156418
+ canvas2.off("object:scaling", configureSelectionControls);
156419
+ canvas2.off("object:rotating");
156420
+ canvas2.off("object:modified", hideDegree);
156421
+ canvas2.off("mouse:up", hideDegree);
155099
156422
  canvas2.off("selection:cleared");
155100
156423
  };
155101
156424
  }
@@ -155140,6 +156463,23 @@ ${elements2.join("\n")}
155140
156463
  canvas2.off("object:rotating", syncBorder);
155141
156464
  };
155142
156465
  }
156466
+ const CUSTOM_PROPERTIES = [
156467
+ "name",
156468
+ "id",
156469
+ "selectable",
156470
+ "evented",
156471
+ "hasControls",
156472
+ "lockMovementX",
156473
+ "lockMovementY",
156474
+ "__printWidth",
156475
+ "__printHeight",
156476
+ "__printDPI",
156477
+ "__baseScale",
156478
+ "__initialZoom",
156479
+ "__borderRect",
156480
+ "__rotationControlSize",
156481
+ "__showDegree"
156482
+ ];
155143
156483
  async function switchCanvasPage(canvas2, currentPage, targetPage, pageJSON, clearSelection, containerRef) {
155144
156484
  if (targetPage === currentPage) return;
155145
156485
  const historyStore = useHistoryStore.getState();
@@ -155148,8 +156488,7 @@ ${elements2.join("\n")}
155148
156488
  const storePages = useEditorStore.getState().pages || {};
155149
156489
  if (storePages.front && !pageJSON.front) pageJSON.front = storePages.front;
155150
156490
  if (storePages.back && !pageJSON.back) pageJSON.back = storePages.back;
155151
- const currentObject = canvas2.toObject(["name"]);
155152
- pageJSON[currentPage] = currentObject;
156491
+ pageJSON[currentPage] = canvas2.toObject(CUSTOM_PROPERTIES);
155153
156492
  useEditorStore.getState().setPages({ ...pageJSON });
155154
156493
  clearSelection();
155155
156494
  historyStore.setPage(targetPage);
@@ -155171,6 +156510,16 @@ ${elements2.join("\n")}
155171
156510
  } finally {
155172
156511
  useLoaderStore.getState().stopLoading();
155173
156512
  }
156513
+ const currentVt = canvas2.viewportTransform ? [...canvas2.viewportTransform] : [canvas2.getZoom(), 0, 0, canvas2.getZoom(), 0, 0];
156514
+ canvas2.setViewportTransform(currentVt);
156515
+ const workarea = canvas2.getObjects().find((obj) => obj.name === "workarea");
156516
+ if (workarea) {
156517
+ createOverlays(canvas2, workarea);
156518
+ bringOverlaysToFront(canvas2);
156519
+ }
156520
+ canvas2.requestRenderAll();
156521
+ canvas2.fire("after:render");
156522
+ useHistoryStore.setState({ isRestoring: false });
155174
156523
  }
155175
156524
  const MIN_ZOOM = 0.09;
155176
156525
  const MAX_ZOOM = 5;
@@ -155183,53 +156532,28 @@ ${elements2.join("\n")}
155183
156532
  function getWorkarea(canvas2) {
155184
156533
  return canvas2.getObjects().find((o2) => o2.name === "workarea") ?? null;
155185
156534
  }
155186
- function normalizeViewportPan(canvas2) {
155187
- const vt2 = canvas2.viewportTransform;
155188
- const zoom = vt2[0];
155189
- const panX = vt2[4];
155190
- const panY = vt2[5];
155191
- if (Math.abs(panX) < 0.01 && Math.abs(panY) < 0.01) return;
155192
- const dx = panX / zoom;
155193
- const dy = panY / zoom;
155194
- canvas2.getObjects().forEach((obj) => {
155195
- obj.set({
155196
- left: (obj.left ?? 0) + dx,
155197
- top: (obj.top ?? 0) + dy
155198
- });
155199
- obj.setCoords();
155200
- });
155201
- canvas2.setViewportTransform([zoom, 0, 0, zoom, 0, 0]);
155202
- }
155203
156535
  function doZoom(canvas2, newZoom, pivot) {
155204
156536
  const clamped = clamp(newZoom, MIN_ZOOM, MAX_ZOOM);
155205
156537
  canvas2.zoomToPoint(pivot, clamped);
155206
- normalizeViewportPan(canvas2);
155207
156538
  const workarea = getWorkarea(canvas2);
155208
156539
  if (workarea) createOverlays(canvas2, workarea);
156540
+ const activeObject = canvas2.getActiveObject();
156541
+ if (activeObject) configureObjectControls(activeObject);
155209
156542
  canvas2.requestRenderAll();
155210
156543
  return clamped;
155211
156544
  }
155212
156545
  function doReset(canvas2, setZoom) {
155213
- const center = new ot(canvas2.getWidth() / 2, canvas2.getHeight() / 2);
155214
- canvas2.zoomToPoint(center, 1);
155215
- normalizeViewportPan(canvas2);
155216
156546
  const workarea = getWorkarea(canvas2);
156547
+ const initialZoom = workarea ? workarea.__initialZoom || 1 : 1;
156548
+ const center = new ot(canvas2.getWidth() / 2, canvas2.getHeight() / 2);
156549
+ canvas2.zoomToPoint(center, initialZoom);
155217
156550
  if (workarea) {
155218
- const waW = workarea.getScaledWidth();
155219
- const waH = workarea.getScaledHeight();
155220
- const dx = canvas2.getWidth() / 2 - ((workarea.left ?? 0) + waW / 2);
155221
- const dy = canvas2.getHeight() / 2 - ((workarea.top ?? 0) + waH / 2);
155222
- canvas2.getObjects().forEach((obj) => {
155223
- obj.set({
155224
- left: (obj.left ?? 0) + dx,
155225
- top: (obj.top ?? 0) + dy
155226
- });
155227
- obj.setCoords();
155228
- });
155229
- createOverlays(canvas2, getWorkarea(canvas2));
156551
+ createOverlays(canvas2, workarea);
155230
156552
  }
156553
+ const activeObject = canvas2.getActiveObject();
156554
+ if (activeObject) configureObjectControls(activeObject);
155231
156555
  canvas2.requestRenderAll();
155232
- setZoom(1);
156556
+ setZoom(initialZoom);
155233
156557
  }
155234
156558
  function useCanvasZoom(canvas2) {
155235
156559
  const [zoom, setZoom] = React.useState(1);
@@ -155556,7 +156880,7 @@ ${elements2.join("\n")}
155556
156880
  left: (clipboardRef.current.left ?? 0) + OFFSET,
155557
156881
  top: (clipboardRef.current.top ?? 0) + OFFSET
155558
156882
  });
155559
- if (clonedObj.type === "activeselection") {
156883
+ if (clonedObj.type?.toLowerCase() === "activeselection") {
155560
156884
  clonedObj.canvas = canvas2;
155561
156885
  clonedObj.forEachObject((obj) => {
155562
156886
  canvas2.add(obj);
@@ -155612,8 +156936,11 @@ ${elements2.join("\n")}
155612
156936
  if (fabricRef.current) return;
155613
156937
  let isMounted = true;
155614
156938
  const setupCanvasWithProduct = async () => {
156939
+ const designType = (getQueryParam("designType") || getQueryParam("designtype") || getQueryParam("type"))?.toLowerCase();
156940
+ const isTextDesign = designType === "text";
155615
156941
  const productId = getQueryParam("id") || getQueryParam("productId");
155616
- if (!productId) {
156942
+ const templateId = getQueryParam("templateId") || (isTextDesign ? getQueryParam("id") : null);
156943
+ if (!productId && !isTextDesign) {
155617
156944
  useProductStore.setState({
155618
156945
  isNotFound: true,
155619
156946
  error: "Product ID is missing from URL query parameters (e.g. ?id=...)."
@@ -155622,9 +156949,26 @@ ${elements2.join("\n")}
155622
156949
  }
155623
156950
  useLoaderStore.getState().startLoading();
155624
156951
  try {
155625
- const product = await useProductStore.getState().fetchProduct(productId);
155626
- if (!product || !product._id) {
155627
- return;
156952
+ let product = null;
156953
+ if (isTextDesign) {
156954
+ product = {
156955
+ _id: "text-template-product",
156956
+ bleedArea: 0,
156957
+ trimArea: 0,
156958
+ safetyArea: 0,
156959
+ hideGuidelines: true,
156960
+ isTextTemplate: true,
156961
+ optionsMap: {
156962
+ size: '2" x 2"',
156963
+ productorientation: "Horizontal"
156964
+ }
156965
+ };
156966
+ useProductStore.setState({ productData: product, isNotFound: false, error: null });
156967
+ } else if (productId) {
156968
+ product = await useProductStore.getState().fetchProduct(productId);
156969
+ if (!product || !product._id) {
156970
+ return;
156971
+ }
155628
156972
  }
155629
156973
  if (!isMounted || !canvasRef.current || !containerRef.current || fabricRef.current) return;
155630
156974
  initializeRotateImage();
@@ -155642,11 +156986,20 @@ ${elements2.join("\n")}
155642
156986
  clearSelection
155643
156987
  );
155644
156988
  const cleanupObjectAdded = setupObjectAddedHandler(canvas2);
155645
- const twoSided = product?.optionsMap?.printing === "4/4";
155646
- const templateId = getQueryParam("templateId");
155647
- let frontData = null;
155648
- let backData = null;
155649
- if (templateId) {
156989
+ if (isTextDesign && templateId) {
156990
+ const item = await ShapeService.getTextTemplateById(templateId);
156991
+ if (item?.jsonData) {
156992
+ const parsed = typeof item.jsonData === "string" ? JSON.parse(item.jsonData) : item.jsonData;
156993
+ pageJSON.current.front = parsed;
156994
+ useHistoryStore.setState({ isRestoring: true });
156995
+ await loadPageToCanvas(canvas2, parsed);
156996
+ useHistoryStore.setState({ isRestoring: false });
156997
+ useHistoryStore.getState().saveState();
156998
+ }
156999
+ } else if (templateId) {
157000
+ const twoSided = product?.optionsMap?.printing === "4/4";
157001
+ let frontData = null;
157002
+ let backData = null;
155650
157003
  const res = await TemplateDesignService.getTemplateDesignById(templateId);
155651
157004
  const tpl = res?.data;
155652
157005
  if (tpl?.pages && Array.isArray(tpl.pages)) {
@@ -155655,16 +157008,16 @@ ${elements2.join("\n")}
155655
157008
  if (frontPage?.designData) frontData = frontPage.designData;
155656
157009
  if (backPage?.designData) backData = backPage.designData;
155657
157010
  }
155658
- }
155659
- if (frontData) {
155660
- pageJSON.current.front = frontData;
155661
- useHistoryStore.setState({ isRestoring: true });
155662
- await loadPageToCanvas(canvas2, frontData);
155663
- useHistoryStore.setState({ isRestoring: false });
155664
- useHistoryStore.getState().saveState();
155665
- }
155666
- if (twoSided && backData) {
155667
- pageJSON.current.back = backData;
157011
+ if (frontData) {
157012
+ pageJSON.current.front = frontData;
157013
+ useHistoryStore.setState({ isRestoring: true });
157014
+ await loadPageToCanvas(canvas2, frontData);
157015
+ useHistoryStore.setState({ isRestoring: false });
157016
+ useHistoryStore.getState().saveState();
157017
+ }
157018
+ if (twoSided && backData) {
157019
+ pageJSON.current.back = backData;
157020
+ }
155668
157021
  }
155669
157022
  useEditorStore.getState().setPages({ ...pageJSON.current });
155670
157023
  return () => {