Add global animation system and entrance effects to UI

This commit is contained in:
2025-08-01 14:21:10 -04:00
parent eaf185d89e
commit 93ffdf3c86
18 changed files with 1363 additions and 124 deletions
+637
View File
@@ -440,3 +440,640 @@ li[data-sonner-toast] button:hover,
);
background-size: 40px 40px;
}
/* ========================================
BEENVOICE ANIMATION SYSTEM
======================================== */
/* CSS Custom Properties for Animation Timing */
:root {
--animation-speed-fast: 0.15s;
--animation-speed-normal: 0.3s;
--animation-speed-slow: 0.5s;
--animation-easing: ease-out;
--animation-easing-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* Accessibility: Respect prefers-reduced-motion */
@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;
}
}
/* ========================================
BASE KEYFRAMES
======================================== */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideInBottom {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes expandDown {
from {
opacity: 0;
max-height: 0;
transform: scaleY(0);
transform-origin: top;
}
to {
opacity: 1;
max-height: 200px;
transform: scaleY(1);
}
}
@keyframes shrinkUp {
from {
opacity: 1;
max-height: 200px;
transform: scaleY(1);
}
to {
opacity: 0;
max-height: 0;
transform: scaleY(0);
transform-origin: top;
}
}
@keyframes bounce {
0%,
20%,
53%,
80%,
100% {
transform: translate3d(0, 0, 0);
}
40%,
43% {
transform: translate3d(0, -15px, 0);
}
70% {
transform: translate3d(0, -7px, 0);
}
90% {
transform: translate3d(0, -2px, 0);
}
}
@keyframes pulse {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.05);
opacity: 0.8;
}
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
@keyframes countUp {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* ========================================
ANIMATION UTILITY CLASSES
======================================== */
/* Base Animations */
.animate-fade-in {
animation: fadeIn var(--animation-speed-slow) var(--animation-easing);
}
.animate-fade-in-up {
animation: fadeInUp var(--animation-speed-slow) var(--animation-easing);
}
.animate-fade-in-down {
animation: fadeInDown var(--animation-speed-slow) var(--animation-easing);
}
.animate-slide-in-left {
animation: slideInLeft var(--animation-speed-slow) var(--animation-easing);
}
.animate-slide-in-right {
animation: slideInRight var(--animation-speed-slow) var(--animation-easing);
}
.animate-slide-in-bottom {
animation: slideInBottom var(--animation-speed-slow) var(--animation-easing);
}
.animate-scale-in {
animation: scaleIn var(--animation-speed-normal) var(--animation-easing);
}
.animate-bounce {
animation: bounce 1s var(--animation-easing);
}
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
.animate-count-up {
animation: countUp 0.8s var(--animation-easing);
}
/* Stagger Animation Delays */
.animate-delay-75 {
animation-delay: 75ms;
}
.animate-delay-100 {
animation-delay: 100ms;
}
.animate-delay-150 {
animation-delay: 150ms;
}
.animate-delay-200 {
animation-delay: 200ms;
}
.animate-delay-300 {
animation-delay: 300ms;
}
.animate-delay-500 {
animation-delay: 500ms;
}
.animate-delay-700 {
animation-delay: 700ms;
}
.animate-delay-1000 {
animation-delay: 1000ms;
}
/* ========================================
HOVER STATE ANIMATIONS
======================================== */
.hover-lift {
transition: transform var(--animation-speed-fast) var(--animation-easing);
}
.hover-lift:hover {
transform: translateY(-2px);
}
.hover-scale {
transition: transform var(--animation-speed-fast) var(--animation-easing);
}
.hover-scale:hover {
transform: scale(1.02);
}
.hover-glow {
transition: box-shadow var(--animation-speed-normal) var(--animation-easing);
}
.hover-glow:hover {
box-shadow: 0 0 20px hsl(var(--primary) / 0.3);
}
.hover-slide-right {
transition: transform var(--animation-speed-fast) var(--animation-easing);
}
.hover-slide-right:hover {
transform: translateX(4px);
}
/* ========================================
LOADING SKELETON ANIMATIONS
======================================== */
.skeleton {
background: linear-gradient(
90deg,
hsl(var(--muted)) 0%,
hsl(var(--muted) / 0.5) 50%,
hsl(var(--muted)) 100%
);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
}
.skeleton-text {
height: 1rem;
border-radius: 0.25rem;
}
.skeleton-text-lg {
height: 1.5rem;
border-radius: 0.25rem;
}
.skeleton-text-xl {
height: 2rem;
border-radius: 0.25rem;
}
.skeleton-button {
height: 2.5rem;
border-radius: 0.375rem;
}
.skeleton-avatar {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
}
.skeleton-card {
height: 8rem;
border-radius: 0.5rem;
}
/* ========================================
PAGE ENTRANCE ANIMATIONS
======================================== */
.page-enter {
animation: fadeInUp 0.6s var(--animation-easing);
}
.page-enter-stagger > * {
animation: fadeInUp var(--animation-speed-slow) var(--animation-easing)
forwards;
opacity: 0;
}
.page-enter-stagger > *:nth-child(1) {
animation-delay: 0ms;
}
.page-enter-stagger > *:nth-child(2) {
animation-delay: 100ms;
}
.page-enter-stagger > *:nth-child(3) {
animation-delay: 200ms;
}
.page-enter-stagger > *:nth-child(4) {
animation-delay: 300ms;
}
.page-enter-stagger > *:nth-child(5) {
animation-delay: 400ms;
}
.page-enter-stagger > *:nth-child(6) {
animation-delay: 500ms;
}
/* ========================================
COMPONENT-SPECIFIC ANIMATIONS
======================================== */
/* Stats Cards */
.stats-card {
animation: fadeInUp var(--animation-speed-slow) var(--animation-easing)
forwards;
opacity: 0;
}
.stats-card:nth-child(1) {
animation-delay: 0ms;
}
.stats-card:nth-child(2) {
animation-delay: 100ms;
}
.stats-card:nth-child(3) {
animation-delay: 200ms;
}
.stats-card:nth-child(4) {
animation-delay: 300ms;
}
/* Invoice Items */
.invoice-item {
animation: fadeInUp var(--animation-speed-normal) var(--animation-easing)
forwards;
opacity: 0;
}
.invoice-item:nth-child(1) {
animation-delay: 0ms;
}
.invoice-item:nth-child(2) {
animation-delay: 100ms;
}
.invoice-item:nth-child(3) {
animation-delay: 200ms;
}
.invoice-item:nth-child(4) {
animation-delay: 300ms;
}
.invoice-item:nth-child(5) {
animation-delay: 400ms;
}
/* Recent Activity Items */
.recent-activity-item {
animation: slideInLeft var(--animation-speed-normal) var(--animation-easing)
forwards;
opacity: 0;
}
.recent-activity-item:nth-child(1) {
animation-delay: 0ms;
}
.recent-activity-item:nth-child(2) {
animation-delay: 75ms;
}
.recent-activity-item:nth-child(3) {
animation-delay: 150ms;
}
.recent-activity-item:nth-child(4) {
animation-delay: 225ms;
}
.recent-activity-item:nth-child(5) {
animation-delay: 300ms;
}
/* Form Animations */
.form-section {
animation: fadeInUp var(--animation-speed-slow) var(--animation-easing)
forwards;
opacity: 0;
}
.form-input:focus {
box-shadow: 0 0 0 3px hsl(var(--primary) / 0.2);
transition: box-shadow var(--animation-speed-fast) var(--animation-easing);
}
/* Line Item Animations */
.line-item-enter {
animation: expandDown var(--animation-speed-normal) var(--animation-easing);
}
.line-item-exit {
animation: shrinkUp var(--animation-speed-fast) ease-in forwards;
}
/* Status Badge Pulse for Pending States */
.status-pending {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* Button Loading States */
.button-loading {
position: relative;
color: transparent;
}
.button-loading::after {
content: "";
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 1rem;
height: 1rem;
border: 2px solid currentColor;
border-radius: 50%;
border-top-color: transparent;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: translate(-50%, -50%) rotate(360deg);
}
}
/* ========================================
SUCCESS/ERROR STATE ANIMATIONS
======================================== */
.success-state {
animation: successPulse 0.6s var(--animation-easing);
}
@keyframes successPulse {
0% {
transform: scale(1);
background-color: hsl(var(--success) / 0.1);
}
50% {
transform: scale(1.02);
background-color: hsl(var(--success) / 0.2);
}
100% {
transform: scale(1);
background-color: hsl(var(--success) / 0.1);
}
}
.error-state {
animation: errorShake 0.5s var(--animation-easing);
}
@keyframes errorShake {
0%,
100% {
transform: translateX(0);
}
10%,
30%,
50%,
70%,
90% {
transform: translateX(-2px);
}
20%,
40%,
60%,
80% {
transform: translateX(2px);
}
}
/* ========================================
TABLE AND LIST ANIMATIONS
======================================== */
.table-row {
transition: all var(--animation-speed-fast) var(--animation-easing);
}
.table-row:hover {
background-color: hsl(var(--muted) / 0.5);
transform: translateX(2px);
}
/* ========================================
MODAL AND DIALOG ANIMATIONS
======================================== */
.modal-enter {
animation: modalSlideIn var(--animation-speed-normal) var(--animation-easing);
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(-20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.modal-backdrop {
animation: backdropFadeIn var(--animation-speed-normal)
var(--animation-easing);
}
@keyframes backdropFadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* ========================================
UTILITY CLASSES FOR COMMON PATTERNS
======================================== */
/* Hidden initially for entrance animations */
.animate-on-load {
opacity: 0;
animation: fadeInUp var(--animation-speed-slow) var(--animation-easing)
forwards;
}
/* Stagger children for list entrances */
.stagger-children > * {
animation: fadeInUp var(--animation-speed-normal) var(--animation-easing)
forwards;
opacity: 0;
}
.stagger-children > *:nth-child(1) {
animation-delay: 0ms;
}
.stagger-children > *:nth-child(2) {
animation-delay: 100ms;
}
.stagger-children > *:nth-child(3) {
animation-delay: 200ms;
}
.stagger-children > *:nth-child(4) {
animation-delay: 300ms;
}
.stagger-children > *:nth-child(5) {
animation-delay: 400ms;
}
.stagger-children > *:nth-child(6) {
animation-delay: 500ms;
}
.stagger-children > *:nth-child(7) {
animation-delay: 600ms;
}
.stagger-children > *:nth-child(8) {
animation-delay: 700ms;
}
/* Performance optimizations */
.will-animate {
will-change: transform, opacity;
}
.will-animate.animation-done {
will-change: auto;
}