# MPV.Rocks Installer - Visual & UI Improvement Plan

**Generated:** February 2026  
**Analyzed by:** GLM-5

---

## Executive Summary

This document outlines visual and user interface improvements for both the Terminal UI (TUI) and Web UI of the MPV.Rocks Installer. Improvements are categorized by priority, effort, and affected components.

---

## Part 1: Terminal UI (TUI) Improvements

### Current State

The TUI is built on **Bubbletea** with a consistent purple theme across 20+ states. It uses `lipgloss` for styling and provides rich functionality including installation management, hardware acceleration configuration, and language preferences.

### 1.1 Color & Theme Enhancements

#### Theme System

| Improvement | Description | Effort |
|-------------|-------------|--------|
| **Dark/Light Mode Detection** | Detect terminal background color using `termenv.HasDarkBackground()` | 2h |
| **Theme Configuration** | Allow users to select theme variant | 4h |
| **High Contrast Mode** | Accessibility-friendly high contrast option | 2h |

```go
// pkg/tui/theme.go (new file)
type Theme struct {
    Primary    lipgloss.Color
    Secondary  lipgloss.Color
    Success    lipgloss.Color
    Error      lipgloss.Color
    Warning    lipgloss.Color
    Background lipgloss.Color
    Text       lipgloss.Color
    Muted      lipgloss.Color
}

var DarkTheme = Theme{
    Primary:    "#7c3aed", // Purple
    Secondary:  "#4f46e5", // Indigo
    Success:    "#10b981", // Green
    Error:      "#ef4444", // Red
    Warning:    "#f59e0b", // Orange
    Background: "#1f2937", // Gray-800
    Text:       "#f9fafb", // Gray-50
    Muted:      "#6b7280", // Gray-500
}

var LightTheme = Theme{
    Primary:    "#7c3aed",
    Secondary:  "#4f46e5",
    Success:    "#059669",
    Error:      "#dc2626",
    Warning:    "#d97706",
    Background: "#f9fafb",
    Text:       "#111827",
    Muted:      "#9ca3af",
}
```

#### Enhanced Color Palette

```go
// Extended color palette
var (
    // Existing
    purple     = lipgloss.Color("#7c3aed")
    lightPurple = lipgloss.Color("#a78bfa")
    darkPurple  = lipgloss.Color("#5b21b6")
    
    // Proposed additions
    indigo     = lipgloss.Color("#4f46e5")
    cyan       = lipgloss.Color("#06b6d4")
    teal       = lipgloss.Color("#14b8a6")
    pink       = lipgloss.Color("#ec4899")
    amber      = lipgloss.Color("#f59e0b")
)
```

### 1.2 Layout Improvements

#### Two-Column Layout for Wide Terminals

```
┌────────────────────────────────────────────────────────────────┐
│                     MPV.Rocks Installer                        │
├────────────────────────────────┬───────────────────────────────┤
│  System Information            │  Quick Actions                │
│  ───────────────────────────── │  ──────────────────────────── │
│  OS: Linux (Ubuntu 24.04)      │  [► Install MPV]              │
│  CPU: AMD Ryzen 9 5950X        │  [► Configure Hardware]       │
│  GPU: NVIDIA RTX 4090          │  [► Language Preferences]     │
│  Memory: 64 GB                 │  [► Check for Updates]        │
├────────────────────────────────┴───────────────────────────────┤
│  [1] Install    [2] Update    [3] Configure    [4] Help       │
└────────────────────────────────────────────────────────────────┘
```

#### Responsive Layout Logic

```go
func (m Model) View() string {
    if m.termWidth >= 100 {
        return m.wideLayoutView()
    }
    return m.standardLayoutView()
}
```

### 1.3 Progress Indicators

#### Multi-Stage Progress

```
Installing MPV...
├─ ✓ Downloading archive     [████████████████████] 100%
├─ ◐ Extracting files        [████████░░░░░░░░░░░░]  40%
├─ ○ Configuring settings    [░░░░░░░░░░░░░░░░░░░░]   0%
└─ ○ Creating shortcuts      [░░░░░░░░░░░░░░░░░░░░]   0%
```

#### Spinner Animations

```go
// pkg/tui/spinner.go
var spinnerFrames = []string{"◐", "◓", "◑", "◒"}
var progressSpinner = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}

func (m *Model) spinnerView() string {
    if m.isLoading {
        return lipgloss.NewStyle().
            Foreground(purple).
            Render(progressSpinner[m.spinnerFrame%len(progressSpinner)] + " ")
    }
    return ""
}
```

### 1.4 Status Icons & Indicators

#### Current vs Proposed

| Element | Current | Proposed |
|---------|---------|----------|
| Success | `✓` | `✓` (green, bold) |
| Error | `✗` | `✗` (red, bold) |
| Warning | `⚠️` | `⚠` (amber) |
| Loading | none | `◐` (animated) |
| Installed | `[✓]` | `●` (green dot) |
| Update available | `↑` | `⬆` (cyan arrow) |
| Recommended | `[ Recommended ]` | `★` (gold star) |

#### Codec Display Enhancement

```go
// Enhanced codec display with icons
func formatCodecDisplay(codecs []string) string {
    codecIcons := map[string]string{
        "AV1":  "🎬",  // Video camera
        "HEVC": "🎥",  // Movie camera
        "VP9":  "📺",  // TV
        "AVC":  "📹",  // Video cassette
    }
    // ...
}
```

### 1.5 Interactive Elements

#### Keyboard Shortcuts Panel

```
┌─────────────────────────────────────┐
│ Keyboard Shortcuts                   │
├─────────────────────────────────────┤
│  Ctrl+C    Quit application          │
│  Ctrl+H    Toggle help               │
│  Ctrl+V    Show version              │
│  /         Search (in lists)         │
│  ↑/↓       Navigate                  │
│  Enter     Select                    │
│  Esc       Back/Cancel               │
│  Ctrl+↑/↓  Reorder items             │
│  ?         Show this help            │
└─────────────────────────────────────┘
```

#### Confirmation Dialogs

```
┌─────────────────────────────────────────┐
│ ⚠ Confirm Uninstall                      │
├─────────────────────────────────────────┤
│                                          │
│  Are you sure you want to uninstall      │
│  MPV? This will remove the application   │
│  but preserve your configuration.        │
│                                          │
│  [Enter] Confirm    [Esc] Cancel         │
└─────────────────────────────────────────┘
```

### 1.6 Accessibility

#### Screen Reader Support

- Add semantic markers for list items
- Announce state changes
- Provide text alternatives for visual indicators

#### Keyboard Navigation

```go
// Add vim-style navigation
case "j", "J":
    // Move down
case "k", "K":
    // Move up
case "g":
    // Go to top
case "G":
    // Go to bottom
case "/":
    // Enter search mode
```

---

## Part 2: Web UI Improvements

### Current State

The Web UI uses TailwindCSS (via CDN), HTMX for dynamic updates, and custom JavaScript for modals and language preferences. It has 8 main pages with a consistent dark theme.

### 2.1 Performance (Critical) ✅ DONE

#### Static TailwindCSS Build ✅ COMPLETED (February 2026)

**Problem:** Every page loads ~3MB from CDN
**Solution:** Built static CSS with content scanning and safelist

**Implementation:**

1. Created `tailwind.config.js`:
```javascript
module.exports = {
  content: [
    "./internal/webassets/templates/**/*.html",
    "./internal/webassets/static/**/*.js",  // IMPORTANT: Include JS files!
  ],
  darkMode: 'class',
  safelist: [
    // Dynamic colors for codec badges
    { pattern: /bg-(red|orange|amber|yellow|green|emerald|teal|cyan|blue|indigo|purple|pink)-600/ },
    // State classes toggled via JS
    'hidden', 'opacity-0', 'opacity-100',
  ],
  theme: { extend: {} },
  plugins: [],
}
```

2. Created `internal/webassets/css/input.css`:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

3. Build command:
```bash
npx tailwindcss -i ./internal/webassets/css/input.css -o ./internal/webassets/static/css/tailwind.min.css --minify
```

4. Updated templates:
```html
<!-- Replaced CDN with static -->
<link href="/static/css/tailwind.min.css" rel="stylesheet">
```

**Result:** ~3MB → ~16KB (187x improvement) ✅

**Important Lessons:**
- Must include JS files in content scanning (not just HTML)
- Use safelist for dynamically-generated classes (codec badge colors)
- Boxicons CSS overrides `hidden` class - use `opacity` instead

### 2.2 Layout Improvements

#### Responsive Sidebar

```html
<!-- Mobile-first collapsible sidebar -->
<nav id="sidebar" 
     class="fixed inset-y-0 left-0 w-64 bg-gray-900 transform 
            -translate-x-full lg:translate-x-0 transition-transform z-50">
  <!-- Navigation content -->
</nav>

<!-- Mobile menu button -->
<button id="menu-toggle" class="lg:hidden fixed top-4 left-4 z-50">
  <svg class="w-6 h-6"><!-- Menu icon --></svg>
</button>
```

#### Breadcrumb Navigation

```html
<nav class="flex items-center space-x-2 text-sm text-gray-400 mb-4">
  <a href="/" class="hover:text-white">Dashboard</a>
  <svg class="w-4 h-4"><!-- Chevron --></svg>
  <span class="text-white">Configuration</span>
</nav>
```

### 2.3 Visual Design System

#### CSS Custom Properties

```css
:root {
  /* Colors */
  --color-primary: #7c3aed;
  --color-primary-hover: #6d28d9;
  --color-success: #10b981;
  --color-error: #ef4444;
  --color-warning: #f59e0b;
  --color-info: #06b6d4;
  
  /* Surfaces */
  --surface-0: #111827;
  --surface-1: #1f2937;
  --surface-2: #374151;
  --surface-3: #4b5563;
  
  /* Typography */
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
  
  /* Spacing */
  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 1.5rem;
  --space-xl: 2rem;
  
  /* Radius */
  --radius-sm: 0.375rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-xl: 1rem;
  
  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.5);
}

/* Dark mode (default) */
.dark {
  color-scheme: dark;
}

/* Light mode */
.light {
  --surface-0: #f9fafb;
  --surface-1: #f3f4f6;
  --surface-2: #e5e7eb;
  --surface-3: #d1d5db;
}
```

### 2.4 Component Improvements

#### Toast Notifications

```javascript
// New toast notification system
const toast = {
  success: (message) => showToast(message, 'success'),
  error: (message) => showToast(message, 'error'),
  warning: (message) => showToast(message, 'warning'),
  info: (message) => showToast(message, 'info'),
};

function showToast(message, type = 'info') {
  const container = document.getElementById('toast-container');
  const toast = document.createElement('div');
  toast.className = `toast toast-${type} animate-slide-in`;
  toast.innerHTML = `
    <span class="toast-icon">${getIcon(type)}</span>
    <span class="toast-message">${escapeHtml(message)}</span>
    <button class="toast-close" onclick="this.parentElement.remove()">×</button>
  `;
  container.appendChild(toast);
  setTimeout(() => toast.remove(), 5000);
}
```

#### Non-Blocking Installation Progress

```html
<!-- Sidebar progress instead of full-screen modal -->
<div id="install-progress" class="fixed bottom-4 right-4 w-80 bg-gray-800 
     rounded-lg shadow-lg p-4 hidden">
  <div class="flex items-center gap-3">
    <div class="animate-spin"><!-- Spinner --></div>
    <div>
      <p class="font-medium">Installing MPV...</p>
      <p class="text-sm text-gray-400" id="install-status">Downloading</p>
    </div>
  </div>
  <div class="mt-2 h-1 bg-gray-700 rounded-full overflow-hidden">
    <div id="install-progress-bar" class="h-full bg-primary-500 transition-all" 
         style="width: 0%"></div>
  </div>
  <button class="mt-2 text-sm text-red-400 hover:text-red-300" 
          onclick="cancelInstall()">Cancel</button>
</div>
```

#### Drag-and-Drop Priority Lists

```javascript
// languages.js enhancement
function initDragAndDrop() {
  const list = document.getElementById('priority-list');
  
  list.addEventListener('dragstart', (e) => {
    e.dataTransfer.setData('text/plain', e.target.dataset.locale);
    e.target.classList.add('opacity-50');
  });
  
  list.addEventListener('dragover', (e) => {
    e.preventDefault();
    const dragging = document.querySelector('.dragging');
    const siblings = [...list.querySelectorAll('.priority-item:not(.dragging)')];
    const nextSibling = siblings.find(sibling => {
      const rect = sibling.getBoundingClientRect();
      return e.clientY < rect.top + rect.height / 2;
    });
    list.insertBefore(dragging, nextSibling);
  });
  
  list.addEventListener('drop', (e) => {
    e.preventDefault();
    savePriorityOrder();
  });
}
```

### 2.5 Accessibility Improvements

#### ARIA Landmarks

```html
<!-- Add to all templates -->
<a href="#main-content" class="sr-only focus:not-sr-only">
  Skip to main content
</a>

<nav aria-label="Main navigation" role="navigation">
  <!-- Sidebar -->
</nav>

<main id="main-content" role="main" aria-label="Main content">
  <!-- Page content -->
</main>

<footer role="contentinfo">
  <!-- Footer -->
</footer>
```

#### Focus Management

```css
/* Visible focus indicators */
:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* Remove default focus for mouse users */
:focus:not(:focus-visible) {
  outline: none;
}

/* Focus trap for modals */
.modal:focus-within {
  /* Ensure focus stays within modal */
}
```

#### Live Regions

```html
<!-- Status announcements -->
<div id="status-announcer" 
     aria-live="polite" 
     aria-atomic="true" 
     class="sr-only">
</div>

<!-- Alert announcements -->
<div id="alert-announcer" 
     aria-live="assertive" 
     aria-atomic="true" 
     class="sr-only">
</div>
```

```javascript
function announce(message, priority = 'polite') {
  const announcer = document.getElementById(
    priority === 'assertive' ? 'alert-announcer' : 'status-announcer'
  );
  announcer.textContent = '';
  setTimeout(() => announcer.textContent = message, 100);
}
```

### 2.6 Dark/Light Mode Toggle

```html
<!-- Theme toggle button -->
<button id="theme-toggle" 
        class="p-2 rounded-lg hover:bg-gray-700"
        aria-label="Toggle theme">
  <svg id="theme-icon-dark" class="w-5 h-5 hidden">
    <!-- Moon icon -->
  </svg>
  <svg id="theme-icon-light" class="w-5 h-5">
    <!-- Sun icon -->
  </svg>
</button>
```

```javascript
// Theme management
const theme = {
  init() {
    const saved = localStorage.getItem('theme');
    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
    this.set(saved || (prefersDark ? 'dark' : 'light'));
  },
  
  toggle() {
    this.set(document.documentElement.classList.contains('dark') ? 'light' : 'dark');
  },
  
  set(theme) {
    document.documentElement.classList.remove('dark', 'light');
    document.documentElement.classList.add(theme);
    localStorage.setItem('theme', theme);
    this.updateIcons(theme);
  },
  
  updateIcons(theme) {
    document.getElementById('theme-icon-dark').classList.toggle('hidden', theme === 'dark');
    document.getElementById('theme-icon-light').classList.toggle('hidden', theme === 'light');
  }
};

// Initialize on page load
theme.init();
```

---

## Implementation Priority

### Phase 1 (Quick Wins) - 1-2 Days ✅ COMPLETED

| Task | Effort | Impact | Status |
|------|--------|--------|--------|
| Fix `debugMode: true` | 5 min | Medium | ✅ DONE |
| Add ARIA landmarks | 1 hour | High | ✅ DONE |
| Add focus indicators | 30 min | High | ✅ DONE |
| Add skip links | 30 min | High | ✅ DONE |
| Add live regions | 30 min | High | ✅ DONE |
| Focus management for modals | 2 hours | High | ✅ DONE |
| Theme toggle (Web) | 2 hours | Medium | Pending |

**Accessibility Improvements Completed:**
- ✅ ARIA landmarks on sidebar navigation (`aria-label="Main navigation"`)
- ✅ ARIA landmarks on main content (`role="main" aria-label="Main content"`)
- ✅ ARIA landmarks on server status (`role="status" aria-live="polite"`)
- ✅ Skip to main content link (`.skip-link` class, hidden until focused)
- ✅ Focus indicators (`:focus-visible` with purple outline)
- ✅ Screen reader only content (`.sr-only` class)
- ✅ Live regions for announcements (`#status-announcer`, `#alert-announcer`)
- ✅ Modal focus trap (Tab cycles through focusable elements within modal)
- ✅ Modal focus restoration (Returns focus to trigger element on close)
- ✅ Modal ARIA attributes (`role="dialog"`, `aria-modal="true"`, `aria-labelledby`)
- ✅ `aria-current="page"` on active navigation items
- ✅ `aria-hidden="true"` on decorative icons

**Additional Fixes Completed:**
- ✅ Fixed favicon path (`icon-64.png` → `favicon.png`) in all templates
- ✅ Fixed language selector checkmarks (Boxicons CSS conflict - use `opacity-0/opacity-100` instead of `hidden`)
- ✅ Added `gap-3` to language list items for proper spacing
- ✅ Made "selected-major-display" darker (`bg-gray-800` with border)

### Phase 2 (Performance) - 2-3 Days ✅ COMPLETED

| Task | Effort | Impact | Status |
|------|--------|--------|--------|
| Static TailwindCSS build | 4 hours | Very High | ✅ DONE (16KB vs 3MB) |
| ES modules migration | 4 hours | Medium | Pending |
| Non-blocking install progress | 4 hours | High | Pending |
| Loading states with spinners | 2 hours | High | ✅ DONE |

**TailwindCSS Build Details:**
- Created `tailwind.config.js` with safelist for dynamic classes
- Added JS files to content scanning (not just HTML templates)
- Safelist includes: `bg-orange-600`, `bg-blue-600`, `bg-green-600`, etc. for codec badges
- Output: `internal/webassets/static/css/tailwind.min.css` (~16KB)
- Make targets: `make tailwind`, `make tailwind-watch`

**Loading States Implementation:**
Added spinners to all async operations in the Web UI:
- Config page: Apply Settings buttons (both top and bottom)
- Hardware Acceleration page: Apply Settings button
- Languages page: Save Preferences button
- Apps page: File Associations Install/Remove buttons
- Logs page: Refresh button
- Modal system: Confirm button during actions

**Implementation patterns:**
- JavaScript fetch(): Disable button, show `<i class="bx bx-loader-alt bx-spin"></i>`, restore on error
- HTMX buttons: Use `hx-on::before-request` and `hx-on::after-request` to toggle `bx-spin` class

**Key Discovery:** Boxicons CSS sets display properties that override Tailwind's `.hidden` class. Solution: use `opacity-0` and `opacity-100` for toggling visibility instead.

### Phase 3 (Enhancement) - 3-5 Days

| Task | Effort | Impact |
|------|--------|--------|
| TUI theme system | 4 hours | Medium |
| TUI progress indicators | 2 hours | Medium |
| Drag-and-drop lists | 4 hours | Medium |
| Responsive sidebar | 4 hours | Medium |
| Toast notifications | 2 hours | Medium |

### Phase 4 (Polish) - 2-3 Days

| Task | Effort | Impact |
|------|--------|--------|
| Two-column TUI layout | 4 hours | Low |
| Breadcrumb navigation | 2 hours | Low |
| Global search | 6 hours | Low |
| Loading skeletons | 2 hours | Low |

---

## Visual Reference

### TUI Color Scheme (Proposed)

```
Primary:    #7c3aed  (Purple)
Secondary:  #4f46e5  (Indigo)
Success:    #10b981  (Green)
Error:      #ef4444  (Red)
Warning:    #f59e0b  (Amber)
Info:       #06b6d4  (Cyan)
Muted:      #6b7280  (Gray)
```

### Web UI Component Examples

```css
/* Buttons */
.btn-primary { @apply px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white rounded-lg; }
.btn-secondary { @apply px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg; }
.btn-danger { @apply px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg; }

/* Cards */
.card { @apply bg-gray-800 rounded-xl p-6 border border-gray-700; }
.card-hover { @apply hover:border-primary-500 transition-colors cursor-pointer; }

/* Inputs */
.input { @apply w-full px-4 py-2 bg-gray-900 border border-gray-700 rounded-lg 
               focus:border-primary-500 focus:ring-1 focus:ring-primary-500; }
```

---

## Testing Checklist

### TUI Testing

- [ ] Test on various terminal sizes (80x24, 120x30, 200x50)
- [ ] Test with both dark and light terminal backgrounds
- [ ] Test keyboard navigation (including vim keys)
- [ ] Test screen reader compatibility (Orca, VoiceOver)
- [ ] Test progress indicators during long operations

### Web UI Testing

- [ ] Test responsive design (320px to 1920px width)
- [ ] Test keyboard navigation (Tab, Enter, Escape)
- [ ] Test with screen reader (NVDA, VoiceOver)
- [ ] Test focus management in modals
- [ ] Test color contrast ratios (WCAG AA minimum)
- [ ] Test dark/light mode persistence
- [ ] Test touch interactions on mobile
