@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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 />
|
||||
}
|
||||
Reference in New Issue
Block a user