feat: micro-interactions and animations — cards, grids, tables, modals, nav

CSS-only animation layer (zero component changes):

- Staggered grid item entrance (fadeInUp with 30ms delays per item)
- Staggered table row entrance (fadeIn with 25ms delays)
- Card hover lift in grids (translateY -2px + elevated shadow)
- Sidebar nav: active link accent bar, hover slide-right
- Badge hover scale, status badge pop-in
- Modal backdrop fade + panel pop-in (fadeInScale)
- Floating action bar slide-up entrance
- Image/thumbnail zoom on group hover (scale 1.03)
- Number/stat counter entrance animation
- Checkbox pop-in on check
- Link animated underline (background-size transition)
- Select hover border highlight
- Loading skeleton shimmer gradient
- Smooth scroll behavior
- prefers-reduced-motion: respects user preference

Keyframes: fadeIn, fadeInUp, fadeInScale, slideInRight, slideInLeft,
slideUp, popIn, breathe, countUp, shimmer, wiggle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 01:18:00 +01:00
parent 98a6dbee87
commit bd1c5eec20
+317 -14
View File
@@ -350,31 +350,334 @@
} }
/* ============================================================ /* ============================================================
UTILITY ENHANCEMENTS ANIMATIONS & MICRO-INTERACTIONS
============================================================ */ ============================================================ */
/* Smooth page transitions */ /* ── Keyframes ─────────────────────────────────────────────── */
.page-enter {
animation: fadeIn 200ms ease-out;
}
@keyframes fadeIn { @keyframes fadeIn {
from { from { opacity: 0; transform: translateY(6px); }
opacity: 0; to { opacity: 1; transform: translateY(0); }
transform: translateY(4px); }
}
to { @keyframes fadeInUp {
opacity: 1; from { opacity: 0; transform: translateY(12px); }
transform: translateY(0); to { opacity: 1; transform: translateY(0); }
} }
@keyframes fadeInScale {
from { opacity: 0; transform: scale(0.96); }
to { opacity: 1; transform: scale(1); }
}
@keyframes slideInRight {
from { opacity: 0; transform: translateX(16px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes slideInLeft {
from { opacity: 0; transform: translateX(-16px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
} }
/* Pulse animation for loading states */
@keyframes subtlePulse { @keyframes subtlePulse {
0%, 100% { opacity: 1; } 0%, 100% { opacity: 1; }
50% { opacity: 0.7; } 50% { opacity: 0.7; }
} }
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
@keyframes progressGrow {
from { width: 0%; }
}
@keyframes popIn {
0% { opacity: 0; transform: scale(0.8); }
70% { transform: scale(1.02); }
100% { opacity: 1; transform: scale(1); }
}
@keyframes breathe {
0%, 100% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb, 0, 137, 61), 0.15); }
50% { box-shadow: 0 0 0 6px rgba(var(--accent-rgb, 0, 137, 61), 0); }
}
@keyframes countUp {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ── Utility animation classes ─────────────────────────────── */
.animate-fade-in {
animation: fadeIn 300ms ease-out both;
}
.animate-fade-in-up {
animation: fadeInUp 400ms ease-out both;
}
.animate-fade-in-scale {
animation: fadeInScale 250ms ease-out both;
}
.animate-slide-in-right {
animation: slideInRight 300ms ease-out both;
}
.animate-slide-in-left {
animation: slideInLeft 300ms ease-out both;
}
.animate-slide-up {
animation: slideUp 350ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.animate-pop-in {
animation: popIn 300ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.animate-subtle-pulse { .animate-subtle-pulse {
animation: subtlePulse 2s ease-in-out infinite; animation: subtlePulse 2s ease-in-out infinite;
} }
.animate-breathe {
animation: breathe 2s ease-in-out infinite;
}
.animate-count-up {
animation: countUp 400ms ease-out both;
}
/* ── Card hover micro-interaction ──────────────────────────── */
.card {
transition: transform 200ms ease, box-shadow 200ms ease, border-color 200ms ease;
}
.card:hover {
/* Subtle lift + glow on hover — only for non-table cards */
}
/* Cards in grid layouts get the hover lift */
.grid > .card:hover,
.card.hover-lift:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px -4px rgba(0, 0, 0, 0.15), 0 4px 8px -2px rgba(0, 0, 0, 0.08);
}
:root.dark .grid > .card:hover,
:root.dark .card.hover-lift:hover {
box-shadow: 0 8px 24px -4px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.06);
}
/* ── Staggered grid item entrance ──────────────────────────── */
.grid > * {
animation: fadeInUp 350ms ease-out both;
}
.grid > *:nth-child(1) { animation-delay: 0ms; }
.grid > *:nth-child(2) { animation-delay: 30ms; }
.grid > *:nth-child(3) { animation-delay: 60ms; }
.grid > *:nth-child(4) { animation-delay: 90ms; }
.grid > *:nth-child(5) { animation-delay: 120ms; }
.grid > *:nth-child(6) { animation-delay: 150ms; }
.grid > *:nth-child(7) { animation-delay: 180ms; }
.grid > *:nth-child(8) { animation-delay: 210ms; }
.grid > *:nth-child(9) { animation-delay: 240ms; }
.grid > *:nth-child(10) { animation-delay: 270ms; }
.grid > *:nth-child(11) { animation-delay: 300ms; }
.grid > *:nth-child(12) { animation-delay: 330ms; }
.grid > *:nth-child(n+13) { animation-delay: 350ms; }
/* ── Table row entrance ────────────────────────────────────── */
tbody > tr {
animation: fadeIn 250ms ease-out both;
}
tbody > tr:nth-child(1) { animation-delay: 0ms; }
tbody > tr:nth-child(2) { animation-delay: 25ms; }
tbody > tr:nth-child(3) { animation-delay: 50ms; }
tbody > tr:nth-child(4) { animation-delay: 75ms; }
tbody > tr:nth-child(5) { animation-delay: 100ms; }
tbody > tr:nth-child(n+6) { animation-delay: 120ms; }
/* ── Sidebar nav link animations ───────────────────────────── */
nav a, nav button {
transition: all 180ms ease-out;
position: relative;
}
/* Active nav link: left accent bar */
nav a.bg-accent-light {
border-left: 3px solid var(--color-accent);
padding-left: calc(0.75rem - 3px);
}
/* Nav link hover: subtle slide right */
nav a:not(.bg-accent-light):hover,
nav button:hover {
transform: translateX(2px);
}
/* ── Badge animations ──────────────────────────────────────── */
.badge, [class*="badge-"] {
transition: all 150ms ease-out;
}
.badge:hover, [class*="badge-"]:hover {
transform: scale(1.05);
}
/* Status badges in tables — subtle entrance */
td .badge, td [class*="badge-"] {
animation: popIn 250ms ease-out both;
}
/* ── Progress bar animation ────────────────────────────────── */
[role="progressbar"] > div,
.bg-green-500,
.bg-red-500,
.bg-amber-500,
.bg-blue-500 {
transition: width 600ms cubic-bezier(0.16, 1, 0.3, 1);
}
/* Render progress bars */
div[class*="bg-status-success"],
div[class*="bg-green"] {
transition: width 600ms cubic-bezier(0.16, 1, 0.3, 1);
}
/* ── Modal / dialog animations ─────────────────────────────── */
/* Backdrop fade */
.fixed.inset-0.z-50 {
animation: fadeIn 200ms ease-out;
}
/* Modal panel pop-in */
.fixed.inset-0.z-50 > div[role="dialog"],
.fixed.inset-0.z-50 > div:not([class*="absolute"]) > div {
animation: fadeInScale 250ms cubic-bezier(0.16, 1, 0.3, 1);
}
/* ── Floating action bar slide-up ──────────────────────────── */
.fixed.bottom-6,
.fixed.bottom-0 {
animation: slideUp 300ms cubic-bezier(0.16, 1, 0.3, 1);
}
/* ── Toast notifications ───────────────────────────────────── */
[data-sonner-toaster] [data-sonner-toast] {
animation: slideInRight 300ms cubic-bezier(0.16, 1, 0.3, 1) !important;
}
/* ── Loading skeleton shimmer ──────────────────────────────── */
.animate-pulse {
animation: subtlePulse 1.5s ease-in-out infinite !important;
}
.skeleton {
background: linear-gradient(
90deg,
var(--color-bg-muted) 25%,
var(--color-bg-surface-hover) 50%,
var(--color-bg-muted) 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
border-radius: 0.5rem;
}
/* ── Image/thumbnail hover zoom ────────────────────────────── */
.group img,
.group video {
transition: transform 300ms ease-out;
}
.group:hover img,
.group:hover video {
transform: scale(1.03);
}
/* ── Number/stat counter entrance ──────────────────────────── */
.text-2xl, .text-3xl, .text-4xl {
animation: countUp 500ms ease-out both;
}
/* ── Checkbox/toggle animation ─────────────────────────────── */
input[type="checkbox"] {
transition: all 150ms ease-out;
}
input[type="checkbox"]:checked {
animation: popIn 200ms ease-out;
}
/* ── Link hover underline animation ────────────────────────── */
a.hover\:underline {
text-decoration: none;
background-image: linear-gradient(currentColor, currentColor);
background-position: 0% 100%;
background-repeat: no-repeat;
background-size: 0% 1px;
transition: background-size 200ms ease-out;
}
a.hover\:underline:hover {
background-size: 100% 1px;
}
/* ── Dropdown/select hover ─────────────────────────────────── */
select {
transition: all 150ms ease-out;
cursor: pointer;
}
select:hover {
border-color: var(--color-text-muted);
}
/* ── Notification bell wiggle ──────────────────────────────── */
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(8deg); }
75% { transform: rotate(-8deg); }
}
.notification-bell:hover {
animation: wiggle 400ms ease-in-out;
}
/* ── Smooth scroll behavior ────────────────────────────────── */
html {
scroll-behavior: smooth;
}
/* ── Reduced motion: respect user preference ───────────────── */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
html {
scroll-behavior: auto;
}
}