update and polish
This commit is contained in:
+139
-160
@@ -2,223 +2,202 @@
|
||||
|
||||
import React from "react";
|
||||
import { Planet, Starship } from "@/types/swapi";
|
||||
import { t } from "@/content/locales";
|
||||
import { t, LocaleType } from "@/content/locales";
|
||||
import { getPlanetTheme } from "@/utils/getPlanetTheme";
|
||||
|
||||
interface ActionPanelProps {
|
||||
planet?: Planet | null;
|
||||
enemy?: Starship | null;
|
||||
onAction: () => void;
|
||||
onEscape?: () => void;
|
||||
onRepair?: () => void;
|
||||
isLoading?: boolean;
|
||||
isCurrentLocation?: boolean;
|
||||
currentHp: number;
|
||||
}
|
||||
|
||||
const getPlanetTheme = (terrain: string = "", climate: string = "") => {
|
||||
const terr = terrain.toLowerCase();
|
||||
const clim = climate.toLowerCase();
|
||||
|
||||
if (terr.includes("ocean") || terr.includes("water"))
|
||||
return {
|
||||
color: "text-blue-400",
|
||||
border: "border-blue-500",
|
||||
glow: "shadow-[0_0_20px_#3b82f6]",
|
||||
label: t.ACTION_PANEL.MODES.AQUATIC,
|
||||
};
|
||||
if (terr.includes("forest") || terr.includes("jungle"))
|
||||
return {
|
||||
color: "text-green-400",
|
||||
border: "border-green-500",
|
||||
glow: "shadow-[0_0_20px_#22c55e]",
|
||||
label: t.ACTION_PANEL.MODES.VERDANT,
|
||||
};
|
||||
if (terr.includes("desert") || clim.includes("arid"))
|
||||
return {
|
||||
color: "text-orange-400",
|
||||
border: "border-orange-500",
|
||||
glow: "shadow-[0_0_20px_#f97316]",
|
||||
label: t.ACTION_PANEL.MODES.ARID,
|
||||
};
|
||||
if (
|
||||
terr.includes("ice") ||
|
||||
terr.includes("glacier") ||
|
||||
clim.includes("frozen")
|
||||
)
|
||||
return {
|
||||
color: "text-cyan-200",
|
||||
border: "border-cyan-300",
|
||||
glow: "shadow-[0_0_20px_#a5f3fc]",
|
||||
label: t.ACTION_PANEL.MODES.CRYO,
|
||||
};
|
||||
if (terr.includes("city") || terr.includes("urban"))
|
||||
return {
|
||||
color: "text-purple-400",
|
||||
border: "border-purple-500",
|
||||
glow: "shadow-[0_0_20px_#a855f7]",
|
||||
label: t.ACTION_PANEL.MODES.ECUMENOPOLIS,
|
||||
};
|
||||
|
||||
return {
|
||||
color: "text-yellow-500",
|
||||
border: "border-yellow-500",
|
||||
glow: "shadow-[0_0_20px_#eab308]",
|
||||
label: t.ACTION_PANEL.MODES.STANDARD,
|
||||
};
|
||||
};
|
||||
|
||||
export default function ActionPanel({
|
||||
planet,
|
||||
enemy,
|
||||
onAction,
|
||||
onEscape,
|
||||
onRepair,
|
||||
isLoading,
|
||||
isCurrentLocation,
|
||||
currentHp,
|
||||
}: ActionPanelProps) {
|
||||
const isCombat = !!enemy;
|
||||
const theme =
|
||||
!isCombat && planet
|
||||
? getPlanetTheme(planet.terrain, planet.climate)
|
||||
: {
|
||||
color: "text-red-500",
|
||||
border: "border-red-600",
|
||||
glow: "shadow-[0_0_30px_#dc2626]",
|
||||
label: t.ACTION_PANEL.MODES.HOSTILE,
|
||||
};
|
||||
|
||||
const displayName = isCombat ? enemy.name : planet?.name;
|
||||
const displaySubtitle = isCombat
|
||||
? `${t.ACTION_PANEL.LABELS.CLASS}: ${enemy.starship_class} // ${t.ACTION_PANEL.LABELS.CREW}: ${enemy.crew}`
|
||||
: `${t.ACTION_PANEL.LABELS.SECTOR}: ${planet?.climate} // ${planet?.terrain}`;
|
||||
const planetVisual = getPlanetTheme(
|
||||
planet?.terrain,
|
||||
planet?.climate,
|
||||
t as LocaleType,
|
||||
);
|
||||
|
||||
const uiTheme = isCombat
|
||||
? {
|
||||
color: "text-red-500",
|
||||
border: "border-red-600",
|
||||
glow: "shadow-[0_0_30px_#dc2626]",
|
||||
label: t.ACTION_PANEL.MODES.HOSTILE,
|
||||
borderAlpha: "border-red-900/30",
|
||||
labelAlpha: "text-red-500/50",
|
||||
}
|
||||
: {
|
||||
color: "text-yellow-500",
|
||||
border: "border-yellow-300",
|
||||
glow: "shadow-[0_0_20px_rgba(234,179,8,0.2)]",
|
||||
label: "NEUTRAL SYSTEM",
|
||||
borderAlpha: "border-yellow-500/10",
|
||||
labelAlpha: "text-yellow-500/50",
|
||||
};
|
||||
|
||||
const stats = isCombat
|
||||
? [
|
||||
{
|
||||
label: t.ACTION_PANEL.LABELS.MANUFACTURER,
|
||||
value: enemy.manufacturer,
|
||||
},
|
||||
{
|
||||
label: t.ACTION_PANEL.LABELS.COST,
|
||||
value: `${enemy.cost_in_credits} Cr`,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
label: t.ACTION_PANEL.LABELS.POPULATION,
|
||||
value: planet?.population.toLocaleString(),
|
||||
},
|
||||
{ label: t.ACTION_PANEL.LABELS.GRAVITY, value: planet?.gravity },
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`w-full max-w-4xl mt-6 bg-black/90 backdrop-blur-md border-2 rounded-lg p-6 shadow-2xl animate-in fade-in slide-in-from-bottom-4 relative overflow-hidden transition-colors duration-500 ${theme.border}`}
|
||||
className={`w-full max-w-4xl mt-6 bg-black/90 backdrop-blur-md border-2 rounded-lg p-6 shadow-2xl relative overflow-hidden transition-all duration-500 ${uiTheme.border}`}
|
||||
>
|
||||
<div
|
||||
className={`absolute inset-0 bg-[linear-gradient(transparent_0%,${isCombat ? "rgba(220,38,38,0.1)" : "rgba(234,179,8,0.05)"}_50%,transparent_100%)] bg-[length:100%_4px] animate-pulse pointer-events-none`}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-8 items-center relative z-10">
|
||||
{/* VISUAL UNIT */}
|
||||
<div className="relative shrink-0">
|
||||
<div
|
||||
className={`w-32 h-32 rounded-full border-4 ${theme.border} ${theme.glow} flex items-center justify-center transition-all duration-700 bg-black overflow-hidden relative`}
|
||||
className={`w-32 h-32 rounded-full flex items-center justify-center bg-black overflow-hidden relative shadow-inner transition-all duration-500 ${
|
||||
isCombat
|
||||
? `border-4 ${uiTheme.border} ${uiTheme.glow}`
|
||||
: `border-1 ${planetVisual.border} shadow-[0_0_15px_${planetVisual.hex}44]`
|
||||
}`}
|
||||
>
|
||||
{isCombat ? (
|
||||
<div className="text-red-600 animate-pulse font-black text-5xl drop-shadow-[0_0_10px_rgba(220,38,38,0.8)]">
|
||||
<div className="text-red-600 animate-pulse font-black text-6xl">
|
||||
!
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className="w-full h-full transition-colors duration-1000"
|
||||
style={{
|
||||
background: `radial-gradient(circle at 30% 30%, ${theme.color.replace("text-", "")}, #000)`,
|
||||
backgroundColor: theme.color.includes("blue")
|
||||
? "#60a5fa"
|
||||
: theme.color.includes("green")
|
||||
? "#4ade80"
|
||||
: theme.color.includes("orange")
|
||||
? "#fb923c"
|
||||
: theme.color.includes("cyan")
|
||||
? "#22d3ee"
|
||||
: theme.color.includes("purple")
|
||||
? "#c084fc"
|
||||
: "#eab308",
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-tr from-black/60 via-transparent to-white/20 pointer-events-none" />
|
||||
<div className="absolute inset-0 opacity-20 bg-[url('https://www.transparenttextures.com/patterns/carbon-fibre.png')] animate-spin-slow mix-blend-overlay" />
|
||||
</>
|
||||
<div
|
||||
className="w-full h-full transition-all duration-1000"
|
||||
style={{
|
||||
background: `radial-gradient(circle at 30% 30%, ${planetVisual.hex}, #000)`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`absolute -top-2 -right-2 bg-black border px-2 py-0.5 text-[8px] uppercase tracking-widest ${isCombat ? "border-red-600 text-red-600" : "border-yellow-500/50 text-yellow-500"}`}
|
||||
className={`absolute -top-2 -right-2 bg-black border px-2 py-0.5 text-[8px] uppercase tracking-widest ${uiTheme.color} ${uiTheme.border}`}
|
||||
>
|
||||
{theme.label}
|
||||
{isCurrentLocation && !isCombat
|
||||
? "CURRENT LOCATION"
|
||||
: uiTheme.label}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 text-center md:text-left space-y-3">
|
||||
{/* INFO SECTION */}
|
||||
<div className="flex-1 text-center md:text-left space-y-4">
|
||||
<div>
|
||||
<h3
|
||||
className={`text-3xl font-black tracking-widest uppercase ${isCombat ? "text-red-600" : "text-white"}`}
|
||||
>
|
||||
{displayName}
|
||||
</h3>
|
||||
<div className="flex flex-col md:flex-row items-baseline gap-3">
|
||||
<h3
|
||||
className={`text-3xl font-black tracking-widest uppercase ${isCombat ? "text-red-600" : "text-white"}`}
|
||||
>
|
||||
{isCombat ? enemy.name : planet?.name}
|
||||
</h3>
|
||||
{isCombat && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="px-2 py-0.5 border border-red-500 text-red-500 font-mono text-xs">
|
||||
DS {enemy.ds}
|
||||
</span>
|
||||
<div className="flex gap-1 ml-2">
|
||||
{[...Array(enemy.hp)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="w-4 h-2 bg-red-600 shadow-[0_0_8px_#dc2626] border border-red-400/50"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p
|
||||
className={`${theme.color} text-xs font-bold uppercase tracking-widest opacity-80`}
|
||||
className={`${uiTheme.color} text-[10px] font-bold uppercase tracking-[0.2em] opacity-80 mt-1`}
|
||||
>
|
||||
{displaySubtitle}
|
||||
{isCombat
|
||||
? `${t.ACTION_PANEL.LABELS.CLASS}: ${enemy.starship_class}`
|
||||
: `${t.ACTION_PANEL.LABELS.SECTOR}: ${planet?.terrain}`}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`grid grid-cols-2 gap-4 border-t border-b py-3 ${isCombat ? "border-red-900/30" : "border-yellow-500/10"}`}
|
||||
className={`grid grid-cols-2 gap-4 border-t border-b py-3 ${uiTheme.borderAlpha}`}
|
||||
>
|
||||
<div className="text-[10px] uppercase">
|
||||
<span
|
||||
className={isCombat ? "text-red-500/50" : "text-yellow-500/50"}
|
||||
>
|
||||
{isCombat
|
||||
? t.ACTION_PANEL.LABELS.MANUFACTURER
|
||||
: t.ACTION_PANEL.LABELS.POPULATION}
|
||||
</span>
|
||||
<p className="text-white font-mono truncate">
|
||||
{isCombat
|
||||
? enemy.manufacturer
|
||||
: planet?.population.toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-[10px] uppercase">
|
||||
<span
|
||||
className={isCombat ? "text-red-500/50" : "text-yellow-500/50"}
|
||||
>
|
||||
{isCombat
|
||||
? t.ACTION_PANEL.LABELS.COST
|
||||
: t.ACTION_PANEL.LABELS.GRAVITY}
|
||||
</span>
|
||||
<p className="text-white font-mono">
|
||||
{isCombat ? `${enemy.cost_in_credits} Cr` : planet?.gravity}
|
||||
</p>
|
||||
</div>
|
||||
{stats.map((stat, idx) => (
|
||||
<div key={idx} className="text-[10px] uppercase">
|
||||
<span className={uiTheme.labelAlpha}>{stat.label}</span>
|
||||
<p className="text-white font-mono truncate">{stat.value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p
|
||||
className={`text-xs italic leading-relaxed ${isCombat ? "text-red-400/60" : "text-yellow-500/60"}`}
|
||||
>
|
||||
{isCombat
|
||||
? t.ACTION_PANEL.STATUS.COMBAT_WARNING
|
||||
: t.ACTION_PANEL.STATUS.PLANET_SCAN}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* BUTTONS */}
|
||||
<div className="flex flex-col gap-3 shrink-0 w-full md:w-auto">
|
||||
<button
|
||||
onClick={onAction}
|
||||
disabled={isLoading}
|
||||
className={`w-full md:w-auto px-10 py-5 border-2 font-black uppercase tracking-[0.3em] transition-all relative group
|
||||
${
|
||||
isCombat
|
||||
? "border-red-600 text-red-600 hover:bg-red-600 hover:text-white shadow-[0_0_15px_rgba(220,38,38,0.3)]"
|
||||
: "border-yellow-500 text-yellow-500 hover:bg-yellow-500 hover:text-black hover:shadow-[0_0_30px_#eab308]"
|
||||
} ${isLoading ? "opacity-50 cursor-not-allowed" : ""}`}
|
||||
>
|
||||
{isLoading ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<span
|
||||
className={`animate-ping inline-flex h-2 w-2 rounded-full ${isCombat ? "bg-red-600" : "bg-yellow-500"}`}
|
||||
></span>
|
||||
{t.ACTION_PANEL.STATUS.ENGAGING}
|
||||
</span>
|
||||
) : isCombat ? (
|
||||
t.ACTION_PANEL.BUTTONS.FIRE
|
||||
) : (
|
||||
t.ACTION_PANEL.BUTTONS.JUMP
|
||||
)}
|
||||
</button>
|
||||
{isCurrentLocation && !isCombat ? (
|
||||
<button
|
||||
onClick={onRepair}
|
||||
disabled={currentHp >= 10 || isLoading}
|
||||
className={`w-full md:w-auto px-10 py-5 border-2 font-black uppercase tracking-[0.3em] transition-all
|
||||
${
|
||||
currentHp < 10
|
||||
? "border-blue-500 text-blue-500 hover:bg-blue-500 hover:text-white shadow-[0_0_20px_rgba(59,130,246,0.3)]"
|
||||
: "border-gray-600 text-gray-600 opacity-50 cursor-not-allowed"
|
||||
}`}
|
||||
>
|
||||
{currentHp < 10
|
||||
? t.ACTION_PANEL.BUTTONS.REPAIR((10 - currentHp) * 20)
|
||||
: t.ACTION_PANEL.BUTTONS.HULL_INTACT}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={onAction}
|
||||
disabled={isLoading}
|
||||
className={`w-full md:w-auto px-10 py-5 border-2 font-black uppercase tracking-[0.3em] transition-all
|
||||
${
|
||||
isCombat
|
||||
? "border-red-600 text-red-600 hover:bg-red-600 hover:text-white"
|
||||
: "border-yellow-500 text-yellow-500 hover:bg-yellow-500 hover:text-black"
|
||||
}
|
||||
${isLoading ? "opacity-50" : ""}`}
|
||||
>
|
||||
{isLoading
|
||||
? t.ACTION_PANEL.STATUS.ENGAGING
|
||||
: isCombat
|
||||
? t.ACTION_PANEL.BUTTONS.FIRE
|
||||
: t.ACTION_PANEL.BUTTONS.JUMP}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{isCombat && (
|
||||
<button
|
||||
onClick={onEscape}
|
||||
className="px-10 py-2 border border-white/20 text-white/40 text-[10px] uppercase tracking-widest hover:bg-white/5 hover:text-white transition-all"
|
||||
className="px-10 py-2 border border-white/40 text-white/80 text-[10px] uppercase tracking-widest hover:bg-white/5 hover:text-white transition-colors"
|
||||
>
|
||||
{t.ACTION_PANEL.BUTTONS.ESCAPE}
|
||||
</button>
|
||||
|
||||
@@ -17,7 +17,6 @@ interface EventLogProps {
|
||||
export default function EventLog({ logs }: EventLogProps) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Auto-Scroll nach unten bei neuen Einträgen
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||
@@ -39,14 +38,12 @@ export default function EventLog({ logs }: EventLogProps) {
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-6 right-6 w-full max-w-md h-48 bg-black/80 border border-yellow-500/30 rounded-lg shadow-2xl overflow-hidden flex flex-col backdrop-blur-md z-40">
|
||||
{/* Header */}
|
||||
<div className="bg-yellow-500/10 px-4 py-1 border-b border-yellow-500/20 flex justify-between items-center">
|
||||
<span className="text-[10px] font-mono text-yellow-500 uppercase tracking-widest">
|
||||
{t.EVENT_LOG.TITLE}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Scrollable Content */}
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="flex-1 overflow-y-auto p-4 font-mono text-sm scrollbar-thin scrollbar-thumb-yellow-500/20"
|
||||
|
||||
+19
-15
@@ -2,22 +2,24 @@
|
||||
|
||||
import React from "react";
|
||||
import { Planet } from "@/types/swapi";
|
||||
import { t } from "@/content/locales"; // Import der Locales
|
||||
import { t } from "@/content/locales";
|
||||
|
||||
interface GalaxyMapProps {
|
||||
planets: Planet[];
|
||||
onSelectLocation: (planet: Planet) => void;
|
||||
currentLocationId?: string;
|
||||
selectedPlanetId?: string;
|
||||
}
|
||||
|
||||
export default function GalaxyMap({
|
||||
planets,
|
||||
onSelectLocation,
|
||||
currentLocationId,
|
||||
selectedPlanetId,
|
||||
}: GalaxyMapProps) {
|
||||
return (
|
||||
<div className="relative w-full max-w-4xl h-[500px] bg-[#080808] border-2 border-yellow-500/20 rounded-xl overflow-hidden shadow-[inset_0_0_50px_rgba(0,0,0,1)] z-10">
|
||||
{/* Hintergrund-Raster (Grid) */}
|
||||
<div className="relative w-full max-w-4xl h-[500px] bg-[#080808] border-2 border-yellow-300 rounded-xl overflow-hidden shadow-[inset_0_0_50px_rgba(0,0,0,1)] z-10">
|
||||
{/* Background-Grid */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-10 pointer-events-none"
|
||||
style={{
|
||||
@@ -27,40 +29,43 @@ export default function GalaxyMap({
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Die Planeten (Nodes) aus der API */}
|
||||
{planets.map((planet) => {
|
||||
const isCurrent = planet.id === currentLocationId;
|
||||
const isSelected = planet.id === selectedPlanetId;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={planet.id}
|
||||
onClick={() => !isCurrent && onSelectLocation(planet)}
|
||||
disabled={isCurrent}
|
||||
className={`absolute group transform -translate-x-1/2 -translate-y-1/2 transition-all
|
||||
${isCurrent ? "cursor-default" : "cursor-pointer hover:scale-110"}`}
|
||||
onClick={() => onSelectLocation(planet)}
|
||||
className="absolute group transform -translate-x-1/2 -translate-y-1/2 transition-all cursor-pointer hover:scale-110"
|
||||
style={{ left: `${planet.x}%`, top: `${planet.y}%` }}
|
||||
>
|
||||
{/* Planet Visuell */}
|
||||
<div
|
||||
className={`w-4 h-4 rounded-full border-2 transition-all duration-300
|
||||
${
|
||||
isCurrent
|
||||
? "bg-blue-500 border-white shadow-[0_0_15px_#3b82f6] scale-125"
|
||||
: "bg-yellow-500/20 border-yellow-500 group-hover:bg-yellow-500 group-hover:shadow-[0_0_10px_#eab308]"
|
||||
: isSelected
|
||||
? "bg-yellow-500 border-yellow-500 shadow-[0_0_10px_#eab308]"
|
||||
: "bg-yellow-500/20 border-yellow-500 group-hover:bg-yellow-500 group-hover:shadow-[0_0_10px_#eab308]"
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Label */}
|
||||
<div className="absolute top-6 left-1/2 -translate-x-1/2 whitespace-nowrap">
|
||||
<span
|
||||
className={`text-[10px] font-mono uppercase tracking-tighter px-1 rounded bg-black/50
|
||||
${isCurrent ? "text-blue-400 font-bold" : "text-yellow-500/60 group-hover:text-yellow-500"}`}
|
||||
${
|
||||
isCurrent
|
||||
? "text-blue-400 font-bold"
|
||||
: isSelected
|
||||
? "text-yellow-500"
|
||||
: "text-yellow-500/60 group-hover:text-yellow-500"
|
||||
}`}
|
||||
>
|
||||
{planet.name}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Pulsierender Ring für aktuellen Standort */}
|
||||
{isCurrent && (
|
||||
<div className="absolute -inset-2 border border-blue-500/50 rounded-full animate-ping pointer-events-none" />
|
||||
)}
|
||||
@@ -68,11 +73,10 @@ export default function GalaxyMap({
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Scan-Linie Effekt */}
|
||||
<div className="absolute top-0 left-0 w-full h-1 bg-yellow-500/5 opacity-20 animate-scan pointer-events-none" />
|
||||
|
||||
{/* Info Overlay */}
|
||||
<div className="absolute bottom-4 left-4 text-[10px] text-yellow-500/40 font-mono uppercase pointer-events-none bg-black/40 p-2 rounded">
|
||||
<div className="absolute bottom-4 left-4 text-[10px] text-red-500 font-mono uppercase pointer-events-none bg-black/40 p-2 rounded">
|
||||
{t.GALAXY_MAP.SCAN_STATUS} <br />
|
||||
{t.GALAXY_MAP.OBJECTS_DETECTED(planets.length)} <br />
|
||||
{t.GALAXY_MAP.HOLONET_STATUS}
|
||||
|
||||
@@ -14,8 +14,39 @@ export default function GameOverScreen({
|
||||
}: GameOverScreenProps) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/90 backdrop-blur-xl animate-in fade-in duration-1000">
|
||||
<div className="text-center space-y-6 p-12 border-2 border-red-600 bg-red-950/20 rounded-lg shadow-[0_0_50px_rgba(220,38,38,0.5)]">
|
||||
<h1 className="text-6xl font-black text-red-600 tracking-tighter uppercase italic">
|
||||
{/* simple starfield background */}
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
<div
|
||||
className="absolute inset-0 opacity-30"
|
||||
style={{
|
||||
backgroundImage: `
|
||||
radial-gradient(1px 1px at 20px 30px, #fff, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 40px 70px, #fff, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 50px 160px, #ddd, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 90px 40px, #fff, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 130px 80px, #fff, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 160px 120px, #ddd, 100%, rgba(0,0,0,0))
|
||||
`,
|
||||
backgroundSize: "200px 200px",
|
||||
backgroundRepeat: "repeat",
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="absolute inset-0 opacity-50"
|
||||
style={{
|
||||
backgroundImage: `
|
||||
radial-gradient(1.5px 1.5px at 100px 150px, #fff, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1.5px 1.5px at 300px 400px, #ddd, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1.5px 1.5px at 500px 100px, #fff, 100%, rgba(0,0,0,0))
|
||||
`,
|
||||
backgroundSize: "450px 450px",
|
||||
backgroundRepeat: "repeat",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-center space-y-6 p-12 border-2 border-red-600 bg-red-950/20 rounded-lg bg-black/80 backdrop-blur-sm shadow-[0_0_50px_rgba(220,38,38,0.5)]">
|
||||
<h1 className="font-starjedi text-6xl text-red-600">
|
||||
{t.GAME_OVER.TITLE}
|
||||
</h1>
|
||||
<p className="text-gray-400 font-mono">{t.GAME_OVER.SUBTITLE}</p>
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ export default function HUD({
|
||||
return (
|
||||
<header className="fixed top-0 left-0 w-full bg-black border-b-2 border-yellow-500/50 p-4 font-mono text-yellow-500 z-50 shadow-[0_0_15px_rgba(234,179,8,0.2)]">
|
||||
<div className="max-w-7xl mx-auto flex flex-wrap justify-between items-center gap-4">
|
||||
{/* LORE & LOCATION */}
|
||||
{/* LOCATION */}
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs uppercase opacity-70">
|
||||
{t.HUD.SECTOR_LABEL}
|
||||
@@ -49,7 +49,7 @@ export default function HUD({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ECONOMY (CREDITS) */}
|
||||
{/* CREDITS */}
|
||||
<div className="flex flex-col items-end">
|
||||
<span className="text-xs uppercase opacity-70">
|
||||
{t.HUD.GOAL_LABEL}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { t } from "@/content/locales";
|
||||
|
||||
interface PowerSettings {
|
||||
engines: number;
|
||||
lasers: number;
|
||||
shields: number;
|
||||
}
|
||||
|
||||
interface PowerDistributorProps {
|
||||
power: PowerSettings;
|
||||
onUpdatePower: (newSettings: PowerSettings) => void;
|
||||
disabled?: boolean;
|
||||
isRerouting?: boolean;
|
||||
}
|
||||
|
||||
export default function PowerDistributor({
|
||||
power,
|
||||
onUpdatePower,
|
||||
disabled = false,
|
||||
isRerouting = false,
|
||||
}: PowerDistributorProps) {
|
||||
const handleBoost = (system: keyof PowerSettings) => {
|
||||
if (disabled || isRerouting) return;
|
||||
|
||||
const newSettings = { ...power };
|
||||
if (power[system] <= 4) {
|
||||
const otherSystems = (
|
||||
Object.keys(power) as Array<keyof PowerSettings>
|
||||
).filter((s) => s !== system);
|
||||
|
||||
if (power[otherSystems[0]] > 0 && power[otherSystems[1]] > 0) {
|
||||
newSettings[system] += 2;
|
||||
newSettings[otherSystems[0]] -= 1;
|
||||
newSettings[otherSystems[1]] -= 1;
|
||||
onUpdatePower(newSettings);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
if (disabled || isRerouting) return;
|
||||
onUpdatePower({ engines: 3, lasers: 3, shields: 3 });
|
||||
};
|
||||
|
||||
const systems = [
|
||||
{
|
||||
key: "engines" as const,
|
||||
label: t.SYSTEMS.ENGINES,
|
||||
color: "bg-blue-500",
|
||||
glow: "shadow-[0_0_15px_#3b82f6]",
|
||||
},
|
||||
{
|
||||
key: "lasers" as const,
|
||||
label: t.SYSTEMS.LASERS,
|
||||
color: "bg-red-600",
|
||||
glow: "shadow-[0_0_15px_#dc2626]",
|
||||
},
|
||||
{
|
||||
key: "shields" as const,
|
||||
label: t.SYSTEMS.SHIELDS,
|
||||
color: "bg-green-500",
|
||||
glow: "shadow-[0_0_15px_#22c55e]",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="bg-[#080808]/90 border-2 border-blue-500/60 p-4 rounded-xl w-full h-full flex flex-col justify-between backdrop-blur-md relative overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h3 className="font-aurebesh text-green-500 text-lg tracking-wider">
|
||||
{t.SYSTEMS.POWER_MANAGEMENT}
|
||||
</h3>
|
||||
<div className="text-[10px] font-mono text-green-500/80 uppercase">
|
||||
{t.SYSTEMS.SUB_LABEL}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleReset}
|
||||
disabled={disabled || isRerouting}
|
||||
className="text-[10px] font-mono px-3 py-1 border border-yellow-500/30 text-yellow-500/80 hover:text-yellow-500 hover:border-yellow-500 transition-all rounded uppercase disabled:opacity-60"
|
||||
>
|
||||
{t.SYSTEMS.RESET}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4 flex-grow">
|
||||
{systems.map((sys) => (
|
||||
<div key={sys.key} className="flex flex-col items-center group">
|
||||
<button
|
||||
onClick={() => handleBoost(sys.key)}
|
||||
disabled={disabled || isRerouting || power[sys.key] >= 6}
|
||||
className="w-full mb-3 py-2 bg-yellow-500/5 border border-yellow-500/40 hover:bg-yellow-500/20 text-yellow-500 rounded-lg text-sm transition-all active:scale-95 disabled:opacity-50 group-hover:border-yellow-500/50"
|
||||
>
|
||||
{t.SYSTEMS.BOOST}
|
||||
</button>
|
||||
|
||||
{/* Visual Bar */}
|
||||
<div className="flex flex-col-reverse gap-1.5 h-40 w-full bg-black/60 border border-white/40 p-1.5 rounded-md shadow-inner">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`h-full w-full rounded-sm transition-all duration-500 ${
|
||||
i < power[sys.key]
|
||||
? `${sys.color} ${sys.glow}`
|
||||
: "bg-white/5"
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<span className="mt-3 text-[10px] font-mono uppercase text-yellow-500 tracking-widest">
|
||||
{sys.label}
|
||||
</span>
|
||||
<span className="text-[10px] font-mono text-white/60">
|
||||
LVL {power[sys.key]}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Overlay */}
|
||||
{isRerouting && (
|
||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-[2px] flex items-center justify-center z-10">
|
||||
<div className="bg-black border border-red-500/50 text-red-500 text-[10px] font-mono px-4 py-2 rounded shadow-[0_0_20px_rgba(239,68,68,0.3)] animate-pulse uppercase tracking-[0.2em]">
|
||||
{t.ENGINE.SYSTEMS.REROUTING}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+56
-30
@@ -9,49 +9,76 @@ interface StartScreenProps {
|
||||
|
||||
export default function StartScreen({ onStart }: StartScreenProps) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-[110] flex items-center justify-center bg-[#050505] overflow-hidden">
|
||||
{/* Hintergrund-Deko */}
|
||||
<div className="absolute inset-0 opacity-20 bg-[radial-gradient(circle_at_center,_#eab308_0%,_transparent_70%)]" />
|
||||
<div className="fixed inset-0 z-[110] flex items-center justify-center bg-black overflow-hidden">
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
<div
|
||||
className="absolute inset-0 opacity-30"
|
||||
style={{
|
||||
backgroundImage: `
|
||||
radial-gradient(1px 1px at 20px 30px, #fff, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 40px 70px, #fff, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 50px 160px, #ddd, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 90px 40px, #fff, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 130px 80px, #fff, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 160px 120px, #ddd, 100%, rgba(0,0,0,0))
|
||||
`,
|
||||
backgroundSize: "200px 200px",
|
||||
backgroundRepeat: "repeat",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="max-w-2xl w-full mx-4 relative z-10 text-center space-y-8 p-10 border border-yellow-500/20 bg-black/60 backdrop-blur-md rounded-2xl shadow-[0_0_100px_rgba(234,179,8,0.1)]">
|
||||
<div
|
||||
className="absolute inset-0 opacity-50"
|
||||
style={{
|
||||
backgroundImage: `
|
||||
radial-gradient(1.5px 1.5px at 100px 150px, #fff, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1.5px 1.5px at 300px 400px, #ddd, 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1.5px 1.5px at 500px 100px, #fff, 100%, rgba(0,0,0,0))
|
||||
`,
|
||||
backgroundSize: "450px 450px",
|
||||
backgroundRepeat: "repeat",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 opacity-10 bg-[radial-gradient(circle_at_center,_#eab308_0%,_transparent_70%)]" />
|
||||
|
||||
<div className="max-w-2xl w-full mx-4 relative z-10 text-center space-y-8 p-10 border-2 border-yellow-500 rounded-2xl bg-black/80 backdrop-blur-sm shadow-[0_0_50px_rgba(0,0,0,0.5)]">
|
||||
<div className="space-y-2">
|
||||
<h1 className="font-starjedi text-6xl text-white">
|
||||
<h1 className="font-starjedi text-6xl text-yellow-500">
|
||||
{t.UI.GAME_TITLE}
|
||||
</h1>
|
||||
<p className="text-yellow-500/60 font-mono tracking-[0.3em] uppercase text-xs">
|
||||
<p className="text-yellow-500 font-mono tracking-[0.3em] uppercase text-xs">
|
||||
{t.START_SCREEN.SUBTITLE}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 text-gray-400 font-light leading-relaxed">
|
||||
<p>
|
||||
<div className="space-y-6 text-yellow-500 leading-relaxed">
|
||||
<p className="text-sm tracking-wider font-medium">
|
||||
{t.START_SCREEN.INTRO_TEXT}
|
||||
<span className="text-white font-medium">
|
||||
{" "}
|
||||
<span className="block mt-1 text-yellow-500 font-black">
|
||||
{t.START_SCREEN.MISSION_HIGHLIGHT}
|
||||
</span>
|
||||
</p>
|
||||
<ul className="text-sm space-y-2 inline-block text-left border-l-2 border-yellow-500/30 pl-6">
|
||||
|
||||
<ul className="text-[10px] space-y-3 inline-block text-left border-l-2 border-yellow-500/50 pl-6 tracking-[0.2em]">
|
||||
<li>
|
||||
•{" "}
|
||||
<strong className="text-yellow-500/80">
|
||||
{t.START_SCREEN.FEATURE_NAV_TITLE}
|
||||
</strong>{" "}
|
||||
{t.START_SCREEN.FEATURE_NAV_DESC}
|
||||
• <strong>{t.START_SCREEN.FEATURE_NAV_TITLE}</strong>{" "}
|
||||
<span className="opacity-70">
|
||||
{t.START_SCREEN.FEATURE_NAV_DESC}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
•{" "}
|
||||
<strong className="text-yellow-500/80">
|
||||
{t.START_SCREEN.FEATURE_RISK_TITLE}
|
||||
</strong>{" "}
|
||||
{t.START_SCREEN.FEATURE_RISK_DESC}
|
||||
• <strong>{t.START_SCREEN.FEATURE_RISK_TITLE}</strong>{" "}
|
||||
<span className="opacity-70">
|
||||
{t.START_SCREEN.FEATURE_RISK_DESC}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
•{" "}
|
||||
<strong className="text-yellow-500/80">
|
||||
{t.START_SCREEN.FEATURE_COMBAT_TITLE}
|
||||
</strong>{" "}
|
||||
{t.START_SCREEN.FEATURE_COMBAT_DESC}
|
||||
• <strong>{t.START_SCREEN.FEATURE_COMBAT_TITLE}</strong>{" "}
|
||||
<span className="opacity-70">
|
||||
{t.START_SCREEN.FEATURE_COMBAT_DESC}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -59,13 +86,12 @@ export default function StartScreen({ onStart }: StartScreenProps) {
|
||||
<div className="pt-6">
|
||||
<button
|
||||
onClick={onStart}
|
||||
className="group relative px-12 py-5 bg-yellow-500 text-black font-black uppercase tracking-[0.2em] transition-all hover:scale-105 hover:shadow-[0_0_40px_rgba(234,179,8,0.4)] overflow-hidden"
|
||||
className="px-12 py-5 border-2 border-yellow-500 text-yellow-500 font-black uppercase tracking-[0.3em] transition-all hover:bg-yellow-500 hover:text-black active:scale-95 shadow-[0_0_20px_rgba(234,179,8,0.2)]"
|
||||
>
|
||||
<span className="relative z-10">{t.UI.START_BUTTON}</span>
|
||||
<div className="absolute inset-0 bg-white opacity-0 group-hover:opacity-20 transition-opacity" />
|
||||
{t.UI.START_BUTTON}
|
||||
</button>
|
||||
|
||||
<p className="mt-4 text-[10px] text-gray-600 uppercase tracking-widest">
|
||||
<p className="mt-6 text-[9px] text-yellow-500/70 uppercase tracking-[0.3em]">
|
||||
{t.START_SCREEN.FOOTER_ENGINE}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { t } from "@/content/locales";
|
||||
|
||||
interface VictoryScreenProps {
|
||||
credits: number;
|
||||
onRestart: () => void;
|
||||
}
|
||||
|
||||
export default function VictoryScreen({
|
||||
credits,
|
||||
onRestart,
|
||||
}: VictoryScreenProps) {
|
||||
const creditsBlue = "#4bd5ee";
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[120] flex items-center justify-center bg-black overflow-hidden">
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
<div
|
||||
className="absolute inset-0 opacity-30"
|
||||
style={{
|
||||
backgroundImage: `
|
||||
radial-gradient(1px 1px at 20px 30px, #fff 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 40px 70px, #fff 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 50px 160px, #ddd 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1px 1px at 90px 40px, #fff 100%, rgba(0,0,0,0))
|
||||
`,
|
||||
backgroundSize: "200px 200px",
|
||||
backgroundRepeat: "repeat",
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="absolute inset-0 opacity-50"
|
||||
style={{
|
||||
backgroundImage: `
|
||||
radial-gradient(1.5px 1.5px at 100px 150px, #fff 100%, rgba(0,0,0,0)),
|
||||
radial-gradient(1.5px 1.5px at 300px 400px, #ddd 100%, rgba(0,0,0,0))
|
||||
`,
|
||||
backgroundSize: "450px 450px",
|
||||
backgroundRepeat: "repeat",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute inset-0 opacity-10"
|
||||
style={{
|
||||
background: `radial-gradient(circle at center, ${creditsBlue} 0%, transparent 70%)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="max-w-2xl w-full mx-4 relative z-10 text-center space-y-8 p-10 border-2 rounded-2xl bg-black/80 backdrop-blur-sm shadow-[0_0_50px_rgba(0,0,0,0.5)]"
|
||||
style={{ borderColor: creditsBlue }}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<h1 className="font-starjedi text-6xl" style={{ color: creditsBlue }}>
|
||||
{t.VICTORY.TITLE}
|
||||
</h1>
|
||||
<p
|
||||
className="font-mono tracking-[0.3em] uppercase text-xs opacity-70"
|
||||
style={{ color: creditsBlue }}
|
||||
>
|
||||
{t.VICTORY.SUBTITLE}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="space-y-6 leading-relaxed"
|
||||
style={{ color: creditsBlue }}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm tracking-wider font-medium uppercase">
|
||||
{t.VICTORY.MESSAGE}
|
||||
</p>
|
||||
<p className="text-4xl font-black tracking-tighter">
|
||||
{credits.toLocaleString()} CR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="text-[10px] py-4 border-y border-opacity-20 inline-block px-8 tracking-[0.2em] uppercase italic"
|
||||
style={{ borderColor: creditsBlue }}
|
||||
>
|
||||
The galaxy will remember your name.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-6">
|
||||
<button
|
||||
onClick={onRestart}
|
||||
className="px-12 py-5 border-2 font-black uppercase tracking-[0.3em] transition-all hover:text-black active:scale-95"
|
||||
style={{
|
||||
borderColor: creditsBlue,
|
||||
color: creditsBlue,
|
||||
}}
|
||||
onMouseOver={(e) => {
|
||||
e.currentTarget.style.backgroundColor = creditsBlue;
|
||||
e.currentTarget.style.color = "black";
|
||||
}}
|
||||
onMouseOut={(e) => {
|
||||
e.currentTarget.style.backgroundColor = "transparent";
|
||||
e.currentTarget.style.color = creditsBlue;
|
||||
}}
|
||||
>
|
||||
{t.VICTORY.BUTTON}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user