Files
lucianoandClaude Opus 4.7 9644ffec35 feat: metadati AI detection + prompt/lyrics + pannello frame grezzi (v1.13.0)
Metadati tab estesa per audit e cleanup file AI-generated:

- Nuovi campi read/write: source_url (WOAS ID3), lyrics (USLT), plus
  \xa9lyr per M4A
- Badge AI con riconoscimento: Suno, Udio, Riffusion, Mubert, AIVA,
  Boomy, SoundRAW, Loudly + presenza C2PA manifest (GEOB frame)
- Pannello "Metadati avanzati" collapsable: tabella di tutti i frame
  ID3 grezzi (chiave, tipo text/binary, preview, size)
- Rimozione selettiva frame custom via bridge remove_metadata_frames:
  ID3.__delitem__() + save() per MP3, MP4.tags per M4A. Frame standard
  protetti (TIT2/TPE1/APIC ecc.) marcati 🔒 nella UI, si svuotano dai
  campi form
- Rilevamento AI riusabile via core.metadata._detect_ai(comment,
  source_url, extra) — case-insensitive, returns (is_ai, source_name)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-09-24 11:13:58 +02:00

1834 lines
85 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>MusicTools</title>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<div class="app">
<!-- ============== SIDEBAR ============== -->
<aside class="sidebar">
<div class="brand">
<div class="brand-logo">
<span>♫</span>
</div>
<div class="brand-name">MusicTools</div>
</div>
<div class="divider"></div>
<nav class="nav">
<div class="nav-section-label">Scarica</div>
<button class="nav-item active" data-view="download" data-feature="audio">
<span class="nav-icon">⬇</span>
<span>Scarica</span>
</button>
<button class="nav-item" data-view="video" data-feature="video">
<span class="nav-icon">🎬</span>
<span>Video</span>
</button>
<button class="nav-item" data-view="spotify" data-feature="audio">
<span class="nav-icon">🟢</span>
<span>Spotify</span>
</button>
<button class="nav-item" data-view="youtube" data-feature="audio">
<span class="nav-icon">▶</span>
<span>YouTube</span>
</button>
<button class="nav-item" data-view="beatport" data-feature="audio">
<span class="nav-icon">🎧</span>
<span>Beatport</span>
</button>
<button class="nav-item" data-view="traxsource" data-feature="audio">
<span class="nav-icon">▲</span>
<span>Traxsource</span>
</button>
<button class="nav-item" data-view="charts" data-feature="audio">
<span class="nav-icon">📊</span>
<span>Charts</span>
</button>
<div class="nav-section-label">Organizza</div>
<button class="nav-item" data-view="dedup" data-feature="audio">
<span class="nav-icon">🗑</span>
<span>Dedup</span>
</button>
<button class="nav-item" data-view="catalog" data-feature="audio">
<span class="nav-icon">📅</span>
<span>Cataloga</span>
</button>
<button class="nav-item" data-view="flatten" data-feature="audio">
<span class="nav-icon">📤</span>
<span>Appiattisci</span>
</button>
<button class="nav-item" data-view="convert" data-feature="audio">
<span class="nav-icon">🔄</span>
<span>Converti</span>
</button>
<button class="nav-item" data-view="meta" data-feature="metadata">
<span class="nav-icon">🏷</span>
<span>Metadati</span>
</button>
<button class="nav-item" data-view="upgrade" data-feature="upgrade">
<span class="nav-icon">⚡</span>
<span>Upgrade</span>
</button>
<div class="nav-section-label">Altro</div>
<button class="nav-item" data-view="record" data-feature="record">
<span class="nav-icon">●</span>
<span>Registra</span>
</button>
<div class="nav-section-label">Sistema</div>
<button class="nav-item" data-view="settings">
<span class="nav-icon">⚙</span>
<span>Impostazioni</span>
</button>
<button class="nav-item" data-view="guide">
<span class="nav-icon">📖</span>
<span>Guida</span>
</button>
</nav>
<div class="quota-box" id="quotaBox" hidden>
<div class="quota-label">Oggi</div>
<div class="quota-value">
<span id="quotaUsed">0</span>
<span class="quota-sep">/</span>
<span id="quotaLimit">10</span>
</div>
<div class="quota-plan" id="quotaPlanName">Basic</div>
<button type="button" class="quota-upgrade" id="quotaUpgradeBtn" hidden>
Upgrade
</button>
</div>
<div class="footer">
<div class="footer-by">by LuZa</div>
<div class="footer-version" id="footerVersion">v1.0</div>
</div>
</aside>
<!-- ============== MAIN ============== -->
<main class="main">
<!-- ===== VIEW: DOWNLOAD ===== -->
<section class="view active" id="view-download">
<header class="hero hero-green">
<div class="hero-content">
<div class="hero-eyebrow">DOWNLOAD</div>
<h1 class="hero-title">Scarica la tua musica</h1>
<p class="hero-subtitle">Spotify · YouTube · SoundCloud · qualsiasi link supportato da yt-dlp</p>
</div>
<div class="hero-deco">♫</div>
</header>
<h2 class="section-label">Sorgente</h2>
<div class="card">
<div class="field">
<label class="field-label">
URL o titolo brano
<span class="badge" id="urlListBadge" hidden></span>
</label>
<div class="row">
<input type="text" id="urlInput" class="input pill" placeholder="Link Spotify/YouTube… oppure Artista - Titolo · Titolo brano" />
<button class="btn btn-ghost pill" id="loadListBtn">
<span>📂</span> Carica lista
</button>
</div>
<div class="hint">
<span class="hint-ico">ⓘ</span>
<div>
Puoi incollare un <strong>link</strong> (Spotify, YouTube, SoundCloud…) oppure scrivere direttamente
<strong>Artista - Titolo</strong> o solo il <strong>titolo del brano</strong>: l'app cerca il match migliore su YouTube e scarica in MP3.
</div>
</div>
</div>
<div class="field">
<label class="field-label">Cartella output</label>
<div class="row">
<div class="path-display" id="dlPathDisplay">Nessuna cartella selezionata</div>
<button class="btn btn-ghost pill btn-sm" id="dlBrowseBtn">Sfoglia</button>
</div>
</div>
</div>
<div class="card actions">
<button class="btn btn-primary pill btn-lg" id="downloadBtn">
<span class="ico">▶</span> Avvia Download
</button>
<button class="btn btn-danger pill btn-lg" id="stopDownloadBtn" disabled>
<span class="ico">◼</span> Stop
</button>
<div class="counter" id="dlCounter"></div>
</div>
<div class="card">
<div class="progress-head">
<span class="micro-label">Progresso</span>
<span class="percent green" id="dlPercent">0%</span>
</div>
<div class="progress">
<div class="progress-fill green" id="dlProgressFill"></div>
</div>
</div>
<h2 class="section-label">Log</h2>
<div class="card log-card">
<div class="log" id="dlLog"></div>
</div>
</section>
<!-- ===== VIEW: VIDEO ===== -->
<section class="view" id="view-video">
<header class="hero hero-pink">
<div class="hero-content">
<div class="hero-eyebrow pink">VIDEO</div>
<h1 class="hero-title">Scarica video</h1>
<p class="hero-subtitle">YouTube · TikTok · Instagram · Facebook · qualsiasi link supportato</p>
</div>
<div class="hero-deco">🎬</div>
</header>
<h2 class="section-label">Sorgente</h2>
<div class="card">
<div class="field">
<label class="field-label">URL</label>
<input type="text" id="videoUrlInput" class="input pill" placeholder="https://youtube.com/watch?v=… · https://tiktok.com/… · https://instagram.com/reel/…" />
</div>
<div class="field">
<label class="field-label">Cartella output</label>
<div class="row">
<div class="path-display" id="videoPathDisplay">Nessuna cartella selezionata</div>
<button class="btn btn-ghost pill btn-sm" id="videoBrowseBtn">Sfoglia</button>
</div>
</div>
<div class="field inline">
<label class="field-label">Modalità</label>
<div class="segmented" id="videoMode">
<button type="button" class="seg-btn active" data-mode="video">🎬 Video (MP4)</button>
<button type="button" class="seg-btn" data-mode="audio">🎵 Solo audio (MP3)</button>
</div>
</div>
<div class="field inline" id="videoQualityField">
<label class="field-label">Qualità</label>
<select id="videoQuality" class="input pill input-sm">
<option value="best">Migliore disponibile</option>
<option value="1080p" selected>1080p</option>
<option value="720p">720p</option>
<option value="480p">480p</option>
</select>
</div>
<div class="hint">
<span class="hint-ico">ⓘ</span>
<div>
In modalità <strong>Solo audio</strong> il file viene salvato in MP3 al bitrate impostato in Impostazioni → Download.<br>
Per Instagram/Facebook privati serve un file <strong>cookies.txt</strong> valido (Impostazioni → Percorsi).
</div>
</div>
</div>
<div class="card actions">
<button class="btn btn-pink pill btn-lg" id="videoDownloadBtn">
<span class="ico">▶</span> Avvia Download
</button>
<button class="btn btn-danger pill btn-lg" id="stopVideoBtn" disabled>
<span class="ico">◼</span> Stop
</button>
<div class="counter" id="videoCounter"></div>
</div>
<div class="card">
<div class="progress-head">
<span class="micro-label">Progresso</span>
<span class="percent pink" id="videoPercent">0%</span>
</div>
<div class="progress">
<div class="progress-fill pink" id="videoProgressFill"></div>
</div>
</div>
<h2 class="section-label">Log</h2>
<div class="card log-card">
<div class="log" id="videoLog"></div>
</div>
</section>
<!-- ===== VIEW: UPGRADE ===== -->
<section class="view" id="view-upgrade">
<header class="hero hero-blue">
<div class="hero-content">
<div class="hero-eyebrow blue">UPGRADE QUALITÀ</div>
<h1 class="hero-title">Migliora i tuoi audio</h1>
<p class="hero-subtitle">Riscarica i file a bitrate più alto. Imposta la soglia minima accettabile.</p>
</div>
<div class="hero-deco">⚡</div>
</header>
<h2 class="section-label">Cartella audio</h2>
<div class="card">
<div class="field">
<div class="row">
<div class="path-display" id="upPathDisplay">Nessuna cartella selezionata</div>
<button class="btn btn-ghost pill btn-sm" id="upBrowseBtn">📂 Sfoglia</button>
</div>
</div>
<label class="checkbox">
<input type="checkbox" id="upRecursive" />
<span class="check-box"></span>
<span>Includi sottocartelle</span>
</label>
<div class="info-row" id="upInfo"></div>
<div class="field inline">
<label class="field-label">Soglia HQ (kbps)</label>
<input type="number" id="upThreshold" class="input pill input-sm" value="310" />
</div>
<div class="field">
<label class="field-label">Cartella archivio (opzionale)</label>
<div class="row">
<div class="path-display empty" id="upArchivePathDisplay">Nessuna cartella</div>
<button class="btn btn-ghost pill btn-sm" id="upArchiveBrowseBtn">📂 Sfoglia</button>
<button class="btn btn-ghost pill btn-sm" id="upArchiveClearBtn" hidden>Rimuovi</button>
</div>
<label style="display:flex;gap:6px;align-items:center;margin-top:8px;font-size:13px;color:var(--text-2);">
<input type="checkbox" id="upArchiveAutoPick"/>
<span>Scelta automatica se ci sono più match (usa quello a bitrate più alto)</span>
</label>
<div class="hint">
<span class="hint-ico">ⓘ</span>
<div>Se selezionata, l'app cerca versioni HQ del brano nella tua libreria locale prima di scaricare da YouTube. In caso di match multipli scegli tu quale usare — oppure attiva "Scelta automatica" per non fermarti.</div>
</div>
</div>
</div>
<div class="card actions">
<button class="btn btn-blue pill btn-lg" id="upgradeBtn">
<span class="ico">▶</span> Avvia Upgrade
</button>
<button class="btn btn-danger pill btn-lg" id="stopUpgradeBtn" disabled>
<span class="ico">◼</span> Stop
</button>
<div class="counter" id="upCounter"></div>
</div>
<div class="card">
<div class="progress-head">
<span class="micro-label">Progresso</span>
<span class="percent blue" id="upPercent">0%</span>
</div>
<div class="progress">
<div class="progress-fill blue" id="upProgressFill"></div>
</div>
</div>
<h2 class="section-label">Log</h2>
<div class="card log-card">
<div class="log" id="upLog"></div>
</div>
</section>
<!-- ===== VIEW: REGISTRA ===== -->
<section class="view" id="view-record">
<header class="hero hero-red">
<div class="hero-content">
<div class="hero-eyebrow red">REGISTRAZIONE</div>
<h1 class="hero-title">Cattura audio</h1>
<p class="hero-subtitle">Registra in MP3 320 kbps da qualsiasi ingresso audio (mixer, scheda, microfono, BlackHole/loopback)</p>
</div>
<div class="hero-deco">●</div>
</header>
<h2 class="section-label">Dispositivo</h2>
<div class="card">
<div class="field">
<label class="field-label">Ingresso audio</label>
<div class="row">
<select id="recDevice" class="input pill">
<option value="">Caricamento…</option>
</select>
<button class="btn btn-ghost pill btn-sm" id="recRefreshBtn">↻ Aggiorna</button>
</div>
</div>
<div class="hint" id="recHint">
<span class="hint-ico">ⓘ</span>
<div>
<strong>Per registrare l'audio di sistema (Spotify/YouTube):</strong>
installa <code>brew install blackhole-2ch</code>, imposta l'uscita audio di macOS su <strong>BlackHole 2ch</strong>, poi seleziona qui sotto <strong>🔄 BlackHole 2ch (loopback)</strong>.
<br><br>
Per sentire l'audio mentre registri serve un <strong>Multi-Output Device</strong> in <em>Configurazione MIDI Audio</em> (BlackHole + altoparlanti) — guida completa nel
<a href="#" id="recOpenGuide">README su GitHub</a>.
</div>
</div>
</div>
<h2 class="section-label">Destinazione</h2>
<div class="card">
<div class="field">
<label class="field-label">Cartella output</label>
<div class="row">
<div class="path-display" id="recPathDisplay">Nessuna cartella selezionata</div>
<button class="btn btn-ghost pill btn-sm" id="recBrowseBtn">Sfoglia</button>
</div>
</div>
<div class="field">
<label class="field-label">Nome file (opzionale)</label>
<input type="text" id="recFilename" class="input pill" placeholder="Registrazione_AAAAMMGG_HHMMSS.mp3" />
</div>
<div class="field inline">
<label class="field-label">Bitrate</label>
<select id="recBitrate" class="input pill input-sm">
<option value="128k">128 kbps</option>
<option value="192k">192 kbps</option>
<option value="256k">256 kbps</option>
<option value="320k" selected>320 kbps</option>
</select>
</div>
</div>
<div class="card actions">
<button class="btn btn-red pill btn-lg" id="recStartBtn">
<span class="ico">●</span> REC
</button>
<button class="btn btn-danger pill btn-lg" id="recStopBtn" disabled>
<span class="ico">◼</span> Stop
</button>
<div class="rec-timer" id="recTimer">00:00:00</div>
<div class="rec-status" id="recStatus"></div>
</div>
<div class="card" id="recOutputCard" hidden>
<div class="micro-label">Ultima registrazione</div>
<div class="rec-result" id="recResult"></div>
</div>
<div class="card rec-error-card" id="recErrorCard" hidden>
<div class="micro-label red">Errore registrazione</div>
<div class="rec-error-msg" id="recErrorMsg"></div>
<div class="row" style="gap: 10px; margin-top: 12px;">
<button class="btn btn-ghost pill btn-sm" id="recShowLogBtn">Mostra log tecnici</button>
<button class="btn btn-ghost pill btn-sm" id="recCopyLogBtn" hidden>Copia log</button>
</div>
<pre class="rec-error-log" id="recErrorLog" hidden></pre>
</div>
</section>
<!-- ===== VIEW: METADATI ===== -->
<section class="view" id="view-meta">
<header class="hero hero-amber">
<div class="hero-content">
<div class="hero-eyebrow amber">METADATI</div>
<h1 class="hero-title">Modifica tag audio</h1>
<p class="hero-subtitle">Titolo · Artista · Album · Anno · Copertina · BPM · Key · MP3/M4A/FLAC</p>
</div>
<div class="hero-deco">🏷</div>
</header>
<h2 class="section-label">File</h2>
<div class="card">
<div class="row">
<button class="btn btn-amber pill" id="metaPickBtn">📂 Apri file audio</button>
<div class="path-display" id="metaPathDisplay">Nessun file selezionato</div>
<span id="metaAiBadge" class="ai-badge" hidden title="Brano generato con AI"></span>
</div>
<div class="meta-info" id="metaInfo" hidden></div>
</div>
<div id="metaForm" hidden>
<h2 class="section-label">Tag</h2>
<div class="card">
<div class="form-grid">
<div class="field full">
<label class="field-label">Titolo</label>
<input type="text" id="metaTitle" class="input pill">
</div>
<div class="field full">
<label class="field-label">Artista</label>
<input type="text" id="metaArtist" class="input pill">
</div>
<div class="field">
<label class="field-label">Album</label>
<input type="text" id="metaAlbum" class="input pill">
</div>
<div class="field">
<label class="field-label">Album Artist</label>
<input type="text" id="metaAlbumArtist" class="input pill">
</div>
<div class="field">
<label class="field-label">Anno</label>
<input type="text" id="metaYear" class="input pill">
</div>
<div class="field">
<label class="field-label">Traccia</label>
<input type="text" id="metaTrack" class="input pill" placeholder="1/12">
</div>
<div class="field">
<label class="field-label">Genere</label>
<input type="text" id="metaGenre" class="input pill">
</div>
<div class="field">
<label class="field-label">BPM</label>
<input type="text" id="metaBpm" class="input pill">
</div>
<div class="field">
<label class="field-label">Key</label>
<input type="text" id="metaKey" class="input pill">
</div>
<div class="field full">
<label class="field-label">Commento</label>
<textarea id="metaComment" class="input textarea" rows="3"></textarea>
</div>
<div class="field full">
<label class="field-label">URL sorgente (WOAS)</label>
<input type="text" id="metaSourceUrl" class="input pill" placeholder="https://...">
<div class="cover-hint">Link alla pagina d'origine del brano (es. Suno, YouTube, SoundCloud). Sopravvive alle copie del file.</div>
</div>
<div class="field full">
<label class="field-label">Testo / Prompt (USLT)</label>
<textarea id="metaLyrics" class="input textarea" rows="8" placeholder="Lyrics del brano o prompt AI utilizzato..."></textarea>
<div class="cover-hint">Testo o prompt strutturato. Suno lo compila automaticamente al momento della generazione.</div>
</div>
<details class="field full raw-frames-details">
<summary>Metadati avanzati (tutti i frame ID3 grezzi)</summary>
<table class="raw-frames-table" id="metaRawFrames">
<thead>
<tr>
<th style="width:32px;"><input type="checkbox" id="metaRawSelectAll" title="Seleziona tutti"></th>
<th>Frame</th>
<th>Tipo</th>
<th>Contenuto</th>
<th style="text-align:right;">Bytes</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="row" style="margin-top:10px;justify-content:space-between;align-items:center;">
<span id="metaRawSelectedCount" style="font-size:12px;color:var(--text-3);">0 selezionati</span>
<button id="metaRawRemoveBtn" class="btn btn-danger pill btn-sm" disabled>
🗑 Rimuovi selezionati
</button>
</div>
<div class="cover-hint" style="margin-top:8px;">
I frame standard (TIT2 titolo, TPE1 artista, TALB album, APIC cover…) si gestiscono con i campi sopra.
Qui rimuovi frame custom come TXXX, GEOB (C2PA), USLT (lyrics) o COMM extra.
</div>
</details>
<div class="field full" id="metaWhereFromField" hidden>
<label class="field-label">
Origine (macOS · kMDItemWhereFroms)
</label>
<textarea id="metaWhereFrom" class="input textarea" rows="2" placeholder="https://... (uno per riga)"></textarea>
<div class="cover-hint">Extended attribute macOS — una URL per riga. Vuoto = rimuove l'attributo.</div>
</div>
</div>
</div>
<h2 class="section-label">Copertina</h2>
<div class="card cover-card">
<div class="cover-preview" id="metaCoverPreview">
<span class="cover-empty">Nessuna copertina</span>
</div>
<div class="cover-actions">
<button class="btn btn-ghost pill" id="metaCoverBtn">📂 Cambia copertina</button>
<button class="btn btn-ghost pill" id="metaCoverRemoveBtn">✕ Rimuovi</button>
<div class="cover-hint" id="metaCoverHint"></div>
</div>
</div>
<div class="card actions">
<button class="btn btn-amber pill btn-lg" id="metaSaveBtn">💾 Salva tag</button>
<button class="btn btn-ghost pill btn-lg" id="metaReloadBtn">↺ Ricarica</button>
<div class="counter" id="metaStatus"></div>
</div>
</div>
</section>
<!-- ===== VIEW: SETTINGS ===== -->
<section class="view" id="view-settings">
<header class="hero hero-purple">
<div class="hero-content">
<div class="hero-eyebrow purple">IMPOSTAZIONI</div>
<h1 class="hero-title">Configurazione</h1>
<p class="hero-subtitle">Credenziali Spotify, qualità audio, percorsi e aggiornamenti.</p>
</div>
<div class="hero-deco">⚙</div>
</header>
<h2 class="section-label">Spotify API</h2>
<div class="card card-accent-purple">
<div class="row gap">
<button class="btn btn-purple pill" id="showGuideBtn">Come ottenere le credenziali?</button>
<button class="btn btn-ghost pill" id="openDashboardBtn">Apri Spotify Dashboard</button>
</div>
<div class="field">
<label class="field-label">Client ID</label>
<input type="text" id="clientIdInput" class="input pill" placeholder="Inserisci Client ID" />
</div>
<div class="field">
<label class="field-label">Client Secret</label>
<div class="row">
<input type="password" id="clientSecretInput" class="input pill" placeholder="Inserisci Client Secret" />
<button class="btn btn-ghost pill btn-sm" id="toggleSecretBtn">Mostra</button>
</div>
</div>
</div>
<h2 class="section-label">Download</h2>
<div class="card">
<div class="field">
<label class="field-label">Bitrate</label>
<select id="bitrateSelect" class="input pill input-sm">
<option value="128K">128 kbps</option>
<option value="192K">192 kbps</option>
<option value="256K">256 kbps</option>
<option value="320K" selected>320 kbps</option>
</select>
</div>
<div class="field">
<label class="field-label">Soglia HQ (kbps)</label>
<input type="number" id="hqThresholdInput" class="input pill input-sm" placeholder="310" />
</div>
</div>
<h2 class="section-label">Percorsi</h2>
<div class="card">
<div class="field">
<label class="field-label">Percorso cookies.txt</label>
<div class="row">
<input type="text" id="cookiesInput" class="input pill" placeholder="cookies.txt" />
<button class="btn btn-ghost pill btn-sm" id="browseCookiesBtn">Sfoglia</button>
</div>
</div>
<div class="field">
<label class="field-label">Cookies da browser (bypass 403 YouTube)</label>
<div class="row">
<select id="cookiesBrowserSelect" class="input pill" style="max-width:220px;">
<option value="">Nessuno</option>
<option value="chrome">Chrome</option>
<option value="safari">Safari</option>
<option value="firefox">Firefox</option>
<option value="edge">Edge</option>
<option value="brave">Brave</option>
</select>
</div>
<div class="hint">
<span class="hint-ico">ⓘ</span>
<div>Alcuni video YouTube (musica protetta, region-lock, età) danno HTTP 403 senza cookies autenticati. Selezionando un browser, l'app legge i cookies dal tuo profilo — richiede che tu sia loggato su YouTube in quel browser. Se hai anche cookies.txt sopra, ha priorità il file.</div>
</div>
</div>
<div class="field">
<label class="field-label">Cartella output predefinita</label>
<div class="row">
<input type="text" id="outputInput" class="input pill" placeholder="MUSICA/" />
<button class="btn btn-ghost pill btn-sm" id="browseOutputBtn">Sfoglia</button>
</div>
</div>
</div>
<h2 class="section-label">Log</h2>
<div class="card">
<div class="field">
<label class="field-label">Cartella dei log</label>
<div class="row">
<div class="path-display" id="logPathDisplay" style="flex:1;">—</div>
<button class="btn btn-ghost pill btn-sm" id="openLogFolderBtn">📁 Apri cartella</button>
</div>
<div class="hint">
<span class="hint-ico">ⓘ</span>
<div>
L'app scrive un log giornaliero (<code>app.log</code>) con rotazione automatica (7 giorni).
Utile per debug: incluide stdout, stderr, log delle tab e traceback.
</div>
</div>
</div>
</div>
<h2 class="section-label">Aspetto</h2>
<div class="card">
<div class="field">
<label class="field-label">Tema</label>
<select id="themeSelect" class="input pill input-sm">
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="system">System</option>
</select>
</div>
</div>
<h2 class="section-label">Licenza</h2>
<div class="card">
<div class="license-box" id="licenseStatusBox"></div>
<div class="license-actions">
<button class="btn btn-ghost pill" id="revalidateLicenseBtn">Verifica ora</button>
<button class="btn btn-danger pill" id="deactivateLicenseBtn">Disattiva su questo device</button>
</div>
<div class="hint">
<span class="hint-ico">ⓘ</span>
<div>
Disattivando liberi uno slot di attivazione, utile se sposti l'app su un altro Mac/PC.
Puoi riattivare in qualsiasi momento con la stessa email e chiave.
</div>
</div>
</div>
<h2 class="section-label">Aggiornamenti</h2>
<div class="card">
<div class="row spread">
<div class="version-line">
Versione corrente: <span class="strong" id="currentVersionLabel">v1.0</span>
</div>
<button class="btn btn-purple pill" id="checkUpdateBtn">Controlla aggiornamenti</button>
</div>
<div class="update-status" id="updateStatus"></div>
<button class="btn btn-success pill" id="downloadUpdateBtn" hidden>Scarica aggiornamento</button>
<div class="update-notes" id="updateNotes"></div>
</div>
<div class="save-row">
<button class="btn btn-primary pill btn-lg" id="saveBtn">Salva Impostazioni</button>
<div class="save-status" id="saveStatus"></div>
</div>
</section>
<!-- ===== VIEW: GUIDA ===== -->
<section class="view" id="view-guide">
<header class="hero hero-indigo">
<div class="hero-content">
<div class="hero-eyebrow indigo">GUIDA</div>
<h1 class="hero-title">Manuale d'uso</h1>
<p class="hero-subtitle">Come usare MusicTools al meglio · setup · troubleshooting</p>
</div>
<div class="hero-deco">📖</div>
</header>
<div class="guide-layout">
<!-- TOC sticky -->
<nav class="guide-toc">
<div class="micro-label" style="margin-bottom: 8px;">Sommario</div>
<a href="#g-intro" class="toc-link">Introduzione</a>
<a href="#g-setup" class="toc-link">Setup iniziale</a>
<a href="#g-download" class="toc-link">Tab Scarica</a>
<a href="#g-video" class="toc-link">Tab Video</a>
<a href="#g-upgrade" class="toc-link">Tab Upgrade</a>
<a href="#g-meta" class="toc-link">Tab Metadati</a>
<a href="#g-record" class="toc-link">Tab Registra</a>
<a href="#g-settings" class="toc-link">Impostazioni</a>
<a href="#g-update" class="toc-link">Aggiornamenti</a>
<a href="#g-troubleshoot" class="toc-link">Troubleshooting</a>
</nav>
<!-- Content -->
<article class="guide-content">
<section id="g-intro" class="g-section">
<h2>Introduzione</h2>
<p><strong>MusicTools</strong> è una app desktop tutto-in-uno per chi lavora con la musica: scarica brani e video da decine di piattaforme, migliora la qualità dei file esistenti, modifica i tag/metadati e registra l'audio da qualsiasi ingresso del computer.</p>
<p>L'interfaccia è organizzata in 6 sezioni accessibili dalla barra laterale: <span class="kbd">⬇ Scarica</span>, <span class="kbd">🎬 Video</span>, <span class="kbd">⚡ Upgrade</span>, <span class="kbd">🏷 Metadati</span>, <span class="kbd">● Registra</span>, <span class="kbd">⚙ Impostazioni</span>.</p>
</section>
<section id="g-setup" class="g-section">
<h2>Setup iniziale</h2>
<p>Prima di usare l'app, conviene fare 3 cose nelle <strong>Impostazioni</strong>:</p>
<ol>
<li><strong>Cartella output predefinita</strong> — dove vengono salvati tutti i download.</li>
<li><strong>Credenziali Spotify</strong> (solo se vuoi scaricare da link Spotify) — vedi il bottone "Come ottenere le credenziali?" nella tab Impostazioni.</li>
<li><strong>cookies.txt</strong> (opzionale) — necessario solo per scaricare contenuti privati Instagram/Facebook o aggirare rate-limit YouTube. Esportalo dal browser con l'estensione "Get cookies.txt LOCALLY".</li>
</ol>
<p>Tutti i campi vengono salvati automaticamente quando premi <strong>Salva Impostazioni</strong>.</p>
</section>
<section id="g-download" class="g-section">
<h2>Tab ⬇ Scarica</h2>
<p>Scarica musica in MP3 al bitrate impostato in Impostazioni (default 320 kbps).</p>
<h3>1 · Da Spotify</h3>
<p>Incolla un link a una <strong>playlist, album o brano</strong> (es. <code>open.spotify.com/playlist/...</code>). L'app risolve i metadati via API Spotify e scarica i brani da YouTube. <strong>Richiede</strong> le credenziali API impostate.</p>
<h3>2 · Da YouTube, SoundCloud e altri</h3>
<p>Incolla qualsiasi URL supportato da yt-dlp (singolo video o playlist). Funziona anche con Bandcamp, Mixcloud, ecc.</p>
<h3>3 · Lista da file .txt</h3>
<p>Clicca <strong>📂 Carica lista</strong>. Il file può contenere:</p>
<ul>
<li><strong>Una URL per riga</strong> — l'app le scarica una dopo l'altra.</li>
<li><strong>Tracklist con titoli</strong> (es. Beatport Top 100): formato <code>1. Artista – Titolo (variant) (durata)</code>. L'app riconosce automaticamente il formato e cerca ogni brano su YouTube.</li>
</ul>
<p>Nel caso tracklist, viene creata automaticamente una <strong>sottocartella</strong> col nome del file (es. <code>Beatport Top 100.txt</code> → <code>Beatport Top 100/</code>).</p>
<h3>4 · Skip duplicati</h3>
<p>Se riesegui la stessa playlist, i brani già scaricati vengono saltati in automatico. Il tracking funziona in due modi:</p>
<ul>
<li>File <code>.downloaded_tracks</code> dentro la cartella output.</li>
<li>Scan dei file presenti, con match dei nomi token-per-token (ignora parole come "Official Video", "Lyrics", "HD"...).</li>
</ul>
</section>
<section id="g-video" class="g-section">
<h2>Tab 🎬 Video</h2>
<p>Scarica video da <strong>YouTube, TikTok, Instagram, Facebook</strong> e oltre 1000 altri siti supportati da yt-dlp.</p>
<h3>Modalità</h3>
<ul>
<li><strong>🎬 Video (MP4)</strong> — output video+audio in MP4, qualità a scelta (best / 1080p / 720p / 480p).</li>
<li><strong>🎵 Solo audio (MP3)</strong> — estrae solo l'audio in MP3 al bitrate impostato in Impostazioni, con thumbnail embedded come copertina.</li>
</ul>
<h3>Contenuti privati</h3>
<p>Per scaricare reel Instagram privati, post Facebook privati o aggirare rate-limit YouTube serve un file <strong><code>cookies.txt</code></strong> valido (formato Netscape). Esportalo dal browser e impostalo in <strong>Impostazioni → Percorsi</strong>.</p>
<div class="g-box info">
<strong>Esempio:</strong> per scaricare la <em>Storia</em> di un utente Instagram seguito, fai login al browser, esporta i cookies, riavvia l'app.
</div>
</section>
<section id="g-upgrade" class="g-section">
<h2>Tab ⚡ Upgrade qualità</h2>
<p>Scansiona una cartella e <strong>riscarica i file sotto la soglia kbps</strong> impostata, sostituendoli con la versione a qualità maggiore.</p>
<ol>
<li>Seleziona la cartella con i file audio.</li>
<li>Imposta la <strong>Soglia HQ (kbps)</strong> — i file con bitrate inferiore verranno riprocessati. Default 310 kbps.</li>
<li>(Opzionale) Spunta <strong>Includi sottocartelle</strong> per la scansione ricorsiva.</li>
<li>Premi <strong>Avvia Upgrade</strong>.</li>
</ol>
<p>Per ogni file l'app cerca la versione migliore su YouTube e la sostituisce mantenendo nome originale. I file già processati vengono saltati grazie al tracking <code>.upgraded_tracks</code>.</p>
</section>
<section id="g-meta" class="g-section">
<h2>Tab 🏷 Metadati</h2>
<p>Editor completo dei tag audio. Apri un file con <strong>📂 Apri file audio</strong>.</p>
<h3>Formati supportati</h3>
<ul>
<li><strong>MP3</strong> — ID3v2.3</li>
<li><strong>M4A / AAC / MP4</strong> — Apple atoms</li>
<li><strong>FLAC</strong> — Vorbis comments</li>
<li><strong>WAV</strong> — ID3 embedded + fallback al chunk RIFF LIST/INFO (per file taggati da ffmpeg/Audacity)</li>
</ul>
<h3>Campi editabili</h3>
<p>Titolo, artista, album, album artist, anno, traccia (formato <code>1/12</code>), genere, BPM, key musicale, commento, copertina (JPG/PNG), e su macOS anche <strong>kMDItemWhereFroms</strong> (l'extended attribute che traccia l'origine del file scaricato).</p>
<h3>Copertina</h3>
<p>Anteprima 180×180 della copertina corrente. <strong>Cambia copertina</strong> ne carica una nuova; <strong>Rimuovi</strong> la marca per la rimozione al prossimo salvataggio.</p>
<h3>Annulla modifiche</h3>
<p>Premi <strong>↺ Ricarica</strong> per rileggere i tag dal file e scartare le modifiche non salvate.</p>
</section>
<section id="g-record" class="g-section">
<h2>Tab ● Registra</h2>
<p>Cattura audio in MP3 da qualsiasi ingresso del computer: <strong>mixer, scheda audio esterna, microfono, driver loopback</strong> (BlackHole/Soundflower/VB-Cable). Bitrate selezionabile 128–320 kbps.</p>
<h3>Setup standard (mixer/microfono)</h3>
<ol>
<li>Dropdown <strong>Dispositivo</strong>: seleziona la sorgente.</li>
<li>Imposta una cartella output e (opzionale) un nome file.</li>
<li>Premi <strong>● REC</strong> → registrazione, timer in tempo reale.</li>
<li><strong>◼ Stop</strong> → file MP3 finalizzato e linkato sotto.</li>
</ol>
<h3>Registrare l'audio di sistema su macOS (Spotify, YouTube...)</h3>
<p>macOS non espone nativamente l'uscita audio. Serve un driver loopback gratuito, il più diffuso è <strong>BlackHole</strong>.</p>
<h4>Installazione</h4>
<p>Apri Terminale e lancia:</p>
<pre><code>brew install blackhole-2ch</code></pre>
<p>Se subito dopo l'install BlackHole non compare nel dropdown, riavvia il servizio audio:</p>
<pre><code>sudo killall coreaudiod</code></pre>
<p>(rilancia da solo in pochi secondi, nessun rischio)</p>
<h4>Permesso microfono</h4>
<p>macOS richiede il permesso "Microfono" per leggere da BlackHole via AVFoundation. Vai su <strong>Preferenze di Sistema → Privacy e sicurezza → Microfono</strong> e abilita Terminal/MusicTools. Se la voce non c'è, comparirà un popup la prima volta che premi REC.</p>
<h4>Setup A — minimo (non senti l'audio durante)</h4>
<ol>
<li>Click icona altoparlante nella barra menu → <strong>Output → BlackHole 2ch</strong></li>
<li>Fai partire Spotify (non sentirai nulla — l'audio va a BlackHole)</li>
<li>Tab Registra → dispositivo <strong>🔄 BlackHole 2ch (loopback)</strong> → REC</li>
</ol>
<h4>Setup B — completo (registri E senti l'audio)</h4>
<ol>
<li>Apri <strong>Configurazione MIDI Audio</strong> (<code>Applicazioni → Utility</code>)</li>
<li>In basso a sinistra: <strong>+ → Crea dispositivo aggregato a uscita multipla</strong></li>
<li>Spunta sia <strong>BlackHole 2ch</strong> sia i tuoi <strong>Altoparlanti integrati</strong></li>
<li>Imposta <strong>BlackHole 2ch come Master Device</strong> (menu in alto)</li>
<li>Abilita <strong>Drift Correction</strong> sulla riga degli altoparlanti</li>
<li>(Opzionale) Rinomina in <code>Speakers + BlackHole</code></li>
<li>Preferenze di Sistema → Audio → Uscita → seleziona <code>Speakers + BlackHole</code></li>
<li>Nella tab Registra: scegli <strong>🔄 BlackHole 2ch (loopback)</strong>, non il dispositivo aggregato</li>
</ol>
<div class="g-box success">
Adesso ogni audio del sistema viene riprodotto sugli speaker (lo senti) e contemporaneamente catturato da BlackHole (viene registrato).
</div>
</section>
<section id="g-settings" class="g-section">
<h2>Impostazioni</h2>
<h3>Spotify API</h3>
<p>Le credenziali API Spotify sono <strong>gratuite</strong> e non richiedono Premium. Vai su <a href="#" data-ext="https://developer.spotify.com/dashboard">Spotify Developer Dashboard</a>, "Create app", compila i campi (Redirect URI <code>http://localhost:8888/callback</code>, seleziona "Web API"), e copia Client ID + Client Secret nei campi corrispondenti.</p>
<h3>Download</h3>
<ul>
<li><strong>Bitrate</strong> — qualità target per gli MP3 (128/192/256/320 kbps).</li>
<li><strong>Soglia HQ</strong> — soglia kbps per la tab Upgrade. I file sotto questa soglia vengono riscaricati.</li>
</ul>
<h3>Percorsi</h3>
<ul>
<li><strong>cookies.txt</strong> — per contenuti privati/protetti.</li>
<li><strong>Cartella output predefinita</strong> — viene proposta in tutte le tab.</li>
</ul>
</section>
<section id="g-update" class="g-section">
<h2>Aggiornamenti</h2>
<p>In <strong>Impostazioni → Aggiornamenti</strong> trovi il bottone <strong>Controlla aggiornamenti</strong>. L'app interroga la pagina release di GitHub e ti mostra se c'è una versione nuova.</p>
<p>Se è disponibile un aggiornamento, comparirà un secondo bottone <strong>Scarica aggiornamento</strong> che apre direttamente il browser sull'asset corretto (macOS o Windows) della release più recente.</p>
</section>
<section id="g-troubleshoot" class="g-section">
<h2>Troubleshooting</h2>
<table class="g-table">
<thead><tr><th>Sintomo</th><th>Causa probabile</th><th>Fix</th></tr></thead>
<tbody>
<tr>
<td>BlackHole non compare nel dropdown della tab Registra</td>
<td>CoreAudio non l'ha ancora caricato dopo l'install</td>
<td><code>sudo killall coreaudiod</code> + premi <strong>↻ Aggiorna</strong></td>
</tr>
<tr>
<td>"Permesso microfono mancante" o errore ffmpeg -6</td>
<td>macOS nega l'accesso al device</td>
<td>Privacy e sicurezza → Microfono → abilita Terminal/MusicTools</td>
</tr>
<tr>
<td>File MP3 della registrazione è muto</td>
<td>L'output di sistema non passa da BlackHole</td>
<td>Imposta output su BlackHole o Multi-Output Device (Setup A/B)</td>
</tr>
<tr>
<td>Spotify dice "Autenticazione fallita"</td>
<td>Client ID/Secret mancanti o errati</td>
<td>Impostazioni → Spotify API → ripeti la procedura</td>
</tr>
<tr>
<td>Instagram/Facebook non scarica</td>
<td>Contenuto privato senza cookies validi</td>
<td>Esporta cookies.txt dal browser dopo login</td>
</tr>
<tr>
<td>Brano scaricato è sbagliato (versione cover invece dell'originale)</td>
<td>Ricerca YouTube ha preso il primo risultato</td>
<td>Modifica la query nel file di tracklist (più dettagli artista/titolo)</td>
</tr>
<tr>
<td>Tag non vengono letti su un WAV</td>
<td>File usa solo chunk INFO non ID3 (raro)</td>
<td>L'app ora gestisce entrambi — controlla di avere v1.2.1+</td>
</tr>
<tr>
<td>Stop button non risponde durante il download</td>
<td>yt-dlp sta finalizzando il file</td>
<td>Attendi qualche secondo, il processo si chiude dopo l'I/O</td>
</tr>
</tbody>
</table>
<h3>Reset configurazione</h3>
<p>Se vuoi azzerare tutte le impostazioni, cancella il file <code>config.json</code>. Posizione:</p>
<ul>
<li><strong>macOS</strong>: <code>~/Library/Application Support/MusicTools/config.json</code></li>
<li><strong>Windows</strong>: <code>%APPDATA%\MusicTools\config.json</code></li>
</ul>
<p>Verrà ricreato con valori di default al prossimo avvio.</p>
</section>
<section class="g-section">
<h2>Risorse</h2>
<ul>
<li><a href="#" data-ext="https://github.com/luzadev/musicdownload">Codice sorgente su GitHub</a></li>
<li><a href="#" data-ext="https://github.com/luzadev/musicdownload/releases">Tutte le release</a></li>
<li><a href="#" data-ext="https://github.com/yt-dlp/yt-dlp/wiki/FAQ">FAQ di yt-dlp</a></li>
<li><a href="#" data-ext="https://existential.audio/blackhole/">BlackHole — driver loopback macOS</a></li>
</ul>
<p style="color: var(--text-3); font-size: 12px; margin-top: 20px;">MusicTools — by <strong style="color: var(--green);">LuZa</strong></p>
</section>
</article>
</div>
</section>
<!-- ===== VIEW: BEATPORT ===== -->
<section class="view" id="view-beatport">
<header class="hero hero-indigo">
<div class="hero-content">
<div class="hero-eyebrow indigo">BEATPORT CHARTS</div>
<h1 class="hero-title">Top 100 per genere</h1>
<p class="hero-subtitle">Sfoglia la Top 100 di Beatport, seleziona i brani e scaricali via YouTube</p>
</div>
<div class="hero-deco">🎧</div>
</header>
<h2 class="section-label">Genere</h2>
<div class="card">
<div class="beatport-header">
<select id="beatport-genre" class="input pill"></select>
<button class="btn btn-primary pill" id="beatport-load-btn">
<span class="ico">↓</span> Carica Top 100
</button>
<span class="hint-inline">Shift+click per bypassare la cache</span>
</div>
<div id="beatport-status" class="beatport-status"></div>
<div id="beatport-output-info" class="beatport-output-info"></div>
</div>
<div class="card beatport-table-card">
<div class="beatport-table-wrap">
<table id="beatport-table" class="beatport-table" hidden>
<thead>
<tr>
<th class="col-check"><input type="checkbox" id="beatport-select-all" /></th>
<th class="col-cover"></th>
<th class="col-pos sortable" data-sort="position">#</th>
<th class="sortable" data-sort="artists">Artista</th>
<th class="sortable" data-sort="title">Titolo (Mix)</th>
<th class="col-dur sortable" data-sort="duration_sec">Durata</th>
<th class="col-date sortable" data-sort="release_date">Data</th>
<th class="col-state">Stato</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="beatport-toolbar" id="beatport-toolbar" hidden>
<span class="counter" id="beatport-selected-count">0/0 selezionati</span>
<button class="btn btn-primary pill" id="beatport-download-btn" disabled>
<span class="ico">▶</span> Scarica selezionati
</button>
<button class="btn btn-danger pill" id="beatport-stop-btn" hidden>
<span class="ico">◼</span> Interrompi
</button>
</div>
</div>
<h2 class="section-label">Log</h2>
<div class="card log-card">
<div class="log" id="beatport-log"></div>
</div>
</section>
<!-- ===== VIEW: TRAXSOURCE ===== -->
<section class="view" id="view-traxsource">
<header class="hero hero-purple">
<div class="hero-content">
<div class="hero-eyebrow purple">TRAXSOURCE CHARTS</div>
<h1 class="hero-title">Top 100 Traxsource per genere</h1>
<p class="hero-subtitle">La classifica mensile ufficiale. Scarica in un click con tag ID3 completi (label, cover art, genere).</p>
</div>
<div class="hero-deco">▲</div>
</header>
<h2 class="section-label">Genere</h2>
<div class="card">
<div class="beatport-header">
<select id="traxsource-genre" class="input pill"></select>
<button class="btn btn-primary pill" id="traxsource-load-btn">
<span class="ico">↓</span> Carica Top 100
</button>
<span class="hint-inline">Shift+click per bypassare la cache</span>
</div>
<div id="traxsource-status" class="beatport-status"></div>
<div id="traxsource-output-info" class="beatport-output-info"></div>
</div>
<div class="card beatport-table-card">
<div class="beatport-table-wrap">
<table id="traxsource-table" class="beatport-table" hidden>
<thead>
<tr>
<th class="col-check"><input type="checkbox" id="traxsource-select-all" /></th>
<th class="col-cover"></th>
<th class="col-pos sortable" data-sort="position">#</th>
<th class="sortable" data-sort="artists">Artista</th>
<th class="sortable" data-sort="title">Titolo (Mix)</th>
<th class="sortable" data-sort="label">Label</th>
<th class="col-state">Stato</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="beatport-toolbar" id="traxsource-toolbar" hidden>
<span class="counter" id="traxsource-selected-count">0/0 selezionati</span>
<button class="btn btn-primary pill" id="traxsource-download-btn" disabled>
<span class="ico">▶</span> Scarica selezionati
</button>
<button class="btn btn-danger pill" id="traxsource-stop-btn" hidden>
<span class="ico">◼</span> Interrompi
</button>
</div>
</div>
<h2 class="section-label">Log</h2>
<div class="card log-card">
<div class="log" id="traxsource-log"></div>
</div>
</section>
<!-- ===== VIEW: SPOTIFY SEARCH ===== -->
<section class="view" id="view-spotify">
<header class="hero hero-indigo">
<div class="hero-content">
<div class="hero-eyebrow indigo">RICERCA SPOTIFY</div>
<h1 class="hero-title">Cerca brani su Spotify</h1>
<p class="hero-subtitle">Digita un artista, un titolo, o entrambi. Attiva "Solo artista" per scaricare tutta la discografia.</p>
</div>
<div class="hero-deco">🟢</div>
</header>
<h2 class="section-label">Ricerca</h2>
<div class="card">
<div class="beatport-header">
<input type="text" id="spotify-query" class="input pill" placeholder="Es: Solomun, Hot Sauce, Kapuchon Hot Sauce…" style="flex:1; min-width:260px;" />
<label style="display:flex; gap:6px; align-items:center; color:var(--text-2); font-size:13px;">
<input type="checkbox" id="spotify-artist-mode" />
<span>Solo artista (discografia)</span>
</label>
<button class="btn btn-primary pill" id="spotify-search-btn">
<span class="ico">🔎</span> Cerca
</button>
</div>
<div id="spotify-status" class="beatport-status"></div>
<div id="spotify-output-info" class="beatport-output-info"></div>
</div>
<div class="card beatport-table-card" id="spotify-table-wrap" hidden>
<div class="beatport-table-wrap">
<table class="beatport-table" id="spotify-table">
<thead>
<tr>
<th class="col-check"><input type="checkbox" id="spotify-select-all" checked /></th>
<th class="col-cover"></th>
<th class="col-pos sortable" data-sort="position">#</th>
<th class="sortable" data-sort="artists">Artista</th>
<th class="sortable" data-sort="name">Titolo</th>
<th class="sortable" data-sort="album">Album</th>
<th class="col-dur sortable" data-sort="duration_sec">Durata</th>
<th class="col-date sortable" data-sort="release_date">Data</th>
<th class="col-state"></th>
</tr>
</thead>
<tbody id="spotify-tbody"></tbody>
</table>
</div>
<div class="beatport-toolbar" id="spotify-toolbar">
<span class="counter" id="spotify-selected-count">0/0 selezionati</span>
<button class="btn btn-primary pill" id="spotify-download-btn" disabled>
<span class="ico">▶</span> Scarica selezionati
</button>
<button class="btn btn-danger pill" id="spotify-stop-btn" hidden>
<span class="ico">◼</span> Interrompi
</button>
</div>
</div>
<h2 class="section-label">Log</h2>
<div class="card log-card">
<div class="log" id="spotify-log"></div>
</div>
</section>
<!-- ===== VIEW: YOUTUBE SEARCH ===== -->
<section class="view" id="view-youtube">
<header class="hero hero-indigo">
<div class="hero-content">
<div class="hero-eyebrow indigo">RICERCA YOUTUBE</div>
<h1 class="hero-title">Cerca video su YouTube</h1>
<p class="hero-subtitle">Perfetto per mix DJ, unreleased, bootleg e brani non presenti su Spotify.</p>
</div>
<div class="hero-deco">▶</div>
</header>
<h2 class="section-label">Ricerca</h2>
<div class="card">
<div class="beatport-header">
<input type="text" id="youtube-query" class="input pill" placeholder="Es: Solomun Cocoricò 2024, Kapuchon Hot Sauce remix…" style="flex:1; min-width:260px;" />
<button class="btn btn-primary pill" id="youtube-search-btn">
<span class="ico">🔎</span> Cerca
</button>
</div>
<div id="youtube-status" class="beatport-status"></div>
<div id="youtube-output-info" class="beatport-output-info"></div>
</div>
<div class="card beatport-table-card" id="youtube-table-wrap" hidden>
<div class="beatport-table-wrap">
<table class="beatport-table" id="youtube-table">
<thead>
<tr>
<th class="col-check"><input type="checkbox" id="youtube-select-all" checked /></th>
<th class="col-cover"></th>
<th class="col-pos sortable" data-sort="position">#</th>
<th class="sortable" data-sort="title">Titolo video</th>
<th class="sortable" data-sort="channel">Canale</th>
<th class="col-dur sortable" data-sort="duration_sec">Durata</th>
<th class="col-date sortable" data-sort="release_date">Data</th>
<th class="col-state"></th>
</tr>
</thead>
<tbody id="youtube-tbody"></tbody>
</table>
</div>
<div class="beatport-toolbar" id="youtube-toolbar">
<span class="counter" id="youtube-selected-count">0/0 selezionati</span>
<button class="btn btn-primary pill" id="youtube-download-btn" disabled>
<span class="ico">▶</span> Scarica selezionati
</button>
<button class="btn btn-danger pill" id="youtube-stop-btn" hidden>
<span class="ico">◼</span> Interrompi
</button>
</div>
</div>
<h2 class="section-label">Log</h2>
<div class="card log-card">
<div class="log" id="youtube-log"></div>
</div>
</section>
<!-- ===== VIEW: CONVERTI (WAV -> MP3) ===== -->
<section class="view" id="view-convert">
<header class="hero hero-purple">
<div class="hero-content">
<div class="hero-eyebrow purple">WAV → MP3</div>
<h1 class="hero-title">Converti file WAV in MP3</h1>
<p class="hero-subtitle">Bitrate configurabile, CBR o VBR, output accanto al file o in cartella custom.</p>
</div>
<div class="hero-deco">🔄</div>
</header>
<h2 class="section-label">Sorgente</h2>
<div class="card">
<div class="beatport-header">
<button id="convert-pick-files" class="btn btn-primary pill">📄 Scegli file .wav</button>
<button id="convert-pick-folder" class="btn btn-ghost pill">📁 Scegli cartella</button>
<button id="convert-clear" class="btn btn-ghost pill">Svuota</button>
</div>
<div class="beatport-header" style="margin-top:12px;gap:16px;">
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
Bitrate:
<select id="convert-bitrate" class="input pill input-sm">
<option value="128">128 kbps</option>
<option value="192">192 kbps</option>
<option value="256">256 kbps</option>
<option value="320" selected>320 kbps</option>
</select>
</label>
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
<input type="checkbox" id="convert-vbr" />
<span>VBR (qualità variabile)</span>
</label>
</div>
<div class="beatport-header" style="margin-top:12px;gap:12px;">
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
<input type="radio" name="convert-out" id="convert-out-same" checked />
<span>Accanto al file originale</span>
</label>
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
<input type="radio" name="convert-out" id="convert-out-custom" />
<span>Cartella custom:</span>
</label>
<button id="convert-out-browse" class="btn btn-ghost pill btn-sm" disabled>Sfoglia…</button>
<span id="convert-out-path" class="beatport-output-info" style="margin:0;"></span>
</div>
<div id="convert-status" class="beatport-status"></div>
</div>
<div class="card beatport-table-card" id="convert-table-wrap" hidden>
<div class="beatport-table-wrap">
<table class="beatport-table" id="convert-table">
<thead>
<tr>
<th class="col-check"><input type="checkbox" id="convert-select-all" checked /></th>
<th class="col-pos">#</th>
<th>File</th>
<th class="col-state">Stato</th>
</tr>
</thead>
<tbody id="convert-tbody"></tbody>
</table>
</div>
<div class="beatport-toolbar" id="convert-toolbar">
<span class="counter" id="convert-selected-count">0/0 selezionati</span>
<button id="convert-run-btn" class="btn btn-primary pill" disabled>
<span class="ico">▶</span> Converti selezionati
</button>
<button id="convert-stop-btn" class="btn btn-danger pill" hidden>
<span class="ico">◼</span> Interrompi
</button>
</div>
</div>
<h2 class="section-label">Log</h2>
<div class="card log-card">
<div class="log" id="convert-log"></div>
</div>
</section>
<!-- ===== VIEW: DEDUP (audio duplicati via Chromaprint) ===== -->
<section class="view" id="view-dedup">
<header class="hero hero-red">
<div class="hero-content">
<div class="hero-eyebrow red">DEDUP AUDIO</div>
<h1 class="hero-title">Trova e rimuovi duplicati audio</h1>
<p class="hero-subtitle">Audio fingerprinting via Chromaprint: riconosce brani identici indipendentemente dal bitrate, dal formato o dai tag.</p>
</div>
<div class="hero-deco">🗑</div>
</header>
<h2 class="section-label">Cartella</h2>
<div class="card">
<div class="beatport-header">
<button id="dedup-pick-folder" class="btn btn-primary pill">📁 Scegli cartella</button>
<button id="dedup-start-btn" class="btn btn-primary pill" disabled>
<span class="ico">▶</span> Scansiona
</button>
<button id="dedup-stop-btn" class="btn btn-danger pill" hidden>
<span class="ico">◼</span> Interrompi
</button>
</div>
<div class="beatport-header" style="margin-top:12px;gap:16px;">
<span id="dedup-path-display" class="beatport-output-info" style="margin:0;">Nessuna cartella selezionata</span>
</div>
<div class="beatport-header" style="margin-top:12px;gap:16px;">
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
<input type="checkbox" id="dedup-recursive" checked />
<span>Ricerca ricorsiva (include sottocartelle)</span>
</label>
</div>
<div class="beatport-header" style="margin-top:12px;gap:16px;">
<label style="display:flex;gap:8px;align-items:center;color:var(--text-2);font-size:13px;">
<span>Metodo:</span>
<select id="dedup-method" class="input" style="padding:4px 8px;font-size:13px;">
<option value="fingerprint">Audio fingerprint (preciso, lento)</option>
<option value="filename">Nome file (veloce, meno preciso)</option>
</select>
</label>
</div>
<div id="dedup-status" class="beatport-status"></div>
</div>
<h2 class="section-label">Progresso</h2>
<div class="card">
<div class="progress">
<div class="progress-bar"><div id="dedupProgressFill" class="progress-fill pink"></div></div>
<div class="progress-meta">
<span id="dedupPercent" class="percent pink">0%</span>
<span id="dedupCounter">In attesa</span>
</div>
</div>
</div>
<h2 class="section-label">Risultati</h2>
<div class="card" id="dedup-summary-card" hidden>
<div class="dedup-summary" id="dedup-summary-line"></div>
</div>
<div id="dedup-groups-wrap" class="dedup-groups-scroll"></div>
<div class="dedup-footer-bar" id="dedup-footer-bar" hidden>
<span class="counter" id="dedup-selected-count">0 file da cancellare</span>
<button id="dedup-select-suggested-btn" class="btn btn-ghost pill">
✓ Seleziona tutti i consigliati
</button>
<button id="dedup-trash-btn" class="btn btn-danger pill" disabled>
<span class="ico">🗑</span> Sposta in cestino
</button>
</div>
<h2 class="section-label">Log</h2>
<div class="card log-card">
<div class="log" id="dedup-log"></div>
</div>
</section>
<!-- ===== VIEW: CATALOGA (AcoustID -> <Anno>/<Genere>/) ===== -->
<section class="view" id="view-catalog">
<header class="hero hero-purple">
<div class="hero-content">
<div class="hero-eyebrow purple">CATALOGA</div>
<h1 class="hero-title">Organizza per anno e genere</h1>
<p class="hero-subtitle">Identifica ogni brano via audio fingerprint (Chromaprint + AcoustID) e sposta i file in <code>&lt;Anno&gt;/&lt;Genere&gt;/</code>.</p>
</div>
<div class="hero-deco">📅</div>
</header>
<h2 class="section-label">Sorgente</h2>
<div class="card">
<div class="beatport-header">
<button id="catalog-pick-source" class="btn btn-primary pill">📁 Cartella input</button>
<button id="catalog-start-btn" class="btn btn-primary pill" disabled>
<span class="ico">▶</span> Scansiona
</button>
<button id="catalog-stop-btn" class="btn btn-danger pill" hidden>
<span class="ico">◼</span> Interrompi
</button>
</div>
<div class="beatport-header" style="margin-top:12px;gap:16px;">
<span id="catalog-source-display" class="beatport-output-info" style="margin:0;">Nessuna cartella selezionata</span>
</div>
<div class="beatport-header" style="margin-top:12px;gap:16px;">
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
<input type="checkbox" id="catalog-recursive" checked />
<span>Ricerca ricorsiva (include sottocartelle)</span>
</label>
</div>
</div>
<h2 class="section-label">Destinazione</h2>
<div class="card">
<div class="beatport-header">
<button id="catalog-pick-target" class="btn btn-ghost pill">📁 Cartella destinazione</button>
<button id="catalog-clear-target" class="btn btn-ghost pill btn-sm">Usa la stessa della sorgente</button>
</div>
<div class="beatport-header" style="margin-top:12px;gap:16px;">
<span id="catalog-target-display" class="beatport-output-info" style="margin:0;">Uguale alla sorgente (in-place)</span>
</div>
<div class="beatport-header" style="margin-top:12px;gap:12px;">
<label style="display:flex;gap:8px;align-items:center;color:var(--text-2);font-size:13px;">
<span>Struttura cartelle:</span>
<select id="catalog-layout" class="input" style="padding:4px 8px;font-size:13px;">
<option value="year_genre">&lt;Anno&gt;/&lt;Genere&gt;/</option>
<option value="genre_year">&lt;Genere&gt;/&lt;Anno&gt;/</option>
</select>
</label>
</div>
<div class="hint" style="margin-top:10px;">
<span class="hint-ico">ⓘ</span>
<div>
Se lasci vuota la destinazione i file vengono organizzati <strong>in-place</strong> nella cartella sorgente.
Le sottocartelle vengono create automaticamente in base allo schema scelto.
File senza match finiscono in <code>Unknown Year</code> / <code>Unknown Genre</code>.
</div>
</div>
<div id="catalog-status" class="beatport-status"></div>
</div>
<h2 class="section-label">Progresso</h2>
<div class="card">
<div class="progress">
<div class="progress-bar"><div id="catalogProgressFill" class="progress-fill purple"></div></div>
<div class="progress-meta">
<span id="catalogPercent" class="percent purple">0%</span>
<span id="catalogCounter">In attesa</span>
</div>
</div>
</div>
<h2 class="section-label">Risultati</h2>
<div class="card" id="catalog-summary-card" hidden>
<div class="catalog-summary" id="catalog-summary-line"></div>
</div>
<div id="catalog-table-wrap" class="catalog-scroll" hidden>
<table class="catalog-table">
<thead>
<tr>
<th class="catalog-col-check">
<input type="checkbox" id="catalog-select-all" title="Seleziona/deseleziona tutti"/>
</th>
<th class="catalog-col-file">File</th>
<th class="catalog-col-artist">Artista</th>
<th class="catalog-col-title">Titolo</th>
<th class="catalog-col-year">Anno</th>
<th class="catalog-col-genre">Genere</th>
<th class="catalog-col-status">Stato</th>
</tr>
</thead>
<tbody id="catalog-table-body"></tbody>
</table>
</div>
<div class="catalog-footer-bar" id="catalog-footer-bar" hidden>
<span class="counter" id="catalog-selected-count">0 file selezionati</span>
<button id="catalog-move-btn" class="btn btn-danger pill" disabled>
<span class="ico">📦</span> Sposta selezionati
</button>
</div>
<h2 class="section-label">Log</h2>
<div class="card log-card">
<div class="log" id="catalog-log"></div>
</div>
</section>
<!-- ===== VIEW: FLATTEN ===== -->
<section class="view" id="view-flatten">
<header class="hero hero-blue">
<div class="hero-content">
<div class="hero-eyebrow blue">APPIATTISCI</div>
<h1 class="hero-title">Sposta tutti i brani nella cartella padre</h1>
<p class="hero-subtitle">
Scansiona ricorsivamente ogni sotto-cartella e sposta i file audio nella root.
Utile come "undo" della catalogazione o per unire archivi frammentati.
</p>
</div>
<div class="hero-deco">📤</div>
</header>
<h2 class="section-label">Cartella</h2>
<div class="card">
<div class="beatport-header">
<button id="flatten-pick-folder" class="btn btn-primary pill">📁 Scegli cartella</button>
<button id="flatten-start-btn" class="btn btn-blue pill" disabled>▶ Sposta tutto nella root</button>
<button id="flatten-stop-btn" class="btn btn-danger pill" hidden>■ Interrompi</button>
</div>
<div class="beatport-header" style="margin-top:12px;">
<span id="flatten-path-display" class="beatport-output-info" style="margin:0;">Nessuna cartella selezionata</span>
</div>
<div class="beatport-header" style="margin-top:12px;">
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
<input type="checkbox" id="flatten-remove-empty" checked />
<span>Elimina le sotto-cartelle rimaste vuote dopo lo spostamento</span>
</label>
</div>
<div class="hint" style="margin-top:10px;">
<span class="hint-ico">ⓘ</span>
<div>
Ogni file con estensione audio (mp3, m4a, wav, flac...) nelle sotto-cartelle
verrà spostato nella cartella padre. In caso di nomi duplicati viene aggiunto
suffisso <code>_1</code>, <code>_2</code>... (nessun overwrite).
</div>
</div>
<div id="flatten-status" class="beatport-status"></div>
</div>
<h2 class="section-label">Progresso</h2>
<div class="card">
<div class="progress">
<div class="progress-bar"><div id="flattenProgressFill" class="progress-fill blue"></div></div>
<div class="progress-meta">
<span id="flattenPercent" class="percent blue">0%</span>
<span id="flattenCounter">In attesa</span>
</div>
</div>
</div>
<h2 class="section-label">Log</h2>
<div class="card log-card">
<div class="log" id="flatten-log"></div>
</div>
</section>
<!-- ===== VIEW: CHARTS ===== -->
<section class="view" id="view-charts">
<header class="hero hero-indigo">
<div class="hero-content">
<div class="hero-eyebrow indigo">CHARTS</div>
<h1 class="hero-title">Esplora le classifiche</h1>
<p class="hero-subtitle">Del momento (iTunes/Spotify), dance italiana (M2O) o storiche per genere (Last.fm) — aggiungi in un click alla coda Scarica.</p>
</div>
<div class="hero-deco">📊</div>
</header>
<h2 class="section-label">Sorgente</h2>
<div class="card">
<div class="charts-source-row">
<label class="charts-radio">
<input type="radio" name="charts-source" value="itunes" checked />
<span>🎵 Del momento (iTunes)</span>
</label>
<label class="charts-radio">
<input type="radio" name="charts-source" value="spotify" />
<span>🟢 Spotify Charts</span>
</label>
<label class="charts-radio">
<input type="radio" name="charts-source" value="m2o" />
<span>🔴 M2O Chart (dance italiana)</span>
</label>
<label class="charts-radio">
<input type="radio" name="charts-source" value="lastfm" />
<span>📻 Storiche per genere (Last.fm)</span>
</label>
</div>
<div class="charts-filters" id="charts-filters-itunes">
<div class="field inline">
<label class="field-label">Paese</label>
<select id="charts-itunes-country" class="input pill input-sm">
<option value="it">🇮🇹 Italia</option>
<option value="us">🇺🇸 USA</option>
<option value="gb">🇬🇧 UK</option>
<option value="ww">🌐 Mondo</option>
</select>
</div>
</div>
<div class="charts-filters" id="charts-filters-spotify" hidden>
<div class="field inline">
<label class="field-label">Playlist chart</label>
<select id="charts-spotify-playlist" class="input pill input-sm">
<option value="top50_global">Top 50 Global</option>
<option value="top50_italy">Top 50 Italy</option>
<option value="top50_usa">Top 50 USA</option>
<option value="top50_uk">Top 50 UK</option>
<option value="viral50_global">Viral 50 Global</option>
<option value="viral50_italy">Viral 50 Italy</option>
</select>
</div>
<div class="hint" style="margin-top:8px;">
<span class="hint-ico">ⓘ</span>
<div>
Le playlist Spotify richiedono le tue credenziali API (Impostazioni → Spotify).
Senza credenziali questa sorgente resta vuota.
</div>
</div>
</div>
<div class="charts-filters" id="charts-filters-m2o" hidden>
<div class="hint">
<span class="hint-ico">ⓘ</span>
<div>
Chart dance italiana settimanale di M2O. Nessun filtro: premi "Aggiorna" per caricare la classifica corrente.
</div>
</div>
</div>
<div class="charts-filters" id="charts-filters-lastfm" hidden>
<div class="field inline">
<label class="field-label">Decade</label>
<select id="charts-lastfm-decade" class="input pill input-sm">
<option value="">Nessuna</option>
<option value="70s">Anni 70</option>
<option value="80s">Anni 80</option>
<option value="90s" selected>Anni 90</option>
<option value="2000s">Anni 2000</option>
<option value="2010s">Anni 2010</option>
</select>
</div>
<div class="field inline">
<label class="field-label">Genere</label>
<select id="charts-lastfm-genre" class="input pill input-sm">
<option value="">Nessuno</option>
<option value="pop">Pop</option>
<option value="rock">Rock</option>
<option value="dance" selected>Dance</option>
<option value="house">House</option>
<option value="hip hop">Hip Hop</option>
<option value="latin">Latin</option>
<option value="r&amp;b">R&amp;B</option>
<option value="italo disco">Italo Disco</option>
<option value="reggae">Reggae</option>
<option value="electronic">Electronic</option>
</select>
</div>
<div class="hint" style="margin-top:8px;">
<span class="hint-ico">ⓘ</span>
<div>
Last.fm richiede una API key: nell'app c'è una chiave demo, se non funziona
registrane una gratuita su <code>last.fm/api/account/create</code> e mettila nel campo
<code>lastfm_api_key</code> di <code>config.json</code>.
</div>
</div>
</div>
<div class="beatport-header" style="margin-top:14px;gap:12px;">
<button id="charts-fetch-btn" class="btn btn-primary pill">
<span class="ico">▶</span> Aggiorna classifica
</button>
<label style="display:flex;gap:6px;align-items:center;color:var(--text-2);font-size:13px;">
<input type="checkbox" id="charts-force" />
<span>Ignora cache (force refresh)</span>
</label>
<span id="charts-status" class="beatport-status"></span>
</div>
</div>
<div id="charts-results-card" class="card" hidden>
<div class="charts-actions-row">
<span id="charts-summary-line" class="catalog-summary"></span>
<div class="charts-actions-btns">
<button id="charts-select-all-btn" class="btn btn-ghost pill btn-sm">Seleziona tutti</button>
<button id="charts-deselect-all-btn" class="btn btn-ghost pill btn-sm">Deseleziona tutti</button>
</div>
</div>
<div id="charts-table-wrap" class="charts-scroll">
<table class="charts-table">
<thead>
<tr>
<th class="charts-col-check">
<input type="checkbox" id="charts-select-all" title="Seleziona/deseleziona tutti" />
</th>
<th class="charts-col-rank">#</th>
<th class="charts-col-artist">Artista</th>
<th class="charts-col-title">Titolo</th>
<th class="charts-col-album">Album</th>
<th class="charts-col-pop">Popolarità</th>
</tr>
</thead>
<tbody id="charts-table-body"></tbody>
</table>
</div>
</div>
<div class="charts-footer-bar" id="charts-footer-bar" hidden>
<span class="counter" id="charts-selected-count">0 brani selezionati</span>
<button id="charts-add-queue-btn" class="btn btn-primary pill" disabled>
<span class="ico">➕</span> Aggiungi alla coda Scarica
</button>
</div>
<h2 class="section-label">Log</h2>
<div class="card log-card">
<div class="log" id="charts-log"></div>
</div>
</section>
</main>
</div>
<!-- ============== SCHERMATA ATTIVAZIONE LICENZA ============== -->
<div class="activate-screen" id="activateScreen" hidden>
<div class="activate-card">
<div class="activate-logo">♫</div>
<h1 class="activate-title">Benvenuto in MusicTools</h1>
<p class="activate-sub">
Inserisci la tua licenza per iniziare. Se non ne hai ancora una,
acquistala dal nostro sito ed entra in pochi secondi.
</p>
<div class="activate-form">
<div class="field">
<label class="field-label">Email dell'acquisto</label>
<input type="email" id="actEmail" class="input pill" placeholder="tu@esempio.com" autocomplete="email" />
</div>
<div class="field">
<label class="field-label">Chiave di licenza</label>
<input type="text" id="actKey" class="input pill" placeholder="XXXX-XXXX-XXXX-XXXX" autocomplete="off" />
</div>
<div class="activate-error" id="actError" hidden></div>
<div class="activate-actions">
<button class="btn btn-primary pill btn-lg" id="actActivateBtn">
<span class="ico">🔓</span> Attiva
</button>
<button class="btn btn-ghost pill btn-lg" id="actBuyBtn">
Compra una licenza
</button>
</div>
</div>
<div class="activate-foot">
Hai problemi? Scrivici a <a href="mailto:info@djluza.com">info@djluza.com</a>
</div>
</div>
</div>
<!-- ============== MODAL: Spotify Guide ============== -->
<div class="modal-backdrop" id="guideModal" hidden>
<div class="modal">
<div class="modal-head">
<h2>Come ottenere le credenziali Spotify</h2>
<button class="modal-close" id="guideCloseBtn">×</button>
</div>
<pre class="modal-body" id="guideBody"></pre>
<div class="modal-foot">
<button class="btn btn-purple pill" id="guideOpenDashBtn">Apri Spotify Dashboard</button>
<button class="btn btn-ghost pill" id="guideCloseBtn2">Chiudi</button>
</div>
</div>
</div>
<!-- ============== MODAL: Upgrade richiesto ============== -->
<div class="modal-backdrop" id="upgradeModal" hidden>
<div class="modal">
<div class="modal-head">
<h2 id="upgradeModalTitle">Passa a un piano superiore</h2>
<button class="modal-close" id="upgradeCloseBtn">×</button>
</div>
<div class="modal-body" id="upgradeModalBody">
<!-- popolato dinamicamente -->
</div>
<div class="modal-foot">
<button class="btn btn-primary pill" id="upgradeOpenBtn">Vedi i piani</button>
<button class="btn btn-ghost pill" id="upgradeCloseBtn2">Chiudi</button>
</div>
</div>
</div>
<!-- ============== MODAL: Match locale Upgrade ============== -->
<div class="modal-backdrop" id="upgradePickerModal" hidden>
<div class="modal">
<div class="modal-head">
<h2>Match locali trovati</h2>
<button class="modal-close" id="upgradePickerCloseBtn">×</button>
</div>
<div class="modal-body upgrade-picker-body">
<div class="modal-sub" id="upgradePickerFilename"></div>
<div id="upgradePickerList" class="upgrade-picker-list"></div>
</div>
<div class="modal-foot">
<button class="btn btn-primary pill" id="upgradePickerUseBtn" disabled>Usa selezionato</button>
<button class="btn btn-ghost pill" id="upgradePickerYoutubeBtn">Cerca su YouTube</button>
<button class="btn btn-ghost pill" id="upgradePickerSkipBtn">Salta</button>
<button class="btn btn-danger pill" id="upgradePickerCancelAllBtn">■ Annulla tutto</button>
</div>
</div>
</div>
<!-- ============== MODAL: Cataloga — conferma spostamento ============== -->
<div class="modal-backdrop" id="catalogConfirmModal" hidden>
<div class="modal">
<div class="modal-head">
<h2>Conferma spostamento</h2>
<button class="modal-close" id="catalogConfirmCloseBtn">×</button>
</div>
<div class="modal-body">
<p id="catalogConfirmMsg">Stai per spostare N file. Procedi?</p>
<p class="modal-hint">
L'operazione riorganizza i file in sottocartelle
<code>&lt;Anno&gt;/&lt;Genere&gt;/</code>. E' reversibile
manualmente dal log ma puo' richiedere tempo su cartelle
grandi.
</p>
</div>
<div class="modal-foot">
<button class="btn btn-ghost pill" id="catalogConfirmCancelBtn">Annulla</button>
<button class="btn btn-danger pill" id="catalogConfirmProceedBtn">Sposta</button>
</div>
</div>
</div>
<!-- ============== TOAST ============== -->
<div class="toast" id="toast"></div>
<script src="js/app.js"></script>
</body>
</html>