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)}
|
onbody={(id) => (fpvBody = id)}
|
||||||
onWarpStep={(creatureId) => dispatch(creatureId
|
onWarpStep={(creatureId) => dispatch(creatureId
|
||||||
? { type: "creatureWarpStep", creatureId }
|
? { type: "creatureWarpStep", creatureId }
|
||||||
: { type: "warpStep" })}
|
: { type: "warpStep" })} />
|
||||||
targeting={!!selectedCard && yourMoment} />
|
|
||||||
{/if}
|
{/if}
|
||||||
<Board
|
<Board
|
||||||
{view}
|
{view}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import type { GameEvent, GameView, Side } from "@wizwar/engine";
|
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;
|
view: GameView;
|
||||||
/** The latest live event batch, numbered so each plays once, with
|
/** The latest live event batch, numbered so each plays once, with
|
||||||
* the view the server sent alongside it. */
|
* the view the server sent alongside it. */
|
||||||
@@ -46,9 +46,6 @@
|
|||||||
/** A dimensional-warp step for whichever body the pane is: the
|
/** A dimensional-warp step for whichever body the pane is: the
|
||||||
* wizard (null) or the ridden creature (its id). */
|
* wizard (null) or the ridden creature (its id). */
|
||||||
onWarpStep?: ((creatureId: string | null) => void) | null;
|
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();
|
} = $props();
|
||||||
|
|
||||||
const povId = $derived(view.you);
|
const povId = $derived(view.you);
|
||||||
@@ -427,36 +424,22 @@
|
|||||||
|
|
||||||
{#if me}
|
{#if me}
|
||||||
<div class="live-fp">
|
<div class="live-fp">
|
||||||
<FirstPerson {view} povId={cutawayShot ? "" : (possessedId ?? povId)}
|
<!-- The bezel: instruments frame the windshield instead of covering
|
||||||
x={cam.x} y={cam.y} facing={cam.facing} width={960} height={400}
|
it — every canvas pixel stays target glass. -->
|
||||||
fx={fpFx} posOverride={actorPos}
|
<div class="bezel-top">
|
||||||
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}
|
|
||||||
{#if possessed}
|
{#if possessed}
|
||||||
<div class="ride-banner">
|
|
||||||
<span class="ride-label">riding the {possessed.kind.replace(/-/g, " ")}</span>
|
<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>
|
<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>
|
<button class="stamp tiny" onclick={dismount}>back to your eyes</button>
|
||||||
</div>
|
|
||||||
{:else if rideable.length > 0 && (onCreatureMove || onCreatureAttack)}
|
{:else if rideable.length > 0 && (onCreatureMove || onCreatureAttack)}
|
||||||
<div class="ride-row">
|
|
||||||
{#each rideable as c (c.id)}
|
{#each rideable as c (c.id)}
|
||||||
<button class="stamp tiny" onclick={() => { possessedId = c.id; manualUntil = performance.now() + 60000; }}>
|
<button class="stamp tiny" onclick={() => { possessedId = c.id; manualUntil = performance.now() + 60000; }}>
|
||||||
👁 ride the {c.kind.replace(/-/g, " ")}
|
👁 ride the {c.kind.replace(/-/g, " ")}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
<span class="bezel-spacer"></span>
|
||||||
{#if withYou.length > 0}
|
{#if withYou.length > 0}
|
||||||
<div class="with-tray">
|
|
||||||
<span class="feet-label">sharing your square</span>
|
<span class="feet-label">sharing your square</span>
|
||||||
{#each withYou as body (body.id)}
|
{#each withYou as body (body.id)}
|
||||||
<button class="feet-item" title={body.label}
|
<button class="feet-item" title={body.label}
|
||||||
@@ -464,10 +447,23 @@
|
|||||||
<img src={body.art} alt={body.label} />
|
<img src={body.art} alt={body.label} />
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
{/if}
|
||||||
|
<button class="live-fp-hide" onclick={onhide} title="hide (re-enable in preferences)">✕</button>
|
||||||
</div>
|
</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}
|
||||||
{#if canStride && ((!possessed && atFeet.length > 0) || underfoot.hazard || underfoot.warpHere)}
|
{#if canStride && ((!possessed && atFeet.length > 0) || underfoot.hazard || underfoot.warpHere)}
|
||||||
<div class="feet-tray">
|
|
||||||
<span class="feet-label">at your feet</span>
|
<span class="feet-label">at your feet</span>
|
||||||
{#if !possessed && onpickup}
|
{#if !possessed && onpickup}
|
||||||
{#each atFeet as item (item.id)}
|
{#each atFeet as item (item.id)}
|
||||||
@@ -487,74 +483,63 @@
|
|||||||
{#if underfoot.warpHere && onWarpStep}
|
{#if underfoot.warpHere && onWarpStep}
|
||||||
<button class="stamp tiny" onclick={() => onWarpStep?.(possessedId)}>⇋ step through the warp</button>
|
<button class="stamp tiny" onclick={() => onWarpStep?.(possessedId)}>⇋ step through the warp</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
<span class="bezel-spacer"></span>
|
||||||
|
<span class="live-fp-hint">← → turn · ↑ ↓ walk</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.live-fp {
|
.live-fp {
|
||||||
position: relative;
|
|
||||||
margin-bottom: 0.6rem;
|
margin-bottom: 0.6rem;
|
||||||
|
border: 1px solid #3a3428;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #14121a;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.live-fp-hide {
|
.bezel-top, .bezel-bottom {
|
||||||
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%);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
background: rgba(13, 12, 18, 0.78);
|
padding: 4px 8px;
|
||||||
border: 1px solid #3a3428;
|
min-height: 34px;
|
||||||
border-radius: 6px;
|
}
|
||||||
padding: 4px 10px;
|
.bezel-top { border-bottom: 1px solid #2a2620; }
|
||||||
z-index: 3;
|
.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 {
|
.ride-label {
|
||||||
font-family: "Oswald", sans-serif;
|
font-family: "Oswald", sans-serif;
|
||||||
@@ -564,34 +549,6 @@
|
|||||||
color: #c9a72a;
|
color: #c9a72a;
|
||||||
}
|
}
|
||||||
.ride-stats { font-size: 0.7rem; color: #8d8672; }
|
.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 {
|
.feet-label {
|
||||||
font-family: "Oswald", sans-serif;
|
font-family: "Oswald", sans-serif;
|
||||||
font-size: 0.6rem;
|
font-size: 0.6rem;
|
||||||
@@ -608,12 +565,7 @@
|
|||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
.feet-item:hover { border-color: #c9a72a; }
|
.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-fallback { font-size: 0.65rem; color: #d8d2c0; padding: 0 4px; }
|
||||||
.feet-hazard { font-size: 0.7rem; color: #d98a4a; white-space: nowrap; }
|
.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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user