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