i am tired boss

This commit is contained in:
tresabhi committed 2026-10-29 15:29:37 -05:00
1 parent 3b34b01536
commit d96f77bf05
9 files changed
+151 -171

No files matched your search

+27 -6
View File
@@ -1,4 +1,24 @@
{ {
"guesser": {
"help": {
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
},
"search": {
"skip": "Skip",
"guess": "Guess",
"next": "Next"
},
"stats": "{correct} / {total} correct, {streak} streak",
"tier": "Tier {tier}",
"select_all": "Select all",
"clear": "Clear"
},
"units": { "units": {
"m": "{value}m", "m": "{value}m",
"mm": "{value}mm", "mm": "{value}mm",
@@ -261,6 +281,13 @@
"common": { "common": {
"name": "BlitzKit", "name": "BlitzKit",
"search": {
"hint_players": "Search players...",
"hint_tanks": "Search tanks...",
"no_results": "No results",
"searching": "Searching..."
},
"units": { "units": {
"m": "{value}m", "m": "{value}m",
"mm": "{value}mm", "mm": "{value}mm",
@@ -473,12 +500,6 @@
}, },
"common": { "common": {
"player_search": {
"hint": "Search players...",
"no_results": "No results",
"searching": "Searching..."
},
"copy_button": { "copy_button": {
"copied": "Copied!" "copied": "Copied!"
}, },
@@ -1,14 +0,0 @@
.guess-background {
animation: moveBackground 180s linear infinite;
}
@keyframes moveBackground {
from {
top: 0;
left: 0;
}
to {
top: -100%;
left: -100%;
}
}
@@ -0,0 +1,54 @@
.foreground {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
transition-duration: var(--duration-subtle);
background-color: var(--accent-2);
&[data-guess-state="correct"] {
background-color: var(--jade-3);
}
&[data-guess-state="incorrect"] {
background-color: var(--tomato-3);
}
.overlay {
width: 100%;
height: 100%;
background: radial-gradient(circle, var(--gray-a9), var(--gray-3));
}
}
.background {
animation: moveBackground 180s linear infinite;
position: absolute;
width: 200%;
height: 200%;
top: 0;
left: 0;
background-image:
radial-gradient(circle, var(--white-a1) 20%, transparent 20%),
radial-gradient(circle at 0 0, var(--white-a1) 10%, transparent 10%),
radial-gradient(circle at 100% 0, var(--white-a1) 10%, transparent 10%),
radial-gradient(circle at 0 100%, var(--white-a1) 10%, transparent 10%),
radial-gradient(circle at 100% 100%, var(--white-a1) 10%, transparent 10%);
background-size: 4rem 4rem;
}
@keyframes moveBackground {
from {
top: 0;
left: 0;
}
to {
top: -100%;
left: -100%;
}
}
@@ -1,77 +1,31 @@
import { Box } from "@radix-ui/themes";
import { useEffect } from "react"; import { useEffect } from "react";
import { api } from "../../api/dynamic"; import { api } from "../../api/dynamic";
import { tankToDuelMember } from "../../core/blitzkit/tankToDuelMember"; import { Guess } from "../../stores/guess";
import { Var } from "../../core/radix/var";
import { Duel } from "../../stores/duel";
import { Guess, GuessState } from "../../stores/guess";
import { Tankopedia } from "../../stores/tankopedia"; import { Tankopedia } from "../../stores/tankopedia";
import "./index.css"; import { createTankState } from "../../tankopedia/createTankState";
import styles from "./index.module.css";
const [modelDefinitions, provisionDefinitions] = await Promise.all([ const [models, tanks] = await Promise.all([api.models(), api.tanks()]);
api.models(),
api.provisions(),
]);
export function GuessBackground() { export function GuessBackground() {
const guessState = Guess.use((state) => state.guessState); const guessState = Guess.use((state) => state.guessState);
const tank = Guess.use((state) => state.tank); const id = Guess.use((state) => state.tank);
useEffect(() => { useEffect(() => {
Duel.mutate((state) => { const tank = tanks.tanks[id];
state.protagonist = tankToDuelMember(tank, provisionDefinitions);
});
Tankopedia.mutate((state) => { Tankopedia.mutate((state) => {
state.model = modelDefinitions.models[tank.id]; state.protagonist = createTankState(tank);
}); });
}, [tank]); }, [id]);
return ( return (
<> <>
<Box <div className={styles.foreground} data-guess-state={guessState}>
position="absolute" <div className={styles.overlay} />
width="100%" </div>
height="100%"
top="0"
left="0"
style={{
transitionDuration: "2s",
backgroundColor: Var(
guessState === GuessState.NotGuessed
? "accent-2"
: guessState === GuessState.Correct
? "jade-3"
: "tomato-3",
),
}}
>
<Box
width="100%"
height="100%"
style={{
background: `radial-gradient(circle, ${Var("color-panel-translucent")}, ${Var("color-panel-solid")})`,
}}
/>
</Box>
<Box <div className={styles.background} />
className="guess-background"
position="absolute"
width="200%"
height="200%"
top="0"
left="0"
style={{
backgroundImage: `
radial-gradient(circle, ${Var("white-a1")} 20%, transparent 20%),
radial-gradient(circle at 0 0, ${Var("white-a1")} 10%, transparent 10%),
radial-gradient(circle at 100% 0, ${Var("white-a1")} 10%, transparent 10%),
radial-gradient(circle at 0 100%, ${Var("white-a1")} 10%, transparent 10%),
radial-gradient(circle at 100% 100%, ${Var("white-a1")} 10%, transparent 10%)
`,
backgroundSize: "4rem 4rem",
}}
/>
</> </>
); );
} }
+26 -44
View File
@@ -10,48 +10,38 @@ import {
StarFilledIcon, StarFilledIcon,
TrashIcon, TrashIcon,
} from "@radix-ui/react-icons"; } from "@radix-ui/react-icons";
import {
AlertDialog,
Box,
Button,
Card,
DropdownMenu,
Flex,
IconButton,
Spinner,
Text,
TextField,
} from "@radix-ui/themes";
import fuzzysort from "fuzzysort"; import fuzzysort from "fuzzysort";
import { debounce } from "lodash-es"; import { debounce } from "lodash-es";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { api } from "../api/dynamic"; import { api } from "../api/dynamic";
import { awaitableTankNames } from "../core/awaitables/tankNames"; import { awaitableTankNames } from "../core/awaitables/tankNames";
import { awaitableTiers } from "../core/awaitables/tiers"; import { awaitableTiers } from "../core/awaitables/tiers";
import { api } from "../core/blitzkit/api"; import { useStrings } from "../hooks/useStrings";
import { useLocale } from "../hooks/useLocale"; import { useUnwrapper } from "../hooks/useUnwrapper";
import { Guess, GuessState } from "../stores/guess"; import { Guess, GuessState } from "../stores/guess";
import { classIcons } from "./ClassIcon"; import { ClassIcon } from "./ClassIcon";
import { SearchResults } from "./SearchResults"; import { SearchResults } from "./SearchResults";
const { go } = fuzzysort; const { go } = fuzzysort;
const [tankNames, tankDefinitions, TIERS] = await Promise.all([ const [tankNames, tankDefinitions, TIERS] = await Promise.all([
awaitableTankNames, awaitableTankNames,
api.tankDefinitions(), api.tanks(),
awaitableTiers, awaitableTiers,
]); ]);
const ids = Object.keys(tankDefinitions.tanks); const ids = Object.keys(tankDefinitions.tanks);
export function Guesser() { export function Guesser() {
const strings = useStrings();
const unwrap = useUnwrapper();
const tank = Guess.use((state) => state.tank); const tank = Guess.use((state) => state.tank);
const guessState = Guess.use((state) => state.guessState); const guessState = Guess.use((state) => state.guessState);
const correctGuesses = Guess.use((state) => state.correctGuesses); const correctGuesses = Guess.use((state) => state.correctGuesses);
const totalGuesses = Guess.use((state) => state.totalGuesses); const totalGuesses = Guess.use((state) => state.totalGuesses);
const helpingReveal = Guess.use((state) => state.helpingReveal); const helpingReveal = Guess.use((state) => state.helpingReveal);
const streak = Guess.use((state) => state.streak); const streak = Guess.use((state) => state.streak);
const { strings, unwrap } = useLocale();
const input = useRef<HTMLInputElement>(null); const input = useRef<HTMLInputElement>(null);
const [searching, setSearching] = useState(false); const [searching, setSearching] = useState(false);
const [results, setResults] = useState<TankDefinition[] | null>(null); const [results, setResults] = useState<TankDefinition[] | null>(null);
@@ -97,7 +87,7 @@ export function Guesser() {
const interval = setInterval(() => { const interval = setInterval(() => {
if (!status.current) return; if (!status.current) return;
status.current.innerText = literals(strings.website.tools.guess.stats, { status.current.innerText = literals(strings.guesser.stats, {
correct: correctGuesses, correct: correctGuesses,
total: totalGuesses, total: totalGuesses,
streak: streak, streak: streak,
@@ -125,16 +115,12 @@ export function Guesser() {
<Box py="2" px="3"> <Box py="2" px="3">
{results.length === 0 && ( {results.length === 0 && (
<Flex justify="center"> <Flex justify="center">
<Text color="gray"> <Text color="gray">{strings.common.search.no_results}</Text>
{strings.website.tools.guess.search.no_results}
</Text>
</Flex> </Flex>
)} )}
<SearchResults.Root> <SearchResults.Root>
{results.map((result) => { {results.map((result) => {
const Icon = classIcons[result.class];
return ( return (
<SearchResults.Item <SearchResults.Item
key={result.id} key={result.id}
@@ -162,7 +148,11 @@ export function Guesser() {
width="38px" width="38px"
justify="center" justify="center"
> >
<Icon width="1em" height="1em" /> <ClassIcon
class={result.class}
width="1em"
height="1em"
/>
{romanize(result.tier)} {romanize(result.tier)}
</Flex> </Flex>
} }
@@ -214,7 +204,7 @@ export function Guesser() {
}); });
}} }}
> >
{literals(strings.website.tools.guess.tier, { {literals(strings.guesser.tier, {
tier: romanize(tier), tier: romanize(tier),
})} })}
</DropdownMenu.CheckboxItem> </DropdownMenu.CheckboxItem>
@@ -232,7 +222,7 @@ export function Guesser() {
}); });
}} }}
> >
<StarFilledIcon /> {strings.website.tools.guess.select_all} <StarFilledIcon /> {strings.guesser.select_all}
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
@@ -245,7 +235,7 @@ export function Guesser() {
}); });
}} }}
> >
<TrashIcon /> {strings.website.tools.guess.clear} <TrashIcon /> {strings.guesser.clear}
</DropdownMenu.Item> </DropdownMenu.Item>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
@@ -255,7 +245,7 @@ export function Guesser() {
ref={input} ref={input}
onChange={requestSearch} onChange={requestSearch}
style={{ flex: 1, maxWidth: "14rem" }} style={{ flex: 1, maxWidth: "14rem" }}
placeholder={strings.website.tools.guess.search.placeholder} placeholder={strings.common.search.hint_tanks}
size={{ initial: "2", sm: "3" }} size={{ initial: "2", sm: "3" }}
variant="classic" variant="classic"
> >
@@ -275,18 +265,14 @@ export function Guesser() {
</AlertDialog.Trigger> </AlertDialog.Trigger>
<AlertDialog.Content> <AlertDialog.Content>
<AlertDialog.Title> <AlertDialog.Title>{strings.guesser.help.title}</AlertDialog.Title>
{strings.website.tools.guess.help.title}
</AlertDialog.Title>
<AlertDialog.Description> <AlertDialog.Description>
{strings.website.tools.guess.help.description} {strings.guesser.help.description}
</AlertDialog.Description> </AlertDialog.Description>
<Flex justify="end" gap="2"> <Flex justify="end" gap="2">
<AlertDialog.Cancel> <AlertDialog.Cancel>
<Button variant="outline"> <Button variant="outline">{strings.guesser.help.cancel}</Button>
{strings.website.tools.guess.help.cancel}
</Button>
</AlertDialog.Cancel> </AlertDialog.Cancel>
<AlertDialog.Action> <AlertDialog.Action>
@@ -299,7 +285,7 @@ export function Guesser() {
}); });
}} }}
> >
{strings.website.tools.guess.help.reveal} {strings.guesser.help.reveal}
</Button> </Button>
</AlertDialog.Action> </AlertDialog.Action>
</Flex> </Flex>
@@ -316,7 +302,7 @@ export function Guesser() {
onClick={() => { onClick={() => {
if (guessState === GuessState.NotGuessed) { if (guessState === GuessState.NotGuessed) {
const correct = const correct =
selected !== null && selected.id === tank.id && !helpingReveal; selected !== null && selected.id === tank && !helpingReveal;
Guess.mutate((draft) => { Guess.mutate((draft) => {
draft.guessState = correct draft.guessState = correct
@@ -340,7 +326,7 @@ export function Guesser() {
Guess.mutate((draft) => { Guess.mutate((draft) => {
if (!input.current) return; if (!input.current) return;
draft.tank = tank; draft.tank = tank.id;
draft.guessState = GuessState.NotGuessed; draft.guessState = GuessState.NotGuessed;
draft.helpingReveal = false; draft.helpingReveal = false;
@@ -351,16 +337,12 @@ export function Guesser() {
> >
{guessState === GuessState.NotGuessed ? ( {guessState === GuessState.NotGuessed ? (
<> <>
{ {strings.guesser.search[selected === null ? "skip" : "guess"]}
strings.website.tools.guess.search[
selected === null ? "skip" : "guess"
]
}
{selected === null ? <ArrowRightIcon /> : <PaperPlaneIcon />} {selected === null ? <ArrowRightIcon /> : <PaperPlaneIcon />}
</> </>
) : ( ) : (
<> <>
{strings.website.tools.guess.search.next} {strings.guesser.search.next}
<ArrowRightIcon /> <ArrowRightIcon />
</> </>
)} )}
@@ -1,7 +1,7 @@
--- ---
import { AstroSeo } from "@astrolib/seo"; import { AstroSeo } from "@astrolib/seo";
import { api } from "../api/dynamic";
import type { Tool } from "../constants/tools"; import type { Tool } from "../constants/tools";
import { getStrings } from "../core/i18n/getStrings";
interface Props { interface Props {
tool: Tool; tool: Tool;
@@ -9,7 +9,7 @@ interface Props {
} }
const { locale, tool } = Astro.props; const { locale, tool } = Astro.props;
const strings = getStrings(locale); const strings = await api.strings(locale);
const toolStrings = const toolStrings =
strings.website.tools[ strings.website.tools[
(tool.strings ?? tool.id) as keyof typeof strings.website.tools (tool.strings ?? tool.id) as keyof typeof strings.website.tools
@@ -5,13 +5,10 @@ import { GuessBackground } from "../../../../components/GuessBackground";
import { Guesser } from "../../../../components/Guesser"; import { Guesser } from "../../../../components/Guesser";
import { GuessRenderer } from "../../../../components/GuessRenderer"; import { GuessRenderer } from "../../../../components/GuessRenderer";
import { GuessRendererLoader } from "../../../../components/GuessRendererLoader"; import { GuessRendererLoader } from "../../../../components/GuessRendererLoader";
import { PageWrapper } from "../../../../components/PageWrapper"; import { withErrorWrapper } from "../../../../hocs/withErrorWrapper";
import { import { withLocale } from "../../../../hocs/withLocale";
type LocaleAcceptorProps, import { DuelModule, useDuel } from "../../../../hooks/useDuel";
LocaleProvider, import { useUnwrapper } from "../../../../hooks/useUnwrapper";
useLocale,
} from "../../../../hooks/useLocale";
import { Duel } from "../../../../stores/duel";
import { Guess, GuessState } from "../../../../stores/guess"; import { Guess, GuessState } from "../../../../stores/guess";
import { Tankopedia } from "../../../../stores/tankopedia"; import { Tankopedia } from "../../../../stores/tankopedia";
import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps"; import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps";
@@ -23,42 +20,34 @@ const [tankDefinitions, modelDefinitions] = await Promise.all([
const ids = Object.keys(tankDefinitions.tanks); const ids = Object.keys(tankDefinitions.tanks);
export function Page({ export const Page = withLocale(
locale, withErrorWrapper(({ skeleton }: MaybeSkeletonComponentProps) => {
skeleton, const id = Number(ids[Math.floor(Math.random() * ids.length)]);
}: LocaleAcceptorProps & MaybeSkeletonComponentProps) {
const id = Number(ids[Math.floor(Math.random() * ids.length)]);
const tank = tankDefinitions.tanks[id];
Guess.useInitialization(tank); Guess.useInitialization(id);
return ( return <Container skeleton={skeleton} />;
<LocaleProvider locale={locale}> }),
<Container skeleton={skeleton} /> );
</LocaleProvider>
);
}
function Container({ skeleton }: MaybeSkeletonComponentProps) { function Container({ skeleton }: MaybeSkeletonComponentProps) {
const tank = Guess.use((state) => state.tank); const id = Guess.use((state) => state.tank);
const model = modelDefinitions.models[tank.id]; const tank = tankDefinitions.tanks[id];
Tankopedia.useInitialization(model); Tankopedia.useInitialization(tank);
Duel.useInitialization({ tank, model });
return ( return <Content skeleton={skeleton} />;
<PageWrapper p="0" maxWidth="unset" color="cyan">
<Content skeleton={skeleton} />
</PageWrapper>
);
} }
function Content({ skeleton }: MaybeSkeletonComponentProps) { function Content({ skeleton }: MaybeSkeletonComponentProps) {
const { unwrap } = useLocale(); const tank = useDuel("protagonist", DuelModule.Tank);
const tank = Guess.use((state) => state.tank);
const unwrap = useUnwrapper();
const name = unwrap(tank.name!);
const guessState = Guess.use((state) => state.guessState); const guessState = Guess.use((state) => state.guessState);
const isRevealed = guessState !== GuessState.NotGuessed; const isRevealed = guessState !== GuessState.NotGuessed;
const name = unwrap(tank.name!);
const fontSize = `min(48vh, ${55 / name.length}vw)`; const fontSize = `min(48vh, ${55 / name.length}vw)`;
const transitionDuration = isRevealed ? "2s" : undefined; const transitionDuration = isRevealed ? "2s" : undefined;
const tiers = Guess.use((state) => state.tiers); const tiers = Guess.use((state) => state.tiers);
@@ -78,10 +67,9 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
const id = Number( const id = Number(
filteredIds[Math.floor(Math.random() * filteredIds.length)], filteredIds[Math.floor(Math.random() * filteredIds.length)],
); );
const newTank = tankDefinitions.tanks[id];
Guess.mutate((draft) => { Guess.mutate((draft) => {
draft.tank = newTank; draft.tank = id;
draft.guessState = GuessState.NotGuessed; draft.guessState = GuessState.NotGuessed;
draft.helpingReveal = false; draft.helpingReveal = false;
}); });
@@ -1,6 +1,4 @@
--- ---
import ToolOpenGraph from "../../../../components/ToolOpenGraph.astro";
import { tools } from "../../../../constants/tools";
import BaseLayout from "../../../../layouts/BaseLayout.astro"; import BaseLayout from "../../../../layouts/BaseLayout.astro";
import { Page } from "./_index"; import { Page } from "./_index";
@@ -9,9 +7,7 @@ export { getStaticPaths } from "../index.astro";
const { locale } = Astro.props; const { locale } = Astro.props;
--- ---
<ToolOpenGraph {locale} tool={tools.guess} /> <BaseLayout accent="blue" {locale}>
<BaseLayout {locale} noFooter>
<Page {locale} client:only="react"> <Page {locale} client:only="react">
<Page {locale} skeleton slot="fallback" /> <Page {locale} skeleton slot="fallback" />
</Page> </Page>
+6 -7
View File
@@ -1,17 +1,16 @@
import type { TankDefinition } from "@blitzkit/core"; import { Soapstone } from "soapstone";
import { Varuna } from "varuna";
import { awaitableTiers } from "../core/awaitables/tiers"; import { awaitableTiers } from "../core/awaitables/tiers";
const tiers = await awaitableTiers; const tiers = await awaitableTiers;
export enum GuessState { export enum GuessState {
Correct, Correct = "correct",
Incorrect, Incorrect = "incorrect",
NotGuessed, NotGuessed = "not-guessed",
} }
export interface Guess { export interface Guess {
tank: TankDefinition; tank: number;
guessState: GuessState; guessState: GuessState;
totalGuesses: number; totalGuesses: number;
correctGuesses: number; correctGuesses: number;
@@ -20,7 +19,7 @@ export interface Guess {
tiers: number[]; tiers: number[];
} }
export const Guess = new Soapstone<Guess, [TankDefinition]>((tank) => ({ export const Guess = new Soapstone<Guess, [number]>((tank) => ({
tank, tank,
guessState: GuessState.NotGuessed, guessState: GuessState.NotGuessed,
totalGuesses: 0, totalGuesses: 0,