@shohojdhara/atomix 0.2.4 → 0.2.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 (214) hide show
  1. package/README.md +19 -0
  2. package/dist/atomix.css +1300 -1418
  3. package/dist/atomix.min.css +3 -3
  4. package/dist/index.d.ts +1259 -874
  5. package/dist/index.esm.js +16256 -26366
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/index.js +15691 -22295
  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 +15036 -0
  12. package/dist/themes/applemix.min.css +72 -0
  13. package/dist/themes/boomdevs.css +1300 -1419
  14. package/dist/themes/boomdevs.min.css +3 -3
  15. package/dist/themes/esrar.css +1301 -1419
  16. package/dist/themes/esrar.min.css +3 -3
  17. package/dist/themes/flashtrade.css +15187 -0
  18. package/dist/themes/flashtrade.min.css +86 -0
  19. package/dist/themes/mashroom.css +1299 -1417
  20. package/dist/themes/mashroom.min.css +5 -5
  21. package/dist/themes/shaj-default.css +1300 -1418
  22. package/dist/themes/shaj-default.min.css +3 -3
  23. package/package.json +6 -17
  24. package/src/components/Accordion/Accordion.stories.tsx +4 -26
  25. package/src/components/Accordion/Accordion.tsx +21 -12
  26. package/src/components/AtomixGlass/AtomixGlass.test.tsx +106 -72
  27. package/src/components/AtomixGlass/AtomixGlass.tsx +485 -1215
  28. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +399 -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 +404 -236
  34. package/src/components/AtomixGlass/{AtomixGlass.stories.tsx → stories/AtomixGlass.stories.tsx} +55 -35
  35. package/src/components/AtomixGlass/stories/Examples.stories.tsx +57 -89
  36. package/src/components/AtomixGlass/stories/Modes.stories.tsx +149 -149
  37. package/src/components/AtomixGlass/stories/Playground.stories.tsx +95 -32
  38. package/src/components/AtomixGlass/stories/ShaderVariants.stories.tsx +0 -2
  39. package/src/components/AtomixGlass/stories/shared-components.tsx +9 -18
  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 +74 -54
  44. package/src/components/Badge/Badge.tsx +8 -12
  45. package/src/components/Breadcrumb/Breadcrumb.tsx +23 -4
  46. package/src/components/Button/Button.tsx +3 -5
  47. package/src/components/Callout/Callout.stories.tsx +86 -35
  48. package/src/components/Callout/Callout.tsx +4 -0
  49. package/src/components/Card/Card.stories.tsx +89 -85
  50. package/src/components/Card/Card.tsx +15 -4
  51. package/src/components/Card/ElevationCard.tsx +2 -0
  52. package/src/components/Chart/AnimatedChart.tsx +179 -156
  53. package/src/components/Chart/AreaChart.tsx +123 -12
  54. package/src/components/Chart/BarChart.tsx +91 -100
  55. package/src/components/Chart/BaseChart.tsx +80 -0
  56. package/src/components/Chart/BubbleChart.tsx +114 -290
  57. package/src/components/Chart/CandlestickChart.tsx +282 -622
  58. package/src/components/Chart/Chart.stories.tsx +576 -179
  59. package/src/components/Chart/Chart.tsx +374 -75
  60. package/src/components/Chart/ChartRenderer.tsx +371 -220
  61. package/src/components/Chart/ChartToolbar.tsx +372 -61
  62. package/src/components/Chart/ChartTooltip.tsx +33 -18
  63. package/src/components/Chart/DonutChart.tsx +172 -254
  64. package/src/components/Chart/FunnelChart.tsx +169 -240
  65. package/src/components/Chart/GaugeChart.tsx +224 -392
  66. package/src/components/Chart/HeatmapChart.tsx +302 -440
  67. package/src/components/Chart/LineChart.tsx +148 -103
  68. package/src/components/Chart/MultiAxisChart.tsx +267 -395
  69. package/src/components/Chart/PieChart.tsx +114 -64
  70. package/src/components/Chart/RadarChart.tsx +202 -218
  71. package/src/components/Chart/ScatterChart.tsx +111 -97
  72. package/src/components/Chart/TreemapChart.tsx +147 -222
  73. package/src/components/Chart/WaterfallChart.tsx +253 -291
  74. package/src/components/Chart/index.ts +11 -9
  75. package/src/components/Chart/types.ts +85 -9
  76. package/src/components/Chart/utils.ts +66 -0
  77. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +121 -11
  78. package/src/components/ColorModeToggle/ColorModeToggle.tsx +149 -45
  79. package/src/components/ColorModeToggle/index.ts +1 -1
  80. package/src/components/Countdown/Countdown.tsx +4 -0
  81. package/src/components/DataTable/DataTable.tsx +2 -1
  82. package/src/components/DatePicker/DatePicker.stories.tsx +0 -11
  83. package/src/components/DatePicker/DatePicker.tsx +3 -9
  84. package/src/components/DatePicker/types.ts +5 -0
  85. package/src/components/Dropdown/Dropdown.stories.tsx +32 -25
  86. package/src/components/Dropdown/Dropdown.tsx +26 -28
  87. package/src/components/EdgePanel/EdgePanel.stories.tsx +13 -15
  88. package/src/components/EdgePanel/EdgePanel.tsx +20 -5
  89. package/src/components/Footer/Footer.stories.tsx +187 -60
  90. package/src/components/Footer/Footer.test.tsx +134 -0
  91. package/src/components/Footer/Footer.tsx +133 -34
  92. package/src/components/Footer/FooterLink.tsx +1 -1
  93. package/src/components/Footer/FooterSection.tsx +53 -36
  94. package/src/components/Footer/FooterSocialLink.tsx +32 -29
  95. package/src/components/Footer/README.md +82 -3
  96. package/src/components/Footer/index.ts +1 -1
  97. package/src/components/Form/Checkbox.stories.tsx +13 -5
  98. package/src/components/Form/Checkbox.tsx +3 -6
  99. package/src/components/Form/Form.stories.tsx +10 -3
  100. package/src/components/Form/Form.tsx +2 -0
  101. package/src/components/Form/FormGroup.tsx +2 -1
  102. package/src/components/Form/Input.stories.tsx +12 -11
  103. package/src/components/Form/Input.tsx +97 -95
  104. package/src/components/Form/Radio.stories.tsx +22 -7
  105. package/src/components/Form/Radio.tsx +3 -6
  106. package/src/components/Form/Select.stories.tsx +21 -6
  107. package/src/components/Form/Select.tsx +3 -5
  108. package/src/components/Form/Textarea.stories.tsx +13 -11
  109. package/src/components/Form/Textarea.tsx +88 -86
  110. package/src/components/Hero/Hero.stories.tsx +2 -3
  111. package/src/components/Hero/Hero.tsx +5 -6
  112. package/src/components/Icon/Icon.tsx +12 -1
  113. package/src/components/List/List.tsx +2 -1
  114. package/src/components/List/ListGroup.tsx +2 -1
  115. package/src/components/Messages/Messages.tsx +3 -2
  116. package/src/components/Modal/Modal.stories.tsx +48 -34
  117. package/src/components/Modal/Modal.tsx +19 -23
  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.tsx +6 -1
  121. package/src/components/Navigation/Nav/NavDropdown.tsx +10 -1
  122. package/src/components/Navigation/Navbar/Navbar.tsx +4 -1
  123. package/src/components/Navigation/SideMenu/SideMenu.tsx +3 -2
  124. package/src/components/Pagination/Pagination.stories.tsx +13 -6
  125. package/src/components/Pagination/Pagination.tsx +7 -6
  126. package/src/components/PhotoViewer/PhotoViewer.tsx +2 -1
  127. package/src/components/Popover/Popover.stories.tsx +32 -24
  128. package/src/components/Popover/Popover.tsx +4 -1
  129. package/src/components/ProductReview/ProductReview.tsx +8 -2
  130. package/src/components/Progress/Progress.tsx +2 -1
  131. package/src/components/Rating/Rating.stories.tsx +11 -6
  132. package/src/components/Rating/Rating.tsx +3 -5
  133. package/src/components/River/River.tsx +5 -5
  134. package/src/components/SectionIntro/SectionIntro.tsx +8 -2
  135. package/src/components/Slider/Slider.stories.tsx +4 -4
  136. package/src/components/Slider/Slider.tsx +4 -3
  137. package/src/components/Spinner/Spinner.tsx +2 -1
  138. package/src/components/Steps/Steps.stories.tsx +5 -4
  139. package/src/components/Steps/Steps.tsx +8 -5
  140. package/src/components/Tab/Tab.stories.tsx +4 -3
  141. package/src/components/Tab/Tab.tsx +8 -6
  142. package/src/components/Testimonial/Testimonial.tsx +8 -2
  143. package/src/components/Todo/Todo.tsx +2 -1
  144. package/src/components/Toggle/Toggle.stories.tsx +5 -4
  145. package/src/components/Toggle/Toggle.tsx +8 -5
  146. package/src/components/Tooltip/Tooltip.stories.tsx +40 -30
  147. package/src/components/Tooltip/Tooltip.tsx +9 -2
  148. package/src/components/Upload/Upload.stories.tsx +252 -0
  149. package/src/components/Upload/Upload.tsx +92 -53
  150. package/src/components/VideoPlayer/VideoPlayer.tsx +3 -1
  151. package/src/components/index.ts +0 -4
  152. package/src/layouts/Grid/Grid.stories.tsx +10 -23
  153. package/src/layouts/Grid/Grid.tsx +20 -1
  154. package/src/layouts/Grid/GridCol.tsx +76 -48
  155. package/src/lib/composables/useAtomixGlass.ts +861 -44
  156. package/src/lib/composables/useBarChart.ts +13 -6
  157. package/src/lib/composables/useChart.ts +17 -13
  158. package/src/lib/composables/useChartExport.ts +19 -78
  159. package/src/lib/composables/useChartToolbar.ts +0 -1
  160. package/src/lib/composables/useEdgePanel.ts +111 -103
  161. package/src/lib/composables/useFooter.ts +3 -3
  162. package/src/lib/composables/useGlassContainer.ts +16 -7
  163. package/src/lib/composables/useLineChart.ts +8 -1
  164. package/src/lib/composables/useRiver.ts +5 -0
  165. package/src/lib/composables/useSlider.ts +62 -24
  166. package/src/lib/composables/useVideoPlayer.ts +60 -63
  167. package/src/lib/constants/components.ts +146 -32
  168. package/src/lib/types/components.ts +258 -10
  169. package/src/lib/utils/displacement-generator.ts +55 -49
  170. package/src/lib/utils/icons.ts +1 -1
  171. package/src/lib/utils/index.ts +16 -10
  172. package/src/styles/01-settings/_settings.accordion.scss +19 -19
  173. package/src/styles/01-settings/_settings.animations.scss +5 -5
  174. package/src/styles/01-settings/_settings.avatar-group.scss +1 -1
  175. package/src/styles/01-settings/_settings.avatar.scss +17 -17
  176. package/src/styles/01-settings/_settings.background.scss +1 -4
  177. package/src/styles/01-settings/_settings.badge.scss +1 -1
  178. package/src/styles/01-settings/_settings.breadcrumb.scss +1 -1
  179. package/src/styles/01-settings/_settings.card.scss +1 -1
  180. package/src/styles/01-settings/_settings.chart.scss +65 -2
  181. package/src/styles/01-settings/_settings.dropdown.scss +1 -1
  182. package/src/styles/01-settings/_settings.footer.scss +35 -42
  183. package/src/styles/01-settings/_settings.input.scss +1 -1
  184. package/src/styles/01-settings/_settings.list.scss +1 -1
  185. package/src/styles/01-settings/_settings.rating.scss +1 -1
  186. package/src/styles/01-settings/_settings.tabs.scss +1 -1
  187. package/src/styles/01-settings/_settings.upload.scss +6 -5
  188. package/src/styles/02-tools/_tools.animations.scss +4 -5
  189. package/src/styles/02-tools/_tools.background.scss +1 -13
  190. package/src/styles/02-tools/_tools.glass.scss +0 -1
  191. package/src/styles/02-tools/_tools.utility-api.scss +42 -34
  192. package/src/styles/03-generic/_generic.root.scss +1 -4
  193. package/src/styles/04-elements/_elements.body.scss +0 -1
  194. package/src/styles/06-components/_components.atomix-glass.scss +217 -39
  195. package/src/styles/06-components/_components.badge.scss +6 -8
  196. package/src/styles/06-components/_components.button.scss +8 -3
  197. package/src/styles/06-components/_components.card.scss +2 -14
  198. package/src/styles/06-components/_components.chart.scss +969 -1449
  199. package/src/styles/06-components/_components.color-mode-toggle.scss +43 -6
  200. package/src/styles/06-components/_components.dropdown.scss +19 -7
  201. package/src/styles/06-components/_components.edge-panel.scss +4 -2
  202. package/src/styles/06-components/_components.footer.scss +166 -85
  203. package/src/styles/06-components/_components.input.scss +8 -9
  204. package/src/styles/06-components/_components.list.scss +1 -0
  205. package/src/styles/06-components/_components.modal.scss +5 -3
  206. package/src/styles/06-components/_components.skeleton.scss +8 -6
  207. package/src/styles/06-components/_components.upload.scss +219 -4
  208. package/src/styles/06-components/old.chart.styles.scss +1 -30
  209. package/src/styles/99-utilities/_utilities.opacity.scss +1 -1
  210. package/src/styles/99-utilities/_utilities.scss +1 -1
  211. package/src/components/Chart/AdvancedChart.tsx +0 -624
  212. package/src/components/Chart/LineChartNew.tsx +0 -167
  213. package/src/components/Chart/RealTimeChart.tsx +0 -436
  214. package/src/components/DatePicker/DatePicker copy.tsx +0 -551
@@ -1,555 +1,8 @@
1
- import {
2
- type CSSProperties,
3
- forwardRef,
4
- useCallback,
5
- useEffect,
6
- useId,
7
- useRef,
8
- useState,
9
- useMemo,
10
- } from 'react';
11
- import {
12
- ShaderDisplacementGenerator,
13
- fragmentShaders,
14
- type FragmentShaderType,
15
- } from './shader-utils';
16
- import { displacementMap, polarDisplacementMap, prominentDisplacementMap } from './utils';
17
-
18
- // Types
19
- type DisplacementMode = 'standard' | 'polar' | 'prominent' | 'shader';
20
- type MousePosition = { x: number; y: number };
21
- type GlassSize = { width: number; height: number };
22
- type OverLightConfig =
23
- | boolean
24
- | 'auto'
25
- | { threshold?: number; opacity?: number; contrast?: number };
26
- type OverLightObjectConfig = { threshold?: number; opacity?: number; contrast?: number };
27
-
28
- // Constants
29
- const ACTIVATION_ZONE = 200;
30
- const MIN_BLUR = 0.1;
31
- const MOUSE_INFLUENCE_DIVISOR = 100;
32
- const EDGE_FADE_PIXELS = 2;
33
-
34
- // Helper functions with validation
35
- const calculateDistance = (pos1: MousePosition, pos2: MousePosition): number => {
36
- if (
37
- !pos1 ||
38
- !pos2 ||
39
- typeof pos1.x !== 'number' ||
40
- typeof pos1.y !== 'number' ||
41
- typeof pos2.x !== 'number' ||
42
- typeof pos2.y !== 'number'
43
- ) {
44
- return 0;
45
- }
46
- const deltaX = pos1.x - pos2.x;
47
- const deltaY = pos1.y - pos2.y;
48
- return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
49
- };
50
-
51
- const calculateElementCenter = (rect: DOMRect | null): MousePosition => {
52
- if (!rect) {
53
- return { x: 0, y: 0 };
54
- }
55
- return {
56
- x: rect.left + rect.width / 2,
57
- y: rect.top + rect.height / 2,
58
- };
59
- };
60
-
61
- const calculateMouseInfluence = (mouseOffset: MousePosition): number => {
62
- if (!mouseOffset || typeof mouseOffset.x !== 'number' || typeof mouseOffset.y !== 'number') {
63
- return 0;
64
- }
65
- return (
66
- Math.sqrt(mouseOffset.x * mouseOffset.x + mouseOffset.y * mouseOffset.y) /
67
- MOUSE_INFLUENCE_DIVISOR
68
- );
69
- };
70
-
71
- const clampBlur = (value: number): number => {
72
- if (typeof value !== 'number' || isNaN(value)) {
73
- return MIN_BLUR;
74
- }
75
- return Math.max(MIN_BLUR, value);
76
- };
77
-
78
- const validateGlassSize = (size: GlassSize): boolean => {
79
- return (
80
- size &&
81
- typeof size.width === 'number' &&
82
- typeof size.height === 'number' &&
83
- size.width > 0 &&
84
- size.height > 0
85
- );
86
- };
87
-
88
-
89
- const getDisplacementMap = (mode: DisplacementMode, shaderMapUrl?: string): string => {
90
- switch (mode) {
91
- case 'standard':
92
- return displacementMap;
93
- case 'polar':
94
- return polarDisplacementMap;
95
- case 'prominent':
96
- return prominentDisplacementMap;
97
- case 'shader':
98
- return shaderMapUrl || displacementMap;
99
- default:
100
- console.warn('AtomixGlass: Invalid displacement mode');
101
- return displacementMap;
102
- }
103
- };
104
-
105
- interface GlassFilterProps {
106
- id: string;
107
- displacementScale: number;
108
- aberrationIntensity: number;
109
- mode: DisplacementMode;
110
- shaderMapUrl?: string;
111
- }
112
-
113
- const GlassFilter: React.FC<GlassFilterProps> = ({
114
- id,
115
- displacementScale,
116
- aberrationIntensity,
117
- mode,
118
- shaderMapUrl,
119
- }) => (
120
- <svg
121
- style={{
122
- position: 'absolute',
123
- width: '100%',
124
- height: '100%',
125
- inset: 0,
126
- visibility: 'hidden',
127
- opacity: 0,
128
- }}
129
- aria-hidden="true"
130
- >
131
- <defs>
132
- <radialGradient id={`${id}-edge-mask`} cx="50%" cy="50%" r="50%">
133
- <stop offset="0%" stopColor="black" stopOpacity="0" />
134
- <stop
135
- offset={`${Math.max(30, 80 - aberrationIntensity * 2)}%`}
136
- stopColor="black"
137
- stopOpacity="0"
138
- />
139
- <stop offset="100%" stopColor="white" stopOpacity="1" />
140
- </radialGradient>
141
- <filter id={id} x="-35%" y="-35%" width="170%" height="170%" colorInterpolationFilters="sRGB">
142
- <feImage
143
- id="feimage"
144
- x="0"
145
- y="0"
146
- width="100%"
147
- height="100%"
148
- result="DISPLACEMENT_MAP"
149
- href={getDisplacementMap(mode, shaderMapUrl)}
150
- preserveAspectRatio="xMidYMid slice"
151
- />
152
-
153
- <feColorMatrix
154
- in="DISPLACEMENT_MAP"
155
- type="matrix"
156
- values="0.3 0.3 0.3 0 0
157
- 0.3 0.3 0.3 0 0
158
- 0.3 0.3 0.3 0 0
159
- 0 0 0 1 0"
160
- result="EDGE_INTENSITY"
161
- />
162
- <feComponentTransfer in="EDGE_INTENSITY" result="EDGE_MASK">
163
- <feFuncA type="discrete" tableValues={`0 ${aberrationIntensity * 0.05} 1`} />
164
- </feComponentTransfer>
165
-
166
- <feOffset in="SourceGraphic" dx="0" dy="0" result="CENTER_ORIGINAL" />
167
-
168
- <feDisplacementMap
169
- in="SourceGraphic"
170
- in2="DISPLACEMENT_MAP"
171
- scale={displacementScale * (mode === 'shader' ? 1 : -1)}
172
- xChannelSelector="R"
173
- yChannelSelector="B"
174
- result="RED_DISPLACED"
175
- />
176
- <feColorMatrix
177
- in="RED_DISPLACED"
178
- type="matrix"
179
- values="1 0 0 0 0
180
- 0 0 0 0 0
181
- 0 0 0 0 0
182
- 0 0 0 1 0"
183
- result="RED_CHANNEL"
184
- />
185
-
186
- <feDisplacementMap
187
- in="SourceGraphic"
188
- in2="DISPLACEMENT_MAP"
189
- scale={displacementScale * ((mode === 'shader' ? 1 : -1) - aberrationIntensity * 0.02)}
190
- xChannelSelector="R"
191
- yChannelSelector="B"
192
- result="GREEN_DISPLACED"
193
- />
194
- <feColorMatrix
195
- in="GREEN_DISPLACED"
196
- type="matrix"
197
- values="0 0 0 0 0
198
- 0 1 0 0 0
199
- 0 0 0 0 0
200
- 0 0 0 1 0"
201
- result="GREEN_CHANNEL"
202
- />
203
-
204
- <feDisplacementMap
205
- in="SourceGraphic"
206
- in2="DISPLACEMENT_MAP"
207
- scale={displacementScale * ((mode === 'shader' ? 1 : -1) - aberrationIntensity * 0.03)}
208
- xChannelSelector="R"
209
- yChannelSelector="B"
210
- result="BLUE_DISPLACED"
211
- />
212
- <feColorMatrix
213
- in="BLUE_DISPLACED"
214
- type="matrix"
215
- values="0 0 0 0 0
216
- 0 0 0 0 0
217
- 0 0 1 0 0
218
- 0 0 0 1 0"
219
- result="BLUE_CHANNEL"
220
- />
221
-
222
- <feBlend in="GREEN_CHANNEL" in2="BLUE_CHANNEL" mode="screen" result="GB_COMBINED" />
223
- <feBlend in="RED_CHANNEL" in2="GB_COMBINED" mode="screen" result="RGB_COMBINED" />
224
-
225
- <feGaussianBlur
226
- in="RGB_COMBINED"
227
- stdDeviation={Math.max(0.1, 0.5 - aberrationIntensity * 0.1)}
228
- result="ABERRATED_BLURRED"
229
- />
230
-
231
- <feComposite
232
- in="ABERRATED_BLURRED"
233
- in2="EDGE_MASK"
234
- operator="in"
235
- result="EDGE_ABERRATION"
236
- />
237
-
238
- <feComponentTransfer in="EDGE_MASK" result="INVERTED_MASK">
239
- <feFuncA type="table" tableValues="1 0" />
240
- </feComponentTransfer>
241
- <feComposite in="CENTER_ORIGINAL" in2="INVERTED_MASK" operator="in" result="CENTER_CLEAN" />
242
-
243
- <feComposite in="EDGE_ABERRATION" in2="CENTER_CLEAN" operator="over" />
244
- </filter>
245
- </defs>
246
- </svg>
247
- );
248
-
249
- interface GlassContainerProps {
250
- className?: string;
251
- style?: React.CSSProperties;
252
- displacementScale?: number;
253
- blurAmount?: number;
254
- saturation?: number;
255
- aberrationIntensity?: number;
256
- mouseOffset?: MousePosition;
257
- globalMousePosition?: MousePosition;
258
- onMouseLeave?: () => void;
259
- onMouseEnter?: () => void;
260
- onMouseDown?: () => void;
261
- onMouseUp?: () => void;
262
- active?: boolean;
263
- isHovered?: boolean;
264
- isActive?: boolean;
265
- overLight?: boolean;
266
- cornerRadius?: number;
267
- padding?: string;
268
- glassSize?: GlassSize;
269
- onClick?: () => void;
270
- mode?: DisplacementMode;
271
- transform?: string;
272
- effectiveDisableEffects?: boolean;
273
- effectiveReducedMotion?: boolean;
274
- shaderVariant?: FragmentShaderType;
275
- enableLiquidBlur?: boolean;
276
- elasticity?: number;
277
- children?: React.ReactNode;
278
- }
279
-
280
- const GlassContainer = forwardRef<HTMLDivElement, GlassContainerProps>(
281
- (
282
- {
283
- children,
284
- className = '',
285
- style,
286
- displacementScale = 25,
287
- blurAmount = 0.0625,
288
- saturation = 180,
289
- aberrationIntensity = 2,
290
- mouseOffset = { x: 0, y: 0 },
291
- globalMousePosition = { x: 0, y: 0 },
292
- onMouseEnter,
293
- onMouseLeave,
294
- onMouseDown,
295
- onMouseUp,
296
- active = false,
297
- isHovered = false,
298
- isActive = false,
299
- overLight = false,
300
- cornerRadius = 0,
301
- padding = '0 0',
302
- glassSize = { width: 0, height: 0 },
303
- onClick,
304
- mode = 'standard',
305
- transform = 'none',
306
- effectiveDisableEffects = false,
307
- effectiveReducedMotion = false,
308
- shaderVariant = 'liquidGlass',
309
- enableLiquidBlur = false,
310
- elasticity = 0,
311
- },
312
- ref
313
- ) => {
314
- const filterId = useId();
315
- const [shaderMapUrl, setShaderMapUrl] = useState<string>('');
316
- const shaderGeneratorRef = useRef<ShaderDisplacementGenerator | null>(null);
317
-
318
- // Generate initial shader map when mode/size/variant changes
319
- useEffect(() => {
320
- if (mode === 'shader' && glassSize.width > 0 && glassSize.height > 0) {
321
- shaderGeneratorRef.current?.destroy();
322
- const selectedShader = fragmentShaders[shaderVariant] || fragmentShaders.liquidGlass;
323
- shaderGeneratorRef.current = new ShaderDisplacementGenerator({
324
- width: glassSize.width,
325
- height: glassSize.height,
326
- fragment: selectedShader,
327
- });
328
- const url = shaderGeneratorRef.current.updateShader();
329
- setShaderMapUrl(url);
330
- }
331
- return () => {
332
- shaderGeneratorRef.current?.destroy();
333
- shaderGeneratorRef.current = null;
334
- };
335
- }, [mode, glassSize.width, glassSize.height, shaderVariant]);
336
-
337
- useEffect(() => {
338
- if (!ref || typeof ref === 'function') return;
339
-
340
- const element = (ref as React.RefObject<HTMLDivElement>).current;
341
- if (!element) return;
342
-
343
- const timeoutId = setTimeout(() => {
344
- // Force reflow to ensure proper sizing
345
- element.offsetHeight;
346
- }, 0);
347
-
348
- return () => clearTimeout(timeoutId);
349
- }, [cornerRadius, glassSize.width, glassSize.height]);
350
-
351
- const [rectCache, setRectCache] = useState<DOMRect | null>(null);
352
-
353
- useEffect(() => {
354
- if (!ref || typeof ref === 'function') return;
355
- const element = (ref as React.RefObject<HTMLDivElement>).current;
356
- if (!element) return;
357
- setRectCache(element.getBoundingClientRect());
358
- }, [ref, glassSize]);
359
-
360
- const liquidBlur = useMemo(() => {
361
- const defaultBlur = {
362
- baseBlur: blurAmount,
363
- edgeBlur: blurAmount * 1.25,
364
- centerBlur: blurAmount * 1.1,
365
- flowBlur: blurAmount * 1.2,
366
- };
367
-
368
- if (!enableLiquidBlur || !rectCache || !globalMousePosition.x || !globalMousePosition.y) {
369
- return defaultBlur;
370
- }
371
-
372
- const center = calculateElementCenter(rectCache);
373
- const distance = calculateDistance(globalMousePosition, center);
374
- const maxDistance =
375
- Math.sqrt(rectCache.width * rectCache.width + rectCache.height * rectCache.height) / 2;
376
- const normalizedDistance = Math.min(distance / maxDistance, 1);
377
- const mouseInfluence = calculateMouseInfluence(mouseOffset);
378
-
379
- const baseBlur = blurAmount + mouseInfluence * blurAmount * 0.4;
380
- const edgeIntensity = normalizedDistance * 1.5 + mouseInfluence * 0.3;
381
- const edgeBlur = baseBlur * (0.8 + edgeIntensity * 0.6);
382
- const centerIntensity = (1 - normalizedDistance) * 0.3 + mouseInfluence * 0.2;
383
- const centerBlur = baseBlur * (0.3 + centerIntensity * 0.4);
384
- const deltaX = globalMousePosition.x - center.x;
385
- const deltaY = globalMousePosition.y - center.y;
386
- const flowDirection = Math.atan2(deltaY, deltaX);
387
- const flowIntensity = Math.sin(flowDirection + mouseInfluence * Math.PI) * 0.5 + 0.5;
388
- const flowBlur = baseBlur * (0.4 + flowIntensity * 0.6);
389
-
390
- const hoverMultiplier = isHovered ? 1.2 : 1;
391
- const activeMultiplier = isActive ? 1.4 : 1;
392
- const stateMultiplier = hoverMultiplier * activeMultiplier;
393
-
394
- return {
395
- baseBlur: clampBlur(baseBlur * stateMultiplier),
396
- edgeBlur: clampBlur(edgeBlur * stateMultiplier),
397
- centerBlur: clampBlur(centerBlur * stateMultiplier),
398
- flowBlur: clampBlur(flowBlur * stateMultiplier),
399
- };
400
- }, [enableLiquidBlur, blurAmount, globalMousePosition, mouseOffset, isHovered, isActive, rectCache]);
401
-
402
- const backdropStyle = useMemo(() => {
403
- const dynamicSaturation = saturation + liquidBlur.baseBlur * 20;
404
-
405
- const blurLayers = [
406
- `blur(${liquidBlur.baseBlur}px)`,
407
- `blur(${liquidBlur.edgeBlur}px)`,
408
- `blur(${liquidBlur.centerBlur}px)`,
409
- `blur(${liquidBlur.flowBlur}px)`,
410
- ];
411
-
412
- return {
413
- backdropFilter: `${blurLayers.join(' ')} saturate(${Math.min(dynamicSaturation, 200)}%) url(#${filterId})`,
414
- };
415
- }, [filterId, liquidBlur, saturation]);
416
-
417
- const containerVars = useMemo(() => {
418
- const mx = mouseOffset?.x || 0;
419
- const my = mouseOffset?.y || 0;
420
- const scopedId = `gc-${filterId.replace(/:/g, '')}`;
421
-
422
- return {
423
- [`--${scopedId}-padding`]: padding,
424
- [`--${scopedId}-radius`]: `${cornerRadius}px`,
425
- [`--${scopedId}-backdrop`]: backdropStyle.backdropFilter,
426
- [`--${scopedId}-shadow`]: overLight
427
- ? [
428
- `inset 0 1px 0 rgba(255, 255, 255, ${0.4 + mx * 0.002})`,
429
- `inset 0 -1px 0 rgba(0, 0, 0, ${0.2 + Math.abs(my) * 0.001})`,
430
- `inset 0 0 20px rgba(0, 0, 0, ${0.08 + Math.abs(mx + my) * 0.001})`,
431
- `0 2px 12px rgba(0, 0, 0, ${0.12 + Math.abs(my) * 0.002})`,
432
- ].join(', ')
433
- : '0 0 20px rgba(0, 0, 0, 0.15) inset, 0 4px 8px rgba(0, 0, 0, 0.08) inset',
434
- [`--${scopedId}-shadow-opacity`]: effectiveDisableEffects ? 0 : 1,
435
- [`--${scopedId}-bg`]: overLight
436
- ? `linear-gradient(${180 + mx * 0.5}deg, rgba(255, 255, 255, 0.1) 0%, transparent 20%, transparent 80%, rgba(0, 0, 0, 0.05) 100%)`
437
- : 'none',
438
- [`--${scopedId}-text-shadow`]: overLight
439
- ? '0px 1px 3px rgba(0, 0, 0, 0.2), 0px 2px 8px rgba(0, 0, 0, 0.1)'
440
- : '0px 2px 12px rgba(0, 0, 0, 0.4)',
441
- '--gc-scoped-id': scopedId,
442
- } as React.CSSProperties;
443
- }, [filterId, padding, cornerRadius, backdropStyle, mouseOffset, overLight, effectiveDisableEffects]);
444
-
445
- const scopedId = `gc-${filterId.replace(/:/g, '')}`;
446
-
447
- return (
448
- <div
449
- ref={ref}
450
- className={` ${className} ${active ? 'active' : ''}`}
451
- style={{ ...style, ...containerVars }}
452
- onClick={onClick}
453
- >
454
- <div
455
- className="atomix-glass"
456
- style={{
457
- position: 'relative',
458
- padding: `var(--${scopedId}-padding)`,
459
- borderRadius: `var(--${scopedId}-radius)`,
460
- }}
461
- onMouseEnter={onMouseEnter}
462
- onMouseLeave={onMouseLeave}
463
- onMouseDown={onMouseDown}
464
- onMouseUp={onMouseUp}
465
- >
466
- <GlassFilter
467
- mode={mode}
468
- id={filterId}
469
- displacementScale={displacementScale}
470
- aberrationIntensity={aberrationIntensity}
471
- shaderMapUrl={shaderMapUrl}
472
- />
473
- <span
474
- className="atomix-glass__warp"
475
- style={{
476
- backdropFilter: `var(--${scopedId}-backdrop)`,
477
- borderRadius: `var(--${scopedId}-radius)`,
478
- position: 'absolute',
479
- inset: '0',
480
- }}
481
- />
482
-
483
- {/* Enhanced Apple Liquid Glass Inner Shadow Layer */}
484
- <div
485
- style={{
486
- position: 'absolute',
487
- inset: '1.5px',
488
- borderRadius: `var(--${scopedId}-radius)`,
489
- pointerEvents: 'none',
490
- boxShadow: `var(--${scopedId}-shadow)`,
491
- opacity: `var(--${scopedId}-shadow-opacity)`,
492
- background: `var(--${scopedId}-bg)`,
493
- }}
494
- />
495
-
496
- <div
497
- style={{
498
- position: 'relative',
499
- ...(elasticity !== 0 && {
500
- zIndex: 4,
501
- textShadow: `var(--${scopedId}-text-shadow)`,
502
- }),
503
- }}
504
- >
505
- {children}
506
- </div>
507
- </div>
508
- </div>
509
- );
510
- }
511
- );
512
-
513
- GlassContainer.displayName = 'GlassContainer';
514
-
515
- interface AtomixGlassProps {
516
- children: React.ReactNode;
517
- displacementScale?: number;
518
- blurAmount?: number;
519
- saturation?: number;
520
- aberrationIntensity?: number;
521
- elasticity?: number;
522
- cornerRadius?: number;
523
- globalMousePosition?: MousePosition;
524
- mouseOffset?: MousePosition;
525
- mouseContainer?: React.RefObject<HTMLElement | null> | null;
526
- className?: string;
527
- padding?: string;
528
- style?: React.CSSProperties;
529
- overLight?: OverLightConfig;
530
- mode?: DisplacementMode;
531
- onClick?: () => void;
532
-
533
- // Shader variant selection
534
- shaderVariant?: FragmentShaderType;
535
-
536
- // Accessibility props
537
- 'aria-label'?: string;
538
- 'aria-describedby'?: string;
539
- role?: string;
540
- tabIndex?: number;
541
-
542
- // Performance and accessibility options
543
- reducedMotion?: boolean;
544
- highContrast?: boolean;
545
- disableEffects?: boolean;
546
- enableLiquidBlur?: boolean;
547
- enableBorderEffect?: boolean;
548
- enableOverLightLayers?: boolean;
549
-
550
- // Performance monitoring
551
- enablePerformanceMonitoring?: boolean;
552
- }
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';
553
6
 
554
7
  /**
555
8
  * AtomixGlass - A high-performance glass morphism component with liquid distortion effects
@@ -557,647 +10,496 @@ interface AtomixGlassProps {
557
10
  * Features:
558
11
  * - Hardware-accelerated glass effects with SVG filters
559
12
  * - Mouse-responsive liquid distortion
560
- * - Automatic light/dark theme detection
13
+ * - Dynamic border-radius extraction from children CSS properties
14
+ * - Automatic light/dark theme detection via overLight prop
561
15
  * - Accessibility and performance optimizations
562
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>
563
77
  */
564
78
  export function AtomixGlass({
565
79
  children,
566
- displacementScale = 20,
567
- blurAmount = 1,
568
- saturation = 140,
569
- aberrationIntensity = 2.5,
570
- elasticity = 0.05,
571
- cornerRadius = 16,
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,
572
86
  globalMousePosition: externalGlobalMousePosition,
573
87
  mouseOffset: externalMouseOffset,
574
88
  mouseContainer = null,
575
89
  className = '',
576
- padding = '0 0',
577
- overLight = false,
90
+ padding = ATOMIX_GLASS.DEFAULTS.PADDING,
91
+ overLight = ATOMIX_GLASS.DEFAULTS.OVER_LIGHT,
578
92
  style = {},
579
- mode = 'standard',
93
+ mode = ATOMIX_GLASS.DEFAULTS.MODE,
580
94
  onClick,
581
95
  shaderVariant = 'liquidGlass',
582
96
  'aria-label': ariaLabel,
583
97
  'aria-describedby': ariaDescribedBy,
584
98
  role,
585
99
  tabIndex,
586
-
587
100
  reducedMotion = false,
588
101
  highContrast = false,
589
102
  disableEffects = false,
590
103
  enableLiquidBlur = false,
591
104
  enableBorderEffect = true,
592
- enableOverLightLayers = false,
593
-
105
+ enableOverLightLayers = ATOMIX_GLASS.DEFAULTS.ENABLE_OVER_LIGHT_LAYERS,
594
106
  enablePerformanceMonitoring = false,
107
+ debugCornerRadius = false,
108
+ debugOverLight = false,
595
109
  }: AtomixGlassProps) {
596
110
  const glassRef = useRef<HTMLDivElement>(null);
597
- const [isHovered, setIsHovered] = useState(false);
598
- const [isActive, setIsActive] = useState(false);
599
- const [glassSize, setGlassSize] = useState<GlassSize>({ width: 270, height: 69 });
600
- const [internalGlobalMousePosition, setInternalGlobalMousePosition] = useState<MousePosition>({
601
- x: 0,
602
- y: 0,
111
+ const contentRef = useRef<HTMLDivElement>(null);
112
+
113
+ // Use composable hook for all state and logic
114
+ const {
115
+ isHovered,
116
+ isActive,
117
+ glassSize,
118
+ effectiveCornerRadius,
119
+ effectiveReducedMotion,
120
+ effectiveHighContrast,
121
+ 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,
603
148
  });
604
- const [internalMouseOffset, setInternalMouseOffset] = useState<MousePosition>({ x: 0, y: 0 });
605
149
 
606
- const [userPrefersReducedMotion, setUserPrefersReducedMotion] = useState(false);
607
- const [userPrefersHighContrast, setUserPrefersHighContrast] = useState(false);
608
- const [detectedOverLight, setDetectedOverLight] = useState(false);
150
+ // Use consistent overLight state from hook
151
+ const isOverLight = overLightConfig.isOverLight;
152
+ const shouldRenderOverLightLayers = enableOverLightLayers && isOverLight;
609
153
 
610
- // Memoized derived values for performance
611
- const effectiveReducedMotion = useMemo(
612
- () => reducedMotion || userPrefersReducedMotion,
613
- [reducedMotion, userPrefersReducedMotion]
614
- );
615
- const effectiveHighContrast = useMemo(
616
- () => highContrast || userPrefersHighContrast,
617
- [highContrast, userPrefersHighContrast]
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]
618
158
  );
619
- const effectiveDisableEffects = useMemo(
620
- () => disableEffects || effectiveReducedMotion,
621
- [disableEffects, effectiveReducedMotion]
622
- );
623
-
624
- useEffect(() => {
625
- // Enhanced auto-detect light background with multiple sampling points
626
- if (overLight === 'auto' && glassRef.current) {
627
- try {
628
- const element = glassRef.current;
629
- const rect = element.getBoundingClientRect();
630
-
631
- let totalLuminance = 0;
632
- let validSamples = 0;
633
-
634
- // Check parent elements and computed styles
635
- let currentElement = element.parentElement;
636
- while (currentElement && validSamples < 3) {
637
- const computedStyle = window.getComputedStyle(currentElement);
638
- const bgColor = computedStyle.backgroundColor;
639
-
640
- if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
641
- const rgb = bgColor.match(/\d+/g);
642
- if (rgb && rgb.length >= 3) {
643
- const r = Number(rgb[0]) || 0;
644
- const g = Number(rgb[1]) || 0;
645
- const b = Number(rgb[2]) || 0;
646
- const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
647
- totalLuminance += luminance;
648
- validSamples++;
649
- }
650
- }
651
- currentElement = currentElement.parentElement;
652
- }
653
-
654
- // Use canvas sampling as fallback for complex backgrounds
655
- if (validSamples === 0 && typeof document !== 'undefined') {
656
- try {
657
- const canvas = document.createElement('canvas');
658
- const ctx = canvas.getContext('2d');
659
- if (ctx) {
660
- canvas.width = 1;
661
- canvas.height = 1;
662
-
663
- // Sample the background at element position
664
- const imageData = ctx.getImageData(0, 0, 1, 1);
665
- const r = imageData.data[0] || 0;
666
- const g = imageData.data[1] || 0;
667
- const b = imageData.data[2] || 0;
668
- const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
669
- totalLuminance = luminance;
670
- validSamples = 1;
671
- }
672
- } catch (canvasError) {
673
- // Canvas sampling failed, use default
674
- }
675
- }
676
159
 
677
- if (validSamples > 0) {
678
- const avgLuminance = totalLuminance / validSamples;
679
- let threshold = 0.7;
680
- if (typeof overLight === 'object' && overLight !== null && overLight !== 'auto') {
681
- const objConfig = overLight as OverLightObjectConfig;
682
- threshold = objConfig.threshold || 0.7;
683
- }
684
- setDetectedOverLight(avgLuminance > threshold);
685
- }
686
- } catch (error) {
687
- console.warn('AtomixGlass: Error detecting background brightness:', error);
688
- }
689
- }
690
-
691
- if (typeof window.matchMedia !== 'function') {
692
- console.warn('AtomixGlass: matchMedia not supported, using default preferences');
693
- return;
694
- }
695
-
696
- try {
697
- const mediaQueryReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
698
- const mediaQueryHighContrast = window.matchMedia('(prefers-contrast: high)');
699
-
700
- setUserPrefersReducedMotion(mediaQueryReducedMotion.matches);
701
- setUserPrefersHighContrast(mediaQueryHighContrast.matches);
702
-
703
- const handleReducedMotionChange = (e: MediaQueryListEvent) => {
704
- setUserPrefersReducedMotion(e.matches);
705
- };
706
-
707
- const handleHighContrastChange = (e: MediaQueryListEvent) => {
708
- setUserPrefersHighContrast(e.matches);
709
- };
710
-
711
- if (mediaQueryReducedMotion.addEventListener) {
712
- mediaQueryReducedMotion.addEventListener('change', handleReducedMotionChange);
713
- mediaQueryHighContrast.addEventListener('change', handleHighContrastChange);
714
- } else if (mediaQueryReducedMotion.addListener) {
715
- mediaQueryReducedMotion.addListener(handleReducedMotionChange);
716
- mediaQueryHighContrast.addListener(handleHighContrastChange);
717
- }
718
-
719
- return () => {
720
- try {
721
- if (mediaQueryReducedMotion.removeEventListener) {
722
- mediaQueryReducedMotion.removeEventListener('change', handleReducedMotionChange);
723
- mediaQueryHighContrast.removeEventListener('change', handleHighContrastChange);
724
- } else if (mediaQueryReducedMotion.removeListener) {
725
- mediaQueryReducedMotion.removeListener(handleReducedMotionChange);
726
- mediaQueryHighContrast.removeListener(handleHighContrastChange);
727
- }
728
- } catch (cleanupError) {
729
- console.error('AtomixGlass: Error cleaning up media query listeners:', cleanupError);
730
- }
731
- };
732
- } catch (error) {
733
- console.error('AtomixGlass: Error setting up media queries:', error);
734
- return undefined;
735
- }
736
- }, []);
737
-
738
- // Derived values are now memoized above
739
-
740
- const globalMousePosition = useMemo(
741
- () => externalGlobalMousePosition || internalGlobalMousePosition,
742
- [externalGlobalMousePosition, internalGlobalMousePosition]
743
- );
744
- const mouseOffset = useMemo(
745
- () => externalMouseOffset || internalMouseOffset,
746
- [externalMouseOffset, internalMouseOffset]
160
+ // Calculate base style with transforms (only dynamic values)
161
+ // Performance: willChange is set only when transforms are active and effects are enabled
162
+ const baseStyle = useMemo(
163
+ () => ({
164
+ ...style,
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',
172
+ }),
173
+ }),
174
+ [style, transformStyle, effectiveDisableEffects, elasticity]
747
175
  );
748
176
 
749
- const getEffectiveOverLight = useCallback(() => {
750
- if (typeof overLight === 'boolean') return overLight;
751
- if (overLight === 'auto') return detectedOverLight;
752
- return detectedOverLight;
753
- }, [overLight, detectedOverLight]);
754
-
755
- const overLightConfig = useMemo(() => {
756
- const isOverLight = getEffectiveOverLight();
757
- const mouseInfluence = calculateMouseInfluence(mouseOffset);
758
- const hoverIntensity = isHovered ? 1.3 : 1;
759
- const activeIntensity = isActive ? 1.5 : 1;
760
-
761
- const baseConfig = {
762
- isOverLight,
763
- threshold: 0.7,
764
- opacity: 0.4 * hoverIntensity * activeIntensity,
765
- contrast: 1.3 + mouseInfluence * 0.2,
766
- brightness: 0.9 + mouseInfluence * 0.1,
767
- saturationBoost: 1.2 + mouseInfluence * 0.3,
768
- shadowIntensity: 0.8 + mouseInfluence * 0.4,
769
- borderOpacity: 0.6 + mouseInfluence * 0.2,
770
- };
771
-
772
- if (typeof overLight === 'object' && overLight !== null) {
773
- const objConfig = overLight as OverLightObjectConfig;
774
- return {
775
- ...baseConfig,
776
- threshold: objConfig.threshold || baseConfig.threshold,
777
- opacity: (objConfig.opacity || 0.4) * hoverIntensity * activeIntensity,
778
- contrast: (objConfig.contrast || 1.3) + mouseInfluence * 0.2,
779
- };
780
- }
781
-
782
- return baseConfig;
783
- }, [overLight, getEffectiveOverLight, mouseOffset, isHovered, isActive]);
784
-
785
- const mouseMoveThrottleRef = useRef<number | null>(null);
786
- const lastMouseEventRef = useRef<MouseEvent | null>(null);
787
-
788
- const handleMouseMove = useCallback(
789
- (e: MouseEvent) => {
790
- lastMouseEventRef.current = e;
791
-
792
- if (mouseMoveThrottleRef.current === null) {
793
- mouseMoveThrottleRef.current = requestAnimationFrame(() => {
794
- const event = lastMouseEventRef.current;
795
- if (!event) {
796
- mouseMoveThrottleRef.current = null;
797
- return;
798
- }
799
-
800
- const container = mouseContainer?.current || glassRef.current;
801
- if (!container) {
802
- mouseMoveThrottleRef.current = null;
803
- return;
804
- }
805
-
806
- const startTime = enablePerformanceMonitoring ? performance.now() : 0;
807
-
808
- const rect = container.getBoundingClientRect();
809
- if (rect.width === 0 || rect.height === 0) {
810
- mouseMoveThrottleRef.current = null;
811
- return;
812
- }
813
-
814
- const center = calculateElementCenter(rect);
815
-
816
- setInternalMouseOffset({
817
- x: ((event.clientX - center.x) / rect.width) * 100,
818
- y: ((event.clientY - center.y) / rect.height) * 100,
819
- });
820
-
821
- setInternalGlobalMousePosition({
822
- x: event.clientX,
823
- y: event.clientY,
824
- });
825
-
826
- if (enablePerformanceMonitoring) {
827
- const endTime = performance.now();
828
- const duration = endTime - startTime;
829
- if (duration > 5) {
830
- console.warn(`AtomixGlass: Mouse tracking took ${duration.toFixed(2)}ms`);
831
- }
832
- }
833
-
834
- mouseMoveThrottleRef.current = null;
835
- });
836
- }
837
- },
838
- [mouseContainer, enablePerformanceMonitoring]
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]
839
190
  );
840
191
 
841
- useEffect(() => {
842
- if (externalGlobalMousePosition && externalMouseOffset) {
843
- return;
844
- }
845
-
846
- if (effectiveDisableEffects) {
847
- return;
848
- }
849
-
850
- const container = mouseContainer?.current || glassRef.current;
851
- if (!container) {
852
- return;
853
- }
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;
854
197
 
855
- container.addEventListener('mousemove', handleMouseMove, { passive: true });
856
-
857
- return () => {
858
- container.removeEventListener('mousemove', handleMouseMove);
859
- if (mouseMoveThrottleRef.current) {
860
- cancelAnimationFrame(mouseMoveThrottleRef.current);
861
- mouseMoveThrottleRef.current = null;
862
- }
863
- };
864
- }, [
865
- handleMouseMove,
866
- mouseContainer,
867
- externalGlobalMousePosition,
868
- externalMouseOffset,
869
- effectiveDisableEffects,
870
- ]);
871
-
872
- const calculateDirectionalScale = useCallback(() => {
873
- if (
874
- !globalMousePosition.x ||
875
- !globalMousePosition.y ||
876
- !glassRef.current ||
877
- !validateGlassSize(glassSize)
878
- ) {
879
- return 'scale(1)';
880
- }
881
-
882
- const rect = glassRef.current.getBoundingClientRect();
883
- const center = calculateElementCenter(rect);
884
- const deltaX = globalMousePosition.x - center.x;
885
- const deltaY = globalMousePosition.y - center.y;
886
-
887
- const edgeDistanceX = Math.max(0, Math.abs(deltaX) - glassSize.width / 2);
888
- const edgeDistanceY = Math.max(0, Math.abs(deltaY) - glassSize.height / 2);
889
- const edgeDistance = calculateDistance({ x: edgeDistanceX, y: edgeDistanceY }, { x: 0, y: 0 });
890
-
891
- if (edgeDistance > ACTIVATION_ZONE) {
892
- return 'scale(1)';
893
- }
894
-
895
- const fadeInFactor = 1 - edgeDistance / ACTIVATION_ZONE;
896
- const centerDistance = calculateDistance(globalMousePosition, center);
198
+ const positionStyles = useMemo(
199
+ () => ({
200
+ position: (baseStylePosition || 'absolute') as React.CSSProperties['position'],
201
+ top: baseStyleTop || 0,
202
+ left: baseStyleLeft || 0,
203
+ }),
204
+ [baseStylePosition, baseStyleTop, baseStyleLeft]
205
+ );
897
206
 
898
- if (centerDistance === 0) {
899
- return 'scale(1)';
900
- }
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;
901
212
 
902
- const normalizedX = deltaX / centerDistance;
903
- const normalizedY = deltaY / centerDistance;
904
- const stretchIntensity = Math.min(centerDistance / 300, 1) * elasticity * fadeInFactor;
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
+ );
905
230
 
906
- const scaleX =
907
- 1 +
908
- Math.abs(normalizedX) * stretchIntensity * 0.3 -
909
- Math.abs(normalizedY) * stretchIntensity * 0.15;
910
- const scaleY =
911
- 1 +
912
- Math.abs(normalizedY) * stretchIntensity * 0.3 -
913
- Math.abs(normalizedX) * stretchIntensity * 0.15;
914
231
 
915
- return `scaleX(${Math.max(0.8, scaleX)}) scaleY(${Math.max(0.8, scaleY)})`;
916
- }, [globalMousePosition, elasticity, glassSize]);
917
232
 
918
- const calculateFadeInFactor = useCallback(() => {
919
- if (
920
- !globalMousePosition.x ||
921
- !globalMousePosition.y ||
922
- !glassRef.current ||
923
- !validateGlassSize(glassSize)
924
- ) {
925
- return 0;
926
- }
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;
927
237
 
928
- const rect = glassRef.current.getBoundingClientRect();
929
- const center = calculateElementCenter(rect);
238
+ const gradientCalculations = useMemo(() => {
239
+ const mx = mouseOffsetX;
240
+ const my = mouseOffsetY;
241
+ const { GRADIENT } = ATOMIX_GLASS.CONSTANTS;
930
242
 
931
- const edgeDistanceX = Math.max(
932
- 0,
933
- Math.abs(globalMousePosition.x - center.x) - glassSize.width / 2
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
934
249
  );
935
- const edgeDistanceY = Math.max(
936
- 0,
937
- Math.abs(globalMousePosition.y - center.y) - glassSize.height / 2
250
+ const borderStop2 = Math.min(
251
+ GRADIENT.BORDER_STOP_2.MAX,
252
+ GRADIENT.BORDER_STOP_2.BASE + my * GRADIENT.BORDER_STOP_2.MULTIPLIER
938
253
  );
939
- const edgeDistance = calculateDistance({ x: edgeDistanceX, y: edgeDistanceY }, { x: 0, y: 0 });
940
-
941
- return edgeDistance > ACTIVATION_ZONE ? 0 : 1 - edgeDistance / ACTIVATION_ZONE;
942
- }, [globalMousePosition, glassSize]);
943
-
944
- const calculateElasticTranslation = useCallback(() => {
945
- if (!glassRef.current) {
946
- return { x: 0, y: 0 };
947
- }
948
-
949
- const fadeInFactor = calculateFadeInFactor();
950
- const rect = glassRef.current.getBoundingClientRect();
951
- const center = calculateElementCenter(rect);
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;
952
278
 
953
279
  return {
954
- x: (globalMousePosition.x - center.x) * elasticity * 0.1 * fadeInFactor,
955
- y: (globalMousePosition.y - center.y) * elasticity * 0.1 * fadeInFactor,
956
- };
957
- }, [globalMousePosition, elasticity, calculateFadeInFactor]);
958
-
959
- useEffect(() => {
960
- const isValidElement = (element: HTMLElement | null): element is HTMLElement =>
961
- element !== null && element instanceof HTMLElement && element.isConnected;
962
-
963
- const validateSize = (size: GlassSize): boolean =>
964
- validateGlassSize(size) && size.width <= 4096 && size.height <= 4096;
965
-
966
- let rafId: number | null = null;
967
- let lastSize = { width: 0, height: 0 };
968
- let lastCornerRadius = cornerRadius;
969
-
970
- const updateGlassSize = (forceUpdate = false): void => {
971
- if (rafId !== null) cancelAnimationFrame(rafId);
972
-
973
- rafId = requestAnimationFrame(() => {
974
- if (!isValidElement(glassRef.current)) return;
975
-
976
- const rect = glassRef.current.getBoundingClientRect();
977
- if (rect.width <= 0 || rect.height <= 0) return;
978
-
979
- const cornerRadiusOffset = Math.max(0, cornerRadius * 0.1);
980
- const newSize: GlassSize = {
981
- width: Math.round(rect.width + cornerRadiusOffset),
982
- height: Math.round(rect.height + cornerRadiusOffset),
983
- };
984
-
985
- const cornerRadiusChanged = lastCornerRadius !== cornerRadius;
986
- const dimensionsChanged =
987
- newSize.width !== lastSize.width || newSize.height !== lastSize.height;
988
-
989
- if ((forceUpdate || cornerRadiusChanged || dimensionsChanged) && validateSize(newSize)) {
990
- lastSize = newSize;
991
- lastCornerRadius = cornerRadius;
992
- setGlassSize(newSize);
993
- }
994
-
995
- rafId = null;
996
- });
997
- };
998
-
999
- let resizeTimeoutId: NodeJS.Timeout | null = null;
1000
- const debouncedResizeHandler = (): void => {
1001
- if (resizeTimeoutId) clearTimeout(resizeTimeoutId);
1002
- resizeTimeoutId = setTimeout(updateGlassSize, 16);
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,
1003
298
  };
1004
-
1005
- updateGlassSize(true);
1006
-
1007
- let resizeObserver: ResizeObserver | null = null;
1008
- let fallbackInterval: NodeJS.Timeout | null = null;
1009
-
1010
- const hasResizeObserver = typeof ResizeObserver !== 'undefined';
1011
-
1012
- if (hasResizeObserver && isValidElement(glassRef.current)) {
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) {
1013
316
  try {
1014
- resizeObserver = new ResizeObserver(entries => {
1015
- for (const entry of entries) {
1016
- if (entry.target === glassRef.current) {
1017
- updateGlassSize();
1018
- break;
1019
- }
1020
- }
1021
- });
1022
- resizeObserver.observe(glassRef.current);
1023
- } catch {
1024
- fallbackInterval = setInterval(
1025
- () => isValidElement(glassRef.current) && updateGlassSize(),
1026
- 100
1027
- );
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
1028
329
  }
1029
- } else {
1030
- fallbackInterval = setInterval(
1031
- () => isValidElement(glassRef.current) && updateGlassSize(),
1032
- 100
1033
- );
1034
330
  }
1035
331
 
1036
- window.addEventListener('resize', debouncedResizeHandler, { passive: true });
1037
-
1038
- return () => {
1039
- if (rafId !== null) cancelAnimationFrame(rafId);
1040
- if (resizeTimeoutId) clearTimeout(resizeTimeoutId);
1041
- if (fallbackInterval) clearInterval(fallbackInterval);
1042
- window.removeEventListener('resize', debouncedResizeHandler);
1043
- resizeObserver?.disconnect();
332
+ const BASE_OVER_LIGHT_OPACITY = opacity40; // Uses design token
333
+ const OVER_OPACITY_MULTIPLIER = 1.1; // Dynamic multiplier for overlay
334
+
335
+ return {
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,
1044
341
  };
1045
- }, [cornerRadius, enablePerformanceMonitoring]);
1046
-
1047
- useEffect(() => {
1048
- if (!glassRef.current) return;
1049
-
1050
- const timeoutId = setTimeout(() => {
1051
- if (!glassRef.current) return;
1052
-
1053
- const rect = glassRef.current.getBoundingClientRect();
1054
- if (rect.width > 0 && rect.height > 0) {
1055
- const cornerRadiusOffset = Math.max(0, cornerRadius * 0.1);
1056
- setGlassSize({
1057
- width: Math.round(rect.width + cornerRadiusOffset),
1058
- height: Math.round(rect.height + cornerRadiusOffset),
1059
- });
1060
- }
1061
- }, 0);
1062
-
1063
- return () => clearTimeout(timeoutId);
1064
- }, [cornerRadius]);
1065
-
1066
- const elasticTranslation = useMemo(() => {
1067
- if (effectiveDisableEffects) {
1068
- return { x: 0, y: 0 };
1069
- }
1070
- return calculateElasticTranslation();
1071
- }, [calculateElasticTranslation, effectiveDisableEffects]);
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;
1072
378
 
1073
- const directionalScale = useMemo(() => {
1074
- if (effectiveDisableEffects) {
1075
- return 'scale(1)';
1076
- }
1077
- return calculateDirectionalScale();
1078
- }, [calculateDirectionalScale, effectiveDisableEffects]);
1079
-
1080
- const transformStyle = useMemo(() => {
1081
- if (effectiveDisableEffects) {
1082
- return isActive && Boolean(onClick) ? 'scale(0.98)' : 'scale(1)';
1083
- }
1084
- return `translate(${elasticTranslation.x}px, ${elasticTranslation.y}px) ${isActive && Boolean(onClick) ? 'scale(0.96)' : directionalScale}`;
1085
- }, [elasticTranslation, isActive, onClick, directionalScale, effectiveDisableEffects]);
1086
-
1087
- const baseStyle = useMemo(
1088
- () => ({
1089
- ...style,
1090
- ...(elasticity !== 0 && {
1091
- transform: transformStyle,
1092
- transition: effectiveReducedMotion ? 'none' : 'all ease-out 0.2s',
1093
- willChange: effectiveDisableEffects ? 'auto' : 'transform',
1094
- }),
1095
- ...(effectiveHighContrast && {
1096
- border: '2px solid currentColor',
1097
- outline: '2px solid transparent',
1098
- outlineOffset: '2px',
1099
- }),
1100
- }),
1101
- [style, transformStyle, effectiveReducedMotion, effectiveDisableEffects, effectiveHighContrast, elasticity]
1102
- );
1103
-
1104
- const positionStyles = useMemo(
1105
- () => ({
1106
- position: (baseStyle.position || 'absolute') as React.CSSProperties['position'],
1107
- top: baseStyle.top || 0,
1108
- left: baseStyle.left || 0,
1109
- }),
1110
- [baseStyle]
1111
- );
1112
-
1113
- const adjustedSize = {
1114
- width:
1115
- baseStyle.position !== 'fixed'
1116
- ? '100%'
1117
- : baseStyle.width
1118
- ? baseStyle.width
1119
- : Math.max(glassSize.width, 0),
1120
- height:
1121
- baseStyle.position !== 'fixed'
1122
- ? '100%'
1123
- : baseStyle.height
1124
- ? baseStyle.height
1125
- : Math.max(glassSize.height, 0),
1126
- };
1127
-
1128
- const glassId = useId();
1129
379
  const glassVars = useMemo(() => {
1130
- const isOverLight = overLightConfig?.isOverLight ?? false;
1131
- const mx = mouseOffset.x;
1132
- const my = mouseOffset.y;
1133
- const scopedId = `ag-${glassId.replace(/:/g, '')}`;
1134
-
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
387
+
1135
388
  return {
1136
- [`--${scopedId}-pos`]: positionStyles.position,
1137
- [`--${scopedId}-top`]: positionStyles.top !== 'fixed' ? `${positionStyles.top}px` : '0',
1138
- [`--${scopedId}-left`]: positionStyles.left !== 'fixed' ? `${positionStyles.left}px` : '0',
1139
- [`--${scopedId}-w`]: baseStyle.position !== 'fixed' ? adjustedSize.width : `${adjustedSize.width}px`,
1140
- [`--${scopedId}-h`]: baseStyle.position !== 'fixed' ? adjustedSize.height : `${adjustedSize.height}px`,
1141
- [`--${scopedId}-r`]: `${cornerRadius}px`,
1142
- [`--${scopedId}-t`]: baseStyle.transform,
1143
- [`--${scopedId}-tr`]: effectiveReducedMotion ? 'none' : baseStyle.transition,
1144
- [`--${scopedId}-blend`]: isOverLight ? 'multiply' : 'overlay',
1145
- [`--${scopedId}-b1`]: `linear-gradient(${135 + mx * 1.2}deg, rgba(255,255,255,0) 0%, rgba(255,255,255,${0.12 + Math.abs(mx) * 0.008}) ${Math.max(10, 33 + my * 0.3)}%, rgba(255,255,255,${0.4 + Math.abs(mx) * 0.012}) ${Math.min(90, 66 + my * 0.4)}%, rgba(255,255,255,0) 100%)`,
1146
- [`--${scopedId}-b2`]: `linear-gradient(${135 + mx * 1.2}deg, rgba(255,255,255,0) 0%, rgba(255,255,255,${0.32 + Math.abs(mx) * 0.008}) ${Math.max(10, 33 + my * 0.3)}%, rgba(255,255,255,${0.6 + Math.abs(mx) * 0.012}) ${Math.min(90, 66 + my * 0.4)}%, rgba(255,255,255,0) 100%)`,
1147
- [`--${scopedId}-h1-o`]: (isHovered || isActive) ? (isOverLight ? 0.3 : 0.5) : 0,
1148
- [`--${scopedId}-h1`]: isOverLight ? `radial-gradient(circle at ${50 + mx / 2}% ${50 + my / 2}%, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.05) 30%, rgba(0,0,0,0) 60%)` : `radial-gradient(circle at ${50 + mx / 2}% ${50 + my / 2}%, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 50%)`,
1149
- [`--${scopedId}-h2-o`]: isActive ? (isOverLight ? 0.4 : 0.5) : 0,
1150
- [`--${scopedId}-h2`]: isOverLight ? `radial-gradient(circle at ${50 + mx / 1.5}% ${50 + my / 1.5}%, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.1) 40%, rgba(0,0,0,0) 80%)` : `radial-gradient(circle at ${50 + mx / 1.5}% ${50 + my / 1.5}%, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 80%)`,
1151
- [`--${scopedId}-h3-o`]: isHovered ? (isOverLight ? 0.25 : 0.4) : isActive ? (isOverLight ? 0.5 : 0.8) : 0,
1152
- [`--${scopedId}-h3`]: isOverLight ? `radial-gradient(circle at ${50 + mx}% ${50 + my}%, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.1) 50%, rgba(0,0,0,0) 100%)` : `radial-gradient(circle at ${50 + mx}% ${50 + my}%, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%)`,
1153
- [`--${scopedId}-base-o`]: isOverLight ? overLightConfig.opacity : 0,
1154
- [`--${scopedId}-base`]: isOverLight ? `linear-gradient(135deg, rgba(0,0,0,${0.12 + mx * 0.002}) 0%, rgba(0,0,0,${0.08 + my * 0.001}) 50%, rgba(0,0,0,${0.15 + Math.abs(mx) * 0.003}) 100%)` : 'rgba(255,255,255,0.1)',
1155
- [`--${scopedId}-over-o`]: isOverLight ? overLightConfig.opacity * 0.9 : 0,
1156
- [`--${scopedId}-over`]: isOverLight ? `radial-gradient(circle at ${50 + mx * 0.5}% ${50 + my * 0.5}%, rgba(0,0,0,${0.08 + Math.abs(mx) * 0.002}) 0%, rgba(0,0,0,0.04) 40%, rgba(0,0,0,${0.12 + Math.abs(my) * 0.002}) 100%)` : 'rgba(255,255,255,0.05)',
1157
- '--ag-scoped-id': scopedId,
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})`,
1158
427
  } as React.CSSProperties;
1159
- }, [glassId, positionStyles, adjustedSize, cornerRadius, baseStyle, effectiveReducedMotion, mouseOffset, isHovered, isActive, overLightConfig]);
1160
-
1161
- const scopedId = `ag-${glassId.replace(/:/g, '')}`;
428
+ }, [
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,
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,
468
+ ]);
1162
469
 
1163
470
  return (
1164
471
  <div
1165
- className="atomix-glass"
1166
- style={{ ...positionStyles, position: 'relative', ...glassVars }}
472
+ className={componentClassName}
473
+ style={glassVars}
1167
474
  role={role || (onClick ? 'button' : undefined)}
1168
475
  tabIndex={onClick ? (tabIndex ?? 0) : tabIndex}
1169
476
  aria-label={ariaLabel}
1170
477
  aria-describedby={ariaDescribedBy}
1171
- aria-disabled={onClick ? false : undefined}
1172
- onKeyDown={
1173
- onClick
1174
- ? e => {
1175
- if (e.key === 'Enter' || e.key === ' ') {
1176
- e.preventDefault();
1177
- onClick();
1178
- }
1179
- }
1180
- : undefined
1181
- }
478
+ aria-disabled={onClick && effectiveDisableEffects ? true : onClick ? false : undefined}
479
+ aria-pressed={onClick && isActive ? true : onClick ? false : undefined}
480
+ onKeyDown={onClick ? handleKeyDown : undefined}
1182
481
  >
1183
- <GlassContainer
482
+
483
+
484
+ <AtomixGlassContainer
1184
485
  ref={glassRef}
486
+ contentRef={contentRef}
1185
487
  className={className}
1186
488
  style={baseStyle}
1187
- cornerRadius={cornerRadius}
489
+ cornerRadius={effectiveCornerRadius}
1188
490
  displacementScale={
1189
491
  effectiveDisableEffects
1190
492
  ? 0
1191
493
  : mode === 'shader'
1192
- ? displacementScale * 0.8
494
+ ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_DISPLACEMENT
1193
495
  : overLightConfig.isOverLight
1194
- ? displacementScale * 0.6
496
+ ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.OVER_LIGHT_DISPLACEMENT
1195
497
  : displacementScale
1196
498
  }
1197
499
  blurAmount={effectiveDisableEffects ? 0 : blurAmount}
1198
500
  saturation={
1199
501
  effectiveHighContrast
1200
- ? 200
502
+ ? ATOMIX_GLASS.CONSTANTS.SATURATION.HIGH_CONTRAST
1201
503
  : overLightConfig.isOverLight
1202
504
  ? saturation * overLightConfig.saturationBoost
1203
505
  : saturation
@@ -1206,21 +508,21 @@ export function AtomixGlass({
1206
508
  effectiveDisableEffects
1207
509
  ? 0
1208
510
  : mode === 'shader'
1209
- ? aberrationIntensity * 0.7
511
+ ? aberrationIntensity * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_ABERRATION
1210
512
  : aberrationIntensity
1211
513
  }
1212
514
  glassSize={glassSize}
1213
515
  padding={padding}
1214
516
  mouseOffset={effectiveDisableEffects ? { x: 0, y: 0 } : mouseOffset}
1215
517
  globalMousePosition={effectiveDisableEffects ? { x: 0, y: 0 } : globalMousePosition}
1216
- onMouseEnter={() => setIsHovered(true)}
1217
- onMouseLeave={() => setIsHovered(false)}
1218
- onMouseDown={() => setIsActive(true)}
1219
- onMouseUp={() => setIsActive(false)}
518
+ onMouseEnter={handleMouseEnter}
519
+ onMouseLeave={handleMouseLeave}
520
+ onMouseDown={handleMouseDown}
521
+ onMouseUp={handleMouseUp}
1220
522
  active={isActive}
1221
523
  isHovered={isHovered}
1222
524
  isActive={isActive}
1223
- overLight={overLightConfig?.isOverLight || false}
525
+ overLight={overLightConfig.isOverLight}
1224
526
  onClick={onClick}
1225
527
  mode={mode}
1226
528
  transform={baseStyle.transform}
@@ -1231,109 +533,77 @@ export function AtomixGlass({
1231
533
  enableLiquidBlur={enableLiquidBlur}
1232
534
  >
1233
535
  {children}
1234
- </GlassContainer>
1235
- {enableBorderEffect && (
536
+ </AtomixGlassContainer>
537
+ {Boolean(onClick) && (
1236
538
  <>
1237
- <span
1238
- className="atomix-glass__border-1"
1239
- style={{
1240
- position: `var(--${scopedId}-pos)` as any,
1241
- top: `var(--${scopedId}-top)`,
1242
- left: `var(--${scopedId}-left)`,
1243
- width: `var(--${scopedId}-w)`,
1244
- height: `var(--${scopedId}-h)`,
1245
- borderRadius: `var(--${scopedId}-r)`,
1246
- transform: `var(--${scopedId}-t)`,
1247
- transition: `var(--${scopedId}-tr)`,
1248
- background: `var(--${scopedId}-b1)`,
1249
- }}
1250
- />
1251
- <span
1252
- className="atomix-glass__border-2"
1253
- style={{
1254
- position: `var(--${scopedId}-pos)` as any,
1255
- top: `var(--${scopedId}-top)`,
1256
- left: `var(--${scopedId}-left)`,
1257
- width: `var(--${scopedId}-w)`,
1258
- height: `var(--${scopedId}-h)`,
1259
- borderRadius: `var(--${scopedId}-r)`,
1260
- transform: `var(--${scopedId}-t)`,
1261
- transition: `var(--${scopedId}-tr)`,
1262
- mixBlendMode: `var(--${scopedId}-blend)` as any,
1263
- background: `var(--${scopedId}-b2)`,
1264
- }}
1265
- />
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} />
1266
543
  </>
1267
544
  )}
1268
- {Boolean(onClick) && (
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
+ }}
565
+ />
566
+ <div
567
+ className={[
568
+ ATOMIX_GLASS.BACKGROUND_LAYER_CLASS,
569
+ ATOMIX_GLASS.BACKGROUND_LAYER_BLACK_CLASS,
570
+ isOverLight
571
+ ? ATOMIX_GLASS.BACKGROUND_LAYER_OVER_LIGHT_CLASS
572
+ : ATOMIX_GLASS.BACKGROUND_LAYER_HIDDEN_CLASS,
573
+ ]
574
+ .filter(Boolean)
575
+ .join(' ')}
576
+ style={{
577
+ ...positionStyles,
578
+ height: adjustedSize.height,
579
+ width: adjustedSize.width,
580
+ borderRadius: `${effectiveCornerRadius}px`,
581
+ transform: baseStyle.transform,
582
+ }}
583
+ />
584
+ {shouldRenderOverLightLayers && (
1269
585
  <>
586
+ {/* Base and overlay layers - opacity and background set via CSS variables in SCSS */}
587
+ <div className={ATOMIX_GLASS.BASE_LAYER_CLASS} />
588
+ <div className={ATOMIX_GLASS.OVERLAY_LAYER_CLASS} />
589
+ {/* Overlay highlight - opacity and background are dynamic, calculated inline */}
1270
590
  <div
1271
- className="atomix-glass__hover-1"
1272
- style={{
1273
- position: 'absolute',
1274
- inset: 0,
1275
- borderRadius: `var(--${scopedId}-r)`,
1276
- transform: `var(--${scopedId}-t)`,
1277
- transition: `var(--${scopedId}-tr)`,
1278
- mixBlendMode: `var(--${scopedId}-blend)` as any,
1279
- opacity: `var(--${scopedId}-h1-o)`,
1280
- background: `var(--${scopedId}-h1)`,
1281
- }}
1282
- />
1283
- <div
1284
- className="atomix-glass__hover-2"
1285
- style={{
1286
- position: 'absolute',
1287
- inset: 0,
1288
- borderRadius: `var(--${scopedId}-r)`,
1289
- transform: `var(--${scopedId}-t)`,
1290
- transition: `var(--${scopedId}-tr)`,
1291
- mixBlendMode: `var(--${scopedId}-blend)` as any,
1292
- opacity: `var(--${scopedId}-h2-o)`,
1293
- background: `var(--${scopedId}-h2)`,
1294
- }}
1295
- />
1296
- <div
1297
- className="atomix-glass__hover-3"
591
+ className={ATOMIX_GLASS.OVERLAY_HIGHLIGHT_CLASS}
1298
592
  style={{
1299
- position: 'absolute',
1300
- inset: 0,
1301
- borderRadius: `var(--${scopedId}-r)`,
1302
- transform: `var(--${scopedId}-t)`,
1303
- transition: `var(--${scopedId}-tr)`,
1304
- mixBlendMode: `var(--${scopedId}-blend)` as any,
1305
- opacity: `var(--${scopedId}-h3-o)`,
1306
- background: `var(--${scopedId}-h3)`,
593
+ opacity:
594
+ opacityValues.over *
595
+ ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.OPACITY_MULTIPLIER,
596
+ 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}%)`,
1307
597
  }}
1308
598
  />
1309
599
  </>
1310
600
  )}
1311
- {enableOverLightLayers && (
601
+ {enableBorderEffect && (
1312
602
  <>
1313
- <div
1314
- className="atomix-glass__base"
1315
- style={{
1316
- position: 'absolute',
1317
- inset: 0,
1318
- borderRadius: `var(--${scopedId}-r)`,
1319
- transform: `var(--${scopedId}-t)`,
1320
- transition: `var(--${scopedId}-tr)`,
1321
- opacity: `var(--${scopedId}-base-o)`,
1322
- background: `var(--${scopedId}-base)`,
1323
- }}
1324
- />
1325
- <div
1326
- className="atomix-glass__overlay"
1327
- style={{
1328
- position: 'absolute',
1329
- inset: 0,
1330
- borderRadius: `var(--${scopedId}-r)`,
1331
- transform: `var(--${scopedId}-t)`,
1332
- transition: `var(--${scopedId}-tr)`,
1333
- opacity: `var(--${scopedId}-over-o)`,
1334
- background: `var(--${scopedId}-over)`,
1335
- }}
1336
- />
603
+ {/* Border elements - all styles (static and dynamic via CSS variables) are in SCSS */}
604
+ {/* Position, size, transform, transition, border-radius all use CSS variables set in glassVars */}
605
+ <span className={ATOMIX_GLASS.BORDER_1_CLASS} />
606
+ <span className={ATOMIX_GLASS.BORDER_2_CLASS} />
1337
607
  </>
1338
608
  )}
1339
609
  </div>