Round two of the outside eye: contrast, scale, and travel

The whiff darkens to slate and sweeps longer; the portal-cell opens
a dark void behind its collapsing rings — an opening in space, not a
selected square; the tacks grow half again and recoil harder; the
streak's reveal slows so the travel reads even in a sampled frame;
dust lifts its contrast a step without turning heavy. The sector
ghosts stay board-machinery on purpose — the reviewer allowed it,
and the maze grinding IS an operation, not a spell.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-17 10:34:02 -04:00
co-authored by Claude Fable 5
parent bcaa06251a
commit e713d69582
5 changed files with 44 additions and 25 deletions
+3 -3
View File
@@ -32,8 +32,8 @@
<defs>
<radialGradient id={`dust-soft-${uid}`}>
<stop offset="0" stop-color="#a5977a" stop-opacity="0.95" />
<stop offset="0.75" stop-color="#8d8266" stop-opacity="0.55" />
<stop offset="0" stop-color="#96876a" stop-opacity="1" />
<stop offset="0.75" stop-color="#7d7358" stop-opacity="0.7" />
<stop offset="1" stop-color="#8d8266" stop-opacity="0" />
</radialGradient>
</defs>
@@ -47,7 +47,7 @@
.puff.back { animation-name: blow-b; }
.puff.late { animation-delay: 0.08s; }
.puff.later { animation-delay: 0.16s; }
.grit circle { fill: #5c523e; animation: grit-fall 0.6s ease-in forwards; }
.grit circle { fill: #4a4232; animation: grit-fall 0.6s ease-in forwards; }
@keyframes blow-a {
0% { opacity: 1; transform: translate(0, 0) scale(0.8); }
100% { opacity: 0; transform: translate(var(--ax), var(--ay)) scale(2); }
+19
View File
@@ -2,6 +2,7 @@
import type { FxOf } from "../fx";
import { CELL } from "./geom";
let { fx }: { fx: FxOf<"portal" | "portal-cell"> } = $props();
const uid = $props.id();
</script>
{#if fx.kind === "portal"}
@@ -12,6 +13,17 @@
<line {x1} {y1} {x2} {y2} class="curtain glow" />
<line {x1} {y1} {x2} {y2} class="curtain" pathLength="100" />
{:else}
<defs>
<radialGradient id={`portal-void-${uid}`}>
<stop offset="0" stop-color="#0d2430" stop-opacity="0.85" />
<stop offset="0.6" stop-color="#12414a" stop-opacity="0.5" />
<stop offset="1" stop-color="#9be3e0" stop-opacity="0.1" />
</radialGradient>
</defs>
<rect x={fx.at.x * CELL + 5} y={fx.at.y * CELL + 5}
width={CELL - 10} height={CELL - 10} rx="8"
fill={`url(#portal-void-${uid})`} class="void"
style={`transform-origin: ${fx.at.x * CELL + CELL / 2}px ${fx.at.y * CELL + CELL / 2}px`} />
<rect x={fx.at.x * CELL + 3} y={fx.at.y * CELL + 3}
width={CELL - 6} height={CELL - 6} rx="6" class="veil outer"
style={`transform-origin: ${fx.at.x * CELL + CELL / 2}px ${fx.at.y * CELL + CELL / 2}px`} />
@@ -55,6 +67,7 @@
stroke-dasharray: 7 5;
filter: drop-shadow(0 0 4px rgba(155, 227, 224, 0.8));
}
.void { animation: void-open 0.9s ease-out forwards; }
.veil.outer { stroke-width: 2.5; animation: veil-in 0.9s ease-in forwards; }
.veil.inner {
stroke: rgba(180, 138, 224, 0.8);
@@ -62,6 +75,12 @@
animation: veil-in 0.9s ease-in 0.2s forwards;
opacity: 0;
}
@keyframes void-open {
0% { opacity: 0; transform: scale(0.4); }
30% { opacity: 1; transform: scale(1); }
75% { opacity: 0.9; transform: scale(0.92); }
100% { opacity: 0; transform: scale(0.5); }
}
@keyframes veil-in {
0% { opacity: 0; transform: scale(1.25); }
25% { opacity: 1; }
+4 -4
View File
@@ -11,7 +11,7 @@
<path {d} pathLength="100" class="trail wide" />
<path {d} pathLength="100" class="trail bright" />
<circle r="4.5" class="head" cx="0" cy="0">
<animateMotion dur="0.26s" fill="freeze" path={d} calcMode="spline"
<animateMotion dur="0.38s" fill="freeze" path={d} calcMode="spline"
keySplines="0.3 0 0.4 1" keyTimes="0;1" keyPoints="0;1" />
</circle>
@@ -20,18 +20,18 @@
fill: none;
stroke-linecap: round;
stroke-dasharray: 100 100;
animation: draw-collapse 0.55s ease-out forwards;
animation: draw-collapse 0.68s ease-out forwards;
}
.trail.wide { stroke: rgba(233, 225, 203, 0.4); stroke-width: 7; }
.trail.bright { stroke: rgba(255, 253, 240, 0.9); stroke-width: 3; }
.head {
fill: #fffdf0;
animation: head-life 0.5s ease-out forwards;
animation: head-life 0.6s ease-out forwards;
}
/* Draw from source to head, then the tail chases it into the destination. */
@keyframes draw-collapse {
0% { stroke-dashoffset: 100; opacity: 0.95; }
47% { stroke-dashoffset: 0; opacity: 0.95; }
56% { stroke-dashoffset: 0; opacity: 0.95; }
100% { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes head-life {
+7 -7
View File
@@ -14,8 +14,8 @@
{#each TACKS as t (t.cls)}
<g transform={`translate(${c.x + t.x} ${c.y + t.y}) rotate(${t.rot})`}>
<g class={`tack ${t.cls}`}>
<circle cx="0" cy="2.5" r="2.6" class="head" />
<path d="M -1.4 1 L 0 -5 L 1.4 1 z" class="spike" />
<circle cx="0" cy="3.8" r="3.9" class="head" />
<path d="M -2.1 1.5 L 0 -7.5 L 2.1 1.5 z" class="spike" />
</g>
</g>
{/each}
@@ -34,10 +34,10 @@
.tack.late { animation-delay: 0.1s; }
.tack.later { animation-delay: 0.2s; }
@keyframes tack-hop {
0% { opacity: 0; transform: translateY(3px); }
25% { opacity: 1; transform: translateY(-6px); }
55% { transform: translateY(-1px); }
75% { transform: translateY(-4px); }
100% { opacity: 0; transform: translateY(-1px); }
0% { opacity: 0; transform: translateY(4px); }
22% { opacity: 1; transform: translateY(-10px); }
50% { transform: translateY(-2px); }
72% { transform: translateY(-7px); }
100% { opacity: 0; transform: translateY(-2px); }
}
</style>
+11 -11
View File
@@ -6,27 +6,27 @@
</script>
<g class="whiff" style={`transform-origin: ${c.x}px ${c.y}px`}>
<path d={`M ${c.x - 20} ${c.y - 8} q 14 -5 40 -3`} pathLength="100" class="streak a" />
<path d={`M ${c.x - 22} ${c.y + 1} q 16 3 42 0`} pathLength="100" class="streak b" />
<path d={`M ${c.x - 18} ${c.y + 9} q 12 5 38 4`} pathLength="100" class="streak c" />
<path d={`M ${c.x - 30} ${c.y - 9} q 20 -7 58 -4`} pathLength="100" class="streak a" />
<path d={`M ${c.x - 32} ${c.y + 1} q 24 4 62 0`} pathLength="100" class="streak b" />
<path d={`M ${c.x - 28} ${c.y + 10} q 18 7 56 5`} pathLength="100" class="streak c" />
</g>
<style>
.streak {
fill: none;
stroke: rgba(190, 200, 210, 0.85);
stroke-width: 2.4;
stroke: rgba(110, 125, 140, 0.9);
stroke-width: 3;
stroke-linecap: round;
stroke-dasharray: 30 100;
stroke-dashoffset: 30;
stroke-dasharray: 38 100;
stroke-dashoffset: 38;
animation: sweep 0.5s ease-out forwards;
}
.streak.b { animation-delay: 0.06s; stroke-width: 3; }
.streak.c { animation-delay: 0.12s; stroke-width: 2; }
.streak.b { animation-delay: 0.06s; stroke-width: 3.6; }
.streak.c { animation-delay: 0.12s; stroke-width: 2.6; }
.whiff { animation: wobble 0.55s ease-in-out forwards; }
@keyframes sweep {
0% { stroke-dashoffset: 30; opacity: 0; }
20% { opacity: 0.9; }
0% { stroke-dashoffset: 38; opacity: 0; }
18% { opacity: 0.95; }
100% { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes wobble {