6247 lines
370 KiB
HTML
6247 lines
370 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="referrer" content="no-referrer" />
|
||
<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="preconnect" href="https://fonts.googleapis.com" />
|
||
<link rel="preconnect" href="https://fonts.gstatic.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="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;600;700;800&family=Noto+Sans+SC:wght@400;500;600;700&family=Noto+Sans+TC:wght@400;500;600;700&family=Noto+Sans+HK:wght@400;500;600;700&family=Noto+Sans+JP:wght@400;500;600;700&family=Noto+Sans+KR:wght@400;500;600;700&family=Noto+Sans+Hebrew:wght@400;500;600;700&family=Noto+Sans+Arabic:wght@400;500;600;700&family=Noto+Sans+Devanagari:wght@400;500;600;700&family=Noto+Sans+Bengali:wght@400;500;600;700&family=Noto+Sans+Thai:wght@400;500;600;700&family=Noto+Sans+Tamil:wght@400;500;600;700&family=Noto+Sans+Telugu:wght@400;500;600;700&family=Noto+Sans+Gujarati:wght@400;500;600;700&family=Noto+Sans+Kannada:wght@400;500;600;700&family=Noto+Sans+Malayalam:wght@400;500;600;700&family=Noto+Sans+Sinhala:wght@400;500;600;700&family=Noto+Sans+Khmer:wght@400;500;600;700&family=Noto+Sans+Lao:wght@400;500;600;700&family=Noto+Sans+Myanmar:wght@400;500;600;700&family=Noto+Sans+Georgian:wght@400;500;600;700&family=Noto+Sans+Armenian:wght@400;500;600;700&family=Noto+Sans+Ethiopic:wght@400;500;600;700&display=swap"
|
||
media="print"
|
||
onload="this.media = 'all'"
|
||
/>
|
||
<noscript>
|
||
<link
|
||
rel="stylesheet"
|
||
href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;600;700;800&family=Noto+Sans+SC:wght@400;500;600;700&family=Noto+Sans+TC:wght@400;500;600;700&family=Noto+Sans+HK:wght@400;500;600;700&family=Noto+Sans+JP:wght@400;500;600;700&family=Noto+Sans+KR:wght@400;500;600;700&family=Noto+Sans+Hebrew:wght@400;500;600;700&family=Noto+Sans+Arabic:wght@400;500;600;700&family=Noto+Sans+Devanagari:wght@400;500;600;700&family=Noto+Sans+Bengali:wght@400;500;600;700&family=Noto+Sans+Thai:wght@400;500;600;700&family=Noto+Sans+Tamil:wght@400;500;600;700&family=Noto+Sans+Telugu:wght@400;500;600;700&family=Noto+Sans+Gujarati:wght@400;500;600;700&family=Noto+Sans+Kannada:wght@400;500;600;700&family=Noto+Sans+Malayalam:wght@400;500;600;700&family=Noto+Sans+Sinhala:wght@400;500;600;700&family=Noto+Sans+Khmer:wght@400;500;600;700&family=Noto+Sans+Lao:wght@400;500;600;700&family=Noto+Sans+Myanmar:wght@400;500;600;700&family=Noto+Sans+Georgian:wght@400;500;600;700&family=Noto+Sans+Armenian:wght@400;500;600;700&family=Noto+Sans+Ethiopic:wght@400;500;600;700&display=swap"
|
||
/>
|
||
</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" id="fullscreen-artwork-card">
|
||
<img
|
||
id="fullscreen-cover-image"
|
||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||
alt="Album Cover"
|
||
/>
|
||
<div class="cd-ring" id="cd-ring"></div>
|
||
<svg width="0" height="0">
|
||
<defs>
|
||
<clipPath id="cd-hole-clip" clipPathUnits="objectBoundingBox">
|
||
<path
|
||
fill-rule="evenodd"
|
||
d="M0,0 H1 V1 H0 Z
|
||
M0.5,0.5
|
||
m-0.069,0
|
||
a0.069,0.069 0 1,0 0.138,0
|
||
a0.069,0.069 0 1,0 -0.138,0 Z"
|
||
/>
|
||
</clipPath>
|
||
</defs>
|
||
</svg>
|
||
</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">
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem"
|
||
>Favorite Albums (Max 5)</label
|
||
>
|
||
<div
|
||
id="edit-favorite-albums-list"
|
||
style="display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.5rem"
|
||
></div>
|
||
<div class="status-picker-container">
|
||
<input
|
||
type="text"
|
||
id="edit-favorite-albums-search"
|
||
class="template-input"
|
||
placeholder="Search for an album..."
|
||
autocomplete="off"
|
||
/>
|
||
<div id="edit-favorite-albums-results" class="search-results-dropdown"></div>
|
||
</div>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">About Me</label>
|
||
<textarea
|
||
id="edit-profile-about"
|
||
class="template-input"
|
||
style="resize: vertical; min-height: 80px"
|
||
placeholder="Tell us about yourself"
|
||
></textarea>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Website</label>
|
||
<input type="url" id="edit-profile-website" class="template-input" placeholder="https://..." />
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Last.fm Username</label>
|
||
<input type="text" id="edit-profile-lastfm" class="template-input" placeholder="Last.fm Username" />
|
||
<p style="font-size: 0.8rem; color: var(--muted-foreground); margin-top: 0.5rem; line-height: 1.5">
|
||
Integrating Last.fm enables recent activity and top stats on your profile. Authorize it in
|
||
<strong>Settings > Scrobbling</strong>. Note: Last.fm authorization is stored locally and
|
||
must be repeated on each device.
|
||
</p>
|
||
</div>
|
||
|
||
<h4 style="margin-top: 1.5rem; margin-bottom: 1rem; font-size: 1rem">Privacy</h4>
|
||
<div class="setting-item" style="padding: 0.5rem 0; border: none">
|
||
<div class="info"><span class="label">Public Playlists</span></div>
|
||
<label class="toggle-switch"
|
||
><input type="checkbox" id="privacy-playlists-toggle" checked /><span class="slider"></span
|
||
></label>
|
||
</div>
|
||
<div class="setting-item" style="padding: 0.5rem 0; border: none">
|
||
<div class="info"><span class="label">Show Last.fm Link & Stats</span></div>
|
||
<label class="toggle-switch"
|
||
><input type="checkbox" id="privacy-lastfm-toggle" checked /><span class="slider"></span
|
||
></label>
|
||
</div>
|
||
|
||
<div class="modal-actions">
|
||
<button id="edit-profile-cancel" class="btn-secondary">Cancel</button>
|
||
<button id="edit-profile-save" class="btn-primary">Save Profile</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="folder-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<h3 id="folder-modal-title">Create Folder</h3>
|
||
<input
|
||
type="text"
|
||
id="folder-name-input"
|
||
class="template-input"
|
||
placeholder="Folder name"
|
||
style="margin: 1rem 0"
|
||
/>
|
||
<input
|
||
type="url"
|
||
id="folder-cover-input"
|
||
class="template-input"
|
||
placeholder="Icon URL (optional)"
|
||
style="margin: 0.5rem 0"
|
||
/>
|
||
<div class="modal-actions">
|
||
<button id="folder-modal-cancel" class="btn-secondary">Cancel</button>
|
||
<button id="folder-modal-save" class="btn-primary">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="email-auth-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content 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">×</button>
|
||
</div>
|
||
<div class="shortcuts-content">
|
||
<div class="shortcut-item"><kbd>Space</kbd><span>Play / Pause</span></div>
|
||
<div class="shortcut-item"><kbd>→</kbd><span>Seek forward 10s</span></div>
|
||
<div class="shortcut-item"><kbd>←</kbd><span>Seek backward 10s</span></div>
|
||
<div class="shortcut-item"><kbd>Shift</kbd> + <kbd>→</kbd><span>Next track</span></div>
|
||
<div class="shortcut-item"><kbd>Shift</kbd> + <kbd>←</kbd><span>Previous track</span></div>
|
||
<div class="shortcut-item"><kbd>↑</kbd><span>Volume up</span></div>
|
||
<div class="shortcut-item"><kbd>↓</kbd><span>Volume down</span></div>
|
||
<div class="shortcut-item"><kbd>M</kbd><span>Mute / Unmute</span></div>
|
||
<div class="shortcut-item"><kbd>S</kbd><span>Toggle shuffle</span></div>
|
||
<div class="shortcut-item"><kbd>R</kbd><span>Toggle repeat</span></div>
|
||
<div class="shortcut-item"><kbd>Q</kbd><span>Open queue</span></div>
|
||
<div class="shortcut-item"><kbd>L</kbd><span>Toggle lyrics</span></div>
|
||
<div class="shortcut-item"><kbd>/</kbd><span>Focus search</span></div>
|
||
<div class="shortcut-item"><kbd>Esc</kbd><span>Close modals</span></div>
|
||
<div class="shortcut-item"><kbd>[</kbd><span>Previous visualizer preset</span></div>
|
||
<div class="shortcut-item"><kbd>]</kbd><span>Next visualizer preset</span></div>
|
||
<div class="shortcut-item"><kbd>\</kbd><span>Toggle visualizer auto-cycle</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="customize-shortcuts-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content medium">
|
||
<div class="shortcuts-header">
|
||
<h3>Customize Shortcuts</h3>
|
||
<button class="close-customize-shortcuts">×</button>
|
||
</div>
|
||
<div class="customize-shortcuts-content">
|
||
<p class="shortcut-hint">Click on a shortcut to rebind it. Press the new key combination.</p>
|
||
<div id="shortcuts-list" class="shortcuts-list"></div>
|
||
</div>
|
||
<div class="customize-shortcuts-actions">
|
||
<button id="reset-shortcuts-btn" class="btn-secondary">Reset to Defaults</button>
|
||
<button id="close-customize-shortcuts-btn" class="btn-primary">Done</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="missing-tracks-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content wide">
|
||
<div class="missing-tracks-header">
|
||
<h3>Note</h3>
|
||
<button class="close-missing-tracks">×</button>
|
||
</div>
|
||
<div class="missing-tracks-content">
|
||
<p>
|
||
Unfortunately, some songs weren't able to be added. This could be an issue with our import
|
||
system - try searching for the song and adding it. It could also be due to Monochrome not having
|
||
the song :(
|
||
</p>
|
||
<div class="missing-tracks-list">
|
||
<h4>Missing Tracks:</h4>
|
||
<ul id="missing-tracks-list-ul"></ul>
|
||
</div>
|
||
</div>
|
||
<div class="missing-tracks-actions">
|
||
<button class="btn-secondary" id="copy-missing-tracks-btn">Copy to Clipboard</button>
|
||
<button class="btn-secondary" id="export-missing-tracks-csv-btn">Export as CSV</button>
|
||
<button class="btn-secondary" id="close-missing-tracks-btn">OK</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="sleep-timer-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content" style="max-width: 300px">
|
||
<h3 style="text-align: center; margin-bottom: 1.5rem">Sleep Timer</h3>
|
||
<div class="timer-options">
|
||
<button class="timer-option btn-secondary" data-minutes="5">5 minutes</button>
|
||
<button class="timer-option btn-secondary" data-minutes="15">15 minutes</button>
|
||
<button class="timer-option btn-secondary" data-minutes="30">30 minutes</button>
|
||
<button class="timer-option btn-secondary" data-minutes="60">1 hour</button>
|
||
<button class="timer-option btn-secondary" data-minutes="120">2 hours</button>
|
||
<div style="display: flex; gap: 0.5rem; margin-top: 0.5rem">
|
||
<input
|
||
type="number"
|
||
id="custom-minutes"
|
||
class="template-input"
|
||
placeholder="Custom"
|
||
min="1"
|
||
max="480"
|
||
/>
|
||
<button class="timer-option btn-primary" id="custom-timer-btn" style="padding: 0.5rem 1rem">
|
||
Set
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="modal-actions" style="justify-content: center">
|
||
<button id="cancel-sleep-timer" class="btn-secondary">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="discography-download-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content" style="max-width: 500px">
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem">
|
||
<h3 style="margin: 0">Download Discography</h3>
|
||
<button
|
||
class="close-modal-btn"
|
||
style="
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
cursor: pointer;
|
||
color: var(--muted-foreground);
|
||
"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
<p style="margin-bottom: 1rem; color: var(--muted-foreground)">
|
||
Select which releases to download for <span id="discography-artist-name"></span>:
|
||
</p>
|
||
<div style="display: flex; flex-direction: column; gap: 1rem">
|
||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer">
|
||
<input type="checkbox" id="download-albums" checked />
|
||
<span>Albums (<span id="albums-count">0</span>)</span>
|
||
</label>
|
||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer">
|
||
<input type="checkbox" id="download-eps" />
|
||
<span>EPs (<span id="eps-count">0</span>)</span>
|
||
</label>
|
||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer">
|
||
<input type="checkbox" id="download-singles" />
|
||
<span>Singles (<span id="singles-count">0</span>)</span>
|
||
</label>
|
||
</div>
|
||
<div class="modal-actions" style="margin-top: 1.5rem">
|
||
<button class="btn-secondary" id="cancel-discography-download">Cancel</button>
|
||
<button class="btn-primary" id="start-discography-download">Download</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="custom-db-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem">
|
||
<h3 style="margin: 0">Custom Database/Auth</h3>
|
||
<button
|
||
id="custom-db-reset"
|
||
class="btn-secondary danger"
|
||
style="padding: 0.4rem 0.8rem; font-size: 0.8rem"
|
||
>
|
||
Reset to Defaults
|
||
</button>
|
||
</div>
|
||
<p style="font-size: 0.9rem; color: var(--muted-foreground); margin-bottom: 1rem">
|
||
Configure custom PocketBase and Appwrite instances. Leave empty to use defaults.
|
||
<br />
|
||
A Guide To Set This Up Can Be Found
|
||
<a
|
||
href="https://github.com/monochrome-music/monochrome/blob/main/DOCKER.md"
|
||
style="text-decoration: underline"
|
||
>Here</a
|
||
>.
|
||
</p>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">PocketBase URL</label>
|
||
<input type="url" id="custom-pb-url" class="template-input" placeholder="https://data.samidy.xyz" />
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Appwrite Endpoint</label>
|
||
<input
|
||
type="url"
|
||
id="custom-appwrite-endpoint"
|
||
class="template-input"
|
||
placeholder="https://auth.samidy.com/v1"
|
||
/>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Appwrite Project ID</label>
|
||
<input
|
||
type="text"
|
||
id="custom-appwrite-project"
|
||
class="template-input"
|
||
placeholder="auth-for-monochrome"
|
||
/>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button id="custom-db-cancel" class="btn-secondary">Cancel</button>
|
||
<button id="custom-db-save" class="btn-primary">Save & Reload</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="theme-store-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div
|
||
class="modal-content wide"
|
||
style="height: 80vh; display: flex; flex-direction: column; max-height: 80vh"
|
||
>
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem">
|
||
<h3 style="margin: 0">Theme Store</h3>
|
||
<button
|
||
class="close-modal-btn"
|
||
style="
|
||
background: none;
|
||
border: none;
|
||
font-size: 1.5rem;
|
||
cursor: pointer;
|
||
color: var(--muted-foreground);
|
||
"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
|
||
<div class="search-tabs" style="margin-bottom: 1rem">
|
||
<button class="search-tab active" data-tab="browse">Browse</button>
|
||
<button class="search-tab" data-tab="upload">Upload</button>
|
||
</div>
|
||
|
||
<div
|
||
id="theme-store-details"
|
||
style="display: none; flex-direction: column; height: 100%; overflow: hidden"
|
||
>
|
||
<button
|
||
class="btn-secondary"
|
||
id="theme-details-back-btn"
|
||
style="
|
||
align-self: flex-start;
|
||
margin-bottom: 1rem;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
"
|
||
>
|
||
<use svg="!lucide/chevron-left.svg" size="16" />
|
||
Back
|
||
</button>
|
||
<div style="flex: 1; overflow-y: auto; padding-right: 0.5rem">
|
||
<div style="display: flex; gap: 1.5rem; flex-wrap: wrap; margin-bottom: 2rem">
|
||
<div
|
||
id="theme-details-preview-container"
|
||
style="
|
||
width: 300px;
|
||
height: 220px;
|
||
flex-shrink: 0;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
"
|
||
></div>
|
||
<div style="flex: 1; min-width: 250px">
|
||
<h2 id="theme-details-name" style="font-size: 2rem; margin-bottom: 0.5rem"></h2>
|
||
<div
|
||
class="meta"
|
||
style="
|
||
color: var(--muted-foreground);
|
||
font-size: 0.9rem;
|
||
margin-bottom: 1rem;
|
||
line-height: 1.6;
|
||
"
|
||
>
|
||
<div id="theme-details-author"></div>
|
||
<div>
|
||
Created: <span id="theme-details-created"></span> - Updated:
|
||
<span id="theme-details-updated"></span>
|
||
</div>
|
||
<div>Installs: <span id="theme-details-installs">0</span></div>
|
||
</div>
|
||
<button
|
||
id="theme-details-apply-btn"
|
||
class="btn-primary"
|
||
style="width: 100%; max-width: 200px"
|
||
>
|
||
Apply Theme
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div style="margin-bottom: 2rem">
|
||
<h3 style="font-size: 1.2rem; margin-bottom: 0.5rem">Description</h3>
|
||
<p
|
||
id="theme-details-desc"
|
||
style="white-space: pre-wrap; color: var(--foreground); line-height: 1.6"
|
||
></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
id="theme-store-browse"
|
||
class="search-tab-content active"
|
||
style="flex: 1; overflow-y: auto; min-height: 0"
|
||
>
|
||
<div class="track-list-search-container" style="margin: 0 0 1rem 0">
|
||
<use svg="!lucide/search.svg" class="search-icon" size="20" />
|
||
<input
|
||
type="search"
|
||
id="theme-store-search"
|
||
placeholder="Search themes..."
|
||
class="track-list-search-input"
|
||
autocomplete="off"
|
||
/>
|
||
</div>
|
||
<div id="community-themes-grid" class="card-grid"></div>
|
||
<div id="theme-store-loading" style="text-align: center; padding: 2rem; display: none">
|
||
<div class="animate-spin" style="display: inline-block">
|
||
<use svg="!lucide/loader-circle.svg" size="24" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
id="theme-store-upload"
|
||
class="search-tab-content"
|
||
style="flex: 1; overflow-y: auto; min-height: 0"
|
||
>
|
||
<div
|
||
id="theme-upload-auth-message"
|
||
style="
|
||
display: none;
|
||
text-align: center;
|
||
padding: 2rem;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
"
|
||
>
|
||
<p>You need to be logged in to upload themes.</p>
|
||
<button class="btn-primary" id="theme-store-login-btn">Go to Login</button>
|
||
</div>
|
||
<form id="theme-upload-form">
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Theme Name</label>
|
||
<input
|
||
type="text"
|
||
id="theme-upload-name"
|
||
class="template-input"
|
||
placeholder="My Awesome Theme"
|
||
required
|
||
/>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">Description</label>
|
||
<textarea
|
||
id="theme-upload-desc"
|
||
class="template-input"
|
||
placeholder="Describe your theme..."
|
||
style="min-height: 80px; resize: vertical"
|
||
></textarea>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem"
|
||
>Author Website (Optional)</label
|
||
>
|
||
<input
|
||
type="url"
|
||
id="theme-upload-website"
|
||
class="template-input"
|
||
placeholder="https://example.com"
|
||
/>
|
||
<p style="font-size: 0.8rem; color: var(--muted-foreground); margin-top: 0.25rem">
|
||
It is recommended to create a Monochrome profile instead.
|
||
</p>
|
||
</div>
|
||
<div style="margin-bottom: 1rem">
|
||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem">CSS</label>
|
||
<p style="font-size: 0.8rem; color: var(--muted-foreground); margin-bottom: 0.5rem">
|
||
Define your CSS variables or custom styles here.
|
||
<a
|
||
href="https://github.com/monochrome-music/monochrome/blob/main/THEME_GUIDE.md"
|
||
target="_blank"
|
||
style="text-decoration: underline; color: var(--primary)"
|
||
>Read the Theme Guide</a
|
||
>.
|
||
</p>
|
||
<div class="theme-editor-toolbar">
|
||
<div class="color-picker-group">
|
||
<input type="color" id="te-bg-color" title="Background" />
|
||
<input type="color" id="te-fg-color" title="Foreground" />
|
||
<input type="color" id="te-primary-color" title="Primary" />
|
||
<input type="color" id="te-sec-color" title="Secondary" />
|
||
<input type="color" id="te-accent-color" title="Accent/Highlight" />
|
||
<input type="color" id="te-card-color" title="Card Background" />
|
||
<input type="color" id="te-border-color" title="Border Color" />
|
||
<input type="color" id="te-muted-color" title="Muted Text" />
|
||
</div>
|
||
<div class="style-picker-group">
|
||
<select id="te-font-family" title="Font Family">
|
||
<option value="">Font...</option>
|
||
<option value="'Inter', sans-serif">Inter</option>
|
||
<option value="system-ui, -apple-system, sans-serif">System</option>
|
||
<option value="'Courier New', monospace">Mono</option>
|
||
<option value="'Times New Roman', serif">Serif</option>
|
||
</select>
|
||
<input
|
||
type="text"
|
||
id="te-font-custom"
|
||
placeholder="Custom Font..."
|
||
style="
|
||
height: 24px;
|
||
padding: 0 4px;
|
||
font-size: 0.75rem;
|
||
width: 100px;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-sm);
|
||
background: var(--input);
|
||
color: var(--foreground);
|
||
"
|
||
/>
|
||
<select id="te-radius" title="Border Radius">
|
||
<option value="">Radius...</option>
|
||
<option value="0px">Square</option>
|
||
<option value="4px">Small</option>
|
||
<option value="8px">Medium</option>
|
||
<option value="16px">Large</option>
|
||
</select>
|
||
</div>
|
||
<div class="editor-actions">
|
||
<button
|
||
type="button"
|
||
id="te-insert-template"
|
||
class="btn-secondary"
|
||
style="padding: 2px 8px; font-size: 0.8rem"
|
||
>
|
||
Template
|
||
</button>
|
||
<button
|
||
type="button"
|
||
id="te-toggle-preview"
|
||
class="btn-secondary"
|
||
style="padding: 2px 8px; font-size: 0.8rem"
|
||
>
|
||
Preview
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<textarea
|
||
id="theme-upload-css"
|
||
class="template-input"
|
||
style="min-height: 200px; font-family: monospace; white-space: pre"
|
||
placeholder=":root { --background: #000000; --foreground: #ffffff; /* ... */ }"
|
||
required
|
||
></textarea>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button
|
||
type="button"
|
||
id="theme-upload-cancel-edit"
|
||
class="btn-secondary"
|
||
style="display: none"
|
||
>
|
||
Cancel Edit
|
||
</button>
|
||
<button type="submit" id="theme-upload-submit-btn" class="btn-primary">Upload Theme</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="theme-preview-window" style="display: none"></div>
|
||
|
||
<div id="tracker-modal" class="modal tracker-modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content wide" style="max-height: 85vh; display: flex; flex-direction: column">
|
||
<header class="detail-header" style="margin-bottom: 1rem; padding-bottom: 0">
|
||
<img
|
||
id="tracker-header-image"
|
||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||
class="detail-header-image"
|
||
style="width: 140px; height: 140px; box-shadow: var(--shadow-md)"
|
||
alt=""
|
||
/>
|
||
<div class="detail-header-info">
|
||
<div class="type">Unreleased Project</div>
|
||
<h1 id="tracker-header-title" class="title" style="font-size: 2rem"></h1>
|
||
<div id="tracker-header-meta" class="meta"></div>
|
||
<div class="detail-header-actions">
|
||
<button class="btn-secondary" id="close-tracker-modal">Close</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<div
|
||
id="tracker-filters"
|
||
style="padding: 0 2rem 1rem; display: flex; gap: 0.5rem; flex-wrap: wrap"
|
||
></div>
|
||
<div id="tracker-tracklist" class="track-list" style="overflow-y: auto; flex: 1">
|
||
<div class="track-list-header">
|
||
<span style="width: 40px; text-align: center">#</span>
|
||
<span>Title</span>
|
||
<span class="duration-header">Duration</span>
|
||
<span style="display: flex; justify-content: flex-end; opacity: 0.8">Menu</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="epilepsy-warning-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<h3 style="color: #ef4444; display: flex; align-items: center; gap: 0.5rem">
|
||
<use svg="!lucide/triangle-alert.svg" size="24" />
|
||
Photosensitivity Warning
|
||
</h3>
|
||
<p style="margin: 1rem 0; line-height: 1.5">
|
||
The visualizer contains flashing lights and rapidly moving patterns that may trigger seizures for
|
||
people with photosensitive epilepsy.
|
||
</p>
|
||
<p style="margin-bottom: 1.5rem; font-size: 0.9rem; color: var(--muted-foreground)">
|
||
Viewer discretion is advised.
|
||
</p>
|
||
<div class="modal-actions" style="flex-direction: column; gap: 0.5rem">
|
||
<button id="epilepsy-accept-btn" class="btn-primary" style="width: 100%">
|
||
Proceed & Don't Show Again
|
||
</button>
|
||
<button id="epilepsy-cancel-btn" class="btn-secondary" style="width: 100%">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="desktop-update-modal" class="modal">
|
||
<div class="modal-overlay"></div>
|
||
<div class="modal-content">
|
||
<h3>Update Available</h3>
|
||
<p>A new version of Monochrome is available.</p>
|
||
<div
|
||
id="desktop-update-notes"
|
||
style="
|
||
margin: 1rem 0;
|
||
padding: 1rem;
|
||
background: var(--background-secondary);
|
||
border-radius: var(--radius);
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
font-size: 0.9rem;
|
||
"
|
||
></div>
|
||
<div class="modal-actions">
|
||
<button id="desktop-update-cancel" class="btn-secondary">Later</button>
|
||
<button id="desktop-update-confirm" class="btn-primary">Update Now</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="command-palette-overlay" style="display: none">
|
||
<div class="command-palette">
|
||
<div class="command-palette-header">
|
||
<svg
|
||
class="command-palette-search-icon"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
width="18"
|
||
height="18"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="2"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
>
|
||
<circle cx="11" cy="11" r="8" />
|
||
<path d="m21 21-4.3-4.3" />
|
||
</svg>
|
||
<input
|
||
type="text"
|
||
id="command-palette-input"
|
||
placeholder="Search commands, music, settings..."
|
||
autocomplete="off"
|
||
spellcheck="false"
|
||
aria-label="Command palette search"
|
||
role="combobox"
|
||
aria-expanded="true"
|
||
aria-controls="command-palette-results"
|
||
aria-autocomplete="list"
|
||
/>
|
||
<kbd class="command-palette-kbd">ESC</kbd>
|
||
</div>
|
||
<div id="command-palette-results" class="command-palette-results" role="listbox"></div>
|
||
<div class="command-palette-footer">
|
||
<span class="command-palette-hint"><kbd>↑↓</kbd> navigate</span>
|
||
<span class="command-palette-hint"><kbd>↵</kbd> select</span>
|
||
<span class="command-palette-hint"><kbd>esc</kbd> close</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="sidebar-overlay"></div>
|
||
|
||
<div id="csv-import-progress" class="csv-import-progress" style="display: none">
|
||
<div class="progress-header">
|
||
<h4>Importing Tracks from CSV</h4>
|
||
<p class="progress-warning">
|
||
This can take a while depending on your playlist size. Please be patient.
|
||
</p>
|
||
</div>
|
||
<div class="progress-content">
|
||
<div class="current-track">Preparing import...</div>
|
||
<div
|
||
class="current-artist"
|
||
style="font-size: 0.9em; color: var(--text-secondary); margin-bottom: 0.5rem"
|
||
></div>
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" id="csv-progress-fill"></div>
|
||
</div>
|
||
<div class="progress-text">
|
||
<span id="csv-progress-current">0</span> / <span id="csv-progress-total">0</span> tracks processed
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="app-container">
|
||
<aside class="sidebar">
|
||
<div class="sidebar-content">
|
||
<div class="sidebar-logo">
|
||
<a href="https://monochrome.tf/" class="sidebar-logo-link">
|
||
<use svg="./images/monochrome-logo.svg" size="200" class="app-logo" />
|
||
<span>Monochrome</span>
|
||
</a>
|
||
<button
|
||
id="sidebar-toggle"
|
||
class="btn-icon desktop-only"
|
||
style="margin-left: auto"
|
||
title="Collapse Sidebar"
|
||
>
|
||
<use svg="!lucide/chevron-left.svg" size="20" />
|
||
</button>
|
||
</div>
|
||
|
||
<nav class="sidebar-nav main">
|
||
<ul>
|
||
<li class="nav-item" id="sidebar-nav-home">
|
||
<a href="/">
|
||
<use svg="!lucide/house.svg" size="24" />
|
||
<span>Home</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-library">
|
||
<a href="/library">
|
||
<use svg="!lucide/library.svg" size="24" />
|
||
<span>Library</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-recent">
|
||
<a href="/recent">
|
||
<use svg="./images/recent.svg" size="24" />
|
||
<span>Recent</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-unreleased">
|
||
<a href="/unreleased">
|
||
<use svg="./images/squares.svg" size="24" />
|
||
<span>Unreleased</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-donate">
|
||
<a id="sidebar-donate-link" href="/donate">
|
||
<use svg="!lucide/hand-heart.svg" size="24" />
|
||
<span>Donate</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" id="sidebar-nav-settings">
|
||
<a href="/settings">
|
||
<use svg="!lucide/settings.svg" size="24" />
|
||
<span>Settings</span>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</nav>
|
||
<div class="sidebar-bottom-container">
|
||
<nav class="sidebar-nav" id="pinned-items-nav" style="display: none">
|
||
<h4 class="pinned-items-header">Pinned</h4>
|
||
<ul id="pinned-items-list">
|
||
<!-- Pinned should be injected here -->
|
||
</ul>
|
||
</nav>
|
||
|
||
<div class="sidebar-nav-bottom">
|
||
<div id="server-disruption-banner" class="server-disruption-sidebar" style="display: none">
|
||
<span class="disruption-icon">⚠</span>
|
||
<span
|
||
>Services are currently unstable. <br /><br />Use the extension
|
||
<a href="https://github.com/monochrome-music/monochrome/tree/main/extension"
|
||
>here</a
|
||
>
|
||
for the best experience.</span
|
||
>
|
||
<button id="dismiss-disruption-btn" class="disruption-dismiss" title="Dismiss">
|
||
×
|
||
</button>
|
||
</div>
|
||
<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"
|
||
>
|
||
×
|
||
</button>
|
||
<div id="search-history" class="search-history" style="display: none"></div>
|
||
</form>
|
||
<button id="help-btn" class="btn-icon" title="Help">
|
||
<use svg="!lucide/help-circle.svg" size="24" />
|
||
</button>
|
||
<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"
|
||
>
|
||
×
|
||
</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"
|
||
>
|
||
×
|
||
</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">
|
||
<div id="artist-detail-banner-container" class="detail-header-banner"></div>
|
||
<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 class="settings-group">
|
||
<div class="setting-item" id="cd-album-cover-setting">
|
||
<div class="info">
|
||
<span class="label" id="cd-album-cover-label">CD Album Cover</span>
|
||
<span class="description" id="cd-album-cover-description"
|
||
>Spin album cover and add CD hole in fullscreen</span
|
||
>
|
||
</div>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input
|
||
type="checkbox"
|
||
id="cd-album-cover-toggle"
|
||
aria-labelledby="cd-album-cover-label"
|
||
aria-describedby="cd-album-cover-description"
|
||
/>
|
||
<span class="slider"></span>
|
||
</label>
|
||
</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">Artist Banners</span>
|
||
<span class="description">Display video banners on artist pages</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="artist-banners-toggle" checked />
|
||
<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 (24-bit)</option>
|
||
<option value="LOSSLESS">Lossless (16-bit)</option>
|
||
<option value="HIGH">AAC 320kbps</option>
|
||
<option value="LOW">AAC 96kbps</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Show Quality Badges</span>
|
||
<span class="description">Display "HD" badge for Hi-Res tracks</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="show-quality-badges-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Album release year</span>
|
||
<span class="description"
|
||
>Show original album year instead of track/remaster date</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="use-album-release-year-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Gapless Playback</span>
|
||
<span class="description">Play audio without interruption between tracks</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="gapless-playback-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group">
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">ReplayGain Mode</span>
|
||
<span class="description">Normalize volume across tracks</span>
|
||
</div>
|
||
<select id="replay-gain-mode">
|
||
<option value="off">Off</option>
|
||
<option value="track">Track</option>
|
||
<option value="album">Album</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">ReplayGain Pre-Amp</span>
|
||
<span class="description">Adjust gain manually (dB)</span>
|
||
</div>
|
||
<input
|
||
type="number"
|
||
id="replay-gain-preamp"
|
||
value="3"
|
||
step="0.5"
|
||
style="width: 80px"
|
||
/>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Mono Audio</span>
|
||
<span class="description">Combine left and right channels into mono</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="mono-audio-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Exponential Volume</span>
|
||
<span class="description"
|
||
>Use logarithmic volume curve for finer low-volume control</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="exponential-volume-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Playback Speed</span>
|
||
<span class="description">Adjust playback speed (0.01x - 100x)</span>
|
||
</div>
|
||
<div class="playback-speed-control">
|
||
<input
|
||
type="range"
|
||
id="playback-speed-slider"
|
||
min="0.25"
|
||
max="4.0"
|
||
step="0.01"
|
||
value="1.0"
|
||
class="playback-speed-slider"
|
||
/>
|
||
<input
|
||
type="number"
|
||
id="playback-speed-input"
|
||
min="0.01"
|
||
max="100"
|
||
step="0.01"
|
||
value="1.0"
|
||
class="playback-speed-number-input"
|
||
/>
|
||
<span class="playback-speed-unit">x</span>
|
||
<button
|
||
id="playback-speed-reset"
|
||
class="btn-secondary"
|
||
title="Reset to default"
|
||
>
|
||
Reset
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Preserve Pitch</span>
|
||
<span class="description">Keep original pitch when changing speed</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="preserve-pitch-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">Binaural / Spatial DSP</span>
|
||
<span class="description"
|
||
>Multichannel HRTF rendering for Atmos & 3D Audio, crossfeed for
|
||
stereo</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="binaural-dsp-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="binaural-dsp-container" id="binaural-dsp-container" style="display: none">
|
||
<div class="binaural-status" id="binaural-status">
|
||
<span class="binaural-mode-label">Mode: Stereo</span>
|
||
</div>
|
||
|
||
<div class="binaural-sub-setting">
|
||
<div class="info">
|
||
<span class="label">Auto-enable for Spatial Audio</span>
|
||
<span class="description"
|
||
>Automatically activate when Atmos or 3D content is detected</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="binaural-auto-spatial-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="binaural-sub-setting">
|
||
<div class="info">
|
||
<span class="label">Crossfeed</span>
|
||
<span class="description">Simulate speaker presentation on headphones</span>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="binaural-crossfeed-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="binaural-sub-setting" id="crossfeed-level-row">
|
||
<div class="info">
|
||
<span class="label">Crossfeed Level</span>
|
||
</div>
|
||
<select id="binaural-crossfeed-level">
|
||
<option value="low">Low</option>
|
||
<option value="medium" selected>Medium</option>
|
||
<option value="high">High</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="binaural-sub-setting">
|
||
<div class="info">
|
||
<span class="label">HRTF Preset</span>
|
||
<span class="description"
|
||
>Virtual speaker angle for multichannel rendering</span
|
||
>
|
||
</div>
|
||
<select id="binaural-hrtf-preset">
|
||
<option value="intimate">Intimate (±22°)</option>
|
||
<option value="studio" selected>Studio (±30°)</option>
|
||
<option value="wide">Wide (±45°)</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="binaural-sub-setting">
|
||
<div class="info">
|
||
<span class="label">Stereo Width</span>
|
||
<span class="description"
|
||
>Adjust spatial width (0 = mono, 1 = neutral, 2 = wide)</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="binaural-widening-toggle" checked />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="binaural-sub-setting" id="widening-slider-row">
|
||
<div class="info">
|
||
<span class="label">Width Amount</span>
|
||
<span class="binaural-width-value" id="binaural-width-value">1.0</span>
|
||
</div>
|
||
<input
|
||
type="range"
|
||
id="binaural-widening-slider"
|
||
min="0"
|
||
max="2"
|
||
step="0.05"
|
||
value="1.0"
|
||
class="binaural-slider"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="setting-item">
|
||
<div class="info">
|
||
<span class="label">EQ Studio</span>
|
||
<span class="description"
|
||
>Multi-mode equalizer with AutoEQ, M/S processing & room
|
||
correction</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" data-mode="parametric">
|
||
Parametric EQ
|
||
</button>
|
||
<button class="autoeq-mode-btn active" data-mode="autoeq">AutoEQ</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">
|
||
×
|
||
</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&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,.csv"
|
||
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&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" style="color: #f59e0b">Dev Mode</span>
|
||
<span class="description"
|
||
>Route all API requests through a local Tidal HiFi API server. Requires a
|
||
compatible server running at the specified URL.</span
|
||
>
|
||
</div>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="dev-mode-toggle" />
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item" id="dev-mode-url-setting" style="display: none">
|
||
<div class="info">
|
||
<span class="label">Dev Mode API URL</span>
|
||
<span class="description">The URL of your local Tidal HiFi API instance</span>
|
||
</div>
|
||
<input
|
||
type="text"
|
||
id="dev-mode-url-input"
|
||
placeholder="http://127.0.0.1:8000"
|
||
style="
|
||
width: 220px;
|
||
padding: 0.35rem 0.5rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--card);
|
||
color: var(--foreground);
|
||
font-size: 0.8rem;
|
||
"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<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 third‑party services
|
||
and APIs that are publicly available on the internet. <br />
|
||
We do not control, operate, or maintain any external content sources and are not
|
||
responsible for the accuracy, availability, or legality of any content provided by third
|
||
parties. <br />
|
||
Users are solely responsible for how they use this application and for ensuring that
|
||
their use complies with applicable laws and regulations in their jurisdiction.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="page-account" class="page">
|
||
<h2 class="section-title" style="text-align: center">Sign Up / Sign In</h2>
|
||
<div class="account-content">
|
||
<p style="text-align: center" class="account-description">
|
||
Make an account to allow syncing your library between devices.
|
||
</p>
|
||
<div
|
||
class="account-buttons"
|
||
id="auth-buttons-container"
|
||
style="
|
||
display: flex;
|
||
gap: 20px;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-top: 50px;
|
||
flex-wrap: wrap;
|
||
"
|
||
>
|
||
<button id="auth-connect-btn" class="btn-secondary">Connect with Google</button>
|
||
<button id="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>
|
||
<div id="page-reset-password" class="page">
|
||
<div
|
||
class="reset-password-container"
|
||
style="text-align: center; padding: var(--space-8); max-width: 400px; margin: 0 auto"
|
||
>
|
||
<h2 class="section-title" style="margin-bottom: var(--space-10)">Reset your Password</h2>
|
||
|
||
<div
|
||
id="reset-password-error"
|
||
class="message error-msg"
|
||
style="
|
||
display: none;
|
||
padding: var(--space-3) 0;
|
||
border-radius: var(--radius-md);
|
||
font-size: var(--text-sm);
|
||
margin-bottom: var(--space-4);
|
||
background: transparent;
|
||
color: #ff6b6b;
|
||
text-align: left;
|
||
"
|
||
></div>
|
||
<div
|
||
id="reset-password-success"
|
||
class="message success-msg"
|
||
style="
|
||
display: none;
|
||
padding: var(--space-3) 0;
|
||
border-radius: var(--radius-md);
|
||
font-size: var(--text-sm);
|
||
margin-bottom: var(--space-4);
|
||
background: transparent;
|
||
color: var(--foreground);
|
||
text-align: left;
|
||
"
|
||
></div>
|
||
|
||
<form id="reset-password-form">
|
||
<div style="margin-bottom: var(--space-4)">
|
||
<input
|
||
type="password"
|
||
id="reset-password-input"
|
||
placeholder="New Password"
|
||
required
|
||
minlength="8"
|
||
style="
|
||
width: 100%;
|
||
padding: var(--space-3) var(--space-4);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--card);
|
||
color: var(--foreground);
|
||
font-size: 0.925rem;
|
||
outline: none;
|
||
transition: border-color 0.15s;
|
||
"
|
||
/>
|
||
</div>
|
||
<div style="margin-bottom: var(--space-6)">
|
||
<input
|
||
type="password"
|
||
id="reset-password-confirm"
|
||
placeholder="Confirm New Password"
|
||
required
|
||
minlength="8"
|
||
style="
|
||
width: 100%;
|
||
padding: var(--space-3) var(--space-4);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--card);
|
||
color: var(--foreground);
|
||
font-size: 0.925rem;
|
||
outline: none;
|
||
transition: border-color 0.15s;
|
||
"
|
||
/>
|
||
</div>
|
||
<button
|
||
type="submit"
|
||
id="reset-password-submit-btn"
|
||
class="btn-primary"
|
||
style="width: 100%; justify-content: center"
|
||
>
|
||
<span id="reset-password-btn-text">Reset Password</span>
|
||
<div
|
||
id="reset-password-btn-spinner"
|
||
class="animate-spin"
|
||
style="display: none; width: 18px; height: 18px"
|
||
>
|
||
<svg
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="2"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
style="width: 100%; height: 100%"
|
||
>
|
||
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
||
</svg>
|
||
</div>
|
||
</button>
|
||
</form>
|
||
</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>
|