@shohojdhara/atomix 0.4.9 → 0.5.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.
Files changed (67) hide show
  1. package/dist/atomix.css +95 -69
  2. package/dist/atomix.css.map +1 -1
  3. package/dist/atomix.min.css +1 -1
  4. package/dist/atomix.min.css.map +1 -1
  5. package/dist/charts.d.ts +1 -0
  6. package/dist/charts.js +231 -332
  7. package/dist/charts.js.map +1 -1
  8. package/dist/core.d.ts +1 -0
  9. package/dist/core.js +232 -333
  10. package/dist/core.js.map +1 -1
  11. package/dist/forms.d.ts +1 -0
  12. package/dist/forms.js +231 -332
  13. package/dist/forms.js.map +1 -1
  14. package/dist/heavy.d.ts +11 -2
  15. package/dist/heavy.js +233 -334
  16. package/dist/heavy.js.map +1 -1
  17. package/dist/index.d.ts +13 -2
  18. package/dist/index.esm.js +228 -327
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/index.js +227 -326
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.min.js +1 -1
  23. package/dist/index.min.js.map +1 -1
  24. package/package.json +11 -1
  25. package/src/components/AtomixGlass/AtomixGlass.tsx +188 -128
  26. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +62 -90
  27. package/src/components/AtomixGlass/PerformanceDashboard.tsx +153 -201
  28. package/src/components/AtomixGlass/glass-utils.ts +50 -0
  29. package/src/components/AtomixGlass/shader-utils.ts +1 -1
  30. package/src/components/AtomixGlass/stories/{Phase1-Animation.stories.tsx → AnimationFeatures.stories.tsx} +53 -47
  31. package/src/components/AtomixGlass/stories/Examples.stories.tsx +573 -236
  32. package/src/components/AtomixGlass/stories/Playground.stories.tsx +656 -44
  33. package/src/components/AtomixGlass/stories/argTypes.ts +384 -0
  34. package/src/components/AtomixGlass/stories/shared-components.tsx +82 -3
  35. package/src/components/AtomixGlass/stories/types.ts +127 -0
  36. package/src/lib/composables/useAtomixGlass.ts +108 -71
  37. package/src/lib/composables/useAtomixGlassStyles.ts +0 -2
  38. package/src/lib/constants/components.ts +1 -0
  39. package/src/lib/types/components.ts +1 -0
  40. package/src/lib/utils/displacement-generator.ts +1 -1
  41. package/src/styles/06-components/_components.atomix-glass.scss +158 -97
  42. package/scripts/cli/__tests__/README.md +0 -81
  43. package/scripts/cli/__tests__/basic.test.js +0 -116
  44. package/scripts/cli/__tests__/clean.test.js +0 -278
  45. package/scripts/cli/__tests__/component-generator.test.js +0 -332
  46. package/scripts/cli/__tests__/component-validator.test.js +0 -433
  47. package/scripts/cli/__tests__/generator.test.js +0 -613
  48. package/scripts/cli/__tests__/glass-motion.test.js +0 -256
  49. package/scripts/cli/__tests__/integration.test.js +0 -938
  50. package/scripts/cli/__tests__/migrate.test.js +0 -74
  51. package/scripts/cli/__tests__/security.test.js +0 -206
  52. package/scripts/cli/__tests__/test-setup.js +0 -135
  53. package/scripts/cli/__tests__/theme-bridge.test.js +0 -507
  54. package/scripts/cli/__tests__/token-manager.test.js +0 -251
  55. package/scripts/cli/__tests__/token-provider.test.js +0 -361
  56. package/scripts/cli/__tests__/utils.test.js +0 -165
  57. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +0 -216
  58. package/src/components/AtomixGlass/stories/Customization.stories.tsx +0 -131
  59. package/src/components/AtomixGlass/stories/Modes.stories.tsx +0 -1082
  60. package/src/components/AtomixGlass/stories/Overview.stories.tsx +0 -497
  61. package/src/components/AtomixGlass/stories/Performance.stories.tsx +0 -103
  62. package/src/components/AtomixGlass/stories/Phase1-Test.stories.tsx +0 -95
  63. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +0 -395
  64. package/src/components/TypedButton/TypedButton.stories.tsx +0 -59
  65. package/src/components/TypedButton/TypedButton.tsx +0 -39
  66. package/src/components/TypedButton/index.ts +0 -2
  67. package/src/lib/composables/useTypedButton.ts +0 -66
package/dist/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
 
3
- import React, { useState, useRef, useEffect, memo, forwardRef, useMemo, useCallback, useId, Children, isValidElement, cloneElement, createContext, useContext, useImperativeHandle, Component } from "react";
3
+ import React, { useState, useRef, useEffect, memo, forwardRef, useId, useMemo, useCallback, Children, isValidElement, cloneElement, createContext, useContext, useImperativeHandle, Component } from "react";
4
4
 
5
5
  import * as PhosphorIcons from "@phosphor-icons/react";
6
6
 
@@ -1695,6 +1695,7 @@ const _includesInstanceProperty = getDefaultExportFromCjs((function(it) {
1695
1695
  FILTER_OVERLAY_CLASS: "c-atomix-glass__filter-overlay",
1696
1696
  FILTER_SHADOW_CLASS: "c-atomix-glass__filter-shadow",
1697
1697
  CONTENT_CLASS: "c-atomix-glass__content",
1698
+ BORDER_BACKDROP_CLASS: "c-atomix-glass__border-backdrop",
1698
1699
  BORDER_1_CLASS: "c-atomix-glass__border-1",
1699
1700
  BORDER_2_CLASS: "c-atomix-glass__border-2",
1700
1701
  HOVER_1_CLASS: "c-atomix-glass__hover-1",
@@ -2097,7 +2098,7 @@ const {CONSTANTS: CONSTANTS$2} = ATOMIX_GLASS, calculateDistance = (pos1, pos2)
2097
2098
  default:
2098
2099
  return console.warn("AtomixGlass: Invalid displacement mode"), displacementMap;
2099
2100
  }
2100
- }, GlassFilterComponent = ({id: id, displacementScale: displacementScale, aberrationIntensity: aberrationIntensity, mode: mode, shaderMapUrl: shaderMapUrl, blurAmount: blurAmount}) => jsx("svg", {
2101
+ }, sharedShaderCache = new Map, GlassFilterComponent = ({id: id, displacementScale: displacementScale, aberrationIntensity: aberrationIntensity, mode: mode, shaderMapUrl: shaderMapUrl, blurAmount: blurAmount}) => jsx("svg", {
2101
2102
  style: {
2102
2103
  position: "absolute",
2103
2104
  width: "100%",
@@ -2238,24 +2239,17 @@ const {CONSTANTS: CONSTANTS$2} = ATOMIX_GLASS, calculateDistance = (pos1, pos2)
2238
2239
  */ GlassFilterComponent.displayName = "GlassFilter";
2239
2240
 
2240
2241
  // Memoize component to prevent unnecessary re-renders
2241
- const GlassFilter = memo(GlassFilterComponent, ((prevProps, nextProps) => prevProps.id === nextProps.id && prevProps.displacementScale === nextProps.displacementScale && prevProps.aberrationIntensity === nextProps.aberrationIntensity && prevProps.mode === nextProps.mode && prevProps.shaderMapUrl === nextProps.shaderMapUrl && prevProps.blurAmount === nextProps.blurAmount));
2242
-
2243
- // Module-level counter for deterministic ID generation
2244
- let idCounter = 0;
2245
-
2246
- // Module-level shared shader cache with LRU eviction
2247
- const sharedShaderCache = new Map, AtomixGlassContainer = forwardRef((({children: children, className: className = "", style: style, displacementScale: displacementScale = 25, blurAmount: blurAmount = .0625, saturation: saturation = 180, aberrationIntensity: aberrationIntensity = 2, mouseOffset: mouseOffset = {
2242
+ const GlassFilter = memo(GlassFilterComponent, ((prevProps, nextProps) => prevProps.id === nextProps.id && prevProps.displacementScale === nextProps.displacementScale && prevProps.aberrationIntensity === nextProps.aberrationIntensity && prevProps.mode === nextProps.mode && prevProps.shaderMapUrl === nextProps.shaderMapUrl && prevProps.blurAmount === nextProps.blurAmount)), AtomixGlassContainer = forwardRef((({children: children, className: className = "", style: style, displacementScale: displacementScale = 25, blurAmount: blurAmount = .0625, saturation: saturation = 180, aberrationIntensity: aberrationIntensity = 2, mouseOffset: mouseOffset = {
2248
2243
  x: 0,
2249
2244
  y: 0
2250
2245
  }, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onMouseDown: onMouseDown, onMouseUp: onMouseUp, isActive: isActive = !1, overLight: overLight = !1, overLightConfig: overLightConfig = {}, borderRadius: borderRadius = 0, padding: padding = "0 0", glassSize: glassSize = {
2251
2246
  width: 0,
2252
2247
  height: 0
2253
- }, onClick: onClick, mode: mode = "standard", effectiveWithoutEffects: effectiveWithoutEffects = !1, effectiveReducedMotion: effectiveReducedMotion = !1, shaderVariant: shaderVariant = "liquidGlass", withLiquidBlur: withLiquidBlur = !1,
2248
+ }, onClick: onClick, mode: mode = "standard", effectiveWithoutEffects: effectiveWithoutEffects = !1, effectiveReducedMotion: effectiveReducedMotion = !1, shaderVariant: shaderVariant = "liquidGlass", withLiquidBlur: withLiquidBlur = !1, isFixedOrSticky: isFixedOrSticky = !1,
2254
2249
  // Phase 1: Animation System props
2255
2250
  shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpeed: animationSpeed = 1, withMultiLayerDistortion: withMultiLayerDistortion = !1, distortionOctaves: distortionOctaves = 3, distortionLacunarity: distortionLacunarity = 2, distortionGain: distortionGain = .5, distortionQuality: distortionQuality = "medium", contentRef: contentRef}, ref) => {
2256
- // Generate a stable, deterministic ID for SSR compatibility
2257
- // Use a module-level counter that's consistent across server and client
2258
- const filterId = useMemo((() => "atomix-glass-filter-" + ++idCounter), []), [shaderMapUrl, setShaderMapUrl] = useState(""), shaderGeneratorRef = useRef(null), shaderUtilsRef = useRef(null), shaderDebounceTimeoutRef = useRef(null), shaderUpdateTimeoutRef = useRef(null), animationFrameRef = useRef(null);
2251
+ // React 18 useId — stable, unique, and SSR-safe (no module-level counter)
2252
+ const rawId = useId(), filterId = useMemo((() => `atomix-glass-filter-${rawId.replace(/:/g, "")}`), [ rawId ]), [shaderMapUrl, setShaderMapUrl] = useState(""), shaderGeneratorRef = useRef(null), shaderUtilsRef = useRef(null), shaderDebounceTimeoutRef = useRef(null), shaderUpdateTimeoutRef = useRef(null), animationFrameRef = useRef(null);
2259
2253
  // Lazy load shader utilities only when shader mode is needed
2260
2254
  useEffect((() => {
2261
2255
  "shader" === mode ?
@@ -2278,9 +2272,7 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
2278
2272
  // Create cache key from size and variant
2279
2273
  const cacheKey = `${glassSize.width}x${glassSize.height}-${shaderVariant}`, cachedUrl = (key => {
2280
2274
  const entry = sharedShaderCache.get(key);
2281
- return entry ? (
2282
- // Update access timestamp for LRU
2283
- entry.timestamp = Date.now(), entry.url) : null;
2275
+ return entry ? (entry.timestamp = Date.now(), entry.url) : null;
2284
2276
  })(cacheKey);
2285
2277
  // Check shared cache first
2286
2278
  if (cachedUrl) return void setShaderMapUrl(cachedUrl);
@@ -2291,29 +2283,24 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
2291
2283
  if (shaderUtilsRef.current) try {
2292
2284
  const {ShaderDisplacementGenerator: ShaderDisplacementGenerator, fragmentShaders: fragmentShaders} = shaderUtilsRef.current;
2293
2285
  shaderGeneratorRef.current?.destroy();
2294
- const selectedShader = fragmentShaders[shaderVariant] || fragmentShaders.liquidGlass;
2286
+ const selectedShader = fragmentShaders[shaderVariant] ?? fragmentShaders.liquidGlass;
2295
2287
  shaderGeneratorRef.current = new ShaderDisplacementGenerator({
2296
2288
  width: glassSize.width,
2297
2289
  height: glassSize.height,
2298
2290
  fragment: selectedShader
2299
2291
  }), shaderUpdateTimeoutRef.current = setTimeout((() => {
2300
- const url = shaderGeneratorRef.current?.updateShader() || "";
2292
+ const url = shaderGeneratorRef.current?.updateShader() ?? "";
2301
2293
  url && ((key, url) => {
2302
- // Evict oldest entries if at capacity
2303
2294
  if (sharedShaderCache.size >= 15) {
2304
2295
  const entries = Array.from(sharedShaderCache.entries());
2305
- // Sort by timestamp (oldest first)
2306
- entries.sort(((a, b) => a[1].timestamp - b[1].timestamp));
2307
- // Remove oldest entry
2308
- const oldestEntry = entries[0];
2309
- oldestEntry && sharedShaderCache.delete(oldestEntry[0]);
2296
+ entries.sort(((a, b) => a[1].timestamp - b[1].timestamp));
2297
+ const oldest = entries[0];
2298
+ oldest && sharedShaderCache.delete(oldest[0]);
2310
2299
  }
2311
2300
  sharedShaderCache.set(key, {
2312
2301
  url: url,
2313
2302
  timestamp: Date.now()
2314
- }),
2315
- // Development mode: log cache size
2316
- "undefined" != typeof process && "production" === process.env?.NODE_ENV || sharedShaderCache.size;
2303
+ }), "undefined" != typeof process && "production" === process.env?.NODE_ENV || sharedShaderCache.size;
2317
2304
  })(cacheKey, url), setShaderMapUrl(url);
2318
2305
  }), 100);
2319
2306
  } catch (error) {
@@ -2382,7 +2369,6 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
2382
2369
  console.warn("AtomixGlassContainer: Error getting element bounds", error), setRectCache(null);
2383
2370
  }
2384
2371
  }), [ ref, glassSize ]);
2385
- // Pre-calculate static multipliers outside useMemo
2386
2372
  const liquidBlur = useMemo((() => {
2387
2373
  const defaultBlur = {
2388
2374
  baseBlur: blurAmount,
@@ -2451,7 +2437,6 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
2451
2437
  }), [ borderRadius, backdropStyle, mouseOffset, overLight, effectiveWithoutEffects, overLightConfig ]);
2452
2438
  return jsx("div", {
2453
2439
  ref: el => {
2454
- // Apply force no-transition
2455
2440
  // Handle forwarded ref
2456
2441
  "function" == typeof ref ? ref(el) : ref && (ref.current = el);
2457
2442
  },
@@ -2493,6 +2478,7 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
2493
2478
  });
2494
2479
  }));
2495
2480
 
2481
+ // ─── Blur multiplier constants (module-level, never change at runtime) ────────
2496
2482
  AtomixGlassContainer.displayName = "AtomixGlassContainer";
2497
2483
 
2498
2484
  // Singleton instance
@@ -2693,8 +2679,6 @@ class {
2693
2679
  backdropFilterString = !withLiquidBlur || effectiveReducedMotion || effectiveWithoutEffects || area > 18e4 ? `blur(${clampBlur(Math.max(liquidBlur.baseBlur, .8 * liquidBlur.edgeBlur, 1.1 * liquidBlur.centerBlur, .9 * liquidBlur.flowBlur))}px) saturate(${Math.min(dynamicSaturation, 200)}%) contrast(${overLightConfig.contrast}) brightness(${overLightConfig.brightness})` : `blur(${clampBlur(.4 * liquidBlur.baseBlur + .25 * liquidBlur.edgeBlur + .15 * liquidBlur.centerBlur + .2 * liquidBlur.flowBlur)}px) saturate(${Math.min(dynamicSaturation, 200)}%) contrast(${overLightConfig.contrast}) brightness(${overLightConfig.brightness})`;
2694
2680
  // Container variables
2695
2681
  const style = containerElement.style;
2696
- style.setProperty("--atomix-glass-container-width", isFixedOrSticky ? `${glassSize.width}` : "100%"),
2697
- style.setProperty("--atomix-glass-container-height", isFixedOrSticky ? `${glassSize.height}` : "100%"),
2698
2682
  style.setProperty("--atomix-glass-container-padding", padding), style.setProperty("--atomix-glass-container-radius", `${effectiveBorderRadius}px`),
2699
2683
  style.setProperty("--atomix-glass-container-backdrop", backdropFilterString),
2700
2684
  // Shadows
@@ -3155,47 +3139,55 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
3155
3139
  return "undefined" == typeof process || process.env, finalConfig;
3156
3140
  }
3157
3141
  return "undefined" == typeof process || process.env, baseConfig;
3158
- }), [ overLight, getEffectiveOverLight, isHovered, isActive, validateConfigValue, debugOverLight ]), transformStyle = useMemo((() => effectiveWithoutEffects || isActive && Boolean(onClick) ? "scale(0.98)" : "scale(1)"), [ effectiveWithoutEffects, isActive, onClick ]), updateRectRef = useRef(null), handleGlobalMousePosition = useCallback((globalPos => {
3159
- if (externalGlobalMousePosition && externalMouseOffset) return;
3160
- if (effectiveWithoutEffects) return;
3161
- const container = mouseContainer?.current || glassRef.current;
3162
- if (!container) return;
3163
- // Use cached rect if available, otherwise get new one
3164
- let rect = cachedRectRef.current;
3165
- if (rect && 0 !== rect.width && 0 !== rect.height || (rect = container.getBoundingClientRect(),
3166
- cachedRectRef.current = rect), 0 === rect.width || 0 === rect.height) return;
3167
- const center = calculateElementCenter(rect);
3168
- // Write raw target — the lerp loop will smoothly pursue it
3169
- targetMouseOffsetRef.current = {
3170
- x: (globalPos.x - center.x) / rect.width * 100,
3171
- y: (globalPos.y - center.y) / rect.height * 100
3172
- }, targetGlobalMousePositionRef.current = globalPos;
3173
- }), [ mouseContainer, glassRef, externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects ]), startLerpLoop = useCallback((() => {
3142
+ }), [ overLight, getEffectiveOverLight, isHovered, isActive, validateConfigValue, debugOverLight ]), transformStyle = useMemo((() => effectiveWithoutEffects || isActive && Boolean(onClick) ? "scale(0.98)" : "scale(1)"), [ effectiveWithoutEffects, isActive, onClick ]), updateRectRef = useRef(null), stopLerpLoop = useCallback((() => {
3143
+ lerpActiveRef.current = !1, null !== lerpRafRef.current && (cancelAnimationFrame(lerpRafRef.current),
3144
+ lerpRafRef.current = null);
3145
+ }), []), startLerpLoop = useCallback((() => {
3174
3146
  if (lerpActiveRef.current) return;
3175
3147
  lerpActiveRef.current = !0;
3176
3148
  const LERP_T = CONSTANTS.LERP_FACTOR, tick = () => {
3177
3149
  if (!lerpActiveRef.current) return;
3178
- // Add ref validity check to prevent memory leaks
3179
- if (!glassRef.current || !wrapperRef?.current) return void (lerpActiveRef.current = !1);
3150
+ if (!glassRef.current) return void (lerpActiveRef.current = !1);
3180
3151
  const cur = internalMouseOffsetRef.current, tgt = targetMouseOffsetRef.current, dx = tgt.x - cur.x, dy = tgt.y - cur.y;
3181
3152
  // If we're close enough, snap and park
3182
- if (Math.abs(dx) < .05 && Math.abs(dy) < .05) internalMouseOffsetRef.current = {
3153
+ if (Math.abs(dx) < .01 && Math.abs(dy) < .01) return internalMouseOffsetRef.current = {
3183
3154
  ...tgt
3184
3155
  }, internalGlobalMousePositionRef.current = {
3185
3156
  ...targetGlobalMousePositionRef.current
3186
- }; else {
3187
- internalMouseOffsetRef.current = {
3188
- x: lerp$1(cur.x, tgt.x, LERP_T),
3189
- y: lerp$1(cur.y, tgt.y, LERP_T)
3190
- };
3191
- const curG = internalGlobalMousePositionRef.current, tgtG = targetGlobalMousePositionRef.current;
3192
- internalGlobalMousePositionRef.current = {
3193
- x: lerp$1(curG.x, tgtG.x, LERP_T),
3194
- y: lerp$1(curG.y, tgtG.y, LERP_T)
3195
- };
3196
- }
3197
- // Imperative style update with the smoothed values
3198
- updateAtomixGlassStyles(wrapperRef.current, glassRef.current, {
3157
+ },
3158
+ // Final update and stop
3159
+ updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
3160
+ mouseOffset: internalMouseOffsetRef.current,
3161
+ globalMousePosition: internalGlobalMousePositionRef.current,
3162
+ glassSize: glassSize,
3163
+ isHovered: isHovered,
3164
+ isActive: isActive,
3165
+ isOverLight: overLightConfig.isOverLight,
3166
+ baseOverLightConfig: overLightConfig,
3167
+ effectiveBorderRadius: effectiveBorderRadius,
3168
+ effectiveWithoutEffects: effectiveWithoutEffects,
3169
+ effectiveReducedMotion: effectiveReducedMotion,
3170
+ elasticity: elasticity,
3171
+ directionalScale: isActive && Boolean(onClick) ? "scale(0.96)" : "scale(1)",
3172
+ onClick: onClick,
3173
+ withLiquidBlur: withLiquidBlur,
3174
+ blurAmount: blurAmount,
3175
+ saturation: saturation,
3176
+ padding: padding,
3177
+ isFixedOrSticky: isFixedOrSticky
3178
+ }), void stopLerpLoop();
3179
+ // Smooth step
3180
+ internalMouseOffsetRef.current = {
3181
+ x: lerp$1(cur.x, tgt.x, LERP_T),
3182
+ y: lerp$1(cur.y, tgt.y, LERP_T)
3183
+ };
3184
+ const curG = internalGlobalMousePositionRef.current, tgtG = targetGlobalMousePositionRef.current;
3185
+ internalGlobalMousePositionRef.current = {
3186
+ x: lerp$1(curG.x, tgtG.x, LERP_T),
3187
+ y: lerp$1(curG.y, tgtG.y, LERP_T)
3188
+ },
3189
+ // Imperative style update
3190
+ updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
3199
3191
  mouseOffset: internalMouseOffsetRef.current,
3200
3192
  globalMousePosition: internalGlobalMousePositionRef.current,
3201
3193
  glassSize: glassSize,
@@ -3218,10 +3210,24 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
3218
3210
  };
3219
3211
  // 0.08 – lower = more viscous
3220
3212
  lerpRafRef.current = requestAnimationFrame(tick);
3221
- }), [ glassRef, wrapperRef, glassSize, isHovered, isActive, overLightConfig, effectiveBorderRadius, effectiveWithoutEffects, effectiveReducedMotion, elasticity, onClick, withLiquidBlur, blurAmount, saturation, padding, isFixedOrSticky ]), stopLerpLoop = useCallback((() => {
3222
- lerpActiveRef.current = !1, null !== lerpRafRef.current && (cancelAnimationFrame(lerpRafRef.current),
3223
- lerpRafRef.current = null);
3224
- }), []);
3213
+ }), [ glassRef, wrapperRef, glassSize, isHovered, isActive, overLightConfig, effectiveBorderRadius, effectiveWithoutEffects, effectiveReducedMotion, elasticity, onClick, withLiquidBlur, blurAmount, saturation, padding, isFixedOrSticky, stopLerpLoop ]), handleGlobalMousePosition = useCallback((globalPos => {
3214
+ if (externalGlobalMousePosition && externalMouseOffset) return;
3215
+ if (effectiveWithoutEffects) return;
3216
+ const container = mouseContainer?.current || glassRef.current;
3217
+ if (!container) return;
3218
+ // Use cached rect if available, otherwise get new one
3219
+ let rect = cachedRectRef.current;
3220
+ if (rect && 0 !== rect.width && 0 !== rect.height || (rect = container.getBoundingClientRect(),
3221
+ cachedRectRef.current = rect), 0 === rect.width || 0 === rect.height) return;
3222
+ const center = calculateElementCenter(rect);
3223
+ // Write raw target — the lerp loop will smoothly pursue it
3224
+ targetMouseOffsetRef.current = {
3225
+ x: (globalPos.x - center.x) / rect.width * 100,
3226
+ y: (globalPos.y - center.y) / rect.height * 100
3227
+ }, targetGlobalMousePositionRef.current = globalPos,
3228
+ // Ensure the lerp loop is running to smoothly chase the new target
3229
+ lerpActiveRef.current || startLerpLoop();
3230
+ }), [ mouseContainer, glassRef, externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects, startLerpLoop ]);
3225
3231
  /**
3226
3232
  * Validate and clamp a numeric config value
3227
3233
  */
@@ -3230,7 +3236,7 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
3230
3236
  if (externalGlobalMousePosition && externalMouseOffset) return;
3231
3237
  if (effectiveWithoutEffects) return;
3232
3238
  const unsubscribe = globalMouseTracker.subscribe(handleGlobalMousePosition);
3233
- // Start the lerp loop — it will smoothly chase the target
3239
+ // Initial start
3234
3240
  startLerpLoop();
3235
3241
  const container = mouseContainer?.current || glassRef.current;
3236
3242
  let resizeObserver = null;
@@ -3243,7 +3249,7 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
3243
3249
  unsubscribe(), stopLerpLoop(), null !== updateRectRef.current && (cancelAnimationFrame(updateRectRef.current),
3244
3250
  updateRectRef.current = null), resizeObserver && resizeObserver.disconnect();
3245
3251
  };
3246
- }), [ handleGlobalMousePosition, startLerpLoop, stopLerpLoop, mouseContainer, glassRef, externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects ]),
3252
+ }), [ externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects, handleGlobalMousePosition, startLerpLoop, stopLerpLoop, mouseContainer, glassRef ]),
3247
3253
  // Also call updateStyles on other state changes (hover, active, etc)
3248
3254
  useEffect((() => {
3249
3255
  updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
@@ -3738,183 +3744,146 @@ function usePerformanceMonitor(config = {}) {
3738
3744
  }
3739
3745
  }
3740
3746
 
3747
+ /** Map an FPS value to a semantic color token string. */ const getQualityColor = quality => {
3748
+ switch (quality) {
3749
+ case "high":
3750
+ return "var(--atomix-color-success, #4ade80)";
3751
+
3752
+ case "medium":
3753
+ return "var(--atomix-color-warning, #fbbf24)";
3754
+
3755
+ case "low":
3756
+ return "var(--atomix-color-danger, #ef4444)";
3757
+
3758
+ default:
3759
+ return "#9ca3af";
3760
+ }
3761
+ }, getFpsLabel = fps => fps >= 58 ? "Optimal" : fps >= 45 ? "Warning" : "Critical";
3762
+
3763
+ /** Map a quality level string to a semantic color token string. */
3764
+ // Inject keyframes once
3765
+ if ("undefined" != typeof document) {
3766
+ const styleId = "perf-dashboard-keyframes";
3767
+ if (!document.getElementById(styleId)) {
3768
+ const styleEl = document.createElement("style");
3769
+ styleEl.id = styleId, styleEl.textContent = "\n@keyframes perf-dashboard-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n}\n",
3770
+ document.head.appendChild(styleEl);
3771
+ }
3772
+ }
3773
+
3741
3774
  /**
3742
- * PerformanceDashboard - Real-time performance monitoring overlay
3775
+ * PerformanceDashboard - Real-time performance monitoring overlay.
3743
3776
  *
3744
- * Displays:
3745
- * - Current FPS with color coding
3746
- * - Frame time statistics
3747
- * - Quality level indicator
3748
- * - GPU memory usage (if available)
3749
- * - Auto-scaling status
3750
- */ const PerformanceDashboard = ({metrics: metrics, isVisible: isVisible = !0, onClose: onClose}) => {
3751
- // Get color for FPS display
3752
- const getFpsColor = fps => fps >= 58 ? "#4ade80" : // Green - good
3753
- fps >= 45 ? "#fbbf24" : "#ef4444" // Red - critical
3754
- , dashboardStyle = useMemo((() => ({
3755
- position: "fixed",
3756
- top: "16px",
3757
- right: "16px",
3758
- padding: "12px 16px",
3759
- backgroundColor: "rgba(17, 24, 39, 0.95)",
3760
- borderRadius: "8px",
3761
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
3762
- fontFamily: "monospace",
3763
- fontSize: "12px",
3764
- color: "#fff",
3765
- zIndex: 9999,
3766
- minWidth: "200px",
3767
- backdropFilter: "blur(8px)",
3768
- border: "1px solid rgba(255, 255, 255, 0.1)",
3769
- transition: "opacity 0.3s ease",
3770
- opacity: isVisible ? 1 : 0,
3771
- pointerEvents: isVisible ? "auto" : "none"
3772
- })), [ isVisible ]), headerStyle = useMemo((() => ({
3773
- display: "flex",
3774
- justifyContent: "space-between",
3775
- alignItems: "center",
3776
- marginBottom: "8px",
3777
- paddingBottom: "8px",
3778
- borderBottom: "1px solid rgba(255, 255, 255, 0.1)"
3779
- })), []), titleStyle = useMemo((() => ({
3780
- fontWeight: "bold",
3781
- fontSize: "13px",
3782
- color: "#fff"
3783
- })), []), closeButtonStyle = useMemo((() => ({
3784
- background: "transparent",
3785
- border: "none",
3786
- color: "#9ca3af",
3787
- cursor: "pointer",
3788
- fontSize: "16px",
3789
- padding: "0",
3790
- lineHeight: "1"
3791
- })), []), metricRowStyle = useMemo((() => ({
3792
- display: "flex",
3793
- justifyContent: "space-between",
3794
- alignItems: "center",
3795
- marginBottom: "6px"
3796
- })), []), labelStyle = useMemo((() => ({
3797
- color: "#9ca3af",
3798
- marginRight: "12px"
3799
- })), []), valueStyle = useMemo((() => ({
3800
- fontWeight: "bold"
3801
- })), []);
3802
- // Get quality level badge color
3803
- return isVisible ? jsxs("div", {
3804
- style: dashboardStyle,
3777
+ * Displays FPS, frame time, quality level, GPU memory, and auto-scaling status.
3778
+ * Rendered only when `debugPerformance={true}` on the parent `AtomixGlass`.
3779
+ */ const PerformanceDashboard = memo((({metrics: metrics, isVisible: isVisible = !0, onClose: onClose}) => {
3780
+ if (!isVisible) return null;
3781
+ const fpsColor = (fps = metrics.fps) >= 58 ? "var(--atomix-color-success, #4ade80)" : fps >= 45 ? "var(--atomix-color-warning, #fbbf24)" : "var(--atomix-color-danger, #ef4444)";
3782
+ var fps;
3783
+ const isCritical = metrics.fps < 45;
3784
+ return jsxs("div", {
3785
+ className: "c-perf-dashboard u-position-fixed u-top-4 u-end-4 u-p-3 u-px-4 u-text-xs u-font-mono u-text-white u-rounded-md u-border u-border-white-alpha-10 u-shadow-lg",
3786
+ style: {
3787
+ zIndex: 9999,
3788
+ minWidth: "12.5rem",
3789
+ // 200px
3790
+ backgroundColor: "rgba(17, 24, 39, 0.95)",
3791
+ backdropFilter: "blur(8px)",
3792
+ transition: "opacity 0.3s ease"
3793
+ },
3805
3794
  children: [ jsxs("div", {
3806
- style: headerStyle,
3795
+ className: "u-flex u-items-center u-justify-between u-mb-2 u-pb-2 u-border-b u-border-white-alpha-10",
3807
3796
  children: [ jsx("span", {
3808
- style: titleStyle,
3797
+ className: "u-text-sm u-font-bold u-text-white",
3809
3798
  children: "Performance Monitor"
3810
3799
  }), onClose && jsx("button", {
3811
- style: closeButtonStyle,
3800
+ className: "u-bg-transparent u-border-none u-p-0 u-line-height-1 u-text-base u-text-gray-400 u-cursor-pointer hover:u-text-white",
3812
3801
  onClick: onClose,
3813
3802
  "aria-label": "Close performance dashboard",
3803
+ style: {
3804
+ transition: "color 0.2s ease"
3805
+ },
3814
3806
  children: "×"
3815
3807
  }) ]
3816
3808
  }), jsxs("div", {
3817
- style: metricRowStyle,
3809
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
3818
3810
  children: [ jsx("span", {
3819
- style: labelStyle,
3811
+ className: "u-text-gray-400 u-me-3",
3820
3812
  children: "FPS"
3821
3813
  }), jsx("span", {
3814
+ className: "u-font-bold",
3822
3815
  style: {
3823
- ...valueStyle,
3824
- color: getFpsColor(metrics.fps)
3816
+ color: fpsColor
3825
3817
  },
3826
3818
  children: Math.round(metrics.fps)
3827
3819
  }) ]
3828
3820
  }), jsxs("div", {
3829
- style: metricRowStyle,
3821
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
3830
3822
  children: [ jsx("span", {
3831
- style: labelStyle,
3823
+ className: "u-text-gray-400 u-me-3",
3832
3824
  children: "Frame Time"
3833
3825
  }), jsxs("span", {
3834
- style: valueStyle,
3826
+ className: "u-font-bold",
3835
3827
  children: [ metrics.frameTime.toFixed(2), "ms" ]
3836
3828
  }) ]
3837
3829
  }), jsxs("div", {
3838
- style: metricRowStyle,
3830
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
3839
3831
  children: [ jsx("span", {
3840
- style: labelStyle,
3832
+ className: "u-text-gray-400 u-me-3",
3841
3833
  children: "Quality"
3842
3834
  }), jsx("span", {
3835
+ className: "u-font-bold u-text-uppercase",
3843
3836
  style: {
3844
- ...valueStyle,
3845
- color: (quality => {
3846
- switch (quality) {
3847
- case "high":
3848
- return "#4ade80";
3849
-
3850
- case "medium":
3851
- return "#fbbf24";
3852
-
3853
- case "low":
3854
- return "#ef4444";
3855
-
3856
- default:
3857
- return "#9ca3af";
3858
- }
3859
- })(metrics.qualityLevel),
3860
- textTransform: "uppercase",
3861
- fontSize: "11px"
3837
+ fontSize: "0.6875rem",
3838
+ // 11px
3839
+ color: getQualityColor(metrics.qualityLevel)
3862
3840
  },
3863
3841
  children: metrics.qualityLevel
3864
3842
  }) ]
3865
3843
  }), metrics.gpuMemory && jsxs("div", {
3866
- style: metricRowStyle,
3844
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
3867
3845
  children: [ jsx("span", {
3868
- style: labelStyle,
3846
+ className: "u-text-gray-400 u-me-3",
3869
3847
  children: "GPU Memory"
3870
3848
  }), jsxs("span", {
3871
- style: valueStyle,
3849
+ className: "u-font-bold",
3872
3850
  children: [ "~", Math.round(metrics.gpuMemory / 1024), "MB" ]
3873
3851
  }) ]
3874
3852
  }), metrics.isAutoScaling && jsx("div", {
3853
+ className: "u-mt-2 u-pt-2 u-border-t u-border-white-alpha-10 u-text-center",
3875
3854
  style: {
3876
- marginTop: "8px",
3877
- paddingTop: "8px",
3878
- borderTop: "1px solid rgba(255, 255, 255, 0.1)",
3879
- fontSize: "10px",
3880
- color: "#6b7280",
3881
- textAlign: "center"
3855
+ fontSize: "0.625rem",
3856
+ // 10px
3857
+ color: "#6b7280"
3882
3858
  },
3883
3859
  children: "Auto-scaling active"
3884
3860
  }), jsxs("div", {
3885
- style: {
3886
- marginTop: "8px",
3887
- paddingTop: "8px",
3888
- borderTop: "1px solid rgba(255, 255, 255, 0.1)",
3889
- display: "flex",
3890
- alignItems: "center",
3891
- gap: "6px"
3892
- },
3861
+ className: "u-flex u-items-center u-gap-2 u-mt-2 u-pt-2 u-border-t u-border-white-alpha-10",
3893
3862
  children: [ jsx("div", {
3863
+ className: "u-rounded-full",
3894
3864
  style: {
3895
- width: "8px",
3896
- height: "8px",
3897
- borderRadius: "50%",
3898
- backgroundColor: getFpsColor(metrics.fps),
3899
- animation: metrics.fps < 45 ? "pulse 1s infinite" : "none"
3865
+ width: "0.5rem",
3866
+ height: "0.5rem",
3867
+ flexShrink: 0,
3868
+ backgroundColor: fpsColor,
3869
+ ...isCritical && {
3870
+ animation: "perf-dashboard-pulse 1s infinite"
3871
+ }
3900
3872
  }
3901
3873
  }), jsx("span", {
3874
+ className: "u-text-xs",
3902
3875
  style: {
3903
- fontSize: "10px",
3904
- color: metrics.fps >= 58 ? "#4ade80" : metrics.fps >= 45 ? "#fbbf24" : "#ef4444"
3876
+ fontSize: "0.625rem",
3877
+ // 10px
3878
+ color: fpsColor
3905
3879
  },
3906
- children: metrics.fps >= 58 ? "Optimal" : metrics.fps >= 45 ? "Warning" : "Critical"
3880
+ children: getFpsLabel(metrics.fps)
3907
3881
  }) ]
3908
3882
  }) ]
3909
- }) : null;
3910
- };
3883
+ });
3884
+ }));
3911
3885
 
3912
- // Add pulse animation for critical FPS
3913
- if ("undefined" != typeof document) {
3914
- const styleSheet = document.createElement("style");
3915
- styleSheet.textContent = "\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n ",
3916
- document.head.appendChild(styleSheet);
3917
- }
3886
+ PerformanceDashboard.displayName = "PerformanceDashboard";
3918
3887
 
3919
3888
  /**
3920
3889
  * Mobile optimization presets
@@ -3925,7 +3894,8 @@ if ("undefined" != typeof document) {
3925
3894
  /**
3926
3895
  * Performance preset - Maximum FPS, reduced quality
3927
3896
  * Best for low-end devices or when battery saving is priority
3928
- */ const PERFORMANCE_PRESET = {
3897
+ */
3898
+ const PERFORMANCE_PRESET = {
3929
3899
  distortionOctaves: 2,
3930
3900
  // Minimal FBM layers
3931
3901
  displacementScale: 50,
@@ -4088,90 +4058,21 @@ function getDevicePreset(presetName) {
4088
4058
  getPixelRatio: () => "undefined" == typeof window ? 1 : window.devicePixelRatio || 1,
4089
4059
  /** Check if device has touch support */
4090
4060
  hasTouchSupport: () => "undefined" != typeof window && ("ontouchstart" in window || navigator.maxTouchPoints > 0)
4091
- };
4092
-
4093
- /**
4094
- * AtomixGlass - A high-performance glass morphism component with liquid distortion effects
4095
- *
4096
- * Features:
4097
- * - Hardware-accelerated glass effects with SVG filters
4098
- * - Mouse-responsive liquid distortion
4099
- * - Dynamic border-radius extraction from children CSS properties
4100
- * - Automatic light/dark theme detection via overLight prop
4101
- * - Accessibility and performance optimizations
4102
- * - Multiple displacement modes (standard, polar, prominent, shader)
4103
- * - Design token integration for consistent theming
4104
- * - Focus ring support for keyboard navigation
4105
- * - Responsive breakpoints for mobile optimization
4106
- * - Enhanced ARIA attributes for screen readers
4107
- * - Time-based animation system with FBM distortion
4108
- * - Device preset optimization for performance/quality balance
4109
- *
4110
- * Design System Compliance:
4111
- * - Uses design tokens for opacity, spacing, and colors
4112
- * - Follows BEM methodology for class naming
4113
- * - Implements focus-ring mixin for accessibility
4114
- * - Supports reduced motion and high contrast preferences
4115
- *
4116
- * @example
4117
- * // Basic usage with dynamic border-radius extraction
4118
- * <AtomixGlass>
4119
- * <div style={{ borderRadius: '12px' }}>Content with 12px radius</div>
4120
- * </AtomixGlass>
4121
- *
4122
- * @example
4123
- * // Manual border-radius override
4124
- * <AtomixGlass borderRadius={20}>
4125
- * <div>Content with 20px glass radius</div>
4126
- * </AtomixGlass>
4127
- *
4128
- * @example
4129
- * // Interactive glass with click handler
4130
- * <AtomixGlass onClick={() => console.log('Clicked')} aria-label="Glass card">
4131
- * <div>Clickable content</div>
4132
- * </AtomixGlass>
4133
- *
4134
- * @example
4135
- * // OverLight - Boolean mode (explicit control)
4136
- * <AtomixGlass overLight={true}>
4137
- * <div>Content on light background</div>
4138
- * </AtomixGlass>
4139
- *
4140
- * @example
4141
- * // OverLight - Auto-detection mode
4142
- * <AtomixGlass overLight="auto">
4143
- * <div>Content with auto-detected background</div>
4144
- * </AtomixGlass>
4145
- *
4146
- * @example
4147
- * // OverLight - Object config with custom settings
4148
- * <AtomixGlass
4149
- * overLight={{
4150
- * threshold: 0.8,
4151
- * opacity: 0.6,
4152
- * contrast: 1.8,
4153
- * brightness: 1.0,
4154
- * saturationBoost: 1.5
4155
- * }}
4156
- * >
4157
- * <div>Content with custom overLight config</div>
4158
- * </AtomixGlass>
4159
- *
4160
- * @example
4161
- * // Debug mode for overLight detection
4162
- * <AtomixGlass overLight="auto" debugOverLight={true}>
4163
- * <div>Content with debug logging enabled</div>
4164
- * </AtomixGlass>
4165
- *
4166
- * @example
4167
- * // Performance-optimized for mobile devices
4168
- * <AtomixGlass devicePreset="performance" disableResponsiveBreakpoints={false}>
4169
- * <div>Mobile-optimized glass effect</div>
4170
- * </AtomixGlass>
4171
- */ function AtomixGlass({children: children, displacementScale: displacementScale = ATOMIX_GLASS.DEFAULTS.DISPLACEMENT_SCALE, blurAmount: blurAmount = ATOMIX_GLASS.DEFAULTS.BLUR_AMOUNT, saturation: saturation = ATOMIX_GLASS.DEFAULTS.SATURATION, aberrationIntensity: aberrationIntensity = ATOMIX_GLASS.DEFAULTS.ABERRATION_INTENSITY, elasticity: elasticity = ATOMIX_GLASS.DEFAULTS.ELASTICITY, borderRadius: borderRadius, globalMousePosition: externalGlobalMousePosition, mouseOffset: externalMouseOffset, mouseContainer: mouseContainer = null, className: className = "", padding: padding = ATOMIX_GLASS.DEFAULTS.PADDING, overLight: overLight = ATOMIX_GLASS.DEFAULTS.OVER_LIGHT, style: style = {}, mode: mode = ATOMIX_GLASS.DEFAULTS.MODE, onClick: onClick, shaderVariant: shaderVariant = "liquidGlass", "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, role: role, tabIndex: tabIndex, reducedMotion: reducedMotion = !1, highContrast: highContrast = !1, withoutEffects: withoutEffects = !1, withLiquidBlur: withLiquidBlur = !1, withBorder: withBorder = !0, withOverLightLayers: withOverLightLayers = ATOMIX_GLASS.DEFAULTS.ENABLE_OVER_LIGHT_LAYERS, debugPerformance: debugPerformance = !1, debugOverLight: debugOverLight = !1, height: height, width: width, withTimeAnimation: withTimeAnimation = !1, animationSpeed: animationSpeed = 1, withMultiLayerDistortion: withMultiLayerDistortion = !1, distortionOctaves: distortionOctaves = 3, distortionLacunarity: distortionLacunarity = 2, distortionGain: distortionGain = .5, distortionQuality: distortionQuality = "medium", devicePreset: devicePreset = "balanced", disableResponsiveBreakpoints: disableResponsiveBreakpoints = !1, ...rest}) {
4172
- const glassRef = useRef(null), contentRef = useRef(null), {zIndex: customZIndex, ...restStyle} = style, isFixedOrSticky = "fixed" === restStyle.position || "sticky" === restStyle.position, {isHovered: isHovered, isActive: isActive, glassSize: glassSize, effectiveBorderRadius: effectiveBorderRadius, effectiveReducedMotion: effectiveReducedMotion, effectiveHighContrast: effectiveHighContrast, effectiveWithoutEffects: effectiveWithoutEffects, overLightConfig: overLightConfig, globalMousePosition: globalMousePosition, mouseOffset: mouseOffset, transformStyle: transformStyle, getShaderTime: getShaderTime, applyTimeBasedDistortion: applyTimeBasedDistortion, handleMouseEnter: handleMouseEnter, handleMouseLeave: handleMouseLeave, handleMouseDown: handleMouseDown, handleMouseUp: handleMouseUp, handleKeyDown: handleKeyDown} = useAtomixGlass({
4061
+ }, AtomixGlassInner = forwardRef((function({children: children, displacementScale: displacementScale = ATOMIX_GLASS.DEFAULTS.DISPLACEMENT_SCALE, blurAmount: blurAmount = ATOMIX_GLASS.DEFAULTS.BLUR_AMOUNT, saturation: saturation = ATOMIX_GLASS.DEFAULTS.SATURATION, aberrationIntensity: aberrationIntensity = ATOMIX_GLASS.DEFAULTS.ABERRATION_INTENSITY, elasticity: elasticity = ATOMIX_GLASS.DEFAULTS.ELASTICITY, borderRadius: borderRadius, globalMousePosition: externalGlobalMousePosition, mouseOffset: externalMouseOffset, mouseContainer: mouseContainer = null, className: className = "", padding: padding = ATOMIX_GLASS.DEFAULTS.PADDING, overLight: overLight = ATOMIX_GLASS.DEFAULTS.OVER_LIGHT, style: style = {}, mode: mode = ATOMIX_GLASS.DEFAULTS.MODE, onClick: onClick, shaderVariant: shaderVariant = "liquidGlass", "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, role: role, tabIndex: tabIndex, reducedMotion: reducedMotion = !1, highContrast: highContrast = !1, withoutEffects: withoutEffects = !1, withLiquidBlur: withLiquidBlur = !1, withBorder: withBorder = !0, withOverLightLayers: withOverLightLayers = ATOMIX_GLASS.DEFAULTS.ENABLE_OVER_LIGHT_LAYERS, debugPerformance: debugPerformance = !1, debugOverLight: debugOverLight = !1, height: height, width: width, withTimeAnimation: withTimeAnimation = !1, animationSpeed: animationSpeed = 1, withMultiLayerDistortion: withMultiLayerDistortion = !1, distortionOctaves: distortionOctaves = 3, distortionLacunarity: distortionLacunarity = 2, distortionGain: distortionGain = .5, distortionQuality: distortionQuality = "medium", devicePreset: devicePreset = "balanced", disableResponsiveBreakpoints: disableResponsiveBreakpoints = !1, isFixedOrSticky: propsIsFixedOrSticky, ...rest}, ref) {
4062
+ const glassRef = useRef(null), contentRef = useRef(null), internalWrapperRef = useRef(null), mergedRef = useMemo((() =>
4063
+ // Helper to merge refs
4064
+ function(...refs) {
4065
+ return node => {
4066
+ refs.forEach((ref => {
4067
+ "function" == typeof ref ? ref(node) : null != ref && (ref.current = node);
4068
+ }));
4069
+ };
4070
+ }
4071
+ // Internal implementation with forwardRef
4072
+ (ref, internalWrapperRef)), [ ref ]), {zIndex: customZIndex, ...restStyle} = style, isFixedOrSticky = propsIsFixedOrSticky || "fixed" === restStyle.position || "sticky" === restStyle.position, {isHovered: isHovered, isActive: isActive, glassSize: glassSize, effectiveBorderRadius: effectiveBorderRadius, effectiveReducedMotion: effectiveReducedMotion, effectiveHighContrast: effectiveHighContrast, effectiveWithoutEffects: effectiveWithoutEffects, overLightConfig: overLightConfig, globalMousePosition: globalMousePosition, mouseOffset: mouseOffset, transformStyle: transformStyle, getShaderTime: getShaderTime, handleMouseEnter: handleMouseEnter, handleMouseLeave: handleMouseLeave, handleMouseDown: handleMouseDown, handleMouseUp: handleMouseUp, handleKeyDown: handleKeyDown} = useAtomixGlass({
4173
4073
  glassRef: glassRef,
4174
4074
  contentRef: contentRef,
4075
+ wrapperRef: internalWrapperRef,
4175
4076
  borderRadius: borderRadius,
4176
4077
  globalMousePosition: externalGlobalMousePosition,
4177
4078
  mouseOffset: externalMouseOffset,
@@ -4200,7 +4101,6 @@ function getDevicePreset(presetName) {
4200
4101
  distortionGain: distortionGain,
4201
4102
  distortionQuality: distortionQuality
4202
4103
  });
4203
- // Re-calculate only when devicePreset changes
4204
4104
  // Responsive breakpoint system - automatically adjusts parameters based on viewport
4205
4105
  useResponsiveGlass({
4206
4106
  baseParams: {
@@ -4215,22 +4115,21 @@ function getDevicePreset(presetName) {
4215
4115
  },
4216
4116
  breakpoints: MOBILE_OPTIMIZED_BREAKPOINTS,
4217
4117
  enabled: !disableResponsiveBreakpoints && "undefined" != typeof window,
4218
- // Enable unless disabled
4219
4118
  debug: !1
4220
4119
  });
4221
4120
  // Performance monitoring - tracks FPS, frame time, memory usage
4222
- const {metrics: performanceMetrics, recommendedQuality: recommendedQuality, isUnderperforming: isUnderperforming, setQualityLevel: setQualityLevel, toggleMonitoring: toggleMonitoring} = usePerformanceMonitor({
4223
- enabled: !1,
4224
- // We'll toggle manually based on prop
4121
+ const {metrics: performanceMetrics, toggleMonitoring: toggleMonitoring} = usePerformanceMonitor({
4122
+ enabled: debugPerformance,
4123
+ // Enable when debugPerformance is true
4225
4124
  debug: !1,
4226
4125
  showOverlay: !1
4227
4126
  });
4228
- // Auto-start performance monitoring if enabled (only in development)
4127
+ // Auto-start performance monitoring when debugPerformance is enabled
4229
4128
  React.useEffect((() => {
4230
- "development" === process.env.NODE_ENV && window?.enablePerformanceMonitoring && toggleMonitoring();
4129
+ debugPerformance && toggleMonitoring();
4231
4130
  // eslint-disable-next-line react-hooks/exhaustive-deps
4232
- }), []);
4233
- // Only run once on mount
4131
+ }), [ debugPerformance ]);
4132
+ // Re-run when debugPerformance changes
4234
4133
  const isOverLight = useMemo((() => overLightConfig.isOverLight), [ overLightConfig.isOverLight ]), shouldRenderOverLightLayers = withOverLightLayers && isOverLight, rootLayoutStyle = useMemo((() => {
4235
4134
  if (!isFixedOrSticky) return {};
4236
4135
  const {position: p, top: t, left: l, right: r, bottom: b} = restStyle;
@@ -4258,34 +4157,30 @@ function getDevicePreset(presetName) {
4258
4157
  if (isFixedOrSticky) {
4259
4158
  const {position: _p, top: _t, left: _l, right: _r, bottom: _b, ...visualStyle} = restStyle;
4260
4159
  return {
4261
- ...visualStyle,
4262
- ...!effectiveWithoutEffects && {
4263
- transform: transformStyle
4264
- }
4160
+ ...visualStyle
4265
4161
  };
4266
4162
  }
4267
4163
  return {
4268
- ...restStyle,
4269
- ...!effectiveWithoutEffects && {
4270
- transform: transformStyle
4271
- }
4164
+ ...restStyle
4272
4165
  };
4273
- }), [ isFixedOrSticky, restStyle, effectiveWithoutEffects, transformStyle ]);
4166
+ }), [ isFixedOrSticky, restStyle ]);
4274
4167
  // Build className with state modifiers
4275
4168
  const componentClassName = [ ATOMIX_GLASS.BASE_CLASS, effectiveReducedMotion && `${ATOMIX_GLASS.BASE_CLASS}--reduced-motion`, effectiveHighContrast && `${ATOMIX_GLASS.BASE_CLASS}--high-contrast`, effectiveWithoutEffects && `${ATOMIX_GLASS.BASE_CLASS}--disabled-effects`, className ].filter(Boolean).join(" "), positionStyles = useMemo((() => ({
4276
4169
  position: isFixedOrSticky ? "absolute" : restStyle.position || "absolute",
4277
- top: isFixedOrSticky ? 0 : restStyle.top || 0,
4278
- left: isFixedOrSticky ? 0 : restStyle.left || 0
4279
- })), [ isFixedOrSticky, restStyle.position, restStyle.top, restStyle.left ]), adjustedSize = useMemo((() => {
4170
+ top: isFixedOrSticky ? restStyle.top ?? 0 : 0,
4171
+ left: isFixedOrSticky ? restStyle.left ?? 0 : 0,
4172
+ right: isFixedOrSticky ? restStyle.right ?? "auto" : "auto",
4173
+ bottom: isFixedOrSticky ? restStyle.bottom ?? "auto" : "auto"
4174
+ })), [ isFixedOrSticky, restStyle.position, restStyle.top, restStyle.left, restStyle.right, restStyle.bottom ]), adjustedSize = useMemo((() => {
4280
4175
  // Keep a reference to positionStyles to avoid unused-variable lint,
4281
4176
  // but sizing is driven by explicit width/height or measured size.
4282
4177
  positionStyles.position;
4283
- const resolveLength = (value, measured) => void 0 !== value ? "number" == typeof value ? `${value}px` : value : measured > 0 ? `${measured}px` : "100%", effectiveWidth = width ?? restStyle.width, effectiveHeight = height ?? restStyle.height;
4178
+ const resolveLength = (value, measured) => void 0 !== value && isFixedOrSticky ? "number" == typeof value ? `${value}px` : value : measured > 0 && isFixedOrSticky ? `${measured}px` : "100%", effectiveWidth = width ?? restStyle.width, effectiveHeight = height ?? restStyle.height;
4284
4179
  return {
4285
4180
  width: resolveLength(effectiveWidth, glassSize.width),
4286
4181
  height: resolveLength(effectiveHeight, glassSize.height)
4287
4182
  };
4288
- }), [ width, height, restStyle.width, restStyle.height, positionStyles.position, glassSize.width, glassSize.height ]), gradientValues = useMemo((() => {
4183
+ }), [ width, height, restStyle.width, restStyle.height, positionStyles.position, glassSize.width, glassSize.height, isFixedOrSticky ]), gradientValues = useMemo((() => {
4289
4184
  const mx = mouseOffset.x, my = mouseOffset.y, absMx = Math.abs(mx), absMy = Math.abs(my), GRADIENT = ATOMIX_GLASS.CONSTANTS.GRADIENT;
4290
4185
  return {
4291
4186
  borderGradientAngle: GRADIENT.BASE_ANGLE + mx * GRADIENT.ANGLE_MULTIPLIER,
@@ -4315,33 +4210,32 @@ function getDevicePreset(presetName) {
4315
4210
  absMx: absMx,
4316
4211
  absMy: absMy
4317
4212
  };
4318
- }), [ mouseOffset.x, mouseOffset.y ]), opacityValues = useMemo((() => {
4319
- const overLightOpacity = overLightConfig.opacity;
4320
- return {
4321
- hover1: isHovered || isActive ? .5 : 0,
4322
- hover2: isActive ? .5 : 0,
4323
- hover3: isHovered ? .4 : isActive ? .8 : 0,
4324
- base: isOverLight ? overLightOpacity || .4 : 0,
4325
- over: isOverLight ? 1.1 * (overLightOpacity || .4) : 0
4326
- };
4327
- }), [ isHovered, isActive, isOverLight, overLightConfig.opacity ]), glassVars = useMemo((() => {
4328
- const whiteColor = ATOMIX_GLASS.CONSTANTS.PALETTE.WHITE, blackColor = ATOMIX_GLASS.CONSTANTS.PALETTE.BLACK, {borderGradientAngle: borderGradientAngle, borderStop1: borderStop1, borderStop2: borderStop2, borderOpacities: borderOpacities, hoverPositions: hoverPositions, basePosition: basePosition, mx: mx, my: my, absMx: absMx, absMy: absMy} = gradientValues, configBorderOpacity = overLightConfig?.borderOpacity ?? 1;
4213
+ }), [ mouseOffset.x, mouseOffset.y ]), clampedOverLightOpacity = Math.max(0, Math.min(1, overLightConfig?.opacity ?? .4)), clampedBorderOpacity = Math.max(0, Math.min(1, overLightConfig?.borderOpacity ?? 1)), opacityValues = useMemo((() => ({
4214
+ hover1: isHovered || isActive ? .5 : 0,
4215
+ hover2: isActive ? .5 : 0,
4216
+ hover3: isHovered ? .4 : isActive ? .8 : 0,
4217
+ base: isOverLight ? clampedOverLightOpacity || .4 : 0,
4218
+ over: isOverLight ? 1.1 * (clampedOverLightOpacity || .4) : 0
4219
+ })), [ isHovered, isActive, isOverLight, clampedOverLightOpacity ]), glassVars = useMemo((() => {
4220
+ const whiteColor = ATOMIX_GLASS.CONSTANTS.PALETTE.WHITE, blackColor = ATOMIX_GLASS.CONSTANTS.PALETTE.BLACK, {borderGradientAngle: borderGradientAngle, borderStop1: borderStop1, borderStop2: borderStop2, borderOpacities: borderOpacities, hoverPositions: hoverPositions, basePosition: basePosition, mx: mx, my: my, absMx: absMx, absMy: absMy} = gradientValues;
4329
4221
  return {
4330
4222
  ...void 0 !== customZIndex && {
4331
4223
  "--atomix-glass-base-z-index": customZIndex
4332
4224
  },
4333
4225
  "--atomix-glass-radius": `${effectiveBorderRadius}px`,
4334
4226
  "--atomix-glass-transform": transformStyle || "none",
4335
- // Internal decorative layers are positioned relative to the root;
4336
- "--atomix-glass-position": rootLayoutStyle.position,
4337
- "--atomix-glass-top": `${isFixedOrSticky ? rootLayoutStyle.top : 0}px`,
4338
- "--atomix-glass-left": `${isFixedOrSticky ? rootLayoutStyle.left : 0}px`,
4227
+ "--atomix-glass-container-position": `${isFixedOrSticky ? rootLayoutStyle.position : positionStyles.position}`,
4228
+ "--atomix-glass-position": `${isFixedOrSticky ? rootLayoutStyle.position : positionStyles.position}`,
4229
+ "--atomix-glass-top": `${isFixedOrSticky ? restStyle.top ?? 0 : 0}px`,
4230
+ "--atomix-glass-left": `${isFixedOrSticky ? restStyle.left ?? 0 : 0}px`,
4231
+ "--atomix-glass-right": isFixedOrSticky ? restStyle.right ?? "auto" : "auto",
4232
+ "--atomix-glass-bottom": isFixedOrSticky ? restStyle.bottom ?? "auto" : "auto",
4339
4233
  "--atomix-glass-width": adjustedSize.width,
4340
4234
  "--atomix-glass-height": adjustedSize.height,
4341
- "--atomix-glass-border-width": "var(--atomix-spacing-0-5, 0.09375rem)",
4235
+ "--atomix-glass-border-width": "var(--atomix-spacing-0-5, 0.125rem)",
4342
4236
  "--atomix-glass-blend-mode": isOverLight ? "multiply" : "overlay",
4343
- "--atomix-glass-border-gradient-1": `linear-gradient(${borderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${(borderOpacities[0] ?? 1) * configBorderOpacity}) ${borderStop1}%, rgba(${whiteColor}, ${(borderOpacities[1] ?? 1) * configBorderOpacity}) ${borderStop2}%, rgba(${whiteColor}, 0) 100%)`,
4344
- "--atomix-glass-border-gradient-2": `linear-gradient(${borderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${(borderOpacities[2] ?? 1) * configBorderOpacity}) ${borderStop1}%, rgba(${whiteColor}, ${(borderOpacities[3] ?? 1) * configBorderOpacity}) ${borderStop2}%, rgba(${whiteColor}, 0) 100%)`,
4237
+ "--atomix-glass-border-gradient-1": `linear-gradient(${borderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${(borderOpacities[0] ?? 1) * clampedBorderOpacity}) ${borderStop1}%, rgba(${whiteColor}, ${(borderOpacities[1] ?? 1) * clampedBorderOpacity}) ${borderStop2}%, rgba(${whiteColor}, 0) 100%)`,
4238
+ "--atomix-glass-border-gradient-2": `linear-gradient(${borderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${(borderOpacities[2] ?? 1) * clampedBorderOpacity}) ${borderStop1}%, rgba(${whiteColor}, ${(borderOpacities[3] ?? 1) * clampedBorderOpacity}) ${borderStop2}%, rgba(${whiteColor}, 0) 100%)`,
4345
4239
  "--atomix-glass-hover-1-opacity": opacityValues.hover1,
4346
4240
  "--atomix-glass-hover-1-gradient": isOverLight ? `radial-gradient(circle at ${hoverPositions.hover1.x}% ${hoverPositions.hover1.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_START}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_STOP}%, rgba(${blackColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_END}%)` : `radial-gradient(circle at ${hoverPositions.hover1.x}% ${hoverPositions.hover1.y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.WHITE_STOP}%)`,
4347
4241
  "--atomix-glass-hover-2-opacity": opacityValues.hover2,
@@ -4355,13 +4249,14 @@ function getDevicePreset(presetName) {
4355
4249
  "--atomix-glass-overlay-highlight-opacity": opacityValues.over * ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.OPACITY_MULTIPLIER,
4356
4250
  "--atomix-glass-overlay-highlight-bg": `radial-gradient(circle at ${ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.POSITION_X}% ${ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.POSITION_Y}%, rgba(255, 255, 255, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.WHITE_OPACITY}) 0%, transparent ${ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.STOP}%)`
4357
4251
  };
4358
- }), [ gradientValues, opacityValues, effectiveBorderRadius, transformStyle, adjustedSize, isOverLight, overLightConfig.borderOpacity, customZIndex, rootLayoutStyle, isFixedOrSticky ]), renderBackgroundLayer = layerType => jsx("div", {
4252
+ }), [ gradientValues, opacityValues, effectiveBorderRadius, transformStyle, adjustedSize, isOverLight, clampedBorderOpacity, customZIndex, isFixedOrSticky, positionStyles.position, rootLayoutStyle.position, restStyle.top, restStyle.left, restStyle.right, restStyle.bottom ]), renderBackgroundLayer = layerType => jsx("div", {
4359
4253
  className: [ ATOMIX_GLASS.BACKGROUND_LAYER_CLASS, "dark" === layerType ? ATOMIX_GLASS.BACKGROUND_LAYER_DARK_CLASS : ATOMIX_GLASS.BACKGROUND_LAYER_BLACK_CLASS, isOverLight ? ATOMIX_GLASS.BACKGROUND_LAYER_OVER_LIGHT_CLASS : ATOMIX_GLASS.BACKGROUND_LAYER_HIDDEN_CLASS ].filter(Boolean).join(" ")
4360
4254
  });
4361
4255
  // Calculate position and size styles for internal layers
4362
4256
  // When root is fixed/sticky, internal layers use absolute (relative to root)
4363
4257
  return jsxs("div", {
4364
4258
  ...rest,
4259
+ ref: mergedRef,
4365
4260
  className: componentClassName,
4366
4261
  style: {
4367
4262
  ...glassVars
@@ -4371,17 +4266,14 @@ function getDevicePreset(presetName) {
4371
4266
  "aria-label": ariaLabel,
4372
4267
  "aria-describedby": ariaDescribedBy,
4373
4268
  "aria-disabled": !(!onClick || !effectiveWithoutEffects) || !onClick && void 0,
4374
- "aria-pressed": !(!onClick || !isActive) || !onClick && void 0,
4269
+ "aria-pressed": void 0,
4375
4270
  onKeyDown: onClick ? handleKeyDown : void 0,
4376
4271
  children: [ jsx(AtomixGlassContainer, {
4377
4272
  ref: glassRef,
4378
4273
  contentRef: contentRef,
4379
4274
  className: className,
4380
4275
  style: {
4381
- ...restStyle,
4382
- ...!isFixedOrSticky && {
4383
- position: "relative"
4384
- }
4276
+ ...restStyle
4385
4277
  },
4386
4278
  borderRadius: effectiveBorderRadius,
4387
4279
  displacementScale: effectiveWithoutEffects ? 0 : "shader" === mode ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_DISPLACEMENT : isOverLight ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.OVER_LIGHT_DISPLACEMENT : displacementScale,
@@ -4417,6 +4309,7 @@ function getDevicePreset(presetName) {
4417
4309
  effectiveReducedMotion: effectiveReducedMotion,
4418
4310
  shaderVariant: shaderVariant,
4419
4311
  withLiquidBlur: withLiquidBlur,
4312
+ isFixedOrSticky: isFixedOrSticky,
4420
4313
  // Phase 1: Animation System props
4421
4314
  shaderTime: getShaderTime(),
4422
4315
  withTimeAnimation: withTimeAnimation,
@@ -4445,6 +4338,8 @@ function getDevicePreset(presetName) {
4445
4338
  }) ]
4446
4339
  }), withBorder && jsxs(Fragment, {
4447
4340
  children: [ jsx("span", {
4341
+ className: ATOMIX_GLASS.BORDER_BACKDROP_CLASS
4342
+ }), jsx("span", {
4448
4343
  className: ATOMIX_GLASS.BORDER_1_CLASS
4449
4344
  }), jsx("span", {
4450
4345
  className: ATOMIX_GLASS.BORDER_2_CLASS
@@ -4455,10 +4350,15 @@ function getDevicePreset(presetName) {
4455
4350
  onClose: () => {}
4456
4351
  }) ]
4457
4352
  });
4458
- }
4353
+ }));
4459
4354
 
4460
- // Default icon
4461
- const DefaultIcon = () => jsx("i", {
4355
+ AtomixGlassInner.displayName = "AtomixGlass";
4356
+
4357
+ /**
4358
+ * AtomixGlass - wrapped with React.memo to prevent unnecessary re-renders.
4359
+ * Ref is forwarded to the root `<div>` element.
4360
+ */
4361
+ const AtomixGlass = memo(AtomixGlassInner), DefaultIcon = () => jsx("i", {
4462
4362
  className: "c-accordion__icon",
4463
4363
  "aria-hidden": "true",
4464
4364
  children: jsx("svg", {
@@ -4492,6 +4392,7 @@ const DefaultIcon = () => jsx("i", {
4492
4392
  });
4493
4393
  }));
4494
4394
 
4395
+ // Default icon
4495
4396
  AccordionHeader.displayName = "AccordionHeader";
4496
4397
 
4497
4398
  const AccordionBody = forwardRef((({children: children, className: className = "", panelRef: panelRef, contentRef: contentRef, ...props}, ref) => {