@shohojdhara/atomix 0.5.0 → 0.5.2

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 (168) hide show
  1. package/atomix.config.ts +12 -0
  2. package/build-tools/webpack-loader.js +5 -4
  3. package/dist/atomix.css +230 -83
  4. package/dist/atomix.css.map +1 -1
  5. package/dist/atomix.min.css +1 -1
  6. package/dist/atomix.min.css.map +1 -1
  7. package/dist/build-tools/webpack-loader.js +5 -4
  8. package/dist/charts.d.ts +24 -23
  9. package/dist/charts.js +271 -369
  10. package/dist/charts.js.map +1 -1
  11. package/dist/config.d.ts +624 -0
  12. package/dist/config.js +59 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/core.d.ts +3 -2
  15. package/dist/core.js +342 -382
  16. package/dist/core.js.map +1 -1
  17. package/dist/forms.d.ts +4 -6
  18. package/dist/forms.js +233 -334
  19. package/dist/forms.js.map +1 -1
  20. package/dist/heavy.d.ts +11 -2
  21. package/dist/heavy.js +406 -445
  22. package/dist/heavy.js.map +1 -1
  23. package/dist/index.d.ts +109 -65
  24. package/dist/index.esm.js +654 -748
  25. package/dist/index.esm.js.map +1 -1
  26. package/dist/index.js +621 -717
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.min.js +1 -1
  29. package/dist/index.min.js.map +1 -1
  30. package/dist/layout.js +59 -60
  31. package/dist/layout.js.map +1 -1
  32. package/dist/theme.js +4 -4
  33. package/dist/theme.js.map +1 -1
  34. package/package.json +24 -9
  35. package/scripts/atomix-cli.js +15 -1
  36. package/scripts/cli/__tests__/complexity-utils.test.js +24 -0
  37. package/scripts/cli/__tests__/detector.test.js +50 -0
  38. package/scripts/cli/__tests__/template-engine.test.js +23 -0
  39. package/scripts/cli/__tests__/test-setup.js +1 -133
  40. package/scripts/cli/commands/doctor.js +15 -3
  41. package/scripts/cli/commands/generate.js +113 -51
  42. package/scripts/cli/internal/ai-engine.js +30 -10
  43. package/scripts/cli/internal/complexity-utils.js +60 -0
  44. package/scripts/cli/internal/component-validator.js +49 -16
  45. package/scripts/cli/internal/generator.js +89 -36
  46. package/scripts/cli/internal/hook-generator.js +5 -2
  47. package/scripts/cli/internal/itcss-generator.js +16 -12
  48. package/scripts/cli/templates/next-templates.js +81 -30
  49. package/scripts/cli/templates/storybook-templates.js +12 -2
  50. package/scripts/cli/utils/detector.js +45 -7
  51. package/scripts/cli/utils/diagnostics.js +78 -0
  52. package/scripts/cli/utils/telemetry.js +13 -0
  53. package/src/components/Accordion/Accordion.stories.tsx +4 -0
  54. package/src/components/AtomixGlass/AtomixGlass.tsx +188 -128
  55. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +63 -91
  56. package/src/components/AtomixGlass/PerformanceDashboard.tsx +153 -201
  57. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +9 -6
  58. package/src/components/AtomixGlass/glass-utils.ts +51 -1
  59. package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +52 -46
  60. package/src/components/AtomixGlass/stories/Examples.stories.tsx +573 -236
  61. package/src/components/AtomixGlass/stories/Playground.stories.tsx +88 -41
  62. package/src/components/AtomixGlass/stories/argTypes.ts +19 -19
  63. package/src/components/AtomixGlass/stories/shared-components.tsx +7 -12
  64. package/src/components/AtomixGlass/stories/types.ts +3 -3
  65. package/src/components/Button/Button.tsx +114 -57
  66. package/src/components/Callout/Callout.tsx +4 -4
  67. package/src/components/Chart/ChartRenderer.tsx +1 -1
  68. package/src/components/Chart/DonutChart.tsx +11 -8
  69. package/src/components/EdgePanel/EdgePanel.tsx +119 -115
  70. package/src/components/Form/Select.tsx +4 -4
  71. package/src/components/List/List.tsx +4 -4
  72. package/src/components/Navigation/SideMenu/SideMenu.tsx +6 -6
  73. package/src/components/PhotoViewer/PhotoViewerImage.tsx +1 -1
  74. package/src/components/ProductReview/ProductReview.tsx +4 -2
  75. package/src/components/Rating/Rating.tsx +4 -2
  76. package/src/components/SectionIntro/SectionIntro.tsx +4 -2
  77. package/src/components/Steps/Steps.tsx +1 -1
  78. package/src/components/Tabs/Tabs.tsx +5 -5
  79. package/src/components/Testimonial/Testimonial.tsx +4 -2
  80. package/src/components/VideoPlayer/VideoPlayer.tsx +4 -2
  81. package/src/layouts/CssGrid/CssGrid.stories.tsx +464 -0
  82. package/src/layouts/CssGrid/CssGrid.tsx +215 -0
  83. package/src/layouts/CssGrid/index.ts +8 -0
  84. package/src/layouts/CssGrid/scripts/CssGrid.js +284 -0
  85. package/src/layouts/CssGrid/scripts/index.js +43 -0
  86. package/src/layouts/Grid/scripts/Container.js +139 -0
  87. package/src/layouts/Grid/scripts/Grid.js +184 -0
  88. package/src/layouts/Grid/scripts/GridCol.js +273 -0
  89. package/src/layouts/Grid/scripts/Row.js +154 -0
  90. package/src/layouts/Grid/scripts/index.js +48 -0
  91. package/src/layouts/MasonryGrid/MasonryGrid.tsx +71 -59
  92. package/src/lib/composables/atomix-glass/useGlassSize.ts +1 -1
  93. package/src/lib/composables/useAccordion.ts +5 -5
  94. package/src/lib/composables/useAtomixGlass.ts +111 -74
  95. package/src/lib/composables/useAtomixGlassStyles.ts +0 -2
  96. package/src/lib/composables/useBarChart.ts +2 -2
  97. package/src/lib/composables/useChart.ts +3 -2
  98. package/src/lib/composables/useChartToolbar.ts +48 -66
  99. package/src/lib/composables/useDataTable.ts +1 -1
  100. package/src/lib/composables/useDatePicker.ts +2 -2
  101. package/src/lib/composables/useEdgePanel.ts +45 -54
  102. package/src/lib/composables/useHeroBackgroundSlider.ts +5 -5
  103. package/src/lib/composables/usePhotoViewer.ts +2 -3
  104. package/src/lib/composables/usePieChart.ts +1 -1
  105. package/src/lib/composables/usePopover.ts +151 -139
  106. package/src/lib/composables/useSideMenu.ts +28 -41
  107. package/src/lib/composables/useSlider.ts +2 -6
  108. package/src/lib/composables/useTooltip.ts +2 -2
  109. package/src/lib/config/index.ts +39 -0
  110. package/src/lib/constants/components.ts +1 -0
  111. package/src/lib/theme/devtools/Comparator.tsx +1 -1
  112. package/src/lib/theme/devtools/Inspector.tsx +1 -1
  113. package/src/lib/theme/devtools/LiveEditor.tsx +1 -1
  114. package/src/lib/theme/runtime/ThemeProvider.tsx +1 -1
  115. package/src/lib/types/components.ts +1 -0
  116. package/src/styles/01-settings/_index.scss +1 -0
  117. package/src/styles/01-settings/_settings.atomix-glass.scss +174 -0
  118. package/src/styles/01-settings/_settings.masonry-grid.scss +42 -6
  119. package/src/styles/02-tools/_tools.glass.scss +6 -0
  120. package/src/styles/05-objects/_objects.masonry-grid.scss +162 -24
  121. package/src/styles/06-components/_components.atomix-glass.scss +160 -99
  122. package/scripts/cli/__tests__/README.md +0 -81
  123. package/scripts/cli/__tests__/basic.test.js +0 -116
  124. package/scripts/cli/__tests__/clean.test.js +0 -278
  125. package/scripts/cli/__tests__/component-generator.test.js +0 -332
  126. package/scripts/cli/__tests__/component-validator.test.js +0 -433
  127. package/scripts/cli/__tests__/generator.test.js +0 -613
  128. package/scripts/cli/__tests__/glass-motion.test.js +0 -256
  129. package/scripts/cli/__tests__/integration.test.js +0 -938
  130. package/scripts/cli/__tests__/migrate.test.js +0 -74
  131. package/scripts/cli/__tests__/security.test.js +0 -206
  132. package/scripts/cli/__tests__/theme-bridge.test.js +0 -507
  133. package/scripts/cli/__tests__/token-manager.test.js +0 -251
  134. package/scripts/cli/__tests__/token-provider.test.js +0 -361
  135. package/scripts/cli/__tests__/utils.test.js +0 -165
  136. package/src/components/AtomixGlass/stories/AnimationTests.stories.tsx +0 -95
  137. package/src/components/AtomixGlass/stories/CardExamples.stories.tsx +0 -212
  138. package/src/components/AtomixGlass/stories/Customization.stories.tsx +0 -131
  139. package/src/components/AtomixGlass/stories/DashboardExamples.stories.tsx +0 -348
  140. package/src/components/AtomixGlass/stories/EcommerceExamples.stories.tsx +0 -410
  141. package/src/components/AtomixGlass/stories/FormExamples.stories.tsx +0 -436
  142. package/src/components/AtomixGlass/stories/HeroExamples.stories.tsx +0 -264
  143. package/src/components/AtomixGlass/stories/InteractivePlayground.stories.tsx +0 -247
  144. package/src/components/AtomixGlass/stories/MobileUIExamples.stories.tsx +0 -418
  145. package/src/components/AtomixGlass/stories/ModalExamples.stories.tsx +0 -402
  146. package/src/components/AtomixGlass/stories/Modes.stories.tsx +0 -1082
  147. package/src/components/AtomixGlass/stories/Overview.stories.tsx +0 -497
  148. package/src/components/AtomixGlass/stories/Performance.stories.tsx +0 -103
  149. package/src/components/AtomixGlass/stories/PresetGallery.stories.tsx +0 -335
  150. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +0 -395
  151. package/src/components/AtomixGlass/stories/WidgetExamples.stories.tsx +0 -441
  152. package/src/components/TypedButton/TypedButton.stories.tsx +0 -59
  153. package/src/components/TypedButton/TypedButton.tsx +0 -39
  154. package/src/components/TypedButton/index.ts +0 -2
  155. package/src/lib/composables/useBreadcrumb.ts +0 -81
  156. package/src/lib/composables/useChartInteractions.ts +0 -123
  157. package/src/lib/composables/useChartPerformance.ts +0 -347
  158. package/src/lib/composables/useDropdown.ts +0 -338
  159. package/src/lib/composables/useModal.ts +0 -110
  160. package/src/lib/composables/useTypedButton.ts +0 -66
  161. package/src/lib/hooks/usePerformanceMonitor.ts +0 -148
  162. package/src/lib/utils/displacement-generator.ts +0 -92
  163. package/src/lib/utils/memoryMonitor.ts +0 -191
  164. package/src/styles/01-settings/_settings.testtypecheck.scss +0 -53
  165. package/src/styles/01-settings/_settings.typedbutton.scss +0 -53
  166. package/src/styles/06-components/_components.testbutton.scss +0 -212
  167. package/src/styles/06-components/_components.testtypecheck.scss +0 -212
  168. package/src/styles/06-components/_components.typedbutton.scss +0 -212
package/dist/heavy.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
 
3
- import React, { memo, forwardRef, useMemo, useState, useRef, useEffect, useCallback, useImperativeHandle, Children, isValidElement } from "react";
3
+ import React, { memo, forwardRef, useId, useMemo, useState, useRef, useEffect, useCallback, useImperativeHandle, Children, isValidElement } from "react";
4
4
 
5
5
  import * as PhosphorIcons from "@phosphor-icons/react";
6
6
 
@@ -33,6 +33,7 @@ import { Pause, Play, SkipBack, SkipForward, SpeakerX, SpeakerHigh, Gear, Downlo
33
33
  FILTER_OVERLAY_CLASS: "c-atomix-glass__filter-overlay",
34
34
  FILTER_SHADOW_CLASS: "c-atomix-glass__filter-shadow",
35
35
  CONTENT_CLASS: "c-atomix-glass__content",
36
+ BORDER_BACKDROP_CLASS: "c-atomix-glass__border-backdrop",
36
37
  BORDER_1_CLASS: "c-atomix-glass__border-1",
37
38
  BORDER_2_CLASS: "c-atomix-glass__border-2",
38
39
  HOVER_1_CLASS: "c-atomix-glass__hover-1",
@@ -378,7 +379,7 @@ import { Pause, Play, SkipBack, SkipForward, SpeakerX, SpeakerHigh, Gear, Downlo
378
379
  default:
379
380
  return console.warn("AtomixGlass: Invalid displacement mode"), displacementMap;
380
381
  }
381
- }, GlassFilterComponent = ({id: id, displacementScale: displacementScale, aberrationIntensity: aberrationIntensity, mode: mode, shaderMapUrl: shaderMapUrl, blurAmount: blurAmount}) => jsx("svg", {
382
+ }, sharedShaderCache = new Map, GlassFilterComponent = ({id: id, displacementScale: displacementScale, aberrationIntensity: aberrationIntensity, mode: mode, shaderMapUrl: shaderMapUrl, blurAmount: blurAmount}) => jsx("svg", {
382
383
  style: {
383
384
  position: "absolute",
384
385
  width: "100%",
@@ -517,24 +518,17 @@ import { Pause, Play, SkipBack, SkipForward, SpeakerX, SpeakerHigh, Gear, Downlo
517
518
  GlassFilterComponent.displayName = "GlassFilter";
518
519
 
519
520
  // Memoize component to prevent unnecessary re-renders
520
- 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));
521
-
522
- // Module-level counter for deterministic ID generation
523
- let idCounter = 0;
524
-
525
- // Module-level shared shader cache with LRU eviction
526
- 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 = {
521
+ 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 = {
527
522
  x: 0,
528
523
  y: 0
529
524
  }, 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 = {
530
525
  width: 0,
531
526
  height: 0
532
- }, onClick: onClick, mode: mode = "standard", effectiveWithoutEffects: effectiveWithoutEffects = !1, effectiveReducedMotion: effectiveReducedMotion = !1, shaderVariant: shaderVariant = "liquidGlass", withLiquidBlur: withLiquidBlur = !1,
527
+ }, onClick: onClick, mode: mode = "standard", effectiveWithoutEffects: effectiveWithoutEffects = !1, effectiveReducedMotion: effectiveReducedMotion = !1, shaderVariant: shaderVariant = "liquidGlass", withLiquidBlur: withLiquidBlur = !1, isFixedOrSticky: isFixedOrSticky = !1,
533
528
  // Phase 1: Animation System props
534
529
  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) => {
535
- // Generate a stable, deterministic ID for SSR compatibility
536
- // Use a module-level counter that's consistent across server and client
537
- 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);
530
+ // React 18 useId — stable, unique, and SSR-safe (no module-level counter)
531
+ 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);
538
532
  // Lazy load shader utilities only when shader mode is needed
539
533
  useEffect((() => {
540
534
  "shader" === mode ?
@@ -557,9 +551,7 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
557
551
  // Create cache key from size and variant
558
552
  const cacheKey = `${glassSize.width}x${glassSize.height}-${shaderVariant}`, cachedUrl = (key => {
559
553
  const entry = sharedShaderCache.get(key);
560
- return entry ? (
561
- // Update access timestamp for LRU
562
- entry.timestamp = Date.now(), entry.url) : null;
554
+ return entry ? (entry.timestamp = Date.now(), entry.url) : null;
563
555
  })(cacheKey);
564
556
  // Check shared cache first
565
557
  if (cachedUrl) return void setShaderMapUrl(cachedUrl);
@@ -570,29 +562,24 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
570
562
  if (shaderUtilsRef.current) try {
571
563
  const {ShaderDisplacementGenerator: ShaderDisplacementGenerator, fragmentShaders: fragmentShaders} = shaderUtilsRef.current;
572
564
  shaderGeneratorRef.current?.destroy();
573
- const selectedShader = fragmentShaders[shaderVariant] || fragmentShaders.liquidGlass;
565
+ const selectedShader = fragmentShaders[shaderVariant] ?? fragmentShaders.liquidGlass;
574
566
  shaderGeneratorRef.current = new ShaderDisplacementGenerator({
575
567
  width: glassSize.width,
576
568
  height: glassSize.height,
577
569
  fragment: selectedShader
578
570
  }), shaderUpdateTimeoutRef.current = setTimeout((() => {
579
- const url = shaderGeneratorRef.current?.updateShader() || "";
571
+ const url = shaderGeneratorRef.current?.updateShader() ?? "";
580
572
  url && ((key, url) => {
581
- // Evict oldest entries if at capacity
582
573
  if (sharedShaderCache.size >= 15) {
583
574
  const entries = Array.from(sharedShaderCache.entries());
584
- // Sort by timestamp (oldest first)
585
- entries.sort(((a, b) => a[1].timestamp - b[1].timestamp));
586
- // Remove oldest entry
587
- const oldestEntry = entries[0];
588
- oldestEntry && sharedShaderCache.delete(oldestEntry[0]);
575
+ entries.sort(((a, b) => a[1].timestamp - b[1].timestamp));
576
+ const oldest = entries[0];
577
+ oldest && sharedShaderCache.delete(oldest[0]);
589
578
  }
590
579
  sharedShaderCache.set(key, {
591
580
  url: url,
592
581
  timestamp: Date.now()
593
- }),
594
- // Development mode: log cache size
595
- "undefined" != typeof process && "production" === process.env?.NODE_ENV || sharedShaderCache.size;
582
+ }), "undefined" != typeof process && "production" === process.env?.NODE_ENV || sharedShaderCache.size;
596
583
  })(cacheKey, url), setShaderMapUrl(url);
597
584
  }), 100);
598
585
  } catch (error) {
@@ -661,7 +648,6 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
661
648
  console.warn("AtomixGlassContainer: Error getting element bounds", error), setRectCache(null);
662
649
  }
663
650
  }), [ ref, glassSize ]);
664
- // Pre-calculate static multipliers outside useMemo
665
651
  const liquidBlur = useMemo((() => {
666
652
  const defaultBlur = {
667
653
  baseBlur: blurAmount,
@@ -730,7 +716,6 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
730
716
  }), [ borderRadius, backdropStyle, mouseOffset, overLight, effectiveWithoutEffects, overLightConfig ]);
731
717
  return jsx("div", {
732
718
  ref: el => {
733
- // Apply force no-transition
734
719
  // Handle forwarded ref
735
720
  "function" == typeof ref ? ref(el) : ref && (ref.current = el);
736
721
  },
@@ -772,6 +757,7 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
772
757
  });
773
758
  }));
774
759
 
760
+ // ─── Blur multiplier constants (module-level, never change at runtime) ────────
775
761
  AtomixGlassContainer.displayName = "AtomixGlassContainer";
776
762
 
777
763
  // Singleton instance
@@ -972,8 +958,6 @@ class {
972
958
  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})`;
973
959
  // Container variables
974
960
  const style = containerElement.style;
975
- style.setProperty("--atomix-glass-container-width", isFixedOrSticky ? `${glassSize.width}` : "100%"),
976
- style.setProperty("--atomix-glass-container-height", isFixedOrSticky ? `${glassSize.height}` : "100%"),
977
961
  style.setProperty("--atomix-glass-container-padding", padding), style.setProperty("--atomix-glass-container-radius", `${effectiveBorderRadius}px`),
978
962
  style.setProperty("--atomix-glass-container-backdrop", backdropFilterString),
979
963
  // Shadows
@@ -1434,47 +1418,55 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
1434
1418
  return "undefined" == typeof process || process.env, finalConfig;
1435
1419
  }
1436
1420
  return "undefined" == typeof process || process.env, baseConfig;
1437
- }), [ 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 => {
1438
- if (externalGlobalMousePosition && externalMouseOffset) return;
1439
- if (effectiveWithoutEffects) return;
1440
- const container = mouseContainer?.current || glassRef.current;
1441
- if (!container) return;
1442
- // Use cached rect if available, otherwise get new one
1443
- let rect = cachedRectRef.current;
1444
- if (rect && 0 !== rect.width && 0 !== rect.height || (rect = container.getBoundingClientRect(),
1445
- cachedRectRef.current = rect), 0 === rect.width || 0 === rect.height) return;
1446
- const center = calculateElementCenter(rect);
1447
- // Write raw target — the lerp loop will smoothly pursue it
1448
- targetMouseOffsetRef.current = {
1449
- x: (globalPos.x - center.x) / rect.width * 100,
1450
- y: (globalPos.y - center.y) / rect.height * 100
1451
- }, targetGlobalMousePositionRef.current = globalPos;
1452
- }), [ mouseContainer, glassRef, externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects ]), startLerpLoop = useCallback((() => {
1421
+ }), [ 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((() => {
1422
+ lerpActiveRef.current = !1, null !== lerpRafRef.current && (cancelAnimationFrame(lerpRafRef.current),
1423
+ lerpRafRef.current = null);
1424
+ }), []), startLerpLoop = useCallback((() => {
1453
1425
  if (lerpActiveRef.current) return;
1454
1426
  lerpActiveRef.current = !0;
1455
1427
  const LERP_T = CONSTANTS.LERP_FACTOR, tick = () => {
1456
1428
  if (!lerpActiveRef.current) return;
1457
- // Add ref validity check to prevent memory leaks
1458
- if (!glassRef.current || !wrapperRef?.current) return void (lerpActiveRef.current = !1);
1429
+ if (!glassRef.current) return void (lerpActiveRef.current = !1);
1459
1430
  const cur = internalMouseOffsetRef.current, tgt = targetMouseOffsetRef.current, dx = tgt.x - cur.x, dy = tgt.y - cur.y;
1460
1431
  // If we're close enough, snap and park
1461
- if (Math.abs(dx) < .05 && Math.abs(dy) < .05) internalMouseOffsetRef.current = {
1432
+ if (Math.abs(dx) < .01 && Math.abs(dy) < .01) return internalMouseOffsetRef.current = {
1462
1433
  ...tgt
1463
1434
  }, internalGlobalMousePositionRef.current = {
1464
1435
  ...targetGlobalMousePositionRef.current
1465
- }; else {
1466
- internalMouseOffsetRef.current = {
1467
- x: lerp$1(cur.x, tgt.x, LERP_T),
1468
- y: lerp$1(cur.y, tgt.y, LERP_T)
1469
- };
1470
- const curG = internalGlobalMousePositionRef.current, tgtG = targetGlobalMousePositionRef.current;
1471
- internalGlobalMousePositionRef.current = {
1472
- x: lerp$1(curG.x, tgtG.x, LERP_T),
1473
- y: lerp$1(curG.y, tgtG.y, LERP_T)
1474
- };
1475
- }
1476
- // Imperative style update with the smoothed values
1477
- updateAtomixGlassStyles(wrapperRef.current, glassRef.current, {
1436
+ },
1437
+ // Final update and stop
1438
+ updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
1439
+ mouseOffset: internalMouseOffsetRef.current,
1440
+ globalMousePosition: internalGlobalMousePositionRef.current,
1441
+ glassSize: glassSize,
1442
+ isHovered: isHovered,
1443
+ isActive: isActive,
1444
+ isOverLight: overLightConfig.isOverLight,
1445
+ baseOverLightConfig: overLightConfig,
1446
+ effectiveBorderRadius: effectiveBorderRadius,
1447
+ effectiveWithoutEffects: effectiveWithoutEffects,
1448
+ effectiveReducedMotion: effectiveReducedMotion,
1449
+ elasticity: elasticity,
1450
+ directionalScale: isActive && Boolean(onClick) ? "scale(0.96)" : "scale(1)",
1451
+ onClick: onClick,
1452
+ withLiquidBlur: withLiquidBlur,
1453
+ blurAmount: blurAmount,
1454
+ saturation: saturation,
1455
+ padding: padding,
1456
+ isFixedOrSticky: isFixedOrSticky
1457
+ }), void stopLerpLoop();
1458
+ // Smooth step
1459
+ internalMouseOffsetRef.current = {
1460
+ x: lerp$1(cur.x, tgt.x, LERP_T),
1461
+ y: lerp$1(cur.y, tgt.y, LERP_T)
1462
+ };
1463
+ const curG = internalGlobalMousePositionRef.current, tgtG = targetGlobalMousePositionRef.current;
1464
+ internalGlobalMousePositionRef.current = {
1465
+ x: lerp$1(curG.x, tgtG.x, LERP_T),
1466
+ y: lerp$1(curG.y, tgtG.y, LERP_T)
1467
+ },
1468
+ // Imperative style update
1469
+ updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
1478
1470
  mouseOffset: internalMouseOffsetRef.current,
1479
1471
  globalMousePosition: internalGlobalMousePositionRef.current,
1480
1472
  glassSize: glassSize,
@@ -1497,10 +1489,24 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
1497
1489
  };
1498
1490
  // 0.08 – lower = more viscous
1499
1491
  lerpRafRef.current = requestAnimationFrame(tick);
1500
- }), [ glassRef, wrapperRef, glassSize, isHovered, isActive, overLightConfig, effectiveBorderRadius, effectiveWithoutEffects, effectiveReducedMotion, elasticity, onClick, withLiquidBlur, blurAmount, saturation, padding, isFixedOrSticky ]), stopLerpLoop = useCallback((() => {
1501
- lerpActiveRef.current = !1, null !== lerpRafRef.current && (cancelAnimationFrame(lerpRafRef.current),
1502
- lerpRafRef.current = null);
1503
- }), []);
1492
+ }), [ glassRef, wrapperRef, glassSize, isHovered, isActive, overLightConfig, effectiveBorderRadius, effectiveWithoutEffects, effectiveReducedMotion, elasticity, onClick, withLiquidBlur, blurAmount, saturation, padding, isFixedOrSticky, stopLerpLoop ]), handleGlobalMousePosition = useCallback((globalPos => {
1493
+ if (externalGlobalMousePosition && externalMouseOffset) return;
1494
+ if (effectiveWithoutEffects) return;
1495
+ const container = mouseContainer?.current || glassRef.current;
1496
+ if (!container) return;
1497
+ // Use cached rect if available, otherwise get new one
1498
+ let rect = cachedRectRef.current;
1499
+ if (rect && 0 !== rect.width && 0 !== rect.height || (rect = container.getBoundingClientRect(),
1500
+ cachedRectRef.current = rect), 0 === rect.width || 0 === rect.height) return;
1501
+ const center = calculateElementCenter(rect);
1502
+ // Write raw target — the lerp loop will smoothly pursue it
1503
+ targetMouseOffsetRef.current = {
1504
+ x: (globalPos.x - center.x) / rect.width * 100,
1505
+ y: (globalPos.y - center.y) / rect.height * 100
1506
+ }, targetGlobalMousePositionRef.current = globalPos,
1507
+ // Ensure the lerp loop is running to smoothly chase the new target
1508
+ lerpActiveRef.current || startLerpLoop();
1509
+ }), [ mouseContainer, glassRef, externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects, startLerpLoop ]);
1504
1510
  /**
1505
1511
  * Validate and clamp a numeric config value
1506
1512
  */
@@ -1509,7 +1515,7 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
1509
1515
  if (externalGlobalMousePosition && externalMouseOffset) return;
1510
1516
  if (effectiveWithoutEffects) return;
1511
1517
  const unsubscribe = globalMouseTracker.subscribe(handleGlobalMousePosition);
1512
- // Start the lerp loop — it will smoothly chase the target
1518
+ // Initial start
1513
1519
  startLerpLoop();
1514
1520
  const container = mouseContainer?.current || glassRef.current;
1515
1521
  let resizeObserver = null;
@@ -1522,7 +1528,7 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
1522
1528
  unsubscribe(), stopLerpLoop(), null !== updateRectRef.current && (cancelAnimationFrame(updateRectRef.current),
1523
1529
  updateRectRef.current = null), resizeObserver && resizeObserver.disconnect();
1524
1530
  };
1525
- }), [ handleGlobalMousePosition, startLerpLoop, stopLerpLoop, mouseContainer, glassRef, externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects ]),
1531
+ }), [ externalGlobalMousePosition, externalMouseOffset, effectiveWithoutEffects, handleGlobalMousePosition, startLerpLoop, stopLerpLoop, mouseContainer, glassRef ]),
1526
1532
  // Also call updateStyles on other state changes (hover, active, etc)
1527
1533
  useEffect((() => {
1528
1534
  updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
@@ -1998,184 +2004,147 @@ const _includesInstanceProperty = getDefaultExportFromCjs((function(it) {
1998
2004
  /**
1999
2005
  * Get GPU memory info if available (Chrome DevTools only)
2000
2006
  */
2007
+ /** Map an FPS value to a semantic color token string. */
2008
+ const getQualityColor = quality => {
2009
+ switch (quality) {
2010
+ case "high":
2011
+ return "var(--atomix-color-success, #4ade80)";
2012
+
2013
+ case "medium":
2014
+ return "var(--atomix-color-warning, #fbbf24)";
2015
+
2016
+ case "low":
2017
+ return "var(--atomix-color-danger, #ef4444)";
2018
+
2019
+ default:
2020
+ return "#9ca3af";
2021
+ }
2022
+ }, getFpsLabel = fps => fps >= 58 ? "Optimal" : fps >= 45 ? "Warning" : "Critical";
2023
+
2024
+ /** Map a quality level string to a semantic color token string. */
2025
+ // Inject keyframes once
2026
+ if ("undefined" != typeof document) {
2027
+ const styleId = "perf-dashboard-keyframes";
2028
+ if (!document.getElementById(styleId)) {
2029
+ const styleEl = document.createElement("style");
2030
+ styleEl.id = styleId, styleEl.textContent = "\n@keyframes perf-dashboard-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n}\n",
2031
+ document.head.appendChild(styleEl);
2032
+ }
2033
+ }
2034
+
2001
2035
  /**
2002
- * PerformanceDashboard - Real-time performance monitoring overlay
2036
+ * PerformanceDashboard - Real-time performance monitoring overlay.
2003
2037
  *
2004
- * Displays:
2005
- * - Current FPS with color coding
2006
- * - Frame time statistics
2007
- * - Quality level indicator
2008
- * - GPU memory usage (if available)
2009
- * - Auto-scaling status
2010
- */
2011
- const PerformanceDashboard = ({metrics: metrics, isVisible: isVisible = !0, onClose: onClose}) => {
2012
- // Get color for FPS display
2013
- const getFpsColor = fps => fps >= 58 ? "#4ade80" : // Green - good
2014
- fps >= 45 ? "#fbbf24" : "#ef4444" // Red - critical
2015
- , dashboardStyle = useMemo((() => ({
2016
- position: "fixed",
2017
- top: "16px",
2018
- right: "16px",
2019
- padding: "12px 16px",
2020
- backgroundColor: "rgba(17, 24, 39, 0.95)",
2021
- borderRadius: "8px",
2022
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
2023
- fontFamily: "monospace",
2024
- fontSize: "12px",
2025
- color: "#fff",
2026
- zIndex: 9999,
2027
- minWidth: "200px",
2028
- backdropFilter: "blur(8px)",
2029
- border: "1px solid rgba(255, 255, 255, 0.1)",
2030
- transition: "opacity 0.3s ease",
2031
- opacity: isVisible ? 1 : 0,
2032
- pointerEvents: isVisible ? "auto" : "none"
2033
- })), [ isVisible ]), headerStyle = useMemo((() => ({
2034
- display: "flex",
2035
- justifyContent: "space-between",
2036
- alignItems: "center",
2037
- marginBottom: "8px",
2038
- paddingBottom: "8px",
2039
- borderBottom: "1px solid rgba(255, 255, 255, 0.1)"
2040
- })), []), titleStyle = useMemo((() => ({
2041
- fontWeight: "bold",
2042
- fontSize: "13px",
2043
- color: "#fff"
2044
- })), []), closeButtonStyle = useMemo((() => ({
2045
- background: "transparent",
2046
- border: "none",
2047
- color: "#9ca3af",
2048
- cursor: "pointer",
2049
- fontSize: "16px",
2050
- padding: "0",
2051
- lineHeight: "1"
2052
- })), []), metricRowStyle = useMemo((() => ({
2053
- display: "flex",
2054
- justifyContent: "space-between",
2055
- alignItems: "center",
2056
- marginBottom: "6px"
2057
- })), []), labelStyle = useMemo((() => ({
2058
- color: "#9ca3af",
2059
- marginRight: "12px"
2060
- })), []), valueStyle = useMemo((() => ({
2061
- fontWeight: "bold"
2062
- })), []);
2063
- // Get quality level badge color
2064
- return isVisible ? jsxs("div", {
2065
- style: dashboardStyle,
2038
+ * Displays FPS, frame time, quality level, GPU memory, and auto-scaling status.
2039
+ * Rendered only when `debugPerformance={true}` on the parent `AtomixGlass`.
2040
+ */ const PerformanceDashboard = memo((({metrics: metrics, isVisible: isVisible = !0, onClose: onClose}) => {
2041
+ if (!isVisible) return null;
2042
+ const fpsColor = (fps = metrics.fps) >= 58 ? "var(--atomix-color-success, #4ade80)" : fps >= 45 ? "var(--atomix-color-warning, #fbbf24)" : "var(--atomix-color-danger, #ef4444)";
2043
+ var fps;
2044
+ const isCritical = metrics.fps < 45;
2045
+ return jsxs("div", {
2046
+ 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",
2047
+ style: {
2048
+ zIndex: 9999,
2049
+ minWidth: "12.5rem",
2050
+ // 200px
2051
+ backgroundColor: "rgba(17, 24, 39, 0.95)",
2052
+ backdropFilter: "blur(8px)",
2053
+ transition: "opacity 0.3s ease"
2054
+ },
2066
2055
  children: [ jsxs("div", {
2067
- style: headerStyle,
2056
+ className: "u-flex u-items-center u-justify-between u-mb-2 u-pb-2 u-border-b u-border-white-alpha-10",
2068
2057
  children: [ jsx("span", {
2069
- style: titleStyle,
2058
+ className: "u-text-sm u-font-bold u-text-white",
2070
2059
  children: "Performance Monitor"
2071
2060
  }), onClose && jsx("button", {
2072
- style: closeButtonStyle,
2061
+ 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",
2073
2062
  onClick: onClose,
2074
2063
  "aria-label": "Close performance dashboard",
2064
+ style: {
2065
+ transition: "color 0.2s ease"
2066
+ },
2075
2067
  children: "×"
2076
2068
  }) ]
2077
2069
  }), jsxs("div", {
2078
- style: metricRowStyle,
2070
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
2079
2071
  children: [ jsx("span", {
2080
- style: labelStyle,
2072
+ className: "u-text-gray-400 u-me-3",
2081
2073
  children: "FPS"
2082
2074
  }), jsx("span", {
2075
+ className: "u-font-bold",
2083
2076
  style: {
2084
- ...valueStyle,
2085
- color: getFpsColor(metrics.fps)
2077
+ color: fpsColor
2086
2078
  },
2087
2079
  children: Math.round(metrics.fps)
2088
2080
  }) ]
2089
2081
  }), jsxs("div", {
2090
- style: metricRowStyle,
2082
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
2091
2083
  children: [ jsx("span", {
2092
- style: labelStyle,
2084
+ className: "u-text-gray-400 u-me-3",
2093
2085
  children: "Frame Time"
2094
2086
  }), jsxs("span", {
2095
- style: valueStyle,
2087
+ className: "u-font-bold",
2096
2088
  children: [ metrics.frameTime.toFixed(2), "ms" ]
2097
2089
  }) ]
2098
2090
  }), jsxs("div", {
2099
- style: metricRowStyle,
2091
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
2100
2092
  children: [ jsx("span", {
2101
- style: labelStyle,
2093
+ className: "u-text-gray-400 u-me-3",
2102
2094
  children: "Quality"
2103
2095
  }), jsx("span", {
2096
+ className: "u-font-bold u-text-uppercase",
2104
2097
  style: {
2105
- ...valueStyle,
2106
- color: (quality => {
2107
- switch (quality) {
2108
- case "high":
2109
- return "#4ade80";
2110
-
2111
- case "medium":
2112
- return "#fbbf24";
2113
-
2114
- case "low":
2115
- return "#ef4444";
2116
-
2117
- default:
2118
- return "#9ca3af";
2119
- }
2120
- })(metrics.qualityLevel),
2121
- textTransform: "uppercase",
2122
- fontSize: "11px"
2098
+ fontSize: "0.6875rem",
2099
+ // 11px
2100
+ color: getQualityColor(metrics.qualityLevel)
2123
2101
  },
2124
2102
  children: metrics.qualityLevel
2125
2103
  }) ]
2126
2104
  }), metrics.gpuMemory && jsxs("div", {
2127
- style: metricRowStyle,
2105
+ className: "u-flex u-items-center u-justify-between u-mb-1-5",
2128
2106
  children: [ jsx("span", {
2129
- style: labelStyle,
2107
+ className: "u-text-gray-400 u-me-3",
2130
2108
  children: "GPU Memory"
2131
2109
  }), jsxs("span", {
2132
- style: valueStyle,
2110
+ className: "u-font-bold",
2133
2111
  children: [ "~", Math.round(metrics.gpuMemory / 1024), "MB" ]
2134
2112
  }) ]
2135
2113
  }), metrics.isAutoScaling && jsx("div", {
2114
+ className: "u-mt-2 u-pt-2 u-border-t u-border-white-alpha-10 u-text-center",
2136
2115
  style: {
2137
- marginTop: "8px",
2138
- paddingTop: "8px",
2139
- borderTop: "1px solid rgba(255, 255, 255, 0.1)",
2140
- fontSize: "10px",
2141
- color: "#6b7280",
2142
- textAlign: "center"
2116
+ fontSize: "0.625rem",
2117
+ // 10px
2118
+ color: "#6b7280"
2143
2119
  },
2144
2120
  children: "Auto-scaling active"
2145
2121
  }), jsxs("div", {
2146
- style: {
2147
- marginTop: "8px",
2148
- paddingTop: "8px",
2149
- borderTop: "1px solid rgba(255, 255, 255, 0.1)",
2150
- display: "flex",
2151
- alignItems: "center",
2152
- gap: "6px"
2153
- },
2122
+ className: "u-flex u-items-center u-gap-2 u-mt-2 u-pt-2 u-border-t u-border-white-alpha-10",
2154
2123
  children: [ jsx("div", {
2124
+ className: "u-rounded-full",
2155
2125
  style: {
2156
- width: "8px",
2157
- height: "8px",
2158
- borderRadius: "50%",
2159
- backgroundColor: getFpsColor(metrics.fps),
2160
- animation: metrics.fps < 45 ? "pulse 1s infinite" : "none"
2126
+ width: "0.5rem",
2127
+ height: "0.5rem",
2128
+ flexShrink: 0,
2129
+ backgroundColor: fpsColor,
2130
+ ...isCritical && {
2131
+ animation: "perf-dashboard-pulse 1s infinite"
2132
+ }
2161
2133
  }
2162
2134
  }), jsx("span", {
2135
+ className: "u-text-xs",
2163
2136
  style: {
2164
- fontSize: "10px",
2165
- color: metrics.fps >= 58 ? "#4ade80" : metrics.fps >= 45 ? "#fbbf24" : "#ef4444"
2137
+ fontSize: "0.625rem",
2138
+ // 10px
2139
+ color: fpsColor
2166
2140
  },
2167
- children: metrics.fps >= 58 ? "Optimal" : metrics.fps >= 45 ? "Warning" : "Critical"
2141
+ children: getFpsLabel(metrics.fps)
2168
2142
  }) ]
2169
2143
  }) ]
2170
- }) : null;
2171
- };
2144
+ });
2145
+ }));
2172
2146
 
2173
- // Add pulse animation for critical FPS
2174
- if ("undefined" != typeof document) {
2175
- const styleSheet = document.createElement("style");
2176
- styleSheet.textContent = "\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n ",
2177
- document.head.appendChild(styleSheet);
2178
- }
2147
+ PerformanceDashboard.displayName = "PerformanceDashboard";
2179
2148
 
2180
2149
  /**
2181
2150
  * Mobile optimization presets
@@ -2186,7 +2155,8 @@ if ("undefined" != typeof document) {
2186
2155
  /**
2187
2156
  * Performance preset - Maximum FPS, reduced quality
2188
2157
  * Best for low-end devices or when battery saving is priority
2189
- */ const PERFORMANCE_PRESET = {
2158
+ */
2159
+ const PERFORMANCE_PRESET = {
2190
2160
  distortionOctaves: 2,
2191
2161
  // Minimal FBM layers
2192
2162
  displacementScale: 50,
@@ -2295,95 +2265,21 @@ if ("undefined" != typeof document) {
2295
2265
  saturation: 70
2296
2266
  }
2297
2267
  }
2298
- };
2299
-
2300
- /**
2301
- * Balanced preset - Good quality with reasonable performance
2302
- * Default preset for most mobile devices
2303
- */
2304
- /**
2305
- * AtomixGlass - A high-performance glass morphism component with liquid distortion effects
2306
- *
2307
- * Features:
2308
- * - Hardware-accelerated glass effects with SVG filters
2309
- * - Mouse-responsive liquid distortion
2310
- * - Dynamic border-radius extraction from children CSS properties
2311
- * - Automatic light/dark theme detection via overLight prop
2312
- * - Accessibility and performance optimizations
2313
- * - Multiple displacement modes (standard, polar, prominent, shader)
2314
- * - Design token integration for consistent theming
2315
- * - Focus ring support for keyboard navigation
2316
- * - Responsive breakpoints for mobile optimization
2317
- * - Enhanced ARIA attributes for screen readers
2318
- * - Time-based animation system with FBM distortion
2319
- * - Device preset optimization for performance/quality balance
2320
- *
2321
- * Design System Compliance:
2322
- * - Uses design tokens for opacity, spacing, and colors
2323
- * - Follows BEM methodology for class naming
2324
- * - Implements focus-ring mixin for accessibility
2325
- * - Supports reduced motion and high contrast preferences
2326
- *
2327
- * @example
2328
- * // Basic usage with dynamic border-radius extraction
2329
- * <AtomixGlass>
2330
- * <div style={{ borderRadius: '12px' }}>Content with 12px radius</div>
2331
- * </AtomixGlass>
2332
- *
2333
- * @example
2334
- * // Manual border-radius override
2335
- * <AtomixGlass borderRadius={20}>
2336
- * <div>Content with 20px glass radius</div>
2337
- * </AtomixGlass>
2338
- *
2339
- * @example
2340
- * // Interactive glass with click handler
2341
- * <AtomixGlass onClick={() => console.log('Clicked')} aria-label="Glass card">
2342
- * <div>Clickable content</div>
2343
- * </AtomixGlass>
2344
- *
2345
- * @example
2346
- * // OverLight - Boolean mode (explicit control)
2347
- * <AtomixGlass overLight={true}>
2348
- * <div>Content on light background</div>
2349
- * </AtomixGlass>
2350
- *
2351
- * @example
2352
- * // OverLight - Auto-detection mode
2353
- * <AtomixGlass overLight="auto">
2354
- * <div>Content with auto-detected background</div>
2355
- * </AtomixGlass>
2356
- *
2357
- * @example
2358
- * // OverLight - Object config with custom settings
2359
- * <AtomixGlass
2360
- * overLight={{
2361
- * threshold: 0.8,
2362
- * opacity: 0.6,
2363
- * contrast: 1.8,
2364
- * brightness: 1.0,
2365
- * saturationBoost: 1.5
2366
- * }}
2367
- * >
2368
- * <div>Content with custom overLight config</div>
2369
- * </AtomixGlass>
2370
- *
2371
- * @example
2372
- * // Debug mode for overLight detection
2373
- * <AtomixGlass overLight="auto" debugOverLight={true}>
2374
- * <div>Content with debug logging enabled</div>
2375
- * </AtomixGlass>
2376
- *
2377
- * @example
2378
- * // Performance-optimized for mobile devices
2379
- * <AtomixGlass devicePreset="performance" disableResponsiveBreakpoints={false}>
2380
- * <div>Mobile-optimized glass effect</div>
2381
- * </AtomixGlass>
2382
- */
2383
- 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}) {
2384
- 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({
2268
+ }, 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) {
2269
+ const glassRef = useRef(null), contentRef = useRef(null), internalWrapperRef = useRef(null), mergedRef = useMemo((() =>
2270
+ // Helper to merge refs
2271
+ function(...refs) {
2272
+ return node => {
2273
+ refs.forEach((ref => {
2274
+ "function" == typeof ref ? ref(node) : null != ref && (ref.current = node);
2275
+ }));
2276
+ };
2277
+ }
2278
+ // Internal implementation with forwardRef
2279
+ (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({
2385
2280
  glassRef: glassRef,
2386
2281
  contentRef: contentRef,
2282
+ wrapperRef: internalWrapperRef,
2387
2283
  borderRadius: borderRadius,
2388
2284
  globalMousePosition: externalGlobalMousePosition,
2389
2285
  mouseOffset: externalMouseOffset,
@@ -2412,7 +2308,6 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
2412
2308
  distortionGain: distortionGain,
2413
2309
  distortionQuality: distortionQuality
2414
2310
  });
2415
- // Re-calculate only when devicePreset changes
2416
2311
  // Responsive breakpoint system - automatically adjusts parameters based on viewport
2417
2312
  !
2418
2313
  /**
@@ -2602,11 +2497,10 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
2602
2497
  },
2603
2498
  breakpoints: MOBILE_OPTIMIZED_BREAKPOINTS,
2604
2499
  enabled: !disableResponsiveBreakpoints && "undefined" != typeof window,
2605
- // Enable unless disabled
2606
2500
  debug: !1
2607
2501
  });
2608
2502
  // Performance monitoring - tracks FPS, frame time, memory usage
2609
- const {metrics: performanceMetrics, recommendedQuality: recommendedQuality, isUnderperforming: isUnderperforming, setQualityLevel: setQualityLevel, toggleMonitoring: toggleMonitoring} =
2503
+ const {metrics: performanceMetrics, toggleMonitoring: toggleMonitoring} =
2610
2504
  /**
2611
2505
  * Performance Monitor Hook
2612
2506
  *
@@ -2770,17 +2664,17 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
2770
2664
  toggleMonitoring: toggleMonitoring
2771
2665
  };
2772
2666
  }({
2773
- enabled: !1,
2774
- // We'll toggle manually based on prop
2667
+ enabled: debugPerformance,
2668
+ // Enable when debugPerformance is true
2775
2669
  debug: !1,
2776
2670
  showOverlay: !1
2777
2671
  });
2778
- // Auto-start performance monitoring if enabled (only in development)
2672
+ // Auto-start performance monitoring when debugPerformance is enabled
2779
2673
  React.useEffect((() => {
2780
- "development" === process.env.NODE_ENV && window?.enablePerformanceMonitoring && toggleMonitoring();
2674
+ debugPerformance && toggleMonitoring();
2781
2675
  // eslint-disable-next-line react-hooks/exhaustive-deps
2782
- }), []);
2783
- // Only run once on mount
2676
+ }), [ debugPerformance ]);
2677
+ // Re-run when debugPerformance changes
2784
2678
  const isOverLight = useMemo((() => overLightConfig.isOverLight), [ overLightConfig.isOverLight ]), shouldRenderOverLightLayers = withOverLightLayers && isOverLight, rootLayoutStyle = useMemo((() => {
2785
2679
  if (!isFixedOrSticky) return {};
2786
2680
  const {position: p, top: t, left: l, right: r, bottom: b} = restStyle;
@@ -2808,34 +2702,30 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
2808
2702
  if (isFixedOrSticky) {
2809
2703
  const {position: _p, top: _t, left: _l, right: _r, bottom: _b, ...visualStyle} = restStyle;
2810
2704
  return {
2811
- ...visualStyle,
2812
- ...!effectiveWithoutEffects && {
2813
- transform: transformStyle
2814
- }
2705
+ ...visualStyle
2815
2706
  };
2816
2707
  }
2817
2708
  return {
2818
- ...restStyle,
2819
- ...!effectiveWithoutEffects && {
2820
- transform: transformStyle
2821
- }
2709
+ ...restStyle
2822
2710
  };
2823
- }), [ isFixedOrSticky, restStyle, effectiveWithoutEffects, transformStyle ]);
2711
+ }), [ isFixedOrSticky, restStyle ]);
2824
2712
  // Build className with state modifiers
2825
2713
  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((() => ({
2826
2714
  position: isFixedOrSticky ? "absolute" : restStyle.position || "absolute",
2827
- top: isFixedOrSticky ? 0 : restStyle.top || 0,
2828
- left: isFixedOrSticky ? 0 : restStyle.left || 0
2829
- })), [ isFixedOrSticky, restStyle.position, restStyle.top, restStyle.left ]), adjustedSize = useMemo((() => {
2715
+ top: isFixedOrSticky ? restStyle.top ?? 0 : 0,
2716
+ left: isFixedOrSticky ? restStyle.left ?? 0 : 0,
2717
+ right: isFixedOrSticky ? restStyle.right ?? "auto" : "auto",
2718
+ bottom: isFixedOrSticky ? restStyle.bottom ?? "auto" : "auto"
2719
+ })), [ isFixedOrSticky, restStyle.position, restStyle.top, restStyle.left, restStyle.right, restStyle.bottom ]), adjustedSize = useMemo((() => {
2830
2720
  // Keep a reference to positionStyles to avoid unused-variable lint,
2831
2721
  // but sizing is driven by explicit width/height or measured size.
2832
2722
  positionStyles.position;
2833
- 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;
2723
+ 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;
2834
2724
  return {
2835
2725
  width: resolveLength(effectiveWidth, glassSize.width),
2836
2726
  height: resolveLength(effectiveHeight, glassSize.height)
2837
2727
  };
2838
- }), [ width, height, restStyle.width, restStyle.height, positionStyles.position, glassSize.width, glassSize.height ]), gradientValues = useMemo((() => {
2728
+ }), [ width, height, restStyle.width, restStyle.height, positionStyles.position, glassSize.width, glassSize.height, isFixedOrSticky ]), gradientValues = useMemo((() => {
2839
2729
  const mx = mouseOffset.x, my = mouseOffset.y, absMx = Math.abs(mx), absMy = Math.abs(my), GRADIENT = ATOMIX_GLASS.CONSTANTS.GRADIENT;
2840
2730
  return {
2841
2731
  borderGradientAngle: GRADIENT.BASE_ANGLE + mx * GRADIENT.ANGLE_MULTIPLIER,
@@ -2865,33 +2755,32 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
2865
2755
  absMx: absMx,
2866
2756
  absMy: absMy
2867
2757
  };
2868
- }), [ mouseOffset.x, mouseOffset.y ]), opacityValues = useMemo((() => {
2869
- const overLightOpacity = overLightConfig.opacity;
2870
- return {
2871
- hover1: isHovered || isActive ? .5 : 0,
2872
- hover2: isActive ? .5 : 0,
2873
- hover3: isHovered ? .4 : isActive ? .8 : 0,
2874
- base: isOverLight ? overLightOpacity || .4 : 0,
2875
- over: isOverLight ? 1.1 * (overLightOpacity || .4) : 0
2876
- };
2877
- }), [ isHovered, isActive, isOverLight, overLightConfig.opacity ]), glassVars = useMemo((() => {
2878
- 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;
2758
+ }), [ 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((() => ({
2759
+ hover1: isHovered || isActive ? .5 : 0,
2760
+ hover2: isActive ? .5 : 0,
2761
+ hover3: isHovered ? .4 : isActive ? .8 : 0,
2762
+ base: isOverLight ? clampedOverLightOpacity || .4 : 0,
2763
+ over: isOverLight ? 1.1 * (clampedOverLightOpacity || .4) : 0
2764
+ })), [ isHovered, isActive, isOverLight, clampedOverLightOpacity ]), glassVars = useMemo((() => {
2765
+ 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;
2879
2766
  return {
2880
2767
  ...void 0 !== customZIndex && {
2881
2768
  "--atomix-glass-base-z-index": customZIndex
2882
2769
  },
2883
2770
  "--atomix-glass-radius": `${effectiveBorderRadius}px`,
2884
2771
  "--atomix-glass-transform": transformStyle || "none",
2885
- // Internal decorative layers are positioned relative to the root;
2886
- "--atomix-glass-position": rootLayoutStyle.position,
2887
- "--atomix-glass-top": `${isFixedOrSticky ? rootLayoutStyle.top : 0}px`,
2888
- "--atomix-glass-left": `${isFixedOrSticky ? rootLayoutStyle.left : 0}px`,
2772
+ "--atomix-glass-container-position": `${isFixedOrSticky ? rootLayoutStyle.position : positionStyles.position}`,
2773
+ "--atomix-glass-position": `${isFixedOrSticky ? rootLayoutStyle.position : positionStyles.position}`,
2774
+ "--atomix-glass-top": `${isFixedOrSticky ? restStyle.top ?? 0 : 0}px`,
2775
+ "--atomix-glass-left": `${isFixedOrSticky ? restStyle.left ?? 0 : 0}px`,
2776
+ "--atomix-glass-right": isFixedOrSticky ? restStyle.right ?? "auto" : "auto",
2777
+ "--atomix-glass-bottom": isFixedOrSticky ? restStyle.bottom ?? "auto" : "auto",
2889
2778
  "--atomix-glass-width": adjustedSize.width,
2890
2779
  "--atomix-glass-height": adjustedSize.height,
2891
- "--atomix-glass-border-width": "var(--atomix-spacing-0-5, 0.09375rem)",
2780
+ "--atomix-glass-border-width": "var(--atomix-spacing-0-5, 0.125rem)",
2892
2781
  "--atomix-glass-blend-mode": isOverLight ? "multiply" : "overlay",
2893
- "--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%)`,
2894
- "--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%)`,
2782
+ "--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%)`,
2783
+ "--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%)`,
2895
2784
  "--atomix-glass-hover-1-opacity": opacityValues.hover1,
2896
2785
  "--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}%)`,
2897
2786
  "--atomix-glass-hover-2-opacity": opacityValues.hover2,
@@ -2905,13 +2794,14 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
2905
2794
  "--atomix-glass-overlay-highlight-opacity": opacityValues.over * ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.OPACITY_MULTIPLIER,
2906
2795
  "--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}%)`
2907
2796
  };
2908
- }), [ gradientValues, opacityValues, effectiveBorderRadius, transformStyle, adjustedSize, isOverLight, overLightConfig.borderOpacity, customZIndex, rootLayoutStyle, isFixedOrSticky ]), renderBackgroundLayer = layerType => jsx("div", {
2797
+ }), [ 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", {
2909
2798
  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(" ")
2910
2799
  });
2911
2800
  // Calculate position and size styles for internal layers
2912
2801
  // When root is fixed/sticky, internal layers use absolute (relative to root)
2913
2802
  return jsxs("div", {
2914
2803
  ...rest,
2804
+ ref: mergedRef,
2915
2805
  className: componentClassName,
2916
2806
  style: {
2917
2807
  ...glassVars
@@ -2921,17 +2811,14 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
2921
2811
  "aria-label": ariaLabel,
2922
2812
  "aria-describedby": ariaDescribedBy,
2923
2813
  "aria-disabled": !(!onClick || !effectiveWithoutEffects) || !onClick && void 0,
2924
- "aria-pressed": !(!onClick || !isActive) || !onClick && void 0,
2814
+ "aria-pressed": void 0,
2925
2815
  onKeyDown: onClick ? handleKeyDown : void 0,
2926
2816
  children: [ jsx(AtomixGlassContainer, {
2927
2817
  ref: glassRef,
2928
2818
  contentRef: contentRef,
2929
2819
  className: className,
2930
2820
  style: {
2931
- ...restStyle,
2932
- ...!isFixedOrSticky && {
2933
- position: "relative"
2934
- }
2821
+ ...restStyle
2935
2822
  },
2936
2823
  borderRadius: effectiveBorderRadius,
2937
2824
  displacementScale: effectiveWithoutEffects ? 0 : "shader" === mode ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_DISPLACEMENT : isOverLight ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.OVER_LIGHT_DISPLACEMENT : displacementScale,
@@ -2967,6 +2854,7 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
2967
2854
  effectiveReducedMotion: effectiveReducedMotion,
2968
2855
  shaderVariant: shaderVariant,
2969
2856
  withLiquidBlur: withLiquidBlur,
2857
+ isFixedOrSticky: isFixedOrSticky,
2970
2858
  // Phase 1: Animation System props
2971
2859
  shaderTime: getShaderTime(),
2972
2860
  withTimeAnimation: withTimeAnimation,
@@ -2995,6 +2883,8 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
2995
2883
  }) ]
2996
2884
  }), withBorder && jsxs(Fragment, {
2997
2885
  children: [ jsx("span", {
2886
+ className: ATOMIX_GLASS.BORDER_BACKDROP_CLASS
2887
+ }), jsx("span", {
2998
2888
  className: ATOMIX_GLASS.BORDER_1_CLASS
2999
2889
  }), jsx("span", {
3000
2890
  className: ATOMIX_GLASS.BORDER_2_CLASS
@@ -3005,11 +2895,18 @@ function AtomixGlass({children: children, displacementScale: displacementScale =
3005
2895
  onClose: () => {}
3006
2896
  }) ]
3007
2897
  });
3008
- }
2898
+ }));
3009
2899
 
3010
- // Adapted from https://github.com/shuding/liquid-glass
3011
- // Constants
3012
- const smoothStep = (a, b, t) => {
2900
+ /**
2901
+ * Balanced preset - Good quality with reasonable performance
2902
+ * Default preset for most mobile devices
2903
+ */ AtomixGlassInner.displayName = "AtomixGlass";
2904
+
2905
+ /**
2906
+ * AtomixGlass - wrapped with React.memo to prevent unnecessary re-renders.
2907
+ * Ref is forwarded to the root `<div>` element.
2908
+ */
2909
+ const AtomixGlass = memo(AtomixGlassInner), smoothStep = (a, b, t) => {
3013
2910
  // Add input validation
3014
2911
  if ("number" != typeof a || "number" != typeof b || "number" != typeof t) return 0;
3015
2912
  const clamped = Math.max(0, Math.min(1, (t - a) / (b - a)));
@@ -3304,6 +3201,8 @@ const smoothStep = (a, b, t) => {
3304
3201
  value: "Module"
3305
3202
  }));
3306
3203
 
3204
+ // Adapted from https://github.com/shuding/liquid-glass
3205
+ // Constants
3307
3206
  /**
3308
3207
  * Component Utilities
3309
3208
  *
@@ -3676,11 +3575,13 @@ const VideoPlayer = forwardRef((({src: src, type: type = "video", youtubeId: yo
3676
3575
  detectBorderRadius();
3677
3576
  // Create ResizeObserver to watch for style changes
3678
3577
  let resizeObserver = null;
3679
- return "undefined" != typeof ResizeObserver && containerRef.current && (resizeObserver = new ResizeObserver(detectBorderRadius),
3578
+ "undefined" != typeof ResizeObserver && containerRef.current && (resizeObserver = new ResizeObserver(detectBorderRadius),
3680
3579
  resizeObserver.observe(containerRef.current)),
3681
3580
  // Also listen for window resize (in case styles change)
3682
- window.addEventListener("resize", detectBorderRadius), () => {
3683
- window.removeEventListener("resize", detectBorderRadius), resizeObserver && containerRef.current && (resizeObserver.unobserve(containerRef.current),
3581
+ window.addEventListener("resize", detectBorderRadius);
3582
+ const currentContainer = containerRef.current;
3583
+ return () => {
3584
+ window.removeEventListener("resize", detectBorderRadius), resizeObserver && currentContainer && (resizeObserver.unobserve(currentContainer),
3684
3585
  resizeObserver.disconnect());
3685
3586
  };
3686
3587
  }), []);
@@ -4015,7 +3916,7 @@ VideoPlayer.displayName = "VideoPlayer";
4015
3916
  */
4016
3917
  const MasonryGrid = forwardRef((({children: children, className: className = "", xs: xs = 1, sm: sm, md: md, lg: lg, xl: xl, xxl: xxl, gap: gap = 16, animate: animate = !0, imagesLoaded: imagesLoaded = !0, onLayoutComplete: onLayoutComplete, onImageLoad: onImageLoad, ...props}, ref) => {
4017
3918
  // === REFS & STATE ===
4018
- const [columns, setColumns] = useState(xs), [positions, setPositions] = useState([]), [layoutComplete, setLayoutComplete] = useState(!1), [loadingImages, setLoadingImages] = useState(!1), containerRef = useRef(null), columnHeights = useRef([]), imagesLoadedCount = useRef(0), totalImagesCount = useRef(0), imageElements = useRef(new Map);
3919
+ const [columns, setColumns] = useState(xs), [positions, setPositions] = useState([]), [, setLayoutComplete] = useState(!1), [loadingImages, setLoadingImages] = useState(!1), containerRef = useRef(null), columnHeights = useRef([]), imagesLoadedCount = useRef(0), totalImagesCount = useRef(0), imageElements = useRef(new Map);
4019
3920
  useEffect((() => {
4020
3921
  setLoadingImages(!!imagesLoaded);
4021
3922
  }), [ columns, imagesLoaded ]),
@@ -4044,34 +3945,45 @@ const MasonryGrid = forwardRef((({children: children, className: className = ""
4044
3945
  });
4045
3946
  })), setItems(newItems);
4046
3947
  }), [ children ]);
4047
- // === TRACK & MANAGE IMAGES ===
4048
- const handleImageLoad = useCallback((img => {
4049
- if (!imageElements.current.get(img)) {
4050
- // Add loaded class for animation
4051
- if (imageElements.current.set(img, !0), imagesLoadedCount.current += 1, containerRef.current && imagesLoaded) {
4052
- const itemElement = img.closest(".o-masonry-grid > div");
4053
- itemElement && (itemElement.offsetHeight, itemElement.classList.add("o-masonry-grid__item-loaded"),
4054
- itemElement.classList.remove("o-masonry-grid__item-loading"));
3948
+ // === MANAGE ITEM LAYOUT ===
3949
+ const calculateLayout = useCallback((() => {
3950
+ if (!containerRef.current || 0 === items.length) return;
3951
+ const colWidth = (containerRef.current.offsetWidth - gap * (columns - 1)) / columns;
3952
+ columnHeights.current = Array(columns).fill(0);
3953
+ const newPositions = [];
3954
+ items.forEach(((item, index) => {
3955
+ if (item.ref.current) {
3956
+ // Find the shortest column
3957
+ const shortestCol = columnHeights.current.indexOf(Math.min(...columnHeights.current)), left = shortestCol * (colWidth + gap), top = columnHeights.current[shortestCol] ?? 0, height = item.ref.current.offsetHeight;
3958
+ columnHeights.current[shortestCol] = top + height + gap, newPositions[index] = {
3959
+ left: left,
3960
+ top: top,
3961
+ width: colWidth,
3962
+ height: height
3963
+ };
4055
3964
  }
4056
- // Ensure layout is recalculated after DOM paints the item image (prevents overlap on slow/late image loads)
4057
- requestAnimationFrame((() => {
4058
- requestAnimationFrame((() => {
4059
- calculateLayout();
4060
- }));
4061
- })), onImageLoad?.(imagesLoadedCount.current, totalImagesCount.current),
4062
- // If all images have loaded, update loading state and complete layout
4063
- imagesLoadedCount.current >= totalImagesCount.current && totalImagesCount.current > 0 && (setLayoutComplete(!0),
4064
- setLoadingImages(!1), // This ensures the loading class is removed *immediately* after images load
4065
- // Force a double requestAnimationFrame for final layout calculation after all images are loaded (guarantees DOM paint)
4066
- requestAnimationFrame((() => {
4067
- requestAnimationFrame((() => {
4068
- calculateLayout(),
4069
- // As a failsafe, if still present for some render lag, force another setLoadingImages(false)
4070
- setLoadingImages(!1);
4071
- }));
4072
- })), onLayoutComplete?.());
3965
+ })), setPositions(newPositions);
3966
+ }), [ items, columns, gap ]), handleImageLoad = useCallback((img => {
3967
+ if (imageElements.current.get(img)) return;
3968
+ // Add loaded class for animation
3969
+ if (imageElements.current.set(img, !0), imagesLoadedCount.current += 1, containerRef.current && imagesLoaded) {
3970
+ const itemElement = img.closest(".o-masonry-grid > div");
3971
+ itemElement && (itemElement.offsetHeight, itemElement.classList.add("o-masonry-grid__item-loaded"),
3972
+ itemElement.classList.remove("o-masonry-grid__item-loading"));
4073
3973
  }
4074
- }), [ onImageLoad, onLayoutComplete, imagesLoaded ]), trackImages = useCallback((() => {
3974
+ // Schedule layout recalculation after next paint to prevent overlap
3975
+ const scheduleLayoutUpdate = () => {
3976
+ const frameId = requestAnimationFrame((() => {
3977
+ onImageLoad?.(imagesLoadedCount.current, totalImagesCount.current), calculateLayout();
3978
+ }));
3979
+ return () => cancelAnimationFrame(frameId);
3980
+ }, cleanup = scheduleLayoutUpdate();
3981
+ // Clean up previous scheduled updates
3982
+ // If all images have loaded, update loading state and complete layout
3983
+ imagesLoadedCount.current >= totalImagesCount.current && totalImagesCount.current > 0 && (setLayoutComplete(!0),
3984
+ setLoadingImages(!1), setTimeout((() => cleanup()), 0), // Clean up after current execution
3985
+ scheduleLayoutUpdate(), onLayoutComplete?.());
3986
+ }), [ onImageLoad, onLayoutComplete, imagesLoaded, calculateLayout ]), trackImages = useCallback((() => {
4075
3987
  if (!imagesLoaded || !containerRef.current) return;
4076
3988
  imageElements.current.clear(), imagesLoadedCount.current = 0;
4077
3989
  const images = containerRef.current.querySelectorAll("img");
@@ -4090,30 +4002,21 @@ const MasonryGrid = forwardRef((({children: children, className: className = ""
4090
4002
  img.removeEventListener("error", masonryImg._masonryLoadHandler), delete masonryImg._masonryLoadHandler);
4091
4003
  }));
4092
4004
  });
4093
- }), [ imagesLoaded, handleImageLoad, onLayoutComplete ]), calculateLayout = useCallback((() => {
4094
- if (!containerRef.current || 0 === items.length) return;
4095
- const colWidth = (containerRef.current.offsetWidth - gap * (columns - 1)) / columns;
4096
- columnHeights.current = Array(columns).fill(0);
4097
- const newPositions = [];
4098
- items.forEach(((item, index) => {
4099
- if (item.ref.current) {
4100
- // Find the shortest column
4101
- const shortestCol = columnHeights.current.indexOf(Math.min(...columnHeights.current)), left = shortestCol * (colWidth + gap), top = columnHeights.current[shortestCol] ?? 0, height = item.ref.current.offsetHeight;
4102
- columnHeights.current[shortestCol] = top + height + gap, newPositions[index] = {
4103
- left: left,
4104
- top: top,
4105
- width: colWidth,
4106
- height: height
4107
- };
4108
- }
4109
- })), setPositions(newPositions);
4110
- }), [ items, columns, gap ]);
4111
- // === OBSERVE CONTAINER RESIZE ===
4005
+ }), [ imagesLoaded, handleImageLoad, onLayoutComplete ]);
4006
+ // === TRACK & MANAGE IMAGES ===
4007
+ // === OBSERVE CONTAINER RESIZE ===
4112
4008
  useEffect((() => {
4113
4009
  if (!containerRef.current) return;
4114
- let animationFrame = null;
4115
- const observer = new ResizeObserver((() => {
4116
- animationFrame && cancelAnimationFrame(animationFrame), animationFrame = requestAnimationFrame((() => calculateLayout()));
4010
+ let animationFrame = null, lastWidth = 0;
4011
+ const observer = new ResizeObserver((entries => {
4012
+ const entry = entries[0];
4013
+ if (!entry) return;
4014
+ const currentWidth = entry.contentRect.width;
4015
+ // Only recalculate if width actually changed (prevents excessive calculations)
4016
+ Math.abs(currentWidth - lastWidth) > 1 && (animationFrame && cancelAnimationFrame(animationFrame),
4017
+ animationFrame = requestAnimationFrame((() => {
4018
+ calculateLayout(), lastWidth = currentWidth;
4019
+ })));
4117
4020
  }));
4118
4021
  return observer.observe(containerRef.current), () => {
4119
4022
  observer.disconnect(), animationFrame && cancelAnimationFrame(animationFrame);
@@ -4124,24 +4027,21 @@ const MasonryGrid = forwardRef((({children: children, className: className = ""
4124
4027
  setLayoutComplete(!0), void setLoadingImages(!1))
4125
4028
  // Only reset layoutComplete when items or columns change
4126
4029
  ), [ items, columns, calculateLayout, imagesLoaded, trackImages ]),
4127
- // === NEW: Add ResizeObservers to all grid items for bulletproof image+content measurement ===
4030
+ // === ADD RESIZEOBSERVERS TO GRID ITEMS FOR DYNAMIC CONTENT MEASUREMENT ===
4128
4031
  React.useEffect((() => {
4129
- // Clean up old observers if items ever change
4130
4032
  const observers = [];
4033
+ let animationFrame = null;
4034
+ // Debounced layout calculation for item resize events
4035
+ const debouncedCalculateLayout = () => {
4036
+ animationFrame && cancelAnimationFrame(animationFrame), animationFrame = requestAnimationFrame(calculateLayout);
4037
+ };
4131
4038
  return items.forEach((item => {
4132
4039
  if (item.ref.current) {
4133
- const obs = new ResizeObserver((() => {
4134
- // Double rAF: ensures layout only runs after DOM/paint/async renders
4135
- requestAnimationFrame((() => {
4136
- requestAnimationFrame((() => {
4137
- calculateLayout();
4138
- }));
4139
- }));
4140
- }));
4040
+ const obs = new ResizeObserver(debouncedCalculateLayout);
4141
4041
  obs.observe(item.ref.current), observers.push(obs);
4142
4042
  }
4143
4043
  })), () => {
4144
- observers.forEach((obs => obs.disconnect()));
4044
+ observers.forEach((obs => obs.disconnect())), animationFrame && cancelAnimationFrame(animationFrame);
4145
4045
  };
4146
4046
  }), [ items, calculateLayout ]);
4147
4047
  // Ensure loadingImages state resets when items/columns/imagesLoaded change
@@ -4452,9 +4352,48 @@ class ThemeNaming {
4452
4352
  }
4453
4353
  }
4454
4354
 
4355
+ /**
4356
+ * Render a slot with the given props
4357
+ *
4358
+ * Priority order:
4359
+ * 1. render function
4360
+ * 2. component
4361
+ * 3. children
4362
+ * 4. fallback
4363
+ *
4364
+ * @example
4365
+ * renderSlot(
4366
+ * { render: (props) => <CustomButton {...props} /> },
4367
+ * { onClick: handleClick, children: 'Click me' }
4368
+ * )
4369
+ */
4370
+ function renderSlot(slot, props, fallback) {
4371
+ // No slot provided, use fallback
4372
+ if (!slot) return fallback;
4373
+ // Slot is a plain React node
4374
+ if ( React.isValidElement(slot) || "string" == typeof slot || "number" == typeof slot) return slot;
4375
+ // Slot is an object with rendering options
4376
+ if ("object" == typeof slot && null !== slot) {
4377
+ const slotObj = slot;
4378
+ // Priority 1: render function
4379
+ if (slotObj.render && "function" == typeof slotObj.render) return slotObj.render(props);
4380
+ // Priority 2: component
4381
+ if (slotObj.component) {
4382
+ const Component = slotObj.component;
4383
+ return jsx(Component, {
4384
+ ...props
4385
+ });
4386
+ }
4387
+ // Priority 3: children
4388
+ if (void 0 !== slotObj.children) return slotObj.children;
4389
+ }
4390
+ // Fallback
4391
+ return fallback;
4392
+ }
4393
+
4455
4394
  ThemeNaming.prefix = "atomix";
4456
4395
 
4457
- const Button = React.memo( forwardRef((({label: label, children: children, onClick: onClick, variant: variant = "primary", size: size = "md", disabled: disabled = !1, loading: loading = !1, loadingText: loadingText, icon: icon, iconName: iconName, iconSize: iconSize = "sm", iconPosition: iconPosition = "start", iconOnly: iconOnly = !1, rounded: rounded = !1, fullWidth: fullWidth = !1, block: block = !1, active: active = !1, selected: selected = !1, type: type = "button", className: className = "", as: Component = "button", href: href, target: target, glass: glass, onHover: onHover, onFocus: onFocus, onBlur: onBlur, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, "aria-expanded": ariaExpanded, "aria-controls": ariaControls, tabIndex: tabIndex, style: style, linkComponent: linkComponent, ...props}, ref) => {
4396
+ const Button = React.memo( forwardRef((({label: label, children: children, onClick: onClick, variant: variant = "primary", size: size = "md", disabled: disabled = !1, loading: loading = !1, loadingText: loadingText, icon: icon, iconName: iconName, iconSize: iconSize = "sm", iconPosition: iconPosition = "start", iconOnly: iconOnly = !1, rounded: rounded = !1, fullWidth: fullWidth = !1, block: block = !1, active: active = !1, selected: selected = !1, type: type = "button", className: className = "", as: Component = "button", href: href, target: target, glass: glass, onHover: onHover, onFocus: onFocus, onBlur: onBlur, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, "aria-expanded": ariaExpanded, "aria-controls": ariaControls, tabIndex: tabIndex, style: style, linkComponent: linkComponent, slots: slots, ...props}, ref) => {
4458
4397
  const isDisabled = disabled || loading, shouldRenderAsLink = Boolean(href), iconElement = iconName ? jsx(Icon, {
4459
4398
  name: iconName,
4460
4399
  size: iconSize
@@ -4470,17 +4409,28 @@ const Button = React.memo( forwardRef((({label: label, children: children, onCl
4470
4409
  children: [ loading && jsx("span", {
4471
4410
  className: ThemeNaming.bemClass("btn", "spinner"),
4472
4411
  "aria-hidden": "true",
4473
- children: jsx(Spinner, {
4412
+ children: renderSlot(slots?.spinner, {
4413
+ className: ThemeNaming.bemClass("btn", "spinner"),
4474
4414
  size: spinnerSize,
4475
4415
  variant: "link" === variant || "string" == typeof variant && variant.startsWith("outline-") ? "primary" : "danger" === variant ? "error" : variant
4476
- })
4416
+ }, jsx(Spinner, {
4417
+ size: spinnerSize,
4418
+ variant: "link" === variant || "string" == typeof variant && variant.startsWith("outline-") ? "primary" : "danger" === variant ? "error" : variant
4419
+ }))
4477
4420
  }), iconElement && !loading && jsx("span", {
4478
4421
  className: ThemeNaming.bemClass("btn", "icon"),
4479
4422
  "aria-hidden": "true",
4480
- children: iconElement
4423
+ children: renderSlot(slots?.icon, {
4424
+ className: ThemeNaming.bemClass("btn", "icon"),
4425
+ children: iconElement,
4426
+ size: iconSize
4427
+ }, iconElement)
4481
4428
  }), !iconOnly && buttonText && jsx("span", {
4482
4429
  className: ThemeNaming.bemClass("btn", "label"),
4483
- children: buttonText
4430
+ children: renderSlot(slots?.label, {
4431
+ className: ThemeNaming.bemClass("btn", "label"),
4432
+ children: buttonText
4433
+ }, buttonText)
4484
4434
  }) ]
4485
4435
  }), buttonProps = {
4486
4436
  className: buttonClass,
@@ -4497,48 +4447,59 @@ const Button = React.memo( forwardRef((({label: label, children: children, onCl
4497
4447
  tabIndex: void 0 !== tabIndex ? tabIndex : isDisabled ? -1 : 0,
4498
4448
  style: style,
4499
4449
  ...props
4500
- };
4501
- // Determine if we should render as a link
4502
- // If disabled, we still check href, but we might want to render as button or anchor with aria-disabled
4503
- // The previous logic was Boolean(href && !isDisabled). This meant if disabled, it renders as <button>.
4504
- // This is a safe fallback for disabled links.
4505
- let content;
4506
- // Render as anchor if href is provided
4507
- if (shouldRenderAsLink)
4508
- // Use custom linkComponent if provided (e.g., Next.js Link)
4509
- if (linkComponent) {
4510
- const LinkComp = linkComponent, linkProps = {
4450
+ }, buttonChildren = renderSlot(slots?.root, {
4451
+ className: buttonClass,
4452
+ children: buttonContent,
4453
+ disabled: isDisabled,
4454
+ loading: loading,
4455
+ onClick: handleClickEvent,
4456
+ type: type,
4457
+ "aria-label": safeAriaLabel,
4458
+ "aria-disabled": isDisabled,
4459
+ "aria-busy": loading
4460
+ }, (() => {
4461
+ // Render as anchor if href is provided
4462
+ if (shouldRenderAsLink) {
4463
+ // Use custom linkComponent if provided (e.g., Next.js Link)
4464
+ if (linkComponent) {
4465
+ const LinkComp = linkComponent, linkProps = {
4466
+ ...buttonProps,
4467
+ ref: ref,
4468
+ // linkComponent usually forwards ref to anchor
4469
+ href: isDisabled ? void 0 : href,
4470
+ to: isDisabled ? void 0 : href,
4471
+ target: target,
4472
+ rel: "_blank" === target ? "noopener noreferrer" : void 0
4473
+ };
4474
+ return jsx(LinkComp, {
4475
+ ...linkProps,
4476
+ children: buttonContent
4477
+ });
4478
+ }
4479
+ // Fallback to regular anchor tag
4480
+ return jsx("a", {
4481
+ ...buttonProps,
4482
+ ref: ref,
4483
+ href: isDisabled ? void 0 : href,
4484
+ target: target,
4485
+ rel: "_blank" === target ? "noopener noreferrer" : void 0,
4486
+ children: buttonContent
4487
+ });
4488
+ }
4489
+ // Default button rendering
4490
+ return jsx(Component, {
4511
4491
  ...buttonProps,
4512
4492
  ref: ref,
4513
- // linkComponent usually forwards ref to anchor
4514
- href: isDisabled ? void 0 : href,
4515
- to: isDisabled ? void 0 : href,
4516
- target: target,
4517
- rel: "_blank" === target ? "noopener noreferrer" : void 0
4518
- };
4519
- content = jsx(LinkComp, {
4520
- ...linkProps,
4493
+ type: "button" === Component ? type : void 0,
4494
+ disabled: isDisabled,
4521
4495
  children: buttonContent
4522
4496
  });
4523
- } else
4524
- // Fallback to regular anchor tag
4525
- content = jsx("a", {
4526
- ...buttonProps,
4527
- ref: ref,
4528
- href: isDisabled ? void 0 : href,
4529
- target: target,
4530
- rel: "_blank" === target ? "noopener noreferrer" : void 0,
4531
- children: buttonContent
4532
- }); else
4533
- // Default button rendering
4534
- content = jsx(Component, {
4535
- ...buttonProps,
4536
- ref: ref,
4537
- type: "button" === Component ? type : void 0,
4538
- disabled: isDisabled,
4539
- children: buttonContent
4540
- });
4541
- if (glass) {
4497
+ })());
4498
+ // Determine if we should render as a link
4499
+ // If disabled, we still check href, but we might want to render as button or anchor with aria-disabled
4500
+ // The previous logic was Boolean(href && !isDisabled). This meant if disabled, it renders as <button>.
4501
+ // This is a safe fallback for disabled links.
4502
+ if (glass) {
4542
4503
  // Default glass props
4543
4504
  const defaultGlassProps = {
4544
4505
  displacementScale: 20,
@@ -4551,10 +4512,10 @@ const Button = React.memo( forwardRef((({label: label, children: children, onCl
4551
4512
  };
4552
4513
  return jsx(AtomixGlass, {
4553
4514
  ...glassProps,
4554
- children: content
4515
+ children: buttonChildren
4555
4516
  });
4556
4517
  }
4557
- return content;
4518
+ return buttonChildren;
4558
4519
  })));
4559
4520
 
4560
4521
  Button.displayName = "Button";
@@ -5093,7 +5054,7 @@ useEffect((() => {
5093
5054
  }
5094
5055
  };
5095
5056
  }));
5096
- }), [ isMounted, currentIndex, calculateBounds, constrainPosition ]), setImagePosition = useCallback((position => {
5057
+ }), [ currentIndex, calculateBounds, constrainPosition ]), setImagePosition = useCallback((position => {
5097
5058
  setImageStates((prev => {
5098
5059
  const currentState = prev[currentIndex] || {
5099
5060
  zoomLevel: 1,
@@ -5143,7 +5104,7 @@ useEffect((() => {
5143
5104
  }
5144
5105
  };
5145
5106
  }));
5146
- }), [ isMounted, currentIndex, calculateBounds, constrainPosition ]), handleWheel = useCallback((event => {
5107
+ }), [ currentIndex, calculateBounds, constrainPosition ]), handleWheel = useCallback((event => {
5147
5108
  var _context;
5148
5109
  if (!isMounted || !event || !event.currentTarget) return;
5149
5110
  // Additional safety check for the target element
@@ -5500,7 +5461,7 @@ useEffect((() => {
5500
5461
  }
5501
5462
  return prev;
5502
5463
  }));
5503
- }), [ isMounted, enableGestures, isDragging, startDragPosition, currentIndex, constrainPosition, calculateBounds ]), handleTouchEnd = useCallback((() => {
5464
+ }), [ enableGestures, isDragging, startDragPosition, currentIndex, constrainPosition, calculateBounds ]), handleTouchEnd = useCallback((() => {
5504
5465
  setIsDragging(!1), lastDistanceRef.current = null, lastMidpointRef.current = null;
5505
5466
  }), []), currentState = imageStates[currentIndex] || {
5506
5467
  zoomLevel: 1,