108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
import type { MotionProps, Transition } from 'framer-motion';
|
|
import { IOS_EASE, IOS_EASE_SOFT } from './motion';
|
|
import type { LiveShowEffectPreset } from '../services/liveShowApi';
|
|
|
|
export type LiveShowEffectSpec = {
|
|
frame: MotionProps;
|
|
flash?: MotionProps;
|
|
};
|
|
|
|
function clamp(value: number, min: number, max: number): number {
|
|
return Math.min(max, Math.max(min, value));
|
|
}
|
|
|
|
function resolveIntensity(intensity: number): number {
|
|
const safe = Number.isFinite(intensity) ? intensity : 70;
|
|
return clamp(safe / 100, 0, 1);
|
|
}
|
|
|
|
function buildTransition(duration: number, ease: Transition['ease']): Transition {
|
|
return {
|
|
duration,
|
|
ease,
|
|
};
|
|
}
|
|
|
|
export function resolveLiveShowEffect(
|
|
preset: LiveShowEffectPreset,
|
|
intensity: number,
|
|
reducedMotion: boolean
|
|
): LiveShowEffectSpec {
|
|
const strength = reducedMotion ? 0 : resolveIntensity(intensity);
|
|
const baseDuration = reducedMotion ? 0.2 : clamp(0.9 - strength * 0.35, 0.45, 1);
|
|
const exitDuration = reducedMotion ? 0.15 : clamp(baseDuration * 0.6, 0.25, 0.6);
|
|
|
|
if (reducedMotion) {
|
|
return {
|
|
frame: {
|
|
initial: { opacity: 0 },
|
|
animate: { opacity: 1 },
|
|
exit: { opacity: 0, transition: buildTransition(exitDuration, IOS_EASE_SOFT) },
|
|
transition: buildTransition(baseDuration, IOS_EASE_SOFT),
|
|
},
|
|
};
|
|
}
|
|
|
|
switch (preset) {
|
|
case 'shutter_flash': {
|
|
const scale = 1 + strength * 0.05;
|
|
return {
|
|
frame: {
|
|
initial: { opacity: 0, scale, y: 12 * strength },
|
|
animate: { opacity: 1, scale: 1, y: 0 },
|
|
exit: { opacity: 0, scale: 0.98, transition: buildTransition(exitDuration, IOS_EASE) },
|
|
transition: buildTransition(baseDuration, IOS_EASE),
|
|
},
|
|
flash: {
|
|
initial: { opacity: 0 },
|
|
animate: { opacity: [0, 0.85, 0], transition: { duration: 0.5, times: [0, 0.2, 1] } },
|
|
},
|
|
};
|
|
}
|
|
case 'polaroid_toss': {
|
|
const rotation = 3 + strength * 5;
|
|
return {
|
|
frame: {
|
|
initial: { opacity: 0, rotate: -rotation, scale: 0.9 },
|
|
animate: { opacity: 1, rotate: 0, scale: 1 },
|
|
exit: { opacity: 0, rotate: rotation * 0.5, scale: 0.98, transition: buildTransition(exitDuration, IOS_EASE) },
|
|
transition: buildTransition(baseDuration, IOS_EASE),
|
|
},
|
|
};
|
|
}
|
|
case 'parallax_glide': {
|
|
const scale = 1 + strength * 0.06;
|
|
return {
|
|
frame: {
|
|
initial: { opacity: 0, scale, y: 24 * strength },
|
|
animate: { opacity: 1, scale: 1, y: 0 },
|
|
exit: { opacity: 0, scale: 1.02, transition: buildTransition(exitDuration, IOS_EASE_SOFT) },
|
|
transition: buildTransition(baseDuration + 0.2, IOS_EASE_SOFT),
|
|
},
|
|
};
|
|
}
|
|
case 'light_effects': {
|
|
return {
|
|
frame: {
|
|
initial: { opacity: 0 },
|
|
animate: { opacity: 1 },
|
|
exit: { opacity: 0, transition: buildTransition(exitDuration, IOS_EASE_SOFT) },
|
|
transition: buildTransition(baseDuration * 0.8, IOS_EASE_SOFT),
|
|
},
|
|
};
|
|
}
|
|
case 'film_cut':
|
|
default: {
|
|
const scale = 1 + strength * 0.03;
|
|
return {
|
|
frame: {
|
|
initial: { opacity: 0, scale },
|
|
animate: { opacity: 1, scale: 1 },
|
|
exit: { opacity: 0, scale: 0.99, transition: buildTransition(exitDuration, IOS_EASE) },
|
|
transition: buildTransition(baseDuration, IOS_EASE),
|
|
},
|
|
};
|
|
}
|
|
}
|
|
}
|