diff --git a/static/css/main.css b/static/css/main.css
new file mode 100644
index 00000000..b2c3455f
--- /dev/null
+++ b/static/css/main.css
@@ -0,0 +1,279 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
+
+/* Tailwind CSS Base */
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* Custom CSS Variables for Dark Theme */
+:root {
+ --bg-primary: #030712; /* gray-950 */
+ --bg-secondary: #111827; /* gray-900 */
+ --bg-tertiary: #1f2937; /* gray-800 */
+ --text-primary: #f3f4f6; /* gray-100 */
+ --text-secondary: #9ca3af; /* gray-400 */
+ --text-muted: #6b7280; /* gray-500 */
+ --border-color: #1f2937; /* gray-800 */
+ --accent-blue: #3b82f6; /* blue-500 */
+ --accent-purple: #8b5cf6; /* purple-500 */
+ --accent-green: #10b981; /* green-500 */
+ --accent-red: #ef4444; /* red-500 */
+}
+
+/* Base Styles */
+body {
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ background-color: var(--bg-primary);
+ color: var(--text-primary);
+}
+
+/* Scrollbar Styling */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--bg-secondary);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--bg-tertiary);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: #374151;
+}
+
+/* HTMX Loading Indicators */
+.htmx-indicator {
+ display: none;
+}
+
+.htmx-request .htmx-indicator {
+ display: inline-block;
+}
+
+.htmx-request.htmx-indicator {
+ display: inline-block;
+}
+
+/* Transition Classes */
+.transition-all-300 {
+ transition: all 300ms ease;
+}
+
+/* Custom Component Styles */
+
+/* Player Progress Bar */
+input[type="range"] {
+ -webkit-appearance: none;
+ appearance: none;
+ background: transparent;
+}
+
+input[type="range"]::-webkit-slider-track {
+ background: #374151;
+ height: 4px;
+ border-radius: 2px;
+}
+
+input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ background: var(--accent-blue);
+ cursor: pointer;
+ margin-top: -4px;
+ opacity: 0;
+ transition: opacity 200ms;
+}
+
+input[type="range"]:hover::-webkit-slider-thumb {
+ opacity: 1;
+}
+
+/* Track List Hover Effects */
+.track-item {
+ transition: background-color 150ms ease;
+}
+
+.track-item:hover {
+ background-color: var(--bg-tertiary);
+}
+
+.track-item:hover .track-actions {
+ opacity: 1;
+}
+
+/* Album Grid */
+.album-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+ gap: 1.5rem;
+}
+
+.album-card {
+ background: var(--bg-secondary);
+ border-radius: 8px;
+ padding: 1rem;
+ transition: transform 150ms ease, box-shadow 150ms ease;
+ cursor: pointer;
+}
+
+.album-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
+}
+
+.album-card img {
+ width: 100%;
+ height: auto;
+ aspect-ratio: 1;
+ object-fit: cover;
+ border-radius: 4px;
+ margin-bottom: 0.75rem;
+}
+
+/* Now Playing Animation */
+@keyframes music-bars {
+ 0%, 100% {
+ height: 3px;
+ }
+ 50% {
+ height: 12px;
+ }
+}
+
+.music-bars {
+ display: flex;
+ align-items: flex-end;
+ gap: 2px;
+ height: 12px;
+}
+
+.music-bars span {
+ width: 3px;
+ background: var(--accent-blue);
+ animation: music-bars 1s ease-in-out infinite;
+ transform-origin: bottom;
+}
+
+.music-bars span:nth-child(2) {
+ animation-delay: 0.2s;
+}
+
+.music-bars span:nth-child(3) {
+ animation-delay: 0.4s;
+}
+
+/* Modal Backdrop */
+.modal-backdrop {
+ background: rgba(0, 0, 0, 0.8);
+ backdrop-filter: blur(4px);
+}
+
+/* Context Menu */
+.context-menu {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+ min-width: 180px;
+}
+
+.context-menu-item {
+ padding: 0.5rem 1rem;
+ transition: background-color 150ms;
+ cursor: pointer;
+}
+
+.context-menu-item:hover {
+ background: var(--bg-tertiary);
+}
+
+/* Toast Animations */
+@keyframes slide-in {
+ from {
+ transform: translateX(100%);
+ opacity: 0;
+ }
+ to {
+ transform: translateX(0);
+ opacity: 1;
+ }
+}
+
+@keyframes slide-out {
+ from {
+ transform: translateX(0);
+ opacity: 1;
+ }
+ to {
+ transform: translateX(100%);
+ opacity: 0;
+ }
+}
+
+.toast-enter {
+ animation: slide-in 300ms ease-out;
+}
+
+.toast-leave {
+ animation: slide-out 200ms ease-in;
+}
+
+/* Skeleton Loading */
+.skeleton {
+ background: linear-gradient(
+ 90deg,
+ var(--bg-tertiary) 0%,
+ #374151 50%,
+ var(--bg-tertiary) 100%
+ );
+ background-size: 200% 100%;
+ animation: skeleton-loading 1.5s ease-in-out infinite;
+ border-radius: 4px;
+}
+
+@keyframes skeleton-loading {
+ 0% {
+ background-position: 200% 0;
+ }
+ 100% {
+ background-position: -200% 0;
+ }
+}
+
+/* Utility Classes */
+.no-scrollbar::-webkit-scrollbar {
+ display: none;
+}
+
+.no-scrollbar {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+}
+
+.text-gradient {
+ background: linear-gradient(135deg, var(--accent-blue) 0%, var(--accent-purple) 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+
+/* Responsive Utilities */
+@media (max-width: 768px) {
+ .album-grid {
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ }
+}
+
+@media (max-width: 640px) {
+ .album-grid {
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ gap: 1rem;
+ }
+}
\ No newline at end of file
diff --git a/static/js/app.js b/static/js/app.js
new file mode 100644
index 00000000..9eec47a5
--- /dev/null
+++ b/static/js/app.js
@@ -0,0 +1,314 @@
+// mt Music Player - HTMX Frontend JavaScript
+// Handles client-side interactions, Alpine.js integrations, and HTMX enhancements
+
+// Toast notification system
+function showToast(message, type = 'info', duration = 3000) {
+ const toastContainer = document.getElementById('toast-container');
+ if (!toastContainer) return;
+
+ const toastId = 'toast-' + Date.now();
+ const toastHTML = `
+
+
$el.remove(), 200)"
+ class="bg-gray-900 border border-gray-800 rounded-lg shadow-lg p-4 min-w-[300px] max-w-md cursor-pointer">
+
+
+
+ ${getToastIcon(type)}
+
+
+
$el.remove(), 200)"
+ class="flex-shrink-0 p-1 rounded hover:bg-gray-800 transition-colors">
+
+
+
+
+
+ `;
+
+ toastContainer.insertAdjacentHTML('beforeend', toastHTML);
+
+ // Auto-remove after animation
+ setTimeout(() => {
+ const toast = document.getElementById(toastId);
+ if (toast) toast.remove();
+ }, duration + 500);
+}
+
+function getToastIcon(type) {
+ switch (type) {
+ case 'success':
+ return ' ';
+ case 'error':
+ return ' ';
+ case 'warning':
+ return ' ';
+ default:
+ return ' ';
+ }
+}
+
+// Modal management
+function showModal(content, title = '') {
+ const modalContainer = document.getElementById('modal-container');
+ if (!modalContainer) return;
+
+ const modalHTML = `
+
+
+
+
+
+
+
+ ${title ? `
+
+
${title}
+ $el.remove(), 200)"
+ class="p-1 rounded hover:bg-gray-800 transition-colors">
+
+
+
+ ` : ''}
+
+
+ ${content}
+
+
+
+
+ `;
+
+ modalContainer.innerHTML = modalHTML;
+}
+
+function closeModal() {
+ const modalContainer = document.getElementById('modal-container');
+ if (modalContainer) {
+ modalContainer.innerHTML = '';
+ }
+}
+
+// Keyboard shortcuts
+document.addEventListener('keydown', function(event) {
+ // Don't trigger shortcuts when typing in inputs
+ if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
+ return;
+ }
+
+ switch (event.key) {
+ case ' ':
+ // Spacebar - toggle play/pause
+ event.preventDefault();
+ htmx.ajax('POST', '/api/player/toggle', { swap: 'none' });
+ break;
+ case 'ArrowLeft':
+ // Left arrow - previous track
+ if (event.ctrlKey || event.metaKey) {
+ event.preventDefault();
+ htmx.ajax('POST', '/api/player/previous', { swap: 'none' });
+ }
+ break;
+ case 'ArrowRight':
+ // Right arrow - next track
+ if (event.ctrlKey || event.metaKey) {
+ event.preventDefault();
+ htmx.ajax('POST', '/api/player/next', { swap: 'none' });
+ }
+ break;
+ case 'ArrowUp':
+ // Up arrow - increase volume
+ if (event.ctrlKey || event.metaKey) {
+ event.preventDefault();
+ // Volume up logic would go here
+ }
+ break;
+ case 'ArrowDown':
+ // Down arrow - decrease volume
+ if (event.ctrlKey || event.metaKey) {
+ event.preventDefault();
+ // Volume down logic would go here
+ }
+ break;
+ case 'm':
+ case 'M':
+ // M - toggle mute
+ if (event.ctrlKey || event.metaKey) {
+ event.preventDefault();
+ htmx.ajax('POST', '/api/player/mute', { swap: 'none' });
+ }
+ break;
+ case 's':
+ case 'S':
+ // S - toggle shuffle
+ if (event.ctrlKey || event.metaKey) {
+ event.preventDefault();
+ htmx.ajax('POST', '/api/player/shuffle', { swap: 'none' });
+ }
+ break;
+ case 'r':
+ case 'R':
+ // R - toggle repeat
+ if (event.ctrlKey || event.metaKey) {
+ event.preventDefault();
+ htmx.ajax('POST', '/api/player/repeat', { swap: 'none' });
+ }
+ break;
+ }
+});
+
+// HTMX event handlers
+document.addEventListener('DOMContentLoaded', function() {
+ // HTMX configuration
+ htmx.config.globalViewTransitions = true;
+ htmx.config.useTemplateFragments = true;
+
+ // Custom event listeners
+ document.body.addEventListener('show-toast', function(event) {
+ const { message, type, duration } = event.detail;
+ showToast(message, type, duration);
+ });
+
+ document.body.addEventListener('show-modal', function(event) {
+ const { content, title } = event.detail;
+ showModal(content, title);
+ });
+
+ document.body.addEventListener('close-modal', function() {
+ closeModal();
+ });
+
+ // Track selection and drag/drop
+ let draggedElement = null;
+
+ document.addEventListener('dragstart', function(event) {
+ if (event.target.closest('[draggable="true"]')) {
+ draggedElement = event.target.closest('[draggable="true"]');
+ draggedElement.classList.add('opacity-50');
+ }
+ });
+
+ document.addEventListener('dragend', function(event) {
+ if (draggedElement) {
+ draggedElement.classList.remove('opacity-50');
+ draggedElement = null;
+ }
+ });
+
+ // Progress bar interaction
+ document.addEventListener('input', function(event) {
+ if (event.target.id === 'progress-bar') {
+ const value = event.target.value;
+ // Update progress visually
+ const progressFill = event.target.nextElementSibling;
+ if (progressFill) {
+ progressFill.style.width = value + '%';
+ }
+ }
+ });
+
+ // Volume slider interaction
+ document.addEventListener('input', function(event) {
+ if (event.target.id === 'volume-slider') {
+ const value = event.target.value;
+ // Update volume visually
+ const volumeFill = event.target.nextElementSibling;
+ if (volumeFill) {
+ volumeFill.style.width = value + '%';
+ }
+ }
+ });
+});
+
+// Utility functions
+function formatDuration(seconds) {
+ if (!seconds || seconds === 0) return '0:00';
+
+ const minutes = Math.floor(seconds / 60);
+ const remainingSeconds = Math.floor(seconds % 60);
+
+ return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
+}
+
+function debounce(func, wait) {
+ let timeout;
+ return function executedFunction(...args) {
+ const later = () => {
+ clearTimeout(timeout);
+ func(...args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ };
+}
+
+// Search functionality
+const searchInput = document.querySelector('input[name="q"]');
+if (searchInput) {
+ searchInput.addEventListener('input', debounce(function(event) {
+ const query = event.target.value.trim();
+ if (query.length > 2) {
+ // Trigger search
+ htmx.ajax('GET', `/api/search?q=${encodeURIComponent(query)}`, {
+ target: '#search-results',
+ swap: 'innerHTML'
+ });
+ } else {
+ // Clear results
+ const results = document.getElementById('search-results');
+ if (results) results.innerHTML = '';
+ }
+ }, 300));
+}
+
+// Theme management
+function setTheme(theme) {
+ document.documentElement.setAttribute('data-theme', theme);
+ localStorage.setItem('theme', theme);
+
+ // Update Alpine.js data
+ if (window.Alpine && Alpine.store) {
+ Alpine.store('theme', theme);
+ }
+}
+
+// Initialize theme on load
+const savedTheme = localStorage.getItem('theme') || 'dark';
+setTheme(savedTheme);
+
+// Export functions for global use
+window.showToast = showToast;
+window.showModal = showModal;
+window.closeModal = closeModal;
+window.formatDuration = formatDuration;
+window.setTheme = setTheme;
\ No newline at end of file
diff --git a/templates/README.md b/templates/README.md
new file mode 100644
index 00000000..07161cab
--- /dev/null
+++ b/templates/README.md
@@ -0,0 +1,190 @@
+# HTMX Base Templates
+
+This directory contains the foundational HTMX templates and partials for the mt music player web interface, designed to match the MusicBee-inspired UI.
+
+## Structure
+
+```
+templates/
+├── base/
+│ └── base.html # Main application layout with three-panel design
+├── components/
+│ ├── navbar.html # Top navigation bar with search and user menu
+│ ├── sidebar.html # Left sidebar with library navigation and playlists
+│ ├── player-controls.html # Bottom player controls with progress and volume
+│ └── queue.html # Right panel queue management
+├── pages/
+│ └── library.html # Library view page extending base template
+└── partials/
+ ├── modal.html # Reusable modal dialog template
+ ├── toast.html # Toast notification template
+ └── track-item.html # Individual track item component
+```
+
+## Features
+
+### Three-Panel Layout
+- **Left Sidebar**: Library navigation, playlists, and library statistics
+- **Center Panel**: Main content area with HTMX-powered dynamic loading
+- **Right Panel**: Queue management with drag-and-drop reordering
+
+### UI Components
+- **Navigation**: Responsive navbar with search, theme toggle, and user menu
+- **Player Controls**: Full-featured audio controls with progress bar and volume
+- **Queue Management**: Interactive queue with drag-and-drop, history, and suggestions
+- **Modals**: Flexible modal system for dialogs and forms
+- **Toast Notifications**: Non-intrusive notification system
+
+### Design System
+- **Basecoat UI**: Custom design system with dark theme
+- **Tailwind CSS**: Utility-first CSS framework
+- **Inter Font**: Modern, readable typography
+- **Bootstrap Icons**: Consistent iconography
+
+### HTMX Integration
+- **Dynamic Content Loading**: Seamless page updates without full reloads
+- **WebSocket Support**: Real-time updates via Server-Sent Events
+- **Form Handling**: Progressive enhancement with HTMX
+- **Loading States**: Built-in loading indicators and transitions
+
+### Alpine.js Enhancements
+- **Reactive UI**: State management for interactive components
+- **Transitions**: Smooth animations and micro-interactions
+- **Keyboard Shortcuts**: Global keyboard navigation
+- **Drag & Drop**: Native drag-and-drop for queue management
+
+## Dependencies
+
+### Frontend
+- **HTMX 1.9.10**: For dynamic content and interactions
+- **Alpine.js 3.13.5**: For reactive UI components
+- **Tailwind CSS**: For styling and layout
+- **Inter Font**: For typography
+- **Bootstrap Icons**: For icons
+
+### Backend Requirements
+The templates expect the following backend endpoints:
+
+#### Player API
+- `POST /api/player/toggle` - Toggle play/pause
+- `POST /api/player/next` - Next track
+- `POST /api/player/previous` - Previous track
+- `POST /api/player/seek` - Seek to position
+- `POST /api/player/volume` - Set volume
+- `POST /api/player/mute` - Toggle mute
+- `POST /api/player/shuffle` - Toggle shuffle
+- `POST /api/player/repeat` - Cycle repeat mode
+
+#### Queue API
+- `GET /api/queue/tracks` - Get queue tracks
+- `POST /api/queue/add/{track_id}` - Add track to queue
+- `POST /api/queue/reorder` - Reorder queue items
+- `POST /api/queue/clear` - Clear queue
+- `POST /api/queue/shuffle` - Shuffle queue
+
+#### Library API
+- `GET /api/library/tracks` - Get library tracks
+- `GET /api/library/stats` - Get library statistics
+- `POST /api/library/scan` - Scan for new music
+
+#### Search API
+- `GET /api/search?q={query}` - Search tracks
+
+## Template Filters
+
+Custom Jinja2 filters are provided in `filters.py`:
+
+- `format_duration(seconds)` - Format seconds to MM:SS
+- `format_file_size(bytes)` - Format bytes to human readable
+- `pluralize(count, singular, plural)` - Pluralize words
+
+## Usage
+
+### Extending Base Template
+
+```html
+{% extends "base/base.html" %}
+
+{% block title %}Page Title - mt music player{% endblock %}
+
+{% block content %}
+
+{% endblock %}
+```
+
+### Including Partials
+
+```html
+{% include "partials/modal.html" %}
+{% include "components/navbar.html" %}
+```
+
+### HTMX Patterns
+
+```html
+
+
+ Loading...
+
+
+
+
+
+
+
+ Perform Action
+
+```
+
+## Customization
+
+### Theme Variables
+CSS custom properties are defined in `static/css/main.css`:
+
+```css
+:root {
+ --bg-primary: #030712;
+ --bg-secondary: #111827;
+ --text-primary: #f3f4f6;
+ --accent-blue: #3b82f6;
+ /* ... */
+}
+```
+
+### Responsive Design
+Templates include responsive breakpoints:
+- Mobile: < 640px
+- Tablet: 640px - 768px
+- Desktop: > 768px
+
+### Accessibility
+- Semantic HTML structure
+- Keyboard navigation support
+- Screen reader friendly
+- Focus management in modals
+
+## Development
+
+### File Organization
+- Keep templates modular and reusable
+- Use consistent naming conventions
+- Document template variables and blocks
+- Include loading states for dynamic content
+
+### Performance
+- Minimize CSS and JavaScript bundle size
+- Use HTMX for efficient content updates
+- Implement lazy loading for large lists
+- Cache static assets appropriately
+
+## Browser Support
+
+- Chrome 90+
+- Firefox 88+
+- Safari 14+
+- Edge 90+
+
+Requires modern browser features like CSS Grid, Flexbox, and ES6 modules.
\ No newline at end of file
diff --git a/templates/base/base.html b/templates/base/base.html
new file mode 100644
index 00000000..a017a1f7
--- /dev/null
+++ b/templates/base/base.html
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+ {% block title %}mt music player{% endblock %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% block styles %}{% endblock %}
+
+
+
+
+
+
+
+
+
+
+
+ {% include 'components/navbar.html' %}
+
+
+
+
+
+
+
+ {% block sidebar %}
+ {% include 'components/sidebar.html' %}
+ {% endblock %}
+
+
+
+
+
+ {% block content %}{% endblock %}
+
+
+
+
+
+ {% block queue %}
+ {% include 'components/queue.html' %}
+ {% endblock %}
+
+
+
+
+
+ {% include 'components/player-controls.html' %}
+
+
+
+
+
+
+
+
+
+
+
+
+ {% block scripts %}{% endblock %}
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/components/navbar.html b/templates/components/navbar.html
new file mode 100644
index 00000000..c18ce0a7
--- /dev/null
+++ b/templates/components/navbar.html
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/components/player-controls.html b/templates/components/player-controls.html
new file mode 100644
index 00000000..775af1b7
--- /dev/null
+++ b/templates/components/player-controls.html
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0:00
+
+
+
+
+
+
+
+ 0:00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/components/queue.html b/templates/components/queue.html
new file mode 100644
index 00000000..89f433e3
--- /dev/null
+++ b/templates/components/queue.html
@@ -0,0 +1,110 @@
+
+
+
+
+
Queue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0 tracks
+
+
+ 0:00
+
+
+
+
+
+
+
+
+
+
+
+
+
Queue is empty
+
Add tracks to start playing
+
+
+
+
+
+
+
+
+
+ History
+
+
+
+ Suggest
+
+
+
+
\ No newline at end of file
diff --git a/templates/components/sidebar.html b/templates/components/sidebar.html
new file mode 100644
index 00000000..36ffc622
--- /dev/null
+++ b/templates/components/sidebar.html
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
Playlists
+
+
+
+
+
+
+
+
+
+
+
Loading playlists...
+
+
+
+
+
+
+
+
+ Library Size
+
+ --
+
+
+
+ Total Tracks
+
+ --
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/filters.py b/templates/filters.py
new file mode 100644
index 00000000..67239bc5
--- /dev/null
+++ b/templates/filters.py
@@ -0,0 +1,58 @@
+"""
+Template filters for HTMX base templates.
+These would typically be registered with your template engine (Jinja2, etc.)
+"""
+
+
+def format_duration(seconds):
+ """
+ Format duration in seconds to MM:SS format
+ """
+ if not seconds or seconds == 0:
+ return "0:00"
+
+ try:
+ seconds = int(seconds)
+ minutes = seconds // 60
+ remaining_seconds = seconds % 60
+ return f"{minutes}:{remaining_seconds:02d}"
+ except (ValueError, TypeError):
+ return "0:00"
+
+
+def format_file_size(bytes_size):
+ """
+ Format file size in bytes to human readable format
+ """
+ if not bytes_size:
+ return "0 B"
+
+ try:
+ bytes_size = int(bytes_size)
+ for unit in ['B', 'KB', 'MB', 'GB']:
+ if bytes_size < 1024.0:
+ return ".1f"
+ bytes_size /= 1024.0
+ return ".1f"
+ except (ValueError, TypeError):
+ return "0 B"
+
+
+def pluralize(count, singular, plural=None):
+ """
+ Return singular or plural form based on count
+ """
+ if plural is None:
+ plural = singular + 's'
+
+ try:
+ count = int(count)
+ return singular if count == 1 else plural
+ except (ValueError, TypeError):
+ return plural
+
+
+# Example usage in templates:
+# {{ track.duration|format_duration }}
+# {{ file.size|format_file_size }}
+# {{ count|pluralize('track', 'tracks') }}
diff --git a/templates/pages/library.html b/templates/pages/library.html
new file mode 100644
index 00000000..c1b3a328
--- /dev/null
+++ b/templates/pages/library.html
@@ -0,0 +1,145 @@
+{% extends "base/base.html" %}
+
+{% block title %}Library - mt music player{% endblock %}
+
+{% block content %}
+
+
+
+
+
Music Library
+
+
+
+
+
+ Grid
+
+
+ List
+
+
+
+
+
+
+
+ Sort
+
+
+
+
+ Title
+
+
+ Artist
+
+
+ Album
+
+
+ Date Added
+
+
+ Play Count
+
+
+
+
+
+
+ Filter
+
+
+
+
+
+
+ {{ total_tracks }} tracks
+ {{ total_albums }} albums
+ {{ total_artists }} artists
+ {{ total_duration|format_duration }} total
+
+
+
+
+
+
+
+
+
+ Showing {{ start }}-{{ end }} of {{ total_tracks }} tracks
+
+
+
+
+ Previous
+
+
+
+
+ {% for page_num in page_numbers %}
+
+ {{ page_num }}
+
+ {% endfor %}
+
+
+
+ Next
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/templates/partials/modal.html b/templates/partials/modal.html
new file mode 100644
index 00000000..8377d66e
--- /dev/null
+++ b/templates/partials/modal.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ modal_title|default('Modal') }}
+ $el.remove(), 200)"
+ class="p-1 rounded hover:bg-gray-800 transition-colors">
+
+
+
+
+
+
+ {% block modal_content %}
+
+ {% endblock %}
+
+
+
+ {% block modal_footer %}
+ {% if show_footer|default(true) %}
+
+ $el.remove(), 200)"
+ class="px-4 py-2 bg-gray-800 hover:bg-gray-700 rounded transition-colors">
+ Cancel
+
+ {% block modal_actions %}
+
+ {% endblock %}
+
+ {% endif %}
+ {% endblock %}
+
+
+
\ No newline at end of file
diff --git a/templates/partials/toast.html b/templates/partials/toast.html
new file mode 100644
index 00000000..cff6edff
--- /dev/null
+++ b/templates/partials/toast.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+ {% if type == 'success' %}
+
+ {% elif type == 'error' %}
+
+ {% elif type == 'warning' %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+
+ {% if title %}
+
{{ title }}
+ {% endif %}
+
{{ message }}
+
+
+
+
+
+
+
+
+
+ {% if show_progress|default(false) %}
+
+ {% endif %}
+
\ No newline at end of file
diff --git a/templates/partials/track-item.html b/templates/partials/track-item.html
new file mode 100644
index 00000000..5c2e1a7a
--- /dev/null
+++ b/templates/partials/track-item.html
@@ -0,0 +1,103 @@
+
+
+
+
+
+ {{ track.position|default(loop.index) }}
+
+
+
+
+
+
+
+ {% if track.album_art %}
+
+ {% else %}
+
+
+
+ {% endif %}
+
+
+
+
+
+
{{ track.title }}
+ {% if track.is_playing %}
+
+ {% endif %}
+
+
+ {{ track.artist }}
+ {% if track.album %}
+ • {{ track.album }}
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Details
+
+
+ Add to Playlist
+
+
+ Start Radio
+
+
+
+ Remove
+
+
+
+
+
+
+
+ {{ track.duration|format_duration }}
+
+
\ No newline at end of file