@shohojdhara/atomix 0.6.2 → 0.6.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 (62) hide show
  1. package/README.md +510 -106
  2. package/dist/atomix.css +28 -24
  3. package/dist/atomix.css.map +1 -1
  4. package/dist/atomix.min.css +5 -5
  5. package/dist/atomix.min.css.map +1 -1
  6. package/dist/atomix.umd.js +1 -1
  7. package/dist/atomix.umd.js.map +1 -1
  8. package/dist/atomix.umd.min.js +1 -1
  9. package/dist/charts.d.ts +2 -2
  10. package/dist/charts.js +251 -131
  11. package/dist/charts.js.map +1 -1
  12. package/dist/core.d.ts +5 -39
  13. package/dist/core.js +254 -137
  14. package/dist/core.js.map +1 -1
  15. package/dist/forms.d.ts +2 -1
  16. package/dist/forms.js +342 -177
  17. package/dist/forms.js.map +1 -1
  18. package/dist/heavy.js +254 -135
  19. package/dist/heavy.js.map +1 -1
  20. package/dist/index.d.ts +141 -159
  21. package/dist/index.esm.js +348 -195
  22. package/dist/index.esm.js.map +1 -1
  23. package/dist/index.js +348 -195
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.min.js +1 -1
  26. package/dist/index.min.js.map +1 -1
  27. package/dist/theme.d.ts +14 -6
  28. package/dist/theme.js +2 -9
  29. package/dist/theme.js.map +1 -1
  30. package/package.json +26 -26
  31. package/src/components/AtomixGlass/AtomixGlass.tsx +1 -1
  32. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +8 -1
  33. package/src/components/AtomixGlass/glass-utils.ts +29 -0
  34. package/src/components/AtomixGlass/stories/Playground.stories.tsx +32 -1
  35. package/src/components/Button/Button.stories.tsx +1 -1
  36. package/src/components/Button/Button.tsx +6 -5
  37. package/src/components/Card/Card.tsx +2 -2
  38. package/src/components/Dropdown/Dropdown.tsx +1 -0
  39. package/src/components/EdgePanel/EdgePanel.tsx +1 -3
  40. package/src/components/Form/Select.test.tsx +75 -0
  41. package/src/components/Form/Select.tsx +348 -252
  42. package/src/components/Form/SelectOption.tsx +16 -10
  43. package/src/components/index.ts +1 -1
  44. package/src/layouts/CssGrid/index.ts +1 -0
  45. package/src/lib/composables/useAtomixGlass.ts +238 -138
  46. package/src/lib/composables/useAtomixGlassStyles.ts +201 -149
  47. package/src/lib/constants/components.ts +50 -35
  48. package/src/lib/theme/config/configLoader.ts +1 -1
  49. package/src/lib/theme/test/testTheme.ts +2 -2
  50. package/src/lib/theme/utils/themeUtils.ts +98 -110
  51. package/src/lib/types/components.ts +21 -63
  52. package/src/styles/01-settings/_settings.spacing.scss +6 -1
  53. package/src/styles/03-generic/_generic.reset.scss +1 -1
  54. package/src/styles/06-components/_components.atomix-glass.scss +20 -29
  55. package/src/styles/06-components/_components.data-table.scss +5 -4
  56. package/src/styles/06-components/_components.dynamic-background.scss +9 -8
  57. package/src/styles/06-components/_components.footer.scss +8 -7
  58. package/src/styles/06-components/_components.hero.scss +2 -2
  59. package/src/styles/06-components/_components.messages.scss +16 -16
  60. package/src/styles/06-components/_components.select.scss +15 -2
  61. package/src/styles/06-components/_components.upload.scss +3 -3
  62. package/CHANGELOG.md +0 -165
package/dist/forms.js CHANGED
@@ -47,12 +47,23 @@ import React, { memo, forwardRef, useId, useMemo, useState, useRef, useEffect, u
47
47
  },
48
48
  DEFAULTS: {
49
49
  DISPLACEMENT_SCALE: 70,
50
- BLUR_AMOUNT: 0,
51
- SATURATION: 140,
52
- ABERRATION_INTENSITY: 2,
50
+ get BLUR_AMOUNT() {
51
+ return .15 * this.DISPLACEMENT_SCALE;
52
+ // Dynamically computed based on displacement
53
+ },
54
+ get SATURATION() {
55
+ return 100 + .5 * this.DISPLACEMENT_SCALE;
56
+ // Saturate relative to intensity
57
+ },
58
+ get ABERRATION_INTENSITY() {
59
+ return .03 * this.DISPLACEMENT_SCALE;
60
+ // Scale aberration with displacement
61
+ },
53
62
  ELASTICITY: .15,
54
- CORNER_RADIUS: 20,
55
- // Default border-radius matching design system
63
+ get CORNER_RADIUS() {
64
+ return 16;
65
+ // Use 16 to match SCSS design system (was 20)
66
+ },
56
67
  PADDING: "0",
57
68
  MODE: "standard",
58
69
  OVER_LIGHT: !1,
@@ -78,6 +89,11 @@ import React, { memo, forwardRef, useId, useMemo, useState, useRef, useEffect, u
78
89
  ELASTICITY_TRANSLATION_FACTOR: .1,
79
90
  ELASTICITY_DISTANCE_THRESHOLD: 200,
80
91
  ELASTICITY_COMPRESSION_FACTOR: .3,
92
+ ELASTICITY_STIFFNESS: .1,
93
+ ELASTICITY_DAMPING: .76,
94
+ ELASTICITY_VELOCITY_FACTOR: .65,
95
+ ELASTICITY_STRETCH_RATIO: .45,
96
+ ELASTICITY_MAGNIFICATION_BASE: 1.02,
81
97
  // Note: This default must match the SCSS variable --atomix-radius-md
82
98
  // @see src/styles/01-settings/_settings.global.scss
83
99
  DEFAULT_CORNER_RADIUS: 16,
@@ -94,84 +110,126 @@ import React, { memo, forwardRef, useId, useMemo, useState, useRef, useEffect, u
94
110
  // Base angle for border gradients (degrees)
95
111
  ANGLE_MULTIPLIER: 1.2,
96
112
  // Multiplier for mouse influence on angle
113
+ VELOCITY_ANGLE_MULTIPLIER: 2.5,
114
+ // How much velocity affects gradient rotation
115
+ CHROMATIC_OFFSET: 1.5,
116
+ // Degree offset for chromatic rim layers
97
117
  BORDER_STOP_1: {
98
118
  MIN: 10,
99
119
  // Minimum percentage for border stop 1
100
120
  BASE: 33,
101
121
  // Base percentage for border stop 1
102
- MULTIPLIER: .3
122
+ get MULTIPLIER() {
123
+ return .009 * this.BASE;
124
+ }
103
125
  },
104
126
  BORDER_STOP_2: {
105
127
  MAX: 90,
106
128
  // Maximum percentage for border stop 2
107
129
  BASE: 66,
108
130
  // Base percentage for border stop 2
109
- MULTIPLIER: .4
131
+ get MULTIPLIER() {
132
+ return .006 * this.BASE;
133
+ }
110
134
  },
111
135
  BORDER_OPACITY: {
112
136
  BASE_1: .12,
113
137
  // Base opacity for border gradient 1
114
- BASE_2: .4,
138
+ get BASE_2() {
139
+ return 3.33 * this.BASE_1;
140
+ },
115
141
  // Base opacity for border gradient 2
116
- BASE_3: .32,
142
+ get BASE_3() {
143
+ return 2.66 * this.BASE_1;
144
+ },
117
145
  // Base opacity for border gradient 3
118
- BASE_4: .6,
146
+ get BASE_4() {
147
+ return 5 * this.BASE_1;
148
+ },
119
149
  // Base opacity for border gradient 4
120
- MULTIPLIER_LOW: .008,
150
+ get MULTIPLIER_LOW() {
151
+ return .066 * this.BASE_1;
152
+ },
121
153
  // Low multiplier for mouse influence on opacity
122
- MULTIPLIER_HIGH: .012
154
+ get MULTIPLIER_HIGH() {
155
+ return .1 * this.BASE_1;
156
+ }
123
157
  },
124
158
  CENTER_POSITION: 50,
125
159
  // Center position percentage (50%)
126
160
  HOVER_POSITION: {
127
161
  DIVISOR_1: 2,
128
162
  // Divisor for hover 1 position calculation
129
- DIVISOR_2: 1.5,
163
+ get DIVISOR_2() {
164
+ return .75 * this.DIVISOR_1;
165
+ },
130
166
  // Divisor for hover 2 position calculation
131
- MULTIPLIER_3: 1
167
+ get MULTIPLIER_3() {
168
+ return .5 * this.DIVISOR_1;
169
+ }
132
170
  },
133
- BASE_LAYER_MULTIPLIER: .5
171
+ get BASE_LAYER_MULTIPLIER() {
172
+ return .5;
173
+ }
134
174
  },
135
175
  // Gradient opacity values for hover effects
136
176
  GRADIENT_OPACITY: {
137
177
  HOVER_1: {
138
178
  BLACK_START: .3,
139
179
  // Start opacity for black hover 1
140
- BLACK_MID: .1,
180
+ get BLACK_MID() {
181
+ return this.BLACK_START / 3;
182
+ },
141
183
  // Mid opacity for black hover 1
142
184
  BLACK_STOP: 30,
143
185
  // Stop percentage for black hover 1
144
- BLACK_END: 60,
186
+ get BLACK_END() {
187
+ return 2 * this.BLACK_STOP;
188
+ },
145
189
  // End percentage for black hover 1
146
190
  WHITE_START: .5,
147
191
  // Start opacity for white hover 1
148
- WHITE_STOP: 50
192
+ get WHITE_STOP() {
193
+ return this.BLACK_END - 10;
194
+ }
149
195
  },
150
196
  HOVER_2: {
151
197
  BLACK_START: .4,
152
198
  // Start opacity for black hover 2
153
- BLACK_MID: .15,
199
+ get BLACK_MID() {
200
+ return .375 * this.BLACK_START;
201
+ },
154
202
  // Mid opacity for black hover 2
155
203
  BLACK_STOP: 40,
156
204
  // Stop percentage for black hover 2
157
- BLACK_END: 80,
205
+ get BLACK_END() {
206
+ return 2 * this.BLACK_STOP;
207
+ },
158
208
  // End percentage for black hover 2
159
209
  WHITE_START: 1,
160
210
  // Start opacity for white hover 2
161
- WHITE_STOP: 80
211
+ get WHITE_STOP() {
212
+ return this.BLACK_END;
213
+ }
162
214
  },
163
215
  HOVER_3: {
164
216
  BLACK_START: .5,
165
217
  // Start opacity for black hover 3
166
- BLACK_MID: .2,
218
+ get BLACK_MID() {
219
+ return .4 * this.BLACK_START;
220
+ },
167
221
  // Mid opacity for black hover 3
168
222
  BLACK_STOP: 50,
169
223
  // Stop percentage for black hover 3
170
- BLACK_END: 100,
224
+ get BLACK_END() {
225
+ return 2 * this.BLACK_STOP;
226
+ },
171
227
  // End percentage for black hover 3
172
228
  WHITE_START: 1,
173
229
  // Start opacity for white hover 3
174
- WHITE_STOP: 100
230
+ get WHITE_STOP() {
231
+ return this.BLACK_END;
232
+ }
175
233
  }
176
234
  },
177
235
  // Base and overlay gradient constants
@@ -180,34 +238,54 @@ import React, { memo, forwardRef, useId, useMemo, useState, useRef, useEffect, u
180
238
  // Gradient angle in degrees
181
239
  BLACK_START_BASE: .15,
182
240
  // Base start opacity for black
183
- BLACK_START_MULTIPLIER: .003,
241
+ get BLACK_START_MULTIPLIER() {
242
+ return .02 * this.BLACK_START_BASE;
243
+ },
184
244
  // Multiplier for mouse X influence on start
185
245
  BLACK_MID_BASE: .1,
186
246
  // Base mid opacity for black
187
- BLACK_MID_MULTIPLIER: .002,
247
+ get BLACK_MID_MULTIPLIER() {
248
+ return .02 * this.BLACK_MID_BASE;
249
+ },
188
250
  // Multiplier for mouse Y influence on mid
189
251
  BLACK_MID_STOP: 50,
190
252
  // Mid stop percentage
191
- BLACK_END_BASE: .18,
253
+ get BLACK_END_BASE() {
254
+ return 1.2 * this.BLACK_START_BASE;
255
+ },
192
256
  // Base end opacity for black
193
- BLACK_END_MULTIPLIER: .004,
257
+ get BLACK_END_MULTIPLIER() {
258
+ return .022 * this.BLACK_END_BASE;
259
+ },
194
260
  // Multiplier for mouse X influence on end
195
- WHITE_OPACITY: .1
261
+ get WHITE_OPACITY() {
262
+ return .666 * this.BLACK_START_BASE;
263
+ }
196
264
  },
197
265
  OVERLAY_GRADIENT: {
198
266
  BLACK_START_BASE: .12,
199
267
  // Base start opacity for black overlay
200
- BLACK_START_MULTIPLIER: .003,
268
+ get BLACK_START_MULTIPLIER() {
269
+ return .025 * this.BLACK_START_BASE;
270
+ },
201
271
  // Multiplier for mouse X influence on start
202
- BLACK_MID: .06,
272
+ get BLACK_MID() {
273
+ return .5 * this.BLACK_START_BASE;
274
+ },
203
275
  // Mid opacity for black overlay
204
276
  BLACK_MID_STOP: 40,
205
277
  // Mid stop percentage
206
- BLACK_END_BASE: .15,
278
+ get BLACK_END_BASE() {
279
+ return 1.25 * this.BLACK_START_BASE;
280
+ },
207
281
  // Base end opacity for black overlay
208
- BLACK_END_MULTIPLIER: .003,
282
+ get BLACK_END_MULTIPLIER() {
283
+ return .02 * this.BLACK_END_BASE;
284
+ },
209
285
  // Multiplier for mouse Y influence on end
210
- WHITE_OPACITY: .05
286
+ get WHITE_OPACITY() {
287
+ return .416 * this.BLACK_START_BASE;
288
+ }
211
289
  },
212
290
  // Overlay highlight constants
213
291
  OVERLAY_HIGHLIGHT: {
@@ -217,9 +295,13 @@ import React, { memo, forwardRef, useId, useMemo, useState, useRef, useEffect, u
217
295
  // Y position percentage
218
296
  WHITE_OPACITY: .4,
219
297
  // White opacity in gradient
220
- STOP: 60,
298
+ get STOP() {
299
+ return 150 * this.WHITE_OPACITY;
300
+ },
221
301
  // Stop percentage
222
- OPACITY_MULTIPLIER: .7
302
+ get OPACITY_MULTIPLIER() {
303
+ return 1.75 * this.WHITE_OPACITY;
304
+ }
223
305
  },
224
306
  // Displacement and aberration multipliers
225
307
  MULTIPLIERS: {
@@ -272,11 +354,7 @@ import React, { memo, forwardRef, useId, useMemo, useState, useRef, useEffect, u
272
354
  }
273
355
  }
274
356
  }
275
- }, {CONSTANTS: CONSTANTS$2} = ATOMIX_GLASS, calculateDistance = (pos1, pos2) => {
276
- if (!pos1 || !pos2 || "number" != typeof pos1.x || "number" != typeof pos1.y || "number" != typeof pos2.x || "number" != typeof pos2.y) return 0;
277
- const deltaX = pos1.x - pos2.x, deltaY = pos1.y - pos2.y;
278
- return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
279
- }, calculateElementCenter = rect => rect ? {
357
+ }, {CONSTANTS: CONSTANTS$2} = ATOMIX_GLASS, calculateElementCenter = rect => rect ? {
280
358
  x: rect.left + rect.width / 2,
281
359
  y: rect.top + rect.height / 2
282
360
  } : {
@@ -348,7 +426,16 @@ import React, { memo, forwardRef, useId, useMemo, useState, useRef, useEffect, u
348
426
  // Silently handle errors
349
427
  }
350
428
  return CONSTANTS$2.DEFAULT_CORNER_RADIUS;
351
- }, lerp$1 = (a, b, t) => a + (b - a) * t, softClamp = (value, max) => max <= 0 ? 0 : max * (1 - Math.exp(-value / max)), getDisplacementMap = (mode, displacementMap, polarDisplacementMap, prominentDisplacementMap, shaderMapUrl) => {
429
+ }, smoothstep = t => {
430
+ const clamped = Math.max(0, Math.min(1, t));
431
+ return clamped * clamped * (3 - 2 * clamped);
432
+ }, lerp$1 = (a, b, t) => a + (b - a) * t, softClamp = (value, max) => max <= 0 ? 0 : max * (1 - Math.exp(-value / max)), calculateSpring = (current, target, velocity, stiffness = .1, damping = .8) => {
433
+ const newVelocity = (velocity + (target - current) * stiffness) * damping;
434
+ return {
435
+ value: current + newVelocity,
436
+ velocity: newVelocity
437
+ };
438
+ }, getDisplacementMap = (mode, displacementMap, polarDisplacementMap, prominentDisplacementMap, shaderMapUrl) => {
352
439
  switch (mode) {
353
440
  case "standard":
354
441
  return displacementMap;
@@ -739,6 +826,9 @@ shaderTime: shaderTime, withTimeAnimation: withTimeAnimation = !1, animationSpee
739
826
  }), jsx("div", {
740
827
  ref: contentRef,
741
828
  className: ATOMIX_GLASS.CONTENT_CLASS,
829
+ style: {
830
+ transform: "var(--atomix-glass-child-parallax, none)"
831
+ },
742
832
  children: children
743
833
  }) ]
744
834
  })
@@ -863,51 +953,26 @@ class {
863
953
  }
864
954
  }, updateAtomixGlassStyles = (wrapperElement, containerElement, params) => {
865
955
  if (!wrapperElement && !containerElement) return;
866
- const {mouseOffset: mouseOffset, globalMousePosition: globalMousePosition, glassSize: glassSize, isHovered: isHovered, isActive: isActive, isOverLight: isOverLight, baseOverLightConfig: baseOverLightConfig, effectiveBorderRadius: effectiveBorderRadius, effectiveWithoutEffects: effectiveWithoutEffects, effectiveReducedMotion: effectiveReducedMotion, elasticity: elasticity, directionalScale: directionalScale, onClick: onClick, withLiquidBlur: withLiquidBlur, blurAmount: blurAmount = ATOMIX_GLASS.DEFAULTS.BLUR_AMOUNT, saturation: saturation = ATOMIX_GLASS.DEFAULTS.SATURATION, padding: padding = ATOMIX_GLASS.DEFAULTS.PADDING, isFixedOrSticky: isFixedOrSticky = !1} = params, mouseInfluence = calculateMouseInfluence(mouseOffset), hoverIntensity = isHovered ? 1.4 : 1, activeIntensity = isActive ? 1.6 : 1, overLightConfig = {
956
+ if (!validateGlassSize(params.glassSize)) return;
957
+ const {mouseOffset: mouseOffset, globalMousePosition: globalMousePosition, glassSize: glassSize, isHovered: isHovered, isActive: isActive, isOverLight: isOverLight, baseOverLightConfig: baseOverLightConfig, effectiveBorderRadius: effectiveBorderRadius, effectiveWithoutEffects: effectiveWithoutEffects, effectiveReducedMotion: effectiveReducedMotion, elasticity: elasticity, elasticTranslation: elasticTranslation, elasticVelocity: elasticVelocity, mouseVelocity: mouseVelocity, directionalScale: directionalScale, scaleBase: scaleBase, onClick: onClick, withLiquidBlur: withLiquidBlur, blurAmount: blurAmount = ATOMIX_GLASS.DEFAULTS.BLUR_AMOUNT, saturation: saturation = ATOMIX_GLASS.DEFAULTS.SATURATION, padding: padding = ATOMIX_GLASS.DEFAULTS.PADDING, isFixedOrSticky: isFixedOrSticky = !1} = params, mouseInfluence = calculateMouseInfluence(mouseOffset), hoverIntensity = isHovered ? 1.4 : 1, activeIntensity = isActive ? 1.6 : 1, overLightConfig = {
867
958
  opacity: baseOverLightConfig.opacity * hoverIntensity * activeIntensity,
868
959
  contrast: Math.min(1.6, baseOverLightConfig.contrast + .1 * mouseInfluence),
869
960
  brightness: Math.min(1.1, baseOverLightConfig.brightness + .05 * mouseInfluence),
870
961
  shadowIntensity: Math.min(1.2, Math.max(.5, baseOverLightConfig.shadowIntensity + .2 * mouseInfluence)),
871
962
  borderOpacity: Math.min(1, Math.max(.3, baseOverLightConfig.borderOpacity + .1 * mouseInfluence)),
872
963
  saturationBoost: baseOverLightConfig.saturationBoost
873
- };
874
- // Calculate mouse influence
875
- let computedDirectionalScale = directionalScale, elasticTranslation = {
964
+ }, scaleX = directionalScale.x * scaleBase, scaleY = directionalScale.y * scaleBase, transformStyle = effectiveWithoutEffects ? `scale(${scaleBase})` : `translate(${elasticTranslation.x}px, ${elasticTranslation.y}px) scaleX(${scaleX}) scaleY(${scaleY})`, stretchMagnitude = ((pos1, pos2) => {
965
+ if (!pos1 || !pos2 || "number" != typeof pos1.x || "number" != typeof pos1.y || "number" != typeof pos2.x || "number" != typeof pos2.y) return 0;
966
+ const deltaX = pos1.x - pos2.x, deltaY = pos1.y - pos2.y;
967
+ return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
968
+ })({
876
969
  x: 0,
877
970
  y: 0
878
- };
879
- // Calculate elastic translation and directional scale
880
- if (!effectiveWithoutEffects && wrapperElement) {
881
- const rect = wrapperElement.getBoundingClientRect(), center = calculateElementCenter(rect);
882
- // Mouse presence and edge distance logic
883
- if (globalMousePosition.x && globalMousePosition.y && validateGlassSize(glassSize)) {
884
- const deltaX = globalMousePosition.x - center.x, deltaY = globalMousePosition.y - center.y, edgeDistanceX = Math.max(0, Math.abs(deltaX) - glassSize.width / 2), edgeDistanceY = Math.max(0, Math.abs(deltaY) - glassSize.height / 2), edgeDistance = calculateDistance({
885
- x: edgeDistanceX,
886
- y: edgeDistanceY
887
- }, {
888
- x: 0,
889
- y: 0
890
- }), rawT = edgeDistance > ATOMIX_GLASS.CONSTANTS.ACTIVATION_ZONE ? 0 : 1 - edgeDistance / ATOMIX_GLASS.CONSTANTS.ACTIVATION_ZONE, fadeInFactor = (t => {
891
- const clamped = Math.max(0, Math.min(1, t));
892
- return clamped * clamped * (3 - 2 * clamped);
893
- })(rawT);
894
- // Directional scale
895
- if (elasticTranslation = {
896
- x: deltaX * elasticity * .1 * fadeInFactor,
897
- y: deltaY * elasticity * .1 * fadeInFactor
898
- }, !isOverLight && edgeDistance <= ATOMIX_GLASS.CONSTANTS.ACTIVATION_ZONE) {
899
- const centerDistance = calculateDistance(globalMousePosition, center);
900
- if (centerDistance > 0) {
901
- const normalizedX = deltaX / centerDistance, normalizedY = deltaY / centerDistance, stretchIntensity = Math.min(centerDistance / 300, 1) * elasticity * rawT, scaleX = 1 + Math.abs(normalizedX) * stretchIntensity * .3 - Math.abs(normalizedY) * stretchIntensity * .15, scaleY = 1 + Math.abs(normalizedY) * stretchIntensity * .3 - Math.abs(normalizedX) * stretchIntensity * .15, softScaleX = 1 - softClamp(Math.max(0, 1 - scaleX), .2), softScaleY = 1 - softClamp(Math.max(0, 1 - scaleY), .2);
902
- computedDirectionalScale = `scaleX(${Math.max(.85, softScaleX)}) scaleY(${Math.max(.85, softScaleY)})`;
903
- }
904
- }
905
- }
906
- }
907
- const transformStyle = effectiveWithoutEffects ? isActive && Boolean(onClick) ? "scale(0.98)" : "scale(1)" : `translate(${elasticTranslation.x}px, ${elasticTranslation.y}px) ${isActive && Boolean(onClick) ? "scale(0.96)" : computedDirectionalScale}`;
908
- // Update Wrapper Styles (glassVars)
909
- if (wrapperElement) {
910
- const mx = mouseOffset.x, my = mouseOffset.y, absMx = Math.abs(mx), absMy = Math.abs(my), GRADIENT = ATOMIX_GLASS.CONSTANTS.GRADIENT, borderGradientAngle = GRADIENT.BASE_ANGLE + mx * GRADIENT.ANGLE_MULTIPLIER, borderStop1 = Math.max(GRADIENT.BORDER_STOP_1.MIN, GRADIENT.BORDER_STOP_1.BASE + my * GRADIENT.BORDER_STOP_1.MULTIPLIER), borderStop2 = Math.min(GRADIENT.BORDER_STOP_2.MAX, GRADIENT.BORDER_STOP_2.BASE + my * GRADIENT.BORDER_STOP_2.MULTIPLIER), borderOpacities = [ GRADIENT.BORDER_OPACITY.BASE_1 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW, GRADIENT.BORDER_OPACITY.BASE_2 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH, GRADIENT.BORDER_OPACITY.BASE_3 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW, GRADIENT.BORDER_OPACITY.BASE_4 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH ], configBorderOpacity = overLightConfig.borderOpacity, whiteColor = ATOMIX_GLASS.CONSTANTS.PALETTE.WHITE, blackColor = ATOMIX_GLASS.CONSTANTS.PALETTE.BLACK, hoverPositions = {
971
+ }, elasticTranslation), tensionFactor = smoothstep(stretchMagnitude / 80), lightingContrast = Math.min(1.8, overLightConfig.contrast + .2 * tensionFactor), lightingBrightness = Math.min(1.2, overLightConfig.brightness + .1 * tensionFactor);
972
+ // Calculate mouse influence
973
+ // Update Wrapper Styles (glassVars)
974
+ if (wrapperElement) {
975
+ const mx = mouseOffset.x, my = mouseOffset.y, absMx = Math.abs(mx), absMy = Math.abs(my), GRADIENT = ATOMIX_GLASS.CONSTANTS.GRADIENT, velocityRotation = (mouseVelocity.x + elasticVelocity.x) * (GRADIENT.VELOCITY_ANGLE_MULTIPLIER || 2.5), borderGradientAngle = GRADIENT.BASE_ANGLE + mx * GRADIENT.ANGLE_MULTIPLIER + velocityRotation, chromaticOffset = GRADIENT.CHROMATIC_OFFSET || 1.5, angleR = borderGradientAngle - chromaticOffset, angleB = borderGradientAngle + chromaticOffset, borderStop1 = Math.max(GRADIENT.BORDER_STOP_1.MIN, GRADIENT.BORDER_STOP_1.BASE + my * GRADIENT.BORDER_STOP_1.MULTIPLIER), borderStop2 = Math.min(GRADIENT.BORDER_STOP_2.MAX, GRADIENT.BORDER_STOP_2.BASE + my * GRADIENT.BORDER_STOP_2.MULTIPLIER), tensionGlow = 1 + .5 * tensionFactor, borderOpacities = [ (GRADIENT.BORDER_OPACITY.BASE_1 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW) * tensionGlow, (GRADIENT.BORDER_OPACITY.BASE_2 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH) * tensionGlow, (GRADIENT.BORDER_OPACITY.BASE_3 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW) * tensionGlow, (GRADIENT.BORDER_OPACITY.BASE_4 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH) * tensionGlow ], configBorderOpacity = overLightConfig.borderOpacity, whiteColor = ATOMIX_GLASS.CONSTANTS.PALETTE.WHITE, blackColor = ATOMIX_GLASS.CONSTANTS.PALETTE.BLACK, hoverPositions = {
911
976
  hover1: {
912
977
  x: GRADIENT.CENTER_POSITION + mx / GRADIENT.HOVER_POSITION.DIVISOR_1,
913
978
  y: GRADIENT.CENTER_POSITION + my / GRADIENT.HOVER_POSITION.DIVISOR_1
@@ -930,10 +995,16 @@ class {
930
995
  base: isOverLight ? overLightConfig.opacity : 0,
931
996
  over: isOverLight ? 1.1 * overLightConfig.opacity : 0
932
997
  }, style = wrapperElement.style;
933
- style.setProperty("--atomix-glass-transform", transformStyle || "none"),
934
- // Gradients
935
- style.setProperty("--atomix-glass-border-gradient-1", `linear-gradient(${borderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${(borderOpacities[0] ?? 1) * configBorderOpacity}) ${borderStop1}%, rgba(${whiteColor}, ${(borderOpacities[1] ?? 1) * configBorderOpacity}) ${borderStop2}%, rgba(${whiteColor}, 0) 100%)`),
936
- style.setProperty("--atomix-glass-border-gradient-2", `linear-gradient(${borderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${(borderOpacities[2] ?? 1) * configBorderOpacity}) ${borderStop1}%, rgba(${whiteColor}, ${(borderOpacities[3] ?? 1) * configBorderOpacity}) ${borderStop2}%, rgba(${whiteColor}, 0) 100%)`),
998
+ style.setProperty("--atomix-glass-transform", transformStyle || "none");
999
+ // Parallax for content (liquid refraction feel)
1000
+ const parallaxFactor = .38 + .12 * tensionFactor;
1001
+ style.setProperty("--atomix-glass-child-parallax", `translate(${elasticTranslation.x * -parallaxFactor}px, ${elasticTranslation.y * -parallaxFactor}px)`),
1002
+ style.setProperty("--atomix-glass-contrast", lightingContrast.toString()), style.setProperty("--atomix-glass-brightness", lightingBrightness.toString()),
1003
+ // ── Chromatic Rim Lighting ──────────────────────────────────────
1004
+ // Layer 1: Core White/Blue highlight
1005
+ style.setProperty("--atomix-glass-border-gradient-1", `linear-gradient(${angleB}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${(borderOpacities[0] ?? 1) * configBorderOpacity}) ${borderStop1}%, rgba(${whiteColor}, ${(borderOpacities[1] ?? 1) * configBorderOpacity}) ${borderStop2}%, rgba(${whiteColor}, 0) 100%)`),
1006
+ // Layer 2: Subtle Red/Warm highlight (offset angle)
1007
+ style.setProperty("--atomix-glass-border-gradient-2", `linear-gradient(${angleR}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${(borderOpacities[2] ?? 1) * configBorderOpacity}) ${borderStop1}%, rgba(${whiteColor}, ${(borderOpacities[3] ?? 1) * configBorderOpacity}) ${borderStop2}%, rgba(${whiteColor}, 0) 100%)`),
937
1008
  // Hover gradients
938
1009
  style.setProperty("--atomix-glass-hover-1-gradient", isOverLight ? `radial-gradient(circle at ${hoverPositions.hover1.x}% ${hoverPositions.hover1.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_START}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_STOP}%, rgba(${blackColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_END}%)` : `radial-gradient(circle at ${hoverPositions.hover1.x}% ${hoverPositions.hover1.y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.WHITE_STOP}%)`),
939
1010
  style.setProperty("--atomix-glass-hover-2-gradient", isOverLight ? `radial-gradient(circle at ${hoverPositions.hover2.x}% ${hoverPositions.hover2.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_START}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_STOP}%, rgba(${blackColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_END}%)` : `radial-gradient(circle at ${hoverPositions.hover2.x}% ${hoverPositions.hover2.y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.WHITE_STOP}%)`),
@@ -961,7 +1032,7 @@ class {
961
1032
  flowBlur: blurAmount * FLOW_BLUR_MULTIPLIER
962
1033
  };
963
1034
  if (withLiquidBlur && rect) {
964
- const mouseInfluence = calculateMouseInfluence(mouseOffset), maxBlur = blurAmount * MAX_BLUR_RELATIVE, baseBlur = Math.min(maxBlur, blurAmount + mouseInfluence * blurAmount * MOUSE_INFLUENCE_BLUR_FACTOR), edgeIntensity = mouseInfluence * EDGE_INTENSITY_MOUSE_FACTOR, edgeBlur = Math.min(maxBlur, baseBlur * (.8 + .4 * edgeIntensity)), centerIntensity = mouseInfluence * CENTER_INTENSITY_MOUSE_FACTOR, centerBlur = Math.min(maxBlur, baseBlur * (.3 + .3 * centerIntensity)), flowBlur = Math.min(maxBlur, baseBlur * FLOW_BLUR_MULTIPLIER);
1035
+ const mouseInfluence = calculateMouseInfluence(mouseOffset), maxBlur = blurAmount * MAX_BLUR_RELATIVE, baseBlur = softClamp(blurAmount + mouseInfluence * blurAmount * MOUSE_INFLUENCE_BLUR_FACTOR, maxBlur), edgeBlur = softClamp(baseBlur * (.8 + mouseInfluence * EDGE_INTENSITY_MOUSE_FACTOR * .4), maxBlur), centerBlur = softClamp(baseBlur * (.3 + mouseInfluence * CENTER_INTENSITY_MOUSE_FACTOR * .3), maxBlur), flowBlur = softClamp(baseBlur * FLOW_BLUR_MULTIPLIER, maxBlur);
965
1036
  liquidBlur = {
966
1037
  baseBlur: clampBlur(baseBlur),
967
1038
  edgeBlur: clampBlur(edgeBlur),
@@ -970,9 +1041,10 @@ class {
970
1041
  };
971
1042
  }
972
1043
  // Backdrop filter
973
- let backdropFilterString = `blur(${blurAmount}px) saturate(${saturation}%) contrast(1.05) brightness(1.05)`;
974
- const dynamicSaturation = saturation + 20 * (liquidBlur.baseBlur || 0), area = rect ? rect.width * rect.height : 0;
975
- backdropFilterString = !withLiquidBlur || effectiveReducedMotion || effectiveWithoutEffects || area > 18e4 ? `blur(${clampBlur(Math.max(liquidBlur.baseBlur, .8 * liquidBlur.edgeBlur, 1.1 * liquidBlur.centerBlur, .9 * liquidBlur.flowBlur))}px) saturate(${Math.min(dynamicSaturation, 200)}%) contrast(${overLightConfig.contrast}) brightness(${overLightConfig.brightness})` : `blur(${clampBlur(.4 * liquidBlur.baseBlur + .25 * liquidBlur.edgeBlur + .15 * liquidBlur.centerBlur + .2 * liquidBlur.flowBlur)}px) saturate(${Math.min(dynamicSaturation, 200)}%) contrast(${overLightConfig.contrast}) brightness(${overLightConfig.brightness})`;
1044
+ const dynamicSaturation = saturation + 40 * tensionFactor + 15 * (liquidBlur.baseBlur || 0);
1045
+ let backdropFilterString = "";
1046
+ const area = rect ? rect.width * rect.height : 0;
1047
+ backdropFilterString = !withLiquidBlur || effectiveReducedMotion || effectiveWithoutEffects || area > 18e4 ? `blur(${clampBlur(Math.max(liquidBlur.baseBlur, .8 * liquidBlur.edgeBlur, 1.1 * liquidBlur.centerBlur, .9 * liquidBlur.flowBlur))}px) saturate(${Math.min(dynamicSaturation, 250)}%) contrast(${lightingContrast}) brightness(${lightingBrightness})` : `blur(${clampBlur(.4 * liquidBlur.baseBlur + .25 * liquidBlur.edgeBlur + .15 * liquidBlur.centerBlur + .2 * liquidBlur.flowBlur)}px) saturate(${Math.min(dynamicSaturation, 250)}%) contrast(${lightingContrast}) brightness(${lightingBrightness})`;
976
1048
  // Container variables
977
1049
  const style = containerElement.style;
978
1050
  style.setProperty("--atomix-glass-container-padding", padding), style.setProperty("--atomix-glass-container-radius", `${effectiveBorderRadius}px`),
@@ -1151,7 +1223,24 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
1151
1223
  }), targetGlobalMousePositionRef = useRef({
1152
1224
  x: 0,
1153
1225
  y: 0
1154
- }), lerpRafRef = useRef(null), lerpActiveRef = useRef(!1), [dynamicBorderRadius, setDynamicCornerRadius] = useState(CONSTANTS.DEFAULT_CORNER_RADIUS), [userPrefersReducedMotion, setUserPrefersReducedMotion] = useState(!1), [userPrefersHighContrast, setUserPrefersHighContrast] = useState(!1), [detectedOverLight, setDetectedOverLight] = useState(!1), animationFrameIdRef = useRef(null), animationStartTimeRef = useRef(0), elapsedTimeRef = useRef(0), shaderTimeRef = useRef(0), fbmConfig = useMemo((() => {
1226
+ }), lerpRafRef = useRef(null), lerpActiveRef = useRef(!1), [dynamicBorderRadius, setDynamicCornerRadius] = useState(CONSTANTS.DEFAULT_CORNER_RADIUS), elasticTranslationRef = useRef({
1227
+ x: 0,
1228
+ y: 0
1229
+ }), elasticVelocityRef = useRef({
1230
+ x: 0,
1231
+ y: 0
1232
+ }), directionalScaleRef = useRef({
1233
+ x: 1,
1234
+ y: 1
1235
+ }), scaleVelocityRef = useRef({
1236
+ x: 0,
1237
+ y: 0
1238
+ });
1239
+ useRef(0);
1240
+ const mouseVelocityRef = useRef({
1241
+ x: 0,
1242
+ y: 0
1243
+ }), [userPrefersReducedMotion, setUserPrefersReducedMotion] = useState(!1), [userPrefersHighContrast, setUserPrefersHighContrast] = useState(!1), [detectedOverLight, setDetectedOverLight] = useState(!1), animationFrameIdRef = useRef(null), animationStartTimeRef = useRef(0), elapsedTimeRef = useRef(0), shaderTimeRef = useRef(0), fbmConfig = useMemo((() => {
1155
1244
  // If quality preset is provided, use it as base
1156
1245
  const preset = (quality = distortionQuality, ATOMIX_GLASS.CONSTANTS.DISTORTION_QUALITY_PRESETS[quality]);
1157
1246
  // Override with custom values if provided
@@ -1436,57 +1525,85 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
1436
1525
  return "undefined" == typeof process || process.env, finalConfig;
1437
1526
  }
1438
1527
  return "undefined" == typeof process || process.env, baseConfig;
1439
- }), [ overLight, getEffectiveOverLight, isHovered, isActive, validateConfigValue, debugOverLight ]), transformStyle = useMemo((() => effectiveWithoutEffects || isActive && Boolean(onClick) ? "scale(0.98)" : "scale(1)"), [ effectiveWithoutEffects, isActive, onClick ]), updateRectRef = useRef(null), stopLerpLoop = useCallback((() => {
1528
+ }), [ overLight, getEffectiveOverLight, isHovered, isActive, validateConfigValue, debugOverLight ]), transformStyle = useMemo((() => effectiveWithoutEffects || isActive && Boolean(onClick) ? "scale(0.99)" : "scale(1)"), [ effectiveWithoutEffects, isActive, onClick ]), updateRectRef = useRef(null), stopLerpLoop = useCallback((() => {
1440
1529
  lerpActiveRef.current = !1, null !== lerpRafRef.current && (cancelAnimationFrame(lerpRafRef.current),
1441
1530
  lerpRafRef.current = null);
1442
1531
  }), []), startLerpLoop = useCallback((() => {
1443
1532
  if (lerpActiveRef.current) return;
1444
- lerpActiveRef.current = !0;
1445
- const LERP_T = CONSTANTS.LERP_FACTOR, tick = () => {
1533
+ lerpActiveRef.current = !0, CONSTANTS.LERP_FACTOR;
1534
+ // 0.08 lower = more viscous
1535
+ const tick = () => {
1446
1536
  if (!lerpActiveRef.current) return;
1447
1537
  if (!glassRef.current) return void (lerpActiveRef.current = !1);
1448
- const cur = internalMouseOffsetRef.current, tgt = targetMouseOffsetRef.current, dx = tgt.x - cur.x, dy = tgt.y - cur.y;
1449
- // If we're close enough, snap and park
1450
- if (Math.abs(dx) < .01 && Math.abs(dy) < .01) return internalMouseOffsetRef.current = {
1451
- ...tgt
1452
- }, internalGlobalMousePositionRef.current = {
1453
- ...targetGlobalMousePositionRef.current
1454
- },
1455
- // Final update and stop
1456
- updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
1457
- mouseOffset: internalMouseOffsetRef.current,
1458
- globalMousePosition: internalGlobalMousePositionRef.current,
1459
- glassSize: glassSize,
1460
- isHovered: isHovered,
1461
- isActive: isActive,
1462
- isOverLight: overLightConfig.isOverLight,
1463
- baseOverLightConfig: overLightConfig,
1464
- effectiveBorderRadius: effectiveBorderRadius,
1465
- effectiveWithoutEffects: effectiveWithoutEffects,
1466
- effectiveReducedMotion: effectiveReducedMotion,
1467
- elasticity: elasticity,
1468
- directionalScale: isActive && Boolean(onClick) ? "scale(0.96)" : "scale(1)",
1469
- onClick: onClick,
1470
- withLiquidBlur: withLiquidBlur,
1471
- blurAmount: blurAmount,
1472
- saturation: saturation,
1473
- padding: padding,
1474
- isFixedOrSticky: isFixedOrSticky
1475
- }), void stopLerpLoop();
1476
- // Smooth step
1477
- internalMouseOffsetRef.current = {
1478
- x: lerp$1(cur.x, tgt.x, LERP_T),
1479
- y: lerp$1(cur.y, tgt.y, LERP_T)
1538
+ const cur = internalMouseOffsetRef.current, tgt = targetMouseOffsetRef.current, springX = calculateSpring(cur.x, tgt.x, mouseVelocityRef.current.x, CONSTANTS.LERP_FACTOR, CONSTANTS.ELASTICITY_DAMPING), springY = calculateSpring(cur.y, tgt.y, mouseVelocityRef.current.y, CONSTANTS.LERP_FACTOR, CONSTANTS.ELASTICITY_DAMPING);
1539
+ internalMouseOffsetRef.current = {
1540
+ x: springX.value,
1541
+ y: springY.value
1542
+ }, mouseVelocityRef.current = {
1543
+ x: springX.velocity,
1544
+ y: springY.velocity
1480
1545
  };
1481
1546
  const curG = internalGlobalMousePositionRef.current, tgtG = targetGlobalMousePositionRef.current;
1482
1547
  internalGlobalMousePositionRef.current = {
1483
- x: lerp$1(curG.x, tgtG.x, LERP_T),
1484
- y: lerp$1(curG.y, tgtG.y, LERP_T)
1548
+ x: lerp$1(curG.x, tgtG.x, CONSTANTS.LERP_FACTOR),
1549
+ y: lerp$1(curG.y, tgtG.y, CONSTANTS.LERP_FACTOR)
1550
+ };
1551
+ // ── Calculate Elastic Physics ─────────────────────────────────────
1552
+ let targetElasticTranslation = {
1553
+ x: 0,
1554
+ y: 0
1555
+ }, targetScale = {
1556
+ x: 1,
1557
+ y: 1
1558
+ };
1559
+ if (!effectiveWithoutEffects && glassRef.current) {
1560
+ const rect = cachedRectRef.current || glassRef.current.getBoundingClientRect(), center = calculateElementCenter(rect), globalPos = internalGlobalMousePositionRef.current;
1561
+ if (globalPos.x && globalPos.y) {
1562
+ const deltaX = globalPos.x - center.x, deltaY = globalPos.y - center.y, edgeDistanceX = Math.max(0, Math.abs(deltaX) - rect.width / 2), edgeDistanceY = Math.max(0, Math.abs(deltaY) - rect.height / 2), edgeDistance = Math.sqrt(edgeDistanceX * edgeDistanceX + edgeDistanceY * edgeDistanceY), activationZone = CONSTANTS.ACTIVATION_ZONE, rawT = edgeDistance > activationZone ? 0 : 1 - edgeDistance / activationZone, fadeInFactor = smoothstep(rawT);
1563
+ // Scale stretch logic (liquid surface tension)
1564
+ if (targetElasticTranslation = {
1565
+ x: deltaX * elasticity * CONSTANTS.ELASTICITY_TRANSLATION_FACTOR * fadeInFactor,
1566
+ y: deltaY * elasticity * CONSTANTS.ELASTICITY_TRANSLATION_FACTOR * fadeInFactor
1567
+ }, edgeDistance <= activationZone) {
1568
+ const centerDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
1569
+ if (centerDistance > 0) {
1570
+ const nx = deltaX / centerDistance, ny = deltaY / centerDistance, stretchIntensity = Math.min(centerDistance / 350, 1) * elasticity * rawT, mag = 1 + .06 * stretchIntensity;
1571
+ targetScale = {
1572
+ x: mag + Math.abs(nx) * stretchIntensity * CONSTANTS.ELASTICITY_STRETCH_RATIO,
1573
+ y: mag + Math.abs(ny) * stretchIntensity * CONSTANTS.ELASTICITY_STRETCH_RATIO
1574
+ },
1575
+ // Maintain liquid volume by compressing the perpendicular axis
1576
+ targetScale.x -= Math.abs(ny) * stretchIntensity * .15, targetScale.y -= Math.abs(nx) * stretchIntensity * .15;
1577
+ }
1578
+ }
1579
+ }
1580
+ }
1581
+ // Integrate Elastic Translation Spring
1582
+ const springTX = calculateSpring(elasticTranslationRef.current.x, targetElasticTranslation.x, elasticVelocityRef.current.x, CONSTANTS.ELASTICITY_STIFFNESS, CONSTANTS.ELASTICITY_DAMPING), springTY = calculateSpring(elasticTranslationRef.current.y, targetElasticTranslation.y, elasticVelocityRef.current.y, CONSTANTS.ELASTICITY_STIFFNESS, CONSTANTS.ELASTICITY_DAMPING);
1583
+ elasticTranslationRef.current = {
1584
+ x: springTX.value,
1585
+ y: springTY.value
1586
+ }, elasticVelocityRef.current = {
1587
+ x: springTX.velocity,
1588
+ y: springTY.velocity
1589
+ };
1590
+ // Integrate Scale Spring
1591
+ const springSX = calculateSpring(directionalScaleRef.current.x, targetScale.x, scaleVelocityRef.current.x, CONSTANTS.ELASTICITY_STIFFNESS, CONSTANTS.ELASTICITY_DAMPING), springSY = calculateSpring(directionalScaleRef.current.y, targetScale.y, scaleVelocityRef.current.y, CONSTANTS.ELASTICITY_STIFFNESS, CONSTANTS.ELASTICITY_DAMPING);
1592
+ directionalScaleRef.current = {
1593
+ x: springSX.value,
1594
+ y: springSY.value
1595
+ }, scaleVelocityRef.current = {
1596
+ x: springSX.velocity,
1597
+ y: springSY.velocity
1485
1598
  },
1486
1599
  // Imperative style update
1487
1600
  updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
1488
1601
  mouseOffset: internalMouseOffsetRef.current,
1489
1602
  globalMousePosition: internalGlobalMousePositionRef.current,
1603
+ elasticTranslation: elasticTranslationRef.current,
1604
+ elasticVelocity: elasticVelocityRef.current,
1605
+ mouseVelocity: mouseVelocityRef.current,
1606
+ directionalScale: directionalScaleRef.current,
1490
1607
  glassSize: glassSize,
1491
1608
  isHovered: isHovered,
1492
1609
  isActive: isActive,
@@ -1496,17 +1613,16 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
1496
1613
  effectiveWithoutEffects: effectiveWithoutEffects,
1497
1614
  effectiveReducedMotion: effectiveReducedMotion,
1498
1615
  elasticity: elasticity,
1499
- directionalScale: isActive && Boolean(onClick) ? "scale(0.96)" : "scale(1)",
1616
+ scaleBase: isActive && Boolean(onClick) ? .99 : 1,
1500
1617
  onClick: onClick,
1501
1618
  withLiquidBlur: withLiquidBlur,
1502
1619
  blurAmount: blurAmount,
1503
1620
  saturation: saturation,
1504
1621
  padding: padding,
1505
1622
  isFixedOrSticky: isFixedOrSticky
1506
- }), lerpRafRef.current = requestAnimationFrame(tick);
1623
+ }), Math.abs(mouseVelocityRef.current.x) < .001 && Math.abs(mouseVelocityRef.current.y) < .001 && Math.abs(elasticVelocityRef.current.x) < .001 && Math.abs(elasticVelocityRef.current.y) < .001 && Math.abs(scaleVelocityRef.current.x) < .001 && Math.abs(scaleVelocityRef.current.y) < .001 && Math.abs(internalMouseOffsetRef.current.x - targetMouseOffsetRef.current.x) < .001 && Math.abs(internalMouseOffsetRef.current.y - targetMouseOffsetRef.current.y) < .001 ? stopLerpLoop() : lerpRafRef.current = requestAnimationFrame(tick);
1507
1624
  };
1508
- // 0.08 – lower = more viscous
1509
- lerpRafRef.current = requestAnimationFrame(tick);
1625
+ lerpRafRef.current = requestAnimationFrame(tick);
1510
1626
  }), [ glassRef, wrapperRef, glassSize, isHovered, isActive, overLightConfig, effectiveBorderRadius, effectiveWithoutEffects, effectiveReducedMotion, elasticity, onClick, withLiquidBlur, blurAmount, saturation, padding, isFixedOrSticky, stopLerpLoop ]), handleGlobalMousePosition = useCallback((globalPos => {
1511
1627
  if (externalGlobalMousePosition && externalMouseOffset) return;
1512
1628
  if (effectiveWithoutEffects) return;
@@ -1553,6 +1669,11 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
1553
1669
  updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
1554
1670
  mouseOffset: externalMouseOffset || internalMouseOffsetRef.current,
1555
1671
  globalMousePosition: externalGlobalMousePosition || internalGlobalMousePositionRef.current,
1672
+ elasticTranslation: elasticTranslationRef.current,
1673
+ elasticVelocity: elasticVelocityRef.current,
1674
+ mouseVelocity: mouseVelocityRef.current,
1675
+ directionalScale: directionalScaleRef.current,
1676
+ scaleBase: isActive && Boolean(onClick) ? .96 : 1,
1556
1677
  glassSize: glassSize,
1557
1678
  isHovered: isHovered,
1558
1679
  isActive: isActive,
@@ -1562,7 +1683,6 @@ withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION, animationSpeed: a
1562
1683
  effectiveWithoutEffects: effectiveWithoutEffects,
1563
1684
  effectiveReducedMotion: effectiveReducedMotion,
1564
1685
  elasticity: elasticity,
1565
- directionalScale: isActive && Boolean(onClick) ? "scale(0.96)" : "scale(1)",
1566
1686
  onClick: onClick,
1567
1687
  withLiquidBlur: withLiquidBlur,
1568
1688
  blurAmount: blurAmount,
@@ -2830,7 +2950,7 @@ const PERFORMANCE_PRESET = {
2830
2950
  "aria-label": ariaLabel,
2831
2951
  "aria-describedby": ariaDescribedBy,
2832
2952
  "aria-disabled": !(!onClick || !effectiveWithoutEffects) || !onClick && void 0,
2833
- "aria-pressed": void 0,
2953
+ "aria-pressed": onClick ? isActive : void 0,
2834
2954
  onKeyDown: onClick ? handleKeyDown : void 0,
2835
2955
  children: [ jsx(AtomixGlassContainer, {
2836
2956
  ref: glassRef,
@@ -3300,26 +3420,25 @@ const Radio = memo((({label: label, checked: checked = !1, onChange: onChange,
3300
3420
 
3301
3421
  Radio.displayName = "Radio";
3302
3422
 
3303
- const SelectContext = createContext(null), SelectOption = memo((({value: value, children: children, disabled: disabled = !1, className: className = "", style: style}) => {
3304
- const context = useContext(SelectContext), label = "string" == typeof children ? children : value;
3305
- // We assume children is the label if it's a string, or we need a way to get label.
3306
- // For simplicity, we use children as label for registration if it's a string.
3307
- if (useEffect((() => {
3423
+ const SelectContext = createContext(null), SelectOption = memo((({value: value, label: label, children: children, disabled: disabled = !1, className: className = "", style: style}) => {
3424
+ const context = useContext(SelectContext), displayLabel = label || ("string" == typeof children ? children : value);
3425
+ if (useEffect((() => {
3308
3426
  if (context) return context.registerOption({
3309
3427
  value: value,
3310
- label: label,
3428
+ label: displayLabel,
3311
3429
  disabled: disabled
3312
3430
  }), () => {
3313
3431
  context.unregisterOption(value);
3314
3432
  };
3315
- }), [ context, value, label, disabled ]), !context) return console.warn("SelectOption must be used within a Select component"),
3433
+ }), [ context, value, displayLabel, disabled ]), !context) return console.warn("SelectOption must be used within a Select component"),
3316
3434
  null;
3317
- const {selectedValue: selectedValue, onSelect: onSelect} = context, isSelected = Array.isArray(selectedValue) ? _includesInstanceProperty(selectedValue).call(selectedValue, value) : selectedValue === value;
3435
+ const {selectedValue: selectedValue, onSelect: onSelect, focusedValue: focusedValue, id: id} = context, isSelected = Array.isArray(selectedValue) ? _includesInstanceProperty(selectedValue).call(selectedValue, value) : selectedValue === value;
3318
3436
  return jsx("li", {
3319
- className: `${SELECT_CLASSES_SELECT_ITEM} ${className}`.trim(),
3437
+ id: id ? `${id}-opt-${value}` : void 0,
3438
+ className: `${SELECT_CLASSES_SELECT_ITEM} ${focusedValue === value ? "is-focused" : ""} ${isSelected ? "is-selected" : ""} ${className}`.trim(),
3320
3439
  "data-value": value,
3321
3440
  onClick: e => {
3322
- e.preventDefault(), e.stopPropagation(), disabled || onSelect(value, label);
3441
+ e.preventDefault(), e.stopPropagation(), disabled || onSelect(value, displayLabel);
3323
3442
  },
3324
3443
  style: style,
3325
3444
  role: "option",
@@ -3339,7 +3458,7 @@ const SelectContext = createContext(null), SelectOption = memo((({value: value
3339
3458
  tabIndex: -1
3340
3459
  }), jsx("div", {
3341
3460
  className: "c-select__item-label",
3342
- children: children
3461
+ children: children || displayLabel
3343
3462
  }) ]
3344
3463
  })
3345
3464
  });
@@ -3395,18 +3514,18 @@ const SelectComponentBase = ({options: options, value: value, onChange: onChange
3395
3514
  disabled: disabled,
3396
3515
  invalid: invalid,
3397
3516
  valid: valid
3398
- }), [isOpen, setIsOpen] = useState(!1), [selectedLabel, setSelectedLabel] = useState(placeholder), dropdownRef = useRef(null), panelRef = useRef(null), bodyRef = useRef(null), nativeSelectRef = useRef(null), [registeredOptions, setRegisteredOptions] = useState([]), registerOption = useCallback((option => {
3517
+ }), [isOpen, setIsOpen] = useState(!1), [focusedIndex, setFocusedIndex] = useState(-1), dropdownRef = useRef(null), panelRef = useRef(null), bodyRef = useRef(null), nativeSelectRef = useRef(null), [registeredOptions, setRegisteredOptions] = useState([]), registerOption = useCallback((option => {
3399
3518
  setRegisteredOptions((prev => prev.some((o => o.value === option.value)) ? prev : [ ...prev, option ]));
3400
3519
  }), []), unregisterOption = useCallback((value => {
3401
3520
  setRegisteredOptions((prev => prev.filter((o => o.value !== value))));
3402
- }), []), hasOptionsProp = options && options.length > 0, activeOptions = hasOptionsProp ? options : registeredOptions;
3403
- // Update selected label when value changes
3404
- useEffect((() => {
3405
- if (value) {
3521
+ }), []), hasOptionsProp = options && options.length > 0, activeOptions = hasOptionsProp ? options : registeredOptions, selectedLabel = useMemo((() => {
3522
+ if (multiple && Array.isArray(value)) return 0 === value.length ? placeholder : activeOptions.filter((opt => _includesInstanceProperty(value).call(value, opt.value))).map((opt => opt.label)).join(", ");
3523
+ if (value && "string" == typeof value) {
3406
3524
  const selectedOption = activeOptions.find((opt => opt.value === value));
3407
- selectedOption && setSelectedLabel(selectedOption.label);
3408
- } else setSelectedLabel(placeholder);
3409
- }), [ value, activeOptions, placeholder ]),
3525
+ return selectedOption ? selectedOption.label : placeholder;
3526
+ }
3527
+ return placeholder;
3528
+ }), [ value, activeOptions, placeholder, multiple ]);
3410
3529
  // Handle click outside to close dropdown
3411
3530
  useEffect((() => {
3412
3531
  const handleClickOutside = event => {
@@ -3419,31 +3538,54 @@ const SelectComponentBase = ({options: options, value: value, onChange: onChange
3419
3538
  }), []);
3420
3539
  // Toggle dropdown
3421
3540
  const handleToggle = () => {
3422
- disabled || (!isOpen && bodyRef.current && panelRef.current ? bodyRef.current.style.height = `${panelRef.current.clientHeight}px` : bodyRef.current && (bodyRef.current.style.height = "0px"),
3423
- setIsOpen(!isOpen));
3424
- }, handleItemClick = useCallback((option => {
3425
- if (setSelectedLabel(option.label), setIsOpen(!1), bodyRef.current && (bodyRef.current.style.height = "0px"),
3426
- nativeSelectRef.current && (nativeSelectRef.current.value = option.value), onChange) {
3427
- // Create a synthetic event
3428
- const event = {
3429
- target: {
3430
- name: name,
3431
- value: option.value
3432
- }
3433
- };
3434
- onChange(event);
3541
+ if (!disabled) {
3542
+ const nextOpen = !isOpen;
3543
+ if (nextOpen && bodyRef.current && panelRef.current)
3544
+ // Set focused index to current selection or first item
3545
+ if (bodyRef.current.style.height = `${panelRef.current.clientHeight}px`, value && !multiple && "string" == typeof value) {
3546
+ const index = activeOptions.findIndex((opt => opt.value === value));
3547
+ setFocusedIndex(index >= 0 ? index : 0);
3548
+ } else if (multiple && Array.isArray(value) && value.length > 0) {
3549
+ const index = activeOptions.findIndex((opt => _includesInstanceProperty(value).call(value, opt.value)));
3550
+ setFocusedIndex(index >= 0 ? index : 0);
3551
+ } else setFocusedIndex(0); else bodyRef.current && (bodyRef.current.style.height = "0px",
3552
+ setFocusedIndex(-1));
3553
+ setIsOpen(nextOpen);
3435
3554
  }
3436
- }), [ onChange, name ]), onSelect = useCallback(((val, label) => {
3555
+ }, handleItemClick = useCallback((option => {
3556
+ let newValue;
3557
+ if (multiple) {
3558
+ const currentValues = Array.isArray(value) ? value : value ? [ value ] : [];
3559
+ newValue = _includesInstanceProperty(currentValues).call(currentValues, option.value) ? currentValues.filter((v => v !== option.value)) : [ ...currentValues, option.value ];
3560
+ } else newValue = option.value, setIsOpen(!1), bodyRef.current && (bodyRef.current.style.height = "0px");
3561
+ onChange && (
3562
+ // Sync native select before firing onChange
3563
+ nativeSelectRef.current && (multiple && Array.isArray(newValue) ? Array.from(nativeSelectRef.current.options).forEach((opt => {
3564
+ opt.selected = _includesInstanceProperty(newValue).call(newValue, opt.value);
3565
+ })) : "string" == typeof newValue && (nativeSelectRef.current.value = newValue)),
3566
+ onChange({
3567
+ target: {
3568
+ name: name,
3569
+ value: newValue
3570
+ }
3571
+ }));
3572
+ }), [ onChange, name, multiple, value ]), onSelect = useCallback(((val, label) => {
3437
3573
  handleItemClick({
3438
3574
  value: val,
3439
3575
  label: label
3440
3576
  });
3441
- }), [ handleItemClick ]), contextValue = React.useMemo((() => ({
3577
+ }), [ handleItemClick ]), focusedValue = useMemo((() => {
3578
+ if (focusedIndex >= 0 && focusedIndex < activeOptions.length) return activeOptions[focusedIndex]?.value;
3579
+ }), [ focusedIndex, activeOptions ]), focusedOptionId = useMemo((() => {
3580
+ if (isOpen && focusedValue) return `${id || "select"}-opt-${focusedValue}`;
3581
+ }), [ isOpen, focusedValue, id ]), contextValue = React.useMemo((() => ({
3442
3582
  registerOption: registerOption,
3443
3583
  unregisterOption: unregisterOption,
3444
3584
  selectedValue: value,
3445
- onSelect: onSelect
3446
- })), [ registerOption, unregisterOption, value, onSelect ]), selectContent = jsx(SelectContext.Provider, {
3585
+ onSelect: onSelect,
3586
+ focusedValue: focusedValue,
3587
+ id: id || "select"
3588
+ })), [ registerOption, unregisterOption, value, onSelect, focusedValue, id ]), selectContent = jsx(SelectContext.Provider, {
3447
3589
  value: contextValue,
3448
3590
  children: jsxs("div", {
3449
3591
  className: `${selectClass} ${isOpen ? SELECT_CLASSES_IS_OPEN : ""}`,
@@ -3491,7 +3633,12 @@ const SelectComponentBase = ({options: options, value: value, onChange: onChange
3491
3633
  if (!disabled) switch (event.key) {
3492
3634
  case "Enter":
3493
3635
  case " ":
3494
- event.preventDefault(), handleToggle();
3636
+ if (event.preventDefault(), isOpen) {
3637
+ if (focusedIndex >= 0 && focusedIndex < activeOptions.length) {
3638
+ const option = activeOptions[focusedIndex];
3639
+ option && !option.disabled && handleItemClick(option);
3640
+ }
3641
+ } else handleToggle();
3495
3642
  break;
3496
3643
 
3497
3644
  case "Escape":
@@ -3499,8 +3646,23 @@ const SelectComponentBase = ({options: options, value: value, onChange: onChange
3499
3646
  break;
3500
3647
 
3501
3648
  case "ArrowDown":
3649
+ event.preventDefault(), isOpen ? setFocusedIndex((prev => prev < activeOptions.length - 1 ? prev + 1 : prev)) : handleToggle();
3650
+ break;
3651
+
3502
3652
  case "ArrowUp":
3503
- isOpen || (event.preventDefault(), handleToggle());
3653
+ event.preventDefault(), isOpen ? setFocusedIndex((prev => prev > 0 ? prev - 1 : 0)) : handleToggle();
3654
+ break;
3655
+
3656
+ case "Home":
3657
+ isOpen && (event.preventDefault(), setFocusedIndex(0));
3658
+ break;
3659
+
3660
+ case "End":
3661
+ isOpen && (event.preventDefault(), setFocusedIndex(activeOptions.length - 1));
3662
+ break;
3663
+
3664
+ case "Tab":
3665
+ isOpen && (setIsOpen(!1), bodyRef.current && (bodyRef.current.style.height = "0px"));
3504
3666
  }
3505
3667
  },
3506
3668
  "aria-disabled": disabled,
@@ -3509,7 +3671,11 @@ const SelectComponentBase = ({options: options, value: value, onChange: onChange
3509
3671
  "aria-haspopup": "listbox",
3510
3672
  "aria-expanded": isOpen,
3511
3673
  "aria-controls": id ? `${id}-listbox` : void 0,
3512
- children: selectedLabel
3674
+ "aria-activedescendant": focusedOptionId,
3675
+ children: jsx("div", {
3676
+ className: "c-select__selected-text",
3677
+ children: selectedLabel
3678
+ })
3513
3679
  }), jsx("i", {
3514
3680
  className: `${SELECT_CLASSES_ICON_CARET} ${SELECT_CLASSES_TOGGLE_ICON}`
3515
3681
  }), jsx("div", {
@@ -3527,11 +3693,13 @@ const SelectComponentBase = ({options: options, value: value, onChange: onChange
3527
3693
  id: id ? `${id}-listbox` : void 0,
3528
3694
  "aria-labelledby": id,
3529
3695
  children: hasOptionsProp ? options.map(((option, index) => jsx("li", {
3530
- className: SELECT_CLASSES_SELECT_ITEM,
3696
+ id: `${id || "select"}-opt-${option.value}`,
3697
+ className: `${SELECT_CLASSES_SELECT_ITEM} ${focusedIndex === index ? "is-focused" : ""} ${multiple && Array.isArray(value) && _includesInstanceProperty(value).call(value, option.value) || value === option.value ? "is-selected" : ""}`,
3531
3698
  "data-value": option.value,
3532
3699
  onClick: () => !option.disabled && handleItemClick(option),
3700
+ onMouseEnter: () => setFocusedIndex(index),
3533
3701
  role: "option",
3534
- "aria-selected": value === option.value,
3702
+ "aria-selected": multiple && Array.isArray(value) && _includesInstanceProperty(value).call(value, option.value) || value === option.value,
3535
3703
  "aria-disabled": option.disabled,
3536
3704
  children: jsxs("label", {
3537
3705
  htmlFor: `SelectItem${index}`,
@@ -3540,7 +3708,7 @@ const SelectComponentBase = ({options: options, value: value, onChange: onChange
3540
3708
  type: "checkbox",
3541
3709
  id: `SelectItem${index}`,
3542
3710
  className: "c-checkbox__input c-select__item-input",
3543
- checked: value === option.value,
3711
+ checked: multiple && Array.isArray(value) && _includesInstanceProperty(value).call(value, option.value) || value === option.value,
3544
3712
  readOnly: !0,
3545
3713
  disabled: option.disabled
3546
3714
  }), jsx("div", {
@@ -3559,10 +3727,7 @@ const SelectComponentBase = ({options: options, value: value, onChange: onChange
3559
3727
  // Default glass settings for select components
3560
3728
  const defaultGlassProps = {
3561
3729
  displacementScale: 60,
3562
- blurAmount: 1,
3563
- saturation: 180,
3564
- aberrationIntensity: .2,
3565
- borderRadius: 12,
3730
+ blurAmount: 10,
3566
3731
  mode: "shader"
3567
3732
  }, glassProps = !0 === glass ? defaultGlassProps : {
3568
3733
  ...defaultGlassProps,