@shohojdhara/atomix 0.6.4 → 0.6.6

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 (83) hide show
  1. package/dist/atomix.css +117 -38
  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/atomix.umd.js +1 -1
  6. package/dist/atomix.umd.js.map +1 -1
  7. package/dist/atomix.umd.min.js +1 -1
  8. package/dist/charts.d.ts +30 -1
  9. package/dist/charts.js +625 -846
  10. package/dist/charts.js.map +1 -1
  11. package/dist/core.d.ts +30 -1
  12. package/dist/core.js +659 -873
  13. package/dist/core.js.map +1 -1
  14. package/dist/forms.d.ts +30 -1
  15. package/dist/forms.js +1171 -1402
  16. package/dist/forms.js.map +1 -1
  17. package/dist/heavy.d.ts +31 -89
  18. package/dist/heavy.js +975 -1195
  19. package/dist/heavy.js.map +1 -1
  20. package/dist/index.d.ts +383 -140
  21. package/dist/index.esm.js +1567 -1679
  22. package/dist/index.esm.js.map +1 -1
  23. package/dist/index.js +1556 -1667
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.min.js +1 -1
  26. package/dist/index.min.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/components/Accordion/Accordion.tsx +2 -5
  29. package/src/components/AtomixGlass/AtomixGlass.test.tsx +14 -16
  30. package/src/components/AtomixGlass/AtomixGlass.tsx +137 -364
  31. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +32 -251
  32. package/src/components/AtomixGlass/GlassFilter.tsx +62 -68
  33. package/src/components/AtomixGlass/README.md +2 -1
  34. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +19 -18
  35. package/src/components/AtomixGlass/glass-border-styles.test.ts +58 -0
  36. package/src/components/AtomixGlass/glass-border-styles.ts +136 -0
  37. package/src/components/AtomixGlass/glass-utils.ts +456 -22
  38. package/src/components/AtomixGlass/shader-utils.ts +19 -77
  39. package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +158 -537
  40. package/src/components/AtomixGlass/stories/Border.stories.tsx +149 -0
  41. package/src/components/AtomixGlass/stories/Examples.stories.tsx +229 -89
  42. package/src/components/AtomixGlass/stories/Playground.stories.tsx +29 -340
  43. package/src/components/AtomixGlass/stories/argTypes.ts +30 -13
  44. package/src/components/AtomixGlass/stories/premium-presets.ts +206 -0
  45. package/src/components/AtomixGlass/stories/shared-components.tsx +52 -8
  46. package/src/components/Badge/Badge.tsx +4 -4
  47. package/src/components/Button/Button.tsx +2 -6
  48. package/src/components/Callout/Callout.test.tsx +4 -3
  49. package/src/components/Callout/Callout.tsx +2 -5
  50. package/src/components/Dropdown/Dropdown.tsx +3 -7
  51. package/src/components/Form/Checkbox.tsx +2 -8
  52. package/src/components/Form/Input.tsx +2 -9
  53. package/src/components/Form/Radio.tsx +2 -9
  54. package/src/components/Form/Select.test.tsx +6 -6
  55. package/src/components/Form/Select.tsx +2 -7
  56. package/src/components/Form/Textarea.stories.tsx +5 -5
  57. package/src/components/Form/Textarea.tsx +2 -9
  58. package/src/components/Messages/Messages.tsx +2 -8
  59. package/src/components/Modal/Modal.tsx +4 -5
  60. package/src/components/Navigation/Nav/Nav.tsx +2 -6
  61. package/src/components/Navigation/Navbar/Navbar.tsx +2 -9
  62. package/src/components/Navigation/SideMenu/SideMenu.tsx +2 -6
  63. package/src/components/Pagination/Pagination.tsx +2 -10
  64. package/src/components/Popover/Popover.tsx +2 -9
  65. package/src/components/Progress/Progress.tsx +2 -7
  66. package/src/components/Rating/Rating.tsx +2 -10
  67. package/src/components/Spinner/Spinner.tsx +2 -7
  68. package/src/components/Steps/Steps.tsx +2 -10
  69. package/src/components/Tabs/Tabs.tsx +2 -9
  70. package/src/components/Toggle/Toggle.tsx +2 -10
  71. package/src/components/Tooltip/Tooltip.tsx +2 -5
  72. package/src/lib/composables/useAtomixGlass.ts +42 -143
  73. package/src/lib/composables/useAtomixGlassStyles.ts +61 -77
  74. package/src/lib/composables/usePerformanceMonitor.ts +5 -66
  75. package/src/lib/constants/components.ts +363 -46
  76. package/src/lib/types/components.ts +33 -1
  77. package/src/styles/01-settings/_settings.atomix-glass.scss +66 -28
  78. package/src/styles/02-tools/_tools.button.scss +51 -42
  79. package/src/styles/02-tools/_tools.glass.scss +45 -3
  80. package/src/styles/06-components/_components.atomix-glass.scss +116 -79
  81. package/src/components/AtomixGlass/PerformanceDashboard.tsx +0 -171
  82. package/src/components/AtomixGlass/animation-system.ts +0 -578
  83. package/src/components/AtomixGlass/deprecated/AtomixGlass.deprecated.tsx +0 -390
@@ -1,12 +1,19 @@
1
1
  import React, { forwardRef, memo, useMemo, useRef } from 'react';
2
2
  import type { AtomixGlassProps } from '../../lib/types/components';
3
3
  import { ATOMIX_GLASS } from '../../lib/constants/components';
4
+ import { mergeClassNames } from '../../lib/utils/componentUtils';
5
+ import useForkRef from '../../lib/utils/useForkRef';
4
6
  import { AtomixGlassContainer } from './AtomixGlassContainer';
7
+ import {
8
+ buildGlassRootCssVariables,
9
+ getGlassInternalPositionStyles,
10
+ isGlassFixedOrSticky,
11
+ resolveGlassAdjustedSize,
12
+ resolveGlassContainerEffects,
13
+ } from './glass-utils';
5
14
  import { useAtomixGlass } from '../../lib/composables/useAtomixGlass';
6
- // Phase 3: Optimization & Adaptation
7
15
  import { useResponsiveGlass } from '../../lib/composables/useResponsiveGlass';
8
16
  import { usePerformanceMonitor } from '../../lib/composables/usePerformanceMonitor';
9
- import { PerformanceDashboard } from './PerformanceDashboard';
10
17
  import {
11
18
  getDevicePreset,
12
19
  MOBILE_OPTIMIZED_BREAKPOINTS,
@@ -35,6 +42,10 @@ import {
35
42
  * - Implements focus-ring mixin for accessibility
36
43
  * - Supports reduced motion and high contrast preferences
37
44
  *
45
+ * Style architecture:
46
+ * - Root (`.c-atomix-glass`): CSS custom properties for layer geometry and motion.
47
+ * - Container (`.c-atomix-glass__container`): layout, z-index, and backdrop-filter.
48
+ *
38
49
  * @example
39
50
  * // Basic usage with dynamic border-radius extraction
40
51
  * <AtomixGlass>
@@ -92,29 +103,7 @@ import {
92
103
  * </AtomixGlass>
93
104
  */
94
105
 
95
- // ─── Type guard for the dev-only performance flag ────────────────────────────
96
- declare global {
97
- interface Window {
98
- enablePerformanceMonitoring?: boolean;
99
- }
100
- }
101
-
102
- // Helper to merge refs
103
- function mergeRefs<T = any>(
104
- ...refs: (React.MutableRefObject<T> | React.LegacyRef<T> | undefined | null)[]
105
- ) {
106
- return (node: T) => {
107
- refs.forEach(ref => {
108
- if (typeof ref === 'function') {
109
- ref(node);
110
- } else if (ref != null) {
111
- (ref as React.MutableRefObject<T | null>).current = node;
112
- }
113
- });
114
- };
115
- }
116
-
117
- // Internal implementation with forwardRef
106
+ /** Internal implementation; ref is forwarded to the root wrapper element. */
118
107
  const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function AtomixGlass(
119
108
  {
120
109
  children,
@@ -128,7 +117,7 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
128
117
  mouseOffset: externalMouseOffset,
129
118
  mouseContainer = null,
130
119
  className = '',
131
- padding = ATOMIX_GLASS.DEFAULTS.PADDING,
120
+
132
121
  overLight = ATOMIX_GLASS.DEFAULTS.OVER_LIGHT,
133
122
  style = {},
134
123
  mode = ATOMIX_GLASS.DEFAULTS.MODE,
@@ -142,7 +131,9 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
142
131
  highContrast = false,
143
132
  withoutEffects = false,
144
133
  withLiquidBlur = false,
134
+ border,
145
135
  withBorder = true,
136
+ debugBorderRadius = false,
146
137
  withOverLightLayers = ATOMIX_GLASS.DEFAULTS.ENABLE_OVER_LIGHT_LAYERS,
147
138
  debugPerformance = false,
148
139
  debugOverLight = false,
@@ -165,21 +156,18 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
165
156
  const glassRef = useRef<HTMLDivElement>(null);
166
157
  const contentRef = useRef<HTMLDivElement>(null);
167
158
  const internalWrapperRef = useRef<HTMLDivElement>(null);
168
- const mergedRef = useMemo(() => mergeRefs(ref, internalWrapperRef), [ref]);
169
-
170
- // ── Layout hoisting ──────────────────────────────────────────────────────
171
- // When position is fixed/sticky the layout props must live on the ROOT
172
- // `.c-atomix-glass` element so that every decorative layer (borders,
173
- // backgrounds, hover effects) stays in the same stacking context.
174
-
175
- // Extract zIndex from style so it becomes the base for ALL internal
176
- // layers via --atomix-glass-base-z-index. It must NOT be applied as a
177
- // real z-index on the root element — that would break the glass effect.
159
+ const mergedRef = useForkRef(ref, internalWrapperRef);
160
+
161
+ /**
162
+ * Style partitioning for backdrop-filter compatibility.
163
+ * - Root (`.c-atomix-glass`): CSS custom properties only (`glassVars`).
164
+ * - Container (`.c-atomix-glass__container`): layout, stacking, and visual styles.
165
+ * Backdrop sampling occurs on `__filter-overlay` inside the container; layout
166
+ * properties must not be applied to the root or the effect will not render correctly.
167
+ */
178
168
  const { zIndex: customZIndex, ...restStyle } = style;
179
- const isFixedOrSticky =
180
- propsIsFixedOrSticky || restStyle.position === 'fixed' || restStyle.position === 'sticky';
169
+ const isFixedOrSticky = isGlassFixedOrSticky(propsIsFixedOrSticky, restStyle.position);
181
170
 
182
- // Use composable hook for all state and logic
183
171
  const {
184
172
  isHovered,
185
173
  isActive,
@@ -192,12 +180,12 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
192
180
  globalMousePosition,
193
181
  mouseOffset,
194
182
  transformStyle,
195
- getShaderTime,
196
183
  handleMouseEnter,
197
184
  handleMouseLeave,
198
185
  handleMouseDown,
199
186
  handleMouseUp,
200
187
  handleKeyDown,
188
+ resolvedBorder,
201
189
  } = useAtomixGlass({
202
190
  glassRef,
203
191
  contentRef,
@@ -218,29 +206,19 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
218
206
  blurAmount,
219
207
  saturation,
220
208
  withLiquidBlur,
221
- padding,
209
+ border,
210
+ withBorder,
211
+ debugBorderRadius,
212
+
222
213
  style,
223
214
  isFixedOrSticky,
224
- // Phase 1: Animation System props
225
- withTimeAnimation,
226
- animationSpeed,
227
- withMultiLayerDistortion,
228
- distortionOctaves,
229
- distortionLacunarity,
230
- distortionGain,
231
- distortionQuality,
232
215
  });
233
216
 
234
- // ============================================================================
235
- // Phase 3: Optimization & Adaptation Systems
236
- // ============================================================================
237
217
 
238
- // Get device preset parameters - memoized to prevent recalculation
239
218
  const devicePresetParams = useMemo(() => {
240
219
  return getDevicePreset(devicePreset);
241
220
  }, [devicePreset]);
242
221
 
243
- // Responsive breakpoint system - automatically adjusts parameters based on viewport
244
222
  useResponsiveGlass({
245
223
  baseParams: {
246
224
  ...devicePresetParams,
@@ -259,309 +237,142 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
259
237
  debug: false,
260
238
  });
261
239
 
262
- // Performance monitoring - tracks FPS, frame time, memory usage
263
- const { metrics: performanceMetrics, toggleMonitoring } = usePerformanceMonitor({
264
- enabled: debugPerformance, // Enable when debugPerformance is true
240
+ usePerformanceMonitor({
241
+ enabled: debugPerformance,
265
242
  debug: false,
266
243
  showOverlay: false,
267
244
  });
268
245
 
269
- // Auto-start performance monitoring when debugPerformance is enabled
270
- React.useEffect(() => {
271
- if (debugPerformance) {
272
- toggleMonitoring();
273
- }
274
- // eslint-disable-next-line react-hooks/exhaustive-deps
275
- }, [debugPerformance]); // Re-run when debugPerformance changes
276
-
277
246
  const isOverLight = useMemo(() => overLightConfig.isOverLight, [overLightConfig.isOverLight]);
278
247
 
279
248
  const shouldRenderOverLightLayers = withOverLightLayers && isOverLight;
280
249
 
281
- const rootLayoutStyle = useMemo<React.CSSProperties>(() => {
282
- if (!isFixedOrSticky) return {};
283
- const { position: p, top: t, left: l, right: r, bottom: b } = restStyle;
284
- return {
285
- ...(p && { position: p }),
286
- ...(t !== undefined && { top: t }),
287
- ...(l !== undefined && { left: l }),
288
- ...(r !== undefined && { right: r }),
289
- ...(b !== undefined && { bottom: b }),
290
- };
291
- }, [isFixedOrSticky, restStyle]);
292
-
293
- // Calculate base style with transforms
294
- // When layout is hoisted to the root, strip those props from the container
295
- const baseStyle = useMemo(() => {
296
- if (isFixedOrSticky) {
297
- const { position: _p, top: _t, left: _l, right: _r, bottom: _b, ...visualStyle } = restStyle;
298
- return {
299
- ...visualStyle,
300
- };
301
- }
302
- return {
250
+ const containerStyle = useMemo(
251
+ () => ({
303
252
  ...restStyle,
304
- };
305
- }, [isFixedOrSticky, restStyle]);
253
+ ...(customZIndex !== undefined && { zIndex: customZIndex }),
254
+ }),
255
+ [restStyle, customZIndex]
256
+ );
306
257
 
307
- // Build className with state modifiers
308
- const componentClassName = [
258
+ const componentClassName = mergeClassNames(
309
259
  ATOMIX_GLASS.BASE_CLASS,
310
260
  effectiveReducedMotion && `${ATOMIX_GLASS.BASE_CLASS}--reduced-motion`,
311
261
  effectiveHighContrast && `${ATOMIX_GLASS.BASE_CLASS}--high-contrast`,
312
262
  effectiveWithoutEffects && `${ATOMIX_GLASS.BASE_CLASS}--disabled-effects`,
313
- className,
314
- ]
315
- .filter(Boolean)
316
- .join(' ');
263
+ className
264
+ );
317
265
 
318
- // Calculate position and size styles for internal layers
319
- // When root is fixed/sticky, internal layers use absolute (relative to root)
320
266
  const positionStyles = useMemo(
321
- () => ({
322
- position: (isFixedOrSticky
323
- ? 'absolute'
324
- : restStyle.position || 'absolute') as React.CSSProperties['position'],
325
- top: !isFixedOrSticky ? 0 : (restStyle.top ?? 0),
326
- left: !isFixedOrSticky ? 0 : (restStyle.left ?? 0),
327
- right: !isFixedOrSticky ? 'auto' : (restStyle.right ?? 'auto'),
328
- bottom: !isFixedOrSticky ? 'auto' : (restStyle.bottom ?? 'auto'),
329
- }),
267
+ () => getGlassInternalPositionStyles(isFixedOrSticky, restStyle),
268
+ [isFixedOrSticky, restStyle]
269
+ );
270
+
271
+ const adjustedSize = useMemo(
272
+ () =>
273
+ resolveGlassAdjustedSize({
274
+ width,
275
+ height,
276
+ restStyle,
277
+ glassSize,
278
+ isFixedOrSticky,
279
+ }),
280
+ [width, height, restStyle, glassSize, isFixedOrSticky]
281
+ );
282
+
283
+ const glassVars = useMemo(
284
+ () =>
285
+ buildGlassRootCssVariables({
286
+ effectiveBorderRadius,
287
+ transformStyle,
288
+ adjustedSize,
289
+ isOverLight,
290
+ customZIndex,
291
+ isFixedOrSticky,
292
+ positionStyles,
293
+ restStyle,
294
+ borderWidth: resolvedBorder.width,
295
+ }),
330
296
  [
297
+ effectiveBorderRadius,
298
+ transformStyle,
299
+ adjustedSize,
300
+ isOverLight,
301
+ customZIndex,
331
302
  isFixedOrSticky,
332
- restStyle.position,
333
- restStyle.top,
334
- restStyle.left,
335
- restStyle.right,
336
- restStyle.bottom,
303
+ positionStyles,
304
+ restStyle,
305
+ resolvedBorder.width,
337
306
  ]
338
307
  );
339
308
 
340
- const adjustedSize = useMemo(() => {
341
- // Keep a reference to positionStyles to avoid unused-variable lint,
342
- // but sizing is driven by explicit width/height or measured size.
343
- const _position = positionStyles.position;
344
-
345
- const resolveLength = (value: string | number | undefined, measured: number): string => {
346
- if (value !== undefined && isFixedOrSticky) {
347
- return typeof value === 'number' ? `${value}px` : value;
348
- }
349
-
350
- if (measured > 0 && isFixedOrSticky) {
351
- return `${measured}px`;
352
- }
353
-
354
- return '100%';
355
- };
356
-
357
- const effectiveWidth = width ?? restStyle.width;
358
- const effectiveHeight = height ?? restStyle.height;
359
-
360
- return {
361
- width: resolveLength(effectiveWidth, glassSize.width),
362
- height: resolveLength(effectiveHeight, glassSize.height),
363
- };
364
- }, [
365
- width,
366
- height,
367
- restStyle.width,
368
- restStyle.height,
369
- positionStyles.position,
370
- glassSize.width,
371
- glassSize.height,
372
- isFixedOrSticky,
373
- ]);
374
-
375
- // Memoize expensive gradient calculations
376
- const gradientValues = useMemo(() => {
377
- const mx = mouseOffset.x;
378
- const my = mouseOffset.y;
379
- const absMx = Math.abs(mx);
380
- const absMy = Math.abs(my);
381
- const GRADIENT = ATOMIX_GLASS.CONSTANTS.GRADIENT;
382
-
383
- return {
384
- borderGradientAngle: GRADIENT.BASE_ANGLE + mx * GRADIENT.ANGLE_MULTIPLIER,
385
- borderStop1: Math.max(
386
- GRADIENT.BORDER_STOP_1.MIN,
387
- GRADIENT.BORDER_STOP_1.BASE + my * GRADIENT.BORDER_STOP_1.MULTIPLIER
388
- ),
389
- borderStop2: Math.min(
390
- GRADIENT.BORDER_STOP_2.MAX,
391
- GRADIENT.BORDER_STOP_2.BASE + my * GRADIENT.BORDER_STOP_2.MULTIPLIER
392
- ),
393
- borderOpacities: [
394
- GRADIENT.BORDER_OPACITY.BASE_1 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW,
395
- GRADIENT.BORDER_OPACITY.BASE_2 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH,
396
- GRADIENT.BORDER_OPACITY.BASE_3 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW,
397
- GRADIENT.BORDER_OPACITY.BASE_4 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH,
398
- ],
399
- hoverPositions: {
400
- hover1: {
401
- x: GRADIENT.CENTER_POSITION + mx / GRADIENT.HOVER_POSITION.DIVISOR_1,
402
- y: GRADIENT.CENTER_POSITION + my / GRADIENT.HOVER_POSITION.DIVISOR_1,
403
- },
404
- hover2: {
405
- x: GRADIENT.CENTER_POSITION + mx / GRADIENT.HOVER_POSITION.DIVISOR_2,
406
- y: GRADIENT.CENTER_POSITION + my / GRADIENT.HOVER_POSITION.DIVISOR_2,
407
- },
408
- hover3: {
409
- x: GRADIENT.CENTER_POSITION + mx * GRADIENT.HOVER_POSITION.MULTIPLIER_3,
410
- y: GRADIENT.CENTER_POSITION + my * GRADIENT.HOVER_POSITION.MULTIPLIER_3,
411
- },
412
- },
413
- basePosition: {
414
- x: GRADIENT.CENTER_POSITION + mx * GRADIENT.BASE_LAYER_MULTIPLIER,
415
- y: GRADIENT.CENTER_POSITION + my * GRADIENT.BASE_LAYER_MULTIPLIER,
416
- },
417
- mx,
418
- my,
419
- absMx,
420
- absMy,
421
- };
422
- }, [mouseOffset.x, mouseOffset.y]);
423
-
424
- // Clamp overLight opacities to [0,1]
425
- const clampedOverLightOpacity = Math.max(0, Math.min(1, overLightConfig?.opacity ?? 0.4));
426
- const clampedBorderOpacity = Math.max(0, Math.min(1, overLightConfig?.borderOpacity ?? 1));
427
-
428
- // Memoize opacity calculations
429
- const opacityValues = useMemo(() => {
430
- // Use clamped opacity value here
431
- const overLightOpacity = clampedOverLightOpacity;
432
- const BASE_OVER_LIGHT_OPACITY = 0.4;
433
- const OVER_OPACITY_MULTIPLIER = 1.1;
434
-
435
- return {
436
- hover1: isHovered || isActive ? 0.5 : 0,
437
- hover2: isActive ? 0.5 : 0,
438
- hover3: isHovered ? 0.4 : isActive ? 0.8 : 0,
439
- base: isOverLight ? overLightOpacity || BASE_OVER_LIGHT_OPACITY : 0,
440
- over: isOverLight
441
- ? (overLightOpacity || BASE_OVER_LIGHT_OPACITY) * OVER_OPACITY_MULTIPLIER
442
- : 0,
443
- };
444
- }, [isHovered, isActive, isOverLight, clampedOverLightOpacity]);
445
-
446
- // Memoize CSS variables object
447
- const glassVars = useMemo(() => {
448
- const whiteColor = ATOMIX_GLASS.CONSTANTS.PALETTE.WHITE;
449
- const blackColor = ATOMIX_GLASS.CONSTANTS.PALETTE.BLACK;
450
- const {
451
- borderGradientAngle,
452
- borderStop1,
453
- borderStop2,
454
- borderOpacities,
455
- hoverPositions,
456
- basePosition,
457
- mx,
458
- my,
459
- absMx,
460
- absMy,
461
- } = gradientValues;
462
-
463
- return {
464
- ...(customZIndex !== undefined && { '--atomix-glass-base-z-index': customZIndex }),
465
- '--atomix-glass-radius': `${effectiveBorderRadius}px`,
466
- '--atomix-glass-transform': transformStyle || 'none',
467
- '--atomix-glass-container-position': `${!isFixedOrSticky ? positionStyles.position : rootLayoutStyle.position}`,
468
- '--atomix-glass-position': `${!isFixedOrSticky ? positionStyles.position : rootLayoutStyle.position}`,
469
- '--atomix-glass-top': `${!isFixedOrSticky ? 0 : (restStyle.top ?? 0)}px`,
470
- '--atomix-glass-left': `${!isFixedOrSticky ? 0 : (restStyle.left ?? 0)}px`,
471
- '--atomix-glass-right': !isFixedOrSticky ? 'auto' : (restStyle.right ?? 'auto'),
472
- '--atomix-glass-bottom': !isFixedOrSticky ? 'auto' : (restStyle.bottom ?? 'auto'),
473
- '--atomix-glass-width': adjustedSize.width,
474
- '--atomix-glass-height': adjustedSize.height,
475
- '--atomix-glass-border-width': 'var(--atomix-spacing-0-5, 0.125rem)',
476
- '--atomix-glass-blend-mode': isOverLight ? 'multiply' : 'overlay',
477
- '--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%)`,
478
- '--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%)`,
479
- '--atomix-glass-hover-1-opacity': opacityValues.hover1,
480
- '--atomix-glass-hover-1-gradient': isOverLight
481
- ? `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}%)`
482
- : `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}%)`,
483
- '--atomix-glass-hover-2-opacity': opacityValues.hover2,
484
- '--atomix-glass-hover-2-gradient': isOverLight
485
- ? `radial-gradient(circle at ${hoverPositions.hover2.x}% ${hoverPositions.hover2.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_START}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_STOP}%, rgba(${blackColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_END}%)`
486
- : `radial-gradient(circle at ${hoverPositions.hover2.x}% ${hoverPositions.hover2.y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.WHITE_STOP}%)`,
487
- '--atomix-glass-hover-3-opacity': opacityValues.hover3,
488
- '--atomix-glass-hover-3-gradient': isOverLight
489
- ? `radial-gradient(circle at ${hoverPositions.hover3.x}% ${hoverPositions.hover3.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.BLACK_START}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.BLACK_STOP}%, rgba(${blackColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.BLACK_END}%)`
490
- : `radial-gradient(circle at ${hoverPositions.hover3.x}% ${hoverPositions.hover3.y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.WHITE_STOP}%)`,
491
- '--atomix-glass-base-opacity': opacityValues.base,
492
- '--atomix-glass-base-gradient': isOverLight
493
- ? `linear-gradient(${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.ANGLE}deg, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_START_BASE + mx * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_START_MULTIPLIER}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_MID_BASE + my * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_MID_MULTIPLIER}) ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_MID_STOP}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_END_BASE + absMx * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_END_MULTIPLIER}) 100%)`
494
- : `rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.WHITE_OPACITY})`,
495
- '--atomix-glass-overlay-opacity': opacityValues.over,
496
- '--atomix-glass-overlay-gradient': isOverLight
497
- ? `radial-gradient(circle at ${basePosition.x}% ${basePosition.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_START_BASE + absMx * ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_START_MULTIPLIER}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_MID_STOP}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_END_BASE + absMy * ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_END_MULTIPLIER}) 100%)`
498
- : `rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.WHITE_OPACITY})`,
499
- '--atomix-glass-overlay-highlight-opacity':
500
- opacityValues.over * ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.OPACITY_MULTIPLIER,
501
- '--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}%)`,
502
- } as React.CSSProperties;
503
- }, [
504
- gradientValues,
505
- opacityValues,
506
- effectiveBorderRadius,
507
- transformStyle,
508
- adjustedSize,
509
- isOverLight,
510
- clampedBorderOpacity,
511
- customZIndex,
512
- isFixedOrSticky,
513
- positionStyles.position,
514
- rootLayoutStyle.position,
515
- restStyle.top,
516
- restStyle.left,
517
- restStyle.right,
518
- restStyle.bottom,
519
- ]);
520
-
521
- // ─── Render helpers ──────────────────────────────────────────────────────
309
+ const containerEffects = useMemo(
310
+ () =>
311
+ resolveGlassContainerEffects({
312
+ displacementScale,
313
+ blurAmount,
314
+ saturation,
315
+ aberrationIntensity,
316
+ mode,
317
+ effectiveWithoutEffects,
318
+ effectiveHighContrast,
319
+ isOverLight,
320
+ saturationBoost: overLightConfig.saturationBoost,
321
+ mouseOffset,
322
+ globalMousePosition,
323
+ }),
324
+ [
325
+ displacementScale,
326
+ blurAmount,
327
+ saturation,
328
+ aberrationIntensity,
329
+ mode,
330
+ effectiveWithoutEffects,
331
+ effectiveHighContrast,
332
+ isOverLight,
333
+ overLightConfig.saturationBoost,
334
+ mouseOffset,
335
+ globalMousePosition,
336
+ ]
337
+ );
522
338
 
523
339
  const renderHoverLayers = () => (
524
340
  <>
525
- {/* Hover layers - opacity and background set via CSS variables in SCSS */}
526
- <div className={ATOMIX_GLASS.HOVER_1_CLASS} />
527
- <div className={ATOMIX_GLASS.HOVER_2_CLASS} />
528
- <div className={ATOMIX_GLASS.HOVER_3_CLASS} />
341
+ <div aria-hidden="true" className={ATOMIX_GLASS.HOVER_1_CLASS} />
342
+ <div aria-hidden="true" className={ATOMIX_GLASS.HOVER_2_CLASS} />
343
+ <div aria-hidden="true" className={ATOMIX_GLASS.HOVER_3_CLASS} />
529
344
  </>
530
345
  );
531
346
 
532
- const renderBackgroundLayer = (layerType: 'dark' | 'black') => (
347
+ const backgroundLayerTypes = ['dark', 'black'] as const;
348
+ const renderBackgroundLayer = (layerType: (typeof backgroundLayerTypes)[number]) => (
533
349
  <div
534
- className={[
350
+ aria-hidden="true"
351
+ className={mergeClassNames(
535
352
  ATOMIX_GLASS.BACKGROUND_LAYER_CLASS,
536
353
  layerType === 'dark'
537
354
  ? ATOMIX_GLASS.BACKGROUND_LAYER_DARK_CLASS
538
355
  : ATOMIX_GLASS.BACKGROUND_LAYER_BLACK_CLASS,
539
356
  isOverLight
540
357
  ? ATOMIX_GLASS.BACKGROUND_LAYER_OVER_LIGHT_CLASS
541
- : ATOMIX_GLASS.BACKGROUND_LAYER_HIDDEN_CLASS,
542
- ]
543
- .filter(Boolean)
544
- .join(' ')}
358
+ : ATOMIX_GLASS.BACKGROUND_LAYER_HIDDEN_CLASS
359
+ )}
545
360
  />
546
361
  );
547
362
 
548
363
  const renderOverLightLayers = () => (
549
364
  <>
550
- {/* Base and overlay layers - opacity and background set via CSS variables in SCSS */}
551
- <div className={ATOMIX_GLASS.BASE_LAYER_CLASS} />
552
- <div className={ATOMIX_GLASS.OVERLAY_LAYER_CLASS} />
553
- {/* Overlay highlight - opacity and background are dynamic, calculated inline */}
554
- <div className={ATOMIX_GLASS.OVERLAY_HIGHLIGHT_CLASS} />
365
+ <div aria-hidden="true" className={ATOMIX_GLASS.BASE_LAYER_CLASS} />
366
+ <div aria-hidden="true" className={ATOMIX_GLASS.OVERLAY_LAYER_CLASS} />
367
+ <div aria-hidden="true" className={ATOMIX_GLASS.OVERLAY_HIGHLIGHT_CLASS} />
555
368
  </>
556
369
  );
557
370
 
558
371
  const renderBorderElements = () => (
559
372
  <>
560
- {/* Border elements - all styles (static and dynamic via CSS variables) are in SCSS */}
561
- {/* Position, size, transform, transition, border-radius all use CSS variables set in glassVars */}
562
- <span className={ATOMIX_GLASS.BORDER_BACKDROP_CLASS} />
563
- <span className={ATOMIX_GLASS.BORDER_1_CLASS} />
564
- <span className={ATOMIX_GLASS.BORDER_2_CLASS} />
373
+ <span aria-hidden="true" className={ATOMIX_GLASS.BORDER_BACKDROP_CLASS} />
374
+ <span aria-hidden="true" className={ATOMIX_GLASS.BORDER_1_CLASS} />
375
+ <span aria-hidden="true" className={ATOMIX_GLASS.BORDER_2_CLASS} />
565
376
  </>
566
377
  );
567
378
 
@@ -570,54 +381,31 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
570
381
  {...rest}
571
382
  ref={mergedRef}
572
383
  className={componentClassName}
573
- style={{ ...glassVars }}
384
+ style={glassVars}
574
385
  role={role || (onClick ? 'button' : undefined)}
575
386
  tabIndex={onClick ? (tabIndex ?? 0) : tabIndex}
576
387
  aria-label={ariaLabel}
577
388
  aria-describedby={ariaDescribedBy}
578
389
  aria-disabled={onClick && effectiveWithoutEffects ? true : onClick ? false : undefined}
579
- aria-pressed={onClick ? isActive : undefined}
580
390
  onKeyDown={onClick ? handleKeyDown : undefined}
581
391
  >
582
392
  <AtomixGlassContainer
583
393
  ref={glassRef}
584
394
  contentRef={contentRef}
585
395
  className={className}
586
- style={{ ...restStyle } as React.CSSProperties}
396
+ style={containerStyle as React.CSSProperties}
587
397
  borderRadius={effectiveBorderRadius}
588
- displacementScale={
589
- effectiveWithoutEffects
590
- ? 0
591
- : mode === 'shader'
592
- ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_DISPLACEMENT
593
- : isOverLight
594
- ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.OVER_LIGHT_DISPLACEMENT
595
- : displacementScale
596
- }
597
- blurAmount={effectiveWithoutEffects ? 0 : blurAmount}
598
- saturation={
599
- effectiveHighContrast
600
- ? ATOMIX_GLASS.CONSTANTS.SATURATION.HIGH_CONTRAST
601
- : isOverLight
602
- ? saturation * overLightConfig.saturationBoost
603
- : saturation
604
- }
605
- aberrationIntensity={
606
- effectiveWithoutEffects
607
- ? 0
608
- : mode === 'shader'
609
- ? aberrationIntensity * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_ABERRATION
610
- : aberrationIntensity
611
- }
398
+ displacementScale={containerEffects.displacementScale}
399
+ blurAmount={containerEffects.blurAmount}
400
+ saturation={containerEffects.saturation}
401
+ aberrationIntensity={containerEffects.aberrationIntensity}
612
402
  glassSize={glassSize}
613
- padding={padding}
614
- mouseOffset={effectiveWithoutEffects ? { x: 0, y: 0 } : mouseOffset}
615
- globalMousePosition={effectiveWithoutEffects ? { x: 0, y: 0 } : globalMousePosition}
403
+ mouseOffset={containerEffects.mouseOffset}
404
+ globalMousePosition={containerEffects.globalMousePosition}
616
405
  onMouseEnter={handleMouseEnter}
617
406
  onMouseLeave={handleMouseLeave}
618
407
  onMouseDown={handleMouseDown}
619
408
  onMouseUp={handleMouseUp}
620
- isHovered={isHovered}
621
409
  isActive={isActive}
622
410
  overLight={isOverLight}
623
411
  overLightConfig={{
@@ -633,8 +421,6 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
633
421
  shaderVariant={shaderVariant}
634
422
  withLiquidBlur={withLiquidBlur}
635
423
  isFixedOrSticky={isFixedOrSticky}
636
- // Phase 1: Animation System props
637
- shaderTime={getShaderTime()}
638
424
  withTimeAnimation={withTimeAnimation}
639
425
  animationSpeed={animationSpeed}
640
426
  withMultiLayerDistortion={withMultiLayerDistortion}
@@ -648,32 +434,19 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
648
434
 
649
435
  {Boolean(onClick) && renderHoverLayers()}
650
436
 
651
- {/* Background layers for over-light mode */}
652
- {/* Static styles (pointer-events) are in SCSS; will-change is managed via .u-glass-clean-root utility for backdrop-filter stability */}
653
- {renderBackgroundLayer('dark')}
654
- {renderBackgroundLayer('black')}
437
+ {backgroundLayerTypes.map(layerType => (
438
+ <React.Fragment key={layerType}>{renderBackgroundLayer(layerType)}</React.Fragment>
439
+ ))}
655
440
  {shouldRenderOverLightLayers && renderOverLightLayers()}
656
441
 
657
- {withBorder && renderBorderElements()}
658
-
659
- {/* Phase 3: Performance Monitoring Dashboard - Only in development with debugPerformance enabled */}
660
- {debugPerformance && performanceMetrics && (
661
- <PerformanceDashboard
662
- metrics={performanceMetrics}
663
- isVisible={true}
664
- onClose={() => {}} // No-op, dashboard always visible when debugPerformance is true
665
- />
666
- )}
442
+ {resolvedBorder.enabled && renderBorderElements()}
667
443
  </div>
668
444
  );
669
445
  });
670
446
 
671
447
  AtomixGlassInner.displayName = 'AtomixGlass';
672
448
 
673
- /**
674
- * AtomixGlass - wrapped with React.memo to prevent unnecessary re-renders.
675
- * Ref is forwarded to the root `<div>` element.
676
- */
449
+ /** Memoized public export. Ref targets the root `.c-atomix-glass` wrapper. */
677
450
  export const AtomixGlass = memo(AtomixGlassInner);
678
451
 
679
452
  export default AtomixGlass;