Add Tools sidebar page with grouped layout and Library Maintenance hero

Dashboard Tools & Operations section replaced with a compact link card.
All 10 tool cards moved to a dedicated Tools page in the sidebar, grouped
into three sections: Database & Scanning, Metadata & Cache, Management.

Library Maintenance promoted to hero position at the top of the page with
accent top bar, logo, enable toggle, and tabbed content (Jobs, Findings,
History) — no longer buried in a modal. openRepairModal() now navigates
to the Tools page. Repair modal HTML removed.

Tool initialization extracted from loadDashboardData() into a dedicated
initializeToolsPage() with idempotent event listener wiring. Container
sizing updated to use margin: 20px (matching Dashboard/Stats) instead of
max-width: 1400px for consistent full-width appearance across all pages.
pull/304/head
Broque Thomas 1 month ago
parent cf18590794
commit 60d737f7ab

@ -239,6 +239,10 @@
<span class="nav-icon"><svg class="nav-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/><line x1="9" y1="7" x2="16" y2="7"/><line x1="9" y1="11" x2="14" y2="11"/></svg></span>
<span class="nav-text">Library</span>
</button>
<button class="nav-button" data-page="tools">
<span class="nav-icon"><svg class="nav-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg></span>
<span class="nav-text">Tools</span>
</button>
<button class="nav-button" data-page="stats">
<span class="nav-icon"><svg class="nav-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg></span>
<span class="nav-text">Stats</span>
@ -727,335 +731,18 @@
<div class="dashboard-section">
<h3 class="section-title">Tools & Operations</h3>
<div class="tools-grid">
<div class="tool-card" id="db-updater-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Database Updater</h4>
<button class="tool-help-button" data-tool="db-updater"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Last Full Refresh: <span id="db-last-refresh">Never</span></p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Artists:</span>
<span class="stat-item-value" id="db-stat-artists">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Albums:</span>
<span class="stat-item-value" id="db-stat-albums">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Tracks:</span>
<span class="stat-item-value" id="db-stat-tracks">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Size:</span>
<span class="stat-item-value" id="db-stat-size">0.0 MB</span>
</div>
</div>
<div class="tool-card-controls">
<select id="db-refresh-type">
<option value="incremental">Incremental Update</option>
<option value="full">Full Refresh</option>
</select>
<button id="db-update-button">Update Database</button>
</div>
<div class="tool-card-progress-section">
<p class="progress-phase-label" id="db-phase-label">Idle</p>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="db-progress-bar" style="width: 0%;"></div>
</div>
<p class="progress-details-label" id="db-progress-label">0 / 0 artists (0.0%)</p>
</div>
</div>
<div class="tool-card" id="metadata-updater-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Metadata Updater</h4>
<button class="tool-help-button" data-tool="metadata-updater"
title="Learn more about this tool">?</button>
</div>
<p class="metadata-updater-description tool-card-info">Updates artist photos, genres,
and album art from Spotify.</p>
<div class="tool-card-controls">
<select id="metadata-refresh-interval">
<option value="180">6 months</option>
<option value="90">3 months</option>
<option value="30" selected>1 month</option>
<option value="14">2 weeks</option>
<option value="7">1 week</option>
<option value="0">Full refresh</option>
</select>
<button id="metadata-update-button">Begin Update</button>
</div>
<div class="tool-card-progress-section">
<p class="progress-phase-label" id="metadata-phase-label">Current Artist: Not
running</p>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="metadata-progress-bar" style="width: 0%;">
</div>
</div>
<p class="progress-details-label" id="metadata-progress-label">0 / 0 artists (0.0%)
</p>
</div>
</div>
<div class="tool-card" id="quality-scanner-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Quality Scanner</h4>
<button class="tool-help-button" data-tool="quality-scanner"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Scan library for tracks below quality preferences</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Processed:</span>
<span class="stat-item-value" id="quality-stat-processed">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Quality Met:</span>
<span class="stat-item-value" id="quality-stat-met">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Low Quality:</span>
<span class="stat-item-value" id="quality-stat-low">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Matched:</span>
<span class="stat-item-value" id="quality-stat-matched">0</span>
</div>
</div>
<div class="tool-card-controls">
<select id="quality-scan-scope">
<option value="watchlist">Watchlist Artists Only</option>
<option value="all">All Library Tracks</option>
</select>
<button id="quality-scan-button">Scan Library</button>
</div>
<div class="tool-card-progress-section">
<p class="progress-phase-label" id="quality-phase-label">Ready to scan</p>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="quality-progress-bar" style="width: 0%;">
</div>
</div>
<p class="progress-details-label" id="quality-progress-label">0 / 0 tracks scanned
(0.0%)</p>
</div>
</div>
<div class="tool-card" id="duplicate-cleaner-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Duplicate Cleaner</h4>
<button class="tool-help-button" data-tool="duplicate-cleaner"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Detect and remove duplicate tracks in Transfer folder</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Files Scanned:</span>
<span class="stat-item-value" id="duplicate-stat-scanned">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Duplicates Found:</span>
<span class="stat-item-value" id="duplicate-stat-found">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Deleted:</span>
<span class="stat-item-value" id="duplicate-stat-deleted">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Space Freed:</span>
<span class="stat-item-value" id="duplicate-stat-space">0 MB</span>
</div>
</div>
<div class="tool-card-controls">
<button id="duplicate-clean-button">Clean Duplicates</button>
</div>
<div class="tool-card-progress-section">
<p class="progress-phase-label" id="duplicate-phase-label">Ready to scan</p>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="duplicate-progress-bar" style="width: 0%;">
</div>
</div>
<p class="progress-details-label" id="duplicate-progress-label">0 files scanned
(0.0%)</p>
</div>
</div>
<div class="tool-card" id="discovery-pool-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Discovery Pool</h4>
</div>
<p class="tool-card-info">View and fix matched/failed discovery results across all mirrored playlists</p>
<div class="tool-card-stats" id="discovery-pool-stats">
<div class="stat-item">
<span class="stat-item-label">Matched:</span>
<span class="stat-item-value" id="discovery-pool-matched-count">&mdash;</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Failed:</span>
<span class="stat-item-value" id="discovery-pool-failed-count" style="background-color: rgba(239, 68, 68, 0.15); color: #ef4444;">&mdash;</span>
</div>
</div>
<div class="tool-card-controls">
<button onclick="openDiscoveryPoolModal()">Open Discovery Pool</button>
</div>
</div>
<div class="tool-card" id="retag-tool-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Retag Tool</h4>
<button class="tool-help-button" data-tool="retag-tool"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Fix metadata on previously downloaded albums &amp; singles</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Groups:</span>
<span class="stat-item-value" id="retag-stat-groups">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Tracks:</span>
<span class="stat-item-value" id="retag-stat-tracks">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Artists:</span>
<span class="stat-item-value" id="retag-stat-artists">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Status:</span>
<span class="stat-item-value" id="retag-stat-status">Idle</span>
</div>
</div>
<div class="tool-card-controls">
<button id="retag-open-button">Open Retag Tool</button>
</div>
<div class="tool-card-progress-section">
<p class="progress-phase-label" id="retag-phase-label">Ready</p>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="retag-progress-bar" style="width: 0%;">
</div>
</div>
<p class="progress-details-label" id="retag-progress-label">0 / 0 tracks (0.0%)</p>
</div>
</div>
<div class="tool-card" id="media-scan-card" style="display: none;">
<div class="tool-card-header">
<h4 class="tool-card-title">Media Server Scan</h4>
<button class="tool-help-button" data-tool="media-scan"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Manually trigger Plex media library scan for music</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Last Scan:</span>
<span class="stat-item-value" id="media-scan-last-time">Never</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Status:</span>
<span class="stat-item-value" id="media-scan-status">Idle</span>
</div>
</div>
<div class="tool-card-controls">
<button id="media-scan-button" class="media-scan-btn">
<span class="scan-icon">📡</span>
<span class="scan-text">Scan Library</span>
</button>
</div>
<div class="tool-card-progress-section">
<p class="progress-phase-label" id="media-scan-phase-label">Ready to scan</p>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="media-scan-progress-bar" style="width: 0%;">
</div>
</div>
<p class="progress-details-label" id="media-scan-progress-label">Waiting for scan
request</p>
</div>
</div>
<div class="tool-card" id="backup-manager-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Backup Manager</h4>
<button class="tool-help-button" data-tool="backup-manager"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Create, download, restore and manage database backups</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Last Backup:</span>
<span class="stat-item-value" id="backup-stat-last">Never</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Backups:</span>
<span class="stat-item-value" id="backup-stat-count">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Latest Size:</span>
<span class="stat-item-value" id="backup-stat-latest-size"></span>
</div>
<div class="stat-item">
<span class="stat-item-label">DB Size:</span>
<span class="stat-item-value" id="backup-stat-db-size"></span>
</div>
</div>
<div class="tool-card-controls">
<button id="backup-now-button">Backup Now</button>
</div>
<div id="backup-list-container" class="backup-list-container"></div>
</div>
<!-- Metadata Cache Tool Card -->
<div class="tool-card" id="metadata-cache-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Metadata Cache</h4>
<button class="tool-help-button" data-tool="metadata-cache"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Cached API responses from Spotify &amp; iTunes</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Artists:</span>
<span class="stat-item-value" id="mcache-stat-artists">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Albums:</span>
<span class="stat-item-value" id="mcache-stat-albums">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Tracks:</span>
<span class="stat-item-value" id="mcache-stat-tracks">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Hits:</span>
<span class="stat-item-value" id="mcache-stat-hits">0</span>
</div>
</div>
<div class="tool-card-controls">
<button id="mcache-browse-button" onclick="openMetadataCacheModal()">Browse Cache</button>
<button onclick="openCacheHealthModal()" class="tool-card-btn-secondary">Cache Health</button>
</div>
</div>
<!-- Download Blacklist Card -->
<div class="tool-card" id="blacklist-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Download Blacklist</h4>
</div>
<p class="tool-card-info">Blocked sources that won't be used for future downloads</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Blocked:</span>
<span class="stat-item-value" id="blacklist-count">0</span>
</div>
</div>
<div class="tool-card-controls">
<button onclick="openBlacklistModal()">View Blacklist</button>
<div class="dashboard-tools-link" onclick="navigateToPage('tools')">
<div class="dashboard-tools-link-content">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.5)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
<div>
<span class="dashboard-tools-link-title">Database, scanning, backups, cache, maintenance & more</span>
</div>
</div>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
</div>
</div>
<div class="dashboard-section">
<div class="section-title-row">
<h3 class="section-title">Recent Activity</h3>
@ -6411,6 +6098,443 @@
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════
TOOLS PAGE
═══════════════════════════════════════════════════════════════════ -->
<div class="page" id="tools-page">
<div class="tools-page-container">
<div class="tools-page-header">
<div class="tools-page-header-left">
<h2 class="tools-page-title">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
</svg>
Tools &amp; Operations
</h2>
<p class="tools-page-subtitle">Database management, library scanning, metadata, backups</p>
</div>
</div>
<!-- ── Library Maintenance (hero section) ── -->
<div class="tools-maintenance-hero">
<div class="tools-maintenance-header">
<div class="tools-maintenance-header-left">
<img src="/static/whisoul.png" alt="" class="tools-maintenance-logo" />
<div>
<h3 class="tools-maintenance-title">Library Maintenance</h3>
<p class="tools-maintenance-subtitle">Automated scanning, detection, and repair of library issues</p>
</div>
</div>
<label class="repair-master-toggle">
<input type="checkbox" id="repair-master-toggle" onchange="toggleRepairMaster()">
<span class="repair-toggle-slider"></span>
<span class="repair-toggle-label" id="repair-master-label">Enabled</span>
</label>
</div>
<div class="repair-tabs">
<button class="repair-tab active" data-tab="jobs" onclick="switchRepairTab('jobs')">Jobs</button>
<button class="repair-tab" data-tab="findings" onclick="switchRepairTab('findings')">
Findings <span class="repair-tab-badge" id="repair-findings-tab-badge" style="display:none">0</span>
</button>
<button class="repair-tab" data-tab="history" onclick="switchRepairTab('history')">History</button>
</div>
<div class="repair-tab-content" id="repair-tab-jobs">
<div class="repair-jobs-list" id="repair-jobs-list">
<div class="repair-loading">Loading jobs...</div>
</div>
</div>
<div class="repair-tab-content" id="repair-tab-findings" style="display:none;">
<div class="repair-findings-dashboard" id="repair-findings-dashboard"></div>
<div class="repair-findings-toolbar">
<div class="repair-findings-filters">
<select id="repair-findings-job-filter" onchange="_repairFindingsPage=0;loadRepairFindings()">
<option value="">All Jobs</option>
</select>
<select id="repair-findings-severity-filter" onchange="_repairFindingsPage=0;loadRepairFindings()">
<option value="">All Severity</option>
<option value="info">Info</option>
<option value="warning">Warning</option>
</select>
<select id="repair-findings-status-filter" onchange="_repairFindingsPage=0;loadRepairFindings()">
<option value="pending">Pending</option>
<option value="">All Status</option>
<option value="resolved">Resolved</option>
<option value="dismissed">Dismissed</option>
</select>
</div>
<label class="repair-select-all" title="Select all on this page">
<input type="checkbox" id="repair-select-all-cb" onchange="toggleSelectAllFindings(this.checked)">
<span>Select All</span>
</label>
<div class="repair-findings-bulk" id="repair-findings-bulk" style="display:none;">
<span class="repair-bulk-count" id="repair-bulk-count"></span>
<button class="repair-bulk-btn fix" onclick="bulkFixFindings()">Fix Selected</button>
<button class="repair-bulk-btn" onclick="bulkRepairAction('dismiss')">Dismiss Selected</button>
<button class="repair-bulk-btn fix-all" id="repair-fix-all-btn" style="display:none;" onclick="fixAllMatchingFindings()">Fix All</button>
</div>
<button class="repair-clear-btn" onclick="clearRepairFindings()" title="Clear findings matching current filters">Clear Findings</button>
</div>
<div class="repair-findings-list" id="repair-findings-list">
<div class="repair-loading">Loading findings...</div>
</div>
<div class="repair-findings-pagination" id="repair-findings-pagination"></div>
</div>
<div class="repair-tab-content" id="repair-tab-history" style="display:none;">
<div class="repair-history-list" id="repair-history-list">
<div class="repair-loading">Loading history...</div>
</div>
</div>
</div>
<!-- ── Database & Scanning ── -->
<div class="tools-section">
<h3 class="tools-section-title">Database &amp; Scanning</h3>
<div class="tools-grid">
<div class="tool-card" id="db-updater-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Database Updater</h4>
<button class="tool-help-button" data-tool="db-updater"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Last Full Refresh: <span id="db-last-refresh">Never</span></p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Artists:</span>
<span class="stat-item-value" id="db-stat-artists">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Albums:</span>
<span class="stat-item-value" id="db-stat-albums">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Tracks:</span>
<span class="stat-item-value" id="db-stat-tracks">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Size:</span>
<span class="stat-item-value" id="db-stat-size">0.0 MB</span>
</div>
</div>
<div class="tool-card-controls">
<select id="db-refresh-type">
<option value="incremental">Incremental Update</option>
<option value="full">Full Refresh</option>
</select>
<button id="db-update-button">Update Database</button>
</div>
<div class="tool-card-progress-section">
<p class="progress-phase-label" id="db-phase-label">Idle</p>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="db-progress-bar" style="width: 0%;"></div>
</div>
<p class="progress-details-label" id="db-progress-label">0 / 0 artists (0.0%)</p>
</div>
</div>
<div class="tool-card" id="metadata-updater-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Metadata Updater</h4>
<button class="tool-help-button" data-tool="metadata-updater"
title="Learn more about this tool">?</button>
</div>
<p class="metadata-updater-description tool-card-info">Updates artist photos, genres,
and album art from Spotify.</p>
<div class="tool-card-controls">
<select id="metadata-refresh-interval">
<option value="180">6 months</option>
<option value="90">3 months</option>
<option value="30" selected>1 month</option>
<option value="14">2 weeks</option>
<option value="7">1 week</option>
<option value="0">Full refresh</option>
</select>
<button id="metadata-update-button">Begin Update</button>
</div>
<div class="tool-card-progress-section">
<p class="progress-phase-label" id="metadata-phase-label">Current Artist: Not
running</p>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="metadata-progress-bar" style="width: 0%;">
</div>
</div>
<p class="progress-details-label" id="metadata-progress-label">0 / 0 artists (0.0%)
</p>
</div>
</div>
<div class="tool-card" id="quality-scanner-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Quality Scanner</h4>
<button class="tool-help-button" data-tool="quality-scanner"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Scan library for tracks below quality preferences</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Processed:</span>
<span class="stat-item-value" id="quality-stat-processed">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Quality Met:</span>
<span class="stat-item-value" id="quality-stat-met">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Low Quality:</span>
<span class="stat-item-value" id="quality-stat-low">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Matched:</span>
<span class="stat-item-value" id="quality-stat-matched">0</span>
</div>
</div>
<div class="tool-card-controls">
<select id="quality-scan-scope">
<option value="watchlist">Watchlist Artists Only</option>
<option value="all">All Library Tracks</option>
</select>
<button id="quality-scan-button">Scan Library</button>
</div>
<div class="tool-card-progress-section">
<p class="progress-phase-label" id="quality-phase-label">Ready to scan</p>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="quality-progress-bar" style="width: 0%;">
</div>
</div>
<p class="progress-details-label" id="quality-progress-label">0 / 0 tracks scanned
(0.0%)</p>
</div>
</div>
<div class="tool-card" id="duplicate-cleaner-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Duplicate Cleaner</h4>
<button class="tool-help-button" data-tool="duplicate-cleaner"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Detect and remove duplicate tracks in Transfer folder</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Files Scanned:</span>
<span class="stat-item-value" id="duplicate-stat-scanned">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Duplicates Found:</span>
<span class="stat-item-value" id="duplicate-stat-found">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Deleted:</span>
<span class="stat-item-value" id="duplicate-stat-deleted">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Space Freed:</span>
<span class="stat-item-value" id="duplicate-stat-space">0 MB</span>
</div>
</div>
<div class="tool-card-controls">
<button id="duplicate-clean-button">Clean Duplicates</button>
</div>
<div class="tool-card-progress-section">
<p class="progress-phase-label" id="duplicate-phase-label">Ready to scan</p>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="duplicate-progress-bar" style="width: 0%;">
</div>
</div>
<p class="progress-details-label" id="duplicate-progress-label">0 files scanned
(0.0%)</p>
</div>
</div>
<!-- media-scan moved here from below for grouping -->
<div class="tool-card" id="media-scan-card" style="display: none;">
<div class="tool-card-header">
<h4 class="tool-card-title">Media Server Scan</h4>
<button class="tool-help-button" data-tool="media-scan"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Manually trigger Plex media library scan for music</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Last Scan:</span>
<span class="stat-item-value" id="media-scan-last-time">Never</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Status:</span>
<span class="stat-item-value" id="media-scan-status">Idle</span>
</div>
</div>
<div class="tool-card-controls">
<button id="media-scan-button" class="media-scan-btn">
<span class="scan-icon">&#128225;</span>
<span class="scan-text">Scan Library</span>
</button>
</div>
<div class="tool-card-progress-section">
<p class="progress-phase-label" id="media-scan-phase-label">Ready to scan</p>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="media-scan-progress-bar" style="width: 0%;"></div>
</div>
<p class="progress-details-label" id="media-scan-progress-label">Waiting for scan request</p>
</div>
</div>
</div></div>
<!-- ── Metadata & Cache ── -->
<div class="tools-section">
<h3 class="tools-section-title">Metadata &amp; Cache</h3>
<div class="tools-grid">
<div class="tool-card" id="discovery-pool-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Discovery Pool</h4>
</div>
<p class="tool-card-info">View and fix matched/failed discovery results across all mirrored playlists</p>
<div class="tool-card-stats" id="discovery-pool-stats">
<div class="stat-item">
<span class="stat-item-label">Matched:</span>
<span class="stat-item-value" id="discovery-pool-matched-count">&mdash;</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Failed:</span>
<span class="stat-item-value" id="discovery-pool-failed-count" style="background-color: rgba(239, 68, 68, 0.15); color: #ef4444;">&mdash;</span>
</div>
</div>
<div class="tool-card-controls">
<button onclick="openDiscoveryPoolModal()">Open Discovery Pool</button>
</div>
</div>
<div class="tool-card" id="retag-tool-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Retag Tool</h4>
<button class="tool-help-button" data-tool="retag-tool"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Fix metadata on previously downloaded albums &amp; singles</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Groups:</span>
<span class="stat-item-value" id="retag-stat-groups">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Tracks:</span>
<span class="stat-item-value" id="retag-stat-tracks">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Artists:</span>
<span class="stat-item-value" id="retag-stat-artists">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Status:</span>
<span class="stat-item-value" id="retag-stat-status">Idle</span>
</div>
</div>
<div class="tool-card-controls">
<button id="retag-open-button">Open Retag Tool</button>
</div>
<div class="tool-card-progress-section">
<p class="progress-phase-label" id="retag-phase-label">Ready</p>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="retag-progress-bar" style="width: 0%;">
</div>
</div>
<p class="progress-details-label" id="retag-progress-label">0 / 0 tracks (0.0%)</p>
</div>
</div>
</div></div>
<!-- ── Management ── -->
<div class="tools-section">
<h3 class="tools-section-title">Management</h3>
<div class="tools-grid">
<div class="tool-card" id="backup-manager-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Backup Manager</h4>
<button class="tool-help-button" data-tool="backup-manager"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Create, download, restore and manage database backups</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Last Backup:</span>
<span class="stat-item-value" id="backup-stat-last">Never</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Backups:</span>
<span class="stat-item-value" id="backup-stat-count">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Latest Size:</span>
<span class="stat-item-value" id="backup-stat-latest-size"></span>
</div>
<div class="stat-item">
<span class="stat-item-label">DB Size:</span>
<span class="stat-item-value" id="backup-stat-db-size"></span>
</div>
</div>
<div class="tool-card-controls">
<button id="backup-now-button">Backup Now</button>
</div>
<div id="backup-list-container" class="backup-list-container"></div>
</div>
<!-- Metadata Cache Tool Card -->
<div class="tool-card" id="metadata-cache-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Metadata Cache</h4>
<button class="tool-help-button" data-tool="metadata-cache"
title="Learn more about this tool">?</button>
</div>
<p class="tool-card-info">Cached API responses from Spotify &amp; iTunes</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Artists:</span>
<span class="stat-item-value" id="mcache-stat-artists">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Albums:</span>
<span class="stat-item-value" id="mcache-stat-albums">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Tracks:</span>
<span class="stat-item-value" id="mcache-stat-tracks">0</span>
</div>
<div class="stat-item">
<span class="stat-item-label">Hits:</span>
<span class="stat-item-value" id="mcache-stat-hits">0</span>
</div>
</div>
<div class="tool-card-controls">
<button id="mcache-browse-button" onclick="openMetadataCacheModal()">Browse Cache</button>
<button onclick="openCacheHealthModal()" class="tool-card-btn-secondary">Cache Health</button>
</div>
</div>
<!-- Download Blacklist Card -->
<div class="tool-card" id="blacklist-card">
<div class="tool-card-header">
<h4 class="tool-card-title">Download Blacklist</h4>
</div>
<p class="tool-card-info">Blocked sources that won't be used for future downloads</p>
<div class="tool-card-stats">
<div class="stat-item">
<span class="stat-item-label">Blocked:</span>
<span class="stat-item-value" id="blacklist-count">0</span>
</div>
</div>
<div class="tool-card-controls">
<button onclick="openBlacklistModal()">View Blacklist</button>
</div>
</div>
</div></div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════
WATCHLIST PAGE
═══════════════════════════════════════════════════════════════════ -->
@ -7497,89 +7621,6 @@
</div>
</div>
<!-- Library Maintenance Modal -->
<div class="repair-modal-overlay" id="repair-modal" style="display:none;" onclick="if(event.target===this)closeRepairModal()">
<div class="repair-modal">
<div class="repair-modal-header">
<div class="repair-modal-header-left">
<img src="/static/whisoul.png" alt="" class="repair-modal-logo" />
<div class="repair-modal-header-text">
<h2 class="repair-modal-title">Library Maintenance</h2>
<p class="repair-modal-subtitle">Scan, detect, and fix issues in your music library</p>
</div>
</div>
<div class="repair-modal-header-actions">
<label class="repair-master-toggle">
<input type="checkbox" id="repair-master-toggle" onchange="toggleRepairMaster()">
<span class="repair-toggle-slider"></span>
<span class="repair-toggle-label" id="repair-master-label">Enabled</span>
</label>
<button class="repair-modal-close" onclick="closeRepairModal()">&times;</button>
</div>
</div>
<div class="repair-tabs">
<button class="repair-tab active" data-tab="jobs" onclick="switchRepairTab('jobs')">Jobs</button>
<button class="repair-tab" data-tab="findings" onclick="switchRepairTab('findings')">
Findings <span class="repair-tab-badge" id="repair-findings-tab-badge" style="display:none">0</span>
</button>
<button class="repair-tab" data-tab="history" onclick="switchRepairTab('history')">History</button>
</div>
<div class="repair-tab-content" id="repair-tab-jobs">
<div class="repair-jobs-list" id="repair-jobs-list">
<div class="repair-loading">Loading jobs...</div>
</div>
</div>
<div class="repair-tab-content" id="repair-tab-findings" style="display:none;">
<!-- Summary dashboard -->
<div class="repair-findings-dashboard" id="repair-findings-dashboard"></div>
<!-- Toolbar: filters + bulk actions -->
<div class="repair-findings-toolbar">
<div class="repair-findings-filters">
<select id="repair-findings-job-filter" onchange="_repairFindingsPage=0;loadRepairFindings()">
<option value="">All Jobs</option>
</select>
<select id="repair-findings-severity-filter" onchange="_repairFindingsPage=0;loadRepairFindings()">
<option value="">All Severity</option>
<option value="info">Info</option>
<option value="warning">Warning</option>
</select>
<select id="repair-findings-status-filter" onchange="_repairFindingsPage=0;loadRepairFindings()">
<option value="pending">Pending</option>
<option value="">All Status</option>
<option value="resolved">Resolved</option>
<option value="dismissed">Dismissed</option>
</select>
</div>
<label class="repair-select-all" title="Select all on this page">
<input type="checkbox" id="repair-select-all-cb" onchange="toggleSelectAllFindings(this.checked)">
<span>Select All</span>
</label>
<div class="repair-findings-bulk" id="repair-findings-bulk" style="display:none;">
<span class="repair-bulk-count" id="repair-bulk-count"></span>
<button class="repair-bulk-btn fix" onclick="bulkFixFindings()">Fix Selected</button>
<button class="repair-bulk-btn" onclick="bulkRepairAction('dismiss')">Dismiss Selected</button>
<button class="repair-bulk-btn fix-all" id="repair-fix-all-btn" style="display:none;" onclick="fixAllMatchingFindings()">Fix All</button>
</div>
<button class="repair-clear-btn" onclick="clearRepairFindings()" title="Clear findings matching current filters">Clear Findings</button>
</div>
<div class="repair-findings-list" id="repair-findings-list">
<div class="repair-loading">Loading findings...</div>
</div>
<div class="repair-findings-pagination" id="repair-findings-pagination"></div>
</div>
<div class="repair-tab-content" id="repair-tab-history" style="display:none;">
<div class="repair-history-list" id="repair-history-list">
<div class="repair-loading">Loading history...</div>
</div>
</div>
</div>
</div>
<!-- Right Sidebar Download Indicator (Global - outside all containers) -->
<div class="discover-download-sidebar" id="discover-download-sidebar">
<div class="discover-download-sidebar-header">

@ -3602,6 +3602,7 @@ const WHATS_NEW = {
'2.2': [
// --- April 15, 2026 ---
{ date: 'April 15, 2026' },
{ title: 'Tools Page', desc: 'All tool cards (Database Updater, Quality Scanner, Duplicate Cleaner, Retag, Backups, Cache, etc.) and Library Maintenance moved from the Dashboard to a dedicated Tools page in the sidebar. Dashboard shows a quick-link card', page: 'tools' },
{ title: 'Watchlist & Wishlist Sidebar Pages', desc: 'Watchlist and Wishlist promoted from modals to full sidebar pages. All features preserved — artist grid, scan controls, batch operations, live activity, countdown timers, category cards with mosaic backgrounds. Header buttons now navigate to the pages', page: 'watchlist' },
{ title: 'Picard-Style MusicBrainz Album Consistency', desc: 'Recording MBIDs now pulled from the matched release tracklist instead of independent searches. Batch-level artist name used for stable cache keys. Post-batch consistency pass rewrites album-level tags on all files to guarantee identical MusicBrainz IDs — prevents Navidrome album splits' },
{ title: 'Fix Spotify API Leaking When Deezer/iTunes is Primary', desc: 'Spotify was being called for watchlist album scanning, similar artist discovery, repair jobs, and the Artists page search even when another source was set as primary. All data-fetching now respects the configured primary source. Spotify playlist sync is unaffected' },

@ -3048,6 +3048,9 @@ async function loadPageData(pageId) {
// Load comparisons
loadHydrabaseComparisons();
break;
case 'tools':
await initializeToolsPage();
break;
case 'watchlist':
await initializeWatchlistPage();
break;
@ -25088,80 +25091,81 @@ function resetWishlistModalToIdleState() {
}
}
async function loadDashboardData() {
// Attach event listeners for the DB updater tool
let toolsPageState = { isInitialized: false };
async function initializeToolsPage() {
// Attach event listeners for tool buttons (idempotent — getElementById returns null if already wired)
const updateButton = document.getElementById('db-update-button');
if (updateButton) {
if (updateButton && !updateButton._toolsWired) {
updateButton.addEventListener('click', handleDbUpdateButtonClick);
updateButton._toolsWired = true;
}
// Attach event listeners for the metadata updater tool
const metadataButton = document.getElementById('metadata-update-button');
if (metadataButton) {
if (metadataButton && !metadataButton._toolsWired) {
metadataButton.addEventListener('click', handleMetadataUpdateButtonClick);
metadataButton._toolsWired = true;
}
// Check active media server and hide metadata updater if not Plex
await checkAndHideMetadataUpdaterForNonPlex();
// Check for ongoing metadata update and restore state
await checkAndRestoreMetadataUpdateState();
// Attach event listener for the quality scanner tool
const qualityScanButton = document.getElementById('quality-scan-button');
if (qualityScanButton) {
if (qualityScanButton && !qualityScanButton._toolsWired) {
qualityScanButton.addEventListener('click', handleQualityScanButtonClick);
qualityScanButton._toolsWired = true;
}
// Attach event listener for the duplicate cleaner tool
const duplicateCleanButton = document.getElementById('duplicate-clean-button');
if (duplicateCleanButton) {
if (duplicateCleanButton && !duplicateCleanButton._toolsWired) {
duplicateCleanButton.addEventListener('click', handleDuplicateCleanButtonClick);
duplicateCleanButton._toolsWired = true;
}
// Attach event listener for the retag tool
const retagOpenButton = document.getElementById('retag-open-button');
if (retagOpenButton) {
if (retagOpenButton && !retagOpenButton._toolsWired) {
retagOpenButton.addEventListener('click', openRetagModal);
retagOpenButton._toolsWired = true;
}
// Attach event listener for the media scan tool
const mediaScanButton = document.getElementById('media-scan-button');
if (mediaScanButton) {
if (mediaScanButton && !mediaScanButton._toolsWired) {
mediaScanButton.addEventListener('click', handleMediaScanButtonClick);
mediaScanButton._toolsWired = true;
}
// Check active media server and show media scan tool only for Plex
await checkAndShowMediaScanForPlex();
// Attach event listener for the backup manager
const backupNowButton = document.getElementById('backup-now-button');
if (backupNowButton) backupNowButton.addEventListener('click', handleBackupNowClick);
loadBackupList();
if (backupNowButton && !backupNowButton._toolsWired) {
backupNowButton.addEventListener('click', handleBackupNowClick);
backupNowButton._toolsWired = true;
}
// Attach event listeners for tool help buttons
// Tool-specific init
await checkAndHideMetadataUpdaterForNonPlex();
await checkAndRestoreMetadataUpdateState();
await checkAndShowMediaScanForPlex();
loadBackupList();
initializeToolHelpButtons();
// Initial load of retag stats
loadRetagStats();
// Check for ongoing retag operation
checkRetagStatus();
// Initial load of stats
await fetchAndUpdateDbStats();
loadDiscoveryPoolStats();
loadMetadataCacheStats();
// Start periodic refresh of stats (every 10 seconds)
stopDbStatsPolling(); // Ensure no duplicates
// Start polling (cleared when navigating away via loadPageData preamble)
stopDbStatsPolling();
dbStatsInterval = setInterval(fetchAndUpdateDbStats, 10000);
// Initial load of discovery pool stats for the tool card
loadDiscoveryPoolStats();
// Check for ongoing operations
await checkAndUpdateDbProgress();
await checkAndUpdateQualityScanProgress();
await checkAndUpdateDuplicateCleanProgress();
// Initial load of metadata cache stats + periodic refresh
loadMetadataCacheStats();
setInterval(loadMetadataCacheStats, 15000);
// Initialize library maintenance section
updateRepairStatus();
switchRepairTab('jobs');
toolsPageState.isInitialized = true;
}
async function loadDashboardData() {
// Initial load of wishlist count
await updateWishlistCount();
@ -25186,15 +25190,6 @@ async function loadDashboardData() {
// Start periodic toast checking (every 3 seconds)
setInterval(checkForActivityToasts, 3000);
// Also check the status of any ongoing update when the page loads
await checkAndUpdateDbProgress();
// Check for any ongoing quality scanner when the page loads
await checkAndUpdateQualityScanProgress();
// Check for any ongoing duplicate cleaner when the page loads
await checkAndUpdateDuplicateCleanProgress();
// Check for any active download processes that need rehydration
await checkForActiveProcesses();
@ -63191,10 +63186,12 @@ let _repairJobsCache = {}; // Cache job data for help modal
* Open the Library Maintenance modal
*/
async function openRepairModal() {
const modal = document.getElementById('repair-modal');
if (!modal) return;
modal.style.display = 'flex';
document.body.style.overflow = 'hidden';
navigateToPage('tools');
// Scroll to maintenance section
setTimeout(() => {
const section = document.querySelector('.tools-maintenance-section');
if (section) section.scrollIntoView({ behavior: 'smooth' });
}, 100);
_repairCurrentTab = 'jobs';
switchRepairTab('jobs');
// Load master toggle state
@ -63213,10 +63210,7 @@ async function openRepairModal() {
}
function closeRepairModal() {
const modal = document.getElementById('repair-modal');
if (!modal) return;
modal.style.display = 'none';
document.body.style.overflow = '';
// No-op — repair content now lives on the tools page, no modal to close
}
async function toggleRepairMaster() {

@ -55797,14 +55797,232 @@ body.reduce-effects *::after {
}
}
/*
DASHBOARD TOOLS LINK CARD
*/
.dashboard-tools-link {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
background: rgba(255, 255, 255, 0.025);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 12px;
cursor: pointer;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.dashboard-tools-link:hover {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(var(--accent-rgb), 0.15);
transform: translateY(-1px);
}
.dashboard-tools-link-content {
display: flex;
align-items: center;
gap: 12px;
}
.dashboard-tools-link-title {
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
font-weight: 500;
}
/*
TOOLS PAGE
*/
.tools-page-container {
padding: 28px 24px 30px;
margin: 20px;
background: linear-gradient(135deg,
rgba(20, 20, 20, 0.55) 0%,
rgba(12, 12, 12, 0.62) 100%);
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-top: 1px solid rgba(255, 255, 255, 0.12);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.3),
0 4px 16px rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.tools-page-header {
padding: 20px 24px 18px;
margin: -28px -24px 24px -24px;
background: linear-gradient(180deg,
rgba(var(--accent-rgb), 0.08) 0%,
rgba(var(--accent-rgb), 0.03) 40%,
transparent 100%);
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
border-top-left-radius: 24px;
border-top-right-radius: 24px;
position: relative;
}
.tools-page-header::after {
content: '';
position: absolute;
bottom: -1px;
left: 10%;
right: 10%;
height: 1px;
background: linear-gradient(90deg,
transparent 0%,
rgba(var(--accent-rgb), 0.2) 20%,
rgba(var(--accent-rgb), 0.35) 50%,
rgba(var(--accent-rgb), 0.2) 80%,
transparent 100%);
}
.tools-page-title {
display: flex;
align-items: center;
gap: 10px;
font-size: 26px;
font-weight: 700;
color: #fff;
margin: 0;
letter-spacing: -0.5px;
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
}
.tools-page-subtitle {
font-size: 13px;
color: rgba(255, 255, 255, 0.4);
margin: 6px 0 0 0;
font-weight: 500;
}
/* ── Library Maintenance hero section ── */
.tools-maintenance-hero {
background: linear-gradient(135deg,
rgba(20, 20, 20, 0.95) 0%,
rgba(12, 12, 12, 0.98) 100%);
border: 1px solid rgba(255, 255, 255, 0.08);
border-top: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 16px;
padding: 20px 24px;
margin-bottom: 28px;
box-shadow:
0 4px 16px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.06);
position: relative;
overflow: hidden;
}
.tools-maintenance-hero::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg,
transparent,
rgba(var(--accent-rgb), 0.5),
rgba(var(--accent-rgb), 0.8),
rgba(var(--accent-rgb), 0.5),
transparent);
box-shadow: 0 0 12px rgba(var(--accent-rgb), 0.3);
}
.tools-maintenance-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.tools-maintenance-header-left {
display: flex;
align-items: center;
gap: 14px;
}
.tools-maintenance-logo {
width: 40px;
height: 40px;
border-radius: 10px;
object-fit: cover;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.tools-maintenance-title {
font-size: 20px;
font-weight: 700;
color: #fff;
margin: 0 0 2px 0;
letter-spacing: -0.3px;
}
.tools-maintenance-subtitle {
font-size: 12px;
color: rgba(255, 255, 255, 0.4);
margin: 0;
font-weight: 500;
}
/* Repair tabs on the tools page */
.tools-maintenance-hero .repair-tabs {
margin-bottom: 16px;
}
.tools-maintenance-hero .repair-tab-content {
min-height: 200px;
}
/* ── Tool card sections ── */
.tools-section {
margin-bottom: 24px;
}
.tools-section-title {
font-size: 13px;
font-weight: 600;
color: rgba(255, 255, 255, 0.35);
text-transform: uppercase;
letter-spacing: 1.2px;
margin: 0 0 12px 4px;
padding-bottom: 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}
@media (max-width: 768px) {
.tools-page-container {
padding: 16px;
margin: 10px;
border-radius: 16px;
}
.tools-page-header {
padding: 16px;
margin: -16px -16px 16px -16px;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
}
.tools-page-title {
font-size: 22px;
}
.tools-maintenance-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
}
/*
WATCHLIST PAGE
*/
.watchlist-page-container {
padding: 28px 24px 30px;
max-width: 1400px;
margin: 0 auto;
margin: 20px;
background: linear-gradient(135deg,
rgba(20, 20, 20, 0.55) 0%,
rgba(12, 12, 12, 0.62) 100%);
@ -56216,8 +56434,7 @@ body.reduce-effects *::after {
.wishlist-page-container {
padding: 28px 24px 30px;
max-width: 1400px;
margin: 0 auto;
margin: 20px;
background: linear-gradient(135deg,
rgba(20, 20, 20, 0.55) 0%,
rgba(12, 12, 12, 0.62) 100%);
@ -56534,6 +56751,7 @@ body.reduce-effects *::after {
.watchlist-page-container,
.wishlist-page-container {
padding: 16px;
margin: 10px;
border-radius: 16px;
}

Loading…
Cancel
Save