From eed52135e7d6ffbbbd64070cf567bcf08653c7d5 Mon Sep 17 00:00:00 2001
From: HsiangNianian <i@jyunko.cn>
Date: Wed, 14 Jan 2026 18:15:31 +0800
Subject: feat: Enhance UI components and add visual effects

- Updated Sidebar component styles for improved aesthetics and usability.
- Refactored VersionsView component with a new layout and enhanced version filtering.
- Improved DownloadMonitor and GameConsole components for better performance and visual consistency.
- Added new settings for GPU acceleration and visual effects in settings store.
- Introduced ParticleBackground component with customizable effects (Constellation and Saturn).
- Implemented ConstellationEffect and SaturnEffect classes for dynamic background animations.
---
 ui/src/App.svelte | 145 ++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 120 insertions(+), 25 deletions(-)

(limited to 'ui/src/App.svelte')

diff --git a/ui/src/App.svelte b/ui/src/App.svelte
index 3750f11..d93374e 100644
--- a/ui/src/App.svelte
+++ b/ui/src/App.svelte
@@ -1,6 +1,7 @@
 <script lang="ts">
   import { getVersion } from "@tauri-apps/api/app";
-  import { onMount } from "svelte";
+  import { onMount, onDestroy } from "svelte";
+  import { convertFileSrc } from "@tauri-apps/api/core";
   import DownloadMonitor from "./lib/DownloadMonitor.svelte";
   import GameConsole from "./lib/GameConsole.svelte";
   
@@ -12,6 +13,7 @@
   import BottomBar from "./components/BottomBar.svelte";
   import LoginModal from "./components/LoginModal.svelte";
   import StatusToast from "./components/StatusToast.svelte";
+  import ParticleBackground from "./components/ParticleBackground.svelte";
 
   // Stores
   import { uiState } from "./stores/ui.svelte";
@@ -19,46 +21,139 @@
   import { settingsState } from "./stores/settings.svelte";
   import { gameState } from "./stores/game.svelte";
 
+  let mouseX = $state(0);
+  let mouseY = $state(0);
+
+  function handleMouseMove(e: MouseEvent) {
+    mouseX = (e.clientX / window.innerWidth) * 2 - 1;
+    mouseY = (e.clientY / window.innerHeight) * 2 - 1;
+  }
+
   onMount(async () => {
     authState.checkAccount();
     settingsState.loadSettings();
     gameState.loadVersions();
     getVersion().then((v) => (uiState.appVersion = v));
+    window.addEventListener("mousemove", handleMouseMove);
+  });
+  
+  onDestroy(() => {
+    if (typeof window !== 'undefined')
+      window.removeEventListener("mousemove", handleMouseMove);
   });
 </script>
 
 <div
-  class="flex h-screen bg-zinc-900 text-white font-sans overflow-hidden select-none"
+  class="relative h-screen w-screen overflow-hidden text-white font-sans selection:bg-indigo-500/30"
 >
-  <Sidebar />
+  <!-- Modern Animated Background -->
+  <div class="absolute inset-0 z-0 bg-[#09090b] overflow-hidden">
+    {#if settingsState.settings.custom_background_path}
+      <img
+        src={convertFileSrc(settingsState.settings.custom_background_path)}
+        alt="Background"
+        class="absolute inset-0 w-full h-full object-cover transition-transform duration-[20s] ease-linear hover:scale-105"
+      />
+      <!-- Dimming Overlay for readability -->
+      <div class="absolute inset-0 bg-black/50 "></div>
+    {:else if settingsState.settings.enable_visual_effects}
+      <!-- Original Gradient -->
+      <div 
+        class="absolute inset-0 opacity-60 bg-gradient-to-br from-emerald-900 via-zinc-900 to-indigo-950"
+      ></div>
 
-  <!-- Main Content -->
-  <main class="flex-1 flex flex-col relative min-w-0">
-    <DownloadMonitor />
-    <!-- Top Bar (Window Controls Placeholder) -->
-    <div
-      class="h-8 w-full bg-zinc-900/50 absolute top-0 left-0 z-50 drag-region"
-      data-tauri-drag-region
-    >
-      <!-- Windows/macOS controls would go here or be handled by OS -->
-    </div>
-
-    <!-- Background / Poster area -->
-    <div class="flex-1 relative overflow-hidden group">
       {#if uiState.currentView === "home"}
-        <HomeView />
-      {:else if uiState.currentView === "versions"}
-        <VersionsView />
-      {:else if uiState.currentView === "settings"}
-        <SettingsView />
+        <ParticleBackground />
       {/if}
+
+      <div 
+        class="absolute inset-0 bg-gradient-to-t from-zinc-900 via-transparent to-black/50"
+      ></div>
+    {/if}
+    
+    <!-- Subtle Grid Overlay -->
+    <div class="absolute inset-0 z-0 opacity-10 pointer-events-none" 
+         style="background-image: linear-gradient(#ffffff 1px, transparent 1px), linear-gradient(90deg, #ffffff 1px, transparent 1px); background-size: 40px 40px; mask-image: radial-gradient(circle at 50% 50%, black 30%, transparent 70%);">
     </div>
+  </div>
+
+  <!-- Content Wrapper -->
+  <div class="relative z-10 flex h-full p-4 gap-4">
+    <!-- Floating Sidebar -->
+    <Sidebar />
+
+    <!-- Main Content Area - Transparent & Flat -->
+    <main class="flex-1 flex flex-col relative min-w-0 overflow-hidden transition-all duration-300">
+      
+      <!-- Window Drag Region -->
+      <div
+        class="h-8 w-full absolute top-0 left-0 z-50 drag-region"
+        data-tauri-drag-region
+      ></div>
 
-    <BottomBar />
-  </main>
+      <!-- App Content -->
+      <div class="flex-1 relative overflow-hidden flex flex-col">
+          <!-- Views Container -->
+          <div class="flex-1 relative overflow-hidden">
+             {#if uiState.currentView === "home"}
+               <HomeView mouseX={mouseX} mouseY={mouseY} />
+             {:else if uiState.currentView === "versions"}
+               <VersionsView />
+             {:else if uiState.currentView === "settings"}
+               <SettingsView />
+             {/if}
+          </div>
+          
+          <!-- Download Monitor Overlay -->
+          <div class="absolute bottom-20 left-4 right-4 pointer-events-none z-20">
+             <div class="pointer-events-auto">
+                 <DownloadMonitor />
+             </div>
+          </div>
+          
+          <!-- Bottom Bar -->
+          <BottomBar />
+      </div>
+    </main>
+  </div>
 
   <LoginModal />
   <StatusToast />
-
-  <GameConsole visible={uiState.showConsole} />
+  
+  {#if uiState.showConsole}
+     <!-- Assuming GameConsole handles its own display mode or overlay -->
+    <div class="fixed inset-0 z-[100] bg-black/80  flex items-center justify-center p-10">
+        <div class="w-full h-full bg-[#1e1e1e] rounded-xl overflow-hidden border border-white/10 shadow-2xl relative">
+            <button class="absolute top-4 right-4 text-white hover:text-red-400 z-10" onclick={() => uiState.toggleConsole()}>✕</button>
+            <GameConsole />
+        </div>
+    </div>
+  {/if}
 </div>
+
+<style>
+  :global(body) {
+    margin: 0;
+    padding: 0;
+    background: #000;
+  }
+  
+  /* Modern Scrollbar */
+  :global(*::-webkit-scrollbar) {
+    width: 6px;
+    height: 6px;
+  }
+  
+  :global(*::-webkit-scrollbar-track) {
+    background: transparent;
+  }
+  
+  :global(*::-webkit-scrollbar-thumb) {
+    background: rgba(255, 255, 255, 0.1);
+    border-radius: 999px;
+  }
+  
+  :global(*::-webkit-scrollbar-thumb:hover) {
+    background: rgba(255, 255, 255, 0.25);
+  }
+</style>
-- 
cgit v1.2.3-70-g09d2


From 74849ad2d18586736d9677dfd10af4875f4ef2ca Mon Sep 17 00:00:00 2001
From: HsiangNianian <i@jyunko.cn>
Date: Wed, 14 Jan 2026 18:40:01 +0800
Subject: feat: enhance dark mode support across UI components

- Updated BottomBar, HomeView, LoginModal, ModLoaderSelector, SettingsView, Sidebar, StatusToast, and VersionsView components for improved dark mode styling.
- Adjusted color schemes for various elements to ensure better visibility and aesthetics in dark mode.
- Added a theme property to settings to enforce dark mode as the default.
- Refactored version badges in VersionsView for better color differentiation.
- Enhanced button and input styles for consistency in both light and dark themes.
---
 assets/image.jpg                           | Bin 172906 -> 161028 bytes
 src-tauri/src/core/config.rs               |   2 +
 src-tauri/src/main.rs                      |   4 --
 src-tauri/tauri.conf.json                  |   4 +-
 ui/src/App.svelte                          |  39 +++++++++++++------
 ui/src/components/BottomBar.svelte         |  20 +++++-----
 ui/src/components/HomeView.svelte          |  10 ++---
 ui/src/components/LoginModal.svelte        |  30 +++++++--------
 ui/src/components/ModLoaderSelector.svelte |  40 +++++++++----------
 ui/src/components/SettingsView.svelte      |  60 ++++++++++++++++++++---------
 ui/src/components/Sidebar.svelte           |   8 ++--
 ui/src/components/StatusToast.svelte       |  10 ++---
 ui/src/components/VersionsView.svelte      |  48 +++++++++++------------
 ui/src/stores/settings.svelte.ts           |   6 +++
 ui/src/types/index.ts                      |   1 +
 15 files changed, 163 insertions(+), 119 deletions(-)

(limited to 'ui/src/App.svelte')

diff --git a/assets/image.jpg b/assets/image.jpg
index 28cffe1..ddda96f 100644
Binary files a/assets/image.jpg and b/assets/image.jpg differ
diff --git a/src-tauri/src/core/config.rs b/src-tauri/src/core/config.rs
index 27e0011..510b126 100644
--- a/src-tauri/src/core/config.rs
+++ b/src-tauri/src/core/config.rs
@@ -17,6 +17,7 @@ pub struct LauncherConfig {
     pub enable_gpu_acceleration: bool,
     pub enable_visual_effects: bool,
     pub active_effect: String,
+    pub theme: String,
 }
 
 impl Default for LauncherConfig {
@@ -32,6 +33,7 @@ impl Default for LauncherConfig {
             enable_gpu_acceleration: false,
             enable_visual_effects: true,
             active_effect: "constellation".to_string(),
+            theme: "dark".to_string(),
         }
     }
 }
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index f7a391a..8a955a5 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -854,8 +854,6 @@ async fn get_recommended_java(
     Ok(core::java::get_recommended_java(required_major_version))
 }
 
-// ==================== Fabric Loader Commands ====================
-
 /// Get Minecraft versions supported by Fabric
 #[tauri::command]
 async fn get_fabric_game_versions() -> Result<Vec<core::fabric::FabricGameVersion>, String> {
@@ -949,8 +947,6 @@ async fn is_fabric_installed(
     ))
 }
 
-// ==================== Forge Loader Commands ====================
-
 /// Get Minecraft versions supported by Forge
 #[tauri::command]
 async fn get_forge_game_versions() -> Result<Vec<String>, String> {
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 9145e1b..682acb0 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -14,8 +14,8 @@
                 "title": "Minecraft DropOut Launcher",
                 "width": 1024,
                 "height": 768,
-                "minWidth": 800,
-                "minHeight": 600,
+                "minWidth": 905,
+                "minHeight": 575,
                 "resizable": true
             }
         ],
diff --git a/ui/src/App.svelte b/ui/src/App.svelte
index d93374e..2160b85 100644
--- a/ui/src/App.svelte
+++ b/ui/src/App.svelte
@@ -31,12 +31,22 @@
 
   onMount(async () => {
     authState.checkAccount();
-    settingsState.loadSettings();
+    await settingsState.loadSettings();
     gameState.loadVersions();
     getVersion().then((v) => (uiState.appVersion = v));
     window.addEventListener("mousemove", handleMouseMove);
   });
   
+  $effect(() => {
+    if (settingsState.settings.theme === 'light') {
+        document.documentElement.classList.remove('dark');
+        document.documentElement.setAttribute('data-theme', 'light');
+    } else {
+        document.documentElement.classList.add('dark');
+        document.documentElement.setAttribute('data-theme', 'dark');
+    }
+  });
+
   onDestroy(() => {
     if (typeof window !== 'undefined')
       window.removeEventListener("mousemove", handleMouseMove);
@@ -44,10 +54,10 @@
 </script>
 
 <div
-  class="relative h-screen w-screen overflow-hidden text-white font-sans selection:bg-indigo-500/30"
+  class="relative h-screen w-screen overflow-hidden dark:text-white text-gray-900 font-sans selection:bg-indigo-500/30"
 >
   <!-- Modern Animated Background -->
-  <div class="absolute inset-0 z-0 bg-[#09090b] overflow-hidden">
+  <div class="absolute inset-0 z-0 bg-[#09090b] dark:bg-[#09090b] bg-gray-100 overflow-hidden">
     {#if settingsState.settings.custom_background_path}
       <img
         src={convertFileSrc(settingsState.settings.custom_background_path)}
@@ -57,28 +67,35 @@
       <!-- Dimming Overlay for readability -->
       <div class="absolute inset-0 bg-black/50 "></div>
     {:else if settingsState.settings.enable_visual_effects}
-      <!-- Original Gradient -->
-      <div 
-        class="absolute inset-0 opacity-60 bg-gradient-to-br from-emerald-900 via-zinc-900 to-indigo-950"
-      ></div>
+      <!-- Original Gradient (Dark Only / or Adjusted for Light) -->
+      {#if settingsState.settings.theme === 'dark'}
+          <div 
+            class="absolute inset-0 opacity-60 bg-gradient-to-br from-emerald-900 via-zinc-900 to-indigo-950"
+          ></div>
+      {:else}
+           <!-- Light Mode Gradient -->
+           <div 
+            class="absolute inset-0 opacity-100 bg-gradient-to-br from-emerald-100 via-gray-100 to-indigo-100"
+          ></div>
+      {/if}
 
       {#if uiState.currentView === "home"}
         <ParticleBackground />
       {/if}
 
       <div 
-        class="absolute inset-0 bg-gradient-to-t from-zinc-900 via-transparent to-black/50"
+        class="absolute inset-0 bg-gradient-to-t from-zinc-900 via-transparent to-black/50 dark:from-zinc-900 dark:to-black/50 from-gray-100 to-transparent"
       ></div>
     {/if}
     
     <!-- Subtle Grid Overlay -->
-    <div class="absolute inset-0 z-0 opacity-10 pointer-events-none" 
-         style="background-image: linear-gradient(#ffffff 1px, transparent 1px), linear-gradient(90deg, #ffffff 1px, transparent 1px); background-size: 40px 40px; mask-image: radial-gradient(circle at 50% 50%, black 30%, transparent 70%);">
+    <div class="absolute inset-0 z-0 opacity-10 dark:opacity-10 opacity-30 pointer-events-none" 
+         style="background-image: linear-gradient({settingsState.settings.theme === 'dark' ? '#ffffff' : '#000000'} 1px, transparent 1px), linear-gradient(90deg, {settingsState.settings.theme === 'dark' ? '#ffffff' : '#000000'} 1px, transparent 1px); background-size: 40px 40px; mask-image: radial-gradient(circle at 50% 50%, black 30%, transparent 70%);">
     </div>
   </div>
 
   <!-- Content Wrapper -->
-  <div class="relative z-10 flex h-full p-4 gap-4">
+  <div class="relative z-10 flex h-full p-4 gap-4 text-gray-900 dark:text-white">
     <!-- Floating Sidebar -->
     <Sidebar />
 
diff --git a/ui/src/components/BottomBar.svelte b/ui/src/components/BottomBar.svelte
index dd218f3..0178111 100644
--- a/ui/src/components/BottomBar.svelte
+++ b/ui/src/components/BottomBar.svelte
@@ -5,7 +5,7 @@
 </script>
 
 <div
-  class="h-24 bg-gradient-to-t from-black/50 to-transparent border-t border-white/5 flex items-center px-8 justify-between z-20 backdrop-blur-md"
+  class="h-24 bg-gradient-to-t from-black/50 to-transparent dark:from-black/50 dark:to-transparent from-white/90 to-transparent border-t dark:border-white/5 border-black/5 flex items-center px-8 justify-between z-20 backdrop-blur-md"
 >
   <!-- Account Area -->
   <div class="flex items-center gap-6">
@@ -17,7 +17,7 @@
       onkeydown={(e) => e.key === "Enter" && authState.openLoginModal()}
     >
       <div
-        class="w-12 h-12 rounded-xl bg-gradient-to-tr from-indigo-500 to-purple-500 shadow-lg shadow-indigo-500/20 flex items-center justify-center text-white font-bold text-xl overflow-hidden ring-2 ring-transparent group-hover:ring-white/20 transition-all"
+        class="w-12 h-12 rounded-xl bg-gradient-to-tr from-indigo-500 to-purple-500 shadow-lg shadow-indigo-500/20 flex items-center justify-center text-white font-bold text-xl overflow-hidden ring-2 ring-transparent dark:group-hover:ring-white/20 group-hover:ring-black/10 transition-all"
       >
         {#if authState.currentAccount}
           <img
@@ -30,25 +30,25 @@
         {/if}
       </div>
       <div>
-        <div class="font-bold text-white text-lg group-hover:text-indigo-300 transition-colors">
+        <div class="font-bold dark:text-white text-gray-900 text-lg group-hover:text-indigo-500 dark:group-hover:text-indigo-300 transition-colors">
           {authState.currentAccount ? authState.currentAccount.username : "Login"}
         </div>
-        <div class="text-xs text-zinc-400 flex items-center gap-1.5">
+        <div class="text-xs dark:text-zinc-400 text-gray-500 flex items-center gap-1.5">
           <span
             class="w-2 h-2 rounded-full {authState.currentAccount
               ? 'bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.5)]'
-              : 'bg-zinc-600'}"
+              : 'dark:bg-zinc-600 bg-gray-400'}"
           ></span>
           {authState.currentAccount ? "Ready to play" : "Guest Mode"}
         </div>
       </div>
     </div>
     
-    <div class="h-8 w-px bg-white/10"></div>
+    <div class="h-8 w-px dark:bg-white/10 bg-black/10"></div>
     
     <!-- Console Toggle -->
     <button
-      class="text-xs font-mono text-zinc-500 hover:text-white transition-colors flex items-center gap-2"
+      class="text-xs font-mono dark:text-zinc-500 text-gray-500 dark:hover:text-white hover:text-black transition-colors flex items-center gap-2"
       onclick={() => uiState.toggleConsole()}
     >
       <span class="text-lg">_</span>
@@ -61,14 +61,14 @@
     <div class="flex flex-col items-end mr-2">
       <label
         for="version-select"
-        class="text-[10px] text-white/40 mb-1 uppercase font-bold tracking-wider"
+        class="text-[10px] dark:text-white/40 text-black/40 mb-1 uppercase font-bold tracking-wider"
         >Selected Version</label
       >
       <div class="relative group">
         <select
           id="version-select"
           bind:value={gameState.selectedVersion}
-          class="appearance-none bg-black/40 text-white border border-white/10 rounded-xl pl-4 pr-10 py-2.5 hover:border-white/30 transition-all cursor-pointer outline-none focus:ring-2 focus:ring-indigo-500/50 w-56 text-sm font-mono backdrop-blur-sm shadow-inner"
+          class="appearance-none dark:bg-black/40 bg-white/60 dark:text-white text-gray-900 border dark:border-white/10 border-black/10 rounded-xl pl-4 pr-10 py-2.5 dark:hover:border-white/30 hover:border-black/30 transition-all cursor-pointer outline-none focus:ring-2 focus:ring-indigo-500/50 w-56 text-sm font-mono backdrop-blur-sm shadow-inner"
         >
           {#if gameState.versions.length === 0}
             <option>Loading...</option>
@@ -78,7 +78,7 @@
             {/each}
           {/if}
         </select>
-        <div class="absolute right-3 top-1/2 -translate-y-1/2 text-white/20 pointer-events-none group-hover:text-white/50 transition-colors">▼</div>
+        <div class="absolute right-3 top-1/2 -translate-y-1/2 dark:text-white/20 text-black/20 pointer-events-none dark:group-hover:text-white/50 group-hover:text-black/50 transition-colors">▼</div>
       </div>
     </div>
 
diff --git a/ui/src/components/HomeView.svelte b/ui/src/components/HomeView.svelte
index 036c03a..9cd8014 100644
--- a/ui/src/components/HomeView.svelte
+++ b/ui/src/components/HomeView.svelte
@@ -19,19 +19,19 @@
     style:transform={`perspective(1000px) rotateX(${mouseY * -2}deg) rotateY(${mouseX * 2}deg)`}
   >
     <h1
-      class="text-8xl font-black tracking-tighter text-white drop-shadow-2xl mb-4"
-      style="text-shadow: 0 10px 30px rgba(0,0,0,0.5);"
+      class="text-8xl font-black tracking-tighter dark:text-white text-gray-900 drop-shadow-2xl mb-4"
+      style:text-shadow="0 10px 30px rgba(0,0,0,0.5)"
     >
       MINECRAFT
     </h1>
     
     <div class="flex items-center gap-4">
       <div 
-        class="bg-white/10 backdrop-blur-md border border-white/10 px-4 py-1.5 rounded-full text-sm font-bold uppercase tracking-widest text-emerald-400 shadow-xl"
+        class="bg-white/10 dark:bg-white/10 bg-black/5 backdrop-blur-md border dark:border-white/10 border-black/10 px-4 py-1.5 rounded-full text-sm font-bold uppercase tracking-widest text-emerald-500 dark:text-emerald-400 shadow-xl"
       >
         Java Edition
       </div>
-      <div class="text-2xl font-light text-zinc-300">
+      <div class="text-2xl font-light dark:text-zinc-300 text-gray-600">
         Latest Release 1.21
       </div>
     </div>
@@ -40,7 +40,7 @@
   <!-- Action Area -->
   <div class="mt-8 flex gap-4">
     <!-- Quick Play Button (Visual only here, logic is in BottomBar usually) -->
-    <div class="text-zinc-400 text-sm italic">
+    <div class="dark:text-zinc-400 text-gray-500 text-sm italic">
       Ready to play. Select version below or hit Launch.
     </div>
   </div>
diff --git a/ui/src/components/LoginModal.svelte b/ui/src/components/LoginModal.svelte
index f1ac0d5..1886cd9 100644
--- a/ui/src/components/LoginModal.svelte
+++ b/ui/src/components/LoginModal.svelte
@@ -9,16 +9,16 @@
 
 {#if authState.isLoginModalOpen}
   <div
-    class="fixed inset-0 z-[60] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4"
+    class="fixed inset-0 z-[60] flex items-center justify-center bg-white/80 dark:bg-black/80 backdrop-blur-sm p-4"
   >
     <div
-      class="bg-zinc-900 border border-zinc-700 rounded-xl shadow-2xl p-6 w-full max-w-md animate-in fade-in zoom-in-0 duration-200"
+      class="bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded-xl shadow-2xl p-6 w-full max-w-md animate-in fade-in zoom-in-0 duration-200"
     >
       <div class="flex justify-between items-center mb-6">
-        <h2 class="text-2xl font-bold text-white">Login</h2>
+        <h2 class="text-2xl font-bold text-gray-900 dark:text-white">Login</h2>
         <button
           onclick={() => authState.closeLoginModal()}
-          class="text-zinc-500 hover:text-white transition group"
+          class="text-zinc-500 hover:text-black dark:hover:text-white transition group"
         >
           ✕
         </button>
@@ -28,7 +28,7 @@
         <div class="space-y-4">
           <button
             onclick={() => authState.startMicrosoftLogin()}
-            class="w-full flex items-center justify-center gap-3 bg-[#2F2F2F] hover:bg-[#3F3F3F] text-white p-4 rounded-lg font-bold border border-transparent hover:border-zinc-500 transition-all group"
+            class="w-full flex items-center justify-center gap-3 bg-gray-100 hover:bg-gray-200 dark:bg-[#2F2F2F] dark:hover:bg-[#3F3F3F] text-gray-900 dark:text-white p-4 rounded-lg font-bold border border-transparent hover:border-zinc-400 dark:hover:border-zinc-500 transition-all group"
           >
             <!-- Microsoft Logo SVG -->
             <svg
@@ -49,10 +49,10 @@
 
           <div class="relative py-2">
             <div class="absolute inset-0 flex items-center">
-              <div class="w-full border-t border-zinc-700"></div>
+              <div class="w-full border-t border-zinc-200 dark:border-zinc-700"></div>
             </div>
             <div class="relative flex justify-center text-xs uppercase">
-              <span class="bg-zinc-900 px-2 text-zinc-500">OR</span>
+              <span class="bg-white dark:bg-zinc-900 px-2 text-zinc-400 dark:text-zinc-500">OR</span>
             </div>
           </div>
 
@@ -61,12 +61,12 @@
               type="text"
               bind:value={authState.offlineUsername}
               placeholder="Offline Username"
-              class="w-full bg-zinc-950 border border-zinc-700 rounded p-3 text-white focus:border-indigo-500 outline-none"
+              class="w-full bg-gray-50 border-zinc-200 dark:bg-zinc-950 dark:border-zinc-700 rounded p-3 text-gray-900 dark:text-white focus:border-indigo-500 outline-none"
               onkeydown={(e) => e.key === "Enter" && authState.performOfflineLogin()}
             />
             <button
               onclick={() => authState.performOfflineLogin()}
-              class="w-full bg-zinc-800 hover:bg-zinc-700 text-zinc-300 p-3 rounded font-medium transition-colors"
+              class="w-full bg-gray-200 hover:bg-gray-300 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-gray-700 dark:text-zinc-300 p-3 rounded font-medium transition-colors"
             >
               Offline Login
             </button>
@@ -80,18 +80,18 @@
             </div>
           {:else if authState.deviceCodeData}
             <div class="space-y-4">
-              <p class="text-sm text-zinc-400">1. Go to this URL:</p>
+              <p class="text-sm text-gray-500 dark:text-zinc-400">1. Go to this URL:</p>
               <button
                 onclick={() =>
                     authState.deviceCodeData && openLink(authState.deviceCodeData.verification_uri)}
-                class="text-indigo-400 hover:text-indigo-300 underline break-all font-mono text-sm"
+                class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-500 dark:hover:text-indigo-300 underline break-all font-mono text-sm"
               >
                 {authState.deviceCodeData.verification_uri}
               </button>
 
-              <p class="text-sm text-zinc-400 mt-2">2. Enter this code:</p>
+              <p class="text-sm text-gray-500 dark:text-zinc-400 mt-2">2. Enter this code:</p>
               <div
-                class="bg-zinc-950 p-4 rounded border border-zinc-700 font-mono text-2xl tracking-widest text-center select-all cursor-pointer hover:border-indigo-500 transition-colors"
+                class="bg-gray-50 dark:bg-zinc-950 p-4 rounded border border-zinc-200 dark:border-zinc-700 font-mono text-2xl tracking-widest text-center select-all cursor-pointer hover:border-indigo-500 transition-colors dark:text-white text-gray-900"
                 role="button"
                 tabindex="0"
                 onkeydown={(e) => e.key === 'Enter' && navigator.clipboard.writeText(authState.deviceCodeData?.user_code || "")}
@@ -106,8 +106,8 @@
 
               <div class="pt-6 space-y-3">
                    <div class="flex flex-col items-center gap-3">
-                       <div class="animate-spin rounded-full h-6 w-6 border-2 border-zinc-600 border-t-indigo-500"></div>
-                       <span class="text-sm text-zinc-400 font-medium break-all text-center">{authState.msLoginStatus}</span>
+                       <div class="animate-spin rounded-full h-6 w-6 border-2 border-zinc-300 dark:border-zinc-600 border-t-indigo-500"></div>
+                       <span class="text-sm text-gray-600 dark:text-zinc-400 font-medium break-all text-center">{authState.msLoginStatus}</span>
                    </div>
                    <p class="text-xs text-zinc-600">This window will update automatically.</p>
               </div>
diff --git a/ui/src/components/ModLoaderSelector.svelte b/ui/src/components/ModLoaderSelector.svelte
index 4a59916..fd26382 100644
--- a/ui/src/components/ModLoaderSelector.svelte
+++ b/ui/src/components/ModLoaderSelector.svelte
@@ -114,16 +114,16 @@
 
 <div class="space-y-4">
   <div class="flex items-center justify-between">
-      <h3 class="text-xs font-bold uppercase tracking-widest text-white/40">Select Mod Loader</h3>
+      <h3 class="text-xs font-bold uppercase tracking-widest text-gray-500 dark:text-white/40">Select Mod Loader</h3>
   </div>
 
   <!-- Loader Type Tabs - Segmented Control -->
-  <div class="flex p-1 bg-black/40 rounded-xl border border-white/5 backdrop-blur-sm">
+  <div class="flex p-1 bg-white/60 dark:bg-black/40 rounded-xl border border-black/5 dark:border-white/5 backdrop-blur-sm">
     <button
       class="flex-1 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-200
       {selectedLoader === 'vanilla'
-        ? 'bg-white/10 text-white shadow-lg border border-white/10'
-        : 'text-zinc-500 hover:text-white hover:bg-white/5'}"
+        ? 'bg-white shadow-lg border border-black/5 text-black dark:bg-white/10 dark:text-white dark:border-white/10'
+        : 'text-gray-500 dark:text-zinc-500 hover:text-black dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5'}"
       onclick={() => onLoaderChange("vanilla")}
     >
       Vanilla
@@ -131,8 +131,8 @@
     <button
       class="flex-1 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-200
       {selectedLoader === 'fabric'
-        ? 'bg-indigo-500/20 text-indigo-300 shadow-lg border border-indigo-500/20'
-        : 'text-zinc-500 hover:text-white hover:bg-white/5'}"
+        ? 'bg-indigo-100 text-indigo-700 border border-indigo-200 dark:bg-indigo-500/20 dark:text-indigo-300 dark:shadow-lg dark:border-indigo-500/20'
+        : 'text-gray-500 dark:text-zinc-500 hover:text-black dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5'}"
       onclick={() => onLoaderChange("fabric")}
     >
       Fabric
@@ -140,8 +140,8 @@
     <button
       class="flex-1 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-200
       {selectedLoader === 'forge'
-        ? 'bg-orange-500/20 text-orange-300 shadow-lg border border-orange-500/20'
-        : 'text-zinc-500 hover:text-white hover:bg-white/5'}"
+        ? 'bg-orange-100 text-orange-700 border border-orange-200 dark:bg-orange-500/20 dark:text-orange-300 dark:shadow-lg dark:border-orange-500/20'
+        : 'text-gray-500 dark:text-zinc-500 hover:text-black dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5'}"
       onclick={() => onLoaderChange("forge")}
     >
       Forge
@@ -151,36 +151,36 @@
   <!-- Content Area -->
   <div class="min-h-[100px] flex flex-col justify-center">
     {#if selectedLoader === "vanilla"}
-        <div class="text-center p-4 rounded-xl bg-white/5 border border-dashed border-white/10 text-white/40 text-sm">
+        <div class="text-center p-4 rounded-xl bg-white/40 dark:bg-white/5 border border-dashed border-black/5 dark:border-white/10 text-gray-500 dark:text-white/40 text-sm">
            No mod loader selected. <br> Pure vanilla experience.
         </div>
         
     {:else if !selectedGameVersion}
-        <div class="text-center p-4 rounded-xl bg-red-500/10 border border-red-500/20 text-red-300 text-sm">
+        <div class="text-center p-4 rounded-xl bg-red-50 border border-red-200 text-red-700 dark:bg-red-500/10 dark:border-red-500/20 dark:text-red-300 text-sm">
            ⚠️ Please select a base Minecraft version first.
         </div>
         
     {:else if isLoading}
-        <div class="flex flex-col items-center gap-2 text-sm text-white/50 py-4">
-            <div class="w-6 h-6 border-2 border-white/20 border-t-white rounded-full animate-spin"></div>
+        <div class="flex flex-col items-center gap-2 text-sm text-gray-500 dark:text-white/50 py-4">
+            <div class="w-6 h-6 border-2 border-gray-200 border-t-gray-500 dark:border-white/20 dark:border-t-white rounded-full animate-spin"></div>
             Loading {selectedLoader} versions...
         </div>
         
     {:else if error}
-        <div class="p-4 bg-red-500/10 border border-red-500/20 rounded-xl text-red-300 text-sm break-words">
+        <div class="p-4 bg-red-50 border border-red-200 text-red-700 dark:bg-red-500/10 dark:border-red-500/20 dark:text-red-300 rounded-xl text-sm break-words">
             {error}
         </div>
         
     {:else if selectedLoader === "fabric"}
         <div class="space-y-4 animate-in fade-in slide-in-from-bottom-2 duration-300">
         <div>
-            <label for="fabric-loader-select" class="block text-xs text-white/40 mb-2 pl-1"
+            <label for="fabric-loader-select" class="block text-xs text-gray-500 dark:text-white/40 mb-2 pl-1"
             >Loader Version</label
             >
             <div class="relative">
                 <select
                 id="fabric-loader-select"
-                class="w-full appearance-none bg-black/40 border border-white/10 rounded-xl px-4 py-3 text-sm focus:outline-none focus:border-indigo-500/50 text-white transition-colors"
+                class="w-full appearance-none bg-white/80 dark:bg-black/40 border border-black/10 dark:border-white/10 rounded-xl px-4 py-3 text-sm focus:outline-none focus:border-indigo-500/50 text-gray-900 dark:text-white transition-colors"
                 bind:value={selectedFabricLoader}
                 >
                 {#each fabricLoaders as loader}
@@ -189,7 +189,7 @@
                     </option>
                 {/each}
                 </select>
-                <div class="absolute right-4 top-1/2 -translate-y-1/2 text-white/20 pointer-events-none">▼</div>
+                <div class="absolute right-4 top-1/2 -translate-y-1/2 text-black/30 dark:text-white/20 pointer-events-none">▼</div>
             </div>
         </div>
         
@@ -205,18 +205,18 @@
     {:else if selectedLoader === "forge"}
         <div class="space-y-4 animate-in fade-in slide-in-from-bottom-2 duration-300">
         {#if forgeVersions.length === 0}
-            <div class="text-center p-4 text-sm text-white/40 italic">
+            <div class="text-center p-4 text-sm text-gray-500 dark:text-white/40 italic">
             No Forge versions available for {selectedGameVersion}
             </div>
         {:else}
             <div>
-            <label for="forge-version-select" class="block text-xs text-white/40 mb-2 pl-1"
+            <label for="forge-version-select" class="block text-xs text-gray-500 dark:text-white/40 mb-2 pl-1"
                 >Forge Version</label
             >
             <div class="relative">
                 <select
                     id="forge-version-select"
-                    class="w-full appearance-none bg-black/40 border border-white/10 rounded-xl px-4 py-3 text-sm focus:outline-none focus:border-orange-500/50 text-white transition-colors"
+                    class="w-full appearance-none bg-white/80 dark:bg-black/40 border border-black/10 dark:border-white/10 rounded-xl px-4 py-3 text-sm focus:outline-none focus:border-orange-500/50 text-gray-900 dark:text-white transition-colors"
                     bind:value={selectedForgeVersion}
                 >
                     {#each forgeVersions as version}
@@ -227,7 +227,7 @@
                     </option>
                     {/each}
                 </select>
-                <div class="absolute right-4 top-1/2 -translate-y-1/2 text-white/20 pointer-events-none">▼</div>
+                <div class="absolute right-4 top-1/2 -translate-y-1/2 text-black/30 dark:text-white/20 pointer-events-none">▼</div>
             </div>
             </div>
             
diff --git a/ui/src/components/SettingsView.svelte b/ui/src/components/SettingsView.svelte
index 4c92220..86bcce1 100644
--- a/ui/src/components/SettingsView.svelte
+++ b/ui/src/components/SettingsView.svelte
@@ -32,24 +32,24 @@
 
 <div class="h-full flex flex-col p-6 overflow-hidden">
   <div class="flex items-center justify-between mb-6">
-     <h2 class="text-3xl font-black bg-clip-text text-transparent bg-gradient-to-r from-white to-white/60">Settings</h2>
+     <h2 class="text-3xl font-black bg-clip-text text-transparent bg-gradient-to-r dark:from-white dark:to-white/60 from-gray-900 to-gray-600">Settings</h2>
   </div>
 
   <div class="flex-1 overflow-y-auto pr-2 space-y-6 custom-scrollbar pb-10">
 
     <!-- Appearance / Background -->
-    <div class="bg-black/20 p-6 rounded-2xl border border-white/5 ">
-      <h3 class="text-xs font-bold uppercase tracking-widest text-white/40 mb-6 flex items-center gap-2">
+    <div class="dark:bg-black/20 bg-white/60 p-6 rounded-2xl border dark:border-white/5 border-black/5 shadow-sm backdrop-blur-sm">
+      <h3 class="text-xs font-bold uppercase tracking-widest dark:text-white/40 text-black/40 mb-6 flex items-center gap-2">
         Appearance
       </h3>
       
       <div class="space-y-4">
         <div>
-          <label class="block text-sm font-medium text-white/70 mb-3">Custom Background Image</label>
+          <label class="block text-sm font-medium dark:text-white/70 text-black/70 mb-3">Custom Background Image</label>
           
           <div class="flex items-center gap-6">
             <!-- Preview -->
-            <div class="w-40 h-24 rounded-xl overflow-hidden bg-black/50 border border-white/10 relative group shadow-lg">
+            <div class="w-40 h-24 rounded-xl overflow-hidden dark:bg-black/50 bg-gray-200 border dark:border-white/10 border-black/10 relative group shadow-lg">
               {#if settingsState.settings.custom_background_path}
                 <img 
                   src={convertFileSrc(settingsState.settings.custom_background_path)} 
@@ -66,7 +66,7 @@
             <div class="flex flex-col gap-2">
               <button
                 onclick={selectBackground}
-                class="bg-white/10 hover:bg-white/20 text-white px-4 py-2 rounded-lg text-sm transition-colors border border-white/5"
+                class="dark:bg-white/10 dark:hover:bg-white/20 bg-black/5 hover:bg-black/10 dark:text-white text-black px-4 py-2 rounded-lg text-sm transition-colors border dark:border-white/5 border-black/5"
               >
                 Select Image
               </button>
@@ -81,39 +81,39 @@
               {/if}
             </div>
           </div>
-          <p class="text-xs text-white/30 mt-3">
+          <p class="text-xs dark:text-white/30 text-black/40 mt-3">
             Select an image from your computer to replace the default gradient background.
             Supported formats: PNG, JPG, WEBP, GIF.
           </p>
         </div>
 
         <!-- Visual Settings -->
-        <div class="pt-4 border-t border-white/5 space-y-4">
+        <div class="pt-4 border-t dark:border-white/5 border-black/5 space-y-4">
              <div class="flex items-center justify-between">
                 <div>
-                   <h4 class="text-sm font-medium text-white/90" id="visual-effects-label">Visual Effects</h4>
-                   <p class="text-xs text-white/40 mt-1">Enable particle effects and animated gradients. (Default: On)</p>
+                   <h4 class="text-sm font-medium dark:text-white/90 text-black/80" id="visual-effects-label">Visual Effects</h4>
+                   <p class="text-xs dark:text-white/40 text-black/50 mt-1">Enable particle effects and animated gradients. (Default: On)</p>
                 </div>
                 <button 
                     aria-labelledby="visual-effects-label"
                     onclick={() => { settingsState.settings.enable_visual_effects = !settingsState.settings.enable_visual_effects; settingsState.saveSettings(); }}
-                    class="w-11 h-6 rounded-full transition-colors duration-200 ease-in-out relative focus:outline-none {settingsState.settings.enable_visual_effects ? 'bg-indigo-500' : 'bg-white/10'}"
+                    class="w-11 h-6 rounded-full transition-colors duration-200 ease-in-out relative focus:outline-none {settingsState.settings.enable_visual_effects ? 'bg-indigo-500' : 'dark:bg-white/10 bg-black/10'}"
                 >
                     <div class="absolute top-1 left-1 bg-white w-4 h-4 rounded-full shadow-sm transition-transform duration-200 ease-in-out {settingsState.settings.enable_visual_effects ? 'translate-x-5' : 'translate-x-0'}"></div>
                 </button>
              </div>
 
              {#if settingsState.settings.enable_visual_effects}
-                 <div class="flex items-center justify-between pl-2 border-l-2 border-white/5 ml-1">
+                 <div class="flex items-center justify-between pl-2 border-l-2 dark:border-white/5 border-black/5 ml-1">
                     <div>
-                       <h4 class="text-sm font-medium text-white/90" id="theme-effect-label">Theme Effect</h4>
-                       <p class="text-xs text-white/40 mt-1">Select the active visual theme.</p>
+                       <h4 class="text-sm font-medium dark:text-white/90 text-black/80" id="theme-effect-label">Theme Effect</h4>
+                       <p class="text-xs dark:text-white/40 text-black/50 mt-1">Select the active visual theme.</p>
                     </div>
                     <select
                         aria-labelledby="theme-effect-label"
                         bind:value={settingsState.settings.active_effect}
                         onchange={() => settingsState.saveSettings()}
-                        class="bg-black/40 text-white text-xs px-3 py-2 rounded-lg border border-white/10 outline-none focus:border-indigo-500/50 appearance-none cursor-pointer hover:bg-white/5 transition-colors"
+                        class="dark:bg-black/40 bg-white dark:text-white text-black text-xs px-3 py-2 rounded-lg border dark:border-white/10 border-black/10 outline-none focus:border-indigo-500/50 appearance-none cursor-pointer hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
                     >
                         <option value="saturn">Saturn (Saturn)</option>
                         <option value="constellation">Network (Constellation)</option>
@@ -123,23 +123,45 @@
 
              <div class="flex items-center justify-between">
                 <div>
-                   <h4 class="text-sm font-medium text-white/90" id="gpu-acceleration-label">GPU Acceleration</h4>
-                   <p class="text-xs text-white/40 mt-1">Enable GPU acceleration for the interface. (Default: Off, Requires Restart)</p>
+                   <h4 class="text-sm font-medium dark:text-white/90 text-black/80" id="gpu-acceleration-label">GPU Acceleration</h4>
+                   <p class="text-xs dark:text-white/40 text-black/50 mt-1">Enable GPU acceleration for the interface. (Default: Off, Requires Restart)</p>
                 </div>
                 <button 
                     aria-labelledby="gpu-acceleration-label"
                     onclick={() => { settingsState.settings.enable_gpu_acceleration = !settingsState.settings.enable_gpu_acceleration; settingsState.saveSettings(); }}
-                    class="w-11 h-6 rounded-full transition-colors duration-200 ease-in-out relative focus:outline-none {settingsState.settings.enable_gpu_acceleration ? 'bg-indigo-500' : 'bg-white/10'}"
+                    class="w-11 h-6 rounded-full transition-colors duration-200 ease-in-out relative focus:outline-none {settingsState.settings.enable_gpu_acceleration ? 'bg-indigo-500' : 'dark:bg-white/10 bg-black/10'}"
                 >
                     <div class="absolute top-1 left-1 bg-white w-4 h-4 rounded-full shadow-sm transition-transform duration-200 ease-in-out {settingsState.settings.enable_gpu_acceleration ? 'translate-x-5' : 'translate-x-0'}"></div>
                 </button>
              </div>
+             
+             <!-- Color Theme Switcher -->
+             <div class="flex items-center justify-between pt-4 border-t dark:border-white/5 border-black/5 opacity-50 cursor-not-allowed">
+                <div>
+                   <h4 class="text-sm font-medium dark:text-white/90 text-black/80" id="color-theme-label">Color Theme</h4>
+                   <p class="text-xs dark:text-white/40 text-black/50 mt-1">Interface color mode. (Locked to Dark)</p>
+                </div>
+                <div class="flex items-center bg-black/5 dark:bg-white/5 rounded-lg p-1 pointer-events-none">
+                    <button
+                        disabled
+                        class="px-3 py-1 rounded-md text-xs font-medium transition-all text-gray-500 dark:text-gray-600"
+                    >
+                        Light
+                    </button>
+                    <button
+                        disabled
+                        class="px-3 py-1 rounded-md text-xs font-medium transition-all bg-indigo-500 text-white shadow-sm"
+                    >
+                        Dark
+                    </button>
+                </div>
+             </div>
         </div>
       </div>
     </div>
 
     <!-- Java Path -->
-    <div class="bg-black/20 p-6 rounded-2xl border border-white/5 ">
+    <div class="dark:bg-black/20 bg-white/60 p-6 rounded-2xl border dark:border-white/5 border-black/5 shadow-sm backdrop-blur-sm">
       <h3 class="text-xs font-bold uppercase tracking-widest text-white/40 mb-6 flex items-center gap-2">
         Java Environment
       </h3>
diff --git a/ui/src/components/Sidebar.svelte b/ui/src/components/Sidebar.svelte
index 7976f6a..e6fbf43 100644
--- a/ui/src/components/Sidebar.svelte
+++ b/ui/src/components/Sidebar.svelte
@@ -3,7 +3,7 @@
 </script>
 
 <aside
-  class="w-20 lg:w-64 bg-black flex flex-col items-center lg:items-start transition-all duration-300 shrink-0 py-6"
+  class="w-20 lg:w-64 dark:bg-black bg-white/80 border-r dark:border-white/5 border-gray-200/50 flex flex-col items-center lg:items-start transition-all duration-300 shrink-0 py-6 backdrop-blur-md"
 >
   <!-- Logo Area -->
   <div
@@ -17,7 +17,7 @@
     </div>
     <!-- Full Logo (Large) -->
     <div
-      class="hidden lg:block font-bold text-2xl tracking-wider text-white"
+      class="hidden lg:block font-bold text-2xl tracking-wider dark:text-white text-gray-900"
     >
       <span class="bg-clip-text text-transparent bg-gradient-to-r from-indigo-400 to-fuchsia-400">DROP</span>OUT
     </div>
@@ -30,8 +30,8 @@
       <button
         class="group flex items-center lg:gap-4 justify-center lg:justify-start w-full px-0 lg:px-5 py-3.5 rounded-xl transition-all duration-200 relative overflow-hidden
         {uiState.currentView === view
-          ? 'bg-gradient-to-r from-indigo-500/20 to-purple-500/20 text-white shadow-lg shadow-indigo-500/10 border border-white/10'
-          : 'text-zinc-400 hover:text-white hover:bg-white/5'}"
+          ? 'bg-gradient-to-r from-indigo-500/20 to-purple-500/20 dark:text-white text-indigo-900 shadow-lg shadow-indigo-500/10 dark:border border-white/10'
+          : 'dark:text-zinc-400 text-gray-500 hover:text-black dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5'}"
         onclick={() => uiState.setView(view)}
       >
         <span class="text-xl relative z-10 transition-transform group-hover:scale-110 duration-200">{icon}</span>
diff --git a/ui/src/components/StatusToast.svelte b/ui/src/components/StatusToast.svelte
index 0d68778..4c981c7 100644
--- a/ui/src/components/StatusToast.svelte
+++ b/ui/src/components/StatusToast.svelte
@@ -5,19 +5,19 @@
 {#if uiState.status !== "Ready"}
   {#key uiState.status}
     <div
-      class="absolute top-12 right-12 bg-zinc-800/90 backdrop-blur border border-zinc-600 p-4 rounded-lg shadow-2xl max-w-sm animate-in fade-in slide-in-from-top-4 duration-300 z-50 group"
+      class="absolute top-12 right-12 bg-white/90 dark:bg-zinc-800/90 backdrop-blur border border-zinc-200 dark:border-zinc-600 p-4 rounded-lg shadow-2xl max-w-sm animate-in fade-in slide-in-from-top-4 duration-300 z-50 group"
     >
       <div class="flex justify-between items-start mb-1">
-        <div class="text-xs text-zinc-400 uppercase font-bold">Status</div>
+        <div class="text-xs text-zinc-500 dark:text-zinc-400 uppercase font-bold">Status</div>
         <button
           onclick={() => uiState.setStatus("Ready")}
-          class="text-zinc-500 hover:text-white transition -mt-1 -mr-1 p-1"
+          class="text-zinc-400 hover:text-black dark:text-zinc-500 dark:hover:text-white transition -mt-1 -mr-1 p-1"
         >
           ✕
         </button>
       </div>
-      <div class="font-mono text-sm whitespace-pre-wrap mb-2">{uiState.status}</div>
-      <div class="w-full bg-zinc-700/50 h-1 rounded-full overflow-hidden">
+      <div class="font-mono text-sm whitespace-pre-wrap mb-2 text-gray-900 dark:text-gray-100">{uiState.status}</div>
+      <div class="w-full bg-gray-200 dark:bg-zinc-700/50 h-1 rounded-full overflow-hidden">
         <div
           class="h-full bg-indigo-500 origin-left w-full progress-bar"
         ></div>
diff --git a/ui/src/components/VersionsView.svelte b/ui/src/components/VersionsView.svelte
index 00ac281..8f3a568 100644
--- a/ui/src/components/VersionsView.svelte
+++ b/ui/src/components/VersionsView.svelte
@@ -73,15 +73,15 @@
   function getVersionBadge(type: string) {
     switch (type) {
       case "release":
-        return { text: "Release", class: "bg-emerald-500/20 text-emerald-300 border-emerald-500/30" };
+        return { text: "Release", class: "bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-500/20 dark:text-emerald-300 dark:border-emerald-500/30" };
       case "snapshot":
-        return { text: "Snapshot", class: "bg-amber-500/20 text-amber-300 border-amber-500/30" };
+        return { text: "Snapshot", class: "bg-amber-100 text-amber-700 border-amber-200 dark:bg-amber-500/20 dark:text-amber-300 dark:border-amber-500/30" };
       case "fabric":
-        return { text: "Fabric", class: "bg-indigo-500/20 text-indigo-300 border-indigo-500/30" };
+        return { text: "Fabric", class: "bg-indigo-100 text-indigo-700 border-indigo-200 dark:bg-indigo-500/20 dark:text-indigo-300 dark:border-indigo-500/30" };
       case "forge":
-        return { text: "Forge", class: "bg-orange-500/20 text-orange-300 border-orange-500/30" };
+        return { text: "Forge", class: "bg-orange-100 text-orange-700 border-orange-200 dark:bg-orange-500/20 dark:text-orange-300 dark:border-orange-500/30" };
       default:
-        return { text: type, class: "bg-zinc-500/20 text-zinc-300 border-zinc-500/30" };
+        return { text: type, class: "bg-zinc-100 text-zinc-700 border-zinc-200 dark:bg-zinc-500/20 dark:text-zinc-300 dark:border-zinc-500/30" };
     }
   }
 
@@ -116,8 +116,8 @@
 
 <div class="h-full flex flex-col p-6 overflow-hidden">
   <div class="flex items-center justify-between mb-6">
-     <h2 class="text-3xl font-black bg-clip-text text-transparent bg-gradient-to-r from-white to-white/60">Version Manager</h2>
-     <div class="text-sm text-white/40">Select a version to play or modify</div>
+     <h2 class="text-3xl font-black bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-600 dark:from-white dark:to-white/60">Version Manager</h2>
+     <div class="text-sm dark:text-white/40 text-black/50">Select a version to play or modify</div>
   </div>
 
   <div class="flex-1 grid grid-cols-1 lg:grid-cols-3 gap-6 overflow-hidden">
@@ -126,24 +126,24 @@
       <!-- Search and Filters (Glass Bar) -->
       <div class="flex gap-3">
         <div class="relative flex-1">
-            <span class="absolute left-3 top-1/2 -translate-y-1/2 text-white/30">🔍</span>
+            <span class="absolute left-3 top-1/2 -translate-y-1/2 dark:text-white/30 text-black/30">🔍</span>
             <input
               type="text"
               placeholder="Search versions..."
-              class="w-full pl-9 pr-4 py-3 bg-black/20 border border-white/10 rounded-xl text-white placeholder-white/30 focus:outline-none focus:border-indigo-500/50 focus:bg-black/40 transition-all backdrop-blur-sm"
+              class="w-full pl-9 pr-4 py-3 bg-white/60 dark:bg-black/20 border border-black/10 dark:border-white/10 rounded-xl dark:text-white text-gray-900 placeholder-black/30 dark:placeholder-white/30 focus:outline-none focus:border-indigo-500/50 dark:focus:bg-black/40 focus:bg-white/80 transition-all backdrop-blur-sm"
               bind:value={searchQuery}
             />
         </div>
       </div>
 
       <!-- Type Filter Tabs (Glass Caps) -->
-      <div class="flex p-1 bg-black/20 rounded-xl border border-white/5">
+      <div class="flex p-1 bg-white/60 dark:bg-black/20 rounded-xl border border-black/5 dark:border-white/5">
         {#each ['all', 'release', 'snapshot', 'modded'] as filter}
             <button
             class="flex-1 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-200 capitalize
             {typeFilter === filter
-                ? 'bg-white/10 text-white shadow-lg border border-white/10'
-                : 'text-white/40 hover:text-white hover:bg-white/5'}"
+                ? 'bg-white shadow-sm border border-black/5 dark:bg-white/10 dark:text-white dark:shadow-lg dark:border-white/10 text-black'
+                : 'text-black/40 dark:text-white/40 hover:text-black dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5'}"
             onclick={() => (typeFilter = filter as any)}
             >
             {filter}
@@ -154,11 +154,11 @@
       <!-- Version List SCROLL -->
       <div class="flex-1 overflow-y-auto pr-2 space-y-2 custom-scrollbar">
         {#if gameState.versions.length === 0}
-          <div class="flex items-center justify-center h-40 text-white/30 italic animate-pulse">
+          <div class="flex items-center justify-center h-40 dark:text-white/30 text-black/30 italic animate-pulse">
              Fetching manifest...
           </div>
         {:else if filteredVersions().length === 0}
-          <div class="flex flex-col items-center justify-center -40 text-white/30 gap-2">
+          <div class="flex flex-col items-center justify-center -40 dark:text-white/30 text-black/30 gap-2">
              <span class="text-2xl">👻</span>
              <span>No matching versions found</span>
           </div>
@@ -169,8 +169,8 @@
             <button
               class="w-full group flex items-center justify-between p-4 rounded-xl text-left border transition-all duration-200 relative overflow-hidden
               {isSelected
-                ? 'bg-indigo-600/20 border-indigo-500/50 shadow-[0_0_20px_rgba(99,102,241,0.2)]'
-                : 'bg-white/5 border-white/5 hover:bg-white/10 hover:border-white/10 hover:translate-x-1'}"
+                ? 'bg-indigo-50 border-indigo-200 dark:bg-indigo-600/20 dark:border-indigo-500/50 shadow-[0_0_20px_rgba(99,102,241,0.2)]'
+                : 'bg-white/40 dark:bg-white/5 border-black/5 dark:border-white/5 hover:bg-white/60 dark:hover:bg-white/10 hover:border-black/10 dark:hover:border-white/10 hover:translate-x-1'}"
               onclick={() => (gameState.selectedVersion = version.id)}
             >
               <!-- Selection Glow -->
@@ -185,11 +185,11 @@
                   {badge.text}
                 </span>
                 <div>
-                  <div class="font-bold font-mono text-lg tracking-tight {isSelected ? 'text-white' : 'text-zinc-300 group-hover:text-white'}">
+                  <div class="font-bold font-mono text-lg tracking-tight {isSelected ? 'text-black dark:text-white' : 'text-gray-700 dark:text-zinc-300 group-hover:text-black dark:group-hover:text-white'}">
                     {version.id}
                   </div>
                   {#if version.releaseTime && version.type !== "fabric" && version.type !== "forge"}
-                    <div class="text-xs text-white/30">
+                    <div class="text-xs dark:text-white/30 text-black/30">
                       {new Date(version.releaseTime).toLocaleDateString()}
                     </div>
                   {/if}
@@ -197,7 +197,7 @@
               </div>
               
               {#if isSelected}
-                <div class="relative z-10 text-indigo-400">
+                <div class="relative z-10 text-indigo-500 dark:text-indigo-400">
                    <span class="text-lg">Selected</span>
                 </div>
               {/if}
@@ -210,21 +210,21 @@
     <!-- Right: Mod Loader Panel -->
     <div class="flex flex-col gap-4">
       <!-- Selected Version Info Card -->
-      <div class="bg-gradient-to-br from-white/10 to-white/5 p-6 rounded-2xl border border-white/10 backdrop-blur-md relative overflow-hidden group">
+      <div class="bg-gradient-to-br from-white/40 to-white/20 dark:from-white/10 dark:to-white/5 p-6 rounded-2xl border border-black/5 dark:border-white/10 backdrop-blur-md relative overflow-hidden group">
           <div class="absolute top-0 right-0 p-8 bg-indigo-500/20 blur-[60px] rounded-full group-hover:bg-indigo-500/30 transition-colors"></div>
           
-          <h3 class="text-xs font-bold uppercase tracking-widest text-white/40 mb-2 relative z-10">Current Selection</h3>
+          <h3 class="text-xs font-bold uppercase tracking-widest dark:text-white/40 text-black/40 mb-2 relative z-10">Current Selection</h3>
           {#if gameState.selectedVersion}
-            <p class="font-mono text-3xl font-black text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 relative z-10 truncate">
+            <p class="font-mono text-3xl font-black text-transparent bg-clip-text bg-gradient-to-r from-gray-900 to-gray-600 dark:from-white dark:to-white/70 relative z-10 truncate">
                 {gameState.selectedVersion}
             </p>
           {:else}
-            <p class="text-white/20 italic relative z-10">None selected</p>
+            <p class="dark:text-white/20 text-black/20 italic relative z-10">None selected</p>
           {/if}
       </div>
 
       <!-- Mod Loader Selector Card -->
-      <div class="bg-black/20 p-4 rounded-2xl border border-white/5 backdrop-blur-sm flex-1 flex flex-col">
+      <div class="bg-white/60 dark:bg-black/20 p-4 rounded-2xl border border-black/5 dark:border-white/5 backdrop-blur-sm flex-1 flex flex-col">
           <ModLoaderSelector
             selectedGameVersion={selectedBaseVersion()}
             onInstall={handleModLoaderInstall}
diff --git a/ui/src/stores/settings.svelte.ts b/ui/src/stores/settings.svelte.ts
index c59bf3c..b67cdc3 100644
--- a/ui/src/stores/settings.svelte.ts
+++ b/ui/src/stores/settings.svelte.ts
@@ -13,6 +13,7 @@ export class SettingsState {
     enable_gpu_acceleration: false,
     enable_visual_effects: true,
     active_effect: "constellation",
+    theme: "dark",
   });
   javaInstallations = $state<JavaInstallation[]>([]);
   isDetectingJava = $state(false);
@@ -21,6 +22,11 @@ export class SettingsState {
     try {
       const result = await invoke<LauncherConfig>("get_settings");
       this.settings = result;
+      // Force dark mode
+      if (this.settings.theme !== "dark") {
+          this.settings.theme = "dark";
+          this.saveSettings();
+      }
     } catch (e) {
       console.error("Failed to load settings:", e);
     }
diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts
index fa48075..7e2cc67 100644
--- a/ui/src/types/index.ts
+++ b/ui/src/types/index.ts
@@ -34,6 +34,7 @@ export interface LauncherConfig {
   enable_gpu_acceleration: boolean;
   enable_visual_effects: boolean;
   active_effect: string;
+  theme: string;
 }
 
 export interface JavaInstallation {
-- 
cgit v1.2.3-70-g09d2


From 8e68ed3ae6fe153418600692d4027a996a1ec089 Mon Sep 17 00:00:00 2001
From: HsiangNianian <i@jyunko.cn>
Date: Wed, 14 Jan 2026 19:24:29 +0800
Subject: feat: enforce dark mode by always applying 'dark' class and attribute

---
 ui/src/App.svelte | 15 ++++++++-------
 ui/src/app.css    |  2 ++
 2 files changed, 10 insertions(+), 7 deletions(-)

(limited to 'ui/src/App.svelte')

diff --git a/ui/src/App.svelte b/ui/src/App.svelte
index 2160b85..1c465b1 100644
--- a/ui/src/App.svelte
+++ b/ui/src/App.svelte
@@ -38,13 +38,14 @@
   });
   
   $effect(() => {
-    if (settingsState.settings.theme === 'light') {
-        document.documentElement.classList.remove('dark');
-        document.documentElement.setAttribute('data-theme', 'light');
-    } else {
-        document.documentElement.classList.add('dark');
-        document.documentElement.setAttribute('data-theme', 'dark');
-    }
+    // ENFORCE DARK MODE: Always add 'dark' class and attribute
+    // This combined with the @variant dark in app.css ensures dark mode is always active
+    // regardless of system preference settings.
+    document.documentElement.classList.add('dark');
+    document.documentElement.setAttribute('data-theme', 'dark');
+    
+    // Ensure 'light' class is never present
+    document.documentElement.classList.remove('light');
   });
 
   onDestroy(() => {
diff --git a/ui/src/app.css b/ui/src/app.css
index f1d8c73..2ea9a8c 100644
--- a/ui/src/app.css
+++ b/ui/src/app.css
@@ -1 +1,3 @@
 @import "tailwindcss";
+
+@variant dark (&:where(.dark, .dark *));
-- 
cgit v1.2.3-70-g09d2


From 1b92b559a37166796d431ade745b31c4f73bc2ed Mon Sep 17 00:00:00 2001
From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com>
Date: Wed, 14 Jan 2026 22:20:41 +0100
Subject: fix(ui): correct styling bugs and code quality issues
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Fix missing h- prefix in VersionsView height class (-40 → h-40)
- Correct typo in SaturnEffect comment (Warn → Warm White)
- Remove double space in App.svelte class attribute
---
 ui/src/App.svelte                     | 2 +-
 ui/src/components/VersionsView.svelte | 2 +-
 ui/src/lib/effects/SaturnEffect.ts    | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

(limited to 'ui/src/App.svelte')

diff --git a/ui/src/App.svelte b/ui/src/App.svelte
index 1c465b1..968f6c5 100644
--- a/ui/src/App.svelte
+++ b/ui/src/App.svelte
@@ -140,7 +140,7 @@
   
   {#if uiState.showConsole}
      <!-- Assuming GameConsole handles its own display mode or overlay -->
-    <div class="fixed inset-0 z-[100] bg-black/80  flex items-center justify-center p-10">
+    <div class="fixed inset-0 z-[100] bg-black/80 flex items-center justify-center p-10">
         <div class="w-full h-full bg-[#1e1e1e] rounded-xl overflow-hidden border border-white/10 shadow-2xl relative">
             <button class="absolute top-4 right-4 text-white hover:text-red-400 z-10" onclick={() => uiState.toggleConsole()}>✕</button>
             <GameConsole />
diff --git a/ui/src/components/VersionsView.svelte b/ui/src/components/VersionsView.svelte
index 8f3a568..99cc296 100644
--- a/ui/src/components/VersionsView.svelte
+++ b/ui/src/components/VersionsView.svelte
@@ -158,7 +158,7 @@
              Fetching manifest...
           </div>
         {:else if filteredVersions().length === 0}
-          <div class="flex flex-col items-center justify-center -40 dark:text-white/30 text-black/30 gap-2">
+          <div class="flex flex-col items-center justify-center h-40 dark:text-white/30 text-black/30 gap-2">
              <span class="text-2xl">👻</span>
              <span>No matching versions found</span>
           </div>
diff --git a/ui/src/lib/effects/SaturnEffect.ts b/ui/src/lib/effects/SaturnEffect.ts
index 8a1c11f..a370936 100644
--- a/ui/src/lib/effects/SaturnEffect.ts
+++ b/ui/src/lib/effects/SaturnEffect.ts
@@ -171,7 +171,7 @@ export class SaturnEffect {
 
             // Optimization: Planet color vs Ring color
             if (type === 0) {
-               // Planet: Warn White
+               // Planet: Warm White
                this.ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`;
             } else {
                // Ring: Cool White
-- 
cgit v1.2.3-70-g09d2


From 43a3e9c285f3d5d04fef025041a06609a0d1c218 Mon Sep 17 00:00:00 2001
From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com>
Date: Thu, 15 Jan 2026 05:29:58 +0100
Subject: feat(java): Implement Java catalog management and download features

- Added commands to fetch and refresh the Java catalog, cancel downloads, and manage pending downloads.
- Enhanced the Java download modal in the UI to support version selection, download progress, and pending downloads.
- Introduced new types for Java catalog, download progress, and pending downloads.
- Updated settings store to handle Java catalog state, download progress, and pending downloads.
- Improved user experience with loading states, error handling, and status notifications for Java installations.
---
 src-tauri/Cargo.toml                  |   2 +-
 src-tauri/src/core/downloader.rs      | 400 +++++++++++++++++++++++++++++++++-
 src-tauri/src/core/java.rs            | 369 ++++++++++++++++++++++++++++---
 src-tauri/src/main.rs                 |  52 +++++
 ui/src/App.svelte                     |  23 +-
 ui/src/components/SettingsView.svelte | 375 ++++++++++++++++++++++++++-----
 ui/src/stores/settings.svelte.ts      | 294 ++++++++++++++++++++++---
 ui/src/types/index.ts                 |  44 ++++
 8 files changed, 1429 insertions(+), 130 deletions(-)

(limited to 'ui/src/App.svelte')

diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index bc831fb..27a0e0c 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -13,7 +13,7 @@ toml = "0.5"
 log = "0.4"
 env_logger = "0.9"
 tokio = { version = "1.49.0", features = ["full"] }
-reqwest = { version = "0.13.1", features = ["json", "blocking"] }
+reqwest = { version = "0.13.1", features = ["json", "blocking", "stream"] }
 serde_json = "1.0.149"
 tauri = { version = "2.9", features = [] }
 tauri-plugin-shell = "2.3"
diff --git a/src-tauri/src/core/downloader.rs b/src-tauri/src/core/downloader.rs
index d33c44d..bf6334f 100644
--- a/src-tauri/src/core/downloader.rs
+++ b/src-tauri/src/core/downloader.rs
@@ -2,10 +2,10 @@ use futures::StreamExt;
 use serde::{Deserialize, Serialize};
 use sha1::Digest as Sha1Digest;
 use std::path::PathBuf;
-use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
+use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
 use std::sync::Arc;
-use tauri::{Emitter, Window};
-use tokio::io::AsyncWriteExt;
+use tauri::{AppHandle, Emitter, Manager, Window};
+use tokio::io::{AsyncSeekExt, AsyncWriteExt};
 use tokio::sync::Semaphore;
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -18,6 +18,400 @@ pub struct DownloadTask {
     pub sha256: Option<String>,
 }
 
+/// Metadata for resumable downloads stored in .part.meta file
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct DownloadMetadata {
+    pub url: String,
+    pub file_name: String,
+    pub total_size: u64,
+    pub downloaded_bytes: u64,
+    pub checksum: Option<String>,
+    pub timestamp: u64,
+    pub segments: Vec<DownloadSegment>,
+}
+
+/// A download segment for multi-segment parallel downloading
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct DownloadSegment {
+    pub start: u64,
+    pub end: u64,
+    pub downloaded: u64,
+    pub completed: bool,
+}
+
+/// Progress event for Java download
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JavaDownloadProgress {
+    pub file_name: String,
+    pub downloaded_bytes: u64,
+    pub total_bytes: u64,
+    pub speed_bytes_per_sec: u64,
+    pub eta_seconds: u64,
+    pub status: String, // "Downloading", "Extracting", "Verifying", "Completed", "Paused", "Error"
+    pub percentage: f32,
+}
+
+/// Pending download task for queue persistence
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct PendingJavaDownload {
+    pub major_version: u32,
+    pub image_type: String,
+    pub download_url: String,
+    pub file_name: String,
+    pub file_size: u64,
+    pub checksum: Option<String>,
+    pub install_path: String,
+    pub created_at: u64,
+}
+
+/// Download queue for persistence
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct DownloadQueue {
+    pub pending_downloads: Vec<PendingJavaDownload>,
+}
+
+impl DownloadQueue {
+    /// Load download queue from file
+    pub fn load(app_handle: &AppHandle) -> Self {
+        let queue_path = app_handle
+            .path()
+            .app_data_dir()
+            .unwrap()
+            .join("download_queue.json");
+        if queue_path.exists() {
+            if let Ok(content) = std::fs::read_to_string(&queue_path) {
+                if let Ok(queue) = serde_json::from_str(&content) {
+                    return queue;
+                }
+            }
+        }
+        Self::default()
+    }
+
+    /// Save download queue to file
+    pub fn save(&self, app_handle: &AppHandle) -> Result<(), String> {
+        let queue_path = app_handle
+            .path()
+            .app_data_dir()
+            .unwrap()
+            .join("download_queue.json");
+        let content = serde_json::to_string_pretty(self).map_err(|e| e.to_string())?;
+        std::fs::write(&queue_path, content).map_err(|e| e.to_string())?;
+        Ok(())
+    }
+
+    /// Add a pending download
+    pub fn add(&mut self, download: PendingJavaDownload) {
+        // Remove existing download for same version/type
+        self.pending_downloads.retain(|d| {
+            !(d.major_version == download.major_version && d.image_type == download.image_type)
+        });
+        self.pending_downloads.push(download);
+    }
+
+    /// Remove a completed or cancelled download
+    pub fn remove(&mut self, major_version: u32, image_type: &str) {
+        self.pending_downloads.retain(|d| {
+            !(d.major_version == major_version && d.image_type == image_type)
+        });
+    }
+}
+
+/// Global cancel flag for Java downloads
+pub static JAVA_DOWNLOAD_CANCELLED: AtomicBool = AtomicBool::new(false);
+
+/// Reset the cancel flag
+pub fn reset_java_download_cancel() {
+    JAVA_DOWNLOAD_CANCELLED.store(false, Ordering::SeqCst);
+}
+
+/// Cancel the current Java download
+pub fn cancel_java_download() {
+    JAVA_DOWNLOAD_CANCELLED.store(true, Ordering::SeqCst);
+}
+
+/// Check if download is cancelled
+pub fn is_java_download_cancelled() -> bool {
+    JAVA_DOWNLOAD_CANCELLED.load(Ordering::SeqCst)
+}
+
+/// Determine optimal segment count based on file size
+fn get_segment_count(file_size: u64) -> usize {
+    if file_size < 20 * 1024 * 1024 {
+        1 // < 20MB: single segment
+    } else if file_size < 100 * 1024 * 1024 {
+        4 // 20-100MB: 4 segments
+    } else {
+        8 // > 100MB: 8 segments
+    }
+}
+
+/// Download a large file with resume support and progress events
+pub async fn download_with_resume(
+    app_handle: &AppHandle,
+    url: &str,
+    dest_path: &PathBuf,
+    checksum: Option<&str>,
+    total_size: u64,
+) -> Result<(), String> {
+    reset_java_download_cancel();
+
+    let part_path = dest_path.with_extension(
+        dest_path
+            .extension()
+            .map(|e| format!("{}.part", e.to_string_lossy()))
+            .unwrap_or_else(|| "part".to_string()),
+    );
+    let meta_path = PathBuf::from(format!("{}.meta", part_path.display()));
+    let file_name = dest_path
+        .file_name()
+        .unwrap_or_default()
+        .to_string_lossy()
+        .to_string();
+
+    // Load or create metadata
+    let mut metadata = if meta_path.exists() {
+        let content = tokio::fs::read_to_string(&meta_path)
+            .await
+            .map_err(|e| e.to_string())?;
+        serde_json::from_str(&content).unwrap_or_else(|_| create_new_metadata(url, &file_name, total_size, checksum))
+    } else {
+        create_new_metadata(url, &file_name, total_size, checksum)
+    };
+
+    // Create parent directory
+    if let Some(parent) = dest_path.parent() {
+        tokio::fs::create_dir_all(parent)
+            .await
+            .map_err(|e| e.to_string())?;
+    }
+
+    // Open or create part file
+    let file = tokio::fs::OpenOptions::new()
+        .create(true)
+        .write(true)
+        .read(true)
+        .open(&part_path)
+        .await
+        .map_err(|e| format!("Failed to open part file: {}", e))?;
+
+    let file = Arc::new(tokio::sync::Mutex::new(file));
+    let client = reqwest::Client::new();
+    let progress = Arc::new(AtomicU64::new(metadata.downloaded_bytes));
+    let start_time = std::time::Instant::now();
+    let last_progress_bytes = Arc::new(AtomicU64::new(metadata.downloaded_bytes));
+
+    // Download segments concurrently
+    let segment_count = metadata.segments.len();
+    let semaphore = Arc::new(Semaphore::new(segment_count.min(8)));
+    let mut handles = Vec::new();
+
+    for (idx, segment) in metadata.segments.iter().enumerate() {
+        if segment.completed {
+            continue;
+        }
+
+        let client = client.clone();
+        let url = url.to_string();
+        let file = file.clone();
+        let progress = progress.clone();
+        let semaphore = semaphore.clone();
+        let segment_start = segment.start + segment.downloaded;
+        let segment_end = segment.end;
+        let app_handle = app_handle.clone();
+        let file_name = file_name.clone();
+        let total_size = total_size;
+        let last_progress_bytes = last_progress_bytes.clone();
+        let start_time = start_time.clone();
+
+        let handle = tokio::spawn(async move {
+            let _permit = semaphore.acquire().await.unwrap();
+
+            if is_java_download_cancelled() {
+                return Err("Download cancelled".to_string());
+            }
+
+            // Send Range request
+            let range = format!("bytes={}-{}", segment_start, segment_end);
+            let response = client
+                .get(&url)
+                .header("Range", &range)
+                .send()
+                .await
+                .map_err(|e| format!("Request failed: {}", e))?;
+
+            if !response.status().is_success() && response.status() != reqwest::StatusCode::PARTIAL_CONTENT {
+                return Err(format!("Server returned error: {}", response.status()));
+            }
+
+            let mut stream = response.bytes_stream();
+            let mut current_pos = segment_start;
+
+            while let Some(chunk_result) = stream.next().await {
+                if is_java_download_cancelled() {
+                    return Err("Download cancelled".to_string());
+                }
+
+                let chunk = chunk_result.map_err(|e| format!("Stream error: {}", e))?;
+                let chunk_len = chunk.len() as u64;
+
+                // Write to file at correct position
+                {
+                    let mut file_guard = file.lock().await;
+                    file_guard
+                        .seek(std::io::SeekFrom::Start(current_pos))
+                        .await
+                        .map_err(|e| format!("Seek error: {}", e))?;
+                    file_guard
+                        .write_all(&chunk)
+                        .await
+                        .map_err(|e| format!("Write error: {}", e))?;
+                }
+
+                current_pos += chunk_len;
+                let total_downloaded = progress.fetch_add(chunk_len, Ordering::Relaxed) + chunk_len;
+
+                // Emit progress event (throttled)
+                let last_bytes = last_progress_bytes.load(Ordering::Relaxed);
+                if total_downloaded - last_bytes > 100 * 1024 || total_downloaded >= total_size {
+                    last_progress_bytes.store(total_downloaded, Ordering::Relaxed);
+
+                    let elapsed = start_time.elapsed().as_secs_f64();
+                    let speed = if elapsed > 0.0 {
+                        (total_downloaded as f64 / elapsed) as u64
+                    } else {
+                        0
+                    };
+                    let remaining = total_size.saturating_sub(total_downloaded);
+                    let eta = if speed > 0 { remaining / speed } else { 0 };
+                    let percentage = (total_downloaded as f32 / total_size as f32) * 100.0;
+
+                    let _ = app_handle.emit(
+                        "java-download-progress",
+                        JavaDownloadProgress {
+                            file_name: file_name.clone(),
+                            downloaded_bytes: total_downloaded,
+                            total_bytes: total_size,
+                            speed_bytes_per_sec: speed,
+                            eta_seconds: eta,
+                            status: "Downloading".to_string(),
+                            percentage,
+                        },
+                    );
+                }
+            }
+
+            Ok::<usize, String>(idx)
+        });
+
+        handles.push(handle);
+    }
+
+    // Wait for all segments
+    let mut all_success = true;
+    for handle in handles {
+        match handle.await {
+            Ok(Ok(idx)) => {
+                metadata.segments[idx].completed = true;
+            }
+            Ok(Err(e)) => {
+                all_success = false;
+                if e.contains("cancelled") {
+                    // Save progress for resume
+                    metadata.downloaded_bytes = progress.load(Ordering::Relaxed);
+                    let meta_content = serde_json::to_string_pretty(&metadata).map_err(|e| e.to_string())?;
+                    tokio::fs::write(&meta_path, meta_content).await.ok();
+                    return Err(e);
+                }
+            }
+            Err(e) => {
+                all_success = false;
+                eprintln!("Segment task panicked: {}", e);
+            }
+        }
+    }
+
+    if !all_success {
+        // Save progress
+        metadata.downloaded_bytes = progress.load(Ordering::Relaxed);
+        let meta_content = serde_json::to_string_pretty(&metadata).map_err(|e| e.to_string())?;
+        tokio::fs::write(&meta_path, meta_content).await.ok();
+        return Err("Some segments failed".to_string());
+    }
+
+    // Verify checksum if provided
+    if let Some(expected) = checksum {
+        let _ = app_handle.emit(
+            "java-download-progress",
+            JavaDownloadProgress {
+                file_name: file_name.clone(),
+                downloaded_bytes: total_size,
+                total_bytes: total_size,
+                speed_bytes_per_sec: 0,
+                eta_seconds: 0,
+                status: "Verifying".to_string(),
+                percentage: 100.0,
+            },
+        );
+
+        let data = tokio::fs::read(&part_path)
+            .await
+            .map_err(|e| format!("Failed to read file for verification: {}", e))?;
+        
+        if !verify_checksum(&data, Some(expected), None) {
+            // Checksum failed, delete files and retry
+            tokio::fs::remove_file(&part_path).await.ok();
+            tokio::fs::remove_file(&meta_path).await.ok();
+            return Err("Checksum verification failed".to_string());
+        }
+    }
+
+    // Rename part file to final destination
+    tokio::fs::rename(&part_path, dest_path)
+        .await
+        .map_err(|e| format!("Failed to rename file: {}", e))?;
+
+    // Clean up metadata file
+    tokio::fs::remove_file(&meta_path).await.ok();
+
+    Ok(())
+}
+
+/// Create new download metadata with segments
+fn create_new_metadata(url: &str, file_name: &str, total_size: u64, checksum: Option<&str>) -> DownloadMetadata {
+    let segment_count = get_segment_count(total_size);
+    let segment_size = total_size / segment_count as u64;
+    let mut segments = Vec::new();
+
+    for i in 0..segment_count {
+        let start = i as u64 * segment_size;
+        let end = if i == segment_count - 1 {
+            total_size - 1
+        } else {
+            (i as u64 + 1) * segment_size - 1
+        };
+        segments.push(DownloadSegment {
+            start,
+            end,
+            downloaded: 0,
+            completed: false,
+        });
+    }
+
+    DownloadMetadata {
+        url: url.to_string(),
+        file_name: file_name.to_string(),
+        total_size,
+        downloaded_bytes: 0,
+        checksum: checksum.map(|s| s.to_string()),
+        timestamp: std::time::SystemTime::now()
+            .duration_since(std::time::UNIX_EPOCH)
+            .unwrap()
+            .as_secs(),
+        segments,
+    }
+}
+
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct ProgressEvent {
     pub file: String,
diff --git a/src-tauri/src/core/java.rs b/src-tauri/src/core/java.rs
index a8fdeea..8341138 100644
--- a/src-tauri/src/core/java.rs
+++ b/src-tauri/src/core/java.rs
@@ -2,11 +2,15 @@ use serde::{Deserialize, Serialize};
 use std::path::PathBuf;
 use std::process::Command;
 use tauri::AppHandle;
+use tauri::Emitter;
 use tauri::Manager;
 
-use crate::core::downloader;
+use crate::core::downloader::{self, JavaDownloadProgress, DownloadQueue, PendingJavaDownload};
 use crate::utils::zip;
 
+const ADOPTIUM_API_BASE: &str = "https://api.adoptium.net/v3";
+const CACHE_DURATION_SECS: u64 = 24 * 60 * 60; // 24 hours
+
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct JavaInstallation {
     pub path: String,
@@ -37,6 +41,42 @@ impl std::fmt::Display for ImageType {
     }
 }
 
+/// Java release information for UI display
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JavaReleaseInfo {
+    pub major_version: u32,
+    pub image_type: String,
+    pub version: String,
+    pub release_name: String,
+    pub release_date: Option<String>,
+    pub file_size: u64,
+    pub checksum: Option<String>,
+    pub download_url: String,
+    pub is_lts: bool,
+    pub is_available: bool,
+    pub architecture: String,
+}
+
+/// Java catalog containing all available versions
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JavaCatalog {
+    pub releases: Vec<JavaReleaseInfo>,
+    pub available_major_versions: Vec<u32>,
+    pub lts_versions: Vec<u32>,
+    pub cached_at: u64,
+}
+
+impl Default for JavaCatalog {
+    fn default() -> Self {
+        Self {
+            releases: Vec::new(),
+            available_major_versions: Vec::new(),
+            lts_versions: Vec::new(),
+            cached_at: 0,
+        }
+    }
+}
+
 /// Adoptium `/v3/assets/latest/{version}/hotspot` API response structures
 #[derive(Debug, Clone, Deserialize)]
 pub struct AdoptiumAsset {
@@ -51,6 +91,8 @@ pub struct AdoptiumBinary {
     pub architecture: String,
     pub image_type: String,
     pub package: AdoptiumPackage,
+    #[serde(default)]
+    pub updated_at: Option<String>,
 }
 
 #[derive(Debug, Clone, Deserialize)]
@@ -70,6 +112,15 @@ pub struct AdoptiumVersionData {
     pub openjdk_version: String,
 }
 
+/// Adoptium available releases response
+#[derive(Debug, Clone, Deserialize)]
+pub struct AvailableReleases {
+    pub available_releases: Vec<u32>,
+    pub available_lts_releases: Vec<u32>,
+    pub most_recent_lts: Option<u32>,
+    pub most_recent_feature_release: Option<u32>,
+}
+
 /// Java download information from Adoptium
 #[derive(Debug, Clone, Serialize)]
 pub struct JavaDownloadInfo {
@@ -140,6 +191,170 @@ pub fn get_java_install_dir(app_handle: &AppHandle) -> PathBuf {
     app_handle.path().app_data_dir().unwrap().join("java")
 }
 
+/// Get the cache file path for Java catalog
+fn get_catalog_cache_path(app_handle: &AppHandle) -> PathBuf {
+    app_handle
+        .path()
+        .app_data_dir()
+        .unwrap()
+        .join("java_catalog_cache.json")
+}
+
+/// Load cached Java catalog if not expired
+pub fn load_cached_catalog(app_handle: &AppHandle) -> Option<JavaCatalog> {
+    let cache_path = get_catalog_cache_path(app_handle);
+    if !cache_path.exists() {
+        return None;
+    }
+
+    let content = std::fs::read_to_string(&cache_path).ok()?;
+    let catalog: JavaCatalog = serde_json::from_str(&content).ok()?;
+
+    let now = std::time::SystemTime::now()
+        .duration_since(std::time::UNIX_EPOCH)
+        .unwrap()
+        .as_secs();
+
+    if now - catalog.cached_at < CACHE_DURATION_SECS {
+        Some(catalog)
+    } else {
+        None
+    }
+}
+
+/// Save Java catalog to cache
+pub fn save_catalog_cache(app_handle: &AppHandle, catalog: &JavaCatalog) -> Result<(), String> {
+    let cache_path = get_catalog_cache_path(app_handle);
+    let content = serde_json::to_string_pretty(catalog).map_err(|e| e.to_string())?;
+    std::fs::write(&cache_path, content).map_err(|e| e.to_string())?;
+    Ok(())
+}
+
+/// Clear Java catalog cache
+pub fn clear_catalog_cache(app_handle: &AppHandle) -> Result<(), String> {
+    let cache_path = get_catalog_cache_path(app_handle);
+    if cache_path.exists() {
+        std::fs::remove_file(&cache_path).map_err(|e| e.to_string())?;
+    }
+    Ok(())
+}
+
+/// Fetch complete Java catalog from Adoptium API with platform availability check
+pub async fn fetch_java_catalog(app_handle: &AppHandle, force_refresh: bool) -> Result<JavaCatalog, String> {
+    // Check cache first unless force refresh
+    if !force_refresh {
+        if let Some(cached) = load_cached_catalog(app_handle) {
+            return Ok(cached);
+        }
+    }
+
+    let os = get_adoptium_os();
+    let arch = get_adoptium_arch();
+    let client = reqwest::Client::new();
+
+    // 1. Fetch available releases
+    let releases_url = format!("{}/info/available_releases", ADOPTIUM_API_BASE);
+    let available: AvailableReleases = client
+        .get(&releases_url)
+        .header("Accept", "application/json")
+        .send()
+        .await
+        .map_err(|e| format!("Failed to fetch available releases: {}", e))?
+        .json()
+        .await
+        .map_err(|e| format!("Failed to parse available releases: {}", e))?;
+
+    let mut releases = Vec::new();
+
+    // 2. Fetch details for each major version
+    for major_version in &available.available_releases {
+        for image_type in &["jre", "jdk"] {
+            let url = format!(
+                "{}/assets/latest/{}/hotspot?os={}&architecture={}&image_type={}",
+                ADOPTIUM_API_BASE, major_version, os, arch, image_type
+            );
+
+            match client
+                .get(&url)
+                .header("Accept", "application/json")
+                .send()
+                .await
+            {
+                Ok(response) => {
+                    if response.status().is_success() {
+                        if let Ok(assets) = response.json::<Vec<AdoptiumAsset>>().await {
+                            if let Some(asset) = assets.into_iter().next() {
+                                let release_date = asset.binary.updated_at.clone();
+                                releases.push(JavaReleaseInfo {
+                                    major_version: *major_version,
+                                    image_type: image_type.to_string(),
+                                    version: asset.version.semver.clone(),
+                                    release_name: asset.release_name.clone(),
+                                    release_date,
+                                    file_size: asset.binary.package.size,
+                                    checksum: asset.binary.package.checksum,
+                                    download_url: asset.binary.package.link,
+                                    is_lts: available.available_lts_releases.contains(major_version),
+                                    is_available: true,
+                                    architecture: asset.binary.architecture.clone(),
+                                });
+                            }
+                        }
+                    } else {
+                        // Platform not available for this version/type
+                        releases.push(JavaReleaseInfo {
+                            major_version: *major_version,
+                            image_type: image_type.to_string(),
+                            version: format!("{}.x", major_version),
+                            release_name: format!("jdk-{}", major_version),
+                            release_date: None,
+                            file_size: 0,
+                            checksum: None,
+                            download_url: String::new(),
+                            is_lts: available.available_lts_releases.contains(major_version),
+                            is_available: false,
+                            architecture: arch.to_string(),
+                        });
+                    }
+                }
+                Err(_) => {
+                    // Network error, mark as unavailable
+                    releases.push(JavaReleaseInfo {
+                        major_version: *major_version,
+                        image_type: image_type.to_string(),
+                        version: format!("{}.x", major_version),
+                        release_name: format!("jdk-{}", major_version),
+                        release_date: None,
+                        file_size: 0,
+                        checksum: None,
+                        download_url: String::new(),
+                        is_lts: available.available_lts_releases.contains(major_version),
+                        is_available: false,
+                        architecture: arch.to_string(),
+                    });
+                }
+            }
+        }
+    }
+
+    let now = std::time::SystemTime::now()
+        .duration_since(std::time::UNIX_EPOCH)
+        .unwrap()
+        .as_secs();
+
+    let catalog = JavaCatalog {
+        releases,
+        available_major_versions: available.available_releases,
+        lts_versions: available.available_lts_releases,
+        cached_at: now,
+    };
+
+    // Save to cache
+    let _ = save_catalog_cache(app_handle, &catalog);
+
+    Ok(catalog)
+}
+
 /// Get Adoptium API download info for a specific Java version and image type
 ///
 /// # Arguments
@@ -157,8 +372,8 @@ pub async fn fetch_java_release(
     let arch = get_adoptium_arch();
 
     let url = format!(
-        "https://api.adoptium.net/v3/assets/latest/{}/hotspot?os={}&architecture={}&image_type={}",
-        major_version, os, arch, image_type
+        "{}/assets/latest/{}/hotspot?os={}&architecture={}&image_type={}",
+        ADOPTIUM_API_BASE, major_version, os, arch, image_type
     );
 
     let client = reqwest::Client::new();
@@ -199,7 +414,7 @@ pub async fn fetch_java_release(
 
 /// Fetch available Java versions from Adoptium API
 pub async fn fetch_available_versions() -> Result<Vec<u32>, String> {
-    let url = "https://api.adoptium.net/v3/info/available_releases";
+    let url = format!("{}/info/available_releases", ADOPTIUM_API_BASE);
 
     let response = reqwest::get(url)
         .await
@@ -218,7 +433,7 @@ pub async fn fetch_available_versions() -> Result<Vec<u32>, String> {
     Ok(releases.available_releases)
 }
 
-/// Download and install Java
+/// Download and install Java with resume support and progress events
 ///
 /// # Arguments
 /// * `app_handle` - Tauri app handle for accessing app directories
@@ -236,6 +451,7 @@ pub async fn download_and_install_java(
 ) -> Result<JavaInstallation, String> {
     // 1. Fetch download information
     let info = fetch_java_release(major_version, image_type).await?;
+    let file_name = info.file_name.clone();
 
     // 2. Prepare installation directory
     let install_base = custom_path.unwrap_or_else(|| get_java_install_dir(app_handle));
@@ -244,7 +460,24 @@ pub async fn download_and_install_java(
     std::fs::create_dir_all(&install_base)
         .map_err(|e| format!("Failed to create installation directory: {}", e))?;
 
-    // 3. Download the archive
+    // 3. Add to download queue for persistence
+    let mut queue = DownloadQueue::load(app_handle);
+    queue.add(PendingJavaDownload {
+        major_version,
+        image_type: image_type.to_string(),
+        download_url: info.download_url.clone(),
+        file_name: info.file_name.clone(),
+        file_size: info.file_size,
+        checksum: info.checksum.clone(),
+        install_path: install_base.to_string_lossy().to_string(),
+        created_at: std::time::SystemTime::now()
+            .duration_since(std::time::UNIX_EPOCH)
+            .unwrap()
+            .as_secs(),
+    });
+    queue.save(app_handle)?;
+
+    // 4. Download the archive with resume support
     let archive_path = install_base.join(&info.file_name);
 
     // Check if we need to download
@@ -261,30 +494,32 @@ pub async fn download_and_install_java(
     };
 
     if need_download {
-        let client = reqwest::Client::new();
-        let response = client
-            .get(&info.download_url)
-            .send()
-            .await
-            .map_err(|e| format!("Download failed: {}", e))?;
-
-        let bytes = response
-            .bytes()
-            .await
-            .map_err(|e| format!("Failed to read download content: {}", e))?;
-
-        // Verify downloaded file checksum
-        if let Some(expected) = &info.checksum {
-            if !downloader::verify_checksum(&bytes, Some(expected), None) {
-                return Err("Downloaded file verification failed, the file may be corrupted".to_string());
-            }
-        }
-
-        std::fs::write(&archive_path, &bytes)
-            .map_err(|e| format!("Failed to save downloaded file: {}", e))?;
+        // Use resumable download
+        downloader::download_with_resume(
+            app_handle,
+            &info.download_url,
+            &archive_path,
+            info.checksum.as_deref(),
+            info.file_size,
+        )
+        .await?;
     }
 
-    // 4. Extract
+    // 5. Emit extracting status
+    let _ = app_handle.emit(
+        "java-download-progress",
+        JavaDownloadProgress {
+            file_name: file_name.clone(),
+            downloaded_bytes: info.file_size,
+            total_bytes: info.file_size,
+            speed_bytes_per_sec: 0,
+            eta_seconds: 0,
+            status: "Extracting".to_string(),
+            percentage: 100.0,
+        },
+    );
+
+    // 6. Extract
     // If the target directory exists, remove it first
     if version_dir.exists() {
         std::fs::remove_dir_all(&version_dir)
@@ -304,10 +539,10 @@ pub async fn download_and_install_java(
         return Err(format!("Unsupported archive format: {}", info.file_name));
     };
 
-    // 5. Clean up downloaded archive
+    // 7. Clean up downloaded archive
     let _ = std::fs::remove_file(&archive_path);
 
-    // 6. Locate java executable
+    // 8. Locate java executable
     // macOS has a different structure: jdk-xxx/Contents/Home/bin/java
     // Linux/Windows: jdk-xxx/bin/java
     let java_home = version_dir.join(&top_level_dir);
@@ -326,10 +561,28 @@ pub async fn download_and_install_java(
         ));
     }
 
-    // 7. Verify installation
+    // 9. Verify installation
     let installation = check_java_installation(&java_bin)
         .ok_or_else(|| "Failed to verify Java installation".to_string())?;
 
+    // 10. Remove from download queue
+    queue.remove(major_version, &image_type.to_string());
+    queue.save(app_handle)?;
+
+    // 11. Emit completed status
+    let _ = app_handle.emit(
+        "java-download-progress",
+        JavaDownloadProgress {
+            file_name,
+            downloaded_bytes: info.file_size,
+            total_bytes: info.file_size,
+            speed_bytes_per_sec: 0,
+            eta_seconds: 0,
+            status: "Completed".to_string(),
+            percentage: 100.0,
+        },
+    );
+
     Ok(installation)
 }
 
@@ -676,3 +929,57 @@ fn find_java_executable(dir: &PathBuf) -> Option<PathBuf> {
 
     None
 }
+
+/// Resume pending Java downloads from queue
+pub async fn resume_pending_downloads(app_handle: &AppHandle) -> Result<Vec<JavaInstallation>, String> {
+    let queue = DownloadQueue::load(app_handle);
+    let mut installed = Vec::new();
+
+    for pending in queue.pending_downloads.iter() {
+        let image_type = if pending.image_type == "jdk" {
+            ImageType::Jdk
+        } else {
+            ImageType::Jre
+        };
+
+        // Try to resume the download
+        match download_and_install_java(
+            app_handle,
+            pending.major_version,
+            image_type,
+            Some(PathBuf::from(&pending.install_path)),
+        )
+        .await
+        {
+            Ok(installation) => {
+                installed.push(installation);
+            }
+            Err(e) => {
+                eprintln!(
+                    "Failed to resume Java {} {} download: {}",
+                    pending.major_version, pending.image_type, e
+                );
+            }
+        }
+    }
+
+    Ok(installed)
+}
+
+/// Cancel current Java download
+pub fn cancel_current_download() {
+    downloader::cancel_java_download();
+}
+
+/// Get pending downloads from queue
+pub fn get_pending_downloads(app_handle: &AppHandle) -> Vec<PendingJavaDownload> {
+    let queue = DownloadQueue::load(app_handle);
+    queue.pending_downloads
+}
+
+/// Clear a specific pending download
+pub fn clear_pending_download(app_handle: &AppHandle, major_version: u32, image_type: &str) -> Result<(), String> {
+    let mut queue = DownloadQueue::load(app_handle);
+    queue.remove(major_version, image_type);
+    queue.save(app_handle)
+}
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index a2b8c6c..53589d8 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -895,6 +895,45 @@ async fn fetch_available_java_versions() -> Result<Vec<u32>, String> {
     core::java::fetch_available_versions().await
 }
 
+/// Fetch Java catalog with platform availability (uses cache)
+#[tauri::command]
+async fn fetch_java_catalog(
+    app_handle: tauri::AppHandle,
+) -> Result<core::java::JavaCatalog, String> {
+    core::java::fetch_java_catalog(&app_handle, false).await
+}
+
+/// Refresh Java catalog (bypass cache)
+#[tauri::command]
+async fn refresh_java_catalog(
+    app_handle: tauri::AppHandle,
+) -> Result<core::java::JavaCatalog, String> {
+    core::java::fetch_java_catalog(&app_handle, true).await
+}
+
+/// Cancel current Java download
+#[tauri::command]
+async fn cancel_java_download() -> Result<(), String> {
+    core::java::cancel_current_download();
+    Ok(())
+}
+
+/// Get pending Java downloads
+#[tauri::command]
+async fn get_pending_java_downloads(
+    app_handle: tauri::AppHandle,
+) -> Result<Vec<core::downloader::PendingJavaDownload>, String> {
+    Ok(core::java::get_pending_downloads(&app_handle))
+}
+
+/// Resume pending Java downloads
+#[tauri::command]
+async fn resume_java_downloads(
+    app_handle: tauri::AppHandle,
+) -> Result<Vec<core::java::JavaInstallation>, String> {
+    core::java::resume_pending_downloads(&app_handle).await
+}
+
 /// Get Minecraft versions supported by Fabric
 #[tauri::command]
 async fn get_fabric_game_versions() -> Result<Vec<core::fabric::FabricGameVersion>, String> {
@@ -1067,6 +1106,13 @@ fn main() {
                 println!("[Startup] Loaded saved account");
             }
 
+            // Check for pending Java downloads and notify frontend
+            let pending = core::java::get_pending_downloads(&app.app_handle());
+            if !pending.is_empty() {
+                println!("[Startup] Found {} pending Java download(s)", pending.len());
+                let _ = app.emit("pending-java-downloads", pending.len());
+            }
+
             Ok(())
         })
         .invoke_handler(tauri::generate_handler![
@@ -1080,11 +1126,17 @@ fn main() {
             start_microsoft_login,
             complete_microsoft_login,
             refresh_account,
+            // Java commands
             detect_java,
             get_recommended_java,
             fetch_adoptium_java,
             download_adoptium_java,
             fetch_available_java_versions,
+            fetch_java_catalog,
+            refresh_java_catalog,
+            cancel_java_download,
+            get_pending_java_downloads,
+            resume_java_downloads,
             // Fabric commands
             get_fabric_game_versions,
             get_fabric_loader_versions,
diff --git a/ui/src/App.svelte b/ui/src/App.svelte
index 968f6c5..f32a42f 100644
--- a/ui/src/App.svelte
+++ b/ui/src/App.svelte
@@ -1,25 +1,23 @@
 <script lang="ts">
   import { getVersion } from "@tauri-apps/api/app";
-  import { onMount, onDestroy } from "svelte";
   import { convertFileSrc } from "@tauri-apps/api/core";
+  import { onDestroy, onMount } from "svelte";
   import DownloadMonitor from "./lib/DownloadMonitor.svelte";
   import GameConsole from "./lib/GameConsole.svelte";
-  
-  // Components
-  import Sidebar from "./components/Sidebar.svelte";
-  import HomeView from "./components/HomeView.svelte";
-  import VersionsView from "./components/VersionsView.svelte";
-  import SettingsView from "./components/SettingsView.svelte";
+// Components
   import BottomBar from "./components/BottomBar.svelte";
+  import HomeView from "./components/HomeView.svelte";
   import LoginModal from "./components/LoginModal.svelte";
-  import StatusToast from "./components/StatusToast.svelte";
   import ParticleBackground from "./components/ParticleBackground.svelte";
-
-  // Stores
-  import { uiState } from "./stores/ui.svelte";
+  import SettingsView from "./components/SettingsView.svelte";
+  import Sidebar from "./components/Sidebar.svelte";
+  import StatusToast from "./components/StatusToast.svelte";
+  import VersionsView from "./components/VersionsView.svelte";
+// Stores
   import { authState } from "./stores/auth.svelte";
-  import { settingsState } from "./stores/settings.svelte";
   import { gameState } from "./stores/game.svelte";
+  import { settingsState } from "./stores/settings.svelte";
+  import { uiState } from "./stores/ui.svelte";
 
   let mouseX = $state(0);
   let mouseY = $state(0);
@@ -32,6 +30,7 @@
   onMount(async () => {
     authState.checkAccount();
     await settingsState.loadSettings();
+    await settingsState.detectJava();
     gameState.loadVersions();
     getVersion().then((v) => (uiState.appVersion = v));
     window.addEventListener("mousemove", handleMouseMove);
diff --git a/ui/src/components/SettingsView.svelte b/ui/src/components/SettingsView.svelte
index d409784..42b1056 100644
--- a/ui/src/components/SettingsView.svelte
+++ b/ui/src/components/SettingsView.svelte
@@ -1,7 +1,7 @@
 <script lang="ts">
-  import { settingsState } from "../stores/settings.svelte";
-  import { open } from "@tauri-apps/plugin-dialog";
   import { convertFileSrc } from "@tauri-apps/api/core";
+  import { open } from "@tauri-apps/plugin-dialog";
+  import { settingsState } from "../stores/settings.svelte";
 
   async function selectBackground() {
     try {
@@ -51,9 +51,9 @@
             <!-- Preview -->
             <div class="w-40 h-24 rounded-xl overflow-hidden dark:bg-black/50 bg-gray-200 border dark:border-white/10 border-black/10 relative group shadow-lg">
               {#if settingsState.settings.custom_background_path}
-                <img 
-                  src={convertFileSrc(settingsState.settings.custom_background_path)} 
-                  alt="Background Preview" 
+                <img
+                  src={convertFileSrc(settingsState.settings.custom_background_path)}
+                  alt="Background Preview"
                   class="w-full h-full object-cover"
                 />
               {:else}
@@ -307,68 +307,329 @@
 <!-- Java Download Modal -->
 {#if settingsState.showJavaDownloadModal}
   <div class="fixed inset-0 z-[100] flex items-center justify-center backdrop-blur-sm bg-black/70">
-    <div class="bg-zinc-900 rounded-2xl border border-white/10 p-8 max-w-md w-full mx-4 shadow-2xl">
-      <h3 class="text-2xl font-bold mb-6 text-white">Download Java</h3>
-      
-      <div class="space-y-6">
-        <!-- Java Version Selection -->
-        <div>
-          <label class="block text-sm font-medium text-white/70 mb-2">Java Version</label>
-          <select
-            bind:value={settingsState.selectedJavaVersion}
-            class="bg-black/40 text-white w-full px-4 py-3 rounded-xl border border-white/10 focus:border-indigo-500/50 outline-none"
+    <div class="bg-zinc-900 rounded-2xl border border-white/10 shadow-2xl w-[900px] max-w-[95vw] h-[600px] max-h-[90vh] flex flex-col overflow-hidden">
+      <!-- Header -->
+      <div class="flex items-center justify-between p-5 border-b border-white/10">
+        <h3 class="text-xl font-bold text-white">Download Java</h3>
+        <button
+          aria-label="Close dialog"
+          onclick={() => settingsState.closeJavaDownloadModal()}
+          disabled={settingsState.isDownloadingJava}
+          class="text-white/40 hover:text-white/80 disabled:opacity-50 transition-colors p-1"
+        >
+          <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
+          </svg>
+        </button>
+      </div>
+
+      <!-- Main Content Area -->
+      <div class="flex flex-1 overflow-hidden">
+        <!-- Left Sidebar: Sources -->
+        <div class="w-40 border-r border-white/10 p-3 flex flex-col gap-1">
+          <span class="text-[10px] font-bold uppercase tracking-widest text-white/30 px-2 mb-2">Sources</span>
+          
+          <button
+            disabled
+            class="flex items-center gap-2 px-3 py-2.5 rounded-lg text-left text-sm opacity-40 cursor-not-allowed text-white/50"
           >
-            {#each settingsState.availableJavaVersions as version}
-              <option value={version}>Java {version}</option>
-            {/each}
-          </select>
+            <div class="w-5 h-5 rounded bg-white/10 flex items-center justify-center text-[10px]">M</div>
+            Mojang
+          </button>
+          
+          <button
+            class="flex items-center gap-2 px-3 py-2.5 rounded-lg text-left text-sm bg-indigo-500/20 border border-indigo-500/40 text-white"
+          >
+            <div class="w-5 h-5 rounded bg-indigo-500 flex items-center justify-center text-[10px] font-bold">A</div>
+            Adoptium
+          </button>
+          
+          <button
+            disabled
+            class="flex items-center gap-2 px-3 py-2.5 rounded-lg text-left text-sm opacity-40 cursor-not-allowed text-white/50"
+          >
+            <div class="w-5 h-5 rounded bg-white/10 flex items-center justify-center text-[10px]">Z</div>
+            Azul Zulu
+          </button>
         </div>
 
-        <!-- Image Type Selection -->
-        <div>
-          <label class="block text-sm font-medium text-white/70 mb-2">Image Type</label>
-          <div class="grid grid-cols-2 gap-3">
-            <button
-              onclick={() => settingsState.selectedImageType = "jre"}
-              class="px-4 py-3 rounded-xl border transition-all {settingsState.selectedImageType === 'jre' ? 'bg-indigo-500/20 border-indigo-500/50 text-white' : 'bg-black/20 border-white/10 text-white/60 hover:bg-white/5'}"
-            >
-              JRE
-            </button>
+        <!-- Center: Version Selection -->
+        <div class="flex-1 flex flex-col overflow-hidden">
+          <!-- Toolbar -->
+          <div class="flex items-center gap-3 p-4 border-b border-white/5">
+            <!-- Search -->
+            <div class="relative flex-1 max-w-xs">
+              <input
+                type="text"
+                bind:value={settingsState.searchQuery}
+                placeholder="Search versions..."
+                class="w-full bg-black/30 text-white text-sm px-4 py-2 pl-9 rounded-lg border border-white/10 focus:border-indigo-500/50 outline-none"
+              />
+              <svg class="absolute left-3 top-2.5 w-4 h-4 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
+              </svg>
+            </div>
+            
+            <!-- Recommended Filter -->
+            <label class="flex items-center gap-2 text-sm text-white/60 cursor-pointer select-none">
+              <input
+                type="checkbox"
+                bind:checked={settingsState.showOnlyRecommended}
+                class="w-4 h-4 rounded border-white/20 bg-black/30 text-indigo-500 focus:ring-indigo-500/30"
+              />
+              LTS Only
+            </label>
+
+            <!-- Image Type Toggle -->
+            <div class="flex items-center bg-black/30 rounded-lg p-0.5 border border-white/10">
+              <button
+                onclick={() => settingsState.selectedImageType = "jre"}
+                class="px-3 py-1.5 rounded-md text-xs font-medium transition-all {settingsState.selectedImageType === 'jre' ? 'bg-indigo-500 text-white shadow' : 'text-white/50 hover:text-white/80'}"
+              >
+                JRE
+              </button>
+              <button
+                onclick={() => settingsState.selectedImageType = "jdk"}
+                class="px-3 py-1.5 rounded-md text-xs font-medium transition-all {settingsState.selectedImageType === 'jdk' ? 'bg-indigo-500 text-white shadow' : 'text-white/50 hover:text-white/80'}"
+              >
+                JDK
+              </button>
+            </div>
+          </div>
+
+          <!-- Loading State -->
+          {#if settingsState.isLoadingCatalog}
+            <div class="flex-1 flex items-center justify-center text-white/50">
+              <div class="flex flex-col items-center gap-3">
+                <div class="w-8 h-8 border-2 border-indigo-500/30 border-t-indigo-500 rounded-full animate-spin"></div>
+                <span class="text-sm">Loading Java versions...</span>
+              </div>
+            </div>
+          {:else if settingsState.catalogError}
+            <div class="flex-1 flex items-center justify-center text-red-400">
+              <div class="flex flex-col items-center gap-3 text-center px-8">
+                <svg class="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
+                </svg>
+                <span class="text-sm">{settingsState.catalogError}</span>
+                <button
+                  onclick={() => settingsState.refreshCatalog()}
+                  class="mt-2 px-4 py-2 bg-white/10 hover:bg-white/20 rounded-lg text-sm text-white transition-colors"
+                >
+                  Retry
+                </button>
+              </div>
+            </div>
+          {:else}
+            <!-- Version List -->
+            <div class="flex-1 overflow-auto p-4">
+              <div class="space-y-2">
+                {#each settingsState.availableMajorVersions as version}
+                  {@const isLts = settingsState.javaCatalog?.lts_versions.includes(version)}
+                  {@const isSelected = settingsState.selectedMajorVersion === version}
+                  {@const releaseInfo = settingsState.javaCatalog?.releases.find(r => r.major_version === version && r.image_type === settingsState.selectedImageType)}
+                  {@const isAvailable = releaseInfo?.is_available ?? false}
+                  {@const installStatus = releaseInfo ? settingsState.getInstallStatus(releaseInfo) : 'download'}
+                  
+                  <button
+                    onclick={() => settingsState.selectMajorVersion(version)}
+                    disabled={!isAvailable}
+                    class="w-full flex items-center gap-4 p-3 rounded-xl border transition-all text-left
+                      {isSelected 
+                        ? 'bg-indigo-500/20 border-indigo-500/50 ring-2 ring-indigo-500/30' 
+                        : isAvailable 
+                          ? 'bg-black/20 border-white/10 hover:bg-white/5 hover:border-white/20' 
+                          : 'bg-black/10 border-white/5 opacity-40 cursor-not-allowed'}"
+                  >
+                    <!-- Version Number -->
+                    <div class="w-14 text-center">
+                      <span class="text-xl font-bold {isSelected ? 'text-white' : 'text-white/80'}">{version}</span>
+                    </div>
+                    
+                    <!-- Version Details -->
+                    <div class="flex-1 min-w-0">
+                      <div class="flex items-center gap-2">
+                        <span class="text-sm text-white/70 font-mono truncate">{releaseInfo?.version ?? '--'}</span>
+                        {#if isLts}
+                          <span class="px-1.5 py-0.5 bg-emerald-500/20 text-emerald-400 text-[9px] font-bold rounded uppercase shrink-0">LTS</span>
+                        {/if}
+                      </div>
+                      {#if releaseInfo}
+                        <div class="text-[10px] text-white/40 truncate mt-0.5">
+                          {releaseInfo.release_name} • {settingsState.formatBytes(releaseInfo.file_size)}
+                        </div>
+                      {/if}
+                    </div>
+                    
+                    <!-- Install Status Badge -->
+                    <div class="shrink-0">
+                      {#if installStatus === 'installed'}
+                        <span class="px-2 py-1 bg-emerald-500/20 text-emerald-400 text-[10px] font-bold rounded uppercase">Installed</span>
+                      {:else if isAvailable}
+                        <span class="px-2 py-1 bg-white/10 text-white/50 text-[10px] font-medium rounded">Download</span>
+                      {:else}
+                        <span class="px-2 py-1 bg-red-500/10 text-red-400/60 text-[10px] font-medium rounded">N/A</span>
+                      {/if}
+                    </div>
+                  </button>
+                {/each}
+              </div>
+            </div>
+          {/if}
+        </div>
+
+        <!-- Right Sidebar: Details -->
+        <div class="w-64 border-l border-white/10 flex flex-col">
+          <div class="p-4 border-b border-white/5">
+            <span class="text-[10px] font-bold uppercase tracking-widest text-white/30">Details</span>
+          </div>
+          
+          {#if settingsState.selectedRelease}
+            <div class="flex-1 p-4 space-y-4 overflow-auto">
+              <div>
+                <div class="text-[10px] text-white/40 uppercase tracking-wider mb-1">Version</div>
+                <div class="text-sm text-white font-mono">{settingsState.selectedRelease.version}</div>
+              </div>
+              
+              <div>
+                <div class="text-[10px] text-white/40 uppercase tracking-wider mb-1">Release Name</div>
+                <div class="text-sm text-white">{settingsState.selectedRelease.release_name}</div>
+              </div>
+              
+              <div>
+                <div class="text-[10px] text-white/40 uppercase tracking-wider mb-1">Release Date</div>
+                <div class="text-sm text-white">{settingsState.formatDate(settingsState.selectedRelease.release_date)}</div>
+              </div>
+              
+              <div>
+                <div class="text-[10px] text-white/40 uppercase tracking-wider mb-1">Size</div>
+                <div class="text-sm text-white">{settingsState.formatBytes(settingsState.selectedRelease.file_size)}</div>
+              </div>
+              
+              <div>
+                <div class="text-[10px] text-white/40 uppercase tracking-wider mb-1">Type</div>
+                <div class="flex items-center gap-2">
+                  <span class="text-sm text-white uppercase">{settingsState.selectedRelease.image_type}</span>
+                  {#if settingsState.selectedRelease.is_lts}
+                    <span class="px-1.5 py-0.5 bg-emerald-500/20 text-emerald-400 text-[9px] font-bold rounded">LTS</span>
+                  {/if}
+                </div>
+              </div>
+              
+              <div>
+                <div class="text-[10px] text-white/40 uppercase tracking-wider mb-1">Architecture</div>
+                <div class="text-sm text-white">{settingsState.selectedRelease.architecture}</div>
+              </div>
+              
+              {#if !settingsState.selectedRelease.is_available}
+                <div class="mt-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg">
+                  <div class="text-xs text-red-400">Not available for your platform</div>
+                </div>
+              {/if}
+            </div>
+          {:else}
+            <div class="flex-1 flex items-center justify-center text-white/30 text-sm p-4 text-center">
+              Select a Java version to view details
+            </div>
+          {/if}
+        </div>
+      </div>
+
+      <!-- Download Progress (MC Style) -->
+      {#if settingsState.isDownloadingJava && settingsState.downloadProgress}
+        <div class="border-t border-white/10 p-4 bg-zinc-900/90">
+          <div class="flex items-center justify-between mb-2">
+            <h3 class="text-white font-bold text-sm">Downloading Java</h3>
+            <span class="text-xs text-zinc-400">{settingsState.downloadProgress.status}</span>
+          </div>
+          
+          <!-- Progress Bar -->
+          <div class="mb-2">
+            <div class="flex justify-between text-[10px] text-zinc-400 mb-1">
+              <span>{settingsState.downloadProgress.file_name}</span>
+              <span>{Math.round(settingsState.downloadProgress.percentage)}%</span>
+            </div>
+            <div class="w-full bg-zinc-800 rounded-full h-2.5 overflow-hidden">
+              <div 
+                class="bg-gradient-to-r from-blue-500 to-cyan-400 h-2.5 rounded-full transition-all duration-200"
+                style="width: {settingsState.downloadProgress.percentage}%"
+              ></div>
+            </div>
+          </div>
+          
+          <!-- Speed & Stats -->
+          <div class="flex justify-between text-[10px] text-zinc-500 font-mono">
+            <span>
+              {settingsState.formatBytes(settingsState.downloadProgress.speed_bytes_per_sec)}/s · 
+              ETA: {settingsState.formatTime(settingsState.downloadProgress.eta_seconds)}
+            </span>
+            <span>
+              {settingsState.formatBytes(settingsState.downloadProgress.downloaded_bytes)} / 
+              {settingsState.formatBytes(settingsState.downloadProgress.total_bytes)}
+            </span>
+          </div>
+        </div>
+      {/if}
+
+      <!-- Pending Downloads Alert -->
+      {#if settingsState.pendingDownloads.length > 0 && !settingsState.isDownloadingJava}
+        <div class="border-t border-amber-500/30 p-4 bg-amber-500/10">
+          <div class="flex items-center justify-between">
+            <div class="flex items-center gap-3">
+              <svg class="w-5 h-5 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
+              </svg>
+              <span class="text-sm text-amber-200">
+                {settingsState.pendingDownloads.length} pending download(s) can be resumed
+              </span>
+            </div>
             <button
-              onclick={() => settingsState.selectedImageType = "jdk"}
-              class="px-4 py-3 rounded-xl border transition-all {settingsState.selectedImageType === 'jdk' ? 'bg-indigo-500/20 border-indigo-500/50 text-white' : 'bg-black/20 border-white/10 text-white/60 hover:bg-white/5'}"
+              onclick={() => settingsState.resumeDownloads()}
+              class="px-4 py-2 bg-amber-500/20 hover:bg-amber-500/30 text-amber-200 rounded-lg text-sm font-medium transition-colors"
             >
-              JDK
+              Resume All
             </button>
           </div>
-          <p class="text-xs text-white/40 mt-2">
-            JRE: Runtime only (smaller). JDK: Includes development tools.
-          </p>
         </div>
+      {/if}
 
-        <!-- Status -->
-        {#if settingsState.javaDownloadStatus}
-          <div class="bg-black/40 border border-white/10 rounded-xl p-4 text-sm text-white/80">
-            {settingsState.javaDownloadStatus}
-          </div>
-        {/if}
+      <!-- Footer Actions -->
+      <div class="flex items-center justify-between p-4 border-t border-white/10 bg-black/20">
+        <button
+          onclick={() => settingsState.refreshCatalog()}
+          disabled={settingsState.isLoadingCatalog || settingsState.isDownloadingJava}
+          class="flex items-center gap-2 px-4 py-2 bg-white/5 hover:bg-white/10 disabled:opacity-50 text-white/70 rounded-lg text-sm transition-colors"
+        >
+          <svg class="w-4 h-4 {settingsState.isLoadingCatalog ? 'animate-spin' : ''}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
+          </svg>
+          Refresh
+        </button>
 
-        <!-- Actions -->
-        <div class="flex gap-3 pt-2">
-          <button
-            onclick={() => settingsState.closeJavaDownloadModal()}
-            disabled={settingsState.isDownloadingJava}
-            class="flex-1 bg-white/10 hover:bg-white/20 disabled:opacity-50 text-white px-4 py-3 rounded-xl border border-white/5 transition-colors font-medium"
-          >
-            Cancel
-          </button>
-          <button
-            onclick={() => settingsState.downloadJava()}
-            disabled={settingsState.isDownloadingJava}
-            class="flex-1 bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white px-4 py-3 rounded-xl transition-colors font-medium"
-          >
-            {settingsState.isDownloadingJava ? "Downloading..." : "Download & Install"}
-          </button>
+        <div class="flex gap-3">
+          {#if settingsState.isDownloadingJava}
+            <button
+              onclick={() => settingsState.cancelDownload()}
+              class="px-5 py-2.5 bg-red-500/20 hover:bg-red-500/30 text-red-400 rounded-lg text-sm font-medium transition-colors"
+            >
+              Cancel Download
+            </button>
+          {:else}
+            {@const isInstalled = settingsState.selectedRelease ? settingsState.getInstallStatus(settingsState.selectedRelease) === 'installed' : false}
+            <button
+              onclick={() => settingsState.closeJavaDownloadModal()}
+              class="px-5 py-2.5 bg-white/10 hover:bg-white/20 text-white rounded-lg text-sm font-medium transition-colors"
+            >
+              Close
+            </button>
+            <button
+              onclick={() => settingsState.downloadJava()}
+              disabled={!settingsState.selectedRelease?.is_available || settingsState.isLoadingCatalog || isInstalled}
+              class="px-5 py-2.5 bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-lg text-sm font-medium transition-colors"
+            >
+              {isInstalled ? 'Already Installed' : 'Download & Install'}
+            </button>
+          {/if}
         </div>
       </div>
     </div>
diff --git a/ui/src/stores/settings.svelte.ts b/ui/src/stores/settings.svelte.ts
index 5b87af2..cad466d 100644
--- a/ui/src/stores/settings.svelte.ts
+++ b/ui/src/stores/settings.svelte.ts
@@ -1,5 +1,14 @@
 import { invoke } from "@tauri-apps/api/core";
-import type { JavaInstallation, LauncherConfig } from "../types";
+import { listen, type UnlistenFn } from "@tauri-apps/api/event";
+import type {
+  JavaCatalog,
+  JavaDownloadProgress,
+  JavaDownloadSource,
+  JavaInstallation,
+  JavaReleaseInfo,
+  LauncherConfig,
+  PendingJavaDownload,
+} from "../types";
 import { uiState } from "./ui.svelte";
 
 export class SettingsState {
@@ -17,14 +26,101 @@ export class SettingsState {
   });
   javaInstallations = $state<JavaInstallation[]>([]);
   isDetectingJava = $state(false);
-  
-  // Java download state
+
+  // Java download modal state
   showJavaDownloadModal = $state(false);
-  availableJavaVersions = $state<number[]>([]);
-  selectedJavaVersion = $state(21);
+  selectedDownloadSource = $state<JavaDownloadSource>("adoptium");
+  
+  // Java catalog state
+  javaCatalog = $state<JavaCatalog | null>(null);
+  isLoadingCatalog = $state(false);
+  catalogError = $state("");
+  
+  // Version selection state
+  selectedMajorVersion = $state<number | null>(null);
   selectedImageType = $state<"jre" | "jdk">("jre");
+  showOnlyRecommended = $state(true);
+  searchQuery = $state("");
+  
+  // Download progress state
   isDownloadingJava = $state(false);
+  downloadProgress = $state<JavaDownloadProgress | null>(null);
   javaDownloadStatus = $state("");
+  
+  // Pending downloads
+  pendingDownloads = $state<PendingJavaDownload[]>([]);
+  
+  // Event listener cleanup
+  private progressUnlisten: UnlistenFn | null = null;
+
+  // Computed: filtered releases based on selection
+  get filteredReleases(): JavaReleaseInfo[] {
+    if (!this.javaCatalog) return [];
+    
+    let releases = this.javaCatalog.releases;
+    
+    // Filter by major version if selected
+    if (this.selectedMajorVersion !== null) {
+      releases = releases.filter(r => r.major_version === this.selectedMajorVersion);
+    }
+    
+    // Filter by image type
+    releases = releases.filter(r => r.image_type === this.selectedImageType);
+    
+    // Filter by recommended (LTS) versions
+    if (this.showOnlyRecommended) {
+      releases = releases.filter(r => r.is_lts);
+    }
+    
+    // Filter by search query
+    if (this.searchQuery.trim()) {
+      const query = this.searchQuery.toLowerCase();
+      releases = releases.filter(
+        r =>
+          r.release_name.toLowerCase().includes(query) ||
+          r.version.toLowerCase().includes(query) ||
+          r.major_version.toString().includes(query)
+      );
+    }
+    
+    return releases;
+  }
+
+  // Computed: available major versions for display
+  get availableMajorVersions(): number[] {
+    if (!this.javaCatalog) return [];
+    let versions = [...this.javaCatalog.available_major_versions];
+    
+    // Filter by LTS if showOnlyRecommended is enabled
+    if (this.showOnlyRecommended) {
+      versions = versions.filter(v => this.javaCatalog!.lts_versions.includes(v));
+    }
+    
+    // Sort descending (newest first)
+    return versions.sort((a, b) => b - a);
+  }
+
+  // Get installation status for a release: 'installed' | 'download'
+  getInstallStatus(release: JavaReleaseInfo): 'installed' | 'download' {
+    // Find installed Java that matches the major version and image type (by path pattern)
+    const matchingInstallations = this.javaInstallations.filter(inst => {
+      // Check if this is a DropOut-managed Java (path contains temurin-XX-jre/jdk pattern)
+      const pathLower = inst.path.toLowerCase();
+      const pattern = `temurin-${release.major_version}-${release.image_type}`;
+      return pathLower.includes(pattern);
+    });
+    
+    // If any matching installation exists, it's installed
+    return matchingInstallations.length > 0 ? 'installed' : 'download';
+  }
+
+  // Computed: selected release details
+  get selectedRelease(): JavaReleaseInfo | null {
+    if (!this.javaCatalog || this.selectedMajorVersion === null) return null;
+    return this.javaCatalog.releases.find(
+      r => r.major_version === this.selectedMajorVersion && r.image_type === this.selectedImageType
+    ) || null;
+  }
 
   async loadSettings() {
     try {
@@ -32,8 +128,8 @@ export class SettingsState {
       this.settings = result;
       // Force dark mode
       if (this.settings.theme !== "dark") {
-          this.settings.theme = "dark";
-          this.saveSettings();
+        this.settings.theme = "dark";
+        this.saveSettings();
       }
     } catch (e) {
       console.error("Failed to load settings:", e);
@@ -74,55 +170,201 @@ export class SettingsState {
   async openJavaDownloadModal() {
     this.showJavaDownloadModal = true;
     this.javaDownloadStatus = "";
+    this.catalogError = "";
+    this.downloadProgress = null;
+    
+    // Setup progress event listener
+    await this.setupProgressListener();
+    
+    // Load catalog
+    await this.loadJavaCatalog(false);
+    
+    // Check for pending downloads
+    await this.loadPendingDownloads();
+  }
+
+  async closeJavaDownloadModal() {
+    if (!this.isDownloadingJava) {
+      this.showJavaDownloadModal = false;
+      // Cleanup listener
+      if (this.progressUnlisten) {
+        this.progressUnlisten();
+        this.progressUnlisten = null;
+      }
+    }
+  }
+
+  private async setupProgressListener() {
+    if (this.progressUnlisten) {
+      this.progressUnlisten();
+    }
+    
+    this.progressUnlisten = await listen<JavaDownloadProgress>(
+      "java-download-progress",
+      (event) => {
+        this.downloadProgress = event.payload;
+        this.javaDownloadStatus = event.payload.status;
+        
+        if (event.payload.status === "Completed") {
+          this.isDownloadingJava = false;
+          setTimeout(async () => {
+            await this.detectJava();
+            uiState.setStatus(`Java installed successfully!`);
+          }, 500);
+        } else if (event.payload.status === "Error") {
+          this.isDownloadingJava = false;
+        }
+      }
+    );
+  }
+
+  async loadJavaCatalog(forceRefresh: boolean) {
+    this.isLoadingCatalog = true;
+    this.catalogError = "";
+    
     try {
-      this.availableJavaVersions = await invoke("fetch_available_java_versions");
-      // Default selection logic
-      if (this.availableJavaVersions.includes(21)) {
-        this.selectedJavaVersion = 21;
-      } else if (this.availableJavaVersions.includes(17)) {
-        this.selectedJavaVersion = 17;
-      } else if (this.availableJavaVersions.length > 0) {
-        this.selectedJavaVersion = this.availableJavaVersions[this.availableJavaVersions.length - 1];
+      const command = forceRefresh ? "refresh_java_catalog" : "fetch_java_catalog";
+      this.javaCatalog = await invoke<JavaCatalog>(command);
+      
+      // Auto-select first LTS version
+      if (this.selectedMajorVersion === null && this.javaCatalog.lts_versions.length > 0) {
+        // Select most recent LTS (21 or highest)
+        const ltsVersions = [...this.javaCatalog.lts_versions].sort((a, b) => b - a);
+        this.selectedMajorVersion = ltsVersions[0];
       }
     } catch (e) {
-      console.error("Failed to fetch available Java versions:", e);
-      this.javaDownloadStatus = "Error fetching Java versions: " + e;
+      console.error("Failed to load Java catalog:", e);
+      this.catalogError = `Failed to load Java catalog: ${e}`;
+    } finally {
+      this.isLoadingCatalog = false;
     }
   }
 
-  closeJavaDownloadModal() {
-    if (!this.isDownloadingJava) {
-      this.showJavaDownloadModal = false;
+  async refreshCatalog() {
+    await this.loadJavaCatalog(true);
+    uiState.setStatus("Java catalog refreshed");
+  }
+
+  async loadPendingDownloads() {
+    try {
+      this.pendingDownloads = await invoke<PendingJavaDownload[]>("get_pending_java_downloads");
+    } catch (e) {
+      console.error("Failed to load pending downloads:", e);
     }
   }
 
+  selectMajorVersion(version: number) {
+    this.selectedMajorVersion = version;
+  }
+
   async downloadJava() {
+    if (!this.selectedRelease || !this.selectedRelease.is_available) {
+      uiState.setStatus("Selected Java version is not available for this platform");
+      return;
+    }
+    
     this.isDownloadingJava = true;
-    this.javaDownloadStatus = `Downloading Java ${this.selectedJavaVersion} ${this.selectedImageType.toUpperCase()}...`;
+    this.javaDownloadStatus = "Starting download...";
+    this.downloadProgress = null;
     
     try {
       const result: JavaInstallation = await invoke("download_adoptium_java", {
-        majorVersion: this.selectedJavaVersion,
+        majorVersion: this.selectedMajorVersion,
         imageType: this.selectedImageType,
         customPath: null,
       });
       
-      this.javaDownloadStatus = `Java ${this.selectedJavaVersion} installed at ${result.path}`;
       this.settings.java_path = result.path;
-      
       await this.detectJava();
       
       setTimeout(() => {
         this.showJavaDownloadModal = false;
-        uiState.setStatus(`Java ${this.selectedJavaVersion} is ready to use!`);
+        uiState.setStatus(`Java ${this.selectedMajorVersion} is ready to use!`);
       }, 1500);
     } catch (e) {
       console.error("Failed to download Java:", e);
-      this.javaDownloadStatus = "Download failed: " + e;
+      this.javaDownloadStatus = `Download failed: ${e}`;
     } finally {
       this.isDownloadingJava = false;
     }
   }
+
+  async cancelDownload() {
+    try {
+      await invoke("cancel_java_download");
+      this.isDownloadingJava = false;
+      this.javaDownloadStatus = "Download cancelled";
+      this.downloadProgress = null;
+      await this.loadPendingDownloads();
+    } catch (e) {
+      console.error("Failed to cancel download:", e);
+    }
+  }
+
+  async resumeDownloads() {
+    if (this.pendingDownloads.length === 0) return;
+    
+    this.isDownloadingJava = true;
+    this.javaDownloadStatus = "Resuming download...";
+    
+    try {
+      const installed = await invoke<JavaInstallation[]>("resume_java_downloads");
+      if (installed.length > 0) {
+        this.settings.java_path = installed[0].path;
+        await this.detectJava();
+        uiState.setStatus(`Resumed and installed ${installed.length} Java version(s)`);
+      }
+      await this.loadPendingDownloads();
+    } catch (e) {
+      console.error("Failed to resume downloads:", e);
+      this.javaDownloadStatus = `Resume failed: ${e}`;
+    } finally {
+      this.isDownloadingJava = false;
+    }
+  }
+
+  // Format bytes to human readable
+  formatBytes(bytes: number): string {
+    if (bytes === 0) return "0 B";
+    const k = 1024;
+    const sizes = ["B", "KB", "MB", "GB"];
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+    return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
+  }
+
+  // Format seconds to human readable
+  formatTime(seconds: number): string {
+    if (seconds === 0 || !isFinite(seconds)) return "--";
+    if (seconds < 60) return `${Math.round(seconds)}s`;
+    if (seconds < 3600) {
+      const mins = Math.floor(seconds / 60);
+      const secs = Math.round(seconds % 60);
+      return `${mins}m ${secs}s`;
+    }
+    const hours = Math.floor(seconds / 3600);
+    const mins = Math.floor((seconds % 3600) / 60);
+    return `${hours}h ${mins}m`;
+  }
+
+  // Format date string
+  formatDate(dateStr: string | null): string {
+    if (!dateStr) return "--";
+    try {
+      const date = new Date(dateStr);
+      return date.toLocaleDateString("en-US", {
+        year: "2-digit",
+        month: "2-digit",
+        day: "2-digit",
+      });
+    } catch {
+      return "--";
+    }
+  }
+
+  // Legacy compatibility
+  get availableJavaVersions(): number[] {
+    return this.availableMajorVersions;
+  }
 }
 
 export const settingsState = new SettingsState();
diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts
index f92cbf2..163dc92 100644
--- a/ui/src/types/index.ts
+++ b/ui/src/types/index.ts
@@ -53,6 +53,50 @@ export interface JavaDownloadInfo {
   image_type: string;
 }
 
+export interface JavaReleaseInfo {
+  major_version: number;
+  image_type: string;
+  version: string;
+  release_name: string;
+  release_date: string | null;
+  file_size: number;
+  checksum: string | null;
+  download_url: string;
+  is_lts: boolean;
+  is_available: boolean;
+  architecture: string;
+}
+
+export interface JavaCatalog {
+  releases: JavaReleaseInfo[];
+  available_major_versions: number[];
+  lts_versions: number[];
+  cached_at: number;
+}
+
+export interface JavaDownloadProgress {
+  file_name: string;
+  downloaded_bytes: number;
+  total_bytes: number;
+  speed_bytes_per_sec: number;
+  eta_seconds: number;
+  status: string;
+  percentage: number;
+}
+
+export interface PendingJavaDownload {
+  major_version: number;
+  image_type: string;
+  download_url: string;
+  file_name: string;
+  file_size: number;
+  checksum: string | null;
+  install_path: string;
+  created_at: number;
+}
+
+export type JavaDownloadSource = "adoptium" | "mojang" | "azul";
+
 // ==================== Fabric Types ====================
 
 export interface FabricGameVersion {
-- 
cgit v1.2.3-70-g09d2


From 76559c624f7d2418c2f25e4cb2d3c994f4218964 Mon Sep 17 00:00:00 2001
From: HsiangNianian <i@jyunko.cn>
Date: Thu, 15 Jan 2026 17:16:34 +0800
Subject: refactor: Clean up imports and enhance GameConsole display with
 conditional rendering and improved styling

---
 ui/src/App.svelte | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

(limited to 'ui/src/App.svelte')

diff --git a/ui/src/App.svelte b/ui/src/App.svelte
index f32a42f..0bb31ae 100644
--- a/ui/src/App.svelte
+++ b/ui/src/App.svelte
@@ -1,6 +1,6 @@
 <script lang="ts">
   import { getVersion } from "@tauri-apps/api/app";
-  import { convertFileSrc } from "@tauri-apps/api/core";
+  // import { convertFileSrc } from "@tauri-apps/api/core"; // Removed duplicate, handled by import below or inline
   import { onDestroy, onMount } from "svelte";
   import DownloadMonitor from "./lib/DownloadMonitor.svelte";
   import GameConsole from "./lib/GameConsole.svelte";
@@ -18,6 +18,7 @@
   import { gameState } from "./stores/game.svelte";
   import { settingsState } from "./stores/settings.svelte";
   import { uiState } from "./stores/ui.svelte";
+  import { convertFileSrc } from "@tauri-apps/api/core";
 
   let mouseX = $state(0);
   let mouseY = $state(0);
@@ -63,6 +64,7 @@
         src={convertFileSrc(settingsState.settings.custom_background_path)}
         alt="Background"
         class="absolute inset-0 w-full h-full object-cover transition-transform duration-[20s] ease-linear hover:scale-105"
+        onerror={(e) => console.error("Failed to load main background:", e)}
       />
       <!-- Dimming Overlay for readability -->
       <div class="absolute inset-0 bg-black/50 "></div>
@@ -129,7 +131,9 @@
           </div>
           
           <!-- Bottom Bar -->
-          <BottomBar />
+          {#if uiState.currentView === "home"}
+            <BottomBar />
+          {/if}
       </div>
     </main>
   </div>
@@ -138,10 +142,8 @@
   <StatusToast />
   
   {#if uiState.showConsole}
-     <!-- Assuming GameConsole handles its own display mode or overlay -->
-    <div class="fixed inset-0 z-[100] bg-black/80 flex items-center justify-center p-10">
-        <div class="w-full h-full bg-[#1e1e1e] rounded-xl overflow-hidden border border-white/10 shadow-2xl relative">
-            <button class="absolute top-4 right-4 text-white hover:text-red-400 z-10" onclick={() => uiState.toggleConsole()}>✕</button>
+    <div class="fixed inset-0 z-[100] bg-black/80 backdrop-blur-sm flex items-center justify-center p-8">
+        <div class="w-full h-full max-w-6xl max-h-[85vh] bg-[#1e1e1e] rounded-lg overflow-hidden border border-zinc-700 shadow-2xl relative flex flex-col">
             <GameConsole />
         </div>
     </div>
-- 
cgit v1.2.3-70-g09d2

