refactor(ui): refresh Tailwind design system tokens

Adopt an indigo brand palette with slate neutrals, add semantic colors, and introduce base/component layers (buttons, cards, badges). Move Inter loading to the Tailwind input CSS and update brand guidelines accordingly.
This commit is contained in:
Dries Peeters
2026-04-26 08:34:08 +02:00
parent dc76042ab8
commit df475ae437
4 changed files with 196 additions and 92 deletions
+80 -31
View File
@@ -1,3 +1,5 @@
@import url('https://fonts.bunny.net/css?family=Inter:400,500,600,700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -6,36 +8,41 @@
@layer base {
:root {
/* Brand (brand-colors.css) */
--brand-primary: #4A90E2;
--brand-primary-dark: #3b82f6;
--brand-primary: #4F46E5;
--brand-primary-dark: #4338ca;
--brand-secondary: #50E3C2;
--brand-secondary-dark: #06b6d4;
--brand-gradient: linear-gradient(135deg, #4A90E2 0%, #50E3C2 100%);
--brand-gradient-horizontal: linear-gradient(to right, #4A90E2 0%, #50E3C2 100%);
--color-success: #4CAF50;
--color-warning: #FF9800;
--color-error: #E53935;
--color-info: #2196F3;
--color-bg-light: #F7F9FB;
--color-bg-light-secondary: #FFFFFF;
--color-text-light: #2D3748;
--color-text-light-secondary: #A0AEC0;
--color-text-light-muted: #718096;
--color-border-light: #E2E8F0;
--color-bg-dark: #1A202C;
--color-bg-dark-secondary: #2D3748;
--color-text-dark: #E2E8F0;
--color-text-dark-secondary: #718096;
--color-text-dark-muted: #A0AEC0;
--color-border-dark: #4A5568;
--brand-gradient: linear-gradient(135deg, #4F46E5 0%, #50E3C2 100%);
--brand-gradient-horizontal: linear-gradient(to right, #4F46E5 0%, #50E3C2 100%);
/* Semantic */
--color-success: #10b981;
--color-warning: #f59e0b;
--color-error: #ef4444;
--color-info: #3b82f6;
/* Neutrals (slate-based) */
--color-bg-light: #f8fafc;
--color-bg-light-secondary: #ffffff;
--color-text-light: #0f172a;
--color-text-light-secondary: #64748b;
--color-text-light-muted: #94a3b8;
--color-border-light: #e2e8f0;
--color-bg-dark: #0b1220;
--color-bg-dark-secondary: #0f172a;
--color-text-dark: #e2e8f0;
--color-text-dark-secondary: #94a3b8;
--color-text-dark-muted: #64748b;
--color-border-dark: #334155;
/* Aliases for keyboard-shortcuts.css */
--color-primary: #4A90E2;
--color-primary: #4F46E5;
--color-card-light: #FFFFFF;
--color-background-light: #F7F9FB;
--color-background-dark: #1A202C;
--color-card-dark: #2D3748;
--color-text-muted-light: #A0AEC0;
--color-text-muted-dark: #718096;
--color-background-light: #f8fafc;
--color-background-dark: #0b1220;
--color-card-dark: #0f172a;
--color-text-muted-light: #64748b;
--color-text-muted-dark: #94a3b8;
/* Spacing (ui-enhancements) */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
@@ -45,6 +52,35 @@
--spacing-2xl: 3rem;
--spacing-3xl: 4rem;
}
html {
font-family: theme(fontFamily.sans);
text-rendering: optimizeLegibility;
}
body {
@apply font-sans antialiased bg-background-light text-text-light dark:bg-background-dark dark:text-text-dark;
}
h1, h2, h3, h4, h5, h6 {
@apply text-text-light dark:text-text-dark font-semibold tracking-tight;
}
a {
@apply text-primary hover:text-primary-dark underline-offset-4;
}
a:hover {
@apply underline;
}
:is(button, [type='button'], [type='submit'], [role='button']) {
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-900;
}
:is(input, select, textarea) {
@apply placeholder:text-slate-400 dark:placeholder:text-slate-500;
}
}
/* ========== Layer: Components ========== */
@@ -99,6 +135,19 @@
@apply px-6 py-3 text-base;
}
.card {
@apply rounded-lg border border-border-light bg-card-light shadow-tt-soft dark:border-border-dark dark:bg-card-dark;
}
.badge {
@apply inline-flex items-center gap-1 rounded-full border border-border-light bg-background-light px-2.5 py-1 text-xs font-medium text-text-light dark:border-border-dark dark:bg-background-dark dark:text-text-dark;
}
.badge-success { @apply badge border-success/20 bg-success/10 text-success dark:border-success/30 dark:bg-success/15; }
.badge-warning { @apply badge border-warning/20 bg-warning/10 text-warning dark:border-warning/30 dark:bg-warning/15; }
.badge-danger { @apply badge border-danger/20 bg-danger/10 text-danger dark:border-danger/30 dark:bg-danger/15; }
.badge-info { @apply badge border-info/20 bg-info/10 text-info dark:border-info/30 dark:bg-info/15; }
/* Brand utilities (from brand-colors.css) */
.bg-brand-gradient { background: var(--brand-gradient); }
.bg-brand-gradient-horizontal { background: var(--brand-gradient-horizontal); }
@@ -282,12 +331,12 @@
.enhanced-table tbody tr { transition: background-color 0.15s; }
.enhanced-table tbody tr:nth-child(even) { background-color: rgba(0, 0, 0, 0.02); }
.dark .enhanced-table tbody tr:nth-child(even) { background-color: rgba(255, 255, 255, 0.03); }
.enhanced-table tbody tr:hover { background-color: rgba(0, 0, 0, 0.04); border-left: 3px solid #4A90E2; }
.enhanced-table tbody tr:hover { background-color: rgba(0, 0, 0, 0.04); border-left: 3px solid #4F46E5; }
.dark .enhanced-table tbody tr:hover { background-color: rgba(255, 255, 255, 0.05); border-left-color: #60a5fa; }
.enhanced-table thead th { position: sticky; top: 0; z-index: 10; background: #fff; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.06); }
.dark .enhanced-table thead th { background: #1e293b; box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.3); }
.table-count-badge { display: inline-flex; align-items: center; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.875rem; font-weight: 500; background: rgba(74, 144, 226, 0.12); color: #4A90E2; }
.dark .table-count-badge { background: rgba(74, 144, 226, 0.2); color: #93c5fd; }
.table-count-badge { display: inline-flex; align-items: center; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.875rem; font-weight: 500; background: rgba(79, 70, 229, 0.12); color: #4F46E5; }
.dark .table-count-badge { background: rgba(79, 70, 229, 0.22); color: #c7d2fe; }
.bulk-actions-bar {
position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(100px);
@@ -498,8 +547,8 @@
/* File input drag-over state */
.form-group-wrapper label:has(input[type="file"]).drag-over {
border-color: #4A90E2;
background-color: rgba(74, 144, 226, 0.08);
border-color: #4F46E5;
background-color: rgba(79, 70, 229, 0.08);
}
/* ----- Responsive Table → Card Layout ----- */
+1 -3
View File
@@ -45,9 +45,7 @@
<link rel="manifest" href="{{ url_for('main.manifest') }}">
<meta name="vapid-public-key" content="{{ config.get('VAPID_PUBLIC_KEY', '') }}">
<script src="{{ url_for('static', filename='pwa-enhancements.js') }}"></script>
<!-- Inter font (Bunny Fonts, GDPR-friendly) -->
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
<link href="https://fonts.bunny.net/css?family=Inter:400,500,600,700&display=swap" rel="stylesheet">
<!-- Inter font is loaded via CSS @import in app/static/src/input.css -->
<link rel="stylesheet" href="{{ url_for('static', filename='dist/output.css') }}?v={{ app_version }}-toastfix1">
<!-- Font Awesome -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
+35 -36
View File
@@ -68,81 +68,80 @@ The primary TimeTracker logo features a rounded square with a gradient backgroun
### Primary Colors
**Primary Blue**
- Hex: `#4A90E2`
- RGB: `74, 144, 226`
- Usage: Primary actions, links, highlights
- WCAG AA compliant on white backgrounds
**Primary Indigo (Brand)**
- Hex: `#4F46E5`
- RGB: `79, 70, 229`
- Usage: Primary actions, links, focus rings, highlights
- Notes: Designed to work with slate-based neutrals in light/dark mode
**Secondary Cyan**
**Secondary Cyan (Accent)**
- Hex: `#50E3C2`
- RGB: `80, 227, 194`
- Usage: Secondary actions, accents, gradients
- WCAG AA compliant on dark backgrounds
- Usage: Secondary accents, gradients, non-critical highlights
**Theme Blue**
**Info Blue**
- Hex: `#3b82f6`
- RGB: `59, 130, 246`
- Usage: PWA theme color, browser chrome
- Usage: Informational states and highlights (not the primary brand color)
### Gradient
The brand uses a gradient from Primary Blue to Secondary Cyan:
- Start: `#4A90E2` (Primary Blue)
The brand uses a gradient from Primary Indigo to Secondary Cyan:
- Start: `#4F46E5` (Primary Indigo)
- End: `#50E3C2` (Secondary Cyan)
- Direction: Diagonal (135deg) or horizontal as needed
### Status Colors
**Success (Green)**
- Hex: `#4CAF50`
- Hex: `#10b981`
- Usage: Success messages, positive indicators
**Warning (Orange)**
- Hex: `#FF9800`
**Warning (Amber)**
- Hex: `#f59e0b`
- Usage: Warnings, caution messages
**Error (Red)**
- Hex: `#E53935`
**Error / Danger (Red)**
- Hex: `#ef4444`
- Usage: Error messages, destructive actions
### Background Colors
**Light Mode:**
- Background: `#F7F9FB`
- Card: `#FFFFFF`
- Secondary: `#f5f5f5`
**Light Mode (Slate):**
- Background: `#f8fafc`
- Card: `#ffffff`
- Border: `#e2e8f0`
**Dark Mode:**
- Background: `#1A202C`
- Card: `#2D3748`
- Secondary: `#4A5568`
**Dark Mode (Slate):**
- Background: `#0b1220`
- Card: `#0f172a`
- Border: `#334155`
### Text Colors
**Light Mode:**
- Primary Text: `#2D3748`
- Secondary Text: `#A0AEC0`
- Muted Text: `#718096`
**Light Mode (Slate):**
- Primary Text: `#0f172a`
- Secondary Text: `#64748b`
- Muted Text: `#94a3b8`
**Dark Mode:**
- Primary Text: `#E2E8F0`
- Secondary Text: `#718096`
- Muted Text: `#A0AEC0`
**Dark Mode (Slate):**
- Primary Text: `#e2e8f0`
- Secondary Text: `#94a3b8`
- Muted Text: `#64748b`
## Typography
### Font Families
**Primary (System Fonts):**
**Primary (Inter):**
```css
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', Arial, sans-serif;
```
**Usage:**
- Body text
- UI elements
- Default text throughout application
- Headings and navigation
### Font Weights
+80 -22
View File
@@ -14,18 +14,21 @@ module.exports = {
},
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#4A90E2',
600: '#3b82f6',
700: '#2563eb',
800: '#1d4ed8',
900: '#1e3a8a',
DEFAULT: '#4A90E2',
dark: '#3b82f6',
// Brand: deep indigo
50: '#eef2ff',
100: '#e0e7ff',
200: '#c7d2fe',
300: '#a5b4fc',
400: '#818cf8',
500: '#6366f1',
600: '#4F46E5',
700: '#4338ca',
800: '#3730a3',
900: '#312e81',
950: '#1e1b4b',
DEFAULT: '#4F46E5',
// Back-compat for `hover:bg-primary-dark` etc.
dark: '#4338ca',
},
secondary: {
50: '#ecfeff',
@@ -41,16 +44,71 @@ module.exports = {
DEFAULT: '#50E3C2',
dark: '#06b6d4',
},
'background-light': '#F7F9FB',
'background-dark': '#1A202C',
'card-light': '#FFFFFF',
'card-dark': '#2D3748',
'text-light': '#2D3748',
'text-dark': '#E2E8F0',
'text-muted-light': '#A0AEC0',
'text-muted-dark': '#718096',
'border-light': '#E2E8F0',
'border-dark': '#4A5568',
// Semantic colors
success: {
50: '#ecfdf5',
100: '#d1fae5',
500: '#10b981',
600: '#059669',
700: '#047857',
DEFAULT: '#10b981',
},
warning: {
50: '#fffbeb',
100: '#fef3c7',
500: '#f59e0b',
600: '#d97706',
700: '#b45309',
DEFAULT: '#f59e0b',
},
danger: {
50: '#fff1f2',
100: '#ffe4e6',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c',
DEFAULT: '#ef4444',
},
info: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
DEFAULT: '#3b82f6',
},
// Neutrals (slate-based) + compatibility aliases used throughout templates
'background-light': '#f8fafc', // slate-50
'background-dark': '#0b1220', // deep slate-ish
'card-light': '#ffffff',
'card-dark': '#0f172a', // slate-900
'text-light': '#0f172a', // slate-900
'text-dark': '#e2e8f0', // slate-200
'text-muted-light': '#64748b', // slate-500
'text-muted-dark': '#94a3b8', // slate-400
'border-light': '#e2e8f0', // slate-200
'border-dark': '#334155', // slate-700
},
borderRadius: {
// Additive tokens (avoid overriding Tailwind defaults)
tt: '0.75rem',
'tt-lg': '1rem',
'tt-xl': '1.25rem',
},
boxShadow: {
// Additive tokens (avoid overriding Tailwind defaults)
'tt-soft': '0 1px 2px rgba(15, 23, 42, 0.04), 0 2px 6px rgba(15, 23, 42, 0.06)',
'tt-card': '0 1px 2px rgba(15, 23, 42, 0.06), 0 6px 18px rgba(15, 23, 42, 0.08)',
'tt-lifted': '0 10px 25px rgba(15, 23, 42, 0.14), 0 4px 10px rgba(15, 23, 42, 0.08)',
},
spacing: {
// Additive tokens (avoid overriding Tailwind defaults)
'tt-xs': '0.375rem',
'tt-sm': '0.625rem',
'tt-md': '0.875rem',
'tt-lg': '1.125rem',
'tt-xl': '1.375rem',
},
},
},