feat: add animation CSS classes, keyframes, and reduced-motion support
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user