FirewallEdge (both orientations), the illusion shimmer, and the sight trace (direct and through-a-warp) join the /?fx gallery — and to keep the display honest, the shimmer and sight trace were extracted from Board.svelte's inline markup into IllusionShimmer.svelte and SightTraceOverlay.svelte, so the workshop mounts the very components the live board does. No copies to drift. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc
55 lines
1.7 KiB
Svelte
55 lines
1.7 KiB
Svelte
<script lang="ts">
|
|
// The sight line an attack traveled — straight when direct, leg by leg
|
|
// through both warp mouths (with pulsing rings) when the maze wrapped.
|
|
import type { SightTrace } from "@wizwar/engine";
|
|
let { from, to, trace }: {
|
|
from: { x: number; y: number };
|
|
to: { x: number; y: number };
|
|
trace: SightTrace;
|
|
} = $props();
|
|
const CELL = 48;
|
|
const A = $derived({ x: from.x * CELL + CELL / 2, y: from.y * CELL + CELL * 0.36 });
|
|
const B = $derived({ x: to.x * CELL + CELL / 2, y: to.y * CELL + CELL * 0.36 });
|
|
</script>
|
|
|
|
<g class="sight-layer" aria-hidden="true">
|
|
{#if trace.kind === "direct"}
|
|
<line x1={A.x} y1={A.y} x2={B.x} y2={B.y} class="sight-line" />
|
|
{:else}
|
|
{@const entry = { x: trace.entry.x * CELL, y: trace.entry.y * CELL }}
|
|
{@const exit = { x: trace.exit.x * CELL, y: trace.exit.y * CELL }}
|
|
<line x1={A.x} y1={A.y} x2={entry.x} y2={entry.y} class="sight-line" />
|
|
<line x1={exit.x} y1={exit.y} x2={B.x} y2={B.y} class="sight-line" />
|
|
<circle cx={entry.x} cy={entry.y} r={7} class="sight-mouth" />
|
|
<circle cx={exit.x} cy={exit.y} r={7} class="sight-mouth" />
|
|
{/if}
|
|
</g>
|
|
|
|
<style>
|
|
.sight-layer { pointer-events: none; }
|
|
.sight-line {
|
|
stroke: #c9a72a;
|
|
stroke-width: 2.5;
|
|
stroke-linecap: round;
|
|
stroke-dasharray: 7 6;
|
|
opacity: 0.85;
|
|
animation: sight-march 0.8s linear infinite;
|
|
}
|
|
.sight-mouth {
|
|
fill: none;
|
|
stroke: #c9a72a;
|
|
stroke-width: 2;
|
|
animation: sight-pulse 1.6s ease-in-out infinite;
|
|
}
|
|
@keyframes sight-march {
|
|
to { stroke-dashoffset: -13; }
|
|
}
|
|
@keyframes sight-pulse {
|
|
0%, 100% { opacity: 0.9; }
|
|
50% { opacity: 0.35; }
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.sight-line, .sight-mouth { animation: none; }
|
|
}
|
|
</style>
|