@shohojdhara/atomix 0.2.2 → 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 (108) hide show
  1. package/dist/atomix.css +292 -529
  2. package/dist/atomix.min.css +5 -5
  3. package/dist/index.d.ts +623 -121
  4. package/dist/index.esm.js +11475 -6047
  5. package/dist/index.esm.js.map +1 -1
  6. package/dist/index.js +4698 -2755
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.min.js +1 -1
  9. package/dist/index.min.js.map +1 -1
  10. package/dist/themes/boomdevs.css +291 -528
  11. package/dist/themes/boomdevs.min.css +6 -6
  12. package/dist/themes/esrar.css +292 -529
  13. package/dist/themes/esrar.min.css +6 -6
  14. package/dist/themes/mashroom.css +286 -526
  15. package/dist/themes/mashroom.min.css +6 -6
  16. package/dist/themes/shaj-default.css +286 -526
  17. package/dist/themes/shaj-default.min.css +6 -6
  18. package/package.json +66 -15
  19. package/src/components/Accordion/Accordion.stories.tsx +137 -0
  20. package/src/components/Accordion/Accordion.tsx +33 -3
  21. package/src/components/AtomixGlass/AtomixGlass.stories.tsx +3011 -0
  22. package/src/components/AtomixGlass/AtomixGlass.test.tsx +199 -0
  23. package/src/components/AtomixGlass/AtomixGlass.tsx +1281 -0
  24. package/src/components/AtomixGlass/AtomixGlassComprehensivePreview.stories.tsx +1369 -0
  25. package/src/components/AtomixGlass/README.md +134 -0
  26. package/src/components/AtomixGlass/index.ts +10 -0
  27. package/src/components/AtomixGlass/shader-utils.ts +140 -0
  28. package/src/components/AtomixGlass/utils.ts +8 -0
  29. package/src/components/Badge/Badge.stories.tsx +169 -0
  30. package/src/components/Badge/Badge.tsx +27 -2
  31. package/src/components/Button/Button.stories.tsx +345 -0
  32. package/src/components/Button/Button.tsx +35 -3
  33. package/src/components/Button/README.md +216 -0
  34. package/src/components/Callout/Callout.stories.tsx +813 -78
  35. package/src/components/Callout/Callout.test.tsx +368 -0
  36. package/src/components/Callout/Callout.tsx +26 -7
  37. package/src/components/Callout/README.md +409 -0
  38. package/src/components/Card/Card.stories.tsx +140 -0
  39. package/src/components/Card/Card.tsx +19 -3
  40. package/src/components/DatePicker/DatePicker copy.tsx +551 -0
  41. package/src/components/DatePicker/DatePicker.stories.tsx +188 -0
  42. package/src/components/DatePicker/DatePicker.tsx +379 -332
  43. package/src/components/DatePicker/readme.md +110 -1
  44. package/src/components/DatePicker/types.ts +8 -0
  45. package/src/components/Dropdown/Dropdown.stories.tsx +145 -0
  46. package/src/components/Dropdown/Dropdown.tsx +34 -5
  47. package/src/components/Form/Checkbox.stories.tsx +101 -0
  48. package/src/components/Form/Checkbox.tsx +26 -2
  49. package/src/components/Form/Input.stories.tsx +124 -0
  50. package/src/components/Form/Input.tsx +36 -7
  51. package/src/components/Form/Radio.stories.tsx +139 -0
  52. package/src/components/Form/Radio.tsx +26 -2
  53. package/src/components/Form/Select.stories.tsx +110 -0
  54. package/src/components/Form/Select.tsx +26 -2
  55. package/src/components/Form/Textarea.stories.tsx +104 -0
  56. package/src/components/Form/Textarea.tsx +36 -7
  57. package/src/components/Hero/Hero.stories.tsx +54 -1
  58. package/src/components/Hero/Hero.tsx +70 -11
  59. package/src/components/Modal/Modal.stories.tsx +235 -0
  60. package/src/components/Modal/Modal.tsx +64 -35
  61. package/src/components/Pagination/Pagination.stories.tsx +101 -0
  62. package/src/components/Pagination/Pagination.tsx +25 -1
  63. package/src/components/Popover/Popover.stories.tsx +94 -0
  64. package/src/components/Popover/Popover.tsx +30 -4
  65. package/src/components/Rating/Rating.stories.tsx +112 -0
  66. package/src/components/Rating/Rating.tsx +25 -1
  67. package/src/components/Steps/Steps.stories.tsx +119 -0
  68. package/src/components/Steps/Steps.tsx +32 -1
  69. package/src/components/Tab/Tab.stories.tsx +88 -0
  70. package/src/components/Tab/Tab.tsx +32 -1
  71. package/src/components/Toggle/Toggle.stories.tsx +92 -0
  72. package/src/components/Toggle/Toggle.tsx +32 -1
  73. package/src/components/Tooltip/Tooltip.stories.tsx +131 -0
  74. package/src/components/Tooltip/Tooltip.tsx +43 -7
  75. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +1002 -196
  76. package/src/components/VideoPlayer/VideoPlayer.tsx +161 -4
  77. package/src/components/index.ts +1 -0
  78. package/src/lib/composables/index.ts +4 -0
  79. package/src/lib/composables/useAtomixGlass.ts +71 -0
  80. package/src/lib/composables/useButton.ts +3 -1
  81. package/src/lib/composables/useCallout.ts +4 -1
  82. package/src/lib/composables/useGlassContainer.ts +168 -0
  83. package/src/lib/constants/components.ts +88 -0
  84. package/src/lib/types/components.ts +352 -0
  85. package/src/lib/utils/displacement-generator.ts +86 -0
  86. package/src/styles/01-settings/_settings.background.scss +8 -7
  87. package/src/styles/01-settings/_settings.callout.scss +7 -7
  88. package/src/styles/02-tools/_tools.animations.scss +19 -0
  89. package/src/styles/02-tools/_tools.background.scss +19 -17
  90. package/src/styles/02-tools/_tools.glass.scss +1 -0
  91. package/src/styles/03-generic/_generic.root.scss +3 -2
  92. package/src/styles/04-elements/_elements.body.scss +0 -18
  93. package/src/styles/06-components/_components.accordion.scss +16 -0
  94. package/src/styles/06-components/_components.atomix-glass.scss +0 -0
  95. package/src/styles/06-components/_components.badge.scss +34 -0
  96. package/src/styles/06-components/_components.button.scss +10 -0
  97. package/src/styles/06-components/_components.callout.scss +41 -2
  98. package/src/styles/06-components/_components.card.scss +17 -0
  99. package/src/styles/06-components/_components.chart.scss +1 -1
  100. package/src/styles/06-components/_components.datepicker.scss +18 -0
  101. package/src/styles/06-components/_components.dropdown.scss +7 -1
  102. package/src/styles/06-components/_components.hero.scss +1 -2
  103. package/src/styles/06-components/_components.input.scss +31 -1
  104. package/src/styles/06-components/_components.video-player.scss +48 -26
  105. package/src/styles/06-components/_index.scss +1 -0
  106. package/src/styles/99-utilities/_utilities.glass-fixes.scss +48 -0
  107. package/dist/themes/yabai.css +0 -15207
  108. 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;