feat: better hero

This commit is contained in:
TrèsAbhi committed 2024-08-28 22:22:53 -05:00
1 parent cd0769675e
commit c3e449745e
8 files changed
+176 -206

No files matched your search

@@ -11,6 +11,7 @@ import { useAwait } from '../../../../../hooks/useAwait';
import * as Duel from '../../../../../stores/duel'; import * as Duel from '../../../../../stores/duel';
import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral'; import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral';
const initialPosition = [0, 5, -16] as const;
const poseDistances: Record<Pose, number> = { const poseDistances: Record<Pose, number> = {
[Pose.HullDown]: 15, [Pose.HullDown]: 15,
[Pose.FaceHug]: 5, [Pose.FaceHug]: 5,
@@ -59,7 +60,7 @@ export function Controls() {
const [autoRotate, setAutoRotate] = useState(true); const [autoRotate, setAutoRotate] = useState(true);
useEffect(() => { useEffect(() => {
camera.position.set(-10, 5, -12); camera.position.set(...initialPosition);
orbitControls.current?.target.set(0, 1.25, 0); orbitControls.current?.target.set(0, 1.25, 0);
}, [camera]); }, [camera]);
@@ -150,7 +151,7 @@ export function Controls() {
} }
case Pose.Default: { case Pose.Default: {
camera.position.set(-4, 4, -16); camera.position.set(...initialPosition);
orbitControls.current?.target.set(0, 1.25, 0); orbitControls.current?.target.set(0, 1.25, 0);
break; break;
} }
@@ -24,6 +24,7 @@ export function Lighting() {
return ( return (
<> <>
<Environment preset={environment} background={showEnvironment} blur={0} /> <Environment preset={environment} background={showEnvironment} blur={0} />
<directionalLight <directionalLight
position={[1, 1, -1]} position={[1, 1, -1]}
intensity={hasPbr ? 1 : 5} intensity={hasPbr ? 1 : 5}
@@ -1,16 +1,16 @@
import { ChevronLeftIcon, MixIcon } from '@radix-ui/react-icons'; import { Box, Flex, Heading, Text } from '@radix-ui/themes';
import { Box, Button, Flex, Heading } from '@radix-ui/themes';
import Link from 'next/link';
import { Suspense, use, useEffect, useMemo, useRef, useState } from 'react'; import { Suspense, use, useEffect, useMemo, useRef, useState } from 'react';
import { ThicknessRange } from '../../../../../../components/Armor/components/StaticArmor'; import { ThicknessRange } from '../../../../../../components/Armor/components/StaticArmor';
import { classIcons } from '../../../../../../components/ClassIcon'; import { classIcons } from '../../../../../../components/ClassIcon';
import { NAVBAR_HEIGHT } from '../../../../../../components/Navbar/index.css';
import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration'; import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration';
import { tankDefinitions } from '../../../../../../core/blitzkit/tankDefinitions'; import { tankDefinitions } from '../../../../../../core/blitzkit/tankDefinitions';
import { Var } from '../../../../../../core/blitzkit/var';
import { useFullScreen } from '../../../../../../hooks/useFullScreen'; import { useFullScreen } from '../../../../../../hooks/useFullScreen';
import * as Duel from '../../../../../../stores/duel'; import * as Duel from '../../../../../../stores/duel';
import { Options } from './components/Options';
import { TankSandbox } from './TankSandbox'; import { TankSandbox } from './TankSandbox';
import { TankSandboxLoader } from './TankSandboxLoader'; import { TankSandboxLoader } from './TankSandboxLoader';
import { Options } from './components/Options';
interface HeroSectionProps { interface HeroSectionProps {
id: number; id: number;
@@ -18,13 +18,8 @@ interface HeroSectionProps {
export function HeroSection({ id }: HeroSectionProps) { export function HeroSection({ id }: HeroSectionProps) {
const canvas = useRef<HTMLCanvasElement>(null); const canvas = useRef<HTMLCanvasElement>(null);
const protagonist = Duel.use((state) => state.protagonist.tank);
const antagonist = Duel.use((state) => state.antagonist.tank);
const compareTanks =
protagonist.id === antagonist.id
? [protagonist.id]
: [protagonist.id, antagonist.id];
const isFullScreen = useFullScreen(); const isFullScreen = useFullScreen();
const protagonist = Duel.use((state) => state.protagonist.tank);
const Icon = classIcons[protagonist.class]; const Icon = classIcons[protagonist.class];
const [dummyLoader, setDummyLoader] = useState(true); const [dummyLoader, setDummyLoader] = useState(true);
const treeColor = const treeColor =
@@ -58,102 +53,72 @@ export function HeroSection({ id }: HeroSectionProps) {
}, []); }, []);
return ( return (
<Flex <Box
justify="center" style={{
pt={{ initial: '6', md: '0' }} background: isFullScreen
style={{ background: 'var(--color-surface)', position: 'relative' }} ? Var('color-background')
height={{ initial: 'calc(75vh - 4rem)', md: undefined }} : Var('color-surface'),
minHeight={{ initial: '40rem' }} zIndex: isFullScreen ? 2 : undefined,
gap="4" }}
height={
isFullScreen ? '100vh' : `calc(100vh - ${NAVBAR_HEIGHT}px - 8rem)`
}
width={isFullScreen ? '100vw' : undefined}
position={isFullScreen ? 'fixed' : 'relative'}
top={isFullScreen ? '0' : undefined}
left={isFullScreen ? '0' : undefined}
> >
<Flex <Flex
direction={{ initial: 'column', md: 'row' }} position="absolute"
style={{ maxWidth: '140rem', flex: 1 }} width="100%"
gap={{ initial: '0', md: '6' }} height="100%"
align="center"
direction="column"
mt="7"
> >
<Flex <Heading
align="center" weight="bold"
justify="center" size={{ initial: '8', xs: '9' }}
flexGrow={{ initial: undefined, md: '1' }} color={treeColor}
flexBasis="0"
> >
<Flex <Flex align="center" gap="3">
gap="2" <Icon width="0.75em" height="0.75em" />
direction="column" {tank.name}
ml={{ initial: '0', md: '8' }}
align={{ initial: 'center', md: 'start' }}
justify="center"
style={{ height: '100%', userSelect: 'none' }}
>
<Flex gap="4">
<Heading size={{ initial: '8', sm: '9' }} color={treeColor}>
<Icon style={{ width: '0.75em', height: '0.75em' }} />{' '}
</Heading>
<Heading
size={{ initial: '8', sm: '9' }}
align={{ initial: 'center', md: 'left' }}
color={treeColor}
>
{protagonist.name}
</Heading>
</Flex>
<Flex gap="4" mt="2" pl="8">
<Link href="/tools/tankopedia">
<Button variant="ghost" size="1" ml="-1">
<ChevronLeftIcon />
Back
</Button>
</Link>
<Link href={`/tools/compare?tanks=${compareTanks.join('%2C')}`}>
<Button variant="ghost" size="1">
<MixIcon />
Compare
</Button>
</Link>
</Flex>
</Flex> </Flex>
</Flex> </Heading>
<Text color="gray" weight="light" mt="1">
<Box BlitzKit Tankopedia
className="tank-sandbox-container" </Text>
height={{ initial: undefined, md: '512px' }}
style={{
flex: 2,
position: isFullScreen ? 'fixed' : 'relative',
top: isFullScreen ? 0 : undefined,
left: isFullScreen ? 0 : undefined,
width: isFullScreen ? '100vw' : undefined,
height: isFullScreen ? '100vh' : 'auto',
zIndex: isFullScreen ? 3 : undefined,
background: isFullScreen ? 'var(--color-background)' : undefined,
}}
>
<Box width="100%" height="100%" position="absolute">
<Box width="100%" height="100%">
<TankSandboxLoader
id={id}
display={dummyLoader ? 'flex' : 'none'}
/>
<Suspense
fallback={
<TankSandboxLoader
id={id}
display={dummyLoader ? 'none' : 'flex'}
/>
}
>
<TankSandbox ref={canvas} thicknessRange={thicknessRange} />
</Suspense>
</Box>
<Options canvas={canvas} thicknessRange={thicknessRange} />
</Box>
</Box>
</Flex> </Flex>
</Flex>
<Box
position="absolute"
width="100%"
height="100%"
className="tank-sandbox-container"
>
<Box width="100%" height="100%" position="absolute">
<Box width="100%" height="100%">
<TankSandboxLoader
id={id}
display={dummyLoader ? 'flex' : 'none'}
/>
<Suspense
fallback={
<TankSandboxLoader
id={id}
display={dummyLoader ? 'none' : 'flex'}
/>
}
>
<TankSandbox ref={canvas} thicknessRange={thicknessRange} />
</Suspense>
</Box>
<Options canvas={canvas} thicknessRange={thicknessRange} />
</Box>
</Box>
</Box>
); );
} }
@@ -1,14 +1,15 @@
import { import {
CameraIcon, CameraIcon,
ChevronLeftIcon,
CopyIcon, CopyIcon,
DownloadIcon, DownloadIcon,
EnterFullScreenIcon, EnterFullScreenIcon,
ExitFullScreenIcon, ExitFullScreenIcon,
EyeOpenIcon, EyeOpenIcon,
GearIcon, GearIcon,
MixIcon,
} from '@radix-ui/react-icons'; } from '@radix-ui/react-icons';
import { import {
Box,
Button, Button,
Dialog, Dialog,
DropdownMenu, DropdownMenu,
@@ -16,7 +17,6 @@ import {
IconButton, IconButton,
Popover, Popover,
SegmentedControl, SegmentedControl,
Strong,
Tabs, Tabs,
Text, Text,
Tooltip, Tooltip,
@@ -24,6 +24,7 @@ import {
import { invalidate } from '@react-three/fiber'; import { invalidate } from '@react-three/fiber';
import { RefObject, use, useEffect, useState } from 'react'; import { RefObject, use, useEffect, useState } from 'react';
import { ThicknessRange } from '../../../../../../../components/Armor/components/StaticArmor'; import { ThicknessRange } from '../../../../../../../components/Armor/components/StaticArmor';
import { Link } from '../../../../../../../components/Link';
import { ModuleButton } from '../../../../../../../components/ModuleButtons/ModuleButton'; import { ModuleButton } from '../../../../../../../components/ModuleButtons/ModuleButton';
import { SmallTankIcon } from '../../../../../../../components/SmallTankIcon'; import { SmallTankIcon } from '../../../../../../../components/SmallTankIcon';
import { resolveNearPenetration } from '../../../../../../../core/blitz/resolveNearPenetration'; import { resolveNearPenetration } from '../../../../../../../core/blitz/resolveNearPenetration';
@@ -60,6 +61,12 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
const [display, setDisplay] = useState<TankopediaDisplay>( const [display, setDisplay] = useState<TankopediaDisplay>(
TankopediaDisplay.Model, TankopediaDisplay.Model,
); );
const antagonist = Duel.use((state) => state.antagonist.tank);
const protagonist = Duel.use((state) => state.protagonist.tank);
const compareTanks =
protagonist.id === antagonist.id
? [protagonist.id]
: [protagonist.id, antagonist.id];
const isFullScreen = useFullScreen(); const isFullScreen = useFullScreen();
const showGrid = TankopediaPersistent.use( const showGrid = TankopediaPersistent.use(
(state) => state.model.visual.showGrid, (state) => state.model.visual.showGrid,
@@ -108,31 +115,6 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
return ( return (
<> <>
{isFullScreen && (
<Box
pl="4"
style={{
position: 'absolute',
left: 0,
top: '50%',
transform: 'translateY(-50%)',
}}
>
<Text
size={{
initial: '3',
xs: '5',
}}
color="gray"
style={{
writingMode: 'vertical-lr',
}}
>
<Strong>BlitzKit</Strong> Tankopedia
</Text>
</Box>
)}
<QuickInputs /> <QuickInputs />
{display === TankopediaDisplay.StaticArmor && ( {display === TankopediaDisplay.StaticArmor && (
@@ -360,73 +342,30 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
</Flex> </Flex>
)} )}
<Box position="absolute" top="3" right="3">
<Popover.Root>
<Popover.Trigger>
<IconButton size={{ initial: '2', sm: '3' }} variant="soft">
<CameraIcon />
</IconButton>
</Popover.Trigger>
<Popover.Content>
<Flex direction="column" gap="2">
<Popover.Close>
<Button
onClick={() => {
if (!canvas.current) return;
const anchor = document.createElement('a');
anchor.setAttribute(
'download',
`${protagonistTank.name}.png`,
);
anchor.setAttribute(
'href',
canvas.current.toDataURL('image/png'),
);
anchor.click();
}}
>
<DownloadIcon />
Download
</Button>
</Popover.Close>
<Popover.Close>
<Button
variant="outline"
onClick={() => {
if (!canvas.current) return;
canvas.current.toBlob((blob) => {
if (!blob) return;
navigator.clipboard.write([
new ClipboardItem({ 'image/png': blob }),
]);
});
}}
>
<CopyIcon />
Copy
</Button>
</Popover.Close>
</Flex>
</Popover.Content>
</Popover.Root>
</Box>
<Flex <Flex
style={{
position: 'absolute',
bottom: 16,
left: '50%',
transform: 'translateX(-50%)',
}}
direction="column" direction="column"
align="center" align="center"
gap="2" position="absolute"
bottom="2"
left="50%"
style={{ transform: 'translateX(-50%)' }}
> >
<Flex gap="4">
<Link href="/tools/tankopedia">
<Button variant="ghost" size="1">
<ChevronLeftIcon />
Back
</Button>
</Link>
<Link href={`/tools/compare?tanks=${compareTanks.join('%2C')}`}>
<Button variant="ghost" size="1">
<MixIcon />
Compare
</Button>
</Link>
</Flex>
{display === TankopediaDisplay.DynamicArmor && ( {display === TankopediaDisplay.DynamicArmor && (
<Flex align="center" gap="2"> <Flex align="center" gap="2">
<Text color="gray" size="2"> <Text color="gray" size="2">
@@ -472,7 +411,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
</Flex> </Flex>
)} )}
<Flex gap="3" align="center"> <Flex gap="3" align="center" mt="2">
<SegmentedControl.Root <SegmentedControl.Root
value={`${display}`} value={`${display}`}
onValueChange={(value) => { onValueChange={(value) => {
@@ -525,6 +464,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
</DropdownMenu.Item> </DropdownMenu.Item>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
<DropdownMenu.Root> <DropdownMenu.Root>
<DropdownMenu.Trigger> <DropdownMenu.Trigger>
<IconButton variant="ghost" color="gray"> <IconButton variant="ghost" color="gray">
@@ -621,6 +561,61 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
</DropdownMenu.Sub> </DropdownMenu.Sub>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
<Popover.Root>
<Popover.Trigger>
<IconButton color="gray" variant="ghost">
<CameraIcon />
</IconButton>
</Popover.Trigger>
<Popover.Content>
<Flex direction="column" gap="2">
<Popover.Close>
<Button
onClick={() => {
if (!canvas.current) return;
const anchor = document.createElement('a');
anchor.setAttribute(
'download',
`${protagonistTank.name}.png`,
);
anchor.setAttribute(
'href',
canvas.current.toDataURL('image/png'),
);
anchor.click();
}}
>
<DownloadIcon />
Download
</Button>
</Popover.Close>
<Popover.Close>
<Button
variant="outline"
onClick={() => {
if (!canvas.current) return;
canvas.current.toBlob((blob) => {
if (!blob) return;
navigator.clipboard.write([
new ClipboardItem({ 'image/png': blob }),
]);
});
}}
>
<CopyIcon />
Copy
</Button>
</Popover.Close>
</Flex>
</Popover.Content>
</Popover.Root>
{fullScreenAvailable && ( {fullScreenAvailable && (
<IconButton <IconButton
color="gray" color="gray"
@@ -3,6 +3,10 @@ import { style } from '@vanilla-extract/css';
export const container = style({ export const container = style({
display: 'none', display: 'none',
':focus-within': {
display: 'flex',
},
selectors: { selectors: {
'.tank-sandbox-container:hover &': { '.tank-sandbox-container:hover &': {
display: 'flex', display: 'flex',
@@ -23,6 +23,11 @@ export function QuickInputs() {
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id]; const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const initialGunPitch = tankModelDefinition.turretRotation?.pitch ?? 0; const initialGunPitch = tankModelDefinition.turretRotation?.pitch ?? 0;
const hasImprovedVerticalStabilizer = useEquipment(122); const hasImprovedVerticalStabilizer = useEquipment(122);
const antagonist = Duel.use((state) => state.antagonist.tank);
const compareTanks =
protagonist.tank.id === antagonist.id
? [protagonist.tank.id]
: [protagonist.tank.id, antagonist.id];
useEffect(() => { useEffect(() => {
yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1); yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
@@ -54,17 +59,17 @@ export function QuickInputs() {
return ( return (
<Flex <Flex
className={styles.container} align="center"
gap="2"
width="10rem"
style={{ style={{
width: 128 + 32,
pointerEvents: 'auto', pointerEvents: 'auto',
position: 'absolute', position: 'absolute',
left: '50%', left: '50%',
top: 16, top: 16,
transform: 'translateX(-50%)', transform: 'translateX(-50%)',
}} }}
align="center" className={styles.container}
gap="2"
> >
<TextField.Root <TextField.Root
size="1" size="1"
+3 -4
View File
@@ -14,7 +14,6 @@ import { GameModeSection } from './components/Model/GameModeSection';
import { HeroSection } from './components/Model/HeroSection'; import { HeroSection } from './components/Model/HeroSection';
import { MetaSection } from './components/Model/MetaSection'; import { MetaSection } from './components/Model/MetaSection';
import { TechTreeSection } from './components/Model/TechTreeSection'; import { TechTreeSection } from './components/Model/TechTreeSection';
import { ShotDisplaySection } from './components/ShotDisplaySection';
import { VideoSection } from './components/VideoSection'; import { VideoSection } from './components/VideoSection';
export default function Page({ params }: { params: { id: string } }) { export default function Page({ params }: { params: { id: string } }) {
@@ -82,11 +81,11 @@ export default function Page({ params }: { params: { id: string } }) {
return ( return (
<PageWrapper p="0" noMaxWidth color="purple" size={1600} gap="9"> <PageWrapper p="0" noMaxWidth color="purple" size={1600} gap="9">
<HeroSection id={id} /> <HeroSection id={id} />
<ShotDisplaySection />
<CalloutsSection /> <CalloutsSection />
<MetaSection /> {/* <ShotDisplaySection /> */}
{!exempt && <AdMidSectionResponsive />}
<TechTreeSection /> <TechTreeSection />
{!exempt && <AdMidSectionResponsive />}
<MetaSection />
<CharacteristicsSection /> <CharacteristicsSection />
{!exempt && <AdMidSectionResponsive />} {!exempt && <AdMidSectionResponsive />}
<GameModeSection /> <GameModeSection />
+4 -4
View File
@@ -1,7 +1,7 @@
import { style, styleVariants } from '@vanilla-extract/css'; import { style, styleVariants } from '@vanilla-extract/css';
const MIN_WIDTH = 640 + 128; const MIN_WIDTH = 640 + 128;
const HEIGHT = 48; export const NAVBAR_HEIGHT = 48;
export const hamburger = style({ export const hamburger = style({
display: 'inline-flex', display: 'inline-flex',
@@ -24,7 +24,7 @@ export const tools = style({
}); });
export const navbar = style({ export const navbar = style({
height: HEIGHT, height: NAVBAR_HEIGHT,
marginBottom: -1, // 1px overlap with content to bleed color marginBottom: -1, // 1px overlap with content to bleed color
position: 'sticky', position: 'sticky',
top: 0, top: 0,
@@ -42,7 +42,7 @@ const navbarExpanderBase = style({
'@media': { '@media': {
[`screen and (min-width: ${MIN_WIDTH}px)`]: { [`screen and (min-width: ${MIN_WIDTH}px)`]: {
maxHeight: HEIGHT, maxHeight: NAVBAR_HEIGHT,
}, },
}, },
}); });
@@ -51,7 +51,7 @@ export const navbarExpander = styleVariants({
false: [ false: [
navbarExpanderBase, navbarExpanderBase,
{ {
maxHeight: HEIGHT, maxHeight: NAVBAR_HEIGHT,
}, },
], ],
true: [ true: [