@shohojdhara/atomix 0.2.3 → 0.2.5

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 (225) hide show
  1. package/README.md +19 -0
  2. package/dist/atomix.css +1703 -1544
  3. package/dist/atomix.min.css +4 -4
  4. package/dist/index.d.ts +1465 -963
  5. package/dist/index.esm.js +16289 -25908
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/index.js +15650 -21780
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.js +1 -1
  10. package/dist/index.min.js.map +1 -1
  11. package/dist/themes/applemix.css +15008 -0
  12. package/dist/themes/applemix.min.css +72 -0
  13. package/dist/themes/boomdevs.css +1608 -1450
  14. package/dist/themes/boomdevs.min.css +5 -5
  15. package/dist/themes/esrar.css +1702 -1543
  16. package/dist/themes/esrar.min.css +4 -4
  17. package/dist/themes/flashtrade.css +15159 -0
  18. package/dist/themes/flashtrade.min.css +86 -0
  19. package/dist/themes/mashroom.css +1699 -1540
  20. package/dist/themes/mashroom.min.css +7 -7
  21. package/dist/themes/shaj-default.css +1693 -1534
  22. package/dist/themes/shaj-default.min.css +4 -4
  23. package/package.json +6 -17
  24. package/src/components/Accordion/Accordion.stories.tsx +662 -21
  25. package/src/components/Accordion/Accordion.tsx +21 -14
  26. package/src/components/AtomixGlass/AtomixGlass.test.tsx +106 -72
  27. package/src/components/AtomixGlass/AtomixGlass.tsx +529 -1195
  28. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +400 -0
  29. package/src/components/AtomixGlass/GlassFilter.tsx +156 -0
  30. package/src/components/AtomixGlass/README.md +124 -2
  31. package/src/components/AtomixGlass/atomixGLass.old.tsx +1266 -0
  32. package/src/components/AtomixGlass/glass-utils.ts +263 -0
  33. package/src/components/AtomixGlass/shader-utils.ts +792 -68
  34. package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1250 -0
  35. package/src/components/AtomixGlass/stories/Examples.stories.tsx +5768 -0
  36. package/src/components/AtomixGlass/stories/Modes.stories.tsx +1065 -0
  37. package/src/components/AtomixGlass/stories/Playground.stories.tsx +1129 -0
  38. package/src/components/AtomixGlass/stories/ShaderVariants.stories.tsx +395 -0
  39. package/src/components/AtomixGlass/stories/shared-components.tsx +301 -0
  40. package/src/components/AtomixGlass/utils.ts +3 -3
  41. package/src/components/Avatar/Avatar.tsx +3 -0
  42. package/src/components/Avatar/AvatarGroup.tsx +2 -1
  43. package/src/components/Badge/Badge.stories.tsx +76 -55
  44. package/src/components/Badge/Badge.tsx +12 -14
  45. package/src/components/Breadcrumb/Breadcrumb.tsx +23 -4
  46. package/src/components/Button/Button.stories.tsx +501 -20
  47. package/src/components/Button/Button.tsx +5 -8
  48. package/src/components/Callout/Callout.stories.tsx +86 -35
  49. package/src/components/Callout/Callout.tsx +31 -9
  50. package/src/components/Card/Card.stories.tsx +565 -2
  51. package/src/components/Card/Card.tsx +15 -4
  52. package/src/components/Card/ElevationCard.tsx +2 -0
  53. package/src/components/Chart/AnimatedChart.tsx +179 -156
  54. package/src/components/Chart/AreaChart.tsx +123 -12
  55. package/src/components/Chart/BarChart.tsx +91 -100
  56. package/src/components/Chart/BaseChart.tsx +80 -0
  57. package/src/components/Chart/BubbleChart.tsx +114 -290
  58. package/src/components/Chart/CandlestickChart.tsx +282 -622
  59. package/src/components/Chart/Chart.stories.tsx +576 -179
  60. package/src/components/Chart/Chart.tsx +374 -75
  61. package/src/components/Chart/ChartRenderer.tsx +371 -220
  62. package/src/components/Chart/ChartToolbar.tsx +372 -61
  63. package/src/components/Chart/ChartTooltip.tsx +33 -18
  64. package/src/components/Chart/DonutChart.tsx +172 -254
  65. package/src/components/Chart/FunnelChart.tsx +169 -240
  66. package/src/components/Chart/GaugeChart.tsx +224 -392
  67. package/src/components/Chart/HeatmapChart.tsx +302 -440
  68. package/src/components/Chart/LineChart.tsx +148 -103
  69. package/src/components/Chart/MultiAxisChart.tsx +267 -395
  70. package/src/components/Chart/PieChart.tsx +114 -64
  71. package/src/components/Chart/RadarChart.tsx +202 -218
  72. package/src/components/Chart/ScatterChart.tsx +111 -97
  73. package/src/components/Chart/TreemapChart.tsx +147 -222
  74. package/src/components/Chart/WaterfallChart.tsx +253 -291
  75. package/src/components/Chart/index.ts +11 -9
  76. package/src/components/Chart/types.ts +85 -9
  77. package/src/components/Chart/utils.ts +66 -0
  78. package/src/components/ColorModeToggle/ColorModeToggle.tsx +6 -3
  79. package/src/components/Countdown/Countdown.tsx +4 -0
  80. package/src/components/DataTable/DataTable.tsx +2 -1
  81. package/src/components/DatePicker/DatePicker.stories.tsx +689 -12
  82. package/src/components/DatePicker/DatePicker.tsx +3 -9
  83. package/src/components/DatePicker/types.ts +5 -0
  84. package/src/components/Dropdown/Dropdown.stories.tsx +32 -25
  85. package/src/components/Dropdown/Dropdown.tsx +26 -28
  86. package/src/components/EdgePanel/EdgePanel.stories.tsx +473 -2
  87. package/src/components/EdgePanel/EdgePanel.tsx +101 -13
  88. package/src/components/Footer/Footer.stories.tsx +187 -60
  89. package/src/components/Footer/Footer.test.tsx +134 -0
  90. package/src/components/Footer/Footer.tsx +133 -34
  91. package/src/components/Footer/FooterLink.tsx +1 -1
  92. package/src/components/Footer/FooterSection.tsx +53 -36
  93. package/src/components/Footer/FooterSocialLink.tsx +32 -29
  94. package/src/components/Footer/README.md +82 -3
  95. package/src/components/Footer/index.ts +1 -1
  96. package/src/components/Form/Checkbox.stories.tsx +13 -5
  97. package/src/components/Form/Checkbox.tsx +3 -6
  98. package/src/components/Form/Form.stories.tsx +10 -3
  99. package/src/components/Form/Form.tsx +2 -0
  100. package/src/components/Form/FormGroup.tsx +2 -1
  101. package/src/components/Form/Input.stories.tsx +12 -11
  102. package/src/components/Form/Input.tsx +97 -95
  103. package/src/components/Form/Radio.stories.tsx +22 -7
  104. package/src/components/Form/Radio.tsx +3 -6
  105. package/src/components/Form/Select.stories.tsx +21 -6
  106. package/src/components/Form/Select.tsx +3 -5
  107. package/src/components/Form/Textarea.stories.tsx +13 -11
  108. package/src/components/Form/Textarea.tsx +88 -86
  109. package/src/components/Hero/Hero.stories.tsx +2 -3
  110. package/src/components/Hero/Hero.tsx +5 -6
  111. package/src/components/Icon/Icon.tsx +12 -1
  112. package/src/components/List/List.tsx +2 -1
  113. package/src/components/List/ListGroup.tsx +2 -1
  114. package/src/components/Messages/Messages.stories.tsx +113 -0
  115. package/src/components/Messages/Messages.tsx +52 -9
  116. package/src/components/Modal/Modal.stories.tsx +48 -32
  117. package/src/components/Modal/Modal.tsx +19 -24
  118. package/src/components/Navigation/Menu/MegaMenu.tsx +2 -2
  119. package/src/components/Navigation/Menu/Menu.tsx +2 -2
  120. package/src/components/Navigation/Nav/Nav.stories.tsx +469 -0
  121. package/src/components/Navigation/Nav/Nav.tsx +22 -4
  122. package/src/components/Navigation/Nav/NavDropdown.tsx +10 -1
  123. package/src/components/Navigation/Navbar/Navbar.stories.tsx +413 -0
  124. package/src/components/Navigation/Navbar/Navbar.tsx +70 -29
  125. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +340 -0
  126. package/src/components/Navigation/SideMenu/SideMenu.tsx +29 -2
  127. package/src/components/Pagination/Pagination.stories.tsx +13 -6
  128. package/src/components/Pagination/Pagination.tsx +7 -6
  129. package/src/components/PhotoViewer/PhotoViewer.tsx +2 -1
  130. package/src/components/Popover/Popover.stories.tsx +32 -24
  131. package/src/components/Popover/Popover.tsx +4 -1
  132. package/src/components/ProductReview/ProductReview.tsx +8 -2
  133. package/src/components/Progress/Progress.tsx +19 -3
  134. package/src/components/Rating/Rating.stories.tsx +11 -6
  135. package/src/components/Rating/Rating.tsx +3 -5
  136. package/src/components/River/River.tsx +5 -5
  137. package/src/components/SectionIntro/SectionIntro.tsx +8 -2
  138. package/src/components/Slider/Slider.stories.tsx +4 -4
  139. package/src/components/Slider/Slider.tsx +4 -3
  140. package/src/components/Spinner/Spinner.tsx +19 -3
  141. package/src/components/Steps/Steps.stories.tsx +5 -4
  142. package/src/components/Steps/Steps.tsx +8 -5
  143. package/src/components/Tab/Tab.stories.tsx +4 -3
  144. package/src/components/Tab/Tab.tsx +8 -6
  145. package/src/components/Testimonial/Testimonial.tsx +8 -2
  146. package/src/components/Todo/Todo.tsx +2 -1
  147. package/src/components/Toggle/Toggle.stories.tsx +5 -4
  148. package/src/components/Toggle/Toggle.tsx +8 -5
  149. package/src/components/Tooltip/Tooltip.stories.tsx +40 -30
  150. package/src/components/Tooltip/Tooltip.tsx +9 -2
  151. package/src/components/Upload/Upload.stories.tsx +252 -0
  152. package/src/components/Upload/Upload.tsx +92 -53
  153. package/src/components/VideoPlayer/VideoPlayer.tsx +3 -1
  154. package/src/components/index.ts +0 -4
  155. package/src/layouts/Grid/Grid.stories.tsx +10 -23
  156. package/src/layouts/Grid/Grid.tsx +20 -1
  157. package/src/layouts/Grid/GridCol.tsx +76 -48
  158. package/src/lib/composables/useAtomixGlass.ts +861 -44
  159. package/src/lib/composables/useBarChart.ts +21 -4
  160. package/src/lib/composables/useChart.ts +227 -370
  161. package/src/lib/composables/useChartExport.ts +19 -78
  162. package/src/lib/composables/useChartToolbar.ts +11 -21
  163. package/src/lib/composables/useEdgePanel.ts +125 -71
  164. package/src/lib/composables/useFooter.ts +3 -3
  165. package/src/lib/composables/useGlassContainer.ts +16 -7
  166. package/src/lib/composables/useLineChart.ts +11 -2
  167. package/src/lib/composables/usePieChart.ts +4 -14
  168. package/src/lib/composables/useRiver.ts +5 -0
  169. package/src/lib/composables/useSlider.ts +62 -24
  170. package/src/lib/composables/useVideoPlayer.ts +60 -63
  171. package/src/lib/constants/components.ts +147 -32
  172. package/src/lib/types/components.ts +355 -25
  173. package/src/lib/utils/displacement-generator.ts +55 -49
  174. package/src/lib/utils/icons.ts +1 -1
  175. package/src/lib/utils/index.ts +16 -10
  176. package/src/styles/01-settings/_settings.accordion.scss +19 -19
  177. package/src/styles/01-settings/_settings.animations.scss +5 -5
  178. package/src/styles/01-settings/_settings.avatar-group.scss +1 -1
  179. package/src/styles/01-settings/_settings.avatar.scss +17 -17
  180. package/src/styles/01-settings/_settings.background.scss +0 -3
  181. package/src/styles/01-settings/_settings.badge.scss +1 -1
  182. package/src/styles/01-settings/_settings.breadcrumb.scss +1 -1
  183. package/src/styles/01-settings/_settings.card.scss +1 -1
  184. package/src/styles/01-settings/_settings.chart.scss +65 -2
  185. package/src/styles/01-settings/_settings.dropdown.scss +1 -1
  186. package/src/styles/01-settings/_settings.edge-panel.scss +1 -1
  187. package/src/styles/01-settings/_settings.footer.scss +35 -42
  188. package/src/styles/01-settings/_settings.input.scss +1 -1
  189. package/src/styles/01-settings/_settings.list.scss +1 -1
  190. package/src/styles/01-settings/_settings.rating.scss +1 -1
  191. package/src/styles/01-settings/_settings.tabs.scss +1 -1
  192. package/src/styles/01-settings/_settings.upload.scss +6 -5
  193. package/src/styles/02-tools/_tools.animations.scss +4 -5
  194. package/src/styles/02-tools/_tools.background.scss +1 -13
  195. package/src/styles/02-tools/_tools.glass.scss +0 -1
  196. package/src/styles/02-tools/_tools.utility-api.scss +91 -48
  197. package/src/styles/03-generic/_generic.root.scss +1 -4
  198. package/src/styles/04-elements/_elements.body.scss +0 -1
  199. package/src/styles/06-components/_components.atomix-glass.scss +249 -0
  200. package/src/styles/06-components/_components.badge.scss +8 -23
  201. package/src/styles/06-components/_components.button.scss +8 -3
  202. package/src/styles/06-components/_components.callout.scss +10 -5
  203. package/src/styles/06-components/_components.card.scss +2 -14
  204. package/src/styles/06-components/_components.chart.scss +969 -1449
  205. package/src/styles/06-components/_components.dropdown.scss +19 -7
  206. package/src/styles/06-components/_components.edge-panel.scss +103 -0
  207. package/src/styles/06-components/_components.footer.scss +166 -85
  208. package/src/styles/06-components/_components.input.scss +8 -9
  209. package/src/styles/06-components/_components.list.scss +1 -0
  210. package/src/styles/06-components/_components.messages.scss +176 -0
  211. package/src/styles/06-components/_components.modal.scss +16 -4
  212. package/src/styles/06-components/_components.navbar.scss +12 -1
  213. package/src/styles/06-components/_components.side-menu.scss +5 -0
  214. package/src/styles/06-components/_components.skeleton.scss +8 -6
  215. package/src/styles/06-components/_components.upload.scss +219 -4
  216. package/src/styles/06-components/old.chart.styles.scss +1 -30
  217. package/src/styles/99-utilities/_index.scss +1 -0
  218. package/src/styles/99-utilities/_utilities.glass-fixes.scss +1 -0
  219. package/src/styles/99-utilities/_utilities.scss +1 -1
  220. package/src/components/AtomixGlass/AtomixGlass.stories.tsx +0 -3011
  221. package/src/components/AtomixGlass/AtomixGlassComprehensivePreview.stories.tsx +0 -1369
  222. package/src/components/Chart/AdvancedChart.tsx +0 -624
  223. package/src/components/Chart/LineChartNew.tsx +0 -167
  224. package/src/components/Chart/RealTimeChart.tsx +0 -436
  225. package/src/components/DatePicker/DatePicker copy.tsx +0 -551
@@ -1,1277 +1,611 @@
1
- import {
2
- type CSSProperties,
3
- forwardRef,
4
- useCallback,
5
- useEffect,
6
- useId,
7
- useRef,
8
- useState,
9
- useMemo,
10
- } from 'react';
11
- import { ShaderDisplacementGenerator, fragmentShaders } from './shader-utils';
12
- import { displacementMap, polarDisplacementMap, prominentDisplacementMap } from './utils';
13
-
14
- const generateShaderDisplacementMap = (width: number, height: number): string => {
15
- try {
16
- const generator = new ShaderDisplacementGenerator({
17
- width,
18
- height,
19
- fragment: fragmentShaders.liquidGlass,
20
- });
21
-
22
- const dataUrl = generator.updateShader();
23
- generator.destroy();
24
-
25
- return dataUrl;
26
- } catch (error) {
27
- return displacementMap;
28
- }
29
- };
30
-
31
- const getMap = (mode: 'standard' | 'polar' | 'prominent' | 'shader', shaderMapUrl?: string) => {
32
- switch (mode) {
33
- case 'standard':
34
- return displacementMap;
35
- case 'polar':
36
- return polarDisplacementMap;
37
- case 'prominent':
38
- return prominentDisplacementMap;
39
- case 'shader':
40
- return shaderMapUrl || displacementMap;
41
- default:
42
- throw new Error(`Invalid mode: ${mode}`);
43
- }
44
- };
45
-
46
- const GlassFilter: React.FC<{
47
- id: string;
48
- displacementScale: number;
49
- aberrationIntensity: number;
50
- mode: 'standard' | 'polar' | 'prominent' | 'shader';
51
- shaderMapUrl?: string;
52
- }> = ({ id, displacementScale, aberrationIntensity, mode, shaderMapUrl }) => (
53
- <svg style={{ position: 'absolute', width: '100%', height: '100%', inset: 0 }} aria-hidden="true">
54
- <defs>
55
- <radialGradient id={`${id}-edge-mask`} cx="50%" cy="50%" r="50%">
56
- <stop offset="0%" stopColor="black" stopOpacity="0" />
57
- <stop
58
- offset={`${Math.max(30, 80 - aberrationIntensity * 2)}%`}
59
- stopColor="black"
60
- stopOpacity="0"
61
- />
62
- <stop offset="100%" stopColor="white" stopOpacity="1" />
63
- </radialGradient>
64
- <filter id={id} x="-35%" y="-35%" width="170%" height="170%" colorInterpolationFilters="sRGB">
65
- <feImage
66
- id="feimage"
67
- x="0"
68
- y="0"
69
- width="100%"
70
- height="100%"
71
- result="DISPLACEMENT_MAP"
72
- href={getMap(mode, shaderMapUrl)}
73
- preserveAspectRatio="xMidYMid slice"
74
- />
75
-
76
- <feColorMatrix
77
- in="DISPLACEMENT_MAP"
78
- type="matrix"
79
- values="0.3 0.3 0.3 0 0
80
- 0.3 0.3 0.3 0 0
81
- 0.3 0.3 0.3 0 0
82
- 0 0 0 1 0"
83
- result="EDGE_INTENSITY"
84
- />
85
- <feComponentTransfer in="EDGE_INTENSITY" result="EDGE_MASK">
86
- <feFuncA type="discrete" tableValues={`0 ${aberrationIntensity * 0.05} 1`} />
87
- </feComponentTransfer>
88
-
89
- <feOffset in="SourceGraphic" dx="0" dy="0" result="CENTER_ORIGINAL" />
90
-
91
- <feDisplacementMap
92
- in="SourceGraphic"
93
- in2="DISPLACEMENT_MAP"
94
- scale={displacementScale * (mode === 'shader' ? 1 : -1)}
95
- xChannelSelector="R"
96
- yChannelSelector="B"
97
- result="RED_DISPLACED"
98
- />
99
- <feColorMatrix
100
- in="RED_DISPLACED"
101
- type="matrix"
102
- values="1 0 0 0 0
103
- 0 0 0 0 0
104
- 0 0 0 0 0
105
- 0 0 0 1 0"
106
- result="RED_CHANNEL"
107
- />
108
-
109
- <feDisplacementMap
110
- in="SourceGraphic"
111
- in2="DISPLACEMENT_MAP"
112
- scale={displacementScale * ((mode === 'shader' ? 1 : -1) - aberrationIntensity * 0.02)}
113
- xChannelSelector="R"
114
- yChannelSelector="B"
115
- result="GREEN_DISPLACED"
116
- />
117
- <feColorMatrix
118
- in="GREEN_DISPLACED"
119
- type="matrix"
120
- values="0 0 0 0 0
121
- 0 1 0 0 0
122
- 0 0 0 0 0
123
- 0 0 0 1 0"
124
- result="GREEN_CHANNEL"
125
- />
126
-
127
- <feDisplacementMap
128
- in="SourceGraphic"
129
- in2="DISPLACEMENT_MAP"
130
- scale={displacementScale * ((mode === 'shader' ? 1 : -1) - aberrationIntensity * 0.03)}
131
- xChannelSelector="R"
132
- yChannelSelector="B"
133
- result="BLUE_DISPLACED"
134
- />
135
- <feColorMatrix
136
- in="BLUE_DISPLACED"
137
- type="matrix"
138
- values="0 0 0 0 0
139
- 0 0 0 0 0
140
- 0 0 1 0 0
141
- 0 0 0 1 0"
142
- result="BLUE_CHANNEL"
143
- />
144
-
145
- <feBlend in="GREEN_CHANNEL" in2="BLUE_CHANNEL" mode="screen" result="GB_COMBINED" />
146
- <feBlend in="RED_CHANNEL" in2="GB_COMBINED" mode="screen" result="RGB_COMBINED" />
147
-
148
- <feGaussianBlur
149
- in="RGB_COMBINED"
150
- stdDeviation={Math.max(0.1, 0.5 - aberrationIntensity * 0.1)}
151
- result="ABERRATED_BLURRED"
152
- />
153
-
154
- <feComposite
155
- in="ABERRATED_BLURRED"
156
- in2="EDGE_MASK"
157
- operator="in"
158
- result="EDGE_ABERRATION"
159
- />
160
-
161
- <feComponentTransfer in="EDGE_MASK" result="INVERTED_MASK">
162
- <feFuncA type="table" tableValues="1 0" />
163
- </feComponentTransfer>
164
- <feComposite in="CENTER_ORIGINAL" in2="INVERTED_MASK" operator="in" result="CENTER_CLEAN" />
165
-
166
- <feComposite in="EDGE_ABERRATION" in2="CENTER_CLEAN" operator="over" />
167
- </filter>
168
- </defs>
169
- </svg>
170
- );
171
-
172
- const GlassContainer = forwardRef<
173
- HTMLDivElement,
174
- React.PropsWithChildren<{
175
- className?: string;
176
- style?: React.CSSProperties;
177
- displacementScale?: number;
178
- blurAmount?: number;
179
- saturation?: number;
180
- aberrationIntensity?: number;
181
- mouseOffset?: { x: number; y: number };
182
- globalMousePos?: { x: number; y: number };
183
- onMouseLeave?: () => void;
184
- onMouseEnter?: () => void;
185
- onMouseDown?: () => void;
186
- onMouseUp?: () => void;
187
- active?: boolean;
188
- isHovered?: boolean;
189
- isActive?: boolean;
190
- overLight?: boolean;
191
- cornerRadius?: number;
192
- padding?: string;
193
- glassSize?: { width: number; height: number };
194
-
195
- onClick?: () => void;
196
- mode?: 'standard' | 'polar' | 'prominent' | 'shader';
197
- transform?: string;
198
- effectiveDisableEffects?: boolean;
199
- effectiveReducedMotion?: boolean;
200
- }>
201
- >(
202
- (
203
- {
204
- children,
205
- className = '',
206
- style,
207
- displacementScale = 25,
208
- blurAmount = 0.0625,
209
- saturation = 180,
210
- aberrationIntensity = 2,
211
- mouseOffset = { x: 0, y: 0 },
212
- globalMousePos = { x: 0, y: 0 },
213
- onMouseEnter,
214
- onMouseLeave,
215
- onMouseDown,
216
- onMouseUp,
217
- active = false,
218
- isHovered = false,
219
- isActive = false,
220
- overLight = false,
221
- cornerRadius = 0,
222
- padding = '0 0',
223
- glassSize = { width: 0, height: 0 },
224
- onClick,
225
- mode = 'standard',
226
- transform = 'none',
227
- effectiveDisableEffects = false,
228
- effectiveReducedMotion = false,
229
- },
230
- ref
231
- ) => {
232
- const filterId = useId();
233
- const [shaderMapUrl, setShaderMapUrl] = useState<string>('');
234
-
235
- const isFirefox = navigator.userAgent.toLowerCase().includes('firefox');
236
-
237
- useEffect(() => {
238
- if (mode === 'shader' && glassSize.width > 0 && glassSize.height > 0) {
239
- try {
240
- const url = generateShaderDisplacementMap(glassSize.width, glassSize.height);
241
- setShaderMapUrl(url);
242
- } catch (error) {
243
- console.warn('Failed to generate shader displacement map:', error);
244
- }
245
- }
246
- }, [mode, glassSize.width, glassSize.height]);
247
-
248
- useEffect(() => {
249
- if (!ref || typeof ref === 'function') return;
250
-
251
- const element = (ref as React.RefObject<HTMLDivElement>).current;
252
- if (!element) return;
253
-
254
- const timeoutId = setTimeout(() => {
255
- try {
256
- element.offsetHeight;
257
- } catch (error) {
258
- console.warn('AtomixGlass: Error in GlassContainer size sync:', error);
259
- }
260
- }, 0);
261
-
262
- return () => clearTimeout(timeoutId);
263
- }, [cornerRadius, glassSize.width, glassSize.height]);
264
-
265
- const liquidBlur = useMemo(() => {
266
- if (!ref || !globalMousePos.x || !globalMousePos.y) {
267
- return {
268
- baseBlur: blurAmount,
269
- edgeBlur: blurAmount * 0.5,
270
- centerBlur: blurAmount * 0.2,
271
- flowBlur: blurAmount * 0.3,
272
- };
273
- }
274
-
275
- const rect = (ref as React.RefObject<HTMLDivElement>).current?.getBoundingClientRect();
276
- if (!rect) {
277
- return {
278
- baseBlur: blurAmount,
279
- edgeBlur: blurAmount * 0.5,
280
- centerBlur: blurAmount * 0.2,
281
- flowBlur: blurAmount * 0.3,
282
- };
283
- }
284
- const centerX = rect.left + rect.width / 2;
285
- const centerY = rect.top + rect.height / 2;
286
-
287
- const deltaX = globalMousePos.x - centerX;
288
- const deltaY = globalMousePos.y - centerY;
289
- const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
290
-
291
- const maxDistance = Math.sqrt(rect.width * rect.width + rect.height * rect.height) / 2;
292
- const normalizedDistance = Math.min(distance / maxDistance, 1);
293
-
294
- const mouseInfluence =
295
- Math.sqrt(mouseOffset.x * mouseOffset.x + mouseOffset.y * mouseOffset.y) / 100;
296
-
297
- const baseBlur = blurAmount + mouseInfluence * blurAmount * 0.4;
298
-
299
- const edgeIntensity = normalizedDistance * 1.5 + mouseInfluence * 0.3;
300
- const edgeBlur = baseBlur * (0.8 + edgeIntensity * 0.6);
301
-
302
- const centerIntensity = (1 - normalizedDistance) * 0.3 + mouseInfluence * 0.2;
303
- const centerBlur = baseBlur * (0.3 + centerIntensity * 0.4);
304
-
305
- const flowDirection = Math.atan2(deltaY, deltaX);
306
- const flowIntensity = Math.sin(flowDirection + mouseInfluence * Math.PI) * 0.5 + 0.5;
307
- const flowBlur = baseBlur * (0.4 + flowIntensity * 0.6);
308
-
309
- const hoverMultiplier = isHovered ? 1.2 : 1;
310
- const activeMultiplier = isActive ? 1.4 : 1;
311
- const stateMultiplier = hoverMultiplier * activeMultiplier;
312
-
313
- return {
314
- baseBlur: Math.max(0.1, baseBlur * stateMultiplier),
315
- edgeBlur: Math.max(0.1, edgeBlur * stateMultiplier),
316
- centerBlur: Math.max(0.1, centerBlur * stateMultiplier),
317
- flowBlur: Math.max(0.1, flowBlur * stateMultiplier),
318
- };
319
- }, [blurAmount, globalMousePos, mouseOffset, isHovered, isActive, ref]);
320
-
321
- const backdropStyle = useMemo(() => {
322
- const dynamicSaturation = saturation + liquidBlur.baseBlur * 20;
323
-
324
- const blurLayers = [
325
- `blur(${liquidBlur.baseBlur}px)`,
326
- // `blur(${liquidBlur.edgeBlur}px)`,
327
- // `blur(${liquidBlur.centerBlur}px)`,
328
- `blur(${liquidBlur.flowBlur}px)`,
329
- ];
330
-
331
- return {
332
- filter: `url(#${filterId})`,
333
- backdropFilter: `${blurLayers.join(' ')} saturate(${Math.min(dynamicSaturation, 200)}%)`,
334
- };
335
- }, [filterId, liquidBlur, saturation]);
336
-
337
- return (
338
- <div
339
- ref={ref}
340
- className={` ${className} ${active ? 'active' : ''}`}
341
- style={style}
342
- onClick={onClick}
343
- >
344
- <div
345
- className="glass"
346
- style={{
347
- position: 'relative',
348
- padding,
349
- borderRadius: `${cornerRadius}px`,
350
- transition: 'all 0.2s ease-out',
351
-
352
- }}
353
- onMouseEnter={onMouseEnter}
354
- onMouseLeave={onMouseLeave}
355
- onMouseDown={onMouseDown}
356
- onMouseUp={onMouseUp}
357
- >
358
- <GlassFilter
359
- mode={mode}
360
- id={filterId}
361
- displacementScale={displacementScale}
362
- aberrationIntensity={aberrationIntensity}
363
- shaderMapUrl={shaderMapUrl}
364
- />
365
- <span
366
- className="glass__warp"
367
- style={
368
- {
369
- ...backdropStyle,
370
- borderRadius: `${cornerRadius}px`,
371
- position: 'absolute',
372
- inset: '0',
373
- } as CSSProperties
374
- }
375
- />
376
-
377
- {/* Apple Liquid Glass Inner Shadow Layer */}
378
- <div
379
- style={{
380
- position: 'absolute',
381
- inset: '1px',
382
- borderRadius: `${cornerRadius}px`,
383
- pointerEvents: 'none' as React.CSSProperties['pointerEvents'],
384
- zIndex: 20,
385
- boxShadow: [
386
- '0 0 20px rgba(0, 0, 0, 0.15) inset',
387
- '0 4px 8px rgba(0, 0, 0, 0.08) inset',
388
- ].join(', '),
389
- opacity: effectiveDisableEffects ? 0 : 1,
390
- transition: effectiveReducedMotion ? 'none' : 'opacity 0.2s ease-out',
391
- }}
392
- />
393
-
394
- <div
395
- style={{
396
- position: 'relative',
397
- zIndex: 1,
398
- textShadow: overLight
399
- ? '0px 2px 12px rgba(0, 0, 0, 0)'
400
- : '0px 2px 12px rgba(0, 0, 0, 0.4)',
401
- }}
402
- >
403
- {children}
404
- </div>
405
- </div>
406
- </div>
407
- );
408
- }
409
- );
410
-
411
- GlassContainer.displayName = 'GlassContainer';
412
-
413
- interface AtomixGlassProps {
414
- children: React.ReactNode;
415
- displacementScale?: number;
416
- blurAmount?: number;
417
- saturation?: number;
418
- aberrationIntensity?: number;
419
- elasticity?: number;
420
- cornerRadius?: number;
421
- globalMousePos?: { x: number; y: number };
422
- mouseOffset?: { x: number; y: number };
423
- mouseContainer?: React.RefObject<HTMLElement | null> | null;
424
- className?: string;
425
- padding?: string;
426
- style?: React.CSSProperties;
427
- overLight?: boolean;
428
- mode?: 'standard' | 'polar' | 'prominent' | 'shader';
429
- onClick?: () => void;
430
-
431
- /**
432
- * Accessibility props
433
- */
434
- 'aria-label'?: string;
435
- 'aria-describedby'?: string;
436
- role?: string;
437
- tabIndex?: number;
438
-
439
- /**
440
- * Performance and accessibility options
441
- */
442
- reducedMotion?: boolean;
443
- highContrast?: boolean;
444
- disableEffects?: boolean;
445
-
446
- /**
447
- * Performance monitoring
448
- */
449
- enablePerformanceMonitoring?: boolean;
450
- }
451
-
1
+ import React, { useMemo, useRef } from 'react';
2
+ import type { AtomixGlassProps, GlassSize } from '../../lib/types/components';
3
+ import { ATOMIX_GLASS } from '../../lib/constants/components';
4
+ import { AtomixGlassContainer } from './AtomixGlassContainer';
5
+ import { useAtomixGlass } from '../../lib/composables/useAtomixGlass';
6
+
7
+ /**
8
+ * AtomixGlass - A high-performance glass morphism component with liquid distortion effects
9
+ *
10
+ * Features:
11
+ * - Hardware-accelerated glass effects with SVG filters
12
+ * - Mouse-responsive liquid distortion
13
+ * - Dynamic border-radius extraction from children CSS properties
14
+ * - Automatic light/dark theme detection via overLight prop
15
+ * - Accessibility and performance optimizations
16
+ * - Multiple displacement modes (standard, polar, prominent, shader)
17
+ * - Design token integration for consistent theming
18
+ * - Focus ring support for keyboard navigation
19
+ * - Responsive breakpoints for mobile optimization
20
+ * - Enhanced ARIA attributes for screen readers
21
+ *
22
+ * Design System Compliance:
23
+ * - Uses design tokens for opacity, spacing, and colors
24
+ * - Follows BEM methodology for class naming
25
+ * - Implements focus-ring mixin for accessibility
26
+ * - Supports reduced motion and high contrast preferences
27
+ *
28
+ * @example
29
+ * // Basic usage with dynamic border-radius extraction
30
+ * <AtomixGlass>
31
+ * <div style={{ borderRadius: '12px' }}>Content with 12px radius</div>
32
+ * </AtomixGlass>
33
+ *
34
+ * @example
35
+ * // Manual border-radius override
36
+ * <AtomixGlass cornerRadius={20}>
37
+ * <div>Content with 20px glass radius</div>
38
+ * </AtomixGlass>
39
+ *
40
+ * @example
41
+ * // Interactive glass with click handler
42
+ * <AtomixGlass onClick={() => console.log('Clicked')} aria-label="Glass card">
43
+ * <div>Clickable content</div>
44
+ * </AtomixGlass>
45
+ *
46
+ * @example
47
+ * // OverLight - Boolean mode (explicit control)
48
+ * <AtomixGlass overLight={true}>
49
+ * <div>Content on light background</div>
50
+ * </AtomixGlass>
51
+ *
52
+ * @example
53
+ * // OverLight - Auto-detection mode
54
+ * <AtomixGlass overLight="auto">
55
+ * <div>Content with auto-detected background</div>
56
+ * </AtomixGlass>
57
+ *
58
+ * @example
59
+ * // OverLight - Object config with custom settings
60
+ * <AtomixGlass
61
+ * overLight={{
62
+ * threshold: 0.8,
63
+ * opacity: 0.6,
64
+ * contrast: 1.8,
65
+ * brightness: 1.0,
66
+ * saturationBoost: 1.5
67
+ * }}
68
+ * >
69
+ * <div>Content with custom overLight config</div>
70
+ * </AtomixGlass>
71
+ *
72
+ * @example
73
+ * // Debug mode for overLight detection
74
+ * <AtomixGlass overLight="auto" debugOverLight={true}>
75
+ * <div>Content with debug logging enabled</div>
76
+ * </AtomixGlass>
77
+ */
452
78
  export function AtomixGlass({
453
79
  children,
454
- displacementScale = 15,
455
- blurAmount = 0.5,
456
- saturation = 120,
457
- aberrationIntensity = 1,
458
- elasticity = 0.15,
459
- cornerRadius = 20,
460
- globalMousePos: externalGlobalMousePos,
80
+ displacementScale = ATOMIX_GLASS.DEFAULTS.DISPLACEMENT_SCALE,
81
+ blurAmount = ATOMIX_GLASS.DEFAULTS.BLUR_AMOUNT,
82
+ saturation = ATOMIX_GLASS.DEFAULTS.SATURATION,
83
+ aberrationIntensity = ATOMIX_GLASS.DEFAULTS.ABERRATION_INTENSITY,
84
+ elasticity = ATOMIX_GLASS.DEFAULTS.ELASTICITY,
85
+ cornerRadius,
86
+ globalMousePosition: externalGlobalMousePosition,
461
87
  mouseOffset: externalMouseOffset,
462
88
  mouseContainer = null,
463
89
  className = '',
464
- padding = '0 0',
465
- overLight = false,
90
+ padding = ATOMIX_GLASS.DEFAULTS.PADDING,
91
+ overLight = ATOMIX_GLASS.DEFAULTS.OVER_LIGHT,
466
92
  style = {},
467
- mode = 'standard',
93
+ mode = ATOMIX_GLASS.DEFAULTS.MODE,
468
94
  onClick,
469
-
95
+ shaderVariant = 'liquidGlass',
470
96
  'aria-label': ariaLabel,
471
97
  'aria-describedby': ariaDescribedBy,
472
98
  role,
473
99
  tabIndex,
474
-
475
100
  reducedMotion = false,
476
101
  highContrast = false,
477
102
  disableEffects = false,
478
-
103
+ enableLiquidBlur = false,
104
+ enableBorderEffect = true,
105
+ enableOverLightLayers = ATOMIX_GLASS.DEFAULTS.ENABLE_OVER_LIGHT_LAYERS,
479
106
  enablePerformanceMonitoring = false,
107
+ debugCornerRadius = false,
108
+ debugOverLight = false,
480
109
  }: AtomixGlassProps) {
481
110
  const glassRef = useRef<HTMLDivElement>(null);
482
- const [isHovered, setIsHovered] = useState(false);
483
- const [isActive, setIsActive] = useState(false);
484
- const [glassSize, setGlassSize] = useState({ width: 270, height: 69 });
485
- const [internalGlobalMousePos, setInternalGlobalMousePos] = useState({ x: 0, y: 0 });
486
- const [internalMouseOffset, setInternalMouseOffset] = useState({ x: 0, y: 0 });
487
-
488
- const [userPrefersReducedMotion, setUserPrefersReducedMotion] = useState(false);
489
- const [userPrefersHighContrast, setUserPrefersHighContrast] = useState(false);
490
-
491
- useEffect(() => {
492
- if (typeof window.matchMedia !== 'function') {
493
- console.warn('AtomixGlass: matchMedia not supported, using default preferences');
494
- return;
495
- }
496
-
497
- try {
498
- const mediaQueryReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
499
- const mediaQueryHighContrast = window.matchMedia('(prefers-contrast: high)');
500
-
501
- setUserPrefersReducedMotion(mediaQueryReducedMotion.matches);
502
- setUserPrefersHighContrast(mediaQueryHighContrast.matches);
503
-
504
- const handleReducedMotionChange = (e: MediaQueryListEvent) => {
505
- setUserPrefersReducedMotion(e.matches);
506
- };
507
-
508
- const handleHighContrastChange = (e: MediaQueryListEvent) => {
509
- setUserPrefersHighContrast(e.matches);
510
- };
511
-
512
- if (mediaQueryReducedMotion.addEventListener) {
513
- mediaQueryReducedMotion.addEventListener('change', handleReducedMotionChange);
514
- mediaQueryHighContrast.addEventListener('change', handleHighContrastChange);
515
- } else if (mediaQueryReducedMotion.addListener) {
516
- mediaQueryReducedMotion.addListener(handleReducedMotionChange);
517
- mediaQueryHighContrast.addListener(handleHighContrastChange);
518
- }
111
+ const contentRef = useRef<HTMLDivElement>(null);
519
112
 
520
- return () => {
521
- if (mediaQueryReducedMotion.removeEventListener) {
522
- mediaQueryReducedMotion.removeEventListener('change', handleReducedMotionChange);
523
- mediaQueryHighContrast.removeEventListener('change', handleHighContrastChange);
524
- } else if (mediaQueryReducedMotion.removeListener) {
525
- mediaQueryReducedMotion.removeListener(handleReducedMotionChange);
526
- mediaQueryHighContrast.removeListener(handleHighContrastChange);
527
- }
528
- };
529
- } catch (error) {
530
- console.warn('AtomixGlass: Error setting up media queries:', error);
531
- }
532
- }, []);
533
-
534
- const effectiveReducedMotion = reducedMotion || userPrefersReducedMotion;
535
- const effectiveHighContrast = highContrast || userPrefersHighContrast;
536
- const effectiveDisableEffects = disableEffects || effectiveReducedMotion;
537
-
538
- const globalMousePos = externalGlobalMousePos || internalGlobalMousePos;
539
- const mouseOffset = externalMouseOffset || internalMouseOffset;
540
-
541
- const mouseMoveThrottleRef = useRef<number | null>(null);
542
- const lastMouseEventRef = useRef<MouseEvent | null>(null);
543
-
544
- const handleMouseMove = useCallback(
545
- (e: MouseEvent) => {
546
- lastMouseEventRef.current = e;
547
-
548
- if (mouseMoveThrottleRef.current === null) {
549
- mouseMoveThrottleRef.current = requestAnimationFrame(() => {
550
- const event = lastMouseEventRef.current;
551
- if (!event) {
552
- mouseMoveThrottleRef.current = null;
553
- return;
554
- }
555
-
556
- const container = mouseContainer?.current || glassRef.current;
557
- if (!container) {
558
- mouseMoveThrottleRef.current = null;
559
- return;
560
- }
561
-
562
- try {
563
- const startTime = enablePerformanceMonitoring ? performance.now() : 0;
564
-
565
- const rect = container.getBoundingClientRect();
566
- const centerX = rect.left + rect.width / 2;
567
- const centerY = rect.top + rect.height / 2;
568
-
569
- setInternalMouseOffset({
570
- x: ((event.clientX - centerX) / rect.width) * 100,
571
- y: ((event.clientY - centerY) / rect.height) * 100,
572
- });
573
-
574
- setInternalGlobalMousePos({
575
- x: event.clientX,
576
- y: event.clientY,
577
- });
578
-
579
- if (enablePerformanceMonitoring) {
580
- const endTime = performance.now();
581
- const duration = endTime - startTime;
582
- if (duration > 5) {
583
- console.warn(`AtomixGlass: Mouse tracking took ${duration.toFixed(2)}ms`);
584
- }
585
- }
586
- } catch (error) {
587
- console.warn('AtomixGlass: Error in mouse tracking:', error);
588
- } finally {
589
- mouseMoveThrottleRef.current = null;
590
- }
591
- });
592
- }
593
- },
594
- [mouseContainer]
595
- );
596
-
597
- useEffect(() => {
598
- if (externalGlobalMousePos && externalMouseOffset) {
599
- return;
600
- }
601
-
602
- if (effectiveDisableEffects) {
603
- return;
604
- }
605
-
606
- const container = mouseContainer?.current || glassRef.current;
607
- if (!container) {
608
- return;
609
- }
610
-
611
- container.addEventListener('mousemove', handleMouseMove, { passive: true });
612
-
613
- return () => {
614
- container.removeEventListener('mousemove', handleMouseMove);
615
- if (mouseMoveThrottleRef.current) {
616
- cancelAnimationFrame(mouseMoveThrottleRef.current);
617
- mouseMoveThrottleRef.current = null;
618
- }
619
- };
620
- }, [
621
- handleMouseMove,
622
- mouseContainer,
623
- externalGlobalMousePos,
624
- externalMouseOffset,
113
+ // Use composable hook for all state and logic
114
+ const {
115
+ isHovered,
116
+ isActive,
117
+ glassSize,
118
+ effectiveCornerRadius,
119
+ effectiveReducedMotion,
120
+ effectiveHighContrast,
625
121
  effectiveDisableEffects,
626
- ]);
627
-
628
- const calculateDirectionalScale = useCallback(() => {
629
- if (!globalMousePos.x || !globalMousePos.y || !glassRef.current) {
630
- return 'scale(1)';
631
- }
632
-
633
- const rect = glassRef.current.getBoundingClientRect();
634
- const pillCenterX = rect.left + rect.width / 2;
635
- const pillCenterY = rect.top + rect.height / 2;
636
- const pillWidth = glassSize.width;
637
- const pillHeight = glassSize.height;
638
-
639
- const deltaX = globalMousePos.x - pillCenterX;
640
- const deltaY = globalMousePos.y - pillCenterY;
641
-
642
- const edgeDistanceX = Math.max(0, Math.abs(deltaX) - pillWidth / 2);
643
- const edgeDistanceY = Math.max(0, Math.abs(deltaY) - pillHeight / 2);
644
- const edgeDistance = Math.sqrt(edgeDistanceX * edgeDistanceX + edgeDistanceY * edgeDistanceY);
645
-
646
- const activationZone = 200;
647
-
648
- if (edgeDistance > activationZone) {
649
- return 'scale(1)';
650
- }
651
-
652
- const fadeInFactor = 1 - edgeDistance / activationZone;
653
-
654
- const centerDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
655
- if (centerDistance === 0) {
656
- return 'scale(1)';
657
- }
658
-
659
- const normalizedX = deltaX / centerDistance;
660
- const normalizedY = deltaY / centerDistance;
661
-
662
- const stretchIntensity = Math.min(centerDistance / 300, 1) * elasticity * fadeInFactor;
663
-
664
- const scaleX =
665
- 1 +
666
- Math.abs(normalizedX) * stretchIntensity * 0.3 -
667
- Math.abs(normalizedY) * stretchIntensity * 0.15;
668
-
669
- const scaleY =
670
- 1 +
671
- Math.abs(normalizedY) * stretchIntensity * 0.3 -
672
- Math.abs(normalizedX) * stretchIntensity * 0.15;
673
-
674
- return `scaleX(${Math.max(0.8, scaleX)}) scaleY(${Math.max(0.8, scaleY)})`;
675
- }, [globalMousePos, elasticity, glassSize]);
676
-
677
- const calculateFadeInFactor = useCallback(() => {
678
- if (!globalMousePos.x || !globalMousePos.y || !glassRef.current) {
679
- return 0;
680
- }
681
-
682
- const rect = glassRef.current.getBoundingClientRect();
683
- const pillCenterX = rect.left + rect.width / 2;
684
- const pillCenterY = rect.top + rect.height / 2;
685
- const pillWidth = glassSize.width;
686
- const pillHeight = glassSize.height;
687
-
688
- const edgeDistanceX = Math.max(0, Math.abs(globalMousePos.x - pillCenterX) - pillWidth / 2);
689
- const edgeDistanceY = Math.max(0, Math.abs(globalMousePos.y - pillCenterY) - pillHeight / 2);
690
- const edgeDistance = Math.sqrt(edgeDistanceX * edgeDistanceX + edgeDistanceY * edgeDistanceY);
691
-
692
- const activationZone = 200;
693
- return edgeDistance > activationZone ? 0 : 1 - edgeDistance / activationZone;
694
- }, [globalMousePos, glassSize]);
695
-
696
- const calculateElasticTranslation = useCallback(() => {
697
- if (!glassRef.current) {
698
- return { x: 0, y: 0 };
699
- }
700
-
701
- const fadeInFactor = calculateFadeInFactor();
702
- const rect = glassRef.current.getBoundingClientRect();
703
- const pillCenterX = rect.left + rect.width / 2;
704
- const pillCenterY = rect.top + rect.height / 2;
705
-
706
- return {
707
- x: (globalMousePos.x - pillCenterX) * elasticity * 0.1 * fadeInFactor,
708
- y: (globalMousePos.y - pillCenterY) * elasticity * 0.1 * fadeInFactor,
709
- };
710
- }, [globalMousePos, elasticity, calculateFadeInFactor]);
711
-
712
- useEffect(() => {
713
- const isValidElement = (element: HTMLElement | null): element is HTMLElement => {
714
- return element !== null && element instanceof HTMLElement && element.isConnected;
715
- };
716
-
717
- let rafId: number | null = null;
718
- let lastSize = { width: 0, height: 0 };
719
- let lastCornerRadius = cornerRadius;
720
-
721
- const updateGlassSize = (forceUpdate = false): void => {
722
- try {
723
- if (rafId !== null) {
724
- cancelAnimationFrame(rafId);
725
- }
726
-
727
- rafId = requestAnimationFrame(() => {
728
- try {
729
- if (!isValidElement(glassRef.current)) {
730
- console.warn('AtomixGlass: Element not available for size calculation');
731
- return;
732
- }
733
-
734
- const rect = glassRef.current.getBoundingClientRect();
735
-
736
- if (rect.width <= 0 || rect.height <= 0) {
737
- console.warn('AtomixGlass: Invalid dimensions detected', {
738
- width: rect.width,
739
- height: rect.height,
740
- });
741
- return;
742
- }
743
-
744
- const cornerRadiusOffset = Math.max(0, cornerRadius * 0.1);
745
- const newSize = {
746
- width: Math.round(rect.width + cornerRadiusOffset),
747
- height: Math.round(rect.height + cornerRadiusOffset),
748
- };
749
-
750
- const cornerRadiusChanged = lastCornerRadius !== cornerRadius;
751
- const dimensionsChanged =
752
- newSize.width !== lastSize.width || newSize.height !== lastSize.height;
753
-
754
- if (forceUpdate || cornerRadiusChanged || dimensionsChanged) {
755
- lastSize = newSize;
756
- lastCornerRadius = cornerRadius;
757
- setGlassSize(newSize);
758
-
759
- if (enablePerformanceMonitoring && (cornerRadiusChanged || dimensionsChanged)) {
760
- console.log('AtomixGlass: Size updated', {
761
- newSize,
762
- cornerRadius,
763
- cornerRadiusChanged,
764
- dimensionsChanged,
765
- });
766
- }
767
- }
768
- } catch (error) {
769
- console.error('AtomixGlass: Error updating glass size:', error);
770
- } finally {
771
- rafId = null;
772
- }
773
- });
774
- } catch (error) {
775
- console.error('AtomixGlass: Error in updateGlassSize:', error);
776
- }
777
- };
778
-
779
- let resizeTimeoutId: NodeJS.Timeout | null = null;
780
- const debouncedResizeHandler = (): void => {
781
- if (resizeTimeoutId) {
782
- clearTimeout(resizeTimeoutId);
783
- }
784
- resizeTimeoutId = setTimeout(updateGlassSize, 16);
785
- };
786
-
787
- try {
788
- updateGlassSize(true);
789
- } catch (error) {
790
- console.error('AtomixGlass: Error in initial size update:', error);
791
- }
792
-
793
- let resizeObserver: ResizeObserver | null = null;
794
- let fallbackInterval: NodeJS.Timeout | null = null;
795
-
796
- try {
797
- const hasResizeObserver =
798
- typeof ResizeObserver !== 'undefined' &&
799
- typeof ResizeObserver.prototype.observe === 'function';
800
-
801
- if (hasResizeObserver && isValidElement(glassRef.current)) {
802
- try {
803
- resizeObserver = new ResizeObserver(entries => {
804
- try {
805
- for (const entry of entries) {
806
- if (entry.target === glassRef.current) {
807
- updateGlassSize();
808
- break;
809
- }
810
- }
811
- } catch (error) {
812
- console.error('AtomixGlass: Error in ResizeObserver callback:', error);
813
- }
814
- });
815
-
816
- resizeObserver.observe(glassRef.current);
817
- } catch (resizeObserverError) {
818
- console.warn(
819
- 'AtomixGlass: ResizeObserver creation failed, using fallback:',
820
- resizeObserverError
821
- );
822
- fallbackInterval = setInterval(() => {
823
- if (isValidElement(glassRef.current)) {
824
- updateGlassSize();
825
- }
826
- }, 100);
827
- }
828
- } else {
829
- console.warn('AtomixGlass: ResizeObserver not supported, using fallback polling');
830
- fallbackInterval = setInterval(() => {
831
- if (isValidElement(glassRef.current)) {
832
- updateGlassSize();
833
- }
834
- }, 100);
835
- }
836
- } catch (error) {
837
- console.error('AtomixGlass: Error setting up ResizeObserver:', error);
838
- fallbackInterval = setInterval(() => {
839
- if (isValidElement(glassRef.current)) {
840
- updateGlassSize();
841
- }
842
- }, 100);
843
- }
844
-
845
- window.addEventListener('resize', debouncedResizeHandler, { passive: true });
846
-
847
- return () => {
848
- try {
849
- if (rafId !== null) {
850
- cancelAnimationFrame(rafId);
851
- rafId = null;
852
- }
853
-
854
- if (resizeTimeoutId) {
855
- clearTimeout(resizeTimeoutId);
856
- resizeTimeoutId = null;
857
- }
858
-
859
- window.removeEventListener('resize', debouncedResizeHandler);
860
-
861
- if (resizeObserver) {
862
- try {
863
- if (isValidElement(glassRef.current)) {
864
- resizeObserver.unobserve(glassRef.current);
865
- }
866
- resizeObserver.disconnect();
867
- } catch (error) {
868
- console.error('AtomixGlass: Error cleaning up ResizeObserver:', error);
869
- }
870
- resizeObserver = null;
871
- }
872
-
873
- if (fallbackInterval) {
874
- clearInterval(fallbackInterval);
875
- fallbackInterval = null;
876
- }
877
- } catch (error) {
878
- console.error('AtomixGlass: Error in cleanup:', error);
879
- }
880
- };
881
- }, [cornerRadius, enablePerformanceMonitoring]);
882
-
883
- useEffect(() => {
884
- if (!glassRef.current) return;
885
-
886
- const timeoutId = setTimeout(() => {
887
- try {
888
- const rect = glassRef.current?.getBoundingClientRect();
889
- if (rect && rect.width > 0 && rect.height > 0) {
890
- const cornerRadiusOffset = Math.max(0, cornerRadius * 0.1);
891
- const newSize = {
892
- width: Math.round(rect.width + cornerRadiusOffset),
893
- height: Math.round(rect.height + cornerRadiusOffset),
894
- };
895
- setGlassSize(newSize);
896
-
897
- if (enablePerformanceMonitoring) {
898
- console.log('AtomixGlass: Corner radius change triggered size update', {
899
- cornerRadius,
900
- newSize,
901
- });
902
- }
903
- }
904
- } catch (error) {
905
- console.warn('AtomixGlass: Error in corner radius size update:', error);
906
- }
907
- }, 0);
908
-
909
- return () => clearTimeout(timeoutId);
910
- }, [cornerRadius, enablePerformanceMonitoring]);
911
-
912
- const elasticTranslation = useMemo(() => {
913
- if (effectiveDisableEffects) {
914
- return { x: 0, y: 0 };
915
- }
916
- return calculateElasticTranslation();
917
- }, [calculateElasticTranslation, effectiveDisableEffects]);
122
+ overLightConfig,
123
+ globalMousePosition,
124
+ mouseOffset,
125
+ transformStyle,
126
+ handleMouseEnter,
127
+ handleMouseLeave,
128
+ handleMouseDown,
129
+ handleMouseUp,
130
+ handleKeyDown,
131
+ } = useAtomixGlass({
132
+ glassRef,
133
+ contentRef,
134
+ cornerRadius,
135
+ globalMousePosition: externalGlobalMousePosition,
136
+ mouseOffset: externalMouseOffset,
137
+ mouseContainer,
138
+ overLight,
139
+ reducedMotion,
140
+ highContrast,
141
+ disableEffects,
142
+ elasticity,
143
+ onClick,
144
+ debugCornerRadius,
145
+ debugOverLight,
146
+ enablePerformanceMonitoring,
147
+ children,
148
+ });
918
149
 
919
- const directionalScale = useMemo(() => {
920
- if (effectiveDisableEffects) {
921
- return 'scale(1)';
922
- }
923
- return calculateDirectionalScale();
924
- }, [calculateDirectionalScale, effectiveDisableEffects]);
150
+ // Use consistent overLight state from hook
151
+ const isOverLight = overLightConfig.isOverLight;
152
+ const shouldRenderOverLightLayers = enableOverLightLayers && isOverLight;
925
153
 
926
- const transformStyle = useMemo(() => {
927
- if (effectiveDisableEffects) {
928
- return isActive && Boolean(onClick) ? 'scale(0.98)' : 'scale(1)';
929
- }
930
- return `translate(${elasticTranslation.x}px, ${elasticTranslation.y}px) ${isActive && Boolean(onClick) ? 'scale(0.96)' : directionalScale}`;
931
- }, [elasticTranslation, isActive, onClick, directionalScale, effectiveDisableEffects]);
154
+ // Memoize transition duration using design token pattern
155
+ const transitionDuration = useMemo(
156
+ () => (effectiveReducedMotion ? 'none' : 'var(--atomix-transition-duration, 0.2s) ease-out'),
157
+ [effectiveReducedMotion]
158
+ );
932
159
 
160
+ // Calculate base style with transforms (only dynamic values)
161
+ // Performance: willChange is set only when transforms are active and effects are enabled
933
162
  const baseStyle = useMemo(
934
163
  () => ({
935
164
  ...style,
936
- transform: transformStyle,
937
- transition: effectiveReducedMotion ? 'none' : 'all ease-out 0.2s',
938
- willChange: effectiveDisableEffects ? 'auto' : 'transform',
939
- ...(effectiveHighContrast && {
940
- border: '2px solid currentColor',
941
- outline: '2px solid transparent',
942
- outlineOffset: '2px',
165
+ ...(elasticity !== 0 && !effectiveDisableEffects && {
166
+ transform: transformStyle,
167
+ willChange: 'transform',
168
+ }),
169
+ // Reset willChange when effects are disabled to allow browser optimization
170
+ ...(effectiveDisableEffects && {
171
+ willChange: 'auto',
943
172
  }),
944
173
  }),
945
- [style, transformStyle, effectiveReducedMotion, effectiveDisableEffects, effectiveHighContrast]
174
+ [style, transformStyle, effectiveDisableEffects, elasticity]
175
+ );
176
+
177
+ // Build className with state modifiers
178
+ const componentClassName = useMemo(
179
+ () =>
180
+ [
181
+ ATOMIX_GLASS.BASE_CLASS,
182
+ effectiveReducedMotion && `${ATOMIX_GLASS.BASE_CLASS}--reduced-motion`,
183
+ effectiveHighContrast && `${ATOMIX_GLASS.BASE_CLASS}--high-contrast`,
184
+ effectiveDisableEffects && `${ATOMIX_GLASS.BASE_CLASS}--disabled-effects`,
185
+ className,
186
+ ]
187
+ .filter(Boolean)
188
+ .join(' '),
189
+ [effectiveReducedMotion, effectiveHighContrast, effectiveDisableEffects, className]
946
190
  );
947
191
 
192
+ // Calculate position and size styles
193
+ // Optimize: only depend on specific baseStyle properties used
194
+ const baseStylePosition = baseStyle.position;
195
+ const baseStyleTop = baseStyle.top;
196
+ const baseStyleLeft = baseStyle.left;
197
+
948
198
  const positionStyles = useMemo(
949
199
  () => ({
950
- position: (baseStyle.position || 'absolute') as React.CSSProperties['position'],
951
- top: baseStyle.top || 0,
952
- left: baseStyle.left || 0,
200
+ position: (baseStylePosition || 'absolute') as React.CSSProperties['position'],
201
+ top: baseStyleTop || 0,
202
+ left: baseStyleLeft || 0,
953
203
  }),
954
- [baseStyle]
204
+ [baseStylePosition, baseStyleTop, baseStyleLeft]
955
205
  );
956
206
 
957
- const getCurrentElementSize = useCallback(() => {
958
- if (!glassRef.current) {
959
- return { width: 0, height: 0 };
960
- }
961
-
962
- try {
963
- const rect = glassRef.current.getBoundingClientRect();
964
- return {
965
- width: Math.max(rect.width, 0),
966
- height: Math.max(rect.height, 0),
967
- };
968
- } catch (error) {
969
- console.warn('AtomixGlass: Error getting current element size:', error);
970
- return { width: 0, height: 0 };
971
- }
972
- }, []);
973
-
974
- const getTransformedSize = useCallback(() => {
975
- const currentSize = getCurrentElementSize();
976
-
977
- if (effectiveDisableEffects || currentSize.width === 0 || currentSize.height === 0) {
978
- return currentSize;
979
- }
980
-
981
- let scaleX = 1;
982
- let scaleY = 1;
983
-
984
- const simpleScaleMatch = directionalScale.match(/scale\(([^)]+)\)/);
985
- if (simpleScaleMatch && simpleScaleMatch[1]) {
986
- const scaleValue = parseFloat(simpleScaleMatch[1]);
987
- scaleX = scaleValue;
988
- scaleY = scaleValue;
989
- } else {
990
- const scaleXMatch = directionalScale.match(/scaleX\(([^)]+)\)/);
991
- if (scaleXMatch && scaleXMatch[1]) {
992
- scaleX = parseFloat(scaleXMatch[1]);
993
- }
994
-
995
- const scaleYMatch = directionalScale.match(/scaleY\(([^)]+)\)/);
996
- if (scaleYMatch && scaleYMatch[1]) {
997
- scaleY = parseFloat(scaleYMatch[1]);
998
- }
999
- }
1000
-
1001
- const transformedSize = {
1002
- width: currentSize.width * scaleX,
1003
- height: currentSize.height * scaleY,
1004
- };
207
+ // Optimize: only depend on specific baseStyle properties used
208
+ const baseStyleWidth = baseStyle.width;
209
+ const baseStyleHeight = baseStyle.height;
210
+ const glassSizeWidth = glassSize.width;
211
+ const glassSizeHeight = glassSize.height;
1005
212
 
1006
- if (enablePerformanceMonitoring && (scaleX !== 1 || scaleY !== 1)) {
1007
- console.log('AtomixGlass: Scale transformation detected', {
1008
- directionalScale,
1009
- scaleX,
1010
- scaleY,
1011
- originalSize: currentSize,
1012
- transformedSize,
1013
- });
1014
- }
1015
-
1016
- return transformedSize;
1017
- }, [
1018
- getCurrentElementSize,
1019
- directionalScale,
1020
- effectiveDisableEffects,
1021
- enablePerformanceMonitoring,
1022
- ]);
213
+ const adjustedSize = useMemo(
214
+ () => ({
215
+ width:
216
+ baseStylePosition !== 'fixed'
217
+ ? '100%'
218
+ : baseStyleWidth
219
+ ? baseStyleWidth
220
+ : Math.max(glassSizeWidth, 0),
221
+ height:
222
+ baseStylePosition !== 'fixed'
223
+ ? '100%'
224
+ : baseStyleHeight
225
+ ? baseStyleHeight
226
+ : Math.max(glassSizeHeight, 0),
227
+ }),
228
+ [baseStylePosition, baseStyleWidth, baseStyleHeight, glassSizeWidth, glassSizeHeight]
229
+ );
1023
230
 
1024
- const borderLayer1Style = useMemo(() => {
1025
- const borderWidth = 1.5;
1026
231
 
1027
- const adjustedSize = {
1028
- width: baseStyle.position !== 'fixed' ? '100%' : baseStyle.width ? baseStyle.width : Math.max(glassSize.width, 0),
1029
- height: baseStyle.position !== 'fixed' ? '100%' : baseStyle.height ? baseStyle.height : Math.max(glassSize.height, 0),
1030
- };
1031
232
 
1032
- return {
1033
- ...positionStyles,
1034
-
1035
- width: adjustedSize.width,
1036
- height: adjustedSize.height,
1037
- borderRadius: `${Math.max(0, cornerRadius)}px`,
1038
- transform: baseStyle.transform,
1039
- transition: effectiveReducedMotion ? 'none' : baseStyle.transition,
1040
- pointerEvents: 'none' as React.CSSProperties['pointerEvents'],
1041
- mixBlendMode: 'screen' as React.CSSProperties['mixBlendMode'],
1042
- opacity: 0.2,
1043
- padding: `${borderWidth}px`,
1044
- boxSizing: 'border-box' as React.CSSProperties['boxSizing'],
1045
- zIndex: 5,
1046
- WebkitMask: 'linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)',
1047
- WebkitMaskComposite: 'xor',
1048
- maskComposite: 'exclude',
1049
- boxShadow:
1050
- '0 0 0 0.5px rgba(255, 255, 255, 0.5) inset, 0 1px 3px rgba(255, 255, 255, 0.25) inset, 0 1px 4px rgba(0, 0, 0, 0.35)',
1051
- background: `linear-gradient(
1052
- ${135 + mouseOffset.x * 1.2}deg,
1053
- rgba(255, 255, 255, 0.0) 0%,
1054
- rgba(255, 255, 255, ${0.12 + Math.abs(mouseOffset.x) * 0.008}) ${Math.max(10, 33 + mouseOffset.y * 0.3)}%,
1055
- rgba(255, 255, 255, ${0.4 + Math.abs(mouseOffset.x) * 0.012}) ${Math.min(90, 66 + mouseOffset.y * 0.4)}%,
1056
- rgba(255, 255, 255, 0.0) 100%
1057
- )`,
1058
- };
1059
- }, [
1060
- positionStyles,
1061
- glassSize,
1062
- cornerRadius,
1063
- baseStyle,
1064
- mouseOffset,
1065
- effectiveReducedMotion,
1066
- ]);
233
+ // Memoize gradient calculations separately for better performance
234
+ // Extract mouse position values for dependency optimization
235
+ const mouseOffsetX = mouseOffset.x;
236
+ const mouseOffsetY = mouseOffset.y;
1067
237
 
1068
- const borderLayer2Style = useMemo(() => {
1069
- const borderWidth = 1.5;
238
+ const gradientCalculations = useMemo(() => {
239
+ const mx = mouseOffsetX;
240
+ const my = mouseOffsetY;
241
+ const { GRADIENT } = ATOMIX_GLASS.CONSTANTS;
1070
242
 
1071
- const adjustedSize = {
1072
- width: baseStyle.position !== 'fixed' ? '100%' : baseStyle.width ? baseStyle.width : Math.max(glassSize.width, 0),
1073
- height: baseStyle.position !== 'fixed' ? '100%' : baseStyle.height ? baseStyle.height : Math.max(glassSize.height, 0),
1074
- };
1075
-
1076
- return {
1077
- ...positionStyles,
1078
- width: adjustedSize.width,
1079
- height: adjustedSize.height,
1080
- borderRadius: `${Math.max(0, cornerRadius)}px`,
1081
- transform: baseStyle.transform,
1082
- transition: effectiveReducedMotion ? 'none' : baseStyle.transition,
1083
- overflow: 'hidden',
1084
- pointerEvents: 'none' as React.CSSProperties['pointerEvents'],
1085
- zIndex: 6,
1086
- mixBlendMode: 'overlay' as React.CSSProperties['mixBlendMode'],
1087
- padding: `${borderWidth}px`,
1088
- boxSizing: 'border-box' as React.CSSProperties['boxSizing'],
1089
- WebkitMask: 'linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)',
1090
- WebkitMaskComposite: 'xor',
1091
- maskComposite: 'exclude',
1092
- boxShadow:
1093
- '0 0 0 0.5px rgba(255, 255, 255, 0.5) inset, 0 1px 3px rgba(255, 255, 255, 0.25) inset, 0 1px 4px rgba(0, 0, 0, 0.35)',
1094
- background: `linear-gradient(
1095
- ${135 + mouseOffset.x * 1.2}deg,
1096
- rgba(255, 255, 255, 0.0) 0%,
1097
- rgba(255, 255, 255, ${0.32 + Math.abs(mouseOffset.x) * 0.008}) ${Math.max(10, 33 + mouseOffset.y * 0.3)}%,
1098
- rgba(255, 255, 255, ${0.6 + Math.abs(mouseOffset.x) * 0.012}) ${Math.min(90, 66 + mouseOffset.y * 0.4)}%,
1099
- rgba(255, 255, 255, 0.0) 100%
1100
- )`,
1101
- };
1102
- }, [
1103
- positionStyles,
1104
- glassSize,
1105
- cornerRadius,
1106
- baseStyle,
1107
- mouseOffset,
1108
- effectiveReducedMotion,
1109
- ]);
243
+ // Calculate gradient angles and stops (optimized)
244
+ const borderGradientAngle =
245
+ GRADIENT.BASE_ANGLE + mx * GRADIENT.ANGLE_MULTIPLIER;
246
+ const borderStop1 = Math.max(
247
+ GRADIENT.BORDER_STOP_1.MIN,
248
+ GRADIENT.BORDER_STOP_1.BASE + my * GRADIENT.BORDER_STOP_1.MULTIPLIER
249
+ );
250
+ const borderStop2 = Math.min(
251
+ GRADIENT.BORDER_STOP_2.MAX,
252
+ GRADIENT.BORDER_STOP_2.BASE + my * GRADIENT.BORDER_STOP_2.MULTIPLIER
253
+ );
254
+ const borderOpacity1 =
255
+ GRADIENT.BORDER_OPACITY.BASE_1 +
256
+ Math.abs(mx) * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW;
257
+ const borderOpacity2 =
258
+ GRADIENT.BORDER_OPACITY.BASE_2 +
259
+ Math.abs(mx) * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH;
260
+ const borderOpacity3 =
261
+ GRADIENT.BORDER_OPACITY.BASE_3 +
262
+ Math.abs(mx) * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW;
263
+ const borderOpacity4 =
264
+ GRADIENT.BORDER_OPACITY.BASE_4 +
265
+ Math.abs(mx) * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH;
266
+
267
+ // Hover gradient positions
268
+ const hover1X = GRADIENT.CENTER_POSITION + mx / GRADIENT.HOVER_POSITION.DIVISOR_1;
269
+ const hover1Y = GRADIENT.CENTER_POSITION + my / GRADIENT.HOVER_POSITION.DIVISOR_1;
270
+ const hover2X = GRADIENT.CENTER_POSITION + mx / GRADIENT.HOVER_POSITION.DIVISOR_2;
271
+ const hover2Y = GRADIENT.CENTER_POSITION + my / GRADIENT.HOVER_POSITION.DIVISOR_2;
272
+ const hover3X = GRADIENT.CENTER_POSITION + mx * GRADIENT.HOVER_POSITION.MULTIPLIER_3;
273
+ const hover3Y = GRADIENT.CENTER_POSITION + my * GRADIENT.HOVER_POSITION.MULTIPLIER_3;
274
+
275
+ // Base layer positions
276
+ const baseX = GRADIENT.CENTER_POSITION + mx * GRADIENT.BASE_LAYER_MULTIPLIER;
277
+ const baseY = GRADIENT.CENTER_POSITION + my * GRADIENT.BASE_LAYER_MULTIPLIER;
1110
278
 
1111
- const hoverEffect1Style = useMemo(() => {
1112
279
  return {
1113
- ...positionStyles,
1114
- position: 'absolute' as React.CSSProperties['position'],
1115
- inset: '0',
1116
- borderRadius: `${Math.max(0, cornerRadius)}px`,
1117
- transform: baseStyle.transform,
1118
- pointerEvents: 'none' as React.CSSProperties['pointerEvents'],
1119
- transition: effectiveReducedMotion ? 'none' : 'all 0.2s ease-out',
1120
- opacity: isHovered || isActive ? 0.5 : 0,
1121
- background: `radial-gradient(
1122
- circle at ${50 + mouseOffset.x / 2}% ${50 + mouseOffset.y / 2}%,
1123
- rgba(255, 255, 255, 0.5) 0%,
1124
- rgba(255, 255, 255, 0) 50%
1125
- )`,
1126
- mixBlendMode: 'overlay' as React.CSSProperties['mixBlendMode'],
280
+ isOverLight,
281
+ mx,
282
+ my,
283
+ borderGradientAngle,
284
+ borderStop1,
285
+ borderStop2,
286
+ borderOpacity1,
287
+ borderOpacity2,
288
+ borderOpacity3,
289
+ borderOpacity4,
290
+ hover1X,
291
+ hover1Y,
292
+ hover2X,
293
+ hover2Y,
294
+ hover3X,
295
+ hover3Y,
296
+ baseX,
297
+ baseY,
1127
298
  };
1128
- }, [
1129
- positionStyles,
1130
- cornerRadius,
1131
- baseStyle,
1132
- isHovered,
1133
- isActive,
1134
- mouseOffset,
1135
- effectiveReducedMotion,
1136
- ]);
299
+ }, [mouseOffsetX, mouseOffsetY, isOverLight]);
300
+
301
+ // Memoize opacity values separately - using design token values where applicable
302
+ // Optimize: extract overLightConfig.opacity to avoid depending on whole object
303
+ const overLightOpacity = overLightConfig.opacity;
304
+
305
+ // Read opacity design tokens from CSS custom properties
306
+ const opacityValues = useMemo(() => {
307
+ // Get opacity values from CSS custom properties with fallbacks
308
+ // These align with design tokens: --atomix-opacity-50, --atomix-opacity-40, etc.
309
+ let opacity50 = 0.5;
310
+ let opacity40 = 0.4;
311
+ let opacity80 = 0.8;
312
+ let opacity0 = 0;
313
+
314
+ // Try to read from CSS custom properties if available (SSR-safe)
315
+ if (typeof window !== 'undefined' && glassRef.current) {
316
+ try {
317
+ const computedStyle = window.getComputedStyle(glassRef.current);
318
+ const opacity50Value = computedStyle.getPropertyValue('--atomix-opacity-50').trim();
319
+ const opacity40Value = computedStyle.getPropertyValue('--atomix-opacity-40').trim();
320
+ const opacity80Value = computedStyle.getPropertyValue('--atomix-opacity-80').trim();
321
+ const opacity0Value = computedStyle.getPropertyValue('--atomix-opacity-0').trim();
322
+
323
+ if (opacity50Value) opacity50 = parseFloat(opacity50Value) || 0.5;
324
+ if (opacity40Value) opacity40 = parseFloat(opacity40Value) || 0.4;
325
+ if (opacity80Value) opacity80 = parseFloat(opacity80Value) || 0.8;
326
+ if (opacity0Value) opacity0 = parseFloat(opacity0Value) || 0;
327
+ } catch (error) {
328
+ // Fallback to defaults if reading fails
329
+ }
330
+ }
1137
331
 
1138
- const hoverEffect2Style = useMemo(() => {
332
+ const BASE_OVER_LIGHT_OPACITY = opacity40; // Uses design token
333
+ const OVER_OPACITY_MULTIPLIER = 1.1; // Dynamic multiplier for overlay
334
+
1139
335
  return {
1140
- ...positionStyles,
1141
- position: 'absolute' as React.CSSProperties['position'],
1142
- inset: '0',
1143
- borderRadius: `${Math.max(0, cornerRadius)}px`,
1144
- overflow: 'hidden',
1145
- transform: baseStyle.transform,
1146
- pointerEvents: 'none' as React.CSSProperties['pointerEvents'],
1147
- transition: effectiveReducedMotion ? 'none' : 'all 0.2s ease-out',
1148
- opacity: isActive ? 0.5 : 0,
1149
- background: `radial-gradient(
1150
- circle at ${50 + mouseOffset.x / 1.5}% ${50 + mouseOffset.y / 1.5}%,
1151
- rgba(255, 255, 255, 1) 0%,
1152
- rgba(255, 255, 255, 0) 80%
1153
- )`,
1154
- mixBlendMode: 'overlay' as React.CSSProperties['mixBlendMode'],
336
+ hover1: isHovered || isActive ? opacity50 : opacity0,
337
+ hover2: isActive ? opacity50 : opacity0,
338
+ hover3: isHovered ? opacity40 : isActive ? opacity80 : opacity0,
339
+ base: isOverLight ? overLightOpacity || BASE_OVER_LIGHT_OPACITY : opacity0,
340
+ over: isOverLight ? (overLightOpacity || BASE_OVER_LIGHT_OPACITY) * OVER_OPACITY_MULTIPLIER : opacity0,
1155
341
  };
1156
- }, [positionStyles, cornerRadius, baseStyle, isActive, mouseOffset, effectiveReducedMotion]);
342
+ }, [isHovered, isActive, isOverLight, overLightOpacity, glassRef]);
343
+
344
+ // Generate CSS variables for layers (only dynamic values)
345
+ // Optimize: extract specific properties from objects to minimize dependencies
346
+ const gradientIsOverLight = gradientCalculations.isOverLight;
347
+ const gradientMx = gradientCalculations.mx;
348
+ const gradientMy = gradientCalculations.my;
349
+ const gradientBorderGradientAngle = gradientCalculations.borderGradientAngle;
350
+ const gradientBorderStop1 = gradientCalculations.borderStop1;
351
+ const gradientBorderStop2 = gradientCalculations.borderStop2;
352
+ const gradientBorderOpacity1 = gradientCalculations.borderOpacity1;
353
+ const gradientBorderOpacity2 = gradientCalculations.borderOpacity2;
354
+ const gradientBorderOpacity3 = gradientCalculations.borderOpacity3;
355
+ const gradientBorderOpacity4 = gradientCalculations.borderOpacity4;
356
+ const gradientHover1X = gradientCalculations.hover1X;
357
+ const gradientHover1Y = gradientCalculations.hover1Y;
358
+ const gradientHover2X = gradientCalculations.hover2X;
359
+ const gradientHover2Y = gradientCalculations.hover2Y;
360
+ const gradientHover3X = gradientCalculations.hover3X;
361
+ const gradientHover3Y = gradientCalculations.hover3Y;
362
+ const gradientBaseX = gradientCalculations.baseX;
363
+ const gradientBaseY = gradientCalculations.baseY;
364
+
365
+ const positionStylesPosition = positionStyles.position;
366
+ const positionStylesTop = positionStyles.top;
367
+ const positionStylesLeft = positionStyles.left;
368
+
369
+ const adjustedSizeWidth = adjustedSize.width;
370
+ const adjustedSizeHeight = adjustedSize.height;
371
+
372
+ const baseStyleTransform = baseStyle.transform;
373
+ const opacityValuesHover1 = opacityValues.hover1;
374
+ const opacityValuesHover2 = opacityValues.hover2;
375
+ const opacityValuesHover3 = opacityValues.hover3;
376
+ const opacityValuesBase = opacityValues.base;
377
+ const opacityValuesOver = opacityValues.over;
378
+
379
+ const glassVars = useMemo(() => {
380
+ // RGB color values for rgba() functions
381
+ // Note: CSS doesn't support rgba(var(--rgb), opacity) syntax, so we use direct values
382
+ // These values align with design tokens: --atomix-white-rgb and --atomix-black-rgb
383
+ // The actual RGB values are defined in SCSS and should match these fallbacks
384
+ // TODO: Consider reading from CSS custom properties if browser support improves
385
+ const whiteColor = '255, 255, 255'; // Matches --atomix-white-rgb design token
386
+ const blackColor = '0, 0, 0'; // Matches --atomix-black-rgb design token
1157
387
 
1158
- const hoverEffect3Style = useMemo(() => {
1159
388
  return {
1160
- ...positionStyles,
1161
- position: 'absolute' as React.CSSProperties['position'],
1162
- inset: '0',
1163
- transform: baseStyle.transform,
1164
- borderRadius: `${Math.max(0, cornerRadius)}px`,
1165
- pointerEvents: 'none' as React.CSSProperties['pointerEvents'],
1166
- transition: effectiveReducedMotion ? 'none' : 'all 0.2s ease-out',
1167
- opacity: isHovered ? 0.4 : isActive ? 0.8 : 0,
1168
- background: `radial-gradient(
1169
- circle at ${50 + mouseOffset.x}% ${50 + mouseOffset.y}%,
1170
- rgba(255, 255, 255, 1) 0%,
1171
- rgba(255, 255, 255, 0) 100%
1172
- )`,
1173
- mixBlendMode: 'overlay' as React.CSSProperties['mixBlendMode'],
1174
- };
389
+ // Standard CSS custom properties for dynamic values
390
+ '--atomix-glass-radius': `${effectiveCornerRadius}px`,
391
+ '--atomix-glass-transform': baseStyleTransform || 'none',
392
+ '--atomix-glass-transition': effectiveReducedMotion ? 'none' : transitionDuration,
393
+ '--atomix-glass-position': positionStylesPosition,
394
+ '--atomix-glass-top': positionStylesTop !== 'fixed' ? `${positionStylesTop}px` : '0',
395
+ '--atomix-glass-left': positionStylesLeft !== 'fixed' ? `${positionStylesLeft}px` : '0',
396
+ '--atomix-glass-width':
397
+ baseStylePosition !== 'fixed' ? adjustedSizeWidth : `${adjustedSizeWidth}px`,
398
+ '--atomix-glass-height':
399
+ baseStylePosition !== 'fixed' ? adjustedSizeHeight : `${adjustedSizeHeight}px`,
400
+ // Border width: Use spacing token for consistency
401
+ '--atomix-glass-border-width': 'var(--atomix-spacing-0-5, 0.09375rem)',
402
+ '--atomix-glass-blend-mode': gradientIsOverLight ? 'multiply' : 'overlay',
403
+ // Dynamic gradients and backgrounds
404
+ // Note: RGB values use design token-aligned constants (white: 255,255,255; black: 0,0,0)
405
+ '--atomix-glass-border-gradient-1': `linear-gradient(${gradientBorderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${gradientBorderOpacity1}) ${gradientBorderStop1}%, rgba(${whiteColor}, ${gradientBorderOpacity2}) ${gradientBorderStop2}%, rgba(${whiteColor}, 0) 100%)`,
406
+ '--atomix-glass-border-gradient-2': `linear-gradient(${gradientBorderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${gradientBorderOpacity3}) ${gradientBorderStop1}%, rgba(${whiteColor}, ${gradientBorderOpacity4}) ${gradientBorderStop2}%, rgba(${whiteColor}, 0) 100%)`,
407
+ '--atomix-glass-hover-1-opacity': opacityValuesHover1,
408
+ '--atomix-glass-hover-1-gradient': gradientIsOverLight
409
+ ? `radial-gradient(circle at ${gradientHover1X}% ${gradientHover1Y}%, 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}%)`
410
+ : `radial-gradient(circle at ${gradientHover1X}% ${gradientHover1Y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.WHITE_STOP}%)`,
411
+ '--atomix-glass-hover-2-opacity': opacityValuesHover2,
412
+ '--atomix-glass-hover-2-gradient': gradientIsOverLight
413
+ ? `radial-gradient(circle at ${gradientHover2X}% ${gradientHover2Y}%, 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}%)`
414
+ : `radial-gradient(circle at ${gradientHover2X}% ${gradientHover2Y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.WHITE_STOP}%)`,
415
+ '--atomix-glass-hover-3-opacity': opacityValuesHover3,
416
+ '--atomix-glass-hover-3-gradient': gradientIsOverLight
417
+ ? `radial-gradient(circle at ${gradientHover3X}% ${gradientHover3Y}%, 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}%)`
418
+ : `radial-gradient(circle at ${gradientHover3X}% ${gradientHover3Y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.WHITE_STOP}%)`,
419
+ '--atomix-glass-base-opacity': opacityValuesBase,
420
+ '--atomix-glass-base-gradient': gradientIsOverLight
421
+ ? `linear-gradient(${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.ANGLE}deg, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_START_BASE + gradientMx * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_START_MULTIPLIER}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_MID_BASE + gradientMy * 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 + Math.abs(gradientMx) * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_END_MULTIPLIER}) 100%)`
422
+ : `rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.WHITE_OPACITY})`,
423
+ '--atomix-glass-overlay-opacity': opacityValuesOver,
424
+ '--atomix-glass-overlay-gradient': gradientIsOverLight
425
+ ? `radial-gradient(circle at ${gradientBaseX}% ${gradientBaseY}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_START_BASE + Math.abs(gradientMx) * 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 + Math.abs(gradientMy) * ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_END_MULTIPLIER}) 100%)`
426
+ : `rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.WHITE_OPACITY})`,
427
+ } as React.CSSProperties;
1175
428
  }, [
1176
- positionStyles,
1177
- cornerRadius,
1178
- baseStyle,
1179
- isHovered,
1180
- isActive,
1181
- mouseOffset,
429
+ // Position styles - only specific properties
430
+ positionStylesPosition,
431
+ positionStylesTop,
432
+ positionStylesLeft,
433
+ // Adjusted size - only specific properties
434
+ adjustedSizeWidth,
435
+ adjustedSizeHeight,
436
+ // Base style - only transform property
437
+ baseStyleTransform,
438
+ baseStylePosition,
439
+ // Other values
440
+ effectiveCornerRadius,
1182
441
  effectiveReducedMotion,
442
+ transitionDuration,
443
+ // Gradient calculations - extracted properties
444
+ gradientIsOverLight,
445
+ gradientMx,
446
+ gradientMy,
447
+ gradientBorderGradientAngle,
448
+ gradientBorderStop1,
449
+ gradientBorderStop2,
450
+ gradientBorderOpacity1,
451
+ gradientBorderOpacity2,
452
+ gradientBorderOpacity3,
453
+ gradientBorderOpacity4,
454
+ gradientHover1X,
455
+ gradientHover1Y,
456
+ gradientHover2X,
457
+ gradientHover2Y,
458
+ gradientHover3X,
459
+ gradientHover3Y,
460
+ gradientBaseX,
461
+ gradientBaseY,
462
+ // Opacity values - extracted properties
463
+ opacityValuesHover1,
464
+ opacityValuesHover2,
465
+ opacityValuesHover3,
466
+ opacityValuesBase,
467
+ opacityValuesOver,
1183
468
  ]);
1184
469
 
1185
470
  return (
1186
471
  <div
1187
- style={{ ...positionStyles, position: 'relative' }}
472
+ className={componentClassName}
473
+ style={glassVars}
1188
474
  role={role || (onClick ? 'button' : undefined)}
1189
475
  tabIndex={onClick ? (tabIndex ?? 0) : tabIndex}
1190
476
  aria-label={ariaLabel}
1191
477
  aria-describedby={ariaDescribedBy}
1192
- aria-disabled={onClick ? false : undefined}
1193
- onKeyDown={
1194
- onClick
1195
- ? e => {
1196
- if (e.key === 'Enter' || e.key === ' ') {
1197
- e.preventDefault();
1198
- onClick();
1199
- }
1200
- }
1201
- : undefined
1202
- }
478
+ aria-disabled={onClick && effectiveDisableEffects ? true : onClick ? false : undefined}
479
+ aria-pressed={onClick && isActive ? true : onClick ? false : undefined}
480
+ onKeyDown={onClick ? handleKeyDown : undefined}
1203
481
  >
1204
- <div
1205
- className={`u-bg-dark ${overLight ? 'u-opacity-50' : 'u-opacity-0'}`}
1206
- style={{
1207
- ...positionStyles,
1208
- height: glassSize.height,
1209
- width: glassSize.width,
1210
- borderRadius: `${cornerRadius}px`,
1211
- transform: baseStyle.transform,
1212
- transition: baseStyle.transition,
1213
- willChange: 'transform',
1214
- }}
1215
- />
1216
- <div
1217
- className={`u-bg-black ${overLight ? 'u-opacity-25' : 'u-opacity-0'}`}
1218
- style={{
1219
- ...positionStyles,
1220
- height: glassSize.height,
1221
- width: glassSize.width,
1222
- borderRadius: `${cornerRadius}px`,
1223
- transform: baseStyle.transform,
1224
- transition: baseStyle.transition,
1225
- mixBlendMode: 'overlay',
1226
- pointerEvents: 'none',
1227
- willChange: 'transform',
1228
- }}
1229
- />
482
+
1230
483
 
1231
- <GlassContainer
484
+ <AtomixGlassContainer
1232
485
  ref={glassRef}
486
+ contentRef={contentRef}
1233
487
  className={className}
1234
- style={{
1235
- ...baseStyle,
1236
- transform: baseStyle.transform,
1237
- }}
1238
- cornerRadius={cornerRadius}
488
+ style={baseStyle}
489
+ cornerRadius={effectiveCornerRadius}
1239
490
  displacementScale={
1240
- effectiveDisableEffects ? 0 : overLight ? displacementScale * 0.5 : displacementScale
491
+ effectiveDisableEffects
492
+ ? 0
493
+ : mode === 'shader'
494
+ ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_DISPLACEMENT
495
+ : overLightConfig.isOverLight
496
+ ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.OVER_LIGHT_DISPLACEMENT
497
+ : displacementScale
1241
498
  }
1242
499
  blurAmount={effectiveDisableEffects ? 0 : blurAmount}
1243
- saturation={effectiveHighContrast ? 200 : saturation}
1244
- aberrationIntensity={effectiveDisableEffects ? 0 : aberrationIntensity}
500
+ saturation={
501
+ effectiveHighContrast
502
+ ? ATOMIX_GLASS.CONSTANTS.SATURATION.HIGH_CONTRAST
503
+ : overLightConfig.isOverLight
504
+ ? saturation * overLightConfig.saturationBoost
505
+ : saturation
506
+ }
507
+ aberrationIntensity={
508
+ effectiveDisableEffects
509
+ ? 0
510
+ : mode === 'shader'
511
+ ? aberrationIntensity * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_ABERRATION
512
+ : aberrationIntensity
513
+ }
1245
514
  glassSize={glassSize}
1246
515
  padding={padding}
1247
516
  mouseOffset={effectiveDisableEffects ? { x: 0, y: 0 } : mouseOffset}
1248
- globalMousePos={effectiveDisableEffects ? { x: 0, y: 0 } : globalMousePos}
1249
- onMouseEnter={() => setIsHovered(true)}
1250
- onMouseLeave={() => setIsHovered(false)}
1251
- onMouseDown={() => setIsActive(true)}
1252
- onMouseUp={() => setIsActive(false)}
517
+ globalMousePosition={effectiveDisableEffects ? { x: 0, y: 0 } : globalMousePosition}
518
+ onMouseEnter={handleMouseEnter}
519
+ onMouseLeave={handleMouseLeave}
520
+ onMouseDown={handleMouseDown}
521
+ onMouseUp={handleMouseUp}
1253
522
  active={isActive}
1254
523
  isHovered={isHovered}
1255
524
  isActive={isActive}
1256
- overLight={overLight}
525
+ overLight={overLightConfig.isOverLight}
1257
526
  onClick={onClick}
1258
- mode={effectiveDisableEffects ? 'standard' : mode}
527
+ mode={mode}
1259
528
  transform={baseStyle.transform}
1260
529
  effectiveDisableEffects={effectiveDisableEffects}
1261
530
  effectiveReducedMotion={effectiveReducedMotion}
531
+ shaderVariant={shaderVariant}
532
+ elasticity={elasticity}
533
+ enableLiquidBlur={enableLiquidBlur}
1262
534
  >
1263
535
  {children}
1264
- </GlassContainer>
1265
-
1266
- <span style={borderLayer1Style} />
1267
-
1268
- <span style={borderLayer2Style} />
1269
-
536
+ </AtomixGlassContainer>
1270
537
  {Boolean(onClick) && (
1271
538
  <>
1272
- <div style={hoverEffect1Style} />
1273
- <div style={hoverEffect2Style} />
1274
- <div style={hoverEffect3Style} />
539
+ {/* Hover layers - opacity and background set via CSS variables in SCSS */}
540
+ <div className={ATOMIX_GLASS.HOVER_1_CLASS} />
541
+ <div className={ATOMIX_GLASS.HOVER_2_CLASS} />
542
+ <div className={ATOMIX_GLASS.HOVER_3_CLASS} />
543
+ </>
544
+ )}
545
+
546
+ {/* Background layers for over-light mode */}
547
+ {/* Static styles (pointer-events, will-change) are in SCSS */}
548
+ <div
549
+ className={[
550
+ ATOMIX_GLASS.BACKGROUND_LAYER_CLASS,
551
+ ATOMIX_GLASS.BACKGROUND_LAYER_DARK_CLASS,
552
+ isOverLight
553
+ ? ATOMIX_GLASS.BACKGROUND_LAYER_OVER_LIGHT_CLASS
554
+ : ATOMIX_GLASS.BACKGROUND_LAYER_HIDDEN_CLASS,
555
+ ]
556
+ .filter(Boolean)
557
+ .join(' ')}
558
+ style={{
559
+ ...positionStyles,
560
+ height: adjustedSize.height,
561
+ width: adjustedSize.width,
562
+ borderRadius: `${effectiveCornerRadius}px`,
563
+ transform: baseStyle.transform,
564
+ transition: baseStyle.transition,
565
+ }}
566
+ />
567
+ <div
568
+ className={[
569
+ ATOMIX_GLASS.BACKGROUND_LAYER_CLASS,
570
+ ATOMIX_GLASS.BACKGROUND_LAYER_BLACK_CLASS,
571
+ isOverLight
572
+ ? ATOMIX_GLASS.BACKGROUND_LAYER_OVER_LIGHT_CLASS
573
+ : ATOMIX_GLASS.BACKGROUND_LAYER_HIDDEN_CLASS,
574
+ ]
575
+ .filter(Boolean)
576
+ .join(' ')}
577
+ style={{
578
+ ...positionStyles,
579
+ height: adjustedSize.height,
580
+ width: adjustedSize.width,
581
+ borderRadius: `${effectiveCornerRadius}px`,
582
+ transform: baseStyle.transform,
583
+ transition: baseStyle.transition,
584
+ }}
585
+ />
586
+ {shouldRenderOverLightLayers && (
587
+ <>
588
+ {/* Base and overlay layers - opacity and background set via CSS variables in SCSS */}
589
+ <div className={ATOMIX_GLASS.BASE_LAYER_CLASS} />
590
+ <div className={ATOMIX_GLASS.OVERLAY_LAYER_CLASS} />
591
+ {/* Overlay highlight - opacity and background are dynamic, calculated inline */}
592
+ <div
593
+ className={ATOMIX_GLASS.OVERLAY_HIGHLIGHT_CLASS}
594
+ style={{
595
+ opacity:
596
+ opacityValues.over *
597
+ ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.OPACITY_MULTIPLIER,
598
+ background: `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}%)`,
599
+ }}
600
+ />
601
+ </>
602
+ )}
603
+ {enableBorderEffect && (
604
+ <>
605
+ {/* Border elements - all styles (static and dynamic via CSS variables) are in SCSS */}
606
+ {/* Position, size, transform, transition, border-radius all use CSS variables set in glassVars */}
607
+ <span className={ATOMIX_GLASS.BORDER_1_CLASS} />
608
+ <span className={ATOMIX_GLASS.BORDER_2_CLASS} />
1275
609
  </>
1276
610
  )}
1277
611
  </div>