feat(docker-watcher): phase 14 - frontend polish & modern UI

Design system with CSS custom properties (light/dark themes).
38 Lucide SVG icon components. Dark mode with system preference.
EN/RU localization with i18n store. Skeleton loaders, empty states,
toggle switches, micro-interactions. Responsive sidebar with
mobile hamburger menu. All pages polished with consistent styling.
This commit is contained in:
2026-03-27 23:53:09 +03:00
parent d4659146fc
commit a3aa5912d9
74 changed files with 2954 additions and 1750 deletions
+258
View File
@@ -0,0 +1,258 @@
/* ── Design Tokens ─────────────────────────────────────────────────────
CSS custom properties for Docker Watcher design system.
Task 1: Color palette, spacing scale, typography, border radius tokens.
Task 12: Dark mode support via [data-theme="dark"] selector.
───────────────────────────────────────────────────────────────────── */
:root {
/* ── Brand Colors ───────────────────────────────────── */
--color-brand-50: #eef2ff;
--color-brand-100: #e0e7ff;
--color-brand-200: #c7d2fe;
--color-brand-300: #a5b4fc;
--color-brand-400: #818cf8;
--color-brand-500: #6366f1;
--color-brand-600: #4f46e5;
--color-brand-700: #4338ca;
--color-brand-800: #3730a3;
--color-brand-900: #312e81;
/* ── Semantic Colors ────────────────────────────────── */
--color-success: #16a34a;
--color-success-light: #dcfce7;
--color-success-dark: #15803d;
--color-warning: #d97706;
--color-warning-light: #fef3c7;
--color-warning-dark: #b45309;
--color-danger: #dc2626;
--color-danger-light: #fee2e2;
--color-danger-dark: #b91c1c;
--color-info: #2563eb;
--color-info-light: #dbeafe;
--color-info-dark: #1d4ed8;
/* ── Surface Colors (Light Mode) ────────────────────── */
--surface-page: #f8fafc;
--surface-card: #ffffff;
--surface-card-hover: #f8fafc;
--surface-sidebar: #ffffff;
--surface-overlay: rgba(0, 0, 0, 0.3);
--surface-input: #ffffff;
/* ── Border Colors ──────────────────────────────────── */
--border-primary: #e2e8f0;
--border-secondary: #f1f5f9;
--border-focus: var(--color-brand-500);
--border-input: #cbd5e1;
/* ── Text Colors ────────────────────────────────────── */
--text-primary: #0f172a;
--text-secondary: #475569;
--text-tertiary: #94a3b8;
--text-inverse: #ffffff;
--text-link: var(--color-brand-600);
--text-link-hover: var(--color-brand-700);
/* ── Spacing Scale (4px base) ───────────────────────── */
--space-0: 0;
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
/* ── Typography Scale ───────────────────────────────── */
--font-family-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif;
--font-family-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', monospace;
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.625;
--weight-normal: 400;
--weight-medium: 500;
--weight-semibold: 600;
--weight-bold: 700;
/* ── Border Radius ──────────────────────────────────── */
--radius-sm: 0.25rem; /* 4px */
--radius-md: 0.375rem; /* 6px */
--radius-lg: 0.5rem; /* 8px */
--radius-xl: 0.75rem; /* 12px */
--radius-2xl: 1rem; /* 16px */
--radius-full: 9999px;
/* ── Shadows ────────────────────────────────────────── */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
/* ── Transitions ────────────────────────────────────── */
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-normal: 200ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
/* ── Sidebar ────────────────────────────────────────── */
--sidebar-width: 16rem; /* 256px */
--sidebar-collapsed-width: 0; /* mobile collapsed */
--topbar-height: 4rem; /* 64px */
}
/* ── Dark Mode Tokens ─────────────────────────────────────────────── */
[data-theme="dark"] {
--surface-page: #0f172a;
--surface-card: #1e293b;
--surface-card-hover: #334155;
--surface-sidebar: #1e293b;
--surface-overlay: rgba(0, 0, 0, 0.6);
--surface-input: #1e293b;
--border-primary: #334155;
--border-secondary: #1e293b;
--border-focus: var(--color-brand-400);
--border-input: #475569;
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--text-tertiary: #64748b;
--text-inverse: #0f172a;
--text-link: var(--color-brand-400);
--text-link-hover: var(--color-brand-300);
--color-success: #22c55e;
--color-success-light: #14532d;
--color-warning: #f59e0b;
--color-warning-light: #451a03;
--color-danger: #ef4444;
--color-danger-light: #450a0a;
--color-info: #3b82f6;
--color-info-light: #172554;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -4px rgba(0, 0, 0, 0.3);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 8px 10px -6px rgba(0, 0, 0, 0.3);
}
/* ── Animations ───────────────────────────────────────────────────── */
@keyframes pulse-status {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
@keyframes slide-in-right {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slide-out-right {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
@keyframes skeleton-shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes scale-in {
from { transform: scale(0.95); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
@keyframes button-press {
0%, 100% { transform: scale(1); }
50% { transform: scale(0.97); }
}
.animate-pulse-status {
animation: pulse-status 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
.animate-slide-in {
animation: slide-in-right var(--transition-slow) forwards;
}
.animate-slide-out {
animation: slide-out-right var(--transition-slow) forwards;
}
.animate-fade-in {
animation: fade-in var(--transition-normal) forwards;
}
.animate-scale-in {
animation: scale-in var(--transition-normal) forwards;
}
.active\:animate-press:active {
animation: button-press 150ms ease-in-out;
}
/* ── Skeleton Loader ──────────────────────────────────────────────── */
.skeleton {
background: linear-gradient(
90deg,
var(--border-secondary) 25%,
var(--border-primary) 50%,
var(--border-secondary) 75%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s ease-in-out infinite;
border-radius: var(--radius-md);
}
/* ── Toggle Switch ────────────────────────────────────────────────── */
.toggle-switch {
position: relative;
width: 2.75rem;
height: 1.5rem;
background-color: var(--border-primary);
border-radius: var(--radius-full);
cursor: pointer;
transition: background-color var(--transition-fast);
}
.toggle-switch[aria-checked="true"] {
background-color: var(--color-brand-600);
}
.toggle-switch::after {
content: '';
position: absolute;
top: 0.125rem;
left: 0.125rem;
width: 1.25rem;
height: 1.25rem;
background-color: white;
border-radius: var(--radius-full);
box-shadow: var(--shadow-sm);
transition: transform var(--transition-fast);
}
.toggle-switch[aria-checked="true"]::after {
transform: translateX(1.25rem);
}