mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
feat: outline title
This commit is contained in:
1 parent
ad8cbb96b7
commit
c764564a96
14 files changed
+205
-52
No files matched your search
@@ -21,6 +21,19 @@ const strings = getStrings(locale);
|
||||
---
|
||||
|
||||
<style>
|
||||
.navbar,
|
||||
.padder {
|
||||
transition-duration: 1s;
|
||||
}
|
||||
|
||||
body.no-navbar .navbar {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
body.no-navbar .padder {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.navbar[data-expanded='false'] *[data-expanded] {
|
||||
display: none;
|
||||
}
|
||||
@@ -49,7 +62,7 @@ const strings = getStrings(locale);
|
||||
});
|
||||
</script>
|
||||
|
||||
<Box height={{ initial: '3.5rem', sm: '3rem' }} />
|
||||
<Box className="padder" height={{ initial: '3.5rem', sm: '3rem' }} />
|
||||
|
||||
<Flex
|
||||
direction="column"
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
.quick-inputs {
|
||||
top: -2rem;
|
||||
left: -5rem;
|
||||
}
|
||||
|
||||
.quick-inputs:focus-within,
|
||||
.tank-sandbox-container:hover .quick-inputs {
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
}
|
||||
+4
-3
@@ -66,12 +66,13 @@ export function QuickInputs() {
|
||||
<Flex
|
||||
align="center"
|
||||
gap="2"
|
||||
width="10rem"
|
||||
width="5rem"
|
||||
direction="column"
|
||||
style={{
|
||||
pointerEvents: 'auto',
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
transitionDuration: '0.2s',
|
||||
}}
|
||||
className="quick-inputs"
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
.scroll-hint {
|
||||
position: absolute;
|
||||
animation: dropFade 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes dropFade {
|
||||
0% {
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
20% {
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
top: 50%;
|
||||
opacity: 1;
|
||||
}
|
||||
99% {
|
||||
top: 50%;
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { Box } from '@radix-ui/themes';
|
||||
import { Var } from '../../../../../core/radix/var';
|
||||
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
|
||||
import './index.css';
|
||||
|
||||
export function ScrollHint() {
|
||||
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
|
||||
|
||||
if (disturbed) return null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
position="absolute"
|
||||
bottom="0"
|
||||
mb="5"
|
||||
right="50%"
|
||||
height="2rem"
|
||||
width="1rem"
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
outline: `2px solid ${Var('gray-11')}`,
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="scroll-hint"
|
||||
width="0.75rem"
|
||||
height="0.75rem"
|
||||
m="0.125rem"
|
||||
style={{
|
||||
borderRadius: '50%',
|
||||
backgroundColor: Var('gray-12'),
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+63
-32
@@ -1,55 +1,86 @@
|
||||
import { TankType } from '@blitzkit/core';
|
||||
import { Heading } from '@radix-ui/themes';
|
||||
import { clamp } from 'three/src/math/MathUtils.js';
|
||||
import { Var, type VarName } from '../../../../../core/radix/var';
|
||||
import { Box, Heading } from '@radix-ui/themes';
|
||||
import { Var } from '../../../../../core/radix/var';
|
||||
import { useLocale } from '../../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../../stores/duel';
|
||||
import type { RadixColor } from '../../../../../stores/embedState';
|
||||
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
|
||||
|
||||
interface TitleProps {
|
||||
outline?: boolean;
|
||||
}
|
||||
|
||||
export const NATION_COLORS: Record<
|
||||
string,
|
||||
{
|
||||
text: RadixColor;
|
||||
background: RadixColor[];
|
||||
}
|
||||
> = {
|
||||
france: { text: 'mint', background: ['mint', 'cyan', 'jade'] },
|
||||
germany: { text: 'sage', background: ['slate', 'sage', 'mauve'] },
|
||||
usa: { text: 'gold', background: ['brown', 'gold', 'gray'] },
|
||||
european: { text: 'gold', background: ['bronze', 'gold', 'brown'] },
|
||||
ussr: { text: 'bronze', background: ['bronze', 'tomato', 'bronze'] },
|
||||
uk: { text: 'gold', background: ['bronze', 'gold', 'brown'] },
|
||||
japan: { text: 'jade', background: ['green', 'teal', 'jade'] },
|
||||
china: { text: 'teal', background: ['green', 'teal', 'jade'] },
|
||||
other: { text: 'amber', background: ['amber', 'yellow', 'gray'] },
|
||||
};
|
||||
|
||||
export function Title({ outline }: TitleProps) {
|
||||
const { unwrap } = useLocale();
|
||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
|
||||
const revealed = TankopediaEphemeral.use((state) => state.revealed);
|
||||
const treeColor =
|
||||
protagonist.type === TankType.COLLECTOR
|
||||
? 'blue'
|
||||
: protagonist.type === TankType.PREMIUM
|
||||
? 'amber'
|
||||
: undefined;
|
||||
const nationColors = NATION_COLORS[protagonist.nation];
|
||||
const color = Var(
|
||||
disturbed
|
||||
? protagonist.type === TankType.COLLECTOR
|
||||
? 'blue-11'
|
||||
: protagonist.type === TankType.PREMIUM
|
||||
? 'amber-11'
|
||||
: 'gray-12'
|
||||
: `${nationColors.text}-12`,
|
||||
);
|
||||
const name = unwrap(protagonist.name);
|
||||
|
||||
return (
|
||||
<Heading
|
||||
weight="bold"
|
||||
<Box
|
||||
position="absolute"
|
||||
top={disturbed ? '8' : '50%'}
|
||||
left="50%"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
marginLeft: disturbed ? '2rem' : 0,
|
||||
left: disturbed ? 0 : '50%',
|
||||
transform: disturbed ? 'translate(0, -50%)' : 'translate(-50%, -50%)',
|
||||
userSelect: 'none',
|
||||
pointerEvents: 'none',
|
||||
transitionDuration: '1s',
|
||||
fontSize: disturbed
|
||||
? '3rem'
|
||||
: `${16 * clamp(12 / name.length, 0, 1)}rem`,
|
||||
color: outline ? Var('gray-a7') : undefined,
|
||||
WebkitTextStroke: outline
|
||||
? `${disturbed ? '1px' : '2px'} ${Var(
|
||||
`${treeColor ? (`${treeColor}-11` satisfies VarName) : 'gray-12'}`,
|
||||
)}`
|
||||
: undefined,
|
||||
letterSpacing: disturbed ? 0 : '-0.06em',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
color={treeColor}
|
||||
wrap="nowrap"
|
||||
>
|
||||
{name}
|
||||
</Heading>
|
||||
<Heading
|
||||
weight="bold"
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
pointerEvents: 'none',
|
||||
fontSize: revealed
|
||||
? disturbed
|
||||
? '2rem'
|
||||
: `${125 / name.length}vw`
|
||||
: `${75 / name.length}vw`,
|
||||
whiteSpace: 'nowrap',
|
||||
color: outline ? 'transparent' : color,
|
||||
WebkitTextStroke: outline
|
||||
? `${revealed ? (disturbed ? '1px' : 'min(2px, 0.2vw)') : 0} ${color}`
|
||||
: undefined,
|
||||
letterSpacing: disturbed || !revealed ? 0 : '-0.03em',
|
||||
transition: `
|
||||
letter-spacing 1s ease ${disturbed ? '0s' : '1s'},
|
||||
font-size 1s,
|
||||
-webkit-text-stroke 1.5s
|
||||
`,
|
||||
}}
|
||||
wrap="nowrap"
|
||||
>
|
||||
{name}
|
||||
</Heading>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+1
@@ -385,6 +385,7 @@ export function Controls({
|
||||
enableRotate={enableRotate}
|
||||
maxDistance={20}
|
||||
minDistance={5}
|
||||
enableZoom={zoomable}
|
||||
zoomSpeed={zoomable ? undefined : 0}
|
||||
ref={orbitControls}
|
||||
enabled={tankopediaEphemeralStore.getState().controlsEnabled}
|
||||
|
||||
+3
@@ -35,6 +35,7 @@ const [modelDefinitions, equipmentDefinitions, provisionDefinitions] =
|
||||
const emptyVector = new Vector2();
|
||||
|
||||
export function SceneProps() {
|
||||
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
|
||||
const targetCircleWrapper = useRef<Group>(null);
|
||||
const clientTargetCircle = useRef<HTMLDivElement>(null);
|
||||
const serverTargetCircle = useRef<HTMLDivElement>(null);
|
||||
@@ -233,6 +234,8 @@ export function SceneProps() {
|
||||
serverTargetCircle.current.style.height = targetCircleDotsSize;
|
||||
});
|
||||
|
||||
if (!disturbed) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{display !== TankopediaDisplay.ShootingRange && (
|
||||
|
||||
+2
-1
@@ -30,6 +30,7 @@ import { AutoClear } from './components/AutoClear';
|
||||
import { Controls } from './components/Control';
|
||||
import { InitialFogReveal } from './components/InitialFogReveal';
|
||||
import { Lighting } from './components/Lighting';
|
||||
import { SceneProps } from './components/SceneProps';
|
||||
import { TankModel } from './components/TankModel';
|
||||
import { TransitionSkeleton } from './components/TransitionSkeleton';
|
||||
|
||||
@@ -233,7 +234,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
||||
outline: naked ? '1rem red solid' : undefined,
|
||||
}}
|
||||
>
|
||||
{/* {!naked && <SceneProps />} */}
|
||||
{!naked && <SceneProps />}
|
||||
{(display === TankopediaDisplay.Model ||
|
||||
(display === TankopediaDisplay.DynamicArmor &&
|
||||
!hideTankModelUnderArmor)) && <TankModel />}
|
||||
|
||||
@@ -2,10 +2,10 @@ import { TankType } from '@blitzkit/core';
|
||||
import { Box, Flex } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { Suspense, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { NAVBAR_HEIGHT } from '../../../constants/navbar';
|
||||
import { awaitableModelDefinitions } from '../../../core/awaitables/modelDefinitions';
|
||||
import { awaitableProvisionDefinitions } from '../../../core/awaitables/provisionDefinitions';
|
||||
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
|
||||
import { Var, type VarName } from '../../../core/radix/var';
|
||||
import { useFullScreen } from '../../../hooks/useFullScreen';
|
||||
import { useLocale } from '../../../hooks/useLocale';
|
||||
import { Duel } from '../../../stores/duel';
|
||||
@@ -14,8 +14,9 @@ import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonCo
|
||||
import type { ThicknessRange } from '../../Armor/components/StaticArmor';
|
||||
import { classIcons } from '../../ClassIcon';
|
||||
import { Options } from './components/Options';
|
||||
import { ScrollHint } from './components/ScrollHint';
|
||||
import { TankSandbox } from './components/TankSandbox';
|
||||
import { Title } from './components/TankSandbox/Title';
|
||||
import { NATION_COLORS, Title } from './components/TankSandbox/Title';
|
||||
|
||||
const [provisionDefinitions, modelDefinitions, tankDefinitions] =
|
||||
await Promise.all([
|
||||
@@ -65,6 +66,13 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const duelStore = Duel.useStore();
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||
const nationColors = NATION_COLORS[protagonist.nation];
|
||||
|
||||
useEffect(() => {
|
||||
if (disturbed) {
|
||||
document.body.classList.remove('no-navbar');
|
||||
}
|
||||
}, [disturbed]);
|
||||
|
||||
useEffect(() => {
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
@@ -88,16 +96,46 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Flex justify="center">
|
||||
<Flex
|
||||
justify="center"
|
||||
style={{
|
||||
backgroundColor: 'black',
|
||||
}}
|
||||
position="relative"
|
||||
onWheel={() => {
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.disturbed = true;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
width="100%"
|
||||
height="100%"
|
||||
top="0"
|
||||
left="0"
|
||||
style={{
|
||||
opacity: disturbed ? 0 : 1,
|
||||
background: `linear-gradient(${nationColors.background
|
||||
.map((color, index) =>
|
||||
Var(
|
||||
`${color}-${Math.round(3 * (1 - index / nationColors.background.length))}` as VarName,
|
||||
),
|
||||
)
|
||||
.join(',')})`,
|
||||
transitionDuration: '1s',
|
||||
}}
|
||||
/>
|
||||
|
||||
<ScrollHint />
|
||||
|
||||
<Flex
|
||||
direction={{ initial: 'column-reverse', md: 'row' }}
|
||||
style={{
|
||||
zIndex: isFullScreen ? 2 : undefined,
|
||||
backgroundColor: 'black',
|
||||
transitionDuration: '1s',
|
||||
}}
|
||||
height={
|
||||
isFullScreen ? '100vh' : `calc(100vh - ${NAVBAR_HEIGHT}px - 8rem)`
|
||||
}
|
||||
height="calc(100vh - 6rem)"
|
||||
maxHeight={isFullScreen ? undefined : '60rem'}
|
||||
maxWidth={isFullScreen ? undefined : '120rem'}
|
||||
flexGrow="1"
|
||||
|
||||
@@ -25,7 +25,7 @@ export function MetaSection() {
|
||||
const { locale, strings } = useLocale();
|
||||
|
||||
return (
|
||||
<Flex justify="center">
|
||||
<Flex justify="center" mt="-9">
|
||||
<Box
|
||||
style={{
|
||||
background: `url(/assets/images/national-wallpapers/${protagonist.nation}.jpg)`,
|
||||
@@ -50,7 +50,7 @@ export function MetaSection() {
|
||||
align="center"
|
||||
py="6rem"
|
||||
style={{
|
||||
background: `linear-gradient(${Var('gray-1')}, ${Var('black-a8')}, ${Var('black-a8')}, ${Var('gray-1')})`,
|
||||
background: `linear-gradient(${Var('black-a8')}, ${Var('black-a8')}, ${Var('gray-1')})`,
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" align="center" gap="6">
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as ColorsRaw from '@radix-ui/colors';
|
||||
|
||||
type ExcludeSuffix<T> =
|
||||
T extends `${string}${'A' | `${'P3' | 'Dark'}${'' | 'A'}`}` ? never : T;
|
||||
type RadixColor = ExcludeSuffix<keyof typeof ColorsRaw>;
|
||||
export type RadixColor = ExcludeSuffix<keyof typeof ColorsRaw>;
|
||||
|
||||
export type VarName =
|
||||
| `color-${'background' | `panel-${'solid' | 'translucent'}` | 'surface' | 'overlay'}`
|
||||
|
||||
@@ -10,9 +10,10 @@ import { DEFAULT_LOCALE } from '@blitzkit/i18n';
|
||||
|
||||
export interface Props {
|
||||
noFooter?: boolean;
|
||||
noNavbar?: boolean;
|
||||
}
|
||||
|
||||
const { noFooter } = Astro.props;
|
||||
const { noFooter, noNavbar } = Astro.props;
|
||||
const locale = Astro.params.locale ?? DEFAULT_LOCALE;
|
||||
---
|
||||
|
||||
@@ -53,7 +54,7 @@ const locale = Astro.params.locale ?? DEFAULT_LOCALE;
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body class={noNavbar ? 'no-navbar' : ''}>
|
||||
<BlitzKitTheme>
|
||||
<Flex direction="column" minHeight="100vh">
|
||||
<Navbar />
|
||||
|
||||
@@ -46,8 +46,8 @@ export function Page({ id, skeleton, locale }: PageProps) {
|
||||
>
|
||||
<PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9">
|
||||
<HeroSection skeleton={skeleton} />
|
||||
<CalloutsSection />
|
||||
<MetaSection />
|
||||
<CalloutsSection />
|
||||
{tank.type === TankType.RESEARCHABLE && !tank.deprecated && (
|
||||
<TechTreeSection skeleton={skeleton} />
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user