@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
@@ -0,0 +1,769 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React, { useEffect, useRef } from "react";
4
+ import { Flex } from ".";
5
+ const WeatherFx = React.forwardRef(({ type = "rain", speed = 1, colors = ["brand-solid-medium"], intensity = 50, angle = 0, duration, trigger = "mount", active = false, children, ...rest }, forwardedRef) => {
6
+ const containerRef = useRef(null);
7
+ const canvasRef = useRef(null);
8
+ const animationRef = useRef(undefined);
9
+ const particlesRef = useRef([]);
10
+ const timeRef = useRef(0);
11
+ const isEmittingRef = useRef(trigger === "mount" || (trigger === "manual" && active));
12
+ const emitStartTimeRef = useRef(Date.now());
13
+ const isHoveredRef = useRef(false);
14
+ const lastLightningTimeRef = useRef(0);
15
+ const lastBoltCountRef = useRef(0);
16
+ useEffect(() => {
17
+ if (forwardedRef) {
18
+ if ("current" in forwardedRef) {
19
+ forwardedRef.current = containerRef.current;
20
+ }
21
+ else if (typeof forwardedRef === "function") {
22
+ forwardedRef(containerRef.current);
23
+ }
24
+ }
25
+ }, [forwardedRef]);
26
+ useEffect(() => {
27
+ const canvas = canvasRef.current;
28
+ const container = containerRef.current;
29
+ if (!canvas || !container)
30
+ return;
31
+ const ctx = canvas.getContext("2d");
32
+ if (!ctx)
33
+ return;
34
+ // Set canvas size
35
+ let canvasWidth = 0;
36
+ let canvasHeight = 0;
37
+ const updateSize = () => {
38
+ const rect = container.getBoundingClientRect();
39
+ canvasWidth = rect.width;
40
+ canvasHeight = rect.height;
41
+ canvas.width = rect.width * 2; // 2x for retina
42
+ canvas.height = rect.height * 2;
43
+ canvas.style.width = `${rect.width}px`;
44
+ canvas.style.height = `${rect.height}px`;
45
+ ctx.scale(2, 2); // Scale for retina
46
+ };
47
+ updateSize();
48
+ window.addEventListener("resize", updateSize);
49
+ // Parse colors - convert token names to CSS variables
50
+ const parsedColors = colors.map((color) => {
51
+ const computedColor = getComputedStyle(container).getPropertyValue(`--${color}`);
52
+ return computedColor || color;
53
+ });
54
+ // Initialize particles based on type
55
+ const initializeRain = () => {
56
+ const particles = [];
57
+ // Calculate spawn area based on angle
58
+ const angleRad = (angle * Math.PI) / 180;
59
+ const horizontalOffset = Math.abs(Math.tan(angleRad) * canvasHeight);
60
+ // Create initial rain drops
61
+ for (let i = 0; i < intensity; i++) {
62
+ // Expand spawn width to account for angle
63
+ const spawnWidth = canvasWidth + horizontalOffset * 2;
64
+ const spawnX = Math.random() * spawnWidth - horizontalOffset;
65
+ particles.push({
66
+ x: spawnX,
67
+ y: Math.random() * canvasHeight - canvasHeight, // Start above canvas
68
+ length: 10 + Math.random() * 20, // Varying lengths
69
+ speed: (2 + Math.random() * 3) * speed, // Varying speeds
70
+ color: parsedColors[Math.floor(Math.random() * parsedColors.length)],
71
+ opacity: 0.3 + Math.random() * 0.5,
72
+ thickness: 1 + Math.random() * 1.5,
73
+ });
74
+ }
75
+ return particles;
76
+ };
77
+ // Initialize snow particles
78
+ const initializeSnow = () => {
79
+ const particles = [];
80
+ // Calculate spawn area based on angle
81
+ const angleRad = (angle * Math.PI) / 180;
82
+ const horizontalOffset = Math.abs(Math.tan(angleRad) * canvasHeight);
83
+ for (let i = 0; i < intensity; i++) {
84
+ const depth = Math.random(); // 0 = far, 1 = near
85
+ const size = 2 + depth * 4; // 2-6px, bigger = closer
86
+ // Expand spawn width to account for angle
87
+ const spawnWidth = canvasWidth + horizontalOffset * 2;
88
+ const spawnX = Math.random() * spawnWidth - horizontalOffset;
89
+ particles.push({
90
+ x: spawnX,
91
+ y: Math.random() * canvasHeight - canvasHeight,
92
+ size,
93
+ speed: (0.3 + depth * 0.7) * speed, // 0.3-1.0x speed, closer = faster
94
+ color: parsedColors[Math.floor(Math.random() * parsedColors.length)],
95
+ opacity: 0.4 + depth * 0.5, // 0.4-0.9, closer = more opaque
96
+ swayAmplitude: 20 + Math.random() * 30, // 20-50px horizontal sway
97
+ swaySpeed: 0.5 + Math.random() * 1, // Varying sway speeds
98
+ swayOffset: Math.random() * Math.PI * 2, // Random starting phase
99
+ depth,
100
+ });
101
+ }
102
+ return particles;
103
+ };
104
+ // Initialize leaves particles
105
+ const initializeLeaves = () => {
106
+ const particles = [];
107
+ // Calculate spawn area based on angle
108
+ const angleRad = (angle * Math.PI) / 180;
109
+ const horizontalOffset = Math.abs(Math.tan(angleRad) * canvasHeight);
110
+ for (let i = 0; i < intensity; i++) {
111
+ const depth = Math.random(); // 0 = far, 1 = near
112
+ const width = 8 + depth * 12; // 8-20px, bigger = closer
113
+ const height = width * (0.6 + Math.random() * 0.4); // Leaf aspect ratio
114
+ // Expand spawn width to account for angle
115
+ const spawnWidth = canvasWidth + horizontalOffset * 2;
116
+ const spawnX = Math.random() * spawnWidth - horizontalOffset;
117
+ // Pick two colors for gradient (either from same color or adjacent colors)
118
+ const colorIndex = Math.floor(Math.random() * parsedColors.length);
119
+ const color1 = parsedColors[colorIndex];
120
+ const color2 = parsedColors[Math.min(colorIndex + 1, parsedColors.length - 1)];
121
+ particles.push({
122
+ x: spawnX,
123
+ y: Math.random() * canvasHeight - canvasHeight,
124
+ width,
125
+ height,
126
+ speed: (0.4 + depth * 0.8) * speed, // 0.4-1.2x speed
127
+ color1,
128
+ color2,
129
+ opacity: 0.6 + depth * 0.3, // 0.6-0.9
130
+ swayAmplitude: 30 + Math.random() * 50, // 30-80px stronger sway
131
+ swaySpeed: 0.3 + Math.random() * 0.7, // Slower sway for leaves
132
+ swayOffset: Math.random() * Math.PI * 2,
133
+ rotation: Math.random() * Math.PI * 2, // Random starting rotation
134
+ rotationSpeed: (Math.random() - 0.5) * 0.08, // -0.04 to 0.04 rad/frame
135
+ rotation3D: Math.random() * Math.PI * 2, // Random 3D flip angle
136
+ rotation3DSpeed: (Math.random() - 0.5) * 0.06, // 3D tumbling speed
137
+ depth,
138
+ });
139
+ }
140
+ return particles;
141
+ };
142
+ // Recursive function to generate chaotic, organic branches
143
+ const generateBranch = (startX, startY, baseSegmentLength, branchLevel, maxLevel, angle, parentThickness) => {
144
+ const segments = [];
145
+ const children = [];
146
+ // Longer branches that decrease more gradually with depth
147
+ const lengthMultiplier = Math.pow(0.7, branchLevel);
148
+ const branchLength = 3 + Math.floor(Math.random() * 4); // 3-6 segments
149
+ let currentX = startX;
150
+ let currentY = startY;
151
+ let currentAngle = angle;
152
+ for (let i = 0; i < branchLength; i++) {
153
+ // Larger segments for longer branches
154
+ const segmentLength = baseSegmentLength * lengthMultiplier * (0.6 + Math.random() * 0.6);
155
+ // Add angular variation for organic chaos
156
+ currentAngle += (Math.random() - 0.5) * 0.8;
157
+ // Move in the current angle direction
158
+ currentX += Math.cos(currentAngle) * segmentLength;
159
+ currentY += Math.sin(currentAngle) * segmentLength;
160
+ segments.push({ x: currentX, y: currentY });
161
+ // Moderate probability of branching
162
+ if (branchLevel < maxLevel && i > 0) {
163
+ // Only primary branches get multiple attempts
164
+ const branchAttempts = branchLevel === 1 ? 1 : 1;
165
+ for (let attempt = 0; attempt < branchAttempts; attempt++) {
166
+ // Lower probability: 35% at level 1, 25% at level 2, etc.
167
+ const branchProbability = 0.35 - branchLevel * 0.1;
168
+ if (Math.random() < branchProbability) {
169
+ // Random angle deviation from current direction (-90 to +90 degrees)
170
+ const angleDeviation = (Math.random() - 0.5) * Math.PI;
171
+ const childAngle = currentAngle + angleDeviation;
172
+ const childBranch = generateBranch(currentX, currentY, baseSegmentLength, branchLevel + 1, maxLevel, childAngle, parentThickness);
173
+ if (childBranch.segments.length > 0) {
174
+ children.push({
175
+ startIndex: i,
176
+ segments: childBranch.segments,
177
+ thickness: parentThickness * Math.pow(0.6, branchLevel + 1),
178
+ children: childBranch.children,
179
+ });
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ return { segments, children };
186
+ };
187
+ // Generate lightning bolt with chaotic branches
188
+ const generateLightningBolt = (startX, startY, endY) => {
189
+ const segments = [];
190
+ const branches = [];
191
+ let currentX = startX;
192
+ let currentY = startY;
193
+ const baseSegmentLength = 12 + Math.random() * 15; // Smaller segments for detail
194
+ const mainThickness = 2.5 + Math.random() * 1.5;
195
+ segments.push({ x: currentX, y: currentY });
196
+ // Main bolt with more erratic path
197
+ let currentAngle = Math.PI / 2; // Start going down (90 degrees)
198
+ while (currentY < endY) {
199
+ // Vary segment length significantly
200
+ const segmentLength = baseSegmentLength * (0.5 + Math.random() * 0.8);
201
+ // Add significant angular variation for jagged appearance
202
+ currentAngle += (Math.random() - 0.5) * 0.6;
203
+ // Ensure we're generally moving downward
204
+ if (currentAngle < Math.PI / 4)
205
+ currentAngle = Math.PI / 4; // Min 45 degrees
206
+ if (currentAngle > (3 * Math.PI) / 4)
207
+ currentAngle = (3 * Math.PI) / 4; // Max 135 degrees
208
+ currentX += Math.cos(currentAngle) * segmentLength;
209
+ currentY += Math.sin(currentAngle) * segmentLength;
210
+ segments.push({ x: currentX, y: currentY });
211
+ // Moderate chance of branching (35%) for balanced appearance
212
+ if (Math.random() < 0.35 && segments.length > 2) {
213
+ // Random angle deviation (-120 to +120 degrees from current)
214
+ const angleDeviation = (Math.random() - 0.5) * (2 * Math.PI / 3);
215
+ const branchAngle = currentAngle + angleDeviation;
216
+ const primaryBranch = generateBranch(currentX, currentY, baseSegmentLength, 1, 3, // Max 3 levels to reduce chaos
217
+ branchAngle, mainThickness);
218
+ if (primaryBranch.segments.length > 0) {
219
+ branches.push({
220
+ startIndex: segments.length - 1,
221
+ segments: primaryBranch.segments,
222
+ thickness: mainThickness * 0.6,
223
+ children: primaryBranch.children,
224
+ });
225
+ }
226
+ }
227
+ }
228
+ return {
229
+ x: startX,
230
+ y: startY,
231
+ segments,
232
+ color: parsedColors[Math.floor(Math.random() * parsedColors.length)],
233
+ opacity: 0.8 + Math.random() * 0.2,
234
+ thickness: mainThickness,
235
+ lifetime: 0.4 + Math.random() * 0.2,
236
+ age: 0,
237
+ branches,
238
+ revealDuration: 0.08 + Math.random() * 0.04,
239
+ };
240
+ };
241
+ // Initialize lightning (empty array, lightning spawns dynamically)
242
+ const initializeLightning = () => {
243
+ return [];
244
+ };
245
+ // Initialize particles only for mount trigger (manual/click handled by their respective handlers)
246
+ // Only initialize if particles don't already exist to preserve active particles across re-renders
247
+ if (particlesRef.current.length === 0) {
248
+ const shouldInitialize = trigger === "mount";
249
+ particlesRef.current = shouldInitialize
250
+ ? (type === "rain" ? initializeRain() :
251
+ type === "snow" ? initializeSnow() :
252
+ type === "leaves" ? initializeLeaves() :
253
+ type === "lightning" ? initializeLightning() :
254
+ [])
255
+ : [];
256
+ }
257
+ // Animation loop
258
+ const angleRad = (angle * Math.PI) / 180; // Convert angle to radians
259
+ const dx = Math.sin(angleRad); // Horizontal component
260
+ const dy = Math.cos(angleRad); // Vertical component
261
+ const horizontalOffset = Math.abs(Math.tan(angleRad) * canvasHeight);
262
+ const animate = () => {
263
+ timeRef.current += 0.016; // Approximate frame time
264
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
265
+ // Check if we should still be emitting new particles
266
+ const shouldEmit = trigger === "hover" ? isHoveredRef.current : isEmittingRef.current;
267
+ // Check duration limit
268
+ if (duration && isEmittingRef.current) {
269
+ const elapsed = (Date.now() - emitStartTimeRef.current) / 1000;
270
+ if (elapsed > duration) {
271
+ isEmittingRef.current = false;
272
+ }
273
+ }
274
+ if (type === "rain") {
275
+ // Update and draw rain drops
276
+ particlesRef.current.forEach((drop) => {
277
+ // Update position with angle
278
+ drop.y += drop.speed * dy;
279
+ drop.x += drop.speed * dx;
280
+ // Reset if it goes below canvas or off the sides (with expanded bounds)
281
+ const leftBound = -horizontalOffset - 50;
282
+ const rightBound = canvasWidth + horizontalOffset + 50;
283
+ if (drop.y > canvasHeight || drop.x < leftBound || drop.x > rightBound) {
284
+ if (shouldEmit) {
285
+ // Respawn at top
286
+ drop.y = -drop.length;
287
+ const spawnWidth = canvasWidth + horizontalOffset * 2;
288
+ drop.x = Math.random() * spawnWidth - horizontalOffset;
289
+ }
290
+ else {
291
+ // Mark for removal by moving far off screen
292
+ drop.y = canvasHeight + 1000;
293
+ }
294
+ }
295
+ // Draw rain drop as an angled line with gradient
296
+ const x1 = drop.x;
297
+ const y1 = drop.y;
298
+ const x2 = drop.x + dx * drop.length;
299
+ const y2 = drop.y + dy * drop.length;
300
+ // Create gradient from solid to transparent
301
+ const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
302
+ gradient.addColorStop(0, 'transparent');
303
+ gradient.addColorStop(1, drop.color);
304
+ ctx.beginPath();
305
+ ctx.strokeStyle = gradient;
306
+ ctx.globalAlpha = drop.opacity;
307
+ ctx.lineWidth = drop.thickness;
308
+ ctx.lineCap = "round";
309
+ ctx.moveTo(x1, y1);
310
+ ctx.lineTo(x2, y2);
311
+ ctx.stroke();
312
+ });
313
+ }
314
+ else if (type === "snow") {
315
+ // Update and draw snowflakes
316
+ particlesRef.current.forEach((flake) => {
317
+ // Calculate sway motion (sine wave)
318
+ const swayX = Math.sin(timeRef.current * flake.swaySpeed + flake.swayOffset) * flake.swayAmplitude;
319
+ // Update position with angle and sway
320
+ flake.y += flake.speed * dy;
321
+ flake.x += flake.speed * dx;
322
+ // Reset if it goes below canvas or off the sides
323
+ const leftBound = -horizontalOffset - 100;
324
+ const rightBound = canvasWidth + horizontalOffset + 100;
325
+ if (flake.y > canvasHeight || flake.x < leftBound || flake.x > rightBound) {
326
+ if (shouldEmit) {
327
+ flake.y = -flake.size;
328
+ const spawnWidth = canvasWidth + horizontalOffset * 2;
329
+ flake.x = Math.random() * spawnWidth - horizontalOffset;
330
+ }
331
+ else {
332
+ flake.y = canvasHeight + 1000;
333
+ }
334
+ }
335
+ // Draw snowflake (no blur filter for performance)
336
+ ctx.globalAlpha = flake.opacity;
337
+ ctx.fillStyle = flake.color;
338
+ // Draw as circle with sway applied
339
+ ctx.beginPath();
340
+ ctx.arc(flake.x + swayX, flake.y, flake.size / 2, 0, Math.PI * 2);
341
+ ctx.fill();
342
+ });
343
+ }
344
+ else if (type === "leaves") {
345
+ // Update and draw leaves
346
+ particlesRef.current.forEach((leaf) => {
347
+ // Calculate sway motion
348
+ const swayX = Math.sin(timeRef.current * leaf.swaySpeed + leaf.swayOffset) * leaf.swayAmplitude;
349
+ // Update rotation (2D spin)
350
+ leaf.rotation += leaf.rotationSpeed;
351
+ // Update 3D rotation (flip/tumble)
352
+ leaf.rotation3D += leaf.rotation3DSpeed;
353
+ // Update position with angle and sway
354
+ leaf.y += leaf.speed * dy;
355
+ leaf.x += leaf.speed * dx;
356
+ // Reset if it goes below canvas or off the sides
357
+ const leftBound = -horizontalOffset - 100;
358
+ const rightBound = canvasWidth + horizontalOffset + 100;
359
+ if (leaf.y > canvasHeight || leaf.x < leftBound || leaf.x > rightBound) {
360
+ if (shouldEmit) {
361
+ leaf.y = -leaf.height;
362
+ const spawnWidth = canvasWidth + horizontalOffset * 2;
363
+ leaf.x = Math.random() * spawnWidth - horizontalOffset;
364
+ leaf.rotation = Math.random() * Math.PI * 2;
365
+ }
366
+ else {
367
+ leaf.y = canvasHeight + 1000;
368
+ }
369
+ }
370
+ // Calculate 3D scale effect (flipping around vertical axis)
371
+ const scaleXRaw = Math.cos(leaf.rotation3D);
372
+ // Clamp scale to minimum 0.15 so leaves stay visible when edge-on
373
+ const scaleX = scaleXRaw > 0
374
+ ? Math.max(0.15, scaleXRaw)
375
+ : Math.min(-0.15, scaleXRaw);
376
+ // Draw leaf with rotation and gradient
377
+ ctx.save();
378
+ ctx.translate(leaf.x + swayX, leaf.y);
379
+ ctx.rotate(leaf.rotation);
380
+ ctx.scale(scaleX, 1); // Apply 3D flip effect with minimum width
381
+ // Fade when edge-on but maintain minimum 50% opacity
382
+ const opacityMultiplier = Math.max(0.5, Math.abs(scaleXRaw * 0.3 + 0.7));
383
+ ctx.globalAlpha = leaf.opacity * opacityMultiplier;
384
+ // Create gradient from color1 to color2
385
+ const gradient = ctx.createLinearGradient(-leaf.width / 2, 0, leaf.width / 2, 0);
386
+ gradient.addColorStop(0, leaf.color1);
387
+ gradient.addColorStop(0.5, leaf.color2);
388
+ gradient.addColorStop(1, leaf.color1);
389
+ // Draw leaf shape (pointed oval using bezier curves)
390
+ ctx.fillStyle = gradient;
391
+ ctx.beginPath();
392
+ const w = leaf.width / 2;
393
+ const h = leaf.height / 2;
394
+ // Start at top tip
395
+ ctx.moveTo(0, -h);
396
+ // Curve to right side (widest point)
397
+ ctx.bezierCurveTo(w * 0.7, -h * 0.5, w * 0.7, h * 0.5, 0, h);
398
+ // Curve to left side and back to top
399
+ ctx.bezierCurveTo(-w * 0.7, h * 0.5, -w * 0.7, -h * 0.5, 0, -h);
400
+ ctx.fill();
401
+ ctx.restore();
402
+ });
403
+ }
404
+ else if (type === "lightning") {
405
+ // Lightning spawning logic
406
+ const currentTime = Date.now();
407
+ const timeSinceLastLightning = (currentTime - lastLightningTimeRef.current) / 1000;
408
+ // Reduced frequency: intensity 1 = ~15s, intensity 10 = ~3s, intensity 50 = ~0.6s, intensity 100 = ~0.3s
409
+ const spawnInterval = Math.max(0.3, 15 / intensity);
410
+ if (shouldEmit && timeSinceLastLightning > spawnInterval) {
411
+ // Calculate number of simultaneous bolts based on intensity
412
+ // After a multi-bolt strike, force a single bolt for variation
413
+ let boltCount = 1;
414
+ if (lastBoltCountRef.current > 1) {
415
+ // Previous strike was multi-bolt, do single bolt this time
416
+ boltCount = 1;
417
+ }
418
+ else {
419
+ // Previous was single, can do multi-bolt based on intensity
420
+ if (intensity > 60) {
421
+ // 60% chance of multi-bolt at high intensity
422
+ boltCount = Math.random() < 0.6 ? (2 + Math.floor(Math.random() * 3)) : 1; // 2-4 bolts or 1
423
+ }
424
+ else if (intensity > 30) {
425
+ // 50% chance of multi-bolt at medium intensity
426
+ boltCount = Math.random() < 0.5 ? (2 + Math.floor(Math.random() * 2)) : 1; // 2-3 bolts or 1
427
+ }
428
+ else if (intensity > 15) {
429
+ // 30% chance of double bolt at low-medium intensity
430
+ boltCount = Math.random() < 0.3 ? 2 : 1;
431
+ }
432
+ // intensity <= 15: always single bolt
433
+ }
434
+ // Store bolt count for next iteration
435
+ lastBoltCountRef.current = boltCount;
436
+ // Spawn multiple bolts with slight time offset for realism
437
+ for (let i = 0; i < boltCount; i++) {
438
+ const startX = Math.random() * canvasWidth;
439
+ const bolt = generateLightningBolt(startX, 0, canvasHeight);
440
+ // Add slight age offset for staggered appearance
441
+ bolt.age = -i * 0.025; // 25ms offset between bolts
442
+ particlesRef.current.push(bolt);
443
+ }
444
+ lastLightningTimeRef.current = currentTime;
445
+ }
446
+ // Update and draw lightning bolts
447
+ particlesRef.current.forEach((bolt, index) => {
448
+ bolt.age += 0.016; // Increment age
449
+ // Skip if not yet started (negative age for staggered bolts)
450
+ if (bolt.age < 0)
451
+ return;
452
+ // Remove expired bolts
453
+ if (bolt.age > bolt.lifetime) {
454
+ particlesRef.current.splice(index, 1);
455
+ return;
456
+ }
457
+ // Calculate reveal progress (how much of the bolt to draw)
458
+ const revealProgress = Math.min(1, bolt.age / bolt.revealDuration);
459
+ // Calculate opacity fade (flash effect)
460
+ const lifeProgress = bolt.age / bolt.lifetime;
461
+ let currentOpacity = bolt.opacity;
462
+ // During reveal: full brightness
463
+ // After reveal: hold briefly, then fade out
464
+ if (lifeProgress < bolt.revealDuration / bolt.lifetime) {
465
+ currentOpacity *= revealProgress; // Fade in during reveal
466
+ }
467
+ else if (lifeProgress < 0.3) {
468
+ currentOpacity *= 1; // Hold at full brightness
469
+ }
470
+ else {
471
+ // Fade out in the remaining time
472
+ const fadeProgress = (lifeProgress - 0.3) / 0.7;
473
+ currentOpacity *= (1 - fadeProgress);
474
+ }
475
+ // Calculate how many segments to draw based on reveal progress
476
+ const totalSegments = bolt.segments.length;
477
+ const segmentsToDraw = Math.ceil(totalSegments * revealProgress);
478
+ // Draw main bolt with reveal effect
479
+ ctx.globalAlpha = currentOpacity;
480
+ ctx.strokeStyle = bolt.color;
481
+ ctx.lineWidth = bolt.thickness;
482
+ ctx.lineCap = "round";
483
+ ctx.lineJoin = "round";
484
+ // Draw glow effect
485
+ ctx.shadowBlur = 15;
486
+ ctx.shadowColor = bolt.color;
487
+ ctx.beginPath();
488
+ for (let i = 0; i < segmentsToDraw; i++) {
489
+ const segment = bolt.segments[i];
490
+ if (i === 0) {
491
+ ctx.moveTo(segment.x, segment.y);
492
+ }
493
+ else {
494
+ ctx.lineTo(segment.x, segment.y);
495
+ }
496
+ }
497
+ ctx.stroke();
498
+ // Recursive function to draw hierarchical branches
499
+ const drawBranch = (branch, parentSegments, parentSegmentsToDraw) => {
500
+ // Only draw if parent bolt has reached this branch point
501
+ if (branch.startIndex < parentSegmentsToDraw) {
502
+ const startSegment = parentSegments[branch.startIndex];
503
+ // Calculate how much of this branch to reveal
504
+ const segmentsPastBranch = parentSegmentsToDraw - branch.startIndex;
505
+ const branchRevealProgress = Math.min(1, segmentsPastBranch / 3);
506
+ const branchSegmentsToDraw = Math.ceil(branch.segments.length * branchRevealProgress);
507
+ // Draw this branch
508
+ ctx.beginPath();
509
+ ctx.moveTo(startSegment.x, startSegment.y);
510
+ for (let i = 0; i < branchSegmentsToDraw; i++) {
511
+ const segment = branch.segments[i];
512
+ ctx.lineTo(segment.x, segment.y);
513
+ }
514
+ ctx.lineWidth = branch.thickness;
515
+ ctx.stroke();
516
+ // Recursively draw child branches
517
+ if (branch.children && branch.children.length > 0) {
518
+ branch.children.forEach((childBranch) => {
519
+ drawBranch(childBranch, branch.segments, branchSegmentsToDraw);
520
+ });
521
+ }
522
+ }
523
+ };
524
+ // Draw all primary branches and their hierarchies
525
+ bolt.branches.forEach((branch) => {
526
+ drawBranch(branch, bolt.segments, segmentsToDraw);
527
+ });
528
+ // Reset shadow
529
+ ctx.shadowBlur = 0;
530
+ });
531
+ }
532
+ ctx.globalAlpha = 1;
533
+ animationRef.current = requestAnimationFrame(animate);
534
+ };
535
+ animate();
536
+ return () => {
537
+ window.removeEventListener("resize", updateSize);
538
+ if (animationRef.current) {
539
+ cancelAnimationFrame(animationRef.current);
540
+ }
541
+ };
542
+ }, [type, colors, speed, intensity, angle, duration, trigger]);
543
+ // Respond to external control in manual mode
544
+ useEffect(() => {
545
+ if (trigger !== "manual")
546
+ return;
547
+ if (active && !isEmittingRef.current) {
548
+ // Initialize particles if starting emission
549
+ if (particlesRef.current.length === 0) {
550
+ const canvas = canvasRef.current;
551
+ const container = containerRef.current;
552
+ if (canvas && container) {
553
+ const canvasWidth = canvas.width / 2;
554
+ const canvasHeight = canvas.height / 2;
555
+ const parsedColors = colors.map((color) => {
556
+ const computedColor = getComputedStyle(container).getPropertyValue(`--${color}`);
557
+ return computedColor || color;
558
+ });
559
+ if (type === "rain") {
560
+ const particles = [];
561
+ const angleRad = (angle * Math.PI) / 180;
562
+ const horizontalOffset = Math.abs(Math.tan(angleRad) * canvasHeight);
563
+ for (let i = 0; i < intensity; i++) {
564
+ const spawnWidth = canvasWidth + horizontalOffset * 2;
565
+ const spawnX = Math.random() * spawnWidth - horizontalOffset;
566
+ particles.push({
567
+ x: spawnX,
568
+ y: Math.random() * canvasHeight - canvasHeight,
569
+ length: 10 + Math.random() * 20,
570
+ speed: (2 + Math.random() * 3) * speed,
571
+ color: parsedColors[Math.floor(Math.random() * parsedColors.length)],
572
+ opacity: 0.3 + Math.random() * 0.5,
573
+ thickness: 1 + Math.random() * 1.5,
574
+ });
575
+ }
576
+ particlesRef.current = particles;
577
+ }
578
+ else if (type === "snow") {
579
+ const particles = [];
580
+ const angleRad = (angle * Math.PI) / 180;
581
+ const horizontalOffset = Math.abs(Math.tan(angleRad) * canvasHeight);
582
+ for (let i = 0; i < intensity; i++) {
583
+ const depth = Math.random();
584
+ const size = 2 + depth * 4;
585
+ const spawnWidth = canvasWidth + horizontalOffset * 2;
586
+ const spawnX = Math.random() * spawnWidth - horizontalOffset;
587
+ particles.push({
588
+ x: spawnX,
589
+ y: Math.random() * canvasHeight - canvasHeight,
590
+ size,
591
+ speed: (0.3 + depth * 0.7) * speed,
592
+ color: parsedColors[Math.floor(Math.random() * parsedColors.length)],
593
+ opacity: 0.4 + depth * 0.5,
594
+ swayAmplitude: 20 + Math.random() * 30,
595
+ swaySpeed: 0.5 + Math.random() * 1,
596
+ swayOffset: Math.random() * Math.PI * 2,
597
+ depth,
598
+ });
599
+ }
600
+ particlesRef.current = particles;
601
+ }
602
+ else if (type === "leaves") {
603
+ const particles = [];
604
+ const angleRad = (angle * Math.PI) / 180;
605
+ const horizontalOffset = Math.abs(Math.tan(angleRad) * canvasHeight);
606
+ for (let i = 0; i < intensity; i++) {
607
+ const depth = Math.random();
608
+ const width = 8 + depth * 12;
609
+ const height = width * (0.6 + Math.random() * 0.4);
610
+ const spawnWidth = canvasWidth + horizontalOffset * 2;
611
+ const spawnX = Math.random() * spawnWidth - horizontalOffset;
612
+ const colorIndex = Math.floor(Math.random() * parsedColors.length);
613
+ const color1 = parsedColors[colorIndex];
614
+ const color2 = parsedColors[Math.min(colorIndex + 1, parsedColors.length - 1)];
615
+ particles.push({
616
+ x: spawnX,
617
+ y: Math.random() * canvasHeight - canvasHeight,
618
+ width,
619
+ height,
620
+ speed: (0.4 + depth * 0.8) * speed,
621
+ color1,
622
+ color2,
623
+ opacity: 0.6 + depth * 0.3,
624
+ swayAmplitude: 30 + Math.random() * 50,
625
+ swaySpeed: 0.3 + Math.random() * 0.7,
626
+ swayOffset: Math.random() * Math.PI * 2,
627
+ rotation: Math.random() * Math.PI * 2,
628
+ rotationSpeed: (Math.random() - 0.5) * 0.08,
629
+ rotation3D: Math.random() * Math.PI * 2,
630
+ rotation3DSpeed: (Math.random() - 0.5) * 0.06,
631
+ depth,
632
+ });
633
+ }
634
+ particlesRef.current = particles;
635
+ }
636
+ }
637
+ }
638
+ isEmittingRef.current = true;
639
+ emitStartTimeRef.current = Date.now();
640
+ }
641
+ else if (!active && isEmittingRef.current) {
642
+ isEmittingRef.current = false;
643
+ }
644
+ }, [active, trigger]);
645
+ const handleMouseEnter = () => {
646
+ if (trigger === "hover" && !isHoveredRef.current) {
647
+ isHoveredRef.current = true;
648
+ isEmittingRef.current = true;
649
+ emitStartTimeRef.current = Date.now();
650
+ }
651
+ };
652
+ const handleMouseLeave = () => {
653
+ if (trigger === "hover" && isHoveredRef.current) {
654
+ isHoveredRef.current = false;
655
+ }
656
+ };
657
+ const handleClick = () => {
658
+ if (trigger !== "click")
659
+ return;
660
+ if (!isEmittingRef.current) {
661
+ // Initialize particles if starting emission
662
+ if (particlesRef.current.length === 0) {
663
+ const canvas = canvasRef.current;
664
+ const container = containerRef.current;
665
+ if (canvas && container) {
666
+ const canvasWidth = canvas.width / 2;
667
+ const canvasHeight = canvas.height / 2;
668
+ const parsedColors = colors.map((color) => {
669
+ const computedColor = getComputedStyle(container).getPropertyValue(`--${color}`);
670
+ return computedColor || color;
671
+ });
672
+ if (type === "rain") {
673
+ const particles = [];
674
+ const angleRad = (angle * Math.PI) / 180;
675
+ const horizontalOffset = Math.abs(Math.tan(angleRad) * canvasHeight);
676
+ for (let i = 0; i < intensity; i++) {
677
+ const spawnWidth = canvasWidth + horizontalOffset * 2;
678
+ const spawnX = Math.random() * spawnWidth - horizontalOffset;
679
+ particles.push({
680
+ x: spawnX,
681
+ y: Math.random() * canvasHeight - canvasHeight,
682
+ length: 10 + Math.random() * 20,
683
+ speed: (2 + Math.random() * 3) * speed,
684
+ color: parsedColors[Math.floor(Math.random() * parsedColors.length)],
685
+ opacity: 0.3 + Math.random() * 0.5,
686
+ thickness: 1 + Math.random() * 1.5,
687
+ });
688
+ }
689
+ particlesRef.current = particles;
690
+ }
691
+ else if (type === "snow") {
692
+ const particles = [];
693
+ const angleRad = (angle * Math.PI) / 180;
694
+ const horizontalOffset = Math.abs(Math.tan(angleRad) * canvasHeight);
695
+ for (let i = 0; i < intensity; i++) {
696
+ const depth = Math.random();
697
+ const size = 2 + depth * 4;
698
+ const spawnWidth = canvasWidth + horizontalOffset * 2;
699
+ const spawnX = Math.random() * spawnWidth - horizontalOffset;
700
+ particles.push({
701
+ x: spawnX,
702
+ y: Math.random() * canvasHeight - canvasHeight,
703
+ size,
704
+ speed: (0.3 + depth * 0.7) * speed,
705
+ color: parsedColors[Math.floor(Math.random() * parsedColors.length)],
706
+ opacity: 0.4 + depth * 0.5,
707
+ swayAmplitude: 20 + Math.random() * 30,
708
+ swaySpeed: 0.5 + Math.random() * 1,
709
+ swayOffset: Math.random() * Math.PI * 2,
710
+ depth,
711
+ });
712
+ }
713
+ particlesRef.current = particles;
714
+ }
715
+ else if (type === "leaves") {
716
+ const particles = [];
717
+ const angleRad = (angle * Math.PI) / 180;
718
+ const horizontalOffset = Math.abs(Math.tan(angleRad) * canvasHeight);
719
+ for (let i = 0; i < intensity; i++) {
720
+ const depth = Math.random();
721
+ const width = 8 + depth * 12;
722
+ const height = width * (0.6 + Math.random() * 0.4);
723
+ const spawnWidth = canvasWidth + horizontalOffset * 2;
724
+ const spawnX = Math.random() * spawnWidth - horizontalOffset;
725
+ const colorIndex = Math.floor(Math.random() * parsedColors.length);
726
+ const color1 = parsedColors[colorIndex];
727
+ const color2 = parsedColors[Math.min(colorIndex + 1, parsedColors.length - 1)];
728
+ particles.push({
729
+ x: spawnX,
730
+ y: Math.random() * canvasHeight - canvasHeight,
731
+ width,
732
+ height,
733
+ speed: (0.4 + depth * 0.8) * speed,
734
+ color1,
735
+ color2,
736
+ opacity: 0.6 + depth * 0.3,
737
+ swayAmplitude: 30 + Math.random() * 50,
738
+ swaySpeed: 0.3 + Math.random() * 0.7,
739
+ swayOffset: Math.random() * Math.PI * 2,
740
+ rotation: Math.random() * Math.PI * 2,
741
+ rotationSpeed: (Math.random() - 0.5) * 0.08,
742
+ rotation3D: Math.random() * Math.PI * 2,
743
+ rotation3DSpeed: (Math.random() - 0.5) * 0.06,
744
+ depth,
745
+ });
746
+ }
747
+ particlesRef.current = particles;
748
+ }
749
+ }
750
+ }
751
+ isEmittingRef.current = true;
752
+ emitStartTimeRef.current = Date.now();
753
+ }
754
+ else {
755
+ isEmittingRef.current = false;
756
+ }
757
+ };
758
+ return (_jsxs(Flex, { ref: containerRef, fill: true, overflow: "hidden", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onClick: handleClick, ...rest, children: [_jsx("canvas", { ref: canvasRef, style: {
759
+ position: "absolute",
760
+ top: 0,
761
+ left: 0,
762
+ width: "100%",
763
+ height: "100%",
764
+ pointerEvents: "none",
765
+ } }), children] }));
766
+ });
767
+ WeatherFx.displayName = "WeatherFx";
768
+ export { WeatherFx };
769
+ //# sourceMappingURL=WeatherFx.js.map