mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: new layout
This commit is contained in:
1 parent
7554f1ffed
commit
eb53b59e32
10 files changed
+334
-27
No files matched your search
@@ -56,7 +56,7 @@ export function HeroSection() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading size={wideFormat ? '9' : '8'}>
|
<Heading size={wideFormat ? '9' : '8'}>
|
||||||
{protagonistTank.name}
|
{protagonistTank.nameFull ?? protagonistTank.name}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<Text color="gray">
|
<Text color="gray">
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { TreeType } from '../../../../../components/Tanks';
|
||||||
|
|
||||||
|
export const treeTypeOrder: TreeType[] = [
|
||||||
|
'researchable',
|
||||||
|
'premium',
|
||||||
|
'collector',
|
||||||
|
];
|
||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
import { go } from 'fuzzysort';
|
import { go } from 'fuzzysort';
|
||||||
import { use, useEffect, useMemo, useRef, useState } from 'react';
|
import { use, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { isMobileOnly } from 'react-device-detect';
|
import { isMobileOnly } from 'react-device-detect';
|
||||||
import { TreeType } from '../../../../../components/Tanks';
|
|
||||||
import { TANK_CLASSES } from '../../../../../components/Tanks/components/Item/constants';
|
import { TANK_CLASSES } from '../../../../../components/Tanks/components/Item/constants';
|
||||||
import { resolveNearPenetration } from '../../../../../core/blitz/resolveNearPenetration';
|
import { resolveNearPenetration } from '../../../../../core/blitz/resolveNearPenetration';
|
||||||
import { modelDefinitions } from '../../../../../core/blitzkit/modelDefinitions';
|
import { modelDefinitions } from '../../../../../core/blitzkit/modelDefinitions';
|
||||||
@@ -35,6 +34,7 @@ import { PageTurner } from '../PageTurner';
|
|||||||
import { Options } from './components/Options';
|
import { Options } from './components/Options';
|
||||||
import { Results } from './components/Results';
|
import { Results } from './components/Results';
|
||||||
import { Sort } from './components/Sort';
|
import { Sort } from './components/Sort';
|
||||||
|
import { treeTypeOrder } from './constants';
|
||||||
|
|
||||||
interface TankSearchProps {
|
interface TankSearchProps {
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
@@ -42,8 +42,6 @@ interface TankSearchProps {
|
|||||||
onSelectAll?: (tanks: TankDefinition[]) => void;
|
onSelectAll?: (tanks: TankDefinition[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const treeTypeOrder: TreeType[] = ['researchable', 'premium', 'collector'];
|
|
||||||
|
|
||||||
export function TankSearch({
|
export function TankSearch({
|
||||||
compact,
|
compact,
|
||||||
onSelect = () => {},
|
onSelect = () => {},
|
||||||
|
|||||||
@@ -1,32 +1,160 @@
|
|||||||
'use client';
|
import { CaretRightIcon } from '@radix-ui/react-icons';
|
||||||
|
import { Button, Flex, Heading, Text, TextField } from '@radix-ui/themes';
|
||||||
import { Flex } from '@radix-ui/themes';
|
import { range } from 'lodash';
|
||||||
import { times } from 'lodash';
|
import { use } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { classIcons } from '../../../components/ClassIcon';
|
||||||
import { Ad, AdType } from '../../../components/Ad';
|
import { Link } from '../../../components/Link';
|
||||||
import PageWrapper from '../../../components/PageWrapper';
|
import PageWrapper from '../../../components/PageWrapper';
|
||||||
import { useAdExempt } from '../../../hooks/useAdExempt';
|
import { TANK_CLASSES } from '../../../components/Tanks/components/Item/constants';
|
||||||
import { TankSearch } from './components/TankSearch';
|
import { asset } from '../../../core/blitzkit/asset';
|
||||||
|
import {
|
||||||
|
NATIONS,
|
||||||
|
Tier,
|
||||||
|
tanksDefinitionsArray,
|
||||||
|
} from '../../../core/blitzkit/tankDefinitions';
|
||||||
|
import { TIER_ROMAN_NUMERALS } from '../../../core/blitzkit/tankDefinitions/constants';
|
||||||
|
import { tankIcon } from '../../../core/blitzkit/tankIcon';
|
||||||
|
import { treeTypeOrder } from './components/TankSearch/constants';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const router = useRouter();
|
const awaitedTanksDefinitionsArray = use(tanksDefinitionsArray);
|
||||||
const exempt = useAdExempt();
|
const awaitedNations = use(NATIONS);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper size={1200} color="purple">
|
<PageWrapper color="purple">
|
||||||
<TankSearch
|
<TextField.Root size="3" placeholder="Search tanks...">
|
||||||
onSelect={(tank) => {
|
<TextField.Slot />
|
||||||
router.push(`./tankopedia/${tank.id}`);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!exempt && (
|
<TextField.Slot>
|
||||||
<Flex justify="center" wrap="wrap">
|
<Button size="3" variant="ghost">
|
||||||
{times(2, (index) => (
|
M48 Patton <CaretRightIcon />
|
||||||
<Ad key={index} type={AdType.MediumRectangleHorizontalPurple} />
|
</Button>
|
||||||
))}
|
</TextField.Slot>
|
||||||
</Flex>
|
</TextField.Root>
|
||||||
)}
|
|
||||||
|
<Flex direction="column" gap="8" mt="4">
|
||||||
|
{range(10, 0).map((tierUntyped) => {
|
||||||
|
const tier = tierUntyped as Tier;
|
||||||
|
const tierTanks = awaitedTanksDefinitionsArray.filter(
|
||||||
|
(tank) => tank.tier === tier,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex key={tier} direction="column" gap="4">
|
||||||
|
<Heading>Tier {TIER_ROMAN_NUMERALS[tier]}</Heading>
|
||||||
|
|
||||||
|
<Flex direction="column" gap="4">
|
||||||
|
{awaitedNations.map((nation, nationIndex) => {
|
||||||
|
const nationTanks = tierTanks
|
||||||
|
.filter((tank) => tank.nation === nation)
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
treeTypeOrder.indexOf(a.treeType) -
|
||||||
|
treeTypeOrder.indexOf(b.treeType),
|
||||||
|
)
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
TANK_CLASSES.indexOf(a.class) -
|
||||||
|
TANK_CLASSES.indexOf(b.class),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nationTanks.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{nationIndex > 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '0.5px',
|
||||||
|
background: 'var(--gray-6)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Flex key={nation} align="center">
|
||||||
|
<img
|
||||||
|
alt={nation}
|
||||||
|
src={asset(`flags/scratched/${nation}.webp`)}
|
||||||
|
width={128}
|
||||||
|
height={64}
|
||||||
|
style={{
|
||||||
|
objectFit: 'contain',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Flex wrap="wrap" gap="2">
|
||||||
|
{nationTanks.map((tank) => {
|
||||||
|
const Icon = classIcons[tank.class];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
size="1"
|
||||||
|
color={
|
||||||
|
tank.treeType === 'collector'
|
||||||
|
? 'blue'
|
||||||
|
: tank.treeType === 'premium'
|
||||||
|
? 'amber'
|
||||||
|
: 'gray'
|
||||||
|
}
|
||||||
|
highContrast={tank.treeType === 'researchable'}
|
||||||
|
underline="hover"
|
||||||
|
key={tank.id}
|
||||||
|
href={`/tools/tankopedia/${tank.id}`}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
direction="column"
|
||||||
|
align="center"
|
||||||
|
width="80px"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt={tank.name}
|
||||||
|
src={tankIcon(tank.id)}
|
||||||
|
width={64}
|
||||||
|
height={64}
|
||||||
|
style={{
|
||||||
|
objectFit: 'contain',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
justify="center"
|
||||||
|
gap="1"
|
||||||
|
align="center"
|
||||||
|
overflow="hidden"
|
||||||
|
width="100%"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
style={{
|
||||||
|
width: '1em',
|
||||||
|
height: '1em',
|
||||||
|
minWidth: '1em',
|
||||||
|
minHeight: '1em',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
align="center"
|
||||||
|
style={{
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tank.name}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { ComponentProps } from 'react';
|
||||||
|
|
||||||
|
export function ClassHeavy(props: ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<g clip-path="url(#clip0_70_177)">
|
||||||
|
<g clip-path="url(#clip1_70_177)">
|
||||||
|
<rect
|
||||||
|
y="32"
|
||||||
|
width="45.2548"
|
||||||
|
height="12.4183"
|
||||||
|
rx="6.20914"
|
||||||
|
transform="rotate(-45 0 32)"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="11.6095"
|
||||||
|
y="43.6095"
|
||||||
|
width="45.2548"
|
||||||
|
height="12.4183"
|
||||||
|
rx="6.20914"
|
||||||
|
transform="rotate(-45 11.6095 43.6095)"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="23.2189"
|
||||||
|
y="55.219"
|
||||||
|
width="45.2548"
|
||||||
|
height="12.4183"
|
||||||
|
rx="6.20914"
|
||||||
|
transform="rotate(-45 23.2189 55.219)"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_70_177">
|
||||||
|
<rect width="64" height="64" fill="white" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip1_70_177">
|
||||||
|
<rect
|
||||||
|
width="45.2548"
|
||||||
|
height="45.2548"
|
||||||
|
fill="white"
|
||||||
|
transform="translate(0 32) rotate(-45)"
|
||||||
|
/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentProps } from 'react';
|
||||||
|
|
||||||
|
export function ClassLight(props: ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
y="32"
|
||||||
|
width="45.2548"
|
||||||
|
height="45.2548"
|
||||||
|
rx="8"
|
||||||
|
transform="rotate(-45 0 32)"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { ComponentProps } from 'react';
|
||||||
|
|
||||||
|
export function ClassMedium(props: ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<g clip-path="url(#clip0_70_149)">
|
||||||
|
<g clip-path="url(#clip1_70_149)">
|
||||||
|
<rect
|
||||||
|
y="32"
|
||||||
|
width="45.2548"
|
||||||
|
height="20.6274"
|
||||||
|
rx="8"
|
||||||
|
transform="rotate(-45 0 32)"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="17.4142"
|
||||||
|
y="49.4142"
|
||||||
|
width="45.2548"
|
||||||
|
height="20.6274"
|
||||||
|
rx="8"
|
||||||
|
transform="rotate(-45 17.4142 49.4142)"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_70_149">
|
||||||
|
<rect width="64" height="64" fill="white" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip1_70_149">
|
||||||
|
<rect
|
||||||
|
width="45.2548"
|
||||||
|
height="45.2548"
|
||||||
|
fill="white"
|
||||||
|
transform="translate(0 32) rotate(-45)"
|
||||||
|
/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { ComponentProps } from 'react';
|
||||||
|
|
||||||
|
export function ClassTankDestroyer(props: ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M36.6355 54.572C34.7073 57.886 29.9201 57.8861 27.992 54.572L4.68577 14.5145C2.74639 11.1812 5.15107 7 9.00751 7H55.6199C59.4764 7 61.881 11.1812 59.9417 14.5145L36.6355 54.572Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { ComponentProps, ReactNode } from 'react';
|
||||||
|
import { TankClass } from '../Tanks';
|
||||||
|
import { ClassHeavy } from './components/ClassHeavy';
|
||||||
|
import { ClassLight } from './components/ClassLight';
|
||||||
|
import { ClassMedium } from './components/ClassMedium';
|
||||||
|
import { ClassTankDestroyer } from './components/ClassTankDestroyer';
|
||||||
|
|
||||||
|
export const classIcons: Record<
|
||||||
|
TankClass,
|
||||||
|
(props: ComponentProps<'svg'>) => ReactNode
|
||||||
|
> = {
|
||||||
|
'AT-SPG': ClassTankDestroyer,
|
||||||
|
heavyTank: ClassHeavy,
|
||||||
|
lightTank: ClassLight,
|
||||||
|
mediumTank: ClassMedium,
|
||||||
|
};
|
||||||
@@ -19,18 +19,27 @@ export const GUN_TYPE_NAMES: Record<GunDefinition['type'], string> = {
|
|||||||
autoLoader: 'Auto loader',
|
autoLoader: 'Auto loader',
|
||||||
autoReloader: 'Auto reloader',
|
autoReloader: 'Auto reloader',
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* @deprecated use svg
|
||||||
|
*/
|
||||||
export const TANK_ICONS: Record<TankClass, string> = {
|
export const TANK_ICONS: Record<TankClass, string> = {
|
||||||
'AT-SPG': 'https://i.imgur.com/BIHSEH0.png',
|
'AT-SPG': 'https://i.imgur.com/BIHSEH0.png',
|
||||||
lightTank: 'https://i.imgur.com/CSNha5V.png',
|
lightTank: 'https://i.imgur.com/CSNha5V.png',
|
||||||
mediumTank: 'https://i.imgur.com/wvf3ltm.png',
|
mediumTank: 'https://i.imgur.com/wvf3ltm.png',
|
||||||
heavyTank: 'https://i.imgur.com/ECeqlZa.png',
|
heavyTank: 'https://i.imgur.com/ECeqlZa.png',
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* @deprecated use svg
|
||||||
|
*/
|
||||||
export const TANK_ICONS_PREMIUM: Record<TankClass, string> = {
|
export const TANK_ICONS_PREMIUM: Record<TankClass, string> = {
|
||||||
'AT-SPG': 'https://i.imgur.com/TCu3EdR.png',
|
'AT-SPG': 'https://i.imgur.com/TCu3EdR.png',
|
||||||
lightTank: 'https://i.imgur.com/zdkpTRb.png',
|
lightTank: 'https://i.imgur.com/zdkpTRb.png',
|
||||||
mediumTank: 'https://i.imgur.com/3z7eHX6.png',
|
mediumTank: 'https://i.imgur.com/3z7eHX6.png',
|
||||||
heavyTank: 'https://i.imgur.com/P3vbmyA.png',
|
heavyTank: 'https://i.imgur.com/P3vbmyA.png',
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* @deprecated use svg
|
||||||
|
*/
|
||||||
export const TANK_ICONS_COLLECTOR: Record<TankClass, string> = {
|
export const TANK_ICONS_COLLECTOR: Record<TankClass, string> = {
|
||||||
'AT-SPG': 'https://i.imgur.com/WTjeirB.png',
|
'AT-SPG': 'https://i.imgur.com/WTjeirB.png',
|
||||||
lightTank: 'https://i.imgur.com/EwhtKkU.png',
|
lightTank: 'https://i.imgur.com/EwhtKkU.png',
|
||||||
|
|||||||
Reference in new issue
Block a user