@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.
- package/README.md +52 -0
- package/dist/components/ai/CodeBlock/CodeBlock.theme.js +1 -1
- package/dist/components/ai/PromptInput/PromptInput.js +3 -1
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.js +4 -4
- package/dist/components/ai/StreamingResponse/AnimatedText.js +3 -3
- package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +22 -22
- package/dist/components/ai/StreamingResponse/StreamingCursor.js +1 -1
- package/dist/components/ai/StreamingResponse/StreamingResponse.animations.js +10 -10
- package/dist/components/ai/StreamingResponse/StreamingResponse.js +9 -3
- package/dist/components/ai/StreamingResponse/WordAnimationContext.d.ts +1 -1
- package/dist/components/ai/StreamingResponse/WordAnimationContext.js +11 -11
- package/dist/components/ai/index.d.ts +0 -7
- package/dist/components/ai/index.js +8 -6
- package/dist/components/charts/DonutChart/DonutChart.js +1 -1
- package/dist/components/charts/DonutChart/MultiSegmentDonutChart.js +1 -1
- package/dist/components/charts/shared/ChartLegend/ChartLegend.types.d.ts +13 -1
- package/dist/components/charts/shared/ChartLegend/ChartLegendContent.js +1 -1
- package/dist/components/charts/shared/ChartTooltip/ChartTooltip.types.d.ts +13 -2
- package/dist/components/core/Accordion/AccordionItem.js +1 -1
- package/dist/components/core/Accordion/AccordionTrigger.js +1 -1
- package/dist/components/core/Alert/Alert.animations.d.ts +11 -0
- package/dist/components/core/Alert/Alert.animations.js +44 -0
- package/dist/components/core/Alert/Alert.js +16 -3
- package/dist/components/core/Avatar/Avatar.js +12 -7
- package/dist/components/core/Avatar/Avatar.theme.d.ts +4 -6
- package/dist/components/core/Avatar/Avatar.theme.js +22 -12
- package/dist/components/core/Avatar/Avatar.types.d.ts +9 -0
- package/dist/components/core/AvatarGroup/AvatarGroup.animations.js +2 -2
- package/dist/components/core/AvatarGroup/AvatarGroup.js +1 -2
- package/dist/components/core/AvatarGroup/AvatarGroup.theme.js +1 -1
- package/dist/components/core/Badge/Badge.animations.js +1 -1
- package/dist/components/core/Badge/Badge.js +23 -47
- package/dist/components/core/Badge/Badge.theme.js +48 -62
- package/dist/components/core/Badge/Badge.types.d.ts +5 -29
- package/dist/components/core/Button/Button.js +2 -2
- package/dist/components/core/Button/Button.theme.js +2 -2
- package/dist/components/core/ButtonGroup/ButtonGroup.theme.js +6 -6
- package/dist/components/core/ButtonGroup/ButtonGroupText.js +1 -3
- package/dist/components/core/Calendar/Calendar.js +29 -3
- package/dist/components/core/Calendar/Calendar.theme.js +11 -7
- package/dist/components/core/Calendar/Calendar.types.d.ts +3 -0
- package/dist/components/core/Carousel/CarouselBody.js +0 -1
- package/dist/components/core/CommandPalette/CommandItem.js +1 -1
- package/dist/components/core/CommandPalette/CommandPalette.js +6 -4
- package/dist/components/core/CommandPalette/CommandPalette.theme.js +1 -0
- package/dist/components/core/CommandPalette/CommandPalette.types.d.ts +6 -0
- package/dist/components/core/ContextMenu/ContextMenu.js +5 -4
- package/dist/components/core/ContextMenu/ContextMenuTrigger.js +5 -6
- package/dist/components/core/Drawer/Drawer.d.ts +1 -1
- package/dist/components/core/Drawer/Drawer.js +25 -2
- package/dist/components/core/Drawer/Drawer.theme.js +2 -2
- package/dist/components/core/Drawer/Drawer.types.d.ts +6 -0
- package/dist/components/core/Dropdown/Dropdown.d.ts +1 -1
- package/dist/components/core/Dropdown/Dropdown.js +5 -1
- package/dist/components/core/Dropdown/Dropdown.types.d.ts +1 -0
- package/dist/components/core/Dropdown/DropdownItem.js +1 -1
- package/dist/components/core/Dropdown/DropdownMenu.js +4 -4
- package/dist/components/core/Dropdown/DropdownSection.js +1 -1
- package/dist/components/core/Dropdown/DropdownSeparator.js +1 -1
- package/dist/components/core/Dropdown/DropdownTrigger.d.ts +6 -1
- package/dist/components/core/Dropdown/DropdownTrigger.js +10 -8
- package/dist/components/core/Kbd/Kbd.theme.js +3 -3
- package/dist/components/core/Link/Link.js +3 -1
- package/dist/components/core/Message/Message.d.ts +9 -0
- package/dist/components/core/Message/Message.js +44 -3
- package/dist/components/core/Message/Message.theme.js +22 -0
- package/dist/components/core/Message/Message.types.d.ts +115 -0
- package/dist/components/core/Message/MessageAudio.d.ts +8 -0
- package/dist/components/core/Message/MessageAudio.js +11 -0
- package/dist/components/core/Message/MessageContext.d.ts +3 -1
- package/dist/components/core/Message/MessageDateDivider.d.ts +6 -0
- package/dist/components/core/Message/MessageDateDivider.js +10 -0
- package/dist/components/core/Message/MessageFile.d.ts +9 -0
- package/dist/components/core/Message/MessageFile.js +15 -0
- package/dist/components/core/Message/MessageImage.d.ts +11 -0
- package/dist/components/core/Message/MessageImage.js +13 -0
- package/dist/components/core/Message/MessageLink.d.ts +10 -0
- package/dist/components/core/Message/MessageLink.js +15 -0
- package/dist/components/core/Message/MessageReactions.d.ts +8 -0
- package/dist/components/core/Message/MessageReactions.js +12 -0
- package/dist/components/core/Message/MessageReply.d.ts +8 -0
- package/dist/components/core/Message/MessageReply.js +10 -0
- package/dist/components/core/Message/MessageSenderName.d.ts +6 -0
- package/dist/components/core/Message/MessageSenderName.js +10 -0
- package/dist/components/core/Message/MessageStatusIndicator.d.ts +9 -0
- package/dist/components/core/Message/MessageStatusIndicator.js +45 -0
- package/dist/components/core/Message/index.d.ts +19 -1
- package/dist/components/core/Modal/Modal.animations.d.ts +1 -0
- package/dist/components/core/Modal/Modal.animations.js +14 -11
- package/dist/components/core/Pagination/Pagination.theme.js +1 -1
- package/dist/components/core/Popover/Popover.d.ts +1 -1
- package/dist/components/core/Popover/PopoverBody.js +2 -2
- package/dist/components/core/Popover/PopoverTrigger.d.ts +1 -1
- package/dist/components/core/Popover/PopoverTrigger.js +11 -11
- package/dist/components/core/Progress/Progress.d.ts +9 -8
- package/dist/components/core/Progress/Progress.js +16 -16
- package/dist/components/core/Progress/Progress.theme.js +4 -15
- package/dist/components/core/Progress/Progress.types.d.ts +2 -20
- package/dist/components/core/Progress/index.d.ts +1 -1
- package/dist/components/core/ScrollArea/ScrollArea.js +49 -7
- package/dist/components/core/ScrollArea/ScrollArea.types.d.ts +12 -3
- package/dist/components/core/ScrollArea/index.d.ts +1 -1
- package/dist/components/core/Segmented/SegmentedItem.js +1 -1
- package/dist/components/core/Sidebar/Sidebar.js +2 -1
- package/dist/components/core/Sidebar/SidebarMobileTrigger.js +2 -1
- package/dist/components/core/Sidebar/SidebarToggle.js +2 -1
- package/dist/components/core/Sidebar/SidebarUserProfile.js +1 -1
- package/dist/components/core/SocialIcon/SocialIcon.js +1 -1
- package/dist/components/core/Sortable/Sortable.js +2 -2
- package/dist/components/core/Sortable/Sortable.theme.js +1 -1
- package/dist/components/core/Sortable/Sortable.types.d.ts +7 -5
- package/dist/components/core/Tabs/TabsBody.js +1 -1
- package/dist/components/core/Tabs/TabsTrigger.js +1 -1
- package/dist/components/core/Tag/Tag.js +2 -2
- package/dist/components/core/Toast/Toast.d.ts +2 -2
- package/dist/components/core/Toast/Toast.js +30 -21
- package/dist/components/core/Toast/Toast.theme.js +4 -9
- package/dist/components/core/Toast/Toast.types.d.ts +4 -21
- package/dist/components/core/Toast/index.d.ts +1 -1
- package/dist/components/core/Tooltip/Tooltip.js +1 -1
- package/dist/components/core/Tooltip/Tooltip.theme.js +1 -1
- package/dist/components/core/index.js +9 -0
- package/dist/components/data-display/GanttChart/GanttChart.types.d.ts +2 -2
- package/dist/components/data-display/Table/Table.types.d.ts +8 -8
- package/dist/components/effects/CustomCursor/CustomCursor.js +2 -2
- package/dist/components/effects/GlassSurface/GlassSurface.d.ts +1 -1
- package/dist/components/effects/GlassSurface/GlassSurface.js +17 -9
- package/dist/components/effects/GlassSurface/GlassSurface.types.d.ts +6 -0
- package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.animations.d.ts +39 -0
- package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.animations.js +98 -0
- package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.d.ts +3 -0
- package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.js +104 -0
- package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.types.d.ts +30 -0
- package/dist/components/effects/InteractiveCharacters/characters/BlobCharacter.d.ts +3 -0
- package/dist/components/effects/InteractiveCharacters/characters/BlobCharacter.js +36 -0
- package/dist/components/effects/InteractiveCharacters/characters/SmallCharacter.d.ts +3 -0
- package/dist/components/effects/InteractiveCharacters/characters/SmallCharacter.js +36 -0
- package/dist/components/effects/InteractiveCharacters/characters/TallCharacter.d.ts +3 -0
- package/dist/components/effects/InteractiveCharacters/characters/TallCharacter.js +37 -0
- package/dist/components/effects/InteractiveCharacters/hooks/useBlink.d.ts +14 -0
- package/dist/components/effects/InteractiveCharacters/hooks/useBlink.js +41 -0
- package/dist/components/effects/InteractiveCharacters/hooks/useIdleAnimation.d.ts +25 -0
- package/dist/components/effects/InteractiveCharacters/hooks/useIdleAnimation.js +84 -0
- package/dist/components/effects/InteractiveCharacters/hooks/useMouseTracking.d.ts +18 -0
- package/dist/components/effects/InteractiveCharacters/hooks/useMouseTracking.js +35 -0
- package/dist/components/effects/InteractiveCharacters/index.d.ts +2 -0
- package/dist/components/effects/MorphingText/MorphingText.d.ts +1 -1
- package/dist/components/effects/MorphingText/MorphingText.js +6 -5
- package/dist/components/effects/Neumorphic/InsetCircleButton.d.ts +10 -0
- package/dist/components/effects/Neumorphic/InsetCircleButton.js +10 -0
- package/dist/components/effects/Neumorphic/InsetPill.d.ts +8 -0
- package/dist/components/effects/Neumorphic/InsetPill.js +7 -0
- package/dist/components/effects/Neumorphic/index.d.ts +4 -0
- package/dist/components/effects/NoiseOverlay/NoiseOverlay.d.ts +1 -1
- package/dist/components/effects/NoiseOverlay/NoiseOverlay.js +4 -4
- package/dist/components/effects/Overlay/Overlay.js +6 -4
- package/dist/components/effects/PageTransition/PageTransition.animations.d.ts +8 -0
- package/dist/components/effects/PageTransition/PageTransition.animations.js +53 -0
- package/dist/components/effects/PageTransition/PageTransition.d.ts +3 -0
- package/dist/components/effects/PageTransition/PageTransition.js +71 -0
- package/dist/components/effects/PageTransition/PageTransition.types.d.ts +15 -0
- package/dist/components/effects/PageTransition/index.d.ts +2 -0
- package/dist/components/effects/index.d.ts +6 -0
- package/dist/components/effects/index.js +4 -0
- package/dist/components/forms/Checkbox/Checkbox.animations.d.ts +14 -0
- package/dist/components/forms/Checkbox/Checkbox.animations.js +53 -0
- package/dist/components/forms/Checkbox/Checkbox.js +4 -2
- package/dist/components/forms/Checkbox/Checkbox.theme.js +3 -3
- package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +1 -1
- package/dist/components/forms/ColorPicker/ColorPickerBody.js +3 -3
- package/dist/components/forms/Combobox/Combobox.js +5 -3
- package/dist/components/forms/DatePicker/DatePickerBody.js +1 -1
- package/dist/components/forms/DateRangePicker/DateRangePicker.animations.d.ts +1 -0
- package/dist/components/forms/DateRangePicker/DateRangePicker.d.ts +37 -0
- package/dist/components/forms/DateRangePicker/DateRangePicker.js +110 -0
- package/dist/components/forms/DateRangePicker/DateRangePicker.theme.d.ts +5 -0
- package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +52 -0
- package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +154 -0
- package/dist/components/forms/DateRangePicker/DateRangePickerBody.d.ts +6 -0
- package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +208 -0
- package/dist/components/forms/DateRangePicker/DateRangePickerPresets.d.ts +6 -0
- package/dist/components/forms/DateRangePicker/DateRangePickerPresets.js +41 -0
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.d.ts +6 -0
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +32 -0
- package/dist/components/forms/DateRangePicker/dateRangePresets.d.ts +5 -0
- package/dist/components/forms/DateRangePicker/dateRangePresets.js +59 -0
- package/dist/components/forms/DateRangePicker/index.d.ts +4 -0
- package/dist/components/forms/FileUpload/FileUploadProgress.animations.d.ts +36 -0
- package/dist/components/forms/FileUpload/FileUploadProgress.animations.js +22 -0
- package/dist/components/forms/FileUpload/FileUploadProgress.d.ts +3 -0
- package/dist/components/forms/FileUpload/FileUploadProgress.js +111 -0
- package/dist/components/forms/FileUpload/FileUploadProgress.theme.d.ts +19 -0
- package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +27 -0
- package/dist/components/forms/FileUpload/FileUploadProgress.types.d.ts +33 -0
- package/dist/components/forms/FileUpload/index.d.ts +3 -0
- package/dist/components/forms/Input/Input.js +8 -13
- package/dist/components/forms/Input/Input.types.d.ts +6 -0
- package/dist/components/forms/InputAddress/InputAddress.theme.js +1 -1
- package/dist/components/forms/InputCounter/InputCounter.js +1 -1
- package/dist/components/forms/InputCreditCard/InputCreditCard.theme.js +1 -1
- package/dist/components/forms/InputOTP/InputOTP.theme.js +1 -1
- package/dist/components/forms/Radio/Radio.animations.d.ts +10 -0
- package/dist/components/forms/Radio/Radio.animations.js +33 -0
- package/dist/components/forms/Radio/Radio.js +11 -2
- package/dist/components/forms/Radio/Radio.theme.js +3 -3
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +13 -4
- package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +2 -2
- package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +11 -0
- package/dist/components/forms/Select/Select.d.ts +1 -1
- package/dist/components/forms/Select/Select.js +7 -7
- package/dist/components/forms/Select/Select.types.d.ts +3 -3
- package/dist/components/forms/SelectableCard/SelectableCard.js +1 -1
- package/dist/components/forms/Slider/Slider.js +10 -3
- package/dist/components/forms/Slider/Slider.theme.js +3 -3
- package/dist/components/forms/Switch/Switch.js +10 -2
- package/dist/components/forms/Switch/Switch.theme.js +3 -3
- package/dist/components/forms/forms.theme.js +1 -1
- package/dist/components/forms/index.d.ts +6 -3
- package/dist/components/forms/index.js +8 -2
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useMediaQuery.d.ts +5 -0
- package/dist/hooks/useMediaQuery.js +21 -0
- package/dist/index.js +21 -2
- package/dist/styles.css +1 -1
- package/dist/utils/dateUtils.js +56 -5
- package/dist/utils/formatUtils.d.ts +10 -0
- package/dist/utils/formatUtils.js +24 -0
- package/dist/utils/index.d.ts +1 -1
- package/package.json +12 -4
- package/src/global.scss +319 -39
- package/src/styles/theme.css +105 -16
- package/src/theme-plugin.css +13 -0
- package/dist/components/ai/ApprovalCard/ApprovalCard.d.ts +0 -3
- package/dist/components/ai/ApprovalCard/ApprovalCard.js +0 -16
- package/dist/components/ai/ApprovalCard/ApprovalCard.theme.d.ts +0 -13
- package/dist/components/ai/ApprovalCard/ApprovalCard.theme.js +0 -15
- package/dist/components/ai/ApprovalCard/ApprovalCard.types.d.ts +0 -43
- package/dist/components/ai/ApprovalCard/index.d.ts +0 -2
- package/dist/components/ai/MessageHistory/MessageHistory.d.ts +0 -18
- package/dist/components/ai/MessageHistory/MessageHistory.js +0 -98
- package/dist/components/ai/MessageHistory/MessageHistory.theme.d.ts +0 -2
- package/dist/components/ai/MessageHistory/MessageHistory.theme.js +0 -8
- package/dist/components/ai/MessageHistory/MessageHistory.types.d.ts +0 -94
- package/dist/components/ai/MessageHistory/index.d.ts +0 -3
- package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.animations.d.ts +0 -10
- package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.animations.js +0 -47
- package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.d.ts +0 -20
- package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.js +0 -55
- package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.theme.d.ts +0 -2
- package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.theme.js +0 -20
- package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.types.d.ts +0 -67
- package/dist/components/ai/ThinkingIndicator/index.d.ts +0 -3
- package/dist/components/forms/TransferList/TransferList.animations.d.ts +0 -10
- package/dist/components/forms/TransferList/TransferList.animations.js +0 -32
- package/dist/components/forms/TransferList/TransferList.d.ts +0 -28
- package/dist/components/forms/TransferList/TransferList.js +0 -199
- package/dist/components/forms/TransferList/TransferList.theme.d.ts +0 -6
- package/dist/components/forms/TransferList/TransferList.theme.js +0 -60
- package/dist/components/forms/TransferList/TransferList.types.d.ts +0 -101
- package/dist/components/forms/TransferList/index.d.ts +0 -3
- package/dist/icons/Icon.d.ts +0 -24
- package/dist/icons/Icon.js +0 -30
- package/dist/icons/Index.d.ts +0 -16
- package/dist/icons/core/ChevronUpDown.d.ts +0 -1
- package/dist/icons/core/ChevronUpDown.js +0 -7
- package/dist/icons/core/DollarIcon.d.ts +0 -1
- package/dist/icons/core/DollarIcon.js +0 -5
- package/dist/icons/core/LockIcon.d.ts +0 -1
- package/dist/icons/core/LockIcon.js +0 -5
- package/dist/icons/core/MinusIcon.d.ts +0 -1
- package/dist/icons/core/MinusIcon.js +0 -7
- package/dist/icons/core/PlusIcon.d.ts +0 -1
- package/dist/icons/core/PlusIcon.js +0 -7
- package/dist/icons/core/SearchIcon.d.ts +0 -1
- package/dist/icons/core/SearchIcon.js +0 -7
- package/dist/icons/core/TickIcon.d.ts +0 -1
- package/dist/icons/core/TickIcon.js +0 -5
- package/dist/icons/core/User.d.ts +0 -1
- 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,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,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,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,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 };
|
|
@@ -14,4 +14,4 @@ import type { MorphingTextProps } from './MorphingText.types';
|
|
|
14
14
|
* />
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
|
-
export declare const MorphingText: React.
|
|
17
|
+
export declare const MorphingText: React.ForwardRefExoticComponent<MorphingTextProps & React.RefAttributes<HTMLDivElement>>;
|