new basic layout

This commit is contained in:
tresabhi committed 2026-06-01 13:48:16 -05:00
1 parent 6aa14d2faf
commit 73de6e3521
6 files changed
+254 -186

No files matched your search

@@ -1,24 +1,37 @@
.characteristics { .characteristics {
--cell-width: 2.5rem;
--group-span: 7;
--inner-padding: var(--space-4);
display: grid;
grid-auto-rows: var(--cell-width);
grid-template-columns: repeat(auto-fill, minmax(var(--cell-width), 1fr));
grid-auto-flow: dense;
padding: var(--space-5); padding: var(--space-5);
gap: var(--space-4);
display: flex;
flex-direction: column;
gap: var(--space-5);
box-sizing: border-box; box-sizing: border-box;
.group { .group {
display: flex; display: grid;
flex-direction: column; grid-template-columns: 1fr;
gap: var(--space-4); grid-column: span var(--group-span);
gap: inherit;
.title-wrapper { .title-wrapper {
background-color: var(--amber-a3);
grid-row: span 1;
padding: 0 var(--inner-padding);
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-4); gap: var(--space-3);
.title { .title {
.title-content { .content {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-2); gap: var(--space-2);
@@ -31,67 +44,60 @@
} }
} }
} }
.separator {
height: var(--space-1);
flex: 1;
background-color: var(--gray-5);
}
} }
.content { .item {
--cell-width: 2rem; padding: 0 var(--inner-padding);
--toy-span: 6;
display: grid; grid-row: span 1;
grid-auto-rows: var(--cell-width);
grid-template-columns: repeat(auto-fill, minmax(var(--cell-width), 1fr));
grid-auto-flow: row dense;
gap: var(--space-4);
.item { display: flex;
grid-row: span 1; flex-direction: column;
grid-column: span var(--toy-span); justify-content: center;
gap: var(--space-2);
.value {
display: flex; display: flex;
flex-direction: column; justify-content: space-between;
justify-content: center; }
gap: var(--space-2);
.value { .ranking {
display: flex; display: flex;
justify-content: space-between; align-items: center;
} gap: var(--space-3);
.ranking { .bar-wrapper {
display: flex; flex: 1;
align-items: center; height: var(--space-1);
gap: var(--space-3);
.bar-wrapper { background-color: var(--gray-3);
flex: 1; border-radius: var(--radius-max);
height: var(--space-1); overflow: hidden;
background-color: var(--gray-3); .bar {
border-radius: var(--radius-max); background-color: var(--jade-9);
overflow: hidden; width: 75%;
height: 100%;
.bar {
background-color: var(--jade-9);
width: 75%;
height: 100%;
}
} }
} }
} }
}
.toy { .toy {
grid-row: span var(--toy-span); grid-row: span var(--group-span);
grid-column: span var(--toy-span); padding: var(--inner-padding);
background-color: var(--gray-3); .content {
border: var(--gray-5) solid var(--space-1); background-color: green;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--gray-2);
outline: var(--gray-5) solid var(--space-1);
border-radius: var(--space-3); border-radius: var(--space-3);
} }
@@ -1,7 +1,6 @@
import { CaretDownIcon, CaretRightIcon } from "@radix-ui/react-icons"; import { GearIcon } from "@radix-ui/react-icons";
import { useCharacteristicRenderer } from "../../hooks/useCharacteristicRenderer"; import { useCharacteristicRenderer } from "../../hooks/useCharacteristicRenderer";
import { useStrings } from "../../hooks/useStrings"; import { useStrings } from "../../hooks/useStrings";
import { Tankopedia } from "../../stores/tankopedia";
import type { CharacteristicOutput } from "../../tankopedia/characteristics"; import type { CharacteristicOutput } from "../../tankopedia/characteristics";
import { import {
characteristicsOrder, characteristicsOrder,
@@ -9,7 +8,6 @@ import {
type CharacteristicsGroup, type CharacteristicsGroup,
} from "../../tankopedia/characteristicsOrder"; } from "../../tankopedia/characteristicsOrder";
import type { ComputedCharacteristics } from "../../tankopedia/computeCharacteristics"; import type { ComputedCharacteristics } from "../../tankopedia/computeCharacteristics";
import { Heading } from "../Heading";
import { Text } from "../Text"; import { Text } from "../Text";
import styles from "./index.module.css"; import styles from "./index.module.css";
@@ -35,35 +33,35 @@ interface GroupProps {
} }
function Group({ group, characteristics }: GroupProps) { function Group({ group, characteristics }: GroupProps) {
const isOpen = Tankopedia.use((state) => state.groups[group.group]);
const strings = useStrings(); const strings = useStrings();
const toys = group.order.filter((item) => "toy" in item).length;
const items = group.order.filter((item) => "name" in item).length;
const span = `calc(${toys} * var(--group-span) + ${items} + 1)`;
return ( return (
<div className={styles.group}> <div
className={styles.group}
style={{
gridRow: `span ${span}`,
gridTemplateRows: `repeat(${span}, 1fr)`,
}}
>
<div className={styles["title-wrapper"]}> <div className={styles["title-wrapper"]}>
<Heading className={styles.title} size="3"> <Text className={styles.title}>
<div className={styles["title-content"]}> <div className={styles.content}>
{isOpen ? <CaretDownIcon /> : <CaretRightIcon />} {group.group}
{ <GearIcon />
(strings.tanks.characteristics.titles as Record<string, string>)[
group.group
]
}
</div> </div>
</Heading> </Text>
<div className={styles.separator} />
</div> </div>
<div className={styles.content}> {group.order.map((item) =>
{group.order.map((item) => "toy" in item ? (
"toy" in item ? ( <Toy {...item} />
<Toy {...item} /> ) : (
) : ( <Item config={item} value={characteristics[item.name]} />
<Item config={item} value={characteristics[item.name]} /> ),
), )}
)}
</div>
</div> </div>
); );
} }
@@ -80,13 +78,7 @@ function Item({ config, value }: ItemProps) {
return ( return (
<span className={styles.item}> <span className={styles.item}>
<div className={styles.value}> <div className={styles.value}>
<Text lowContrast> <Text lowContrast>{config.name}</Text>
{
(strings.tanks.characteristics.items as Record<string, string>)[
config.name
]
}
</Text>
<Text>{renderCharacteristic(value, config)}</Text> <Text>{renderCharacteristic(value, config)}</Text>
</div> </div>
@@ -108,5 +100,11 @@ interface ToyProps {
} }
function Toy({ toy }: ToyProps) { function Toy({ toy }: ToyProps) {
return <span className={styles.toy}></span>; return (
<div className={styles.toy}>
<div className={styles.content}>
<Text lowContrast>{toy} toy</Text>
</div>
</div>
);
} }
@@ -1,4 +1,4 @@
import { Fragment, useMemo } from "react"; import { useMemo } from "react";
import { api } from "../../../../api/dynamic"; import { api } from "../../../../api/dynamic";
import { TankopediaCharacteristics } from "../../../../components/TankopediaCharacteristics"; import { TankopediaCharacteristics } from "../../../../components/TankopediaCharacteristics";
import { TankopediaLoadout } from "../../../../components/TankopediaLoadout"; import { TankopediaLoadout } from "../../../../components/TankopediaLoadout";
@@ -6,10 +6,8 @@ import { TankopediaSandbox } from "../../../../components/TankopediaSandbox";
import { withErrorWrapper } from "../../../../hocs/withErrorWrapper"; import { withErrorWrapper } from "../../../../hocs/withErrorWrapper";
import { withLocale } from "../../../../hocs/withLocale"; import { withLocale } from "../../../../hocs/withLocale";
import { useAwait } from "../../../../hooks/useAwait"; import { useAwait } from "../../../../hooks/useAwait";
import { useCharacteristicRenderer } from "../../../../hooks/useCharacteristicRenderer";
import { useEquipment } from "../../../../hooks/useEquipment"; import { useEquipment } from "../../../../hooks/useEquipment";
import { Tankopedia } from "../../../../stores/tankopedia"; import { Tankopedia } from "../../../../stores/tankopedia";
import { characteristicsOrder } from "../../../../tankopedia/characteristicsOrder";
import { computeCharacteristics } from "../../../../tankopedia/computeCharacteristics"; import { computeCharacteristics } from "../../../../tankopedia/computeCharacteristics";
import styles from "./_index.module.css"; import styles from "./_index.module.css";
@@ -50,62 +48,3 @@ export const Page = withErrorWrapper(
); );
}), }),
); );
export const _Page = withErrorWrapper(
withLocale<PageProps>(({ id }) => {
const protagonistTank = useAwait(() => api.tank(id), `tank-${id}`);
Tankopedia.useInitialization(protagonistTank);
const renderCharacteristic = useCharacteristicRenderer();
const protagonist = Tankopedia.use((state) => state.protagonist);
const protagonistEquipment = useEquipment(protagonistTank.tank!);
const { characteristics } = useMemo(
() =>
computeCharacteristics(
protagonistTank,
protagonistEquipment,
protagonist,
),
[protagonist],
);
return (
<>
<h1>{protagonist.id}</h1>
<br />
<TankopediaLoadout characteristics={characteristics} />
<h2>characteristics</h2>
{characteristicsOrder.map((group) => {
return (
<Fragment key={group.group}>
<h3>{group.group}</h3>
{group.order.map((item) => {
if ("toy" in item) {
return <span key={`toy-${item.toy}`}>toy: {item.toy}</span>;
}
const value = characteristics[item.name];
if (value == null) return null;
return (
<span key={`characteristic-${item.name}`}>
{item.name}: {renderCharacteristic(value, item)}
</span>
);
})}
</Fragment>
);
})}
</>
);
}),
);
@@ -23,6 +23,7 @@ const { id, locale } = Astro.props;
<ColorScale color="red" /> <ColorScale color="red" />
<ColorScale color="jade" /> <ColorScale color="jade" />
<ColorScale color="amber" /> <ColorScale color="amber" />
<ColorScale color="amber" alpha />
<Page {locale} {id} client:load /> <Page {locale} {id} client:load />
</BaseLayout> </BaseLayout>
@@ -419,4 +419,60 @@ export const characteristics = {
compensation({ tank }) { compensation({ tank }) {
return tank.compensation!.compensation?.currency_reward?.amount ?? null; return tank.compensation!.compensation?.currency_reward?.amount ?? null;
}, },
purchase_price() {
return -Infinity;
},
upgrade_price() {
return -Infinity;
},
research_level() {
return -Infinity;
},
crew_xp_multiplier() {
return -Infinity;
},
clipping_potential() {
return -Infinity;
},
burst_size() {
return -Infinity;
},
intra_burst() {
return -Infinity;
},
dispersion_angle() {
return -Infinity;
},
gun_yaw_range() {
return -Infinity;
},
brake_force() {
return -Infinity;
},
gun_traverse_speed() {
return -Infinity;
},
fire_health_burn() {
return -Infinity;
},
ramming_resistance() {
return -Infinity;
},
width() {
return -Infinity;
},
} satisfies Record<string, Characteristic>; } satisfies Record<string, Characteristic>;
@@ -29,19 +29,25 @@ export interface CharacteristicRenderConfig {
}) => ReactNode; }) => ReactNode;
} }
export interface ToyRenderConfig {
toy: string;
}
export type CharacteristicsGroup = { export type CharacteristicsGroup = {
group: string; group: string;
order: ( render: CharacteristicsGroupRender;
| CharacteristicRenderConfig order: (CharacteristicRenderConfig | ToyRenderConfig)[];
| {
toy: string;
}
)[];
}; };
export enum CharacteristicsGroupRender {
Flashy,
Statistical,
}
export const characteristicsOrder: CharacteristicsGroup[] = [ export const characteristicsOrder: CharacteristicsGroup[] = [
{ {
group: "meta", group: "meta",
render: CharacteristicsGroupRender.Flashy,
order: [ order: [
{ {
name: "name", name: "name",
@@ -80,15 +86,29 @@ export const characteristicsOrder: CharacteristicsGroup[] = [
return strings.tiers[name as keyof typeof strings.tiers]; return strings.tiers[name as keyof typeof strings.tiers];
}, },
}, },
{ name: "compensation" },
{ name: "id" }, { name: "id" },
{ name: "slug" }, { name: "slug" },
], ],
}, },
{ {
group: "firepower", group: "economics",
render: CharacteristicsGroupRender.Statistical,
order: [
{ name: "purchase_price" },
{ name: "compensation" },
{ name: "upgrade_price" },
{ name: "research_level" },
{ name: "crew_xp_multiplier" },
// TODO: investigate ATTRIBUTE_NAME_CREW_XP_MULTIPLIER
],
},
{
group: "gun",
render: CharacteristicsGroupRender.Statistical,
order: [ order: [
{ toy: "reload" },
{ {
name: "gun_type", name: "gun_type",
render({ output }) { render({ output }) {
@@ -96,75 +116,123 @@ export const characteristicsOrder: CharacteristicsGroup[] = [
}, },
}, },
{ name: "dpm", decimals: 0, units: "hp_min" }, { name: "dpm", decimals: 0, units: "hp_min" },
{ name: "damage", units: "hp" },
{ name: "module_damage", units: "hp" },
{ name: "reload", decimals: 2, units: "s" }, { name: "reload", decimals: 2, units: "s" },
{ name: "reloads", decimals: 2, units: "s" }, { name: "reloads", decimals: 2, units: "s" },
{ name: "damage", units: "hp" }, { toy: "reload" },
{ name: "module_damage", units: "hp" }, ],
},
{
group: "clip",
render: CharacteristicsGroupRender.Statistical,
order: [
{ name: "clipping_potential" },
{ name: "clip_size" }, { name: "clip_size" },
{ name: "optimal_shell" },
{ name: "intra_clip", decimals: 2, units: "s" }, { name: "intra_clip", decimals: 2, units: "s" },
// { name: "burst_size" }, // TODO: implement burst size { name: "burst_size" },
// { name: 'intra_burst', decimals: 2, units: "s" }, // TODO: implement intra burst { name: "intra_burst", decimals: 2, units: "s" },
// TODO: investigate ATTRIBUTE_NAME_SHELLS_SLOTS // TODO: investigate ATTRIBUTE_NAME_SHELLS_SLOTS
// TODO: investigate ATTRIBUTE_NAME_RELOAD_SPEED_SCALE // TODO: investigate ATTRIBUTE_NAME_RELOAD_SPEED_SCALE
// TODO: investigate ATTRIBUTE_NAME_AVERAGE_THICKNESS // TODO: investigate ATTRIBUTE_NAME_AVERAGE_THICKNESS
// TODO: investigate ATTRIBUTE_NAME_TRACER_SHELLS_OBSERVATION_TIMEOUT
],
},
// { toy: "ricochet" }, {
group: "shell",
render: CharacteristicsGroupRender.Statistical,
order: [
{ name: "penetration", units: "mm" }, { name: "penetration", units: "mm" },
{ name: "shell_velocity", units: "m_s" },
{ name: "shell_range", units: "m" },
{ name: "shell_capacity" },
{ name: "caliber", units: "mm" }, { name: "caliber", units: "mm" },
{ name: "normalization", units: "deg" }, { name: "normalization", units: "deg" },
{ name: "ricochet", units: "deg" }, { name: "ricochet", units: "deg" },
{ name: "shell_velocity", units: "m_s" }, { toy: "ricochet" },
{ name: "shell_range", units: "m" },
{ name: "shell_capacity" },
// { toy: "aim_time" },
{ name: "aim_time", decimals: 1, units: "s" },
{ name: "dispersion", decimals: 3, units: "m" },
// { name: "dispersion_angle", decimals: 3, units: "m" }, // TODO: implement dispersion angle
// { toy: "flexibility" },
{ name: "gun_depression", decimals: 0, units: "deg" },
{ name: "gun_elevation", decimals: 0, units: "deg" },
// { name: "gun_yaw_range", decimals: 0, units: "deg" }, // TODO: implement gun yaw range
], ],
}, },
{
group: "accuracy",
render: CharacteristicsGroupRender.Statistical,
order: [
{ name: "aim_time", decimals: 1, units: "s" },
{ name: "dispersion", decimals: 3, units: "m" },
{ name: "dispersion_angle", decimals: 3, units: "m" },
{ toy: "aim_time" },
],
},
{
group: "flexibility",
render: CharacteristicsGroupRender.Statistical,
order: [
{ name: "gun_depression", decimals: 0, units: "deg" },
{ name: "gun_elevation", decimals: 0, units: "deg" },
{ name: "gun_yaw_range", decimals: 0, units: "deg" },
{ toy: "flexibility" },
],
},
{ {
group: "maneuverability", group: "maneuverability",
render: CharacteristicsGroupRender.Statistical,
order: [ order: [
// { toy: "traverse" },
{ name: "speed_forward", units: "km_h" }, { name: "speed_forward", units: "km_h" },
{ name: "speed_backward", units: "km_h" }, { name: "speed_backward", units: "km_h" },
{ name: "engine_power", units: "bhp" }, { name: "engine_power", units: "bhp" },
// { name: "brake_force", units: "N" }, // TODO: implement brake force { name: "brake_force", units: "N" },
{ name: "weight", decimals: 1, units: "t" }, { name: "weight", decimals: 1, units: "t" },
{ name: "terrain_coefficient" }, { name: "terrain_coefficient" },
{ name: "power_to_weight", decimals: 1, units: "bhp_t" }, { name: "power_to_weight", decimals: 1, units: "bhp_t" },
{ name: "hull_traverse_speed", units: "deg_s" }, { name: "hull_traverse_speed", units: "deg_s" },
{ name: "turret_traverse_speed", units: "deg_s" }, { name: "turret_traverse_speed", units: "deg_s" },
// { name: "gun_traverse_speed", units: "deg_s" }, // TODO: implement gun traverse speed { name: "gun_traverse_speed", units: "deg_s" },
{ toy: "traverse" },
// TODO: investigate ATTRIBUTE_NAME_TERRAIN_SIDE_FRICTION
// TODO: investigate ATTRIBUTE_NAME_TERRAIN_FORWARD_FRICTION
// TODO: investigate ATTRIBUTE_NAME_RESERVE_TRACK_BACKWARD_SPEED
], ],
}, },
{ {
group: "survivability", group: "survivability",
render: CharacteristicsGroupRender.Statistical,
order: [ order: [
{ name: "health", units: "hp" }, { name: "health", units: "hp" },
{ name: "fire_chance", units: "%" }, { name: "fire_chance", units: "%" },
// { name: "fire_health_burn", units: "hp_s" }, // TODO: implement fire health burn { name: "fire_health_burn", units: "hp_s" },
// { name: "ramming_resistance", units: "%" }, // TODO: implement ramming resistance { name: "ramming_resistance", units: "%" },
// { toy: "view_range" }, { toy: "fire_health_burn" },
// TODO: investigate ATTRIBUTE_NAME_MAX_FIRE_DURATION
],
},
{
group: "concealment",
render: CharacteristicsGroupRender.Statistical,
order: [
{ name: "view_range", units: "m" }, { name: "view_range", units: "m" },
{ name: "camouflage", decimals: 0, units: "%" }, { name: "camouflage", decimals: 0, units: "%" },
// { name: "consumable_cooldown", decimals: 0, units: "%" }, // TODO: implement consumable cooldown { toy: "view_range" },
// { name: "consumable_duration", decimals: 0, units: "s" }, // TODO: implement consumable duration
// { name: "consumable_reload", decimals: 0, units: "s" }, // TODO: implement consumable reload
// { name: "width", units: "m" }, // TODO: implement width { name: "width", units: "m" },
{ name: "height", units: "m" }, { name: "height", units: "m" },
{ name: "length", units: "m" }, { name: "length", units: "m" },
{ name: "volume", units: "m" }, { name: "volume", units: "m" },