Files
fotospiel-app/resources/js/guest/lib/liveShowEffects.ts
Codex Agent 53eb560aa5
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Add live show player playback and effects
2026-01-05 18:31:01 +01:00

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),
},
};
}
}
}