@hua-labs/motion-core 2.1.0-alpha.6 → 2.1.0-alpha.8

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 (174) hide show
  1. package/README.md +16 -10
  2. package/dist/index.d.mts +1091 -0
  3. package/dist/index.d.ts +1091 -35
  4. package/dist/index.js +4241 -65
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +4187 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +7 -4
  9. package/dist/.tsbuildinfo +0 -1
  10. package/dist/core/MotionEngine.d.ts +0 -111
  11. package/dist/core/MotionEngine.d.ts.map +0 -1
  12. package/dist/core/MotionEngine.js +0 -206
  13. package/dist/core/MotionEngine.js.map +0 -1
  14. package/dist/core/PerformanceOptimizer.d.ts +0 -124
  15. package/dist/core/PerformanceOptimizer.d.ts.map +0 -1
  16. package/dist/core/PerformanceOptimizer.js +0 -334
  17. package/dist/core/PerformanceOptimizer.js.map +0 -1
  18. package/dist/core/TransitionEffects.d.ts +0 -76
  19. package/dist/core/TransitionEffects.d.ts.map +0 -1
  20. package/dist/core/TransitionEffects.js +0 -321
  21. package/dist/core/TransitionEffects.js.map +0 -1
  22. package/dist/hooks/useBounceIn.d.ts +0 -12
  23. package/dist/hooks/useBounceIn.d.ts.map +0 -1
  24. package/dist/hooks/useBounceIn.js +0 -75
  25. package/dist/hooks/useBounceIn.js.map +0 -1
  26. package/dist/hooks/useButtonEffect.d.ts +0 -48
  27. package/dist/hooks/useButtonEffect.d.ts.map +0 -1
  28. package/dist/hooks/useButtonEffect.js +0 -311
  29. package/dist/hooks/useButtonEffect.js.map +0 -1
  30. package/dist/hooks/useCardList.d.ts +0 -23
  31. package/dist/hooks/useCardList.d.ts.map +0 -1
  32. package/dist/hooks/useCardList.js +0 -119
  33. package/dist/hooks/useCardList.js.map +0 -1
  34. package/dist/hooks/useClickToggle.d.ts +0 -30
  35. package/dist/hooks/useClickToggle.d.ts.map +0 -1
  36. package/dist/hooks/useClickToggle.js +0 -137
  37. package/dist/hooks/useClickToggle.js.map +0 -1
  38. package/dist/hooks/useFadeIn.d.ts +0 -3
  39. package/dist/hooks/useFadeIn.d.ts.map +0 -1
  40. package/dist/hooks/useFadeIn.js +0 -107
  41. package/dist/hooks/useFadeIn.js.map +0 -1
  42. package/dist/hooks/useFocusToggle.d.ts +0 -30
  43. package/dist/hooks/useFocusToggle.d.ts.map +0 -1
  44. package/dist/hooks/useFocusToggle.js +0 -137
  45. package/dist/hooks/useFocusToggle.js.map +0 -1
  46. package/dist/hooks/useGesture.d.ts +0 -45
  47. package/dist/hooks/useGesture.d.ts.map +0 -1
  48. package/dist/hooks/useGesture.js +0 -274
  49. package/dist/hooks/useGesture.js.map +0 -1
  50. package/dist/hooks/useGestureMotion.d.ts +0 -26
  51. package/dist/hooks/useGestureMotion.d.ts.map +0 -1
  52. package/dist/hooks/useGestureMotion.js +0 -167
  53. package/dist/hooks/useGestureMotion.js.map +0 -1
  54. package/dist/hooks/useGradient.d.ts +0 -14
  55. package/dist/hooks/useGradient.d.ts.map +0 -1
  56. package/dist/hooks/useGradient.js +0 -87
  57. package/dist/hooks/useGradient.js.map +0 -1
  58. package/dist/hooks/useHoverMotion.d.ts +0 -5
  59. package/dist/hooks/useHoverMotion.d.ts.map +0 -1
  60. package/dist/hooks/useHoverMotion.js +0 -48
  61. package/dist/hooks/useHoverMotion.js.map +0 -1
  62. package/dist/hooks/useLoadingSpinner.d.ts +0 -30
  63. package/dist/hooks/useLoadingSpinner.d.ts.map +0 -1
  64. package/dist/hooks/useLoadingSpinner.js +0 -283
  65. package/dist/hooks/useLoadingSpinner.js.map +0 -1
  66. package/dist/hooks/useMotion.d.ts +0 -103
  67. package/dist/hooks/useMotion.d.ts.map +0 -1
  68. package/dist/hooks/useMotion.js +0 -266
  69. package/dist/hooks/useMotion.js.map +0 -1
  70. package/dist/hooks/useMotionState.d.ts +0 -29
  71. package/dist/hooks/useMotionState.d.ts.map +0 -1
  72. package/dist/hooks/useMotionState.js +0 -202
  73. package/dist/hooks/useMotionState.js.map +0 -1
  74. package/dist/hooks/useNavigation.d.ts +0 -40
  75. package/dist/hooks/useNavigation.d.ts.map +0 -1
  76. package/dist/hooks/useNavigation.js +0 -212
  77. package/dist/hooks/useNavigation.js.map +0 -1
  78. package/dist/hooks/usePageMotions.d.ts +0 -17
  79. package/dist/hooks/usePageMotions.d.ts.map +0 -1
  80. package/dist/hooks/usePageMotions.js +0 -352
  81. package/dist/hooks/usePageMotions.js.map +0 -1
  82. package/dist/hooks/usePulse.d.ts +0 -10
  83. package/dist/hooks/usePulse.d.ts.map +0 -1
  84. package/dist/hooks/usePulse.js +0 -108
  85. package/dist/hooks/usePulse.js.map +0 -1
  86. package/dist/hooks/useRepeat.d.ts +0 -21
  87. package/dist/hooks/useRepeat.d.ts.map +0 -1
  88. package/dist/hooks/useRepeat.js +0 -65
  89. package/dist/hooks/useRepeat.js.map +0 -1
  90. package/dist/hooks/useScaleIn.d.ts +0 -13
  91. package/dist/hooks/useScaleIn.d.ts.map +0 -1
  92. package/dist/hooks/useScaleIn.js +0 -72
  93. package/dist/hooks/useScaleIn.js.map +0 -1
  94. package/dist/hooks/useScrollProgress.d.ts +0 -11
  95. package/dist/hooks/useScrollProgress.d.ts.map +0 -1
  96. package/dist/hooks/useScrollProgress.js +0 -37
  97. package/dist/hooks/useScrollProgress.js.map +0 -1
  98. package/dist/hooks/useScrollReveal.d.ts +0 -14
  99. package/dist/hooks/useScrollReveal.d.ts.map +0 -1
  100. package/dist/hooks/useScrollReveal.js +0 -116
  101. package/dist/hooks/useScrollReveal.js.map +0 -1
  102. package/dist/hooks/useScrollToggle.d.ts +0 -17
  103. package/dist/hooks/useScrollToggle.d.ts.map +0 -1
  104. package/dist/hooks/useScrollToggle.js +0 -119
  105. package/dist/hooks/useScrollToggle.js.map +0 -1
  106. package/dist/hooks/useSimplePageMotion.d.ts +0 -29
  107. package/dist/hooks/useSimplePageMotion.d.ts.map +0 -1
  108. package/dist/hooks/useSimplePageMotion.js +0 -145
  109. package/dist/hooks/useSimplePageMotion.js.map +0 -1
  110. package/dist/hooks/useSkeleton.d.ts +0 -21
  111. package/dist/hooks/useSkeleton.d.ts.map +0 -1
  112. package/dist/hooks/useSkeleton.js +0 -139
  113. package/dist/hooks/useSkeleton.js.map +0 -1
  114. package/dist/hooks/useSlideDown.d.ts +0 -25
  115. package/dist/hooks/useSlideDown.d.ts.map +0 -1
  116. package/dist/hooks/useSlideDown.js +0 -263
  117. package/dist/hooks/useSlideDown.js.map +0 -1
  118. package/dist/hooks/useSlideLeft.d.ts +0 -13
  119. package/dist/hooks/useSlideLeft.d.ts.map +0 -1
  120. package/dist/hooks/useSlideLeft.js +0 -72
  121. package/dist/hooks/useSlideLeft.js.map +0 -1
  122. package/dist/hooks/useSlideRight.d.ts +0 -13
  123. package/dist/hooks/useSlideRight.d.ts.map +0 -1
  124. package/dist/hooks/useSlideRight.js +0 -72
  125. package/dist/hooks/useSlideRight.js.map +0 -1
  126. package/dist/hooks/useSlideUp.d.ts +0 -3
  127. package/dist/hooks/useSlideUp.d.ts.map +0 -1
  128. package/dist/hooks/useSlideUp.js +0 -122
  129. package/dist/hooks/useSlideUp.js.map +0 -1
  130. package/dist/hooks/useSmartMotion.d.ts +0 -31
  131. package/dist/hooks/useSmartMotion.d.ts.map +0 -1
  132. package/dist/hooks/useSmartMotion.js +0 -257
  133. package/dist/hooks/useSmartMotion.js.map +0 -1
  134. package/dist/hooks/useSpringMotion.d.ts +0 -22
  135. package/dist/hooks/useSpringMotion.d.ts.map +0 -1
  136. package/dist/hooks/useSpringMotion.js +0 -133
  137. package/dist/hooks/useSpringMotion.js.map +0 -1
  138. package/dist/hooks/useStaggerMotion.d.ts +0 -81
  139. package/dist/hooks/useStaggerMotion.d.ts.map +0 -1
  140. package/dist/hooks/useStaggerMotion.js +0 -113
  141. package/dist/hooks/useStaggerMotion.js.map +0 -1
  142. package/dist/hooks/useToggleMotion.d.ts +0 -16
  143. package/dist/hooks/useToggleMotion.d.ts.map +0 -1
  144. package/dist/hooks/useToggleMotion.js +0 -53
  145. package/dist/hooks/useToggleMotion.js.map +0 -1
  146. package/dist/hooks/useUnifiedMotion.d.ts +0 -51
  147. package/dist/hooks/useUnifiedMotion.d.ts.map +0 -1
  148. package/dist/hooks/useUnifiedMotion.js +0 -106
  149. package/dist/hooks/useUnifiedMotion.js.map +0 -1
  150. package/dist/hooks/useVisibilityToggle.d.ts +0 -15
  151. package/dist/hooks/useVisibilityToggle.d.ts.map +0 -1
  152. package/dist/hooks/useVisibilityToggle.js +0 -106
  153. package/dist/hooks/useVisibilityToggle.js.map +0 -1
  154. package/dist/index.d.ts.map +0 -1
  155. package/dist/managers/MotionStateManager.d.ts +0 -63
  156. package/dist/managers/MotionStateManager.d.ts.map +0 -1
  157. package/dist/managers/MotionStateManager.js +0 -159
  158. package/dist/managers/MotionStateManager.js.map +0 -1
  159. package/dist/presets/index.d.ts +0 -16
  160. package/dist/presets/index.d.ts.map +0 -1
  161. package/dist/presets/index.js +0 -120
  162. package/dist/presets/index.js.map +0 -1
  163. package/dist/types/common.d.ts +0 -155
  164. package/dist/types/common.d.ts.map +0 -1
  165. package/dist/types/common.js +0 -5
  166. package/dist/types/common.js.map +0 -1
  167. package/dist/types/index.d.ts +0 -77
  168. package/dist/types/index.d.ts.map +0 -1
  169. package/dist/types/index.js +0 -5
  170. package/dist/types/index.js.map +0 -1
  171. package/dist/utils/easing.d.ts +0 -98
  172. package/dist/utils/easing.d.ts.map +0 -1
  173. package/dist/utils/easing.js +0 -233
  174. package/dist/utils/easing.js.map +0 -1
package/dist/index.mjs ADDED
@@ -0,0 +1,4187 @@
1
+ import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
2
+
3
+ // src/core/MotionEngine.ts
4
+ var MotionEngine = class {
5
+ constructor() {
6
+ this.motions = /* @__PURE__ */ new Map();
7
+ this.isRunning = false;
8
+ this.animationFrameId = null;
9
+ }
10
+ /**
11
+ * 모션 시작
12
+ */
13
+ motion(element, motionFrames, options) {
14
+ return new Promise((resolve) => {
15
+ const motionId = this.generateMotionId();
16
+ this.enableGPUAcceleration(element);
17
+ this.createLayer(element);
18
+ const motion = {
19
+ id: motionId,
20
+ element,
21
+ isRunning: true,
22
+ isPaused: false,
23
+ currentProgress: 0,
24
+ startTime: Date.now() + (options.delay || 0),
25
+ pauseTime: 0,
26
+ options: {
27
+ ...options,
28
+ onComplete: () => {
29
+ options.onComplete?.();
30
+ this.motions.delete(motionId);
31
+ }
32
+ }
33
+ };
34
+ this.motions.set(motionId, motion);
35
+ if (!this.isRunning) {
36
+ this.startAnimationLoop();
37
+ }
38
+ options.onStart?.();
39
+ resolve(motionId);
40
+ });
41
+ }
42
+ /**
43
+ * 모션 중지
44
+ */
45
+ stop(motionId) {
46
+ const motion = this.motions.get(motionId);
47
+ if (motion) {
48
+ motion.isRunning = false;
49
+ motion.options.onCancel?.();
50
+ this.motions.delete(motionId);
51
+ }
52
+ }
53
+ /**
54
+ * 모션 일시정지
55
+ */
56
+ pause(motionId) {
57
+ const motion = this.motions.get(motionId);
58
+ if (motion && motion.isRunning && !motion.isPaused) {
59
+ motion.isPaused = true;
60
+ motion.pauseTime = Date.now();
61
+ }
62
+ }
63
+ /**
64
+ * 모션 재개
65
+ */
66
+ resume(motionId) {
67
+ const motion = this.motions.get(motionId);
68
+ if (motion && motion.isPaused) {
69
+ motion.isPaused = false;
70
+ if (motion.pauseTime > 0) {
71
+ motion.startTime += Date.now() - motion.pauseTime;
72
+ }
73
+ }
74
+ }
75
+ /**
76
+ * 모든 모션 중지
77
+ */
78
+ stopAll() {
79
+ this.motions.forEach((motion) => {
80
+ motion.isRunning = false;
81
+ motion.options.onCancel?.();
82
+ });
83
+ this.motions.clear();
84
+ this.stopAnimationLoop();
85
+ }
86
+ /**
87
+ * 모션 상태 확인
88
+ */
89
+ getMotion(motionId) {
90
+ return this.motions.get(motionId);
91
+ }
92
+ /**
93
+ * 실행 중인 모션 수
94
+ */
95
+ getActiveMotionCount() {
96
+ return this.motions.size;
97
+ }
98
+ /**
99
+ * 애니메이션 루프 시작
100
+ */
101
+ startAnimationLoop() {
102
+ if (this.isRunning) return;
103
+ this.isRunning = true;
104
+ this.animate();
105
+ }
106
+ /**
107
+ * 애니메이션 루프 중지
108
+ */
109
+ stopAnimationLoop() {
110
+ if (this.animationFrameId) {
111
+ cancelAnimationFrame(this.animationFrameId);
112
+ this.animationFrameId = null;
113
+ }
114
+ this.isRunning = false;
115
+ }
116
+ /**
117
+ * 메인 애니메이션 루프
118
+ */
119
+ animate() {
120
+ if (!this.isRunning || this.motions.size === 0) {
121
+ this.stopAnimationLoop();
122
+ return;
123
+ }
124
+ const currentTime = Date.now();
125
+ const completedMotions = [];
126
+ this.motions.forEach((motion) => {
127
+ if (!motion.isRunning || motion.isPaused) return;
128
+ const elapsed = currentTime - motion.startTime;
129
+ if (elapsed < 0) return;
130
+ const progress = Math.min(elapsed / motion.options.duration, 1);
131
+ const easedProgress = motion.options.easing(progress);
132
+ motion.currentProgress = easedProgress;
133
+ this.applyMotionFrame(motion.element, easedProgress);
134
+ motion.options.onUpdate?.(easedProgress);
135
+ if (progress >= 1) {
136
+ completedMotions.push(motion.id);
137
+ motion.isRunning = false;
138
+ motion.options.onComplete?.();
139
+ }
140
+ });
141
+ completedMotions.forEach((id) => this.motions.delete(id));
142
+ this.animationFrameId = requestAnimationFrame(() => this.animate());
143
+ }
144
+ /**
145
+ * 모션 프레임 적용
146
+ */
147
+ applyMotionFrame(element, progress) {
148
+ const transforms = [];
149
+ if (element.style.transform) {
150
+ transforms.push(element.style.transform);
151
+ }
152
+ element.style.transform = transforms.join(" ");
153
+ }
154
+ /**
155
+ * GPU 가속 활성화
156
+ */
157
+ enableGPUAcceleration(element) {
158
+ element.style.willChange = "transform, opacity";
159
+ element.style.transform = "translateZ(0)";
160
+ }
161
+ /**
162
+ * 레이어 분리
163
+ */
164
+ createLayer(element) {
165
+ element.style.transform = "translateZ(0)";
166
+ element.style.backfaceVisibility = "hidden";
167
+ }
168
+ /**
169
+ * 고유 모션 ID 생성
170
+ */
171
+ generateMotionId() {
172
+ return `motion_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
173
+ }
174
+ /**
175
+ * 정리
176
+ */
177
+ destroy() {
178
+ this.stopAll();
179
+ this.stopAnimationLoop();
180
+ }
181
+ };
182
+ var motionEngine = new MotionEngine();
183
+
184
+ // src/core/TransitionEffects.ts
185
+ var TransitionEffects = class _TransitionEffects {
186
+ constructor() {
187
+ this.activeTransitions = /* @__PURE__ */ new Map();
188
+ }
189
+ static getInstance() {
190
+ if (!_TransitionEffects.instance) {
191
+ _TransitionEffects.instance = new _TransitionEffects();
192
+ }
193
+ return _TransitionEffects.instance;
194
+ }
195
+ /**
196
+ * 페이드 인/아웃 전환
197
+ */
198
+ async fade(element, options) {
199
+ const transitionId = this.generateTransitionId();
200
+ return new Promise(async (resolve) => {
201
+ const initialOpacity = parseFloat(getComputedStyle(element).opacity) || 1;
202
+ const targetOpacity = options.direction === "reverse" ? 0 : 1;
203
+ if (options.direction === "reverse") {
204
+ element.style.opacity = initialOpacity.toString();
205
+ } else {
206
+ element.style.opacity = "0";
207
+ }
208
+ this.enableGPUAcceleration(element);
209
+ const motionId = await motionEngine.motion(
210
+ element,
211
+ [
212
+ { progress: 0, properties: { opacity: options.direction === "reverse" ? initialOpacity : 0 } },
213
+ { progress: 1, properties: { opacity: targetOpacity } }
214
+ ],
215
+ {
216
+ duration: options.duration,
217
+ easing: options.easing || this.getDefaultEasing(),
218
+ delay: options.delay,
219
+ onStart: options.onTransitionStart,
220
+ onUpdate: (progress) => {
221
+ const currentOpacity = options.direction === "reverse" ? initialOpacity * (1 - progress) : targetOpacity * progress;
222
+ element.style.opacity = currentOpacity.toString();
223
+ },
224
+ onComplete: () => {
225
+ options.onTransitionComplete?.();
226
+ this.activeTransitions.delete(transitionId);
227
+ resolve();
228
+ }
229
+ }
230
+ );
231
+ this.activeTransitions.set(transitionId, motionId);
232
+ });
233
+ }
234
+ /**
235
+ * 슬라이드 전환
236
+ */
237
+ async slide(element, options) {
238
+ const transitionId = this.generateTransitionId();
239
+ const distance = options.distance || 100;
240
+ return new Promise(async (resolve) => {
241
+ const initialTransform = getComputedStyle(element).transform;
242
+ const isReverse = options.direction === "reverse";
243
+ if (!isReverse) {
244
+ element.style.transform = `translateX(${distance}px)`;
245
+ }
246
+ this.enableGPUAcceleration(element);
247
+ const motionId = await motionEngine.motion(
248
+ element,
249
+ [
250
+ { progress: 0, properties: { translateX: isReverse ? 0 : distance } },
251
+ { progress: 1, properties: { translateX: isReverse ? distance : 0 } }
252
+ ],
253
+ {
254
+ duration: options.duration,
255
+ easing: options.easing || this.getDefaultEasing(),
256
+ delay: options.delay,
257
+ onStart: options.onTransitionStart,
258
+ onUpdate: (progress) => {
259
+ const currentTranslateX = isReverse ? distance * progress : distance * (1 - progress);
260
+ element.style.transform = `translateX(${currentTranslateX}px)`;
261
+ },
262
+ onComplete: () => {
263
+ element.style.transform = initialTransform;
264
+ options.onTransitionComplete?.();
265
+ this.activeTransitions.delete(transitionId);
266
+ resolve();
267
+ }
268
+ }
269
+ );
270
+ this.activeTransitions.set(transitionId, motionId);
271
+ });
272
+ }
273
+ /**
274
+ * 스케일 전환
275
+ */
276
+ async scale(element, options) {
277
+ const transitionId = this.generateTransitionId();
278
+ const scaleValue = options.scale || 0.8;
279
+ return new Promise(async (resolve) => {
280
+ const initialTransform = getComputedStyle(element).transform;
281
+ const isReverse = options.direction === "reverse";
282
+ if (!isReverse) {
283
+ element.style.transform = `scale(${scaleValue})`;
284
+ }
285
+ this.enableGPUAcceleration(element);
286
+ const motionId = await motionEngine.motion(
287
+ element,
288
+ [
289
+ { progress: 0, properties: { scale: isReverse ? 1 : scaleValue } },
290
+ { progress: 1, properties: { scale: isReverse ? scaleValue : 1 } }
291
+ ],
292
+ {
293
+ duration: options.duration,
294
+ easing: options.easing || this.getDefaultEasing(),
295
+ delay: options.delay,
296
+ onStart: options.onTransitionStart,
297
+ onUpdate: (progress) => {
298
+ const currentScale = isReverse ? 1 - (1 - scaleValue) * progress : scaleValue + (1 - scaleValue) * progress;
299
+ element.style.transform = `scale(${currentScale})`;
300
+ },
301
+ onComplete: () => {
302
+ element.style.transform = initialTransform;
303
+ options.onTransitionComplete?.();
304
+ this.activeTransitions.delete(transitionId);
305
+ resolve();
306
+ }
307
+ }
308
+ );
309
+ this.activeTransitions.set(transitionId, motionId);
310
+ });
311
+ }
312
+ /**
313
+ * 플립 전환 (3D 회전)
314
+ */
315
+ async flip(element, options) {
316
+ const transitionId = this.generateTransitionId();
317
+ const perspective = options.perspective || 1e3;
318
+ return new Promise(async (resolve) => {
319
+ const initialTransform = getComputedStyle(element).transform;
320
+ const isReverse = options.direction === "reverse";
321
+ element.style.perspective = `${perspective}px`;
322
+ element.style.transformStyle = "preserve-3d";
323
+ if (!isReverse) {
324
+ element.style.transform = `rotateY(90deg)`;
325
+ }
326
+ this.enableGPUAcceleration(element);
327
+ const motionId = await motionEngine.motion(
328
+ element,
329
+ [
330
+ { progress: 0, properties: { rotateY: isReverse ? 0 : 90 } },
331
+ { progress: 1, properties: { rotateY: isReverse ? 90 : 0 } }
332
+ ],
333
+ {
334
+ duration: options.duration,
335
+ easing: options.easing || this.getDefaultEasing(),
336
+ delay: options.delay,
337
+ onStart: options.onTransitionStart,
338
+ onUpdate: (progress) => {
339
+ const currentRotateY = isReverse ? 90 * progress : 90 * (1 - progress);
340
+ element.style.transform = `rotateY(${currentRotateY}deg)`;
341
+ },
342
+ onComplete: () => {
343
+ element.style.transform = initialTransform;
344
+ element.style.perspective = "";
345
+ element.style.transformStyle = "";
346
+ options.onTransitionComplete?.();
347
+ this.activeTransitions.delete(transitionId);
348
+ resolve();
349
+ }
350
+ }
351
+ );
352
+ this.activeTransitions.set(transitionId, motionId);
353
+ });
354
+ }
355
+ /**
356
+ * 큐브 전환 (3D 큐브 회전)
357
+ */
358
+ async cube(element, options) {
359
+ const transitionId = this.generateTransitionId();
360
+ const perspective = options.perspective || 1200;
361
+ return new Promise(async (resolve) => {
362
+ const initialTransform = getComputedStyle(element).transform;
363
+ const isReverse = options.direction === "reverse";
364
+ element.style.perspective = `${perspective}px`;
365
+ element.style.transformStyle = "preserve-3d";
366
+ if (!isReverse) {
367
+ element.style.transform = `rotateX(90deg) rotateY(45deg)`;
368
+ }
369
+ this.enableGPUAcceleration(element);
370
+ const motionId = await motionEngine.motion(
371
+ element,
372
+ [
373
+ { progress: 0, properties: { rotateX: isReverse ? 0 : 90, rotateY: isReverse ? 0 : 45 } },
374
+ { progress: 1, properties: { rotateX: isReverse ? 90 : 0, rotateY: isReverse ? 45 : 0 } }
375
+ ],
376
+ {
377
+ duration: options.duration,
378
+ easing: options.easing || this.getDefaultEasing(),
379
+ delay: options.delay,
380
+ onStart: options.onTransitionStart,
381
+ onUpdate: (progress) => {
382
+ const currentRotateX = isReverse ? 90 * progress : 90 * (1 - progress);
383
+ const currentRotateY = isReverse ? 45 * progress : 45 * (1 - progress);
384
+ element.style.transform = `rotateX(${currentRotateX}deg) rotateY(${currentRotateY}deg)`;
385
+ },
386
+ onComplete: () => {
387
+ element.style.transform = initialTransform;
388
+ element.style.perspective = "";
389
+ element.style.transformStyle = "";
390
+ options.onTransitionComplete?.();
391
+ this.activeTransitions.delete(transitionId);
392
+ resolve();
393
+ }
394
+ }
395
+ );
396
+ this.activeTransitions.set(transitionId, motionId);
397
+ });
398
+ }
399
+ /**
400
+ * 모프 전환 (복합 변형)
401
+ */
402
+ async morph(element, options) {
403
+ const transitionId = this.generateTransitionId();
404
+ return new Promise(async (resolve) => {
405
+ const initialTransform = getComputedStyle(element).transform;
406
+ const isReverse = options.direction === "reverse";
407
+ if (!isReverse) {
408
+ element.style.transform = `scale(0.9) rotate(5deg)`;
409
+ }
410
+ this.enableGPUAcceleration(element);
411
+ const motionId = await motionEngine.motion(
412
+ element,
413
+ [
414
+ { progress: 0, properties: { scale: isReverse ? 1 : 0.9, rotate: isReverse ? 0 : 5 } },
415
+ { progress: 1, properties: { scale: isReverse ? 0.9 : 1, rotate: isReverse ? 5 : 0 } }
416
+ ],
417
+ {
418
+ duration: options.duration,
419
+ easing: options.easing || this.getDefaultEasing(),
420
+ delay: options.delay,
421
+ onStart: options.onTransitionStart,
422
+ onUpdate: (progress) => {
423
+ const currentScale = isReverse ? 1 - 0.1 * progress : 0.9 + 0.1 * progress;
424
+ const currentRotate = isReverse ? 5 * progress : 5 * (1 - progress);
425
+ element.style.transform = `scale(${currentScale}) rotate(${currentRotate}deg)`;
426
+ },
427
+ onComplete: () => {
428
+ element.style.transform = initialTransform;
429
+ options.onTransitionComplete?.();
430
+ this.activeTransitions.delete(transitionId);
431
+ resolve();
432
+ }
433
+ }
434
+ );
435
+ this.activeTransitions.set(transitionId, motionId);
436
+ });
437
+ }
438
+ /**
439
+ * 전환 중지
440
+ */
441
+ stopTransition(transitionId) {
442
+ const motionId = this.activeTransitions.get(transitionId);
443
+ if (motionId) {
444
+ motionEngine.stop(motionId);
445
+ this.activeTransitions.delete(transitionId);
446
+ }
447
+ }
448
+ /**
449
+ * 모든 전환 중지
450
+ */
451
+ stopAllTransitions() {
452
+ this.activeTransitions.forEach((motionId) => {
453
+ motionEngine.stop(motionId);
454
+ });
455
+ this.activeTransitions.clear();
456
+ }
457
+ /**
458
+ * 활성 전환 수 확인
459
+ */
460
+ getActiveTransitionCount() {
461
+ return this.activeTransitions.size;
462
+ }
463
+ /**
464
+ * GPU 가속 활성화
465
+ */
466
+ enableGPUAcceleration(element) {
467
+ element.style.willChange = "transform, opacity";
468
+ element.style.transform = "translateZ(0)";
469
+ }
470
+ /**
471
+ * 기본 이징 함수
472
+ */
473
+ getDefaultEasing() {
474
+ return (t) => t * t * (3 - 2 * t);
475
+ }
476
+ /**
477
+ * 고유 전환 ID 생성
478
+ */
479
+ generateTransitionId() {
480
+ return `transition_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
481
+ }
482
+ /**
483
+ * 정리
484
+ */
485
+ destroy() {
486
+ this.stopAllTransitions();
487
+ }
488
+ };
489
+ var transitionEffects = TransitionEffects.getInstance();
490
+
491
+ // src/core/PerformanceOptimizer.ts
492
+ var PerformanceOptimizer = class _PerformanceOptimizer {
493
+ constructor() {
494
+ this.performanceObserver = null;
495
+ this.layerRegistry = /* @__PURE__ */ new Set();
496
+ this.isMonitoring = false;
497
+ this.config = {
498
+ enableGPUAcceleration: true,
499
+ enableLayerSeparation: true,
500
+ enableMemoryOptimization: true,
501
+ targetFPS: 60,
502
+ maxLayerCount: 100,
503
+ memoryThreshold: 50 * 1024 * 1024
504
+ // 50MB
505
+ };
506
+ this.metrics = {
507
+ fps: 0,
508
+ layerCount: 0,
509
+ activeMotions: 0
510
+ };
511
+ this.initializePerformanceMonitoring();
512
+ }
513
+ static getInstance() {
514
+ if (!_PerformanceOptimizer.instance) {
515
+ _PerformanceOptimizer.instance = new _PerformanceOptimizer();
516
+ }
517
+ return _PerformanceOptimizer.instance;
518
+ }
519
+ /**
520
+ * 성능 모니터링 초기화
521
+ */
522
+ initializePerformanceMonitoring() {
523
+ if (typeof PerformanceObserver !== "undefined") {
524
+ try {
525
+ this.performanceObserver = new PerformanceObserver((list) => {
526
+ const entries = list.getEntries();
527
+ this.updatePerformanceMetrics(entries);
528
+ });
529
+ this.performanceObserver.observe({ entryTypes: ["measure", "navigation"] });
530
+ } catch (error) {
531
+ if (process.env.NODE_ENV === "development") {
532
+ console.warn("Performance monitoring not supported:", error);
533
+ }
534
+ }
535
+ }
536
+ }
537
+ /**
538
+ * 성능 메트릭 업데이트
539
+ */
540
+ updatePerformanceMetrics(entries) {
541
+ entries.forEach((entry) => {
542
+ if (entry.entryType === "measure") {
543
+ this.calculateFPS();
544
+ }
545
+ });
546
+ }
547
+ /**
548
+ * FPS 계산
549
+ */
550
+ calculateFPS() {
551
+ const now = performance.now();
552
+ const deltaTime = now - (this.lastFrameTime || now);
553
+ this.lastFrameTime = now;
554
+ if (deltaTime > 0) {
555
+ this.metrics.fps = Math.round(1e3 / deltaTime);
556
+ }
557
+ }
558
+ /**
559
+ * GPU 가속 활성화
560
+ */
561
+ enableGPUAcceleration(element) {
562
+ if (!this.config.enableGPUAcceleration) return;
563
+ try {
564
+ element.style.willChange = "transform, opacity";
565
+ element.style.transform = "translateZ(0)";
566
+ element.style.backfaceVisibility = "hidden";
567
+ element.style.transformStyle = "preserve-3d";
568
+ this.registerLayer(element);
569
+ } catch (error) {
570
+ if (process.env.NODE_ENV === "development") {
571
+ console.warn("GPU acceleration failed:", error);
572
+ }
573
+ }
574
+ }
575
+ /**
576
+ * 레이어 분리 및 최적화
577
+ */
578
+ createOptimizedLayer(element) {
579
+ if (!this.config.enableLayerSeparation) return;
580
+ try {
581
+ element.style.transform = "translateZ(0)";
582
+ element.style.backfaceVisibility = "hidden";
583
+ element.style.perspective = "1000px";
584
+ this.registerLayer(element);
585
+ this.checkLayerLimit();
586
+ } catch (error) {
587
+ if (process.env.NODE_ENV === "development") {
588
+ console.warn("Layer optimization failed:", error);
589
+ }
590
+ }
591
+ }
592
+ /**
593
+ * 레이어 등록
594
+ */
595
+ registerLayer(element) {
596
+ if (this.layerRegistry.has(element)) return;
597
+ this.layerRegistry.add(element);
598
+ this.metrics.layerCount = this.layerRegistry.size;
599
+ this.checkMemoryUsage();
600
+ }
601
+ /**
602
+ * 레이어 제거
603
+ */
604
+ removeLayer(element) {
605
+ if (this.layerRegistry.has(element)) {
606
+ this.layerRegistry.delete(element);
607
+ this.metrics.layerCount = this.layerRegistry.size;
608
+ element.style.willChange = "auto";
609
+ element.style.transform = "";
610
+ element.style.backfaceVisibility = "";
611
+ element.style.transformStyle = "";
612
+ element.style.perspective = "";
613
+ }
614
+ }
615
+ /**
616
+ * 레이어 수 제한 체크
617
+ */
618
+ checkLayerLimit() {
619
+ if (this.metrics.layerCount > this.config.maxLayerCount) {
620
+ if (process.env.NODE_ENV === "development") {
621
+ console.warn(`Layer count (${this.metrics.layerCount}) exceeds limit (${this.config.maxLayerCount})`);
622
+ }
623
+ this.cleanupOldLayers();
624
+ }
625
+ }
626
+ /**
627
+ * 오래된 레이어 정리
628
+ */
629
+ cleanupOldLayers() {
630
+ const layersToRemove = Array.from(this.layerRegistry).slice(0, 10);
631
+ layersToRemove.forEach((layer) => {
632
+ this.removeLayer(layer);
633
+ });
634
+ }
635
+ /**
636
+ * 메모리 사용량 체크
637
+ */
638
+ checkMemoryUsage() {
639
+ if (!this.config.enableMemoryOptimization) return;
640
+ if ("memory" in performance) {
641
+ const memory = performance.memory;
642
+ this.metrics.memoryUsage = memory.usedJSHeapSize;
643
+ if (memory.usedJSHeapSize > this.config.memoryThreshold) {
644
+ if (process.env.NODE_ENV === "development") {
645
+ console.warn("Memory usage high, cleaning up...");
646
+ }
647
+ this.cleanupMemory();
648
+ }
649
+ }
650
+ }
651
+ /**
652
+ * 메모리 정리
653
+ */
654
+ cleanupMemory() {
655
+ if ("gc" in window) {
656
+ try {
657
+ window.gc();
658
+ } catch (error) {
659
+ }
660
+ }
661
+ this.cleanupOldLayers();
662
+ }
663
+ /**
664
+ * 성능 최적화 설정 업데이트
665
+ */
666
+ updateConfig(newConfig) {
667
+ this.config = { ...this.config, ...newConfig };
668
+ if (!this.config.enableGPUAcceleration) {
669
+ this.disableAllGPUAcceleration();
670
+ }
671
+ if (!this.config.enableLayerSeparation) {
672
+ this.disableAllLayers();
673
+ }
674
+ }
675
+ /**
676
+ * 모든 GPU 가속 비활성화
677
+ */
678
+ disableAllGPUAcceleration() {
679
+ this.layerRegistry.forEach((element) => {
680
+ element.style.willChange = "auto";
681
+ element.style.transform = "";
682
+ });
683
+ }
684
+ /**
685
+ * 모든 레이어 비활성화
686
+ */
687
+ disableAllLayers() {
688
+ this.layerRegistry.forEach((element) => {
689
+ this.removeLayer(element);
690
+ });
691
+ }
692
+ /**
693
+ * 성능 메트릭 가져오기
694
+ */
695
+ getMetrics() {
696
+ return { ...this.metrics };
697
+ }
698
+ /**
699
+ * 성능 모니터링 시작
700
+ */
701
+ startMonitoring() {
702
+ if (this.isMonitoring) return;
703
+ this.isMonitoring = true;
704
+ this.monitoringInterval = setInterval(() => {
705
+ this.updateMetrics();
706
+ }, 1e3);
707
+ }
708
+ /**
709
+ * 성능 모니터링 중지
710
+ */
711
+ stopMonitoring() {
712
+ if (!this.isMonitoring) return;
713
+ this.isMonitoring = false;
714
+ if (this.monitoringInterval) {
715
+ clearInterval(this.monitoringInterval);
716
+ this.monitoringInterval = void 0;
717
+ }
718
+ }
719
+ /**
720
+ * 메트릭 업데이트
721
+ */
722
+ updateMetrics() {
723
+ if ("memory" in performance) {
724
+ const memory = performance.memory;
725
+ this.metrics.memoryUsage = memory.usedJSHeapSize;
726
+ }
727
+ this.metrics.layerCount = this.layerRegistry.size;
728
+ }
729
+ /**
730
+ * 성능 리포트 생성
731
+ */
732
+ generateReport() {
733
+ const metrics = this.getMetrics();
734
+ return `
735
+ === HUA Motion Performance Report ===
736
+ FPS: ${metrics.fps}
737
+ Active Layers: ${metrics.layerCount}
738
+ Memory Usage: ${this.formatBytes(metrics.memoryUsage || 0)}
739
+ Active Motions: ${metrics.activeMotions}
740
+ GPU Acceleration: ${this.config.enableGPUAcceleration ? "Enabled" : "Disabled"}
741
+ Layer Separation: ${this.config.enableLayerSeparation ? "Enabled" : "Disabled"}
742
+ Memory Optimization: ${this.config.enableMemoryOptimization ? "Enabled" : "Disabled"}
743
+ =====================================
744
+ `.trim();
745
+ }
746
+ /**
747
+ * 바이트 단위 포맷팅
748
+ */
749
+ formatBytes(bytes) {
750
+ if (bytes === 0) return "0 B";
751
+ const k = 1024;
752
+ const sizes = ["B", "KB", "MB", "GB"];
753
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
754
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
755
+ }
756
+ /**
757
+ * 성능 최적화 권장사항
758
+ */
759
+ getOptimizationRecommendations() {
760
+ const recommendations = [];
761
+ const metrics = this.getMetrics();
762
+ if (metrics.fps < this.config.targetFPS) {
763
+ recommendations.push("FPS\uAC00 \uB0AE\uC2B5\uB2C8\uB2E4. GPU \uAC00\uC18D\uC744 \uD65C\uC131\uD654\uD558\uAC70\uB098 \uB808\uC774\uC5B4 \uC218\uB97C \uC904\uC774\uC138\uC694.");
764
+ }
765
+ if (metrics.layerCount > this.config.maxLayerCount * 0.8) {
766
+ recommendations.push("\uB808\uC774\uC5B4 \uC218\uAC00 \uB9CE\uC2B5\uB2C8\uB2E4. \uBD88\uD544\uC694\uD55C \uB808\uC774\uC5B4\uB97C \uC815\uB9AC\uD558\uC138\uC694.");
767
+ }
768
+ if (metrics.memoryUsage && metrics.memoryUsage > this.config.memoryThreshold * 0.8) {
769
+ recommendations.push("\uBA54\uBAA8\uB9AC \uC0AC\uC6A9\uB7C9\uC774 \uB192\uC2B5\uB2C8\uB2E4. \uBA54\uBAA8\uB9AC \uC815\uB9AC\uB97C \uACE0\uB824\uD558\uC138\uC694.");
770
+ }
771
+ return recommendations;
772
+ }
773
+ /**
774
+ * 정리
775
+ */
776
+ destroy() {
777
+ this.stopMonitoring();
778
+ if (this.performanceObserver) {
779
+ this.performanceObserver.disconnect();
780
+ this.performanceObserver = null;
781
+ }
782
+ this.layerRegistry.forEach((element) => {
783
+ this.removeLayer(element);
784
+ });
785
+ this.layerRegistry.clear();
786
+ }
787
+ };
788
+ var performanceOptimizer = PerformanceOptimizer.getInstance();
789
+
790
+ // src/presets/index.ts
791
+ var MOTION_PRESETS = {
792
+ hero: {
793
+ entrance: "fadeIn",
794
+ delay: 200,
795
+ duration: 800,
796
+ hover: false,
797
+ click: false
798
+ },
799
+ title: {
800
+ entrance: "slideUp",
801
+ delay: 400,
802
+ duration: 700,
803
+ hover: false,
804
+ click: false
805
+ },
806
+ button: {
807
+ entrance: "scaleIn",
808
+ delay: 600,
809
+ duration: 300,
810
+ hover: true,
811
+ click: true
812
+ },
813
+ card: {
814
+ entrance: "slideUp",
815
+ delay: 800,
816
+ duration: 500,
817
+ hover: true,
818
+ click: false
819
+ },
820
+ text: {
821
+ entrance: "fadeIn",
822
+ delay: 200,
823
+ duration: 600,
824
+ hover: false,
825
+ click: false
826
+ },
827
+ image: {
828
+ entrance: "scaleIn",
829
+ delay: 400,
830
+ duration: 600,
831
+ hover: true,
832
+ click: false
833
+ }
834
+ };
835
+ var PAGE_MOTIONS = {
836
+ // 홈페이지
837
+ home: {
838
+ hero: { type: "hero" },
839
+ title: { type: "title" },
840
+ description: { type: "text" },
841
+ cta: { type: "button" },
842
+ feature1: { type: "card" },
843
+ feature2: { type: "card" },
844
+ feature3: { type: "card" }
845
+ },
846
+ // 대시보드
847
+ dashboard: {
848
+ header: { type: "hero" },
849
+ sidebar: { type: "card", entrance: "slideLeft" },
850
+ main: { type: "text", entrance: "fadeIn" },
851
+ card1: { type: "card" },
852
+ card2: { type: "card" },
853
+ card3: { type: "card" },
854
+ chart: { type: "image" }
855
+ },
856
+ // 제품 페이지
857
+ product: {
858
+ hero: { type: "hero" },
859
+ title: { type: "title" },
860
+ image: { type: "image" },
861
+ description: { type: "text" },
862
+ price: { type: "text" },
863
+ buyButton: { type: "button" },
864
+ features: { type: "card" }
865
+ },
866
+ // 블로그
867
+ blog: {
868
+ header: { type: "hero" },
869
+ title: { type: "title" },
870
+ content: { type: "text" },
871
+ sidebar: { type: "card", entrance: "slideRight" },
872
+ related1: { type: "card" },
873
+ related2: { type: "card" },
874
+ related3: { type: "card" }
875
+ }
876
+ };
877
+ function mergeWithPreset(preset, custom = {}) {
878
+ return {
879
+ ...preset,
880
+ ...custom
881
+ };
882
+ }
883
+ function getPagePreset(pageType) {
884
+ return PAGE_MOTIONS[pageType];
885
+ }
886
+ function getMotionPreset(type) {
887
+ return MOTION_PRESETS[type] || MOTION_PRESETS.text;
888
+ }
889
+
890
+ // src/hooks/useSimplePageMotion.ts
891
+ function useSimplePageMotion(pageType) {
892
+ const config = getPagePreset(pageType);
893
+ return useSimplePageMotions(config);
894
+ }
895
+ function useSimplePageMotions(config) {
896
+ const [motions, setMotions] = useState(/* @__PURE__ */ new Map());
897
+ const observersRef = useRef(/* @__PURE__ */ new Map());
898
+ const calculateMotionValues = useCallback((isVisible, elementConfig) => {
899
+ const preset = getMotionPreset(elementConfig.type);
900
+ mergeWithPreset(preset, elementConfig);
901
+ let opacity = isVisible ? 1 : 0;
902
+ let translateY = isVisible ? 0 : 20;
903
+ let translateX = 0;
904
+ let scale = isVisible ? 1 : 0.95;
905
+ return { opacity, translateY, translateX, scale };
906
+ }, []);
907
+ useEffect(() => {
908
+ const newMotions = /* @__PURE__ */ new Map();
909
+ Object.entries(config).forEach(([elementId, elementConfig]) => {
910
+ const ref = { current: null };
911
+ const { opacity, translateY, translateX, scale } = calculateMotionValues(false, elementConfig);
912
+ newMotions.set(elementId, {
913
+ ref,
914
+ style: {
915
+ opacity,
916
+ transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`,
917
+ transition: `all ${elementConfig.duration || 700}ms ease-out`,
918
+ pointerEvents: "auto",
919
+ willChange: "transform, opacity"
920
+ },
921
+ isVisible: false,
922
+ isHovered: false,
923
+ isClicked: false
924
+ });
925
+ });
926
+ setMotions(newMotions);
927
+ }, [config, calculateMotionValues]);
928
+ useEffect(() => {
929
+ const visibleElements = /* @__PURE__ */ new Set();
930
+ Object.entries(config).forEach(([elementId, elementConfig]) => {
931
+ const observer = new IntersectionObserver(
932
+ (entries) => {
933
+ entries.forEach((entry) => {
934
+ if (entry.isIntersecting && !visibleElements.has(elementId)) {
935
+ visibleElements.add(elementId);
936
+ const preset = getMotionPreset(elementConfig.type);
937
+ const mergedConfig = mergeWithPreset(preset, elementConfig);
938
+ const delay = mergedConfig.delay || 0;
939
+ setTimeout(() => {
940
+ const { opacity, translateY, translateX, scale } = calculateMotionValues(true, elementConfig);
941
+ setMotions((prev) => {
942
+ const current = prev.get(elementId);
943
+ if (!current) return prev;
944
+ const newMotion = {
945
+ ...current,
946
+ style: {
947
+ ...current.style,
948
+ opacity,
949
+ transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`
950
+ },
951
+ isVisible: true
952
+ };
953
+ const newMap = new Map(prev);
954
+ newMap.set(elementId, newMotion);
955
+ return newMap;
956
+ });
957
+ if (process.env.NODE_ENV === "development") {
958
+ console.log("\uBAA8\uC158 \uC2E4\uD589:", elementId, "delay:", delay);
959
+ }
960
+ }, delay);
961
+ observer.unobserve(entry.target);
962
+ }
963
+ });
964
+ },
965
+ { threshold: elementConfig.threshold || 0.1 }
966
+ );
967
+ observersRef.current.set(elementId, observer);
968
+ });
969
+ const timer = setTimeout(() => {
970
+ Object.entries(config).forEach(([elementId]) => {
971
+ const observer = observersRef.current.get(elementId);
972
+ if (observer) {
973
+ const element = document.querySelector(`[data-motion-id="${elementId}"]`);
974
+ if (element) {
975
+ observer.observe(element);
976
+ }
977
+ }
978
+ });
979
+ }, 100);
980
+ return () => {
981
+ clearTimeout(timer);
982
+ observersRef.current.forEach((observer) => observer.disconnect());
983
+ observersRef.current.clear();
984
+ };
985
+ }, [config, calculateMotionValues]);
986
+ const getPageMotionRefs = useCallback(() => {
987
+ const result = {};
988
+ motions.forEach((motion, elementId) => {
989
+ result[elementId] = motion;
990
+ });
991
+ return result;
992
+ }, [motions]);
993
+ return getPageMotionRefs();
994
+ }
995
+
996
+ // src/managers/MotionStateManager.ts
997
+ var MotionStateManager = class {
998
+ constructor() {
999
+ this.states = /* @__PURE__ */ new Map();
1000
+ this.listeners = /* @__PURE__ */ new Map();
1001
+ }
1002
+ /**
1003
+ * 요소의 상태를 초기화
1004
+ */
1005
+ initializeElement(elementId, config) {
1006
+ const initialState = {
1007
+ internalVisibility: false,
1008
+ // 초기에 숨김 상태로 시작 (스크롤 리빌용)
1009
+ triggeredVisibility: false,
1010
+ // Intersection Observer가 아직 트리거되지 않음
1011
+ finalVisibility: false,
1012
+ // 초기에 숨김 상태로 시작
1013
+ opacity: 0,
1014
+ // 초기에 투명 상태로 시작
1015
+ translateY: 20,
1016
+ // 초기에 아래로 이동된 상태로 시작
1017
+ translateX: 0,
1018
+ scale: 0.95,
1019
+ // 초기에 약간 축소된 상태로 시작
1020
+ rotation: 0,
1021
+ isHovered: false,
1022
+ isClicked: false,
1023
+ isAnimating: false
1024
+ };
1025
+ this.states.set(elementId, initialState);
1026
+ this.computeFinalState(elementId);
1027
+ }
1028
+ /**
1029
+ * 내부 가시성 상태 업데이트 (초기화, 리셋 등)
1030
+ */
1031
+ setInternalVisibility(elementId, visible) {
1032
+ const state = this.states.get(elementId);
1033
+ if (!state) return;
1034
+ state.internalVisibility = visible;
1035
+ this.computeFinalState(elementId);
1036
+ this.notifyListeners(elementId, state);
1037
+ }
1038
+ /**
1039
+ * 외부 트리거 가시성 상태 업데이트 (Intersection Observer)
1040
+ */
1041
+ setTriggeredVisibility(elementId, visible) {
1042
+ const state = this.states.get(elementId);
1043
+ if (!state) return;
1044
+ state.triggeredVisibility = visible;
1045
+ this.computeFinalState(elementId);
1046
+ this.notifyListeners(elementId, state);
1047
+ }
1048
+ /**
1049
+ * 모션 값 업데이트
1050
+ */
1051
+ updateMotionValues(elementId, values) {
1052
+ const state = this.states.get(elementId);
1053
+ if (!state) return;
1054
+ Object.assign(state, values);
1055
+ this.notifyListeners(elementId, state);
1056
+ }
1057
+ /**
1058
+ * 최종 상태 계산
1059
+ */
1060
+ computeFinalState(elementId) {
1061
+ const state = this.states.get(elementId);
1062
+ if (!state) return;
1063
+ state.finalVisibility = state.internalVisibility || state.triggeredVisibility;
1064
+ state.isAnimating = state.finalVisibility && (state.opacity < 1 || state.translateY > 0);
1065
+ }
1066
+ /**
1067
+ * 현재 상태 조회
1068
+ */
1069
+ getState(elementId) {
1070
+ return this.states.get(elementId);
1071
+ }
1072
+ /**
1073
+ * 모든 상태 조회
1074
+ */
1075
+ getAllStates() {
1076
+ return new Map(this.states);
1077
+ }
1078
+ /**
1079
+ * 상태 변경 리스너 등록
1080
+ */
1081
+ subscribe(elementId, listener) {
1082
+ if (!this.listeners.has(elementId)) {
1083
+ this.listeners.set(elementId, /* @__PURE__ */ new Set());
1084
+ }
1085
+ this.listeners.get(elementId).add(listener);
1086
+ return () => {
1087
+ const listeners = this.listeners.get(elementId);
1088
+ if (listeners) {
1089
+ listeners.delete(listener);
1090
+ }
1091
+ };
1092
+ }
1093
+ /**
1094
+ * 리스너들에게 상태 변경 알림
1095
+ */
1096
+ notifyListeners(elementId, state) {
1097
+ const listeners = this.listeners.get(elementId);
1098
+ if (!listeners) return;
1099
+ listeners.forEach((listener) => {
1100
+ try {
1101
+ listener(state);
1102
+ } catch (error) {
1103
+ if (process.env.NODE_ENV === "development") {
1104
+ console.error(`MotionStateManager listener error for ${elementId}:`, error);
1105
+ }
1106
+ }
1107
+ });
1108
+ }
1109
+ /**
1110
+ * 모든 상태 초기화
1111
+ */
1112
+ reset() {
1113
+ this.states.clear();
1114
+ this.listeners.clear();
1115
+ }
1116
+ /**
1117
+ * 특정 요소 상태 초기화
1118
+ */
1119
+ resetElement(elementId) {
1120
+ this.states.delete(elementId);
1121
+ this.listeners.delete(elementId);
1122
+ }
1123
+ /**
1124
+ * 디버그용 상태 출력
1125
+ */
1126
+ debug() {
1127
+ if (process.env.NODE_ENV === "development") {
1128
+ console.log("MotionStateManager Debug:");
1129
+ this.states.forEach((state, elementId) => {
1130
+ console.log(` ${elementId}:`, {
1131
+ internalVisibility: state.internalVisibility,
1132
+ triggeredVisibility: state.triggeredVisibility,
1133
+ finalVisibility: state.finalVisibility,
1134
+ opacity: state.opacity,
1135
+ translateY: state.translateY,
1136
+ isAnimating: state.isAnimating
1137
+ });
1138
+ });
1139
+ }
1140
+ }
1141
+ };
1142
+ var motionStateManager = new MotionStateManager();
1143
+
1144
+ // src/hooks/usePageMotions.ts
1145
+ function usePageMotions(config) {
1146
+ const [motions, setMotions] = useState(/* @__PURE__ */ new Map());
1147
+ const observersRef = useRef(/* @__PURE__ */ new Map());
1148
+ const unsubscribeRef = useRef(/* @__PURE__ */ new Map());
1149
+ const [resetKey, setResetKey] = useState(0);
1150
+ const reset = useCallback(() => {
1151
+ observersRef.current.forEach((observer) => observer.disconnect());
1152
+ observersRef.current.clear();
1153
+ unsubscribeRef.current.forEach((unsubscribe) => unsubscribe());
1154
+ unsubscribeRef.current.clear();
1155
+ motionStateManager.reset();
1156
+ setMotions(/* @__PURE__ */ new Map());
1157
+ setResetKey((prev) => prev + 1);
1158
+ }, []);
1159
+ const calculateMotionValues = useCallback((state, elementConfig) => {
1160
+ const preset = getMotionPreset(elementConfig.type);
1161
+ const mergedConfig = mergeWithPreset(preset, elementConfig);
1162
+ let opacity = state.finalVisibility ? 1 : 0;
1163
+ let translateY = state.finalVisibility ? 0 : 20;
1164
+ let translateX = 0;
1165
+ let scale = state.finalVisibility ? 1 : 0.95;
1166
+ if (mergedConfig.hover && state.isHovered) {
1167
+ scale *= 1.1;
1168
+ translateY -= 5;
1169
+ opacity = 0.9;
1170
+ }
1171
+ if (mergedConfig.click && state.isClicked) {
1172
+ scale *= 0.9;
1173
+ translateY += 3;
1174
+ opacity = 0.8;
1175
+ }
1176
+ return { opacity, translateY, translateX, scale };
1177
+ }, []);
1178
+ const updateMotionState = useCallback((elementId, updates) => {
1179
+ const currentState = motionStateManager.getState(elementId);
1180
+ if (!currentState) return;
1181
+ if (updates.opacity !== void 0 || updates.translateY !== void 0 || updates.translateX !== void 0 || updates.scale !== void 0) {
1182
+ motionStateManager.updateMotionValues(elementId, updates);
1183
+ }
1184
+ if (updates.isHovered !== void 0) {
1185
+ currentState.isHovered = updates.isHovered;
1186
+ motionStateManager.notifyListeners(elementId, currentState);
1187
+ }
1188
+ if (updates.isClicked !== void 0) {
1189
+ currentState.isClicked = updates.isClicked;
1190
+ motionStateManager.notifyListeners(elementId, currentState);
1191
+ }
1192
+ }, []);
1193
+ useEffect(() => {
1194
+ const newMotions = /* @__PURE__ */ new Map();
1195
+ if (!config || typeof config !== "object") {
1196
+ if (process.env.NODE_ENV === "development") {
1197
+ console.warn("usePageMotions: config\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4:", config);
1198
+ }
1199
+ return;
1200
+ }
1201
+ motionStateManager.reset();
1202
+ Object.entries(config).forEach(([elementId, elementConfig]) => {
1203
+ const ref = { current: null };
1204
+ motionStateManager.initializeElement(elementId, elementConfig);
1205
+ const initialState = motionStateManager.getState(elementId);
1206
+ if (process.env.NODE_ENV === "development") {
1207
+ console.log(`\uCD08\uAE30 \uC0C1\uD0DC [${elementId}]:`, initialState);
1208
+ }
1209
+ const { opacity, translateY, translateX, scale } = calculateMotionValues(initialState, elementConfig);
1210
+ newMotions.set(elementId, {
1211
+ ref,
1212
+ style: {
1213
+ opacity,
1214
+ transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`,
1215
+ transition: `all ${elementConfig.duration || 700}ms ease-out`,
1216
+ pointerEvents: "auto",
1217
+ willChange: "transform, opacity"
1218
+ },
1219
+ isVisible: initialState.finalVisibility,
1220
+ isHovered: initialState.isHovered,
1221
+ isClicked: initialState.isClicked
1222
+ });
1223
+ const unsubscribe = motionStateManager.subscribe(elementId, (newState) => {
1224
+ const { opacity: opacity2, translateY: translateY2, translateX: translateX2, scale: scale2 } = calculateMotionValues(newState, elementConfig);
1225
+ setMotions((prev) => {
1226
+ const current = prev.get(elementId);
1227
+ if (!current) return prev;
1228
+ const transform = `translate(${translateX2}px, ${translateY2}px) scale(${scale2})`;
1229
+ const hasChanged = current.style.opacity !== opacity2 || current.style.transform !== transform || current.isVisible !== newState.finalVisibility || current.isHovered !== newState.isHovered || current.isClicked !== newState.isClicked;
1230
+ if (!hasChanged) return prev;
1231
+ const newMotion = {
1232
+ ...current,
1233
+ style: {
1234
+ ...current.style,
1235
+ opacity: opacity2,
1236
+ transform
1237
+ },
1238
+ isVisible: newState.finalVisibility,
1239
+ isHovered: newState.isHovered,
1240
+ isClicked: newState.isClicked
1241
+ };
1242
+ const newMap = new Map(prev);
1243
+ newMap.set(elementId, newMotion);
1244
+ return newMap;
1245
+ });
1246
+ });
1247
+ unsubscribeRef.current.set(elementId, unsubscribe);
1248
+ });
1249
+ setMotions(newMotions);
1250
+ return () => {
1251
+ unsubscribeRef.current.forEach((unsubscribe) => unsubscribe());
1252
+ unsubscribeRef.current.clear();
1253
+ motionStateManager.reset();
1254
+ };
1255
+ }, [config, resetKey]);
1256
+ useEffect(() => {
1257
+ const visibleElements = /* @__PURE__ */ new Set();
1258
+ if (!config || typeof config !== "object") {
1259
+ return;
1260
+ }
1261
+ Object.entries(config).forEach(([elementId, elementConfig]) => {
1262
+ const observer = new IntersectionObserver(
1263
+ (entries) => {
1264
+ entries.forEach((entry) => {
1265
+ if (entry.isIntersecting && !visibleElements.has(elementId)) {
1266
+ visibleElements.add(elementId);
1267
+ const preset = getMotionPreset(elementConfig.type);
1268
+ const mergedConfig = mergeWithPreset(preset, elementConfig);
1269
+ const delay = mergedConfig.delay || 0;
1270
+ setTimeout(() => {
1271
+ motionStateManager.setTriggeredVisibility(elementId, true);
1272
+ if (process.env.NODE_ENV === "development") {
1273
+ console.log("\uBAA8\uC158 \uC2E4\uD589:", elementId, "delay:", delay);
1274
+ }
1275
+ }, delay);
1276
+ observer.unobserve(entry.target);
1277
+ }
1278
+ });
1279
+ },
1280
+ {
1281
+ threshold: elementConfig.threshold || 0.3,
1282
+ // 30% 보여야 실행
1283
+ rootMargin: "0px 0px -50px 0px"
1284
+ // 하단에서 50px 전에 실행
1285
+ }
1286
+ );
1287
+ observersRef.current.set(elementId, observer);
1288
+ });
1289
+ const timer = setTimeout(() => {
1290
+ if (!config || typeof config !== "object") {
1291
+ return;
1292
+ }
1293
+ Object.entries(config).forEach(([elementId]) => {
1294
+ const observer = observersRef.current.get(elementId);
1295
+ if (observer) {
1296
+ const element = document.querySelector(`[data-motion-id="${elementId}"]`);
1297
+ if (element) {
1298
+ observer.observe(element);
1299
+ }
1300
+ }
1301
+ });
1302
+ }, 100);
1303
+ return () => {
1304
+ clearTimeout(timer);
1305
+ observersRef.current.forEach((observer) => observer.disconnect());
1306
+ observersRef.current.clear();
1307
+ };
1308
+ }, [config, resetKey]);
1309
+ useEffect(() => {
1310
+ if (!config || typeof config !== "object") {
1311
+ return;
1312
+ }
1313
+ const handleMouseEnter = (event) => {
1314
+ const target = event.target;
1315
+ if (!target) return;
1316
+ let element = target;
1317
+ let elementId = null;
1318
+ while (element && element !== document.body) {
1319
+ if (element.getAttribute && typeof element.getAttribute === "function") {
1320
+ elementId = element.getAttribute("data-motion-id");
1321
+ if (elementId) break;
1322
+ }
1323
+ element = element.parentElement;
1324
+ }
1325
+ if (elementId && config[elementId]?.hover) {
1326
+ if (process.env.NODE_ENV === "development") {
1327
+ console.log("\uD638\uBC84 \uC2DC\uC791:", elementId);
1328
+ }
1329
+ updateMotionState(elementId, { isHovered: true });
1330
+ }
1331
+ };
1332
+ const handleMouseLeave = (event) => {
1333
+ const target = event.target;
1334
+ if (!target) return;
1335
+ let element = target;
1336
+ let elementId = null;
1337
+ while (element && element !== document.body) {
1338
+ if (element.getAttribute && typeof element.getAttribute === "function") {
1339
+ elementId = element.getAttribute("data-motion-id");
1340
+ if (elementId) break;
1341
+ }
1342
+ element = element.parentElement;
1343
+ }
1344
+ if (elementId && config[elementId]?.hover) {
1345
+ if (process.env.NODE_ENV === "development") {
1346
+ console.log("\uD638\uBC84 \uC885\uB8CC:", elementId);
1347
+ }
1348
+ updateMotionState(elementId, { isHovered: false });
1349
+ }
1350
+ };
1351
+ const handleMouseDown = (event) => {
1352
+ const target = event.target;
1353
+ if (!target) return;
1354
+ let element = target;
1355
+ let elementId = null;
1356
+ while (element && element !== document.body) {
1357
+ if (element.getAttribute && typeof element.getAttribute === "function") {
1358
+ elementId = element.getAttribute("data-motion-id");
1359
+ if (elementId) break;
1360
+ }
1361
+ element = element.parentElement;
1362
+ }
1363
+ if (elementId && config[elementId]?.click) {
1364
+ if (process.env.NODE_ENV === "development") {
1365
+ console.log("\uD074\uB9AD \uC2DC\uC791:", elementId);
1366
+ }
1367
+ updateMotionState(elementId, { isClicked: true });
1368
+ }
1369
+ };
1370
+ const handleMouseUp = (event) => {
1371
+ const target = event.target;
1372
+ if (!target) return;
1373
+ let element = target;
1374
+ let elementId = null;
1375
+ while (element && element !== document.body) {
1376
+ if (element.getAttribute && typeof element.getAttribute === "function") {
1377
+ elementId = element.getAttribute("data-motion-id");
1378
+ if (elementId) break;
1379
+ }
1380
+ element = element.parentElement;
1381
+ }
1382
+ if (elementId && config[elementId]?.click) {
1383
+ if (process.env.NODE_ENV === "development") {
1384
+ console.log("\uD074\uB9AD \uC885\uB8CC:", elementId);
1385
+ }
1386
+ updateMotionState(elementId, { isClicked: false });
1387
+ }
1388
+ };
1389
+ const timer = setTimeout(() => {
1390
+ document.addEventListener("mouseenter", handleMouseEnter, false);
1391
+ document.addEventListener("mouseleave", handleMouseLeave, false);
1392
+ document.addEventListener("mousedown", handleMouseDown, false);
1393
+ document.addEventListener("mouseup", handleMouseUp, false);
1394
+ }, 200);
1395
+ return () => {
1396
+ clearTimeout(timer);
1397
+ document.removeEventListener("mouseenter", handleMouseEnter, false);
1398
+ document.removeEventListener("mouseleave", handleMouseLeave, false);
1399
+ document.removeEventListener("mousedown", handleMouseDown, false);
1400
+ document.removeEventListener("mouseup", handleMouseUp, false);
1401
+ };
1402
+ }, [config]);
1403
+ const getPageMotionRefs = useCallback(() => {
1404
+ const result = {};
1405
+ motions.forEach((motion, elementId) => {
1406
+ result[elementId] = motion;
1407
+ });
1408
+ return result;
1409
+ }, [motions]);
1410
+ return {
1411
+ ...getPageMotionRefs(),
1412
+ reset
1413
+ };
1414
+ }
1415
+ function useSmartMotion(options = {}) {
1416
+ const {
1417
+ type = "text",
1418
+ entrance: customEntrance,
1419
+ hover: customHover,
1420
+ click: customClick,
1421
+ delay: customDelay,
1422
+ duration: customDuration,
1423
+ threshold = 0.1,
1424
+ autoLanguageSync = false
1425
+ } = options;
1426
+ const getPresetConfig = useCallback(() => {
1427
+ return MOTION_PRESETS[type] || MOTION_PRESETS.text;
1428
+ }, [type]);
1429
+ const preset = getPresetConfig();
1430
+ const entrance = customEntrance || preset.entrance;
1431
+ const hover = customHover !== void 0 ? customHover : preset.hover;
1432
+ const click = customClick !== void 0 ? customClick : preset.click;
1433
+ const delay = customDelay !== void 0 ? customDelay : preset.delay;
1434
+ const duration = customDuration !== void 0 ? customDuration : preset.duration;
1435
+ const elementRef = useRef(null);
1436
+ const getInitialMotionValues = () => {
1437
+ const initialState = {
1438
+ isVisible: false,
1439
+ isHovered: false,
1440
+ isClicked: false,
1441
+ opacity: 0,
1442
+ translateY: 0,
1443
+ translateX: 0,
1444
+ scale: 1
1445
+ };
1446
+ switch (entrance) {
1447
+ case "fadeIn":
1448
+ initialState.opacity = 0;
1449
+ break;
1450
+ case "slideUp":
1451
+ initialState.opacity = 0;
1452
+ initialState.translateY = 20;
1453
+ break;
1454
+ case "slideLeft":
1455
+ initialState.opacity = 0;
1456
+ initialState.translateX = -20;
1457
+ break;
1458
+ case "slideRight":
1459
+ initialState.opacity = 0;
1460
+ initialState.translateX = 20;
1461
+ break;
1462
+ case "scaleIn":
1463
+ initialState.opacity = 0;
1464
+ initialState.scale = 0.8;
1465
+ break;
1466
+ case "bounceIn":
1467
+ initialState.opacity = 0;
1468
+ initialState.scale = 0.5;
1469
+ break;
1470
+ }
1471
+ return initialState;
1472
+ };
1473
+ const [state, setState] = useState(() => {
1474
+ const initialValues = getInitialMotionValues();
1475
+ if (threshold === 0) {
1476
+ initialValues.isVisible = true;
1477
+ initialValues.opacity = 1;
1478
+ initialValues.translateY = 0;
1479
+ initialValues.translateX = 0;
1480
+ initialValues.scale = 1;
1481
+ }
1482
+ return initialValues;
1483
+ });
1484
+ const calculateMotionValues = useCallback((currentState) => {
1485
+ const { isVisible, isHovered, isClicked } = currentState;
1486
+ let opacity = 0;
1487
+ let translateY = 0;
1488
+ let translateX = 0;
1489
+ let scale = 1;
1490
+ if (isVisible) {
1491
+ opacity = 1;
1492
+ switch (entrance) {
1493
+ case "fadeIn":
1494
+ break;
1495
+ case "slideUp":
1496
+ translateY = 0;
1497
+ break;
1498
+ case "slideLeft":
1499
+ translateX = 0;
1500
+ break;
1501
+ case "slideRight":
1502
+ translateX = 0;
1503
+ break;
1504
+ case "scaleIn":
1505
+ scale = 1;
1506
+ break;
1507
+ case "bounceIn":
1508
+ scale = 1;
1509
+ break;
1510
+ }
1511
+ } else {
1512
+ switch (entrance) {
1513
+ case "fadeIn":
1514
+ opacity = 0;
1515
+ break;
1516
+ case "slideUp":
1517
+ opacity = 0;
1518
+ translateY = 20;
1519
+ break;
1520
+ case "slideLeft":
1521
+ opacity = 0;
1522
+ translateX = -20;
1523
+ break;
1524
+ case "slideRight":
1525
+ opacity = 0;
1526
+ translateX = 20;
1527
+ break;
1528
+ case "scaleIn":
1529
+ opacity = 0;
1530
+ scale = 0.8;
1531
+ break;
1532
+ case "bounceIn":
1533
+ opacity = 0;
1534
+ scale = 0.5;
1535
+ break;
1536
+ }
1537
+ }
1538
+ if (hover && isHovered) {
1539
+ scale *= 1.1;
1540
+ translateY -= 5;
1541
+ }
1542
+ if (click && isClicked) {
1543
+ scale *= 0.85;
1544
+ translateY += 3;
1545
+ }
1546
+ return { opacity, translateY, translateX, scale };
1547
+ }, [entrance, hover, click]);
1548
+ useEffect(() => {
1549
+ if (!elementRef.current) return;
1550
+ const observer = new IntersectionObserver(
1551
+ (entries) => {
1552
+ entries.forEach((entry) => {
1553
+ if (entry.isIntersecting) {
1554
+ setTimeout(() => {
1555
+ setState((prev) => ({ ...prev, isVisible: true }));
1556
+ }, delay);
1557
+ }
1558
+ });
1559
+ },
1560
+ { threshold }
1561
+ );
1562
+ observer.observe(elementRef.current);
1563
+ return () => {
1564
+ observer.disconnect();
1565
+ };
1566
+ }, [delay, threshold]);
1567
+ useEffect(() => {
1568
+ if (!hover || !elementRef.current) return;
1569
+ const element = elementRef.current;
1570
+ const handleMouseEnter = () => {
1571
+ setState((prev) => ({ ...prev, isHovered: true }));
1572
+ };
1573
+ const handleMouseLeave = () => {
1574
+ setState((prev) => ({ ...prev, isHovered: false }));
1575
+ };
1576
+ element.addEventListener("mouseenter", handleMouseEnter);
1577
+ element.addEventListener("mouseleave", handleMouseLeave);
1578
+ return () => {
1579
+ element.removeEventListener("mouseenter", handleMouseEnter);
1580
+ element.removeEventListener("mouseleave", handleMouseLeave);
1581
+ };
1582
+ }, [hover]);
1583
+ useEffect(() => {
1584
+ if (!click || !elementRef.current) return;
1585
+ const element = elementRef.current;
1586
+ const handleClick = () => {
1587
+ setState((prev) => ({ ...prev, isClicked: true }));
1588
+ setTimeout(() => {
1589
+ setState((prev) => ({ ...prev, isClicked: false }));
1590
+ }, 300);
1591
+ };
1592
+ element.addEventListener("click", handleClick);
1593
+ return () => {
1594
+ element.removeEventListener("click", handleClick);
1595
+ };
1596
+ }, [click]);
1597
+ useEffect(() => {
1598
+ setState((prev) => {
1599
+ const { opacity, translateY, translateX, scale } = calculateMotionValues(prev);
1600
+ if (prev.opacity === opacity && prev.translateY === translateY && prev.translateX === translateX && prev.scale === scale) {
1601
+ return prev;
1602
+ }
1603
+ return { ...prev, opacity, translateY, translateX, scale };
1604
+ });
1605
+ }, [state.isVisible, state.isHovered, state.isClicked, calculateMotionValues]);
1606
+ useEffect(() => {
1607
+ if (!autoLanguageSync) return;
1608
+ const handleLanguageChange = () => {
1609
+ setState((prev) => ({ ...prev, isVisible: false }));
1610
+ setTimeout(() => {
1611
+ setState((prev) => ({ ...prev, isVisible: true }));
1612
+ }, 100);
1613
+ };
1614
+ window.addEventListener("storage", handleLanguageChange);
1615
+ return () => {
1616
+ window.removeEventListener("storage", handleLanguageChange);
1617
+ };
1618
+ }, [autoLanguageSync]);
1619
+ const motionStyle = useMemo(() => ({
1620
+ opacity: state.opacity,
1621
+ transform: `translate(${state.translateX}px, ${state.translateY}px) scale(${state.scale})`,
1622
+ transition: `all ${duration}ms ease-out`,
1623
+ // CSS transition과 충돌 방지
1624
+ pointerEvents: "auto",
1625
+ // 강제로 스타일 적용
1626
+ willChange: "transform, opacity"
1627
+ }), [state.opacity, state.translateX, state.translateY, state.scale, duration]);
1628
+ return {
1629
+ ref: elementRef,
1630
+ style: motionStyle,
1631
+ isVisible: state.isVisible,
1632
+ isHovered: state.isHovered,
1633
+ isClicked: state.isClicked
1634
+ };
1635
+ }
1636
+ function getInitialStyle(type, distance) {
1637
+ switch (type) {
1638
+ case "slideUp":
1639
+ return { opacity: 0, transform: `translateY(${distance}px)` };
1640
+ case "slideLeft":
1641
+ return { opacity: 0, transform: `translateX(${distance}px)` };
1642
+ case "slideRight":
1643
+ return { opacity: 0, transform: `translateX(-${distance}px)` };
1644
+ case "scaleIn":
1645
+ return { opacity: 0, transform: "scale(0)" };
1646
+ case "bounceIn":
1647
+ return { opacity: 0, transform: "scale(0)" };
1648
+ case "fadeIn":
1649
+ default:
1650
+ return { opacity: 0, transform: "none" };
1651
+ }
1652
+ }
1653
+ function getVisibleStyle() {
1654
+ return { opacity: 1, transform: "none" };
1655
+ }
1656
+ function getEasingForType(type, easing2) {
1657
+ if (easing2) return easing2;
1658
+ if (type === "bounceIn") return "cubic-bezier(0.34, 1.56, 0.64, 1)";
1659
+ return "ease-out";
1660
+ }
1661
+ function useUnifiedMotion(options) {
1662
+ const {
1663
+ type,
1664
+ duration = 600,
1665
+ autoStart = false,
1666
+ delay = 0,
1667
+ easing: easing2,
1668
+ threshold = 0.1,
1669
+ triggerOnce = true,
1670
+ distance = 50,
1671
+ onComplete,
1672
+ onStart,
1673
+ onStop,
1674
+ onReset
1675
+ } = options;
1676
+ const resolvedEasing = getEasingForType(type, easing2);
1677
+ const ref = useRef(null);
1678
+ const [isVisible, setIsVisible] = useState(false);
1679
+ const [isAnimating, setIsAnimating] = useState(false);
1680
+ const [progress, setProgress] = useState(0);
1681
+ const observerRef = useRef(null);
1682
+ const timeoutRef = useRef(null);
1683
+ const startRef = useRef(() => {
1684
+ });
1685
+ const start = useCallback(() => {
1686
+ if (isAnimating) return;
1687
+ setIsAnimating(true);
1688
+ setProgress(0);
1689
+ onStart?.();
1690
+ timeoutRef.current = window.setTimeout(() => {
1691
+ setIsVisible(true);
1692
+ setProgress(1);
1693
+ setIsAnimating(false);
1694
+ onComplete?.();
1695
+ }, delay);
1696
+ }, [isAnimating, delay, onStart, onComplete]);
1697
+ startRef.current = start;
1698
+ const stop = useCallback(() => {
1699
+ if (timeoutRef.current) {
1700
+ clearTimeout(timeoutRef.current);
1701
+ timeoutRef.current = null;
1702
+ }
1703
+ setIsAnimating(false);
1704
+ onStop?.();
1705
+ }, [onStop]);
1706
+ const reset = useCallback(() => {
1707
+ stop();
1708
+ setIsVisible(false);
1709
+ setProgress(0);
1710
+ onReset?.();
1711
+ }, [stop, onReset]);
1712
+ useEffect(() => {
1713
+ if (!ref.current || !autoStart) return;
1714
+ observerRef.current = new IntersectionObserver(
1715
+ (entries) => {
1716
+ entries.forEach((entry) => {
1717
+ if (entry.isIntersecting) {
1718
+ startRef.current();
1719
+ if (triggerOnce) {
1720
+ observerRef.current?.disconnect();
1721
+ }
1722
+ }
1723
+ });
1724
+ },
1725
+ { threshold }
1726
+ );
1727
+ observerRef.current.observe(ref.current);
1728
+ return () => {
1729
+ observerRef.current?.disconnect();
1730
+ };
1731
+ }, [autoStart, threshold, triggerOnce]);
1732
+ useEffect(() => {
1733
+ return () => stop();
1734
+ }, [stop]);
1735
+ const style = useMemo(() => {
1736
+ const base = isVisible ? getVisibleStyle() : getInitialStyle(type, distance);
1737
+ return {
1738
+ ...base,
1739
+ transition: `all ${duration}ms ${resolvedEasing}`,
1740
+ "--motion-delay": `${delay}ms`,
1741
+ "--motion-duration": `${duration}ms`,
1742
+ "--motion-easing": resolvedEasing,
1743
+ "--motion-progress": `${progress}`
1744
+ };
1745
+ }, [isVisible, type, distance, duration, resolvedEasing, delay, progress]);
1746
+ return {
1747
+ ref,
1748
+ isVisible,
1749
+ isAnimating,
1750
+ style,
1751
+ progress,
1752
+ start,
1753
+ stop,
1754
+ reset
1755
+ };
1756
+ }
1757
+ function useFadeIn(options = {}) {
1758
+ const {
1759
+ delay = 0,
1760
+ duration = 700,
1761
+ threshold = 0.1,
1762
+ triggerOnce = true,
1763
+ easing: easing2 = "ease-out",
1764
+ autoStart = true,
1765
+ initialOpacity = 0,
1766
+ targetOpacity = 1,
1767
+ onComplete,
1768
+ onStart,
1769
+ onStop,
1770
+ onReset
1771
+ } = options;
1772
+ const ref = useRef(null);
1773
+ const [isVisible, setIsVisible] = useState(false);
1774
+ const [isAnimating, setIsAnimating] = useState(false);
1775
+ const [progress, setProgress] = useState(0);
1776
+ const [nodeReady, setNodeReady] = useState(false);
1777
+ const observerRef = useRef(null);
1778
+ const motionRef = useRef(null);
1779
+ const timeoutRef = useRef(null);
1780
+ const startRef = useRef(() => {
1781
+ });
1782
+ useEffect(() => {
1783
+ if (nodeReady) return;
1784
+ if (ref.current) {
1785
+ setNodeReady(true);
1786
+ return;
1787
+ }
1788
+ const id = setInterval(() => {
1789
+ if (ref.current) {
1790
+ setNodeReady(true);
1791
+ clearInterval(id);
1792
+ }
1793
+ }, 50);
1794
+ return () => clearInterval(id);
1795
+ }, [nodeReady]);
1796
+ const start = useCallback(() => {
1797
+ if (isAnimating) return;
1798
+ setIsAnimating(true);
1799
+ setProgress(0);
1800
+ onStart?.();
1801
+ if (delay > 0) {
1802
+ timeoutRef.current = window.setTimeout(() => {
1803
+ setIsVisible(true);
1804
+ setProgress(1);
1805
+ setIsAnimating(false);
1806
+ onComplete?.();
1807
+ }, delay);
1808
+ } else {
1809
+ setIsVisible(true);
1810
+ setProgress(1);
1811
+ setIsAnimating(false);
1812
+ onComplete?.();
1813
+ }
1814
+ }, [delay, isAnimating, onStart, onComplete]);
1815
+ startRef.current = start;
1816
+ const stop = useCallback(() => {
1817
+ if (timeoutRef.current) {
1818
+ clearTimeout(timeoutRef.current);
1819
+ timeoutRef.current = null;
1820
+ }
1821
+ if (motionRef.current) {
1822
+ cancelAnimationFrame(motionRef.current);
1823
+ motionRef.current = null;
1824
+ }
1825
+ setIsAnimating(false);
1826
+ onStop?.();
1827
+ }, [onStop]);
1828
+ const reset = useCallback(() => {
1829
+ stop();
1830
+ setIsVisible(false);
1831
+ setProgress(0);
1832
+ onReset?.();
1833
+ }, [stop, onReset]);
1834
+ useEffect(() => {
1835
+ if (!ref.current || !autoStart) return;
1836
+ observerRef.current = new IntersectionObserver(
1837
+ (entries) => {
1838
+ entries.forEach((entry) => {
1839
+ if (entry.isIntersecting) {
1840
+ startRef.current();
1841
+ if (triggerOnce) {
1842
+ observerRef.current?.disconnect();
1843
+ }
1844
+ }
1845
+ });
1846
+ },
1847
+ { threshold }
1848
+ );
1849
+ observerRef.current.observe(ref.current);
1850
+ return () => {
1851
+ if (observerRef.current) {
1852
+ observerRef.current.disconnect();
1853
+ }
1854
+ };
1855
+ }, [autoStart, threshold, triggerOnce, nodeReady]);
1856
+ useEffect(() => {
1857
+ if (!autoStart) {
1858
+ start();
1859
+ }
1860
+ }, [autoStart, start]);
1861
+ useEffect(() => {
1862
+ return () => {
1863
+ stop();
1864
+ };
1865
+ }, [stop]);
1866
+ const style = useMemo(() => ({
1867
+ opacity: isVisible ? targetOpacity : initialOpacity,
1868
+ transition: `opacity ${duration}ms ${easing2}`,
1869
+ "--motion-delay": `${delay}ms`,
1870
+ "--motion-duration": `${duration}ms`,
1871
+ "--motion-easing": easing2,
1872
+ "--motion-progress": `${progress}`
1873
+ }), [isVisible, targetOpacity, initialOpacity, duration, easing2, delay, progress]);
1874
+ return {
1875
+ ref,
1876
+ isVisible,
1877
+ isAnimating,
1878
+ style,
1879
+ progress,
1880
+ start,
1881
+ stop,
1882
+ reset
1883
+ };
1884
+ }
1885
+ function useSlideUp(options = {}) {
1886
+ const {
1887
+ delay = 0,
1888
+ duration = 700,
1889
+ threshold = 0.1,
1890
+ triggerOnce = true,
1891
+ easing: easing2 = "ease-out",
1892
+ autoStart = true,
1893
+ direction = "up",
1894
+ distance = 50,
1895
+ onComplete,
1896
+ onStart,
1897
+ onStop,
1898
+ onReset
1899
+ } = options;
1900
+ const ref = useRef(null);
1901
+ const [isVisible, setIsVisible] = useState(false);
1902
+ const [isAnimating, setIsAnimating] = useState(false);
1903
+ const [progress, setProgress] = useState(0);
1904
+ const [nodeReady, setNodeReady] = useState(false);
1905
+ const observerRef = useRef(null);
1906
+ const timeoutRef = useRef(null);
1907
+ const startRef = useRef(() => {
1908
+ });
1909
+ useEffect(() => {
1910
+ if (nodeReady) return;
1911
+ if (ref.current) {
1912
+ setNodeReady(true);
1913
+ return;
1914
+ }
1915
+ const id = setInterval(() => {
1916
+ if (ref.current) {
1917
+ setNodeReady(true);
1918
+ clearInterval(id);
1919
+ }
1920
+ }, 50);
1921
+ return () => clearInterval(id);
1922
+ }, [nodeReady]);
1923
+ const getInitialTransform = useCallback(() => {
1924
+ switch (direction) {
1925
+ case "up":
1926
+ return `translateY(${distance}px)`;
1927
+ case "down":
1928
+ return `translateY(-${distance}px)`;
1929
+ case "left":
1930
+ return `translateX(${distance}px)`;
1931
+ case "right":
1932
+ return `translateX(-${distance}px)`;
1933
+ default:
1934
+ return `translateY(${distance}px)`;
1935
+ }
1936
+ }, [direction, distance]);
1937
+ const start = useCallback(() => {
1938
+ if (isAnimating) return;
1939
+ setIsAnimating(true);
1940
+ setProgress(0);
1941
+ onStart?.();
1942
+ if (delay > 0) {
1943
+ timeoutRef.current = window.setTimeout(() => {
1944
+ setIsVisible(true);
1945
+ setProgress(1);
1946
+ setIsAnimating(false);
1947
+ onComplete?.();
1948
+ }, delay);
1949
+ } else {
1950
+ setIsVisible(true);
1951
+ setProgress(1);
1952
+ setIsAnimating(false);
1953
+ onComplete?.();
1954
+ }
1955
+ }, [delay, isAnimating, onStart, onComplete]);
1956
+ startRef.current = start;
1957
+ const stop = useCallback(() => {
1958
+ if (timeoutRef.current) {
1959
+ clearTimeout(timeoutRef.current);
1960
+ timeoutRef.current = null;
1961
+ }
1962
+ setIsAnimating(false);
1963
+ onStop?.();
1964
+ }, [onStop]);
1965
+ const reset = useCallback(() => {
1966
+ stop();
1967
+ setIsVisible(false);
1968
+ setProgress(0);
1969
+ onReset?.();
1970
+ }, [stop, onReset]);
1971
+ useEffect(() => {
1972
+ if (!ref.current || !autoStart) return;
1973
+ observerRef.current = new IntersectionObserver(
1974
+ (entries) => {
1975
+ entries.forEach((entry) => {
1976
+ if (entry.isIntersecting) {
1977
+ startRef.current();
1978
+ if (triggerOnce) {
1979
+ observerRef.current?.disconnect();
1980
+ }
1981
+ }
1982
+ });
1983
+ },
1984
+ { threshold }
1985
+ );
1986
+ observerRef.current.observe(ref.current);
1987
+ return () => {
1988
+ if (observerRef.current) {
1989
+ observerRef.current.disconnect();
1990
+ }
1991
+ };
1992
+ }, [autoStart, threshold, triggerOnce, nodeReady]);
1993
+ useEffect(() => {
1994
+ if (!autoStart) {
1995
+ start();
1996
+ }
1997
+ }, [autoStart, start]);
1998
+ useEffect(() => {
1999
+ return () => {
2000
+ stop();
2001
+ };
2002
+ }, [stop]);
2003
+ const initialTransform = useMemo(() => getInitialTransform(), [getInitialTransform]);
2004
+ const finalTransform = useMemo(() => {
2005
+ return direction === "left" || direction === "right" ? "translateX(0)" : "translateY(0)";
2006
+ }, [direction]);
2007
+ const style = useMemo(() => ({
2008
+ opacity: isVisible ? 1 : 0,
2009
+ transform: isVisible ? finalTransform : initialTransform,
2010
+ transition: `opacity ${duration}ms ${easing2}, transform ${duration}ms ${easing2}`,
2011
+ "--motion-delay": `${delay}ms`,
2012
+ "--motion-duration": `${duration}ms`,
2013
+ "--motion-easing": easing2,
2014
+ "--motion-progress": `${progress}`,
2015
+ "--motion-direction": direction,
2016
+ "--motion-distance": `${distance}px`
2017
+ }), [isVisible, initialTransform, finalTransform, duration, easing2, delay, progress, direction, distance]);
2018
+ return {
2019
+ ref,
2020
+ isVisible,
2021
+ isAnimating,
2022
+ style,
2023
+ progress,
2024
+ start,
2025
+ stop,
2026
+ reset
2027
+ };
2028
+ }
2029
+
2030
+ // src/hooks/useSlideLeft.ts
2031
+ function useSlideLeft(options = {}) {
2032
+ return useSlideUp({ ...options, direction: "left" });
2033
+ }
2034
+
2035
+ // src/hooks/useSlideRight.ts
2036
+ function useSlideRight(options = {}) {
2037
+ return useSlideUp({ ...options, direction: "right" });
2038
+ }
2039
+ function useScaleIn(options = {}) {
2040
+ const {
2041
+ initialScale = 0,
2042
+ targetScale = 1,
2043
+ duration = 700,
2044
+ delay = 0,
2045
+ autoStart = true,
2046
+ easing: easing2 = "ease-out",
2047
+ threshold = 0.1,
2048
+ triggerOnce = true,
2049
+ onComplete,
2050
+ onStart,
2051
+ onStop,
2052
+ onReset
2053
+ } = options;
2054
+ const ref = useRef(null);
2055
+ const [scale, setScale] = useState(autoStart ? initialScale : targetScale);
2056
+ const [opacity, setOpacity] = useState(autoStart ? 0 : 1);
2057
+ const [isAnimating, setIsAnimating] = useState(false);
2058
+ const [isVisible, setIsVisible] = useState(autoStart ? false : true);
2059
+ const [progress, setProgress] = useState(autoStart ? 0 : 1);
2060
+ const observerRef = useRef(null);
2061
+ const timeoutRef = useRef(null);
2062
+ const startRef = useRef(() => {
2063
+ });
2064
+ const start = useCallback(() => {
2065
+ if (isAnimating) return;
2066
+ setIsAnimating(true);
2067
+ setScale(initialScale);
2068
+ setOpacity(0);
2069
+ setProgress(0);
2070
+ onStart?.();
2071
+ timeoutRef.current = window.setTimeout(() => {
2072
+ setProgress(1);
2073
+ setScale(targetScale);
2074
+ setOpacity(1);
2075
+ setIsVisible(true);
2076
+ setIsAnimating(false);
2077
+ onComplete?.();
2078
+ }, delay);
2079
+ }, [delay, initialScale, targetScale, isAnimating, onStart, onComplete]);
2080
+ startRef.current = start;
2081
+ const stop = useCallback(() => {
2082
+ if (timeoutRef.current) {
2083
+ clearTimeout(timeoutRef.current);
2084
+ timeoutRef.current = null;
2085
+ }
2086
+ setIsAnimating(false);
2087
+ onStop?.();
2088
+ }, [onStop]);
2089
+ const reset = useCallback(() => {
2090
+ stop();
2091
+ setScale(initialScale);
2092
+ setOpacity(0);
2093
+ setProgress(0);
2094
+ setIsVisible(false);
2095
+ if (ref.current) {
2096
+ const element = ref.current;
2097
+ element.style.transition = "none";
2098
+ element.style.opacity = "0";
2099
+ element.style.transform = `scale(${initialScale})`;
2100
+ requestAnimationFrame(() => {
2101
+ element.style.transition = "";
2102
+ });
2103
+ }
2104
+ onReset?.();
2105
+ }, [stop, initialScale, onReset]);
2106
+ useEffect(() => {
2107
+ if (!ref.current || !autoStart) return;
2108
+ observerRef.current = new IntersectionObserver(
2109
+ (entries) => {
2110
+ entries.forEach((entry) => {
2111
+ if (entry.isIntersecting) {
2112
+ startRef.current();
2113
+ if (triggerOnce) {
2114
+ observerRef.current?.disconnect();
2115
+ }
2116
+ }
2117
+ });
2118
+ },
2119
+ { threshold }
2120
+ );
2121
+ observerRef.current.observe(ref.current);
2122
+ return () => {
2123
+ if (observerRef.current) {
2124
+ observerRef.current.disconnect();
2125
+ }
2126
+ };
2127
+ }, [autoStart, threshold, triggerOnce]);
2128
+ useEffect(() => {
2129
+ return () => {
2130
+ stop();
2131
+ };
2132
+ }, [stop]);
2133
+ const style = useMemo(() => ({
2134
+ transform: `scale(${scale})`,
2135
+ opacity,
2136
+ transition: `all ${duration}ms ${easing2}`,
2137
+ "--motion-delay": `${delay}ms`,
2138
+ "--motion-duration": `${duration}ms`,
2139
+ "--motion-easing": easing2,
2140
+ "--motion-progress": `${progress}`
2141
+ }), [scale, opacity, duration, easing2, delay, progress]);
2142
+ return {
2143
+ ref,
2144
+ isVisible,
2145
+ isAnimating,
2146
+ style,
2147
+ progress,
2148
+ start,
2149
+ reset,
2150
+ stop
2151
+ };
2152
+ }
2153
+ function useBounceIn(options = {}) {
2154
+ const {
2155
+ duration = 600,
2156
+ delay = 0,
2157
+ autoStart = true,
2158
+ intensity = 0.3,
2159
+ threshold = 0.1,
2160
+ triggerOnce = true,
2161
+ easing: easing2 = "cubic-bezier(0.34, 1.56, 0.64, 1)",
2162
+ // 바운스 이징
2163
+ onComplete,
2164
+ onStart,
2165
+ onStop,
2166
+ onReset
2167
+ } = options;
2168
+ const ref = useRef(null);
2169
+ const [scale, setScale] = useState(autoStart ? 0 : 1);
2170
+ const [opacity, setOpacity] = useState(autoStart ? 0 : 1);
2171
+ const [isAnimating, setIsAnimating] = useState(false);
2172
+ const [isVisible, setIsVisible] = useState(autoStart ? false : true);
2173
+ const [progress, setProgress] = useState(autoStart ? 0 : 1);
2174
+ const observerRef = useRef(null);
2175
+ const timeoutRef = useRef(null);
2176
+ const bounceTimeoutRef = useRef(null);
2177
+ const startRef = useRef(() => {
2178
+ });
2179
+ const start = useCallback(() => {
2180
+ if (isAnimating) return;
2181
+ setIsAnimating(true);
2182
+ setScale(0);
2183
+ setOpacity(0);
2184
+ setProgress(0);
2185
+ onStart?.();
2186
+ timeoutRef.current = window.setTimeout(() => {
2187
+ setProgress(0.5);
2188
+ setScale(1 + intensity);
2189
+ setOpacity(1);
2190
+ bounceTimeoutRef.current = window.setTimeout(() => {
2191
+ setProgress(1);
2192
+ setScale(1);
2193
+ setIsVisible(true);
2194
+ setIsAnimating(false);
2195
+ onComplete?.();
2196
+ }, duration * 0.3);
2197
+ }, delay);
2198
+ }, [delay, intensity, duration, isAnimating, onStart, onComplete]);
2199
+ startRef.current = start;
2200
+ const stop = useCallback(() => {
2201
+ if (timeoutRef.current) {
2202
+ clearTimeout(timeoutRef.current);
2203
+ timeoutRef.current = null;
2204
+ }
2205
+ if (bounceTimeoutRef.current) {
2206
+ clearTimeout(bounceTimeoutRef.current);
2207
+ bounceTimeoutRef.current = null;
2208
+ }
2209
+ setIsAnimating(false);
2210
+ onStop?.();
2211
+ }, [onStop]);
2212
+ const reset = useCallback(() => {
2213
+ stop();
2214
+ setScale(0);
2215
+ setOpacity(0);
2216
+ setProgress(0);
2217
+ setIsVisible(false);
2218
+ if (ref.current) {
2219
+ const element = ref.current;
2220
+ element.style.transition = "none";
2221
+ element.style.opacity = "0";
2222
+ element.style.transform = "scale(0)";
2223
+ requestAnimationFrame(() => {
2224
+ element.style.transition = "";
2225
+ });
2226
+ }
2227
+ onReset?.();
2228
+ }, [stop, onReset]);
2229
+ useEffect(() => {
2230
+ if (!ref.current || !autoStart) return;
2231
+ observerRef.current = new IntersectionObserver(
2232
+ (entries) => {
2233
+ entries.forEach((entry) => {
2234
+ if (entry.isIntersecting) {
2235
+ startRef.current();
2236
+ if (triggerOnce) {
2237
+ observerRef.current?.disconnect();
2238
+ }
2239
+ }
2240
+ });
2241
+ },
2242
+ { threshold }
2243
+ );
2244
+ observerRef.current.observe(ref.current);
2245
+ return () => {
2246
+ if (observerRef.current) {
2247
+ observerRef.current.disconnect();
2248
+ }
2249
+ };
2250
+ }, [autoStart, threshold, triggerOnce]);
2251
+ useEffect(() => {
2252
+ return () => {
2253
+ stop();
2254
+ };
2255
+ }, [stop]);
2256
+ const style = useMemo(() => ({
2257
+ transform: `scale(${scale})`,
2258
+ opacity,
2259
+ transition: `all ${duration}ms ${easing2}`,
2260
+ "--motion-delay": `${delay}ms`,
2261
+ "--motion-duration": `${duration}ms`,
2262
+ "--motion-easing": easing2,
2263
+ "--motion-progress": `${progress}`
2264
+ }), [scale, opacity, duration, easing2, delay, progress]);
2265
+ return {
2266
+ ref,
2267
+ isVisible,
2268
+ isAnimating,
2269
+ style,
2270
+ progress,
2271
+ start,
2272
+ reset,
2273
+ stop
2274
+ };
2275
+ }
2276
+
2277
+ // src/utils/easing.ts
2278
+ var linear = (t) => t;
2279
+ var easeIn = (t) => t * t;
2280
+ var easeOut = (t) => 1 - (1 - t) * (1 - t);
2281
+ var easeInOut = (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
2282
+ var easeInQuad = (t) => t * t;
2283
+ var easeOutQuad = (t) => 1 - (1 - t) * (1 - t);
2284
+ var easeInOutQuad = (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
2285
+ var easeInCubic = (t) => t * t * t;
2286
+ var easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
2287
+ var easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
2288
+ var easeInQuart = (t) => t * t * t * t;
2289
+ var easeOutQuart = (t) => 1 - Math.pow(1 - t, 4);
2290
+ var easeInOutQuart = (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
2291
+ var easeInQuint = (t) => t * t * t * t * t;
2292
+ var easeOutQuint = (t) => 1 - Math.pow(1 - t, 5);
2293
+ var easeInOutQuint = (t) => t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2;
2294
+ var easeInSine = (t) => 1 - Math.cos(t * Math.PI / 2);
2295
+ var easeOutSine = (t) => Math.sin(t * Math.PI / 2);
2296
+ var easeInOutSine = (t) => -(Math.cos(Math.PI * t) - 1) / 2;
2297
+ var easeInExpo = (t) => t === 0 ? 0 : Math.pow(2, 10 * t - 10);
2298
+ var easeOutExpo = (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
2299
+ var easeInOutExpo = (t) => {
2300
+ if (t === 0) return 0;
2301
+ if (t === 1) return 1;
2302
+ if (t < 0.5) return Math.pow(2, 20 * t - 10) / 2;
2303
+ return (2 - Math.pow(2, -20 * t + 10)) / 2;
2304
+ };
2305
+ var easeInCirc = (t) => 1 - Math.sqrt(1 - Math.pow(t, 2));
2306
+ var easeOutCirc = (t) => Math.sqrt(1 - Math.pow(t - 1, 2));
2307
+ var easeInOutCirc = (t) => {
2308
+ if (t < 0.5) return (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2;
2309
+ return (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
2310
+ };
2311
+ var easeInBounce = (t) => 1 - easeOutBounce(1 - t);
2312
+ var easeOutBounce = (t) => {
2313
+ const n1 = 7.5625;
2314
+ const d1 = 2.75;
2315
+ if (t < 1 / d1) {
2316
+ return n1 * t * t;
2317
+ } else if (t < 2 / d1) {
2318
+ return n1 * (t -= 1.5 / d1) * t + 0.75;
2319
+ } else if (t < 2.5 / d1) {
2320
+ return n1 * (t -= 2.25 / d1) * t + 0.9375;
2321
+ } else {
2322
+ return n1 * (t -= 2.625 / d1) * t + 0.984375;
2323
+ }
2324
+ };
2325
+ var easeInOutBounce = (t) => {
2326
+ if (t < 0.5) return (1 - easeOutBounce(1 - 2 * t)) / 2;
2327
+ return (1 + easeOutBounce(2 * t - 1)) / 2;
2328
+ };
2329
+ var easeInBack = (t) => {
2330
+ const c1 = 1.70158;
2331
+ const c3 = c1 + 1;
2332
+ return c3 * t * t * t - c1 * t * t;
2333
+ };
2334
+ var easeOutBack = (t) => {
2335
+ const c1 = 1.70158;
2336
+ const c3 = c1 + 1;
2337
+ return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
2338
+ };
2339
+ var easeInOutBack = (t) => {
2340
+ const c1 = 1.70158;
2341
+ const c2 = c1 * 1.525;
2342
+ if (t < 0.5) {
2343
+ return Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2) / 2;
2344
+ } else {
2345
+ return (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
2346
+ }
2347
+ };
2348
+ var easeInElastic = (t) => {
2349
+ const c4 = 2 * Math.PI / 3;
2350
+ if (t === 0) return 0;
2351
+ if (t === 1) return 1;
2352
+ return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 0.75) * c4);
2353
+ };
2354
+ var easeOutElastic = (t) => {
2355
+ const c4 = 2 * Math.PI / 3;
2356
+ if (t === 0) return 0;
2357
+ if (t === 1) return 1;
2358
+ return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
2359
+ };
2360
+ var easeInOutElastic = (t) => {
2361
+ const c5 = 2 * Math.PI / 4.5;
2362
+ if (t === 0) return 0;
2363
+ if (t === 1) return 1;
2364
+ if (t < 0.5) {
2365
+ return -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2;
2366
+ } else {
2367
+ return Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5) / 2 + 1;
2368
+ }
2369
+ };
2370
+ var pulse = (t) => Math.sin(t * Math.PI) * 0.5 + 0.5;
2371
+ var pulseSmooth = (t) => Math.sin(t * Math.PI * 2) * 0.3 + 0.7;
2372
+ var skeletonWave = (t) => Math.sin((t - 0.5) * Math.PI * 2) * 0.5 + 0.5;
2373
+ var blink = (t) => t < 0.5 ? 1 : 0;
2374
+ var easing = {
2375
+ linear,
2376
+ easeIn,
2377
+ easeOut,
2378
+ easeInOut,
2379
+ easeInQuad,
2380
+ easeOutQuad,
2381
+ easeInOutQuad,
2382
+ easeInCubic,
2383
+ easeOutCubic,
2384
+ easeInOutCubic,
2385
+ easeInQuart,
2386
+ easeOutQuart,
2387
+ easeInOutQuart,
2388
+ easeInQuint,
2389
+ easeOutQuint,
2390
+ easeInOutQuint,
2391
+ easeInSine,
2392
+ easeOutSine,
2393
+ easeInOutSine,
2394
+ easeInExpo,
2395
+ easeOutExpo,
2396
+ easeInOutExpo,
2397
+ easeInCirc,
2398
+ easeOutCirc,
2399
+ easeInOutCirc,
2400
+ easeInBounce,
2401
+ easeOutBounce,
2402
+ easeInOutBounce,
2403
+ easeInBack,
2404
+ easeOutBack,
2405
+ easeInOutBack,
2406
+ easeInElastic,
2407
+ easeOutElastic,
2408
+ easeInOutElastic,
2409
+ pulse,
2410
+ pulseSmooth,
2411
+ skeletonWave,
2412
+ blink
2413
+ };
2414
+ function isValidEasing(easingName) {
2415
+ return easingName in easing;
2416
+ }
2417
+ function getEasing(easingName) {
2418
+ if (typeof easingName === "function") {
2419
+ return easingName;
2420
+ }
2421
+ if (typeof easingName === "string") {
2422
+ if (easingName in easing) {
2423
+ return easing[easingName];
2424
+ }
2425
+ if (easingName === "bounce") {
2426
+ return easing.easeOutBounce;
2427
+ }
2428
+ if (process.env.NODE_ENV === "development") {
2429
+ console.warn(`[HUA Motion] Unknown easing "${easingName}", using default "easeOut".`);
2430
+ }
2431
+ }
2432
+ return easeOut;
2433
+ }
2434
+ function applyEasing(t, easingName) {
2435
+ const easingFn = getEasing(easingName);
2436
+ return easingFn(t);
2437
+ }
2438
+ function safeApplyEasing(t, easingName) {
2439
+ try {
2440
+ const easingFn = getEasing(easingName);
2441
+ return easingFn(t);
2442
+ } catch (err) {
2443
+ if (process.env.NODE_ENV === "development") {
2444
+ console.error(`[HUA Motion] Failed to apply easing "${easingName}":`, err);
2445
+ }
2446
+ return easeOut(t);
2447
+ }
2448
+ }
2449
+ function getAvailableEasings() {
2450
+ return Object.keys(easing);
2451
+ }
2452
+ function isEasingFunction(value) {
2453
+ return typeof value === "function";
2454
+ }
2455
+ var easingPresets = {
2456
+ default: "easeOut",
2457
+ smooth: "easeInOutCubic",
2458
+ fast: "easeOutQuad",
2459
+ slow: "easeInOutSine",
2460
+ bouncy: "easeOutBounce",
2461
+ elastic: "easeOutElastic",
2462
+ fade: "easeInOut",
2463
+ scale: "easeOutBack"
2464
+ };
2465
+ function getPresetEasing(preset) {
2466
+ const easingName = easingPresets[preset];
2467
+ return getEasing(easingName);
2468
+ }
2469
+
2470
+ // src/hooks/usePulse.ts
2471
+ function usePulse(options = {}) {
2472
+ const {
2473
+ duration = 3e3,
2474
+ intensity = 1,
2475
+ repeatCount = Infinity,
2476
+ repeatDelay = 0,
2477
+ autoStart = false
2478
+ } = options;
2479
+ const ref = useRef(null);
2480
+ const [isAnimating, setIsAnimating] = useState(false);
2481
+ const [isVisible, setIsVisible] = useState(true);
2482
+ const [progress, setProgress] = useState(0);
2483
+ const motionRef = useRef(null);
2484
+ const easingFn = useMemo(() => getEasing("easeInOut"), []);
2485
+ const start = useCallback(() => {
2486
+ if (!ref.current) return;
2487
+ const element = ref.current;
2488
+ let currentRepeat = 0;
2489
+ setIsAnimating(true);
2490
+ const animate = (startTime) => {
2491
+ const updateMotion = (currentTime) => {
2492
+ const elapsed = currentTime - startTime;
2493
+ const rawProgress = Math.min(elapsed / duration, 1);
2494
+ const easedProgress = easingFn(rawProgress);
2495
+ const finalProgress = currentRepeat % 2 === 1 ? 1 - easedProgress : easedProgress;
2496
+ const opacity = 0.3 + 0.7 * finalProgress * intensity;
2497
+ element.style.opacity = opacity.toString();
2498
+ setProgress(rawProgress);
2499
+ if (rawProgress < 1) {
2500
+ motionRef.current = requestAnimationFrame(updateMotion);
2501
+ } else {
2502
+ currentRepeat++;
2503
+ if (repeatCount === Infinity || currentRepeat < repeatCount * 2) {
2504
+ if (repeatDelay > 0) {
2505
+ const delayTimeout = window.setTimeout(() => {
2506
+ motionRef.current = requestAnimationFrame(() => animate(performance.now()));
2507
+ }, repeatDelay);
2508
+ motionRef.current = delayTimeout;
2509
+ } else {
2510
+ motionRef.current = requestAnimationFrame(() => animate(performance.now()));
2511
+ }
2512
+ } else {
2513
+ setIsAnimating(false);
2514
+ }
2515
+ }
2516
+ };
2517
+ motionRef.current = requestAnimationFrame(updateMotion);
2518
+ };
2519
+ animate(performance.now());
2520
+ }, [duration, intensity, repeatCount, repeatDelay, easingFn]);
2521
+ const stop = useCallback(() => {
2522
+ if (motionRef.current) {
2523
+ cancelAnimationFrame(motionRef.current);
2524
+ clearTimeout(motionRef.current);
2525
+ motionRef.current = null;
2526
+ }
2527
+ setIsAnimating(false);
2528
+ }, []);
2529
+ const reset = useCallback(() => {
2530
+ if (motionRef.current) {
2531
+ cancelAnimationFrame(motionRef.current);
2532
+ clearTimeout(motionRef.current);
2533
+ motionRef.current = null;
2534
+ }
2535
+ setIsAnimating(false);
2536
+ setProgress(0);
2537
+ if (ref.current) {
2538
+ const element = ref.current;
2539
+ element.style.transition = "none";
2540
+ element.style.opacity = "1";
2541
+ requestAnimationFrame(() => {
2542
+ element.style.transition = "";
2543
+ });
2544
+ }
2545
+ }, []);
2546
+ useEffect(() => {
2547
+ if (autoStart) {
2548
+ start();
2549
+ }
2550
+ }, [autoStart, start]);
2551
+ useEffect(() => {
2552
+ return () => {
2553
+ if (motionRef.current) {
2554
+ cancelAnimationFrame(motionRef.current);
2555
+ clearTimeout(motionRef.current);
2556
+ }
2557
+ };
2558
+ }, []);
2559
+ const style = useMemo(() => ({
2560
+ opacity: isAnimating ? 0.3 + 0.7 * progress * intensity : 1,
2561
+ transition: isAnimating ? "none" : "opacity 0.3s ease-in-out"
2562
+ }), [isAnimating, progress, intensity]);
2563
+ return {
2564
+ ref,
2565
+ isVisible,
2566
+ isAnimating,
2567
+ style,
2568
+ progress,
2569
+ start,
2570
+ stop,
2571
+ reset
2572
+ };
2573
+ }
2574
+ function useSpringMotion(options) {
2575
+ const {
2576
+ from,
2577
+ to,
2578
+ mass = 1,
2579
+ stiffness = 100,
2580
+ damping = 10,
2581
+ restDelta = 0.01,
2582
+ restSpeed = 0.01,
2583
+ onComplete,
2584
+ enabled = true,
2585
+ autoStart = false
2586
+ } = options;
2587
+ const ref = useRef(null);
2588
+ const [springState, setSpringState] = useState({
2589
+ value: from,
2590
+ velocity: 0,
2591
+ isAnimating: false
2592
+ });
2593
+ const [isVisible, setIsVisible] = useState(true);
2594
+ const [progress, setProgress] = useState(0);
2595
+ const motionRef = useRef(null);
2596
+ const lastTimeRef = useRef(0);
2597
+ const calculateSpring = useCallback((currentValue, currentVelocity, targetValue, deltaTime) => {
2598
+ const displacement = currentValue - targetValue;
2599
+ const springForce = -stiffness * displacement;
2600
+ const dampingForce = -damping * currentVelocity;
2601
+ const totalForce = springForce + dampingForce;
2602
+ const acceleration = totalForce / mass;
2603
+ const newVelocity = currentVelocity + acceleration * deltaTime;
2604
+ const newValue = currentValue + newVelocity * deltaTime;
2605
+ return { value: newValue, velocity: newVelocity };
2606
+ }, [mass, stiffness, damping]);
2607
+ const animate = useCallback((currentTime) => {
2608
+ if (!enabled || !springState.isAnimating) return;
2609
+ const deltaTime = Math.min(currentTime - lastTimeRef.current, 16) / 1e3;
2610
+ lastTimeRef.current = currentTime;
2611
+ const { value, velocity } = calculateSpring(
2612
+ springState.value,
2613
+ springState.velocity,
2614
+ to,
2615
+ deltaTime
2616
+ );
2617
+ const range = Math.abs(to - from);
2618
+ const currentProgress = range > 0 ? Math.min(Math.abs(value - from) / range, 1) : 1;
2619
+ setProgress(currentProgress);
2620
+ const isAtRest = Math.abs(value - to) < restDelta && Math.abs(velocity) < restSpeed;
2621
+ if (isAtRest) {
2622
+ setSpringState({
2623
+ value: to,
2624
+ velocity: 0,
2625
+ isAnimating: false
2626
+ });
2627
+ setProgress(1);
2628
+ onComplete?.();
2629
+ return;
2630
+ }
2631
+ setSpringState({
2632
+ value,
2633
+ velocity,
2634
+ isAnimating: true
2635
+ });
2636
+ motionRef.current = requestAnimationFrame(animate);
2637
+ }, [enabled, springState.isAnimating, to, from, restDelta, restSpeed, onComplete, calculateSpring]);
2638
+ const start = useCallback(() => {
2639
+ if (springState.isAnimating) return;
2640
+ setSpringState((prev) => ({
2641
+ ...prev,
2642
+ isAnimating: true
2643
+ }));
2644
+ lastTimeRef.current = performance.now();
2645
+ motionRef.current = requestAnimationFrame(animate);
2646
+ }, [springState.isAnimating, animate]);
2647
+ const stop = useCallback(() => {
2648
+ if (motionRef.current) {
2649
+ cancelAnimationFrame(motionRef.current);
2650
+ motionRef.current = null;
2651
+ }
2652
+ setSpringState((prev) => ({
2653
+ ...prev,
2654
+ isAnimating: false
2655
+ }));
2656
+ }, []);
2657
+ const reset = useCallback(() => {
2658
+ stop();
2659
+ setSpringState({
2660
+ value: from,
2661
+ velocity: 0,
2662
+ isAnimating: false
2663
+ });
2664
+ setProgress(0);
2665
+ motionRef.current = null;
2666
+ }, [from, stop]);
2667
+ useEffect(() => {
2668
+ if (autoStart) {
2669
+ start();
2670
+ }
2671
+ }, [autoStart, start]);
2672
+ useEffect(() => {
2673
+ return () => {
2674
+ if (motionRef.current) {
2675
+ cancelAnimationFrame(motionRef.current);
2676
+ }
2677
+ };
2678
+ }, []);
2679
+ const style = useMemo(() => ({
2680
+ "--motion-progress": `${progress}`,
2681
+ "--motion-value": `${springState.value}`
2682
+ }), [progress, springState.value]);
2683
+ return {
2684
+ ref,
2685
+ isVisible,
2686
+ isAnimating: springState.isAnimating,
2687
+ style,
2688
+ progress,
2689
+ value: springState.value,
2690
+ velocity: springState.velocity,
2691
+ start,
2692
+ stop,
2693
+ reset
2694
+ };
2695
+ }
2696
+ var defaultColors = ["#60a5fa", "#34d399", "#fbbf24", "#f87171"];
2697
+ var keyframesInjected = false;
2698
+ function ensureGradientKeyframes() {
2699
+ if (typeof document === "undefined" || keyframesInjected) return;
2700
+ const name = "gradientShift";
2701
+ if (!document.head.querySelector(`style[data-gradient="${name}"]`)) {
2702
+ const style = document.createElement("style");
2703
+ style.setAttribute("data-gradient", name);
2704
+ style.textContent = `
2705
+ @keyframes ${name} {
2706
+ 0%, 100% { background-position: 0% 50%; }
2707
+ 50% { background-position: 100% 50%; }
2708
+ }
2709
+ `;
2710
+ document.head.appendChild(style);
2711
+ }
2712
+ keyframesInjected = true;
2713
+ }
2714
+ function useGradient(options = {}) {
2715
+ const {
2716
+ colors = defaultColors,
2717
+ duration = 6e3,
2718
+ direction = "diagonal",
2719
+ size = 300,
2720
+ easing: easing2 = "ease-in-out",
2721
+ autoStart = false
2722
+ } = options;
2723
+ const ref = useRef(null);
2724
+ const [isAnimating, setIsAnimating] = useState(autoStart);
2725
+ const [isVisible, setIsVisible] = useState(true);
2726
+ const [motionProgress, setMotionProgress] = useState(0);
2727
+ useEffect(() => {
2728
+ ensureGradientKeyframes();
2729
+ }, []);
2730
+ const style = useMemo(() => {
2731
+ const gradientDirection = direction === "horizontal" ? "90deg" : direction === "vertical" ? "180deg" : "135deg";
2732
+ const background = `linear-gradient(${gradientDirection}, ${colors.join(", ")})`;
2733
+ const backgroundSize = `${size}% ${size}%`;
2734
+ return {
2735
+ background,
2736
+ backgroundSize,
2737
+ animation: isAnimating ? `gradientShift ${duration}ms ${easing2} infinite` : "none",
2738
+ backgroundPosition: isAnimating ? void 0 : `${motionProgress}% 50%`
2739
+ };
2740
+ }, [colors, direction, size, duration, easing2, isAnimating, motionProgress]);
2741
+ const start = useCallback(() => {
2742
+ setIsAnimating(true);
2743
+ }, []);
2744
+ const pause = useCallback(() => {
2745
+ setIsAnimating(false);
2746
+ }, []);
2747
+ const resume = useCallback(() => {
2748
+ setIsAnimating(true);
2749
+ }, []);
2750
+ const reset = useCallback(() => {
2751
+ setIsAnimating(false);
2752
+ setMotionProgress(0);
2753
+ }, []);
2754
+ const stop = useCallback(() => {
2755
+ setIsAnimating(false);
2756
+ }, []);
2757
+ useEffect(() => {
2758
+ if (!isAnimating) {
2759
+ const interval = setInterval(() => {
2760
+ setMotionProgress((prev) => {
2761
+ const newProgress = prev + 100 / (duration / 16);
2762
+ return newProgress >= 100 ? 0 : newProgress;
2763
+ });
2764
+ }, 16);
2765
+ return () => clearInterval(interval);
2766
+ }
2767
+ }, [isAnimating, duration]);
2768
+ useEffect(() => {
2769
+ setIsAnimating(autoStart);
2770
+ }, [autoStart]);
2771
+ return {
2772
+ ref,
2773
+ isVisible,
2774
+ isAnimating,
2775
+ style,
2776
+ progress: motionProgress / 100,
2777
+ start,
2778
+ pause,
2779
+ resume,
2780
+ reset,
2781
+ stop
2782
+ };
2783
+ }
2784
+ function useHoverMotion(options = {}) {
2785
+ const {
2786
+ duration = 200,
2787
+ easing: easing2 = "ease-out",
2788
+ hoverScale = 1.05,
2789
+ hoverY = -2,
2790
+ hoverOpacity = 1
2791
+ } = options;
2792
+ const ref = useRef(null);
2793
+ const [isHovered, setIsHovered] = useState(false);
2794
+ const [isAnimating, setIsAnimating] = useState(false);
2795
+ useEffect(() => {
2796
+ const element = ref.current;
2797
+ if (!element) return;
2798
+ const handleMouseEnter = () => {
2799
+ setIsHovered(true);
2800
+ setIsAnimating(true);
2801
+ };
2802
+ const handleMouseLeave = () => {
2803
+ setIsHovered(false);
2804
+ setIsAnimating(true);
2805
+ };
2806
+ const handleTransitionEnd = () => {
2807
+ setIsAnimating(false);
2808
+ };
2809
+ element.addEventListener("mouseenter", handleMouseEnter);
2810
+ element.addEventListener("mouseleave", handleMouseLeave);
2811
+ element.addEventListener("transitionend", handleTransitionEnd);
2812
+ return () => {
2813
+ element.removeEventListener("mouseenter", handleMouseEnter);
2814
+ element.removeEventListener("mouseleave", handleMouseLeave);
2815
+ element.removeEventListener("transitionend", handleTransitionEnd);
2816
+ };
2817
+ }, []);
2818
+ const style = useMemo(() => ({
2819
+ transform: isHovered ? `scale(${hoverScale}) translateY(${hoverY}px)` : "scale(1) translateY(0px)",
2820
+ opacity: isHovered ? hoverOpacity : 1,
2821
+ transition: `transform ${duration}ms ${easing2}, opacity ${duration}ms ${easing2}`
2822
+ }), [isHovered, hoverScale, hoverY, hoverOpacity, duration, easing2]);
2823
+ const start = useCallback(() => {
2824
+ setIsHovered(true);
2825
+ setIsAnimating(true);
2826
+ }, []);
2827
+ const stop = useCallback(() => {
2828
+ setIsAnimating(false);
2829
+ }, []);
2830
+ const reset = useCallback(() => {
2831
+ setIsHovered(false);
2832
+ setIsAnimating(false);
2833
+ }, []);
2834
+ return {
2835
+ ref,
2836
+ isVisible: true,
2837
+ isAnimating,
2838
+ isHovered,
2839
+ style,
2840
+ progress: isHovered ? 1 : 0,
2841
+ start,
2842
+ stop,
2843
+ reset
2844
+ };
2845
+ }
2846
+ function useClickToggle(options = {}) {
2847
+ const {
2848
+ initialState = false,
2849
+ toggleOnClick = true,
2850
+ toggleOnDoubleClick = false,
2851
+ toggleOnRightClick = false,
2852
+ toggleOnEnter = true,
2853
+ toggleOnSpace = true,
2854
+ autoReset = false,
2855
+ resetDelay = 3e3,
2856
+ preventDefault = false,
2857
+ stopPropagation = false,
2858
+ showOnMount = false
2859
+ } = options;
2860
+ const [isActive, setIsActive] = useState(showOnMount ? initialState : false);
2861
+ const [mounted, setMounted] = useState(false);
2862
+ const resetTimeoutRef = useRef(null);
2863
+ useEffect(() => {
2864
+ setMounted(true);
2865
+ }, []);
2866
+ const startResetTimer = useCallback(() => {
2867
+ if (!autoReset || resetDelay <= 0) return;
2868
+ if (resetTimeoutRef.current !== null) {
2869
+ clearTimeout(resetTimeoutRef.current);
2870
+ }
2871
+ resetTimeoutRef.current = window.setTimeout(() => {
2872
+ setIsActive(false);
2873
+ resetTimeoutRef.current = null;
2874
+ }, resetDelay);
2875
+ }, [autoReset, resetDelay]);
2876
+ const toggle = useCallback(() => {
2877
+ if (!mounted) return;
2878
+ setIsActive((prev) => {
2879
+ const newState = !prev;
2880
+ if (newState && autoReset) {
2881
+ startResetTimer();
2882
+ } else if (!newState && resetTimeoutRef.current !== null) {
2883
+ clearTimeout(resetTimeoutRef.current);
2884
+ resetTimeoutRef.current = null;
2885
+ }
2886
+ return newState;
2887
+ });
2888
+ }, [mounted, autoReset, startResetTimer]);
2889
+ const activate = useCallback(() => {
2890
+ if (!mounted || isActive) return;
2891
+ setIsActive(true);
2892
+ if (autoReset) {
2893
+ startResetTimer();
2894
+ }
2895
+ }, [mounted, isActive, autoReset, startResetTimer]);
2896
+ const deactivate = useCallback(() => {
2897
+ if (!mounted || !isActive) return;
2898
+ setIsActive(false);
2899
+ if (resetTimeoutRef.current !== null) {
2900
+ clearTimeout(resetTimeoutRef.current);
2901
+ resetTimeoutRef.current = null;
2902
+ }
2903
+ }, [mounted, isActive]);
2904
+ const reset = useCallback(() => {
2905
+ setIsActive(initialState);
2906
+ if (resetTimeoutRef.current !== null) {
2907
+ clearTimeout(resetTimeoutRef.current);
2908
+ resetTimeoutRef.current = null;
2909
+ }
2910
+ }, [initialState]);
2911
+ const handleClick = useCallback((event) => {
2912
+ if (!toggleOnClick) return;
2913
+ if (preventDefault) event.preventDefault();
2914
+ if (stopPropagation) event.stopPropagation();
2915
+ toggle();
2916
+ }, [toggleOnClick, preventDefault, stopPropagation, toggle]);
2917
+ const handleDoubleClick = useCallback((event) => {
2918
+ if (!toggleOnDoubleClick) return;
2919
+ if (preventDefault) event.preventDefault();
2920
+ if (stopPropagation) event.stopPropagation();
2921
+ toggle();
2922
+ }, [toggleOnDoubleClick, preventDefault, stopPropagation, toggle]);
2923
+ const handleContextMenu = useCallback((event) => {
2924
+ if (!toggleOnRightClick) return;
2925
+ if (preventDefault) event.preventDefault();
2926
+ if (stopPropagation) event.stopPropagation();
2927
+ toggle();
2928
+ }, [toggleOnRightClick, preventDefault, stopPropagation, toggle]);
2929
+ const handleKeyDown = useCallback((event) => {
2930
+ const shouldToggle = event.key === "Enter" && toggleOnEnter || event.key === " " && toggleOnSpace;
2931
+ if (!shouldToggle) return;
2932
+ if (preventDefault) event.preventDefault();
2933
+ if (stopPropagation) event.stopPropagation();
2934
+ toggle();
2935
+ }, [toggleOnEnter, toggleOnSpace, preventDefault, stopPropagation, toggle]);
2936
+ useEffect(() => {
2937
+ return () => {
2938
+ if (resetTimeoutRef.current !== null) {
2939
+ clearTimeout(resetTimeoutRef.current);
2940
+ }
2941
+ };
2942
+ }, []);
2943
+ const clickHandlers = {
2944
+ ...toggleOnClick && { onClick: handleClick },
2945
+ ...toggleOnDoubleClick && { onDoubleClick: handleDoubleClick },
2946
+ ...toggleOnRightClick && { onContextMenu: handleContextMenu },
2947
+ ...(toggleOnEnter || toggleOnSpace) && { onKeyDown: handleKeyDown }
2948
+ };
2949
+ return {
2950
+ isActive,
2951
+ mounted,
2952
+ toggle,
2953
+ activate,
2954
+ deactivate,
2955
+ reset,
2956
+ clickHandlers
2957
+ };
2958
+ }
2959
+ function useFocusToggle(options = {}) {
2960
+ const {
2961
+ initialState = false,
2962
+ toggleOnFocus = true,
2963
+ toggleOnBlur = false,
2964
+ toggleOnFocusIn = false,
2965
+ toggleOnFocusOut = false,
2966
+ autoReset = false,
2967
+ resetDelay = 3e3,
2968
+ preventDefault = false,
2969
+ stopPropagation = false,
2970
+ showOnMount = false
2971
+ } = options;
2972
+ const [isActive, setIsActive] = useState(showOnMount ? initialState : false);
2973
+ const [mounted, setMounted] = useState(false);
2974
+ const resetTimeoutRef = useRef(null);
2975
+ const elementRef = useRef(null);
2976
+ useEffect(() => {
2977
+ setMounted(true);
2978
+ }, []);
2979
+ const startResetTimer = useCallback(() => {
2980
+ if (!autoReset || resetDelay <= 0) return;
2981
+ if (resetTimeoutRef.current !== null) {
2982
+ clearTimeout(resetTimeoutRef.current);
2983
+ }
2984
+ resetTimeoutRef.current = window.setTimeout(() => {
2985
+ setIsActive(false);
2986
+ resetTimeoutRef.current = null;
2987
+ }, resetDelay);
2988
+ }, [autoReset, resetDelay]);
2989
+ const toggle = useCallback(() => {
2990
+ if (!mounted) return;
2991
+ setIsActive((prev) => {
2992
+ const newState = !prev;
2993
+ if (newState && autoReset) {
2994
+ startResetTimer();
2995
+ } else if (!newState && resetTimeoutRef.current !== null) {
2996
+ clearTimeout(resetTimeoutRef.current);
2997
+ resetTimeoutRef.current = null;
2998
+ }
2999
+ return newState;
3000
+ });
3001
+ }, [mounted, autoReset, startResetTimer]);
3002
+ const activate = useCallback(() => {
3003
+ if (!mounted || isActive) return;
3004
+ setIsActive(true);
3005
+ if (autoReset) {
3006
+ startResetTimer();
3007
+ }
3008
+ }, [mounted, isActive, autoReset, startResetTimer]);
3009
+ const deactivate = useCallback(() => {
3010
+ if (!mounted || !isActive) return;
3011
+ setIsActive(false);
3012
+ if (resetTimeoutRef.current !== null) {
3013
+ clearTimeout(resetTimeoutRef.current);
3014
+ resetTimeoutRef.current = null;
3015
+ }
3016
+ }, [mounted, isActive]);
3017
+ const reset = useCallback(() => {
3018
+ setIsActive(initialState);
3019
+ if (resetTimeoutRef.current !== null) {
3020
+ clearTimeout(resetTimeoutRef.current);
3021
+ resetTimeoutRef.current = null;
3022
+ }
3023
+ }, [initialState]);
3024
+ const handleFocus = useCallback((event) => {
3025
+ if (!toggleOnFocus) return;
3026
+ if (preventDefault) event.preventDefault();
3027
+ if (stopPropagation) event.stopPropagation();
3028
+ activate();
3029
+ }, [toggleOnFocus, preventDefault, stopPropagation, activate]);
3030
+ const handleBlur = useCallback((event) => {
3031
+ if (!toggleOnBlur) return;
3032
+ if (preventDefault) event.preventDefault();
3033
+ if (stopPropagation) event.stopPropagation();
3034
+ deactivate();
3035
+ }, [toggleOnBlur, preventDefault, stopPropagation, deactivate]);
3036
+ const handleFocusIn = useCallback((event) => {
3037
+ if (!toggleOnFocusIn) return;
3038
+ if (preventDefault) event.preventDefault();
3039
+ if (stopPropagation) event.stopPropagation();
3040
+ activate();
3041
+ }, [toggleOnFocusIn, preventDefault, stopPropagation, activate]);
3042
+ const handleFocusOut = useCallback((event) => {
3043
+ if (!toggleOnFocusOut) return;
3044
+ if (preventDefault) event.preventDefault();
3045
+ if (stopPropagation) event.stopPropagation();
3046
+ deactivate();
3047
+ }, [toggleOnFocusOut, preventDefault, stopPropagation, deactivate]);
3048
+ useEffect(() => {
3049
+ return () => {
3050
+ if (resetTimeoutRef.current !== null) {
3051
+ clearTimeout(resetTimeoutRef.current);
3052
+ }
3053
+ };
3054
+ }, []);
3055
+ const focusHandlers = {
3056
+ ...toggleOnFocus && { onFocus: handleFocus },
3057
+ ...toggleOnBlur && { onBlur: handleBlur },
3058
+ ...toggleOnFocusIn && { onFocusIn: handleFocusIn },
3059
+ ...toggleOnFocusOut && { onFocusOut: handleFocusOut }
3060
+ };
3061
+ return {
3062
+ isActive,
3063
+ mounted,
3064
+ toggle,
3065
+ activate,
3066
+ deactivate,
3067
+ reset,
3068
+ focusHandlers,
3069
+ ref: elementRef
3070
+ };
3071
+ }
3072
+ function useScrollReveal(options = {}) {
3073
+ const {
3074
+ threshold = 0.1,
3075
+ rootMargin = "0px",
3076
+ triggerOnce = true,
3077
+ delay = 0,
3078
+ duration = 700,
3079
+ easing: easing2 = "ease-out",
3080
+ motionType = "fadeIn",
3081
+ onComplete,
3082
+ onStart,
3083
+ onStop,
3084
+ onReset
3085
+ } = options;
3086
+ const ref = useRef(null);
3087
+ const [isVisible, setIsVisible] = useState(false);
3088
+ const [isAnimating, setIsAnimating] = useState(false);
3089
+ const [hasTriggered, setHasTriggered] = useState(false);
3090
+ const [progress, setProgress] = useState(0);
3091
+ const observerCallback = useCallback((entries) => {
3092
+ entries.forEach((entry) => {
3093
+ if (entry.isIntersecting && (!triggerOnce || !hasTriggered)) {
3094
+ setIsAnimating(true);
3095
+ onStart?.();
3096
+ setTimeout(() => {
3097
+ setIsVisible(true);
3098
+ setHasTriggered(true);
3099
+ setProgress(1);
3100
+ setIsAnimating(false);
3101
+ onComplete?.();
3102
+ }, delay);
3103
+ }
3104
+ });
3105
+ }, [triggerOnce, hasTriggered, delay, onStart, onComplete]);
3106
+ useEffect(() => {
3107
+ if (!ref.current) return;
3108
+ const observer = new IntersectionObserver(observerCallback, {
3109
+ threshold,
3110
+ rootMargin
3111
+ });
3112
+ observer.observe(ref.current);
3113
+ return () => {
3114
+ observer.disconnect();
3115
+ };
3116
+ }, [observerCallback, threshold, rootMargin]);
3117
+ const style = useMemo(() => {
3118
+ const baseTransition = `all ${duration}ms ${easing2}`;
3119
+ if (!isVisible) {
3120
+ switch (motionType) {
3121
+ case "fadeIn":
3122
+ return {
3123
+ opacity: 0,
3124
+ transition: baseTransition
3125
+ };
3126
+ case "slideUp":
3127
+ return {
3128
+ opacity: 0,
3129
+ transform: "translateY(32px)",
3130
+ transition: baseTransition
3131
+ };
3132
+ case "slideLeft":
3133
+ return {
3134
+ opacity: 0,
3135
+ transform: "translateX(-32px)",
3136
+ transition: baseTransition
3137
+ };
3138
+ case "slideRight":
3139
+ return {
3140
+ opacity: 0,
3141
+ transform: "translateX(32px)",
3142
+ transition: baseTransition
3143
+ };
3144
+ case "scaleIn":
3145
+ return {
3146
+ opacity: 0,
3147
+ transform: "scale(0.95)",
3148
+ transition: baseTransition
3149
+ };
3150
+ case "bounceIn":
3151
+ return {
3152
+ opacity: 0,
3153
+ transform: "scale(0.75)",
3154
+ transition: baseTransition
3155
+ };
3156
+ default:
3157
+ return {
3158
+ opacity: 0,
3159
+ transition: baseTransition
3160
+ };
3161
+ }
3162
+ }
3163
+ return {
3164
+ opacity: 1,
3165
+ transform: "none",
3166
+ transition: baseTransition
3167
+ };
3168
+ }, [isVisible, motionType, duration, easing2]);
3169
+ const start = useCallback(() => {
3170
+ setIsAnimating(true);
3171
+ onStart?.();
3172
+ setTimeout(() => {
3173
+ setIsVisible(true);
3174
+ setProgress(1);
3175
+ setIsAnimating(false);
3176
+ onComplete?.();
3177
+ }, delay);
3178
+ }, [delay, onStart, onComplete]);
3179
+ const reset = useCallback(() => {
3180
+ setIsVisible(false);
3181
+ setIsAnimating(false);
3182
+ setProgress(0);
3183
+ setHasTriggered(false);
3184
+ onReset?.();
3185
+ }, [onReset]);
3186
+ const stop = useCallback(() => {
3187
+ setIsAnimating(false);
3188
+ onStop?.();
3189
+ }, [onStop]);
3190
+ return {
3191
+ ref,
3192
+ isVisible,
3193
+ isAnimating,
3194
+ progress,
3195
+ style,
3196
+ start,
3197
+ reset,
3198
+ stop
3199
+ };
3200
+ }
3201
+ function useScrollProgress(options = {}) {
3202
+ const {
3203
+ target,
3204
+ offset = 0,
3205
+ showOnMount = false
3206
+ } = options;
3207
+ const [progress, setProgress] = useState(showOnMount ? 0 : 0);
3208
+ const [mounted, setMounted] = useState(false);
3209
+ useEffect(() => {
3210
+ setMounted(true);
3211
+ }, []);
3212
+ useEffect(() => {
3213
+ if (!mounted) return;
3214
+ const calculateProgress = () => {
3215
+ if (typeof window !== "undefined") {
3216
+ const scrollTop = window.pageYOffset;
3217
+ const scrollHeight = target || document.documentElement.scrollHeight - window.innerHeight;
3218
+ const adjustedScrollTop = Math.max(0, scrollTop - offset);
3219
+ const progressPercent = Math.min(100, Math.max(0, adjustedScrollTop / scrollHeight * 100));
3220
+ setProgress(progressPercent);
3221
+ }
3222
+ };
3223
+ calculateProgress();
3224
+ window.addEventListener("scroll", calculateProgress, { passive: true });
3225
+ window.addEventListener("resize", calculateProgress, { passive: true });
3226
+ return () => {
3227
+ window.removeEventListener("scroll", calculateProgress);
3228
+ window.removeEventListener("resize", calculateProgress);
3229
+ };
3230
+ }, [target, offset, mounted]);
3231
+ return {
3232
+ progress,
3233
+ mounted
3234
+ };
3235
+ }
3236
+ function useMotionState(options = {}) {
3237
+ const {
3238
+ initialState = "idle",
3239
+ autoPlay,
3240
+ autoStart = autoPlay ?? false,
3241
+ loop = false,
3242
+ direction = "forward",
3243
+ duration = 1e3,
3244
+ delay = 0,
3245
+ showOnMount = false
3246
+ } = options;
3247
+ const [state, setState] = useState(showOnMount ? initialState : "idle");
3248
+ const [currentDirection, setCurrentDirection] = useState(direction);
3249
+ const [progress, setProgress] = useState(0);
3250
+ const [elapsed, setElapsed] = useState(0);
3251
+ const [mounted, setMounted] = useState(false);
3252
+ const motionRef = useRef(null);
3253
+ const startTimeRef = useRef(null);
3254
+ const pauseTimeRef = useRef(null);
3255
+ const totalPausedTimeRef = useRef(0);
3256
+ useEffect(() => {
3257
+ setMounted(true);
3258
+ }, []);
3259
+ const animate = useCallback((timestamp) => {
3260
+ if (!startTimeRef.current) {
3261
+ startTimeRef.current = timestamp;
3262
+ }
3263
+ const adjustedTimestamp = timestamp - totalPausedTimeRef.current;
3264
+ const elapsedTime = adjustedTimestamp - startTimeRef.current;
3265
+ const newElapsed = Math.max(0, elapsedTime - delay);
3266
+ setElapsed(newElapsed);
3267
+ let newProgress = 0;
3268
+ if (newElapsed >= 0) {
3269
+ newProgress = Math.min(100, newElapsed / duration * 100);
3270
+ }
3271
+ if (currentDirection === "reverse") {
3272
+ newProgress = 100 - newProgress;
3273
+ } else if (currentDirection === "alternate") {
3274
+ const cycle = Math.floor(newElapsed / duration);
3275
+ const cycleProgress = newElapsed % duration / duration;
3276
+ newProgress = cycle % 2 === 0 ? cycleProgress * 100 : (1 - cycleProgress) * 100;
3277
+ }
3278
+ setProgress(newProgress);
3279
+ if (newElapsed >= duration) {
3280
+ if (loop) {
3281
+ startTimeRef.current = timestamp || performance.now();
3282
+ totalPausedTimeRef.current = 0;
3283
+ setElapsed(0);
3284
+ setProgress(currentDirection === "reverse" ? 100 : 0);
3285
+ } else {
3286
+ setState("completed");
3287
+ setProgress(currentDirection === "reverse" ? 0 : 100);
3288
+ if (motionRef.current) {
3289
+ cancelAnimationFrame(motionRef.current);
3290
+ motionRef.current = null;
3291
+ }
3292
+ return;
3293
+ }
3294
+ }
3295
+ if (state === "playing") {
3296
+ motionRef.current = requestAnimationFrame(animate);
3297
+ }
3298
+ }, [state, duration, delay, loop, currentDirection]);
3299
+ const play = useCallback(() => {
3300
+ if (!mounted) return;
3301
+ if (state === "completed") {
3302
+ reset();
3303
+ }
3304
+ setState("playing");
3305
+ if (pauseTimeRef.current) {
3306
+ totalPausedTimeRef.current += performance.now() - pauseTimeRef.current;
3307
+ pauseTimeRef.current = null;
3308
+ } else {
3309
+ startTimeRef.current = null;
3310
+ totalPausedTimeRef.current = 0;
3311
+ }
3312
+ if (!motionRef.current) {
3313
+ motionRef.current = requestAnimationFrame(animate);
3314
+ }
3315
+ }, [mounted, state, animate]);
3316
+ const pause = useCallback(() => {
3317
+ if (state !== "playing") return;
3318
+ setState("paused");
3319
+ pauseTimeRef.current = performance.now();
3320
+ if (motionRef.current) {
3321
+ cancelAnimationFrame(motionRef.current);
3322
+ motionRef.current = null;
3323
+ }
3324
+ }, [state]);
3325
+ const stop = useCallback(() => {
3326
+ setState("idle");
3327
+ setProgress(0);
3328
+ setElapsed(0);
3329
+ startTimeRef.current = null;
3330
+ pauseTimeRef.current = null;
3331
+ totalPausedTimeRef.current = 0;
3332
+ if (motionRef.current) {
3333
+ cancelAnimationFrame(motionRef.current);
3334
+ motionRef.current = null;
3335
+ }
3336
+ }, []);
3337
+ const reset = useCallback(() => {
3338
+ setState("idle");
3339
+ setProgress(0);
3340
+ setElapsed(0);
3341
+ setCurrentDirection(direction);
3342
+ startTimeRef.current = null;
3343
+ pauseTimeRef.current = null;
3344
+ totalPausedTimeRef.current = 0;
3345
+ if (motionRef.current) {
3346
+ cancelAnimationFrame(motionRef.current);
3347
+ motionRef.current = null;
3348
+ }
3349
+ }, [direction]);
3350
+ const reverse = useCallback(() => {
3351
+ const newDirection = currentDirection === "forward" ? "reverse" : "forward";
3352
+ setCurrentDirection(newDirection);
3353
+ if (state === "playing") {
3354
+ const remainingTime = duration - elapsed;
3355
+ startTimeRef.current = performance.now() - remainingTime;
3356
+ totalPausedTimeRef.current = 0;
3357
+ }
3358
+ }, [currentDirection, state, duration, elapsed]);
3359
+ const seek = useCallback((targetProgress) => {
3360
+ const clampedProgress = Math.max(0, Math.min(100, targetProgress));
3361
+ setProgress(clampedProgress);
3362
+ let targetElapsed = 0;
3363
+ if (currentDirection === "reverse") {
3364
+ targetElapsed = (100 - clampedProgress) / 100 * duration;
3365
+ } else if (currentDirection === "alternate") {
3366
+ targetElapsed = clampedProgress / 100 * duration;
3367
+ } else {
3368
+ targetElapsed = clampedProgress / 100 * duration;
3369
+ }
3370
+ setElapsed(targetElapsed);
3371
+ if (state === "playing" && startTimeRef.current) {
3372
+ const currentTime = performance.now();
3373
+ startTimeRef.current = currentTime - targetElapsed - totalPausedTimeRef.current;
3374
+ }
3375
+ }, [currentDirection, duration, state]);
3376
+ const setMotionState = useCallback((newState) => {
3377
+ setState(newState);
3378
+ if (newState === "playing" && !motionRef.current) {
3379
+ motionRef.current = requestAnimationFrame(animate);
3380
+ } else if (newState !== "playing" && motionRef.current) {
3381
+ cancelAnimationFrame(motionRef.current);
3382
+ motionRef.current = null;
3383
+ }
3384
+ }, [animate]);
3385
+ useEffect(() => {
3386
+ if (mounted && autoStart && state === "idle") {
3387
+ play();
3388
+ }
3389
+ }, [mounted, autoStart, state, play]);
3390
+ useEffect(() => {
3391
+ return () => {
3392
+ if (motionRef.current) {
3393
+ cancelAnimationFrame(motionRef.current);
3394
+ }
3395
+ };
3396
+ }, []);
3397
+ return {
3398
+ state,
3399
+ direction: currentDirection,
3400
+ progress,
3401
+ elapsed,
3402
+ remaining: Math.max(0, duration - elapsed),
3403
+ mounted,
3404
+ play,
3405
+ pause,
3406
+ stop,
3407
+ reset,
3408
+ reverse,
3409
+ seek,
3410
+ setState: setMotionState
3411
+ };
3412
+ }
3413
+ function useRepeat(options = {}) {
3414
+ const {
3415
+ duration = 1e3,
3416
+ delay = 0,
3417
+ autoStart = true,
3418
+ type = "pulse",
3419
+ intensity = 0.1
3420
+ } = options;
3421
+ const ref = useRef(null);
3422
+ const [scale, setScale] = useState(1);
3423
+ const [opacity, setOpacity] = useState(1);
3424
+ const [isAnimating, setIsAnimating] = useState(false);
3425
+ const [progress, setProgress] = useState(0);
3426
+ const animationTimers = useRef([]);
3427
+ const isRunning = useRef(false);
3428
+ const clearAllTimers = useCallback(() => {
3429
+ animationTimers.current.forEach((id) => clearTimeout(id));
3430
+ animationTimers.current = [];
3431
+ }, []);
3432
+ const addTimer = useCallback((callback, ms) => {
3433
+ const id = window.setTimeout(callback, ms);
3434
+ animationTimers.current.push(id);
3435
+ return id;
3436
+ }, []);
3437
+ const animate = useCallback(() => {
3438
+ if (!isRunning.current) return;
3439
+ setIsAnimating(true);
3440
+ setProgress(0);
3441
+ switch (type) {
3442
+ case "pulse":
3443
+ setScale(1 + intensity);
3444
+ addTimer(() => {
3445
+ if (!isRunning.current) return;
3446
+ setScale(1);
3447
+ setProgress(0.5);
3448
+ }, duration / 2);
3449
+ break;
3450
+ case "bounce":
3451
+ setScale(1 + intensity);
3452
+ addTimer(() => {
3453
+ if (!isRunning.current) return;
3454
+ setScale(1 - intensity);
3455
+ setProgress(0.33);
3456
+ }, duration / 3);
3457
+ addTimer(() => {
3458
+ if (!isRunning.current) return;
3459
+ setScale(1);
3460
+ setProgress(0.66);
3461
+ }, duration);
3462
+ break;
3463
+ case "wave":
3464
+ setScale(1 + intensity);
3465
+ addTimer(() => {
3466
+ if (!isRunning.current) return;
3467
+ setScale(1 - intensity);
3468
+ setProgress(0.5);
3469
+ }, duration / 2);
3470
+ addTimer(() => {
3471
+ if (!isRunning.current) return;
3472
+ setScale(1);
3473
+ setProgress(0.75);
3474
+ }, duration);
3475
+ break;
3476
+ case "fade":
3477
+ setOpacity(0.5);
3478
+ addTimer(() => {
3479
+ if (!isRunning.current) return;
3480
+ setOpacity(1);
3481
+ setProgress(0.5);
3482
+ }, duration / 2);
3483
+ break;
3484
+ }
3485
+ addTimer(() => {
3486
+ if (!isRunning.current) return;
3487
+ setProgress(1);
3488
+ setIsAnimating(false);
3489
+ animate();
3490
+ }, duration);
3491
+ }, [type, intensity, duration, addTimer]);
3492
+ const start = useCallback(() => {
3493
+ isRunning.current = true;
3494
+ clearAllTimers();
3495
+ if (delay > 0) {
3496
+ addTimer(() => animate(), delay);
3497
+ } else {
3498
+ animate();
3499
+ }
3500
+ }, [delay, animate, clearAllTimers, addTimer]);
3501
+ const stop = useCallback(() => {
3502
+ isRunning.current = false;
3503
+ clearAllTimers();
3504
+ setIsAnimating(false);
3505
+ setScale(1);
3506
+ setOpacity(1);
3507
+ setProgress(0);
3508
+ }, [clearAllTimers]);
3509
+ const reset = useCallback(() => {
3510
+ stop();
3511
+ }, [stop]);
3512
+ useEffect(() => {
3513
+ if (autoStart) {
3514
+ start();
3515
+ }
3516
+ return () => {
3517
+ isRunning.current = false;
3518
+ clearAllTimers();
3519
+ };
3520
+ }, []);
3521
+ const style = useMemo(() => ({
3522
+ transform: `scale(${scale})`,
3523
+ opacity,
3524
+ transition: `transform ${duration / 2}ms ease-in-out, opacity ${duration / 2}ms ease-in-out`
3525
+ }), [scale, opacity, duration]);
3526
+ return {
3527
+ ref,
3528
+ isVisible: true,
3529
+ isAnimating,
3530
+ style,
3531
+ progress,
3532
+ start,
3533
+ stop,
3534
+ reset
3535
+ };
3536
+ }
3537
+ function useToggleMotion(options = {}) {
3538
+ const { duration = 300, delay = 0, easing: easing2 = "ease-in-out" } = options;
3539
+ const ref = useRef(null);
3540
+ const [isVisible, setIsVisible] = useState(false);
3541
+ const [isAnimating, setIsAnimating] = useState(false);
3542
+ const show = useCallback(() => {
3543
+ setIsVisible(true);
3544
+ setIsAnimating(true);
3545
+ setTimeout(() => setIsAnimating(false), duration + delay);
3546
+ }, [duration, delay]);
3547
+ const hide = useCallback(() => {
3548
+ setIsVisible(false);
3549
+ setIsAnimating(true);
3550
+ setTimeout(() => setIsAnimating(false), duration + delay);
3551
+ }, [duration, delay]);
3552
+ const toggle = useCallback(() => {
3553
+ if (isVisible) {
3554
+ hide();
3555
+ } else {
3556
+ show();
3557
+ }
3558
+ }, [isVisible, show, hide]);
3559
+ const start = useCallback(() => show(), [show]);
3560
+ const stop = useCallback(() => setIsAnimating(false), []);
3561
+ const reset = useCallback(() => {
3562
+ setIsVisible(false);
3563
+ setIsAnimating(false);
3564
+ }, []);
3565
+ const style = useMemo(() => ({
3566
+ opacity: isVisible ? 1 : 0,
3567
+ transform: isVisible ? "translateY(0) scale(1)" : "translateY(10px) scale(0.95)",
3568
+ transition: `opacity ${duration}ms ${easing2} ${delay}ms, transform ${duration}ms ${easing2} ${delay}ms`
3569
+ }), [isVisible, duration, easing2, delay]);
3570
+ return {
3571
+ ref,
3572
+ isVisible,
3573
+ isAnimating,
3574
+ style,
3575
+ progress: isVisible ? 1 : 0,
3576
+ start,
3577
+ stop,
3578
+ reset,
3579
+ toggle,
3580
+ show,
3581
+ hide
3582
+ };
3583
+ }
3584
+
3585
+ // src/hooks/useSlideDown.ts
3586
+ function useSlideDown(options = {}) {
3587
+ return useSlideUp({ ...options, direction: "down" });
3588
+ }
3589
+ function useInView(options = {}) {
3590
+ const {
3591
+ threshold = 0,
3592
+ rootMargin = "0px",
3593
+ triggerOnce = false,
3594
+ initialInView = false
3595
+ } = options;
3596
+ const ref = useRef(null);
3597
+ const [inView, setInView] = useState(initialInView);
3598
+ const [entry, setEntry] = useState(null);
3599
+ const frozenRef = useRef(false);
3600
+ const handleIntersect = useCallback(
3601
+ (entries) => {
3602
+ const [observerEntry] = entries;
3603
+ if (frozenRef.current) return;
3604
+ setEntry(observerEntry);
3605
+ setInView(observerEntry.isIntersecting);
3606
+ if (triggerOnce && observerEntry.isIntersecting) {
3607
+ frozenRef.current = true;
3608
+ }
3609
+ },
3610
+ [triggerOnce]
3611
+ );
3612
+ useEffect(() => {
3613
+ const element = ref.current;
3614
+ if (!element) return;
3615
+ const observer = new IntersectionObserver(handleIntersect, {
3616
+ threshold,
3617
+ rootMargin
3618
+ });
3619
+ observer.observe(element);
3620
+ return () => {
3621
+ observer.disconnect();
3622
+ };
3623
+ }, [threshold, rootMargin, handleIntersect]);
3624
+ return {
3625
+ ref,
3626
+ inView,
3627
+ entry
3628
+ };
3629
+ }
3630
+ function useMouse(options = {}) {
3631
+ const { targetRef, throttle = 0 } = options;
3632
+ const [state, setState] = useState({
3633
+ x: 0,
3634
+ y: 0,
3635
+ elementX: 0,
3636
+ elementY: 0,
3637
+ isOver: false
3638
+ });
3639
+ const lastUpdateRef = useRef(0);
3640
+ const rafIdRef = useRef(null);
3641
+ const updateMousePosition = useCallback(
3642
+ (clientX, clientY, isOver) => {
3643
+ const now = Date.now();
3644
+ if (throttle > 0 && now - lastUpdateRef.current < throttle) {
3645
+ return;
3646
+ }
3647
+ lastUpdateRef.current = now;
3648
+ let elementX = 0;
3649
+ let elementY = 0;
3650
+ if (targetRef?.current) {
3651
+ const rect = targetRef.current.getBoundingClientRect();
3652
+ elementX = (clientX - rect.left) / rect.width;
3653
+ elementY = (clientY - rect.top) / rect.height;
3654
+ elementX = Math.max(0, Math.min(1, elementX));
3655
+ elementY = Math.max(0, Math.min(1, elementY));
3656
+ }
3657
+ setState({
3658
+ x: clientX,
3659
+ y: clientY,
3660
+ elementX,
3661
+ elementY,
3662
+ isOver
3663
+ });
3664
+ },
3665
+ [targetRef, throttle]
3666
+ );
3667
+ useEffect(() => {
3668
+ const target = targetRef?.current;
3669
+ const handleMouseMove = (e) => {
3670
+ if (rafIdRef.current) {
3671
+ cancelAnimationFrame(rafIdRef.current);
3672
+ }
3673
+ rafIdRef.current = requestAnimationFrame(() => {
3674
+ const isOver = target ? target.contains(e.target) : true;
3675
+ updateMousePosition(e.clientX, e.clientY, isOver);
3676
+ });
3677
+ };
3678
+ const handleMouseEnter = () => {
3679
+ setState((prev) => ({ ...prev, isOver: true }));
3680
+ };
3681
+ const handleMouseLeave = () => {
3682
+ setState((prev) => ({ ...prev, isOver: false }));
3683
+ };
3684
+ if (target) {
3685
+ target.addEventListener("mousemove", handleMouseMove);
3686
+ target.addEventListener("mouseenter", handleMouseEnter);
3687
+ target.addEventListener("mouseleave", handleMouseLeave);
3688
+ } else {
3689
+ window.addEventListener("mousemove", handleMouseMove);
3690
+ }
3691
+ return () => {
3692
+ if (rafIdRef.current) {
3693
+ cancelAnimationFrame(rafIdRef.current);
3694
+ }
3695
+ if (target) {
3696
+ target.removeEventListener("mousemove", handleMouseMove);
3697
+ target.removeEventListener("mouseenter", handleMouseEnter);
3698
+ target.removeEventListener("mouseleave", handleMouseLeave);
3699
+ } else {
3700
+ window.removeEventListener("mousemove", handleMouseMove);
3701
+ }
3702
+ };
3703
+ }, [targetRef, updateMousePosition]);
3704
+ return state;
3705
+ }
3706
+ function useReducedMotion() {
3707
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
3708
+ useEffect(() => {
3709
+ if (typeof window === "undefined") return;
3710
+ const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
3711
+ setPrefersReducedMotion(mediaQuery.matches);
3712
+ const handleChange = (event) => {
3713
+ setPrefersReducedMotion(event.matches);
3714
+ };
3715
+ mediaQuery.addEventListener("change", handleChange);
3716
+ return () => {
3717
+ mediaQuery.removeEventListener("change", handleChange);
3718
+ };
3719
+ }, []);
3720
+ return {
3721
+ prefersReducedMotion
3722
+ };
3723
+ }
3724
+ function useWindowSize(options = {}) {
3725
+ const {
3726
+ debounce = 100,
3727
+ initialWidth = 0,
3728
+ initialHeight = 0
3729
+ } = options;
3730
+ const [state, setState] = useState({
3731
+ width: initialWidth,
3732
+ height: initialHeight,
3733
+ isMounted: false
3734
+ });
3735
+ const timeoutRef = useRef(null);
3736
+ const updateSize = useCallback(() => {
3737
+ if (typeof window === "undefined") return;
3738
+ setState({
3739
+ width: window.innerWidth,
3740
+ height: window.innerHeight,
3741
+ isMounted: true
3742
+ });
3743
+ }, []);
3744
+ const handleResize = useCallback(() => {
3745
+ if (timeoutRef.current) {
3746
+ clearTimeout(timeoutRef.current);
3747
+ }
3748
+ if (debounce > 0) {
3749
+ timeoutRef.current = window.setTimeout(updateSize, debounce);
3750
+ } else {
3751
+ updateSize();
3752
+ }
3753
+ }, [debounce, updateSize]);
3754
+ useEffect(() => {
3755
+ updateSize();
3756
+ window.addEventListener("resize", handleResize);
3757
+ return () => {
3758
+ if (timeoutRef.current) {
3759
+ clearTimeout(timeoutRef.current);
3760
+ }
3761
+ window.removeEventListener("resize", handleResize);
3762
+ };
3763
+ }, [handleResize, updateSize]);
3764
+ return state;
3765
+ }
3766
+ function useGesture(options = {}) {
3767
+ const {
3768
+ enabled = true,
3769
+ threshold = 10,
3770
+ timeout = 300,
3771
+ swipeThreshold = 50,
3772
+ swipeVelocity = 0.3,
3773
+ swipeDirections = ["up", "down", "left", "right"],
3774
+ pinchThreshold = 10,
3775
+ minScale = 0.1,
3776
+ maxScale = 10,
3777
+ rotateThreshold = 5,
3778
+ panThreshold = 10,
3779
+ onSwipe,
3780
+ onPinch,
3781
+ onRotate,
3782
+ onPan,
3783
+ onTap,
3784
+ onDoubleTap,
3785
+ onLongPress,
3786
+ onStart,
3787
+ onMove,
3788
+ onEnd
3789
+ } = options;
3790
+ const [isActive, setIsActive] = useState(false);
3791
+ const [gesture, setGesture] = useState(null);
3792
+ const [scale, setScale] = useState(1);
3793
+ const [rotation, setRotation] = useState(0);
3794
+ const [deltaX, setDeltaX] = useState(0);
3795
+ const [deltaY, setDeltaY] = useState(0);
3796
+ const [distance, setDistance] = useState(0);
3797
+ const [velocity, setVelocity] = useState(0);
3798
+ const stateRef = useRef({
3799
+ isActive: false,
3800
+ startX: 0,
3801
+ startY: 0,
3802
+ currentX: 0,
3803
+ currentY: 0,
3804
+ deltaX: 0,
3805
+ deltaY: 0,
3806
+ distance: 0,
3807
+ velocity: 0,
3808
+ startTime: 0,
3809
+ currentTime: 0,
3810
+ scale: 1,
3811
+ rotation: 0,
3812
+ startDistance: 0,
3813
+ startAngle: 0,
3814
+ touchCount: 0
3815
+ });
3816
+ const timeoutRef = useRef(null);
3817
+ const longPressRef = useRef(null);
3818
+ const lastTapRef = useRef(0);
3819
+ const getDistance = useCallback((x1, y1, x2, y2) => {
3820
+ return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
3821
+ }, []);
3822
+ const getAngle = useCallback((x1, y1, x2, y2) => {
3823
+ return Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;
3824
+ }, []);
3825
+ const getSwipeDirection = useCallback((deltaX2, deltaY2) => {
3826
+ const absX = Math.abs(deltaX2);
3827
+ const absY = Math.abs(deltaY2);
3828
+ if (absX > absY) {
3829
+ return deltaX2 > 0 ? "right" : "left";
3830
+ } else {
3831
+ return deltaY2 > 0 ? "down" : "up";
3832
+ }
3833
+ }, []);
3834
+ const startGesture = useCallback((x, y, touchCount = 1) => {
3835
+ if (!enabled) return;
3836
+ const now = performance.now();
3837
+ stateRef.current = {
3838
+ ...stateRef.current,
3839
+ isActive: true,
3840
+ startX: x,
3841
+ startY: y,
3842
+ currentX: x,
3843
+ currentY: y,
3844
+ deltaX: 0,
3845
+ deltaY: 0,
3846
+ distance: 0,
3847
+ velocity: 0,
3848
+ startTime: now,
3849
+ currentTime: now,
3850
+ touchCount
3851
+ };
3852
+ setIsActive(true);
3853
+ setGesture(null);
3854
+ onStart?.(x, y);
3855
+ if (onLongPress) {
3856
+ longPressRef.current = window.setTimeout(() => {
3857
+ onLongPress(x, y);
3858
+ }, 500);
3859
+ }
3860
+ }, [enabled, onStart, onLongPress]);
3861
+ const updateGesture = useCallback((x, y, touches) => {
3862
+ if (!enabled || !stateRef.current.isActive) return;
3863
+ const now = performance.now();
3864
+ const state = stateRef.current;
3865
+ const deltaX2 = x - state.startX;
3866
+ const deltaY2 = y - state.startY;
3867
+ const distance2 = getDistance(state.startX, state.startY, x, y);
3868
+ const velocity2 = distance2 / (now - state.startTime) * 1e3;
3869
+ state.currentX = x;
3870
+ state.currentY = y;
3871
+ state.deltaX = deltaX2;
3872
+ state.deltaY = deltaY2;
3873
+ state.distance = distance2;
3874
+ state.velocity = velocity2;
3875
+ state.currentTime = now;
3876
+ setDeltaX(deltaX2);
3877
+ setDeltaY(deltaY2);
3878
+ setDistance(distance2);
3879
+ setVelocity(velocity2);
3880
+ if (touches && touches.length === 2) {
3881
+ const touch1 = touches[0];
3882
+ const touch2 = touches[1];
3883
+ const currentDistance = getDistance(touch1.clientX, touch1.clientY, touch2.clientX, touch2.clientY);
3884
+ const currentAngle = getAngle(touch1.clientX, touch1.clientY, touch2.clientX, touch2.clientY);
3885
+ if (state.startDistance === 0) {
3886
+ state.startDistance = currentDistance;
3887
+ state.startAngle = currentAngle;
3888
+ } else {
3889
+ const scaleDelta = currentDistance / state.startDistance;
3890
+ const newScale = Math.max(minScale, Math.min(maxScale, scale * scaleDelta));
3891
+ if (Math.abs(scaleDelta - 1) * 100 > pinchThreshold) {
3892
+ setScale(newScale);
3893
+ setGesture("pinch");
3894
+ onPinch?.(newScale, scaleDelta - 1);
3895
+ }
3896
+ const angleDelta = currentAngle - state.startAngle;
3897
+ if (Math.abs(angleDelta) > rotateThreshold) {
3898
+ const newRotation = rotation + angleDelta;
3899
+ setRotation(newRotation);
3900
+ setGesture("rotate");
3901
+ onRotate?.(newRotation, angleDelta);
3902
+ }
3903
+ }
3904
+ }
3905
+ if (distance2 > panThreshold) {
3906
+ setGesture("pan");
3907
+ onPan?.(deltaX2, deltaY2, x - state.startX, y - state.startY);
3908
+ }
3909
+ onMove?.(x, y);
3910
+ }, [enabled, getDistance, getAngle, scale, rotation, minScale, maxScale, pinchThreshold, rotateThreshold, panThreshold, onPinch, onRotate, onPan, onMove]);
3911
+ const endGesture = useCallback((x, y) => {
3912
+ if (!enabled || !stateRef.current.isActive) return;
3913
+ const state = stateRef.current;
3914
+ const now = performance.now();
3915
+ const deltaX2 = x - state.startX;
3916
+ const deltaY2 = y - state.startY;
3917
+ const distance2 = getDistance(state.startX, state.startY, x, y);
3918
+ const velocity2 = distance2 / (now - state.startTime) * 1e3;
3919
+ if (longPressRef.current) {
3920
+ clearTimeout(longPressRef.current);
3921
+ longPressRef.current = null;
3922
+ }
3923
+ if (distance2 > swipeThreshold && velocity2 > swipeVelocity) {
3924
+ const direction = getSwipeDirection(deltaX2, deltaY2);
3925
+ if (direction && swipeDirections.includes(direction)) {
3926
+ setGesture(`swipe-${direction}`);
3927
+ onSwipe?.(direction, distance2, velocity2);
3928
+ }
3929
+ }
3930
+ if (distance2 < threshold && now - state.startTime < timeout) {
3931
+ const timeSinceLastTap = now - lastTapRef.current;
3932
+ if (timeSinceLastTap < 300) {
3933
+ onDoubleTap?.(x, y);
3934
+ lastTapRef.current = 0;
3935
+ } else {
3936
+ onTap?.(x, y);
3937
+ lastTapRef.current = now;
3938
+ }
3939
+ }
3940
+ state.isActive = false;
3941
+ setIsActive(false);
3942
+ onEnd?.(x, y);
3943
+ timeoutRef.current = window.setTimeout(() => {
3944
+ setGesture(null);
3945
+ setDeltaX(0);
3946
+ setDeltaY(0);
3947
+ setDistance(0);
3948
+ setVelocity(0);
3949
+ }, 100);
3950
+ }, [enabled, getDistance, getSwipeDirection, swipeThreshold, swipeVelocity, swipeDirections, threshold, timeout, onSwipe, onTap, onDoubleTap, onEnd]);
3951
+ const onTouchStart = useCallback((e) => {
3952
+ if (e.cancelable) {
3953
+ e.preventDefault();
3954
+ }
3955
+ const touch = e.touches[0];
3956
+ startGesture(touch.clientX, touch.clientY, e.touches.length);
3957
+ }, [startGesture]);
3958
+ const onTouchMove = useCallback((e) => {
3959
+ if (e.cancelable) {
3960
+ e.preventDefault();
3961
+ }
3962
+ const touch = e.touches[0];
3963
+ updateGesture(touch.clientX, touch.clientY, Array.from(e.touches));
3964
+ }, [updateGesture]);
3965
+ const onTouchEnd = useCallback((e) => {
3966
+ if (e.cancelable) {
3967
+ e.preventDefault();
3968
+ }
3969
+ const touch = e.changedTouches[0];
3970
+ endGesture(touch.clientX, touch.clientY);
3971
+ }, [endGesture]);
3972
+ const onMouseDown = useCallback((e) => {
3973
+ e.preventDefault();
3974
+ startGesture(e.clientX, e.clientY, 1);
3975
+ }, [startGesture]);
3976
+ const onMouseMove = useCallback((e) => {
3977
+ e.preventDefault();
3978
+ updateGesture(e.clientX, e.clientY);
3979
+ }, [updateGesture]);
3980
+ const onMouseUp = useCallback((e) => {
3981
+ e.preventDefault();
3982
+ endGesture(e.clientX, e.clientY);
3983
+ }, [endGesture]);
3984
+ const start = useCallback(() => {
3985
+ setIsActive(true);
3986
+ }, []);
3987
+ const stop = useCallback(() => {
3988
+ setIsActive(false);
3989
+ setGesture(null);
3990
+ if (longPressRef.current) {
3991
+ clearTimeout(longPressRef.current);
3992
+ longPressRef.current = null;
3993
+ }
3994
+ }, []);
3995
+ const reset = useCallback(() => {
3996
+ setIsActive(false);
3997
+ setGesture(null);
3998
+ setScale(1);
3999
+ setRotation(0);
4000
+ setDeltaX(0);
4001
+ setDeltaY(0);
4002
+ setDistance(0);
4003
+ setVelocity(0);
4004
+ if (longPressRef.current) {
4005
+ clearTimeout(longPressRef.current);
4006
+ longPressRef.current = null;
4007
+ }
4008
+ }, []);
4009
+ useEffect(() => {
4010
+ return () => {
4011
+ if (timeoutRef.current) {
4012
+ clearTimeout(timeoutRef.current);
4013
+ }
4014
+ if (longPressRef.current) {
4015
+ clearTimeout(longPressRef.current);
4016
+ }
4017
+ };
4018
+ }, []);
4019
+ return {
4020
+ isActive,
4021
+ gesture,
4022
+ scale,
4023
+ rotation,
4024
+ deltaX,
4025
+ deltaY,
4026
+ distance,
4027
+ velocity,
4028
+ start,
4029
+ stop,
4030
+ reset,
4031
+ onTouchStart,
4032
+ onTouchMove,
4033
+ onTouchEnd,
4034
+ onMouseDown,
4035
+ onMouseMove,
4036
+ onMouseUp
4037
+ };
4038
+ }
4039
+ function useGestureMotion(options) {
4040
+ const {
4041
+ gestureType,
4042
+ duration = 300,
4043
+ easing: easing2 = "ease-out",
4044
+ sensitivity = 1,
4045
+ enabled = true,
4046
+ onGestureStart,
4047
+ onGestureEnd
4048
+ } = options;
4049
+ const elementRef = useRef(null);
4050
+ const [gestureState, setGestureState] = useState({
4051
+ isActive: false,
4052
+ x: 0,
4053
+ y: 0,
4054
+ deltaX: 0,
4055
+ deltaY: 0,
4056
+ scale: 1,
4057
+ rotation: 0
4058
+ });
4059
+ const [motionStyle, setMotionStyle] = useState({});
4060
+ const startPoint = useRef({ x: 0, y: 0 });
4061
+ const isDragging = useRef(false);
4062
+ const updateMotionStyle = useCallback(() => {
4063
+ if (!enabled) return;
4064
+ const { isActive, deltaX, deltaY, scale, rotation } = gestureState;
4065
+ let transform = "";
4066
+ switch (gestureType) {
4067
+ case "hover":
4068
+ transform = isActive ? `scale(${1 + sensitivity * 0.05}) translateY(-${sensitivity * 2}px)` : "scale(1) translateY(0)";
4069
+ break;
4070
+ case "drag":
4071
+ transform = isActive ? `translate(${deltaX * sensitivity}px, ${deltaY * sensitivity}px)` : "translate(0, 0)";
4072
+ break;
4073
+ case "pinch":
4074
+ transform = `scale(${scale})`;
4075
+ break;
4076
+ case "swipe":
4077
+ transform = isActive ? `translateX(${deltaX * sensitivity}px) rotateY(${deltaX * 0.1}deg)` : "translateX(0) rotateY(0)";
4078
+ break;
4079
+ case "tilt":
4080
+ transform = isActive ? `rotateX(${deltaY * 0.1}deg) rotateY(${deltaX * 0.1}deg)` : "rotateX(0) rotateY(0)";
4081
+ break;
4082
+ }
4083
+ setMotionStyle({
4084
+ transform,
4085
+ transition: isActive ? "none" : `all ${duration}ms ${easing2}`,
4086
+ cursor: gestureType === "drag" && isActive ? "grabbing" : "pointer"
4087
+ });
4088
+ }, [gestureState, gestureType, enabled, duration, easing2, sensitivity]);
4089
+ const handleMouseDown = useCallback((e) => {
4090
+ if (!enabled || gestureType !== "drag") return;
4091
+ isDragging.current = true;
4092
+ startPoint.current = { x: e.clientX, y: e.clientY };
4093
+ setGestureState((prev) => ({ ...prev, isActive: true }));
4094
+ onGestureStart?.();
4095
+ }, [enabled, gestureType, onGestureStart]);
4096
+ const handleMouseMove = useCallback((e) => {
4097
+ if (!enabled || !isDragging.current) return;
4098
+ const deltaX = e.clientX - startPoint.current.x;
4099
+ const deltaY = e.clientY - startPoint.current.y;
4100
+ setGestureState((prev) => ({
4101
+ ...prev,
4102
+ x: e.clientX,
4103
+ y: e.clientY,
4104
+ deltaX,
4105
+ deltaY
4106
+ }));
4107
+ }, [enabled]);
4108
+ const handleMouseUp = useCallback(() => {
4109
+ if (!enabled) return;
4110
+ isDragging.current = false;
4111
+ setGestureState((prev) => ({ ...prev, isActive: false }));
4112
+ onGestureEnd?.();
4113
+ }, [enabled, onGestureEnd]);
4114
+ const handleMouseEnter = useCallback(() => {
4115
+ if (!enabled || gestureType !== "hover") return;
4116
+ setGestureState((prev) => ({ ...prev, isActive: true }));
4117
+ onGestureStart?.();
4118
+ }, [enabled, gestureType, onGestureStart]);
4119
+ const handleMouseLeave = useCallback(() => {
4120
+ if (!enabled || gestureType !== "hover") return;
4121
+ setGestureState((prev) => ({ ...prev, isActive: false }));
4122
+ onGestureEnd?.();
4123
+ }, [enabled, gestureType, onGestureEnd]);
4124
+ const handleTouchStart = useCallback((e) => {
4125
+ if (!enabled) return;
4126
+ const touch = e.touches[0];
4127
+ startPoint.current = { x: touch.clientX, y: touch.clientY };
4128
+ setGestureState((prev) => ({ ...prev, isActive: true }));
4129
+ onGestureStart?.();
4130
+ }, [enabled, onGestureStart]);
4131
+ const handleTouchMove = useCallback((e) => {
4132
+ if (!enabled) return;
4133
+ const touch = e.touches[0];
4134
+ const deltaX = touch.clientX - startPoint.current.x;
4135
+ const deltaY = touch.clientY - startPoint.current.y;
4136
+ setGestureState((prev) => ({
4137
+ ...prev,
4138
+ x: touch.clientX,
4139
+ y: touch.clientY,
4140
+ deltaX,
4141
+ deltaY
4142
+ }));
4143
+ }, [enabled]);
4144
+ const handleTouchEnd = useCallback(() => {
4145
+ if (!enabled) return;
4146
+ setGestureState((prev) => ({ ...prev, isActive: false }));
4147
+ onGestureEnd?.();
4148
+ }, [enabled, onGestureEnd]);
4149
+ useEffect(() => {
4150
+ if (!elementRef.current) return;
4151
+ const element = elementRef.current;
4152
+ if (gestureType === "hover") {
4153
+ element.addEventListener("mouseenter", handleMouseEnter);
4154
+ element.addEventListener("mouseleave", handleMouseLeave);
4155
+ } else if (gestureType === "drag") {
4156
+ element.addEventListener("mousedown", handleMouseDown);
4157
+ document.addEventListener("mousemove", handleMouseMove);
4158
+ document.addEventListener("mouseup", handleMouseUp);
4159
+ }
4160
+ element.addEventListener("touchstart", handleTouchStart);
4161
+ element.addEventListener("touchmove", handleTouchMove);
4162
+ element.addEventListener("touchend", handleTouchEnd);
4163
+ return () => {
4164
+ element.removeEventListener("mouseenter", handleMouseEnter);
4165
+ element.removeEventListener("mouseleave", handleMouseLeave);
4166
+ element.removeEventListener("mousedown", handleMouseDown);
4167
+ document.removeEventListener("mousemove", handleMouseMove);
4168
+ document.removeEventListener("mouseup", handleMouseUp);
4169
+ element.removeEventListener("touchstart", handleTouchStart);
4170
+ element.removeEventListener("touchmove", handleTouchMove);
4171
+ element.removeEventListener("touchend", handleTouchEnd);
4172
+ };
4173
+ }, [gestureType, handleMouseEnter, handleMouseLeave, handleMouseDown, handleMouseMove, handleMouseUp, handleTouchStart, handleTouchMove, handleTouchEnd]);
4174
+ useEffect(() => {
4175
+ updateMotionStyle();
4176
+ }, [updateMotionStyle]);
4177
+ return {
4178
+ ref: elementRef,
4179
+ gestureState,
4180
+ motionStyle,
4181
+ isActive: gestureState.isActive
4182
+ };
4183
+ }
4184
+
4185
+ export { MOTION_PRESETS, MotionEngine, PAGE_MOTIONS, PerformanceOptimizer, TransitionEffects, applyEasing, easeIn, easeInOut, easeInOutQuad, easeInQuad, easeOut, easeOutQuad, easingPresets, getAvailableEasings, getEasing, getMotionPreset, getPagePreset, getPresetEasing, isEasingFunction, isValidEasing, linear, mergeWithPreset, motionEngine, performanceOptimizer, safeApplyEasing, transitionEffects, useBounceIn, useClickToggle, useFadeIn, useFocusToggle, useGesture, useGestureMotion, useGradient, useHoverMotion, useInView, useMotionState, useMouse, usePageMotions, usePulse, useReducedMotion, useRepeat, useScaleIn, useScrollProgress, useScrollReveal, useSimplePageMotion, useSlideDown, useSlideLeft, useSlideRight, useSlideUp, useSmartMotion, useSpringMotion, useToggleMotion, useUnifiedMotion, useWindowSize };
4186
+ //# sourceMappingURL=index.mjs.map
4187
+ //# sourceMappingURL=index.mjs.map