@flikk/ui 1.0.0-beta.14 → 1.0.0-beta.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 (279) hide show
  1. package/README.md +52 -0
  2. package/dist/components/ai/CodeBlock/CodeBlock.theme.js +1 -1
  3. package/dist/components/ai/PromptInput/PromptInput.js +3 -1
  4. package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.js +4 -4
  5. package/dist/components/ai/StreamingResponse/AnimatedText.js +3 -3
  6. package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +22 -22
  7. package/dist/components/ai/StreamingResponse/StreamingCursor.js +1 -1
  8. package/dist/components/ai/StreamingResponse/StreamingResponse.animations.js +10 -10
  9. package/dist/components/ai/StreamingResponse/StreamingResponse.js +9 -3
  10. package/dist/components/ai/StreamingResponse/WordAnimationContext.d.ts +1 -1
  11. package/dist/components/ai/StreamingResponse/WordAnimationContext.js +11 -11
  12. package/dist/components/ai/index.d.ts +0 -7
  13. package/dist/components/ai/index.js +8 -6
  14. package/dist/components/charts/DonutChart/DonutChart.js +1 -1
  15. package/dist/components/charts/DonutChart/MultiSegmentDonutChart.js +1 -1
  16. package/dist/components/charts/shared/ChartLegend/ChartLegend.types.d.ts +13 -1
  17. package/dist/components/charts/shared/ChartLegend/ChartLegendContent.js +1 -1
  18. package/dist/components/charts/shared/ChartTooltip/ChartTooltip.types.d.ts +13 -2
  19. package/dist/components/core/Accordion/AccordionItem.js +1 -1
  20. package/dist/components/core/Accordion/AccordionTrigger.js +1 -1
  21. package/dist/components/core/Alert/Alert.animations.d.ts +11 -0
  22. package/dist/components/core/Alert/Alert.animations.js +44 -0
  23. package/dist/components/core/Alert/Alert.js +16 -3
  24. package/dist/components/core/Avatar/Avatar.js +12 -7
  25. package/dist/components/core/Avatar/Avatar.theme.d.ts +4 -6
  26. package/dist/components/core/Avatar/Avatar.theme.js +22 -12
  27. package/dist/components/core/Avatar/Avatar.types.d.ts +9 -0
  28. package/dist/components/core/AvatarGroup/AvatarGroup.animations.js +2 -2
  29. package/dist/components/core/AvatarGroup/AvatarGroup.js +1 -2
  30. package/dist/components/core/AvatarGroup/AvatarGroup.theme.js +1 -1
  31. package/dist/components/core/Badge/Badge.animations.js +1 -1
  32. package/dist/components/core/Badge/Badge.js +23 -47
  33. package/dist/components/core/Badge/Badge.theme.js +48 -62
  34. package/dist/components/core/Badge/Badge.types.d.ts +5 -29
  35. package/dist/components/core/Button/Button.js +2 -2
  36. package/dist/components/core/Button/Button.theme.js +2 -2
  37. package/dist/components/core/ButtonGroup/ButtonGroup.theme.js +6 -6
  38. package/dist/components/core/ButtonGroup/ButtonGroupText.js +1 -3
  39. package/dist/components/core/Calendar/Calendar.js +29 -3
  40. package/dist/components/core/Calendar/Calendar.theme.js +11 -7
  41. package/dist/components/core/Calendar/Calendar.types.d.ts +3 -0
  42. package/dist/components/core/Carousel/CarouselBody.js +0 -1
  43. package/dist/components/core/CommandPalette/CommandItem.js +1 -1
  44. package/dist/components/core/CommandPalette/CommandPalette.js +6 -4
  45. package/dist/components/core/CommandPalette/CommandPalette.theme.js +1 -0
  46. package/dist/components/core/CommandPalette/CommandPalette.types.d.ts +6 -0
  47. package/dist/components/core/ContextMenu/ContextMenu.js +5 -4
  48. package/dist/components/core/ContextMenu/ContextMenuTrigger.js +5 -6
  49. package/dist/components/core/Drawer/Drawer.d.ts +1 -1
  50. package/dist/components/core/Drawer/Drawer.js +25 -2
  51. package/dist/components/core/Drawer/Drawer.theme.js +2 -2
  52. package/dist/components/core/Drawer/Drawer.types.d.ts +6 -0
  53. package/dist/components/core/Dropdown/Dropdown.d.ts +1 -1
  54. package/dist/components/core/Dropdown/Dropdown.js +5 -1
  55. package/dist/components/core/Dropdown/Dropdown.types.d.ts +1 -0
  56. package/dist/components/core/Dropdown/DropdownItem.js +1 -1
  57. package/dist/components/core/Dropdown/DropdownMenu.js +4 -4
  58. package/dist/components/core/Dropdown/DropdownSection.js +1 -1
  59. package/dist/components/core/Dropdown/DropdownSeparator.js +1 -1
  60. package/dist/components/core/Dropdown/DropdownTrigger.d.ts +6 -1
  61. package/dist/components/core/Dropdown/DropdownTrigger.js +10 -8
  62. package/dist/components/core/Kbd/Kbd.theme.js +3 -3
  63. package/dist/components/core/Link/Link.js +3 -1
  64. package/dist/components/core/Message/Message.d.ts +9 -0
  65. package/dist/components/core/Message/Message.js +44 -3
  66. package/dist/components/core/Message/Message.theme.js +22 -0
  67. package/dist/components/core/Message/Message.types.d.ts +115 -0
  68. package/dist/components/core/Message/MessageAudio.d.ts +8 -0
  69. package/dist/components/core/Message/MessageAudio.js +11 -0
  70. package/dist/components/core/Message/MessageContext.d.ts +3 -1
  71. package/dist/components/core/Message/MessageDateDivider.d.ts +6 -0
  72. package/dist/components/core/Message/MessageDateDivider.js +10 -0
  73. package/dist/components/core/Message/MessageFile.d.ts +9 -0
  74. package/dist/components/core/Message/MessageFile.js +15 -0
  75. package/dist/components/core/Message/MessageImage.d.ts +11 -0
  76. package/dist/components/core/Message/MessageImage.js +13 -0
  77. package/dist/components/core/Message/MessageLink.d.ts +10 -0
  78. package/dist/components/core/Message/MessageLink.js +15 -0
  79. package/dist/components/core/Message/MessageReactions.d.ts +8 -0
  80. package/dist/components/core/Message/MessageReactions.js +12 -0
  81. package/dist/components/core/Message/MessageReply.d.ts +8 -0
  82. package/dist/components/core/Message/MessageReply.js +10 -0
  83. package/dist/components/core/Message/MessageSenderName.d.ts +6 -0
  84. package/dist/components/core/Message/MessageSenderName.js +10 -0
  85. package/dist/components/core/Message/MessageStatusIndicator.d.ts +9 -0
  86. package/dist/components/core/Message/MessageStatusIndicator.js +45 -0
  87. package/dist/components/core/Message/index.d.ts +19 -1
  88. package/dist/components/core/Modal/Modal.animations.d.ts +1 -0
  89. package/dist/components/core/Modal/Modal.animations.js +14 -11
  90. package/dist/components/core/Pagination/Pagination.theme.js +1 -1
  91. package/dist/components/core/Popover/Popover.d.ts +1 -1
  92. package/dist/components/core/Popover/PopoverBody.js +2 -2
  93. package/dist/components/core/Popover/PopoverTrigger.d.ts +1 -1
  94. package/dist/components/core/Popover/PopoverTrigger.js +11 -11
  95. package/dist/components/core/Progress/Progress.d.ts +9 -8
  96. package/dist/components/core/Progress/Progress.js +16 -16
  97. package/dist/components/core/Progress/Progress.theme.js +4 -15
  98. package/dist/components/core/Progress/Progress.types.d.ts +2 -20
  99. package/dist/components/core/Progress/index.d.ts +1 -1
  100. package/dist/components/core/ScrollArea/ScrollArea.js +49 -7
  101. package/dist/components/core/ScrollArea/ScrollArea.types.d.ts +12 -3
  102. package/dist/components/core/ScrollArea/index.d.ts +1 -1
  103. package/dist/components/core/Segmented/SegmentedItem.js +1 -1
  104. package/dist/components/core/Sidebar/Sidebar.js +2 -1
  105. package/dist/components/core/Sidebar/SidebarMobileTrigger.js +2 -1
  106. package/dist/components/core/Sidebar/SidebarToggle.js +2 -1
  107. package/dist/components/core/Sidebar/SidebarUserProfile.js +1 -1
  108. package/dist/components/core/SocialIcon/SocialIcon.js +1 -1
  109. package/dist/components/core/Sortable/Sortable.js +2 -2
  110. package/dist/components/core/Sortable/Sortable.theme.js +1 -1
  111. package/dist/components/core/Sortable/Sortable.types.d.ts +7 -5
  112. package/dist/components/core/Tabs/TabsBody.js +1 -1
  113. package/dist/components/core/Tabs/TabsTrigger.js +1 -1
  114. package/dist/components/core/Tag/Tag.js +2 -2
  115. package/dist/components/core/Toast/Toast.d.ts +2 -2
  116. package/dist/components/core/Toast/Toast.js +30 -21
  117. package/dist/components/core/Toast/Toast.theme.js +4 -9
  118. package/dist/components/core/Toast/Toast.types.d.ts +4 -21
  119. package/dist/components/core/Toast/index.d.ts +1 -1
  120. package/dist/components/core/Tooltip/Tooltip.js +1 -1
  121. package/dist/components/core/Tooltip/Tooltip.theme.js +1 -1
  122. package/dist/components/core/index.js +9 -0
  123. package/dist/components/data-display/GanttChart/GanttChart.types.d.ts +2 -2
  124. package/dist/components/data-display/Table/Table.types.d.ts +8 -8
  125. package/dist/components/effects/CustomCursor/CustomCursor.js +2 -2
  126. package/dist/components/effects/GlassSurface/GlassSurface.d.ts +1 -1
  127. package/dist/components/effects/GlassSurface/GlassSurface.js +17 -9
  128. package/dist/components/effects/GlassSurface/GlassSurface.types.d.ts +6 -0
  129. package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.animations.d.ts +39 -0
  130. package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.animations.js +98 -0
  131. package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.d.ts +3 -0
  132. package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.js +104 -0
  133. package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.types.d.ts +30 -0
  134. package/dist/components/effects/InteractiveCharacters/characters/BlobCharacter.d.ts +3 -0
  135. package/dist/components/effects/InteractiveCharacters/characters/BlobCharacter.js +36 -0
  136. package/dist/components/effects/InteractiveCharacters/characters/SmallCharacter.d.ts +3 -0
  137. package/dist/components/effects/InteractiveCharacters/characters/SmallCharacter.js +36 -0
  138. package/dist/components/effects/InteractiveCharacters/characters/TallCharacter.d.ts +3 -0
  139. package/dist/components/effects/InteractiveCharacters/characters/TallCharacter.js +37 -0
  140. package/dist/components/effects/InteractiveCharacters/hooks/useBlink.d.ts +14 -0
  141. package/dist/components/effects/InteractiveCharacters/hooks/useBlink.js +41 -0
  142. package/dist/components/effects/InteractiveCharacters/hooks/useIdleAnimation.d.ts +25 -0
  143. package/dist/components/effects/InteractiveCharacters/hooks/useIdleAnimation.js +84 -0
  144. package/dist/components/effects/InteractiveCharacters/hooks/useMouseTracking.d.ts +18 -0
  145. package/dist/components/effects/InteractiveCharacters/hooks/useMouseTracking.js +35 -0
  146. package/dist/components/effects/InteractiveCharacters/index.d.ts +2 -0
  147. package/dist/components/effects/MorphingText/MorphingText.d.ts +1 -1
  148. package/dist/components/effects/MorphingText/MorphingText.js +6 -5
  149. package/dist/components/effects/Neumorphic/InsetCircleButton.d.ts +10 -0
  150. package/dist/components/effects/Neumorphic/InsetCircleButton.js +10 -0
  151. package/dist/components/effects/Neumorphic/InsetPill.d.ts +8 -0
  152. package/dist/components/effects/Neumorphic/InsetPill.js +7 -0
  153. package/dist/components/effects/Neumorphic/index.d.ts +4 -0
  154. package/dist/components/effects/NoiseOverlay/NoiseOverlay.d.ts +1 -1
  155. package/dist/components/effects/NoiseOverlay/NoiseOverlay.js +4 -4
  156. package/dist/components/effects/Overlay/Overlay.js +6 -4
  157. package/dist/components/effects/PageTransition/PageTransition.animations.d.ts +8 -0
  158. package/dist/components/effects/PageTransition/PageTransition.animations.js +53 -0
  159. package/dist/components/effects/PageTransition/PageTransition.d.ts +3 -0
  160. package/dist/components/effects/PageTransition/PageTransition.js +71 -0
  161. package/dist/components/effects/PageTransition/PageTransition.types.d.ts +15 -0
  162. package/dist/components/effects/PageTransition/index.d.ts +2 -0
  163. package/dist/components/effects/index.d.ts +6 -0
  164. package/dist/components/effects/index.js +4 -0
  165. package/dist/components/forms/Checkbox/Checkbox.animations.d.ts +14 -0
  166. package/dist/components/forms/Checkbox/Checkbox.animations.js +53 -0
  167. package/dist/components/forms/Checkbox/Checkbox.js +4 -2
  168. package/dist/components/forms/Checkbox/Checkbox.theme.js +3 -3
  169. package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +1 -1
  170. package/dist/components/forms/ColorPicker/ColorPickerBody.js +3 -3
  171. package/dist/components/forms/Combobox/Combobox.js +5 -3
  172. package/dist/components/forms/DatePicker/DatePickerBody.js +1 -1
  173. package/dist/components/forms/DateRangePicker/DateRangePicker.animations.d.ts +1 -0
  174. package/dist/components/forms/DateRangePicker/DateRangePicker.d.ts +37 -0
  175. package/dist/components/forms/DateRangePicker/DateRangePicker.js +110 -0
  176. package/dist/components/forms/DateRangePicker/DateRangePicker.theme.d.ts +5 -0
  177. package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +52 -0
  178. package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +154 -0
  179. package/dist/components/forms/DateRangePicker/DateRangePickerBody.d.ts +6 -0
  180. package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +208 -0
  181. package/dist/components/forms/DateRangePicker/DateRangePickerPresets.d.ts +6 -0
  182. package/dist/components/forms/DateRangePicker/DateRangePickerPresets.js +41 -0
  183. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.d.ts +6 -0
  184. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +32 -0
  185. package/dist/components/forms/DateRangePicker/dateRangePresets.d.ts +5 -0
  186. package/dist/components/forms/DateRangePicker/dateRangePresets.js +59 -0
  187. package/dist/components/forms/DateRangePicker/index.d.ts +4 -0
  188. package/dist/components/forms/FileUpload/FileUploadProgress.animations.d.ts +36 -0
  189. package/dist/components/forms/FileUpload/FileUploadProgress.animations.js +22 -0
  190. package/dist/components/forms/FileUpload/FileUploadProgress.d.ts +3 -0
  191. package/dist/components/forms/FileUpload/FileUploadProgress.js +111 -0
  192. package/dist/components/forms/FileUpload/FileUploadProgress.theme.d.ts +19 -0
  193. package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +27 -0
  194. package/dist/components/forms/FileUpload/FileUploadProgress.types.d.ts +33 -0
  195. package/dist/components/forms/FileUpload/index.d.ts +3 -0
  196. package/dist/components/forms/Input/Input.js +8 -13
  197. package/dist/components/forms/Input/Input.types.d.ts +6 -0
  198. package/dist/components/forms/InputAddress/InputAddress.theme.js +1 -1
  199. package/dist/components/forms/InputCounter/InputCounter.js +1 -1
  200. package/dist/components/forms/InputCreditCard/InputCreditCard.theme.js +1 -1
  201. package/dist/components/forms/InputOTP/InputOTP.theme.js +1 -1
  202. package/dist/components/forms/Radio/Radio.animations.d.ts +10 -0
  203. package/dist/components/forms/Radio/Radio.animations.js +33 -0
  204. package/dist/components/forms/Radio/Radio.js +11 -2
  205. package/dist/components/forms/Radio/Radio.theme.js +3 -3
  206. package/dist/components/forms/RichTextEditor/RichTextEditor.js +13 -4
  207. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +2 -2
  208. package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +11 -0
  209. package/dist/components/forms/Select/Select.d.ts +1 -1
  210. package/dist/components/forms/Select/Select.js +7 -7
  211. package/dist/components/forms/Select/Select.types.d.ts +3 -3
  212. package/dist/components/forms/SelectableCard/SelectableCard.js +1 -1
  213. package/dist/components/forms/Slider/Slider.js +10 -3
  214. package/dist/components/forms/Slider/Slider.theme.js +3 -3
  215. package/dist/components/forms/Switch/Switch.js +10 -2
  216. package/dist/components/forms/Switch/Switch.theme.js +3 -3
  217. package/dist/components/forms/forms.theme.js +1 -1
  218. package/dist/components/forms/index.d.ts +6 -3
  219. package/dist/components/forms/index.js +8 -2
  220. package/dist/hooks/index.d.ts +1 -0
  221. package/dist/hooks/useMediaQuery.d.ts +5 -0
  222. package/dist/hooks/useMediaQuery.js +21 -0
  223. package/dist/index.js +21 -2
  224. package/dist/styles.css +1 -1
  225. package/dist/utils/dateUtils.js +56 -5
  226. package/dist/utils/formatUtils.d.ts +10 -0
  227. package/dist/utils/formatUtils.js +24 -0
  228. package/dist/utils/index.d.ts +1 -1
  229. package/package.json +12 -4
  230. package/src/global.scss +319 -39
  231. package/src/styles/theme.css +105 -16
  232. package/src/theme-plugin.css +13 -0
  233. package/dist/components/ai/ApprovalCard/ApprovalCard.d.ts +0 -3
  234. package/dist/components/ai/ApprovalCard/ApprovalCard.js +0 -16
  235. package/dist/components/ai/ApprovalCard/ApprovalCard.theme.d.ts +0 -13
  236. package/dist/components/ai/ApprovalCard/ApprovalCard.theme.js +0 -15
  237. package/dist/components/ai/ApprovalCard/ApprovalCard.types.d.ts +0 -43
  238. package/dist/components/ai/ApprovalCard/index.d.ts +0 -2
  239. package/dist/components/ai/MessageHistory/MessageHistory.d.ts +0 -18
  240. package/dist/components/ai/MessageHistory/MessageHistory.js +0 -98
  241. package/dist/components/ai/MessageHistory/MessageHistory.theme.d.ts +0 -2
  242. package/dist/components/ai/MessageHistory/MessageHistory.theme.js +0 -8
  243. package/dist/components/ai/MessageHistory/MessageHistory.types.d.ts +0 -94
  244. package/dist/components/ai/MessageHistory/index.d.ts +0 -3
  245. package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.animations.d.ts +0 -10
  246. package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.animations.js +0 -47
  247. package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.d.ts +0 -20
  248. package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.js +0 -55
  249. package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.theme.d.ts +0 -2
  250. package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.theme.js +0 -20
  251. package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.types.d.ts +0 -67
  252. package/dist/components/ai/ThinkingIndicator/index.d.ts +0 -3
  253. package/dist/components/forms/TransferList/TransferList.animations.d.ts +0 -10
  254. package/dist/components/forms/TransferList/TransferList.animations.js +0 -32
  255. package/dist/components/forms/TransferList/TransferList.d.ts +0 -28
  256. package/dist/components/forms/TransferList/TransferList.js +0 -199
  257. package/dist/components/forms/TransferList/TransferList.theme.d.ts +0 -6
  258. package/dist/components/forms/TransferList/TransferList.theme.js +0 -60
  259. package/dist/components/forms/TransferList/TransferList.types.d.ts +0 -101
  260. package/dist/components/forms/TransferList/index.d.ts +0 -3
  261. package/dist/icons/Icon.d.ts +0 -24
  262. package/dist/icons/Icon.js +0 -30
  263. package/dist/icons/Index.d.ts +0 -16
  264. package/dist/icons/core/ChevronUpDown.d.ts +0 -1
  265. package/dist/icons/core/ChevronUpDown.js +0 -7
  266. package/dist/icons/core/DollarIcon.d.ts +0 -1
  267. package/dist/icons/core/DollarIcon.js +0 -5
  268. package/dist/icons/core/LockIcon.d.ts +0 -1
  269. package/dist/icons/core/LockIcon.js +0 -5
  270. package/dist/icons/core/MinusIcon.d.ts +0 -1
  271. package/dist/icons/core/MinusIcon.js +0 -7
  272. package/dist/icons/core/PlusIcon.d.ts +0 -1
  273. package/dist/icons/core/PlusIcon.js +0 -7
  274. package/dist/icons/core/SearchIcon.d.ts +0 -1
  275. package/dist/icons/core/SearchIcon.js +0 -7
  276. package/dist/icons/core/TickIcon.d.ts +0 -1
  277. package/dist/icons/core/TickIcon.js +0 -5
  278. package/dist/icons/core/User.d.ts +0 -1
  279. package/dist/utils/useClickOutside.d.ts +0 -1
@@ -0,0 +1,98 @@
1
+ // Eyelid variants — controls how much the eye is covered
2
+ // scaleY: 0 = fully open, 1 = fully closed
3
+ const eyelidVariants = {
4
+ idle: { scaleY: 0 },
5
+ email: { scaleY: 0 },
6
+ password: { scaleY: 1 },
7
+ peek: { scaleY: 0.6 },
8
+ surprised: { scaleY: 0 }, // wide open
9
+ love: { scaleY: 0.65 }, // happy squint ^_^
10
+ excited: { scaleY: 0 }, // wide open
11
+ };
12
+ // Body sway variants per character
13
+ const blobBodyVariants = {
14
+ idle: { rotate: 0, y: 0 },
15
+ email: { rotate: 2, y: -2 },
16
+ password: { rotate: -1, y: 2, scale: 0.97 },
17
+ peek: { rotate: 3, y: -1 },
18
+ surprised: { rotate: 0, y: -8, scale: 1.05 }, // jump up, puff out
19
+ love: { rotate: 0, y: 0 },
20
+ excited: { rotate: 0, y: 0 },
21
+ };
22
+ const tallBodyVariants = {
23
+ idle: { rotate: 0, y: 0 },
24
+ email: { rotate: -1.5, y: 0 },
25
+ password: { rotate: 0, y: 3, scale: 0.96 },
26
+ peek: { rotate: 2, y: -2 },
27
+ surprised: { rotate: 0, y: 0 },
28
+ love: { rotate: -2, y: -3, scale: 1.03 }, // slight lean, puff up
29
+ excited: { rotate: 0, y: 0 },
30
+ };
31
+ const smallBodyVariants = {
32
+ idle: { rotate: 0, y: 0 },
33
+ email: { rotate: 3, y: -3 },
34
+ password: { rotate: -2, y: 1, scale: 0.95 },
35
+ peek: { rotate: -3, y: -2 },
36
+ surprised: { rotate: 0, y: 0 },
37
+ love: { rotate: 0, y: 0 },
38
+ excited: { rotate: 5, y: -10, scale: 1.08 }, // big bounce, spin
39
+ };
40
+ // Mouth paths for each character per state
41
+ // All paths use M + Q for smooth interpolation between states
42
+ const blobMouthPaths = {
43
+ idle: 'M -10 0 Q 0 8 10 0', // wide gentle smile
44
+ email: 'M -12 -2 Q 0 12 12 -2', // big happy smile
45
+ password: 'M -6 0 Q 0 2 6 0', // flat/neutral
46
+ peek: 'M -8 0 Q 0 5 8 0', // small smirk
47
+ surprised: 'M -5 -4 Q 0 10 5 -4', // round "O" mouth
48
+ love: 'M -10 0 Q 0 8 10 0', // gentle smile
49
+ excited: 'M -12 -2 Q 0 12 12 -2', // big grin
50
+ };
51
+ const tallMouthPaths = {
52
+ idle: 'M -6 0 Q 0 3 6 0', // subtle relaxed smile
53
+ email: 'M -10 -1 Q 0 10 10 -1', // big happy smile
54
+ password: 'M -5 0 Q 0 0 5 0', // flat line (Q-based for animation)
55
+ peek: 'M -5 0 Q 2 6 7 -1', // asymmetric smirk
56
+ surprised: 'M -5 -3 Q 0 8 5 -3', // small O
57
+ love: 'M -10 -2 Q 0 12 10 -2', // wide beaming smile
58
+ excited: 'M -8 -1 Q 0 10 8 -1', // happy grin
59
+ };
60
+ const smallMouthPaths = {
61
+ idle: 'M -7 0 Q 0 6 7 0', // sweet smile
62
+ email: 'M -9 -1 Q 0 9 9 -1', // big grin
63
+ password: 'M -4 0 Q 0 1 4 0', // tiny neutral
64
+ peek: 'M -4 1 Q 1 6 6 0', // cheeky smirk
65
+ surprised: 'M -4 -3 Q 0 8 4 -3', // little O
66
+ love: 'M -7 0 Q 0 6 7 0', // sweet smile
67
+ excited: 'M -10 -3 Q 0 14 10 -3', // huge open grin
68
+ };
69
+ // Eyebrow angles for Tall character
70
+ const eyebrowVariants = {
71
+ idle: { rotate: 0, y: 0 },
72
+ email: { rotate: -5, y: -2 },
73
+ password: { rotate: 8, y: 2 },
74
+ peek: { rotate: -10, y: -1 },
75
+ surprised: { rotate: -12, y: -4 }, // raised high
76
+ love: { rotate: -6, y: -2 }, // gently raised
77
+ excited: { rotate: -8, y: -3 }, // raised
78
+ };
79
+ // Spring configs per character for mouse tracking
80
+ const springConfigs = {
81
+ small: { stiffness: 300, damping: 20 },
82
+ blob: { stiffness: 150, damping: 25 },
83
+ tall: { stiffness: 80, damping: 30 },
84
+ };
85
+ // Body spring for state transitions
86
+ const bodyTransition = {
87
+ type: 'spring',
88
+ stiffness: 200,
89
+ damping: 20,
90
+ };
91
+ // Bouncier transition for click reactions
92
+ const clickTransition = {
93
+ type: 'spring',
94
+ stiffness: 300,
95
+ damping: 15,
96
+ };
97
+
98
+ export { blobBodyVariants, blobMouthPaths, bodyTransition, clickTransition, eyebrowVariants, eyelidVariants, smallBodyVariants, smallMouthPaths, springConfigs, tallBodyVariants, tallMouthPaths };
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ import type { InteractiveCharactersProps } from './InteractiveCharacters.types';
3
+ export declare const InteractiveCharacters: React.FC<InteractiveCharactersProps>;
@@ -0,0 +1,104 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { useRef, useState, useCallback, useEffect } from 'react';
3
+ import { useReducedMotion } from 'motion/react';
4
+ import { cn } from '../../../utils/cn.js';
5
+ import { springConfigs } from './InteractiveCharacters.animations.js';
6
+ import { useMouseTracking } from './hooks/useMouseTracking.js';
7
+ import { useIdleAnimation } from './hooks/useIdleAnimation.js';
8
+ import { useBlink } from './hooks/useBlink.js';
9
+ import { BlobCharacter } from './characters/BlobCharacter.js';
10
+ import { TallCharacter } from './characters/TallCharacter.js';
11
+ import { SmallCharacter } from './characters/SmallCharacter.js';
12
+
13
+ const sizeMap = {
14
+ sm: 'w-[200px]',
15
+ md: 'w-[320px]',
16
+ lg: 'w-[400px]',
17
+ };
18
+ /** Click reaction states per character */
19
+ const CLICK_REACTIONS = {
20
+ blob: 'surprised',
21
+ tall: 'love',
22
+ small: 'excited',
23
+ };
24
+ /** How long click reactions last (ms) */
25
+ const CLICK_DURATION = 1500;
26
+ const InteractiveCharacters = ({ state = 'idle', trackMouse = true, size = 'md', className, }) => {
27
+ const svgRef = useRef(null);
28
+ const shouldReduceMotion = useReducedMotion();
29
+ const clickTimeoutRef = useRef(null);
30
+ const [clickedChar, setClickedChar] = useState(null);
31
+ const handleCharacterClick = useCallback((character) => {
32
+ if (clickTimeoutRef.current)
33
+ clearTimeout(clickTimeoutRef.current);
34
+ setClickedChar(character);
35
+ clickTimeoutRef.current = setTimeout(() => setClickedChar(null), CLICK_DURATION);
36
+ }, []);
37
+ // Cleanup timeout on unmount
38
+ useEffect(() => {
39
+ return () => {
40
+ if (clickTimeoutRef.current)
41
+ clearTimeout(clickTimeoutRef.current);
42
+ };
43
+ }, []);
44
+ // Determine effective state per character (click overrides form state)
45
+ const blobState = clickedChar === 'blob' ? CLICK_REACTIONS.blob : state;
46
+ const tallState = clickedChar === 'tall' ? CLICK_REACTIONS.tall : state;
47
+ const smallState = clickedChar === 'small' ? CLICK_REACTIONS.small : state;
48
+ const isTrackingEnabled = trackMouse && state !== 'password' && !shouldReduceMotion;
49
+ const isIdleAnimated = !shouldReduceMotion;
50
+ // Mouse tracking — different spring configs per character
51
+ const blobEye = useMouseTracking({
52
+ containerRef: svgRef,
53
+ enabled: isTrackingEnabled,
54
+ ...springConfigs.blob,
55
+ });
56
+ const tallEye = useMouseTracking({
57
+ containerRef: svgRef,
58
+ enabled: isTrackingEnabled,
59
+ ...springConfigs.tall,
60
+ });
61
+ const smallEye = useMouseTracking({
62
+ containerRef: svgRef,
63
+ enabled: isTrackingEnabled,
64
+ ...springConfigs.small,
65
+ });
66
+ // Idle sway/breathing/head — each character at different rates & phase offsets
67
+ const blobIdle = useIdleAnimation({
68
+ enabled: isIdleAnimated,
69
+ swayAmount: 1.5,
70
+ swaySpeed: 0.8,
71
+ breathAmount: 0.008,
72
+ headTiltAmount: 3,
73
+ headNudgeAmount: 1.5,
74
+ phaseOffset: 0,
75
+ });
76
+ const tallIdle = useIdleAnimation({
77
+ enabled: isIdleAnimated,
78
+ swayAmount: 1,
79
+ swaySpeed: 0.6,
80
+ breathAmount: 0.006,
81
+ headTiltAmount: 2,
82
+ headNudgeAmount: 1,
83
+ phaseOffset: 2.5,
84
+ });
85
+ const smallIdle = useIdleAnimation({
86
+ enabled: isIdleAnimated,
87
+ swayAmount: 2.5,
88
+ swaySpeed: 1.2,
89
+ breathAmount: 0.012,
90
+ headTiltAmount: 4,
91
+ headNudgeAmount: 2,
92
+ phaseOffset: 5,
93
+ });
94
+ // Blink — disable during password/peek and click reactions
95
+ const canBlinkBlob = blobState === 'idle' || blobState === 'email';
96
+ const canBlinkTall = tallState === 'idle' || tallState === 'email';
97
+ const canBlinkSmall = smallState === 'idle' || smallState === 'email';
98
+ const blobBlink = useBlink({ enabled: canBlinkBlob, minInterval: 2500, maxInterval: 5000 });
99
+ const tallBlink = useBlink({ enabled: canBlinkTall, minInterval: 3000, maxInterval: 6000 });
100
+ const smallBlink = useBlink({ enabled: canBlinkSmall, minInterval: 1800, maxInterval: 4000 });
101
+ return (jsx("div", { className: cn(sizeMap[size], className), "data-state": state, children: jsxs("svg", { ref: svgRef, viewBox: "0 0 400 180", xmlns: "http://www.w3.org/2000/svg", className: "w-full h-auto", role: "img", "aria-label": "Interactive characters that react to form input", children: [jsx("g", { transform: "translate(200, 170)", className: "cursor-pointer", children: jsx(TallCharacter, { state: tallState, eyeOffset: { x: tallEye.x, y: tallEye.y }, idle: tallIdle, blinkAmount: tallBlink, onClick: () => handleCharacterClick('tall') }) }), jsx("g", { transform: "translate(100, 170)", className: "cursor-pointer", children: jsx(BlobCharacter, { state: blobState, eyeOffset: { x: blobEye.x, y: blobEye.y }, idle: blobIdle, blinkAmount: blobBlink, onClick: () => handleCharacterClick('blob') }) }), jsx("g", { transform: "translate(310, 170)", className: "cursor-pointer", children: jsx(SmallCharacter, { state: smallState, eyeOffset: { x: smallEye.x, y: smallEye.y }, idle: smallIdle, blinkAmount: smallBlink, onClick: () => handleCharacterClick('small') }) })] }) }));
102
+ };
103
+
104
+ export { InteractiveCharacters };
@@ -0,0 +1,30 @@
1
+ import type { MotionValue } from 'motion/react';
2
+ export type CharacterState = 'idle' | 'email' | 'password' | 'peek' | 'surprised' | 'love' | 'excited';
3
+ export type CharacterSize = 'sm' | 'md' | 'lg';
4
+ export interface InteractiveCharactersProps {
5
+ /** Current character state — controls expressions and animations */
6
+ state?: CharacterState;
7
+ /** Enable mouse/cursor tracking for eye movement (default: true) */
8
+ trackMouse?: boolean;
9
+ /** Size preset (default: 'md') */
10
+ size?: CharacterSize;
11
+ /** Additional class names for the container */
12
+ className?: string;
13
+ }
14
+ export interface CharacterProps {
15
+ state: CharacterState;
16
+ eyeOffset: {
17
+ x: MotionValue<number>;
18
+ y: MotionValue<number>;
19
+ };
20
+ idle: {
21
+ sway: MotionValue<number>;
22
+ bob: MotionValue<number>;
23
+ breath: MotionValue<number>;
24
+ headTilt: MotionValue<number>;
25
+ headX: MotionValue<number>;
26
+ headY: MotionValue<number>;
27
+ };
28
+ blinkAmount: MotionValue<number>;
29
+ onClick?: () => void;
30
+ }
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ import type { CharacterProps } from '../InteractiveCharacters.types';
3
+ export declare const BlobCharacter: React.FC<CharacterProps>;
@@ -0,0 +1,36 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useTransform, motion } from 'motion/react';
3
+ import { eyelidVariants, clickTransition, bodyTransition, blobBodyVariants, blobMouthPaths } from '../InteractiveCharacters.animations.js';
4
+
5
+ const LEFT_EYE = { cx: 32, cy: -50 };
6
+ const RIGHT_EYE = { cx: 68, cy: -50 };
7
+ const CLICK_STATES = new Set(['surprised', 'love', 'excited']);
8
+ const BlobCharacter = ({ state, eyeOffset, idle, blinkAmount, onClick, }) => {
9
+ const leftPupilX = useTransform(eyeOffset.x, (v) => LEFT_EYE.cx + v);
10
+ const leftPupilY = useTransform(eyeOffset.y, (v) => LEFT_EYE.cy + v);
11
+ const rightPupilX = useTransform(eyeOffset.x, (v) => RIGHT_EYE.cx + v);
12
+ const rightPupilY = useTransform(eyeOffset.y, (v) => RIGHT_EYE.cy + v);
13
+ // Combine idle head movement with cursor tracking for head turning
14
+ const combinedHeadX = useTransform([idle.headX, eyeOffset.x], ([idleX, cursorX]) => idleX + cursorX * 0.5);
15
+ const combinedHeadY = useTransform([idle.headY, eyeOffset.y], ([idleY, cursorY]) => idleY + cursorY * 0.3);
16
+ const combinedHeadRotate = useTransform([idle.headTilt, eyeOffset.x], ([tilt, cursorX]) => tilt + cursorX * 0.5);
17
+ const stateEyelid = eyelidVariants[state].scaleY;
18
+ const eyelidScale = useTransform(blinkAmount, (blink) => Math.max(stateEyelid, blink));
19
+ const isClickReaction = CLICK_STATES.has(state);
20
+ const transition = isClickReaction ? clickTransition : bodyTransition;
21
+ return (jsxs(motion.g, { animate: state, variants: blobBodyVariants, transition: transition, style: {
22
+ originX: '50%',
23
+ originY: '100%',
24
+ x: idle.sway,
25
+ y: idle.bob,
26
+ scale: idle.breath,
27
+ }, onClick: onClick, children: [jsx("path", { d: "M 0 0 Q 0 -95 65 -95 Q 100 -95 100 -50 Q 100 0 100 0 Z", fill: "#F97316" }), jsxs(motion.g, { style: {
28
+ originX: '50px',
29
+ originY: '-50px',
30
+ rotate: combinedHeadRotate,
31
+ x: combinedHeadX,
32
+ y: combinedHeadY,
33
+ }, children: [jsxs("g", { children: [jsx("clipPath", { id: "blob-eye-l", children: jsx("ellipse", { cx: LEFT_EYE.cx, cy: LEFT_EYE.cy, rx: 14, ry: 16 }) }), jsx("ellipse", { cx: LEFT_EYE.cx, cy: LEFT_EYE.cy, rx: 14, ry: 16, fill: "white" }), jsx(motion.circle, { cx: leftPupilX, cy: leftPupilY, r: 8.5, fill: "#1E293B", clipPath: "url(#blob-eye-l)" }), jsx("circle", { cx: LEFT_EYE.cx + 4, cy: LEFT_EYE.cy - 5.5, r: 3, fill: "white", opacity: 0.95 }), jsx("circle", { cx: LEFT_EYE.cx - 3, cy: LEFT_EYE.cy + 4, r: 1.5, fill: "white", opacity: 0.7 }), jsx(motion.rect, { x: LEFT_EYE.cx - 14, y: LEFT_EYE.cy - 16, width: 28, height: 32, fill: "#F97316", rx: 2, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsxs("g", { children: [jsx("clipPath", { id: "blob-eye-r", children: jsx("ellipse", { cx: RIGHT_EYE.cx, cy: RIGHT_EYE.cy, rx: 14, ry: 16 }) }), jsx("ellipse", { cx: RIGHT_EYE.cx, cy: RIGHT_EYE.cy, rx: 14, ry: 16, fill: "white" }), jsx(motion.circle, { cx: rightPupilX, cy: rightPupilY, r: 8.5, fill: "#1E293B", clipPath: "url(#blob-eye-r)" }), jsx("circle", { cx: RIGHT_EYE.cx + 4, cy: RIGHT_EYE.cy - 5.5, r: 3, fill: "white", opacity: 0.95 }), jsx("circle", { cx: RIGHT_EYE.cx - 3, cy: RIGHT_EYE.cy + 4, r: 1.5, fill: "white", opacity: 0.7 }), jsx(motion.rect, { x: RIGHT_EYE.cx - 14, y: RIGHT_EYE.cy - 16, width: 28, height: 32, fill: "#F97316", rx: 2, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsx("ellipse", { cx: 18, cy: -28, rx: 8, ry: 5.5, fill: "#FB923C", opacity: state === 'surprised' ? 0.7 : 0.5 }), jsx("ellipse", { cx: 82, cy: -28, rx: 8, ry: 5.5, fill: "#FB923C", opacity: state === 'surprised' ? 0.7 : 0.5 }), jsx(motion.path, { d: blobMouthPaths[state], fill: "none", stroke: "#1E293B", strokeWidth: 2.5, strokeLinecap: "round", animate: { d: blobMouthPaths[state] }, transition: transition, transform: "translate(50, -24)" })] })] }));
34
+ };
35
+
36
+ export { BlobCharacter };
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ import type { CharacterProps } from '../InteractiveCharacters.types';
3
+ export declare const SmallCharacter: React.FC<CharacterProps>;
@@ -0,0 +1,36 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useTransform, motion } from 'motion/react';
3
+ import { eyelidVariants, clickTransition, bodyTransition, smallBodyVariants, smallMouthPaths } from '../InteractiveCharacters.animations.js';
4
+
5
+ const LEFT_EYE = { cx: -14, cy: -32 };
6
+ const RIGHT_EYE = { cx: 14, cy: -32 };
7
+ const CLICK_STATES = new Set(['surprised', 'love', 'excited']);
8
+ const SmallCharacter = ({ state, eyeOffset, idle, blinkAmount, onClick, }) => {
9
+ const leftPupilX = useTransform(eyeOffset.x, (v) => LEFT_EYE.cx + v);
10
+ const leftPupilY = useTransform(eyeOffset.y, (v) => LEFT_EYE.cy + v);
11
+ const rightPupilX = useTransform(eyeOffset.x, (v) => RIGHT_EYE.cx + v);
12
+ const rightPupilY = useTransform(eyeOffset.y, (v) => RIGHT_EYE.cy + v);
13
+ // Combine idle head movement with cursor tracking for head turning
14
+ const combinedHeadX = useTransform([idle.headX, eyeOffset.x], ([idleX, cursorX]) => idleX + cursorX * 0.6);
15
+ const combinedHeadY = useTransform([idle.headY, eyeOffset.y], ([idleY, cursorY]) => idleY + cursorY * 0.35);
16
+ const combinedHeadRotate = useTransform([idle.headTilt, eyeOffset.x], ([tilt, cursorX]) => tilt + cursorX * 0.6);
17
+ const stateEyelid = eyelidVariants[state].scaleY;
18
+ const eyelidScale = useTransform(blinkAmount, (blink) => Math.max(stateEyelid, blink));
19
+ const isClickReaction = CLICK_STATES.has(state);
20
+ const transition = isClickReaction ? clickTransition : bodyTransition;
21
+ return (jsxs(motion.g, { animate: state, variants: smallBodyVariants, transition: transition, style: {
22
+ originX: '50%',
23
+ originY: '100%',
24
+ x: idle.sway,
25
+ y: idle.bob,
26
+ scale: idle.breath,
27
+ }, onClick: onClick, children: [jsx("path", { d: "M -45 0 Q -45 -60 0 -60 Q 45 -60 45 0 Z", fill: "#FACC15" }), jsxs(motion.g, { style: {
28
+ originX: '0px',
29
+ originY: '-30px',
30
+ rotate: combinedHeadRotate,
31
+ x: combinedHeadX,
32
+ y: combinedHeadY,
33
+ }, children: [jsxs("g", { children: [jsx("clipPath", { id: "small-eye-l", children: jsx("circle", { cx: LEFT_EYE.cx, cy: LEFT_EYE.cy, r: 10 }) }), jsx("circle", { cx: LEFT_EYE.cx, cy: LEFT_EYE.cy, r: 10, fill: "white" }), jsx(motion.circle, { cx: leftPupilX, cy: leftPupilY, r: 6, fill: "#1E293B", clipPath: "url(#small-eye-l)" }), jsx("circle", { cx: LEFT_EYE.cx + 3, cy: LEFT_EYE.cy - 4, r: 2.2, fill: "white", opacity: 0.95 }), jsx("circle", { cx: LEFT_EYE.cx - 2, cy: LEFT_EYE.cy + 3, r: 1.1, fill: "white", opacity: 0.7 }), jsx(motion.rect, { x: LEFT_EYE.cx - 10, y: LEFT_EYE.cy - 10, width: 20, height: 20, fill: "#FACC15", rx: 2, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsxs("g", { children: [jsx("clipPath", { id: "small-eye-r", children: jsx("circle", { cx: RIGHT_EYE.cx, cy: RIGHT_EYE.cy, r: 10 }) }), jsx("circle", { cx: RIGHT_EYE.cx, cy: RIGHT_EYE.cy, r: 10, fill: "white" }), jsx(motion.circle, { cx: rightPupilX, cy: rightPupilY, r: 6, fill: "#1E293B", clipPath: "url(#small-eye-r)" }), jsx("circle", { cx: RIGHT_EYE.cx + 3, cy: RIGHT_EYE.cy - 4, r: 2.2, fill: "white", opacity: 0.95 }), jsx("circle", { cx: RIGHT_EYE.cx - 2, cy: RIGHT_EYE.cy + 3, r: 1.1, fill: "white", opacity: 0.7 }), jsx(motion.rect, { x: RIGHT_EYE.cx - 10, y: RIGHT_EYE.cy - 10, width: 20, height: 20, fill: "#FACC15", rx: 2, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsx("ellipse", { cx: -22, cy: -18, rx: 6, ry: 4, fill: "#FDE68A", opacity: state === 'excited' ? 0.85 : 0.6 }), jsx("ellipse", { cx: 22, cy: -18, rx: 6, ry: 4, fill: "#FDE68A", opacity: state === 'excited' ? 0.85 : 0.6 }), jsx(motion.path, { d: smallMouthPaths[state], fill: "none", stroke: "#1E293B", strokeWidth: 2, strokeLinecap: "round", animate: { d: smallMouthPaths[state] }, transition: transition, transform: "translate(0, -14)" })] })] }));
34
+ };
35
+
36
+ export { SmallCharacter };
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ import type { CharacterProps } from '../InteractiveCharacters.types';
3
+ export declare const TallCharacter: React.FC<CharacterProps>;
@@ -0,0 +1,37 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useTransform, motion } from 'motion/react';
3
+ import { eyelidVariants, clickTransition, bodyTransition, tallBodyVariants, eyebrowVariants, tallMouthPaths } from '../InteractiveCharacters.animations.js';
4
+
5
+ const LEFT_EYE = { cx: -12, cy: -82 };
6
+ const RIGHT_EYE = { cx: 12, cy: -82 };
7
+ const CLICK_STATES = new Set(['surprised', 'love', 'excited']);
8
+ const TallCharacter = ({ state, eyeOffset, idle, blinkAmount, onClick, }) => {
9
+ const leftPupilX = useTransform(eyeOffset.x, (v) => LEFT_EYE.cx + v);
10
+ const leftPupilY = useTransform(eyeOffset.y, (v) => LEFT_EYE.cy + v);
11
+ const rightPupilX = useTransform(eyeOffset.x, (v) => RIGHT_EYE.cx + v);
12
+ const rightPupilY = useTransform(eyeOffset.y, (v) => RIGHT_EYE.cy + v);
13
+ // Combine idle head movement with cursor tracking for head turning
14
+ const combinedHeadX = useTransform([idle.headX, eyeOffset.x], ([idleX, cursorX]) => idleX + cursorX * 0.4);
15
+ const combinedHeadY = useTransform([idle.headY, eyeOffset.y], ([idleY, cursorY]) => idleY + cursorY * 0.25);
16
+ const combinedHeadRotate = useTransform([idle.headTilt, eyeOffset.x], ([tilt, cursorX]) => tilt + cursorX * 0.4);
17
+ const stateEyelid = eyelidVariants[state].scaleY;
18
+ const eyelidScale = useTransform(blinkAmount, (blink) => Math.max(stateEyelid, blink));
19
+ const eyebrow = eyebrowVariants[state];
20
+ const isClickReaction = CLICK_STATES.has(state);
21
+ const transition = isClickReaction ? clickTransition : bodyTransition;
22
+ return (jsxs(motion.g, { animate: state, variants: tallBodyVariants, transition: transition, style: {
23
+ originX: '50%',
24
+ originY: '100%',
25
+ x: idle.sway,
26
+ y: idle.bob,
27
+ scale: idle.breath,
28
+ }, onClick: onClick, children: [jsx("rect", { x: -30, y: -140, width: 60, height: 140, rx: 22, fill: "#8B5CF6" }), jsxs(motion.g, { style: {
29
+ originX: '0px',
30
+ originY: '-70px',
31
+ rotate: combinedHeadRotate,
32
+ x: combinedHeadX,
33
+ y: combinedHeadY,
34
+ }, children: [jsx(motion.line, { x1: -22, y1: -103, x2: -4, y2: -103, stroke: "#1E293B", strokeWidth: 2.5, strokeLinecap: "round", animate: { rotate: eyebrow.rotate, y: eyebrow.y }, transition: transition, style: { originX: '50%', originY: '50%', transformBox: 'fill-box' } }), jsx(motion.line, { x1: 4, y1: -103, x2: 22, y2: -103, stroke: "#1E293B", strokeWidth: 2.5, strokeLinecap: "round", animate: { rotate: -eyebrow.rotate, y: eyebrow.y }, transition: transition, style: { originX: '50%', originY: '50%', transformBox: 'fill-box' } }), jsxs("g", { children: [jsx("clipPath", { id: "tall-eye-l", children: jsx("ellipse", { cx: LEFT_EYE.cx, cy: LEFT_EYE.cy, rx: 11, ry: 14 }) }), jsx("ellipse", { cx: LEFT_EYE.cx, cy: LEFT_EYE.cy, rx: 11, ry: 14, fill: "white" }), jsx(motion.circle, { cx: leftPupilX, cy: leftPupilY, r: 6.5, fill: "#1E293B", clipPath: "url(#tall-eye-l)" }), jsx("circle", { cx: LEFT_EYE.cx + 3.5, cy: LEFT_EYE.cy - 5, r: 2.5, fill: "white", opacity: 0.95 }), jsx("circle", { cx: LEFT_EYE.cx - 2.5, cy: LEFT_EYE.cy + 3, r: 1.2, fill: "white", opacity: 0.7 }), jsx(motion.rect, { x: LEFT_EYE.cx - 11, y: LEFT_EYE.cy - 14, width: 22, height: 28, fill: "#8B5CF6", rx: 2, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsxs("g", { children: [jsx("clipPath", { id: "tall-eye-r", children: jsx("ellipse", { cx: RIGHT_EYE.cx, cy: RIGHT_EYE.cy, rx: 11, ry: 14 }) }), jsx("ellipse", { cx: RIGHT_EYE.cx, cy: RIGHT_EYE.cy, rx: 11, ry: 14, fill: "white" }), jsx(motion.circle, { cx: rightPupilX, cy: rightPupilY, r: 6.5, fill: "#1E293B", clipPath: "url(#tall-eye-r)" }), jsx("circle", { cx: RIGHT_EYE.cx + 3.5, cy: RIGHT_EYE.cy - 5, r: 2.5, fill: "white", opacity: 0.95 }), jsx("circle", { cx: RIGHT_EYE.cx - 2.5, cy: RIGHT_EYE.cy + 3, r: 1.2, fill: "white", opacity: 0.7 }), jsx(motion.rect, { x: RIGHT_EYE.cx - 11, y: RIGHT_EYE.cy - 14, width: 22, height: 28, fill: "#8B5CF6", rx: 2, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsx("ellipse", { cx: -22, cy: -62, rx: 7, ry: 5, fill: "#F9A8D4", opacity: state === 'love' ? 0.65 : 0.45 }), jsx("ellipse", { cx: 22, cy: -62, rx: 7, ry: 5, fill: "#F9A8D4", opacity: state === 'love' ? 0.65 : 0.45 }), jsx(motion.path, { d: tallMouthPaths[state], fill: "none", stroke: "#1E293B", strokeWidth: 2, strokeLinecap: "round", animate: { d: tallMouthPaths[state] }, transition: transition, transform: "translate(0, -55)" })] })] }));
35
+ };
36
+
37
+ export { TallCharacter };
@@ -0,0 +1,14 @@
1
+ interface UseBlinkOptions {
2
+ /** Whether blinking is enabled */
3
+ enabled?: boolean;
4
+ /** Min time between blinks in ms (default: 2000) */
5
+ minInterval?: number;
6
+ /** Max time between blinks in ms (default: 5000) */
7
+ maxInterval?: number;
8
+ }
9
+ /**
10
+ * Returns a MotionValue representing eyelid close amount (0 = open, 1 = closed).
11
+ * Fires periodic natural blinks when enabled.
12
+ */
13
+ export declare function useBlink({ enabled, minInterval, maxInterval, }?: UseBlinkOptions): import("motion-dom").MotionValue<number>;
14
+ export {};
@@ -0,0 +1,41 @@
1
+ import { useRef, useCallback, useEffect } from 'react';
2
+ import { useMotionValue, useSpring } from 'motion/react';
3
+
4
+ /**
5
+ * Returns a MotionValue representing eyelid close amount (0 = open, 1 = closed).
6
+ * Fires periodic natural blinks when enabled.
7
+ */
8
+ function useBlink({ enabled = true, minInterval = 2000, maxInterval = 5000, } = {}) {
9
+ const raw = useMotionValue(0);
10
+ const blink = useSpring(raw, { stiffness: 600, damping: 30 });
11
+ const timeoutRef = useRef(undefined);
12
+ const doBlink = useCallback(() => {
13
+ // Close eyes
14
+ raw.set(1);
15
+ // Open after 120ms
16
+ setTimeout(() => raw.set(0), 120);
17
+ }, [raw]);
18
+ const scheduleNext = useCallback(() => {
19
+ const delay = minInterval + Math.random() * (maxInterval - minInterval);
20
+ timeoutRef.current = setTimeout(() => {
21
+ if (enabled) {
22
+ doBlink();
23
+ scheduleNext();
24
+ }
25
+ }, delay);
26
+ }, [enabled, minInterval, maxInterval, doBlink]);
27
+ useEffect(() => {
28
+ if (!enabled) {
29
+ raw.set(0);
30
+ return;
31
+ }
32
+ scheduleNext();
33
+ return () => {
34
+ if (timeoutRef.current)
35
+ clearTimeout(timeoutRef.current);
36
+ };
37
+ }, [enabled, scheduleNext, raw]);
38
+ return blink;
39
+ }
40
+
41
+ export { useBlink };
@@ -0,0 +1,25 @@
1
+ interface UseIdleAnimationOptions {
2
+ /** Whether idle animation is active */
3
+ enabled?: boolean;
4
+ /** Amplitude of the sway in SVG units (default: 2) */
5
+ swayAmount?: number;
6
+ /** Speed multiplier for the sway cycle (default: 1) */
7
+ swaySpeed?: number;
8
+ /** Amplitude of the breathing scale oscillation (default: 0.01) */
9
+ breathAmount?: number;
10
+ /** Amplitude of head tilt in degrees (default: 3) */
11
+ headTiltAmount?: number;
12
+ /** Amplitude of random head nudge in SVG units (default: 1.5) */
13
+ headNudgeAmount?: number;
14
+ /** Phase offset in seconds — staggers characters so they don't move in sync */
15
+ phaseOffset?: number;
16
+ }
17
+ export declare function useIdleAnimation({ enabled, swayAmount, swaySpeed, breathAmount, headTiltAmount, headNudgeAmount, phaseOffset, }?: UseIdleAnimationOptions): {
18
+ sway: import("motion-dom").MotionValue<number>;
19
+ bob: import("motion-dom").MotionValue<number>;
20
+ breath: import("motion-dom").MotionValue<number>;
21
+ headTilt: import("motion-dom").MotionValue<number>;
22
+ headX: import("motion-dom").MotionValue<number>;
23
+ headY: import("motion-dom").MotionValue<number>;
24
+ };
25
+ export {};
@@ -0,0 +1,84 @@
1
+ import { useRef, useCallback, useEffect } from 'react';
2
+ import { useReducedMotion, useMotionValue, useSpring } from 'motion/react';
3
+
4
+ function useIdleAnimation({ enabled = true, swayAmount = 2, swaySpeed = 1, breathAmount = 0.01, headTiltAmount = 3, headNudgeAmount = 1.5, phaseOffset = 0, } = {}) {
5
+ const shouldReduceMotion = useReducedMotion();
6
+ const rafRef = useRef(0);
7
+ const startTimeRef = useRef(null);
8
+ const isFirstFrame = useRef(true);
9
+ const rawSway = useMotionValue(0);
10
+ const rawBob = useMotionValue(0);
11
+ const rawBreath = useMotionValue(1);
12
+ const rawHeadTilt = useMotionValue(0);
13
+ const rawHeadX = useMotionValue(0);
14
+ const rawHeadY = useMotionValue(0);
15
+ const sway = useSpring(rawSway, { stiffness: 40, damping: 15 });
16
+ const bob = useSpring(rawBob, { stiffness: 40, damping: 15 });
17
+ const breath = useSpring(rawBreath, { stiffness: 40, damping: 15 });
18
+ const headTilt = useSpring(rawHeadTilt, { stiffness: 30, damping: 12 });
19
+ const headX = useSpring(rawHeadX, { stiffness: 35, damping: 14 });
20
+ const headY = useSpring(rawHeadY, { stiffness: 35, damping: 14 });
21
+ const animate = useCallback((time) => {
22
+ if (startTimeRef.current === null)
23
+ startTimeRef.current = time;
24
+ const t = (time - startTimeRef.current) / 1000 + phaseOffset;
25
+ // Body sway: slow sinusoidal
26
+ const swayVal = Math.sin(t * 0.8 * swaySpeed) * swayAmount;
27
+ rawSway.set(swayVal);
28
+ // Body bob: slightly different frequency
29
+ const bobVal = Math.sin(t * 1.2 * swaySpeed) * (swayAmount * 0.5);
30
+ rawBob.set(bobVal);
31
+ // Breathing: subtle scale oscillation
32
+ const breathVal = 1 + Math.sin(t * 1.5 * swaySpeed) * breathAmount;
33
+ rawBreath.set(breathVal);
34
+ // Head tilt: layered sinusoids for organic, non-repeating feel
35
+ const tilt1 = Math.sin(t * 0.5 * swaySpeed) * 0.6;
36
+ const tilt2 = Math.sin(t * 1.3 * swaySpeed) * 0.3;
37
+ const tilt3 = Math.sin(t * 0.3 * swaySpeed) * 0.1;
38
+ const tiltVal = (tilt1 + tilt2 + tilt3) * headTiltAmount;
39
+ rawHeadTilt.set(tiltVal);
40
+ // Head nudge X/Y: different frequencies from body for independence
41
+ const headXVal = (Math.sin(t * 0.7 * swaySpeed) * 0.6 +
42
+ Math.sin(t * 1.9 * swaySpeed) * 0.4) *
43
+ headNudgeAmount;
44
+ rawHeadX.set(headXVal);
45
+ const headYVal = (Math.sin(t * 0.9 * swaySpeed) * 0.5 +
46
+ Math.sin(t * 2.1 * swaySpeed) * 0.3) *
47
+ headNudgeAmount *
48
+ 0.6;
49
+ rawHeadY.set(headYVal);
50
+ // On first frame, jump springs directly to skip settling animation
51
+ if (isFirstFrame.current) {
52
+ isFirstFrame.current = false;
53
+ sway.jump(swayVal);
54
+ bob.jump(bobVal);
55
+ breath.jump(breathVal);
56
+ headTilt.jump(tiltVal);
57
+ headX.jump(headXVal);
58
+ headY.jump(headYVal);
59
+ }
60
+ rafRef.current = requestAnimationFrame(animate);
61
+ }, [
62
+ rawSway, rawBob, rawBreath, rawHeadTilt, rawHeadX, rawHeadY,
63
+ sway, bob, breath, headTilt, headX, headY,
64
+ swayAmount, swaySpeed, breathAmount, headTiltAmount, headNudgeAmount, phaseOffset,
65
+ ]);
66
+ useEffect(() => {
67
+ if (!enabled || shouldReduceMotion) {
68
+ rawSway.set(0);
69
+ rawBob.set(0);
70
+ rawBreath.set(1);
71
+ rawHeadTilt.set(0);
72
+ rawHeadX.set(0);
73
+ rawHeadY.set(0);
74
+ return;
75
+ }
76
+ startTimeRef.current = null;
77
+ isFirstFrame.current = true;
78
+ rafRef.current = requestAnimationFrame(animate);
79
+ return () => cancelAnimationFrame(rafRef.current);
80
+ }, [enabled, shouldReduceMotion, animate, rawSway, rawBob, rawBreath, rawHeadTilt, rawHeadX, rawHeadY]);
81
+ return { sway, bob, breath, headTilt, headX, headY };
82
+ }
83
+
84
+ export { useIdleAnimation };
@@ -0,0 +1,18 @@
1
+ import type { RefObject } from 'react';
2
+ interface UseMouseTrackingOptions {
3
+ /** Reference to the SVG container element */
4
+ containerRef: RefObject<SVGSVGElement | null>;
5
+ /** Whether tracking is enabled */
6
+ enabled?: boolean;
7
+ /** Spring stiffness (default: 150) */
8
+ stiffness?: number;
9
+ /** Spring damping (default: 25) */
10
+ damping?: number;
11
+ /** Maximum eye offset in pixels (default: 6) */
12
+ maxOffset?: number;
13
+ }
14
+ export declare function useMouseTracking({ containerRef, enabled, stiffness, damping, maxOffset, }: UseMouseTrackingOptions): {
15
+ x: import("motion-dom").MotionValue<number>;
16
+ y: import("motion-dom").MotionValue<number>;
17
+ };
18
+ export {};
@@ -0,0 +1,35 @@
1
+ import { useCallback, useEffect } from 'react';
2
+ import { useReducedMotion, useMotionValue, useSpring } from 'motion/react';
3
+
4
+ function useMouseTracking({ containerRef, enabled = true, stiffness = 150, damping = 25, maxOffset = 6, }) {
5
+ const shouldReduceMotion = useReducedMotion();
6
+ const rawX = useMotionValue(0);
7
+ const rawY = useMotionValue(0);
8
+ const x = useSpring(rawX, { stiffness, damping });
9
+ const y = useSpring(rawY, { stiffness, damping });
10
+ const handleMouseMove = useCallback((e) => {
11
+ const el = containerRef.current;
12
+ if (!el)
13
+ return;
14
+ const rect = el.getBoundingClientRect();
15
+ const centerX = rect.left + rect.width / 2;
16
+ const centerY = rect.top + rect.height / 2;
17
+ // Normalize to -1..1 range based on distance from center
18
+ const nx = Math.max(-1, Math.min(1, (e.clientX - centerX) / (rect.width / 2)));
19
+ const ny = Math.max(-1, Math.min(1, (e.clientY - centerY) / (rect.height / 2)));
20
+ rawX.set(nx * maxOffset);
21
+ rawY.set(ny * maxOffset);
22
+ }, [containerRef, rawX, rawY, maxOffset]);
23
+ useEffect(() => {
24
+ if (!enabled || shouldReduceMotion) {
25
+ rawX.set(0);
26
+ rawY.set(0);
27
+ return;
28
+ }
29
+ window.addEventListener('mousemove', handleMouseMove);
30
+ return () => window.removeEventListener('mousemove', handleMouseMove);
31
+ }, [enabled, shouldReduceMotion, handleMouseMove, rawX, rawY]);
32
+ return { x, y };
33
+ }
34
+
35
+ export { useMouseTracking };
@@ -0,0 +1,2 @@
1
+ export { InteractiveCharacters } from './InteractiveCharacters';
2
+ export type { InteractiveCharactersProps, CharacterState, CharacterSize, } from './InteractiveCharacters.types';
@@ -14,4 +14,4 @@ import type { MorphingTextProps } from './MorphingText.types';
14
14
  * />
15
15
  * ```
16
16
  */
17
- export declare const MorphingText: React.FC<MorphingTextProps>;
17
+ export declare const MorphingText: React.ForwardRefExoticComponent<MorphingTextProps & React.RefAttributes<HTMLDivElement>>;