Compare commits

...

10 Commits

10 changed files with 261 additions and 165 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "ezpplauncher",
"version": "3.1.1",
"version": "3.1.2",
"description": "",
"type": "module",
"scripts": {
@@ -32,7 +32,7 @@
"animejs": "^4.1.3",
"buffer": "^6.0.3",
"crypto-js": "^4.2.0",
"lucide-svelte": "^0.562.0",
"lucide-svelte": "^0.575.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-to-webcomponent": "^2.0.1",
@@ -45,7 +45,7 @@
"@eslint/compat": "^2.0.0",
"@eslint/js": "^9.35.0",
"@iarna/toml": "2.2.5",
"@lucide/svelte": "^0.562.0",
"@lucide/svelte": "^0.575.0",
"@sveltejs/adapter-static": "^3.0.9",
"@sveltejs/kit": "^2.37.1",
"@sveltejs/vite-plugin-svelte": "^6.1.4",
+2 -1
View File
@@ -1007,10 +1007,11 @@ dependencies = [
[[package]]
name = "ezpplauncher"
version = "3.1.0"
version = "3.1.2"
dependencies = [
"base64 0.22.1",
"discord-rich-presence",
"futures-util",
"hardware-id",
"md5",
"once_cell",
+3 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "ezpplauncher"
version = "3.1.1"
version = "3.1.2"
description = "EZPPLauncher redefined."
authors = ["HorizonCode"]
edition = "2024"
@@ -32,15 +32,16 @@ reqwest = { version = "0.13.1", features = ["json", "stream", "query"] }
md5 = "0.8.0"
tokio = { version = "1.48.0", features = ["full"] }
open = "5.3.3"
windows-sys = "0.61.2"
discord-rich-presence = "1.0.0"
once_cell = "1.21.3"
futures-util = "0.3"
[target.'cfg(windows)'.dependencies]
winreg = "0.55.0"
winapi = { version = "0.3", features = ["winuser", "wincrypt", "memoryapi", "winbase", "dpapi"] }
base64 = "0.22.1"
widestring = "1.2.1"
windows-sys = "0.61.2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-single-instance = "2.3.6"
+7 -7
View File
@@ -77,10 +77,10 @@ pub fn find_osu_installation() -> Option<String> {
let hklm_registry_paths = ["SOFTWARE\\Classes\\osu\\DefaultIcon"];
let hkcr_registry_paths = [
"osustable.File.osk\\DefaultIcon",
"osustable.File.osr\\DefaultIcon",
"osustable.File.osz\\DefaultIcon",
let hkcu_registry_paths = [
"Software\\Classes\\osustable.File.osk\\DefaultIcon",
"Software\\Classes\\osustable.File.osr\\DefaultIcon",
"Software\\Classes\\osustable.File.osz\\DefaultIcon",
];
let osu_folder_files = [
@@ -129,10 +129,10 @@ pub fn find_osu_installation() -> Option<String> {
}
}
let hkcr = RegKey::predef(HKEY_CLASSES_ROOT);
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
for reg_path in &hkcr_registry_paths {
if let Ok(subkey) = hkcr.open_subkey_with_flags(reg_path, KEY_READ | KEY_WOW64_32KEY) {
for reg_path in &hkcu_registry_paths {
if let Ok(subkey) = hkcu.open_subkey_with_flags(reg_path, KEY_READ | KEY_WOW64_32KEY) {
let value_names = [""];
for value_name in &value_names {
if let Ok(value) = subkey.get_value::<String, _>(value_name) {
+30 -12
View File
@@ -2,6 +2,7 @@ use discord_rich_presence::{
DiscordIpc, DiscordIpcClient,
activity::{Activity, Assets, Button, Timestamps},
};
use futures_util::future::BoxFuture;
use once_cell::sync::Lazy;
use std::sync::Mutex as StdMutex;
use std::time::{SystemTime, UNIX_EPOCH};
@@ -24,6 +25,7 @@ enum PresenceCommand {
Disconnect(oneshot::Sender<()>),
UpdateData(PresenceData),
IsConnected(oneshot::Sender<bool>),
Tick,
}
struct PresenceActor {
@@ -58,7 +60,7 @@ impl PresenceActor {
}
async fn run(&mut self) {
let mut update_interval = interval(Duration::from_millis(2500));
let mut update_interval = interval(Duration::from_secs(5));
loop {
tokio::select! {
@@ -74,18 +76,22 @@ impl PresenceActor {
PresenceCommand::IsConnected(responder) => {
let _ = responder.send(self.client.is_some());
}
PresenceCommand::Tick => {
if self.client.is_some() {
self.handle_update().await;
}
}
}
}
_ = update_interval.tick() => {
if self.client.is_some() {
self.handle_update().await;
}
let _ = PRESENCE_TX.send(PresenceCommand::Tick).await;
}
}
}
}
async fn handle_connect(&mut self, responder: oneshot::Sender<bool>) {
fn handle_connect(&mut self, responder: oneshot::Sender<bool>) -> BoxFuture<'_, ()> {
Box::pin(async move {
if self.client.is_some() {
let _ = responder.send(true);
return;
@@ -101,10 +107,20 @@ impl PresenceActor {
let _ = responder.send(true);
}
Err(e) => {
eprintln!("Failed to create Discord client: {:?}", e);
let _ = responder.send(false);
eprintln!("Failed to connect to Discord: {:?}. Retrying in 5s...", e);
let tx = PRESENCE_TX.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(5)).await;
let (resp_tx, _) = oneshot::channel();
let _ = tx.send(PresenceCommand::Connect(resp_tx)).await;
});
if !responder.is_closed() {
let _ = responder.send(false);
}
}
}
})
}
async fn handle_disconnect(&mut self, responder: oneshot::Sender<()>) {
@@ -143,12 +159,14 @@ impl PresenceActor {
Button::new("Join EZPPFarm", "https://ez-pp.farm/discord"),
]);
if let Err(e) = client.set_activity(activity).map_err(|e| e.to_string()) {
if let Err(e) = client.set_activity(activity) {
eprintln!("Failed to set activity, disconnecting: {:?}", e);
if let Some(mut client) = self.client.take() {
let _ = client.clear_activity();
let _ = client.close();
}
self.client = None;
let (tx, _) = oneshot::channel();
self.handle_connect(tx).await;
} else {
println!("Actor: Presence updated successfully.");
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "EZPPLauncher",
"version": "3.1.1",
"version": "3.1.2",
"identifier": "farm.EZPPFarm.Launcher",
"mainBinaryName": "ezpplauncher",
"build": {
@@ -1,4 +1,8 @@
<script lang="ts">
import Logo from '$assets/logo.png';
import { reduceAnimations } from '@/userSettings';
import { fade } from 'svelte/transition';
//TODO: maybe dynamic background images fetched from ezpp?
const COUNT = 60;
@@ -7,41 +11,43 @@
return Math.random() * (max - min) + min;
}
const streaks = Array.from({ length: COUNT }).map((_, i) => {
const depth = rand(0, 2);
const streaks = Array.from({ length: COUNT })
.map((_, i) => {
const depth = rand(0, 2);
const scale = 0.6 + depth * 0.8;
const speed = 0.4 + depth * 1.35;
const scale = 0.6 + depth * 0.8;
const speed = 0.4 + depth * 1.35;
const distance = 150;
const duration = distance / speed;
const distance = 150;
const duration = distance / speed;
return {
width: rand(180, 320) * scale,
height: rand(16, 32) * scale,
x: (i / COUNT) * 200 - 50,
y: ((i * 137.5) % 100) - 20,
opacity: 0.25 + depth * 0.65,
duration,
delay: rand(0, duration),
scale,
};
});
return {
width: rand(300, 320) * scale,
height: 32 * scale,
x: (i / COUNT) * 200 - 50,
y: ((i * 137.5) % 100) - 20,
opacity: 0.25 + depth * 0.65,
duration,
delay: rand(0, duration),
scale,
};
})
.sort((a, b) => a.scale - b.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}
{#each streaks as s, index (index)}
<div
class="streak drop-shadow-lg"
class="streak drop-shadow-lg transition-opacity duration-1000"
style="
width:{s.width}px;
height:{s.height}px;
left:{s.x}%;
top:{s.y}%;
opacity:{s.opacity};
opacity:{$reduceAnimations ? 0 : s.opacity};
animation-duration:{s.duration}s;
animation-delay:-{s.delay}s;
"
@@ -54,7 +60,12 @@
position: fixed;
inset: 0;
overflow: hidden;
background: linear-gradient(180deg, var(--color-primary-300) 0%, var(--color-primary-600) 40%, var(--color-primary-900) 100%);
background: linear-gradient(
180deg,
var(--color-primary-300) 0%,
var(--color-primary-600) 40%,
var(--color-primary-900) 100%
);
}
.streak {
+24 -21
View File
@@ -8,6 +8,7 @@
import { onMount } from 'svelte';
import { launcherVersion } from '@/global';
import Badge from '../badge/badge.svelte';
import { reduceAnimations } from '@/userSettings';
onMount(() => {
const appWindow = getCurrentWindow();
@@ -20,22 +21,39 @@
</script>
<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 class="relative flex items-center justify-center p-1 group" id="titlebar-minimize">
<div
class="absolute inset-0 bg-background rounded
scale-50 opacity-0
group-hover:opacity-100
group-hover:scale-100
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
origin-center"
></div>
<Minimize class="z-10 pointer-events-none" size={14} />
</div>
<div class="titlebar-button close rounded transition-colors duration-75" id="titlebar-close">
<Close size={14} />
<div class="relative flex items-center justify-center p-1 group" id="titlebar-close">
<div
class="absolute inset-0 bg-red-500 rounded
scale-50 opacity-0
group-hover:opacity-100
group-hover:scale-100
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
origin-center"
></div>
<Close class="z-10 pointer-events-none" size={14} />
</div>
</div>
<style lang="scss">
.titlebar {
height: 35px;
/* background: #040612; */
user-select: none;
display: flex;
justify-content: flex-end;
gap: 10px;
gap: 5px;
align-items: center;
position: fixed;
top: 0;
@@ -45,19 +63,4 @@
padding-right: 5px;
pointer-events: auto;
}
.titlebar-button {
display: inline-flex;
justify-content: center;
align-items: center;
width: 25px;
height: 25px;
user-select: none;
-webkit-user-select: none;
}
.titlebar-button:hover {
&.close {
background: #c22e2e;
}
background: #2d3049;
}
</style>
+32 -11
View File
@@ -882,7 +882,10 @@
class="flex size-12 items-center gap-2 border {selectedView === 'home'
? 'bg-primary-300/50 border-primary-300/50'
: 'bg-black/20 border-black/20'} hover:bg-primary-300/50 hover:border-primary-300/50 rounded-[0.85rem] p-3 mt-3"
onclick={() => (selectedView = 'home')}
disabled={$launching}
onclick={() => {
if (!$launching) selectedView = 'home';
}}
>
<House class="text-theme-200 !size-5" />
</Button>
@@ -890,13 +893,16 @@
class="flex size-12 items-center gap-2 border {selectedView === 'settings'
? 'bg-primary-300/50 border-primary-300/50'
: 'bg-black/20 border-black/20'} hover:bg-primary-300/50 hover:border-primary-300/50 rounded-[0.85rem] p-3 mt-3"
onclick={() => (selectedView = 'settings')}
disabled={$launching}
onclick={() => {
if (!$launching) selectedView = 'settings';
}}
>
<Settings class="text-theme-200 !size-5" />
</Button>
<div class="mt-auto">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<DropdownMenu.Trigger disabled={$launching}>
<Avatar.Root class="size-10">
<Avatar.Image src="https://a.ez-pp.farm/{$currentUser?.id ?? 0}" />
<Avatar.Fallback class="bg-theme-900">
@@ -942,8 +948,13 @@
{#if selectedView === 'home'}
<div
class="flex flex-col-reverse h-full"
in:fly={{ duration: 400, delay: 400, y: 5, opacity: 0 }}
out:fly={{ duration: 400, y: -5, opacity: 0 }}
in:fly={{
duration: $reduceAnimations ? 0 : 400,
delay: $reduceAnimations ? 0 : 400,
y: 5,
opacity: 0,
}}
out:fly={{ duration: $reduceAnimations ? 0 : 400, y: -5, opacity: 0 }}
>
<div class="relative z-10 px-8 py-4 flex items-center justify-between">
<div class="flex items-center gap-4">
@@ -1064,8 +1075,8 @@
{#if $currentUserInfo}
<div
class="m-1 w-72 h-48 bg-black/40 backdrop-blur-sm rounded-md border border-black/40 flex flex-col items-center p-3"
in:fly={{ duration: 400, y: 5, opacity: 0 }}
out:fly={{ duration: 400, y: -5, opacity: 0 }}
in:fly={{ duration: $reduceAnimations ? 0 : 400, y: 5, opacity: 0 }}
out:fly={{ duration: $reduceAnimations ? 0 : 400, y: -5, opacity: 0 }}
>
<Select.Root
type="single"
@@ -1217,8 +1228,13 @@
{:else if selectedView === 'settings'}
<div
class="h-[100vh] w-full flex flex-col items-center justify-center"
in:fly={{ duration: 400, delay: 400, y: 5, opacity: 0 }}
out:fly={{ duration: 400, y: -5, opacity: 0 }}
in:fly={{
duration: $reduceAnimations ? 0 : 400,
delay: $reduceAnimations ? 0 : 400,
y: 5,
opacity: 0,
}}
out:fly={{ duration: $reduceAnimations ? 0 : 400, y: -5, opacity: 0 }}
>
<div class="p-8 w-full">
<div class="bg-black/40 backdrop-blur-sm py-8 px-6 rounded-lg">
@@ -1419,8 +1435,13 @@
{:else if selectedView === 'login'}
<div
class="h-[100vh] w-full flex flex-col items-center justify-center"
in:fly={{ duration: 400, delay: 400, y: 5, opacity: 0 }}
out:fly={{ duration: 400, y: -5, opacity: 0 }}
in:fly={{
duration: $reduceAnimations ? 0 : 400,
delay: $reduceAnimations ? 0 : 400,
y: 5,
opacity: 0,
}}
out:fly={{ duration: $reduceAnimations ? 0 : 400, y: -5, opacity: 0 }}
>
<div class="p-8 px-52 w-full">
<form onsubmit={performLogin} class="bg-black/40 backdrop-blur-sm rounded-lg p-8">
+127 -86
View File
@@ -5,7 +5,7 @@
import { animate } from 'animejs';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import { Check, CheckCircle, CircleOff, Settings2 } from 'lucide-svelte';
import { Check, CircleCheckBig, CircleOff, LoaderCircle } from 'lucide-svelte';
import { open } from '@tauri-apps/plugin-dialog';
import Checkbox from '@/components/ui/checkbox/checkbox.svelte';
import {
@@ -17,7 +17,17 @@
userSettings,
} from '@/userSettings';
import Label from '@/components/ui/label/label.svelte';
import { beatmapSets, currentSkin, currentView, osuBuild, osuStream, skins } from '@/global';
import {
beatmapSets,
currentSkin,
currentView,
discordPresence,
osuBuild,
osuStream,
platform,
presenceLoading,
skins,
} from '@/global';
import Launch from './Launch.svelte';
import Confetti from 'svelte-confetti';
import {
@@ -110,6 +120,7 @@
onMount(async () => {
const osuPath = await autoDetectOsuInstallFolder();
console.log(osuPath);
if (osuPath) {
osuInstallPath = osuPath;
autoDetectedOsuPath = true;
@@ -119,7 +130,7 @@
</script>
{#if wizardFinished}
<div class="relative flex flex-col items-center justify-center h-[100vh]">
<div class="relative flex flex-col items-center justify-center h-[100vh] z-50">
<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,15 +139,17 @@
<Button class="mt-4" onclick={saveConfig}>Finish</Button>
</div>
{:else}
<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">
<div class="grid grid-cols-[0.41fr_1fr] h-[100vh] z-50">
<div
class="w-full h-full border-r border-black/40 bg-black/40 backdrop-blur-md flex flex-col gap-6 p-3 z-50"
>
{#each steps as step, i (step)}
<div
class="flex flex-row items-center gap-2 border {selectedStep === i + 1
? 'border-primary-800/30 bg-primary-900/30'
? 'border-primary-600/30 bg-primary-700/30'
: selectedStep > i
? 'border-green-800/30 bg-green-900/30'
: 'border-theme-800 bg-theme-900'} rounded-lg p-2 transition-all"
: 'border-black/30 bg-black/30'} rounded-lg p-2 transition-all"
>
<div
class="flex flex-col items-center justify-center h-8 w-8 border-[2px] {selectedStep >
@@ -160,10 +173,10 @@
</div>
{/each}
</div>
<div class="flex flex-col gap-6 w-full h-full bg-theme-900/40 p-6">
<div class="flex flex-col gap-6 w-full h-full backdrop-blur-md p-6 z-50">
{#if selectedStep === 1}
<div
class="my-auto h-full w-full bg-theme-800/15 rounded-lg border border-900/60 p-6 mt-5 flex flex-col items-center justify-center"
class="my-auto h-full w-full bg-black/40 rounded-lg border border-black/40 p-6 mt-5 flex flex-col items-center justify-center"
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
>
<img
@@ -178,13 +191,13 @@
<p class="text-muted-foreground mt-2">
This setup wizard will guide you through the initial setup of EZPPLauncher.
</p>
<div class="bg-red-800/20 border border-red-900/20 text-red-500 p-4 rounded-lg mt-4">
<div class="bg-red-800 border border-red-900 text-red-200 p-4 rounded-lg mt-4">
Please make sure you have osu! installed on your system before proceeding.
</div>
</div>
{:else if selectedStep === 2}
<div
class="my-auto h-full w-full bg-theme-800/15 rounded-lg border border-900/60 p-6 mt-5 flex flex-col items-center justify-center"
class="my-auto h-full w-full bg-black/40 rounded-lg border border-black/40 p-6 mt-5 flex flex-col items-center justify-center"
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
>
<h1 class="text-3xl font-semibold">Locate your osu! Installation</h1>
@@ -207,14 +220,14 @@
{#if !manualSelect}
{#if autoDetectedOsuPath}
<div
class="flex flex-row gap-3 bg-green-800/20 border border-green-900/20 text-green-500 p-4 rounded-lg mt-4"
class="flex flex-row gap-3 bg-green-800 border border-green-900 text-green-500 p-4 rounded-lg mt-4"
>
<CheckCircle />
<CircleCheckBig />
<span>Auto-detected osu! installation path! Please check if its correct!</span>
</div>
{:else}
<div
class="flex flex-row gap-3 bg-red-800/20 border border-red-900/20 text-red-500 p-4 rounded-lg mt-4"
class="flex flex-row gap-3 bg-red-800 border border-red-900 text-red-500 p-4 rounded-lg mt-4"
>
<CircleOff />
<span>Could not auto-detect osu! installation path. Please select it manually.</span
@@ -223,14 +236,14 @@
{/if}
{:else if manualSelectValid}
<div
class="flex flex-row gap-3 bg-green-800/20 border border-green-900/20 text-green-500 p-4 rounded-lg mt-4"
class="flex flex-row gap-3 bg-green-800 border border-green-900 text-green-500 p-4 rounded-lg mt-4"
>
<CheckCircle />
<CircleCheckBig />
<span>Selected osu! installation path is valid!</span>
</div>
{:else}
<div
class="flex flex-row gap-3 bg-red-800/20 border border-red-900/20 text-red-500 p-4 rounded-lg mt-4"
class="flex flex-row gap-3 bg-red-800 border border-red-900 text-red-500 p-4 rounded-lg mt-4"
>
<CircleOff />
<span
@@ -241,80 +254,108 @@
{/if}
</div>
{:else if selectedStep === 3}
<div
class="bg-theme-900/90 flex flex-col justify-center gap-3 mt-5 border border-theme-800/90 rounded-lg"
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
>
<div class="flex flex-row items-center gap-3 font-semibold text-xl px-3 pt-3">
<Settings2 /> EZPPLauncher Settings
</div>
<div class="h-full w-full flex items-center justify-center">
<div
class="grid grid-cols-[1fr_auto] gap-y-5 items-center border-t border-theme-800 py-3 px-6"
class="bg-black/40 backdrop-blur-sm py-8 px-8 rounded-lg"
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
>
<div class="flex flex-col">
<Label class="text-sm" for="setting-custom-cursor">Patching</Label>
<div class="text-muted-foreground text-xs">Shows misses in Relax and Autopilot</div>
</div>
<Checkbox
id="setting-custom-cursor"
checked={$patch}
onCheckedChange={async (e) => {
patch.set(e);
$userSettings.save();
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
<div class="grid grid-cols-[1fr_auto] gap-y-5 items-center px-6">
<div class="flex flex-col">
<Label class="text-sm" for="setting-patch">Patching</Label>
<div class="text-muted-foreground text-xs">
Shows misses in Relax and Autopilot {#if $platform !== 'windows'}<span
class="text-red-500 bg-red-800/20 border border-red-600/20 p-0.5 mx-1 px-2 rounded-lg !text-[0.55rem]"
>currently only on windows!</span
>
{/if}
</div>
</div>
<Checkbox
id="setting-patch"
checked={$platform === 'windows' ? $patch : false}
disabled={$platform !== 'windows'}
onCheckedChange={async (e) => {
patch.set(e);
$userSettings.save();
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
<div class="flex flex-col">
<Label class="text-sm" for="setting-custom-cursor">Lazer-Style Cursor</Label>
<div class="text-muted-foreground text-xs">
Enable a custom cursor in the Launcher like in the lazer build of osu!
<div class="flex flex-col">
<Label class="text-sm" for="setting-custom-cursor">Lazer-Style Cursor</Label>
<div class="text-muted-foreground text-xs">
Enable a custom cursor in the Launcher like in the lazer build of osu!
</div>
</div>
<Checkbox
id="setting-custom-cursor"
checked={$customCursor}
onCheckedChange={async (e) => {
if (!e) {
cursorSmoothening.set(false);
}
customCursor.set(e);
$userSettings.save();
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
<div class="flex flex-col">
<Label class="text-sm" for="setting-cursor-smoothening">Cursor Smoothening</Label>
<div class="text-muted-foreground text-xs">
Makes the custom cursor movement smoother.
</div>
</div>
<Checkbox
id="setting-cursor-smoothening"
checked={$cursorSmoothening}
onCheckedChange={async (e) => {
if (!$customCursor) return;
cursorSmoothening.set(e);
$userSettings.save();
}}
disabled={!$customCursor}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
<div class="flex flex-col">
<Label class="text-sm" for="setting-reduce-animations">Reduce Animations</Label>
<div class="text-muted-foreground text-xs">
Disables some animations in the Launcher to improve performance on low-end
devices.
</div>
</div>
<Checkbox
id="setting-reduce-animations"
checked={$reduceAnimations}
onCheckedChange={async (e) => {
reduceAnimations.set(e);
$userSettings.save();
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
<div class="flex flex-col">
<Label class="text-sm" for="setting-rich-presence">Discord Rich Presence</Label>
<div class="text-muted-foreground text-xs">
Let other discord users show what you are doing right now 👀
</div>
</div>
<div class="relative">
{#if $presenceLoading}
<div class="-left-8 absolute" transition:fade>
<LoaderCircle class="animate-spin" />
</div>
{/if}
<Checkbox
id="setting-rich-presence"
bind:checked={$discordPresence}
disabled={$presenceLoading}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
</div>
</div>
<Checkbox
id="setting-custom-cursor"
checked={$customCursor}
onCheckedChange={async (e) => {
if (!e) {
cursorSmoothening.set(false);
}
customCursor.set(e);
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
<div class="flex flex-col">
<Label class="text-sm" for="setting-cursor-smoothening">Cursor Smoothening</Label>
<div class="text-muted-foreground text-xs">
Makes the custom cursor movement smoother.
</div>
</div>
<Checkbox
id="setting-cursor-smoothening"
checked={$cursorSmoothening}
onCheckedChange={async (e) => {
if (!$customCursor) return;
cursorSmoothening.set(e);
}}
disabled={!$customCursor}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
<div class="flex flex-col">
<Label class="text-sm" for="setting-cursor-smoothening">Reduce Animations</Label>
<div class="text-muted-foreground text-xs">
Disables some animations in the Launcher to improve performance on low-end devices.
</div>
</div>
<Checkbox
id="setting-cursor-smoothening"
checked={$reduceAnimations}
onCheckedChange={async (e) => {
reduceAnimations.set(e);
}}
disabled={!$customCursor}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
</div>
</div>
{/if}