From 636a2934a289375e2974b04fb6dcb2cab71def05 Mon Sep 17 00:00:00 2001 From: Mark Greene Date: Sat, 3 Jan 2026 22:07:01 +1300 Subject: [PATCH 1/7] Reworked Game Mode Ability Filters --- .../TankSearch/components/FilterControl.tsx | 332 ++++++++---------- 1 file changed, 145 insertions(+), 187 deletions(-) diff --git a/packages/website/src/components/TankSearch/components/FilterControl.tsx b/packages/website/src/components/TankSearch/components/FilterControl.tsx index fc51ff253..22cc52588 100644 --- a/packages/website/src/components/TankSearch/components/FilterControl.tsx +++ b/packages/website/src/components/TankSearch/components/FilterControl.tsx @@ -26,7 +26,7 @@ import { type FlexProps, } from "@radix-ui/themes"; import { times } from "lodash-es"; -import { Fragment, type ComponentProps, type ReactNode } from "react"; +import { Fragment, useState, type ComponentProps, type ReactNode } from "react"; import { awaitableConsumableDefinitions } from "../../../core/awaitables/consumableDefinitions"; import { awaitableGameDefinitions } from "../../../core/awaitables/gameDefinitions"; import { awaitableProvisionDefinitions } from "../../../core/awaitables/provisionDefinitions"; @@ -160,17 +160,13 @@ export function FilterControl() { - - - - - + ); @@ -1031,205 +1027,166 @@ function ProvisionsFilter() { ); } -function PowersFilter() { - const { unwrap, strings } = useLocale(); +function GameModeAbilitiesFilter() { + const { unwrap } = useLocale(); + const rawAbilities = TankFilters.use((state) => state.abilities); const rawPowers = TankFilters.use((state) => state.powers); - const powers = - rawPowers.length === 0 - ? Array.from(allGameModeProvisions.values()) - : rawPowers; + const [activeTab, setActiveTab] = useState<"active" | "passive">("active"); + + const totalSelected = rawAbilities.length + rawPowers.length; return ( - - {gameModeRoles.map(({ gameModeId, provisions }) => { - const gameMode = gameDefinitions.gameModes[gameModeId]; - - if (provisions.length === 0) return null; - - return ( - - {unwrap(gameMode.name)} - - {provisions.map((provision) => { - { - const selected = rawPowers.includes(provision); - const powerDefinition = - provisionDefinitions.provisions[provision]; - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.powers = draft.powers.filter( - (n) => n !== provision - ); - } else { - draft.powers = [...draft.powers, provision]; - } - }); - }} - checked={selected} - key={provision} - > - - - {unwrap(powerDefinition.name)} - - ); - } - })} - - ); - })} + + {/* Tab buttons */} + + + + - { - event.preventDefault(); + {/* Active Abilities Content */} + {activeTab === "active" && ( + <> + {gameModeRoles.map(({ gameModeId, consumables }) => { + const gameMode = gameDefinitions.gameModes[gameModeId]; - TankFilters.mutate((draft) => { - draft.powers = []; - }); - }} - > - - {strings.website.common.tank_search.clear} - - - - ); -} + if (consumables.length === 0) return null; -function AbilitiesFilter() { - const { unwrap, strings } = useLocale(); - const rawAbilities = TankFilters.use((state) => state.abilities); - const abilities = - rawAbilities.length === 0 - ? Array.from(allGameModeConsumables.values()) - : rawAbilities; + return ( + + + {unwrap(gameMode.name)} + - return ( - - - - + return ( + { + event.preventDefault(); - - {gameModeRoles.map(({ gameModeId, consumables }) => { - const gameMode = gameDefinitions.gameModes[gameModeId]; - - if (consumables.length === 0) return null; - - return ( - - {unwrap(gameMode.name)} - - {consumables.map((consumable) => { - { - const selected = rawAbilities.includes(consumable); - const abilityDefinition = - consumableDefinitions.consumables[consumable]; - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.abilities = draft.abilities.filter( - (n) => n !== consumable - ); - } else { - draft.abilities = [...draft.abilities, consumable]; - } - }); - }} - checked={selected} - key={consumable} - > - { + if (selected) { + draft.abilities = draft.abilities.filter( + (n) => n !== consumable + ); + } else { + draft.abilities = [ + ...draft.abilities, + consumable, + ]; + } + }); }} - src={asset(`icons/consumables/${consumable}.webp`)} - /> + checked={selected} + key={consumable} + > + - {unwrap(abilityDefinition.name)} - - ); - } - })} - - ); - })} + {unwrap(abilityDefinition.name)} + + ); + })} + + ); + })} + + )} + + {/* Passive Abilities Content */} + {activeTab === "passive" && ( + <> + {gameModeRoles.map(({ gameModeId, provisions }) => { + const gameMode = gameDefinitions.gameModes[gameModeId]; + + if (provisions.length === 0) return null; + + return ( + + + {unwrap(gameMode.name)} + + + {provisions.map((provision) => { + const selected = rawPowers.includes(provision); + const powerDefinition = + provisionDefinitions.provisions[provision]; + + return ( + { + event.preventDefault(); + + TankFilters.mutate((draft) => { + if (selected) { + draft.powers = draft.powers.filter( + (n) => n !== provision + ); + } else { + draft.powers = [...draft.powers, provision]; + } + }); + }} + checked={selected} + key={provision} + > + + + {unwrap(powerDefinition.name)} + + ); + })} + + ); + })} + + )} @@ -1240,11 +1197,12 @@ function AbilitiesFilter() { TankFilters.mutate((draft) => { draft.abilities = []; + draft.powers = []; }); }} > - {strings.website.common.tank_search.clear} + Clear All From 24e57d712537904994662ba44594af7baffed8b1 Mon Sep 17 00:00:00 2001 From: Mark Greene Date: Sun, 4 Jan 2026 10:18:48 +1300 Subject: [PATCH 2/7] Added Tier Filter to Guesser --- packages/website/src/components/Guesser.tsx | 18 ++- .../src/pages/[...locale]/guess/_index.tsx | 108 +++++++++++++++++- 2 files changed, 120 insertions(+), 6 deletions(-) diff --git a/packages/website/src/components/Guesser.tsx b/packages/website/src/components/Guesser.tsx index ce512cffb..d8e8a0e6f 100644 --- a/packages/website/src/components/Guesser.tsx +++ b/packages/website/src/components/Guesser.tsx @@ -41,7 +41,11 @@ const [tankNames, tankDefinitions] = await Promise.all([ const ids = Object.keys(tankDefinitions.tanks); -export function Guesser() { +interface GuesserProps { + selectedTiers: number[]; +} + +export function Guesser({ selectedTiers }: GuesserProps) { const tank = Guess.use((state) => state.tank); const guessState = Guess.use((state) => state.guessState); const correctGuesses = Guess.use((state) => state.correctGuesses); @@ -99,6 +103,8 @@ export function Guesser() { maxWidth="25rem" gap="4" > + + {results !== null && ( @@ -219,6 +225,7 @@ export function Guesser() { + ); } diff --git a/packages/website/src/pages/[...locale]/guess/_index.tsx b/packages/website/src/pages/[...locale]/guess/_index.tsx index 7282e7bbc..b572617ed 100644 --- a/packages/website/src/pages/[...locale]/guess/_index.tsx +++ b/packages/website/src/pages/[...locale]/guess/_index.tsx @@ -1,5 +1,8 @@ -import { Box, Flex, Heading } from "@radix-ui/themes"; -import { Suspense } from "react"; +import { Box, DropdownMenu, Flex, Heading, IconButton, Text } from "@radix-ui/themes"; +import { Suspense, useEffect, useState } from "react"; +import { MixerVerticalIcon } from "@radix-ui/react-icons"; +import { times } from "lodash-es"; +import { TIER_ROMAN_NUMERALS } from "@blitzkit/core"; import { GuessBackground } from "../../../components/GuessBackground"; import { Guesser } from "../../../components/Guesser"; import { GuessRenderer } from "../../../components/GuessRenderer"; @@ -55,18 +58,115 @@ function Container({ skeleton }: MaybeSkeletonComponentProps) { } function Content({ skeleton }: MaybeSkeletonComponentProps) { - const { unwrap } = useLocale(); + const { unwrap, strings } = useLocale(); const tank = Guess.use((state) => state.tank); const guessState = Guess.use((state) => state.guessState); const isRevealed = guessState !== GuessState.NotGuessed; const name = unwrap(tank.name); const fontSize = `min(48vh, ${55 / name.length}vw)`; const transitionDuration = isRevealed ? "2s" : undefined; + const [selectedTiers, setSelectedTiers] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + useEffect(() => { + if (selectedTiers.length === 0) return; + + const filteredIds = ids.filter((id) => { + const tank = tankDefinitions.tanks[Number(id)]; + return selectedTiers.includes(tank.tier); + }); + + if (filteredIds.length === 0) return; + + const id = Number(filteredIds[Math.floor(Math.random() * filteredIds.length)]); + const newTank = tankDefinitions.tanks[id]; + + Guess.mutate((draft) => { + draft.tank = newTank; + draft.guessState = GuessState.NotGuessed; + draft.helpingReveal = false; + }); + }, [selectedTiers]); return ( + + + + + + + + + {times(10, (index) => { + const tier = 10 - index; + const isSelected = selectedTiers.includes(tier); + + return ( + { + event.preventDefault(); + setSelectedTiers((prev) => { + if (prev.includes(tier)) { + return prev.filter((t) => t !== tier); + } else { + return [...prev, tier]; + } + }); + }} + > + {TIER_ROMAN_NUMERALS[tier]} + + ); + })} + + + + setSelectedTiers([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])} + > + Select All + + + setSelectedTiers([])} + > + Clear All + + + + + {selectedTiers.length === 0 && ( + + + Select tier(s) to start playing + + + )} + - + ); } From e24940836088e6c5ee4df93242b67bfcd29251f9 Mon Sep 17 00:00:00 2001 From: Mark Greene Date: Sun, 4 Jan 2026 14:22:20 +1300 Subject: [PATCH 3/7] Fixed Filters not centering --- .../src/components/TankSearch/components/FilterControl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/TankSearch/components/FilterControl.tsx b/packages/website/src/components/TankSearch/components/FilterControl.tsx index 22cc52588..139ff56f0 100644 --- a/packages/website/src/components/TankSearch/components/FilterControl.tsx +++ b/packages/website/src/components/TankSearch/components/FilterControl.tsx @@ -155,7 +155,7 @@ const TIERS = times(10, (i) => 10 - i); export function FilterControl() { return ( - + From 5a0b309b0b1ed2a505bc50d65ee4e7effa3e8898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 4 Jan 2026 14:58:59 -0600 Subject: [PATCH 4/7] feat: combine tabs --- packages/i18n/strings/en.json | 2 + .../TankSearch/components/FilterControl.tsx | 276 +++++++++--------- 2 files changed, 142 insertions(+), 136 deletions(-) diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 94def8954..9d077301b 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -762,6 +762,8 @@ "sorting_by": "Sorting by {name}", "clear": "Clear", "tier": "Tier {tier}", + "passive": "{name} (Passive)", + "active": "{name} (Active)", "no_tanks_found": { "body": "No tanks found.", diff --git a/packages/website/src/components/TankSearch/components/FilterControl.tsx b/packages/website/src/components/TankSearch/components/FilterControl.tsx index 139ff56f0..3991366a1 100644 --- a/packages/website/src/components/TankSearch/components/FilterControl.tsx +++ b/packages/website/src/components/TankSearch/components/FilterControl.tsx @@ -26,7 +26,7 @@ import { type FlexProps, } from "@radix-ui/themes"; import { times } from "lodash-es"; -import { Fragment, useState, type ComponentProps, type ReactNode } from "react"; +import { Fragment, type ComponentProps, type ReactNode } from "react"; import { awaitableConsumableDefinitions } from "../../../core/awaitables/consumableDefinitions"; import { awaitableGameDefinitions } from "../../../core/awaitables/gameDefinitions"; import { awaitableProvisionDefinitions } from "../../../core/awaitables/provisionDefinitions"; @@ -160,13 +160,16 @@ export function FilterControl() { + + + ); @@ -1028,165 +1031,166 @@ function ProvisionsFilter() { } function GameModeAbilitiesFilter() { - const { unwrap } = useLocale(); + const { unwrap, strings } = useLocale(); const rawAbilities = TankFilters.use((state) => state.abilities); const rawPowers = TankFilters.use((state) => state.powers); - const [activeTab, setActiveTab] = useState<"active" | "passive">("active"); - const totalSelected = rawAbilities.length + rawPowers.length; + const abilities = + rawAbilities.length === 0 && rawPowers.length === 0 + ? Array.from(allGameModeConsumables.values()) + : rawAbilities; + const powers = + rawPowers.length === 0 && rawAbilities.length === 0 + ? Array.from(allGameModeProvisions.values()) + : rawPowers; + + const icons = [ + ...abilities.map((ability) => `icons/consumables/${ability}.webp`), + ...powers.map((power) => `icons/provisions/${power}.webp`), + ]; return ( - - {/* Tab buttons */} - - - - + + {gameModeRoles.map(({ gameModeId, consumables, provisions }) => { + const gameMode = gameDefinitions.gameModes[gameModeId]; + const hasNeither = + consumables.length === 0 && provisions.length === 0; - + if (hasNeither) return null; - {/* Active Abilities Content */} - {activeTab === "active" && ( - <> - {gameModeRoles.map(({ gameModeId, consumables }) => { - const gameMode = gameDefinitions.gameModes[gameModeId]; + const hasBoth = consumables.length > 0 && provisions.length > 0; - if (consumables.length === 0) return null; + return ( + + {consumables.length > 0 && ( + + {hasBoth + ? literals(strings.website.common.tank_search.active, { + name: unwrap(gameMode.name), + }) + : unwrap(gameMode.name)} + + )} - return ( - - - {unwrap(gameMode.name)} - + {consumables.map((consumable) => { + { + const selected = rawAbilities.includes(consumable); + const abilityDefinition = + consumableDefinitions.consumables[consumable]; - {consumables.map((consumable) => { - const selected = rawAbilities.includes(consumable); - const abilityDefinition = - consumableDefinitions.consumables[consumable]; + return ( + { + event.preventDefault(); - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.abilities = draft.abilities.filter( - (n) => n !== consumable - ); - } else { - draft.abilities = [ - ...draft.abilities, - consumable, - ]; - } - }); + TankFilters.mutate((draft) => { + if (selected) { + draft.abilities = draft.abilities.filter( + (n) => n !== consumable + ); + } else { + draft.abilities = [...draft.abilities, consumable]; + } + }); + }} + checked={selected} + key={consumable} + > + - + src={asset(`icons/consumables/${consumable}.webp`)} + /> - {unwrap(abilityDefinition.name)} - - ); - })} - - ); - })} - - )} + {unwrap(abilityDefinition.name)} + + ); + } + })} - {/* Passive Abilities Content */} - {activeTab === "passive" && ( - <> - {gameModeRoles.map(({ gameModeId, provisions }) => { - const gameMode = gameDefinitions.gameModes[gameModeId]; + {provisions.length > 0 && ( + + {hasBoth + ? literals(strings.website.common.tank_search.passive, { + name: unwrap(gameMode.name), + }) + : unwrap(gameMode.name)} + + )} - if (provisions.length === 0) return null; + {provisions.map((provision) => { + { + const selected = rawPowers.includes(provision); + const powerDefinition = + provisionDefinitions.provisions[provision]; - return ( - - - {unwrap(gameMode.name)} - + return ( + { + event.preventDefault(); - {provisions.map((provision) => { - const selected = rawPowers.includes(provision); - const powerDefinition = - provisionDefinitions.provisions[provision]; - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.powers = draft.powers.filter( - (n) => n !== provision - ); - } else { - draft.powers = [...draft.powers, provision]; - } - }); + TankFilters.mutate((draft) => { + if (selected) { + draft.powers = draft.powers.filter( + (n) => n !== provision + ); + } else { + draft.powers = [...draft.powers, provision]; + } + }); + }} + checked={selected} + key={provision} + > + - + src={asset(`icons/provisions/${provision}.webp`)} + /> - {unwrap(powerDefinition.name)} - - ); - })} - - ); - })} - - )} + {unwrap(powerDefinition.name)} + + ); + } + })} + + ); + })} @@ -1202,7 +1206,7 @@ function GameModeAbilitiesFilter() { }} > - Clear All + {strings.website.common.tank_search.clear} From 7997f490fcf9107d28ccf9050e6701ca8a345ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 4 Jan 2026 15:11:20 -0600 Subject: [PATCH 5/7] feat: clean up tiers ui --- packages/i18n/strings/en.json | 5 +- packages/website/src/components/Guesser.tsx | 182 ++++++++++++------ .../src/pages/[...locale]/guess/_index.tsx | 90 ++------- packages/website/src/stores/guess.ts | 3 + 4 files changed, 147 insertions(+), 133 deletions(-) diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 9d077301b..4ecf9071a 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -930,7 +930,10 @@ "description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!", "cancel": "Cancel", "reveal": "Reveal" - } + }, + "clear": "Clear", + "select_all": "Select all", + "tier": "Tier {tier}" }, "tankopedia": { diff --git a/packages/website/src/components/Guesser.tsx b/packages/website/src/components/Guesser.tsx index d8e8a0e6f..3600ee417 100644 --- a/packages/website/src/components/Guesser.tsx +++ b/packages/website/src/components/Guesser.tsx @@ -9,13 +9,17 @@ import { ArrowRightIcon, EyeOpenIcon, MagnifyingGlassIcon, + MixerVerticalIcon, PaperPlaneIcon, + StarFilledIcon, + TrashIcon, } from "@radix-ui/react-icons"; import { AlertDialog, Box, Button, Card, + DropdownMenu, Flex, IconButton, Spinner, @@ -23,7 +27,7 @@ import { TextField, } from "@radix-ui/themes"; import fuzzysort from "fuzzysort"; -import { debounce } from "lodash-es"; +import { debounce, times } from "lodash-es"; import { useCallback, useEffect, useRef, useState } from "react"; import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions"; import { awaitableTankNames } from "../core/awaitables/tankNames"; @@ -41,11 +45,7 @@ const [tankNames, tankDefinitions] = await Promise.all([ const ids = Object.keys(tankDefinitions.tanks); -interface GuesserProps { - selectedTiers: number[]; -} - -export function Guesser({ selectedTiers }: GuesserProps) { +export function Guesser() { const tank = Guess.use((state) => state.tank); const guessState = Guess.use((state) => state.guessState); const correctGuesses = Guess.use((state) => state.correctGuesses); @@ -57,6 +57,7 @@ export function Guesser({ selectedTiers }: GuesserProps) { const [searching, setSearching] = useState(false); const [results, setResults] = useState(null); const [selected, setSelected] = useState(null); + const tiers = Guess.use((state) => state.tiers); const requestSearch = useCallback(() => { setSelected(null); @@ -101,60 +102,60 @@ export function Guesser({ selectedTiers }: GuesserProps) { width="100%" p="4" maxWidth="25rem" - gap="4" + gap="3" > + {results !== null && ( + + + {results.length === 0 && ( + + + {strings.website.tools.guess.search.no_results} + + + )} - {results !== null && ( - - - {results.length === 0 && ( - - - {strings.website.tools.guess.search.no_results} - - - )} + + {results.map((result) => { + const Icon = classIcons[result.class]; - - {results.map((result) => { - const Icon = classIcons[result.class]; + return ( + { + if (!input.current) return; - return ( - { - if (!input.current) return; - - input.current.value = unwrap(result.name); - setSelected(result); - setResults(null); - }} - text={unwrap(result.name)} - prefix={ - - } - discriminator={ - - - {TIER_ROMAN_NUMERALS[result.tier]} - - } - /> - ); - })} - - - - )} + input.current.value = unwrap(result.name); + setSelected(result); + setResults(null); + }} + text={unwrap(result.name)} + prefix={ + + } + discriminator={ + + + {TIER_ROMAN_NUMERALS[result.tier]} + + } + /> + ); + })} + + + + )} + @@ -167,11 +168,75 @@ export function Guesser({ selectedTiers }: GuesserProps) { + + + + + + + + + {times(10, (index) => { + const tier = 10 - index; + const isSelected = tiers.includes(tier); + + return ( + { + event.preventDefault(); + + Guess.mutate((draft) => { + if (isSelected) { + draft.tiers = draft.tiers.filter((t) => t !== tier); + } else { + draft.tiers = [...draft.tiers, tier]; + } + }); + }} + > + {literals(strings.website.tools.guess.tier, { + tier: TIER_ROMAN_NUMERALS[tier], + })} + + ); + })} + + + + { + event.preventDefault(); + + Guess.mutate((draft) => { + draft.tiers = Guess.initial.tiers; + }); + }} + > + {strings.website.tools.guess.select_all} + + + { + event.preventDefault(); + + Guess.mutate((draft) => { + draft.tiers = []; + }); + }} + > + {strings.website.tools.guess.clear} + + + + { const tank = tankDefinitions.tanks[Number(id)]; - return selectedTiers.includes(tank.tier); + return tiers.includes(tank.tier); }); const id = Number( filteredIds[Math.floor(Math.random() * filteredIds.length)] @@ -283,7 +348,6 @@ export function Guesser({ selectedTiers }: GuesserProps) { )} - ); } diff --git a/packages/website/src/pages/[...locale]/guess/_index.tsx b/packages/website/src/pages/[...locale]/guess/_index.tsx index b572617ed..b549bd0bc 100644 --- a/packages/website/src/pages/[...locale]/guess/_index.tsx +++ b/packages/website/src/pages/[...locale]/guess/_index.tsx @@ -1,8 +1,5 @@ -import { Box, DropdownMenu, Flex, Heading, IconButton, Text } from "@radix-ui/themes"; -import { Suspense, useEffect, useState } from "react"; -import { MixerVerticalIcon } from "@radix-ui/react-icons"; -import { times } from "lodash-es"; -import { TIER_ROMAN_NUMERALS } from "@blitzkit/core"; +import { Box, Flex, Heading, Text } from "@radix-ui/themes"; +import { Suspense, useEffect } from "react"; import { GuessBackground } from "../../../components/GuessBackground"; import { Guesser } from "../../../components/Guesser"; import { GuessRenderer } from "../../../components/GuessRenderer"; @@ -58,26 +55,28 @@ function Container({ skeleton }: MaybeSkeletonComponentProps) { } function Content({ skeleton }: MaybeSkeletonComponentProps) { - const { unwrap, strings } = useLocale(); + const { unwrap } = useLocale(); const tank = Guess.use((state) => state.tank); const guessState = Guess.use((state) => state.guessState); const isRevealed = guessState !== GuessState.NotGuessed; const name = unwrap(tank.name); const fontSize = `min(48vh, ${55 / name.length}vw)`; const transitionDuration = isRevealed ? "2s" : undefined; - const [selectedTiers, setSelectedTiers] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + const tiers = Guess.use((state) => state.tiers); useEffect(() => { - if (selectedTiers.length === 0) return; + if (tiers.length === 0) return; const filteredIds = ids.filter((id) => { const tank = tankDefinitions.tanks[Number(id)]; - return selectedTiers.includes(tank.tier); + return tiers.includes(tank.tier); }); if (filteredIds.length === 0) return; - const id = Number(filteredIds[Math.floor(Math.random() * filteredIds.length)]); + const id = Number( + filteredIds[Math.floor(Math.random() * filteredIds.length)] + ); const newTank = tankDefinitions.tanks[id]; Guess.mutate((draft) => { @@ -85,72 +84,13 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) { draft.guessState = GuessState.NotGuessed; draft.helpingReveal = false; }); - }, [selectedTiers]); + }, [tiers]); return ( - - - - - - - - - {times(10, (index) => { - const tier = 10 - index; - const isSelected = selectedTiers.includes(tier); - - return ( - { - event.preventDefault(); - setSelectedTiers((prev) => { - if (prev.includes(tier)) { - return prev.filter((t) => t !== tier); - } else { - return [...prev, tier]; - } - }); - }} - > - {TIER_ROMAN_NUMERALS[tier]} - - ); - })} - - - - setSelectedTiers([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])} - > - Select All - - - setSelectedTiers([])} - > - Clear All - - - - - {selectedTiers.length === 0 && ( + {tiers.length === 0 && ( - + Select tier(s) to start playing @@ -216,7 +160,7 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) { {name} - + ); } diff --git a/packages/website/src/stores/guess.ts b/packages/website/src/stores/guess.ts index 22d6a10f0..0a3e94f49 100644 --- a/packages/website/src/stores/guess.ts +++ b/packages/website/src/stores/guess.ts @@ -1,4 +1,5 @@ import type { TankDefinition } from "@blitzkit/core"; +import { times } from "lodash-es"; import { Varuna } from "varuna"; export enum GuessState { @@ -14,6 +15,7 @@ export interface Guess { correctGuesses: number; streak: number; helpingReveal: boolean; + tiers: number[]; } export const Guess = new Varuna((tank) => ({ @@ -23,4 +25,5 @@ export const Guess = new Varuna((tank) => ({ correctGuesses: 0, streak: 0, helpingReveal: false, + tiers: times(10, (index) => index + 1), })); From 85cd70b2d5ba1ef78483914a51b47bcf3b1251b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 4 Jan 2026 15:16:57 -0600 Subject: [PATCH 6/7] feat: disable selecting 0 tiers --- packages/website/src/components/Guesser.tsx | 3 ++- .../src/pages/[...locale]/guess/_index.tsx | 27 +++---------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/packages/website/src/components/Guesser.tsx b/packages/website/src/components/Guesser.tsx index 3600ee417..e10baace6 100644 --- a/packages/website/src/components/Guesser.tsx +++ b/packages/website/src/components/Guesser.tsx @@ -189,6 +189,8 @@ export function Guesser() { Guess.mutate((draft) => { if (isSelected) { + if (draft.tiers.length === 1) return; + draft.tiers = draft.tiers.filter((t) => t !== tier); } else { draft.tiers = [...draft.tiers, tier]; @@ -290,7 +292,6 @@ export function Guesser() {