diff --git a/packaging/dev.moku.app.metainfo.xml b/packaging/dev.moku.app.metainfo.xml index 3cd8748..f362f7a 100644 --- a/packaging/dev.moku.app.metainfo.xml +++ b/packaging/dev.moku.app.metainfo.xml @@ -27,9 +27,9 @@ - + -

Initial release.

+

Svelte rewrite with improved UI, bundled server, and cross-platform fixes.

diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 7f960aa..35e3e04 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -8,6 +8,23 @@ "shell:allow-open", "shell:allow-kill", "shell:allow-spawn", - "shell:allow-execute" + "shell:allow-execute", + "core:window:allow-minimize", + "core:window:allow-unminimize", + "core:window:allow-maximize", + "core:window:allow-unmaximize", + "core:window:allow-toggle-maximize", + "core:window:allow-close", + "core:window:allow-start-dragging", + "core:window:allow-set-focus", + "core:window:allow-set-fullscreen", + "core:window:allow-is-fullscreen", + "core:window:allow-is-maximized", + "core:window:allow-is-minimized", + "core:window:allow-inner-size", + "core:window:allow-outer-size", + "core:window:allow-inner-position", + "core:window:allow-outer-position", + "core:window:allow-scale-factor" ] } diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png index 7fb3c77..35f0692 100644 Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png index 001e4e8..8955a67 100644 Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png index a296b8c..74a383b 100644 Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png index 801eea6..c881f74 100644 Binary files a/src-tauri/icons/64x64.png and b/src-tauri/icons/64x64.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png index b2f77f2..8f534b7 100644 Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png index d2961aa..6e07c88 100644 Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png index f1db733..c676225 100644 Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png index 45a6b8f..a73a0b5 100644 Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png index 136d4a3..46f5d2f 100644 Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png index 864fd32..5569ac1 100644 Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png index b26b59c..ae1060e 100644 Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png index 2b67df8..b1ddb08 100644 Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png index 9e9888b..9c86eac 100644 Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png index 82e9e9e..c2dd29a 100644 Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns index 0bf6444..df5bf1f 100644 Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index af8bd84..3e1959e 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png index 74fbc6d..17c701d 100644 Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 28777b8..8560203 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -83,8 +83,13 @@ fn get_storage_info(downloads_path: String) -> Result { } #[tauri::command] -fn get_scale_factor(window: tauri::Window) -> f64 { - window.scale_factor().unwrap_or(1.0) +fn get_platform_ui_scale() -> f64 { + #[cfg(target_os = "windows")] + return 1.0; + #[cfg(target_os = "macos")] + return 1.0; + #[cfg(not(any(target_os = "windows", target_os = "macos")))] + return 1.5; } fn kill_tachidesk(app: &tauri::AppHandle) { @@ -249,51 +254,34 @@ fn resolve_server_binary( #[cfg(not(target_os = "macos"))] { - // Tauri 2 resource bundling behaviour depends on the config: - // - Structured layout: resource_dir/binaries/suwayomi-bundle/{bin,jre}/... - // - Flat layout: resource_dir/{java.exe,Suwayomi-Server.jar,...} - // We try both so the binary works regardless of which layout the installer produced. - let search_candidates: &[(&str, &str)] = &[ - // Structured — what the config intends - ("binaries/suwayomi-bundle", "binaries/suwayomi-bundle/bin/Suwayomi-Server.jar"), - // Flat — what Tauri 2 actually produces with glob resources - ("", "Suwayomi-Server.jar"), - ]; + let bundle_dir = resource_dir.join("binaries").join("suwayomi-bundle"); + let jar = bundle_dir.join("bin").join("Suwayomi-Server.jar"); - for (bundle_rel, jar_rel) in search_candidates { - let bundle_dir = if bundle_rel.is_empty() { - resource_dir.clone() - } else { - resource_dir.join(bundle_rel) - }; - let jar = resource_dir.join(jar_rel); + do_log(log, &format!("[resolve] bundle_dir = {:?}", bundle_dir)); + do_log(log, &format!("[resolve] bundle_dir exists: {}", bundle_dir.exists())); + do_log(log, &format!("[resolve] jar = {:?}", jar)); + do_log(log, &format!("[resolve] jar exists: {}", jar.exists())); - do_log(log, &format!("[resolve] trying bundle_dir = {:?}", bundle_dir)); - do_log(log, &format!("[resolve] bundle_dir exists: {}", bundle_dir.exists())); - do_log(log, &format!("[resolve] jar = {:?}", jar)); - do_log(log, &format!("[resolve] jar exists: {}", jar.exists())); - - match find_java_in_bundle(&bundle_dir, log) { - Some(java) => { - do_log(log, &format!("[resolve] java found: {:?}", java)); - if jar.exists() { - do_log(log, "[resolve] both java and jar found — using bundled JRE"); - return Ok(ServerInvocation { - bin: java.to_string_lossy().into_owned(), - args: vec![ - "-jar".to_string(), - jar.to_string_lossy().into_owned(), - ], - working_dir: Some(bundle_dir), - }); - } else { - do_log(log, "[resolve] java found but jar MISSING — trying next candidate"); - } - } - None => { - do_log(log, "[resolve] java NOT found — trying next candidate"); + match find_java_in_bundle(&bundle_dir, log) { + Some(java) => { + do_log(log, &format!("[resolve] java found: {:?}", java)); + if jar.exists() { + do_log(log, "[resolve] both java and jar found — using bundled JRE"); + return Ok(ServerInvocation { + bin: java.to_string_lossy().into_owned(), + args: vec![ + "-jar".to_string(), + jar.to_string_lossy().into_owned(), + ], + working_dir: Some(bundle_dir), + }); + } else { + do_log(log, "[resolve] java found but jar MISSING — skipping bundled path"); } } + None => { + do_log(log, "[resolve] java NOT found in bundle — skipping bundled path"); + } } } @@ -436,7 +424,7 @@ pub fn run() { get_storage_info, spawn_server, kill_server, - get_scale_factor, + get_platform_ui_scale, ]) .setup(|_app| Ok(())) .on_window_event(|window, event| { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 4235974..0ab044b 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -17,7 +17,8 @@ "minHeight": 600, "resizable": true, "fullscreen": false, - "decorations": false + "decorations": false, + "center": true } ], "security": { @@ -26,7 +27,9 @@ }, "bundle": { "active": true, - "targets": ["nsis"], + "targets": [ + "nsis" + ], "icon": [ "icons/32x32.png", "icons/128x128.png", diff --git a/src/App.svelte b/src/App.svelte index 044065e..c913636 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -14,7 +14,7 @@ import SplashScreen from "./components/layout/SplashScreen.svelte"; import MangaPreview from "./components/shared/MangaPreview.svelte"; - const MAX_ATTEMPTS = 30; + const MAX_ATTEMPTS = 60; let serverProbeOk = $state(!store.settings.autoStartServer); let appReady = $state(!store.settings.autoStartServer); @@ -22,6 +22,14 @@ let notConfigured = $state(false); let idle = $state(false); let devSplash = $state(false); + let platformScale = $state(1); + + function applyZoom() { + const normalized = store.settings.uiScale * platformScale; + document.documentElement.style.zoom = `${normalized}%`; + document.documentElement.style.setProperty("--ui-scale", String(normalized)); + document.documentElement.style.setProperty("--visual-vh", `${window.innerHeight / (normalized / 100)}px`); + } let prevQueue: DownloadQueueItem[] = []; let idleTimer: ReturnType | null = null; @@ -66,10 +74,9 @@ }); $effect(() => { - const scale = store.settings.uiScale * 1.5; - document.documentElement.style.zoom = `${scale}%`; - document.documentElement.style.setProperty("--ui-scale", String(scale)); - document.documentElement.style.setProperty("--visual-vh", `${window.innerHeight / (scale / 100)}px`); + // Re-runs whenever uiScale or platformScale changes. + store.settings.uiScale; platformScale; + applyZoom(); }); $effect(() => { @@ -85,61 +92,48 @@ return () => clearInterval(pollInterval); }); - // Probe the server in a loop until it responds or we hit MAX_ATTEMPTS. - // Returns a cleanup function that cancels any pending probe. - function startProbe(): () => void { - let cancelled = false, tries = 0; - - async function probe() { - if (cancelled) return; - tries++; - try { - const res = await fetch(`${store.settings.serverUrl}/api/graphql`, { - method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ query: "{ __typename }" }), - signal: AbortSignal.timeout(2000), - }); - if (res.ok && !cancelled) { serverProbeOk = true; return; } - } catch {} - if (tries >= MAX_ATTEMPTS && !cancelled) { failed = true; return; } - if (!cancelled) setTimeout(probe, 800); - } - - // Give the server a moment to start binding its port before the first probe. - setTimeout(probe, 1200); - return () => { cancelled = true; }; - } - onMount(async () => { document.addEventListener("contextmenu", e => e.preventDefault()); (window as any).__mokuShowSplash = () => devSplash = true; - let cancelProbe = () => {}; + // Fetch the platform scale factor then immediately re-apply zoom. + platformScale = await invoke("get_platform_ui_scale").catch(() => 1); + applyZoom(); if (store.settings.autoStartServer) { - try { - await invoke("spawn_server", { binary: store.settings.serverBinary ?? "" }); - // spawn_server succeeded — JRE found and process started. Begin probing. - cancelProbe = startProbe(); - } catch (err: any) { + invoke("spawn_server", { binary: store.settings.serverBinary }).catch((err: any) => { if (err?.kind === "NotConfigured") { notConfigured = true; } else { - // SpawnFailed — process couldn't be launched (permissions, bad path, etc.) - console.error("spawn_server failed:", err); - failed = true; + console.warn("Could not start server:", err); } + }); + } + + if (!serverProbeOk) { + let cancelled = false, tries = 0; + async function probe() { + if (cancelled) return; + tries++; + try { + const res = await fetch(`${store.settings.serverUrl}/api/graphql`, { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ query: "{ __typename }" }), + signal: AbortSignal.timeout(2000), + }); + if (res.ok && !cancelled) { serverProbeOk = true; return; } + } catch {} + if (tries >= MAX_ATTEMPTS && !cancelled) { failed = true; return; } + if (!cancelled) setTimeout(probe, 500); } - } else { - // autoStartServer is off — user manages the server themselves, just probe. - cancelProbe = startProbe(); + setTimeout(probe, 800); } type P = { chapterId: number; mangaId: number; progress: number }[]; unlistenDownload = await listen

("download-progress", e => { setActiveDownloads(e.payload); }); return () => { - cancelProbe(); + cancelled = true; if (store.settings.autoStartServer) invoke("kill_server").catch(() => {}); if (idleTimer) clearTimeout(idleTimer); if (pollInterval) clearInterval(pollInterval); @@ -148,13 +142,7 @@ }; }); - function handleRetry() { - failed = false; - notConfigured = false; - serverProbeOk = false; - // Re-run the full startup flow by reloading — simplest way to reset all state cleanly. - window.location.reload(); - } + function handleRetry() { failed = false; notConfigured = false; serverProbeOk = false; } {#if devSplash} diff --git a/src/assets/logo.png b/src/assets/logo.png deleted file mode 100644 index 24f8180..0000000 Binary files a/src/assets/logo.png and /dev/null differ diff --git a/src/assets/moku-icon-rounded.svg b/src/assets/moku-icon-splash.svg similarity index 75% rename from src/assets/moku-icon-rounded.svg rename to src/assets/moku-icon-splash.svg index a215c63..e88a390 100644 --- a/src/assets/moku-icon-rounded.svg +++ b/src/assets/moku-icon-splash.svg @@ -1,13 +1,5 @@ - - - - - - - - + + + + + + diff --git a/src/assets/moku-icon.svg b/src/assets/moku-icon.svg index b41a7b1..f522609 100644 --- a/src/assets/moku-icon.svg +++ b/src/assets/moku-icon.svg @@ -1,27 +1,22 @@ - - - - - - + + + + + diff --git a/src/components/layout/Sidebar.svelte b/src/components/layout/Sidebar.svelte index 7d637bb..60f9e72 100644 --- a/src/components/layout/Sidebar.svelte +++ b/src/components/layout/Sidebar.svelte @@ -54,7 +54,7 @@ .logo:hover { opacity: 0.8; transform: scale(0.96); } .logo:active { transform: scale(0.92); } .logo:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } - .logo-icon { width: 80px; height: 80px; background-color: var(--accent); mask-image: url("../../assets/moku-icon.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: contain; -webkit-mask-image: url("../../assets/moku-icon.svg"); -webkit-mask-repeat: no-repeat; -webkit-mask-position: center; -webkit-mask-size: contain; filter: drop-shadow(0 0 8px rgba(107,143,107,0.35)); pointer-events: none; } + .logo-icon { width: 67px; height: 67px; background-color: var(--accent); mask-image: url("../../assets/moku-icon-wordmark.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: contain; -webkit-mask-image: url("../../assets/moku-icon-wordmark.svg"); -webkit-mask-repeat: no-repeat; -webkit-mask-position: center; -webkit-mask-size: contain; filter: drop-shadow(0 0 8px rgba(107,143,107,0.35)); pointer-events: none; } .nav { flex: 1; display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); width: 100%; padding: 0 var(--sp-2); } .tab { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: var(--radius-md); color: var(--text-faint); background: none; border: none; outline: none; cursor: pointer; padding: 0; appearance: none; -webkit-appearance: none; transition: color var(--t-base), background var(--t-base); } .tab:hover { color: var(--text-muted); background: var(--bg-raised); } diff --git a/src/components/layout/SplashScreen.svelte b/src/components/layout/SplashScreen.svelte index 14cf063..70967c0 100644 --- a/src/components/layout/SplashScreen.svelte +++ b/src/components/layout/SplashScreen.svelte @@ -2,7 +2,7 @@ import { onMount } from "svelte"; import { getCurrentWindow } from "@tauri-apps/api/window"; import { store } from "../../store/state.svelte"; - import logoUrl from "../../assets/moku-icon.svg"; + import logoUrl from "../../assets/moku-icon-splash.svg"; interface Props { mode?: "loading" | "idle"; @@ -20,6 +20,15 @@ showCards = true, showFps = false, onReady, onRetry, onDismiss }: Props = $props(); const EXIT_MS = 320; + // Server typically takes 8-20s to boot. We animate the ring through three + // phases so it always feels like something is happening: + // 0 → 0.75 over ~12s (eased crawl while server starts) + // 0.75 → 0.92 over ~8s (slow down near the end, implying "almost there") + // jumps to 1.0 the moment the probe succeeds + const PHASE1_TARGET = 0.85; + const PHASE1_MS = 3000; + const PHASE2_TARGET = 0.95; + const PHASE2_MS = 10000; let dots = $state(""); let ringProg = $state(0.025); @@ -35,8 +44,42 @@ setTimeout(() => cb?.(), EXIT_MS); } + // Animate ring progress with easing so it never stalls visually + let animFrame: number; + let animStart: number | null = null; + let animPhase = 1; + + function animateRing(ts: number) { + if (exitLock) return; + if (animStart === null) animStart = ts; + const elapsed = ts - animStart; + + if (animPhase === 1) { + const t = Math.min(elapsed / PHASE1_MS, 1); + // ease-out cubic so it starts fast and slows down + const eased = 1 - Math.pow(1 - t, 3); + ringProg = 0.025 + eased * (PHASE1_TARGET - 0.025); + if (t >= 1) { animPhase = 2; animStart = ts; } + } else if (animPhase === 2) { + const t = Math.min(elapsed / PHASE2_MS, 1); + const eased = 1 - Math.pow(1 - t, 4); + ringProg = PHASE1_TARGET + eased * (PHASE2_TARGET - PHASE1_TARGET); + // Phase 2 never completes on its own — only ringFull triggers completion + } + + animFrame = requestAnimationFrame(animateRing); + } + + $effect(() => { + if (mode === "loading" && !failed && !notConfigured) { + animFrame = requestAnimationFrame(animateRing); + return () => cancelAnimationFrame(animFrame); + } + }); + $effect(() => { if (ringFull) { + cancelAnimationFrame(animFrame); ringProg = 1; setTimeout(() => triggerExit(onReady), 650); } @@ -149,7 +192,7 @@ const ctx = oc.getContext("2d")!; ctx.scale(dpr, dpr); const g = ctx.createRadialGradient(vw / 2, vh / 2, 0, vw / 2, vh / 2, Math.max(vw, vh) * 0.65); - g.addColorStop(0.15, "rgba(0,0,0,0)"); g.addColorStop(1, "rgba(0,0,0,0.82)"); + g.addColorStop(0, "rgba(0,0,0,0)"); g.addColorStop(0.4, "rgba(0,0,0,0)"); g.addColorStop(0.7, "rgba(0,0,0,0.25)"); g.addColorStop(1, "rgba(0,0,0,0.65)"); ctx.fillStyle = g; ctx.fillRect(0, 0, vw, vh); return oc; } diff --git a/src/components/pages/Home.svelte b/src/components/pages/Home.svelte index 869a762..e298eea 100644 --- a/src/components/pages/Home.svelte +++ b/src/components/pages/Home.svelte @@ -337,13 +337,15 @@ - {#if recentHistory.length > 0} -

-
- Recent Activity +
+
+ Recent Activity + {#if recentHistory.length > 0} -
-
+ {/if} +
+
+ {#if recentHistory.length > 0} {#each recentHistory as entry (entry.chapterId)} {/each} -
+ {:else} +
+ {#each Array(5) as _, i} +
+
+
+
+
+
+
+
+ {/each} +
+ +
+
+ {/if}
- {:else} -
-

Start reading to build your activity feed

- -
- {/if} +
@@ -506,7 +521,7 @@ :global(.ch-play-icon) { color: var(--accent-fg); flex-shrink: 0; } .chapter-row-sk { display: flex; gap: var(--sp-2); padding: 7px var(--sp-2); align-items: center; } .sk-info { flex: 1; display: flex; flex-direction: column; gap: 4px; } - .sk { background: rgba(255,255,255,0.08); border-radius: var(--radius-sm); animation: pulse 1.4s ease infinite; } + .sk { background: rgba(255,255,255,0.06); border-radius: var(--radius-sm); } .sk-num { width: 32px; height: 10px; flex-shrink: 0; } .sk-name { height: 11px; width: 85%; } .sk-meta { height: 9px; width: 50%; } @@ -529,12 +544,12 @@ .activity-play { color: var(--accent-fg); flex-shrink: 0; opacity: 0; transition: opacity var(--t-base); } .bottom-row { display: grid; grid-template-columns: 1fr 1px 1fr; padding: 0 var(--sp-4); border-top: 1px solid var(--border-dim); flex-shrink: 0; } .bottom-divider { background: var(--border-dim); align-self: stretch; } - .bottom-col { display: flex; flex-direction: column; min-width: 0; padding-top: var(--sp-3); padding-bottom: var(--sp-4); } + .bottom-col { display: flex; flex-direction: column; min-width: 0; padding-top: var(--sp-4); padding-bottom: var(--sp-5); } .bottom-col:first-child { padding-right: var(--sp-4); } .bottom-col:last-child { padding-left: var(--sp-4); } .bottom-section-hd { display: flex; align-items: center; justify-content: space-between; padding-bottom: var(--sp-2); } .bottom-empty { font-family: var(--font-ui); font-size: var(--text-sm); color: var(--text-faint); letter-spacing: var(--tracking-wide); padding: var(--sp-1) 0; } - .mini-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: var(--sp-3); } + .mini-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: var(--sp-3); } .mini-card { width: 100%; background: none; border: none; padding: 0; cursor: pointer; text-align: left; } .mini-card:hover .mini-cover { filter: brightness(1.08) saturate(1.05); transform: scale(1.02); } @@ -546,19 +561,25 @@ .mini-card-title { font-size: var(--text-xs); font-weight: var(--weight-medium); color: rgba(255,255,255,0.92); line-height: var(--leading-snug); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-shadow: 0 1px 4px rgba(0,0,0,0.7); } .mini-card-source { font-family: var(--font-ui); font-size: 9px; color: rgba(255,255,255,0.45); letter-spacing: var(--tracking-wide); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); } - .stat-card { display: flex; align-items: center; gap: var(--sp-3); background: var(--bg-raised); border: 1px solid var(--border-dim); border-radius: var(--radius-md); padding: var(--sp-2) var(--sp-3); } - .stat-icon-wrap { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--radius-sm); flex-shrink: 0; } + .stat-card { display: flex; align-items: center; gap: var(--sp-3); background: var(--bg-raised); border: 1px solid var(--border-dim); border-radius: var(--radius-md); padding: var(--sp-3) var(--sp-3); } + .stat-icon-wrap { display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; border-radius: var(--radius-sm); flex-shrink: 0; } .stat-fire { background: rgba(251,146,60,0.15); color: #fb923c; } .stat-accent { background: var(--accent-muted); color: var(--accent-fg); } .stat-neutral { background: var(--bg-overlay); color: var(--text-faint); } .stat-green { background: rgba(34,197,94,0.12); color: #22c55e; } .stat-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; } - .stat-val { font-family: var(--font-ui); font-size: var(--text-base); font-weight: var(--weight-medium); color: var(--text-secondary); line-height: 1; } + .stat-val { font-family: var(--font-ui); font-size: var(--text-lg, 1.05rem); font-weight: var(--weight-medium); color: var(--text-secondary); line-height: 1; } .stat-label { font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); white-space: nowrap; } - .empty-state { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); padding: var(--sp-3) var(--sp-6); flex-shrink: 0; } - .empty-text { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); } - .empty-cta { display: flex; align-items: center; gap: var(--sp-2); font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); padding: 7px 16px; border-radius: var(--radius-full); background: var(--accent-muted); border: 1px solid var(--accent-dim); color: var(--accent-fg); cursor: pointer; transition: filter var(--t-base); } - .empty-cta:hover { filter: brightness(1.1); } + .activity-row-sk { cursor: default; pointer-events: none; } + .sk-thumb { width: 33px; height: 48px; border-radius: var(--radius-sm); background: rgba(255,255,255,0.06); flex-shrink: 0; } + .sk { background: var(--bg-raised); border-radius: var(--radius-sm); } + .sk-title { height: 11px; margin-bottom: 5px; } + .sk-sub { height: 9px; } + .sk-time { width: 32px; height: 9px; flex-shrink: 0; background: rgba(255,255,255,0.06); border-radius: var(--radius-sm); } + .activity-placeholder { position: relative; } + .activity-placeholder-overlay { position: absolute; left: 0; right: 0; top: 0; bottom: -1px; display: flex; align-items: flex-end; justify-content: center; padding-bottom: var(--sp-4); pointer-events: none; background: linear-gradient(to bottom, transparent 0%, rgba(0,0,0,0.6) 100%); } + .activity-placeholder-cta { pointer-events: all; display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); padding: 7px 16px; border-radius: var(--radius-full); background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.14); color: rgba(255,255,255,0.65); cursor: pointer; transition: background var(--t-base), color var(--t-base); } + .activity-placeholder-cta:hover { background: rgba(255,255,255,0.13); color: rgba(255,255,255,0.9); } .picker-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: var(--z-settings); display: flex; align-items: center; justify-content: center; animation: fadeIn 0.1s ease both; backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); } .picker-modal { width: min(460px, calc(100vw - 48px)); max-height: 68vh; display: flex; flex-direction: column; background: var(--bg-surface); border: 1px solid var(--border-base); border-radius: var(--radius-xl); overflow: hidden; box-shadow: 0 24px 64px rgba(0,0,0,0.6); animation: scaleIn 0.14s ease both; } .picker-header { display: flex; align-items: center; justify-content: space-between; padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--border-dim); flex-shrink: 0; } diff --git a/src/components/settings/Settings.svelte b/src/components/settings/Settings.svelte index 9efe8a9..1506ff7 100644 --- a/src/components/settings/Settings.svelte +++ b/src/components/settings/Settings.svelte @@ -258,13 +258,13 @@

Interface Scale

- updateSettings({ uiScale: Number(e.currentTarget.value) })} class="scale-slider" /> {store.settings.uiScale}%

- {#each [70,80,90,100,110,125,150] as v} + {#each [70,80,90,100,110,125,150,175,200] as v} {/each}

@@ -275,10 +275,7 @@
Server URLBase URL of your Suwayomi instance
updateSettings({ serverUrl: e.currentTarget.value })} placeholder="http://localhost:4567" spellcheck="false" />
-
-
Server binaryPath or command to launch tachidesk-server
- updateSettings({ serverBinary: e.currentTarget.value })} placeholder="tachidesk-server" spellcheck="false" /> -
+