Complete platform with unified design system and real API integration. Apps: Dashboard, Fitness, Budget, Inventory, Trips, Reader, Media, Settings Infrastructure: SvelteKit + Python gateway + Docker Compose
462 lines
13 KiB
CSS
462 lines
13 KiB
CSS
@import 'tailwindcss';
|
||
|
||
/* ═══════════════════════════════════════════════
|
||
DESIGN SYSTEM — Single source of truth
|
||
|
||
RULES:
|
||
1. No raw px in component <style> blocks — use tokens
|
||
2. No raw rgba/hex colors — use semantic tokens
|
||
3. No copy-pasting base component styles — use globals
|
||
4. Surface hierarchy: canvas → surface → card
|
||
5. Text hierarchy: text-1 → text-2 → text-3 → text-4
|
||
═══════════════════════════════════════════════ */
|
||
|
||
@layer base {
|
||
:root {
|
||
/* ── Fonts ── */
|
||
--font: 'Inter', -apple-system, system-ui, sans-serif;
|
||
--mono: 'JetBrains Mono', ui-monospace, monospace;
|
||
--transition: 180ms ease;
|
||
|
||
/* ── Spacing scale (4px grid) ──
|
||
* Use these everywhere: padding, margin, gap.
|
||
* Naming: --sp-{n} where value = n × 4px
|
||
*/
|
||
--sp-0: 0px;
|
||
--sp-px: 1px;
|
||
--sp-0.5: 2px;
|
||
--sp-1: 4px;
|
||
--sp-1.5: 6px;
|
||
--sp-2: 8px;
|
||
--sp-3: 12px;
|
||
--sp-4: 16px;
|
||
--sp-5: 20px;
|
||
--sp-6: 24px;
|
||
--sp-7: 28px;
|
||
--sp-8: 32px;
|
||
--sp-10: 40px;
|
||
--sp-12: 48px;
|
||
--sp-16: 64px;
|
||
--sp-20: 80px;
|
||
|
||
/* Semantic spacing aliases */
|
||
--section-gap: var(--sp-7);
|
||
--card-pad: var(--sp-5);
|
||
--card-pad-primary: var(--sp-7);
|
||
--card-pad-secondary: var(--sp-4);
|
||
--row-gap: var(--sp-3);
|
||
--module-gap: var(--sp-5);
|
||
--row-pad-y: 14px;
|
||
--row-pad-x: var(--sp-4);
|
||
--inner-gap: var(--sp-3);
|
||
|
||
/* ── Radius scale ── */
|
||
--radius-xs: 4px;
|
||
--radius-sm: 6px;
|
||
--radius-md: 8px;
|
||
--radius: 12px;
|
||
--radius-lg: 16px;
|
||
--radius-full: 9999px;
|
||
|
||
/* ── Elevation scale ──
|
||
* xs: barely visible (row hover, inner elements)
|
||
* sm: subtle lift (secondary cards, inputs)
|
||
* md: standard card elevation
|
||
* lg: elevated (dropdowns, popovers, hero cards)
|
||
* xl: overlay (modals, slide-out panels)
|
||
*/
|
||
--shadow-xs: 0 1px 2px rgba(0,0,0,0.03);
|
||
--shadow-sm: 0 1px 3px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.04);
|
||
--shadow-md: 0 2px 6px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.06);
|
||
--shadow-lg: 0 4px 12px rgba(0,0,0,0.06), 0 16px 40px rgba(0,0,0,0.1);
|
||
--shadow-xl: 0 8px 24px rgba(0,0,0,0.08), 0 24px 60px rgba(0,0,0,0.15);
|
||
/* Legacy aliases */
|
||
--card-shadow: var(--shadow-md);
|
||
--card-shadow-sm: var(--shadow-sm);
|
||
|
||
/* ── Typography scale ──
|
||
* xs: badges, pills, tiny counters
|
||
* sm: labels, meta, captions, button text
|
||
* base: body text, list items, inputs
|
||
* md: card titles, important rows (16px avoids iOS zoom)
|
||
* lg: section headers, modal titles
|
||
* xl: page titles
|
||
* 2xl: hero headings
|
||
* 3xl: large hero numbers
|
||
*/
|
||
--text-xs: 11px;
|
||
--text-sm: 13px;
|
||
--text-base: 14px;
|
||
--text-md: 15px;
|
||
--text-lg: 17px;
|
||
--text-xl: 22px;
|
||
--text-2xl: 28px;
|
||
--text-3xl: 36px;
|
||
|
||
/* Line heights */
|
||
--leading-tight: 1.2;
|
||
--leading-snug: 1.35;
|
||
--leading-normal: 1.5;
|
||
--leading-relaxed: 1.65;
|
||
--leading-loose: 1.8;
|
||
}
|
||
|
||
/* ── LIGHT MODE ── */
|
||
:root {
|
||
/* Surface hierarchy: canvas (page bg) → surface (sidebars, panels) → card (content containers) */
|
||
--canvas: #F5F6F8;
|
||
--surface: #FFFFFF;
|
||
--surface-secondary: #FAFAFB;
|
||
--card: #FFFFFF;
|
||
--card-secondary: #FAFAFB;
|
||
--card-hover: #f0f0f3;
|
||
|
||
/* Borders */
|
||
--border: rgba(0,0,0,0.06);
|
||
--border-strong: rgba(0,0,0,0.1);
|
||
|
||
/* Text hierarchy: 1 (headings/names) → 2 (body) → 3 (labels/meta) → 4 (placeholder/disabled) */
|
||
--text-1: #1a1a1f;
|
||
--text-2: #4a4a55;
|
||
--text-3: #6b6b76;
|
||
--text-4: #b4b4bd;
|
||
|
||
/* Accent — indigo */
|
||
--accent: #4F46E5;
|
||
--accent-bg: #EEF2FF;
|
||
--accent-dim: rgba(79,70,229,0.06);
|
||
--accent-border: rgba(79,70,229,0.10);
|
||
--accent-focus: rgba(79,70,229,0.12);
|
||
|
||
/* Semantic: success */
|
||
--success: #16A34A;
|
||
--success-bg: #F0FDF4;
|
||
--success-dim: rgba(34,197,94,0.08);
|
||
|
||
/* Semantic: error */
|
||
--error: #DC2626;
|
||
--error-bg: #FEF2F2;
|
||
--error-dim: rgba(239,68,68,0.08);
|
||
|
||
/* Semantic: warning */
|
||
--warning: #d97706;
|
||
--warning-bg: rgba(245,158,11,0.08);
|
||
|
||
/* Overlay */
|
||
--overlay: rgba(0,0,0,0.3);
|
||
--overlay-strong: rgba(0,0,0,0.5);
|
||
--nav-bg: rgba(255,255,255,0.9);
|
||
}
|
||
|
||
/* ── DARK MODE ── */
|
||
.dark {
|
||
--canvas: #09090b;
|
||
--surface: #0f0f12;
|
||
--surface-secondary: #111114;
|
||
--card: #161619;
|
||
--card-secondary: #111114;
|
||
--card-hover: #1c1c20;
|
||
|
||
--border: rgba(255,255,255,0.06);
|
||
--border-strong: rgba(255,255,255,0.1);
|
||
|
||
--text-1: #fafafa;
|
||
--text-2: #a1a1aa;
|
||
--text-3: #71717a;
|
||
--text-4: #3f3f46;
|
||
|
||
--accent: #3b82f6;
|
||
--accent-bg: rgba(59,130,246,0.1);
|
||
--accent-dim: rgba(59,130,246,0.08);
|
||
--accent-border: rgba(59,130,246,0.12);
|
||
--accent-focus: rgba(59,130,246,0.15);
|
||
|
||
--success: #22c55e;
|
||
--success-bg: rgba(34,197,94,0.1);
|
||
--success-dim: rgba(34,197,94,0.08);
|
||
|
||
--error: #ef4444;
|
||
--error-bg: rgba(239,68,68,0.1);
|
||
--error-dim: rgba(239,68,68,0.08);
|
||
|
||
--warning: #f59e0b;
|
||
--warning-bg: rgba(245,158,11,0.1);
|
||
|
||
--overlay: rgba(0,0,0,0.6);
|
||
--overlay-strong: rgba(0,0,0,0.75);
|
||
--nav-bg: rgba(15,15,18,0.9);
|
||
|
||
/* Dark shadows need higher opacity */
|
||
--shadow-xs: 0 1px 2px rgba(0,0,0,0.1);
|
||
--shadow-sm: 0 1px 3px rgba(0,0,0,0.15), 0 4px 12px rgba(0,0,0,0.1);
|
||
--shadow-md: 0 2px 6px rgba(0,0,0,0.15), 0 8px 24px rgba(0,0,0,0.12);
|
||
--shadow-lg: 0 4px 12px rgba(0,0,0,0.2), 0 16px 40px rgba(0,0,0,0.2);
|
||
--shadow-xl: 0 8px 24px rgba(0,0,0,0.25), 0 24px 60px rgba(0,0,0,0.3);
|
||
}
|
||
|
||
/* ── Base resets ── */
|
||
html, body {
|
||
font-family: var(--font);
|
||
background: var(--canvas);
|
||
color: var(--text-1);
|
||
min-height: 100vh;
|
||
transition: background var(--transition), color var(--transition);
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
* { border-color: var(--border); }
|
||
a { color: inherit; text-decoration: none; }
|
||
button { font-family: var(--font); cursor: pointer; }
|
||
input { font-family: var(--font); }
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════
|
||
GLOBAL COMPONENT CLASSES
|
||
Use these in any component without re-declaring
|
||
═══════════════════════════════════════════════ */
|
||
|
||
/* ── Skeleton loader ── */
|
||
.skeleton {
|
||
background: linear-gradient(90deg, var(--card) 25%, var(--card-hover) 50%, var(--card) 75%);
|
||
background-size: 200% 100%;
|
||
animation: shimmer 1.5s infinite;
|
||
border-radius: var(--radius-xs);
|
||
}
|
||
@keyframes shimmer {
|
||
0% { background-position: 200% 0; }
|
||
100% { background-position: -200% 0; }
|
||
}
|
||
|
||
/* ── Buttons ── */
|
||
.btn-primary {
|
||
padding: var(--sp-2) var(--sp-4);
|
||
border-radius: var(--radius-md);
|
||
background: var(--accent);
|
||
color: white;
|
||
border: none;
|
||
font-size: var(--text-sm);
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all var(--transition);
|
||
}
|
||
.btn-primary:hover { opacity: 0.9; }
|
||
.btn-primary.full {
|
||
width: 100%;
|
||
padding: var(--sp-3) var(--sp-4);
|
||
font-size: var(--text-md);
|
||
font-weight: 600;
|
||
border-radius: var(--radius);
|
||
}
|
||
|
||
.btn-secondary {
|
||
padding: var(--sp-2) var(--sp-4);
|
||
border-radius: var(--radius-md);
|
||
background: var(--card-secondary);
|
||
color: var(--text-2);
|
||
border: 1px solid var(--border);
|
||
font-size: var(--text-sm);
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all var(--transition);
|
||
}
|
||
.btn-secondary:hover { background: var(--card-hover); color: var(--text-1); }
|
||
|
||
.btn-icon {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: var(--radius-md);
|
||
background: var(--card-secondary);
|
||
border: 1px solid var(--border);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
color: var(--text-3);
|
||
transition: all var(--transition);
|
||
flex-shrink: 0;
|
||
}
|
||
.btn-icon:hover { color: var(--text-1); background: var(--card-hover); }
|
||
.btn-icon svg { width: var(--sp-4); height: var(--sp-4); }
|
||
|
||
/* ── Inputs ── */
|
||
.input {
|
||
width: 100%;
|
||
padding: 10px 14px;
|
||
border-radius: var(--radius-md);
|
||
background: var(--surface-secondary);
|
||
border: 1px solid var(--border);
|
||
color: var(--text-1);
|
||
font-size: var(--text-base);
|
||
font-family: var(--font);
|
||
outline: none;
|
||
transition: border-color var(--transition);
|
||
}
|
||
.input:focus { border-color: var(--accent); }
|
||
.input::placeholder { color: var(--text-4); }
|
||
|
||
/* ── Module (card container) ──
|
||
* Use for any content block: dashboard widgets, data tables, etc.
|
||
* Variants: .primary (hero, more padding + elevation), .flush (no padding)
|
||
*/
|
||
.module {
|
||
background: var(--card);
|
||
border-radius: var(--radius);
|
||
border: 1px solid var(--border);
|
||
box-shadow: var(--shadow-md);
|
||
padding: var(--card-pad);
|
||
}
|
||
.module.primary {
|
||
padding: var(--card-pad-primary);
|
||
box-shadow: var(--shadow-lg);
|
||
}
|
||
.module.flush { padding: 0; }
|
||
|
||
.module-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: var(--sp-5);
|
||
}
|
||
|
||
.module-title {
|
||
font-size: var(--text-sm);
|
||
font-weight: 600;
|
||
color: var(--text-3);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
}
|
||
|
||
.module-action {
|
||
font-size: var(--text-sm);
|
||
color: var(--accent);
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
text-decoration: none;
|
||
}
|
||
.module-action:hover { text-decoration: underline; }
|
||
|
||
/* ── Data row ──
|
||
* Standard list item pattern: name + meta on left, value/badge on right.
|
||
* Use in transactions, inventory items, feed entries, etc.
|
||
*/
|
||
.data-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--inner-gap);
|
||
padding: var(--row-pad-y) var(--row-pad-x);
|
||
transition: background var(--transition);
|
||
}
|
||
.data-row:hover { background: var(--card-hover); }
|
||
.data-row + .data-row { border-top: 1px solid var(--border); }
|
||
.data-row:nth-child(even) { background: color-mix(in srgb, var(--surface) 68%, var(--card)); }
|
||
.data-row:nth-child(even):hover { background: var(--card-hover); }
|
||
|
||
/* ── Badges ──
|
||
* Semantic status badges. Variants: error, success, warning, accent, muted.
|
||
*/
|
||
.badge {
|
||
font-size: var(--text-xs);
|
||
font-weight: 500;
|
||
padding: 3px 10px;
|
||
border-radius: var(--radius-sm);
|
||
flex-shrink: 0;
|
||
}
|
||
.badge.error { background: var(--error-dim); color: var(--error); }
|
||
.badge.success { background: var(--success-dim); color: var(--success); }
|
||
.badge.warning { background: var(--warning-bg); color: var(--warning); }
|
||
.badge.accent { background: var(--accent-dim); color: var(--accent); }
|
||
.badge.muted { background: var(--card-hover); color: var(--text-4); }
|
||
|
||
/* ── Tabs ──
|
||
* Pill-style tab bar.
|
||
*/
|
||
.tab-bar { display: flex; gap: var(--sp-1); }
|
||
.tab {
|
||
padding: var(--sp-2) 14px;
|
||
border-radius: var(--radius-md);
|
||
font-size: var(--text-base);
|
||
font-weight: 500;
|
||
color: var(--text-3);
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: all var(--transition);
|
||
font-family: var(--font);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--sp-1.5);
|
||
}
|
||
.tab:hover { color: var(--text-1); background: var(--card-hover); }
|
||
.tab.active { color: var(--text-1); background: var(--card); box-shadow: var(--shadow-xs); }
|
||
|
||
.tab-badge {
|
||
font-size: var(--text-xs);
|
||
font-family: var(--mono);
|
||
background: var(--accent-dim);
|
||
color: var(--accent);
|
||
padding: 1px 6px;
|
||
border-radius: var(--radius-xs);
|
||
margin-left: var(--sp-1);
|
||
}
|
||
|
||
/* ── Section header ──
|
||
* Uppercase label above a group of content.
|
||
*/
|
||
.section-label {
|
||
font-size: var(--text-xs);
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
color: var(--text-3);
|
||
margin-bottom: var(--sp-1.5);
|
||
}
|
||
|
||
/* ── Page wrapper ── */
|
||
.page { padding: var(--sp-8) 0 var(--sp-20); }
|
||
.page-header { margin-bottom: var(--section-gap); }
|
||
.page-title {
|
||
font-size: var(--text-sm);
|
||
font-weight: 500;
|
||
color: var(--text-3);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
margin-bottom: var(--sp-1);
|
||
}
|
||
.page-greeting {
|
||
font-size: var(--text-2xl);
|
||
font-weight: 300;
|
||
color: var(--text-1);
|
||
line-height: var(--leading-tight);
|
||
}
|
||
.page-greeting strong { font-weight: 600; }
|
||
|
||
/* ── App surface (centered container) ── */
|
||
.app-surface {
|
||
max-width: 1200px;
|
||
width: 100%;
|
||
margin: 0 auto;
|
||
padding: 0 var(--sp-6);
|
||
}
|
||
|
||
/* ── Responsive ── */
|
||
@media (max-width: 768px) {
|
||
:root {
|
||
--text-xs: 12px;
|
||
--text-sm: 15px;
|
||
--text-base: 16px;
|
||
--text-md: 17px;
|
||
--text-lg: 18px;
|
||
--text-xl: 22px;
|
||
--text-2xl: 26px;
|
||
--text-3xl: 32px;
|
||
|
||
--card-pad: var(--sp-4);
|
||
--card-pad-primary: var(--sp-5);
|
||
--row-pad-y: var(--sp-4);
|
||
--section-gap: var(--sp-5);
|
||
}
|
||
.page-greeting { font-size: var(--text-xl); }
|
||
.page { padding: var(--sp-5) 0 var(--sp-20); }
|
||
.app-surface { padding: 0 var(--sp-5); }
|
||
}
|