@stianlarsen/react-light-beam 3.1.0 → 3.1.1
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/LICENSE +0 -0
- package/README.md +76 -451
- package/dist/index.cjs +148 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +13 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +2 -123
- package/dist/index.js +81 -413
- package/dist/index.js.map +1 -1
- package/package.json +32 -31
- package/dist/index.d.mts +0 -138
- package/dist/index.mjs +0 -450
- package/dist/index.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stianlarsen/react-light-beam",
|
|
3
|
-
"version": "3.1.
|
|
4
|
-
"description": "A customizable React component that creates a light beam effect using conic gradients.
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
3
|
+
"version": "3.1.1",
|
|
4
|
+
"description": "A customizable React component that creates a light beam effect using conic gradients. Supports dark mode and various customization options.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
8
9
|
"scripts": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"typecheck": "tsc --noEmit",
|
|
12
|
-
"prepublishOnly": "npm run build"
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
11
|
+
"build": "tsup"
|
|
13
12
|
},
|
|
14
13
|
"files": [
|
|
15
|
-
"dist"
|
|
16
|
-
"README.md",
|
|
17
|
-
"LICENSE"
|
|
14
|
+
"dist"
|
|
18
15
|
],
|
|
19
16
|
"exports": {
|
|
20
17
|
".": {
|
|
21
18
|
"import": {
|
|
22
|
-
"types": "./dist/index.d.mts",
|
|
23
|
-
"default": "./dist/index.mjs"
|
|
24
|
-
},
|
|
25
|
-
"require": {
|
|
26
19
|
"types": "./dist/index.d.ts",
|
|
27
20
|
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"require": {
|
|
23
|
+
"types": "./dist/index.d.cts",
|
|
24
|
+
"default": "./dist/index.cjs"
|
|
28
25
|
}
|
|
29
26
|
}
|
|
30
27
|
},
|
|
31
|
-
"sideEffects":
|
|
28
|
+
"sideEffects": [
|
|
29
|
+
"*.css"
|
|
30
|
+
],
|
|
32
31
|
"repository": {
|
|
33
32
|
"type": "git",
|
|
34
33
|
"url": "git+https://github.com/stianalars1/react-light-beam.git"
|
|
@@ -38,9 +37,7 @@
|
|
|
38
37
|
"light beam",
|
|
39
38
|
"conic gradient",
|
|
40
39
|
"dark mode",
|
|
41
|
-
"
|
|
42
|
-
"scrolltrigger",
|
|
43
|
-
"scroll animation",
|
|
40
|
+
"framer-motion",
|
|
44
41
|
"animation"
|
|
45
42
|
],
|
|
46
43
|
"author": "Stian Larsen <stian.larsen@mac.com>",
|
|
@@ -48,20 +45,24 @@
|
|
|
48
45
|
"bugs": {
|
|
49
46
|
"url": "https://github.com/stianalars1/react-light-beam/issues"
|
|
50
47
|
},
|
|
51
|
-
"homepage": "https://
|
|
52
|
-
"dependencies": {
|
|
53
|
-
"@gsap/react": "^2.1.2",
|
|
54
|
-
"gsap": "^3.12.5"
|
|
55
|
-
},
|
|
48
|
+
"homepage": "https://github.com/stianalars1/react-light-beam#readme",
|
|
56
49
|
"peerDependencies": {
|
|
57
|
-
"
|
|
58
|
-
"
|
|
50
|
+
"@emotion/is-prop-valid": "^1.3.1",
|
|
51
|
+
"framer-motion": "^11.11.1",
|
|
52
|
+
"react": "^18 || ^19",
|
|
53
|
+
"react-dom": "^18 || ^19"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"framer-motion": {
|
|
57
|
+
"optional": false
|
|
58
|
+
},
|
|
59
|
+
"@emotion/is-prop-valid": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
59
62
|
},
|
|
60
63
|
"devDependencies": {
|
|
61
|
-
"@types/react": "^19
|
|
62
|
-
"@types/react-dom": "^19
|
|
63
|
-
"react": "^19.2.3",
|
|
64
|
-
"react-dom": "^19.2.3",
|
|
64
|
+
"@types/react": "^18 || ^19",
|
|
65
|
+
"@types/react-dom": "^18 || ^19",
|
|
65
66
|
"tsup": "^8.5.1",
|
|
66
67
|
"typescript": "^5.5.4"
|
|
67
68
|
}
|
package/dist/index.d.mts
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
|
|
3
|
-
type DustParticlesConfig = {
|
|
4
|
-
/**
|
|
5
|
-
* Enable floating dust particles in the beam
|
|
6
|
-
* @default false
|
|
7
|
-
*/
|
|
8
|
-
enabled?: boolean;
|
|
9
|
-
/**
|
|
10
|
-
* Number of dust particles
|
|
11
|
-
* @default 30
|
|
12
|
-
*/
|
|
13
|
-
count?: number;
|
|
14
|
-
/**
|
|
15
|
-
* Animation speed multiplier (1 = normal, 2 = twice as fast)
|
|
16
|
-
* @default 1
|
|
17
|
-
*/
|
|
18
|
-
speed?: number;
|
|
19
|
-
/**
|
|
20
|
-
* Particle size range [min, max] in pixels
|
|
21
|
-
* @default [1, 3]
|
|
22
|
-
*/
|
|
23
|
-
sizeRange?: [number, number];
|
|
24
|
-
/**
|
|
25
|
-
* Particle opacity range [min, max]
|
|
26
|
-
* @default [0.2, 0.6]
|
|
27
|
-
*/
|
|
28
|
-
opacityRange?: [number, number];
|
|
29
|
-
/**
|
|
30
|
-
* Particle color (inherits beam color if not specified)
|
|
31
|
-
*/
|
|
32
|
-
color?: string;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
type MistConfig = {
|
|
36
|
-
/**
|
|
37
|
-
* Enable mist/fog effect
|
|
38
|
-
* @default false
|
|
39
|
-
*/
|
|
40
|
-
enabled?: boolean;
|
|
41
|
-
/**
|
|
42
|
-
* Mist intensity (0-1)
|
|
43
|
-
* @default 0.3
|
|
44
|
-
*/
|
|
45
|
-
intensity?: number;
|
|
46
|
-
/**
|
|
47
|
-
* Animation speed multiplier
|
|
48
|
-
* @default 1
|
|
49
|
-
*/
|
|
50
|
-
speed?: number;
|
|
51
|
-
/**
|
|
52
|
-
* Number of layered mist effects for depth
|
|
53
|
-
* @default 2
|
|
54
|
-
*/
|
|
55
|
-
layers?: number;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
type PulseConfig = {
|
|
59
|
-
/**
|
|
60
|
-
* Enable rhythmic pulse effect
|
|
61
|
-
* @default false
|
|
62
|
-
*/
|
|
63
|
-
enabled?: boolean;
|
|
64
|
-
/**
|
|
65
|
-
* Pulse duration in seconds
|
|
66
|
-
* @default 2
|
|
67
|
-
*/
|
|
68
|
-
duration?: number;
|
|
69
|
-
/**
|
|
70
|
-
* Pulse intensity (0-1) - how much brightness varies
|
|
71
|
-
* @default 0.2
|
|
72
|
-
*/
|
|
73
|
-
intensity?: number;
|
|
74
|
-
/**
|
|
75
|
-
* GSAP easing function
|
|
76
|
-
* @default "sine.inOut"
|
|
77
|
-
*/
|
|
78
|
-
easing?: string;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
type LightBeamProps = {
|
|
82
|
-
className?: string;
|
|
83
|
-
/**
|
|
84
|
-
* Custom styles to merge with or override default styles.
|
|
85
|
-
* User styles take priority over defaults.
|
|
86
|
-
* @example style={{ height: '800px', width: '80vw' }}
|
|
87
|
-
*/
|
|
88
|
-
style?: React.CSSProperties;
|
|
89
|
-
fullWidth?: number;
|
|
90
|
-
colorLightmode?: string;
|
|
91
|
-
colorDarkmode?: string;
|
|
92
|
-
maskLightByProgress?: boolean;
|
|
93
|
-
invert?: boolean;
|
|
94
|
-
id?: string;
|
|
95
|
-
scrollElement?: EventTarget;
|
|
96
|
-
onLoaded?: () => void;
|
|
97
|
-
/**
|
|
98
|
-
* Disable default inline styles. Set to true if you want to provide custom CSS via className only.
|
|
99
|
-
* @default false
|
|
100
|
-
*/
|
|
101
|
-
disableDefaultStyles?: boolean;
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* GSAP ScrollTrigger start position
|
|
105
|
-
* @default "top bottom"
|
|
106
|
-
* @example "top center", "center bottom", "top 80%"
|
|
107
|
-
*/
|
|
108
|
-
scrollStart?: string;
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* GSAP ScrollTrigger end position
|
|
112
|
-
* @default "top top"
|
|
113
|
-
* @example "top 20%", "center center", "bottom top"
|
|
114
|
-
*/
|
|
115
|
-
scrollEnd?: string;
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Dust particles configuration
|
|
119
|
-
* @example dustParticles={{ enabled: true, count: 50, speed: 1.5 }}
|
|
120
|
-
*/
|
|
121
|
-
dustParticles?: DustParticlesConfig;
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Mist/fog effect configuration
|
|
125
|
-
* @example mist={{ enabled: true, intensity: 0.5, layers: 3 }}
|
|
126
|
-
*/
|
|
127
|
-
mist?: MistConfig;
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Pulse effect configuration
|
|
131
|
-
* @example pulse={{ enabled: true, duration: 3, intensity: 0.3 }}
|
|
132
|
-
*/
|
|
133
|
-
pulse?: PulseConfig;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
declare const LightBeam: ({ className, style, colorLightmode, colorDarkmode, maskLightByProgress, fullWidth, invert, id, onLoaded, scrollElement, disableDefaultStyles, scrollStart, scrollEnd, dustParticles, mist, pulse, }: LightBeamProps) => react_jsx_runtime.JSX.Element;
|
|
137
|
-
|
|
138
|
-
export { LightBeam };
|
package/dist/index.mjs
DELETED
|
@@ -1,450 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import gsap3 from 'gsap';
|
|
3
|
-
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
4
|
-
import { useGSAP } from '@gsap/react';
|
|
5
|
-
import { useRef, useEffect, useState, useMemo } from 'react';
|
|
6
|
-
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
7
|
-
|
|
8
|
-
var useIsDarkmode = () => {
|
|
9
|
-
const [isDarkmode, setIsDarkmodeActive] = useState(false);
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
const matchMedia = window.matchMedia("(prefers-color-scheme: dark)");
|
|
12
|
-
const handleChange = () => {
|
|
13
|
-
console.log("Darkmode match?", matchMedia.matches);
|
|
14
|
-
setIsDarkmodeActive(matchMedia.matches);
|
|
15
|
-
};
|
|
16
|
-
setIsDarkmodeActive(matchMedia.matches);
|
|
17
|
-
matchMedia.addEventListener("change", handleChange);
|
|
18
|
-
handleChange();
|
|
19
|
-
return () => {
|
|
20
|
-
matchMedia.removeEventListener("change", handleChange);
|
|
21
|
-
};
|
|
22
|
-
}, []);
|
|
23
|
-
return { isDarkmode };
|
|
24
|
-
};
|
|
25
|
-
var DustParticles = ({ config, beamColor }) => {
|
|
26
|
-
const {
|
|
27
|
-
enabled = false,
|
|
28
|
-
count = 30,
|
|
29
|
-
speed = 1,
|
|
30
|
-
sizeRange = [1, 3],
|
|
31
|
-
opacityRange = [0.2, 0.6],
|
|
32
|
-
color
|
|
33
|
-
} = config;
|
|
34
|
-
const particles = useMemo(() => {
|
|
35
|
-
if (!enabled) return [];
|
|
36
|
-
return Array.from({ length: count }, (_, i) => {
|
|
37
|
-
const x = Math.random() * 100;
|
|
38
|
-
const y = Math.random() * 100;
|
|
39
|
-
const size = sizeRange[0] + Math.random() * (sizeRange[1] - sizeRange[0]);
|
|
40
|
-
const opacity = opacityRange[0] + Math.random() * (opacityRange[1] - opacityRange[0]);
|
|
41
|
-
const duration = (3 + Math.random() * 4) / speed;
|
|
42
|
-
const delay = Math.random() * duration;
|
|
43
|
-
return {
|
|
44
|
-
id: `dust-${i}`,
|
|
45
|
-
x,
|
|
46
|
-
y,
|
|
47
|
-
size,
|
|
48
|
-
opacity,
|
|
49
|
-
duration,
|
|
50
|
-
delay
|
|
51
|
-
};
|
|
52
|
-
});
|
|
53
|
-
}, [enabled, count, sizeRange, opacityRange, speed]);
|
|
54
|
-
useGSAP(
|
|
55
|
-
() => {
|
|
56
|
-
if (!enabled || particles.length === 0) return;
|
|
57
|
-
const timelines = [];
|
|
58
|
-
particles.forEach((particle) => {
|
|
59
|
-
const element = document.getElementById(particle.id);
|
|
60
|
-
if (!element) return;
|
|
61
|
-
const tl = gsap3.timeline({
|
|
62
|
-
repeat: -1,
|
|
63
|
-
yoyo: true,
|
|
64
|
-
delay: particle.delay
|
|
65
|
-
});
|
|
66
|
-
tl.to(element, {
|
|
67
|
-
y: `-=${20 + Math.random() * 30}`,
|
|
68
|
-
// Float upward 20-50px
|
|
69
|
-
x: `+=${Math.random() * 20 - 10}`,
|
|
70
|
-
// Slight horizontal drift ±10px
|
|
71
|
-
opacity: particle.opacity * 0.5,
|
|
72
|
-
// Fade slightly
|
|
73
|
-
duration: particle.duration,
|
|
74
|
-
ease: "sine.inOut"
|
|
75
|
-
});
|
|
76
|
-
timelines.push(tl);
|
|
77
|
-
});
|
|
78
|
-
return () => {
|
|
79
|
-
timelines.forEach((tl) => tl.kill());
|
|
80
|
-
};
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
dependencies: [particles, enabled]
|
|
84
|
-
}
|
|
85
|
-
);
|
|
86
|
-
if (!enabled) return null;
|
|
87
|
-
const particleColor = color || beamColor;
|
|
88
|
-
return /* @__PURE__ */ jsx(Fragment, { children: particles.map((particle) => /* @__PURE__ */ jsx(
|
|
89
|
-
"div",
|
|
90
|
-
{
|
|
91
|
-
id: particle.id,
|
|
92
|
-
style: {
|
|
93
|
-
position: "absolute",
|
|
94
|
-
left: `${particle.x}%`,
|
|
95
|
-
top: `${particle.y}%`,
|
|
96
|
-
width: `${particle.size}px`,
|
|
97
|
-
height: `${particle.size}px`,
|
|
98
|
-
borderRadius: "50%",
|
|
99
|
-
backgroundColor: particleColor,
|
|
100
|
-
opacity: particle.opacity,
|
|
101
|
-
pointerEvents: "none",
|
|
102
|
-
willChange: "transform, opacity"
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
particle.id
|
|
106
|
-
)) });
|
|
107
|
-
};
|
|
108
|
-
var MistEffect = ({ config, beamColor }) => {
|
|
109
|
-
const {
|
|
110
|
-
enabled = false,
|
|
111
|
-
intensity = 0.3,
|
|
112
|
-
speed = 1,
|
|
113
|
-
layers = 2
|
|
114
|
-
} = config;
|
|
115
|
-
const mistLayers = useMemo(() => {
|
|
116
|
-
if (!enabled) return [];
|
|
117
|
-
return Array.from({ length: layers }, (_, i) => {
|
|
118
|
-
const layerOpacity = intensity * 0.6 / (i + 1);
|
|
119
|
-
const duration = (8 + i * 3) / speed;
|
|
120
|
-
const delay = i * 1.5 / speed;
|
|
121
|
-
const scale = 1 + i * 0.2;
|
|
122
|
-
return {
|
|
123
|
-
id: `mist-layer-${i}`,
|
|
124
|
-
opacity: layerOpacity,
|
|
125
|
-
duration,
|
|
126
|
-
delay,
|
|
127
|
-
scale
|
|
128
|
-
};
|
|
129
|
-
});
|
|
130
|
-
}, [enabled, intensity, speed, layers]);
|
|
131
|
-
useGSAP(
|
|
132
|
-
() => {
|
|
133
|
-
if (!enabled || mistLayers.length === 0) return;
|
|
134
|
-
const timelines = [];
|
|
135
|
-
mistLayers.forEach((layer) => {
|
|
136
|
-
const element = document.getElementById(layer.id);
|
|
137
|
-
if (!element) return;
|
|
138
|
-
const tl = gsap3.timeline({
|
|
139
|
-
repeat: -1,
|
|
140
|
-
yoyo: false
|
|
141
|
-
});
|
|
142
|
-
tl.fromTo(
|
|
143
|
-
element,
|
|
144
|
-
{
|
|
145
|
-
x: "-100%",
|
|
146
|
-
opacity: 0
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
x: "100%",
|
|
150
|
-
opacity: layer.opacity,
|
|
151
|
-
duration: layer.duration,
|
|
152
|
-
ease: "none",
|
|
153
|
-
delay: layer.delay
|
|
154
|
-
}
|
|
155
|
-
).to(element, {
|
|
156
|
-
opacity: 0,
|
|
157
|
-
duration: layer.duration * 0.2,
|
|
158
|
-
ease: "power1.in"
|
|
159
|
-
});
|
|
160
|
-
timelines.push(tl);
|
|
161
|
-
});
|
|
162
|
-
return () => {
|
|
163
|
-
timelines.forEach((tl) => tl.kill());
|
|
164
|
-
};
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
dependencies: [mistLayers, enabled]
|
|
168
|
-
}
|
|
169
|
-
);
|
|
170
|
-
if (!enabled) return null;
|
|
171
|
-
const mistColor = beamColor.replace(/[\d.]+\)$/g, `${intensity})`);
|
|
172
|
-
return /* @__PURE__ */ jsx(Fragment, { children: mistLayers.map((layer) => /* @__PURE__ */ jsx(
|
|
173
|
-
"div",
|
|
174
|
-
{
|
|
175
|
-
id: layer.id,
|
|
176
|
-
style: {
|
|
177
|
-
position: "absolute",
|
|
178
|
-
top: 0,
|
|
179
|
-
left: 0,
|
|
180
|
-
width: "100%",
|
|
181
|
-
height: "100%",
|
|
182
|
-
background: `radial-gradient(ellipse 120% 80% at 50% 20%, ${mistColor}, transparent 70%)`,
|
|
183
|
-
opacity: 0,
|
|
184
|
-
pointerEvents: "none",
|
|
185
|
-
willChange: "transform, opacity",
|
|
186
|
-
transform: `scale(${layer.scale})`,
|
|
187
|
-
filter: "blur(40px)"
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
layer.id
|
|
191
|
-
)) });
|
|
192
|
-
};
|
|
193
|
-
var PulseEffect = ({ config, containerRef }) => {
|
|
194
|
-
const {
|
|
195
|
-
enabled = false,
|
|
196
|
-
duration = 2,
|
|
197
|
-
intensity = 0.2,
|
|
198
|
-
easing = "sine.inOut"
|
|
199
|
-
} = config;
|
|
200
|
-
useGSAP(
|
|
201
|
-
() => {
|
|
202
|
-
if (!enabled || !containerRef.current) return;
|
|
203
|
-
const element = containerRef.current;
|
|
204
|
-
const timeline = gsap3.timeline({
|
|
205
|
-
repeat: -1,
|
|
206
|
-
// Infinite loop
|
|
207
|
-
yoyo: true
|
|
208
|
-
// Reverse on each iteration
|
|
209
|
-
});
|
|
210
|
-
const maxMultiplier = Math.min(2, 1 + intensity);
|
|
211
|
-
timeline.fromTo(
|
|
212
|
-
element,
|
|
213
|
-
{
|
|
214
|
-
"--pulse-multiplier": 1
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
"--pulse-multiplier": maxMultiplier,
|
|
218
|
-
duration,
|
|
219
|
-
ease: easing
|
|
220
|
-
}
|
|
221
|
-
);
|
|
222
|
-
const updateOpacity = () => {
|
|
223
|
-
const baseOpacity = getComputedStyle(element).getPropertyValue("--base-opacity") || "1";
|
|
224
|
-
const pulseMultiplier = getComputedStyle(element).getPropertyValue("--pulse-multiplier") || "1";
|
|
225
|
-
element.style.opacity = `calc(${baseOpacity} * ${pulseMultiplier})`;
|
|
226
|
-
};
|
|
227
|
-
const ticker = gsap3.ticker.add(updateOpacity);
|
|
228
|
-
return () => {
|
|
229
|
-
timeline.kill();
|
|
230
|
-
gsap3.ticker.remove(ticker);
|
|
231
|
-
};
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
dependencies: [enabled, duration, intensity, easing],
|
|
235
|
-
scope: containerRef
|
|
236
|
-
}
|
|
237
|
-
);
|
|
238
|
-
return null;
|
|
239
|
-
};
|
|
240
|
-
gsap3.registerPlugin(ScrollTrigger, useGSAP);
|
|
241
|
-
var defaultStyles = {
|
|
242
|
-
height: "var(--react-light-beam-height, 500px)",
|
|
243
|
-
width: "var(--react-light-beam-width, 100vw)",
|
|
244
|
-
// CRITICAL: NO transition on GSAP-controlled properties (background, opacity, mask)
|
|
245
|
-
// Transitions would fight with GSAP's instant updates, causing visual glitches
|
|
246
|
-
// especially when scroll direction changes
|
|
247
|
-
transition: "none",
|
|
248
|
-
willChange: "background, opacity",
|
|
249
|
-
// Specific properties for better performance
|
|
250
|
-
userSelect: "none",
|
|
251
|
-
pointerEvents: "none",
|
|
252
|
-
contain: "layout style paint",
|
|
253
|
-
// CSS containment for better performance
|
|
254
|
-
WebkitTransition: "none",
|
|
255
|
-
WebkitUserSelect: "none",
|
|
256
|
-
MozUserSelect: "none"
|
|
257
|
-
};
|
|
258
|
-
var LightBeam = ({
|
|
259
|
-
className,
|
|
260
|
-
style,
|
|
261
|
-
colorLightmode = "rgba(0,0,0, 0.5)",
|
|
262
|
-
colorDarkmode = "rgba(255, 255, 255, 0.5)",
|
|
263
|
-
maskLightByProgress = false,
|
|
264
|
-
fullWidth = 1,
|
|
265
|
-
// Default to full width range
|
|
266
|
-
invert = false,
|
|
267
|
-
id = void 0,
|
|
268
|
-
onLoaded = void 0,
|
|
269
|
-
scrollElement,
|
|
270
|
-
disableDefaultStyles = false,
|
|
271
|
-
scrollStart = "top bottom",
|
|
272
|
-
scrollEnd = "top top",
|
|
273
|
-
dustParticles = { enabled: false },
|
|
274
|
-
mist = { enabled: false },
|
|
275
|
-
pulse = { enabled: false }
|
|
276
|
-
}) => {
|
|
277
|
-
const elementRef = useRef(null);
|
|
278
|
-
const { isDarkmode } = useIsDarkmode();
|
|
279
|
-
const chosenColor = isDarkmode ? colorDarkmode : colorLightmode;
|
|
280
|
-
const colorRef = useRef(chosenColor);
|
|
281
|
-
const invertRef = useRef(invert);
|
|
282
|
-
const maskByProgressRef = useRef(maskLightByProgress);
|
|
283
|
-
const scrollTriggerRef = useRef(null);
|
|
284
|
-
useEffect(() => {
|
|
285
|
-
colorRef.current = chosenColor;
|
|
286
|
-
if (elementRef.current) {
|
|
287
|
-
elementRef.current.style.setProperty("--beam-color", chosenColor);
|
|
288
|
-
}
|
|
289
|
-
}, [chosenColor, colorLightmode, colorDarkmode]);
|
|
290
|
-
useEffect(() => {
|
|
291
|
-
const prevInvert = invertRef.current;
|
|
292
|
-
invertRef.current = invert;
|
|
293
|
-
if (prevInvert !== invert && scrollTriggerRef.current && elementRef.current) {
|
|
294
|
-
const st = scrollTriggerRef.current;
|
|
295
|
-
elementRef.current;
|
|
296
|
-
st.refresh();
|
|
297
|
-
}
|
|
298
|
-
}, [invert]);
|
|
299
|
-
useEffect(() => {
|
|
300
|
-
const prevMaskByProgress = maskByProgressRef.current;
|
|
301
|
-
maskByProgressRef.current = maskLightByProgress;
|
|
302
|
-
if (prevMaskByProgress !== maskLightByProgress && elementRef.current) {
|
|
303
|
-
const element = elementRef.current;
|
|
304
|
-
if (maskLightByProgress) {
|
|
305
|
-
element.style.setProperty("--beam-mask-stop", "50%");
|
|
306
|
-
element.style.maskImage = `linear-gradient(to bottom, var(--beam-color) 0%, transparent var(--beam-mask-stop))`;
|
|
307
|
-
element.style.webkitMaskImage = `linear-gradient(to bottom, var(--beam-color) 0%, transparent var(--beam-mask-stop))`;
|
|
308
|
-
} else {
|
|
309
|
-
element.style.maskImage = `linear-gradient(to bottom, var(--beam-color) 25%, transparent 95%)`;
|
|
310
|
-
element.style.webkitMaskImage = `linear-gradient(to bottom, var(--beam-color) 25%, transparent 95%)`;
|
|
311
|
-
}
|
|
312
|
-
if (scrollTriggerRef.current) {
|
|
313
|
-
scrollTriggerRef.current.refresh();
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}, [maskLightByProgress]);
|
|
317
|
-
useEffect(() => {
|
|
318
|
-
onLoaded && onLoaded();
|
|
319
|
-
}, []);
|
|
320
|
-
useGSAP(
|
|
321
|
-
() => {
|
|
322
|
-
const element = elementRef.current;
|
|
323
|
-
if (!element || typeof window === "undefined") return;
|
|
324
|
-
const opacityMin = 0.839322;
|
|
325
|
-
const opacityRange = 0.160678;
|
|
326
|
-
const updateColorVar = (color) => {
|
|
327
|
-
element.style.setProperty("--beam-color", color);
|
|
328
|
-
};
|
|
329
|
-
const initGradientStructure = (color) => {
|
|
330
|
-
updateColorVar(color);
|
|
331
|
-
const baseGradient = `conic-gradient(from 90deg at var(--beam-left-pos) 0%, var(--beam-color), transparent 180deg) 0% 0% / 50% var(--beam-left-size) no-repeat, conic-gradient(from 270deg at var(--beam-right-pos) 0%, transparent 180deg, var(--beam-color)) 100% 0% / 50% 100% no-repeat`;
|
|
332
|
-
element.style.background = baseGradient;
|
|
333
|
-
if (maskByProgressRef.current) {
|
|
334
|
-
element.style.maskImage = `linear-gradient(to bottom, var(--beam-color) 0%, transparent var(--beam-mask-stop))`;
|
|
335
|
-
element.style.webkitMaskImage = `linear-gradient(to bottom, var(--beam-color) 0%, transparent var(--beam-mask-stop))`;
|
|
336
|
-
} else {
|
|
337
|
-
element.style.maskImage = `linear-gradient(to bottom, var(--beam-color) 25%, transparent 95%)`;
|
|
338
|
-
element.style.webkitMaskImage = `linear-gradient(to bottom, var(--beam-color) 25%, transparent 95%)`;
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
const adjustedFullWidth = 1 - fullWidth;
|
|
342
|
-
const calculateProgress = (rawProgress) => {
|
|
343
|
-
const normalizedPosition = Math.max(
|
|
344
|
-
adjustedFullWidth,
|
|
345
|
-
// Floor value (1 - fullWidth)
|
|
346
|
-
Math.min(1, 1 - rawProgress)
|
|
347
|
-
// Inverted GSAP progress
|
|
348
|
-
);
|
|
349
|
-
return invertRef.current ? normalizedPosition : 1 - normalizedPosition;
|
|
350
|
-
};
|
|
351
|
-
const scroller = scrollElement ? scrollElement : void 0;
|
|
352
|
-
const applyProgressState = (progress) => {
|
|
353
|
-
const leftPos = 90 - progress * 90;
|
|
354
|
-
const rightPos = 10 + progress * 90;
|
|
355
|
-
const leftSize = 150 - progress * 50;
|
|
356
|
-
const baseOpacity = opacityMin + opacityRange * progress;
|
|
357
|
-
const maskStop = maskByProgressRef.current ? 50 + progress * 45 : void 0;
|
|
358
|
-
const cssProps = {
|
|
359
|
-
"--beam-left-pos": `${leftPos}%`,
|
|
360
|
-
"--beam-right-pos": `${rightPos}%`,
|
|
361
|
-
"--beam-left-size": `${leftSize}%`,
|
|
362
|
-
"--base-opacity": baseOpacity
|
|
363
|
-
};
|
|
364
|
-
if (maskStop !== void 0) {
|
|
365
|
-
cssProps["--beam-mask-stop"] = `${maskStop}%`;
|
|
366
|
-
}
|
|
367
|
-
if (!pulse.enabled) {
|
|
368
|
-
cssProps.opacity = baseOpacity;
|
|
369
|
-
}
|
|
370
|
-
gsap3.set(element, cssProps);
|
|
371
|
-
};
|
|
372
|
-
initGradientStructure(colorRef.current);
|
|
373
|
-
const st = ScrollTrigger.create({
|
|
374
|
-
trigger: element,
|
|
375
|
-
start: scrollStart,
|
|
376
|
-
// When to start the animation
|
|
377
|
-
end: scrollEnd,
|
|
378
|
-
// When to end the animation
|
|
379
|
-
scroller,
|
|
380
|
-
scrub: 0.15,
|
|
381
|
-
// Fast catch-up (300ms) for responsive scroll without jitter
|
|
382
|
-
onUpdate: (self) => {
|
|
383
|
-
const progress = calculateProgress(self.progress);
|
|
384
|
-
applyProgressState(progress);
|
|
385
|
-
},
|
|
386
|
-
onRefresh: (self) => {
|
|
387
|
-
const progress = calculateProgress(self.progress);
|
|
388
|
-
applyProgressState(progress);
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
scrollTriggerRef.current = st;
|
|
392
|
-
const initialProgress = calculateProgress(st.progress);
|
|
393
|
-
applyProgressState(initialProgress);
|
|
394
|
-
const refreshTimeout = setTimeout(() => {
|
|
395
|
-
ScrollTrigger.refresh();
|
|
396
|
-
}, 100);
|
|
397
|
-
return () => {
|
|
398
|
-
st.kill();
|
|
399
|
-
clearTimeout(refreshTimeout);
|
|
400
|
-
};
|
|
401
|
-
},
|
|
402
|
-
{
|
|
403
|
-
// CRITICAL: Use refs for frequently changing values!
|
|
404
|
-
// colorRef, invertRef, maskByProgressRef allow updates without recreating ScrollTrigger
|
|
405
|
-
// This prevents visual glitches when these values change mid-scroll
|
|
406
|
-
// Only include values that affect ScrollTrigger's position/range calculations
|
|
407
|
-
dependencies: [
|
|
408
|
-
fullWidth,
|
|
409
|
-
// Affects progress range calculation
|
|
410
|
-
scrollElement,
|
|
411
|
-
// Affects which element to watch
|
|
412
|
-
scrollStart,
|
|
413
|
-
// Affects when animation starts
|
|
414
|
-
scrollEnd
|
|
415
|
-
// Affects when animation ends
|
|
416
|
-
],
|
|
417
|
-
scope: elementRef
|
|
418
|
-
}
|
|
419
|
-
);
|
|
420
|
-
const combinedClassName = `react-light-beam ${className || ""}`.trim();
|
|
421
|
-
const finalStyles = disableDefaultStyles ? {
|
|
422
|
-
// No default styles, only user styles
|
|
423
|
-
willChange: "background, opacity",
|
|
424
|
-
contain: "layout style paint",
|
|
425
|
-
...style
|
|
426
|
-
// User styles override
|
|
427
|
-
} : {
|
|
428
|
-
// Merge default styles with user styles
|
|
429
|
-
...defaultStyles,
|
|
430
|
-
...style
|
|
431
|
-
// User styles override everything
|
|
432
|
-
};
|
|
433
|
-
return /* @__PURE__ */ jsxs(
|
|
434
|
-
"div",
|
|
435
|
-
{
|
|
436
|
-
ref: elementRef,
|
|
437
|
-
className: combinedClassName,
|
|
438
|
-
style: finalStyles,
|
|
439
|
-
...id ? { id } : {},
|
|
440
|
-
children: [
|
|
441
|
-
dustParticles.enabled && /* @__PURE__ */ jsx(DustParticles, { config: dustParticles, beamColor: chosenColor }),
|
|
442
|
-
mist.enabled && /* @__PURE__ */ jsx(MistEffect, { config: mist, beamColor: chosenColor }),
|
|
443
|
-
pulse.enabled && /* @__PURE__ */ jsx(PulseEffect, { config: pulse, containerRef: elementRef })
|
|
444
|
-
]
|
|
445
|
-
}
|
|
446
|
-
);
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
export { LightBeam };
|
|
450
|
-
//# sourceMappingURL=index.mjs.map
|