Files
HartOMat/frontend/src/index.css
T
Hartmut 79651bc41d feat: dashboard widget animations — staggered entrance, hover glow, progress bars
- Widgets scale in with staggered delays (60ms per widget, up to 12)
- Widget hover: lift 3px + accent border glow (dark mode: accent shadow)
- Inner numbers animate up with count-up effect
- Progress bars grow from left with spring curve (800ms delay for content-first feel)
- All wrapped in prefers-reduced-motion guard

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 01:22:35 +01:00

746 lines
22 KiB
CSS

@tailwind base;
@tailwind components;
@tailwind utilities;
/* ============================================================
ACCENT PRESETS
Applied via data-accent="<key>" on <html>
============================================================ */
/* Default / Schaeffler Green */
:root,
[data-accent="green"] {
--color-accent: #00893d;
--color-accent-hover: #006e31;
--color-accent-light: #e6f4ec;
--color-accent-text: #ffffff;
}
[data-accent="blue"] {
--color-accent: #2563eb;
--color-accent-hover: #1d4ed8;
--color-accent-light: #dbeafe;
--color-accent-text: #ffffff;
}
[data-accent="purple"] {
--color-accent: #7c3aed;
--color-accent-hover: #6d28d9;
--color-accent-light: #ede9fe;
--color-accent-text: #ffffff;
}
[data-accent="amber"] {
--color-accent: #d97706;
--color-accent-hover: #b45309;
--color-accent-light: #fef3c7;
--color-accent-text: #ffffff;
}
[data-accent="teal"] {
--color-accent: #0d9488;
--color-accent-hover: #0f766e;
--color-accent-light: #ccfbf1;
--color-accent-text: #ffffff;
}
/* ============================================================
LIGHT THEME (default)
============================================================ */
:root {
/* Surfaces */
--color-bg-app: #f8f9fb;
--color-bg-surface: #ffffff;
--color-bg-surface-hover: #f5f6f8;
--color-bg-muted: #f1f3f5;
/* Text */
--color-text: #18181b;
--color-text-secondary: #52525b;
--color-text-muted: #a1a1aa;
--color-text-inverse: #ffffff;
/* Borders */
--color-border: #e4e4e7;
--color-border-light: #f1f3f5;
/* Status — Success */
--color-status-success-bg: #dcfce7;
--color-status-success-text: #166534;
/* Status — Warning */
--color-status-warning-bg: #fef9c3;
--color-status-warning-text: #854d0e;
/* Status — Error */
--color-status-error-bg: #fee2e2;
--color-status-error-text: #991b1b;
/* Status — Info */
--color-status-info-bg: #dbeafe;
--color-status-info-text: #1e40af;
/* Extended badge colors */
--color-badge-purple-bg: rgba(124, 58, 237, 0.1);
--color-badge-purple-text: #6d28d9;
--color-badge-orange-bg: rgba(234, 88, 12, 0.1);
--color-badge-orange-text: #c2410c;
--color-badge-teal-bg: rgba(13, 148, 136, 0.1);
--color-badge-teal-text: #0f766e;
}
/* ============================================================
DARK THEME
Applied via .dark class on <html>
============================================================ */
:root.dark {
/* Surfaces — neutral grays instead of blue-slate */
--color-bg-app: #09090b;
--color-bg-surface: #18181b;
--color-bg-surface-hover: #27272a;
--color-bg-muted: #1c1c1f;
/* Text */
--color-text: #fafafa;
--color-text-secondary: #a1a1aa;
--color-text-muted: #71717a;
--color-text-inverse: #09090b;
/* Borders — subtle */
--color-border: rgba(255, 255, 255, 0.08);
--color-border-light: rgba(255, 255, 255, 0.04);
/* Status — Success */
--color-status-success-bg: rgba(34, 197, 94, 0.12);
--color-status-success-text: #4ade80;
/* Status — Warning */
--color-status-warning-bg: rgba(234, 179, 8, 0.12);
--color-status-warning-text: #facc15;
/* Status — Error */
--color-status-error-bg: rgba(239, 68, 68, 0.12);
--color-status-error-text: #f87171;
/* Status — Info */
--color-status-info-bg: rgba(59, 130, 246, 0.12);
--color-status-info-text: #60a5fa;
/* Extended badge colors */
--color-badge-purple-bg: rgba(124, 58, 237, 0.15);
--color-badge-purple-text: #a78bfa;
--color-badge-orange-bg: rgba(234, 88, 12, 0.15);
--color-badge-orange-text: #fb923c;
--color-badge-teal-bg: rgba(13, 148, 136, 0.15);
--color-badge-teal-text: #2dd4bf;
}
/* Dark accent-light overrides (rgba instead of solid pastel) */
:root.dark,
:root.dark [data-accent="green"] {
--color-accent-light: rgba(0, 137, 61, 0.12);
}
:root.dark [data-accent="blue"] {
--color-accent-light: rgba(37, 99, 235, 0.12);
}
:root.dark [data-accent="purple"] {
--color-accent-light: rgba(124, 58, 237, 0.12);
}
:root.dark [data-accent="amber"] {
--color-accent-light: rgba(217, 119, 6, 0.12);
}
:root.dark [data-accent="teal"] {
--color-accent-light: rgba(13, 148, 136, 0.12);
}
/* ============================================================
BASE LAYER
============================================================ */
@layer base {
body {
@apply antialiased;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background-color: var(--color-bg-app);
color: var(--color-text);
transition: background-color 200ms ease, color 200ms ease;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
letter-spacing: -0.01em;
}
/* Native color scheme for form controls */
:root {
color-scheme: light;
}
:root.dark {
color-scheme: dark;
}
/* Checkbox / radio accent color */
input[type="checkbox"],
input[type="radio"] {
accent-color: var(--color-accent);
}
/* Better default scrollbar (thin, subtle) */
* {
scrollbar-width: thin;
scrollbar-color: var(--color-border) transparent;
}
*::-webkit-scrollbar {
width: 6px;
height: 6px;
}
*::-webkit-scrollbar-track {
background: transparent;
}
*::-webkit-scrollbar-thumb {
background-color: var(--color-border);
border-radius: 3px;
}
/* Smoother global transitions */
a, button, input, select, textarea {
transition: all 150ms ease;
}
/* Better selection color */
::selection {
background-color: var(--color-accent);
color: white;
}
}
/* ============================================================
COMPONENT CLASSES
============================================================ */
@layer components {
/* ── Buttons ─────────────────────────────────────────────── */
.btn {
@apply inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg font-medium text-sm transition-all duration-150 ease-out focus:outline-none focus:ring-2 focus:ring-offset-1 disabled:opacity-50 disabled:cursor-not-allowed;
}
.btn-primary {
@apply btn shadow-sm;
background-color: var(--color-accent);
color: var(--color-accent-text);
--tw-ring-color: var(--color-accent);
}
.btn-primary:hover:not(:disabled) {
background-color: var(--color-accent-hover);
transform: translateY(-0.5px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
.btn-primary:active:not(:disabled) {
transform: translateY(0);
}
.btn-secondary {
@apply btn border;
background-color: var(--color-bg-surface);
color: var(--color-text-secondary);
border-color: var(--color-border);
--tw-ring-color: var(--color-accent);
}
.btn-secondary:hover:not(:disabled) {
background-color: var(--color-bg-surface-hover);
border-color: var(--color-text-muted);
}
.btn-danger {
@apply btn bg-red-600 text-white shadow-sm hover:bg-red-700 focus:ring-red-500;
}
.btn-danger:hover:not(:disabled) {
transform: translateY(-0.5px);
}
/* Icon-only button variant */
.btn-icon {
@apply inline-flex items-center justify-center p-1.5 rounded-lg transition-all duration-150 ease-out hover:bg-surface-hover focus:outline-none focus:ring-1;
--tw-ring-color: var(--color-accent);
}
/* ── Cards ───────────────────────────────────────────────── */
.card {
@apply rounded-xl border;
background-color: var(--color-bg-surface);
border-color: var(--color-border);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.04), 0 1px 2px -1px rgba(0, 0, 0, 0.03);
}
:root.dark .card {
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.03);
}
/* ── Badges ──────────────────────────────────────────────── */
.badge {
@apply inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium tracking-wide;
}
.badge-green {
@apply badge;
background-color: var(--color-status-success-bg);
color: var(--color-status-success-text);
}
.badge-yellow {
@apply badge;
background-color: var(--color-status-warning-bg);
color: var(--color-status-warning-text);
}
.badge-red {
@apply badge;
background-color: var(--color-status-error-bg);
color: var(--color-status-error-text);
}
.badge-blue {
@apply badge;
background-color: var(--color-status-info-bg);
color: var(--color-status-info-text);
}
.badge-gray {
@apply badge;
background-color: var(--color-bg-muted);
color: var(--color-text-secondary);
}
.badge-purple {
@apply badge;
background-color: var(--color-badge-purple-bg);
color: var(--color-badge-purple-text);
}
.badge-orange {
@apply badge;
background-color: var(--color-badge-orange-bg);
color: var(--color-badge-orange-text);
}
.badge-teal {
@apply badge;
background-color: var(--color-badge-teal-bg);
color: var(--color-badge-teal-text);
}
/* ── Inputs ──────────────────────────────────────────────── */
.input-base {
@apply w-full px-3 py-2 rounded-lg text-sm border focus:outline-none focus:ring-2 focus:ring-offset-0 transition-all duration-150;
background-color: var(--color-bg-surface);
color: var(--color-text);
border-color: var(--color-border);
--tw-ring-color: var(--color-accent);
}
.input-base::placeholder {
color: var(--color-text-muted);
}
.input-base:focus {
border-color: var(--color-accent);
}
/* Small input variant (used in admin tables) */
.input-sm {
@apply px-2 py-1 rounded-md text-sm border focus:outline-none focus:ring-1 focus:ring-offset-0 transition-all duration-150;
background-color: var(--color-bg-surface);
color: var(--color-text);
border-color: var(--color-border);
--tw-ring-color: var(--color-accent);
}
.input-sm:focus {
border-color: var(--color-accent);
}
/* ── Tables ──────────────────────────────────────────────── */
table th {
@apply text-xs uppercase tracking-wider font-semibold;
color: var(--color-text-muted);
letter-spacing: 0.05em;
}
}
/* ============================================================
ANIMATIONS & MICRO-INTERACTIONS
============================================================ */
/* ── Keyframes ─────────────────────────────────────────────── */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(12px); }
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); }
}
@keyframes subtlePulse {
0%, 100% { opacity: 1; }
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 {
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;
}
/* ── Dashboard widget animations ───────────────────────────── */
/* Dashboard widgets: staggered scale-in entrance */
.grid > div > .card {
animation: widgetEnter 450ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes widgetEnter {
from {
opacity: 0;
transform: scale(0.94) translateY(8px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
/* Staggered delays for dashboard widgets */
.grid > div:nth-child(1) > .card { animation-delay: 0ms; }
.grid > div:nth-child(2) > .card { animation-delay: 60ms; }
.grid > div:nth-child(3) > .card { animation-delay: 120ms; }
.grid > div:nth-child(4) > .card { animation-delay: 180ms; }
.grid > div:nth-child(5) > .card { animation-delay: 240ms; }
.grid > div:nth-child(6) > .card { animation-delay: 300ms; }
.grid > div:nth-child(7) > .card { animation-delay: 360ms; }
.grid > div:nth-child(8) > .card { animation-delay: 420ms; }
.grid > div:nth-child(9) > .card { animation-delay: 480ms; }
.grid > div:nth-child(10) > .card { animation-delay: 540ms; }
.grid > div:nth-child(11) > .card { animation-delay: 600ms; }
.grid > div:nth-child(12) > .card { animation-delay: 660ms; }
.grid > div:nth-child(n+13) > .card { animation-delay: 700ms; }
/* Dashboard widget hover: subtle glow + lift */
.grid > div > .card:hover {
transform: translateY(-3px);
box-shadow: 0 12px 32px -6px rgba(0, 0, 0, 0.15), 0 4px 12px -2px rgba(0, 0, 0, 0.08);
border-color: var(--color-accent);
transition: all 250ms cubic-bezier(0.16, 1, 0.3, 1);
}
:root.dark .grid > div > .card:hover {
box-shadow: 0 12px 32px -6px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--color-accent), 0 0 20px -4px rgba(var(--accent-rgb, 0, 137, 61), 0.15);
}
/* Widget inner content numbers — count-up effect */
.card .text-2xl,
.card .text-3xl,
.card .text-4xl {
animation: countUp 600ms cubic-bezier(0.16, 1, 0.3, 1) both;
animation-delay: inherit;
}
/* Widget progress bars — animated grow from left */
.card [class*="bg-green"],
.card [class*="bg-red"],
.card [class*="bg-amber"],
.card [class*="bg-blue"],
.card [class*="bg-status"] {
animation: progressGrow 800ms cubic-bezier(0.16, 1, 0.3, 1) both;
animation-delay: 400ms;
}
/* ── 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;
}
}