@stianlarsen/react-light-beam 2.1.2 → 3.1.0

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/dist/index.js CHANGED
@@ -1,65 +1,249 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
- var gsap = require('gsap');
4
+ var gsap3 = require('gsap');
5
5
  var ScrollTrigger = require('gsap/ScrollTrigger');
6
- var react = require('react');
6
+ var react = require('@gsap/react');
7
+ var react$1 = require('react');
7
8
  var jsxRuntime = require('react/jsx-runtime');
8
9
 
9
10
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
11
 
11
- var gsap__default = /*#__PURE__*/_interopDefault(gsap);
12
+ var gsap3__default = /*#__PURE__*/_interopDefault(gsap3);
12
13
 
13
- var useIsomorphicLayoutEffect = typeof document !== "undefined" ? react.useLayoutEffect : react.useEffect;
14
- var isConfig = (value) => value && !Array.isArray(value) && typeof value === "object";
15
- var emptyArray = [];
16
- var defaultConfig = {};
17
- var _gsap = gsap__default.default;
18
- var useGSAP = (callback, dependencies = emptyArray) => {
19
- let config = defaultConfig;
20
- if (isConfig(callback)) {
21
- config = callback;
22
- callback = null;
23
- dependencies = "dependencies" in config ? config.dependencies : emptyArray;
24
- } else if (isConfig(dependencies)) {
25
- config = dependencies;
26
- dependencies = "dependencies" in config ? config.dependencies : emptyArray;
27
- }
28
- callback && typeof callback !== "function" && console.warn("First parameter must be a function or config object");
29
- const { scope, revertOnUpdate } = config, mounted = react.useRef(false), context = react.useRef(_gsap.context(() => {
30
- }, scope)), contextSafe = react.useRef((func) => context.current.add(null, func)), deferCleanup = dependencies && dependencies.length && !revertOnUpdate;
31
- deferCleanup && useIsomorphicLayoutEffect(() => {
32
- mounted.current = true;
33
- return () => context.current.revert();
34
- }, emptyArray);
35
- useIsomorphicLayoutEffect(() => {
36
- callback && context.current.add(callback, scope);
37
- if (!deferCleanup || !mounted.current) {
38
- return () => context.current.revert();
39
- }
40
- }, dependencies);
41
- return { context: context.current, contextSafe: contextSafe.current };
42
- };
43
- useGSAP.register = (core) => {
44
- _gsap = core;
45
- };
46
- useGSAP.headless = true;
47
14
  var useIsDarkmode = () => {
48
- const [isDarkmode, setIsDarkmodeActive] = react.useState(false);
49
- react.useEffect(() => {
15
+ const [isDarkmode, setIsDarkmodeActive] = react$1.useState(false);
16
+ react$1.useEffect(() => {
50
17
  const matchMedia = window.matchMedia("(prefers-color-scheme: dark)");
51
18
  const handleChange = () => {
19
+ console.log("Darkmode match?", matchMedia.matches);
52
20
  setIsDarkmodeActive(matchMedia.matches);
53
21
  };
54
22
  setIsDarkmodeActive(matchMedia.matches);
55
23
  matchMedia.addEventListener("change", handleChange);
24
+ handleChange();
56
25
  return () => {
57
26
  matchMedia.removeEventListener("change", handleChange);
58
27
  };
59
28
  }, []);
60
29
  return { isDarkmode };
61
30
  };
62
- gsap__default.default.registerPlugin(ScrollTrigger.ScrollTrigger, useGSAP);
31
+ var DustParticles = ({ config, beamColor }) => {
32
+ const {
33
+ enabled = false,
34
+ count = 30,
35
+ speed = 1,
36
+ sizeRange = [1, 3],
37
+ opacityRange = [0.2, 0.6],
38
+ color
39
+ } = config;
40
+ const particles = react$1.useMemo(() => {
41
+ if (!enabled) return [];
42
+ return Array.from({ length: count }, (_, i) => {
43
+ const x = Math.random() * 100;
44
+ const y = Math.random() * 100;
45
+ const size = sizeRange[0] + Math.random() * (sizeRange[1] - sizeRange[0]);
46
+ const opacity = opacityRange[0] + Math.random() * (opacityRange[1] - opacityRange[0]);
47
+ const duration = (3 + Math.random() * 4) / speed;
48
+ const delay = Math.random() * duration;
49
+ return {
50
+ id: `dust-${i}`,
51
+ x,
52
+ y,
53
+ size,
54
+ opacity,
55
+ duration,
56
+ delay
57
+ };
58
+ });
59
+ }, [enabled, count, sizeRange, opacityRange, speed]);
60
+ react.useGSAP(
61
+ () => {
62
+ if (!enabled || particles.length === 0) return;
63
+ const timelines = [];
64
+ particles.forEach((particle) => {
65
+ const element = document.getElementById(particle.id);
66
+ if (!element) return;
67
+ const tl = gsap3__default.default.timeline({
68
+ repeat: -1,
69
+ yoyo: true,
70
+ delay: particle.delay
71
+ });
72
+ tl.to(element, {
73
+ y: `-=${20 + Math.random() * 30}`,
74
+ // Float upward 20-50px
75
+ x: `+=${Math.random() * 20 - 10}`,
76
+ // Slight horizontal drift ±10px
77
+ opacity: particle.opacity * 0.5,
78
+ // Fade slightly
79
+ duration: particle.duration,
80
+ ease: "sine.inOut"
81
+ });
82
+ timelines.push(tl);
83
+ });
84
+ return () => {
85
+ timelines.forEach((tl) => tl.kill());
86
+ };
87
+ },
88
+ {
89
+ dependencies: [particles, enabled]
90
+ }
91
+ );
92
+ if (!enabled) return null;
93
+ const particleColor = color || beamColor;
94
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: particles.map((particle) => /* @__PURE__ */ jsxRuntime.jsx(
95
+ "div",
96
+ {
97
+ id: particle.id,
98
+ style: {
99
+ position: "absolute",
100
+ left: `${particle.x}%`,
101
+ top: `${particle.y}%`,
102
+ width: `${particle.size}px`,
103
+ height: `${particle.size}px`,
104
+ borderRadius: "50%",
105
+ backgroundColor: particleColor,
106
+ opacity: particle.opacity,
107
+ pointerEvents: "none",
108
+ willChange: "transform, opacity"
109
+ }
110
+ },
111
+ particle.id
112
+ )) });
113
+ };
114
+ var MistEffect = ({ config, beamColor }) => {
115
+ const {
116
+ enabled = false,
117
+ intensity = 0.3,
118
+ speed = 1,
119
+ layers = 2
120
+ } = config;
121
+ const mistLayers = react$1.useMemo(() => {
122
+ if (!enabled) return [];
123
+ return Array.from({ length: layers }, (_, i) => {
124
+ const layerOpacity = intensity * 0.6 / (i + 1);
125
+ const duration = (8 + i * 3) / speed;
126
+ const delay = i * 1.5 / speed;
127
+ const scale = 1 + i * 0.2;
128
+ return {
129
+ id: `mist-layer-${i}`,
130
+ opacity: layerOpacity,
131
+ duration,
132
+ delay,
133
+ scale
134
+ };
135
+ });
136
+ }, [enabled, intensity, speed, layers]);
137
+ react.useGSAP(
138
+ () => {
139
+ if (!enabled || mistLayers.length === 0) return;
140
+ const timelines = [];
141
+ mistLayers.forEach((layer) => {
142
+ const element = document.getElementById(layer.id);
143
+ if (!element) return;
144
+ const tl = gsap3__default.default.timeline({
145
+ repeat: -1,
146
+ yoyo: false
147
+ });
148
+ tl.fromTo(
149
+ element,
150
+ {
151
+ x: "-100%",
152
+ opacity: 0
153
+ },
154
+ {
155
+ x: "100%",
156
+ opacity: layer.opacity,
157
+ duration: layer.duration,
158
+ ease: "none",
159
+ delay: layer.delay
160
+ }
161
+ ).to(element, {
162
+ opacity: 0,
163
+ duration: layer.duration * 0.2,
164
+ ease: "power1.in"
165
+ });
166
+ timelines.push(tl);
167
+ });
168
+ return () => {
169
+ timelines.forEach((tl) => tl.kill());
170
+ };
171
+ },
172
+ {
173
+ dependencies: [mistLayers, enabled]
174
+ }
175
+ );
176
+ if (!enabled) return null;
177
+ const mistColor = beamColor.replace(/[\d.]+\)$/g, `${intensity})`);
178
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: mistLayers.map((layer) => /* @__PURE__ */ jsxRuntime.jsx(
179
+ "div",
180
+ {
181
+ id: layer.id,
182
+ style: {
183
+ position: "absolute",
184
+ top: 0,
185
+ left: 0,
186
+ width: "100%",
187
+ height: "100%",
188
+ background: `radial-gradient(ellipse 120% 80% at 50% 20%, ${mistColor}, transparent 70%)`,
189
+ opacity: 0,
190
+ pointerEvents: "none",
191
+ willChange: "transform, opacity",
192
+ transform: `scale(${layer.scale})`,
193
+ filter: "blur(40px)"
194
+ }
195
+ },
196
+ layer.id
197
+ )) });
198
+ };
199
+ var PulseEffect = ({ config, containerRef }) => {
200
+ const {
201
+ enabled = false,
202
+ duration = 2,
203
+ intensity = 0.2,
204
+ easing = "sine.inOut"
205
+ } = config;
206
+ react.useGSAP(
207
+ () => {
208
+ if (!enabled || !containerRef.current) return;
209
+ const element = containerRef.current;
210
+ const timeline = gsap3__default.default.timeline({
211
+ repeat: -1,
212
+ // Infinite loop
213
+ yoyo: true
214
+ // Reverse on each iteration
215
+ });
216
+ const maxMultiplier = Math.min(2, 1 + intensity);
217
+ timeline.fromTo(
218
+ element,
219
+ {
220
+ "--pulse-multiplier": 1
221
+ },
222
+ {
223
+ "--pulse-multiplier": maxMultiplier,
224
+ duration,
225
+ ease: easing
226
+ }
227
+ );
228
+ const updateOpacity = () => {
229
+ const baseOpacity = getComputedStyle(element).getPropertyValue("--base-opacity") || "1";
230
+ const pulseMultiplier = getComputedStyle(element).getPropertyValue("--pulse-multiplier") || "1";
231
+ element.style.opacity = `calc(${baseOpacity} * ${pulseMultiplier})`;
232
+ };
233
+ const ticker = gsap3__default.default.ticker.add(updateOpacity);
234
+ return () => {
235
+ timeline.kill();
236
+ gsap3__default.default.ticker.remove(ticker);
237
+ };
238
+ },
239
+ {
240
+ dependencies: [enabled, duration, intensity, easing],
241
+ scope: containerRef
242
+ }
243
+ );
244
+ return null;
245
+ };
246
+ gsap3__default.default.registerPlugin(ScrollTrigger.ScrollTrigger, react.useGSAP);
63
247
  var defaultStyles = {
64
248
  height: "var(--react-light-beam-height, 500px)",
65
249
  width: "var(--react-light-beam-width, 100vw)",
@@ -84,86 +268,135 @@ var LightBeam = ({
84
268
  colorDarkmode = "rgba(255, 255, 255, 0.5)",
85
269
  maskLightByProgress = false,
86
270
  fullWidth = 1,
87
- // Default to full width
271
+ // Default to full width range
88
272
  invert = false,
89
273
  id = void 0,
90
274
  onLoaded = void 0,
91
275
  scrollElement,
92
- disableDefaultStyles = false
276
+ disableDefaultStyles = false,
277
+ scrollStart = "top bottom",
278
+ scrollEnd = "top top",
279
+ dustParticles = { enabled: false },
280
+ mist = { enabled: false },
281
+ pulse = { enabled: false }
93
282
  }) => {
94
- const elementRef = react.useRef(null);
283
+ const elementRef = react$1.useRef(null);
95
284
  const { isDarkmode } = useIsDarkmode();
96
285
  const chosenColor = isDarkmode ? colorDarkmode : colorLightmode;
97
- const colorRef = react.useRef(chosenColor);
98
- const invertRef = react.useRef(invert);
99
- const maskByProgressRef = react.useRef(maskLightByProgress);
100
- react.useEffect(() => {
286
+ const colorRef = react$1.useRef(chosenColor);
287
+ const invertRef = react$1.useRef(invert);
288
+ const maskByProgressRef = react$1.useRef(maskLightByProgress);
289
+ const scrollTriggerRef = react$1.useRef(null);
290
+ react$1.useEffect(() => {
101
291
  colorRef.current = chosenColor;
292
+ if (elementRef.current) {
293
+ elementRef.current.style.setProperty("--beam-color", chosenColor);
294
+ }
295
+ }, [chosenColor, colorLightmode, colorDarkmode]);
296
+ react$1.useEffect(() => {
297
+ const prevInvert = invertRef.current;
102
298
  invertRef.current = invert;
299
+ if (prevInvert !== invert && scrollTriggerRef.current && elementRef.current) {
300
+ const st = scrollTriggerRef.current;
301
+ elementRef.current;
302
+ st.refresh();
303
+ }
304
+ }, [invert]);
305
+ react$1.useEffect(() => {
306
+ const prevMaskByProgress = maskByProgressRef.current;
103
307
  maskByProgressRef.current = maskLightByProgress;
104
- }, [chosenColor, colorLightmode, colorDarkmode, invert, maskLightByProgress]);
105
- react.useEffect(() => {
308
+ if (prevMaskByProgress !== maskLightByProgress && elementRef.current) {
309
+ const element = elementRef.current;
310
+ if (maskLightByProgress) {
311
+ element.style.setProperty("--beam-mask-stop", "50%");
312
+ element.style.maskImage = `linear-gradient(to bottom, var(--beam-color) 0%, transparent var(--beam-mask-stop))`;
313
+ element.style.webkitMaskImage = `linear-gradient(to bottom, var(--beam-color) 0%, transparent var(--beam-mask-stop))`;
314
+ } else {
315
+ element.style.maskImage = `linear-gradient(to bottom, var(--beam-color) 25%, transparent 95%)`;
316
+ element.style.webkitMaskImage = `linear-gradient(to bottom, var(--beam-color) 25%, transparent 95%)`;
317
+ }
318
+ if (scrollTriggerRef.current) {
319
+ scrollTriggerRef.current.refresh();
320
+ }
321
+ }
322
+ }, [maskLightByProgress]);
323
+ react$1.useEffect(() => {
106
324
  onLoaded && onLoaded();
107
325
  }, []);
108
- useGSAP(
326
+ react.useGSAP(
109
327
  () => {
110
328
  const element = elementRef.current;
111
329
  if (!element || typeof window === "undefined") return;
112
330
  const opacityMin = 0.839322;
113
331
  const opacityRange = 0.160678;
114
- const interpolateBackground = (progress, color) => {
115
- const leftPos = 90 - progress * 90;
116
- const rightPos = 10 + progress * 90;
117
- const leftSize = 150 - progress * 50;
118
- return `conic-gradient(from 90deg at ${leftPos}% 0%, ${color}, transparent 180deg) 0% 0% / 50% ${leftSize}% no-repeat, conic-gradient(from 270deg at ${rightPos}% 0%, transparent 180deg, ${color}) 100% 0% / 50% 100% no-repeat`;
332
+ const updateColorVar = (color) => {
333
+ element.style.setProperty("--beam-color", color);
119
334
  };
120
- const interpolateMask = (progress, color) => {
121
- if (!maskByProgressRef.current) {
122
- return `linear-gradient(to bottom, ${color} 25%, transparent 95%)`;
335
+ const initGradientStructure = (color) => {
336
+ updateColorVar(color);
337
+ 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`;
338
+ element.style.background = baseGradient;
339
+ if (maskByProgressRef.current) {
340
+ element.style.maskImage = `linear-gradient(to bottom, var(--beam-color) 0%, transparent var(--beam-mask-stop))`;
341
+ element.style.webkitMaskImage = `linear-gradient(to bottom, var(--beam-color) 0%, transparent var(--beam-mask-stop))`;
342
+ } else {
343
+ element.style.maskImage = `linear-gradient(to bottom, var(--beam-color) 25%, transparent 95%)`;
344
+ element.style.webkitMaskImage = `linear-gradient(to bottom, var(--beam-color) 25%, transparent 95%)`;
123
345
  }
124
- const stopPoint = 50 + progress * 45;
125
- return `linear-gradient(to bottom, ${color} 0%, transparent ${stopPoint}%)`;
126
346
  };
127
347
  const adjustedFullWidth = 1 - fullWidth;
128
348
  const calculateProgress = (rawProgress) => {
129
349
  const normalizedPosition = Math.max(
130
350
  adjustedFullWidth,
131
- // Minimum (floor)
351
+ // Floor value (1 - fullWidth)
132
352
  Math.min(1, 1 - rawProgress)
133
- // Convert GSAP progress to Framer's normalized position
353
+ // Inverted GSAP progress
134
354
  );
135
355
  return invertRef.current ? normalizedPosition : 1 - normalizedPosition;
136
356
  };
137
357
  const scroller = scrollElement ? scrollElement : void 0;
358
+ const applyProgressState = (progress) => {
359
+ const leftPos = 90 - progress * 90;
360
+ const rightPos = 10 + progress * 90;
361
+ const leftSize = 150 - progress * 50;
362
+ const baseOpacity = opacityMin + opacityRange * progress;
363
+ const maskStop = maskByProgressRef.current ? 50 + progress * 45 : void 0;
364
+ const cssProps = {
365
+ "--beam-left-pos": `${leftPos}%`,
366
+ "--beam-right-pos": `${rightPos}%`,
367
+ "--beam-left-size": `${leftSize}%`,
368
+ "--base-opacity": baseOpacity
369
+ };
370
+ if (maskStop !== void 0) {
371
+ cssProps["--beam-mask-stop"] = `${maskStop}%`;
372
+ }
373
+ if (!pulse.enabled) {
374
+ cssProps.opacity = baseOpacity;
375
+ }
376
+ gsap3__default.default.set(element, cssProps);
377
+ };
378
+ initGradientStructure(colorRef.current);
138
379
  const st = ScrollTrigger.ScrollTrigger.create({
139
380
  trigger: element,
140
- start: "top bottom",
141
- // Element top hits viewport bottom
142
- end: "top top",
143
- // Element top hits viewport top
381
+ start: scrollStart,
382
+ // When to start the animation
383
+ end: scrollEnd,
384
+ // When to end the animation
144
385
  scroller,
145
- scrub: true,
146
- // Instant scrubbing
386
+ scrub: 0.15,
387
+ // Fast catch-up (300ms) for responsive scroll without jitter
147
388
  onUpdate: (self) => {
148
389
  const progress = calculateProgress(self.progress);
149
- element.style.background = interpolateBackground(progress, colorRef.current);
150
- element.style.opacity = String(opacityMin + opacityRange * progress);
151
- element.style.maskImage = interpolateMask(progress, colorRef.current);
152
- element.style.webkitMaskImage = interpolateMask(progress, colorRef.current);
390
+ applyProgressState(progress);
153
391
  },
154
392
  onRefresh: (self) => {
155
393
  const progress = calculateProgress(self.progress);
156
- element.style.background = interpolateBackground(progress, colorRef.current);
157
- element.style.opacity = String(opacityMin + opacityRange * progress);
158
- element.style.maskImage = interpolateMask(progress, colorRef.current);
159
- element.style.webkitMaskImage = interpolateMask(progress, colorRef.current);
394
+ applyProgressState(progress);
160
395
  }
161
396
  });
397
+ scrollTriggerRef.current = st;
162
398
  const initialProgress = calculateProgress(st.progress);
163
- element.style.background = interpolateBackground(initialProgress, colorRef.current);
164
- element.style.opacity = String(opacityMin + opacityRange * initialProgress);
165
- element.style.maskImage = interpolateMask(initialProgress, colorRef.current);
166
- element.style.webkitMaskImage = interpolateMask(initialProgress, colorRef.current);
399
+ applyProgressState(initialProgress);
167
400
  const refreshTimeout = setTimeout(() => {
168
401
  ScrollTrigger.ScrollTrigger.refresh();
169
402
  }, 100);
@@ -179,9 +412,13 @@ var LightBeam = ({
179
412
  // Only include values that affect ScrollTrigger's position/range calculations
180
413
  dependencies: [
181
414
  fullWidth,
182
- // Affects trigger range
183
- scrollElement
415
+ // Affects progress range calculation
416
+ scrollElement,
184
417
  // Affects which element to watch
418
+ scrollStart,
419
+ // Affects when animation starts
420
+ scrollEnd
421
+ // Affects when animation ends
185
422
  ],
186
423
  scope: elementRef
187
424
  }
@@ -199,29 +436,21 @@ var LightBeam = ({
199
436
  ...style
200
437
  // User styles override everything
201
438
  };
202
- return /* @__PURE__ */ jsxRuntime.jsx(
439
+ return /* @__PURE__ */ jsxRuntime.jsxs(
203
440
  "div",
204
441
  {
205
442
  ref: elementRef,
206
443
  className: combinedClassName,
207
444
  style: finalStyles,
208
- ...id ? { id } : {}
445
+ ...id ? { id } : {},
446
+ children: [
447
+ dustParticles.enabled && /* @__PURE__ */ jsxRuntime.jsx(DustParticles, { config: dustParticles, beamColor: chosenColor }),
448
+ mist.enabled && /* @__PURE__ */ jsxRuntime.jsx(MistEffect, { config: mist, beamColor: chosenColor }),
449
+ pulse.enabled && /* @__PURE__ */ jsxRuntime.jsx(PulseEffect, { config: pulse, containerRef: elementRef })
450
+ ]
209
451
  }
210
452
  );
211
453
  };
212
- /*! Bundled license information:
213
-
214
- @gsap/react/src/index.js:
215
- (*!
216
- * @gsap/react 2.1.2
217
- * https://gsap.com
218
- *
219
- * Copyright 2008-2025, GreenSock. All rights reserved.
220
- * Subject to the terms at https://gsap.com/standard-license or for
221
- * Club GSAP members, the agreement issued with that membership.
222
- * @author: Jack Doyle, jack@greensock.com
223
- *)
224
- */
225
454
 
226
455
  exports.LightBeam = LightBeam;
227
456
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../node_modules/@gsap/react/src/index.js","../src/hooks/useDarkmode.tsx","../src/index.tsx"],"names":["useLayoutEffect","useEffect","gsap","useRef","useState","ScrollTrigger","jsx"],"mappings":"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","file":"index.js","sourcesContent":["/*!\n * @gsap/react 2.1.2\n * https://gsap.com\n *\n * Copyright 2008-2025, GreenSock. All rights reserved.\n * Subject to the terms at https://gsap.com/standard-license or for\n * Club GSAP members, the agreement issued with that membership.\n * @author: Jack Doyle, jack@greensock.com\n*/\n/* eslint-disable */\nimport { useEffect, useLayoutEffect, useRef } from \"react\";\nimport gsap from \"gsap\";\n\nlet useIsomorphicLayoutEffect = typeof document !== \"undefined\" ? useLayoutEffect : useEffect,\n isConfig = value => value && !Array.isArray(value) && typeof(value) === \"object\",\n emptyArray = [],\n defaultConfig = {},\n _gsap = gsap; // accommodates situations where different versions of GSAP may be loaded, so a user can gsap.registerPlugin(useGSAP);\n\nexport const useGSAP = (callback, dependencies = emptyArray) => {\n let config = defaultConfig;\n if (isConfig(callback)) {\n config = callback;\n callback = null;\n dependencies = \"dependencies\" in config ? config.dependencies : emptyArray;\n } else if (isConfig(dependencies)) {\n config = dependencies;\n dependencies = \"dependencies\" in config ? config.dependencies : emptyArray;\n }\n (callback && typeof callback !== \"function\") && console.warn(\"First parameter must be a function or config object\");\n const { scope, revertOnUpdate } = config,\n mounted = useRef(false),\n context = useRef(_gsap.context(() => { }, scope)),\n contextSafe = useRef((func) => context.current.add(null, func)),\n deferCleanup = dependencies && dependencies.length && !revertOnUpdate;\n deferCleanup && useIsomorphicLayoutEffect(() => {\n mounted.current = true;\n return () => context.current.revert();\n }, emptyArray);\n useIsomorphicLayoutEffect(() => {\n callback && context.current.add(callback, scope);\n if (!deferCleanup || !mounted.current) { // React renders bottom-up, thus there could be hooks with dependencies that run BEFORE the component mounts, thus cleanup wouldn't occur since a hook with an empty dependency Array would only run once the component mounts.\n return () => context.current.revert();\n }\n }, dependencies);\n return { context: context.current, contextSafe: contextSafe.current };\n};\nuseGSAP.register = core => { _gsap = core; };\nuseGSAP.headless = true; // doesn't require the window to be registered.\n","\"use client\";\nimport { useEffect, useState } from \"react\";\n\nexport const useIsDarkmode = () => {\n const [isDarkmode, setIsDarkmodeActive] = useState(false);\n\n useEffect(() => {\n const matchMedia = window.matchMedia(\"(prefers-color-scheme: dark)\");\n\n const handleChange = () => {\n setIsDarkmodeActive(matchMedia.matches);\n };\n\n // Set the initial value\n setIsDarkmodeActive(matchMedia.matches);\n\n // Listen for changes\n matchMedia.addEventListener(\"change\", handleChange);\n\n // Cleanup listener on unmount\n return () => {\n matchMedia.removeEventListener(\"change\", handleChange);\n };\n }, []);\n\n return { isDarkmode };\n};\n","\"use client\";\nimport gsap from \"gsap\";\nimport {ScrollTrigger} from \"gsap/ScrollTrigger\";\nimport {useGSAP} from \"@gsap/react\";\nimport React, {useEffect, useRef} from \"react\";\nimport {LightBeamProps} from \"../types/types\";\nimport {useIsDarkmode} from \"./hooks/useDarkmode\";\n\n// Register GSAP plugins\ngsap.registerPlugin(ScrollTrigger, useGSAP);\n\n// Default inline styles using CSS variables for easy customization\n// Users can override via className by setting CSS variables\nconst defaultStyles: React.CSSProperties = {\n height: \"var(--react-light-beam-height, 500px)\",\n width: \"var(--react-light-beam-width, 100vw)\",\n // CRITICAL: NO transition on GSAP-controlled properties (background, opacity, mask)\n // Transitions would fight with GSAP's instant updates, causing visual glitches\n // especially when scroll direction changes\n transition: \"none\",\n willChange: \"background, opacity\", // Specific properties for better performance\n userSelect: \"none\",\n pointerEvents: \"none\",\n contain: \"layout style paint\", // CSS containment for better performance\n WebkitTransition: \"none\",\n WebkitUserSelect: \"none\",\n MozUserSelect: \"none\",\n};\n\nexport const LightBeam = ({\n className,\n style,\n colorLightmode = \"rgba(0,0,0, 0.5)\",\n colorDarkmode = \"rgba(255, 255, 255, 0.5)\",\n maskLightByProgress = false,\n fullWidth = 1.0, // Default to full width\n invert = false,\n id = undefined,\n onLoaded = undefined,\n scrollElement,\n disableDefaultStyles = false,\n }: LightBeamProps) => {\n const elementRef = useRef<HTMLDivElement>(null);\n const {isDarkmode} = useIsDarkmode();\n const chosenColor = isDarkmode ? colorDarkmode : colorLightmode;\n\n // Use refs to track current values without triggering useGSAP re-runs\n const colorRef = useRef(chosenColor);\n const invertRef = useRef(invert);\n const maskByProgressRef = useRef(maskLightByProgress);\n\n // Update refs whenever values change\n useEffect(() => {\n colorRef.current = chosenColor;\n invertRef.current = invert;\n maskByProgressRef.current = maskLightByProgress;\n }, [chosenColor, colorLightmode, colorDarkmode, invert, maskLightByProgress]);\n\n // Call onLoaded callback when component mounts\n useEffect(() => {\n onLoaded && onLoaded();\n }, []);\n\n // GSAP ScrollTrigger implementation\n useGSAP(\n () => {\n const element = elementRef.current;\n if (!element || typeof window === \"undefined\") return;\n\n // Pre-calculate constants for performance\n const opacityMin = 0.839322;\n const opacityRange = 0.160678; // 1 - 0.839322\n\n // Helper function to interpolate background gradient\n // NOTE: Takes color as parameter to always use current value (not closure!)\n const interpolateBackground = (progress: number, color: string): string => {\n // At progress 0: gradients are wide (90% and 10% positions)\n // At progress 1: gradients converge (0% and 100% positions)\n const leftPos = 90 - progress * 90; // 90% → 0%\n const rightPos = 10 + progress * 90; // 10% → 100%\n const leftSize = 150 - progress * 50; // 150% → 100%\n\n return `conic-gradient(from 90deg at ${leftPos}% 0%, ${color}, transparent 180deg) 0% 0% / 50% ${leftSize}% no-repeat, conic-gradient(from 270deg at ${rightPos}% 0%, transparent 180deg, ${color}) 100% 0% / 50% 100% no-repeat`;\n };\n\n // Helper function to interpolate mask\n // NOTE: Takes color as parameter to always use current value (not closure!)\n const interpolateMask = (progress: number, color: string): string => {\n if (!maskByProgressRef.current) {\n return `linear-gradient(to bottom, ${color} 25%, transparent 95%)`;\n }\n const stopPoint = 50 + progress * 45; // 50% → 95%\n return `linear-gradient(to bottom, ${color} 0%, transparent ${stopPoint}%)`;\n };\n\n // EXACT MATCH TO FRAMER MOTION LOGIC:\n // fullWidth controls the MINIMUM beam width, not maximum!\n // fullWidth=1.0 → beam goes from 0% to 100% wide (full range)\n // fullWidth=0.5 → beam goes from 50% to 100% wide (narrower minimum)\n // fullWidth=0.2 → beam goes from 80% to 100% wide (very narrow minimum)\n const adjustedFullWidth = 1 - fullWidth;\n\n // Helper function to calculate progress (EXACTLY like Framer Motion)\n const calculateProgress = (rawProgress: number): number => {\n // ScrollTrigger rawProgress is 0-1 as element moves from start to end\n // We need to convert this to match Framer's rect.top / windowHeight logic\n // CRITICAL: GSAP progress (0→1) is INVERSE of Framer's normalizedPosition (1→0)\n\n // Apply fullWidth floor (minimum progress value)\n const normalizedPosition = Math.max(\n adjustedFullWidth, // Minimum (floor)\n Math.min(1, 1 - rawProgress) // Convert GSAP progress to Framer's normalized position\n );\n\n // Apply invert logic (EXACTLY like Framer Motion)\n // Use invertRef to get current value without closure issues\n return invertRef.current ? normalizedPosition : 1 - normalizedPosition;\n };\n\n // Determine scroll container\n const scroller = scrollElement\n ? (scrollElement as Element | Window)\n : undefined;\n\n // Create ScrollTrigger with FIXED range (like Framer Motion)\n const st = ScrollTrigger.create({\n trigger: element,\n start: \"top bottom\", // Element top hits viewport bottom\n end: \"top top\", // Element top hits viewport top\n scroller: scroller,\n scrub: true, // Instant scrubbing\n onUpdate: (self) => {\n // Calculate progress using Framer Motion logic\n const progress = calculateProgress(self.progress);\n\n // Update styles directly (not via gsap.set to avoid CSS variable parsing issues)\n element.style.background = interpolateBackground(progress, colorRef.current);\n element.style.opacity = String(opacityMin + opacityRange * progress);\n element.style.maskImage = interpolateMask(progress, colorRef.current);\n element.style.webkitMaskImage = interpolateMask(progress, colorRef.current);\n },\n onRefresh: (self) => {\n // Set initial state when ScrollTrigger refreshes\n const progress = calculateProgress(self.progress);\n element.style.background = interpolateBackground(progress, colorRef.current);\n element.style.opacity = String(opacityMin + opacityRange * progress);\n element.style.maskImage = interpolateMask(progress, colorRef.current);\n element.style.webkitMaskImage = interpolateMask(progress, colorRef.current);\n },\n });\n\n // Set initial state immediately\n const initialProgress = calculateProgress(st.progress);\n element.style.background = interpolateBackground(initialProgress, colorRef.current);\n element.style.opacity = String(opacityMin + opacityRange * initialProgress);\n element.style.maskImage = interpolateMask(initialProgress, colorRef.current);\n element.style.webkitMaskImage = interpolateMask(initialProgress, colorRef.current);\n\n // Refresh ScrollTrigger after a brief delay to ensure layout is settled\n // This is especially important for Next.js SSR/hydration\n const refreshTimeout = setTimeout(() => {\n ScrollTrigger.refresh();\n }, 100);\n\n // Cleanup function to kill ScrollTrigger and clear timeout\n return () => {\n st.kill();\n clearTimeout(refreshTimeout);\n };\n },\n {\n // CRITICAL: Use refs for frequently changing values!\n // colorRef, invertRef, maskByProgressRef allow updates without recreating ScrollTrigger\n // This prevents visual glitches when these values change mid-scroll\n // Only include values that affect ScrollTrigger's position/range calculations\n dependencies: [\n fullWidth, // Affects trigger range\n scrollElement, // Affects which element to watch\n ],\n scope: elementRef,\n }\n );\n\n const combinedClassName = `react-light-beam ${className || \"\"}`.trim();\n\n // Prepare final styles (same logic as before, just without MotionValues)\n const finalStyles = disableDefaultStyles\n ? {\n // No default styles, only user styles\n willChange: \"background, opacity\",\n contain: \"layout style paint\",\n ...style, // User styles override\n }\n : {\n // Merge default styles with user styles\n ...defaultStyles,\n ...style, // User styles override everything\n };\n\n return (\n <div\n ref={elementRef}\n className={combinedClassName}\n style={finalStyles}\n {...(id ? {id} : {})}\n />\n );\n};\n\n\n\n// <div class=\"react-light-beam max-h:[500px] absolute top:[-100px] inset:[0]\" style=\"height: var(--react-light-beam-height, 500px); width: var(--react-light-beam-width, 100vw); will-change: background,\n// opacity; pointer-events: none; contain: layout style paint; transition: var(--react-light-beam-transition, all 0.25s ease); mask-image: linear-gradient(rgba(255, 255, 255, 0.8) 25%, transparent 95%);\n// opacity: 0.9434; background: conic-gradient(from 90deg at 31.723% 0%, rgba(255, 255, 255, 0.8), transparent 180deg) 0% 0% / 50% 117.624% no-repeat, conic-gradient(from 270deg at 68.277% 0%, transparent\n// 180deg, rgba(255, 255, 255, 0.8)) 100% 0% / 50% 100% no-repeat;\"></div>\n\n// <div class=\"react-light-beam max-h:[500px] absolute top:[-100px] inset:[0]\" style=\"height: var(--react-light-beam-height, 500px); width: var(--react-light-beam-width, 100vw); will-change: background,\n// opacity; pointer-events: none; contain: layout style paint; transition: var(--react-light-beam-transition, all 0.25s ease); mask-image: linear-gradient(rgba(0, 0, 0, 0.2) 25%, transparent 95%); opacity:\n// 0.941; background: conic-gradient(from 90deg at 33.0405% 0%, rgba(0, 0, 0, 0.2), transparent 180deg) 0% 0% / 50% 118.356% no-repeat, conic-gradient(from 270deg at 66.9595% 0%, transparent 180deg, rgba(0, 0,\n// 0, 0.2)) 100% 0% / 50% 100% no-repeat;\"></div>\n\n"]}
1
+ {"version":3,"sources":["../src/hooks/useDarkmode.tsx","../src/effects/DustParticles.tsx","../src/effects/MistEffect.tsx","../src/effects/PulseEffect.tsx","../src/index.tsx"],"names":["useState","useEffect","useMemo","useGSAP","gsap","jsx","Fragment","ScrollTrigger","useRef","jsxs"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\nimport {useEffect, useState} from \"react\";\n\nexport const useIsDarkmode = () => {\n const [isDarkmode, setIsDarkmodeActive] = useState(false);\n\n useEffect(() => {\n const matchMedia = window.matchMedia(\"(prefers-color-scheme: dark)\");\n\n const handleChange = () => {\n console.log(\"Darkmode match?\", matchMedia.matches)\n \n setIsDarkmodeActive(matchMedia.matches);\n };\n\n // Set the initial value\n setIsDarkmodeActive(matchMedia.matches);\n\n // Listen for changes\n matchMedia.addEventListener(\"change\", handleChange);\n handleChange()\n\n // Cleanup listener on unmount\n return () => {\n matchMedia.removeEventListener(\"change\", handleChange);\n };\n }, []);\n\n return {isDarkmode};\n};\n","\"use client\";\nimport gsap from \"gsap\";\nimport {useGSAP} from \"@gsap/react\";\nimport {useMemo} from \"react\";\nimport {DustParticlesConfig} from \"../../types/types\";\n\ninterface DustParticlesProps {\n config: DustParticlesConfig;\n beamColor: string;\n}\n\nexport const DustParticles = ({config, beamColor}: DustParticlesProps) => {\n const {\n enabled = false,\n count = 30,\n speed = 1,\n sizeRange = [1, 3],\n opacityRange = [0.2, 0.6],\n color,\n } = config;\n\n // Generate particle data once\n const particles = useMemo(() => {\n if (!enabled) return [];\n\n return Array.from({length: count}, (_, i) => {\n // Random position\n const x = Math.random() * 100; // 0-100%\n const y = Math.random() * 100; // 0-100%\n\n // Random size within range\n const size = sizeRange[0] + Math.random() * (sizeRange[1] - sizeRange[0]);\n\n // Random opacity within range\n const opacity = opacityRange[0] + Math.random() * (opacityRange[1] - opacityRange[0]);\n\n // Random animation duration (inversely proportional to speed)\n const duration = (3 + Math.random() * 4) / speed; // 3-7s / speed\n\n // Random animation delay for stagger effect\n const delay = Math.random() * duration;\n\n return {\n id: `dust-${i}`,\n x,\n y,\n size,\n opacity,\n duration,\n delay,\n };\n });\n }, [enabled, count, sizeRange, opacityRange, speed]);\n\n useGSAP(\n () => {\n if (!enabled || particles.length === 0) return;\n\n const timelines: gsap.core.Timeline[] = [];\n\n particles.forEach((particle) => {\n const element = document.getElementById(particle.id);\n if (!element) return;\n\n // Create floating animation for each particle\n const tl = gsap.timeline({\n repeat: -1,\n yoyo: true,\n delay: particle.delay,\n });\n\n // Animate vertical movement and slight horizontal drift\n tl.to(element, {\n y: `-=${20 + Math.random() * 30}`, // Float upward 20-50px\n x: `+=${Math.random() * 20 - 10}`, // Slight horizontal drift ±10px\n opacity: particle.opacity * 0.5, // Fade slightly\n duration: particle.duration,\n ease: \"sine.inOut\",\n });\n\n timelines.push(tl);\n });\n\n return () => {\n timelines.forEach((tl) => tl.kill());\n };\n },\n {\n dependencies: [particles, enabled],\n }\n );\n\n if (!enabled) return null;\n\n const particleColor = color || beamColor;\n\n return (\n <>\n {particles.map((particle) => (\n <div\n key={particle.id}\n id={particle.id}\n style={{\n position: \"absolute\",\n left: `${particle.x}%`,\n top: `${particle.y}%`,\n width: `${particle.size}px`,\n height: `${particle.size}px`,\n borderRadius: \"50%\",\n backgroundColor: particleColor,\n opacity: particle.opacity,\n pointerEvents: \"none\",\n willChange: \"transform, opacity\",\n }}\n />\n ))}\n </>\n );\n};\n","\"use client\";\nimport gsap from \"gsap\";\nimport {useGSAP} from \"@gsap/react\";\nimport {useMemo} from \"react\";\nimport {MistConfig} from \"../../types/types\";\n\ninterface MistEffectProps {\n config: MistConfig;\n beamColor: string;\n}\n\nexport const MistEffect = ({config, beamColor}: MistEffectProps) => {\n const {\n enabled = false,\n intensity = 0.3,\n speed = 1,\n layers = 2,\n } = config;\n\n // Generate mist layer data\n const mistLayers = useMemo(() => {\n if (!enabled) return [];\n\n return Array.from({length: layers}, (_, i) => {\n // Each layer has different characteristics for depth\n const layerOpacity = (intensity * 0.6) / (i + 1); // Deeper layers are more transparent\n const duration = (8 + i * 3) / speed; // Deeper layers move slower\n const delay = (i * 1.5) / speed; // Stagger start times\n const scale = 1 + i * 0.2; // Deeper layers are larger\n\n return {\n id: `mist-layer-${i}`,\n opacity: layerOpacity,\n duration,\n delay,\n scale,\n };\n });\n }, [enabled, intensity, speed, layers]);\n\n useGSAP(\n () => {\n if (!enabled || mistLayers.length === 0) return;\n\n const timelines: gsap.core.Timeline[] = [];\n\n mistLayers.forEach((layer) => {\n const element = document.getElementById(layer.id);\n if (!element) return;\n\n // Create drifting mist animation\n const tl = gsap.timeline({\n repeat: -1,\n yoyo: false,\n });\n\n // Horizontal drift animation\n tl.fromTo(\n element,\n {\n x: \"-100%\",\n opacity: 0,\n },\n {\n x: \"100%\",\n opacity: layer.opacity,\n duration: layer.duration,\n ease: \"none\",\n delay: layer.delay,\n }\n ).to(element, {\n opacity: 0,\n duration: layer.duration * 0.2,\n ease: \"power1.in\",\n });\n\n timelines.push(tl);\n });\n\n return () => {\n timelines.forEach((tl) => tl.kill());\n };\n },\n {\n dependencies: [mistLayers, enabled],\n }\n );\n\n if (!enabled) return null;\n\n // Parse beam color and create mist color with lower opacity\n const mistColor = beamColor.replace(/[\\d.]+\\)$/g, `${intensity})`);\n\n return (\n <>\n {mistLayers.map((layer) => (\n <div\n key={layer.id}\n id={layer.id}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n background: `radial-gradient(ellipse 120% 80% at 50% 20%, ${mistColor}, transparent 70%)`,\n opacity: 0,\n pointerEvents: \"none\",\n willChange: \"transform, opacity\",\n transform: `scale(${layer.scale})`,\n filter: \"blur(40px)\",\n }}\n />\n ))}\n </>\n );\n};\n","\"use client\";\nimport gsap from \"gsap\";\nimport {useGSAP} from \"@gsap/react\";\nimport {PulseConfig} from \"../../types/types\";\n\ninterface PulseEffectProps {\n config: PulseConfig;\n containerRef: React.RefObject<HTMLDivElement | null>;\n}\n\nexport const PulseEffect = ({config, containerRef}: PulseEffectProps) => {\n const {\n enabled = false,\n duration = 2,\n intensity = 0.2,\n easing = \"sine.inOut\",\n } = config;\n\n useGSAP(\n () => {\n if (!enabled || !containerRef.current) return;\n\n const element = containerRef.current;\n\n // Create pulsing animation using GSAP timeline\n // Pulse multiplies the base opacity (from scroll) with pulse intensity\n const timeline = gsap.timeline({\n repeat: -1, // Infinite loop\n yoyo: true, // Reverse on each iteration\n });\n\n // Calculate pulse range based on intensity\n // Pulse multiplier varies from (1 - intensity) to (1 + intensity)\n const minMultiplier = Math.max(0, 1 - intensity);\n const maxMultiplier = Math.min(2, 1 + intensity);\n\n timeline.fromTo(\n element,\n {\n \"--pulse-multiplier\": 1,\n },\n {\n \"--pulse-multiplier\": maxMultiplier,\n duration: duration,\n ease: easing,\n }\n );\n\n // Apply combined opacity using calc()\n // opacity = base-opacity * pulse-multiplier\n const updateOpacity = () => {\n const baseOpacity = getComputedStyle(element).getPropertyValue('--base-opacity') || '1';\n const pulseMultiplier = getComputedStyle(element).getPropertyValue('--pulse-multiplier') || '1';\n element.style.opacity = `calc(${baseOpacity} * ${pulseMultiplier})`;\n };\n\n // Update opacity continuously during animation\n const ticker = gsap.ticker.add(updateOpacity);\n\n return () => {\n timeline.kill();\n gsap.ticker.remove(ticker);\n };\n },\n {\n dependencies: [enabled, duration, intensity, easing],\n scope: containerRef,\n }\n );\n\n // This component doesn't render anything - it just adds animations\n return null;\n};\n","\"use client\";\nimport gsap from \"gsap\";\nimport {ScrollTrigger} from \"gsap/ScrollTrigger\";\nimport {useGSAP} from \"@gsap/react\";\nimport React, {useEffect, useRef} from \"react\";\nimport {LightBeamProps} from \"../types/types\";\nimport {useIsDarkmode} from \"./hooks/useDarkmode\";\nimport {DustParticles} from \"./effects/DustParticles\";\nimport {MistEffect} from \"./effects/MistEffect\";\nimport {PulseEffect} from \"./effects/PulseEffect\";\n\n// Register GSAP plugins\ngsap.registerPlugin(ScrollTrigger, useGSAP);\n\n// Default inline styles using CSS variables for easy customization\n// Users can override via className by setting CSS variables\nconst defaultStyles: React.CSSProperties = {\n height: \"var(--react-light-beam-height, 500px)\",\n width: \"var(--react-light-beam-width, 100vw)\",\n // CRITICAL: NO transition on GSAP-controlled properties (background, opacity, mask)\n // Transitions would fight with GSAP's instant updates, causing visual glitches\n // especially when scroll direction changes\n transition: \"none\",\n willChange: \"background, opacity\", // Specific properties for better performance\n userSelect: \"none\",\n pointerEvents: \"none\",\n contain: \"layout style paint\", // CSS containment for better performance\n WebkitTransition: \"none\",\n WebkitUserSelect: \"none\",\n MozUserSelect: \"none\",\n};\n\nexport const LightBeam = ({\n className,\n style,\n colorLightmode = \"rgba(0,0,0, 0.5)\",\n colorDarkmode = \"rgba(255, 255, 255, 0.5)\",\n maskLightByProgress = false,\n fullWidth = 1.0, // Default to full width range\n invert = false,\n id = undefined,\n onLoaded = undefined,\n scrollElement,\n disableDefaultStyles = false,\n scrollStart = \"top bottom\",\n scrollEnd = \"top top\",\n dustParticles = {enabled: false},\n mist = {enabled: false},\n pulse = {enabled: false},\n }: LightBeamProps) => {\n const elementRef = useRef<HTMLDivElement>(null);\n const {isDarkmode} = useIsDarkmode();\n const chosenColor = isDarkmode ? colorDarkmode : colorLightmode;\n\n // Use refs to track current values without triggering useGSAP re-runs\n const colorRef = useRef(chosenColor);\n const invertRef = useRef(invert);\n const maskByProgressRef = useRef(maskLightByProgress);\n\n // Store ScrollTrigger instance for manual updates\n const scrollTriggerRef = useRef<ScrollTrigger | null>(null);\n\n // Update refs whenever values change\n useEffect(() => {\n colorRef.current = chosenColor;\n\n // PERFORMANCE: Update color CSS variable when color changes\n // This avoids recreating ScrollTrigger on color changes\n if (elementRef.current) {\n elementRef.current.style.setProperty('--beam-color', chosenColor);\n }\n }, [chosenColor, colorLightmode, colorDarkmode]);\n\n // Handle invert changes separately - needs immediate update\n useEffect(() => {\n const prevInvert = invertRef.current;\n invertRef.current = invert;\n\n // If invert changed and ScrollTrigger exists, immediately update\n if (prevInvert !== invert && scrollTriggerRef.current && elementRef.current) {\n const st = scrollTriggerRef.current;\n const element = elementRef.current;\n\n // Force immediate recalculation with new invert value\n // This prevents lag/jitter when toggling invert during scroll\n st.refresh();\n }\n }, [invert]);\n\n // Handle maskLightByProgress changes separately - needs structure rebuild\n useEffect(() => {\n const prevMaskByProgress = maskByProgressRef.current;\n maskByProgressRef.current = maskLightByProgress;\n\n // If maskLightByProgress changed and element exists, rebuild mask structure\n if (prevMaskByProgress !== maskLightByProgress && elementRef.current) {\n const element = elementRef.current;\n\n // Rebuild mask gradient structure with new setting\n if (maskLightByProgress) {\n // Initialize mask stop value\n element.style.setProperty('--beam-mask-stop', '50%');\n element.style.maskImage = `linear-gradient(to bottom, var(--beam-color) 0%, transparent var(--beam-mask-stop))`;\n element.style.webkitMaskImage = `linear-gradient(to bottom, var(--beam-color) 0%, transparent var(--beam-mask-stop))`;\n } else {\n // Static mask\n element.style.maskImage = `linear-gradient(to bottom, var(--beam-color) 25%, transparent 95%)`;\n element.style.webkitMaskImage = `linear-gradient(to bottom, var(--beam-color) 25%, transparent 95%)`;\n }\n\n // If ScrollTrigger exists, refresh to apply current state\n if (scrollTriggerRef.current) {\n scrollTriggerRef.current.refresh();\n }\n }\n }, [maskLightByProgress]);\n\n // Call onLoaded callback when component mounts\n useEffect(() => {\n onLoaded && onLoaded();\n }, []);\n\n // GSAP ScrollTrigger implementation\n useGSAP(\n () => {\n const element = elementRef.current;\n if (!element || typeof window === \"undefined\") return;\n\n // Pre-calculate constants for performance\n const opacityMin = 0.839322;\n const opacityRange = 0.160678; // 1 - 0.839322\n\n // Helper function to set color (only when color changes, not every frame)\n const updateColorVar = (color: string): void => {\n element.style.setProperty('--beam-color', color);\n };\n\n // Set initial gradient structure (once, not per frame!)\n const initGradientStructure = (color: string): void => {\n updateColorVar(color);\n 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`;\n element.style.background = baseGradient;\n\n // Set mask structure\n if (maskByProgressRef.current) {\n element.style.maskImage = `linear-gradient(to bottom, var(--beam-color) 0%, transparent var(--beam-mask-stop))`;\n element.style.webkitMaskImage = `linear-gradient(to bottom, var(--beam-color) 0%, transparent var(--beam-mask-stop))`;\n } else {\n element.style.maskImage = `linear-gradient(to bottom, var(--beam-color) 25%, transparent 95%)`;\n element.style.webkitMaskImage = `linear-gradient(to bottom, var(--beam-color) 25%, transparent 95%)`;\n }\n };\n\n // FRAMER MOTION LOGIC (PRESERVED):\n // fullWidth controls the MAXIMUM beam width expansion during scroll\n // fullWidth=1.0 → beam expands from 0% to 100% of maximum width (full range)\n // fullWidth=0.8 → beam expands from 0% to 80% of maximum width\n // fullWidth=0.5 → beam expands from 0% to 50% of maximum width\n // fullWidth=0.2 → beam expands from 0% to 20% of maximum width\n const adjustedFullWidth = 1 - fullWidth;\n\n // Helper function to calculate progress (EXACTLY like Framer Motion)\n const calculateProgress = (rawProgress: number): number => {\n // ScrollTrigger rawProgress: 0 (element entering) → 1 (element at trigger end)\n // We convert to match Framer's rect.top / windowHeight logic\n // GSAP progress (0→1) is INVERSE of Framer's normalizedPosition (1→0)\n\n // Apply fullWidth ceiling (maximum progress value)\n // Math.max ensures progress never goes below (1 - fullWidth)\n // This limits how much the beam can expand\n const normalizedPosition = Math.max(\n adjustedFullWidth, // Floor value (1 - fullWidth)\n Math.min(1, 1 - rawProgress) // Inverted GSAP progress\n );\n\n // Apply invert logic (EXACTLY like Framer Motion)\n // Use invertRef to get current value without closure issues\n return invertRef.current ? normalizedPosition : 1 - normalizedPosition;\n };\n\n // Determine scroll container\n const scroller = scrollElement\n ? (scrollElement as Element | Window)\n : undefined;\n\n // PERFORMANCE OPTIMIZATION: Shared update function to avoid code duplication\n // Batches all style updates for better performance\n const applyProgressState = (progress: number): void => {\n // Pre-calculate all values\n const leftPos = 90 - progress * 90;\n const rightPos = 10 + progress * 90;\n const leftSize = 150 - progress * 50;\n const baseOpacity = opacityMin + opacityRange * progress;\n const maskStop = maskByProgressRef.current ? 50 + progress * 45 : undefined;\n\n // BATCH UPDATE: Use single gsap.set() call instead of multiple setProperty()\n // This is significantly faster as GSAP batches all updates in one frame\n const cssProps: any = {\n '--beam-left-pos': `${leftPos}%`,\n '--beam-right-pos': `${rightPos}%`,\n '--beam-left-size': `${leftSize}%`,\n '--base-opacity': baseOpacity,\n };\n\n // Conditionally add mask stop\n if (maskStop !== undefined) {\n cssProps['--beam-mask-stop'] = `${maskStop}%`;\n }\n\n // Conditionally add opacity (only if pulse not enabled)\n if (!pulse.enabled) {\n cssProps.opacity = baseOpacity;\n }\n\n // Single batch update via GSAP (more efficient than multiple setProperty calls)\n gsap.set(element, cssProps);\n };\n\n // Initialize gradient structure once\n initGradientStructure(colorRef.current);\n\n // Create ScrollTrigger with customizable start/end positions\n // SCRUB VALUE EXPLANATION:\n // - scrub: true (or 1) = 1 second catch-up delay (causes visible lag and stuttering)\n // - scrub: 0.3 = 300ms catch-up (fast and smooth, optimal for UI) ← CURRENT SETTING\n // - scrub: 0 = instant (may feel jittery on fast scrolls, no smoothing)\n // Research: Lower values (0.2-0.5) recommended for responsive scroll UX\n const st = ScrollTrigger.create({\n trigger: element,\n start: scrollStart, // When to start the animation\n end: scrollEnd, // When to end the animation\n scroller: scroller,\n scrub: 0.15, // Fast catch-up (300ms) for responsive scroll without jitter\n onUpdate: (self) => {\n // Calculate progress using Framer Motion logic\n const progress = calculateProgress(self.progress);\n\n // Apply all updates in single batch\n applyProgressState(progress);\n },\n onRefresh: (self) => {\n // CRITICAL: ScrollTrigger.refresh() is called on window resize, orientation change,\n // or when content changes. We need to recalculate and reapply styles to ensure\n // the beam renders correctly after layout changes.\n const progress = calculateProgress(self.progress);\n\n // Apply all updates in single batch\n applyProgressState(progress);\n },\n });\n\n // Store ScrollTrigger instance for manual updates (invert/maskLightByProgress changes)\n scrollTriggerRef.current = st;\n\n // Set initial state immediately using batched update\n const initialProgress = calculateProgress(st.progress);\n applyProgressState(initialProgress);\n\n // Refresh ScrollTrigger after a brief delay to ensure layout is settled\n // This is especially important for Next.js SSR/hydration\n const refreshTimeout = setTimeout(() => {\n ScrollTrigger.refresh();\n }, 100);\n\n // Cleanup function to kill ScrollTrigger and clear timeout\n return () => {\n st.kill();\n clearTimeout(refreshTimeout);\n };\n },\n {\n // CRITICAL: Use refs for frequently changing values!\n // colorRef, invertRef, maskByProgressRef allow updates without recreating ScrollTrigger\n // This prevents visual glitches when these values change mid-scroll\n // Only include values that affect ScrollTrigger's position/range calculations\n dependencies: [\n fullWidth, // Affects progress range calculation\n scrollElement, // Affects which element to watch\n scrollStart, // Affects when animation starts\n scrollEnd, // Affects when animation ends\n ],\n scope: elementRef,\n }\n );\n\n const combinedClassName = `react-light-beam ${className || \"\"}`.trim();\n\n // Prepare final styles\n const finalStyles = disableDefaultStyles\n ? {\n // No default styles, only user styles\n willChange: \"background, opacity\",\n contain: \"layout style paint\",\n ...style, // User styles override\n }\n : {\n // Merge default styles with user styles\n ...defaultStyles,\n ...style, // User styles override everything\n };\n\n return (\n <div\n ref={elementRef}\n className={combinedClassName}\n style={finalStyles}\n {...(id ? {id} : {})}\n >\n {/* Atmospheric Effects */}\n {dustParticles.enabled && (\n <DustParticles config={dustParticles} beamColor={chosenColor}/>\n )}\n {mist.enabled && (\n <MistEffect config={mist} beamColor={chosenColor}/>\n )}\n {pulse.enabled && (\n <PulseEffect config={pulse} containerRef={elementRef}/>\n )}\n </div>\n );\n};\n"]}