The fireball sets the bar, and the rest of the catalog rises to it
Eric's fireball lands as the house style — layered construction, gradient core, glow halo, a directional tail riding rotate="auto" — with its defs scoped per instance via $props.id(), so simultaneous fireballs cannot steal each other's gradients. The rest of the catalog is rebuilt in its image: the waterbolt becomes its cold sibling (spray tail, no turbulence — one heavy filter stack is enough); lightning grows a glowing core and branching forks; the burst blooms through a fire gradient with a shockwave and flying embers; the splash throws a crown of droplets; the shield raises a gradient dome with a rim strike; the pow star flashes white and radiates speed dashes; claws leave tapered gashes; the absorbed card spirals in along a pull ring; the soul is a proper little ghost with eyes, swaying as it rises; fireworks ride colored trails; Chaos catches card slips in its vortex; the pit opens a gradient hole that swallows a faller; ooze splats and flings blobs; the thornbush sheds leaves; slime closes over and pops bubbles; masonry dust goes soft with falling grit. Whiff, tacks, portal, and the sector ghosts keep their simpler forms on purpose — restraint is part of the style. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
fe0318b99b
commit
308f1b6ff9
@@ -1,22 +1,46 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"absorb"> } = $props();
|
||||
const uid = $props.id();
|
||||
const c = $derived(center(fx.at));
|
||||
</script>
|
||||
|
||||
<rect x={c.x - 9} y={c.y - 13} width="18" height="26" rx="2" class="absorb"
|
||||
<defs>
|
||||
<linearGradient id={`absorb-shine-${uid}`} x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#fffdf4" />
|
||||
<stop offset="0.5" stop-color="#f6f0df" />
|
||||
<stop offset="1" stop-color="#d8ccb0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<circle cx={c.x} cy={c.y} r="14" class="pull" />
|
||||
<rect x={c.x - 9} y={c.y - 13} width="18" height="26" rx="2"
|
||||
fill={`url(#absorb-shine-${uid})`} class="card"
|
||||
style={`transform-origin: ${c.x}px ${c.y}px`} />
|
||||
|
||||
<style>
|
||||
.absorb {
|
||||
fill: #f6f0df;
|
||||
.card {
|
||||
stroke: #43331f;
|
||||
stroke-width: 1.5;
|
||||
animation: swallow 0.65s ease-in forwards;
|
||||
}
|
||||
.pull {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
fill: none;
|
||||
stroke: rgba(180, 138, 224, 0.6);
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: 5 6;
|
||||
animation: pull-in 0.6s ease-in forwards;
|
||||
}
|
||||
@keyframes swallow {
|
||||
0% { opacity: 0.95; transform: scale(1) rotate(0deg); }
|
||||
100% { opacity: 0; transform: scale(0.05) rotate(50deg); }
|
||||
100% { opacity: 0; transform: scale(0.05) rotate(140deg); }
|
||||
}
|
||||
@keyframes pull-in {
|
||||
0% { opacity: 0.8; transform: scale(1.6) rotate(0deg); }
|
||||
100% { opacity: 0; transform: scale(0.2) rotate(-90deg); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,33 +1,69 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"bolt"> } = $props();
|
||||
/** A fresh jagged path every strike. */
|
||||
const points = $derived.by(() => {
|
||||
const uid = $props.id();
|
||||
|
||||
/** A fresh jagged path every strike, plus two forks off mid-joints. */
|
||||
const geom = $derived.by(() => {
|
||||
const a = center(fx.from), b = center(fx.to);
|
||||
const segs = 6;
|
||||
const pts: string[] = [`${a.x},${a.y}`];
|
||||
const joints: { x: number; y: number }[] = [a];
|
||||
for (let i = 1; i < segs; i++) {
|
||||
const t = i / segs;
|
||||
pts.push(`${(a.x + (b.x - a.x) * t + (Math.random() - 0.5) * 16).toFixed(1)},${(a.y + (b.y - a.y) * t + (Math.random() - 0.5) * 16).toFixed(1)}`);
|
||||
joints.push({
|
||||
x: a.x + (b.x - a.x) * t + (Math.random() - 0.5) * 16,
|
||||
y: a.y + (b.y - a.y) * t + (Math.random() - 0.5) * 16,
|
||||
});
|
||||
}
|
||||
pts.push(`${b.x},${b.y}`);
|
||||
return pts.join(" ");
|
||||
joints.push(b);
|
||||
const points = joints.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ");
|
||||
const fork = (j: { x: number; y: number }) => {
|
||||
const dx = (Math.random() - 0.5) * 26, dy = (Math.random() - 0.5) * 26;
|
||||
return `M ${j.x} ${j.y} l ${dx / 2} ${dy / 2} l ${dx / 2 + (Math.random() - 0.5) * 8} ${dy / 2}`;
|
||||
};
|
||||
return { points, forks: [fork(joints[2]!), fork(joints[4]!)] };
|
||||
});
|
||||
</script>
|
||||
|
||||
<polyline {points} class="bolt" />
|
||||
<defs>
|
||||
<filter id={`bolt-glow-${uid}`} x="-60%" y="-60%" width="220%" height="220%">
|
||||
<feGaussianBlur stdDeviation="4" result="blur" />
|
||||
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<g class="bolt" filter={`url(#bolt-glow-${uid})`}>
|
||||
<polyline points={geom.points} class="wide" />
|
||||
{#each geom.forks as d, i (i)}
|
||||
<path {d} class="fork" />
|
||||
{/each}
|
||||
<polyline points={geom.points} class="core" />
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.bolt {
|
||||
.bolt .wide {
|
||||
fill: none;
|
||||
stroke: #ffe94d;
|
||||
stroke-width: 3;
|
||||
stroke-width: 5;
|
||||
stroke-linejoin: round;
|
||||
filter: drop-shadow(0 0 7px rgba(255, 233, 77, 0.95));
|
||||
animation: flicker 0.5s steps(2, jump-none) forwards;
|
||||
opacity: 0.55;
|
||||
}
|
||||
@keyframes flicker {
|
||||
.bolt .fork {
|
||||
fill: none;
|
||||
stroke: #fff6b0;
|
||||
stroke-width: 1.6;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
.bolt .core {
|
||||
fill: none;
|
||||
stroke: #fffdf0;
|
||||
stroke-width: 1.8;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
.bolt { animation: bolt-flicker 0.5s steps(2, jump-none) forwards; }
|
||||
@keyframes bolt-flicker {
|
||||
0% { opacity: 0; } 15% { opacity: 1; } 40% { opacity: 0.3; }
|
||||
60% { opacity: 1; } 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
@@ -1,26 +1,64 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"burst"> } = $props();
|
||||
const uid = $props.id();
|
||||
const c = $derived(center(fx.at));
|
||||
const EMBERS = [15, 80, 145, 210, 275, 340];
|
||||
</script>
|
||||
|
||||
<circle cx={c.x} cy={c.y} r="4" class="burst" />
|
||||
<circle cx={c.x} cy={c.y} r="4" class="burst late" />
|
||||
<defs>
|
||||
<radialGradient id={`burst-fire-${uid}`}>
|
||||
<stop offset="0" stop-color="#fffde0" />
|
||||
<stop offset="0.3" stop-color="#ffd34e" />
|
||||
<stop offset="0.65" stop-color="#ff8c1a" stop-opacity="0.85" />
|
||||
<stop offset="1" stop-color="#ef3b08" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<circle cx={c.x} cy={c.y} r="16" fill={`url(#burst-fire-${uid})`} class="bloom" />
|
||||
<circle cx={c.x} cy={c.y} r="6" class="shockwave" />
|
||||
{#each EMBERS as deg (deg)}
|
||||
<circle
|
||||
cx={c.x + 13 * Math.cos((deg * Math.PI) / 180)}
|
||||
cy={c.y + 13 * Math.sin((deg * Math.PI) / 180)}
|
||||
r={deg % 2 === 0 ? 2 : 1.5}
|
||||
class="ember"
|
||||
style={`transform-origin: ${c.x}px ${c.y}px`}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<style>
|
||||
.burst {
|
||||
.bloom {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
animation: bloom 0.5s ease-out forwards;
|
||||
}
|
||||
.shockwave {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
fill: none;
|
||||
stroke: #ff8c1a;
|
||||
stroke-width: 4;
|
||||
animation: ring 0.5s ease-out 0.05s forwards;
|
||||
stroke: #ffd27a;
|
||||
stroke-width: 2.5;
|
||||
animation: shockwave 0.45s ease-out 0.08s forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
.burst.late { stroke: #ffd27a; animation-delay: 0.16s; }
|
||||
@keyframes ring {
|
||||
0% { opacity: 0.95; transform: scale(1); }
|
||||
100% { opacity: 0; transform: scale(5); }
|
||||
.ember {
|
||||
fill: #ff9d19;
|
||||
animation: ember-fly 0.55s ease-out forwards;
|
||||
}
|
||||
@keyframes bloom {
|
||||
0% { opacity: 0; transform: scale(0.2); }
|
||||
25% { opacity: 1; transform: scale(1.2); }
|
||||
100% { opacity: 0; transform: scale(2.4); }
|
||||
}
|
||||
@keyframes shockwave {
|
||||
0% { opacity: 0.9; transform: scale(1); }
|
||||
100% { opacity: 0; transform: scale(6); }
|
||||
}
|
||||
@keyframes ember-fly {
|
||||
0% { opacity: 1; transform: scale(1); }
|
||||
100% { opacity: 0; transform: scale(2.1) rotate(24deg); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,29 +1,45 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"chaos-swirl"> } = $props();
|
||||
const c = $derived(center(fx.at));
|
||||
const CARDS = [0, 72, 144, 216, 288];
|
||||
</script>
|
||||
|
||||
<g class="chaos" style={`transform-origin: ${c.x}px ${c.y}px`}>
|
||||
<circle cx={c.x} cy={c.y} r="30" />
|
||||
<circle cx={c.x} cy={c.y} r="55" class="mid" />
|
||||
<circle cx={c.x} cy={c.y} r="80" class="outer" />
|
||||
<circle cx={c.x} cy={c.y} r="30" class="ring inner" />
|
||||
<circle cx={c.x} cy={c.y} r="55" class="ring mid" />
|
||||
<circle cx={c.x} cy={c.y} r="80" class="ring outer" />
|
||||
{#each CARDS as deg (deg)}
|
||||
<rect
|
||||
x={c.x + 46 * Math.cos((deg * Math.PI) / 180) - 5}
|
||||
y={c.y + 46 * Math.sin((deg * Math.PI) / 180) - 7}
|
||||
width="10" height="14" rx="1.5"
|
||||
class="slip"
|
||||
transform={`rotate(${deg + 25} ${c.x + 46 * Math.cos((deg * Math.PI) / 180)} ${c.y + 46 * Math.sin((deg * Math.PI) / 180)})`}
|
||||
/>
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.chaos circle {
|
||||
.ring {
|
||||
fill: none;
|
||||
stroke: #b48ae0;
|
||||
stroke-width: 4;
|
||||
stroke-dasharray: 30 22;
|
||||
}
|
||||
.chaos .mid { stroke: #8a5fc0; stroke-dasharray: 44 30; }
|
||||
.chaos .outer { stroke: #e2c8ff; stroke-dasharray: 60 40; }
|
||||
.ring.inner { stroke: #b48ae0; }
|
||||
.ring.mid { stroke: #8a5fc0; stroke-dasharray: 44 30; }
|
||||
.ring.outer { stroke: #e2c8ff; stroke-dasharray: 60 40; }
|
||||
.slip {
|
||||
fill: #f6f0df;
|
||||
stroke: #43331f;
|
||||
stroke-width: 1;
|
||||
}
|
||||
.chaos { animation: chaos-spin 1.4s ease-in-out forwards; }
|
||||
@keyframes chaos-spin {
|
||||
0% { opacity: 0; transform: rotate(0deg) scale(0.6); }
|
||||
0% { opacity: 0; transform: rotate(0deg) scale(0.5); }
|
||||
20% { opacity: 1; }
|
||||
100% { opacity: 0; transform: rotate(200deg) scale(1.5); }
|
||||
100% { opacity: 0; transform: rotate(220deg) scale(1.4); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"claw"> } = $props();
|
||||
const uid = $props.id();
|
||||
const c = $derived(center(fx.at));
|
||||
</script>
|
||||
|
||||
<defs>
|
||||
<linearGradient id={`claw-cut-${uid}`} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#e0604f" />
|
||||
<stop offset="0.5" stop-color="#b3372b" />
|
||||
<stop offset="1" stop-color="#6e1a12" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<g class="claw">
|
||||
<line x1={c.x - 10} y1={c.y - 14} x2={c.x - 2} y2={c.y + 12} />
|
||||
<line x1={c.x - 2} y1={c.y - 16} x2={c.x + 6} y2={c.y + 10} />
|
||||
<line x1={c.x + 6} y1={c.y - 14} x2={c.x + 14} y2={c.y + 12} />
|
||||
<!-- Three tapered gashes: wide at entry, pointed at exit -->
|
||||
<path d={`M ${c.x - 12} ${c.y - 14} q 4 12 10 26 q -1 1 -2 0 q -7 -13 -11 -25 z`} fill={`url(#claw-cut-${uid})`} />
|
||||
<path d={`M ${c.x - 3} ${c.y - 16} q 4 12 9 26 q -1 1 -2 0 q -6 -13 -10 -25 z`} fill={`url(#claw-cut-${uid})`} />
|
||||
<path d={`M ${c.x + 6} ${c.y - 14} q 4 12 10 26 q -1 1 -2 0 q -7 -13 -11 -25 z`} fill={`url(#claw-cut-${uid})`} />
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.claw line {
|
||||
stroke: #b3372b;
|
||||
stroke-width: 3;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
.claw { animation: rake 0.55s ease-out forwards; }
|
||||
@keyframes rake {
|
||||
0% { opacity: 0; transform: translateY(-6px); }
|
||||
20% { opacity: 1; }
|
||||
0% { opacity: 0; transform: translateY(-7px); }
|
||||
18% { opacity: 1; }
|
||||
70% { opacity: 0.9; }
|
||||
100% { opacity: 0; transform: translateY(6px); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { CELL, center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"dust-puff" | "edge-dust"> } = $props();
|
||||
const uid = $props.id();
|
||||
const c = $derived.by(() => {
|
||||
if (fx.kind === "dust-puff") return center(fx.at);
|
||||
const mid = center(fx.cell);
|
||||
@@ -12,16 +14,37 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<circle cx={c.x - 6} cy={c.y} r="4" class="dust" />
|
||||
<circle cx={c.x + 5} cy={c.y - 3} r="3" class="dust late" />
|
||||
<circle cx={c.x} cy={c.y + 4} r="3.5" class="dust later" />
|
||||
<defs>
|
||||
<radialGradient id={`dust-soft-${uid}`}>
|
||||
<stop offset="0" stop-color="#b5a88e" stop-opacity="0.85" />
|
||||
<stop offset="1" stop-color="#8d8266" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<circle cx={c.x - 6} cy={c.y} r="6" fill={`url(#dust-soft-${uid})`} class="dust" />
|
||||
<circle cx={c.x + 5} cy={c.y - 3} r="5" fill={`url(#dust-soft-${uid})`} class="dust late" />
|
||||
<circle cx={c.x} cy={c.y + 4} r="5.5" fill={`url(#dust-soft-${uid})`} class="dust later" />
|
||||
<g class="grit">
|
||||
<circle cx={c.x - 4} cy={c.y - 5} r="1" />
|
||||
<circle cx={c.x + 6} cy={c.y + 2} r="1.2" />
|
||||
<circle cx={c.x + 1} cy={c.y - 2} r="0.8" />
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.dust { fill: rgba(160, 150, 130, 0.75); animation: drift 0.7s ease-out forwards; }
|
||||
.dust {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
animation: drift 0.75s ease-out forwards;
|
||||
}
|
||||
.dust.late { animation-delay: 0.09s; }
|
||||
.dust.later { animation-delay: 0.17s; }
|
||||
.grit circle { fill: #6e6450; animation: grit-fall 0.6s ease-in forwards; }
|
||||
@keyframes drift {
|
||||
0% { opacity: 0.8; transform: translateY(0) scale(1); }
|
||||
100% { opacity: 0; transform: translateY(-10px) scale(1.8); }
|
||||
0% { opacity: 0.9; transform: translateY(0) scale(1); }
|
||||
100% { opacity: 0; transform: translateY(-12px) scale(2); }
|
||||
}
|
||||
@keyframes grit-fall {
|
||||
0% { opacity: 0.9; transform: translateY(-3px); }
|
||||
100% { opacity: 0; transform: translateY(7px); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,22 +1,102 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"fireball"> } = $props();
|
||||
const uid = $props.id();
|
||||
|
||||
const a = $derived(center(fx.from));
|
||||
const b = $derived(center(fx.to));
|
||||
</script>
|
||||
|
||||
<circle r="7" class="fireball" cx="0" cy="0">
|
||||
<animateMotion dur="0.42s" fill="freeze" path={`M ${a.x} ${a.y} L ${b.x} ${b.y}`} />
|
||||
</circle>
|
||||
<defs>
|
||||
<radialGradient id={`fireball-core-${uid}`}>
|
||||
<stop offset="0" stop-color="#fffde0" />
|
||||
<stop offset="0.28" stop-color="#fff09a" />
|
||||
<stop offset="0.62" stop-color="#ffad22" />
|
||||
<stop offset="1" stop-color="#ef3b08" stop-opacity="0.15" />
|
||||
</radialGradient>
|
||||
|
||||
<linearGradient id={`fireball-tail-${uid}`} x1="1" x2="0">
|
||||
<stop offset="0" stop-color="#fff2a1" stop-opacity="0.95" />
|
||||
<stop offset="0.2" stop-color="#ff9d16" stop-opacity="0.86" />
|
||||
<stop offset="0.62" stop-color="#ee3907" stop-opacity="0.42" />
|
||||
<stop offset="1" stop-color="#581002" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
|
||||
<filter id={`fireball-warp-${uid}`} x="-60%" y="-160%" width="230%" height="420%">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.022 0.12" numOctaves="3" seed="7" result="noise">
|
||||
<animate
|
||||
attributeName="baseFrequency"
|
||||
values="0.022 0.12; 0.035 0.17; 0.018 0.1"
|
||||
dur="0.22s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</feTurbulence>
|
||||
<feDisplacementMap in="SourceGraphic" in2="noise" scale="14" xChannelSelector="R" yChannelSelector="B" />
|
||||
<feGaussianBlur stdDeviation="1.4" />
|
||||
</filter>
|
||||
|
||||
<filter id={`fireball-glow-${uid}`} x="-180%" y="-180%" width="460%" height="460%">
|
||||
<feGaussianBlur stdDeviation="7" result="blur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="blur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<g class="fireball">
|
||||
<g>
|
||||
<!-- Outer orange glow -->
|
||||
<ellipse cx="-4" cy="0" rx="27" ry="16" fill="#ff5b0a" opacity="0.32" filter={`url(#fireball-glow-${uid})`} />
|
||||
|
||||
<!-- Turbulent outer flame -->
|
||||
<path
|
||||
d="M -8 -13 C -30 -18, -52 -8, -91 -3 C -60 1, -68 14, -98 20 C -54 17, -28 11, -7 12 Z"
|
||||
fill={`url(#fireball-tail-${uid})`}
|
||||
filter={`url(#fireball-warp-${uid})`}
|
||||
/>
|
||||
|
||||
<!-- Hot inner flame -->
|
||||
<path
|
||||
d="M -6 -8 C -24 -10, -42 -4, -64 1 C -39 2, -32 8, -10 8 Z"
|
||||
fill="#ffd34e"
|
||||
opacity="0.74"
|
||||
filter={`url(#fireball-warp-${uid})`}
|
||||
/>
|
||||
|
||||
<!-- Fireball body -->
|
||||
<circle r="15" fill="#ff5c09" opacity="0.72" filter={`url(#fireball-glow-${uid})`} />
|
||||
<circle r="12" fill={`url(#fireball-core-${uid})`} />
|
||||
|
||||
<!-- White-hot highlight -->
|
||||
<ellipse cx="4" cy="-3" rx="6" ry="5" fill="#fffde7" opacity="0.95" />
|
||||
|
||||
<!-- Sparks -->
|
||||
<g fill="#ff9d19">
|
||||
<circle cx="-28" cy="-17" r="2.2">
|
||||
<animate attributeName="cy" values="-17; -23; -17" dur="0.16s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="-46" cy="12" r="1.6">
|
||||
<animate attributeName="cy" values="12; 19; 12" dur="0.2s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="-70" cy="-7" r="1.4">
|
||||
<animate attributeName="opacity" values="1; 0.1; 1" dur="0.14s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<animateMotion dur="0.42s" fill="freeze" rotate="auto" path={`M ${a.x} ${a.y} L ${b.x} ${b.y}`} />
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.fireball {
|
||||
fill: #ff8c1a;
|
||||
stroke: #ffd27a;
|
||||
stroke-width: 2;
|
||||
filter: drop-shadow(0 0 6px rgba(255, 120, 20, 0.9));
|
||||
animation: fade 0.55s ease-in forwards;
|
||||
opacity: 0;
|
||||
animation: fireball-fade 0.55s ease-in forwards;
|
||||
}
|
||||
@keyframes fireball-fade {
|
||||
0%, 70% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
@keyframes fade { 70% { opacity: 1; } 100% { opacity: 0; } }
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,55 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"fireworks"> } = $props();
|
||||
const c = $derived(center(fx.at));
|
||||
const COLORS = ["#e74c3c", "#f1c40f", "#3b8dd6", "#7ac47e"];
|
||||
const RAYS = [0, 45, 90, 135, 180, 225, 270, 315];
|
||||
</script>
|
||||
|
||||
<g class="fireworks" style={`transform-origin: ${c.x}px ${c.y}px`}>
|
||||
{#each [0, 45, 90, 135, 180, 225, 270, 315] as deg (deg)}
|
||||
<g class="fireworks">
|
||||
{#each RAYS as deg, i (deg)}
|
||||
{@const dx = Math.cos((deg * Math.PI) / 180)}
|
||||
{@const dy = Math.sin((deg * Math.PI) / 180)}
|
||||
<line
|
||||
x1={c.x + 4 * dx} y1={c.y + 4 * dy}
|
||||
x2={c.x + 20 * dx} y2={c.y + 20 * dy}
|
||||
stroke={COLORS[i % 4]}
|
||||
class="trail"
|
||||
style={`transform-origin: ${c.x}px ${c.y}px`}
|
||||
/>
|
||||
<circle
|
||||
cx={c.x + 18 * Math.cos((deg * Math.PI) / 180)}
|
||||
cy={c.y + 18 * Math.sin((deg * Math.PI) / 180)}
|
||||
r="3" fill={COLORS[(deg / 45) % 4]}
|
||||
cx={c.x + 21 * dx} cy={c.y + 21 * dy} r="2.6"
|
||||
fill={COLORS[i % 4]}
|
||||
class="spark"
|
||||
style={`transform-origin: ${c.x}px ${c.y}px`}
|
||||
/>
|
||||
{/each}
|
||||
<circle cx={c.x} cy={c.y} r="3.5" class="heart" />
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.fireworks { animation: boom 1.1s ease-out forwards; }
|
||||
@keyframes boom {
|
||||
0% { opacity: 0; transform: scale(0.1); }
|
||||
.trail {
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
animation: trail-out 0.9s ease-out forwards;
|
||||
}
|
||||
.spark { animation: spark-out 1.05s ease-out forwards; }
|
||||
.heart { fill: #fffdf0; animation: heart-pop 0.4s ease-out forwards; }
|
||||
@keyframes trail-out {
|
||||
0% { opacity: 0; transform: scale(0.15); }
|
||||
25% { opacity: 1; }
|
||||
100% { opacity: 0; transform: scale(2.4) rotate(30deg); }
|
||||
100% { opacity: 0; transform: scale(1.5); }
|
||||
}
|
||||
@keyframes spark-out {
|
||||
0% { opacity: 0; transform: scale(0.15); }
|
||||
30% { opacity: 1; }
|
||||
80% { opacity: 0.9; transform: scale(1.55); }
|
||||
100% { opacity: 0; transform: scale(1.6) translateY(4px); }
|
||||
}
|
||||
@keyframes heart-pop {
|
||||
0% { opacity: 1; transform: scale(0.5); }
|
||||
100% { opacity: 0; transform: scale(3); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,23 +1,45 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"ooze-slip"> } = $props();
|
||||
const uid = $props.id();
|
||||
const c = $derived(center(fx.at));
|
||||
</script>
|
||||
|
||||
<ellipse cx={c.x} cy={c.y + 8} rx="13" ry="5" class="ooze" />
|
||||
<defs>
|
||||
<radialGradient id={`ooze-splat-${uid}`}>
|
||||
<stop offset="0" stop-color="#a5cc60" stop-opacity="0.8" />
|
||||
<stop offset="0.7" stop-color="#6ea03c" stop-opacity="0.55" />
|
||||
<stop offset="1" stop-color="#4a7024" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<ellipse cx={c.x} cy={c.y + 8} rx="14" ry="5.5" fill={`url(#ooze-splat-${uid})`} class="splat" />
|
||||
<g class="blobs">
|
||||
<circle cx={c.x - 12} cy={c.y + 3} r="2.5" class="blob" />
|
||||
<circle cx={c.x + 11} cy={c.y + 2} r="2" class="blob late" />
|
||||
<circle cx={c.x + 3} cy={c.y - 3} r="1.7" class="blob later" />
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.ooze {
|
||||
.splat {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
fill: rgba(110, 160, 60, 0.55);
|
||||
animation: wobble 0.7s ease-out forwards;
|
||||
animation: splat-wobble 0.7s ease-out forwards;
|
||||
}
|
||||
@keyframes wobble {
|
||||
0% { opacity: 0.9; transform: scaleX(0.6); }
|
||||
35% { transform: scaleX(1.25); }
|
||||
65% { transform: scaleX(0.9); }
|
||||
100% { opacity: 0; transform: scaleX(1.1); }
|
||||
.blob { fill: #8fbf4d; animation: blob-fly 0.55s ease-out forwards; }
|
||||
.blob.late { animation-delay: 0.06s; }
|
||||
.blob.later { animation-delay: 0.12s; }
|
||||
@keyframes splat-wobble {
|
||||
0% { opacity: 0.95; transform: scale(0.5, 1); }
|
||||
35% { transform: scale(1.3, 0.85); }
|
||||
65% { transform: scale(0.9, 1.05); }
|
||||
100% { opacity: 0; transform: scale(1.15, 0.95); }
|
||||
}
|
||||
@keyframes blob-fly {
|
||||
0% { opacity: 0.9; transform: translateY(0); }
|
||||
50% { transform: translateY(-7px); }
|
||||
100% { opacity: 0; transform: translateY(2px); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,29 +1,54 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"pit-fall"> } = $props();
|
||||
const uid = $props.id();
|
||||
const c = $derived(center(fx.at));
|
||||
</script>
|
||||
|
||||
<circle cx={c.x} cy={c.y} r="10" class="pitfall" />
|
||||
<circle cx={c.x - 9} cy={c.y - 4} r="3" class="dust" />
|
||||
<circle cx={c.x + 9} cy={c.y - 4} r="3" class="dust late" />
|
||||
<defs>
|
||||
<radialGradient id={`pit-hole-${uid}`}>
|
||||
<stop offset="0" stop-color="#0c0905" />
|
||||
<stop offset="0.75" stop-color="#241b10" />
|
||||
<stop offset="1" stop-color="#241b10" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<circle cx={c.x} cy={c.y} r="14" fill={`url(#pit-hole-${uid})`} class="hole" />
|
||||
<circle cx={c.x} cy={c.y - 2} r="8" class="faller" style={`transform-origin: ${c.x}px ${c.y}px`} />
|
||||
<g class="rim-dust">
|
||||
<circle cx={c.x - 11} cy={c.y - 6} r="3" class="dust" />
|
||||
<circle cx={c.x + 10} cy={c.y - 7} r="2.5" class="dust late" />
|
||||
<circle cx={c.x + 1} cy={c.y - 11} r="2" class="dust later" />
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.pitfall {
|
||||
.hole {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
fill: rgba(30, 24, 16, 0.8);
|
||||
animation: swallow 0.7s ease-in forwards;
|
||||
animation: hole-open 0.75s ease-out forwards;
|
||||
}
|
||||
.dust { fill: rgba(160, 150, 130, 0.75); animation: drift 0.7s ease-out forwards; }
|
||||
.dust.late { animation-delay: 0.09s; }
|
||||
@keyframes swallow {
|
||||
0% { opacity: 0.95; transform: scale(1) rotate(0deg); }
|
||||
100% { opacity: 0; transform: scale(0.05) rotate(50deg); }
|
||||
.faller {
|
||||
fill: rgba(120, 100, 70, 0.85);
|
||||
animation: fall-in 0.55s ease-in 0.06s forwards;
|
||||
}
|
||||
@keyframes drift {
|
||||
0% { opacity: 0.8; transform: translateY(0) scale(1); }
|
||||
100% { opacity: 0; transform: translateY(-10px) scale(1.8); }
|
||||
.dust { fill: rgba(160, 150, 130, 0.75); animation: dust-drift 0.7s ease-out 0.15s forwards; opacity: 0; }
|
||||
.dust.late { animation-delay: 0.24s; }
|
||||
.dust.later { animation-delay: 0.32s; }
|
||||
@keyframes hole-open {
|
||||
0% { opacity: 0; transform: scale(0.3); }
|
||||
25% { opacity: 1; transform: scale(1); }
|
||||
80% { opacity: 1; }
|
||||
100% { opacity: 0; transform: scale(1); }
|
||||
}
|
||||
@keyframes fall-in {
|
||||
0% { opacity: 1; transform: scale(1) translateY(-6px); }
|
||||
100% { opacity: 0; transform: scale(0.1) translateY(4px) rotate(60deg); }
|
||||
}
|
||||
@keyframes dust-drift {
|
||||
0% { opacity: 0; transform: translateY(0) scale(1); }
|
||||
30% { opacity: 0.8; }
|
||||
100% { opacity: 0; transform: translateY(-10px) scale(1.7); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,25 +1,63 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"pow"> } = $props();
|
||||
const uid = $props.id();
|
||||
const c = $derived(center(fx.at));
|
||||
const DASHES = [20, 95, 160, 250, 320];
|
||||
</script>
|
||||
|
||||
<defs>
|
||||
<radialGradient id={`pow-star-${uid}`}>
|
||||
<stop offset="0" stop-color="#fffdf0" />
|
||||
<stop offset="0.45" stop-color="#ffe94d" />
|
||||
<stop offset="1" stop-color="#ffb02e" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<g class="pow" style={`transform-origin: ${c.x}px ${c.y}px`}>
|
||||
<path d={`M ${c.x} ${c.y - 14} l 4 8 9 -4 -4 9 8 5 -9 3 2 10 -8 -6 -6 8 -1 -10 -10 1 7 -7 -7 -7 10 0 1 -9 6 8 z`} />
|
||||
<path
|
||||
d={`M ${c.x} ${c.y - 14} l 4 8 9 -4 -4 9 8 5 -9 3 2 10 -8 -6 -6 8 -1 -10 -10 1 7 -7 -7 -7 10 0 1 -9 6 8 z`}
|
||||
fill={`url(#pow-star-${uid})`}
|
||||
/>
|
||||
<circle cx={c.x} cy={c.y} r="4" class="flash" />
|
||||
</g>
|
||||
{#each DASHES as deg (deg)}
|
||||
<line
|
||||
x1={c.x + 16 * Math.cos((deg * Math.PI) / 180)}
|
||||
y1={c.y + 16 * Math.sin((deg * Math.PI) / 180)}
|
||||
x2={c.x + 24 * Math.cos((deg * Math.PI) / 180)}
|
||||
y2={c.y + 24 * Math.sin((deg * Math.PI) / 180)}
|
||||
class="dash"
|
||||
style={`transform-origin: ${c.x}px ${c.y}px`}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<style>
|
||||
.pow path {
|
||||
fill: #ffe94d;
|
||||
stroke: #b3372b;
|
||||
stroke-width: 1.6;
|
||||
}
|
||||
.pow path { stroke: #b3372b; stroke-width: 1.6; }
|
||||
.pow .flash { fill: #ffffff; animation: flash 0.3s ease-out forwards; }
|
||||
.pow { animation: pow-hit 0.5s ease-out forwards; }
|
||||
.dash {
|
||||
stroke: #b3372b;
|
||||
stroke-width: 2.5;
|
||||
stroke-linecap: round;
|
||||
animation: dash-out 0.4s ease-out 0.06s forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
@keyframes pow-hit {
|
||||
0% { opacity: 0; transform: scale(0.3) rotate(-15deg); }
|
||||
25% { opacity: 1; transform: scale(1.25) rotate(5deg); }
|
||||
55% { transform: scale(1) rotate(0deg); }
|
||||
100% { opacity: 0; transform: scale(1.05); }
|
||||
}
|
||||
@keyframes flash {
|
||||
0% { opacity: 1; transform: scale(1); }
|
||||
100% { opacity: 0; transform: scale(3); }
|
||||
}
|
||||
@keyframes dash-out {
|
||||
0% { opacity: 0; transform: scale(0.7); }
|
||||
30% { opacity: 0.9; }
|
||||
100% { opacity: 0; transform: scale(1.3); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,52 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"shield"> } = $props();
|
||||
const uid = $props.id();
|
||||
const c = $derived(center(fx.at));
|
||||
</script>
|
||||
|
||||
<circle cx={c.x} cy={c.y} r="16" class="shield" />
|
||||
<defs>
|
||||
<radialGradient id={`shield-dome-${uid}`}>
|
||||
<stop offset="0" stop-color="#ffe9a3" stop-opacity="0" />
|
||||
<stop offset="0.75" stop-color="#ffdf7a" stop-opacity="0.25" />
|
||||
<stop offset="1" stop-color="#c9a72a" stop-opacity="0.9" />
|
||||
</radialGradient>
|
||||
<filter id={`shield-glow-${uid}`} x="-80%" y="-80%" width="260%" height="260%">
|
||||
<feGaussianBlur stdDeviation="3" result="blur" />
|
||||
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<circle cx={c.x} cy={c.y} r="16" fill={`url(#shield-dome-${uid})`} class="dome" filter={`url(#shield-glow-${uid})`} />
|
||||
<circle cx={c.x} cy={c.y} r="16" class="rim" />
|
||||
|
||||
<style>
|
||||
.shield {
|
||||
.dome {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
animation: dome 0.7s ease-out forwards;
|
||||
}
|
||||
.rim {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
fill: none;
|
||||
stroke: #c9a72a;
|
||||
stroke-width: 3.5;
|
||||
filter: drop-shadow(0 0 6px rgba(201, 167, 42, 0.8));
|
||||
animation: pulse 0.7s ease-out forwards;
|
||||
stroke: #fff3c4;
|
||||
stroke-width: 2;
|
||||
animation: rim-strike 0.7s ease-out forwards;
|
||||
}
|
||||
@keyframes pulse {
|
||||
@keyframes dome {
|
||||
0% { opacity: 0; transform: scale(0.6); }
|
||||
30% { opacity: 1; transform: scale(1.1); }
|
||||
60% { transform: scale(0.95); }
|
||||
100% { opacity: 0; transform: scale(1.15); }
|
||||
}
|
||||
@keyframes rim-strike {
|
||||
0% { opacity: 0; transform: scale(0.6); }
|
||||
28% { opacity: 1; transform: scale(1.12); }
|
||||
45% { opacity: 0.4; }
|
||||
58% { opacity: 0.9; transform: scale(0.95); }
|
||||
100% { opacity: 0; transform: scale(1.18); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"shimmer"> } = $props();
|
||||
const c = $derived(center(fx.at));
|
||||
</script>
|
||||
|
||||
<circle cx={c.x} cy={c.y} r="6" class="shimmer" />
|
||||
<circle cx={c.x} cy={c.y} r="6" class="shimmer late" />
|
||||
<circle cx={c.x} cy={c.y} r="6" class="ring" />
|
||||
<circle cx={c.x} cy={c.y} r="6" class="ring late" />
|
||||
<g class="motes">
|
||||
<circle cx={c.x - 8} cy={c.y - 8} r="1.5" />
|
||||
<circle cx={c.x + 9} cy={c.y - 4} r="1.2" class="late" />
|
||||
<circle cx={c.x - 3} cy={c.y + 9} r="1.3" class="later" />
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.shimmer {
|
||||
.ring {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
fill: none;
|
||||
@@ -17,9 +23,17 @@
|
||||
stroke-width: 2.5;
|
||||
animation: ring 0.6s ease-out forwards;
|
||||
}
|
||||
.shimmer.late { stroke: #e2c8ff; animation-delay: 0.18s; opacity: 0; }
|
||||
.ring.late { stroke: #e2c8ff; animation-delay: 0.18s; opacity: 0; }
|
||||
.motes circle { fill: #e2c8ff; animation: mote 0.7s ease-out forwards; }
|
||||
.motes .late { animation-delay: 0.1s; opacity: 0; }
|
||||
.motes .later { animation-delay: 0.18s; opacity: 0; }
|
||||
@keyframes ring {
|
||||
0% { opacity: 0.95; transform: scale(1); }
|
||||
100% { opacity: 0; transform: scale(5); }
|
||||
}
|
||||
@keyframes mote {
|
||||
0% { opacity: 0; transform: translateY(0); }
|
||||
30% { opacity: 1; }
|
||||
100% { opacity: 0; transform: translateY(-12px); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,23 +1,48 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"slime-stuck"> } = $props();
|
||||
const uid = $props.id();
|
||||
const c = $derived(center(fx.at));
|
||||
</script>
|
||||
|
||||
<circle cx={c.x} cy={c.y} r="12" class="slime" />
|
||||
<defs>
|
||||
<radialGradient id={`slime-goo-${uid}`}>
|
||||
<stop offset="0" stop-color="#c8e87a" stop-opacity="0.4" />
|
||||
<stop offset="0.8" stop-color="#8cc83c" stop-opacity="0.65" />
|
||||
<stop offset="1" stop-color="#7a9e2e" stop-opacity="0.9" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<circle cx={c.x} cy={c.y} r="13" fill={`url(#slime-goo-${uid})`} class="goo" />
|
||||
<circle cx={c.x + 4} cy={c.y - 5} r="2.5" class="bubble" />
|
||||
<circle cx={c.x - 5} cy={c.y + 2} r="1.8" class="bubble late" />
|
||||
|
||||
<style>
|
||||
.slime {
|
||||
.goo {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
fill: rgba(140, 200, 60, 0.5);
|
||||
stroke: #7a9e2e;
|
||||
stroke-width: 3;
|
||||
animation: sink 0.9s ease-out forwards;
|
||||
animation: goo-close 0.9s ease-out forwards;
|
||||
}
|
||||
@keyframes sink {
|
||||
0% { opacity: 0.9; transform: scale(1.3); }
|
||||
100% { opacity: 0; transform: scale(0.7); }
|
||||
.bubble {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
fill: none;
|
||||
stroke: #c8e87a;
|
||||
stroke-width: 1.5;
|
||||
animation: bubble-pop 0.6s ease-out 0.2s forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
.bubble.late { animation-delay: 0.4s; }
|
||||
@keyframes goo-close {
|
||||
0% { opacity: 0.95; transform: scale(1.35); }
|
||||
60% { opacity: 0.85; transform: scale(0.9); }
|
||||
100% { opacity: 0; transform: scale(1); }
|
||||
}
|
||||
@keyframes bubble-pop {
|
||||
0% { opacity: 0; transform: scale(0.4); }
|
||||
50% { opacity: 0.9; transform: scale(1); }
|
||||
100% { opacity: 0; transform: scale(1.8); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,44 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"soul"> } = $props();
|
||||
const uid = $props.id();
|
||||
const c = $derived(center(fx.at));
|
||||
</script>
|
||||
|
||||
<defs>
|
||||
<radialGradient id={`soul-body-${uid}`} cy="0.35">
|
||||
<stop offset="0" stop-color="#f7f4ff" stop-opacity="0.95" />
|
||||
<stop offset="0.7" stop-color="#d9d0f0" stop-opacity="0.75" />
|
||||
<stop offset="1" stop-color="#a096c8" stop-opacity="0.35" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<g class="soul">
|
||||
<circle cx={c.x} cy={c.y - 4} r="8" />
|
||||
<circle cx={c.x - 5} cy={c.y + 3} r="4" />
|
||||
<circle cx={c.x + 5} cy={c.y + 3} r="4" />
|
||||
<g class="sway" style={`transform-origin: ${c.x}px ${c.y}px`}>
|
||||
<!-- Rounded head, wavy hem -->
|
||||
<path
|
||||
d={`M ${c.x - 8} ${c.y + 6} C ${c.x - 9} ${c.y - 6}, ${c.x - 5} ${c.y - 11}, ${c.x} ${c.y - 11}
|
||||
C ${c.x + 5} ${c.y - 11}, ${c.x + 9} ${c.y - 6}, ${c.x + 8} ${c.y + 6}
|
||||
q -2 -2.5 -4 0 q -2 2.5 -4 0 q -2 -2.5 -4 0 q -2 2.5 -4 0 z`}
|
||||
fill={`url(#soul-body-${uid})`}
|
||||
/>
|
||||
<circle cx={c.x - 3} cy={c.y - 4} r="1.3" fill="#5a5178" />
|
||||
<circle cx={c.x + 3} cy={c.y - 4} r="1.3" fill="#5a5178" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.soul circle {
|
||||
fill: rgba(233, 228, 245, 0.8);
|
||||
stroke: rgba(160, 150, 200, 0.6);
|
||||
stroke-width: 1;
|
||||
}
|
||||
.soul { animation: ascend 1.3s ease-out forwards; }
|
||||
.sway { animation: sway 0.65s ease-in-out 2; }
|
||||
@keyframes ascend {
|
||||
0% { opacity: 0; transform: translateY(0); }
|
||||
25% { opacity: 0.9; }
|
||||
100% { opacity: 0; transform: translateY(-34px); }
|
||||
22% { opacity: 0.95; }
|
||||
100% { opacity: 0; transform: translateY(-36px); }
|
||||
}
|
||||
@keyframes sway {
|
||||
0%, 100% { transform: rotate(0deg); }
|
||||
50% { transform: rotate(6deg); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,33 +1,58 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"splash"> } = $props();
|
||||
const uid = $props.id();
|
||||
const c = $derived(center(fx.at));
|
||||
</script>
|
||||
|
||||
<circle cx={c.x} cy={c.y} r="4" class="splash" />
|
||||
<circle cx={c.x - 10} cy={c.y - 6} r="2.5" class="droplet" />
|
||||
<circle cx={c.x + 9} cy={c.y - 8} r="2" class="droplet late" />
|
||||
<circle cx={c.x + 3} cy={c.y - 12} r="1.8" class="droplet later" />
|
||||
<defs>
|
||||
<radialGradient id={`splash-pool-${uid}`}>
|
||||
<stop offset="0" stop-color="#bfe8ff" stop-opacity="0.7" />
|
||||
<stop offset="0.7" stop-color="#4fa3e3" stop-opacity="0.4" />
|
||||
<stop offset="1" stop-color="#155a9e" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<ellipse cx={c.x} cy={c.y + 4} rx="12" ry="5" fill={`url(#splash-pool-${uid})`} class="pool" />
|
||||
<circle cx={c.x} cy={c.y} r="5" class="ring" />
|
||||
<!-- The crown: droplets thrown upward, falling back -->
|
||||
<g class="crown">
|
||||
<circle cx={c.x - 10} cy={c.y - 6} r="2.5" class="drop" />
|
||||
<circle cx={c.x - 3} cy={c.y - 12} r="2" class="drop late" />
|
||||
<circle cx={c.x + 5} cy={c.y - 10} r="2.2" class="drop" />
|
||||
<circle cx={c.x + 11} cy={c.y - 5} r="1.7" class="drop later" />
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.splash {
|
||||
.pool {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
animation: pool-spread 0.6s ease-out forwards;
|
||||
}
|
||||
.ring {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
fill: none;
|
||||
stroke: #4fa3e3;
|
||||
stroke-width: 3.5;
|
||||
animation: ring 0.55s ease-out forwards;
|
||||
stroke: #7cc0ee;
|
||||
stroke-width: 2.5;
|
||||
animation: ring-out 0.55s ease-out forwards;
|
||||
}
|
||||
.droplet { fill: #7cc0ee; animation: drop 0.6s ease-out forwards; }
|
||||
.droplet.late { animation-delay: 0.08s; }
|
||||
.droplet.later { animation-delay: 0.15s; }
|
||||
@keyframes ring {
|
||||
0% { opacity: 0.95; transform: scale(1); }
|
||||
100% { opacity: 0; transform: scale(5); }
|
||||
.drop { fill: #7cc0ee; animation: drop-arc 0.6s ease-in forwards; }
|
||||
.drop.late { animation-delay: 0.07s; }
|
||||
.drop.later { animation-delay: 0.13s; }
|
||||
@keyframes pool-spread {
|
||||
0% { opacity: 0.9; transform: scale(0.4); }
|
||||
100% { opacity: 0; transform: scale(2.2); }
|
||||
}
|
||||
@keyframes drop {
|
||||
0% { opacity: 0.9; transform: translateY(0); }
|
||||
100% { opacity: 0; transform: translateY(-14px); }
|
||||
@keyframes ring-out {
|
||||
0% { opacity: 0.9; transform: scale(1); }
|
||||
100% { opacity: 0; transform: scale(4); }
|
||||
}
|
||||
@keyframes drop-arc {
|
||||
0% { opacity: 0.95; transform: translateY(0); }
|
||||
45% { transform: translateY(-8px); }
|
||||
100% { opacity: 0; transform: translateY(6px); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,23 +1,44 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"streak"> } = $props();
|
||||
const uid = $props.id();
|
||||
const a = $derived(center(fx.from));
|
||||
const b = $derived(center(fx.to));
|
||||
</script>
|
||||
|
||||
<line x1={a.x} y1={a.y} x2={b.x} y2={b.y} class="streak" />
|
||||
<defs>
|
||||
<linearGradient id={`streak-trail-${uid}`}
|
||||
x1={a.x} y1={a.y} x2={b.x} y2={b.y} gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#e9e1cb" stop-opacity="0" />
|
||||
<stop offset="0.7" stop-color="#e9e1cb" stop-opacity="0.6" />
|
||||
<stop offset="1" stop-color="#fffdf0" stop-opacity="0.95" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<line x1={a.x} y1={a.y} x2={b.x} y2={b.y} stroke={`url(#streak-trail-${uid})`} class="trail" />
|
||||
<circle cx={b.x} cy={b.y} r="5" class="head" />
|
||||
|
||||
<style>
|
||||
.streak {
|
||||
stroke: rgba(233, 225, 203, 0.85);
|
||||
stroke-width: 5;
|
||||
.trail {
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 14 9;
|
||||
animation: streak-fade 0.55s ease-out forwards;
|
||||
animation: trail-fade 0.55s ease-out forwards;
|
||||
}
|
||||
@keyframes streak-fade {
|
||||
0% { opacity: 0.9; stroke-dashoffset: 46; }
|
||||
100% { opacity: 0; stroke-dashoffset: 0; }
|
||||
.head {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
fill: rgba(255, 253, 240, 0.9);
|
||||
animation: head-land 0.45s ease-out forwards;
|
||||
}
|
||||
@keyframes trail-fade {
|
||||
0% { opacity: 0.95; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
@keyframes head-land {
|
||||
0% { opacity: 0; transform: scale(0.4); }
|
||||
35% { opacity: 1; transform: scale(1.3); }
|
||||
100% { opacity: 0; transform: scale(0.9); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,36 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"thorn-snap"> } = $props();
|
||||
const c = $derived(center(fx.at));
|
||||
</script>
|
||||
|
||||
<path class="thorn"
|
||||
<path class="thorn" style={`transform-origin: ${c.x}px ${c.y}px`}
|
||||
d={`M ${c.x - 12} ${c.y} l 6 -4 2 -8 4 6 8 -4 -3 8 7 5 -9 1 -2 9 -5 -7 -8 2 z`} />
|
||||
<g class="leaves">
|
||||
<ellipse cx={c.x - 12} cy={c.y - 10} rx="3" ry="1.6" class="leaf" transform={`rotate(-30 ${c.x - 12} ${c.y - 10})`} />
|
||||
<ellipse cx={c.x + 13} cy={c.y - 8} rx="2.6" ry="1.4" class="leaf late" transform={`rotate(40 ${c.x + 13} ${c.y - 8})`} />
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.thorn {
|
||||
fill: rgba(70, 120, 50, 0.7);
|
||||
fill: rgba(70, 120, 50, 0.75);
|
||||
stroke: #2f5423;
|
||||
stroke-width: 1.5;
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
animation: snap 0.55s ease-out forwards;
|
||||
}
|
||||
.leaf { fill: #7fae55; animation: leaf-fall 0.7s ease-in 0.1s forwards; opacity: 0; }
|
||||
.leaf.late { animation-delay: 0.18s; }
|
||||
@keyframes snap {
|
||||
0% { opacity: 0; transform: scale(0.3) rotate(-15deg); }
|
||||
25% { opacity: 1; transform: scale(1.25) rotate(5deg); }
|
||||
55% { transform: scale(1) rotate(0deg); }
|
||||
100% { opacity: 0; transform: scale(1.05); }
|
||||
}
|
||||
@keyframes leaf-fall {
|
||||
0% { opacity: 0; transform: translateY(0) rotate(0deg); }
|
||||
25% { opacity: 0.9; }
|
||||
100% { opacity: 0; transform: translateY(10px) rotate(50deg); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,22 +1,66 @@
|
||||
<script lang="ts">
|
||||
import type { FxOf } from "../fx";
|
||||
import { center } from "./geom";
|
||||
|
||||
let { fx }: { fx: FxOf<"waterbolt"> } = $props();
|
||||
const uid = $props.id();
|
||||
|
||||
const a = $derived(center(fx.from));
|
||||
const b = $derived(center(fx.to));
|
||||
</script>
|
||||
|
||||
<circle r="6" class="waterbolt" cx="0" cy="0">
|
||||
<animateMotion dur="0.42s" fill="freeze" path={`M ${a.x} ${a.y} L ${b.x} ${b.y}`} />
|
||||
</circle>
|
||||
<defs>
|
||||
<radialGradient id={`wb-core-${uid}`}>
|
||||
<stop offset="0" stop-color="#eafaff" />
|
||||
<stop offset="0.35" stop-color="#9fd9f7" />
|
||||
<stop offset="0.7" stop-color="#3b8dd6" />
|
||||
<stop offset="1" stop-color="#155a9e" stop-opacity="0.2" />
|
||||
</radialGradient>
|
||||
<linearGradient id={`wb-tail-${uid}`} x1="1" x2="0">
|
||||
<stop offset="0" stop-color="#bfe8ff" stop-opacity="0.9" />
|
||||
<stop offset="0.4" stop-color="#4fa3e3" stop-opacity="0.55" />
|
||||
<stop offset="1" stop-color="#155a9e" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<filter id={`wb-glow-${uid}`} x="-160%" y="-160%" width="420%" height="420%">
|
||||
<feGaussianBlur stdDeviation="5" result="blur" />
|
||||
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<g class="waterbolt">
|
||||
<g>
|
||||
<!-- Spray tail -->
|
||||
<path
|
||||
d="M -5 -9 C -22 -12, -44 -6, -70 -2 C -46 0, -50 8, -74 13 C -44 11, -22 8, -5 9 Z"
|
||||
fill={`url(#wb-tail-${uid})`}
|
||||
/>
|
||||
<!-- Body and core -->
|
||||
<circle r="11" fill="#3b8dd6" opacity="0.6" filter={`url(#wb-glow-${uid})`} />
|
||||
<circle r="9" fill={`url(#wb-core-${uid})`} />
|
||||
<ellipse cx="3" cy="-2.5" rx="4" ry="3" fill="#f2fcff" opacity="0.9" />
|
||||
<!-- Trailing droplets -->
|
||||
<g fill="#7cc0ee">
|
||||
<circle cx="-24" cy="-10" r="2">
|
||||
<animate attributeName="cy" values="-10; -15; -10" dur="0.18s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="-40" cy="9" r="1.6">
|
||||
<animate attributeName="cy" values="9; 14; 9" dur="0.22s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="-56" cy="-4" r="1.3">
|
||||
<animate attributeName="opacity" values="1; 0.2; 1" dur="0.16s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
</g>
|
||||
</g>
|
||||
<animateMotion dur="0.42s" fill="freeze" rotate="auto" path={`M ${a.x} ${a.y} L ${b.x} ${b.y}`} />
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.waterbolt {
|
||||
fill: #3b8dd6;
|
||||
stroke: #b9e2ff;
|
||||
stroke-width: 2;
|
||||
filter: drop-shadow(0 0 5px rgba(80, 160, 230, 0.9));
|
||||
animation: fade 0.55s ease-in forwards;
|
||||
opacity: 0;
|
||||
animation: wb-fade 0.55s ease-in forwards;
|
||||
}
|
||||
@keyframes wb-fade {
|
||||
0%, 70% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
@keyframes fade { 70% { opacity: 1; } 100% { opacity: 0; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user