@nice2dev/ui-graphics 1.3.14 → 1.3.16

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 (294) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/animation/AnimatedPerson.js +470 -143
  3. package/dist/cjs/animation/AnimationEditor.js +289 -101
  4. package/dist/cjs/animation/Audience.js +83 -61
  5. package/dist/cjs/animation/BodyRenderer.js +69 -28
  6. package/dist/cjs/animation/animationHelper.js +281 -288
  7. package/dist/cjs/animation/characterTypes.js +200 -203
  8. package/dist/cjs/animation/choreoDSL.js +205 -219
  9. package/dist/cjs/animation/choreography.js +4 -32
  10. package/dist/cjs/animation/karaokeIntegration.js +68 -77
  11. package/dist/cjs/animation/lottieExport.js +108 -114
  12. package/dist/cjs/animation/motionPath.js +225 -248
  13. package/dist/cjs/animation/physics.js +227 -254
  14. package/dist/cjs/animation/rig/RigPlayer.js +53 -56
  15. package/dist/cjs/animation/rig/RiggedBody.js +71 -26
  16. package/dist/cjs/animation/rig/choreoRigBridge.js +72 -77
  17. package/dist/cjs/animation/rig/ik.js +13 -18
  18. package/dist/cjs/animation/rig/poseOps.js +34 -41
  19. package/dist/cjs/animation/rig/presets.js +64 -72
  20. package/dist/cjs/animation/rig/rigMath.js +25 -32
  21. package/dist/cjs/animation/rig/rigTypes.js +59 -63
  22. package/dist/cjs/animation/shapes/arms.js +31 -13
  23. package/dist/cjs/animation/shapes/eyes.js +129 -47
  24. package/dist/cjs/animation/shapes/hair.js +72 -45
  25. package/dist/cjs/animation/shapes/heads.js +131 -57
  26. package/dist/cjs/animation/shapes/headwear.js +47 -38
  27. package/dist/cjs/animation/shapes/legs.js +31 -13
  28. package/dist/cjs/animation/shapes/mouths.js +120 -44
  29. package/dist/cjs/animation/shapes/noses.js +80 -25
  30. package/dist/cjs/animation/shapes/outfits.js +101 -39
  31. package/dist/cjs/animation/shapes/torsos.js +15 -11
  32. package/dist/cjs/animation/spineExport.js +103 -121
  33. package/dist/cjs/core/LocalUI.js +108 -43
  34. package/dist/cjs/core/NiceBrushEngine.js +758 -552
  35. package/dist/cjs/core/collaboration.js +249 -251
  36. package/dist/cjs/core/colorBlindness.js +100 -115
  37. package/dist/cjs/core/dragDrop.js +155 -158
  38. package/dist/cjs/core/editorAsk.js +51 -41
  39. package/dist/cjs/core/fixes.js +140 -171
  40. package/dist/cjs/core/gridGuides.js +166 -201
  41. package/dist/cjs/core/historyVisual.js +156 -135
  42. package/dist/cjs/core/i18n.js +13 -14
  43. package/dist/cjs/core/integrations.js +238 -249
  44. package/dist/cjs/core/minimap.js +149 -151
  45. package/dist/cjs/core/plugins.js +98 -113
  46. package/dist/cjs/core/rtl.js +71 -83
  47. package/dist/cjs/core/shortcuts.js +196 -225
  48. package/dist/cjs/core/theme.js +142 -149
  49. package/dist/cjs/core/yjsCollaboration.js +232 -293
  50. package/dist/cjs/design/design-tools.js +12 -22
  51. package/dist/cjs/font/NiceFontEditor.js +877 -316
  52. package/dist/cjs/font/fontFormatUtils.js +388 -448
  53. package/dist/cjs/game/GameAsset2dEditor.js +1201 -392
  54. package/dist/cjs/game/GameAsset2dEditor.module.css.js +146 -5
  55. package/dist/cjs/game/gameAssetTypes.js +7 -16
  56. package/dist/cjs/game/gameAssetUtils.js +390 -413
  57. package/dist/cjs/game/useGameAssetEditor.js +820 -763
  58. package/dist/cjs/index.js +81 -84
  59. package/dist/cjs/nice2dev-ui-graphics.css +1 -1
  60. package/dist/cjs/node_modules/lib0/array.js +8 -70
  61. package/dist/cjs/node_modules/lib0/binary.js +3 -20
  62. package/dist/cjs/node_modules/lib0/broadcastchannel.js +24 -86
  63. package/dist/cjs/node_modules/lib0/buffer.js +15 -82
  64. package/dist/cjs/node_modules/lib0/conditions.js +3 -16
  65. package/dist/cjs/node_modules/lib0/decoding.js +108 -287
  66. package/dist/cjs/node_modules/lib0/dom.js +14 -44
  67. package/dist/cjs/node_modules/lib0/encoding.js +68 -418
  68. package/dist/cjs/node_modules/lib0/environment.js +26 -92
  69. package/dist/cjs/node_modules/lib0/error.js +5 -29
  70. package/dist/cjs/node_modules/lib0/function.js +27 -66
  71. package/dist/cjs/node_modules/lib0/iterator.js +8 -33
  72. package/dist/cjs/node_modules/lib0/logging.common.js +12 -22
  73. package/dist/cjs/node_modules/lib0/logging.js +32 -71
  74. package/dist/cjs/node_modules/lib0/map.js +16 -86
  75. package/dist/cjs/node_modules/lib0/math.js +3 -32
  76. package/dist/cjs/node_modules/lib0/number.js +4 -15
  77. package/dist/cjs/node_modules/lib0/object.js +14 -88
  78. package/dist/cjs/node_modules/lib0/observable.js +38 -78
  79. package/dist/cjs/node_modules/lib0/pair.js +3 -20
  80. package/dist/cjs/node_modules/lib0/prng.js +8 -77
  81. package/dist/cjs/node_modules/lib0/promise.js +6 -30
  82. package/dist/cjs/node_modules/lib0/random.js +8 -23
  83. package/dist/cjs/node_modules/lib0/schema.js +313 -687
  84. package/dist/cjs/node_modules/lib0/set.js +3 -10
  85. package/dist/cjs/node_modules/lib0/storage.js +20 -55
  86. package/dist/cjs/node_modules/lib0/string.js +17 -72
  87. package/dist/cjs/node_modules/lib0/symbol.js +2 -12
  88. package/dist/cjs/node_modules/lib0/time.js +2 -15
  89. package/dist/cjs/node_modules/lib0/trait/equality.js +3 -28
  90. package/dist/cjs/node_modules/lib0/url.js +4 -18
  91. package/dist/cjs/node_modules/lib0/webcrypto.js +2 -5
  92. package/dist/cjs/node_modules/y-protocols/auth.js +5 -19
  93. package/dist/cjs/node_modules/y-protocols/awareness.js +53 -116
  94. package/dist/cjs/node_modules/y-protocols/sync.js +16 -103
  95. package/dist/cjs/node_modules/y-websocket/src/y-websocket.js +77 -219
  96. package/dist/cjs/node_modules/yjs/dist/yjs.js +2128 -4868
  97. package/dist/cjs/photo/FilterThumb.js +37 -25
  98. package/dist/cjs/photo/NiceAITools.js +679 -467
  99. package/dist/cjs/photo/PhotoEditor.js +34 -20
  100. package/dist/cjs/photo/PhotoEditor.module.css.js +296 -5
  101. package/dist/cjs/photo/PhotoEditorCanvas.js +250 -91
  102. package/dist/cjs/photo/PhotoEditorLeftPanel.js +1086 -224
  103. package/dist/cjs/photo/PhotoEditorToolbar.js +135 -19
  104. package/dist/cjs/photo/photoEditorActions.js +89 -72
  105. package/dist/cjs/photo/photoEditorTypes.js +29 -32
  106. package/dist/cjs/photo/photoFilters.js +959 -985
  107. package/dist/cjs/photo/photoOverlays.js +899 -945
  108. package/dist/cjs/photo/photoSelection.js +427 -499
  109. package/dist/cjs/photo/usePhotoEditor.js +739 -741
  110. package/dist/cjs/pixel/HSVPicker.js +191 -175
  111. package/dist/cjs/pixel/PixelEditor.js +159 -48
  112. package/dist/cjs/pixel/PixelEditor.module.css.js +209 -5
  113. package/dist/cjs/pixel/PixelEditorCanvas.js +29 -16
  114. package/dist/cjs/pixel/PixelEditorMenuBar.js +160 -162
  115. package/dist/cjs/pixel/PixelEditorRightPanel.js +377 -45
  116. package/dist/cjs/pixel/PixelEditorStatusBar.js +47 -20
  117. package/dist/cjs/pixel/PixelEditorTimeline.js +106 -23
  118. package/dist/cjs/pixel/PixelEditorToolbar.js +99 -21
  119. package/dist/cjs/pixel/SpriteGeneratorPanel.js +483 -303
  120. package/dist/cjs/pixel/animalGeneratorPresets.js +776 -601
  121. package/dist/cjs/pixel/asepriteFormat.js +511 -560
  122. package/dist/cjs/pixel/buildingGeneratorPresets.js +693 -520
  123. package/dist/cjs/pixel/characterGeneratorPresets.js +644 -544
  124. package/dist/cjs/pixel/pixel-pro.js +52 -63
  125. package/dist/cjs/pixel/pixelEditorExports.js +388 -428
  126. package/dist/cjs/pixel/pixelEditorTypes.js +91 -108
  127. package/dist/cjs/pixel/pixelEditorUtils.js +519 -561
  128. package/dist/cjs/pixel/spriteGeneratorCore.js +206 -257
  129. package/dist/cjs/pixel/usePixelEditor.js +1321 -1343
  130. package/dist/cjs/texture/Nice3DTexturePainter.js +707 -440
  131. package/dist/cjs/ui/NiceUIDesigner.js +954 -527
  132. package/dist/cjs/vector/NiceSvgToCode.js +586 -446
  133. package/dist/cjs/vector/VectorEditor.js +290 -117
  134. package/dist/cjs/vector/VectorEditor.module.css.js +86 -5
  135. package/dist/cjs/vector/VectorEditorMenuBar.js +333 -275
  136. package/dist/cjs/vector/VectorEditorRightPanel.js +1094 -371
  137. package/dist/cjs/vector/VectorEditorShapeRenderer.js +198 -60
  138. package/dist/cjs/vector/VectorEditorStatusBar.js +39 -13
  139. package/dist/cjs/vector/useVectorEditor.js +848 -889
  140. package/dist/cjs/vector/vectorAnimation.js +110 -137
  141. package/dist/cjs/vector/vectorBooleanOps.js +398 -485
  142. package/dist/cjs/vector/vectorEditorExport.js +201 -203
  143. package/dist/cjs/vector/vectorEditorImport.js +173 -189
  144. package/dist/cjs/vector/vectorEditorTypes.js +42 -49
  145. package/dist/cjs/vector/vectorGradients.js +151 -174
  146. package/dist/cjs/vector/vectorIconExport.js +52 -59
  147. package/dist/cjs/vector/vectorIconImport.js +169 -219
  148. package/dist/esm/animation/AnimatedPerson.js +470 -141
  149. package/dist/esm/animation/AnimationEditor.js +290 -100
  150. package/dist/esm/animation/Audience.js +83 -59
  151. package/dist/esm/animation/BodyRenderer.js +71 -25
  152. package/dist/esm/animation/animationHelper.js +284 -287
  153. package/dist/esm/animation/characterTypes.js +204 -202
  154. package/dist/esm/animation/choreoDSL.js +212 -218
  155. package/dist/esm/animation/choreography.js +8 -31
  156. package/dist/esm/animation/karaokeIntegration.js +74 -76
  157. package/dist/esm/animation/lottieExport.js +112 -113
  158. package/dist/esm/animation/motionPath.js +235 -247
  159. package/dist/esm/animation/physics.js +235 -253
  160. package/dist/esm/animation/rig/RigPlayer.js +53 -54
  161. package/dist/esm/animation/rig/RiggedBody.js +71 -24
  162. package/dist/esm/animation/rig/choreoRigBridge.js +74 -76
  163. package/dist/esm/animation/rig/ik.js +14 -17
  164. package/dist/esm/animation/rig/poseOps.js +37 -40
  165. package/dist/esm/animation/rig/presets.js +70 -71
  166. package/dist/esm/animation/rig/rigMath.js +28 -31
  167. package/dist/esm/animation/rig/rigTypes.js +62 -62
  168. package/dist/esm/animation/shapes/arms.js +32 -12
  169. package/dist/esm/animation/shapes/eyes.js +135 -46
  170. package/dist/esm/animation/shapes/hair.js +80 -44
  171. package/dist/esm/animation/shapes/heads.js +140 -56
  172. package/dist/esm/animation/shapes/headwear.js +54 -37
  173. package/dist/esm/animation/shapes/legs.js +32 -12
  174. package/dist/esm/animation/shapes/mouths.js +127 -43
  175. package/dist/esm/animation/shapes/noses.js +87 -24
  176. package/dist/esm/animation/shapes/outfits.js +106 -38
  177. package/dist/esm/animation/shapes/torsos.js +16 -10
  178. package/dist/esm/animation/spineExport.js +106 -120
  179. package/dist/esm/core/LocalUI.js +111 -42
  180. package/dist/esm/core/NiceBrushEngine.js +761 -551
  181. package/dist/esm/core/collaboration.js +255 -250
  182. package/dist/esm/core/colorBlindness.js +106 -114
  183. package/dist/esm/core/dragDrop.js +157 -157
  184. package/dist/esm/core/editorAsk.js +53 -40
  185. package/dist/esm/core/fixes.js +145 -170
  186. package/dist/esm/core/gridGuides.js +178 -200
  187. package/dist/esm/core/historyVisual.js +158 -134
  188. package/dist/esm/core/i18n.js +15 -13
  189. package/dist/esm/core/integrations.js +244 -248
  190. package/dist/esm/core/minimap.js +151 -150
  191. package/dist/esm/core/plugins.js +102 -112
  192. package/dist/esm/core/rtl.js +77 -82
  193. package/dist/esm/core/shortcuts.js +200 -224
  194. package/dist/esm/core/theme.js +150 -148
  195. package/dist/esm/core/yjsCollaboration.js +240 -292
  196. package/dist/esm/design/design-tools.js +13 -21
  197. package/dist/esm/font/NiceFontEditor.js +880 -315
  198. package/dist/esm/font/fontFormatUtils.js +394 -447
  199. package/dist/esm/game/GameAsset2dEditor.js +1202 -391
  200. package/dist/esm/game/GameAsset2dEditor.module.css.js +146 -3
  201. package/dist/esm/game/gameAssetTypes.js +8 -15
  202. package/dist/esm/game/gameAssetUtils.js +408 -412
  203. package/dist/esm/game/useGameAssetEditor.js +821 -762
  204. package/dist/esm/index.js +332 -69
  205. package/dist/esm/nice2dev-ui-graphics.css +1 -1
  206. package/dist/esm/node_modules/lib0/array.js +15 -69
  207. package/dist/esm/node_modules/lib0/binary.js +14 -19
  208. package/dist/esm/node_modules/lib0/broadcastchannel.js +27 -85
  209. package/dist/esm/node_modules/lib0/buffer.js +20 -80
  210. package/dist/esm/node_modules/lib0/conditions.js +4 -15
  211. package/dist/esm/node_modules/lib0/decoding.js +128 -286
  212. package/dist/esm/node_modules/lib0/dom.js +20 -43
  213. package/dist/esm/node_modules/lib0/encoding.js +90 -415
  214. package/dist/esm/node_modules/lib0/environment.js +33 -91
  215. package/dist/esm/node_modules/lib0/error.js +8 -28
  216. package/dist/esm/node_modules/lib0/function.js +31 -65
  217. package/dist/esm/node_modules/lib0/iterator.js +11 -32
  218. package/dist/esm/node_modules/lib0/logging.common.js +22 -21
  219. package/dist/esm/node_modules/lib0/logging.js +44 -70
  220. package/dist/esm/node_modules/lib0/map.js +21 -85
  221. package/dist/esm/node_modules/lib0/math.js +9 -31
  222. package/dist/esm/node_modules/lib0/number.js +7 -14
  223. package/dist/esm/node_modules/lib0/object.js +26 -87
  224. package/dist/esm/node_modules/lib0/observable.js +40 -77
  225. package/dist/esm/node_modules/lib0/pair.js +5 -19
  226. package/dist/esm/node_modules/lib0/prng.js +15 -76
  227. package/dist/esm/node_modules/lib0/promise.js +7 -29
  228. package/dist/esm/node_modules/lib0/random.js +10 -22
  229. package/dist/esm/node_modules/lib0/schema.js +367 -686
  230. package/dist/esm/node_modules/lib0/set.js +4 -9
  231. package/dist/esm/node_modules/lib0/storage.js +23 -54
  232. package/dist/esm/node_modules/lib0/string.js +26 -71
  233. package/dist/esm/node_modules/lib0/symbol.js +3 -11
  234. package/dist/esm/node_modules/lib0/time.js +3 -14
  235. package/dist/esm/node_modules/lib0/trait/equality.js +5 -27
  236. package/dist/esm/node_modules/lib0/url.js +5 -17
  237. package/dist/esm/node_modules/lib0/webcrypto.js +3 -4
  238. package/dist/esm/node_modules/y-protocols/auth.js +7 -18
  239. package/dist/esm/node_modules/y-protocols/awareness.js +58 -115
  240. package/dist/esm/node_modules/y-protocols/sync.js +26 -102
  241. package/dist/esm/node_modules/y-websocket/src/y-websocket.js +82 -218
  242. package/dist/esm/node_modules/yjs/dist/yjs.js +2263 -4899
  243. package/dist/esm/photo/FilterThumb.js +37 -23
  244. package/dist/esm/photo/NiceAITools.js +685 -466
  245. package/dist/esm/photo/PhotoEditor.js +34 -18
  246. package/dist/esm/photo/PhotoEditor.module.css.js +296 -3
  247. package/dist/esm/photo/PhotoEditorCanvas.js +250 -89
  248. package/dist/esm/photo/PhotoEditorLeftPanel.js +1086 -222
  249. package/dist/esm/photo/PhotoEditorToolbar.js +135 -17
  250. package/dist/esm/photo/photoEditorActions.js +93 -71
  251. package/dist/esm/photo/photoEditorTypes.js +32 -31
  252. package/dist/esm/photo/photoFilters.js +967 -984
  253. package/dist/esm/photo/photoOverlays.js +913 -944
  254. package/dist/esm/photo/photoSelection.js +438 -498
  255. package/dist/esm/photo/usePhotoEditor.js +740 -740
  256. package/dist/esm/pixel/HSVPicker.js +192 -174
  257. package/dist/esm/pixel/PixelEditor.js +160 -47
  258. package/dist/esm/pixel/PixelEditor.module.css.js +209 -3
  259. package/dist/esm/pixel/PixelEditorCanvas.js +29 -14
  260. package/dist/esm/pixel/PixelEditorMenuBar.js +160 -160
  261. package/dist/esm/pixel/PixelEditorRightPanel.js +377 -43
  262. package/dist/esm/pixel/PixelEditorStatusBar.js +47 -18
  263. package/dist/esm/pixel/PixelEditorTimeline.js +106 -21
  264. package/dist/esm/pixel/PixelEditorToolbar.js +99 -19
  265. package/dist/esm/pixel/SpriteGeneratorPanel.js +483 -301
  266. package/dist/esm/pixel/animalGeneratorPresets.js +782 -600
  267. package/dist/esm/pixel/asepriteFormat.js +517 -559
  268. package/dist/esm/pixel/buildingGeneratorPresets.js +699 -519
  269. package/dist/esm/pixel/characterGeneratorPresets.js +650 -543
  270. package/dist/esm/pixel/pixel-pro.js +53 -62
  271. package/dist/esm/pixel/pixelEditorExports.js +397 -427
  272. package/dist/esm/pixel/pixelEditorTypes.js +103 -107
  273. package/dist/esm/pixel/pixelEditorUtils.js +551 -560
  274. package/dist/esm/pixel/spriteGeneratorCore.js +223 -256
  275. package/dist/esm/pixel/usePixelEditor.js +1322 -1342
  276. package/dist/esm/texture/Nice3DTexturePainter.js +709 -439
  277. package/dist/esm/ui/NiceUIDesigner.js +957 -526
  278. package/dist/esm/vector/NiceSvgToCode.js +589 -445
  279. package/dist/esm/vector/VectorEditor.js +291 -116
  280. package/dist/esm/vector/VectorEditor.module.css.js +86 -3
  281. package/dist/esm/vector/VectorEditorMenuBar.js +333 -273
  282. package/dist/esm/vector/VectorEditorRightPanel.js +1094 -369
  283. package/dist/esm/vector/VectorEditorShapeRenderer.js +199 -59
  284. package/dist/esm/vector/VectorEditorStatusBar.js +39 -11
  285. package/dist/esm/vector/useVectorEditor.js +849 -888
  286. package/dist/esm/vector/vectorAnimation.js +115 -136
  287. package/dist/esm/vector/vectorBooleanOps.js +401 -484
  288. package/dist/esm/vector/vectorEditorExport.js +207 -202
  289. package/dist/esm/vector/vectorEditorImport.js +174 -188
  290. package/dist/esm/vector/vectorEditorTypes.js +50 -48
  291. package/dist/esm/vector/vectorGradients.js +158 -173
  292. package/dist/esm/vector/vectorIconExport.js +53 -58
  293. package/dist/esm/vector/vectorIconImport.js +171 -218
  294. package/package.json +9 -15
@@ -1,1000 +1,954 @@
1
- 'use strict';
2
-
3
- var uiCore = require('@nice2dev/ui-core');
4
-
5
- /**
6
- * photoOverlays.ts — Overlay / layer engine for PhotoEditor.
7
- *
8
- * Handles emojis, text, shapes, freehand drawing strokes, and decorative frames
9
- * as positioned layers on top of the base (filtered) image.
10
- *
11
- * All positions use **fractional coordinates** (0 … 1) relative to the rendered
12
- * canvas so overlays survive crop / resize changes.
13
- */
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const uiCore = require("@nice2dev/ui-core");
14
4
  const EMOJI_CATALOG = [
15
- {
16
- id: 'faces',
17
- label: 'Faces',
18
- icon: '😊',
19
- emojis: [
20
- '😀',
21
- '😃',
22
- '😄',
23
- '😁',
24
- '😆',
25
- '😅',
26
- '🤣',
27
- '😂',
28
- '🙂',
29
- '😉',
30
- '😊',
31
- '😇',
32
- '🥰',
33
- '😍',
34
- '🤩',
35
- '😘',
36
- '😗',
37
- '😚',
38
- '😋',
39
- '😛',
40
- '😜',
41
- '🤪',
42
- '😝',
43
- '🤑',
44
- '🤗',
45
- '🤭',
46
- '🤫',
47
- '🤔',
48
- '😐',
49
- '😑',
50
- '😶',
51
- '🙄',
52
- '😏',
53
- '😣',
54
- '😥',
55
- '😮',
56
- '🤐',
57
- '😯',
58
- '😪',
59
- '😫',
60
- '🥱',
61
- '😴',
62
- '🤤',
63
- '😌',
64
- '😷',
65
- '🤒',
66
- '🤕',
67
- '🤢',
68
- '🤮',
69
- '🥵',
70
- '🥶',
71
- '🥴',
72
- '😵',
73
- '🤯',
74
- '🤠',
75
- '🥳',
76
- '🥸',
77
- '😎',
78
- '🤓',
79
- '🧐',
80
- '😤',
81
- '😡',
82
- '🤬',
83
- '😈',
84
- '👿',
85
- '💀',
86
- '☠️',
87
- '💩',
88
- '🤡',
89
- '👹',
90
- '👻',
91
- '👽',
92
- '🤖',
93
- ],
94
- },
95
- {
96
- id: 'gestures',
97
- label: 'Gestures',
98
- icon: '👍',
99
- emojis: [
100
- '👍',
101
- '👎',
102
- '👊',
103
- '✊',
104
- '🤛',
105
- '🤜',
106
- '🤝',
107
- '👏',
108
- '🙌',
109
- '🫶',
110
- '👐',
111
- '🤲',
112
- '🙏',
113
- '✌️',
114
- '🤞',
115
- '🤟',
116
- '🤘',
117
- '🤙',
118
- '👈',
119
- '👉',
120
- '👆',
121
- '👇',
122
- '☝️',
123
- '✋',
124
- '🤚',
125
- '🖐️',
126
- '🖖',
127
- '👋',
128
- '🤏',
129
- '✍️',
130
- '💪',
131
- '🦾',
132
- '🦵',
133
- '🦶',
134
- '👂',
135
- '👃',
136
- '👀',
137
- '👁️',
138
- '👅',
139
- '👄',
140
- ],
141
- },
142
- {
143
- id: 'hearts',
144
- label: 'Hearts',
145
- icon: '❤️',
146
- emojis: [
147
- '❤️',
148
- '🧡',
149
- '💛',
150
- '💚',
151
- '💙',
152
- '💜',
153
- '🖤',
154
- '🤍',
155
- '🤎',
156
- '❤️‍🔥',
157
- '💔',
158
- '❣️',
159
- '💕',
160
- '💞',
161
- '💓',
162
- '💗',
163
- '💖',
164
- '💘',
165
- '💝',
166
- '💟',
167
- '♥️',
168
- '💌',
169
- '💋',
170
- '💍',
171
- '💎',
172
- ],
173
- },
174
- {
175
- id: 'animals',
176
- label: 'Animals',
177
- icon: '🐶',
178
- emojis: [
179
- '🐶',
180
- '🐱',
181
- '🐭',
182
- '🐹',
183
- '🐰',
184
- '🦊',
185
- '🐻',
186
- '🐼',
187
- '🐨',
188
- '🐯',
189
- '🦁',
190
- '🐮',
191
- '🐷',
192
- '🐸',
193
- '🐵',
194
- '🙈',
195
- '🙉',
196
- '🙊',
197
- '🐔',
198
- '🐧',
199
- '🐦',
200
- '🦆',
201
- '🦅',
202
- '🦉',
203
- '🦋',
204
- '🐌',
205
- '🐞',
206
- '🐙',
207
- '🐬',
208
- '🐳',
209
- '🦈',
210
- '🐊',
211
- '🐘',
212
- '🦒',
213
- '🦘',
214
- '🐴',
215
- '🦄',
216
- ],
217
- },
218
- {
219
- id: 'party',
220
- label: 'Party',
221
- icon: '🎉',
222
- emojis: [
223
- '🎉',
224
- '🎊',
225
- '🎈',
226
- '🎁',
227
- '🎀',
228
- '🎃',
229
- '🎄',
230
- '🎆',
231
- '🎇',
232
- '✨',
233
- '🏆',
234
- '🏅',
235
- '🥇',
236
- '🥈',
237
- '🥉',
238
- '🎪',
239
- '🎭',
240
- '🎨',
241
- '🎬',
242
- '🎤',
243
- '🎧',
244
- '🎼',
245
- '🎵',
246
- '🎶',
247
- '🎹',
248
- '🥁',
249
- '🎷',
250
- '🎺',
251
- '🎸',
252
- '🎻',
253
- '🎲',
254
- '🎯',
255
- '🎳',
256
- '🕹️',
257
- '🎮',
258
- '🎰',
259
- ],
260
- },
261
- {
262
- id: 'food',
263
- label: 'Food',
264
- icon: '🍕',
265
- emojis: [
266
- '🍏',
267
- '🍎',
268
- '🍐',
269
- '🍊',
270
- '🍋',
271
- '🍌',
272
- '🍉',
273
- '🍇',
274
- '🍓',
275
- '🍒',
276
- '🍑',
277
- '🥭',
278
- '🍍',
279
- '🥝',
280
- '🍅',
281
- '🥑',
282
- '🍆',
283
- '🌽',
284
- '🌶️',
285
- '🍞',
286
- '🧀',
287
- '🥚',
288
- '🍳',
289
- '🥓',
290
- '🍗',
291
- '🌭',
292
- '🍔',
293
- '🍟',
294
- '🍕',
295
- '🌮',
296
- '🌯',
297
- '🥪',
298
- '🍝',
299
- '🍜',
300
- '🍣',
301
- '🍤',
302
- '🍦',
303
- '🍰',
304
- '🎂',
305
- '🍩',
306
- '🍪',
307
- '🍫',
308
- '🍬',
309
- '🍭',
310
- '🍿',
311
- '☕',
312
- '🍵',
313
- '🍺',
314
- '🍻',
315
- '🥂',
316
- '🍷',
317
- '🍸',
318
- '🍹',
319
- '🧃',
320
- '🍾',
321
- ],
322
- },
323
- {
324
- id: 'nature',
325
- label: 'Nature',
326
- icon: '🌸',
327
- emojis: [
328
- '🌸',
329
- '🌺',
330
- '🌹',
331
- '🌻',
332
- '🌼',
333
- '💐',
334
- '🌷',
335
- '🌱',
336
- '🪴',
337
- '🌲',
338
- '🌳',
339
- '🌴',
340
- '🌵',
341
- '🍀',
342
- '☘️',
343
- '🌿',
344
- '🍃',
345
- '🍂',
346
- '🍁',
347
- '🌊',
348
- '🌈',
349
- '☀️',
350
- '🌤️',
351
- '⛅',
352
- '☁️',
353
- '🌧️',
354
- '⛈️',
355
- '🌩️',
356
- '❄️',
357
- '☃️',
358
- '⛄',
359
- '🔥',
360
- '💧',
361
- '💦',
362
- '🌏',
363
- '🌙',
364
- '🌟',
365
- '💫',
366
- '✨',
367
- '☄️',
368
- ],
369
- },
370
- {
371
- id: 'symbols',
372
- label: 'Symbols',
373
- icon: '⚡',
374
- emojis: [
375
- '⚡',
376
- '💥',
377
- '🔥',
378
- '✨',
379
- '💫',
380
- '🌟',
381
- '⭐',
382
- '💡',
383
- '💣',
384
- '🧨',
385
- '🎯',
386
- '♠️',
387
- '♥️',
388
- '♦️',
389
- '♣️',
390
- '🔮',
391
- '🧿',
392
- '♻️',
393
- '⚜️',
394
- '⭕',
395
- '✅',
396
- '❌',
397
- '➕',
398
- '➖',
399
- '❗',
400
- '❓',
401
- '‼️',
402
- '⁉️',
403
- '💯',
404
- '🔴',
405
- '🟠',
406
- '🟡',
407
- '🟢',
408
- '🔵',
409
- '🟣',
410
- '⚫',
411
- '⚪',
412
- '🔺',
413
- '🔻',
414
- '🔶',
415
- '🔷',
416
- '🏳️',
417
- '🏴',
418
- '🚩',
419
- '🏳️‍🌈',
420
- ],
421
- },
5
+ {
6
+ id: "faces",
7
+ label: "Faces",
8
+ icon: "😊",
9
+ emojis: [
10
+ "😀",
11
+ "😃",
12
+ "😄",
13
+ "😁",
14
+ "😆",
15
+ "😅",
16
+ "🤣",
17
+ "😂",
18
+ "🙂",
19
+ "😉",
20
+ "😊",
21
+ "😇",
22
+ "🥰",
23
+ "😍",
24
+ "🤩",
25
+ "😘",
26
+ "😗",
27
+ "😚",
28
+ "😋",
29
+ "😛",
30
+ "😜",
31
+ "🤪",
32
+ "😝",
33
+ "🤑",
34
+ "🤗",
35
+ "🤭",
36
+ "🤫",
37
+ "🤔",
38
+ "😐",
39
+ "😑",
40
+ "😶",
41
+ "🙄",
42
+ "😏",
43
+ "😣",
44
+ "😥",
45
+ "😮",
46
+ "🤐",
47
+ "😯",
48
+ "😪",
49
+ "😫",
50
+ "🥱",
51
+ "😴",
52
+ "🤤",
53
+ "😌",
54
+ "😷",
55
+ "🤒",
56
+ "🤕",
57
+ "🤢",
58
+ "🤮",
59
+ "🥵",
60
+ "🥶",
61
+ "🥴",
62
+ "😵",
63
+ "🤯",
64
+ "🤠",
65
+ "🥳",
66
+ "🥸",
67
+ "😎",
68
+ "🤓",
69
+ "🧐",
70
+ "😤",
71
+ "😡",
72
+ "🤬",
73
+ "😈",
74
+ "👿",
75
+ "💀",
76
+ "☠️",
77
+ "💩",
78
+ "🤡",
79
+ "👹",
80
+ "👻",
81
+ "👽",
82
+ "🤖"
83
+ ]
84
+ },
85
+ {
86
+ id: "gestures",
87
+ label: "Gestures",
88
+ icon: "👍",
89
+ emojis: [
90
+ "👍",
91
+ "👎",
92
+ "👊",
93
+ "✊",
94
+ "🤛",
95
+ "🤜",
96
+ "🤝",
97
+ "👏",
98
+ "🙌",
99
+ "🫶",
100
+ "👐",
101
+ "🤲",
102
+ "🙏",
103
+ "✌️",
104
+ "🤞",
105
+ "🤟",
106
+ "🤘",
107
+ "🤙",
108
+ "👈",
109
+ "👉",
110
+ "👆",
111
+ "👇",
112
+ "☝️",
113
+ "✋",
114
+ "🤚",
115
+ "🖐️",
116
+ "🖖",
117
+ "👋",
118
+ "🤏",
119
+ "✍️",
120
+ "💪",
121
+ "🦾",
122
+ "🦵",
123
+ "🦶",
124
+ "👂",
125
+ "👃",
126
+ "👀",
127
+ "👁️",
128
+ "👅",
129
+ "👄"
130
+ ]
131
+ },
132
+ {
133
+ id: "hearts",
134
+ label: "Hearts",
135
+ icon: "❤️",
136
+ emojis: [
137
+ "❤️",
138
+ "🧡",
139
+ "💛",
140
+ "💚",
141
+ "💙",
142
+ "💜",
143
+ "🖤",
144
+ "🤍",
145
+ "🤎",
146
+ "❤️‍🔥",
147
+ "💔",
148
+ "❣️",
149
+ "💕",
150
+ "💞",
151
+ "💓",
152
+ "💗",
153
+ "💖",
154
+ "💘",
155
+ "💝",
156
+ "💟",
157
+ "♥️",
158
+ "💌",
159
+ "💋",
160
+ "💍",
161
+ "💎"
162
+ ]
163
+ },
164
+ {
165
+ id: "animals",
166
+ label: "Animals",
167
+ icon: "🐶",
168
+ emojis: [
169
+ "🐶",
170
+ "🐱",
171
+ "🐭",
172
+ "🐹",
173
+ "🐰",
174
+ "🦊",
175
+ "🐻",
176
+ "🐼",
177
+ "🐨",
178
+ "🐯",
179
+ "🦁",
180
+ "🐮",
181
+ "🐷",
182
+ "🐸",
183
+ "🐵",
184
+ "🙈",
185
+ "🙉",
186
+ "🙊",
187
+ "🐔",
188
+ "🐧",
189
+ "🐦",
190
+ "🦆",
191
+ "🦅",
192
+ "🦉",
193
+ "🦋",
194
+ "🐌",
195
+ "🐞",
196
+ "🐙",
197
+ "🐬",
198
+ "🐳",
199
+ "🦈",
200
+ "🐊",
201
+ "🐘",
202
+ "🦒",
203
+ "🦘",
204
+ "🐴",
205
+ "🦄"
206
+ ]
207
+ },
208
+ {
209
+ id: "party",
210
+ label: "Party",
211
+ icon: "🎉",
212
+ emojis: [
213
+ "🎉",
214
+ "🎊",
215
+ "🎈",
216
+ "🎁",
217
+ "🎀",
218
+ "🎃",
219
+ "🎄",
220
+ "🎆",
221
+ "🎇",
222
+ "✨",
223
+ "🏆",
224
+ "🏅",
225
+ "🥇",
226
+ "🥈",
227
+ "🥉",
228
+ "🎪",
229
+ "🎭",
230
+ "🎨",
231
+ "🎬",
232
+ "🎤",
233
+ "🎧",
234
+ "🎼",
235
+ "🎵",
236
+ "🎶",
237
+ "🎹",
238
+ "🥁",
239
+ "🎷",
240
+ "🎺",
241
+ "🎸",
242
+ "🎻",
243
+ "🎲",
244
+ "🎯",
245
+ "🎳",
246
+ "🕹️",
247
+ "🎮",
248
+ "🎰"
249
+ ]
250
+ },
251
+ {
252
+ id: "food",
253
+ label: "Food",
254
+ icon: "🍕",
255
+ emojis: [
256
+ "🍏",
257
+ "🍎",
258
+ "🍐",
259
+ "🍊",
260
+ "🍋",
261
+ "🍌",
262
+ "🍉",
263
+ "🍇",
264
+ "🍓",
265
+ "🍒",
266
+ "🍑",
267
+ "🥭",
268
+ "🍍",
269
+ "🥝",
270
+ "🍅",
271
+ "🥑",
272
+ "🍆",
273
+ "🌽",
274
+ "🌶️",
275
+ "🍞",
276
+ "🧀",
277
+ "🥚",
278
+ "🍳",
279
+ "🥓",
280
+ "🍗",
281
+ "🌭",
282
+ "🍔",
283
+ "🍟",
284
+ "🍕",
285
+ "🌮",
286
+ "🌯",
287
+ "🥪",
288
+ "🍝",
289
+ "🍜",
290
+ "🍣",
291
+ "🍤",
292
+ "🍦",
293
+ "🍰",
294
+ "🎂",
295
+ "🍩",
296
+ "🍪",
297
+ "🍫",
298
+ "🍬",
299
+ "🍭",
300
+ "🍿",
301
+ "☕",
302
+ "🍵",
303
+ "🍺",
304
+ "🍻",
305
+ "🥂",
306
+ "🍷",
307
+ "🍸",
308
+ "🍹",
309
+ "🧃",
310
+ "🍾"
311
+ ]
312
+ },
313
+ {
314
+ id: "nature",
315
+ label: "Nature",
316
+ icon: "🌸",
317
+ emojis: [
318
+ "🌸",
319
+ "🌺",
320
+ "🌹",
321
+ "🌻",
322
+ "🌼",
323
+ "💐",
324
+ "🌷",
325
+ "🌱",
326
+ "🪴",
327
+ "🌲",
328
+ "🌳",
329
+ "🌴",
330
+ "🌵",
331
+ "🍀",
332
+ "☘️",
333
+ "🌿",
334
+ "🍃",
335
+ "🍂",
336
+ "🍁",
337
+ "🌊",
338
+ "🌈",
339
+ "☀️",
340
+ "🌤️",
341
+ "⛅",
342
+ "☁️",
343
+ "🌧️",
344
+ "⛈️",
345
+ "🌩️",
346
+ "❄️",
347
+ "☃️",
348
+ "⛄",
349
+ "🔥",
350
+ "💧",
351
+ "💦",
352
+ "🌏",
353
+ "🌙",
354
+ "🌟",
355
+ "💫",
356
+ "✨",
357
+ "☄️"
358
+ ]
359
+ },
360
+ {
361
+ id: "symbols",
362
+ label: "Symbols",
363
+ icon: "⚡",
364
+ emojis: [
365
+ "⚡",
366
+ "💥",
367
+ "🔥",
368
+ "✨",
369
+ "💫",
370
+ "🌟",
371
+ "⭐",
372
+ "💡",
373
+ "💣",
374
+ "🧨",
375
+ "🎯",
376
+ "♠️",
377
+ "♥️",
378
+ "♦️",
379
+ "♣️",
380
+ "🔮",
381
+ "🧿",
382
+ "♻️",
383
+ "⚜️",
384
+ "⭕",
385
+ "✅",
386
+ "❌",
387
+ "➕",
388
+ "➖",
389
+ "❗",
390
+ "❓",
391
+ "‼️",
392
+ "⁉️",
393
+ "💯",
394
+ "🔴",
395
+ "🟠",
396
+ "🟡",
397
+ "🟢",
398
+ "🔵",
399
+ "🟣",
400
+ "⚫",
401
+ "⚪",
402
+ "🔺",
403
+ "🔻",
404
+ "🔶",
405
+ "🔷",
406
+ "🏳️",
407
+ "🏴",
408
+ "🚩",
409
+ "🏳️‍🌈"
410
+ ]
411
+ }
422
412
  ];
423
413
  const SHAPE_CATALOG = [
424
- { id: 'rect', label: 'Rectangle', icon: 'square' },
425
- { id: 'circle', label: 'Circle', icon: 'circle' },
426
- { id: 'triangle', label: 'Triangle', icon: 'triangle' },
427
- { id: 'star', label: 'Star', icon: 'star' },
428
- { id: 'heart', label: 'Heart', icon: 'heart' },
429
- { id: 'arrow', label: 'Arrow', icon: 'arrow-right' },
430
- { id: 'diamond', label: 'Diamond', icon: 'diamond' },
431
- { id: 'hexagon', label: 'Hexagon', icon: 'hexagon' },
432
- { id: 'cross', label: 'Cross', icon: 'plus' },
433
- { id: 'lightning', label: 'Lightning', icon: 'zap' },
414
+ { id: "rect", label: "Rectangle", icon: "square" },
415
+ { id: "circle", label: "Circle", icon: "circle" },
416
+ { id: "triangle", label: "Triangle", icon: "triangle" },
417
+ { id: "star", label: "Star", icon: "star" },
418
+ { id: "heart", label: "Heart", icon: "heart" },
419
+ { id: "arrow", label: "Arrow", icon: "arrow-right" },
420
+ { id: "diamond", label: "Diamond", icon: "diamond" },
421
+ { id: "hexagon", label: "Hexagon", icon: "hexagon" },
422
+ { id: "cross", label: "Cross", icon: "plus" },
423
+ { id: "lightning", label: "Lightning", icon: "zap" }
434
424
  ];
435
425
  const FRAME_CATALOG = [
436
- { id: 'simple', label: 'Simple', icon: 'square' },
437
- { id: 'double', label: 'Double', icon: 'copy' },
438
- { id: 'rounded', label: 'Rounded', icon: 'card' },
439
- { id: 'polaroid', label: 'Polaroid', icon: 'camera' },
440
- { id: 'film', label: 'Film', icon: 'video' },
441
- { id: 'vintage', label: 'Vintage', icon: 'image' },
442
- { id: 'ornate', label: 'Ornate', icon: 'hexagon' },
443
- { id: 'shadow', label: 'Shadow', icon: 'moon' },
444
- { id: 'neon', label: 'Neon', icon: 'lightbulb' },
445
- { id: 'torn', label: 'Torn', icon: 'file' },
426
+ { id: "simple", label: "Simple", icon: "square" },
427
+ { id: "double", label: "Double", icon: "copy" },
428
+ { id: "rounded", label: "Rounded", icon: "card" },
429
+ { id: "polaroid", label: "Polaroid", icon: "camera" },
430
+ { id: "film", label: "Film", icon: "video" },
431
+ { id: "vintage", label: "Vintage", icon: "image" },
432
+ { id: "ornate", label: "Ornate", icon: "hexagon" },
433
+ { id: "shadow", label: "Shadow", icon: "moon" },
434
+ { id: "neon", label: "Neon", icon: "lightbulb" },
435
+ { id: "torn", label: "Torn", icon: "file" }
446
436
  ];
447
- // ═══════════════════════════════════════════════════════════════
448
- // ── ID generation
449
- // ═══════════════════════════════════════════════════════════════
450
437
  let _nextId = 1;
451
438
  function generateOverlayId() {
452
- return `ov-${_nextId++}-${Date.now().toString(36)}`;
439
+ return `ov-${_nextId++}-${Date.now().toString(36)}`;
453
440
  }
454
- // ═══════════════════════════════════════════════════════════════
455
- // ── Rendering
456
- // ═══════════════════════════════════════════════════════════════
457
- /** Render all overlays to a canvas context. */
458
441
  function renderAllOverlays(ctx, overlays, w, h) {
459
- for (const ov of overlays) {
460
- renderSingleOverlay(ctx, ov, w, h);
461
- }
442
+ for (const ov of overlays) {
443
+ renderSingleOverlay(ctx, ov, w, h);
444
+ }
462
445
  }
463
446
  function renderSingleOverlay(ctx, ov, w, h) {
464
- ctx.save();
465
- ctx.globalAlpha = ov.opacity;
466
- if (ov.type === 'drawing') {
467
- renderDrawing(ctx, ov, w, h);
468
- ctx.restore();
469
- return;
470
- }
471
- if (ov.type === 'frame') {
472
- renderFrame(ctx, ov, w, h);
473
- ctx.restore();
474
- return;
475
- }
476
- // Position-based overlays (emoji, text, shape)
477
- const cx = ov.x * w;
478
- const cy = ov.y * h;
479
- ctx.translate(cx, cy);
480
- ctx.rotate((ov.rotation * Math.PI) / 180);
481
- ctx.scale(ov.scaleX, ov.scaleY);
482
- switch (ov.type) {
483
- case 'emoji':
484
- renderEmoji(ctx, ov);
485
- break;
486
- case 'text':
487
- renderText(ctx, ov);
488
- break;
489
- case 'shape':
490
- renderShape(ctx, ov);
491
- break;
492
- }
447
+ ctx.save();
448
+ ctx.globalAlpha = ov.opacity;
449
+ if (ov.type === "drawing") {
450
+ renderDrawing(ctx, ov, w, h);
451
+ ctx.restore();
452
+ return;
453
+ }
454
+ if (ov.type === "frame") {
455
+ renderFrame(ctx, ov, w, h);
493
456
  ctx.restore();
457
+ return;
458
+ }
459
+ const cx = ov.x * w;
460
+ const cy = ov.y * h;
461
+ ctx.translate(cx, cy);
462
+ ctx.rotate(ov.rotation * Math.PI / 180);
463
+ ctx.scale(ov.scaleX, ov.scaleY);
464
+ switch (ov.type) {
465
+ case "emoji":
466
+ renderEmoji(ctx, ov);
467
+ break;
468
+ case "text":
469
+ renderText(ctx, ov);
470
+ break;
471
+ case "shape":
472
+ renderShape(ctx, ov);
473
+ break;
474
+ }
475
+ ctx.restore();
494
476
  }
495
- // -- Emoji -------------------------------------------------------
496
477
  function renderEmoji(ctx, ov) {
497
- ctx.font = `${ov.fontSize}px "Segoe UI Emoji","Apple Color Emoji","Noto Color Emoji",sans-serif`;
498
- ctx.textAlign = 'center';
499
- ctx.textBaseline = 'middle';
500
- ctx.fillText(ov.emoji, 0, 0);
478
+ ctx.font = `${ov.fontSize}px "Segoe UI Emoji","Apple Color Emoji","Noto Color Emoji",sans-serif`;
479
+ ctx.textAlign = "center";
480
+ ctx.textBaseline = "middle";
481
+ ctx.fillText(ov.emoji, 0, 0);
501
482
  }
502
- // -- Text --------------------------------------------------------
503
483
  function renderText(ctx, ov) {
504
- const style = `${ov.italic ? 'italic ' : ''}${ov.bold ? 'bold ' : ''}`;
505
- ctx.font = `${style}${ov.fontSize}px ${ov.fontFamily || 'Arial'},sans-serif`;
506
- ctx.textAlign = 'center';
507
- ctx.textBaseline = 'middle';
508
- const lines = ov.text.split('\n');
509
- const lineH = ov.fontSize * 1.25;
510
- const totalH = lineH * lines.length;
511
- const startY = -(totalH / 2) + lineH / 2;
512
- // Background
513
- if (ov.background) {
514
- const maxW = Math.max(...lines.map((l) => ctx.measureText(l).width));
515
- const pad = ov.backgroundPadding;
516
- ctx.fillStyle = ov.background;
517
- const rx = -(maxW / 2 + pad);
518
- const ry = startY - lineH / 2 - pad;
519
- roundRect(ctx, rx, ry, maxW + pad * 2, totalH + pad * 2, 6);
520
- ctx.fill();
521
- }
522
- // Shadow
523
- if (ov.shadow) {
524
- ctx.shadowColor = ov.shadow;
525
- ctx.shadowBlur = 4;
526
- ctx.shadowOffsetX = 2;
527
- ctx.shadowOffsetY = 2;
528
- }
529
- for (let i = 0; i < lines.length; i++) {
530
- const ly = startY + i * lineH;
531
- if (ov.outlineColor && ov.outlineWidth > 0) {
532
- ctx.strokeStyle = ov.outlineColor;
533
- ctx.lineWidth = ov.outlineWidth;
534
- ctx.lineJoin = 'round';
535
- ctx.strokeText(lines[i], 0, ly);
536
- }
537
- ctx.fillStyle = ov.color;
538
- ctx.fillText(lines[i], 0, ly);
484
+ const style = `${ov.italic ? "italic " : ""}${ov.bold ? "bold " : ""}`;
485
+ ctx.font = `${style}${ov.fontSize}px ${ov.fontFamily || "Arial"},sans-serif`;
486
+ ctx.textAlign = "center";
487
+ ctx.textBaseline = "middle";
488
+ const lines = ov.text.split("\n");
489
+ const lineH = ov.fontSize * 1.25;
490
+ const totalH = lineH * lines.length;
491
+ const startY = -(totalH / 2) + lineH / 2;
492
+ if (ov.background) {
493
+ const maxW = Math.max(...lines.map((l) => ctx.measureText(l).width));
494
+ const pad = ov.backgroundPadding;
495
+ ctx.fillStyle = ov.background;
496
+ const rx = -(maxW / 2 + pad);
497
+ const ry = startY - lineH / 2 - pad;
498
+ roundRect(ctx, rx, ry, maxW + pad * 2, totalH + pad * 2, 6);
499
+ ctx.fill();
500
+ }
501
+ if (ov.shadow) {
502
+ ctx.shadowColor = ov.shadow;
503
+ ctx.shadowBlur = 4;
504
+ ctx.shadowOffsetX = 2;
505
+ ctx.shadowOffsetY = 2;
506
+ }
507
+ for (let i = 0; i < lines.length; i++) {
508
+ const ly = startY + i * lineH;
509
+ if (ov.outlineColor && ov.outlineWidth > 0) {
510
+ ctx.strokeStyle = ov.outlineColor;
511
+ ctx.lineWidth = ov.outlineWidth;
512
+ ctx.lineJoin = "round";
513
+ ctx.strokeText(lines[i], 0, ly);
539
514
  }
540
- ctx.shadowColor = 'transparent';
515
+ ctx.fillStyle = ov.color;
516
+ ctx.fillText(lines[i], 0, ly);
517
+ }
518
+ ctx.shadowColor = "transparent";
541
519
  }
542
- // -- Shape -------------------------------------------------------
543
520
  function renderShape(ctx, ov) {
544
- drawShapePath(ctx, ov.shapeKind, ov.width, ov.height);
545
- if (ov.fillColor) {
546
- ctx.fillStyle = ov.fillColor;
547
- ctx.fill();
548
- }
549
- if (ov.strokeColor && ov.strokeWidth > 0) {
550
- ctx.strokeStyle = ov.strokeColor;
551
- ctx.lineWidth = ov.strokeWidth;
552
- ctx.stroke();
553
- }
521
+ drawShapePath(ctx, ov.shapeKind, ov.width, ov.height);
522
+ if (ov.fillColor) {
523
+ ctx.fillStyle = ov.fillColor;
524
+ ctx.fill();
525
+ }
526
+ if (ov.strokeColor && ov.strokeWidth > 0) {
527
+ ctx.strokeStyle = ov.strokeColor;
528
+ ctx.lineWidth = ov.strokeWidth;
529
+ ctx.stroke();
530
+ }
554
531
  }
555
532
  function drawShapePath(ctx, kind, w, h) {
556
- const hw = w / 2, hh = h / 2;
557
- ctx.beginPath();
558
- switch (kind) {
559
- case 'rect':
560
- ctx.rect(-hw, -hh, w, h);
561
- break;
562
- case 'circle':
563
- ctx.ellipse(0, 0, hw, hh, 0, 0, Math.PI * 2);
564
- break;
565
- case 'triangle':
566
- ctx.moveTo(0, -hh);
567
- ctx.lineTo(hw, hh);
568
- ctx.lineTo(-hw, hh);
569
- ctx.closePath();
570
- break;
571
- case 'star': {
572
- const spikes = 5, outer = Math.min(hw, hh), inner = outer * 0.4;
573
- for (let i = 0; i < spikes * 2; i++) {
574
- const r = i % 2 === 0 ? outer : inner;
575
- const a = (Math.PI * i) / spikes - Math.PI / 2;
576
- if (i === 0) {
577
- ctx.moveTo(Math.cos(a) * r, Math.sin(a) * r);
578
- }
579
- else {
580
- ctx.lineTo(Math.cos(a) * r, Math.sin(a) * r);
581
- }
582
- }
583
- ctx.closePath();
584
- break;
585
- }
586
- case 'heart': {
587
- const s = Math.min(hw, hh);
588
- ctx.moveTo(0, s * 0.4);
589
- ctx.bezierCurveTo(-s, -s * 0.2, -s * 0.6, -s, 0, -s * 0.4);
590
- ctx.bezierCurveTo(s * 0.6, -s, s, -s * 0.2, 0, s * 0.4);
591
- break;
592
- }
593
- case 'arrow':
594
- ctx.moveTo(-hw, -hh * 0.3);
595
- ctx.lineTo(hw * 0.3, -hh * 0.3);
596
- ctx.lineTo(hw * 0.3, -hh);
597
- ctx.lineTo(hw, 0);
598
- ctx.lineTo(hw * 0.3, hh);
599
- ctx.lineTo(hw * 0.3, hh * 0.3);
600
- ctx.lineTo(-hw, hh * 0.3);
601
- ctx.closePath();
602
- break;
603
- case 'diamond':
604
- ctx.moveTo(0, -hh);
605
- ctx.lineTo(hw, 0);
606
- ctx.lineTo(0, hh);
607
- ctx.lineTo(-hw, 0);
608
- ctx.closePath();
609
- break;
610
- case 'hexagon':
611
- for (let i = 0; i < 6; i++) {
612
- const a = (Math.PI * 2 * i) / 6 - Math.PI / 2;
613
- const r = Math.min(hw, hh);
614
- const px = Math.cos(a) * r, py = Math.sin(a) * r;
615
- if (i === 0) {
616
- ctx.moveTo(px, py);
617
- }
618
- else {
619
- ctx.lineTo(px, py);
620
- }
621
- }
622
- ctx.closePath();
623
- break;
624
- case 'cross': {
625
- const t = Math.min(hw, hh) * 0.3;
626
- ctx.moveTo(-t, -hh);
627
- ctx.lineTo(t, -hh);
628
- ctx.lineTo(t, -t);
629
- ctx.lineTo(hw, -t);
630
- ctx.lineTo(hw, t);
631
- ctx.lineTo(t, t);
632
- ctx.lineTo(t, hh);
633
- ctx.lineTo(-t, hh);
634
- ctx.lineTo(-t, t);
635
- ctx.lineTo(-hw, t);
636
- ctx.lineTo(-hw, -t);
637
- ctx.lineTo(-t, -t);
638
- ctx.closePath();
639
- break;
640
- }
641
- case 'lightning': {
642
- const s = Math.min(hw, hh);
643
- ctx.moveTo(-s * 0.1, -s);
644
- ctx.lineTo(s * 0.5, -s * 0.1);
645
- ctx.lineTo(s * 0.05, -s * 0.1);
646
- ctx.lineTo(s * 0.4, s);
647
- ctx.lineTo(-s * 0.15, s * 0.15);
648
- ctx.lineTo(s * 0.05, s * 0.15);
649
- ctx.lineTo(-s * 0.4, -s * 0.5);
650
- ctx.closePath();
651
- break;
533
+ const hw = w / 2, hh = h / 2;
534
+ ctx.beginPath();
535
+ switch (kind) {
536
+ case "rect":
537
+ ctx.rect(-hw, -hh, w, h);
538
+ break;
539
+ case "circle":
540
+ ctx.ellipse(0, 0, hw, hh, 0, 0, Math.PI * 2);
541
+ break;
542
+ case "triangle":
543
+ ctx.moveTo(0, -hh);
544
+ ctx.lineTo(hw, hh);
545
+ ctx.lineTo(-hw, hh);
546
+ ctx.closePath();
547
+ break;
548
+ case "star": {
549
+ const spikes = 5, outer = Math.min(hw, hh), inner = outer * 0.4;
550
+ for (let i = 0; i < spikes * 2; i++) {
551
+ const r = i % 2 === 0 ? outer : inner;
552
+ const a = Math.PI * i / spikes - Math.PI / 2;
553
+ if (i === 0) {
554
+ ctx.moveTo(Math.cos(a) * r, Math.sin(a) * r);
555
+ } else {
556
+ ctx.lineTo(Math.cos(a) * r, Math.sin(a) * r);
652
557
  }
558
+ }
559
+ ctx.closePath();
560
+ break;
653
561
  }
654
- }
655
- // -- Drawing (freehand strokes) ----------------------------------
656
- function renderDrawing(ctx, ov, w, h) {
657
- for (const stroke of ov.strokes) {
658
- if (stroke.points.length < 2) {
659
- continue;
660
- }
661
- ctx.beginPath();
662
- ctx.strokeStyle = stroke.color;
663
- ctx.lineWidth = stroke.width;
664
- ctx.lineCap = 'round';
665
- ctx.lineJoin = 'round';
666
- ctx.globalAlpha = stroke.opacity;
667
- const [sx, sy] = stroke.points[0];
668
- ctx.moveTo(sx * w, sy * h);
669
- for (let i = 1; i < stroke.points.length; i++) {
670
- const [px, py] = stroke.points[i];
671
- ctx.lineTo(px * w, py * h);
562
+ case "heart": {
563
+ const s = Math.min(hw, hh);
564
+ ctx.moveTo(0, s * 0.4);
565
+ ctx.bezierCurveTo(-s, -s * 0.2, -s * 0.6, -s, 0, -s * 0.4);
566
+ ctx.bezierCurveTo(s * 0.6, -s, s, -s * 0.2, 0, s * 0.4);
567
+ break;
568
+ }
569
+ case "arrow":
570
+ ctx.moveTo(-hw, -hh * 0.3);
571
+ ctx.lineTo(hw * 0.3, -hh * 0.3);
572
+ ctx.lineTo(hw * 0.3, -hh);
573
+ ctx.lineTo(hw, 0);
574
+ ctx.lineTo(hw * 0.3, hh);
575
+ ctx.lineTo(hw * 0.3, hh * 0.3);
576
+ ctx.lineTo(-hw, hh * 0.3);
577
+ ctx.closePath();
578
+ break;
579
+ case "diamond":
580
+ ctx.moveTo(0, -hh);
581
+ ctx.lineTo(hw, 0);
582
+ ctx.lineTo(0, hh);
583
+ ctx.lineTo(-hw, 0);
584
+ ctx.closePath();
585
+ break;
586
+ case "hexagon":
587
+ for (let i = 0; i < 6; i++) {
588
+ const a = Math.PI * 2 * i / 6 - Math.PI / 2;
589
+ const r = Math.min(hw, hh);
590
+ const px = Math.cos(a) * r, py = Math.sin(a) * r;
591
+ if (i === 0) {
592
+ ctx.moveTo(px, py);
593
+ } else {
594
+ ctx.lineTo(px, py);
672
595
  }
673
- ctx.stroke();
596
+ }
597
+ ctx.closePath();
598
+ break;
599
+ case "cross": {
600
+ const t = Math.min(hw, hh) * 0.3;
601
+ ctx.moveTo(-t, -hh);
602
+ ctx.lineTo(t, -hh);
603
+ ctx.lineTo(t, -t);
604
+ ctx.lineTo(hw, -t);
605
+ ctx.lineTo(hw, t);
606
+ ctx.lineTo(t, t);
607
+ ctx.lineTo(t, hh);
608
+ ctx.lineTo(-t, hh);
609
+ ctx.lineTo(-t, t);
610
+ ctx.lineTo(-hw, t);
611
+ ctx.lineTo(-hw, -t);
612
+ ctx.lineTo(-t, -t);
613
+ ctx.closePath();
614
+ break;
615
+ }
616
+ case "lightning": {
617
+ const s = Math.min(hw, hh);
618
+ ctx.moveTo(-s * 0.1, -s);
619
+ ctx.lineTo(s * 0.5, -s * 0.1);
620
+ ctx.lineTo(s * 0.05, -s * 0.1);
621
+ ctx.lineTo(s * 0.4, s);
622
+ ctx.lineTo(-s * 0.15, s * 0.15);
623
+ ctx.lineTo(s * 0.05, s * 0.15);
624
+ ctx.lineTo(-s * 0.4, -s * 0.5);
625
+ ctx.closePath();
626
+ break;
674
627
  }
628
+ }
675
629
  }
676
- // -- Canvas stroke (draw a live, in-progress stroke) -------------
677
- function renderLiveStroke(ctx, points, color, width, canvasW, canvasH) {
678
- if (points.length < 2) {
679
- return;
630
+ function renderDrawing(ctx, ov, w, h) {
631
+ for (const stroke of ov.strokes) {
632
+ if (stroke.points.length < 2) {
633
+ continue;
680
634
  }
681
- ctx.save();
682
635
  ctx.beginPath();
683
- ctx.strokeStyle = color;
684
- ctx.lineWidth = width;
685
- ctx.lineCap = 'round';
686
- ctx.lineJoin = 'round';
687
- ctx.moveTo(points[0][0] * canvasW, points[0][1] * canvasH);
688
- for (let i = 1; i < points.length; i++) {
689
- ctx.lineTo(points[i][0] * canvasW, points[i][1] * canvasH);
636
+ ctx.strokeStyle = stroke.color;
637
+ ctx.lineWidth = stroke.width;
638
+ ctx.lineCap = "round";
639
+ ctx.lineJoin = "round";
640
+ ctx.globalAlpha = stroke.opacity;
641
+ const [sx, sy] = stroke.points[0];
642
+ ctx.moveTo(sx * w, sy * h);
643
+ for (let i = 1; i < stroke.points.length; i++) {
644
+ const [px, py] = stroke.points[i];
645
+ ctx.lineTo(px * w, py * h);
690
646
  }
691
647
  ctx.stroke();
692
- ctx.restore();
648
+ }
649
+ }
650
+ function renderLiveStroke(ctx, points, color, width, canvasW, canvasH) {
651
+ if (points.length < 2) {
652
+ return;
653
+ }
654
+ ctx.save();
655
+ ctx.beginPath();
656
+ ctx.strokeStyle = color;
657
+ ctx.lineWidth = width;
658
+ ctx.lineCap = "round";
659
+ ctx.lineJoin = "round";
660
+ ctx.moveTo(points[0][0] * canvasW, points[0][1] * canvasH);
661
+ for (let i = 1; i < points.length; i++) {
662
+ ctx.lineTo(points[i][0] * canvasW, points[i][1] * canvasH);
663
+ }
664
+ ctx.stroke();
665
+ ctx.restore();
693
666
  }
694
- // -- Frame -------------------------------------------------------
695
667
  function renderFrame(ctx, ov, w, h) {
696
- const t = Math.min(w, h) * ov.thickness;
697
- ctx.strokeStyle = ov.color;
698
- ctx.fillStyle = ov.color;
699
- switch (ov.frameStyle) {
700
- case 'simple':
701
- ctx.lineWidth = t;
702
- ctx.strokeRect(t / 2, t / 2, w - t, h - t);
703
- break;
704
- case 'double':
705
- ctx.lineWidth = t * 0.3;
706
- ctx.strokeRect(t * 0.15, t * 0.15, w - t * 0.3, h - t * 0.3);
707
- ctx.strokeRect(t * 0.7, t * 0.7, w - t * 1.4, h - t * 1.4);
708
- break;
709
- case 'rounded':
710
- ctx.lineWidth = t;
711
- roundRect(ctx, t / 2, t / 2, w - t, h - t, t * 2);
712
- ctx.stroke();
713
- break;
714
- case 'polaroid':
715
- ctx.fillRect(0, 0, w, t);
716
- ctx.fillRect(0, 0, t, h);
717
- ctx.fillRect(w - t, 0, t, h);
718
- ctx.fillRect(0, h - t * 3, w, t * 3);
719
- break;
720
- case 'film': {
721
- ctx.fillRect(0, 0, w, t);
722
- ctx.fillRect(0, h - t, w, t);
723
- const holeW = t * 0.5, holeH = t * 0.7;
724
- const count = Math.floor(w / (t * 1.5)) || 1;
725
- const spacing = w / (count + 1);
726
- ctx.fillStyle = uiCore.resolveCssColor('var(--nice-text, #111)', ctx.canvas);
727
- for (let i = 1; i <= count; i++) {
728
- const cx = spacing * i;
729
- roundRect(ctx, cx - holeW / 2, (t - holeH) / 2, holeW, holeH, 2);
730
- ctx.fill();
731
- roundRect(ctx, cx - holeW / 2, h - t + (t - holeH) / 2, holeW, holeH, 2);
732
- ctx.fill();
733
- }
734
- break;
735
- }
736
- case 'vintage': {
737
- ctx.lineWidth = t * 0.5;
738
- ctx.strokeRect(t * 0.25, t * 0.25, w - t * 0.5, h - t * 0.5);
739
- ctx.lineWidth = 1;
740
- ctx.strokeStyle = ov.color + '88';
741
- ctx.strokeRect(t, t, w - t * 2, h - t * 2);
742
- const os = t * 0.8;
743
- for (const [dx, dy] of [
744
- [0, 0],
745
- [w - os, 0],
746
- [0, h - os],
747
- [w - os, h - os],
748
- ]) {
749
- ctx.beginPath();
750
- ctx.moveTo(dx + os * 0.2, dy + os * 0.2);
751
- ctx.lineTo(dx + os * 0.8, dy + os * 0.2);
752
- ctx.lineTo(dx + os * 0.5, dy + os * 0.5);
753
- ctx.lineTo(dx + os * 0.2, dy + os * 0.8);
754
- ctx.closePath();
755
- ctx.strokeStyle = ov.color;
756
- ctx.lineWidth = 1.5;
757
- ctx.stroke();
758
- }
759
- break;
760
- }
761
- case 'ornate': {
762
- ctx.lineWidth = t * 0.4;
763
- ctx.strokeRect(t * 0.2, t * 0.2, w - t * 0.4, h - t * 0.4);
764
- const cs = t * 1.5;
765
- ctx.lineWidth = 2;
766
- for (const [ox, oy, sx, sy] of [
767
- [0, 0, 1, 1],
768
- [w, 0, -1, 1],
769
- [0, h, 1, -1],
770
- [w, h, -1, -1],
771
- ]) {
772
- ctx.save();
773
- ctx.translate(ox, oy);
774
- ctx.scale(sx, sy);
775
- ctx.beginPath();
776
- ctx.arc(cs * 0.3, cs * 0.3, cs * 0.2, 0, Math.PI * 1.5, true);
777
- ctx.stroke();
778
- ctx.beginPath();
779
- ctx.moveTo(0, cs * 0.5);
780
- ctx.quadraticCurveTo(cs * 0.5, cs * 0.5, cs * 0.5, 0);
781
- ctx.stroke();
782
- ctx.restore();
783
- }
784
- break;
785
- }
786
- case 'shadow':
787
- ctx.shadowColor = uiCore.resolveCssColor('var(--nice-overlay-50, rgba(0, 0, 0, 0.5))', ctx.canvas);
788
- ctx.shadowBlur = t * 2;
789
- ctx.shadowOffsetX = t * 0.5;
790
- ctx.shadowOffsetY = t * 0.5;
791
- ctx.lineWidth = t * 0.3;
792
- ctx.strokeRect(t, t, w - t * 2, h - t * 2);
793
- ctx.shadowColor = 'transparent';
794
- break;
795
- case 'neon':
796
- for (let i = 3; i >= 0; i--) {
797
- ctx.shadowColor = ov.color;
798
- ctx.shadowBlur = t * (i + 1) * 2;
799
- ctx.lineWidth = t * 0.2;
800
- // The white core of the neon tube is drawn INTO the photo (canvas ignores var()).
801
- ctx.strokeStyle = i === 0 ? '#fff' : ov.color;
802
- ctx.globalAlpha = i === 0 ? ov.opacity : 0.3 * ov.opacity;
803
- ctx.strokeRect(t, t, w - t * 2, h - t * 2);
804
- }
805
- ctx.shadowColor = 'transparent';
806
- break;
807
- case 'torn': {
808
- const step = 8;
809
- const seed = (i) => {
810
- const x = Math.sin(i * 127.1 + 311.7) * 43758.5453;
811
- return x - Math.floor(x) - 0.5;
812
- };
813
- // Top
814
- ctx.beginPath();
815
- ctx.moveTo(0, 0);
816
- for (let i = 0, x = 0; x <= w; x += step, i++) {
817
- ctx.lineTo(x, t + seed(i) * t * 0.8);
818
- }
819
- ctx.lineTo(w, 0);
820
- ctx.closePath();
821
- ctx.fill();
822
- // Bottom
823
- ctx.beginPath();
824
- ctx.moveTo(0, h);
825
- for (let i = 0, x = 0; x <= w; x += step, i++) {
826
- ctx.lineTo(x, h - t - seed(i + 1000) * t * 0.8);
827
- }
828
- ctx.lineTo(w, h);
829
- ctx.closePath();
830
- ctx.fill();
831
- // Left
832
- ctx.beginPath();
833
- ctx.moveTo(0, 0);
834
- for (let i = 0, y = 0; y <= h; y += step, i++) {
835
- ctx.lineTo(t + seed(i + 2000) * t * 0.8, y);
836
- }
837
- ctx.lineTo(0, h);
838
- ctx.closePath();
839
- ctx.fill();
840
- // Right
841
- ctx.beginPath();
842
- ctx.moveTo(w, 0);
843
- for (let i = 0, y = 0; y <= h; y += step, i++) {
844
- ctx.lineTo(w - t - seed(i + 3000) * t * 0.8, y);
845
- }
846
- ctx.lineTo(w, h);
847
- ctx.closePath();
848
- ctx.fill();
849
- break;
850
- }
668
+ const t = Math.min(w, h) * ov.thickness;
669
+ ctx.strokeStyle = ov.color;
670
+ ctx.fillStyle = ov.color;
671
+ switch (ov.frameStyle) {
672
+ case "simple":
673
+ ctx.lineWidth = t;
674
+ ctx.strokeRect(t / 2, t / 2, w - t, h - t);
675
+ break;
676
+ case "double":
677
+ ctx.lineWidth = t * 0.3;
678
+ ctx.strokeRect(t * 0.15, t * 0.15, w - t * 0.3, h - t * 0.3);
679
+ ctx.strokeRect(t * 0.7, t * 0.7, w - t * 1.4, h - t * 1.4);
680
+ break;
681
+ case "rounded":
682
+ ctx.lineWidth = t;
683
+ roundRect(ctx, t / 2, t / 2, w - t, h - t, t * 2);
684
+ ctx.stroke();
685
+ break;
686
+ case "polaroid":
687
+ ctx.fillRect(0, 0, w, t);
688
+ ctx.fillRect(0, 0, t, h);
689
+ ctx.fillRect(w - t, 0, t, h);
690
+ ctx.fillRect(0, h - t * 3, w, t * 3);
691
+ break;
692
+ case "film": {
693
+ ctx.fillRect(0, 0, w, t);
694
+ ctx.fillRect(0, h - t, w, t);
695
+ const holeW = t * 0.5, holeH = t * 0.7;
696
+ const count = Math.floor(w / (t * 1.5)) || 1;
697
+ const spacing = w / (count + 1);
698
+ ctx.fillStyle = uiCore.resolveCssColor("var(--nice-text, #111)", ctx.canvas);
699
+ for (let i = 1; i <= count; i++) {
700
+ const cx = spacing * i;
701
+ roundRect(ctx, cx - holeW / 2, (t - holeH) / 2, holeW, holeH, 2);
702
+ ctx.fill();
703
+ roundRect(ctx, cx - holeW / 2, h - t + (t - holeH) / 2, holeW, holeH, 2);
704
+ ctx.fill();
705
+ }
706
+ break;
851
707
  }
708
+ case "vintage": {
709
+ ctx.lineWidth = t * 0.5;
710
+ ctx.strokeRect(t * 0.25, t * 0.25, w - t * 0.5, h - t * 0.5);
711
+ ctx.lineWidth = 1;
712
+ ctx.strokeStyle = ov.color + "88";
713
+ ctx.strokeRect(t, t, w - t * 2, h - t * 2);
714
+ const os = t * 0.8;
715
+ for (const [dx, dy] of [
716
+ [0, 0],
717
+ [w - os, 0],
718
+ [0, h - os],
719
+ [w - os, h - os]
720
+ ]) {
721
+ ctx.beginPath();
722
+ ctx.moveTo(dx + os * 0.2, dy + os * 0.2);
723
+ ctx.lineTo(dx + os * 0.8, dy + os * 0.2);
724
+ ctx.lineTo(dx + os * 0.5, dy + os * 0.5);
725
+ ctx.lineTo(dx + os * 0.2, dy + os * 0.8);
726
+ ctx.closePath();
727
+ ctx.strokeStyle = ov.color;
728
+ ctx.lineWidth = 1.5;
729
+ ctx.stroke();
730
+ }
731
+ break;
732
+ }
733
+ case "ornate": {
734
+ ctx.lineWidth = t * 0.4;
735
+ ctx.strokeRect(t * 0.2, t * 0.2, w - t * 0.4, h - t * 0.4);
736
+ const cs = t * 1.5;
737
+ ctx.lineWidth = 2;
738
+ for (const [ox, oy, sx, sy] of [
739
+ [0, 0, 1, 1],
740
+ [w, 0, -1, 1],
741
+ [0, h, 1, -1],
742
+ [w, h, -1, -1]
743
+ ]) {
744
+ ctx.save();
745
+ ctx.translate(ox, oy);
746
+ ctx.scale(sx, sy);
747
+ ctx.beginPath();
748
+ ctx.arc(cs * 0.3, cs * 0.3, cs * 0.2, 0, Math.PI * 1.5, true);
749
+ ctx.stroke();
750
+ ctx.beginPath();
751
+ ctx.moveTo(0, cs * 0.5);
752
+ ctx.quadraticCurveTo(cs * 0.5, cs * 0.5, cs * 0.5, 0);
753
+ ctx.stroke();
754
+ ctx.restore();
755
+ }
756
+ break;
757
+ }
758
+ case "shadow":
759
+ ctx.shadowColor = uiCore.resolveCssColor("var(--nice-overlay-50, rgba(0, 0, 0, 0.5))", ctx.canvas);
760
+ ctx.shadowBlur = t * 2;
761
+ ctx.shadowOffsetX = t * 0.5;
762
+ ctx.shadowOffsetY = t * 0.5;
763
+ ctx.lineWidth = t * 0.3;
764
+ ctx.strokeRect(t, t, w - t * 2, h - t * 2);
765
+ ctx.shadowColor = "transparent";
766
+ break;
767
+ case "neon":
768
+ for (let i = 3; i >= 0; i--) {
769
+ ctx.shadowColor = ov.color;
770
+ ctx.shadowBlur = t * (i + 1) * 2;
771
+ ctx.lineWidth = t * 0.2;
772
+ ctx.strokeStyle = i === 0 ? "#fff" : ov.color;
773
+ ctx.globalAlpha = i === 0 ? ov.opacity : 0.3 * ov.opacity;
774
+ ctx.strokeRect(t, t, w - t * 2, h - t * 2);
775
+ }
776
+ ctx.shadowColor = "transparent";
777
+ break;
778
+ case "torn": {
779
+ const step = 8;
780
+ const seed = (i) => {
781
+ const x = Math.sin(i * 127.1 + 311.7) * 43758.5453;
782
+ return x - Math.floor(x) - 0.5;
783
+ };
784
+ ctx.beginPath();
785
+ ctx.moveTo(0, 0);
786
+ for (let i = 0, x = 0; x <= w; x += step, i++) {
787
+ ctx.lineTo(x, t + seed(i) * t * 0.8);
788
+ }
789
+ ctx.lineTo(w, 0);
790
+ ctx.closePath();
791
+ ctx.fill();
792
+ ctx.beginPath();
793
+ ctx.moveTo(0, h);
794
+ for (let i = 0, x = 0; x <= w; x += step, i++) {
795
+ ctx.lineTo(x, h - t - seed(i + 1e3) * t * 0.8);
796
+ }
797
+ ctx.lineTo(w, h);
798
+ ctx.closePath();
799
+ ctx.fill();
800
+ ctx.beginPath();
801
+ ctx.moveTo(0, 0);
802
+ for (let i = 0, y = 0; y <= h; y += step, i++) {
803
+ ctx.lineTo(t + seed(i + 2e3) * t * 0.8, y);
804
+ }
805
+ ctx.lineTo(0, h);
806
+ ctx.closePath();
807
+ ctx.fill();
808
+ ctx.beginPath();
809
+ ctx.moveTo(w, 0);
810
+ for (let i = 0, y = 0; y <= h; y += step, i++) {
811
+ ctx.lineTo(w - t - seed(i + 3e3) * t * 0.8, y);
812
+ }
813
+ ctx.lineTo(w, h);
814
+ ctx.closePath();
815
+ ctx.fill();
816
+ break;
817
+ }
818
+ }
852
819
  }
853
820
  function roundRect(ctx, x, y, w, h, r) {
854
- ctx.beginPath();
855
- ctx.moveTo(x + r, y);
856
- ctx.lineTo(x + w - r, y);
857
- ctx.quadraticCurveTo(x + w, y, x + w, y + r);
858
- ctx.lineTo(x + w, y + h - r);
859
- ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
860
- ctx.lineTo(x + r, y + h);
861
- ctx.quadraticCurveTo(x, y + h, x, y + h - r);
862
- ctx.lineTo(x, y + r);
863
- ctx.quadraticCurveTo(x, y, x + r, y);
864
- ctx.closePath();
821
+ ctx.beginPath();
822
+ ctx.moveTo(x + r, y);
823
+ ctx.lineTo(x + w - r, y);
824
+ ctx.quadraticCurveTo(x + w, y, x + w, y + r);
825
+ ctx.lineTo(x + w, y + h - r);
826
+ ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
827
+ ctx.lineTo(x + r, y + h);
828
+ ctx.quadraticCurveTo(x, y + h, x, y + h - r);
829
+ ctx.lineTo(x, y + r);
830
+ ctx.quadraticCurveTo(x, y, x + r, y);
831
+ ctx.closePath();
865
832
  }
866
- // ═══════════════════════════════════════════════════════════════
867
- // ── Hit testing
868
- // ═══════════════════════════════════════════════════════════════
869
- /**
870
- * Returns `true` when the point (px, py) in canvas-fraction space
871
- * hits the bounding box of the overlay (ignoring rotation).
872
- */
873
833
  function hitTestOverlay(ov, px, py, _w, _h) {
874
- if (ov.type === 'drawing' || ov.type === 'frame') {
875
- return false;
876
- }
877
- let hitW, hitH;
878
- switch (ov.type) {
879
- case 'emoji':
880
- hitW = hitH = (ov.fontSize * ov.scaleX) / _w;
881
- break;
882
- case 'text': {
883
- hitW = (ov.text.length * ov.fontSize * 0.55 * ov.scaleX) / _w;
884
- hitH = (ov.fontSize * 1.3 * ov.scaleY) / _h;
885
- break;
886
- }
887
- case 'shape':
888
- hitW = (ov.width * ov.scaleX) / _w;
889
- hitH = (ov.height * ov.scaleY) / _h;
890
- break;
891
- default:
892
- return false;
834
+ if (ov.type === "drawing" || ov.type === "frame") {
835
+ return false;
836
+ }
837
+ let hitW, hitH;
838
+ switch (ov.type) {
839
+ case "emoji":
840
+ hitW = hitH = ov.fontSize * ov.scaleX / _w;
841
+ break;
842
+ case "text": {
843
+ hitW = ov.text.length * ov.fontSize * 0.55 * ov.scaleX / _w;
844
+ hitH = ov.fontSize * 1.3 * ov.scaleY / _h;
845
+ break;
893
846
  }
894
- const margin = 15 / _w;
895
- return Math.abs(px - ov.x) < hitW / 2 + margin && Math.abs(py - ov.y) < hitH / 2 + margin;
847
+ case "shape":
848
+ hitW = ov.width * ov.scaleX / _w;
849
+ hitH = ov.height * ov.scaleY / _h;
850
+ break;
851
+ default:
852
+ return false;
853
+ }
854
+ const margin = 15 / _w;
855
+ return Math.abs(px - ov.x) < hitW / 2 + margin && Math.abs(py - ov.y) < hitH / 2 + margin;
896
856
  }
897
- // ═══════════════════════════════════════════════════════════════
898
- // ── Factory helpers
899
- // ═══════════════════════════════════════════════════════════════
900
857
  function createEmojiOverlay(emoji, canvasW, canvasH) {
901
- return {
902
- id: generateOverlayId(),
903
- type: 'emoji',
904
- x: 0.5,
905
- y: 0.5,
906
- scaleX: 1,
907
- scaleY: 1,
908
- rotation: 0,
909
- opacity: 1,
910
- emoji,
911
- fontSize: Math.round(Math.min(canvasW, canvasH) * 0.08),
912
- };
858
+ return {
859
+ id: generateOverlayId(),
860
+ type: "emoji",
861
+ x: 0.5,
862
+ y: 0.5,
863
+ scaleX: 1,
864
+ scaleY: 1,
865
+ rotation: 0,
866
+ opacity: 1,
867
+ emoji,
868
+ fontSize: Math.round(Math.min(canvasW, canvasH) * 0.08)
869
+ };
913
870
  }
914
- function createTextOverlay(text = 'Text') {
915
- return {
916
- id: generateOverlayId(),
917
- type: 'text',
918
- x: 0.5,
919
- y: 0.5,
920
- scaleX: 1,
921
- scaleY: 1,
922
- rotation: 0,
923
- opacity: 1,
924
- text,
925
- fontFamily: 'Arial',
926
- fontSize: 36,
927
- // Overlay colours are paint drawn into the photo on canvas (which ignores var()),
928
- // and some are extended with an alpha byte (`color + '88'`) — so hex.
929
- color: '#ffffff',
930
- bold: true,
931
- italic: false,
932
- outlineColor: '#000000',
933
- outlineWidth: 2,
934
- shadow: null,
935
- background: null,
936
- backgroundPadding: 6,
937
- };
871
+ function createTextOverlay(text = "Text") {
872
+ return {
873
+ id: generateOverlayId(),
874
+ type: "text",
875
+ x: 0.5,
876
+ y: 0.5,
877
+ scaleX: 1,
878
+ scaleY: 1,
879
+ rotation: 0,
880
+ opacity: 1,
881
+ text,
882
+ fontFamily: "Arial",
883
+ fontSize: 36,
884
+ // Overlay colours are paint drawn into the photo on canvas (which ignores var()),
885
+ // and some are extended with an alpha byte (`color + '88'`) — so hex.
886
+ color: "#ffffff",
887
+ bold: true,
888
+ italic: false,
889
+ outlineColor: "#000000",
890
+ outlineWidth: 2,
891
+ shadow: null,
892
+ background: null,
893
+ backgroundPadding: 6
894
+ };
938
895
  }
939
896
  function createShapeOverlay(kind) {
940
- return {
941
- id: generateOverlayId(),
942
- type: 'shape',
943
- x: 0.5,
944
- y: 0.5,
945
- scaleX: 1,
946
- scaleY: 1,
947
- rotation: 0,
948
- opacity: 1,
949
- shapeKind: kind,
950
- width: 100,
951
- height: 100,
952
- fillColor: null,
953
- strokeColor: '#ffffff',
954
- strokeWidth: 3,
955
- };
897
+ return {
898
+ id: generateOverlayId(),
899
+ type: "shape",
900
+ x: 0.5,
901
+ y: 0.5,
902
+ scaleX: 1,
903
+ scaleY: 1,
904
+ rotation: 0,
905
+ opacity: 1,
906
+ shapeKind: kind,
907
+ width: 100,
908
+ height: 100,
909
+ fillColor: null,
910
+ strokeColor: "#ffffff",
911
+ strokeWidth: 3
912
+ };
956
913
  }
957
914
  function createDrawingOverlay() {
958
- return {
959
- id: generateOverlayId(),
960
- type: 'drawing',
961
- x: 0,
962
- y: 0,
963
- scaleX: 1,
964
- scaleY: 1,
965
- rotation: 0,
966
- opacity: 1,
967
- strokes: [],
968
- };
915
+ return {
916
+ id: generateOverlayId(),
917
+ type: "drawing",
918
+ x: 0,
919
+ y: 0,
920
+ scaleX: 1,
921
+ scaleY: 1,
922
+ rotation: 0,
923
+ opacity: 1,
924
+ strokes: []
925
+ };
969
926
  }
970
- function createFrameOverlay(style, color = '#ffffff') {
971
- return {
972
- id: generateOverlayId(),
973
- type: 'frame',
974
- x: 0,
975
- y: 0,
976
- scaleX: 1,
977
- scaleY: 1,
978
- rotation: 0,
979
- opacity: 1,
980
- frameStyle: style,
981
- color,
982
- thickness: 0.03,
983
- };
927
+ function createFrameOverlay(style, color = "#ffffff") {
928
+ return {
929
+ id: generateOverlayId(),
930
+ type: "frame",
931
+ x: 0,
932
+ y: 0,
933
+ scaleX: 1,
934
+ scaleY: 1,
935
+ rotation: 0,
936
+ opacity: 1,
937
+ frameStyle: style,
938
+ color,
939
+ thickness: 0.03
940
+ };
984
941
  }
985
942
  function serializeEditorState(state) {
986
- return JSON.stringify(state);
943
+ return JSON.stringify(state);
987
944
  }
988
945
  function deserializeEditorState(json) {
989
- try {
990
- return JSON.parse(json);
991
- }
992
- catch (_a) {
993
- /* Expected: JSON.parse may fail on malformed serialized state */
994
- return null;
995
- }
946
+ try {
947
+ return JSON.parse(json);
948
+ } catch {
949
+ return null;
950
+ }
996
951
  }
997
-
998
952
  exports.EMOJI_CATALOG = EMOJI_CATALOG;
999
953
  exports.FRAME_CATALOG = FRAME_CATALOG;
1000
954
  exports.SHAPE_CATALOG = SHAPE_CATALOG;