mirror of
https://github.com/LCE-Hub/LCE-Emerald-Launcher.git
synced 2026-07-15 23:10:48 +00:00
feat: ui fixes + new credit view (not finished yet
This commit is contained in:
@@ -98,12 +98,9 @@ export function AchievementToast({
|
||||
<div
|
||||
className="flex items-center gap-4 p-4 min-w-[300px] max-w-[450px]"
|
||||
style={{
|
||||
backgroundColor: "#212121",
|
||||
border: "4px solid",
|
||||
borderTopColor: "#7F7F7F",
|
||||
borderLeftColor: "#7F7F7F",
|
||||
borderBottomColor: "#3F3F3F",
|
||||
borderRightColor: "#3F3F3F",
|
||||
backgroundImage: "url('/images/notification.png')",
|
||||
backgroundSize: "cover",
|
||||
backgroundRepeat: "no-repeat",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function TeamModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
playPressSound,
|
||||
playSfx,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
playPressSound: () => void;
|
||||
playSfx: (sound: string) => void;
|
||||
}) {
|
||||
const [focusIndex, setFocusIndex] = useState(0);
|
||||
|
||||
const team = [
|
||||
{ name: "neoapps", url: "https://github.com/neoapps-dev" },
|
||||
{ name: "KayJann", url: "https://github.com/KayJannOnGit" },
|
||||
{ name: "Santiago Fisela", url: "https://github.com/PinkLittleKitty" },
|
||||
{ name: "Leon", url: "https://github.com/hornyalcoholic" },
|
||||
{ name: "Criador_Mods", url: "https://github.com/CriadorMods" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setFocusIndex(0);
|
||||
return;
|
||||
}
|
||||
const handleKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
playSfx("close_click.wav");
|
||||
onClose();
|
||||
} else if (e.key === "ArrowDown" || e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
setFocusIndex((prev) => (prev + 1) % (team.length + 1));
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setFocusIndex((prev) => (prev - 1 + (team.length + 1)) % (team.length + 1));
|
||||
} else if (e.key === "Enter") {
|
||||
if (focusIndex === team.length) {
|
||||
playSfx("close_click.wav");
|
||||
onClose();
|
||||
} else {
|
||||
playPressSound();
|
||||
window.open(team[focusIndex].url, "_blank", "noopener,noreferrer");
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKey);
|
||||
return () => window.removeEventListener("keydown", handleKey);
|
||||
}, [isOpen, focusIndex]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="absolute inset-0 z-[100] flex items-center justify-center bg-black/80 backdrop-blur-sm outline-none border-none"
|
||||
>
|
||||
<div
|
||||
className="relative w-[360px] p-6 flex flex-col items-center shadow-2xl"
|
||||
style={{
|
||||
backgroundImage: "url('/images/frame_background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
<h2 className="text-[#FFFF55] text-2xl mc-text-shadow mb-4 border-b-2 border-[#373737] pb-2 w-full text-center uppercase">
|
||||
Emerald Team
|
||||
</h2>
|
||||
<div className="flex flex-col gap-3 w-full items-center">
|
||||
{team.map((dev, idx) => (
|
||||
<a
|
||||
key={dev.name}
|
||||
href={dev.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => playPressSound()}
|
||||
onMouseEnter={() => setFocusIndex(idx)}
|
||||
className={`w-56 h-10 flex items-center justify-center mc-text-shadow text-xl transition-all outline-none border-none bg-transparent ${focusIndex === idx ? "text-[#FFFF55]" : "text-white"}`}
|
||||
style={{
|
||||
backgroundImage: focusIndex === idx
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
{dev.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onMouseEnter={() => setFocusIndex(team.length)}
|
||||
onClick={() => {
|
||||
playSfx("close_click.wav");
|
||||
onClose();
|
||||
}}
|
||||
className={`mt-6 w-56 h-12 flex items-center justify-center transition-colors text-2xl mc-text-shadow outline-none border-none ${focusIndex === team.length ? "text-[#FFFF55]" : "text-white"}`}
|
||||
style={{
|
||||
backgroundImage: focusIndex === team.length
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
289
src/components/views/CreditsView.tsx
Normal file
289
src/components/views/CreditsView.tsx
Normal file
@@ -0,0 +1,289 @@
|
||||
import { useEffect, memo } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useUI, useAudio } from "../../context/LauncherContext";
|
||||
|
||||
interface CreditCategory {
|
||||
category: string;
|
||||
icon?: string;
|
||||
subcategories: {
|
||||
name: string;
|
||||
icon: string;
|
||||
roles: {
|
||||
role: string;
|
||||
members: {
|
||||
name: string;
|
||||
url: string;
|
||||
}[];
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
|
||||
const CreditsView = memo(function CreditsView() {
|
||||
const { setActiveView } = useUI();
|
||||
const { playBackSound } = useAudio();
|
||||
|
||||
const credits: CreditCategory[] = [
|
||||
{
|
||||
category: "Emerald Team",
|
||||
icon: "/images/emerald_0.png",
|
||||
subcategories: [
|
||||
{
|
||||
name: "Leadership",
|
||||
icon: "",
|
||||
roles: [
|
||||
{
|
||||
role: "Founder & Maintainer",
|
||||
members: [
|
||||
{ name: "KayJann", url: "https://github.com/KayJannOnGit" },
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "Active Maintainer",
|
||||
members: [
|
||||
{ name: "neoapps", url: "https://github.com/neoapps-dev" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Contributors",
|
||||
icon: "",
|
||||
roles: [
|
||||
{
|
||||
role: "",
|
||||
members: [
|
||||
{ name: "Santiago Fisela", url: "https://github.com/PinkLittleKitty" },
|
||||
{ name: "Leon", url: "https://github.com/hornyalcoholic" },
|
||||
{ name: "Criador_Mods", url: "https://github.com/CriadorMods" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "LCE TEAM",
|
||||
subcategories: [
|
||||
{
|
||||
name: "neoLegacy",
|
||||
icon: "/images/neoLegacy.png",
|
||||
roles: [
|
||||
{
|
||||
role: "Founder",
|
||||
members: [
|
||||
{ name: "Piebot", url: "https://github.com/Piebot" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "360 Revived",
|
||||
icon: "/images/360_revived.png",
|
||||
roles: [
|
||||
{
|
||||
role: "Founder",
|
||||
members: [
|
||||
{ name: "BluTac10", url: "https://github.com/BluTac10" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Hellish Ends",
|
||||
icon: "",
|
||||
roles: [
|
||||
{
|
||||
role: "Founder",
|
||||
members: [
|
||||
{ name: "DeadVoxelx", url: "https://github.com/DeadVoxelx" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Revelations",
|
||||
icon: "",
|
||||
roles: [
|
||||
{
|
||||
role: "Founder",
|
||||
members: [
|
||||
{ name: "Revela", url: "https://github.com/Revela" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Portable LCE",
|
||||
icon: "",
|
||||
roles: [
|
||||
{
|
||||
role: "Founder",
|
||||
members: [
|
||||
{ name: "TBD", url: "#" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
{
|
||||
category: "SPECIAL THANKS",
|
||||
icon: "",
|
||||
subcategories: [
|
||||
{
|
||||
name: "Discord Booster",
|
||||
icon: "/images/discord.png",
|
||||
roles: [
|
||||
{
|
||||
role: "",
|
||||
members: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Donors",
|
||||
icon: "",
|
||||
roles: [
|
||||
{
|
||||
role: "",
|
||||
members: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const handleKey = (e: KeyboardEvent) => {
|
||||
if (document.activeElement?.tagName === "INPUT") return;
|
||||
if (e.key === "Escape") {
|
||||
playBackSound();
|
||||
setActiveView("main");
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKey);
|
||||
return () => window.removeEventListener("keydown", handleKey);
|
||||
}, [setActiveView, playBackSound]);
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full flex items-center justify-center overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ y: "50%" }}
|
||||
animate={{ y: "-200%" }}
|
||||
transition={{ duration: 30, ease: "linear" }}
|
||||
onAnimationComplete={() => setActiveView("main")}
|
||||
className="flex flex-col items-center justify-center space-y-8 py-20"
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
playBackSound();
|
||||
setActiveView("main");
|
||||
}}
|
||||
className="absolute bottom-8 right-8 h-10 flex items-center justify-center gap-2 px-4 text-xl mc-text-shadow transition-colors outline-none border-none text-white"
|
||||
style={{
|
||||
backgroundImage: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
Back to Menu
|
||||
</button>
|
||||
|
||||
{credits.map((cat) => (
|
||||
<div key={cat.category} className="flex flex-col items-center gap-6">
|
||||
<h2
|
||||
className={`text-3xl font-bold mc-text-shadow uppercase tracking-wider text-center flex items-center gap-3 ${
|
||||
cat.category === "LCE TEAM" ? "text-[#9B59B6]" :
|
||||
cat.category === "SPECIAL THANKS" ? "text-[#FFD700]" :
|
||||
"text-[#50C878]"
|
||||
}`}
|
||||
style={{ textShadow: "2px 2px 0px #000000" }}
|
||||
>
|
||||
{cat.icon && (
|
||||
<img
|
||||
src={cat.icon}
|
||||
alt={cat.category}
|
||||
className="w-10 h-10 object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
)}
|
||||
{cat.category}
|
||||
</h2>
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
{cat.subcategories.map((sub) => (
|
||||
<div key={sub.name} className="flex flex-col items-center gap-3">
|
||||
<h3 className={`text-2xl mc-text-shadow uppercase tracking-wide text-center flex items-center gap-3 ${
|
||||
sub.name === "Discord Booster" ? "text-[#FF69B4]" : "text-white/90"
|
||||
}`}>
|
||||
{sub.icon && (
|
||||
<img
|
||||
src={sub.icon}
|
||||
alt={sub.name}
|
||||
className="w-8 h-8 object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
)}
|
||||
{sub.name}
|
||||
</h3>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
{sub.roles.map((role) => (
|
||||
<div key={role.role} className="flex flex-col items-center gap-2">
|
||||
{role.role && (
|
||||
<h4 className="text-white/70 text-lg mc-text-shadow uppercase tracking-wide text-center">
|
||||
{role.role}
|
||||
</h4>
|
||||
)}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
{role.members.map((member) => (
|
||||
<a
|
||||
key={member.name}
|
||||
href={member.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-[#e2cc4c] text-2xl mc-text-shadow font-medium cursor-pointer"
|
||||
style={{ textShadow: "2px 2px 0px #000000" }}
|
||||
>
|
||||
{member.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="flex flex-row items-center justify-center gap-6 mt-12">
|
||||
<div className="bg-white rounded-xl p-3 shadow-2xl w-64">
|
||||
<img
|
||||
src="/images/LCE Team.png"
|
||||
alt="LCE Team"
|
||||
className="w-full h-auto object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl p-3 shadow-2xl w-64">
|
||||
<img
|
||||
src="/images/esrb_warning.png"
|
||||
alt="ESRB Warning"
|
||||
className="w-full h-auto object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-2 mt-8">
|
||||
<p className="text-white/60 text-sm mc-text-shadow text-center uppercase tracking-wider">
|
||||
Not affiliated with Mojang, Microsoft, or 4J Studios
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default CreditsView;
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import type { Edition } from "../../types/edition";
|
||||
|
||||
const HomeView = memo(function HomeView() {
|
||||
const { setActiveView, setShowCredits, focusSection, onNavigateToSkin } =
|
||||
const { setActiveView, focusSection, onNavigateToSkin } =
|
||||
useUI();
|
||||
const { profile, legacyMode } = useConfig();
|
||||
const { playPressSound, playSfx } = useAudio();
|
||||
@@ -208,12 +208,12 @@ const HomeView = memo(function HomeView() {
|
||||
onClick={() => {
|
||||
if (isFocusedSection) {
|
||||
playSfx("orb.ogg");
|
||||
setShowCredits(true);
|
||||
setActiveView("credits");
|
||||
}
|
||||
}}
|
||||
className={`text-white hover:text-[#FFFF55] text-xl mc-text-shadow tracking-widest transition-colors mt-1 bg-transparent border-none outline-none ${!isFocusedSection ? "pointer-events-none" : ""}`}
|
||||
>
|
||||
EMERALD TEAM
|
||||
CREDITS
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -18,8 +18,6 @@ interface UIContextType {
|
||||
isUiHidden: boolean;
|
||||
setIsUiHidden: (hidden: boolean) => void;
|
||||
isWindowVisible: boolean;
|
||||
showCredits: boolean;
|
||||
setShowCredits: (show: boolean) => void;
|
||||
focusSection: "menu" | "skin";
|
||||
setFocusSection: (section: "menu" | "skin") => void;
|
||||
onNavigateToSkin: () => void;
|
||||
@@ -40,7 +38,6 @@ export function LauncherProvider({ children }: { children: React.ReactNode }) {
|
||||
const [activeView, setActiveView] = useState("main");
|
||||
const [isUiHidden, setIsUiHidden] = useState(false);
|
||||
const [isWindowVisible, setIsWindowVisible] = useState(true);
|
||||
const [showCredits, setShowCredits] = useState(false);
|
||||
const [focusSection, setFocusSection] = useState<"menu" | "skin">("menu");
|
||||
|
||||
const { updateMessage, updateUrl, clearUpdateMessage } = useUpdateCheck();
|
||||
@@ -191,10 +188,10 @@ export function LauncherProvider({ children }: { children: React.ReactNode }) {
|
||||
activeView, setActiveView, showIntro, setShowIntro,
|
||||
logoAnimDone, setLogoAnimDone, isUiHidden, setIsUiHidden,
|
||||
isWindowVisible,
|
||||
showCredits, setShowCredits, focusSection, setFocusSection,
|
||||
focusSection, setFocusSection,
|
||||
onNavigateToSkin, onNavigateToMenu, connected,
|
||||
updateMessage, updateUrl, clearUpdateMessage
|
||||
}), [activeView, showIntro, logoAnimDone, isUiHidden, isWindowVisible, showCredits, focusSection, onNavigateToSkin, onNavigateToMenu, connected, updateMessage, updateUrl, clearUpdateMessage]);
|
||||
}), [activeView, showIntro, logoAnimDone, isUiHidden, isWindowVisible, focusSection, onNavigateToSkin, onNavigateToMenu, connected, updateMessage, updateUrl, clearUpdateMessage]);
|
||||
|
||||
return (
|
||||
<UIContext.Provider value={uiValue}>
|
||||
|
||||
@@ -17,8 +17,8 @@ import OptionsEditorView from "../components/views/OptionsEditorView";
|
||||
import ScreenshotsView from "../components/views/ScreenshotsView";
|
||||
import SwfView from "../components/views/SwfView";
|
||||
import LceLiveView from "../components/views/LceLiveView";
|
||||
import CreditsView from "../components/views/CreditsView";
|
||||
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";
|
||||
@@ -44,8 +44,6 @@ export default function App() {
|
||||
setActiveView,
|
||||
isUiHidden,
|
||||
setIsUiHidden,
|
||||
showCredits,
|
||||
setShowCredits,
|
||||
focusSection,
|
||||
onNavigateToMenu,
|
||||
updateMessage,
|
||||
@@ -164,17 +162,6 @@ export default function App() {
|
||||
|
||||
{config.vfxEnabled && <ClickParticles />}
|
||||
|
||||
<AnimatePresence>
|
||||
{showCredits && (
|
||||
<TeamModal
|
||||
isOpen={showCredits}
|
||||
onClose={() => setShowCredits(false)}
|
||||
playPressSound={audio.playPressSound}
|
||||
playSfx={audio.playSfx}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<DownloadOverlay
|
||||
downloadProgress={game.downloadProgress}
|
||||
downloadingId={game.downloadingId}
|
||||
@@ -309,31 +296,35 @@ export default function App() {
|
||||
|
||||
<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" }}
|
||||
{activeView !== "credits" && (
|
||||
<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" }}
|
||||
/>
|
||||
)}
|
||||
{activeView !== "credits" && (
|
||||
<motion.div
|
||||
{...uiFade}
|
||||
className="absolute bottom-[20%] right-[5%] w-0 h-0 flex items-center justify-center"
|
||||
>
|
||||
{audio.splashIndex === -1
|
||||
? `Welcome ${config.username}!`
|
||||
: audio.splashes[audio.splashIndex]}
|
||||
</div>
|
||||
</motion.div>
|
||||
<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}
|
||||
@@ -409,6 +400,9 @@ export default function App() {
|
||||
{activeView === "screenshots" && (
|
||||
<ScreenshotsView key="screenshots-view" />
|
||||
)}
|
||||
{activeView === "credits" && (
|
||||
<CreditsView key="credits-view" />
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user