4818 lines
275 KiB
HTML
4818 lines
275 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||
<title>Monochrome Music</title>
|
||
<link rel="canonical" href="https://monochrome.tf/" />
|
||
<meta name="theme-color" content="#000000" />
|
||
<meta name="mobile-web-app-capable" content="yes" />
|
||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||
<base href="/" />
|
||
<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" />
|
||
|
||
<!-- Preconnect to critical third-party origins -->
|
||
<link rel="preconnect" href="https://api.fonts.coollabs.io" crossorigin />
|
||
<link rel="preconnect" href="https://ws.audioscrobbler.com" crossorigin />
|
||
<link rel="preconnect" href="https://libre.fm" crossorigin />
|
||
<link rel="preconnect" href="https://api.listenbrainz.org" crossorigin />
|
||
<link rel="preconnect" href="https://resources.tidal.com" crossorigin />
|
||
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
|
||
<link rel="preconnect" href="https://unpkg.com" crossorigin />
|
||
|
||
<link rel="apple-touch-icon" href="/assets/logo.svg" />
|
||
<link rel="manifest" href="/manifest.json" />
|
||
<link rel="icon" href="/assets/logo.svg" type="image/svg+xml" />
|
||
<link
|
||
href="https://api.fonts.coollabs.io/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" crossorigin="anonymous" style="display: none"></audio>
|
||
<video id="video-player" crossorigin="anonymous" style="display: none"></video>
|
||
<div id="context-menu">
|
||
<ul>
|
||
<li data-action="shuffle-play-card" data-type-filter="album,playlist,mix,user-playlist">
|
||
Shuffle play
|
||
</li>
|
||
<li data-action="start-infinite-radio" data-type-filter="track,album,playlist,user-playlist">
|
||
Start Infinite Radio
|
||
</li>
|
||
<li data-action="start-mix" data-type-filter="album,track,video">Start mix</li>
|
||
<li data-action="play-next">Play next</li>
|
||
<li data-action="add-to-queue">Add to queue</li>
|
||
<li
|
||
data-action="toggle-like"
|
||
data-label-track="Like"
|
||
data-label-unlike-track="Unlike"
|
||
data-label-album="Save album to library"
|
||
data-label-unlike-album="Remove album from library"
|
||
data-label-playlist="Save playlist to library"
|
||
data-label-unlike-playlist="Remove playlist from library"
|
||
data-label-video="Like"
|
||
data-label-unlike-video="Unlike"
|
||
>
|
||
Like
|
||
</li>
|
||
<li data-action="toggle-pin" data-type-filter="album,artist,playlist,user-playlist">Pin</li>
|
||
<li data-action="add-to-playlist" data-type-filter="track,video">Add to playlist</li>
|
||
<li data-action="go-to-artist" data-type-filter="track,album,video">Go to artist</li>
|
||
<li data-action="go-to-album" data-type-filter="track,video">Go to album</li>
|
||
<li data-action="copy-link">Copy link</li>
|
||
<li data-action="open-in-new-tab">Open in new tab</li>
|
||
<li data-action="open-in-harmony" data-type-filter="album">Open in Harmony</li>
|
||
<li data-action="track-info" data-type-filter="track,video">Track info</li>
|
||
<li data-action="open-original-url" data-type-filter="track,video">Open original URL</li>
|
||
<li data-action="download">Download</li>
|
||
<li class="separator"></li>
|
||
<li
|
||
data-action="block-track"
|
||
data-type-filter="track"
|
||
data-label-block="Block track"
|
||
data-label-unblock="Unblock track"
|
||
>
|
||
Block track
|
||
</li>
|
||
<li
|
||
data-action="block-album"
|
||
data-type-filter="album,track"
|
||
data-label-block="Block album"
|
||
data-label-unblock="Unblock album"
|
||
>
|
||
Block album
|
||
</li>
|
||
<li
|
||
data-action="block-artist"
|
||
data-type-filter="track,album,artist"
|
||
data-label-block="Block artist"
|
||
data-label-unblock="Unblock artist"
|
||
>
|
||
Block artist
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div id="sort-menu" style="display: none">
|
||
<ul>
|
||
<li data-sort="custom">Playlist Order</li>
|
||
<li data-sort="added-newest" class="requires-added-date">Date Added (Newest)</li>
|
||
<li data-sort="added-oldest" class="requires-added-date">Date Added (Oldest)</li>
|
||
<li data-sort="title">Title (A-Z)</li>
|
||
<li data-sort="artist">Artist (A-Z)</li>
|
||
<li data-sort="album">Album (A-Z)</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div id="side-panel" class="side-panel">
|
||
<div class="panel-header">
|
||
<h3 id="side-panel-title">Panel</h3>
|
||
<div class="panel-controls" id="side-panel-controls">
|
||
<!-- Controls injected dynamically -->
|
||
</div>
|
||
</div>
|
||
<div id="side-panel-content" class="panel-content">
|
||
<!-- Content injected dynamically -->
|
||
</div>
|
||
</div>
|
||
|
||
<div id="fullscreen-cover-overlay" style="display: none">
|
||
<div id="visualizer-container">
|
||
<canvas id="visualizer-canvas"></canvas>
|
||
</div>
|
||
<div class="fullscreen-cover-content">
|
||
<div
|
||
id="fullscreen-video-container"
|
||
style="
|
||
display: none;
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
justify-content: center;
|
||
align-items: center;
|
||
background: black;
|
||
z-index: 0;
|
||
"
|
||
></div>
|
||
<button id="toggle-ui-btn" class="fullscreen-ui-toggle" title="Toggle UI">
|
||
<use svg="!lucide/x.svg" size="24" />
|
||
</button>
|
||
<button id="toggle-fullscreen-lyrics-btn" class="fullscreen-lyrics-toggle" title="Toggle Lyrics">
|
||
<use svg="./images/mic.svg" size="24" />
|
||
</button>
|
||
<button id="close-fullscreen-cover-btn" title="Close">×</button>
|
||
<div class="fullscreen-main-view">
|
||
<img
|
||
id="fullscreen-cover-image"
|
||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||
alt="Album Cover"
|
||
/>
|
||
|
||
<div class="fullscreen-track-info">
|
||
<h2 id="fullscreen-track-title"></h2>
|
||
<h3 id="fullscreen-track-artist"></h3>
|
||
<div class="fullscreen-actions">
|
||
<button id="fs-like-btn" class="btn-icon like-btn" title="Like">
|
||
<use svg="!lucide/heart.svg" class="heart-icon" size="24" />
|
||
</button>
|
||
<button id="fs-add-playlist-btn" class="btn-icon" title="Add to Playlist">
|
||
<use svg="!lucide/square-pen.svg" size="24" />
|
||
</button>
|
||
<button id="fs-download-btn" class="btn-icon" title="Download">
|
||
<use svg="!lucide/download.svg" size="24" />
|
||
</button>
|
||
<button id="fs-cast-btn" class="btn-icon" title="Cast">
|
||
<use svg="!lucide/cast.svg" size="24" />
|
||
</button>
|
||
<button id="fs-queue-btn" class="btn-icon" title="Queue">
|
||
<use svg="!lucide/list.svg" size="24" />
|
||
</button>
|
||
</div>
|
||
<div id="fullscreen-next-track" style="display: none">
|
||
<span class="label">Up Next: </span>
|
||
<span class="value"></span>
|
||
</div>
|
||
</div>
|
||
<div class="fullscreen-controls">
|
||
<div class="fullscreen-progress-container">
|
||
<span id="fs-current-time">0:00</span>
|
||
<div id="fs-progress-bar" class="progress-bar">
|
||
<div id="fs-progress-fill" class="progress-fill"></div>
|
||
</div>
|
||
<span id="fs-total-duration">0:00</span>
|
||
</div>
|
||
<div class="fullscreen-buttons">
|
||
<button id="fs-shuffle-btn" title="Shuffle">
|
||
<use svg="!lucide/shuffle.svg" size="24" />
|
||
</button>
|
||
<button id="fs-prev-btn" title="Previous">
|
||
<use svg="!lucide/arrow-left-to-line.svg" size="24" />
|
||
</button>
|
||
<button id="fs-play-pause-btn" class="play-pause-btn" title="Play">
|
||
<use svg="./images/play-large.svg" size="32" />
|
||
</button>
|
||
<button id="fs-next-btn" title="Next">
|
||
<use svg="!lucide/arrow-right-to-line.svg" size="24" />
|
||
</button>
|
||
<button id="fs-repeat-btn" title="Repeat">
|
||
<use svg="!lucide/repeat.svg" size="24" />
|
||
</button>
|
||
<button id="fs-quality-btn" class="fs-quality-btn" title="Quality" style="display: none">
|
||
<use svg="!lucide/pencil-line.svg" size="20" />
|
||
<span class="fs-quality-label">Auto</span>
|
||
</button>
|
||
<div id="fs-quality-menu" class="fs-quality-menu" style="display: none"></div>
|
||
</div>
|
||
<div class="fullscreen-volume-container">
|
||
<button id="fs-volume-btn" class="fs-volume-btn" title="Mute">
|
||
<use svg="!lucide/volume-1.svg" size="24" />
|
||
</button>
|
||
<div id="fs-volume-bar" class="fs-volume-bar">
|
||
<div id="fs-volume-fill" class="fs-volume-fill"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="playlist-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<h3 id="playlist-modal-title">Create Playlist</h3>
|
||
<input
|
||
type="text"
|
||
id="playlist-name-input"
|
||
class="template-input"
|
||
placeholder="Playlist name"
|
||
style="margin: 1rem 0"
|
||
/>
|
||
<div
|
||
id="playlist-cover-wrapper"
|
||
style="margin: 0.5rem 0; display: flex; gap: 0.5rem; align-items: stretch"
|
||
>
|
||
<input
|
||
type="url"
|
||
id="playlist-cover-input"
|
||
class="template-input"
|
||
placeholder="Cover image URL"
|
||
style="flex: 1; margin: 0; display: none"
|
||
/>
|
||
<input type="file" id="playlist-cover-file-input" accept="image/*" style="display: none" />
|
||
<button
|
||
type="button"
|
||
id="playlist-cover-upload-btn"
|
||
class="template-btn"
|
||
style="
|
||
flex: 1;
|
||
padding: 0.5rem;
|
||
font-size: 0.85rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.5rem;
|
||
"
|
||
>
|
||
<use svg="!lucide/upload.svg" size="16" />
|
||
<span id="playlist-cover-btn-text">Upload</span>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
id="playlist-cover-toggle-url-btn"
|
||
class="template-btn"
|
||
style="padding: 0.5rem; font-size: 0.85rem; white-space: nowrap"
|
||
title="Switch to URL input"
|
||
>
|
||
or URL
|
||
</button>
|
||
</div>
|
||
<div
|
||
id="playlist-cover-upload-status"
|
||
style="display: none; margin: -0.25rem 0 0.5rem 0; font-size: 0.75rem; opacity: 0.8"
|
||
>
|
||
<span id="playlist-cover-upload-text"></span>
|
||
</div>
|
||
<textarea
|
||
id="playlist-description-input"
|
||
class="template-input"
|
||
placeholder="Description (optional)"
|
||
style="margin: 0.5rem 0; min-height: 80px; resize: vertical"
|
||
></textarea>
|
||
<br />
|
||
<div
|
||
id="import-section"
|
||
style="
|
||
display: none;
|
||
margin: 1rem 0;
|
||
padding: 1rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
background: var(--background-secondary);
|
||
"
|
||
>
|
||
<div
|
||
class="import-tabs"
|
||
style="
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
margin-bottom: 1rem;
|
||
border-bottom: 1px solid var(--border);
|
||
padding-bottom: 0.5rem;
|
||
"
|
||
>
|
||
<button
|
||
type="button"
|
||
class="import-tab active"
|
||
data-import-type="csv"
|
||
style="
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--foreground);
|
||
cursor: pointer;
|
||
padding: 0.25rem 0.5rem;
|
||
"
|
||
>
|
||
CSV
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="import-tab"
|
||
data-import-type="jspf"
|
||
style="
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--foreground);
|
||
cursor: pointer;
|
||
padding: 0.25rem 0.5rem;
|
||
opacity: 0.7;
|
||
"
|
||
>
|
||
JSPF
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="import-tab"
|
||
data-import-type="xspf"
|
||
style="
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--foreground);
|
||
cursor: pointer;
|
||
padding: 0.25rem 0.5rem;
|
||
opacity: 0.7;
|
||
"
|
||
>
|
||
XSPF
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="import-tab"
|
||
data-import-type="xml"
|
||
style="
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--foreground);
|
||
cursor: pointer;
|
||
padding: 0.25rem 0.5rem;
|
||
opacity: 0.7;
|
||
"
|
||
>
|
||
XML
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="import-tab"
|
||
data-import-type="m3u"
|
||
style="
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--foreground);
|
||
cursor: pointer;
|
||
padding: 0.25rem 0.5rem;
|
||
opacity: 0.7;
|
||
"
|
||
>
|
||
M3U
|
||
</button>
|
||
</div>
|
||
|
||
<div id="csv-import-panel" class="import-panel active">
|
||
<p style="margin-bottom: 0.5rem; font-size: 0.9rem">Import from CSV</p>
|
||
|
||
<div style="display: flex; gap: 0.5rem; margin-bottom: 1rem">
|
||
<button
|
||
type="button"
|
||
id="csv-spotify-btn"
|
||
class="btn-secondary"
|
||
style="flex: 1; opacity: 0.7"
|
||
>
|
||
Spotify
|
||
</button>
|
||
<button
|
||
type="button"
|
||
id="csv-apple-btn"
|
||
class="btn-secondary"
|
||
style="flex: 1; opacity: 0.7"
|
||
>
|
||
Apple Music
|
||
</button>
|
||
<button type="button" id="csv-ytm-btn" class="btn-secondary" style="flex: 1; opacity: 0.7">
|
||
YouTube Music
|
||
</button>
|
||
</div>
|
||
|
||
<div id="csv-spotify-guide" style="display: none; margin-bottom: 1rem">
|
||
<p style="font-size: 0.8rem; margin: 0">
|
||
Please use
|
||
<a href="https://exportify.app/" target="_blank" style="text-decoration: underline"
|
||
>Exportify</a
|
||
>
|
||
to export your Spotify playlist into a .csv.
|
||
</p>
|
||
</div>
|
||
|
||
<div id="csv-apple-guide" style="display: none; margin-bottom: 1rem">
|
||
<p style="font-size: 0.8rem; margin: 0">
|
||
Please use
|
||
<a
|
||
href="https://www.tunemymusic.com/transfer/spotify-to-apple-music"
|
||
target="_blank"
|
||
style="text-decoration: underline"
|
||
>TuneMyMusic</a
|
||
>
|
||
to export your Apple Music playlist into a .csv. <br /><span style="opacity: 0.7"
|
||
>(Apple Music imports are prone to errors)</span
|
||
>
|
||
</p>
|
||
</div>
|
||
|
||
<div id="csv-ytm-guide" style="display: none; margin-bottom: 1rem">
|
||
<p style="font-size: 0.8rem; margin: 0 0 0.5rem 0">Paste a YouTube Music Playlist URL.</p>
|
||
<input
|
||
type="text"
|
||
id="ytm-url-input"
|
||
class="template-input"
|
||
placeholder="https://music.youtube.com/playlist?list=..."
|
||
style="width: 100%; margin-bottom: 0.5rem"
|
||
/>
|
||
<p id="ytm-status" style="font-size: 0.8rem; margin: 0; opacity: 0.7"></p>
|
||
</div>
|
||
|
||
<div id="csv-input-container" style="display: none">
|
||
<input
|
||
type="file"
|
||
id="csv-file-input"
|
||
class="btn-secondary"
|
||
accept=".csv"
|
||
style="width: 100%; margin-bottom: 0.5rem"
|
||
/>
|
||
<p style="font-size: 0.8rem; margin: 0; opacity: 0.7">
|
||
Make sure its headers are in English.
|
||
</p>
|
||
</div>
|
||
|
||
<div
|
||
class="setting-item"
|
||
style="margin-top: 1rem; margin-bottom: 1rem; background: transparent"
|
||
>
|
||
<div class="info">
|
||
<span style="font-size: 0.9rem; font-weight: 600">Strict Album Matching</span>
|
||
<span style="font-size: 0.8rem; max-width: 14rem">
|
||
Album name should strictly match CSV metadata. Disable for better discovery.
|
||
</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="strict-album-match-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="jspf-import-panel" class="import-panel" style="display: none">
|
||
<p style="margin-bottom: 0.5rem; font-size: 0.9rem">Import from JSPF</p>
|
||
<p style="font-size: 0.8rem; margin: 0">
|
||
JSPF (JSON Shareable Playlist Format) is supported by const replayGainPreamp =
|
||
document.getElementById('replay-gain-preamp'); if (replayGainPreamp) {
|
||
replayGainPreamp.value = replayGainSettings.getPreamp();
|
||
replayGainPreamp.addEventListener('change', (e) => { const val = parseFloat(e.target.value);
|
||
replayGainSettings.setPreamp(isNaN(val) ? 3 : val); player.applyReplayGain(); }); } Import
|
||
playlists with rich metadata including MusicBrainz identifiers.
|
||
</p>
|
||
<br />
|
||
<input
|
||
type="file"
|
||
id="jspf-file-input"
|
||
class="btn-secondary"
|
||
accept=".json,.jspf"
|
||
style="width: 100%; margin-bottom: 0.5rem"
|
||
/>
|
||
</div>
|
||
|
||
<div id="xspf-import-panel" class="import-panel" style="display: none">
|
||
<p style="margin-bottom: 0.5rem; font-size: 0.9rem">Import from XSPF</p>
|
||
<p style="font-size: 0.8rem; margin: 0">
|
||
XSPF (XML Shareable Playlist Format) is a standard XML playlist format supported by many
|
||
media players including VLC, Audacious, and Clementine.
|
||
</p>
|
||
<br />
|
||
<input
|
||
type="file"
|
||
id="xspf-file-input"
|
||
class="btn-secondary"
|
||
accept=".xspf,.xml"
|
||
style="width: 100%; margin-bottom: 0.5rem"
|
||
/>
|
||
</div>
|
||
|
||
<div id="xml-import-panel" class="import-panel" style="display: none">
|
||
<p style="margin-bottom: 0.5rem; font-size: 0.9rem">Import from XML</p>
|
||
<p style="font-size: 0.8rem; margin: 0">
|
||
Import playlists from generic XML formats including iTunes XML playlists, Winamp XML, and
|
||
other custom XML playlist formats.
|
||
</p>
|
||
<br />
|
||
<input
|
||
type="file"
|
||
id="xml-file-input"
|
||
class="btn-secondary"
|
||
accept=".xml"
|
||
style="width: 100%; margin-bottom: 0.5rem"
|
||
/>
|
||
</div>
|
||
|
||
<div id="m3u-import-panel" class="import-panel" style="display: none">
|
||
<p style="margin-bottom: 0.5rem; font-size: 0.9rem">Import from M3U/M3U8</p>
|
||
<p style="font-size: 0.8rem; margin: 0">
|
||
M3U is the most widely supported playlist format. Import from any M3U or M3U8 playlist file.
|
||
Track information is extracted from the extended M3U format.
|
||
</p>
|
||
<br />
|
||
<input
|
||
type="file"
|
||
id="m3u-file-input"
|
||
class="btn-secondary"
|
||
accept=".m3u,.m3u8"
|
||
style="width: 100%; margin-bottom: 0.5rem"
|
||
/>
|
||
</div>
|
||
|
||
<p style="font-size: 0.8rem; margin: 0">
|
||
<b>Warning:</b> This feature isn't perfect and is prone to errors! Please check your playlist
|
||
after to remove any unwanted songs that were added by the system.
|
||
</p>
|
||
</div>
|
||
|
||
<div
|
||
class="setting-item"
|
||
style="margin-bottom: 1rem; padding: 0; border: none; background: transparent"
|
||
>
|
||
<div class="info">
|
||
<span class="label">Public Playlist</span>
|
||
<span class="description">Visible to anyone with the link.</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="playlist-public-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="modal-actions">
|
||
<button id="playlist-share-btn" class="btn-secondary" style="display: none">Share</button>
|
||
<button id="playlist-modal-cancel" class="btn-secondary">Cancel</button>
|
||
<button id="playlist-modal-save" class="btn-primary">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="edit-profile-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<h3>Edit Profile</h3>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Username</label>
|
||
<input type="text" id="edit-profile-username" class="template-input" placeholder="username" />
|
||
<p
|
||
id="username-error"
|
||
style="color: #ef4444; font-size: 0.8rem; display: none; margin-top: 0.25rem"
|
||
></p>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Display Name</label>
|
||
<input
|
||
type="text"
|
||
id="edit-profile-display-name"
|
||
class="template-input"
|
||
placeholder="Display Name"
|
||
/>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Avatar URL</label>
|
||
<div style="display: flex; gap: 0.5rem; align-items: stretch">
|
||
<input
|
||
type="url"
|
||
id="edit-profile-avatar"
|
||
class="template-input"
|
||
placeholder="Avatar URL"
|
||
style="flex: 1; margin: 0; display: none"
|
||
/>
|
||
<input type="file" id="edit-profile-avatar-file" accept="image/*" style="display: none" />
|
||
<button
|
||
type="button"
|
||
id="edit-profile-avatar-upload-btn"
|
||
class="template-btn"
|
||
style="
|
||
flex: 1;
|
||
padding: 0.5rem;
|
||
font-size: 0.85rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.5rem;
|
||
background: var(--input);
|
||
border: 1px solid var(--border);
|
||
color: var(--foreground);
|
||
border-radius: var(--radius);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
<use svg="!lucide/upload.svg" size="16" />
|
||
<span>Upload</span>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
id="edit-profile-avatar-toggle-btn"
|
||
class="template-btn"
|
||
style="
|
||
padding: 0.5rem;
|
||
font-size: 0.85rem;
|
||
white-space: nowrap;
|
||
background: var(--input);
|
||
border: 1px solid var(--border);
|
||
color: var(--foreground);
|
||
border-radius: var(--radius);
|
||
cursor: pointer;
|
||
"
|
||
title="Switch to URL input"
|
||
>
|
||
or URL
|
||
</button>
|
||
</div>
|
||
<div
|
||
id="edit-profile-avatar-upload-status"
|
||
style="display: none; margin-top: 0.25rem; font-size: 0.75rem; opacity: 0.8"
|
||
></div>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Banner URL</label>
|
||
<div style="display: flex; gap: 0.5rem; align-items: stretch">
|
||
<input
|
||
type="url"
|
||
id="edit-profile-banner"
|
||
class="template-input"
|
||
placeholder="Banner URL"
|
||
style="flex: 1; margin: 0; display: none"
|
||
/>
|
||
<input type="file" id="edit-profile-banner-file" accept="image/*" style="display: none" />
|
||
<button
|
||
type="button"
|
||
id="edit-profile-banner-upload-btn"
|
||
class="template-btn"
|
||
style="
|
||
flex: 1;
|
||
padding: 0.5rem;
|
||
font-size: 0.85rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.5rem;
|
||
background: var(--input);
|
||
border: 1px solid var(--border);
|
||
color: var(--foreground);
|
||
border-radius: var(--radius);
|
||
cursor: pointer;
|
||
"
|
||
>
|
||
<use svg="!lucide/upload.svg" size="16" />
|
||
<span>Upload</span>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
id="edit-profile-banner-toggle-btn"
|
||
class="template-btn"
|
||
style="
|
||
padding: 0.5rem;
|
||
font-size: 0.85rem;
|
||
white-space: nowrap;
|
||
background: var(--input);
|
||
border: 1px solid var(--border);
|
||
color: var(--foreground);
|
||
border-radius: var(--radius);
|
||
cursor: pointer;
|
||
"
|
||
title="Switch to URL input"
|
||
>
|
||
or URL
|
||
</button>
|
||
</div>
|
||
<div
|
||
id="edit-profile-banner-upload-status"
|
||
style="display: none; margin-top: 0.25rem; font-size: 0.75rem; opacity: 0.8"
|
||
></div>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem"
|
||
>Status (Listening to...)</label
|
||
>
|
||
<div class="status-picker-container">
|
||
<input
|
||
type="text"
|
||
id="edit-profile-status-search"
|
||
class="template-input"
|
||
placeholder="Search for a song or album..."
|
||
autocomplete="off"
|
||
/>
|
||
<div id="status-search-results" class="search-results-dropdown"></div>
|
||
<input type="hidden" id="edit-profile-status-json" />
|
||
<div
|
||
id="status-preview"
|
||
style="
|
||
display: none;
|
||
margin-top: 0.5rem;
|
||
padding: 0.5rem;
|
||
background: var(--secondary);
|
||
border-radius: var(--radius);
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
"
|
||
>
|
||
<img
|
||
id="status-preview-img"
|
||
alt="Status preview"
|
||
style="width: 32px; height: 32px; border-radius: 4px; object-fit: cover"
|
||
/>
|
||
<div style="flex: 1; min-width: 0">
|
||
<div
|
||
id="status-preview-title"
|
||
style="
|
||
font-weight: 500;
|
||
font-size: 0.9rem;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
"
|
||
></div>
|
||
<div
|
||
id="status-preview-subtitle"
|
||
style="
|
||
font-size: 0.8rem;
|
||
color: var(--muted-foreground);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
"
|
||
></div>
|
||
</div>
|
||
<button id="clear-status-btn" class="btn-icon" style="width: 24px; height: 24px">
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem"
|
||
>Favorite Albums (Max 5)</label
|
||
>
|
||
<div
|
||
id="edit-favorite-albums-list"
|
||
style="display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.5rem"
|
||
></div>
|
||
<div class="status-picker-container">
|
||
<input
|
||
type="text"
|
||
id="edit-favorite-albums-search"
|
||
class="template-input"
|
||
placeholder="Search for an album..."
|
||
autocomplete="off"
|
||
/>
|
||
<div id="edit-favorite-albums-results" class="search-results-dropdown"></div>
|
||
</div>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">About Me</label>
|
||
<textarea
|
||
id="edit-profile-about"
|
||
class="template-input"
|
||
style="resize: vertical; min-height: 80px"
|
||
placeholder="Tell us about yourself"
|
||
></textarea>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Website</label>
|
||
<input type="url" id="edit-profile-website" class="template-input" placeholder="https://..." />
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Last.fm Username</label>
|
||
<input type="text" id="edit-profile-lastfm" class="template-input" placeholder="Last.fm Username" />
|
||
<p style="font-size: 0.8rem; color: var(--muted-foreground); margin-top: 0.5rem; line-height: 1.5">
|
||
Integrating Last.fm enables recent activity and top stats on your profile. Authorize it in
|
||
<strong>Settings > Scrobbling</strong>. Note: Last.fm authorization is stored locally and
|
||
must be repeated on each device.
|
||
</p>
|
||
</div>
|
||
|
||
<h4 style="margin-top: 1.5rem; margin-bottom: 1rem; font-size: 1rem">Privacy</h4>
|
||
<div class="setting-item" style="padding: 0.5rem 0; border: none">
|
||
<div class="info"><span class="label">Public Playlists</span></div>
|
||
<label class="toggle-switch"
|
||
><input type="checkbox" id="privacy-playlists-toggle" checked /><span class="slider"></span
|
||
></label>
|
||
</div>
|
||
<div class="setting-item" style="padding: 0.5rem 0; border: none">
|
||
<div class="info"><span class="label">Show Last.fm Link & Stats</span></div>
|
||
<label class="toggle-switch"
|
||
><input type="checkbox" id="privacy-lastfm-toggle" checked /><span class="slider"></span
|
||
></label>
|
||
</div>
|
||
|
||
<div class="modal-actions">
|
||
<button id="edit-profile-cancel" class="btn-secondary">Cancel</button>
|
||
<button id="edit-profile-save" class="btn-primary">Save Profile</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="folder-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<h3 id="folder-modal-title">Create Folder</h3>
|
||
<input
|
||
type="text"
|
||
id="folder-name-input"
|
||
class="template-input"
|
||
placeholder="Folder name"
|
||
style="margin: 1rem 0"
|
||
/>
|
||
<input
|
||
type="url"
|
||
id="folder-cover-input"
|
||
class="template-input"
|
||
placeholder="Icon URL (optional)"
|
||
style="margin: 0.5rem 0"
|
||
/>
|
||
<div class="modal-actions">
|
||
<button id="folder-modal-cancel" class="btn-secondary">Cancel</button>
|
||
<button id="folder-modal-save" class="btn-primary">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="email-auth-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<h3 style="text-align: center; margin-bottom: 10px">Email Authentication</h3>
|
||
<input
|
||
type="email"
|
||
id="auth-email"
|
||
class="template-input"
|
||
placeholder="Email Address"
|
||
style="margin-bottom: 0.5rem"
|
||
/>
|
||
<input
|
||
type="password"
|
||
id="auth-password"
|
||
class="template-input"
|
||
placeholder="Password"
|
||
style="margin-bottom: 1rem"
|
||
/>
|
||
<div style="display: flex; gap: 10px; justify-content: center; margin-top: 10px">
|
||
<button id="email-signin-btn" class="btn-primary" style="flex: 1">Sign In</button>
|
||
<button id="email-signup-btn" class="btn-secondary" style="flex: 1">Sign Up</button>
|
||
</div>
|
||
<button
|
||
id="reset-password-btn"
|
||
class="btn-secondary"
|
||
style="width: 100%; margin-top: 10px; font-size: 0.9rem"
|
||
>
|
||
Forgot Password?
|
||
</button>
|
||
<button id="cancel-email-auth-btn" class="btn-secondary" style="width: 100%; margin-top: 10px">
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="playlist-select-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<h3>Add to Playlist</h3>
|
||
<div id="playlist-select-list" class="modal-list">
|
||
<!-- Options will be injected here -->
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button id="playlist-select-cancel" class="btn-secondary">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="goto-playlist-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<h3>Go to Playlist</h3>
|
||
<div id="goto-playlist-list" class="modal-list">
|
||
<!-- Options will be injected here -->
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button id="goto-playlist-cancel" class="btn-secondary">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="shortcuts-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content medium">
|
||
<div class="shortcuts-header">
|
||
<h3>Keyboard Shortcuts</h3>
|
||
<button class="close-shortcuts">×</button>
|
||
</div>
|
||
<div class="shortcuts-content">
|
||
<div class="shortcut-item"><kbd>Space</kbd><span>Play / Pause</span></div>
|
||
<div class="shortcut-item"><kbd>→</kbd><span>Seek forward 10s</span></div>
|
||
<div class="shortcut-item"><kbd>←</kbd><span>Seek backward 10s</span></div>
|
||
<div class="shortcut-item"><kbd>Shift</kbd> + <kbd>→</kbd><span>Next track</span></div>
|
||
<div class="shortcut-item"><kbd>Shift</kbd> + <kbd>←</kbd><span>Previous track</span></div>
|
||
<div class="shortcut-item"><kbd>↑</kbd><span>Volume up</span></div>
|
||
<div class="shortcut-item"><kbd>↓</kbd><span>Volume down</span></div>
|
||
<div class="shortcut-item"><kbd>M</kbd><span>Mute / Unmute</span></div>
|
||
<div class="shortcut-item"><kbd>S</kbd><span>Toggle shuffle</span></div>
|
||
<div class="shortcut-item"><kbd>R</kbd><span>Toggle repeat</span></div>
|
||
<div class="shortcut-item"><kbd>Q</kbd><span>Open queue</span></div>
|
||
<div class="shortcut-item"><kbd>L</kbd><span>Toggle lyrics</span></div>
|
||
<div class="shortcut-item"><kbd>/</kbd><span>Focus search</span></div>
|
||
<div class="shortcut-item"><kbd>Esc</kbd><span>Close modals</span></div>
|
||
<div class="shortcut-item"><kbd>[</kbd><span>Previous visualizer preset</span></div>
|
||
<div class="shortcut-item"><kbd>]</kbd><span>Next visualizer preset</span></div>
|
||
<div class="shortcut-item"><kbd>\</kbd><span>Toggle visualizer auto-cycle</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="customize-shortcuts-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content medium">
|
||
<div class="shortcuts-header">
|
||
<h3>Customize Shortcuts</h3>
|
||
<button class="close-customize-shortcuts">×</button>
|
||
</div>
|
||
<div class="customize-shortcuts-content">
|
||
<p class="shortcut-hint">Click on a shortcut to rebind it. Press the new key combination.</p>
|
||
<div id="shortcuts-list" class="shortcuts-list"></div>
|
||
</div>
|
||
<div class="customize-shortcuts-actions">
|
||
<button id="reset-shortcuts-btn" class="btn-secondary">Reset to Defaults</button>
|
||
<button id="close-customize-shortcuts-btn" class="btn-primary">Done</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="missing-tracks-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content wide">
|
||
<div class="missing-tracks-header">
|
||
<h3>Note</h3>
|
||
<button class="close-missing-tracks">×</button>
|
||
</div>
|
||
<div class="missing-tracks-content">
|
||
<p>
|
||
Unfortunately, some songs weren't able to be added. This could be an issue with our import
|
||
system - try searching for the song and adding it. It could also be due to Monochrome not having
|
||
the song :(
|
||
</p>
|
||
<div class="missing-tracks-list">
|
||
<h4>Missing Tracks:</h4>
|
||
<ul id="missing-tracks-list-ul"></ul>
|
||
</div>
|
||
</div>
|
||
<div class="missing-tracks-actions">
|
||
<button class="btn-secondary" id="copy-missing-tracks-btn">Copy to Clipboard</button>
|
||
<button class="btn-secondary" id="export-missing-tracks-csv-btn">Export as CSV</button>
|
||
<button class="btn-secondary" id="close-missing-tracks-btn">OK</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="sleep-timer-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content" style="max-width: 300px">
|
||
<h3 style="text-align: center; margin-bottom: 1.5rem">Sleep Timer</h3>
|
||
<div class="timer-options">
|
||
<button class="timer-option btn-secondary" data-minutes="5">5 minutes</button>
|
||
<button class="timer-option btn-secondary" data-minutes="15">15 minutes</button>
|
||
<button class="timer-option btn-secondary" data-minutes="30">30 minutes</button>
|
||
<button class="timer-option btn-secondary" data-minutes="60">1 hour</button>
|
||
<button class="timer-option btn-secondary" data-minutes="120">2 hours</button>
|
||
<div style="display: flex; gap: 0.5rem; margin-top: 0.5rem">
|
||
<input
|
||
type="number"
|
||
id="custom-minutes"
|
||
class="template-input"
|
||
placeholder="Custom"
|
||
min="1"
|
||
max="480"
|
||
/>
|
||
<button class="timer-option btn-primary" id="custom-timer-btn" style="padding: 0.5rem 1rem">
|
||
Set
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="modal-actions" style="justify-content: center">
|
||
<button id="cancel-sleep-timer" class="btn-secondary">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="discography-download-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content" style="max-width: 500px">
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem">
|
||
<h3 style="margin: 0">Download Discography</h3>
|
||
<button
|
||
class="close-modal-btn"
|
||
style="
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
cursor: pointer;
|
||
color: var(--muted-foreground);
|
||
"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
<p style="margin-bottom: 1rem; color: var(--muted-foreground)">
|
||
Select which releases to download for <span id="discography-artist-name"></span>:
|
||
</p>
|
||
<div style="display: flex; flex-direction: column; gap: 1rem">
|
||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer">
|
||
<input type="checkbox" id="download-albums" checked />
|
||
<span>Albums (<span id="albums-count">0</span>)</span>
|
||
</label>
|
||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer">
|
||
<input type="checkbox" id="download-eps" />
|
||
<span>EPs (<span id="eps-count">0</span>)</span>
|
||
</label>
|
||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer">
|
||
<input type="checkbox" id="download-singles" />
|
||
<span>Singles (<span id="singles-count">0</span>)</span>
|
||
</label>
|
||
</div>
|
||
<div class="modal-actions" style="margin-top: 1.5rem">
|
||
<button class="btn-secondary" id="cancel-discography-download">Cancel</button>
|
||
<button class="btn-primary" id="start-discography-download">Download</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="custom-db-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem">
|
||
<h3 style="margin: 0">Custom Database/Auth</h3>
|
||
<button
|
||
id="custom-db-reset"
|
||
class="btn-secondary danger"
|
||
style="padding: 0.4rem 0.8rem; font-size: 0.8rem"
|
||
>
|
||
Reset to Defaults
|
||
</button>
|
||
</div>
|
||
<p style="font-size: 0.9rem; color: var(--muted-foreground); margin-bottom: 1rem">
|
||
Configure custom PocketBase and Appwrite instances. Leave empty to use defaults.
|
||
<br />
|
||
A Guide To Set This Up Can Be Found
|
||
<a
|
||
href="https://github.com/monochrome-music/monochrome/blob/main/DOCKER.md"
|
||
style="text-decoration: underline"
|
||
>Here</a
|
||
>.
|
||
</p>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">PocketBase URL</label>
|
||
<input type="url" id="custom-pb-url" class="template-input" placeholder="https://data.samidy.xyz" />
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Appwrite Endpoint</label>
|
||
<input
|
||
type="url"
|
||
id="custom-appwrite-endpoint"
|
||
class="template-input"
|
||
placeholder="https://auth.samidy.com/v1"
|
||
/>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Appwrite Project ID</label>
|
||
<input
|
||
type="text"
|
||
id="custom-appwrite-project"
|
||
class="template-input"
|
||
placeholder="auth-for-monochrome"
|
||
/>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button id="custom-db-cancel" class="btn-secondary">Cancel</button>
|
||
<button id="custom-db-save" class="btn-primary">Save & Reload</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="theme-store-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div
|
||
class="modal-content wide"
|
||
style="height: 80vh; display: flex; flex-direction: column; max-height: 80vh"
|
||
>
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem">
|
||
<h3 style="margin: 0">Theme Store</h3>
|
||
<button
|
||
class="close-modal-btn"
|
||
style="
|
||
background: none;
|
||
border: none;
|
||
font-size: 1.5rem;
|
||
cursor: pointer;
|
||
color: var(--muted-foreground);
|
||
"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
|
||
<div class="search-tabs" style="margin-bottom: 1rem">
|
||
<button class="search-tab active" data-tab="browse">Browse</button>
|
||
<button class="search-tab" data-tab="upload">Upload</button>
|
||
</div>
|
||
|
||
<div
|
||
id="theme-store-details"
|
||
style="display: none; flex-direction: column; height: 100%; overflow: hidden"
|
||
>
|
||
<button
|
||
class="btn-secondary"
|
||
id="theme-details-back-btn"
|
||
style="
|
||
align-self: flex-start;
|
||
margin-bottom: 1rem;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
"
|
||
>
|
||
<use svg="!lucide/chevron-left.svg" size="16" />
|
||
Back
|
||
</button>
|
||
<div style="flex: 1; overflow-y: auto; padding-right: 0.5rem">
|
||
<div style="display: flex; gap: 1.5rem; flex-wrap: wrap; margin-bottom: 2rem">
|
||
<div
|
||
id="theme-details-preview-container"
|
||
style="
|
||
width: 300px;
|
||
height: 220px;
|
||
flex-shrink: 0;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
"
|
||
></div>
|
||
<div style="flex: 1; min-width: 250px">
|
||
<h2 id="theme-details-name" style="font-size: 2rem; margin-bottom: 0.5rem"></h2>
|
||
<div
|
||
class="meta"
|
||
style="
|
||
color: var(--muted-foreground);
|
||
font-size: 0.9rem;
|
||
margin-bottom: 1rem;
|
||
line-height: 1.6;
|
||
"
|
||
>
|
||
<div id="theme-details-author"></div>
|
||
<div>
|
||
Created: <span id="theme-details-created"></span> - Updated:
|
||
<span id="theme-details-updated"></span>
|
||
</div>
|
||
<div>Installs: <span id="theme-details-installs">0</span></div>
|
||
</div>
|
||
<button
|
||
id="theme-details-apply-btn"
|
||
class="btn-primary"
|
||
style="width: 100%; max-width: 200px"
|
||
>
|
||
Apply Theme
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div style="margin-bottom: 2rem">
|
||
<h3 style="font-size: 1.2rem; margin-bottom: 0.5rem">Description</h3>
|
||
<p
|
||
id="theme-details-desc"
|
||
style="white-space: pre-wrap; color: var(--foreground); line-height: 1.6"
|
||
></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
id="theme-store-browse"
|
||
class="search-tab-content active"
|
||
style="flex: 1; overflow-y: auto; min-height: 0"
|
||
>
|
||
<div class="track-list-search-container" style="margin: 0 0 1rem 0">
|
||
<use svg="!lucide/search.svg" class="search-icon" size="20" />
|
||
<input
|
||
type="search"
|
||
id="theme-store-search"
|
||
placeholder="Search themes..."
|
||
class="track-list-search-input"
|
||
autocomplete="off"
|
||
/>
|
||
</div>
|
||
<div id="community-themes-grid" class="card-grid"></div>
|
||
<div id="theme-store-loading" style="text-align: center; padding: 2rem; display: none">
|
||
<div class="animate-spin" style="display: inline-block">
|
||
<use svg="!lucide/loader-circle.svg" size="24" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
id="theme-store-upload"
|
||
class="search-tab-content"
|
||
style="flex: 1; overflow-y: auto; min-height: 0"
|
||
>
|
||
<div
|
||
id="theme-upload-auth-message"
|
||
style="
|
||
display: none;
|
||
text-align: center;
|
||
padding: 2rem;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
"
|
||
>
|
||
<p>You need to be logged in to upload themes.</p>
|
||
<button class="btn-primary" id="theme-store-login-btn">Go to Login</button>
|
||
</div>
|
||
<form id="theme-upload-form">
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Theme Name</label>
|
||
<input
|
||
type="text"
|
||
id="theme-upload-name"
|
||
class="template-input"
|
||
placeholder="My Awesome Theme"
|
||
required
|
||
/>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Description</label>
|
||
<textarea
|
||
id="theme-upload-desc"
|
||
class="template-input"
|
||
placeholder="Describe your theme..."
|
||
style="min-height: 80px; resize: vertical"
|
||
></textarea>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem"
|
||
>Author Website (Optional)</label
|
||
>
|
||
<input
|
||
type="url"
|
||
id="theme-upload-website"
|
||
class="template-input"
|
||
placeholder="https://example.com"
|
||
/>
|
||
<p style="font-size: 0.8rem; color: var(--muted-foreground); margin-top: 0.25rem">
|
||
It is recommended to create a Monochrome profile instead.
|
||
</p>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">CSS</label>
|
||
<p style="font-size: 0.8rem; color: var(--muted-foreground); margin-bottom: 0.5rem">
|
||
Define your CSS variables or custom styles here.
|
||
<a
|
||
href="https://github.com/monochrome-music/monochrome/blob/main/THEME_GUIDE.md"
|
||
target="_blank"
|
||
style="text-decoration: underline; color: var(--primary)"
|
||
>Read the Theme Guide</a
|
||
>.
|
||
</p>
|
||
<div class="theme-editor-toolbar">
|
||
<div class="color-picker-group">
|
||
<input type="color" id="te-bg-color" title="Background" />
|
||
<input type="color" id="te-fg-color" title="Foreground" />
|
||
<input type="color" id="te-primary-color" title="Primary" />
|
||
<input type="color" id="te-sec-color" title="Secondary" />
|
||
<input type="color" id="te-accent-color" title="Accent/Highlight" />
|
||
<input type="color" id="te-card-color" title="Card Background" />
|
||
<input type="color" id="te-border-color" title="Border Color" />
|
||
<input type="color" id="te-muted-color" title="Muted Text" />
|
||
</div>
|
||
<div class="style-picker-group">
|
||
<select id="te-font-family" title="Font Family">
|
||
<option value="">Font...</option>
|
||
<option value="'Inter', sans-serif">Inter</option>
|
||
<option value="system-ui, -apple-system, sans-serif">System</option>
|
||
<option value="'Courier New', monospace">Mono</option>
|
||
<option value="'Times New Roman', serif">Serif</option>
|
||
</select>
|
||
<input
|
||
type="text"
|
||
id="te-font-custom"
|
||
placeholder="Custom Font..."
|
||
style="
|
||
height: 24px;
|
||
padding: 0 4px;
|
||
font-size: 0.75rem;
|
||
width: 100px;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-sm);
|
||
background: var(--input);
|
||
color: var(--foreground);
|
||
"
|
||
/>
|
||
<select id="te-radius" title="Border Radius">
|
||
<option value="">Radius...</option>
|
||
<option value="0px">Square</option>
|
||
<option value="4px">Small</option>
|
||
<option value="8px">Medium</option>
|
||
<option value="16px">Large</option>
|
||
</select>
|
||
</div>
|
||
<div class="editor-actions">
|
||
<button
|
||
type="button"
|
||
id="te-insert-template"
|
||
class="btn-secondary"
|
||
style="padding: 2px 8px; font-size: 0.8rem"
|
||
>
|
||
Template
|
||
</button>
|
||
<button
|
||
type="button"
|
||
id="te-toggle-preview"
|
||
class="btn-secondary"
|
||
style="padding: 2px 8px; font-size: 0.8rem"
|
||
>
|
||
Preview
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<textarea
|
||
id="theme-upload-css"
|
||
class="template-input"
|
||
style="min-height: 200px; font-family: monospace; white-space: pre"
|
||
placeholder=":root { --background: #000000; --foreground: #ffffff; /* ... */ }"
|
||
required
|
||
></textarea>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button
|
||
type="button"
|
||
id="theme-upload-cancel-edit"
|
||
class="btn-secondary"
|
||
style="display: none"
|
||
>
|
||
Cancel Edit
|
||
</button>
|
||
<button type="submit" id="theme-upload-submit-btn" class="btn-primary">Upload Theme</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="theme-preview-window" style="display: none"></div>
|
||
|
||
<div id="tracker-modal" class="modal tracker-modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content wide" style="max-height: 85vh; display: flex; flex-direction: column">
|
||
<header class="detail-header" style="margin-bottom: 1rem; padding-bottom: 0">
|
||
<img
|
||
id="tracker-header-image"
|
||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||
class="detail-header-image"
|
||
style="width: 140px; height: 140px; box-shadow: var(--shadow-md)"
|
||
alt=""
|
||
/>
|
||
<div class="detail-header-info">
|
||
<div class="type">Unreleased Project</div>
|
||
<h1 id="tracker-header-title" class="title" style="font-size: 2rem"></h1>
|
||
<div id="tracker-header-meta" class="meta"></div>
|
||
<div class="detail-header-actions">
|
||
<button class="btn-secondary" id="close-tracker-modal">Close</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<div
|
||
id="tracker-filters"
|
||
style="padding: 0 2rem 1rem; display: flex; gap: 0.5rem; flex-wrap: wrap"
|
||
></div>
|
||
<div id="tracker-tracklist" class="track-list" style="overflow-y: auto; flex: 1">
|
||
<div class="track-list-header">
|
||
<span style="width: 40px; text-align: center">#</span>
|
||
<span>Title</span>
|
||
<span class="duration-header">Duration</span>
|
||
<span style="display: flex; justify-content: flex-end; opacity: 0.8">Menu</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="epilepsy-warning-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<h3 style="color: #ef4444; display: flex; align-items: center; gap: 0.5rem">
|
||
<use svg="!lucide/triangle-alert.svg" size="24" />
|
||
Photosensitivity Warning
|
||
</h3>
|
||
<p style="margin: 1rem 0; line-height: 1.5">
|
||
The visualizer contains flashing lights and rapidly moving patterns that may trigger seizures for
|
||
people with photosensitive epilepsy.
|
||
</p>
|
||
<p style="margin-bottom: 1.5rem; font-size: 0.9rem; color: var(--muted-foreground)">
|
||
Viewer discretion is advised.
|
||
</p>
|
||
<div class="modal-actions" style="flex-direction: column; gap: 0.5rem">
|
||
<button id="epilepsy-accept-btn" class="btn-primary" style="width: 100%">
|
||
Proceed & Don't Show Again
|
||
</button>
|
||
<button id="epilepsy-cancel-btn" class="btn-secondary" style="width: 100%">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="maintenance-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content" style="text-align: center">
|
||
<h3 style="color: #ef4444; display: flex; align-items: center; gap: 0.5rem; justify-content: center">
|
||
<use svg="!lucide/wrench.svg" size="24" />
|
||
Maintenance Notice
|
||
</h3>
|
||
<p style="margin: 1.5rem 0; line-height: 1.6; font-size: 1.1rem">
|
||
The desktop version of Monochrome is currently undergoing essential maintenance to address critical
|
||
issues.
|
||
</p>
|
||
<p style="margin-bottom: 2rem; color: var(--muted-foreground); line-height: 1.5">
|
||
Downloads are temporarily disabled while we work on fixing these issues. Please check back later.
|
||
</p>
|
||
<div class="modal-actions" style="justify-content: center">
|
||
<button id="maintenance-home-btn" class="btn-primary" style="padding: 0.75rem 2rem">
|
||
Back to Home
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="desktop-update-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<h3>Update Available</h3>
|
||
<p>A new version of Monochrome is available.</p>
|
||
<div
|
||
id="desktop-update-notes"
|
||
style="
|
||
margin: 1rem 0;
|
||
padding: 1rem;
|
||
background: var(--background-secondary);
|
||
border-radius: var(--radius);
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
font-size: 0.9rem;
|
||
"
|
||
></div>
|
||
<div class="modal-actions">
|
||
<button id="desktop-update-cancel" class="btn-secondary">Later</button>
|
||
<button id="desktop-update-confirm" class="btn-primary">Update Now</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="command-palette-overlay" style="display: none">
|
||
<div class="command-palette">
|
||
<div class="command-palette-header">
|
||
<svg
|
||
class="command-palette-search-icon"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
width="18"
|
||
height="18"
|
||
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" />
|
||
<path d="m21 21-4.3-4.3" />
|
||
</svg>
|
||
<input
|
||
type="text"
|
||
id="command-palette-input"
|
||
placeholder="Search commands, music, settings..."
|
||
autocomplete="off"
|
||
spellcheck="false"
|
||
aria-label="Command palette search"
|
||
role="combobox"
|
||
aria-expanded="true"
|
||
aria-controls="command-palette-results"
|
||
aria-autocomplete="list"
|
||
/>
|
||
<kbd class="command-palette-kbd">ESC</kbd>
|
||
</div>
|
||
<div id="command-palette-results" class="command-palette-results" role="listbox"></div>
|
||
<div class="command-palette-footer">
|
||
<span class="command-palette-hint"><kbd>↑↓</kbd> navigate</span>
|
||
<span class="command-palette-hint"><kbd>↵</kbd> select</span>
|
||
<span class="command-palette-hint"><kbd>esc</kbd> close</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="sidebar-overlay"></div>
|
||
|
||
<div id="csv-import-progress" class="csv-import-progress" style="display: none">
|
||
<div class="progress-header">
|
||
<h4>Importing Tracks from CSV</h4>
|
||
<p class="progress-warning">
|
||
This can take a while depending on your playlist size. Please be patient.
|
||
</p>
|
||
</div>
|
||
<div class="progress-content">
|
||
<div class="current-track">Preparing import...</div>
|
||
<div
|
||
class="current-artist"
|
||
style="font-size: 0.9em; color: var(--text-secondary); margin-bottom: 0.5rem"
|
||
></div>
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" id="csv-progress-fill"></div>
|
||
</div>
|
||
<div class="progress-text">
|
||
<span id="csv-progress-current">0</span> / <span id="csv-progress-total">0</span> tracks processed
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="app-container">
|
||
<aside class="sidebar">
|
||
<div class="sidebar-content">
|
||
<div class="sidebar-logo">
|
||
<a href="https://monochrome.tf/" class="sidebar-logo-link">
|
||
<use svg="./images/monochrome-logo.svg" size="200" class="app-logo" />
|
||
<span>Monochrome</span>
|
||
</a>
|
||
<button
|
||
id="sidebar-toggle"
|
||
class="btn-icon desktop-only"
|
||
style="margin-left: auto"
|
||
title="Collapse Sidebar"
|
||
>
|
||
<use svg="!lucide/chevron-left.svg" size="20" />
|
||
</button>
|
||
</div>
|
||
<nav class="sidebar-nav main">
|
||
<ul>
|
||
<li class="nav-item" id="sidebar-nav-home">
|
||
<a href="/">
|
||
<use svg="!lucide/house.svg" size="24" />
|
||
<span>Home</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-library">
|
||
<a href="/library">
|
||
<use svg="!lucide/library.svg" size="24" />
|
||
<span>Library</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-recent">
|
||
<a href="/recent">
|
||
<use svg="./images/recent.svg" size="24" />
|
||
<span>Recent</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-unreleased">
|
||
<a href="/unreleased">
|
||
<use svg="./images/squares.svg" size="24" />
|
||
<span>Unreleased</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-donate">
|
||
<a id="sidebar-donate-link" href="/donate">
|
||
<use svg="!lucide/hand-heart.svg" size="24" />
|
||
<span>Donate</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-settings">
|
||
<a href="/settings">
|
||
<use svg="!lucide/settings.svg" size="24" />
|
||
<span>Settings</span>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</nav>
|
||
<div class="sidebar-bottom-container">
|
||
<nav class="sidebar-nav" id="pinned-items-nav" style="display: none">
|
||
<h4 class="pinned-items-header">Pinned</h4>
|
||
<ul id="pinned-items-list">
|
||
<!-- Pinned should be injected here -->
|
||
</ul>
|
||
</nav>
|
||
<div class="sidebar-nav-bottom">
|
||
<nav class="sidebar-nav bottom">
|
||
<ul>
|
||
<li class="nav-item" id="sidebar-nav-about-bottom">
|
||
<a href="/about">
|
||
<use svg="!lucide/info.svg" size="24" />
|
||
<span>About</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-discordbtn">
|
||
<a href="discord.html" target="_blank">
|
||
<use svg="./images/discord.svg" size="64" />
|
||
<span>Discord</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-githubbtn" style="display: none">
|
||
<a href="https://github.com/monochrome-music/monochrome" target="_blank">
|
||
<use svg="./images/github.svg" size="24" />
|
||
<span>GitHub</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-download-bottom">
|
||
<a href="/download">
|
||
<use svg="!lucide/download.svg" size="24" />
|
||
<span>Download</span>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<main class="main-content">
|
||
<div id="page-background"></div>
|
||
<header class="main-header">
|
||
<div class="navigation-controls desktop-only">
|
||
<button id="nav-back" class="nav-btn" title="Go Back">
|
||
<use svg="!lucide/chevron-left.svg" size="24" />
|
||
</button>
|
||
<button id="nav-forward" class="nav-btn" title="Go Forward">
|
||
<use svg="!lucide/chevron-right.svg" size="24" />
|
||
</button>
|
||
</div>
|
||
<button class="hamburger-menu" id="hamburger-btn" title="Open navigation">
|
||
<use svg="!lucide/menu.svg" size="24" />
|
||
</button>
|
||
<div class="header-account-control" style="position: relative; margin-right: 1rem">
|
||
<button
|
||
id="header-account-btn"
|
||
class="btn-icon"
|
||
title="Account"
|
||
style="
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 50%;
|
||
overflow: hidden;
|
||
padding: 0;
|
||
border: 1px solid var(--border);
|
||
"
|
||
>
|
||
<use svg="!lucide/user.svg" size="20" />
|
||
<img
|
||
id="header-account-img"
|
||
alt="Account avatar"
|
||
style="width: 100%; height: 100%; object-fit: cover; display: none"
|
||
/>
|
||
</button>
|
||
<div id="header-account-dropdown" class="dropdown-menu"></div>
|
||
</div>
|
||
<form class="search-bar" id="search-form">
|
||
<use svg="!lucide/search.svg" class="search-icon" size="24" />
|
||
<input
|
||
type="search"
|
||
id="search-input"
|
||
placeholder="Search for tracks, artists, albums..."
|
||
autocomplete="off"
|
||
autocorrect="off"
|
||
autocapitalize="off"
|
||
spellcheck="false"
|
||
/>
|
||
<button
|
||
type="button"
|
||
class="search-clear-btn btn-icon"
|
||
title="Clear search"
|
||
style="display: none"
|
||
>
|
||
×
|
||
</button>
|
||
<div id="search-history" class="search-history" style="display: none"></div>
|
||
</form>
|
||
</header>
|
||
|
||
<div id="page-home" class="page">
|
||
<div class="home-header-tabs">
|
||
<button class="home-tab active" data-tab="for-you">Home</button>
|
||
<button class="home-tab" data-tab="explore">Hot & New</button>
|
||
</div>
|
||
|
||
<div id="home-view-for-you" class="home-view active">
|
||
<div id="home-welcome" style="display: none; text-align: center; padding: 4rem 2rem">
|
||
<h2 style="margin-bottom: 1rem">Welcome to Monochrome</h2>
|
||
<p style="color: var(--muted-foreground)">
|
||
You haven't listened to anything yet. Search for your favorite songs to get started!
|
||
</p>
|
||
</div>
|
||
|
||
<section class="content-section" id="home-editors-picks-section-empty" style="margin-top: 0">
|
||
<div
|
||
style="
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 1rem;
|
||
"
|
||
>
|
||
<h2 class="section-title" style="margin-bottom: 0">Editor's Picks</h2>
|
||
</div>
|
||
<div class="card-grid" id="home-editors-picks-empty"></div>
|
||
</section>
|
||
|
||
<div id="home-content" style="display: none">
|
||
<section class="content-section">
|
||
<div class="section-header-row">
|
||
<div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap">
|
||
<h2 class="section-title">Recommended Songs</h2>
|
||
<button
|
||
class="btn-primary"
|
||
id="home-start-infinite-radio-btn"
|
||
title="Start Infinite Radio"
|
||
style="
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 12px;
|
||
font-size: 0.85rem;
|
||
"
|
||
>
|
||
<use svg="!lucide/radio.svg" size="16" />
|
||
Start Infinite Radio
|
||
</button>
|
||
</div>
|
||
<button
|
||
class="btn-secondary"
|
||
id="refresh-songs-btn"
|
||
title="Refresh"
|
||
style="padding: 4px 8px"
|
||
>
|
||
<use svg="!lucide/rotate-cw.svg" size="16" />
|
||
</button>
|
||
</div>
|
||
<div class="track-list" id="home-recommended-songs"></div>
|
||
</section>
|
||
<section class="content-section">
|
||
<div
|
||
style="
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 1rem;
|
||
"
|
||
>
|
||
<h2 class="section-title" style="margin-bottom: 0">Recommended Albums</h2>
|
||
<button
|
||
class="btn-secondary"
|
||
id="refresh-albums-btn"
|
||
title="Refresh"
|
||
style="padding: 4px 8px"
|
||
>
|
||
<use svg="!lucide/rotate-cw.svg" size="16" />
|
||
</button>
|
||
</div>
|
||
<div class="card-grid" id="home-recommended-albums"></div>
|
||
</section>
|
||
<section class="content-section">
|
||
<div
|
||
style="
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 1rem;
|
||
"
|
||
>
|
||
<h2 class="section-title" style="margin-bottom: 0">Recommended Artists</h2>
|
||
<button
|
||
class="btn-secondary"
|
||
id="refresh-artists-btn"
|
||
title="Refresh"
|
||
style="padding: 4px 8px"
|
||
>
|
||
<use svg="!lucide/rotate-cw.svg" size="16" />
|
||
</button>
|
||
</div>
|
||
<div class="card-grid" id="home-recommended-artists"></div>
|
||
</section>
|
||
<section class="content-section">
|
||
<div
|
||
style="
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 1rem;
|
||
"
|
||
>
|
||
<h2 class="section-title" style="margin-bottom: 0">Jump Back In</h2>
|
||
<button
|
||
class="btn-secondary"
|
||
id="clear-recent-btn"
|
||
title="Clear History"
|
||
style="padding: 4px 8px"
|
||
>
|
||
<use svg="!lucide/trash.svg" size="16" />
|
||
</button>
|
||
</div>
|
||
<div class="card-grid" id="home-recent-mixed"></div>
|
||
</section>
|
||
<section class="content-section" id="home-editors-picks-section">
|
||
<div
|
||
style="
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 1rem;
|
||
"
|
||
>
|
||
<h2 class="section-title" style="margin-bottom: 0">Editor's Picks</h2>
|
||
</div>
|
||
<div class="card-grid" id="home-editors-picks"></div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="home-view-explore" class="home-view" style="display: none">
|
||
<div id="explore-content">
|
||
<div class="card-grid" id="explore-grid"></div>
|
||
</div>
|
||
</div>
|
||
</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="videos">Videos</button>
|
||
<button class="search-tab" data-tab="albums">Albums</button>
|
||
<button class="search-tab" data-tab="artists">Artists</button>
|
||
<button class="search-tab" data-tab="playlists">Playlists</button>
|
||
<button class="search-tab" data-tab="podcasts">Podcasts</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-videos">
|
||
<div class="card-grid" id="search-videos-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 class="search-tab-content" id="search-tab-playlists">
|
||
<div class="card-grid" id="search-playlists-container"></div>
|
||
</div>
|
||
<div class="search-tab-content" id="search-tab-podcasts">
|
||
<div class="card-grid" id="search-podcasts-container"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="page-library" class="page">
|
||
<section class="content-section">
|
||
<div class="library-header">
|
||
<h2>My Playlists</h2>
|
||
<button id="create-playlist-btn" class="btn-primary" style="margin: 10px 0">
|
||
Create Playlist
|
||
</button>
|
||
<button
|
||
id="create-folder-btn"
|
||
class="btn-primary"
|
||
style="
|
||
margin: 10px 0 10px 10px;
|
||
background-color: var(--secondary);
|
||
color: var(--foreground);
|
||
"
|
||
>
|
||
Create Folder
|
||
</button>
|
||
</div>
|
||
<div class="card-grid" id="my-folders-container"></div>
|
||
<div class="card-grid" id="my-playlists-container"></div>
|
||
</section>
|
||
|
||
<section class="content-section">
|
||
<h2 class="section-title">Favorites</h2>
|
||
<div class="search-tabs">
|
||
<button class="search-tab active" data-tab="tracks">Liked Tracks</button>
|
||
<button class="search-tab" data-tab="videos">Videos</button>
|
||
<button class="search-tab" data-tab="albums">Albums</button>
|
||
<button class="search-tab" data-tab="artists">Artists</button>
|
||
<button class="search-tab" data-tab="playlists">Playlists and Mixes</button>
|
||
<button class="search-tab" data-tab="local">Local Files</button>
|
||
</div>
|
||
<div class="search-tab-content active" id="library-tab-tracks">
|
||
<div style="display: flex; justify-content: flex-start; gap: 0.5rem; margin-bottom: 0.5rem">
|
||
<button
|
||
id="shuffle-liked-tracks-btn"
|
||
class="btn-secondary"
|
||
style="
|
||
display: none;
|
||
width: 32px;
|
||
height: 32px;
|
||
padding: 0;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 50%;
|
||
"
|
||
title="Shuffle Liked Tracks"
|
||
>
|
||
<use svg="!lucide/shuffle.svg" size="16" />
|
||
</button>
|
||
<button
|
||
id="download-liked-tracks-btn"
|
||
class="btn-secondary"
|
||
style="
|
||
display: none;
|
||
width: 32px;
|
||
height: 32px;
|
||
padding: 0;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 50%;
|
||
"
|
||
title="Download Liked Tracks"
|
||
>
|
||
<use svg="!lucide/download.svg" size="16" />
|
||
</button>
|
||
</div>
|
||
<div class="track-list" id="library-tracks-container"></div>
|
||
</div>
|
||
<div class="search-tab-content" id="library-tab-videos">
|
||
<div class="card-grid" id="library-videos-container"></div>
|
||
</div>
|
||
<div class="search-tab-content" id="library-tab-albums">
|
||
<div class="card-grid" id="library-albums-container"></div>
|
||
</div>
|
||
<div class="search-tab-content" id="library-tab-artists">
|
||
<div class="card-grid" id="library-artists-container"></div>
|
||
</div>
|
||
<div class="search-tab-content" id="library-tab-playlists">
|
||
<div class="card-grid" id="library-playlists-container"></div>
|
||
</div>
|
||
<div class="search-tab-content" id="library-tab-local">
|
||
<div class="track-list" id="library-local-container">
|
||
<div id="local-files-intro" style="padding: 20px; text-align: center">
|
||
<button id="select-local-folder-btn" class="btn-secondary">
|
||
<use svg="!lucide/folder.svg" size="20" />
|
||
<span id="select-local-folder-text">Select Music Folder</span>
|
||
</button>
|
||
<p
|
||
id="local-browser-warning"
|
||
style="display: none; color: #ef4444; margin-top: 10px; font-size: 0.9rem"
|
||
>
|
||
Please use Google Chrome or Microsoft Edge to play local files.
|
||
</p>
|
||
<p style="margin-top: 10px; font-size: 0.9rem; color: var(--muted-foreground)">
|
||
Select a folder on your device to play local files. <br />
|
||
Note: Metadata reading is basic (FLAC/MP3 tags).
|
||
</p>
|
||
</div>
|
||
<div
|
||
id="local-files-header"
|
||
style="
|
||
display: none;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 10px 20px;
|
||
"
|
||
>
|
||
<h3>Local Files</h3>
|
||
<button
|
||
id="change-local-folder-btn"
|
||
class="btn-secondary"
|
||
style="font-size: 0.8rem; padding: 4px 8px"
|
||
>
|
||
Change Folder
|
||
</button>
|
||
</div>
|
||
<div id="local-files-list"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<div id="page-recent" class="page">
|
||
<div
|
||
style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem"
|
||
>
|
||
<h2 class="section-title" style="margin-bottom: 0">Recently played</h2>
|
||
<button class="btn-secondary" id="clear-history-btn" title="Clear History">
|
||
<use svg="!lucide/trash-2.svg" size="16" />
|
||
<span>Clear</span>
|
||
</button>
|
||
</div>
|
||
<div class="track-list" id="recent-tracks-container"></div>
|
||
</div>
|
||
|
||
<div id="page-unreleased" class="page">
|
||
<div id="unreleased-content" style="padding: 1rem 0"></div>
|
||
</div>
|
||
|
||
<div id="page-podcasts" class="page">
|
||
<header class="detail-header">
|
||
<img
|
||
id="podcasts-detail-image"
|
||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||
alt=""
|
||
class="detail-header-image artist"
|
||
/>
|
||
<div class="detail-header-info">
|
||
<div class="type">Podcast</div>
|
||
<h1 class="title" id="podcasts-detail-name"></h1>
|
||
<div class="meta" id="podcasts-detail-meta"></div>
|
||
<div class="detail-header-actions">
|
||
<button id="play-podcasts-btn" class="btn-primary" title="Play Latest Episode">
|
||
<use svg="./images/play.svg" size="20" />
|
||
<span>Play Latest</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<div class="content-section">
|
||
<h2 class="section-title">Episodes</h2>
|
||
<div class="track-list" id="podcasts-episodes-container"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="page-podcasts-browse" class="page">
|
||
<h2 class="section-title">Browse Podcasts</h2>
|
||
<div class="search-tabs">
|
||
<button class="search-tab active" data-tab="trending">Trending</button>
|
||
<button class="search-tab" data-tab="recent">Recent</button>
|
||
</div>
|
||
<div class="search-tab-content active" id="podcasts-tab-trending">
|
||
<div class="card-grid" id="podcasts-trending-container"></div>
|
||
</div>
|
||
<div class="search-tab-content" id="podcasts-tab-recent">
|
||
<div class="card-grid" id="podcasts-recent-container"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="page-tracker-artist" class="page">
|
||
<header class="detail-header">
|
||
<img
|
||
id="tracker-artist-detail-image"
|
||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||
alt=""
|
||
class="detail-header-image artist"
|
||
/>
|
||
<div class="detail-header-info">
|
||
<div class="type">Unreleased Artist</div>
|
||
<h1 class="title" id="tracker-artist-detail-name"></h1>
|
||
<div class="meta" id="tracker-artist-detail-meta"></div>
|
||
<div class="detail-header-actions">
|
||
<button id="play-tracker-artist-btn" class="btn-primary" title="Shuffle Play">
|
||
<use svg="./images/play.svg" size="20" />
|
||
<span>Shuffle Play</span>
|
||
</button>
|
||
<button id="download-tracker-artist-btn" class="btn-primary" title="Download">
|
||
<use svg="!lucide/download.svg" size="20" />
|
||
<span>Download All</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<div id="tracker-artist-projects-container"></div>
|
||
</div>
|
||
|
||
<div id="page-album" class="page">
|
||
<header class="detail-header">
|
||
<img
|
||
id="album-detail-image"
|
||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||
alt=""
|
||
class="detail-header-image"
|
||
/>
|
||
<div class="detail-header-info">
|
||
<h1 class="title" id="album-detail-title"></h1>
|
||
<div class="meta" id="album-detail-meta"></div>
|
||
<div class="meta" id="album-detail-producer"></div>
|
||
<div class="detail-header-actions">
|
||
<button id="play-album-btn" class="btn-primary" title="Play Album">
|
||
<use svg="./images/play.svg" size="20" />
|
||
<span>Play Album</span>
|
||
</button>
|
||
<button id="shuffle-album-btn" class="btn-primary" title="Shuffle">
|
||
<use svg="!lucide/shuffle.svg" size="18" />
|
||
<span>Shuffle</span>
|
||
</button>
|
||
<button id="album-mix-btn" class="btn-primary" style="display: none" title="Mix">
|
||
<use svg="./images/mix.svg" size="20" />
|
||
<span>Mix</span>
|
||
</button>
|
||
<button id="download-album-btn" class="btn-primary" title="Download">
|
||
<use svg="!lucide/download.svg" size="20" />
|
||
<span>Download</span>
|
||
</button>
|
||
<button id="add-album-to-playlist-btn" class="btn-secondary" title="Add to Playlist">
|
||
<use svg="!lucide/plus.svg" size="20" />
|
||
<span>Add to Playlist</span>
|
||
</button>
|
||
<button
|
||
id="like-album-btn"
|
||
class="btn-secondary like-btn"
|
||
data-action="toggle-like"
|
||
data-type="album"
|
||
title="Save to Favorites"
|
||
>
|
||
<use svg="!lucide/heart.svg" class="heart-icon" size="20" />
|
||
<span>Save</span>
|
||
</button>
|
||
<button
|
||
id="album-menu-btn"
|
||
class="btn-secondary"
|
||
data-action="card-menu"
|
||
data-type="album"
|
||
title="More options"
|
||
>
|
||
<use svg="!lucide/ellipsis-vertical.svg" size="24" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<div class="album-content-layout">
|
||
<div class="track-list" id="album-detail-tracklist"></div>
|
||
|
||
<section
|
||
class="content-section"
|
||
id="album-section-more-albums"
|
||
style="display: none; margin-top: 3rem"
|
||
>
|
||
<h2 class="section-title" id="album-title-more-albums">from Artist</h2>
|
||
<div class="card-grid" id="album-detail-more-albums"></div>
|
||
</section>
|
||
|
||
<section class="content-section" id="album-section-eps" style="display: none; margin-top: 3rem">
|
||
<h2 class="section-title" id="album-title-eps">EPs and Singles</h2>
|
||
<div class="card-grid" id="album-detail-eps"></div>
|
||
</section>
|
||
|
||
<section
|
||
class="content-section"
|
||
id="album-section-similar-artists"
|
||
style="display: none; margin-top: 3rem"
|
||
>
|
||
<h2 class="section-title">Similar Artists</h2>
|
||
<div class="card-grid" id="album-detail-similar-artists"></div>
|
||
</section>
|
||
|
||
<section
|
||
class="content-section"
|
||
id="album-section-similar-albums"
|
||
style="display: none; margin-top: 3rem"
|
||
>
|
||
<h2 class="section-title">Similar Albums</h2>
|
||
<div class="card-grid" id="album-detail-similar-albums"></div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
|
||
<section id="page-playlist" class="page">
|
||
<header class="detail-header">
|
||
<div class="detail-header-cover-container">
|
||
<img
|
||
id="playlist-detail-image"
|
||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||
class="detail-header-image"
|
||
alt="Playlist Cover"
|
||
/>
|
||
<div id="playlist-detail-collage" class="detail-header-collage" style="display: none"></div>
|
||
</div>
|
||
<div class="detail-header-info">
|
||
<h1 class="title" id="playlist-detail-title"></h1>
|
||
<div class="meta" id="playlist-detail-meta"></div>
|
||
<div class="meta" id="playlist-detail-description" class="detail-description"></div>
|
||
<div class="detail-header-actions">
|
||
<button id="play-playlist-btn" class="btn-primary" title="Play">
|
||
<use svg="./images/play.svg" size="20" />
|
||
<span>Play</span>
|
||
</button>
|
||
<button id="shuffle-playlist-btn" class="btn-primary" title="Shuffle">
|
||
<use svg="!lucide/shuffle.svg" size="18" />
|
||
<span>Shuffle</span>
|
||
</button>
|
||
<button id="download-playlist-btn" class="btn-primary" title="Download">
|
||
<use svg="!lucide/download.svg" size="20" />
|
||
<span>Download</span>
|
||
</button>
|
||
<button
|
||
id="like-playlist-btn"
|
||
class="btn-secondary like-btn"
|
||
data-action="toggle-like"
|
||
data-type="playlist"
|
||
title="Save to Favorites"
|
||
>
|
||
<use svg="!lucide/heart.svg" class="heart-icon" size="20" />
|
||
<span>Save</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<form class="track-list-search-container" onsubmit="return false;">
|
||
<use svg="!lucide/search.svg" class="search-icon" size="20" />
|
||
<input
|
||
type="search"
|
||
id="track-list-search-input"
|
||
placeholder="Search tracks..."
|
||
class="track-list-search-input"
|
||
autocomplete="off"
|
||
autocorrect="off"
|
||
autocapitalize="off"
|
||
spellcheck="false"
|
||
/>
|
||
<button
|
||
type="button"
|
||
class="search-clear-btn btn-icon"
|
||
title="Clear search"
|
||
style="display: none"
|
||
>
|
||
×
|
||
</button>
|
||
</form>
|
||
<div id="playlist-detail-tracklist" class="track-list"></div>
|
||
|
||
<section
|
||
class="content-section"
|
||
id="playlist-section-recommended"
|
||
style="display: none; margin-top: 3rem"
|
||
>
|
||
<div class="section-header-row">
|
||
<div>
|
||
<h2 class="section-title">Recommended Songs</h2>
|
||
<p style="color: grey; margin-bottom: 15px">Suggested Songs From Your Playlist</p>
|
||
</div>
|
||
<button
|
||
id="refresh-recommended-songs-btn"
|
||
class="btn-secondary"
|
||
title="Refresh Recommendations"
|
||
>
|
||
<use svg="!lucide/rotate-cw.svg" size="18" />
|
||
</button>
|
||
</div>
|
||
<div class="track-list" id="playlist-detail-recommended"></div>
|
||
</section>
|
||
</section>
|
||
|
||
<section id="page-folder" class="page">
|
||
<header class="detail-header">
|
||
<img
|
||
id="folder-detail-image"
|
||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||
class="detail-header-image"
|
||
alt="Folder Cover"
|
||
/>
|
||
<div class="detail-header-info">
|
||
<h1 class="title" id="folder-detail-title"></h1>
|
||
<div class="meta" id="folder-detail-meta"></div>
|
||
<div class="detail-header-actions">
|
||
<button id="delete-folder-btn" class="btn-secondary danger">Delete Folder</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<div class="card-grid" id="folder-detail-container"></div>
|
||
</section>
|
||
|
||
<section id="page-mix" class="page">
|
||
<header class="detail-header">
|
||
<img
|
||
id="mix-detail-image"
|
||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||
class="detail-header-image"
|
||
alt="Mix Cover"
|
||
/>
|
||
<div class="detail-header-info">
|
||
<h1 class="title" id="mix-detail-title"></h1>
|
||
<div class="meta" id="mix-detail-meta"></div>
|
||
<div class="meta" id="mix-detail-description" class="detail-description"></div>
|
||
<div class="detail-header-actions">
|
||
<button id="play-mix-btn" class="btn-primary" title="Play">
|
||
<use svg="./images/play.svg" size="20" />
|
||
<span>Play</span>
|
||
</button>
|
||
<button id="download-mix-btn" class="btn-primary" title="Download">
|
||
<use svg="!lucide/download.svg" size="20" />
|
||
<span>Download</span>
|
||
</button>
|
||
<button
|
||
id="like-mix-btn"
|
||
class="btn-secondary like-btn"
|
||
data-action="toggle-like"
|
||
data-type="mix"
|
||
title="Save to Favorites"
|
||
style="display: none"
|
||
>
|
||
<use svg="!lucide/heart.svg" class="heart-icon" size="20" />
|
||
<span>Save</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<div id="mix-detail-tracklist" class="track-list"></div>
|
||
</section>
|
||
|
||
<div id="page-artist" class="page">
|
||
<header class="detail-header">
|
||
<img
|
||
id="artist-detail-image"
|
||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||
alt="Artist"
|
||
class="detail-header-image artist"
|
||
/>
|
||
<div class="detail-header-info">
|
||
<h1 class="title" id="artist-detail-name"></h1>
|
||
<div class="meta" id="artist-detail-meta"></div>
|
||
<div id="artist-detail-socials" class="artist-socials"></div>
|
||
<div id="artist-detail-bio" class="artist-bio"></div>
|
||
<div class="detail-header-actions">
|
||
<button id="play-artist-radio-btn" class="btn-primary" title="Artist Radio">
|
||
<use svg="!lucide/volume-1.svg" size="20" />
|
||
<span>Radio</span>
|
||
</button>
|
||
<button id="shuffle-artist-btn" class="btn-primary" title="Shuffle">
|
||
<use svg="!lucide/shuffle.svg" size="18" />
|
||
<span>Shuffle</span>
|
||
</button>
|
||
<button id="artist-mix-btn" class="btn-primary" style="display: none" title="Mix">
|
||
<use svg="./images/mix.svg" size="20" />
|
||
<span>Mix</span>
|
||
</button>
|
||
<button id="download-discography-btn" class="btn-primary" title="Download">
|
||
<use svg="!lucide/download.svg" size="20" />
|
||
<span>Download</span>
|
||
</button>
|
||
<button
|
||
id="like-artist-btn"
|
||
class="btn-secondary like-btn"
|
||
data-action="toggle-like"
|
||
data-type="artist"
|
||
title="Save to Favorites"
|
||
>
|
||
<use svg="!lucide/heart.svg" class="heart-icon" size="20" />
|
||
<span>Save</span>
|
||
</button>
|
||
</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" id="artist-section-in-library" style="display: none">
|
||
<h2 class="section-title">
|
||
<button
|
||
id="in-library-toggle"
|
||
aria-expanded="false"
|
||
aria-controls="artist-detail-in-library"
|
||
style="
|
||
background: none;
|
||
border: none;
|
||
color: inherit;
|
||
font: inherit;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0;
|
||
user-select: none;
|
||
"
|
||
>
|
||
In Your Library
|
||
<use
|
||
svg="!lucide/chevron-right.svg"
|
||
size="22"
|
||
id="in-library-chevron"
|
||
style="transition: transform 0.25s ease; flex-shrink: 0"
|
||
/>
|
||
</button>
|
||
</h2>
|
||
<div class="track-list" id="artist-detail-in-library" hidden></div>
|
||
</section>
|
||
<section class="content-section">
|
||
<h2 class="section-title">Albums</h2>
|
||
<div class="card-grid" id="artist-detail-albums"></div>
|
||
</section>
|
||
<section class="content-section" id="artist-section-videos" style="display: none">
|
||
<h2 class="section-title">Videos</h2>
|
||
<div class="card-grid" id="artist-detail-videos"></div>
|
||
</section>
|
||
<section class="content-section" id="artist-section-eps" style="display: none">
|
||
<h2 class="section-title">EPs and Singles</h2>
|
||
<div class="card-grid" id="artist-detail-eps"></div>
|
||
</section>
|
||
<section
|
||
class="content-section"
|
||
id="artist-section-load-unreleased"
|
||
style="display: none; margin: 1.5rem 0"
|
||
>
|
||
<button id="load-unreleased-btn" class="btn-primary">Load Unreleased Projects</button>
|
||
</section>
|
||
<section class="content-section" id="artist-section-unreleased" style="display: none">
|
||
<h2 class="section-title">Unreleased Music</h2>
|
||
<div class="card-grid" id="artist-detail-unreleased"></div>
|
||
</section>
|
||
<section class="content-section" id="artist-section-similar" style="display: none">
|
||
<h2 class="section-title">Similar Artists</h2>
|
||
<div class="card-grid" id="artist-detail-similar"></div>
|
||
</section>
|
||
</div>
|
||
|
||
<div id="page-track" class="page">
|
||
<header class="detail-header">
|
||
<img
|
||
id="track-detail-image"
|
||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||
class="detail-header-image"
|
||
alt="Track Cover"
|
||
/>
|
||
<div class="detail-header-info">
|
||
<div class="type">Song</div>
|
||
<h1 class="title" id="track-detail-title"></h1>
|
||
<div class="meta">
|
||
<span id="track-detail-artist"></span> • <span id="track-detail-album"></span> •
|
||
<span id="track-detail-year"></span>
|
||
</div>
|
||
<div class="detail-header-actions">
|
||
<button id="play-track-btn" class="btn-primary" title="Play">
|
||
<use svg="./images/play.svg" size="20" />
|
||
<span>Play</span>
|
||
</button>
|
||
<button id="track-lyrics-btn" class="btn-secondary" title="Lyrics">
|
||
<use svg="./images/mic.svg" size="20" />
|
||
<span>Lyrics</span>
|
||
</button>
|
||
<button id="share-track-btn" class="btn-secondary" title="Share">
|
||
<use svg="!lucide/share.svg" size="20" />
|
||
<span>Share</span>
|
||
</button>
|
||
<button
|
||
id="like-track-btn"
|
||
class="btn-secondary like-btn"
|
||
data-action="toggle-like"
|
||
data-type="track"
|
||
title="Save to Favorites"
|
||
>
|
||
<use svg="!lucide/heart.svg" class="heart-icon" size="20" />
|
||
<span>Save</span>
|
||
</button>
|
||
<button id="download-track-btn" class="btn-secondary" title="Download">
|
||
<use svg="!lucide/download.svg" size="20" />
|
||
<span>Download</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<section class="content-section" id="track-album-section">
|
||
<h2 class="section-title">More from Album</h2>
|
||
<div class="track-list" id="track-detail-album-tracks"></div>
|
||
</section>
|
||
<section class="content-section" id="track-similar-section" style="display: none">
|
||
<h2 class="section-title">Similar Tracks</h2>
|
||
<div class="track-list" id="track-detail-similar-tracks"></div>
|
||
</section>
|
||
</div>
|
||
|
||
<div id="page-profile" class="page">
|
||
<div class="profile-header-container">
|
||
<div class="profile-banner" id="profile-banner"></div>
|
||
<div class="profile-info-section">
|
||
<img id="profile-avatar" src="/assets/appicon.png" class="profile-avatar" alt="Avatar" />
|
||
<div class="profile-details">
|
||
<h1 id="profile-display-name">User</h1>
|
||
<div id="profile-username" class="profile-username">@username</div>
|
||
<div id="profile-status" class="profile-status" style="display: none"></div>
|
||
<div id="profile-about" class="profile-about"></div>
|
||
<div class="profile-links">
|
||
<a
|
||
id="profile-website"
|
||
href="#"
|
||
target="_blank"
|
||
class="profile-link"
|
||
style="display: none"
|
||
>Website</a
|
||
>
|
||
<a
|
||
id="profile-lastfm"
|
||
href="#"
|
||
target="_blank"
|
||
class="profile-link"
|
||
style="display: none"
|
||
>Last.fm</a
|
||
>
|
||
</div>
|
||
</div>
|
||
<div class="profile-actions">
|
||
<button id="profile-edit-btn" class="btn-secondary" style="display: none">
|
||
Edit Profile
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="profile-content">
|
||
<h2 class="section-title">Public Playlists</h2>
|
||
<div class="card-grid" id="profile-playlists-container"></div>
|
||
<div id="profile-favorite-albums-section" style="display: none; margin-top: 3rem">
|
||
<h2 class="section-title">Favorite Albums of All Time</h2>
|
||
<div id="profile-favorite-albums-container"></div>
|
||
</div>
|
||
<div id="profile-recent-scrobbles-section" style="display: none; margin-top: 3rem">
|
||
<div style="display: flex; align-items: baseline; gap: 1rem; margin-bottom: 1rem">
|
||
<h2 class="section-title" style="margin-bottom: 0">Recent Scrobbling</h2>
|
||
<span style="font-size: 0.8rem; color: var(--muted-foreground)"
|
||
>Powered by Last.fm</span
|
||
>
|
||
</div>
|
||
<div class="track-list" id="profile-recent-scrobbles-container"></div>
|
||
</div>
|
||
<div id="profile-top-artists-section" style="display: none; margin-top: 3rem">
|
||
<h2 class="section-title">Top Artists</h2>
|
||
<div class="card-grid" id="profile-top-artists-container"></div>
|
||
</div>
|
||
<div id="profile-top-albums-section" style="display: none; margin-top: 3rem">
|
||
<h2 class="section-title">Top Albums</h2>
|
||
<div class="card-grid" id="profile-top-albums-container"></div>
|
||
</div>
|
||
<div id="profile-top-tracks-section" style="display: none; margin-top: 3rem">
|
||
<h2 class="section-title">Top Tracks</h2>
|
||
<div class="track-list" id="profile-top-tracks-container"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="page-settings" class="page">
|
||
<h2 class="section-title">Settings</h2>
|
||
<form
|
||
class="track-list-search-container settings-search-container"
|
||
onsubmit="return false;"
|
||
style="margin: 1rem 0"
|
||
>
|
||
<use svg="!lucide/search.svg" class="search-icon" size="20" />
|
||
<input
|
||
type="search"
|
||
id="settings-search-input"
|
||
placeholder="Search settings..."
|
||
class="track-list-search-input"
|
||
autocomplete="off"
|
||
autocorrect="off"
|
||
autocapitalize="off"
|
||
spellcheck="false"
|
||
/>
|
||
<button
|
||
type="button"
|
||
class="search-clear-btn btn-icon"
|
||
title="Clear search"
|
||
style="display: none"
|
||
>
|
||
×
|
||
</button>
|
||
</form>
|
||
<div class="settings-tabs">
|
||
<button class="settings-tab active" data-tab="appearance">Appearance</button>
|
||
<button class="settings-tab" data-tab="interface">Interface</button>
|
||
<button class="settings-tab" data-tab="scrobbling">Scrobbling</button>
|
||
<button class="settings-tab" data-tab="audio">Audio</button>
|
||
<button class="settings-tab" data-tab="downloads">Downloads</button>
|
||
<button class="settings-tab" data-tab="instances">Instances</button>
|
||
<button class="settings-tab" data-tab="system">System</button>
|
||
</div>
|
||
<div class="settings-tab-content active" id="settings-tab-appearance">
|
||
<div class="settings-list">
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Theme</span>
|
||
<span class="description">Choose your preferred color scheme</span>
|
||
</div>
|
||
</div>
|
||
<div class="theme-picker" id="theme-picker">
|
||
<div class="theme-option" data-theme="system">System</div>
|
||
<div class="theme-option" data-theme="monochrome">Black</div>
|
||
<div class="theme-option" data-theme="white">White</div>
|
||
<div class="theme-option" data-theme="dark">Dark</div>
|
||
<div class="theme-option" data-theme="ocean">Ocean</div>
|
||
<div class="theme-option" data-theme="purple">Purple</div>
|
||
<div class="theme-option" data-theme="forest">Forest</div>
|
||
<div class="theme-option" data-theme="mocha">Mocha</div>
|
||
<div class="theme-option" data-theme="machiatto">Machiatto</div>
|
||
<div class="theme-option" data-theme="frappe">Frappé</div>
|
||
<div class="theme-option" data-theme="latte">Latte</div>
|
||
<div class="theme-option" data-theme="custom">Custom</div>
|
||
</div>
|
||
<div id="applied-community-theme-container" style="display: none; margin-top: 1rem">
|
||
<button
|
||
id="applied-community-theme-btn"
|
||
class="btn-secondary"
|
||
style="
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 1rem;
|
||
"
|
||
>
|
||
<span>Applied Community Theme</span>
|
||
<span
|
||
id="applied-theme-name"
|
||
style="color: var(--muted-foreground); font-size: 0.9rem"
|
||
></span>
|
||
</button>
|
||
<div
|
||
id="community-theme-details-panel"
|
||
style="
|
||
display: none;
|
||
padding: 1rem;
|
||
background: var(--secondary);
|
||
border-radius: var(--radius);
|
||
margin-top: 0.5rem;
|
||
border: 1px solid var(--border);
|
||
"
|
||
>
|
||
<div
|
||
style="margin-bottom: 0.5rem; font-weight: 600"
|
||
id="ct-details-title"
|
||
></div>
|
||
<div
|
||
style="
|
||
margin-bottom: 1rem;
|
||
font-size: 0.9rem;
|
||
color: var(--muted-foreground);
|
||
"
|
||
id="ct-details-author"
|
||
></div>
|
||
<button id="ct-unapply-btn" class="btn-secondary danger" style="width: 100%">
|
||
Unapply Theme
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="custom-theme-editor" id="custom-theme-editor">
|
||
<h4>Custom Theme</h4>
|
||
<div class="theme-color-grid" id="theme-color-grid"></div>
|
||
<div class="theme-actions">
|
||
<button class="btn-secondary" id="apply-custom-theme">Apply Theme</button>
|
||
<button class="btn-secondary" id="reset-custom-theme">Reset</button>
|
||
</div>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Community Themes</span>
|
||
<span class="description"
|
||
>Browse and apply themes created by the community</span
|
||
>
|
||
</div>
|
||
<button id="open-theme-store-btn" class="btn-secondary">Open Theme Store</button>
|
||
</div>
|
||
<div class="setting-item font-settings-container">
|
||
<div class="info">
|
||
<span class="label">Font</span>
|
||
<span class="description"
|
||
>Choose from presets, Google Fonts, URLs, or upload your own</span
|
||
>
|
||
</div>
|
||
<div class="font-input-group">
|
||
<select id="font-type-select" class="font-type-select">
|
||
<option value="preset">Preset</option>
|
||
<option value="google">Google Fonts</option>
|
||
<option value="url">URL</option>
|
||
<option value="upload">Upload</option>
|
||
</select>
|
||
|
||
<div id="font-preset-section" class="font-section">
|
||
<select id="font-preset-select">
|
||
<option value="Inter">Inter (Default)</option>
|
||
<option value="Apple Music">Apple Music</option>
|
||
<option value="IBM Plex Mono">IBM Plex Mono</option>
|
||
<option value="Roboto">Roboto</option>
|
||
<option value="Open Sans">Open Sans</option>
|
||
<option value="Lato">Lato</option>
|
||
<option value="Montserrat">Montserrat</option>
|
||
<option value="Poppins">Poppins</option>
|
||
<option value="System UI">System UI</option>
|
||
<option value="monospace">Monospace</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div id="font-google-section" class="font-section" style="display: none">
|
||
<input
|
||
type="text"
|
||
id="font-google-input"
|
||
placeholder="Enter Google Fonts URL or font name (e.g., IBM Plex Mono)"
|
||
class="font-input"
|
||
/>
|
||
<button id="font-google-apply" class="btn-secondary">Apply</button>
|
||
</div>
|
||
|
||
<div id="font-url-section" class="font-section" style="display: none">
|
||
<input
|
||
type="text"
|
||
id="font-url-input"
|
||
placeholder="Enter font file URL (.woff, .woff2, .ttf, .otf)"
|
||
class="font-input"
|
||
/>
|
||
<input
|
||
type="text"
|
||
id="font-url-name"
|
||
placeholder="Font name (optional)"
|
||
class="font-input font-name-input"
|
||
/>
|
||
<button id="font-url-apply" class="btn-secondary">Apply</button>
|
||
</div>
|
||
|
||
<div id="font-upload-section" class="font-section" style="display: none">
|
||
<input
|
||
type="file"
|
||
id="font-upload-input"
|
||
accept=".woff,.woff2,.ttf,.otf"
|
||
class="font-file-input"
|
||
/>
|
||
<div id="uploaded-fonts-list" class="uploaded-fonts-list"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Font Size</span>
|
||
<span class="description">Adjust the base font size (50% - 200%)</span>
|
||
</div>
|
||
<div class="font-size-control">
|
||
<input
|
||
type="range"
|
||
id="font-size-slider"
|
||
min="50"
|
||
max="200"
|
||
value="100"
|
||
step="5"
|
||
class="font-size-slider"
|
||
/>
|
||
<input
|
||
type="number"
|
||
id="font-size-input"
|
||
min="50"
|
||
max="200"
|
||
value="100"
|
||
step="1"
|
||
class="font-size-number-input"
|
||
title="Enter font size percentage"
|
||
/>
|
||
<span class="font-size-unit">%</span>
|
||
<button id="font-size-reset" class="btn-secondary" title="Reset to default">
|
||
Reset
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Waveform Seekbar</span>
|
||
<span class="description"
|
||
>Show a visual waveform of the track in the progress bar
|
||
(Experimental)</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="waveform-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Album Cover Background</span>
|
||
<span class="description"
|
||
>Use the album cover as a blurred background on album pages and as primary
|
||
color</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="album-background-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Dynamic Colors</span>
|
||
<span class="description"
|
||
>Automatically change the app accent color based on the currently playing
|
||
track's album art</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="dynamic-color-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Full-screen Visualizer</span>
|
||
<span class="description">Enable the visualizer in full-screen mode</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="visualizer-enabled-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item" id="visualizer-preset-setting">
|
||
<div class="info">
|
||
<span class="label">Visualizer Style</span>
|
||
<span class="description">Select the visualization style</span>
|
||
</div>
|
||
<select id="visualizer-preset-select">
|
||
<option value="lcd">LCD Pixels</option>
|
||
<option value="particles">Particles</option>
|
||
<option value="unknown-pleasures">Unknown Pleasures</option>
|
||
<option value="butterchurn">Butterchurn (Milkdrop)</option>
|
||
<option value="kawarp">Kawarp</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-item" id="visualizer-mode-setting">
|
||
<div class="info">
|
||
<span class="label">Visualizer Mode</span>
|
||
<span class="description"
|
||
>Choose how the visualizer is displayed in full-screen</span
|
||
>
|
||
</div>
|
||
<select id="visualizer-mode-select">
|
||
<option value="solid">Solid Background</option>
|
||
<option value="blended">Blended on Cover Art</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-item" id="visualizer-smart-intensity-setting">
|
||
<div class="info">
|
||
<span class="label">Smart Intensity Switching</span>
|
||
<span class="description"
|
||
>Automatically adjust visualizer intensity based on song energy</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="smart-intensity-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item" id="visualizer-sensitivity-setting">
|
||
<div class="info">
|
||
<span class="label">Visualizer Sensitivity</span>
|
||
<span class="description"
|
||
>Adjust the intensity of the visualizer effects.
|
||
<strong
|
||
>Warning: High sensitivity may cause flashing lights and rapid motion,
|
||
which can trigger seizures in people with photosensitive
|
||
epilepsy.</strong
|
||
></span
|
||
>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 10px">
|
||
<input
|
||
type="range"
|
||
id="visualizer-sensitivity-slider"
|
||
min="0.1"
|
||
max="2.0"
|
||
step="0.1"
|
||
value="0.6"
|
||
style="width: 100px"
|
||
/>
|
||
<span
|
||
id="visualizer-sensitivity-value"
|
||
style="font-size: 0.9rem; min-width: 3em; text-align: right"
|
||
>60%</span
|
||
>
|
||
</div>
|
||
</div>
|
||
<div class="setting-item" id="visualizer-dimming-setting">
|
||
<div class="info">
|
||
<span class="label">Visualizer Brightness</span>
|
||
<span class="description"
|
||
>Adjust the brightness of the visualizer. Lower this if the visualizer is
|
||
too bright for you.</span
|
||
>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 10px">
|
||
<input
|
||
type="range"
|
||
id="visualizer-dimming-slider"
|
||
min="0.1"
|
||
max="1.0"
|
||
step="0.05"
|
||
value="1.0"
|
||
style="width: 100px"
|
||
/>
|
||
<span
|
||
id="visualizer-dimming-value"
|
||
style="font-size: 0.9rem; min-width: 3em; text-align: right"
|
||
>100%</span
|
||
>
|
||
</div>
|
||
</div>
|
||
<div class="setting-item" id="butterchurn-cycle-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Cycle Presets</span>
|
||
<span class="description">Automatically change visualizer presets</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="butterchurn-cycle-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div
|
||
class="setting-item"
|
||
id="butterchurn-specific-preset-setting"
|
||
style="display: none"
|
||
>
|
||
<div class="info">
|
||
<span class="label">Current Preset</span>
|
||
<span class="description">Select a specific Butterchurn preset</span>
|
||
</div>
|
||
<select id="butterchurn-specific-preset-select" style="width: 200px">
|
||
<option value="">Loading...</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-item" id="butterchurn-duration-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Cycle Duration</span>
|
||
<span class="description">Seconds between preset changes</span>
|
||
</div>
|
||
<input
|
||
type="number"
|
||
id="butterchurn-duration-input"
|
||
min="5"
|
||
max="300"
|
||
value="30"
|
||
class="template-input"
|
||
style="width: 80px"
|
||
/>
|
||
</div>
|
||
<div class="setting-item" id="butterchurn-randomize-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Randomize Presets</span>
|
||
<span class="description"
|
||
>Select next preset randomly instead of sequentially</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="butterchurn-randomize-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="settings-tab-content" id="settings-tab-interface">
|
||
<div class="settings-list">
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Recommended Songs</span>
|
||
<span class="description">Display recommended songs on the home page</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="show-recommended-songs-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Recommended Albums</span>
|
||
<span class="description">Display recommended albums on the home page</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="show-recommended-albums-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Recommended Artists</span>
|
||
<span class="description">Display recommended artists on the home page</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="show-recommended-artists-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Jump Back In</span>
|
||
<span class="description"
|
||
>Display recent albums, playlists, and mixes on the home page</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="show-jump-back-in-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Editor's Picks</span>
|
||
<span class="description"
|
||
>Display curated album selections on the home page</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="show-editors-picks-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Shuffle Editor's Picks</span>
|
||
<span class="description"
|
||
>Randomize the order of editor's picks on each load</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="shuffle-editors-picks-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Compact Artists</span>
|
||
<span class="description"
|
||
>Show artist cards in a compact, horizontal layout</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="compact-artist-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Compact Albums</span>
|
||
<span class="description"
|
||
>Show album cards in a compact, horizontal layout</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="compact-album-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group" id="sidebar-order-settings-group">
|
||
<div class="sidebar-settings-section sidebar-settings-main">
|
||
<span class="sidebar-settings-section-label">TOP SECTION</span>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Home in Sidebar</span>
|
||
<span class="description"
|
||
>Display the Home link in the sidebar navigation</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="sidebar-show-home-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Library in Sidebar</span>
|
||
<span class="description"
|
||
>Display the Library link in the sidebar navigation</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="sidebar-show-library-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Recent in Sidebar</span>
|
||
<span class="description"
|
||
>Display the Recent link in the sidebar navigation</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="sidebar-show-recent-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Unreleased in Sidebar</span>
|
||
<span class="description"
|
||
>Display the Unreleased link in the sidebar navigation</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="sidebar-show-unreleased-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Donate in Sidebar</span>
|
||
<span class="description"
|
||
>Display the Donate link in the sidebar navigation</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="sidebar-show-donate-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Settings in Sidebar</span>
|
||
<span class="description"
|
||
>Display the Settings link in the sidebar navigation</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="sidebar-show-settings-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="sidebar-settings-section sidebar-settings-bottom">
|
||
<span class="sidebar-settings-section-label">BOTTOM SECTION</span>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show About in Sidebar</span>
|
||
<span class="description"
|
||
>Display the About link in the sidebar navigation</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="sidebar-show-about-bottom-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Download in Sidebar</span>
|
||
<span class="description"
|
||
>Display the Download link in the sidebar navigation</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="sidebar-show-download-bottom-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Discord in Sidebar</span>
|
||
<span class="description"
|
||
>Display the Discord link in the sidebar navigation</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="sidebar-show-discordbtn-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show GitHub in Sidebar</span>
|
||
<span class="description"
|
||
>Display the GitHub link in the sidebar navigation</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="sidebar-show-githubbtn-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Close Modals on Navigation</span>
|
||
<span class="description"
|
||
>Close open modals and panels (like lyrics, queue) when navigating back or
|
||
to a new page</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="close-modals-on-navigation-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Intercept Back to Close Modals</span>
|
||
<span class="description"
|
||
>When pressing back, close open modals/panels first without navigating.
|
||
Press back again to actually go back.</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="intercept-back-to-close-modals-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Now Playing View Mode</span>
|
||
<span class="description">Choose what shows when you click the album art</span>
|
||
</div>
|
||
<select id="now-playing-mode">
|
||
<option value="album">Go to Album</option>
|
||
<option value="cover">Fullscreen Mode</option>
|
||
<option value="lyrics">Lyrics Panel</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Fullscreen Cover Click Action</span>
|
||
<span class="description"
|
||
>Choose what happens when you click the cover in fullscreen mode</span
|
||
>
|
||
</div>
|
||
<select id="fullscreen-cover-click-action">
|
||
<option value="exit">Exit fullscreen mode</option>
|
||
<option value="hide-ui">Hide UI</option>
|
||
<option value="pause-resume">Pause/resume track</option>
|
||
<option value="next">Skip song</option>
|
||
<option value="previous">Previous song</option>
|
||
<option value="nothing">Do nothing</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="settings-tab-content" id="settings-tab-scrobbling">
|
||
<div class="settings-list">
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Scrobble Threshold</span>
|
||
<span class="description"
|
||
>Percentage of track to play before scrobbling (1-100%)</span
|
||
>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 10px">
|
||
<input
|
||
type="range"
|
||
id="scrobble-percentage-slider"
|
||
min="1"
|
||
max="100"
|
||
step="1"
|
||
value="75"
|
||
style="width: 100px"
|
||
/>
|
||
<input
|
||
type="number"
|
||
id="scrobble-percentage-input"
|
||
min="1"
|
||
max="100"
|
||
value="75"
|
||
style="
|
||
width: 50px;
|
||
font-size: 0.9rem;
|
||
text-align: center;
|
||
padding: 4px;
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 4px;
|
||
background: var(--input-bg);
|
||
color: var(--text-color);
|
||
"
|
||
/>
|
||
<span style="font-size: 0.9rem">%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Last.fm Scrobbling</span>
|
||
<span class="description" id="lastfm-status"
|
||
>Connect your Last.fm account to scrobble tracks</span
|
||
>
|
||
</div>
|
||
<div id="lastfm-controls">
|
||
<button id="lastfm-connect-btn" class="btn-secondary">Connect Last.fm</button>
|
||
</div>
|
||
<div id="lastfm-credential-auth" style="display: none; margin-top: 12px">
|
||
<div id="lastfm-credential-form" style="display: none">
|
||
<p style="margin: 0 0 8px 0; font-size: 0.85rem; color: var(--muted)">
|
||
Enter your Last.fm credentials:
|
||
</p>
|
||
<input
|
||
type="text"
|
||
id="lastfm-username"
|
||
placeholder="Username"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
background: var(--background);
|
||
color: var(--foreground);
|
||
width: 100%;
|
||
margin-bottom: 8px;
|
||
"
|
||
/>
|
||
<input
|
||
type="password"
|
||
id="lastfm-password"
|
||
placeholder="Password"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
background: var(--background);
|
||
color: var(--foreground);
|
||
width: 100%;
|
||
margin-bottom: 8px;
|
||
"
|
||
/>
|
||
<div style="display: flex; gap: 8px">
|
||
<button
|
||
id="lastfm-login-credentials"
|
||
class="btn-secondary"
|
||
style="padding: 6px 12px; font-size: 0.85rem; flex: 1"
|
||
>
|
||
Login
|
||
</button>
|
||
<button
|
||
id="lastfm-use-oauth"
|
||
class="btn-secondary"
|
||
style="padding: 6px 12px; font-size: 0.85rem; flex: 1"
|
||
>
|
||
Use OAuth Instead
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="lastfm-credential-auth-secondary" style="display: none; margin-top: 12px">
|
||
<div style="display: flex; flex-direction: column; gap: 8px">
|
||
<div id="lastfm-credential-toggle-container-secondary">
|
||
<button
|
||
id="lastfm-show-credential-auth-secondary"
|
||
class="btn-secondary"
|
||
style="padding: 6px 12px; font-size: 0.85rem; width: 100%"
|
||
>
|
||
Login with Username/Password
|
||
</button>
|
||
</div>
|
||
<div id="lastfm-credential-form-secondary" style="display: none">
|
||
<input
|
||
type="text"
|
||
id="lastfm-username-secondary"
|
||
placeholder="Last.fm Username"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
background: var(--background);
|
||
color: var(--foreground);
|
||
width: 100%;
|
||
margin-bottom: 8px;
|
||
"
|
||
/>
|
||
<input
|
||
type="password"
|
||
id="lastfm-password-secondary"
|
||
placeholder="Last.fm Password"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
background: var(--background);
|
||
color: var(--foreground);
|
||
width: 100%;
|
||
margin-bottom: 8px;
|
||
"
|
||
/>
|
||
<div style="display: flex; gap: 8px">
|
||
<button
|
||
id="lastfm-login-credentials-secondary"
|
||
class="btn-secondary"
|
||
style="padding: 6px 12px; font-size: 0.85rem; flex: 1"
|
||
>
|
||
Login
|
||
</button>
|
||
<button
|
||
id="lastfm-use-oauth-secondary"
|
||
class="btn-secondary"
|
||
style="padding: 6px 12px; font-size: 0.85rem; flex: 1"
|
||
>
|
||
Use OAuth
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="lastfm-credential-auth-alt" style="margin-top: 12px; display: none">
|
||
<div style="display: flex; flex-direction: column; gap: 8px">
|
||
<input
|
||
type="text"
|
||
id="lastfm-username-alt"
|
||
placeholder="Last.fm Username"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
background: var(--background);
|
||
color: var(--foreground);
|
||
"
|
||
/>
|
||
<input
|
||
type="password"
|
||
id="lastfm-password-alt"
|
||
placeholder="Last.fm Password"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
background: var(--background);
|
||
color: var(--foreground);
|
||
"
|
||
/>
|
||
<div style="display: flex; gap: 8px; margin-top: 4px">
|
||
<button
|
||
id="lastfm-login-credentials-alt"
|
||
class="btn-secondary"
|
||
style="padding: 6px 12px; font-size: 0.85rem"
|
||
>
|
||
Login with Credentials
|
||
</button>
|
||
<button
|
||
id="lastfm-use-oauth-alt"
|
||
class="btn-secondary"
|
||
style="padding: 6px 12px; font-size: 0.85rem"
|
||
>
|
||
Use OAuth Instead
|
||
</button>
|
||
</div>
|
||
<div style="display: flex; gap: 8px; margin-top: 4px">
|
||
<button
|
||
id="lastfm-show-credential-auth-alt"
|
||
class="btn-secondary"
|
||
style="padding: 6px 12px; font-size: 0.85rem"
|
||
>
|
||
Login with Username/Password
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="setting-item" id="lastfm-toggle-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Enable Scrobbling</span>
|
||
<span class="description">Automatically scrobble played tracks</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="lastfm-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="setting-item" id="lastfm-love-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Love on Like</span>
|
||
<span class="description"
|
||
>Automatically 'love' tracks on Last.fm when you like them</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="lastfm-love-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="setting-item" id="lastfm-custom-creds-toggle-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Use Custom API Credentials</span>
|
||
<span class="description">Use your own Last.fm API key and secret</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="lastfm-custom-creds-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="setting-item" id="lastfm-custom-creds-setting" style="display: none">
|
||
<div class="info" style="flex: 1; min-width: 0">
|
||
<span class="label">Custom API Credentials</span>
|
||
<div style="display: flex; flex-direction: column; gap: 8px; margin-top: 8px">
|
||
<input
|
||
type="text"
|
||
id="lastfm-custom-api-key"
|
||
placeholder="API Key"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
background: var(--background);
|
||
color: var(--foreground);
|
||
"
|
||
/>
|
||
<input
|
||
type="password"
|
||
id="lastfm-custom-api-secret"
|
||
placeholder="API Secret"
|
||
style="
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
background: var(--background);
|
||
color: var(--foreground);
|
||
"
|
||
/>
|
||
<div style="display: flex; gap: 8px; margin-top: 4px">
|
||
<button
|
||
id="lastfm-save-custom-creds"
|
||
class="btn-secondary"
|
||
style="padding: 6px 12px; font-size: 0.85rem"
|
||
>
|
||
Save Credentials
|
||
</button>
|
||
<button
|
||
id="lastfm-clear-custom-creds"
|
||
class="btn-secondary danger"
|
||
style="padding: 6px 12px; font-size: 0.85rem; display: none"
|
||
>
|
||
Clear
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Libre.fm Scrobbling</span>
|
||
<span class="description" id="librefm-status"
|
||
>Connect your Libre.fm account to scrobble tracks</span
|
||
>
|
||
</div>
|
||
<div id="librefm-controls">
|
||
<button id="librefm-connect-btn" class="btn-secondary">Connect Libre.fm</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="setting-item" id="librefm-toggle-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Enable Scrobbling</span>
|
||
<span class="description">Automatically scrobble played tracks</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="librefm-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="setting-item" id="librefm-love-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Love on Like</span>
|
||
<span class="description"
|
||
>Automatically 'love' tracks on Libre.fm when you like them</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="librefm-love-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">ListenBrainz Scrobbling</span>
|
||
<span class="description"
|
||
>Submit listens to ListenBrainz (requires User Token)</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="listenbrainz-enabled-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item" id="listenbrainz-token-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">User Token</span>
|
||
<span class="description">Found on your ListenBrainz profile page</span>
|
||
</div>
|
||
<input
|
||
type="password"
|
||
id="listenbrainz-token-input"
|
||
placeholder="Enter Token"
|
||
class="template-input"
|
||
style="width: 250px"
|
||
/>
|
||
</div>
|
||
<div class="setting-item" id="listenbrainz-custom-url-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Custom API URL (Optional)</span>
|
||
<span class="description">Leave empty to use official ListenBrainz server</span>
|
||
</div>
|
||
<input
|
||
type="url"
|
||
id="listenbrainz-custom-url-input"
|
||
placeholder="https://api.listenbrainz.org"
|
||
class="template-input"
|
||
style="width: 250px"
|
||
/>
|
||
</div>
|
||
<div class="setting-item" id="listenbrainz-love-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Love on Like</span>
|
||
<span class="description"
|
||
>Automatically 'love' tracks on ListenBrainz when you like them</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="listenbrainz-love-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Maloja Scrobbling</span>
|
||
<span class="description">Submit listens to a self-hosted Maloja server</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="maloja-enabled-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item" id="maloja-token-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">API Key</span>
|
||
<span class="description">Found in your Maloja settings</span>
|
||
</div>
|
||
<input
|
||
type="password"
|
||
id="maloja-token-input"
|
||
placeholder="Enter API Key"
|
||
class="template-input"
|
||
style="width: 250px"
|
||
/>
|
||
</div>
|
||
<div class="setting-item" id="maloja-custom-url-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Maloja Server URL</span>
|
||
<span class="description">Your Maloja instance URL</span>
|
||
</div>
|
||
<input
|
||
type="url"
|
||
id="maloja-custom-url-input"
|
||
placeholder="https://maloja.example.com"
|
||
class="template-input"
|
||
style="width: 250px"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="settings-tab-content" id="settings-tab-audio">
|
||
<div class="settings-list">
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Music Provider</span>
|
||
<span class="description">Default service for searching and streaming</span>
|
||
</div>
|
||
<select id="music-provider-setting">
|
||
<option value="tidal">Tidal</option>
|
||
<option value="qobuz">Qobuz</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Streaming Quality</span>
|
||
<span class="description">Quality for streaming playback</span>
|
||
</div>
|
||
<select id="streaming-quality-setting">
|
||
<option value="HI_RES_LOSSLESS">Hi-Res Lossless (24-bit)</option>
|
||
<option value="LOSSLESS">Lossless (16-bit)</option>
|
||
<option value="HIGH">AAC 320kbps</option>
|
||
<option value="LOW">AAC 96kbps</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Quality Badges</span>
|
||
<span class="description">Display "HD" badge for Hi-Res tracks</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="show-quality-badges-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Album release year</span>
|
||
<span class="description"
|
||
>Show original album year instead of track/remaster date</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="use-album-release-year-toggle" 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" id="gapless-playback-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">ReplayGain Mode</span>
|
||
<span class="description">Normalize volume across tracks</span>
|
||
</div>
|
||
<select id="replay-gain-mode">
|
||
<option value="off">Off</option>
|
||
<option value="track">Track</option>
|
||
<option value="album">Album</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">ReplayGain Pre-Amp</span>
|
||
<span class="description">Adjust gain manually (dB)</span>
|
||
</div>
|
||
<input
|
||
type="number"
|
||
id="replay-gain-preamp"
|
||
value="3"
|
||
step="0.5"
|
||
style="width: 80px"
|
||
/>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Mono Audio</span>
|
||
<span class="description">Combine left and right channels into mono</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="mono-audio-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Exponential Volume</span>
|
||
<span class="description"
|
||
>Use logarithmic volume curve for finer low-volume control</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="exponential-volume-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Playback Speed</span>
|
||
<span class="description">Adjust playback speed (0.01x - 100x)</span>
|
||
</div>
|
||
<div class="playback-speed-control">
|
||
<input
|
||
type="range"
|
||
id="playback-speed-slider"
|
||
min="0.25"
|
||
max="4.0"
|
||
step="0.01"
|
||
value="1.0"
|
||
class="playback-speed-slider"
|
||
/>
|
||
<input
|
||
type="number"
|
||
id="playback-speed-input"
|
||
min="0.01"
|
||
max="100"
|
||
step="0.01"
|
||
value="1.0"
|
||
class="playback-speed-number-input"
|
||
/>
|
||
<span class="playback-speed-unit">x</span>
|
||
<button
|
||
id="playback-speed-reset"
|
||
class="btn-secondary"
|
||
title="Reset to default"
|
||
>
|
||
Reset
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Preserve Pitch</span>
|
||
<span class="description">Keep original pitch when changing speed</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="preserve-pitch-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Equalizer</span>
|
||
<span class="description"
|
||
>16-band parametric equalizer for fine audio control</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="equalizer-enabled-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="equalizer-container" id="equalizer-container" style="display: none">
|
||
<div class="equalizer-header">
|
||
<div class="equalizer-preset-row">
|
||
<label for="equalizer-preset-select">Preset</label>
|
||
<select id="equalizer-preset-select">
|
||
<optgroup label="Built-in Presets">
|
||
<option value="flat">Flat</option>
|
||
<option value="bass_boost">Bass Boost</option>
|
||
<option value="bass_reducer">Bass Reducer</option>
|
||
<option value="treble_boost">Treble Boost</option>
|
||
<option value="treble_reducer">Treble Reducer</option>
|
||
<option value="vocal_boost">Vocal Boost</option>
|
||
<option value="loudness">Loudness</option>
|
||
<option value="rock">Rock</option>
|
||
<option value="pop">Pop</option>
|
||
<option value="classical">Classical</option>
|
||
<option value="jazz">Jazz</option>
|
||
<option value="electronic">Electronic</option>
|
||
<option value="hip_hop">Hip-Hop</option>
|
||
<option value="r_and_b">R&B</option>
|
||
<option value="acoustic">Acoustic</option>
|
||
<option value="podcast">Podcast / Speech</option>
|
||
</optgroup>
|
||
<optgroup label="Custom Presets" id="custom-presets-optgroup">
|
||
<!-- Custom presets will be populated by JavaScript -->
|
||
</optgroup>
|
||
</select>
|
||
<label for="eq-band-count">Bands</label>
|
||
<input
|
||
type="number"
|
||
id="eq-band-count"
|
||
class="eq-band-count-input"
|
||
min="3"
|
||
max="32"
|
||
value="16"
|
||
title="Number of EQ bands (3-32)"
|
||
/>
|
||
<button
|
||
id="equalizer-reset-btn"
|
||
class="btn-secondary"
|
||
title="Reset to Flat"
|
||
>
|
||
<use svg="!lucide/rotate-ccw.svg" size="16" />
|
||
</button>
|
||
<button
|
||
id="eq-export-btn"
|
||
class="btn-secondary"
|
||
title="Export EQ settings to text"
|
||
style="font-size: 0.75rem; padding: 0.25rem 0.5rem"
|
||
>
|
||
Export
|
||
</button>
|
||
<button
|
||
id="eq-import-btn"
|
||
class="btn-secondary"
|
||
title="Import EQ settings from text or file"
|
||
style="font-size: 0.75rem; padding: 0.25rem 0.5rem"
|
||
>
|
||
Import
|
||
</button>
|
||
<input
|
||
type="file"
|
||
id="eq-import-file"
|
||
accept=".txt"
|
||
style="display: none"
|
||
/>
|
||
</div>
|
||
|
||
<div class="custom-preset-controls">
|
||
<div class="custom-preset-input-row">
|
||
<input
|
||
type="text"
|
||
id="custom-preset-name"
|
||
placeholder="Preset name (e.g., Home, Car, Work)"
|
||
maxlength="50"
|
||
/>
|
||
<button
|
||
id="save-custom-preset-btn"
|
||
class="btn-primary"
|
||
title="Save current EQ as custom preset"
|
||
>
|
||
Save
|
||
</button>
|
||
</div>
|
||
<button
|
||
id="delete-custom-preset-btn"
|
||
class="btn-secondary delete-preset-btn"
|
||
style="display: none"
|
||
title="Delete selected custom preset"
|
||
>
|
||
<use svg="!lucide/trash.svg" size="16" />
|
||
Delete Preset
|
||
</button>
|
||
</div>
|
||
|
||
<div class="eq-range-controls">
|
||
<label>DB Range:</label>
|
||
<input
|
||
type="number"
|
||
id="eq-range-min"
|
||
class="eq-range-input"
|
||
min="-60"
|
||
max="0"
|
||
value="-30"
|
||
title="Minimum gain in dB"
|
||
/>
|
||
<span>to</span>
|
||
<input
|
||
type="number"
|
||
id="eq-range-max"
|
||
class="eq-range-input"
|
||
min="0"
|
||
max="60"
|
||
value="30"
|
||
title="Maximum gain in dB"
|
||
/>
|
||
<span>dB</span>
|
||
<button
|
||
id="apply-eq-range-btn"
|
||
class="btn-secondary"
|
||
title="Apply new range to all bands"
|
||
>
|
||
Apply
|
||
</button>
|
||
<button
|
||
id="reset-eq-range-btn"
|
||
class="btn-secondary"
|
||
title="Reset to default (-30 to +30 dB)"
|
||
>
|
||
Reset
|
||
</button>
|
||
</div>
|
||
|
||
<div class="eq-freq-controls">
|
||
<label>Freq Range:</label>
|
||
<input
|
||
type="number"
|
||
id="eq-freq-min"
|
||
class="eq-freq-input"
|
||
min="10"
|
||
max="20000"
|
||
value="20"
|
||
title="Minimum frequency in Hz"
|
||
/>
|
||
<span>Hz to</span>
|
||
<input
|
||
type="number"
|
||
id="eq-freq-max"
|
||
class="eq-freq-input"
|
||
min="20"
|
||
max="96000"
|
||
value="20000"
|
||
title="Maximum frequency in Hz"
|
||
/>
|
||
<span>Hz</span>
|
||
<button
|
||
id="apply-eq-freq-btn"
|
||
class="btn-secondary"
|
||
title="Apply new frequency range"
|
||
>
|
||
Apply
|
||
</button>
|
||
<button
|
||
id="reset-eq-freq-btn"
|
||
class="btn-secondary"
|
||
title="Reset to default (20 Hz to 20 kHz)"
|
||
>
|
||
Reset
|
||
</button>
|
||
</div>
|
||
|
||
<div
|
||
class="eq-preamp-controls"
|
||
style="display: flex; align-items: center; gap: 0.5rem; margin-top: 0.5rem"
|
||
>
|
||
<label style="font-size: 0.75rem; opacity: 0.8">Preamp:</label>
|
||
<input
|
||
type="range"
|
||
id="eq-preamp-slider"
|
||
min="-20"
|
||
max="20"
|
||
step="0.1"
|
||
value="0"
|
||
style="flex: 1; max-width: 120px"
|
||
title="Preamp gain in dB"
|
||
/>
|
||
<input
|
||
type="number"
|
||
id="eq-preamp-input"
|
||
min="-20"
|
||
max="20"
|
||
step="0.1"
|
||
value="0"
|
||
style="width: 60px; padding: 0.25rem; font-size: 0.75rem"
|
||
title="Preamp value in dB"
|
||
/>
|
||
<span style="font-size: 0.75rem">dB</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="equalizer-bands-wrapper">
|
||
<canvas id="eq-response-canvas" class="eq-response-canvas"></canvas>
|
||
<div class="equalizer-bands" id="equalizer-bands">
|
||
<!-- Bands will be dynamically generated by JavaScript -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="equalizer-scale">
|
||
<span>+30 dB</span>
|
||
<span>0 dB</span>
|
||
<span>-30 dB</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="settings-tab-content" id="settings-tab-downloads">
|
||
<div class="settings-list">
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Bulk Download Method</span>
|
||
<span class="description"
|
||
>Choose how multiple tracks are downloaded together</span
|
||
>
|
||
</div>
|
||
<select id="bulk-download-method">
|
||
<option value="zip">ZIP Archive</option>
|
||
<option value="folder">Folder Picker</option>
|
||
<option value="local">Local Media Folder</option>
|
||
<option value="individual">Individual Files</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-item" id="remember-folder-setting">
|
||
<div class="info">
|
||
<span class="label">Remember Last Folder</span>
|
||
<span class="description"
|
||
>Re-use the last chosen directory for Folder Picker downloads</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="remember-folder-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item" id="reset-saved-folder-setting">
|
||
<div class="info">
|
||
<span class="label">Reset Saved Folder</span>
|
||
<span class="description">Clear the remembered Folder Picker directory</span>
|
||
</div>
|
||
<button id="reset-saved-folder-btn" class="btn-secondary">Reset</button>
|
||
</div>
|
||
<div class="setting-item" id="single-to-folder-setting">
|
||
<div class="info">
|
||
<span class="label">Single Downloads to Folder</span>
|
||
<span class="description"
|
||
>Save individual track downloads directly to the configured folder instead
|
||
of triggering a browser download</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="single-to-folder-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Force ZIP as Blob</span>
|
||
<span class="description"
|
||
>Download ZIP in memory instead of streaming to disk (use if ZIP streaming
|
||
causes issues)</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="force-zip-blob-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Download Lyrics</span>
|
||
<span class="description"
|
||
>Include .lrc files when downloading tracks/albums</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="download-lyrics-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Romaji Lyrics</span>
|
||
<span class="description"
|
||
>Convert Japanese lyrics to Romaji (Latin characters)</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="romaji-lyrics-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Download Quality</span>
|
||
<span class="description">Quality for track downloads</span>
|
||
</div>
|
||
<select id="download-quality-setting">
|
||
<option value="HI_RES_LOSSLESS">Hi-Res Lossless (24-bit)</option>
|
||
<option value="LOSSLESS">Lossless (16-bit)</option>
|
||
<option value="HIGH">AAC 320kbps</option>
|
||
<option value="LOW">AAC 96kbps</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-item" id="hi-res-download-warning" style="display: none">
|
||
<div class="info setting-details">
|
||
<span class="description">
|
||
24-bit downloads may crash the browser on some devices, or be missing
|
||
metadata.
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Lossless Container</span>
|
||
<span class="description">Container format for lossless downloads</span>
|
||
</div>
|
||
<select id="lossless-container-setting">
|
||
<option value="nochange">Don't change</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Cover Art Size</span>
|
||
<span class="description">Size for downloaded/embedded cover art</span>
|
||
</div>
|
||
<input
|
||
type="text"
|
||
id="cover-art-size-setting"
|
||
class="template-input"
|
||
style="width: 120px; text-align: right"
|
||
placeholder="1280x1280"
|
||
/>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Filename Template</span>
|
||
<span class="description"
|
||
>Customize download filenames. Available: {discNumber}, {trackNumber},
|
||
{artist}, {title}, {album}</span
|
||
>
|
||
</div>
|
||
<input
|
||
type="text"
|
||
id="filename-template"
|
||
class="template-input"
|
||
placeholder="{trackNumber} - {artist} - {title}"
|
||
/>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Folder Template</span>
|
||
<span class="description"
|
||
>Customize album folder names. Use <code>/</code> for nested folders.
|
||
Available: {albumTitle}, {albumArtist}, {year}</span
|
||
>
|
||
</div>
|
||
<input
|
||
type="text"
|
||
id="zip-folder-template"
|
||
class="template-input"
|
||
placeholder="{albumTitle} - {albumArtist} - monochrome.tf"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Generate M3U</span>
|
||
<span class="description">Include M3U playlist files in downloads</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="generate-m3u-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Generate M3U8</span>
|
||
<span class="description"
|
||
>Include extended M3U8 playlist files in downloads</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="generate-m3u8-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Generate CUE</span>
|
||
<span class="description"
|
||
>Include CUE sheets for gapless playback in downloads</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="generate-cue-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Generate NFO</span>
|
||
<span class="description"
|
||
>Include NFO files for media center compatibility</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="generate-nfo-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Generate JSON</span>
|
||
<span class="description">Include JSON files with rich metadata</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="generate-json-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Relative Paths</span>
|
||
<span class="description">Use relative paths in playlist files</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="relative-paths-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Separate Discs</span>
|
||
<span class="description"
|
||
>Put tracks in Disc folders when a release has multiple discs</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="separate-discs-zip-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Include Cover File</span>
|
||
<span class="description">Include cover.jpg in downloads</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="include-cover-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-tab-content" id="settings-tab-instances">
|
||
<div class="settings-list">
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">ADVANCED: Custom Database/Auth</span>
|
||
<span class="description"
|
||
>Configure custom PocketBase and Appwrite instances</span
|
||
>
|
||
</div>
|
||
<button id="custom-db-btn" class="btn-secondary">Configure</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div id="api-instance-manager">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">API Instances</span>
|
||
<span class="description">Manage and prioritize API instances.</span>
|
||
</div>
|
||
<button id="refresh-speed-test-btn" class="btn-secondary">
|
||
Refresh Instance List
|
||
</button>
|
||
</div>
|
||
<ul id="api-instance-list"></ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-tab-content" id="settings-tab-system">
|
||
<div class="settings-list">
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Keyboard Shortcuts</span>
|
||
<span class="description">View and customize keyboard shortcuts</span>
|
||
</div>
|
||
<button id="customize-shortcuts-btn" class="btn-secondary">Customize</button>
|
||
</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 class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Auto-Update App</span>
|
||
<span class="description"
|
||
>Automatically reload when a new version is available</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="pwa-auto-update-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item" id="desktop-update-container" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Desktop Update</span>
|
||
<span class="description">Check for updates to the desktop application</span>
|
||
</div>
|
||
<button id="check-desktop-updates-btn" class="btn-secondary">
|
||
Check for Updates
|
||
</button>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Analytics</span>
|
||
<span class="description"
|
||
>Send anonymous usage data to help improve the app</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="analytics-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Reset Local Data</span>
|
||
<span class="description"
|
||
>Clear all local storage and cached data (does not affect cloud sync)</span
|
||
>
|
||
</div>
|
||
<button id="reset-local-data-btn" class="btn-secondary danger">Reset</button>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Clear Cloud Data</span>
|
||
<span class="description"
|
||
>Delete all your data from the cloud (cannot be undone)</span
|
||
>
|
||
</div>
|
||
<button id="auth-clear-cloud-btn" class="btn-secondary danger">
|
||
Clear Cloud Data
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Backup & Restore</span>
|
||
<span class="description"
|
||
>Export or import your library and history as JSON</span
|
||
>
|
||
</div>
|
||
<div style="display: flex; gap: 0.5rem">
|
||
<button id="export-library-btn" class="btn-secondary">Export</button>
|
||
<button id="import-library-btn" class="btn-secondary">Import</button>
|
||
<input
|
||
type="file"
|
||
id="import-library-input"
|
||
style="display: none"
|
||
accept=".json"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Export All Settings</span>
|
||
<span class="description">Export all app settings as JSON</span>
|
||
</div>
|
||
<div style="display: flex; gap: 0.5rem">
|
||
<button id="export-settings-btn" class="btn-secondary">Export</button>
|
||
<button id="import-settings-btn" class="btn-secondary">Import</button>
|
||
<input
|
||
type="file"
|
||
id="import-settings-input"
|
||
style="display: none"
|
||
accept=".json"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Blocked Content</span>
|
||
<span class="description"
|
||
>Manage artists, albums, and tracks you've blocked from
|
||
recommendations</span
|
||
>
|
||
</div>
|
||
<div style="display: flex; gap: 0.5rem">
|
||
<button id="manage-blocked-btn" class="btn-secondary">Manage</button>
|
||
<button
|
||
id="clear-all-blocked-btn"
|
||
class="btn-secondary danger"
|
||
style="display: none"
|
||
>
|
||
Clear All
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="page-about" class="page">
|
||
<h2 class="section-title" style="text-align: center">About Monochrome</h2>
|
||
<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.
|
||
<br />
|
||
</p>
|
||
<div class="about-donate">
|
||
<h2 class="section-title" style="text-align: center">Support Monochrome</h2>
|
||
<p style="text-align: center" class="donate-description">
|
||
If Monochrome has been useful to you and you're able to, consider making a donation.
|
||
<br />
|
||
It helps pay for the server and domain, and you get to support us :)
|
||
</p>
|
||
<p style="text-align: center; color: var(--muted-foreground); margin-top: 1rem">
|
||
If you cannot financially support us, please consider starring the project on GitHub and
|
||
sharing with friends!
|
||
</p>
|
||
<div
|
||
class="donate-button"
|
||
id="donate-button-container"
|
||
style="
|
||
display: flex;
|
||
gap: 20px;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-top: 30px;
|
||
flex-wrap: wrap;
|
||
"
|
||
>
|
||
<a
|
||
href="/donate"
|
||
class="btn-secondary"
|
||
style="
|
||
text-decoration: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
"
|
||
>
|
||
Donate to Monochrome
|
||
</a>
|
||
<a
|
||
href="https://github.com/monochrome-music/monochrome"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
class="btn-secondary"
|
||
style="
|
||
text-decoration: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
"
|
||
>
|
||
Star on GitHub
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<p style="text-align: center; color: grey">
|
||
made with ❤︎ by
|
||
<a href="https://prigoana.com/" style="text-decoration: underline">Edideaur</a> &
|
||
<a href="https://samidy.com" style="text-decoration: underline">Samidy</a>
|
||
</p>
|
||
<div class="about-footer">
|
||
<p class="version">Version 2.5.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>
|
||
<p class="disclaimer">
|
||
This application does not host, store, or distribute any media files. <br />
|
||
All content displayed or accessed through this app is provided by third‑party services
|
||
and APIs that are publicly available on the internet. <br />
|
||
We do not control, operate, or maintain any external content sources and are not
|
||
responsible for the accuracy, availability, or legality of any content provided by third
|
||
parties. <br />
|
||
Users are solely responsible for how they use this application and for ensuring that
|
||
their use complies with applicable laws and regulations in their jurisdiction.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="page-account" class="page">
|
||
<h2 class="section-title" style="text-align: center">Sign Up / Sign In</h2>
|
||
<div class="account-content">
|
||
<p style="text-align: center" class="account-description">
|
||
Make an account to allow syncing your library between devices.
|
||
</p>
|
||
<div
|
||
class="account-buttons"
|
||
id="auth-buttons-container"
|
||
style="
|
||
display: flex;
|
||
gap: 20px;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-top: 50px;
|
||
flex-wrap: wrap;
|
||
"
|
||
>
|
||
<button id="auth-connect-btn" class="btn-secondary">Connect with Google</button>
|
||
<button id="toggle-email-auth-btn" class="btn-secondary">Connect with Email</button>
|
||
<button id="view-my-profile-btn" class="btn-secondary" style="display: none">
|
||
View My Profile
|
||
</button>
|
||
</div>
|
||
|
||
<p id="auth-status" style="text-align: center; padding-top: 15px; color: #8b8b93">
|
||
Sync your library across devices
|
||
</p>
|
||
<p style="padding-top: 50px; text-align: center; color: #8b8b93">
|
||
We only store music data and a randomized ID to find out which Google/Email account is
|
||
which.
|
||
<br />
|
||
All data is anonymous. We do not store anything like emails, usernames, or anything
|
||
sensitive. <br />
|
||
</p>
|
||
<p style="padding-top: 50px; text-align: center; color: #8b8b93">
|
||
However, if you want complete control over your data, we allow you to use your own Database
|
||
Configuration.
|
||
</p>
|
||
<div
|
||
style="
|
||
display: flex;
|
||
gap: 50px;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding-top: 25px;
|
||
"
|
||
>
|
||
<a id="advanced-config-link" class="btn-secondary" href="/settings"
|
||
>Advanced: Custom Configuration</a
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="page-download" class="page">
|
||
<h2 class="section-title" style="text-align: center">Monochrome Official App</h2>
|
||
<div class="download-content">
|
||
<p style="text-align: center" class="account-description">
|
||
Install the Monochrome Desktop App for a refined & improved music listening experience.
|
||
</p>
|
||
<br /><br />
|
||
|
||
<div class="download-buttons-icons">
|
||
<div class="download-buttons-icons-windows" style="display: flex; justify-content: center">
|
||
<use svg="./images/windows.svg" size="96" />
|
||
</div>
|
||
<br />
|
||
<div style="display: flex; justify-content: center">
|
||
<a
|
||
id="download-windows-btn"
|
||
class="btn-secondary"
|
||
href="https://downloads.samidy.com/out_delivery/monochrome-windows.zip"
|
||
>Download Windows Version</a
|
||
>
|
||
</div>
|
||
<br />
|
||
<div class="download-buttons-icons-linux" style="display: flex; justify-content: center">
|
||
<use svg="./images/linux.svg" size="96" />
|
||
</div>
|
||
<br />
|
||
<div style="display: flex; justify-content: center">
|
||
<a
|
||
id="download-linux-btn"
|
||
class="btn-secondary"
|
||
href="https://downloads.samidy.com/out_delivery/monochrome-linux.zip"
|
||
>Download Linux Version</a
|
||
>
|
||
</div>
|
||
<br />
|
||
</div>
|
||
<br />
|
||
<h4 style="text-align: center; padding-top: 15px; color: #8b8b93">
|
||
The App is still in Beta. Please report any issues in our
|
||
<a href="discord.html" style="text-decoration: underline">Discord server.</a>
|
||
</h4>
|
||
</div>
|
||
</div>
|
||
<div id="page-donate" class="page">
|
||
<div style="text-align: center; padding: 2rem; max-width: 500px; margin: 0 auto">
|
||
<h2 class="section-title" style="margin-bottom: 1.5rem">Support Monochrome</h2>
|
||
<p style="margin-bottom: 1.5rem; line-height: 1.6; color: var(--muted-foreground)">
|
||
If Monochrome has been useful to you and you're able to, consider making a donation. It
|
||
helps pay for the server and domain, and you get to support us :)
|
||
</p>
|
||
<a
|
||
href="https://ko-fi.com/monochromemusic"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
class="btn-primary"
|
||
style="
|
||
text-decoration: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
margin-bottom: 2rem;
|
||
"
|
||
>
|
||
Donate on Ko-fi
|
||
</a>
|
||
<div style="padding-top: 1.5rem; border-top: 1px solid var(--border)">
|
||
<p style="font-size: 0.9rem; color: var(--muted-foreground); margin-bottom: 1rem">
|
||
If you cannot financially support us, please consider starring the project on GitHub and
|
||
sharing with friends!
|
||
</p>
|
||
<a
|
||
href="https://github.com/monochrome-music/monochrome"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
class="btn-secondary"
|
||
style="text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem"
|
||
>
|
||
Star on GitHub
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<footer class="now-playing-bar">
|
||
<div class="track-info">
|
||
<img src="/assets/appicon.png" alt="Current Track Cover" class="cover" />
|
||
<div class="details">
|
||
<div class="title">Select a song</div>
|
||
<div class="album"></div>
|
||
<div class="artist"></div>
|
||
</div>
|
||
</div>
|
||
<div class="player-controls">
|
||
<div id="radio-loading-indicator">
|
||
<div class="animate-spin"></div>
|
||
<span>Finding more songs for you...</span>
|
||
</div>
|
||
<div class="buttons">
|
||
<button id="shuffle-btn" title="Shuffle">
|
||
<use svg="!lucide/shuffle.svg" size="24" />
|
||
</button>
|
||
<button id="prev-btn" title="Previous">
|
||
<use svg="!lucide/arrow-left-to-line.svg" size="24" />
|
||
</button>
|
||
<button class="play-pause-btn" title="Play"></button>
|
||
<button id="next-btn" title="Next">
|
||
<use svg="!lucide/arrow-right-to-line.svg" size="24" />
|
||
</button>
|
||
<button id="repeat-btn" title="Repeat">
|
||
<use svg="!lucide/repeat.svg" size="24" />
|
||
</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">
|
||
<div class="player-actions-row">
|
||
<button
|
||
id="now-playing-like-btn"
|
||
class="like-btn"
|
||
data-action="toggle-like"
|
||
title="Save to Favorites"
|
||
style="display: none"
|
||
></button>
|
||
<button id="now-playing-add-playlist-btn" title="Add to Playlist" class="desktop-only">
|
||
<use svg="!lucide/square-pen.svg" size="20" />
|
||
</button>
|
||
|
||
<button
|
||
id="now-playing-mix-btn"
|
||
class="mix-btn"
|
||
data-action="track-mix"
|
||
title="Track Mix"
|
||
style="display: none"
|
||
>
|
||
<use svg="./images/mix.svg" size="20" />
|
||
</button>
|
||
<button id="toggle-lyrics-btn" title="Lyrics" style="display: none">
|
||
<use svg="./images/mic.svg" size="20" />
|
||
</button>
|
||
<button id="download-current-btn" title="Download current track" class="desktop-only">
|
||
<use svg="!lucide/download.svg" size="20" />
|
||
</button>
|
||
<button id="cast-btn" title="Cast">
|
||
<use svg="!lucide/cast.svg" size="20" />
|
||
</button>
|
||
|
||
<button id="mobile-add-playlist-btn" class="mobile-only">
|
||
<use svg="!lucide/square-pen.svg" size="20" />
|
||
</button>
|
||
<button id="sleep-timer-btn" title="Sleep Timer" class="mobile-only">
|
||
<use svg="!lucide/clock.svg" size="20" />
|
||
</button>
|
||
<button id="queue-btn" title="Queue">
|
||
<use svg="!lucide/list.svg" size="24" />
|
||
</button>
|
||
</div>
|
||
<div class="volume-slider-row desktop-only">
|
||
<button id="volume-btn" title="Mute"></button>
|
||
<div id="volume-bar" class="volume-bar">
|
||
<div id="volume-fill" class="volume-fill"></div>
|
||
</div>
|
||
<button id="sleep-timer-btn-desktop" title="Sleep Timer">
|
||
<use svg="!lucide/clock.svg" size="20" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
<script src="https://cdn.jsdelivr.net/npm/pocketbase@0.21.3/dist/pocketbase.umd.js"></script>
|
||
<script type="module" src="/js/app.js"></script>
|
||
</body>
|
||
</html>
|