e.preventDefault()}>
-
-
-
Welcome to Emerald Legacy!
- setUsername(e.target.value)} className="w-full bg-black border-4 border-emerald-900 p-4 text-3xl text-center mb-8 outline-none" placeholder="Username..." />
- { ensureAudio(); playSfx('click.wav'); invoke("save_config", { config: { username, linuxRunner: isLinux ? selectedRunner : undefined } }); setIsFirstRun(false); setTimeout(playRandomMusic, 500); }} disabled={!username.trim() || (isLinux && availableRunners.length === 0)} className="legacy-btn py-4 px-12 text-3xl w-full">Start Setup
-
-
+ e.preventDefault()}>
+
e.preventDefault()}
+ >
-
-
-
-
-
- { playSfx('click.wav'); setActiveTab("home"); updateAllStatus(); }} className={`p-4 legacy-btn justify-start ${activeTab === "home" ? "active-tab" : ""}`}>HOME
- { playSfx('click.wav'); setActiveTab("versions"); updateAllStatus(); }} className={`p-4 legacy-btn justify-start ${activeTab === "versions" ? "active-tab" : ""}`}>VERSIONS
- { playSfx('click.wav'); setActiveTab("settings"); }} className={`p-4 legacy-btn justify-start ${activeTab === "settings" ? "active-tab" : ""}`}>SETTINGS
-
-
- {installingInstance && (
-
-
- Installing
- { playSfx('back.ogg'); invoke("cancel_download"); }} className="text-red-500 hover:underline">CANCEL
-
-
-
-
{downloadProgress}%
-
-
- )}
-
- { playSfx('click.wav'); openUrl("https://github.com/KayJannOnGit"); }} className={`${installingInstance ? "pt-6" : "mt-auto pt-6"} flex flex-col items-center border-t-4 border-black/30 cursor-pointer group`}>
- Developed by
- KayJann
-
-
+
+
{activeTab === "home" && (
-
-
-
-
Welcome, {username}!
-
-
- {installedStatus.vanilla_tu19 || installedStatus.vanilla_tu24 ? (
- <>
-
{ playSfx('click.wav'); setSelectedInstance(e.target.value); }} className="w-full legacy-select p-3 text-2xl outline-none">
- {installedStatus.vanilla_tu19 && Vanilla Nightly (TU19) }
- {installedStatus.vanilla_tu24 && Vanilla TU24 }
-
-
{installingInstance ? "WAITING..." : isRunning ? "RUNNING..." : "PLAY"}
- >
- ) : (
-
-
Game not installed
-
{ playSfx('click.wav'); setActiveTab("versions"); }} className="legacy-btn py-4 px-8 text-3xl w-full">Go to Versions
-
- )}
-
-
+
)}
{activeTab === "versions" && (
-
-
Instances
-
-
- {/* TU19 */}
-
-
Vanilla Nightly (TU19) Leaked 4J Studios build.
-
- {installedStatus.vanilla_tu19 ? (
- <>
- { playSfx('pop.wav'); invoke("open_instance_folder", { instanceId: "vanilla_tu19" }); }} className="legacy-btn px-4 py-2 text-xl">Folder
- { playSfx('click.wav'); setReinstallModal({ id: "vanilla_tu19", url: "https://huggingface.co/datasets/KayJann/emerald-legacy-assets/resolve/main/emerald_tu19_vanilla.zip" }); }} disabled={!!installingInstance} className="legacy-btn px-4 py-2 text-xl reinstall-btn">Reinstall
- >
- ) : (
- { playSfx('click.wav'); executeInstall("vanilla_tu19", "https://huggingface.co/datasets/KayJann/emerald-legacy-assets/resolve/main/emerald_tu19_vanilla.zip"); }} disabled={!!installingInstance} className="legacy-btn px-6 py-2 text-xl">INSTALL
- )}
-
-
-
- {/* TU24 */}
-
-
Vanilla TU24 Horses and Wither update.
-
- {installedStatus.vanilla_tu24 ? (
- <>
- { playSfx('pop.wav'); invoke("open_instance_folder", { instanceId: "vanilla_tu24" }); }} className="legacy-btn px-4 py-2 text-xl">Folder
- { playSfx('click.wav'); setReinstallModal({ id: "vanilla_tu24", url: "https://huggingface.co/datasets/KayJann/emerald-legacy-assets/resolve/main/emerald_tu24_vanilla.zip" }); }} disabled={!!installingInstance} className="legacy-btn px-4 py-2 text-xl reinstall-btn">Reinstall
- >
- ) : (
- { playSfx('click.wav'); executeInstall("vanilla_tu24", "https://huggingface.co/datasets/KayJann/emerald-legacy-assets/resolve/main/emerald_tu24_vanilla.zip"); }} disabled={!!installingInstance} className="legacy-btn px-6 py-2 text-xl">INSTALL
- )}
-
-
-
- {['TU75', 'TU9', 'Modded Pack'].map(v => (
-
-
Vanilla {v} Legacy version.
-
SOON
-
- ))}
-
-
+
)}
{activeTab === "settings" && (
-
-
Settings
-
-
-
In-game Username
-
- setUsername(e.target.value)} className="flex-1 bg-black border-4 border-slate-700 p-4 text-3xl outline-none focus:border-emerald-500" />
- { playSfx('wood click.wav'); invoke("save_config", { config: { username, linuxRunner: selectedRunner || undefined } }); }} className="legacy-btn px-8 text-2xl relative">Save
-
-
-
- {isLinux && (
-
-
Linux Runner
-
-
{ playSfx('click.wav'); setSelectedRunner(e.target.value); }}
- className="w-full legacy-select p-4 text-2xl outline-none focus:border-emerald-500"
- >
- Select a runner...
- {availableRunners.map(r => (
- {r.name} ({r.type})
- ))}
-
- {availableRunners.length === 0 && (
-
No Proton or Wine installations found. Please install Steam or Wine.
- )}
-
-
- )}
-
-
-
Audio Controls
-
-
{ setIsMuted(!isMuted); playSfx('pop.wav'); }} className="legacy-btn mt-4 py-2">{isMuted ? "UNMUTE ALL" : "MUTE ALL"}
-
-
-
-
About the project
-
- I'm KayJann , and I absolutely love this project! It's my very first one,
- and my goal is to create a central hub for the LCE community to bring us all together.
-
-
Social Links
-
- openUrl("https://discord.gg/nzbxB8Hxjh")} className="social-btn btn-discord" title="Discord">
- openUrl("https://github.com/KayJannOnGit")} className="social-btn btn-github" title="GitHub">
- openUrl("https://reddit.com/user/KayJann")} className="social-btn btn-reddit" title="Reddit">
-
-
-
+
)}
{reinstallModal && (
-
-
-
Warning
-
Reinstalling will delete all data. Continue?
-
- { playSfx('back.ogg'); setReinstallModal(null); }} className="legacy-btn px-8 py-4 text-3xl w-1/2">Cancel
- { playSfx('click.wav'); executeInstall(reinstallModal.id, reinstallModal.url); setReinstallModal(null); }} className="legacy-btn px-8 py-4 text-3xl w-1/2 confirm-red-btn">Confirm
-
-
-
+ setReinstallModal(null)}
+ onConfirm={(id, url) => {
+ executeInstall(id, url);
+ setReinstallModal(null);
+ }}
+ playSfx={playSfx}
+ />
)}
{mcNotif && (
-
-
✓
-
{mcNotif.t} {mcNotif.m}
-
+
)}
diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx
new file mode 100644
index 0000000..e384bd3
--- /dev/null
+++ b/src/components/Icons.tsx
@@ -0,0 +1,29 @@
+export const Icons = {
+ Discord: () => (
+
+
+
+ ),
+ Github: () => (
+
+
+
+ ),
+ Reddit: () => (
+
+
+
+ ),
+ Volume: ({ level }: { level: number }) => (
+
+
+ {level > 0 && }
+ {level > 0.5 && }
+
+ ),
+ Linux: () => (
+
+
+
+ ),
+};
diff --git a/src/components/common/Notification.tsx b/src/components/common/Notification.tsx
new file mode 100644
index 0000000..d47f06e
--- /dev/null
+++ b/src/components/common/Notification.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+
+interface NotificationProps {
+ title: string;
+ message: string;
+}
+
+export const Notification: React.FC
= ({ title, message }) => {
+ return (
+
+
+ ✓
+
+
+ {title}
+ {message}
+
+
+ );
+};
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx
new file mode 100644
index 0000000..d7b6ac0
--- /dev/null
+++ b/src/components/layout/Sidebar.tsx
@@ -0,0 +1,95 @@
+import React from 'react';
+import { openUrl } from "@tauri-apps/plugin-opener";
+import { TauriService } from '../../services/tauri';
+
+interface SidebarProps {
+ activeTab: string;
+ setActiveTab: (tab: string) => void;
+ playSfx: (name: string, multiplier?: number) => void;
+ updateAllStatus: () => void;
+ installingInstance: string | null;
+ downloadProgress: number;
+}
+
+export const Sidebar: React.FC = ({
+ activeTab,
+ setActiveTab,
+ playSfx,
+ updateAllStatus,
+ installingInstance,
+ downloadProgress,
+}) => {
+ return (
+
+
+
+
+
+ {
+ playSfx('click.wav');
+ setActiveTab("home");
+ updateAllStatus();
+ }}
+ className={`p-4 legacy-btn justify-start ${activeTab === "home" ? "active-tab" : ""}`}
+ >
+ HOME
+
+ {
+ playSfx('click.wav');
+ setActiveTab("versions");
+ updateAllStatus();
+ }}
+ className={`p-4 legacy-btn justify-start ${activeTab === "versions" ? "active-tab" : ""}`}
+ >
+ VERSIONS
+
+ {
+ playSfx('click.wav');
+ setActiveTab("settings");
+ }}
+ className={`p-4 legacy-btn justify-start ${activeTab === "settings" ? "active-tab" : ""}`}
+ >
+ SETTINGS
+
+
+
+ {installingInstance && (
+
+
+ Installing
+ {
+ playSfx('back.ogg');
+ TauriService.cancelDownload();
+ }}
+ className="text-red-500 hover:underline"
+ >
+ CANCEL
+
+
+
+
+
{downloadProgress}%
+
+
+ )}
+
+ {
+ playSfx('click.wav');
+ openUrl("https://github.com/KayJannOnGit");
+ }}
+ className={`${installingInstance ? "pt-6" : "mt-auto pt-6"} flex flex-col items-center border-t-4 border-black/30 cursor-pointer group`}
+ >
+ Developed by
+ KayJann
+
+
+ );
+};
diff --git a/src/components/modals/ReinstallModal.tsx b/src/components/modals/ReinstallModal.tsx
new file mode 100644
index 0000000..732b85c
--- /dev/null
+++ b/src/components/modals/ReinstallModal.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import { ReinstallModalData } from '../../types';
+
+interface ReinstallModalProps {
+ data: ReinstallModalData;
+ onCancel: () => void;
+ onConfirm: (id: string, url: string) => void;
+ playSfx: (name: string, multiplier?: number) => void;
+}
+
+export const ReinstallModal: React.FC = ({
+ data,
+ onCancel,
+ onConfirm,
+ playSfx,
+}) => {
+ return (
+
+
+
+ Warning
+
+
+ Reinstalling will delete all data. Continue?
+
+
+ {
+ playSfx('back.ogg');
+ onCancel();
+ }}
+ className="legacy-btn px-8 py-4 text-3xl w-1/2"
+ >
+ Cancel
+
+ {
+ playSfx('click.wav');
+ onConfirm(data.id, data.url);
+ }}
+ className="legacy-btn px-8 py-4 text-3xl w-1/2 confirm-red-btn"
+ >
+ Confirm
+
+
+
+
+ );
+};
diff --git a/src/components/views/FirstRunView.tsx b/src/components/views/FirstRunView.tsx
new file mode 100644
index 0000000..cdb9261
--- /dev/null
+++ b/src/components/views/FirstRunView.tsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import { TauriService } from '../../services/tauri';
+import { Runner } from '../../types';
+
+interface FirstRunViewProps {
+ username: string;
+ setUsername: (name: string) => void;
+ isLinux: boolean;
+ selectedRunner: string;
+ availableRunners: Runner[];
+ setIsFirstRun: (val: boolean) => void;
+ playRandomMusic: () => void;
+ playSfx: (name: string, multiplier?: number) => void;
+ ensureAudio: () => void;
+}
+
+export const FirstRunView: React.FC = ({
+ username,
+ setUsername,
+ isLinux,
+ selectedRunner,
+ availableRunners,
+ setIsFirstRun,
+ playRandomMusic,
+ playSfx,
+ ensureAudio,
+}) => {
+ return (
+ e.preventDefault()}
+ >
+
+
+
Welcome to Emerald Legacy!
+ setUsername(e.target.value)}
+ className="w-full bg-black border-4 border-emerald-900 p-4 text-3xl text-center mb-8 outline-none"
+ placeholder="Username..."
+ />
+ {
+ ensureAudio();
+ playSfx('click.wav');
+ TauriService.saveConfig({
+ username,
+ linuxRunner: isLinux ? selectedRunner : undefined,
+ });
+ setIsFirstRun(false);
+ setTimeout(playRandomMusic, 500);
+ }}
+ disabled={!username.trim() || (isLinux && availableRunners.length === 0)}
+ className="legacy-btn py-4 px-12 text-3xl w-full"
+ >
+ Start Setup
+
+
+
+ );
+};
diff --git a/src/components/views/HomeView.tsx b/src/components/views/HomeView.tsx
new file mode 100644
index 0000000..102a3f4
--- /dev/null
+++ b/src/components/views/HomeView.tsx
@@ -0,0 +1,79 @@
+import React from 'react';
+
+interface HomeViewProps {
+ username: string;
+ selectedInstance: string;
+ setSelectedInstance: (id: string) => void;
+ installedStatus: Record;
+ isRunning: boolean;
+ installingInstance: string | null;
+ fadeAndLaunch: () => void;
+ playSfx: (name: string, multiplier?: number) => void;
+ setActiveTab: (tab: string) => void;
+}
+
+export const HomeView: React.FC = ({
+ username,
+ selectedInstance,
+ setSelectedInstance,
+ installedStatus,
+ isRunning,
+ installingInstance,
+ fadeAndLaunch,
+ playSfx,
+ setActiveTab,
+}) => {
+ const hasInstalledInstance = installedStatus.vanilla_tu19 || installedStatus.vanilla_tu24;
+
+ return (
+
+
+
+
+ Welcome, {username}!
+
+
+
+ {hasInstalledInstance ? (
+ <>
+
{
+ playSfx('click.wav');
+ setSelectedInstance(e.target.value);
+ }}
+ className="w-full legacy-select p-3 text-2xl outline-none"
+ >
+ {installedStatus.vanilla_tu19 && (
+ Vanilla Nightly (TU19)
+ )}
+ {installedStatus.vanilla_tu24 && (
+ Vanilla TU24
+ )}
+
+
+ {installingInstance ? "WAITING..." : isRunning ? "RUNNING..." : "PLAY"}
+
+ >
+ ) : (
+
+
Game not installed
+
{
+ playSfx('click.wav');
+ setActiveTab("versions");
+ }}
+ className="legacy-btn py-4 px-8 text-3xl w-full"
+ >
+ Go to Versions
+
+
+ )}
+
+
+ );
+};
diff --git a/src/components/views/SettingsView.tsx b/src/components/views/SettingsView.tsx
new file mode 100644
index 0000000..4cc95d0
--- /dev/null
+++ b/src/components/views/SettingsView.tsx
@@ -0,0 +1,177 @@
+import React from 'react';
+import { Icons } from '../Icons';
+import { TauriService } from '../../services/tauri';
+import { Runner } from '../../types';
+import { openUrl } from "@tauri-apps/plugin-opener";
+
+interface SettingsViewProps {
+ username: string;
+ setUsername: (name: string) => void;
+ isLinux: boolean;
+ selectedRunner: string;
+ setSelectedRunner: (runner: string) => void;
+ availableRunners: Runner[];
+ musicVol: number;
+ setMusicVol: (vol: number) => void;
+ sfxVol: number;
+ setSfxVol: (vol: number) => void;
+ isMuted: boolean;
+ setIsMuted: (muted: boolean) => void;
+ playSfx: (name: string, multiplier?: number) => void;
+}
+
+export const SettingsView: React.FC = ({
+ username,
+ setUsername,
+ isLinux,
+ selectedRunner,
+ setSelectedRunner,
+ availableRunners,
+ musicVol,
+ setMusicVol,
+ sfxVol,
+ setSfxVol,
+ isMuted,
+ setIsMuted,
+ playSfx,
+}) => {
+ return (
+
+
Settings
+
+
+
In-game Username
+
+ setUsername(e.target.value)}
+ className="flex-1 bg-black border-4 border-slate-700 p-4 text-3xl outline-none focus:border-emerald-500"
+ />
+ {
+ playSfx('wood click.wav');
+ TauriService.saveConfig({
+ username,
+ linuxRunner: selectedRunner || undefined,
+ });
+ }}
+ className="legacy-btn px-8 text-2xl relative"
+ >
+ Save
+
+
+
+
+ {isLinux && (
+
+
+ Linux Runner
+
+
+
{
+ playSfx('click.wav');
+ setSelectedRunner(e.target.value);
+ }}
+ className="w-full legacy-select p-4 text-2xl outline-none focus:border-emerald-500"
+ >
+ Select a runner...
+ {availableRunners.map((r) => (
+
+ {r.name} ({r.type})
+
+ ))}
+
+ {availableRunners.length === 0 && (
+
+ No Proton or Wine installations found. Please install Steam or Wine.
+
+ )}
+
+
+ )}
+
+
+
+ Audio Controls
+
+
+
{
+ setIsMuted(!isMuted);
+ playSfx('pop.wav');
+ }}
+ className="legacy-btn mt-4 py-2"
+ >
+ {isMuted ? "UNMUTE ALL" : "MUTE ALL"}
+
+
+
+
+
+ About the project
+
+
+ I'm KayJann , and I absolutely love this project! It's my very first one,
+ and my goal is to create a central hub for the LCE community to bring us all together.
+
+
Social Links
+
+ openUrl("https://discord.gg/nzbxB8Hxjh")}
+ className="social-btn btn-discord"
+ title="Discord"
+ >
+
+
+ openUrl("https://github.com/KayJannOnGit")}
+ className="social-btn btn-github"
+ title="GitHub"
+ >
+
+
+ openUrl("https://reddit.com/user/KayJann")}
+ className="social-btn btn-reddit"
+ title="Reddit"
+ >
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/views/VersionsView.tsx b/src/components/views/VersionsView.tsx
new file mode 100644
index 0000000..c2eca9e
--- /dev/null
+++ b/src/components/views/VersionsView.tsx
@@ -0,0 +1,96 @@
+import React from 'react';
+import { TauriService } from '../../services/tauri';
+import { ReinstallModalData } from '../../types';
+
+interface VersionsViewProps {
+ installedStatus: Record;
+ installingInstance: string | null;
+ executeInstall: (id: string, url: string) => void;
+ setReinstallModal: (data: ReinstallModalData | null) => void;
+ playSfx: (name: string, multiplier?: number) => void;
+}
+
+export const VersionsView: React.FC = ({
+ installedStatus,
+ installingInstance,
+ executeInstall,
+ setReinstallModal,
+ playSfx,
+}) => {
+ const versions = [
+ {
+ id: "vanilla_tu19",
+ name: "Vanilla Nightly (TU19)",
+ desc: "Leaked 4J Studios build.",
+ url: "https://huggingface.co/datasets/KayJann/emerald-legacy-assets/resolve/main/emerald_tu19_vanilla.zip"
+ },
+ {
+ id: "vanilla_tu24",
+ name: "Vanilla TU24",
+ desc: "Horses and Wither update.",
+ url: "https://huggingface.co/datasets/KayJann/emerald-legacy-assets/resolve/main/emerald_tu24_vanilla.zip"
+ }
+ ];
+
+ return (
+
+
Instances
+
+ {versions.map(v => (
+
+
+
+ {installedStatus[v.id] ? (
+ <>
+ {
+ playSfx('pop.wav');
+ TauriService.openInstanceFolder(v.id);
+ }}
+ className="legacy-btn px-4 py-2 text-xl"
+ >
+ Folder
+
+ {
+ playSfx('click.wav');
+ setReinstallModal({ id: v.id, url: v.url });
+ }}
+ disabled={!!installingInstance}
+ className="legacy-btn px-4 py-2 text-xl reinstall-btn"
+ >
+ Reinstall
+
+ >
+ ) : (
+ {
+ playSfx('click.wav');
+ executeInstall(v.id, v.url);
+ }}
+ disabled={!!installingInstance}
+ className="legacy-btn px-6 py-2 text-xl"
+ >
+ INSTALL
+
+ )}
+
+
+ ))}
+
+ {['TU75', 'TU9', 'Modded Pack'].map(v => (
+
+
+
Vanilla {v}
+
Legacy version.
+
+
SOON
+
+ ))}
+
+
+ );
+};
diff --git a/src/hooks/useAudio.ts b/src/hooks/useAudio.ts
new file mode 100644
index 0000000..6839d6c
--- /dev/null
+++ b/src/hooks/useAudio.ts
@@ -0,0 +1,34 @@
+import { useRef, useEffect } from 'react';
+import { playSfx as playSfxService, ensureAudio } from '../services/audio';
+
+export const useAudio = (musicVol: number, sfxVol: number, isMuted: boolean) => {
+ const musicRef = useRef(null);
+ const lastTrack = useRef(0);
+
+ useEffect(() => {
+ if (musicRef.current) {
+ musicRef.current.volume = isMuted ? 0 : musicVol;
+ }
+ }, [musicVol, isMuted]);
+
+ const playRandomMusic = () => {
+ if (!musicRef.current) return;
+ let track = Math.floor(Math.random() * 5) + 1;
+ if (track === lastTrack.current) track = (track % 5) + 1;
+ lastTrack.current = track;
+ musicRef.current.src = `/music/music${track}.ogg`;
+ musicRef.current.volume = isMuted ? 0 : musicVol;
+ musicRef.current.play().catch(() => {});
+ };
+
+ const playSfx = (name: string, multiplier: number = 1.0) => {
+ playSfxService(name, sfxVol, isMuted, multiplier);
+ };
+
+ return {
+ musicRef,
+ playRandomMusic,
+ playSfx,
+ ensureAudio,
+ };
+};
diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts
new file mode 100644
index 0000000..6b0b81b
--- /dev/null
+++ b/src/hooks/useSettings.ts
@@ -0,0 +1,22 @@
+import { useState, useEffect } from 'react';
+
+export const useSettings = () => {
+ const [musicVol, setMusicVol] = useState(parseFloat(localStorage.getItem("musicVol") || "0.4"));
+ const [sfxVol, setSfxVol] = useState(parseFloat(localStorage.getItem("sfxVol") || "0.7"));
+ const [isMuted, setIsMuted] = useState(localStorage.getItem("isMuted") === "true");
+
+ useEffect(() => {
+ localStorage.setItem("musicVol", musicVol.toString());
+ localStorage.setItem("sfxVol", sfxVol.toString());
+ localStorage.setItem("isMuted", isMuted.toString());
+ }, [musicVol, sfxVol, isMuted]);
+
+ return {
+ musicVol,
+ setMusicVol,
+ sfxVol,
+ setSfxVol,
+ isMuted,
+ setIsMuted,
+ };
+};
diff --git a/src/App.css b/src/index.css
similarity index 97%
rename from src/App.css
rename to src/index.css
index 6a17817..85bda6d 100644
--- a/src/App.css
+++ b/src/index.css
@@ -1,12 +1,13 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* Fonts and Globals */
@font-face {
font-family: 'Minecraft';
src: url('/fonts/Mojangles.ttf') format('truetype');
}
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
* {
border-radius: 0 !important;
font-family: 'Minecraft', sans-serif !important;
@@ -28,7 +29,6 @@ main::before {
content: "";
position: absolute;
inset: 0;
- /*background: rgba(0, 0, 0, 0.7);*/
z-index: 0;
}
@@ -41,6 +41,15 @@ main::before {
scrollbar-width: none;
}
+.sidebar-progress {
+ background: #2a2a2a;
+ border-top: 4px solid #000;
+ padding: 20px;
+ position: relative;
+ box-shadow: inset 0 4px #444;
+}
+
+/* Legacy UI Components */
.legacy-btn {
@apply transition-all duration-75 flex items-center justify-center;
background: #bebebe;
@@ -83,14 +92,6 @@ main::before {
outline-offset: -8px;
}
-.sidebar-progress {
- background: #2a2a2a;
- border-top: 4px solid #000;
- padding: 20px;
- position: relative;
- box-shadow: inset 0 4px #444;
-}
-
.mc-progress-container {
background: #313131 !important;
border: 4px solid #000 !important;
@@ -133,6 +134,7 @@ main::before {
}
.mc-range {
+ appearance: none;
-webkit-appearance: none;
background: #000;
height: 12px;
@@ -167,12 +169,6 @@ main::before {
transform: scale(1.1);
}
-.social-btn svg {
- fill: white;
- width: 32px;
- height: 32px;
-}
-
.btn-discord {
background: #5865F2 !important;
box-shadow: inset 4px 4px #8ea1e1, inset -4px -4px #313338 !important;
@@ -203,8 +199,7 @@ main::before {
from {
transform: rotate(-20deg) scale(1);
}
-
to {
transform: rotate(-20deg) scale(1.05);
}
-}
\ No newline at end of file
+}
diff --git a/src/main.tsx b/src/main.tsx
index 1381b8e..02e3004 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,7 +1,7 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
-import './App.css';
+import './index.css';
const rootElement = document.getElementById("root");
diff --git a/src/services/audio.ts b/src/services/audio.ts
new file mode 100644
index 0000000..4d93c78
--- /dev/null
+++ b/src/services/audio.ts
@@ -0,0 +1,45 @@
+let audioCtx: AudioContext | null = null;
+let sfxGain: GainNode | null = null;
+const buffers: Record = {};
+
+export const ensureAudio = async () => {
+ let currentCtx = audioCtx;
+ if (!currentCtx) {
+ const AC = (window as any).AudioContext || (window as any).webkitAudioContext;
+ if (!AC) return null;
+ const newCtx = new AC() as AudioContext;
+ const newGain = newCtx.createGain();
+ newGain.connect(newCtx.destination);
+ audioCtx = newCtx;
+ sfxGain = newGain;
+ currentCtx = newCtx;
+
+ const sounds = ['click.wav', 'orb.ogg', 'levelup.ogg', 'back.ogg', 'pop.wav', 'wood click.wav'];
+ sounds.forEach((s) => {
+ fetch(`/sounds/${s}`)
+ .then((r) => r.arrayBuffer())
+ .then((b) => newCtx.decodeAudioData(b))
+ .then((buf) => {
+ if (buf) buffers[s] = buf;
+ });
+ });
+ }
+ if (currentCtx.state === 'suspended') await currentCtx.resume();
+ return { audioCtx, sfxGain, buffers };
+};
+
+export const playSfx = async (n: string, sfxVol: number, isMuted: boolean, multiplier: number = 1.0) => {
+ const audioData = await ensureAudio();
+ if (!audioData) return;
+ const { audioCtx, sfxGain, buffers } = audioData;
+
+ if (!audioCtx || !sfxGain || !buffers[n] || isMuted) return;
+
+ const s = audioCtx.createBufferSource();
+ s.buffer = buffers[n];
+ const g = audioCtx.createGain();
+ g.gain.value = sfxVol * multiplier;
+ s.connect(g);
+ g.connect(audioCtx.destination);
+ s.start(0);
+};
diff --git a/src/services/tauri.ts b/src/services/tauri.ts
new file mode 100644
index 0000000..f40c149
--- /dev/null
+++ b/src/services/tauri.ts
@@ -0,0 +1,13 @@
+import { invoke } from "@tauri-apps/api/core";
+import { AppConfig, Runner } from "../types";
+
+export const TauriService = {
+ loadConfig: () => invoke("load_config"),
+ saveConfig: (config: AppConfig) => invoke("save_config", { config }),
+ launchGame: (instanceId: string) => invoke("launch_game", { instanceId }),
+ downloadAndInstall: (url: string, instanceId: string) => invoke("download_and_install", { url, instanceId }),
+ checkGameInstalled: (instanceId: string) => invoke("check_game_installed", { instanceId }),
+ getAvailableRunners: () => invoke("get_available_runners"),
+ openInstanceFolder: (instanceId: string) => invoke("open_instance_folder", { instanceId }),
+ cancelDownload: () => invoke("cancel_download"),
+};
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..5500c77
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,27 @@
+export interface Runner {
+ id: string;
+ name: string;
+ path: string;
+ type: string;
+}
+
+export interface AppConfig {
+ username: string;
+ linuxRunner?: string;
+}
+
+export interface InstalledStatus {
+ vanilla_tu19: boolean;
+ vanilla_tu24: boolean;
+ [key: string]: boolean;
+}
+
+export interface ReinstallModalData {
+ id: string;
+ url: string;
+}
+
+export interface McNotification {
+ t: string;
+ m: string;
+}