chore: rework launcher design

This commit is contained in:
2026-02-15 18:42:01 +01:00
parent f9b38ec9eb
commit 1f845f1282
10 changed files with 709 additions and 677 deletions
+1 -1
View File
@@ -15,7 +15,7 @@
{
"title": "EZPPLauncher",
"width": 1000,
"height": 737,
"height": 650,
"decorations": false,
"resizable": false,
"maximizable": false
@@ -0,0 +1,77 @@
<script lang="ts">
//TODO: maybe dynamic background images fetched from ezpp?
const COUNT = 120;
function rand(min: number, max: number) {
return Math.random() * (max - min) + min;
}
const streaks = Array.from({ length: COUNT }).map(() => {
const depth = rand(0, 2);
const scale = 0.6 + depth * 0.8;
const speed = 0.4 + depth * 1.35;
const distance = 150;
const duration = distance / speed;
return {
width: rand(180, 320) * scale,
height: rand(16, 32) * scale,
x: rand(-80, 100),
y: rand(-80, 100),
opacity: 0.25 + depth * 0.65,
duration,
delay: rand(0, duration),
scale,
};
});
</script>
<div class="absolute bg blur-[2px]">
<div
class="relative h-full w-full from-0% to-90% bg-gradient-to-b from-transparent to-black z-50"
></div>
{#each streaks as s}
<div
class="streak drop-shadow-lg"
style="
width:{s.width}px;
height:{s.height}px;
left:{s.x}%;
top:{s.y}%;
opacity:{s.opacity};
animation-duration:{s.duration}s;
animation-delay:-{s.delay}s;
"
></div>
{/each}
</div>
<style>
.bg {
position: fixed;
inset: 0;
overflow: hidden;
background: linear-gradient(180deg, #8f2f64 0%, #963f75 40%, #9a6c7f 100%);
}
.streak {
position: absolute;
background: #d6f3ff;
border-radius: 999px;
transform: rotate(-35deg);
animation: move linear infinite;
will-change: transform;
}
@keyframes move {
from {
transform: translate(-150vw, 150vh) rotate(-35deg);
}
to {
transform: translate(150vw, -150vh) rotate(-35deg);
}
}
</style>
@@ -0,0 +1,91 @@
<script lang="ts">
import Ellipsis from 'lucide-svelte/icons/ellipsis';
import Play from 'lucide-svelte/icons/play';
const prop: {
downloading: boolean;
progress: number;
text: string;
subtext?: string;
disabled?: boolean;
onClick?: () => void;
} = $props();
const radius = 16;
const circumference = 2 * Math.PI * radius;
const strokeDashoffset = $derived(circumference - (prop.progress / 100) * circumference);
</script>
<button
class="group flex items-center h-12 rounded-full bg-pink-400 disabled:bg-pink-400/70 shadow-lg shadow-primary/25 transition-all not-disabled:hover:shadow-primary/40 not-disabled:hover:brightness-110 not-disabled:active:scale-[0.98] cursor-pointer w-fit"
disabled={prop.disabled}
onclick={prop.onClick}
>
<div class="relative size-12 shrink-0 flex items-center justify-center">
{#if prop.downloading}
{#if prop.progress === -1}
<svg class="absolute inset-0 animate-spin" viewBox="0 0 48 48">
<circle class="stroke-black/10" cx="24" cy="24" r={radius} fill="none" stroke-width="3" />
<circle
class="stroke-pink-950"
cx="24"
cy="24"
r={radius}
fill="none"
stroke-width="3"
stroke-linecap="round"
stroke-dasharray={circumference}
stroke-dashoffset={circumference * 0.7}
/>
</svg>
{:else}
<svg class="absolute inset-0 -rotate-90" viewBox="0 0 48 48">
<circle
cx="24"
cy="24"
r={radius}
fill="none"
stroke="rgba(0,0,0,0.2)"
stroke-width="3"
/>
<circle
cx="24"
cy="24"
r={radius}
fill="none"
stroke="rgba(0,0,0,0.7)"
stroke-width="3"
stroke-linecap="round"
stroke-dasharray={circumference}
stroke-dashoffset={strokeDashoffset}
class="transition-[stroke-dashoffset] duration-100"
/>
</svg>
{/if}
{/if}
<div class="size-9 rounded-full bg-black/30 flex items-center justify-center relative z-10">
{#if prop.downloading}
{#if prop.progress === -1}
<Ellipsis class="size-4 text-primary-foreground fill-current animate-pulse" />
{:else}
<span class="text-[10px] font-bold text-primary-foreground">
{prop.progress.toFixed()}
</span>
{/if}
{:else}
<Play class="size-4 text-primary-foreground fill-current ml-0.5" />
{/if}
</div>
</div>
<div class="pr-6 pl-1 text-center w-52">
<p class="text-sm font-bold text-primary-foreground leading-tight">
{prop.text}
</p>
{#if prop.subtext}
<p class="text-[10px] text-primary-foreground/70 leading-tight">
{prop.subtext}
</p>
{/if}
</div>
</button>
@@ -85,7 +85,7 @@
animate(cursor, {
duration: smoothCursor ? $cursorSmoothness : 0,
translateX: mouseX,
translateY: mouseY - 50,
translateY: mouseY,
ease: (t: number) => (t - 1) ** 5 + 1,
});
+9 -18
View File
@@ -19,27 +19,18 @@
});
</script>
<div data-tauri-drag-region class="titlebar z-[60] border-b border-theme-800/90">
<div
class="mr-auto ms-2 flex flex-row gap-2 items-center text-[1.05rem] font-semibold pointer-events-none"
>
<img src={Logo} alt="EZPP Launcher Logo" class="h-11 w-11 inline-block" />
<span>EZPPLauncher</span>
<Badge class="!text-[0.6rem] py-[0.5px] bg-primary-500 hover:bg-primary-500"
>{$launcherVersion}</Badge
>
<div data-tauri-drag-region class="titlebar z-[100]">
<div class="titlebar-button rounded transition-colors duration-75" id="titlebar-minimize">
<Minimize size={14} />
</div>
<div class="titlebar-button rounded-lg transition-colors duration-75" id="titlebar-minimize">
<Minimize size={18} />
</div>
<div class="titlebar-button close rounded-lg transition-colors duration-75" id="titlebar-close">
<Close size={18} />
<div class="titlebar-button close rounded transition-colors duration-75" id="titlebar-close">
<Close size={14} />
</div>
</div>
<style lang="scss">
.titlebar {
height: 50px;
height: 35px;
/* background: #040612; */
user-select: none;
display: flex;
@@ -52,14 +43,14 @@
right: 0;
margin-bottom: 10px;
padding-right: 5px;
pointer-events: all !important;
pointer-events: auto;
}
.titlebar-button {
display: inline-flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
width: 25px;
height: 25px;
user-select: none;
-webkit-user-select: none;
}
+1 -1
View File
@@ -150,7 +150,7 @@
<OsuCursor smoothCursor={$cursorSmoothening} />
{/if}
<Toaster richColors closeButton theme="dark" />
<Toaster richColors closeButton theme="dark" position="top-right" />
<Titlebar />
+526 -526
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -202,7 +202,7 @@
});
</script>
<div class="flex flex-col items-center justify-center mt-[50px] h-[calc(100vh-50px)] w-full">
<div class="flex flex-col items-center justify-center h-[100vh] w-full">
<div class="relative w-80 h-80 flex items-center justify-center">
<svg
class="absolute top-0 left-0 w-full h-full animate-spin"
-127
View File
@@ -1,127 +0,0 @@
<script lang="ts">
import Logo from '$assets/logo.png';
import { ezppfarm } from '@/api/ezpp';
import Button from '@/components/ui/button/button.svelte';
import Input from '@/components/ui/input/input.svelte';
import Label from '@/components/ui/label/label.svelte';
import { currentView } from '@/global';
import { currentUser, userAuth } from '@/userAuthentication';
import { animate } from 'animejs';
import { LoaderCircle } from 'lucide-svelte';
import { toast } from 'svelte-sonner';
import Launch from './Launch.svelte';
import { currentUserInfo } from '@/data';
import { preferredMode, preferredType } from '@/userSettings';
let username = $state('');
let password = $state('');
let isLoading = $state(false);
let ezppLogo: HTMLImageElement | undefined = $state(undefined);
const logo_mouseenter = () => {
if (ezppLogo) {
animate(ezppLogo, {
duration: 700,
scale: 1.2,
ease: (t: number) => Math.pow(2, -5 * t) * Math.sin((t - 0.075) * 20.94) + 1 - 0.0005 * t,
});
}
};
const logo_mouseleave = () => {
if (ezppLogo) {
animate(ezppLogo, {
duration: 700,
scale: 1,
ease: (t: number) => (t - 1) ** 7 + 1,
});
}
};
const performLogin = async () => {
isLoading = true;
try {
const loginResult = await ezppfarm.login(username, password);
if (loginResult && loginResult.user) {
toast.success('Login successful!', {
description: `Welcome back, ${loginResult.user.name}!`,
});
$userAuth.value('username').set(username);
$userAuth.value('password').set(password);
await $userAuth.save();
currentUser.set(loginResult.user);
currentView.set(Launch);
} else {
toast.error('Login failed!', {
description: 'Please check your username and password.',
});
isLoading = false;
}
} catch {
toast.error('Server error occurred during login.', {
description: 'There was an issue connecting to the server. Please try again later.',
});
isLoading = false;
}
if ($currentUser) {
const userInfo = await ezppfarm.getUserInfo($currentUser.id);
if (userInfo) {
currentUserInfo.set(userInfo.player);
preferredMode.set(userInfo.player.info.preferred_mode);
preferredType.set(userInfo.player.info.preferred_type);
}
}
};
</script>
<div class="mt-[50px] h-[calc(100vh-50px)] w-full">
<div class="w-full h-full flex flex-col items-center justify-center">
<img
src={Logo}
alt="EZPPLauncher Logo"
class="w-52 h-52 mb-2"
bind:this={ezppLogo}
onmouseenter={logo_mouseenter}
onmouseleave={logo_mouseleave}
/>
<form onsubmit={performLogin} class="w-full max-w-sm">
<div class="mb-4">
<Label for="username" class="block text-sm font-medium">Username</Label>
<Input
class="mt-4 w-full bg-theme-900 border-theme-800"
type="text"
id="username"
bind:value={username}
disabled={isLoading}
autocomplete="off"
autocorrect="off"
/>
</div>
<div class="mb-4">
<Label for="password" class="block text-sm font-medium">Password</Label>
<Input
class="mt-4 w-full bg-theme-900 border-theme-800"
type="password"
id="password"
bind:value={password}
disabled={isLoading}
autocomplete="off"
autocorrect="off"
/>
</div>
<Button class="w-full" type="submit" disabled={isLoading}>
{#if isLoading}
<LoaderCircle class="animate-spin" />
{:else}
Login
{/if}
</Button>
</form>
</div>
</div>
+2 -2
View File
@@ -119,7 +119,7 @@
</script>
{#if wizardFinished}
<div class="relative flex flex-col items-center justify-center mt-[50px] h-[calc(100vh-50px)]">
<div class="relative flex flex-col items-center justify-center h-[100vh]">
<div class="absolute h-fit w-fit top-1/2 left-1/2">
<Confetti amount={200} y={[-1, 1.5]} x={[-2.3, 2.3]} colorArray={['#C6A0F3']} />
</div>
@@ -128,7 +128,7 @@
<Button class="mt-4" onclick={saveConfig}>Finish</Button>
</div>
{:else}
<div class="grid grid-cols-[0.41fr_1fr] mt-[50px] h-[calc(100vh-50px)]">
<div class="grid grid-cols-[0.41fr_1fr] h-[100vh]">
<div class="w-full h-full border-r border-theme-800/90 flex flex-col gap-6 p-3">
{#each steps as step, i (step)}
<div