@once-ui-system/core 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ <br/>
2
+
3
+ <div align="center">
4
+ <picture>
5
+ <source media="(prefers-color-scheme: dark)" srcset="https://docs.once-ui.com/trademarks/icon-dark.svg" width="64" height="64">
6
+ <source media="(prefers-color-scheme: light)" srcset="https://docs.once-ui.com/trademarks/icon-light.svg" width="64" height="64">
7
+ <img alt="Once UI Logo" src="https://docs.once-ui.com/trademarks/icon-dark.svg" width="64" height="64">
8
+ </picture><picture>
9
+ <source media="(prefers-color-scheme: dark)" srcset="https://docs.once-ui.com/trademarks/type-dark.svg" width="200" height="64">
10
+ <source media="(prefers-color-scheme: light)" srcset="https://docs.once-ui.com/trademarks/type-light.svg" width="200" height="64">
11
+ <img alt="Once UI Wordmark" src="https://docs.once-ui.com/trademarks/type-dark.svg" width="200" height="64">
12
+ </picture>
13
+
14
+ <br/>
15
+
16
+ The indie design system for Next.js apps
17
+
18
+ [![npm version](https://img.shields.io/npm/v/@once-ui-system/core.svg)](https://www.npmjs.com/package/@once-ui-system/core)
19
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE.md)
20
+ [![npm downloads](https://img.shields.io/npm/dm/@once-ui-system/core.svg)](https://www.npmjs.com/package/@once-ui-system/core)
21
+ [![Discord](https://img.shields.io/discord/1083398120035074148?color=7289da&logo=discord&logoColor=white)](https://discord.com/invite/5EyAQ4eNdS)
22
+ </div>
23
+
24
+
25
+
26
+ <br/>
27
+
28
+ ## Documentation
29
+
30
+ Learn how to set up and build with Once UI at [docs.once-ui.com](https://docs.once-ui.com/once-ui/quick-start).
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ npm install @once-ui-system/core
36
+ ```
37
+
38
+ ## Authors
39
+
40
+ Built and maintained by [**Lorant One**](https://lorant.one).
41
+
42
+ ## Community
43
+
44
+ Join the [Design Engineers Club](https://discord.com/invite/5EyAQ4eNdS) for help, support and discussion.
45
+
46
+ Found a bug? Report it [here](https://github.com/once-ui-system/core/issues/new?labels=bug&template=bug_report.md). Got a feature request? Submit it [here](https://github.com/once-ui-system/core/issues/new?labels=feature%20request&template=feature_request.md).
47
+
48
+ ## Contributing
49
+
50
+ Please read our [CONTRIBUTING.md](https://github.com/once-ui-system/core/blob/main/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to Once UI.
51
+
52
+ ## Sponsors
53
+
54
+ Once UI is an indie project. [Sponsor us](https://github.com/sponsors/once-ui-system) and get featured on our site!
package/dist/README.md ADDED
@@ -0,0 +1,54 @@
1
+ <br/>
2
+
3
+ <div align="center">
4
+ <picture>
5
+ <source media="(prefers-color-scheme: dark)" srcset="https://docs.once-ui.com/trademarks/icon-dark.svg" width="64" height="64">
6
+ <source media="(prefers-color-scheme: light)" srcset="https://docs.once-ui.com/trademarks/icon-light.svg" width="64" height="64">
7
+ <img alt="Once UI Logo" src="https://docs.once-ui.com/trademarks/icon-dark.svg" width="64" height="64">
8
+ </picture><picture>
9
+ <source media="(prefers-color-scheme: dark)" srcset="https://docs.once-ui.com/trademarks/type-dark.svg" width="200" height="64">
10
+ <source media="(prefers-color-scheme: light)" srcset="https://docs.once-ui.com/trademarks/type-light.svg" width="200" height="64">
11
+ <img alt="Once UI Wordmark" src="https://docs.once-ui.com/trademarks/type-dark.svg" width="200" height="64">
12
+ </picture>
13
+
14
+ <br/>
15
+
16
+ The indie design system for Next.js apps
17
+
18
+ [![npm version](https://img.shields.io/npm/v/@once-ui-system/core.svg)](https://www.npmjs.com/package/@once-ui-system/core)
19
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE.md)
20
+ [![npm downloads](https://img.shields.io/npm/dm/@once-ui-system/core.svg)](https://www.npmjs.com/package/@once-ui-system/core)
21
+ [![Discord](https://img.shields.io/discord/1083398120035074148?color=7289da&logo=discord&logoColor=white)](https://discord.com/invite/5EyAQ4eNdS)
22
+ </div>
23
+
24
+
25
+
26
+ <br/>
27
+
28
+ ## Documentation
29
+
30
+ Learn how to set up and build with Once UI at [docs.once-ui.com](https://docs.once-ui.com/once-ui/quick-start).
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ npm install @once-ui-system/core
36
+ ```
37
+
38
+ ## Authors
39
+
40
+ Built and maintained by [**Lorant One**](https://lorant.one).
41
+
42
+ ## Community
43
+
44
+ Join the [Design Engineers Club](https://discord.com/invite/5EyAQ4eNdS) for help, support and discussion.
45
+
46
+ Found a bug? Report it [here](https://github.com/once-ui-system/core/issues/new?labels=bug&template=bug_report.md). Got a feature request? Submit it [here](https://github.com/once-ui-system/core/issues/new?labels=feature%20request&template=feature_request.md).
47
+
48
+ ## Contributing
49
+
50
+ Please read our [CONTRIBUTING.md](https://github.com/once-ui-system/core/blob/main/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to Once UI.
51
+
52
+ ## Sponsors
53
+
54
+ Once UI is an indie project. [Sponsor us](https://github.com/sponsors/once-ui-system) and get featured on our site!
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { CountFxProps } from ".";
3
+ export interface CountdownFxProps extends Omit<CountFxProps, 'value' | 'format' | 'separator' | 'effect'> {
4
+ targetDate: Date | string;
5
+ format?: "HH:MM:SS" | "DD:HH:MM:SS" | "MM:SS";
6
+ effect?: CountFxProps['effect'];
7
+ onComplete?: () => void;
8
+ }
9
+ declare const CountdownFx: React.FC<CountdownFxProps>;
10
+ export { CountdownFx };
11
+ //# sourceMappingURL=CountdownFx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CountdownFx.d.ts","sourceRoot":"","sources":["../../src/components/CountdownFx.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAC;AACnD,OAAO,EAAsB,YAAY,EAAE,MAAM,GAAG,CAAC;AAErD,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,YAAY,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;IACvG,UAAU,EAAE,IAAI,GAAG,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,UAAU,GAAG,aAAa,GAAG,OAAO,CAAC;IAC9C,MAAM,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAkH3C,CAAC;AAGF,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,49 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ import { Row, Text, CountFx } from ".";
5
+ const CountdownFx = ({ targetDate, format = "HH:MM:SS", effect = "wheel", onComplete, ...countFxProps }) => {
6
+ const [timeRemaining, setTimeRemaining] = useState({
7
+ days: 0,
8
+ hours: 0,
9
+ minutes: 0,
10
+ seconds: 0,
11
+ total: 0,
12
+ });
13
+ useEffect(() => {
14
+ const calculateTimeRemaining = () => {
15
+ const now = new Date().getTime();
16
+ const target = typeof targetDate === 'string' ? new Date(targetDate).getTime() : targetDate.getTime();
17
+ const difference = target - now;
18
+ if (difference <= 0) {
19
+ setTimeRemaining({ days: 0, hours: 0, minutes: 0, seconds: 0, total: 0 });
20
+ if (onComplete) {
21
+ onComplete();
22
+ }
23
+ return;
24
+ }
25
+ const days = Math.floor(difference / (1000 * 60 * 60 * 24));
26
+ const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
27
+ const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
28
+ const seconds = Math.floor((difference % (1000 * 60)) / 1000);
29
+ setTimeRemaining({ days, hours, minutes, seconds, total: difference });
30
+ };
31
+ calculateTimeRemaining();
32
+ const interval = setInterval(calculateTimeRemaining, 1000);
33
+ return () => clearInterval(interval);
34
+ }, [targetDate, onComplete]);
35
+ const padZero = (num) => num.toString().padStart(2, '0');
36
+ const renderTimeUnit = (value, key) => {
37
+ const paddedValue = padZero(value);
38
+ const digits = paddedValue.split('');
39
+ return (_jsx(Row, { gap: "0", inline: true, children: digits.map((digit, index) => (_jsx(CountFx, { value: parseInt(digit), effect: effect, speed: 400, ...countFxProps }, `${key}-${index}`))) }, key));
40
+ };
41
+ const renderSeparator = () => (_jsx(Text, { ...countFxProps, style: { width: '0.5em', textAlign: 'center' }, children: ":" }, `sep-${Math.random()}`));
42
+ if (timeRemaining.total === 0) {
43
+ return (_jsxs(Row, { gap: "0", align: "center", textVariant: countFxProps.variant, style: { fontVariantNumeric: 'tabular-nums' }, children: [format === "DD:HH:MM:SS" && (_jsxs(_Fragment, { children: [renderTimeUnit(0, 'days'), renderSeparator()] })), renderTimeUnit(0, 'hours'), renderSeparator(), renderTimeUnit(0, 'minutes'), format !== "MM:SS" && (_jsxs(_Fragment, { children: [renderSeparator(), renderTimeUnit(0, 'seconds')] }))] }));
44
+ }
45
+ return (_jsxs(Row, { gap: "0", align: "center", style: { fontVariantNumeric: 'tabular-nums' }, children: [format === "DD:HH:MM:SS" && timeRemaining.days > 0 && (_jsxs(_Fragment, { children: [renderTimeUnit(timeRemaining.days, 'days'), renderSeparator()] })), format !== "MM:SS" && (_jsxs(_Fragment, { children: [renderTimeUnit(format === "DD:HH:MM:SS" ? timeRemaining.hours : timeRemaining.days * 24 + timeRemaining.hours, 'hours'), renderSeparator()] })), renderTimeUnit(timeRemaining.minutes, 'minutes'), renderSeparator(), renderTimeUnit(timeRemaining.seconds, 'seconds')] }));
46
+ };
47
+ CountdownFx.displayName = "CountdownFx";
48
+ export { CountdownFx };
49
+ //# sourceMappingURL=CountdownFx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CountdownFx.js","sourceRoot":"","sources":["../../src/components/CountdownFx.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Heading.d.ts","sourceRoot":"","sources":["../../src/components/Heading.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,OAAO,CAAC;AAGrE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGrE,KAAK,YAAY,CAAC,CAAC,SAAS,WAAW,IAAI,SAAS,CAAC,CAAC,CAAC,GACrD,WAAW,GACX,YAAY,GACZ,wBAAwB,CAAC,CAAC,CAAC,CAAC;AAE9B,QAAA,MAAM,OAAO;KAAI,CAAC,SAAS,WAAW,2RA4BnC,YAAY,CAAC,CAAC,CAAC;;CAsEjB,CAAC;AAIF,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"Heading.d.ts","sourceRoot":"","sources":["../../src/components/Heading.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,OAAO,CAAC;AAG9D,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGrE,KAAK,YAAY,CAAC,CAAC,SAAS,WAAW,IAAI,SAAS,CAAC,CAAC,CAAC,GACrD,WAAW,GACX,YAAY,GACZ,wBAAwB,CAAC,CAAC,CAAC,CAAC;AAE9B,QAAA,MAAM,OAAO;KAAI,CAAC,SAAS,WAAW,2RA4BnC,YAAY,CAAC,CAAC,CAAC;;CAsEjB,CAAC;AAIF,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -1,5 +1,12 @@
1
1
  import React from "react";
2
2
  import { Flex } from ".";
3
+ interface BulgeConfig {
4
+ type?: "ripple" | "wave";
5
+ duration?: number;
6
+ intensity?: number;
7
+ repeat?: boolean;
8
+ delay?: number;
9
+ }
3
10
  interface MatrixFxProps extends React.ComponentProps<typeof Flex> {
4
11
  speed?: number;
5
12
  colors?: string[];
@@ -8,8 +15,10 @@ interface MatrixFxProps extends React.ComponentProps<typeof Flex> {
8
15
  revealFrom?: "center" | "top" | "bottom" | "left" | "right";
9
16
  trigger?: "hover" | "instant" | "mount";
10
17
  flicker?: boolean;
18
+ bulge?: BulgeConfig;
11
19
  children?: React.ReactNode;
12
20
  }
13
21
  declare const MatrixFx: React.ForwardRefExoticComponent<Omit<MatrixFxProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
14
22
  export { MatrixFx };
23
+ export type { BulgeConfig };
15
24
  //# sourceMappingURL=MatrixFx.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MatrixFx.d.ts","sourceRoot":"","sources":["../../src/components/MatrixFx.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4B,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC;AAEzB,UAAU,aAAc,SAAQ,KAAK,CAAC,cAAc,CAAC,OAAO,IAAI,CAAC;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC5D,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,QAAA,MAAM,QAAQ,mGAoYb,CAAC;AAGF,OAAO,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"MatrixFx.d.ts","sourceRoot":"","sources":["../../src/components/MatrixFx.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4B,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC;AAEzB,UAAU,WAAW;IACnB,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,aAAc,SAAQ,KAAK,CAAC,cAAc,CAAC,OAAO,IAAI,CAAC;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC5D,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,QAAA,MAAM,QAAQ,mGAgrBb,CAAC;AAGF,OAAO,EAAE,QAAQ,EAAE,CAAC;AACpB,YAAY,EAAE,WAAW,EAAE,CAAC"}
@@ -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", 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,7 @@ 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());
15
16
  useEffect(() => {
16
17
  if (forwardedRef) {
17
18
  if ("current" in forwardedRef) {
@@ -51,15 +52,18 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
51
52
  const computedColor = getComputedStyle(container).getPropertyValue(`--${color}`);
52
53
  return computedColor || color;
53
54
  });
54
- // Create dot grid
55
+ // Create dot grid with padding to prevent edge gaps during displacement
55
56
  const totalSize = size + spacing;
56
- const cols = Math.ceil(canvasWidth / totalSize);
57
- const rows = Math.ceil(canvasHeight / totalSize);
57
+ const maxDisplacement = (bulge?.intensity ?? 10) * 2; // Account for max possible displacement
58
+ const paddedWidth = canvasWidth + maxDisplacement * 2;
59
+ const paddedHeight = canvasHeight + maxDisplacement * 2;
60
+ const cols = Math.ceil(paddedWidth / totalSize);
61
+ const rows = Math.ceil(paddedHeight / totalSize);
58
62
  const dots = [];
59
63
  for (let row = 0; row < rows; row++) {
60
64
  for (let col = 0; col < cols; col++) {
61
- const x = col * totalSize + size / 2;
62
- const y = row * totalSize + size / 2;
65
+ const x = col * totalSize + size / 2 - maxDisplacement;
66
+ const y = row * totalSize + size / 2 - maxDisplacement;
63
67
  // Calculate distance from reveal origin
64
68
  let distanceFromOrigin = 0;
65
69
  const centerX = canvasWidth / 2;
@@ -101,9 +105,56 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
101
105
  const maxDistance = Math.max(...dots.map((d) => d.distanceFromOrigin));
102
106
  // Animation loop
103
107
  const startTime = Date.now();
108
+ // Bulge configuration
109
+ const bulgeEnabled = !!bulge;
110
+ const bulgeType = bulge?.type ?? "ripple"; // Default: ripple
111
+ const bulgeDuration = bulge?.duration ?? 3; // Default: 3 seconds per wave
112
+ const bulgeIntensity = bulge?.intensity ?? 10; // Default: 10px displacement
113
+ const bulgeRepeat = bulge?.repeat ?? true; // Default: true
114
+ const bulgeDelay = bulge?.delay ?? 0; // Default: 0ms
115
+ // Calculate center point for circular wave
116
+ const centerX = canvasWidth / 2;
117
+ const centerY = canvasHeight / 2;
118
+ const maxRadius = Math.sqrt(centerX * centerX + centerY * centerY);
104
119
  const animate = () => {
105
120
  ctx.clearRect(0, 0, canvasWidth, canvasHeight);
106
121
  const time = (Date.now() - startTime) / 1000; // Time in seconds
122
+ // Calculate circular wave radius (travels from center to edge)
123
+ let waveProgress = 0;
124
+ let showBulge = false;
125
+ let bulgeFadeOut = 1; // Multiplier for fading out bulge effect (1 = full effect, 0 = no effect)
126
+ if (bulgeEnabled) {
127
+ const bulgeElapsed = (Date.now() - bulgeStartTimeRef.current) / 1000;
128
+ const delaySeconds = bulgeDelay / 1000;
129
+ const totalCycleDuration = bulgeDuration + delaySeconds;
130
+ const adjustedTime = bulgeElapsed - delaySeconds;
131
+ const fadeStartPercent = 0.6; // Start fading at 60% of wave duration
132
+ if (adjustedTime >= 0) {
133
+ showBulge = true;
134
+ if (bulgeRepeat) {
135
+ // Continuous repeating wave
136
+ const cycleTime = adjustedTime % totalCycleDuration;
137
+ waveProgress = cycleTime < bulgeDuration ? cycleTime / bulgeDuration : 0;
138
+ showBulge = cycleTime < bulgeDuration; // Only show during active wave, not delay
139
+ }
140
+ else {
141
+ // Single wave with opacity fade during last 40%
142
+ if (adjustedTime <= bulgeDuration) {
143
+ waveProgress = adjustedTime / bulgeDuration;
144
+ // Start fading opacity at 60% of duration
145
+ if (waveProgress >= fadeStartPercent) {
146
+ const fadeProgress = (waveProgress - fadeStartPercent) / (1 - fadeStartPercent);
147
+ bulgeFadeOut = 1 - fadeProgress; // Fade from 1 to 0
148
+ }
149
+ }
150
+ else {
151
+ waveProgress = 0;
152
+ showBulge = false;
153
+ }
154
+ }
155
+ }
156
+ }
157
+ const waveRadius = waveProgress * maxRadius * 1.5; // Travel beyond edges for smooth cycle
107
158
  // For instant trigger, show all dots immediately at full opacity
108
159
  if (trigger === "instant") {
109
160
  dots.forEach((dot) => {
@@ -115,8 +166,74 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
115
166
  const flickerMultiplier = 0.6 + (flickerValue * 0.4); // Oscillate between 0.6 and 1.0
116
167
  opacity *= flickerMultiplier;
117
168
  }
118
- ctx.globalAlpha = opacity;
119
- ctx.fillRect(dot.x, dot.y, size, size);
169
+ // Calculate bulge displacement
170
+ let offsetX = 0;
171
+ let offsetY = 0;
172
+ let sizeMultiplier = 1;
173
+ let bulgeOpacity = 1;
174
+ if (bulgeEnabled && showBulge) {
175
+ if (bulgeType === "ripple") {
176
+ // Ripple: circular wave from center
177
+ const dx = dot.x - centerX;
178
+ const dy = dot.y - centerY;
179
+ const distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
180
+ const distanceToWave = Math.abs(distanceFromCenter - waveRadius);
181
+ const waveWidth = maxRadius * 0.15;
182
+ const distanceNorm = distanceToWave / waveWidth;
183
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 4);
184
+ const angle = Math.atan2(dy, dx);
185
+ const displacementAmount = waveFactor * bulgeIntensity * bulgeFadeOut;
186
+ offsetX = Math.cos(angle) * displacementAmount;
187
+ offsetY = Math.sin(angle) * displacementAmount;
188
+ sizeMultiplier = 1 + waveFactor * 0.8 * bulgeFadeOut;
189
+ const waveOpacity = 0.3 + waveFactor * 0.7;
190
+ bulgeOpacity = 1 + (waveOpacity - 1) * bulgeFadeOut;
191
+ }
192
+ else if (bulgeType === "wave") {
193
+ // Wave: Organic S-curve from bottom-left to top-right with rotation
194
+ const diagonalLength = Math.sqrt(canvasWidth * canvasWidth + canvasHeight * canvasHeight);
195
+ // Start wave off-screen before bottom-left, end off-screen after top-right
196
+ const wavePosOnDiagonal = (waveProgress * diagonalLength * 1.4) - (diagonalLength * 0.2);
197
+ // Calculate distance along the diagonal (from bottom-left to top-right)
198
+ const normalizedX = dot.x / canvasWidth;
199
+ const normalizedY = 1 - (dot.y / canvasHeight);
200
+ const dotDiagonalPos = (normalizedX + normalizedY) / 2 * diagonalLength;
201
+ // Distance from wave front
202
+ const distanceToWaveFront = dotDiagonalPos - wavePosOnDiagonal;
203
+ const waveWidth = diagonalLength * 0.25;
204
+ const distanceNorm = distanceToWaveFront / waveWidth;
205
+ // Smooth wave factor with wider influence
206
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 2.5);
207
+ // Create rotating S-curve with multiple frequency components
208
+ const perpendicularOffset = normalizedY - normalizedX;
209
+ // Primary S-curve that rotates over time
210
+ const rotationPhase = waveProgress * Math.PI * 3; // Rotates 1.5 times during animation
211
+ const primaryFreq = 3; // Number of S-curves along the wave
212
+ const sCurvePrimary = Math.sin(perpendicularOffset * Math.PI * primaryFreq + rotationPhase);
213
+ // Secondary curve for complexity (higher frequency, lower amplitude)
214
+ const secondaryFreq = 7;
215
+ const sCurveSecondary = Math.sin(perpendicularOffset * Math.PI * secondaryFreq - rotationPhase * 1.5) * 0.4;
216
+ // Combine curves for organic motion
217
+ const sCurveFactor = sCurvePrimary + sCurveSecondary;
218
+ // Wave straightens at the edges (distanceNorm affects curve strength)
219
+ const curveStrength = 1 - Math.abs(distanceNorm) * 0.5;
220
+ const modulatedCurve = sCurveFactor * curveStrength;
221
+ // Apply displacement
222
+ const baseDisplacement = waveFactor * bulgeIntensity * bulgeFadeOut;
223
+ const diagonalAngle = Math.PI / 4;
224
+ // Perpendicular displacement creates the S-curve
225
+ const perpAngle = diagonalAngle + Math.PI / 2;
226
+ offsetX = Math.cos(diagonalAngle) * baseDisplacement + Math.cos(perpAngle) * modulatedCurve * baseDisplacement * 0.8;
227
+ offsetY = -Math.sin(diagonalAngle) * baseDisplacement - Math.sin(perpAngle) * modulatedCurve * baseDisplacement * 0.8;
228
+ sizeMultiplier = 1 + waveFactor * 0.5 * bulgeFadeOut;
229
+ const waveOpacity = 0.4 + waveFactor * 0.6;
230
+ bulgeOpacity = 1 + (waveOpacity - 1) * bulgeFadeOut;
231
+ }
232
+ }
233
+ ctx.globalAlpha = opacity * bulgeOpacity;
234
+ const adjustedSize = size * sizeMultiplier;
235
+ const sizeOffset = (adjustedSize - size) / 2;
236
+ ctx.fillRect(dot.x + offsetX - sizeOffset, dot.y + offsetY - sizeOffset, adjustedSize, adjustedSize);
120
237
  });
121
238
  ctx.globalAlpha = 1;
122
239
  animationRef.current = requestAnimationFrame(animate);
@@ -149,9 +266,53 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
149
266
  }
150
267
  }
151
268
  if (opacity > 0) {
269
+ // Calculate bulge displacement
270
+ let offsetX = 0;
271
+ let offsetY = 0;
272
+ let sizeMultiplier = 1;
273
+ let bulgeOpacity = 1;
274
+ if (bulgeEnabled) {
275
+ if (bulgeType === "ripple") {
276
+ const dx = dot.x - centerX;
277
+ const dy = dot.y - centerY;
278
+ const distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
279
+ const distanceToWave = Math.abs(distanceFromCenter - waveRadius);
280
+ const waveWidth = maxRadius * 0.15;
281
+ const distanceNorm = distanceToWave / waveWidth;
282
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 4);
283
+ const angle = Math.atan2(dy, dx);
284
+ const displacementAmount = waveFactor * bulgeIntensity;
285
+ offsetX = Math.cos(angle) * displacementAmount;
286
+ offsetY = Math.sin(angle) * displacementAmount;
287
+ sizeMultiplier = 1 + waveFactor * 0.8;
288
+ bulgeOpacity = 0.3 + waveFactor * 0.7;
289
+ }
290
+ else if (bulgeType === "wave") {
291
+ const diagonalLength = Math.sqrt(canvasWidth * canvasWidth + canvasHeight * canvasHeight);
292
+ const wavePosOnDiagonal = waveProgress * diagonalLength * 1.2;
293
+ const normalizedX = dot.x / canvasWidth;
294
+ const normalizedY = 1 - (dot.y / canvasHeight);
295
+ const dotDiagonalPos = (normalizedX + normalizedY) / 2 * diagonalLength;
296
+ const distanceToWaveFront = dotDiagonalPos - wavePosOnDiagonal;
297
+ const waveWidth = diagonalLength * 0.2;
298
+ const distanceNorm = Math.abs(distanceToWaveFront) / waveWidth;
299
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 3);
300
+ const perpendicularOffset = normalizedY - normalizedX;
301
+ const sCurvePhase = perpendicularOffset * Math.PI * 2;
302
+ const sCurveFactor = Math.sin(sCurvePhase + waveProgress * Math.PI * 2);
303
+ const baseDisplacement = waveFactor * bulgeIntensity;
304
+ const diagonalAngle = Math.PI / 4;
305
+ offsetX = Math.cos(diagonalAngle) * baseDisplacement + sCurveFactor * baseDisplacement * 0.5;
306
+ offsetY = -Math.sin(diagonalAngle) * baseDisplacement + sCurveFactor * baseDisplacement * 0.5;
307
+ sizeMultiplier = 1 + waveFactor * 0.6;
308
+ bulgeOpacity = 0.4 + waveFactor * 0.6;
309
+ }
310
+ }
152
311
  ctx.fillStyle = dot.color;
153
- ctx.globalAlpha = opacity;
154
- ctx.fillRect(dot.x, dot.y, size, size);
312
+ ctx.globalAlpha = opacity * bulgeOpacity;
313
+ const adjustedSize = size * sizeMultiplier;
314
+ const sizeOffset = (adjustedSize - size) / 2;
315
+ ctx.fillRect(dot.x + offsetX - sizeOffset, dot.y + offsetY - sizeOffset, adjustedSize, adjustedSize);
155
316
  }
156
317
  });
157
318
  }
@@ -165,8 +326,52 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
165
326
  const flickerMultiplier = 0.6 + (flickerValue * 0.4);
166
327
  opacity *= flickerMultiplier;
167
328
  }
168
- ctx.globalAlpha = opacity;
169
- ctx.fillRect(dot.x, dot.y, size, size);
329
+ // Calculate bulge displacement
330
+ let offsetX = 0;
331
+ let offsetY = 0;
332
+ let sizeMultiplier = 1;
333
+ let bulgeOpacity = 1;
334
+ if (bulgeEnabled) {
335
+ if (bulgeType === "ripple") {
336
+ const dx = dot.x - centerX;
337
+ const dy = dot.y - centerY;
338
+ const distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
339
+ const distanceToWave = Math.abs(distanceFromCenter - waveRadius);
340
+ const waveWidth = maxRadius * 0.15;
341
+ const distanceNorm = distanceToWave / waveWidth;
342
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 4);
343
+ const angle = Math.atan2(dy, dx);
344
+ const displacementAmount = waveFactor * bulgeIntensity;
345
+ offsetX = Math.cos(angle) * displacementAmount;
346
+ offsetY = Math.sin(angle) * displacementAmount;
347
+ sizeMultiplier = 1 + waveFactor * 0.8;
348
+ bulgeOpacity = 0.3 + waveFactor * 0.7;
349
+ }
350
+ else if (bulgeType === "wave") {
351
+ const diagonalLength = Math.sqrt(canvasWidth * canvasWidth + canvasHeight * canvasHeight);
352
+ const wavePosOnDiagonal = waveProgress * diagonalLength * 1.2;
353
+ const normalizedX = dot.x / canvasWidth;
354
+ const normalizedY = 1 - (dot.y / canvasHeight);
355
+ const dotDiagonalPos = (normalizedX + normalizedY) / 2 * diagonalLength;
356
+ const distanceToWaveFront = dotDiagonalPos - wavePosOnDiagonal;
357
+ const waveWidth = diagonalLength * 0.2;
358
+ const distanceNorm = Math.abs(distanceToWaveFront) / waveWidth;
359
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 3);
360
+ const perpendicularOffset = normalizedY - normalizedX;
361
+ const sCurvePhase = perpendicularOffset * Math.PI * 2;
362
+ const sCurveFactor = Math.sin(sCurvePhase + waveProgress * Math.PI * 2);
363
+ const baseDisplacement = waveFactor * bulgeIntensity;
364
+ const diagonalAngle = Math.PI / 4;
365
+ offsetX = Math.cos(diagonalAngle) * baseDisplacement + sCurveFactor * baseDisplacement * 0.5;
366
+ offsetY = -Math.sin(diagonalAngle) * baseDisplacement + sCurveFactor * baseDisplacement * 0.5;
367
+ sizeMultiplier = 1 + waveFactor * 0.6;
368
+ bulgeOpacity = 0.4 + waveFactor * 0.6;
369
+ }
370
+ }
371
+ ctx.globalAlpha = opacity * bulgeOpacity;
372
+ const adjustedSize = size * sizeMultiplier;
373
+ const sizeOffset = (adjustedSize - size) / 2;
374
+ ctx.fillRect(dot.x + offsetX - sizeOffset, dot.y + offsetY - sizeOffset, adjustedSize, adjustedSize);
170
375
  });
171
376
  }
172
377
  ctx.globalAlpha = 1;
@@ -200,9 +405,53 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
200
405
  }
201
406
  }
202
407
  if (opacity > 0) {
408
+ // Calculate bulge displacement
409
+ let offsetX = 0;
410
+ let offsetY = 0;
411
+ let sizeMultiplier = 1;
412
+ let bulgeOpacity = 1;
413
+ if (bulgeEnabled) {
414
+ if (bulgeType === "ripple") {
415
+ const dx = dot.x - centerX;
416
+ const dy = dot.y - centerY;
417
+ const distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
418
+ const distanceToWave = Math.abs(distanceFromCenter - waveRadius);
419
+ const waveWidth = maxRadius * 0.15;
420
+ const distanceNorm = distanceToWave / waveWidth;
421
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 4);
422
+ const angle = Math.atan2(dy, dx);
423
+ const displacementAmount = waveFactor * bulgeIntensity;
424
+ offsetX = Math.cos(angle) * displacementAmount;
425
+ offsetY = Math.sin(angle) * displacementAmount;
426
+ sizeMultiplier = 1 + waveFactor * 0.8;
427
+ bulgeOpacity = 0.3 + waveFactor * 0.7;
428
+ }
429
+ else if (bulgeType === "wave") {
430
+ const diagonalLength = Math.sqrt(canvasWidth * canvasWidth + canvasHeight * canvasHeight);
431
+ const wavePosOnDiagonal = waveProgress * diagonalLength * 1.2;
432
+ const normalizedX = dot.x / canvasWidth;
433
+ const normalizedY = 1 - (dot.y / canvasHeight);
434
+ const dotDiagonalPos = (normalizedX + normalizedY) / 2 * diagonalLength;
435
+ const distanceToWaveFront = dotDiagonalPos - wavePosOnDiagonal;
436
+ const waveWidth = diagonalLength * 0.2;
437
+ const distanceNorm = Math.abs(distanceToWaveFront) / waveWidth;
438
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 3);
439
+ const perpendicularOffset = normalizedY - normalizedX;
440
+ const sCurvePhase = perpendicularOffset * Math.PI * 2;
441
+ const sCurveFactor = Math.sin(sCurvePhase + waveProgress * Math.PI * 2);
442
+ const baseDisplacement = waveFactor * bulgeIntensity;
443
+ const diagonalAngle = Math.PI / 4;
444
+ offsetX = Math.cos(diagonalAngle) * baseDisplacement + sCurveFactor * baseDisplacement * 0.5;
445
+ offsetY = -Math.sin(diagonalAngle) * baseDisplacement + sCurveFactor * baseDisplacement * 0.5;
446
+ sizeMultiplier = 1 + waveFactor * 0.6;
447
+ bulgeOpacity = 0.4 + waveFactor * 0.6;
448
+ }
449
+ }
203
450
  ctx.fillStyle = dot.color;
204
- ctx.globalAlpha = opacity;
205
- ctx.fillRect(dot.x, dot.y, size, size);
451
+ ctx.globalAlpha = opacity * bulgeOpacity;
452
+ const adjustedSize = size * sizeMultiplier;
453
+ const sizeOffset = (adjustedSize - size) / 2;
454
+ ctx.fillRect(dot.x + offsetX - sizeOffset, dot.y + offsetY - sizeOffset, adjustedSize, adjustedSize);
206
455
  }
207
456
  });
208
457
  }
@@ -232,9 +481,31 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
232
481
  }
233
482
  }
234
483
  if (opacity > 0) {
484
+ // Calculate bulge displacement
485
+ let offsetX = 0;
486
+ let offsetY = 0;
487
+ let sizeMultiplier = 1;
488
+ let bulgeOpacity = 1;
489
+ if (bulgeEnabled) {
490
+ const dx = dot.x - centerX;
491
+ const dy = dot.y - centerY;
492
+ const distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
493
+ const distanceToWave = Math.abs(distanceFromCenter - waveRadius);
494
+ const waveWidth = maxRadius * 0.15;
495
+ const distanceNorm = distanceToWave / waveWidth;
496
+ const waveFactor = Math.exp(-distanceNorm * distanceNorm * 4);
497
+ const angle = Math.atan2(dy, dx);
498
+ const displacementAmount = waveFactor * bulgeIntensity;
499
+ offsetX = Math.cos(angle) * displacementAmount * 0.3;
500
+ offsetY = Math.sin(angle) * displacementAmount * 0.3 - waveFactor * bulgeIntensity;
501
+ sizeMultiplier = 1 + waveFactor * 0.8;
502
+ bulgeOpacity = 0.3 + waveFactor * 0.7;
503
+ }
235
504
  ctx.fillStyle = dot.color;
236
- ctx.globalAlpha = opacity;
237
- ctx.fillRect(dot.x, dot.y, size, size);
505
+ ctx.globalAlpha = opacity * bulgeOpacity;
506
+ const adjustedSize = size * sizeMultiplier;
507
+ const sizeOffset = (adjustedSize - size) / 2;
508
+ ctx.fillRect(dot.x + offsetX - sizeOffset, dot.y + offsetY - sizeOffset, adjustedSize, adjustedSize);
238
509
  }
239
510
  });
240
511
  }
@@ -254,7 +525,7 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
254
525
  cancelAnimationFrame(animationRef.current);
255
526
  }
256
527
  };
257
- }, [colors, size, spacing, speed, revealFrom, trigger, flicker]);
528
+ }, [colors, size, spacing, speed, revealFrom, trigger, flicker, bulge]);
258
529
  const handleMouseEnter = () => {
259
530
  if (trigger === "hover" && !isHoveredRef.current) {
260
531
  const now = Date.now();
@@ -275,6 +546,10 @@ const MatrixFx = React.forwardRef(({ speed = 1, colors = ["brand-solid-medium"],
275
546
  // Fresh start
276
547
  revealStartTimeRef.current = now;
277
548
  }
549
+ // Reset bulge animation on each hover when repeat is false
550
+ if (bulge && !bulge.repeat) {
551
+ bulgeStartTimeRef.current = now;
552
+ }
278
553
  isHoveredRef.current = true;
279
554
  hideStartProgressRef.current = 0; // Clear hide state
280
555
  }