feat: add animation CSS classes, keyframes, and reduced-motion support

This commit is contained in:
Your Name
2026-02-18 11:22:32 +02:00
parent 5630751adc
commit bd0dbaf91d

View File

@@ -196,11 +196,11 @@
@keyframes toast-enter { @keyframes toast-enter {
from { from {
opacity: 0; opacity: 0;
transform: translateY(-20px); transform: translateY(-20px) translateX(10px);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0) translateX(0);
} }
} }
@@ -211,9 +211,11 @@
@keyframes toast-exit { @keyframes toast-exit {
from { from {
opacity: 1; opacity: 1;
transform: translateY(0);
} }
to { to {
opacity: 0; opacity: 0;
transform: translateY(-10px);
} }
} }
@@ -245,3 +247,206 @@
color: var(--color-accent-text); color: var(--color-accent-text);
text-decoration: underline; text-decoration: underline;
} }
/* ============================================
MOTION SYSTEM — Transitions & Animations
============================================ */
/* Page transitions */
.page-enter-active {
transition: opacity 250ms cubic-bezier(0.22, 1, 0.36, 1),
transform 250ms cubic-bezier(0.22, 1, 0.36, 1);
}
.page-leave-active {
transition: opacity 150ms ease-out,
transform 150ms ease-out;
}
.page-enter-from {
opacity: 0;
transform: translateY(8px);
}
.page-leave-to {
opacity: 0;
transform: translateY(-8px);
}
/* List item transitions */
.list-enter-active {
transition: opacity 250ms cubic-bezier(0.22, 1, 0.36, 1),
transform 250ms cubic-bezier(0.22, 1, 0.36, 1);
}
.list-leave-active {
transition: opacity 150ms ease-in,
transform 150ms ease-in;
position: absolute;
}
.list-move {
transition: transform 300ms cubic-bezier(0.22, 1, 0.36, 1);
}
.list-enter-from {
opacity: 0;
transform: translateY(12px);
}
.list-leave-to {
opacity: 0;
transform: translateX(-20px);
}
/* Tag chip transitions */
.chip-enter-active {
transition: opacity 200ms cubic-bezier(0.34, 1.56, 0.64, 1),
transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.chip-leave-active {
transition: opacity 100ms ease-in,
transform 100ms ease-in;
}
.chip-enter-from {
opacity: 0;
transform: scale(0.8);
}
.chip-leave-to {
opacity: 0;
transform: scale(0.8);
}
/* Modal transitions */
.modal-enter-active {
transition: opacity 200ms ease-out;
}
.modal-leave-active {
transition: opacity 150ms ease-in;
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
}
/* Dropdown transitions */
.dropdown-enter-active {
transition: opacity 150ms cubic-bezier(0.22, 1, 0.36, 1),
transform 150ms cubic-bezier(0.22, 1, 0.36, 1);
}
.dropdown-leave-active {
transition: opacity 100ms ease-in,
transform 100ms ease-in;
}
.dropdown-enter-from {
opacity: 0;
transform: translateY(-4px) scale(0.95);
}
.dropdown-leave-to {
opacity: 0;
transform: translateY(-4px);
}
/* Content fade-in */
.fade-enter-active {
transition: opacity 250ms cubic-bezier(0.22, 1, 0.36, 1),
transform 250ms cubic-bezier(0.22, 1, 0.36, 1);
}
.fade-leave-active {
transition: opacity 150ms ease-out;
}
.fade-enter-from {
opacity: 0;
transform: translateY(4px);
}
.fade-leave-to {
opacity: 0;
}
/* Skeleton shimmer */
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(90deg, var(--color-bg-elevated) 25%, var(--color-bg-surface) 50%, var(--color-bg-elevated) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
}
/* Empty state floating icon */
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
/* Button interactive feedback */
.btn-primary {
transition: transform 150ms cubic-bezier(0.22, 1, 0.36, 1),
box-shadow 150ms ease;
}
.btn-primary:hover {
transform: scale(1.02);
}
.btn-primary:active {
transform: scale(0.97);
transition-duration: 50ms;
}
.btn-icon:active {
transform: scale(0.85);
transition: transform 100ms cubic-bezier(0.22, 1, 0.36, 1);
}
.btn-icon-delete:active {
transform: scale(0.85) rotate(-10deg);
transition: transform 100ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* Card hover lift */
.card-hover {
transition: transform 200ms cubic-bezier(0.22, 1, 0.36, 1),
box-shadow 200ms ease;
}
.card-hover:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.card-hover:active {
transform: translateY(0);
}
/* Progress bar animate-in */
.progress-bar {
transition: width 600ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* Timer start pulse */
@keyframes timer-pulse {
0% { transform: scale(1); }
50% { transform: scale(1.03); }
100% { transform: scale(1); }
}
.animate-timer-pulse {
animation: timer-pulse 300ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* Timer stop glow */
@keyframes timer-glow {
0% { text-shadow: 0 0 0 transparent; }
30% { text-shadow: 0 0 12px var(--color-accent-muted); }
100% { text-shadow: 0 0 0 transparent; }
}
.animate-timer-glow {
animation: timer-glow 600ms ease-out;
}
/* Respect reduced motion preference */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}