Warp travel shimmers at the threshold, not around the traveler
Green rings at the traveler's squares become a portal curtain: the opening LINE itself shimmers on both boards — resolved from the warp table via the step's direction — in iridescent cyan over a soft violet breath, dashes drifting like light through a veil. Dimensional warp tokens, whose mouths are whole squares, wrap in the same veil around the square instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
68b0da6301
commit
a82b25b0e8
@@ -662,10 +662,16 @@
|
|||||||
{@const c = center(fx.at)}
|
{@const c = center(fx.at)}
|
||||||
<rect x={c.x - 9} y={c.y - 13} width="18" height="26" rx="2" class="fx-absorb"
|
<rect x={c.x - 9} y={c.y - 13} width="18" height="26" rx="2" class="fx-absorb"
|
||||||
style={`transform-origin: ${c.x}px ${c.y}px`} />
|
style={`transform-origin: ${c.x}px ${c.y}px`} />
|
||||||
{:else if fx.kind === "warp-glow"}
|
{:else if fx.kind === "portal"}
|
||||||
{@const c = center(fx.at)}
|
{@const x1 = fx.side === "E" ? (fx.cell.x + 1) * CELL : fx.cell.x * CELL}
|
||||||
<circle cx={c.x} cy={c.y} r="8" class="fx-warpglow" />
|
{@const y1 = fx.side === "S" ? (fx.cell.y + 1) * CELL : fx.cell.y * CELL}
|
||||||
<circle cx={c.x} cy={c.y} r="8" class="fx-warpglow late" />
|
{@const x2 = fx.side === "W" ? fx.cell.x * CELL : (fx.cell.x + 1) * CELL}
|
||||||
|
{@const y2 = fx.side === "N" ? fx.cell.y * CELL : (fx.cell.y + 1) * CELL}
|
||||||
|
<line {x1} {y1} {x2} {y2} class="fx-portal glow" />
|
||||||
|
<line {x1} {y1} {x2} {y2} class="fx-portal" />
|
||||||
|
{:else if fx.kind === "portal-cell"}
|
||||||
|
<rect x={fx.at.x * CELL + 3} y={fx.at.y * CELL + 3}
|
||||||
|
width={CELL - 6} height={CELL - 6} rx="6" class="fx-portal-veil" />
|
||||||
{:else if fx.kind === "streak"}
|
{:else if fx.kind === "streak"}
|
||||||
{@const a = center(fx.from)}
|
{@const a = center(fx.from)}
|
||||||
{@const b = center(fx.to)}
|
{@const b = center(fx.to)}
|
||||||
@@ -989,16 +995,40 @@
|
|||||||
stroke-width: 1.5;
|
stroke-width: 1.5;
|
||||||
animation: fx-swallow 0.65s ease-in forwards;
|
animation: fx-swallow 0.65s ease-in forwards;
|
||||||
}
|
}
|
||||||
.fx-warpglow {
|
.fx-portal {
|
||||||
transform-box: fill-box;
|
stroke: #9be3e0;
|
||||||
transform-origin: center;
|
|
||||||
fill: none;
|
|
||||||
stroke: #2e7d32;
|
|
||||||
stroke-width: 3;
|
stroke-width: 3;
|
||||||
filter: drop-shadow(0 0 5px rgba(46, 125, 50, 0.8));
|
stroke-dasharray: 6 5;
|
||||||
animation: fx-ring 0.55s ease-out forwards;
|
stroke-linecap: round;
|
||||||
|
filter: drop-shadow(0 0 4px rgba(155, 227, 224, 0.9));
|
||||||
|
animation: fx-portal-shimmer 0.95s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
.fx-portal.glow {
|
||||||
|
stroke: rgba(180, 138, 224, 0.5);
|
||||||
|
stroke-width: 9;
|
||||||
|
stroke-dasharray: none;
|
||||||
|
filter: blur(2px);
|
||||||
|
animation: fx-portal-breathe 0.95s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
.fx-portal-veil {
|
||||||
|
fill: rgba(155, 227, 224, 0.12);
|
||||||
|
stroke: #9be3e0;
|
||||||
|
stroke-width: 2.5;
|
||||||
|
stroke-dasharray: 7 5;
|
||||||
|
filter: drop-shadow(0 0 4px rgba(155, 227, 224, 0.8));
|
||||||
|
animation: fx-portal-shimmer 0.95s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
@keyframes fx-portal-shimmer {
|
||||||
|
0% { opacity: 0; stroke-dashoffset: 0; }
|
||||||
|
20% { opacity: 1; }
|
||||||
|
80% { opacity: 0.85; }
|
||||||
|
100% { opacity: 0; stroke-dashoffset: 34; }
|
||||||
|
}
|
||||||
|
@keyframes fx-portal-breathe {
|
||||||
|
0% { opacity: 0; }
|
||||||
|
30% { opacity: 0.8; }
|
||||||
|
100% { opacity: 0; }
|
||||||
}
|
}
|
||||||
.fx-warpglow.late { stroke: #7ac47e; animation-delay: 0.15s; opacity: 0; }
|
|
||||||
.fx-streak {
|
.fx-streak {
|
||||||
stroke: rgba(233, 225, 203, 0.85);
|
stroke: rgba(233, 225, 203, 0.85);
|
||||||
stroke-width: 5;
|
stroke-width: 5;
|
||||||
|
|||||||
+16
-5
@@ -10,8 +10,9 @@ type Side = "N" | "E" | "S" | "W";
|
|||||||
type FxShape =
|
type FxShape =
|
||||||
| { kind: "fireball" | "bolt" | "waterbolt" | "streak"; from: Cell; to: Cell }
|
| { kind: "fireball" | "bolt" | "waterbolt" | "streak"; from: Cell; to: Cell }
|
||||||
| { kind: "burst" | "splash" | "shimmer" | "shield" | "sparkle" | "whiff" | "hit"
|
| { kind: "burst" | "splash" | "shimmer" | "shield" | "sparkle" | "whiff" | "hit"
|
||||||
| "pow" | "claw" | "absorb" | "warp-glow" | "soul" | "fireworks" | "chaos-swirl"
|
| "pow" | "claw" | "absorb" | "portal-cell" | "soul" | "fireworks" | "chaos-swirl"
|
||||||
| "pit-fall" | "ooze-slip" | "tacks-ow" | "thorn-snap" | "slime-stuck" | "dust-puff"; at: Cell }
|
| "pit-fall" | "ooze-slip" | "tacks-ow" | "thorn-snap" | "slime-stuck" | "dust-puff"; at: Cell }
|
||||||
|
| { kind: "portal"; cell: Cell; side: Side }
|
||||||
| { kind: "sector-spin"; origin: Cell; clockwise: boolean }
|
| { kind: "sector-spin"; origin: Cell; clockwise: boolean }
|
||||||
| { kind: "sector-slide"; from: Cell; to: Cell }
|
| { kind: "sector-slide"; from: Cell; to: Cell }
|
||||||
| { kind: "edge-dust"; cell: Cell; side: Side };
|
| { kind: "edge-dust"; cell: Cell; side: Side };
|
||||||
@@ -24,6 +25,7 @@ export function fxTtl(kind: BoardFx["kind"]): number {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "fireball": case "waterbolt": return 700;
|
case "fireball": case "waterbolt": return 700;
|
||||||
case "bolt": case "streak": return 600;
|
case "bolt": case "streak": return 600;
|
||||||
|
case "portal": case "portal-cell":
|
||||||
case "soul": case "fireworks": case "chaos-swirl":
|
case "soul": case "fireworks": case "chaos-swirl":
|
||||||
case "sector-spin": case "sector-slide": return 1500;
|
case "sector-spin": case "sector-slide": return 1500;
|
||||||
default: return 900;
|
default: return 900;
|
||||||
@@ -131,14 +133,23 @@ export function fxForEvents(
|
|||||||
break;
|
break;
|
||||||
case "moved":
|
case "moved":
|
||||||
if (e.via === "warp") {
|
if (e.via === "warp") {
|
||||||
push({ kind: "warp-glow", at: e.from });
|
// The mouths are edges: shimmer the opening lines on both boards.
|
||||||
push({ kind: "warp-glow", at: e.to }, 150);
|
const w = view.board.warps.find((w) =>
|
||||||
|
w.from.cell.x === e.from.x && w.from.cell.y === e.from.y && w.from.side === e.direction);
|
||||||
|
if (w) {
|
||||||
|
push({ kind: "portal", cell: w.from.cell, side: w.from.side });
|
||||||
|
push({ kind: "portal", cell: w.to.cell, side: w.to.side }, 150);
|
||||||
|
} else {
|
||||||
|
push({ kind: "portal-cell", at: e.from });
|
||||||
|
push({ kind: "portal-cell", at: e.to }, 150);
|
||||||
|
}
|
||||||
beat++;
|
beat++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "warpStepped":
|
case "warpStepped":
|
||||||
push({ kind: "warp-glow", at: e.from });
|
// Dimensional warp tokens fill their squares; the veil wraps them.
|
||||||
push({ kind: "warp-glow", at: e.to }, 150);
|
push({ kind: "portal-cell", at: e.from });
|
||||||
|
push({ kind: "portal-cell", at: e.to }, 150);
|
||||||
beat++;
|
beat++;
|
||||||
break;
|
break;
|
||||||
case "wallCreated":
|
case "wallCreated":
|
||||||
|
|||||||
Reference in New Issue
Block a user