mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +02:00
feat: better layout
This commit is contained in:
1 parent
299ccef579
commit
ab510361b5
6 files changed
+463
-435
No files matched your search
@@ -32,7 +32,7 @@ export function Info({
|
||||
style={{ width: '100%', paddingLeft: indent ? 24 : 0 }}
|
||||
gap="4"
|
||||
>
|
||||
<Text color={highlight ? 'amber' : undefined}>
|
||||
<Text color={highlight ? 'amber' : 'gray'}>
|
||||
{name}
|
||||
{unit != undefined && (
|
||||
<>
|
||||
|
||||
@@ -25,12 +25,14 @@ import {
|
||||
} from '../../../../../../core/blitzkit/tankDefinitions/constants';
|
||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
|
||||
import { useWideFormat } from '../../../../../../hooks/useWideFormat';
|
||||
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
|
||||
import { useTankopediaTemporary } from '../../../../../../stores/tankopedia';
|
||||
import { Info } from './components/Info';
|
||||
import { InfoWithDelta } from './components/InfoWithDelta';
|
||||
|
||||
export function Characteristics() {
|
||||
const wideFormat = useWideFormat(960);
|
||||
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
|
||||
const awaitedModelDefinitions = use(modelDefinitions);
|
||||
const awaitedProvisionDefinitions = use(provisionDefinitions);
|
||||
@@ -128,445 +130,450 @@ export function Characteristics() {
|
||||
if (isFullScreen) return null;
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="8">
|
||||
<Flex direction="column" gap="2">
|
||||
<Flex align="center" gap="4">
|
||||
<Heading size="5">Fire</Heading>
|
||||
<Flex direction={wideFormat ? 'row' : 'column'} gap="8" wrap="wrap">
|
||||
<Flex direction="column" gap="8" style={{ flex: 1 }}>
|
||||
<Flex direction="column" gap="2">
|
||||
<Flex align="center" gap="4">
|
||||
<Heading size="5">Fire</Heading>
|
||||
|
||||
<Flex>
|
||||
{gun.shells.map((thisShell, shellIndex) => (
|
||||
<IconButton
|
||||
color={thisShell.id === shell.id ? undefined : 'gray'}
|
||||
variant="soft"
|
||||
key={thisShell.id}
|
||||
style={{
|
||||
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
|
||||
borderBottomLeftRadius: shellIndex === 0 ? undefined : 0,
|
||||
borderTopRightRadius:
|
||||
shellIndex === gun.shells.length - 1 ? undefined : 0,
|
||||
borderBottomRightRadius:
|
||||
shellIndex === gun.shells.length - 1 ? undefined : 0,
|
||||
marginLeft: shellIndex === 0 ? 0 : -1,
|
||||
}}
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
draft.protagonist!.shell = thisShell;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img
|
||||
width={16}
|
||||
height={16}
|
||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||
/>
|
||||
</IconButton>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info>
|
||||
<InfoWithDelta name="DPM" decimals={0} unit="hp / min">
|
||||
{stats.dpm}
|
||||
</InfoWithDelta>
|
||||
{gun.type === 'autoReloader' && (
|
||||
<>
|
||||
<InfoWithDelta decimals={0} indent name="Maximum" unit="hp / min">
|
||||
{stats.dpmMaximum!}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={0}
|
||||
indent
|
||||
name="Effective at 60s"
|
||||
unit="hp / min"
|
||||
>
|
||||
{stats.dpmEffective!}
|
||||
</InfoWithDelta>
|
||||
<Info
|
||||
indent
|
||||
name={
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://tresabhi.github.io/blitzkit/guide/dpm.html"
|
||||
<Flex>
|
||||
{gun.shells.map((thisShell, shellIndex) => (
|
||||
<IconButton
|
||||
color={thisShell.id === shell.id ? undefined : 'gray'}
|
||||
variant="soft"
|
||||
key={thisShell.id}
|
||||
style={{
|
||||
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
|
||||
borderBottomLeftRadius: shellIndex === 0 ? undefined : 0,
|
||||
borderTopRightRadius:
|
||||
shellIndex === gun.shells.length - 1 ? undefined : 0,
|
||||
borderBottomRightRadius:
|
||||
shellIndex === gun.shells.length - 1 ? undefined : 0,
|
||||
marginLeft: shellIndex === 0 ? 0 : -1,
|
||||
}}
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
draft.protagonist!.shell = thisShell;
|
||||
});
|
||||
}}
|
||||
>
|
||||
What's the difference?
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{gun.type !== 'regular' && (
|
||||
<InfoWithDelta name="Shells">{stats.shells}</InfoWithDelta>
|
||||
)}
|
||||
{gun.type === 'autoReloader' ? (
|
||||
<>
|
||||
<Info indent name="Most optimal">
|
||||
{stats.mostOptimalShellIndex}
|
||||
</Info>
|
||||
<Info name="Shell reloads" unit="s" />
|
||||
{stats.shellReloads!.map((reload, index) => (
|
||||
<InfoWithDelta
|
||||
key={index}
|
||||
indent
|
||||
name={`Shell ${index + 1}`}
|
||||
decimals={2}
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{reload}
|
||||
<img
|
||||
width={16}
|
||||
height={16}
|
||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||
/>
|
||||
</IconButton>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info>
|
||||
<InfoWithDelta name="DPM" decimals={0} unit="hp / min">
|
||||
{stats.dpm}
|
||||
</InfoWithDelta>
|
||||
{gun.type === 'autoReloader' && (
|
||||
<>
|
||||
<InfoWithDelta decimals={0} indent name="Maximum" unit="hp / min">
|
||||
{stats.dpmMaximum!}
|
||||
</InfoWithDelta>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<InfoWithDelta
|
||||
decimals={2}
|
||||
name="Reload"
|
||||
unit="s"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.shellReload!}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
{(gun.type === 'autoLoader' || gun.type === 'autoReloader') && (
|
||||
<InfoWithDelta
|
||||
indent
|
||||
decimals={2}
|
||||
name="Intra-clip"
|
||||
unit="s"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.intraClip!}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
<InfoWithDelta decimals={0} name="Penetration" unit="mm">
|
||||
{stats.penetration}
|
||||
</InfoWithDelta>
|
||||
{typeof shell.penetration !== 'number' && (
|
||||
<>
|
||||
<InfoWithDelta
|
||||
decimals={0}
|
||||
indent
|
||||
name="Effective at 60s"
|
||||
unit="hp / min"
|
||||
>
|
||||
{stats.dpmEffective!}
|
||||
</InfoWithDelta>
|
||||
<Info
|
||||
indent
|
||||
name={
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://tresabhi.github.io/blitzkit/guide/dpm.html"
|
||||
>
|
||||
What's the difference?
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{gun.type !== 'regular' && (
|
||||
<InfoWithDelta name="Shells">{stats.shells}</InfoWithDelta>
|
||||
)}
|
||||
{gun.type === 'autoReloader' ? (
|
||||
<>
|
||||
<Info indent name="Most optimal">
|
||||
{stats.mostOptimalShellIndex}
|
||||
</Info>
|
||||
<Info name="Shell reloads" unit="s" />
|
||||
{stats.shellReloads!.map((reload, index) => (
|
||||
<InfoWithDelta
|
||||
key={index}
|
||||
indent
|
||||
name={`Shell ${index + 1}`}
|
||||
decimals={2}
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{reload}
|
||||
</InfoWithDelta>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<InfoWithDelta
|
||||
decimals={2}
|
||||
name="Reload"
|
||||
unit="s"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.shellReload!}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
{(gun.type === 'autoLoader' || gun.type === 'autoReloader') && (
|
||||
<InfoWithDelta
|
||||
indent
|
||||
decimals={0}
|
||||
name={`At ${penetrationDistance}m`}
|
||||
decimals={2}
|
||||
name="Intra-clip"
|
||||
unit="s"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{lerp(
|
||||
shell.penetration[0],
|
||||
shell.penetration[1],
|
||||
(penetrationDistance * penetrationLossOverDistanceCoefficient) /
|
||||
500,
|
||||
) * penetrationCoefficient}
|
||||
{stats.intraClip!}
|
||||
</InfoWithDelta>
|
||||
<Flex align="center" gap="2" style={{ paddingLeft: 24 }}>
|
||||
<Text>Distance</Text>
|
||||
<Slider
|
||||
key={penetrationDistance}
|
||||
min={0}
|
||||
max={500}
|
||||
style={{ flex: 1 }}
|
||||
defaultValue={[penetrationDistance]}
|
||||
onValueChange={debounce(([value]) => {
|
||||
setPenetrationDistance(value);
|
||||
}, 500)}
|
||||
/>
|
||||
<TextField.Root
|
||||
style={{ width: 64 }}
|
||||
ref={penetrationDistanceInput}
|
||||
defaultValue={penetrationDistance}
|
||||
onBlur={() => {
|
||||
setPenetrationDistance(
|
||||
Math.min(
|
||||
parseInt(penetrationDistanceInput.current!.value),
|
||||
500,
|
||||
),
|
||||
);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TextField.Slot side="right">m</TextField.Slot>
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
<InfoWithDelta name="Damage" unit="hp" decimals={0}>
|
||||
{stats.damage}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta name="Module damage" unit="hp" decimals={0}>
|
||||
{stats.moduleDamage}
|
||||
</InfoWithDelta>
|
||||
{isExplosive(shell.type) && (
|
||||
<InfoWithDelta name="Splash radius" unit="m" decimals={0}>
|
||||
{stats.explosionRadius!}
|
||||
)}
|
||||
<InfoWithDelta decimals={0} name="Penetration" unit="mm">
|
||||
{stats.penetration}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
|
||||
{stats.caliber}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta name="Normalization" decimals={0} unit="°">
|
||||
{stats.shellNormalization}
|
||||
</InfoWithDelta>
|
||||
{shell.type !== 'he' && (
|
||||
<InfoWithDelta
|
||||
name="Ricochet"
|
||||
decimals={0}
|
||||
deltaType="lowerIsBetter"
|
||||
unit="°"
|
||||
>
|
||||
{stats.shellRicochet!}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
<InfoWithDelta name="Shell velocity" unit="m/s" decimals={0}>
|
||||
{stats.shellVelocity}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={2}
|
||||
name="Aim time"
|
||||
unit="s"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.aimTime}
|
||||
</InfoWithDelta>
|
||||
<Info name="Dispersion at 100m" />
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
indent
|
||||
name="Still"
|
||||
unit="m"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersion}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
prefix="+ "
|
||||
decimals={3}
|
||||
indent
|
||||
name="Moving"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionMoving}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
prefix="+ "
|
||||
indent
|
||||
name="Hull traversing"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionHullTraversing}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
prefix="+ "
|
||||
indent
|
||||
name="Turret traversing"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionTurretTraversing}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
prefix="+ "
|
||||
indent
|
||||
name="After shooting"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionShooting}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={1}
|
||||
prefix="x "
|
||||
indent
|
||||
name="Gun damaged"
|
||||
unit="scalar"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionGunDamaged}
|
||||
</InfoWithDelta>
|
||||
<Info name="Gun flexibility" unit="°" />
|
||||
<InfoWithDelta decimals={1} indent name="Depression">
|
||||
{stats.gunDepression}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Elevation">
|
||||
{stats.gunElevation}
|
||||
</InfoWithDelta>
|
||||
{gunModelDefinition.pitch.front && (
|
||||
<>
|
||||
<InfoWithDelta decimals={1} indent name="Frontal depression">
|
||||
{stats.gunFrontalDepression!}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Frontal elevation">
|
||||
{stats.gunFrontalElevation!}
|
||||
</InfoWithDelta>
|
||||
</>
|
||||
)}
|
||||
{gunModelDefinition.pitch.back && (
|
||||
<>
|
||||
<InfoWithDelta decimals={1} indent name="Rear depression">
|
||||
{stats.gunRearDepression!}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Rear elevation">
|
||||
{stats.gunRearElevation!}
|
||||
</InfoWithDelta>
|
||||
</>
|
||||
)}
|
||||
{turretModelDefinition.yaw && (
|
||||
<>
|
||||
<InfoWithDelta decimals={1} indent name="Azimuth left">
|
||||
{stats.azimuthLeft!}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Azimuth right">
|
||||
{stats.azimuthRight!}
|
||||
</InfoWithDelta>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading size="5">Maneuverability</Heading>
|
||||
<Info name="Speed" unit="km/hr" />
|
||||
<InfoWithDelta decimals={0} indent name="Forwards">
|
||||
{stats.speedForwards}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={0} indent name="Backwards">
|
||||
{stats.speedBackwards}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={0} name="Engine power" unit="hp">
|
||||
{stats.enginePower}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={1}
|
||||
name="Weight"
|
||||
unit="tn"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.weight}
|
||||
</InfoWithDelta>
|
||||
<Info name="Power to weight ratio" unit="hp/tn" />
|
||||
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
||||
{stats.powerToWeightRatioHardTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
||||
{stats.powerToWeightRatioMediumTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
||||
{stats.powerToWeightRatioSoftTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta name="Turret traverse speed" unit="°/s" decimals={1}>
|
||||
{stats.turretTraverseSpeed}
|
||||
</InfoWithDelta>
|
||||
<Info name="Hull traverse speed" unit="°/s" />
|
||||
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
||||
{stats.hullTraverseHardTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
||||
{stats.hullTraverseMediumTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
||||
{stats.hullTraverseSoftTerrain}
|
||||
</InfoWithDelta>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading size="5">Survivability</Heading>
|
||||
<InfoWithDelta name="Health" unit="hp" decimals={0}>
|
||||
{stats.health}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
name="Fire chance"
|
||||
unit="%"
|
||||
deltaType="lowerIsBetter"
|
||||
decimals={0}
|
||||
>
|
||||
{stats.fireChance * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta name="View range" unit="m" decimals={0}>
|
||||
{stats.viewRange}
|
||||
</InfoWithDelta>
|
||||
<Info name="Camouflage" unit="%" />
|
||||
<InfoWithDelta indent name="Still" decimals={0}>
|
||||
{stats.camouflageStill * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta indent name="Moving" decimals={0}>
|
||||
{stats.camouflageMoving * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta indent name="Shooting still" decimals={0}>
|
||||
{stats.camouflageShootingStill * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta indent name="Shooting moving" decimals={0}>
|
||||
{stats.camouflageShootingMoving * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta indent name="Caught on fire" decimals={0}>
|
||||
{stats.camouflageCaughtOnFire * 100}
|
||||
</InfoWithDelta>
|
||||
<Info name="Width" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||
{stats.width}
|
||||
</Info>
|
||||
<Info name="Height" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||
{stats.height}
|
||||
</Info>
|
||||
<Info name="Length" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||
{stats.length}
|
||||
</Info>
|
||||
<Info name="Volume" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||
{stats.volume}
|
||||
</Info>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="2">
|
||||
<Flex gap="2" align="center">
|
||||
<Heading size="5">Crew training</Heading>
|
||||
|
||||
<Popover.Root>
|
||||
<Popover.Trigger>
|
||||
<IconButton variant="ghost">
|
||||
<InfoCircledIcon />
|
||||
</IconButton>
|
||||
</Popover.Trigger>
|
||||
|
||||
<Popover.Content>
|
||||
<Flex gap="1" align="center">
|
||||
<AccessibilityIcon />
|
||||
<Text>represents the other roles a crew member can take.</Text>
|
||||
</Flex>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
</Flex>
|
||||
|
||||
{tank.crew.map((member) => {
|
||||
const count = member.count ?? 1;
|
||||
|
||||
return (
|
||||
<Fragment key={member.type}>
|
||||
{typeof shell.penetration !== 'number' && (
|
||||
<>
|
||||
<InfoWithDelta
|
||||
key={`${member.type}-root`}
|
||||
name={`${CREW_MEMBER_NAMES[member.type]}${count > 1 ? ` x ${count}` : ''}`}
|
||||
unit="%"
|
||||
indent
|
||||
decimals={0}
|
||||
name={`At ${penetrationDistance}m`}
|
||||
>
|
||||
{(member.type === 'commander'
|
||||
? commanderMastery
|
||||
: crewMemberMastery) * 100}
|
||||
{lerp(
|
||||
shell.penetration[0],
|
||||
shell.penetration[1],
|
||||
(penetrationDistance *
|
||||
penetrationLossOverDistanceCoefficient) /
|
||||
500,
|
||||
) * penetrationCoefficient}
|
||||
</InfoWithDelta>
|
||||
{member.substitute && (
|
||||
<Info
|
||||
key={`${member.type}-substitute`}
|
||||
indent
|
||||
name={
|
||||
<>
|
||||
<Flex align="center" gap="1">
|
||||
<AccessibilityIcon />
|
||||
{member.substitute
|
||||
.map((sub, index) =>
|
||||
index === 0 ? CREW_MEMBER_NAMES[sub] : sub,
|
||||
)
|
||||
.join(', ')}
|
||||
</Flex>
|
||||
</>
|
||||
}
|
||||
<Flex align="center" gap="2" style={{ paddingLeft: 24 }}>
|
||||
<Text>Distance</Text>
|
||||
<Slider
|
||||
key={penetrationDistance}
|
||||
min={0}
|
||||
max={500}
|
||||
style={{ flex: 1 }}
|
||||
defaultValue={[penetrationDistance]}
|
||||
onValueChange={debounce(([value]) => {
|
||||
setPenetrationDistance(value);
|
||||
}, 500)}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
<TextField.Root
|
||||
style={{ width: 64 }}
|
||||
ref={penetrationDistanceInput}
|
||||
defaultValue={penetrationDistance}
|
||||
onBlur={() => {
|
||||
setPenetrationDistance(
|
||||
Math.min(
|
||||
parseInt(penetrationDistanceInput.current!.value),
|
||||
500,
|
||||
),
|
||||
);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TextField.Slot side="right">m</TextField.Slot>
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
<InfoWithDelta name="Damage" unit="hp" decimals={0}>
|
||||
{stats.damage}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta name="Module damage" unit="hp" decimals={0}>
|
||||
{stats.moduleDamage}
|
||||
</InfoWithDelta>
|
||||
{isExplosive(shell.type) && (
|
||||
<InfoWithDelta name="Splash radius" unit="m" decimals={0}>
|
||||
{stats.explosionRadius!}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
|
||||
{stats.caliber}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta name="Normalization" decimals={0} unit="°">
|
||||
{stats.shellNormalization}
|
||||
</InfoWithDelta>
|
||||
{shell.type !== 'he' && (
|
||||
<InfoWithDelta
|
||||
name="Ricochet"
|
||||
decimals={0}
|
||||
deltaType="lowerIsBetter"
|
||||
unit="°"
|
||||
>
|
||||
{stats.shellRicochet!}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
<InfoWithDelta name="Shell velocity" unit="m/s" decimals={0}>
|
||||
{stats.shellVelocity}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={2}
|
||||
name="Aim time"
|
||||
unit="s"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.aimTime}
|
||||
</InfoWithDelta>
|
||||
<Info name="Dispersion at 100m" />
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
indent
|
||||
name="Still"
|
||||
unit="m"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersion}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
prefix="+ "
|
||||
decimals={3}
|
||||
indent
|
||||
name="Moving"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionMoving}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
prefix="+ "
|
||||
indent
|
||||
name="Hull traversing"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionHullTraversing}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
prefix="+ "
|
||||
indent
|
||||
name="Turret traversing"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionTurretTraversing}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
prefix="+ "
|
||||
indent
|
||||
name="After shooting"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionShooting}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={1}
|
||||
prefix="x "
|
||||
indent
|
||||
name="Gun damaged"
|
||||
unit="scalar"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionGunDamaged}
|
||||
</InfoWithDelta>
|
||||
<Info name="Gun flexibility" unit="°" />
|
||||
<InfoWithDelta decimals={1} indent name="Depression">
|
||||
{stats.gunDepression}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Elevation">
|
||||
{stats.gunElevation}
|
||||
</InfoWithDelta>
|
||||
{gunModelDefinition.pitch.front && (
|
||||
<>
|
||||
<InfoWithDelta decimals={1} indent name="Frontal depression">
|
||||
{stats.gunFrontalDepression!}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Frontal elevation">
|
||||
{stats.gunFrontalElevation!}
|
||||
</InfoWithDelta>
|
||||
</>
|
||||
)}
|
||||
{gunModelDefinition.pitch.back && (
|
||||
<>
|
||||
<InfoWithDelta decimals={1} indent name="Rear depression">
|
||||
{stats.gunRearDepression!}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Rear elevation">
|
||||
{stats.gunRearElevation!}
|
||||
</InfoWithDelta>
|
||||
</>
|
||||
)}
|
||||
{turretModelDefinition.yaw && (
|
||||
<>
|
||||
<InfoWithDelta decimals={1} indent name="Azimuth left">
|
||||
{stats.azimuthLeft!}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Azimuth right">
|
||||
{stats.azimuthRight!}
|
||||
</InfoWithDelta>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex direction="column" gap="2">
|
||||
<Flex gap="2" align="center">
|
||||
<Heading size="5">Crew training</Heading>
|
||||
|
||||
<Popover.Root>
|
||||
<Popover.Trigger>
|
||||
<IconButton variant="ghost">
|
||||
<InfoCircledIcon />
|
||||
</IconButton>
|
||||
</Popover.Trigger>
|
||||
|
||||
<Popover.Content>
|
||||
<Flex gap="1" align="center">
|
||||
<AccessibilityIcon />
|
||||
<Text>
|
||||
represents the other roles a crew member can take.
|
||||
</Text>
|
||||
</Flex>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
</Flex>
|
||||
|
||||
{tank.crew.map((member) => {
|
||||
const count = member.count ?? 1;
|
||||
|
||||
return (
|
||||
<Fragment key={member.type}>
|
||||
<InfoWithDelta
|
||||
key={`${member.type}-root`}
|
||||
name={`${CREW_MEMBER_NAMES[member.type]}${count > 1 ? ` x ${count}` : ''}`}
|
||||
unit="%"
|
||||
decimals={0}
|
||||
>
|
||||
{(member.type === 'commander'
|
||||
? commanderMastery
|
||||
: crewMemberMastery) * 100}
|
||||
</InfoWithDelta>
|
||||
{member.substitute && (
|
||||
<Info
|
||||
key={`${member.type}-substitute`}
|
||||
indent
|
||||
name={
|
||||
<>
|
||||
<Flex align="center" gap="1">
|
||||
<AccessibilityIcon />
|
||||
{member.substitute
|
||||
.map((sub, index) =>
|
||||
index === 0 ? CREW_MEMBER_NAMES[sub] : sub,
|
||||
)
|
||||
.join(', ')}
|
||||
</Flex>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="8" style={{ flex: 1 }}>
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading size="5">Maneuverability</Heading>
|
||||
<Info name="Speed" unit="km/hr" />
|
||||
<InfoWithDelta decimals={0} indent name="Forwards">
|
||||
{stats.speedForwards}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={0} indent name="Backwards">
|
||||
{stats.speedBackwards}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={0} name="Engine power" unit="hp">
|
||||
{stats.enginePower}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={1}
|
||||
name="Weight"
|
||||
unit="tn"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.weight}
|
||||
</InfoWithDelta>
|
||||
<Info name="Power to weight ratio" unit="hp/tn" />
|
||||
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
||||
{stats.powerToWeightRatioHardTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
||||
{stats.powerToWeightRatioMediumTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
||||
{stats.powerToWeightRatioSoftTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta name="Turret traverse speed" unit="°/s" decimals={1}>
|
||||
{stats.turretTraverseSpeed}
|
||||
</InfoWithDelta>
|
||||
<Info name="Hull traverse speed" unit="°/s" />
|
||||
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
||||
{stats.hullTraverseHardTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
||||
{stats.hullTraverseMediumTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
||||
{stats.hullTraverseSoftTerrain}
|
||||
</InfoWithDelta>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading size="5">Survivability</Heading>
|
||||
<InfoWithDelta name="Health" unit="hp" decimals={0}>
|
||||
{stats.health}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
name="Fire chance"
|
||||
unit="%"
|
||||
deltaType="lowerIsBetter"
|
||||
decimals={0}
|
||||
>
|
||||
{stats.fireChance * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta name="View range" unit="m" decimals={0}>
|
||||
{stats.viewRange}
|
||||
</InfoWithDelta>
|
||||
<Info name="Camouflage" unit="%" />
|
||||
<InfoWithDelta indent name="Still" decimals={0}>
|
||||
{stats.camouflageStill * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta indent name="Moving" decimals={0}>
|
||||
{stats.camouflageMoving * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta indent name="Shooting still" decimals={0}>
|
||||
{stats.camouflageShootingStill * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta indent name="Shooting moving" decimals={0}>
|
||||
{stats.camouflageShootingMoving * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta indent name="Caught on fire" decimals={0}>
|
||||
{stats.camouflageCaughtOnFire * 100}
|
||||
</InfoWithDelta>
|
||||
<Info name="Width" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||
{stats.width}
|
||||
</Info>
|
||||
<Info name="Height" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||
{stats.height}
|
||||
</Info>
|
||||
<Info name="Length" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||
{stats.length}
|
||||
</Info>
|
||||
<Info name="Volume" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||
{stats.volume}
|
||||
</Info>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Flex, Heading } from '@radix-ui/themes';
|
||||
import PageWrapper from '../../../../../../components/PageWrapper';
|
||||
import { useWideFormat } from '../../../../../../hooks/useWideFormat';
|
||||
import { Characteristics } from '../Characteristics';
|
||||
import { Consumables } from '../Characteristics/components/Consumables';
|
||||
@@ -10,16 +9,25 @@ import { Provisions } from '../Characteristics/components/Provisions';
|
||||
import { Skills } from '../Characteristics/components/Skills';
|
||||
|
||||
export function CharacteristicsSection() {
|
||||
const wideFormat = useWideFormat(720);
|
||||
const wideFormat = useWideFormat(700);
|
||||
|
||||
return (
|
||||
<PageWrapper >
|
||||
<Flex
|
||||
align="center"
|
||||
px="4"
|
||||
style={{
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
direction={wideFormat ? 'row' : 'column'}
|
||||
mt="4"
|
||||
>
|
||||
<Flex
|
||||
gap="6"
|
||||
style={{ width: '100%' }}
|
||||
direction={wideFormat ? 'row' : 'column'}
|
||||
justify="center"
|
||||
>
|
||||
<Flex style={{ flex: 1, maxWidth: 480 }} direction="column" gap="4">
|
||||
<Flex style={{ flex: 1, maxWidth: 380 }} direction="column" gap="4">
|
||||
<Heading>Configure</Heading>
|
||||
<Modules />
|
||||
<Equipment />
|
||||
@@ -30,7 +38,11 @@ export function CharacteristicsSection() {
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
style={{ flex: 1, width: wideFormat ? undefined : '100%' }}
|
||||
style={{
|
||||
flex: 1,
|
||||
maxWidth: wideFormat ? 640 : undefined,
|
||||
minWidth: 320,
|
||||
}}
|
||||
direction="column"
|
||||
gap="4"
|
||||
>
|
||||
@@ -38,6 +50,6 @@ export function CharacteristicsSection() {
|
||||
<Characteristics />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</PageWrapper>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -25,7 +25,7 @@ export function HeroSection() {
|
||||
style={{
|
||||
background: `linear-gradient(${wideFormat ? -90 : 180}deg, ${theme.colors.appBackground1}, ${theme.colors.appBackground2})`,
|
||||
position: 'relative',
|
||||
height: wideFormat ? undefined : 'calc(87.5vh - 64px)',
|
||||
height: wideFormat ? undefined : 'calc(75vh - 64px)',
|
||||
}}
|
||||
gap="4"
|
||||
>
|
||||
|
||||
@@ -124,7 +124,12 @@ export function TechTreeSection() {
|
||||
<PageWrapper
|
||||
style={{
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
containerProps={{
|
||||
style: {
|
||||
overflow: 'hidden',
|
||||
marginTop: 16,
|
||||
},
|
||||
}}
|
||||
size={tank.ancestors && tank.successors ? 880 : 480}
|
||||
gap="0"
|
||||
|
||||
@@ -8,6 +8,7 @@ type PageWrapperProps = FlexProps & {
|
||||
noPadding?: boolean;
|
||||
noMaxWidth?: boolean;
|
||||
highlight?: boolean;
|
||||
containerProps?: ComponentProps<typeof Theme>;
|
||||
};
|
||||
|
||||
export default function PageWrapper({
|
||||
@@ -18,6 +19,7 @@ export default function PageWrapper({
|
||||
noPadding = false,
|
||||
noMaxWidth = false,
|
||||
highlight = false,
|
||||
containerProps,
|
||||
...props
|
||||
}: PageWrapperProps) {
|
||||
return (
|
||||
@@ -30,7 +32,9 @@ export default function PageWrapper({
|
||||
background: highlight
|
||||
? `linear-gradient(-90deg, ${theme.colors.appBackground1}, ${theme.colors.appBackground2})`
|
||||
: undefined,
|
||||
...containerProps?.style,
|
||||
}}
|
||||
{...containerProps}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
|
||||
Reference in new issue
Block a user