update and polish

This commit is contained in:
StrangeD0s
2026-02-08 17:06:59 +01:00
parent 81a53e441e
commit fcd8cadf43
18 changed files with 915 additions and 354 deletions
+139 -160
View File
@@ -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>