@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,996 +1,965 @@
1
- import { resolveCssColor } from '@nice2dev/ui-core';
2
-
3
- /**
4
- * photoOverlays.ts — Overlay / layer engine for PhotoEditor.
5
- *
6
- * Handles emojis, text, shapes, freehand drawing strokes, and decorative frames
7
- * as positioned layers on top of the base (filtered) image.
8
- *
9
- * All positions use **fractional coordinates** (0 … 1) relative to the rendered
10
- * canvas so overlays survive crop / resize changes.
11
- */
1
+ import { resolveCssColor } from "@nice2dev/ui-core";
12
2
  const EMOJI_CATALOG = [
13
- {
14
- id: 'faces',
15
- label: 'Faces',
16
- icon: '😊',
17
- emojis: [
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
- '🤡',
87
- '👹',
88
- '👻',
89
- '👽',
90
- '🤖',
91
- ],
92
- },
93
- {
94
- id: 'gestures',
95
- label: 'Gestures',
96
- icon: '👍',
97
- emojis: [
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
- '👃',
134
- '👀',
135
- '👁️',
136
- '👅',
137
- '👄',
138
- ],
139
- },
140
- {
141
- id: 'hearts',
142
- label: 'Hearts',
143
- icon: '❤️',
144
- emojis: [
145
- '❤️',
146
- '🧡',
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
- id: 'animals',
174
- label: 'Animals',
175
- icon: '🐶',
176
- emojis: [
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
- '🐘',
210
- '🦒',
211
- '🦘',
212
- '🐴',
213
- '🦄',
214
- ],
215
- },
216
- {
217
- id: 'party',
218
- label: 'Party',
219
- icon: '🎉',
220
- emojis: [
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
- '🎯',
253
- '🎳',
254
- '🕹️',
255
- '🎮',
256
- '🎰',
257
- ],
258
- },
259
- {
260
- id: 'food',
261
- label: 'Food',
262
- icon: '🍕',
263
- emojis: [
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
- '🍷',
315
- '🍸',
316
- '🍹',
317
- '🧃',
318
- '🍾',
319
- ],
320
- },
321
- {
322
- id: 'nature',
323
- label: 'Nature',
324
- icon: '🌸',
325
- emojis: [
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
- '🌙',
362
- '🌟',
363
- '💫',
364
- '✨',
365
- '☄️',
366
- ],
367
- },
368
- {
369
- id: 'symbols',
370
- label: 'Symbols',
371
- icon: '⚡',
372
- emojis: [
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
- '🔻',
412
- '🔶',
413
- '🔷',
414
- '🏳️',
415
- '🏴',
416
- '🚩',
417
- '🏳️‍🌈',
418
- ],
419
- },
3
+ {
4
+ id: "faces",
5
+ label: "Faces",
6
+ icon: "😊",
7
+ emojis: [
8
+ "😀",
9
+ "😃",
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
+ id: "gestures",
85
+ label: "Gestures",
86
+ icon: "👍",
87
+ emojis: [
88
+ "👍",
89
+ "👎",
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
+ id: "hearts",
132
+ label: "Hearts",
133
+ icon: "❤️",
134
+ emojis: [
135
+ "❤️",
136
+ "🧡",
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
+ id: "animals",
164
+ label: "Animals",
165
+ icon: "🐶",
166
+ emojis: [
167
+ "🐶",
168
+ "🐱",
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
+ id: "party",
208
+ label: "Party",
209
+ icon: "🎉",
210
+ emojis: [
211
+ "🎉",
212
+ "🎊",
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
+ id: "food",
251
+ label: "Food",
252
+ icon: "🍕",
253
+ emojis: [
254
+ "🍏",
255
+ "🍎",
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
+ id: "nature",
313
+ label: "Nature",
314
+ icon: "🌸",
315
+ emojis: [
316
+ "🌸",
317
+ "🌺",
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
+ id: "symbols",
360
+ label: "Symbols",
361
+ icon: "⚡",
362
+ emojis: [
363
+ "⚡",
364
+ "💥",
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
+ }
420
410
  ];
421
411
  const SHAPE_CATALOG = [
422
- { id: 'rect', label: 'Rectangle', icon: 'square' },
423
- { id: 'circle', label: 'Circle', icon: 'circle' },
424
- { id: 'triangle', label: 'Triangle', icon: 'triangle' },
425
- { id: 'star', label: 'Star', icon: 'star' },
426
- { id: 'heart', label: 'Heart', icon: 'heart' },
427
- { id: 'arrow', label: 'Arrow', icon: 'arrow-right' },
428
- { id: 'diamond', label: 'Diamond', icon: 'diamond' },
429
- { id: 'hexagon', label: 'Hexagon', icon: 'hexagon' },
430
- { id: 'cross', label: 'Cross', icon: 'plus' },
431
- { id: 'lightning', label: 'Lightning', icon: 'zap' },
412
+ { id: "rect", label: "Rectangle", icon: "square" },
413
+ { id: "circle", label: "Circle", icon: "circle" },
414
+ { id: "triangle", label: "Triangle", icon: "triangle" },
415
+ { id: "star", label: "Star", icon: "star" },
416
+ { id: "heart", label: "Heart", icon: "heart" },
417
+ { id: "arrow", label: "Arrow", icon: "arrow-right" },
418
+ { id: "diamond", label: "Diamond", icon: "diamond" },
419
+ { id: "hexagon", label: "Hexagon", icon: "hexagon" },
420
+ { id: "cross", label: "Cross", icon: "plus" },
421
+ { id: "lightning", label: "Lightning", icon: "zap" }
432
422
  ];
433
423
  const FRAME_CATALOG = [
434
- { id: 'simple', label: 'Simple', icon: 'square' },
435
- { id: 'double', label: 'Double', icon: 'copy' },
436
- { id: 'rounded', label: 'Rounded', icon: 'card' },
437
- { id: 'polaroid', label: 'Polaroid', icon: 'camera' },
438
- { id: 'film', label: 'Film', icon: 'video' },
439
- { id: 'vintage', label: 'Vintage', icon: 'image' },
440
- { id: 'ornate', label: 'Ornate', icon: 'hexagon' },
441
- { id: 'shadow', label: 'Shadow', icon: 'moon' },
442
- { id: 'neon', label: 'Neon', icon: 'lightbulb' },
443
- { id: 'torn', label: 'Torn', icon: 'file' },
424
+ { id: "simple", label: "Simple", icon: "square" },
425
+ { id: "double", label: "Double", icon: "copy" },
426
+ { id: "rounded", label: "Rounded", icon: "card" },
427
+ { id: "polaroid", label: "Polaroid", icon: "camera" },
428
+ { id: "film", label: "Film", icon: "video" },
429
+ { id: "vintage", label: "Vintage", icon: "image" },
430
+ { id: "ornate", label: "Ornate", icon: "hexagon" },
431
+ { id: "shadow", label: "Shadow", icon: "moon" },
432
+ { id: "neon", label: "Neon", icon: "lightbulb" },
433
+ { id: "torn", label: "Torn", icon: "file" }
444
434
  ];
445
- // ═══════════════════════════════════════════════════════════════
446
- // ── ID generation
447
- // ═══════════════════════════════════════════════════════════════
448
435
  let _nextId = 1;
449
436
  function generateOverlayId() {
450
- return `ov-${_nextId++}-${Date.now().toString(36)}`;
437
+ return `ov-${_nextId++}-${Date.now().toString(36)}`;
451
438
  }
452
- // ═══════════════════════════════════════════════════════════════
453
- // ── Rendering
454
- // ═══════════════════════════════════════════════════════════════
455
- /** Render all overlays to a canvas context. */
456
439
  function renderAllOverlays(ctx, overlays, w, h) {
457
- for (const ov of overlays) {
458
- renderSingleOverlay(ctx, ov, w, h);
459
- }
440
+ for (const ov of overlays) {
441
+ renderSingleOverlay(ctx, ov, w, h);
442
+ }
460
443
  }
461
444
  function renderSingleOverlay(ctx, ov, w, h) {
462
- ctx.save();
463
- ctx.globalAlpha = ov.opacity;
464
- if (ov.type === 'drawing') {
465
- renderDrawing(ctx, ov, w, h);
466
- ctx.restore();
467
- return;
468
- }
469
- if (ov.type === 'frame') {
470
- renderFrame(ctx, ov, w, h);
471
- ctx.restore();
472
- return;
473
- }
474
- // Position-based overlays (emoji, text, shape)
475
- const cx = ov.x * w;
476
- const cy = ov.y * h;
477
- ctx.translate(cx, cy);
478
- ctx.rotate((ov.rotation * Math.PI) / 180);
479
- ctx.scale(ov.scaleX, ov.scaleY);
480
- switch (ov.type) {
481
- case 'emoji':
482
- renderEmoji(ctx, ov);
483
- break;
484
- case 'text':
485
- renderText(ctx, ov);
486
- break;
487
- case 'shape':
488
- renderShape(ctx, ov);
489
- break;
490
- }
445
+ ctx.save();
446
+ ctx.globalAlpha = ov.opacity;
447
+ if (ov.type === "drawing") {
448
+ renderDrawing(ctx, ov, w, h);
449
+ ctx.restore();
450
+ return;
451
+ }
452
+ if (ov.type === "frame") {
453
+ renderFrame(ctx, ov, w, h);
491
454
  ctx.restore();
455
+ return;
456
+ }
457
+ const cx = ov.x * w;
458
+ const cy = ov.y * h;
459
+ ctx.translate(cx, cy);
460
+ ctx.rotate(ov.rotation * Math.PI / 180);
461
+ ctx.scale(ov.scaleX, ov.scaleY);
462
+ switch (ov.type) {
463
+ case "emoji":
464
+ renderEmoji(ctx, ov);
465
+ break;
466
+ case "text":
467
+ renderText(ctx, ov);
468
+ break;
469
+ case "shape":
470
+ renderShape(ctx, ov);
471
+ break;
472
+ }
473
+ ctx.restore();
492
474
  }
493
- // -- Emoji -------------------------------------------------------
494
475
  function renderEmoji(ctx, ov) {
495
- ctx.font = `${ov.fontSize}px "Segoe UI Emoji","Apple Color Emoji","Noto Color Emoji",sans-serif`;
496
- ctx.textAlign = 'center';
497
- ctx.textBaseline = 'middle';
498
- ctx.fillText(ov.emoji, 0, 0);
476
+ ctx.font = `${ov.fontSize}px "Segoe UI Emoji","Apple Color Emoji","Noto Color Emoji",sans-serif`;
477
+ ctx.textAlign = "center";
478
+ ctx.textBaseline = "middle";
479
+ ctx.fillText(ov.emoji, 0, 0);
499
480
  }
500
- // -- Text --------------------------------------------------------
501
481
  function renderText(ctx, ov) {
502
- const style = `${ov.italic ? 'italic ' : ''}${ov.bold ? 'bold ' : ''}`;
503
- ctx.font = `${style}${ov.fontSize}px ${ov.fontFamily || 'Arial'},sans-serif`;
504
- ctx.textAlign = 'center';
505
- ctx.textBaseline = 'middle';
506
- const lines = ov.text.split('\n');
507
- const lineH = ov.fontSize * 1.25;
508
- const totalH = lineH * lines.length;
509
- const startY = -(totalH / 2) + lineH / 2;
510
- // Background
511
- if (ov.background) {
512
- const maxW = Math.max(...lines.map((l) => ctx.measureText(l).width));
513
- const pad = ov.backgroundPadding;
514
- ctx.fillStyle = ov.background;
515
- const rx = -(maxW / 2 + pad);
516
- const ry = startY - lineH / 2 - pad;
517
- roundRect(ctx, rx, ry, maxW + pad * 2, totalH + pad * 2, 6);
518
- ctx.fill();
519
- }
520
- // Shadow
521
- if (ov.shadow) {
522
- ctx.shadowColor = ov.shadow;
523
- ctx.shadowBlur = 4;
524
- ctx.shadowOffsetX = 2;
525
- ctx.shadowOffsetY = 2;
526
- }
527
- for (let i = 0; i < lines.length; i++) {
528
- const ly = startY + i * lineH;
529
- if (ov.outlineColor && ov.outlineWidth > 0) {
530
- ctx.strokeStyle = ov.outlineColor;
531
- ctx.lineWidth = ov.outlineWidth;
532
- ctx.lineJoin = 'round';
533
- ctx.strokeText(lines[i], 0, ly);
534
- }
535
- ctx.fillStyle = ov.color;
536
- ctx.fillText(lines[i], 0, ly);
482
+ const style = `${ov.italic ? "italic " : ""}${ov.bold ? "bold " : ""}`;
483
+ ctx.font = `${style}${ov.fontSize}px ${ov.fontFamily || "Arial"},sans-serif`;
484
+ ctx.textAlign = "center";
485
+ ctx.textBaseline = "middle";
486
+ const lines = ov.text.split("\n");
487
+ const lineH = ov.fontSize * 1.25;
488
+ const totalH = lineH * lines.length;
489
+ const startY = -(totalH / 2) + lineH / 2;
490
+ if (ov.background) {
491
+ const maxW = Math.max(...lines.map((l) => ctx.measureText(l).width));
492
+ const pad = ov.backgroundPadding;
493
+ ctx.fillStyle = ov.background;
494
+ const rx = -(maxW / 2 + pad);
495
+ const ry = startY - lineH / 2 - pad;
496
+ roundRect(ctx, rx, ry, maxW + pad * 2, totalH + pad * 2, 6);
497
+ ctx.fill();
498
+ }
499
+ if (ov.shadow) {
500
+ ctx.shadowColor = ov.shadow;
501
+ ctx.shadowBlur = 4;
502
+ ctx.shadowOffsetX = 2;
503
+ ctx.shadowOffsetY = 2;
504
+ }
505
+ for (let i = 0; i < lines.length; i++) {
506
+ const ly = startY + i * lineH;
507
+ if (ov.outlineColor && ov.outlineWidth > 0) {
508
+ ctx.strokeStyle = ov.outlineColor;
509
+ ctx.lineWidth = ov.outlineWidth;
510
+ ctx.lineJoin = "round";
511
+ ctx.strokeText(lines[i], 0, ly);
537
512
  }
538
- ctx.shadowColor = 'transparent';
513
+ ctx.fillStyle = ov.color;
514
+ ctx.fillText(lines[i], 0, ly);
515
+ }
516
+ ctx.shadowColor = "transparent";
539
517
  }
540
- // -- Shape -------------------------------------------------------
541
518
  function renderShape(ctx, ov) {
542
- drawShapePath(ctx, ov.shapeKind, ov.width, ov.height);
543
- if (ov.fillColor) {
544
- ctx.fillStyle = ov.fillColor;
545
- ctx.fill();
546
- }
547
- if (ov.strokeColor && ov.strokeWidth > 0) {
548
- ctx.strokeStyle = ov.strokeColor;
549
- ctx.lineWidth = ov.strokeWidth;
550
- ctx.stroke();
551
- }
519
+ drawShapePath(ctx, ov.shapeKind, ov.width, ov.height);
520
+ if (ov.fillColor) {
521
+ ctx.fillStyle = ov.fillColor;
522
+ ctx.fill();
523
+ }
524
+ if (ov.strokeColor && ov.strokeWidth > 0) {
525
+ ctx.strokeStyle = ov.strokeColor;
526
+ ctx.lineWidth = ov.strokeWidth;
527
+ ctx.stroke();
528
+ }
552
529
  }
553
530
  function drawShapePath(ctx, kind, w, h) {
554
- const hw = w / 2, hh = h / 2;
555
- ctx.beginPath();
556
- switch (kind) {
557
- case 'rect':
558
- ctx.rect(-hw, -hh, w, h);
559
- break;
560
- case 'circle':
561
- ctx.ellipse(0, 0, hw, hh, 0, 0, Math.PI * 2);
562
- break;
563
- case 'triangle':
564
- ctx.moveTo(0, -hh);
565
- ctx.lineTo(hw, hh);
566
- ctx.lineTo(-hw, hh);
567
- ctx.closePath();
568
- break;
569
- case 'star': {
570
- const spikes = 5, outer = Math.min(hw, hh), inner = outer * 0.4;
571
- for (let i = 0; i < spikes * 2; i++) {
572
- const r = i % 2 === 0 ? outer : inner;
573
- const a = (Math.PI * i) / spikes - Math.PI / 2;
574
- if (i === 0) {
575
- ctx.moveTo(Math.cos(a) * r, Math.sin(a) * r);
576
- }
577
- else {
578
- ctx.lineTo(Math.cos(a) * r, Math.sin(a) * r);
579
- }
580
- }
581
- ctx.closePath();
582
- break;
583
- }
584
- case 'heart': {
585
- const s = Math.min(hw, hh);
586
- ctx.moveTo(0, s * 0.4);
587
- ctx.bezierCurveTo(-s, -s * 0.2, -s * 0.6, -s, 0, -s * 0.4);
588
- ctx.bezierCurveTo(s * 0.6, -s, s, -s * 0.2, 0, s * 0.4);
589
- break;
590
- }
591
- case 'arrow':
592
- ctx.moveTo(-hw, -hh * 0.3);
593
- ctx.lineTo(hw * 0.3, -hh * 0.3);
594
- ctx.lineTo(hw * 0.3, -hh);
595
- ctx.lineTo(hw, 0);
596
- ctx.lineTo(hw * 0.3, hh);
597
- ctx.lineTo(hw * 0.3, hh * 0.3);
598
- ctx.lineTo(-hw, hh * 0.3);
599
- ctx.closePath();
600
- break;
601
- case 'diamond':
602
- ctx.moveTo(0, -hh);
603
- ctx.lineTo(hw, 0);
604
- ctx.lineTo(0, hh);
605
- ctx.lineTo(-hw, 0);
606
- ctx.closePath();
607
- break;
608
- case 'hexagon':
609
- for (let i = 0; i < 6; i++) {
610
- const a = (Math.PI * 2 * i) / 6 - Math.PI / 2;
611
- const r = Math.min(hw, hh);
612
- const px = Math.cos(a) * r, py = Math.sin(a) * r;
613
- if (i === 0) {
614
- ctx.moveTo(px, py);
615
- }
616
- else {
617
- ctx.lineTo(px, py);
618
- }
619
- }
620
- ctx.closePath();
621
- break;
622
- case 'cross': {
623
- const t = Math.min(hw, hh) * 0.3;
624
- ctx.moveTo(-t, -hh);
625
- ctx.lineTo(t, -hh);
626
- ctx.lineTo(t, -t);
627
- ctx.lineTo(hw, -t);
628
- ctx.lineTo(hw, t);
629
- ctx.lineTo(t, t);
630
- ctx.lineTo(t, hh);
631
- ctx.lineTo(-t, hh);
632
- ctx.lineTo(-t, t);
633
- ctx.lineTo(-hw, t);
634
- ctx.lineTo(-hw, -t);
635
- ctx.lineTo(-t, -t);
636
- ctx.closePath();
637
- break;
638
- }
639
- case 'lightning': {
640
- const s = Math.min(hw, hh);
641
- ctx.moveTo(-s * 0.1, -s);
642
- ctx.lineTo(s * 0.5, -s * 0.1);
643
- ctx.lineTo(s * 0.05, -s * 0.1);
644
- ctx.lineTo(s * 0.4, s);
645
- ctx.lineTo(-s * 0.15, s * 0.15);
646
- ctx.lineTo(s * 0.05, s * 0.15);
647
- ctx.lineTo(-s * 0.4, -s * 0.5);
648
- ctx.closePath();
649
- break;
531
+ const hw = w / 2, hh = h / 2;
532
+ ctx.beginPath();
533
+ switch (kind) {
534
+ case "rect":
535
+ ctx.rect(-hw, -hh, w, h);
536
+ break;
537
+ case "circle":
538
+ ctx.ellipse(0, 0, hw, hh, 0, 0, Math.PI * 2);
539
+ break;
540
+ case "triangle":
541
+ ctx.moveTo(0, -hh);
542
+ ctx.lineTo(hw, hh);
543
+ ctx.lineTo(-hw, hh);
544
+ ctx.closePath();
545
+ break;
546
+ case "star": {
547
+ const spikes = 5, outer = Math.min(hw, hh), inner = outer * 0.4;
548
+ for (let i = 0; i < spikes * 2; i++) {
549
+ const r = i % 2 === 0 ? outer : inner;
550
+ const a = Math.PI * i / spikes - Math.PI / 2;
551
+ if (i === 0) {
552
+ ctx.moveTo(Math.cos(a) * r, Math.sin(a) * r);
553
+ } else {
554
+ ctx.lineTo(Math.cos(a) * r, Math.sin(a) * r);
650
555
  }
556
+ }
557
+ ctx.closePath();
558
+ break;
651
559
  }
652
- }
653
- // -- Drawing (freehand strokes) ----------------------------------
654
- function renderDrawing(ctx, ov, w, h) {
655
- for (const stroke of ov.strokes) {
656
- if (stroke.points.length < 2) {
657
- continue;
658
- }
659
- ctx.beginPath();
660
- ctx.strokeStyle = stroke.color;
661
- ctx.lineWidth = stroke.width;
662
- ctx.lineCap = 'round';
663
- ctx.lineJoin = 'round';
664
- ctx.globalAlpha = stroke.opacity;
665
- const [sx, sy] = stroke.points[0];
666
- ctx.moveTo(sx * w, sy * h);
667
- for (let i = 1; i < stroke.points.length; i++) {
668
- const [px, py] = stroke.points[i];
669
- ctx.lineTo(px * w, py * h);
560
+ case "heart": {
561
+ const s = Math.min(hw, hh);
562
+ ctx.moveTo(0, s * 0.4);
563
+ ctx.bezierCurveTo(-s, -s * 0.2, -s * 0.6, -s, 0, -s * 0.4);
564
+ ctx.bezierCurveTo(s * 0.6, -s, s, -s * 0.2, 0, s * 0.4);
565
+ break;
566
+ }
567
+ case "arrow":
568
+ ctx.moveTo(-hw, -hh * 0.3);
569
+ ctx.lineTo(hw * 0.3, -hh * 0.3);
570
+ ctx.lineTo(hw * 0.3, -hh);
571
+ ctx.lineTo(hw, 0);
572
+ ctx.lineTo(hw * 0.3, hh);
573
+ ctx.lineTo(hw * 0.3, hh * 0.3);
574
+ ctx.lineTo(-hw, hh * 0.3);
575
+ ctx.closePath();
576
+ break;
577
+ case "diamond":
578
+ ctx.moveTo(0, -hh);
579
+ ctx.lineTo(hw, 0);
580
+ ctx.lineTo(0, hh);
581
+ ctx.lineTo(-hw, 0);
582
+ ctx.closePath();
583
+ break;
584
+ case "hexagon":
585
+ for (let i = 0; i < 6; i++) {
586
+ const a = Math.PI * 2 * i / 6 - Math.PI / 2;
587
+ const r = Math.min(hw, hh);
588
+ const px = Math.cos(a) * r, py = Math.sin(a) * r;
589
+ if (i === 0) {
590
+ ctx.moveTo(px, py);
591
+ } else {
592
+ ctx.lineTo(px, py);
670
593
  }
671
- ctx.stroke();
594
+ }
595
+ ctx.closePath();
596
+ break;
597
+ case "cross": {
598
+ const t = Math.min(hw, hh) * 0.3;
599
+ ctx.moveTo(-t, -hh);
600
+ ctx.lineTo(t, -hh);
601
+ ctx.lineTo(t, -t);
602
+ ctx.lineTo(hw, -t);
603
+ ctx.lineTo(hw, t);
604
+ ctx.lineTo(t, t);
605
+ ctx.lineTo(t, hh);
606
+ ctx.lineTo(-t, hh);
607
+ ctx.lineTo(-t, t);
608
+ ctx.lineTo(-hw, t);
609
+ ctx.lineTo(-hw, -t);
610
+ ctx.lineTo(-t, -t);
611
+ ctx.closePath();
612
+ break;
613
+ }
614
+ case "lightning": {
615
+ const s = Math.min(hw, hh);
616
+ ctx.moveTo(-s * 0.1, -s);
617
+ ctx.lineTo(s * 0.5, -s * 0.1);
618
+ ctx.lineTo(s * 0.05, -s * 0.1);
619
+ ctx.lineTo(s * 0.4, s);
620
+ ctx.lineTo(-s * 0.15, s * 0.15);
621
+ ctx.lineTo(s * 0.05, s * 0.15);
622
+ ctx.lineTo(-s * 0.4, -s * 0.5);
623
+ ctx.closePath();
624
+ break;
672
625
  }
626
+ }
673
627
  }
674
- // -- Canvas stroke (draw a live, in-progress stroke) -------------
675
- function renderLiveStroke(ctx, points, color, width, canvasW, canvasH) {
676
- if (points.length < 2) {
677
- return;
628
+ function renderDrawing(ctx, ov, w, h) {
629
+ for (const stroke of ov.strokes) {
630
+ if (stroke.points.length < 2) {
631
+ continue;
678
632
  }
679
- ctx.save();
680
633
  ctx.beginPath();
681
- ctx.strokeStyle = color;
682
- ctx.lineWidth = width;
683
- ctx.lineCap = 'round';
684
- ctx.lineJoin = 'round';
685
- ctx.moveTo(points[0][0] * canvasW, points[0][1] * canvasH);
686
- for (let i = 1; i < points.length; i++) {
687
- ctx.lineTo(points[i][0] * canvasW, points[i][1] * canvasH);
634
+ ctx.strokeStyle = stroke.color;
635
+ ctx.lineWidth = stroke.width;
636
+ ctx.lineCap = "round";
637
+ ctx.lineJoin = "round";
638
+ ctx.globalAlpha = stroke.opacity;
639
+ const [sx, sy] = stroke.points[0];
640
+ ctx.moveTo(sx * w, sy * h);
641
+ for (let i = 1; i < stroke.points.length; i++) {
642
+ const [px, py] = stroke.points[i];
643
+ ctx.lineTo(px * w, py * h);
688
644
  }
689
645
  ctx.stroke();
690
- ctx.restore();
646
+ }
647
+ }
648
+ function renderLiveStroke(ctx, points, color, width, canvasW, canvasH) {
649
+ if (points.length < 2) {
650
+ return;
651
+ }
652
+ ctx.save();
653
+ ctx.beginPath();
654
+ ctx.strokeStyle = color;
655
+ ctx.lineWidth = width;
656
+ ctx.lineCap = "round";
657
+ ctx.lineJoin = "round";
658
+ ctx.moveTo(points[0][0] * canvasW, points[0][1] * canvasH);
659
+ for (let i = 1; i < points.length; i++) {
660
+ ctx.lineTo(points[i][0] * canvasW, points[i][1] * canvasH);
661
+ }
662
+ ctx.stroke();
663
+ ctx.restore();
691
664
  }
692
- // -- Frame -------------------------------------------------------
693
665
  function renderFrame(ctx, ov, w, h) {
694
- const t = Math.min(w, h) * ov.thickness;
695
- ctx.strokeStyle = ov.color;
696
- ctx.fillStyle = ov.color;
697
- switch (ov.frameStyle) {
698
- case 'simple':
699
- ctx.lineWidth = t;
700
- ctx.strokeRect(t / 2, t / 2, w - t, h - t);
701
- break;
702
- case 'double':
703
- ctx.lineWidth = t * 0.3;
704
- ctx.strokeRect(t * 0.15, t * 0.15, w - t * 0.3, h - t * 0.3);
705
- ctx.strokeRect(t * 0.7, t * 0.7, w - t * 1.4, h - t * 1.4);
706
- break;
707
- case 'rounded':
708
- ctx.lineWidth = t;
709
- roundRect(ctx, t / 2, t / 2, w - t, h - t, t * 2);
710
- ctx.stroke();
711
- break;
712
- case 'polaroid':
713
- ctx.fillRect(0, 0, w, t);
714
- ctx.fillRect(0, 0, t, h);
715
- ctx.fillRect(w - t, 0, t, h);
716
- ctx.fillRect(0, h - t * 3, w, t * 3);
717
- break;
718
- case 'film': {
719
- ctx.fillRect(0, 0, w, t);
720
- ctx.fillRect(0, h - t, w, t);
721
- const holeW = t * 0.5, holeH = t * 0.7;
722
- const count = Math.floor(w / (t * 1.5)) || 1;
723
- const spacing = w / (count + 1);
724
- ctx.fillStyle = resolveCssColor('var(--nice-text, #111)', ctx.canvas);
725
- for (let i = 1; i <= count; i++) {
726
- const cx = spacing * i;
727
- roundRect(ctx, cx - holeW / 2, (t - holeH) / 2, holeW, holeH, 2);
728
- ctx.fill();
729
- roundRect(ctx, cx - holeW / 2, h - t + (t - holeH) / 2, holeW, holeH, 2);
730
- ctx.fill();
731
- }
732
- break;
733
- }
734
- case 'vintage': {
735
- ctx.lineWidth = t * 0.5;
736
- ctx.strokeRect(t * 0.25, t * 0.25, w - t * 0.5, h - t * 0.5);
737
- ctx.lineWidth = 1;
738
- ctx.strokeStyle = ov.color + '88';
739
- ctx.strokeRect(t, t, w - t * 2, h - t * 2);
740
- const os = t * 0.8;
741
- for (const [dx, dy] of [
742
- [0, 0],
743
- [w - os, 0],
744
- [0, h - os],
745
- [w - os, h - os],
746
- ]) {
747
- ctx.beginPath();
748
- ctx.moveTo(dx + os * 0.2, dy + os * 0.2);
749
- ctx.lineTo(dx + os * 0.8, dy + os * 0.2);
750
- ctx.lineTo(dx + os * 0.5, dy + os * 0.5);
751
- ctx.lineTo(dx + os * 0.2, dy + os * 0.8);
752
- ctx.closePath();
753
- ctx.strokeStyle = ov.color;
754
- ctx.lineWidth = 1.5;
755
- ctx.stroke();
756
- }
757
- break;
758
- }
759
- case 'ornate': {
760
- ctx.lineWidth = t * 0.4;
761
- ctx.strokeRect(t * 0.2, t * 0.2, w - t * 0.4, h - t * 0.4);
762
- const cs = t * 1.5;
763
- ctx.lineWidth = 2;
764
- for (const [ox, oy, sx, sy] of [
765
- [0, 0, 1, 1],
766
- [w, 0, -1, 1],
767
- [0, h, 1, -1],
768
- [w, h, -1, -1],
769
- ]) {
770
- ctx.save();
771
- ctx.translate(ox, oy);
772
- ctx.scale(sx, sy);
773
- ctx.beginPath();
774
- ctx.arc(cs * 0.3, cs * 0.3, cs * 0.2, 0, Math.PI * 1.5, true);
775
- ctx.stroke();
776
- ctx.beginPath();
777
- ctx.moveTo(0, cs * 0.5);
778
- ctx.quadraticCurveTo(cs * 0.5, cs * 0.5, cs * 0.5, 0);
779
- ctx.stroke();
780
- ctx.restore();
781
- }
782
- break;
783
- }
784
- case 'shadow':
785
- ctx.shadowColor = resolveCssColor('var(--nice-overlay-50, rgba(0, 0, 0, 0.5))', ctx.canvas);
786
- ctx.shadowBlur = t * 2;
787
- ctx.shadowOffsetX = t * 0.5;
788
- ctx.shadowOffsetY = t * 0.5;
789
- ctx.lineWidth = t * 0.3;
790
- ctx.strokeRect(t, t, w - t * 2, h - t * 2);
791
- ctx.shadowColor = 'transparent';
792
- break;
793
- case 'neon':
794
- for (let i = 3; i >= 0; i--) {
795
- ctx.shadowColor = ov.color;
796
- ctx.shadowBlur = t * (i + 1) * 2;
797
- ctx.lineWidth = t * 0.2;
798
- // The white core of the neon tube is drawn INTO the photo (canvas ignores var()).
799
- ctx.strokeStyle = i === 0 ? '#fff' : ov.color;
800
- ctx.globalAlpha = i === 0 ? ov.opacity : 0.3 * ov.opacity;
801
- ctx.strokeRect(t, t, w - t * 2, h - t * 2);
802
- }
803
- ctx.shadowColor = 'transparent';
804
- break;
805
- case 'torn': {
806
- const step = 8;
807
- const seed = (i) => {
808
- const x = Math.sin(i * 127.1 + 311.7) * 43758.5453;
809
- return x - Math.floor(x) - 0.5;
810
- };
811
- // Top
812
- ctx.beginPath();
813
- ctx.moveTo(0, 0);
814
- for (let i = 0, x = 0; x <= w; x += step, i++) {
815
- ctx.lineTo(x, t + seed(i) * t * 0.8);
816
- }
817
- ctx.lineTo(w, 0);
818
- ctx.closePath();
819
- ctx.fill();
820
- // Bottom
821
- ctx.beginPath();
822
- ctx.moveTo(0, h);
823
- for (let i = 0, x = 0; x <= w; x += step, i++) {
824
- ctx.lineTo(x, h - t - seed(i + 1000) * t * 0.8);
825
- }
826
- ctx.lineTo(w, h);
827
- ctx.closePath();
828
- ctx.fill();
829
- // Left
830
- ctx.beginPath();
831
- ctx.moveTo(0, 0);
832
- for (let i = 0, y = 0; y <= h; y += step, i++) {
833
- ctx.lineTo(t + seed(i + 2000) * t * 0.8, y);
834
- }
835
- ctx.lineTo(0, h);
836
- ctx.closePath();
837
- ctx.fill();
838
- // Right
839
- ctx.beginPath();
840
- ctx.moveTo(w, 0);
841
- for (let i = 0, y = 0; y <= h; y += step, i++) {
842
- ctx.lineTo(w - t - seed(i + 3000) * t * 0.8, y);
843
- }
844
- ctx.lineTo(w, h);
845
- ctx.closePath();
846
- ctx.fill();
847
- break;
848
- }
666
+ const t = Math.min(w, h) * ov.thickness;
667
+ ctx.strokeStyle = ov.color;
668
+ ctx.fillStyle = ov.color;
669
+ switch (ov.frameStyle) {
670
+ case "simple":
671
+ ctx.lineWidth = t;
672
+ ctx.strokeRect(t / 2, t / 2, w - t, h - t);
673
+ break;
674
+ case "double":
675
+ ctx.lineWidth = t * 0.3;
676
+ ctx.strokeRect(t * 0.15, t * 0.15, w - t * 0.3, h - t * 0.3);
677
+ ctx.strokeRect(t * 0.7, t * 0.7, w - t * 1.4, h - t * 1.4);
678
+ break;
679
+ case "rounded":
680
+ ctx.lineWidth = t;
681
+ roundRect(ctx, t / 2, t / 2, w - t, h - t, t * 2);
682
+ ctx.stroke();
683
+ break;
684
+ case "polaroid":
685
+ ctx.fillRect(0, 0, w, t);
686
+ ctx.fillRect(0, 0, t, h);
687
+ ctx.fillRect(w - t, 0, t, h);
688
+ ctx.fillRect(0, h - t * 3, w, t * 3);
689
+ break;
690
+ case "film": {
691
+ ctx.fillRect(0, 0, w, t);
692
+ ctx.fillRect(0, h - t, w, t);
693
+ const holeW = t * 0.5, holeH = t * 0.7;
694
+ const count = Math.floor(w / (t * 1.5)) || 1;
695
+ const spacing = w / (count + 1);
696
+ ctx.fillStyle = resolveCssColor("var(--nice-text, #111)", ctx.canvas);
697
+ for (let i = 1; i <= count; i++) {
698
+ const cx = spacing * i;
699
+ roundRect(ctx, cx - holeW / 2, (t - holeH) / 2, holeW, holeH, 2);
700
+ ctx.fill();
701
+ roundRect(ctx, cx - holeW / 2, h - t + (t - holeH) / 2, holeW, holeH, 2);
702
+ ctx.fill();
703
+ }
704
+ break;
849
705
  }
706
+ case "vintage": {
707
+ ctx.lineWidth = t * 0.5;
708
+ ctx.strokeRect(t * 0.25, t * 0.25, w - t * 0.5, h - t * 0.5);
709
+ ctx.lineWidth = 1;
710
+ ctx.strokeStyle = ov.color + "88";
711
+ ctx.strokeRect(t, t, w - t * 2, h - t * 2);
712
+ const os = t * 0.8;
713
+ for (const [dx, dy] of [
714
+ [0, 0],
715
+ [w - os, 0],
716
+ [0, h - os],
717
+ [w - os, h - os]
718
+ ]) {
719
+ ctx.beginPath();
720
+ ctx.moveTo(dx + os * 0.2, dy + os * 0.2);
721
+ ctx.lineTo(dx + os * 0.8, dy + os * 0.2);
722
+ ctx.lineTo(dx + os * 0.5, dy + os * 0.5);
723
+ ctx.lineTo(dx + os * 0.2, dy + os * 0.8);
724
+ ctx.closePath();
725
+ ctx.strokeStyle = ov.color;
726
+ ctx.lineWidth = 1.5;
727
+ ctx.stroke();
728
+ }
729
+ break;
730
+ }
731
+ case "ornate": {
732
+ ctx.lineWidth = t * 0.4;
733
+ ctx.strokeRect(t * 0.2, t * 0.2, w - t * 0.4, h - t * 0.4);
734
+ const cs = t * 1.5;
735
+ ctx.lineWidth = 2;
736
+ for (const [ox, oy, sx, sy] of [
737
+ [0, 0, 1, 1],
738
+ [w, 0, -1, 1],
739
+ [0, h, 1, -1],
740
+ [w, h, -1, -1]
741
+ ]) {
742
+ ctx.save();
743
+ ctx.translate(ox, oy);
744
+ ctx.scale(sx, sy);
745
+ ctx.beginPath();
746
+ ctx.arc(cs * 0.3, cs * 0.3, cs * 0.2, 0, Math.PI * 1.5, true);
747
+ ctx.stroke();
748
+ ctx.beginPath();
749
+ ctx.moveTo(0, cs * 0.5);
750
+ ctx.quadraticCurveTo(cs * 0.5, cs * 0.5, cs * 0.5, 0);
751
+ ctx.stroke();
752
+ ctx.restore();
753
+ }
754
+ break;
755
+ }
756
+ case "shadow":
757
+ ctx.shadowColor = resolveCssColor("var(--nice-overlay-50, rgba(0, 0, 0, 0.5))", ctx.canvas);
758
+ ctx.shadowBlur = t * 2;
759
+ ctx.shadowOffsetX = t * 0.5;
760
+ ctx.shadowOffsetY = t * 0.5;
761
+ ctx.lineWidth = t * 0.3;
762
+ ctx.strokeRect(t, t, w - t * 2, h - t * 2);
763
+ ctx.shadowColor = "transparent";
764
+ break;
765
+ case "neon":
766
+ for (let i = 3; i >= 0; i--) {
767
+ ctx.shadowColor = ov.color;
768
+ ctx.shadowBlur = t * (i + 1) * 2;
769
+ ctx.lineWidth = t * 0.2;
770
+ ctx.strokeStyle = i === 0 ? "#fff" : ov.color;
771
+ ctx.globalAlpha = i === 0 ? ov.opacity : 0.3 * ov.opacity;
772
+ ctx.strokeRect(t, t, w - t * 2, h - t * 2);
773
+ }
774
+ ctx.shadowColor = "transparent";
775
+ break;
776
+ case "torn": {
777
+ const step = 8;
778
+ const seed = (i) => {
779
+ const x = Math.sin(i * 127.1 + 311.7) * 43758.5453;
780
+ return x - Math.floor(x) - 0.5;
781
+ };
782
+ ctx.beginPath();
783
+ ctx.moveTo(0, 0);
784
+ for (let i = 0, x = 0; x <= w; x += step, i++) {
785
+ ctx.lineTo(x, t + seed(i) * t * 0.8);
786
+ }
787
+ ctx.lineTo(w, 0);
788
+ ctx.closePath();
789
+ ctx.fill();
790
+ ctx.beginPath();
791
+ ctx.moveTo(0, h);
792
+ for (let i = 0, x = 0; x <= w; x += step, i++) {
793
+ ctx.lineTo(x, h - t - seed(i + 1e3) * t * 0.8);
794
+ }
795
+ ctx.lineTo(w, h);
796
+ ctx.closePath();
797
+ ctx.fill();
798
+ ctx.beginPath();
799
+ ctx.moveTo(0, 0);
800
+ for (let i = 0, y = 0; y <= h; y += step, i++) {
801
+ ctx.lineTo(t + seed(i + 2e3) * t * 0.8, y);
802
+ }
803
+ ctx.lineTo(0, h);
804
+ ctx.closePath();
805
+ ctx.fill();
806
+ ctx.beginPath();
807
+ ctx.moveTo(w, 0);
808
+ for (let i = 0, y = 0; y <= h; y += step, i++) {
809
+ ctx.lineTo(w - t - seed(i + 3e3) * t * 0.8, y);
810
+ }
811
+ ctx.lineTo(w, h);
812
+ ctx.closePath();
813
+ ctx.fill();
814
+ break;
815
+ }
816
+ }
850
817
  }
851
818
  function roundRect(ctx, x, y, w, h, r) {
852
- ctx.beginPath();
853
- ctx.moveTo(x + r, y);
854
- ctx.lineTo(x + w - r, y);
855
- ctx.quadraticCurveTo(x + w, y, x + w, y + r);
856
- ctx.lineTo(x + w, y + h - r);
857
- ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
858
- ctx.lineTo(x + r, y + h);
859
- ctx.quadraticCurveTo(x, y + h, x, y + h - r);
860
- ctx.lineTo(x, y + r);
861
- ctx.quadraticCurveTo(x, y, x + r, y);
862
- ctx.closePath();
819
+ ctx.beginPath();
820
+ ctx.moveTo(x + r, y);
821
+ ctx.lineTo(x + w - r, y);
822
+ ctx.quadraticCurveTo(x + w, y, x + w, y + r);
823
+ ctx.lineTo(x + w, y + h - r);
824
+ ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
825
+ ctx.lineTo(x + r, y + h);
826
+ ctx.quadraticCurveTo(x, y + h, x, y + h - r);
827
+ ctx.lineTo(x, y + r);
828
+ ctx.quadraticCurveTo(x, y, x + r, y);
829
+ ctx.closePath();
863
830
  }
864
- // ═══════════════════════════════════════════════════════════════
865
- // ── Hit testing
866
- // ═══════════════════════════════════════════════════════════════
867
- /**
868
- * Returns `true` when the point (px, py) in canvas-fraction space
869
- * hits the bounding box of the overlay (ignoring rotation).
870
- */
871
831
  function hitTestOverlay(ov, px, py, _w, _h) {
872
- if (ov.type === 'drawing' || ov.type === 'frame') {
873
- return false;
874
- }
875
- let hitW, hitH;
876
- switch (ov.type) {
877
- case 'emoji':
878
- hitW = hitH = (ov.fontSize * ov.scaleX) / _w;
879
- break;
880
- case 'text': {
881
- hitW = (ov.text.length * ov.fontSize * 0.55 * ov.scaleX) / _w;
882
- hitH = (ov.fontSize * 1.3 * ov.scaleY) / _h;
883
- break;
884
- }
885
- case 'shape':
886
- hitW = (ov.width * ov.scaleX) / _w;
887
- hitH = (ov.height * ov.scaleY) / _h;
888
- break;
889
- default:
890
- return false;
832
+ if (ov.type === "drawing" || ov.type === "frame") {
833
+ return false;
834
+ }
835
+ let hitW, hitH;
836
+ switch (ov.type) {
837
+ case "emoji":
838
+ hitW = hitH = ov.fontSize * ov.scaleX / _w;
839
+ break;
840
+ case "text": {
841
+ hitW = ov.text.length * ov.fontSize * 0.55 * ov.scaleX / _w;
842
+ hitH = ov.fontSize * 1.3 * ov.scaleY / _h;
843
+ break;
891
844
  }
892
- const margin = 15 / _w;
893
- return Math.abs(px - ov.x) < hitW / 2 + margin && Math.abs(py - ov.y) < hitH / 2 + margin;
845
+ case "shape":
846
+ hitW = ov.width * ov.scaleX / _w;
847
+ hitH = ov.height * ov.scaleY / _h;
848
+ break;
849
+ default:
850
+ return false;
851
+ }
852
+ const margin = 15 / _w;
853
+ return Math.abs(px - ov.x) < hitW / 2 + margin && Math.abs(py - ov.y) < hitH / 2 + margin;
894
854
  }
895
- // ═══════════════════════════════════════════════════════════════
896
- // ── Factory helpers
897
- // ═══════════════════════════════════════════════════════════════
898
855
  function createEmojiOverlay(emoji, canvasW, canvasH) {
899
- return {
900
- id: generateOverlayId(),
901
- type: 'emoji',
902
- x: 0.5,
903
- y: 0.5,
904
- scaleX: 1,
905
- scaleY: 1,
906
- rotation: 0,
907
- opacity: 1,
908
- emoji,
909
- fontSize: Math.round(Math.min(canvasW, canvasH) * 0.08),
910
- };
856
+ return {
857
+ id: generateOverlayId(),
858
+ type: "emoji",
859
+ x: 0.5,
860
+ y: 0.5,
861
+ scaleX: 1,
862
+ scaleY: 1,
863
+ rotation: 0,
864
+ opacity: 1,
865
+ emoji,
866
+ fontSize: Math.round(Math.min(canvasW, canvasH) * 0.08)
867
+ };
911
868
  }
912
- function createTextOverlay(text = 'Text') {
913
- return {
914
- id: generateOverlayId(),
915
- type: 'text',
916
- x: 0.5,
917
- y: 0.5,
918
- scaleX: 1,
919
- scaleY: 1,
920
- rotation: 0,
921
- opacity: 1,
922
- text,
923
- fontFamily: 'Arial',
924
- fontSize: 36,
925
- // Overlay colours are paint drawn into the photo on canvas (which ignores var()),
926
- // and some are extended with an alpha byte (`color + '88'`) — so hex.
927
- color: '#ffffff',
928
- bold: true,
929
- italic: false,
930
- outlineColor: '#000000',
931
- outlineWidth: 2,
932
- shadow: null,
933
- background: null,
934
- backgroundPadding: 6,
935
- };
869
+ function createTextOverlay(text = "Text") {
870
+ return {
871
+ id: generateOverlayId(),
872
+ type: "text",
873
+ x: 0.5,
874
+ y: 0.5,
875
+ scaleX: 1,
876
+ scaleY: 1,
877
+ rotation: 0,
878
+ opacity: 1,
879
+ text,
880
+ fontFamily: "Arial",
881
+ fontSize: 36,
882
+ // Overlay colours are paint drawn into the photo on canvas (which ignores var()),
883
+ // and some are extended with an alpha byte (`color + '88'`) — so hex.
884
+ color: "#ffffff",
885
+ bold: true,
886
+ italic: false,
887
+ outlineColor: "#000000",
888
+ outlineWidth: 2,
889
+ shadow: null,
890
+ background: null,
891
+ backgroundPadding: 6
892
+ };
936
893
  }
937
894
  function createShapeOverlay(kind) {
938
- return {
939
- id: generateOverlayId(),
940
- type: 'shape',
941
- x: 0.5,
942
- y: 0.5,
943
- scaleX: 1,
944
- scaleY: 1,
945
- rotation: 0,
946
- opacity: 1,
947
- shapeKind: kind,
948
- width: 100,
949
- height: 100,
950
- fillColor: null,
951
- strokeColor: '#ffffff',
952
- strokeWidth: 3,
953
- };
895
+ return {
896
+ id: generateOverlayId(),
897
+ type: "shape",
898
+ x: 0.5,
899
+ y: 0.5,
900
+ scaleX: 1,
901
+ scaleY: 1,
902
+ rotation: 0,
903
+ opacity: 1,
904
+ shapeKind: kind,
905
+ width: 100,
906
+ height: 100,
907
+ fillColor: null,
908
+ strokeColor: "#ffffff",
909
+ strokeWidth: 3
910
+ };
954
911
  }
955
912
  function createDrawingOverlay() {
956
- return {
957
- id: generateOverlayId(),
958
- type: 'drawing',
959
- x: 0,
960
- y: 0,
961
- scaleX: 1,
962
- scaleY: 1,
963
- rotation: 0,
964
- opacity: 1,
965
- strokes: [],
966
- };
913
+ return {
914
+ id: generateOverlayId(),
915
+ type: "drawing",
916
+ x: 0,
917
+ y: 0,
918
+ scaleX: 1,
919
+ scaleY: 1,
920
+ rotation: 0,
921
+ opacity: 1,
922
+ strokes: []
923
+ };
967
924
  }
968
- function createFrameOverlay(style, color = '#ffffff') {
969
- return {
970
- id: generateOverlayId(),
971
- type: 'frame',
972
- x: 0,
973
- y: 0,
974
- scaleX: 1,
975
- scaleY: 1,
976
- rotation: 0,
977
- opacity: 1,
978
- frameStyle: style,
979
- color,
980
- thickness: 0.03,
981
- };
925
+ function createFrameOverlay(style, color = "#ffffff") {
926
+ return {
927
+ id: generateOverlayId(),
928
+ type: "frame",
929
+ x: 0,
930
+ y: 0,
931
+ scaleX: 1,
932
+ scaleY: 1,
933
+ rotation: 0,
934
+ opacity: 1,
935
+ frameStyle: style,
936
+ color,
937
+ thickness: 0.03
938
+ };
982
939
  }
983
940
  function serializeEditorState(state) {
984
- return JSON.stringify(state);
941
+ return JSON.stringify(state);
985
942
  }
986
943
  function deserializeEditorState(json) {
987
- try {
988
- return JSON.parse(json);
989
- }
990
- catch (_a) {
991
- /* Expected: JSON.parse may fail on malformed serialized state */
992
- return null;
993
- }
944
+ try {
945
+ return JSON.parse(json);
946
+ } catch {
947
+ return null;
948
+ }
994
949
  }
995
-
996
- export { EMOJI_CATALOG, FRAME_CATALOG, SHAPE_CATALOG, createDrawingOverlay, createEmojiOverlay, createFrameOverlay, createShapeOverlay, createTextOverlay, deserializeEditorState, generateOverlayId, hitTestOverlay, renderAllOverlays, renderLiveStroke, serializeEditorState };
950
+ export {
951
+ EMOJI_CATALOG,
952
+ FRAME_CATALOG,
953
+ SHAPE_CATALOG,
954
+ createDrawingOverlay,
955
+ createEmojiOverlay,
956
+ createFrameOverlay,
957
+ createShapeOverlay,
958
+ createTextOverlay,
959
+ deserializeEditorState,
960
+ generateOverlayId,
961
+ hitTestOverlay,
962
+ renderAllOverlays,
963
+ renderLiveStroke,
964
+ serializeEditorState
965
+ };