mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: migrate all
This commit is contained in:
1 parent
5594c0ec3b
commit
320a764d45
38 files changed
+632
-600
No files matched your search
+30
-28
@@ -1,16 +1,16 @@
|
||||
import type { AssaultRanges } from '@blitzkit/core';
|
||||
import { literals } from '@blitzkit/i18n';
|
||||
import { Box, Flex, Text } from '@radix-ui/themes';
|
||||
import { clamp } from 'three/src/math/MathUtils.js';
|
||||
import type { AssaultRanges } from "@blitzkit/core";
|
||||
import { literals } from "@blitzkit/i18n";
|
||||
import { Box, Flex, Text } from "@radix-ui/themes";
|
||||
import { clamp } from "three/src/math/MathUtils.js";
|
||||
import {
|
||||
fakeLogSquishyHigh,
|
||||
fakeLogSquishyLow,
|
||||
} from '../../../../../../../core/blitzkit/fakeLog';
|
||||
import type { TankCharacteristics } from '../../../../../../../core/blitzkit/tankCharacteristics';
|
||||
import { Var } from '../../../../../../../core/radix/var';
|
||||
import { useLocale } from '../../../../../../../hooks/useLocale';
|
||||
import { VisualizerCard } from '../VisualizerCard';
|
||||
import { AssaultCursor } from './components/Cursor';
|
||||
} from "../../../../../../../core/blitzkit/fakeLog";
|
||||
import type { TankCharacteristics } from "../../../../../../../core/blitzkit/tankCharacteristics";
|
||||
import { Var } from "../../../../../../../core/radix/var";
|
||||
import { useLocale } from "../../../../../../../hooks/useLocale";
|
||||
import { VisualizerCard } from "../VisualizerCard";
|
||||
import { AssaultCursor } from "./components/Cursor";
|
||||
|
||||
interface Props {
|
||||
ranges: AssaultRanges;
|
||||
@@ -48,27 +48,29 @@ export function AssaultRangesVisualizer({ ranges: _ranges, stats }: Props) {
|
||||
p="3"
|
||||
pt="8"
|
||||
pr="5"
|
||||
style={{ backgroundColor: Var('gray-1') }}
|
||||
style={{ backgroundColor: Var("gray-1") }}
|
||||
flexGrow="1"
|
||||
>
|
||||
<Flex flexGrow="1">
|
||||
<Box position="relative" width={Var('space-8')}>
|
||||
<Box position="relative" width={Var("space-8")}>
|
||||
{ranges.map((range, index) => (
|
||||
<Text
|
||||
key={index}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
bottom: `${fakeLogSquishyLow(range.factor / maxFactor) * 100}%`,
|
||||
transform: 'translateY(50%)',
|
||||
transform: "translateY(50%)",
|
||||
}}
|
||||
color="gray"
|
||||
size="1"
|
||||
wrap="nowrap"
|
||||
>
|
||||
{literals(strings.common.units.hp, [
|
||||
`${Math.round(stats.damageWithoutAssault * range.factor)}`,
|
||||
])}
|
||||
{literals(strings.common.units.hp, {
|
||||
value: Math.round(
|
||||
stats.damageWithoutAssault * range.factor
|
||||
),
|
||||
})}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
@@ -77,8 +79,8 @@ export function AssaultRangesVisualizer({ ranges: _ranges, stats }: Props) {
|
||||
{ranges.map((range, index) => {
|
||||
const mix =
|
||||
clamp(2 * (range.factor / maxFactor - 1) + 1, 0, 1) * 100;
|
||||
const color0 = `color-mix(in hsl, ${Var('tomato-9')}, ${Var('jade-9')} ${mix}%)`;
|
||||
const color1 = `color-mix(in hsl, ${Var('tomato-7')}, ${Var('jade-7')} ${mix}%)`;
|
||||
const color0 = `color-mix(in hsl, ${Var("tomato-9")}, ${Var("jade-9")} ${mix}%)`;
|
||||
const color1 = `color-mix(in hsl, ${Var("tomato-7")}, ${Var("jade-7")} ${mix}%)`;
|
||||
const last = ranges[index - 1];
|
||||
const lastFakeDistance = last?.fakeDistance ?? 0;
|
||||
const fakeWidth = range.fakeDistance - lastFakeDistance;
|
||||
@@ -105,8 +107,8 @@ export function AssaultRangesVisualizer({ ranges: _ranges, stats }: Props) {
|
||||
width="100%"
|
||||
height="1pt"
|
||||
style={{
|
||||
transform: 'translateY(50%)',
|
||||
backgroundColor: Var('gray-11'),
|
||||
transform: "translateY(50%)",
|
||||
backgroundColor: Var("gray-11"),
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -118,7 +120,7 @@ export function AssaultRangesVisualizer({ ranges: _ranges, stats }: Props) {
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex ml="8" height={Var('space-7')}>
|
||||
<Flex ml="8" height={Var("space-7")}>
|
||||
{ranges.map((range, index) => {
|
||||
const last = ranges[index - 1];
|
||||
const lastFakeDistance = last?.fakeDistance ?? 0;
|
||||
@@ -137,15 +139,15 @@ export function AssaultRangesVisualizer({ ranges: _ranges, stats }: Props) {
|
||||
color="gray"
|
||||
mt="2"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
transform: 'translateX(50%)',
|
||||
writingMode: 'vertical-rl',
|
||||
transform: "translateX(50%)",
|
||||
writingMode: "vertical-rl",
|
||||
}}
|
||||
>
|
||||
{literals(strings.common.units.m, [
|
||||
`${Math.round(range.distance)}`,
|
||||
])}
|
||||
{literals(strings.common.units.m, {
|
||||
value: Math.round(range.distance),
|
||||
})}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
+6
-5
@@ -38,16 +38,17 @@ export function CompareOptions(props: FlexProps) {
|
||||
<SegmentedControl.Item value={`${TankopediaRelativeAgainst.Class}`}>
|
||||
{literals(
|
||||
strings.website.tools.tankopedia.characteristics.compare.class,
|
||||
[
|
||||
TIER_ROMAN_NUMERALS[tank.tier],
|
||||
strings.common.tank_class_short_plural_lowercase[tank.class],
|
||||
]
|
||||
{
|
||||
tier: TIER_ROMAN_NUMERALS[tank.tier],
|
||||
class:
|
||||
strings.common.tank_class_short_plural_lowercase[tank.class],
|
||||
}
|
||||
)}
|
||||
</SegmentedControl.Item>
|
||||
<SegmentedControl.Item value={`${TankopediaRelativeAgainst.Tier}`}>
|
||||
{literals(
|
||||
strings.website.tools.tankopedia.characteristics.compare.tier,
|
||||
[TIER_ROMAN_NUMERALS[tank.tier]]
|
||||
{ tier: TIER_ROMAN_NUMERALS[tank.tier] }
|
||||
)}
|
||||
</SegmentedControl.Item>
|
||||
<SegmentedControl.Item value={`${TankopediaRelativeAgainst.All}`}>
|
||||
|
||||
+17
-17
@@ -1,15 +1,15 @@
|
||||
import { CREW_MEMBER_NAMES, CrewType } from '@blitzkit/core';
|
||||
import { literals } from '@blitzkit/i18n';
|
||||
import { AccessibilityIcon, InfoCircledIcon } from '@radix-ui/react-icons';
|
||||
import { Flex, Heading, IconButton, Popover, Text } from '@radix-ui/themes';
|
||||
import { Fragment } from 'react/jsx-runtime';
|
||||
import { awaitableProvisionDefinitions } from '../../../../../../core/awaitables/provisionDefinitions';
|
||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../../../stores/duel';
|
||||
import { InfoWithDelta } from './InfoWithDelta';
|
||||
import { StatsTableWrapper } from './StatsTableWrapper';
|
||||
import type { StatsAcceptorProps } from './TraverseVisualizer';
|
||||
import { CREW_MEMBER_NAMES, CrewType } from "@blitzkit/core";
|
||||
import { literals } from "@blitzkit/i18n";
|
||||
import { AccessibilityIcon, InfoCircledIcon } from "@radix-ui/react-icons";
|
||||
import { Flex, Heading, IconButton, Popover, Text } from "@radix-ui/themes";
|
||||
import { Fragment } from "react/jsx-runtime";
|
||||
import { awaitableProvisionDefinitions } from "../../../../../../core/awaitables/provisionDefinitions";
|
||||
import { useEquipment } from "../../../../../../hooks/useEquipment";
|
||||
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { InfoWithDelta } from "./InfoWithDelta";
|
||||
import { StatsTableWrapper } from "./StatsTableWrapper";
|
||||
import type { StatsAcceptorProps } from "./TraverseVisualizer";
|
||||
|
||||
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||
|
||||
@@ -24,7 +24,7 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
||||
provisionDefinitions.provisions[provision].crew
|
||||
? total + provisionDefinitions.provisions[provision].crew! / 100
|
||||
: total,
|
||||
0,
|
||||
0
|
||||
) + (hasImprovedVentilation ? 0.08 : 0);
|
||||
const commanderMastery = 1 + provisionCrewBonus;
|
||||
|
||||
@@ -65,8 +65,8 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
||||
]
|
||||
}${
|
||||
member.count > 1
|
||||
? ` ${literals(strings.common.units.x, [`${member.count}`])}`
|
||||
: ''
|
||||
? ` ${literals(strings.common.units.x, { value: member.count })}`
|
||||
: ""
|
||||
}`}
|
||||
decimals={0}
|
||||
noRanking
|
||||
@@ -93,9 +93,9 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
||||
? strings.website.tools.tankopedia.crew[
|
||||
CREW_MEMBER_NAMES[sub]
|
||||
]
|
||||
: sub,
|
||||
: sub
|
||||
)
|
||||
.join(', ')}
|
||||
.join(", ")}
|
||||
</Flex>
|
||||
</>
|
||||
}
|
||||
|
||||
+2
-2
@@ -119,7 +119,7 @@ export function Firepower({
|
||||
name={literals(
|
||||
strings.website.tools.tankopedia.characteristics.values
|
||||
.shell_index,
|
||||
[`${index + 1}`]
|
||||
{ index: index + 1 }
|
||||
)}
|
||||
decimals={2}
|
||||
deltaType="lowerIsBetter"
|
||||
@@ -191,7 +191,7 @@ export function Firepower({
|
||||
name={literals(
|
||||
strings.website.tools.tankopedia.characteristics.values
|
||||
.dispersion_at_distance,
|
||||
["100"]
|
||||
{ distance: 100 }
|
||||
)}
|
||||
/>
|
||||
<InfoWithDelta
|
||||
|
||||
+12
-10
@@ -359,31 +359,33 @@ export function GunFlexibilityVisualizer({
|
||||
|
||||
<VisualizerCornerStat
|
||||
label={strings.website.tools.tankopedia.visualizers.flexibility.max}
|
||||
value={literals(strings.common.units.deg, [
|
||||
radToDeg(maxPitch).toFixed(0),
|
||||
])}
|
||||
value={literals(strings.common.units.deg, {
|
||||
value: radToDeg(maxPitch).toFixed(0),
|
||||
})}
|
||||
side="bottom-left"
|
||||
/>
|
||||
|
||||
<VisualizerCornerStat
|
||||
label={strings.website.tools.tankopedia.visualizers.flexibility.min}
|
||||
value={literals(strings.common.units.deg, [
|
||||
radToDeg(minPitch).toFixed(0),
|
||||
])}
|
||||
value={literals(strings.common.units.deg, {
|
||||
value: radToDeg(minPitch).toFixed(0),
|
||||
})}
|
||||
side="bottom-right"
|
||||
/>
|
||||
|
||||
<VisualizerCornerStat
|
||||
label={strings.website.tools.tankopedia.visualizers.flexibility.pitch}
|
||||
value={literals(strings.common.units.deg, [
|
||||
radToDeg(pitch).toFixed(0),
|
||||
])}
|
||||
value={literals(strings.common.units.deg, {
|
||||
value: radToDeg(pitch).toFixed(0),
|
||||
})}
|
||||
side="top-left"
|
||||
/>
|
||||
|
||||
<VisualizerCornerStat
|
||||
label={strings.website.tools.tankopedia.visualizers.flexibility.yaw}
|
||||
value={literals(strings.common.units.deg, [radToDeg(yaw).toFixed(0)])}
|
||||
value={literals(strings.common.units.deg, {
|
||||
value: radToDeg(yaw).toFixed(0),
|
||||
})}
|
||||
side="top-right"
|
||||
/>
|
||||
</VisualizerCard>
|
||||
|
||||
+57
-55
@@ -1,15 +1,15 @@
|
||||
import { asset, isExplosive } from '@blitzkit/core';
|
||||
import { literals } from '@blitzkit/i18n';
|
||||
import { Box, Flex, Text } from '@radix-ui/themes';
|
||||
import { useRef, useState } from 'react';
|
||||
import { clamp, degToRad, radToDeg } from 'three/src/math/MathUtils.js';
|
||||
import { Var } from '../../../../../../../core/radix/var';
|
||||
import { useLocale } from '../../../../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../../../../stores/duel';
|
||||
import type { StatsAcceptorProps } from '../TraverseVisualizer';
|
||||
import { VisualizerCard } from '../VisualizerCard';
|
||||
import { VisualizerCornerStat } from '../VisualizerCornerStat';
|
||||
import { ShellSwitcher } from './components/ShellSwitcher';
|
||||
import { asset, isExplosive } from "@blitzkit/core";
|
||||
import { literals } from "@blitzkit/i18n";
|
||||
import { Box, Flex, Text } from "@radix-ui/themes";
|
||||
import { useRef, useState } from "react";
|
||||
import { clamp, degToRad, radToDeg } from "three/src/math/MathUtils.js";
|
||||
import { Var } from "../../../../../../../core/radix/var";
|
||||
import { useLocale } from "../../../../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../../../../stores/duel";
|
||||
import type { StatsAcceptorProps } from "../TraverseVisualizer";
|
||||
import { VisualizerCard } from "../VisualizerCard";
|
||||
import { VisualizerCornerStat } from "../VisualizerCornerStat";
|
||||
import { ShellSwitcher } from "./components/ShellSwitcher";
|
||||
|
||||
export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
const shell = Duel.use((state) => state.protagonist.shell);
|
||||
@@ -30,7 +30,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
<Flex direction="column" mb="6" align="center">
|
||||
<VisualizerCard
|
||||
mb="0"
|
||||
style={{ width: '100%' }}
|
||||
style={{ width: "100%" }}
|
||||
ref={container}
|
||||
onPointerMove={(event) => {
|
||||
if (!container.current || explodes) return;
|
||||
@@ -50,7 +50,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
top="0"
|
||||
left="0"
|
||||
style={{
|
||||
background: `linear-gradient(to right, transparent, ${Var('gray-a4')})`,
|
||||
background: `linear-gradient(to right, transparent, ${Var("gray-a4")})`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@@ -73,20 +73,20 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
background: `conic-gradient(
|
||||
at 0 50%,
|
||||
|
||||
${Var('ruby-a2')},
|
||||
${Var('ruby-a1')} ${Math.PI / 2 - ricochet}rad,
|
||||
${Var("ruby-a2")},
|
||||
${Var("ruby-a1")} ${Math.PI / 2 - ricochet}rad,
|
||||
|
||||
${Var('jade-a2')} ${Math.PI / 2 - ricochet}rad,
|
||||
${Var('jade-a4')} ${Math.PI / 2 - normalization}rad,
|
||||
${Var("jade-a2")} ${Math.PI / 2 - ricochet}rad,
|
||||
${Var("jade-a4")} ${Math.PI / 2 - normalization}rad,
|
||||
|
||||
${Var('cyan-a5')} ${Math.PI / 2 - normalization}rad,
|
||||
${Var('cyan-a3')} ${Math.PI / 2 + normalization}rad,
|
||||
${Var("cyan-a5")} ${Math.PI / 2 - normalization}rad,
|
||||
${Var("cyan-a3")} ${Math.PI / 2 + normalization}rad,
|
||||
|
||||
${Var('jade-a2')} ${Math.PI / 2 + normalization}rad,
|
||||
${Var('jade-a4')} ${Math.PI / 2 + ricochet}rad,
|
||||
${Var("jade-a2")} ${Math.PI / 2 + normalization}rad,
|
||||
${Var("jade-a4")} ${Math.PI / 2 + ricochet}rad,
|
||||
|
||||
${Var('ruby-a1')} ${Math.PI / 2 + ricochet}rad,
|
||||
${Var('ruby-a2')} 180deg
|
||||
${Var("ruby-a1")} ${Math.PI / 2 + ricochet}rad,
|
||||
${Var("ruby-a2")} 180deg
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
@@ -97,7 +97,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
top="50%"
|
||||
height="18rem"
|
||||
style={{
|
||||
transformOrigin: 'top center',
|
||||
transformOrigin: "top center",
|
||||
transform: `translateX(-50%) rotate(${-Math.PI / 2 - angle}rad)`,
|
||||
}}
|
||||
>
|
||||
@@ -107,8 +107,8 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
height="100%"
|
||||
bottom="100%"
|
||||
style={{
|
||||
transform: 'translateX(-50%)',
|
||||
backgroundColor: Var('gray-a8'),
|
||||
transform: "translateX(-50%)",
|
||||
backgroundColor: Var("gray-a8"),
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
@@ -116,22 +116,22 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
width="3px"
|
||||
height="100%"
|
||||
style={{
|
||||
transform: 'translateX(-50%)',
|
||||
background: `linear-gradient(${Var('gray-a11')}, ${Var('gray-a4')})`,
|
||||
transform: "translateX(-50%)",
|
||||
background: `linear-gradient(${Var("gray-a11")}, ${Var("gray-a4")})`,
|
||||
}}
|
||||
/>
|
||||
|
||||
<img
|
||||
src={asset(`icons/shells/${shell.icon}.webp`)}
|
||||
style={{
|
||||
objectFit: 'contain',
|
||||
transform: 'translate(-50%, -50%) rotate(-45deg)',
|
||||
position: 'absolute',
|
||||
width: '2rem',
|
||||
height: '2rem',
|
||||
top: '6rem',
|
||||
objectFit: "contain",
|
||||
transform: "translate(-50%, -50%) rotate(-45deg)",
|
||||
position: "absolute",
|
||||
width: "2rem",
|
||||
height: "2rem",
|
||||
top: "6rem",
|
||||
left: 0,
|
||||
filter: 'drop-shadow(0px 0px 0.25rem black)',
|
||||
filter: "drop-shadow(0px 0px 0.25rem black)",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@@ -142,7 +142,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
top="50%"
|
||||
height="16rem"
|
||||
style={{
|
||||
transformOrigin: 'top center',
|
||||
transformOrigin: "top center",
|
||||
transform: `translateX(-50%) rotate(${-Math.PI / 2 - effectiveAngle}rad)`,
|
||||
}}
|
||||
>
|
||||
@@ -152,17 +152,17 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
height="100%"
|
||||
top="-100%"
|
||||
style={{
|
||||
transform: 'translateX(-50%)',
|
||||
background: `linear-gradient(${Var('gray-a4')}, ${Var('gray-a11')})`,
|
||||
transform: "translateX(-50%)",
|
||||
background: `linear-gradient(${Var("gray-a4")}, ${Var("gray-a11")})`,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<VisualizerCornerStat
|
||||
label={strings.website.tools.tankopedia.visualizers.ricochet.angle}
|
||||
value={literals(strings.common.units.deg, [
|
||||
radToDeg(Math.abs(angle)).toFixed(0),
|
||||
])}
|
||||
value={literals(strings.common.units.deg, {
|
||||
value: radToDeg(Math.abs(angle)).toFixed(0),
|
||||
})}
|
||||
side="top-left"
|
||||
/>
|
||||
<VisualizerCornerStat
|
||||
@@ -171,10 +171,12 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
}
|
||||
value={
|
||||
doesRicochet
|
||||
? '-'
|
||||
: literals(strings.common.units.deg, [
|
||||
radToDeg(Math.min(normalization, Math.abs(angle))).toFixed(0),
|
||||
])
|
||||
? "-"
|
||||
: literals(strings.common.units.deg, {
|
||||
value: radToDeg(
|
||||
Math.min(normalization, Math.abs(angle))
|
||||
).toFixed(0),
|
||||
})
|
||||
}
|
||||
side="top-right"
|
||||
/>
|
||||
@@ -185,10 +187,10 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
}
|
||||
value={
|
||||
doesRicochet
|
||||
? '-'
|
||||
: literals(strings.common.units.deg, [
|
||||
radToDeg(Math.abs(effectiveAngle)).toFixed(0),
|
||||
])
|
||||
? "-"
|
||||
: literals(strings.common.units.deg, {
|
||||
value: radToDeg(Math.abs(effectiveAngle)).toFixed(0),
|
||||
})
|
||||
}
|
||||
side="bottom-left"
|
||||
/>
|
||||
@@ -196,10 +198,10 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
label={strings.website.tools.tankopedia.visualizers.ricochet.armor}
|
||||
value={
|
||||
doesRicochet
|
||||
? '-'
|
||||
: literals(strings.common.units.percentage, [
|
||||
armorPercentage.toFixed(0),
|
||||
])
|
||||
? "-"
|
||||
: literals(strings.common.units.percentage, {
|
||||
value: armorPercentage.toFixed(0),
|
||||
})
|
||||
}
|
||||
side="bottom-right"
|
||||
/>
|
||||
@@ -215,7 +217,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
top="0"
|
||||
left="0"
|
||||
style={{
|
||||
background: `radial-gradient(${Var('black-a7')}, ${Var('black-a11')})`,
|
||||
background: `radial-gradient(${Var("black-a7")}, ${Var("black-a11")})`,
|
||||
}}
|
||||
>
|
||||
<Text color="gray" align="center">
|
||||
|
||||
+34
-34
@@ -1,20 +1,20 @@
|
||||
import { literals } from '@blitzkit/i18n';
|
||||
import { Checkbox, Code, Flex, Tabs, Text } from '@radix-ui/themes';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import type { TankCharacteristics } from '../../../../../../../core/blitzkit/tankCharacteristics';
|
||||
import { useLocale } from '../../../../../../../hooks/useLocale';
|
||||
import { VisualizerCard } from '../VisualizerCard';
|
||||
import { literals } from "@blitzkit/i18n";
|
||||
import { Checkbox, Code, Flex, Tabs, Text } from "@radix-ui/themes";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { TankCharacteristics } from "../../../../../../../core/blitzkit/tankCharacteristics";
|
||||
import { useLocale } from "../../../../../../../hooks/useLocale";
|
||||
import { VisualizerCard } from "../VisualizerCard";
|
||||
|
||||
export interface StatsAcceptorProps {
|
||||
stats: TankCharacteristics;
|
||||
}
|
||||
|
||||
type Terrain = 'Hard' | 'Medium' | 'Soft';
|
||||
type Terrain = "Hard" | "Medium" | "Soft";
|
||||
|
||||
export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
||||
const hull = useRef<HTMLDivElement>(null);
|
||||
const turret = useRef<HTMLImageElement>(null);
|
||||
const [terrain, setTerrain] = useState<Terrain>('Hard');
|
||||
const [terrain, setTerrain] = useState<Terrain>("Hard");
|
||||
const [rotateHull, setRotateHull] = useState(true);
|
||||
const [rotateTurret, setRotateTurret] = useState(true);
|
||||
const { strings } = useLocale();
|
||||
@@ -63,10 +63,10 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
||||
<div
|
||||
ref={hull}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
animationDuration: `${
|
||||
rotateHull ? 360 / stats[`hullTraverse${terrain}Terrain`] : 0
|
||||
}s`,
|
||||
@@ -75,12 +75,12 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
||||
<img
|
||||
draggable="false"
|
||||
style={{
|
||||
width: isTurretless ? '22rem' : '12rem',
|
||||
height: isTurretless ? '22rem' : '12rem',
|
||||
objectFit: 'contain',
|
||||
filter: 'drop-shadow(0px 0px 4px black)',
|
||||
width: isTurretless ? "22rem" : "12rem",
|
||||
height: isTurretless ? "22rem" : "12rem",
|
||||
objectFit: "contain",
|
||||
filter: "drop-shadow(0px 0px 4px black)",
|
||||
}}
|
||||
src={`/assets/images/tankopedia/visualizers/hull-traverse/hull${isTurretless ? '-only' : ''}.png`}
|
||||
src={`/assets/images/tankopedia/visualizers/hull-traverse/hull${isTurretless ? "-only" : ""}.png`}
|
||||
/>
|
||||
|
||||
{!isTurretless && (
|
||||
@@ -88,14 +88,14 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
||||
draggable="false"
|
||||
ref={turret}
|
||||
style={{
|
||||
width: '27rem',
|
||||
height: '27rem',
|
||||
objectFit: 'contain',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
filter: 'drop-shadow(0px 0px 4px black)',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: "27rem",
|
||||
height: "27rem",
|
||||
objectFit: "contain",
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
filter: "drop-shadow(0px 0px 4px black)",
|
||||
transform: "translate(-50%, -50%)",
|
||||
animationDuration: `${
|
||||
rotateTurret ? 360 / stats.turretTraverseSpeed : 0
|
||||
}s`,
|
||||
@@ -110,7 +110,7 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
||||
bottom="0"
|
||||
left="50%"
|
||||
style={{
|
||||
transform: 'translateX(-50%)',
|
||||
transform: "translateX(-50%)",
|
||||
}}
|
||||
>
|
||||
<Tabs.Root
|
||||
@@ -137,16 +137,16 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
||||
left="50%"
|
||||
bottom="7"
|
||||
style={{
|
||||
transform: 'translateX(-50%)',
|
||||
transform: "translateX(-50%)",
|
||||
}}
|
||||
>
|
||||
<Text color="gray" size="1">
|
||||
{strings.website.tools.tankopedia.visualizers.rotator.effective}
|
||||
</Text>
|
||||
<Code size="1" variant="ghost">
|
||||
{literals(strings.common.units.deg_s, [
|
||||
`${(turretSpeedDeg + hullSpeedDeg).toFixed(1)}`,
|
||||
])}
|
||||
{literals(strings.common.units.deg_s, {
|
||||
value: (turretSpeedDeg + hullSpeedDeg).toFixed(1),
|
||||
})}
|
||||
</Code>
|
||||
</Flex>
|
||||
|
||||
@@ -160,15 +160,15 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
||||
gap="5"
|
||||
justify="center"
|
||||
style={{
|
||||
transform: 'translateX(-50%)',
|
||||
userSelect: 'none',
|
||||
transform: "translateX(-50%)",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
align="center"
|
||||
gap="2"
|
||||
onClick={() => setRotateHull((state) => !state)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<Checkbox variant="classic" checked={rotateHull} />
|
||||
<Text size="2">
|
||||
@@ -180,7 +180,7 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
||||
align="center"
|
||||
gap="2"
|
||||
onClick={() => setRotateTurret((state) => !state)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<Checkbox variant="classic" checked={rotateTurret} />
|
||||
<Text size="2">
|
||||
|
||||
+28
-28
@@ -1,20 +1,20 @@
|
||||
import { literals } from '@blitzkit/i18n';
|
||||
import { EyeOpenIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Card, Code, Flex, Slider, Tabs, Text } from '@radix-ui/themes';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Var } from '../../../../../../core/radix/var';
|
||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
||||
import type { StatsAcceptorProps } from './TraverseVisualizer';
|
||||
import { literals } from "@blitzkit/i18n";
|
||||
import { EyeOpenIcon } from "@radix-ui/react-icons";
|
||||
import { Box, Card, Code, Flex, Slider, Tabs, Text } from "@radix-ui/themes";
|
||||
import { useRef, useState } from "react";
|
||||
import { Var } from "../../../../../../core/radix/var";
|
||||
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||
import type { StatsAcceptorProps } from "./TraverseVisualizer";
|
||||
|
||||
const maps = [
|
||||
{
|
||||
id: 'ghost-factory',
|
||||
string: 'ghost_factory',
|
||||
id: "ghost-factory",
|
||||
string: "ghost_factory",
|
||||
size: 512,
|
||||
},
|
||||
{
|
||||
id: 'dead-rail',
|
||||
string: 'dead_rail',
|
||||
id: "dead-rail",
|
||||
string: "dead_rail",
|
||||
size: 512,
|
||||
},
|
||||
] as const;
|
||||
@@ -32,7 +32,7 @@ export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) {
|
||||
|
||||
return (
|
||||
<Card
|
||||
style={{ aspectRatio: '1 / 1', touchAction: 'none' }}
|
||||
style={{ aspectRatio: "1 / 1", touchAction: "none" }}
|
||||
mb="6"
|
||||
variant="classic"
|
||||
>
|
||||
@@ -61,8 +61,8 @@ export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) {
|
||||
style={{
|
||||
backgroundImage: `url(/assets/images/tankopedia/visualizers/range/${map.id}.png)`,
|
||||
filter: `grayscale(0.5) brightness(0.8) contrast(1.2)`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -75,16 +75,18 @@ export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) {
|
||||
align="center"
|
||||
justify="center"
|
||||
style={{
|
||||
aspectRatio: '1 / 1',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
background: `radial-gradient(${Var('black-a6')}, ${Var('gray-a12')})`,
|
||||
borderRadius: '50%',
|
||||
aspectRatio: "1 / 1",
|
||||
transform: "translate(-50%, -50%)",
|
||||
background: `radial-gradient(${Var("black-a6")}, ${Var("gray-a12")})`,
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
>
|
||||
<Text style={{ userSelect: 'none' }}>
|
||||
<Text style={{ userSelect: "none" }}>
|
||||
<Flex align="center" gap="1">
|
||||
<EyeOpenIcon width="1.25rem" height="1.25rem" />
|
||||
{literals(strings.common.units.m, [resolvedViewRange.toFixed(0)])}
|
||||
{literals(strings.common.units.m, {
|
||||
value: resolvedViewRange.toFixed(0),
|
||||
})}
|
||||
</Flex>
|
||||
</Text>
|
||||
</Flex>
|
||||
@@ -100,8 +102,8 @@ export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) {
|
||||
gap="3"
|
||||
align="center"
|
||||
style={{
|
||||
backgroundColor: Var('black-a7'),
|
||||
backdropFilter: 'blur(4rem)',
|
||||
backgroundColor: Var("black-a7"),
|
||||
backdropFilter: "blur(4rem)",
|
||||
}}
|
||||
>
|
||||
<Text size="2">
|
||||
@@ -120,11 +122,9 @@ export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) {
|
||||
|
||||
<Code size="1" color="gray" variant="ghost">
|
||||
<Flex justify="center" width="2em">
|
||||
{literals(
|
||||
strings.website.tools.tankopedia.visualizers.view_range
|
||||
.percentage,
|
||||
[`${Math.round(camouflage * 100)}`.padStart(3, ' ')],
|
||||
)}
|
||||
{literals(strings.common.units.percentage, {
|
||||
percentage: `${Math.round(camouflage * 100)}`.padStart(3, " "),
|
||||
})}
|
||||
</Flex>
|
||||
</Code>
|
||||
</Flex>
|
||||
@@ -136,7 +136,7 @@ export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) {
|
||||
left="0"
|
||||
width="100%"
|
||||
style={{
|
||||
background: `radial-gradient(at 50% 100%, ${Var('gray-1')}, transparent 70%)`,
|
||||
background: `radial-gradient(at 50% 100%, ${Var("gray-1")}, transparent 70%)`,
|
||||
}}
|
||||
>
|
||||
<Tabs.Root
|
||||
|
||||
+8
-8
@@ -151,7 +151,7 @@ export function CustomShellButton() {
|
||||
}}
|
||||
>
|
||||
<TextField.Slot side="right">
|
||||
{literals(strings.common.units.mm, [""])}
|
||||
{literals(strings.common.units.mm, { value: "" })}
|
||||
</TextField.Slot>
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
@@ -188,7 +188,7 @@ export function CustomShellButton() {
|
||||
}}
|
||||
>
|
||||
<TextField.Slot side="right">
|
||||
{literals(strings.common.units.mm, [""])}
|
||||
{literals(strings.common.units.mm, { value: "" })}
|
||||
</TextField.Slot>
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
@@ -212,9 +212,9 @@ export function CustomShellButton() {
|
||||
minWidth="8rem"
|
||||
>
|
||||
<Text color="gray">
|
||||
{literals(strings.common.units.deg, [
|
||||
(customShell.normalization ?? 0).toFixed(0),
|
||||
])}
|
||||
{literals(strings.common.units.deg, {
|
||||
value: (customShell.normalization ?? 0).toFixed(0),
|
||||
})}
|
||||
</Text>
|
||||
<Slider
|
||||
variant="classic"
|
||||
@@ -243,9 +243,9 @@ export function CustomShellButton() {
|
||||
|
||||
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem">
|
||||
<Text color="gray">
|
||||
{literals(strings.common.units.deg, [
|
||||
(customShell.ricochet ?? 90).toFixed(0),
|
||||
])}
|
||||
{literals(strings.common.units.deg, {
|
||||
value: (customShell.ricochet ?? 90).toFixed(0),
|
||||
})}
|
||||
</Text>
|
||||
<Slider
|
||||
variant="classic"
|
||||
|
||||
+8
-8
@@ -59,15 +59,15 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
||||
>
|
||||
<Flex direction="column" align="end" justify="between">
|
||||
<Text color="gray" size="1">
|
||||
{literals(strings.common.units.mm, [
|
||||
(thicknessRange.value * 1.5).toFixed(0),
|
||||
])}
|
||||
{literals(strings.common.units.mm, {
|
||||
value: (thicknessRange.value * 1.5).toFixed(0),
|
||||
})}
|
||||
</Text>
|
||||
<Text size="1">
|
||||
{strings.website.tools.tankopedia.sandbox.static.primary}
|
||||
</Text>
|
||||
<Text color="gray" size="1">
|
||||
{literals(strings.common.units.mm, ["0"])}
|
||||
{literals(strings.common.units.mm, { value: 0 })}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
@@ -100,15 +100,15 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
||||
>
|
||||
<Flex direction="column" align="end" justify="between">
|
||||
<Text color="gray" size="1">
|
||||
{literals(strings.common.units.mm, [
|
||||
thicknessRange.value.toFixed(0),
|
||||
])}
|
||||
{literals(strings.common.units.mm, {
|
||||
value: thicknessRange.value.toFixed(0),
|
||||
})}
|
||||
</Text>
|
||||
<Text size="1">
|
||||
{strings.website.tools.tankopedia.sandbox.static.spaced}
|
||||
</Text>
|
||||
<Text color="gray" size="1">
|
||||
{literals(strings.common.units.mm, ["0"])}
|
||||
{literals(strings.common.units.mm, { value: 0 })}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
|
||||
@@ -92,14 +92,14 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
>
|
||||
{!hasCustomShell && (
|
||||
<Text color="gray" size={{ initial: "1", sm: "2" }}>
|
||||
{literals(strings.common.units.mm, [
|
||||
(
|
||||
{literals(strings.common.units.mm, {
|
||||
value: (
|
||||
resolvePenetrationCoefficient(
|
||||
hasCalibratedShells,
|
||||
antagonistShell.type
|
||||
) * antagonistShell.penetration.near
|
||||
).toFixed(0),
|
||||
])}
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -156,9 +156,9 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
|
||||
secondaryDiscriminator={
|
||||
<Text style={{ fontSize: "0.75em" }}>
|
||||
{literals(strings.common.units.mm, [
|
||||
gun.shells[0].caliber.toFixed(0),
|
||||
])}
|
||||
{literals(strings.common.units.mm, {
|
||||
value: gun.shells[0].caliber.toFixed(0),
|
||||
})}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -135,7 +135,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
||||
? strings.website.tools.tankopedia.tech_tree.battle
|
||||
: literals(
|
||||
strings.website.tools.tankopedia.tech_tree.battles,
|
||||
[`${games}`]
|
||||
{ count: games }
|
||||
))}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -158,7 +158,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
pl="1"
|
||||
>
|
||||
<Text size="2">
|
||||
{literals(strings.common.units.x, [`${multiplier}`])}
|
||||
{literals(strings.common.units.x, { value: multiplier })}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
@@ -218,10 +218,10 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
<CaretLeftIcon />
|
||||
</IconButton>
|
||||
<Text>
|
||||
{literals(strings.website.tools.tankopedia.tech_tree.route, [
|
||||
`${lineIndex + 1}`,
|
||||
`${lines.length}`,
|
||||
])}
|
||||
{literals(strings.website.tools.tankopedia.tech_tree.route, {
|
||||
index: lineIndex + 1,
|
||||
count: lines.length,
|
||||
})}
|
||||
</Text>
|
||||
<IconButton
|
||||
size="2"
|
||||
|
||||
Reference in new issue
Block a user