diff --git a/components/animate/AnimationEditor.tsx b/components/animate/AnimationEditor.tsx index 74be420..86bf710 100644 --- a/components/animate/AnimationEditor.tsx +++ b/components/animate/AnimationEditor.tsx @@ -98,7 +98,7 @@ export function AnimationEditor() { {/* Row 4: Preset Library */} - + ); } diff --git a/components/animate/PresetLibrary.tsx b/components/animate/PresetLibrary.tsx index 046ff0f..bcba38d 100644 --- a/components/animate/PresetLibrary.tsx +++ b/components/animate/PresetLibrary.tsx @@ -3,39 +3,29 @@ import { useEffect, useRef } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { cn } from '@/lib/utils/cn'; import { PRESETS, PRESET_CATEGORIES } from '@/lib/animate/presets'; -import { buildCSS } from '@/lib/animate/cssBuilder'; +import { buildKeyframesOnly } from '@/lib/animate/cssBuilder'; import type { AnimationConfig, AnimationPreset } from '@/types/animate'; interface Props { - currentName: string; onSelect: (config: AnimationConfig) => void; } -function PresetCard({ preset, isActive, onSelect }: { +function PresetCard({ preset, onSelect }: { preset: AnimationPreset; - isActive: boolean; onSelect: () => void; }) { const styleRef = useRef(null); - const key = `preset-${preset.id}`; + const animName = `preview-${preset.id}`; - // Each card gets its own @keyframes injected with a unique name to avoid conflicts + // Inject only the @keyframes block under a unique name — no .animated class rule useEffect(() => { - const renamedConfig = { - ...preset.config, - name: key, - keyframes: preset.config.keyframes, - }; + const renamedConfig = { ...preset.config, name: animName }; if (!styleRef.current) { styleRef.current = document.createElement('style'); document.head.appendChild(styleRef.current); } - styleRef.current.textContent = buildCSS(renamedConfig).replace( - /animation: \S+/, - `animation: ${key}` - ); + styleRef.current.textContent = buildKeyframesOnly(renamedConfig); return () => { styleRef.current?.remove(); styleRef.current = null; @@ -43,32 +33,36 @@ function PresetCard({ preset, isActive, onSelect }: { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // Cap thumbnail duration so fast presets loop nicely; slow ones cap at 1.2s + const thumbDuration = Math.min(preset.config.duration, 1200); + return ( ); } -export function PresetLibrary({ currentName, onSelect }: Props) { +export function PresetLibrary({ onSelect }: Props) { return ( @@ -90,7 +84,6 @@ export function PresetLibrary({ currentName, onSelect }: Props) { onSelect(preset.config)} /> ))} diff --git a/lib/animate/cssBuilder.ts b/lib/animate/cssBuilder.ts index cf7da5d..74d1321 100644 --- a/lib/animate/cssBuilder.ts +++ b/lib/animate/cssBuilder.ts @@ -61,6 +61,24 @@ export function buildAnimationShorthand(config: AnimationConfig): string { return `${config.name} ${config.duration}ms ${config.easing}${delay} ${iter} ${config.direction} ${config.fillMode}`; } +export function buildKeyframesOnly(config: AnimationConfig): string { + const sorted = [...config.keyframes].sort((a, b) => a.offset - b.offset); + let out = `@keyframes ${config.name} {\n`; + for (const kf of sorted) { + const lines = buildProperties(kf.properties); + if (lines.length === 0) { + out += ` ${kf.offset}% { }\n`; + } else { + out += ` ${kf.offset}% {\n`; + for (const line of lines) out += ` ${line};\n`; + if (kf.easing) out += ` animation-timing-function: ${kf.easing};\n`; + out += ` }\n`; + } + } + out += `}\n`; + return out; +} + export function buildCSS(config: AnimationConfig): string { const sorted = [...config.keyframes].sort((a, b) => a.offset - b.offset);