@elixpo/lixsketch 5.6.5 → 5.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/dist/mcp/index.js +5 -1
  2. package/dist/mcp/index.js.map +2 -2
  3. package/dist/mcp/stdio.js +5 -1
  4. package/dist/mcp/stdio.js.map +2 -2
  5. package/dist/react/{AIRenderer-SRF7LU5K.js → AIRenderer-R5M2CIDO.js} +13 -6
  6. package/dist/react/AIRenderer-R5M2CIDO.js.map +7 -0
  7. package/dist/react/Circle-HMM5PKJ2.js +10 -0
  8. package/dist/react/{CopyPaste-YUDZ6DHY.js → CopyPaste-P6G35CXO.js} +11 -10
  9. package/dist/react/{CopyPaste-YUDZ6DHY.js.map → CopyPaste-P6G35CXO.js.map} +1 -1
  10. package/dist/react/{EventDispatcher-7LAK7GQ7.js → EventDispatcher-M5B4EWHJ.js} +216 -63
  11. package/dist/react/EventDispatcher-M5B4EWHJ.js.map +7 -0
  12. package/dist/react/Frame-QEUEYITT.js +11 -0
  13. package/dist/react/{FreehandStroke-U6GYZOM4.js → FreehandStroke-WXIJKD5I.js} +2 -2
  14. package/dist/react/{GraphEngine-MM6ALA2N.js → GraphEngine-XH3RPYO4.js} +9 -2
  15. package/dist/react/GraphEngine-XH3RPYO4.js.map +7 -0
  16. package/dist/react/IconShape-NHJE4YBY.js +10 -0
  17. package/dist/react/ImageShape-33VCMSDP.js +10 -0
  18. package/dist/react/Line-A6FE232Q.js +10 -0
  19. package/dist/react/{LixScriptParser-JYWJX375.js → LixScriptParser-YS3ZOTGS.js} +6 -2
  20. package/dist/react/LixScriptParser-YS3ZOTGS.js.map +7 -0
  21. package/dist/react/{MermaidFlowchartRenderer-PQEC4TMI.js → MermaidFlowchartRenderer-EL3Q2MNW.js} +6 -2
  22. package/dist/react/MermaidFlowchartRenderer-EL3Q2MNW.js.map +7 -0
  23. package/dist/react/MermaidSequenceParser-DBYXW23P.js +8 -0
  24. package/dist/react/{MermaidSequenceRenderer-KCUHO7UI.js → MermaidSequenceRenderer-K4KHHEVC.js} +10 -3
  25. package/dist/react/MermaidSequenceRenderer-K4KHHEVC.js.map +7 -0
  26. package/dist/react/{MermaidStructuredRenderer-W7UKN7BM.js → MermaidStructuredRenderer-6LN3WISP.js} +7 -2
  27. package/dist/react/MermaidStructuredRenderer-6LN3WISP.js.map +7 -0
  28. package/dist/react/Rectangle-4U4CD2ER.js +10 -0
  29. package/dist/react/{SceneSerializer-SG6SJN5X.js → SceneSerializer-JEYTYXZQ.js} +120 -46
  30. package/dist/react/SceneSerializer-JEYTYXZQ.js.map +7 -0
  31. package/dist/react/{Selection-2TSH3J6P.js → Selection-23MOLHEI.js} +4 -4
  32. package/dist/react/{SketchEngine-UD2XV2KT.js → SketchEngine-OPJON7TD.js} +26 -26
  33. package/dist/react/{UndoRedo-IN4SXDG6.js → UndoRedo-QVNRI5OB.js} +8 -2
  34. package/dist/react/{arrowTool-OYDWJGQK.js → arrowTool-VH3PJPDM.js} +3 -3
  35. package/dist/react/{chunk-LSHZYN77.js → chunk-2DZHO67H.js} +2 -2
  36. package/dist/react/{chunk-XOWNCYE4.js → chunk-5NHJJJBZ.js} +3 -3
  37. package/dist/react/chunk-6AIFXYV6.js +31 -0
  38. package/dist/react/chunk-6AIFXYV6.js.map +7 -0
  39. package/dist/react/{chunk-K5RY4EV7.js → chunk-6GPWSPJF.js} +9 -5
  40. package/dist/react/{chunk-K5RY4EV7.js.map → chunk-6GPWSPJF.js.map} +2 -2
  41. package/dist/react/{chunk-ASBC5G35.js → chunk-B6CYUZC4.js} +2 -2
  42. package/dist/react/{chunk-OP56XPWF.js → chunk-DNFPTVIP.js} +2 -2
  43. package/dist/react/{chunk-XYUFEIZR.js → chunk-DWEFPKDO.js} +2 -2
  44. package/dist/react/{chunk-GQUZPON4.js → chunk-F56XUE5S.js} +4 -4
  45. package/dist/react/{chunk-7CM5XQDN.js → chunk-G3DJQY65.js} +20 -36
  46. package/dist/react/chunk-G3DJQY65.js.map +7 -0
  47. package/dist/react/{chunk-LJR7EVW2.js → chunk-JEGLUNBC.js} +3 -3
  48. package/dist/react/{chunk-6GPEWDRB.js → chunk-M7GAB24F.js} +52 -6
  49. package/dist/react/chunk-M7GAB24F.js.map +7 -0
  50. package/dist/react/{chunk-VQVEPJSA.js → chunk-MHV5P6TS.js} +2 -1
  51. package/dist/react/chunk-MHV5P6TS.js.map +7 -0
  52. package/dist/react/{chunk-PXFPPO3R.js → chunk-NH3NVSWT.js} +18 -2
  53. package/dist/react/chunk-NH3NVSWT.js.map +7 -0
  54. package/dist/react/{chunk-XFL4R5ZF.js → chunk-OK4ROGMM.js} +16 -35
  55. package/dist/react/chunk-OK4ROGMM.js.map +7 -0
  56. package/dist/react/{chunk-APTOPAOY.js → chunk-SSFEN5CY.js} +3 -3
  57. package/dist/react/{chunk-RGMDWLRJ.js → chunk-UEL2G6D2.js} +3 -3
  58. package/dist/react/{chunk-M6TMN76K.js → chunk-ULPIHUVC.js} +3 -3
  59. package/dist/react/{chunk-FETTJLCV.js → chunk-VZPH7OAM.js} +14 -22
  60. package/dist/react/chunk-VZPH7OAM.js.map +7 -0
  61. package/dist/react/{chunk-TKXKJNTX.js → chunk-W7XUY4GR.js} +16 -19
  62. package/dist/react/chunk-W7XUY4GR.js.map +7 -0
  63. package/dist/react/{chunk-EOURRJ55.js → chunk-XSE4HQ2D.js} +2 -2
  64. package/dist/react/{chunk-K5BG5MHY.js → chunk-ZNZAICWY.js} +36 -60
  65. package/dist/react/chunk-ZNZAICWY.js.map +7 -0
  66. package/dist/react/{circleTool-LDKGZALF.js → circleTool-2UODOBCV.js} +4 -4
  67. package/dist/react/{codeTool-4YZO5W7J.js → codeTool-F4RV7SAI.js} +4 -4
  68. package/dist/react/{frameTool-MXNJEXQQ.js → frameTool-NTGA5BI6.js} +4 -4
  69. package/dist/react/{freehandTool-26D3FOOX.js → freehandTool-L55EUB5C.js} +4 -4
  70. package/dist/react/{iconTool-46O5VEUX.js → iconTool-KQQBLGR3.js} +4 -4
  71. package/dist/react/{imageTool-P2RWZRHO.js → imageTool-LDJ45AD7.js} +4 -4
  72. package/dist/react/index.js +148 -37
  73. package/dist/react/index.js.map +4 -4
  74. package/dist/react/{lineTool-ZMR27U5L.js → lineTool-CSAFD7PW.js} +4 -4
  75. package/dist/react/{rectangleTool-EJKK6UTX.js → rectangleTool-YPD2SIIZ.js} +4 -4
  76. package/dist/react/{textTool-S4XXZHZH.js → textTool-VQHMWEFJ.js} +5 -5
  77. package/package.json +5 -2
  78. package/src/core/AIRenderer.js +8 -2
  79. package/src/core/CanvasSpace.js +28 -0
  80. package/src/core/GraphRenderer.js +8 -3
  81. package/src/core/LixScriptParser.js +7 -1
  82. package/src/core/MermaidFlowchartRenderer.js +5 -1
  83. package/src/core/MermaidSequenceParser.js +1 -0
  84. package/src/core/MermaidSequenceRenderer.js +8 -3
  85. package/src/core/MermaidStructuredRenderer.js +6 -1
  86. package/src/core/SceneSerializer.js +121 -38
  87. package/src/core/Selection.js +12 -2
  88. package/src/core/UndoRedo.js +59 -5
  89. package/src/react/components/AppMenu.jsx +20 -19
  90. package/src/react/components/canvas/CanvasRulers.jsx +8 -13
  91. package/src/react/components/canvas/SVGCanvas.jsx +2 -1
  92. package/src/react/components/canvas/rulerUnits.js +28 -0
  93. package/src/react/components/sidebars/PaintBucketSidebar.jsx +1 -1
  94. package/src/react/store/useSketchStore.js +24 -3
  95. package/src/react/store/useUIStore.js +24 -1
  96. package/src/react/utils/canvasBackgrounds.js +29 -0
  97. package/src/shapes/Circle.js +12 -19
  98. package/src/shapes/Frame.js +38 -69
  99. package/src/shapes/FreehandStroke.js +24 -1
  100. package/src/shapes/Rectangle.js +10 -22
  101. package/src/tools/circleTool.js +3 -27
  102. package/src/tools/paintBucketTool.js +72 -15
  103. package/src/tools/rectangleTool.js +7 -23
  104. package/src/tools/shapeRecognition.js +270 -0
  105. package/src/tools/shapeRecognitionTool.js +36 -183
  106. package/dist/react/AIRenderer-SRF7LU5K.js.map +0 -7
  107. package/dist/react/Circle-6POZCUZD.js +0 -9
  108. package/dist/react/EventDispatcher-7LAK7GQ7.js.map +0 -7
  109. package/dist/react/Frame-7D2MXCFF.js +0 -11
  110. package/dist/react/GraphEngine-MM6ALA2N.js.map +0 -7
  111. package/dist/react/IconShape-ZADX7Y33.js +0 -10
  112. package/dist/react/ImageShape-UJQN2HI7.js +0 -10
  113. package/dist/react/Line-APEULMHE.js +0 -10
  114. package/dist/react/LixScriptParser-JYWJX375.js.map +0 -7
  115. package/dist/react/MermaidFlowchartRenderer-PQEC4TMI.js.map +0 -7
  116. package/dist/react/MermaidSequenceParser-UKG2NSZY.js +0 -8
  117. package/dist/react/MermaidSequenceRenderer-KCUHO7UI.js.map +0 -7
  118. package/dist/react/MermaidStructuredRenderer-W7UKN7BM.js.map +0 -7
  119. package/dist/react/Rectangle-S7XUKWYD.js +0 -9
  120. package/dist/react/SceneSerializer-SG6SJN5X.js.map +0 -7
  121. package/dist/react/chunk-6GPEWDRB.js.map +0 -7
  122. package/dist/react/chunk-7CM5XQDN.js.map +0 -7
  123. package/dist/react/chunk-FETTJLCV.js.map +0 -7
  124. package/dist/react/chunk-K5BG5MHY.js.map +0 -7
  125. package/dist/react/chunk-PXFPPO3R.js.map +0 -7
  126. package/dist/react/chunk-TKXKJNTX.js.map +0 -7
  127. package/dist/react/chunk-VQVEPJSA.js.map +0 -7
  128. package/dist/react/chunk-XFL4R5ZF.js.map +0 -7
  129. /package/dist/react/{Circle-6POZCUZD.js.map → Circle-HMM5PKJ2.js.map} +0 -0
  130. /package/dist/react/{Frame-7D2MXCFF.js.map → Frame-QEUEYITT.js.map} +0 -0
  131. /package/dist/react/{FreehandStroke-U6GYZOM4.js.map → FreehandStroke-WXIJKD5I.js.map} +0 -0
  132. /package/dist/react/{IconShape-ZADX7Y33.js.map → IconShape-NHJE4YBY.js.map} +0 -0
  133. /package/dist/react/{ImageShape-UJQN2HI7.js.map → ImageShape-33VCMSDP.js.map} +0 -0
  134. /package/dist/react/{Line-APEULMHE.js.map → Line-A6FE232Q.js.map} +0 -0
  135. /package/dist/react/{MermaidSequenceParser-UKG2NSZY.js.map → MermaidSequenceParser-DBYXW23P.js.map} +0 -0
  136. /package/dist/react/{Rectangle-S7XUKWYD.js.map → Rectangle-4U4CD2ER.js.map} +0 -0
  137. /package/dist/react/{Selection-2TSH3J6P.js.map → Selection-23MOLHEI.js.map} +0 -0
  138. /package/dist/react/{SketchEngine-UD2XV2KT.js.map → SketchEngine-OPJON7TD.js.map} +0 -0
  139. /package/dist/react/{UndoRedo-IN4SXDG6.js.map → UndoRedo-QVNRI5OB.js.map} +0 -0
  140. /package/dist/react/{arrowTool-OYDWJGQK.js.map → arrowTool-VH3PJPDM.js.map} +0 -0
  141. /package/dist/react/{chunk-LSHZYN77.js.map → chunk-2DZHO67H.js.map} +0 -0
  142. /package/dist/react/{chunk-XOWNCYE4.js.map → chunk-5NHJJJBZ.js.map} +0 -0
  143. /package/dist/react/{chunk-ASBC5G35.js.map → chunk-B6CYUZC4.js.map} +0 -0
  144. /package/dist/react/{chunk-OP56XPWF.js.map → chunk-DNFPTVIP.js.map} +0 -0
  145. /package/dist/react/{chunk-XYUFEIZR.js.map → chunk-DWEFPKDO.js.map} +0 -0
  146. /package/dist/react/{chunk-GQUZPON4.js.map → chunk-F56XUE5S.js.map} +0 -0
  147. /package/dist/react/{chunk-LJR7EVW2.js.map → chunk-JEGLUNBC.js.map} +0 -0
  148. /package/dist/react/{chunk-APTOPAOY.js.map → chunk-SSFEN5CY.js.map} +0 -0
  149. /package/dist/react/{chunk-RGMDWLRJ.js.map → chunk-UEL2G6D2.js.map} +0 -0
  150. /package/dist/react/{chunk-M6TMN76K.js.map → chunk-ULPIHUVC.js.map} +0 -0
  151. /package/dist/react/{chunk-EOURRJ55.js.map → chunk-XSE4HQ2D.js.map} +0 -0
  152. /package/dist/react/{circleTool-LDKGZALF.js.map → circleTool-2UODOBCV.js.map} +0 -0
  153. /package/dist/react/{codeTool-4YZO5W7J.js.map → codeTool-F4RV7SAI.js.map} +0 -0
  154. /package/dist/react/{frameTool-MXNJEXQQ.js.map → frameTool-NTGA5BI6.js.map} +0 -0
  155. /package/dist/react/{freehandTool-26D3FOOX.js.map → freehandTool-L55EUB5C.js.map} +0 -0
  156. /package/dist/react/{iconTool-46O5VEUX.js.map → iconTool-KQQBLGR3.js.map} +0 -0
  157. /package/dist/react/{imageTool-P2RWZRHO.js.map → imageTool-LDJ45AD7.js.map} +0 -0
  158. /package/dist/react/{lineTool-ZMR27U5L.js.map → lineTool-CSAFD7PW.js.map} +0 -0
  159. /package/dist/react/{rectangleTool-EJKK6UTX.js.map → rectangleTool-YPD2SIIZ.js.map} +0 -0
  160. /package/dist/react/{textTool-S4XXZHZH.js.map → textTool-VQHMWEFJ.js.map} +0 -0
@@ -16,8 +16,41 @@ import { useCallback as useCallback17, useEffect as useEffect20, useRef as useRe
16
16
 
17
17
  // src/react/store/useSketchStore.js
18
18
  import { create } from "zustand";
19
+
20
+ // src/react/utils/canvasBackgrounds.js
21
+ var CANVAS_BACKGROUNDS = {
22
+ light: [
23
+ { color: "#FBE4DB", label: "menu.canvasBg.white" },
24
+ { color: "#F6DBC0", label: "menu.canvasBg.cream" },
25
+ { color: "#F5EED2", label: "menu.canvasBg.paper" },
26
+ { color: "#F5F2ED", label: "menu.canvasBg.skyTint" },
27
+ { color: "#F5DABF", label: "menu.canvasBg.sageTint" }
28
+ ],
29
+ dark: [
30
+ { color: "#000000", label: "menu.canvasBg.black" },
31
+ { color: "#161718", label: "menu.canvasBg.darkGray" },
32
+ { color: "#15111f", label: "menu.canvasBg.blueBlack" },
33
+ { color: "#181605", label: "menu.canvasBg.darkYellow" },
34
+ { color: "#1B1615", label: "menu.canvasBg.darkBrown" }
35
+ ]
36
+ };
37
+ var DEFAULT_CANVAS_BACKGROUNDS = { dark: "#15111f", light: "#F5F2ED" };
38
+ var canvasBackgroundStorageKey = (theme) => `lixsketch-canvas-background-${theme}`;
39
+ function readCanvasBackground(theme, storage) {
40
+ const resolved = theme === "light" ? "light" : "dark";
41
+ try {
42
+ const stored = storage?.getItem(canvasBackgroundStorageKey(resolved));
43
+ const match = CANVAS_BACKGROUNDS[resolved].find(({ color }) => color.toLowerCase() === stored?.toLowerCase());
44
+ if (match) return match.color;
45
+ } catch {
46
+ }
47
+ return DEFAULT_CANVAS_BACKGROUNDS[resolved];
48
+ }
49
+
50
+ // src/react/store/useSketchStore.js
19
51
  var GRID_STORAGE_KEY = "lixsketch-grid-enabled";
20
52
  var RULER_STORAGE_KEY = "lixsketch-rulers-enabled";
53
+ var RULER_UNIT_STORAGE_KEY = "lixsketch-ruler-unit";
21
54
  var TOOLS = {
22
55
  SELECT: "select",
23
56
  PAN: "pan",
@@ -174,13 +207,25 @@ var useSketchStore = create((set, get) => ({
174
207
  setIsPanning: (v) => set({ isPanning: v }),
175
208
  setPanStart: (p) => set({ panStart: p }),
176
209
  // --- Canvas background ---
177
- canvasBackground: "var(--lixsketch-bg, #15111f)",
178
- setCanvasBackground: (color) => {
179
- set({ canvasBackground: color });
210
+ canvasBackground: DEFAULT_CANVAS_BACKGROUNDS.dark,
211
+ canvasBackgrounds: { ...DEFAULT_CANVAS_BACKGROUNDS },
212
+ setCanvasBackground: (color, theme = "dark") => {
213
+ const resolved = theme === "light" ? "light" : "dark";
214
+ set((state) => ({ canvasBackground: color, canvasBackgrounds: { ...state.canvasBackgrounds, [resolved]: color } }));
180
215
  if (typeof window !== "undefined") {
216
+ try {
217
+ localStorage.setItem(canvasBackgroundStorageKey(resolved), color);
218
+ } catch {
219
+ }
181
220
  requestAnimationFrame(() => window.__adaptCanvasContrast?.(color));
182
221
  }
183
222
  },
223
+ restoreCanvasBackground: (theme) => {
224
+ const resolved = theme === "light" ? "light" : "dark";
225
+ const color = readCanvasBackground(resolved, typeof window === "undefined" ? null : localStorage);
226
+ set((state) => ({ canvasBackground: color, canvasBackgrounds: { ...state.canvasBackgrounds, [resolved]: color } }));
227
+ if (typeof window !== "undefined") requestAnimationFrame(() => window.__adaptCanvasContrast?.(color));
228
+ },
184
229
  // --- Grid ---
185
230
  gridEnabled: false,
186
231
  toggleGrid: () => set((s) => {
@@ -205,6 +250,7 @@ var useSketchStore = create((set, get) => ({
205
250
  },
206
251
  // --- Rulers ---
207
252
  rulersEnabled: false,
253
+ rulerUnit: "px",
208
254
  toggleRulers: () => set((s) => {
209
255
  const rulersEnabled = !s.rulersEnabled;
210
256
  if (typeof window !== "undefined") {
@@ -215,6 +261,16 @@ var useSketchStore = create((set, get) => ({
215
261
  }
216
262
  return { rulersEnabled };
217
263
  }),
264
+ setRulerUnit: (unit) => {
265
+ const rulerUnit = ["px", "cm", "in"].includes(unit) ? unit : "px";
266
+ if (typeof window !== "undefined") {
267
+ try {
268
+ localStorage.setItem(RULER_UNIT_STORAGE_KEY, rulerUnit);
269
+ } catch {
270
+ }
271
+ }
272
+ set({ rulerUnit });
273
+ },
218
274
  hydrateRulers: () => {
219
275
  if (typeof window === "undefined") return;
220
276
  try {
@@ -222,6 +278,8 @@ var useSketchStore = create((set, get) => ({
222
278
  if (saved === "true" || saved === "false") {
223
279
  set({ rulersEnabled: saved === "true" });
224
280
  }
281
+ const savedUnit = localStorage.getItem(RULER_UNIT_STORAGE_KEY);
282
+ if (["px", "cm", "in"].includes(savedUnit)) set({ rulerUnit: savedUnit });
225
283
  } catch {
226
284
  }
227
285
  },
@@ -371,6 +429,20 @@ function invertShapeColors(prevResolved, nextResolved) {
371
429
  }
372
430
  }
373
431
  }
432
+ function readStoredTheme() {
433
+ if (typeof window === "undefined") return "dark";
434
+ try {
435
+ const theme = localStorage.getItem("theme");
436
+ if (theme && ["dark", "light", "system"].includes(theme)) return theme;
437
+ } catch {
438
+ }
439
+ try {
440
+ const prefs = JSON.parse(localStorage.getItem("lix_ui_prefs") || "{}");
441
+ return ["dark", "light", "system"].includes(prefs.theme) ? prefs.theme : "dark";
442
+ } catch {
443
+ return "dark";
444
+ }
445
+ }
374
446
  function applyTheme(theme) {
375
447
  const body = document.body;
376
448
  if (!body) return;
@@ -452,13 +524,22 @@ var useUIStore = create2((set, get) => ({
452
524
  canvasLoadingMessage: "Loading canvas...",
453
525
  setCanvasLoading: (loading, message) => set({ canvasLoading: loading, canvasLoadingMessage: message || "Loading canvas..." }),
454
526
  // --- Theme ---
455
- theme: "dark",
527
+ theme: readStoredTheme(),
456
528
  setTheme: (newTheme) => {
457
529
  const prev = get().theme;
458
530
  const resolve = (t) => t === "system" ? window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : t;
459
531
  invertShapeColors(resolve(prev), resolve(newTheme));
460
532
  applyTheme(newTheme);
461
533
  set({ theme: newTheme });
534
+ useSketchStore_default.getState().restoreCanvasBackground(resolve(newTheme));
535
+ if (typeof window !== "undefined") {
536
+ try {
537
+ localStorage.setItem("theme", newTheme);
538
+ const prefs = JSON.parse(localStorage.getItem("lix_ui_prefs") || "{}");
539
+ localStorage.setItem("lix_ui_prefs", JSON.stringify({ ...prefs, theme: newTheme }));
540
+ } catch {
541
+ }
542
+ }
462
543
  },
463
544
  // --- Language / i18n ---
464
545
  language: "en",
@@ -544,7 +625,7 @@ function useSketchEngine(svgRef, ready = true) {
544
625
  setZoom: (zoom) => useSketchStore_default.setState({ zoom }),
545
626
  getState: () => useSketchStore_default.getState()
546
627
  };
547
- const { SketchEngine } = await import("./SketchEngine-UD2XV2KT.js");
628
+ const { SketchEngine } = await import("./SketchEngine-OPJON7TD.js");
548
629
  if (cancelled) return;
549
630
  const engine = new SketchEngine(svgRef.current);
550
631
  await engine.init();
@@ -581,6 +662,34 @@ function useSketchEngine(svgRef, ready = true) {
581
662
 
582
663
  // src/react/components/canvas/CanvasRulers.jsx
583
664
  import { useEffect as useEffect2, useMemo, useState } from "react";
665
+
666
+ // src/react/components/canvas/rulerUnits.js
667
+ var RULER_UNITS = ["px", "cm", "in"];
668
+ var PIXELS_PER_UNIT = { px: 1, cm: 96 / 2.54, in: 96 };
669
+ function normalizeRulerUnit(unit) {
670
+ return RULER_UNITS.includes(unit) ? unit : "px";
671
+ }
672
+ function convertRulerValue(pixelValue, unit = "px") {
673
+ return pixelValue / PIXELS_PER_UNIT[normalizeRulerUnit(unit)];
674
+ }
675
+ function formatRulerValue(pixelValue, unit = "px", suffix = false) {
676
+ const normalized = normalizeRulerUnit(unit);
677
+ const converted = convertRulerValue(pixelValue, normalized);
678
+ let label;
679
+ if (normalized === "px") {
680
+ const rounded = Math.round(converted);
681
+ const absolute = Math.abs(rounded);
682
+ if (absolute >= 1e6) label = `${Number((rounded / 1e6).toFixed(1))}m`;
683
+ else if (absolute >= 1e4) label = `${Number((rounded / 1e3).toFixed(1))}k`;
684
+ else label = String(rounded);
685
+ } else {
686
+ const precision = Math.abs(converted) < 10 ? 2 : Math.abs(converted) < 100 ? 1 : 0;
687
+ label = String(Number(converted.toFixed(precision)));
688
+ }
689
+ return suffix ? `${label} ${normalized}` : label;
690
+ }
691
+
692
+ // src/react/components/canvas/CanvasRulers.jsx
584
693
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
585
694
  var GRID_SIZE = 20;
586
695
  var MIN_LABEL_GAP = 52;
@@ -594,13 +703,6 @@ function getRulerStep(zoom) {
594
703
  }
595
704
  return GRID_SIZE;
596
705
  }
597
- function formatDimension(value) {
598
- const rounded = Math.round(value);
599
- const absolute = Math.abs(rounded);
600
- if (absolute >= 1e6) return `${Number((rounded / 1e6).toFixed(1))}m`;
601
- if (absolute >= 1e4) return `${Number((rounded / 1e3).toFixed(1))}k`;
602
- return String(rounded);
603
- }
604
706
  function buildTicks(start, extent, zoom, step) {
605
707
  const first = Math.floor(start / step) * step;
606
708
  const last = start + extent / zoom;
@@ -610,7 +712,8 @@ function buildTicks(start, extent, zoom, step) {
610
712
  }
611
713
  return ticks;
612
714
  }
613
- function CanvasRulers({ enabled, svgRef }) {
715
+ function CanvasRulers({ enabled, svgRef, unit = "px" }) {
716
+ const rulerUnit = normalizeRulerUnit(unit);
614
717
  const [viewport, setViewport] = useState({ x: 0, y: 0, zoom: 1, width: 0, height: 0, rulerLeft: 64, rulerTop: 48 });
615
718
  const [pointer, setPointer] = useState({ x: 0, y: 0, visible: false });
616
719
  useEffect2(() => {
@@ -680,14 +783,15 @@ function CanvasRulers({ enabled, svgRef }) {
680
783
  const pointerWorldY = viewport.y + pointer.y / viewport.zoom;
681
784
  return /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute inset-0 z-[900] select-none text-text-muted", "aria-hidden": "true", children: [
682
785
  /* @__PURE__ */ jsxs("div", { className: "absolute right-0 overflow-hidden border-b border-border-light bg-surface/95 shadow-sm backdrop-blur-sm", style: { height: RULER_SIZE, left: horizontalStart, top: viewport.rulerTop }, children: [
683
- horizontalTicks.map(({ value, position }) => /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 h-2 border-l border-text-dim", style: { left: position - horizontalStart }, children: /* @__PURE__ */ jsx("span", { className: "absolute bottom-2 left-1 font-mono text-[9px] leading-none tabular-nums", children: formatDimension(value) }) }, value)),
684
- pointerInDrawingArea && /* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 border-l border-accent", style: { left: pointer.x - horizontalStart }, children: /* @__PURE__ */ jsx("span", { className: "absolute left-1 top-1 rounded bg-accent px-1 font-mono text-[9px] leading-4 text-white shadow-sm", children: formatDimension(pointerWorldX) }) })
786
+ horizontalTicks.map(({ value, position }) => /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 h-2 border-l border-text-dim", style: { left: position - horizontalStart }, children: /* @__PURE__ */ jsx("span", { className: "absolute bottom-2 left-1 font-mono text-[9px] leading-none tabular-nums", children: formatRulerValue(value, rulerUnit) }) }, value)),
787
+ pointerInDrawingArea && /* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 border-l border-accent", style: { left: pointer.x - horizontalStart }, children: /* @__PURE__ */ jsx("span", { className: "absolute left-1 top-1 rounded bg-accent px-1 font-mono text-[9px] leading-4 text-white shadow-sm", children: formatRulerValue(pointerWorldX, rulerUnit, true) }) })
685
788
  ] }),
686
789
  /* @__PURE__ */ jsxs("div", { className: "absolute bottom-0 overflow-hidden border-r border-border-light bg-surface/95 shadow-sm backdrop-blur-sm", style: { width: RULER_SIZE, left: viewport.rulerLeft, top: verticalStart }, children: [
687
- verticalTicks.map(({ value, position }) => /* @__PURE__ */ jsx("div", { className: "absolute right-0 w-2 border-t border-text-dim", style: { top: position - verticalStart }, children: /* @__PURE__ */ jsx("span", { className: "absolute right-2 top-1 font-mono text-[9px] leading-none tabular-nums", style: { transform: "rotate(-90deg)", transformOrigin: "top right" }, children: formatDimension(value) }) }, value)),
688
- pointerInDrawingArea && /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 border-t border-accent", style: { top: pointer.y - verticalStart }, children: /* @__PURE__ */ jsx("span", { className: "absolute right-1 top-1 rounded bg-accent px-1 font-mono text-[9px] leading-4 text-white shadow-sm", style: { transform: "rotate(-90deg)", transformOrigin: "top right" }, children: formatDimension(pointerWorldY) }) })
790
+ verticalTicks.map(({ value, position }) => /* @__PURE__ */ jsx("div", { className: "absolute right-0 w-2 border-t border-text-dim", style: { top: position - verticalStart }, children: /* @__PURE__ */ jsx("span", { className: "absolute right-2 top-1 font-mono text-[9px] leading-none tabular-nums", style: { transform: "rotate(-90deg)", transformOrigin: "top right" }, children: formatRulerValue(value, rulerUnit) }) }, value)),
791
+ pointerInDrawingArea && /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 border-t border-accent", style: { top: pointer.y - verticalStart }, children: /* @__PURE__ */ jsx("span", { className: "absolute right-1 top-1 rounded bg-accent px-1 font-mono text-[9px] leading-4 text-white shadow-sm", style: { transform: "rotate(-90deg)", transformOrigin: "top right" }, children: formatRulerValue(pointerWorldY, rulerUnit, true) }) })
689
792
  ] }),
690
793
  /* @__PURE__ */ jsxs("div", { className: "absolute border-b border-r border-border-light bg-accent/20", style: { width: RULER_SIZE, height: RULER_SIZE, left: viewport.rulerLeft, top: viewport.rulerTop }, children: [
794
+ /* @__PURE__ */ jsx("span", { className: "absolute bottom-0.5 left-1 font-mono text-[8px] uppercase text-accent", children: rulerUnit }),
691
795
  /* @__PURE__ */ jsx("span", { className: "absolute left-1/2 top-1/2 h-2.5 -translate-x-1/2 -translate-y-1/2 border-l border-accent" }),
692
796
  /* @__PURE__ */ jsx("span", { className: "absolute left-1/2 top-1/2 w-2.5 -translate-x-1/2 -translate-y-1/2 border-t border-accent" })
693
797
  ] }),
@@ -709,6 +813,7 @@ function SVGCanvas() {
709
813
  const gridEnabled = useSketchStore_default((s) => s.gridEnabled);
710
814
  const hydrateGrid = useSketchStore_default((s) => s.hydrateGrid);
711
815
  const rulersEnabled = useSketchStore_default((s) => s.rulersEnabled);
816
+ const rulerUnit = useSketchStore_default((s) => s.rulerUnit);
712
817
  const hydrateRulers = useSketchStore_default((s) => s.hydrateRulers);
713
818
  const getCursor = useSketchStore_default((s) => s.getCursor);
714
819
  const cursor = getCursor();
@@ -837,7 +942,7 @@ function SVGCanvas() {
837
942
  ]
838
943
  }
839
944
  ),
840
- /* @__PURE__ */ jsx2(CanvasRulers, { enabled: rulersEnabled, svgRef })
945
+ /* @__PURE__ */ jsx2(CanvasRulers, { enabled: rulersEnabled, svgRef, unit: rulerUnit })
841
946
  ] });
842
947
  }
843
948
 
@@ -1210,20 +1315,6 @@ function useTranslation() {
1210
1315
 
1211
1316
  // src/react/components/AppMenu.jsx
1212
1317
  import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1213
- var CANVAS_BACKGROUNDS_LIGHT = [
1214
- { color: "#ffffff", label: "menu.canvasBg.white" },
1215
- { color: "#fbf9fd", label: "menu.canvasBg.cream" },
1216
- { color: "#f5f3ed", label: "menu.canvasBg.paper" },
1217
- { color: "#f0f5fb", label: "menu.canvasBg.skyTint" },
1218
- { color: "#f0f5ef", label: "menu.canvasBg.sageTint" }
1219
- ];
1220
- var CANVAS_BACKGROUNDS_DARK = [
1221
- { color: "#000000", label: "menu.canvasBg.black" },
1222
- { color: "#161718", label: "menu.canvasBg.darkGray" },
1223
- { color: "#15111f", label: "menu.canvasBg.blueBlack" },
1224
- { color: "#181605", label: "menu.canvasBg.darkYellow" },
1225
- { color: "#1B1615", label: "menu.canvasBg.darkBrown" }
1226
- ];
1227
1318
  function DangerWarningDialog({ action, workspaceName, onCancel, onConfirm }) {
1228
1319
  useEffect5(() => {
1229
1320
  if (!action) return void 0;
@@ -1325,6 +1416,8 @@ function AppMenu() {
1325
1416
  const toggleGrid = useSketchStore_default((s) => s.toggleGrid);
1326
1417
  const rulersEnabled = useSketchStore_default((s) => s.rulersEnabled);
1327
1418
  const toggleRulers = useSketchStore_default((s) => s.toggleRulers);
1419
+ const rulerUnit = useSketchStore_default((s) => s.rulerUnit);
1420
+ const setRulerUnit = useSketchStore_default((s) => s.setRulerUnit);
1328
1421
  const viewMode = useSketchStore_default((s) => s.viewMode);
1329
1422
  const zenMode = useSketchStore_default((s) => s.zenMode);
1330
1423
  const toolLock = useSketchStore_default((s) => s.toolLock);
@@ -1493,7 +1586,25 @@ function AppMenu() {
1493
1586
  children: [
1494
1587
  /* @__PURE__ */ jsx5("option", { value: "en", children: "English" }),
1495
1588
  /* @__PURE__ */ jsx5("option", { value: "bg", children: "\u0411\u044A\u043B\u0433\u0430\u0440\u0441\u043A\u0438" }),
1496
- /* @__PURE__ */ jsx5("option", { value: "de", children: "Deutsch" })
1589
+ /* @__PURE__ */ jsx5("option", { value: "de", children: "Deutsch" }),
1590
+ /* @__PURE__ */ jsx5("option", { value: "hi", children: "\u0939\u093F\u0928\u094D\u0926\u0940" })
1591
+ ]
1592
+ }
1593
+ )
1594
+ ] }),
1595
+ /* @__PURE__ */ jsxs5("div", { className: "w-full flex items-center justify-between px-3 py-2 border-b border-border-light text-text-secondary text-[11px]", children: [
1596
+ /* @__PURE__ */ jsx5("span", { children: "Ruler unit" }),
1597
+ /* @__PURE__ */ jsxs5(
1598
+ "select",
1599
+ {
1600
+ "aria-label": "Ruler unit",
1601
+ className: "cursor-pointer rounded border border-border-light bg-surface-hover px-1 text-[10px] uppercase text-text-primary outline-none",
1602
+ value: rulerUnit,
1603
+ onChange: (event) => setRulerUnit(event.target.value),
1604
+ children: [
1605
+ /* @__PURE__ */ jsx5("option", { value: "px", children: "px" }),
1606
+ /* @__PURE__ */ jsx5("option", { value: "cm", children: "cm" }),
1607
+ /* @__PURE__ */ jsx5("option", { value: "in", children: "in" })
1497
1608
  ]
1498
1609
  }
1499
1610
  )
@@ -1633,10 +1744,10 @@ function AppMenu() {
1633
1744
  ] }),
1634
1745
  /* @__PURE__ */ jsxs5("div", { className: "px-3 py-2", children: [
1635
1746
  /* @__PURE__ */ jsx5("p", { className: "text-text-dim text-xs uppercase tracking-wider mb-2", children: t("menu.canvasBackground") }),
1636
- /* @__PURE__ */ jsx5("div", { className: "flex items-center gap-1.5", children: (theme === "dark" ? CANVAS_BACKGROUNDS_DARK : CANVAS_BACKGROUNDS_LIGHT).map((bg) => /* @__PURE__ */ jsx5(
1747
+ /* @__PURE__ */ jsx5("div", { className: "flex items-center gap-1.5", children: CANVAS_BACKGROUNDS[theme === "light" ? "light" : "dark"].map((bg) => /* @__PURE__ */ jsx5(
1637
1748
  "button",
1638
1749
  {
1639
- onClick: () => setCanvasBackground(bg.color),
1750
+ onClick: () => setCanvasBackground(bg.color, theme === "light" ? "light" : "dark"),
1640
1751
  title: t(bg.label),
1641
1752
  className: `w-7 h-7 rounded-full border-2 cursor-pointer transition-all duration-200 ${canvasBackground === bg.color ? "border-accent scale-110" : "border-border hover:border-border-light"}`,
1642
1753
  style: { backgroundColor: bg.color }
@@ -2081,7 +2192,7 @@ function PaintBucketSidebar() {
2081
2192
  fill.label
2082
2193
  ] }, fill.value)) })
2083
2194
  ] }),
2084
- /* @__PURE__ */ jsx9("div", { className: "max-w-[185px] px-2 text-[10px] leading-4 text-text-dim", children: "Click a rectangle or circle to apply the selected fill." })
2195
+ /* @__PURE__ */ jsx9("div", { className: "max-w-[185px] px-2 text-[10px] leading-4 text-text-dim", children: "Fill rectangles, circles, frames, and closed freehand shapes. Lines and arrows are left unchanged." })
2085
2196
  ] });
2086
2197
  }
2087
2198
 
@@ -4995,7 +5106,7 @@ var _saveScene = null;
4995
5106
  var _loadScene = null;
4996
5107
  async function ensureSceneSerializer() {
4997
5108
  if (_saveScene && _loadScene) return;
4998
- const m = await import("./SceneSerializer-SG6SJN5X.js");
5109
+ const m = await import("./SceneSerializer-JEYTYXZQ.js");
4999
5110
  _saveScene = m.saveScene;
5000
5111
  _loadScene = m.loadScene;
5001
5112
  }