@flikk/ui 1.0.0-beta.24 → 1.0.0-beta.26

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 (135) hide show
  1. package/dist/components/ai/CodeBlock/CodeBlock.theme.js +2 -2
  2. package/dist/components/ai/PromptInput/PromptInput.js +1 -0
  3. package/dist/components/ai/PromptInput/VoiceRecorder.js +1 -0
  4. package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +1 -1
  5. package/dist/components/ai/TokenCounter/TokenCounter.theme.js +1 -1
  6. package/dist/components/charts/BarChart/BarChart.js +2 -2
  7. package/dist/components/charts/StackedBarChart/StackedBarChart.js +1 -1
  8. package/dist/components/charts/shared/BarRenderer/BarRenderer.js +2 -2
  9. package/dist/components/core/Accordion/Accordion.theme.js +2 -2
  10. package/dist/components/core/Breadcrumbs/Breadcrumbs.js +1 -1
  11. package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +2 -2
  12. package/dist/components/core/Button/Button.js +1 -1
  13. package/dist/components/core/Button/Button.theme.js +4 -4
  14. package/dist/components/core/Calendar/Calendar.js +1 -1
  15. package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +4 -2
  16. package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +2 -2
  17. package/dist/components/core/ContextMenu/ContextMenu.theme.js +1 -1
  18. package/dist/components/core/Drawer/Drawer.theme.js +2 -2
  19. package/dist/components/core/Dropdown/Dropdown.theme.js +2 -2
  20. package/dist/components/core/Empty/Empty.js +1 -1
  21. package/dist/components/core/Empty/Empty.theme.js +1 -1
  22. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.d.ts +14 -0
  23. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.js +28 -0
  24. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.d.ts +6 -0
  25. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.js +8 -0
  26. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.types.d.ts +32 -0
  27. package/dist/components/core/ImagePlaceholder/index.d.ts +2 -0
  28. package/dist/components/core/Link/Link.js +1 -1
  29. package/dist/components/core/Message/Message.theme.js +1 -1
  30. package/dist/components/core/Modal/Modal.theme.js +1 -1
  31. package/dist/components/core/NavItem/NavItem.d.ts +1 -1
  32. package/dist/components/core/NavItem/NavItem.js +171 -68
  33. package/dist/components/core/NavItem/NavItem.theme.js +20 -12
  34. package/dist/components/core/NavItem/NavItem.types.d.ts +17 -39
  35. package/dist/components/core/Pagination/Pagination.theme.js +3 -3
  36. package/dist/components/core/Popover/Popover.theme.js +1 -1
  37. package/dist/components/core/Progress/Progress.theme.js +1 -1
  38. package/dist/components/core/ScrollArea/ScrollArea.js +2 -2
  39. package/dist/components/core/ScrollArea/ScrollArea.theme.js +1 -1
  40. package/dist/components/core/Segmented/Segmented.js +8 -6
  41. package/dist/components/core/Segmented/Segmented.theme.js +3 -3
  42. package/dist/components/core/Sidebar/Sidebar.d.ts +1 -1
  43. package/dist/components/core/Sidebar/Sidebar.js +42 -13
  44. package/dist/components/core/Sidebar/Sidebar.theme.js +5 -7
  45. package/dist/components/core/Sidebar/Sidebar.types.d.ts +33 -4
  46. package/dist/components/core/Sidebar/SidebarContent.js +6 -6
  47. package/dist/components/core/Sidebar/SidebarContext.d.ts +17 -16
  48. package/dist/components/core/Sidebar/SidebarContext.js +42 -19
  49. package/dist/components/core/Sidebar/SidebarFooter.js +4 -6
  50. package/dist/components/core/Sidebar/SidebarHeader.js +4 -9
  51. package/dist/components/core/Sidebar/SidebarLogo.js +3 -7
  52. package/dist/components/core/Sidebar/SidebarNav.js +4 -3
  53. package/dist/components/core/Sidebar/SidebarNavGroup.js +4 -7
  54. package/dist/components/core/Sidebar/SidebarToggle.js +6 -1
  55. package/dist/components/core/Sidebar/SidebarUserProfile.js +33 -17
  56. package/dist/components/core/Sidebar/index.d.ts +2 -3
  57. package/dist/components/core/Skeleton/Skeleton.theme.js +1 -1
  58. package/dist/components/core/Spinner/Spinner.theme.js +1 -1
  59. package/dist/components/core/Tabs/Tabs.js +6 -1
  60. package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
  61. package/dist/components/core/Tabs/TabsBody.js +5 -3
  62. package/dist/components/core/Tag/Tag.theme.js +1 -1
  63. package/dist/components/core/index.d.ts +1 -0
  64. package/dist/components/core/index.js +2 -2
  65. package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +1 -1
  66. package/dist/components/data-display/KPI/KPI.js +13 -3
  67. package/dist/components/data-display/KPI/KPI.theme.js +3 -3
  68. package/dist/components/data-display/Metric/Metric.theme.js +3 -3
  69. package/dist/components/data-display/Table/Table.js +4 -3
  70. package/dist/components/data-display/Table/Table.theme.js +11 -1
  71. package/dist/components/data-display/Table/Table.types.d.ts +15 -0
  72. package/dist/components/data-display/Table/TableColumnManager.js +1 -1
  73. package/dist/components/data-display/Table/index.d.ts +2 -2
  74. package/dist/components/data-display/Timeline/Timeline.animations.d.ts +0 -7
  75. package/dist/components/data-display/Timeline/Timeline.animations.js +1 -27
  76. package/dist/components/data-display/Timeline/Timeline.js +3 -4
  77. package/dist/components/data-display/Timeline/Timeline.types.d.ts +0 -13
  78. package/dist/components/data-display/Timeline/TimelineContent.js +3 -7
  79. package/dist/components/data-display/Timeline/TimelineItem.js +4 -17
  80. package/dist/components/data-display/Timeline/index.d.ts +2 -2
  81. package/dist/components/effects/Aurora/Aurora.js +79 -40
  82. package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.js +3 -3
  83. package/dist/components/effects/InteractiveCharacters/characters/BlobCharacter.js +13 -4
  84. package/dist/components/effects/InteractiveCharacters/characters/SmallCharacter.js +13 -4
  85. package/dist/components/effects/InteractiveCharacters/characters/TallCharacter.js +14 -4
  86. package/dist/components/effects/MorphingText/MorphingText.js +40 -7
  87. package/dist/components/effects/StripePattern/StripePattern.d.ts +18 -0
  88. package/dist/components/effects/StripePattern/StripePattern.js +54 -0
  89. package/dist/components/effects/StripePattern/StripePattern.types.d.ts +53 -0
  90. package/dist/components/effects/StripePattern/index.d.ts +2 -0
  91. package/dist/components/effects/index.d.ts +9 -0
  92. package/dist/components/effects/index.js +4 -0
  93. package/dist/components/forms/Checkbox/Checkbox.theme.js +1 -1
  94. package/dist/components/forms/CronInput/CronInput.theme.js +16 -16
  95. package/dist/components/forms/DatePicker/DatePickerTrigger.js +1 -1
  96. package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +1 -1
  97. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +1 -1
  98. package/dist/components/forms/FileUpload/FileUpload.js +1 -1
  99. package/dist/components/forms/FileUpload/FileUploadProgress.js +27 -20
  100. package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +2 -2
  101. package/dist/components/forms/Input/Input.theme.js +9 -11
  102. package/dist/components/forms/Mention/Mention.theme.js +3 -3
  103. package/dist/components/forms/Radio/Radio.theme.js +1 -1
  104. package/dist/components/forms/RichTextEditor/RichTextEditor.d.ts +18 -0
  105. package/dist/components/forms/RichTextEditor/RichTextEditor.js +1231 -236
  106. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.d.ts +53 -1
  107. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +99 -25
  108. package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +42 -3
  109. package/dist/components/forms/RichTextEditor/index.d.ts +2 -2
  110. package/dist/components/forms/Select/Select.js +2 -2
  111. package/dist/components/forms/Slider/Slider.theme.js +1 -1
  112. package/dist/components/forms/Switch/Switch.theme.js +1 -1
  113. package/dist/components/forms/TimePicker/TimePicker.theme.js +2 -2
  114. package/dist/components/forms/TimePicker/TimePickerContent.js +1 -0
  115. package/dist/components/forms/TimePicker/TimePickerTrigger.js +1 -1
  116. package/dist/components/forms/forms.theme.js +12 -12
  117. package/dist/components/forms/index.js +1 -0
  118. package/dist/components/layout/FormLayout/FormLayout.js +6 -4
  119. package/dist/components/layout/FormLayout/FormLayout.types.d.ts +3 -3
  120. package/dist/components/layout/FormLayout/FormLayoutSection.js +6 -1
  121. package/dist/components/layout/PageLayout/PageLayout.d.ts +2 -2
  122. package/dist/components/layout/PageLayout/PageLayout.js +23 -5
  123. package/dist/components/layout/PageLayout/PageLayout.theme.d.ts +2 -1
  124. package/dist/components/layout/PageLayout/PageLayout.theme.js +35 -6
  125. package/dist/components/layout/PageLayout/PageLayout.types.d.ts +45 -0
  126. package/dist/components/layout/PageLayout/PageLayoutContent.js +6 -4
  127. package/dist/components/layout/PageLayout/PageLayoutHeader.js +3 -3
  128. package/dist/components/layout/PageLayout/PageLayoutSidebar.js +5 -3
  129. package/dist/components/layout/PageLayout/index.d.ts +1 -1
  130. package/dist/index.js +2 -2
  131. package/dist/styles.css +1 -1
  132. package/package.json +1 -1
  133. package/src/styles/theme.css +127 -92
  134. package/dist/components/core/Sidebar/SidebarSubmenu.d.ts +0 -7
  135. package/dist/components/core/Sidebar/SidebarSubmenu.js +0 -12
@@ -1,8 +1,13 @@
1
- import { jsx } from 'react/jsx-runtime';
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { useReducedMotion, motion } from 'motion/react';
3
3
  import { cn } from '../../../utils/cn.js';
4
4
 
5
5
  const DEFAULT_COLORS = ['#3b82f6', '#8b5cf6', '#ec4899', '#06b6d4'];
6
+ const clamp = (value, min, max) => {
7
+ if (Number.isNaN(value))
8
+ return min;
9
+ return Math.min(Math.max(value, min), max);
10
+ };
6
11
  /**
7
12
  * Each blob follows a unique organic path using keyframed x/y translations
8
13
  * and scale changes. The durations are intentionally varied so the blobs
@@ -37,16 +42,32 @@ const blobAnimations = [
37
42
  scale: [1, 0.95, 1.1, 0.9, 1],
38
43
  baseDuration: 31,
39
44
  },
45
+ {
46
+ // Blob 4 -- slow vertical ribbon sweep
47
+ x: ['0%', '12%', '-18%', '20%', '0%'],
48
+ y: ['0%', '-35%', '-5%', '24%', '0%'],
49
+ scale: [1, 1.25, 0.95, 1.1, 1],
50
+ baseDuration: 29,
51
+ },
52
+ {
53
+ // Blob 5 -- small counter drift to keep dense palettes from syncing
54
+ x: ['0%', '-18%', '10%', '28%', '0%'],
55
+ y: ['0%', '12%', '-22%', '-8%', '0%'],
56
+ scale: [1, 0.88, 1.18, 0.98, 1],
57
+ baseDuration: 34,
58
+ },
40
59
  ];
41
60
  /**
42
61
  * Positions each blob at a different quadrant of the container so they
43
62
  * begin spread out. Additional blobs beyond 4 cycle through positions.
44
63
  */
45
64
  const blobPositions = [
46
- { top: '10%', left: '15%' },
47
- { top: '5%', right: '10%' },
48
- { bottom: '10%', left: '20%' },
49
- { bottom: '5%', right: '15%' },
65
+ { top: '8%', left: '12%' },
66
+ { top: '0%', right: '8%' },
67
+ { bottom: '8%', left: '18%' },
68
+ { bottom: '0%', right: '12%' },
69
+ { top: '34%', left: '36%' },
70
+ { bottom: '28%', right: '34%' },
50
71
  ];
51
72
  /**
52
73
  * Aurora creates an animated multi-color gradient background effect.
@@ -71,49 +92,67 @@ const blobPositions = [
71
92
  */
72
93
  const Aurora = ({ colors = DEFAULT_COLORS, blur = 80, speed = 1, opacity = 0.3, size = 1, className, style, ...props }) => {
73
94
  const shouldReduceMotion = useReducedMotion();
95
+ const safeColors = colors.length > 0 ? colors : DEFAULT_COLORS;
96
+ const safeBlur = Math.max(0, blur);
97
+ const safeSpeed = Math.max(0.1, speed);
98
+ const safeOpacity = clamp(opacity, 0, 1);
99
+ const safeSize = Math.max(0.1, size);
74
100
  // Compute base blob dimension from the size multiplier.
75
101
  // Using percentage-based sizing so it scales with the container.
76
- const blobDimension = `${40 * size}%`;
77
- return (jsx("div", { className: cn('absolute inset-0 overflow-hidden pointer-events-none', className), style: {
78
- opacity,
79
- filter: `blur(${blur}px)`,
102
+ const blobDimension = `${40 * safeSize}%`;
103
+ const bleed = safeBlur * 1.5;
104
+ const wash = safeColors
105
+ .slice(0, 4)
106
+ .map((color, index) => {
107
+ const stops = [
108
+ '20% 30%',
109
+ '80% 20%',
110
+ '30% 80%',
111
+ '75% 75%',
112
+ ];
113
+ return `radial-gradient(circle at ${stops[index]}, ${color}55, transparent 42%)`;
114
+ })
115
+ .join(', ');
116
+ return (jsxs("div", { className: cn('absolute inset-0 overflow-hidden pointer-events-none', className), style: {
80
117
  ...style,
81
- }, "aria-hidden": "true", ...props, children: colors.map((color, index) => {
82
- const anim = blobAnimations[index % blobAnimations.length];
83
- const position = blobPositions[index % blobPositions.length];
84
- const duration = anim.baseDuration / speed;
85
- // When reduced motion is preferred, render static blobs with no animation.
86
- if (shouldReduceMotion) {
87
- return (jsx("div", { style: {
88
- position: 'absolute',
89
- ...position,
90
- width: blobDimension,
91
- height: blobDimension,
92
- borderRadius: '50%',
93
- background: `radial-gradient(circle at center, ${color}, transparent 70%)`,
94
- mixBlendMode: 'screen',
95
- } }, index));
96
- }
97
- return (jsx(motion.div, { animate: {
98
- x: anim.x,
99
- y: anim.y,
100
- scale: anim.scale,
101
- }, transition: {
102
- duration,
103
- repeat: Infinity,
104
- repeatType: 'loop',
105
- ease: 'easeInOut',
106
- }, style: {
118
+ }, "aria-hidden": "true", ...props, children: [jsx("div", { className: "absolute inset-0", style: {
119
+ background: wash,
120
+ opacity: safeOpacity * 0.65,
121
+ mixBlendMode: 'screen',
122
+ } }), safeColors.map((color, index) => {
123
+ const anim = blobAnimations[index % blobAnimations.length];
124
+ const position = blobPositions[index % blobPositions.length];
125
+ const duration = anim.baseDuration / safeSpeed;
126
+ const blobStyle = {
107
127
  position: 'absolute',
108
128
  ...position,
109
129
  width: blobDimension,
110
130
  height: blobDimension,
111
- borderRadius: '50%',
112
- background: `radial-gradient(circle at center, ${color}, transparent 70%)`,
131
+ borderRadius: '9999px',
132
+ background: `radial-gradient(circle at center, ${color}, transparent 68%)`,
133
+ filter: `blur(${safeBlur}px)`,
113
134
  mixBlendMode: 'screen',
114
- willChange: 'transform',
115
- } }, index));
116
- }) }));
135
+ opacity: safeOpacity,
136
+ margin: -bleed,
137
+ };
138
+ // When reduced motion is preferred, render static blobs with no animation.
139
+ if (shouldReduceMotion) {
140
+ return (jsx("div", { style: blobStyle }, index));
141
+ }
142
+ return (jsx(motion.div, { animate: {
143
+ x: anim.x,
144
+ y: anim.y,
145
+ scale: anim.scale,
146
+ }, transition: {
147
+ duration,
148
+ repeat: Infinity,
149
+ repeatType: 'loop',
150
+ ease: 'easeInOut',
151
+ }, style: {
152
+ ...blobStyle,
153
+ willChange: 'transform',
154
+ } }, index));
155
+ })] }));
117
156
  };
118
157
 
119
158
  export { Aurora };
@@ -15,7 +15,7 @@ const sizeMap = {
15
15
  md: 'w-[320px]',
16
16
  lg: 'w-[400px]',
17
17
  };
18
- /** Click reaction states per character */
18
+ /** Click reaction states per abstract character */
19
19
  const CLICK_REACTIONS = {
20
20
  blob: 'surprised',
21
21
  tall: 'love',
@@ -47,7 +47,7 @@ const InteractiveCharacters = ({ state = 'idle', trackMouse = true, size = 'md',
47
47
  const smallState = clickedChar === 'small' ? CLICK_REACTIONS.small : state;
48
48
  const isTrackingEnabled = trackMouse && state !== 'password' && !shouldReduceMotion;
49
49
  const isIdleAnimated = !shouldReduceMotion;
50
- // Mouse tracking different spring configs per character
50
+ // Mouse tracking: each abstract form gets a slightly different response.
51
51
  const blobEye = useMouseTracking({
52
52
  containerRef: svgRef,
53
53
  enabled: isTrackingEnabled,
@@ -63,7 +63,7 @@ const InteractiveCharacters = ({ state = 'idle', trackMouse = true, size = 'md',
63
63
  enabled: isTrackingEnabled,
64
64
  ...springConfigs.small,
65
65
  });
66
- // Idle sway/breathing/head each character at different rates & phase offsets
66
+ // Idle sway/breathing/head movement runs at different rates and phase offsets.
67
67
  const blobIdle = useIdleAnimation({
68
68
  enabled: isIdleAnimated,
69
69
  swayAmount: 1.5,
@@ -1,11 +1,16 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useId } from 'react';
2
3
  import { useTransform, motion } from 'motion/react';
3
4
  import { eyelidVariants, clickTransition, bodyTransition, blobBodyVariants, blobMouthPaths } from '../InteractiveCharacters.animations.js';
4
5
 
5
- const LEFT_EYE = { cx: 32, cy: -50 };
6
- const RIGHT_EYE = { cx: 68, cy: -50 };
6
+ const LEFT_EYE = { cx: 32, cy: -58 };
7
+ const RIGHT_EYE = { cx: 62, cy: -58 };
7
8
  const CLICK_STATES = new Set(['surprised', 'love', 'excited']);
8
9
  const BlobCharacter = ({ state, eyeOffset, idle, blinkAmount, onClick, }) => {
10
+ const id = useId().replace(/:/g, '');
11
+ const leftClipId = `${id}-aura-eye-l`;
12
+ const rightClipId = `${id}-aura-eye-r`;
13
+ const bodyGradientId = `${id}-aura-body`;
9
14
  const leftPupilX = useTransform(eyeOffset.x, (v) => LEFT_EYE.cx + v);
10
15
  const leftPupilY = useTransform(eyeOffset.y, (v) => LEFT_EYE.cy + v);
11
16
  const rightPupilX = useTransform(eyeOffset.x, (v) => RIGHT_EYE.cx + v);
@@ -24,13 +29,17 @@ const BlobCharacter = ({ state, eyeOffset, idle, blinkAmount, onClick, }) => {
24
29
  x: idle.sway,
25
30
  y: idle.bob,
26
31
  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: {
32
+ }, onClick: onClick, children: [jsx("defs", { children: jsxs("linearGradient", { id: bodyGradientId, x1: "15", y1: "-105", x2: "92", y2: "-5", gradientUnits: "userSpaceOnUse", children: [jsx("stop", { stopColor: "#16B8A6" }), jsx("stop", { offset: "1", stopColor: "#0F766E" })] }) }), jsx(motion.path, { d: "M 19 -99 C 40 -113 78 -101 92 -75 C 108 -45 91 -10 58 -1 C 26 8 -2 -13 -5 -47 C -8 -72 3 -89 19 -99 Z", fill: `url(#${bodyGradientId})`, animate: {
33
+ d: state === 'surprised'
34
+ ? 'M 19 -105 C 42 -121 82 -106 97 -78 C 115 -43 94 -6 57 1 C 21 8 -9 -17 -10 -52 C -11 -79 2 -96 19 -105 Z'
35
+ : 'M 19 -99 C 40 -113 78 -101 92 -75 C 108 -45 91 -10 58 -1 C 26 8 -2 -13 -5 -47 C -8 -72 3 -89 19 -99 Z',
36
+ }, transition: transition }), jsx("path", { d: "M 72 -91 C 91 -78 98 -52 88 -30", fill: "none", stroke: "#7DD3C7", strokeWidth: 5, strokeLinecap: "round", opacity: 0.38 }), jsx("ellipse", { cx: 11, cy: -31, rx: 8, ry: 12, fill: "#5EEAD4", opacity: 0.34, transform: "rotate(-24 11 -31)" }), jsxs(motion.g, { style: {
28
37
  originX: '50px',
29
38
  originY: '-50px',
30
39
  rotate: combinedHeadRotate,
31
40
  x: combinedHeadX,
32
41
  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)" })] })] }));
42
+ }, children: [jsxs("g", { children: [jsx("clipPath", { id: leftClipId, 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: 12, ry: 14, fill: "#F8FAFC" }), jsx(motion.circle, { cx: leftPupilX, cy: leftPupilY, r: 7, fill: "#122A33", clipPath: `url(#${leftClipId})` }), 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 - 12, y: LEFT_EYE.cy - 14, width: 24, height: 28, fill: "#16B8A6", rx: 3, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsxs("g", { children: [jsx("clipPath", { id: rightClipId, 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: 12, ry: 14, fill: "#F8FAFC" }), jsx(motion.circle, { cx: rightPupilX, cy: rightPupilY, r: 7, fill: "#122A33", clipPath: `url(#${rightClipId})` }), 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 - 12, y: RIGHT_EYE.cy - 14, width: 24, height: 28, fill: "#16B8A6", rx: 3, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsx("ellipse", { cx: 20, cy: -37, rx: 6, ry: 3.5, fill: "#99F6E4", opacity: state === 'surprised' ? 0.72 : 0.45 }), jsx("ellipse", { cx: 76, cy: -36, rx: 6, ry: 3.5, fill: "#99F6E4", opacity: state === 'surprised' ? 0.72 : 0.45 }), jsx(motion.path, { d: blobMouthPaths[state], fill: "none", stroke: "#122A33", strokeWidth: 2.4, strokeLinecap: "round", animate: { d: blobMouthPaths[state] }, transition: transition, transform: "translate(47, -30)" })] })] }));
34
43
  };
35
44
 
36
45
  export { BlobCharacter };
@@ -1,11 +1,16 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useId } from 'react';
2
3
  import { useTransform, motion } from 'motion/react';
3
4
  import { eyelidVariants, clickTransition, bodyTransition, smallBodyVariants, smallMouthPaths } from '../InteractiveCharacters.animations.js';
4
5
 
5
- const LEFT_EYE = { cx: -14, cy: -32 };
6
- const RIGHT_EYE = { cx: 14, cy: -32 };
6
+ const LEFT_EYE = { cx: -13, cy: -34 };
7
+ const RIGHT_EYE = { cx: 12, cy: -34 };
7
8
  const CLICK_STATES = new Set(['surprised', 'love', 'excited']);
8
9
  const SmallCharacter = ({ state, eyeOffset, idle, blinkAmount, onClick, }) => {
10
+ const id = useId().replace(/:/g, '');
11
+ const leftClipId = `${id}-glyph-eye-l`;
12
+ const rightClipId = `${id}-glyph-eye-r`;
13
+ const bodyGradientId = `${id}-glyph-body`;
9
14
  const leftPupilX = useTransform(eyeOffset.x, (v) => LEFT_EYE.cx + v);
10
15
  const leftPupilY = useTransform(eyeOffset.y, (v) => LEFT_EYE.cy + v);
11
16
  const rightPupilX = useTransform(eyeOffset.x, (v) => RIGHT_EYE.cx + v);
@@ -24,13 +29,17 @@ const SmallCharacter = ({ state, eyeOffset, idle, blinkAmount, onClick, }) => {
24
29
  x: idle.sway,
25
30
  y: idle.bob,
26
31
  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: {
32
+ }, onClick: onClick, children: [jsx("defs", { children: jsxs("linearGradient", { id: bodyGradientId, x1: "-48", y1: "-73", x2: "38", y2: "0", gradientUnits: "userSpaceOnUse", children: [jsx("stop", { stopColor: "#FDA4AF" }), jsx("stop", { offset: "1", stopColor: "#FB7185" })] }) }), jsx(motion.path, { d: "M -38 -28 C -43 -57 -18 -78 13 -67 C 39 -58 50 -27 31 -8 C 13 10 -29 3 -38 -28 Z", fill: `url(#${bodyGradientId})`, animate: {
33
+ d: state === 'excited'
34
+ ? 'M -40 -31 C -47 -63 -17 -85 17 -72 C 45 -61 56 -27 33 -5 C 12 15 -31 5 -40 -31 Z'
35
+ : 'M -38 -28 C -43 -57 -18 -78 13 -67 C 39 -58 50 -27 31 -8 C 13 10 -29 3 -38 -28 Z',
36
+ }, transition: transition }), jsx("path", { d: "M 20 -59 C 38 -47 42 -26 31 -13", fill: "none", stroke: "#FFE4E6", strokeWidth: 4, strokeLinecap: "round", opacity: 0.5 }), jsx("circle", { cx: -44, cy: -61, r: 7, fill: "#38BDF8", opacity: state === 'excited' ? 0.95 : 0.65 }), jsxs(motion.g, { style: {
28
37
  originX: '0px',
29
38
  originY: '-30px',
30
39
  rotate: combinedHeadRotate,
31
40
  x: combinedHeadX,
32
41
  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)" })] })] }));
42
+ }, children: [jsxs("g", { children: [jsx("clipPath", { id: leftClipId, children: jsx("circle", { cx: LEFT_EYE.cx, cy: LEFT_EYE.cy, r: 10 }) }), jsx("circle", { cx: LEFT_EYE.cx, cy: LEFT_EYE.cy, r: 8.5, fill: "#FFF7ED" }), jsx(motion.circle, { cx: leftPupilX, cy: leftPupilY, r: 5.5, fill: "#253040", clipPath: `url(#${leftClipId})` }), 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 - 8.5, y: LEFT_EYE.cy - 8.5, width: 17, height: 17, fill: "#FB7B8C", rx: 2, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsxs("g", { children: [jsx("clipPath", { id: rightClipId, children: jsx("circle", { cx: RIGHT_EYE.cx, cy: RIGHT_EYE.cy, r: 10 }) }), jsx("circle", { cx: RIGHT_EYE.cx, cy: RIGHT_EYE.cy, r: 8.5, fill: "#FFF7ED" }), jsx(motion.circle, { cx: rightPupilX, cy: rightPupilY, r: 5.5, fill: "#253040", clipPath: `url(#${rightClipId})` }), 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 - 8.5, y: RIGHT_EYE.cy - 8.5, width: 17, height: 17, fill: "#FB7B8C", rx: 2, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsx("ellipse", { cx: -25, cy: -20, rx: 5, ry: 3, fill: "#FFE4E6", opacity: state === 'excited' ? 0.9 : 0.55 }), jsx("ellipse", { cx: 23, cy: -20, rx: 5, ry: 3, fill: "#FFE4E6", opacity: state === 'excited' ? 0.9 : 0.55 }), jsx(motion.path, { d: smallMouthPaths[state], fill: "none", stroke: "#1E293B", strokeWidth: 2, strokeLinecap: "round", animate: { d: smallMouthPaths[state] }, transition: transition, transform: "translate(0, -17)" })] })] }));
34
43
  };
35
44
 
36
45
  export { SmallCharacter };
@@ -1,11 +1,17 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useId } from 'react';
2
3
  import { useTransform, motion } from 'motion/react';
3
4
  import { eyelidVariants, clickTransition, bodyTransition, tallBodyVariants, eyebrowVariants, tallMouthPaths } from '../InteractiveCharacters.animations.js';
4
5
 
5
- const LEFT_EYE = { cx: -12, cy: -82 };
6
- const RIGHT_EYE = { cx: 12, cy: -82 };
6
+ const LEFT_EYE = { cx: -12, cy: -73 };
7
+ const RIGHT_EYE = { cx: 12, cy: -73 };
7
8
  const CLICK_STATES = new Set(['surprised', 'love', 'excited']);
8
9
  const TallCharacter = ({ state, eyeOffset, idle, blinkAmount, onClick, }) => {
10
+ const id = useId().replace(/:/g, '');
11
+ const leftClipId = `${id}-stack-eye-l`;
12
+ const rightClipId = `${id}-stack-eye-r`;
13
+ const topGradientId = `${id}-stack-top`;
14
+ const baseGradientId = `${id}-stack-base`;
9
15
  const leftPupilX = useTransform(eyeOffset.x, (v) => LEFT_EYE.cx + v);
10
16
  const leftPupilY = useTransform(eyeOffset.y, (v) => LEFT_EYE.cy + v);
11
17
  const rightPupilX = useTransform(eyeOffset.x, (v) => RIGHT_EYE.cx + v);
@@ -25,13 +31,17 @@ const TallCharacter = ({ state, eyeOffset, idle, blinkAmount, onClick, }) => {
25
31
  x: idle.sway,
26
32
  y: idle.bob,
27
33
  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: {
34
+ }, onClick: onClick, children: [jsxs("defs", { children: [jsxs("linearGradient", { id: topGradientId, x1: "-33", y1: "-128", x2: "34", y2: "-54", gradientUnits: "userSpaceOnUse", children: [jsx("stop", { stopColor: "#A5B4FC" }), jsx("stop", { offset: "1", stopColor: "#6366F1" })] }), jsxs("linearGradient", { id: baseGradientId, x1: "-46", y1: "-69", x2: "45", y2: "0", gradientUnits: "userSpaceOnUse", children: [jsx("stop", { stopColor: "#67E8F9" }), jsx("stop", { offset: "1", stopColor: "#14B8A6" })] })] }), jsx(motion.path, { d: "M -20 -132 C 8 -146 37 -124 34 -94 C 32 -70 8 -57 -16 -65 C -42 -74 -47 -117 -20 -132 Z", fill: `url(#${topGradientId})`, animate: {
35
+ d: state === 'love'
36
+ ? 'M -22 -136 C 9 -153 42 -126 37 -92 C 34 -66 8 -53 -18 -63 C -48 -74 -51 -120 -22 -136 Z'
37
+ : 'M -20 -132 C 8 -146 37 -124 34 -94 C 32 -70 8 -57 -16 -65 C -42 -74 -47 -117 -20 -132 Z',
38
+ }, transition: transition }), jsx("path", { d: "M -39 -63 C -15 -86 31 -78 43 -47 C 55 -13 28 9 -10 4 C -47 -1 -66 -35 -39 -63 Z", fill: `url(#${baseGradientId})` }), jsx("path", { d: "M -25 -121 C -6 -132 15 -129 25 -112", fill: "none", stroke: "#E0E7FF", strokeLinecap: "round", strokeWidth: 4, opacity: 0.48 }), jsxs(motion.g, { style: {
29
39
  originX: '0px',
30
40
  originY: '-70px',
31
41
  rotate: combinedHeadRotate,
32
42
  x: combinedHeadX,
33
43
  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)" })] })] }));
44
+ }, children: [jsx(motion.line, { x1: -23, y1: -94, x2: -5, y2: -94, 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: 5, y1: -94, x2: 23, y2: -94, 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: leftClipId, 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: 10, ry: 12, fill: "#F8FAFC" }), jsx(motion.circle, { cx: leftPupilX, cy: leftPupilY, r: 6.5, fill: "#1E293B", clipPath: `url(#${leftClipId})` }), 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 - 10, y: LEFT_EYE.cy - 12, width: 20, height: 24, fill: "#7C83F3", rx: 3, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsxs("g", { children: [jsx("clipPath", { id: rightClipId, 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: 10, ry: 12, fill: "#F8FAFC" }), jsx(motion.circle, { cx: rightPupilX, cy: rightPupilY, r: 6.5, fill: "#1E293B", clipPath: `url(#${rightClipId})` }), 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 - 10, y: RIGHT_EYE.cy - 12, width: 20, height: 24, fill: "#7C83F3", rx: 3, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsx("ellipse", { cx: -25, cy: -57, rx: 6, ry: 3.5, fill: "#C4B5FD", opacity: state === 'love' ? 0.75 : 0.45 }), jsx("ellipse", { cx: 25, cy: -57, rx: 6, ry: 3.5, fill: "#C4B5FD", opacity: state === 'love' ? 0.75 : 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, -49)" })] })] }));
35
45
  };
36
46
 
37
47
  export { TallCharacter };
@@ -1,8 +1,9 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import React__default, { useId, useState, useRef, useCallback, useEffect } from 'react';
3
- import { useReducedMotion, AnimatePresence, motion } from 'motion/react';
2
+ import React__default, { useState, useRef, useCallback, useEffect, useLayoutEffect } from 'react';
3
+ import { useReducedMotion, motion, AnimatePresence } from 'motion/react';
4
4
  import { cn } from '../../../utils/cn.js';
5
5
 
6
+ const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
6
7
  /**
7
8
  * MorphingText smoothly morphs between different text strings using a blur/fade
8
9
  * transition. Two overlapping text layers cross-fade with a Gaussian blur effect
@@ -18,11 +19,12 @@ import { cn } from '../../../utils/cn.js';
18
19
  * ```
19
20
  */
20
21
  const MorphingText = React__default.forwardRef(({ texts, interval = 3000, transitionDuration = 800, loop = true, pauseOnHover = false, currentIndex: controlledIndex, onTextChange, className, ...props }, ref) => {
21
- const filterId = useId();
22
22
  const shouldReduceMotion = useReducedMotion();
23
23
  const isControlled = controlledIndex !== undefined;
24
24
  const [internalIndex, setInternalIndex] = useState(0);
25
25
  const activeIndex = isControlled ? controlledIndex : internalIndex;
26
+ const measureRefs = useRef([]);
27
+ const [textRects, setTextRects] = useState([]);
26
28
  const [isPaused, setIsPaused] = useState(false);
27
29
  const intervalRef = useRef(null);
28
30
  const hasReachedEnd = useRef(false);
@@ -59,6 +61,28 @@ const MorphingText = React__default.forwardRef(({ texts, interval = 3000, transi
59
61
  useEffect(() => {
60
62
  hasReachedEnd.current = false;
61
63
  }, [loop, texts]);
64
+ useIsomorphicLayoutEffect(() => {
65
+ const measure = () => {
66
+ setTextRects(texts.map((_, index) => {
67
+ var _a, _b, _c;
68
+ const rect = (_a = measureRefs.current[index]) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
69
+ return {
70
+ width: (_b = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _b !== void 0 ? _b : 0,
71
+ height: (_c = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _c !== void 0 ? _c : 0,
72
+ };
73
+ }));
74
+ };
75
+ measure();
76
+ if (typeof ResizeObserver === 'undefined') {
77
+ return;
78
+ }
79
+ const observer = new ResizeObserver(measure);
80
+ measureRefs.current.forEach((element) => {
81
+ if (element)
82
+ observer.observe(element);
83
+ });
84
+ return () => observer.disconnect();
85
+ }, [texts]);
62
86
  // Fire onTextChange for controlled index changes
63
87
  useEffect(() => {
64
88
  if (isControlled) {
@@ -78,14 +102,23 @@ const MorphingText = React__default.forwardRef(({ texts, interval = 3000, transi
78
102
  }
79
103
  const durationSeconds = transitionDuration / 1000;
80
104
  const currentText = texts[safeIndex];
105
+ const currentRect = textRects[safeIndex];
106
+ const measuredSize = (currentRect === null || currentRect === void 0 ? void 0 : currentRect.width) && (currentRect === null || currentRect === void 0 ? void 0 : currentRect.height)
107
+ ? { width: currentRect.width, height: currentRect.height }
108
+ : undefined;
81
109
  // Reduced motion: instant swap with no animation
82
110
  if (shouldReduceMotion) {
83
111
  return (jsx("div", { ref: ref, className: cn('relative inline-block', className), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, "aria-live": "polite", "aria-atomic": "true", ...props, children: jsx("span", { children: currentText }) }));
84
112
  }
85
- return (jsxs("div", { ref: ref, className: cn('relative inline-block', className), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, "aria-live": "polite", "aria-atomic": "true", ...props, children: [jsx("svg", { className: "absolute h-0 w-0", "aria-hidden": "true", children: jsx("defs", { children: jsx("filter", { id: `${filterId}-blur`, children: jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: "0" }) }) }) }), jsx("span", { className: "invisible block", "aria-hidden": "true", children: currentText }), jsx(AnimatePresence, { mode: "popLayout", initial: false, children: jsx(motion.span, { className: "absolute inset-0 flex items-center", initial: { opacity: 0, filter: 'blur(8px)' }, animate: { opacity: 1, filter: 'blur(0px)' }, exit: { opacity: 0, filter: 'blur(8px)' }, transition: {
86
- duration: durationSeconds,
87
- ease: 'easeInOut',
88
- }, children: currentText }, safeIndex) })] }));
113
+ return (jsx("div", { ref: ref, className: cn('relative inline-block align-bottom', className), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, "aria-live": "polite", "aria-atomic": "true", ...props, children: jsxs(motion.span, { className: "relative inline-block align-bottom", initial: false, animate: measuredSize, transition: {
114
+ duration: durationSeconds,
115
+ ease: 'easeInOut',
116
+ }, children: [jsx("span", { className: "pointer-events-none invisible absolute left-0 top-0 whitespace-nowrap", "aria-hidden": "true", children: texts.map((text, index) => (jsx("span", { ref: (element) => {
117
+ measureRefs.current[index] = element;
118
+ }, className: "block w-fit whitespace-nowrap", children: text }, `${text}-${index}`))) }), !measuredSize && (jsx("span", { className: "whitespace-nowrap", children: currentText })), measuredSize && (jsx(AnimatePresence, { mode: "popLayout", initial: false, children: jsx(motion.span, { className: "absolute left-0 top-0 flex whitespace-nowrap", initial: { opacity: 0, filter: 'blur(8px)' }, animate: { opacity: 1, filter: 'blur(0px)' }, exit: { opacity: 0, filter: 'blur(8px)' }, transition: {
119
+ duration: durationSeconds,
120
+ ease: 'easeInOut',
121
+ }, children: currentText }, safeIndex) }))] }) }));
89
122
  });
90
123
  MorphingText.displayName = "MorphingText";
91
124
 
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import type { StripePatternProps } from "./StripePattern.types";
3
+ /**
4
+ * StripePattern renders an SVG-based diagonal stripe background pattern.
5
+ *
6
+ * Use it as an absolute-positioned decorative or placeholder fill inside a
7
+ * container with `position: relative`. The pattern is non-interactive
8
+ * (`pointer-events: none`) and sits at `z-index: 0` by default.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * <div className="relative overflow-hidden h-40 rounded-lg">
13
+ * <StripePattern />
14
+ * <div className="relative z-10">Empty cover slot</div>
15
+ * </div>
16
+ * ```
17
+ */
18
+ export declare const StripePattern: React.ForwardRefExoticComponent<StripePatternProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,54 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import React__default, { useId, useMemo } from 'react';
3
+ import { useReducedMotion, motion } from 'motion/react';
4
+ import { cn } from '../../../utils/cn.js';
5
+
6
+ function buildMaskImage(direction, fadeSize) {
7
+ const stop = Math.max(0, Math.min(100, fadeSize));
8
+ if (direction === "radial") {
9
+ return `radial-gradient(ellipse at center, black ${100 - stop}%, transparent 100%)`;
10
+ }
11
+ const directionMap = {
12
+ top: "to top",
13
+ bottom: "to bottom",
14
+ left: "to left",
15
+ right: "to right",
16
+ };
17
+ return `linear-gradient(${directionMap[direction]}, black ${100 - stop}%, transparent 100%)`;
18
+ }
19
+ /**
20
+ * StripePattern renders an SVG-based diagonal stripe background pattern.
21
+ *
22
+ * Use it as an absolute-positioned decorative or placeholder fill inside a
23
+ * container with `position: relative`. The pattern is non-interactive
24
+ * (`pointer-events: none`) and sits at `z-index: 0` by default.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * <div className="relative overflow-hidden h-40 rounded-lg">
29
+ * <StripePattern />
30
+ * <div className="relative z-10">Empty cover slot</div>
31
+ * </div>
32
+ * ```
33
+ */
34
+ const StripePattern = React__default.forwardRef(({ spacing = 12, strokeWidth = 6, angle = -45, color = "color-mix(in srgb, var(--color-border) 50%, transparent)", fade = false, fadeDirection = "radial", fadeSize = 50, animated = false, className, style, ...rest }, ref) => {
35
+ const patternId = useId();
36
+ const shouldReduceMotion = useReducedMotion();
37
+ const enableAnimation = animated && !shouldReduceMotion;
38
+ const maskImage = useMemo(() => (fade ? buildMaskImage(fadeDirection, fadeSize) : undefined), [fade, fadeDirection, fadeSize]);
39
+ const containerStyle = {
40
+ ...style,
41
+ ...(maskImage
42
+ ? {
43
+ maskImage,
44
+ WebkitMaskImage: maskImage,
45
+ }
46
+ : {}),
47
+ };
48
+ const lineX = spacing / 2;
49
+ const lineElement = enableAnimation ? (jsx(motion.line, { x1: lineX, y1: 0, x2: lineX, y2: spacing, stroke: color, strokeWidth: strokeWidth, animate: { opacity: [0.4, 1, 0.4] }, transition: { duration: 4, ease: "easeInOut", repeat: Infinity } })) : (jsx("line", { x1: lineX, y1: 0, x2: lineX, y2: spacing, stroke: color, strokeWidth: strokeWidth }));
50
+ return (jsx("div", { ref: ref, className: cn("absolute inset-0 pointer-events-none z-0 overflow-hidden", className), style: containerStyle, ...rest, children: jsxs("svg", { "aria-hidden": "true", className: "absolute inset-0 w-full h-full", children: [jsx("defs", { children: jsx("pattern", { id: patternId, x: "0", y: "0", width: spacing, height: spacing, patternUnits: "userSpaceOnUse", patternTransform: `rotate(${angle})`, children: lineElement }) }), jsx("rect", { width: "100%", height: "100%", fill: `url(#${patternId})` })] }) }));
51
+ });
52
+ StripePattern.displayName = "StripePattern";
53
+
54
+ export { StripePattern };
@@ -0,0 +1,53 @@
1
+ import React from "react";
2
+ /** Direction for the fade gradient mask */
3
+ export type StripePatternFadeDirection = "radial" | "top" | "bottom" | "left" | "right";
4
+ export interface StripePatternProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ /**
6
+ * Distance between adjacent stripes in pixels (center to center).
7
+ * @default 12
8
+ */
9
+ spacing?: number;
10
+ /**
11
+ * Thickness of each stripe in pixels.
12
+ * @default 6
13
+ */
14
+ strokeWidth?: number;
15
+ /**
16
+ * Stripe rotation in degrees. `-45` runs bottom-left to top-right.
17
+ * @default -45
18
+ */
19
+ angle?: number;
20
+ /**
21
+ * Stroke color of the stripes.
22
+ * Accepts any valid CSS color value or CSS variable.
23
+ * @default "color-mix(in srgb, var(--color-border) 50%, transparent)"
24
+ */
25
+ color?: string;
26
+ /**
27
+ * Whether to apply a gradient mask that fades the pattern at the edges.
28
+ * Defaults to `false` so the pattern reads as a flat fill — useful for
29
+ * placeholder surfaces. Set to `true` for decorative backgrounds.
30
+ * @default false
31
+ */
32
+ fade?: boolean;
33
+ /**
34
+ * Direction of the fade gradient.
35
+ * - "radial" fades from center outward
36
+ * - "top" | "bottom" | "left" | "right" fades toward that edge
37
+ * @default "radial"
38
+ */
39
+ fadeDirection?: StripePatternFadeDirection;
40
+ /**
41
+ * Size of the fade area as a percentage (0-100).
42
+ * @default 50
43
+ */
44
+ fadeSize?: number;
45
+ /**
46
+ * Whether to apply a subtle pulse animation to the stripes.
47
+ * Respects prefers-reduced-motion automatically.
48
+ * @default false
49
+ */
50
+ animated?: boolean;
51
+ /** Additional className for style overrides (shadcn pattern). */
52
+ className?: string;
53
+ }
@@ -0,0 +1,2 @@
1
+ export { StripePattern } from "./StripePattern";
2
+ export type { StripePatternProps, StripePatternFadeDirection, } from "./StripePattern.types";
@@ -20,10 +20,14 @@ export { GridPattern } from "./GridPattern";
20
20
  export type { GridPatternProps, GridPatternFadeDirection, } from "./GridPattern";
21
21
  export { DotPattern } from "./DotPattern";
22
22
  export type { DotPatternProps, DotPatternFadeDirection, } from "./DotPattern";
23
+ export { StripePattern } from "./StripePattern";
24
+ export type { StripePatternProps, StripePatternFadeDirection, } from "./StripePattern";
23
25
  export { NoiseOverlay } from "./NoiseOverlay";
24
26
  export type { NoiseOverlayProps, NoiseBlendMode } from "./NoiseOverlay";
25
27
  export { Spotlight } from "./Spotlight";
26
28
  export type { SpotlightProps } from "./Spotlight";
29
+ export { SpotlightBorder } from "./SpotlightBorder";
30
+ export type { SpotlightBorderProps, SpotlightBorderVariation, } from "./SpotlightBorder";
27
31
  export { ParallaxSection } from "./ParallaxSection";
28
32
  export type { ParallaxSectionProps } from "./ParallaxSection";
29
33
  export { ScrollReveal } from "./ScrollReveal";
@@ -36,3 +40,8 @@ export { InteractiveCharacters } from "./InteractiveCharacters";
36
40
  export type { InteractiveCharactersProps, CharacterState, CharacterSize, } from "./InteractiveCharacters";
37
41
  export { InsetCircleButton, InsetPill } from "./Neumorphic";
38
42
  export type { InsetCircleButtonProps, InsetPillProps, } from "./Neumorphic";
43
+ export { AIOrb } from "./AIOrb";
44
+ export type { AIOrbProps, OrbState } from "./AIOrb";
45
+ export type { DotSphereProps, LiquidProps, AuraProps } from "./AIOrb";
46
+ export { Particles } from "./Particles";
47
+ export type { ParticlesProps } from "./Particles";
@@ -9,8 +9,10 @@ export { MagneticElement } from './MagneticElement/MagneticElement.js';
9
9
  export { MorphingText } from './MorphingText/MorphingText.js';
10
10
  export { GridPattern } from './GridPattern/GridPattern.js';
11
11
  export { DotPattern } from './DotPattern/DotPattern.js';
12
+ export { StripePattern } from './StripePattern/StripePattern.js';
12
13
  export { NoiseOverlay } from './NoiseOverlay/NoiseOverlay.js';
13
14
  export { Spotlight } from './Spotlight/Spotlight.js';
15
+ export { SpotlightBorder } from './SpotlightBorder/SpotlightBorder.js';
14
16
  export { ParallaxSection } from './ParallaxSection/ParallaxSection.js';
15
17
  export { ScrollReveal } from './ScrollReveal/ScrollReveal.js';
16
18
  export { StickyScroll } from './StickyScroll/StickyScroll.js';
@@ -18,3 +20,5 @@ export { PageTransition } from './PageTransition/PageTransition.js';
18
20
  export { InteractiveCharacters } from './InteractiveCharacters/InteractiveCharacters.js';
19
21
  export { InsetCircleButton } from './Neumorphic/InsetCircleButton.js';
20
22
  export { InsetPill } from './Neumorphic/InsetPill.js';
23
+ export { AIOrb } from './AIOrb/AIOrb.js';
24
+ export { Particles } from './Particles/Particles.js';
@@ -10,7 +10,7 @@ const checkboxTheme = {
10
10
  // Default state - light
11
11
  "border-[var(--color-border)] bg-[var(--color-surface)] " +
12
12
  // Default state - dark
13
- "dark:bg-[var(--color-background-tertiary)] " +
13
+ "dark:bg-[var(--color-surface-raised)] " +
14
14
  // Checked state - light
15
15
  "checked:border-[var(--color-primary)] checked:bg-[var(--color-primary)] " +
16
16
  // Checked state - dark