chore: remove konami code, add themes

This commit is contained in:
2026-04-29 23:32:47 +02:00
parent ef36f49a6f
commit 6a1c5441c3
13 changed files with 222 additions and 182 deletions
+56
View File
@@ -145,6 +145,62 @@
--color-sidebar-ring: var(--sidebar-ring); --color-sidebar-ring: var(--sidebar-ring);
} }
main[data-theme="execution_clap"] {
--primary: #fc112b;
--primary-foreground: #fff;
--ring: #fc112b;
--color-primary-50: #fef2f2;
--color-primary-100: #fee2e2;
--color-primary-200: #fecaca;
--color-primary-300: #fca5a5;
--color-primary-400: #f87171;
--color-primary-500: #ef4444;
--color-primary-600: #dc2626;
--color-primary-700: #b91c1c;
--color-primary-800: #991b1b;
--color-primary-900: #7f1d1d;
--color-primary-950: #450a0a;
--bg-primary-50: #fef2f2;
--bg-primary-100: #fee2e2;
--bg-primary-200: #fecaca;
--bg-primary-300: #fca5a5;
--bg-primary-400: #f87171;
--bg-primary-500: #ef4444;
--bg-primary-600: #dc2626;
--bg-primary-700: #b91c1c;
--bg-primary-800: #991b1b;
--bg-primary-900: #7f1d1d;
--bg-primary-950: #450a0a;
}
main[data-theme="lost_umbrella"] {
--primary: #e5e5e5;
--primary-foreground: #000;
--ring: #e5e5e5;
--color-primary-50: #0d0d0d;
--color-primary-100: #1a1a1a;
--color-primary-200: #333333;
--color-primary-300: #4d4d4d;
--color-primary-400: #666666;
--color-primary-500: #808080;
--color-primary-600: #999999;
--color-primary-700: #b3b3b3;
--color-primary-800: #cccccc;
--color-primary-900: #e6e6e6;
--color-primary-950: #f2f2f2;
--bg-primary-50: #0d0d0d;
--bg-primary-100: #1a1a1a;
--bg-primary-200: #333333;
--bg-primary-300: #4d4d4d;
--bg-primary-400: #666666;
--bg-primary-500: #808080;
--bg-primary-600: #999999;
--bg-primary-700: #b3b3b3;
--bg-primary-800: #cccccc;
--bg-primary-900: #e6e6e6;
--bg-primary-950: #f2f2f2;
}
@layer base { @layer base {
* { * {
@apply border-border outline-ring/50; @apply border-border outline-ring/50;

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import NyanCat from '$assets/nyan-cat.gif'; import NyanCat from '$assets/themes/nyan_cat/image.gif';
import { reduceAnimations } from '@/userSettings'; import { reduceAnimations } from '@/userSettings';
import { fade } from 'svelte/transition';
//TODO: maybe dynamic background images fetched from ezpp? //TODO: maybe dynamic background images fetched from ezpp?
@@ -17,7 +17,7 @@
</script> </script>
<button <button
class="group flex items-center h-12 rounded-full bg-primary-300 disabled:bg-primary-300/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" class="group flex items-center h-12 rounded-full bg-primary disabled:bg-primary/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} disabled={prop.disabled}
onclick={prop.onClick} onclick={prop.onClick}
> >
@@ -17,7 +17,7 @@
bind:ref bind:ref
{value} {value}
class={cn( class={cn(
'data-[highlighted]:bg-primary-300 data-[highlighted]:text-primary-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50', 'data-[highlighted]:bg-primary data-[highlighted]:text-primary-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className className
)} )}
{...restProps} {...restProps}
+3 -1
View File
@@ -3,6 +3,7 @@ import { ezppfarm } from './api/ezpp';
import type { Component } from 'svelte'; import type { Component } from 'svelte';
import Loading from '../screens/Loading.svelte'; import Loading from '../screens/Loading.svelte';
import type { Release } from './types'; import type { Release } from './types';
import { THEMES } from './themes';
export const currentView = writable<Component>(Loading); export const currentView = writable<Component>(Loading);
@@ -24,7 +25,8 @@ export const currentLoadingInfo = writable<string>('Initializing...');
export const firstStartup = writable<boolean>(false); export const firstStartup = writable<boolean>(false);
export const launching = writable<boolean>(false); export const launching = writable<boolean>(false);
export const nyanCatSong = writable<HTMLAudioElement | undefined>(undefined); export const theme = writable<(typeof THEMES)[number]>(THEMES[0]);
export const theme_video = writable<HTMLVideoElement | undefined>(undefined);
export const serverPing = writable<number | undefined>(undefined); export const serverPing = writable<number | undefined>(undefined);
export const serverConnectionFails = writable(0); export const serverConnectionFails = writable(0);
+3 -1
View File
@@ -7,7 +7,9 @@ const osuSkinsCollator = new Intl.Collator(undefined, {
sensitivity: 'base', sensitivity: 'base',
}); });
export const sortSkins = (skins: { name: string, author: string | undefined, modified: number}[]) => export const sortSkins = (
skins: { name: string; author: string | undefined; modified: number }[]
) =>
skins.sort((a, b) => { skins.sort((a, b) => {
const pa = priority[a.name[0]]; const pa = priority[a.name[0]];
const pb = priority[b.name[0]]; const pb = priority[b.name[0]];
+43
View File
@@ -0,0 +1,43 @@
import executionclap_video from '$assets/themes/execution_clap/video.webm';
import lostumbrella_video from '$assets/themes/lost_umbrella/video.webm';
import nyancat_image from '$assets/themes/nyan_cat/image.gif';
import nyancat_audio from '$assets/themes/nyan_cat/audio.mp3';
export const THEMES = [
{
name: 'default',
display_name: 'Default',
assets: {
background_video: undefined,
background_image: undefined,
audio: undefined,
},
},
{
name: 'execution_clap',
display_name: 'Execution Clap',
assets: {
background_video: executionclap_video,
background_image: undefined,
audio: undefined,
},
},
{
name: 'lost_umbrella',
display_name: 'Lost Umbrella',
assets: {
background_video: lostumbrella_video,
background_image: undefined,
audio: undefined,
},
},
{
name: 'nyan_cat',
display_name: 'Nyan Cat',
assets: {
background_video: undefined,
background_image: nyancat_image,
audio: nyancat_audio,
},
},
];
+4 -13
View File
@@ -7,14 +7,8 @@ declare global {
*/ */
interface Umami { interface Umami {
track(): Promise<string> | undefined; track(): Promise<string> | undefined;
track( track(event_name: string, event_data?: Record<string, unknown>): Promise<string> | undefined;
event_name: string, track(custom_payload: { website: string; [key: string]: unknown }): Promise<string> | undefined;
event_data?: Record<string, unknown>,
): Promise<string> | undefined;
track(custom_payload: {
website: string;
[key: string]: unknown;
}): Promise<string> | undefined;
track( track(
callback: (props: { callback: (props: {
hostname: string; hostname: string;
@@ -24,17 +18,14 @@ interface Umami {
title: string; title: string;
url: string; url: string;
website: string; website: string;
}) => { website: string; [key: string]: unknown }, }) => { website: string; [key: string]: unknown }
): Promise<string> | undefined; ): Promise<string> | undefined;
/** Pass in your own ID to identify a user. */ /** Pass in your own ID to identify a user. */
identify(identity_id: string): Promise<void>; identify(identity_id: string): Promise<void>;
/** Save data about the current session. */ /** Save data about the current session. */
identify( identify(identity_id: string, data: Record<string, unknown>): Promise<void>;
identity_id: string,
data: Record<string, unknown>,
): Promise<void>;
/** To save data without a unique ID, pass in only a JSON object. */ /** To save data without a unique ID, pass in only a JSON object. */
identify(data: Record<string, unknown>): Promise<void>; identify(data: Record<string, unknown>): Promise<void>;
+25 -121
View File
@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import Logo from '$assets/logo.png'; import Logo from '$assets/logo.png';
import NyanCatSong from '$assets/nyan-cat.mp3';
import '../app.css'; import '../app.css';
import Titlebar from '@/components/ui/titlebar/titlebar.svelte'; import Titlebar from '@/components/ui/titlebar/titlebar.svelte';
@@ -10,10 +9,11 @@
discordPresence, discordPresence,
firstStartup, firstStartup,
launcherVersion, launcherVersion,
nyanCatSong,
platform, platform,
presenceLoading, presenceLoading,
setupValues, setupValues,
theme,
theme_video,
trackingEnabled, trackingEnabled,
} from '@/global'; } from '@/global';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@@ -38,12 +38,10 @@
import '@fontsource/space-mono'; import '@fontsource/space-mono';
import AnimatedBg from '@/components/ui/animated-bg/AnimatedBg.svelte'; import AnimatedBg from '@/components/ui/animated-bg/AnimatedBg.svelte';
import NyanCatBg from '@/components/ui/animated-bg/NyanCatBg.svelte'; import NyanCatBg from '@/components/ui/animated-bg/NyanCatBg.svelte';
import { sileo } from 'sileo';
let { children } = $props(); let { children } = $props();
let unsupported_platform = $state<boolean>(false); let unsupported_platform = $state<boolean>(false);
let secret_enabled = $state<boolean>(false);
function disableReload() { function disableReload() {
if (window.location.hostname !== 'tauri.localhost') { if (window.location.hostname !== 'tauri.localhost') {
@@ -93,19 +91,6 @@
); );
} }
let konami_code = [
'ArrowUp',
'ArrowUp',
'ArrowDown',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'ArrowLeft',
'ArrowRight',
'b',
'a',
];
onMount(async () => { onMount(async () => {
window.Buffer = Buffer; window.Buffer = Buffer;
@@ -118,108 +103,6 @@
firstStartup.set(isFirstStartup); firstStartup.set(isFirstStartup);
$userAuth.init(); $userAuth.init();
document.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === konami_code[0].toLowerCase()) {
konami_code.shift();
if (konami_code.length === 0) {
if (secret_enabled) {
sileo.success({
title: 'Cheatcode deactivated!',
duration: 3000,
fill: '#181825',
styles: {
description: 'text-center!',
},
});
const audio = $nyanCatSong;
if (audio) {
const fadeOutDuration = 2000; // 2 seconds
const fadeOutSteps = 20;
const fadeOutStepTime = fadeOutDuration / fadeOutSteps;
const volumeStep = audio.volume / fadeOutSteps;
const fadeOutInterval = setInterval(() => {
if (audio.volume > 0) {
audio.volume = Math.max(0, audio.volume - volumeStep);
} else {
audio.pause();
clearInterval(fadeOutInterval);
}
}, fadeOutStepTime);
}
nyanCatSong.set(undefined);
secret_enabled = false;
konami_code = [
'ArrowUp',
'ArrowUp',
'ArrowDown',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'ArrowLeft',
'ArrowRight',
'b',
'a',
];
return;
}
sileo.success({
title: 'Cheatcode activated!',
duration: 3000,
fill: '#181825',
styles: {
description: 'text-center!',
},
});
const audio = new Audio(NyanCatSong);
audio.loop = true;
audio.volume = 0;
audio.play();
const fadeInVolume = 0.15;
const fadeInDuration = 2000; // 2 seconds
const fadeInSteps = 20;
const fadeInStepTime = fadeInDuration / fadeInSteps;
const volumeStep = (fadeInVolume - audio.volume) / fadeInSteps;
const fadeInInterval = setInterval(() => {
if (audio.volume < fadeInVolume) {
audio.volume = Math.min(fadeInVolume, audio.volume + volumeStep);
} else {
clearInterval(fadeInInterval);
}
}, fadeInStepTime);
nyanCatSong.set(audio);
secret_enabled = true;
konami_code = [
'ArrowUp',
'ArrowUp',
'ArrowDown',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'ArrowLeft',
'ArrowRight',
'b',
'a',
];
}
} else {
konami_code = [
'ArrowUp',
'ArrowUp',
'ArrowDown',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'ArrowLeft',
'ArrowRight',
'b',
'a',
];
}
});
currentLoadingInfo.set('Loading config...'); currentLoadingInfo.set('Loading config...');
const config_patching = $userSettings.value('patching'); const config_patching = $userSettings.value('patching');
const config_custom_cursor = $userSettings.value('custom_cursor'); const config_custom_cursor = $userSettings.value('custom_cursor');
@@ -298,9 +181,25 @@
</AlertDialog.Content> </AlertDialog.Content>
</AlertDialog.Root> </AlertDialog.Root>
<main> <main data-theme={$theme.name ?? 'default'}>
<div class="opacity-30"> <div class="opacity-30">
{#if secret_enabled} {#if $theme}
{#if $theme.assets.background_video}
{#key $theme.name}
<div transition:fade={{ duration: $reduceAnimations ? 0 : 500 }}>
<video
class="absolute z-0 top-0 left-0 w-full h-full object-cover object-center aspect-video"
autoplay
playsinline
loop
volume={0.15}
bind:this={$theme_video}
>
<source src={$theme.assets.background_video} />
</video>
</div>
{/key}
{:else if $theme.name === 'nyan_cat'}
<div transition:fade={{ duration: $reduceAnimations ? 0 : 500 }}> <div transition:fade={{ duration: $reduceAnimations ? 0 : 500 }}>
<NyanCatBg /> <NyanCatBg />
</div> </div>
@@ -309,6 +208,11 @@
<AnimatedBg /> <AnimatedBg />
</div> </div>
{/if} {/if}
{:else}
<div transition:fade={{ duration: $reduceAnimations ? 0 : 500 }}>
<AnimatedBg />
</div>
{/if}
</div> </div>
{@render children()} {@render children()}
</main> </main>
+59 -21
View File
@@ -13,7 +13,6 @@
launcherVersion, launcherVersion,
launching, launching,
newVersion, newVersion,
nyanCatSong,
osuBuild, osuBuild,
osuStream, osuStream,
platform, platform,
@@ -22,6 +21,8 @@
serverPing, serverPing,
skins, skins,
skinsCount, skinsCount,
theme,
theme_video,
trackingEnabled, trackingEnabled,
} from '@/global'; } from '@/global';
import { import {
@@ -110,6 +111,7 @@
import DownloadButton from '@/components/ui/download-button/DownloadButton.svelte'; import DownloadButton from '@/components/ui/download-button/DownloadButton.svelte';
import { animate } from 'animejs'; import { animate } from 'animejs';
import { sileo } from 'sileo'; import { sileo } from 'sileo';
import { THEMES } from '@/themes';
let selectedView = $state('home'); let selectedView = $state('home');
let progress = $state(-1); let progress = $state(-1);
@@ -435,20 +437,20 @@
await new Promise((res) => setTimeout(res, 1500)); await new Promise((res) => setTimeout(res, 1500));
launchInfo = 'Launching osu!...'; launchInfo = 'Launching osu!...';
if ($nyanCatSong) { if ($theme_video) {
const audio = $nyanCatSong; const video = $theme_video;
const fadeOutDuration = 2000; // 2 seconds const fadeOutDuration = 2000; // 2 seconds
const fadeOutSteps = 20; const fadeOutSteps = 20;
const fadeOutStepTime = fadeOutDuration / fadeOutSteps; const fadeOutStepTime = fadeOutDuration / fadeOutSteps;
const volumeStep = audio.volume / fadeOutSteps; const volumeStep = video.volume / fadeOutSteps;
const fadeOutInterval = setInterval(() => { const fadeOutInterval = setInterval(() => {
if (audio.volume > 0) { if (video.volume > 0) {
audio.volume = Math.max(0, audio.volume - volumeStep); video.volume = Math.max(0, video.volume - volumeStep);
} else { } else {
clearInterval(fadeOutInterval); clearInterval(fadeOutInterval);
audio.pause(); video.pause();
audio.currentTime = 0; video.currentTime = 0;
} }
}, fadeOutStepTime); }, fadeOutStepTime);
} }
@@ -563,20 +565,21 @@
cleanup = true; cleanup = true;
launchInfo = 'Cleaning up...'; launchInfo = 'Cleaning up...';
if ($nyanCatSong) { if ($theme_video) {
const audio = $nyanCatSong; const video = $theme_video;
audio.play(); video.play();
audio.volume = 0; video.currentTime = 0;
video.volume = 0;
const fadeInVolume = 0.15; const fadeInVolume = 0.15;
const fadeInDuration = 2000; // 2 seconds const fadeInDuration = 2000; // 2 seconds
const fadeInSteps = 20; const fadeInSteps = 20;
const fadeInStepTime = fadeInDuration / fadeInSteps; const fadeInStepTime = fadeInDuration / fadeInSteps;
const volumeStep = (fadeInVolume - audio.volume) / fadeInSteps; const volumeStep = (fadeInVolume - video.volume) / fadeInSteps;
const fadeInInterval = setInterval(() => { const fadeInInterval = setInterval(() => {
if (audio.volume < fadeInVolume) { if (video.volume < fadeInVolume) {
audio.volume = Math.min(fadeInVolume, audio.volume + volumeStep); video.volume = Math.min(fadeInVolume, video.volume + volumeStep);
} else { } else {
clearInterval(fadeInInterval); clearInterval(fadeInInterval);
} }
@@ -925,15 +928,15 @@
<div <div
class="p-3 border-r border-r-theme-900 flex flex-col items-center gap-2 z-10 bg-black/40 backdrop-blur-sm" class="p-3 border-r border-r-theme-900 flex flex-col items-center gap-2 z-10 bg-black/40 backdrop-blur-sm"
> >
<div class="bg-primary-800/80 ring-1 ring-inset ring-white/15 rounded-[1.1rem] p-1.5"> <div class="bg-primary/30 ring-1 ring-inset ring-white/15 rounded-[1.1rem] p-1.5">
<img class="pointer-events-none" src={Logo} alt="logo" bind:this={ezppLogo} /> <img class="pointer-events-none" src={Logo} alt="logo" bind:this={ezppLogo} />
</div> </div>
<Badge class="text-[0.5rem] py-0 px-2">{$launcherVersion || 'dev'}</Badge> <Badge class="text-[0.5rem] py-0 px-2">{$launcherVersion || 'dev'}</Badge>
<Button <Button
class="flex size-12 items-center gap-2 ring-1 ring-inset ring-white/15 {selectedView === class="flex size-12 items-center gap-2 ring-1 ring-inset ring-white/15 {selectedView ===
'home' 'home'
? 'bg-primary-300/50' ? 'bg-primary/50'
: 'bg-black/20 border-black/20'} hover:bg-primary-300/50 rounded-[0.85rem] p-3 mt-3" : 'bg-black/20 border-black/20'} hover:bg-primary/50 rounded-[0.85rem] p-3 mt-3"
disabled={$launching} disabled={$launching}
onclick={() => { onclick={() => {
if (!$launching) selectedView = 'home'; if (!$launching) selectedView = 'home';
@@ -944,8 +947,8 @@
<Button <Button
class="flex size-12 items-center gap-2 ring-1 ring-inset ring-white/15 {selectedView === class="flex size-12 items-center gap-2 ring-1 ring-inset ring-white/15 {selectedView ===
'settings' 'settings'
? 'bg-primary-300/50' ? 'bg-primary/50'
: 'bg-black/20 border-black/20'} hover:bg-primary-300/50 rounded-[0.85rem] p-3 mt-3" : 'bg-black/20 border-black/20'} hover:bg-primary/50 rounded-[0.85rem] p-3 mt-3"
disabled={$launching} disabled={$launching}
onclick={() => { onclick={() => {
if (!$launching) selectedView = 'settings'; if (!$launching) selectedView = 'settings';
@@ -1009,7 +1012,9 @@
}} }}
out:fly={{ duration: $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="relative z-10 px-8 py-4 flex items-center justify-between bg-black/40 backdrop-blur-sm border-t border-t-theme-900"
>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
{#if $osuInstallationPath !== ''} {#if $osuInstallationPath !== ''}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@@ -1479,6 +1484,39 @@
</Select.Root> </Select.Root>
</div> </div>
{/if} {/if}
<div class="flex flex-col">
<Label class="text-sm" for="setting-custom-cursor">launcher theme</Label>
<div class="text-muted-foreground text-xs">select the theme of this interface</div>
</div>
<div class="flex flex-row w-full">
<Select.Root
type="single"
value={$theme.name ?? 'default'}
onValueChange={async (them) => {
const newTheme = THEMES.find((theme) => theme.name === them);
if (newTheme) theme.set(newTheme);
$userSettings.value('launcherTheme').set(them);
await $userSettings.save();
}}
>
<Select.Trigger
class="border-theme-800 bg-theme-950 !text-muted-foreground font-semibold"
>
<div class="flex flex-row items-center gap-2 font-normal text-foreground">
{$theme.display_name}
</div>
</Select.Trigger>
<Select.Content class="bg-theme-950 border border-theme-950 rounded-lg">
{#each THEMES as theme (theme.name)}
<Select.Item value={theme.name}>
<div class="flex flex-row gap-2 items-center">
{theme.display_name}
</div>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
</div> </div>
</div> </div>
{:else if selectedView === 'login'} {:else if selectedView === 'login'}
+6 -1
View File
@@ -15,6 +15,7 @@
osuStream, osuStream,
skins, skins,
skinsCount, skinsCount,
theme,
} from '@/global'; } from '@/global';
import { import {
cursorSmoothness, cursorSmoothness,
@@ -42,6 +43,7 @@
import { git } from '@/api/git'; import { git } from '@/api/git';
import { sileo } from 'sileo'; import { sileo } from 'sileo';
import type { EZPPUser } from '@/types'; import type { EZPPUser } from '@/types';
import { THEMES } from '@/themes';
let ezppLogo: HTMLImageElement; let ezppLogo: HTMLImageElement;
let spinnerCircle: SVGCircleElement; let spinnerCircle: SVGCircleElement;
@@ -131,6 +133,9 @@
} }
if (!$firstStartup) { if (!$firstStartup) {
const launcherTheme = $userSettings.value('launcherTheme').get('default');
const them = THEMES.find((theme) => theme.name === launcherTheme);
if (them) theme.set(them);
currentLoadingInfo.set('Checking osu installation path...'); currentLoadingInfo.set('Checking osu installation path...');
const validFolder = await isValidOsuFolder($osuInstallationPath); const validFolder = await isValidOsuFolder($osuInstallationPath);
if (!validFolder) { if (!validFolder) {
@@ -259,11 +264,11 @@
viewBox="0 0 208 208" viewBox="0 0 208 208"
> >
<circle <circle
class="stroke-primary"
cx="104" cx="104"
cy="104" cy="104"
r="90" r="90"
fill="none" fill="none"
stroke="#ff0098"
stroke-width="8" stroke-width="8"
stroke-linecap="round" stroke-linecap="round"
stroke-dasharray="180 385" stroke-dasharray="180 385"