feat: outline title

This commit is contained in:
tresabhi committed 2025-05-06 13:29:40 -05:00
1 parent ad8cbb96b7
commit c764564a96
14 files changed
+205 -52

No files matched your search

+14 -1
View File
@@ -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"
@@ -1,8 +1,8 @@
.quick-inputs {
top: -2rem;
left: -5rem;
}
.quick-inputs:focus-within,
.tank-sandbox-container:hover .quick-inputs {
top: 1rem;
left: 1rem;
}
@@ -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"
@@ -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;
}
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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}
@@ -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 && (
@@ -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">
+1 -1
View File
@@ -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} />
)}