@shohojdhara/atomix 0.2.1 → 0.2.3

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 (201) hide show
  1. package/README.md +1 -28
  2. package/dist/atomix.css +1500 -241
  3. package/dist/atomix.min.css +6 -6
  4. package/dist/index.d.ts +1052 -194
  5. package/dist/index.esm.js +12201 -6066
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/index.js +5481 -2827
  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/boomdevs.css +1500 -301
  12. package/dist/themes/boomdevs.min.css +60 -8
  13. package/dist/themes/esrar.css +1500 -241
  14. package/dist/themes/esrar.min.css +6 -6
  15. package/dist/themes/mashroom.css +1496 -237
  16. package/dist/themes/mashroom.min.css +8 -8
  17. package/dist/themes/shaj-default.css +1451 -192
  18. package/dist/themes/shaj-default.min.css +6 -6
  19. package/package.json +66 -15
  20. package/src/components/Accordion/Accordion.stories.tsx +137 -0
  21. package/src/components/Accordion/Accordion.tsx +33 -3
  22. package/src/components/AtomixGlass/AtomixGlass.stories.tsx +3011 -0
  23. package/src/components/AtomixGlass/AtomixGlass.test.tsx +199 -0
  24. package/src/components/AtomixGlass/AtomixGlass.tsx +1281 -0
  25. package/src/components/AtomixGlass/AtomixGlassComprehensivePreview.stories.tsx +1369 -0
  26. package/src/components/AtomixGlass/README.md +134 -0
  27. package/src/components/AtomixGlass/index.ts +10 -0
  28. package/src/components/AtomixGlass/shader-utils.ts +140 -0
  29. package/src/components/AtomixGlass/utils.ts +8 -0
  30. package/src/components/Badge/Badge.stories.tsx +169 -0
  31. package/src/components/Badge/Badge.tsx +27 -2
  32. package/src/components/Button/Button.stories.tsx +345 -0
  33. package/src/components/Button/Button.tsx +35 -3
  34. package/src/components/Button/README.md +216 -0
  35. package/src/components/Callout/Callout.stories.tsx +813 -78
  36. package/src/components/Callout/Callout.test.tsx +368 -0
  37. package/src/components/Callout/Callout.tsx +26 -7
  38. package/src/components/Callout/README.md +409 -0
  39. package/src/components/Card/Card.stories.tsx +140 -0
  40. package/src/components/Card/Card.tsx +19 -3
  41. package/src/components/DatePicker/DatePicker copy.tsx +551 -0
  42. package/src/components/DatePicker/DatePicker.stories.tsx +188 -0
  43. package/src/components/DatePicker/DatePicker.tsx +379 -332
  44. package/src/components/DatePicker/readme.md +110 -1
  45. package/src/components/DatePicker/types.ts +8 -0
  46. package/src/components/Dropdown/Dropdown.stories.tsx +145 -0
  47. package/src/components/Dropdown/Dropdown.tsx +34 -5
  48. package/src/components/Footer/Footer.stories.tsx +388 -0
  49. package/src/components/Footer/Footer.tsx +197 -0
  50. package/src/components/Footer/FooterLink.tsx +72 -0
  51. package/src/components/Footer/FooterSection.tsx +87 -0
  52. package/src/components/Footer/FooterSocialLink.tsx +117 -0
  53. package/src/components/Footer/README.md +261 -0
  54. package/src/components/Footer/index.ts +13 -0
  55. package/src/components/Form/Checkbox.stories.tsx +101 -0
  56. package/src/components/Form/Checkbox.tsx +26 -2
  57. package/src/components/Form/Input.stories.tsx +124 -0
  58. package/src/components/Form/Input.tsx +36 -7
  59. package/src/components/Form/Radio.stories.tsx +139 -0
  60. package/src/components/Form/Radio.tsx +26 -2
  61. package/src/components/Form/Select.stories.tsx +110 -0
  62. package/src/components/Form/Select.tsx +26 -2
  63. package/src/components/Form/Textarea.stories.tsx +104 -0
  64. package/src/components/Form/Textarea.tsx +36 -7
  65. package/src/components/Hero/Hero.stories.tsx +54 -1
  66. package/src/components/Hero/Hero.tsx +70 -11
  67. package/src/components/Modal/Modal.stories.tsx +235 -0
  68. package/src/components/Modal/Modal.tsx +64 -35
  69. package/src/components/Pagination/Pagination.stories.tsx +101 -0
  70. package/src/components/Pagination/Pagination.tsx +25 -1
  71. package/src/components/Popover/Popover.stories.tsx +94 -0
  72. package/src/components/Popover/Popover.tsx +30 -4
  73. package/src/components/Rating/Rating.stories.tsx +112 -0
  74. package/src/components/Rating/Rating.tsx +25 -1
  75. package/src/components/SectionIntro/SectionIntro.tsx +9 -11
  76. package/src/components/Slider/Slider.stories.tsx +634 -50
  77. package/src/components/Slider/Slider.tsx +5 -3
  78. package/src/components/Steps/Steps.stories.tsx +119 -0
  79. package/src/components/Steps/Steps.tsx +32 -1
  80. package/src/components/Tab/Tab.stories.tsx +88 -0
  81. package/src/components/Tab/Tab.tsx +32 -1
  82. package/src/components/Toggle/Toggle.stories.tsx +92 -0
  83. package/src/components/Toggle/Toggle.tsx +32 -1
  84. package/src/components/Tooltip/Tooltip.stories.tsx +131 -0
  85. package/src/components/Tooltip/Tooltip.tsx +43 -7
  86. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +1002 -196
  87. package/src/components/VideoPlayer/VideoPlayer.tsx +161 -4
  88. package/src/components/index.ts +14 -0
  89. package/src/layouts/Grid/Grid.stories.tsx +226 -159
  90. package/src/lib/composables/index.ts +4 -0
  91. package/src/lib/composables/useAtomixGlass.ts +71 -0
  92. package/src/lib/composables/useButton.ts +3 -1
  93. package/src/lib/composables/useCallout.ts +4 -1
  94. package/src/lib/composables/useFooter.ts +85 -0
  95. package/src/lib/composables/useGlassContainer.ts +168 -0
  96. package/src/lib/composables/useSlider.ts +191 -4
  97. package/src/lib/constants/components.ts +173 -0
  98. package/src/lib/types/components.ts +622 -0
  99. package/src/lib/utils/displacement-generator.ts +86 -0
  100. package/src/styles/01-settings/_index.scss +1 -0
  101. package/src/styles/01-settings/_settings.accordion.scss +20 -19
  102. package/src/styles/01-settings/_settings.animations.scss +5 -5
  103. package/src/styles/01-settings/_settings.avatar-group.scss +1 -1
  104. package/src/styles/01-settings/_settings.avatar.scss +17 -18
  105. package/src/styles/01-settings/_settings.background.scss +10 -0
  106. package/src/styles/01-settings/_settings.badge.scss +1 -1
  107. package/src/styles/01-settings/_settings.breadcrumb.scss +8 -2
  108. package/src/styles/01-settings/_settings.callout.scss +7 -7
  109. package/src/styles/01-settings/_settings.card.scss +2 -2
  110. package/src/styles/01-settings/_settings.chart.scss +7 -7
  111. package/src/styles/01-settings/_settings.checkbox-group.scss +5 -2
  112. package/src/styles/01-settings/_settings.checkbox.scss +10 -4
  113. package/src/styles/01-settings/_settings.countdown.scss +6 -4
  114. package/src/styles/01-settings/_settings.dropdown.scss +9 -7
  115. package/src/styles/01-settings/_settings.edge-panel.scss +3 -2
  116. package/src/styles/01-settings/_settings.footer.scss +125 -0
  117. package/src/styles/01-settings/_settings.form-group.scss +3 -1
  118. package/src/styles/01-settings/_settings.form.scss +4 -2
  119. package/src/styles/01-settings/_settings.hero.scss +9 -7
  120. package/src/styles/01-settings/_settings.input.scss +9 -7
  121. package/src/styles/01-settings/_settings.list-group.scss +4 -2
  122. package/src/styles/01-settings/_settings.list.scss +4 -2
  123. package/src/styles/01-settings/_settings.menu.scss +10 -8
  124. package/src/styles/01-settings/_settings.messages.scss +19 -17
  125. package/src/styles/01-settings/_settings.modal.scss +6 -4
  126. package/src/styles/01-settings/_settings.nav.scss +6 -4
  127. package/src/styles/01-settings/_settings.navbar.scss +8 -5
  128. package/src/styles/01-settings/_settings.pagination.scss +5 -3
  129. package/src/styles/01-settings/_settings.popover.scss +6 -4
  130. package/src/styles/01-settings/_settings.rating.scss +5 -3
  131. package/src/styles/01-settings/_settings.river.scss +8 -6
  132. package/src/styles/01-settings/_settings.sectionintro.scss +8 -6
  133. package/src/styles/01-settings/_settings.select.scss +7 -5
  134. package/src/styles/01-settings/_settings.side-menu.scss +15 -13
  135. package/src/styles/01-settings/_settings.spacing.scss +4 -0
  136. package/src/styles/01-settings/_settings.steps.scss +7 -5
  137. package/src/styles/01-settings/_settings.tabs.scss +7 -5
  138. package/src/styles/01-settings/_settings.testimonials.scss +6 -4
  139. package/src/styles/01-settings/_settings.toggle.scss +3 -1
  140. package/src/styles/01-settings/_settings.tooltip.scss +5 -3
  141. package/src/styles/01-settings/_settings.upload.scss +22 -20
  142. package/src/styles/02-tools/_tools.animations.scss +19 -0
  143. package/src/styles/02-tools/_tools.background.scss +87 -0
  144. package/src/styles/02-tools/_tools.glass.scss +1 -0
  145. package/src/styles/02-tools/_tools.rem.scss +18 -5
  146. package/src/styles/02-tools/_tools.utility-api.scss +32 -26
  147. package/src/styles/03-generic/_generic.root.scss +15 -2
  148. package/src/styles/04-elements/_elements.body.scss +6 -0
  149. package/src/styles/06-components/_components.accordion.scss +24 -4
  150. package/src/styles/06-components/_components.atomix-glass.scss +0 -0
  151. package/src/styles/06-components/_components.avatar-group.scss +2 -1
  152. package/src/styles/06-components/_components.avatar.scss +2 -1
  153. package/src/styles/06-components/_components.badge.scss +36 -1
  154. package/src/styles/06-components/_components.breadcrumb.scss +2 -1
  155. package/src/styles/06-components/_components.button.scss +14 -3
  156. package/src/styles/06-components/_components.callout.scss +44 -4
  157. package/src/styles/06-components/_components.card.scss +21 -2
  158. package/src/styles/06-components/_components.chart.scss +3 -2
  159. package/src/styles/06-components/_components.checkbox.scss +2 -1
  160. package/src/styles/06-components/_components.color-mode-toggle.scss +3 -2
  161. package/src/styles/06-components/_components.countdown.scss +2 -1
  162. package/src/styles/06-components/_components.data-table.scss +7 -6
  163. package/src/styles/06-components/_components.datepicker.scss +20 -1
  164. package/src/styles/06-components/_components.dropdown.scss +11 -4
  165. package/src/styles/06-components/_components.edge-panel.scss +4 -3
  166. package/src/styles/06-components/_components.footer.scss +825 -0
  167. package/src/styles/06-components/_components.form-group.scss +1 -0
  168. package/src/styles/06-components/_components.hero.scss +4 -4
  169. package/src/styles/06-components/_components.image-gallery.scss +1 -0
  170. package/src/styles/06-components/_components.input.scss +33 -2
  171. package/src/styles/06-components/_components.list-group.scss +3 -2
  172. package/src/styles/06-components/_components.list.scss +2 -1
  173. package/src/styles/06-components/_components.menu.scss +5 -4
  174. package/src/styles/06-components/_components.messages.scss +8 -7
  175. package/src/styles/06-components/_components.modal.scss +3 -2
  176. package/src/styles/06-components/_components.nav.scss +6 -5
  177. package/src/styles/06-components/_components.navbar.scss +4 -3
  178. package/src/styles/06-components/_components.pagination.scss +2 -1
  179. package/src/styles/06-components/_components.photoviewer.scss +4 -3
  180. package/src/styles/06-components/_components.popover.scss +3 -2
  181. package/src/styles/06-components/_components.product-review.scss +3 -2
  182. package/src/styles/06-components/_components.progress.scss +3 -2
  183. package/src/styles/06-components/_components.river.scss +3 -2
  184. package/src/styles/06-components/_components.sectionintro.scss +2 -1
  185. package/src/styles/06-components/_components.select.scss +5 -4
  186. package/src/styles/06-components/_components.side-menu.scss +8 -7
  187. package/src/styles/06-components/_components.skeleton.scss +3 -2
  188. package/src/styles/06-components/_components.slider.scss +7 -6
  189. package/src/styles/06-components/_components.spinner.scss +1 -0
  190. package/src/styles/06-components/_components.steps.scss +3 -2
  191. package/src/styles/06-components/_components.tabs.scss +4 -3
  192. package/src/styles/06-components/_components.testimonials.scss +2 -1
  193. package/src/styles/06-components/_components.todo.scss +3 -2
  194. package/src/styles/06-components/_components.toggle.scss +5 -4
  195. package/src/styles/06-components/_components.tooltip.scss +3 -2
  196. package/src/styles/06-components/_components.upload.scss +4 -3
  197. package/src/styles/06-components/_components.video-player.scss +50 -27
  198. package/src/styles/06-components/_index.scss +2 -0
  199. package/src/styles/99-utilities/_utilities.glass-fixes.scss +48 -0
  200. package/dist/themes/yabai.css +0 -13711
  201. package/dist/themes/yabai.min.css +0 -189
@@ -0,0 +1,1281 @@
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
+
452
+ export function AtomixGlass({
453
+ children,
454
+ displacementScale = 15,
455
+ blurAmount = 0.5,
456
+ saturation = 120,
457
+ aberrationIntensity = 1,
458
+ elasticity = 0.15,
459
+ cornerRadius = 20,
460
+ globalMousePos: externalGlobalMousePos,
461
+ mouseOffset: externalMouseOffset,
462
+ mouseContainer = null,
463
+ className = '',
464
+ padding = '0 0',
465
+ overLight = false,
466
+ style = {},
467
+ mode = 'standard',
468
+ onClick,
469
+
470
+ 'aria-label': ariaLabel,
471
+ 'aria-describedby': ariaDescribedBy,
472
+ role,
473
+ tabIndex,
474
+
475
+ reducedMotion = false,
476
+ highContrast = false,
477
+ disableEffects = false,
478
+
479
+ enablePerformanceMonitoring = false,
480
+ }: AtomixGlassProps) {
481
+ 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
+ }
519
+
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,
625
+ 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]);
918
+
919
+ const directionalScale = useMemo(() => {
920
+ if (effectiveDisableEffects) {
921
+ return 'scale(1)';
922
+ }
923
+ return calculateDirectionalScale();
924
+ }, [calculateDirectionalScale, effectiveDisableEffects]);
925
+
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]);
932
+
933
+ const baseStyle = useMemo(
934
+ () => ({
935
+ ...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',
943
+ }),
944
+ }),
945
+ [style, transformStyle, effectiveReducedMotion, effectiveDisableEffects, effectiveHighContrast]
946
+ );
947
+
948
+ const positionStyles = useMemo(
949
+ () => ({
950
+ position: (baseStyle.position || 'absolute') as React.CSSProperties['position'],
951
+ top: baseStyle.top || 0,
952
+ left: baseStyle.left || 0,
953
+ }),
954
+ [baseStyle]
955
+ );
956
+
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
+ };
1005
+
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
+ ]);
1023
+
1024
+ const borderLayer1Style = useMemo(() => {
1025
+ const borderWidth = 1.5;
1026
+
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
+
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
+ ]);
1067
+
1068
+ const borderLayer2Style = useMemo(() => {
1069
+ const borderWidth = 1.5;
1070
+
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
+ ]);
1110
+
1111
+ const hoverEffect1Style = useMemo(() => {
1112
+ 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'],
1127
+ };
1128
+ }, [
1129
+ positionStyles,
1130
+ cornerRadius,
1131
+ baseStyle,
1132
+ isHovered,
1133
+ isActive,
1134
+ mouseOffset,
1135
+ effectiveReducedMotion,
1136
+ ]);
1137
+
1138
+ const hoverEffect2Style = useMemo(() => {
1139
+ 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'],
1155
+ };
1156
+ }, [positionStyles, cornerRadius, baseStyle, isActive, mouseOffset, effectiveReducedMotion]);
1157
+
1158
+ const hoverEffect3Style = useMemo(() => {
1159
+ 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
+ };
1175
+ }, [
1176
+ positionStyles,
1177
+ cornerRadius,
1178
+ baseStyle,
1179
+ isHovered,
1180
+ isActive,
1181
+ mouseOffset,
1182
+ effectiveReducedMotion,
1183
+ ]);
1184
+
1185
+ return (
1186
+ <div
1187
+ style={{ ...positionStyles, position: 'relative' }}
1188
+ role={role || (onClick ? 'button' : undefined)}
1189
+ tabIndex={onClick ? (tabIndex ?? 0) : tabIndex}
1190
+ aria-label={ariaLabel}
1191
+ 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
+ }
1203
+ >
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
+ />
1230
+
1231
+ <GlassContainer
1232
+ ref={glassRef}
1233
+ className={className}
1234
+ style={{
1235
+ ...baseStyle,
1236
+ transform: baseStyle.transform,
1237
+ }}
1238
+ cornerRadius={cornerRadius}
1239
+ displacementScale={
1240
+ effectiveDisableEffects ? 0 : overLight ? displacementScale * 0.5 : displacementScale
1241
+ }
1242
+ blurAmount={effectiveDisableEffects ? 0 : blurAmount}
1243
+ saturation={effectiveHighContrast ? 200 : saturation}
1244
+ aberrationIntensity={effectiveDisableEffects ? 0 : aberrationIntensity}
1245
+ glassSize={glassSize}
1246
+ padding={padding}
1247
+ 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)}
1253
+ active={isActive}
1254
+ isHovered={isHovered}
1255
+ isActive={isActive}
1256
+ overLight={overLight}
1257
+ onClick={onClick}
1258
+ mode={effectiveDisableEffects ? 'standard' : mode}
1259
+ transform={baseStyle.transform}
1260
+ effectiveDisableEffects={effectiveDisableEffects}
1261
+ effectiveReducedMotion={effectiveReducedMotion}
1262
+ >
1263
+ {children}
1264
+ </GlassContainer>
1265
+
1266
+ <span style={borderLayer1Style} />
1267
+
1268
+ <span style={borderLayer2Style} />
1269
+
1270
+ {Boolean(onClick) && (
1271
+ <>
1272
+ <div style={hoverEffect1Style} />
1273
+ <div style={hoverEffect2Style} />
1274
+ <div style={hoverEffect3Style} />
1275
+ </>
1276
+ )}
1277
+ </div>
1278
+ );
1279
+ }
1280
+
1281
+ export default AtomixGlass;