docs: add UI polish & UX upgrade design doc
Covers amber accent system, charcoal background lift, button hierarchy, toast notifications, rich empty states, UI zoom, portable storage, and window state persistence.
This commit is contained in:
371
docs/plans/2026-02-17-ui-polish-design.md
Normal file
371
docs/plans/2026-02-17-ui-polish-design.md
Normal file
@@ -0,0 +1,371 @@
|
||||
# ZeroClock UI Polish & UX Upgrade — Design
|
||||
|
||||
## Problem
|
||||
|
||||
The first redesign pass established a Swiss/Dieter Rams foundation but went too far into monochrome territory. The app is a wall of near-black grey with zero personality. The signature amber accent color is completely absent. All buttons look identical. Empty states are sad grey text. `alert()` calls for all feedback. No visual hierarchy for primary actions. The app also stores data in AppData (not portable) and doesn't remember window position/size.
|
||||
|
||||
## Goals
|
||||
|
||||
1. Reintroduce warm amber (#D97706) as the strategic accent color
|
||||
2. Lift the entire background palette from near-black to charcoal
|
||||
3. Establish clear button hierarchy (primary/secondary/ghost/danger)
|
||||
4. Replace all `alert()` calls with a toast notification system
|
||||
5. Design rich empty states with icons, copy, and CTA buttons
|
||||
6. Add amber focus states on all inputs
|
||||
7. Add UI zoom control in Settings (persistent)
|
||||
8. Make the app fully portable (data next to exe)
|
||||
9. Persist window position and size between runs
|
||||
|
||||
---
|
||||
|
||||
## Design System
|
||||
|
||||
### Color Palette (Revised)
|
||||
|
||||
```
|
||||
Background layers (lifted from near-black to charcoal):
|
||||
--bg-base: #1A1A18 app background
|
||||
--bg-surface: #222220 cards, panels, navRail, titlebar
|
||||
--bg-elevated: #2C2C28 hover, active, raised elements
|
||||
--bg-inset: #141413 inputs, recessed areas
|
||||
|
||||
Text (warm whites — unchanged):
|
||||
--text-primary: #F5F5F0 headings, active items
|
||||
--text-secondary: #8A8A82 body, descriptions
|
||||
--text-tertiary: #5A5A54 disabled, placeholders (bumped from #4A4A45)
|
||||
|
||||
Borders (bumped to match brighter backgrounds):
|
||||
--border-subtle: #2E2E2A dividers, card borders
|
||||
--border-visible: #3D3D38 input borders, focused
|
||||
|
||||
Accent (NEW — amber):
|
||||
--accent: #D97706 button fills, active indicators
|
||||
--accent-hover: #B45309 hover/pressed amber
|
||||
--accent-muted: rgba(217,119,6,0.12) subtle glows, backgrounds
|
||||
--accent-text: #FBBF24 amber text on dark backgrounds
|
||||
|
||||
Status (semantic — unchanged):
|
||||
--status-running: #34D399 timer active, success, toggle on
|
||||
--status-warning: #EAB308 pending, caution
|
||||
--status-error: #EF4444 destructive, overdue
|
||||
--status-info: #3B82F6 informational
|
||||
```
|
||||
|
||||
Remove all legacy alias colors (--color-amber mapped to white, --color-background, --color-surface, etc.).
|
||||
|
||||
### Button Hierarchy
|
||||
|
||||
**Primary** — amber fill, for the ONE main action per view:
|
||||
- `bg-accent text-[#1A1A18] font-medium rounded`
|
||||
- Hover: `bg-accent-hover`
|
||||
- Used: Start timer, Create Project, Save Settings, Generate Report, Apply Filters, Create Invoice
|
||||
|
||||
**Secondary** — outlined, for supporting actions:
|
||||
- `border border-border-visible text-text-primary rounded`
|
||||
- Hover: `bg-bg-elevated`
|
||||
- Used: Export CSV, Export Data, Cancel buttons, Clear filters
|
||||
|
||||
**Ghost** — text only, for low-priority actions:
|
||||
- `text-text-secondary hover:text-text-primary`
|
||||
- No border, no background
|
||||
- Used: "View all" links, tab navigation inactive
|
||||
|
||||
**Danger** — destructive actions:
|
||||
- `border border-status-error text-status-error rounded`
|
||||
- Hover: `bg-status-error/10`
|
||||
- Used: Clear Data, Delete buttons in confirmation dialogs
|
||||
|
||||
### Input Focus States
|
||||
|
||||
All inputs, selects, and textareas:
|
||||
- Current: `focus:border-border-visible` (grey, barely visible)
|
||||
- New: `focus:border-accent focus:outline-none` with `box-shadow: 0 0 0 2px rgba(217,119,6,0.12)` (amber border + subtle amber glow)
|
||||
|
||||
---
|
||||
|
||||
## Shell
|
||||
|
||||
### TitleBar
|
||||
|
||||
- "ZEROCLOCK" wordmark: `text-accent-text` (amber #FBBF24) instead of `text-text-secondary`
|
||||
- Running timer section: unchanged (green dot + project + time + stop)
|
||||
- Window controls: unchanged
|
||||
|
||||
### NavRail
|
||||
|
||||
- Active indicator: 2px left border in `bg-accent` (#D97706) instead of `bg-text-primary` (white)
|
||||
- Active icon color: stays `text-text-primary` (white)
|
||||
- Tooltip: add subtle caret/triangle pointing left for polish
|
||||
- Timer dot at bottom: stays green (semantic)
|
||||
|
||||
---
|
||||
|
||||
## View Designs
|
||||
|
||||
### Dashboard
|
||||
|
||||
**Header:**
|
||||
- Greeting: "Good morning/afternoon/evening" in `text-lg text-text-secondary`
|
||||
- Date: "Monday, February 17, 2026" in `text-xs text-text-tertiary`
|
||||
|
||||
**Stats row (4 stats):**
|
||||
- This Week | This Month | Today | Active Projects
|
||||
- Labels: `text-xs text-text-tertiary uppercase tracking-[0.08em]`
|
||||
- Values: `text-[1.25rem] font-mono text-accent-text` (amber)
|
||||
|
||||
**Weekly chart:**
|
||||
- Bar fill: `#D97706` (amber), today's bar: `#FBBF24` (lighter amber)
|
||||
- Grid: `#2E2E2A`, ticks: `#5A5A54`
|
||||
|
||||
**Recent entries:**
|
||||
- Flat list as current
|
||||
- "View all" ghost link bottom-right, navigates to Entries
|
||||
|
||||
**Empty state:**
|
||||
- Centered vertically in available space
|
||||
- Lucide `Clock` icon, 48px, `text-text-tertiary`
|
||||
- "Start tracking your time" — `text-text-secondary`
|
||||
- "Your dashboard will come alive with stats, charts, and recent activity once you start logging hours." — `text-xs text-text-tertiary`
|
||||
- Primary amber button: "Go to Timer"
|
||||
|
||||
### Timer
|
||||
|
||||
**Hero display:**
|
||||
- `text-[3rem] font-mono text-text-primary` centered
|
||||
- When running: colon separators pulse amber (`text-accent-text` with opacity animation)
|
||||
- When stopped: all white, static
|
||||
|
||||
**Start/Stop button:**
|
||||
- Start: `bg-accent text-[#1A1A18] px-10 py-3 text-sm font-medium rounded`
|
||||
- Stop: `bg-status-error text-white px-10 py-3 text-sm font-medium rounded` (filled red)
|
||||
- 150ms color transition between states
|
||||
|
||||
**Inputs:**
|
||||
- `max-w-[36rem] mx-auto` (centered)
|
||||
- Amber focus states
|
||||
- Project select: small colored dot preview of selected project color
|
||||
|
||||
**Recent entries:**
|
||||
- Scoped to today's entries
|
||||
- Most recent entry: subtle amber left border
|
||||
- Max 5, "View all" ghost link
|
||||
|
||||
**Empty state:**
|
||||
- Lucide `Timer` icon, 40px, `text-text-tertiary`
|
||||
- "No entries today" — `text-text-secondary`
|
||||
- "Select a project and hit Start to begin tracking" — `text-xs text-text-tertiary`
|
||||
|
||||
### Projects
|
||||
|
||||
**Header:**
|
||||
- Title "Projects" left
|
||||
- "+ Add" becomes small amber primary button: `bg-accent text-[#1A1A18] px-3 py-1.5 text-xs font-medium rounded`
|
||||
|
||||
**Cards:**
|
||||
- 2px left border in project color
|
||||
- `bg-bg-surface hover:bg-bg-elevated` transition
|
||||
- Hover: left border widens from 2px to 3px
|
||||
- Rate and client inline: "ClientName · $50.00/hr"
|
||||
|
||||
**Create/Edit dialog:**
|
||||
- Submit: amber primary
|
||||
- Cancel: secondary
|
||||
- Color picker: row of 8 preset swatches above hex input
|
||||
- Presets: #D97706, #3B82F6, #8B5CF6, #EC4899, #10B981, #EF4444, #06B6D4, #6B7280
|
||||
|
||||
**Empty state:**
|
||||
- Lucide `FolderKanban` icon, 48px, `text-text-tertiary`
|
||||
- "No projects yet" — `text-text-secondary`
|
||||
- "Projects organize your time entries and set billing rates for clients." — `text-xs text-text-tertiary`
|
||||
- Primary amber button: "Create Project" (opens dialog)
|
||||
|
||||
### Entries
|
||||
|
||||
**Filter bar:**
|
||||
- Wrapped in `bg-bg-surface rounded p-4` container
|
||||
- "Apply": amber primary
|
||||
- "Clear": ghost button
|
||||
- `mb-6` gap between filter bar and table
|
||||
|
||||
**Table:**
|
||||
- Header row: `bg-bg-surface` background
|
||||
- Duration column: `text-accent-text font-mono` (amber)
|
||||
- Edit/delete hover reveal: unchanged
|
||||
|
||||
**Empty state (below filter bar):**
|
||||
- Lucide `List` icon, 48px, `text-text-tertiary`
|
||||
- "No entries found" — `text-text-secondary`
|
||||
- "Time entries will appear here as you track your work. Try adjusting the date range if you have existing entries." — `text-xs text-text-tertiary`
|
||||
- Primary amber button: "Go to Timer"
|
||||
|
||||
### Reports
|
||||
|
||||
**Filter bar:**
|
||||
- Same `bg-bg-surface rounded p-4` container
|
||||
- "Generate": amber primary
|
||||
- "Export CSV": secondary
|
||||
|
||||
**Stats:**
|
||||
- `mt-6` spacing after filter bar
|
||||
- Values: `text-accent-text font-mono` (amber)
|
||||
|
||||
**Chart:**
|
||||
- Bars use each project's own assigned color
|
||||
- Fallback for no-color projects: #D97706
|
||||
- Grid: `#2E2E2A`, ticks: `#5A5A54`
|
||||
|
||||
**Breakdown:**
|
||||
- Hours value: `text-accent-text font-mono`
|
||||
- Earnings: `text-text-secondary font-mono`
|
||||
|
||||
**Empty state:**
|
||||
- Lucide `BarChart3` icon, 48px, `text-text-tertiary`
|
||||
- "Generate a report to see your data" — `text-text-secondary`
|
||||
- No CTA button (Generate button is right there)
|
||||
|
||||
### Invoices
|
||||
|
||||
**Tabs:**
|
||||
- Active: `border-b-2 border-accent text-text-primary` (amber underline)
|
||||
- Inactive: `text-text-tertiary hover:text-text-secondary`
|
||||
|
||||
**List table:**
|
||||
- Header row: `bg-bg-surface`
|
||||
- Amount: `text-accent-text font-mono`
|
||||
- Status colors: unchanged (semantic)
|
||||
|
||||
**Create form:**
|
||||
- Submit: amber primary
|
||||
- Cancel: secondary
|
||||
- Total line: `text-accent-text font-mono`
|
||||
|
||||
**Invoice detail dialog:**
|
||||
- Export PDF: amber primary
|
||||
- Total: `text-accent-text`
|
||||
|
||||
**Empty state:**
|
||||
- Lucide `FileText` icon, 48px, `text-text-tertiary`
|
||||
- "No invoices yet" — `text-text-secondary`
|
||||
- "Create invoices from your tracked time to bill clients." — `text-xs text-text-tertiary`
|
||||
- Primary amber button: "Create Invoice" (switches to Create tab)
|
||||
|
||||
### Settings
|
||||
|
||||
**Buttons:**
|
||||
- "Save Settings": amber primary
|
||||
- "Export": secondary
|
||||
- "Clear Data": danger (red)
|
||||
|
||||
**Toggle:** stays green when active (semantic "on" state)
|
||||
|
||||
**All `alert()` calls:** replaced with toasts
|
||||
|
||||
**UI Zoom control (NEW):**
|
||||
- New section "Appearance" between Timer and Data sections
|
||||
- Label: "UI Scale"
|
||||
- Minus button [-] | value display "100%" | Plus button [+]
|
||||
- Steps: 80%, 90%, 100%, 110%, 120%, 130%, 150%
|
||||
- Implementation: CSS `zoom` property on the `#app` root element
|
||||
- Persisted via `update_settings('ui_zoom', '100')` in the settings SQLite table
|
||||
- Applied on app startup before first paint (read from settings store in App.vue onMounted)
|
||||
|
||||
---
|
||||
|
||||
## Toast Notification System
|
||||
|
||||
### Component: `ToastNotification.vue`
|
||||
|
||||
- Fixed position, top-center of the main content area, `top-4`
|
||||
- Max width 320px
|
||||
- `bg-bg-surface border border-border-subtle rounded shadow-lg`
|
||||
- 3px left border colored by type:
|
||||
- Success: `border-status-running` (green)
|
||||
- Error: `border-status-error` (red)
|
||||
- Info: `border-accent` (amber)
|
||||
- Content: Lucide icon (Check/X/Info, 16px) + message in `text-sm text-text-primary`
|
||||
- Enter animation: slide down from -20px + fade, 200ms
|
||||
- Exit: fade out 150ms
|
||||
- Auto-dismiss: 3 seconds
|
||||
- Click to dismiss early
|
||||
- Stack with 8px gap, max 3 visible
|
||||
|
||||
### Store: `useToastStore`
|
||||
|
||||
- `addToast(message: string, type: 'success' | 'error' | 'info')`
|
||||
- Auto-generates unique ID
|
||||
- Auto-removes after 3s timeout
|
||||
- Max 3 toasts visible, oldest removed first
|
||||
|
||||
### Replacements
|
||||
|
||||
All `alert()` calls across the app become toast calls:
|
||||
- Settings save success/failure
|
||||
- Clear data success/failure
|
||||
- Export data success/failure
|
||||
- Timer "Please select a project"
|
||||
- Reports "Please select a date range"
|
||||
- Reports "No data to export"
|
||||
- Invoices "Please select a client"
|
||||
|
||||
---
|
||||
|
||||
## Portable App
|
||||
|
||||
### Problem
|
||||
|
||||
Currently uses `directories::ProjectDirs` to store the SQLite database in the OS app data directory (e.g., `C:\Users\<user>\AppData\Roaming\ZeroClock\`). This is not portable.
|
||||
|
||||
### Solution
|
||||
|
||||
Change `get_data_dir()` in `lib.rs` to always resolve relative to the executable:
|
||||
|
||||
```rust
|
||||
fn get_data_dir() -> PathBuf {
|
||||
let exe_path = std::env::current_exe().unwrap();
|
||||
let data_dir = exe_path.parent().unwrap().join("data");
|
||||
std::fs::create_dir_all(&data_dir).ok();
|
||||
data_dir
|
||||
}
|
||||
```
|
||||
|
||||
This stores `data/timetracker.db` next to the `.exe`. The `directories` crate dependency can be removed from Cargo.toml.
|
||||
|
||||
---
|
||||
|
||||
## Window State Persistence
|
||||
|
||||
### Plugin: `tauri-plugin-window-state`
|
||||
|
||||
Add the Tauri window-state plugin to save/restore:
|
||||
- Window position (x, y)
|
||||
- Window size (width, height)
|
||||
- Maximized state
|
||||
|
||||
### Implementation
|
||||
|
||||
1. Add dependency: `tauri-plugin-window-state = "2"` to Cargo.toml
|
||||
2. Add `"window-state"` to plugins in tauri.conf.json
|
||||
3. Register plugin: `.plugin(tauri_plugin_window_state::Builder::new().build())` in lib.rs
|
||||
4. The plugin auto-saves state to a `.window-state` file. Since we're making the app portable, configure the plugin to store state in our `data/` directory next to the exe.
|
||||
|
||||
### Config
|
||||
|
||||
In `tauri.conf.json`, add to plugins:
|
||||
```json
|
||||
"window-state": {
|
||||
"all": true
|
||||
}
|
||||
```
|
||||
|
||||
Remove `"center": true` from the window config so the saved position is respected on subsequent launches.
|
||||
|
||||
---
|
||||
|
||||
## Transitions & Motion
|
||||
|
||||
Unchanged from first redesign, plus:
|
||||
- Toast enter: translateY(-20px) + opacity 0 to 0+1, 200ms ease-out
|
||||
- Toast exit: opacity 1 to 0, 150ms
|
||||
- Button hover: 150ms background-color transition
|
||||
- Project card left-border width: 150ms transition on hover
|
||||
- Timer colon amber pulse: opacity 0.4 to 1.0 on accent-text color, 1s ease-in-out infinite (only when running)
|
||||
Reference in New Issue
Block a user