mirror of
https://github.com/19JVJeffery/tiddl-gui.git
synced 2026-10-19 01:19:51 -05:00
Agent-Logs-Url: https://github.com/19JVJeffery/tiddl-gui/sessions/9cafa33d-fa02-4511-94f4-aa11b732e573 Co-authored-by: 19JVJeffery <94115228+19JVJeffery@users.noreply.github.com>
848 lines
64 KiB
HTML
848 lines
64 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" data-theme="">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>tiddl-gui — Tidal Downloader</title>
|
||
<meta name="description" content="Download Tidal tracks, albums and playlists — now in your browser." />
|
||
<link rel="icon" type="image/svg+xml" id="favicon-link" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%230b0b14'/%3E%3Ctext x='16' y='24' text-anchor='middle' font-family='system-ui' font-weight='800' font-size='22' fill='%234fd08c'%3Et%3C/text%3E%3C/svg%3E" />
|
||
<meta name="theme-color" id="theme-color-meta-dark" media="(prefers-color-scheme: dark)" content="#111120" />
|
||
<meta name="theme-color" id="theme-color-meta-light" media="(prefers-color-scheme: light)" content="#ffffff" />
|
||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet" />
|
||
<link rel="stylesheet" href="css/style.css" />
|
||
</head>
|
||
<body>
|
||
|
||
<header>
|
||
<div class="logo">tiddl-gui <span>♫ tidal downloader</span></div>
|
||
<div class="header-spacer"></div>
|
||
<span id="auth-badge" class="auth-badge auth-badge--out">Not logged in</span>
|
||
<button id="btn-logout" class="btn-logout hidden">Logout</button>
|
||
<button id="theme-toggle" class="theme-toggle-btn" title="Toggle theme" aria-label="Toggle theme">
|
||
<svg id="theme-icon" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
|
||
</button>
|
||
</header>
|
||
|
||
<div class="layout">
|
||
|
||
<nav class="sidebar">
|
||
<button class="tab-btn" data-tab="download">
|
||
<svg width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 3v13m0 0-4-4m4 4 4-4M4 20h16"/></svg>
|
||
Download
|
||
</button>
|
||
<button class="tab-btn" data-tab="search">
|
||
<svg width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||
Search
|
||
</button>
|
||
<button class="tab-btn" data-tab="library">
|
||
<svg width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="2" y="3" width="4" height="18"/><rect x="9" y="3" width="4" height="18"/><path d="M16 3l5 2v14l-5 2V3z"/></svg>
|
||
Library
|
||
</button>
|
||
<button class="tab-btn" data-tab="auth">
|
||
<svg width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||
Account
|
||
</button>
|
||
<button class="tab-btn" data-tab="settings">
|
||
<svg width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
|
||
Settings
|
||
</button>
|
||
<div class="sidebar-spacer"></div>
|
||
<button class="tab-btn" data-tab="help">
|
||
<svg width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3M12 17h.01"/></svg>
|
||
Help
|
||
</button>
|
||
</nav>
|
||
|
||
<main>
|
||
|
||
<!-- Download panel -->
|
||
<section id="panel-download" class="tab-panel hidden">
|
||
<div class="panel-sticky-header">
|
||
<h2 class="panel-title">
|
||
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 3v13m0 0-4-4m4 4 4-4M4 20h16"/></svg>
|
||
Download
|
||
</h2>
|
||
</div>
|
||
<div class="panel-scroll-body">
|
||
|
||
<div class="dl-url-section">
|
||
<label class="field-label" for="url-pill-input">Tidal URLs / resource IDs</label>
|
||
<div class="url-pill-box" id="url-pill-box" role="list" aria-label="URLs to download">
|
||
<input id="url-pill-input" type="text"
|
||
placeholder="Paste URL or ID and press Enter to add…"
|
||
autocomplete="off" spellcheck="false" />
|
||
</div>
|
||
<div class="dl-url-actions">
|
||
<button id="btn-import-file" class="btn-ghost url-import-btn" title="Import Tidal URLs from a file. Supports .txt, .csv, .tsv, .json, .yaml, .xml, .html, .md, source files, and more.">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||
Import file
|
||
</button>
|
||
<input type="file" id="url-file-input"
|
||
accept=".txt,.csv,.tsv,.md,.html,.htm,.xml,.json,.css,.py,.java,.c,.cpp,.php,.cs,.rtf,.doc,.docx,.odt,.pdf,.ini,.cfg,.log,.yaml,.yml"
|
||
style="display:none" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dl-quality-row">
|
||
<div class="quality-picker-wrap">
|
||
<label class="field-label" for="quality-select">Quality <span class="tip" tabindex="0" data-tip="Download quality. In normal mode, applies to all queued items. In Advanced mode (Settings), set quality per item.">?</span></label>
|
||
<select id="quality-select" class="quality-select-dl" data-quality="HIGH" title="Download quality. In normal mode, applies to all queued items. In Advanced mode (Settings), set quality per item.">
|
||
<option value="LOW">Low — 96 kbps · M4A</option>
|
||
<option value="HIGH" selected>High — 320 kbps · M4A</option>
|
||
<option value="LOSSLESS">HiFi — FLAC 16-bit</option>
|
||
<option value="HI_RES_LOSSLESS">Max — FLAC 24-bit</option>
|
||
</select>
|
||
</div>
|
||
<button id="btn-download" class="btn-primary btn-download-main">
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" viewBox="0 0 24 24" style="vertical-align:-2px;margin-right:4px"><path d="M12 3v13m0 0-4-4m4 4 4-4"/></svg>
|
||
Download
|
||
</button>
|
||
</div>
|
||
|
||
<div id="all-qualities-banner" class="all-qualities-banner hidden">
|
||
<span class="all-qualities-banner-icon">
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||
</span>
|
||
<span><strong>All Qualities Mode</strong> — each item downloads in every quality tier: Low, High, HiFi & Max</span>
|
||
<span class="all-qualities-banner-pills">
|
||
<span class="quality-pill-sm" data-quality="LOW">Low</span>
|
||
<span class="quality-pill-sm" data-quality="HIGH">High</span>
|
||
<span class="quality-pill-sm" data-quality="LOSSLESS">HiFi</span>
|
||
<span class="quality-pill-sm" data-quality="HI_RES_LOSSLESS">Max</span>
|
||
</span>
|
||
</div>
|
||
|
||
<div id="download-queue-section" class="download-queue-section hidden">
|
||
<div class="download-queue-header">
|
||
<span class="download-queue-title">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/></svg>
|
||
Queue
|
||
<span id="queue-count" class="queue-count">0</span>
|
||
</span>
|
||
<button id="btn-clear-queue" class="btn-ghost" style="font-size:0.75rem;padding:0.2rem 0.65rem">Clear all</button>
|
||
</div>
|
||
<div id="download-queue" class="download-queue" aria-label="Download queue"></div>
|
||
</div>
|
||
|
||
<p class="download-hint">
|
||
Type or paste Tidal URLs / short IDs (e.g. <code>track/103805726</code>, <code>album/103805723</code>) and press <kbd>Enter</kbd> to add to queue.
|
||
Paste multiple URLs at once or use <strong>Import file</strong> for a text file. Encrypted streams cannot be saved in the browser.
|
||
</p>
|
||
</div><!-- .panel-scroll-body -->
|
||
</section>
|
||
<section id="panel-search" class="tab-panel hidden">
|
||
<div class="panel-sticky-header">
|
||
<h2 class="panel-title">
|
||
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||
Search
|
||
</h2>
|
||
|
||
<div class="search-queue-banner hidden" id="search-queue-banner">
|
||
<span id="search-queue-banner-text">0 items in queue</span>
|
||
<button id="btn-go-downloads" class="btn-go-downloads">
|
||
Go to Downloads
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path d="M5 12h14m-7-7 7 7-7 7"/></svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="search-row">
|
||
<div class="search-input-wrap">
|
||
<input id="search-input" type="text" placeholder="Artist, track, album, playlist…" autocomplete="off" />
|
||
<button id="btn-clear-search" class="btn-clear-search hidden" aria-label="Clear search">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||
</button>
|
||
</div>
|
||
<button id="btn-search" class="btn-primary">Search</button>
|
||
</div>
|
||
|
||
<div class="search-history hidden" id="search-history">
|
||
<div class="search-history-header">
|
||
<span class="search-history-label">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||
Recent searches
|
||
</span>
|
||
<button id="btn-clear-history" class="btn-ghost" style="font-size:0.72rem;padding:0.15rem 0.55rem">Clear</button>
|
||
</div>
|
||
<div id="search-history-list"></div>
|
||
</div>
|
||
|
||
<div class="search-type-pills" id="search-type-pills">
|
||
<button class="type-pill active" data-type="all">All</button>
|
||
<button class="type-pill" data-type="tracks">Tracks</button>
|
||
<button class="type-pill" data-type="albums">Albums</button>
|
||
<button class="type-pill" data-type="artists">Artists</button>
|
||
<button class="type-pill" data-type="playlists">Playlists</button>
|
||
</div>
|
||
</div><!-- .panel-sticky-header -->
|
||
|
||
<div class="panel-scroll-body">
|
||
<div id="search-results"></div>
|
||
</div><!-- .panel-scroll-body -->
|
||
</section>
|
||
|
||
<!-- Library panel -->
|
||
<section id="panel-library" class="tab-panel hidden">
|
||
<div class="panel-sticky-header">
|
||
<div id="library-welcome" class="library-welcome hidden">
|
||
<div class="library-welcome-inner">
|
||
<span class="library-welcome-emoji">👋</span>
|
||
<div>
|
||
<strong id="library-welcome-name">Welcome!</strong>
|
||
<span class="library-welcome-sub">Your library is loading…</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="panel-title">
|
||
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="2" y="3" width="4" height="18"/><rect x="9" y="3" width="4" height="18"/><path d="M16 3l5 2v14l-5 2V3z"/></svg>
|
||
Library
|
||
</h2>
|
||
|
||
<div class="library-controls-row">
|
||
<div class="search-type-pills" id="library-section-pills">
|
||
<button class="type-pill active" data-section="tracks">Tracks</button>
|
||
<button class="type-pill" data-section="albums">Albums</button>
|
||
<button class="type-pill" data-section="playlists">Playlists</button>
|
||
</div>
|
||
<div class="library-filter-wrap">
|
||
<div class="search-input-wrap">
|
||
<input id="library-filter-input" type="text" placeholder="Filter…" autocomplete="off" title="Filter items in the current library section" />
|
||
<button id="btn-clear-library-filter" class="btn-clear-search hidden" aria-label="Clear filter">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<select id="library-sort-select" class="library-sort-select" title="Sort library items" aria-label="Sort by">
|
||
<option value="dateAdded-desc">Date Added ↓</option>
|
||
<option value="dateAdded-asc">Date Added ↑</option>
|
||
<option value="title-asc">Title A–Z</option>
|
||
<option value="title-desc">Title Z–A</option>
|
||
<option value="artist-asc">Artist A–Z</option>
|
||
<option value="artist-desc">Artist Z–A</option>
|
||
</select>
|
||
</div>
|
||
</div><!-- .panel-sticky-header -->
|
||
|
||
<div class="panel-scroll-body">
|
||
<div id="library-content"><p class="no-results">Sign in to see your library.</p></div>
|
||
</div><!-- .panel-scroll-body -->
|
||
</section>
|
||
|
||
<!-- Detail panel -->
|
||
<section id="panel-detail" class="tab-panel hidden">
|
||
<div class="panel-sticky-header">
|
||
<div class="detail-topbar">
|
||
<button id="btn-detail-back" class="btn-detail-back">
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path d="M19 12H5m7-7-7 7 7 7"/></svg>
|
||
<span id="btn-detail-back-label">Back</span>
|
||
</button>
|
||
</div>
|
||
<div id="detail-header" class="detail-header"></div>
|
||
</div><!-- .panel-sticky-header -->
|
||
|
||
<div class="panel-scroll-body">
|
||
<div id="detail-body" class="detail-body"></div>
|
||
</div><!-- .panel-scroll-body -->
|
||
</section>
|
||
|
||
<!-- Auth panel -->
|
||
<section id="panel-auth" class="tab-panel hidden">
|
||
<div class="panel-scroll-body">
|
||
<!-- Logged-in view (shown when authenticated) -->
|
||
<div id="auth-logged-in" class="auth-page hidden">
|
||
<div class="auth-hero">
|
||
<div class="auth-hero-icon">
|
||
<svg width="36" height="36" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
||
</div>
|
||
<h1 class="auth-hero-title">tiddl-gui</h1>
|
||
</div>
|
||
|
||
<div class="auth-card auth-account-card">
|
||
<div class="auth-account-status-row">
|
||
<div class="auth-account-avatar">
|
||
<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||
</div>
|
||
<div class="auth-account-info">
|
||
<span id="auth-account-username" class="auth-account-name">Loading…</span>
|
||
<span class="auth-account-status">
|
||
<svg width="10" height="10" fill="var(--color-accent)" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5"/></svg>
|
||
Connected to Tidal
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="auth-account-actions">
|
||
<button class="btn-primary" id="btn-go-library-from-auth">
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="margin-right:4px;vertical-align:-2px"><rect x="2" y="3" width="4" height="18"/><rect x="9" y="3" width="4" height="18"/><path d="M16 3l5 2v14l-5 2V3z"/></svg>
|
||
Open Library
|
||
</button>
|
||
<button class="btn-secondary" id="btn-go-download-from-auth">
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="margin-right:4px;vertical-align:-2px"><path d="M12 3v13m0 0-4-4m4 4 4-4"/></svg>
|
||
Start Downloading
|
||
</button>
|
||
</div>
|
||
<button id="btn-logout-account" class="btn-ghost auth-logout-btn">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="margin-right:4px;vertical-align:-1px"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/></svg>
|
||
Sign out
|
||
</button>
|
||
</div>
|
||
|
||
<div class="auth-features">
|
||
<div class="auth-feature">
|
||
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||
<span>Tokens stored locally — never leave your device</span>
|
||
</div>
|
||
<div class="auth-feature">
|
||
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
||
<span>Lossless & Max quality downloads</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Login view (shown when not authenticated) -->
|
||
<div id="auth-login-view" class="auth-page">
|
||
<div class="auth-hero">
|
||
<div class="auth-hero-icon">
|
||
<svg width="36" height="36" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
||
</div>
|
||
<h1 class="auth-hero-title">tiddl-gui</h1>
|
||
<p class="auth-hero-sub">Your Tidal music library, downloaded at maximum quality.</p>
|
||
</div>
|
||
|
||
<div class="auth-card">
|
||
<h2 class="auth-card-heading">Sign in with Tidal</h2>
|
||
<p>Use the secure device-code flow — no password is entered here. Approve the request in a new tab, then return.</p>
|
||
<button id="btn-login" class="btn-login-big">
|
||
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/></svg>
|
||
Login with Tidal
|
||
</button>
|
||
<div id="login-status"></div>
|
||
</div>
|
||
|
||
<div class="auth-features">
|
||
<div class="auth-feature">
|
||
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||
<span>Tokens stored locally — never leave your device</span>
|
||
</div>
|
||
<div class="auth-feature">
|
||
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
||
<span>Lossless & Max quality downloads</span>
|
||
</div>
|
||
<div class="auth-feature">
|
||
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="2" y="3" width="4" height="18"/><rect x="9" y="3" width="4" height="18"/><path d="M16 3l5 2v14l-5 2V3z"/></svg>
|
||
<span>Browse and download your full library</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div><!-- .panel-scroll-body -->
|
||
</section>
|
||
|
||
<!-- Settings panel -->
|
||
<section id="panel-settings" class="tab-panel hidden">
|
||
<div class="panel-sticky-header">
|
||
<h2 class="panel-title">
|
||
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
|
||
Settings
|
||
</h2>
|
||
</div><!-- .panel-sticky-header -->
|
||
<div class="panel-scroll-body">
|
||
<div class="settings-wrapper">
|
||
|
||
<!-- Appearance -->
|
||
<div class="settings-section">
|
||
<h3>
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
|
||
Appearance
|
||
</h3>
|
||
<div class="field-group">
|
||
<label class="field-label">Theme <span class="tip" tabindex="0" data-tip="Colour scheme for the app. System follows your OS preference; Dark always uses dark mode; Light always uses light mode.">?</span></label>
|
||
<div class="segmented-control">
|
||
<button class="seg-btn" data-theme-val="system" data-tip="Follow your operating system's dark/light mode preference automatically.">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
|
||
System
|
||
</button>
|
||
<button class="seg-btn" data-theme-val="dark" data-tip="Always use the dark colour scheme, regardless of OS setting.">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
||
Dark
|
||
</button>
|
||
<button class="seg-btn" data-theme-val="light" data-tip="Always use the light colour scheme, regardless of OS setting.">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
|
||
Light
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="field-group">
|
||
<label class="field-label">UI Effects <span class="tip" tabindex="0" data-tip="Controls glassmorphism, animation speed, and blur effects. 'Full' enables all glass effects; 'Reduced' tones them down; 'Minimal' disables most motion and blur; 'Classic' restores the original solid-surface look with no transparency.">?</span></label>
|
||
<div class="segmented-control">
|
||
<button class="seg-btn" data-effects-val="full">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2z"/></svg>
|
||
Full
|
||
</button>
|
||
<button class="seg-btn" data-effects-val="reduced">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M8 12h8"/></svg>
|
||
Reduced
|
||
</button>
|
||
<button class="seg-btn" data-effects-val="minimal">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>
|
||
Minimal
|
||
</button>
|
||
<button class="seg-btn" data-effects-val="classic">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="2" y="2" width="20" height="20" rx="3"/><path d="M2 7h20M7 2v5"/></svg>
|
||
Classic
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="field-group">
|
||
<label class="field-label">Accent Colour <span class="tip" tabindex="0" data-tip="The highlight colour used for buttons, selected items, active states, and progress bars throughout the app.">?</span></label>
|
||
<div class="color-swatches">
|
||
<button class="swatch" data-color="#4fd08c" style="--c:#4fd08c" title="Green (default)" aria-label="Green"></button>
|
||
<button class="swatch" data-color="#4f9fd0" style="--c:#4f9fd0" title="Blue" aria-label="Blue"></button>
|
||
<button class="swatch" data-color="#9f6fe8" style="--c:#9f6fe8" title="Purple" aria-label="Purple"></button>
|
||
<button class="swatch" data-color="#e86fa0" style="--c:#e86fa0" title="Pink" aria-label="Pink"></button>
|
||
<button class="swatch" data-color="#e8a04f" style="--c:#e8a04f" title="Orange" aria-label="Orange"></button>
|
||
<button class="swatch" data-color="#e8e04f" style="--c:#e8e04f" title="Yellow" aria-label="Yellow"></button>
|
||
<input type="color" id="accent-custom" value="#4fd08c" title="Custom colour" aria-label="Custom colour" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Download -->
|
||
<div class="settings-section">
|
||
<h3>
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 3v13m0 0-4-4m4 4 4-4M4 20h16"/></svg>
|
||
Download
|
||
</h3>
|
||
<div class="settings-grid">
|
||
<div class="field-group">
|
||
<label class="field-label" for="setting-preferred-format">Preferred Format <span class="tip" tabindex="0" data-tip="Choose your preferred audio container format. M4A uses AAC encoding (Low / High quality tiers). FLAC automatically upgrades Low and High quality to HiFi (16-bit lossless) so downloads always use FLAC. Choosing FLAC requires a Tidal HiFi or HiFi Plus subscription.">?</span></label>
|
||
<select id="setting-preferred-format" title="M4A=AAC encoding (Low/High quality) · FLAC=lossless; upgrades Low/High to HiFi automatically">
|
||
<option value="m4a">M4A — AAC (Low / High quality)</option>
|
||
<option value="flac">FLAC — lossless (upgrades to HiFi or above)</option>
|
||
</select>
|
||
</div>
|
||
<div class="field-group">
|
||
<label class="field-label" for="setting-track-quality">Default Track Quality <span class="tip" tabindex="0" data-tip="Audio quality for downloaded tracks. Low=96 kbps M4A · High=320 kbps M4A · HiFi=16-bit FLAC · Max=up to 24-bit FLAC. Higher tiers require a HiFi or HiFi Plus Tidal subscription.">?</span></label>
|
||
<select id="setting-track-quality" title="Low=96 kbps M4A · High=320 kbps M4A · HiFi=16-bit FLAC · Max=up to 24-bit FLAC. Higher tiers require a HiFi or HiFi Plus Tidal subscription.">
|
||
<option value="LOW">Low — 96 kbps .m4a</option>
|
||
<option value="HIGH">High — 320 kbps .m4a</option>
|
||
<option value="LOSSLESS">Lossless — FLAC 16-bit</option>
|
||
<option value="HI_RES_LOSSLESS">Max — FLAC up to 24-bit</option>
|
||
</select>
|
||
</div>
|
||
<div class="field-group">
|
||
<label class="field-label" for="setting-video-quality">Video Quality <span class="tip" tabindex="0" data-tip="Resolution for downloaded video files. Higher quality looks better but uses more storage. SD=360p · HD=720p · Full HD=1080p.">?</span></label>
|
||
<select id="setting-video-quality" title="SD=360p · HD=720p · Full HD=1080p. Higher resolutions use more storage.">
|
||
<option value="sd">SD — 360p</option>
|
||
<option value="hd">HD — 720p</option>
|
||
<option value="fhd">Full HD — 1080p</option>
|
||
</select>
|
||
</div>
|
||
<div class="field-group">
|
||
<label class="field-label" for="setting-threads">Parallel Downloads <span class="tip" tabindex="0" data-tip="How many tracks are downloaded at the same time. Higher values are faster but may trigger Tidal API rate limits. 4 is a safe default.">?</span></label>
|
||
<input id="setting-threads" type="number" min="1" max="20" value="4" title="Number of tracks downloaded at the same time. 4 is a good default. Very high values may trigger API rate limits." />
|
||
</div>
|
||
<div class="field-group">
|
||
<label class="field-label" for="setting-singles-filter">Singles Filter <span class="tip" tabindex="0" data-tip="Controls whether artist discography downloads include singles and EPs. None=full albums only · Only=singles & EPs only · Include=albums and singles/EPs.">?</span></label>
|
||
<select id="setting-singles-filter" title="None=full albums only · Only=singles & EPs only · Include=albums plus singles & EPs">
|
||
<option value="none">None — full albums only</option>
|
||
<option value="only">Only — singles only</option>
|
||
<option value="include">Include — albums + singles</option>
|
||
</select>
|
||
</div>
|
||
<div class="field-group">
|
||
<label class="field-label" for="setting-videos-filter">Videos Filter <span class="tip" tabindex="0" data-tip="Controls whether video content is included in downloads. None=skip all videos · Only=videos only, skip audio tracks · Allow=download both tracks and videos.">?</span></label>
|
||
<select id="setting-videos-filter" title="None=skip videos · Only=videos only · Allow=tracks plus videos">
|
||
<option value="none">None — skip videos</option>
|
||
<option value="only">Only — videos only</option>
|
||
<option value="allow">Allow — tracks + videos</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="checkbox-group">
|
||
<label class="checkbox-label" title="Skip files that have already been downloaded to avoid re-downloading. Matched by filename."><input type="checkbox" id="setting-skip-existing" checked /><span>Skip already-downloaded files</span><span class="tip" tabindex="0" data-tip="When enabled, files that already exist on disk are skipped to avoid re-downloading. Matched by filename — renamed files will be re-downloaded.">?</span></label>
|
||
<label class="checkbox-label" title="Update the file modification timestamp on already-downloaded files when they are skipped."><input type="checkbox" id="setting-update-mtime" /><span>Update modification time of existing files</span><span class="tip" tabindex="0" data-tip="Touches the last-modified timestamp on skipped files, as if they had just been downloaded now. Useful if other tools sort by date.">?</span></label>
|
||
<label class="checkbox-label" title="Re-embed metadata tags into files that already exist, even if the audio was skipped."><input type="checkbox" id="setting-rewrite-metadata" /><span>Rewrite metadata for already-downloaded files</span><span class="tip" tabindex="0" data-tip="Re-embeds metadata tags (title, artist, cover, etc.) into existing files even if the audio itself was not re-downloaded.">?</span></label>
|
||
<label class="checkbox-label" title="Enables a per-item quality selector in the download queue. The main quality selector becomes an 'override all' control."><input type="checkbox" id="setting-advanced-mode" /><span>Advanced mode — set quality per-item in the download queue</span><span class="tip" tabindex="0" data-tip="Adds a quality picker to each item in the download queue so you can use a different quality for each track, album, or playlist individually.">?</span></label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- File Path Templates -->
|
||
<div class="settings-section">
|
||
<h3>
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
||
File Path Templates <span class="tip" tabindex="0" data-tip="Customise the folder structure and filename used when saving downloads. Tokens like {item.title} are replaced with real values at download time. Use / to create sub-folders.">?</span>
|
||
</h3>
|
||
<p>Build custom file path templates. Click or drag a token chip, or type freely. Use <code>/</code> for sub-folders.</p>
|
||
<div class="template-tabs">
|
||
<button class="template-tab-btn active" data-tab="default">Default</button>
|
||
<button class="template-tab-btn" data-tab="track">Track</button>
|
||
<button class="template-tab-btn" data-tab="album">Album</button>
|
||
<button class="template-tab-btn" data-tab="playlist">Playlist</button>
|
||
<button class="template-tab-btn" data-tab="mix">Mix</button>
|
||
<button class="template-tab-btn" data-tab="video">Video</button>
|
||
</div>
|
||
<div class="template-builder active" data-type="default">
|
||
<div class="template-palette"></div><div class="template-drop-zone" aria-label="Drop zone"></div>
|
||
<input class="template-text-input" type="text" placeholder="{album.artist}/{album.title}/{item.title}" spellcheck="false" />
|
||
<div style="margin-bottom:var(--sp-2)"><button class="btn-ghost template-reset-btn">Reset to default</button></div>
|
||
<div class="template-preview-bar"><span class="template-preview-label">Preview:</span><span class="template-preview-value">—</span></div>
|
||
</div>
|
||
<div class="template-builder" data-type="track"><div class="template-palette"></div><div class="template-drop-zone"></div><input class="template-text-input" type="text" placeholder="(uses default if empty)" spellcheck="false" /><div style="margin-bottom:var(--sp-2)"><button class="btn-ghost template-reset-btn">Reset to default</button></div><div class="template-preview-bar"><span class="template-preview-label">Preview:</span><span class="template-preview-value">—</span></div></div>
|
||
<div class="template-builder" data-type="album"><div class="template-palette"></div><div class="template-drop-zone"></div><input class="template-text-input" type="text" placeholder="(uses default if empty)" spellcheck="false" /><div style="margin-bottom:var(--sp-2)"><button class="btn-ghost template-reset-btn">Reset to default</button></div><div class="template-preview-bar"><span class="template-preview-label">Preview:</span><span class="template-preview-value">—</span></div></div>
|
||
<div class="template-builder" data-type="playlist"><div class="template-palette"></div><div class="template-drop-zone"></div><input class="template-text-input" type="text" placeholder="(uses default if empty)" spellcheck="false" /><div style="margin-bottom:var(--sp-2)"><button class="btn-ghost template-reset-btn">Reset to default</button></div><div class="template-preview-bar"><span class="template-preview-label">Preview:</span><span class="template-preview-value">—</span></div></div>
|
||
<div class="template-builder" data-type="mix"><div class="template-palette"></div><div class="template-drop-zone"></div><input class="template-text-input" type="text" placeholder="(uses default if empty)" spellcheck="false" /><div style="margin-bottom:var(--sp-2)"><button class="btn-ghost template-reset-btn">Reset to default</button></div><div class="template-preview-bar"><span class="template-preview-label">Preview:</span><span class="template-preview-value">—</span></div></div>
|
||
<div class="template-builder" data-type="video"><div class="template-palette"></div><div class="template-drop-zone"></div><input class="template-text-input" type="text" placeholder="(uses default if empty)" spellcheck="false" /><div style="margin-bottom:var(--sp-2)"><button class="btn-ghost template-reset-btn">Reset to default</button></div><div class="template-preview-bar"><span class="template-preview-label">Preview:</span><span class="template-preview-value">—</span></div></div>
|
||
</div>
|
||
|
||
<!-- Metadata -->
|
||
<div class="settings-section">
|
||
<h3>
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M4 4h16v16H4z"/><path d="M8 8h8M8 12h8M8 16h4"/></svg>
|
||
Metadata
|
||
</h3>
|
||
<div class="checkbox-group">
|
||
<label class="checkbox-label" title="Embed ID3/FLAC tags (title, artist, album, track number, etc.) into downloaded files."><input type="checkbox" id="setting-meta-enable" checked /><span>Embed metadata in downloaded files</span><span class="tip" tabindex="0" data-tip="Writes ID3 or FLAC tags (title, artist, album, track number, year, etc.) into every downloaded file.">?</span></label>
|
||
<label class="checkbox-label" title="Fetch and embed synced or unsynced lyrics from Tidal into the file tags."><input type="checkbox" id="setting-meta-lyrics" /><span>Embed lyrics in metadata</span><span class="tip" tabindex="0" data-tip="Fetches synced or plain lyrics from Tidal and stores them in the LYRICS/USLT tag inside the file.">?</span></label>
|
||
<label class="checkbox-label" title="Include LRC-style timestamps ([MM:SS.xx]) in the embedded lyrics."><input type="checkbox" id="setting-lyrics-timestamps" checked /><span>Include timestamps in lyrics</span><span class="tip" tabindex="0" data-tip="When enabled, synced lyrics with LRC timestamps ([MM:SS.xx]) are embedded. Disable to store plain text lyrics without timestamps instead.">?</span></label>
|
||
<label class="checkbox-label" title="Embed the album cover art directly inside the downloaded audio file (increases file size)."><input type="checkbox" id="setting-meta-cover" /><span>Embed album cover in track file</span><span class="tip" tabindex="0" data-tip="Stores the album artwork as an embedded thumbnail inside each audio file. Makes files larger but keeps artwork portable.">?</span></label>
|
||
<label class="checkbox-label" title="Store the Tidal album review text in the COMMENT metadata field."><input type="checkbox" id="setting-meta-album-review" /><span>Embed album review in COMMENT field</span><span class="tip" tabindex="0" data-tip="Saves the Tidal album description/review into the COMMENT tag of every track in that album.">?</span></label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cover Art -->
|
||
<div class="settings-section">
|
||
<h3>
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
|
||
Cover Art
|
||
</h3>
|
||
<div class="field-group"><label class="checkbox-label"><input type="checkbox" id="setting-cover-save" /><span>Save cover art as a separate file</span><span class="tip" tabindex="0" data-tip="Saves album or playlist cover artwork as a standalone image file (e.g. cover.jpg) inside the download folder alongside the audio files.">?</span></label></div>
|
||
<div class="field-group">
|
||
<label class="field-label" for="setting-cover-size">Cover size (px, max 1280) <span class="tip" tabindex="0" data-tip="Width and height in pixels of the saved cover image (images are always square). Maximum is 1280. Common values: 640, 1080, 1280.">?</span></label>
|
||
<input id="setting-cover-size" type="number" min="80" max="1280" step="80" value="1280" style="max-width:140px" />
|
||
</div>
|
||
<div class="field-group">
|
||
<label class="field-label">Save cover for <span class="tip" tabindex="0" data-tip="Choose which download types will trigger saving a separate cover image file.">?</span></label>
|
||
<div class="checkbox-group">
|
||
<label class="checkbox-label"><input type="checkbox" id="setting-cover-allowed-track" /><span>Tracks</span></label>
|
||
<label class="checkbox-label"><input type="checkbox" id="setting-cover-allowed-album" /><span>Albums</span></label>
|
||
<label class="checkbox-label"><input type="checkbox" id="setting-cover-allowed-playlist" /><span>Playlists (max 1080px)</span></label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- M3U -->
|
||
<div class="settings-section">
|
||
<h3>
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
||
M3U Playlists
|
||
</h3>
|
||
<div class="field-group"><label class="checkbox-label"><input type="checkbox" id="setting-m3u-save" /><span>Save M3U playlist files alongside downloads</span><span class="tip" tabindex="0" data-tip="Generates a .m3u playlist file listing the downloaded audio tracks. Useful for media players like VLC, foobar2000, or Winamp.">?</span></label></div>
|
||
<div class="field-group">
|
||
<label class="field-label">Generate M3U for <span class="tip" tabindex="0" data-tip="Choose which download types will have an .m3u playlist file generated alongside the audio files.">?</span></label>
|
||
<div class="checkbox-group">
|
||
<label class="checkbox-label"><input type="checkbox" id="setting-m3u-allowed-album" checked /><span>Albums</span></label>
|
||
<label class="checkbox-label"><input type="checkbox" id="setting-m3u-allowed-mix" checked /><span>Mixes</span></label>
|
||
<label class="checkbox-label"><input type="checkbox" id="setting-m3u-allowed-playlist" checked /><span>Playlists</span></label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Experimental -->
|
||
<details class="settings-section settings-advanced">
|
||
<summary class="settings-advanced-summary">
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||
Experimental
|
||
<span class="settings-advanced-badge">experimental</span>
|
||
</summary>
|
||
<p class="settings-advanced-note">
|
||
These features are experimental and may change behaviour. Use with caution.
|
||
</p>
|
||
<div class="checkbox-group">
|
||
<label class="checkbox-label" title="Download each queue item in every quality tier (Low, High, HiFi, Max). Files are named with the quality tier appended. Mutually exclusive with Advanced Mode."><input type="checkbox" id="setting-all-qualities" /><span>Download in all quality tiers</span><span class="tip" tabindex="0" data-tip="When enabled, every item in the queue is downloaded separately in Low, High, HiFi and Max quality. Output filenames include the quality tier label. The quality picker and Advanced Mode per-item selector are disabled while this is active.">?</span></label>
|
||
</div>
|
||
<div class="settings-note settings-note-warning" style="margin-top:var(--sp-3)">
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="flex-shrink:0;margin-top:1px"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
||
<span>All Qualities Mode triggers <strong>four separate downloads per item</strong> — your browser may prompt for each one. It cannot be used alongside Advanced Mode.</span>
|
||
</div>
|
||
</details>
|
||
|
||
<!-- Advanced -->
|
||
<details class="settings-section settings-advanced">
|
||
<summary class="settings-advanced-summary">
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||
Advanced / Developer
|
||
<span class="settings-advanced-badge">for power users</span>
|
||
</summary>
|
||
<p class="settings-advanced-note">
|
||
Only change these if you run your own CORS proxy or have custom Tidal API credentials.
|
||
<strong>The defaults work for most users.</strong>
|
||
</p>
|
||
<div class="field-group">
|
||
<label class="field-label" for="proxy-input">CORS proxy prefix URL <span class="tip" tabindex="0" data-tip="Prepended to every Tidal API request so the browser can reach the API. Required because browsers block direct cross-origin requests. The default (corsproxy.io) works for most users. Leave blank to use the default.">?</span></label>
|
||
<p style="font-size:0.78rem;color:var(--color-text-muted);margin-bottom:0.4rem;line-height:1.5">
|
||
Required for browser API access — Tidal's API blocks direct browser requests.
|
||
Default (<a href="https://corsproxy.io" target="_blank" rel="noopener">corsproxy.io</a>) works out of the box.
|
||
</p>
|
||
<input id="proxy-input" type="url" placeholder="https://corsproxy.io/?url=" autocomplete="off" spellcheck="false" />
|
||
</div>
|
||
<hr class="divider" />
|
||
<p style="font-size:0.78rem;color:var(--color-text-muted);margin-bottom:var(--sp-3);line-height:1.5">
|
||
Override the built-in Tidal API credentials. Only needed if you have your own Tidal developer app.
|
||
</p>
|
||
<div class="settings-grid">
|
||
<div class="field-group">
|
||
<label class="field-label" for="client-id-input">Client ID <span class="tip" tabindex="0" data-tip="Your custom Tidal API client ID. Only needed if you have your own Tidal developer app registered at developer.tidal.com. Leave blank to use the built-in default.">?</span></label>
|
||
<input id="client-id-input" type="text" placeholder="(default)" autocomplete="off" spellcheck="false" />
|
||
</div>
|
||
<div class="field-group">
|
||
<label class="field-label" for="client-secret-input">Client Secret <span class="tip" tabindex="0" data-tip="Your custom Tidal API client secret. Paired with the Client ID above. Leave blank to use the built-in default.">?</span></label>
|
||
<input id="client-secret-input" type="password" placeholder="(default)" autocomplete="off" />
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<!-- About -->
|
||
<div class="settings-section">
|
||
<h3>
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
|
||
About
|
||
</h3>
|
||
<p>
|
||
<strong>tiddl-gui</strong> is an open-source, browser-based Tidal downloader.<br /><br />
|
||
Auth tokens are stored in <code>localStorage</code> and never leave your device.
|
||
</p>
|
||
</div>
|
||
|
||
</div>
|
||
</div><!-- .panel-scroll-body -->
|
||
<div class="settings-save-strip">
|
||
<button id="btn-save-settings" class="btn-primary">Save settings</button>
|
||
<span id="settings-save-msg" class="settings-save-msg">✓ Settings saved</span>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Help panel -->
|
||
<section id="panel-help" class="tab-panel hidden">
|
||
<div class="panel-sticky-header">
|
||
<h2 class="panel-title">
|
||
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3M12 17h.01"/></svg>
|
||
Help
|
||
</h2>
|
||
</div><!-- .panel-sticky-header -->
|
||
<div class="help-layout">
|
||
<nav class="help-sidenav">
|
||
<button class="help-nav-btn active" data-section="login">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/><polyline points="10 17 15 12 10 7"/><line x1="15" y1="12" x2="3" y2="12"/></svg>
|
||
Sign in
|
||
</button>
|
||
<button class="help-nav-btn" data-section="download">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 3v13m0 0-4-4m4 4 4-4M4 20h16"/></svg>
|
||
Download
|
||
</button>
|
||
<button class="help-nav-btn" data-section="search">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||
Search
|
||
</button>
|
||
<button class="help-nav-btn" data-section="library">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="2" y="3" width="4" height="18"/><rect x="9" y="3" width="4" height="18"/><path d="M16 3l5 2v14l-5 2V3z"/></svg>
|
||
Library
|
||
</button>
|
||
<button class="help-nav-btn" data-section="quality">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
||
Quality
|
||
</button>
|
||
<button class="help-nav-btn" data-section="settings">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
|
||
Settings
|
||
</button>
|
||
<button class="help-nav-btn" data-section="cors">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74"/><path d="M21 3v9h-9"/></svg>
|
||
CORS Proxy
|
||
</button>
|
||
<button class="help-nav-btn" data-section="run-locally">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
||
Run Locally
|
||
</button>
|
||
</nav>
|
||
|
||
<div class="help-body panel-scroll-body">
|
||
|
||
<div class="help-section active" id="help-login">
|
||
<h3>Sign in to Tidal</h3>
|
||
<p>Open the <strong>Account</strong> tab and click <strong>Login with Tidal</strong>. A Tidal authorisation page opens in a new tab — approve it there, then return. The app polls automatically and logs you in.</p>
|
||
<p>Your token is stored in <code>localStorage</code> and refreshed automatically; nothing is ever sent to a third-party server.</p>
|
||
<p>Once logged in, the Account tab will show your username and a green <em>Connected to Tidal</em> indicator. You can sign out at any time from the Account tab or the header.</p>
|
||
</div>
|
||
|
||
<div class="help-section" id="help-download">
|
||
<h3>Download tracks, albums & playlists</h3>
|
||
<p>Open the <strong>Download</strong> tab. Type or paste Tidal URLs into the input box and press <kbd>Enter</kbd> to add each one as a pill. Paste a whole list of URLs (one per line) and they all get added at once. Use <strong>Import file</strong> to load a <code>.txt</code> file of URLs.</p>
|
||
<div class="help-table-wrap">
|
||
<table class="help-table">
|
||
<thead><tr><th>Input type</th><th>Example</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Full URL</td><td><code>https://tidal.com/browse/track/103805726</code></td></tr>
|
||
<tr><td>Track</td><td><code>track/103805726</code></td></tr>
|
||
<tr><td>Album</td><td><code>album/103805723</code></td></tr>
|
||
<tr><td>Playlist</td><td><code>playlist/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</code></td></tr>
|
||
<tr><td>Mix</td><td><code>mix/0123456789abcdef</code></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<p>Select your desired quality from the <strong>Quality</strong> dropdown, then click <strong>Download</strong>. In normal mode the quality applies to all items in the queue. Enable <strong>Advanced mode</strong> in Settings to set quality per item.</p>
|
||
<p class="help-note">HiFi / Max requires an eligible Tidal subscription. Encrypted (DRM) streams cannot be saved in the browser.</p>
|
||
</div>
|
||
|
||
<div class="help-section" id="help-search">
|
||
<h3>Search Tidal</h3>
|
||
<p>Click a <strong>track</strong> result to add it to the download queue. Click an <strong>artist</strong>, <strong>album</strong>, or <strong>playlist</strong> card to open a detail page where you can browse contents and download individual items or everything at once. Use the <strong>⊕</strong> button on a card to queue the whole item without navigating away.</p>
|
||
<p>Use the type filter tabs (<em>All, Tracks, Albums, Artists, Playlists</em>) to narrow results. Clicking a tab when results are already showing re-filters instantly; clicking with a query typed but not yet searched will trigger the search automatically.</p>
|
||
<p>Your recent searches are saved and shown as suggestions when you focus the search box.</p>
|
||
</div>
|
||
|
||
<div class="help-section" id="help-library">
|
||
<h3>Your Library</h3>
|
||
<p>Shows your saved Tidal tracks, albums, and playlists. Click any item to add it to the queue or open its detail page. Use the <strong>Tracks / Albums / Playlists</strong> tabs to switch sections. Use the filter box to quickly find items by name within the current section.</p>
|
||
<p>The library requires you to be signed in. It loads on demand and caches within the session.</p>
|
||
</div>
|
||
|
||
<div class="help-section" id="help-quality">
|
||
<h3>Quality tiers</h3>
|
||
<div class="help-table-wrap">
|
||
<table class="help-table">
|
||
<thead><tr><th>Quality</th><th>Format</th><th>Bitrate / Detail</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><span class="quality-pill-sm" data-quality="LOW">Low</span></td><td>.m4a (AAC)</td><td>96 kbps</td></tr>
|
||
<tr><td><span class="quality-pill-sm" data-quality="HIGH">High</span></td><td>.m4a (AAC)</td><td>320 kbps</td></tr>
|
||
<tr><td><span class="quality-pill-sm" data-quality="LOSSLESS">HiFi</span></td><td>.flac</td><td>16-bit 44.1 kHz lossless</td></tr>
|
||
<tr><td><span class="quality-pill-sm" data-quality="HI_RES_LOSSLESS">Max</span></td><td>.flac</td><td>Up to 24-bit 192 kHz</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<p class="help-note">HiFi / Max requires an eligible Tidal HiFi or HiFi Plus subscription. Encrypted (DRM) streams cannot be saved in the browser.</p>
|
||
<p>The <strong>Quality</strong> dropdown on the Download tab applies to all items in the queue (normal mode). Enable <strong>Advanced mode</strong> (Settings → Download) to set quality individually per item. Changing the main quality in advanced mode will prompt you to apply it to all items.</p>
|
||
</div>
|
||
|
||
<div class="help-section" id="help-settings">
|
||
<h3>Settings reference</h3>
|
||
<p><strong>Appearance</strong> — Theme (Dark / Light / System) and accent colour.</p>
|
||
<p><strong>Default Track Quality</strong> — The quality tier used for new downloads. Changes here sync to the Download tab.</p>
|
||
<p><strong>Parallel Downloads</strong> — How many tracks are fetched simultaneously (default: 4). Very high values may hit Tidal API rate limits.</p>
|
||
<p><strong>Skip already-downloaded files</strong> — Avoids re-downloading files that already exist, matched by filename.</p>
|
||
<p><strong>Advanced mode</strong> — Adds a per-item quality selector in the download queue. The main quality selector becomes an "override all" control with a confirmation prompt.</p>
|
||
<p><strong>File Path Templates</strong> — Customise the folder and file names of downloaded files using token chips (e.g. <code>{album.artist}</code>, <code>{item.title}</code>). Use <code>/</code> for subdirectories.</p>
|
||
<p><strong>Metadata</strong> — Controls what tags are embedded in downloaded files (lyrics, cover art, album review).</p>
|
||
<p><strong>Cover Art</strong> — Optionally save the cover image as a separate file alongside downloads.</p>
|
||
<p><strong>M3U Playlists</strong> — Generate <code>.m3u</code> playlist files alongside album and playlist downloads.</p>
|
||
</div>
|
||
|
||
<div class="help-section" id="help-cors">
|
||
<h3>CORS proxy</h3>
|
||
<p>Tidal's API blocks direct browser requests. All API calls go through a CORS proxy. The default (<a href="https://corsproxy.io" target="_blank" rel="noopener">corsproxy.io</a>) works out of the box and requires no setup.</p>
|
||
<p>To use a different proxy, open <strong>Settings → Advanced / Developer</strong> and enter the prefix URL. The proxy must support arbitrary URL-encoded targets.</p>
|
||
</div>
|
||
|
||
<div class="help-section" id="help-run-locally">
|
||
<h3>Run locally</h3>
|
||
<p>No build step is required. Clone the repo and serve the <code>web/</code> folder with any static file server.</p>
|
||
<p><strong>Prerequisites:</strong> <a href="https://git-scm.com" target="_blank" rel="noopener">Git</a> and one of the server options below.</p>
|
||
|
||
<p><strong>Python 3</strong> (usually pre-installed on macOS & Linux):</p>
|
||
<pre class="help-code">git clone https://github.com/19JVJeffery/tiddl-gui.git
|
||
cd tiddl-gui
|
||
python3 -m http.server 8080 --directory web</pre>
|
||
|
||
<p><strong>Node.js</strong> (via <code>npx</code> — no global install needed):</p>
|
||
<pre class="help-code">git clone https://github.com/19JVJeffery/tiddl-gui.git
|
||
cd tiddl-gui
|
||
npx serve web -l 8080</pre>
|
||
|
||
<p><strong>VS Code</strong> — install the <a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer" target="_blank" rel="noopener">Live Server</a> extension, open the <code>web/</code> folder, and click <strong>Go Live</strong>.</p>
|
||
|
||
<p>Open <a href="http://localhost:8080" target="_blank" rel="noopener">http://localhost:8080</a> in your browser.</p>
|
||
|
||
<p><strong>Stop the server:</strong> press <kbd>Ctrl</kbd>+<kbd>C</kbd> in the terminal.</p>
|
||
|
||
<p><strong>Uninstall / remove:</strong></p>
|
||
<ol>
|
||
<li>Stop the server.</li>
|
||
<li>Delete the cloned folder (<code>rm -rf tiddl-gui</code> on macOS/Linux, or <code>rmdir /s /q tiddl-gui</code> on Windows).</li>
|
||
<li>If you used <code>npx serve</code>, no global package was installed — nothing else to remove.</li>
|
||
<li>To clear stored login tokens and settings from your browser, open <strong>Settings → Advanced / Developer → Clear all data</strong> (removes all tokens and settings), or go to <strong>DevTools → Application → Local Storage → localhost → Clear All</strong>.</li>
|
||
</ol>
|
||
</div>
|
||
|
||
</div><!-- .help-body -->
|
||
</div><!-- .help-layout -->
|
||
</section>
|
||
|
||
</main>
|
||
</div>
|
||
|
||
<!-- ── Pinned status bar (progress / log) — always visible at screen bottom ── -->
|
||
<div class="dl-status-bar collapsed" id="dl-status-bar">
|
||
<div id="dl-status-panel" class="dl-status-panel">
|
||
<div class="dl-status-tabs">
|
||
<button class="dl-tab-btn active" id="dl-tab-progress-btn" aria-selected="true">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
|
||
Progress
|
||
</button>
|
||
<button class="dl-tab-btn" id="dl-tab-log-btn" aria-selected="false">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/></svg>
|
||
Log
|
||
</button>
|
||
<div class="dl-tab-spacer"></div>
|
||
<button id="btn-clear-log" class="btn-ghost dl-log-action-btn hidden">Clear log</button>
|
||
<button id="btn-export-log" class="btn-ghost dl-log-action-btn hidden">Export log</button>
|
||
<button id="btn-toggle-status-bar" class="btn-ghost dl-toggle-btn" title="Collapse / expand status bar" aria-label="Expand status bar" aria-expanded="false">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><polyline points="18 15 12 9 6 15"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="dl-status-panel-body">
|
||
<div id="dl-progress-view" class="dl-status-view">
|
||
<div class="progress-section">
|
||
<div class="progress-track"><div id="progress-bar" class="progress-fill" style="width:0%"></div></div>
|
||
<div id="progress-label" class="progress-label">Idle</div>
|
||
</div>
|
||
<div id="dl-item-progress-list" class="dl-item-progress-list"></div>
|
||
</div>
|
||
<div id="dl-log-view" class="dl-status-view hidden">
|
||
<div id="download-log" class="log-box" aria-label="Download log" role="log" aria-live="polite"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="mobile-nav">
|
||
<button class="tab-btn" data-tab="download">
|
||
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 3v13m0 0-4-4m4 4 4-4M4 20h16"/></svg>
|
||
Download
|
||
</button>
|
||
<button class="tab-btn" data-tab="search">
|
||
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||
Search
|
||
</button>
|
||
<button class="tab-btn" data-tab="library">
|
||
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="2" y="3" width="4" height="18"/><rect x="9" y="3" width="4" height="18"/><path d="M16 3l5 2v14l-5 2V3z"/></svg>
|
||
Library
|
||
</button>
|
||
<button class="tab-btn" data-tab="auth">
|
||
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||
Account
|
||
</button>
|
||
<button class="tab-btn" data-tab="settings">
|
||
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
|
||
Settings
|
||
</button>
|
||
</nav>
|
||
|
||
<script type="module">
|
||
import { init } from './js/app.js';
|
||
window.addEventListener('DOMContentLoaded', init);
|
||
</script>
|
||
</body>
|
||
</html>
|