feat: unify life-sim play controls
This commit is contained in:
+246
-22
@@ -765,6 +765,72 @@
|
||||
than mystery glyphs: the controls disappear everywhere space is tight
|
||||
except on the devices that cannot drive without them. */
|
||||
.drive-controls, .walk-controls { display: none; }
|
||||
.play-hud {
|
||||
position: fixed;
|
||||
top: calc(var(--s4) + env(safe-area-inset-top));
|
||||
left: 50%;
|
||||
z-index: 4;
|
||||
min-width: min(27rem, calc(100vw - 12rem));
|
||||
max-width: calc(100vw - var(--s4) * 2);
|
||||
transform: translateX(-50%);
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: baseline;
|
||||
gap: var(--s2) var(--s3);
|
||||
padding: var(--s2) var(--s3);
|
||||
pointer-events: none;
|
||||
color: var(--ink-2);
|
||||
background: rgba(8, 12, 17, 0.78);
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: var(--r);
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: var(--blur);
|
||||
-webkit-backdrop-filter: var(--blur);
|
||||
font-size: 10px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.play-hud[hidden] { display: none; }
|
||||
.play-hud-mode { color: var(--amber-ink); text-transform: uppercase; letter-spacing: 0.11em; }
|
||||
.play-hud-primary { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.play-hud-status { text-align: right; color: var(--ink-3); }
|
||||
.play-hud-status.warning { color: #ff9d72; }
|
||||
.mode-dock {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: calc(var(--s4) + env(safe-area-inset-bottom));
|
||||
z-index: 4;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 4px;
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: 999px;
|
||||
background: rgba(8, 12, 17, 0.82);
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: var(--blur);
|
||||
-webkit-backdrop-filter: var(--blur);
|
||||
}
|
||||
.mode-dock button {
|
||||
min-width: 44px;
|
||||
min-height: 38px;
|
||||
padding: 5px 10px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
color: var(--ink-3);
|
||||
background: transparent;
|
||||
font: inherit;
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mode-dock button[aria-pressed="true"] {
|
||||
color: var(--amber-ink);
|
||||
background: rgba(242, 177, 52, 0.2);
|
||||
}
|
||||
.mode-dock button[hidden] { display: none; }
|
||||
@media (pointer: coarse) {
|
||||
.drive-controls:not([hidden]), .walk-controls:not([hidden]) {
|
||||
position: fixed;
|
||||
@@ -807,6 +873,142 @@
|
||||
body:has(.drive-controls:not([hidden])) .source,
|
||||
body:has(.walk-controls:not([hidden])) .source { bottom: 7.5rem; }
|
||||
}
|
||||
body.touch-capable .drive-controls:not([hidden]),
|
||||
body.touch-capable .walk-controls:not([hidden]) {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: calc(var(--s3) + env(safe-area-inset-bottom));
|
||||
transform: translateX(-50%);
|
||||
z-index: 5;
|
||||
width: min(23rem, calc(100vw - var(--s4) * 2));
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: var(--s1);
|
||||
padding: var(--s1);
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: var(--r);
|
||||
background: var(--glass-strong);
|
||||
backdrop-filter: var(--blur);
|
||||
-webkit-backdrop-filter: var(--blur);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
body.touch-capable .drive-control,
|
||||
body.touch-capable .walk-control {
|
||||
min-height: 48px;
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: var(--r-sm);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: var(--ink);
|
||||
font: inherit;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
body.touch-capable .drive-control[aria-pressed="true"],
|
||||
body.touch-capable .walk-control[aria-pressed="true"] {
|
||||
border-color: rgba(242, 177, 52, 0.65);
|
||||
background: rgba(242, 177, 52, 0.28);
|
||||
color: var(--amber-ink);
|
||||
}
|
||||
body:has(.drive-controls:not([hidden])) .mode-dock,
|
||||
body:has(.drive-controls:not([hidden])) .rail { bottom: calc(8rem + env(safe-area-inset-bottom)); }
|
||||
body:has(.walk-controls:not([hidden])) .mode-dock,
|
||||
body:has(.walk-controls:not([hidden])) .rail { bottom: calc(11.5rem + env(safe-area-inset-bottom)); }
|
||||
|
||||
/* GTA-like touch surface: analogue movement on the left, only the
|
||||
actions meaningful to the current vehicle/actor on the right. */
|
||||
.touch-play-controls { display: none; }
|
||||
@media (pointer: coarse) {
|
||||
.touch-play-controls:not([hidden]) { display: block; }
|
||||
}
|
||||
body.touch-capable .touch-play-controls:not([hidden]) { display: block; }
|
||||
.touch-play-controls:not([hidden]) {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.play-stick {
|
||||
--stick-x: 0px;
|
||||
--stick-y: 0px;
|
||||
position: absolute;
|
||||
left: max(var(--s3), env(safe-area-inset-left));
|
||||
bottom: calc(var(--s3) + env(safe-area-inset-bottom));
|
||||
width: clamp(7.5rem, 31vw, 9rem);
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
pointer-events: auto;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
background: radial-gradient(circle, rgba(255,255,255,.08) 0 38%, rgba(8,12,17,.78) 40% 100%);
|
||||
border: 1px solid rgba(255,255,255,.18);
|
||||
box-shadow: inset 0 0 24px rgba(0,0,0,.35), var(--shadow);
|
||||
backdrop-filter: var(--blur);
|
||||
-webkit-backdrop-filter: var(--blur);
|
||||
}
|
||||
.play-stick-ring {
|
||||
position: absolute;
|
||||
inset: 21%;
|
||||
border: 1px solid rgba(242,177,52,.32);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.play-stick-knob {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 3.25rem;
|
||||
height: 3.25rem;
|
||||
transform: translate(calc(-50% + var(--stick-x)), calc(-50% + var(--stick-y)));
|
||||
border-radius: 50%;
|
||||
background: rgba(242,177,52,.28);
|
||||
border: 1px solid rgba(242,177,52,.72);
|
||||
box-shadow: 0 4px 14px rgba(0,0,0,.45);
|
||||
transition: transform 80ms ease-out;
|
||||
}
|
||||
.play-stick.active .play-stick-knob { transition: none; }
|
||||
.touch-actions {
|
||||
position: absolute;
|
||||
right: max(var(--s3), env(safe-area-inset-right));
|
||||
bottom: calc(var(--s3) + env(safe-area-inset-bottom));
|
||||
width: min(10rem, 42vw);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 5px;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.touch-actions button {
|
||||
min-height: 46px;
|
||||
padding: 5px 7px;
|
||||
border: 1px solid rgba(255,255,255,.18);
|
||||
border-radius: 999px;
|
||||
background: rgba(8,12,17,.78);
|
||||
color: var(--ink-2);
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: var(--blur);
|
||||
-webkit-backdrop-filter: var(--blur);
|
||||
font: inherit;
|
||||
font-size: 9px;
|
||||
letter-spacing: .06em;
|
||||
text-transform: uppercase;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.touch-actions button[hidden] { display: none; }
|
||||
.touch-actions button[aria-pressed="true"] {
|
||||
border-color: rgba(242,177,52,.72);
|
||||
background: rgba(242,177,52,.28);
|
||||
color: var(--amber-ink);
|
||||
}
|
||||
body:has(.touch-play-controls:not([hidden])) .mode-dock {
|
||||
bottom: calc(10rem + env(safe-area-inset-bottom));
|
||||
}
|
||||
body:has(.touch-play-controls:not([hidden])) #hint { display: none; }
|
||||
body:has(.touch-play-controls:not([hidden])) .source { bottom: 10rem; }
|
||||
|
||||
/* ---- Responsive -------------------------------------------------------
|
||||
Two breakpoints and no more. At 900 the left column stops being furniture
|
||||
@@ -850,6 +1052,18 @@
|
||||
`deviceProfile()` in `stage.ts` keys off this exact 600px edge — so the
|
||||
stylesheet and the pixel budget cannot drift apart. */
|
||||
@media (max-width: 600px) {
|
||||
.play-hud {
|
||||
top: calc(var(--s3) + env(safe-area-inset-top) + 2.65rem);
|
||||
min-width: 0;
|
||||
width: calc(100vw - var(--s3) * 2);
|
||||
}
|
||||
.mode-dock {
|
||||
max-width: calc(100vw - var(--s3) * 2);
|
||||
bottom: calc(var(--s3) + env(safe-area-inset-bottom));
|
||||
}
|
||||
.mode-dock button { padding-inline: 8px; }
|
||||
body:has(.drive-controls:not([hidden])) #hint,
|
||||
body:has(.walk-controls:not([hidden])) #hint { display: none; }
|
||||
/* Everything hit by a finger clears 44px, which is 11 steps of the 4px
|
||||
rhythm and the smallest target anyone has managed to defend. `#help`
|
||||
was 17px tall. */
|
||||
@@ -1039,28 +1253,36 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="drive-controls" class="drive-controls" aria-label="Vehicle controls" hidden>
|
||||
<button class="drive-control" data-drive-key="a" aria-pressed="false">Left</button>
|
||||
<button class="drive-control" data-drive-key="w" aria-pressed="false">Throttle</button>
|
||||
<button class="drive-control" data-drive-key="s" aria-pressed="false">Brake</button>
|
||||
<button class="drive-control" data-drive-key="d" aria-pressed="false">Right</button>
|
||||
<button class="drive-control" data-drive-key=" " aria-pressed="false">Handbrake</button>
|
||||
<button class="drive-control" data-drive-action="assist">Assist</button>
|
||||
<button class="drive-control" data-drive-action="reset">Reset</button>
|
||||
<button class="drive-control" data-drive-action="camera">Camera</button>
|
||||
</div>
|
||||
<section id="play-hud" class="play-hud" aria-live="polite" hidden>
|
||||
<span id="play-hud-mode" class="play-hud-mode"></span>
|
||||
<strong id="play-hud-primary" class="play-hud-primary"></strong>
|
||||
<span id="play-hud-status" class="play-hud-status"></span>
|
||||
</section>
|
||||
|
||||
<div id="walk-controls" class="walk-controls" aria-label="Walking controls" hidden>
|
||||
<button class="walk-control" data-walk-key="a" data-walk-label="Left" data-aircraft-label="Roll left" aria-pressed="false">Left</button>
|
||||
<button class="walk-control" data-walk-key="w" data-walk-label="Forward" data-aircraft-label="Pitch up" aria-pressed="false">Forward</button>
|
||||
<button class="walk-control" data-walk-key="s" data-walk-label="Back" data-aircraft-label="Pitch down" aria-pressed="false">Back</button>
|
||||
<button class="walk-control" data-walk-key="d" data-walk-label="Right" data-aircraft-label="Roll right" aria-pressed="false">Right</button>
|
||||
<button class="walk-control flight-only" data-walk-key="q" data-walk-label="Descend" data-aircraft-label="Yaw left" aria-pressed="false">Descend</button>
|
||||
<button class="walk-control flight-only" data-walk-key="e" data-walk-label="Climb" data-aircraft-label="Yaw right" aria-pressed="false">Climb</button>
|
||||
<button class="walk-control crow-only" data-walk-key="g" data-walk-label="Glide" aria-pressed="false" hidden>Glide</button>
|
||||
<button class="walk-control aircraft-only" data-walk-key=" " data-aircraft-label="Throttle" aria-pressed="false" hidden>Throttle</button>
|
||||
<button class="walk-control aircraft-only" data-aircraft-action="assist" hidden>Assist</button>
|
||||
<button class="walk-control aircraft-only" data-aircraft-action="reset" hidden>Reset</button>
|
||||
<nav id="mode-dock" class="mode-dock" aria-label="Control mode">
|
||||
<button type="button" data-control-mode="overview" aria-pressed="true">View</button>
|
||||
<button type="button" data-control-mode="drive" aria-pressed="false" hidden>Drive</button>
|
||||
<button type="button" data-control-mode="actor" aria-pressed="false" hidden>Explore</button>
|
||||
<button type="button" data-control-mode="aircraft" aria-pressed="false" hidden>Fly</button>
|
||||
<button type="button" data-control-mode="office-walk" aria-pressed="false" hidden>Walk</button>
|
||||
</nav>
|
||||
|
||||
<div id="touch-play-controls" class="touch-play-controls" aria-label="Touch play controls" hidden>
|
||||
<div id="play-stick" class="play-stick" role="application"
|
||||
aria-label="Movement joystick. Drag in any direction.">
|
||||
<span class="play-stick-ring" aria-hidden="true"></span>
|
||||
<span id="play-stick-knob" class="play-stick-knob" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="touch-actions">
|
||||
<button id="touch-primary" data-play-control="primary" aria-pressed="false">Action</button>
|
||||
<button id="touch-secondary" data-play-control="secondary" aria-pressed="false" hidden>Glide</button>
|
||||
<button id="touch-pitch-up" data-play-control="pitch-up" aria-pressed="false" hidden>Pitch +</button>
|
||||
<button id="touch-pitch-down" data-play-control="pitch-down" aria-pressed="false" hidden>Pitch −</button>
|
||||
<button id="touch-assist" hidden>Assist</button>
|
||||
<button id="touch-reset" hidden>Reset</button>
|
||||
<button id="touch-camera" hidden>Camera</button>
|
||||
<button id="touch-map" aria-pressed="false">Map</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p id="source" class="source"></p>
|
||||
@@ -1082,6 +1304,7 @@
|
||||
<dt><kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd></dt><dd>Drive, walk, or manually fly the California aircraft</dd>
|
||||
<dt><kbd>V</kbd></dt><dd>Walk through an office / return to the dollhouse view</dd>
|
||||
<dt><kbd>Q</kbd> / <kbd>E</kbd></dt><dd>Descend / climb while flying as a crow</dd>
|
||||
<dt><kbd>I</kbd> / <kbd>K</kbd></dt><dd>Pitch the crow up / down</dd>
|
||||
<dt><kbd>Space</kbd></dt><dd>Handbrake while driving</dd>
|
||||
<dt><kbd>P</kbd> / <kbd>R</kbd></dt><dd>Resume assisted drive or flight / reset the vehicle</dd>
|
||||
<dt><kbd>C</kbd></dt><dd>Switch chase / driver-height camera</dd>
|
||||
@@ -1099,7 +1322,8 @@
|
||||
<dt>One finger</dt><dd>Orbit</dd>
|
||||
<dt>Two fingers</dt><dd>Pinch to zoom, drag to move over the ground</dd>
|
||||
<dt>Tap a marker</dt><dd>Its card, at the bottom of the screen</dd>
|
||||
<dt>Route controls</dt><dd>Choose 101 or I-5; the driving pad appears at the bottom</dd>
|
||||
<dt>Play joystick</dt><dd>Move with the lower-left stick; current actions appear lower right</dd>
|
||||
<dt>Map</dt><dd>The play action opens or closes the local-player plan</dd>
|
||||
<dt>Tap the map</dt><dd>Dismiss the card; tap the ☰ sheet's scrim to close it</dd>
|
||||
<dt>Plan view</dt><dd>The button on the rail shows or hides it, as a sheet above the rail</dd>
|
||||
</dl>
|
||||
|
||||
Reference in New Issue
Block a user