@hua-labs/motion-core 2.3.0 → 2.4.0
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.
- package/README.md +128 -39
- package/dist/chunk-47QAGQLN.mjs +1057 -0
- package/dist/chunk-47QAGQLN.mjs.map +1 -0
- package/dist/chunk-LSIP7MB5.cjs +1090 -0
- package/dist/chunk-LSIP7MB5.cjs.map +1 -0
- package/dist/index.cjs +7532 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +607 -676
- package/dist/index.d.ts +1558 -0
- package/dist/index.mjs +2732 -1177
- package/dist/index.mjs.map +1 -1
- package/dist/native.cjs +692 -0
- package/dist/native.cjs.map +1 -0
- package/dist/native.d.mts +177 -0
- package/dist/native.d.ts +177 -0
- package/dist/native.mjs +555 -0
- package/dist/native.mjs.map +1 -0
- package/dist/springPhysics-DLyZ4nbx.d.mts +687 -0
- package/dist/springPhysics-DLyZ4nbx.d.ts +687 -0
- package/package.json +28 -8
package/dist/index.mjs
CHANGED
|
@@ -1,559 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { getPagePreset, getMotionPreset, mergeWithPreset, motionStateManager, MOTION_PRESETS, useMotionProfile, getEasing, calculateSpring } from './chunk-47QAGQLN.mjs';
|
|
3
|
+
export { MOTION_PRESETS, MotionEngine, MotionProfileProvider, PAGE_MOTIONS, TransitionEffects, applyEasing, calculateSpring, easeIn, easeInOut, easeInOutQuad, easeInQuad, easeOut, easeOutQuad, easingPresets, getAvailableEasings, getEasing, getMotionPreset, getPagePreset, getPresetEasing, hua, isEasingFunction, isValidEasing, linear, mergeProfileOverrides, mergeWithPreset, motionEngine, neutral, resolveProfile, safeApplyEasing, transitionEffects, useMotionProfile } from './chunk-47QAGQLN.mjs';
|
|
4
|
+
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
3
5
|
import { jsx } from 'react/jsx-runtime';
|
|
4
6
|
|
|
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 executeTransition(element, options, config) {
|
|
201
|
-
const transitionId = this.generateTransitionId();
|
|
202
|
-
config.setup();
|
|
203
|
-
this.enableGPUAcceleration(element);
|
|
204
|
-
let resolveTransition;
|
|
205
|
-
const completed = new Promise((resolve) => {
|
|
206
|
-
resolveTransition = resolve;
|
|
207
|
-
});
|
|
208
|
-
const motionId = await motionEngine.motion(
|
|
209
|
-
element,
|
|
210
|
-
[
|
|
211
|
-
{ progress: 0, properties: config.keyframes[0] },
|
|
212
|
-
{ progress: 1, properties: config.keyframes[1] }
|
|
213
|
-
],
|
|
214
|
-
{
|
|
215
|
-
duration: options.duration,
|
|
216
|
-
easing: options.easing || this.getDefaultEasing(),
|
|
217
|
-
delay: options.delay,
|
|
218
|
-
onStart: options.onTransitionStart,
|
|
219
|
-
onUpdate: config.onUpdate,
|
|
220
|
-
onComplete: () => {
|
|
221
|
-
config.onCleanup();
|
|
222
|
-
options.onTransitionComplete?.();
|
|
223
|
-
this.activeTransitions.delete(transitionId);
|
|
224
|
-
resolveTransition();
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
);
|
|
228
|
-
this.activeTransitions.set(transitionId, motionId);
|
|
229
|
-
return completed;
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* 페이드 인/아웃 전환
|
|
233
|
-
*/
|
|
234
|
-
async fade(element, options) {
|
|
235
|
-
const initialOpacity = parseFloat(getComputedStyle(element).opacity) || 1;
|
|
236
|
-
const targetOpacity = options.direction === "reverse" ? 0 : 1;
|
|
237
|
-
return this.executeTransition(element, options, {
|
|
238
|
-
setup: () => {
|
|
239
|
-
if (options.direction === "reverse") {
|
|
240
|
-
element.style.opacity = initialOpacity.toString();
|
|
241
|
-
} else {
|
|
242
|
-
element.style.opacity = "0";
|
|
243
|
-
}
|
|
244
|
-
},
|
|
245
|
-
keyframes: [
|
|
246
|
-
{ opacity: options.direction === "reverse" ? initialOpacity : 0 },
|
|
247
|
-
{ opacity: targetOpacity }
|
|
248
|
-
],
|
|
249
|
-
onUpdate: (progress) => {
|
|
250
|
-
const currentOpacity = options.direction === "reverse" ? initialOpacity * (1 - progress) : targetOpacity * progress;
|
|
251
|
-
element.style.opacity = currentOpacity.toString();
|
|
252
|
-
},
|
|
253
|
-
onCleanup: () => {
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* 슬라이드 전환
|
|
259
|
-
*/
|
|
260
|
-
async slide(element, options) {
|
|
261
|
-
const distance = options.distance || 100;
|
|
262
|
-
const initialTransform = getComputedStyle(element).transform;
|
|
263
|
-
const isReverse = options.direction === "reverse";
|
|
264
|
-
return this.executeTransition(element, options, {
|
|
265
|
-
setup: () => {
|
|
266
|
-
if (!isReverse) {
|
|
267
|
-
element.style.transform = `translateX(${distance}px)`;
|
|
268
|
-
}
|
|
269
|
-
},
|
|
270
|
-
keyframes: [
|
|
271
|
-
{ translateX: isReverse ? 0 : distance },
|
|
272
|
-
{ translateX: isReverse ? distance : 0 }
|
|
273
|
-
],
|
|
274
|
-
onUpdate: (progress) => {
|
|
275
|
-
const currentTranslateX = isReverse ? distance * progress : distance * (1 - progress);
|
|
276
|
-
element.style.transform = `translateX(${currentTranslateX}px)`;
|
|
277
|
-
},
|
|
278
|
-
onCleanup: () => {
|
|
279
|
-
element.style.transform = initialTransform;
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* 스케일 전환
|
|
285
|
-
*/
|
|
286
|
-
async scale(element, options) {
|
|
287
|
-
const scaleValue = options.scale || 0.8;
|
|
288
|
-
const initialTransform = getComputedStyle(element).transform;
|
|
289
|
-
const isReverse = options.direction === "reverse";
|
|
290
|
-
return this.executeTransition(element, options, {
|
|
291
|
-
setup: () => {
|
|
292
|
-
if (!isReverse) {
|
|
293
|
-
element.style.transform = `scale(${scaleValue})`;
|
|
294
|
-
}
|
|
295
|
-
},
|
|
296
|
-
keyframes: [
|
|
297
|
-
{ scale: isReverse ? 1 : scaleValue },
|
|
298
|
-
{ scale: isReverse ? scaleValue : 1 }
|
|
299
|
-
],
|
|
300
|
-
onUpdate: (progress) => {
|
|
301
|
-
const currentScale = isReverse ? 1 - (1 - scaleValue) * progress : scaleValue + (1 - scaleValue) * progress;
|
|
302
|
-
element.style.transform = `scale(${currentScale})`;
|
|
303
|
-
},
|
|
304
|
-
onCleanup: () => {
|
|
305
|
-
element.style.transform = initialTransform;
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* 플립 전환 (3D 회전)
|
|
311
|
-
*/
|
|
312
|
-
async flip(element, options) {
|
|
313
|
-
const perspective = options.perspective || 1e3;
|
|
314
|
-
const initialTransform = getComputedStyle(element).transform;
|
|
315
|
-
const isReverse = options.direction === "reverse";
|
|
316
|
-
return this.executeTransition(element, options, {
|
|
317
|
-
setup: () => {
|
|
318
|
-
element.style.perspective = `${perspective}px`;
|
|
319
|
-
element.style.transformStyle = "preserve-3d";
|
|
320
|
-
if (!isReverse) {
|
|
321
|
-
element.style.transform = `rotateY(90deg)`;
|
|
322
|
-
}
|
|
323
|
-
},
|
|
324
|
-
keyframes: [
|
|
325
|
-
{ rotateY: isReverse ? 0 : 90 },
|
|
326
|
-
{ rotateY: isReverse ? 90 : 0 }
|
|
327
|
-
],
|
|
328
|
-
onUpdate: (progress) => {
|
|
329
|
-
const currentRotateY = isReverse ? 90 * progress : 90 * (1 - progress);
|
|
330
|
-
element.style.transform = `rotateY(${currentRotateY}deg)`;
|
|
331
|
-
},
|
|
332
|
-
onCleanup: () => {
|
|
333
|
-
element.style.transform = initialTransform;
|
|
334
|
-
element.style.perspective = "";
|
|
335
|
-
element.style.transformStyle = "";
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* 큐브 전환 (3D 큐브 회전)
|
|
341
|
-
*/
|
|
342
|
-
async cube(element, options) {
|
|
343
|
-
const perspective = options.perspective || 1200;
|
|
344
|
-
const initialTransform = getComputedStyle(element).transform;
|
|
345
|
-
const isReverse = options.direction === "reverse";
|
|
346
|
-
return this.executeTransition(element, options, {
|
|
347
|
-
setup: () => {
|
|
348
|
-
element.style.perspective = `${perspective}px`;
|
|
349
|
-
element.style.transformStyle = "preserve-3d";
|
|
350
|
-
if (!isReverse) {
|
|
351
|
-
element.style.transform = `rotateX(90deg) rotateY(45deg)`;
|
|
352
|
-
}
|
|
353
|
-
},
|
|
354
|
-
keyframes: [
|
|
355
|
-
{ rotateX: isReverse ? 0 : 90, rotateY: isReverse ? 0 : 45 },
|
|
356
|
-
{ rotateX: isReverse ? 90 : 0, rotateY: isReverse ? 45 : 0 }
|
|
357
|
-
],
|
|
358
|
-
onUpdate: (progress) => {
|
|
359
|
-
const currentRotateX = isReverse ? 90 * progress : 90 * (1 - progress);
|
|
360
|
-
const currentRotateY = isReverse ? 45 * progress : 45 * (1 - progress);
|
|
361
|
-
element.style.transform = `rotateX(${currentRotateX}deg) rotateY(${currentRotateY}deg)`;
|
|
362
|
-
},
|
|
363
|
-
onCleanup: () => {
|
|
364
|
-
element.style.transform = initialTransform;
|
|
365
|
-
element.style.perspective = "";
|
|
366
|
-
element.style.transformStyle = "";
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* 모프 전환 (복합 변형)
|
|
372
|
-
*/
|
|
373
|
-
async morph(element, options) {
|
|
374
|
-
const initialTransform = getComputedStyle(element).transform;
|
|
375
|
-
const isReverse = options.direction === "reverse";
|
|
376
|
-
return this.executeTransition(element, options, {
|
|
377
|
-
setup: () => {
|
|
378
|
-
if (!isReverse) {
|
|
379
|
-
element.style.transform = `scale(0.9) rotate(5deg)`;
|
|
380
|
-
}
|
|
381
|
-
},
|
|
382
|
-
keyframes: [
|
|
383
|
-
{ scale: isReverse ? 1 : 0.9, rotate: isReverse ? 0 : 5 },
|
|
384
|
-
{ scale: isReverse ? 0.9 : 1, rotate: isReverse ? 5 : 0 }
|
|
385
|
-
],
|
|
386
|
-
onUpdate: (progress) => {
|
|
387
|
-
const currentScale = isReverse ? 1 - 0.1 * progress : 0.9 + 0.1 * progress;
|
|
388
|
-
const currentRotate = isReverse ? 5 * progress : 5 * (1 - progress);
|
|
389
|
-
element.style.transform = `scale(${currentScale}) rotate(${currentRotate}deg)`;
|
|
390
|
-
},
|
|
391
|
-
onCleanup: () => {
|
|
392
|
-
element.style.transform = initialTransform;
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* 전환 중지
|
|
398
|
-
*/
|
|
399
|
-
stopTransition(transitionId) {
|
|
400
|
-
const motionId = this.activeTransitions.get(transitionId);
|
|
401
|
-
if (motionId) {
|
|
402
|
-
motionEngine.stop(motionId);
|
|
403
|
-
this.activeTransitions.delete(transitionId);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* 모든 전환 중지
|
|
408
|
-
*/
|
|
409
|
-
stopAllTransitions() {
|
|
410
|
-
this.activeTransitions.forEach((motionId) => {
|
|
411
|
-
motionEngine.stop(motionId);
|
|
412
|
-
});
|
|
413
|
-
this.activeTransitions.clear();
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* 활성 전환 수 확인
|
|
417
|
-
*/
|
|
418
|
-
getActiveTransitionCount() {
|
|
419
|
-
return this.activeTransitions.size;
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* GPU 가속 활성화
|
|
423
|
-
*/
|
|
424
|
-
enableGPUAcceleration(element) {
|
|
425
|
-
element.style.willChange = "transform, opacity";
|
|
426
|
-
const currentTransform = element.style.transform;
|
|
427
|
-
if (currentTransform && currentTransform !== "none" && currentTransform !== "") {
|
|
428
|
-
if (!currentTransform.includes("translateZ")) {
|
|
429
|
-
element.style.transform = `${currentTransform} translateZ(0)`;
|
|
430
|
-
}
|
|
431
|
-
} else {
|
|
432
|
-
element.style.transform = "translateZ(0)";
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* 기본 이징 함수
|
|
437
|
-
*/
|
|
438
|
-
getDefaultEasing() {
|
|
439
|
-
return (t) => t * t * (3 - 2 * t);
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* 고유 전환 ID 생성
|
|
443
|
-
*/
|
|
444
|
-
generateTransitionId() {
|
|
445
|
-
return `transition_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* 정리
|
|
449
|
-
*/
|
|
450
|
-
destroy() {
|
|
451
|
-
this.stopAllTransitions();
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
var transitionEffects = TransitionEffects.getInstance();
|
|
455
|
-
|
|
456
|
-
// src/presets/index.ts
|
|
457
|
-
var MOTION_PRESETS = {
|
|
458
|
-
hero: {
|
|
459
|
-
entrance: "fadeIn",
|
|
460
|
-
delay: 200,
|
|
461
|
-
duration: 800,
|
|
462
|
-
hover: false,
|
|
463
|
-
click: false
|
|
464
|
-
},
|
|
465
|
-
title: {
|
|
466
|
-
entrance: "slideUp",
|
|
467
|
-
delay: 400,
|
|
468
|
-
duration: 700,
|
|
469
|
-
hover: false,
|
|
470
|
-
click: false
|
|
471
|
-
},
|
|
472
|
-
button: {
|
|
473
|
-
entrance: "scaleIn",
|
|
474
|
-
delay: 600,
|
|
475
|
-
duration: 300,
|
|
476
|
-
hover: true,
|
|
477
|
-
click: true
|
|
478
|
-
},
|
|
479
|
-
card: {
|
|
480
|
-
entrance: "slideUp",
|
|
481
|
-
delay: 800,
|
|
482
|
-
duration: 500,
|
|
483
|
-
hover: true,
|
|
484
|
-
click: false
|
|
485
|
-
},
|
|
486
|
-
text: {
|
|
487
|
-
entrance: "fadeIn",
|
|
488
|
-
delay: 200,
|
|
489
|
-
duration: 600,
|
|
490
|
-
hover: false,
|
|
491
|
-
click: false
|
|
492
|
-
},
|
|
493
|
-
image: {
|
|
494
|
-
entrance: "scaleIn",
|
|
495
|
-
delay: 400,
|
|
496
|
-
duration: 600,
|
|
497
|
-
hover: true,
|
|
498
|
-
click: false
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
var PAGE_MOTIONS = {
|
|
502
|
-
// 홈페이지
|
|
503
|
-
home: {
|
|
504
|
-
hero: { type: "hero" },
|
|
505
|
-
title: { type: "title" },
|
|
506
|
-
description: { type: "text" },
|
|
507
|
-
cta: { type: "button" },
|
|
508
|
-
feature1: { type: "card" },
|
|
509
|
-
feature2: { type: "card" },
|
|
510
|
-
feature3: { type: "card" }
|
|
511
|
-
},
|
|
512
|
-
// 대시보드
|
|
513
|
-
dashboard: {
|
|
514
|
-
header: { type: "hero" },
|
|
515
|
-
sidebar: { type: "card", entrance: "slideLeft" },
|
|
516
|
-
main: { type: "text", entrance: "fadeIn" },
|
|
517
|
-
card1: { type: "card" },
|
|
518
|
-
card2: { type: "card" },
|
|
519
|
-
card3: { type: "card" },
|
|
520
|
-
chart: { type: "image" }
|
|
521
|
-
},
|
|
522
|
-
// 제품 페이지
|
|
523
|
-
product: {
|
|
524
|
-
hero: { type: "hero" },
|
|
525
|
-
title: { type: "title" },
|
|
526
|
-
image: { type: "image" },
|
|
527
|
-
description: { type: "text" },
|
|
528
|
-
price: { type: "text" },
|
|
529
|
-
buyButton: { type: "button" },
|
|
530
|
-
features: { type: "card" }
|
|
531
|
-
},
|
|
532
|
-
// 블로그
|
|
533
|
-
blog: {
|
|
534
|
-
header: { type: "hero" },
|
|
535
|
-
title: { type: "title" },
|
|
536
|
-
content: { type: "text" },
|
|
537
|
-
sidebar: { type: "card", entrance: "slideRight" },
|
|
538
|
-
related1: { type: "card" },
|
|
539
|
-
related2: { type: "card" },
|
|
540
|
-
related3: { type: "card" }
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
|
-
function mergeWithPreset(preset, custom = {}) {
|
|
544
|
-
return {
|
|
545
|
-
...preset,
|
|
546
|
-
...custom
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
function getPagePreset(pageType) {
|
|
550
|
-
return PAGE_MOTIONS[pageType];
|
|
551
|
-
}
|
|
552
|
-
function getMotionPreset(type) {
|
|
553
|
-
return MOTION_PRESETS[type] || MOTION_PRESETS.text;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// src/hooks/useSimplePageMotion.ts
|
|
557
7
|
function useSimplePageMotion(pageType) {
|
|
558
8
|
const config = getPagePreset(pageType);
|
|
559
9
|
return useSimplePageMotions(config);
|
|
@@ -658,156 +108,6 @@ function useSimplePageMotions(config) {
|
|
|
658
108
|
}, [motions]);
|
|
659
109
|
return getPageMotionRefs();
|
|
660
110
|
}
|
|
661
|
-
|
|
662
|
-
// src/managers/MotionStateManager.ts
|
|
663
|
-
var MotionStateManager = class {
|
|
664
|
-
constructor() {
|
|
665
|
-
this.states = /* @__PURE__ */ new Map();
|
|
666
|
-
this.listeners = /* @__PURE__ */ new Map();
|
|
667
|
-
}
|
|
668
|
-
/**
|
|
669
|
-
* 요소의 상태를 초기화
|
|
670
|
-
*/
|
|
671
|
-
initializeElement(elementId, config) {
|
|
672
|
-
const initialState = {
|
|
673
|
-
internalVisibility: false,
|
|
674
|
-
// 초기에 숨김 상태로 시작 (스크롤 리빌용)
|
|
675
|
-
triggeredVisibility: false,
|
|
676
|
-
// Intersection Observer가 아직 트리거되지 않음
|
|
677
|
-
finalVisibility: false,
|
|
678
|
-
// 초기에 숨김 상태로 시작
|
|
679
|
-
opacity: 0,
|
|
680
|
-
// 초기에 투명 상태로 시작
|
|
681
|
-
translateY: 20,
|
|
682
|
-
// 초기에 아래로 이동된 상태로 시작
|
|
683
|
-
translateX: 0,
|
|
684
|
-
scale: 0.95,
|
|
685
|
-
// 초기에 약간 축소된 상태로 시작
|
|
686
|
-
rotation: 0,
|
|
687
|
-
isHovered: false,
|
|
688
|
-
isClicked: false,
|
|
689
|
-
isAnimating: false
|
|
690
|
-
};
|
|
691
|
-
this.states.set(elementId, initialState);
|
|
692
|
-
this.computeFinalState(elementId);
|
|
693
|
-
}
|
|
694
|
-
/**
|
|
695
|
-
* 내부 가시성 상태 업데이트 (초기화, 리셋 등)
|
|
696
|
-
*/
|
|
697
|
-
setInternalVisibility(elementId, visible) {
|
|
698
|
-
const state = this.states.get(elementId);
|
|
699
|
-
if (!state) return;
|
|
700
|
-
state.internalVisibility = visible;
|
|
701
|
-
this.computeFinalState(elementId);
|
|
702
|
-
this.notifyListeners(elementId, state);
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* 외부 트리거 가시성 상태 업데이트 (Intersection Observer)
|
|
706
|
-
*/
|
|
707
|
-
setTriggeredVisibility(elementId, visible) {
|
|
708
|
-
const state = this.states.get(elementId);
|
|
709
|
-
if (!state) return;
|
|
710
|
-
state.triggeredVisibility = visible;
|
|
711
|
-
this.computeFinalState(elementId);
|
|
712
|
-
this.notifyListeners(elementId, state);
|
|
713
|
-
}
|
|
714
|
-
/**
|
|
715
|
-
* 모션 값 업데이트
|
|
716
|
-
*/
|
|
717
|
-
updateMotionValues(elementId, values) {
|
|
718
|
-
const state = this.states.get(elementId);
|
|
719
|
-
if (!state) return;
|
|
720
|
-
Object.assign(state, values);
|
|
721
|
-
this.notifyListeners(elementId, state);
|
|
722
|
-
}
|
|
723
|
-
/**
|
|
724
|
-
* 최종 상태 계산
|
|
725
|
-
*/
|
|
726
|
-
computeFinalState(elementId) {
|
|
727
|
-
const state = this.states.get(elementId);
|
|
728
|
-
if (!state) return;
|
|
729
|
-
state.finalVisibility = state.internalVisibility || state.triggeredVisibility;
|
|
730
|
-
state.isAnimating = state.finalVisibility && (state.opacity < 1 || state.translateY > 0);
|
|
731
|
-
}
|
|
732
|
-
/**
|
|
733
|
-
* 현재 상태 조회
|
|
734
|
-
*/
|
|
735
|
-
getState(elementId) {
|
|
736
|
-
return this.states.get(elementId);
|
|
737
|
-
}
|
|
738
|
-
/**
|
|
739
|
-
* 모든 상태 조회
|
|
740
|
-
*/
|
|
741
|
-
getAllStates() {
|
|
742
|
-
return new Map(this.states);
|
|
743
|
-
}
|
|
744
|
-
/**
|
|
745
|
-
* 상태 변경 리스너 등록
|
|
746
|
-
*/
|
|
747
|
-
subscribe(elementId, listener) {
|
|
748
|
-
if (!this.listeners.has(elementId)) {
|
|
749
|
-
this.listeners.set(elementId, /* @__PURE__ */ new Set());
|
|
750
|
-
}
|
|
751
|
-
this.listeners.get(elementId).add(listener);
|
|
752
|
-
return () => {
|
|
753
|
-
const listeners = this.listeners.get(elementId);
|
|
754
|
-
if (listeners) {
|
|
755
|
-
listeners.delete(listener);
|
|
756
|
-
}
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* 리스너들에게 상태 변경 알림
|
|
761
|
-
*/
|
|
762
|
-
notifyListeners(elementId, state) {
|
|
763
|
-
const listeners = this.listeners.get(elementId);
|
|
764
|
-
if (!listeners) return;
|
|
765
|
-
listeners.forEach((listener) => {
|
|
766
|
-
try {
|
|
767
|
-
listener(state);
|
|
768
|
-
} catch (error) {
|
|
769
|
-
if (process.env.NODE_ENV === "development") {
|
|
770
|
-
console.error(`MotionStateManager listener error for ${elementId}:`, error);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
});
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* 모든 상태 초기화
|
|
777
|
-
*/
|
|
778
|
-
reset() {
|
|
779
|
-
this.states.clear();
|
|
780
|
-
this.listeners.clear();
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* 특정 요소 상태 초기화
|
|
784
|
-
*/
|
|
785
|
-
resetElement(elementId) {
|
|
786
|
-
this.states.delete(elementId);
|
|
787
|
-
this.listeners.delete(elementId);
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* 디버그용 상태 출력
|
|
791
|
-
*/
|
|
792
|
-
debug() {
|
|
793
|
-
if (process.env.NODE_ENV === "development") {
|
|
794
|
-
console.log("MotionStateManager Debug:");
|
|
795
|
-
this.states.forEach((state, elementId) => {
|
|
796
|
-
console.log(` ${elementId}:`, {
|
|
797
|
-
internalVisibility: state.internalVisibility,
|
|
798
|
-
triggeredVisibility: state.triggeredVisibility,
|
|
799
|
-
finalVisibility: state.finalVisibility,
|
|
800
|
-
opacity: state.opacity,
|
|
801
|
-
translateY: state.translateY,
|
|
802
|
-
isAnimating: state.isAnimating
|
|
803
|
-
});
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
};
|
|
808
|
-
var motionStateManager = new MotionStateManager();
|
|
809
|
-
|
|
810
|
-
// src/hooks/usePageMotions.ts
|
|
811
111
|
function usePageMotions(config) {
|
|
812
112
|
const [motions, setMotions] = useState(/* @__PURE__ */ new Map());
|
|
813
113
|
const observersRef = useRef(/* @__PURE__ */ new Map());
|
|
@@ -1300,155 +600,6 @@ function useSmartMotion(options = {}) {
|
|
|
1300
600
|
};
|
|
1301
601
|
}
|
|
1302
602
|
|
|
1303
|
-
// src/profiles/neutral.ts
|
|
1304
|
-
var neutral = {
|
|
1305
|
-
name: "neutral",
|
|
1306
|
-
base: {
|
|
1307
|
-
duration: 700,
|
|
1308
|
-
easing: "ease-out",
|
|
1309
|
-
threshold: 0.1,
|
|
1310
|
-
triggerOnce: true
|
|
1311
|
-
},
|
|
1312
|
-
entrance: {
|
|
1313
|
-
slide: {
|
|
1314
|
-
distance: 32,
|
|
1315
|
-
easing: "ease-out"
|
|
1316
|
-
},
|
|
1317
|
-
fade: {
|
|
1318
|
-
initialOpacity: 0
|
|
1319
|
-
},
|
|
1320
|
-
scale: {
|
|
1321
|
-
from: 0.95
|
|
1322
|
-
},
|
|
1323
|
-
bounce: {
|
|
1324
|
-
intensity: 0.3,
|
|
1325
|
-
easing: "cubic-bezier(0.34, 1.56, 0.64, 1)"
|
|
1326
|
-
}
|
|
1327
|
-
},
|
|
1328
|
-
stagger: {
|
|
1329
|
-
perItem: 100,
|
|
1330
|
-
baseDelay: 0
|
|
1331
|
-
},
|
|
1332
|
-
interaction: {
|
|
1333
|
-
hover: {
|
|
1334
|
-
scale: 1.05,
|
|
1335
|
-
y: -2,
|
|
1336
|
-
duration: 200,
|
|
1337
|
-
easing: "ease-out"
|
|
1338
|
-
}
|
|
1339
|
-
},
|
|
1340
|
-
spring: {
|
|
1341
|
-
mass: 1,
|
|
1342
|
-
stiffness: 100,
|
|
1343
|
-
damping: 10,
|
|
1344
|
-
restDelta: 0.01,
|
|
1345
|
-
restSpeed: 0.01
|
|
1346
|
-
},
|
|
1347
|
-
reducedMotion: "fade-only"
|
|
1348
|
-
};
|
|
1349
|
-
|
|
1350
|
-
// src/profiles/hua.ts
|
|
1351
|
-
var hua = {
|
|
1352
|
-
name: "hua",
|
|
1353
|
-
base: {
|
|
1354
|
-
duration: 640,
|
|
1355
|
-
easing: "cubic-bezier(0.22, 0.68, 0.35, 1.10)",
|
|
1356
|
-
threshold: 0.12,
|
|
1357
|
-
triggerOnce: true
|
|
1358
|
-
},
|
|
1359
|
-
entrance: {
|
|
1360
|
-
slide: {
|
|
1361
|
-
distance: 28,
|
|
1362
|
-
easing: "cubic-bezier(0.22, 0.68, 0.35, 1.14)"
|
|
1363
|
-
},
|
|
1364
|
-
fade: {
|
|
1365
|
-
initialOpacity: 0
|
|
1366
|
-
},
|
|
1367
|
-
scale: {
|
|
1368
|
-
from: 0.97
|
|
1369
|
-
},
|
|
1370
|
-
bounce: {
|
|
1371
|
-
intensity: 0.2,
|
|
1372
|
-
easing: "cubic-bezier(0.22, 0.68, 0.35, 1.12)"
|
|
1373
|
-
}
|
|
1374
|
-
},
|
|
1375
|
-
stagger: {
|
|
1376
|
-
perItem: 80,
|
|
1377
|
-
baseDelay: 0
|
|
1378
|
-
},
|
|
1379
|
-
interaction: {
|
|
1380
|
-
hover: {
|
|
1381
|
-
scale: 1.008,
|
|
1382
|
-
y: -1,
|
|
1383
|
-
duration: 180,
|
|
1384
|
-
easing: "cubic-bezier(0.22, 0.68, 0.35, 1.10)"
|
|
1385
|
-
}
|
|
1386
|
-
},
|
|
1387
|
-
spring: {
|
|
1388
|
-
mass: 1,
|
|
1389
|
-
stiffness: 180,
|
|
1390
|
-
damping: 18,
|
|
1391
|
-
restDelta: 5e-3,
|
|
1392
|
-
restSpeed: 5e-3
|
|
1393
|
-
},
|
|
1394
|
-
reducedMotion: "fade-only"
|
|
1395
|
-
};
|
|
1396
|
-
|
|
1397
|
-
// src/profiles/index.ts
|
|
1398
|
-
var PROFILES = {
|
|
1399
|
-
neutral,
|
|
1400
|
-
hua
|
|
1401
|
-
};
|
|
1402
|
-
function resolveProfile(profile) {
|
|
1403
|
-
if (typeof profile === "string") {
|
|
1404
|
-
return PROFILES[profile] ?? neutral;
|
|
1405
|
-
}
|
|
1406
|
-
return profile;
|
|
1407
|
-
}
|
|
1408
|
-
function mergeProfileOverrides(base, overrides) {
|
|
1409
|
-
return deepMerge(
|
|
1410
|
-
base,
|
|
1411
|
-
overrides
|
|
1412
|
-
);
|
|
1413
|
-
}
|
|
1414
|
-
function deepMerge(target, source) {
|
|
1415
|
-
const result = { ...target };
|
|
1416
|
-
for (const key of Object.keys(source)) {
|
|
1417
|
-
const sourceVal = source[key];
|
|
1418
|
-
const targetVal = result[key];
|
|
1419
|
-
if (sourceVal !== null && sourceVal !== void 0 && typeof sourceVal === "object" && !Array.isArray(sourceVal) && typeof targetVal === "object" && targetVal !== null && !Array.isArray(targetVal)) {
|
|
1420
|
-
result[key] = deepMerge(
|
|
1421
|
-
targetVal,
|
|
1422
|
-
sourceVal
|
|
1423
|
-
);
|
|
1424
|
-
} else if (sourceVal !== void 0) {
|
|
1425
|
-
result[key] = sourceVal;
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
return result;
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
// src/profiles/MotionProfileContext.ts
|
|
1432
|
-
var MotionProfileContext = createContext(neutral);
|
|
1433
|
-
function MotionProfileProvider({
|
|
1434
|
-
profile = "neutral",
|
|
1435
|
-
overrides,
|
|
1436
|
-
children
|
|
1437
|
-
}) {
|
|
1438
|
-
const resolved = useMemo(() => {
|
|
1439
|
-
const base = resolveProfile(profile);
|
|
1440
|
-
return overrides ? mergeProfileOverrides(base, overrides) : base;
|
|
1441
|
-
}, [profile, overrides]);
|
|
1442
|
-
return createElement(
|
|
1443
|
-
MotionProfileContext.Provider,
|
|
1444
|
-
{ value: resolved },
|
|
1445
|
-
children
|
|
1446
|
-
);
|
|
1447
|
-
}
|
|
1448
|
-
function useMotionProfile() {
|
|
1449
|
-
return useContext(MotionProfileContext);
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
603
|
// src/utils/sharedIntersectionObserver.ts
|
|
1453
604
|
var pool = /* @__PURE__ */ new Map();
|
|
1454
605
|
function makeKey(threshold, rootMargin) {
|
|
@@ -1541,8 +692,8 @@ function getInitialStyle(type, distance) {
|
|
|
1541
692
|
function getVisibleStyle() {
|
|
1542
693
|
return { opacity: 1, transform: "none" };
|
|
1543
694
|
}
|
|
1544
|
-
function getEasingForType(type,
|
|
1545
|
-
if (
|
|
695
|
+
function getEasingForType(type, easing) {
|
|
696
|
+
if (easing) return easing;
|
|
1546
697
|
if (type === "bounceIn") return "cubic-bezier(0.34, 1.56, 0.64, 1)";
|
|
1547
698
|
return "ease-out";
|
|
1548
699
|
}
|
|
@@ -1608,8 +759,8 @@ function getMultiEffectVisibleStyle(effects) {
|
|
|
1608
759
|
}
|
|
1609
760
|
return style;
|
|
1610
761
|
}
|
|
1611
|
-
function getMultiEffectEasing(effects,
|
|
1612
|
-
if (
|
|
762
|
+
function getMultiEffectEasing(effects, easing) {
|
|
763
|
+
if (easing) return easing;
|
|
1613
764
|
if (effects.bounce) return "cubic-bezier(0.34, 1.56, 0.64, 1)";
|
|
1614
765
|
return "ease-out";
|
|
1615
766
|
}
|
|
@@ -1621,7 +772,7 @@ function useUnifiedMotion(options) {
|
|
|
1621
772
|
duration = profile.base.duration,
|
|
1622
773
|
autoStart = true,
|
|
1623
774
|
delay = 0,
|
|
1624
|
-
easing
|
|
775
|
+
easing,
|
|
1625
776
|
threshold = profile.base.threshold,
|
|
1626
777
|
triggerOnce = profile.base.triggerOnce,
|
|
1627
778
|
distance = profile.entrance.slide.distance,
|
|
@@ -1631,7 +782,7 @@ function useUnifiedMotion(options) {
|
|
|
1631
782
|
onReset
|
|
1632
783
|
} = options;
|
|
1633
784
|
const resolvedType = type ?? "fadeIn";
|
|
1634
|
-
const resolvedEasing = getEasingForType(resolvedType,
|
|
785
|
+
const resolvedEasing = getEasingForType(resolvedType, easing);
|
|
1635
786
|
const ref = useRef(null);
|
|
1636
787
|
const [isVisible, setIsVisible] = useState(false);
|
|
1637
788
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
@@ -1683,7 +834,7 @@ function useUnifiedMotion(options) {
|
|
|
1683
834
|
const style = useMemo(() => {
|
|
1684
835
|
if (effects) {
|
|
1685
836
|
const base2 = isVisible ? getMultiEffectVisibleStyle(effects) : getMultiEffectInitialStyle(effects, distance);
|
|
1686
|
-
const resolvedEasingMulti = getMultiEffectEasing(effects,
|
|
837
|
+
const resolvedEasingMulti = getMultiEffectEasing(effects, easing);
|
|
1687
838
|
return {
|
|
1688
839
|
...base2,
|
|
1689
840
|
transition: `all ${duration}ms ${resolvedEasingMulti}`,
|
|
@@ -1702,7 +853,7 @@ function useUnifiedMotion(options) {
|
|
|
1702
853
|
"--motion-easing": resolvedEasing,
|
|
1703
854
|
"--motion-progress": `${progress}`
|
|
1704
855
|
};
|
|
1705
|
-
}, [isVisible, type, effects, distance, duration, resolvedEasing,
|
|
856
|
+
}, [isVisible, type, effects, distance, duration, resolvedEasing, easing, delay, progress, resolvedType]);
|
|
1706
857
|
return {
|
|
1707
858
|
ref,
|
|
1708
859
|
isVisible,
|
|
@@ -1721,7 +872,7 @@ function useFadeIn(options = {}) {
|
|
|
1721
872
|
duration = profile.base.duration,
|
|
1722
873
|
threshold = profile.base.threshold,
|
|
1723
874
|
triggerOnce = profile.base.triggerOnce,
|
|
1724
|
-
easing
|
|
875
|
+
easing = profile.base.easing,
|
|
1725
876
|
autoStart = true,
|
|
1726
877
|
initialOpacity = profile.entrance.fade.initialOpacity,
|
|
1727
878
|
targetOpacity = 1,
|
|
@@ -1809,12 +960,12 @@ function useFadeIn(options = {}) {
|
|
|
1809
960
|
}, [stop]);
|
|
1810
961
|
const style = useMemo(() => ({
|
|
1811
962
|
opacity: isVisible ? targetOpacity : initialOpacity,
|
|
1812
|
-
transition: `opacity ${duration}ms ${
|
|
963
|
+
transition: `opacity ${duration}ms ${easing}`,
|
|
1813
964
|
"--motion-delay": `${delay}ms`,
|
|
1814
965
|
"--motion-duration": `${duration}ms`,
|
|
1815
|
-
"--motion-easing":
|
|
966
|
+
"--motion-easing": easing,
|
|
1816
967
|
"--motion-progress": `${progress}`
|
|
1817
|
-
}), [isVisible, targetOpacity, initialOpacity, duration,
|
|
968
|
+
}), [isVisible, targetOpacity, initialOpacity, duration, easing, delay, progress]);
|
|
1818
969
|
return {
|
|
1819
970
|
ref,
|
|
1820
971
|
isVisible,
|
|
@@ -1833,7 +984,7 @@ function useSlideUp(options = {}) {
|
|
|
1833
984
|
duration = profile.base.duration,
|
|
1834
985
|
threshold = profile.base.threshold,
|
|
1835
986
|
triggerOnce = profile.base.triggerOnce,
|
|
1836
|
-
easing
|
|
987
|
+
easing = profile.entrance.slide.easing,
|
|
1837
988
|
autoStart = true,
|
|
1838
989
|
direction = "up",
|
|
1839
990
|
distance = profile.entrance.slide.distance,
|
|
@@ -1935,14 +1086,14 @@ function useSlideUp(options = {}) {
|
|
|
1935
1086
|
const style = useMemo(() => ({
|
|
1936
1087
|
opacity: isVisible ? 1 : 0,
|
|
1937
1088
|
transform: isVisible ? finalTransform : initialTransform,
|
|
1938
|
-
transition: `opacity ${duration}ms ${
|
|
1089
|
+
transition: `opacity ${duration}ms ${easing}, transform ${duration}ms ${easing}`,
|
|
1939
1090
|
"--motion-delay": `${delay}ms`,
|
|
1940
1091
|
"--motion-duration": `${duration}ms`,
|
|
1941
|
-
"--motion-easing":
|
|
1092
|
+
"--motion-easing": easing,
|
|
1942
1093
|
"--motion-progress": `${progress}`,
|
|
1943
1094
|
"--motion-direction": direction,
|
|
1944
1095
|
"--motion-distance": `${distance}px`
|
|
1945
|
-
}), [isVisible, initialTransform, finalTransform, duration,
|
|
1096
|
+
}), [isVisible, initialTransform, finalTransform, duration, easing, delay, progress, direction, distance]);
|
|
1946
1097
|
return {
|
|
1947
1098
|
ref,
|
|
1948
1099
|
isVisible,
|
|
@@ -1972,7 +1123,7 @@ function useScaleIn(options = {}) {
|
|
|
1972
1123
|
duration = profile.base.duration,
|
|
1973
1124
|
delay = 0,
|
|
1974
1125
|
autoStart = true,
|
|
1975
|
-
easing
|
|
1126
|
+
easing = profile.base.easing,
|
|
1976
1127
|
threshold = profile.base.threshold,
|
|
1977
1128
|
triggerOnce = profile.base.triggerOnce,
|
|
1978
1129
|
onComplete,
|
|
@@ -2050,12 +1201,12 @@ function useScaleIn(options = {}) {
|
|
|
2050
1201
|
const style = useMemo(() => ({
|
|
2051
1202
|
transform: `scale(${scale})`,
|
|
2052
1203
|
opacity,
|
|
2053
|
-
transition: `all ${duration}ms ${
|
|
1204
|
+
transition: `all ${duration}ms ${easing}`,
|
|
2054
1205
|
"--motion-delay": `${delay}ms`,
|
|
2055
1206
|
"--motion-duration": `${duration}ms`,
|
|
2056
|
-
"--motion-easing":
|
|
1207
|
+
"--motion-easing": easing,
|
|
2057
1208
|
"--motion-progress": `${progress}`
|
|
2058
|
-
}), [scale, opacity, duration,
|
|
1209
|
+
}), [scale, opacity, duration, easing, delay, progress]);
|
|
2059
1210
|
return {
|
|
2060
1211
|
ref,
|
|
2061
1212
|
isVisible,
|
|
@@ -2076,7 +1227,7 @@ function useBounceIn(options = {}) {
|
|
|
2076
1227
|
intensity = profile.entrance.bounce.intensity,
|
|
2077
1228
|
threshold = profile.base.threshold,
|
|
2078
1229
|
triggerOnce = profile.base.triggerOnce,
|
|
2079
|
-
easing
|
|
1230
|
+
easing = profile.entrance.bounce.easing,
|
|
2080
1231
|
onComplete,
|
|
2081
1232
|
onStart,
|
|
2082
1233
|
onStop,
|
|
@@ -2161,12 +1312,12 @@ function useBounceIn(options = {}) {
|
|
|
2161
1312
|
const style = useMemo(() => ({
|
|
2162
1313
|
transform: `scale(${scale})`,
|
|
2163
1314
|
opacity,
|
|
2164
|
-
transition: `all ${duration}ms ${
|
|
1315
|
+
transition: `all ${duration}ms ${easing}`,
|
|
2165
1316
|
"--motion-delay": `${delay}ms`,
|
|
2166
1317
|
"--motion-duration": `${duration}ms`,
|
|
2167
|
-
"--motion-easing":
|
|
1318
|
+
"--motion-easing": easing,
|
|
2168
1319
|
"--motion-progress": `${progress}`
|
|
2169
|
-
}), [scale, opacity, duration,
|
|
1320
|
+
}), [scale, opacity, duration, easing, delay, progress]);
|
|
2170
1321
|
return {
|
|
2171
1322
|
ref,
|
|
2172
1323
|
isVisible,
|
|
@@ -2178,201 +1329,6 @@ function useBounceIn(options = {}) {
|
|
|
2178
1329
|
stop
|
|
2179
1330
|
};
|
|
2180
1331
|
}
|
|
2181
|
-
|
|
2182
|
-
// src/utils/easing.ts
|
|
2183
|
-
var linear = (t) => t;
|
|
2184
|
-
var easeIn = (t) => t * t;
|
|
2185
|
-
var easeOut = (t) => 1 - (1 - t) * (1 - t);
|
|
2186
|
-
var easeInOut = (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
2187
|
-
var easeInQuad = (t) => t * t;
|
|
2188
|
-
var easeOutQuad = (t) => 1 - (1 - t) * (1 - t);
|
|
2189
|
-
var easeInOutQuad = (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
2190
|
-
var easeInCubic = (t) => t * t * t;
|
|
2191
|
-
var easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
|
|
2192
|
-
var easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
2193
|
-
var easeInQuart = (t) => t * t * t * t;
|
|
2194
|
-
var easeOutQuart = (t) => 1 - Math.pow(1 - t, 4);
|
|
2195
|
-
var easeInOutQuart = (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
|
|
2196
|
-
var easeInQuint = (t) => t * t * t * t * t;
|
|
2197
|
-
var easeOutQuint = (t) => 1 - Math.pow(1 - t, 5);
|
|
2198
|
-
var easeInOutQuint = (t) => t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2;
|
|
2199
|
-
var easeInSine = (t) => 1 - Math.cos(t * Math.PI / 2);
|
|
2200
|
-
var easeOutSine = (t) => Math.sin(t * Math.PI / 2);
|
|
2201
|
-
var easeInOutSine = (t) => -(Math.cos(Math.PI * t) - 1) / 2;
|
|
2202
|
-
var easeInExpo = (t) => t === 0 ? 0 : Math.pow(2, 10 * t - 10);
|
|
2203
|
-
var easeOutExpo = (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
|
|
2204
|
-
var easeInOutExpo = (t) => {
|
|
2205
|
-
if (t === 0) return 0;
|
|
2206
|
-
if (t === 1) return 1;
|
|
2207
|
-
if (t < 0.5) return Math.pow(2, 20 * t - 10) / 2;
|
|
2208
|
-
return (2 - Math.pow(2, -20 * t + 10)) / 2;
|
|
2209
|
-
};
|
|
2210
|
-
var easeInCirc = (t) => 1 - Math.sqrt(1 - Math.pow(t, 2));
|
|
2211
|
-
var easeOutCirc = (t) => Math.sqrt(1 - Math.pow(t - 1, 2));
|
|
2212
|
-
var easeInOutCirc = (t) => {
|
|
2213
|
-
if (t < 0.5) return (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2;
|
|
2214
|
-
return (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
|
|
2215
|
-
};
|
|
2216
|
-
var easeInBounce = (t) => 1 - easeOutBounce(1 - t);
|
|
2217
|
-
var easeOutBounce = (t) => {
|
|
2218
|
-
const n1 = 7.5625;
|
|
2219
|
-
const d1 = 2.75;
|
|
2220
|
-
if (t < 1 / d1) {
|
|
2221
|
-
return n1 * t * t;
|
|
2222
|
-
} else if (t < 2 / d1) {
|
|
2223
|
-
return n1 * (t -= 1.5 / d1) * t + 0.75;
|
|
2224
|
-
} else if (t < 2.5 / d1) {
|
|
2225
|
-
return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
|
2226
|
-
} else {
|
|
2227
|
-
return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
|
2228
|
-
}
|
|
2229
|
-
};
|
|
2230
|
-
var easeInOutBounce = (t) => {
|
|
2231
|
-
if (t < 0.5) return (1 - easeOutBounce(1 - 2 * t)) / 2;
|
|
2232
|
-
return (1 + easeOutBounce(2 * t - 1)) / 2;
|
|
2233
|
-
};
|
|
2234
|
-
var easeInBack = (t) => {
|
|
2235
|
-
const c1 = 1.70158;
|
|
2236
|
-
const c3 = c1 + 1;
|
|
2237
|
-
return c3 * t * t * t - c1 * t * t;
|
|
2238
|
-
};
|
|
2239
|
-
var easeOutBack = (t) => {
|
|
2240
|
-
const c1 = 1.70158;
|
|
2241
|
-
const c3 = c1 + 1;
|
|
2242
|
-
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
2243
|
-
};
|
|
2244
|
-
var easeInOutBack = (t) => {
|
|
2245
|
-
const c1 = 1.70158;
|
|
2246
|
-
const c2 = c1 * 1.525;
|
|
2247
|
-
if (t < 0.5) {
|
|
2248
|
-
return Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2) / 2;
|
|
2249
|
-
} else {
|
|
2250
|
-
return (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
|
|
2251
|
-
}
|
|
2252
|
-
};
|
|
2253
|
-
var easeInElastic = (t) => {
|
|
2254
|
-
const c4 = 2 * Math.PI / 3;
|
|
2255
|
-
if (t === 0) return 0;
|
|
2256
|
-
if (t === 1) return 1;
|
|
2257
|
-
return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 0.75) * c4);
|
|
2258
|
-
};
|
|
2259
|
-
var easeOutElastic = (t) => {
|
|
2260
|
-
const c4 = 2 * Math.PI / 3;
|
|
2261
|
-
if (t === 0) return 0;
|
|
2262
|
-
if (t === 1) return 1;
|
|
2263
|
-
return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
|
2264
|
-
};
|
|
2265
|
-
var easeInOutElastic = (t) => {
|
|
2266
|
-
const c5 = 2 * Math.PI / 4.5;
|
|
2267
|
-
if (t === 0) return 0;
|
|
2268
|
-
if (t === 1) return 1;
|
|
2269
|
-
if (t < 0.5) {
|
|
2270
|
-
return -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2;
|
|
2271
|
-
} else {
|
|
2272
|
-
return Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5) / 2 + 1;
|
|
2273
|
-
}
|
|
2274
|
-
};
|
|
2275
|
-
var pulse = (t) => Math.sin(t * Math.PI) * 0.5 + 0.5;
|
|
2276
|
-
var pulseSmooth = (t) => Math.sin(t * Math.PI * 2) * 0.3 + 0.7;
|
|
2277
|
-
var skeletonWave = (t) => Math.sin((t - 0.5) * Math.PI * 2) * 0.5 + 0.5;
|
|
2278
|
-
var blink = (t) => t < 0.5 ? 1 : 0;
|
|
2279
|
-
var easing = {
|
|
2280
|
-
linear,
|
|
2281
|
-
easeIn,
|
|
2282
|
-
easeOut,
|
|
2283
|
-
easeInOut,
|
|
2284
|
-
easeInQuad,
|
|
2285
|
-
easeOutQuad,
|
|
2286
|
-
easeInOutQuad,
|
|
2287
|
-
easeInCubic,
|
|
2288
|
-
easeOutCubic,
|
|
2289
|
-
easeInOutCubic,
|
|
2290
|
-
easeInQuart,
|
|
2291
|
-
easeOutQuart,
|
|
2292
|
-
easeInOutQuart,
|
|
2293
|
-
easeInQuint,
|
|
2294
|
-
easeOutQuint,
|
|
2295
|
-
easeInOutQuint,
|
|
2296
|
-
easeInSine,
|
|
2297
|
-
easeOutSine,
|
|
2298
|
-
easeInOutSine,
|
|
2299
|
-
easeInExpo,
|
|
2300
|
-
easeOutExpo,
|
|
2301
|
-
easeInOutExpo,
|
|
2302
|
-
easeInCirc,
|
|
2303
|
-
easeOutCirc,
|
|
2304
|
-
easeInOutCirc,
|
|
2305
|
-
easeInBounce,
|
|
2306
|
-
easeOutBounce,
|
|
2307
|
-
easeInOutBounce,
|
|
2308
|
-
easeInBack,
|
|
2309
|
-
easeOutBack,
|
|
2310
|
-
easeInOutBack,
|
|
2311
|
-
easeInElastic,
|
|
2312
|
-
easeOutElastic,
|
|
2313
|
-
easeInOutElastic,
|
|
2314
|
-
pulse,
|
|
2315
|
-
pulseSmooth,
|
|
2316
|
-
skeletonWave,
|
|
2317
|
-
blink
|
|
2318
|
-
};
|
|
2319
|
-
function isValidEasing(easingName) {
|
|
2320
|
-
return easingName in easing;
|
|
2321
|
-
}
|
|
2322
|
-
function getEasing(easingName) {
|
|
2323
|
-
if (typeof easingName === "function") {
|
|
2324
|
-
return easingName;
|
|
2325
|
-
}
|
|
2326
|
-
if (typeof easingName === "string") {
|
|
2327
|
-
if (easingName in easing) {
|
|
2328
|
-
return easing[easingName];
|
|
2329
|
-
}
|
|
2330
|
-
if (easingName === "bounce") {
|
|
2331
|
-
return easing.easeOutBounce;
|
|
2332
|
-
}
|
|
2333
|
-
if (process.env.NODE_ENV === "development") {
|
|
2334
|
-
console.warn(`[HUA Motion] Unknown easing "${easingName}", using default "easeOut".`);
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
return easeOut;
|
|
2338
|
-
}
|
|
2339
|
-
function applyEasing(t, easingName) {
|
|
2340
|
-
const easingFn = getEasing(easingName);
|
|
2341
|
-
return easingFn(t);
|
|
2342
|
-
}
|
|
2343
|
-
function safeApplyEasing(t, easingName) {
|
|
2344
|
-
try {
|
|
2345
|
-
const easingFn = getEasing(easingName);
|
|
2346
|
-
return easingFn(t);
|
|
2347
|
-
} catch (err) {
|
|
2348
|
-
if (process.env.NODE_ENV === "development") {
|
|
2349
|
-
console.error(`[HUA Motion] Failed to apply easing "${easingName}":`, err);
|
|
2350
|
-
}
|
|
2351
|
-
return easeOut(t);
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
function getAvailableEasings() {
|
|
2355
|
-
return Object.keys(easing);
|
|
2356
|
-
}
|
|
2357
|
-
function isEasingFunction(value) {
|
|
2358
|
-
return typeof value === "function";
|
|
2359
|
-
}
|
|
2360
|
-
var easingPresets = {
|
|
2361
|
-
default: "easeOut",
|
|
2362
|
-
smooth: "easeInOutCubic",
|
|
2363
|
-
fast: "easeOutQuad",
|
|
2364
|
-
slow: "easeInOutSine",
|
|
2365
|
-
bouncy: "easeOutBounce",
|
|
2366
|
-
elastic: "easeOutElastic",
|
|
2367
|
-
fade: "easeInOut",
|
|
2368
|
-
scale: "easeOutBack"
|
|
2369
|
-
};
|
|
2370
|
-
function getPresetEasing(preset) {
|
|
2371
|
-
const easingName = easingPresets[preset];
|
|
2372
|
-
return getEasing(easingName);
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
// src/hooks/usePulse.ts
|
|
2376
1332
|
function usePulse(options = {}) {
|
|
2377
1333
|
const {
|
|
2378
1334
|
duration = 3e3,
|
|
@@ -2476,20 +1432,6 @@ function usePulse(options = {}) {
|
|
|
2476
1432
|
reset
|
|
2477
1433
|
};
|
|
2478
1434
|
}
|
|
2479
|
-
|
|
2480
|
-
// src/utils/springPhysics.ts
|
|
2481
|
-
function calculateSpring(currentValue, currentVelocity, targetValue, deltaTime, config) {
|
|
2482
|
-
const { stiffness, damping, mass } = config;
|
|
2483
|
-
const displacement = currentValue - targetValue;
|
|
2484
|
-
const springForce = -stiffness * displacement;
|
|
2485
|
-
const dampingForce = -damping * currentVelocity;
|
|
2486
|
-
const acceleration = (springForce + dampingForce) / mass;
|
|
2487
|
-
const newVelocity = currentVelocity + acceleration * deltaTime;
|
|
2488
|
-
const newValue = currentValue + newVelocity * deltaTime;
|
|
2489
|
-
return { value: newValue, velocity: newVelocity };
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
// src/hooks/useSpringMotion.ts
|
|
2493
1435
|
function useSpringMotion(options) {
|
|
2494
1436
|
const profile = useMotionProfile();
|
|
2495
1437
|
const {
|
|
@@ -2629,7 +1571,7 @@ function useGradient(options = {}) {
|
|
|
2629
1571
|
duration = 6e3,
|
|
2630
1572
|
direction = "diagonal",
|
|
2631
1573
|
size = 300,
|
|
2632
|
-
easing
|
|
1574
|
+
easing = "ease-in-out",
|
|
2633
1575
|
autoStart = false
|
|
2634
1576
|
} = options;
|
|
2635
1577
|
const ref = useRef(null);
|
|
@@ -2646,10 +1588,10 @@ function useGradient(options = {}) {
|
|
|
2646
1588
|
return {
|
|
2647
1589
|
background,
|
|
2648
1590
|
backgroundSize,
|
|
2649
|
-
animation: isAnimating ? `gradientShift ${duration}ms ${
|
|
1591
|
+
animation: isAnimating ? `gradientShift ${duration}ms ${easing} infinite` : "none",
|
|
2650
1592
|
backgroundPosition: isAnimating ? void 0 : `${motionProgress}% 50%`
|
|
2651
1593
|
};
|
|
2652
|
-
}, [colors, direction, size, duration,
|
|
1594
|
+
}, [colors, direction, size, duration, easing, isAnimating, motionProgress]);
|
|
2653
1595
|
const start = useCallback(() => {
|
|
2654
1596
|
setIsAnimating(true);
|
|
2655
1597
|
}, []);
|
|
@@ -2697,7 +1639,7 @@ function useHoverMotion(options = {}) {
|
|
|
2697
1639
|
const profile = useMotionProfile();
|
|
2698
1640
|
const {
|
|
2699
1641
|
duration = profile.interaction.hover.duration,
|
|
2700
|
-
easing
|
|
1642
|
+
easing = profile.interaction.hover.easing,
|
|
2701
1643
|
hoverScale = profile.interaction.hover.scale,
|
|
2702
1644
|
hoverY = profile.interaction.hover.y,
|
|
2703
1645
|
hoverOpacity = 1
|
|
@@ -2731,8 +1673,8 @@ function useHoverMotion(options = {}) {
|
|
|
2731
1673
|
const style = useMemo(() => ({
|
|
2732
1674
|
transform: isHovered ? `scale(${hoverScale}) translateY(${hoverY}px)` : "scale(1) translateY(0px)",
|
|
2733
1675
|
opacity: isHovered ? hoverOpacity : 1,
|
|
2734
|
-
transition: `transform ${duration}ms ${
|
|
2735
|
-
}), [isHovered, hoverScale, hoverY, hoverOpacity, duration,
|
|
1676
|
+
transition: `transform ${duration}ms ${easing}, opacity ${duration}ms ${easing}`
|
|
1677
|
+
}), [isHovered, hoverScale, hoverY, hoverOpacity, duration, easing]);
|
|
2736
1678
|
const start = useCallback(() => {
|
|
2737
1679
|
setIsHovered(true);
|
|
2738
1680
|
setIsAnimating(true);
|
|
@@ -2990,7 +1932,7 @@ function useScrollReveal(options = {}) {
|
|
|
2990
1932
|
triggerOnce = profile.base.triggerOnce,
|
|
2991
1933
|
delay = 0,
|
|
2992
1934
|
duration = profile.base.duration,
|
|
2993
|
-
easing
|
|
1935
|
+
easing = profile.base.easing,
|
|
2994
1936
|
motionType = "fadeIn",
|
|
2995
1937
|
onComplete,
|
|
2996
1938
|
onStart,
|
|
@@ -3028,7 +1970,7 @@ function useScrollReveal(options = {}) {
|
|
|
3028
1970
|
const slideDistance = profile.entrance.slide.distance;
|
|
3029
1971
|
const scaleFrom = profile.entrance.scale.from;
|
|
3030
1972
|
const style = useMemo(() => {
|
|
3031
|
-
const baseTransition = `all ${duration}ms ${
|
|
1973
|
+
const baseTransition = `all ${duration}ms ${easing}`;
|
|
3032
1974
|
if (!isVisible) {
|
|
3033
1975
|
switch (motionType) {
|
|
3034
1976
|
case "fadeIn":
|
|
@@ -3078,7 +2020,7 @@ function useScrollReveal(options = {}) {
|
|
|
3078
2020
|
transform: "none",
|
|
3079
2021
|
transition: baseTransition
|
|
3080
2022
|
};
|
|
3081
|
-
}, [isVisible, motionType, duration,
|
|
2023
|
+
}, [isVisible, motionType, duration, easing, slideDistance, scaleFrom]);
|
|
3082
2024
|
const start = useCallback(() => {
|
|
3083
2025
|
setIsAnimating(true);
|
|
3084
2026
|
onStart?.();
|
|
@@ -3300,14 +2242,7 @@ function useMotionState(options = {}) {
|
|
|
3300
2242
|
const seek = useCallback((targetProgress) => {
|
|
3301
2243
|
const clampedProgress = Math.max(0, Math.min(100, targetProgress));
|
|
3302
2244
|
setProgress(clampedProgress);
|
|
3303
|
-
|
|
3304
|
-
if (currentDirection === "reverse") {
|
|
3305
|
-
targetElapsed = (100 - clampedProgress) / 100 * duration;
|
|
3306
|
-
} else if (currentDirection === "alternate") {
|
|
3307
|
-
targetElapsed = clampedProgress / 100 * duration;
|
|
3308
|
-
} else {
|
|
3309
|
-
targetElapsed = clampedProgress / 100 * duration;
|
|
3310
|
-
}
|
|
2245
|
+
const targetElapsed = currentDirection === "reverse" ? (100 - clampedProgress) / 100 * duration : clampedProgress / 100 * duration;
|
|
3311
2246
|
setElapsed(targetElapsed);
|
|
3312
2247
|
if (state === "playing" && startTimeRef.current) {
|
|
3313
2248
|
const currentTime = performance.now();
|
|
@@ -3476,7 +2411,7 @@ function useRepeat(options = {}) {
|
|
|
3476
2411
|
};
|
|
3477
2412
|
}
|
|
3478
2413
|
function useToggleMotion(options = {}) {
|
|
3479
|
-
const { duration = 300, delay = 0, easing
|
|
2414
|
+
const { duration = 300, delay = 0, easing = "ease-in-out" } = options;
|
|
3480
2415
|
const ref = useRef(null);
|
|
3481
2416
|
const [isVisible, setIsVisible] = useState(false);
|
|
3482
2417
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
@@ -3506,8 +2441,8 @@ function useToggleMotion(options = {}) {
|
|
|
3506
2441
|
const style = useMemo(() => ({
|
|
3507
2442
|
opacity: isVisible ? 1 : 0,
|
|
3508
2443
|
transform: isVisible ? "translateY(0) scale(1)" : "translateY(10px) scale(0.95)",
|
|
3509
|
-
transition: `opacity ${duration}ms ${
|
|
3510
|
-
}), [isVisible, duration,
|
|
2444
|
+
transition: `opacity ${duration}ms ${easing} ${delay}ms, transform ${duration}ms ${easing} ${delay}ms`
|
|
2445
|
+
}), [isVisible, duration, easing, delay]);
|
|
3511
2446
|
return {
|
|
3512
2447
|
ref,
|
|
3513
2448
|
isVisible,
|
|
@@ -3978,7 +2913,7 @@ function useGestureMotion(options) {
|
|
|
3978
2913
|
const {
|
|
3979
2914
|
gestureType,
|
|
3980
2915
|
duration = 300,
|
|
3981
|
-
easing
|
|
2916
|
+
easing = "ease-out",
|
|
3982
2917
|
sensitivity = 1,
|
|
3983
2918
|
enabled = true,
|
|
3984
2919
|
onGestureStart,
|
|
@@ -4020,10 +2955,10 @@ function useGestureMotion(options) {
|
|
|
4020
2955
|
}
|
|
4021
2956
|
setMotionStyle({
|
|
4022
2957
|
transform,
|
|
4023
|
-
transition: isActive ? "none" : `all ${duration}ms ${
|
|
2958
|
+
transition: isActive ? "none" : `all ${duration}ms ${easing}`,
|
|
4024
2959
|
cursor: gestureType === "drag" && isActive ? "grabbing" : "pointer"
|
|
4025
2960
|
});
|
|
4026
|
-
}, [gestureState, gestureType, enabled, duration,
|
|
2961
|
+
}, [gestureState, gestureType, enabled, duration, easing, sensitivity]);
|
|
4027
2962
|
const handleMouseDown = useCallback((e) => {
|
|
4028
2963
|
if (!enabled || gestureType !== "drag") return;
|
|
4029
2964
|
isDragging.current = true;
|
|
@@ -4122,7 +3057,7 @@ function useGestureMotion(options) {
|
|
|
4122
3057
|
function useButtonEffect(options = {}) {
|
|
4123
3058
|
const {
|
|
4124
3059
|
duration = 200,
|
|
4125
|
-
easing
|
|
3060
|
+
easing = "ease-out",
|
|
4126
3061
|
type = "scale",
|
|
4127
3062
|
scaleAmount = 0.95,
|
|
4128
3063
|
rippleColor = "rgba(255, 255, 255, 0.6)",
|
|
@@ -4396,7 +3331,7 @@ function useButtonEffect(options = {}) {
|
|
|
4396
3331
|
`,
|
|
4397
3332
|
boxShadow,
|
|
4398
3333
|
opacity: disabled ? disabledOpacity : 1,
|
|
4399
|
-
transition: `all ${duration}ms ${
|
|
3334
|
+
transition: `all ${duration}ms ${easing}`,
|
|
4400
3335
|
willChange: "transform, box-shadow, opacity",
|
|
4401
3336
|
cursor: disabled ? "not-allowed" : "pointer",
|
|
4402
3337
|
position: "relative",
|
|
@@ -4433,7 +3368,7 @@ function useButtonEffect(options = {}) {
|
|
|
4433
3368
|
function useVisibilityToggle(options = {}) {
|
|
4434
3369
|
const {
|
|
4435
3370
|
duration = 300,
|
|
4436
|
-
easing
|
|
3371
|
+
easing = "ease-out",
|
|
4437
3372
|
showScale = 1,
|
|
4438
3373
|
showOpacity = 1,
|
|
4439
3374
|
showRotate = 0,
|
|
@@ -4521,7 +3456,7 @@ function useVisibilityToggle(options = {}) {
|
|
|
4521
3456
|
translate(${isVisible ? showTranslateX : hideTranslateX}px, ${isVisible ? showTranslateY : hideTranslateY}px)
|
|
4522
3457
|
`,
|
|
4523
3458
|
opacity: isVisible ? showOpacity : hideOpacity,
|
|
4524
|
-
transition: `all ${duration}ms ${
|
|
3459
|
+
transition: `all ${duration}ms ${easing}`,
|
|
4525
3460
|
willChange: "transform, opacity"
|
|
4526
3461
|
};
|
|
4527
3462
|
return {
|
|
@@ -4544,7 +3479,7 @@ function useVisibilityToggle(options = {}) {
|
|
|
4544
3479
|
function useScrollToggle(options = {}) {
|
|
4545
3480
|
const {
|
|
4546
3481
|
duration = 300,
|
|
4547
|
-
easing
|
|
3482
|
+
easing = "ease-out",
|
|
4548
3483
|
showScale = 1,
|
|
4549
3484
|
showOpacity = 1,
|
|
4550
3485
|
showRotate = 0,
|
|
@@ -4653,7 +3588,7 @@ function useScrollToggle(options = {}) {
|
|
|
4653
3588
|
translate(${isVisible ? showTranslateX : hideTranslateX}px, ${isVisible ? showTranslateY : hideTranslateY}px)
|
|
4654
3589
|
`,
|
|
4655
3590
|
opacity: isVisible ? showOpacity : hideOpacity,
|
|
4656
|
-
transition: `all ${duration}ms ${
|
|
3591
|
+
transition: `all ${duration}ms ${easing}`,
|
|
4657
3592
|
willChange: "transform, opacity"
|
|
4658
3593
|
};
|
|
4659
3594
|
return {
|
|
@@ -4672,7 +3607,7 @@ function useScrollToggle(options = {}) {
|
|
|
4672
3607
|
function useCardList(options = {}) {
|
|
4673
3608
|
const {
|
|
4674
3609
|
duration = 500,
|
|
4675
|
-
easing
|
|
3610
|
+
easing = "ease-out",
|
|
4676
3611
|
staggerDelay = 100,
|
|
4677
3612
|
cardScale = 1,
|
|
4678
3613
|
cardOpacity = 1,
|
|
@@ -4714,7 +3649,7 @@ function useCardList(options = {}) {
|
|
|
4714
3649
|
translate(${isCardVisible ? cardTranslateX : initialTranslateX}px, ${isCardVisible ? cardTranslateY : initialTranslateY}px)
|
|
4715
3650
|
`,
|
|
4716
3651
|
opacity: isCardVisible ? cardOpacity : initialOpacity,
|
|
4717
|
-
transition: `all ${duration}ms ${
|
|
3652
|
+
transition: `all ${duration}ms ${easing} ${delay}ms`,
|
|
4718
3653
|
willChange: "transform, opacity"
|
|
4719
3654
|
};
|
|
4720
3655
|
});
|
|
@@ -4793,7 +3728,7 @@ function useCardList(options = {}) {
|
|
|
4793
3728
|
function useLoadingSpinner(options = {}) {
|
|
4794
3729
|
const {
|
|
4795
3730
|
duration = 1e3,
|
|
4796
|
-
easing
|
|
3731
|
+
easing = "linear",
|
|
4797
3732
|
type = "rotate",
|
|
4798
3733
|
rotationSpeed = 1,
|
|
4799
3734
|
pulseSpeed = 1,
|
|
@@ -4957,7 +3892,7 @@ function useLoadingSpinner(options = {}) {
|
|
|
4957
3892
|
borderTop: `${thickness}px solid ${color}`,
|
|
4958
3893
|
borderRadius: "50%",
|
|
4959
3894
|
transform: `rotate(${rotationAngle}deg)`,
|
|
4960
|
-
transition: `transform ${duration}ms ${
|
|
3895
|
+
transition: `transform ${duration}ms ${easing}`
|
|
4961
3896
|
};
|
|
4962
3897
|
case "pulse":
|
|
4963
3898
|
return {
|
|
@@ -4965,7 +3900,7 @@ function useLoadingSpinner(options = {}) {
|
|
|
4965
3900
|
backgroundColor: color,
|
|
4966
3901
|
borderRadius: "50%",
|
|
4967
3902
|
transform: `scale(${pulseScale})`,
|
|
4968
|
-
transition: `transform ${duration}ms ${
|
|
3903
|
+
transition: `transform ${duration}ms ${easing}`
|
|
4969
3904
|
};
|
|
4970
3905
|
case "bounce":
|
|
4971
3906
|
return {
|
|
@@ -4973,7 +3908,7 @@ function useLoadingSpinner(options = {}) {
|
|
|
4973
3908
|
backgroundColor: color,
|
|
4974
3909
|
borderRadius: "50%",
|
|
4975
3910
|
transform: `translateY(${bounceOffset}px)`,
|
|
4976
|
-
transition: `transform ${duration}ms ${
|
|
3911
|
+
transition: `transform ${duration}ms ${easing}`
|
|
4977
3912
|
};
|
|
4978
3913
|
case "wave":
|
|
4979
3914
|
return {
|
|
@@ -5028,7 +3963,7 @@ function useLoadingSpinner(options = {}) {
|
|
|
5028
3963
|
function useNavigation(options = {}) {
|
|
5029
3964
|
const {
|
|
5030
3965
|
duration = 300,
|
|
5031
|
-
easing
|
|
3966
|
+
easing = "ease-out",
|
|
5032
3967
|
type = "slide",
|
|
5033
3968
|
slideDirection = "left",
|
|
5034
3969
|
staggerDelay = 50,
|
|
@@ -5184,14 +4119,14 @@ function useNavigation(options = {}) {
|
|
|
5184
4119
|
translate(${translateX}px, ${translateY}px)
|
|
5185
4120
|
`,
|
|
5186
4121
|
opacity,
|
|
5187
|
-
transition: `all ${duration}ms ${
|
|
4122
|
+
transition: `all ${duration}ms ${easing} ${delay}ms`,
|
|
5188
4123
|
willChange: "transform, opacity",
|
|
5189
4124
|
cursor: "pointer"
|
|
5190
4125
|
};
|
|
5191
4126
|
});
|
|
5192
4127
|
const getNavigationStyle = () => {
|
|
5193
4128
|
const baseStyle = {
|
|
5194
|
-
transition: `all ${duration}ms ${
|
|
4129
|
+
transition: `all ${duration}ms ${easing}`,
|
|
5195
4130
|
willChange: "transform, opacity"
|
|
5196
4131
|
};
|
|
5197
4132
|
switch (type) {
|
|
@@ -5245,7 +4180,7 @@ function useSkeleton(options = {}) {
|
|
|
5245
4180
|
const {
|
|
5246
4181
|
delay = 0,
|
|
5247
4182
|
duration = 1500,
|
|
5248
|
-
easing
|
|
4183
|
+
easing = "ease-in-out",
|
|
5249
4184
|
autoStart = true,
|
|
5250
4185
|
backgroundColor = "#f0f0f0",
|
|
5251
4186
|
highlightColor = "#e0e0e0",
|
|
@@ -5254,7 +4189,7 @@ function useSkeleton(options = {}) {
|
|
|
5254
4189
|
width = "100%",
|
|
5255
4190
|
borderRadius = 4,
|
|
5256
4191
|
wave = true,
|
|
5257
|
-
pulse
|
|
4192
|
+
pulse = false,
|
|
5258
4193
|
onComplete,
|
|
5259
4194
|
onStart,
|
|
5260
4195
|
onStop,
|
|
@@ -5337,13 +4272,13 @@ function useSkeleton(options = {}) {
|
|
|
5337
4272
|
position: "relative",
|
|
5338
4273
|
overflow: "hidden",
|
|
5339
4274
|
opacity: isVisible ? 1 : 0,
|
|
5340
|
-
transition: `opacity ${duration}ms ${
|
|
4275
|
+
transition: `opacity ${duration}ms ${easing}`
|
|
5341
4276
|
};
|
|
5342
4277
|
if (wave && isAnimating) {
|
|
5343
4278
|
baseStyle.background = `linear-gradient(90deg, ${backgroundColor} 25%, ${highlightColor} 50%, ${backgroundColor} 75%)`;
|
|
5344
4279
|
baseStyle.backgroundSize = "200% 100%";
|
|
5345
4280
|
baseStyle.animation = `skeleton-wave ${motionSpeed}ms infinite linear`;
|
|
5346
|
-
} else if (
|
|
4281
|
+
} else if (pulse && isAnimating) {
|
|
5347
4282
|
baseStyle.animation = `skeleton-pulse ${motionSpeed}ms infinite ease-in-out`;
|
|
5348
4283
|
}
|
|
5349
4284
|
return baseStyle;
|
|
@@ -5668,52 +4603,2672 @@ function useElementProgress(options = {}) {
|
|
|
5668
4603
|
}, [start, end, clamp]);
|
|
5669
4604
|
return { ref, progress, isInView };
|
|
5670
4605
|
}
|
|
5671
|
-
function
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
const
|
|
5694
|
-
const
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
4606
|
+
function useAutoFade(options = {}) {
|
|
4607
|
+
const {
|
|
4608
|
+
initialOpacity = 0,
|
|
4609
|
+
targetOpacity = 1,
|
|
4610
|
+
duration = 1e3,
|
|
4611
|
+
delay = 0,
|
|
4612
|
+
repeat = false,
|
|
4613
|
+
repeatDelay = 1e3,
|
|
4614
|
+
repeatCount = -1,
|
|
4615
|
+
ease = "ease-in-out",
|
|
4616
|
+
autoStart = true,
|
|
4617
|
+
onComplete,
|
|
4618
|
+
onRepeat,
|
|
4619
|
+
showOnMount = false
|
|
4620
|
+
} = options;
|
|
4621
|
+
const [opacity, setOpacity] = useState(showOnMount ? initialOpacity : 0);
|
|
4622
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
4623
|
+
const [isVisible, setIsVisible] = useState(
|
|
4624
|
+
showOnMount ? initialOpacity > 0 : false
|
|
4625
|
+
);
|
|
4626
|
+
const [mounted, setMounted] = useState(false);
|
|
4627
|
+
const motionRef = useRef(null);
|
|
4628
|
+
const timeoutRef = useRef(null);
|
|
4629
|
+
const repeatCountRef = useRef(0);
|
|
4630
|
+
const isFadingInRef = useRef(true);
|
|
4631
|
+
useEffect(() => {
|
|
4632
|
+
setMounted(true);
|
|
4633
|
+
}, []);
|
|
4634
|
+
const getEasing2 = useCallback(
|
|
4635
|
+
(t) => {
|
|
4636
|
+
switch (ease) {
|
|
4637
|
+
case "linear":
|
|
4638
|
+
return t;
|
|
4639
|
+
case "ease-in":
|
|
4640
|
+
return t * t;
|
|
4641
|
+
case "ease-out":
|
|
4642
|
+
return 1 - (1 - t) * (1 - t);
|
|
4643
|
+
case "ease-in-out":
|
|
4644
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
4645
|
+
default:
|
|
4646
|
+
return t;
|
|
4647
|
+
}
|
|
4648
|
+
},
|
|
4649
|
+
[ease]
|
|
4650
|
+
);
|
|
4651
|
+
const animate = useCallback(
|
|
4652
|
+
(from, to, onFinish) => {
|
|
4653
|
+
if (!mounted) return;
|
|
4654
|
+
setIsAnimating(true);
|
|
4655
|
+
const startTime = performance.now();
|
|
4656
|
+
const startOpacity = from;
|
|
4657
|
+
const updateOpacity = (currentTime) => {
|
|
4658
|
+
const elapsed = currentTime - startTime;
|
|
4659
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
4660
|
+
const easedProgress = getEasing2(progress);
|
|
4661
|
+
const currentOpacity = startOpacity + (to - startOpacity) * easedProgress;
|
|
4662
|
+
setOpacity(currentOpacity);
|
|
4663
|
+
setIsVisible(currentOpacity > 0);
|
|
4664
|
+
if (progress < 1) {
|
|
4665
|
+
motionRef.current = requestAnimationFrame(updateOpacity);
|
|
4666
|
+
} else {
|
|
4667
|
+
setIsAnimating(false);
|
|
4668
|
+
onFinish?.();
|
|
4669
|
+
}
|
|
4670
|
+
};
|
|
4671
|
+
motionRef.current = requestAnimationFrame(updateOpacity);
|
|
4672
|
+
},
|
|
4673
|
+
[mounted, duration, getEasing2]
|
|
5716
4674
|
);
|
|
4675
|
+
const fadeIn = useCallback(() => {
|
|
4676
|
+
if (!mounted || isAnimating) return;
|
|
4677
|
+
animate(initialOpacity, targetOpacity, () => {
|
|
4678
|
+
onComplete?.();
|
|
4679
|
+
if (repeat && (repeatCount === -1 || repeatCountRef.current < repeatCount)) {
|
|
4680
|
+
repeatCountRef.current++;
|
|
4681
|
+
onRepeat?.(repeatCountRef.current);
|
|
4682
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
4683
|
+
fadeOut();
|
|
4684
|
+
}, repeatDelay);
|
|
4685
|
+
}
|
|
4686
|
+
});
|
|
4687
|
+
}, [
|
|
4688
|
+
mounted,
|
|
4689
|
+
isAnimating,
|
|
4690
|
+
animate,
|
|
4691
|
+
initialOpacity,
|
|
4692
|
+
targetOpacity,
|
|
4693
|
+
onComplete,
|
|
4694
|
+
repeat,
|
|
4695
|
+
repeatCount,
|
|
4696
|
+
repeatDelay,
|
|
4697
|
+
onRepeat
|
|
4698
|
+
]);
|
|
4699
|
+
const fadeOut = useCallback(() => {
|
|
4700
|
+
if (!mounted || isAnimating) return;
|
|
4701
|
+
animate(targetOpacity, initialOpacity, () => {
|
|
4702
|
+
onComplete?.();
|
|
4703
|
+
if (repeat && (repeatCount === -1 || repeatCountRef.current < repeatCount)) {
|
|
4704
|
+
repeatCountRef.current++;
|
|
4705
|
+
onRepeat?.(repeatCountRef.current);
|
|
4706
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
4707
|
+
fadeIn();
|
|
4708
|
+
}, repeatDelay);
|
|
4709
|
+
}
|
|
4710
|
+
});
|
|
4711
|
+
}, [
|
|
4712
|
+
mounted,
|
|
4713
|
+
isAnimating,
|
|
4714
|
+
animate,
|
|
4715
|
+
targetOpacity,
|
|
4716
|
+
initialOpacity,
|
|
4717
|
+
onComplete,
|
|
4718
|
+
repeat,
|
|
4719
|
+
repeatCount,
|
|
4720
|
+
repeatDelay,
|
|
4721
|
+
onRepeat
|
|
4722
|
+
]);
|
|
4723
|
+
const start = useCallback(() => {
|
|
4724
|
+
if (!mounted || isAnimating) return;
|
|
4725
|
+
if (delay > 0) {
|
|
4726
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
4727
|
+
fadeIn();
|
|
4728
|
+
}, delay);
|
|
4729
|
+
} else {
|
|
4730
|
+
fadeIn();
|
|
4731
|
+
}
|
|
4732
|
+
}, [mounted, isAnimating, delay, fadeIn]);
|
|
4733
|
+
const stop = useCallback(() => {
|
|
4734
|
+
if (motionRef.current !== null) {
|
|
4735
|
+
cancelAnimationFrame(motionRef.current);
|
|
4736
|
+
motionRef.current = null;
|
|
4737
|
+
}
|
|
4738
|
+
if (timeoutRef.current !== null) {
|
|
4739
|
+
clearTimeout(timeoutRef.current);
|
|
4740
|
+
timeoutRef.current = null;
|
|
4741
|
+
}
|
|
4742
|
+
setIsAnimating(false);
|
|
4743
|
+
}, []);
|
|
4744
|
+
const reset = useCallback(() => {
|
|
4745
|
+
stop();
|
|
4746
|
+
setOpacity(initialOpacity);
|
|
4747
|
+
setIsVisible(initialOpacity > 0);
|
|
4748
|
+
repeatCountRef.current = 0;
|
|
4749
|
+
isFadingInRef.current = true;
|
|
4750
|
+
}, [stop, initialOpacity]);
|
|
4751
|
+
const toggle = useCallback(() => {
|
|
4752
|
+
if (isFadingInRef.current) {
|
|
4753
|
+
fadeOut();
|
|
4754
|
+
isFadingInRef.current = false;
|
|
4755
|
+
} else {
|
|
4756
|
+
fadeIn();
|
|
4757
|
+
isFadingInRef.current = true;
|
|
4758
|
+
}
|
|
4759
|
+
}, [fadeIn, fadeOut]);
|
|
4760
|
+
useEffect(() => {
|
|
4761
|
+
if (mounted && autoStart) {
|
|
4762
|
+
start();
|
|
4763
|
+
}
|
|
4764
|
+
}, [mounted, autoStart, start]);
|
|
4765
|
+
useEffect(() => {
|
|
4766
|
+
return () => {
|
|
4767
|
+
if (motionRef.current !== null) {
|
|
4768
|
+
cancelAnimationFrame(motionRef.current);
|
|
4769
|
+
}
|
|
4770
|
+
if (timeoutRef.current !== null) {
|
|
4771
|
+
clearTimeout(timeoutRef.current);
|
|
4772
|
+
}
|
|
4773
|
+
};
|
|
4774
|
+
}, []);
|
|
4775
|
+
return {
|
|
4776
|
+
opacity,
|
|
4777
|
+
isAnimating,
|
|
4778
|
+
isVisible,
|
|
4779
|
+
mounted,
|
|
4780
|
+
start,
|
|
4781
|
+
stop,
|
|
4782
|
+
reset,
|
|
4783
|
+
fadeIn,
|
|
4784
|
+
fadeOut,
|
|
4785
|
+
toggle
|
|
4786
|
+
};
|
|
4787
|
+
}
|
|
4788
|
+
function useAutoPlay(options = {}) {
|
|
4789
|
+
const {
|
|
4790
|
+
interval = 3e3,
|
|
4791
|
+
delay = 0,
|
|
4792
|
+
repeat = "infinite",
|
|
4793
|
+
autoStart = true,
|
|
4794
|
+
pauseOnHover = false,
|
|
4795
|
+
pauseOnBlur = true,
|
|
4796
|
+
showOnMount = false
|
|
4797
|
+
} = options;
|
|
4798
|
+
const [isPlaying, setIsPlaying] = useState(showOnMount ? autoStart : false);
|
|
4799
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
4800
|
+
const [mounted, setMounted] = useState(false);
|
|
4801
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
4802
|
+
const intervalRef = useRef(null);
|
|
4803
|
+
const timeoutRef = useRef(null);
|
|
4804
|
+
const repeatCountRef = useRef(0);
|
|
4805
|
+
useEffect(() => {
|
|
4806
|
+
setMounted(true);
|
|
4807
|
+
}, []);
|
|
4808
|
+
const next = useCallback(() => {
|
|
4809
|
+
setCurrentStep((prev) => {
|
|
4810
|
+
const nextStep = prev + 1;
|
|
4811
|
+
if (repeat !== "infinite") {
|
|
4812
|
+
repeatCountRef.current += 1;
|
|
4813
|
+
if (repeatCountRef.current >= repeat) {
|
|
4814
|
+
stop();
|
|
4815
|
+
return prev;
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
return nextStep;
|
|
4819
|
+
});
|
|
4820
|
+
}, [repeat]);
|
|
4821
|
+
const startInterval = useCallback(() => {
|
|
4822
|
+
if (intervalRef.current) {
|
|
4823
|
+
clearInterval(intervalRef.current);
|
|
4824
|
+
}
|
|
4825
|
+
intervalRef.current = window.setInterval(() => {
|
|
4826
|
+
next();
|
|
4827
|
+
}, interval);
|
|
4828
|
+
}, [interval, next]);
|
|
4829
|
+
const pause = useCallback(() => {
|
|
4830
|
+
if (!isPlaying) return;
|
|
4831
|
+
setIsPaused(true);
|
|
4832
|
+
if (intervalRef.current) {
|
|
4833
|
+
clearInterval(intervalRef.current);
|
|
4834
|
+
intervalRef.current = null;
|
|
4835
|
+
}
|
|
4836
|
+
}, [isPlaying]);
|
|
4837
|
+
const resume = useCallback(() => {
|
|
4838
|
+
if (!isPlaying || !isPaused) return;
|
|
4839
|
+
setIsPaused(false);
|
|
4840
|
+
startInterval();
|
|
4841
|
+
}, [isPlaying, isPaused, startInterval]);
|
|
4842
|
+
const stop = useCallback(() => {
|
|
4843
|
+
setIsPlaying(false);
|
|
4844
|
+
setIsPaused(false);
|
|
4845
|
+
setCurrentStep(0);
|
|
4846
|
+
repeatCountRef.current = 0;
|
|
4847
|
+
if (intervalRef.current) {
|
|
4848
|
+
clearInterval(intervalRef.current);
|
|
4849
|
+
intervalRef.current = null;
|
|
4850
|
+
}
|
|
4851
|
+
if (timeoutRef.current) {
|
|
4852
|
+
clearTimeout(timeoutRef.current);
|
|
4853
|
+
timeoutRef.current = null;
|
|
4854
|
+
}
|
|
4855
|
+
}, []);
|
|
4856
|
+
const start = useCallback(() => {
|
|
4857
|
+
if (!mounted) return;
|
|
4858
|
+
setIsPlaying(true);
|
|
4859
|
+
setIsPaused(false);
|
|
4860
|
+
setCurrentStep(0);
|
|
4861
|
+
repeatCountRef.current = 0;
|
|
4862
|
+
if (delay > 0) {
|
|
4863
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
4864
|
+
startInterval();
|
|
4865
|
+
}, delay);
|
|
4866
|
+
} else {
|
|
4867
|
+
startInterval();
|
|
4868
|
+
}
|
|
4869
|
+
}, [mounted, delay, startInterval]);
|
|
4870
|
+
const previous = useCallback(() => {
|
|
4871
|
+
setCurrentStep((prev) => Math.max(0, prev - 1));
|
|
4872
|
+
}, []);
|
|
4873
|
+
const goTo = useCallback((step) => {
|
|
4874
|
+
setCurrentStep(Math.max(0, step));
|
|
4875
|
+
}, []);
|
|
4876
|
+
useEffect(() => {
|
|
4877
|
+
if (mounted && autoStart) {
|
|
4878
|
+
start();
|
|
4879
|
+
}
|
|
4880
|
+
}, [mounted, autoStart, start]);
|
|
4881
|
+
useEffect(() => {
|
|
4882
|
+
if (!pauseOnHover) return;
|
|
4883
|
+
const handleMouseEnter = () => {
|
|
4884
|
+
if (isPlaying && !isPaused) {
|
|
4885
|
+
pause();
|
|
4886
|
+
}
|
|
4887
|
+
};
|
|
4888
|
+
const handleMouseLeave = () => {
|
|
4889
|
+
if (isPlaying && isPaused) {
|
|
4890
|
+
resume();
|
|
4891
|
+
}
|
|
4892
|
+
};
|
|
4893
|
+
document.addEventListener("mouseenter", handleMouseEnter);
|
|
4894
|
+
document.addEventListener("mouseleave", handleMouseLeave);
|
|
4895
|
+
return () => {
|
|
4896
|
+
document.removeEventListener("mouseenter", handleMouseEnter);
|
|
4897
|
+
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
4898
|
+
};
|
|
4899
|
+
}, [pauseOnHover, isPlaying, isPaused, pause, resume]);
|
|
4900
|
+
useEffect(() => {
|
|
4901
|
+
if (!pauseOnBlur) return;
|
|
4902
|
+
const handleBlur = () => {
|
|
4903
|
+
if (isPlaying && !isPaused) {
|
|
4904
|
+
pause();
|
|
4905
|
+
}
|
|
4906
|
+
};
|
|
4907
|
+
const handleFocus = () => {
|
|
4908
|
+
if (isPlaying && isPaused) {
|
|
4909
|
+
resume();
|
|
4910
|
+
}
|
|
4911
|
+
};
|
|
4912
|
+
window.addEventListener("blur", handleBlur);
|
|
4913
|
+
window.addEventListener("focus", handleFocus);
|
|
4914
|
+
return () => {
|
|
4915
|
+
window.removeEventListener("blur", handleBlur);
|
|
4916
|
+
window.removeEventListener("focus", handleFocus);
|
|
4917
|
+
};
|
|
4918
|
+
}, [pauseOnBlur, isPlaying, isPaused, pause, resume]);
|
|
4919
|
+
useEffect(() => {
|
|
4920
|
+
return () => {
|
|
4921
|
+
if (intervalRef.current) {
|
|
4922
|
+
clearInterval(intervalRef.current);
|
|
4923
|
+
}
|
|
4924
|
+
if (timeoutRef.current) {
|
|
4925
|
+
clearTimeout(timeoutRef.current);
|
|
4926
|
+
}
|
|
4927
|
+
};
|
|
4928
|
+
}, []);
|
|
4929
|
+
return {
|
|
4930
|
+
isPlaying,
|
|
4931
|
+
isPaused,
|
|
4932
|
+
currentStep,
|
|
4933
|
+
mounted,
|
|
4934
|
+
start,
|
|
4935
|
+
stop,
|
|
4936
|
+
pause,
|
|
4937
|
+
resume,
|
|
4938
|
+
next,
|
|
4939
|
+
previous,
|
|
4940
|
+
goTo
|
|
4941
|
+
};
|
|
4942
|
+
}
|
|
4943
|
+
function useAutoScale(options = {}) {
|
|
4944
|
+
const {
|
|
4945
|
+
initialScale = 0,
|
|
4946
|
+
targetScale = 1,
|
|
4947
|
+
duration = 1e3,
|
|
4948
|
+
delay = 0,
|
|
4949
|
+
repeat = false,
|
|
4950
|
+
repeatDelay = 1e3,
|
|
4951
|
+
repeatCount = -1,
|
|
4952
|
+
ease = "ease-in-out",
|
|
4953
|
+
autoStart = true,
|
|
4954
|
+
onComplete,
|
|
4955
|
+
onRepeat,
|
|
4956
|
+
showOnMount = false,
|
|
4957
|
+
centerTransform: _centerTransform = true
|
|
4958
|
+
} = options;
|
|
4959
|
+
const [scale, setScale] = useState(showOnMount ? initialScale : 0);
|
|
4960
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
4961
|
+
const [isVisible, setIsVisible] = useState(
|
|
4962
|
+
showOnMount ? initialScale > 0 : false
|
|
4963
|
+
);
|
|
4964
|
+
const [mounted, setMounted] = useState(false);
|
|
4965
|
+
const motionRef = useRef(null);
|
|
4966
|
+
const timeoutRef = useRef(null);
|
|
4967
|
+
const repeatCountRef = useRef(0);
|
|
4968
|
+
const isScalingInRef = useRef(true);
|
|
4969
|
+
useEffect(() => {
|
|
4970
|
+
setMounted(true);
|
|
4971
|
+
}, []);
|
|
4972
|
+
const getEasing2 = useCallback(
|
|
4973
|
+
(t) => {
|
|
4974
|
+
switch (ease) {
|
|
4975
|
+
case "linear":
|
|
4976
|
+
return t;
|
|
4977
|
+
case "ease-in":
|
|
4978
|
+
return t * t;
|
|
4979
|
+
case "ease-out":
|
|
4980
|
+
return 1 - (1 - t) * (1 - t);
|
|
4981
|
+
case "ease-in-out":
|
|
4982
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
4983
|
+
case "bounce":
|
|
4984
|
+
if (t < 1 / 2.75) {
|
|
4985
|
+
return 7.5625 * t * t;
|
|
4986
|
+
} else if (t < 2 / 2.75) {
|
|
4987
|
+
return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
|
|
4988
|
+
} else if (t < 2.5 / 2.75) {
|
|
4989
|
+
return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
|
|
4990
|
+
} else {
|
|
4991
|
+
return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
|
|
4992
|
+
}
|
|
4993
|
+
case "elastic":
|
|
4994
|
+
if (t === 0) return 0;
|
|
4995
|
+
if (t === 1) return 1;
|
|
4996
|
+
return Math.pow(2, -10 * t) * Math.sin((t - 0.075) * (2 * Math.PI) / 0.3) + 1;
|
|
4997
|
+
default:
|
|
4998
|
+
return t;
|
|
4999
|
+
}
|
|
5000
|
+
},
|
|
5001
|
+
[ease]
|
|
5002
|
+
);
|
|
5003
|
+
const animate = useCallback(
|
|
5004
|
+
(from, to, onFinish) => {
|
|
5005
|
+
if (!mounted) return;
|
|
5006
|
+
setIsAnimating(true);
|
|
5007
|
+
const startTime = performance.now();
|
|
5008
|
+
const startScale = from;
|
|
5009
|
+
const updateScale = (currentTime) => {
|
|
5010
|
+
const elapsed = currentTime - startTime;
|
|
5011
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
5012
|
+
const easedProgress = getEasing2(progress);
|
|
5013
|
+
const currentScale = startScale + (to - startScale) * easedProgress;
|
|
5014
|
+
setScale(currentScale);
|
|
5015
|
+
setIsVisible(currentScale > 0);
|
|
5016
|
+
if (progress < 1) {
|
|
5017
|
+
motionRef.current = requestAnimationFrame(updateScale);
|
|
5018
|
+
} else {
|
|
5019
|
+
setIsAnimating(false);
|
|
5020
|
+
onFinish?.();
|
|
5021
|
+
}
|
|
5022
|
+
};
|
|
5023
|
+
motionRef.current = requestAnimationFrame(updateScale);
|
|
5024
|
+
},
|
|
5025
|
+
[mounted, duration, getEasing2]
|
|
5026
|
+
);
|
|
5027
|
+
const scaleIn = useCallback(() => {
|
|
5028
|
+
if (!mounted || isAnimating) return;
|
|
5029
|
+
animate(initialScale, targetScale, () => {
|
|
5030
|
+
onComplete?.();
|
|
5031
|
+
if (repeat && (repeatCount === -1 || repeatCountRef.current < repeatCount)) {
|
|
5032
|
+
repeatCountRef.current++;
|
|
5033
|
+
onRepeat?.(repeatCountRef.current);
|
|
5034
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
5035
|
+
scaleOut();
|
|
5036
|
+
}, repeatDelay);
|
|
5037
|
+
}
|
|
5038
|
+
});
|
|
5039
|
+
}, [
|
|
5040
|
+
mounted,
|
|
5041
|
+
isAnimating,
|
|
5042
|
+
animate,
|
|
5043
|
+
initialScale,
|
|
5044
|
+
targetScale,
|
|
5045
|
+
onComplete,
|
|
5046
|
+
repeat,
|
|
5047
|
+
repeatCount,
|
|
5048
|
+
repeatDelay,
|
|
5049
|
+
onRepeat
|
|
5050
|
+
]);
|
|
5051
|
+
const scaleOut = useCallback(() => {
|
|
5052
|
+
if (!mounted || isAnimating) return;
|
|
5053
|
+
animate(targetScale, initialScale, () => {
|
|
5054
|
+
onComplete?.();
|
|
5055
|
+
if (repeat && (repeatCount === -1 || repeatCountRef.current < repeatCount)) {
|
|
5056
|
+
repeatCountRef.current++;
|
|
5057
|
+
onRepeat?.(repeatCountRef.current);
|
|
5058
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
5059
|
+
scaleIn();
|
|
5060
|
+
}, repeatDelay);
|
|
5061
|
+
}
|
|
5062
|
+
});
|
|
5063
|
+
}, [
|
|
5064
|
+
mounted,
|
|
5065
|
+
isAnimating,
|
|
5066
|
+
animate,
|
|
5067
|
+
targetScale,
|
|
5068
|
+
initialScale,
|
|
5069
|
+
onComplete,
|
|
5070
|
+
repeat,
|
|
5071
|
+
repeatCount,
|
|
5072
|
+
repeatDelay,
|
|
5073
|
+
onRepeat
|
|
5074
|
+
]);
|
|
5075
|
+
const start = useCallback(() => {
|
|
5076
|
+
if (!mounted || isAnimating) return;
|
|
5077
|
+
if (delay > 0) {
|
|
5078
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
5079
|
+
scaleIn();
|
|
5080
|
+
}, delay);
|
|
5081
|
+
} else {
|
|
5082
|
+
scaleIn();
|
|
5083
|
+
}
|
|
5084
|
+
}, [mounted, isAnimating, delay, scaleIn]);
|
|
5085
|
+
const stop = useCallback(() => {
|
|
5086
|
+
if (motionRef.current !== null) {
|
|
5087
|
+
cancelAnimationFrame(motionRef.current);
|
|
5088
|
+
motionRef.current = null;
|
|
5089
|
+
}
|
|
5090
|
+
if (timeoutRef.current !== null) {
|
|
5091
|
+
clearTimeout(timeoutRef.current);
|
|
5092
|
+
timeoutRef.current = null;
|
|
5093
|
+
}
|
|
5094
|
+
setIsAnimating(false);
|
|
5095
|
+
}, []);
|
|
5096
|
+
const reset = useCallback(() => {
|
|
5097
|
+
stop();
|
|
5098
|
+
setScale(initialScale);
|
|
5099
|
+
setIsVisible(initialScale > 0);
|
|
5100
|
+
repeatCountRef.current = 0;
|
|
5101
|
+
isScalingInRef.current = true;
|
|
5102
|
+
}, [stop, initialScale]);
|
|
5103
|
+
const toggle = useCallback(() => {
|
|
5104
|
+
if (isScalingInRef.current) {
|
|
5105
|
+
scaleOut();
|
|
5106
|
+
isScalingInRef.current = false;
|
|
5107
|
+
} else {
|
|
5108
|
+
scaleIn();
|
|
5109
|
+
isScalingInRef.current = true;
|
|
5110
|
+
}
|
|
5111
|
+
}, [scaleIn, scaleOut]);
|
|
5112
|
+
useEffect(() => {
|
|
5113
|
+
if (mounted && autoStart) {
|
|
5114
|
+
start();
|
|
5115
|
+
}
|
|
5116
|
+
}, [mounted, autoStart, start]);
|
|
5117
|
+
useEffect(() => {
|
|
5118
|
+
return () => {
|
|
5119
|
+
if (motionRef.current !== null) {
|
|
5120
|
+
cancelAnimationFrame(motionRef.current);
|
|
5121
|
+
}
|
|
5122
|
+
if (timeoutRef.current !== null) {
|
|
5123
|
+
clearTimeout(timeoutRef.current);
|
|
5124
|
+
}
|
|
5125
|
+
};
|
|
5126
|
+
}, []);
|
|
5127
|
+
return {
|
|
5128
|
+
scale,
|
|
5129
|
+
isAnimating,
|
|
5130
|
+
isVisible,
|
|
5131
|
+
mounted,
|
|
5132
|
+
start,
|
|
5133
|
+
stop,
|
|
5134
|
+
reset,
|
|
5135
|
+
scaleIn,
|
|
5136
|
+
scaleOut,
|
|
5137
|
+
toggle
|
|
5138
|
+
};
|
|
5139
|
+
}
|
|
5140
|
+
function useAutoSlide(options = {}) {
|
|
5141
|
+
const {
|
|
5142
|
+
direction = "left",
|
|
5143
|
+
distance = 100,
|
|
5144
|
+
initialPosition,
|
|
5145
|
+
targetPosition,
|
|
5146
|
+
duration = 1e3,
|
|
5147
|
+
delay = 0,
|
|
5148
|
+
repeat = false,
|
|
5149
|
+
repeatDelay = 1e3,
|
|
5150
|
+
repeatCount = -1,
|
|
5151
|
+
ease = "ease-in-out",
|
|
5152
|
+
autoStart = true,
|
|
5153
|
+
onComplete,
|
|
5154
|
+
onRepeat,
|
|
5155
|
+
showOnMount = false
|
|
5156
|
+
} = options;
|
|
5157
|
+
const getDefaultPositions = useCallback(() => {
|
|
5158
|
+
const defaultInitial = { x: 0, y: 0 };
|
|
5159
|
+
const defaultTarget = { x: 0, y: 0 };
|
|
5160
|
+
switch (direction) {
|
|
5161
|
+
case "left":
|
|
5162
|
+
defaultInitial.x = distance;
|
|
5163
|
+
defaultTarget.x = 0;
|
|
5164
|
+
break;
|
|
5165
|
+
case "right":
|
|
5166
|
+
defaultInitial.x = -distance;
|
|
5167
|
+
defaultTarget.x = 0;
|
|
5168
|
+
break;
|
|
5169
|
+
case "up":
|
|
5170
|
+
defaultInitial.y = distance;
|
|
5171
|
+
defaultTarget.y = 0;
|
|
5172
|
+
break;
|
|
5173
|
+
case "down":
|
|
5174
|
+
defaultInitial.y = -distance;
|
|
5175
|
+
defaultTarget.y = 0;
|
|
5176
|
+
break;
|
|
5177
|
+
case "left-up":
|
|
5178
|
+
defaultInitial.x = distance;
|
|
5179
|
+
defaultInitial.y = distance;
|
|
5180
|
+
defaultTarget.x = 0;
|
|
5181
|
+
defaultTarget.y = 0;
|
|
5182
|
+
break;
|
|
5183
|
+
case "left-down":
|
|
5184
|
+
defaultInitial.x = distance;
|
|
5185
|
+
defaultInitial.y = -distance;
|
|
5186
|
+
defaultTarget.x = 0;
|
|
5187
|
+
defaultTarget.y = 0;
|
|
5188
|
+
break;
|
|
5189
|
+
case "right-up":
|
|
5190
|
+
defaultInitial.x = -distance;
|
|
5191
|
+
defaultInitial.y = distance;
|
|
5192
|
+
defaultTarget.x = 0;
|
|
5193
|
+
defaultTarget.y = 0;
|
|
5194
|
+
break;
|
|
5195
|
+
case "right-down":
|
|
5196
|
+
defaultInitial.x = -distance;
|
|
5197
|
+
defaultInitial.y = -distance;
|
|
5198
|
+
defaultTarget.x = 0;
|
|
5199
|
+
defaultTarget.y = 0;
|
|
5200
|
+
break;
|
|
5201
|
+
}
|
|
5202
|
+
return {
|
|
5203
|
+
initial: initialPosition || defaultInitial,
|
|
5204
|
+
target: targetPosition || defaultTarget
|
|
5205
|
+
};
|
|
5206
|
+
}, [direction, distance, initialPosition, targetPosition]);
|
|
5207
|
+
const positions = getDefaultPositions();
|
|
5208
|
+
const [position, setPosition] = useState(
|
|
5209
|
+
showOnMount ? positions.initial : positions.target
|
|
5210
|
+
);
|
|
5211
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
5212
|
+
const [isVisible, setIsVisible] = useState(showOnMount ? true : false);
|
|
5213
|
+
const [mounted, setMounted] = useState(false);
|
|
5214
|
+
const motionRef = useRef(null);
|
|
5215
|
+
const timeoutRef = useRef(null);
|
|
5216
|
+
const repeatCountRef = useRef(0);
|
|
5217
|
+
const isSlidingInRef = useRef(true);
|
|
5218
|
+
useEffect(() => {
|
|
5219
|
+
setMounted(true);
|
|
5220
|
+
}, []);
|
|
5221
|
+
const getEasing2 = useCallback(
|
|
5222
|
+
(t) => {
|
|
5223
|
+
switch (ease) {
|
|
5224
|
+
case "linear":
|
|
5225
|
+
return t;
|
|
5226
|
+
case "ease-in":
|
|
5227
|
+
return t * t;
|
|
5228
|
+
case "ease-out":
|
|
5229
|
+
return 1 - (1 - t) * (1 - t);
|
|
5230
|
+
case "ease-in-out":
|
|
5231
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
5232
|
+
default:
|
|
5233
|
+
return t;
|
|
5234
|
+
}
|
|
5235
|
+
},
|
|
5236
|
+
[ease]
|
|
5237
|
+
);
|
|
5238
|
+
const animate = useCallback(
|
|
5239
|
+
(from, to, onFinish) => {
|
|
5240
|
+
if (!mounted) return;
|
|
5241
|
+
setIsAnimating(true);
|
|
5242
|
+
const startTime = performance.now();
|
|
5243
|
+
const startPosition = from;
|
|
5244
|
+
const updatePosition = (currentTime) => {
|
|
5245
|
+
const elapsed = currentTime - startTime;
|
|
5246
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
5247
|
+
const easedProgress = getEasing2(progress);
|
|
5248
|
+
const currentX = startPosition.x + (to.x - startPosition.x) * easedProgress;
|
|
5249
|
+
const currentY = startPosition.y + (to.y - startPosition.y) * easedProgress;
|
|
5250
|
+
setPosition({ x: currentX, y: currentY });
|
|
5251
|
+
setIsVisible(true);
|
|
5252
|
+
if (progress < 1) {
|
|
5253
|
+
motionRef.current = requestAnimationFrame(updatePosition);
|
|
5254
|
+
} else {
|
|
5255
|
+
setIsAnimating(false);
|
|
5256
|
+
onFinish?.();
|
|
5257
|
+
}
|
|
5258
|
+
};
|
|
5259
|
+
motionRef.current = requestAnimationFrame(updatePosition);
|
|
5260
|
+
},
|
|
5261
|
+
[mounted, duration, getEasing2]
|
|
5262
|
+
);
|
|
5263
|
+
const slideIn = useCallback(() => {
|
|
5264
|
+
if (!mounted || isAnimating) return;
|
|
5265
|
+
animate(positions.initial, positions.target, () => {
|
|
5266
|
+
onComplete?.();
|
|
5267
|
+
if (repeat && (repeatCount === -1 || repeatCountRef.current < repeatCount)) {
|
|
5268
|
+
repeatCountRef.current++;
|
|
5269
|
+
onRepeat?.(repeatCountRef.current);
|
|
5270
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
5271
|
+
slideOut();
|
|
5272
|
+
}, repeatDelay);
|
|
5273
|
+
}
|
|
5274
|
+
});
|
|
5275
|
+
}, [
|
|
5276
|
+
mounted,
|
|
5277
|
+
isAnimating,
|
|
5278
|
+
animate,
|
|
5279
|
+
positions.initial,
|
|
5280
|
+
positions.target,
|
|
5281
|
+
onComplete,
|
|
5282
|
+
repeat,
|
|
5283
|
+
repeatCount,
|
|
5284
|
+
repeatDelay,
|
|
5285
|
+
onRepeat
|
|
5286
|
+
]);
|
|
5287
|
+
const slideOut = useCallback(() => {
|
|
5288
|
+
if (!mounted || isAnimating) return;
|
|
5289
|
+
animate(positions.target, positions.initial, () => {
|
|
5290
|
+
onComplete?.();
|
|
5291
|
+
if (repeat && (repeatCount === -1 || repeatCountRef.current < repeatCount)) {
|
|
5292
|
+
repeatCountRef.current++;
|
|
5293
|
+
onRepeat?.(repeatCountRef.current);
|
|
5294
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
5295
|
+
slideIn();
|
|
5296
|
+
}, repeatDelay);
|
|
5297
|
+
}
|
|
5298
|
+
});
|
|
5299
|
+
}, [
|
|
5300
|
+
mounted,
|
|
5301
|
+
isAnimating,
|
|
5302
|
+
animate,
|
|
5303
|
+
positions.target,
|
|
5304
|
+
positions.initial,
|
|
5305
|
+
onComplete,
|
|
5306
|
+
repeat,
|
|
5307
|
+
repeatCount,
|
|
5308
|
+
repeatDelay,
|
|
5309
|
+
onRepeat
|
|
5310
|
+
]);
|
|
5311
|
+
const start = useCallback(() => {
|
|
5312
|
+
if (!mounted || isAnimating) return;
|
|
5313
|
+
if (delay > 0) {
|
|
5314
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
5315
|
+
slideIn();
|
|
5316
|
+
}, delay);
|
|
5317
|
+
} else {
|
|
5318
|
+
slideIn();
|
|
5319
|
+
}
|
|
5320
|
+
}, [mounted, isAnimating, delay, slideIn]);
|
|
5321
|
+
const stop = useCallback(() => {
|
|
5322
|
+
if (motionRef.current !== null) {
|
|
5323
|
+
cancelAnimationFrame(motionRef.current);
|
|
5324
|
+
motionRef.current = null;
|
|
5325
|
+
}
|
|
5326
|
+
if (timeoutRef.current !== null) {
|
|
5327
|
+
clearTimeout(timeoutRef.current);
|
|
5328
|
+
timeoutRef.current = null;
|
|
5329
|
+
}
|
|
5330
|
+
setIsAnimating(false);
|
|
5331
|
+
}, []);
|
|
5332
|
+
const reset = useCallback(() => {
|
|
5333
|
+
stop();
|
|
5334
|
+
setPosition(positions.initial);
|
|
5335
|
+
setIsVisible(showOnMount ? true : false);
|
|
5336
|
+
repeatCountRef.current = 0;
|
|
5337
|
+
isSlidingInRef.current = true;
|
|
5338
|
+
}, [stop, positions.initial, showOnMount]);
|
|
5339
|
+
const toggle = useCallback(() => {
|
|
5340
|
+
if (isSlidingInRef.current) {
|
|
5341
|
+
slideOut();
|
|
5342
|
+
isSlidingInRef.current = false;
|
|
5343
|
+
} else {
|
|
5344
|
+
slideIn();
|
|
5345
|
+
isSlidingInRef.current = true;
|
|
5346
|
+
}
|
|
5347
|
+
}, [slideIn, slideOut]);
|
|
5348
|
+
useEffect(() => {
|
|
5349
|
+
if (mounted && autoStart) {
|
|
5350
|
+
start();
|
|
5351
|
+
}
|
|
5352
|
+
}, [mounted, autoStart, start]);
|
|
5353
|
+
useEffect(() => {
|
|
5354
|
+
return () => {
|
|
5355
|
+
if (motionRef.current !== null) {
|
|
5356
|
+
cancelAnimationFrame(motionRef.current);
|
|
5357
|
+
}
|
|
5358
|
+
if (timeoutRef.current !== null) {
|
|
5359
|
+
clearTimeout(timeoutRef.current);
|
|
5360
|
+
}
|
|
5361
|
+
};
|
|
5362
|
+
}, []);
|
|
5363
|
+
return {
|
|
5364
|
+
position,
|
|
5365
|
+
isAnimating,
|
|
5366
|
+
isVisible,
|
|
5367
|
+
mounted,
|
|
5368
|
+
start,
|
|
5369
|
+
stop,
|
|
5370
|
+
reset,
|
|
5371
|
+
slideIn,
|
|
5372
|
+
slideOut,
|
|
5373
|
+
toggle
|
|
5374
|
+
};
|
|
5375
|
+
}
|
|
5376
|
+
function useMotionOrchestra(options = {}) {
|
|
5377
|
+
const {
|
|
5378
|
+
mode = "sequential",
|
|
5379
|
+
staggerDelay = 100,
|
|
5380
|
+
autoStart = false,
|
|
5381
|
+
loop = false,
|
|
5382
|
+
onComplete
|
|
5383
|
+
} = options;
|
|
5384
|
+
const [orchestraState, setOrchestraState] = useState({
|
|
5385
|
+
isPlaying: false,
|
|
5386
|
+
currentStep: 0,
|
|
5387
|
+
completedSteps: /* @__PURE__ */ new Set()
|
|
5388
|
+
});
|
|
5389
|
+
const motionsRef = useRef([]);
|
|
5390
|
+
const timeoutsRef = useRef([]);
|
|
5391
|
+
const addMotion = useCallback((step) => {
|
|
5392
|
+
motionsRef.current.push(step);
|
|
5393
|
+
}, []);
|
|
5394
|
+
const removeMotion = useCallback((id) => {
|
|
5395
|
+
motionsRef.current = motionsRef.current.filter((step) => step.id !== id);
|
|
5396
|
+
}, []);
|
|
5397
|
+
const clearTimeouts = useCallback(() => {
|
|
5398
|
+
timeoutsRef.current.forEach((timeout) => clearTimeout(timeout));
|
|
5399
|
+
timeoutsRef.current = [];
|
|
5400
|
+
}, []);
|
|
5401
|
+
const playSequential = useCallback(() => {
|
|
5402
|
+
if (motionsRef.current.length === 0) return;
|
|
5403
|
+
const playStep = (index) => {
|
|
5404
|
+
if (index >= motionsRef.current.length) {
|
|
5405
|
+
setOrchestraState((prev) => ({
|
|
5406
|
+
...prev,
|
|
5407
|
+
isPlaying: false,
|
|
5408
|
+
currentStep: 0
|
|
5409
|
+
}));
|
|
5410
|
+
onComplete?.();
|
|
5411
|
+
if (loop) {
|
|
5412
|
+
setTimeout(() => {
|
|
5413
|
+
setOrchestraState((prev) => ({
|
|
5414
|
+
...prev,
|
|
5415
|
+
isPlaying: true,
|
|
5416
|
+
completedSteps: /* @__PURE__ */ new Set()
|
|
5417
|
+
}));
|
|
5418
|
+
playSequential();
|
|
5419
|
+
}, 1e3);
|
|
5420
|
+
}
|
|
5421
|
+
return;
|
|
5422
|
+
}
|
|
5423
|
+
const step = motionsRef.current[index];
|
|
5424
|
+
setOrchestraState((prev) => ({
|
|
5425
|
+
...prev,
|
|
5426
|
+
currentStep: index,
|
|
5427
|
+
completedSteps: /* @__PURE__ */ new Set([...prev.completedSteps, step.id])
|
|
5428
|
+
}));
|
|
5429
|
+
step.motion();
|
|
5430
|
+
if (step.onComplete) {
|
|
5431
|
+
step.onComplete();
|
|
5432
|
+
}
|
|
5433
|
+
const timeout = setTimeout(() => {
|
|
5434
|
+
playStep(index + 1);
|
|
5435
|
+
}, step.delay || 0);
|
|
5436
|
+
timeoutsRef.current.push(timeout);
|
|
5437
|
+
};
|
|
5438
|
+
playStep(0);
|
|
5439
|
+
}, [loop, onComplete]);
|
|
5440
|
+
const playParallel = useCallback(() => {
|
|
5441
|
+
if (motionsRef.current.length === 0) return;
|
|
5442
|
+
const completedSteps = /* @__PURE__ */ new Set();
|
|
5443
|
+
motionsRef.current.forEach((step) => {
|
|
5444
|
+
const timeout = setTimeout(() => {
|
|
5445
|
+
step.motion();
|
|
5446
|
+
completedSteps.add(step.id);
|
|
5447
|
+
if (step.onComplete) {
|
|
5448
|
+
step.onComplete();
|
|
5449
|
+
}
|
|
5450
|
+
if (completedSteps.size === motionsRef.current.length) {
|
|
5451
|
+
setOrchestraState((prev) => ({
|
|
5452
|
+
...prev,
|
|
5453
|
+
isPlaying: false,
|
|
5454
|
+
currentStep: 0
|
|
5455
|
+
}));
|
|
5456
|
+
onComplete?.();
|
|
5457
|
+
if (loop) {
|
|
5458
|
+
setTimeout(() => {
|
|
5459
|
+
setOrchestraState((prev) => ({ ...prev, isPlaying: true }));
|
|
5460
|
+
playParallel();
|
|
5461
|
+
}, 1e3);
|
|
5462
|
+
}
|
|
5463
|
+
}
|
|
5464
|
+
}, step.delay || 0);
|
|
5465
|
+
timeoutsRef.current.push(timeout);
|
|
5466
|
+
});
|
|
5467
|
+
setOrchestraState((prev) => ({
|
|
5468
|
+
...prev,
|
|
5469
|
+
completedSteps: new Set(completedSteps)
|
|
5470
|
+
}));
|
|
5471
|
+
}, [loop, onComplete]);
|
|
5472
|
+
const playStagger = useCallback(() => {
|
|
5473
|
+
if (motionsRef.current.length === 0) return;
|
|
5474
|
+
const completedSteps = /* @__PURE__ */ new Set();
|
|
5475
|
+
motionsRef.current.forEach((step, index) => {
|
|
5476
|
+
const timeout = setTimeout(
|
|
5477
|
+
() => {
|
|
5478
|
+
step.motion();
|
|
5479
|
+
completedSteps.add(step.id);
|
|
5480
|
+
if (step.onComplete) {
|
|
5481
|
+
step.onComplete();
|
|
5482
|
+
}
|
|
5483
|
+
setOrchestraState((prev) => ({
|
|
5484
|
+
...prev,
|
|
5485
|
+
currentStep: index,
|
|
5486
|
+
completedSteps: /* @__PURE__ */ new Set([...prev.completedSteps, step.id])
|
|
5487
|
+
}));
|
|
5488
|
+
if (completedSteps.size === motionsRef.current.length) {
|
|
5489
|
+
setOrchestraState((prev) => ({
|
|
5490
|
+
...prev,
|
|
5491
|
+
isPlaying: false,
|
|
5492
|
+
currentStep: 0
|
|
5493
|
+
}));
|
|
5494
|
+
onComplete?.();
|
|
5495
|
+
if (loop) {
|
|
5496
|
+
setTimeout(() => {
|
|
5497
|
+
setOrchestraState((prev) => ({
|
|
5498
|
+
...prev,
|
|
5499
|
+
isPlaying: true,
|
|
5500
|
+
completedSteps: /* @__PURE__ */ new Set()
|
|
5501
|
+
}));
|
|
5502
|
+
playStagger();
|
|
5503
|
+
}, 1e3);
|
|
5504
|
+
}
|
|
5505
|
+
}
|
|
5506
|
+
},
|
|
5507
|
+
(step.delay || 0) + index * staggerDelay
|
|
5508
|
+
);
|
|
5509
|
+
timeoutsRef.current.push(timeout);
|
|
5510
|
+
});
|
|
5511
|
+
}, [staggerDelay, loop, onComplete]);
|
|
5512
|
+
const play = useCallback(() => {
|
|
5513
|
+
clearTimeouts();
|
|
5514
|
+
setOrchestraState((prev) => ({
|
|
5515
|
+
...prev,
|
|
5516
|
+
isPlaying: true,
|
|
5517
|
+
currentStep: 0,
|
|
5518
|
+
completedSteps: /* @__PURE__ */ new Set()
|
|
5519
|
+
}));
|
|
5520
|
+
switch (mode) {
|
|
5521
|
+
case "sequential":
|
|
5522
|
+
playSequential();
|
|
5523
|
+
break;
|
|
5524
|
+
case "parallel":
|
|
5525
|
+
playParallel();
|
|
5526
|
+
break;
|
|
5527
|
+
case "stagger":
|
|
5528
|
+
playStagger();
|
|
5529
|
+
break;
|
|
5530
|
+
}
|
|
5531
|
+
}, [mode, clearTimeouts, playSequential, playParallel, playStagger]);
|
|
5532
|
+
const stop = useCallback(() => {
|
|
5533
|
+
clearTimeouts();
|
|
5534
|
+
setOrchestraState((prev) => ({
|
|
5535
|
+
...prev,
|
|
5536
|
+
isPlaying: false,
|
|
5537
|
+
currentStep: 0
|
|
5538
|
+
}));
|
|
5539
|
+
}, [clearTimeouts]);
|
|
5540
|
+
const pause = useCallback(() => {
|
|
5541
|
+
setOrchestraState((prev) => ({ ...prev, isPlaying: false }));
|
|
5542
|
+
}, []);
|
|
5543
|
+
const resume = useCallback(() => {
|
|
5544
|
+
if (orchestraState.currentStep < motionsRef.current.length) {
|
|
5545
|
+
setOrchestraState((prev) => ({ ...prev, isPlaying: true }));
|
|
5546
|
+
switch (mode) {
|
|
5547
|
+
case "sequential":
|
|
5548
|
+
playSequential();
|
|
5549
|
+
break;
|
|
5550
|
+
case "parallel":
|
|
5551
|
+
playParallel();
|
|
5552
|
+
break;
|
|
5553
|
+
case "stagger":
|
|
5554
|
+
playStagger();
|
|
5555
|
+
break;
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
}, [
|
|
5559
|
+
mode,
|
|
5560
|
+
orchestraState.currentStep,
|
|
5561
|
+
playSequential,
|
|
5562
|
+
playParallel,
|
|
5563
|
+
playStagger
|
|
5564
|
+
]);
|
|
5565
|
+
useEffect(() => {
|
|
5566
|
+
if (autoStart && motionsRef.current.length > 0) {
|
|
5567
|
+
play();
|
|
5568
|
+
}
|
|
5569
|
+
}, [autoStart, play]);
|
|
5570
|
+
useEffect(() => {
|
|
5571
|
+
return () => {
|
|
5572
|
+
clearTimeouts();
|
|
5573
|
+
};
|
|
5574
|
+
}, [clearTimeouts]);
|
|
5575
|
+
return {
|
|
5576
|
+
addMotion,
|
|
5577
|
+
removeMotion,
|
|
5578
|
+
play,
|
|
5579
|
+
stop,
|
|
5580
|
+
pause,
|
|
5581
|
+
resume,
|
|
5582
|
+
isPlaying: orchestraState.isPlaying,
|
|
5583
|
+
currentStep: orchestraState.currentStep,
|
|
5584
|
+
completedSteps: orchestraState.completedSteps,
|
|
5585
|
+
totalSteps: motionsRef.current.length
|
|
5586
|
+
};
|
|
5587
|
+
}
|
|
5588
|
+
function useOrchestration(options = {}) {
|
|
5589
|
+
const {
|
|
5590
|
+
autoStart = false,
|
|
5591
|
+
loop = false,
|
|
5592
|
+
loopCount = -1,
|
|
5593
|
+
loopDelay = 1e3,
|
|
5594
|
+
timeline = [],
|
|
5595
|
+
duration: totalDuration,
|
|
5596
|
+
speed = 1,
|
|
5597
|
+
reverse = false,
|
|
5598
|
+
onStart,
|
|
5599
|
+
onComplete,
|
|
5600
|
+
onLoop,
|
|
5601
|
+
onError: _onError,
|
|
5602
|
+
onProgress,
|
|
5603
|
+
onStepStart,
|
|
5604
|
+
onStepComplete
|
|
5605
|
+
} = options;
|
|
5606
|
+
const [state, setState] = useState({
|
|
5607
|
+
isPlaying: false,
|
|
5608
|
+
isPaused: false,
|
|
5609
|
+
currentTime: 0,
|
|
5610
|
+
progress: 0,
|
|
5611
|
+
currentStep: null,
|
|
5612
|
+
loopCount: 0,
|
|
5613
|
+
error: null
|
|
5614
|
+
});
|
|
5615
|
+
const [steps, setSteps] = useState(timeline);
|
|
5616
|
+
const [currentSpeed, setCurrentSpeed] = useState(speed);
|
|
5617
|
+
const [isReversed, setIsReversed] = useState(reverse);
|
|
5618
|
+
const motionRef = useRef(null);
|
|
5619
|
+
const startTimeRef = useRef(0);
|
|
5620
|
+
const pauseTimeRef = useRef(0);
|
|
5621
|
+
const stepStartTimesRef = useRef(/* @__PURE__ */ new Map());
|
|
5622
|
+
const stepDurationsRef = useRef(/* @__PURE__ */ new Map());
|
|
5623
|
+
const getEasing2 = useCallback(
|
|
5624
|
+
(t, ease = "linear") => {
|
|
5625
|
+
switch (ease) {
|
|
5626
|
+
case "linear":
|
|
5627
|
+
return t;
|
|
5628
|
+
case "ease-in":
|
|
5629
|
+
return t * t;
|
|
5630
|
+
case "ease-out":
|
|
5631
|
+
return 1 - (1 - t) * (1 - t);
|
|
5632
|
+
case "ease-in-out":
|
|
5633
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
5634
|
+
case "bounce":
|
|
5635
|
+
if (t < 1 / 2.75) {
|
|
5636
|
+
return 7.5625 * t * t;
|
|
5637
|
+
} else if (t < 2 / 2.75) {
|
|
5638
|
+
return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
|
|
5639
|
+
} else if (t < 2.5 / 2.75) {
|
|
5640
|
+
return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
|
|
5641
|
+
} else {
|
|
5642
|
+
return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
|
|
5643
|
+
}
|
|
5644
|
+
case "elastic":
|
|
5645
|
+
if (t === 0) return 0;
|
|
5646
|
+
if (t === 1) return 1;
|
|
5647
|
+
return Math.pow(2, -10 * t) * Math.sin((t - 0.075) * (2 * Math.PI) / 0.3) + 1;
|
|
5648
|
+
default:
|
|
5649
|
+
return t;
|
|
5650
|
+
}
|
|
5651
|
+
},
|
|
5652
|
+
[]
|
|
5653
|
+
);
|
|
5654
|
+
const getTotalDuration = useCallback(() => {
|
|
5655
|
+
if (totalDuration) return totalDuration;
|
|
5656
|
+
let maxEndTime = 0;
|
|
5657
|
+
steps.forEach((step) => {
|
|
5658
|
+
const stepEndTime = (step.delay || 0) + step.duration;
|
|
5659
|
+
maxEndTime = Math.max(maxEndTime, stepEndTime);
|
|
5660
|
+
});
|
|
5661
|
+
return maxEndTime;
|
|
5662
|
+
}, [steps, totalDuration]);
|
|
5663
|
+
const calculateStepTimes = useCallback(() => {
|
|
5664
|
+
const stepTimes = /* @__PURE__ */ new Map();
|
|
5665
|
+
const stepDurations = /* @__PURE__ */ new Map();
|
|
5666
|
+
let currentTime = 0;
|
|
5667
|
+
steps.forEach((step) => {
|
|
5668
|
+
stepTimes.set(step.id, currentTime + (step.delay || 0));
|
|
5669
|
+
stepDurations.set(step.id, step.duration);
|
|
5670
|
+
currentTime += (step.delay || 0) + step.duration;
|
|
5671
|
+
});
|
|
5672
|
+
stepStartTimesRef.current = stepTimes;
|
|
5673
|
+
stepDurationsRef.current = stepDurations;
|
|
5674
|
+
}, [steps]);
|
|
5675
|
+
const getCurrentStep = useCallback(
|
|
5676
|
+
(time) => {
|
|
5677
|
+
for (const step of steps) {
|
|
5678
|
+
const startTime = stepStartTimesRef.current.get(step.id) || 0;
|
|
5679
|
+
const endTime = startTime + (stepDurationsRef.current.get(step.id) || 0);
|
|
5680
|
+
if (time >= startTime && time <= endTime) {
|
|
5681
|
+
return step.id;
|
|
5682
|
+
}
|
|
5683
|
+
}
|
|
5684
|
+
return null;
|
|
5685
|
+
},
|
|
5686
|
+
[steps]
|
|
5687
|
+
);
|
|
5688
|
+
const getStepProgress = useCallback(
|
|
5689
|
+
(stepId) => {
|
|
5690
|
+
const startTime = stepStartTimesRef.current.get(stepId) || 0;
|
|
5691
|
+
const duration = stepDurationsRef.current.get(stepId) || 0;
|
|
5692
|
+
const currentTime = state.currentTime;
|
|
5693
|
+
if (currentTime < startTime) return 0;
|
|
5694
|
+
if (currentTime > startTime + duration) return 1;
|
|
5695
|
+
const stepProgress = (currentTime - startTime) / duration;
|
|
5696
|
+
return Math.max(0, Math.min(1, stepProgress));
|
|
5697
|
+
},
|
|
5698
|
+
[state.currentTime]
|
|
5699
|
+
);
|
|
5700
|
+
const getStepTime = useCallback(
|
|
5701
|
+
(stepId) => {
|
|
5702
|
+
const startTime = stepStartTimesRef.current.get(stepId) || 0;
|
|
5703
|
+
const duration = stepDurationsRef.current.get(stepId) || 0;
|
|
5704
|
+
const currentTime = state.currentTime;
|
|
5705
|
+
if (currentTime < startTime) return 0;
|
|
5706
|
+
if (currentTime > startTime + duration) return duration;
|
|
5707
|
+
return currentTime - startTime;
|
|
5708
|
+
},
|
|
5709
|
+
[state.currentTime]
|
|
5710
|
+
);
|
|
5711
|
+
const updateMotion = useCallback(
|
|
5712
|
+
(currentTime) => {
|
|
5713
|
+
const total = getTotalDuration();
|
|
5714
|
+
const adjustedTime = isReversed ? total - currentTime : currentTime;
|
|
5715
|
+
const progress = Math.min(adjustedTime / total, 1);
|
|
5716
|
+
const currentStep = getCurrentStep(adjustedTime);
|
|
5717
|
+
setState((prev) => ({
|
|
5718
|
+
...prev,
|
|
5719
|
+
currentTime: adjustedTime,
|
|
5720
|
+
progress,
|
|
5721
|
+
currentStep
|
|
5722
|
+
}));
|
|
5723
|
+
onProgress?.(progress);
|
|
5724
|
+
if (currentStep) {
|
|
5725
|
+
const step = steps.find((s) => s.id === currentStep);
|
|
5726
|
+
if (step) {
|
|
5727
|
+
const stepProgress = getStepProgress(currentStep);
|
|
5728
|
+
const easedProgress = getEasing2(stepProgress, step.ease);
|
|
5729
|
+
step.onUpdate?.(easedProgress);
|
|
5730
|
+
}
|
|
5731
|
+
}
|
|
5732
|
+
if (progress >= 1) {
|
|
5733
|
+
if (loop && (loopCount === -1 || state.loopCount < loopCount)) {
|
|
5734
|
+
setState((prev) => ({
|
|
5735
|
+
...prev,
|
|
5736
|
+
loopCount: prev.loopCount + 1
|
|
5737
|
+
}));
|
|
5738
|
+
onLoop?.(state.loopCount + 1);
|
|
5739
|
+
setTimeout(() => {
|
|
5740
|
+
reset();
|
|
5741
|
+
play();
|
|
5742
|
+
}, loopDelay);
|
|
5743
|
+
} else {
|
|
5744
|
+
setState((prev) => ({
|
|
5745
|
+
...prev,
|
|
5746
|
+
isPlaying: false,
|
|
5747
|
+
currentTime: isReversed ? 0 : total,
|
|
5748
|
+
progress: 1
|
|
5749
|
+
}));
|
|
5750
|
+
onComplete?.();
|
|
5751
|
+
}
|
|
5752
|
+
} else {
|
|
5753
|
+
motionRef.current = requestAnimationFrame(() => {
|
|
5754
|
+
const elapsed = (performance.now() - startTimeRef.current) * currentSpeed / 1e3;
|
|
5755
|
+
updateMotion(elapsed);
|
|
5756
|
+
});
|
|
5757
|
+
}
|
|
5758
|
+
},
|
|
5759
|
+
[
|
|
5760
|
+
getTotalDuration,
|
|
5761
|
+
isReversed,
|
|
5762
|
+
getCurrentStep,
|
|
5763
|
+
onProgress,
|
|
5764
|
+
steps,
|
|
5765
|
+
getStepProgress,
|
|
5766
|
+
getEasing2,
|
|
5767
|
+
loop,
|
|
5768
|
+
loopCount,
|
|
5769
|
+
state.loopCount,
|
|
5770
|
+
loopDelay,
|
|
5771
|
+
onLoop,
|
|
5772
|
+
onComplete,
|
|
5773
|
+
currentSpeed
|
|
5774
|
+
]
|
|
5775
|
+
);
|
|
5776
|
+
const play = useCallback(() => {
|
|
5777
|
+
if (state.isPlaying) return;
|
|
5778
|
+
setState((prev) => ({
|
|
5779
|
+
...prev,
|
|
5780
|
+
isPlaying: true,
|
|
5781
|
+
isPaused: false,
|
|
5782
|
+
error: null
|
|
5783
|
+
}));
|
|
5784
|
+
onStart?.();
|
|
5785
|
+
const startTime = performance.now() - state.currentTime * 1e3 / currentSpeed;
|
|
5786
|
+
startTimeRef.current = startTime;
|
|
5787
|
+
motionRef.current = requestAnimationFrame(() => {
|
|
5788
|
+
const elapsed = (performance.now() - startTimeRef.current) * currentSpeed / 1e3;
|
|
5789
|
+
updateMotion(elapsed);
|
|
5790
|
+
});
|
|
5791
|
+
}, [state.isPlaying, state.currentTime, currentSpeed, onStart, updateMotion]);
|
|
5792
|
+
const pause = useCallback(() => {
|
|
5793
|
+
if (!state.isPlaying || state.isPaused) return;
|
|
5794
|
+
setState((prev) => ({
|
|
5795
|
+
...prev,
|
|
5796
|
+
isPaused: true
|
|
5797
|
+
}));
|
|
5798
|
+
if (motionRef.current) {
|
|
5799
|
+
cancelAnimationFrame(motionRef.current);
|
|
5800
|
+
motionRef.current = null;
|
|
5801
|
+
}
|
|
5802
|
+
pauseTimeRef.current = state.currentTime;
|
|
5803
|
+
}, [state.isPlaying, state.isPaused, state.currentTime]);
|
|
5804
|
+
const stop = useCallback(() => {
|
|
5805
|
+
setState((prev) => ({
|
|
5806
|
+
...prev,
|
|
5807
|
+
isPlaying: false,
|
|
5808
|
+
isPaused: false,
|
|
5809
|
+
currentTime: 0,
|
|
5810
|
+
progress: 0,
|
|
5811
|
+
currentStep: null
|
|
5812
|
+
}));
|
|
5813
|
+
if (motionRef.current) {
|
|
5814
|
+
cancelAnimationFrame(motionRef.current);
|
|
5815
|
+
motionRef.current = null;
|
|
5816
|
+
}
|
|
5817
|
+
}, []);
|
|
5818
|
+
const reset = useCallback(() => {
|
|
5819
|
+
stop();
|
|
5820
|
+
setState((prev) => ({
|
|
5821
|
+
...prev,
|
|
5822
|
+
currentTime: 0,
|
|
5823
|
+
progress: 0,
|
|
5824
|
+
currentStep: null,
|
|
5825
|
+
loopCount: 0
|
|
5826
|
+
}));
|
|
5827
|
+
}, [stop]);
|
|
5828
|
+
const seek = useCallback(
|
|
5829
|
+
(time) => {
|
|
5830
|
+
const total = getTotalDuration();
|
|
5831
|
+
const clampedTime = Math.max(0, Math.min(time, total));
|
|
5832
|
+
setState((prev) => ({
|
|
5833
|
+
...prev,
|
|
5834
|
+
currentTime: clampedTime,
|
|
5835
|
+
progress: clampedTime / total,
|
|
5836
|
+
currentStep: getCurrentStep(clampedTime)
|
|
5837
|
+
}));
|
|
5838
|
+
},
|
|
5839
|
+
[getTotalDuration, getCurrentStep]
|
|
5840
|
+
);
|
|
5841
|
+
const setSpeed = useCallback((speed2) => {
|
|
5842
|
+
setCurrentSpeed(Math.max(0.1, speed2));
|
|
5843
|
+
}, []);
|
|
5844
|
+
const reverseDirection = useCallback(() => {
|
|
5845
|
+
setIsReversed((prev) => !prev);
|
|
5846
|
+
}, []);
|
|
5847
|
+
const addStep = useCallback((step) => {
|
|
5848
|
+
setSteps((prev) => [...prev, step]);
|
|
5849
|
+
}, []);
|
|
5850
|
+
const removeStep = useCallback((stepId) => {
|
|
5851
|
+
setSteps((prev) => prev.filter((step) => step.id !== stepId));
|
|
5852
|
+
}, []);
|
|
5853
|
+
const updateStep = useCallback(
|
|
5854
|
+
(stepId, updates) => {
|
|
5855
|
+
setSteps(
|
|
5856
|
+
(prev) => prev.map(
|
|
5857
|
+
(step) => step.id === stepId ? { ...step, ...updates } : step
|
|
5858
|
+
)
|
|
5859
|
+
);
|
|
5860
|
+
},
|
|
5861
|
+
[]
|
|
5862
|
+
);
|
|
5863
|
+
const reorderSteps = useCallback((stepIds) => {
|
|
5864
|
+
setSteps((prev) => {
|
|
5865
|
+
const stepMap = new Map(prev.map((step) => [step.id, step]));
|
|
5866
|
+
return stepIds.map((id) => stepMap.get(id)).filter(Boolean);
|
|
5867
|
+
});
|
|
5868
|
+
}, []);
|
|
5869
|
+
useEffect(() => {
|
|
5870
|
+
const currentStep = state.currentStep;
|
|
5871
|
+
if (currentStep) {
|
|
5872
|
+
onStepStart?.(currentStep);
|
|
5873
|
+
const step = steps.find((s) => s.id === currentStep);
|
|
5874
|
+
step?.onStart?.();
|
|
5875
|
+
}
|
|
5876
|
+
}, [state.currentStep, steps, onStepStart]);
|
|
5877
|
+
useEffect(() => {
|
|
5878
|
+
const currentStep = state.currentStep;
|
|
5879
|
+
if (currentStep) {
|
|
5880
|
+
const stepProgress = getStepProgress(currentStep);
|
|
5881
|
+
if (stepProgress >= 1) {
|
|
5882
|
+
const step = steps.find((s) => s.id === currentStep);
|
|
5883
|
+
step?.onComplete?.();
|
|
5884
|
+
onStepComplete?.(currentStep);
|
|
5885
|
+
}
|
|
5886
|
+
}
|
|
5887
|
+
}, [
|
|
5888
|
+
state.currentTime,
|
|
5889
|
+
state.currentStep,
|
|
5890
|
+
steps,
|
|
5891
|
+
getStepProgress,
|
|
5892
|
+
onStepComplete
|
|
5893
|
+
]);
|
|
5894
|
+
useEffect(() => {
|
|
5895
|
+
calculateStepTimes();
|
|
5896
|
+
}, [calculateStepTimes]);
|
|
5897
|
+
useEffect(() => {
|
|
5898
|
+
if (autoStart && steps.length > 0) {
|
|
5899
|
+
play();
|
|
5900
|
+
}
|
|
5901
|
+
}, [autoStart, steps.length, play]);
|
|
5902
|
+
useEffect(() => {
|
|
5903
|
+
return () => {
|
|
5904
|
+
if (motionRef.current) {
|
|
5905
|
+
cancelAnimationFrame(motionRef.current);
|
|
5906
|
+
}
|
|
5907
|
+
};
|
|
5908
|
+
}, []);
|
|
5909
|
+
return {
|
|
5910
|
+
// 상태
|
|
5911
|
+
isPlaying: state.isPlaying,
|
|
5912
|
+
isPaused: state.isPaused,
|
|
5913
|
+
currentTime: state.currentTime,
|
|
5914
|
+
progress: state.progress,
|
|
5915
|
+
currentStep: state.currentStep,
|
|
5916
|
+
loopCount: state.loopCount,
|
|
5917
|
+
error: state.error,
|
|
5918
|
+
// 제어
|
|
5919
|
+
play,
|
|
5920
|
+
pause,
|
|
5921
|
+
stop,
|
|
5922
|
+
reset,
|
|
5923
|
+
seek,
|
|
5924
|
+
setSpeed,
|
|
5925
|
+
reverse: reverseDirection,
|
|
5926
|
+
// 타임라인 관리
|
|
5927
|
+
addStep,
|
|
5928
|
+
removeStep,
|
|
5929
|
+
updateStep,
|
|
5930
|
+
reorderSteps,
|
|
5931
|
+
// 유틸리티
|
|
5932
|
+
getStepProgress,
|
|
5933
|
+
getStepTime,
|
|
5934
|
+
getTotalDuration
|
|
5935
|
+
};
|
|
5936
|
+
}
|
|
5937
|
+
function useSequence(sequence, options = {}) {
|
|
5938
|
+
const { autoStart = true, loop = false } = options;
|
|
5939
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
5940
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
5941
|
+
const motionsRef = useRef([]);
|
|
5942
|
+
const timeoutsRef = useRef([]);
|
|
5943
|
+
const motions = sequence.map((item) => item.hook());
|
|
5944
|
+
const start = useCallback(() => {
|
|
5945
|
+
if (isPlaying) return;
|
|
5946
|
+
setIsPlaying(true);
|
|
5947
|
+
setCurrentIndex(0);
|
|
5948
|
+
motionsRef.current = motions;
|
|
5949
|
+
if (motionsRef.current[0]) {
|
|
5950
|
+
motionsRef.current[0].start();
|
|
5951
|
+
}
|
|
5952
|
+
sequence.forEach((item, index) => {
|
|
5953
|
+
if (index === 0) return;
|
|
5954
|
+
const timeout = window.setTimeout(() => {
|
|
5955
|
+
if (motionsRef.current[index]) {
|
|
5956
|
+
motionsRef.current[index].start();
|
|
5957
|
+
setCurrentIndex(index);
|
|
5958
|
+
}
|
|
5959
|
+
}, item.delay || 0);
|
|
5960
|
+
timeoutsRef.current.push(timeout);
|
|
5961
|
+
});
|
|
5962
|
+
const totalDuration = sequence.reduce((total, item) => {
|
|
5963
|
+
return total + (item.delay || 0);
|
|
5964
|
+
}, 0);
|
|
5965
|
+
const finalTimeout = window.setTimeout(() => {
|
|
5966
|
+
setIsPlaying(false);
|
|
5967
|
+
if (loop) {
|
|
5968
|
+
start();
|
|
5969
|
+
}
|
|
5970
|
+
}, totalDuration + 1e3);
|
|
5971
|
+
timeoutsRef.current.push(finalTimeout);
|
|
5972
|
+
}, [sequence, isPlaying, loop, motions]);
|
|
5973
|
+
const stop = useCallback(() => {
|
|
5974
|
+
setIsPlaying(false);
|
|
5975
|
+
setCurrentIndex(0);
|
|
5976
|
+
timeoutsRef.current.forEach((timeout) => window.clearTimeout(timeout));
|
|
5977
|
+
timeoutsRef.current = [];
|
|
5978
|
+
motionsRef.current.forEach((motion) => {
|
|
5979
|
+
if (motion && motion.stop) {
|
|
5980
|
+
motion.stop();
|
|
5981
|
+
}
|
|
5982
|
+
});
|
|
5983
|
+
}, []);
|
|
5984
|
+
const reset = useCallback(() => {
|
|
5985
|
+
stop();
|
|
5986
|
+
setCurrentIndex(0);
|
|
5987
|
+
motionsRef.current.forEach((motion) => {
|
|
5988
|
+
if (motion && motion.reset) {
|
|
5989
|
+
motion.reset();
|
|
5990
|
+
}
|
|
5991
|
+
});
|
|
5992
|
+
}, [stop]);
|
|
5993
|
+
const pause = useCallback(() => {
|
|
5994
|
+
setIsPlaying(false);
|
|
5995
|
+
if (motionsRef.current[currentIndex] && motionsRef.current[currentIndex].pause) {
|
|
5996
|
+
motionsRef.current[currentIndex].pause();
|
|
5997
|
+
}
|
|
5998
|
+
}, [currentIndex]);
|
|
5999
|
+
const resume = useCallback(() => {
|
|
6000
|
+
setIsPlaying(true);
|
|
6001
|
+
if (motionsRef.current[currentIndex] && motionsRef.current[currentIndex].resume) {
|
|
6002
|
+
motionsRef.current[currentIndex].resume();
|
|
6003
|
+
}
|
|
6004
|
+
}, [currentIndex]);
|
|
6005
|
+
useEffect(() => {
|
|
6006
|
+
if (autoStart && !isPlaying) {
|
|
6007
|
+
start();
|
|
6008
|
+
}
|
|
6009
|
+
}, [autoStart, isPlaying, start]);
|
|
6010
|
+
return {
|
|
6011
|
+
start,
|
|
6012
|
+
stop,
|
|
6013
|
+
pause,
|
|
6014
|
+
resume,
|
|
6015
|
+
reset,
|
|
6016
|
+
isPlaying,
|
|
6017
|
+
currentIndex,
|
|
6018
|
+
totalMotions: sequence.length,
|
|
6019
|
+
ref: motions[0]?.ref || (() => {
|
|
6020
|
+
})
|
|
6021
|
+
// 첫 번째 모션의 ref 반환
|
|
6022
|
+
};
|
|
6023
|
+
}
|
|
6024
|
+
function useLayoutMotion(config) {
|
|
6025
|
+
const {
|
|
6026
|
+
from,
|
|
6027
|
+
to,
|
|
6028
|
+
duration = 500,
|
|
6029
|
+
easing = "ease-in-out",
|
|
6030
|
+
autoStart = false,
|
|
6031
|
+
onComplete
|
|
6032
|
+
} = config;
|
|
6033
|
+
const ref = useRef(null);
|
|
6034
|
+
const [state, setState] = useState({
|
|
6035
|
+
isAnimating: false,
|
|
6036
|
+
progress: 0,
|
|
6037
|
+
currentStyle: {}
|
|
6038
|
+
});
|
|
6039
|
+
const motionFrameRef = useRef(null);
|
|
6040
|
+
const startTimeRef = useRef(0);
|
|
6041
|
+
const parseValue = useCallback(
|
|
6042
|
+
(value) => {
|
|
6043
|
+
if (typeof value === "number") return value;
|
|
6044
|
+
if (typeof value === "string") {
|
|
6045
|
+
const match = value.match(/^(\d+(?:\.\d+)?)(px|%|em|rem|vh|vw)?$/);
|
|
6046
|
+
return match ? parseFloat(match[1]) : 0;
|
|
6047
|
+
}
|
|
6048
|
+
return 0;
|
|
6049
|
+
},
|
|
6050
|
+
[]
|
|
6051
|
+
);
|
|
6052
|
+
const interpolate = useCallback(
|
|
6053
|
+
(from2, to2, progress) => {
|
|
6054
|
+
return from2 + (to2 - from2) * progress;
|
|
6055
|
+
},
|
|
6056
|
+
[]
|
|
6057
|
+
);
|
|
6058
|
+
const applyEasing2 = useCallback(
|
|
6059
|
+
(t) => {
|
|
6060
|
+
switch (easing) {
|
|
6061
|
+
case "ease-in":
|
|
6062
|
+
return t * t;
|
|
6063
|
+
case "ease-out":
|
|
6064
|
+
return 1 - (1 - t) * (1 - t);
|
|
6065
|
+
case "ease-in-out":
|
|
6066
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
6067
|
+
default:
|
|
6068
|
+
return t;
|
|
6069
|
+
}
|
|
6070
|
+
},
|
|
6071
|
+
[easing]
|
|
6072
|
+
);
|
|
6073
|
+
const calculateStyle = useCallback(
|
|
6074
|
+
(progress) => {
|
|
6075
|
+
const easedProgress = applyEasing2(progress);
|
|
6076
|
+
const style = {};
|
|
6077
|
+
if (from.width !== void 0 && to.width !== void 0) {
|
|
6078
|
+
const fromWidth = parseValue(from.width);
|
|
6079
|
+
const toWidth = parseValue(to.width);
|
|
6080
|
+
style.width = `${interpolate(fromWidth, toWidth, easedProgress)}px`;
|
|
6081
|
+
}
|
|
6082
|
+
if (from.height !== void 0 && to.height !== void 0) {
|
|
6083
|
+
const fromHeight = parseValue(from.height);
|
|
6084
|
+
const toHeight = parseValue(to.height);
|
|
6085
|
+
style.height = `${interpolate(fromHeight, toHeight, easedProgress)}px`;
|
|
6086
|
+
}
|
|
6087
|
+
if (from.flexDirection !== to.flexDirection) {
|
|
6088
|
+
style.flexDirection = progress < 0.5 ? from.flexDirection : to.flexDirection;
|
|
6089
|
+
}
|
|
6090
|
+
if (from.justifyContent !== to.justifyContent) {
|
|
6091
|
+
style.justifyContent = progress < 0.5 ? from.justifyContent : to.justifyContent;
|
|
6092
|
+
}
|
|
6093
|
+
if (from.alignItems !== to.alignItems) {
|
|
6094
|
+
style.alignItems = progress < 0.5 ? from.alignItems : to.alignItems;
|
|
6095
|
+
}
|
|
6096
|
+
if (from.gap !== void 0 && to.gap !== void 0) {
|
|
6097
|
+
const fromGap = parseValue(from.gap);
|
|
6098
|
+
const toGap = parseValue(to.gap);
|
|
6099
|
+
style.gap = `${interpolate(fromGap, toGap, easedProgress)}px`;
|
|
6100
|
+
}
|
|
6101
|
+
if (from.gridTemplateColumns !== to.gridTemplateColumns) {
|
|
6102
|
+
style.gridTemplateColumns = progress < 0.5 ? from.gridTemplateColumns : to.gridTemplateColumns;
|
|
6103
|
+
}
|
|
6104
|
+
if (from.gridTemplateRows !== to.gridTemplateRows) {
|
|
6105
|
+
style.gridTemplateRows = progress < 0.5 ? from.gridTemplateRows : to.gridTemplateRows;
|
|
6106
|
+
}
|
|
6107
|
+
if (from.gridGap !== void 0 && to.gridGap !== void 0) {
|
|
6108
|
+
const fromGridGap = parseValue(from.gridGap);
|
|
6109
|
+
const toGridGap = parseValue(to.gridGap);
|
|
6110
|
+
style.gridGap = `${interpolate(fromGridGap, toGridGap, easedProgress)}px`;
|
|
6111
|
+
}
|
|
6112
|
+
return style;
|
|
6113
|
+
},
|
|
6114
|
+
[from, to, applyEasing2, parseValue, interpolate]
|
|
6115
|
+
);
|
|
6116
|
+
const updateMotion = useCallback(() => {
|
|
6117
|
+
if (!state.isAnimating) return;
|
|
6118
|
+
const elapsed = Date.now() - startTimeRef.current;
|
|
6119
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
6120
|
+
setState((prev) => ({
|
|
6121
|
+
...prev,
|
|
6122
|
+
progress,
|
|
6123
|
+
currentStyle: calculateStyle(progress)
|
|
6124
|
+
}));
|
|
6125
|
+
if (progress < 1) {
|
|
6126
|
+
motionFrameRef.current = requestAnimationFrame(updateMotion);
|
|
6127
|
+
} else {
|
|
6128
|
+
setState((prev) => ({ ...prev, isAnimating: false }));
|
|
6129
|
+
onComplete?.();
|
|
6130
|
+
}
|
|
6131
|
+
}, [state.isAnimating, duration, calculateStyle, onComplete]);
|
|
6132
|
+
const start = useCallback(() => {
|
|
6133
|
+
setState((prev) => ({ ...prev, isAnimating: true, progress: 0 }));
|
|
6134
|
+
startTimeRef.current = Date.now();
|
|
6135
|
+
updateMotion();
|
|
6136
|
+
}, [updateMotion]);
|
|
6137
|
+
const stop = useCallback(() => {
|
|
6138
|
+
setState((prev) => ({ ...prev, isAnimating: false }));
|
|
6139
|
+
if (motionFrameRef.current) {
|
|
6140
|
+
cancelAnimationFrame(motionFrameRef.current);
|
|
6141
|
+
}
|
|
6142
|
+
}, []);
|
|
6143
|
+
const reset = useCallback(() => {
|
|
6144
|
+
setState({
|
|
6145
|
+
isAnimating: false,
|
|
6146
|
+
progress: 0,
|
|
6147
|
+
currentStyle: calculateStyle(0)
|
|
6148
|
+
});
|
|
6149
|
+
if (motionFrameRef.current) {
|
|
6150
|
+
cancelAnimationFrame(motionFrameRef.current);
|
|
6151
|
+
}
|
|
6152
|
+
}, [calculateStyle]);
|
|
6153
|
+
useEffect(() => {
|
|
6154
|
+
if (autoStart) {
|
|
6155
|
+
start();
|
|
6156
|
+
}
|
|
6157
|
+
return () => {
|
|
6158
|
+
if (motionFrameRef.current) {
|
|
6159
|
+
cancelAnimationFrame(motionFrameRef.current);
|
|
6160
|
+
}
|
|
6161
|
+
};
|
|
6162
|
+
}, [autoStart, start]);
|
|
6163
|
+
return {
|
|
6164
|
+
ref,
|
|
6165
|
+
state,
|
|
6166
|
+
start,
|
|
6167
|
+
stop,
|
|
6168
|
+
reset
|
|
6169
|
+
};
|
|
6170
|
+
}
|
|
6171
|
+
function createLayoutTransition(from, to, options = {}) {
|
|
6172
|
+
return {
|
|
6173
|
+
from,
|
|
6174
|
+
to,
|
|
6175
|
+
duration: options.duration || 500,
|
|
6176
|
+
easing: options.easing || "ease-in-out",
|
|
6177
|
+
autoStart: options.autoStart || false,
|
|
6178
|
+
onComplete: options.onComplete
|
|
6179
|
+
};
|
|
6180
|
+
}
|
|
6181
|
+
function useKeyboardToggle(options = {}) {
|
|
6182
|
+
const {
|
|
6183
|
+
initialState = false,
|
|
6184
|
+
keys = [" "],
|
|
6185
|
+
keyCode,
|
|
6186
|
+
keyCombo,
|
|
6187
|
+
toggleOnKeyDown = true,
|
|
6188
|
+
toggleOnKeyUp = false,
|
|
6189
|
+
toggleOnKeyPress = false,
|
|
6190
|
+
autoReset = false,
|
|
6191
|
+
resetDelay = 3e3,
|
|
6192
|
+
preventDefault = false,
|
|
6193
|
+
stopPropagation = false,
|
|
6194
|
+
requireFocus = false,
|
|
6195
|
+
showOnMount = false
|
|
6196
|
+
} = options;
|
|
6197
|
+
const [isActive, setIsActive] = useState(showOnMount ? initialState : false);
|
|
6198
|
+
const [mounted, setMounted] = useState(false);
|
|
6199
|
+
const resetTimeoutRef = useRef(null);
|
|
6200
|
+
const elementRef = useRef(null);
|
|
6201
|
+
const pressedKeysRef = useRef(/* @__PURE__ */ new Set());
|
|
6202
|
+
useEffect(() => {
|
|
6203
|
+
setMounted(true);
|
|
6204
|
+
}, []);
|
|
6205
|
+
const startResetTimer = useCallback(() => {
|
|
6206
|
+
if (!autoReset || resetDelay <= 0) return;
|
|
6207
|
+
if (resetTimeoutRef.current !== null) {
|
|
6208
|
+
clearTimeout(resetTimeoutRef.current);
|
|
6209
|
+
}
|
|
6210
|
+
resetTimeoutRef.current = window.setTimeout(() => {
|
|
6211
|
+
setIsActive(false);
|
|
6212
|
+
resetTimeoutRef.current = null;
|
|
6213
|
+
}, resetDelay);
|
|
6214
|
+
}, [autoReset, resetDelay]);
|
|
6215
|
+
const toggle = useCallback(() => {
|
|
6216
|
+
if (!mounted) return;
|
|
6217
|
+
setIsActive((prev) => {
|
|
6218
|
+
const newState = !prev;
|
|
6219
|
+
if (newState && autoReset) {
|
|
6220
|
+
startResetTimer();
|
|
6221
|
+
} else if (!newState && resetTimeoutRef.current !== null) {
|
|
6222
|
+
clearTimeout(resetTimeoutRef.current);
|
|
6223
|
+
resetTimeoutRef.current = null;
|
|
6224
|
+
}
|
|
6225
|
+
return newState;
|
|
6226
|
+
});
|
|
6227
|
+
}, [mounted, autoReset, startResetTimer]);
|
|
6228
|
+
const activate = useCallback(() => {
|
|
6229
|
+
if (!mounted || isActive) return;
|
|
6230
|
+
setIsActive(true);
|
|
6231
|
+
if (autoReset) {
|
|
6232
|
+
startResetTimer();
|
|
6233
|
+
}
|
|
6234
|
+
}, [mounted, isActive, autoReset, startResetTimer]);
|
|
6235
|
+
const deactivate = useCallback(() => {
|
|
6236
|
+
if (!mounted || !isActive) return;
|
|
6237
|
+
setIsActive(false);
|
|
6238
|
+
if (resetTimeoutRef.current !== null) {
|
|
6239
|
+
clearTimeout(resetTimeoutRef.current);
|
|
6240
|
+
resetTimeoutRef.current = null;
|
|
6241
|
+
}
|
|
6242
|
+
}, [mounted, isActive]);
|
|
6243
|
+
const reset = useCallback(() => {
|
|
6244
|
+
setIsActive(initialState);
|
|
6245
|
+
if (resetTimeoutRef.current !== null) {
|
|
6246
|
+
clearTimeout(resetTimeoutRef.current);
|
|
6247
|
+
resetTimeoutRef.current = null;
|
|
6248
|
+
}
|
|
6249
|
+
}, [initialState]);
|
|
6250
|
+
const isKeyMatch = useCallback(
|
|
6251
|
+
(event) => {
|
|
6252
|
+
if (keyCode !== void 0 && event.keyCode === keyCode) {
|
|
6253
|
+
return true;
|
|
6254
|
+
}
|
|
6255
|
+
if (keys.length > 0 && keys.includes(event.key)) {
|
|
6256
|
+
return true;
|
|
6257
|
+
}
|
|
6258
|
+
if (keyCombo && keyCombo.length > 0) {
|
|
6259
|
+
const pressedKeys = Array.from(pressedKeysRef.current);
|
|
6260
|
+
const comboMatch = keyCombo.every((key) => pressedKeys.includes(key));
|
|
6261
|
+
return comboMatch;
|
|
6262
|
+
}
|
|
6263
|
+
return false;
|
|
6264
|
+
},
|
|
6265
|
+
[keys, keyCode, keyCombo]
|
|
6266
|
+
);
|
|
6267
|
+
const isFocused = useCallback(() => {
|
|
6268
|
+
if (!requireFocus) return true;
|
|
6269
|
+
return document.activeElement === elementRef.current;
|
|
6270
|
+
}, [requireFocus]);
|
|
6271
|
+
const handleKeyDown = useCallback(
|
|
6272
|
+
(event) => {
|
|
6273
|
+
if (!toggleOnKeyDown || !isFocused()) return;
|
|
6274
|
+
pressedKeysRef.current.add(event.key);
|
|
6275
|
+
if (isKeyMatch(event)) {
|
|
6276
|
+
if (preventDefault) event.preventDefault();
|
|
6277
|
+
if (stopPropagation) event.stopPropagation();
|
|
6278
|
+
toggle();
|
|
6279
|
+
}
|
|
6280
|
+
},
|
|
6281
|
+
[
|
|
6282
|
+
toggleOnKeyDown,
|
|
6283
|
+
isFocused,
|
|
6284
|
+
isKeyMatch,
|
|
6285
|
+
preventDefault,
|
|
6286
|
+
stopPropagation,
|
|
6287
|
+
toggle
|
|
6288
|
+
]
|
|
6289
|
+
);
|
|
6290
|
+
const handleKeyUp = useCallback(
|
|
6291
|
+
(event) => {
|
|
6292
|
+
if (!toggleOnKeyUp || !isFocused()) return;
|
|
6293
|
+
pressedKeysRef.current.delete(event.key);
|
|
6294
|
+
if (isKeyMatch(event)) {
|
|
6295
|
+
if (preventDefault) event.preventDefault();
|
|
6296
|
+
if (stopPropagation) event.stopPropagation();
|
|
6297
|
+
toggle();
|
|
6298
|
+
}
|
|
6299
|
+
},
|
|
6300
|
+
[
|
|
6301
|
+
toggleOnKeyUp,
|
|
6302
|
+
isFocused,
|
|
6303
|
+
isKeyMatch,
|
|
6304
|
+
preventDefault,
|
|
6305
|
+
stopPropagation,
|
|
6306
|
+
toggle
|
|
6307
|
+
]
|
|
6308
|
+
);
|
|
6309
|
+
const handleKeyPress = useCallback(
|
|
6310
|
+
(event) => {
|
|
6311
|
+
if (!toggleOnKeyPress || !isFocused()) return;
|
|
6312
|
+
if (isKeyMatch(event)) {
|
|
6313
|
+
if (preventDefault) event.preventDefault();
|
|
6314
|
+
if (stopPropagation) event.stopPropagation();
|
|
6315
|
+
toggle();
|
|
6316
|
+
}
|
|
6317
|
+
},
|
|
6318
|
+
[
|
|
6319
|
+
toggleOnKeyPress,
|
|
6320
|
+
isFocused,
|
|
6321
|
+
isKeyMatch,
|
|
6322
|
+
preventDefault,
|
|
6323
|
+
stopPropagation,
|
|
6324
|
+
toggle
|
|
6325
|
+
]
|
|
6326
|
+
);
|
|
6327
|
+
useEffect(() => {
|
|
6328
|
+
if (requireFocus || !mounted) return;
|
|
6329
|
+
const handleGlobalKeyDown = (event) => {
|
|
6330
|
+
pressedKeysRef.current.add(event.key);
|
|
6331
|
+
if (isKeyMatch(event)) {
|
|
6332
|
+
if (preventDefault) event.preventDefault();
|
|
6333
|
+
if (stopPropagation) event.stopPropagation();
|
|
6334
|
+
toggle();
|
|
6335
|
+
}
|
|
6336
|
+
};
|
|
6337
|
+
const handleGlobalKeyUp = (event) => {
|
|
6338
|
+
pressedKeysRef.current.delete(event.key);
|
|
6339
|
+
if (isKeyMatch(event)) {
|
|
6340
|
+
if (preventDefault) event.preventDefault();
|
|
6341
|
+
if (stopPropagation) event.stopPropagation();
|
|
6342
|
+
toggle();
|
|
6343
|
+
}
|
|
6344
|
+
};
|
|
6345
|
+
document.addEventListener("keydown", handleGlobalKeyDown);
|
|
6346
|
+
document.addEventListener("keyup", handleGlobalKeyUp);
|
|
6347
|
+
return () => {
|
|
6348
|
+
document.removeEventListener("keydown", handleGlobalKeyDown);
|
|
6349
|
+
document.removeEventListener("keyup", handleGlobalKeyUp);
|
|
6350
|
+
};
|
|
6351
|
+
}, [
|
|
6352
|
+
requireFocus,
|
|
6353
|
+
mounted,
|
|
6354
|
+
isKeyMatch,
|
|
6355
|
+
preventDefault,
|
|
6356
|
+
stopPropagation,
|
|
6357
|
+
toggle
|
|
6358
|
+
]);
|
|
6359
|
+
useEffect(() => {
|
|
6360
|
+
return () => {
|
|
6361
|
+
if (resetTimeoutRef.current !== null) {
|
|
6362
|
+
clearTimeout(resetTimeoutRef.current);
|
|
6363
|
+
}
|
|
6364
|
+
};
|
|
6365
|
+
}, []);
|
|
6366
|
+
const keyboardHandlers = {
|
|
6367
|
+
...toggleOnKeyDown && { onKeyDown: handleKeyDown },
|
|
6368
|
+
...toggleOnKeyUp && { onKeyUp: handleKeyUp },
|
|
6369
|
+
...toggleOnKeyPress && { onKeyPress: handleKeyPress }
|
|
6370
|
+
};
|
|
6371
|
+
return {
|
|
6372
|
+
isActive,
|
|
6373
|
+
mounted,
|
|
6374
|
+
toggle,
|
|
6375
|
+
activate,
|
|
6376
|
+
deactivate,
|
|
6377
|
+
reset,
|
|
6378
|
+
keyboardHandlers,
|
|
6379
|
+
ref: elementRef
|
|
6380
|
+
};
|
|
6381
|
+
}
|
|
6382
|
+
function useScrollDirection(options = {}) {
|
|
6383
|
+
const { threshold = 10, idleDelay = 150, showOnMount = false } = options;
|
|
6384
|
+
const [direction, setDirection] = useState(
|
|
6385
|
+
showOnMount ? "idle" : "idle"
|
|
6386
|
+
);
|
|
6387
|
+
const [mounted, setMounted] = useState(false);
|
|
6388
|
+
const [lastScrollY, setLastScrollY] = useState(0);
|
|
6389
|
+
const [idleTimeout, setIdleTimeout] = useState(null);
|
|
6390
|
+
useEffect(() => {
|
|
6391
|
+
setMounted(true);
|
|
6392
|
+
}, []);
|
|
6393
|
+
useEffect(() => {
|
|
6394
|
+
if (!mounted) return;
|
|
6395
|
+
const handleScroll = () => {
|
|
6396
|
+
if (typeof window !== "undefined") {
|
|
6397
|
+
const currentScrollY = window.pageYOffset;
|
|
6398
|
+
const scrollDifference = Math.abs(currentScrollY - lastScrollY);
|
|
6399
|
+
if (idleTimeout !== null) {
|
|
6400
|
+
clearTimeout(idleTimeout);
|
|
6401
|
+
}
|
|
6402
|
+
if (scrollDifference > threshold) {
|
|
6403
|
+
const newDirection = currentScrollY > lastScrollY ? "down" : "up";
|
|
6404
|
+
setDirection(newDirection);
|
|
6405
|
+
setLastScrollY(currentScrollY);
|
|
6406
|
+
const timeout = setTimeout(() => {
|
|
6407
|
+
setDirection("idle");
|
|
6408
|
+
}, idleDelay);
|
|
6409
|
+
setIdleTimeout(timeout);
|
|
6410
|
+
}
|
|
6411
|
+
}
|
|
6412
|
+
};
|
|
6413
|
+
if (typeof window !== "undefined") {
|
|
6414
|
+
setLastScrollY(window.pageYOffset);
|
|
6415
|
+
}
|
|
6416
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
6417
|
+
return () => {
|
|
6418
|
+
window.removeEventListener("scroll", handleScroll);
|
|
6419
|
+
if (idleTimeout !== null) {
|
|
6420
|
+
clearTimeout(idleTimeout);
|
|
6421
|
+
}
|
|
6422
|
+
};
|
|
6423
|
+
}, [threshold, idleDelay, mounted, lastScrollY, idleTimeout]);
|
|
6424
|
+
return {
|
|
6425
|
+
direction,
|
|
6426
|
+
mounted
|
|
6427
|
+
};
|
|
6428
|
+
}
|
|
6429
|
+
function useStickyToggle(options = {}) {
|
|
6430
|
+
const {
|
|
6431
|
+
offset = 0,
|
|
6432
|
+
behavior: _behavior = "smooth",
|
|
6433
|
+
showOnMount = false
|
|
6434
|
+
} = options;
|
|
6435
|
+
const [isSticky, setIsSticky] = useState(showOnMount);
|
|
6436
|
+
const [mounted, setMounted] = useState(false);
|
|
6437
|
+
useEffect(() => {
|
|
6438
|
+
setMounted(true);
|
|
6439
|
+
}, []);
|
|
6440
|
+
useEffect(() => {
|
|
6441
|
+
if (!mounted) return;
|
|
6442
|
+
const toggleSticky = () => {
|
|
6443
|
+
if (typeof window !== "undefined") {
|
|
6444
|
+
if (window.pageYOffset > offset) {
|
|
6445
|
+
setIsSticky(true);
|
|
6446
|
+
} else {
|
|
6447
|
+
setIsSticky(false);
|
|
6448
|
+
}
|
|
6449
|
+
}
|
|
6450
|
+
};
|
|
6451
|
+
toggleSticky();
|
|
6452
|
+
window.addEventListener("scroll", toggleSticky, { passive: true });
|
|
6453
|
+
window.addEventListener("resize", toggleSticky, { passive: true });
|
|
6454
|
+
return () => {
|
|
6455
|
+
window.removeEventListener("scroll", toggleSticky);
|
|
6456
|
+
window.removeEventListener("resize", toggleSticky);
|
|
6457
|
+
};
|
|
6458
|
+
}, [offset, mounted]);
|
|
6459
|
+
return {
|
|
6460
|
+
isSticky,
|
|
6461
|
+
mounted
|
|
6462
|
+
};
|
|
6463
|
+
}
|
|
6464
|
+
function useInteractive(config = {}) {
|
|
6465
|
+
const {
|
|
6466
|
+
hoverScale = 1.05,
|
|
6467
|
+
clickScale = 0.95,
|
|
6468
|
+
duration: _duration = 200
|
|
6469
|
+
} = config;
|
|
6470
|
+
const ref = useRef(null);
|
|
6471
|
+
const [scale, setScale] = useState(1);
|
|
6472
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
6473
|
+
const [isClicked, setIsClicked] = useState(false);
|
|
6474
|
+
const handleMouseEnter = useCallback(() => {
|
|
6475
|
+
setIsHovered(true);
|
|
6476
|
+
setScale(hoverScale);
|
|
6477
|
+
}, [hoverScale]);
|
|
6478
|
+
const handleMouseLeave = useCallback(() => {
|
|
6479
|
+
setIsHovered(false);
|
|
6480
|
+
setScale(1);
|
|
6481
|
+
}, []);
|
|
6482
|
+
const handleMouseDown = useCallback(() => {
|
|
6483
|
+
setIsClicked(true);
|
|
6484
|
+
setScale(clickScale);
|
|
6485
|
+
}, [clickScale]);
|
|
6486
|
+
const handleMouseUp = useCallback(() => {
|
|
6487
|
+
setIsClicked(false);
|
|
6488
|
+
setScale(isHovered ? hoverScale : 1);
|
|
6489
|
+
}, [isHovered, hoverScale]);
|
|
6490
|
+
const setRef = (element) => {
|
|
6491
|
+
if (ref.current !== element) {
|
|
6492
|
+
ref.current = element;
|
|
6493
|
+
}
|
|
6494
|
+
};
|
|
6495
|
+
return {
|
|
6496
|
+
ref: setRef,
|
|
6497
|
+
scale,
|
|
6498
|
+
isHovered,
|
|
6499
|
+
isClicked,
|
|
6500
|
+
handleMouseEnter,
|
|
6501
|
+
handleMouseLeave,
|
|
6502
|
+
handleMouseDown,
|
|
6503
|
+
handleMouseUp
|
|
6504
|
+
};
|
|
6505
|
+
}
|
|
6506
|
+
function usePerformanceMonitor(config = {}) {
|
|
6507
|
+
const { threshold = 30, onPerformanceIssue } = config;
|
|
6508
|
+
const ref = useRef(null);
|
|
6509
|
+
const [fps, setFps] = useState(60);
|
|
6510
|
+
const [isLowPerformance, setIsLowPerformance] = useState(false);
|
|
6511
|
+
const [frameCount, setFrameCount] = useState(0);
|
|
6512
|
+
const lastTime = useRef(performance.now());
|
|
6513
|
+
const frameCountRef = useRef(0);
|
|
6514
|
+
const measurePerformance = () => {
|
|
6515
|
+
const now = performance.now();
|
|
6516
|
+
frameCountRef.current++;
|
|
6517
|
+
if (now - lastTime.current >= 1e3) {
|
|
6518
|
+
const currentFps = Math.round(
|
|
6519
|
+
frameCountRef.current * 1e3 / (now - lastTime.current)
|
|
6520
|
+
);
|
|
6521
|
+
setFps(currentFps);
|
|
6522
|
+
setFrameCount(frameCountRef.current);
|
|
6523
|
+
const lowPerformance = currentFps < threshold;
|
|
6524
|
+
setIsLowPerformance(lowPerformance);
|
|
6525
|
+
if (lowPerformance && onPerformanceIssue) {
|
|
6526
|
+
onPerformanceIssue(currentFps);
|
|
6527
|
+
}
|
|
6528
|
+
frameCountRef.current = 0;
|
|
6529
|
+
lastTime.current = now;
|
|
6530
|
+
}
|
|
6531
|
+
requestAnimationFrame(measurePerformance);
|
|
6532
|
+
};
|
|
6533
|
+
useEffect(() => {
|
|
6534
|
+
const motionId = requestAnimationFrame(measurePerformance);
|
|
6535
|
+
return () => cancelAnimationFrame(motionId);
|
|
6536
|
+
}, []);
|
|
6537
|
+
const setRef = (element) => {
|
|
6538
|
+
if (ref.current !== element) {
|
|
6539
|
+
ref.current = element;
|
|
6540
|
+
}
|
|
6541
|
+
};
|
|
6542
|
+
return {
|
|
6543
|
+
ref: setRef,
|
|
6544
|
+
fps,
|
|
6545
|
+
isLowPerformance,
|
|
6546
|
+
frameCount
|
|
6547
|
+
};
|
|
6548
|
+
}
|
|
6549
|
+
function useLanguageAwareMotion(options) {
|
|
6550
|
+
const {
|
|
6551
|
+
motionType,
|
|
6552
|
+
duration = 700,
|
|
6553
|
+
delay = 0,
|
|
6554
|
+
threshold = 0.1,
|
|
6555
|
+
pauseOnLanguageChange = true,
|
|
6556
|
+
restartOnLanguageChange = false,
|
|
6557
|
+
currentLanguage: externalLanguage
|
|
6558
|
+
} = options;
|
|
6559
|
+
const elementRef = useRef(null);
|
|
6560
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
6561
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
6562
|
+
const [internalLanguage, setInternalLanguage] = useState("");
|
|
6563
|
+
const isVisibleRef = useRef(false);
|
|
6564
|
+
const isPausedRef = useRef(false);
|
|
6565
|
+
isVisibleRef.current = isVisible;
|
|
6566
|
+
isPausedRef.current = isPaused;
|
|
6567
|
+
useEffect(() => {
|
|
6568
|
+
if (externalLanguage && internalLanguage !== externalLanguage) {
|
|
6569
|
+
setInternalLanguage(externalLanguage);
|
|
6570
|
+
if (pauseOnLanguageChange && isVisible) {
|
|
6571
|
+
setIsPaused(true);
|
|
6572
|
+
setTimeout(() => {
|
|
6573
|
+
setIsPaused(false);
|
|
6574
|
+
}, 200);
|
|
6575
|
+
}
|
|
6576
|
+
if (restartOnLanguageChange && isVisible) {
|
|
6577
|
+
setIsVisible(false);
|
|
6578
|
+
setTimeout(() => {
|
|
6579
|
+
setIsVisible(true);
|
|
6580
|
+
}, 100);
|
|
6581
|
+
}
|
|
6582
|
+
}
|
|
6583
|
+
}, [
|
|
6584
|
+
externalLanguage,
|
|
6585
|
+
internalLanguage,
|
|
6586
|
+
isVisible,
|
|
6587
|
+
pauseOnLanguageChange,
|
|
6588
|
+
restartOnLanguageChange
|
|
6589
|
+
]);
|
|
6590
|
+
useEffect(() => {
|
|
6591
|
+
if (!elementRef.current) return;
|
|
6592
|
+
const observer = new IntersectionObserver(
|
|
6593
|
+
(entries) => {
|
|
6594
|
+
entries.forEach((entry) => {
|
|
6595
|
+
if (entry.isIntersecting && !isVisibleRef.current && !isPausedRef.current) {
|
|
6596
|
+
setTimeout(() => {
|
|
6597
|
+
setIsVisible(true);
|
|
6598
|
+
}, delay);
|
|
6599
|
+
}
|
|
6600
|
+
});
|
|
6601
|
+
},
|
|
6602
|
+
{ threshold }
|
|
6603
|
+
);
|
|
6604
|
+
observer.observe(elementRef.current);
|
|
6605
|
+
return () => {
|
|
6606
|
+
observer.disconnect();
|
|
6607
|
+
};
|
|
6608
|
+
}, [delay, threshold]);
|
|
6609
|
+
const getMotionStyle = useCallback(() => {
|
|
6610
|
+
const baseTransition = `all ${duration}ms ease-out`;
|
|
6611
|
+
if (isPaused) {
|
|
6612
|
+
return {
|
|
6613
|
+
opacity: 1,
|
|
6614
|
+
transform: "none",
|
|
6615
|
+
transition: baseTransition
|
|
6616
|
+
};
|
|
6617
|
+
}
|
|
6618
|
+
if (!isVisible) {
|
|
6619
|
+
switch (motionType) {
|
|
6620
|
+
case "fadeIn":
|
|
6621
|
+
return {
|
|
6622
|
+
opacity: 0,
|
|
6623
|
+
transition: baseTransition
|
|
6624
|
+
};
|
|
6625
|
+
case "slideUp":
|
|
6626
|
+
return {
|
|
6627
|
+
opacity: 0,
|
|
6628
|
+
transform: "translateY(32px)",
|
|
6629
|
+
transition: baseTransition
|
|
6630
|
+
};
|
|
6631
|
+
case "slideLeft":
|
|
6632
|
+
return {
|
|
6633
|
+
opacity: 0,
|
|
6634
|
+
transform: "translateX(-32px)",
|
|
6635
|
+
transition: baseTransition
|
|
6636
|
+
};
|
|
6637
|
+
case "slideRight":
|
|
6638
|
+
return {
|
|
6639
|
+
opacity: 0,
|
|
6640
|
+
transform: "translateX(32px)",
|
|
6641
|
+
transition: baseTransition
|
|
6642
|
+
};
|
|
6643
|
+
case "scaleIn":
|
|
6644
|
+
return {
|
|
6645
|
+
opacity: 0,
|
|
6646
|
+
transform: "scale(0.95)",
|
|
6647
|
+
transition: baseTransition
|
|
6648
|
+
};
|
|
6649
|
+
case "bounceIn":
|
|
6650
|
+
return {
|
|
6651
|
+
opacity: 0,
|
|
6652
|
+
transform: "scale(0.75)",
|
|
6653
|
+
transition: baseTransition
|
|
6654
|
+
};
|
|
6655
|
+
default:
|
|
6656
|
+
return {
|
|
6657
|
+
opacity: 0,
|
|
6658
|
+
transition: baseTransition
|
|
6659
|
+
};
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
return {
|
|
6663
|
+
opacity: 1,
|
|
6664
|
+
transform: "none",
|
|
6665
|
+
transition: baseTransition
|
|
6666
|
+
};
|
|
6667
|
+
}, [isVisible, isPaused, motionType, duration]);
|
|
6668
|
+
const pauseMotion = useCallback(() => {
|
|
6669
|
+
setIsPaused(true);
|
|
6670
|
+
}, []);
|
|
6671
|
+
const resumeMotion = useCallback(() => {
|
|
6672
|
+
setIsPaused(false);
|
|
6673
|
+
}, []);
|
|
6674
|
+
const restartMotion = useCallback(() => {
|
|
6675
|
+
setIsVisible(false);
|
|
6676
|
+
setTimeout(() => {
|
|
6677
|
+
setIsVisible(true);
|
|
6678
|
+
}, 100);
|
|
6679
|
+
}, []);
|
|
6680
|
+
const motionStyle = useMemo(() => getMotionStyle(), [getMotionStyle]);
|
|
6681
|
+
return {
|
|
6682
|
+
ref: elementRef,
|
|
6683
|
+
isVisible,
|
|
6684
|
+
isPaused,
|
|
6685
|
+
style: motionStyle,
|
|
6686
|
+
pauseMotion,
|
|
6687
|
+
resumeMotion,
|
|
6688
|
+
restartMotion,
|
|
6689
|
+
currentLanguage: internalLanguage
|
|
6690
|
+
};
|
|
6691
|
+
}
|
|
6692
|
+
function useGameLoop(options = {}) {
|
|
6693
|
+
const {
|
|
6694
|
+
fps = 60,
|
|
6695
|
+
autoStart = false,
|
|
6696
|
+
maxFPS = 120,
|
|
6697
|
+
minFPS = 30,
|
|
6698
|
+
showOnMount = false
|
|
6699
|
+
} = options;
|
|
6700
|
+
const [isRunning, setIsRunning] = useState(showOnMount ? autoStart : false);
|
|
6701
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
6702
|
+
const [currentFPS, setCurrentFPS] = useState(0);
|
|
6703
|
+
const [deltaTime, setDeltaTime] = useState(0);
|
|
6704
|
+
const [elapsedTime, setElapsedTime] = useState(0);
|
|
6705
|
+
const [frameCount, setFrameCount] = useState(0);
|
|
6706
|
+
const [mounted, setMounted] = useState(false);
|
|
6707
|
+
const motionRef = useRef(null);
|
|
6708
|
+
const lastTimeRef = useRef(null);
|
|
6709
|
+
const frameTimeRef = useRef(1e3 / fps);
|
|
6710
|
+
const fpsUpdateTimeRef = useRef(0);
|
|
6711
|
+
const fpsFrameCountRef = useRef(0);
|
|
6712
|
+
const updateCallbacksRef = useRef([]);
|
|
6713
|
+
const renderCallbacksRef = useRef([]);
|
|
6714
|
+
useEffect(() => {
|
|
6715
|
+
setMounted(true);
|
|
6716
|
+
}, []);
|
|
6717
|
+
const gameLoop = useCallback(
|
|
6718
|
+
(currentTime) => {
|
|
6719
|
+
if (!isRunning || isPaused) return;
|
|
6720
|
+
if (lastTimeRef.current === null) {
|
|
6721
|
+
lastTimeRef.current = currentTime;
|
|
6722
|
+
motionRef.current = requestAnimationFrame(gameLoop);
|
|
6723
|
+
return;
|
|
6724
|
+
}
|
|
6725
|
+
const delta = currentTime - lastTimeRef.current;
|
|
6726
|
+
const targetDelta = frameTimeRef.current;
|
|
6727
|
+
if (delta >= targetDelta) {
|
|
6728
|
+
updateCallbacksRef.current.forEach((callback) => {
|
|
6729
|
+
try {
|
|
6730
|
+
callback(delta, elapsedTime);
|
|
6731
|
+
} catch (error) {
|
|
6732
|
+
if (process.env.NODE_ENV === "development") {
|
|
6733
|
+
console.error("Game loop update error:", error);
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
});
|
|
6737
|
+
renderCallbacksRef.current.forEach((callback) => {
|
|
6738
|
+
try {
|
|
6739
|
+
callback(delta, elapsedTime);
|
|
6740
|
+
} catch (error) {
|
|
6741
|
+
if (process.env.NODE_ENV === "development") {
|
|
6742
|
+
console.error("Game loop render error:", error);
|
|
6743
|
+
}
|
|
6744
|
+
}
|
|
6745
|
+
});
|
|
6746
|
+
setDeltaTime(delta);
|
|
6747
|
+
setElapsedTime((prev) => prev + delta);
|
|
6748
|
+
setFrameCount((prev) => prev + 1);
|
|
6749
|
+
lastTimeRef.current = currentTime;
|
|
6750
|
+
fpsFrameCountRef.current++;
|
|
6751
|
+
if (currentTime - fpsUpdateTimeRef.current >= 1e3) {
|
|
6752
|
+
const newFPS = Math.round(
|
|
6753
|
+
fpsFrameCountRef.current * 1e3 / (currentTime - fpsUpdateTimeRef.current)
|
|
6754
|
+
);
|
|
6755
|
+
setCurrentFPS(newFPS);
|
|
6756
|
+
fpsFrameCountRef.current = 0;
|
|
6757
|
+
fpsUpdateTimeRef.current = currentTime;
|
|
6758
|
+
if (process.env.NODE_ENV === "development" && typeof window !== "undefined" && window.location.hostname === "localhost") {
|
|
6759
|
+
if (newFPS < minFPS) {
|
|
6760
|
+
console.warn(`Low FPS detected: ${newFPS} (min: ${minFPS})`);
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6765
|
+
motionRef.current = requestAnimationFrame(gameLoop);
|
|
6766
|
+
},
|
|
6767
|
+
[isRunning, isPaused, elapsedTime, minFPS]
|
|
6768
|
+
);
|
|
6769
|
+
const start = useCallback(() => {
|
|
6770
|
+
if (!mounted) return;
|
|
6771
|
+
setIsRunning(true);
|
|
6772
|
+
setIsPaused(false);
|
|
6773
|
+
setElapsedTime(0);
|
|
6774
|
+
setFrameCount(0);
|
|
6775
|
+
setDeltaTime(0);
|
|
6776
|
+
setCurrentFPS(0);
|
|
6777
|
+
lastTimeRef.current = null;
|
|
6778
|
+
fpsUpdateTimeRef.current = 0;
|
|
6779
|
+
fpsFrameCountRef.current = 0;
|
|
6780
|
+
if (!motionRef.current) {
|
|
6781
|
+
motionRef.current = requestAnimationFrame(gameLoop);
|
|
6782
|
+
}
|
|
6783
|
+
}, [mounted, gameLoop]);
|
|
6784
|
+
const stop = useCallback(() => {
|
|
6785
|
+
setIsRunning(false);
|
|
6786
|
+
setIsPaused(false);
|
|
6787
|
+
if (motionRef.current) {
|
|
6788
|
+
cancelAnimationFrame(motionRef.current);
|
|
6789
|
+
motionRef.current = null;
|
|
6790
|
+
}
|
|
6791
|
+
}, []);
|
|
6792
|
+
const pause = useCallback(() => {
|
|
6793
|
+
if (!isRunning) return;
|
|
6794
|
+
setIsPaused(true);
|
|
6795
|
+
}, [isRunning]);
|
|
6796
|
+
const resume = useCallback(() => {
|
|
6797
|
+
if (!isRunning) return;
|
|
6798
|
+
setIsPaused(false);
|
|
6799
|
+
if (!motionRef.current) {
|
|
6800
|
+
motionRef.current = requestAnimationFrame(gameLoop);
|
|
6801
|
+
}
|
|
6802
|
+
}, [isRunning, gameLoop]);
|
|
6803
|
+
const reset = useCallback(() => {
|
|
6804
|
+
setElapsedTime(0);
|
|
6805
|
+
setFrameCount(0);
|
|
6806
|
+
setDeltaTime(0);
|
|
6807
|
+
setCurrentFPS(0);
|
|
6808
|
+
lastTimeRef.current = null;
|
|
6809
|
+
fpsUpdateTimeRef.current = 0;
|
|
6810
|
+
fpsFrameCountRef.current = 0;
|
|
6811
|
+
}, []);
|
|
6812
|
+
const onUpdate = useCallback(
|
|
6813
|
+
(callback) => {
|
|
6814
|
+
updateCallbacksRef.current.push(callback);
|
|
6815
|
+
return () => {
|
|
6816
|
+
const index = updateCallbacksRef.current.indexOf(callback);
|
|
6817
|
+
if (index > -1) {
|
|
6818
|
+
updateCallbacksRef.current.splice(index, 1);
|
|
6819
|
+
}
|
|
6820
|
+
};
|
|
6821
|
+
},
|
|
6822
|
+
[]
|
|
6823
|
+
);
|
|
6824
|
+
const onRender = useCallback(
|
|
6825
|
+
(callback) => {
|
|
6826
|
+
renderCallbacksRef.current.push(callback);
|
|
6827
|
+
return () => {
|
|
6828
|
+
const index = renderCallbacksRef.current.indexOf(callback);
|
|
6829
|
+
if (index > -1) {
|
|
6830
|
+
renderCallbacksRef.current.splice(index, 1);
|
|
6831
|
+
}
|
|
6832
|
+
};
|
|
6833
|
+
},
|
|
6834
|
+
[]
|
|
6835
|
+
);
|
|
6836
|
+
useEffect(() => {
|
|
6837
|
+
frameTimeRef.current = 1e3 / Math.min(fps, maxFPS);
|
|
6838
|
+
}, [fps, maxFPS]);
|
|
6839
|
+
useEffect(() => {
|
|
6840
|
+
if (mounted && autoStart && !isRunning) {
|
|
6841
|
+
start();
|
|
6842
|
+
}
|
|
6843
|
+
}, [mounted, autoStart, isRunning, start]);
|
|
6844
|
+
useEffect(() => {
|
|
6845
|
+
return () => {
|
|
6846
|
+
if (motionRef.current) {
|
|
6847
|
+
cancelAnimationFrame(motionRef.current);
|
|
6848
|
+
}
|
|
6849
|
+
};
|
|
6850
|
+
}, []);
|
|
6851
|
+
return {
|
|
6852
|
+
isRunning,
|
|
6853
|
+
fps: currentFPS,
|
|
6854
|
+
deltaTime,
|
|
6855
|
+
elapsedTime,
|
|
6856
|
+
frameCount,
|
|
6857
|
+
mounted,
|
|
6858
|
+
start,
|
|
6859
|
+
stop,
|
|
6860
|
+
pause,
|
|
6861
|
+
resume,
|
|
6862
|
+
reset,
|
|
6863
|
+
onUpdate,
|
|
6864
|
+
onRender
|
|
6865
|
+
};
|
|
6866
|
+
}
|
|
6867
|
+
function useMotion(configOrFrom = {}, to, options) {
|
|
6868
|
+
let config;
|
|
6869
|
+
let fromValues;
|
|
6870
|
+
let toValues;
|
|
6871
|
+
if (to && options) {
|
|
6872
|
+
fromValues = configOrFrom;
|
|
6873
|
+
toValues = to;
|
|
6874
|
+
config = {
|
|
6875
|
+
duration: options.duration || 1e3,
|
|
6876
|
+
delay: options.delay || 0,
|
|
6877
|
+
autoStart: options.autoStart || false,
|
|
6878
|
+
easing: options.ease || "ease-out"
|
|
6879
|
+
};
|
|
6880
|
+
} else {
|
|
6881
|
+
config = configOrFrom;
|
|
6882
|
+
const { type = "fade" } = config;
|
|
6883
|
+
switch (type) {
|
|
6884
|
+
case "fade":
|
|
6885
|
+
fromValues = { opacity: 0 };
|
|
6886
|
+
toValues = { opacity: 1 };
|
|
6887
|
+
break;
|
|
6888
|
+
case "slide":
|
|
6889
|
+
fromValues = { opacity: 0, translateX: 100 };
|
|
6890
|
+
toValues = { opacity: 1, translateX: 0 };
|
|
6891
|
+
break;
|
|
6892
|
+
case "scale":
|
|
6893
|
+
fromValues = { opacity: 0, scale: 0 };
|
|
6894
|
+
toValues = { opacity: 1, scale: 1 };
|
|
6895
|
+
break;
|
|
6896
|
+
case "rotate":
|
|
6897
|
+
fromValues = { opacity: 0, rotate: 180 };
|
|
6898
|
+
toValues = { opacity: 1, rotate: 0 };
|
|
6899
|
+
break;
|
|
6900
|
+
default:
|
|
6901
|
+
fromValues = { opacity: 0 };
|
|
6902
|
+
toValues = { opacity: 1 };
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
const {
|
|
6906
|
+
duration = 1e3,
|
|
6907
|
+
delay = 0,
|
|
6908
|
+
autoStart = true,
|
|
6909
|
+
easing = "ease-out"
|
|
6910
|
+
} = config;
|
|
6911
|
+
const ref = useRef(null);
|
|
6912
|
+
const fromValuesRef = useRef(fromValues);
|
|
6913
|
+
const toValuesRef = useRef(toValues);
|
|
6914
|
+
const initialBgColor = useRef(fromValues.backgroundColor);
|
|
6915
|
+
const [state, setState] = useState({
|
|
6916
|
+
transform: "",
|
|
6917
|
+
opacity: fromValues.opacity ?? 1,
|
|
6918
|
+
backgroundColor: fromValues.backgroundColor,
|
|
6919
|
+
isAnimating: false
|
|
6920
|
+
});
|
|
6921
|
+
const easingFunction = useCallback(
|
|
6922
|
+
(t) => {
|
|
6923
|
+
switch (easing) {
|
|
6924
|
+
case "ease-in":
|
|
6925
|
+
return t * t;
|
|
6926
|
+
case "ease-out":
|
|
6927
|
+
return 1 - (1 - t) * (1 - t);
|
|
6928
|
+
case "ease-in-out":
|
|
6929
|
+
return t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t);
|
|
6930
|
+
default:
|
|
6931
|
+
return t;
|
|
6932
|
+
}
|
|
6933
|
+
},
|
|
6934
|
+
[easing]
|
|
6935
|
+
);
|
|
6936
|
+
const interpolate = useCallback(
|
|
6937
|
+
(from, to2, progress) => {
|
|
6938
|
+
return from + (to2 - from) * progress;
|
|
6939
|
+
},
|
|
6940
|
+
[]
|
|
6941
|
+
);
|
|
6942
|
+
const updateMotion = useCallback(
|
|
6943
|
+
(progress) => {
|
|
6944
|
+
const easedProgress = easingFunction(progress);
|
|
6945
|
+
const from = fromValuesRef.current;
|
|
6946
|
+
const to2 = toValuesRef.current;
|
|
6947
|
+
const newState = {
|
|
6948
|
+
transform: "",
|
|
6949
|
+
opacity: 1,
|
|
6950
|
+
backgroundColor: initialBgColor.current,
|
|
6951
|
+
isAnimating: true
|
|
6952
|
+
};
|
|
6953
|
+
if (from.opacity !== void 0 && to2.opacity !== void 0) {
|
|
6954
|
+
newState.opacity = interpolate(from.opacity, to2.opacity, easedProgress);
|
|
6955
|
+
}
|
|
6956
|
+
const transforms = [];
|
|
6957
|
+
if (from.translateX !== void 0 && to2.translateX !== void 0) {
|
|
6958
|
+
const translateX = interpolate(
|
|
6959
|
+
from.translateX,
|
|
6960
|
+
to2.translateX,
|
|
6961
|
+
easedProgress
|
|
6962
|
+
);
|
|
6963
|
+
transforms.push(`translateX(${translateX}px)`);
|
|
6964
|
+
}
|
|
6965
|
+
if (from.translateY !== void 0 && to2.translateY !== void 0) {
|
|
6966
|
+
const translateY = interpolate(
|
|
6967
|
+
from.translateY,
|
|
6968
|
+
to2.translateY,
|
|
6969
|
+
easedProgress
|
|
6970
|
+
);
|
|
6971
|
+
transforms.push(`translateY(${translateY}px)`);
|
|
6972
|
+
}
|
|
6973
|
+
if (from.scale !== void 0 && to2.scale !== void 0) {
|
|
6974
|
+
const scaleVal = interpolate(from.scale, to2.scale, easedProgress);
|
|
6975
|
+
transforms.push(`scale(${scaleVal})`);
|
|
6976
|
+
}
|
|
6977
|
+
if (from.rotate !== void 0 && to2.rotate !== void 0) {
|
|
6978
|
+
const rotate = interpolate(from.rotate, to2.rotate, easedProgress);
|
|
6979
|
+
transforms.push(`rotate(${rotate}deg)`);
|
|
6980
|
+
}
|
|
6981
|
+
if (transforms.length > 0) {
|
|
6982
|
+
newState.transform = transforms.join(" ");
|
|
6983
|
+
}
|
|
6984
|
+
if (from.backgroundColor && to2.backgroundColor) {
|
|
6985
|
+
newState.backgroundColor = easedProgress > 0.5 ? to2.backgroundColor : from.backgroundColor;
|
|
6986
|
+
}
|
|
6987
|
+
setState(newState);
|
|
6988
|
+
},
|
|
6989
|
+
[easingFunction, interpolate]
|
|
6990
|
+
);
|
|
6991
|
+
const start = useCallback(() => {
|
|
6992
|
+
setState((prev) => ({ ...prev, isAnimating: true }));
|
|
6993
|
+
const startTime = Date.now();
|
|
6994
|
+
const animate = () => {
|
|
6995
|
+
const elapsed = Date.now() - startTime;
|
|
6996
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
6997
|
+
updateMotion(progress);
|
|
6998
|
+
if (progress < 1) {
|
|
6999
|
+
requestAnimationFrame(animate);
|
|
7000
|
+
} else {
|
|
7001
|
+
setState((prev) => ({ ...prev, isAnimating: false }));
|
|
7002
|
+
}
|
|
7003
|
+
};
|
|
7004
|
+
setTimeout(() => {
|
|
7005
|
+
requestAnimationFrame(animate);
|
|
7006
|
+
}, delay);
|
|
7007
|
+
}, [duration, delay, updateMotion]);
|
|
7008
|
+
const reset = useCallback(() => {
|
|
7009
|
+
const from = fromValuesRef.current;
|
|
7010
|
+
setState({
|
|
7011
|
+
transform: "",
|
|
7012
|
+
opacity: from.opacity ?? 1,
|
|
7013
|
+
backgroundColor: from.backgroundColor,
|
|
7014
|
+
isAnimating: false
|
|
7015
|
+
});
|
|
7016
|
+
}, []);
|
|
7017
|
+
const stop = useCallback(() => {
|
|
7018
|
+
setState((prev) => ({ ...prev, isAnimating: false }));
|
|
7019
|
+
}, []);
|
|
7020
|
+
useEffect(() => {
|
|
7021
|
+
if (autoStart) {
|
|
7022
|
+
start();
|
|
7023
|
+
}
|
|
7024
|
+
}, []);
|
|
7025
|
+
const setRef = (element) => {
|
|
7026
|
+
if (ref.current !== element) {
|
|
7027
|
+
ref.current = element;
|
|
7028
|
+
}
|
|
7029
|
+
};
|
|
7030
|
+
const style = {
|
|
7031
|
+
opacity: state.opacity,
|
|
7032
|
+
...state.transform && { transform: state.transform },
|
|
7033
|
+
...state.backgroundColor && { backgroundColor: state.backgroundColor }
|
|
7034
|
+
};
|
|
7035
|
+
return {
|
|
7036
|
+
ref: setRef,
|
|
7037
|
+
style,
|
|
7038
|
+
transform: state.transform,
|
|
7039
|
+
opacity: state.opacity,
|
|
7040
|
+
backgroundColor: state.backgroundColor,
|
|
7041
|
+
isAnimating: state.isAnimating,
|
|
7042
|
+
start,
|
|
7043
|
+
stop,
|
|
7044
|
+
reset
|
|
7045
|
+
};
|
|
7046
|
+
}
|
|
7047
|
+
function useViewportToggle(options = {}) {
|
|
7048
|
+
const {
|
|
7049
|
+
threshold = 0.1,
|
|
7050
|
+
rootMargin = "0px",
|
|
7051
|
+
trigger = "both",
|
|
7052
|
+
once = false,
|
|
7053
|
+
showOnMount = false
|
|
7054
|
+
} = options;
|
|
7055
|
+
const [isVisible, setIsVisible] = useState(showOnMount);
|
|
7056
|
+
const [mounted, setMounted] = useState(false);
|
|
7057
|
+
const elementRef = useRef(null);
|
|
7058
|
+
const hasTriggeredRef = useRef(false);
|
|
7059
|
+
const isVisibleRef = useRef(showOnMount);
|
|
7060
|
+
isVisibleRef.current = isVisible;
|
|
7061
|
+
useEffect(() => {
|
|
7062
|
+
setMounted(true);
|
|
7063
|
+
}, []);
|
|
7064
|
+
useEffect(() => {
|
|
7065
|
+
if (!mounted || !elementRef.current) return;
|
|
7066
|
+
const observer = new IntersectionObserver(
|
|
7067
|
+
(entries) => {
|
|
7068
|
+
entries.forEach((entry) => {
|
|
7069
|
+
const isIntersecting = entry.isIntersecting;
|
|
7070
|
+
if (once && hasTriggeredRef.current) return;
|
|
7071
|
+
let shouldBeVisible = isVisibleRef.current;
|
|
7072
|
+
if (trigger === "enter" && isIntersecting) {
|
|
7073
|
+
shouldBeVisible = true;
|
|
7074
|
+
if (once) hasTriggeredRef.current = true;
|
|
7075
|
+
} else if (trigger === "exit" && !isIntersecting) {
|
|
7076
|
+
shouldBeVisible = true;
|
|
7077
|
+
if (once) hasTriggeredRef.current = true;
|
|
7078
|
+
} else if (trigger === "both") {
|
|
7079
|
+
shouldBeVisible = isIntersecting;
|
|
7080
|
+
if (once && isIntersecting) hasTriggeredRef.current = true;
|
|
7081
|
+
}
|
|
7082
|
+
setIsVisible(shouldBeVisible);
|
|
7083
|
+
});
|
|
7084
|
+
},
|
|
7085
|
+
{
|
|
7086
|
+
threshold,
|
|
7087
|
+
rootMargin
|
|
7088
|
+
}
|
|
7089
|
+
);
|
|
7090
|
+
observer.observe(elementRef.current);
|
|
7091
|
+
return () => {
|
|
7092
|
+
observer.disconnect();
|
|
7093
|
+
};
|
|
7094
|
+
}, [threshold, rootMargin, trigger, once, mounted]);
|
|
7095
|
+
return {
|
|
7096
|
+
ref: elementRef,
|
|
7097
|
+
isVisible,
|
|
7098
|
+
mounted
|
|
7099
|
+
};
|
|
7100
|
+
}
|
|
7101
|
+
function useScrollPositionToggle(options = {}) {
|
|
7102
|
+
const { threshold = 400, showOnMount = false, smooth = true } = options;
|
|
7103
|
+
const [isVisible, setIsVisible] = useState(showOnMount);
|
|
7104
|
+
const [mounted, setMounted] = useState(false);
|
|
7105
|
+
useEffect(() => {
|
|
7106
|
+
setMounted(true);
|
|
7107
|
+
}, []);
|
|
7108
|
+
useEffect(() => {
|
|
7109
|
+
if (!mounted) return;
|
|
7110
|
+
const toggleVisibility = () => {
|
|
7111
|
+
if (typeof window !== "undefined") {
|
|
7112
|
+
if (window.pageYOffset > threshold) {
|
|
7113
|
+
setIsVisible(true);
|
|
7114
|
+
} else {
|
|
7115
|
+
setIsVisible(false);
|
|
7116
|
+
}
|
|
7117
|
+
}
|
|
7118
|
+
};
|
|
7119
|
+
toggleVisibility();
|
|
7120
|
+
window.addEventListener("scroll", toggleVisibility, { passive: true });
|
|
7121
|
+
window.addEventListener("resize", toggleVisibility, { passive: true });
|
|
7122
|
+
return () => {
|
|
7123
|
+
window.removeEventListener("scroll", toggleVisibility);
|
|
7124
|
+
window.removeEventListener("resize", toggleVisibility);
|
|
7125
|
+
};
|
|
7126
|
+
}, [threshold, mounted]);
|
|
7127
|
+
const scrollToTop = () => {
|
|
7128
|
+
if (typeof window !== "undefined") {
|
|
7129
|
+
if (smooth) {
|
|
7130
|
+
window.scrollTo({
|
|
7131
|
+
top: 0,
|
|
7132
|
+
behavior: "smooth"
|
|
7133
|
+
});
|
|
7134
|
+
} else {
|
|
7135
|
+
window.scrollTo(0, 0);
|
|
7136
|
+
}
|
|
7137
|
+
}
|
|
7138
|
+
};
|
|
7139
|
+
return {
|
|
7140
|
+
isVisible,
|
|
7141
|
+
scrollToTop,
|
|
7142
|
+
mounted
|
|
7143
|
+
};
|
|
7144
|
+
}
|
|
7145
|
+
function Motion({
|
|
7146
|
+
as: Component = "div",
|
|
7147
|
+
type,
|
|
7148
|
+
effects,
|
|
7149
|
+
scroll,
|
|
7150
|
+
delay,
|
|
7151
|
+
duration,
|
|
7152
|
+
children,
|
|
7153
|
+
className,
|
|
7154
|
+
style: userStyle,
|
|
7155
|
+
...rest
|
|
7156
|
+
}) {
|
|
7157
|
+
const scrollOptions = useMemo(() => {
|
|
7158
|
+
if (!scroll) return null;
|
|
7159
|
+
const base = typeof scroll === "object" ? scroll : {};
|
|
7160
|
+
return {
|
|
7161
|
+
...base,
|
|
7162
|
+
...delay != null && { delay },
|
|
7163
|
+
...duration != null && { duration },
|
|
7164
|
+
...type != null && { motionType: type }
|
|
7165
|
+
};
|
|
7166
|
+
}, [scroll, delay, duration, type]);
|
|
7167
|
+
const scrollMotion = useScrollReveal(scrollOptions ?? { delay: 0 });
|
|
7168
|
+
const unifiedMotion = useUnifiedMotion({
|
|
7169
|
+
type: type ?? "fadeIn",
|
|
7170
|
+
effects,
|
|
7171
|
+
delay,
|
|
7172
|
+
duration,
|
|
7173
|
+
autoStart: true
|
|
7174
|
+
});
|
|
7175
|
+
const isScroll = scroll != null && scroll !== false;
|
|
7176
|
+
const motion = isScroll ? scrollMotion : unifiedMotion;
|
|
7177
|
+
const mergedStyle = useMemo(() => {
|
|
7178
|
+
if (!userStyle) return motion.style;
|
|
7179
|
+
return { ...motion.style, ...userStyle };
|
|
7180
|
+
}, [motion.style, userStyle]);
|
|
7181
|
+
return /* @__PURE__ */ jsx(
|
|
7182
|
+
Component,
|
|
7183
|
+
{
|
|
7184
|
+
ref: motion.ref,
|
|
7185
|
+
className,
|
|
7186
|
+
style: mergedStyle,
|
|
7187
|
+
...rest,
|
|
7188
|
+
children
|
|
7189
|
+
}
|
|
7190
|
+
);
|
|
7191
|
+
}
|
|
7192
|
+
function useCountUp(options) {
|
|
7193
|
+
const {
|
|
7194
|
+
end,
|
|
7195
|
+
suffix = "",
|
|
7196
|
+
duration = 1500,
|
|
7197
|
+
delay = 0,
|
|
7198
|
+
active = true
|
|
7199
|
+
} = options;
|
|
7200
|
+
const [value, setValue] = useState(0);
|
|
7201
|
+
const startedRef = useRef(false);
|
|
7202
|
+
useEffect(() => {
|
|
7203
|
+
if (!active || startedRef.current) return;
|
|
7204
|
+
const timer = setTimeout(() => {
|
|
7205
|
+
startedRef.current = true;
|
|
7206
|
+
const startTime = performance.now();
|
|
7207
|
+
const animate = (now) => {
|
|
7208
|
+
const elapsed = now - startTime;
|
|
7209
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
7210
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
7211
|
+
setValue(Math.round(eased * end));
|
|
7212
|
+
if (progress < 1) requestAnimationFrame(animate);
|
|
7213
|
+
};
|
|
7214
|
+
requestAnimationFrame(animate);
|
|
7215
|
+
}, delay);
|
|
7216
|
+
return () => clearTimeout(timer);
|
|
7217
|
+
}, [active, end, duration, delay]);
|
|
7218
|
+
return { value, display: `${value}${suffix}` };
|
|
7219
|
+
}
|
|
7220
|
+
var SPRING_EASING = "cubic-bezier(0.34, 1.56, 0.64, 1)";
|
|
7221
|
+
function useClipReveal(options = {}) {
|
|
7222
|
+
const {
|
|
7223
|
+
delay = 0,
|
|
7224
|
+
duration = 900,
|
|
7225
|
+
easing = SPRING_EASING,
|
|
7226
|
+
active = true
|
|
7227
|
+
} = options;
|
|
7228
|
+
const [triggered, setTriggered] = useState(false);
|
|
7229
|
+
useEffect(() => {
|
|
7230
|
+
if (active && !triggered) {
|
|
7231
|
+
setTriggered(true);
|
|
7232
|
+
}
|
|
7233
|
+
}, [active, triggered]);
|
|
7234
|
+
const containerStyle = useMemo(() => ({
|
|
7235
|
+
overflow: "hidden",
|
|
7236
|
+
display: "inline-block"
|
|
7237
|
+
}), []);
|
|
7238
|
+
const textStyle = useMemo(() => ({
|
|
7239
|
+
display: "block",
|
|
7240
|
+
transform: triggered ? "translateY(0)" : "translateY(110%)",
|
|
7241
|
+
transition: `transform ${duration}ms ${easing} ${delay}ms`
|
|
7242
|
+
}), [triggered, duration, easing, delay]);
|
|
7243
|
+
return { containerStyle, textStyle, isVisible: triggered };
|
|
7244
|
+
}
|
|
7245
|
+
var SMOOTH_EASING = "cubic-bezier(0.16, 1, 0.3, 1)";
|
|
7246
|
+
function useBlurIn(options = {}) {
|
|
7247
|
+
const {
|
|
7248
|
+
delay = 0,
|
|
7249
|
+
duration = 1200,
|
|
7250
|
+
blurAmount = 12,
|
|
7251
|
+
scale = 0.95,
|
|
7252
|
+
easing = SMOOTH_EASING,
|
|
7253
|
+
active = true
|
|
7254
|
+
} = options;
|
|
7255
|
+
const [triggered, setTriggered] = useState(false);
|
|
7256
|
+
useEffect(() => {
|
|
7257
|
+
if (active && !triggered) {
|
|
7258
|
+
setTriggered(true);
|
|
7259
|
+
}
|
|
7260
|
+
}, [active, triggered]);
|
|
7261
|
+
const style = useMemo(() => ({
|
|
7262
|
+
opacity: triggered ? 1 : 0,
|
|
7263
|
+
filter: triggered ? "blur(0px)" : `blur(${blurAmount}px)`,
|
|
7264
|
+
transform: triggered ? "scale(1)" : `scale(${scale})`,
|
|
7265
|
+
transition: [
|
|
7266
|
+
`opacity ${duration}ms ${easing} ${delay}ms`,
|
|
7267
|
+
`filter ${duration}ms ${easing} ${delay}ms`,
|
|
7268
|
+
`transform ${duration}ms ${easing} ${delay}ms`
|
|
7269
|
+
].join(", ")
|
|
7270
|
+
}), [triggered, duration, blurAmount, scale, easing, delay]);
|
|
7271
|
+
return { style, isVisible: triggered };
|
|
5717
7272
|
}
|
|
5718
7273
|
function getInitialTransform(motionType, slideDistance, scaleFrom) {
|
|
5719
7274
|
switch (motionType) {
|
|
@@ -5741,7 +7296,7 @@ function useStagger(options) {
|
|
|
5741
7296
|
duration = profile.base.duration,
|
|
5742
7297
|
motionType = "fadeIn",
|
|
5743
7298
|
threshold = profile.base.threshold,
|
|
5744
|
-
easing
|
|
7299
|
+
easing = profile.base.easing
|
|
5745
7300
|
} = options;
|
|
5746
7301
|
const containerRef = useRef(null);
|
|
5747
7302
|
const [isVisible, setIsVisible] = useState(false);
|
|
@@ -5767,16 +7322,16 @@ function useStagger(options) {
|
|
|
5767
7322
|
return {
|
|
5768
7323
|
opacity: 0,
|
|
5769
7324
|
transform: initialTransform,
|
|
5770
|
-
transition: `opacity ${duration}ms ${
|
|
7325
|
+
transition: `opacity ${duration}ms ${easing} ${itemDelay}ms, transform ${duration}ms ${easing} ${itemDelay}ms`
|
|
5771
7326
|
};
|
|
5772
7327
|
}
|
|
5773
7328
|
return {
|
|
5774
7329
|
opacity: 1,
|
|
5775
7330
|
transform: "none",
|
|
5776
|
-
transition: `opacity ${duration}ms ${
|
|
7331
|
+
transition: `opacity ${duration}ms ${easing} ${itemDelay}ms, transform ${duration}ms ${easing} ${itemDelay}ms`
|
|
5777
7332
|
};
|
|
5778
7333
|
});
|
|
5779
|
-
}, [count, isVisible, staggerDelay, baseDelay, duration, motionType,
|
|
7334
|
+
}, [count, isVisible, staggerDelay, baseDelay, duration, motionType, easing, initialTransform]);
|
|
5780
7335
|
return {
|
|
5781
7336
|
containerRef,
|
|
5782
7337
|
styles,
|
|
@@ -5784,6 +7339,6 @@ function useStagger(options) {
|
|
|
5784
7339
|
};
|
|
5785
7340
|
}
|
|
5786
7341
|
|
|
5787
|
-
export {
|
|
7342
|
+
export { Motion, createLayoutTransition, observeElement, useAutoFade, useAutoPlay, useAutoScale, useAutoSlide, useBlurIn, useBounceIn, useButtonEffect, useCardList, useClickToggle, useClipReveal, useCountUp, useCustomCursor, useElementProgress, useFadeIn, useFocusToggle, useGameLoop, useGesture, useGestureMotion, useGradient, useHoverMotion, useInView, useInteractive, useKeyboardToggle, useLanguageAwareMotion, useLayoutMotion, useLoadingSpinner, useMagneticCursor, useMotion, useMotionOrchestra, useMotionState, useMouse, useNavigation, useOrchestration, usePageMotions, usePerformanceMonitor, usePulse, useReducedMotion, useRepeat, useScaleIn, useScrollDirection, useScrollPositionToggle, useScrollProgress, useScrollReveal, useScrollToggle, useSequence, useSimplePageMotion, useSkeleton, useSlideDown, useSlideLeft, useSlideRight, useSlideUp, useSmartMotion, useSmoothScroll, useSpringMotion, useStagger, useStickyToggle, useToggleMotion, useTypewriter, useUnifiedMotion, useViewportToggle, useVisibilityToggle, useWindowSize };
|
|
5788
7343
|
//# sourceMappingURL=index.mjs.map
|
|
5789
7344
|
//# sourceMappingURL=index.mjs.map
|