animation variety: per-scene motion signatures (occlusion-safe scenes only) - aperitif tumble, Course IV curved-arc trajectories, Course V spiral orbit-in; achieved via manual per-frame transform math, no plugin/dependency added; bite-carrying scenes keep clean translate

This commit is contained in:
2026-08-17 21:32:15 +10:00
parent 20b1dcfb9a
commit f155061f35
5 changed files with 28 additions and 4 deletions
+18 -1
View File
@@ -943,13 +943,30 @@ body:not(.fx) .slab{display:none}
u.flat.style.visibility = "hidden";
const lis = u.legend ? [...u.legend.children] : [];
const n = lis.length;
// per-scene motion signature (occlusion-safe scenes only; default is straight translate).
// "away" is 0 at the hero layout, 1 at collapsed/flown - every signature returns to identity
// at the hero so the assembled slices always match the source image exactly.
const M = u.man.motion, RAD = Math.PI / 180;
u.apply = t => {
const sg = u.man.stagger || 0, k = u.man.parts.length;
u.man.parts.forEach((p, i) => {
const pt = sg ? Math.min(1, Math.max(0, t * (1 + sg * (k - 1)) - sg * i)) : t;
const e = u.man.reverse ? (1 - pt) : pt;
const away = 1 - e;
let ox = p.dx * away, oy = p.dy * away, extra = "";
if (M === "tumble") { // aperitif: fragments spin as they fly out
const spin = (p.spin != null ? p.spin : (i % 2 ? 20 : -20)) * away;
extra = " rotate(" + spin.toFixed(2) + "deg)";
} else if (M === "arc") { // traffic: slices travel a bowed (curved) path
const len = Math.hypot(p.dx, p.dy) || 1;
const bow = (u.man.bow || 9) * 4 * away * (1 - away);
ox += (-p.dy / len) * bow; oy += (p.dx / len) * bow;
} else if (M === "orbit") { // OpenShift: satellites spiral in around the core
const a = (u.man.orbit || 55) * away * RAD, c = Math.cos(a), sn = Math.sin(a);
ox = (p.dx * c - p.dy * sn) * away; oy = (p.dx * sn + p.dy * c) * away;
}
u.slabs[i].style.transform =
"translate3d(" + (p.dx * (1 - e)).toFixed(3) + "%," + (p.dy * (1 - e)).toFixed(3) + "%,0)";
"translate3d(" + ox.toFixed(3) + "%," + oy.toFixed(3) + "%,0)" + extra;
});
u.scene.style.transform = "scale(" + (0.95 + 0.05 * t).toFixed(4) + ")";
if (n) lis.forEach((li, i) => li.classList.toggle("lit", t >= 0.9 * ((i + 0.5) / n)));
+3 -1
View File
@@ -828,5 +828,7 @@
"dx": 13.85,
"dy": 15.36
}
]
],
"motion": "orbit",
"orbit": 60
}
+2 -1
View File
@@ -675,5 +675,6 @@
"dy": 31.74
}
],
"reverse": true
"reverse": true,
"motion": "tumble"
}
+3 -1
View File
@@ -667,5 +667,7 @@
"dy": -11.61
}
],
"stagger": 0.22
"stagger": 0.22,
"motion": "arc",
"bow": 8
}
+2
View File
@@ -96,6 +96,8 @@ def emit(name, tag, spec):
man["stagger"] = spec["stagger"]
if spec.get("reverse"):
man["reverse"] = True
for kk in ("motion","bow","orbit"):
if kk in spec: man[kk]=spec[kk]
out = ROOT / "pilot" / f"manifest-{tag}.json"
out.write_text(json.dumps(man, indent=1))
print(f"manifest-{tag}.json: {len(parts)} parts ->",