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