Merge pull request #7 from fallout2-ce/feat/main

This commit is contained in:
Daniel X Feng
2025-09-05 20:26:56 +03:00
committed by GitHub
13 changed files with 214 additions and 19 deletions
+6
View File
@@ -5,6 +5,12 @@
<link rel="icon" type="image/x-icon" href="./fallout2-ce.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fallout 2 Community Edition</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Exo+2:ital,wght@0,100..900;1,100..900&family=Orbitron:wght@400..900&display=swap"
rel="stylesheet"
/>
</head>
<body class="dark">
<div id="root"></div>
+1
View File
@@ -11,6 +11,7 @@
},
"dependencies": {
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/vite": "^4.1.12",
"class-variance-authority": "^0.7.1",
+38
View File
@@ -11,6 +11,9 @@ importers:
'@radix-ui/react-dropdown-menu':
specifier: ^2.1.16
version: 2.1.16(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-scroll-area':
specifier: ^1.2.10
version: 1.2.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-slot':
specifier: ^1.2.3
version: 1.2.3(@types/react@19.1.12)(react@19.1.1)
@@ -434,6 +437,9 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@radix-ui/number@1.1.1':
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
'@radix-ui/primitive@1.1.3':
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
@@ -625,6 +631,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-scroll-area@1.2.10':
resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.2.3':
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
peerDependencies:
@@ -2070,6 +2089,8 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1
'@radix-ui/number@1.1.1': {}
'@radix-ui/primitive@1.1.3': {}
'@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
@@ -2253,6 +2274,23 @@ snapshots:
'@types/react': 19.1.12
'@types/react-dom': 19.1.9(@types/react@19.1.12)
'@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@radix-ui/number': 1.1.1
'@radix-ui/primitive': 1.1.3
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1)
'@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1)
'@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@19.1.1)
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
'@types/react': 19.1.12
'@types/react-dom': 19.1.9(@types/react@19.1.12)
'@radix-ui/react-slot@1.2.3(@types/react@19.1.12)(react@19.1.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1)
+39 -2
View File
@@ -1,7 +1,44 @@
import useLang from "@/lib/hooks/useLang";
import { translations } from "@/locale";
import Introduction from "@/components/Introduction";
import { Button } from "@/components/ui/button";
function App() {
const { currLang } = useLang();
return (
<div className="w-full max-w-4xl p-4 lg:my-8">
<h1>Title</h1>
<div className="w-full max-w-4xl p-4 lg:my-8 gap-8 flex flex-col">
<h1 className="text-primary text-5xl lg:text-6xl font-extrabold italic max-w-2/3 orbitron tracking-tighter">
{translations[currLang].title}
</h1>
<p className="text-2xl font-bold italic">
{translations[currLang].subtitle}
</p>
<Introduction />
<div className="flex w-full lg:w-1/2 gap-6">
<a
className="w-full flex-1"
href="https://fallout2-ce.github.io/fallout2-ce"
target="_blank"
rel="noopener noreferrer"
>
<Button
className="w-full rounded-full text-lg py-4 h-14 lg:py-6 px-6"
variant="outline"
>
{translations[currLang].webDemo}
</Button>
</a>
<a
className="w-full flex-1"
href="https://github.com/fallout2-ce/fallout2-ce/releases"
target="_blank"
rel="noopener noreferrer"
>
<Button className="w-full rounded-full text-lg py-4 h-14 lg:py-6 px-8">
{translations[currLang].download}
</Button>
</a>
</div>
</div>
);
}
+2 -2
View File
@@ -1,5 +1,5 @@
import useLang from "@/lib/hooks/useLang";
import translations from "@/locale";
import { translations } from "@/locale";
import { SiDiscord, SiTelegram } from "react-icons/si";
// TODO: plz update the links
@@ -8,7 +8,7 @@ const Footer = () => {
const { currLang } = useLang();
return (
<footer className="w-full h-16 lg:h-12 shrink-0 flex items-center justify-center bg-background/10 sticky bottom-0 z-20 backdrop-blur-xs border-t-background">
<div className="w-full max-w-4xl justify-between p-4 items-center flex flex-col lg:flex-row gap-2 my-2 text-sm">
<div className="w-full max-w-4xl justify-between p-4 items-center flex flex-col lg:flex-row gap-2 my-2 text-sm text-muted-foreground">
<div className="flex gap-6">
<a
href="https://discord.gg/"
+1 -1
View File
@@ -3,7 +3,7 @@ import LangToggle from "@/components/LangToggle";
const Header = () => (
<header className="w-full h-16 shrink-0 flex items-center justify-center bg-background/20 sticky top-0 z-20 backdrop-blur-xs border-b-background">
<div className="w-full max-w-4xl flex justify-between p-4 items-center">
<div className="w-full max-w-4xl flex justify-between p-4 items-center text-muted-foreground">
<Link to="/">
<img
src="/logo.png"
+25
View File
@@ -0,0 +1,25 @@
import useLang from "@/lib/hooks/useLang";
import { transIntro, translations } from "@/locale";
import { ScrollArea } from "@/components/ui/scroll-area";
const Introduction = () => {
const { currLang } = useLang();
return (
<div>
<h3 className="font-semibold orbitron">
{translations[currLang].features}:
</h3>
<ScrollArea className="mt-2 relative">
<div className="ml-2">
<ul className="flex flex-col gap-2 text-base text-muted-foreground list-disc w-full p-4 max-h-[5.5rem] lg:max-h-[8rem]">
{transIntro[currLang].map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
</ScrollArea>
</div>
);
};
export default Introduction;
+1 -1
View File
@@ -32,7 +32,7 @@ const LangToggle = () => {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost">
<Globe className="h-6 w-6" />
<Globe />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
+56
View File
@@ -0,0 +1,56 @@
import * as React from "react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import { cn } from "@/lib/utils";
function ScrollArea({
className,
children,
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn("relative", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
);
}
function ScrollBar({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
);
}
export { ScrollArea, ScrollBar };
+15 -6
View File
@@ -28,7 +28,7 @@
--secondary: oklch(0.968 0.007 247.896);
--secondary-foreground: oklch(0.208 0.042 265.755);
--muted: oklch(0.968 0.007 247.896);
--muted-foreground: oklch(0.554 0.046 257.417);
--muted-foreground: oklch(87.2% 0.01 258.338);
--accent: oklch(0.968 0.007 247.896);
--accent-foreground: oklch(0.208 0.042 265.755);
--destructive: oklch(0.577 0.245 27.325);
@@ -90,17 +90,17 @@
.dark {
--background: oklch(0.129 0.042 264.695);
--foreground: oklch(0.984 0.003 247.858);
--foreground: oklch(96.7% 0.001 286.375);
--card: oklch(0.208 0.042 265.755);
--card-foreground: oklch(0.984 0.003 247.858);
--popover: oklch(0.208 0.042 265.755);
--popover-foreground: oklch(0.984 0.003 247.858);
--primary: oklch(0.929 0.013 255.508);
--primary: oklch(85.2% 0.199 91.936);
--primary-foreground: oklch(0.208 0.042 265.755);
--secondary: oklch(0.279 0.041 260.031);
--secondary-foreground: oklch(0.984 0.003 247.858);
--muted: oklch(0.279 0.041 260.031);
--muted-foreground: oklch(0.704 0.04 256.788);
--muted-foreground: oklch(70.8% 0 0);
--accent: oklch(0.279 0.041 260.031);
--accent-foreground: oklch(0.984 0.003 247.858);
--destructive: oklch(0.704 0.191 22.216);
@@ -122,8 +122,17 @@
--sidebar-ring: oklch(0.551 0.027 264.364);
}
@layer utilities {
.exo-2 {
font-family: "Exo 2", sans-serif;
}
.orbitron {
font-family: "Orbitron", sans-serif;
}
}
@layer base {
* {
@apply m-0 p-0 text-foreground;
html body {
@apply m-0 p-0 text-foreground exo-2;
}
}
+2 -2
View File
@@ -8,12 +8,12 @@ import { useEffect, useMemo } from "react";
const getLangFromSys = (): Lang => {
const navLang = navigator.language;
const langs = SupportedLangs.filter((l) => l != "sys");
const langs = SupportedLangs.filter((l) => l != "System");
return langs.find((l) => navLang.startsWith(l)) || "en";
};
const resolveLang = (lang: LangWithSys): Lang => {
return lang === "sys" ? getLangFromSys() : lang;
return lang === "System" ? getLangFromSys() : lang;
};
const useLang = () => {
+3 -3
View File
@@ -1,10 +1,10 @@
import { atomWithStorage } from "jotai/utils";
const SupportedLangs = ["sys", "en"] as const;
const SupportedLangs = ["System", "en"] as const;
type LangWithSys = (typeof SupportedLangs)[number];
type Lang = Exclude<LangWithSys, "sys">;
type Lang = Exclude<LangWithSys, "System">;
const langAtom = atomWithStorage<LangWithSys>("lang", "sys");
const langAtom = atomWithStorage<LangWithSys>("lang", "System");
export default langAtom;
+25 -2
View File
@@ -1,13 +1,36 @@
import type { Lang } from "@/lib/stores/langStore";
type TranslationKey = "developedBy" | "title";
type TranslationKey =
| "developedBy"
| "title"
| "subtitle"
| "webDemo"
| "download"
| "features";
const translations: Record<Lang, Record<TranslationKey, string>> = {
en: {
developedBy: "Developed with ❤️ by",
title: "Fallout 2 Community Edition",
subtitle: "A fully working re-implementation",
webDemo: "Try in browser",
download: "Download",
features: "Features",
},
};
export default translations;
const transIntro: Record<Lang, string[]> = {
en: [
"High resolution support",
"Increased pathfinding nodes 5x for more accurate pathfinding",
"Ctrl-click to quickly move items when bartering, looting, or stealing",
"Press 'a' to select all when choosing item quantity",
"Press 'a' to Take All when looting",
"When bartering, caps default to the right amount to balance the trade (if possible)",
"Music continues playing between maps (requires config)",
"Auto open doors (requires config)",
],
};
export { transIntro, translations };
export type { TranslationKey };