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