feat: cooler header

This commit is contained in:
tresabhi committed 2025-04-14 19:26:41 -05:00
1 parent f0295079cc
commit 9d916a588d
10 files changed
+99 -38

No files matched your search

@@ -64,6 +64,7 @@ export function InfoWithDelta({
: (stats[props.value] as number);
const delta = useDelta(uhWhatDoICallThisVariable);
const protagonistTank = Duel.use((state) => state.protagonist.tank);
const model = modelDefinitions.models[protagonistTank.id];
const others = useMemo(() => {
const defaultSkills = createDefaultSkills(skillDefinitions);
@@ -78,7 +79,12 @@ export function InfoWithDelta({
relativeAgainst === TankopediaRelativeAgainst.All,
)
.map((tank) => {
const member = tankToDuelMember(tank, provisionDefinitions);
const thisTankModel = modelDefinitions.models[tank.id];
const member = tankToDuelMember(
tank,
thisTankModel,
provisionDefinitions,
);
return tankCharacteristics(
{
@@ -1,7 +1,7 @@
import { OrbitControls } from '@react-three/drei';
import { invalidate, useThree } from '@react-three/fiber';
import { clamp } from 'lodash-es';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useRef } from 'react';
import { PerspectiveCamera, Vector3 } from 'three';
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
import { degToRad } from 'three/src/math/MathUtils.js';
@@ -81,16 +81,14 @@ export function Controls({ naked }: ControlsProps) {
protagonistTrackModelDefinition.origin.y +
antagonistModelDefinition.turret_origin.y +
antagonistTurretModelDefinition.gun_origin.y;
const [autoRotate, setAutoRotate] = useState(
display !== TankopediaDisplay.ShootingRange && !naked,
);
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
const gunHeight =
protagonistHullOrigin.y +
protagonistTurretOrigin.y +
protagonistGunOrigin.y;
const inspectModeInitialPosition = [
-8,
gunHeight + (naked ? 10 : 4),
gunHeight + (naked ? 10 : 2),
-13,
] as const;
const protagonistGunOriginOnlyY = new Vector3(
@@ -217,10 +215,6 @@ export function Controls({ naked }: ControlsProps) {
useEffect(() => {
let isInitialIteration = true;
function handleDisturbance() {
setAutoRotate(false);
}
function handleKeyDown(event: KeyboardEvent) {
if (display !== TankopediaDisplay.ShootingRange || event.repeat) return;
@@ -254,8 +248,14 @@ export function Controls({ naked }: ControlsProps) {
event.preventDefault();
}
poseEvent.on(handleDisturbance);
canvas.addEventListener('pointerdown', handleDisturbance);
function disturb() {
mutateTankopediaEphemeral((draft) => {
draft.disturbed = true;
});
}
poseEvent.on(disturb);
canvas.addEventListener('pointerdown', disturb);
window.addEventListener('keydown', handleKeyDown);
canvas.addEventListener('wheel', handleWheel);
document.body.addEventListener('scroll', handleScroll);
@@ -319,7 +319,7 @@ export function Controls({ naked }: ControlsProps) {
const unsubscribeDisplay = tankopediaEphemeralStore.subscribe(
(state) => state.display === TankopediaDisplay.ShootingRange,
() => {
handleDisturbance();
disturb();
updateCamera();
},
);
@@ -330,8 +330,8 @@ export function Controls({ naked }: ControlsProps) {
);
return () => {
canvas.removeEventListener('pointerdown', handleDisturbance);
poseEvent.off(handleDisturbance);
canvas.removeEventListener('pointerdown', disturb);
poseEvent.off(disturb);
unsubscribeDisplay();
unsubscribeShootingRangeZoom();
window.removeEventListener('keydown', handleKeyDown);
@@ -347,7 +347,7 @@ export function Controls({ naked }: ControlsProps) {
ref={orbitControls}
enabled={tankopediaEphemeralStore.getState().controlsEnabled}
enableDamping={false}
autoRotate={autoRotate}
autoRotate={!disturbed}
autoRotateSpeed={1 / 4}
/>
);
@@ -18,6 +18,7 @@ import { TankSandboxLoader } from './components/TankSandboxLoader';
const tankDefinitions = await awaitableTankDefinitions;
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
const { unwrap } = useLocale();
const canvas = useRef<HTMLCanvasElement>(null);
const isFullScreen = useFullScreen();
@@ -73,10 +74,13 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
}, []);
return (
<Flex justify="center" style={{ backgroundColor: 'black' }}>
<Flex justify="center">
<Flex
direction={{ initial: 'column', md: 'row' }}
style={{ zIndex: isFullScreen ? 2 : undefined }}
style={{
zIndex: isFullScreen ? 2 : undefined,
backgroundColor: 'black',
}}
height={
isFullScreen ? '100vh' : `calc(100vh - ${NAVBAR_HEIGHT}px - 8rem)`
}
@@ -94,22 +98,33 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
left={{ initial: '0', md: '5', lg: '9' }}
py="4"
top={{ initial: '8', md: '50%' }}
style={{ transform: 'translateY(-50%)' }}
style={{ transform: 'translateY(-50%)', transitionDuration: '200ms' }}
direction="column"
align={{ initial: 'center', md: 'start' }}
gap={disturbed ? '0' : '2'}
>
<Flex align="center" gap="3">
<Heading
color={treeColor}
trim="end"
size={{ initial: '7', lg: '6' }}
size={
disturbed
? { initial: '7', lg: '8' }
: { initial: '8', lg: '9' }
}
style={{ transitionDuration: '200ms' }}
>
<Icon width="1em" height="1em" />
</Heading>
<Heading
weight="bold"
size={{ initial: '7', lg: '8' }}
size={
disturbed
? { initial: '7', lg: '8' }
: { initial: '8', lg: '9' }
}
style={{ transitionDuration: '200ms' }}
wrap="nowrap"
color={treeColor}
>
@@ -117,14 +132,21 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
</Heading>
</Flex>
<Text
color="gray"
size={{ initial: '3', lg: '4' }}
weight="light"
ml={{ initial: '0', md: '7', lg: '7' }}
>
BlitzKit Tankopedia
</Text>
<Box ml="3">
<Text
color="gray"
size={{ initial: '3', lg: disturbed ? '4' : '5' }}
weight="light"
ml={{
initial: '0',
md: '8',
lg: disturbed ? 'var(--font-size-8)' : 'var(--font-size-9)',
}}
style={{ transitionDuration: '200ms' }}
>
BlitzKit Tankopedia
</Text>
</Box>
</Flex>
<Box
@@ -134,8 +156,14 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
flexShrink="0"
position="relative"
>
<Box position="absolute" width="100%" height="100%">
<Box width="100%" height="100%">
<Box position="absolute" width="100%" height="100%" overflow="hidden">
<Box
width="100%"
height="100%"
position="relative"
left={disturbed ? '0' : '12.5%'}
style={{ transitionDuration: '200ms' }}
>
{skeleton && <TankSandboxLoader id={protagonist.id} />}
<Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
@@ -95,6 +95,8 @@ export function MetaSection() {
<TankSearch
compact
onSelect={(tank) => {
const model = modelDefinitions.models[tank.id];
setShowSwapDialog(false);
mutateTankopediaEphemeral((draft) => {
draft.model = modelDefinitions.models[tank.id];
@@ -102,6 +104,7 @@ export function MetaSection() {
mutateDuel((draft) => {
draft.protagonist = tankToDuelMember(
tank,
model,
provisionDefinitions,
);
});
@@ -1,14 +1,19 @@
import type { ProvisionDefinitions, TankDefinition } from '@blitzkit/core';
import type {
ModelDefinition,
ProvisionDefinitions,
TankDefinition,
} from '@blitzkit/core';
import { uniqueId } from 'lodash-es';
import type { CompareMember } from '../../stores/compareEphemeral';
import { tankToDuelMember } from './tankToDuelMember';
export function tankToCompareMember(
tank: TankDefinition,
model: ModelDefinition,
provisionDefinitions: ProvisionDefinitions,
) {
return {
...tankToDuelMember(tank, provisionDefinitions),
...tankToDuelMember(tank, model, provisionDefinitions),
key: uniqueId(),
} satisfies CompareMember;
}
@@ -1,17 +1,30 @@
import {
createDefaultProvisions,
ModelDefinition,
type ProvisionDefinitions,
type TankDefinition,
} from '@blitzkit/core';
import { degToRad } from 'three/src/math/MathUtils.js';
import type { DuelMember } from '../../stores/duel';
import { genericDefaultEquipmentMatrix } from '../../stores/duel/constants';
import { applyPitchYawLimits } from '../blitz/applyPitchYawLimits';
export function tankToDuelMember(
tank: TankDefinition,
model: ModelDefinition,
provisionDefinitions: ProvisionDefinitions,
) {
const turret = tank.turrets.at(-1)!;
const turretModel = model.turrets[turret.id];
const gun = turret.guns.at(-1)!;
const gunModel = turretModel.guns[gun.gun_type!.value.base.id];
const [pitch, yaw] = applyPitchYawLimits(
degToRad(8),
degToRad(25),
gunModel.pitch,
turretModel.yaw,
);
return {
camouflage: true,
@@ -24,8 +37,8 @@ export function tankToDuelMember(
shell: tank.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0],
tank,
track: tank.tracks.at(-1)!,
yaw: 0,
pitch: 0,
yaw,
pitch,
turret,
} satisfies DuelMember;
}
@@ -42,7 +42,7 @@ export function Page({ id, skeleton, locale }: PageProps) {
<App.Provider>
<TankopediaPersistent.Provider>
<Duel.Provider
data={{ tank, provisionDefinitions: provisionDefinitions }}
data={{ tank, model, provisionDefinitions: provisionDefinitions }}
>
<PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9">
<HeroSection skeleton={skeleton} />
@@ -27,7 +27,7 @@ export function Page({ id }: PageProps) {
<LocaleProvider locale="en">
<App.Provider>
<TankopediaPersistent.Provider>
<Duel.Provider data={{ provisionDefinitions, tank }}>
<Duel.Provider data={{ provisionDefinitions, model, tank }}>
<TankopediaEphemeral.Provider data={model}>
<Content />
</TankopediaEphemeral.Provider>
+5 -1
View File
@@ -1,4 +1,5 @@
import {
ModelDefinition,
type EngineDefinition,
type GunDefinition,
type ProvisionDefinitions,
@@ -48,16 +49,19 @@ export interface DuelStore {
export const Duel = createContextualStore(
({
tank,
model,
provisionDefinitions,
}: {
tank: TankDefinition;
model: ModelDefinition;
provisionDefinitions: ProvisionDefinitions;
}) => {
const protagonist = tankToDuelMember(tank, provisionDefinitions);
const protagonist = tankToDuelMember(tank, model, provisionDefinitions);
return create<DuelStore>()(
subscribeWithSelector<DuelStore>(() => ({
protagonist,
model,
antagonist: protagonist,
})),
);
@@ -96,6 +96,7 @@ export const SHOOTING_RANGE_ZOOM_COEFFICIENTS: Record<
};
interface TankopediaEphemeral {
disturbed: boolean;
shot?: Shot;
shootingRangeZoom: ShootingRangeZoom;
skills: Record<string, number>;
@@ -128,6 +129,7 @@ export const TankopediaEphemeral = createContextualStore(
(model: ModelDefinition) => {
return create<TankopediaEphemeral>()(
subscribeWithSelector<TankopediaEphemeral>(() => ({
disturbed: false,
shootingRangeZoom: ShootingRangeZoom.Arcade,
relativeAgainst: TankopediaRelativeAgainst.Class,
editStatic: false,