Add /testdesign/h2
Lint / lint (push) Successful in 30s

This commit is contained in:
Julien Valverdé
2026-07-27 09:49:46 +02:00
parent 1dd3904cad
commit 35a36e668d
6 changed files with 509 additions and 4 deletions
@@ -0,0 +1,93 @@
import { useEffect, useState } from "react"
const menuItems = [
"Continue",
"Revert to Last Checkpoint",
"Restart Level",
"Controller Settings",
"Save and Quit",
] as const
const objectives = [
{ label: "Defend the Marines until help arrives.", complete: true },
{ label: "Rendezvous with the Pelican.", complete: true },
{ label: "Find the Marines from second downed Pelican.", complete: true },
{ label: "Destroy the Covenant on the shoreline.", complete: false },
] as const
export function HaloPauseMenu() {
const [selected, setSelected] = useState(0)
useEffect(() => {
const selectMenuItem = (event: KeyboardEvent) => {
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
event.preventDefault()
setSelected(current => {
if (event.key === "ArrowUp") {
return (current - 1 + menuItems.length) % menuItems.length
}
return (current + 1) % menuItems.length
})
}
}
window.addEventListener("keydown", selectMenuItem)
return () => window.removeEventListener("keydown", selectMenuItem)
}, [])
return (
<main className="h2-screen">
<div className="h2-screen__nebula" aria-hidden="true" />
<div className="h2-screen__scanlines" aria-hidden="true" />
<section className="h2-pause" aria-label="Game paused">
<div className="h2-pause__frame" aria-hidden="true" />
<div className="h2-pause__menu">
<h1>Game Paused</h1>
<nav aria-label="Pause menu">
{menuItems.map((item, index) => (
<button
type="button"
key={item}
className={selected === index ? "is-selected" : undefined}
onClick={() => setSelected(index)}
onMouseEnter={() => setSelected(index)}
aria-current={selected === index ? "true" : undefined}
>
<span aria-hidden="true" />
{item}
</button>
))}
</nav>
</div>
<div className="h2-pause__objectives">
<h2>Mission Objectives:</h2>
<ul>
{objectives.map(objective => (
<li key={objective.label}>
<span
className={objective.complete ? "is-complete" : undefined}
role="img"
aria-label={objective.complete ? "Complete" : "Incomplete"}
>
{objective.complete && "✓"}
</span>
<p>{objective.label}</p>
</li>
))}
</ul>
</div>
<section className="h2-pause__controls" aria-label="Controller hints">
<span className="h2-control h2-control--a" aria-hidden="true">A</span>
<span>Select</span>
<span className="h2-control h2-control--b" aria-hidden="true">B</span>
<span>Resume</span>
</section>
</section>
</main>
)
}
@@ -0,0 +1,350 @@
.h2-screen {
--h2-ice: #afcbea;
--h2-blue: #4b82c1;
--h2-navy: #00132f;
position: relative;
display: grid;
min-width: 320px;
min-height: max(100vh, 100svh);
overflow: hidden;
place-items: center;
isolation: isolate;
color: var(--h2-ice);
background:
radial-gradient(ellipse at 51% 46%, rgb(11 42 80 / 64%) 0, rgb(2 24 55 / 44%) 27%, transparent 58%),
radial-gradient(ellipse at 16% 76%, rgb(10 51 91 / 44%), transparent 35%),
linear-gradient(112deg, #001630 0%, #001026 43%, #000a1d 100%);
font-family: "Arial Narrow", "Roboto Condensed", "Trebuchet MS", sans-serif;
font-synthesis: none;
}
.h2-screen::before {
position: absolute;
z-index: 5;
border: 1px solid rgb(123 177 222 / 9%);
content: "";
inset: 0;
pointer-events: none;
}
.h2-screen::after {
position: absolute;
z-index: 4;
background: radial-gradient(ellipse at center, transparent 35%, rgb(0 4 16 / 31%) 100%);
content: "";
inset: 0;
pointer-events: none;
}
.h2-screen__nebula {
position: absolute;
width: min(1100px, 130vw);
height: min(680px, 90vw);
border-radius: 50%;
background:
radial-gradient(ellipse at 18% 52%, rgb(22 68 113 / 12%), transparent 24%),
radial-gradient(ellipse at 71% 38%, rgb(10 52 102 / 15%), transparent 27%);
filter: blur(18px);
transform: rotate(-7deg);
}
.h2-screen__scanlines {
position: absolute;
z-index: 6;
background: repeating-linear-gradient(
to bottom,
transparent 0,
transparent 2px,
rgb(0 0 0 / 10%) 3px
);
inset: 0;
mix-blend-mode: multiply;
opacity: 0.42;
pointer-events: none;
}
.h2-pause {
position: relative;
display: grid;
width: min(84vw, 930px);
min-height: clamp(218px, 31.5vw, 348px);
grid-template-columns: 48% 52%;
grid-template-rows: 1fr auto;
padding:
clamp(18px, 3vw, 38px)
clamp(16px, 2.5vw, 32px)
clamp(14px, 2vw, 25px)
clamp(16px, 2.45vw, 31px);
filter: drop-shadow(0 0 9px rgb(38 102 171 / 32%));
letter-spacing: 0.012em;
text-shadow:
0 0 2px #8fb9e1,
0 0 7px rgb(112 165 218 / 35%);
transform: perspective(1100px) rotateX(0.7deg);
}
.h2-pause::before {
position: absolute;
z-index: -2;
background:
linear-gradient(90deg, rgb(7 35 70 / 75%), rgb(3 28 61 / 89%) 45%, rgb(3 26 57 / 70%)),
linear-gradient(180deg, rgb(27 75 123 / 28%), transparent);
clip-path: polygon(2.5% 0, 100% 0, 100% 91%, 97% 100%, 0 100%, 0 8%);
content: "";
inset: 0;
}
.h2-pause::after {
position: absolute;
z-index: -1;
background:
linear-gradient(90deg, rgb(91 152 215 / 34%) 0 2px, transparent 2px) left 25px / 2px calc(100% - 50px) no-repeat,
linear-gradient(90deg, transparent, rgb(74 132 194 / 15%), transparent);
clip-path: polygon(2.5% 0, 100% 0, 100% 91%, 97% 100%, 0 100%, 0 8%);
content: "";
inset: 1px;
}
.h2-pause__frame {
position: absolute;
z-index: 2;
inset: 0;
pointer-events: none;
}
.h2-pause__frame::before,
.h2-pause__frame::after {
position: absolute;
width: 31px;
height: 31px;
content: "";
}
.h2-pause__frame::before {
top: -1px;
left: -1px;
border-top: 3px solid rgb(116 172 225 / 76%);
border-left: 3px solid rgb(116 172 225 / 76%);
clip-path: polygon(0 0, 100% 0, 100% 10%, 11% 10%, 11% 100%, 0 100%);
filter: drop-shadow(0 0 4px #4f99df);
}
.h2-pause__frame::after {
right: -1px;
bottom: -1px;
border-right: 3px solid rgb(116 172 225 / 76%);
border-bottom: 3px solid rgb(116 172 225 / 76%);
clip-path: polygon(89% 0, 100% 0, 100% 100%, 0 100%, 0 90%, 89% 90%);
filter: drop-shadow(0 0 4px #4f99df);
}
.h2-pause h1,
.h2-pause h2 {
margin: 0;
color: #c4d5e9;
font-family: inherit;
font-size: clamp(16px, 1.65vw, 24px);
font-weight: 700;
letter-spacing: 0.025em;
line-height: 1;
text-transform: uppercase;
}
.h2-pause__menu {
min-width: 0;
padding-right: 21px;
}
.h2-pause__menu nav {
display: flex;
flex-direction: column;
gap: 2px;
margin-top: clamp(12px, 2vw, 26px);
}
.h2-pause__menu button {
position: relative;
width: 100%;
min-height: clamp(23px, 2.75vw, 35px);
padding: 3px 13px 3px 35px;
overflow: hidden;
border: 0;
color: rgb(128 157 188 / 41%);
background: linear-gradient(90deg, rgb(19 55 93 / 66%), rgb(18 54 91 / 14%));
font: inherit;
font-size: clamp(14px, 1.65vw, 23px);
font-stretch: condensed;
letter-spacing: 0.025em;
line-height: 1;
text-align: left;
text-shadow: 0 0 5px rgb(110 160 211 / 26%);
transition: color 100ms ease, background 100ms ease;
}
.h2-pause__menu button > span {
position: absolute;
top: 3px;
bottom: 3px;
left: 4px;
width: 17px;
border-left: 4px solid rgb(119 166 210 / 22%);
border-radius: 12px 0 0 12px;
}
.h2-pause__menu button > span::before,
.h2-pause__menu button > span::after {
position: absolute;
left: 0;
width: 11px;
height: 4px;
background: rgb(119 166 210 / 22%);
content: "";
}
.h2-pause__menu button > span::before {
top: 0;
}
.h2-pause__menu button > span::after {
bottom: 0;
}
.h2-pause__menu button.is-selected {
color: #b7cde4;
background:
linear-gradient(90deg, rgb(104 147 190 / 61%), rgb(44 88 133 / 50%) 76%, transparent);
text-shadow: 0 0 4px #7eadd9;
}
.h2-pause__menu button.is-selected > span {
border-color: #c0d6ea;
filter: drop-shadow(0 0 3px #8dc5f2);
}
.h2-pause__menu button.is-selected > span::before,
.h2-pause__menu button.is-selected > span::after {
background: #c0d6ea;
}
.h2-pause__objectives {
min-width: 0;
padding-left: 4px;
}
.h2-pause__objectives ul {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0;
margin: 18px 0 0;
list-style: none;
}
.h2-pause__objectives li {
display: grid;
grid-template-columns: 23px 1fr;
gap: 12px;
align-items: start;
color: #b6cae1;
font-size: clamp(14px, 1.55vw, 22px);
font-weight: 600;
line-height: 1.18;
}
.h2-pause__objectives li > span {
display: grid;
width: 19px;
height: 19px;
border: 3px solid #4275ae;
margin-top: 1px;
color: #c9a900;
font-size: 21px;
font-weight: 900;
line-height: 12px;
place-items: center;
text-shadow: 0 0 4px #e2bd18;
transform: skew(-5deg);
}
.h2-pause__objectives li > span.is-complete {
border-color: #806d19;
}
.h2-pause__objectives p {
max-width: 390px;
margin: 0;
}
.h2-pause__controls {
display: flex;
grid-column: 2;
align-items: center;
justify-content: flex-end;
padding-top: 15px;
color: #b7cade;
font-size: clamp(12px, 1.35vw, 19px);
font-weight: 700;
text-transform: uppercase;
}
.h2-control {
display: inline-grid;
width: 23px;
height: 23px;
margin-right: 5px;
border-radius: 50%;
color: #e9f1ef;
font-size: 14px;
line-height: 1;
place-items: center;
text-shadow: 0 1px 1px rgb(0 0 0 / 70%);
box-shadow:
inset 1px 1px 2px rgb(255 255 255 / 55%),
0 0 5px currentColor;
}
.h2-control--a {
background: #54a81e;
color: #d9f2c4;
}
.h2-control--b {
margin-left: 14px;
background: #a83045;
color: #f4c3ca;
}
@media (max-width: 500px) {
.h2-pause {
width: min(92vw, 500px);
min-height: 0;
grid-template-columns: 1fr;
padding: 30px 25px 22px;
}
.h2-pause__menu {
padding-right: 0;
}
.h2-pause__menu nav {
margin-top: 17px;
}
.h2-pause__objectives {
padding: 27px 0 0;
}
.h2-pause__objectives ul {
margin-top: 14px;
}
.h2-pause__controls {
grid-column: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.h2-pause__menu button {
transition: none;
}
}
+49 -3
View File
@@ -9,9 +9,16 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
import { Route as TestdesignRouteImport } from './routes/testdesign'
import { Route as MainRouteImport } from './routes/_main'
import { Route as MainIndexRouteImport } from './routes/_main.index'
import { Route as TestdesignH2RouteImport } from './routes/testdesign.h2'
const TestdesignRoute = TestdesignRouteImport.update({
id: '/testdesign',
path: '/testdesign',
getParentRoute: () => rootRouteImport,
} as any)
const MainRoute = MainRouteImport.update({
id: '/_main',
getParentRoute: () => rootRouteImport,
@@ -21,32 +28,51 @@ const MainIndexRoute = MainIndexRouteImport.update({
path: '/',
getParentRoute: () => MainRoute,
} as any)
const TestdesignH2Route = TestdesignH2RouteImport.update({
id: '/h2',
path: '/h2',
getParentRoute: () => TestdesignRoute,
} as any)
export interface FileRoutesByFullPath {
'/': typeof MainIndexRoute
'/testdesign': typeof TestdesignRouteWithChildren
'/testdesign/h2': typeof TestdesignH2Route
}
export interface FileRoutesByTo {
'/testdesign': typeof TestdesignRouteWithChildren
'/testdesign/h2': typeof TestdesignH2Route
'/': typeof MainIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/_main': typeof MainRouteWithChildren
'/testdesign': typeof TestdesignRouteWithChildren
'/testdesign/h2': typeof TestdesignH2Route
'/_main/': typeof MainIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/'
fullPaths: '/' | '/testdesign' | '/testdesign/h2'
fileRoutesByTo: FileRoutesByTo
to: '/'
id: '__root__' | '/_main' | '/_main/'
to: '/testdesign' | '/testdesign/h2' | '/'
id: '__root__' | '/_main' | '/testdesign' | '/testdesign/h2' | '/_main/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
MainRoute: typeof MainRouteWithChildren
TestdesignRoute: typeof TestdesignRouteWithChildren
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/testdesign': {
id: '/testdesign'
path: '/testdesign'
fullPath: '/testdesign'
preLoaderRoute: typeof TestdesignRouteImport
parentRoute: typeof rootRouteImport
}
'/_main': {
id: '/_main'
path: ''
@@ -61,6 +87,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MainIndexRouteImport
parentRoute: typeof MainRoute
}
'/testdesign/h2': {
id: '/testdesign/h2'
path: '/h2'
fullPath: '/testdesign/h2'
preLoaderRoute: typeof TestdesignH2RouteImport
parentRoute: typeof TestdesignRoute
}
}
}
@@ -74,8 +107,21 @@ const MainRouteChildren: MainRouteChildren = {
const MainRouteWithChildren = MainRoute._addFileChildren(MainRouteChildren)
interface TestdesignRouteChildren {
TestdesignH2Route: typeof TestdesignH2Route
}
const TestdesignRouteChildren: TestdesignRouteChildren = {
TestdesignH2Route: TestdesignH2Route,
}
const TestdesignRouteWithChildren = TestdesignRoute._addFileChildren(
TestdesignRouteChildren,
)
const rootRouteChildren: RootRouteChildren = {
MainRoute: MainRouteWithChildren,
TestdesignRoute: TestdesignRouteWithChildren,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
+1 -1
View File
@@ -9,6 +9,6 @@ export const Route = createRootRoute({
function RootComponent() {
return <>
<Outlet />
{import.meta.env.DEV && <TanStackRouterDevtools />}
<TanStackRouterDevtools />
</>
}
@@ -0,0 +1,7 @@
import { createFileRoute } from "@tanstack/react-router"
import { HaloPauseMenu } from "@/components/testdesign/h2/HaloPauseMenu"
import "@/components/testdesign/h2/halo-pause-menu.css"
export const Route = createFileRoute("/testdesign/h2")({
component: HaloPauseMenu,
})
@@ -0,0 +1,9 @@
import { createFileRoute, Outlet } from "@tanstack/react-router"
export const Route = createFileRoute("/testdesign")({
component: TestDesignLayout,
})
function TestDesignLayout() {
return <Outlet />
}