Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions skills/hyperframes-animation/scripts/animation-map-sampling.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Seek and measure every sample for one tween inside a single browser
* evaluation. GSAP/HyperFrames seeks update DOM state synchronously, so a
* separate CDP round trip and wall-clock sleep per sample only adds latency.
*/
export async function sampleTweenBboxes(page, selector, times) {
return page.evaluate(
({ selector: sel, times: sampleTimes }) => {
const seek = (time) => {
if (window.__hf && typeof window.__hf.seek === "function") {
window.__hf.seek(time);
return;
}
const timelines = window.__timelines;
if (!timelines) return;
for (const timeline of Object.values(timelines)) {
if (typeof timeline.seek === "function") timeline.seek(time);
}
};

return sampleTimes.map((time) => {
seek(time);
const el = document.querySelector(sel);
if (!el) return { t: time, x: 0, y: 0, w: 0, h: 0, missing: true };
const rect = el.getBoundingClientRect();
const style = getComputedStyle(el);
return {
t: time,
x: Math.round(rect.x),
y: Math.round(rect.y),
w: Math.round(rect.width),
h: Math.round(rect.height),
opacity: parseFloat(style.opacity),
visible: style.visibility !== "hidden" && style.display !== "none",
};
});
},
{ selector, times },
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import assert from "node:assert/strict";
import test from "node:test";
import { sampleTweenBboxes } from "./animation-map-sampling.mjs";

test("samples every tween time in one browser evaluation", async () => {
const calls = [];
const seekTimes = [];
const originalGlobals = {
window: globalThis.window,
document: globalThis.document,
getComputedStyle: globalThis.getComputedStyle,
};
let currentTime = 0;
globalThis.window = { __hf: { seek: (time) => (currentTime = time) } };
globalThis.document = {
querySelector: () => ({
getBoundingClientRect: () => ({ x: currentTime, y: 20, width: 30, height: 40 }),
}),
};
globalThis.getComputedStyle = () => ({ opacity: "1", visibility: "visible", display: "block" });
const page = {
async evaluate(callback, payload) {
calls.push(payload);
const originalSeek = globalThis.window.__hf.seek;
globalThis.window.__hf.seek = (time) => {
seekTimes.push(time);
originalSeek(time);
};
return callback(payload);
},
};

try {
const result = await sampleTweenBboxes(page, "#card", [1, 2, 3]);

assert.deepEqual(result, [
{ t: 1, x: 1, y: 20, w: 30, h: 40, opacity: 1, visible: true },
{ t: 2, x: 2, y: 20, w: 30, h: 40, opacity: 1, visible: true },
{ t: 3, x: 3, y: 20, w: 30, h: 40, opacity: 1, visible: true },
]);
assert.deepEqual(seekTimes, [1, 2, 3]);
assert.deepEqual(calls, [{ selector: "#card", times: [1, 2, 3] }]);
} finally {
globalThis.window = originalGlobals.window;
globalThis.document = originalGlobals.document;
globalThis.getComputedStyle = originalGlobals.getComputedStyle;
}
});
25 changes: 2 additions & 23 deletions skills/hyperframes-animation/scripts/animation-map.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import { mkdir, writeFile } from "node:fs/promises";
import { resolve, join } from "node:path";
import { sampleTweenBboxes } from "./animation-map-sampling.mjs";
import { hyperframesPackageSpec, importPackagesOrBootstrap } from "./package-loader.mjs";

const {
Expand Down Expand Up @@ -77,12 +78,7 @@ try {
(_, k) => +(tw.start + ((k + 0.5) / FRAMES) * (tw.end - tw.start)).toFixed(3),
);

const bboxes = [];
for (const t of times) {
await seekTo(session, t);
const bbox = await measureTarget(session, tw.selectorHint);
bboxes.push({ t, ...bbox });
}
const bboxes = await sampleTweenBboxes(session.page, tw.selectorHint, times);

const animProps = tw.props.filter(
(p) => !["parent", "overwrite", "immediateRender", "startAt", "runBackwards"].includes(p),
Expand Down Expand Up @@ -205,23 +201,6 @@ async function enumerateTweens(session) {
});
}

async function measureTarget(session, selector) {
return await session.page.evaluate((sel) => {
const el = document.querySelector(sel);
if (!el) return { x: 0, y: 0, w: 0, h: 0, missing: true };
const r = el.getBoundingClientRect();
const cs = getComputedStyle(el);
return {
x: Math.round(r.x),
y: Math.round(r.y),
w: Math.round(r.width),
h: Math.round(r.height),
opacity: parseFloat(cs.opacity),
visible: cs.visibility !== "hidden" && cs.display !== "none",
};
}, selector);
}

// ─── Tween description (the key output for agents) ──────────────────────────

function describeTween(tw, props, bboxes, flags) {
Expand Down
Loading