Covers amber accent system, charcoal background lift, button hierarchy, toast notifications, rich empty states, UI zoom, portable storage, and window state persistence.
12 KiB
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
- Reintroduce warm amber (#D97706) as the strategic accent color
- Lift the entire background palette from near-black to charcoal
- Establish clear button hierarchy (primary/secondary/ghost/danger)
- Replace all
alert()calls with a toast notification system - Design rich empty states with icons, copy, and CTA buttons
- Add amber focus states on all inputs
- Add UI zoom control in Settings (persistent)
- Make the app fully portable (data next to exe)
- 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-nonewithbox-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 oftext-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 ofbg-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
Clockicon, 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-primarycentered- When running: colon separators pulse amber (
text-accent-textwith 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
Timericon, 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-elevatedtransition- 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
FolderKanbanicon, 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-4container - "Apply": amber primary
- "Clear": ghost button
mb-6gap between filter bar and table
Table:
- Header row:
bg-bg-surfacebackground - Duration column:
text-accent-text font-mono(amber) - Edit/delete hover reveal: unchanged
Empty state (below filter bar):
- Lucide
Listicon, 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-4container - "Generate": amber primary
- "Export CSV": secondary
Stats:
mt-6spacing 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
BarChart3icon, 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
FileTexticon, 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
zoomproperty on the#approot 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)
- Success:
- 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:
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
- Add dependency:
tauri-plugin-window-state = "2"to Cargo.toml - Add
"window-state"to plugins in tauri.conf.json - Register plugin:
.plugin(tauri_plugin_window_state::Builder::new().build())in lib.rs - The plugin auto-saves state to a
.window-statefile. Since we're making the app portable, configure the plugin to store state in ourdata/directory next to the exe.
Config
In tauri.conf.json, add to plugins:
"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)