@sequent-org/moodboard 1.0.24 → 1.2.0

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 (346) hide show
  1. package/package.json +2 -2
  2. package/src/assets/emodji//320/226/320/265/320/275/321/201/320/272/320/270/320/265 /321/215/320/274/320/276/321/206/320/270/320/270/1f645.png +0 -0
  3. package/src/assets/emodji//320/226/320/265/320/275/321/201/320/272/320/270/320/265 /321/215/320/274/320/276/321/206/320/270/320/270/1f646.png +0 -0
  4. package/src/assets/emodji//320/226/320/265/320/275/321/201/320/272/320/270/320/265 /321/215/320/274/320/276/321/206/320/270/320/270/1f64b.png +0 -0
  5. package/src/assets/emodji//320/226/320/265/320/275/321/201/320/272/320/270/320/265 /321/215/320/274/320/276/321/206/320/270/320/270/1f64d.png +0 -0
  6. package/src/assets/emodji//320/226/320/265/320/275/321/201/320/272/320/270/320/265 /321/215/320/274/320/276/321/206/320/270/320/270/1f64e.png +0 -0
  7. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f446.png +0 -0
  8. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f447.png +0 -0
  9. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f448.png +0 -0
  10. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f449.png +0 -0
  11. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f44a.png +0 -0
  12. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f44b.png +0 -0
  13. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f44c.png +0 -0
  14. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f450.png +0 -0
  15. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f4aa.png +0 -0
  16. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f590.png +0 -0
  17. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f596.png +0 -0
  18. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f64c.png +0 -0
  19. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/1f64f.png +0 -0
  20. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/261d.png +0 -0
  21. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/270a.png +0 -0
  22. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/270b.png +0 -0
  23. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/270c.png +0 -0
  24. package/src/assets/emodji//320/226/320/265/321/201/321/202/321/213/270d.png +0 -0
  25. package/src/assets/emodji//320/232/320/276/321/202/320/270/320/272/320/270/1f638.png +0 -0
  26. package/src/assets/emodji//320/232/320/276/321/202/320/270/320/272/320/270/1f639.png +0 -0
  27. package/src/assets/emodji//320/232/320/276/321/202/320/270/320/272/320/270/1f63a.png +0 -0
  28. package/src/assets/emodji//320/232/320/276/321/202/320/270/320/272/320/270/1f63b.png +0 -0
  29. package/src/assets/emodji//320/232/320/276/321/202/320/270/320/272/320/270/1f63c.png +0 -0
  30. package/src/assets/emodji//320/232/320/276/321/202/320/270/320/272/320/270/1f63d.png +0 -0
  31. package/src/assets/emodji//320/232/320/276/321/202/320/270/320/272/320/270/1f63e.png +0 -0
  32. package/src/assets/emodji//320/232/320/276/321/202/320/270/320/272/320/270/1f63f.png +0 -0
  33. package/src/assets/emodji//320/232/320/276/321/202/320/270/320/272/320/270/1f640.png +0 -0
  34. package/src/assets/emodji//320/236/320/261/320/265/320/267/321/214/321/217/320/275/320/272/320/260/1f435.png +0 -0
  35. package/src/assets/emodji//320/236/320/261/320/265/320/267/321/214/321/217/320/275/320/272/320/260/1f648.png +0 -0
  36. package/src/assets/emodji//320/236/320/261/320/265/320/267/321/214/321/217/320/275/320/272/320/260/1f649.png +0 -0
  37. package/src/assets/emodji//320/236/320/261/320/265/320/267/321/214/321/217/320/275/320/272/320/260/1f64a.png +0 -0
  38. package/src/assets/emodji//320/240/320/260/320/267/320/275/320/276/320/265/1f440.png +0 -0
  39. package/src/assets/emodji//320/240/320/260/320/267/320/275/320/276/320/265/1f441.png +0 -0
  40. package/src/assets/emodji//320/240/320/260/320/267/320/275/320/276/320/265/1f499.png +0 -0
  41. package/src/assets/emodji//320/240/320/260/320/267/320/275/320/276/320/265/1f4a1.png +0 -0
  42. package/src/assets/emodji//320/240/320/260/320/267/320/275/320/276/320/265/1f4a3.png +0 -0
  43. package/src/assets/emodji//320/240/320/260/320/267/320/275/320/276/320/265/1f4a9.png +0 -0
  44. package/src/assets/emodji//320/240/320/260/320/267/320/275/320/276/320/265/1f4ac.png +0 -0
  45. package/src/assets/emodji//320/240/320/260/320/267/320/275/320/276/320/265/1f4af.png +0 -0
  46. package/src/assets/emodji//320/240/320/260/320/267/320/275/320/276/320/265/203c.png +0 -0
  47. package/src/assets/emodji//320/240/320/260/320/267/320/275/320/276/320/265/26d4.png +0 -0
  48. package/src/assets/emodji//320/240/320/260/320/267/320/275/320/276/320/265/2764.png +0 -0
  49. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f600.png +0 -0
  50. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f601.png +0 -0
  51. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f602.png +0 -0
  52. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f603.png +0 -0
  53. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f604.png +0 -0
  54. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f605.png +0 -0
  55. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f606.png +0 -0
  56. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f607.png +0 -0
  57. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f609.png +0 -0
  58. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f60a.png +0 -0
  59. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f60b.png +0 -0
  60. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f60c.png +0 -0
  61. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f60d.png +0 -0
  62. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f60e.png +0 -0
  63. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f60f.png +0 -0
  64. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f610.png +0 -0
  65. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f611.png +0 -0
  66. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f612.png +0 -0
  67. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f613.png +0 -0
  68. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f614.png +0 -0
  69. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f615.png +0 -0
  70. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f616.png +0 -0
  71. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f617.png +0 -0
  72. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f618.png +0 -0
  73. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f619.png +0 -0
  74. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f61a.png +0 -0
  75. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f61b.png +0 -0
  76. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f61c.png +0 -0
  77. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f61d.png +0 -0
  78. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f61e.png +0 -0
  79. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f61f.png +0 -0
  80. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f620.png +0 -0
  81. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f621.png +0 -0
  82. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f622.png +0 -0
  83. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f623.png +0 -0
  84. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f624.png +0 -0
  85. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f625.png +0 -0
  86. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f626.png +0 -0
  87. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f627.png +0 -0
  88. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f628.png +0 -0
  89. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f629.png +0 -0
  90. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f62a.png +0 -0
  91. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f62b.png +0 -0
  92. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f62c.png +0 -0
  93. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f62d.png +0 -0
  94. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f62e.png +0 -0
  95. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f62f.png +0 -0
  96. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f630.png +0 -0
  97. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f631.png +0 -0
  98. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f632.png +0 -0
  99. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f633.png +0 -0
  100. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f635.png +0 -0
  101. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f636.png +0 -0
  102. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f641.png +0 -0
  103. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/1f642.png +0 -0
  104. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/2639.png +0 -0
  105. package/src/assets/emodji//320/241/320/274/320/260/320/271/320/273/320/270/320/272/320/270/263a.png +0 -0
  106. package/src/assets/fonts/amatic-sc/AmaticSC-Bold.ttf +0 -0
  107. package/src/assets/fonts/amatic-sc/AmaticSC-Regular.ttf +0 -0
  108. package/src/assets/fonts/caveat/Caveat-Bold.ttf +0 -0
  109. package/src/assets/fonts/caveat/Caveat-Medium.ttf +0 -0
  110. package/src/assets/fonts/caveat/Caveat-Regular.ttf +0 -0
  111. package/src/assets/fonts/caveat/Caveat-SemiBold.ttf +0 -0
  112. package/src/assets/fonts/caveat/Caveat-VariableFont_wght.ttf +0 -0
  113. package/src/assets/fonts/great-vibes/GreatVibes-Regular.ttf +0 -0
  114. package/src/assets/fonts/lobster/Lobster-Regular.ttf +0 -0
  115. package/src/assets/fonts/noto-serif/NotoSerif-Black.ttf +0 -0
  116. package/src/assets/fonts/noto-serif/NotoSerif-BlackItalic.ttf +0 -0
  117. package/src/assets/fonts/noto-serif/NotoSerif-Bold.ttf +0 -0
  118. package/src/assets/fonts/noto-serif/NotoSerif-BoldItalic.ttf +0 -0
  119. package/src/assets/fonts/noto-serif/NotoSerif-ExtraBold.ttf +0 -0
  120. package/src/assets/fonts/noto-serif/NotoSerif-ExtraBoldItalic.ttf +0 -0
  121. package/src/assets/fonts/noto-serif/NotoSerif-ExtraLight.ttf +0 -0
  122. package/src/assets/fonts/noto-serif/NotoSerif-ExtraLightItalic.ttf +0 -0
  123. package/src/assets/fonts/noto-serif/NotoSerif-Italic-VariableFont_wdth,wght.ttf +0 -0
  124. package/src/assets/fonts/noto-serif/NotoSerif-Italic.ttf +0 -0
  125. package/src/assets/fonts/noto-serif/NotoSerif-Light.ttf +0 -0
  126. package/src/assets/fonts/noto-serif/NotoSerif-LightItalic.ttf +0 -0
  127. package/src/assets/fonts/noto-serif/NotoSerif-Medium.ttf +0 -0
  128. package/src/assets/fonts/noto-serif/NotoSerif-MediumItalic.ttf +0 -0
  129. package/src/assets/fonts/noto-serif/NotoSerif-Regular.ttf +0 -0
  130. package/src/assets/fonts/noto-serif/NotoSerif-SemiBold.ttf +0 -0
  131. package/src/assets/fonts/noto-serif/NotoSerif-SemiBoldItalic.ttf +0 -0
  132. package/src/assets/fonts/noto-serif/NotoSerif-Thin.ttf +0 -0
  133. package/src/assets/fonts/noto-serif/NotoSerif-ThinItalic.ttf +0 -0
  134. package/src/assets/fonts/noto-serif/NotoSerif-VariableFont_wdth,wght.ttf +0 -0
  135. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-Black.ttf +0 -0
  136. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-BlackItalic.ttf +0 -0
  137. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-Bold.ttf +0 -0
  138. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-BoldItalic.ttf +0 -0
  139. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-ExtraBold.ttf +0 -0
  140. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-ExtraBoldItalic.ttf +0 -0
  141. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-ExtraLight.ttf +0 -0
  142. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-ExtraLightItalic.ttf +0 -0
  143. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-Italic.ttf +0 -0
  144. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-Light.ttf +0 -0
  145. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-LightItalic.ttf +0 -0
  146. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-Medium.ttf +0 -0
  147. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-MediumItalic.ttf +0 -0
  148. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-Regular.ttf +0 -0
  149. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-SemiBold.ttf +0 -0
  150. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-SemiBoldItalic.ttf +0 -0
  151. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-Thin.ttf +0 -0
  152. package/src/assets/fonts/noto-serif/NotoSerif_Condensed-ThinItalic.ttf +0 -0
  153. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-Black.ttf +0 -0
  154. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-BlackItalic.ttf +0 -0
  155. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-Bold.ttf +0 -0
  156. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-BoldItalic.ttf +0 -0
  157. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-ExtraBold.ttf +0 -0
  158. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-ExtraBoldItalic.ttf +0 -0
  159. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-ExtraLight.ttf +0 -0
  160. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-ExtraLightItalic.ttf +0 -0
  161. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-Italic.ttf +0 -0
  162. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-Light.ttf +0 -0
  163. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-LightItalic.ttf +0 -0
  164. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-Medium.ttf +0 -0
  165. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-MediumItalic.ttf +0 -0
  166. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-Regular.ttf +0 -0
  167. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-SemiBold.ttf +0 -0
  168. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-SemiBoldItalic.ttf +0 -0
  169. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-Thin.ttf +0 -0
  170. package/src/assets/fonts/noto-serif/NotoSerif_ExtraCondensed-ThinItalic.ttf +0 -0
  171. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-Black.ttf +0 -0
  172. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-BlackItalic.ttf +0 -0
  173. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-Bold.ttf +0 -0
  174. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-BoldItalic.ttf +0 -0
  175. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-ExtraBold.ttf +0 -0
  176. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-ExtraBoldItalic.ttf +0 -0
  177. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-ExtraLight.ttf +0 -0
  178. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-ExtraLightItalic.ttf +0 -0
  179. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-Italic.ttf +0 -0
  180. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-Light.ttf +0 -0
  181. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-LightItalic.ttf +0 -0
  182. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-Medium.ttf +0 -0
  183. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-MediumItalic.ttf +0 -0
  184. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-Regular.ttf +0 -0
  185. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-SemiBold.ttf +0 -0
  186. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-SemiBoldItalic.ttf +0 -0
  187. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-Thin.ttf +0 -0
  188. package/src/assets/fonts/noto-serif/NotoSerif_SemiCondensed-ThinItalic.ttf +0 -0
  189. package/src/assets/fonts/oswald/Oswald-Bold.ttf +0 -0
  190. package/src/assets/fonts/oswald/Oswald-ExtraLight.ttf +0 -0
  191. package/src/assets/fonts/oswald/Oswald-Light.ttf +0 -0
  192. package/src/assets/fonts/oswald/Oswald-Medium.ttf +0 -0
  193. package/src/assets/fonts/oswald/Oswald-Regular.ttf +0 -0
  194. package/src/assets/fonts/oswald/Oswald-SemiBold.ttf +0 -0
  195. package/src/assets/fonts/oswald/Oswald-VariableFont_wght.ttf +0 -0
  196. package/src/assets/fonts/pacifico/Pacifico-Regular.ttf +0 -0
  197. package/src/assets/fonts/playfair/PlayfairDisplay-Black.ttf +0 -0
  198. package/src/assets/fonts/playfair/PlayfairDisplay-BlackItalic.ttf +0 -0
  199. package/src/assets/fonts/playfair/PlayfairDisplay-Bold.ttf +0 -0
  200. package/src/assets/fonts/playfair/PlayfairDisplay-BoldItalic.ttf +0 -0
  201. package/src/assets/fonts/playfair/PlayfairDisplay-ExtraBold.ttf +0 -0
  202. package/src/assets/fonts/playfair/PlayfairDisplay-ExtraBoldItalic.ttf +0 -0
  203. package/src/assets/fonts/playfair/PlayfairDisplay-Italic-VariableFont_wght.ttf +0 -0
  204. package/src/assets/fonts/playfair/PlayfairDisplay-Italic.ttf +0 -0
  205. package/src/assets/fonts/playfair/PlayfairDisplay-Medium.ttf +0 -0
  206. package/src/assets/fonts/playfair/PlayfairDisplay-MediumItalic.ttf +0 -0
  207. package/src/assets/fonts/playfair/PlayfairDisplay-Regular.ttf +0 -0
  208. package/src/assets/fonts/playfair/PlayfairDisplay-SemiBold.ttf +0 -0
  209. package/src/assets/fonts/playfair/PlayfairDisplay-SemiBoldItalic.ttf +0 -0
  210. package/src/assets/fonts/playfair/PlayfairDisplay-VariableFont_wght.ttf +0 -0
  211. package/src/assets/fonts/poiret-one/PoiretOne-Regular.ttf +0 -0
  212. package/src/assets/fonts/roboto/Roboto-Black.ttf +0 -0
  213. package/src/assets/fonts/roboto/Roboto-BlackItalic.ttf +0 -0
  214. package/src/assets/fonts/roboto/Roboto-Bold.ttf +0 -0
  215. package/src/assets/fonts/roboto/Roboto-BoldItalic.ttf +0 -0
  216. package/src/assets/fonts/roboto/Roboto-ExtraBold.ttf +0 -0
  217. package/src/assets/fonts/roboto/Roboto-ExtraBoldItalic.ttf +0 -0
  218. package/src/assets/fonts/roboto/Roboto-ExtraLight.ttf +0 -0
  219. package/src/assets/fonts/roboto/Roboto-ExtraLightItalic.ttf +0 -0
  220. package/src/assets/fonts/roboto/Roboto-Italic-VariableFont_wdth,wght.ttf +0 -0
  221. package/src/assets/fonts/roboto/Roboto-Italic.ttf +0 -0
  222. package/src/assets/fonts/roboto/Roboto-Light.ttf +0 -0
  223. package/src/assets/fonts/roboto/Roboto-LightItalic.ttf +0 -0
  224. package/src/assets/fonts/roboto/Roboto-Medium.ttf +0 -0
  225. package/src/assets/fonts/roboto/Roboto-MediumItalic.ttf +0 -0
  226. package/src/assets/fonts/roboto/Roboto-Regular.ttf +0 -0
  227. package/src/assets/fonts/roboto/Roboto-SemiBold.ttf +0 -0
  228. package/src/assets/fonts/roboto/Roboto-SemiBoldItalic.ttf +0 -0
  229. package/src/assets/fonts/roboto/Roboto-Thin.ttf +0 -0
  230. package/src/assets/fonts/roboto/Roboto-ThinItalic.ttf +0 -0
  231. package/src/assets/fonts/roboto/Roboto-VariableFont_wdth,wght.ttf +0 -0
  232. package/src/assets/fonts/roboto/Roboto_Condensed-Black.ttf +0 -0
  233. package/src/assets/fonts/roboto/Roboto_Condensed-BlackItalic.ttf +0 -0
  234. package/src/assets/fonts/roboto/Roboto_Condensed-Bold.ttf +0 -0
  235. package/src/assets/fonts/roboto/Roboto_Condensed-BoldItalic.ttf +0 -0
  236. package/src/assets/fonts/roboto/Roboto_Condensed-ExtraBold.ttf +0 -0
  237. package/src/assets/fonts/roboto/Roboto_Condensed-ExtraBoldItalic.ttf +0 -0
  238. package/src/assets/fonts/roboto/Roboto_Condensed-ExtraLight.ttf +0 -0
  239. package/src/assets/fonts/roboto/Roboto_Condensed-ExtraLightItalic.ttf +0 -0
  240. package/src/assets/fonts/roboto/Roboto_Condensed-Italic.ttf +0 -0
  241. package/src/assets/fonts/roboto/Roboto_Condensed-Light.ttf +0 -0
  242. package/src/assets/fonts/roboto/Roboto_Condensed-LightItalic.ttf +0 -0
  243. package/src/assets/fonts/roboto/Roboto_Condensed-Medium.ttf +0 -0
  244. package/src/assets/fonts/roboto/Roboto_Condensed-MediumItalic.ttf +0 -0
  245. package/src/assets/fonts/roboto/Roboto_Condensed-Regular.ttf +0 -0
  246. package/src/assets/fonts/roboto/Roboto_Condensed-SemiBold.ttf +0 -0
  247. package/src/assets/fonts/roboto/Roboto_Condensed-SemiBoldItalic.ttf +0 -0
  248. package/src/assets/fonts/roboto/Roboto_Condensed-Thin.ttf +0 -0
  249. package/src/assets/fonts/roboto/Roboto_Condensed-ThinItalic.ttf +0 -0
  250. package/src/assets/fonts/roboto/Roboto_SemiCondensed-Black.ttf +0 -0
  251. package/src/assets/fonts/roboto/Roboto_SemiCondensed-BlackItalic.ttf +0 -0
  252. package/src/assets/fonts/roboto/Roboto_SemiCondensed-Bold.ttf +0 -0
  253. package/src/assets/fonts/roboto/Roboto_SemiCondensed-BoldItalic.ttf +0 -0
  254. package/src/assets/fonts/roboto/Roboto_SemiCondensed-ExtraBold.ttf +0 -0
  255. package/src/assets/fonts/roboto/Roboto_SemiCondensed-ExtraBoldItalic.ttf +0 -0
  256. package/src/assets/fonts/roboto/Roboto_SemiCondensed-ExtraLight.ttf +0 -0
  257. package/src/assets/fonts/roboto/Roboto_SemiCondensed-ExtraLightItalic.ttf +0 -0
  258. package/src/assets/fonts/roboto/Roboto_SemiCondensed-Italic.ttf +0 -0
  259. package/src/assets/fonts/roboto/Roboto_SemiCondensed-Light.ttf +0 -0
  260. package/src/assets/fonts/roboto/Roboto_SemiCondensed-LightItalic.ttf +0 -0
  261. package/src/assets/fonts/roboto/Roboto_SemiCondensed-Medium.ttf +0 -0
  262. package/src/assets/fonts/roboto/Roboto_SemiCondensed-MediumItalic.ttf +0 -0
  263. package/src/assets/fonts/roboto/Roboto_SemiCondensed-Regular.ttf +0 -0
  264. package/src/assets/fonts/roboto/Roboto_SemiCondensed-SemiBold.ttf +0 -0
  265. package/src/assets/fonts/roboto/Roboto_SemiCondensed-SemiBoldItalic.ttf +0 -0
  266. package/src/assets/fonts/roboto/Roboto_SemiCondensed-Thin.ttf +0 -0
  267. package/src/assets/fonts/roboto/Roboto_SemiCondensed-ThinItalic.ttf +0 -0
  268. package/src/assets/fonts/roboto-slab/RobotoSlab-Black.ttf +0 -0
  269. package/src/assets/fonts/roboto-slab/RobotoSlab-Bold.ttf +0 -0
  270. package/src/assets/fonts/roboto-slab/RobotoSlab-ExtraBold.ttf +0 -0
  271. package/src/assets/fonts/roboto-slab/RobotoSlab-ExtraLight.ttf +0 -0
  272. package/src/assets/fonts/roboto-slab/RobotoSlab-Light.ttf +0 -0
  273. package/src/assets/fonts/roboto-slab/RobotoSlab-Medium.ttf +0 -0
  274. package/src/assets/fonts/roboto-slab/RobotoSlab-Regular.ttf +0 -0
  275. package/src/assets/fonts/roboto-slab/RobotoSlab-SemiBold.ttf +0 -0
  276. package/src/assets/fonts/roboto-slab/RobotoSlab-Thin.ttf +0 -0
  277. package/src/assets/fonts/roboto-slab/RobotoSlab-VariableFont_wght.ttf +0 -0
  278. package/src/assets/fonts/rubik-mono-one/RubikMonoOne-Regular.ttf +0 -0
  279. package/src/assets/icons/arrows-up-down-left-right.svg +1 -0
  280. package/src/assets/icons/arrows-up-down.svg +1 -0
  281. package/src/assets/icons/attachments.svg +1 -3
  282. package/src/assets/icons/attachments2.svg +3 -0
  283. package/src/assets/icons/clear.svg +1 -5
  284. package/src/assets/icons/cursor-default-custom.svg +10 -0
  285. package/src/assets/icons/cursor-default.svg +1 -0
  286. package/src/assets/icons/cursor-default2.svg +1 -0
  287. package/src/assets/icons/emoji.svg +1 -6
  288. package/src/assets/icons/emoji2.svg +6 -0
  289. package/src/assets/icons/frame.svg +1 -3
  290. package/src/assets/icons/frame2.svg +3 -0
  291. package/src/assets/icons/i-cursor.svg +1 -0
  292. package/src/assets/icons/image.svg +1 -3
  293. package/src/assets/icons/image2.svg +3 -0
  294. package/src/assets/icons/note.svg +1 -3
  295. package/src/assets/icons/note2.svg +3 -0
  296. package/src/assets/icons/pencil.svg +1 -3
  297. package/src/assets/icons/pencil2.svg +3 -0
  298. package/src/assets/icons/rotate-icon.svg +3 -0
  299. package/src/assets/icons/select.svg +1 -1
  300. package/src/assets/icons/shapes.svg +1 -3
  301. package/src/assets/icons/shapes2.svg +3 -0
  302. package/src/assets/icons/text-add.svg +1 -3
  303. package/src/assets/icons/text-add2.svg +3 -0
  304. package/src/assets/icons/trash.svg +1 -0
  305. package/src/core/PixiEngine.js +32 -0
  306. package/src/core/commands/CopyObjectCommand.js +20 -9
  307. package/src/core/commands/GroupMoveCommand.js +49 -13
  308. package/src/core/commands/MoveObjectCommand.js +4 -24
  309. package/src/core/commands/PasteObjectCommand.js +26 -15
  310. package/src/core/events/Events.js +2 -0
  311. package/src/core/index.js +575 -47
  312. package/src/grid/GridFactory.js +3 -3
  313. package/src/grid/LineGrid.js +42 -20
  314. package/src/moodboard/MoodBoard.js +11 -0
  315. package/src/objects/DrawingObject.js +16 -7
  316. package/src/objects/FileObject.js +25 -11
  317. package/src/objects/FrameObject.js +38 -9
  318. package/src/objects/ImageObject.js +24 -5
  319. package/src/objects/NoteObject.js +227 -39
  320. package/src/objects/ShapeObject.js +9 -8
  321. package/src/objects/TextObject.js +2 -20
  322. package/src/services/BoardService.js +3 -3
  323. package/src/services/FrameService.js +95 -17
  324. package/src/services/ZoomPanController.js +14 -3
  325. package/src/tools/BaseTool.js +6 -1
  326. package/src/tools/ToolManager.js +24 -1
  327. package/src/tools/object-tools/DrawingTool.js +3 -1
  328. package/src/tools/object-tools/PlacementTool.js +366 -105
  329. package/src/tools/object-tools/SelectTool.js +508 -153
  330. package/src/tools/object-tools/TextTool.js +23 -2
  331. package/src/tools/object-tools/selection/BoxSelectController.js +5 -0
  332. package/src/ui/FilePropertiesPanel.js +9 -2
  333. package/src/ui/FramePropertiesPanel.js +239 -91
  334. package/src/ui/HtmlHandlesLayer.js +383 -126
  335. package/src/ui/HtmlTextLayer.js +122 -26
  336. package/src/ui/NotePropertiesPanel.js +128 -44
  337. package/src/ui/TextPropertiesPanel.js +100 -118
  338. package/src/ui/Toolbar.js +254 -65
  339. package/src/ui/Topbar.js +112 -10
  340. package/src/ui/ZoomPanel.js +8 -2
  341. package/src/ui/styles/index.css +5 -0
  342. package/src/ui/styles/panels.css +232 -0
  343. package/src/ui/styles/toolbar.css +77 -0
  344. package/src/ui/styles/topbar.css +113 -0
  345. package/src/ui/styles/workspace.css +481 -263
  346. package/src/utils/emojiResolver.js +121 -0
package/src/core/index.js CHANGED
@@ -213,19 +213,115 @@ export class CoreMoodBoard {
213
213
  if (this.clipboard.type === 'object') {
214
214
  this.pasteObject({ x, y });
215
215
  } else if (this.clipboard.type === 'group') {
216
- // Вставляем группу с сохранением относительных позиций относительно клика
217
216
  const group = this.clipboard;
218
217
  const data = Array.isArray(group.data) ? group.data : [];
219
218
  if (data.length === 0) return;
220
- // Вычисляем топ-левт группы для относительного смещения клик-точки
221
- let minX = Infinity, minY = Infinity;
222
- data.forEach(o => {
223
- if (!o || !o.position) return;
224
- minX = Math.min(minX, o.position.x);
225
- minY = Math.min(minY, o.position.y);
226
- });
227
- if (!isFinite(minX) || !isFinite(minY)) return;
228
- const baseX = minX, baseY = minY;
219
+
220
+ // Особая логика: если это бандл фрейма (фрейм + дети)
221
+ if (group.meta && group.meta.frameBundle) {
222
+ // Вычисляем топ-левт группы для относительного смещения клик-точки
223
+ let minX = Infinity, minY = Infinity;
224
+ data.forEach(o => {
225
+ if (!o || !o.position) return;
226
+ minX = Math.min(minX, o.position.x);
227
+ minY = Math.min(minY, o.position.y);
228
+ });
229
+ if (!isFinite(minX) || !isFinite(minY)) return;
230
+ const baseX = minX, baseY = minY;
231
+
232
+ // Ищем фрейм в бандле
233
+ const frames = data.filter(o => o && o.type === 'frame');
234
+ if (frames.length !== 1) {
235
+ // fallback к обычной вставке группы
236
+ const newIds = [];
237
+ let pending = data.length;
238
+ const onPasted = (payload) => {
239
+ if (!payload || !payload.newId) return;
240
+ newIds.push(payload.newId);
241
+ pending -= 1;
242
+ if (pending === 0) {
243
+ this.eventBus.off(Events.Object.Pasted, onPasted);
244
+ requestAnimationFrame(() => {
245
+ if (this.selectTool && newIds.length > 0) {
246
+ this.selectTool.setSelection(newIds);
247
+ this.selectTool.updateResizeHandles();
248
+ }
249
+ });
250
+ }
251
+ };
252
+ this.eventBus.on(Events.Object.Pasted, onPasted);
253
+ data.forEach(orig => {
254
+ const cloned = JSON.parse(JSON.stringify(orig));
255
+ const targetPos = {
256
+ x: x + (cloned.position.x - baseX),
257
+ y: y + (cloned.position.y - baseY)
258
+ };
259
+ this.clipboard = { type: 'object', data: cloned };
260
+ const cmd = new PasteObjectCommand(this, targetPos);
261
+ cmd.setEventBus(this.eventBus);
262
+ this.history.executeCommand(cmd);
263
+ });
264
+ this.clipboard = group;
265
+ return;
266
+ }
267
+
268
+ const frameOriginal = frames[0];
269
+ const children = data.filter(o => o && o.id !== frameOriginal.id);
270
+ const totalToPaste = 1 + children.length;
271
+ const newIds = [];
272
+ let pastedCount = 0;
273
+ let newFrameId = null;
274
+
275
+ const onPasted = (payload) => {
276
+ if (!payload || !payload.newId) return;
277
+ newIds.push(payload.newId);
278
+ pastedCount += 1;
279
+ // Как только вставили фрейм — вставляем детей с новым frameId
280
+ if (!newFrameId && payload.originalId === frameOriginal.id) {
281
+ newFrameId = payload.newId;
282
+ for (const child of children) {
283
+ const clonedChild = JSON.parse(JSON.stringify(child));
284
+ clonedChild.properties = clonedChild.properties || {};
285
+ clonedChild.properties.frameId = newFrameId;
286
+ const targetPos = {
287
+ x: x + (clonedChild.position.x - baseX),
288
+ y: y + (clonedChild.position.y - baseY)
289
+ };
290
+ this.clipboard = { type: 'object', data: clonedChild };
291
+ const cmdChild = new PasteObjectCommand(this, targetPos);
292
+ cmdChild.setEventBus(this.eventBus);
293
+ this.history.executeCommand(cmdChild);
294
+ }
295
+ }
296
+ if (pastedCount === totalToPaste) {
297
+ this.eventBus.off(Events.Object.Pasted, onPasted);
298
+ requestAnimationFrame(() => {
299
+ if (this.selectTool && newIds.length > 0) {
300
+ this.selectTool.setSelection(newIds);
301
+ this.selectTool.updateResizeHandles();
302
+ }
303
+ });
304
+ }
305
+ };
306
+ this.eventBus.on(Events.Object.Pasted, onPasted);
307
+
308
+ // Вставляем фрейм первым
309
+ const frameClone = JSON.parse(JSON.stringify(frameOriginal));
310
+ this.clipboard = { type: 'object', data: frameClone };
311
+ const targetPosFrame = {
312
+ x: x + (frameClone.position.x - baseX),
313
+ y: y + (frameClone.position.y - baseY)
314
+ };
315
+ const cmdFrame = new PasteObjectCommand(this, targetPosFrame);
316
+ cmdFrame.setEventBus(this.eventBus);
317
+ this.history.executeCommand(cmdFrame);
318
+
319
+ // Возвращаем clipboard к группе для повторных вставок
320
+ this.clipboard = group;
321
+ return;
322
+ }
323
+
324
+ // Обычная вставка группы (не фрейм-бандл)
229
325
  const newIds = [];
230
326
  let pending = data.length;
231
327
  const onPasted = (payload) => {
@@ -246,15 +342,14 @@ export class CoreMoodBoard {
246
342
  data.forEach(orig => {
247
343
  const cloned = JSON.parse(JSON.stringify(orig));
248
344
  const targetPos = {
249
- x: x + (cloned.position.x - baseX),
250
- y: y + (cloned.position.y - baseY)
345
+ x: x + (cloned.position.x - minX),
346
+ y: y + (cloned.position.y - minY)
251
347
  };
252
348
  this.clipboard = { type: 'object', data: cloned };
253
349
  const cmd = new PasteObjectCommand(this, targetPos);
254
350
  cmd.setEventBus(this.eventBus);
255
351
  this.history.executeCommand(cmd);
256
352
  });
257
- // Возвращаем clipboard к группе для повторных вставок
258
353
  this.clipboard = group;
259
354
  }
260
355
  });
@@ -456,7 +551,8 @@ export class CoreMoodBoard {
456
551
 
457
552
  // События перетаскивания
458
553
  this.eventBus.on(Events.Tool.DragStart, (data) => {
459
- // Сохраняем начальную позицию как левый-верх, переводя центр PIXI в state-координаты
554
+ // Сохраняем начальную позицию как левый-верх
555
+ // Все объекты используют pivot по центру, поэтому логика одинакова
460
556
  const pixiObject = this.pixi.objects.get(data.object);
461
557
  if (pixiObject) {
462
558
  const halfW = (pixiObject.width || 0) / 2;
@@ -593,7 +689,8 @@ export class CoreMoodBoard {
593
689
  }
594
690
  }
595
691
  if (moves.length > 0) {
596
- const cmd = new GroupMoveCommand(this, moves);
692
+ // Обычное групповое перемещение - координаты центров PIXI
693
+ const cmd = new GroupMoveCommand(this, moves, false);
597
694
  cmd.setEventBus(this.eventBus);
598
695
  this.history.executeCommand(cmd);
599
696
  }
@@ -618,7 +715,9 @@ export class CoreMoodBoard {
618
715
  if (this.dragStartPosition) {
619
716
  const pixiObject = this.pixi.objects.get(data.object);
620
717
  if (pixiObject) {
621
- const finalPosition = { x: pixiObject.x - (pixiObject.width||0)/2, y: pixiObject.y - (pixiObject.height||0)/2 };
718
+ // Берем финальную позицию из state, который обновлялся во время drag:update
719
+ const objState = this.state.state.objects.find(o => o.id === data.object);
720
+ const finalPosition = objState && objState.position ? { x: objState.position.x, y: objState.position.y } : { x: 0, y: 0 };
622
721
 
623
722
  // Создаем команду только если позиция действительно изменилась
624
723
  if (this.dragStartPosition.x !== finalPosition.x ||
@@ -642,7 +741,8 @@ export class CoreMoodBoard {
642
741
  const to = { x: child.position.x, y: child.position.y };
643
742
  moves.push({ id: childId, from, to });
644
743
  }
645
- const cmd = new GroupMoveCommand(this, moves);
744
+ // Frame перемещение - координаты уже левый-верх
745
+ const cmd = new GroupMoveCommand(this, moves, true);
646
746
  cmd.setEventBus(this.eventBus);
647
747
  this.history.executeCommand(cmd);
648
748
  } else {
@@ -673,13 +773,95 @@ export class CoreMoodBoard {
673
773
  const original = objects.find(obj => obj.id === originalId);
674
774
  if (!original) return;
675
775
 
676
- // Сохраняем копию в буфер обмена, чтобы переиспользовать PasteObjectCommand
776
+ // Если дублируем фрейм копируем вместе с его содержимым
777
+ if (original.type === 'frame') {
778
+ const frame = JSON.parse(JSON.stringify(original));
779
+ const dx = (position?.x ?? frame.position.x) - frame.position.x;
780
+ const dy = (position?.y ?? frame.position.y) - frame.position.y;
781
+
782
+ // Дети фрейма
783
+ const children = (this.state.state.objects || []).filter(o => o && o.properties && o.properties.frameId === originalId);
784
+
785
+ // После вставки фрейма вставим детей, перепривязав к новому frameId
786
+ const onFramePasted = (payload) => {
787
+ if (!payload || payload.originalId !== originalId) return;
788
+ const newFrameId = payload.newId;
789
+ this.eventBus.off(Events.Object.Pasted, onFramePasted);
790
+ for (const child of children) {
791
+ const clonedChild = JSON.parse(JSON.stringify(child));
792
+ clonedChild.properties = clonedChild.properties || {};
793
+ clonedChild.properties.frameId = newFrameId;
794
+ const targetPos = {
795
+ x: (child.position?.x || 0) + dx,
796
+ y: (child.position?.y || 0) + dy
797
+ };
798
+ this.clipboard = { type: 'object', data: clonedChild };
799
+ const cmdChild = new PasteObjectCommand(this, targetPos);
800
+ cmdChild.setEventBus(this.eventBus);
801
+ this.history.executeCommand(cmdChild);
802
+ }
803
+ };
804
+ this.eventBus.on(Events.Object.Pasted, onFramePasted);
805
+
806
+ // Подготовим буфер для фрейма (с новым названием)
807
+ const frameClone = JSON.parse(JSON.stringify(frame));
808
+ try {
809
+ const arr = this.state.state.objects || [];
810
+ let maxNum = 0;
811
+ for (const o of arr) {
812
+ if (!o || o.type !== 'frame') continue;
813
+ const t = o?.properties?.title || '';
814
+ const m = t.match(/^\s*Фрейм\s+(\d+)\s*$/i);
815
+ if (m) {
816
+ const n = parseInt(m[1], 10);
817
+ if (Number.isFinite(n)) maxNum = Math.max(maxNum, n);
818
+ }
819
+ }
820
+ const next = maxNum + 1;
821
+ frameClone.properties = frameClone.properties || {};
822
+ frameClone.properties.title = `Фрейм ${next}`;
823
+ } catch (_) {}
824
+ this.clipboard = { type: 'object', data: frameClone };
825
+ const cmdFrame = new PasteObjectCommand(this, { x: frame.position.x + dx, y: frame.position.y + dy });
826
+ cmdFrame.setEventBus(this.eventBus);
827
+ this.history.executeCommand(cmdFrame);
828
+ return;
829
+ }
830
+
831
+ // Обычная логика для остальных типов
677
832
  this.clipboard = {
678
833
  type: 'object',
679
834
  data: JSON.parse(JSON.stringify(original))
680
835
  };
836
+ // Запоминаем исходное название фрейма, чтобы не менять его
837
+ try {
838
+ if (original.type === 'frame') {
839
+ this._dupTitleMap = this._dupTitleMap || new Map();
840
+ const prevTitle = (original.properties && typeof original.properties.title !== 'undefined') ? original.properties.title : undefined;
841
+ this._dupTitleMap.set(originalId, prevTitle);
842
+ }
843
+ } catch (_) {}
844
+ // Если фрейм — проставим будущий заголовок в буфер
845
+ try {
846
+ if (this.clipboard.data && this.clipboard.data.type === 'frame') {
847
+ const arr = this.state.state.objects || [];
848
+ let maxNum = 0;
849
+ for (const o of arr) {
850
+ if (!o || o.type !== 'frame') continue;
851
+ const t = o?.properties?.title || '';
852
+ const m = t.match(/^\s*Фрейм\s+(\d+)\s*$/i);
853
+ if (m) {
854
+ const n = parseInt(m[1], 10);
855
+ if (Number.isFinite(n)) maxNum = Math.max(maxNum, n);
856
+ }
857
+ }
858
+ const next = maxNum + 1;
859
+ this.clipboard.data.properties = this.clipboard.data.properties || {};
860
+ this.clipboard.data.properties.title = `Фрейм ${next}`;
861
+ }
862
+ } catch (_) {}
681
863
 
682
- // Вызываем вставку с конкретной позицией (там рассчитается ID и пр.)
864
+ // Вызываем вставку по указанной позиции (под курсором)
683
865
  this.pasteObject(position);
684
866
  });
685
867
 
@@ -715,14 +897,77 @@ export class CoreMoodBoard {
715
897
  this.eventBus.on(Events.Object.Pasted, handler);
716
898
  // Кладем в clipboard объект, затем вызываем PasteObjectCommand с текущей позицией
717
899
  this.clipboard = { type: 'object', data: JSON.parse(JSON.stringify(obj)) };
900
+ // Запомним оригинальные названия фреймов
901
+ try {
902
+ if (obj.type === 'frame') {
903
+ this._dupTitleMap = this._dupTitleMap || new Map();
904
+ const prevTitle = (obj.properties && typeof obj.properties.title !== 'undefined') ? obj.properties.title : undefined;
905
+ this._dupTitleMap.set(obj.id, prevTitle);
906
+ }
907
+ } catch (_) { /* no-op */ }
908
+ // Если фрейм — сразу проставим новый заголовок в буфер
909
+ try {
910
+ if (this.clipboard.data && this.clipboard.data.type === 'frame') {
911
+ const arr = this.state.state.objects || [];
912
+ let maxNum = 0;
913
+ for (const o2 of arr) {
914
+ if (!o2 || o2.type !== 'frame') continue;
915
+ const t2 = o2?.properties?.title || '';
916
+ const m2 = t2.match(/^\s*Фрейм\s+(\d+)\s*$/i);
917
+ if (m2) {
918
+ const n2 = parseInt(m2[1], 10);
919
+ if (Number.isFinite(n2)) maxNum = Math.max(maxNum, n2);
920
+ }
921
+ }
922
+ const next2 = maxNum + 1;
923
+ this.clipboard.data.properties = this.clipboard.data.properties || {};
924
+ this.clipboard.data.properties.title = `Фрейм ${next2}`;
925
+ }
926
+ } catch (_) { /* no-op */ }
718
927
  const cmd = new PasteObjectCommand(this, { x: obj.position.x, y: obj.position.y });
719
928
  cmd.setEventBus(this.eventBus);
720
929
  this.history.executeCommand(cmd);
721
930
  }
722
931
  });
723
932
 
724
- // Когда объект вставлен (из PasteObjectCommand) — сообщаем SelectTool
933
+ // Когда объект вставлен (из PasteObjectCommand)
725
934
  this.eventBus.on(Events.Object.Pasted, ({ originalId, newId }) => {
935
+ try {
936
+ const arr = this.state.state.objects || [];
937
+ const newObj = arr.find(o => o.id === newId);
938
+ const origObj = arr.find(o => o.id === originalId);
939
+ if (newObj && newObj.type === 'frame') {
940
+ // Рассчитываем следующий номер среди уже существующих (кроме только что вставленного)
941
+ let maxNum = 0;
942
+ for (const o of arr) {
943
+ if (!o || o.id === newId || o.type !== 'frame') continue;
944
+ const t = o?.properties?.title || '';
945
+ const m = t.match(/^\s*Фрейм\s+(\d+)\s*$/i);
946
+ if (m) {
947
+ const n = parseInt(m[1], 10);
948
+ if (Number.isFinite(n)) maxNum = Math.max(maxNum, n);
949
+ }
950
+ }
951
+ const next = maxNum + 1;
952
+ // Присваиваем новое имя только НОВОМУ
953
+ newObj.properties = newObj.properties || {};
954
+ newObj.properties.title = `Фрейм ${next}`;
955
+ const pixNew = this.pixi.objects.get(newId);
956
+ if (pixNew && pixNew._mb?.instance?.setTitle) pixNew._mb.instance.setTitle(newObj.properties.title);
957
+ // Восстанавливаем исходное имя оригинала, если оно было записано
958
+ if (this._dupTitleMap && this._dupTitleMap.has(originalId) && origObj && origObj.type === 'frame') {
959
+ const prev = this._dupTitleMap.get(originalId);
960
+ origObj.properties = origObj.properties || {};
961
+ // Если prev undefined, очистим title
962
+ origObj.properties.title = prev;
963
+ const pixOrig = this.pixi.objects.get(originalId);
964
+ if (pixOrig && pixOrig._mb?.instance?.setTitle) pixOrig._mb.instance.setTitle(prev);
965
+ this._dupTitleMap.delete(originalId);
966
+ }
967
+ this.state.markDirty();
968
+ }
969
+ } catch (_) { /* no-op */ }
970
+ // Сообщаем SelectTool id нового объекта для переключения drag
726
971
  this.eventBus.emit(Events.Tool.DuplicateReady, { originalId, newId });
727
972
  });
728
973
 
@@ -733,6 +978,13 @@ export class CoreMoodBoard {
733
978
  const object = objects.find(obj => obj.id === data.object);
734
979
  if (object) {
735
980
  this.resizeStartSize = { width: object.width, height: object.height };
981
+ // Сохраняем контекст активного ресайза для расчёта позиции, если она не будет передана
982
+ this._activeResize = {
983
+ objectId: data.object,
984
+ handle: data.handle,
985
+ startSize: { width: object.width, height: object.height },
986
+ startPosition: { x: object.position.x, y: object.position.y }
987
+ };
736
988
  }
737
989
  });
738
990
 
@@ -814,13 +1066,187 @@ export class CoreMoodBoard {
814
1066
  const objects = this.state.getObjects();
815
1067
  const object = objects.find(obj => obj.id === data.object);
816
1068
  const objectType = object ? object.type : null;
817
-
818
- this.updateObjectSizeAndPositionDirect(data.object, data.size, data.position, objectType);
1069
+
1070
+ // Сохраняем пропорции для фреймов, кроме произвольных (lockedAspect=false)
1071
+ if ((objectType === 'frame' || (objectType === 'image' && object?.properties?.isEmojiIcon)) && data.size) {
1072
+ const lockedAspect = !!(object?.properties && (object.properties.lockedAspect === true));
1073
+ const isEmoji = (objectType === 'image' && object?.properties?.isEmojiIcon);
1074
+ if (!lockedAspect && !isEmoji) {
1075
+ // произвольные фреймы — без ограничений
1076
+ } else {
1077
+ const start = this._activeResize?.startSize || { width: object.width, height: object.height };
1078
+ const aspect = isEmoji ? 1 : ((start.width > 0 && start.height > 0) ? (start.width / start.height) : (object.width / Math.max(1, object.height)));
1079
+ let w = Math.max(1, data.size.width);
1080
+ let h = Math.max(1, data.size.height);
1081
+ const hndl = (this._activeResize?.handle || '').toLowerCase();
1082
+ if (isEmoji) {
1083
+ // Делаем квадрат, фиксируя противоположную сторону к старту
1084
+ const s = Math.max(w, h);
1085
+ // Вычислим позицию сразу, чтобы не было дрейфа правой/нижней границы
1086
+ if (!data.position && this._activeResize && this._activeResize.objectId === data.object) {
1087
+ const startPos = this._activeResize.startPosition;
1088
+ const sw = this._activeResize.startSize.width;
1089
+ const sh = this._activeResize.startSize.height;
1090
+ let x = startPos.x;
1091
+ let y = startPos.y;
1092
+ if (hndl.includes('w')) { x = startPos.x + (sw - s); }
1093
+ if (hndl.includes('n')) { y = startPos.y + (sh - s); }
1094
+ const isEdge = ['n','s','e','w'].includes(hndl);
1095
+ if (isEdge) {
1096
+ if (hndl === 'n' || hndl === 's') x = startPos.x + Math.round((sw - s) / 2);
1097
+ if (hndl === 'e' || hndl === 'w') y = startPos.y + Math.round((sh - s) / 2);
1098
+ }
1099
+ data.position = { x: Math.round(x), y: Math.round(y) };
1100
+ }
1101
+ w = s; h = s;
1102
+ } else {
1103
+ // Обычная поддержка аспекта для фреймов
1104
+ const dw = Math.abs(w - start.width);
1105
+ const dh = Math.abs(h - start.height);
1106
+ if (dw >= dh) { h = Math.round(w / aspect); } else { w = Math.round(h * aspect); }
1107
+ }
1108
+ // Минимальная площадь — только для фреймов
1109
+ if (!isEmoji) {
1110
+ const minArea = 1800;
1111
+ const area = Math.max(1, w * h);
1112
+ if (area < minArea) {
1113
+ const scale = Math.sqrt(minArea / area);
1114
+ w = Math.round(w * scale);
1115
+ h = Math.round(h * scale);
1116
+ }
1117
+ }
1118
+ data.size = { width: w, height: h };
1119
+
1120
+ // Если позиция известна (фиксированная противоположная сторона) — откорректируем её
1121
+ if (!data.position && this._activeResize && this._activeResize.objectId === data.object) {
1122
+ const hndl = (this._activeResize.handle || '').toLowerCase();
1123
+ const startPos = this._activeResize.startPosition;
1124
+ const sw = this._activeResize.startSize.width;
1125
+ const sh = this._activeResize.startSize.height;
1126
+ let x = startPos.x;
1127
+ let y = startPos.y;
1128
+ // Базовая привязка противоположной стороны
1129
+ if (hndl.includes('w')) { x = startPos.x + (sw - w); }
1130
+ if (hndl.includes('n')) { y = startPos.y + (sh - h); }
1131
+ // Симметрическая компенсация по перпендикулярной оси для edge-хэндлов
1132
+ const isEdge = ['n','s','e','w'].includes(hndl);
1133
+ if (isEdge) {
1134
+ if (hndl === 'n' || hndl === 's') {
1135
+ // Вверх/вниз: верх или низ фиксируется, ширина меняется симметрично относительно центра
1136
+ x = startPos.x + Math.round((sw - w) / 2);
1137
+ } else if (hndl === 'e' || hndl === 'w') {
1138
+ // Вправо/влево: левая или правая фиксируется, высота меняется симметрично относительно центра
1139
+ y = startPos.y + Math.round((sh - h) / 2);
1140
+ }
1141
+ }
1142
+ data.position = { x: Math.round(x), y: Math.round(y) };
1143
+ }
1144
+ }
1145
+ }
1146
+
1147
+ // Если позиция не пришла из UI, вычислим её из контекста активной ручки
1148
+ let position = data.position;
1149
+ if (!position && this._activeResize && this._activeResize.objectId === data.object) {
1150
+ const h = (this._activeResize.handle || '').toLowerCase();
1151
+ const start = this._activeResize.startPosition;
1152
+ const startSize = this._activeResize.startSize;
1153
+ const dw = (data.size?.width || startSize.width) - startSize.width;
1154
+ const dh = (data.size?.height || startSize.height) - startSize.height;
1155
+ let nx = start.x;
1156
+ let ny = start.y;
1157
+ // Для левых/верхних ручек смещаем топ-лев на полную величину изменения
1158
+ if (h.includes('w')) nx = start.x + dw;
1159
+ if (h.includes('n')) ny = start.y + dh;
1160
+ // Для правых/нижних ручек топ-лев остаётся стартовым (nx, ny уже равны start)
1161
+ position = { x: nx, y: ny };
1162
+ }
1163
+
1164
+ // Для фреймов с произвольным аспектом также обеспечим минимальную площадь
1165
+ if (objectType === 'frame' && data.size) {
1166
+ const minArea = 1800;
1167
+ const w0 = Math.max(1, data.size.width);
1168
+ const h0 = Math.max(1, data.size.height);
1169
+ const area0 = w0 * h0;
1170
+ if (area0 < minArea) {
1171
+ const scale = Math.sqrt(minArea / Math.max(1, area0));
1172
+ const w = Math.round(w0 * scale);
1173
+ const h = Math.round(h0 * scale);
1174
+ data.size = { width: w, height: h };
1175
+ // позиция будет скорректирована ниже общей логикой (уже рассчитана выше при необходимости)
1176
+ }
1177
+ }
1178
+
1179
+ this.updateObjectSizeAndPositionDirect(data.object, data.size, position, objectType);
819
1180
  });
820
1181
 
821
1182
  this.eventBus.on(Events.Tool.ResizeEnd, (data) => {
822
1183
  // В конце создаем одну команду изменения размера
823
1184
  if (this.resizeStartSize && data.oldSize && data.newSize) {
1185
+ // Принудительно сохраняем пропорции для фреймов (если lockedAspect=true)
1186
+ const objects = this.state.getObjects();
1187
+ const object = objects.find(obj => obj.id === data.object);
1188
+ const objectType = object ? object.type : null;
1189
+ if (objectType === 'frame' && !!(object?.properties && object.properties.lockedAspect === true)) {
1190
+ const start = this._activeResize?.startSize || { width: object.width, height: object.height };
1191
+ const aspect = (start.width > 0 && start.height > 0) ? (start.width / start.height) : (object.width / Math.max(1, object.height));
1192
+ let w = Math.max(1, data.newSize.width);
1193
+ let h = Math.max(1, data.newSize.height);
1194
+ const dw = Math.abs(w - start.width);
1195
+ const dh = Math.abs(h - start.height);
1196
+ if (dw >= dh) { h = Math.round(w / aspect); } else { w = Math.round(h * aspect); }
1197
+ // Минимальная площадь фрейма ~1800px²
1198
+ const minArea = 1800;
1199
+ const area = Math.max(1, w * h);
1200
+ if (area < minArea) {
1201
+ const scale = Math.sqrt(minArea / area);
1202
+ w = Math.round(w * scale);
1203
+ h = Math.round(h * scale);
1204
+ }
1205
+ data.newSize = { width: w, height: h };
1206
+ if (!data.newPosition && this._activeResize && this._activeResize.objectId === data.object) {
1207
+ const hndl = (this._activeResize.handle || '').toLowerCase();
1208
+ const startPos = this._activeResize.startPosition;
1209
+ const sw = this._activeResize.startSize.width;
1210
+ const sh = this._activeResize.startSize.height;
1211
+ let x = startPos.x;
1212
+ let y = startPos.y;
1213
+ if (hndl.includes('w')) { x = startPos.x + (sw - w); }
1214
+ if (hndl.includes('n')) { y = startPos.y + (sh - h); }
1215
+ const isEdge = ['n','s','e','w'].includes(hnl = hndl);
1216
+ if (isEdge) {
1217
+ if (hnl === 'n' || hnl === 's') {
1218
+ x = startPos.x + Math.round((sw - w) / 2);
1219
+ } else if (hnl === 'e' || hnl === 'w') {
1220
+ y = startPos.y + Math.round((sh - h) / 2);
1221
+ }
1222
+ }
1223
+ data.newPosition = { x: Math.round(x), y: Math.round(y) };
1224
+ }
1225
+ }
1226
+ // Для произвольных фреймов также обеспечим минимальную площадь
1227
+ if (objectType === 'frame' && data.newSize && !(object?.properties && object.properties.lockedAspect === true)) {
1228
+ const minArea = 1800;
1229
+ const w0 = Math.max(1, data.newSize.width);
1230
+ const h0 = Math.max(1, data.newSize.height);
1231
+ const area0 = w0 * h0;
1232
+ if (area0 < minArea) {
1233
+ const scale = Math.sqrt(minArea / Math.max(1, area0));
1234
+ const w = Math.round(w0 * scale);
1235
+ const h = Math.round(h0 * scale);
1236
+ data.newSize = { width: w, height: h };
1237
+ if (!data.newPosition && this._activeResize && this._activeResize.objectId === data.object) {
1238
+ const hndl2 = (this._activeResize.handle || '').toLowerCase();
1239
+ const startPos2 = this._activeResize.startPosition;
1240
+ const sw2 = this._activeResize.startSize.width;
1241
+ const sh2 = this._activeResize.startSize.height;
1242
+ let x2 = startPos2.x;
1243
+ let y2 = startPos2.y;
1244
+ if (hndl2.includes('w')) { x2 = startPos2.x + (sw2 - w); }
1245
+ if (hndl2.includes('n')) { y2 = startPos2.y + (sh2 - h); }
1246
+ data.newPosition = { x: Math.round(x2), y: Math.round(y2) };
1247
+ }
1248
+ }
1249
+ }
824
1250
  // Создаем команду только если размер действительно изменился
825
1251
  if (data.oldSize.width !== data.newSize.width ||
826
1252
  data.oldSize.height !== data.newSize.height) {
@@ -833,19 +1259,33 @@ export class CoreMoodBoard {
833
1259
  newPosition: data.newPosition
834
1260
  });
835
1261
 
1262
+ // Гарантируем согласованность позиции: если UI не передал, вычислим
1263
+ let oldPos = data.oldPosition;
1264
+ let newPos = data.newPosition;
1265
+ if ((!oldPos || !newPos) && this._activeResize && this._activeResize.objectId === data.object) {
1266
+ const h = (this._activeResize.handle || '').toLowerCase();
1267
+ const start = this._activeResize.startPosition;
1268
+ const startSize = this._activeResize.startSize;
1269
+ const dw = (data.newSize?.width || startSize.width) - startSize.width;
1270
+ const dh = (data.newSize?.height || startSize.height) - startSize.height;
1271
+ const calcNew = { x: start.x + (h.includes('w') ? dw : 0), y: start.y + (h.includes('n') ? dh : 0) };
1272
+ if (!oldPos) oldPos = { x: start.x, y: start.y };
1273
+ if (!newPos) newPos = calcNew;
1274
+ }
836
1275
  const command = new ResizeObjectCommand(
837
1276
  this,
838
1277
  data.object,
839
1278
  data.oldSize,
840
1279
  data.newSize,
841
- data.oldPosition,
842
- data.newPosition
1280
+ oldPos,
1281
+ newPos
843
1282
  );
844
1283
  command.setEventBus(this.eventBus);
845
1284
  this.history.executeCommand(command);
846
1285
  }
847
1286
  }
848
1287
  this.resizeStartSize = null;
1288
+ this._activeResize = null;
849
1289
  });
850
1290
 
851
1291
  // === ОБРАБОТЧИКИ СОБЫТИЙ ВРАЩЕНИЯ ===
@@ -960,9 +1400,9 @@ export class CoreMoodBoard {
960
1400
 
961
1401
  // Обновляем ручки когда объект изменяется через команды (Undo/Redo)
962
1402
  this.eventBus.on(Events.Object.TransformUpdated, (data) => {
963
- console.log(`🔄 Объект ${data.objectId} был изменен через команду, обновляем ручки`);
964
1403
  // Обновляем ручки если объект выделен
965
- if (this.selectTool && this.selectTool.selectedObjects.has(data.objectId)) {
1404
+ if (this.selectTool && this.selectTool.selection && this.selectTool.selection.has(data.objectId)) {
1405
+ console.log(`🔄 Core: Объект ${data.objectId} изменен через команду, обновляем ручки SelectTool`);
966
1406
  this.selectTool.updateResizeHandles();
967
1407
  }
968
1408
  });
@@ -974,27 +1414,21 @@ export class CoreMoodBoard {
974
1414
  });
975
1415
 
976
1416
  // Получение позиции объекта (левый-верх логических координат)
1417
+ // Используем размеры PIXI для согласованности с updateObjectPositionDirect
977
1418
  this.eventBus.on(Events.Tool.GetObjectPosition, (data) => {
978
1419
  const pixiObject = this.pixi.objects.get(data.objectId);
979
- if (pixiObject) {
980
- const halfW = (pixiObject.width || 0) / 2;
981
- const halfH = (pixiObject.height || 0) / 2;
982
- data.position = { x: pixiObject.x - halfW, y: pixiObject.y - halfH };
983
- }
1420
+ if (!pixiObject) return;
1421
+
1422
+ // Всегда используем размеры из PIXI для согласованности
1423
+ const halfW = (pixiObject.width || 0) / 2;
1424
+ const halfH = (pixiObject.height || 0) / 2;
1425
+ data.position = { x: pixiObject.x - halfW, y: pixiObject.y - halfH };
984
1426
  });
985
1427
 
986
1428
  // Получение PIXI объекта
987
1429
  this.eventBus.on(Events.Tool.GetObjectPixi, (data) => {
988
- console.log(`🔍 Запрос PIXI объекта для ${data.objectId}`);
989
- console.log('📋 Доступные PIXI объекты:', Array.from(this.pixi.objects.keys()));
990
-
991
1430
  const pixiObject = this.pixi.objects.get(data.objectId);
992
- if (pixiObject) {
993
- console.log(`✅ PIXI объект найден для ${data.objectId}`);
994
- data.pixiObject = pixiObject;
995
- } else {
996
- console.log(`❌ PIXI объект НЕ найден для ${data.objectId}`);
997
- }
1431
+ data.pixiObject = pixiObject || null;
998
1432
  });
999
1433
 
1000
1434
  // Получение списка всех объектов (с их PIXI и логическими границами)
@@ -1119,6 +1553,9 @@ export class CoreMoodBoard {
1119
1553
  if (updates.properties.fontSize !== undefined) {
1120
1554
  styleUpdates.fontSize = updates.properties.fontSize;
1121
1555
  }
1556
+ if (updates.properties.fontFamily !== undefined) {
1557
+ styleUpdates.fontFamily = updates.properties.fontFamily;
1558
+ }
1122
1559
 
1123
1560
  if (Object.keys(styleUpdates).length > 0) {
1124
1561
  instance.setStyle(styleUpdates);
@@ -1293,7 +1730,61 @@ export class CoreMoodBoard {
1293
1730
  group.meta.pasteCount = (group.meta.pasteCount || 0) + 1;
1294
1731
  const dx = offsetStep * group.meta.pasteCount;
1295
1732
  const dy = offsetStep * group.meta.pasteCount;
1296
- // Подготовим сбор новых id через единый временный слушатель
1733
+
1734
+ // Особая логика: фрейм-бандл (фрейм + дети)
1735
+ if (group.meta && group.meta.frameBundle) {
1736
+ const frames = data.filter(o => o && o.type === 'frame');
1737
+ if (frames.length === 1) {
1738
+ const frameOriginal = frames[0];
1739
+ const children = data.filter(o => o && o.id !== frameOriginal.id);
1740
+ const totalToPaste = 1 + children.length;
1741
+ let pastedCount = 0;
1742
+ const newIds = [];
1743
+ let newFrameId = null;
1744
+
1745
+ const onPasted = (payload) => {
1746
+ if (!payload || !payload.newId) return;
1747
+ newIds.push(payload.newId);
1748
+ pastedCount += 1;
1749
+ if (!newFrameId && payload.originalId === frameOriginal.id) {
1750
+ newFrameId = payload.newId;
1751
+ for (const child of children) {
1752
+ const clonedChild = JSON.parse(JSON.stringify(child));
1753
+ clonedChild.properties = clonedChild.properties || {};
1754
+ clonedChild.properties.frameId = newFrameId;
1755
+ const targetPos = {
1756
+ x: (clonedChild.position?.x || 0) + dx,
1757
+ y: (clonedChild.position?.y || 0) + dy
1758
+ };
1759
+ this.clipboard = { type: 'object', data: clonedChild };
1760
+ const cmdChild = new PasteObjectCommand(this, targetPos);
1761
+ cmdChild.setEventBus(this.eventBus);
1762
+ this.history.executeCommand(cmdChild);
1763
+ }
1764
+ }
1765
+ if (pastedCount === totalToPaste) {
1766
+ this.eventBus.off(Events.Object.Pasted, onPasted);
1767
+ if (this.selectTool && newIds.length > 0) {
1768
+ requestAnimationFrame(() => {
1769
+ this.selectTool.setSelection(newIds);
1770
+ this.selectTool.updateResizeHandles();
1771
+ });
1772
+ }
1773
+ }
1774
+ };
1775
+ this.eventBus.on(Events.Object.Pasted, onPasted);
1776
+
1777
+ const frameClone = JSON.parse(JSON.stringify(frameOriginal));
1778
+ this.clipboard = { type: 'object', data: frameClone };
1779
+ const cmdFrame = new PasteObjectCommand(this, { x: (frameClone.position?.x || 0) + dx, y: (frameClone.position?.y || 0) + dy });
1780
+ cmdFrame.setEventBus(this.eventBus);
1781
+ this.history.executeCommand(cmdFrame);
1782
+ this.clipboard = group;
1783
+ return;
1784
+ }
1785
+ }
1786
+
1787
+ // Обычная вставка группы
1297
1788
  let pending = data.length;
1298
1789
  const newIds = [];
1299
1790
  const onPasted = (payload) => {
@@ -1302,7 +1793,6 @@ export class CoreMoodBoard {
1302
1793
  pending -= 1;
1303
1794
  if (pending === 0) {
1304
1795
  this.eventBus.off(Events.Object.Pasted, onPasted);
1305
- // Выделяем новую группу и показываем рамку с ручками
1306
1796
  if (this.selectTool && newIds.length > 0) {
1307
1797
  requestAnimationFrame(() => {
1308
1798
  this.selectTool.setSelection(newIds);
@@ -1313,22 +1803,18 @@ export class CoreMoodBoard {
1313
1803
  };
1314
1804
  this.eventBus.on(Events.Object.Pasted, onPasted);
1315
1805
 
1316
- // Вставляем каждый объект группы, сохраняя относительное расположение + общее смещение
1317
1806
  for (const original of data) {
1318
1807
  const cloned = JSON.parse(JSON.stringify(original));
1319
1808
  const targetPos = {
1320
1809
  x: (cloned.position?.x || 0) + dx,
1321
1810
  y: (cloned.position?.y || 0) + dy
1322
1811
  };
1323
- // Используем существующую логику PasteObjectCommand поверх clipboard типа object
1324
1812
  this.clipboard = { type: 'object', data: cloned };
1325
1813
  const cmd = new PasteObjectCommand(this, targetPos);
1326
1814
  cmd.setEventBus(this.eventBus);
1327
1815
  this.history.executeCommand(cmd);
1328
1816
  }
1329
- // После вставки возвращаем clipboard к группе, чтобы можно было ещё раз вставлять с новым смещением
1330
1817
  this.clipboard = group;
1331
- // Рамка появится по завершении обработки всех событий object:pasted
1332
1818
  }
1333
1819
  });
1334
1820
 
@@ -1409,7 +1895,8 @@ export class CoreMoodBoard {
1409
1895
  * Используется во время перетаскивания для плавного движения
1410
1896
  */
1411
1897
  updateObjectPositionDirect(objectId, position) {
1412
- // position — левый верх (state); приводим к центру в PIXI
1898
+ // position — левый верх (state); приводим к центру в PIXI, используя размеры PIXI объекта
1899
+ // Все объекты используют pivot по центру, поэтому логика одинакова для всех
1413
1900
  const pixiObject = this.pixi.objects.get(objectId);
1414
1901
  if (pixiObject) {
1415
1902
  const halfW = (pixiObject.width || 0) / 2;
@@ -1494,6 +1981,47 @@ export class CoreMoodBoard {
1494
1981
  };
1495
1982
  const initialWidth = (properties && typeof properties.width === 'number') ? properties.width : 100;
1496
1983
  const initialHeight = (properties && typeof properties.height === 'number') ? properties.height : 100;
1984
+ // Зафиксировать пропорции для эмоджи-иконок (квадрат)
1985
+ if (type === 'image' && properties && properties.isEmojiIcon) {
1986
+ const s = Math.max(1, Math.round((initialWidth + initialHeight) / 2));
1987
+ properties.lockedAspect = true;
1988
+ properties.aspect = 1; // квадрат
1989
+ properties.width = s;
1990
+ properties.height = s;
1991
+ }
1992
+
1993
+ // Если создаём НЕ фрейм — проверим, попадает ли центр нового объекта внутрь какого-либо фрейма.
1994
+ // Если да, сразу прикрепляем объект к этому фрейму (properties.frameId)
1995
+ if (type !== 'frame' && position && this.pixi && typeof this.pixi.findObjectByPosition === 'function') {
1996
+ const center = {
1997
+ x: position.x + initialWidth / 2,
1998
+ y: position.y + initialHeight / 2
1999
+ };
2000
+ try {
2001
+ const hostFrame = this.pixi.findObjectByPosition(center, 'frame');
2002
+ if (hostFrame && hostFrame.id) {
2003
+ properties = { ...(properties || {}), frameId: hostFrame.id };
2004
+ }
2005
+ } catch (e) {
2006
+ // fail-safe: не мешаем созданию при ошибке поиска
2007
+ }
2008
+ }
2009
+
2010
+ // Именование фреймов: "Фрейм N", где N = количество уже пронумерованных фреймов + 1
2011
+ if (type === 'frame') {
2012
+ try {
2013
+ const objects = this.state?.state?.objects || [];
2014
+ const numberedCount = objects.filter(o => o && o.type === 'frame').reduce((acc, o) => {
2015
+ const t = o?.properties?.title || '';
2016
+ // Считаем только пронумерованные: "Фрейм <число>"
2017
+ return (/^\s*Фрейм\s+\d+\s*$/i.test(t)) ? acc + 1 : acc;
2018
+ }, 0);
2019
+ const nextIndex = numberedCount + 1;
2020
+ properties = { ...(properties || {}), title: `Фрейм ${nextIndex}` };
2021
+ } catch (_) {
2022
+ properties = { ...(properties || {}), title: 'Фрейм 1' };
2023
+ }
2024
+ }
1497
2025
  const objectData = {
1498
2026
  id: generateObjectId(exists),
1499
2027
  type,