diff --git a/src/lib/components/ai/model-selector.svelte b/src/lib/components/ai/model-selector.svelte
index 8db0d44..e366f1d 100644
--- a/src/lib/components/ai/model-selector.svelte
+++ b/src/lib/components/ai/model-selector.svelte
@@ -1,5 +1,5 @@
+
+
= { + time: number; + property: P; + value: P extends TransformProperty + ? number + : P extends + | 'opacity' + | 'blur' + | 'brightness' + | 'contrast' + | 'saturate' + | 'dropShadowX' + | 'dropShadowY' + | 'dropShadowBlur' + ? number + : P extends 'dropShadowColor' + ? string + : never; + interpolation?: Interpolation; +}; + +/** + * Type-safe animation preset with category + */ +export interface TypedAnimationPreset { + id: string; + name: string; + category: PresetCategory; + keyframes: PresetKeyframe[]; +} + +/** + * Helper to create type-safe preset keyframes + */ +function kf
( + time: number, + property: P, + value: PresetKeyframe
['value'], + interpolation?: Interpolation +): PresetKeyframe
{ + return { time, property, value, interpolation } as PresetKeyframe
;
+}
+
+/**
+ * Helper to create a type-safe preset
+ */
+function preset(
+ id: string,
+ name: string,
+ category: PresetCategory,
+ keyframes: PresetKeyframe[]
+): TypedAnimationPreset {
+ return { id, name, category, keyframes };
+}
/**
* All available animation presets
* Keyframe times are normalized (0-1) and will be scaled by duration when applied
*/
-export const animationPresets: AnimationPreset[] = [
+export const animationPresets: TypedAnimationPreset[] = [
// ============================================
// Fade animations
// ============================================
- {
- id: 'fade-in',
- name: 'Fade In',
- keyframes: [
- {
- time: 0,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'fade-out',
- name: 'Fade Out',
- keyframes: [
- {
- time: 0,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
+ preset('fade-in', 'Fade In', 'enter', [
+ kf(0, 'opacity', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(1, 'opacity', 1, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('fade-out', 'Fade Out', 'exit', [
+ kf(0, 'opacity', 1, { family: 'continuous', strategy: 'ease-in' }),
+ kf(1, 'opacity', 0, { family: 'continuous', strategy: 'linear' })
+ ]),
// ============================================
// Slide animations
// ============================================
- {
- id: 'slide-in-left',
- name: 'Slide In from Left',
- keyframes: [
- {
- time: 0,
- property: 'position.x',
- value: -500,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 1,
- property: 'position.x',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'slide-in-right',
- name: 'Slide In from Right',
- keyframes: [
- {
- time: 0,
- property: 'position.x',
- value: 500,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 1,
- property: 'position.x',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'slide-in-top',
- name: 'Slide In from Top',
- keyframes: [
- {
- time: 0,
- property: 'position.y',
- value: -400,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 1,
- property: 'position.y',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'slide-in-bottom',
- name: 'Slide In from Bottom',
- keyframes: [
- {
- time: 0,
- property: 'position.y',
- value: 400,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 1,
- property: 'position.y',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'slide-out-left',
- name: 'Slide Out to Left',
- keyframes: [
- {
- time: 0,
- property: 'position.x',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-in' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in' }
- },
- {
- time: 1,
- property: 'position.x',
- value: -500,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'slide-out-right',
- name: 'Slide Out to Right',
- keyframes: [
- {
- time: 0,
- property: 'position.x',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-in' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in' }
- },
- {
- time: 1,
- property: 'position.x',
- value: 500,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
+ preset('slide-in-left', 'Slide In from Left', 'enter', [
+ kf(0, 'position.x', -500, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'opacity', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(1, 'position.x', 0, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 1, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('slide-in-right', 'Slide In from Right', 'enter', [
+ kf(0, 'position.x', 500, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'opacity', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(1, 'position.x', 0, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 1, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('slide-in-top', 'Slide In from Top', 'enter', [
+ kf(0, 'position.y', -400, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'opacity', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(1, 'position.y', 0, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 1, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('slide-in-bottom', 'Slide In from Bottom', 'enter', [
+ kf(0, 'position.y', 400, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'opacity', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(1, 'position.y', 0, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 1, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('slide-out-left', 'Slide Out to Left', 'exit', [
+ kf(0, 'position.x', 0, { family: 'continuous', strategy: 'ease-in' }),
+ kf(0, 'opacity', 1, { family: 'continuous', strategy: 'ease-in' }),
+ kf(1, 'position.x', -500, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 0, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('slide-out-right', 'Slide Out to Right', 'exit', [
+ kf(0, 'position.x', 0, { family: 'continuous', strategy: 'ease-in' }),
+ kf(0, 'opacity', 1, { family: 'continuous', strategy: 'ease-in' }),
+ kf(1, 'position.x', 500, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 0, { family: 'continuous', strategy: 'linear' })
+ ]),
// ============================================
// Scale animations
// ============================================
- {
- id: 'scale-in',
- name: 'Scale In',
- keyframes: [
- {
- time: 0,
- property: 'scale.x',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'scale.y',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 1,
- property: 'scale.x',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'scale.y',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'scale-out',
- name: 'Scale Out',
- keyframes: [
- {
- time: 0,
- property: 'scale.x',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in' }
- },
- {
- time: 0,
- property: 'scale.y',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in' }
- },
- {
- time: 1,
- property: 'scale.x',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'scale.y',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'pop',
- name: 'Pop (Scale with Overshoot)',
- keyframes: [
- {
- time: 0,
- property: 'scale.x',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'scale.y',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0.6,
- property: 'scale.x',
- value: 1.15,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0.6,
- property: 'scale.y',
- value: 1.15,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0.6,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 1,
- property: 'scale.x',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 1,
- property: 'scale.y',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- }
- ]
- },
+ preset('scale-in', 'Scale In', 'enter', [
+ kf(0, 'scale.x', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'scale.y', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'opacity', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(1, 'scale.x', 1, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'scale.y', 1, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 1, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('scale-out', 'Scale Out', 'exit', [
+ kf(0, 'scale.x', 1, { family: 'continuous', strategy: 'ease-in' }),
+ kf(0, 'scale.y', 1, { family: 'continuous', strategy: 'ease-in' }),
+ kf(0, 'opacity', 1, { family: 'continuous', strategy: 'ease-in' }),
+ kf(1, 'scale.x', 0, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'scale.y', 0, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 0, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('pop', 'Pop (Scale with Overshoot)', 'enter', [
+ kf(0, 'scale.x', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'scale.y', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'opacity', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0.6, 'scale.x', 1.15, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0.6, 'scale.y', 1.15, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0.6, 'opacity', 1, { family: 'continuous', strategy: 'ease-out' }),
+ kf(1, 'scale.x', 1, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(1, 'scale.y', 1, { family: 'continuous', strategy: 'ease-in-out' })
+ ]),
// ============================================
// Bounce animations
// ============================================
- {
- id: 'bounce',
- name: 'Bounce',
- keyframes: [
- {
- time: 0,
- property: 'scale.y',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 0.25,
- property: 'scale.y',
- value: 1.2,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 0.5,
- property: 'scale.y',
- value: 0.9,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 0.75,
- property: 'scale.y',
- value: 1.05,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 1,
- property: 'scale.y',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'bounce-in',
- name: 'Bounce In',
- keyframes: [
- {
- time: 0,
- property: 'scale.x',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'scale.y',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0.4,
- property: 'scale.x',
- value: 1.2,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0.4,
- property: 'scale.y',
- value: 1.2,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0.4,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 0.6,
- property: 'scale.x',
- value: 0.9,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 0.6,
- property: 'scale.y',
- value: 0.9,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 0.8,
- property: 'scale.x',
- value: 1.05,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 0.8,
- property: 'scale.y',
- value: 1.05,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 1,
- property: 'scale.x',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 1,
- property: 'scale.y',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- }
- ]
- },
+ preset('bounce', 'Bounce', 'generic', [
+ kf(0, 'scale.y', 1, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(0.25, 'scale.y', 1.2, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(0.5, 'scale.y', 0.9, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(0.75, 'scale.y', 1.05, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(1, 'scale.y', 1, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('bounce-in', 'Bounce In', 'enter', [
+ kf(0, 'scale.x', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'scale.y', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'opacity', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0.4, 'scale.x', 1.2, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0.4, 'scale.y', 1.2, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0.4, 'opacity', 1, { family: 'continuous', strategy: 'linear' }),
+ kf(0.6, 'scale.x', 0.9, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(0.6, 'scale.y', 0.9, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(0.8, 'scale.x', 1.05, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(0.8, 'scale.y', 1.05, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(1, 'scale.x', 1, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(1, 'scale.y', 1, { family: 'continuous', strategy: 'ease-in-out' })
+ ]),
// ============================================
// Rotation animations
// ============================================
- {
- id: 'rotate-in',
- name: 'Rotate In',
- keyframes: [
- {
- time: 0,
- property: 'rotation.z',
- value: -Math.PI,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'scale.x',
- value: 0.5,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'scale.y',
- value: 0.5,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 1,
- property: 'rotation.z',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'scale.x',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'scale.y',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'spin',
- name: 'Spin (Full Rotation)',
- keyframes: [
- {
- time: 0,
- property: 'rotation.z',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'rotation.z',
- value: Math.PI * 2,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
+ preset('rotate-in', 'Rotate In', 'enter', [
+ kf(0, 'rotation.z', -Math.PI, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'opacity', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'scale.x', 0.5, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'scale.y', 0.5, { family: 'continuous', strategy: 'ease-out' }),
+ kf(1, 'rotation.z', 0, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 1, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'scale.x', 1, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'scale.y', 1, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('spin', 'Spin (Full Rotation)', 'generic', [
+ kf(0, 'rotation.z', 0, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'rotation.z', Math.PI * 2, { family: 'continuous', strategy: 'linear' })
+ ]),
// ============================================
// Attention/emphasis animations
// ============================================
- {
- id: 'pulse',
- name: 'Pulse',
- keyframes: [
- {
- time: 0,
- property: 'scale.x',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 0,
- property: 'scale.y',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 0.5,
- property: 'scale.x',
- value: 1.1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 0.5,
- property: 'scale.y',
- value: 1.1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 1,
- property: 'scale.x',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 1,
- property: 'scale.y',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- }
- ]
- },
- {
- id: 'shake',
- name: 'Shake',
- keyframes: [
- {
- time: 0,
- property: 'position.x',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 0.1,
- property: 'position.x',
- value: -10,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 0.2,
- property: 'position.x',
- value: 10,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 0.3,
- property: 'position.x',
- value: -10,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 0.4,
- property: 'position.x',
- value: 10,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 0.5,
- property: 'position.x',
- value: -5,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 0.6,
- property: 'position.x',
- value: 5,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 0.7,
- property: 'position.x',
- value: -2,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 0.8,
- property: 'position.x',
- value: 2,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'position.x',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'float',
- name: 'Float (Subtle Up/Down)',
- keyframes: [
- {
- time: 0,
- property: 'position.y',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 0.5,
- property: 'position.y',
- value: -15,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 1,
- property: 'position.y',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- }
- ]
- },
- {
- id: 'glow',
- name: 'Glow (Opacity Pulse)',
- keyframes: [
- {
- time: 0,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 0.5,
- property: 'opacity',
- value: 0.6,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- }
- ]
- },
+ preset('pulse', 'Pulse', 'generic', [
+ kf(0, 'scale.x', 1, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(0, 'scale.y', 1, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(0.5, 'scale.x', 1.1, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(0.5, 'scale.y', 1.1, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(1, 'scale.x', 1, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(1, 'scale.y', 1, { family: 'continuous', strategy: 'ease-in-out' })
+ ]),
+ preset('shake', 'Shake', 'generic', [
+ kf(0, 'position.x', 0, { family: 'continuous', strategy: 'linear' }),
+ kf(0.1, 'position.x', -10, { family: 'continuous', strategy: 'linear' }),
+ kf(0.2, 'position.x', 10, { family: 'continuous', strategy: 'linear' }),
+ kf(0.3, 'position.x', -10, { family: 'continuous', strategy: 'linear' }),
+ kf(0.4, 'position.x', 10, { family: 'continuous', strategy: 'linear' }),
+ kf(0.5, 'position.x', -5, { family: 'continuous', strategy: 'linear' }),
+ kf(0.6, 'position.x', 5, { family: 'continuous', strategy: 'linear' }),
+ kf(0.7, 'position.x', -2, { family: 'continuous', strategy: 'linear' }),
+ kf(0.8, 'position.x', 2, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'position.x', 0, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('float', 'Float (Subtle Up/Down)', 'generic', [
+ kf(0, 'position.y', 0, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(0.5, 'position.y', -15, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(1, 'position.y', 0, { family: 'continuous', strategy: 'ease-in-out' })
+ ]),
+ preset('glow', 'Glow (Opacity Pulse)', 'generic', [
+ kf(0, 'opacity', 1, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(0.5, 'opacity', 0.6, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(1, 'opacity', 1, { family: 'continuous', strategy: 'ease-in-out' })
+ ]),
// ============================================
// Zoom animations
// ============================================
- {
- id: 'zoom-in',
- name: 'Zoom In (from far)',
- keyframes: [
- {
- time: 0,
- property: 'scale.x',
- value: 0.3,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'scale.y',
- value: 0.3,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 1,
- property: 'scale.x',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'scale.y',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'zoom-out',
- name: 'Zoom Out (to far)',
- keyframes: [
- {
- time: 0,
- property: 'scale.x',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in' }
- },
- {
- time: 0,
- property: 'scale.y',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'ease-in' }
- },
- {
- time: 1,
- property: 'scale.x',
- value: 0.3,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'scale.y',
- value: 0.3,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
+ preset('zoom-in', 'Zoom In (from far)', 'enter', [
+ kf(0, 'scale.x', 0.3, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'scale.y', 0.3, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'opacity', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(1, 'scale.x', 1, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'scale.y', 1, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 1, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('zoom-out', 'Zoom Out (to far)', 'exit', [
+ kf(0, 'scale.x', 1, { family: 'continuous', strategy: 'ease-in' }),
+ kf(0, 'scale.y', 1, { family: 'continuous', strategy: 'ease-in' }),
+ kf(0, 'opacity', 1, { family: 'continuous', strategy: 'ease-in' }),
+ kf(1, 'scale.x', 0.3, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'scale.y', 0.3, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 0, { family: 'continuous', strategy: 'linear' })
+ ]),
// ============================================
// Special effects
// ============================================
- {
- id: 'flip-in-x',
- name: 'Flip In (Horizontal)',
- keyframes: [
- {
- time: 0,
- property: 'rotation.y',
- value: Math.PI / 2,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 1,
- property: 'rotation.y',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'flip-in-y',
- name: 'Flip In (Vertical)',
- keyframes: [
- {
- time: 0,
- property: 'rotation.x',
- value: Math.PI / 2,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 0,
- property: 'opacity',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-out' }
- },
- {
- time: 1,
- property: 'rotation.x',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'linear' }
- },
- {
- time: 1,
- property: 'opacity',
- value: 1,
- interpolation: { family: 'continuous', strategy: 'linear' }
- }
- ]
- },
- {
- id: 'swing',
- name: 'Swing',
- keyframes: [
- {
- time: 0,
- property: 'rotation.z',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- },
- {
- time: 0.2,
- property: 'rotation.z',
- value: 0.26,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- }, // ~15deg
- {
- time: 0.4,
- property: 'rotation.z',
- value: -0.17,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- }, // ~-10deg
- {
- time: 0.6,
- property: 'rotation.z',
- value: 0.09,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- }, // ~5deg
- {
- time: 0.8,
- property: 'rotation.z',
- value: -0.05,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- }, // ~-3deg
- {
- time: 1,
- property: 'rotation.z',
- value: 0,
- interpolation: { family: 'continuous', strategy: 'ease-in-out' }
- }
- ]
- }
+ preset('flip-in-x', 'Flip In (Horizontal)', 'enter', [
+ kf(0, 'rotation.y', Math.PI / 2, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'opacity', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(1, 'rotation.y', 0, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 1, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('flip-in-y', 'Flip In (Vertical)', 'enter', [
+ kf(0, 'rotation.x', Math.PI / 2, { family: 'continuous', strategy: 'ease-out' }),
+ kf(0, 'opacity', 0, { family: 'continuous', strategy: 'ease-out' }),
+ kf(1, 'rotation.x', 0, { family: 'continuous', strategy: 'linear' }),
+ kf(1, 'opacity', 1, { family: 'continuous', strategy: 'linear' })
+ ]),
+ preset('swing', 'Swing', 'generic', [
+ kf(0, 'rotation.z', 0, { family: 'continuous', strategy: 'ease-in-out' }),
+ kf(0.2, 'rotation.z', 0.26, { family: 'continuous', strategy: 'ease-in-out' }), // ~15deg
+ kf(0.4, 'rotation.z', -0.17, { family: 'continuous', strategy: 'ease-in-out' }), // ~-10deg
+ kf(0.6, 'rotation.z', 0.09, { family: 'continuous', strategy: 'ease-in-out' }), // ~5deg
+ kf(0.8, 'rotation.z', -0.05, { family: 'continuous', strategy: 'ease-in-out' }), // ~-3deg
+ kf(1, 'rotation.z', 0, { family: 'continuous', strategy: 'ease-in-out' })
+ ])
];
/**
@@ -936,7 +311,7 @@ export type AnimationPresetId = z.infer