From e4e92d76b4c1fb65da2e03a42a7b93f1a7db5f1a Mon Sep 17 00:00:00 2001 From: Mark Greene Date: Mon, 5 Oct 2026 18:18:19 +1300 Subject: [PATCH 01/11] Feature: tier XI compatibility Prepares BlitzKit for tier XI tanks ahead of their release. Tier X was hardcoded as the maximum in the tank search filters, the guesser and the tech tree, and had no roman numeral beyond X. - Added `XI` to `TIER_ROMAN_NUMERALS` - Added an awaitable tier list derived from the highest tier in the tank definitions, so nothing changes until tier XI tanks exist - Used it for the tank search tier filters and the guesser tier picker and defaults - Made the tech tree check for successors instead of assuming tier X is the last tier Fixes #302 --- .../core/src/blitzkit/tankDefinitions/constants.ts | 1 + packages/website/src/components/Guesser.tsx | 9 +++++---- .../components/TankSearch/components/FilterControl.tsx | 4 ++-- .../components/TankSearch/components/TierFilter.tsx | 7 ++++--- .../Tankopedia/TechTreeSection/components/Node.tsx | 2 +- .../components/Tankopedia/TechTreeSection/index.tsx | 2 +- packages/website/src/core/awaitables/tiers.ts | 10 ++++++++++ packages/website/src/stores/guess.ts | 6 ++++-- 8 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 packages/website/src/core/awaitables/tiers.ts diff --git a/packages/core/src/blitzkit/tankDefinitions/constants.ts b/packages/core/src/blitzkit/tankDefinitions/constants.ts index 5224cdae7..9671b938a 100644 --- a/packages/core/src/blitzkit/tankDefinitions/constants.ts +++ b/packages/core/src/blitzkit/tankDefinitions/constants.ts @@ -54,6 +54,7 @@ export const TIER_ROMAN_NUMERALS: Record = { 8: "VIII", 9: "IX", 10: "X", + 11: "XI", }; export const flags: Record = { diff --git a/packages/website/src/components/Guesser.tsx b/packages/website/src/components/Guesser.tsx index 09c1a3cd0..a468d900b 100644 --- a/packages/website/src/components/Guesser.tsx +++ b/packages/website/src/components/Guesser.tsx @@ -27,9 +27,10 @@ import { TextField, } from "@radix-ui/themes"; import fuzzysort from "fuzzysort"; -import { debounce, times } from "lodash-es"; +import { debounce } from "lodash-es"; import { useCallback, useEffect, useRef, useState } from "react"; import { awaitableTankNames } from "../core/awaitables/tankNames"; +import { awaitableTiers } from "../core/awaitables/tiers"; import { api } from "../core/blitzkit/api"; import { useLocale } from "../hooks/useLocale"; import { Guess, GuessState } from "../stores/guess"; @@ -38,9 +39,10 @@ import { SearchResults } from "./SearchResults"; const { go } = fuzzysort; -const [tankNames, tankDefinitions] = await Promise.all([ +const [tankNames, tankDefinitions, TIERS] = await Promise.all([ awaitableTankNames, api.tankDefinitions(), + awaitableTiers, ]); const ids = Object.keys(tankDefinitions.tanks); @@ -193,8 +195,7 @@ export function Guesser() { - {times(10, (index) => { - const tier = 10 - index; + {TIERS.map((tier) => { const isSelected = tiers.includes(tier); return ( diff --git a/packages/website/src/components/TankSearch/components/FilterControl.tsx b/packages/website/src/components/TankSearch/components/FilterControl.tsx index 41400d365..5892aacd1 100644 --- a/packages/website/src/components/TankSearch/components/FilterControl.tsx +++ b/packages/website/src/components/TankSearch/components/FilterControl.tsx @@ -25,8 +25,8 @@ import { Tooltip, type FlexProps, } from "@radix-ui/themes"; -import { times } from "lodash-es"; import { Fragment, type ComponentProps, type ReactNode } from "react"; +import { awaitableTiers } from "../../../core/awaitables/tiers"; import { api } from "../../../core/blitzkit/api"; import { useLocale } from "../../../hooks/useLocale"; import { App } from "../../../stores/app"; @@ -171,7 +171,7 @@ const GUN_TYPES = Object.keys( const MAX_ICONS = 4; -const TIERS = times(10, (i) => 10 - i); +const TIERS = await awaitableTiers; export function FilterControl() { return ( diff --git a/packages/website/src/components/TankSearch/components/TierFilter.tsx b/packages/website/src/components/TankSearch/components/TierFilter.tsx index 735399ea9..7129ac418 100644 --- a/packages/website/src/components/TankSearch/components/TierFilter.tsx +++ b/packages/website/src/components/TankSearch/components/TierFilter.tsx @@ -1,10 +1,12 @@ import { TIER_ROMAN_NUMERALS } from "@blitzkit/core"; import { Flex, IconButton, Text } from "@radix-ui/themes"; -import { times } from "lodash-es"; import { memo } from "react"; +import { awaitableTiers } from "../../../core/awaitables/tiers"; import { TankFilters } from "../../../stores/tankFilters"; import { TankSort } from "../../../stores/tankopediaSort"; +const TIERS = await awaitableTiers; + export const TierFilter = memo(() => { const by = TankSort.use((state) => state.by); const search = TankFilters.use((state) => state.search); @@ -19,8 +21,7 @@ export const TierFilter = memo(() => { overflow="hidden" style={{ borderRadius: "var(--radius-full)" }} > - {times(10, (index) => { - const tier = 10 - index; + {TIERS.map((tier) => { const selected = tiers.includes(tier); return ( diff --git a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx b/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx index 5a710564e..6f575a078 100644 --- a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx +++ b/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx @@ -127,7 +127,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) { - {averages && nextTanks && tank.tier !== 10 && ( + {averages && nextTanks && nextTanks.length > 0 && ( {skeleton && } {!skeleton && diff --git a/packages/website/src/components/Tankopedia/TechTreeSection/index.tsx b/packages/website/src/components/Tankopedia/TechTreeSection/index.tsx index ab6119b1c..1b099c735 100644 --- a/packages/website/src/components/Tankopedia/TechTreeSection/index.tsx +++ b/packages/website/src/components/Tankopedia/TechTreeSection/index.tsx @@ -191,7 +191,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) { ); })} - {master.tier < 10 && ( + {master.successors.length > 0 && ( <> {master.successors!.map((id, index) => ( diff --git a/packages/website/src/core/awaitables/tiers.ts b/packages/website/src/core/awaitables/tiers.ts new file mode 100644 index 000000000..a83389b73 --- /dev/null +++ b/packages/website/src/core/awaitables/tiers.ts @@ -0,0 +1,10 @@ +import { times } from "lodash-es"; +import { api } from "../blitzkit/api"; + +export const awaitableTiers = api.tankDefinitions().then((tankDefinitions) => { + const maxTier = Math.max( + ...Object.values(tankDefinitions.tanks).map((tank) => tank.tier), + ); + + return times(maxTier, (index) => maxTier - index); +}); diff --git a/packages/website/src/stores/guess.ts b/packages/website/src/stores/guess.ts index 0a3e94f49..76a6f7c8c 100644 --- a/packages/website/src/stores/guess.ts +++ b/packages/website/src/stores/guess.ts @@ -1,6 +1,8 @@ import type { TankDefinition } from "@blitzkit/core"; -import { times } from "lodash-es"; import { Varuna } from "varuna"; +import { awaitableTiers } from "../core/awaitables/tiers"; + +const tiers = await awaitableTiers; export enum GuessState { Correct, @@ -25,5 +27,5 @@ export const Guess = new Varuna((tank) => ({ correctGuesses: 0, streak: 0, helpingReveal: false, - tiers: times(10, (index) => index + 1), + tiers, })); From 541da65bfc910bfa1e80a33cdeb9699fb812894a Mon Sep 17 00:00:00 2001 From: Mark Greene Date: Mon, 5 Oct 2026 19:52:46 +1300 Subject: [PATCH 02/11] Feature: option to turn off automatic tank model loading Lets people on limited data stop the Tankopedia from downloading 3D models automatically. - Added an Automatically load 3D models switch to the settings page, on by default - When off, the tank page hero shows a Load 3D model button where the loading bar sits - Held back every model download on the tank page until requested, including the dynamic armor switcher and the gun flexibility visualizer - Wrapped the tank sandbox in its own suspense boundary so the loading bar shows real progress instead of the whole page waiting on the model - Measured loading progress from when each loading bar appears so it always starts empty - Added the English strings; other languages fall back until translated Fixes #311 --- packages/i18n/strings/en.json | 3 ++ .../GunFlexibilityVisualizer/index.tsx | 4 +- .../HeroSection/components/Options/index.tsx | 3 +- .../components/TankSandbox/Title/index.tsx | 43 +++++++++++++++++-- .../Tankopedia/HeroSection/index.tsx | 11 +++-- .../website/src/hooks/useStableProgress.ts | 4 +- .../[...locale]/settings/_index.tsx | 17 ++++++++ packages/website/src/stores/app/index.ts | 2 + packages/website/src/stores/tankopedia.ts | 3 ++ 9 files changed, 79 insertions(+), 11 deletions(-) diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index fe4ed47b1..c610ad57e 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -215,6 +215,7 @@ "title": "Settings", "description": "Change BlitzKit settings", "dev_mode": "Development mode", + "auto_load_models": "Automatically load 3D models", "language": "Language" }, @@ -458,6 +459,8 @@ }, "sandbox": { + "load_model": "Load 3D model", + "poses": { "hull_down": "Hull down", "face_hug": "Face hug", diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx index 93d7a5e1a..f28050abc 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx @@ -13,6 +13,7 @@ import { modelTransformEvent } from "../../../../../../../core/blitzkit/modelTra import { Var } from "../../../../../../../core/radix/var"; import { useLocale } from "../../../../../../../hooks/useLocale"; import { Duel } from "../../../../../../../stores/duel"; +import { Tankopedia } from "../../../../../../../stores/tankopedia"; import type { MaybeSkeletonComponentProps } from "../../../../../../../types/maybeSkeletonComponentProps"; import { VisualizerCard } from "../VisualizerCard"; import { VisualizerCornerStat } from "../VisualizerCornerStat"; @@ -43,6 +44,7 @@ export function GunFlexibilityVisualizer({ const tank = Duel.use((state) => state.protagonist.tank); const turret = Duel.use((state) => state.protagonist.turret); const gun = Duel.use((state) => state.protagonist.gun); + const modelRequested = Tankopedia.use((state) => state.modelRequested); const tankModel = modelDefinition.models[tank.id]; const turretModel = tankModel.turrets[turret.id]; @@ -193,7 +195,7 @@ export function GunFlexibilityVisualizer({ top="0" left="0" > - {!skeleton && } + {!skeleton && modelRequested && } state.revealed); const disturbed = Tankopedia.use((state) => state.disturbed); const highGraphics = TankopediaPersistent.use((state) => state.highGraphics); + const modelRequested = Tankopedia.use((state) => state.modelRequested); const equalize = Duel.use((state) => state.equalize); const equalizer = Duel.use((state) => state.antagonist.tank.equalizer); @@ -283,7 +284,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { - {!skeleton && ( + {!skeleton && modelRequested && ( diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx index 694d24f12..b3dab3aaf 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx @@ -1,11 +1,14 @@ -import { Flex, Heading } from "@radix-ui/themes"; +import { DownloadIcon } from "@radix-ui/react-icons"; +import { Button, Flex, Heading } from "@radix-ui/themes"; import { useLocale } from "../../../../../../hooks/useLocale"; import { Duel } from "../../../../../../stores/duel"; import { Tankopedia } from "../../../../../../stores/tankopedia"; +import type { MaybeSkeletonComponentProps } from "../../../../../../types/maybeSkeletonComponentProps"; import { Tracker } from "./components/Tracker"; -export function Title() { - const { unwrap } = useLocale(); +export function Title({ skeleton }: MaybeSkeletonComponentProps) { + const { unwrap, strings } = useLocale(); + const modelRequested = Tankopedia.use((state) => state.modelRequested); const protagonist = Duel.use((state) => state.protagonist.tank); const revealed = Tankopedia.use((state) => state.revealed); const disturbed = Tankopedia.use((state) => state.disturbed); @@ -21,6 +24,8 @@ export function Title() { onPointerDown={(event) => { event.preventDefault(); + if (!modelRequested) return; + Tankopedia.mutate((draft) => { draft.revealed = true; }); @@ -60,7 +65,37 @@ export function Title() { {name} - {!revealed && } + {!skeleton && !revealed && modelRequested && ( + + )} + + {!skeleton && !modelRequested && ( + + + + )} ); } diff --git a/packages/website/src/components/Tankopedia/HeroSection/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/index.tsx index 11f13d58e..51205fb49 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/index.tsx @@ -1,6 +1,6 @@ import { Box, Flex } from "@radix-ui/themes"; import { times } from "lodash-es"; -import { useEffect, useMemo, useRef } from "react"; +import { Suspense, useEffect, useMemo, useRef } from "react"; import { api } from "../../../core/blitzkit/api"; import { defaultEqualizer } from "../../../core/blitzkit/tankToDuelMember"; import { useFullScreen } from "../../../hooks/useFullScreen"; @@ -20,6 +20,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) { const canvas = useRef(null!); const isFullScreen = useFullScreen(); const protagonist = Duel.use((state) => state.protagonist.tank); + const modelRequested = Tankopedia.use((state) => state.modelRequested); const thicknessRange = useMemo(() => { const entries = Object.values(tankDefinitions.tanks); const filtered = entries.filter( @@ -131,13 +132,15 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) { top="0" left="0" > - {!skeleton && ( - + {!skeleton && modelRequested && ( + + + )} - + <Title skeleton={skeleton} /> <Options skeleton={skeleton} diff --git a/packages/website/src/hooks/useStableProgress.ts b/packages/website/src/hooks/useStableProgress.ts index 332ded1d0..396136e51 100644 --- a/packages/website/src/hooks/useStableProgress.ts +++ b/packages/website/src/hooks/useStableProgress.ts @@ -3,11 +3,13 @@ import { useRef } from "react"; export function useStableProgress() { const { loaded, total } = useProgress(); + const baseline = useRef(loaded); const progress = useRef(0); + const pending = total - baseline.current; progress.current = Math.max( progress.current, - total === 0 ? 0 : (loaded / total) * 100 + pending <= 0 ? 0 : ((loaded - baseline.current) / pending) * 100 ); return progress.current; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/settings/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/settings/_index.tsx index c5054b676..374d24198 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/settings/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/settings/_index.tsx @@ -18,6 +18,10 @@ export function Page({ locale }: LocaleAcceptorProps) { function Content({ locale }: LocaleAcceptorProps) { const developerMode = App.useDeferred((state) => state.developerMode, false); + const autoLoadModels = App.useDeferred( + (state) => state.autoLoadModels, + true, + ); const { strings } = useLocale(); return ( @@ -36,6 +40,19 @@ function Content({ locale }: LocaleAcceptorProps) { /> </Flex> + <Flex align="center" gap="2" justify="between"> + <Text>{strings.website.settings.auto_load_models}</Text> + <Switch + variant="classic" + checked={autoLoadModels} + onCheckedChange={(checked) => + App.mutate((draft) => { + draft.autoLoadModels = checked; + }) + } + /> + </Flex> + <Flex align="center" gap="2" justify="between"> <Text>{strings.website.settings.language}</Text> <LocaleSwitcher locale={locale} /> diff --git a/packages/website/src/stores/app/index.ts b/packages/website/src/stores/app/index.ts index 6b15394f2..ba975056c 100644 --- a/packages/website/src/stores/app/index.ts +++ b/packages/website/src/stores/app/index.ts @@ -8,6 +8,7 @@ export interface WargamingLogin { export interface App { developerMode: boolean; + autoLoadModels: boolean; policiesAgreementIndex: number; logins: { wargaming?: WargamingLogin; @@ -17,6 +18,7 @@ export interface App { export const App = new Varuna<App>( { developerMode: false, + autoLoadModels: true, policiesAgreementIndex: -1, logins: {}, }, diff --git a/packages/website/src/stores/tankopedia.ts b/packages/website/src/stores/tankopedia.ts index d2eb8a066..023ab0e87 100644 --- a/packages/website/src/stores/tankopedia.ts +++ b/packages/website/src/stores/tankopedia.ts @@ -9,6 +9,7 @@ import type { ArmorType } from "../components/Armor/components/SpacedArmorScene" import type { ExternalModuleVariant } from "../components/Armor/components/SpacedArmorSceneComponent"; import type { XP_MULTIPLIERS } from "../components/Tankopedia/TechTreeSection"; import { api } from "../core/blitzkit/api"; +import { App } from "./app"; import { TankopediaDisplay } from "./tankopediaPersistent/constants"; export interface ShotLayerBase { @@ -79,6 +80,7 @@ export enum TankopediaRelativeAgainst { interface Tankopedia { disturbed: boolean; revealed: boolean; + modelRequested: boolean; shot?: Shot; skills: Record<string, number>; relativeAgainst: TankopediaRelativeAgainst; @@ -110,6 +112,7 @@ const skillDefinitions = await api.skillDefinitions(); export const Tankopedia = new Varuna<Tankopedia, ModelDefinition>((model) => ({ disturbed: false, revealed: false, + modelRequested: App.state.autoLoadModels, relativeAgainst: TankopediaRelativeAgainst.Class, editStatic: false, skills: createDefaultSkills(skillDefinitions), From d79e0ddb5d289c157f95f3157c2fc2a3cfa8bb7c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:24:01 -0500 Subject: [PATCH 03/11] caching for strings --- .../core/src/blitzkit/api/server/0_base.ts | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/core/src/blitzkit/api/server/0_base.ts b/packages/core/src/blitzkit/api/server/0_base.ts index a41ec123d..d67af5898 100644 --- a/packages/core/src/blitzkit/api/server/0_base.ts +++ b/packages/core/src/blitzkit/api/server/0_base.ts @@ -25,8 +25,8 @@ import { VehicleDefinitionList, } from "@blitzkit/core"; import { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract"; -import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; import locales from "@blitzkit/i18n/locales.json"; +import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n/src/strings"; import { parse as parseYaml } from "yaml"; import { BlitzKitAPI } from "../base"; @@ -113,6 +113,9 @@ export abstract class ServerBlitzKitAPI0 extends BlitzKitAPI { } async init() { + const { existsSync } = await import("fs"); + const { readFile, writeFile, mkdir } = await import("fs/promises"); + console.log("Initializing virtual file system..."); await this.vfs.init(); @@ -155,31 +158,37 @@ export abstract class ServerBlitzKitAPI0 extends BlitzKitAPI { console.log("Fetching game localizations..."); - if (import.meta.env.DEV) { - locales.supported = [locales.supported[0]]; - } - - let fetchedLocalizations = 0; await Promise.all( locales.supported.map(async ({ locale }) => { + const root = "../../temp/strings"; + const cachePath = `${root}/${locale}.json`; + + if (existsSync(cachePath)) { + console.log(` Cache hit for ${locale}`); + + const content = await readFile(cachePath, "utf-8"); + this.stringsI18n[locale] = JSON.parse(content) as BlitzStrings; + + return; + } + + console.log(` Cache miss for ${locale} (this will take a while...)`); + const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale]; - const cache = await fetch( + const networkStrings = (await fetch( `https://stufficons.wgcdn.co/localizations/${blitzLocale}.yaml`, ) .then((response) => response.text()) - .then((string) => parseYaml(string) as BlitzStrings); - const preInstalled = await this.vfs.yaml<BlitzStrings>( + .then(parseYaml)) as BlitzStrings; + const localStrings = await this.vfs.yaml<BlitzStrings>( `Data/Strings/${blitzLocale}.yaml`, ); + const combined = { ...networkStrings, ...localStrings }; - this.stringsI18n[locale] = { - ...cache, - ...preInstalled, - }; + await mkdir(root, { recursive: true }); + await writeFile(cachePath, JSON.stringify(combined)); - console.log( - `Fetched localizations for ${locale} (${++fetchedLocalizations}/${locales.supported.length})`, - ); + this.stringsI18n[locale] = combined; }), ); From c70fae81622c6e55593d425bc387b159fb41d387 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:24:29 -0500 Subject: [PATCH 04/11] game strings --- packages/core/src/blitzkit/api/server/0_base.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/blitzkit/api/server/0_base.ts b/packages/core/src/blitzkit/api/server/0_base.ts index d67af5898..2efff35ec 100644 --- a/packages/core/src/blitzkit/api/server/0_base.ts +++ b/packages/core/src/blitzkit/api/server/0_base.ts @@ -160,7 +160,7 @@ export abstract class ServerBlitzKitAPI0 extends BlitzKitAPI { await Promise.all( locales.supported.map(async ({ locale }) => { - const root = "../../temp/strings"; + const root = "../../temp/game-strings"; const cachePath = `${root}/${locale}.json`; if (existsSync(cachePath)) { From 5ce927be4e8748861fa7b5c41a27963c7085501f Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 7 Oct 2026 10:14:44 -0500 Subject: [PATCH 05/11] force bun even with node installed --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 979a37799..3b4791010 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "license": "BlitzKit License", "private": true, "scripts": { - "dev": "cd packages/website && bun run dev", + "dev": "cd packages/website && bun --bun run dev", "build": "cd packages/website && bun run build", "discover-ids": "cd packages/scripts && bun run discover-ids", "build-protos": "cd packages/scripts && bun run build-protos", From 00149a3d9d8a0d9aad1ebbbfb372d703ff4885bc Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:03:41 -0500 Subject: [PATCH 06/11] simplify strings --- packages/i18n/strings/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index c610ad57e..04125f138 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -215,7 +215,7 @@ "title": "Settings", "description": "Change BlitzKit settings", "dev_mode": "Development mode", - "auto_load_models": "Automatically load 3D models", + "auto_load_models": "Auto-load models", "language": "Language" }, @@ -459,7 +459,7 @@ }, "sandbox": { - "load_model": "Load 3D model", + "load_model": "Load model", "poses": { "hull_down": "Hull down", From e1b079ca02c233de57ddbb5047f5e763bd68dc67 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:06:52 -0500 Subject: [PATCH 07/11] high contrast button --- .../HeroSection/components/TankSandbox/Title/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx index b3dab3aaf..69f7a0193 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx @@ -81,8 +81,7 @@ export function Title({ skeleton }: MaybeSkeletonComponentProps) { }} > <Button - variant="surface" - color="gray" + variant="solid" highContrast onPointerDown={(event) => event.stopPropagation()} onClick={() => { From 16be5faae6516e4ba1a5bf8adc68b1ff4a5ef615 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:08:56 -0500 Subject: [PATCH 08/11] use UpdateIcon --- .../HeroSection/components/TankSandbox/Title/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx index 69f7a0193..d31db7a1f 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx @@ -1,4 +1,4 @@ -import { DownloadIcon } from "@radix-ui/react-icons"; +import { UpdateIcon } from "@radix-ui/react-icons"; import { Button, Flex, Heading } from "@radix-ui/themes"; import { useLocale } from "../../../../../../hooks/useLocale"; import { Duel } from "../../../../../../stores/duel"; @@ -90,7 +90,7 @@ export function Title({ skeleton }: MaybeSkeletonComponentProps) { }); }} > - <DownloadIcon /> + <UpdateIcon /> {strings.website.tools.tankopedia.sandbox.load_model} </Button> </Flex> From d4e649804e1a35d740fbd791f675c8ba430b387a Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:15:07 -0500 Subject: [PATCH 09/11] add astro cleaning script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3b4791010..d9bc165ba 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "discover-ids": "cd packages/scripts && bun run discover-ids", "build-protos": "cd packages/scripts && bun run build-protos", "record-performance": "cd packages/scripts && bun run record-performance", - "download-client": "cd packages/scripts && bun run download-client" + "download-client": "cd packages/scripts && bun run download-client", + "clean-astro": "rm -rf packages/website/.astro packages/website/node_modules/.astro packages/website/node_modules/.vite" }, "type": "module", "workspaces": [ From 3397e7f67bc30260f291f197fc4bce4eea31d45e Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:25:34 -0500 Subject: [PATCH 10/11] truthy array --- .../components/Tankopedia/TechTreeSection/components/Node.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx b/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx index 6f575a078..7b743aeaa 100644 --- a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx +++ b/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx @@ -127,7 +127,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) { </Text> </Flex> - {averages && nextTanks && nextTanks.length > 0 && ( + {averages && nextTanks && ( <Text color="gray" size="1" mt="2"> {skeleton && <Skeleton height="1em" width="4em" />} {!skeleton && From 0e119b6ba858db629e9c649670a32e9add880aa2 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:27:38 -0500 Subject: [PATCH 11/11] optimize awaitable tiers --- packages/website/src/core/awaitables/tiers.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/website/src/core/awaitables/tiers.ts b/packages/website/src/core/awaitables/tiers.ts index a83389b73..0729a3d45 100644 --- a/packages/website/src/core/awaitables/tiers.ts +++ b/packages/website/src/core/awaitables/tiers.ts @@ -2,9 +2,11 @@ import { times } from "lodash-es"; import { api } from "../blitzkit/api"; export const awaitableTiers = api.tankDefinitions().then((tankDefinitions) => { - const maxTier = Math.max( - ...Object.values(tankDefinitions.tanks).map((tank) => tank.tier), - ); + let max = 0; - return times(maxTier, (index) => maxTier - index); + for (const tank of Object.values(tankDefinitions.tanks)) { + if (tank.tier > max) max = tank.tier; + } + + return times(max, (index) => max - index); });