game mode abilities

This commit is contained in:
tresabhi committed 2026-10-11 13:28:38 -05:00
1 parent 5c482e0bac
commit 53eebc7a43
6 files changed
+164 -122

No files matched your search

+4
View File
@@ -98,6 +98,10 @@
}
},
"game_modes": {
"title": "Game mode abilities"
},
"navigation": {
"tank": "Tank",
"characteristics": "Characteristics"
@@ -0,0 +1,64 @@
.section {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-5);
padding: var(--space-4);
.cards {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: var(--space-4);
width: 100%;
.card {
width: 16rem;
height: 20rem;
display: flex;
flex: 0 0 auto;
flex-direction: column;
overflow: hidden;
border-radius: var(--space-3);
background-size: cover;
background-position: center;
.banner {
flex: 1;
background-size: cover;
background-position: center;
}
.info {
display: flex;
min-height: 6.125rem;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-2);
padding: var(--space-3);
backdrop-filter: blur(4rem);
-webkit-backdrop-filter: blur(4rem);
.abilities {
display: flex;
flex-direction: column;
align-items: flex-start;
.ability {
display: flex;
align-items: center;
gap: var(--space-1);
white-space: nowrap;
img {
width: 1.5em;
height: 1.5em;
object-fit: contain;
}
}
}
}
}
}
}
@@ -0,0 +1,93 @@
import { alias } from "@blitzkit/core";
import { api } from "../../api/dynamic";
import { DuelModule, useDuel } from "../../hooks/useDuel";
import { useStrings } from "../../hooks/useStrings";
import { useUnwrapper } from "../../hooks/useUnwrapper";
import { Heading } from "../Heading";
import { Text } from "../Text";
import styles from "./index.module.css";
const [gameDefinitions, consumableDefinitions, provisionDefinitions, tanks] =
await Promise.all([
api.game(),
api.consumables(),
api.provisions(),
api.tanks(),
]);
export function GameModeSection() {
const unwrap = useUnwrapper();
const strings = useStrings();
const tank = useDuel("protagonist", DuelModule.Tank);
const roles = Object.entries(tank.roles);
if (roles.length === 0) return null;
return (
<section className={styles.section}>
<Heading size="3">{strings.tanks.game_modes.title}</Heading>
<div className={styles.cards}>
{roles.map(([id, value]) => {
const gameMode = gameDefinitions.gameModes[id];
const role = gameDefinitions.roles[value];
const banner = alias("api", `/gamemodes/${id}/banner.webp`);
return (
<article
key={id}
className={styles.card}
style={{ backgroundImage: `url(${banner})` }}
>
<div
className={styles.banner}
style={{ backgroundImage: `url(${banner})` }}
/>
<div className={styles.info}>
<Text size="major" weight="bold" align="center">
{unwrap(gameMode.name!)}
</Text>
<div className={styles.abilities}>
{role.provisions.map((id) => {
const provisions = provisionDefinitions.provisions[id];
return (
<Ability
key={`provision-${id}`}
icon={alias("api", `/icons/provisions/${id}.webp`)}
name={unwrap(provisions.name!)}
/>
);
})}
{role.consumables.map((id) => {
const consumable = consumableDefinitions.consumables[id];
return (
<Ability
key={`consumable-${id}`}
icon={alias("api", `/icons/consumables/${id}.webp`)}
name={unwrap(consumable.name!)}
/>
);
})}
</div>
</div>
</article>
);
})}
</div>
</section>
);
}
function Ability({ icon, name }: { icon: string; name: string }) {
return (
<Text lowContrast className={styles.ability}>
<img src={icon} alt={name} />
{name}
</Text>
);
}
@@ -1,122 +0,0 @@
import { alias } from "@blitzkit/core";
import { Box, Flex, Heading, Text } from "@radix-ui/themes";
import { api } from "../../api/dynamic";
import { useLocale } from "../../hooks/useLocale";
import { Duel } from "../../stores/duel";
const [gameDefinitions, consumableDefinitions, provisionDefinitions] =
await Promise.all([api.game(), api.consumables(), api.provisions()]);
export function GameModeSection() {
const { unwrap, strings } = useLocale();
const tank = Duel.use((state) => state.protagonist.tank);
const roles = Object.entries(tank.roles);
if (roles.length === 0) return null;
return (
<Flex direction="column" gap="4" align="center">
<Heading size="6">
{strings.website.tools.tankopedia.game_modes.title}
</Heading>
<Flex justify="center" gap="4" wrap="wrap" px="4">
{roles.map(([id, value]) => {
const gameMode = gameDefinitions.gameModes[id];
return (
<Flex
key={id}
width="16rem"
height="20rem"
overflow="hidden"
direction="column"
style={{
borderRadius: "var(--radius-3)",
background: `url(${alias(
"api",
`/gamemodes/${id}/banner.webp`,
)})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
>
<Box
flexGrow="1"
style={{
background: `url(${alias(
"api",
`/gamemodes/${id}/banner.webp`,
)})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
/>
<Flex
p="3"
direction="column"
height="98px"
flexShrink="0"
style={{
backdropFilter: "blur(4rem)",
WebkitBackdropFilter: "blur(4rem)",
}}
align="center"
justify="center"
>
<Text size="4" weight="bold">
{unwrap(gameMode.name!)}
</Text>
<Flex direction="column">
{gameDefinitions.roles[value].provisions.map((id) => {
const provisions = provisionDefinitions.provisions[id];
return (
<Text color="gray" wrap="nowrap" key={id}>
<Flex align="center" gap="1">
<img
style={{
width: "1.5em",
height: "1.5em",
objectFit: "contain",
}}
src={alias("api", `/icons/provisions/${id}.webp`)}
alt={unwrap(provisions.name!)}
/>
{unwrap(provisions.name!)}
</Flex>
</Text>
);
})}
{gameDefinitions.roles[value].consumables.map((id) => {
const consumable = consumableDefinitions.consumables[id];
return (
<Text color="gray" wrap="nowrap" key={id}>
<Flex align="center" gap="1">
<img
style={{
width: "1.5em",
height: "1.5em",
objectFit: "contain",
}}
src={alias("api", `/icons/consumables/${id}.webp`)}
alt={unwrap(consumable.name!)}
/>
{unwrap(consumable.name!)}
</Flex>
</Text>
);
})}
</Flex>
</Flex>
</Flex>
);
})}
</Flex>
</Flex>
);
}
@@ -3,6 +3,7 @@ import { api } from "../../../../../api/dynamic";
import type { ThicknessRange } from "../../../../../components/StaticArmor";
import { TankopediaCharacteristics } from "../../../../../components/TankopediaCharacteristics";
import { TankopediaLoadout } from "../../../../../components/TankopediaLoadout";
import { GameModeSection } from "../../../../../components/GameModeSection";
import { defaultEqualizer } from "../../../../../config/equalizer";
import { withErrorWrapper } from "../../../../../hocs/withErrorWrapper";
import { withLocale } from "../../../../../hocs/withLocale";
@@ -58,6 +59,7 @@ export const Page = withErrorWrapper(
<div className={styles.sandbox}>
{/* {!skeleton && <TankopediaSandbox thicknessRange={thicknessRange} />} */}
<TankopediaCharacteristics characteristics={characteristics} />
<GameModeSection />
</div>
</div>
);