export interface AnimationConfig { duration?: number; delay?: number; easing?: string; fillMode?: "forwards" | "backwards" | "both" | "none"; iterationCount?: number | "infinite"; } export const animations = { // 페이드 애니메이션 fadeIn: (config: AnimationConfig = {}) => ({ animation: `fadeIn ${config.duration || 300}ms ${config.easing || "ease-in-out"} ${config.delay || 0}ms ${config.fillMode || "forwards"}`, "@keyframes fadeIn": { "0%": { opacity: 0 }, "100%": { opacity: 1 }, }, }), fadeOut: (config: AnimationConfig = {}) => ({ animation: `fadeOut ${config.duration || 300}ms ${config.easing || "ease-in-out"} ${config.delay || 0}ms ${config.fillMode || "forwards"}`, "@keyframes fadeOut": { "0%": { opacity: 1 }, "100%": { opacity: 0 }, }, }), // 슬라이드 애니메이션 slideInFromLeft: (config: AnimationConfig = {}) => ({ animation: `slideInFromLeft ${config.duration || 400}ms ${config.easing || "ease-out"} ${config.delay || 0}ms ${config.fillMode || "forwards"}`, "@keyframes slideInFromLeft": { "0%": { transform: "translateX(-100%)", opacity: 0 }, "100%": { transform: "translateX(0)", opacity: 1 }, }, }), slideInFromRight: (config: AnimationConfig = {}) => ({ animation: `slideInFromRight ${config.duration || 400}ms ${config.easing || "ease-out"} ${config.delay || 0}ms ${config.fillMode || "forwards"}`, "@keyframes slideInFromRight": { "0%": { transform: "translateX(100%)", opacity: 0 }, "100%": { transform: "translateX(0)", opacity: 1 }, }, }), slideInFromTop: (config: AnimationConfig = {}) => ({ animation: `slideInFromTop ${config.duration || 400}ms ${config.easing || "ease-out"} ${config.delay || 0}ms ${config.fillMode || "forwards"}`, "@keyframes slideInFromTop": { "0%": { transform: "translateY(-100%)", opacity: 0 }, "100%": { transform: "translateY(0)", opacity: 1 }, }, }), slideInFromBottom: (config: AnimationConfig = {}) => ({ animation: `slideInFromBottom ${config.duration || 400}ms ${config.easing || "ease-out"} ${config.delay || 0}ms ${config.fillMode || "forwards"}`, "@keyframes slideInFromBottom": { "0%": { transform: "translateY(100%)", opacity: 0 }, "100%": { transform: "translateY(0)", opacity: 1 }, }, }), // 스케일 애니메이션 scaleIn: (config: AnimationConfig = {}) => ({ animation: `scaleIn ${config.duration || 300}ms ${config.easing || "ease-out"} ${config.delay || 0}ms ${config.fillMode || "forwards"}`, "@keyframes scaleIn": { "0%": { transform: "scale(0)", opacity: 0 }, "100%": { transform: "scale(1)", opacity: 1 }, }, }), scaleOut: (config: AnimationConfig = {}) => ({ animation: `scaleOut ${config.duration || 300}ms ${config.easing || "ease-in"} ${config.delay || 0}ms ${config.fillMode || "forwards"}`, "@keyframes scaleOut": { "0%": { transform: "scale(1)", opacity: 1 }, "100%": { transform: "scale(0)", opacity: 0 }, }, }), // 바운스 애니메이션 bounce: (config: AnimationConfig = {}) => ({ animation: `bounce ${config.duration || 600}ms ${config.easing || "ease-in-out"} ${config.delay || 0}ms ${config.iterationCount || 1}`, "@keyframes bounce": { "0%, 20%, 53%, 80%, 100%": { transform: "translate3d(0,0,0)" }, "40%, 43%": { transform: "translate3d(0,-30px,0)" }, "70%": { transform: "translate3d(0,-15px,0)" }, "90%": { transform: "translate3d(0,-4px,0)" }, }, }), // 회전 애니메이션 rotate: (config: AnimationConfig = {}) => ({ animation: `rotate ${config.duration || 1000}ms ${config.easing || "linear"} ${config.delay || 0}ms ${config.iterationCount || "infinite"}`, "@keyframes rotate": { "0%": { transform: "rotate(0deg)" }, "100%": { transform: "rotate(360deg)" }, }, }), // 펄스 애니메이션 pulse: (config: AnimationConfig = {}) => ({ animation: `pulse ${config.duration || 1000}ms ${config.easing || "ease-in-out"} ${config.delay || 0}ms ${config.iterationCount || "infinite"}`, "@keyframes pulse": { "0%": { transform: "scale(1)", opacity: 1 }, "50%": { transform: "scale(1.05)", opacity: 0.8 }, "100%": { transform: "scale(1)", opacity: 1 }, }, }), // 타이핑 애니메이션 typewriter: (config: AnimationConfig = {}) => ({ animation: `typewriter ${config.duration || 2000}ms ${config.easing || "steps(40, end)"} ${config.delay || 0}ms ${config.fillMode || "forwards"}`, "@keyframes typewriter": { "0%": { width: "0" }, "100%": { width: "100%" }, }, }), // 글로우 애니메이션 glow: (config: AnimationConfig = {}) => ({ animation: `glow ${config.duration || 2000}ms ${config.easing || "ease-in-out"} ${config.delay || 0}ms ${config.iterationCount || "infinite"}`, "@keyframes glow": { "0%, 100%": { boxShadow: "0 0 5px rgba(59, 130, 246, 0.5)" }, "50%": { boxShadow: "0 0 20px rgba(59, 130, 246, 0.8), 0 0 30px rgba(59, 130, 246, 0.6)" }, }, }), // 웨이브 애니메이션 wave: (config: AnimationConfig = {}) => ({ animation: `wave ${config.duration || 1000}ms ${config.easing || "ease-in-out"} ${config.delay || 0}ms ${config.iterationCount || "infinite"}`, "@keyframes wave": { "0%, 100%": { transform: "rotate(0deg)" }, "25%": { transform: "rotate(20deg)" }, "75%": { transform: "rotate(-10deg)" }, }, }), }; // 애니메이션 조합 export const animationCombos = { // 페이지 전환 pageTransition: (direction: "left" | "right" | "up" | "down" = "right") => { const slideAnimation = direction === "left" ? animations.slideInFromLeft : direction === "right" ? animations.slideInFromRight : direction === "up" ? animations.slideInFromTop : animations.slideInFromBottom; return { ...slideAnimation({ duration: 500, easing: "cubic-bezier(0.4, 0, 0.2, 1)" }), ...animations.fadeIn({ duration: 500, delay: 100 }), }; }, // 모달 등장 modalEnter: () => ({ ...animations.scaleIn({ duration: 300, easing: "cubic-bezier(0.34, 1.56, 0.64, 1)" }), ...animations.fadeIn({ duration: 300 }), }), // 모달 퇴장 modalExit: () => ({ ...animations.scaleOut({ duration: 200, easing: "cubic-bezier(0.4, 0, 1, 1)" }), ...animations.fadeOut({ duration: 200 }), }), // 버튼 클릭 buttonClick: () => ({ ...animations.scaleIn({ duration: 150, easing: "ease-out" }), }), // 성공 알림 successNotification: () => ({ ...animations.slideInFromRight({ duration: 400, easing: "ease-out" }), ...animations.bounce({ duration: 600, delay: 200 }), }), // 로딩 스피너 loadingSpinner: () => ({ ...animations.rotate({ duration: 1000, iterationCount: "infinite" }), }), // 호버 효과 hoverLift: () => ({ transition: "transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out", "&:hover": { transform: "translateY(-2px)", boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)", }, }), // 타이핑 효과 typingText: (text: string, speed: number = 50) => ({ ...animations.typewriter({ duration: text.length * speed }), overflow: "hidden", whiteSpace: "nowrap", borderRight: "2px solid", borderRightColor: "currentColor", }), };