@shohojdhara/atomix 0.5.0 → 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 (76) 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/stories/AnimationFeatures.stories.tsx +52 -46
  30. package/src/components/AtomixGlass/stories/Examples.stories.tsx +573 -236
  31. package/src/components/AtomixGlass/stories/Playground.stories.tsx +88 -41
  32. package/src/components/AtomixGlass/stories/argTypes.ts +19 -19
  33. package/src/components/AtomixGlass/stories/shared-components.tsx +7 -12
  34. package/src/components/AtomixGlass/stories/types.ts +3 -3
  35. package/src/lib/composables/useAtomixGlass.ts +108 -71
  36. package/src/lib/composables/useAtomixGlassStyles.ts +0 -2
  37. package/src/lib/constants/components.ts +1 -0
  38. package/src/lib/types/components.ts +1 -0
  39. package/src/lib/utils/displacement-generator.ts +1 -1
  40. package/src/styles/06-components/_components.atomix-glass.scss +158 -97
  41. package/scripts/cli/__tests__/README.md +0 -81
  42. package/scripts/cli/__tests__/basic.test.js +0 -116
  43. package/scripts/cli/__tests__/clean.test.js +0 -278
  44. package/scripts/cli/__tests__/component-generator.test.js +0 -332
  45. package/scripts/cli/__tests__/component-validator.test.js +0 -433
  46. package/scripts/cli/__tests__/generator.test.js +0 -613
  47. package/scripts/cli/__tests__/glass-motion.test.js +0 -256
  48. package/scripts/cli/__tests__/integration.test.js +0 -938
  49. package/scripts/cli/__tests__/migrate.test.js +0 -74
  50. package/scripts/cli/__tests__/security.test.js +0 -206
  51. package/scripts/cli/__tests__/test-setup.js +0 -135
  52. package/scripts/cli/__tests__/theme-bridge.test.js +0 -507
  53. package/scripts/cli/__tests__/token-manager.test.js +0 -251
  54. package/scripts/cli/__tests__/token-provider.test.js +0 -361
  55. package/scripts/cli/__tests__/utils.test.js +0 -165
  56. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +0 -216
  57. package/src/components/AtomixGlass/stories/AnimationTests.stories.tsx +0 -95
  58. package/src/components/AtomixGlass/stories/CardExamples.stories.tsx +0 -212
  59. package/src/components/AtomixGlass/stories/Customization.stories.tsx +0 -131
  60. package/src/components/AtomixGlass/stories/DashboardExamples.stories.tsx +0 -348
  61. package/src/components/AtomixGlass/stories/EcommerceExamples.stories.tsx +0 -410
  62. package/src/components/AtomixGlass/stories/FormExamples.stories.tsx +0 -436
  63. package/src/components/AtomixGlass/stories/HeroExamples.stories.tsx +0 -264
  64. package/src/components/AtomixGlass/stories/InteractivePlayground.stories.tsx +0 -247
  65. package/src/components/AtomixGlass/stories/MobileUIExamples.stories.tsx +0 -418
  66. package/src/components/AtomixGlass/stories/ModalExamples.stories.tsx +0 -402
  67. package/src/components/AtomixGlass/stories/Modes.stories.tsx +0 -1082
  68. package/src/components/AtomixGlass/stories/Overview.stories.tsx +0 -497
  69. package/src/components/AtomixGlass/stories/Performance.stories.tsx +0 -103
  70. package/src/components/AtomixGlass/stories/PresetGallery.stories.tsx +0 -335
  71. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +0 -395
  72. package/src/components/AtomixGlass/stories/WidgetExamples.stories.tsx +0 -441
  73. package/src/components/TypedButton/TypedButton.stories.tsx +0 -59
  74. package/src/components/TypedButton/TypedButton.tsx +0 -39
  75. package/src/components/TypedButton/index.ts +0 -2
  76. package/src/lib/composables/useTypedButton.ts +0 -66
package/dist/charts.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
 
3
- import React, { useState, useRef, useEffect, useCallback, useMemo, memo, forwardRef, createContext, useContext } from "react";
3
+ import React, { useState, useRef, useEffect, useCallback, useMemo, memo, forwardRef, useId, createContext, useContext } from "react";
4
4
 
5
5
  import * as PhosphorIcons from "@phosphor-icons/react";
6
6
 
@@ -470,6 +470,7 @@ const _reduceInstanceProperty = getDefaultExportFromCjs((function(it) {
470
470
  FILTER_OVERLAY_CLASS: "c-atomix-glass__filter-overlay",
471
471
  FILTER_SHADOW_CLASS: "c-atomix-glass__filter-shadow",
472
472
  CONTENT_CLASS: "c-atomix-glass__content",
473
+ BORDER_BACKDROP_CLASS: "c-atomix-glass__border-backdrop",
473
474
  BORDER_1_CLASS: "c-atomix-glass__border-1",
474
475
  BORDER_2_CLASS: "c-atomix-glass__border-2",
475
476
  HOVER_1_CLASS: "c-atomix-glass__hover-1",
@@ -1669,7 +1670,7 @@ const {CONSTANTS: CONSTANTS$2} = ATOMIX_GLASS, calculateDistance = (pos1, pos2)
1669
1670
  default:
1670
1671
  return console.warn("AtomixGlass: Invalid displacement mode"), displacementMap;
1671
1672
  }
1672
- }, GlassFilterComponent = ({id: id, displacementScale: displacementScale, aberrationIntensity: aberrationIntensity, mode: mode, shaderMapUrl: shaderMapUrl, blurAmount: blurAmount}) => jsx("svg", {
1673
+ }, sharedShaderCache = new Map, GlassFilterComponent = ({id: id, displacementScale: displacementScale, aberrationIntensity: aberrationIntensity, mode: mode, shaderMapUrl: shaderMapUrl, blurAmount: blurAmount}) => jsx("svg", {
1673
1674
  style: {
1674
1675
  position: "absolute",
1675
1676
  width: "100%",
@@ -1810,24 +1811,17 @@ const {CONSTANTS: CONSTANTS$2} = ATOMIX_GLASS, calculateDistance = (pos1, pos2)
1810
1811
  */ GlassFilterComponent.displayName = "GlassFilter";
1811
1812
 
1812
1813
  // Memoize component to prevent unnecessary re-renders
1813
- 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));
1814
-
1815
- // Module-level counter for deterministic ID generation
1816
- let idCounter = 0;
1817
-
1818
- // Module-level shared shader cache with LRU eviction
1819
- 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 = {
1814
+ 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 = {
1820
1815
  x: 0,
1821
1816
  y: 0
1822
1817
  }, 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 = {
1823
1818
  width: 0,
1824
1819
  height: 0
1825
- }, onClick: onClick, mode: mode = "standard", effectiveWithoutEffects: effectiveWithoutEffects = !1, effectiveReducedMotion: effectiveReducedMotion = !1, shaderVariant: shaderVariant = "liquidGlass", withLiquidBlur: withLiquidBlur = !1,
1820
+ }, onClick: onClick, mode: mode = "standard", effectiveWithoutEffects: effectiveWithoutEffects = !1, effectiveReducedMotion: effectiveReducedMotion = !1, shaderVariant: shaderVariant = "liquidGlass", withLiquidBlur: withLiquidBlur = !1, isFixedOrSticky: isFixedOrSticky = !1,
1826
1821
  // Phase 1: Animation System props
1827
1822
  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) => {
1828
- // Generate a stable, deterministic ID for SSR compatibility
1829
- // Use a module-level counter that's consistent across server and client
1830
- 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);
1823
+ // React 18 useId — stable, unique, and SSR-safe (no module-level counter)
1824
+ 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);
1831
1825
  // Lazy load shader utilities only when shader mode is needed
1832
1826
  useEffect((() => {
1833
1827
  "shader" === mode ?
@@ -1850,9 +1844,7 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
1850
1844
  // Create cache key from size and variant
1851
1845
  const cacheKey = `${glassSize.width}x${glassSize.height}-${shaderVariant}`, cachedUrl = (key => {
1852
1846
  const entry = sharedShaderCache.get(key);
1853
- return entry ? (
1854
- // Update access timestamp for LRU
1855
- entry.timestamp = Date.now(), entry.url) : null;
1847
+ return entry ? (entry.timestamp = Date.now(), entry.url) : null;
1856
1848
  })(cacheKey);
1857
1849
  // Check shared cache first
1858
1850
  if (cachedUrl) return void setShaderMapUrl(cachedUrl);
@@ -1863,29 +1855,24 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
1863
1855
  if (shaderUtilsRef.current) try {
1864
1856
  const {ShaderDisplacementGenerator: ShaderDisplacementGenerator, fragmentShaders: fragmentShaders} = shaderUtilsRef.current;
1865
1857
  shaderGeneratorRef.current?.destroy();
1866
- const selectedShader = fragmentShaders[shaderVariant] || fragmentShaders.liquidGlass;
1858
+ const selectedShader = fragmentShaders[shaderVariant] ?? fragmentShaders.liquidGlass;
1867
1859
  shaderGeneratorRef.current = new ShaderDisplacementGenerator({
1868
1860
  width: glassSize.width,
1869
1861
  height: glassSize.height,
1870
1862
  fragment: selectedShader
1871
1863
  }), shaderUpdateTimeoutRef.current = setTimeout((() => {
1872
- const url = shaderGeneratorRef.current?.updateShader() || "";
1864
+ const url = shaderGeneratorRef.current?.updateShader() ?? "";
1873
1865
  url && ((key, url) => {
1874
- // Evict oldest entries if at capacity
1875
1866
  if (sharedShaderCache.size >= 15) {
1876
1867
  const entries = Array.from(sharedShaderCache.entries());
1877
- // Sort by timestamp (oldest first)
1878
- entries.sort(((a, b) => a[1].timestamp - b[1].timestamp));
1879
- // Remove oldest entry
1880
- const oldestEntry = entries[0];
1881
- oldestEntry && sharedShaderCache.delete(oldestEntry[0]);
1868
+ entries.sort(((a, b) => a[1].timestamp - b[1].timestamp));
1869
+ const oldest = entries[0];
1870
+ oldest && sharedShaderCache.delete(oldest[0]);
1882
1871
  }
1883
1872
  sharedShaderCache.set(key, {
1884
1873
  url: url,
1885
1874
  timestamp: Date.now()
1886
- }),
1887
- // Development mode: log cache size
1888
- "undefined" != typeof process && "production" === process.env?.NODE_ENV || sharedShaderCache.size;
1875
+ }), "undefined" != typeof process && "production" === process.env?.NODE_ENV || sharedShaderCache.size;
1889
1876
  })(cacheKey, url), setShaderMapUrl(url);
1890
1877
  }), 100);
1891
1878
  } catch (error) {
@@ -1954,7 +1941,6 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
1954
1941
  console.warn("AtomixGlassContainer: Error getting element bounds", error), setRectCache(null);
1955
1942
  }
1956
1943
  }), [ ref, glassSize ]);
1957
- // Pre-calculate static multipliers outside useMemo
1958
1944
  const liquidBlur = useMemo((() => {
1959
1945
  const defaultBlur = {
1960
1946
  baseBlur: blurAmount,
@@ -2023,7 +2009,6 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
2023
2009
  }), [ borderRadius, backdropStyle, mouseOffset, overLight, effectiveWithoutEffects, overLightConfig ]);
2024
2010
  return jsx("div", {
2025
2011
  ref: el => {
2026
- // Apply force no-transition
2027
2012
  // Handle forwarded ref
2028
2013
  "function" == typeof ref ? ref(el) : ref && (ref.current = el);
2029
2014
  },
@@ -2065,6 +2050,7 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
2065
2050
  });
2066
2051
  }));
2067
2052
 
2053
+ // ─── Blur multiplier constants (module-level, never change at runtime) ────────
2068
2054
  AtomixGlassContainer.displayName = "AtomixGlassContainer";
2069
2055
 
2070
2056
  // Singleton instance
@@ -2265,8 +2251,6 @@ class {
2265
2251
  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})`;
2266
2252
  // Container variables
2267
2253
  const style = containerElement.style;
2268
- style.setProperty("--atomix-glass-container-width", isFixedOrSticky ? `${glassSize.width}` : "100%"),
2269
- style.setProperty("--atomix-glass-container-height", isFixedOrSticky ? `${glassSize.height}` : "100%"),
2270
2254
  style.setProperty("--atomix-glass-container-padding", padding), style.setProperty("--atomix-glass-container-radius", `${effectiveBorderRadius}px`),
2271
2255
  style.setProperty("--atomix-glass-container-backdrop", backdropFilterString),
2272
2256
  // Shadows
@@ -2727,47 +2711,55 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
2727
2711
  return "undefined" == typeof process || process.env, finalConfig;
2728
2712
  }
2729
2713
  return "undefined" == typeof process || process.env, baseConfig;
2730
- }), [ 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 => {
2731
- if (externalGlobalMousePosition && externalMouseOffset) return;
2732
- if (effectiveWithoutEffects) return;
2733
- const container = mouseContainer?.current || glassRef.current;
2734
- if (!container) return;
2735
- // Use cached rect if available, otherwise get new one
2736
- let rect = cachedRectRef.current;
2737
- if (rect && 0 !== rect.width && 0 !== rect.height || (rect = container.getBoundingClientRect(),
2738
- cachedRectRef.current = rect), 0 === rect.width || 0 === rect.height) return;
2739
- const center = calculateElementCenter(rect);
2740
- // Write raw target — the lerp loop will smoothly pursue it
2741
- targetMouseOffsetRef.current = {
2742
- x: (globalPos.x - center.x) / rect.width * 100,
2743
- y: (globalPos.y - center.y) / rect.height * 100
2744
- }, targetGlobalMousePositionRef.current = globalPos;
2745
- }), [ mouseContainer, glassRef, externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects ]), startLerpLoop = useCallback((() => {
2714
+ }), [ 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((() => {
2715
+ lerpActiveRef.current = !1, null !== lerpRafRef.current && (cancelAnimationFrame(lerpRafRef.current),
2716
+ lerpRafRef.current = null);
2717
+ }), []), startLerpLoop = useCallback((() => {
2746
2718
  if (lerpActiveRef.current) return;
2747
2719
  lerpActiveRef.current = !0;
2748
2720
  const LERP_T = CONSTANTS.LERP_FACTOR, tick = () => {
2749
2721
  if (!lerpActiveRef.current) return;
2750
- // Add ref validity check to prevent memory leaks
2751
- if (!glassRef.current || !wrapperRef?.current) return void (lerpActiveRef.current = !1);
2722
+ if (!glassRef.current) return void (lerpActiveRef.current = !1);
2752
2723
  const cur = internalMouseOffsetRef.current, tgt = targetMouseOffsetRef.current, dx = tgt.x - cur.x, dy = tgt.y - cur.y;
2753
2724
  // If we're close enough, snap and park
2754
- if (Math.abs(dx) < .05 && Math.abs(dy) < .05) internalMouseOffsetRef.current = {
2725
+ if (Math.abs(dx) < .01 && Math.abs(dy) < .01) return internalMouseOffsetRef.current = {
2755
2726
  ...tgt
2756
2727
  }, internalGlobalMousePositionRef.current = {
2757
2728
  ...targetGlobalMousePositionRef.current
2758
- }; else {
2759
- internalMouseOffsetRef.current = {
2760
- x: lerp$1(cur.x, tgt.x, LERP_T),
2761
- y: lerp$1(cur.y, tgt.y, LERP_T)
2762
- };
2763
- const curG = internalGlobalMousePositionRef.current, tgtG = targetGlobalMousePositionRef.current;
2764
- internalGlobalMousePositionRef.current = {
2765
- x: lerp$1(curG.x, tgtG.x, LERP_T),
2766
- y: lerp$1(curG.y, tgtG.y, LERP_T)
2767
- };
2768
- }
2769
- // Imperative style update with the smoothed values
2770
- updateAtomixGlassStyles(wrapperRef.current, glassRef.current, {
2729
+ },
2730
+ // Final update and stop
2731
+ updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
2732
+ mouseOffset: internalMouseOffsetRef.current,
2733
+ globalMousePosition: internalGlobalMousePositionRef.current,
2734
+ glassSize: glassSize,
2735
+ isHovered: isHovered,
2736
+ isActive: isActive,
2737
+ isOverLight: overLightConfig.isOverLight,
2738
+ baseOverLightConfig: overLightConfig,
2739
+ effectiveBorderRadius: effectiveBorderRadius,
2740
+ effectiveWithoutEffects: effectiveWithoutEffects,
2741
+ effectiveReducedMotion: effectiveReducedMotion,
2742
+ elasticity: elasticity,
2743
+ directionalScale: isActive && Boolean(onClick) ? "scale(0.96)" : "scale(1)",
2744
+ onClick: onClick,
2745
+ withLiquidBlur: withLiquidBlur,
2746
+ blurAmount: blurAmount,
2747
+ saturation: saturation,
2748
+ padding: padding,
2749
+ isFixedOrSticky: isFixedOrSticky
2750
+ }), void stopLerpLoop();
2751
+ // Smooth step
2752
+ internalMouseOffsetRef.current = {
2753
+ x: lerp$1(cur.x, tgt.x, LERP_T),
2754
+ y: lerp$1(cur.y, tgt.y, LERP_T)
2755
+ };
2756
+ const curG = internalGlobalMousePositionRef.current, tgtG = targetGlobalMousePositionRef.current;
2757
+ internalGlobalMousePositionRef.current = {
2758
+ x: lerp$1(curG.x, tgtG.x, LERP_T),
2759
+ y: lerp$1(curG.y, tgtG.y, LERP_T)
2760
+ },
2761
+ // Imperative style update
2762
+ updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
2771
2763
  mouseOffset: internalMouseOffsetRef.current,
2772
2764
  globalMousePosition: internalGlobalMousePositionRef.current,
2773
2765
  glassSize: glassSize,
@@ -2790,10 +2782,24 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
2790
2782
  };
2791
2783
  // 0.08 – lower = more viscous
2792
2784
  lerpRafRef.current = requestAnimationFrame(tick);
2793
- }), [ glassRef, wrapperRef, glassSize, isHovered, isActive, overLightConfig, effectiveBorderRadius, effectiveWithoutEffects, effectiveReducedMotion, elasticity, onClick, withLiquidBlur, blurAmount, saturation, padding, isFixedOrSticky ]), stopLerpLoop = useCallback((() => {
2794
- lerpActiveRef.current = !1, null !== lerpRafRef.current && (cancelAnimationFrame(lerpRafRef.current),
2795
- lerpRafRef.current = null);
2796
- }), []);
2785
+ }), [ glassRef, wrapperRef, glassSize, isHovered, isActive, overLightConfig, effectiveBorderRadius, effectiveWithoutEffects, effectiveReducedMotion, elasticity, onClick, withLiquidBlur, blurAmount, saturation, padding, isFixedOrSticky, stopLerpLoop ]), handleGlobalMousePosition = useCallback((globalPos => {
2786
+ if (externalGlobalMousePosition && externalMouseOffset) return;
2787
+ if (effectiveWithoutEffects) return;
2788
+ const container = mouseContainer?.current || glassRef.current;
2789
+ if (!container) return;
2790
+ // Use cached rect if available, otherwise get new one
2791
+ let rect = cachedRectRef.current;
2792
+ if (rect && 0 !== rect.width && 0 !== rect.height || (rect = container.getBoundingClientRect(),
2793
+ cachedRectRef.current = rect), 0 === rect.width || 0 === rect.height) return;
2794
+ const center = calculateElementCenter(rect);
2795
+ // Write raw target — the lerp loop will smoothly pursue it
2796
+ targetMouseOffsetRef.current = {
2797
+ x: (globalPos.x - center.x) / rect.width * 100,
2798
+ y: (globalPos.y - center.y) / rect.height * 100
2799
+ }, targetGlobalMousePositionRef.current = globalPos,
2800
+ // Ensure the lerp loop is running to smoothly chase the new target
2801
+ lerpActiveRef.current || startLerpLoop();
2802
+ }), [ mouseContainer, glassRef, externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects, startLerpLoop ]);
2797
2803
  /**
2798
2804
  * Validate and clamp a numeric config value
2799
2805
  */
@@ -2802,7 +2808,7 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
2802
2808
  if (externalGlobalMousePosition && externalMouseOffset) return;
2803
2809
  if (effectiveWithoutEffects) return;
2804
2810
  const unsubscribe = globalMouseTracker.subscribe(handleGlobalMousePosition);
2805
- // Start the lerp loop — it will smoothly chase the target
2811
+ // Initial start
2806
2812
  startLerpLoop();
2807
2813
  const container = mouseContainer?.current || glassRef.current;
2808
2814
  let resizeObserver = null;
@@ -2815,7 +2821,7 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
2815
2821
  unsubscribe(), stopLerpLoop(), null !== updateRectRef.current && (cancelAnimationFrame(updateRectRef.current),
2816
2822
  updateRectRef.current = null), resizeObserver && resizeObserver.disconnect();
2817
2823
  };
2818
- }), [ handleGlobalMousePosition, startLerpLoop, stopLerpLoop, mouseContainer, glassRef, externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects ]),
2824
+ }), [ externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects, handleGlobalMousePosition, startLerpLoop, stopLerpLoop, mouseContainer, glassRef ]),
2819
2825
  // Also call updateStyles on other state changes (hover, active, etc)
2820
2826
  useEffect((() => {
2821
2827
  updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
@@ -3007,184 +3013,147 @@ const _includesInstanceProperty = getDefaultExportFromCjs((function(it) {
3007
3013
  /**
3008
3014
  * Get GPU memory info if available (Chrome DevTools only)
3009
3015
  */
3016
+ /** Map an FPS value to a semantic color token string. */
3017
+ const getQualityColor = quality => {
3018
+ switch (quality) {
3019
+ case "high":
3020
+ return "var(--atomix-color-success, #4ade80)";
3021
+
3022
+ case "medium":
3023
+ return "var(--atomix-color-warning, #fbbf24)";
3024
+
3025
+ case "low":
3026
+ return "var(--atomix-color-danger, #ef4444)";
3027
+
3028
+ default:
3029
+ return "#9ca3af";
3030
+ }
3031
+ }, getFpsLabel = fps => fps >= 58 ? "Optimal" : fps >= 45 ? "Warning" : "Critical";
3032
+
3033
+ /** Map a quality level string to a semantic color token string. */
3034
+ // Inject keyframes once
3035
+ if ("undefined" != typeof document) {
3036
+ const styleId = "perf-dashboard-keyframes";
3037
+ if (!document.getElementById(styleId)) {
3038
+ const styleEl = document.createElement("style");
3039
+ styleEl.id = styleId, styleEl.textContent = "\n@keyframes perf-dashboard-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n}\n",
3040
+ document.head.appendChild(styleEl);
3041
+ }
3042
+ }
3043
+
3010
3044
  /**
3011
- * PerformanceDashboard - Real-time performance monitoring overlay
3045
+ * PerformanceDashboard - Real-time performance monitoring overlay.
3012
3046
  *
3013
- * Displays:
3014
- * - Current FPS with color coding
3015
- * - Frame time statistics
3016
- * - Quality level indicator
3017
- * - GPU memory usage (if available)
3018
- * - Auto-scaling status
3019
- */
3020
- const PerformanceDashboard = ({metrics: metrics, isVisible: isVisible = !0, onClose: onClose}) => {
3021
- // Get color for FPS display
3022
- const getFpsColor = fps => fps >= 58 ? "#4ade80" : // Green - good
3023
- fps >= 45 ? "#fbbf24" : "#ef4444" // Red - critical
3024
- , dashboardStyle = useMemo((() => ({
3025
- position: "fixed",
3026
- top: "16px",
3027
- right: "16px",
3028
- padding: "12px 16px",
3029
- backgroundColor: "rgba(17, 24, 39, 0.95)",
3030
- borderRadius: "8px",
3031
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
3032
- fontFamily: "monospace",
3033
- fontSize: "12px",
3034
- color: "#fff",
3035
- zIndex: 9999,
3036
- minWidth: "200px",
3037
- backdropFilter: "blur(8px)",
3038
- border: "1px solid rgba(255, 255, 255, 0.1)",
3039
- transition: "opacity 0.3s ease",
3040
- opacity: isVisible ? 1 : 0,
3041
- pointerEvents: isVisible ? "auto" : "none"
3042
- })), [ isVisible ]), headerStyle = useMemo((() => ({
3043
- display: "flex",
3044
- justifyContent: "space-between",
3045
- alignItems: "center",
3046
- marginBottom: "8px",
3047
- paddingBottom: "8px",
3048
- borderBottom: "1px solid rgba(255, 255, 255, 0.1)"
3049
- })), []), titleStyle = useMemo((() => ({
3050
- fontWeight: "bold",
3051
- fontSize: "13px",
3052
- color: "#fff"
3053
- })), []), closeButtonStyle = useMemo((() => ({
3054
- background: "transparent",
3055
- border: "none",
3056
- color: "#9ca3af",
3057
- cursor: "pointer",
3058
- fontSize: "16px",
3059
- padding: "0",
3060
- lineHeight: "1"
3061
- })), []), metricRowStyle = useMemo((() => ({
3062
- display: "flex",
3063
- justifyContent: "space-between",
3064
- alignItems: "center",
3065
- marginBottom: "6px"
3066
- })), []), labelStyle = useMemo((() => ({
3067
- color: "#9ca3af",
3068
- marginRight: "12px"
3069
- })), []), valueStyle = useMemo((() => ({
3070
- fontWeight: "bold"
3071
- })), []);
3072
- // Get quality level badge color
3073
- return isVisible ? jsxs("div", {
3074
- style: dashboardStyle,
3047
+ * Displays FPS, frame time, quality level, GPU memory, and auto-scaling status.
3048
+ * Rendered only when `debugPerformance={true}` on the parent `AtomixGlass`.
3049
+ */ const PerformanceDashboard = memo((({metrics: metrics, isVisible: isVisible = !0, onClose: onClose}) => {
3050
+ if (!isVisible) return null;
3051
+ const fpsColor = (fps = metrics.fps) >= 58 ? "var(--atomix-color-success, #4ade80)" : fps >= 45 ? "var(--atomix-color-warning, #fbbf24)" : "var(--atomix-color-danger, #ef4444)";
3052
+ var fps;
3053
+ const isCritical = metrics.fps < 45;
3054
+ return jsxs("div", {
3055
+ 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",
3056
+ style: {
3057
+ zIndex: 9999,
3058
+ minWidth: "12.5rem",
3059
+ // 200px
3060
+ backgroundColor: "rgba(17, 24, 39, 0.95)",
3061
+ backdropFilter: "blur(8px)",
3062
+ transition: "opacity 0.3s ease"
3063
+ },
3075
3064
  children: [ jsxs("div", {
3076
- style: headerStyle,
3065
+ className: "u-flex u-items-center u-justify-between u-mb-2 u-pb-2 u-border-b u-border-white-alpha-10",
3077
3066
  children: [ jsx("span", {
3078
- style: titleStyle,
3067
+ className: "u-text-sm u-font-bold u-text-white",
3079
3068
  children: "Performance Monitor"
3080
3069
  }), onClose && jsx("button", {
3081
- style: closeButtonStyle,
3070
+ 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",
3082
3071
  onClick: onClose,
3083
3072
  "aria-label": "Close performance dashboard",
3073
+ style: {
3074
+ transition: "color 0.2s ease"
3075
+ },
3084
3076
  children: "×"
3085
3077
  }) ]
3086
3078
  }), jsxs("div", {
3087
- style: metricRowStyle,
3079
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
3088
3080
  children: [ jsx("span", {
3089
- style: labelStyle,
3081
+ className: "u-text-gray-400 u-me-3",
3090
3082
  children: "FPS"
3091
3083
  }), jsx("span", {
3084
+ className: "u-font-bold",
3092
3085
  style: {
3093
- ...valueStyle,
3094
- color: getFpsColor(metrics.fps)
3086
+ color: fpsColor
3095
3087
  },
3096
3088
  children: Math.round(metrics.fps)
3097
3089
  }) ]
3098
3090
  }), jsxs("div", {
3099
- style: metricRowStyle,
3091
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
3100
3092
  children: [ jsx("span", {
3101
- style: labelStyle,
3093
+ className: "u-text-gray-400 u-me-3",
3102
3094
  children: "Frame Time"
3103
3095
  }), jsxs("span", {
3104
- style: valueStyle,
3096
+ className: "u-font-bold",
3105
3097
  children: [ metrics.frameTime.toFixed(2), "ms" ]
3106
3098
  }) ]
3107
3099
  }), jsxs("div", {
3108
- style: metricRowStyle,
3100
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
3109
3101
  children: [ jsx("span", {
3110
- style: labelStyle,
3102
+ className: "u-text-gray-400 u-me-3",
3111
3103
  children: "Quality"
3112
3104
  }), jsx("span", {
3105
+ className: "u-font-bold u-text-uppercase",
3113
3106
  style: {
3114
- ...valueStyle,
3115
- color: (quality => {
3116
- switch (quality) {
3117
- case "high":
3118
- return "#4ade80";
3119
-
3120
- case "medium":
3121
- return "#fbbf24";
3122
-
3123
- case "low":
3124
- return "#ef4444";
3125
-
3126
- default:
3127
- return "#9ca3af";
3128
- }
3129
- })(metrics.qualityLevel),
3130
- textTransform: "uppercase",
3131
- fontSize: "11px"
3107
+ fontSize: "0.6875rem",
3108
+ // 11px
3109
+ color: getQualityColor(metrics.qualityLevel)
3132
3110
  },
3133
3111
  children: metrics.qualityLevel
3134
3112
  }) ]
3135
3113
  }), metrics.gpuMemory && jsxs("div", {
3136
- style: metricRowStyle,
3114
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
3137
3115
  children: [ jsx("span", {
3138
- style: labelStyle,
3116
+ className: "u-text-gray-400 u-me-3",
3139
3117
  children: "GPU Memory"
3140
3118
  }), jsxs("span", {
3141
- style: valueStyle,
3119
+ className: "u-font-bold",
3142
3120
  children: [ "~", Math.round(metrics.gpuMemory / 1024), "MB" ]
3143
3121
  }) ]
3144
3122
  }), metrics.isAutoScaling && jsx("div", {
3123
+ className: "u-mt-2 u-pt-2 u-border-t u-border-white-alpha-10 u-text-center",
3145
3124
  style: {
3146
- marginTop: "8px",
3147
- paddingTop: "8px",
3148
- borderTop: "1px solid rgba(255, 255, 255, 0.1)",
3149
- fontSize: "10px",
3150
- color: "#6b7280",
3151
- textAlign: "center"
3125
+ fontSize: "0.625rem",
3126
+ // 10px
3127
+ color: "#6b7280"
3152
3128
  },
3153
3129
  children: "Auto-scaling active"
3154
3130
  }), jsxs("div", {
3155
- style: {
3156
- marginTop: "8px",
3157
- paddingTop: "8px",
3158
- borderTop: "1px solid rgba(255, 255, 255, 0.1)",
3159
- display: "flex",
3160
- alignItems: "center",
3161
- gap: "6px"
3162
- },
3131
+ className: "u-flex u-items-center u-gap-2 u-mt-2 u-pt-2 u-border-t u-border-white-alpha-10",
3163
3132
  children: [ jsx("div", {
3133
+ className: "u-rounded-full",
3164
3134
  style: {
3165
- width: "8px",
3166
- height: "8px",
3167
- borderRadius: "50%",
3168
- backgroundColor: getFpsColor(metrics.fps),
3169
- animation: metrics.fps < 45 ? "pulse 1s infinite" : "none"
3135
+ width: "0.5rem",
3136
+ height: "0.5rem",
3137
+ flexShrink: 0,
3138
+ backgroundColor: fpsColor,
3139
+ ...isCritical && {
3140
+ animation: "perf-dashboard-pulse 1s infinite"
3141
+ }
3170
3142
  }
3171
3143
  }), jsx("span", {
3144
+ className: "u-text-xs",
3172
3145
  style: {
3173
- fontSize: "10px",
3174
- color: metrics.fps >= 58 ? "#4ade80" : metrics.fps >= 45 ? "#fbbf24" : "#ef4444"
3146
+ fontSize: "0.625rem",
3147
+ // 10px
3148
+ color: fpsColor
3175
3149
  },
3176
- children: metrics.fps >= 58 ? "Optimal" : metrics.fps >= 45 ? "Warning" : "Critical"
3150
+ children: getFpsLabel(metrics.fps)
3177
3151
  }) ]
3178
3152
  }) ]
3179
- }) : null;
3180
- };
3153
+ });
3154
+ }));
3181
3155
 
3182
- // Add pulse animation for critical FPS
3183
- if ("undefined" != typeof document) {
3184
- const styleSheet = document.createElement("style");
3185
- styleSheet.textContent = "\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n ",
3186
- document.head.appendChild(styleSheet);
3187
- }
3156
+ PerformanceDashboard.displayName = "PerformanceDashboard";
3188
3157
 
3189
3158
  /**
3190
3159
  * Mobile optimization presets
@@ -3195,7 +3164,8 @@ if ("undefined" != typeof document) {
3195
3164
  /**
3196
3165
  * Performance preset - Maximum FPS, reduced quality
3197
3166
  * Best for low-end devices or when battery saving is priority
3198
- */ const PERFORMANCE_PRESET = {
3167
+ */
3168
+ const PERFORMANCE_PRESET = {
3199
3169
  distortionOctaves: 2,
3200
3170
  // Minimal FBM layers
3201
3171
  displacementScale: 50,
@@ -3304,95 +3274,21 @@ if ("undefined" != typeof document) {
3304
3274
  saturation: 70
3305
3275
  }
3306
3276
  }
3307
- };
3308
-
3309
- /**
3310
- * Balanced preset - Good quality with reasonable performance
3311
- * Default preset for most mobile devices
3312
- */
3313
- /**
3314
- * AtomixGlass - A high-performance glass morphism component with liquid distortion effects
3315
- *
3316
- * Features:
3317
- * - Hardware-accelerated glass effects with SVG filters
3318
- * - Mouse-responsive liquid distortion
3319
- * - Dynamic border-radius extraction from children CSS properties
3320
- * - Automatic light/dark theme detection via overLight prop
3321
- * - Accessibility and performance optimizations
3322
- * - Multiple displacement modes (standard, polar, prominent, shader)
3323
- * - Design token integration for consistent theming
3324
- * - Focus ring support for keyboard navigation
3325
- * - Responsive breakpoints for mobile optimization
3326
- * - Enhanced ARIA attributes for screen readers
3327
- * - Time-based animation system with FBM distortion
3328
- * - Device preset optimization for performance/quality balance
3329
- *
3330
- * Design System Compliance:
3331
- * - Uses design tokens for opacity, spacing, and colors
3332
- * - Follows BEM methodology for class naming
3333
- * - Implements focus-ring mixin for accessibility
3334
- * - Supports reduced motion and high contrast preferences
3335
- *
3336
- * @example
3337
- * // Basic usage with dynamic border-radius extraction
3338
- * <AtomixGlass>
3339
- * <div style={{ borderRadius: '12px' }}>Content with 12px radius</div>
3340
- * </AtomixGlass>
3341
- *
3342
- * @example
3343
- * // Manual border-radius override
3344
- * <AtomixGlass borderRadius={20}>
3345
- * <div>Content with 20px glass radius</div>
3346
- * </AtomixGlass>
3347
- *
3348
- * @example
3349
- * // Interactive glass with click handler
3350
- * <AtomixGlass onClick={() => console.log('Clicked')} aria-label="Glass card">
3351
- * <div>Clickable content</div>
3352
- * </AtomixGlass>
3353
- *
3354
- * @example
3355
- * // OverLight - Boolean mode (explicit control)
3356
- * <AtomixGlass overLight={true}>
3357
- * <div>Content on light background</div>
3358
- * </AtomixGlass>
3359
- *
3360
- * @example
3361
- * // OverLight - Auto-detection mode
3362
- * <AtomixGlass overLight="auto">
3363
- * <div>Content with auto-detected background</div>
3364
- * </AtomixGlass>
3365
- *
3366
- * @example
3367
- * // OverLight - Object config with custom settings
3368
- * <AtomixGlass
3369
- * overLight={{
3370
- * threshold: 0.8,
3371
- * opacity: 0.6,
3372
- * contrast: 1.8,
3373
- * brightness: 1.0,
3374
- * saturationBoost: 1.5
3375
- * }}
3376
- * >
3377
- * <div>Content with custom overLight config</div>
3378
- * </AtomixGlass>
3379
- *
3380
- * @example
3381
- * // Debug mode for overLight detection
3382
- * <AtomixGlass overLight="auto" debugOverLight={true}>
3383
- * <div>Content with debug logging enabled</div>
3384
- * </AtomixGlass>
3385
- *
3386
- * @example
3387
- * // Performance-optimized for mobile devices
3388
- * <AtomixGlass devicePreset="performance" disableResponsiveBreakpoints={false}>
3389
- * <div>Mobile-optimized glass effect</div>
3390
- * </AtomixGlass>
3391
- */
3392
- 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}) {
3393
- 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({
3277
+ }, 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) {
3278
+ const glassRef = useRef(null), contentRef = useRef(null), internalWrapperRef = useRef(null), mergedRef = useMemo((() =>
3279
+ // Helper to merge refs
3280
+ function(...refs) {
3281
+ return node => {
3282
+ refs.forEach((ref => {
3283
+ "function" == typeof ref ? ref(node) : null != ref && (ref.current = node);
3284
+ }));
3285
+ };
3286
+ }
3287
+ // Internal implementation with forwardRef
3288
+ (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({
3394
3289
  glassRef: glassRef,
3395
3290
  contentRef: contentRef,
3291
+ wrapperRef: internalWrapperRef,
3396
3292
  borderRadius: borderRadius,
3397
3293
  globalMousePosition: externalGlobalMousePosition,
3398
3294
  mouseOffset: externalMouseOffset,
@@ -3421,7 +3317,6 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
3421
3317
  distortionGain: distortionGain,
3422
3318
  distortionQuality: distortionQuality
3423
3319
  });
3424
- // Re-calculate only when devicePreset changes
3425
3320
  // Responsive breakpoint system - automatically adjusts parameters based on viewport
3426
3321
  !
3427
3322
  /**
@@ -3611,11 +3506,10 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
3611
3506
  },
3612
3507
  breakpoints: MOBILE_OPTIMIZED_BREAKPOINTS,
3613
3508
  enabled: !disableResponsiveBreakpoints && "undefined" != typeof window,
3614
- // Enable unless disabled
3615
3509
  debug: !1
3616
3510
  });
3617
3511
  // Performance monitoring - tracks FPS, frame time, memory usage
3618
- const {metrics: performanceMetrics, recommendedQuality: recommendedQuality, isUnderperforming: isUnderperforming, setQualityLevel: setQualityLevel, toggleMonitoring: toggleMonitoring} =
3512
+ const {metrics: performanceMetrics, toggleMonitoring: toggleMonitoring} =
3619
3513
  /**
3620
3514
  * Performance Monitor Hook
3621
3515
  *
@@ -3779,17 +3673,17 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
3779
3673
  toggleMonitoring: toggleMonitoring
3780
3674
  };
3781
3675
  }({
3782
- enabled: !1,
3783
- // We'll toggle manually based on prop
3676
+ enabled: debugPerformance,
3677
+ // Enable when debugPerformance is true
3784
3678
  debug: !1,
3785
3679
  showOverlay: !1
3786
3680
  });
3787
- // Auto-start performance monitoring if enabled (only in development)
3681
+ // Auto-start performance monitoring when debugPerformance is enabled
3788
3682
  React.useEffect((() => {
3789
- "development" === process.env.NODE_ENV && window?.enablePerformanceMonitoring && toggleMonitoring();
3683
+ debugPerformance && toggleMonitoring();
3790
3684
  // eslint-disable-next-line react-hooks/exhaustive-deps
3791
- }), []);
3792
- // Only run once on mount
3685
+ }), [ debugPerformance ]);
3686
+ // Re-run when debugPerformance changes
3793
3687
  const isOverLight = useMemo((() => overLightConfig.isOverLight), [ overLightConfig.isOverLight ]), shouldRenderOverLightLayers = withOverLightLayers && isOverLight, rootLayoutStyle = useMemo((() => {
3794
3688
  if (!isFixedOrSticky) return {};
3795
3689
  const {position: p, top: t, left: l, right: r, bottom: b} = restStyle;
@@ -3817,34 +3711,30 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
3817
3711
  if (isFixedOrSticky) {
3818
3712
  const {position: _p, top: _t, left: _l, right: _r, bottom: _b, ...visualStyle} = restStyle;
3819
3713
  return {
3820
- ...visualStyle,
3821
- ...!effectiveWithoutEffects && {
3822
- transform: transformStyle
3823
- }
3714
+ ...visualStyle
3824
3715
  };
3825
3716
  }
3826
3717
  return {
3827
- ...restStyle,
3828
- ...!effectiveWithoutEffects && {
3829
- transform: transformStyle
3830
- }
3718
+ ...restStyle
3831
3719
  };
3832
- }), [ isFixedOrSticky, restStyle, effectiveWithoutEffects, transformStyle ]);
3720
+ }), [ isFixedOrSticky, restStyle ]);
3833
3721
  // Build className with state modifiers
3834
3722
  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((() => ({
3835
3723
  position: isFixedOrSticky ? "absolute" : restStyle.position || "absolute",
3836
- top: isFixedOrSticky ? 0 : restStyle.top || 0,
3837
- left: isFixedOrSticky ? 0 : restStyle.left || 0
3838
- })), [ isFixedOrSticky, restStyle.position, restStyle.top, restStyle.left ]), adjustedSize = useMemo((() => {
3724
+ top: isFixedOrSticky ? restStyle.top ?? 0 : 0,
3725
+ left: isFixedOrSticky ? restStyle.left ?? 0 : 0,
3726
+ right: isFixedOrSticky ? restStyle.right ?? "auto" : "auto",
3727
+ bottom: isFixedOrSticky ? restStyle.bottom ?? "auto" : "auto"
3728
+ })), [ isFixedOrSticky, restStyle.position, restStyle.top, restStyle.left, restStyle.right, restStyle.bottom ]), adjustedSize = useMemo((() => {
3839
3729
  // Keep a reference to positionStyles to avoid unused-variable lint,
3840
3730
  // but sizing is driven by explicit width/height or measured size.
3841
3731
  positionStyles.position;
3842
- 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;
3732
+ 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;
3843
3733
  return {
3844
3734
  width: resolveLength(effectiveWidth, glassSize.width),
3845
3735
  height: resolveLength(effectiveHeight, glassSize.height)
3846
3736
  };
3847
- }), [ width, height, restStyle.width, restStyle.height, positionStyles.position, glassSize.width, glassSize.height ]), gradientValues = useMemo((() => {
3737
+ }), [ width, height, restStyle.width, restStyle.height, positionStyles.position, glassSize.width, glassSize.height, isFixedOrSticky ]), gradientValues = useMemo((() => {
3848
3738
  const mx = mouseOffset.x, my = mouseOffset.y, absMx = Math.abs(mx), absMy = Math.abs(my), GRADIENT = ATOMIX_GLASS.CONSTANTS.GRADIENT;
3849
3739
  return {
3850
3740
  borderGradientAngle: GRADIENT.BASE_ANGLE + mx * GRADIENT.ANGLE_MULTIPLIER,
@@ -3874,33 +3764,32 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
3874
3764
  absMx: absMx,
3875
3765
  absMy: absMy
3876
3766
  };
3877
- }), [ mouseOffset.x, mouseOffset.y ]), opacityValues = useMemo((() => {
3878
- const overLightOpacity = overLightConfig.opacity;
3879
- return {
3880
- hover1: isHovered || isActive ? .5 : 0,
3881
- hover2: isActive ? .5 : 0,
3882
- hover3: isHovered ? .4 : isActive ? .8 : 0,
3883
- base: isOverLight ? overLightOpacity || .4 : 0,
3884
- over: isOverLight ? 1.1 * (overLightOpacity || .4) : 0
3885
- };
3886
- }), [ isHovered, isActive, isOverLight, overLightConfig.opacity ]), glassVars = useMemo((() => {
3887
- 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;
3767
+ }), [ 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((() => ({
3768
+ hover1: isHovered || isActive ? .5 : 0,
3769
+ hover2: isActive ? .5 : 0,
3770
+ hover3: isHovered ? .4 : isActive ? .8 : 0,
3771
+ base: isOverLight ? clampedOverLightOpacity || .4 : 0,
3772
+ over: isOverLight ? 1.1 * (clampedOverLightOpacity || .4) : 0
3773
+ })), [ isHovered, isActive, isOverLight, clampedOverLightOpacity ]), glassVars = useMemo((() => {
3774
+ 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;
3888
3775
  return {
3889
3776
  ...void 0 !== customZIndex && {
3890
3777
  "--atomix-glass-base-z-index": customZIndex
3891
3778
  },
3892
3779
  "--atomix-glass-radius": `${effectiveBorderRadius}px`,
3893
3780
  "--atomix-glass-transform": transformStyle || "none",
3894
- // Internal decorative layers are positioned relative to the root;
3895
- "--atomix-glass-position": rootLayoutStyle.position,
3896
- "--atomix-glass-top": `${isFixedOrSticky ? rootLayoutStyle.top : 0}px`,
3897
- "--atomix-glass-left": `${isFixedOrSticky ? rootLayoutStyle.left : 0}px`,
3781
+ "--atomix-glass-container-position": `${isFixedOrSticky ? rootLayoutStyle.position : positionStyles.position}`,
3782
+ "--atomix-glass-position": `${isFixedOrSticky ? rootLayoutStyle.position : positionStyles.position}`,
3783
+ "--atomix-glass-top": `${isFixedOrSticky ? restStyle.top ?? 0 : 0}px`,
3784
+ "--atomix-glass-left": `${isFixedOrSticky ? restStyle.left ?? 0 : 0}px`,
3785
+ "--atomix-glass-right": isFixedOrSticky ? restStyle.right ?? "auto" : "auto",
3786
+ "--atomix-glass-bottom": isFixedOrSticky ? restStyle.bottom ?? "auto" : "auto",
3898
3787
  "--atomix-glass-width": adjustedSize.width,
3899
3788
  "--atomix-glass-height": adjustedSize.height,
3900
- "--atomix-glass-border-width": "var(--atomix-spacing-0-5, 0.09375rem)",
3789
+ "--atomix-glass-border-width": "var(--atomix-spacing-0-5, 0.125rem)",
3901
3790
  "--atomix-glass-blend-mode": isOverLight ? "multiply" : "overlay",
3902
- "--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%)`,
3903
- "--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%)`,
3791
+ "--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%)`,
3792
+ "--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%)`,
3904
3793
  "--atomix-glass-hover-1-opacity": opacityValues.hover1,
3905
3794
  "--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}%)`,
3906
3795
  "--atomix-glass-hover-2-opacity": opacityValues.hover2,
@@ -3914,13 +3803,14 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
3914
3803
  "--atomix-glass-overlay-highlight-opacity": opacityValues.over * ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.OPACITY_MULTIPLIER,
3915
3804
  "--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}%)`
3916
3805
  };
3917
- }), [ gradientValues, opacityValues, effectiveBorderRadius, transformStyle, adjustedSize, isOverLight, overLightConfig.borderOpacity, customZIndex, rootLayoutStyle, isFixedOrSticky ]), renderBackgroundLayer = layerType => jsx("div", {
3806
+ }), [ 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", {
3918
3807
  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(" ")
3919
3808
  });
3920
3809
  // Calculate position and size styles for internal layers
3921
3810
  // When root is fixed/sticky, internal layers use absolute (relative to root)
3922
3811
  return jsxs("div", {
3923
3812
  ...rest,
3813
+ ref: mergedRef,
3924
3814
  className: componentClassName,
3925
3815
  style: {
3926
3816
  ...glassVars
@@ -3930,17 +3820,14 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
3930
3820
  "aria-label": ariaLabel,
3931
3821
  "aria-describedby": ariaDescribedBy,
3932
3822
  "aria-disabled": !(!onClick || !effectiveWithoutEffects) || !onClick && void 0,
3933
- "aria-pressed": !(!onClick || !isActive) || !onClick && void 0,
3823
+ "aria-pressed": void 0,
3934
3824
  onKeyDown: onClick ? handleKeyDown : void 0,
3935
3825
  children: [ jsx(AtomixGlassContainer, {
3936
3826
  ref: glassRef,
3937
3827
  contentRef: contentRef,
3938
3828
  className: className,
3939
3829
  style: {
3940
- ...restStyle,
3941
- ...!isFixedOrSticky && {
3942
- position: "relative"
3943
- }
3830
+ ...restStyle
3944
3831
  },
3945
3832
  borderRadius: effectiveBorderRadius,
3946
3833
  displacementScale: effectiveWithoutEffects ? 0 : "shader" === mode ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_DISPLACEMENT : isOverLight ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.OVER_LIGHT_DISPLACEMENT : displacementScale,
@@ -3976,6 +3863,7 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
3976
3863
  effectiveReducedMotion: effectiveReducedMotion,
3977
3864
  shaderVariant: shaderVariant,
3978
3865
  withLiquidBlur: withLiquidBlur,
3866
+ isFixedOrSticky: isFixedOrSticky,
3979
3867
  // Phase 1: Animation System props
3980
3868
  shaderTime: getShaderTime(),
3981
3869
  withTimeAnimation: withTimeAnimation,
@@ -4004,6 +3892,8 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
4004
3892
  }) ]
4005
3893
  }), withBorder && jsxs(Fragment, {
4006
3894
  children: [ jsx("span", {
3895
+ className: ATOMIX_GLASS.BORDER_BACKDROP_CLASS
3896
+ }), jsx("span", {
4007
3897
  className: ATOMIX_GLASS.BORDER_1_CLASS
4008
3898
  }), jsx("span", {
4009
3899
  className: ATOMIX_GLASS.BORDER_2_CLASS
@@ -4014,9 +3904,18 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
4014
3904
  onClose: () => {}
4015
3905
  }) ]
4016
3906
  });
4017
- }
3907
+ }));
3908
+
3909
+ /**
3910
+ * Balanced preset - Good quality with reasonable performance
3911
+ * Default preset for most mobile devices
3912
+ */ AtomixGlassInner.displayName = "AtomixGlass";
4018
3913
 
4019
- const ChartContext = createContext(null), Chart = memo( forwardRef((({children: children, type: type = "line", size: size = "md", variant: variant = "primary", title: title, subtitle: subtitle, loading: loading = !1, error: error, className: className = "", "aria-label": ariaLabel, onFullscreen: onFullscreen, onExport: onExport, onRefresh: onRefresh, showToolbar: showToolbar = !1, enableFullscreen: enableFullscreen = !1, enableExport: enableExport = !1, enableRefresh: enableRefresh = !1, exportFormats: exportFormats = [ "png", "svg", "csv" ], datasets: datasets, config: config,
3914
+ /**
3915
+ * AtomixGlass - wrapped with React.memo to prevent unnecessary re-renders.
3916
+ * Ref is forwarded to the root `<div>` element.
3917
+ */
3918
+ const AtomixGlass = memo(AtomixGlassInner), ChartContext = createContext(null), Chart = memo( forwardRef((({children: children, type: type = "line", size: size = "md", variant: variant = "primary", title: title, subtitle: subtitle, loading: loading = !1, error: error, className: className = "", "aria-label": ariaLabel, onFullscreen: onFullscreen, onExport: onExport, onRefresh: onRefresh, showToolbar: showToolbar = !1, enableFullscreen: enableFullscreen = !1, enableExport: enableExport = !1, enableRefresh: enableRefresh = !1, exportFormats: exportFormats = [ "png", "svg", "csv" ], datasets: datasets, config: config,
4020
3919
  // Destructure non-DOM props to prevent passing to DOM element
4021
3920
  toolbarConfig: toolbarConfig, customToolbarActions: customToolbarActions, customToolbarGroups: customToolbarGroups, data: data, showLegend: showLegend, interactive: interactive, fullscreen: fullscreen, onDataPointClick: onDataPointClick, onLegendItemClick: onLegendItemClick, glass: glass, ...props}, ref) => {
4022
3921
  const [isFullscreen, setIsFullscreen] = useState(!1), [isExporting, setIsExporting] = useState(!1), chartContainerRef = useRef(null), [zoomLevel, setZoomLevel] = useState(1), [panOffset, setPanOffset] = useState({