Merge branch 'main' into ue
No files matched your search
@@ -37,6 +37,7 @@ env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
|
||||
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"arrayfy",
|
||||
"artefacts",
|
||||
"astrojs",
|
||||
"astrolib",
|
||||
"atgm",
|
||||
"Autoloader",
|
||||
"Autoreloader",
|
||||
@@ -138,9 +139,11 @@
|
||||
"outfile",
|
||||
"ownedtanks",
|
||||
"paks",
|
||||
"partytown",
|
||||
"PIXELFORMAT",
|
||||
"playerachievements",
|
||||
"playerinfo",
|
||||
"playform",
|
||||
"pngjs",
|
||||
"POSITIVEX",
|
||||
"POSITIVEY",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# FreakKit 1.27.4
|
||||
|
||||
It has come to our attention that tanks are very harsh objects. To even it out, we made the website a pretty pretty princess <3
|
||||
@@ -0,0 +1,11 @@
|
||||
# BlitzKit 1.27.5
|
||||
|
||||
This update only affects the bot.
|
||||
|
||||
## Changes
|
||||
|
||||
- Removed `/auction`
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fixed a bug that caused `/rating` to disappear
|
||||
@@ -0,0 +1,23 @@
|
||||
# BlitzKit 1.27.6
|
||||
|
||||
This update only affects the website.
|
||||
|
||||
## Features
|
||||
|
||||
- Added a promotional card for [the official Discord server](https://discord.gg/nDt7AjGJQH) to the homepage
|
||||
- Added a better navbar
|
||||
- The added spaces and padding helps add more room for error when clicking, especially on mobile
|
||||
- The dropdown menu is now a lost smaller, disposing of the need to scroll at all
|
||||
- Added shell filters to the tank search
|
||||
- Reworked the filter buttons and their positions to allow for more space
|
||||
|
||||
## Fixes
|
||||
|
||||
- Moved most images from imgur to BlitzKit allowing for users in certain countries to load BlitzKit without getting blocked
|
||||
|
||||
## Technical Features
|
||||
|
||||
- Added Partytown to offload some workload off the main thread, allowing for a more responsive experience
|
||||
- Reworked `robots.txt` to disallow indexing of BlitzKit variants
|
||||
- Improved the SEO further for most pages
|
||||
- Added a few compressor library to speed up load times
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"private": true,
|
||||
"version": "1.27.3",
|
||||
"version": "1.27.6",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"type": "module",
|
||||
"types": "./types.d.ts",
|
||||
|
||||
@@ -16,8 +16,8 @@ export function CommandWrapper({ children, fat }: CommandWrapperProps) {
|
||||
gap: 16,
|
||||
width: '100%',
|
||||
color: theme.colors.textHighContrast,
|
||||
backgroundColor: theme.colors.appBackground1,
|
||||
position: 'relative',
|
||||
background: theme.colors.appBackground1,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -15,7 +15,8 @@ export function localizationObject(
|
||||
(localizations, locale) => {
|
||||
const { strings } = translator(locale);
|
||||
const translation = literalsFunc(path(strings), literals);
|
||||
const isValid = !strict || validNameRegex.test(translation);
|
||||
const isValid =
|
||||
(!strict || validNameRegex.test(translation)) && translation.length > 0;
|
||||
|
||||
if (!isValid) {
|
||||
console.warn(
|
||||
|
||||
@@ -19,11 +19,11 @@ import { Writeable } from '../../../types/writable';
|
||||
export async function handleChatInputCommand(
|
||||
interaction: ChatInputCommandInteraction<CacheType>,
|
||||
) {
|
||||
await interaction.deferReply();
|
||||
|
||||
const awaitedCommands = await commands;
|
||||
const registry = awaitedCommands[interaction.commandName];
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
try {
|
||||
const returnable = await registry.handler(interaction);
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
SlashCommandSubcommandsOnlyBuilder,
|
||||
} from 'discord.js';
|
||||
import { aboutCommand } from '../../commands/about';
|
||||
import { auctionCommand } from '../../commands/auction';
|
||||
import { breakdownCommand } from '../../commands/breakdown';
|
||||
import { debugCommand } from '../../commands/debug';
|
||||
import { evolutionCommand } from '../../commands/evolution';
|
||||
@@ -80,7 +79,7 @@ interface CommandRegistryExplicit extends CommandRegistryBase {
|
||||
export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit;
|
||||
|
||||
export const COMMANDS_RAW: Promise<CommandRegistry>[] = [
|
||||
auctionCommand,
|
||||
// auctionCommand,
|
||||
permissionsCommand,
|
||||
debugCommand,
|
||||
aboutCommand,
|
||||
|
||||
@@ -45,7 +45,7 @@ const interval = setInterval(async () => {
|
||||
const usernames = awaitedDiscoveredIdsDefinitions.count;
|
||||
|
||||
manager.broadcastEval(
|
||||
(client, { servers, users, channels, tanks, usernames }) => {
|
||||
async (client, { servers, users, channels, tanks, usernames }) => {
|
||||
const bios = [
|
||||
`Living in ${servers.toLocaleString()} servers`,
|
||||
`Serving ${users.toLocaleString()} users`,
|
||||
@@ -62,6 +62,13 @@ const interval = setInterval(async () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
for (const guild of client.guilds.cache.values()) {
|
||||
try {
|
||||
const botMember = await guild.members.fetch(client.user.id);
|
||||
await botMember.setNickname('BlitzKit');
|
||||
} catch {}
|
||||
}
|
||||
},
|
||||
{ context: { servers, channels, users, tanks, usernames } },
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ export const theme = {
|
||||
spaces: createSpaces(),
|
||||
|
||||
colors: {
|
||||
...createColors(slateDark),
|
||||
...createColors(mauveDark),
|
||||
...createColors(slateDarkA, 'alpha'),
|
||||
...createColors(slate, 'light'),
|
||||
|
||||
|
||||
@@ -756,6 +756,7 @@
|
||||
},
|
||||
|
||||
"tank_search": {
|
||||
"clear_filters": "Clear",
|
||||
"search_bar_hint": "Search tanks...",
|
||||
"recent": "Recently viewed",
|
||||
"select_all": "Select all",
|
||||
@@ -824,6 +825,12 @@
|
||||
},
|
||||
|
||||
"home": {
|
||||
"discord": {
|
||||
"join_short": "Join cool people!",
|
||||
"join_long": "Join super duper cool people!",
|
||||
"action": "Discord"
|
||||
},
|
||||
|
||||
"hero": {
|
||||
"credit": "By %s1 ❤️"
|
||||
},
|
||||
@@ -838,6 +845,13 @@
|
||||
"seo_description": "Tools for everything World of Tanks Blitz"
|
||||
},
|
||||
|
||||
"docs": {
|
||||
"patches": {
|
||||
"name": "BlitzKit Changelogs",
|
||||
"seo_description": "Changelogs for BlitzKit Discord bot and website"
|
||||
}
|
||||
},
|
||||
|
||||
"tools": {
|
||||
"api": {
|
||||
"name": "API",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { imgur } from '@blitzkit/core';
|
||||
import {
|
||||
CodeIcon,
|
||||
HeightIcon,
|
||||
@@ -63,7 +62,7 @@ function Content({ embed }: PageProps) {
|
||||
const embedStateStore = EmbedState.useStore();
|
||||
const config = embedConfigurations[embed] as EmbedConfig;
|
||||
const [backgroundImage, setBackgroundImage] = useState(
|
||||
imgur('SO13zur', { format: 'jpeg' }),
|
||||
'/assets/images/backgrounds/embed-default.webp',
|
||||
);
|
||||
const fileInput = useRef<HTMLInputElement>();
|
||||
const { strings } = useLocale();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
import { capitalize } from 'lodash-es';
|
||||
import EmbedLayout from '../../../../../../layouts/EmbedLayout.astro';
|
||||
import type { getStaticPaths } from '../index.astro';
|
||||
import { Page } from './_index';
|
||||
@@ -7,8 +6,6 @@ import { Page } from './_index';
|
||||
export { getStaticPaths } from '../index.astro';
|
||||
---
|
||||
|
||||
<title>BlitzKit - {capitalize(Astro.params.embed)} embed</title>
|
||||
|
||||
<EmbedLayout>
|
||||
<Page client:only="react" embed={Astro.params.embed} />
|
||||
</EmbedLayout>
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import { AstroSeo } from '@astrolib/seo';
|
||||
import type { GetStaticPaths } from 'astro';
|
||||
import { capitalize } from 'lodash-es';
|
||||
import { embedConfigurations } from '../../../../../constants/embeds';
|
||||
@@ -21,10 +22,24 @@ export const getStaticPaths = (() => {
|
||||
.flat();
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
const name = capitalize(Astro.params.embed);
|
||||
const title = `BlitzKit - ${name} embed`;
|
||||
const description = `BlitzKit ${name} embeds for streaming World of Tanks Blitz with extreme customization`;
|
||||
const { locale } = Astro.params;
|
||||
---
|
||||
|
||||
<title>BlitzKit - {capitalize(Astro.params.embed)} embed</title>
|
||||
<AstroSeo
|
||||
title={title}
|
||||
description={description}
|
||||
canonical={Astro.url.href}
|
||||
openGraph={{
|
||||
url: Astro.url.href,
|
||||
title,
|
||||
description,
|
||||
site_name: 'BlitzKit',
|
||||
}}
|
||||
twitter={{ cardType: 'summary_large_image' }}
|
||||
/>
|
||||
|
||||
<style is:global>
|
||||
@keyframes fade-out-border {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { imgur, ImgurSize } from '@blitzkit/core';
|
||||
import { Box, Flex, Link, Text } from '@radix-ui/themes';
|
||||
import { PreviewWrapper } from '../../../../components/Embeds/PreviewWrapper';
|
||||
import { LinkI18n } from '../../../../components/LinkI18n';
|
||||
@@ -85,7 +84,8 @@ function Content() {
|
||||
overflow="hidden"
|
||||
position="relative"
|
||||
style={{
|
||||
backgroundImage: `url(${imgur('SO13zur', { format: 'jpeg', size: ImgurSize.SmallThumbnail })})`,
|
||||
backgroundImage:
|
||||
'url(/assets/images/backgrounds/embed-default.webp)',
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// @ts-check
|
||||
|
||||
import partytown from '@astrojs/partytown';
|
||||
import react from '@astrojs/react';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
|
||||
import playformCompress from '@playform/compress';
|
||||
import playformInline from '@playform/inline';
|
||||
import compressor from 'astro-compressor';
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
export default defineConfig({
|
||||
@@ -24,7 +28,15 @@ export default defineConfig({
|
||||
concurrency: 8,
|
||||
},
|
||||
|
||||
integrations: [react(), sitemap()],
|
||||
integrations: [
|
||||
react(),
|
||||
sitemap(),
|
||||
partytown(),
|
||||
playformCompress(),
|
||||
compressor(),
|
||||
playformInline(),
|
||||
],
|
||||
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/partytown": "^2.1.4",
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@blitzkit/i18n": "workspace:*",
|
||||
"@nanostores/persistent": "^0.10.2",
|
||||
@@ -42,10 +44,13 @@
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@astrojs/react": "^3.6.2",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@playform/compress": "^0.1.9",
|
||||
"@playform/inline": "^0.1.2",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/three": "^0.160.0",
|
||||
"astro": "^4.16.7"
|
||||
"astro": "^4.16.7",
|
||||
"astro-compressor": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 611 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 404 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 443 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 715 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 676 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 346 KiB |
|
After Width: | Height: | Size: 406 KiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 200 KiB |
@@ -1,4 +1,3 @@
|
||||
import { imgur } from '@blitzkit/core';
|
||||
import { Flex, Heading, Link, Text } from '@radix-ui/themes';
|
||||
import { Var } from '../core/radix/var';
|
||||
|
||||
@@ -18,7 +17,7 @@ export function AesonPlug() {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={imgur('akrxfFC')}
|
||||
src="/assets/images/third-party/wotb-news.png"
|
||||
style={{ width: '6rem', height: '6rem', objectFit: 'cover' }}
|
||||
/>
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export function BlitzKitTheme({
|
||||
}: BlitzKitThemeProps) {
|
||||
return (
|
||||
<Theme
|
||||
style={{ minHeight: 'unset', ...style }}
|
||||
style={{ minHeight: 'unset', display: 'contents', ...style }}
|
||||
className={`blitzkit-theme ${className}`}
|
||||
grayColor="mauve"
|
||||
appearance="dark"
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
:root {
|
||||
--scroll-time: 80s;
|
||||
}
|
||||
|
||||
.scroller-0 {
|
||||
animation: move-1 var(--scroll-time) linear infinite;
|
||||
}
|
||||
|
||||
.scroller-1 {
|
||||
animation-delay: var(--scroll-time);
|
||||
animation: move-1 var(--scroll-time) linear infinite;
|
||||
}
|
||||
|
||||
@keyframes move-1 {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
import { CaretRightIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Button, Flex, Link, Text } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { getStrings } from '../../core/i18n/getStrings';
|
||||
import { Var } from '../../core/radix/var';
|
||||
import type { LocaleAcceptorProps } from '../../hooks/useLocale';
|
||||
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
|
||||
import { InlineSkeleton } from '../InlineSkeleton';
|
||||
import './index.css';
|
||||
|
||||
export interface DiscordWidget {
|
||||
id: string;
|
||||
name: string;
|
||||
instant_invite: string;
|
||||
channels: any[];
|
||||
members: Member[];
|
||||
presence_count: number;
|
||||
}
|
||||
|
||||
export interface Member {
|
||||
id: string;
|
||||
username: string;
|
||||
discriminator: string;
|
||||
avatar: null;
|
||||
status: Status;
|
||||
avatar_url: string;
|
||||
game?: Game;
|
||||
}
|
||||
|
||||
export interface Game {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export enum Status {
|
||||
DND = 'dnd',
|
||||
Idle = 'idle',
|
||||
Online = 'online',
|
||||
}
|
||||
|
||||
const COLUMN_COUNT = 3;
|
||||
|
||||
const discordServerId = assertSecret(import.meta.env.PUBLIC_DISCORD_SERVER_ID);
|
||||
const widget = await fetch(
|
||||
`https://discord.com/api/guilds/${discordServerId}/widget.json`,
|
||||
).then((response) => response.json() as Promise<DiscordWidget>);
|
||||
const filteredMembers = widget.members
|
||||
.filter((member) => {
|
||||
const isPrivateUsername =
|
||||
member.username.length === 4 && member.username.endsWith('...');
|
||||
return !isPrivateUsername;
|
||||
})
|
||||
.sort(() => Math.random() - 0.5);
|
||||
|
||||
const columnSize = Math.floor(filteredMembers.length / COLUMN_COUNT);
|
||||
const columns = times(COLUMN_COUNT, (index) => {
|
||||
const start = index * columnSize;
|
||||
const end = start + columnSize;
|
||||
return filteredMembers.slice(start, end);
|
||||
});
|
||||
|
||||
function Member({
|
||||
id,
|
||||
avatar_url,
|
||||
username,
|
||||
status,
|
||||
skeleton,
|
||||
}: Member & MaybeSkeletonComponentProps) {
|
||||
let statusColor: string;
|
||||
|
||||
if (skeleton) {
|
||||
statusColor = Var('gray-9');
|
||||
} else {
|
||||
switch (status) {
|
||||
case Status.DND:
|
||||
statusColor = Var('red-9');
|
||||
break;
|
||||
|
||||
case Status.Idle:
|
||||
statusColor = Var('amber-9');
|
||||
break;
|
||||
|
||||
case Status.Online:
|
||||
statusColor = Var('jade-9');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={{
|
||||
maxWidth: '8rem',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
color="gray"
|
||||
>
|
||||
<Flex key={id} align="center" gap="2" flexShrink="0" width="8rem">
|
||||
<Box width="1.5em" height="1.5em" flexShrink="0" position="relative">
|
||||
{!skeleton && (
|
||||
<img
|
||||
src={avatar_url}
|
||||
alt={`${username} profile picture`}
|
||||
style={{ width: '100%', height: '100%', borderRadius: '50%' }}
|
||||
/>
|
||||
)}
|
||||
{skeleton && (
|
||||
<Box
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
background: Var('gray-6'),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Box
|
||||
width="0.5em"
|
||||
height="0.5em"
|
||||
position="absolute"
|
||||
bottom="0"
|
||||
right="0"
|
||||
style={{ borderRadius: '50%', backgroundColor: statusColor }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{skeleton && <InlineSkeleton style={{ flex: 1 }} />}
|
||||
{!skeleton && username}
|
||||
</Flex>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
function Scroller({
|
||||
index,
|
||||
skeleton,
|
||||
}: { index: number } & MaybeSkeletonComponentProps) {
|
||||
return (
|
||||
<Flex gap="6" className={`scroller-${index}`} pt="2">
|
||||
{columns.map((column, index) => (
|
||||
<Flex direction="column" gap="2" key={index}>
|
||||
{column.map((member) => (
|
||||
<Member skeleton={skeleton} key={member.id} {...member} />
|
||||
))}
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export function DiscordPlug({
|
||||
locale,
|
||||
skeleton,
|
||||
}: LocaleAcceptorProps & MaybeSkeletonComponentProps) {
|
||||
const strings = getStrings(locale);
|
||||
|
||||
return (
|
||||
<Flex justify="center" flexGrow="1" flexShrink="0">
|
||||
<Link
|
||||
style={{ display: 'contents' }}
|
||||
href="https://discord.gg/nDt7AjGJQH"
|
||||
target="_blank"
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
maxWidth="26rem"
|
||||
overflow="hidden"
|
||||
style={{
|
||||
borderRadius: Var('radius-2'),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
height="5rem"
|
||||
style={{
|
||||
perspective: '32rem',
|
||||
background: `linear-gradient(90deg, ${Var('indigo-2')}, ${Var('blue-2')}, ${Var('purple-2')})`,
|
||||
}}
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
>
|
||||
<Flex
|
||||
top="-250%"
|
||||
position="absolute"
|
||||
direction="column"
|
||||
width="fit-content"
|
||||
height="32rem"
|
||||
flexGrow="1"
|
||||
overflow="hidden"
|
||||
style={{
|
||||
transform: 'rotateX(60deg) rotateZ(-20deg)',
|
||||
}}
|
||||
>
|
||||
<Scroller skeleton={skeleton} index={0} />
|
||||
<Scroller skeleton={skeleton} index={1} />
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
<Flex
|
||||
align="center"
|
||||
justify="between"
|
||||
p="3"
|
||||
gap="4"
|
||||
style={{
|
||||
background: `linear-gradient(90deg, ${Var('indigo-3')}, ${Var('blue-3')}, ${Var('purple-3')})`,
|
||||
}}
|
||||
>
|
||||
<Box display={{ initial: 'block', xs: 'none' }}>
|
||||
<Text color="gray" highContrast>
|
||||
{strings.website.home.discord.join_short}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box display={{ initial: 'none', xs: 'block' }}>
|
||||
<Text color="gray" highContrast>
|
||||
{strings.website.home.discord.join_long}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Button color="iris">
|
||||
{strings.website.home.discord.action}
|
||||
<CaretRightIcon />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Link>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,14 @@ export function HomeLocaleSwitcher({
|
||||
}: LocaleAcceptorProps) {
|
||||
return (
|
||||
<BlitzKitTheme>
|
||||
<Flex justify="center" mt="4" py="6" align="center" gap="3">
|
||||
<Flex
|
||||
flexGrow="2"
|
||||
flexShrink="0"
|
||||
justify="center"
|
||||
mt="4"
|
||||
align="center"
|
||||
gap="3"
|
||||
>
|
||||
<Text size="5" trim="end" color="gray">
|
||||
<GlobeIcon width="1em" height="1em" />
|
||||
</Text>
|
||||
|
||||
@@ -83,7 +83,7 @@ function Content() {
|
||||
const { locale, strings } = useLocale();
|
||||
|
||||
return (
|
||||
<Flex direction="column" pt="4" pb="8">
|
||||
<Flex direction="column" pt="4" pb="6">
|
||||
<Heading align="center" size="5">
|
||||
{strings.website.home.hot_tanks.title}
|
||||
</Heading>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
|
||||
export function MissingShellIcon(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="M39.5461 16.5714L47.4286 24.4539M16.1429 37.5714L26.4286 47.8571M56 10.3802L53.6198 8C32.2378 20.0846 22.0873 28.6952 8 48.0661C8.13223 48.595 8.63471 50.2876 11.1736 52.8264C13.7124 55.3653 15.405 55.8678 15.9339 56C35.3048 41.9127 43.9154 31.7622 56 10.3802Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -6,24 +6,14 @@ import {
|
||||
GearIcon,
|
||||
HamburgerMenuIcon,
|
||||
} from '@radix-ui/react-icons';
|
||||
import {
|
||||
Box,
|
||||
Code,
|
||||
Flex,
|
||||
Grid,
|
||||
IconButton,
|
||||
Link,
|
||||
ScrollArea,
|
||||
Separator,
|
||||
Text,
|
||||
} from '@radix-ui/themes';
|
||||
import { Box, Code, Flex, IconButton, Link } from '@radix-ui/themes';
|
||||
import { TOOLS } from '../constants/tools';
|
||||
import { resolveBranchName } from '../core/blitzkit/branches';
|
||||
import { homeTool, TOOLS } from '../constants/tools';
|
||||
import { LinkI18n } from './LinkI18n';
|
||||
import { getStrings } from '../core/i18n/getStrings';
|
||||
import { Var } from '../core/radix/var';
|
||||
import { LinkI18n } from './LinkI18n';
|
||||
|
||||
const toolsFiltered = TOOLS.filter((tool) => !('href' in tool));
|
||||
const tools = [homeTool, ...toolsFiltered];
|
||||
const { locale } = Astro.params;
|
||||
const branchName = resolveBranchName(locale);
|
||||
const isBranchNamed = branchName !== undefined;
|
||||
@@ -31,93 +21,64 @@ const strings = getStrings(locale);
|
||||
---
|
||||
|
||||
<style>
|
||||
.navbar {
|
||||
height: var(--navbar-height);
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
.expander {
|
||||
--navbar-height: 48px;
|
||||
|
||||
/* overflow: hidden; */
|
||||
transition-duration: 250ms;
|
||||
width: 100%;
|
||||
backdrop-filter: blur(4rem) brightness(0.75);
|
||||
-webkit-backdrop-filter: blur(4rem) brightness(0.75);
|
||||
}
|
||||
|
||||
.expander[data-expanded='false'] {
|
||||
height: var(--navbar-height);
|
||||
}
|
||||
|
||||
.expander[data-expanded='false'] *[data-expanded] {
|
||||
.navbar[data-expanded='false'] *[data-expanded] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.expander[data-expanded='true'] {
|
||||
height: 100vh;
|
||||
.navbar[data-expanded='true'] {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.expander[data-expanded='true'] *[data-closed] {
|
||||
.navbar[data-expanded='true'] *[data-closed] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.expander div[data-radix-scroll-area-viewport] {
|
||||
overflow-y: scroll !important;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.tools {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.hamburger {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tools {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.expander {
|
||||
max-height: var(--navbar-height) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const expander = document.querySelector<HTMLDivElement>('.expander');
|
||||
const navbar = document.querySelector<HTMLDivElement>('.navbar');
|
||||
const hamburger = document.querySelector<HTMLButtonElement>('.hamburger');
|
||||
|
||||
expander?.addEventListener('click', () => {
|
||||
expander.dataset.expanded = 'false';
|
||||
navbar?.addEventListener('click', () => {
|
||||
navbar.dataset.expanded = 'false';
|
||||
});
|
||||
hamburger?.addEventListener('click', (event) => {
|
||||
if (!expander) return;
|
||||
if (!navbar) return;
|
||||
|
||||
event.stopPropagation();
|
||||
expander.dataset.expanded = `${expander.dataset.expanded === 'false'}`;
|
||||
navbar.dataset.expanded = `${navbar.dataset.expanded === 'false'}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<Flex className="navbar">
|
||||
<Flex className="expander" data-expanded="false" direction="column" gap="2">
|
||||
<Flex justify="center">
|
||||
<Flex flexGrow="1" maxWidth="1024px" p="3" align="center">
|
||||
<Box height={{ initial: '3.5rem', sm: '3rem' }} />
|
||||
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
data-expanded="false"
|
||||
className="navbar"
|
||||
width="100%"
|
||||
top="0"
|
||||
position="fixed"
|
||||
style={{
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
width="100%"
|
||||
p={{ initial: '4', sm: '3' }}
|
||||
direction="column"
|
||||
align="center"
|
||||
style={{ backgroundColor: Var('gray-1') }}
|
||||
>
|
||||
<Flex gap="4" direction="column" maxWidth="80rem" width="100%">
|
||||
<Flex align="center">
|
||||
<LinkI18n
|
||||
{locale}
|
||||
color="gray"
|
||||
highContrast
|
||||
href="/"
|
||||
underline="hover"
|
||||
weight="bold"
|
||||
weight="medium"
|
||||
>
|
||||
<Flex align="center" gap="1">
|
||||
BlitzKit
|
||||
@@ -133,7 +94,13 @@ const strings = getStrings(locale);
|
||||
|
||||
<Box flexGrow="1" />
|
||||
|
||||
<Flex align="center" gap="2" justify="center" className="tools">
|
||||
<Flex
|
||||
align="center"
|
||||
gap={{ initial: '4', md: '6' }}
|
||||
display={{ initial: 'none', sm: 'flex' }}
|
||||
justify="center"
|
||||
className="tools"
|
||||
>
|
||||
{
|
||||
toolsFiltered.map((tool, index) => {
|
||||
const unavailableOnBranch = tool.branches?.every(
|
||||
@@ -150,19 +117,17 @@ const strings = getStrings(locale);
|
||||
if (unavailableOnBranch) return null;
|
||||
|
||||
return (
|
||||
<Fragment key={tool.id}>
|
||||
{index > 0 && <Separator orientation="vertical" size="1" />}
|
||||
<LinkI18n
|
||||
{locale}
|
||||
color="gray"
|
||||
highContrast={selected}
|
||||
size="2"
|
||||
href={`/${tool.id}`}
|
||||
underline={selected ? 'always' : 'hover'}
|
||||
>
|
||||
{toolStrings.name}
|
||||
</LinkI18n>
|
||||
</Fragment>
|
||||
<LinkI18n
|
||||
{locale}
|
||||
weight={selected ? 'medium' : 'regular'}
|
||||
color="gray"
|
||||
highContrast={selected}
|
||||
size="2"
|
||||
href={`/${tool.id}`}
|
||||
underline="hover"
|
||||
>
|
||||
{toolStrings.name}
|
||||
</LinkI18n>
|
||||
);
|
||||
})
|
||||
}
|
||||
@@ -170,7 +135,7 @@ const strings = getStrings(locale);
|
||||
|
||||
<Box flexGrow="1" />
|
||||
|
||||
<Flex align="center" gap="3">
|
||||
<Flex align="center" gap="4">
|
||||
<Link
|
||||
style={{ display: 'flex', alignContent: 'center' }}
|
||||
color="gray"
|
||||
@@ -178,7 +143,7 @@ const strings = getStrings(locale);
|
||||
underline="none"
|
||||
target="_blank"
|
||||
>
|
||||
<DiscordLogoIcon />
|
||||
<DiscordLogoIcon width="1em" height="1em" />
|
||||
</Link>
|
||||
|
||||
<LinkI18n
|
||||
@@ -188,100 +153,84 @@ const strings = getStrings(locale);
|
||||
href="/settings"
|
||||
underline="none"
|
||||
>
|
||||
<GearIcon />
|
||||
<GearIcon width="1em" height="1em" />
|
||||
</LinkI18n>
|
||||
|
||||
<!-- <NavbarAccount client:idle /> -->
|
||||
|
||||
<IconButton variant="ghost" color="gray" className="hamburger">
|
||||
<Cross1Icon data-expanded />
|
||||
<HamburgerMenuIcon data-closed />
|
||||
</IconButton>
|
||||
<Flex align="center" display={{ initial: 'flex', sm: 'none' }}>
|
||||
<IconButton variant="ghost" color="gray" className="hamburger">
|
||||
<Cross1Icon data-expanded />
|
||||
<HamburgerMenuIcon data-closed />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<ScrollArea scrollbars="vertical" style={{ height: '100%', width: '100%' }}>
|
||||
<Grid
|
||||
columns="repeat(auto-fill, minmax(12rem, 1fr))"
|
||||
flow="dense"
|
||||
gap="2"
|
||||
width="100%"
|
||||
p="3"
|
||||
pt="0"
|
||||
<Flex
|
||||
direction="column"
|
||||
data-expanded
|
||||
gap="3"
|
||||
display={{ initial: 'flex', sm: 'none' }}
|
||||
>
|
||||
{
|
||||
tools.map((tool) => {
|
||||
toolsFiltered.map((tool) => {
|
||||
const unavailableOnBranch = tool.branches?.every(
|
||||
(branch) =>
|
||||
branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
|
||||
);
|
||||
|
||||
if (unavailableOnBranch) return null;
|
||||
|
||||
const toolStrings =
|
||||
strings.website.tools[
|
||||
(tool.stringsId ??
|
||||
tool.id) as keyof typeof strings.website.tools
|
||||
];
|
||||
|
||||
if (unavailableOnBranch) return null;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
key={tool.id}
|
||||
style={{
|
||||
position: 'relative',
|
||||
borderRadius: 'var(--radius-2)',
|
||||
overflow: 'hidden',
|
||||
backgroundImage: `url(${imgur(tool.image, {
|
||||
format: 'jpeg',
|
||||
size: ImgurSize.MediumThumbnail,
|
||||
})})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
aspectRatio: '2 / 1',
|
||||
}}
|
||||
<Link
|
||||
color="gray"
|
||||
highContrast
|
||||
style={{ display: 'contents' }}
|
||||
href={`/tools/${tool.id}`}
|
||||
>
|
||||
<LinkI18n
|
||||
{locale}
|
||||
href={tool.id.length === 0 ? '/' : `/${tool.id}`}
|
||||
<Box
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
flexDirection: 'column',
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
borderRadius: Var('radius-2'),
|
||||
backgroundImage: `url(/assets/images/tools/${tool.id}.webp)`,
|
||||
backgroundSize: '200%',
|
||||
backgroundPosition: 'right center',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
<Flex
|
||||
py="4"
|
||||
justify="end"
|
||||
flexGrow="1"
|
||||
style={{
|
||||
backgroundImage: `url(${imgur(tool.image, {
|
||||
format: 'jpeg',
|
||||
size: ImgurSize.MediumThumbnail,
|
||||
})})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Flex
|
||||
p="2"
|
||||
align="center"
|
||||
width="100%"
|
||||
style={{
|
||||
backgroundColor: 'var(--color-panel-translucent)',
|
||||
backdropFilter: 'blur(4rem)',
|
||||
WebkitBackdropFilter: 'blur(4rem)',
|
||||
background: `linear-gradient(90deg, transparent, ${Var('gray-1')})`,
|
||||
}}
|
||||
>
|
||||
<Text>{toolStrings.name}</Text>
|
||||
{toolStrings.name}
|
||||
</Flex>
|
||||
</LinkI18n>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Grid>
|
||||
</ScrollArea>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Box
|
||||
data-expanded
|
||||
flexGrow="1"
|
||||
width="100%"
|
||||
display={{ initial: 'block', sm: 'none' }}
|
||||
style={{
|
||||
backgroundColor: Var('black-a7'),
|
||||
backdropFilter: 'blur(0.5rem)',
|
||||
WebkitBackdropFilter: 'blur(0.5rem)',
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
@@ -39,7 +39,7 @@ export function PageWrapper({
|
||||
gap="4"
|
||||
p={padding}
|
||||
width="100%"
|
||||
maxWidth="50rem"
|
||||
maxWidth="80rem"
|
||||
style={{
|
||||
margin: 'auto',
|
||||
boxSizing: 'border-box',
|
||||
|
||||
@@ -10,9 +10,14 @@ const PUBLIC_GOOGLE_TAG_MANAGER_ID = assertSecret(
|
||||
<script
|
||||
is:inline
|
||||
async
|
||||
type="text/partytown"
|
||||
src={`https://www.googletagmanager.com/gtag/js?id=${PUBLIC_GOOGLE_TAG_MANAGER_ID}`}
|
||||
></script>
|
||||
<script is:inline define:vars={{ PUBLIC_GOOGLE_TAG_MANAGER_ID }}>
|
||||
<script
|
||||
is:inline
|
||||
define:vars={{ PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
type="text/partytown"
|
||||
>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function gtag() {
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
import {
|
||||
asset,
|
||||
ShellType,
|
||||
TANK_CLASSES,
|
||||
TankType,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
} from '@blitzkit/core';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { ReloadIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Flex, IconButton, Text } from '@radix-ui/themes';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
IconButton,
|
||||
Inset,
|
||||
Popover,
|
||||
Text,
|
||||
} from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { awaitableGameDefinitions } from '../../../core/awaitables/gameDefinitions';
|
||||
import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters';
|
||||
import { Var } from '../../../core/radix/var';
|
||||
import { useLocale } from '../../../hooks/useLocale';
|
||||
import {
|
||||
$tankFilters,
|
||||
initialTankFilters,
|
||||
type TankFilters,
|
||||
} from '../../../stores/tankFilters';
|
||||
import {
|
||||
$tankopediaSort,
|
||||
initialTankopediaSort,
|
||||
@@ -19,6 +34,7 @@ import { ExperimentIcon } from '../../ExperimentIcon';
|
||||
import { GunAutoloaderIcon } from '../../GunAutoloaderIcon';
|
||||
import { GunAutoreloaderIcon } from '../../GunAutoreloaderIcon';
|
||||
import { GunRegularIcon } from '../../GunRegularIcon';
|
||||
import { MissingShellIcon } from '../../MissingShellIcon';
|
||||
import { ResearchedIcon } from '../../ResearchedIcon';
|
||||
import { ScienceIcon } from '../../ScienceIcon';
|
||||
import { ScienceOffIcon } from '../../ScienceOffIcon';
|
||||
@@ -29,11 +45,90 @@ interface FilterControlProps {
|
||||
|
||||
const gameDefinitions = await awaitableGameDefinitions;
|
||||
|
||||
export function FilterControl({ compact }: FilterControlProps) {
|
||||
const shellTypeIcons: Record<ShellType, string> = {
|
||||
[ShellType.AP]: 'ap',
|
||||
[ShellType.APCR]: 'ap_cr',
|
||||
[ShellType.HE]: 'he',
|
||||
[ShellType.HEAT]: 'hc',
|
||||
};
|
||||
|
||||
function ShellFilter({ index, premium }: { index: number; premium?: boolean }) {
|
||||
const tankFilters = useStore($tankFilters);
|
||||
|
||||
return (
|
||||
<Flex height="fit-content" gap="2" align="start" justify="center">
|
||||
<Popover.Root>
|
||||
<Popover.Trigger>
|
||||
<IconButton variant="soft" radius="none" color="gray" highContrast>
|
||||
{tankFilters.shells[index] === null && (
|
||||
<Text color="gray" style={{ display: 'contents' }}>
|
||||
<MissingShellIcon width="1em" height="1em" />
|
||||
</Text>
|
||||
)}
|
||||
{tankFilters.shells[index] !== null && (
|
||||
<img
|
||||
style={{ width: '1em', height: '1em' }}
|
||||
src={asset(
|
||||
`icons/shells/${shellTypeIcons[tankFilters.shells[index]]}${premium ? '_premium' : ''}.webp`,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
</Popover.Trigger>
|
||||
|
||||
<Popover.Content>
|
||||
<Inset>
|
||||
<Flex>
|
||||
{Object.values(ShellType).map((shellType) => {
|
||||
if (typeof shellType === 'string') return null;
|
||||
|
||||
const selected = tankFilters.shells[index] === shellType;
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
value={`${shellType}`}
|
||||
radius="none"
|
||||
variant={selected ? 'solid' : 'soft'}
|
||||
onClick={() => {
|
||||
const mutated = [
|
||||
tankFilters.shells[0],
|
||||
tankFilters.shells[1],
|
||||
tankFilters.shells[2],
|
||||
] as TankFilters['shells'];
|
||||
|
||||
mutated[index] = selected ? null : shellType;
|
||||
|
||||
$tankFilters.setKey('shells', mutated);
|
||||
}}
|
||||
highContrast={selected}
|
||||
color={selected ? undefined : 'gray'}
|
||||
>
|
||||
<img
|
||||
src={asset(
|
||||
`icons/shells/${shellTypeIcons[shellType]}${premium ? '_premium' : ''}.webp`,
|
||||
)}
|
||||
style={{ width: '1em', height: '1em' }}
|
||||
/>
|
||||
</IconButton>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Inset>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
);
|
||||
}
|
||||
|
||||
export function FilterControl({ compact }: FilterControlProps) {
|
||||
const tankFilters = useStore($tankFilters);
|
||||
const { strings } = useLocale();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
height="fit-content"
|
||||
gap="2"
|
||||
align={{ initial: 'start', md: 'center' }}
|
||||
justify="center"
|
||||
>
|
||||
<Flex
|
||||
direction={compact ? 'row' : { initial: 'row', md: 'column' }}
|
||||
overflow="hidden"
|
||||
@@ -67,6 +162,15 @@ export function FilterControl({ compact }: FilterControlProps) {
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
|
||||
<Box
|
||||
display={{ initial: 'none', md: 'block' }}
|
||||
style={{
|
||||
height: Var('space-1'),
|
||||
backgroundColor: Var('gray-a3'),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Flex direction={compact ? 'column' : { md: 'row', initial: 'column' }}>
|
||||
{times(5, (index) => {
|
||||
const tier = index + 6;
|
||||
@@ -137,6 +241,15 @@ export function FilterControl({ compact }: FilterControlProps) {
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
|
||||
<Box
|
||||
display={{ initial: 'none', md: 'block' }}
|
||||
style={{
|
||||
height: Var('space-1'),
|
||||
backgroundColor: Var('gray-a3'),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Flex direction={compact ? 'column' : { md: 'row', initial: 'column' }}>
|
||||
{gameDefinitions.nations
|
||||
.slice(Math.ceil(gameDefinitions.nations.length / 2))
|
||||
@@ -176,270 +289,315 @@ export function FilterControl({ compact }: FilterControlProps) {
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: 'var(--radius-full)' }}
|
||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||
direction={{ initial: 'row', md: 'column' }}
|
||||
align="start"
|
||||
gap={{ initial: '2', md: '1' }}
|
||||
>
|
||||
{TANK_CLASSES.map((tankClass) => {
|
||||
const Icon = classIcons[tankClass];
|
||||
const selected = tankFilters.classes?.includes(tankClass);
|
||||
<Flex gap="2" align="start">
|
||||
<Flex
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: 'var(--radius-full)' }}
|
||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||
>
|
||||
{TANK_CLASSES.map((tankClass) => {
|
||||
const Icon = classIcons[tankClass];
|
||||
const selected = tankFilters.classes?.includes(tankClass);
|
||||
|
||||
return (
|
||||
return (
|
||||
<IconButton
|
||||
key={tankClass}
|
||||
variant={selected ? 'solid' : 'soft'}
|
||||
radius="none"
|
||||
color={selected ? undefined : 'gray'}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.classes.includes(tankClass)) {
|
||||
$tankFilters.setKey(
|
||||
'classes',
|
||||
tankFilters.classes.filter((c) => c !== tankClass),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('classes', [
|
||||
...tankFilters.classes,
|
||||
tankClass,
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon style={{ width: '1em', height: '1em' }} />
|
||||
</IconButton>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: 'var(--radius-full)' }}
|
||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||
>
|
||||
<IconButton
|
||||
key={tankClass}
|
||||
variant={selected ? 'solid' : 'soft'}
|
||||
variant={
|
||||
tankFilters.gunType.includes('regular') ? 'solid' : 'soft'
|
||||
}
|
||||
radius="none"
|
||||
color={selected ? undefined : 'gray'}
|
||||
color={
|
||||
tankFilters.gunType.includes('regular') ? undefined : 'gray'
|
||||
}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.classes.includes(tankClass)) {
|
||||
if (tankFilters.gunType.includes('regular')) {
|
||||
$tankFilters.setKey(
|
||||
'classes',
|
||||
tankFilters.classes.filter((c) => c !== tankClass),
|
||||
'gunType',
|
||||
tankFilters.gunType.filter((t) => t !== 'regular'),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('classes', [
|
||||
...tankFilters.classes,
|
||||
tankClass,
|
||||
$tankFilters.setKey('gunType', [
|
||||
...tankFilters.gunType,
|
||||
'regular',
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon style={{ width: '1em', height: '1em' }} />
|
||||
<GunRegularIcon style={{ width: '1em', height: '1em' }} />
|
||||
</IconButton>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
<IconButton
|
||||
variant={
|
||||
tankFilters.gunType.includes('auto_loader') ? 'solid' : 'soft'
|
||||
}
|
||||
radius="none"
|
||||
color={
|
||||
tankFilters.gunType.includes('auto_loader') ? undefined : 'gray'
|
||||
}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.gunType.includes('auto_loader')) {
|
||||
$tankFilters.setKey(
|
||||
'gunType',
|
||||
tankFilters.gunType.filter((t) => t !== 'auto_loader'),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('gunType', [
|
||||
...tankFilters.gunType,
|
||||
'auto_loader',
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<GunAutoloaderIcon style={{ width: '1em', height: '1em' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant={
|
||||
tankFilters.gunType.includes('auto_reloader') ? 'solid' : 'soft'
|
||||
}
|
||||
radius="none"
|
||||
color={
|
||||
tankFilters.gunType.includes('auto_reloader')
|
||||
? undefined
|
||||
: 'gray'
|
||||
}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.gunType.includes('auto_reloader')) {
|
||||
$tankFilters.setKey(
|
||||
'gunType',
|
||||
tankFilters.gunType.filter((t) => t !== 'auto_reloader'),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('gunType', [
|
||||
...tankFilters.gunType,
|
||||
'auto_reloader',
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<GunAutoreloaderIcon style={{ width: '1em', height: '1em' }} />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: 'var(--radius-full)' }}
|
||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||
>
|
||||
<IconButton
|
||||
variant={tankFilters.gunType.includes('regular') ? 'solid' : 'soft'}
|
||||
radius="none"
|
||||
color={tankFilters.gunType.includes('regular') ? undefined : 'gray'}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.gunType.includes('regular')) {
|
||||
$tankFilters.setKey(
|
||||
'gunType',
|
||||
tankFilters.gunType.filter((t) => t !== 'regular'),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('gunType', [
|
||||
...tankFilters.gunType,
|
||||
'regular',
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<GunRegularIcon style={{ width: '1em', height: '1em' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant={
|
||||
tankFilters.gunType.includes('auto_loader') ? 'solid' : 'soft'
|
||||
}
|
||||
radius="none"
|
||||
color={
|
||||
tankFilters.gunType.includes('auto_loader') ? undefined : 'gray'
|
||||
}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.gunType.includes('auto_loader')) {
|
||||
$tankFilters.setKey(
|
||||
'gunType',
|
||||
tankFilters.gunType.filter((t) => t !== 'auto_loader'),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('gunType', [
|
||||
...tankFilters.gunType,
|
||||
'auto_loader',
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<GunAutoloaderIcon style={{ width: '1em', height: '1em' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant={
|
||||
tankFilters.gunType.includes('auto_reloader') ? 'solid' : 'soft'
|
||||
}
|
||||
radius="none"
|
||||
color={
|
||||
tankFilters.gunType.includes('auto_reloader') ? undefined : 'gray'
|
||||
}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.gunType.includes('auto_reloader')) {
|
||||
$tankFilters.setKey(
|
||||
'gunType',
|
||||
tankFilters.gunType.filter((t) => t !== 'auto_reloader'),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('gunType', [
|
||||
...tankFilters.gunType,
|
||||
'auto_reloader',
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<GunAutoreloaderIcon style={{ width: '1em', height: '1em' }} />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
<Box display={{ initial: 'none', md: 'block' }}>
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
$tankFilters.set(initialTankFilters);
|
||||
$tankopediaSort.set(initialTankopediaSort);
|
||||
}}
|
||||
>
|
||||
<ReloadIcon />
|
||||
{strings.website.common.tank_search.clear_filters}
|
||||
</Button>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: 'var(--radius-full)' }}
|
||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||
>
|
||||
<IconButton
|
||||
variant={
|
||||
tankFilters.types?.includes(TankType.RESEARCHABLE)
|
||||
? 'solid'
|
||||
: 'soft'
|
||||
}
|
||||
radius="none"
|
||||
color={
|
||||
tankFilters.types?.includes(TankType.RESEARCHABLE)
|
||||
? undefined
|
||||
: 'gray'
|
||||
}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.types.includes(TankType.RESEARCHABLE)) {
|
||||
$tankFilters.setKey(
|
||||
'types',
|
||||
tankFilters.types.filter((t) => t !== TankType.RESEARCHABLE),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('types', [
|
||||
...tankFilters.types,
|
||||
TankType.RESEARCHABLE,
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant={
|
||||
tankFilters.types?.includes(TankType.PREMIUM) ? 'solid' : 'soft'
|
||||
}
|
||||
radius="none"
|
||||
color={
|
||||
tankFilters.types?.includes(TankType.PREMIUM) ? 'amber' : 'gray'
|
||||
}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.types.includes(TankType.PREMIUM)) {
|
||||
$tankFilters.setKey(
|
||||
'types',
|
||||
tankFilters.types.filter((t) => t !== TankType.PREMIUM),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('types', [
|
||||
...tankFilters.types,
|
||||
TankType.PREMIUM,
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
color={
|
||||
tankFilters.types?.includes(TankType.PREMIUM)
|
||||
? undefined
|
||||
: 'amber'
|
||||
}
|
||||
style={{ display: 'flex', justifyContent: 'center' }}
|
||||
<Flex gap="2" align="start">
|
||||
<Flex
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: 'var(--radius-full)' }}
|
||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||
>
|
||||
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
||||
</Text>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant={
|
||||
tankFilters.types?.includes(TankType.COLLECTOR) ? 'solid' : 'soft'
|
||||
}
|
||||
radius="none"
|
||||
color={
|
||||
tankFilters.types?.includes(TankType.COLLECTOR) ? 'blue' : 'gray'
|
||||
}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.types.includes(TankType.COLLECTOR)) {
|
||||
$tankFilters.setKey(
|
||||
'types',
|
||||
tankFilters.types.filter((t) => t !== TankType.COLLECTOR),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('types', [
|
||||
...tankFilters.types,
|
||||
TankType.COLLECTOR,
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
color={
|
||||
tankFilters.types?.includes(TankType.COLLECTOR)
|
||||
? undefined
|
||||
: 'blue'
|
||||
}
|
||||
style={{ display: 'flex', justifyContent: 'center' }}
|
||||
<IconButton
|
||||
variant={
|
||||
tankFilters.types?.includes(TankType.RESEARCHABLE)
|
||||
? 'solid'
|
||||
: 'soft'
|
||||
}
|
||||
radius="none"
|
||||
color={
|
||||
tankFilters.types?.includes(TankType.RESEARCHABLE)
|
||||
? undefined
|
||||
: 'gray'
|
||||
}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.types.includes(TankType.RESEARCHABLE)) {
|
||||
$tankFilters.setKey(
|
||||
'types',
|
||||
tankFilters.types.filter(
|
||||
(t) => t !== TankType.RESEARCHABLE,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('types', [
|
||||
...tankFilters.types,
|
||||
TankType.RESEARCHABLE,
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant={
|
||||
tankFilters.types?.includes(TankType.PREMIUM) ? 'solid' : 'soft'
|
||||
}
|
||||
radius="none"
|
||||
color={
|
||||
tankFilters.types?.includes(TankType.PREMIUM) ? 'amber' : 'gray'
|
||||
}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.types.includes(TankType.PREMIUM)) {
|
||||
$tankFilters.setKey(
|
||||
'types',
|
||||
tankFilters.types.filter((t) => t !== TankType.PREMIUM),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('types', [
|
||||
...tankFilters.types,
|
||||
TankType.PREMIUM,
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
color={
|
||||
tankFilters.types?.includes(TankType.PREMIUM)
|
||||
? undefined
|
||||
: 'amber'
|
||||
}
|
||||
style={{ display: 'flex', justifyContent: 'center' }}
|
||||
>
|
||||
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
||||
</Text>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant={
|
||||
tankFilters.types?.includes(TankType.COLLECTOR)
|
||||
? 'solid'
|
||||
: 'soft'
|
||||
}
|
||||
radius="none"
|
||||
color={
|
||||
tankFilters.types?.includes(TankType.COLLECTOR)
|
||||
? 'blue'
|
||||
: 'gray'
|
||||
}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (tankFilters.types.includes(TankType.COLLECTOR)) {
|
||||
$tankFilters.setKey(
|
||||
'types',
|
||||
tankFilters.types.filter((t) => t !== TankType.COLLECTOR),
|
||||
);
|
||||
} else {
|
||||
$tankFilters.setKey('types', [
|
||||
...tankFilters.types,
|
||||
TankType.COLLECTOR,
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
color={
|
||||
tankFilters.types?.includes(TankType.COLLECTOR)
|
||||
? undefined
|
||||
: 'blue'
|
||||
}
|
||||
style={{ display: 'flex', justifyContent: 'center' }}
|
||||
>
|
||||
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
||||
</Text>
|
||||
</IconButton>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: 'var(--radius-full)' }}
|
||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||
>
|
||||
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
||||
</Text>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant={tankFilters.testing === 'include' ? 'solid' : 'soft'}
|
||||
radius="none"
|
||||
color={tankFilters.testing === 'include' ? undefined : 'gray'}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
$tankFilters.setKey('testing', 'include');
|
||||
}}
|
||||
>
|
||||
<ExperimentIcon
|
||||
style={{ width: '1em', height: '1em', color: 'currentColor' }}
|
||||
/>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant={tankFilters.testing === 'exclude' ? 'solid' : 'soft'}
|
||||
radius="none"
|
||||
color={tankFilters.testing === 'exclude' ? undefined : 'gray'}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
$tankFilters.setKey('testing', 'exclude');
|
||||
}}
|
||||
>
|
||||
<ScienceOffIcon
|
||||
style={{ width: '1em', height: '1em', color: 'currentColor' }}
|
||||
/>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant={tankFilters.testing === 'only' ? 'solid' : 'soft'}
|
||||
radius="none"
|
||||
color={tankFilters.testing === 'only' ? undefined : 'gray'}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
$tankFilters.setKey('testing', 'only');
|
||||
}}
|
||||
>
|
||||
<ScienceIcon
|
||||
style={{ width: '1em', height: '1em', color: 'currentColor' }}
|
||||
/>
|
||||
</IconButton>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: 'var(--radius-full)' }}
|
||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||
>
|
||||
<ShellFilter index={0} />
|
||||
<ShellFilter index={1} premium />
|
||||
<ShellFilter index={2} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: 'var(--radius-full)' }}
|
||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||
>
|
||||
<IconButton
|
||||
variant={tankFilters.testing === 'include' ? 'solid' : 'soft'}
|
||||
radius="none"
|
||||
color={tankFilters.testing === 'include' ? undefined : 'gray'}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
$tankFilters.setKey('testing', 'include');
|
||||
}}
|
||||
>
|
||||
<ExperimentIcon
|
||||
style={{ width: '1em', height: '1em', color: 'currentColor' }}
|
||||
/>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant={tankFilters.testing === 'exclude' ? 'solid' : 'soft'}
|
||||
radius="none"
|
||||
color={tankFilters.testing === 'exclude' ? undefined : 'gray'}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
$tankFilters.setKey('testing', 'exclude');
|
||||
}}
|
||||
>
|
||||
<ScienceOffIcon
|
||||
style={{ width: '1em', height: '1em', color: 'currentColor' }}
|
||||
/>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant={tankFilters.testing === 'only' ? 'solid' : 'soft'}
|
||||
radius="none"
|
||||
color={tankFilters.testing === 'only' ? undefined : 'gray'}
|
||||
highContrast
|
||||
onClick={() => {
|
||||
$tankFilters.setKey('testing', 'only');
|
||||
}}
|
||||
>
|
||||
<ScienceIcon
|
||||
style={{ width: '1em', height: '1em', color: 'currentColor' }}
|
||||
/>
|
||||
</IconButton>
|
||||
</Flex>
|
||||
|
||||
<Box>
|
||||
<Box display={{ initial: 'block', md: 'none' }} ml="4">
|
||||
<IconButton
|
||||
color="red"
|
||||
onClick={() => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { imgur, ShellType } from '@blitzkit/core';
|
||||
import { ShellType } from '@blitzkit/core';
|
||||
import { literals } from '@blitzkit/i18n/src/literals';
|
||||
import {
|
||||
Button,
|
||||
@@ -42,7 +42,7 @@ export function CustomShellButton() {
|
||||
>
|
||||
<img
|
||||
alt="custom shell"
|
||||
src={imgur('j2CoXak')}
|
||||
src="/assets/images/icons/custom-shell.png"
|
||||
style={{
|
||||
width: '50%',
|
||||
height: '50%',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { imgur } from '@blitzkit/core';
|
||||
import { Box, Flex, SegmentedControl, Tooltip } from '@radix-ui/themes';
|
||||
import { useState, type RefObject } from 'react';
|
||||
import { useFullScreen } from '../../../../../hooks/useFullScreen';
|
||||
@@ -65,7 +64,7 @@ export function Options({ canvas }: OptionsProps) {
|
||||
left="50%"
|
||||
style={{
|
||||
zIndex: 2,
|
||||
backgroundImage: `url(${imgur('27Gwth4')})`,
|
||||
backgroundImage: 'url(/assets/images/icons/aim-caret.png)',
|
||||
backgroundSize: 'contain',
|
||||
transform: 'translateX(-50%)',
|
||||
borderRadius: '50%',
|
||||
@@ -373,7 +372,10 @@ export function Options({ canvas }: OptionsProps) {
|
||||
<SegmentedControl.Item value={`${TankopediaDisplay.Model}`}>
|
||||
<Tooltip content={strings.website.tools.tanks.sandbox.model.name}>
|
||||
<Flex height="100%" align="center">
|
||||
<img src={imgur('jAdYf0m')} style={{ height: '1.25em' }} />
|
||||
<img
|
||||
src="/assets/images/icons/tankopedia-model.png"
|
||||
style={{ height: '1.25em' }}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item>
|
||||
@@ -382,7 +384,10 @@ export function Options({ canvas }: OptionsProps) {
|
||||
content={strings.website.tools.tanks.sandbox.dynamic.name}
|
||||
>
|
||||
<Flex height="100%" align="center">
|
||||
<img src={imgur('oe4Cq0g')} style={{ height: '1.25em' }} />
|
||||
<img
|
||||
src="/assets/images/icons/tankopedia-dynamic-armor.png"
|
||||
style={{ height: '1.25em' }}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item>
|
||||
@@ -391,7 +396,10 @@ export function Options({ canvas }: OptionsProps) {
|
||||
content={strings.website.tools.tanks.sandbox.static.name}
|
||||
>
|
||||
<Flex height="100%" align="center">
|
||||
<img src={imgur('VQ4uDno')} style={{ height: '1.25em' }} />
|
||||
<img
|
||||
src="/assets/images/icons/tankopedia-static-armor.png"
|
||||
style={{ height: '1.25em' }}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item>
|
||||
@@ -400,14 +408,29 @@ export function Options({ canvas }: OptionsProps) {
|
||||
content={strings.website.tools.tanks.sandbox.dissector.name}
|
||||
>
|
||||
<Flex height="100%" align="center">
|
||||
<img src={imgur('ErJEWbY')} style={{ height: '1.25em' }} />
|
||||
<img
|
||||
src="/assets/images/icons/tankopedia-static-armor.png"
|
||||
style={{ height: '1.25em' }}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item>
|
||||
<SegmentedControl.Item value={`${TankopediaDisplay.Dissector}`}>
|
||||
<Tooltip
|
||||
content={strings.website.tools.tanks.sandbox.dissector.name}
|
||||
>
|
||||
<Flex height="100%" align="center">
|
||||
<img
|
||||
src="/assets/images/icons/tankopedia-dissector.png"
|
||||
style={{ height: '1.25em' }}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item>
|
||||
{/* <SegmentedControl.Item value={`${TankopediaDisplay.ShootingRange}`}>
|
||||
<Tooltip content="Shooting range">
|
||||
<Flex height="100%" align="center">
|
||||
<img src={imgur('q8ZPfzG')} style={{ height: '1.25em' }} />
|
||||
<img src="/assets/images/icons/tankopedia-shooting-range.png" style={{ height: '1.25em' }} />
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item> */}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { imgur } from '@blitzkit/core';
|
||||
import { Box, type BoxProps } from '@radix-ui/themes';
|
||||
import { forwardRef } from 'react';
|
||||
import { Var } from '../../../../../../../../core/radix/var';
|
||||
@@ -56,7 +55,7 @@ export const TargetCircle = forwardRef<HTMLDivElement, TargetCircleProps>(
|
||||
top="50%"
|
||||
left="50%"
|
||||
style={{
|
||||
backgroundImage: `url(${imgur('27Gwth4')})`,
|
||||
backgroundImage: 'url(/assets/images/icons/aim-caret.png)',
|
||||
backgroundSize: 'contain',
|
||||
transform: 'translateX(-50%)',
|
||||
borderRadius: '50%',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { imgur, J_HAT } from '@blitzkit/core';
|
||||
import { J_HAT } from '@blitzkit/core';
|
||||
import { useLoader } from '@react-three/fiber';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { Group, Plane, TextureLoader, Vector2 } from 'three';
|
||||
@@ -89,8 +89,11 @@ export function SceneProps() {
|
||||
// let { dispersion } = characteristics;
|
||||
// const gunRotationSpeed = degToRad(characteristics.gunTraverseSpeed);
|
||||
// const mockTank = useLoader(GLTFLoader, asset(`3d/tanks/models/6929.glb`));
|
||||
const gridTexture = useLoader(TextureLoader, imgur('iq0iOCJ'));
|
||||
const dragTexture = useLoader(TextureLoader, imgur('Vi3xXsb'));
|
||||
const gridTexture = useLoader(TextureLoader, '/assets/images/3d/grid.png');
|
||||
const dragTexture = useLoader(
|
||||
TextureLoader,
|
||||
'/assets/images/3d/drag-slicer.png',
|
||||
);
|
||||
gridTexture.anisotropy = 2;
|
||||
// const path = new Vector3();
|
||||
// const direction = new Vector3();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { asset, imgur, TankType } from '@blitzkit/core';
|
||||
import { asset, TankType } from '@blitzkit/core';
|
||||
import { literals } from '@blitzkit/i18n/src/literals';
|
||||
import { CaretLeftIcon, CaretRightIcon, PlusIcon } from '@radix-ui/react-icons';
|
||||
import { Flex, Heading, IconButton, ScrollArea, Text } from '@radix-ui/themes';
|
||||
@@ -140,7 +140,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
<Flex
|
||||
key={multiplier}
|
||||
style={{
|
||||
background: `url(${imgur('7hDltb4')})`,
|
||||
background: 'url(/assets/images/icons/xp-multiplier.png)',
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import { assertSecret, imgur, ImgurSize } from '@blitzkit/core';
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
import { CaretRightIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Button, Code, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import type { Tool } from '../constants/tools';
|
||||
@@ -48,12 +48,7 @@ const toolStrings =
|
||||
</style>
|
||||
|
||||
<Flex
|
||||
style={{
|
||||
backgroundImage: `url(${imgur(tool.image, {
|
||||
format: 'jpeg',
|
||||
size: ImgurSize.SmallThumbnail,
|
||||
})})`,
|
||||
}}
|
||||
style={{ backgroundImage: `url(/assets/images/tools/${tool.id}.webp)` }}
|
||||
data-disabled={disabled}
|
||||
data-significant={tool.significant}
|
||||
className="container"
|
||||
@@ -72,12 +67,7 @@ const toolStrings =
|
||||
<Box
|
||||
flexGrow="1"
|
||||
style={{
|
||||
backgroundImage: `url(${imgur(tool.image, {
|
||||
format: 'jpeg',
|
||||
size: tool.significant
|
||||
? ImgurSize.HugeThumbnail
|
||||
: ImgurSize.LargeThumbnail,
|
||||
})})`,
|
||||
backgroundImage: `url(/assets/images/tools/${tool.id}.webp)`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import { imgur } from '@blitzkit/core';
|
||||
import { AstroSeo } from '@astrolib/seo';
|
||||
import { type Tool } from '../constants/tools';
|
||||
import { getStrings } from '../core/i18n/getStrings';
|
||||
|
||||
@@ -16,7 +16,18 @@ const toolStrings =
|
||||
];
|
||||
---
|
||||
|
||||
<title>{toolStrings.name}</title>
|
||||
<meta property="og:image" content={imgur(tool.image)} />
|
||||
<meta name="description" content={toolStrings.seo_description} />
|
||||
<meta property="og:description" content={toolStrings.seo_description} />
|
||||
<AstroSeo
|
||||
title={toolStrings.name}
|
||||
description={toolStrings.seo_description}
|
||||
canonical={Astro.url.href}
|
||||
openGraph={{
|
||||
url: Astro.url.href,
|
||||
images: [
|
||||
{ url: `${Astro.url.origin}/assets/images/tools/${tool.id}.webp` },
|
||||
],
|
||||
title: toolStrings.name,
|
||||
description: toolStrings.seo_description,
|
||||
site_name: 'BlitzKit',
|
||||
}}
|
||||
twitter={{ cardType: 'summary_large_image' }}
|
||||
/>
|
||||
@@ -4,7 +4,6 @@ import type { ComponentProps } from 'react';
|
||||
export interface Tool {
|
||||
id: string;
|
||||
stringsId?: string;
|
||||
image: string;
|
||||
disabled?: boolean;
|
||||
href?: string;
|
||||
button: {
|
||||
@@ -17,7 +16,6 @@ export interface Tool {
|
||||
|
||||
export const tanksTool: Tool = {
|
||||
id: 'tanks',
|
||||
image: 'D8j6GZx',
|
||||
button: {
|
||||
color: 'purple',
|
||||
},
|
||||
@@ -36,7 +34,6 @@ export const tanksTool: Tool = {
|
||||
|
||||
export const compareTool: Tool = {
|
||||
id: 'compare',
|
||||
image: 'O6VNl6e',
|
||||
button: {
|
||||
color: 'crimson',
|
||||
},
|
||||
@@ -46,7 +43,6 @@ export const compareTool: Tool = {
|
||||
export const sessionTool: Tool = {
|
||||
// TODO: rename to tracker
|
||||
id: 'session',
|
||||
image: 'HdG9sTf',
|
||||
button: {
|
||||
color: 'blue',
|
||||
},
|
||||
@@ -56,7 +52,6 @@ export const sessionTool: Tool = {
|
||||
export const discordTool: Tool = {
|
||||
id: 'discord',
|
||||
href: 'https://discord.com/application-directory/1097673957865443370',
|
||||
image: '0bWE5hC',
|
||||
button: {
|
||||
color: 'indigo',
|
||||
},
|
||||
@@ -65,7 +60,6 @@ export const discordTool: Tool = {
|
||||
export const moreTool: Tool = {
|
||||
id: 'more',
|
||||
href: 'https://discord.gg/nDt7AjGJQH',
|
||||
image: '1nPm6VI',
|
||||
button: {
|
||||
color: 'plum',
|
||||
},
|
||||
@@ -76,7 +70,6 @@ export const playersTool: Tool = {
|
||||
button: {
|
||||
color: 'blue',
|
||||
},
|
||||
image: 'YelSOfT',
|
||||
href: '_', // TODO: remove this to re-enable link
|
||||
branches: ['main', 'dev'],
|
||||
};
|
||||
@@ -86,7 +79,6 @@ export const performanceTool: Tool = {
|
||||
button: {
|
||||
color: 'jade',
|
||||
},
|
||||
image: 'vOKFB03',
|
||||
branches: ['main', 'dev'],
|
||||
};
|
||||
|
||||
@@ -95,14 +87,12 @@ export const embedTool: Tool = {
|
||||
button: {
|
||||
color: 'red',
|
||||
},
|
||||
image: 'Q0faRYg',
|
||||
branches: ['main', 'dev'],
|
||||
};
|
||||
|
||||
export const homeTool: Tool = {
|
||||
id: 'home',
|
||||
button: { color: 'blue' },
|
||||
image: 'rUPie9G',
|
||||
};
|
||||
|
||||
export const galleryTool: Tool = {
|
||||
@@ -110,7 +100,6 @@ export const galleryTool: Tool = {
|
||||
button: {
|
||||
color: 'gold',
|
||||
},
|
||||
image: 'VhKlknk',
|
||||
branches: ['main', 'dev'],
|
||||
};
|
||||
|
||||
@@ -120,7 +109,6 @@ export const tierListTool: Tool = {
|
||||
button: {
|
||||
color: 'orange',
|
||||
},
|
||||
image: 'oKKc8Xv',
|
||||
branches: ['main', 'dev', 'opentest', 'preview'],
|
||||
};
|
||||
|
||||
@@ -129,7 +117,6 @@ export const chartsTool: Tool = {
|
||||
button: {
|
||||
color: 'bronze',
|
||||
},
|
||||
image: '6fhkljA',
|
||||
branches: ['main', 'dev', 'opentest', 'preview'],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { TankDefinition } from '@blitzkit/core';
|
||||
import { times } from 'lodash-es';
|
||||
import type { TankFilters } from '../../stores/tankFilters';
|
||||
|
||||
const SHELLS = times(3, (index) => index);
|
||||
|
||||
export function filterTank(filters: TankFilters, tank: TankDefinition) {
|
||||
return (
|
||||
(filters.tiers.length === 0 || filters.tiers.includes(tank.tier)) &&
|
||||
@@ -22,6 +25,17 @@ export function filterTank(filters: TankFilters, tank: TankDefinition) {
|
||||
(filters.gunType.includes('auto_reloader') &&
|
||||
tank.turrets.some((turret) =>
|
||||
turret.guns.some((gun) => gun.gun_type!.$case === 'auto_reloader'),
|
||||
)))
|
||||
))) &&
|
||||
tank.turrets.some((turret) =>
|
||||
turret.guns.some((gun) =>
|
||||
SHELLS.every(
|
||||
(index) =>
|
||||
filters.shells[index] === null ||
|
||||
gun.gun_type!.value.base.shells[index] === undefined ||
|
||||
gun.gun_type!.value.base.shells[index].type ===
|
||||
filters.shells[index],
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export type VarName =
|
||||
| `radius-${1 | 2 | 3 | 4 | 5 | 6 | 'full'}`
|
||||
| `space-${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`
|
||||
| `shadow-${1 | 2 | 3 | 4 | 5 | 6}`
|
||||
| `${RadixColor | 'accent' | 'gray'}-${
|
||||
| `${RadixColor | 'accent' | 'gray' | 'black' | 'white'}-${
|
||||
| `${'' | 'a'}${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12}`
|
||||
| 'surface'
|
||||
| 'indicator'
|
||||
|
||||
@@ -29,7 +29,6 @@ const locale = Astro.params.locale ?? DEFAULT_LOCALE;
|
||||
<script>
|
||||
import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
|
||||
import { getBestLocale } from '../core/i18n/getBestLocale';
|
||||
|
||||
const isAPI = window.location.pathname.startsWith('/api');
|
||||
const isI18nPath = SUPPORTED_LOCALES.some((locale) =>
|
||||
window.location.pathname.startsWith(`/${locale}`),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
import { imgur, ImgurSize } from '@blitzkit/core';
|
||||
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import ToolOpenGraph from '../components/ToolOpenGraph.astro';
|
||||
import { type Tool } from '../constants/tools';
|
||||
@@ -24,10 +23,7 @@ const toolStrings =
|
||||
<BaseLayout>
|
||||
<Box
|
||||
style={{
|
||||
backgroundImage: `url(${imgur(tool!.image, {
|
||||
format: 'jpeg',
|
||||
size: ImgurSize.SmallThumbnail,
|
||||
})})`,
|
||||
backgroundImage: `url(/assets/images/tools/${tool.id}.webp)`,
|
||||
backgroundSize: 'cover',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import { AstroSeo } from '@astrolib/seo';
|
||||
import type { GetStaticPaths } from 'astro';
|
||||
import { readFile } from 'fs/promises';
|
||||
import BaseLayout from '../../../../../layouts/BaseLayout.astro';
|
||||
@@ -7,7 +8,6 @@ import { Page } from './_index';
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
const parentPaths = parentGetStaticPaths();
|
||||
|
||||
const ROOT = '../../../../../../../../docs/';
|
||||
const files = import.meta.glob('../../../../../../../../docs/**/*.md', {
|
||||
eager: true,
|
||||
@@ -40,7 +40,18 @@ const { content } = Astro.props;
|
||||
const title = content.split('\n')[0].replace('# ', '');
|
||||
---
|
||||
|
||||
<title>{title}</title>
|
||||
<AstroSeo
|
||||
title={title}
|
||||
description={title}
|
||||
canonical={Astro.url.href}
|
||||
openGraph={{
|
||||
url: Astro.url.href,
|
||||
title,
|
||||
description: title,
|
||||
site_name: 'BlitzKit',
|
||||
}}
|
||||
twitter={{ cardType: 'summary_large_image' }}
|
||||
/>
|
||||
|
||||
<BaseLayout>
|
||||
<Page content={content} />
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
---
|
||||
import { AstroSeo } from '@astrolib/seo';
|
||||
import { MagicWandIcon, StarFilledIcon } from '@radix-ui/react-icons';
|
||||
import { Flex, Heading, Link } from '@radix-ui/themes';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import { PageWrapper } from '../../../../components/PageWrapper';
|
||||
import { getStrings } from '../../../../core/i18n/getStrings';
|
||||
import BaseLayout from '../../../../layouts/BaseLayout.astro';
|
||||
|
||||
export { getStaticPaths } from '../../index.astro';
|
||||
|
||||
// this can't be used in template literal :(
|
||||
const ROOT = '../../../../../../../docs/changelogs/';
|
||||
|
||||
const strings = getStrings(Astro.params.locale);
|
||||
const versions = Object.keys(
|
||||
import.meta.glob('../../../../../../../docs/changelogs/*.md', {
|
||||
eager: true,
|
||||
@@ -20,7 +22,18 @@ const versions = Object.keys(
|
||||
.reverse();
|
||||
---
|
||||
|
||||
<title>BlitzKit Patches</title>
|
||||
<AstroSeo
|
||||
title={strings.website.docs.patches.name}
|
||||
description={strings.website.docs.patches.seo_description}
|
||||
canonical={Astro.url.href}
|
||||
openGraph={{
|
||||
url: Astro.url.href,
|
||||
title: strings.website.docs.patches.name,
|
||||
description: strings.website.docs.patches.seo_description,
|
||||
site_name: 'BlitzKit',
|
||||
}}
|
||||
twitter={{ cardType: 'summary_large_image' }}
|
||||
/>
|
||||
|
||||
<BaseLayout>
|
||||
<PageWrapper align="center">
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
import { AstroSeo } from '@astrolib/seo';
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@blitzkit/i18n';
|
||||
import { literals } from '@blitzkit/i18n/src/literals';
|
||||
import { Box, Flex, Heading, Link } from '@radix-ui/themes';
|
||||
import type { GetStaticPaths } from 'astro';
|
||||
import { AesonPlug } from '../../components/AesonPlug';
|
||||
import { DiscordPlug } from '../../components/Discord';
|
||||
import { HomeLocaleSwitcher } from '../../components/Home/HomeLocaleSwitcher';
|
||||
import HomeHero from '../../components/HomeHero.astro';
|
||||
import { Plugs } from '../../components/Plugs';
|
||||
@@ -39,13 +41,37 @@ export const getStaticPaths = (() => {
|
||||
}) satisfies GetStaticPaths;
|
||||
---
|
||||
|
||||
<title>BlitzKit</title>
|
||||
<meta name="description" content={strings.website.home.seo_description} />
|
||||
<AstroSeo
|
||||
title="BlitzKit"
|
||||
description={strings.website.home.seo_description}
|
||||
canonical={Astro.url.href}
|
||||
openGraph={{
|
||||
url: Astro.url.href,
|
||||
title: 'BlitzKit',
|
||||
description: strings.website.home.seo_description,
|
||||
site_name: 'BlitzKit',
|
||||
}}
|
||||
twitter={{ cardType: 'summary_large_image' }}
|
||||
/>
|
||||
|
||||
<BaseLayout>
|
||||
<HomeHero />
|
||||
|
||||
<HomeLocaleSwitcher {locale} client:load />
|
||||
<Flex justify="center" py="6" px="4" justify="center">
|
||||
<Flex
|
||||
wrap="wrap"
|
||||
justify="between"
|
||||
maxWidth="64rem"
|
||||
gap="6"
|
||||
align="center"
|
||||
flexGrow="1"
|
||||
>
|
||||
<HomeLocaleSwitcher {locale} client:load />
|
||||
<DiscordPlug {locale} client:only="react">
|
||||
<DiscordPlug {locale} skeleton slot="fallback" />
|
||||
</DiscordPlug>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{
|
||||
locale !== DEFAULT_LOCALE && (
|
||||
@@ -75,7 +101,7 @@ export const getStaticPaths = (() => {
|
||||
}
|
||||
|
||||
<Flex p="4" justify="center" gap="4" mt="3">
|
||||
<Flex maxWidth="64rem" flexGrow="1" gap="4" direction="column">
|
||||
<Flex maxWidth="80rem" flexGrow="1" gap="4" direction="column">
|
||||
<ToolCard tool={tanksTool} />
|
||||
|
||||
<!-- {
|
||||
@@ -119,16 +145,16 @@ export const getStaticPaths = (() => {
|
||||
<ToolCard
|
||||
tool={{
|
||||
button: { color: 'blue' },
|
||||
id: 'blitz_analysis',
|
||||
image: 'j76YXGl',
|
||||
id: 'blitz-analysis',
|
||||
stringsId: 'blitz_analysis',
|
||||
href: 'https://blitzanalysiz.com/',
|
||||
}}
|
||||
/>
|
||||
<ToolCard
|
||||
tool={{
|
||||
button: { color: 'purple', highContrast: true },
|
||||
image: 'zYrrYbR',
|
||||
id: 'blitz_insider',
|
||||
id: 'blitz-insider',
|
||||
stringsId: 'blitz_insider',
|
||||
href: 'https://discord.gg/UjWf9eGgtR',
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
import type { APIContext } from 'astro';
|
||||
|
||||
export async function GET({}: APIContext) {
|
||||
export async function GET({ request }: APIContext) {
|
||||
const allowRobots = assertSecret(import.meta.env.ALLOW_ROBOTS) === 'true';
|
||||
const site = assertSecret(import.meta.env.SITE);
|
||||
const flags: Record<string, string | null> = {
|
||||
'User-agent': '*',
|
||||
[allowRobots ? 'Allow' : 'Disallow']: '/',
|
||||
|
||||
space: null,
|
||||
Sitemap: `${new URL(request.url).origin}/sitemap-index.xml`,
|
||||
};
|
||||
|
||||
return new Response(
|
||||
`
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
${
|
||||
allowRobots
|
||||
? `
|
||||
Host: ${site}
|
||||
Sitemap: ${site}/sitemap-index.xml
|
||||
`
|
||||
: ''
|
||||
}
|
||||
`.trim(),
|
||||
Object.entries(flags)
|
||||
.map(([key, value]) => (value === null ? '' : `${key}: ${value}`))
|
||||
.join('\n'),
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { GunDefinition, TankClass, TankType } from '@blitzkit/core';
|
||||
import type {
|
||||
GunDefinition,
|
||||
ShellType,
|
||||
TankClass,
|
||||
TankType,
|
||||
} from '@blitzkit/core';
|
||||
import { map } from 'nanostores';
|
||||
|
||||
export type CaseType<T> = T extends {
|
||||
@@ -16,6 +21,7 @@ export interface TankFilters {
|
||||
search?: string;
|
||||
searching: boolean;
|
||||
gunType: CaseType<GunDefinition>[];
|
||||
shells: [ShellType | null, ShellType | null, ShellType | null];
|
||||
}
|
||||
|
||||
export const initialTankFilters: TankFilters = {
|
||||
@@ -27,6 +33,7 @@ export const initialTankFilters: TankFilters = {
|
||||
search: undefined,
|
||||
searching: false,
|
||||
gunType: [],
|
||||
shells: [null, null, null],
|
||||
};
|
||||
|
||||
export const $tankFilters = map<TankFilters>(initialTankFilters);
|
||||