@once-ui-system/core 1.5.1 → 1.5.3

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 (84) hide show
  1. package/README.md +54 -0
  2. package/dist/README.md +54 -0
  3. package/dist/components/Avatar.d.ts +2 -0
  4. package/dist/components/Avatar.d.ts.map +1 -1
  5. package/dist/components/Avatar.js +2 -2
  6. package/dist/components/Avatar.js.map +1 -1
  7. package/dist/components/CelebrationFx.d.ts +16 -0
  8. package/dist/components/CelebrationFx.d.ts.map +1 -0
  9. package/dist/components/CelebrationFx.js +310 -0
  10. package/dist/components/CelebrationFx.js.map +1 -0
  11. package/dist/components/CountdownFx.d.ts +11 -0
  12. package/dist/components/CountdownFx.d.ts.map +1 -0
  13. package/dist/components/CountdownFx.js +49 -0
  14. package/dist/components/CountdownFx.js.map +1 -0
  15. package/dist/components/Heading.d.ts.map +1 -1
  16. package/dist/components/MatrixFx.d.ts +11 -1
  17. package/dist/components/MatrixFx.d.ts.map +1 -1
  18. package/dist/components/MatrixFx.js +480 -118
  19. package/dist/components/MatrixFx.js.map +1 -1
  20. package/dist/components/Media.d.ts.map +1 -1
  21. package/dist/components/Media.js +10 -5
  22. package/dist/components/Media.js.map +1 -1
  23. package/dist/components/Pulse.d.ts.map +1 -1
  24. package/dist/components/Pulse.js +1 -0
  25. package/dist/components/Pulse.js.map +1 -1
  26. package/dist/components/SegmentedControl.d.ts +1 -0
  27. package/dist/components/SegmentedControl.d.ts.map +1 -1
  28. package/dist/components/SegmentedControl.js +3 -3
  29. package/dist/components/SegmentedControl.js.map +1 -1
  30. package/dist/components/ShineFx.d.ts +1 -0
  31. package/dist/components/ShineFx.d.ts.map +1 -1
  32. package/dist/components/ShineFx.js +3 -2
  33. package/dist/components/ShineFx.js.map +1 -1
  34. package/dist/components/ShineFx.module.scss +18 -1
  35. package/dist/components/StylePanel.js +1 -1
  36. package/dist/components/StylePanel.js.map +1 -1
  37. package/dist/components/WeatherFx.d.ts +17 -0
  38. package/dist/components/WeatherFx.d.ts.map +1 -0
  39. package/dist/components/WeatherFx.js +769 -0
  40. package/dist/components/WeatherFx.js.map +1 -0
  41. package/dist/components/index.d.ts +3 -0
  42. package/dist/components/index.d.ts.map +1 -1
  43. package/dist/components/index.js +3 -0
  44. package/dist/components/index.js.map +1 -1
  45. package/dist/data/emoji-data.json +10 -8
  46. package/dist/icons.d.ts.map +1 -1
  47. package/dist/icons.js +3 -0
  48. package/dist/icons.js.map +1 -1
  49. package/dist/modules/code/CodeBlock.js +1 -1
  50. package/dist/modules/code/CodeBlock.js.map +1 -1
  51. package/dist/modules/data/BarChart.d.ts +2 -0
  52. package/dist/modules/data/BarChart.d.ts.map +1 -1
  53. package/dist/modules/data/BarChart.js +43 -41
  54. package/dist/modules/data/BarChart.js.map +1 -1
  55. package/dist/modules/data/Gauge.module.css +27 -0
  56. package/dist/modules/data/LineBarChart.d.ts +2 -0
  57. package/dist/modules/data/LineBarChart.d.ts.map +1 -1
  58. package/dist/modules/data/LineBarChart.js +35 -33
  59. package/dist/modules/data/LineBarChart.js.map +1 -1
  60. package/dist/modules/data/LineChart.d.ts +2 -0
  61. package/dist/modules/data/LineChart.d.ts.map +1 -1
  62. package/dist/modules/data/LineChart.js +43 -41
  63. package/dist/modules/data/LineChart.js.map +1 -1
  64. package/dist/modules/data/LinearGauge.d.ts +18 -0
  65. package/dist/modules/data/LinearGauge.d.ts.map +1 -0
  66. package/dist/modules/data/LinearGauge.js +78 -0
  67. package/dist/modules/data/LinearGauge.js.map +1 -0
  68. package/dist/modules/data/RadialGauge.d.ts +25 -0
  69. package/dist/modules/data/RadialGauge.d.ts.map +1 -0
  70. package/dist/modules/data/RadialGauge.js +94 -0
  71. package/dist/modules/data/RadialGauge.js.map +1 -0
  72. package/dist/modules/data/index.d.ts +2 -0
  73. package/dist/modules/data/index.d.ts.map +1 -1
  74. package/dist/modules/data/index.js +2 -0
  75. package/dist/modules/data/index.js.map +1 -1
  76. package/dist/modules/index.d.ts +3 -16
  77. package/dist/modules/index.d.ts.map +1 -1
  78. package/dist/modules/index.js +2 -15
  79. package/dist/modules/index.js.map +1 -1
  80. package/dist/modules/navigation/MegaMenu.d.ts.map +1 -1
  81. package/dist/modules/navigation/MegaMenu.js +11 -11
  82. package/dist/modules/navigation/MegaMenu.js.map +1 -1
  83. package/dist/package.json +1 -1
  84. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React, { useEffect, useRef } from "react";
4
4
  import { Flex } from ".";
5
- const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"], size = 3, spacing = 3, revealFrom = "center", trigger = "instant", flicker = false, children, ...rest }, forwardedRef) => {
5
+ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"], size = 3, spacing = 3, revealFrom = "center", trigger = "instant", active = false, flicker = false, bulge, children, ...rest }, forwardedRef) => {
6
6
  const containerRef = useRef(null);
7
7
  const canvasRef = useRef(null);
8
8
  const animationRef = useRef(undefined);
@@ -12,6 +12,8 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
12
12
  const hideStartProgressRef = useRef(0);
13
13
  const isHoveredRef = useRef(false);
14
14
  const mountAnimationCompleteRef = useRef(false);
15
+ const bulgeStartTimeRef = useRef(Date.now());
16
+ const dotsRef = useRef([]);
15
17
  useEffect(() => {
16
18
  if (forwardedRef) {
17
19
  if ("current" in forwardedRef) {
@@ -51,59 +53,126 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
51
53
  const computedColor = getComputedStyle(container).getPropertyValue(`--${color}`);
52
54
  return computedColor || color;
53
55
  });
54
- // Create dot grid
56
+ // Create dot grid with padding to prevent edge gaps during displacement
55
57
  const totalSize = size + spacing;
56
- const cols = Math.ceil(canvasWidth / totalSize);
57
- const rows = Math.ceil(canvasHeight / totalSize);
58
- const dots = [];
59
- for (let row = 0; row < rows; row++) {
60
- for (let col = 0; col < cols; col++) {
61
- const x = col * totalSize + size / 2;
62
- const y = row * totalSize + size / 2;
63
- // Calculate distance from reveal origin
64
- let distanceFromOrigin = 0;
65
- const centerX = canvasWidth / 2;
66
- const centerY = canvasHeight / 2;
67
- switch (revealFrom) {
68
- case "center":
69
- const dx = x - centerX;
70
- const dy = y - centerY;
71
- distanceFromOrigin = Math.sqrt(dx * dx + dy * dy);
72
- break;
73
- case "top":
74
- distanceFromOrigin = y;
75
- break;
76
- case "bottom":
77
- distanceFromOrigin = canvasHeight - y;
78
- break;
79
- case "left":
80
- distanceFromOrigin = x;
81
- break;
82
- case "right":
83
- distanceFromOrigin = canvasWidth - x;
84
- break;
58
+ const maxDisplacement = (bulge?.intensity ?? 10) * 2; // Account for max possible displacement
59
+ const paddedWidth = canvasWidth + maxDisplacement * 2;
60
+ const paddedHeight = canvasHeight + maxDisplacement * 2;
61
+ const cols = Math.ceil(paddedWidth / totalSize);
62
+ const rows = Math.ceil(paddedHeight / totalSize);
63
+ // Only create new dots if grid doesn't exist or dimensions/size changed
64
+ let dots = dotsRef.current;
65
+ let maxDistance = 0;
66
+ if (dots.length === 0 || dots[0]?.gridSize !== totalSize || dots[0]?.canvasW !== canvasWidth || dots[0]?.canvasH !== canvasHeight) {
67
+ // Create new dot grid
68
+ dots = [];
69
+ for (let row = 0; row < rows; row++) {
70
+ for (let col = 0; col < cols; col++) {
71
+ const x = col * totalSize + size / 2 - maxDisplacement;
72
+ const y = row * totalSize + size / 2 - maxDisplacement;
73
+ // Calculate distance from reveal origin
74
+ let distanceFromOrigin = 0;
75
+ const centerX = canvasWidth / 2;
76
+ const centerY = canvasHeight / 2;
77
+ switch (revealFrom) {
78
+ case "center":
79
+ const dx = x - centerX;
80
+ const dy = y - centerY;
81
+ distanceFromOrigin = Math.sqrt(dx * dx + dy * dy);
82
+ break;
83
+ case "top":
84
+ distanceFromOrigin = y;
85
+ break;
86
+ case "bottom":
87
+ distanceFromOrigin = canvasHeight - y;
88
+ break;
89
+ case "left":
90
+ distanceFromOrigin = x;
91
+ break;
92
+ case "right":
93
+ distanceFromOrigin = canvasWidth - x;
94
+ break;
95
+ }
96
+ dots.push({
97
+ x,
98
+ y,
99
+ gridX: col,
100
+ gridY: row,
101
+ color: parsedColors[Math.floor(Math.random() * parsedColors.length)],
102
+ baseOpacity: 0.3 + Math.random() * 0.7,
103
+ distanceFromOrigin,
104
+ randomOffset: Math.random() * 0.3,
105
+ flickerPhase: Math.random() * Math.PI * 2,
106
+ flickerSpeed: 0.8 + Math.random() * 0.4,
107
+ gridSize: totalSize,
108
+ canvasW: canvasWidth,
109
+ canvasH: canvasHeight,
110
+ });
85
111
  }
86
- dots.push({
87
- x,
88
- y,
89
- gridX: col,
90
- gridY: row,
91
- color: parsedColors[Math.floor(Math.random() * parsedColors.length)],
92
- baseOpacity: 0.3 + Math.random() * 0.7,
93
- distanceFromOrigin,
94
- randomOffset: Math.random() * 0.3,
95
- flickerPhase: Math.random() * Math.PI * 2, // Random starting point
96
- flickerSpeed: 0.8 + Math.random() * 0.4, // Random speed between 0.8 and 1.2
97
- });
98
112
  }
113
+ // Find max distance for normalization
114
+ maxDistance = Math.max(...dots.map((d) => d.distanceFromOrigin));
115
+ dotsRef.current = dots;
116
+ }
117
+ else {
118
+ // Update colors on existing dots
119
+ dots.forEach((dot) => {
120
+ dot.color = parsedColors[Math.floor(Math.random() * parsedColors.length)];
121
+ });
122
+ maxDistance = Math.max(...dots.map((d) => d.distanceFromOrigin));
99
123
  }
100
- // Find max distance for normalization
101
- const maxDistance = Math.max(...dots.map((d) => d.distanceFromOrigin));
102
124
  // Animation loop
103
125
  const startTime = Date.now();
126
+ // Bulge configuration
127
+ const bulgeEnabled = !!bulge;
128
+ const bulgeType = bulge?.type ?? "ripple"; // Default: ripple
129
+ const bulgeDuration = bulge?.duration ?? 3; // Default: 3 seconds per wave
130
+ const bulgeIntensity = bulge?.intensity ?? 10; // Default: 10px displacement
131
+ const bulgeRepeat = bulge?.repeat ?? true; // Default: true
132
+ const bulgeDelay = bulge?.delay ?? 0; // Default: 0ms
133
+ // Calculate center point for circular wave
134
+ const centerX = canvasWidth / 2;
135
+ const centerY = canvasHeight / 2;
136
+ const maxRadius = Math.sqrt(centerX * centerX + centerY * centerY);
104
137
  const animate = () => {
105
138
  ctx.clearRect(0, 0, canvasWidth, canvasHeight);
106
139
  const time = (Date.now() - startTime) / 1000; // Time in seconds
140
+ // Calculate circular wave radius (travels from center to edge)
141
+ let waveProgress = 0;
142
+ let showBulge = false;
143
+ let bulgeFadeOut = 1; // Multiplier for fading out bulge effect (1 = full effect, 0 = no effect)
144
+ if (bulgeEnabled) {
145
+ const bulgeElapsed = (Date.now() - bulgeStartTimeRef.current) / 1000;
146
+ const delaySeconds = bulgeDelay / 1000;
147
+ const totalCycleDuration = bulgeDuration + delaySeconds;
148
+ const adjustedTime = bulgeElapsed - delaySeconds;
149
+ const fadeStartPercent = 0.6; // Start fading at 60% of wave duration
150
+ if (adjustedTime >= 0) {
151
+ showBulge = true;
152
+ if (bulgeRepeat) {
153
+ // Continuous repeating wave
154
+ const cycleTime = adjustedTime % totalCycleDuration;
155
+ waveProgress = cycleTime < bulgeDuration ? cycleTime / bulgeDuration : 0;
156
+ showBulge = cycleTime < bulgeDuration; // Only show during active wave, not delay
157
+ }
158
+ else {
159
+ // Single wave with opacity fade during last 40%
160
+ if (adjustedTime <= bulgeDuration) {
161
+ waveProgress = adjustedTime / bulgeDuration;
162
+ // Start fading opacity at 60% of duration
163
+ if (waveProgress >= fadeStartPercent) {
164
+ const fadeProgress = (waveProgress - fadeStartPercent) / (1 - fadeStartPercent);
165
+ bulgeFadeOut = 1 - fadeProgress; // Fade from 1 to 0
166
+ }
167
+ }
168
+ else {
169
+ waveProgress = 0;
170
+ showBulge = false;
171
+ }
172
+ }
173
+ }
174
+ }
175
+ const waveRadius = waveProgress * maxRadius * 1.5; // Travel beyond edges for smooth cycle
107
176
  // For instant trigger, show all dots immediately at full opacity
108
177
  if (trigger === "instant") {
109
178
  dots.forEach((dot) => {
@@ -115,8 +184,74 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
115
184
  const flickerMultiplier = 0.6 + (flickerValue * 0.4); // Oscillate between 0.6 and 1.0
116
185
  opacity *= flickerMultiplier;
117
186
  }
118
- ctx.globalAlpha = opacity;
119
- ctx.fillRect(dot.x, dot.y, size, size);
187
+ // Calculate bulge displacement
188
+ let offsetX = 0;
189
+ let offsetY = 0;
190
+ let sizeMultiplier = 1;
191
+ let bulgeOpacity = 1;
192
+ if (bulgeEnabled && showBulge) {
193
+ if (bulgeType === "ripple") {
194
+ // Ripple: circular wave from center
195
+ const dx = dot.x - centerX;
196
+ const dy = dot.y - centerY;
197
+ const distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
198
+ const distanceToWave = Math.abs(distanceFromCenter - waveRadius);
199
+ const waveWidth = maxRadius * 0.15;
200
+ const distanceNorm = distanceToWave / waveWidth;
201
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 4);
202
+ const angle = Math.atan2(dy, dx);
203
+ const displacementAmount = waveFactor * bulgeIntensity * bulgeFadeOut;
204
+ offsetX = Math.cos(angle) * displacementAmount;
205
+ offsetY = Math.sin(angle) * displacementAmount;
206
+ sizeMultiplier = 1 + waveFactor * 0.8 * bulgeFadeOut;
207
+ const waveOpacity = 0.3 + waveFactor * 0.7;
208
+ bulgeOpacity = 1 + (waveOpacity - 1) * bulgeFadeOut;
209
+ }
210
+ else if (bulgeType === "wave") {
211
+ // Wave: Organic S-curve from bottom-left to top-right with rotation
212
+ const diagonalLength = Math.sqrt(canvasWidth * canvasWidth + canvasHeight * canvasHeight);
213
+ // Start wave off-screen before bottom-left, end off-screen after top-right
214
+ const wavePosOnDiagonal = (waveProgress * diagonalLength * 1.4) - (diagonalLength * 0.2);
215
+ // Calculate distance along the diagonal (from bottom-left to top-right)
216
+ const normalizedX = dot.x / canvasWidth;
217
+ const normalizedY = 1 - (dot.y / canvasHeight);
218
+ const dotDiagonalPos = (normalizedX + normalizedY) / 2 * diagonalLength;
219
+ // Distance from wave front
220
+ const distanceToWaveFront = dotDiagonalPos - wavePosOnDiagonal;
221
+ const waveWidth = diagonalLength * 0.25;
222
+ const distanceNorm = distanceToWaveFront / waveWidth;
223
+ // Smooth wave factor with wider influence
224
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 2.5);
225
+ // Create rotating S-curve with multiple frequency components
226
+ const perpendicularOffset = normalizedY - normalizedX;
227
+ // Primary S-curve that rotates over time
228
+ const rotationPhase = waveProgress * Math.PI * 3; // Rotates 1.5 times during animation
229
+ const primaryFreq = 3; // Number of S-curves along the wave
230
+ const sCurvePrimary = Math.sin(perpendicularOffset * Math.PI * primaryFreq + rotationPhase);
231
+ // Secondary curve for complexity (higher frequency, lower amplitude)
232
+ const secondaryFreq = 7;
233
+ const sCurveSecondary = Math.sin(perpendicularOffset * Math.PI * secondaryFreq - rotationPhase * 1.5) * 0.4;
234
+ // Combine curves for organic motion
235
+ const sCurveFactor = sCurvePrimary + sCurveSecondary;
236
+ // Wave straightens at the edges (distanceNorm affects curve strength)
237
+ const curveStrength = 1 - Math.abs(distanceNorm) * 0.5;
238
+ const modulatedCurve = sCurveFactor * curveStrength;
239
+ // Apply displacement
240
+ const baseDisplacement = waveFactor * bulgeIntensity * bulgeFadeOut;
241
+ const diagonalAngle = Math.PI / 4;
242
+ // Perpendicular displacement creates the S-curve
243
+ const perpAngle = diagonalAngle + Math.PI / 2;
244
+ offsetX = Math.cos(diagonalAngle) * baseDisplacement + Math.cos(perpAngle) * modulatedCurve * baseDisplacement * 0.8;
245
+ offsetY = -Math.sin(diagonalAngle) * baseDisplacement - Math.sin(perpAngle) * modulatedCurve * baseDisplacement * 0.8;
246
+ sizeMultiplier = 1 + waveFactor * 0.5 * bulgeFadeOut;
247
+ const waveOpacity = 0.4 + waveFactor * 0.6;
248
+ bulgeOpacity = 1 + (waveOpacity - 1) * bulgeFadeOut;
249
+ }
250
+ }
251
+ ctx.globalAlpha = opacity * bulgeOpacity;
252
+ const adjustedSize = size * sizeMultiplier;
253
+ const sizeOffset = (adjustedSize - size) / 2;
254
+ ctx.fillRect(dot.x + offsetX - sizeOffset, dot.y + offsetY - sizeOffset, adjustedSize, adjustedSize);
120
255
  });
121
256
  ctx.globalAlpha = 1;
122
257
  animationRef.current = requestAnimationFrame(animate);
@@ -149,9 +284,53 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
149
284
  }
150
285
  }
151
286
  if (opacity > 0) {
287
+ // Calculate bulge displacement
288
+ let offsetX = 0;
289
+ let offsetY = 0;
290
+ let sizeMultiplier = 1;
291
+ let bulgeOpacity = 1;
292
+ if (bulgeEnabled) {
293
+ if (bulgeType === "ripple") {
294
+ const dx = dot.x - centerX;
295
+ const dy = dot.y - centerY;
296
+ const distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
297
+ const distanceToWave = Math.abs(distanceFromCenter - waveRadius);
298
+ const waveWidth = maxRadius * 0.15;
299
+ const distanceNorm = distanceToWave / waveWidth;
300
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 4);
301
+ const angle = Math.atan2(dy, dx);
302
+ const displacementAmount = waveFactor * bulgeIntensity;
303
+ offsetX = Math.cos(angle) * displacementAmount;
304
+ offsetY = Math.sin(angle) * displacementAmount;
305
+ sizeMultiplier = 1 + waveFactor * 0.8;
306
+ bulgeOpacity = 0.3 + waveFactor * 0.7;
307
+ }
308
+ else if (bulgeType === "wave") {
309
+ const diagonalLength = Math.sqrt(canvasWidth * canvasWidth + canvasHeight * canvasHeight);
310
+ const wavePosOnDiagonal = waveProgress * diagonalLength * 1.2;
311
+ const normalizedX = dot.x / canvasWidth;
312
+ const normalizedY = 1 - (dot.y / canvasHeight);
313
+ const dotDiagonalPos = (normalizedX + normalizedY) / 2 * diagonalLength;
314
+ const distanceToWaveFront = dotDiagonalPos - wavePosOnDiagonal;
315
+ const waveWidth = diagonalLength * 0.2;
316
+ const distanceNorm = Math.abs(distanceToWaveFront) / waveWidth;
317
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 3);
318
+ const perpendicularOffset = normalizedY - normalizedX;
319
+ const sCurvePhase = perpendicularOffset * Math.PI * 2;
320
+ const sCurveFactor = Math.sin(sCurvePhase + waveProgress * Math.PI * 2);
321
+ const baseDisplacement = waveFactor * bulgeIntensity;
322
+ const diagonalAngle = Math.PI / 4;
323
+ offsetX = Math.cos(diagonalAngle) * baseDisplacement + sCurveFactor * baseDisplacement * 0.5;
324
+ offsetY = -Math.sin(diagonalAngle) * baseDisplacement + sCurveFactor * baseDisplacement * 0.5;
325
+ sizeMultiplier = 1 + waveFactor * 0.6;
326
+ bulgeOpacity = 0.4 + waveFactor * 0.6;
327
+ }
328
+ }
152
329
  ctx.fillStyle = dot.color;
153
- ctx.globalAlpha = opacity;
154
- ctx.fillRect(dot.x, dot.y, size, size);
330
+ ctx.globalAlpha = opacity * bulgeOpacity;
331
+ const adjustedSize = size * sizeMultiplier;
332
+ const sizeOffset = (adjustedSize - size) / 2;
333
+ ctx.fillRect(dot.x + offsetX - sizeOffset, dot.y + offsetY - sizeOffset, adjustedSize, adjustedSize);
155
334
  }
156
335
  });
157
336
  }
@@ -165,82 +344,194 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
165
344
  const flickerMultiplier = 0.6 + (flickerValue * 0.4);
166
345
  opacity *= flickerMultiplier;
167
346
  }
168
- ctx.globalAlpha = opacity;
169
- ctx.fillRect(dot.x, dot.y, size, size);
347
+ // Calculate bulge displacement
348
+ let offsetX = 0;
349
+ let offsetY = 0;
350
+ let sizeMultiplier = 1;
351
+ let bulgeOpacity = 1;
352
+ if (bulgeEnabled) {
353
+ if (bulgeType === "ripple") {
354
+ const dx = dot.x - centerX;
355
+ const dy = dot.y - centerY;
356
+ const distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
357
+ const distanceToWave = Math.abs(distanceFromCenter - waveRadius);
358
+ const waveWidth = maxRadius * 0.15;
359
+ const distanceNorm = distanceToWave / waveWidth;
360
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 4);
361
+ const angle = Math.atan2(dy, dx);
362
+ const displacementAmount = waveFactor * bulgeIntensity;
363
+ offsetX = Math.cos(angle) * displacementAmount;
364
+ offsetY = Math.sin(angle) * displacementAmount;
365
+ sizeMultiplier = 1 + waveFactor * 0.8;
366
+ bulgeOpacity = 0.3 + waveFactor * 0.7;
367
+ }
368
+ else if (bulgeType === "wave") {
369
+ const diagonalLength = Math.sqrt(canvasWidth * canvasWidth + canvasHeight * canvasHeight);
370
+ const wavePosOnDiagonal = waveProgress * diagonalLength * 1.2;
371
+ const normalizedX = dot.x / canvasWidth;
372
+ const normalizedY = 1 - (dot.y / canvasHeight);
373
+ const dotDiagonalPos = (normalizedX + normalizedY) / 2 * diagonalLength;
374
+ const distanceToWaveFront = dotDiagonalPos - wavePosOnDiagonal;
375
+ const waveWidth = diagonalLength * 0.2;
376
+ const distanceNorm = Math.abs(distanceToWaveFront) / waveWidth;
377
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 3);
378
+ const perpendicularOffset = normalizedY - normalizedX;
379
+ const sCurvePhase = perpendicularOffset * Math.PI * 2;
380
+ const sCurveFactor = Math.sin(sCurvePhase + waveProgress * Math.PI * 2);
381
+ const baseDisplacement = waveFactor * bulgeIntensity;
382
+ const diagonalAngle = Math.PI / 4;
383
+ offsetX = Math.cos(diagonalAngle) * baseDisplacement + sCurveFactor * baseDisplacement * 0.5;
384
+ offsetY = -Math.sin(diagonalAngle) * baseDisplacement + sCurveFactor * baseDisplacement * 0.5;
385
+ sizeMultiplier = 1 + waveFactor * 0.6;
386
+ bulgeOpacity = 0.4 + waveFactor * 0.6;
387
+ }
388
+ }
389
+ ctx.globalAlpha = opacity * bulgeOpacity;
390
+ const adjustedSize = size * sizeMultiplier;
391
+ const sizeOffset = (adjustedSize - size) / 2;
392
+ ctx.fillRect(dot.x + offsetX - sizeOffset, dot.y + offsetY - sizeOffset, adjustedSize, adjustedSize);
170
393
  });
171
394
  }
172
395
  ctx.globalAlpha = 1;
173
396
  animationRef.current = requestAnimationFrame(animate);
174
397
  return;
175
398
  }
176
- // For hover trigger with animation
177
- if (isHoveredRef.current) {
178
- // Revealing animation with explosive easing
179
- const now = Date.now();
180
- const elapsed = (now - revealStartTimeRef.current) / 1000;
181
- // Cubic easing: starts very slow, then explodes
182
- const revealProgress = Math.pow(elapsed, 3) * speed * 3;
183
- // Cap progress to prevent infinite growth (max offset is ~1.3)
184
- const cappedProgress = Math.min(revealProgress, 2.0);
185
- // Track maximum progress for reverse animation
186
- maxRevealProgressRef.current = cappedProgress;
187
- dots.forEach((dot) => {
188
- const normalizedDistance = dot.distanceFromOrigin / maxDistance;
189
- const introOffset = normalizedDistance * 0.8 + dot.randomOffset * 0.5; // Much lower threshold
190
- let opacity = 0;
191
- if (cappedProgress > introOffset) {
192
- // Explosive opacity increase
193
- const fadeIn = (cappedProgress - introOffset) * 8; // Faster fade-in
194
- opacity = Math.min(1, fadeIn * fadeIn) * dot.baseOpacity;
195
- // Apply flicker effect if enabled
196
- if (flicker) {
197
- const flickerValue = Math.sin(time * dot.flickerSpeed * 3 + dot.flickerPhase);
198
- const flickerMultiplier = 0.6 + (flickerValue * 0.4); // Oscillate between 0.6 and 1.0
199
- opacity *= flickerMultiplier;
399
+ // For hover, click, and manual triggers with animation
400
+ if (trigger === "hover" || trigger === "click" || trigger === "manual") {
401
+ if (isHoveredRef.current) {
402
+ // Revealing animation with explosive easing
403
+ const now = Date.now();
404
+ const elapsed = (now - revealStartTimeRef.current) / 1000;
405
+ // Cubic easing: starts very slow, then explodes
406
+ const revealProgress = Math.pow(elapsed, 3) * speed * 3;
407
+ // Cap progress to prevent infinite growth (max offset is ~1.3)
408
+ const cappedProgress = Math.min(revealProgress, 2.0);
409
+ // Track maximum progress for reverse animation
410
+ maxRevealProgressRef.current = cappedProgress;
411
+ dots.forEach((dot) => {
412
+ const normalizedDistance = dot.distanceFromOrigin / maxDistance;
413
+ const introOffset = normalizedDistance * 0.8 + dot.randomOffset * 0.5; // Much lower threshold
414
+ let opacity = 0;
415
+ if (cappedProgress > introOffset) {
416
+ // Explosive opacity increase
417
+ const fadeIn = (cappedProgress - introOffset) * 8; // Faster fade-in
418
+ opacity = Math.min(1, fadeIn * fadeIn) * dot.baseOpacity;
419
+ // Apply flicker effect if enabled
420
+ if (flicker) {
421
+ const flickerValue = Math.sin(time * dot.flickerSpeed * 3 + dot.flickerPhase);
422
+ const flickerMultiplier = 0.6 + (flickerValue * 0.4); // Oscillate between 0.6 and 1.0
423
+ opacity *= flickerMultiplier;
424
+ }
200
425
  }
201
- }
202
- if (opacity > 0) {
203
- ctx.fillStyle = dot.color;
204
- ctx.globalAlpha = opacity;
205
- ctx.fillRect(dot.x, dot.y, size, size);
206
- }
207
- });
208
- }
209
- else {
210
- // Reverse animation when hover ends - only run if there's something to hide
211
- if (hideStartProgressRef.current > 0) {
212
- const elapsed = (Date.now() - hideStartTimeRef.current) / 1000;
213
- // Use quadratic easing for faster hide (power of 2)
214
- const hideSpeed = speed * 6; // Faster to handle max progress of 2.0
215
- const hideProgress = Math.pow(elapsed, 2) * hideSpeed;
216
- // Reverse from the progress we had when hide started
217
- const reverseProgress = Math.max(0, hideStartProgressRef.current - hideProgress);
218
- if (reverseProgress > 0.01) { // Small threshold to ensure completion
219
- // Still have dots to hide
220
- dots.forEach((dot) => {
221
- const normalizedDistance = dot.distanceFromOrigin / maxDistance;
222
- const introOffset = normalizedDistance * 0.8 + dot.randomOffset * 0.5;
223
- let opacity = 0;
224
- if (reverseProgress > introOffset) {
225
- const fadeIn = (reverseProgress - introOffset) * 8;
226
- opacity = Math.min(1, fadeIn * fadeIn) * dot.baseOpacity;
227
- // Apply flicker effect if enabled
228
- if (flicker) {
229
- const flickerValue = Math.sin(time * dot.flickerSpeed * 3 + dot.flickerPhase);
230
- const flickerMultiplier = 0.6 + (flickerValue * 0.4);
231
- opacity *= flickerMultiplier;
426
+ if (opacity > 0) {
427
+ // Calculate bulge displacement
428
+ let offsetX = 0;
429
+ let offsetY = 0;
430
+ let sizeMultiplier = 1;
431
+ let bulgeOpacity = 1;
432
+ if (bulgeEnabled) {
433
+ if (bulgeType === "ripple") {
434
+ const dx = dot.x - centerX;
435
+ const dy = dot.y - centerY;
436
+ const distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
437
+ const distanceToWave = Math.abs(distanceFromCenter - waveRadius);
438
+ const waveWidth = maxRadius * 0.15;
439
+ const distanceNorm = distanceToWave / waveWidth;
440
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 4);
441
+ const angle = Math.atan2(dy, dx);
442
+ const displacementAmount = waveFactor * bulgeIntensity;
443
+ offsetX = Math.cos(angle) * displacementAmount;
444
+ offsetY = Math.sin(angle) * displacementAmount;
445
+ sizeMultiplier = 1 + waveFactor * 0.8;
446
+ bulgeOpacity = 0.3 + waveFactor * 0.7;
447
+ }
448
+ else if (bulgeType === "wave") {
449
+ const diagonalLength = Math.sqrt(canvasWidth * canvasWidth + canvasHeight * canvasHeight);
450
+ const wavePosOnDiagonal = waveProgress * diagonalLength * 1.2;
451
+ const normalizedX = dot.x / canvasWidth;
452
+ const normalizedY = 1 - (dot.y / canvasHeight);
453
+ const dotDiagonalPos = (normalizedX + normalizedY) / 2 * diagonalLength;
454
+ const distanceToWaveFront = dotDiagonalPos - wavePosOnDiagonal;
455
+ const waveWidth = diagonalLength * 0.2;
456
+ const distanceNorm = Math.abs(distanceToWaveFront) / waveWidth;
457
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 3);
458
+ const perpendicularOffset = normalizedY - normalizedX;
459
+ const sCurvePhase = perpendicularOffset * Math.PI * 2;
460
+ const sCurveFactor = Math.sin(sCurvePhase + waveProgress * Math.PI * 2);
461
+ const baseDisplacement = waveFactor * bulgeIntensity;
462
+ const diagonalAngle = Math.PI / 4;
463
+ offsetX = Math.cos(diagonalAngle) * baseDisplacement + sCurveFactor * baseDisplacement * 0.5;
464
+ offsetY = -Math.sin(diagonalAngle) * baseDisplacement + sCurveFactor * baseDisplacement * 0.5;
465
+ sizeMultiplier = 1 + waveFactor * 0.6;
466
+ bulgeOpacity = 0.4 + waveFactor * 0.6;
232
467
  }
233
468
  }
234
- if (opacity > 0) {
235
- ctx.fillStyle = dot.color;
236
- ctx.globalAlpha = opacity;
237
- ctx.fillRect(dot.x, dot.y, size, size);
238
- }
239
- });
240
- }
241
- else {
242
- // Hide animation complete, reset progress and clear canvas
243
- hideStartProgressRef.current = 0;
469
+ ctx.fillStyle = dot.color;
470
+ ctx.globalAlpha = opacity * bulgeOpacity;
471
+ const adjustedSize = size * sizeMultiplier;
472
+ const sizeOffset = (adjustedSize - size) / 2;
473
+ ctx.fillRect(dot.x + offsetX - sizeOffset, dot.y + offsetY - sizeOffset, adjustedSize, adjustedSize);
474
+ }
475
+ });
476
+ }
477
+ else {
478
+ // Reverse animation when hover ends - only run if there's something to hide
479
+ if (hideStartProgressRef.current > 0) {
480
+ const elapsed = (Date.now() - hideStartTimeRef.current) / 1000;
481
+ // Use quadratic easing for faster hide (power of 2)
482
+ const hideSpeed = speed * 6; // Faster to handle max progress of 2.0
483
+ const hideProgress = Math.pow(elapsed, 2) * hideSpeed;
484
+ // Reverse from the progress we had when hide started
485
+ const reverseProgress = Math.max(0, hideStartProgressRef.current - hideProgress);
486
+ if (reverseProgress > 0.01) { // Small threshold to ensure completion
487
+ // Still have dots to hide
488
+ dots.forEach((dot) => {
489
+ const normalizedDistance = dot.distanceFromOrigin / maxDistance;
490
+ const introOffset = normalizedDistance * 0.8 + dot.randomOffset * 0.5;
491
+ let opacity = 0;
492
+ if (reverseProgress > introOffset) {
493
+ const fadeIn = (reverseProgress - introOffset) * 8;
494
+ opacity = Math.min(1, fadeIn * fadeIn) * dot.baseOpacity;
495
+ // Apply flicker effect if enabled
496
+ if (flicker) {
497
+ const flickerValue = Math.sin(time * dot.flickerSpeed * 3 + dot.flickerPhase);
498
+ const flickerMultiplier = 0.6 + (flickerValue * 0.4);
499
+ opacity *= flickerMultiplier;
500
+ }
501
+ }
502
+ if (opacity > 0) {
503
+ // Calculate bulge displacement
504
+ let offsetX = 0;
505
+ let offsetY = 0;
506
+ let sizeMultiplier = 1;
507
+ let bulgeOpacity = 1;
508
+ if (bulgeEnabled) {
509
+ const dx = dot.x - centerX;
510
+ const dy = dot.y - centerY;
511
+ const distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
512
+ const distanceToWave = Math.abs(distanceFromCenter - waveRadius);
513
+ const waveWidth = maxRadius * 0.15;
514
+ const distanceNorm = distanceToWave / waveWidth;
515
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 4);
516
+ const angle = Math.atan2(dy, dx);
517
+ const displacementAmount = waveFactor * bulgeIntensity;
518
+ offsetX = Math.cos(angle) * displacementAmount * 0.3;
519
+ offsetY = Math.sin(angle) * displacementAmount * 0.3 - waveFactor * bulgeIntensity;
520
+ sizeMultiplier = 1 + waveFactor * 0.8;
521
+ bulgeOpacity = 0.3 + waveFactor * 0.7;
522
+ }
523
+ ctx.fillStyle = dot.color;
524
+ ctx.globalAlpha = opacity * bulgeOpacity;
525
+ const adjustedSize = size * sizeMultiplier;
526
+ const sizeOffset = (adjustedSize - size) / 2;
527
+ ctx.fillRect(dot.x + offsetX - sizeOffset, dot.y + offsetY - sizeOffset, adjustedSize, adjustedSize);
528
+ }
529
+ });
530
+ }
531
+ else {
532
+ // Hide animation complete, reset progress and clear canvas
533
+ hideStartProgressRef.current = 0;
534
+ }
244
535
  }
245
536
  }
246
537
  }
@@ -254,7 +545,42 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
254
545
  cancelAnimationFrame(animationRef.current);
255
546
  }
256
547
  };
257
- }, [colors, size, spacing, speed, revealFrom, trigger, flicker]);
548
+ }, [colors, size, spacing, speed, revealFrom, trigger, flicker, bulge]);
549
+ // Manual trigger control via `active` prop
550
+ useEffect(() => {
551
+ if (trigger !== "manual")
552
+ return;
553
+ const now = Date.now();
554
+ if (active) {
555
+ // Mimic mouse enter
556
+ if (hideStartProgressRef.current > 0) {
557
+ const hideElapsed = (now - hideStartTimeRef.current) / 1000;
558
+ const hideSpeed = speed * 6;
559
+ const hideProgress = Math.pow(hideElapsed, 2) * hideSpeed;
560
+ const currentProgress = Math.max(0, hideStartProgressRef.current - hideProgress);
561
+ const effectiveElapsed = Math.pow(currentProgress / (speed * 3), 1 / 3);
562
+ const simulatedStartTime = now - effectiveElapsed * 1000;
563
+ revealStartTimeRef.current = simulatedStartTime;
564
+ }
565
+ else {
566
+ revealStartTimeRef.current = now;
567
+ }
568
+ if (bulge && !bulge.repeat) {
569
+ bulgeStartTimeRef.current = now;
570
+ }
571
+ isHoveredRef.current = true;
572
+ hideStartProgressRef.current = 0;
573
+ }
574
+ else {
575
+ // Mimic mouse leave
576
+ if (isHoveredRef.current) {
577
+ const currentProgress = maxRevealProgressRef.current;
578
+ hideStartTimeRef.current = Date.now();
579
+ hideStartProgressRef.current = currentProgress;
580
+ isHoveredRef.current = false;
581
+ }
582
+ }
583
+ }, [active, trigger, speed, bulge]);
258
584
  const handleMouseEnter = () => {
259
585
  if (trigger === "hover" && !isHoveredRef.current) {
260
586
  const now = Date.now();
@@ -275,6 +601,10 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
275
601
  // Fresh start
276
602
  revealStartTimeRef.current = now;
277
603
  }
604
+ // Reset bulge animation on each hover when repeat is false
605
+ if (bulge && !bulge.repeat) {
606
+ bulgeStartTimeRef.current = now;
607
+ }
278
608
  isHoveredRef.current = true;
279
609
  hideStartProgressRef.current = 0; // Clear hide state
280
610
  }
@@ -293,7 +623,39 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
293
623
  isHoveredRef.current = false;
294
624
  }
295
625
  };
296
- return (_jsxs(Flex, { ref: containerRef, fill: true, overflow: "hidden", onMouseEnter: handleMouseEnter, onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, ...rest, children: [_jsx("canvas", { ref: canvasRef, style: {
626
+ const handleClick = () => {
627
+ if (trigger !== "click")
628
+ return;
629
+ if (!isHoveredRef.current) {
630
+ // Enter
631
+ const now = Date.now();
632
+ if (hideStartProgressRef.current > 0) {
633
+ const hideElapsed = (now - hideStartTimeRef.current) / 1000;
634
+ const hideSpeed = speed * 6;
635
+ const hideProgress = Math.pow(hideElapsed, 2) * hideSpeed;
636
+ const currentProgress = Math.max(0, hideStartProgressRef.current - hideProgress);
637
+ const effectiveElapsed = Math.pow(currentProgress / (speed * 3), 1 / 3);
638
+ const simulatedStartTime = now - effectiveElapsed * 1000;
639
+ revealStartTimeRef.current = simulatedStartTime;
640
+ }
641
+ else {
642
+ revealStartTimeRef.current = now;
643
+ }
644
+ if (bulge && !bulge.repeat) {
645
+ bulgeStartTimeRef.current = now;
646
+ }
647
+ isHoveredRef.current = true;
648
+ hideStartProgressRef.current = 0;
649
+ }
650
+ else {
651
+ // Leave
652
+ const currentProgress = maxRevealProgressRef.current;
653
+ hideStartTimeRef.current = Date.now();
654
+ hideStartProgressRef.current = currentProgress;
655
+ isHoveredRef.current = false;
656
+ }
657
+ };
658
+ return (_jsxs(Flex, { ref: containerRef, fill: true, overflow: "hidden", onMouseEnter: handleMouseEnter, onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, onClick: handleClick, ...rest, children: [_jsx("canvas", { ref: canvasRef, style: {
297
659
  position: "absolute",
298
660
  top: 0,
299
661
  left: 0,