fix: planchette idle wander was confined to a tiny central zone
The wander amplitude (w*0.16, h*0.14) was a fixed fraction of the canvas with no relationship to the outer letter ring's actual radius (min(w*0.4, h*0.52) — set in computeLayout's arc() calls), so idle drift could never reach anywhere near the outer letters (A/M/N/Z, the arc tops). Tied the wander radius directly to that same ring radius/squash factor instead, so it genuinely roams the whole board. Also added an ambient "letter brushing" effect: while idly wandering (not actively spelling a real reply), a letter the planchette drifts near gets a faint glow — visibly dimmer than the bright hover/dwell glow used for real spelled letters, so a passing brush never reads as the spirit actually saying something. Purely cosmetic, no game-state changes.
This commit is contained in:
@@ -162,18 +162,42 @@ export function PlanchetteBoard({ machine, hue = 150 }: PlanchetteBoardProps) {
|
||||
lastKnock = knockKey
|
||||
|
||||
// --- target: spelled letter, else idle wander ---
|
||||
// Wander radius is tied directly to the outer letter ring's own
|
||||
// radius/squash (computeLayout's `arc('ABCDEFGHIJKLM', ...)` call) so
|
||||
// the drift genuinely reaches the board's edges — not a fixed
|
||||
// fraction of the canvas that happens to undershoot the ring.
|
||||
const outerRingRadius = Math.min(w * 0.4, h * 0.52)
|
||||
let target: Point | null = null
|
||||
if (snap.current) target = letterTarget(snap.current)
|
||||
if (!target) {
|
||||
target = {
|
||||
x: layout.center.x + wander(t, 1.3) * w * 0.16,
|
||||
y: layout.center.y + wander(t * 0.83, 4.1) * h * 0.14,
|
||||
x: layout.center.x + wander(t, 1.3) * outerRingRadius * 0.94,
|
||||
y: layout.center.y + wander(t * 0.83, 4.1) * outerRingRadius * 0.72 * 0.94,
|
||||
}
|
||||
}
|
||||
const speed = snap.current ? 0.16 : 0.03
|
||||
pos.x += (target.x - pos.x) * speed
|
||||
pos.y += (target.y - pos.y) * speed
|
||||
|
||||
// --- ambient letter-brushing: during idle wander (not actively
|
||||
// spelling), a letter the planchette happens to drift near gets a
|
||||
// soft "passively brushed" glow — distinct from the brighter
|
||||
// hover/dwell glow used for real spelling, so it never reads as the
|
||||
// spirit actually saying something. Purely cosmetic, no game state.
|
||||
let brushed: string | null = null
|
||||
if (!snap.current) {
|
||||
let best = Infinity
|
||||
for (const [ch, p] of layout.letters) {
|
||||
const d = Math.hypot(p.x - pos.x, p.y - pos.y)
|
||||
if (d < best) {
|
||||
best = d
|
||||
brushed = ch
|
||||
}
|
||||
}
|
||||
const BRUSH_RADIUS = Math.max(26, h * 0.06)
|
||||
if (best > BRUSH_RADIUS) brushed = null
|
||||
}
|
||||
|
||||
// --- smoke trail ---
|
||||
const moving = Math.abs(target.x - pos.x) + Math.abs(target.y - pos.y) > 2
|
||||
if ((moving || snap.current) && smoke.length < 220) {
|
||||
@@ -233,15 +257,15 @@ export function PlanchetteBoard({ machine, hue = 150 }: PlanchetteBoardProps) {
|
||||
ctx.fillText('G O O D B Y E', layout.goodbye.x, layout.goodbye.y)
|
||||
|
||||
// Letters + numbers.
|
||||
const hovered = snap.current
|
||||
const hovered = snap.current ?? brushed
|
||||
const dwelling = snap.phase === 'dwelling' ? snap.current : null
|
||||
ctx.font = `600 ${Math.max(13, h * 0.04)}px Cinzel, Georgia, serif`
|
||||
for (const [ch, p] of layout.letters) {
|
||||
drawGlyph(ctx, ch, p, hovered === ch, dwelling === ch, accent, hueDeg)
|
||||
drawGlyph(ctx, ch, p, hovered === ch, dwelling === ch, accent, hueDeg, ch === brushed)
|
||||
}
|
||||
ctx.font = `600 ${Math.max(12, h * 0.034)}px "IBM Plex Mono", monospace`
|
||||
for (const [ch, p] of layout.numbers) {
|
||||
drawGlyph(ctx, ch, p, hovered === ch, dwelling === ch, accent, hueDeg)
|
||||
drawGlyph(ctx, ch, p, hovered === ch, dwelling === ch, accent, hueDeg, false)
|
||||
}
|
||||
|
||||
// Smoke (additive).
|
||||
@@ -325,6 +349,7 @@ function drawGlyph(
|
||||
dwell: boolean,
|
||||
accent: string,
|
||||
hueDeg: number,
|
||||
soft: boolean,
|
||||
): void {
|
||||
if (hovered) {
|
||||
ctx.save()
|
||||
@@ -335,13 +360,18 @@ function drawGlyph(
|
||||
ctx.translate(-p.x, -p.y)
|
||||
}
|
||||
ctx.shadowColor = accent
|
||||
ctx.shadowBlur = dwell ? 28 : 16
|
||||
// Ambient brush (idle drift passing near, `soft`) gets a faint glow —
|
||||
// deliberately dimmer than a real spelled letter, so it never reads as
|
||||
// the spirit actually saying something, just the planchette stirring.
|
||||
ctx.shadowBlur = dwell ? 28 : soft ? 9 : 16
|
||||
ctx.fillStyle = accent
|
||||
ctx.globalAlpha = soft ? 0.6 : 1
|
||||
ctx.fillText(ch, p.x, p.y)
|
||||
ctx.globalAlpha = 1
|
||||
ctx.restore()
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = `hsla(${hueDeg} 90% 75% / ${dwell ? 0.85 : 0.5})`
|
||||
ctx.arc(p.x, p.y, dwell ? 19 : 15, 0, Math.PI * 2)
|
||||
ctx.strokeStyle = `hsla(${hueDeg} 90% 75% / ${dwell ? 0.85 : soft ? 0.28 : 0.5})`
|
||||
ctx.arc(p.x, p.y, dwell ? 19 : soft ? 13 : 15, 0, Math.PI * 2)
|
||||
ctx.stroke()
|
||||
} else {
|
||||
ctx.fillStyle = `hsla(${hueDeg} 40% 82% / 0.75)`
|
||||
|
||||
Reference in New Issue
Block a user