veza/apps/web/src/index.css
senke c3cd478508 feat(ui): contextual skeleton loading + list stagger animations
Skeleton loading (5 pages migrated from spinner):
- SettingsPage: tabs + profile + settings cards skeleton
- RolesPage: table header + 6 data rows + assign role skeleton
- MarketplaceHome: filter bar + category pills + 8 product cards
- TrackSearchResults: results count + 8 track card grid
- PlaybackSummary: 3-column stats skeleton

List stagger animations (5 lists):
- New stagger-fade-in CSS keyframe (translateY 8px, 250ms, ease-out)
- 50ms per item delay, capped at 500ms (10+ items render together)
- Applied to: NotificationsPage, PlaylistList, PlaylistTrackList (static),
  dashboard TrackList, NotificationMenuList
- Respects prefers-reduced-motion

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 23:59:54 +01:00

1347 lines
No EOL
34 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
/* ╔══════════════════════════════════════════════════════════════════════════╗
║ KŌDŌ DESIGN SYSTEM v3.0 — "The Path of Sound" ║
║ VEZA × TALAS — Manga × Graffiti × Nature × Gaming × Linux ║
╚══════════════════════════════════════════════════════════════════════════╝ */
:root {
/* ═══════════════════════════════════════════════════════════════════════════
CORE PALETTE — Light Mode
═══════════════════════════════════════════════════════════════════════════ */
--background: oklch(0.985 0 0);
--foreground: oklch(0.145 0.005 265);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0.005 265);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0.005 265);
/* Primary: Neon Cyan — Tech, Flow, Future */
--primary: oklch(0.75 0.18 195);
--primary-foreground: oklch(0.13 0.02 265);
/* Secondary: Hot Magenta — Street, Passion, Edge */
--secondary: oklch(0.65 0.25 330);
--secondary-foreground: oklch(0.98 0 0);
--muted: oklch(0.96 0.005 265);
--muted-foreground: oklch(0.42 0.015 265);
--accent: oklch(0.95 0.01 265);
--accent-foreground: oklch(0.20 0.01 265);
/* Semantic Colors */
--destructive: oklch(0.60 0.22 25);
--destructive-foreground: oklch(0.98 0 0);
--success: oklch(0.72 0.19 145);
--success-foreground: oklch(0.13 0.02 145);
--warning: oklch(0.80 0.16 75);
--warning-foreground: oklch(0.20 0.05 75);
--info: oklch(0.70 0.15 230);
--info-foreground: oklch(0.13 0.02 230);
--border: oklch(0.90 0.005 265);
--input: oklch(0.90 0.005 265);
--ring: oklch(0.75 0.18 195);
/* Charts */
--chart-1: oklch(0.75 0.18 195);
--chart-2: oklch(0.65 0.25 330);
--chart-3: oklch(0.72 0.19 145);
--chart-4: oklch(0.70 0.20 290);
--chart-5: oklch(0.80 0.16 75);
--radius: 0.5rem;
/* Sidebar */
--sidebar: oklch(0.98 0.003 265);
--sidebar-foreground: oklch(0.145 0.005 265);
--sidebar-primary: oklch(0.75 0.18 195);
--sidebar-primary-foreground: oklch(0.13 0.02 265);
--sidebar-accent: oklch(0.94 0.01 265);
--sidebar-accent-foreground: oklch(0.20 0.01 265);
--sidebar-border: oklch(0.90 0.005 265);
--sidebar-ring: oklch(0.75 0.18 195);
/* Sidebar active item indicator (left border) — use instead of arbitrary shadow */
--sidebar-active-indicator: inset 2px 0 0 0 var(--sidebar-primary);
/* Sidebar layout — Phase 3: tokens (Discord 240px expanded, 72px collapsed) */
--sidebar-width-expanded: 15rem;
/* 240px — aligné Discord channel list */
--sidebar-width-collapsed: 5rem;
/* 80px — collapsed icons-only */
--sidebar-offset-left: 1.5rem;
--sidebar-offset-top: 5rem;
--sidebar-offset-bottom: 1.5rem;
/* margin between bottom of sidebar and viewport bottom */
--sidebar-z-index: 95;
--sidebar-overlay-z-index: 90;
/* Dashboard stat cards — Phase 3: ombre réduite pour hiérarchie */
--stat-icon-shadow: 0 0 6px rgb(255 255 255 / 0.08);
/* Sparkline / path glow (currentColor) */
--stat-sparkline-glow: 0 0 8px currentColor;
/* Layout primitives — standard widths/heights for app shell and pages */
--layout-content-max-width: 100rem;
/* 1600px — main content max width */
--layout-main-min-height: calc(100vh - 4rem);
/* below 64px header */
--layout-page-min-height: 37.5rem;
/* 600px — full-page min height */
--layout-page-min-height-sm: 25rem;
/* 400px — compact page / empty state */
--layout-story-decorator-min-height: 12rem;
/* 192px — story wrapper (min-h-48) */
--layout-gap: 1rem;
/* 16px — default gap between sections (align Tailwind gap-4) */
--layout-gap-sm: 0.75rem;
/* 12px — tight gap (gap-3) */
--layout-gap-lg: 1.5rem;
/* 24px — loose gap (gap-6) */
--layout-drawer-max-height: 60vh;
/* queue, side panels */
--layout-panel-max-height: 70vh;
/* floating panels (e.g. audio player popover) */
--layout-list-max-height: 25rem;
/* 400px — scroll lists (align min-h-layout-page-sm) */
--layout-modal-max-height: 85vh;
--layout-modal-max-height-sm: 80vh;
--layout-modal-max-height-xs: 70vh;
--layout-modal-max-height-lg: 90vh;
--layout-lyrics-height: 60vh;
--layout-lyrics-height-sm: 50vh;
/* App shell — header, main offsets and margins (sidebar-driven) */
--header-height: 4rem;
/* fixed header bar height (was h-16) */
--main-offset-top: 5rem;
/* main padding-top to clear fixed header (was pt-20) */
--main-offset-bottom: 9rem;
/* main padding-bottom reserve for floating player (144px — avoid content under player) */
--main-margin-left-expanded: 18rem;
/* main margin when sidebar open: sidebar 15rem + 3rem gap */
--main-margin-left-collapsed: 7rem;
/* main margin when sidebar collapsed: 5rem + 2rem gap */
--header-left-expanded: 18rem;
/* header bar left edge when sidebar open */
--header-left-collapsed: 5rem;
/* header bar left edge when sidebar collapsed */
/* ═══════════════════════════════════════════════════════════════════════════
KŌDŌ EXTENDED PALETTE — "Spectre Urbain"
═══════════════════════════════════════════════════════════════════════════ */
/* Void Scale */
--void-0: oklch(0.985 0 0);
--void-50: oklch(0.97 0.003 265);
--void-100: oklch(0.94 0.005 265);
--void-200: oklch(0.88 0.008 265);
--void-300: oklch(0.78 0.01 265);
--void-400: oklch(0.62 0.015 265);
--void-500: oklch(0.50 0.02 265);
--void-600: oklch(0.40 0.025 265);
--void-700: oklch(0.30 0.02 265);
--void-800: oklch(0.22 0.015 265);
--void-900: oklch(0.15 0.01 265);
--void-950: oklch(0.10 0.005 265);
/* Neon Cyan Scale */
--cyan-50: oklch(0.97 0.04 195);
--cyan-100: oklch(0.93 0.08 195);
--cyan-200: oklch(0.88 0.12 195);
--cyan-300: oklch(0.82 0.15 195);
--cyan-400: oklch(0.78 0.17 195);
--cyan-500: oklch(0.75 0.18 195);
--cyan-600: oklch(0.65 0.16 195);
--cyan-700: oklch(0.55 0.14 195);
--cyan-800: oklch(0.45 0.12 195);
--cyan-900: oklch(0.35 0.10 195);
/* Hot Magenta Scale */
--magenta-50: oklch(0.97 0.05 330);
--magenta-100: oklch(0.93 0.10 330);
--magenta-200: oklch(0.85 0.16 330);
--magenta-300: oklch(0.78 0.20 330);
--magenta-400: oklch(0.70 0.23 330);
--magenta-500: oklch(0.65 0.25 330);
--magenta-600: oklch(0.55 0.22 330);
--magenta-700: oklch(0.45 0.18 330);
--magenta-800: oklch(0.35 0.14 330);
--magenta-900: oklch(0.28 0.10 330);
/* Acid Lime Scale */
--lime-50: oklch(0.97 0.05 145);
--lime-100: oklch(0.94 0.10 145);
--lime-200: oklch(0.88 0.14 145);
--lime-300: oklch(0.82 0.17 145);
--lime-400: oklch(0.77 0.18 145);
--lime-500: oklch(0.72 0.19 145);
--lime-600: oklch(0.62 0.17 145);
--lime-700: oklch(0.52 0.14 145);
--lime-800: oklch(0.42 0.11 145);
--lime-900: oklch(0.32 0.08 145);
/* Gaming Colors */
--xp-gold: oklch(0.85 0.16 85);
--hp-red: oklch(0.60 0.22 25);
--mp-blue: oklch(0.60 0.18 250);
--shield-purple: oklch(0.55 0.22 300);
/* Nature Tones */
--moss: oklch(0.45 0.10 145);
--moss-light: oklch(0.55 0.12 145);
--bark: oklch(0.35 0.05 55);
--leaf: oklch(0.62 0.15 140);
/* Manga Spectrum */
--sakura: oklch(0.85 0.10 350);
--yurei: oklch(0.60 0.15 290);
--shonen: oklch(0.60 0.20 30);
/* Terminal */
--terminal-green: oklch(0.75 0.20 145);
--terminal-amber: oklch(0.80 0.15 80);
--matrix: oklch(0.15 0.05 145);
/* Gradients */
--gradient-neon: linear-gradient(135deg, var(--cyan-500), var(--magenta-500), var(--lime-500));
--gradient-cyber: linear-gradient(135deg, var(--cyan-400), var(--magenta-500));
--gradient-sunset: linear-gradient(135deg, oklch(0.65 0.20 35), var(--magenta-500));
--gradient-forest: linear-gradient(135deg, var(--moss), var(--leaf), var(--lime-500));
--gradient-manga: linear-gradient(135deg, var(--sakura), var(--yurei));
--gradient-terminal: linear-gradient(180deg, var(--matrix), var(--void-950));
--gradient-gaming: linear-gradient(135deg, var(--shonen), var(--xp-gold));
/* Glows */
--glow-cyan: 0 0 20px oklch(0.75 0.18 195 / 0.4);
--glow-magenta: 0 0 20px oklch(0.65 0.25 330 / 0.4);
--glow-lime: 0 0 20px oklch(0.72 0.19 145 / 0.4);
--glow-gold: 0 0 20px oklch(0.85 0.16 85 / 0.4);
--glow-terminal: 0 0 10px oklch(0.75 0.20 145 / 0.3);
/* Glass */
--glass-bg: oklch(1 0 0 / 0.8);
--glass-border: oklch(0 0 0 / 0.05);
--glass-blur: 16px;
/* Animation Easings */
--ease-out: cubic-bezier(0.33, 1, 0.68, 1);
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
--duration-instant: 100ms;
--duration-fast: 150ms;
--duration-normal: 250ms;
/* Immersive UI: micro-interactions (hover, focus) */
--duration-immersive: 200ms;
--duration-slow: 400ms;
--duration-slower: 600ms;
/* Clip Paths — Manga Style */
--clip-manga: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px));
--clip-manga-lg: polygon(0 0, calc(100% - 20px) 0, 100% 20px, 100% 100%, 20px 100%, 0 calc(100% - 20px));
--clip-hex: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
--clip-badge: polygon(0 10%, 10% 0, 90% 0, 100% 10%, 100% 90%, 90% 100%, 10% 100%, 0 90%);
--clip-slant: polygon(0 0, 100% 0, 100% calc(100% - 8px), 0 100%);
}
/* ═══════════════════════════════════════════════════════════════════════════
DARK MODE — The Void
═══════════════════════════════════════════════════════════════════════════ */
.dark {
/* Spotify/Discord Premium: Deep, rich dark tones */
/* Main Background: Very deep void (approx #0d0d12) */
--background: oklch(0.10 0.005 265);
--foreground: oklch(0.98 0 0);
/* Cards/Surfaces: Slight elevation (approx #16161e) */
--card: oklch(0.13 0.01 265);
--card-foreground: oklch(0.98 0 0);
--popover: oklch(0.13 0.01 265);
--popover-foreground: oklch(0.98 0 0);
/* Primary: Electric Cyan (preserved but refined) */
--primary: oklch(0.72 0.19 195);
--primary-foreground: oklch(0.08 0.02 265);
--secondary: oklch(0.25 0.04 265);
/* Darker secondary surface */
--secondary-foreground: oklch(0.90 0.01 265);
--muted: oklch(0.18 0.01 265);
--muted-foreground: oklch(0.70 0.015 265);
/* Accent: Hover states (approx #23232f) */
--accent: oklch(0.20 0.02 265);
--accent-foreground: oklch(0.98 0 0);
--destructive: oklch(0.62 0.22 25);
--destructive-foreground: oklch(0.98 0 0);
/* Borders: Very subtle, refined transparency */
--border: oklch(1 0 0 / 0.08);
--input: oklch(1 0 0 / 0.08);
--ring: oklch(0.72 0.19 195 / 0.5);
/* Sidebar: Darkest surface (approx #08080c) */
--sidebar: oklch(0.08 0.005 265);
--sidebar-foreground: oklch(0.70 0.015 265);
/* Muted by default — synced with --muted-foreground for consistency */
--sidebar-primary: oklch(0.72 0.19 195);
--sidebar-primary-foreground: oklch(0.98 0 0);
--sidebar-accent: oklch(1 0 0 / 0.04);
--sidebar-accent-foreground: oklch(0.98 0 0);
--sidebar-border: oklch(1 0 0 / 0.04);
--sidebar-ring: oklch(0.72 0.19 195);
/* Charts */
--chart-1: oklch(0.75 0.18 195);
--chart-2: oklch(0.65 0.25 330);
--chart-3: oklch(0.72 0.19 145);
--chart-4: oklch(0.70 0.20 290);
--chart-5: oklch(0.80 0.16 75);
/* Update Void Scale for lighter theme (shifted up) */
--void-0: oklch(0.15 0.02 265);
--void-50: oklch(0.18 0.02 265);
--void-100: oklch(0.22 0.02 265);
/* ...rest can stay relative or be unused */
--void-950: oklch(0.98 0 0);
/* Glows - Adjusted for lighter contrast */
--glow-cyan: 0 0 25px oklch(0.75 0.18 195 / 0.25);
--glow-magenta: 0 0 25px oklch(0.65 0.25 330 / 0.25);
--glow-lime: 0 0 25px oklch(0.72 0.19 145 / 0.25);
--glow-gold: 0 0 25px oklch(0.85 0.16 85 / 0.25);
--glow-terminal: 0 0 15px oklch(0.78 0.20 145 / 0.2);
/* Glass - Lighter background opacity */
--glass-bg: oklch(0.18 0.02 265 / 0.6);
--glass-border: oklch(1 0 0 / 0.1);
--glass-blur: 24px;
}
@theme inline {
/* Typography — KŌDŌ Font Stack (audit P3: explicit fallback if Rajdhani glyphs fail) */
--font-sans: 'Rajdhani', 'Inter', 'Noto Sans JP', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', 'Consolas', monospace;
--font-display: 'Orbitron', 'Bebas Neue', sans-serif;
--font-jp: 'Noto Sans JP', sans-serif;
/* Core Design Tokens */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
/* Extended Semantic Colors */
--color-success: var(--success);
--color-success-foreground: var(--success-foreground);
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
--color-info: var(--info);
--color-info-foreground: var(--info-foreground);
/* Neon Colors */
--color-cyan: var(--cyan-500);
--color-magenta: var(--magenta-500);
--color-lime: var(--lime-500);
--color-gold: var(--xp-gold);
/* Chart Colors */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
/* Radius — Squircle: xl for surfaces, full for interactive */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-full: 9999px;
/* Sidebar */
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
/* ═══════════════════════════════════════════════════════════════════════════
BASE STYLES
═══════════════════════════════════════════════════════════════════════════ */
@layer base {
* {
@apply border-border outline-ring/50;
}
html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
@apply bg-background text-foreground;
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
}
/* Noise texture overlay */
body::before {
content: '';
position: fixed;
inset: 0;
background:
url("data:image/svg+xml,%3Csvg viewBox='0 0 400 400' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
opacity: 0.015;
pointer-events: none;
z-index: 9998;
}
::selection {
background: var(--primary);
color: var(--primary-foreground);
}
/* Custom Scrollbar — Spotify/Discord-like: thin, subtle, low visual weight */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: color-mix(in oklch, var(--muted-foreground) 30%, transparent);
border-radius: 9999px;
transition: background-color 0.2s ease;
}
::-webkit-scrollbar-thumb:hover {
background: color-mix(in oklch, var(--muted-foreground) 50%, transparent);
}
@supports (scrollbar-width: thin) {
* {
scrollbar-width: thin;
scrollbar-color: color-mix(in oklch, var(--muted-foreground) 30%, transparent) transparent;
}
}
/* Typography */
h1,
h2,
h3,
h4,
h5,
h6 {
@apply font-sans font-semibold tracking-tight text-foreground;
text-wrap: balance;
}
h1 {
@apply text-4xl md:text-5xl;
}
h2 {
@apply text-3xl md:text-4xl;
}
h3 {
@apply text-2xl md:text-3xl;
}
h4 {
@apply text-xl md:text-2xl;
}
h5 {
@apply text-lg md:text-xl;
}
h6 {
@apply text-base md:text-lg;
}
p {
@apply text-muted-foreground leading-relaxed;
text-wrap: pretty;
}
a:not([class]) {
@apply text-primary transition-colors;
}
a:not([class]):hover {
@apply text-primary/80;
}
code {
@apply font-mono text-sm bg-muted px-1.5 py-0.5 rounded;
}
pre {
@apply font-mono text-sm bg-muted p-4 rounded-lg overflow-x-auto;
}
}
/* ═══════════════════════════════════════════════════════════════════════════
KŌDŌ UTILITY CLASSES
═══════════════════════════════════════════════════════════════════════════ */
@layer utilities {
/* Layout primitives — use instead of arbitrary values for app shell and pages */
.max-w-layout-content {
max-width: var(--layout-content-max-width);
}
.min-h-layout-main {
min-height: var(--layout-main-min-height);
}
.min-h-layout-page {
min-height: var(--layout-page-min-height);
}
.min-h-layout-page-sm {
min-height: var(--layout-page-min-height-sm);
}
.min-h-layout-story {
min-height: var(--layout-story-decorator-min-height);
}
.max-h-layout-drawer {
max-height: var(--layout-drawer-max-height);
}
.max-h-layout-panel {
max-height: var(--layout-panel-max-height);
}
.max-h-layout-list {
max-height: var(--layout-list-max-height);
}
.max-h-layout-modal {
max-height: var(--layout-modal-max-height);
}
.max-h-layout-modal-sm {
max-height: var(--layout-modal-max-height-sm);
}
.max-h-layout-modal-xs {
max-height: var(--layout-modal-max-height-xs);
}
.max-h-layout-modal-lg {
max-height: var(--layout-modal-max-height-lg);
}
.h-layout-lyrics {
height: var(--layout-lyrics-height);
}
.h-layout-lyrics-sm {
height: var(--layout-lyrics-height-sm);
}
/* Sidebar layout — use tokens instead of arbitrary values */
.w-sidebar-expanded {
width: var(--sidebar-width-expanded);
}
.w-sidebar-collapsed {
width: var(--sidebar-width-collapsed);
}
.left-sidebar {
left: var(--sidebar-offset-left);
}
.top-sidebar {
top: var(--sidebar-offset-top);
}
.bottom-sidebar {
bottom: var(--sidebar-offset-bottom);
}
.z-sidebar {
z-index: var(--sidebar-z-index);
}
.z-sidebar-overlay {
z-index: var(--sidebar-overlay-z-index);
}
/* App shell — utility classes for layout (use with lg: for desktop) */
.h-header {
height: var(--header-height);
}
.pt-main {
padding-top: var(--main-offset-top);
}
.pb-main {
padding-bottom: var(--main-offset-bottom);
}
.ml-main-expanded {
margin-left: var(--main-margin-left-expanded);
}
.ml-main-collapsed {
margin-left: var(--main-margin-left-collapsed);
}
/* Responsive margin-left for main (Tailwind does not generate lg: for custom classes) */
@media (min-width: 1024px) {
.lg\:ml-main-expanded {
margin-left: var(--main-margin-left-expanded);
}
.lg\:ml-main-collapsed {
margin-left: var(--main-margin-left-collapsed);
}
/* Fixed player: do not overlap sidebar (same left as content area) */
.lg\:left-main-expanded {
left: var(--main-margin-left-expanded);
}
.lg\:left-main-collapsed {
left: var(--main-margin-left-collapsed);
}
}
.left-header-expanded {
left: var(--header-left-expanded);
}
.left-header-collapsed {
left: var(--header-left-collapsed);
}
.drop-shadow-stat-icon {
filter: drop-shadow(var(--stat-icon-shadow));
}
.drop-shadow-stat-sparkline {
filter: drop-shadow(var(--stat-sparkline-glow));
}
/* Shell transition — sidebar width/opacity (use on aside and inner content) */
.transition-shell {
transition: width var(--duration-normal) var(--ease-out),
opacity var(--duration-normal) var(--ease-out),
transform var(--duration-normal) var(--ease-out);
}
/* Respect prefers-reduced-motion: no decorative motion for sidebar and player */
@media (prefers-reduced-motion: reduce) {
.transition-shell {
transition: none;
}
.player-bar-entrance {
animation: none !important;
}
}
/* Modal/page animations (camelCase class names used in codebase) — tokenized */
.animate-fadeIn {
animation: fade-in var(--duration-normal) var(--ease-out);
}
.animate-scaleIn {
animation: scale-in var(--duration-normal) var(--ease-out);
}
/* Semantic shadows — card, modal, tooltip (tokens from design-system.css) */
.shadow-card {
box-shadow: var(--shadow-card);
}
.shadow-card-hover {
box-shadow: var(--shadow-card-hover);
}
.shadow-card-glow-cyan {
box-shadow: var(--shadow-card-glow-cyan);
}
.shadow-card-glow-magenta {
box-shadow: var(--shadow-card-glow-magenta);
}
.shadow-modal {
box-shadow: var(--shadow-modal);
}
.shadow-tooltip {
box-shadow: var(--shadow-tooltip);
}
/* Sidebar active nav item — left border indicator */
.sidebar-active-indicator {
box-shadow: var(--sidebar-active-indicator);
}
/* Button primary hover glow */
.shadow-button-primary-glow {
box-shadow: var(--button-primary-glow);
}
.shadow-button-primary-glow-hover {
box-shadow: var(--button-primary-glow-hover);
}
/* Player thumb and bar hover (tokens from design-system.css) */
.shadow-player-thumb {
box-shadow: var(--player-thumb-glow);
}
.shadow-player-hover {
box-shadow: var(--player-hover-glow);
}
.shadow-queue-item-current {
box-shadow: var(--queue-item-current-glow);
}
.shadow-slider-thumb {
box-shadow: var(--slider-thumb-glow);
}
/* Status / activity dots (dashboard) */
.shadow-status-dot-cyan {
box-shadow: var(--status-dot-glow-cyan);
}
.shadow-status-dot-lime {
box-shadow: var(--status-dot-glow-lime);
}
.shadow-status-dot-magenta {
box-shadow: var(--status-dot-glow-magenta);
}
.shadow-cover-depth {
box-shadow: var(--shadow-cover-depth);
}
.shadow-gold-glow {
box-shadow: var(--shadow-gold-glow);
}
.shadow-fab-glow {
box-shadow: var(--shadow-fab-glow);
}
.shadow-fab-glow-hover:hover {
box-shadow: var(--shadow-fab-glow-hover);
}
/* Neon Glows */
.glow-cyan {
box-shadow: var(--glow-cyan);
}
.glow-magenta {
box-shadow: var(--glow-magenta);
}
.glow-lime {
box-shadow: var(--glow-lime);
}
.glow-gold {
box-shadow: var(--glow-gold);
}
.glow-terminal {
box-shadow: var(--glow-terminal);
}
/* Text Gradients */
.text-gradient-neon {
@apply bg-clip-text text-transparent;
background-image: var(--gradient-neon);
}
.text-gradient-cyber {
@apply bg-clip-text text-transparent;
background-image: var(--gradient-cyber);
}
.text-gradient-sunset {
@apply bg-clip-text text-transparent;
background-image: var(--gradient-sunset);
}
.text-gradient-forest {
@apply bg-clip-text text-transparent;
background-image: var(--gradient-forest);
}
.text-gradient-manga {
@apply bg-clip-text text-transparent;
background-image: var(--gradient-manga);
}
/* Glass Effect */
.glass {
background: var(--glass-bg);
backdrop-filter: blur(var(--glass-blur));
border: 1px solid var(--glass-border);
}
/* Noise Texture */
.noise {
position: relative;
}
.noise::before {
content: "";
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.7' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
pointer-events: none;
opacity: 0.5;
mix-blend-mode: overlay;
border-radius: inherit;
}
/* Scanlines */
.scanlines::after {
content: '';
position: absolute;
inset: 0;
background: repeating-linear-gradient(0deg,
transparent,
transparent 2px,
oklch(0 0 0 / 0.03) 2px,
oklch(0 0 0 / 0.03) 4px);
pointer-events: none;
border-radius: inherit;
}
/* Manga Clip Paths */
.clip-manga {
clip-path: var(--clip-manga);
}
.clip-manga-lg {
clip-path: var(--clip-manga-lg);
}
.clip-hex {
clip-path: var(--clip-hex);
}
.clip-badge {
clip-path: var(--clip-badge);
}
.clip-slant {
clip-path: var(--clip-slant);
}
/* Font Utilities */
.font-display {
font-family: var(--font-display);
}
.font-jp {
font-family: var(--font-jp);
}
/* Typography — Standard heading/body utility classes (KŌDŌ hierarchy) */
.text-display {
@apply text-4xl font-bold tracking-tight;
}
.text-heading-1 {
@apply text-3xl font-semibold tracking-tight;
}
.text-heading-2 {
@apply text-2xl font-semibold;
}
.text-heading-3 {
@apply text-xl font-medium;
}
.text-heading-4 {
@apply text-lg font-medium;
}
.text-body-lg {
@apply text-base leading-relaxed;
}
.text-body {
@apply text-sm leading-relaxed;
}
.text-caption {
@apply text-xs text-muted-foreground;
}
.text-label {
@apply text-xs font-medium uppercase tracking-wider text-muted-foreground;
}
/* Animation Utilities */
.animate-like-bounce {
animation: like-bounce var(--duration-slow) var(--ease-out);
}
.animate-glow-pulse {
animation: glow-pulse 2s ease-in-out infinite;
}
.animate-float {
animation: float 6s ease-in-out infinite;
}
.animate-slide-up {
animation: slide-up var(--duration-normal) var(--ease-out);
}
.animate-fade-in {
animation: fade-in var(--duration-normal) var(--ease-out);
}
.animate-empty-state-in {
animation: empty-state-in var(--duration-normal) var(--ease-out) both;
}
.animate-spin-slow {
animation: spin-slow 10s linear infinite;
}
.animate-achievement {
animation: achievement-slide 0.5s var(--ease-spring);
}
.animate-eq {
animation: eq-bounce 0.5s ease-in-out infinite;
}
.animate-marquee {
animation: marquee 10s linear infinite;
}
/* Hover Effects */
.hover-lift {
@apply transition-transform duration-[var(--duration-fast)];
}
.hover-lift:hover {
@apply -translate-y-1;
}
.hover-glow-cyan {
@apply transition-shadow duration-[var(--duration-fast)];
}
.hover-glow-cyan:hover {
box-shadow: var(--glow-cyan);
}
.hover-glow-magenta {
@apply transition-shadow duration-[var(--duration-fast)];
}
.hover-glow-magenta:hover {
box-shadow: var(--glow-magenta);
}
.hover-glow-lime {
@apply transition-shadow duration-[var(--duration-fast)];
}
.hover-glow-lime:hover {
box-shadow: var(--glow-lime);
}
.hover-glow-gold {
@apply transition-shadow duration-[var(--duration-fast)];
}
.hover-glow-gold:hover {
box-shadow: var(--glow-gold);
}
/* Gaming specific */
.text-xp {
color: var(--xp-gold);
}
.text-hp {
color: var(--hp-red);
}
.text-mp {
color: var(--mp-blue);
}
.text-terminal {
color: var(--terminal-green);
text-shadow: var(--glow-terminal);
}
.bg-terminal {
background: var(--gradient-terminal);
}
.bg-gaming {
background: var(--gradient-gaming);
}
.bg-manga {
background: var(--gradient-manga);
}
.bg-forest {
background: var(--gradient-forest);
}
/* Border neon */
.border-neon-cyan {
border-color: var(--cyan-500);
}
.border-neon-magenta {
border-color: var(--magenta-500);
}
.border-neon-lime {
border-color: var(--lime-500);
}
.border-neon-gold {
border-color: var(--xp-gold);
}
/* Error shake — premium form validation feedback */
.animate-shake {
animation: shake 0.4s ease-in-out;
}
/* Stagger fade-in — cascading list entry animation (Discord/Spotify-like) */
.animate-stagger-in {
animation: stagger-fade-in var(--duration-normal) var(--ease-out) both;
}
}
/* ═══════════════════════════════════════════════════════════════════════════
KEYFRAME ANIMATIONS
═══════════════════════════════════════════════════════════════════════════ */
@keyframes like-bounce {
0% {
transform: scale(1);
}
25% {
transform: scale(1.3);
}
50% {
transform: scale(0.9);
}
75% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
20%, 40%, 60%, 80% { transform: translateX(4px); }
}
@keyframes glow-pulse {
0%,
100% {
opacity: 1;
filter: brightness(1);
}
50% {
opacity: 0.85;
filter: brightness(1.15);
}
}
@keyframes float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes empty-state-in {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
/* Skeleton shimmer — premium loading effect (Spotify/Discord-like) */
.skeleton-shimmer {
background: linear-gradient(
90deg,
transparent 0%,
rgba(255, 255, 255, 0.06) 40%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0.06) 60%,
transparent 100%
);
background-size: 200% 100%;
animation: shimmer 1.8s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.skeleton-shimmer {
animation: none;
background: none;
}
.animate-stagger-in {
animation: none;
}
}
@keyframes spin-slow {
to {
transform: rotate(360deg);
}
}
@keyframes pulse-ring {
0% {
transform: scale(0.8);
opacity: 1;
}
100% {
transform: scale(1.5);
opacity: 0;
}
}
@keyframes eq-bounce {
0%,
100% {
transform: scaleY(1);
}
50% {
transform: scaleY(0.3);
}
}
@keyframes typing {
0%,
60%,
100% {
transform: translateY(0);
}
30% {
transform: translateY(-4px);
}
}
@keyframes marquee {
0%, 20% {
transform: translateX(0);
}
80%, 100% {
transform: translateX(-100%);
}
}
@keyframes achievement-slide {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes graffiti-shake {
0%,
100% {
transform: rotate(0deg);
}
25% {
transform: rotate(-2deg);
}
75% {
transform: rotate(2deg);
}
}
@keyframes terminal-blink {
0%,
50% {
opacity: 1;
}
51%,
100% {
opacity: 0;
}
}
@keyframes neon-flicker {
0%,
19%,
21%,
23%,
25%,
54%,
56%,
100% {
opacity: 1;
text-shadow: var(--glow-cyan);
}
20%,
24%,
55% {
opacity: 0.8;
text-shadow: none;
}
}
@keyframes stagger-fade-in {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes bar-fill {
from {
width: 0;
}
}
@keyframes level-up {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
filter: brightness(1.5);
}
100% {
transform: scale(1);
}
}