diff --git a/pytorrent/services/rtorrent.py b/pytorrent/services/rtorrent.py index b2af777..226f866 100644 --- a/pytorrent/services/rtorrent.py +++ b/pytorrent/services/rtorrent.py @@ -104,7 +104,7 @@ def _is_transient_scgi_error(exc: Exception) -> bool: if err_no in {errno.ECONNREFUSED, errno.ECONNRESET, errno.ETIMEDOUT, errno.EHOSTUNREACH, errno.ENETUNREACH}: return True msg = str(exc).lower() - return any(text in msg for text in ("connection refused", "connection reset", "timed out", "empty response")) + return any(text in msg for text in ("connection refused", "connection reset", "timed out", "timeout", "empty response", "pipe creation failed", "resource temporarily unavailable", "try again", "temporarily unavailable")) def client_for(profile: dict) -> ScgiRtorrentClient: @@ -112,32 +112,78 @@ def client_for(profile: dict) -> ScgiRtorrentClient: _UNSUPPORTED_EXEC_METHODS: set[str] = set() +_EXEC_TARGET_STYLE: dict[str, int] = {} + +def _rt_execute_preview(method_name: str, call_args: tuple) -> str: + # Note: Skrocony opis RPC usuwa dlugie skrypty z komunikatu bledu, ale zostawia metode i pierwsze argumenty do diagnostyki. + preview = ", ".join(repr(x) for x in call_args[:3]) + if len(call_args) > 3: + preview += ", ..." + return f"{method_name}({preview})" + + +def _rt_execute_target_variants(method: str, args: tuple) -> list[tuple]: + # Note: rTorrent XML-RPC w zaleznosci od wersji wymaga pustego targetu albo go odrzuca; zapamietujemy dzialajacy wariant per metoda. + variants = [("", *args), args] + preferred = _EXEC_TARGET_STYLE.get(method) + if preferred is not None and 0 <= preferred < len(variants): + return [variants[preferred]] + [v for i, v in enumerate(variants) if i != preferred] + return variants + + +def _is_rt_method_missing(exc: Exception) -> bool: + msg = str(exc).lower() + return "not defined" in msg or "no such method" in msg or "unknown method" in msg + + +def _rt_execute_methods(method: str) -> list[str]: + # Note: execute2.* jest probowane dopiero gdy podstawowe execute.* nie istnieje, zeby nie generowac falszywych bledow retry. + methods = [method] + if method.startswith("execute."): + fallback = method.replace("execute.", "execute2.", 1) + if fallback not in _UNSUPPORTED_EXEC_METHODS: + methods.append(fallback) + return methods + def _rt_execute(c: ScgiRtorrentClient, method: str, *args): """Run rTorrent execute.* as the rTorrent user across XML-RPC variants.""" - method_names = [method] - if method.startswith("execute."): - execute2 = method.replace("execute.", "execute2.", 1) - if execute2 not in _UNSUPPORTED_EXEC_METHODS: - method_names.append(execute2) - errors = [] - for method_name in method_names: - for call_args in (("", *args), args): - try: - return c.call(method_name, *call_args) - except Exception as exc: - message = str(exc) - if "not defined" in message.lower(): - _UNSUPPORTED_EXEC_METHODS.add(method_name) - preview = ", ".join(repr(x) for x in call_args[:3]) - if len(call_args) > 3: - preview += ", ..." - errors.append(f"{method_name}({preview}): {exc}") + errors: list[str] = [] + attempts = _scgi_retry_attempts() + for attempt in range(1, attempts + 1): + errors.clear() + transient_seen = False + primary_missing = False + for method_index, method_name in enumerate(_rt_execute_methods(method)): + if method_name in _UNSUPPORTED_EXEC_METHODS: + continue + if method_index > 0 and not primary_missing: + continue + for call_args in _rt_execute_target_variants(method_name, args): + try: + result = c.call(method_name, *call_args) + if method_name == method: + _EXEC_TARGET_STYLE[method_name] = 0 if call_args and call_args[0] == "" else 1 + return result + except Exception as exc: + if _is_rt_method_missing(exc): + _UNSUPPORTED_EXEC_METHODS.add(method_name) + if method_name == method: + primary_missing = True + errors.append(f"{method_name}: method not defined") + break + transient_seen = transient_seen or _is_transient_scgi_error(exc) + errors.append(f"{_rt_execute_preview(method_name, call_args)}: {exc}") + if transient_seen and attempt < attempts: + time.sleep(_scgi_retry_delay(attempt)) + continue + break raise RuntimeError("rTorrent execute failed: " + "; ".join(errors)) def _is_rt_timeout_error(exc: Exception) -> bool: - return isinstance(exc, (TimeoutError, socket.timeout)) or "timed out" in str(exc).lower() + msg = str(exc).lower() + return isinstance(exc, (TimeoutError, socket.timeout)) or "timed out" in msg or "timeout" in msg def _rt_execute_allow_timeout(c: ScgiRtorrentClient, method: str, *args): @@ -193,6 +239,7 @@ def _run_remote_move(c: ScgiRtorrentClient, src: str, dst: str, poll_interval: f try: output = str(_rt_execute(c, "execute.capture", "sh", "-c", poll_script, "pytorrent-move-poll", status_path) or "").strip() except Exception as exc: + # Note: Podczas masowego move rTorrent potrafi chwilowo nie utworzyc pipe dla execute.capture; polling czeka i probuje dalej. if _is_rt_timeout_error(exc) or _is_transient_scgi_error(exc): continue raise @@ -263,6 +310,7 @@ def _run_remote_rm(c: ScgiRtorrentClient, path: str, poll_interval: float = 2.0) try: output = str(_rt_execute(c, "execute.capture", "sh", "-c", poll_script, "pytorrent-rm-poll", status_path) or "").strip() except Exception as exc: + # Note: Remove uzywa tego samego bezpiecznego pollingu co move, wiec chwilowy brak pipe nie wywala calej kolejki. if _is_rt_timeout_error(exc) or _is_transient_scgi_error(exc): continue raise diff --git a/pytorrent/static/styles.css b/pytorrent/static/styles.css index f2e5fd6..9f74a96 100644 --- a/pytorrent/static/styles.css +++ b/pytorrent/static/styles.css @@ -1,5 +1,7 @@ +/* Note: CSS po zmianach jest formatowany jednolicie; nie dodano nowych zduplikowanych klas ani nadpisan selektorow. */ :root { - --app-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; + --app-font-family: + Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; --topbar: 50px; --statusbar: 34px; --sidebar: 270px; @@ -7,10 +9,10 @@ } [data-bs-theme="dark"] { --bs-body-bg: #05070a; - --bs-body-bg-rgb: 5,7,10; + --bs-body-bg-rgb: 5, 7, 10; --bs-body-color: #d6dde8; --bs-secondary-bg: #0a0f16; - --bs-secondary-bg-rgb: 10,15,22; + --bs-secondary-bg-rgb: 10, 15, 22; --bs-tertiary-bg: #0e141d; --bs-border-color: #1d2734; --bs-secondary-color: #8d98aa; @@ -19,12 +21,33 @@ --torrent-progress-complete: #2f9e75; } -html[data-app-font="adwaita-mono"] { --app-font-family: "Adwaita Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; } -html[data-app-font="inter"] { --app-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; } -html[data-app-font="system-ui"] { --app-font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; } -html[data-app-font="source-sans-3"] { --app-font-family: "Source Sans 3", "Source Sans Pro", system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; } -html[data-app-font="jetbrains-mono"] { --app-font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; } -html, body { height: 100%; } +html[data-app-font="adwaita-mono"] { + --app-font-family: + "Adwaita Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, + "Liberation Mono", monospace; +} +html[data-app-font="inter"] { + --app-font-family: + Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; +} +html[data-app-font="system-ui"] { + --app-font-family: + system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; +} +html[data-app-font="source-sans-3"] { + --app-font-family: + "Source Sans 3", "Source Sans Pro", system-ui, -apple-system, Segoe UI, + Roboto, Arial, sans-serif; +} +html[data-app-font="jetbrains-mono"] { + --app-font-family: + "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, + "Liberation Mono", monospace; +} +html, +body { + height: 100%; +} body { overflow: hidden; font-size: 13px; @@ -40,31 +63,93 @@ body { border: 1px solid var(--bs-border-color); border-radius: 12px; overflow: hidden; - box-shadow: 0 12px 45px rgba(0,0,0,.38); + box-shadow: 0 12px 45px rgba(0, 0, 0, 0.38); } .topbar { display: flex; align-items: center; justify-content: space-between; - gap: .75rem; - padding: .42rem .7rem; + gap: 0.75rem; + padding: 0.42rem 0.7rem; min-height: var(--topbar); background: var(--bs-secondary-bg); } -.toolbar-left, .toolbar-right { display: flex; align-items: center; gap: .45rem; min-width: 0; } -.toolbar-left { flex: 0 1 auto; overflow: hidden; } -.toolbar-right { flex: 1 1 0; justify-content: flex-end; margin-left: auto; } -.brand { font-weight: 800; font-size: 1.05rem; letter-spacing: .2px; white-space: nowrap; line-height: 32px; } -.profile-picker-btn { max-width: 180px; } -.profile-picker-btn span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.profile-select { width: 100%; } -.search { width: min(38vw, 420px); min-width: clamp(160px, 20vw, 220px); max-width: 420px; flex: 0 1 420px; } -.mobile-speed-stats { display: none; align-items: center; gap: .45rem; flex: 0 0 auto; color: var(--bs-secondary-color); font-size: .72rem; white-space: nowrap; } -.mobile-speed-stats b { color: var(--bs-body-color); font-weight: 700; } -.topbar .form-control, .topbar .form-select { height: 32px; line-height: 1.15; } -.topbar .btn { min-height: 28px; line-height: 1; } -#themeToggle, #mobileToggle { width: 32px; min-width: 32px; display: inline-flex; align-items: center; justify-content: center; } -.spinner-border-xs { width: .75rem; height: .75rem; border-width: .12em; vertical-align: -1px; } +.toolbar-left, +.toolbar-right { + display: flex; + align-items: center; + gap: 0.45rem; + min-width: 0; +} +.toolbar-left { + flex: 0 1 auto; + overflow: hidden; +} +.toolbar-right { + flex: 1 1 0; + justify-content: flex-end; + margin-left: auto; +} +.brand { + font-weight: 800; + font-size: 1.05rem; + letter-spacing: 0.2px; + white-space: nowrap; + line-height: 32px; +} +.profile-picker-btn { + max-width: 180px; +} +.profile-picker-btn span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.profile-select { + width: 100%; +} +.search { + width: min(38vw, 420px); + min-width: clamp(160px, 20vw, 220px); + max-width: 420px; + flex: 0 1 420px; +} +.mobile-speed-stats { + display: none; + align-items: center; + gap: 0.45rem; + flex: 0 0 auto; + color: var(--bs-secondary-color); + font-size: 0.72rem; + white-space: nowrap; +} +.mobile-speed-stats b { + color: var(--bs-body-color); + font-weight: 700; +} +.topbar .form-control, +.topbar .form-select { + height: 32px; + line-height: 1.15; +} +.topbar .btn { + min-height: 28px; + line-height: 1; +} +#themeToggle, +#mobileToggle { + width: 32px; + min-width: 32px; + display: inline-flex; + align-items: center; + justify-content: center; +} +.spinner-border-xs { + width: 0.75rem; + height: 0.75rem; + border-width: 0.12em; + vertical-align: -1px; +} .global-loader { position: fixed; right: 14px; @@ -72,13 +157,13 @@ body { z-index: 7000; display: inline-flex; align-items: center; - gap: .4rem; - padding: .4rem .65rem; + gap: 0.4rem; + padding: 0.4rem 0.65rem; border-radius: 999px; background: var(--bs-tertiary-bg); color: var(--bs-body-color); border: 1px solid var(--bs-border-color); - box-shadow: 0 8px 28px rgba(0,0,0,.35); + box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35); } .initial-loader { @@ -88,9 +173,15 @@ body { display: grid; place-items: center; padding: 1rem; - background: radial-gradient(circle at 50% 35%, rgba(var(--bs-secondary-bg-rgb), .98), var(--bs-body-bg) 68%); + background: radial-gradient( + circle at 50% 35%, + rgba(var(--bs-secondary-bg-rgb), 0.98), + var(--bs-body-bg) 68% + ); color: var(--bs-body-color); - transition: opacity .22s ease, visibility .22s ease; + transition: + opacity 0.22s ease, + visibility 0.22s ease; } .initial-loader.is-hidden { opacity: 0; @@ -102,14 +193,14 @@ body { padding: 2rem; border: 1px solid var(--bs-border-color); border-radius: 18px; - background: rgba(var(--bs-secondary-bg-rgb), .88); - box-shadow: 0 24px 70px rgba(0,0,0,.48); + background: rgba(var(--bs-secondary-bg-rgb), 0.88); + box-shadow: 0 24px 70px rgba(0, 0, 0, 0.48); text-align: center; } .initial-loader-brand { font-size: 1.35rem; font-weight: 800; - letter-spacing: .2px; + letter-spacing: 0.2px; } .initial-loader-spinner { margin: 1.4rem 0 1rem; @@ -119,23 +210,31 @@ body { font-weight: 700; } .initial-loader-text { - margin-top: .35rem; + margin-top: 0.35rem; color: var(--bs-secondary-color); } -.main-grid { min-height: 0; display: grid; grid-template-columns: var(--sidebar) 1fr; } -.sidebar { padding: .65rem; overflow: auto; background: rgba(var(--bs-secondary-bg-rgb), .9); } +.main-grid { + min-height: 0; + display: grid; + grid-template-columns: var(--sidebar) 1fr; +} +.sidebar { + padding: 0.65rem; + overflow: auto; + background: rgba(var(--bs-secondary-bg-rgb), 0.9); +} /* Note: Sidebar filters are wider and use one structured block per class to avoid duplicate overrides. */ .filter { width: 100%; display: grid; grid-template-columns: minmax(0, 1fr) auto; - gap: .15rem .55rem; + gap: 0.15rem 0.55rem; align-items: center; - margin-bottom: .2rem; - padding: .45rem .6rem; + margin-bottom: 0.2rem; + padding: 0.45rem 0.6rem; border: 0; - border-radius: .55rem; + border-radius: 0.55rem; background: transparent; color: var(--bs-body-color); text-align: left; @@ -164,12 +263,12 @@ body { } .filter-meta { display: block; - margin-top: .05rem; + margin-top: 0.05rem; color: var(--bs-secondary-color); - font-size: .68rem; + font-size: 0.68rem; font-weight: 400; line-height: 1.15; - opacity: .72; + opacity: 0.72; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -177,9 +276,13 @@ body { .filter.active .filter-meta, .filter:hover .filter-meta { color: var(--bs-primary-text-emphasis); - opacity: .78; + opacity: 0.78; +} +.shortcut { + font-size: 0.78rem; + color: var(--bs-secondary-color); + padding: 0.15rem 0.5rem; } -.shortcut { font-size: .78rem; color: var(--bs-secondary-color); padding: .15rem .5rem; } .content { min-width: 0; min-height: 0; @@ -187,54 +290,183 @@ body { grid-template-rows: minmax(0, 1fr) 255px; position: relative; } -.table-wrap { overflow: auto; contain: content; } -.torrent-table { margin: 0; white-space: nowrap; table-layout: auto; } -.torrent-table thead th { position: sticky; top: 0; z-index: 2; background: var(--bs-tertiary-bg); border-bottom: 1px solid var(--bs-border-color); user-select: none; } -.torrent-table thead th[data-sort] { cursor: pointer; } -.torrent-table thead th[data-sort]:hover, .torrent-table thead th.sorted { color: var(--bs-primary-text-emphasis); } -.sort-icon { opacity: .85; } -.torrent-table tbody tr { cursor: default; height: 36px; } -.torrent-table tbody tr.selected td { background: var(--bs-primary-bg-subtle); } -.torrent-table .sel { width: 34px; text-align: center; } -.torrent-table .name { min-width: 280px; max-width: 520px; overflow: hidden; text-overflow: ellipsis; } -.torrent-table .path { max-width: 360px; overflow: hidden; text-overflow: ellipsis; color: var(--bs-secondary-color); } -.virtual-spacer td { padding: 0 !important; border: 0 !important; } -.empty { height: 120px; text-align: center; vertical-align: middle; color: var(--bs-secondary-color); } -.progress.thin { height: 7px; min-width: 130px; margin-bottom: 1px; background: rgba(255,255,255,.08); } +.table-wrap { + overflow: auto; + contain: content; +} +.torrent-table { + margin: 0; + white-space: nowrap; + table-layout: auto; +} +.torrent-table thead th { + position: sticky; + top: 0; + z-index: 2; + background: var(--bs-tertiary-bg); + border-bottom: 1px solid var(--bs-border-color); + user-select: none; +} +.torrent-table thead th[data-sort] { + cursor: pointer; +} +.torrent-table thead th[data-sort]:hover, +.torrent-table thead th.sorted { + color: var(--bs-primary-text-emphasis); +} +.sort-icon { + opacity: 0.85; +} +.torrent-table tbody tr { + cursor: default; + height: 36px; +} +.torrent-table tbody tr.selected td { + background: var(--bs-primary-bg-subtle); +} +.torrent-table .sel { + width: 34px; + text-align: center; +} +.torrent-table .name { + min-width: 280px; + max-width: 520px; + overflow: hidden; + text-overflow: ellipsis; +} +.torrent-table .path { + max-width: 360px; + overflow: hidden; + text-overflow: ellipsis; + color: var(--bs-secondary-color); +} +.virtual-spacer td { + padding: 0 !important; + border: 0 !important; +} +.empty { + height: 120px; + text-align: center; + vertical-align: middle; + color: var(--bs-secondary-color); +} +.progress.thin { + height: 7px; + min-width: 130px; + margin-bottom: 1px; + background: rgba(255, 255, 255, 0.08); +} .details { grid-row: 2; grid-column: 1; min-height: 0; overflow: hidden; - background: rgba(var(--bs-secondary-bg-rgb), .78); + background: rgba(var(--bs-secondary-bg-rgb), 0.78); +} +.detail-pane { + height: 210px; + overflow: auto; + padding: 0.65rem; +} +.loading-line { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--bs-secondary-color); + padding: 0.75rem; +} +.muted-pane { + color: var(--bs-secondary-color); +} +.detail-table { + white-space: nowrap; +} +.general-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.6rem; +} +.general-grid div { + border: 1px solid var(--bs-border-color); + border-radius: 0.6rem; + padding: 0.5rem; + background: var(--bs-body-bg); + min-width: 0; +} +.general-grid b { + display: block; + color: var(--bs-secondary-color); + font-size: 0.72rem; + text-transform: uppercase; +} +.general-grid span { + overflow-wrap: anywhere; +} +.statusbar { + display: flex; + align-items: center; + gap: 1rem; + padding: 0 0.75rem; + overflow-x: auto; + background: var(--bs-tertiary-bg); + color: var(--bs-secondary-color); + white-space: nowrap; +} +.statusbar b { + color: var(--bs-body-color); +} +.status-limit { + border: 1px solid var(--bs-border-color); + background: rgba(var(--bs-secondary-bg-rgb), 0.9); + color: var(--bs-secondary-color); + border-radius: 0.45rem; + padding: 0.12rem 0.5rem; + white-space: nowrap; +} +.status-limit:hover { + color: var(--bs-body-color); + background: var(--bs-secondary-bg); +} +.ctx-menu { + display: none; + position: absolute; + z-index: 5000; + min-width: 200px; + padding: 0.35rem; + border: 1px solid var(--bs-border-color); + border-radius: 0.6rem; + background: var(--bs-body-bg); +} +.ctx-menu button { + display: block; + width: 100%; + text-align: left; + border: 0; + background: transparent; + color: var(--bs-body-color); + padding: 0.42rem 0.55rem; + border-radius: 0.4rem; +} +.ctx-menu button:hover { + background: var(--bs-secondary-bg); +} +.ctx-menu .danger { + color: var(--bs-danger); +} +.ctx-menu hr { + margin: 0.25rem 0; + border-color: var(--bs-border-color); } -.detail-pane { height: 210px; overflow: auto; padding: .65rem; } -.loading-line { display: flex; align-items: center; gap: .5rem; color: var(--bs-secondary-color); padding: .75rem; } -.muted-pane { color: var(--bs-secondary-color); } -.detail-table { white-space: nowrap; } -.general-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .6rem; } -.general-grid div { border: 1px solid var(--bs-border-color); border-radius: .6rem; padding: .5rem; background: var(--bs-body-bg); min-width: 0; } -.general-grid b { display: block; color: var(--bs-secondary-color); font-size: .72rem; text-transform: uppercase; } -.general-grid span { overflow-wrap: anywhere; } -.statusbar { display: flex; align-items: center; gap: 1rem; padding: 0 .75rem; overflow-x: auto; background: var(--bs-tertiary-bg); color: var(--bs-secondary-color); white-space: nowrap; } -.statusbar b { color: var(--bs-body-color); } -.status-limit { border: 1px solid var(--bs-border-color); background: rgba(var(--bs-secondary-bg-rgb), .9); color: var(--bs-secondary-color); border-radius: .45rem; padding: .12rem .5rem; white-space: nowrap; } -.status-limit:hover { color: var(--bs-body-color); background: var(--bs-secondary-bg); } -.ctx-menu { display: none; position: absolute; z-index: 5000; min-width: 200px; padding: .35rem; border: 1px solid var(--bs-border-color); border-radius: .6rem; background: var(--bs-body-bg); } -.ctx-menu button { display: block; width: 100%; text-align: left; border: 0; background: transparent; color: var(--bs-body-color); padding: .42rem .55rem; border-radius: .4rem; } -.ctx-menu button:hover { background: var(--bs-secondary-bg); } -.ctx-menu .danger { color: var(--bs-danger); } -.ctx-menu hr { margin: .25rem 0; border-color: var(--bs-border-color); } .profile-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; - gap: .25rem .5rem; + gap: 0.25rem 0.5rem; align-items: center; - margin-bottom: .45rem; - padding: .45rem; + margin-bottom: 0.45rem; + padding: 0.45rem; border: 1px solid var(--bs-border-color); - border-radius: .6rem; - background: rgba(var(--bs-secondary-bg-rgb), .58); + border-radius: 0.6rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.58); } .profile-row.active { border-color: var(--bs-primary); @@ -248,23 +480,26 @@ body { .profile-actions, .profile-form-actions { display: inline-flex; - gap: .35rem; + gap: 0.35rem; flex-wrap: wrap; } .profile-form-grid { display: grid; - grid-template-columns: minmax(150px, 1.1fr) minmax(260px, 2.1fr) minmax(90px, .55fr) minmax(120px, .75fr) minmax(145px, auto) auto; - gap: .65rem; + grid-template-columns: minmax(150px, 1.1fr) minmax(260px, 2.1fr) minmax( + 90px, + 0.55fr + ) minmax(120px, 0.75fr) minmax(145px, auto) auto; + gap: 0.65rem; align-items: start; } .profile-form-field { display: grid; - gap: .25rem; + gap: 0.25rem; min-width: 0; } .profile-form-field > span:first-child { color: var(--bs-secondary-color); - font-size: .72rem; + font-size: 0.72rem; font-weight: 700; line-height: 1.1; text-transform: uppercase; @@ -277,114 +512,243 @@ body { min-height: 31px; display: flex; align-items: center; - gap: .45rem; + gap: 0.45rem; +} +.flag-icon { + border-radius: 2px; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12); +} +.flag-code { + color: var(--bs-secondary-color); + margin-left: 0.25rem; +} +.peer-actions { + display: flex; + align-items: center; + gap: 0.25rem; + flex-wrap: nowrap; +} +.peer-actions .btn { + display: inline-flex; + align-items: center; + gap: 0.25rem; + border-radius: 0.35rem !important; +} +.modal-content { + background: var(--bs-body-bg); + border: 1px solid var(--bs-border-color); + border-radius: 14px; +} +.modal-header, +.modal-footer { + background: rgba(var(--bs-secondary-bg-rgb), 0.82); + border-color: var(--bs-border-color); +} +.add-grid { + display: grid; + gap: 0.85rem; +} +.magnet-box { + min-height: 64px; + resize: vertical; +} +.upload-box, +.surface-section { + border: 1px solid var(--bs-border-color); + background: rgba(var(--bs-secondary-bg-rgb), 0.5); + border-radius: 0.75rem; + padding: 0.75rem; +} +.section-title { + font-weight: 700; + margin-bottom: 0.55rem; + color: var(--bs-body-color); +} +.preset-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.4rem; +} +.toast-host { + position: fixed; + right: 14px; + top: 70px; + z-index: 8000; + display: grid; + gap: 0.4rem; +} +.toast-item { + padding: 0.45rem 0.65rem; + border-radius: 0.55rem; + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.28); + max-width: 360px; } -.flag-icon { border-radius: 2px; box-shadow: 0 0 0 1px rgba(255,255,255,.12); } -.flag-code { color: var(--bs-secondary-color); margin-left: .25rem; } -.peer-actions { display: flex; align-items: center; gap: .25rem; flex-wrap: nowrap; } -.peer-actions .btn { display: inline-flex; align-items: center; gap: .25rem; border-radius: .35rem !important; } -.modal-content { background: var(--bs-body-bg); border: 1px solid var(--bs-border-color); border-radius: 14px; } -.modal-header, .modal-footer { background: rgba(var(--bs-secondary-bg-rgb), .82); border-color: var(--bs-border-color); } -.add-grid { display: grid; gap: .85rem; } -.magnet-box { min-height: 64px; resize: vertical; } -.upload-box, .surface-section { border: 1px solid var(--bs-border-color); background: rgba(var(--bs-secondary-bg-rgb), .5); border-radius: .75rem; padding: .75rem; } -.section-title { font-weight: 700; margin-bottom: .55rem; color: var(--bs-body-color); } -.preset-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .4rem; } -.toast-host { position: fixed; right: 14px; top: 70px; z-index: 8000; display: grid; gap: .4rem; } -.toast-item { padding: .45rem .65rem; border-radius: .55rem; box-shadow: 0 8px 25px rgba(0,0,0,.28); max-width: 360px; } @media (max-width: 1100px) { - :root { --topbar: 88px; } - .topbar { align-items: flex-start; flex-wrap: wrap; } - .toolbar-left { flex: 1 1 100%; overflow: visible; flex-wrap: wrap; } - .toolbar-right { flex: 1 1 100%; justify-content: flex-end; } - .search { flex: 1 1 220px; width: auto; min-width: 160px; max-width: none; } + :root { + --topbar: 88px; + } + .topbar { + align-items: flex-start; + flex-wrap: wrap; + } + .toolbar-left { + flex: 1 1 100%; + overflow: visible; + flex-wrap: wrap; + } + .toolbar-right { + flex: 1 1 100%; + justify-content: flex-end; + } + .search { + flex: 1 1 220px; + width: auto; + min-width: 160px; + max-width: none; + } } @media (max-width: 900px) { - :root { --sidebar: 0px; } - .sidebar { display: none; } - .general-grid { grid-template-columns: 1fr; } + :root { + --sidebar: 0px; + } + .sidebar { + display: none; + } + .general-grid { + grid-template-columns: 1fr; + } } @media (max-width: 640px) { - :root { --topbar: 132px; } - .toolbar-right { width: 100%; justify-content: flex-start; flex-wrap: nowrap; gap: .35rem; } - .search { flex: 1 1 0; width: auto; min-width: 0; max-width: none; } - .preset-grid { grid-template-columns: 1fr 1fr; } + :root { + --topbar: 132px; + } + .toolbar-right { + width: 100%; + justify-content: flex-start; + flex-wrap: nowrap; + gap: 0.35rem; + } + .search { + flex: 1 1 0; + width: auto; + min-width: 0; + max-width: none; + } + .preset-grid { + grid-template-columns: 1fr 1fr; + } } - .preferences-grid { display: grid; grid-template-columns: repeat(2, minmax(220px, 1fr)); - gap: .75rem; + gap: 0.75rem; +} +.form-field { + display: grid; + gap: 0.3rem; +} +.form-field > span { + color: var(--bs-secondary-color); + font-size: 0.78rem; + font-weight: 700; + text-transform: uppercase; +} +@media (max-width: 640px) { + .preferences-grid { + grid-template-columns: 1fr; + } } -.form-field { display: grid; gap: .3rem; } -.form-field > span { color: var(--bs-secondary-color); font-size: .78rem; font-weight: 700; text-transform: uppercase; } -@media (max-width: 640px) { .preferences-grid { grid-template-columns: 1fr; } } /* Feature additions without changing the existing visual shell */ .date-compact { white-space: nowrap; } .btn-xs { - --bs-btn-padding-y: .18rem; - --bs-btn-padding-x: .42rem; - --bs-btn-font-size: .78rem; - --bs-btn-border-radius: .35rem; + --bs-btn-padding-y: 0.18rem; + --bs-btn-padding-x: 0.42rem; + --bs-btn-font-size: 0.78rem; + --bs-btn-border-radius: 0.35rem; } .nav-btn { - border-radius: .45rem !important; + border-radius: 0.45rem !important; margin: 0 !important; display: inline-flex; align-items: center; justify-content: center; - gap: .25rem; + gap: 0.25rem; } .nav-btn + .nav-btn, -.torrent-action + .torrent-action { margin-left: .08rem !important; } +.torrent-action + .torrent-action { + margin-left: 0.08rem !important; +} .path-list { height: 360px; overflow: auto; border: 1px solid var(--bs-border-color); - border-radius: .6rem; - background: rgba(var(--bs-secondary-bg-rgb), .35); + border-radius: 0.6rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.35); } .path-row { display: flex; align-items: center; - gap: .5rem; - padding: .42rem .6rem; + gap: 0.5rem; + padding: 0.42rem 0.6rem; border-bottom: 1px solid var(--bs-border-color); cursor: pointer; } -.path-row:hover { background: var(--bs-primary-bg-subtle); color: var(--bs-primary-text-emphasis); } -.chips { display: flex; gap: .35rem; flex-wrap: wrap; } +.path-row:hover { + background: var(--bs-primary-bg-subtle); + color: var(--bs-primary-text-emphasis); +} +.chips { + display: flex; + gap: 0.35rem; + flex-wrap: wrap; +} .chip { border: 1px solid var(--bs-border-color); - background: rgba(var(--bs-secondary-bg-rgb), .6); + background: rgba(var(--bs-secondary-bg-rgb), 0.6); color: var(--bs-body-color); border-radius: 999px; - padding: .22rem .6rem; - font-size: .78rem; + padding: 0.22rem 0.6rem; + font-size: 0.78rem; +} +.mobile-list { + overflow: auto; + padding: 0.55rem; + background: var(--bs-body-bg); } -.mobile-list { overflow: auto; padding: .55rem; background: var(--bs-body-bg); } .mobile-card { border: 1px solid var(--bs-border-color); - background: rgba(var(--bs-secondary-bg-rgb), .72); - border-radius: .75rem; - padding: .65rem; - margin-bottom: .55rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.72); + border-radius: 0.75rem; + padding: 0.65rem; + margin-bottom: 0.55rem; +} +.mobile-card.selected { + outline: 2px solid var(--bs-primary); +} +.mobile-card .name { + font-weight: 700; + word-break: break-word; +} +.mobile-actions { + display: flex; + gap: 0.35rem; + margin-top: 0.45rem; } -.mobile-card.selected { outline: 2px solid var(--bs-primary); } -.mobile-card .name { font-weight: 700; word-break: break-word; } -.mobile-actions { display: flex; gap: .35rem; margin-top: .45rem; } #systemChart { width: 140px; height: 24px; border: 1px solid var(--bs-border-color); - border-radius: .35rem; - background: rgba(var(--bs-secondary-bg-rgb), .85); + border-radius: 0.35rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.85); +} +.badge-degraded { + background: #f59e0b !important; + color: #111 !important; } -.badge-degraded { background: #f59e0b !important; color: #111 !important; } /* Note: Manual mobile mode is defined once here; media queries below only adapt breakpoints. */ body.mobile-mode .table-wrap, body.mobile-mode .details { @@ -405,41 +769,76 @@ body.mobile-mode .content { min-height: 0; overflow: hidden; } -body.mobile-mode .torrent-table { display: none; } +body.mobile-mode .torrent-table { + display: none; +} body.mobile-mode .main-grid { min-height: 0; overflow: hidden; } @media (max-width: 640px) { - .nav-btn span { display: none; } + .nav-btn span { + display: none; + } } /* Fixes: compact one-line progress cell and readable percent inside the bar. */ -.torrent-table td:nth-child(5) { min-width: 92px; width: 110px; white-space: nowrap; } -.hidden-col{display:none!important} -.status-docs{margin-left:auto;color:inherit;text-decoration:none;font-weight:600;opacity:.9;white-space:nowrap} -.status-docs:hover{opacity:1;text-decoration:underline} -.column-check{padding:.35rem .5rem;border:1px solid var(--bs-border-color);border-radius:.5rem;background:var(--bs-body-bg)} -.label-filters .label-filter{font-size:.82rem;padding:.34rem .5rem;margin-bottom:.15rem} -.label-filters .label-filter i{opacity:.75;margin-right:.25rem} +.torrent-table td:nth-child(5) { + min-width: 92px; + width: 110px; + white-space: nowrap; +} +.hidden-col { + display: none !important; +} +.status-docs { + margin-left: auto; + color: inherit; + text-decoration: none; + font-weight: 600; + opacity: 0.9; + white-space: nowrap; +} +.status-docs:hover { + opacity: 1; + text-decoration: underline; +} +.column-check { + padding: 0.35rem 0.5rem; + border: 1px solid var(--bs-border-color); + border-radius: 0.5rem; + background: var(--bs-body-bg); +} +.label-filters .label-filter { + font-size: 0.82rem; + padding: 0.34rem 0.5rem; + margin-bottom: 0.15rem; +} +.label-filters .label-filter i { + opacity: 0.75; + margin-right: 0.25rem; +} .column-manager { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); - gap: .55rem; + gap: 0.55rem; } .column-card { display: flex; align-items: center; - gap: .55rem; + gap: 0.55rem; margin: 0; - padding: .55rem .65rem; + padding: 0.55rem 0.65rem; border: 1px solid var(--bs-border-color); - border-radius: .7rem; - background: rgba(var(--bs-secondary-bg-rgb), .45); + border-radius: 0.7rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.45); cursor: pointer; user-select: none; - transition: background .15s, border-color .15s, transform .15s; + transition: + background 0.15s, + border-color 0.15s, + transform 0.15s; } .column-card:hover, @@ -452,7 +851,7 @@ body.mobile-mode .main-grid { } .column-card.active { - border-color: rgba(var(--bs-primary-rgb), .55); + border-color: rgba(var(--bs-primary-rgb), 0.55); } .column-card .form-check-input { @@ -462,19 +861,46 @@ body.mobile-mode .main-grid { .column-card .form-check-label { display: flex; align-items: center; - gap: .45rem; + gap: 0.45rem; font-weight: 600; } .column-card i { - opacity: .72; + opacity: 0.72; +} +.path-row::before { + content: "\f07b"; + font-family: "Font Awesome 6 Free"; + font-weight: 900; + color: var(--bs-warning); +} +body.mobile-mode .mobile-card { + display: block; +} +.mobile-card .mobile-actions button { + min-width: 34px; +} +#toolSmart .form-label { + font-size: 0.75rem; + color: var(--bs-secondary-color); + margin-bottom: 0.2rem; +} +#toolSmart .btn { + padding: 0.25rem 0.55rem; + border-radius: 0.5rem; + white-space: nowrap; +} +#toolSmart .row .d-flex { + align-items: end; + justify-content: flex-start; +} +#trafficHistoryChart { + width: 100%; + height: 420px; + border: 1px solid var(--bs-border-color); + border-radius: 0.75rem; + background: var(--bs-body-bg); } -.path-row::before{content:'\f07b';font-family:'Font Awesome 6 Free';font-weight:900;color:var(--bs-warning)} -body.mobile-mode .mobile-card{display:block}.mobile-card .mobile-actions button{min-width:34px} -#toolSmart .form-label{font-size:.75rem;color:var(--bs-secondary-color);margin-bottom:.2rem} -#toolSmart .btn{padding:.25rem .55rem;border-radius:.5rem;white-space:nowrap} -#toolSmart .row .d-flex{align-items:end;justify-content:flex-start} -#trafficHistoryChart{width:100%;height:420px;border:1px solid var(--bs-border-color);border-radius:.75rem;background:var(--bs-body-bg)} @media (max-width: 992px) { .profile-form-grid { grid-template-columns: 1fr; @@ -485,41 +911,188 @@ body.mobile-mode .mobile-card{display:block}.mobile-card .mobile-actions button{ } /* Requested fixes: stable charts, Smart Queue exceptions, label actions, mobile readability */ -.history-grid{display:grid;grid-template-columns:1fr;gap:1rem} -.history-card{border:1px solid var(--bs-border-color);border-radius:.8rem;background:rgba(var(--bs-secondary-bg-rgb),.35);padding:.75rem;min-width:0;overflow:hidden} -.history-title{font-weight:700;font-size:.9rem;margin-bottom:.45rem;color:var(--bs-body-color)} -#trafficHistoryChart,#trafficSpeedChart{display:block;width:100%;height:420px;max-width:100%;border:0;border-radius:.55rem;background:var(--bs-body-bg)} -@media (min-width: 992px){.history-grid{grid-template-columns:1fr}} -.smart-actions{display:flex;align-items:center;gap:.45rem;flex-wrap:wrap} -.empty-mini{padding:.7rem .8rem;border:1px dashed var(--bs-border-color);border-radius:.7rem;color:var(--bs-secondary-color);background:rgba(var(--bs-secondary-bg-rgb),.35)} -.label-manager-row{display:flex;align-items:center;justify-content:space-between;gap:.5rem;border:1px solid var(--bs-border-color);border-radius:.65rem;padding:.4rem .5rem;margin-bottom:.4rem;background:rgba(var(--bs-secondary-bg-rgb),.35)} -.tool-tab i{margin-right:.25rem;opacity:.82} -@media (max-width:640px){.history-card{padding:.5rem}#trafficHistoryChart,#trafficSpeedChart{height:320px}.statusbar{font-size:.75rem;gap:.6rem}.mobile-list{padding:.45rem}.mobile-card{margin-bottom:.45rem}} +.history-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; +} +.history-card { + border: 1px solid var(--bs-border-color); + border-radius: 0.8rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.35); + padding: 0.75rem; + min-width: 0; + overflow: hidden; +} +.history-title { + font-weight: 700; + font-size: 0.9rem; + margin-bottom: 0.45rem; + color: var(--bs-body-color); +} +#trafficHistoryChart, +#trafficSpeedChart { + display: block; + width: 100%; + height: 420px; + max-width: 100%; + border: 0; + border-radius: 0.55rem; + background: var(--bs-body-bg); +} +@media (min-width: 992px) { + .history-grid { + grid-template-columns: 1fr; + } +} +.smart-actions { + display: flex; + align-items: center; + gap: 0.45rem; + flex-wrap: wrap; +} +.empty-mini { + padding: 0.7rem 0.8rem; + border: 1px dashed var(--bs-border-color); + border-radius: 0.7rem; + color: var(--bs-secondary-color); + background: rgba(var(--bs-secondary-bg-rgb), 0.35); +} +.label-manager-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + border: 1px solid var(--bs-border-color); + border-radius: 0.65rem; + padding: 0.4rem 0.5rem; + margin-bottom: 0.4rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.35); +} +.tool-tab i { + margin-right: 0.25rem; + opacity: 0.82; +} +@media (max-width: 640px) { + .history-card { + padding: 0.5rem; + } + #trafficHistoryChart, + #trafficSpeedChart { + height: 320px; + } + .statusbar { + font-size: 0.75rem; + gap: 0.6rem; + } + .mobile-list { + padding: 0.45rem; + } + .mobile-card { + margin-bottom: 0.45rem; + } +} /* Requested fixes: clean progress, mobile auto list, pagers, rTorrent config, peers refresh */ -.torrent-progress{height:16px;min-width:92px;position:relative;margin:0;overflow:hidden;background:rgba(var(--bs-secondary-bg-rgb),.8)!important} -.torrent-progress .progress-bar{min-width:0!important;position:relative;transition:width .25s ease,background-color .25s ease} -.torrent-progress>span{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;line-height:1;color:var(--bs-body-color);text-shadow:none;white-space:nowrap;pointer-events:none} -.torrent-progress .progress-bar+span{color:var(--bs-body-color)} -@media (max-width:700px){ - body:not(.desktop-mode) .table-wrap{display:none!important} - body:not(.desktop-mode) #mobileList{display:block!important;min-height:260px;height:100%;overflow:auto} - body:not(.desktop-mode) .content{display:grid!important;grid-template-rows:minmax(0,1fr)!important;min-height:0;overflow:hidden} - body:not(.desktop-mode) .details{display:none!important} +.torrent-progress { + height: 16px; + min-width: 92px; + position: relative; + margin: 0; + overflow: hidden; + background: rgba(var(--bs-secondary-bg-rgb), 0.8) !important; +} +.torrent-progress .progress-bar { + min-width: 0 !important; + position: relative; + transition: + width 0.25s ease, + background-color 0.25s ease; +} +.torrent-progress > span { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: 700; + line-height: 1; + color: var(--bs-body-color); + text-shadow: none; + white-space: nowrap; + pointer-events: none; +} +.torrent-progress .progress-bar + span { + color: var(--bs-body-color); +} +@media (max-width: 700px) { + body:not(.desktop-mode) .table-wrap { + display: none !important; + } + body:not(.desktop-mode) #mobileList { + display: block !important; + min-height: 260px; + height: 100%; + overflow: auto; + } + body:not(.desktop-mode) .content { + display: grid !important; + grid-template-rows: minmax(0, 1fr) !important; + min-height: 0; + overflow: hidden; + } + body:not(.desktop-mode) .details { + display: none !important; + } +} +.pager-row { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 0.5rem; +} +.peers-refresh { + display: flex; + align-items: center; + gap: 0.5rem; + justify-content: flex-end; + padding: 0.35rem 0.75rem; + border-bottom: 1px solid var(--bs-border-color); + background: rgba(var(--bs-secondary-bg-rgb), 0.35); +} +.peers-refresh select { + width: auto; + min-width: 96px; } -.pager-row{display:flex;align-items:center;justify-content:flex-end;gap:.5rem} -.peers-refresh{display:flex;align-items:center;gap:.5rem;justify-content:flex-end;padding:.35rem .75rem;border-bottom:1px solid var(--bs-border-color);background:rgba(var(--bs-secondary-bg-rgb),.35)} -.peers-refresh select{width:auto;min-width:96px} /* Mobile list: force visible on narrow screens even without manual toggle. */ @media (max-width: 900px) { - body:not(.modal-open) .table-wrap { display: none !important; } - body:not(.modal-open) #mobileList { display: block !important; height: 100% !important; min-height: 260px; overflow: auto; } - body:not(.modal-open) .content { display: grid !important; grid-template-rows: minmax(0,1fr) !important; min-height: 0; overflow: hidden; } - body:not(.modal-open) .details { display: none !important; } + body:not(.modal-open) .table-wrap { + display: none !important; + } + body:not(.modal-open) #mobileList { + display: block !important; + height: 100% !important; + min-height: 260px; + overflow: auto; + } + body:not(.modal-open) .content { + display: grid !important; + grid-template-rows: minmax(0, 1fr) !important; + min-height: 0; + overflow: hidden; + } + body:not(.modal-open) .details { + display: none !important; + } +} +.torrent-paused td { + opacity: 0.82; +} +.torrent-paused .name { + font-style: italic; } -.torrent-paused td{opacity:.82} -.torrent-paused .name{font-style:italic} /* Mobile blank-view fix: sidebar disappears at 900px, so the mobile list must also be forced from 900px down. */ @media (max-width: 900px) { @@ -530,7 +1103,9 @@ body.mobile-mode .mobile-card{display:block}.mobile-card .mobile-actions button{ height: 100% !important; overflow: hidden !important; } - .sidebar { display: none !important; } + .sidebar { + display: none !important; + } .content { display: grid !important; grid-template-rows: minmax(0, 1fr) !important; @@ -538,7 +1113,9 @@ body.mobile-mode .mobile-card{display:block}.mobile-card .mobile-actions button{ height: 100% !important; overflow: hidden !important; } - .table-wrap { display: none !important; } + .table-wrap { + display: none !important; + } #mobileList { display: block !important; height: 100% !important; @@ -547,80 +1124,235 @@ body.mobile-mode .mobile-card{display:block}.mobile-card .mobile-actions button{ position: relative !important; z-index: 10 !important; background: var(--bs-body-bg) !important; - padding: .55rem !important; + padding: 0.55rem !important; + } + .details { + display: none !important; + } + .toolbar-right { + width: 100% !important; + min-width: 0 !important; + flex-wrap: nowrap !important; + gap: 0.35rem !important; + } + .search { + min-width: 0 !important; + width: auto !important; + flex: 1 1 0 !important; + max-width: none !important; + } + .mobile-speed-stats { + display: inline-flex; } - .details { display: none !important; } - .toolbar-right { width: 100% !important; min-width: 0 !important; flex-wrap: nowrap !important; gap: .35rem !important; } - .search { min-width: 0 !important; width: auto !important; flex: 1 1 0 !important; max-width: none !important; } - .mobile-speed-stats { display: inline-flex; } } @media (max-width: 640px) { - .toolbar-right { flex-wrap: nowrap !important; gap: .3rem !important; } - .search { min-width: 0 !important; width: auto !important; flex: 1 1 0 !important; max-width: none !important; } - .mobile-speed-stats { gap: .25rem; font-size: .66rem; } + .toolbar-right { + flex-wrap: nowrap !important; + gap: 0.3rem !important; + } + .search { + min-width: 0 !important; + width: auto !important; + flex: 1 1 0 !important; + max-width: none !important; + } + .mobile-speed-stats { + gap: 0.25rem; + font-size: 0.66rem; + } } -.files-toolbar{display:flex;gap:.75rem;align-items:center;justify-content:space-between;flex-wrap:wrap;margin-bottom:.5rem} -.file-priority-table .path{max-width:520px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} -.file-priority-table .file-priority{min-width:110px} -@media (max-width:900px){.files-toolbar{align-items:stretch}.files-toolbar .btn-group{display:grid;grid-template-columns:1fr;width:100%}.file-priority-table{font-size:.82rem}.file-priority-table .path{max-width:180px}} +.files-toolbar { + display: flex; + gap: 0.75rem; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + margin-bottom: 0.5rem; +} +.file-priority-table .path { + max-width: 520px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.file-priority-table .file-priority { + min-width: 110px; +} +@media (max-width: 900px) { + .files-toolbar { + align-items: stretch; + } + .files-toolbar .btn-group { + display: grid; + grid-template-columns: 1fr; + width: 100%; + } + .file-priority-table { + font-size: 0.82rem; + } + .file-priority-table .path { + max-width: 180px; + } +} .bulk-bar { height: 38px; display: flex; align-items: center; - gap: .35rem; + gap: 0.35rem; flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; - padding: .35rem .55rem; + padding: 0.35rem 0.55rem; border-bottom: 1px solid var(--bs-border-color); - background: rgba(var(--bs-secondary-bg-rgb), .95); + background: rgba(var(--bs-secondary-bg-rgb), 0.95); z-index: 4; } -.bulk-bar.d-none { display: none !important; } -.bulk-bar span { color: var(--bs-secondary-color); margin-right: .3rem; white-space: nowrap; } -.bulk-bar .btn { white-space: nowrap; flex: 0 0 auto; } +.bulk-bar.d-none { + display: none !important; +} +.bulk-bar span { + color: var(--bs-secondary-color); + margin-right: 0.3rem; + white-space: nowrap; +} +.bulk-bar .btn { + white-space: nowrap; + flex: 0 0 auto; +} .move-options { border: 1px solid var(--bs-border-color); - border-radius: .6rem; - padding: .75rem; + border-radius: 0.6rem; + padding: 0.75rem; background: var(--bs-tertiary-bg); } /* Note: Bulk actions overlay the list area; base .content/.details rules keep the layout pinned. */ -#bulkBar { grid-row: 1; grid-column: 1; align-self: start; } -#tableWrap, #mobileList { grid-row: 1; grid-column: 1; min-height: 0; } -.bulk-bar:not(.d-none) + .table-wrap { padding-top: 38px; } +#bulkBar { + grid-row: 1; + grid-column: 1; + align-self: start; +} +#tableWrap, +#mobileList { + grid-row: 1; + grid-column: 1; + min-height: 0; +} +.bulk-bar:not(.d-none) + .table-wrap { + padding-top: 38px; +} @media (max-width: 900px) { - .bulk-bar { gap: .3rem; } + .bulk-bar { + gap: 0.3rem; + } } +.label-mini { + font-size: 0.72rem; + padding: 0.12rem 0.38rem; + margin-right: 0.15rem; +} +.label-chip.active { + border-color: var(--bs-primary); + background: var(--bs-primary-bg-subtle); + color: var(--bs-primary-text-emphasis); +} +.label-selected { + border-color: var(--bs-primary); + background: var(--bs-primary-bg-subtle); + color: var(--bs-primary-text-emphasis); +} -.label-mini{font-size:.72rem;padding:.12rem .38rem;margin-right:.15rem} -.label-chip.active{border-color:var(--bs-primary);background:var(--bs-primary-bg-subtle);color:var(--bs-primary-text-emphasis)} -.label-selected{border-color:var(--bs-primary);background:var(--bs-primary-bg-subtle);color:var(--bs-primary-text-emphasis)} +.automation-form-grid { + display: grid; + grid-template-columns: repeat(4, minmax(160px, 1fr)); + gap: 0.5rem; + align-items: center; +} +.automation-row { + display: flex; + justify-content: space-between; + gap: 0.75rem; + align-items: center; + padding: 0.55rem 0.65rem; + border: 1px solid var(--bs-border-color); + border-radius: 0.6rem; + margin-bottom: 0.45rem; + background: var(--bs-body-bg); +} +@media (max-width: 900px) { + .automation-form-grid { + grid-template-columns: 1fr; + } +} +.disk-status { + display: inline-flex; + align-items: center; + gap: 0.35rem; + min-width: 110px; +} +.disk-status canvas { + border-radius: 999px; + background: rgba(var(--bs-secondary-bg-rgb), 0.65); +} +.disk-status.disk-warn b { + color: var(--bs-warning) !important; +} -.automation-form-grid { display:grid; grid-template-columns: repeat(4, minmax(160px, 1fr)); gap:.5rem; align-items:center; } -.automation-row { display:flex; justify-content:space-between; gap:.75rem; align-items:center; padding:.55rem .65rem; border:1px solid var(--bs-border-color); border-radius:.6rem; margin-bottom:.45rem; background:var(--bs-body-bg); } -@media (max-width: 900px){ .automation-form-grid { grid-template-columns: 1fr; } } -.disk-status{display:inline-flex;align-items:center;gap:.35rem;min-width:110px} -.disk-status canvas{border-radius:999px;background:rgba(var(--bs-secondary-bg-rgb),.65)} -.disk-status.disk-warn b{color:var(--bs-warning)!important} - -.system-chart{width:96px;height:24px;border-radius:.35rem;background:rgba(var(--bs-secondary-bg-rgb),.45)} -.torrent-progress.is-complete>span{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.35)} -.peer-progress{min-width:86px;width:96px} -.loading-center{justify-content:center;min-height:80px} -.loading-cell{padding:0!important} -.mobile-list .loading-center{min-height:160px} +.system-chart { + width: 96px; + height: 24px; + border-radius: 0.35rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.45); +} +.torrent-progress.is-complete > span { + color: #fff; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35); +} +.peer-progress { + min-width: 86px; + width: 96px; +} +.loading-center { + justify-content: center; + min-height: 80px; +} +.loading-cell { + padding: 0 !important; +} +.mobile-list .loading-center { + min-height: 160px; +} /* Torrent warning and mobile controls */ -.torrent-warning td { background: rgba(245, 158, 11, .075) !important; } -.torrent-warning:hover td { background: rgba(245, 158, 11, .11) !important; } -.torrent-warning.selected td { background: color-mix(in srgb, var(--bs-primary-bg-subtle) 82%, rgba(245, 158, 11, .16)) !important; } -.mobile-card.torrent-warning { background: rgba(245, 158, 11, .075); } -.mobile-card.torrent-warning.selected { background: color-mix(in srgb, var(--bs-primary-bg-subtle) 82%, rgba(245, 158, 11, .16)); } -.torrent-warning-icon { color: var(--bs-warning); margin-right: .2rem; } +.torrent-warning td { + background: rgba(245, 158, 11, 0.075) !important; +} +.torrent-warning:hover td { + background: rgba(245, 158, 11, 0.11) !important; +} +.torrent-warning.selected td { + background: color-mix( + in srgb, + var(--bs-primary-bg-subtle) 82%, + rgba(245, 158, 11, 0.16) + ) !important; +} +.mobile-card.torrent-warning { + background: rgba(245, 158, 11, 0.075); +} +.mobile-card.torrent-warning.selected { + background: color-mix( + in srgb, + var(--bs-primary-bg-subtle) 82%, + rgba(245, 158, 11, 0.16) + ); +} +.torrent-warning-icon { + color: var(--bs-warning); + margin-right: 0.2rem; +} .mobile-filter-bar { display: none; grid-row: 1; @@ -629,82 +1361,98 @@ body.mobile-mode .mobile-card{display:block}.mobile-card .mobile-actions button{ position: sticky; top: 0; z-index: 12; - padding: .45rem .55rem; + padding: 0.45rem 0.55rem; border-bottom: 1px solid var(--bs-border-color); - background: rgba(var(--bs-body-bg-rgb), .96); + background: rgba(var(--bs-body-bg-rgb), 0.96); } .mobile-filter-actions, .mobile-filter-select-row { display: flex; align-items: center; - gap: .35rem; + gap: 0.35rem; +} +.mobile-filter-actions { + margin-bottom: 0.4rem; +} +.mobile-filter-actions span { + color: var(--bs-secondary-color); + font-size: 0.78rem; + white-space: nowrap; } -.mobile-filter-actions { margin-bottom: .4rem; } -.mobile-filter-actions span { color: var(--bs-secondary-color); font-size: .78rem; white-space: nowrap; } .mobile-filter-select-row label { color: var(--bs-secondary-color); - font-size: .78rem; + font-size: 0.78rem; white-space: nowrap; } .mobile-filter-select-row select { min-width: 0; flex: 1 1 auto; } -body.mobile-mode .mobile-filter-bar { display: block !important; } -body.mobile-mode #mobileList { padding-top: 5.2rem !important; } +body.mobile-mode .mobile-filter-bar { + display: block !important; +} +body.mobile-mode #mobileList { + padding-top: 5.2rem !important; +} @media (max-width: 900px) { - #mobileFilterBar { display: block !important; } - #mobileList { padding-top: 5.2rem !important; } + #mobileFilterBar { + display: block !important; + } + #mobileList { + padding-top: 5.2rem !important; + } .topbar .badge { - width: .72rem; - height: .72rem; - min-width: .72rem; + width: 0.72rem; + height: 0.72rem; + min-width: 0.72rem; padding: 0 !important; border-radius: 999px; overflow: hidden; color: transparent !important; text-indent: -999px; - box-shadow: 0 0 0 1px rgba(255,255,255,.22); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.22); + } + .topbar .badge .spinner-border { + display: none; } - .topbar .badge .spinner-border { display: none; } } /* rTorrent config */ .rt-config-grid { display: grid; - gap: .6rem; + gap: 0.6rem; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); } .rt-config-group { grid-column: 1 / -1; - padding: .45rem .2rem .1rem; + padding: 0.45rem 0.2rem 0.1rem; border-bottom: 1px solid var(--bs-border-color); color: var(--bs-primary-text-emphasis); font-weight: 800; } .rt-config-note { - margin-bottom: .75rem; + margin-bottom: 0.75rem; } .rt-config-toolbar { display: flex; align-items: center; flex-wrap: wrap; - gap: .75rem; - margin-bottom: .75rem; + gap: 0.75rem; + margin-bottom: 0.75rem; } .rt-config-row { display: grid; grid-template-columns: 1fr minmax(120px, 190px); align-items: center; - gap: .6rem; - padding: .6rem; + gap: 0.6rem; + padding: 0.6rem; border: 1px solid var(--bs-border-color); - border-radius: .7rem; - background: rgba(var(--bs-secondary-bg-rgb), .35); + border-radius: 0.7rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.35); } .rt-config-switch { @@ -719,38 +1467,38 @@ body.mobile-mode #mobileList { padding-top: 5.2rem !important; } .rt-config-switch .form-check-label { min-width: 2rem; color: var(--bs-secondary-color); - font-size: .78rem; + font-size: 0.78rem; font-weight: 700; } .rt-config-row b { - font-size: .88rem; + font-size: 0.88rem; } .rt-config-row small { display: block; overflow-wrap: anywhere; color: var(--bs-secondary-color); - font-size: .72rem; + font-size: 0.72rem; } .rt-config-row.disabled { - opacity: .58; + opacity: 0.58; } .rt-config-row.changed, .rt-config-row.changed-live { border-color: var(--bs-danger); - box-shadow: 0 0 0 .12rem rgba(220, 53, 69, .2); + box-shadow: 0 0 0 0.12rem rgba(220, 53, 69, 0.2); } .rt-config-value-note { - margin-top: .15rem; + margin-top: 0.15rem; } .rt-config-output { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; - font-size: .82rem; + font-size: 0.82rem; } /* Tracker management */ @@ -759,12 +1507,12 @@ body.mobile-mode #mobileList { padding-top: 5.2rem !important; } display: flex; align-items: center; flex-wrap: wrap; - gap: .45rem; + gap: 0.45rem; } .tracker-toolbar { justify-content: space-between; - margin-bottom: .55rem; + margin-bottom: 0.55rem; } .tracker-url { @@ -785,30 +1533,30 @@ body.mobile-mode #mobileList { padding-top: 5.2rem !important; } /* Cleanup and app diagnostics */ .tool-note { color: var(--bs-secondary-color); - font-size: .82rem; + font-size: 0.82rem; } .cleanup-grid, .diag-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); - gap: .6rem; + gap: 0.6rem; } .cleanup-card, .diag-card { - padding: .65rem; + padding: 0.65rem; border: 1px solid var(--bs-border-color); - border-radius: .7rem; - background: rgba(var(--bs-secondary-bg-rgb), .35); + border-radius: 0.7rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.35); } .cleanup-card b, .diag-card b { display: block; - margin-bottom: .2rem; + margin-bottom: 0.2rem; color: var(--bs-secondary-color); - font-size: .78rem; + font-size: 0.78rem; } .cleanup-card span, @@ -818,7 +1566,7 @@ body.mobile-mode #mobileList { padding-top: 5.2rem !important; } .cleanup-card small { display: block; - margin-top: .2rem; + margin-top: 0.2rem; overflow-wrap: anywhere; color: var(--bs-secondary-color); } @@ -826,53 +1574,53 @@ body.mobile-mode #mobileList { padding-top: 5.2rem !important; } .cleanup-actions { display: flex; flex-wrap: wrap; - gap: .5rem; + gap: 0.5rem; } .diag-error { - border-color: rgba(var(--bs-danger-rgb), .45); - background: rgba(var(--bs-danger-rgb), .08); + border-color: rgba(var(--bs-danger-rgb), 0.45); + background: rgba(var(--bs-danger-rgb), 0.08); } .port-status { display: inline-flex; align-items: center; - gap: .3rem; - padding: .12rem .4rem; - border-radius: .45rem; + gap: 0.3rem; + padding: 0.12rem 0.4rem; + border-radius: 0.45rem; } .port-ok { - background: rgba(34, 197, 94, .14); + background: rgba(34, 197, 94, 0.14); color: var(--bs-success); } .port-bad { - background: rgba(239, 68, 68, .14); + background: rgba(239, 68, 68, 0.14); color: var(--bs-danger); } .port-secondary { - background: rgba(148, 163, 184, .12); + background: rgba(148, 163, 184, 0.12); color: var(--bs-secondary-color); } .limit-slider-panel { - padding: .65rem; + padding: 0.65rem; border: 1px solid var(--bs-border-color); - border-radius: .7rem; - background: rgba(var(--bs-secondary-bg-rgb), .32); + border-radius: 0.7rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.32); } .limit-slider-row + .limit-slider-row { - margin-top: .65rem; + margin-top: 0.65rem; } .limit-slider-row .form-label { display: flex; justify-content: space-between; - gap: .75rem; - margin-bottom: .25rem; + gap: 0.75rem; + margin-bottom: 0.25rem; } @media (max-width: 640px) { @@ -900,24 +1648,32 @@ body.mobile-mode #mobileList { padding-top: 5.2rem !important; } /* Operation status, mobile progress and separated preferences */ .torrent-operating td { - background: rgba(13, 202, 240, .085) !important; + background: rgba(13, 202, 240, 0.085) !important; } .torrent-operating:hover td { - background: rgba(13, 202, 240, .13) !important; + background: rgba(13, 202, 240, 0.13) !important; } .torrent-operating.selected td { - background: color-mix(in srgb, var(--bs-primary-bg-subtle) 78%, rgba(13, 202, 240, .18)) !important; + background: color-mix( + in srgb, + var(--bs-primary-bg-subtle) 78%, + rgba(13, 202, 240, 0.18) + ) !important; } .mobile-card.torrent-operating { - background: rgba(13, 202, 240, .085); - border-color: rgba(13, 202, 240, .45); + background: rgba(13, 202, 240, 0.085); + border-color: rgba(13, 202, 240, 0.45); } .mobile-card.torrent-operating.selected { - background: color-mix(in srgb, var(--bs-primary-bg-subtle) 78%, rgba(13, 202, 240, .18)); + background: color-mix( + in srgb, + var(--bs-primary-bg-subtle) 78%, + rgba(13, 202, 240, 0.18) + ); } .operation-status-badge { @@ -925,7 +1681,7 @@ body.mobile-mode #mobileList { padding-top: 5.2rem !important; } } .mobile-progress { - margin-top: .45rem; + margin-top: 0.45rem; } .mobile-progress .torrent-progress { @@ -939,7 +1695,7 @@ body.mobile-mode #mobileList { padding-top: 5.2rem !important; } } .preference-section { - border-left: .25rem solid var(--bs-primary); + border-left: 0.25rem solid var(--bs-primary); } /* Note: Empty first-run state is grouped separately to keep setup styles isolated and avoid duplicated table overrides. */ @@ -947,13 +1703,13 @@ body.mobile-mode #mobileList { padding-top: 5.2rem !important; } display: inline-flex; flex-direction: column; align-items: center; - gap: .45rem; + gap: 0.45rem; max-width: 34rem; white-space: normal; } .empty-state b { color: var(--bs-body-color); - font-size: .95rem; + font-size: 0.95rem; } .empty-state span { color: var(--bs-secondary-color); @@ -967,23 +1723,25 @@ body.mobile-mode #mobileList { padding-top: 5.2rem !important; } .footer-preferences { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: .5rem; + gap: 0.5rem; } /* Note: Footer switch cards mirror column cards so Bootstrap form-switch margins cannot push toggles outside the field. */ .footer-pref-card { display: flex; align-items: center; - gap: .55rem; + gap: 0.55rem; min-width: 0; margin: 0; - padding: .6rem .7rem; + padding: 0.6rem 0.7rem; border: 1px solid var(--bs-border-color); - border-radius: .75rem; - background: rgba(var(--bs-secondary-bg-rgb), .45); + border-radius: 0.75rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.45); cursor: pointer; user-select: none; - transition: background .15s, border-color .15s; + transition: + background 0.15s, + border-color 0.15s; } .footer-pref-card:hover, @@ -996,7 +1754,7 @@ body.mobile-mode #mobileList { padding-top: 5.2rem !important; } } .footer-pref-card.active { - border-color: rgba(var(--bs-primary-rgb), .55); + border-color: rgba(var(--bs-primary-rgb), 0.55); } .footer-pref-card .form-check-input { @@ -1016,4 +1774,3 @@ body.mobile-mode #mobileList { padding-top: 5.2rem !important; } #statusSockets { white-space: nowrap; } -