ui(settings): collapsible sections + Lidarr-style polish for Indexers tab

Restructure the Indexers & Downloaders tab to mirror the
Paths & Organization / Post-Processing / Library Preferences
pattern on the Library page — each subsystem (Indexers / Torrent
Client / Usenet Client) gets its own collapsible section header
with a status dot, hint, and animated arrow.

Visual cues borrowed from Lidarr but rendered in SoulSync's
existing dark-glass theme:
- Intro hero card at the top of the tab with a 1-2-3 flow:
  Indexers find releases → Downloader fetches → SoulSync imports.
  Accent-color stepper pills + sub-copy summarising what's
  optional vs required.
- Status dot in each section header — grey 'unknown' before
  testing, green after Test Connection succeeds, red on failure.
  Driven by _setIndStatusDot() helper called from each test
  handler. Soft glow on the active states.
- Per-service service-title color accents matching existing
  spotify-title / tidal-title pattern: prowlarr-title (orange,
  Prowlarr brand), torrent-title (sky blue, qBit family),
  usenet-title (violet).
- Indexer list cards replace the inline-emoji list — proper
  protocol badges (Torrent vs Usenet pill), monospace id chip,
  privacy tag, dimmed appearance when the indexer is disabled
  in Prowlarr.
- Indexers section starts open; Torrent + Usenet start collapsed
  since most users only configure one protocol.

No behavior changes — same fields, same endpoints, same save
flow. Pure visual restructure of the panels added in the previous
three commits.
This commit is contained in:
Broque Thomas 2026-05-20 16:07:35 -07:00
parent e4ca56b499
commit 9b36d421ee
4 changed files with 396 additions and 146 deletions

View file

@ -4947,160 +4947,206 @@
</div>
<!-- ═══ INDEXERS & DOWNLOADERS ═══ -->
<div class="settings-group" data-stg="indexers">
<h3>🔎 Prowlarr (Indexer Aggregator)</h3>
<div class="setting-help-text" style="margin-bottom: 10px;">
Prowlarr manages your Usenet and torrent indexers and exposes them through one API. SoulSync uses Prowlarr to search across every indexer at once for the Torrent and Usenet download sources.
<br><br>
Don't have it? Grab Prowlarr from <code>prowlarr.com</code> (or your *arr stack). You point Prowlarr at your indexers, then point SoulSync at Prowlarr.
<!-- Intro hero — explains the search → fetch flow in one glance -->
<div class="settings-group ind-hero" data-stg="indexers">
<div class="ind-hero-title">Indexers &amp; Downloaders</div>
<div class="ind-hero-flow">
<span class="ind-hero-step"><span class="ind-hero-step-num">1</span> Indexers find releases</span>
<span class="ind-hero-arrow"></span>
<span class="ind-hero-step"><span class="ind-hero-step-num">2</span> Downloader fetches them</span>
<span class="ind-hero-arrow"></span>
<span class="ind-hero-step"><span class="ind-hero-step-num">3</span> SoulSync imports to library</span>
</div>
<div class="form-group">
<label>Prowlarr URL:</label>
<input type="text" id="prowlarr-url" placeholder="http://localhost:9696">
<div class="setting-help-text">
Full URL to your Prowlarr instance (e.g. <code>http://192.168.1.100:9696</code>).
</div>
<div class="ind-hero-sub">
Configure your Prowlarr instance to feed search results, and pick a torrent client and/or usenet client to handle the actual downloads. You only need to set up the protocols you actually use.
</div>
<div class="form-group">
<label>API Key:</label>
<input type="password" id="prowlarr-api-key" placeholder="Your Prowlarr API key">
<div class="setting-help-text">
Found in Prowlarr → Settings → General → Security → API Key.
</div>
</div>
<div class="form-group">
<label>Prowlarr Status:</label>
<div class="form-actions" style="margin-top: 4px;">
<button class="test-button" id="prowlarr-test-btn" onclick="testProwlarrConnection()">
Test Connection
</button>
<button class="test-button" id="prowlarr-refresh-indexers-btn" onclick="loadProwlarrIndexers()" style="margin-left: 6px;">
Refresh Indexer List
</button>
<span id="prowlarr-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
</div>
</div>
<div class="form-group">
<label>Configured Indexers:</label>
<div id="prowlarr-indexer-list" class="setting-help-text" style="margin-top: 4px;">
Connect to Prowlarr and click <strong>Refresh Indexer List</strong> to see the indexers SoulSync can search.
</div>
</div>
<div class="form-group">
<label>Restrict to indexer IDs (optional):</label>
<input type="text" id="prowlarr-indexer-ids" placeholder="e.g. 1,3,7 (blank = all enabled indexers)">
<div class="setting-help-text">
Comma-separated Prowlarr indexer IDs. Leave blank to search every enabled indexer. Restrict when you have a private tracker you want to prioritise or a noisy public one to exclude.
</div>
<!-- ─── INDEXERS section ─── -->
<div class="settings-section-header" data-stg="indexers" onclick="this.classList.toggle('collapsed'); const b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':''">
<span class="settings-section-arrow">&#9660;</span>
<h3>🔎 Indexers</h3>
<span class="settings-section-hint">Prowlarr — search aggregator</span>
<span class="ind-status-dot ind-status-dot-unknown" id="prowlarr-status-dot" title="Not tested"></span>
</div>
<div class="settings-section-body" data-stg="indexers">
<div class="settings-group" data-stg="indexers">
<div class="api-service-frame">
<h4 class="service-title prowlarr-title">⬢ Prowlarr</h4>
<div class="setting-help-text" style="margin-bottom: 14px;">
Prowlarr manages your Usenet and torrent indexers and exposes them through one API. SoulSync uses Prowlarr to search across every indexer at once for the Torrent and Usenet download sources.
<br><br>
Don't have it? Grab Prowlarr from <code>prowlarr.com</code> (or your *arr stack). You point Prowlarr at your indexers, then point SoulSync at Prowlarr.
</div>
<div class="form-group">
<label>Prowlarr URL:</label>
<input type="text" id="prowlarr-url" placeholder="http://localhost:9696">
<div class="setting-help-text">
Full URL to your Prowlarr instance (e.g. <code>http://192.168.1.100:9696</code>).
</div>
</div>
<div class="form-group">
<label>API Key:</label>
<input type="password" id="prowlarr-api-key" placeholder="Your Prowlarr API key">
<div class="setting-help-text">
Found in Prowlarr → Settings → General → Security → API Key.
</div>
</div>
<div class="form-group">
<label>Restrict to indexer IDs (optional):</label>
<input type="text" id="prowlarr-indexer-ids" placeholder="e.g. 1,3,7 (blank = all enabled indexers)">
<div class="setting-help-text">
Comma-separated Prowlarr indexer IDs. Leave blank to search every enabled indexer. Restrict when you have a private tracker you want to prioritise or a noisy public one to exclude.
</div>
</div>
<div class="form-group">
<label>Status:</label>
<div class="form-actions" style="margin-top: 4px;">
<button class="test-button" id="prowlarr-test-btn" onclick="testProwlarrConnection()">
Test Connection
</button>
<button class="test-button" id="prowlarr-refresh-indexers-btn" onclick="loadProwlarrIndexers()" style="margin-left: 6px;">
Refresh Indexer List
</button>
<span id="prowlarr-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
</div>
</div>
<div class="form-group">
<label>Configured Indexers:</label>
<div id="prowlarr-indexer-list" class="ind-indexer-list">
Connect to Prowlarr and click <strong>Refresh Indexer List</strong> to see the indexers SoulSync can search.
</div>
</div>
</div>
</div>
</div>
<!-- ═══ TORRENT CLIENT ═══ -->
<div class="settings-group" data-stg="indexers">
<!-- ─── TORRENT CLIENT section ─── -->
<div class="settings-section-header collapsed" data-stg="indexers" onclick="this.classList.toggle('collapsed'); const b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':''">
<span class="settings-section-arrow">&#9660;</span>
<h3>🧲 Torrent Client</h3>
<div class="setting-help-text" style="margin-bottom: 10px;">
Where SoulSync sends torrents once Prowlarr finds them. Pick the client you already use — only one can be active at a time. Add the URL of the client's WebUI, log in if it asks for credentials, and hit Test Connection.
</div>
<div class="form-group">
<label>Client Type:</label>
<select id="torrent-client-type" class="form-select">
<option value="qbittorrent">qBittorrent</option>
<option value="transmission">Transmission</option>
<option value="deluge">Deluge 2.x</option>
</select>
<div class="setting-help-text" id="torrent-client-help">
qBittorrent: WebUI port (default 8080). Transmission: RPC port (default 9091). Deluge: WebUI port (default 8112).
</div>
</div>
<div class="form-group">
<label>WebUI URL:</label>
<input type="text" id="torrent-client-url" placeholder="http://localhost:8080">
</div>
<div class="form-group" id="torrent-client-username-group">
<label>Username:</label>
<input type="text" id="torrent-client-username" placeholder="leave blank if WebUI doesn't require auth">
<div class="setting-help-text">
qBittorrent and Transmission use username + password. Deluge uses password only — paste it in the password field below.
</div>
</div>
<div class="form-group">
<label>Password:</label>
<input type="password" id="torrent-client-password" placeholder="WebUI password">
</div>
<div class="form-group">
<label>Category / Label:</label>
<input type="text" id="torrent-client-category" placeholder="soulsync" value="soulsync">
<div class="setting-help-text">
SoulSync tags every torrent it adds with this label so it's easy to spot in your client. Deluge needs the Label plugin installed for this to stick.
</div>
</div>
<div class="form-group">
<label>Save Path (optional):</label>
<input type="text" id="torrent-client-save-path" placeholder="leave blank to use the client's default">
<div class="setting-help-text">
Override where the torrent client writes downloads. This path is on the <strong>torrent client's</strong> machine, not SoulSync's.
</div>
</div>
<div class="form-group">
<label>Torrent Client Status:</label>
<div class="form-actions" style="margin-top: 4px;">
<button class="test-button" id="torrent-client-test-btn" onclick="testTorrentClientConnection()">
Test Connection
</button>
<span id="torrent-client-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
<span class="settings-section-hint">qBittorrent · Transmission · Deluge</span>
<span class="ind-status-dot ind-status-dot-unknown" id="torrent-client-status-dot" title="Not tested"></span>
</div>
<div class="settings-section-body collapsed" data-stg="indexers">
<div class="settings-group" data-stg="indexers">
<div class="api-service-frame">
<h4 class="service-title torrent-title">⬢ Torrent Downloader</h4>
<div class="setting-help-text" style="margin-bottom: 14px;">
Where SoulSync sends torrents once Prowlarr finds them. Pick the client you already use — only one can be active at a time. Add the URL of the client's WebUI, log in if it asks for credentials, and hit Test Connection.
</div>
<div class="form-group">
<label>Client Type:</label>
<select id="torrent-client-type" class="form-select">
<option value="qbittorrent">qBittorrent</option>
<option value="transmission">Transmission</option>
<option value="deluge">Deluge 2.x</option>
</select>
<div class="setting-help-text" id="torrent-client-help">
qBittorrent: WebUI port (default 8080). Transmission: RPC port (default 9091). Deluge: WebUI port (default 8112).
</div>
</div>
<div class="form-group">
<label>WebUI URL:</label>
<input type="text" id="torrent-client-url" placeholder="http://localhost:8080">
</div>
<div class="form-group" id="torrent-client-username-group">
<label>Username:</label>
<input type="text" id="torrent-client-username" placeholder="leave blank if WebUI doesn't require auth">
<div class="setting-help-text">
qBittorrent and Transmission use username + password. Deluge uses password only — paste it in the password field below.
</div>
</div>
<div class="form-group">
<label>Password:</label>
<input type="password" id="torrent-client-password" placeholder="WebUI password">
</div>
<div class="form-group">
<label>Category / Label:</label>
<input type="text" id="torrent-client-category" placeholder="soulsync" value="soulsync">
<div class="setting-help-text">
SoulSync tags every torrent it adds with this label so it's easy to spot in your client. Deluge needs the Label plugin installed for this to stick.
</div>
</div>
<div class="form-group">
<label>Save Path (optional):</label>
<input type="text" id="torrent-client-save-path" placeholder="leave blank to use the client's default">
<div class="setting-help-text">
Override where the torrent client writes downloads. This path is on the <strong>torrent client's</strong> machine, not SoulSync's.
</div>
</div>
<div class="form-group">
<label>Status:</label>
<div class="form-actions" style="margin-top: 4px;">
<button class="test-button" id="torrent-client-test-btn" onclick="testTorrentClientConnection()">
Test Connection
</button>
<span id="torrent-client-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
</div>
</div>
</div>
</div>
</div>
<!-- ═══ USENET CLIENT ═══ -->
<div class="settings-group" data-stg="indexers">
<!-- ─── USENET CLIENT section ─── -->
<div class="settings-section-header collapsed" data-stg="indexers" onclick="this.classList.toggle('collapsed'); const b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':''">
<span class="settings-section-arrow">&#9660;</span>
<h3>📰 Usenet Client</h3>
<div class="setting-help-text" style="margin-bottom: 10px;">
Where SoulSync sends NZBs once Prowlarr finds them. Pick one usenet downloader. SABnzbd uses an API key for auth; NZBGet uses a username + password.
</div>
<div class="form-group">
<label>Client Type:</label>
<select id="usenet-client-type" class="form-select" onchange="updateUsenetClientUI()">
<option value="sabnzbd">SABnzbd</option>
<option value="nzbget">NZBGet</option>
</select>
<div class="setting-help-text">
SABnzbd: default WebUI port 8080. NZBGet: default WebUI port 6789.
</div>
</div>
<div class="form-group">
<label>WebUI URL:</label>
<input type="text" id="usenet-client-url" placeholder="http://localhost:8080">
</div>
<div class="form-group" id="usenet-apikey-group">
<label>API Key:</label>
<input type="password" id="usenet-client-api-key" placeholder="SABnzbd API key">
<div class="setting-help-text">
SABnzbd → Config → General → API Key. Used by SABnzbd only.
</div>
</div>
<div class="form-group" id="usenet-username-group" style="display: none;">
<label>Username:</label>
<input type="text" id="usenet-client-username" placeholder="NZBGet WebUI username">
</div>
<div class="form-group" id="usenet-password-group" style="display: none;">
<label>Password:</label>
<input type="password" id="usenet-client-password" placeholder="NZBGet WebUI password">
</div>
<div class="form-group">
<label>Category / Label:</label>
<input type="text" id="usenet-client-category" placeholder="soulsync" value="soulsync">
<div class="setting-help-text">
SoulSync tags every NZB with this category so it ends up in a predictable post-processing folder.
</div>
</div>
<div class="form-group">
<label>Usenet Client Status:</label>
<div class="form-actions" style="margin-top: 4px;">
<button class="test-button" id="usenet-client-test-btn" onclick="testUsenetClientConnection()">
Test Connection
</button>
<span id="usenet-client-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
<span class="settings-section-hint">SABnzbd · NZBGet</span>
<span class="ind-status-dot ind-status-dot-unknown" id="usenet-client-status-dot" title="Not tested"></span>
</div>
<div class="settings-section-body collapsed" data-stg="indexers">
<div class="settings-group" data-stg="indexers">
<div class="api-service-frame">
<h4 class="service-title usenet-title">⬢ Usenet Downloader</h4>
<div class="setting-help-text" style="margin-bottom: 14px;">
Where SoulSync sends NZBs once Prowlarr finds them. Pick one usenet downloader. SABnzbd uses an API key for auth; NZBGet uses a username + password.
</div>
<div class="form-group">
<label>Client Type:</label>
<select id="usenet-client-type" class="form-select" onchange="updateUsenetClientUI()">
<option value="sabnzbd">SABnzbd</option>
<option value="nzbget">NZBGet</option>
</select>
<div class="setting-help-text">
SABnzbd: default WebUI port 8080. NZBGet: default WebUI port 6789.
</div>
</div>
<div class="form-group">
<label>WebUI URL:</label>
<input type="text" id="usenet-client-url" placeholder="http://localhost:8080">
</div>
<div class="form-group" id="usenet-apikey-group">
<label>API Key:</label>
<input type="password" id="usenet-client-api-key" placeholder="SABnzbd API key">
<div class="setting-help-text">
SABnzbd → Config → General → API Key. Used by SABnzbd only.
</div>
</div>
<div class="form-group" id="usenet-username-group" style="display: none;">
<label>Username:</label>
<input type="text" id="usenet-client-username" placeholder="NZBGet WebUI username">
</div>
<div class="form-group" id="usenet-password-group" style="display: none;">
<label>Password:</label>
<input type="password" id="usenet-client-password" placeholder="NZBGet WebUI password">
</div>
<div class="form-group">
<label>Category / Label:</label>
<input type="text" id="usenet-client-category" placeholder="soulsync" value="soulsync">
<div class="setting-help-text">
SoulSync tags every NZB with this category so it ends up in a predictable post-processing folder.
</div>
</div>
<div class="form-group">
<label>Status:</label>
<div class="form-actions" style="margin-top: 4px;">
<button class="test-button" id="usenet-client-test-btn" onclick="testUsenetClientConnection()">
Test Connection
</button>
<span id="usenet-client-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
</div>
</div>
</div>
</div>
</div>

View file

@ -3415,6 +3415,7 @@ function closeHelperSearch() {
const WHATS_NEW = {
'2.6.0': [
{ unreleased: true },
{ title: 'Indexers & Downloaders tab restyled with collapsible sections', desc: 'tab now opens with an intro hero card explaining the flow (indexers find releases → downloader fetches them → SoulSync imports) and folds the rest into three collapsible sections: Indexers, Torrent Client, Usenet Client. each section gets a per-service color accent (Prowlarr orange, torrent sky-blue, usenet violet), a status dot in the header (green when Test Connection succeeds, red on failure, grey before testing), and Lidarr-style indexer cards with protocol badges instead of the inline emoji list. Indexers section is open by default; the downloader sections start collapsed since not everyone uses both protocols.' },
{ title: 'Regression tests for the new indexer + downloader plumbing', desc: 'mocked unit tests for Prowlarr + all five downloader adapters (qBittorrent, Transmission, Deluge, SABnzbd, NZBGet). 54 tests pin the state-mapping tables, the parse logic, and the protocol quirks each client needs handled (qBit Referer header, Transmission session-id renegotiation, Deluge magnet-vs-URL method split, SAB queue+history merge, NZBGet 64-bit size fields). next time anyone touches one of these adapters, CI catches breakage before it hits a real downloader.' },
{ title: 'Usenet client adapters (SABnzbd, NZBGet)', desc: 'third commit in the torrent + usenet rollout. SoulSync now talks to SABnzbd and NZBGet through a sibling adapter contract that mirrors the torrent adapter set — pick one downloader in Settings → Indexers & Downloaders, fill in its API key (SABnzbd) or username + password (NZBGet), and Test Connection confirms the link. all three layers are now stood up: Prowlarr finds releases, the torrent adapter and the usenet adapter each know how to ship work to the underlying client. next commit wires Prowlarr search → adapter dispatch → archive extraction so the new sources actually download. job state mapping covers SABnzbd queue + history and NZBGet groups + history, including the verify/repair/unpack phases that are unique to usenet.' },
{ title: 'Torrent client adapters (qBittorrent, Transmission, Deluge)', desc: 'second commit in the torrent + usenet rollout. SoulSync can now talk to any of the three big torrent clients through a single adapter contract — pick which one you use in Settings → Indexers & Downloaders, paste your WebUI URL and credentials, and Test Connection confirms the link. each adapter handles its own auth quirk (qBit cookies, Transmission session-id, Deluge JSON-RPC) and maps native state strings onto a uniform set so the rest of the app stays client-agnostic. no download wiring yet — that gets layered on once the usenet client adapters land in the next commit.' },

View file

@ -3582,6 +3582,22 @@ async function testLidarrConnection() {
}
}
function _setIndStatusDot(dotId, state) {
const dot = document.getElementById(dotId);
if (!dot) return;
dot.classList.remove('ind-status-dot-unknown', 'ind-status-dot-connected', 'ind-status-dot-error');
if (state === 'connected') {
dot.classList.add('ind-status-dot-connected');
dot.title = 'Connected';
} else if (state === 'error') {
dot.classList.add('ind-status-dot-error');
dot.title = 'Connection failed';
} else {
dot.classList.add('ind-status-dot-unknown');
dot.title = 'Not tested';
}
}
async function testProwlarrConnection() {
const statusEl = document.getElementById('prowlarr-connection-status');
if (!statusEl) return;
@ -3598,14 +3614,17 @@ async function testProwlarrConnection() {
if (data.success) {
statusEl.textContent = data.message || 'Connected';
statusEl.style.color = '#4caf50';
_setIndStatusDot('prowlarr-status-dot', 'connected');
loadProwlarrIndexers();
} else {
statusEl.textContent = data.error || 'Connection failed';
statusEl.style.color = '#f44336';
_setIndStatusDot('prowlarr-status-dot', 'error');
}
} catch (e) {
statusEl.textContent = 'Connection error';
statusEl.style.color = '#f44336';
_setIndStatusDot('prowlarr-status-dot', 'error');
}
}
@ -3641,13 +3660,16 @@ async function testUsenetClientConnection() {
if (data.success) {
statusEl.textContent = data.message || 'Connected';
statusEl.style.color = '#4caf50';
_setIndStatusDot('usenet-client-status-dot', 'connected');
} else {
statusEl.textContent = data.error || 'Connection failed';
statusEl.style.color = '#f44336';
_setIndStatusDot('usenet-client-status-dot', 'error');
}
} catch (e) {
statusEl.textContent = 'Connection error';
statusEl.style.color = '#f44336';
_setIndStatusDot('usenet-client-status-dot', 'error');
}
}
@ -3667,13 +3689,16 @@ async function testTorrentClientConnection() {
if (data.success) {
statusEl.textContent = data.message || 'Connected';
statusEl.style.color = '#4caf50';
_setIndStatusDot('torrent-client-status-dot', 'connected');
} else {
statusEl.textContent = data.error || 'Connection failed';
statusEl.style.color = '#f44336';
_setIndStatusDot('torrent-client-status-dot', 'error');
}
} catch (e) {
statusEl.textContent = 'Connection error';
statusEl.style.color = '#f44336';
_setIndStatusDot('torrent-client-status-dot', 'error');
}
}
@ -3694,10 +3719,16 @@ async function loadProwlarrIndexers() {
}
const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const rows = data.indexers.map(idx => {
const proto = idx.protocol === 'usenet' ? '📰 Usenet' : '🧲 Torrent';
const enabled = idx.enable ? '✅' : '⛔';
const privacy = idx.privacy ? `<span style="opacity:0.6;">(${esc(idx.privacy)})</span>` : '';
return `<div style="padding:3px 0;">${enabled} <strong>#${esc(idx.id)}</strong> ${esc(idx.name)}${proto} ${privacy}</div>`;
const protoClass = idx.protocol === 'usenet' ? 'ind-indexer-card-proto-usenet' : 'ind-indexer-card-proto-torrent';
const protoLabel = idx.protocol === 'usenet' ? 'Usenet' : 'Torrent';
const privacyHTML = idx.privacy ? `<span class="ind-indexer-card-privacy">${esc(idx.privacy)}</span>` : '';
const disabledClass = idx.enable ? '' : ' ind-indexer-card-disabled';
return `<div class="ind-indexer-card${disabledClass}">
<span class="ind-indexer-card-id">#${esc(idx.id)}</span>
<span class="ind-indexer-card-name">${esc(idx.name)}</span>
${privacyHTML}
<span class="ind-indexer-card-proto ${protoClass}">${protoLabel}</span>
</div>`;
}).join('');
listEl.innerHTML = rows;
} catch (e) {

View file

@ -3243,6 +3243,178 @@ body.helper-mode-active #dashboard-activity-feed:hover {
color: #eb743b;
}
/* Indexers & Downloaders tab — service title colors */
.prowlarr-title {
color: #e07712; /* Prowlarr brand orange */
}
.torrent-title {
color: #5dade2; /* Torrent — sky blue (qBit family) */
}
.usenet-title {
color: #a78bfa; /* Usenet — violet */
}
/* Lidarr-style intro hero card on Indexers & Downloaders tab */
.ind-hero {
padding: 18px 22px;
background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.08), rgba(35, 35, 35, 0.6));
border: 1px solid rgba(var(--accent-rgb), 0.15);
}
.ind-hero-title {
font-size: 13px;
font-weight: 700;
letter-spacing: 0.8px;
text-transform: uppercase;
color: rgb(var(--accent-rgb));
margin-bottom: 12px;
}
.ind-hero-flow {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
margin-bottom: 12px;
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
}
.ind-hero-step {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 999px;
}
.ind-hero-step-num {
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(var(--accent-rgb), 0.2);
color: rgb(var(--accent-rgb));
font-weight: 700;
font-size: 11px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.ind-hero-arrow {
color: rgba(var(--accent-rgb), 0.55);
font-weight: 700;
}
.ind-hero-sub {
font-size: 12.5px;
color: rgba(255, 255, 255, 0.55);
line-height: 1.55;
}
/* Section-header status dots — Lidarr-style green/red/grey indicator */
.ind-status-dot {
width: 9px;
height: 9px;
border-radius: 50%;
margin-left: 8px;
flex-shrink: 0;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.2);
}
.ind-status-dot-unknown {
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.ind-status-dot-connected {
background: #2ecc71;
box-shadow: 0 0 8px rgba(46, 204, 113, 0.6), 0 0 0 2px rgba(0, 0, 0, 0.2);
}
.ind-status-dot-error {
background: #e74c3c;
box-shadow: 0 0 8px rgba(231, 76, 60, 0.5), 0 0 0 2px rgba(0, 0, 0, 0.2);
}
/* Indexer list — card-per-indexer, Lidarr-style */
.ind-indexer-list {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.7);
}
.ind-indexer-card {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 8px;
transition: all 0.15s ease;
}
.ind-indexer-card:hover {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.1);
}
.ind-indexer-card-id {
font-family: 'SF Mono', Monaco, monospace;
font-size: 11px;
color: rgba(255, 255, 255, 0.4);
background: rgba(0, 0, 0, 0.3);
padding: 2px 6px;
border-radius: 4px;
flex-shrink: 0;
}
.ind-indexer-card-name {
flex: 1;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
}
.ind-indexer-card-proto {
font-size: 11px;
padding: 3px 8px;
border-radius: 999px;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
flex-shrink: 0;
}
.ind-indexer-card-proto-torrent {
background: rgba(93, 173, 226, 0.15);
color: #5dade2;
border: 1px solid rgba(93, 173, 226, 0.3);
}
.ind-indexer-card-proto-usenet {
background: rgba(167, 139, 250, 0.15);
color: #a78bfa;
border: 1px solid rgba(167, 139, 250, 0.3);
}
.ind-indexer-card-privacy {
font-size: 10.5px;
color: rgba(255, 255, 255, 0.4);
flex-shrink: 0;
}
.ind-indexer-card-disabled {
opacity: 0.45;
}
.acoustid-title {
color: #ba55d3;
}