aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/src/components
diff options
context:
space:
mode:
authorHsiangNianian <i@jyunko.cn>2026-01-14 18:15:31 +0800
committerHsiangNianian <i@jyunko.cn>2026-01-14 18:15:31 +0800
commiteed52135e7d6ffbbbd64070cf567bcf08653c7d5 (patch)
treec6fba957f507b2368125f7c2e1dfed6cef5aad53 /ui/src/components
parent802b8cf5c0723b606ba5936c060e01d4c83222dd (diff)
downloadDropOut-eed52135e7d6ffbbbd64070cf567bcf08653c7d5.tar.gz
DropOut-eed52135e7d6ffbbbd64070cf567bcf08653c7d5.zip
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.
Diffstat (limited to 'ui/src/components')
-rw-r--r--ui/src/components/BottomBar.svelte79
-rw-r--r--ui/src/components/HomeView.svelte57
-rw-r--r--ui/src/components/ModLoaderSelector.svelte214
-rw-r--r--ui/src/components/ParticleBackground.svelte57
-rw-r--r--ui/src/components/SettingsView.svelte282
-rw-r--r--ui/src/components/Sidebar.svelte76
-rw-r--r--ui/src/components/VersionsView.svelte184
7 files changed, 571 insertions, 378 deletions
diff --git a/ui/src/components/BottomBar.svelte b/ui/src/components/BottomBar.svelte
index dcad9e8..dd218f3 100644
--- a/ui/src/components/BottomBar.svelte
+++ b/ui/src/components/BottomBar.svelte
@@ -5,18 +5,19 @@
</script>
<div
- class="h-24 bg-zinc-900 border-t border-zinc-800 flex items-center px-8 justify-between z-20 shadow-2xl"
+ 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"
>
- <div class="flex items-center gap-4">
+ <!-- Account Area -->
+ <div class="flex items-center gap-6">
<div
- class="flex items-center gap-4 cursor-pointer hover:opacity-80 transition-opacity"
+ class="group flex items-center gap-4 cursor-pointer transition-all duration-300 hover:scale-105"
onclick={() => authState.openLoginModal()}
role="button"
tabindex="0"
onkeydown={(e) => e.key === "Enter" && authState.openLoginModal()}
>
<div
- class="w-12 h-12 rounded bg-gradient-to-tr from-indigo-500 to-purple-500 shadow-lg flex items-center justify-center text-white font-bold text-xl overflow-hidden"
+ 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"
>
{#if authState.currentAccount}
<img
@@ -25,63 +26,73 @@
class="w-full h-full"
/>
{:else}
- ?
+ <span class="text-white/50 text-2xl">?</span>
{/if}
</div>
<div>
- <div class="font-bold text-white text-lg">
- {authState.currentAccount ? authState.currentAccount.username : "Click to Login"}
+ <div class="font-bold text-white text-lg 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">
+ <div class="text-xs text-zinc-400 flex items-center gap-1.5">
<span
- class="w-1.5 h-1.5 rounded-full {authState.currentAccount
- ? 'bg-green-500'
- : 'bg-zinc-500'}"
+ 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'}"
></span>
- {authState.currentAccount ? "Ready" : "Guest"}
+ {authState.currentAccount ? "Ready to play" : "Guest Mode"}
</div>
</div>
</div>
+
+ <div class="h-8 w-px bg-white/10"></div>
+
<!-- Console Toggle -->
<button
- class="ml-4 text-xs text-zinc-500 hover:text-zinc-300 transition"
+ class="text-xs font-mono text-zinc-500 hover:text-white transition-colors flex items-center gap-2"
onclick={() => uiState.toggleConsole()}
>
+ <span class="text-lg">_</span>
{uiState.showConsole ? "Hide Logs" : "Show Logs"}
</button>
</div>
- <div class="flex items-center gap-4">
+ <!-- Action Area -->
+ <div class="flex items-center gap-6">
<div class="flex flex-col items-end mr-2">
<label
for="version-select"
- class="text-xs text-zinc-500 mb-1 uppercase font-bold tracking-wider"
- >Version</label
+ class="text-[10px] text-white/40 mb-1 uppercase font-bold tracking-wider"
+ >Selected Version</label
>
- <select
- id="version-select"
- bind:value={gameState.selectedVersion}
- class="bg-zinc-950 text-zinc-200 border border-zinc-700 rounded px-4 py-2 hover:border-zinc-500 transition-colors cursor-pointer outline-none focus:ring-1 focus:ring-indigo-500 w-48"
- >
- {#if gameState.versions.length === 0}
- <option>Loading...</option>
- {:else}
- {#each gameState.versions as version}
- <option value={version.id}>{version.id} ({version.type})</option
- >
- {/each}
- {/if}
- </select>
+ <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"
+ >
+ {#if gameState.versions.length === 0}
+ <option>Loading...</option>
+ {:else}
+ {#each gameState.versions as version}
+ <option value={version.id}>{version.id} {version.type !== 'release' ? `(${version.type})` : ''}</option>
+ {/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>
</div>
<button
onclick={() => gameState.startGame()}
- class="bg-green-600 hover:bg-green-500 text-white font-bold h-14 px-12 rounded transition-all transform active:scale-95 shadow-[0_0_15px_rgba(22,163,74,0.4)] hover:shadow-[0_0_25px_rgba(22,163,74,0.6)] flex flex-col items-center justify-center uppercase tracking-wider text-lg"
+ class="bg-gradient-to-r from-emerald-600 to-green-600 hover:from-emerald-500 hover:to-green-500 text-white font-bold h-14 px-10 rounded-xl transition-all duration-300 transform hover:scale-105 active:scale-95 shadow-[0_0_20px_rgba(16,185,129,0.3)] hover:shadow-[0_0_40px_rgba(16,185,129,0.5)] flex flex-col items-center justify-center uppercase tracking-widest text-xl relative overflow-hidden group"
>
- Play
+ <div class="absolute inset-0 bg-white/20 translate-y-full group-hover:translate-y-0 transition-transform duration-300 skew-y-12"></div>
+ <span class="relative z-10 flex items-center gap-2">
+ PLAY
+ </span>
<span
- class="text-[10px] font-normal opacity-80 normal-case tracking-normal"
- >Click to launch</span
+ class="relative z-10 text-[9px] font-normal opacity-70 normal-case tracking-wide -mt-1"
+ >Launch Game</span
>
</button>
</div>
diff --git a/ui/src/components/HomeView.svelte b/ui/src/components/HomeView.svelte
index e876c14..036c03a 100644
--- a/ui/src/components/HomeView.svelte
+++ b/ui/src/components/HomeView.svelte
@@ -1,26 +1,47 @@
<script lang="ts">
- // No script needed currently, just static markup mostly
+ type Props = {
+ mouseX: number;
+ mouseY: number;
+ };
+ let { mouseX = 0, mouseY = 0 }: Props = $props();
</script>
-<!-- Background Image - Using gradient fallback -->
-<div
- class="absolute inset-0 z-0 opacity-60 bg-gradient-to-br from-emerald-900 via-zinc-900 to-indigo-950 transition-transform duration-[10s] ease-linear group-hover:scale-105"
-></div>
-<div
- class="absolute inset-0 z-0 bg-gradient-to-t from-zinc-900 via-transparent to-black/50"
-></div>
+<div class="absolute inset-0 z-0 overflow-hidden">
+ <!-- Parallax Background Layers -->
+
+ <div class="absolute inset-0 bg-gradient-to-t from-[#09090b] via-[#09090b]/40 to-transparent"></div>
+</div>
-<div class="absolute bottom-24 left-8 z-10 p-4">
- <h1
- class="text-6xl font-black mb-2 tracking-tight text-white drop-shadow-lg"
+<div class="relative z-10 h-full flex flex-col justify-end p-12 pb-24">
+ <!-- 3D Floating Hero Text -->
+ <div
+ class="transition-transform duration-200 ease-out origin-bottom-left"
+ style:transform={`perspective(1000px) rotateX(${mouseY * -2}deg) rotateY(${mouseX * 2}deg)`}
>
- MINECRAFT
- </h1>
- <div class="flex items-center gap-2 text-zinc-300">
- <span
- class="bg-zinc-800 text-xs px-2 py-1 rounded border border-zinc-600"
- >JAVA EDITION</span
+ <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);"
>
- <span class="text-lg">Release 1.20.4</span>
+ 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"
+ >
+ Java Edition
+ </div>
+ <div class="text-2xl font-light text-zinc-300">
+ Latest Release 1.21
+ </div>
+ </div>
+ </div>
+
+ <!-- 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">
+ Ready to play. Select version below or hit Launch.
+ </div>
</div>
</div>
diff --git a/ui/src/components/ModLoaderSelector.svelte b/ui/src/components/ModLoaderSelector.svelte
index 06eb6ae..4a59916 100644
--- a/ui/src/components/ModLoaderSelector.svelte
+++ b/ui/src/components/ModLoaderSelector.svelte
@@ -112,134 +112,134 @@
}
</script>
-<div class="bg-zinc-800 rounded-lg p-4 border border-zinc-700">
- <h3 class="text-sm font-semibold text-zinc-400 mb-3">Mod Loader</h3>
+<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>
+ </div>
- <!-- Loader Type Tabs -->
- <div class="flex gap-1 mb-4 bg-zinc-900 rounded-lg p-1">
+ <!-- Loader Type Tabs - Segmented Control -->
+ <div class="flex p-1 bg-black/40 rounded-xl border border-white/5 backdrop-blur-sm">
<button
- class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {selectedLoader ===
- 'vanilla'
- ? 'bg-zinc-700 text-white'
- : 'text-zinc-400 hover:text-white hover:bg-zinc-800'}"
+ 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'}"
onclick={() => onLoaderChange("vanilla")}
>
Vanilla
</button>
<button
- class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {selectedLoader ===
- 'fabric'
- ? 'bg-blue-600 text-white'
- : 'text-zinc-400 hover:text-white hover:bg-zinc-800'}"
+ 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'}"
onclick={() => onLoaderChange("fabric")}
>
Fabric
</button>
<button
- class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {selectedLoader ===
- 'forge'
- ? 'bg-orange-600 text-white'
- : 'text-zinc-400 hover:text-white hover:bg-zinc-800'}"
+ 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'}"
onclick={() => onLoaderChange("forge")}
>
Forge
</button>
</div>
- {#if selectedLoader === "vanilla"}
- <p class="text-sm text-zinc-500">
- Launch the selected Minecraft version without any mod loaders.
- </p>
- {:else if !selectedGameVersion}
- <p class="text-sm text-zinc-500">
- Select a Minecraft version first to see available {selectedLoader} versions.
- </p>
- {:else if isLoading}
- <div class="flex items-center gap-2 text-sm text-zinc-400">
- <svg
- class="animate-spin h-4 w-4"
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- >
- <circle
- class="opacity-25"
- cx="12"
- cy="12"
- r="10"
- stroke="currentColor"
- stroke-width="4"
- ></circle>
- <path
- class="opacity-75"
- fill="currentColor"
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
- ></path>
- </svg>
- Loading {selectedLoader} versions...
- </div>
- {:else if error}
- <p class="text-sm text-red-400">{error}</p>
- {:else if selectedLoader === "fabric"}
- <div class="space-y-3">
- <div>
- <label for="fabric-loader-select" class="block text-xs text-zinc-500 mb-1"
- >Loader Version</label
- >
- <select
- id="fabric-loader-select"
- class="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-2 text-sm focus:outline-none focus:border-blue-500"
- bind:value={selectedFabricLoader}
- >
- {#each fabricLoaders as loader}
- <option value={loader.version}>
- {loader.version}
- {loader.stable ? "(stable)" : ""}
- </option>
- {/each}
- </select>
- </div>
- <button
- class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded font-medium text-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- onclick={installModLoader}
- disabled={isLoading || !selectedFabricLoader}
- >
- Install Fabric {selectedFabricLoader}
- </button>
- </div>
- {:else if selectedLoader === "forge"}
- <div class="space-y-3">
- {#if forgeVersions.length === 0}
- <p class="text-sm text-zinc-500">
- No Forge versions available for Minecraft {selectedGameVersion}
- </p>
- {:else}
+ <!-- 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">
+ 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">
+ ⚠️ 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>
+ 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">
+ {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="forge-version-select" class="block text-xs text-zinc-500 mb-1"
- >Forge Version</label
- >
- <select
- id="forge-version-select"
- class="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-2 text-sm focus:outline-none focus:border-orange-500"
- bind:value={selectedForgeVersion}
- >
- {#each forgeVersions as version}
- <option value={version.version}>
- {version.version}
- {version.recommended ? "⭐ recommended" : ""}
- {version.latest ? "(latest)" : ""}
- </option>
- {/each}
- </select>
+ <label for="fabric-loader-select" class="block text-xs 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"
+ bind:value={selectedFabricLoader}
+ >
+ {#each fabricLoaders as loader}
+ <option value={loader.version}>
+ {loader.version} {loader.stable ? "(stable)" : ""}
+ </option>
+ {/each}
+ </select>
+ <div class="absolute right-4 top-1/2 -translate-y-1/2 text-white/20 pointer-events-none">▼</div>
+ </div>
</div>
+
<button
- class="w-full bg-orange-600 hover:bg-orange-700 text-white py-2 px-4 rounded font-medium text-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- onclick={installModLoader}
- disabled={isLoading || !selectedForgeVersion}
+ class="w-full bg-indigo-600 hover:bg-indigo-500 text-white py-3 px-4 rounded-xl font-bold text-sm transition-all shadow-lg shadow-indigo-500/20 disabled:opacity-50 disabled:shadow-none hover:scale-[1.02] active:scale-[0.98]"
+ onclick={installModLoader}
+ disabled={isLoading || !selectedFabricLoader}
>
- Install Forge {selectedForgeVersion}
+ Install Fabric {selectedFabricLoader}
</button>
- {/if}
- </div>
- {/if}
+ </div>
+
+ {: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">
+ 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"
+ >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"
+ bind:value={selectedForgeVersion}
+ >
+ {#each forgeVersions as version}
+ <option value={version.version}>
+ {version.version}
+ {version.recommended ? "⭐ recommended" : ""}
+ {version.latest ? "(latest)" : ""}
+ </option>
+ {/each}
+ </select>
+ <div class="absolute right-4 top-1/2 -translate-y-1/2 text-white/20 pointer-events-none">▼</div>
+ </div>
+ </div>
+
+ <button
+ class="w-full bg-orange-600 hover:bg-orange-500 text-white py-3 px-4 rounded-xl font-bold text-sm transition-all shadow-lg shadow-orange-500/20 disabled:opacity-50 disabled:shadow-none hover:scale-[1.02] active:scale-[0.98]"
+ onclick={installModLoader}
+ disabled={isLoading || !selectedForgeVersion}
+ >
+ Install Forge {selectedForgeVersion}
+ </button>
+ {/if}
+ </div>
+ {/if}
+ </div>
</div>
diff --git a/ui/src/components/ParticleBackground.svelte b/ui/src/components/ParticleBackground.svelte
new file mode 100644
index 0000000..080f1f2
--- /dev/null
+++ b/ui/src/components/ParticleBackground.svelte
@@ -0,0 +1,57 @@
+<script lang="ts">
+ import { onMount, onDestroy } from "svelte";
+ import { ConstellationEffect } from "../lib/effects/ConstellationEffect";
+ import { SaturnEffect } from "../lib/effects/SaturnEffect";
+ import { settingsState } from "../stores/settings.svelte";
+
+ let canvas: HTMLCanvasElement;
+ let activeEffect: any;
+
+ function loadEffect() {
+ if (activeEffect) {
+ activeEffect.destroy();
+ }
+
+ if (!canvas) return;
+
+ if (settingsState.settings.active_effect === "saturn") {
+ activeEffect = new SaturnEffect(canvas);
+ } else {
+ activeEffect = new ConstellationEffect(canvas);
+ }
+
+ // Ensure correct size immediately
+ activeEffect.resize(window.innerWidth, window.innerHeight);
+ }
+
+ $effect(() => {
+ const _ = settingsState.settings.active_effect;
+ if (canvas) {
+ loadEffect();
+ }
+ });
+
+ onMount(() => {
+ const resizeObserver = new ResizeObserver(() => {
+ if (canvas && activeEffect) {
+ activeEffect.resize(window.innerWidth, window.innerHeight);
+ }
+ });
+
+ resizeObserver.observe(document.body);
+
+ return () => {
+ resizeObserver.disconnect();
+ if (activeEffect) activeEffect.destroy();
+ };
+ });
+
+ onDestroy(() => {
+ if (activeEffect) activeEffect.destroy();
+ });
+</script>
+
+<canvas
+ bind:this={canvas}
+ class="absolute inset-0 z-0 pointer-events-none"
+></canvas>
diff --git a/ui/src/components/SettingsView.svelte b/ui/src/components/SettingsView.svelte
index 801970b..4c92220 100644
--- a/ui/src/components/SettingsView.svelte
+++ b/ui/src/components/SettingsView.svelte
@@ -1,150 +1,274 @@
<script lang="ts">
import { settingsState } from "../stores/settings.svelte";
+ import { open } from "@tauri-apps/plugin-dialog";
+ import { convertFileSrc } from "@tauri-apps/api/core";
+
+ async function selectBackground() {
+ try {
+ const selected = await open({
+ multiple: false,
+ filters: [
+ {
+ name: "Images",
+ extensions: ["png", "jpg", "jpeg", "webp", "gif"],
+ },
+ ],
+ });
+
+ if (selected && typeof selected === "string") {
+ settingsState.settings.custom_background_path = selected;
+ settingsState.saveSettings();
+ }
+ } catch (e) {
+ console.error("Failed to select background:", e);
+ }
+ }
+
+ function clearBackground() {
+ settingsState.settings.custom_background_path = undefined;
+ settingsState.saveSettings();
+ }
</script>
-<div class="p-8 bg-zinc-900 h-full overflow-y-auto">
- <h2 class="text-3xl font-bold mb-8">Settings</h2>
+<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>
+ </div>
+
+ <div class="flex-1 overflow-y-auto pr-2 space-y-6 custom-scrollbar pb-10">
- <div class="space-y-6 max-w-2xl">
- <!-- Java Path -->
- <div class="bg-zinc-800/50 p-6 rounded-lg border border-zinc-700">
- <label
- for="java-path"
- class="block text-sm font-bold text-zinc-400 mb-2 uppercase tracking-wide"
- >Java Executable Path</label
- >
- <div class="flex gap-2">
- <input
- id="java-path"
- bind:value={settingsState.settings.java_path}
- type="text"
- class="bg-zinc-950 text-white flex-1 p-3 rounded border border-zinc-700 focus:border-indigo-500 outline-none font-mono text-sm"
- placeholder="e.g. java, /usr/bin/java"
- />
- <button
- onclick={() => settingsState.detectJava()}
- disabled={settingsState.isDetectingJava}
- class="bg-zinc-700 hover:bg-zinc-600 disabled:opacity-50 text-white px-4 py-2 rounded transition-colors whitespace-nowrap"
- >
- {settingsState.isDetectingJava ? "Detecting..." : "Auto Detect"}
- </button>
+ <!-- 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">
+ Appearance
+ </h3>
+
+ <div class="space-y-4">
+ <div>
+ <label class="block text-sm font-medium text-white/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">
+ {#if settingsState.settings.custom_background_path}
+ <img
+ src={convertFileSrc(settingsState.settings.custom_background_path)}
+ alt="Background Preview"
+ class="w-full h-full object-cover"
+ />
+ {:else}
+ <div class="w-full h-full bg-gradient-to-br from-emerald-900 via-zinc-900 to-indigo-950 opacity-100"></div>
+ <div class="absolute inset-0 flex items-center justify-center text-[10px] text-white/50 bg-black/20 ">Default Gradient</div>
+ {/if}
+ </div>
+
+ <!-- Actions -->
+ <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"
+ >
+ Select Image
+ </button>
+
+ {#if settingsState.settings.custom_background_path}
+ <button
+ onclick={clearBackground}
+ class="text-red-400 hover:text-red-300 text-sm px-4 py-1 text-left transition-colors"
+ >
+ Reset to Default
+ </button>
+ {/if}
+ </div>
+ </div>
+ <p class="text-xs text-white/30 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="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>
+ </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'}"
+ >
+ <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>
+ <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>
+ </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"
+ >
+ <option value="saturn">Saturn (Saturn)</option>
+ <option value="constellation">Network (Constellation)</option>
+ </select>
+ </div>
+ {/if}
+
+ <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>
+ </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'}"
+ >
+ <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>
+ </div>
</div>
+ </div>
+
+ <!-- Java Path -->
+ <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">
+ Java Environment
+ </h3>
+ <div class="space-y-4">
+ <div>
+ <label for="java-path" class="block text-sm font-medium text-white/70 mb-2">Java Executable Path</label>
+ <div class="flex gap-2">
+ <input
+ id="java-path"
+ bind:value={settingsState.settings.java_path}
+ type="text"
+ class="bg-black/40 text-white flex-1 px-4 py-3 rounded-xl border border-white/10 focus:border-indigo-500/50 outline-none font-mono text-xs transition-colors"
+ placeholder="e.g. java, /usr/bin/java"
+ />
+ <button
+ onclick={() => settingsState.detectJava()}
+ disabled={settingsState.isDetectingJava}
+ class="bg-white/10 hover:bg-white/20 disabled:opacity-50 text-white px-4 py-2 rounded-xl border border-white/5 transition-colors whitespace-nowrap text-sm font-medium"
+ >
+ {settingsState.isDetectingJava ? "Detecting..." : "Auto Detect"}
+ </button>
+ </div>
+ </div>
{#if settingsState.javaInstallations.length > 0}
<div class="mt-4 space-y-2">
- <p class="text-xs text-zinc-400 uppercase font-bold">Detected Java Installations:</p>
+ <p class="text-[10px] text-white/30 uppercase font-bold tracking-wider">Detected Installations</p>
{#each settingsState.javaInstallations as java}
<button
onclick={() => settingsState.selectJava(java.path)}
- class="w-full text-left p-3 bg-zinc-950 rounded border transition-colors {settingsState.settings.java_path === java.path ? 'border-indigo-500 bg-indigo-950/30' : 'border-zinc-700 hover:border-zinc-500'}"
+ class="w-full text-left p-3 rounded-lg border transition-all duration-200 group
+ {settingsState.settings.java_path === java.path
+ ? 'bg-indigo-500/20 border-indigo-500/30'
+ : 'bg-black/20 border-white/5 hover:bg-white/5 hover:border-white/10'}"
>
<div class="flex justify-between items-center">
<div>
- <span class="text-white font-mono text-sm">{java.version}</span>
- <span class="text-zinc-500 text-xs ml-2">{java.is_64bit ? "64-bit" : "32-bit"}</span>
+ <span class="text-white font-mono text-xs font-bold">{java.version}</span>
+ <span class="text-white/40 text-[10px] ml-2">{java.is_64bit ? "64-bit" : "32-bit"}</span>
</div>
{#if settingsState.settings.java_path === java.path}
- <span class="text-indigo-400 text-xs">Selected</span>
+ <span class="text-indigo-300 text-[10px] font-bold uppercase tracking-wider">Selected</span>
{/if}
</div>
- <div class="text-zinc-500 text-xs font-mono truncate mt-1">{java.path}</div>
+ <div class="text-white/30 text-[10px] font-mono truncate mt-1 group-hover:text-white/50">{java.path}</div>
</button>
{/each}
</div>
{/if}
-
- <p class="text-xs text-zinc-500 mt-2">
- The command or path to the Java Runtime Environment. Click "Auto Detect" to find installed Java versions.
- </p>
+ </div>
</div>
<!-- Memory -->
- <div class="bg-zinc-800/50 p-6 rounded-lg border border-zinc-700">
- <h3
- class="block text-sm font-bold text-zinc-400 mb-4 uppercase tracking-wide"
- >Memory Allocation (RAM)</h3>
-
+ <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">
+ Memory Allocation (RAM)
+ </h3>
<div class="grid grid-cols-2 gap-6">
<div>
- <label for="min-memory" class="block text-xs text-zinc-500 mb-1"
- >Minimum (MB)</label
- >
+ <label for="min-memory" class="block text-sm font-medium text-white/70 mb-2">Minimum (MB)</label>
<input
id="min-memory"
bind:value={settingsState.settings.min_memory}
type="number"
- class="bg-zinc-950 text-white w-full p-3 rounded border border-zinc-700 focus:border-indigo-500 outline-none"
+ 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 transition-colors"
/>
</div>
<div>
- <label for="max-memory" class="block text-xs text-zinc-500 mb-1"
- >Maximum (MB)</label
- >
+ <label for="max-memory" class="block text-sm font-medium text-white/70 mb-2">Maximum (MB)</label>
<input
id="max-memory"
bind:value={settingsState.settings.max_memory}
type="number"
- class="bg-zinc-950 text-white w-full p-3 rounded border border-zinc-700 focus:border-indigo-500 outline-none"
+ 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 transition-colors"
/>
</div>
</div>
</div>
<!-- Resolution -->
- <div class="bg-zinc-800/50 p-6 rounded-lg border border-zinc-700">
- <h3
- class="block text-sm font-bold text-zinc-400 mb-4 uppercase tracking-wide"
- >Game Window Size</h3>
+ <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">
+ Game Window Size
+ </h3>
<div class="grid grid-cols-2 gap-6">
<div>
- <label for="window-width" class="block text-xs text-zinc-500 mb-1">Width</label>
+ <label for="window-width" class="block text-sm font-medium text-white/70 mb-2">Width</label>
<input
id="window-width"
bind:value={settingsState.settings.width}
type="number"
- class="bg-zinc-950 text-white w-full p-3 rounded border border-zinc-700 focus:border-indigo-500 outline-none"
+ 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 transition-colors"
/>
</div>
<div>
- <label for="window-height" class="block text-xs text-zinc-500 mb-1">Height</label>
+ <label for="window-height" class="block text-sm font-medium text-white/70 mb-2">Height</label>
<input
id="window-height"
bind:value={settingsState.settings.height}
type="number"
- class="bg-zinc-950 text-white w-full p-3 rounded border border-zinc-700 focus:border-indigo-500 outline-none"
+ 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 transition-colors"
/>
</div>
</div>
</div>
<!-- Download Settings -->
- <div class="bg-zinc-800/50 p-6 rounded-lg border border-zinc-700">
- <h3
- class="block text-sm font-bold text-zinc-400 mb-4 uppercase tracking-wide"
- >Download Settings</h3>
- <div>
- <label for="download-threads" class="block text-xs text-zinc-500 mb-1"
- >Concurrent Download Threads</label
- >
- <input
- id="download-threads"
- bind:value={settingsState.settings.download_threads}
- type="number"
- min="1"
- max="128"
- step="1"
- class="bg-zinc-950 text-white w-full p-3 rounded border border-zinc-700 focus:border-indigo-500 outline-none"
- />
- <p class="text-xs text-zinc-500 mt-2">
- Number of concurrent download threads (1-128). Higher values increase download speed but use more bandwidth and system resources. Default: 32
- </p>
- </div>
+ <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">
+ Network
+ </h3>
+ <div>
+ <label for="download-threads" class="block text-sm font-medium text-white/70 mb-2">Concurrent Download Threads</label>
+ <input
+ id="download-threads"
+ bind:value={settingsState.settings.download_threads}
+ type="number"
+ min="1"
+ max="128"
+ 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 transition-colors"
+ />
+ <p class="text-xs text-white/30 mt-2">Higher values usually mean faster downloads but use more CPU/Network.</p>
+ </div>
</div>
- <div class="pt-4">
+ <div class="pt-4 flex justify-end">
<button
onclick={() => settingsState.saveSettings()}
- class="bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-3 px-8 rounded shadow-lg transition-transform active:scale-95"
+ class="bg-gradient-to-r from-emerald-600 to-green-600 hover:from-emerald-500 hover:to-green-500 text-white font-bold py-3 px-8 rounded-xl shadow-lg shadow-emerald-500/20 transition-all hover:scale-105 active:scale-95"
>
Save Settings
</button>
diff --git a/ui/src/components/Sidebar.svelte b/ui/src/components/Sidebar.svelte
index a4f4e35..7976f6a 100644
--- a/ui/src/components/Sidebar.svelte
+++ b/ui/src/components/Sidebar.svelte
@@ -3,64 +3,56 @@
</script>
<aside
- class="w-20 lg:w-64 bg-zinc-950 flex flex-col items-center lg:items-start transition-all duration-300 border-r border-zinc-800 shrink-0"
+ class="w-20 lg:w-64 bg-black flex flex-col items-center lg:items-start transition-all duration-300 shrink-0 py-6"
>
+ <!-- Logo Area -->
<div
- class="h-20 w-full flex items-center justify-center lg:justify-start lg:px-6 border-b border-zinc-800/50"
+ class="h-16 w-full flex items-center justify-center lg:justify-start lg:px-8 mb-6"
>
- <!-- Icon Logo (Visible on small) -->
+ <!-- Icon Logo (Small) -->
<div
- class="lg:hidden text-2xl font-black bg-clip-text text-transparent bg-gradient-to-tr from-indigo-400 to-purple-400"
+ class="lg:hidden text-3xl font-black bg-clip-text text-transparent bg-gradient-to-tr from-indigo-400 to-fuchsia-400 drop-shadow-lg"
>
D
</div>
- <!-- Full Logo (Visible on large) -->
+ <!-- Full Logo (Large) -->
<div
- class="hidden lg:block font-bold text-xl tracking-wider text-indigo-400"
+ class="hidden lg:block font-bold text-2xl tracking-wider text-white"
>
- DROP<span class="text-white">OUT</span>
+ <span class="bg-clip-text text-transparent bg-gradient-to-r from-indigo-400 to-fuchsia-400">DROP</span>OUT
</div>
</div>
- <nav class="flex-1 w-full flex flex-col gap-2 p-3">
- <button
- class="group flex items-center lg:gap-4 justify-center lg:justify-start w-full px-0 lg:px-4 py-3 rounded-lg hover:bg-zinc-800 {uiState.currentView ===
- 'home'
- ? 'bg-zinc-800/80 text-white'
- : 'text-zinc-400'} transition-all relative"
- onclick={() => uiState.setView("home")}
- >
- <span class="text-xl relative z-10">🏠</span>
- <span
- class="hidden lg:block font-medium relative z-10 transition-opacity"
- >Home</span
+ <!-- Navigation -->
+ <nav class="flex-1 w-full flex flex-col gap-3 px-3">
+ <!-- Nav Item Helper -->
+ {#snippet navItem(view, icon, label)}
+ <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'}"
+ onclick={() => uiState.setView(view)}
>
- </button>
- <button
- class="group flex items-center lg:gap-4 justify-center lg:justify-start w-full px-0 lg:px-4 py-3 rounded-lg hover:bg-zinc-800 {uiState.currentView ===
- 'versions'
- ? 'bg-zinc-800/80 text-white'
- : 'text-zinc-400'} transition-all"
- onclick={() => uiState.setView("versions")}
- >
- <span class="text-xl">📦</span>
- <span class="hidden lg:block font-medium">Versions</span>
- </button>
- <button
- class="group flex items-center lg:gap-4 justify-center lg:justify-start w-full px-0 lg:px-4 py-3 rounded-lg hover:bg-zinc-800 {uiState.currentView ===
- 'settings'
- ? 'bg-zinc-800/80 text-white'
- : 'text-zinc-400'} transition-all"
- onclick={() => uiState.setView("settings")}
- >
- <span class="text-xl">⚙️</span>
- <span class="hidden lg:block font-medium">Settings</span>
- </button>
+ <span class="text-xl relative z-10 transition-transform group-hover:scale-110 duration-200">{icon}</span>
+ <span class="hidden lg:block font-medium relative z-10">{label}</span>
+
+ <!-- Active Indicator Line -->
+ {#if uiState.currentView === view}
+ <div class="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-8 bg-indigo-500 rounded-r-full lg:hidden"></div>
+ {/if}
+ </button>
+ {/snippet}
+
+ {@render navItem('home', '🏠', 'Home')}
+ {@render navItem('versions', '📦', 'Versions')}
+ {@render navItem('settings', '⚙️', 'Settings')}
</nav>
+ <!-- Footer Info -->
<div
- class="p-4 w-full border-t border-zinc-800 flex justify-center lg:justify-start"
+ class="p-4 w-full flex justify-center lg:justify-start lg:px-8 opacity-50 hover:opacity-100 transition-opacity"
>
- <div class="text-xs text-zinc-600 font-mono">v{uiState.appVersion}</div>
+ <div class="text-xs font-mono tracking-widest text-zinc-500">v{uiState.appVersion}</div>
</div>
</aside>
diff --git a/ui/src/components/VersionsView.svelte b/ui/src/components/VersionsView.svelte
index 1ea4878..00ac281 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-green-600" };
+ return { text: "Release", class: "bg-emerald-500/20 text-emerald-300 border-emerald-500/30" };
case "snapshot":
- return { text: "Snapshot", class: "bg-yellow-600" };
+ return { text: "Snapshot", class: "bg-amber-500/20 text-amber-300 border-amber-500/30" };
case "fabric":
- return { text: "Fabric", class: "bg-blue-600" };
+ return { text: "Fabric", class: "bg-indigo-500/20 text-indigo-300 border-indigo-500/30" };
case "forge":
- return { text: "Forge", class: "bg-orange-600" };
+ return { text: "Forge", class: "bg-orange-500/20 text-orange-300 border-orange-500/30" };
default:
- return { text: type, class: "bg-zinc-600" };
+ return { text: type, class: "bg-zinc-500/20 text-zinc-300 border-zinc-500/30" };
}
}
@@ -114,101 +114,92 @@
});
</script>
-<div class="p-8 h-full overflow-y-auto bg-zinc-900">
- <h2 class="text-3xl font-bold mb-6">Versions</h2>
+<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>
+ </div>
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
+ <div class="flex-1 grid grid-cols-1 lg:grid-cols-3 gap-6 overflow-hidden">
<!-- Left: Version List -->
- <div class="lg:col-span-2 space-y-4">
- <!-- Search and Filters -->
+ <div class="lg:col-span-2 flex flex-col gap-4 overflow-hidden">
+ <!-- Search and Filters (Glass Bar) -->
<div class="flex gap-3">
- <input
- type="text"
- placeholder="Search versions..."
- class="flex-1 p-3 bg-zinc-800 border border-zinc-700 rounded text-white focus:outline-none focus:border-green-500 transition-colors"
- bind:value={searchQuery}
- />
+ <div class="relative flex-1">
+ <span class="absolute left-3 top-1/2 -translate-y-1/2 text-white/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"
+ bind:value={searchQuery}
+ />
+ </div>
</div>
- <!-- Type Filter Tabs -->
- <div class="flex gap-1 bg-zinc-800 rounded-lg p-1">
- <button
- class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {typeFilter ===
- 'all'
- ? 'bg-zinc-700 text-white'
- : 'text-zinc-400 hover:text-white'}"
- onclick={() => (typeFilter = "all")}
- >
- All
- </button>
- <button
- class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {typeFilter ===
- 'release'
- ? 'bg-green-600 text-white'
- : 'text-zinc-400 hover:text-white'}"
- onclick={() => (typeFilter = "release")}
- >
- Releases
- </button>
- <button
- class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {typeFilter ===
- 'snapshot'
- ? 'bg-yellow-600 text-white'
- : 'text-zinc-400 hover:text-white'}"
- onclick={() => (typeFilter = "snapshot")}
- >
- Snapshots
- </button>
- <button
- class="flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors {typeFilter ===
- 'modded'
- ? 'bg-purple-600 text-white'
- : 'text-zinc-400 hover:text-white'}"
- onclick={() => (typeFilter = "modded")}
- >
- Modded
- </button>
+ <!-- Type Filter Tabs (Glass Caps) -->
+ <div class="flex p-1 bg-black/20 rounded-xl border 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'}"
+ onclick={() => (typeFilter = filter as any)}
+ >
+ {filter}
+ </button>
+ {/each}
</div>
- <!-- Version List -->
- <div class="grid gap-2 max-h-[calc(100vh-320px)] overflow-y-auto pr-2">
+ <!-- Version List SCROLL -->
+ <div class="flex-1 overflow-y-auto pr-2 space-y-2 custom-scrollbar">
{#if gameState.versions.length === 0}
- <div class="text-zinc-500">Loading versions...</div>
+ <div class="flex items-center justify-center h-40 text-white/30 italic animate-pulse">
+ Fetching manifest...
+ </div>
{:else if filteredVersions().length === 0}
- <div class="text-zinc-500">
- {#if normalizedQuery.length > 0}
- No versions found matching "{searchQuery}"
- {:else}
- No versions in this category
- {/if}
+ <div class="flex flex-col items-center justify-center -40 text-white/30 gap-2">
+ <span class="text-2xl">👻</span>
+ <span>No matching versions found</span>
</div>
{:else}
{#each filteredVersions() as version}
{@const badge = getVersionBadge(version.type)}
+ {@const isSelected = gameState.selectedVersion === version.id}
<button
- class="flex items-center justify-between p-4 bg-zinc-800 rounded hover:bg-zinc-700 transition text-left border border-zinc-700 {gameState.selectedVersion ===
- version.id
- ? 'border-green-500 bg-zinc-800/80 ring-1 ring-green-500'
- : ''}"
+ 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'}"
onclick={() => (gameState.selectedVersion = version.id)}
>
- <div class="flex items-center gap-3">
+ <!-- Selection Glow -->
+ {#if isSelected}
+ <div class="absolute inset-0 bg-gradient-to-r from-indigo-500/10 to-transparent pointer-events-none"></div>
+ {/if}
+
+ <div class="relative z-10 flex items-center gap-4">
<span
- class="px-2 py-0.5 rounded text-xs font-medium {badge.class}"
+ class="px-2.5 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wide border {badge.class}"
>
{badge.text}
</span>
<div>
- <div class="font-bold font-mono">{version.id}</div>
+ <div class="font-bold font-mono text-lg tracking-tight {isSelected ? 'text-white' : 'text-zinc-300 group-hover:text-white'}">
+ {version.id}
+ </div>
{#if version.releaseTime && version.type !== "fabric" && version.type !== "forge"}
- <div class="text-xs text-zinc-400">
+ <div class="text-xs text-white/30">
{new Date(version.releaseTime).toLocaleDateString()}
</div>
{/if}
</div>
</div>
- {#if gameState.selectedVersion === version.id}
- <div class="text-green-500 font-bold text-sm">SELECTED</div>
+
+ {#if isSelected}
+ <div class="relative z-10 text-indigo-400">
+ <span class="text-lg">Selected</span>
+ </div>
{/if}
</button>
{/each}
@@ -217,32 +208,29 @@
</div>
<!-- Right: Mod Loader Panel -->
- <div class="space-y-4">
- <!-- Selected Version Info -->
- {#if gameState.selectedVersion}
- <div class="bg-zinc-800 rounded-lg p-4 border border-zinc-700">
- <h3 class="text-sm font-semibold text-zinc-400 mb-2">Selected</h3>
- <p class="font-mono text-lg text-green-400">
- {gameState.selectedVersion}
- </p>
- </div>
- {/if}
-
- <!-- Mod Loader Selector -->
- <ModLoaderSelector
- selectedGameVersion={selectedBaseVersion()}
- onInstall={handleModLoaderInstall}
- />
-
- <!-- Help Text -->
- <div class="bg-zinc-800/50 rounded-lg p-4 border border-zinc-700/50">
- <h4 class="text-sm font-semibold text-zinc-400 mb-2">💡 Tip</h4>
- <p class="text-xs text-zinc-500">
- Select a vanilla Minecraft version, then use the Mod Loader panel to
- install Fabric or Forge. Installed modded versions will appear in the
- list with colored badges.
- </p>
+ <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="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>
+ {#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">
+ {gameState.selectedVersion}
+ </p>
+ {:else}
+ <p class="text-white/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">
+ <ModLoaderSelector
+ selectedGameVersion={selectedBaseVersion()}
+ onInstall={handleModLoaderInstall}
+ />
+ </div>
+
</div>
</div>
</div>