improve toolbar on mobile, add joystick modes

This commit is contained in:
Brian Beck 2026-02-12 12:04:26 -08:00
parent 68c7b303aa
commit d9293b1a0b
30 changed files with 480 additions and 152 deletions

View file

@ -123,6 +123,8 @@ function MapInspector() {
const cameraRef = useRef<Camera | null>(null);
const joystickStateRef = useRef<JoystickState>({ angle: 0, force: 0 });
const joystickZoneRef = useRef<HTMLDivElement | null>(null);
const lookJoystickStateRef = useRef<JoystickState>({ angle: 0, force: 0 });
const lookJoystickZoneRef = useRef<HTMLDivElement | null>(null);
return (
<QueryClientProvider client={queryClient}>
@ -147,6 +149,8 @@ function MapInspector() {
<TouchJoystick
joystickState={joystickStateRef}
joystickZone={joystickZoneRef}
lookJoystickState={lookJoystickStateRef}
lookJoystickZone={lookJoystickZoneRef}
/>
)}
<Canvas
@ -171,6 +175,8 @@ function MapInspector() {
<TouchCameraMovement
joystickState={joystickStateRef}
joystickZone={joystickZoneRef}
lookJoystickState={lookJoystickStateRef}
lookJoystickZone={lookJoystickZoneRef}
/>
) : (
<ObserverControls />
@ -184,6 +190,7 @@ function MapInspector() {
missionType={missionType}
onChangeMission={changeMission}
cameraRef={cameraRef}
isTouch={isTouch}
/>
</SettingsProvider>
</main>

View file

@ -49,9 +49,6 @@ main {
}
#controls {
display: flex;
align-items: center;
gap: 20px;
position: fixed;
top: 0;
left: 0;
@ -67,7 +64,8 @@ input[type="range"] {
max-width: 80px;
}
.CheckboxField {
.CheckboxField,
.LabelledButton {
display: flex;
align-items: center;
gap: 6px;
@ -79,6 +77,64 @@ input[type="range"] {
gap: 6px;
}
#controls,
.Controls-dropdown,
.Controls-group {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
}
@media (max-width: 1279px) {
.Controls-dropdown[data-open="false"] {
display: none;
}
.Controls-dropdown {
position: absolute;
top: calc(100% + 2px);
left: 2px;
right: 2px;
display: flex;
overflow: auto;
max-height: calc(100dvh - 56px);
flex-direction: column;
align-items: center;
gap: 12px;
background: rgba(0, 0, 0, 0.8);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
padding: 12px;
box-shadow: 0 0 12px rgba(0, 0, 0, 0.4);
}
.Controls-group {
flex-wrap: wrap;
gap: 12px 20px;
}
}
@media (max-width: 639px) {
#controls {
right: 0;
border-radius: 0;
}
#controls > .MissionSelect-inputWrapper {
flex: 1 1 0;
min-width: 0;
}
#controls > .MissionSelect-inputWrapper .MissionSelect-input {
width: 100%;
}
.Controls-toggle {
flex: 0 0 auto;
}
}
.IconButton {
position: relative;
display: flex;
@ -104,17 +160,49 @@ input[type="range"] {
border-color 0.2s;
}
.IconButton:hover {
background: rgba(0, 98, 179, 0.8);
border-color: rgba(255, 255, 255, 0.4);
.ButtonLabel {
font-size: 12px;
}
.IconButton:active {
.IconButton svg {
pointer-events: none;
}
@media (hover: hover) {
.IconButton:hover {
background: rgba(0, 98, 179, 0.8);
border-color: rgba(255, 255, 255, 0.4);
}
}
.IconButton:active,
.IconButton[aria-expanded="true"] {
background: rgba(0, 98, 179, 0.7);
border-color: rgba(255, 255, 255, 0.3);
transform: translate(0, 1px);
}
.Controls-toggle {
margin: 0;
}
@media (max-width: 1279px) {
.LabelledButton {
width: auto;
padding: 0 10px;
}
}
@media (min-width: 1280px) {
.Controls-toggle {
display: none;
}
.LabelledButton .ButtonLabel {
display: none;
}
}
.CopyCoordinatesButton[data-copied="true"] {
background: rgba(0, 117, 213, 0.9);
border-color: rgba(255, 255, 255, 0.4);
@ -146,7 +234,9 @@ input[type="range"] {
.StatsPanel {
left: auto !important;
top: auto !important;
right: 0;
bottom: 0;
}
.AxisLabel {
@ -428,6 +518,19 @@ input[type="range"] {
width: 140px;
height: 140px;
z-index: 10;
-webkit-touch-callout: none;
user-select: none;
}
.TouchJoystick--left {
left: 20px;
transform: none;
}
.TouchJoystick--right {
left: auto;
right: 20px;
transform: none;
}
.TouchJoystick .back {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,9 +1,9 @@
1:"$Sreact.fragment"
2:I[47257,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"ClientPageRoot"]
3:I[31713,["/t2-mapper/_next/static/chunks/e6da73430a674f20.js","/t2-mapper/_next/static/chunks/69160059bd4715b0.js","/t2-mapper/_next/static/chunks/93b588fa7f31935c.js","/t2-mapper/_next/static/chunks/f5fbb30641aa8588.js","/t2-mapper/_next/static/chunks/7cc8b088c0b74e6e.js"],"default"]
3:I[31713,["/t2-mapper/_next/static/chunks/e6da73430a674f20.js","/t2-mapper/_next/static/chunks/69160059bd4715b0.js","/t2-mapper/_next/static/chunks/93b588fa7f31935c.js","/t2-mapper/_next/static/chunks/affab05731a53281.js","/t2-mapper/_next/static/chunks/5109a68f602829f3.js"],"default"]
6:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"OutletBoundary"]
7:"$Sreact.suspense"
0:{"buildId":"FMM2sqIA1T3SyE5jWLnR9","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/69160059bd4715b0.js","async":true}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/93b588fa7f31935c.js","async":true}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/f5fbb30641aa8588.js","async":true}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/7cc8b088c0b74e6e.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
0:{"buildId":"44qtzzWDxYYxKQeshxCwM","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/69160059bd4715b0.js","async":true}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/93b588fa7f31935c.js","async":true}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/affab05731a53281.js","async":true}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/5109a68f602829f3.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
4:{}
5:"$0:rsc:props:children:0:props:serverProvidedParams:params"
8:null

View file

@ -3,14 +3,14 @@
3:I[39756,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"default"]
4:I[37457,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"default"]
5:I[47257,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"ClientPageRoot"]
6:I[31713,["/t2-mapper/_next/static/chunks/e6da73430a674f20.js","/t2-mapper/_next/static/chunks/69160059bd4715b0.js","/t2-mapper/_next/static/chunks/93b588fa7f31935c.js","/t2-mapper/_next/static/chunks/f5fbb30641aa8588.js","/t2-mapper/_next/static/chunks/7cc8b088c0b74e6e.js"],"default"]
6:I[31713,["/t2-mapper/_next/static/chunks/e6da73430a674f20.js","/t2-mapper/_next/static/chunks/69160059bd4715b0.js","/t2-mapper/_next/static/chunks/93b588fa7f31935c.js","/t2-mapper/_next/static/chunks/affab05731a53281.js","/t2-mapper/_next/static/chunks/5109a68f602829f3.js"],"default"]
9:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"OutletBoundary"]
a:"$Sreact.suspense"
c:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"ViewportBoundary"]
e:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"MetadataBoundary"]
10:I[68027,[],"default"]
:HL["/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","style"]
0:{"P":null,"b":"FMM2sqIA1T3SyE5jWLnR9","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/e6da73430a674f20.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/69160059bd4715b0.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/93b588fa7f31935c.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/f5fbb30641aa8588.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/7cc8b088c0b74e6e.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],null]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
:HL["/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","style"]
0:{"P":null,"b":"44qtzzWDxYYxKQeshxCwM","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/e6da73430a674f20.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/69160059bd4715b0.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/93b588fa7f31935c.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/affab05731a53281.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/5109a68f602829f3.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],null]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
7:{}
8:"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params"
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]

View file

@ -3,4 +3,4 @@
3:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"MetadataBoundary"]
4:"$Sreact.suspense"
5:I[27201,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"IconMark"]
0:{"buildId":"FMM2sqIA1T3SyE5jWLnR9","rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"MapGenius  Explore maps for Tribes 2"}],["$","meta","1",{"name":"description","content":"Tribes 2 forever."}],["$","link","2",{"rel":"icon","href":"/t2-mapper/icon.png?icon.2911bba1.png","sizes":"108x128","type":"image/png"}],["$","$L5","3",{}]]}]}]}],null]}],"loading":null,"isPartial":false}
0:{"buildId":"44qtzzWDxYYxKQeshxCwM","rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"MapGenius  Explore maps for Tribes 2"}],["$","meta","1",{"name":"description","content":"Tribes 2 forever."}],["$","link","2",{"rel":"icon","href":"/t2-mapper/icon.png?icon.2911bba1.png","sizes":"108x128","type":"image/png"}],["$","$L5","3",{}]]}]}]}],null]}],"loading":null,"isPartial":false}

View file

@ -2,5 +2,5 @@
2:I[12985,["/t2-mapper/_next/static/chunks/e6da73430a674f20.js"],"NuqsAdapter"]
3:I[39756,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"default"]
4:I[37457,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"default"]
:HL["/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","style"]
0:{"buildId":"FMM2sqIA1T3SyE5jWLnR9","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/e6da73430a674f20.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","template":["$","$L4",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]]}],"loading":null,"isPartial":false}
:HL["/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","style"]
0:{"buildId":"44qtzzWDxYYxKQeshxCwM","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/e6da73430a674f20.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","template":["$","$L4",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]]}],"loading":null,"isPartial":false}

View file

@ -1,2 +1,2 @@
:HL["/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","style"]
0:{"buildId":"FMM2sqIA1T3SyE5jWLnR9","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
:HL["/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","style"]
0:{"buildId":"44qtzzWDxYYxKQeshxCwM","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":true},"staleTime":300}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -7,8 +7,8 @@
8:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"ViewportBoundary"]
a:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"MetadataBoundary"]
c:I[68027,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"default"]
:HL["/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","style"]
0:{"P":null,"b":"FMM2sqIA1T3SyE5jWLnR9","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/e6da73430a674f20.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L5",null,{"children":["$","$6",null,{"name":"Next.MetadataOutlet","children":"$@7"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$6",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],null]}],false]],"m":"$undefined","G":["$c","$undefined"],"S":true}
:HL["/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","style"]
0:{"P":null,"b":"44qtzzWDxYYxKQeshxCwM","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/e6da73430a674f20.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L5",null,{"children":["$","$6",null,{"name":"Next.MetadataOutlet","children":"$@7"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$6",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],null]}],false]],"m":"$undefined","G":["$c","$undefined"],"S":true}
9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]
d:I[27201,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"IconMark"]
7:null

View file

@ -3,4 +3,4 @@
3:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"MetadataBoundary"]
4:"$Sreact.suspense"
5:I[27201,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"IconMark"]
0:{"buildId":"FMM2sqIA1T3SyE5jWLnR9","rsc":["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"MapGenius  Explore maps for Tribes 2"}],["$","meta","1",{"name":"description","content":"Tribes 2 forever."}],["$","link","2",{"rel":"icon","href":"/t2-mapper/icon.png?icon.2911bba1.png","sizes":"108x128","type":"image/png"}],["$","$L5","3",{}]]}]}]}],null]}],"loading":null,"isPartial":false}
0:{"buildId":"44qtzzWDxYYxKQeshxCwM","rsc":["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"MapGenius  Explore maps for Tribes 2"}],["$","meta","1",{"name":"description","content":"Tribes 2 forever."}],["$","link","2",{"rel":"icon","href":"/t2-mapper/icon.png?icon.2911bba1.png","sizes":"108x128","type":"image/png"}],["$","$L5","3",{}]]}]}]}],null]}],"loading":null,"isPartial":false}

View file

@ -2,5 +2,5 @@
2:I[12985,["/t2-mapper/_next/static/chunks/e6da73430a674f20.js"],"NuqsAdapter"]
3:I[39756,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"default"]
4:I[37457,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"default"]
:HL["/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","style"]
0:{"buildId":"FMM2sqIA1T3SyE5jWLnR9","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/e6da73430a674f20.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","template":["$","$L4",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]]}],"loading":null,"isPartial":false}
:HL["/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","style"]
0:{"buildId":"44qtzzWDxYYxKQeshxCwM","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/e6da73430a674f20.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","template":["$","$L4",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]]}],"loading":null,"isPartial":false}

View file

@ -1,5 +1,5 @@
1:"$Sreact.fragment"
2:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"OutletBoundary"]
3:"$Sreact.suspense"
0:{"buildId":"FMM2sqIA1T3SyE5jWLnR9","rsc":["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"loading":null,"isPartial":false}
0:{"buildId":"44qtzzWDxYYxKQeshxCwM","rsc":["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"loading":null,"isPartial":false}
4:null

View file

@ -1,4 +1,4 @@
1:"$Sreact.fragment"
2:I[39756,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"default"]
3:I[37457,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"default"]
0:{"buildId":"FMM2sqIA1T3SyE5jWLnR9","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}
0:{"buildId":"44qtzzWDxYYxKQeshxCwM","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}

View file

@ -1,2 +1,2 @@
:HL["/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","style"]
0:{"buildId":"FMM2sqIA1T3SyE5jWLnR9","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"/_not-found","paramType":null,"paramKey":"/_not-found","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
:HL["/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","style"]
0:{"buildId":"44qtzzWDxYYxKQeshxCwM","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"/_not-found","paramType":null,"paramKey":"/_not-found","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":false}},"isRootLayout":true},"staleTime":300}

File diff suppressed because one or more lines are too long

View file

@ -7,8 +7,8 @@
8:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"ViewportBoundary"]
a:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"MetadataBoundary"]
c:I[68027,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"default"]
:HL["/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","style"]
0:{"P":null,"b":"FMM2sqIA1T3SyE5jWLnR9","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/e6da73430a674f20.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L5",null,{"children":["$","$6",null,{"name":"Next.MetadataOutlet","children":"$@7"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$6",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],null]}],false]],"m":"$undefined","G":["$c","$undefined"],"S":true}
:HL["/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","style"]
0:{"P":null,"b":"44qtzzWDxYYxKQeshxCwM","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/e6da73430a674f20.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L5",null,{"children":["$","$6",null,{"name":"Next.MetadataOutlet","children":"$@7"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$6",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],null]}],false]],"m":"$undefined","G":["$c","$undefined"],"S":true}
9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]
d:I[27201,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"IconMark"]
7:null

File diff suppressed because one or more lines are too long

View file

@ -3,14 +3,14 @@
3:I[39756,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"default"]
4:I[37457,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"default"]
5:I[47257,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"ClientPageRoot"]
6:I[31713,["/t2-mapper/_next/static/chunks/e6da73430a674f20.js","/t2-mapper/_next/static/chunks/69160059bd4715b0.js","/t2-mapper/_next/static/chunks/93b588fa7f31935c.js","/t2-mapper/_next/static/chunks/f5fbb30641aa8588.js","/t2-mapper/_next/static/chunks/7cc8b088c0b74e6e.js"],"default"]
6:I[31713,["/t2-mapper/_next/static/chunks/e6da73430a674f20.js","/t2-mapper/_next/static/chunks/69160059bd4715b0.js","/t2-mapper/_next/static/chunks/93b588fa7f31935c.js","/t2-mapper/_next/static/chunks/affab05731a53281.js","/t2-mapper/_next/static/chunks/5109a68f602829f3.js"],"default"]
9:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"OutletBoundary"]
a:"$Sreact.suspense"
c:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"ViewportBoundary"]
e:I[97367,["/t2-mapper/_next/static/chunks/4fd93823156e59e8.js"],"MetadataBoundary"]
10:I[68027,[],"default"]
:HL["/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","style"]
0:{"P":null,"b":"FMM2sqIA1T3SyE5jWLnR9","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/5c8f13e9504ceb7f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/e6da73430a674f20.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/69160059bd4715b0.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/93b588fa7f31935c.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/f5fbb30641aa8588.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/7cc8b088c0b74e6e.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],null]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
:HL["/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","style"]
0:{"P":null,"b":"44qtzzWDxYYxKQeshxCwM","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/4b117a58395a92c9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/e6da73430a674f20.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/69160059bd4715b0.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/93b588fa7f31935c.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/affab05731a53281.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/5109a68f602829f3.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],null]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
7:{}
8:"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params"
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]

View file

@ -47,14 +47,16 @@ export function CopyCoordinatesButton({
return (
<button
type="button"
className="IconButton CopyCoordinatesButton"
className="IconButton LabelledButton CopyCoordinatesButton"
aria-label="Copy coordinates URL"
title="Copy coordinates URL"
onClick={handleCopyLink}
data-copied={showCopied ? "true" : "false"}
id="copyCoordinatesButton"
>
<FaMapPin className="MapPin" />
<FaClipboardCheck className="ClipboardCheck" />
<span className="ButtonLabel"> Copy coordinates URL</span>
</button>
);
}

View file

@ -1,14 +1,21 @@
import { useControls, useDebug, useSettings } from "./SettingsProvider";
import {
useControls,
useDebug,
useSettings,
type TouchMode,
} from "./SettingsProvider";
import { MissionSelect } from "./MissionSelect";
import { RefObject } from "react";
import { RefObject, useEffect, useState, useRef } from "react";
import { Camera } from "three";
import { CopyCoordinatesButton } from "./CopyCoordinatesButton";
import { FiSettings } from "react-icons/fi";
export function InspectorControls({
missionName,
missionType,
onChangeMission,
cameraRef,
isTouch,
}: {
missionName: string;
missionType: string;
@ -20,6 +27,7 @@ export function InspectorControls({
missionType: string;
}) => void;
cameraRef: RefObject<Camera | null>;
isTouch: boolean | null;
}) {
const {
fogEnabled,
@ -31,8 +39,139 @@ export function InspectorControls({
animationEnabled,
setAnimationEnabled,
} = useSettings();
const { speedMultiplier, setSpeedMultiplier } = useControls();
const { speedMultiplier, setSpeedMultiplier, touchMode, setTouchMode } =
useControls();
const { debugMode, setDebugMode } = useDebug();
const [settingsOpen, setSettingsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
// Focus the panel when it opens.
useEffect(() => {
if (settingsOpen) {
dropdownRef.current?.focus();
}
}, [settingsOpen]);
const handleDropdownBlur = (e: React.FocusEvent) => {
const related = e.relatedTarget as Node | null;
if (
related &&
(dropdownRef.current?.contains(related) ||
buttonRef.current?.contains(related))
) {
return;
}
setSettingsOpen(false);
};
// Close on Escape and return focus to the gear button.
const handlePanelKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Escape") {
setSettingsOpen(false);
buttonRef.current?.focus();
}
};
const settingsFields = (
<>
<div className="Controls-group">
<CopyCoordinatesButton cameraRef={cameraRef} />
</div>
<div className="Controls-group">
<div className="CheckboxField">
<input
id="fogInput"
type="checkbox"
checked={fogEnabled}
onChange={(event) => {
setFogEnabled(event.target.checked);
}}
/>
<label htmlFor="fogInput">Fog?</label>
</div>
<div className="CheckboxField">
<input
id="audioInput"
type="checkbox"
checked={audioEnabled}
onChange={(event) => {
setAudioEnabled(event.target.checked);
}}
/>
<label htmlFor="audioInput">Audio?</label>
</div>
</div>
<div className="Controls-group">
<div className="CheckboxField">
<input
id="animationInput"
type="checkbox"
checked={animationEnabled}
onChange={(event) => {
setAnimationEnabled(event.target.checked);
}}
/>
<label htmlFor="animationInput">Animation?</label>
</div>
<div className="CheckboxField">
<input
id="debugInput"
type="checkbox"
checked={debugMode}
onChange={(event) => {
setDebugMode(event.target.checked);
}}
/>
<label htmlFor="debugInput">Debug?</label>
</div>
</div>
<div className="Controls-group">
<div className="Field">
<label htmlFor="fovInput">FOV</label>
<input
id="fovInput"
type="range"
min={75}
max={120}
step={5}
value={fov}
onChange={(event) => setFov(parseInt(event.target.value))}
/>
<output htmlFor="fovInput">{fov}</output>
</div>
<div className="Field">
<label htmlFor="speedInput">Speed</label>
<input
id="speedInput"
type="range"
min={0.1}
max={5}
step={0.05}
value={speedMultiplier}
onChange={(event) =>
setSpeedMultiplier(parseFloat(event.target.value))
}
/>
</div>
</div>
{isTouch && (
<div className="Controls-group">
<div className="Field">
<label htmlFor="touchModeInput">Joystick:</label>{" "}
<select
id="touchModeInput"
value={touchMode}
onChange={(e) => setTouchMode(e.target.value as TouchMode)}
>
<option value="dualStick">Dual Stick</option>
<option value="moveLookStick">Single Stick</option>
</select>
</div>
</div>
)}
</>
);
return (
<div
@ -46,77 +185,27 @@ export function InspectorControls({
missionType={missionType}
onChange={onChangeMission}
/>
<CopyCoordinatesButton cameraRef={cameraRef} />
<div className="CheckboxField">
<input
id="fogInput"
type="checkbox"
checked={fogEnabled}
onChange={(event) => {
setFogEnabled(event.target.checked);
}}
/>
<label htmlFor="fogInput">Fog?</label>
</div>
<div className="CheckboxField">
<input
id="audioInput"
type="checkbox"
checked={audioEnabled}
onChange={(event) => {
setAudioEnabled(event.target.checked);
}}
/>
<label htmlFor="audioInput">Audio?</label>
</div>
<div className="CheckboxField">
<input
id="animationInput"
type="checkbox"
checked={animationEnabled}
onChange={(event) => {
setAnimationEnabled(event.target.checked);
}}
/>
<label htmlFor="animationInput">Animation?</label>
</div>
<div className="CheckboxField">
<input
id="debugInput"
type="checkbox"
checked={debugMode}
onChange={(event) => {
setDebugMode(event.target.checked);
}}
/>
<label htmlFor="debugInput">Debug?</label>
</div>
<div className="Field">
<label htmlFor="fovInput">FOV</label>
<input
id="fovInput"
type="range"
min={75}
max={120}
step={5}
value={fov}
onChange={(event) => setFov(parseInt(event.target.value))}
/>
<output htmlFor="speedInput">{fov}</output>
</div>
<div className="Field">
<label htmlFor="speedInput">Speed</label>
<input
id="speedInput"
type="range"
min={0.1}
max={5}
step={0.05}
value={speedMultiplier}
onChange={(event) =>
setSpeedMultiplier(parseFloat(event.target.value))
}
/>
<button
ref={buttonRef}
className="IconButton Controls-toggle"
onClick={() => setSettingsOpen((isOpen) => !isOpen)}
onDoubleClick={(e) => e.preventDefault()}
aria-expanded={settingsOpen}
aria-controls="settingsPanel"
aria-label="Settings"
>
<FiSettings />
</button>
<div
className="Controls-dropdown"
ref={dropdownRef}
id="settingsPanel"
tabIndex={-1}
onKeyDown={handlePanelKeyDown}
onBlur={handleDropdownBlur}
data-open={settingsOpen}
>
{settingsFields}
</div>
</div>
);

View file

@ -11,6 +11,8 @@ import {
type StateSetter<T> = ReturnType<typeof useState<T>>[1];
export type TouchMode = "dualStick" | "moveLookStick";
type SettingsContext = {
fogEnabled: boolean;
setFogEnabled: StateSetter<boolean>;
@ -32,6 +34,8 @@ type DebugContext = {
type ControlsContext = {
speedMultiplier: number;
setSpeedMultiplier: StateSetter<number>;
touchMode: TouchMode;
setTouchMode: StateSetter<TouchMode>;
};
const SettingsContext = createContext<SettingsContext | null>(null);
@ -46,6 +50,7 @@ type PersistedSettings = {
audioEnabled?: boolean;
animationEnabled?: boolean;
debugMode?: boolean;
touchMode?: TouchMode;
};
export function useSettings() {
@ -68,6 +73,7 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
const [audioEnabled, setAudioEnabled] = useState(false);
const [animationEnabled, setAnimationEnabled] = useState(true);
const [debugMode, setDebugMode] = useState(false);
const [touchMode, setTouchMode] = useState<TouchMode>("moveLookStick");
const settingsContext: SettingsContext = useMemo(
() => ({
@ -91,8 +97,8 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
);
const controlsContext: ControlsContext = useMemo(
() => ({ speedMultiplier, setSpeedMultiplier }),
[speedMultiplier, setSpeedMultiplier],
() => ({ speedMultiplier, setSpeedMultiplier, touchMode, setTouchMode }),
[speedMultiplier, setSpeedMultiplier, touchMode, setTouchMode],
);
// Read persisted settings from localStorage.
@ -124,6 +130,9 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
if (savedSettings.fov != null) {
setFov(savedSettings.fov);
}
if (savedSettings.touchMode != null) {
setTouchMode(savedSettings.touchMode);
}
}, []);
// Persist settings to localStorage with debouncing to avoid excessive writes
@ -145,6 +154,7 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
audioEnabled,
animationEnabled,
debugMode,
touchMode,
};
try {
localStorage.setItem("settings", JSON.stringify(settingsToSave));
@ -166,6 +176,7 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
audioEnabled,
animationEnabled,
debugMode,
touchMode,
]);
return (

View file

@ -6,6 +6,8 @@ import { useControls } from "./SettingsProvider";
const BASE_SPEED = 80;
const LOOK_SENSITIVITY = 0.004;
const STICK_LOOK_SENSITIVITY = 2.5;
const SINGLE_STICK_DEADZONE = 0.25;
const MAX_PITCH = Math.PI / 2 - 0.01; // ~89°
export type JoystickState = {
@ -16,10 +18,19 @@ export type JoystickState = {
type SharedProps = {
joystickState: RefObject<JoystickState>;
joystickZone: RefObject<HTMLDivElement | null>;
lookJoystickState: RefObject<JoystickState>;
lookJoystickZone: RefObject<HTMLDivElement | null>;
};
/** Renders the joystick zone. Place inside canvasContainer, outside Canvas. */
export function TouchJoystick({ joystickState, joystickZone }: SharedProps) {
/** Renders the joystick zone(s). Place inside canvasContainer, outside Canvas. */
export function TouchJoystick({
joystickState,
joystickZone,
lookJoystickState,
lookJoystickZone,
}: SharedProps) {
const { touchMode } = useControls();
// Move joystick
useEffect(() => {
const zone = joystickZone.current;
if (!zone) return;
@ -39,7 +50,6 @@ export function TouchJoystick({ joystickState, joystickZone }: SharedProps) {
manager.on("move", (_event, data) => {
joystickState.current.angle = data.angle.radian;
// Clamp force to 01 range (nipplejs force can exceed 1)
joystickState.current.force = Math.min(1, data.force);
});
@ -52,17 +62,77 @@ export function TouchJoystick({ joystickState, joystickZone }: SharedProps) {
cancelled = true;
manager?.destroy();
};
}, [joystickState, joystickZone]);
}, [joystickState, joystickZone, touchMode]);
return <div ref={joystickZone} className="TouchJoystick" />;
// Look joystick (dual stick mode only)
useEffect(() => {
if (touchMode !== "dualStick") return;
const zone = lookJoystickZone.current;
if (!zone) return;
let manager: nipplejs.JoystickManager | null = null;
let cancelled = false;
import("nipplejs").then((mod) => {
if (cancelled) return;
manager = mod.default.create({
zone,
mode: "static",
position: { right: "70px", bottom: "70px" },
size: 120,
restOpacity: 0.9,
});
manager.on("move", (_event, data) => {
lookJoystickState.current.angle = data.angle.radian;
lookJoystickState.current.force = Math.min(1, data.force);
});
manager.on("end", () => {
lookJoystickState.current.force = 0;
});
});
return () => {
cancelled = true;
manager?.destroy();
};
}, [touchMode, lookJoystickState, lookJoystickZone]);
if (touchMode === "dualStick") {
return (
<>
<div
ref={joystickZone}
className="TouchJoystick TouchJoystick--left"
onContextMenu={(e) => e.preventDefault()}
/>
<div
ref={lookJoystickZone}
className="TouchJoystick TouchJoystick--right"
onContextMenu={(e) => e.preventDefault()}
/>
</>
);
}
return (
<div
ref={joystickZone}
className="TouchJoystick"
onContextMenu={(e) => e.preventDefault()}
/>
);
}
/** Handles touch look and joystick-driven movement. Place inside Canvas. */
export function TouchCameraMovement({
joystickState,
joystickZone,
lookJoystickState,
}: SharedProps) {
const { speedMultiplier } = useControls();
const { speedMultiplier, touchMode } = useControls();
const { camera, gl } = useThree();
// Touch look state
@ -80,8 +150,10 @@ export function TouchCameraMovement({
euler.current.setFromQuaternion(camera.quaternion, "YXZ");
}, [camera]);
// Touch look handling
// Touch-drag look handling (moveLookStick mode)
useEffect(() => {
if (touchMode !== "moveLookStick") return;
const canvas = gl.domElement;
const isTouchOnJoystick = (touch: Touch) => {
@ -149,38 +221,82 @@ export function TouchCameraMovement({
canvas.removeEventListener("touchmove", handleTouchMove);
canvas.removeEventListener("touchend", handleTouchEnd);
canvas.removeEventListener("touchcancel", handleTouchEnd);
lookTouchId.current = null;
};
}, [camera, gl.domElement, joystickZone]);
}, [camera, gl.domElement, joystickZone, touchMode]);
useFrame((_state, delta) => {
const { force, angle } = joystickState.current;
if (force === 0) return;
const speed = BASE_SPEED * speedMultiplier * force;
if (touchMode === "dualStick") {
// Right stick → camera rotation
const look = lookJoystickState.current;
if (look.force > 0) {
const lookX = Math.cos(look.angle);
const lookY = Math.sin(look.angle);
// Decompose joystick angle into forward/sideways components
// nipplejs angle: 0 = right, π/2 = up, π = left, 3π/2 = down
const joyX = Math.cos(angle); // right component
const joyY = Math.sin(angle); // forward component
euler.current.setFromQuaternion(camera.quaternion, "YXZ");
euler.current.y -= lookX * look.force * STICK_LOOK_SENSITIVITY * delta;
euler.current.x += lookY * look.force * STICK_LOOK_SENSITIVITY * delta;
euler.current.x = Math.max(
-MAX_PITCH,
Math.min(MAX_PITCH, euler.current.x),
);
camera.quaternion.setFromEuler(euler.current);
}
// Forward/backward: use full camera direction (including Y) so
// looking up and pushing forward moves upward, like ObserverControls.
camera.getWorldDirection(forwardVec.current);
forwardVec.current.normalize();
// Left stick → movement
if (force > 0) {
const speed = BASE_SPEED * speedMultiplier * force;
const joyX = Math.cos(angle);
const joyY = Math.sin(angle);
// Left/right: move along XZ plane
sideVec.current.crossVectors(camera.up, forwardVec.current).normalize();
camera.getWorldDirection(forwardVec.current);
forwardVec.current.normalize();
sideVec.current
.crossVectors(camera.up, forwardVec.current)
.normalize();
// Combine: joyY pushes forward, joyX pushes right.
// sideVec points left (up × forward), so negate joyX.
moveVec.current
.set(0, 0, 0)
.addScaledVector(forwardVec.current, joyY)
.addScaledVector(sideVec.current, -joyX);
moveVec.current
.set(0, 0, 0)
.addScaledVector(forwardVec.current, joyY)
.addScaledVector(sideVec.current, -joyX);
if (moveVec.current.lengthSq() > 0) {
moveVec.current.normalize().multiplyScalar(speed * delta);
camera.position.add(moveVec.current);
if (moveVec.current.lengthSq() > 0) {
moveVec.current.normalize().multiplyScalar(speed * delta);
camera.position.add(moveVec.current);
}
}
} else if (touchMode === "moveLookStick") {
if (force > 0) {
// Move forward at half the configured speed.
const speed = BASE_SPEED * speedMultiplier * 0.5;
camera.getWorldDirection(forwardVec.current);
forwardVec.current.normalize();
moveVec.current
.copy(forwardVec.current)
.multiplyScalar(speed * delta);
camera.position.add(moveVec.current);
if (force >= SINGLE_STICK_DEADZONE) {
// Outer zone: also control camera look (yaw + pitch).
const lookX = Math.cos(angle);
const lookY = Math.sin(angle);
const lookForce =
(force - SINGLE_STICK_DEADZONE) / (1 - SINGLE_STICK_DEADZONE);
euler.current.setFromQuaternion(camera.quaternion, "YXZ");
euler.current.y -=
lookX * lookForce * STICK_LOOK_SENSITIVITY * 0.5 * delta;
euler.current.x +=
lookY * lookForce * STICK_LOOK_SENSITIVITY * 0.5 * delta;
euler.current.x = Math.max(
-MAX_PITCH,
Math.min(MAX_PITCH, euler.current.x),
);
camera.quaternion.setFromEuler(euler.current);
}
}
}
});