summaryrefslogtreecommitdiff
path: root/apps/web/src/effects/definitions/motion.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/effects/definitions/motion.ts')
-rw-r--r--apps/web/src/effects/definitions/motion.ts115
1 files changed, 114 insertions, 1 deletions
diff --git a/apps/web/src/effects/definitions/motion.ts b/apps/web/src/effects/definitions/motion.ts
index 8c81d801..2e3292a2 100644
--- a/apps/web/src/effects/definitions/motion.ts
+++ b/apps/web/src/effects/definitions/motion.ts
@@ -5,6 +5,76 @@ import { MOTION_EASING_OPTIONS, getMotionIntensity } from "@/effects/motion";
const ZOOM_RANGE = 0.35;
const PAN_ZOOM_HEADROOM = 0.15;
const PAN_TRAVEL = 0.06;
+const SHAKE_SCALE_HEADROOM = 0.08;
+const SHAKE_TRAVEL_X = 0.025;
+const SHAKE_TRAVEL_Y = 0.02;
+const SHAKE_FREQUENCY = 6;
+const SHAKE_Y_DETUNE = 1.31;
+const PULSE_RANGE = 0.12;
+const PULSE_FREQUENCY = 3;
+const SPIN_FULL_TURN_DEGREES = 360;
+const CUSTOM_ZOOM_MIN_SCALE = 25;
+const CUSTOM_ZOOM_MAX_SCALE = 400;
+
+function readScaleParam({
+ effectParams,
+ key,
+}: {
+ effectParams: ParamValues;
+ key: string;
+}): number {
+ const raw = effectParams[key];
+ const value = typeof raw === "number" ? raw : Number.parseFloat(String(raw));
+ return Number.isFinite(value) ? value : 100;
+}
+
+export const customZoomEffectDefinition: EffectDefinition = {
+ type: "custom-zoom",
+ name: "Custom Zoom",
+ keywords: ["zoom", "ken burns", "custom", "scale", "start", "end"],
+ category: "lens",
+ params: [
+ {
+ key: "startScale",
+ label: "Start scale",
+ type: "number",
+ default: 100,
+ min: CUSTOM_ZOOM_MIN_SCALE,
+ max: CUSTOM_ZOOM_MAX_SCALE,
+ step: 1,
+ keyframable: true,
+ },
+ {
+ key: "endScale",
+ label: "End scale",
+ type: "number",
+ default: 135,
+ min: CUSTOM_ZOOM_MIN_SCALE,
+ max: CUSTOM_ZOOM_MAX_SCALE,
+ step: 1,
+ keyframable: true,
+ },
+ {
+ key: "easing",
+ label: "Easing",
+ type: "select",
+ default: "smooth",
+ options: MOTION_EASING_OPTIONS,
+ },
+ ],
+ renderer: { passes: [] },
+ motion: {
+ evaluate: ({ effectParams, progress }) => {
+ const startScale = readScaleParam({ effectParams, key: "startScale" });
+ const endScale = readScaleParam({ effectParams, key: "endScale" });
+ return {
+ scale: (startScale + (endScale - startScale) * progress) / 100,
+ translateX: 0,
+ translateY: 0,
+ };
+ },
+ },
+};
function motionParams(): ParamDefinition[] {
return [
@@ -79,10 +149,10 @@ function buildMotionDefinition({
type,
name,
keywords,
+ category: "lens",
params: motionParams(),
renderer: { passes: [] },
motion: { evaluate },
- supportsStandalone: false,
};
}
@@ -129,4 +199,47 @@ export const motionEffectDefinitions: EffectDefinition[] = [
evaluate: ({ effectParams, progress }) =>
panOffset({ effectParams, progress, axis: "y", direction: 1 }),
}),
+ buildMotionDefinition({
+ type: "shake",
+ name: "Shake",
+ keywords: ["shake", "camera", "handheld", "wobble", "jitter"],
+ evaluate: ({ effectParams, progress }) => {
+ const intensity = getMotionIntensity({ effectParams });
+ const angle = progress * Math.PI * 2 * SHAKE_FREQUENCY;
+ return {
+ scale: 1 + SHAKE_SCALE_HEADROOM * intensity,
+ translateX: Math.sin(angle) * SHAKE_TRAVEL_X * intensity,
+ translateY:
+ Math.cos(angle * SHAKE_Y_DETUNE) * SHAKE_TRAVEL_Y * intensity,
+ };
+ },
+ }),
+ buildMotionDefinition({
+ type: "pulse",
+ name: "Pulse",
+ keywords: ["pulse", "beat", "pump", "scale", "bounce"],
+ evaluate: ({ effectParams, progress }) => ({
+ scale:
+ 1 +
+ Math.sin(progress * Math.PI * 2 * PULSE_FREQUENCY) *
+ PULSE_RANGE *
+ getMotionIntensity({ effectParams }),
+ translateX: 0,
+ translateY: 0,
+ }),
+ }),
+ buildMotionDefinition({
+ type: "spin",
+ name: "Spin",
+ keywords: ["spin", "rotate", "turn", "twirl"],
+ evaluate: ({ effectParams, progress }) => ({
+ scale: 1,
+ translateX: 0,
+ translateY: 0,
+ rotate:
+ progress *
+ SPIN_FULL_TURN_DEGREES *
+ getMotionIntensity({ effectParams }),
+ }),
+ }),
];