kv-music/index.html
2026-04-10 16:06:04 +03:00

5928 lines
350 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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</title>
<link rel="canonical" href="https://monochrome.tf/" />
<link rel="alternate" hreflang="en" 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="Stream and download millions of Hi-Res FLACs, unreleased songs and music videos, all for free on Monochrome."
/>
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
<meta name="googlebot" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<meta property="og:site_name" content="Monochrome" />
<meta property="og:title" content="Monochrome" />
<meta
property="og:description"
content="Stream and download millions of Hi-Res FLACs, unreleased songs and music videos, all for free on Monochrome."
/>
<meta property="og:url" content="https://monochrome.tf/" />
<meta property="og:image" content="https://monochrome.tf/assets/banner.jpg" />
<meta property="og:image:type" content="image/jpeg" />
<meta property="og:image:alt" content="Monochrome banner" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content="https://monochrome.tf/" />
<meta name="twitter:title" content="Monochrome" />
<meta
name="twitter:description"
content="Stream and download millions of Hi-Res FLACs, unreleased songs and music videos, all for free on Monochrome."
/>
<meta name="twitter:image" content="https://monochrome.tf/assets/banner-twitter.jpg" />
<meta name="twitter:image:alt" content="Monochrome banner" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Monochrome",
"url": "https://monochrome.tf/",
"description": "Stream and download millions of Hi-Res FLACs, unreleased songs and music videos, all for free on Monochrome.",
"potentialAction": {
"@type": "SearchAction",
"target": "https://monochrome.tf/search/{search_term_string}",
"query-input": "required name=search_term_string"
}
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "Monochrome",
"url": "https://monochrome.tf/",
"applicationCategory": "MusicApplication",
"operatingSystem": "Web Browser",
"image": "https://monochrome.tf/assets/logo.svg",
"description": "Stream and download millions of Hi-Res FLACs, unreleased songs and music videos, all for free on Monochrome."
}
</script>
<!-- bini's seo thing -->
<meta
name="ahrefs-site-verification"
content="889c6a5c1584832256b87bb36beaa665b40bb6e201582d416f04d074794044ef"
/>
<meta name="google-site-verification" content="ChAPlKFJ5Dk2YbWhiUfnH5sHgCC8SnNdCBtjVcahArY" />
<!-- Preconnect to critical third-party origins -->
<link rel="preconnect" href="https://api.fonts.coollabs.io" crossorigin />
<link rel="preconnect" href="https://resources.tidal.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
rel="preload"
as="style"
href="https://api.fonts.coollabs.io/css2?family=Inter:wght@400;500;600;700;800&display=swap"
/>
<link
href="https://api.fonts.coollabs.io/css2?family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
media="print"
onload="this.media = 'all'"
/>
<noscript>
<link
href="https://api.fonts.coollabs.io/css2?family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
</noscript>
<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-type-filter="album,artist,playlist,mix,user-playlist"
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"
>
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="request-song" data-type-filter="track,video">Request song</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="eq-node-context-menu">
<ul>
<li data-action="eq-channel-stereo" class="eq-ctx-channel">Stereo</li>
<li data-action="eq-channel-mid" class="eq-ctx-channel">Mid</li>
<li data-action="eq-channel-side" class="eq-ctx-channel">Side</li>
<li class="separator"></li>
<li data-action="eq-type-lowshelf" class="eq-ctx-type">Low Shelf</li>
<li data-action="eq-type-peaking" class="eq-ctx-type">Peaking</li>
<li data-action="eq-type-highshelf" class="eq-ctx-type">High Shelf</li>
</ul>
</div>
<div id="eq-empty-context-menu">
<ul>
<li data-action="eq-add-node">Add Node</li>
</ul>
</div>
<div id="side-panel" class="side-panel">
<div id="side-panel-resizer" class="side-panel-resizer"></div>
<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="fullscreen-dismiss-handle" type="button" aria-label="Dismiss fullscreen"></button>
<button
id="toggle-fullscreen-lyrics-mobile-btn"
class="fullscreen-mobile-lyrics-toggle"
title="Hide Lyrics"
>
<use svg="!lucide/mic-vocal.svg" size="18" />
</button>
<button id="toggle-ui-btn" class="fullscreen-ui-toggle" title="Toggle UI">
<use svg="!lucide/eye-off.svg" size="24" />
</button>
<div class="fullscreen-top-actions">
<button id="toggle-fullscreen-lyrics-btn" class="fullscreen-lyrics-toggle" title="Hide Lyrics">
<use svg="!lucide/mic-vocal.svg" size="20" />
</button>
<button id="fs-visualizer-btn" class="fs-visualizer-btn" title="Disable Visualizer">
<use svg="!lucide/audio-lines.svg" size="20" />
</button>
<button id="close-fullscreen-cover-btn" title="Close"><use svg="!lucide/x.svg" size="24" /></button>
</div>
<div class="fullscreen-shell">
<div class="fullscreen-main-view">
<div class="fullscreen-media-column">
<div class="fullscreen-artwork-card">
<img
id="fullscreen-cover-image"
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
alt="Album Cover"
/>
</div>
<div class="fullscreen-track-info">
<div class="fullscreen-track-text">
<h2 id="fullscreen-track-title"></h2>
<h3 id="fullscreen-track-artist"></h3>
</div>
<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
id="fullscreen-mobile-quality"
class="fullscreen-mobile-quality"
aria-hidden="true"
></div>
<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>
<aside id="fullscreen-lyrics-pane" class="fullscreen-lyrics-pane">
<div class="fullscreen-lyrics-shell">
<div id="fullscreen-lyrics-content" class="fullscreen-lyrics-content">
<div class="fullscreen-lyrics-empty">Lyrics appear here.</div>
</div>
</div>
</aside>
</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 many media players. 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">
&times;
</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 &gt; 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 email-auth-modal-content">
<div class="email-auth-modal-header">
<h3>Email Authentication</h3>
<button
type="button"
class="btn-icon email-auth-modal-close"
id="email-auth-modal-close"
title="Close"
>
<use svg="!lucide/x.svg" size="20" />
</button>
</div>
<input
type="email"
id="auth-email"
class="template-input email-auth-input"
placeholder="Email Address"
/>
<div class="email-auth-password-block">
<input
type="password"
id="auth-password"
class="template-input email-auth-input"
placeholder="Password"
/>
<button type="button" id="reset-password-btn" class="email-auth-forgot-link">
Forgot password?
</button>
</div>
<div class="email-auth-actions">
<button type="button" id="email-signin-btn" class="btn-primary email-auth-submit">Sign In</button>
<button type="button" id="email-signup-btn" class="btn-secondary email-auth-submit">Sign Up</button>
</div>
</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">&times;</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">&times;</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">&times;</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);
"
>
&times;
</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);
"
>
&times;
</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 {&#10; --background: #000000;&#10; --foreground: #ffffff;&#10; /* ... */&#10;}"
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="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>&uarr;&darr;</kbd> navigate</span>
<span class="command-palette-hint"><kbd>&crarr;</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-party">
<a href="/parties" id="sidebar-party-btn">
<use svg="!lucide/users.svg" size="24" />
<span>Parties</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>
</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>
<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"
>
&times;
</button>
<div id="search-history" class="search-history" style="display: none"></div>
</form>
<div class="header-account-control">
<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);
"
>
<span id="header-account-icon" class="header-account-icon-wrap">
<use svg="!lucide/user.svg" size="20" />
</span>
<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>
</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; display: none"
>
<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-parties" class="page">
<div style="text-align: center; padding: 4rem 2rem; max-width: 600px; margin: 0 auto">
<h2 class="section-title">Listening Parties</h2>
<p style="color: var(--muted-foreground); margin-bottom: 2rem">
Listen to music together with your friends in real-time. Host controls the music, everyone
enjoys it.
</p>
<div id="parties-auth-required" style="display: none">
<p style="margin-bottom: 1rem">You need an account to host a listening party.</p>
<button id="parties-login-btn" class="btn-primary">Sign In / Sign Up</button>
</div>
<div id="parties-host-controls" style="display: none">
<input
type="text"
id="party-name-input"
class="template-input"
placeholder="Party Name"
style="margin-bottom: 1rem"
/>
<button id="create-party-btn" class="btn-primary" style="width: 100%">
Create Listening Party
</button>
</div>
</div>
</div>
<div id="page-party-detail" class="page">
<div class="party-container">
<header class="detail-header">
<div class="detail-header-info">
<div class="type">Listening Party</div>
<h1 class="title" id="party-title">Party Name</h1>
<div class="meta" id="party-meta">Host: ...</div>
<div class="detail-header-actions">
<button id="leave-party-btn" class="btn-secondary danger">Leave Party</button>
<button id="copy-party-link-btn" class="btn-secondary">Copy Invite Link</button>
</div>
</div>
</header>
<div
class="party-content-layout"
style="display: grid; grid-template-columns: 1fr 350px; gap: 2rem; margin-top: 2rem"
>
<div class="party-main-section">
<section class="content-section">
<h2 class="section-title">Currently Playing</h2>
<div id="party-current-track-display"></div>
</section>
<section class="content-section">
<h2 class="section-title">Participants (<span id="party-member-count">0</span>)</h2>
<div id="party-members-list" class="card-grid compact"></div>
</section>
<section class="content-section" id="party-requests-section">
<h2 class="section-title">Song Requests</h2>
<div id="party-requests-list" class="track-list"></div>
</section>
</div>
<div class="party-sidebar-section">
<div
class="party-chat-container"
style="
display: flex;
flex-direction: column;
height: 600px;
background: var(--background-secondary);
border-radius: var(--radius);
border: 1px solid var(--border);
"
>
<div
class="chat-header"
style="padding: 1rem; border-bottom: 1px solid var(--border); font-weight: 600"
>
Chat
</div>
<div
id="party-chat-messages"
style="
flex: 1;
overflow-y: auto;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
"
></div>
<div
class="chat-input-container"
style="
padding: 1rem;
border-top: 1px solid var(--border);
display: flex;
gap: 0.5rem;
"
>
<input
type="text"
id="party-chat-input"
class="template-input"
placeholder="Say something..."
style="flex: 1"
/>
<button id="party-chat-send-btn" class="btn-primary" style="padding: 0 1rem">
Send
</button>
</div>
</div>
</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>
</div>
<div class="card-grid" id="my-folders-container"></div>
<div class="card-grid" id="my-playlists-container">
<button
type="button"
class="card library-create-dashed-card"
id="library-create-playlist-card"
>
<div class="library-create-dashed-art">
<use svg="!lucide/list-music.svg" size="28" />
</div>
<div class="card-info">
<h4 class="card-title">Create playlist</h4>
</div>
</button>
<button
type="button"
class="card library-create-dashed-card"
id="library-create-folder-card"
>
<div class="library-create-dashed-art">
<use svg="!lucide/folder-plus.svg" size="28" />
</div>
<div class="card-info">
<h4 class="card-title">Create folder</h4>
</div>
</button>
</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
id="library-liked-tracks-toolbar"
class="library-liked-tracks-toolbar"
style="display: none"
>
<form class="library-liked-search track-list-search-container" onsubmit="return false;">
<use svg="!lucide/search.svg" class="search-icon" size="18" />
<input
type="search"
id="library-liked-tracks-search"
class="track-list-search-input"
placeholder="Search liked tracks..."
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
<button
type="button"
class="search-clear-btn btn-icon"
title="Clear search"
style="display: none"
>
&times;
</button>
</form>
<div class="library-liked-tracks-toolbar-actions">
<button
type="button"
id="library-liked-tracks-view-list"
class="btn-icon library-liked-view-btn"
title="List view"
>
<use svg="!lucide/layout-list.svg" size="18" />
</button>
<button
type="button"
id="library-liked-tracks-view-grid"
class="btn-icon library-liked-view-btn"
title="Card view"
>
<use svg="!lucide/layout-grid.svg" size="18" />
</button>
<button
id="shuffle-liked-tracks-btn"
class="btn-secondary"
style="
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="
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>
<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>
<br />
<div class="ratings" id="album-detail-ratings-critics"></div>
<div class="ratings" id="album-detail-ratings-users"></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"
>
&times;
</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 class="setting-item">
<div class="info">
<span class="label">No Round Album Cover</span>
<span class="description">Do not round the album cover in fullscreen view</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="fullscreen-cover-no-round-toggle" checked />
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Vanilla Tilt Album Cover</span>
<span class="description"
>Enable 3D tilt effect on the album cover in fullscreen view</span
>
</div>
<label class="toggle-switch">
<input type="checkbox" id="fullscreen-cover-vanilla-tilt-toggle" checked />
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Tilt Distance</span>
<span class="description">Max tilt distance (default: 10)</span>
</div>
<input
type="range"
id="fullscreen-cover-tilt-distance"
min="0"
max="30"
value="10"
/>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Tilt Speed</span>
<span class="description">Tilt animation speed in ms (default: 240)</span>
</div>
<input
type="range"
id="fullscreen-cover-tilt-speed"
min="50"
max="1000"
value="240"
/>
</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">Editor's Picks Source</span>
<span class="description">Choose which editor's picks file to display</span>
</div>
<select id="editors-picks-source-select" class="select-dropdown">
<option value="current">Current</option>
</select>
</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 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">Streaming Quality</span>
<span class="description">Default playback quality for streams</span>
</div>
<select id="streaming-quality-setting">
<option value="auto">Auto (Adaptive)</option>
<option value="HI_RES_LOSSLESS">Hi-Res Lossless (up to 24-bit/192kHz)</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">AutoEQ</span>
<span class="description"
>Precision headphone correction &amp; parametric equalizer</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">
<!-- Mode Toggle + How To -->
<div class="autoeq-mode-row">
<div class="autoeq-mode-toggle">
<button class="autoeq-mode-btn" data-mode="legacy">Legacy EQ</button>
<button class="autoeq-mode-btn active" data-mode="autoeq">AutoEQ</button>
<button class="autoeq-mode-btn" data-mode="parametric">
Parametric EQ
</button>
<button class="autoeq-mode-btn" data-mode="speaker">Speaker EQ</button>
</div>
<button class="eq-howto-btn" id="eq-howto-btn" title="How to use">?</button>
</div>
<!-- Tutorial Panel -->
<div class="eq-howto-panel" id="eq-howto-panel" style="display: none">
<button class="eq-howto-close" id="eq-howto-close" aria-label="Close tutorial">
&times;
</button>
<div class="eq-howto-tab legacy" id="eq-howto-legacy" style="display: none">
<h4>Legacy EQ - Graphic Equalizer</h4>
<ol>
<li>
Set the <b>number of bands</b> (3-32) and
<b>frequency range</b> (Min/Max Hz) at the top to customize the
equalizer layout.
</li>
<li>
<b>Drag the sliders</b> to boost or cut each frequency band. Bands
are spaced logarithmically across your range.
</li>
<li>
Pick a <b>preset</b> (Bass Boost, Rock, Vocal, etc.) as a starting
point - presets auto-scale to your band count.
</li>
<li>
Adjust the <b>preamp</b> slider to raise or lower the overall level
- reduce it if you hear distortion from large boosts.
</li>
<li>
<b>Save</b> your own custom presets with a name so you can recall
them later.
</li>
<li>
<b>Export</b> saves the EQ in EqualizerAPO text format.
<b>Export APO</b> saves a GraphicEQ config line you can paste
directly into Equalizer APO's config.txt.
</li>
<li>
<b>Import</b> loads EQ settings from EqualizerAPO text files or
simple frequency/gain CSV files - points are mapped to your current
bands automatically.
</li>
<li>Click <b>Reset</b> to flatten all bands back to 0 dB.</li>
</ol>
<p class="eq-howto-tip">
Tip: Cut problem frequencies rather than boosting others - it sounds
cleaner and avoids clipping.
</p>
</div>
<div class="eq-howto-tab autoeq" id="eq-howto-autoeq">
<h4>AutoEQ - Headphone Correction</h4>
<ol>
<li>
<b>Select your headphone</b> from the dropdown or search the
database below.
</li>
<li>
<b>Pick a target curve</b> - Harman is the most popular. You can
also import a custom target.
</li>
<li>
Adjust <b>filter bands</b> (more = finer correction, 10 is a good
default).
</li>
<li>
Click <b>AutoEQ</b> - the algorithm generates parametric filters
that shape your headphone's response toward the target.
</li>
<li>
The <b>pink corrected curve</b> on the graph shows the predicted
result.
</li>
<li>
<b>Drag nodes</b> to adjust frequency and gain.
<b>Scroll on a node</b> to adjust Q (bandwidth).
</li>
<li>
<b>Right-click a node</b> to change its filter type (Peaking, Low
Shelf, High Shelf) or channel mode (Stereo, Mid, Side).
</li>
<li>
<b>Right-click empty space</b> or <b>double-click</b> to add a node.
<b>Double-click a node</b> to remove it.
</li>
<li>
<b>Save</b> the profile so you can switch between headphones
instantly.
</li>
</ol>
<p class="eq-howto-tip">
Tip: Use "Auto Preamp Compensation" to prevent clipping from positive EQ
gains.
</p>
</div>
<div
class="eq-howto-tab parametric"
id="eq-howto-parametric"
style="display: none"
>
<h4>Parametric EQ - Manual Control</h4>
<ol>
<li>
Each band supports <b>Peaking, Low Shelf, and High Shelf</b> filter
types with frequency, gain, and Q (bandwidth).
</li>
<li>
<b>Drag nodes</b> on the graph to adjust frequency and gain.
<b>Scroll on a node</b> to adjust Q.
</li>
<li>
<b>Right-click a node</b> to change its filter type or set its
channel mode to <b>Stereo</b>, <b>Mid</b>, or <b>Side</b>.
</li>
<li>
<b>Right-click empty space</b> or <b>double-click</b> to add a node
at that position. <b>Double-click a node</b> to delete it.
</li>
<li>
Use <b>+ Add Band / - Remove Band</b> to change the number of
filters.
</li>
<li>
<b>Import/Export</b> settings in EqualizerAPO format for use in
other apps.
</li>
<li>
<b>Save</b> profiles with custom names to keep your favorite EQ
curves.
</li>
</ol>
<p class="eq-howto-tip">
Tip: Lower Q = wider curve, higher Q = narrower surgical cut.
</p>
<p class="eq-howto-tip">
Mid/Side tips: Set a band to <b>Mid</b> to EQ only the center image
(vocals, bass, kick). Set it to <b>Side</b> to EQ only the stereo width
(reverb, ambience, panned instruments). Try cutting low-end on Side
below 200 Hz for tighter, mono-compatible bass - or boost presence on
Mid around 2-5 kHz to bring vocals forward without touching the sides.
</p>
</div>
<div class="eq-howto-tab speaker" id="eq-howto-speaker" style="display: none">
<h4>Speaker EQ - Room Correction</h4>
<ol>
<li>Select your <b>speaker config</b> (2.0, 5.1, or 7.1).</li>
<li>
Click a <b>channel tab</b> (FL, FR, etc.) to work on one speaker at
a time.
</li>
<li>
<b>Measure</b>: click the mic button - pink noise plays for 5
seconds while your microphone captures the room response. Or
<b>import</b> a measurement file from REW or similar.
</li>
<li>
Pick a <b>target</b> - Harman In-Room is recommended for speakers.
</li>
<li>
Set <b>Bass Limit</b> (don't EQ below this) and
<b>Room Limit</b> (don't EQ above this) - the colored lines on the
graph show the active range.
</li>
<li>
Click <b>AutoEQ</b> to generate correction filters for that channel.
</li>
<li>
<b>Drag nodes</b> to fine-tune. <b>Scroll on a node</b> to adjust Q.
<b>Right-click a node</b> to change type or channel mode.
</li>
<li>
<b>Right-click empty space</b> or <b>double-click</b> to add a node.
<b>Double-click a node</b> to remove it.
</li>
<li>
Repeat for each channel, then <b>Export JSON</b> with all channels.
</li>
</ol>
<p class="eq-howto-tip">
Tip: Place your mic at the listening position. Measure each speaker
separately for best results.
</p>
</div>
</div>
<!-- Legacy 16-Band Graphic EQ (visible in legacy mode) -->
<div class="graphic-eq-section" id="graphic-eq-section" style="display: none">
<div class="graphic-eq-config-row">
<label>Bands</label>
<input
type="number"
id="legacy-geq-band-count"
min="3"
max="32"
value="16"
class="geq-config-input"
/>
<label>Min Hz</label>
<input
type="number"
id="legacy-geq-freq-min"
min="10"
max="96000"
value="25"
class="geq-config-input"
/>
<label>Max Hz</label>
<input
type="number"
id="legacy-geq-freq-max"
min="10"
max="96000"
value="20000"
class="geq-config-input"
/>
</div>
<div class="graphic-eq-preset-row">
<label for="legacy-graphic-eq-preset-select" class="graphic-eq-preset-label"
>Preset</label
>
<select
id="legacy-graphic-eq-preset-select"
class="graphic-eq-preset-select"
>
<option value="">Custom</option>
<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&amp;B</option>
<option value="acoustic">Acoustic</option>
<option value="podcast">Speech</option>
</select>
<button
id="legacy-geq-save-preset-btn"
class="btn-secondary"
title="Save current EQ as a preset"
>
Save
</button>
<button
id="legacy-geq-delete-preset-btn"
class="btn-secondary"
title="Delete selected custom preset"
style="display: none"
>
Delete
</button>
</div>
<div class="graphic-eq-bands" id="legacy-graphic-eq-bands">
<!-- 16 vertical sliders generated by JS -->
</div>
<div class="graphic-eq-bottom-row">
<div class="graphic-eq-preamp">
<label
for="legacy-graphic-eq-preamp-slider"
class="graphic-eq-preamp-label"
>Preamp</label
>
<input
type="range"
id="legacy-graphic-eq-preamp-slider"
class="graphic-eq-preamp-slider"
min="-20"
max="20"
step="0.1"
value="0"
/>
<span
class="graphic-eq-preamp-value"
id="legacy-graphic-eq-preamp-value"
>0 dB</span
>
</div>
<button id="legacy-graphic-eq-reset-btn" class="btn-secondary">
Reset
</button>
<button
id="legacy-geq-import-btn"
class="btn-secondary"
title="Import EQ from text file (frequency/gain pairs, Q values ignored)"
>
Import
</button>
<button
id="legacy-geq-export-btn"
class="btn-secondary"
title="Export EQ to text file"
>
Export
</button>
<button
id="legacy-geq-export-csv-btn"
class="btn-secondary"
title="Export EQ as Equalizer APO GraphicEQ config line"
>
Export APO
</button>
<input
type="file"
id="legacy-geq-import-file"
accept=".txt"
style="display: none"
/>
</div>
</div>
<!-- Frequency Response Graph -->
<div class="autoeq-graph-section">
<div class="autoeq-graph-header">
<span class="autoeq-graph-title">Frequency Response</span>
<div class="autoeq-graph-legend">
<span class="legend-item legend-original"
><span class="legend-dot"></span>Original</span
>
<span class="legend-item legend-target"
><span class="legend-dot"></span>Target (Primary)</span
>
<span class="legend-item legend-corrected"
><span class="legend-dot"></span>Corrected</span
>
</div>
</div>
<div class="autoeq-graph-wrapper" id="autoeq-graph-wrapper">
<canvas id="autoeq-response-canvas" class="autoeq-response-canvas"></canvas>
</div>
<div class="autoeq-auto-preamp">
<div class="autoeq-preamp-row">
<span class="autoeq-preamp-label">Preamp</span>
<input
type="range"
id="eq-preamp-slider"
class="autoeq-preamp-slider"
min="-20"
max="20"
step="0.1"
value="0"
/>
<span class="autoeq-preamp-value" id="autoeq-preamp-value">0 dB</span>
</div>
<label class="autoeq-auto-preamp-label" for="autoeq-auto-preamp-toggle">
Auto Preamp Compensation
</label>
<label class="toggle-switch toggle-switch-sm">
<input type="checkbox" id="autoeq-auto-preamp-toggle" />
<span class="slider"></span>
</label>
</div>
<button id="autoeq-run-btn" class="autoeq-run-btn" disabled>AutoEq</button>
</div>
<!-- Database Browser -->
<div class="autoeq-database-section" id="autoeq-database-section">
<div class="autoeq-database-header" id="autoeq-database-toggle">
<div>
<h4 class="autoeq-database-title">Database</h4>
<span class="autoeq-database-subtitle">AutoEq Repo</span>
</div>
<div class="autoeq-database-header-right">
<span class="autoeq-database-count" id="autoeq-database-count"></span>
<button
class="autoeq-collapse-btn"
id="autoeq-database-collapse"
aria-label="Collapse database"
aria-expanded="true"
aria-controls="autoeq-database-body"
>
<use svg="!lucide/chevron-up.svg" size="18" />
</button>
</div>
</div>
<div class="autoeq-database-body" id="autoeq-database-body">
<div class="autoeq-database-search">
<use svg="!lucide/search.svg" size="16" />
<input
type="text"
id="autoeq-headphone-search"
placeholder="Search model (e.g. HD 600)..."
autocomplete="off"
aria-label="Search headphone model"
/>
</div>
<div class="autoeq-database-content">
<div class="autoeq-database-list" id="autoeq-database-list">
<!-- Dynamically populated -->
</div>
<div class="autoeq-database-alpha-index" id="autoeq-alpha-index">
<!-- A-Z generated by JS -->
</div>
</div>
</div>
</div>
<!-- AutoEQ Controls -->
<div class="autoeq-controls-section">
<div class="autoeq-control-group">
<label class="autoeq-control-label" for="autoeq-headphone-select"
>HEADPHONE MODEL</label
>
<div class="autoeq-select-wrapper">
<select id="autoeq-headphone-select" aria-label="Headphone model">
<option value="">Select a headphone...</option>
</select>
<button
id="autoeq-import-measurement-btn"
class="autoeq-settings-btn"
title="Import measurement file"
aria-label="Import measurement file"
>
<use svg="!lucide/file-input.svg" size="16" />
</button>
</div>
</div>
<div class="autoeq-control-group">
<label class="autoeq-control-label"
>TARGET
<span
style="
font-weight: 400;
color: var(--muted-foreground);
text-transform: none;
letter-spacing: 0;
"
>· import custom ↑</span
></label
>
<div class="autoeq-select-wrapper">
<select id="autoeq-target-select">
<option value="harman_oe_2018">Harman Over-Ear 2018</option>
<option value="harman_ie_2019">Harman In-Ear 2019</option>
<option value="seap">SEAP</option>
<option value="seap_bass">SEAP Bass Boost</option>
<option value="diffuse_field">Diffuse Field</option>
<option value="knowles">Knowles</option>
<option value="moondrop">Moondrop VDSF</option>
<option value="hifi_endgame">HiFi Endgame 2026</option>
<option value="peqdb_ultra">PEQdB Ultra</option>
<option value="flat">Flat (Calibration)</option>
</select>
<button
id="autoeq-import-target-btn"
class="autoeq-settings-btn"
title="Import target curve"
aria-label="Import target curve"
>
<use svg="!lucide/file-up.svg" size="16" />
</button>
<input
type="file"
id="autoeq-import-target-file"
accept=".txt,.csv"
style="display: none"
/>
</div>
</div>
<div class="autoeq-controls-row">
<div class="autoeq-control-mini">
<label class="autoeq-control-label" for="autoeq-band-count"
>FILTER BANDS</label
>
<select id="autoeq-band-count" aria-label="Filter bands">
<option value="5">5</option>
<option value="10" selected>10</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="32">32</option>
</select>
</div>
<div class="autoeq-control-mini">
<label class="autoeq-control-label" for="autoeq-max-freq">MAX HZ</label>
<select id="autoeq-max-freq" aria-label="Maximum frequency">
<option value="6000">6k</option>
<option value="8000">8k</option>
<option value="10000">10k</option>
<option value="16000" selected>16k</option>
<option value="20000">20k</option>
<option value="22000">22k</option>
</select>
</div>
<div class="autoeq-control-mini">
<label class="autoeq-control-label" for="autoeq-sample-rate"
>SAMPLE RATE</label
>
<select id="autoeq-sample-rate" aria-label="Sample rate">
<option value="44100">44.1k</option>
<option value="48000" selected>48k</option>
<option value="96000">96k</option>
</select>
</div>
</div>
<div class="autoeq-actions-row">
<button
id="autoeq-download-btn"
class="autoeq-download-btn"
title="Export EQ settings"
aria-label="Export EQ settings"
>
<use svg="!lucide/download.svg" size="20" />
</button>
</div>
<span
id="autoeq-status"
class="autoeq-status"
role="status"
aria-live="polite"
></span>
</div>
<!-- Saved Profiles -->
<div class="autoeq-saved-section" id="autoeq-saved-section">
<div class="autoeq-saved-header" id="autoeq-saved-toggle">
<div class="autoeq-saved-header-left">
<span class="autoeq-saved-title">SAVED PROFILES</span>
<span class="autoeq-saved-count" id="autoeq-saved-count">0</span>
</div>
<div class="autoeq-saved-header-right">
<input
type="text"
id="autoeq-profile-name"
class="autoeq-profile-name-input"
placeholder="Profile name..."
aria-label="AutoEQ profile name"
maxlength="50"
/>
<button id="autoeq-save-btn" class="btn-primary autoeq-save-btn">
Save
</button>
<button
class="autoeq-collapse-btn"
id="autoeq-saved-collapse"
aria-label="Collapse EQ section"
aria-expanded="true"
>
<use svg="!lucide/chevron-up.svg" size="18" />
</button>
</div>
</div>
<div class="autoeq-saved-grid" id="autoeq-saved-grid">
<!-- Dynamically populated profile cards -->
</div>
</div>
<!-- Speaker EQ Section -->
<div class="speaker-eq-section" id="speaker-eq-section" style="display: none">
<!-- Config + Channel Tabs -->
<div class="speaker-eq-header">
<div class="speaker-eq-config-row">
<select id="speaker-config-select" class="speaker-config-select">
<option value="2.0">2.0 Stereo</option>
<option value="5.1">5.1 Surround</option>
<option value="7.1">7.1 Surround</option>
</select>
<button
id="speaker-export-btn"
class="btn-secondary"
title="Export all channels as JSON"
>
Export JSON
</button>
<button
id="speaker-import-btn"
class="btn-secondary"
title="Import EQ settings from JSON"
>
Import JSON
</button>
<input
type="file"
id="speaker-import-file"
accept=".json"
style="display: none"
/>
</div>
<div class="speaker-channel-tabs" id="speaker-channel-tabs">
<!-- Populated by JS -->
</div>
</div>
<!-- Per-Channel Controls -->
<div class="speaker-eq-controls">
<div class="speaker-eq-measurement-row">
<div class="autoeq-control-group" style="flex: 1">
<label class="autoeq-control-label">MEASUREMENT</label>
<div class="autoeq-select-wrapper">
<span
id="speaker-measurement-status"
class="speaker-measurement-status"
role="status"
aria-live="polite"
>No measurement</span
>
<button
id="speaker-measure-btn"
class="autoeq-settings-btn speaker-measure-btn"
title="Measure room with pink noise"
aria-label="Measure room with pink noise"
>
<use svg="!lucide/mic.svg" size="16" />
</button>
<button
id="speaker-import-measurement-btn"
class="autoeq-settings-btn"
title="Import measurement file"
aria-label="Import measurement file"
>
<use svg="!lucide/file-up.svg" size="16" />
</button>
<button
id="speaker-clear-measurement-btn"
class="autoeq-settings-btn"
title="Clear measurement"
aria-label="Clear measurement"
style="display: none"
>
<use svg="!lucide/x.svg" size="16" />
</button>
</div>
<input
type="file"
id="speaker-import-measurement-file"
accept=".txt,.csv"
style="display: none"
/>
</div>
<div
class="autoeq-control-group"
style="min-width: 150px; flex: 0 1 auto"
>
<label class="autoeq-control-label">TARGET</label>
<div class="autoeq-select-wrapper">
<select id="speaker-target-select">
<option value="harman_room">Harman In-Room (2013)</option>
<option value="seap_bass">SEAP Bass (Room)</option>
<option value="flat">Flat</option>
</select>
<button
id="speaker-import-target-btn"
class="autoeq-settings-btn"
title="Import target curve"
aria-label="Import target curve"
>
<use svg="!lucide/file-up.svg" size="16" />
</button>
<input
type="file"
id="speaker-import-target-file"
accept=".txt,.csv"
style="display: none"
/>
</div>
</div>
</div>
<div class="speaker-eq-params-row">
<div class="autoeq-control-mini">
<label class="autoeq-control-label">BANDS</label>
<select id="speaker-band-count">
<option value="5">5</option>
<option value="10" selected>10</option>
<option value="15">15</option>
</select>
</div>
<div class="autoeq-control-mini speaker-eq-slider-control">
<label class="autoeq-control-label bass">BASS LIMIT</label>
<div class="speaker-eq-slider-row">
<input
type="range"
id="speaker-bass-cutoff"
min="20"
max="100"
step="5"
value="40"
class="autoeq-preamp-slider"
/>
<span
id="speaker-bass-cutoff-value"
class="speaker-eq-slider-value bass"
>40 Hz</span
>
</div>
</div>
<div class="autoeq-control-mini speaker-eq-slider-control">
<label class="autoeq-control-label room">ROOM LIMIT</label>
<div class="speaker-eq-slider-row">
<input
type="range"
id="speaker-room-limit"
min="100"
max="1000"
step="10"
value="500"
class="autoeq-preamp-slider"
/>
<span
id="speaker-room-limit-value"
class="speaker-eq-slider-value room"
>500 Hz</span
>
</div>
</div>
<div class="autoeq-control-mini speaker-eq-slider-control">
<label class="autoeq-control-label">PREAMP</label>
<div class="speaker-eq-slider-row">
<input
type="range"
id="speaker-preamp-slider"
min="-20"
max="20"
step="0.1"
value="0"
class="autoeq-preamp-slider"
/>
<span id="speaker-preamp-value" class="speaker-eq-slider-value"
>0 dB</span
>
</div>
</div>
</div>
<div class="speaker-eq-actions-row">
<button id="speaker-autoeq-btn" class="autoeq-run-btn" disabled>
AutoEQ
</button>
<button
id="speaker-autoeq-all-btn"
class="autoeq-run-btn speaker-all-btn"
title="Run AutoEQ on all channels that have measurements"
>
AutoEQ All
</button>
<button
id="speaker-measure-all-btn"
class="autoeq-run-btn speaker-all-btn"
title="Measure once and apply averaged result to all channels"
>
Measure All
</button>
<span
id="speaker-eq-status"
class="autoeq-status"
role="status"
aria-live="polite"
></span>
</div>
</div>
</div>
<!-- Speaker Saved Profiles -->
<div class="speaker-saved-section" id="speaker-saved-section" style="display: none">
<div class="autoeq-saved-header" id="speaker-saved-toggle">
<div class="autoeq-saved-header-left">
<span class="autoeq-saved-title">SPEAKER PROFILES</span>
<span class="autoeq-saved-count" id="speaker-saved-count">0</span>
</div>
<div class="autoeq-saved-header-right">
<input
type="text"
id="speaker-profile-name"
class="autoeq-profile-name-input"
placeholder="Profile name..."
aria-label="Speaker EQ profile name"
maxlength="50"
/>
<button id="speaker-save-btn" class="btn-primary autoeq-save-btn">
Save
</button>
<button
class="autoeq-collapse-btn"
id="speaker-saved-collapse"
aria-label="Collapse EQ section"
aria-expanded="true"
>
<use svg="!lucide/chevron-up.svg" size="18" />
</button>
</div>
</div>
<div class="autoeq-saved-grid" id="speaker-saved-grid">
<!-- Dynamically populated speaker profile cards -->
</div>
</div>
<!-- Parametric EQ Filters (collapsible) -->
<div class="autoeq-filters-section" id="autoeq-filters-section">
<div class="autoeq-filters-header" id="autoeq-filters-toggle">
<span>PARAMETRIC EQ FILTERS</span>
<button
class="autoeq-collapse-btn"
id="autoeq-filters-collapse"
aria-label="Collapse EQ filters"
aria-expanded="true"
>
<use svg="!lucide/chevron-up.svg" size="18" />
</button>
</div>
<div class="autoeq-filters-content" id="autoeq-filters-content">
<!-- Preset Selector (visible in parametric mode) -->
<div class="autoeq-preset-row" id="autoeq-preset-row" style="display: none">
<div class="autoeq-control-group">
<label class="autoeq-control-label">PRESET</label>
<select id="parametric-preset-select">
<option value="">Custom</option>
<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&amp;B</option>
<option value="acoustic">Acoustic</option>
<option value="podcast">Speech</option>
<option disabled>── Shelf ──</option>
<option value="shelf_warm">Warm</option>
<option value="shelf_bright">Bright & Airy</option>
<option value="shelf_hifi">Hi-Fi</option>
<option value="shelf_dark">Dark & Smooth</option>
<option value="shelf_radio">Radio Ready</option>
<option disabled>── Mid/Side ──</option>
<option value="ms_vocal_clarity">M/S Vocal Clarity</option>
<option value="ms_wide_stereo">M/S Wide Stereo</option>
<option value="ms_mono_bass">M/S Mono Bass</option>
<option value="ms_master_polish">M/S Master Polish</option>
<option value="ms_rock_master">M/S Rock Master</option>
<option value="ms_hiphop">M/S Hip-Hop</option>
</select>
</div>
</div>
<!-- Saved Profiles for Parametric EQ -->
<div
class="autoeq-parametric-profiles"
id="autoeq-parametric-profiles"
style="display: none"
>
<div class="autoeq-saved-header">
<div class="autoeq-saved-header-left">
<span class="autoeq-saved-title">SAVED PROFILES</span>
<span class="autoeq-saved-count" id="parametric-saved-count"
>0</span
>
</div>
<div class="autoeq-saved-header-right">
<input
type="text"
id="parametric-profile-name"
class="autoeq-profile-name-input"
placeholder="Profile name..."
aria-label="Parametric EQ profile name"
maxlength="50"
/>
<button
id="parametric-save-btn"
class="btn-primary autoeq-save-btn"
>
Save
</button>
</div>
</div>
<div class="autoeq-saved-grid" id="parametric-saved-grid">
<!-- Dynamically populated -->
</div>
</div>
<!-- Import/Export + Band Controls -->
<div class="autoeq-filters-actions">
<button
id="parametric-import-btn"
class="btn-secondary"
title="Import EQ from text file"
>
Import
</button>
<button
id="parametric-export-btn"
class="btn-secondary"
title="Export EQ to text file"
>
Export
</button>
<span style="flex: 1"></span>
<button
id="autoeq-add-band-btn"
class="btn-secondary"
title="Add a new EQ band"
>
+ Add Band
</button>
<button
id="autoeq-remove-band-btn"
class="btn-secondary"
title="Remove last EQ band"
>
- Remove Band
</button>
<button
id="autoeq-reset-bands-btn"
class="btn-secondary"
title="Reset all bands to flat"
>
Reset
</button>
</div>
<input
type="file"
id="parametric-import-file"
accept=".txt"
style="display: none"
/>
<div class="autoeq-bands-list" id="autoeq-bands-list">
<!-- Dynamically generated per-band controls -->
</div>
</div>
</div>
<!-- Hidden file inputs -->
<input
type="file"
id="autoeq-import-measurement-file"
accept=".txt,.csv"
style="display: none"
/>
</div>
</div>
</div>
</div>
<div class="settings-tab-content" id="settings-tab-downloads">
<div class="settings-list">
<div class="settings-group">
<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">Dolby Atmos</span>
<span class="description">Prefer Dolby Atmos tracks when available</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="dolby-atmos-toggle" />
<span class="slider"></span>
</label>
</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>
<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>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Write Artists Separately</span>
<span class="description"
>Write artists separately to metadata. Requires player support.</span
>
</div>
<label class="toggle-switch">
<input type="checkbox" id="write-artists-separately-toggle" />
<span class="slider"></span>
</label>
</div>
</div>
<div class="settings-group">
<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 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">
<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 id="blocked-content-list" style="display: none">
<p
id="blocked-empty-message"
style="
display: none;
color: var(--muted-foreground);
font-size: 0.875rem;
padding: 0.5rem 0;
"
>
Nothing blocked yet.
</p>
<div id="blocked-artists-section" style="display: none">
<h4
style="
font-size: 0.75rem;
text-transform: uppercase;
color: var(--muted-foreground);
margin: 0.75rem 0 0.25rem;
"
>
Artists
</h4>
<ul id="blocked-artists-list" class="blocked-items-list"></ul>
</div>
<div id="blocked-albums-section" style="display: none">
<h4
style="
font-size: 0.75rem;
text-transform: uppercase;
color: var(--muted-foreground);
margin: 0.75rem 0 0.25rem;
"
>
Albums
</h4>
<ul id="blocked-albums-list" class="blocked-items-list"></ul>
</div>
<div id="blocked-tracks-section" style="display: none">
<h4
style="
font-size: 0.75rem;
text-transform: uppercase;
color: var(--muted-foreground);
margin: 0.75rem 0 0.25rem;
"
>
Tracks
</h4>
<ul id="blocked-tracks-list" class="blocked-items-list"></ul>
</div>
</div>
</div>
<p
id="settings-commit-info"
style="
text-align: center;
color: var(--muted-foreground);
font-size: 0.75rem;
padding: 1rem 0 0.5rem;
"
></p>
</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>
<br />
<h3 style="text-align: center">Contributors List:</h3>
<br />
<div class="about-contributors"></div>
<div class="about-contributors-failed"></div>
<div class="about-footer">
<p class="version">Version 2.5.0</p>
<p class="version" id="about-commit-info"></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 thirdparty 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="auth-github-btn" class="btn-secondary">Connect with GitHub</button>
<button id="auth-discord-btn" class="btn-secondary">Connect with Discord</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-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" fetchpriority="high" />
<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"
aria-label="Add to playlist"
>
<use svg="!lucide/list-plus.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" aria-label="Lyrics">
<use svg="!lucide/mic-vocal.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"
title="Add to playlist"
aria-label="Add to playlist"
>
<use svg="!lucide/list-plus.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 type="module" src="/js/app.js"></script>
</body>
</html>