Merge branch 'feature/big-cool-ui-redesign'
BIN
public/images/LCE Team.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
public/images/esrb_warning.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 273 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 63 KiB |
BIN
public/music/Moog City 2.opus
Normal file
BIN
public/sounds/in.ogg
Normal file
BIN
public/sounds/notification.ogg
Normal file
BIN
public/sounds/out.ogg
Normal file
|
|
@ -25,6 +25,7 @@ pub fn load_config_raw(app: AppHandle) -> AppConfig {
|
|||
animations_enabled: Some(true),
|
||||
vfx_enabled: Some(true),
|
||||
rpc_enabled: Some(true),
|
||||
start_fullscreen: Some(true),
|
||||
music_vol: Some(50),
|
||||
sfx_vol: Some(100),
|
||||
legacy_mode: Some(false),
|
||||
|
|
|
|||
|
|
@ -97,6 +97,14 @@ pub fn run() {
|
|||
relay::join_game,
|
||||
])
|
||||
.setup(|app| {
|
||||
let app_handle = app.handle().clone();
|
||||
let config = config::load_config_raw(app_handle.clone());
|
||||
if config.start_fullscreen.unwrap_or(true) {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.set_fullscreen(true);
|
||||
}
|
||||
}
|
||||
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 && !args[1].starts_with('-') {
|
||||
let instance_id = args[1].clone();
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ pub struct AppConfig {
|
|||
pub animations_enabled: Option<bool>,
|
||||
pub vfx_enabled: Option<bool>,
|
||||
pub rpc_enabled: Option<bool>,
|
||||
pub start_fullscreen: Option<bool>,
|
||||
pub music_vol: Option<u32>,
|
||||
pub sfx_vol: Option<u32>,
|
||||
pub legacy_mode: Option<bool>,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { useAudio } from "../../context/LauncherContext";
|
||||
|
||||
interface AchievementToastProps {
|
||||
message: string | null;
|
||||
|
|
@ -16,14 +17,24 @@ export function AchievementToast({
|
|||
title = "Error!",
|
||||
variant = "error",
|
||||
}: AchievementToastProps) {
|
||||
const { playSfx } = useAudio();
|
||||
|
||||
useEffect(() => {
|
||||
if (message) {
|
||||
if (variant === "update") {
|
||||
playSfx("notification.ogg");
|
||||
} else {
|
||||
playSfx("in.ogg");
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
onClose();
|
||||
}, 8000);
|
||||
return () => clearTimeout(timer);
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
playSfx("out.ogg");
|
||||
};
|
||||
}
|
||||
}, [message, onClose]);
|
||||
}, [message, onClose, variant, playSfx]);
|
||||
|
||||
const getIcon = () => {
|
||||
if (variant === "update") {
|
||||
|
|
|
|||
93
src/components/common/CinematicIntro.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
interface CinematicIntroProps {
|
||||
onComplete: () => void;
|
||||
startMusic: () => void;
|
||||
}
|
||||
|
||||
type Phase = "black" | "white-lceteam" | "white-esrb" | "out";
|
||||
|
||||
const TIMINGS = {
|
||||
black: 800,
|
||||
"white-lceteam": 3000,
|
||||
"white-esrb": 3000,
|
||||
out: 800,
|
||||
};
|
||||
|
||||
export function CinematicIntro({ onComplete, startMusic }: CinematicIntroProps) {
|
||||
const [phase, setPhase] = useState<Phase>("black");
|
||||
|
||||
useEffect(() => {
|
||||
startMusic();
|
||||
|
||||
const run = async () => {
|
||||
await new Promise((r) => setTimeout(r, TIMINGS.black));
|
||||
setPhase("white-lceteam");
|
||||
|
||||
await new Promise((r) => setTimeout(r, TIMINGS["white-lceteam"]));
|
||||
setPhase("white-esrb");
|
||||
|
||||
await new Promise((r) => setTimeout(r, TIMINGS["white-esrb"]));
|
||||
setPhase("out");
|
||||
|
||||
await new Promise((r) => setTimeout(r, TIMINGS.out));
|
||||
onComplete();
|
||||
};
|
||||
|
||||
run();
|
||||
}, [onComplete, startMusic]);
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 z-50 bg-black">
|
||||
<AnimatePresence initial={false}>
|
||||
{phase === "black" && (
|
||||
<motion.div
|
||||
key="black"
|
||||
className="absolute inset-0 bg-black"
|
||||
initial={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(phase === "white-lceteam" || phase === "white-esrb" || phase === "out") && (
|
||||
<motion.div
|
||||
key="white-bg"
|
||||
className="absolute inset-0 bg-white flex items-center justify-center"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: phase === "out" ? 0 : 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: phase === "out" ? 0.8 : 0.6 }}
|
||||
>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
{phase === "white-lceteam" && (
|
||||
<motion.img
|
||||
key="lceteam"
|
||||
src="/images/LCE Team.png"
|
||||
className="max-w-3xl object-contain"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(phase === "white-esrb" || phase === "out") && (
|
||||
<motion.img
|
||||
key="esrb"
|
||||
src="/images/esrb_warning.png"
|
||||
className="max-w-3xl object-contain"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -803,16 +803,7 @@ const LceLiveView = memo(function LceLiveView() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="flex-1 flex flex-col p-8 z-10 relative overflow-hidden rounded-b shadow-[0_0_30px_rgba(0,0,0,0.6)] border-4 border-[#222] border-t-0"
|
||||
style={{
|
||||
backgroundImage: "url('/images/background.png')",
|
||||
backgroundSize: "100% auto",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "top",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
<div className="flex-1 flex flex-col p-8 z-10 relative overflow-hidden mc-options-bg">
|
||||
{renderContent()}
|
||||
</div>
|
||||
<div className="flex justify-center pt-4 pb-2">
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ const SettingsView = memo(function SettingsView() {
|
|||
setPerfBoost,
|
||||
rpcEnabled,
|
||||
setRpcEnabled,
|
||||
startFullscreen,
|
||||
setStartFullscreen,
|
||||
legacyMode,
|
||||
setLegacyMode,
|
||||
mangohudEnabled,
|
||||
|
|
@ -100,6 +102,11 @@ const SettingsView = memo(function SettingsView() {
|
|||
setRpcEnabled(!rpcEnabled);
|
||||
};
|
||||
|
||||
const handleFullscreenToggle = () => {
|
||||
playPressSound();
|
||||
setStartFullscreen(!startFullscreen);
|
||||
};
|
||||
|
||||
const handleLegacyToggle = () => {
|
||||
playPressSound();
|
||||
setLegacyMode(!legacyMode);
|
||||
|
|
@ -396,6 +403,12 @@ const SettingsView = memo(function SettingsView() {
|
|||
},
|
||||
});
|
||||
} else if (currentSubMenu === "launcher") {
|
||||
items.push({
|
||||
id: "fullscreen",
|
||||
label: `Start in Fullscreen: ${startFullscreen ? "ON" : "OFF"}`,
|
||||
type: "button",
|
||||
onClick: handleFullscreenToggle,
|
||||
});
|
||||
items.push({
|
||||
id: "rpc",
|
||||
label: `Discord RPC: ${rpcEnabled ? "ON" : "OFF"}`,
|
||||
|
|
|
|||
|
|
@ -299,31 +299,29 @@ const VersionsView = memo(function VersionsView() {
|
|||
onMouseEnter={() => !isComingSoon && setFocusIndex(i)}
|
||||
>
|
||||
<div className="w-6 flex items-center justify-center flex-shrink-0">
|
||||
{isComingSoon ? (
|
||||
<img
|
||||
src="/images/wool_8.png"
|
||||
alt="Coming Soon"
|
||||
className="w-4 h-4 object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
) : isDownloading ? (
|
||||
{isDownloading ? (
|
||||
<span className="text-xs text-gray-400 font-bold">
|
||||
{Math.floor(downloadProgress || 0)}%
|
||||
</span>
|
||||
) : isInstalled ? (
|
||||
<img
|
||||
src="/images/wool_5.png"
|
||||
alt="Installed"
|
||||
className="w-4 h-4 object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
) : edition.logo ? (
|
||||
edition.logo.startsWith("http") ||
|
||||
edition.logo.startsWith("/images") ? (
|
||||
<img
|
||||
src={edition.logo}
|
||||
alt=""
|
||||
className={`w-6 h-6 object-contain ${isComingSoon ? "opacity-40 grayscale" : ""}`}
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
) : (
|
||||
<ScreenshotImage
|
||||
path={edition.logo}
|
||||
alt=""
|
||||
className={`w-6 h-6 object-contain ${isComingSoon ? "opacity-40 grayscale" : ""}`}
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<img
|
||||
src="/images/wool_14.png"
|
||||
alt="Not installed"
|
||||
className="w-4 h-4 object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
<div className="w-6 h-6 bg-gray-600/30 rounded-sm" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
@ -338,23 +336,6 @@ const VersionsView = memo(function VersionsView() {
|
|||
} ${isComingSoon ? "cursor-not-allowed" : ""}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{edition.logo &&
|
||||
(edition.logo.startsWith("http") ||
|
||||
edition.logo.startsWith("/images") ? (
|
||||
<img
|
||||
src={edition.logo}
|
||||
alt=""
|
||||
className="w-5 h-5 object-contain flex-shrink-0"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
) : (
|
||||
<ScreenshotImage
|
||||
path={edition.logo}
|
||||
alt=""
|
||||
className="w-5 h-5 object-contain flex-shrink-0"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
))}
|
||||
<span
|
||||
className={`text-xl tracking-wide truncate ${
|
||||
isSelected ? "text-white" : "text-black"
|
||||
|
|
|
|||
|
|
@ -36,14 +36,9 @@ const SERVERS_URL =
|
|||
const SERVERS_BASE =
|
||||
"https://raw.githubusercontent.com/bytebukkit/servers/refs/heads/main";
|
||||
const CATEGORY_TABS = ["Skin", "Texture", "World", "Mod", "DLC"] as const;
|
||||
const ALL_TABS = [
|
||||
...CATEGORY_TABS,
|
||||
"Versions",
|
||||
"Installed",
|
||||
"Server",
|
||||
"Plugins",
|
||||
"Search",
|
||||
] as const;
|
||||
const UTILITY_TABS = ["Versions", "Installed", "Search"] as const;
|
||||
const SERVER_TABS = ["Server", "Plugins"] as const;
|
||||
const ALL_TABS = [...CATEGORY_TABS, ...UTILITY_TABS, ...SERVER_TABS] as const;
|
||||
type TabType = (typeof ALL_TABS)[number];
|
||||
interface RegistryPackage {
|
||||
id: string;
|
||||
|
|
@ -506,8 +501,49 @@ const WorkshopView = memo(function WorkshopView() {
|
|||
Workshop
|
||||
</h2>
|
||||
|
||||
<div className="flex items-center justify-center gap-2 mb-6 w-full flex-wrap px-4">
|
||||
{ALL_TABS.map((tab) => {
|
||||
<div className="flex items-center justify-center gap-2 mb-4 w-full flex-wrap px-4">
|
||||
{CATEGORY_TABS.map((tab) => {
|
||||
const isActive = tab === activeTab;
|
||||
return (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => selectTab(tab)}
|
||||
className={`
|
||||
relative h-10 px-5 text-sm mc-text-shadow tracking-widest border-none outline-none cursor-pointer transition-all
|
||||
${isActive ? "text-[#FFFF55] scale-105" : "text-white hover:text-[#FFFF55] hover:scale-105"}
|
||||
`}
|
||||
style={{
|
||||
backgroundImage: isActive
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
{tab.toUpperCase()}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-2 mb-4 w-full flex-wrap px-4">
|
||||
{SERVER_TABS.map((tab) => {
|
||||
const isActive = tab === activeTab;
|
||||
return (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => selectTab(tab)}
|
||||
className={`
|
||||
relative h-8 px-4 text-xs mc-text-shadow tracking-widest border outline-none cursor-pointer transition-all
|
||||
${isActive ? "text-[#FFFF55] border-[#FFFF55] bg-black/40" : "text-[#A0A0A0] border-[#444] hover:text-white hover:border-[#888]"}
|
||||
`}
|
||||
>
|
||||
{tab.toUpperCase()}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<div className="w-px h-6 bg-[#444] mx-2" />
|
||||
{UTILITY_TABS.map((tab) => {
|
||||
const isActive = tab === activeTab;
|
||||
const updateCount =
|
||||
tab === "Installed"
|
||||
|
|
@ -518,16 +554,9 @@ const WorkshopView = memo(function WorkshopView() {
|
|||
key={tab}
|
||||
onClick={() => selectTab(tab)}
|
||||
className={`
|
||||
relative h-10 px-6 text-lg mc-text-shadow tracking-widest border-none outline-none cursor-pointer transition-all
|
||||
${isActive ? "text-[#FFFF55] scale-105" : "text-white hover:text-[#FFFF55] hover:scale-105"}
|
||||
relative h-8 px-4 text-xs mc-text-shadow tracking-widest border outline-none cursor-pointer transition-all
|
||||
${isActive ? "text-[#FFFF55] border-[#FFFF55] bg-black/40" : "text-[#A0A0A0] border-[#444] hover:text-white hover:border-[#888]"}
|
||||
`}
|
||||
style={{
|
||||
backgroundImage: isActive
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
{tab.toUpperCase()}
|
||||
{updateCount > 0 && (
|
||||
|
|
@ -540,16 +569,9 @@ const WorkshopView = memo(function WorkshopView() {
|
|||
})}
|
||||
</div>
|
||||
|
||||
<div className="w-[98%] flex-1 relative overflow-hidden">
|
||||
<AnimatePresence mode="wait">
|
||||
{showSearch ? (
|
||||
<motion.div
|
||||
key={isInstalledTab ? "installed-tab" : "search-tab"}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="absolute inset-0 flex flex-col pt-2"
|
||||
>
|
||||
<div className="w-[98%] flex-1 relative overflow-hidden mc-options-bg">
|
||||
{showSearch ? (
|
||||
<div className="absolute inset-0 flex flex-col pt-2">
|
||||
<div className="flex items-center gap-3 px-6 pb-4">
|
||||
<div
|
||||
className="flex items-center flex-1 h-12 px-4 border-2 border-[#444] bg-black/40 rounded shadow-inner"
|
||||
|
|
@ -745,40 +767,21 @@ const WorkshopView = memo(function WorkshopView() {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
) : loading ? (
|
||||
<motion.div
|
||||
key="loading"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
>
|
||||
</div>
|
||||
) : loading ? (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="text-3xl text-[#FFFF55] mc-text-shadow tracking-widest animate-pulse uppercase">
|
||||
Searching Archives...
|
||||
</span>
|
||||
</motion.div>
|
||||
) : error ? (
|
||||
<motion.div
|
||||
key="error"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="text-xl text-red-500 mc-text-shadow uppercase tracking-widest">
|
||||
{error}
|
||||
</span>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key={activeTab}
|
||||
ref={gridRef}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -20 }}
|
||||
className="absolute inset-0 overflow-y-auto p-6 scroll-smooth"
|
||||
>
|
||||
</div>
|
||||
) : (
|
||||
<div ref={gridRef} className="absolute inset-0 overflow-y-auto p-6 scroll-smooth">
|
||||
{filteredItems.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<span className="text-2xl text-[#E0E0E0] mc-text-shadow uppercase tracking-widest opacity-40">
|
||||
|
|
@ -805,9 +808,8 @@ const WorkshopView = memo(function WorkshopView() {
|
|||
))}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="w-full mt-6 mb-4 flex justify-center">
|
||||
|
|
@ -1104,26 +1106,8 @@ function PackageModal({
|
|||
: "REINSTALL";
|
||||
return (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-[200] flex items-center justify-center bg-black/85 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
>
|
||||
<motion.div
|
||||
initial={{ y: 20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: 20, opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="flex flex-col w-[640px] max-h-[85vh] overflow-hidden font-['Mojangles'] border-2 border-[#555] rounded-sm"
|
||||
style={{
|
||||
backgroundImage: "url('/images/frame_background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/85" onClick={onClose}>
|
||||
<div onClick={(e) => e.stopPropagation()} className="flex flex-col w-[640px] max-h-[85vh] overflow-hidden font-['Mojangles'] mc-options-bg">
|
||||
<div className="w-full h-[240px] flex-shrink-0 bg-black/60 overflow-hidden relative border-b border-[#444]">
|
||||
{imgError ? (
|
||||
<div className="absolute inset-0 flex items-center justify-center opacity-20">
|
||||
|
|
@ -1176,11 +1160,11 @@ function PackageModal({
|
|||
<div className="space-y-4">
|
||||
{pkg.extended_description &&
|
||||
pkg.extended_description.trim() !== "" && (
|
||||
<div className="space-y-2 bg-black/20 p-4 border border-[#333] rounded-sm">
|
||||
<span className="text-[10px] text-[#555] mc-text-shadow uppercase tracking-[0.2em] font-bold">
|
||||
Plugin Description
|
||||
<div className="space-y-2 p-4 border border-[#444] bg-black/40">
|
||||
<span className="text-[10px] text-[#AAAAAA] mc-text-shadow uppercase tracking-[0.2em] font-bold">
|
||||
Description
|
||||
</span>
|
||||
<div className="text-sm text-[#A0A0A0] mc-text-shadow leading-relaxed workshop-markdown">
|
||||
<div className="text-sm text-white mc-text-shadow leading-relaxed workshop-markdown">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{pkg.extended_description}
|
||||
</ReactMarkdown>
|
||||
|
|
@ -1189,9 +1173,9 @@ function PackageModal({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-8 pt-4 border-t border-[#333]">
|
||||
<div className="grid grid-cols-2 gap-8 pt-4 border-t border-[#444]">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<span className="text-[10px] text-[#666] mc-text-shadow uppercase tracking-[0.2em] font-bold">
|
||||
<span className="text-[10px] text-[#888] mc-text-shadow uppercase tracking-[0.2em] font-bold">
|
||||
Metadata
|
||||
</span>
|
||||
<div className="flex flex-col gap-1">
|
||||
|
|
@ -1435,8 +1419,8 @@ function PackageModal({
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{showInstall && (
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ export function LauncherProvider({ children }: { children: React.ReactNode }) {
|
|||
const audioRaw = useAudioController({
|
||||
musicVol: configRaw.musicVol,
|
||||
sfxVol: configRaw.sfxVol,
|
||||
showIntro,
|
||||
isGameRunning: gameRaw.isGameRunning,
|
||||
isWindowVisible,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1 +1,55 @@
|
|||
/* empty :P */
|
||||
@keyframes splashPulse {
|
||||
0% { transform: scale(0.95) rotate(-20deg); }
|
||||
100% { transform: scale(1.08) rotate(-20deg); }
|
||||
}
|
||||
|
||||
.mc-splash {
|
||||
animation: splashPulse 0.45s ease-in-out infinite alternate;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.mc-slider-custom {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mc-slider-custom::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 14px;
|
||||
height: 44px;
|
||||
background: url('/images/Slider_Handle.png') no-repeat center;
|
||||
background-size: 100% 100%;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
button, input {
|
||||
border-radius: 0 !important;
|
||||
border: none !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.mc-sq-btn {
|
||||
background: url('/images/Button_Square.png') no-repeat center;
|
||||
background-size: 100% 100%;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.mc-sq-btn:hover {
|
||||
background: url('/images/Button_Square_Highlighted.png') no-repeat center;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
156
src/data/splashes.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
export const SPLASHES = [
|
||||
"Legacy is back!",
|
||||
"Pixelated goodness!",
|
||||
"Console Edition vibe!",
|
||||
"100% Not Microsoft!",
|
||||
"Symmetry is key!",
|
||||
"Does anyone even read these?",
|
||||
"Task failed successfully.",
|
||||
"Hardware accelerated!",
|
||||
"It's a feature, not a bug.",
|
||||
"Look behind you.",
|
||||
"Works on my machine.",
|
||||
"Now gluten-free!",
|
||||
"Mom, get the camera!",
|
||||
"Batteries not included.",
|
||||
"May contain nuts.",
|
||||
"Press Alt+F4 for diamonds!",
|
||||
"Downloading more RAM...",
|
||||
"Reinventing the wheel!",
|
||||
"The cake is a lie.",
|
||||
"Powered by copious amounts of coffee.",
|
||||
"I'm running out of ideas.",
|
||||
"That's no moon...",
|
||||
"Now with 100% more nostalgia!",
|
||||
"Legacy is the new modern.",
|
||||
"No microtransactions!",
|
||||
"As seen on TV!",
|
||||
"Ironic, isn't it?",
|
||||
"Creeper? Aww man.",
|
||||
"Technoblade never dies!",
|
||||
"is smartcmd dead ?",
|
||||
"NO BUILT IN MS AUTH !",
|
||||
"Mr_Anilex wasn't here!",
|
||||
"Who's Jack ?",
|
||||
"This text is blue!",
|
||||
"Bonjour!",
|
||||
"Salam!",
|
||||
"Reverse engineering Wii U version",
|
||||
"Don't try Valorant!",
|
||||
"This could never be a sad place!",
|
||||
"Made without microslop",
|
||||
"Thank you C418",
|
||||
"Bread is pain",
|
||||
"From the star!",
|
||||
"Never gonna give you up!",
|
||||
"9+10=21",
|
||||
".party() was successful",
|
||||
"Not Kogama",
|
||||
"You can be proud of you!",
|
||||
"Let's drink Orange Joe",
|
||||
"Kirater is a great singer!",
|
||||
"Mirkette My beloved",
|
||||
"Started in Bordeaux",
|
||||
"Oui Oui Baguette",
|
||||
"Milk In The Microwave",
|
||||
"8-3: DISINTEGRATION LOOP",
|
||||
"Turn the light OFF",
|
||||
"Not written by Mr_Anilex",
|
||||
"The One Who's Running the Show!",
|
||||
"Playing Forever",
|
||||
"The World looks cubic!",
|
||||
"huh?",
|
||||
"Sybau",
|
||||
"Available on Toaster",
|
||||
"Try ArchLinux",
|
||||
"69% Accurate",
|
||||
"A molecule of meow",
|
||||
"http://localhost:3000",
|
||||
"uuhhhh...",
|
||||
"Oyasumi",
|
||||
"I don't want to set the world on fire",
|
||||
"Directed by Michael Bay",
|
||||
"We see you, Opal!",
|
||||
"A Cool Cat in Town",
|
||||
"Not BrainRotted!",
|
||||
"Farting is Natural -Leon",
|
||||
"93/100 on metacritic",
|
||||
"Not (anymore) on Steam",
|
||||
"Sudo apt install EmeraldLauncher",
|
||||
"Sudo pacman -S EmeraldLauncher",
|
||||
"Kay-Chan my beloved! <3",
|
||||
"Peak!",
|
||||
"OpenSource!",
|
||||
"made by human with bone and flesh",
|
||||
"Made with hate against microslop",
|
||||
"Steelorse :fire:",
|
||||
"It's Minecraft but i'm not sure",
|
||||
"Look at you!",
|
||||
"You're beautiful",
|
||||
"Mr_Anilex has a big ego",
|
||||
"Traduis-moi !",
|
||||
"May contains Mr_Anilex",
|
||||
"Neoapps didn't write this splash",
|
||||
"Where's Kinger?",
|
||||
"KayJann, Breakcore and code",
|
||||
"Hey Goku!",
|
||||
"Vegeta is a DZ mashallah",
|
||||
"Bogos Binted? Vorp",
|
||||
"YOU SHALL NOT PASS !",
|
||||
"Bready, Steady, GO !",
|
||||
"Not-so-Empty-house",
|
||||
"We'll Meet Again",
|
||||
"idk",
|
||||
"wdym",
|
||||
"Not making sense",
|
||||
"Dw!",
|
||||
"i forgor",
|
||||
"Remember to be patient!",
|
||||
"NOW'S YOUR CHANCE TO BE A.",
|
||||
"BIG SHOT",
|
||||
"A burning memory",
|
||||
"FREE MONEY!",
|
||||
"Can You Really Call This A Hotel. I didn't Reveive A Mint On My Pillow Or Anything",
|
||||
"Try Indie Game",
|
||||
"SHARK WITH LEGS!",
|
||||
"it's a seal!",
|
||||
"Shrimp.",
|
||||
"Limited edition!",
|
||||
"Fat free!",
|
||||
"GOTY!",
|
||||
"Water proof!",
|
||||
"LALALA-LAVA",
|
||||
"CHICHICHI-CHICKEN",
|
||||
"Tasty ah hell",
|
||||
"1% sugar!",
|
||||
"150% hyperbole!",
|
||||
"Hotter than the sun!",
|
||||
"Woo, reddit!",
|
||||
"piebot was here!",
|
||||
"Legacy in an evolved manner.",
|
||||
"neoapps is cool!",
|
||||
"neoapps has put a self insert into this program!",
|
||||
"Also try neoLegacy!",
|
||||
"$20 is $20",
|
||||
"no RenderDragon!",
|
||||
"Iggy Jiggy",
|
||||
"Arch, btw!",
|
||||
"Bedrock bad!",
|
||||
"Bedrock Linux not bad",
|
||||
"Also try Terraria!",
|
||||
"Also try LC Launcher!",
|
||||
"Exclusively abandonware!",
|
||||
"100% legal in all 3 states (of matter)",
|
||||
"Herobrine has been confirmed we are fighting him please help",
|
||||
"LGTM",
|
||||
"git revert",
|
||||
"We do not crypto mine on your computer.",
|
||||
"100% legal!",
|
||||
"Definitely not pirated",
|
||||
"Long Live LCE!",
|
||||
"For the community, by the community!",
|
||||
"Not affiliated with Microslop",
|
||||
"What is nether update",
|
||||
"To the good old days",
|
||||
"Also try : Minecraft Pocket Edition",
|
||||
];
|
||||
|
|
@ -8,6 +8,7 @@ export function useAppConfig() {
|
|||
const [vfxEnabled, setVfxEnabled] = useLocalStorage("lce-vfx", true);
|
||||
const [animationsEnabled, setAnimationsEnabled] = useLocalStorage("lce-animations", true);
|
||||
const [rpcEnabled, setRpcEnabled] = useLocalStorage("discord-rpc", true);
|
||||
const [startFullscreen, setStartFullscreen] = useLocalStorage("lce-fullscreen", true);
|
||||
const [musicVol, setMusicVol] = useLocalStorage("lce-music", 50);
|
||||
const [sfxVol, setSfxVol] = useLocalStorage("lce-sfx", 100);
|
||||
const [isDayTime, setIsDayTime] = useLocalStorage("lce-daytime", true);
|
||||
|
|
@ -36,6 +37,7 @@ export function useAppConfig() {
|
|||
if (config.vfxEnabled !== undefined) setVfxEnabled(config.vfxEnabled);
|
||||
if (config.animationsEnabled !== undefined) setAnimationsEnabled(config.animationsEnabled);
|
||||
if (config.rpcEnabled !== undefined) setRpcEnabled(config.rpcEnabled);
|
||||
if (config.startFullscreen !== undefined) setStartFullscreen(config.startFullscreen);
|
||||
if (config.musicVol !== undefined && config.musicVol !== null) setMusicVol(config.musicVol);
|
||||
if (config.sfxVol !== undefined && config.sfxVol !== null) setSfxVol(config.sfxVol);
|
||||
if (config.legacyMode !== undefined) setLegacyMode(config.legacyMode);
|
||||
|
|
@ -60,6 +62,7 @@ export function useAppConfig() {
|
|||
animationsEnabled,
|
||||
vfxEnabled,
|
||||
rpcEnabled,
|
||||
startFullscreen,
|
||||
musicVol,
|
||||
sfxVol,
|
||||
legacyMode,
|
||||
|
|
@ -69,7 +72,7 @@ export function useAppConfig() {
|
|||
launchEnvVars,
|
||||
}).catch(console.error);
|
||||
}
|
||||
}, [username, theme, linuxRunner, perfBoost, profile, customEditions, customizations, animationsEnabled, vfxEnabled, rpcEnabled, musicVol, sfxVol, legacyMode, mangohudEnabled, extraLaunchArgs, launchPrefix, launchEnvVars, isLoaded]);
|
||||
}, [username, theme, linuxRunner, perfBoost, profile, customEditions, customizations, animationsEnabled, vfxEnabled, rpcEnabled, startFullscreen, musicVol, sfxVol, legacyMode, mangohudEnabled, extraLaunchArgs, launchPrefix, launchEnvVars, isLoaded]);
|
||||
|
||||
return {
|
||||
username,
|
||||
|
|
@ -84,6 +87,8 @@ export function useAppConfig() {
|
|||
setAnimationsEnabled,
|
||||
rpcEnabled,
|
||||
setRpcEnabled,
|
||||
startFullscreen,
|
||||
setStartFullscreen,
|
||||
musicVol: musicVol ?? 50,
|
||||
setMusicVol,
|
||||
sfxVol: sfxVol ?? 100,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { SPLASHES } from "../data/splashes";
|
||||
|
||||
const TRACKS = [
|
||||
"music/Moog City 2.opus",
|
||||
"music/Blind Spots.ogg",
|
||||
"music/Key.ogg",
|
||||
"music/Living Mice.ogg",
|
||||
|
|
@ -13,167 +15,10 @@ const resolveAudioUrl = (path: string) => {
|
|||
return new URL(relativePath, window.location.origin).href;
|
||||
};
|
||||
|
||||
const SPLASHES = [
|
||||
"Legacy is back!",
|
||||
"Pixelated goodness!",
|
||||
"Console Edition vibe!",
|
||||
"100% Not Microsoft!",
|
||||
"Symmetry is key!",
|
||||
"Does anyone even read these?",
|
||||
"Task failed successfully.",
|
||||
"Hardware accelerated!",
|
||||
"It's a feature, not a bug.",
|
||||
"Look behind you.",
|
||||
"Works on my machine.",
|
||||
"Now gluten-free!",
|
||||
"Mom, get the camera!",
|
||||
"Batteries not included.",
|
||||
"May contain nuts.",
|
||||
"Press Alt+F4 for diamonds!",
|
||||
"Downloading more RAM...",
|
||||
"Reinventing the wheel!",
|
||||
"The cake is a lie.",
|
||||
"Powered by copious amounts of coffee.",
|
||||
"I'm running out of ideas.",
|
||||
"That's no moon...",
|
||||
"Now with 100% more nostalgia!",
|
||||
"Legacy is the new modern.",
|
||||
"No microtransactions!",
|
||||
"As seen on TV!",
|
||||
"Ironic, isn't it?",
|
||||
"Creeper? Aww man.",
|
||||
"Technoblade never dies!",
|
||||
"is smartcmd dead ?",
|
||||
"NO BUILT IN MS AUTH !",
|
||||
"Mr_Anilex wasn't here!",
|
||||
"Who's Jack ?",
|
||||
"This text is blue!",
|
||||
"Bonjour!",
|
||||
"Salam!",
|
||||
"Reverse engineering Wii U version",
|
||||
"Don't try Valorant!",
|
||||
"This could never be a sad place!",
|
||||
"Made without microslop",
|
||||
"Thank you C418",
|
||||
"Bread is pain",
|
||||
"From the star!",
|
||||
"Never gonna give you up!",
|
||||
"9+10=21",
|
||||
".party() was successful",
|
||||
"Not Kogama",
|
||||
"You can be proud of you!",
|
||||
"Let's drink Orange Joe",
|
||||
"Kirater is a great singer!",
|
||||
"Mirkette My beloved",
|
||||
"Started in Bordeaux",
|
||||
"Oui Oui Baguette",
|
||||
"Milk In The Microwave",
|
||||
"8-3: DISINTEGRATION LOOP",
|
||||
"Turn the light OFF",
|
||||
"Not written by Mr_Anilex",
|
||||
"The One Who's Running the Show!",
|
||||
"Playing Forever",
|
||||
"The World looks cubic!",
|
||||
"huh?",
|
||||
"Sybau",
|
||||
"Available on Toaster",
|
||||
"Try ArchLinux",
|
||||
"69% Accurate",
|
||||
"A molecule of meow",
|
||||
"http://localhost:3000",
|
||||
"uuhhhh...",
|
||||
"Oyasumi",
|
||||
"I don't want to set the world on fire",
|
||||
"Directed by Michael Bay",
|
||||
"We see you, Opal!",
|
||||
"A Cool Cat in Town",
|
||||
"Not BrainRotted!",
|
||||
"Farting is Natural -Leon",
|
||||
"93/100 on metacritic",
|
||||
"Not (anymore) on Steam",
|
||||
"Sudo apt install EmeraldLauncher",
|
||||
"Sudo pacman -S EmeraldLauncher",
|
||||
"Kay-Chan my beloved! <3",
|
||||
"Peak!",
|
||||
"OpenSource!",
|
||||
"made by human with bone and flesh",
|
||||
"Made with hate against microslop",
|
||||
"Steelorse :fire:",
|
||||
"It's Minecraft but i'm not sure",
|
||||
"Look at you!",
|
||||
"You're beautiful",
|
||||
"Mr_Anilex has a big ego",
|
||||
"Traduis-moi !",
|
||||
"May contains Mr_Anilex",
|
||||
"Neoapps didn't write this splash",
|
||||
"Where's Kinger?",
|
||||
"KayJann, Breakcore and code",
|
||||
"Hey Goku!",
|
||||
"Vegeta is a DZ mashallah",
|
||||
"Bogos Binted? Vorp",
|
||||
"YOU SHALL NOT PASS !",
|
||||
"Bready, Steady, GO !",
|
||||
"Not-so-Empty-house",
|
||||
"We'll Meet Again",
|
||||
"idk",
|
||||
"wdym",
|
||||
"Not making sense",
|
||||
"Dw!",
|
||||
"i forgor",
|
||||
"Remember to be patient!",
|
||||
"NOW'S YOUR CHANCE TO BE A.",
|
||||
"BIG SHOT",
|
||||
"A burning memory",
|
||||
"FREE MONEY!",
|
||||
"Can You Really Call This A Hotel. I didn't Reveive A Mint On My Pillow Or Anything",
|
||||
"Try Indie Game",
|
||||
"SHARK WITH LEGS!",
|
||||
"it's a seal!",
|
||||
"Shrimp.",
|
||||
"Limited edition!",
|
||||
"Fat free!",
|
||||
"GOTY!",
|
||||
"Water proof!",
|
||||
"LALALA-LAVA",
|
||||
"CHICHICHI-CHICKEN",
|
||||
"Tasty ah hell",
|
||||
"1% sugar!",
|
||||
"150% hyperbole!",
|
||||
"Hotter than the sun!",
|
||||
"Woo, reddit!",
|
||||
"piebot was here!",
|
||||
"Legacy in an evolved manner.",
|
||||
"neoapps is cool!",
|
||||
"neoapps has put a self insert into this program!",
|
||||
"Also try neoLegacy!",
|
||||
"$20 is $20",
|
||||
"no RenderDragon!",
|
||||
"Iggy Jiggy",
|
||||
"Arch, btw!",
|
||||
"Bedrock bad!",
|
||||
"Bedrock Linux not bad",
|
||||
"Also try Terraria!",
|
||||
"Also try LC Launcher!",
|
||||
"Exclusively abandonware!",
|
||||
"100% legal in all 3 states (of matter)",
|
||||
"Herobrine has been confirmed we are fighting him please help",
|
||||
"LGTM",
|
||||
"git revert",
|
||||
"We do not crypto mine on your computer.",
|
||||
"100% legal!",
|
||||
"Definitely not pirated",
|
||||
"Long Live LCE!",
|
||||
"For the community, by the community!",
|
||||
"Not affiliated with Microslop",
|
||||
"What is nether update",
|
||||
"To the good old days",
|
||||
"Also try : Minecraft Pocket Edition",
|
||||
];
|
||||
|
||||
interface AudioControllerProps {
|
||||
musicVol: number;
|
||||
sfxVol: number;
|
||||
showIntro: boolean;
|
||||
isGameRunning: boolean;
|
||||
isWindowVisible: boolean;
|
||||
}
|
||||
|
|
@ -181,7 +26,6 @@ interface AudioControllerProps {
|
|||
export function useAudioController({
|
||||
musicVol,
|
||||
sfxVol,
|
||||
showIntro,
|
||||
isGameRunning,
|
||||
isWindowVisible,
|
||||
}: AudioControllerProps) {
|
||||
|
|
@ -404,10 +248,18 @@ export function useAudioController({
|
|||
setSplashIndex(newIndex);
|
||||
}, [playSplashSound, splashIndex]);
|
||||
|
||||
const [isMusicStarted, setIsMusicStarted] = useState(false);
|
||||
|
||||
const startMusic = useCallback(() => {
|
||||
setIsMusicStarted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (showIntro) return;
|
||||
if (!isMusicStarted) return;
|
||||
|
||||
const loadAndPlay = async () => {
|
||||
await ensureAudioContextReady();
|
||||
|
||||
let buffer = trackBuffersRef.current.get(currentTrack);
|
||||
if (!buffer) {
|
||||
buffer = await loadAudioBuffer(resolveAudioUrl(TRACKS[currentTrack]));
|
||||
|
|
@ -416,35 +268,16 @@ export function useAudioController({
|
|||
}
|
||||
}
|
||||
if (buffer) {
|
||||
await playMusicBuffer(buffer);
|
||||
}
|
||||
};
|
||||
|
||||
loadAndPlay();
|
||||
|
||||
return () => {
|
||||
stopMusic();
|
||||
};
|
||||
}, [showIntro, currentTrack, loadAudioBuffer, playMusicBuffer, stopMusic]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!audioContextRef.current || showIntro) return;
|
||||
|
||||
const loadAndPlay = async () => {
|
||||
let buffer = trackBuffersRef.current.get(currentTrack);
|
||||
if (!buffer) {
|
||||
buffer = await loadAudioBuffer(resolveAudioUrl(TRACKS[currentTrack]));
|
||||
if (buffer) {
|
||||
trackBuffersRef.current.set(currentTrack, buffer);
|
||||
if (currentTrack === 0) {
|
||||
await playMusicBuffer(buffer);
|
||||
} else {
|
||||
await fadeInMusic(buffer, targetVolumeRef.current, 500);
|
||||
}
|
||||
}
|
||||
if (buffer) {
|
||||
await fadeInMusic(buffer, musicVol / 100, 500);
|
||||
}
|
||||
};
|
||||
|
||||
loadAndPlay();
|
||||
}, [currentTrack, showIntro, musicVol, loadAudioBuffer, fadeInMusic]);
|
||||
}, [isMusicStarted, currentTrack, loadAudioBuffer, playMusicBuffer, fadeInMusic, ensureAudioContextReady]);
|
||||
|
||||
useEffect(() => {
|
||||
const shouldPause = isGameRunning || !isWindowVisible;
|
||||
|
|
@ -517,5 +350,6 @@ export function useAudioController({
|
|||
playSfx,
|
||||
tracks: TRACKS,
|
||||
splashes: SPLASHES,
|
||||
startMusic,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useMemo, useCallback } from "react";
|
||||
import { motion, AnimatePresence, MotionConfig } from "framer-motion";
|
||||
import "../css/App.css";
|
||||
import HomeView from "../components/views/HomeView";
|
||||
import SettingsView from "../components/views/SettingsView";
|
||||
import VersionsView from "../components/views/VersionsView";
|
||||
|
|
@ -20,6 +21,7 @@ import SkinViewer from "../components/common/SkinViewer";
|
|||
import TeamModal from "../components/modals/TeamModal";
|
||||
import PanoramaBackground from "../components/common/PanoramaBackground";
|
||||
import { ClickParticles } from "../components/common/ClickParticles";
|
||||
import { CinematicIntro } from "../components/common/CinematicIntro";
|
||||
import { DownloadOverlay } from "../components/layout/DownloadOverlay";
|
||||
import { AchievementToast } from "../components/common/AchievementToast";
|
||||
import {
|
||||
|
|
@ -34,11 +36,10 @@ import { useLceLiveNotifications } from "../hooks/useLceLiveNotifications";
|
|||
import type { Edition } from "../types/edition";
|
||||
import pkg from "../../package.json";
|
||||
export default function App() {
|
||||
const ui = useUI();
|
||||
const {
|
||||
showIntro,
|
||||
setShowIntro,
|
||||
logoAnimDone,
|
||||
setLogoAnimDone,
|
||||
activeView,
|
||||
setActiveView,
|
||||
isUiHidden,
|
||||
|
|
@ -50,44 +51,44 @@ export default function App() {
|
|||
updateMessage,
|
||||
updateUrl,
|
||||
clearUpdateMessage,
|
||||
} = useUI();
|
||||
connected,
|
||||
} = ui;
|
||||
const config = useConfig();
|
||||
const audio = useAudio();
|
||||
const game = useGame();
|
||||
const { skinUrl, setSkinUrl, capeUrl } = useSkin();
|
||||
const skin = useSkin();
|
||||
const { skinUrl, setSkinUrl, capeUrl } = skin;
|
||||
const notifications = useLceLiveNotifications();
|
||||
const {
|
||||
friendRequestMessage,
|
||||
gameInviteMessage,
|
||||
clearFriendRequestMessage,
|
||||
clearGameInviteMessage,
|
||||
} = useLceLiveNotifications();
|
||||
const [showSetup, setShowSetup] = useState(true);
|
||||
const [displayIsDay, setDisplayIsDay] = useState(config.isDayTime);
|
||||
useEffect(() => {
|
||||
setDisplayIsDay(config.isDayTime);
|
||||
}, [config.isDayTime]);
|
||||
} = notifications;
|
||||
const [showSetup, setShowSetup] = useState(false);
|
||||
const [isSetupChecked, setIsSetupChecked] = useState(false);
|
||||
const displayIsDay = config.isDayTime;
|
||||
|
||||
const selectedEdition = game.editions.find(
|
||||
(e: Edition) => e.instanceId === config.profile,
|
||||
);
|
||||
const selectedVersionName = selectedEdition?.name || "";
|
||||
const hasAnyInstall = game.installs.length > 0;
|
||||
const titleImage = hasAnyInstall
|
||||
? selectedEdition?.titleImage || "/images/MenuTitle.png"
|
||||
: "/images/MenuTitle.png";
|
||||
const clearError = useCallback(() => game.setError(null), [game]);
|
||||
const clearGameUpdate = useCallback(() => game.setGameUpdateMessage(null), [game]);
|
||||
const clearSteamSuccess = useCallback(() => game.setSteamSuccessMessage(null), [game]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (config.isLoaded) {
|
||||
const setupCompleted =
|
||||
localStorage.getItem("lce-setup-completed") === "true";
|
||||
const setupCompleted = localStorage.getItem("lce-setup-completed") === "true";
|
||||
setShowSetup(!setupCompleted);
|
||||
setIsSetupChecked(true);
|
||||
}
|
||||
}, [config.isLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => setShowIntro(false), 2400);
|
||||
setTimeout(() => setLogoAnimDone(true), 3400);
|
||||
}, [showSetup]);
|
||||
const selectedEdition = useMemo(() =>
|
||||
game.editions.find((e: Edition) => e.instanceId === config.profile),
|
||||
[game.editions, config.profile]
|
||||
);
|
||||
const selectedVersionName = selectedEdition?.name ?? "";
|
||||
const hasAnyInstall = game.installs.length > 0;
|
||||
const titleImage = selectedEdition?.titleImage ?? "/images/MenuTitle.png";
|
||||
|
||||
useEffect(() => {
|
||||
const handleContextMenu = (e: MouseEvent) => e.preventDefault();
|
||||
|
|
@ -95,35 +96,56 @@ export default function App() {
|
|||
return () => document.removeEventListener("contextmenu", handleContextMenu);
|
||||
}, []);
|
||||
|
||||
const uiFade = {
|
||||
const animDuration = config.animationsEnabled ? undefined : { duration: 0 };
|
||||
const uiFade = useMemo(() => ({
|
||||
initial: { opacity: 0 },
|
||||
animate: { opacity: 1 },
|
||||
exit: { opacity: 0 },
|
||||
transition: { duration: config.animationsEnabled ? 0.5 : 0 },
|
||||
};
|
||||
transition: animDuration ?? { duration: 0.5 },
|
||||
}), [animDuration]);
|
||||
|
||||
const backgroundFade = {
|
||||
const backgroundFade = useMemo(() => ({
|
||||
initial: { opacity: 0 },
|
||||
animate: { opacity: 1 },
|
||||
exit: { opacity: 0 },
|
||||
transition: { duration: config.animationsEnabled ? 0.8 : 0 },
|
||||
};
|
||||
transition: animDuration ?? { duration: 0.8 },
|
||||
}), [animDuration]);
|
||||
|
||||
if (!config.isLoaded || !isSetupChecked) {
|
||||
return <div className="w-screen h-screen bg-black" />;
|
||||
}
|
||||
|
||||
if (showSetup) {
|
||||
return (
|
||||
<div className={`w-screen h-screen overflow-hidden select-none flex flex-col relative bg-black text-white font-['Mojangles'] outline-none focus:outline-none ${!config.animationsEnabled ? "no-animations" : ""}`}>
|
||||
<SetupView
|
||||
onComplete={() => {
|
||||
setShowSetup(false);
|
||||
setShowIntro(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (showIntro) {
|
||||
return (
|
||||
<div className={`w-screen h-screen overflow-hidden select-none flex flex-col relative bg-black text-white font-['Mojangles'] outline-none focus:outline-none ${!config.animationsEnabled ? "no-animations" : ""}`}>
|
||||
<CinematicIntro
|
||||
onComplete={() => {
|
||||
setShowIntro(false);
|
||||
}}
|
||||
startMusic={audio.startMusic}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MotionConfig transition={config.animationsEnabled ? {} : { duration: 0 }}>
|
||||
<div
|
||||
className={`w-screen h-screen overflow-hidden select-none flex flex-col relative bg-black text-white font-['Mojangles'] outline-none focus:outline-none ${!config.animationsEnabled ? "no-animations" : ""}`}
|
||||
>
|
||||
<style>{`
|
||||
@keyframes splashPulse { 0% { transform: scale(0.95) rotate(-20deg); } 100% { transform: scale(1.08) rotate(-20deg); } }
|
||||
.mc-splash { animation: splashPulse 0.45s ease-in-out infinite alternate; transform-origin: center; }
|
||||
.mc-slider-custom { -webkit-appearance: none; appearance: none; background: transparent; height: 100%; outline: none; border: none; margin: 0; padding: 0; }
|
||||
.mc-slider-custom::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 14px; height: 44px; background: url('/images/Slider_Handle.png') no-repeat center; background-size: 100% 100%; cursor: pointer; position: relative; z-index: 30; }
|
||||
*:focus { outline: none !important; box-shadow: none !important; }
|
||||
button, input { border-radius: 0 !important; border: none !important; outline: none !important; box-shadow: none !important; }
|
||||
.mc-sq-btn { background: url('/images/Button_Square.png') no-repeat center; background-size: 100% 100%; image-rendering: pixelated; }
|
||||
.mc-sq-btn:hover { background: url('/images/Button_Square_Highlighted.png') no-repeat center; background-size: 100% 100%; }
|
||||
`}</style>
|
||||
|
||||
<div className="absolute inset-0">
|
||||
<AnimatePresence>
|
||||
|
|
@ -139,6 +161,7 @@ export default function App() {
|
|||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{config.vfxEnabled && <ClickParticles />}
|
||||
|
||||
<AnimatePresence>
|
||||
|
|
@ -152,17 +175,15 @@ export default function App() {
|
|||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<AnimatePresence>
|
||||
<DownloadOverlay
|
||||
downloadProgress={game.downloadProgress}
|
||||
downloadingId={game.downloadingId}
|
||||
editions={game.editions}
|
||||
/>
|
||||
</AnimatePresence>
|
||||
<DownloadOverlay
|
||||
downloadProgress={game.downloadProgress}
|
||||
downloadingId={game.downloadingId}
|
||||
editions={game.editions}
|
||||
/>
|
||||
|
||||
<AchievementToast
|
||||
message={game.error}
|
||||
onClose={() => game.setError(null)}
|
||||
onClose={clearError}
|
||||
/>
|
||||
|
||||
<AchievementToast
|
||||
|
|
@ -180,9 +201,9 @@ export default function App() {
|
|||
|
||||
<AchievementToast
|
||||
message={game.gameUpdateMessage}
|
||||
onClose={() => game.setGameUpdateMessage(null)}
|
||||
onClose={clearGameUpdate}
|
||||
onClick={() => {
|
||||
game.setGameUpdateMessage(null);
|
||||
clearGameUpdate();
|
||||
setActiveView("versions");
|
||||
}}
|
||||
title="Game Update Available!"
|
||||
|
|
@ -191,280 +212,225 @@ export default function App() {
|
|||
|
||||
<AchievementToast
|
||||
message={game.steamSuccessMessage}
|
||||
onClose={() => game.setSteamSuccessMessage(null)}
|
||||
onClose={clearSteamSuccess}
|
||||
title="Steam Integration"
|
||||
variant="steam"
|
||||
/>
|
||||
|
||||
<AnimatePresence>
|
||||
{showSetup ? (
|
||||
<SetupView
|
||||
key="setup"
|
||||
onComplete={() => {
|
||||
setShowSetup(false);
|
||||
setShowIntro(true);
|
||||
}}
|
||||
/>
|
||||
) : showIntro ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="flex flex-col h-full z-10 w-full relative"
|
||||
>
|
||||
{!config.legacyMode && (
|
||||
<motion.div
|
||||
key="intro"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="flex flex-1 items-center justify-center z-10 pointer-events-none"
|
||||
{...uiFade}
|
||||
className="absolute top-4 left-8 z-50"
|
||||
>
|
||||
<motion.img
|
||||
layoutId="mainLogo"
|
||||
initial={{ y: 20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
src={titleImage}
|
||||
className="w-3/4 max-w-3xl"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="dashboard"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="flex flex-col h-full z-10 w-full relative"
|
||||
>
|
||||
<AnimatePresence>
|
||||
{logoAnimDone && (
|
||||
<>
|
||||
{!config.legacyMode && (
|
||||
<motion.div
|
||||
key="hideBtn"
|
||||
{...uiFade}
|
||||
className="absolute top-4 left-8 z-50"
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
audio.playPressSound();
|
||||
setIsUiHidden(!isUiHidden);
|
||||
}}
|
||||
className="hover:scale-110 active:scale-95 transition-transform outline-none bg-transparent border-none"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
isUiHidden
|
||||
? "/images/Unhide_UI_Button.png"
|
||||
: "/images/Hide_UI_Button.png"
|
||||
}
|
||||
className="w-10 h-10 cursor-pointer object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{!config.legacyMode && (
|
||||
<motion.div
|
||||
key="dayToggle"
|
||||
{...uiFade}
|
||||
className="absolute bottom-6 right-8 z-50 flex items-center gap-3"
|
||||
>
|
||||
<span className="text-[#E0E0E0] text-[10px] mc-text-shadow tracking-widest uppercase opacity-70 mt-1">
|
||||
{displayIsDay ? "Day" : "Night"}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
audio.playPressSound();
|
||||
config.setIsDayTime(!config.isDayTime);
|
||||
}}
|
||||
className="hover:scale-110 active:scale-95 transition-transform outline-none bg-transparent border-none"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
displayIsDay
|
||||
? "/images/Day_Toggle.png"
|
||||
: "/images/Night_Toggle.png"
|
||||
}
|
||||
alt="Toggle Time"
|
||||
className="w-12 h-12 cursor-pointer block object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{isUiHidden &&
|
||||
!displayIsDay &&
|
||||
activeView == "devtools" && (
|
||||
<motion.div
|
||||
key="secret-swf-btn"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.8 }}
|
||||
className="absolute inset-0 z-[100] flex items-center justify-center pointer-events-none"
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
audio.playPressSound();
|
||||
setIsUiHidden(false);
|
||||
setActiveView("swf-editor");
|
||||
}}
|
||||
className="pointer-events-auto hover:scale-110 active:scale-95 transition-transform outline-none bg-transparent border-none flex flex-col items-center gap-2 group"
|
||||
>
|
||||
<img
|
||||
src="/images/tools/pck.png"
|
||||
className="w-16 h-16 cursor-pointer object-contain opacity-50 group-hover:opacity-100 drop-shadow-[0_4px_4px_rgba(0,0,0,1)] grayscale group-hover:grayscale-0"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src =
|
||||
"/images/Button_Background.png";
|
||||
}}
|
||||
/>
|
||||
<span className="text-[#FFFF55] text-sm mc-text-shadow opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
SWF Editor
|
||||
</span>
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<div className="shrink-0 flex justify-center py-4 relative w-full pt-4">
|
||||
<div className="relative w-full max-w-135 flex justify-center">
|
||||
<motion.img
|
||||
layoutId="mainLogo"
|
||||
src={titleImage}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 300,
|
||||
damping: 25,
|
||||
}}
|
||||
className="w-full drop-shadow-[0_8px_6px_rgba(0,0,0,0.8)] pointer-events-none"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
{logoAnimDone && (
|
||||
<>
|
||||
<motion.div
|
||||
key="splash"
|
||||
{...uiFade}
|
||||
className="absolute bottom-[20%] right-[5%] w-0 h-0 flex items-center justify-center"
|
||||
>
|
||||
<div
|
||||
onClick={audio.cycleSplash}
|
||||
className="mc-splash text-[#FFFF55] text-[28px] z-100 cursor-pointer whitespace-nowrap"
|
||||
style={{ textShadow: "2px 2px 0px #3F3F00" }}
|
||||
>
|
||||
{audio.splashIndex === -1
|
||||
? `Welcome ${config.username}!`
|
||||
: audio.splashes[audio.splashIndex]}
|
||||
</div>
|
||||
</motion.div>
|
||||
{activeView === "main" &&
|
||||
hasAnyInstall &&
|
||||
titleImage === "/images/MenuTitle.png" && (
|
||||
<motion.div
|
||||
key="tu-subtitle"
|
||||
{...uiFade}
|
||||
className="absolute -bottom-6 text-[#A0A0A0] text-sm mc-text-shadow tracking-widest uppercase opacity-80 font-['Mojangles']"
|
||||
>
|
||||
{selectedVersionName}
|
||||
</motion.div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main className="flex-1 w-full relative">
|
||||
<div
|
||||
className={`w-full h-full flex flex-col items-center justify-center ${!logoAnimDone || isUiHidden ? "opacity-0 pointer-events-none" : "opacity-100"}`}
|
||||
>
|
||||
<AnimatePresence mode="wait">
|
||||
{activeView === "main" && (
|
||||
<SkinViewer
|
||||
key="skin-viewer"
|
||||
username={config.username}
|
||||
setUsername={config.setUsername}
|
||||
playPressSound={audio.playPressSound}
|
||||
skinUrl={skinUrl}
|
||||
capeUrl={config.legacyMode ? null : capeUrl}
|
||||
setSkinUrl={setSkinUrl}
|
||||
setActiveView={setActiveView}
|
||||
isFocusedSection={focusSection === "skin"}
|
||||
onNavigateRight={onNavigateToMenu}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<div className="w-full h-full max-w-4xl relative flex justify-center items-center overflow-hidden">
|
||||
<AnimatePresence mode="wait">
|
||||
{activeView === "main" && <HomeView key="main-view" />}
|
||||
{activeView === "settings" && (
|
||||
<SettingsView key="settings-view" />
|
||||
)}
|
||||
{activeView === "versions" && (
|
||||
<VersionsView key="versions-view" />
|
||||
)}
|
||||
{activeView === "workshop" && (
|
||||
<WorkshopView key="workshop-view" />
|
||||
)}
|
||||
{activeView === "devtools" && (
|
||||
<DevtoolsView key="devtools-view" />
|
||||
)}
|
||||
{activeView === "pck-editor" && (
|
||||
<PckEditorView key="pck-editor-view" />
|
||||
)}
|
||||
{activeView === "arc-editor" && (
|
||||
<ArcEditorView key="arc-editor-view" />
|
||||
)}
|
||||
{activeView === "loc-editor" && (
|
||||
<LocEditorView key="loc-editor-view" />
|
||||
)}
|
||||
{activeView === "grf-editor" && (
|
||||
<GrfEditorView key="grf-editor-view" />
|
||||
)}
|
||||
{activeView === "col-editor" && (
|
||||
<ColEditorView key="col-editor-view" />
|
||||
)}
|
||||
{activeView === "options-editor" && (
|
||||
<OptionsEditorView key="options-editor-view" />
|
||||
)}
|
||||
{activeView === "swf-editor" && (
|
||||
<SwfView key="swf-editor-view" />
|
||||
)}
|
||||
{activeView === "lcelive" && (
|
||||
<LceLiveView key="lcelive-view" />
|
||||
)}
|
||||
{activeView === "skins" && <SkinsView key="skins-view" />}
|
||||
{activeView === "screenshots" && (
|
||||
<ScreenshotsView key="screenshots-view" />
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<AnimatePresence>
|
||||
{logoAnimDone && (
|
||||
<motion.footer
|
||||
key="footer"
|
||||
{...uiFade}
|
||||
className="shrink-0 p-4 flex justify-between items-end text-[10px] text-[#A0A0A0] mc-text-shadow bg-gradient-to-t from-black/80 to-transparent uppercase tracking-widest opacity-60 font-['Mojangles']"
|
||||
style={{ fontWeight: "normal" }}
|
||||
>
|
||||
<div className="flex-1 text-left whitespace-nowrap">
|
||||
Version: {pkg.version} ({__BUILD_DATE__})
|
||||
</div>
|
||||
<div className="flex-[2] text-center whitespace-nowrap">
|
||||
Not affiliated with Mojang AB or Microsoft. "Minecraft" is
|
||||
a trademark of Mojang Synergies AB.
|
||||
</div>
|
||||
<div className="flex-1 text-right whitespace-nowrap">
|
||||
{useUI().connected && "CONTROLLER CONNECTED"}
|
||||
</div>
|
||||
</motion.footer>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<button
|
||||
onClick={() => {
|
||||
audio.playPressSound();
|
||||
setIsUiHidden(!isUiHidden);
|
||||
}}
|
||||
className="hover:scale-110 active:scale-95 transition-transform outline-none bg-transparent border-none"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
isUiHidden
|
||||
? "/images/Unhide_UI_Button.png"
|
||||
: "/images/Hide_UI_Button.png"
|
||||
}
|
||||
className="w-10 h-10 cursor-pointer object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{!config.legacyMode && (
|
||||
<motion.div
|
||||
{...uiFade}
|
||||
className="absolute bottom-6 right-8 z-50 flex items-center gap-3"
|
||||
>
|
||||
<span className="text-[#E0E0E0] text-[10px] mc-text-shadow tracking-widest uppercase opacity-70 mt-1">
|
||||
{displayIsDay ? "Day" : "Night"}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
audio.playPressSound();
|
||||
config.setIsDayTime(!config.isDayTime);
|
||||
}}
|
||||
className="hover:scale-110 active:scale-95 transition-transform outline-none bg-transparent border-none"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
displayIsDay
|
||||
? "/images/Day_Toggle.png"
|
||||
: "/images/Night_Toggle.png"
|
||||
}
|
||||
alt="Toggle Time"
|
||||
className="w-12 h-12 cursor-pointer block object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{isUiHidden && !displayIsDay && activeView === "devtools" && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.8 }}
|
||||
className="absolute inset-0 z-[100] flex items-center justify-center pointer-events-none"
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
audio.playPressSound();
|
||||
setIsUiHidden(false);
|
||||
setActiveView("swf-editor");
|
||||
}}
|
||||
className="pointer-events-auto hover:scale-110 active:scale-95 transition-transform outline-none bg-transparent border-none flex flex-col items-center gap-2 group"
|
||||
>
|
||||
<img
|
||||
src="/images/tools/pck.png"
|
||||
className="w-16 h-16 cursor-pointer object-contain opacity-50 group-hover:opacity-100 drop-shadow-[0_4px_4px_rgba(0,0,0,1)] grayscale group-hover:grayscale-0"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src =
|
||||
"/images/Button_Background.png";
|
||||
}}
|
||||
/>
|
||||
<span className="text-[#FFFF55] text-sm mc-text-shadow opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
SWF Editor
|
||||
</span>
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<div className="shrink-0 flex justify-center py-4 relative w-full pt-4">
|
||||
<div className="relative w-full max-w-135 flex justify-center">
|
||||
<motion.img
|
||||
layoutId="mainLogo"
|
||||
src={titleImage}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 300,
|
||||
damping: 25,
|
||||
}}
|
||||
className="w-full drop-shadow-[0_8px_6px_rgba(0,0,0,0.8)] pointer-events-none"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
<motion.div
|
||||
{...uiFade}
|
||||
className="absolute bottom-[20%] right-[5%] w-0 h-0 flex items-center justify-center"
|
||||
>
|
||||
<div
|
||||
onClick={audio.cycleSplash}
|
||||
className="mc-splash text-[#FFFF55] text-[28px] z-100 cursor-pointer whitespace-nowrap"
|
||||
style={{ textShadow: "2px 2px 0px #3F3F00" }}
|
||||
>
|
||||
{audio.splashIndex === -1
|
||||
? `Welcome ${config.username}!`
|
||||
: audio.splashes[audio.splashIndex]}
|
||||
</div>
|
||||
</motion.div>
|
||||
{activeView === "main" && hasAnyInstall && titleImage === "/images/MenuTitle.png" && (
|
||||
<motion.div
|
||||
{...uiFade}
|
||||
className="absolute -bottom-6 text-[#A0A0A0] text-sm mc-text-shadow tracking-widest uppercase opacity-80 font-['Mojangles']"
|
||||
>
|
||||
{selectedVersionName}
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main className="flex-1 w-full relative">
|
||||
<div
|
||||
className={`w-full h-full flex flex-col items-center justify-center ${isUiHidden ? "opacity-0 pointer-events-none" : "opacity-100"}`}
|
||||
>
|
||||
<AnimatePresence mode="wait">
|
||||
{activeView === "main" && (
|
||||
<SkinViewer
|
||||
key="skin-viewer"
|
||||
username={config.username}
|
||||
setUsername={config.setUsername}
|
||||
playPressSound={audio.playPressSound}
|
||||
skinUrl={skinUrl}
|
||||
capeUrl={config.legacyMode ? null : capeUrl}
|
||||
setSkinUrl={setSkinUrl}
|
||||
setActiveView={setActiveView}
|
||||
isFocusedSection={focusSection === "skin"}
|
||||
onNavigateRight={onNavigateToMenu}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<div className="w-full h-full max-w-4xl relative flex justify-center items-center overflow-hidden">
|
||||
<AnimatePresence mode="wait">
|
||||
{activeView === "main" && <HomeView key="main-view" />}
|
||||
{activeView === "settings" && (
|
||||
<SettingsView key="settings-view" />
|
||||
)}
|
||||
{activeView === "versions" && (
|
||||
<VersionsView key="versions-view" />
|
||||
)}
|
||||
{activeView === "workshop" && (
|
||||
<WorkshopView key="workshop-view" />
|
||||
)}
|
||||
{activeView === "devtools" && (
|
||||
<DevtoolsView key="devtools-view" />
|
||||
)}
|
||||
{activeView === "pck-editor" && (
|
||||
<PckEditorView key="pck-editor-view" />
|
||||
)}
|
||||
{activeView === "arc-editor" && (
|
||||
<ArcEditorView key="arc-editor-view" />
|
||||
)}
|
||||
{activeView === "loc-editor" && (
|
||||
<LocEditorView key="loc-editor-view" />
|
||||
)}
|
||||
{activeView === "grf-editor" && (
|
||||
<GrfEditorView key="grf-editor-view" />
|
||||
)}
|
||||
{activeView === "col-editor" && (
|
||||
<ColEditorView key="col-editor-view" />
|
||||
)}
|
||||
{activeView === "options-editor" && (
|
||||
<OptionsEditorView key="options-editor-view" />
|
||||
)}
|
||||
{activeView === "swf-editor" && (
|
||||
<SwfView key="swf-editor-view" />
|
||||
)}
|
||||
{activeView === "lcelive" && (
|
||||
<LceLiveView key="lcelive-view" />
|
||||
)}
|
||||
{activeView === "skins" && <SkinsView key="skins-view" />}
|
||||
{activeView === "screenshots" && (
|
||||
<ScreenshotsView key="screenshots-view" />
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<motion.footer
|
||||
{...uiFade}
|
||||
className="shrink-0 p-4 flex justify-between items-end text-[10px] text-[#A0A0A0] mc-text-shadow bg-gradient-to-t from-black/80 to-transparent uppercase tracking-widest opacity-60 font-['Mojangles']"
|
||||
style={{ fontWeight: "normal" }}
|
||||
>
|
||||
<div className="flex-1 text-left whitespace-nowrap">
|
||||
Version: {pkg.version} ({__BUILD_DATE__})
|
||||
</div>
|
||||
<div className="flex-[2] text-center whitespace-nowrap">
|
||||
Not affiliated with Mojang AB or Microsoft. "Minecraft" is
|
||||
a trademark of Mojang Synergies AB.
|
||||
</div>
|
||||
<div className="flex-1 text-right whitespace-nowrap">
|
||||
{connected && "CONTROLLER CONNECTED"}
|
||||
</div>
|
||||
</motion.footer>
|
||||
</motion.div>
|
||||
|
||||
<AchievementToast
|
||||
message={friendRequestMessage}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export interface AppConfig {
|
|||
animationsEnabled?: boolean;
|
||||
vfxEnabled?: boolean;
|
||||
rpcEnabled?: boolean;
|
||||
startFullscreen?: boolean;
|
||||
musicVol?: number;
|
||||
sfxVol?: number;
|
||||
legacyMode?: boolean;
|
||||
|
|
|
|||