338 lines
No EOL
20 KiB
HTML
338 lines
No EOL
20 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Monochrome Music</title>
|
|
<meta name="theme-color" content="#000000" />
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<meta name="apple-mobile-web-app-title" content="Monochrome">
|
|
<meta name="description" content="A minimalist music streaming application">
|
|
<link rel="apple-touch-icon" href="https://prigoana.com/favicon.png">
|
|
<link rel="manifest" href="/manifest.json">
|
|
<link rel="icon" href="https://prigoana.com/favicon.png" type="image/png">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="styles.css">
|
|
</head>
|
|
<body>
|
|
<audio id="audio-player"></audio>
|
|
<div id="context-menu">
|
|
<ul>
|
|
<li data-action="add-to-queue">Add to Queue</li>
|
|
<li data-action="download">Download</li>
|
|
</ul>
|
|
</div>
|
|
<div id="queue-modal-overlay" style="display: none;">
|
|
<div id="queue-modal">
|
|
<div id="queue-modal-header">
|
|
<h3>Queue</h3>
|
|
<button id="close-queue-btn">×</button>
|
|
</div>
|
|
<div id="queue-list"></div>
|
|
</div>
|
|
</div>
|
|
<div id="sidebar-overlay"></div>
|
|
|
|
<div class="app-container">
|
|
<aside class="sidebar">
|
|
<div>
|
|
<div class="sidebar-logo">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M9 18V5l12-2v13"></path>
|
|
<circle cx="6" cy="18" r="3"></circle>
|
|
<circle cx="18" cy="16" r="3"></circle>
|
|
</svg>
|
|
<span>Monochrome</span>
|
|
</div>
|
|
<nav class="sidebar-nav">
|
|
<ul>
|
|
<li class="nav-item">
|
|
<a href="#home">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
|
|
<polyline points="9 22 9 12 15 12 15 22"></polyline>
|
|
</svg>
|
|
<span>Home</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="#settings">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 0 2l-.15.08a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l-.22-.38a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1 0 2l.15-.08a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path>
|
|
<circle cx="12" cy="12" r="3"></circle>
|
|
</svg>
|
|
<span>Settings</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</aside>
|
|
|
|
<main class="main-content">
|
|
<header class="main-header">
|
|
<button class="hamburger-menu" id="hamburger-btn" title="Open navigation">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<line x1="3" y1="12" x2="21" y2="12"></line>
|
|
<line x1="3" y1="6" x2="21" y2="6"></line>
|
|
<line x1="3" y1="18" x2="21" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
<form class="search-bar" id="search-form">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="11" cy="11" r="8"></circle>
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
</svg>
|
|
<input type="search" id="search-input" placeholder="Search for tracks, artists, albums...">
|
|
</form>
|
|
</header>
|
|
|
|
<div id="page-home" class="page">
|
|
<section class="content-section">
|
|
<h2 class="section-title">Recent Albums</h2>
|
|
<div class="card-grid" id="home-recent-albums"></div>
|
|
</section>
|
|
<section class="content-section">
|
|
<h2 class="section-title">Recent Artists</h2>
|
|
<div class="card-grid" id="home-recent-artists"></div>
|
|
</section>
|
|
</div>
|
|
|
|
<div id="page-search" class="page">
|
|
<h2 class="section-title" id="search-results-title">Search Results</h2>
|
|
<div class="search-tabs">
|
|
<button class="search-tab active" data-tab="tracks">Tracks</button>
|
|
<button class="search-tab" data-tab="albums">Albums</button>
|
|
<button class="search-tab" data-tab="artists">Artists</button>
|
|
</div>
|
|
<div class="search-tab-content active" id="search-tab-tracks">
|
|
<div class="track-list" id="search-tracks-container"></div>
|
|
</div>
|
|
<div class="search-tab-content" id="search-tab-albums">
|
|
<div class="card-grid" id="search-albums-container"></div>
|
|
</div>
|
|
<div class="search-tab-content" id="search-tab-artists">
|
|
<div class="card-grid" id="search-artists-container"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="page-album" class="page">
|
|
<header class="detail-header">
|
|
<img id="album-detail-image" src="" alt="" class="detail-header-image">
|
|
<div class="detail-header-info">
|
|
<div class="type">Album</div>
|
|
<h1 class="title" id="album-detail-title"></h1>
|
|
<div class="meta" id="album-detail-meta"></div>
|
|
</div>
|
|
</header>
|
|
<div class="track-list" id="album-detail-tracklist"></div>
|
|
</div>
|
|
|
|
<div id="page-artist" class="page">
|
|
<header class="detail-header">
|
|
<img id="artist-detail-image" src="" alt="Artist" class="detail-header-image artist">
|
|
<div class="detail-header-info">
|
|
<div class="type">Artist</div>
|
|
<h1 class="title" id="artist-detail-name"></h1>
|
|
<div class="meta" id="artist-detail-meta"></div>
|
|
</div>
|
|
</header>
|
|
<section class="content-section">
|
|
<h2 class="section-title">Popular Tracks</h2>
|
|
<div class="track-list" id="artist-detail-tracks"></div>
|
|
</section>
|
|
<section class="content-section">
|
|
<h2 class="section-title">Albums</h2>
|
|
<div class="card-grid" id="artist-detail-albums"></div>
|
|
</section>
|
|
</div>
|
|
|
|
<div id="page-settings" class="page">
|
|
<h2 class="section-title">Settings</h2>
|
|
<div class="settings-list">
|
|
<div class="setting-item">
|
|
<div class="info">
|
|
<span class="label">Audio Quality</span>
|
|
<span class="description">Set to LOSSLESS by default.</span>
|
|
</div>
|
|
</div>
|
|
<div class="setting-item">
|
|
<div class="info">
|
|
<span class="label">Crossfade</span>
|
|
<span class="description">Allow songs to fade into each other.</span>
|
|
</div>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox" checked>
|
|
<span class="slider"></span>
|
|
</label>
|
|
</div>
|
|
<div class="setting-item">
|
|
<div class="info">
|
|
<span class="label">Gapless Playback</span>
|
|
<span class="description">Play audio without interruption between tracks.</span>
|
|
</div>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox" checked>
|
|
<span class="slider"></span>
|
|
</label>
|
|
</div>
|
|
<div class="setting-item">
|
|
<div class="info">
|
|
<span class="label">Normalize Volume</span>
|
|
<span class="description">Set the same volume level for all tracks.</span>
|
|
</div>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox">
|
|
<span class="slider"></span>
|
|
</label>
|
|
</div>
|
|
<div class="setting-item">
|
|
<div class="info">
|
|
<span class="label">Cache</span>
|
|
<span class="description" id="cache-info">Stores API responses to reduce requests.</span>
|
|
</div>
|
|
<button id="clear-cache-btn" class="btn-secondary">Clear Cache</button>
|
|
</div>
|
|
<div id="api-instance-manager">
|
|
<div class="setting-item" style="padding-bottom: 1rem; border: none;">
|
|
<div class="info">
|
|
<span class="label">API Instances</span>
|
|
<span class="description">Manage and prioritize API instances. The app will try them in order if one fails.</span>
|
|
</div>
|
|
</div>
|
|
<ul id="api-instance-list"></ul>
|
|
<form id="add-instance-form">
|
|
<input type="url" id="custom-instance-input" placeholder="https://custom.instance.xyz" required>
|
|
<button type="submit">Add Instance</button>
|
|
</form>
|
|
</div>
|
|
<div id="about-section">
|
|
<div class="setting-item" style="padding-bottom: 1rem; border: none; margin-top: 2rem;">
|
|
<div class="info">
|
|
<span class="label">About Monochrome</span>
|
|
<span class="description">A minimalist, open-source music streaming application</span>
|
|
</div>
|
|
</div>
|
|
<div class="about-content">
|
|
<p class="about-description">
|
|
Monochrome is a lightweight, privacy-focused music streaming client designed for high-fidelity audio playback.
|
|
Built with modern web technologies, it provides a clean, distraction-free listening experience.
|
|
</p>
|
|
<div class="about-features">
|
|
<h4>Features</h4>
|
|
<ul>
|
|
<li>High-quality lossless audio streaming</li>
|
|
<li>Intelligent API caching for improved performance</li>
|
|
<li>Offline-capable Progressive Web App (PWA)</li>
|
|
<li>Media Session API integration for system controls</li>
|
|
<li>Queue management with shuffle and repeat modes</li>
|
|
<li>Track downloads with automatic metadata embedding</li>
|
|
<li>Multiple API instance support with failover</li>
|
|
<li>Dark, minimalist interface optimized for focus</li>
|
|
</ul>
|
|
</div>
|
|
<div class="about-tech">
|
|
<h4>Technology Stack</h4>
|
|
<p>Vanilla JavaScript • ES6 Modules • IndexedDB • Service Workers • Media Session API</p>
|
|
</div>
|
|
<div class="about-links">
|
|
<a href="https://github.com/eduardprigoana/monochrome" target="_blank" rel="noopener noreferrer" class="github-link">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
|
</svg>
|
|
<span>View on GitHub</span>
|
|
</a>
|
|
<a href="https://github.com/eduardprigoana/monochrome/issues" target="_blank" rel="noopener noreferrer" class="github-link">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<circle cx="12" cy="12" r="10"></circle>
|
|
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
</svg>
|
|
<span>Report Issue</span>
|
|
</a>
|
|
</div>
|
|
<div class="about-footer">
|
|
<p class="version">Version 1.0.0</p>
|
|
<p class="disclaimer">This is an independent client and is not affiliated with or endorsed by TIDAL or any music streaming service.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<footer class="now-playing-bar">
|
|
<div class="track-info">
|
|
<img src="https://picsum.photos/seed/placeholder/112" alt="Current Track Cover" class="cover">
|
|
<div class="details">
|
|
<div class="title">Select a song</div>
|
|
<div class="artist"></div>
|
|
</div>
|
|
</div>
|
|
<div class="player-controls">
|
|
<div class="buttons">
|
|
<button id="shuffle-btn" title="Shuffle">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="16 3 21 3 21 8"></polyline>
|
|
<line x1="4" y1="20" x2="21" y2="3"></line>
|
|
<polyline points="16 16 21 16 21 21"></polyline>
|
|
<line x1="15" y1="15" x2="21" y2="21"></line>
|
|
<line x1="4" y1="4" x2="9" y2="9"></line>
|
|
</svg>
|
|
</button>
|
|
<button id="prev-btn" title="Previous">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<polygon points="19 20 9 12 19 4 19 20"></polygon>
|
|
<line x1="5" y1="19" x2="5" y2="5"></line>
|
|
</svg>
|
|
</button>
|
|
<button class="play-pause-btn" title="Play"></button>
|
|
<button id="next-btn" title="Next">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<polygon points="5 4 15 12 5 20 5 4"></polygon>
|
|
<line x1="19" y1="5" x2="19" y2="19"></line>
|
|
</svg>
|
|
</button>
|
|
<button id="repeat-btn" title="Repeat">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="17 1 21 5 17 9"></polyline>
|
|
<path d="M3 11V9a4 4 0 0 1 4-4h14"></path>
|
|
<polyline points="7 23 3 19 7 15"></polyline>
|
|
<path d="M21 13v2a4 4 0 0 1-4 4H3"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="progress-container">
|
|
<span id="current-time">0:00</span>
|
|
<div id="progress-bar" class="progress-bar">
|
|
<div id="progress-fill" class="progress-fill"></div>
|
|
</div>
|
|
<span id="total-duration">0:00</span>
|
|
</div>
|
|
</div>
|
|
<div class="volume-controls">
|
|
<button id="queue-btn" title="Queue">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<line x1="8" y1="6" x2="21" y2="6"></line>
|
|
<line x1="8" y1="12" x2="21" y2="12"></line>
|
|
<line x1="8" y1="18" x2="21" y2="18"></line>
|
|
<line x1="3" y1="6" x2="3.01" y2="6"></line>
|
|
<line x1="3" y1="12" x2="3.01" y2="12"></line>
|
|
<line x1="3" y1="18" x2="3.01" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
<button id="volume-btn" title="Mute"></button>
|
|
<div id="volume-bar" class="volume-bar">
|
|
<div id="volume-fill" class="volume-fill"></div>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
|
|
<script type="module" src="js/app.js"></script>
|
|
</body>
|
|
</html> |