The instruments move into the bezel; the windshield goes bare
Per the table's suggestion: chevrons and trays no longer overlay the canvas — a frame holds them. The top bezel carries the ride controls, the sharing-your-square portraits, and the hide button; tall turn paddles flank the glass; the bottom bezel holds the strides, the at-your-feet tray, hazard chips, the warp-step, and the key hint. Every canvas pixel is now permanently hover-and-click target glass, and the yield-while-targeting hack retires unneeded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
co-authored by
Claude Fable 5
parent
0530ecb039
commit
7bd8eff11c
@@ -1914,8 +1914,7 @@
|
||||
onbody={(id) => (fpvBody = id)}
|
||||
onWarpStep={(creatureId) => dispatch(creatureId
|
||||
? { type: "creatureWarpStep", creatureId }
|
||||
: { type: "warpStep" })}
|
||||
targeting={!!selectedCard && yourMoment} />
|
||||
: { type: "warpStep" })} />
|
||||
{/if}
|
||||
<Board
|
||||
{view}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { untrack } from "svelte";
|
||||
import type { GameEvent, GameView, Side } from "@wizwar/engine";
|
||||
|
||||
let { view, batch, onhide, onstride = null, canStride = false, ontarget = null, onfacing = null, litCells = null, onpickup = null, onCreatureMove = null, onCreatureAttack = null, onbody = null, onWarpStep = null, targeting = false }: {
|
||||
let { view, batch, onhide, onstride = null, canStride = false, ontarget = null, onfacing = null, litCells = null, onpickup = null, onCreatureMove = null, onCreatureAttack = null, onbody = null, onWarpStep = null }: {
|
||||
view: GameView;
|
||||
/** The latest live event batch, numbered so each plays once, with
|
||||
* the view the server sent alongside it. */
|
||||
@@ -46,9 +46,6 @@
|
||||
/** A dimensional-warp step for whichever body the pane is: the
|
||||
* wizard (null) or the ridden creature (its id). */
|
||||
onWarpStep?: ((creatureId: string | null) => void) | null;
|
||||
/** A card is aimed: the drive strips yield the glass so every pixel
|
||||
* hovers and clicks as a target (keys still steer). */
|
||||
targeting?: boolean;
|
||||
} = $props();
|
||||
|
||||
const povId = $derived(view.you);
|
||||
@@ -427,36 +424,22 @@
|
||||
|
||||
{#if me}
|
||||
<div class="live-fp">
|
||||
<FirstPerson {view} povId={cutawayShot ? "" : (possessedId ?? povId)}
|
||||
x={cam.x} y={cam.y} facing={cam.facing} width={960} height={400}
|
||||
fx={fpFx} posOverride={actorPos}
|
||||
ontarget={handleTarget} {litCells} />
|
||||
<button class="live-fp-hide" onclick={onhide} title="hide (re-enable in preferences)">✕</button>
|
||||
<!-- The helm, tappable: edge strips turn and stride. -->
|
||||
<button class="drive drive-left" class:yielded={targeting} onclick={() => manualTurn(-1)} aria-label="turn left">‹</button>
|
||||
<button class="drive drive-right" class:yielded={targeting} onclick={() => manualTurn(1)} aria-label="turn right">›</button>
|
||||
{#if canStride}
|
||||
<button class="drive drive-fwd" class:yielded={targeting} onclick={() => manualStride(false)} aria-label="step forward">︿</button>
|
||||
<button class="drive drive-back" class:yielded={targeting} onclick={() => manualStride(true)} aria-label="step back">﹀</button>
|
||||
<div class="live-fp-hint">← → turn · ↑ ↓ walk</div>
|
||||
{/if}
|
||||
<!-- The bezel: instruments frame the windshield instead of covering
|
||||
it — every canvas pixel stays target glass. -->
|
||||
<div class="bezel-top">
|
||||
{#if possessed}
|
||||
<div class="ride-banner">
|
||||
<span class="ride-label">riding the {possessed.kind.replace(/-/g, " ")}</span>
|
||||
<span class="ride-stats">{Math.max(0, possessed.movesPerTurn - possessed.movementUsed)} moves{possessed.attackUsed ? "" : " · claws ready"}</span>
|
||||
<button class="stamp tiny" onclick={dismount}>back to your eyes</button>
|
||||
</div>
|
||||
{:else if rideable.length > 0 && (onCreatureMove || onCreatureAttack)}
|
||||
<div class="ride-row">
|
||||
{#each rideable as c (c.id)}
|
||||
<button class="stamp tiny" onclick={() => { possessedId = c.id; manualUntil = performance.now() + 60000; }}>
|
||||
👁 ride the {c.kind.replace(/-/g, " ")}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<span class="bezel-spacer"></span>
|
||||
{#if withYou.length > 0}
|
||||
<div class="with-tray">
|
||||
<span class="feet-label">sharing your square</span>
|
||||
{#each withYou as body (body.id)}
|
||||
<button class="feet-item" title={body.label}
|
||||
@@ -464,10 +447,23 @@
|
||||
<img src={body.art} alt={body.label} />
|
||||
</button>
|
||||
{/each}
|
||||
{/if}
|
||||
<button class="live-fp-hide" onclick={onhide} title="hide (re-enable in preferences)">✕</button>
|
||||
</div>
|
||||
<div class="bezel-mid">
|
||||
<button class="drive" onclick={() => manualTurn(-1)} aria-label="turn left">‹</button>
|
||||
<FirstPerson {view} povId={cutawayShot ? "" : (possessedId ?? povId)}
|
||||
x={cam.x} y={cam.y} facing={cam.facing} width={960} height={400}
|
||||
fx={fpFx} posOverride={actorPos}
|
||||
ontarget={handleTarget} {litCells} />
|
||||
<button class="drive" onclick={() => manualTurn(1)} aria-label="turn right">›</button>
|
||||
</div>
|
||||
<div class="bezel-bottom">
|
||||
{#if canStride}
|
||||
<button class="drive stride" onclick={() => manualStride(false)} aria-label="step forward">︿ forward</button>
|
||||
<button class="drive stride" onclick={() => manualStride(true)} aria-label="step back">﹀ back</button>
|
||||
{/if}
|
||||
{#if canStride && ((!possessed && atFeet.length > 0) || underfoot.hazard || underfoot.warpHere)}
|
||||
<div class="feet-tray">
|
||||
<span class="feet-label">at your feet</span>
|
||||
{#if !possessed && onpickup}
|
||||
{#each atFeet as item (item.id)}
|
||||
@@ -487,74 +483,63 @@
|
||||
{#if underfoot.warpHere && onWarpStep}
|
||||
<button class="stamp tiny" onclick={() => onWarpStep?.(possessedId)}>⇋ step through the warp</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<span class="bezel-spacer"></span>
|
||||
<span class="live-fp-hint">← → turn · ↑ ↓ walk</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.live-fp {
|
||||
position: relative;
|
||||
margin-bottom: 0.6rem;
|
||||
border: 1px solid #3a3428;
|
||||
border-radius: 6px;
|
||||
background: #14121a;
|
||||
overflow: hidden;
|
||||
}
|
||||
.live-fp-hide {
|
||||
position: absolute;
|
||||
top: 0.4rem;
|
||||
right: 0.4rem;
|
||||
background: rgba(13, 12, 18, 0.65);
|
||||
border: 1px solid #5a5342;
|
||||
border-radius: 3px;
|
||||
color: #a49c86;
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.1rem 0.4rem;
|
||||
}
|
||||
.live-fp-hide:hover { color: #e9e1cb; }
|
||||
.live-fp-hint {
|
||||
position: absolute;
|
||||
bottom: 0.45rem;
|
||||
left: 0.6rem;
|
||||
color: rgba(233, 225, 203, 0.55);
|
||||
font-family: "Courier Prime", monospace;
|
||||
font-size: 0.7rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* The helm: faint chevron strips along the pane's edges — always
|
||||
* there for a thumb, louder under a pointer. */
|
||||
.drive {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: rgba(233, 225, 203, 0.35);
|
||||
font-size: 2.2rem;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.drive:hover {
|
||||
color: rgba(233, 225, 203, 0.95);
|
||||
background: linear-gradient(to right, rgba(0, 0, 0, 0.22), transparent);
|
||||
}
|
||||
.drive-left { left: 0; top: 15%; bottom: 15%; width: 13%; }
|
||||
.drive-right { right: 0; top: 15%; bottom: 15%; width: 13%; }
|
||||
.drive-right:hover { background: linear-gradient(to left, rgba(0, 0, 0, 0.22), transparent); }
|
||||
.ride-row, .ride-banner {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
.bezel-top, .bezel-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: rgba(13, 12, 18, 0.78);
|
||||
border: 1px solid #3a3428;
|
||||
border-radius: 6px;
|
||||
padding: 4px 10px;
|
||||
z-index: 3;
|
||||
padding: 4px 8px;
|
||||
min-height: 34px;
|
||||
}
|
||||
.bezel-top { border-bottom: 1px solid #2a2620; }
|
||||
.bezel-bottom { border-top: 1px solid #2a2620; }
|
||||
.bezel-spacer { flex: 1; }
|
||||
.bezel-mid {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
.bezel-mid :global(.fpv-canvas) {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.drive {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #8d8672;
|
||||
font-size: 1.3rem;
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.drive:hover { color: #e9e1cb; background: rgba(201, 167, 42, 0.08); }
|
||||
.stride { font-size: 0.8rem; letter-spacing: 0.06em; }
|
||||
.live-fp-hide {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #8d8672;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.live-fp-hide:hover { color: #e9e1cb; }
|
||||
.live-fp-hint {
|
||||
font-family: "Courier Prime", monospace;
|
||||
font-size: 0.65rem;
|
||||
color: #5d5748;
|
||||
}
|
||||
.ride-label {
|
||||
font-family: "Oswald", sans-serif;
|
||||
@@ -564,34 +549,6 @@
|
||||
color: #c9a72a;
|
||||
}
|
||||
.ride-stats { font-size: 0.7rem; color: #8d8672; }
|
||||
.with-tray {
|
||||
position: absolute;
|
||||
bottom: 54px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: rgba(13, 12, 18, 0.78);
|
||||
border: 1px solid #3a3428;
|
||||
border-radius: 6px;
|
||||
padding: 4px 10px;
|
||||
z-index: 3;
|
||||
}
|
||||
.feet-tray {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: rgba(13, 12, 18, 0.78);
|
||||
border: 1px solid #3a3428;
|
||||
border-radius: 6px;
|
||||
padding: 4px 10px;
|
||||
z-index: 3;
|
||||
}
|
||||
.feet-label {
|
||||
font-family: "Oswald", sans-serif;
|
||||
font-size: 0.6rem;
|
||||
@@ -608,12 +565,7 @@
|
||||
line-height: 0;
|
||||
}
|
||||
.feet-item:hover { border-color: #c9a72a; }
|
||||
.feet-item img { width: 30px; height: 30px; object-fit: contain; }
|
||||
.feet-item img { width: 26px; height: 26px; object-fit: contain; }
|
||||
.feet-fallback { font-size: 0.65rem; color: #d8d2c0; padding: 0 4px; }
|
||||
.feet-hazard { font-size: 0.7rem; color: #d98a4a; white-space: nowrap; }
|
||||
.yielded { pointer-events: none; opacity: 0.12; }
|
||||
.drive-fwd { top: 0; left: 20%; right: 20%; height: 16%; }
|
||||
.drive-fwd:hover { background: linear-gradient(to bottom, rgba(0, 0, 0, 0.22), transparent); }
|
||||
.drive-back { bottom: 0; left: 20%; right: 20%; height: 15%; }
|
||||
.drive-back:hover { background: linear-gradient(to top, rgba(0, 0, 0, 0.22), transparent); }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user