@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.js CHANGED
@@ -1718,6 +1718,7 @@ const THEME_COLORS = [ "primary", "secondary", "success", "info", "warning", "er
1718
1718
  FILTER_OVERLAY_CLASS: "c-atomix-glass__filter-overlay",
1719
1719
  FILTER_SHADOW_CLASS: "c-atomix-glass__filter-shadow",
1720
1720
  CONTENT_CLASS: "c-atomix-glass__content",
1721
+ BORDER_BACKDROP_CLASS: "c-atomix-glass__border-backdrop",
1721
1722
  BORDER_1_CLASS: "c-atomix-glass__border-1",
1722
1723
  BORDER_2_CLASS: "c-atomix-glass__border-2",
1723
1724
  HOVER_1_CLASS: "c-atomix-glass__hover-1",
@@ -2120,7 +2121,7 @@ const {CONSTANTS: CONSTANTS$2} = ATOMIX_GLASS, calculateDistance = (pos1, pos2)
2120
2121
  default:
2121
2122
  return console.warn("AtomixGlass: Invalid displacement mode"), displacementMap;
2122
2123
  }
2123
- }, GlassFilterComponent = ({id: id, displacementScale: displacementScale, aberrationIntensity: aberrationIntensity, mode: mode, shaderMapUrl: shaderMapUrl, blurAmount: blurAmount}) => jsxRuntime.jsx("svg", {
2124
+ }, sharedShaderCache = new Map, GlassFilterComponent = ({id: id, displacementScale: displacementScale, aberrationIntensity: aberrationIntensity, mode: mode, shaderMapUrl: shaderMapUrl, blurAmount: blurAmount}) => jsxRuntime.jsx("svg", {
2124
2125
  style: {
2125
2126
  position: "absolute",
2126
2127
  width: "100%",
@@ -2261,24 +2262,17 @@ const {CONSTANTS: CONSTANTS$2} = ATOMIX_GLASS, calculateDistance = (pos1, pos2)
2261
2262
  */ GlassFilterComponent.displayName = "GlassFilter";
2262
2263
 
2263
2264
  // Memoize component to prevent unnecessary re-renders
2264
- const GlassFilter = React.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));
2265
-
2266
- // Module-level counter for deterministic ID generation
2267
- let idCounter = 0;
2268
-
2269
- // Module-level shared shader cache with LRU eviction
2270
- const sharedShaderCache = new Map, AtomixGlassContainer = React.forwardRef((({children: children, className: className = "", style: style, displacementScale: displacementScale = 25, blurAmount: blurAmount = .0625, saturation: saturation = 180, aberrationIntensity: aberrationIntensity = 2, mouseOffset: mouseOffset = {
2265
+ const GlassFilter = React.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 = React.forwardRef((({children: children, className: className = "", style: style, displacementScale: displacementScale = 25, blurAmount: blurAmount = .0625, saturation: saturation = 180, aberrationIntensity: aberrationIntensity = 2, mouseOffset: mouseOffset = {
2271
2266
  x: 0,
2272
2267
  y: 0
2273
2268
  }, 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 = {
2274
2269
  width: 0,
2275
2270
  height: 0
2276
- }, onClick: onClick, mode: mode = "standard", effectiveWithoutEffects: effectiveWithoutEffects = !1, effectiveReducedMotion: effectiveReducedMotion = !1, shaderVariant: shaderVariant = "liquidGlass", withLiquidBlur: withLiquidBlur = !1,
2271
+ }, onClick: onClick, mode: mode = "standard", effectiveWithoutEffects: effectiveWithoutEffects = !1, effectiveReducedMotion: effectiveReducedMotion = !1, shaderVariant: shaderVariant = "liquidGlass", withLiquidBlur: withLiquidBlur = !1, isFixedOrSticky: isFixedOrSticky = !1,
2277
2272
  // Phase 1: Animation System props
2278
2273
  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) => {
2279
- // Generate a stable, deterministic ID for SSR compatibility
2280
- // Use a module-level counter that's consistent across server and client
2281
- const filterId = React.useMemo((() => "atomix-glass-filter-" + ++idCounter), []), [shaderMapUrl, setShaderMapUrl] = React.useState(""), shaderGeneratorRef = React.useRef(null), shaderUtilsRef = React.useRef(null), shaderDebounceTimeoutRef = React.useRef(null), shaderUpdateTimeoutRef = React.useRef(null), animationFrameRef = React.useRef(null);
2274
+ // React 18 useId — stable, unique, and SSR-safe (no module-level counter)
2275
+ const rawId = React.useId(), filterId = React.useMemo((() => `atomix-glass-filter-${rawId.replace(/:/g, "")}`), [ rawId ]), [shaderMapUrl, setShaderMapUrl] = React.useState(""), shaderGeneratorRef = React.useRef(null), shaderUtilsRef = React.useRef(null), shaderDebounceTimeoutRef = React.useRef(null), shaderUpdateTimeoutRef = React.useRef(null), animationFrameRef = React.useRef(null);
2282
2276
  // Lazy load shader utilities only when shader mode is needed
2283
2277
  React.useEffect((() => {
2284
2278
  "shader" === mode ?
@@ -2303,9 +2297,7 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
2303
2297
  // Create cache key from size and variant
2304
2298
  const cacheKey = `${glassSize.width}x${glassSize.height}-${shaderVariant}`, cachedUrl = (key => {
2305
2299
  const entry = sharedShaderCache.get(key);
2306
- return entry ? (
2307
- // Update access timestamp for LRU
2308
- entry.timestamp = Date.now(), entry.url) : null;
2300
+ return entry ? (entry.timestamp = Date.now(), entry.url) : null;
2309
2301
  })(cacheKey);
2310
2302
  // Check shared cache first
2311
2303
  if (cachedUrl) return void setShaderMapUrl(cachedUrl);
@@ -2316,29 +2308,24 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
2316
2308
  if (shaderUtilsRef.current) try {
2317
2309
  const {ShaderDisplacementGenerator: ShaderDisplacementGenerator, fragmentShaders: fragmentShaders} = shaderUtilsRef.current;
2318
2310
  shaderGeneratorRef.current?.destroy();
2319
- const selectedShader = fragmentShaders[shaderVariant] || fragmentShaders.liquidGlass;
2311
+ const selectedShader = fragmentShaders[shaderVariant] ?? fragmentShaders.liquidGlass;
2320
2312
  shaderGeneratorRef.current = new ShaderDisplacementGenerator({
2321
2313
  width: glassSize.width,
2322
2314
  height: glassSize.height,
2323
2315
  fragment: selectedShader
2324
2316
  }), shaderUpdateTimeoutRef.current = setTimeout((() => {
2325
- const url = shaderGeneratorRef.current?.updateShader() || "";
2317
+ const url = shaderGeneratorRef.current?.updateShader() ?? "";
2326
2318
  url && ((key, url) => {
2327
- // Evict oldest entries if at capacity
2328
2319
  if (sharedShaderCache.size >= 15) {
2329
2320
  const entries = Array.from(sharedShaderCache.entries());
2330
- // Sort by timestamp (oldest first)
2331
- entries.sort(((a, b) => a[1].timestamp - b[1].timestamp));
2332
- // Remove oldest entry
2333
- const oldestEntry = entries[0];
2334
- oldestEntry && sharedShaderCache.delete(oldestEntry[0]);
2321
+ entries.sort(((a, b) => a[1].timestamp - b[1].timestamp));
2322
+ const oldest = entries[0];
2323
+ oldest && sharedShaderCache.delete(oldest[0]);
2335
2324
  }
2336
2325
  sharedShaderCache.set(key, {
2337
2326
  url: url,
2338
2327
  timestamp: Date.now()
2339
- }),
2340
- // Development mode: log cache size
2341
- "undefined" != typeof process && "production" === process.env?.NODE_ENV || sharedShaderCache.size;
2328
+ }), "undefined" != typeof process && "production" === process.env?.NODE_ENV || sharedShaderCache.size;
2342
2329
  })(cacheKey, url), setShaderMapUrl(url);
2343
2330
  }), 100);
2344
2331
  } catch (error) {
@@ -2407,7 +2394,6 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
2407
2394
  console.warn("AtomixGlassContainer: Error getting element bounds", error), setRectCache(null);
2408
2395
  }
2409
2396
  }), [ ref, glassSize ]);
2410
- // Pre-calculate static multipliers outside useMemo
2411
2397
  const liquidBlur = React.useMemo((() => {
2412
2398
  const defaultBlur = {
2413
2399
  baseBlur: blurAmount,
@@ -2476,7 +2462,6 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
2476
2462
  }), [ borderRadius, backdropStyle, mouseOffset, overLight, effectiveWithoutEffects, overLightConfig ]);
2477
2463
  return jsxRuntime.jsx("div", {
2478
2464
  ref: el => {
2479
- // Apply force no-transition
2480
2465
  // Handle forwarded ref
2481
2466
  "function" == typeof ref ? ref(el) : ref && (ref.current = el);
2482
2467
  },
@@ -2518,6 +2503,7 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
2518
2503
  });
2519
2504
  }));
2520
2505
 
2506
+ // ─── Blur multiplier constants (module-level, never change at runtime) ────────
2521
2507
  AtomixGlassContainer.displayName = "AtomixGlassContainer";
2522
2508
 
2523
2509
  // Singleton instance
@@ -2718,8 +2704,6 @@ class {
2718
2704
  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})`;
2719
2705
  // Container variables
2720
2706
  const style = containerElement.style;
2721
- style.setProperty("--atomix-glass-container-width", isFixedOrSticky ? `${glassSize.width}` : "100%"),
2722
- style.setProperty("--atomix-glass-container-height", isFixedOrSticky ? `${glassSize.height}` : "100%"),
2723
2707
  style.setProperty("--atomix-glass-container-padding", padding), style.setProperty("--atomix-glass-container-radius", `${effectiveBorderRadius}px`),
2724
2708
  style.setProperty("--atomix-glass-container-backdrop", backdropFilterString),
2725
2709
  // Shadows
@@ -3180,47 +3164,55 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
3180
3164
  return "undefined" == typeof process || process.env, finalConfig;
3181
3165
  }
3182
3166
  return "undefined" == typeof process || process.env, baseConfig;
3183
- }), [ overLight, getEffectiveOverLight, isHovered, isActive, validateConfigValue, debugOverLight ]), transformStyle = React.useMemo((() => effectiveWithoutEffects || isActive && Boolean(onClick) ? "scale(0.98)" : "scale(1)"), [ effectiveWithoutEffects, isActive, onClick ]), updateRectRef = React.useRef(null), handleGlobalMousePosition = React.useCallback((globalPos => {
3184
- if (externalGlobalMousePosition && externalMouseOffset) return;
3185
- if (effectiveWithoutEffects) return;
3186
- const container = mouseContainer?.current || glassRef.current;
3187
- if (!container) return;
3188
- // Use cached rect if available, otherwise get new one
3189
- let rect = cachedRectRef.current;
3190
- if (rect && 0 !== rect.width && 0 !== rect.height || (rect = container.getBoundingClientRect(),
3191
- cachedRectRef.current = rect), 0 === rect.width || 0 === rect.height) return;
3192
- const center = calculateElementCenter(rect);
3193
- // Write raw target — the lerp loop will smoothly pursue it
3194
- targetMouseOffsetRef.current = {
3195
- x: (globalPos.x - center.x) / rect.width * 100,
3196
- y: (globalPos.y - center.y) / rect.height * 100
3197
- }, targetGlobalMousePositionRef.current = globalPos;
3198
- }), [ mouseContainer, glassRef, externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects ]), startLerpLoop = React.useCallback((() => {
3167
+ }), [ overLight, getEffectiveOverLight, isHovered, isActive, validateConfigValue, debugOverLight ]), transformStyle = React.useMemo((() => effectiveWithoutEffects || isActive && Boolean(onClick) ? "scale(0.98)" : "scale(1)"), [ effectiveWithoutEffects, isActive, onClick ]), updateRectRef = React.useRef(null), stopLerpLoop = React.useCallback((() => {
3168
+ lerpActiveRef.current = !1, null !== lerpRafRef.current && (cancelAnimationFrame(lerpRafRef.current),
3169
+ lerpRafRef.current = null);
3170
+ }), []), startLerpLoop = React.useCallback((() => {
3199
3171
  if (lerpActiveRef.current) return;
3200
3172
  lerpActiveRef.current = !0;
3201
3173
  const LERP_T = CONSTANTS.LERP_FACTOR, tick = () => {
3202
3174
  if (!lerpActiveRef.current) return;
3203
- // Add ref validity check to prevent memory leaks
3204
- if (!glassRef.current || !wrapperRef?.current) return void (lerpActiveRef.current = !1);
3175
+ if (!glassRef.current) return void (lerpActiveRef.current = !1);
3205
3176
  const cur = internalMouseOffsetRef.current, tgt = targetMouseOffsetRef.current, dx = tgt.x - cur.x, dy = tgt.y - cur.y;
3206
3177
  // If we're close enough, snap and park
3207
- if (Math.abs(dx) < .05 && Math.abs(dy) < .05) internalMouseOffsetRef.current = {
3178
+ if (Math.abs(dx) < .01 && Math.abs(dy) < .01) return internalMouseOffsetRef.current = {
3208
3179
  ...tgt
3209
3180
  }, internalGlobalMousePositionRef.current = {
3210
3181
  ...targetGlobalMousePositionRef.current
3211
- }; else {
3212
- internalMouseOffsetRef.current = {
3213
- x: lerp$1(cur.x, tgt.x, LERP_T),
3214
- y: lerp$1(cur.y, tgt.y, LERP_T)
3215
- };
3216
- const curG = internalGlobalMousePositionRef.current, tgtG = targetGlobalMousePositionRef.current;
3217
- internalGlobalMousePositionRef.current = {
3218
- x: lerp$1(curG.x, tgtG.x, LERP_T),
3219
- y: lerp$1(curG.y, tgtG.y, LERP_T)
3220
- };
3221
- }
3222
- // Imperative style update with the smoothed values
3223
- updateAtomixGlassStyles(wrapperRef.current, glassRef.current, {
3182
+ },
3183
+ // Final update and stop
3184
+ updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
3185
+ mouseOffset: internalMouseOffsetRef.current,
3186
+ globalMousePosition: internalGlobalMousePositionRef.current,
3187
+ glassSize: glassSize,
3188
+ isHovered: isHovered,
3189
+ isActive: isActive,
3190
+ isOverLight: overLightConfig.isOverLight,
3191
+ baseOverLightConfig: overLightConfig,
3192
+ effectiveBorderRadius: effectiveBorderRadius,
3193
+ effectiveWithoutEffects: effectiveWithoutEffects,
3194
+ effectiveReducedMotion: effectiveReducedMotion,
3195
+ elasticity: elasticity,
3196
+ directionalScale: isActive && Boolean(onClick) ? "scale(0.96)" : "scale(1)",
3197
+ onClick: onClick,
3198
+ withLiquidBlur: withLiquidBlur,
3199
+ blurAmount: blurAmount,
3200
+ saturation: saturation,
3201
+ padding: padding,
3202
+ isFixedOrSticky: isFixedOrSticky
3203
+ }), void stopLerpLoop();
3204
+ // Smooth step
3205
+ internalMouseOffsetRef.current = {
3206
+ x: lerp$1(cur.x, tgt.x, LERP_T),
3207
+ y: lerp$1(cur.y, tgt.y, LERP_T)
3208
+ };
3209
+ const curG = internalGlobalMousePositionRef.current, tgtG = targetGlobalMousePositionRef.current;
3210
+ internalGlobalMousePositionRef.current = {
3211
+ x: lerp$1(curG.x, tgtG.x, LERP_T),
3212
+ y: lerp$1(curG.y, tgtG.y, LERP_T)
3213
+ },
3214
+ // Imperative style update
3215
+ updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
3224
3216
  mouseOffset: internalMouseOffsetRef.current,
3225
3217
  globalMousePosition: internalGlobalMousePositionRef.current,
3226
3218
  glassSize: glassSize,
@@ -3243,10 +3235,24 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
3243
3235
  };
3244
3236
  // 0.08 – lower = more viscous
3245
3237
  lerpRafRef.current = requestAnimationFrame(tick);
3246
- }), [ glassRef, wrapperRef, glassSize, isHovered, isActive, overLightConfig, effectiveBorderRadius, effectiveWithoutEffects, effectiveReducedMotion, elasticity, onClick, withLiquidBlur, blurAmount, saturation, padding, isFixedOrSticky ]), stopLerpLoop = React.useCallback((() => {
3247
- lerpActiveRef.current = !1, null !== lerpRafRef.current && (cancelAnimationFrame(lerpRafRef.current),
3248
- lerpRafRef.current = null);
3249
- }), []);
3238
+ }), [ glassRef, wrapperRef, glassSize, isHovered, isActive, overLightConfig, effectiveBorderRadius, effectiveWithoutEffects, effectiveReducedMotion, elasticity, onClick, withLiquidBlur, blurAmount, saturation, padding, isFixedOrSticky, stopLerpLoop ]), handleGlobalMousePosition = React.useCallback((globalPos => {
3239
+ if (externalGlobalMousePosition && externalMouseOffset) return;
3240
+ if (effectiveWithoutEffects) return;
3241
+ const container = mouseContainer?.current || glassRef.current;
3242
+ if (!container) return;
3243
+ // Use cached rect if available, otherwise get new one
3244
+ let rect = cachedRectRef.current;
3245
+ if (rect && 0 !== rect.width && 0 !== rect.height || (rect = container.getBoundingClientRect(),
3246
+ cachedRectRef.current = rect), 0 === rect.width || 0 === rect.height) return;
3247
+ const center = calculateElementCenter(rect);
3248
+ // Write raw target — the lerp loop will smoothly pursue it
3249
+ targetMouseOffsetRef.current = {
3250
+ x: (globalPos.x - center.x) / rect.width * 100,
3251
+ y: (globalPos.y - center.y) / rect.height * 100
3252
+ }, targetGlobalMousePositionRef.current = globalPos,
3253
+ // Ensure the lerp loop is running to smoothly chase the new target
3254
+ lerpActiveRef.current || startLerpLoop();
3255
+ }), [ mouseContainer, glassRef, externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects, startLerpLoop ]);
3250
3256
  /**
3251
3257
  * Validate and clamp a numeric config value
3252
3258
  */
@@ -3255,7 +3261,7 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
3255
3261
  if (externalGlobalMousePosition && externalMouseOffset) return;
3256
3262
  if (effectiveWithoutEffects) return;
3257
3263
  const unsubscribe = globalMouseTracker.subscribe(handleGlobalMousePosition);
3258
- // Start the lerp loop — it will smoothly chase the target
3264
+ // Initial start
3259
3265
  startLerpLoop();
3260
3266
  const container = mouseContainer?.current || glassRef.current;
3261
3267
  let resizeObserver = null;
@@ -3268,7 +3274,7 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
3268
3274
  unsubscribe(), stopLerpLoop(), null !== updateRectRef.current && (cancelAnimationFrame(updateRectRef.current),
3269
3275
  updateRectRef.current = null), resizeObserver && resizeObserver.disconnect();
3270
3276
  };
3271
- }), [ handleGlobalMousePosition, startLerpLoop, stopLerpLoop, mouseContainer, glassRef, externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects ]),
3277
+ }), [ externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects, handleGlobalMousePosition, startLerpLoop, stopLerpLoop, mouseContainer, glassRef ]),
3272
3278
  // Also call updateStyles on other state changes (hover, active, etc)
3273
3279
  React.useEffect((() => {
3274
3280
  updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
@@ -3763,183 +3769,146 @@ function usePerformanceMonitor(config = {}) {
3763
3769
  }
3764
3770
  }
3765
3771
 
3772
+ /** Map an FPS value to a semantic color token string. */ const getQualityColor = quality => {
3773
+ switch (quality) {
3774
+ case "high":
3775
+ return "var(--atomix-color-success, #4ade80)";
3776
+
3777
+ case "medium":
3778
+ return "var(--atomix-color-warning, #fbbf24)";
3779
+
3780
+ case "low":
3781
+ return "var(--atomix-color-danger, #ef4444)";
3782
+
3783
+ default:
3784
+ return "#9ca3af";
3785
+ }
3786
+ }, getFpsLabel = fps => fps >= 58 ? "Optimal" : fps >= 45 ? "Warning" : "Critical";
3787
+
3788
+ /** Map a quality level string to a semantic color token string. */
3789
+ // Inject keyframes once
3790
+ if ("undefined" != typeof document) {
3791
+ const styleId = "perf-dashboard-keyframes";
3792
+ if (!document.getElementById(styleId)) {
3793
+ const styleEl = document.createElement("style");
3794
+ styleEl.id = styleId, styleEl.textContent = "\n@keyframes perf-dashboard-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n}\n",
3795
+ document.head.appendChild(styleEl);
3796
+ }
3797
+ }
3798
+
3766
3799
  /**
3767
- * PerformanceDashboard - Real-time performance monitoring overlay
3800
+ * PerformanceDashboard - Real-time performance monitoring overlay.
3768
3801
  *
3769
- * Displays:
3770
- * - Current FPS with color coding
3771
- * - Frame time statistics
3772
- * - Quality level indicator
3773
- * - GPU memory usage (if available)
3774
- * - Auto-scaling status
3775
- */ const PerformanceDashboard = ({metrics: metrics, isVisible: isVisible = !0, onClose: onClose}) => {
3776
- // Get color for FPS display
3777
- const getFpsColor = fps => fps >= 58 ? "#4ade80" : // Green - good
3778
- fps >= 45 ? "#fbbf24" : "#ef4444" // Red - critical
3779
- , dashboardStyle = React.useMemo((() => ({
3780
- position: "fixed",
3781
- top: "16px",
3782
- right: "16px",
3783
- padding: "12px 16px",
3784
- backgroundColor: "rgba(17, 24, 39, 0.95)",
3785
- borderRadius: "8px",
3786
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
3787
- fontFamily: "monospace",
3788
- fontSize: "12px",
3789
- color: "#fff",
3790
- zIndex: 9999,
3791
- minWidth: "200px",
3792
- backdropFilter: "blur(8px)",
3793
- border: "1px solid rgba(255, 255, 255, 0.1)",
3794
- transition: "opacity 0.3s ease",
3795
- opacity: isVisible ? 1 : 0,
3796
- pointerEvents: isVisible ? "auto" : "none"
3797
- })), [ isVisible ]), headerStyle = React.useMemo((() => ({
3798
- display: "flex",
3799
- justifyContent: "space-between",
3800
- alignItems: "center",
3801
- marginBottom: "8px",
3802
- paddingBottom: "8px",
3803
- borderBottom: "1px solid rgba(255, 255, 255, 0.1)"
3804
- })), []), titleStyle = React.useMemo((() => ({
3805
- fontWeight: "bold",
3806
- fontSize: "13px",
3807
- color: "#fff"
3808
- })), []), closeButtonStyle = React.useMemo((() => ({
3809
- background: "transparent",
3810
- border: "none",
3811
- color: "#9ca3af",
3812
- cursor: "pointer",
3813
- fontSize: "16px",
3814
- padding: "0",
3815
- lineHeight: "1"
3816
- })), []), metricRowStyle = React.useMemo((() => ({
3817
- display: "flex",
3818
- justifyContent: "space-between",
3819
- alignItems: "center",
3820
- marginBottom: "6px"
3821
- })), []), labelStyle = React.useMemo((() => ({
3822
- color: "#9ca3af",
3823
- marginRight: "12px"
3824
- })), []), valueStyle = React.useMemo((() => ({
3825
- fontWeight: "bold"
3826
- })), []);
3827
- // Get quality level badge color
3828
- return isVisible ? jsxRuntime.jsxs("div", {
3829
- style: dashboardStyle,
3802
+ * Displays FPS, frame time, quality level, GPU memory, and auto-scaling status.
3803
+ * Rendered only when `debugPerformance={true}` on the parent `AtomixGlass`.
3804
+ */ const PerformanceDashboard = React.memo((({metrics: metrics, isVisible: isVisible = !0, onClose: onClose}) => {
3805
+ if (!isVisible) return null;
3806
+ const fpsColor = (fps = metrics.fps) >= 58 ? "var(--atomix-color-success, #4ade80)" : fps >= 45 ? "var(--atomix-color-warning, #fbbf24)" : "var(--atomix-color-danger, #ef4444)";
3807
+ var fps;
3808
+ const isCritical = metrics.fps < 45;
3809
+ return jsxRuntime.jsxs("div", {
3810
+ 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",
3811
+ style: {
3812
+ zIndex: 9999,
3813
+ minWidth: "12.5rem",
3814
+ // 200px
3815
+ backgroundColor: "rgba(17, 24, 39, 0.95)",
3816
+ backdropFilter: "blur(8px)",
3817
+ transition: "opacity 0.3s ease"
3818
+ },
3830
3819
  children: [ jsxRuntime.jsxs("div", {
3831
- style: headerStyle,
3820
+ className: "u-flex u-items-center u-justify-between u-mb-2 u-pb-2 u-border-b u-border-white-alpha-10",
3832
3821
  children: [ jsxRuntime.jsx("span", {
3833
- style: titleStyle,
3822
+ className: "u-text-sm u-font-bold u-text-white",
3834
3823
  children: "Performance Monitor"
3835
3824
  }), onClose && jsxRuntime.jsx("button", {
3836
- style: closeButtonStyle,
3825
+ 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",
3837
3826
  onClick: onClose,
3838
3827
  "aria-label": "Close performance dashboard",
3828
+ style: {
3829
+ transition: "color 0.2s ease"
3830
+ },
3839
3831
  children: "×"
3840
3832
  }) ]
3841
3833
  }), jsxRuntime.jsxs("div", {
3842
- style: metricRowStyle,
3834
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
3843
3835
  children: [ jsxRuntime.jsx("span", {
3844
- style: labelStyle,
3836
+ className: "u-text-gray-400 u-me-3",
3845
3837
  children: "FPS"
3846
3838
  }), jsxRuntime.jsx("span", {
3839
+ className: "u-font-bold",
3847
3840
  style: {
3848
- ...valueStyle,
3849
- color: getFpsColor(metrics.fps)
3841
+ color: fpsColor
3850
3842
  },
3851
3843
  children: Math.round(metrics.fps)
3852
3844
  }) ]
3853
3845
  }), jsxRuntime.jsxs("div", {
3854
- style: metricRowStyle,
3846
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
3855
3847
  children: [ jsxRuntime.jsx("span", {
3856
- style: labelStyle,
3848
+ className: "u-text-gray-400 u-me-3",
3857
3849
  children: "Frame Time"
3858
3850
  }), jsxRuntime.jsxs("span", {
3859
- style: valueStyle,
3851
+ className: "u-font-bold",
3860
3852
  children: [ metrics.frameTime.toFixed(2), "ms" ]
3861
3853
  }) ]
3862
3854
  }), jsxRuntime.jsxs("div", {
3863
- style: metricRowStyle,
3855
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
3864
3856
  children: [ jsxRuntime.jsx("span", {
3865
- style: labelStyle,
3857
+ className: "u-text-gray-400 u-me-3",
3866
3858
  children: "Quality"
3867
3859
  }), jsxRuntime.jsx("span", {
3860
+ className: "u-font-bold u-text-uppercase",
3868
3861
  style: {
3869
- ...valueStyle,
3870
- color: (quality => {
3871
- switch (quality) {
3872
- case "high":
3873
- return "#4ade80";
3874
-
3875
- case "medium":
3876
- return "#fbbf24";
3877
-
3878
- case "low":
3879
- return "#ef4444";
3880
-
3881
- default:
3882
- return "#9ca3af";
3883
- }
3884
- })(metrics.qualityLevel),
3885
- textTransform: "uppercase",
3886
- fontSize: "11px"
3862
+ fontSize: "0.6875rem",
3863
+ // 11px
3864
+ color: getQualityColor(metrics.qualityLevel)
3887
3865
  },
3888
3866
  children: metrics.qualityLevel
3889
3867
  }) ]
3890
3868
  }), metrics.gpuMemory && jsxRuntime.jsxs("div", {
3891
- style: metricRowStyle,
3869
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
3892
3870
  children: [ jsxRuntime.jsx("span", {
3893
- style: labelStyle,
3871
+ className: "u-text-gray-400 u-me-3",
3894
3872
  children: "GPU Memory"
3895
3873
  }), jsxRuntime.jsxs("span", {
3896
- style: valueStyle,
3874
+ className: "u-font-bold",
3897
3875
  children: [ "~", Math.round(metrics.gpuMemory / 1024), "MB" ]
3898
3876
  }) ]
3899
3877
  }), metrics.isAutoScaling && jsxRuntime.jsx("div", {
3878
+ className: "u-mt-2 u-pt-2 u-border-t u-border-white-alpha-10 u-text-center",
3900
3879
  style: {
3901
- marginTop: "8px",
3902
- paddingTop: "8px",
3903
- borderTop: "1px solid rgba(255, 255, 255, 0.1)",
3904
- fontSize: "10px",
3905
- color: "#6b7280",
3906
- textAlign: "center"
3880
+ fontSize: "0.625rem",
3881
+ // 10px
3882
+ color: "#6b7280"
3907
3883
  },
3908
3884
  children: "Auto-scaling active"
3909
3885
  }), jsxRuntime.jsxs("div", {
3910
- style: {
3911
- marginTop: "8px",
3912
- paddingTop: "8px",
3913
- borderTop: "1px solid rgba(255, 255, 255, 0.1)",
3914
- display: "flex",
3915
- alignItems: "center",
3916
- gap: "6px"
3917
- },
3886
+ className: "u-flex u-items-center u-gap-2 u-mt-2 u-pt-2 u-border-t u-border-white-alpha-10",
3918
3887
  children: [ jsxRuntime.jsx("div", {
3888
+ className: "u-rounded-full",
3919
3889
  style: {
3920
- width: "8px",
3921
- height: "8px",
3922
- borderRadius: "50%",
3923
- backgroundColor: getFpsColor(metrics.fps),
3924
- animation: metrics.fps < 45 ? "pulse 1s infinite" : "none"
3890
+ width: "0.5rem",
3891
+ height: "0.5rem",
3892
+ flexShrink: 0,
3893
+ backgroundColor: fpsColor,
3894
+ ...isCritical && {
3895
+ animation: "perf-dashboard-pulse 1s infinite"
3896
+ }
3925
3897
  }
3926
3898
  }), jsxRuntime.jsx("span", {
3899
+ className: "u-text-xs",
3927
3900
  style: {
3928
- fontSize: "10px",
3929
- color: metrics.fps >= 58 ? "#4ade80" : metrics.fps >= 45 ? "#fbbf24" : "#ef4444"
3901
+ fontSize: "0.625rem",
3902
+ // 10px
3903
+ color: fpsColor
3930
3904
  },
3931
- children: metrics.fps >= 58 ? "Optimal" : metrics.fps >= 45 ? "Warning" : "Critical"
3905
+ children: getFpsLabel(metrics.fps)
3932
3906
  }) ]
3933
3907
  }) ]
3934
- }) : null;
3935
- };
3908
+ });
3909
+ }));
3936
3910
 
3937
- // Add pulse animation for critical FPS
3938
- if ("undefined" != typeof document) {
3939
- const styleSheet = document.createElement("style");
3940
- styleSheet.textContent = "\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n ",
3941
- document.head.appendChild(styleSheet);
3942
- }
3911
+ PerformanceDashboard.displayName = "PerformanceDashboard";
3943
3912
 
3944
3913
  /**
3945
3914
  * Mobile optimization presets
@@ -3950,7 +3919,8 @@ if ("undefined" != typeof document) {
3950
3919
  /**
3951
3920
  * Performance preset - Maximum FPS, reduced quality
3952
3921
  * Best for low-end devices or when battery saving is priority
3953
- */ const PERFORMANCE_PRESET = {
3922
+ */
3923
+ const PERFORMANCE_PRESET = {
3954
3924
  distortionOctaves: 2,
3955
3925
  // Minimal FBM layers
3956
3926
  displacementScale: 50,
@@ -4113,90 +4083,21 @@ function getDevicePreset(presetName) {
4113
4083
  getPixelRatio: () => "undefined" == typeof window ? 1 : window.devicePixelRatio || 1,
4114
4084
  /** Check if device has touch support */
4115
4085
  hasTouchSupport: () => "undefined" != typeof window && ("ontouchstart" in window || navigator.maxTouchPoints > 0)
4116
- };
4117
-
4118
- /**
4119
- * AtomixGlass - A high-performance glass morphism component with liquid distortion effects
4120
- *
4121
- * Features:
4122
- * - Hardware-accelerated glass effects with SVG filters
4123
- * - Mouse-responsive liquid distortion
4124
- * - Dynamic border-radius extraction from children CSS properties
4125
- * - Automatic light/dark theme detection via overLight prop
4126
- * - Accessibility and performance optimizations
4127
- * - Multiple displacement modes (standard, polar, prominent, shader)
4128
- * - Design token integration for consistent theming
4129
- * - Focus ring support for keyboard navigation
4130
- * - Responsive breakpoints for mobile optimization
4131
- * - Enhanced ARIA attributes for screen readers
4132
- * - Time-based animation system with FBM distortion
4133
- * - Device preset optimization for performance/quality balance
4134
- *
4135
- * Design System Compliance:
4136
- * - Uses design tokens for opacity, spacing, and colors
4137
- * - Follows BEM methodology for class naming
4138
- * - Implements focus-ring mixin for accessibility
4139
- * - Supports reduced motion and high contrast preferences
4140
- *
4141
- * @example
4142
- * // Basic usage with dynamic border-radius extraction
4143
- * <AtomixGlass>
4144
- * <div style={{ borderRadius: '12px' }}>Content with 12px radius</div>
4145
- * </AtomixGlass>
4146
- *
4147
- * @example
4148
- * // Manual border-radius override
4149
- * <AtomixGlass borderRadius={20}>
4150
- * <div>Content with 20px glass radius</div>
4151
- * </AtomixGlass>
4152
- *
4153
- * @example
4154
- * // Interactive glass with click handler
4155
- * <AtomixGlass onClick={() => console.log('Clicked')} aria-label="Glass card">
4156
- * <div>Clickable content</div>
4157
- * </AtomixGlass>
4158
- *
4159
- * @example
4160
- * // OverLight - Boolean mode (explicit control)
4161
- * <AtomixGlass overLight={true}>
4162
- * <div>Content on light background</div>
4163
- * </AtomixGlass>
4164
- *
4165
- * @example
4166
- * // OverLight - Auto-detection mode
4167
- * <AtomixGlass overLight="auto">
4168
- * <div>Content with auto-detected background</div>
4169
- * </AtomixGlass>
4170
- *
4171
- * @example
4172
- * // OverLight - Object config with custom settings
4173
- * <AtomixGlass
4174
- * overLight={{
4175
- * threshold: 0.8,
4176
- * opacity: 0.6,
4177
- * contrast: 1.8,
4178
- * brightness: 1.0,
4179
- * saturationBoost: 1.5
4180
- * }}
4181
- * >
4182
- * <div>Content with custom overLight config</div>
4183
- * </AtomixGlass>
4184
- *
4185
- * @example
4186
- * // Debug mode for overLight detection
4187
- * <AtomixGlass overLight="auto" debugOverLight={true}>
4188
- * <div>Content with debug logging enabled</div>
4189
- * </AtomixGlass>
4190
- *
4191
- * @example
4192
- * // Performance-optimized for mobile devices
4193
- * <AtomixGlass devicePreset="performance" disableResponsiveBreakpoints={false}>
4194
- * <div>Mobile-optimized glass effect</div>
4195
- * </AtomixGlass>
4196
- */ 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}) {
4197
- const glassRef = React.useRef(null), contentRef = React.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({
4086
+ }, AtomixGlassInner = React.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) {
4087
+ const glassRef = React.useRef(null), contentRef = React.useRef(null), internalWrapperRef = React.useRef(null), mergedRef = React.useMemo((() =>
4088
+ // Helper to merge refs
4089
+ function(...refs) {
4090
+ return node => {
4091
+ refs.forEach((ref => {
4092
+ "function" == typeof ref ? ref(node) : null != ref && (ref.current = node);
4093
+ }));
4094
+ };
4095
+ }
4096
+ // Internal implementation with forwardRef
4097
+ (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({
4198
4098
  glassRef: glassRef,
4199
4099
  contentRef: contentRef,
4100
+ wrapperRef: internalWrapperRef,
4200
4101
  borderRadius: borderRadius,
4201
4102
  globalMousePosition: externalGlobalMousePosition,
4202
4103
  mouseOffset: externalMouseOffset,
@@ -4225,7 +4126,6 @@ function getDevicePreset(presetName) {
4225
4126
  distortionGain: distortionGain,
4226
4127
  distortionQuality: distortionQuality
4227
4128
  });
4228
- // Re-calculate only when devicePreset changes
4229
4129
  // Responsive breakpoint system - automatically adjusts parameters based on viewport
4230
4130
  useResponsiveGlass({
4231
4131
  baseParams: {
@@ -4240,22 +4140,21 @@ function getDevicePreset(presetName) {
4240
4140
  },
4241
4141
  breakpoints: MOBILE_OPTIMIZED_BREAKPOINTS,
4242
4142
  enabled: !disableResponsiveBreakpoints && "undefined" != typeof window,
4243
- // Enable unless disabled
4244
4143
  debug: !1
4245
4144
  });
4246
4145
  // Performance monitoring - tracks FPS, frame time, memory usage
4247
- const {metrics: performanceMetrics, recommendedQuality: recommendedQuality, isUnderperforming: isUnderperforming, setQualityLevel: setQualityLevel, toggleMonitoring: toggleMonitoring} = usePerformanceMonitor({
4248
- enabled: !1,
4249
- // We'll toggle manually based on prop
4146
+ const {metrics: performanceMetrics, toggleMonitoring: toggleMonitoring} = usePerformanceMonitor({
4147
+ enabled: debugPerformance,
4148
+ // Enable when debugPerformance is true
4250
4149
  debug: !1,
4251
4150
  showOverlay: !1
4252
4151
  });
4253
- // Auto-start performance monitoring if enabled (only in development)
4152
+ // Auto-start performance monitoring when debugPerformance is enabled
4254
4153
  React__default.default.useEffect((() => {
4255
- "development" === process.env.NODE_ENV && window?.enablePerformanceMonitoring && toggleMonitoring();
4154
+ debugPerformance && toggleMonitoring();
4256
4155
  // eslint-disable-next-line react-hooks/exhaustive-deps
4257
- }), []);
4258
- // Only run once on mount
4156
+ }), [ debugPerformance ]);
4157
+ // Re-run when debugPerformance changes
4259
4158
  const isOverLight = React.useMemo((() => overLightConfig.isOverLight), [ overLightConfig.isOverLight ]), shouldRenderOverLightLayers = withOverLightLayers && isOverLight, rootLayoutStyle = React.useMemo((() => {
4260
4159
  if (!isFixedOrSticky) return {};
4261
4160
  const {position: p, top: t, left: l, right: r, bottom: b} = restStyle;
@@ -4283,34 +4182,30 @@ function getDevicePreset(presetName) {
4283
4182
  if (isFixedOrSticky) {
4284
4183
  const {position: _p, top: _t, left: _l, right: _r, bottom: _b, ...visualStyle} = restStyle;
4285
4184
  return {
4286
- ...visualStyle,
4287
- ...!effectiveWithoutEffects && {
4288
- transform: transformStyle
4289
- }
4185
+ ...visualStyle
4290
4186
  };
4291
4187
  }
4292
4188
  return {
4293
- ...restStyle,
4294
- ...!effectiveWithoutEffects && {
4295
- transform: transformStyle
4296
- }
4189
+ ...restStyle
4297
4190
  };
4298
- }), [ isFixedOrSticky, restStyle, effectiveWithoutEffects, transformStyle ]);
4191
+ }), [ isFixedOrSticky, restStyle ]);
4299
4192
  // Build className with state modifiers
4300
4193
  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 = React.useMemo((() => ({
4301
4194
  position: isFixedOrSticky ? "absolute" : restStyle.position || "absolute",
4302
- top: isFixedOrSticky ? 0 : restStyle.top || 0,
4303
- left: isFixedOrSticky ? 0 : restStyle.left || 0
4304
- })), [ isFixedOrSticky, restStyle.position, restStyle.top, restStyle.left ]), adjustedSize = React.useMemo((() => {
4195
+ top: isFixedOrSticky ? restStyle.top ?? 0 : 0,
4196
+ left: isFixedOrSticky ? restStyle.left ?? 0 : 0,
4197
+ right: isFixedOrSticky ? restStyle.right ?? "auto" : "auto",
4198
+ bottom: isFixedOrSticky ? restStyle.bottom ?? "auto" : "auto"
4199
+ })), [ isFixedOrSticky, restStyle.position, restStyle.top, restStyle.left, restStyle.right, restStyle.bottom ]), adjustedSize = React.useMemo((() => {
4305
4200
  // Keep a reference to positionStyles to avoid unused-variable lint,
4306
4201
  // but sizing is driven by explicit width/height or measured size.
4307
4202
  positionStyles.position;
4308
- 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;
4203
+ 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;
4309
4204
  return {
4310
4205
  width: resolveLength(effectiveWidth, glassSize.width),
4311
4206
  height: resolveLength(effectiveHeight, glassSize.height)
4312
4207
  };
4313
- }), [ width, height, restStyle.width, restStyle.height, positionStyles.position, glassSize.width, glassSize.height ]), gradientValues = React.useMemo((() => {
4208
+ }), [ width, height, restStyle.width, restStyle.height, positionStyles.position, glassSize.width, glassSize.height, isFixedOrSticky ]), gradientValues = React.useMemo((() => {
4314
4209
  const mx = mouseOffset.x, my = mouseOffset.y, absMx = Math.abs(mx), absMy = Math.abs(my), GRADIENT = ATOMIX_GLASS.CONSTANTS.GRADIENT;
4315
4210
  return {
4316
4211
  borderGradientAngle: GRADIENT.BASE_ANGLE + mx * GRADIENT.ANGLE_MULTIPLIER,
@@ -4340,33 +4235,32 @@ function getDevicePreset(presetName) {
4340
4235
  absMx: absMx,
4341
4236
  absMy: absMy
4342
4237
  };
4343
- }), [ mouseOffset.x, mouseOffset.y ]), opacityValues = React.useMemo((() => {
4344
- const overLightOpacity = overLightConfig.opacity;
4345
- return {
4346
- hover1: isHovered || isActive ? .5 : 0,
4347
- hover2: isActive ? .5 : 0,
4348
- hover3: isHovered ? .4 : isActive ? .8 : 0,
4349
- base: isOverLight ? overLightOpacity || .4 : 0,
4350
- over: isOverLight ? 1.1 * (overLightOpacity || .4) : 0
4351
- };
4352
- }), [ isHovered, isActive, isOverLight, overLightConfig.opacity ]), glassVars = React.useMemo((() => {
4353
- 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;
4238
+ }), [ 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 = React.useMemo((() => ({
4239
+ hover1: isHovered || isActive ? .5 : 0,
4240
+ hover2: isActive ? .5 : 0,
4241
+ hover3: isHovered ? .4 : isActive ? .8 : 0,
4242
+ base: isOverLight ? clampedOverLightOpacity || .4 : 0,
4243
+ over: isOverLight ? 1.1 * (clampedOverLightOpacity || .4) : 0
4244
+ })), [ isHovered, isActive, isOverLight, clampedOverLightOpacity ]), glassVars = React.useMemo((() => {
4245
+ 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;
4354
4246
  return {
4355
4247
  ...void 0 !== customZIndex && {
4356
4248
  "--atomix-glass-base-z-index": customZIndex
4357
4249
  },
4358
4250
  "--atomix-glass-radius": `${effectiveBorderRadius}px`,
4359
4251
  "--atomix-glass-transform": transformStyle || "none",
4360
- // Internal decorative layers are positioned relative to the root;
4361
- "--atomix-glass-position": rootLayoutStyle.position,
4362
- "--atomix-glass-top": `${isFixedOrSticky ? rootLayoutStyle.top : 0}px`,
4363
- "--atomix-glass-left": `${isFixedOrSticky ? rootLayoutStyle.left : 0}px`,
4252
+ "--atomix-glass-container-position": `${isFixedOrSticky ? rootLayoutStyle.position : positionStyles.position}`,
4253
+ "--atomix-glass-position": `${isFixedOrSticky ? rootLayoutStyle.position : positionStyles.position}`,
4254
+ "--atomix-glass-top": `${isFixedOrSticky ? restStyle.top ?? 0 : 0}px`,
4255
+ "--atomix-glass-left": `${isFixedOrSticky ? restStyle.left ?? 0 : 0}px`,
4256
+ "--atomix-glass-right": isFixedOrSticky ? restStyle.right ?? "auto" : "auto",
4257
+ "--atomix-glass-bottom": isFixedOrSticky ? restStyle.bottom ?? "auto" : "auto",
4364
4258
  "--atomix-glass-width": adjustedSize.width,
4365
4259
  "--atomix-glass-height": adjustedSize.height,
4366
- "--atomix-glass-border-width": "var(--atomix-spacing-0-5, 0.09375rem)",
4260
+ "--atomix-glass-border-width": "var(--atomix-spacing-0-5, 0.125rem)",
4367
4261
  "--atomix-glass-blend-mode": isOverLight ? "multiply" : "overlay",
4368
- "--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%)`,
4369
- "--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%)`,
4262
+ "--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%)`,
4263
+ "--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%)`,
4370
4264
  "--atomix-glass-hover-1-opacity": opacityValues.hover1,
4371
4265
  "--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}%)`,
4372
4266
  "--atomix-glass-hover-2-opacity": opacityValues.hover2,
@@ -4380,13 +4274,14 @@ function getDevicePreset(presetName) {
4380
4274
  "--atomix-glass-overlay-highlight-opacity": opacityValues.over * ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.OPACITY_MULTIPLIER,
4381
4275
  "--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}%)`
4382
4276
  };
4383
- }), [ gradientValues, opacityValues, effectiveBorderRadius, transformStyle, adjustedSize, isOverLight, overLightConfig.borderOpacity, customZIndex, rootLayoutStyle, isFixedOrSticky ]), renderBackgroundLayer = layerType => jsxRuntime.jsx("div", {
4277
+ }), [ gradientValues, opacityValues, effectiveBorderRadius, transformStyle, adjustedSize, isOverLight, clampedBorderOpacity, customZIndex, isFixedOrSticky, positionStyles.position, rootLayoutStyle.position, restStyle.top, restStyle.left, restStyle.right, restStyle.bottom ]), renderBackgroundLayer = layerType => jsxRuntime.jsx("div", {
4384
4278
  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(" ")
4385
4279
  });
4386
4280
  // Calculate position and size styles for internal layers
4387
4281
  // When root is fixed/sticky, internal layers use absolute (relative to root)
4388
4282
  return jsxRuntime.jsxs("div", {
4389
4283
  ...rest,
4284
+ ref: mergedRef,
4390
4285
  className: componentClassName,
4391
4286
  style: {
4392
4287
  ...glassVars
@@ -4396,17 +4291,14 @@ function getDevicePreset(presetName) {
4396
4291
  "aria-label": ariaLabel,
4397
4292
  "aria-describedby": ariaDescribedBy,
4398
4293
  "aria-disabled": !(!onClick || !effectiveWithoutEffects) || !onClick && void 0,
4399
- "aria-pressed": !(!onClick || !isActive) || !onClick && void 0,
4294
+ "aria-pressed": void 0,
4400
4295
  onKeyDown: onClick ? handleKeyDown : void 0,
4401
4296
  children: [ jsxRuntime.jsx(AtomixGlassContainer, {
4402
4297
  ref: glassRef,
4403
4298
  contentRef: contentRef,
4404
4299
  className: className,
4405
4300
  style: {
4406
- ...restStyle,
4407
- ...!isFixedOrSticky && {
4408
- position: "relative"
4409
- }
4301
+ ...restStyle
4410
4302
  },
4411
4303
  borderRadius: effectiveBorderRadius,
4412
4304
  displacementScale: effectiveWithoutEffects ? 0 : "shader" === mode ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_DISPLACEMENT : isOverLight ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.OVER_LIGHT_DISPLACEMENT : displacementScale,
@@ -4442,6 +4334,7 @@ function getDevicePreset(presetName) {
4442
4334
  effectiveReducedMotion: effectiveReducedMotion,
4443
4335
  shaderVariant: shaderVariant,
4444
4336
  withLiquidBlur: withLiquidBlur,
4337
+ isFixedOrSticky: isFixedOrSticky,
4445
4338
  // Phase 1: Animation System props
4446
4339
  shaderTime: getShaderTime(),
4447
4340
  withTimeAnimation: withTimeAnimation,
@@ -4470,6 +4363,8 @@ function getDevicePreset(presetName) {
4470
4363
  }) ]
4471
4364
  }), withBorder && jsxRuntime.jsxs(jsxRuntime.Fragment, {
4472
4365
  children: [ jsxRuntime.jsx("span", {
4366
+ className: ATOMIX_GLASS.BORDER_BACKDROP_CLASS
4367
+ }), jsxRuntime.jsx("span", {
4473
4368
  className: ATOMIX_GLASS.BORDER_1_CLASS
4474
4369
  }), jsxRuntime.jsx("span", {
4475
4370
  className: ATOMIX_GLASS.BORDER_2_CLASS
@@ -4480,10 +4375,15 @@ function getDevicePreset(presetName) {
4480
4375
  onClose: () => {}
4481
4376
  }) ]
4482
4377
  });
4483
- }
4378
+ }));
4484
4379
 
4485
- // Default icon
4486
- const DefaultIcon = () => jsxRuntime.jsx("i", {
4380
+ AtomixGlassInner.displayName = "AtomixGlass";
4381
+
4382
+ /**
4383
+ * AtomixGlass - wrapped with React.memo to prevent unnecessary re-renders.
4384
+ * Ref is forwarded to the root `<div>` element.
4385
+ */
4386
+ const AtomixGlass = React.memo(AtomixGlassInner), DefaultIcon = () => jsxRuntime.jsx("i", {
4487
4387
  className: "c-accordion__icon",
4488
4388
  "aria-hidden": "true",
4489
4389
  children: jsxRuntime.jsx("svg", {
@@ -4517,6 +4417,7 @@ const DefaultIcon = () => jsxRuntime.jsx("i", {
4517
4417
  });
4518
4418
  }));
4519
4419
 
4420
+ // Default icon
4520
4421
  AccordionHeader.displayName = "AccordionHeader";
4521
4422
 
4522
4423
  const AccordionBody = React.forwardRef((({children: children, className: className = "", panelRef: panelRef, contentRef: contentRef, ...props}, ref) => {