diff --git a/package.json b/package.json
index 33b4b16ce2..d884ebec1e 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
"player:perf": "bun run --filter @hyperframes/player perf",
"format:check": "oxfmt --check .",
"knip": "knip",
- "test:scripts": "node --import tsx --test scripts/validate-release-channel.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/verify-packed-manifests.test.mjs",
+ "test:scripts": "node --import tsx --test scripts/validate-release-channel.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/verify-packed-manifests.test.mjs scripts/gsap-to-anime/*.test.ts",
"test:skills": "node --test 'skills/**/*.test.mjs'",
"generate:previews": "tsx scripts/generate-template-previews.ts",
"generate:catalog-previews": "tsx scripts/generate-catalog-previews.ts",
diff --git a/scripts/gsap-to-anime/__fixtures__/eases.after.html b/scripts/gsap-to-anime/__fixtures__/eases.after.html
new file mode 100644
index 0000000000..a65ee69ef9
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/eases.after.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/eases.before.html b/scripts/gsap-to-anime/__fixtures__/eases.before.html
new file mode 100644
index 0000000000..decf1230d0
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/eases.before.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/fromto-set.after.html b/scripts/gsap-to-anime/__fixtures__/fromto-set.after.html
new file mode 100644
index 0000000000..881be90b9d
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/fromto-set.after.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/fromto-set.before.html b/scripts/gsap-to-anime/__fixtures__/fromto-set.before.html
new file mode 100644
index 0000000000..5dca45105e
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/fromto-set.before.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/iife-postamble.after.html b/scripts/gsap-to-anime/__fixtures__/iife-postamble.after.html
new file mode 100644
index 0000000000..83048f344f
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/iife-postamble.after.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/iife-postamble.before.html b/scripts/gsap-to-anime/__fixtures__/iife-postamble.before.html
new file mode 100644
index 0000000000..ab86786273
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/iife-postamble.before.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/motionpath-manual.before.html b/scripts/gsap-to-anime/__fixtures__/motionpath-manual.before.html
new file mode 100644
index 0000000000..b381feef7b
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/motionpath-manual.before.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/onupdate-manual.before.html b/scripts/gsap-to-anime/__fixtures__/onupdate-manual.before.html
new file mode 100644
index 0000000000..5f776f0a86
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/onupdate-manual.before.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/passthrough-properties.after.html b/scripts/gsap-to-anime/__fixtures__/passthrough-properties.after.html
new file mode 100644
index 0000000000..20059c7e48
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/passthrough-properties.after.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/passthrough-properties.before.html b/scripts/gsap-to-anime/__fixtures__/passthrough-properties.before.html
new file mode 100644
index 0000000000..59483ea50c
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/passthrough-properties.before.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/positions.after.html b/scripts/gsap-to-anime/__fixtures__/positions.after.html
new file mode 100644
index 0000000000..cc6eeeb31e
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/positions.after.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/positions.before.html b/scripts/gsap-to-anime/__fixtures__/positions.before.html
new file mode 100644
index 0000000000..0929c23caa
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/positions.before.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/repeat-yoyo.after.html b/scripts/gsap-to-anime/__fixtures__/repeat-yoyo.after.html
new file mode 100644
index 0000000000..974ca39d20
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/repeat-yoyo.after.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/repeat-yoyo.before.html b/scripts/gsap-to-anime/__fixtures__/repeat-yoyo.before.html
new file mode 100644
index 0000000000..03ab726b41
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/repeat-yoyo.before.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/simple-to.after.html b/scripts/gsap-to-anime/__fixtures__/simple-to.after.html
new file mode 100644
index 0000000000..52f575cfa4
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/simple-to.after.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/simple-to.before.html b/scripts/gsap-to-anime/__fixtures__/simple-to.before.html
new file mode 100644
index 0000000000..c6ff93fd55
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/simple-to.before.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/splittext-manual.before.html b/scripts/gsap-to-anime/__fixtures__/splittext-manual.before.html
new file mode 100644
index 0000000000..af31740953
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/splittext-manual.before.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/stagger-grid.after.html b/scripts/gsap-to-anime/__fixtures__/stagger-grid.after.html
new file mode 100644
index 0000000000..4a2253f799
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/stagger-grid.after.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/stagger-grid.before.html b/scripts/gsap-to-anime/__fixtures__/stagger-grid.before.html
new file mode 100644
index 0000000000..61cfd174bd
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/stagger-grid.before.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/unrelated-add.after.html b/scripts/gsap-to-anime/__fixtures__/unrelated-add.after.html
new file mode 100644
index 0000000000..8e076f3a88
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/unrelated-add.after.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/unrelated-add.before.html b/scripts/gsap-to-anime/__fixtures__/unrelated-add.before.html
new file mode 100644
index 0000000000..7237e99d6e
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/unrelated-add.before.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/unrelated-callback-config.after.html b/scripts/gsap-to-anime/__fixtures__/unrelated-callback-config.after.html
new file mode 100644
index 0000000000..16c5a1efb9
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/unrelated-callback-config.after.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/__fixtures__/unrelated-callback-config.before.html b/scripts/gsap-to-anime/__fixtures__/unrelated-callback-config.before.html
new file mode 100644
index 0000000000..ff4ec82c57
--- /dev/null
+++ b/scripts/gsap-to-anime/__fixtures__/unrelated-callback-config.before.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/scripts/gsap-to-anime/attr.ts b/scripts/gsap-to-anime/attr.ts
new file mode 100644
index 0000000000..1640928a4f
--- /dev/null
+++ b/scripts/gsap-to-anime/attr.ts
@@ -0,0 +1,117 @@
+export interface AttrEntry {
+ key: string;
+ value: number | string;
+}
+
+export function parseAttrWrapper(value: unknown): AttrEntry[] | null {
+ const raw = rawText(value);
+ if (!raw?.startsWith("{") || !raw.endsWith("}")) return null;
+ const body = raw.slice(1, -1).trim();
+ if (body.length === 0) return [];
+
+ const entries: AttrEntry[] = [];
+ for (const part of splitTopLevel(body, ",")) {
+ const property = splitProperty(part);
+ if (property === null) return null;
+ const key = parseKey(property.key.trim());
+ const parsedValue = parseValue(property.value.trim());
+ if (key === null || parsedValue === null) return null;
+ entries.push({ key, value: parsedValue });
+ }
+ return entries;
+}
+
+function splitProperty(source: string): { key: string; value: string } | null {
+ const colon = findTopLevelColon(source);
+ if (colon === null) return null;
+ return {
+ key: source.slice(0, colon),
+ value: source.slice(colon + 1),
+ };
+}
+
+function splitTopLevel(source: string, separator: string): string[] {
+ const parts: string[] = [];
+ let start = 0;
+ let depth = 0;
+ let quote: string | null = null;
+ let escaped = false;
+
+ for (let index = 0; index < source.length; index += 1) {
+ const char = source[index] ?? "";
+ if (quote !== null) {
+ if (escaped) escaped = false;
+ else if (char === "\\") escaped = true;
+ else if (char === quote) quote = null;
+ continue;
+ }
+ if (char === "'" || char === '"' || char === "`") {
+ quote = char;
+ continue;
+ }
+ if (char === "{" || char === "[" || char === "(") depth += 1;
+ if (char === "}" || char === "]" || char === ")") depth -= 1;
+ if (char === separator && depth === 0) {
+ parts.push(source.slice(start, index).trim());
+ start = index + 1;
+ }
+ }
+ parts.push(source.slice(start).trim());
+ return parts.filter((part) => part.length > 0);
+}
+
+function findTopLevelColon(source: string): number | null {
+ let depth = 0;
+ let quote: string | null = null;
+ let escaped = false;
+
+ for (let index = 0; index < source.length; index += 1) {
+ const char = source[index] ?? "";
+ if (quote !== null) {
+ if (escaped) escaped = false;
+ else if (char === "\\") escaped = true;
+ else if (char === quote) quote = null;
+ continue;
+ }
+ if (char === "'" || char === '"' || char === "`") {
+ quote = char;
+ continue;
+ }
+ if (char === "{" || char === "[" || char === "(") depth += 1;
+ if (char === "}" || char === "]" || char === ")") depth -= 1;
+ if (char === ":" && depth === 0) return index;
+ }
+ return null;
+}
+
+function parseKey(value: string): string | null {
+ if (/^[A-Za-z_$][\w$]*$/.test(value)) return value;
+ return parseStringLiteral(value);
+}
+
+function parseValue(value: string): number | string | null {
+ const numeric = Number(value);
+ if (value.length > 0 && Number.isFinite(numeric)) return numeric;
+ return parseStringLiteral(value);
+}
+
+function parseStringLiteral(value: string): string | null {
+ const quote = value[0];
+ if ((quote !== "'" && quote !== '"') || value[value.length - 1] !== quote) return null;
+ if (quote === '"') {
+ try {
+ const parsed: unknown = JSON.parse(value);
+ return typeof parsed === "string" ? parsed : null;
+ } catch {
+ return null;
+ }
+ }
+ return value.slice(1, -1).replace(/\\'/g, "'").replace(/\\\\/g, "\\");
+}
+
+function rawText(value: unknown): string | null {
+ if (typeof value === "string")
+ return value.startsWith("__raw:") ? value.slice(6).trim() : value.trim();
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
+ return null;
+}
diff --git a/scripts/gsap-to-anime/classifier.ts b/scripts/gsap-to-anime/classifier.ts
new file mode 100644
index 0000000000..67c6f83d49
--- /dev/null
+++ b/scripts/gsap-to-anime/classifier.ts
@@ -0,0 +1,364 @@
+import { resolveEase } from "@hyperframes/core";
+import type { GsapAnimation, ParsedGsap } from "@hyperframes/parsers";
+import { parseAttrWrapper } from "./attr.ts";
+import { addUniqueNote, note, statusFor } from "./notes.ts";
+import { parseRegistrationPostamble } from "./registration.ts";
+import type { ClassificationNote, CodemodClassification } from "./types.ts";
+
+const ALLOWED_EXTRAS = new Set(["stagger", "repeat", "yoyo"]);
+const PLUGIN_NAMES = [
+ "ScrollTrigger",
+ "Draggable",
+ "Flip",
+ "TextPlugin",
+ "DrawSVGPlugin",
+ "MorphSVGPlugin",
+ "Physics2DPlugin",
+ "CustomWiggle",
+];
+
+export function classifyGsapScript(script: string, parsed: ParsedGsap): CodemodClassification {
+ const reasons: ClassificationNote[] = [];
+ const warnings: ClassificationNote[] = [];
+
+ addRawSourceReasons(script, parsed, reasons);
+ addParsedReasons(parsed, reasons);
+ addAnimationReasons(parsed.animations, reasons, warnings);
+ if (parseRegistrationPostamble(parsed.postamble, parsed.timelineVar) === null) {
+ addUniqueNote(
+ reasons,
+ note("unrecognized-postamble", "postamble is not only timeline registration"),
+ );
+ }
+
+ return {
+ status: statusFor(reasons, warnings),
+ reasons,
+ warnings,
+ parsed,
+ };
+}
+
+function addRawSourceReasons(
+ script: string,
+ parsed: ParsedGsap,
+ reasons: ClassificationNote[],
+): void {
+ if (/SplitText/i.test(script) || hasHandRolledTextSplit(script)) {
+ addUniqueNote(
+ reasons,
+ note("splitText", "SplitText or hand-rolled text splitting feeds the timeline"),
+ );
+ }
+ if (/CustomEase\.create\s*\(/.test(script)) {
+ addUniqueNote(
+ reasons,
+ note("customEase-beyond-shim", "CustomEase registrations are outside the inline path shim"),
+ );
+ }
+ if (/MotionPathPlugin|motionPath\s*:/.test(script)) {
+ addUniqueNote(reasons, note("motionPath", "motionPath requires manual conversion"));
+ }
+ if (hasAdvancedGsapUtils(script)) {
+ addUniqueNote(reasons, note("gsap-utils-advanced", "gsap.utils usage beyond toArray"));
+ }
+ if (hasTimelineControlCall(script, parsed.timelineVar)) {
+ addUniqueNote(
+ reasons,
+ note("computed-timeline", "timeline call/add/addPause is not rewritten safely"),
+ );
+ }
+ const tweenBodies = gsapTweenArgumentSources(script, parsed.timelineVar);
+ if (tweenBodies.some(hasTweenCallback)) {
+ addUniqueNote(
+ reasons,
+ note("computed-timeline", "timeline callback properties need manual review"),
+ );
+ }
+ if (tweenBodies.some((body) => /\bdelay\s*:/.test(body))) {
+ addUniqueNote(
+ reasons,
+ note("computed-timeline", "GSAP delay is not represented in parsed placement data"),
+ );
+ }
+ addPluginReasons(script, reasons);
+}
+
+function hasTimelineControlCall(script: string, timelineVar: string): boolean {
+ const escapedVar = escapeRegExp(timelineVar);
+ return new RegExp(`\\b${escapedVar}\\s*\\.\\s*(?:call|add|addPause)\\s*\\(`).test(script);
+}
+
+function gsapTweenArgumentSources(script: string, timelineVar: string): string[] {
+ const escapedVar = escapeRegExp(timelineVar);
+ const calls = new RegExp(
+ `(?:\\b${escapedVar}\\s*\\.\\s*(?:fromTo|from|to|set)|\\bgsap\\s*\\.\\s*set)\\s*\\(`,
+ "g",
+ );
+ const bodies: string[] = [];
+ for (const match of script.matchAll(calls)) {
+ const start = match.index ?? 0;
+ const open = script.indexOf("(", start);
+ const close = findMatchingParen(script, open);
+ if (close !== null) bodies.push(script.slice(open + 1, close));
+ }
+ return bodies;
+}
+
+function findMatchingParen(source: string, open: number): number | null {
+ if (open < 0) return null;
+ let depth = 0;
+ let state: "code" | "lineComment" | "blockComment" | "string" = "code";
+ let quote = "";
+ let escaped = false;
+
+ for (let index = open; index < source.length; index += 1) {
+ const char = source[index] ?? "";
+ const next = source[index + 1] ?? "";
+ const stateResult = updateScanState(state, char, next, quote, escaped);
+ state = stateResult.state;
+ quote = stateResult.quote;
+ escaped = stateResult.escaped;
+ if (stateResult.skipNext) index += 1;
+ if (state !== "code") continue;
+ if (char === "(") depth += 1;
+ if (char === ")") {
+ depth -= 1;
+ if (depth === 0) return index;
+ }
+ }
+ return null;
+}
+
+function updateScanState(
+ state: "code" | "lineComment" | "blockComment" | "string",
+ char: string,
+ next: string,
+ quote: string,
+ escaped: boolean,
+): {
+ state: "code" | "lineComment" | "blockComment" | "string";
+ quote: string;
+ escaped: boolean;
+ skipNext: boolean;
+} {
+ if (state === "lineComment") {
+ return char === "\n" || char === "\r"
+ ? { state: "code", quote, escaped: false, skipNext: false }
+ : { state, quote, escaped: false, skipNext: false };
+ }
+ if (state === "blockComment") {
+ return char === "*" && next === "/"
+ ? { state: "code", quote, escaped: false, skipNext: true }
+ : { state, quote, escaped: false, skipNext: false };
+ }
+ if (state === "string") {
+ if (escaped) return { state, quote, escaped: false, skipNext: false };
+ if (char === "\\") return { state, quote, escaped: true, skipNext: false };
+ if (char === quote) return { state: "code", quote: "", escaped: false, skipNext: false };
+ return { state, quote, escaped: false, skipNext: false };
+ }
+ if (char === "/" && next === "/")
+ return { state: "lineComment", quote, escaped: false, skipNext: true };
+ if (char === "/" && next === "*")
+ return { state: "blockComment", quote, escaped: false, skipNext: true };
+ if (char === "'" || char === '"' || char === "`")
+ return { state: "string", quote: char, escaped: false, skipNext: false };
+ return { state, quote, escaped: false, skipNext: false };
+}
+
+function hasTweenCallback(source: string): boolean {
+ return /\b(onComplete|onStart|onUpdate|onRepeat|onReverseComplete)\s*(?::|\()/.test(source);
+}
+
+function hasHandRolledTextSplit(script: string): boolean {
+ return (
+ /\.split\s*\(\s*["'](?:\s?|)["']\s*\)/.test(script) &&
+ /chars|words|createElement|innerHTML/.test(script)
+ );
+}
+
+function hasAdvancedGsapUtils(script: string): boolean {
+ for (const match of script.matchAll(/gsap\.utils\.([A-Za-z_$][\w$]*)/g)) {
+ if (match[1] !== "toArray") return true;
+ }
+ return false;
+}
+
+function addPluginReasons(script: string, reasons: ClassificationNote[]): void {
+ if (/gsap\.registerPlugin\s*\([^)]*CustomEase/.test(script)) {
+ addUniqueNote(
+ reasons,
+ note("customEase-beyond-shim", "registered CustomEase name needs manual review"),
+ );
+ }
+ if (/gsap\.registerPlugin\s*\([^)]*MotionPathPlugin/.test(script)) {
+ addUniqueNote(reasons, note("motionPath", "registered MotionPathPlugin needs manual review"));
+ }
+ for (const plugin of PLUGIN_NAMES) {
+ if (new RegExp(`\\b${plugin}\\b`).test(script)) {
+ addUniqueNote(
+ reasons,
+ note(`plugin:${plugin}`, `${plugin} plugin usage needs manual review`),
+ );
+ }
+ }
+}
+
+function addParsedReasons(parsed: ParsedGsap, reasons: ClassificationNote[]): void {
+ if (!/^[A-Za-z_$][\w$]*$/.test(parsed.timelineVar)) {
+ addUniqueNote(reasons, note("computed-timeline", "timeline is not a simple identifier"));
+ }
+ if (parsed.multipleTimelines || parsed.unsupportedTimelinePattern) {
+ addUniqueNote(reasons, note("computed-timeline", "multiple or unsupported timelines"));
+ }
+}
+
+function addAnimationReasons(
+ animations: GsapAnimation[],
+ reasons: ClassificationNote[],
+ warnings: ClassificationNote[],
+): void {
+ for (const animation of animations) {
+ if (animation.arcPath)
+ addUniqueNote(reasons, note("motionPath", "motionPath requires manual conversion"));
+ if (
+ (animation.hasUnresolvedKeyframes || animation.hasUnresolvedSelector) &&
+ !hasReason(reasons, "splitText")
+ ) {
+ addUniqueNote(reasons, note("computed-timeline", "unresolved selector or keyframes"));
+ }
+ if (animation.keyframes && !animation.extras?.stagger && !animation.arcPath) {
+ addUniqueNote(reasons, note("keyframes", "native GSAP keyframes need manual review"));
+ }
+ if (animation.resolvedStart === undefined) {
+ addUniqueNote(reasons, note("computed-timeline", "animation start could not be resolved"));
+ }
+ if (animation.provenance && animation.provenance.kind !== "literal") {
+ addUniqueNote(reasons, note("computed-timeline", "computed timeline construction"));
+ }
+ if (isNonSelectorTarget(animation.targetSelector)) {
+ addUniqueNote(
+ reasons,
+ note("non-selector-target", "tween target is not a CSS selector string"),
+ );
+ }
+ addPropertyReasons(animation, reasons, warnings);
+ addExtraReasons(animation, reasons, warnings);
+ addEaseReasons(animation, reasons, warnings);
+ }
+}
+
+function hasReason(reasons: ClassificationNote[], code: string): boolean {
+ return reasons.some((reason) => reason.code === code);
+}
+
+function addPropertyReasons(
+ animation: GsapAnimation,
+ reasons: ClassificationNote[],
+ warnings: ClassificationNote[],
+): void {
+ for (const [key, value] of propertyEntries(animation)) {
+ if (key === "attr" && parseAttrWrapper(value) === null) {
+ addUniqueNote(reasons, note("attr-wrapper", "GSAP attr wrapper is not statically literal"));
+ }
+ if (key === "autoAlpha") {
+ addUniqueNote(
+ warnings,
+ note("autoAlpha-expansion", "autoAlpha expands to opacity and visibility"),
+ );
+ }
+ if (key === "transformPerspective") {
+ addUniqueNote(
+ warnings,
+ note("transformPerspective-perspective", "transformPerspective maps to CSS perspective"),
+ );
+ }
+ }
+}
+
+function propertyEntries(animation: GsapAnimation): Array<[string, number | string]> {
+ return [
+ ...Object.entries(animation.properties),
+ ...Object.entries(animation.fromProperties ?? {}),
+ ];
+}
+
+function isNonSelectorTarget(selector: string): boolean {
+ return selector === "dwell/hold" || selector.startsWith("proxy ");
+}
+
+function addExtraReasons(
+ animation: GsapAnimation,
+ reasons: ClassificationNote[],
+ warnings: ClassificationNote[],
+): void {
+ for (const key of Object.keys(animation.extras ?? {})) {
+ if (!ALLOWED_EXTRAS.has(key)) {
+ addUniqueNote(reasons, note("unsupported-extra", `Unsupported GSAP extra: ${key}`));
+ }
+ }
+ const repeat = animation.extras?.repeat;
+ if (isInfiniteRepeat(repeat)) {
+ addUniqueNote(
+ warnings,
+ note("infinite-repeat", "repeat:-1 or repeat:Infinity creates an infinite loop"),
+ );
+ }
+}
+
+function isInfiniteRepeat(value: unknown): boolean {
+ const raw = rawText(value);
+ return raw === "-1" || raw === "Infinity";
+}
+
+function addEaseReasons(
+ animation: GsapAnimation,
+ reasons: ClassificationNote[],
+ warnings: ClassificationNote[],
+): void {
+ const eases = animationEases(animation);
+ for (const ease of eases) {
+ if (isEaseShimDivergence(ease)) {
+ addUniqueNote(warnings, note("ease-shim-divergence", "ease shim divergence"));
+ }
+ const resolved = resolveEase(ease);
+ if (resolved.warning) {
+ addUniqueNote(
+ reasons,
+ note(
+ "customEase-beyond-shim",
+ `Unrecognized or fallback ease needs manual review: ${ease}`,
+ ),
+ );
+ }
+ }
+}
+
+function animationEases(animation: GsapAnimation): string[] {
+ const eases: string[] = [];
+ if (animation.ease) eases.push(animation.ease);
+ const keyframes = animation.keyframes;
+ if (keyframes?.ease) eases.push(keyframes.ease);
+ if (keyframes?.easeEach) eases.push(keyframes.easeEach);
+ for (const keyframe of keyframes?.keyframes ?? []) {
+ if (keyframe.ease) eases.push(keyframe.ease);
+ }
+ return eases;
+}
+
+function isEaseShimDivergence(ease: string): boolean {
+ const compact = ease.trim().replace(/\s+/g, "").toLowerCase();
+ return compact.startsWith("expo.") || /^steps\(\d+\)$/.test(compact);
+}
+
+function rawText(value: unknown): string | null {
+ if (typeof value === "string")
+ return value.startsWith("__raw:") ? value.slice(6).trim() : value.trim();
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
+ return null;
+}
+
+function escapeRegExp(value: string): string {
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+}
diff --git a/scripts/gsap-to-anime/cli.ts b/scripts/gsap-to-anime/cli.ts
new file mode 100644
index 0000000000..931ff2b09a
--- /dev/null
+++ b/scripts/gsap-to-anime/cli.ts
@@ -0,0 +1,82 @@
+#!/usr/bin/env tsx
+
+import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
+import { resolve } from "node:path";
+import {
+ assertTotalsReconcile,
+ renderMarkdownReport,
+ scanRegistryTree,
+ transformHtml,
+} from "./index.ts";
+
+interface Options {
+ target: string;
+ dryRun: boolean;
+ write: boolean;
+ reportJson: string;
+ reportMd: string;
+}
+
+function main(): void {
+ const options = parseArgs(process.argv.slice(2));
+ const target = resolve(options.target);
+ if (!existsSync(target)) fail(`Path not found: ${target}`);
+ if (statSync(target).isDirectory()) {
+ runDirectory(target, options);
+ return;
+ }
+ runFile(target, options);
+}
+
+function parseArgs(args: string[]): Options {
+ let target = "";
+ let dryRun = false;
+ let write = false;
+ let reportJson = "scripts/gsap-to-anime/dry-run-report.json";
+ let reportMd = "scripts/gsap-to-anime/dry-run-report.md";
+ for (let index = 0; index < args.length; index += 1) {
+ const arg = args[index];
+ if (arg === "--dry-run") dryRun = true;
+ else if (arg === "--write") write = true;
+ else if (arg === "--report-json") reportJson = readValue(args, (index += 1), arg);
+ else if (arg === "--report-md") reportMd = readValue(args, (index += 1), arg);
+ else if (arg && !arg.startsWith("--") && target === "") target = arg;
+ else fail(`Unknown argument: ${arg ?? ""}`);
+ }
+ if (!target)
+ fail(
+ "Usage: node --import tsx scripts/gsap-to-anime/cli.ts [--dry-run] [--write]",
+ );
+ return { target, dryRun, write, reportJson, reportMd };
+}
+
+function readValue(args: string[], index: number, flag: string): string {
+ const value = args[index];
+ if (!value) fail(`${flag} requires a value`);
+ return value;
+}
+
+function runFile(path: string, options: Options): void {
+ const html = readFileSync(path, "utf-8");
+ const result = transformHtml(html);
+ console.log(JSON.stringify(result.classification, null, 2));
+ if (options.write && !options.dryRun && result.changed) {
+ writeFileSync(path, result.html, "utf-8");
+ }
+}
+
+function runDirectory(path: string, options: Options): void {
+ if (!options.dryRun) fail("Directory mode is dry-run only for this codemod unit");
+ const report = scanRegistryTree(path);
+ assertTotalsReconcile(report);
+ writeFileSync(resolve(options.reportJson), `${JSON.stringify(report, null, 2)}\n`, "utf-8");
+ writeFileSync(resolve(options.reportMd), renderMarkdownReport(report), "utf-8");
+ console.log(JSON.stringify(report.totals));
+}
+
+function fail(message: string): never {
+ console.error(message);
+ process.exit(1);
+}
+
+main();
diff --git a/scripts/gsap-to-anime/converter.ts b/scripts/gsap-to-anime/converter.ts
new file mode 100644
index 0000000000..0cf7078818
--- /dev/null
+++ b/scripts/gsap-to-anime/converter.ts
@@ -0,0 +1,339 @@
+import { resolveEase } from "@hyperframes/core";
+import type { GsapAnimation, ParsedGsap } from "@hyperframes/parsers";
+import { extractGsapLabels } from "@hyperframes/parsers/gsap-parser-acorn";
+import { parseAttrWrapper, type AttrEntry } from "./attr.ts";
+import { parseRegistrationPostamble, stripLegacyRegistrySetup } from "./registration.ts";
+
+interface AnimeProperty {
+ key: string;
+ code: string;
+}
+
+const PROP_RENAMES = new Map([
+ ["x", "translateX"],
+ ["y", "translateY"],
+ ["z", "translateZ"],
+ ["rotation", "rotate"],
+ ["rotationX", "rotateX"],
+ ["rotationY", "rotateY"],
+ ["rotationZ", "rotateZ"],
+ // anime.js v4 uses transformOrigin and perspective unchanged. GSAP's
+ // transformPerspective alias is folded into CSS perspective and warned on.
+ ["transformPerspective", "perspective"],
+]);
+
+export function convertGsapScript(script: string, parsed: ParsedGsap): string {
+ const registration = parseRegistrationPostamble(parsed.postamble, parsed.timelineVar);
+ const registrationId = registration?.id ?? "main";
+ const rawPreamble = convertPreamble(parsed.preamble, parsed.timelineVar);
+ const bodyIndent = wrapperBodyIndent(rawPreamble);
+ const preamble = bodyIndent ? indentPreambleBody(rawPreamble, bodyIndent) : rawPreamble;
+ const registrationCall = `${indentBlock(
+ renderRegistration(registrationId, parsed.timelineVar, extractGsapLabels(script)),
+ bodyIndent,
+ )}${renderTrailingCloser(registration?.trailing ?? "")}`;
+ const lines = [
+ preamble,
+ ...parsed.animations.map((animation) =>
+ indentBlock(convertAnimation(animation, parsed.timelineVar), bodyIndent),
+ ),
+ registrationCall,
+ ];
+ return `\n${lines.filter((line) => line.length > 0).join("\n")}\n`;
+}
+
+function convertPreamble(preamble: string, timelineVar: string): string {
+ const withoutRegistry = stripLegacyRegistrySetup(preamble).trim();
+ const rewrittenUtils = rewriteGsapToArray(withoutRegistry);
+ const timelineDecl = new RegExp(
+ `\\b(const|let|var)\\s+${escapeRegExp(timelineVar)}\\s*=\\s*gsap\\.timeline\\s*\\([^;]*\\)\\s*;?`,
+ );
+ const converted = rewrittenUtils.replace(
+ timelineDecl,
+ `const ${timelineVar} = anime.createTimeline({ autoplay: false });`,
+ );
+ return converted
+ .split("\n")
+ .map((line) => line.trimStart())
+ .join("\n");
+}
+
+function rewriteGsapToArray(source: string): string {
+ return source.replace(
+ /gsap\.utils\.toArray\s*\(\s*(["'][^"']+["'])\s*\)/g,
+ "[...document.querySelectorAll($1)]",
+ );
+}
+
+function wrapperBodyIndent(preamble: string): string {
+ const firstLine = preamble.split("\n").find((line) => line.trim().length > 0);
+ return firstLine?.trimEnd().endsWith("{") ? " " : "";
+}
+
+function indentPreambleBody(preamble: string, indent: string): string {
+ const lines = preamble.split("\n");
+ return lines
+ .map((line, index) => (index === 0 || line.trim().length === 0 ? line : `${indent}${line}`))
+ .join("\n");
+}
+
+function indentBlock(block: string, indent: string): string {
+ if (indent.length === 0) return block;
+ return block
+ .split("\n")
+ .map((line) => (line.trim().length === 0 ? line : `${indent}${line}`))
+ .join("\n");
+}
+
+function convertAnimation(animation: GsapAnimation, timelineVar: string): string {
+ const target = JSON.stringify(animation.targetSelector);
+ const properties = buildProperties(animation);
+ const position = milliseconds(animation.resolvedStart ?? 0);
+ return renderAddCall(timelineVar, target, properties, position);
+}
+
+function buildProperties(animation: GsapAnimation): AnimeProperty[] {
+ const entries: AnimeProperty[] = [];
+ for (const [key, value] of Object.entries(animation.properties)) {
+ pushConvertedProperty(entries, animation, key, value);
+ }
+ entries.push({ key: "duration", code: String(durationMs(animation)) });
+ if (animation.ease)
+ entries.push({ key: "ease", code: JSON.stringify(resolveEase(animation.ease).animeEase) });
+ const stagger = renderStagger(animation.extras?.stagger);
+ if (stagger) entries.push({ key: "delay", code: stagger });
+ const repeat = renderRepeat(animation.extras?.repeat);
+ if (repeat) entries.push({ key: "loop", code: repeat });
+ if (rawBoolean(animation.extras?.yoyo) === true) entries.push({ key: "alternate", code: "true" });
+ return entries;
+}
+
+function pushConvertedProperty(
+ entries: AnimeProperty[],
+ animation: GsapAnimation,
+ key: string,
+ value: number | string,
+): void {
+ if (key === "attr") {
+ pushAttrProperties(entries, animation, value);
+ return;
+ }
+ if (key === "autoAlpha") {
+ pushAutoAlpha(entries, animation, value);
+ return;
+ }
+ const animeKey = PROP_RENAMES.get(key) ?? key;
+ entries.push({ key: animeKey, code: renderTweenValue(animation, key, value) });
+}
+
+function pushAttrProperties(
+ entries: AnimeProperty[],
+ animation: GsapAnimation,
+ value: number | string,
+): void {
+ const attrs = parseAttrWrapper(value);
+ if (attrs === null) return;
+ const fromAttrs = parseAttrWrapper(animation.fromProperties?.attr);
+ for (const attr of attrs) {
+ entries.push({ key: attr.key, code: renderAttrTweenValue(animation.method, attr, fromAttrs) });
+ }
+}
+
+function renderAttrTweenValue(
+ method: GsapAnimation["method"],
+ attr: AttrEntry,
+ fromAttrs: AttrEntry[] | null,
+): string {
+ const from = fromAttrs?.find((entry) => entry.key === attr.key);
+ if (method === "fromTo" && from) return serializeArray([from.value, attr.value]);
+ if (method === "from") return serializeArray([attr.value, identityValue(attr.key)]);
+ return serializeValue(attr.value);
+}
+
+function pushAutoAlpha(
+ entries: AnimeProperty[],
+ animation: GsapAnimation,
+ value: number | string,
+): void {
+ const from = animation.fromProperties?.autoAlpha;
+ if (typeof from === "number" || typeof from === "string") {
+ entries.push({ key: "opacity", code: serializeArray([from, value]) });
+ entries.push({
+ key: "visibility",
+ code: serializeArray([visibilityFor(from), visibilityFor(value)]),
+ });
+ return;
+ }
+ entries.push({ key: "opacity", code: serializeValue(value) });
+ entries.push({ key: "visibility", code: serializeValue(visibilityFor(value)) });
+}
+
+function renderTweenValue(animation: GsapAnimation, key: string, value: number | string): string {
+ if (animation.method === "fromTo") {
+ const fromValue = animation.fromProperties?.[key];
+ return fromValue === undefined ? serializeValue(value) : serializeArray([fromValue, value]);
+ }
+ if (animation.method === "from") {
+ return serializeArray([value, identityValue(key)]);
+ }
+ if (animation.method === "to") {
+ const fromValue = animation.fromProperties?.[key];
+ return fromValue === undefined ? serializeValue(value) : serializeArray([fromValue, value]);
+ }
+ return serializeValue(value);
+}
+
+function identityValue(key: string): number | string {
+ if (key === "opacity" || key === "autoAlpha") return 1;
+ if (key === "visibility") return "visible";
+ if (key.startsWith("scale")) return 1;
+ return 0;
+}
+
+function visibilityFor(value: number | string): string {
+ if (typeof value === "number") return value <= 0 ? "hidden" : "visible";
+ return value === "0" || value === "hidden" ? "hidden" : "visible";
+}
+
+function durationMs(animation: GsapAnimation): number {
+ if (animation.method === "set") return 0;
+ return milliseconds(animation.duration ?? 0.5);
+}
+
+function milliseconds(seconds: number): number {
+ return Math.round(seconds * 1000);
+}
+
+function renderStagger(value: unknown): string | null {
+ const raw = rawText(value);
+ if (!raw) return null;
+ if (/function|=>/.test(raw)) return null;
+ const numeric = Number(raw);
+ if (Number.isFinite(numeric)) return `anime.stagger(${milliseconds(numeric)})`;
+ if (!raw.trim().startsWith("{")) return null;
+ return renderObjectStagger(raw);
+}
+
+function renderObjectStagger(raw: string): string | null {
+ const keys = [...raw.matchAll(/([A-Za-z_$][\w$]*)\s*:/g)].map((match) => match[1] ?? "");
+ if (
+ keys.some((key) => !["each", "amount", "from", "grid", "axis", "start", "ease"].includes(key))
+ ) {
+ return null;
+ }
+ const seconds = numericField(raw, "each") ?? numericField(raw, "amount");
+ if (seconds === null) return null;
+ const options: string[] = [];
+ const grid = /\bgrid\s*:\s*(\[[^\]]+\])/.exec(raw)?.[1];
+ const from = /\bfrom\s*:\s*("[^"]+"|'[^']+'|[A-Za-z_$][\w$]*)/.exec(raw)?.[1];
+ const axis = /\baxis\s*:\s*("[^"]+"|'[^']+'|[A-Za-z_$][\w$]*)/.exec(raw)?.[1];
+ const start = numericField(raw, "start");
+ const ease = /\bease\s*:\s*("[^"]+"|'[^']+')/.exec(raw)?.[1];
+ if (grid) options.push(`grid: ${grid}`);
+ if (from) options.push(`from: ${normalizeStringLiteral(from)}`);
+ if (axis) options.push(`axis: ${normalizeStringLiteral(axis)}`);
+ if (start !== null) options.push(`start: ${milliseconds(start)}`);
+ if (ease) options.push(`ease: ${normalizeStringLiteral(ease)}`);
+ return options.length > 0
+ ? `anime.stagger(${milliseconds(seconds)}, { ${options.join(", ")} })`
+ : `anime.stagger(${milliseconds(seconds)})`;
+}
+
+function numericField(raw: string, key: string): number | null {
+ const match = new RegExp(`\\b${key}\\s*:\\s*(-?\\d+(?:\\.\\d+)?)`).exec(raw);
+ if (!match?.[1]) return null;
+ const value = Number(match[1]);
+ return Number.isFinite(value) ? value : null;
+}
+
+function renderRepeat(value: unknown): string | null {
+ const raw = rawText(value);
+ if (!raw) return null;
+ if (raw === "-1" || raw === "Infinity") return "true";
+ const repeat = Number(raw);
+ if (!Number.isFinite(repeat)) return null;
+ return String(Math.max(0, Math.floor(repeat)) + 1);
+}
+
+function rawBoolean(value: unknown): boolean | null {
+ const raw = rawText(value);
+ if (raw === "true") return true;
+ if (raw === "false") return false;
+ return null;
+}
+
+function rawText(value: unknown): string | null {
+ if (typeof value === "string")
+ return value.startsWith("__raw:") ? value.slice(6).trim() : value.trim();
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
+ return null;
+}
+
+function normalizeStringLiteral(value: string): string {
+ if (value.startsWith("'") && value.endsWith("'")) return JSON.stringify(value.slice(1, -1));
+ return value;
+}
+
+function renderRegistration(
+ id: string,
+ timelineVar: string,
+ labels: Array<{ name: string; position: number }>,
+): string {
+ if (labels.length === 0)
+ return `hyperframesAnime.register(${JSON.stringify(id)}, ${timelineVar});`;
+ const labelEntries = labels.map(
+ (label) => `${safeKey(label.name)}: ${formatNumber(label.position)}`,
+ );
+ return `hyperframesAnime.register(${JSON.stringify(id)}, ${timelineVar}, { labels: { ${labelEntries.join(", ")} } });`;
+}
+
+function renderTrailingCloser(trailing: string): string {
+ const closer = trailing.trim();
+ return closer.length === 0 ? "" : `\n${closer}`;
+}
+
+function serializeProperties(properties: AnimeProperty[]): string {
+ return `{ ${properties.map((property) => `${safeKey(property.key)}: ${property.code}`).join(", ")} }`;
+}
+
+function renderAddCall(
+ timelineVar: string,
+ target: string,
+ properties: AnimeProperty[],
+ position: number,
+): string {
+ const props = serializeProperties(properties);
+ const oneLine = `${timelineVar}.add(${target}, ${props}, ${position});`;
+ if (properties.some((property) => property.code.startsWith("[")) && oneLine.length > 95) {
+ return `${timelineVar}.add(\n ${target},\n ${props},\n ${position},\n);`;
+ }
+ if (oneLine.length <= 110) return oneLine;
+ if (properties.some((property) => property.code.includes("anime.stagger"))) {
+ const propLines = properties.map(
+ (property) => ` ${safeKey(property.key)}: ${property.code},`,
+ );
+ return `${timelineVar}.add(\n ${target},\n {\n${propLines.join("\n")}\n },\n ${position},\n);`;
+ }
+ return `${timelineVar}.add(\n ${target},\n ${props},\n ${position},\n);`;
+}
+
+function serializeArray(values: Array): string {
+ return `[${values.map(serializeValue).join(", ")}]`;
+}
+
+function serializeValue(value: number | string): string {
+ return typeof value === "number" ? formatNumber(value) : JSON.stringify(value);
+}
+
+function safeKey(key: string): string {
+ return /^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key);
+}
+
+function formatNumber(value: number): string {
+ const rounded = Math.round(value * 100000) / 100000;
+ return String(rounded);
+}
+
+function escapeRegExp(value: string): string {
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+}
diff --git a/scripts/gsap-to-anime/dry-run-report.json b/scripts/gsap-to-anime/dry-run-report.json
new file mode 100644
index 0000000000..ff9d18bd5c
--- /dev/null
+++ b/scripts/gsap-to-anime/dry-run-report.json
@@ -0,0 +1,4871 @@
+{
+ "root": "/Users/miguel07code/dev/hyperframes-oss/.claude/worktrees/animejs-u9/registry",
+ "totals": {
+ "converted": 39,
+ "converted-with-warnings": 3,
+ "manual": 105
+ },
+ "items": [
+ {
+ "kind": "block",
+ "name": "app-showcase",
+ "path": "blocks/app-showcase",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/app-showcase/app-showcase.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "apple-money-count",
+ "path": "blocks/apple-money-count",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/apple-money-count/apple-money-count.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "blue-sweater-intro-video",
+ "path": "blocks/blue-sweater-intro-video",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "unsupported-extra",
+ "message": "Unsupported GSAP extra: overwrite"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "keyframes",
+ "message": "native GSAP keyframes need manual review"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/blue-sweater-intro-video/blue-sweater-intro-video.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "unsupported-extra",
+ "message": "Unsupported GSAP extra: overwrite"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "keyframes",
+ "message": "native GSAP keyframes need manual review"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "chromatic-radial-split",
+ "path": "blocks/chromatic-radial-split",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/chromatic-radial-split/chromatic-radial-split.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "cinematic-zoom",
+ "path": "blocks/cinematic-zoom",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/cinematic-zoom/cinematic-zoom.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-3d-extrude",
+ "path": "blocks/code-3d-extrude",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-3d-extrude/code-3d-extrude.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-diff",
+ "path": "blocks/code-diff",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-diff/code-diff.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-highlight",
+ "path": "blocks/code-highlight",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-highlight/code-highlight.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-morph",
+ "path": "blocks/code-morph",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "multiple or unsupported timelines"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-morph/code-morph.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "multiple or unsupported timelines"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-particle-assemble",
+ "path": "blocks/code-particle-assemble",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-particle-assemble/code-particle-assemble.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-scroll",
+ "path": "blocks/code-scroll",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-scroll/code-scroll.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-shader-dissolve",
+ "path": "blocks/code-shader-dissolve",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-shader-dissolve/code-shader-dissolve.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-apple-terminal-basic",
+ "path": "blocks/code-snippet-apple-terminal-basic",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-apple-terminal-basic/code-snippet-apple-terminal-basic.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-apple-terminal-clear-dark",
+ "path": "blocks/code-snippet-apple-terminal-clear-dark",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-apple-terminal-clear-dark/code-snippet-apple-terminal-clear-dark.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-apple-terminal-clear-light",
+ "path": "blocks/code-snippet-apple-terminal-clear-light",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-apple-terminal-clear-light/code-snippet-apple-terminal-clear-light.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-apple-terminal-grass",
+ "path": "blocks/code-snippet-apple-terminal-grass",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-apple-terminal-grass/code-snippet-apple-terminal-grass.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-apple-terminal-homebrew",
+ "path": "blocks/code-snippet-apple-terminal-homebrew",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-apple-terminal-homebrew/code-snippet-apple-terminal-homebrew.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-apple-terminal-man-page",
+ "path": "blocks/code-snippet-apple-terminal-man-page",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-apple-terminal-man-page/code-snippet-apple-terminal-man-page.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-apple-terminal-novel",
+ "path": "blocks/code-snippet-apple-terminal-novel",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-apple-terminal-novel/code-snippet-apple-terminal-novel.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-apple-terminal-ocean",
+ "path": "blocks/code-snippet-apple-terminal-ocean",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-apple-terminal-ocean/code-snippet-apple-terminal-ocean.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-apple-terminal-pro",
+ "path": "blocks/code-snippet-apple-terminal-pro",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-apple-terminal-pro/code-snippet-apple-terminal-pro.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-apple-terminal-red-sands",
+ "path": "blocks/code-snippet-apple-terminal-red-sands",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-apple-terminal-red-sands/code-snippet-apple-terminal-red-sands.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-apple-terminal-silver-aerogel",
+ "path": "blocks/code-snippet-apple-terminal-silver-aerogel",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-apple-terminal-silver-aerogel/code-snippet-apple-terminal-silver-aerogel.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-apple-terminal-solid-colors",
+ "path": "blocks/code-snippet-apple-terminal-solid-colors",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-apple-terminal-solid-colors/code-snippet-apple-terminal-solid-colors.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-dark-2026",
+ "path": "blocks/code-snippet-dark-2026",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/code-snippet-dark-2026/code-snippet-dark-2026.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-dark-modern",
+ "path": "blocks/code-snippet-dark-modern",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/code-snippet-dark-modern/code-snippet-dark-modern.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-dark-plus",
+ "path": "blocks/code-snippet-dark-plus",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/code-snippet-dark-plus/code-snippet-dark-plus.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-flight",
+ "path": "blocks/code-snippet-flight",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-snippet-flight/code-snippet-flight.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-high-contrast",
+ "path": "blocks/code-snippet-high-contrast",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/code-snippet-high-contrast/code-snippet-high-contrast.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-high-contrast-light",
+ "path": "blocks/code-snippet-high-contrast-light",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/code-snippet-high-contrast-light/code-snippet-high-contrast-light.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-light-2026",
+ "path": "blocks/code-snippet-light-2026",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/code-snippet-light-2026/code-snippet-light-2026.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-light-modern",
+ "path": "blocks/code-snippet-light-modern",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/code-snippet-light-modern/code-snippet-light-modern.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-light-plus",
+ "path": "blocks/code-snippet-light-plus",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/code-snippet-light-plus/code-snippet-light-plus.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-monokai",
+ "path": "blocks/code-snippet-monokai",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/code-snippet-monokai/code-snippet-monokai.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-solarized-light",
+ "path": "blocks/code-snippet-solarized-light",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/code-snippet-solarized-light/code-snippet-solarized-light.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-visual-studio-dark",
+ "path": "blocks/code-snippet-visual-studio-dark",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/code-snippet-visual-studio-dark/code-snippet-visual-studio-dark.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-snippet-visual-studio-light",
+ "path": "blocks/code-snippet-visual-studio-light",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/code-snippet-visual-studio-light/code-snippet-visual-studio-light.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "code-typing",
+ "path": "blocks/code-typing",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/code-typing/code-typing.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "cross-warp-morph",
+ "path": "blocks/cross-warp-morph",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/cross-warp-morph/cross-warp-morph.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "data-chart",
+ "path": "blocks/data-chart",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "attr-wrapper",
+ "message": "GSAP attr wrapper is not statically literal"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/data-chart/data-chart.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "attr-wrapper",
+ "message": "GSAP attr wrapper is not statically literal"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "domain-warp-dissolve",
+ "path": "blocks/domain-warp-dissolve",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/domain-warp-dissolve/domain-warp-dissolve.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "flash-through-white",
+ "path": "blocks/flash-through-white",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/flash-through-white/flash-through-white.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "flowchart",
+ "path": "blocks/flowchart",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unsupported-extra",
+ "message": "Unsupported GSAP extra: overwrite"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/flowchart/flowchart.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unsupported-extra",
+ "message": "Unsupported GSAP extra: overwrite"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "flowchart-vertical",
+ "path": "blocks/flowchart-vertical",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unsupported-extra",
+ "message": "Unsupported GSAP extra: overwrite"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/flowchart-vertical/flowchart-vertical.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unsupported-extra",
+ "message": "Unsupported GSAP extra: overwrite"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "glitch",
+ "path": "blocks/glitch",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/glitch/glitch.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "gravitational-lens",
+ "path": "blocks/gravitational-lens",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/gravitational-lens/gravitational-lens.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "instagram-follow",
+ "path": "blocks/instagram-follow",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/instagram-follow/instagram-follow.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "ios26-liquid-glass",
+ "path": "blocks/ios26-liquid-glass",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "customEase-beyond-shim",
+ "message": "Unrecognized or fallback ease needs manual review: __raw:driftEase"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/ios26-liquid-glass/ios26-liquid-glass.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "customEase-beyond-shim",
+ "message": "Unrecognized or fallback ease needs manual review: __raw:driftEase"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "light-leak",
+ "path": "blocks/light-leak",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/light-leak/light-leak.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "liquid-glass-context-menu",
+ "path": "blocks/liquid-glass-context-menu",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/liquid-glass-context-menu/liquid-glass-context-menu.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "liquid-glass-media-controls",
+ "path": "blocks/liquid-glass-media-controls",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/liquid-glass-media-controls/liquid-glass-media-controls.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "liquid-glass-notification",
+ "path": "blocks/liquid-glass-notification",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/liquid-glass-notification/liquid-glass-notification.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "liquid-glass-widgets",
+ "path": "blocks/liquid-glass-widgets",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/liquid-glass-widgets/liquid-glass-widgets.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "logo-outro",
+ "path": "blocks/logo-outro",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/logo-outro/logo-outro.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "lower-third-bild",
+ "path": "blocks/lower-third-bild",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/lower-third-bild/lower-third-bild.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "lt-accent-underline",
+ "path": "blocks/lt-accent-underline",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/lt-accent-underline/lt-accent-underline.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "lt-bold-block",
+ "path": "blocks/lt-bold-block",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/lt-bold-block/lt-bold-block.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "lt-clean-bar",
+ "path": "blocks/lt-clean-bar",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/lt-clean-bar/lt-clean-bar.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "lt-color-block",
+ "path": "blocks/lt-color-block",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/lt-color-block/lt-color-block.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "lt-dark-card",
+ "path": "blocks/lt-dark-card",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/lt-dark-card/lt-dark-card.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "lt-kicker-name",
+ "path": "blocks/lt-kicker-name",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/lt-kicker-name/lt-kicker-name.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "lt-mask-reveal",
+ "path": "blocks/lt-mask-reveal",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/lt-mask-reveal/lt-mask-reveal.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "lt-side-rule",
+ "path": "blocks/lt-side-rule",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/lt-side-rule/lt-side-rule.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "lt-soft-pill",
+ "path": "blocks/lt-soft-pill",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/lt-soft-pill/lt-soft-pill.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "lt-stack-bars",
+ "path": "blocks/lt-stack-bars",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/lt-stack-bars/lt-stack-bars.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "macos-notification",
+ "path": "blocks/macos-notification",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/macos-notification/macos-notification.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "macos-tahoe-liquid-glass",
+ "path": "blocks/macos-tahoe-liquid-glass",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "customEase-beyond-shim",
+ "message": "Unrecognized or fallback ease needs manual review: __raw:driftEase"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/macos-tahoe-liquid-glass/macos-tahoe-liquid-glass.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "customEase-beyond-shim",
+ "message": "Unrecognized or fallback ease needs manual review: __raw:driftEase"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "news-ticker",
+ "path": "blocks/news-ticker",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/news-ticker/news-ticker.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "north-korea-locked-down",
+ "path": "blocks/north-korea-locked-down",
+ "status": "converted-with-warnings",
+ "changed": true,
+ "reasons": [],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/north-korea-locked-down/north-korea-locked-down.html",
+ "status": "converted-with-warnings",
+ "changed": true,
+ "reasons": [],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "nyc-paris-flight",
+ "path": "blocks/nyc-paris-flight",
+ "status": "converted-with-warnings",
+ "changed": true,
+ "reasons": [],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/nyc-paris-flight/nyc-paris-flight.html",
+ "status": "converted-with-warnings",
+ "changed": true,
+ "reasons": [],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "reddit-post",
+ "path": "blocks/reddit-post",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/reddit-post/reddit-post.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "ridged-burn",
+ "path": "blocks/ridged-burn",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/ridged-burn/ridged-burn.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "ripple-waves",
+ "path": "blocks/ripple-waves",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/ripple-waves/ripple-waves.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "sdf-iris",
+ "path": "blocks/sdf-iris",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/sdf-iris/sdf-iris.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "spain-map",
+ "path": "blocks/spain-map",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/spain-map/spain-map.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "spotify-card",
+ "path": "blocks/spotify-card",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/spotify-card/spotify-card.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "swirl-vortex",
+ "path": "blocks/swirl-vortex",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/swirl-vortex/swirl-vortex.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "thermal-distortion",
+ "path": "blocks/thermal-distortion",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/thermal-distortion/thermal-distortion.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "tiktok-follow",
+ "path": "blocks/tiktok-follow",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/tiktok-follow/tiktok-follow.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-3d",
+ "path": "blocks/transitions-3d",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "plugin:Flip",
+ "message": "Flip plugin usage needs manual review"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-3d/transitions-3d.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "plugin:Flip",
+ "message": "Flip plugin usage needs manual review"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-blur",
+ "path": "blocks/transitions-blur",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-blur/transitions-blur.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-cover",
+ "path": "blocks/transitions-cover",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-cover/transitions-cover.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-destruction",
+ "path": "blocks/transitions-destruction",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-destruction/transitions-destruction.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-dissolve",
+ "path": "blocks/transitions-dissolve",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-dissolve/transitions-dissolve.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-distortion",
+ "path": "blocks/transitions-distortion",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-distortion/transitions-distortion.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-grid",
+ "path": "blocks/transitions-grid",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-grid/transitions-grid.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-light",
+ "path": "blocks/transitions-light",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-light/transitions-light.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-mechanical",
+ "path": "blocks/transitions-mechanical",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-mechanical/transitions-mechanical.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-other",
+ "path": "blocks/transitions-other",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-other/transitions-other.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-push",
+ "path": "blocks/transitions-push",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-push/transitions-push.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-radial",
+ "path": "blocks/transitions-radial",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-radial/transitions-radial.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "transitions-scale",
+ "path": "blocks/transitions-scale",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/transitions-scale/transitions-scale.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "ui-3d-reveal",
+ "path": "blocks/ui-3d-reveal",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/ui-3d-reveal/ui-3d-reveal.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "us-map",
+ "path": "blocks/us-map",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/us-map/us-map.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "us-map-bubble",
+ "path": "blocks/us-map-bubble",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "attr-wrapper",
+ "message": "GSAP attr wrapper is not statically literal"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/us-map-bubble/us-map-bubble.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "attr-wrapper",
+ "message": "GSAP attr wrapper is not statically literal"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "us-map-flow",
+ "path": "blocks/us-map-flow",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/us-map-flow/us-map-flow.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "us-map-hex",
+ "path": "blocks/us-map-hex",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/us-map-hex/us-map-hex.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "vfx-iphone-device",
+ "path": "blocks/vfx-iphone-device",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "customEase-beyond-shim",
+ "message": "Unrecognized or fallback ease needs manual review: __raw:breatheEase"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/vfx-iphone-device/vfx-iphone-device.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "customEase-beyond-shim",
+ "message": "Unrecognized or fallback ease needs manual review: __raw:breatheEase"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "vfx-liquid-background",
+ "path": "blocks/vfx-liquid-background",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/vfx-liquid-background/vfx-liquid-background.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "splitText",
+ "message": "SplitText or hand-rolled text splitting feeds the timeline"
+ },
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "vfx-liquid-glass",
+ "path": "blocks/vfx-liquid-glass",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/vfx-liquid-glass/vfx-liquid-glass.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "vfx-magnetic",
+ "path": "blocks/vfx-magnetic",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/vfx-magnetic/vfx-magnetic.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "vfx-portal",
+ "path": "blocks/vfx-portal",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/vfx-portal/vfx-portal.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "vfx-shatter",
+ "path": "blocks/vfx-shatter",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/vfx-shatter/vfx-shatter.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "vfx-text-cursor",
+ "path": "blocks/vfx-text-cursor",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "customEase-beyond-shim",
+ "message": "Unrecognized or fallback ease needs manual review: __raw:entryEase"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "blocks/vfx-text-cursor/vfx-text-cursor.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "customEase-beyond-shim",
+ "message": "Unrecognized or fallback ease needs manual review: __raw:entryEase"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "vpn-youtube-spot",
+ "path": "blocks/vpn-youtube-spot",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/vpn-youtube-spot/vpn-youtube-spot.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "whip-pan",
+ "path": "blocks/whip-pan",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/whip-pan/whip-pan.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "world-map",
+ "path": "blocks/world-map",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/world-map/world-map.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "x-post",
+ "path": "blocks/x-post",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/x-post/x-post.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "name": "yt-lower-third",
+ "path": "blocks/yt-lower-third",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "blocks/yt-lower-third/yt-lower-third.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-blend-difference",
+ "path": "components/caption-blend-difference",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-blend-difference/caption-blend-difference.html",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-clip-wipe",
+ "path": "components/caption-clip-wipe",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-clip-wipe/caption-clip-wipe.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-editorial-emphasis",
+ "path": "components/caption-editorial-emphasis",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-editorial-emphasis/caption-editorial-emphasis.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-emoji-pop",
+ "path": "components/caption-emoji-pop",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-emoji-pop/caption-emoji-pop.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-glitch-rgb",
+ "path": "components/caption-glitch-rgb",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-glitch-rgb/caption-glitch-rgb.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-gradient-fill",
+ "path": "components/caption-gradient-fill",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-gradient-fill/caption-gradient-fill.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-highlight",
+ "path": "components/caption-highlight",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-highlight/caption-highlight.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-kinetic-slam",
+ "path": "components/caption-kinetic-slam",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "components/caption-kinetic-slam/caption-kinetic-slam.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-matrix-decode",
+ "path": "components/caption-matrix-decode",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-matrix-decode/caption-matrix-decode.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-neon-accent",
+ "path": "components/caption-neon-accent",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-neon-accent/caption-neon-accent.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-neon-glow",
+ "path": "components/caption-neon-glow",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "components/caption-neon-glow/caption-neon-glow.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-parallax-layers",
+ "path": "components/caption-parallax-layers",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-parallax-layers/caption-parallax-layers.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-particle-burst",
+ "path": "components/caption-particle-burst",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-particle-burst/caption-particle-burst.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-pill-karaoke",
+ "path": "components/caption-pill-karaoke",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-pill-karaoke/caption-pill-karaoke.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-texture",
+ "path": "components/caption-texture",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-texture/caption-texture.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "caption-weight-shift",
+ "path": "components/caption-weight-shift",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/caption-weight-shift/caption-weight-shift.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "grain-overlay",
+ "path": "components/grain-overlay",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/grain-overlay/grain-overlay.html",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "grid-pixelate-wipe",
+ "path": "components/grid-pixelate-wipe",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/grid-pixelate-wipe/grid-pixelate-wipe.html",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "morph-text",
+ "path": "components/morph-text",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/morph-text/morph-text.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "motion-blur",
+ "path": "components/motion-blur",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/motion-blur/motion-blur.html",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "parallax-unzoom",
+ "path": "components/parallax-unzoom",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/parallax-unzoom/parallax-unzoom.html",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "parallax-zoom",
+ "path": "components/parallax-zoom",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/parallax-zoom/parallax-zoom.html",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "shimmer-sweep",
+ "path": "components/shimmer-sweep",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/shimmer-sweep/shimmer-sweep.html",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "texture-mask-text",
+ "path": "components/texture-mask-text",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/texture-mask-text/texture-mask-text.html",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "component",
+ "name": "vignette",
+ "path": "components/vignette",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "components/vignette/vignette.html",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "airbnb-deck",
+ "path": "examples/airbnb-deck",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "examples/airbnb-deck/demo.html",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": []
+ },
+ {
+ "path": "examples/airbnb-deck/index.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "decision-tree",
+ "path": "examples/decision-tree",
+ "status": "manual",
+ "changed": true,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unsupported-extra",
+ "message": "Unsupported GSAP extra: overwrite"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "examples/decision-tree/compositions/decision_tree.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline call/add/addPause is not rewritten safely"
+ },
+ {
+ "code": "unsupported-extra",
+ "message": "Unsupported GSAP extra: overwrite"
+ }
+ ],
+ "warnings": []
+ },
+ {
+ "path": "examples/decision-tree/index.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "kinetic-type",
+ "path": "examples/kinetic-type",
+ "status": "converted-with-warnings",
+ "changed": true,
+ "reasons": [],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "examples/kinetic-type/compositions/main-graphics.html",
+ "status": "converted-with-warnings",
+ "changed": true,
+ "reasons": [],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ },
+ {
+ "path": "examples/kinetic-type/index.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "motion-blur",
+ "path": "examples/motion-blur",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "examples/motion-blur/index.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ },
+ {
+ "code": "unrecognized-postamble",
+ "message": "postamble is not only timeline registration"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "nyt-graph",
+ "path": "examples/nyt-graph",
+ "status": "manual",
+ "changed": true,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "attr-wrapper",
+ "message": "GSAP attr wrapper is not statically literal"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "examples/nyt-graph/compositions/nyt-chart.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "attr-wrapper",
+ "message": "GSAP attr wrapper is not statically literal"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ },
+ {
+ "path": "examples/nyt-graph/index.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "play-mode",
+ "path": "examples/play-mode",
+ "status": "manual",
+ "changed": true,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "autoAlpha-expansion",
+ "message": "autoAlpha expands to opacity and visibility"
+ }
+ ],
+ "files": [
+ {
+ "path": "examples/play-mode/compositions/captions.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "autoAlpha-expansion",
+ "message": "autoAlpha expands to opacity and visibility"
+ }
+ ]
+ },
+ {
+ "path": "examples/play-mode/compositions/intro.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "GSAP delay is not represented in parsed placement data"
+ }
+ ],
+ "warnings": []
+ },
+ {
+ "path": "examples/play-mode/compositions/stats.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ }
+ ],
+ "warnings": []
+ },
+ {
+ "path": "examples/play-mode/index.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "product-promo",
+ "path": "examples/product-promo",
+ "status": "manual",
+ "changed": true,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "GSAP delay is not represented in parsed placement data"
+ },
+ {
+ "code": "unsupported-extra",
+ "message": "Unsupported GSAP extra: overwrite"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "examples/product-promo/compositions/scene1-logo-intro.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "GSAP delay is not represented in parsed placement data"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ },
+ {
+ "path": "examples/product-promo/compositions/scene2-4-canvas.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ },
+ {
+ "code": "unsupported-extra",
+ "message": "Unsupported GSAP extra: overwrite"
+ },
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ },
+ {
+ "path": "examples/product-promo/compositions/scene5-logo-outro.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ },
+ {
+ "path": "examples/product-promo/index.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "slideshow-demo",
+ "path": "examples/slideshow-demo",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": [],
+ "files": [
+ {
+ "path": "examples/slideshow-demo/index.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "startup-pitch",
+ "path": "examples/startup-pitch",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "examples/startup-pitch/demo.html",
+ "status": "converted",
+ "changed": false,
+ "reasons": [],
+ "warnings": []
+ },
+ {
+ "path": "examples/startup-pitch/index.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "non-selector-target",
+ "message": "tween target is not a CSS selector string"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "swiss-grid",
+ "path": "examples/swiss-grid",
+ "status": "manual",
+ "changed": true,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "examples/swiss-grid/compositions/captions.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": []
+ },
+ {
+ "path": "examples/swiss-grid/compositions/graphics.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ },
+ {
+ "path": "examples/swiss-grid/compositions/intro.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ },
+ {
+ "path": "examples/swiss-grid/index.html",
+ "status": "converted-with-warnings",
+ "changed": true,
+ "reasons": [],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "vignelli",
+ "path": "examples/vignelli",
+ "status": "manual",
+ "changed": true,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ],
+ "files": [
+ {
+ "path": "examples/vignelli/compositions/captions.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "unresolved selector or keyframes"
+ }
+ ],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ },
+ {
+ "path": "examples/vignelli/compositions/overlays.html",
+ "status": "converted-with-warnings",
+ "changed": true,
+ "reasons": [],
+ "warnings": [
+ {
+ "code": "ease-shim-divergence",
+ "message": "ease shim divergence"
+ }
+ ]
+ },
+ {
+ "path": "examples/vignelli/index.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "vscode-theme-visualizer",
+ "path": "examples/vscode-theme-visualizer",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "examples/vscode-theme-visualizer/index.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline is not a simple identifier"
+ }
+ ],
+ "warnings": []
+ }
+ ]
+ },
+ {
+ "kind": "example",
+ "name": "warm-grain",
+ "path": "examples/warm-grain",
+ "status": "manual",
+ "changed": true,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ }
+ ],
+ "warnings": [],
+ "files": [
+ {
+ "path": "examples/warm-grain/compositions/captions.html",
+ "status": "manual",
+ "changed": false,
+ "reasons": [
+ {
+ "code": "computed-timeline",
+ "message": "timeline callback properties need manual review"
+ }
+ ],
+ "warnings": []
+ },
+ {
+ "path": "examples/warm-grain/compositions/graphics.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ },
+ {
+ "path": "examples/warm-grain/compositions/intro.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ },
+ {
+ "path": "examples/warm-grain/index.html",
+ "status": "converted",
+ "changed": true,
+ "reasons": [],
+ "warnings": []
+ }
+ ]
+ }
+ ]
+}
diff --git a/scripts/gsap-to-anime/dry-run-report.md b/scripts/gsap-to-anime/dry-run-report.md
new file mode 100644
index 0000000000..2c7138241e
--- /dev/null
+++ b/scripts/gsap-to-anime/dry-run-report.md
@@ -0,0 +1,170 @@
+# GSAP to anime.js dry-run report
+
+## Totals
+
+- converted: 39
+- converted-with-warnings: 3
+- manual: 105
+- total: 147
+
+## Manual reason breakdown
+
+- computed-timeline: 99
+- unrecognized-postamble: 40
+- non-selector-target: 33
+- splitText: 13
+- unsupported-extra: 5
+- customEase-beyond-shim: 4
+- attr-wrapper: 3
+- keyframes: 1
+- plugin:Flip: 1
+
+## Items
+
+- manual: blocks/app-showcase (computed-timeline)
+- manual: blocks/apple-money-count (computed-timeline, non-selector-target)
+- manual: blocks/blue-sweater-intro-video (unsupported-extra, computed-timeline, keyframes)
+- manual: blocks/chromatic-radial-split (computed-timeline)
+- manual: blocks/cinematic-zoom (computed-timeline)
+- manual: blocks/code-3d-extrude (non-selector-target, unrecognized-postamble)
+- manual: blocks/code-diff (computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: blocks/code-highlight (computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: blocks/code-morph (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-particle-assemble (computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: blocks/code-scroll (computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: blocks/code-shader-dissolve (computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: blocks/code-snippet-apple-terminal-basic (splitText, computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-apple-terminal-clear-dark (splitText, computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-apple-terminal-clear-light (splitText, computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-apple-terminal-grass (splitText, computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-apple-terminal-homebrew (splitText, computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-apple-terminal-man-page (splitText, computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-apple-terminal-novel (splitText, computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-apple-terminal-ocean (splitText, computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-apple-terminal-pro (splitText, computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-apple-terminal-red-sands (splitText, computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-apple-terminal-silver-aerogel (splitText, computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-apple-terminal-solid-colors (splitText, computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-dark-2026 (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-dark-modern (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-dark-plus (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-flight (computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: blocks/code-snippet-high-contrast (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-high-contrast-light (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-light-2026 (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-light-modern (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-light-plus (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-monokai (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-solarized-light (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-visual-studio-dark (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-snippet-visual-studio-light (computed-timeline, unrecognized-postamble)
+- manual: blocks/code-typing (computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: blocks/cross-warp-morph (computed-timeline)
+- manual: blocks/data-chart (computed-timeline, attr-wrapper, non-selector-target)
+- manual: blocks/domain-warp-dissolve (computed-timeline)
+- manual: blocks/flash-through-white (computed-timeline)
+- manual: blocks/flowchart (computed-timeline, unsupported-extra)
+- manual: blocks/flowchart-vertical (computed-timeline, unsupported-extra)
+- manual: blocks/glitch (computed-timeline)
+- manual: blocks/gravitational-lens (computed-timeline)
+- converted: blocks/instagram-follow
+- manual: blocks/ios26-liquid-glass (computed-timeline, non-selector-target, customEase-beyond-shim)
+- manual: blocks/light-leak (computed-timeline)
+- manual: blocks/liquid-glass-context-menu (non-selector-target)
+- manual: blocks/liquid-glass-media-controls (computed-timeline, non-selector-target)
+- manual: blocks/liquid-glass-notification (computed-timeline, non-selector-target)
+- manual: blocks/liquid-glass-widgets (computed-timeline, non-selector-target)
+- manual: blocks/logo-outro (computed-timeline)
+- converted: blocks/lower-third-bild
+- converted: blocks/lt-accent-underline
+- converted: blocks/lt-bold-block
+- converted: blocks/lt-clean-bar
+- converted: blocks/lt-color-block
+- converted: blocks/lt-dark-card
+- converted: blocks/lt-kicker-name
+- converted: blocks/lt-mask-reveal
+- converted: blocks/lt-side-rule
+- converted: blocks/lt-soft-pill
+- converted: blocks/lt-stack-bars
+- converted: blocks/macos-notification
+- manual: blocks/macos-tahoe-liquid-glass (computed-timeline, non-selector-target, customEase-beyond-shim)
+- converted: blocks/news-ticker
+- converted-with-warnings: blocks/north-korea-locked-down
+- converted-with-warnings: blocks/nyc-paris-flight
+- manual: blocks/reddit-post (computed-timeline)
+- manual: blocks/ridged-burn (computed-timeline)
+- manual: blocks/ripple-waves (computed-timeline)
+- manual: blocks/sdf-iris (computed-timeline)
+- converted: blocks/spain-map
+- converted: blocks/spotify-card
+- manual: blocks/swirl-vortex (computed-timeline)
+- manual: blocks/thermal-distortion (computed-timeline)
+- converted: blocks/tiktok-follow
+- manual: blocks/transitions-3d (plugin:Flip)
+- converted: blocks/transitions-blur
+- manual: blocks/transitions-cover (computed-timeline)
+- manual: blocks/transitions-destruction (computed-timeline, non-selector-target)
+- converted: blocks/transitions-dissolve
+- manual: blocks/transitions-distortion (computed-timeline)
+- manual: blocks/transitions-grid (computed-timeline)
+- converted: blocks/transitions-light
+- converted: blocks/transitions-mechanical
+- converted: blocks/transitions-other
+- converted: blocks/transitions-push
+- converted: blocks/transitions-radial
+- converted: blocks/transitions-scale
+- manual: blocks/ui-3d-reveal (computed-timeline)
+- converted: blocks/us-map
+- manual: blocks/us-map-bubble (computed-timeline, attr-wrapper)
+- manual: blocks/us-map-flow (computed-timeline, non-selector-target)
+- manual: blocks/us-map-hex (computed-timeline)
+- manual: blocks/vfx-iphone-device (computed-timeline, non-selector-target, customEase-beyond-shim)
+- manual: blocks/vfx-liquid-background (splitText, computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: blocks/vfx-liquid-glass (non-selector-target, unrecognized-postamble)
+- manual: blocks/vfx-magnetic (computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: blocks/vfx-portal (computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: blocks/vfx-shatter (computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: blocks/vfx-text-cursor (computed-timeline, non-selector-target, customEase-beyond-shim)
+- converted: blocks/vpn-youtube-spot
+- manual: blocks/whip-pan (computed-timeline)
+- manual: blocks/world-map (computed-timeline)
+- manual: blocks/x-post (computed-timeline)
+- converted: blocks/yt-lower-third
+- converted: components/caption-blend-difference
+- manual: components/caption-clip-wipe (computed-timeline)
+- manual: components/caption-editorial-emphasis (computed-timeline, non-selector-target)
+- manual: components/caption-emoji-pop (computed-timeline)
+- manual: components/caption-glitch-rgb (computed-timeline)
+- manual: components/caption-gradient-fill (computed-timeline)
+- manual: components/caption-highlight (computed-timeline)
+- manual: components/caption-kinetic-slam (computed-timeline)
+- manual: components/caption-matrix-decode (computed-timeline)
+- manual: components/caption-neon-accent (computed-timeline)
+- manual: components/caption-neon-glow (computed-timeline)
+- manual: components/caption-parallax-layers (computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: components/caption-particle-burst (computed-timeline)
+- converted: components/caption-pill-karaoke
+- manual: components/caption-texture (computed-timeline)
+- manual: components/caption-weight-shift (computed-timeline)
+- converted: components/grain-overlay
+- converted: components/grid-pixelate-wipe
+- manual: components/morph-text (computed-timeline, non-selector-target)
+- converted: components/motion-blur
+- converted: components/parallax-unzoom
+- converted: components/parallax-zoom
+- converted: components/shimmer-sweep
+- converted: components/texture-mask-text
+- converted: components/vignette
+- manual: examples/airbnb-deck (non-selector-target)
+- manual: examples/decision-tree (computed-timeline, unsupported-extra)
+- converted-with-warnings: examples/kinetic-type
+- manual: examples/motion-blur (computed-timeline, non-selector-target, unrecognized-postamble)
+- manual: examples/nyt-graph (computed-timeline, attr-wrapper, non-selector-target)
+- manual: examples/play-mode (computed-timeline)
+- manual: examples/product-promo (computed-timeline, unsupported-extra, non-selector-target)
+- converted: examples/slideshow-demo
+- manual: examples/startup-pitch (non-selector-target)
+- manual: examples/swiss-grid (computed-timeline)
+- manual: examples/vignelli (computed-timeline)
+- manual: examples/vscode-theme-visualizer (computed-timeline)
+- manual: examples/warm-grain (computed-timeline)
diff --git a/scripts/gsap-to-anime/gsap-to-anime.test.ts b/scripts/gsap-to-anime/gsap-to-anime.test.ts
new file mode 100644
index 0000000000..86fade9721
--- /dev/null
+++ b/scripts/gsap-to-anime/gsap-to-anime.test.ts
@@ -0,0 +1,133 @@
+import assert from "node:assert/strict";
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+import { describe, it } from "node:test";
+import { transformHtml } from "./index.ts";
+
+const FIXTURES = join(import.meta.dirname, "__fixtures__");
+
+function fixture(name: string): string {
+ return readFileSync(join(FIXTURES, name), "utf-8");
+}
+
+function reasonCodes(result: ReturnType): string[] {
+ return result.classification.reasons.map((reason) => reason.code);
+}
+
+function warningCodes(result: ReturnType): string[] {
+ return result.classification.warnings.map((warning) => warning.code);
+}
+
+describe("gsap-to-anime transform", () => {
+ it("converts a simple to chain", () => {
+ const result = transformHtml(fixture("simple-to.before.html"));
+
+ assert.equal(result.classification.status, "converted");
+ assert.equal(result.changed, true);
+ assert.equal(result.html, fixture("simple-to.after.html"));
+ });
+
+ it("converts fromTo and set calls", () => {
+ const result = transformHtml(fixture("fromto-set.before.html"));
+
+ assert.equal(result.classification.status, "converted");
+ assert.equal(result.html, fixture("fromto-set.after.html"));
+ });
+
+ it("uses absolute millisecond positions for labels and relative GSAP positions", () => {
+ const result = transformHtml(fixture("positions.before.html"));
+
+ assert.equal(result.classification.status, "converted");
+ assert.equal(result.html, fixture("positions.after.html"));
+ });
+
+ it("converts a stagger grid to anime.stagger", () => {
+ const result = transformHtml(fixture("stagger-grid.before.html"));
+
+ assert.equal(result.classification.status, "converted");
+ assert.equal(result.html, fixture("stagger-grid.after.html"));
+ });
+
+ it("converts supported ease families and warns for expo or steps shim divergence", () => {
+ const result = transformHtml(fixture("eases.before.html"));
+
+ assert.equal(result.classification.status, "converted-with-warnings");
+ assert.deepEqual(warningCodes(result), ["ease-shim-divergence"]);
+ assert.equal(result.html, fixture("eases.after.html"));
+ });
+
+ it("converts repeat and yoyo to loop and alternate", () => {
+ const result = transformHtml(fixture("repeat-yoyo.before.html"));
+
+ assert.equal(result.classification.status, "converted");
+ assert.equal(result.html, fixture("repeat-yoyo.after.html"));
+ });
+
+ it("ignores unrelated .add calls outside the parsed timeline", () => {
+ const result = transformHtml(fixture("unrelated-add.before.html"));
+
+ assert.equal(result.classification.status, "converted");
+ assert.equal(result.html, fixture("unrelated-add.after.html"));
+ });
+
+ it("passes through arbitrary properties and unwraps GSAP attr wrappers", () => {
+ const result = transformHtml(fixture("passthrough-properties.before.html"));
+
+ assert.equal(result.classification.status, "converted");
+ assert.equal(result.html, fixture("passthrough-properties.after.html"));
+ });
+
+ it("preserves a trailing IIFE close after timeline registration", () => {
+ const result = transformHtml(fixture("iife-postamble.before.html"));
+
+ assert.equal(result.classification.status, "converted");
+ assert.equal(result.html, fixture("iife-postamble.after.html"));
+ });
+
+ it("scopes callback and delay scans to GSAP tween arguments", () => {
+ const result = transformHtml(fixture("unrelated-callback-config.before.html"));
+
+ assert.equal(result.classification.status, "converted");
+ assert.equal(result.html, fixture("unrelated-callback-config.after.html"));
+ });
+
+ it("classifies motionPath as manual and leaves the file unchanged", () => {
+ const before = fixture("motionpath-manual.before.html");
+ const result = transformHtml(before);
+
+ assert.equal(result.classification.status, "manual");
+ assert.deepEqual(reasonCodes(result), ["motionPath"]);
+ assert.equal(result.changed, false);
+ assert.equal(result.html, before);
+ });
+
+ it("classifies a mixed convertible and SplitText script as manual as a whole", () => {
+ const before = fixture("splittext-manual.before.html");
+ const result = transformHtml(before);
+
+ assert.equal(result.classification.status, "manual");
+ assert.deepEqual(reasonCodes(result), ["splitText"]);
+ assert.equal(result.changed, false);
+ assert.equal(result.html, before);
+ });
+
+ it("classifies real tween callbacks as manual", () => {
+ const before = fixture("onupdate-manual.before.html");
+ const result = transformHtml(before);
+
+ assert.equal(result.classification.status, "manual");
+ assert.equal(result.changed, false);
+ assert.ok(reasonCodes(result).includes("computed-timeline"));
+ assert.ok(reasonCodes(result).includes("non-selector-target"));
+ assert.equal(result.html, before);
+ });
+
+ it("is idempotent on already-converted anime code", () => {
+ const html = fixture("simple-to.after.html");
+ const result = transformHtml(html);
+
+ assert.equal(result.classification.status, "converted");
+ assert.equal(result.changed, false);
+ assert.equal(result.html, html);
+ });
+});
diff --git a/scripts/gsap-to-anime/html.ts b/scripts/gsap-to-anime/html.ts
new file mode 100644
index 0000000000..db058dc098
--- /dev/null
+++ b/scripts/gsap-to-anime/html.ts
@@ -0,0 +1,39 @@
+import type { ScriptBlock } from "./types.ts";
+
+const INLINE_SCRIPT_RE = /".length,
+ };
+ const score = timelineMethodScore(scriptText);
+ if (score > bestScore) {
+ best = block;
+ bestScore = score;
+ }
+ }
+ return best;
+}
+
+export function replaceScriptBlock(html: string, block: ScriptBlock, scriptText: string): string {
+ return `${html.slice(0, block.start)}${scriptText}${html.slice(block.end)}`;
+}
+
+function timelineMethodScore(scriptText: string): number {
+ let score = 0;
+ for (const match of scriptText.matchAll(/\.(?:set|to|from|fromTo)\s*\(/g)) {
+ if (match[0]) score += 1;
+ }
+ return score;
+}
diff --git a/scripts/gsap-to-anime/index.ts b/scripts/gsap-to-anime/index.ts
new file mode 100644
index 0000000000..ecd9e0810f
--- /dev/null
+++ b/scripts/gsap-to-anime/index.ts
@@ -0,0 +1,56 @@
+import { parseGsapScriptAcorn } from "@hyperframes/parsers/gsap-parser-acorn";
+import { classifyGsapScript } from "./classifier.ts";
+import { convertGsapScript } from "./converter.ts";
+import { findGsapScriptBlock, replaceScriptBlock } from "./html.ts";
+import type { TransformResult } from "./types.ts";
+
+export type {
+ ClassificationNote,
+ CodemodClassification,
+ CodemodStatus,
+ RegistryFileReport,
+ RegistryItemReport,
+ RegistryReport,
+ TransformResult,
+} from "./types.ts";
+export { classifyGsapScript } from "./classifier.ts";
+export { scanRegistryTree, renderMarkdownReport, assertTotalsReconcile } from "./report.ts";
+
+export function transformHtml(html: string): TransformResult {
+ const block = findGsapScriptBlock(html);
+ if (block === null) {
+ return {
+ html,
+ changed: false,
+ classification: { status: "converted", reasons: [], warnings: [] },
+ };
+ }
+
+ const parsed = parseGsapScriptAcorn(block.scriptText);
+ const classification = classifyGsapScript(block.scriptText, parsed);
+ if (classification.status === "manual") {
+ return { html, changed: false, classification };
+ }
+
+ const script = preserveClosingIndent(
+ convertGsapScript(block.scriptText, parsed),
+ block.scriptText,
+ );
+ const nextHtml = replaceScriptBlock(html, block, script);
+ return { html: nextHtml, changed: nextHtml !== html, classification };
+}
+
+function preserveClosingIndent(script: string, previousScript: string): string {
+ const codeIndent = /\n([ \t]*)\S/.exec(previousScript)?.[1] ?? "";
+ const closingIndent = /\n([ \t]*)$/.exec(previousScript)?.[1] ?? "";
+ const lines = trimCommonIndent(script.trim().split("\n"));
+ return `\n${lines.map((line) => `${codeIndent}${line}`).join("\n")}\n${closingIndent}`;
+}
+
+function trimCommonIndent(lines: string[]): string[] {
+ const indents = lines
+ .filter((line) => line.trim().length > 0)
+ .map((line) => line.match(/^[ \t]*/)?.[0].length ?? 0);
+ const common = indents.length > 0 ? Math.min(...indents) : 0;
+ return common > 0 ? lines.map((line) => line.slice(common)) : lines;
+}
diff --git a/scripts/gsap-to-anime/notes.ts b/scripts/gsap-to-anime/notes.ts
new file mode 100644
index 0000000000..b5855584d6
--- /dev/null
+++ b/scripts/gsap-to-anime/notes.ts
@@ -0,0 +1,27 @@
+import type { ClassificationNote, CodemodStatus } from "./types.ts";
+
+export function note(code: string, message = code): ClassificationNote {
+ return { code, message };
+}
+
+export function addUniqueNote(notes: ClassificationNote[], next: ClassificationNote): void {
+ if (notes.some((note) => note.code === next.code)) return;
+ notes.push(next);
+}
+
+export function statusFor(
+ reasons: ClassificationNote[],
+ warnings: ClassificationNote[],
+): CodemodStatus {
+ if (reasons.length > 0) return "manual";
+ if (warnings.length > 0) return "converted-with-warnings";
+ return "converted";
+}
+
+export function mergeNotes(groups: ClassificationNote[][]): ClassificationNote[] {
+ const merged: ClassificationNote[] = [];
+ for (const group of groups) {
+ for (const entry of group) addUniqueNote(merged, entry);
+ }
+ return merged;
+}
diff --git a/scripts/gsap-to-anime/registration.ts b/scripts/gsap-to-anime/registration.ts
new file mode 100644
index 0000000000..c9eddc2ba5
--- /dev/null
+++ b/scripts/gsap-to-anime/registration.ts
@@ -0,0 +1,50 @@
+import type { RegistrationInfo } from "./types.ts";
+
+function escapeRegExp(value: string): string {
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+}
+
+function stripComments(source: string): string {
+ return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/[^\n\r]*/g, "$1");
+}
+
+function removeRegistrySetup(source: string): string {
+ return source
+ .replace(/window\.__timelines\s*=\s*window\.__timelines\s*\|\|\s*\{\}\s*;?/g, "")
+ .replace(/window\.__timelines\s*=\s*window\.__timelines\s*\|\|\s*\[\]\s*;?/g, "")
+ .replace(
+ /if\s*\(\s*!window\.__timelines\s*\)\s*\{\s*window\.__timelines\s*=\s*\{\}\s*;?\s*\}/g,
+ "",
+ );
+}
+
+export function parseRegistrationPostamble(
+ postamble: string,
+ timelineVar: string,
+): RegistrationInfo | null {
+ const trimmed = stripComments(postamble).trim();
+ if (trimmed === "") return { id: "main", trailing: "" };
+
+ const withoutSetup = removeRegistrySetup(trimmed);
+ const escapedVar = escapeRegExp(timelineVar);
+ const assignment = new RegExp(
+ `window\\.__timelines\\s*\\[\\s*["']([^"']+)["']\\s*\\]\\s*=\\s*${escapedVar}\\s*;?`,
+ "g",
+ );
+ const push = new RegExp(`window\\.__timelines\\.push\\s*\\(\\s*${escapedVar}\\s*\\)\\s*;?`, "g");
+ let id = "main";
+ let consumed = withoutSetup.replace(assignment, (_match, key: string) => {
+ id = key;
+ return "";
+ });
+ consumed = consumed.replace(push, "");
+ return isClosingTail(consumed) ? { id, trailing: consumed } : null;
+}
+
+export function stripLegacyRegistrySetup(preamble: string): string {
+ return removeRegistrySetup(preamble);
+}
+
+function isClosingTail(source: string): boolean {
+ return /^[\s});(]*$/.test(source);
+}
diff --git a/scripts/gsap-to-anime/report.ts b/scripts/gsap-to-anime/report.ts
new file mode 100644
index 0000000000..b3399f380d
--- /dev/null
+++ b/scripts/gsap-to-anime/report.ts
@@ -0,0 +1,219 @@
+import { existsSync, readdirSync, readFileSync } from "node:fs";
+import { join, relative } from "node:path";
+import { transformHtml } from "./index.ts";
+import { mergeNotes } from "./notes.ts";
+import type {
+ CodemodStatus,
+ RegistryFileReport,
+ RegistryItemReport,
+ RegistryReport,
+} from "./types.ts";
+
+interface RegistryFileEntry {
+ path: string;
+ type: string;
+}
+
+interface RegistryManifest {
+ name: string;
+ type: string;
+ files: RegistryFileEntry[];
+}
+
+const STATUSES: CodemodStatus[] = ["converted", "converted-with-warnings", "manual"];
+
+export function scanRegistryTree(root: string): RegistryReport {
+ const items = discoverRegistryItems(root).map((item) => scanRegistryItem(root, item));
+ const totals = createTotals();
+ for (const item of items) totals[item.status] += 1;
+ return { root, totals, items };
+}
+
+export function renderMarkdownReport(report: RegistryReport): string {
+ const manualBreakdown = manualReasonCounts(report);
+ const lines = [
+ "# GSAP to anime.js dry-run report",
+ "",
+ "## Totals",
+ "",
+ `- converted: ${report.totals.converted}`,
+ `- converted-with-warnings: ${report.totals["converted-with-warnings"]}`,
+ `- manual: ${report.totals.manual}`,
+ `- total: ${report.items.length}`,
+ "",
+ "## Manual reason breakdown",
+ "",
+ ...renderReasonLines(manualBreakdown),
+ "",
+ "## Items",
+ "",
+ ...report.items.map(renderItemLine),
+ "",
+ ];
+ return `${lines.join("\n")}`;
+}
+
+function discoverRegistryItems(root: string): RegistryManifest[] {
+ const manifests: RegistryManifest[] = [];
+ for (const kind of ["blocks", "components", "examples"]) {
+ const dir = join(root, kind);
+ if (!existsSync(dir)) continue;
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
+ if (!entry.isDirectory()) continue;
+ const itemDir = join(dir, entry.name);
+ const manifest = readManifest(join(itemDir, "registry-item.json"));
+ manifests.push(manifest ?? fallbackManifest(kind, entry.name, itemDir));
+ }
+ }
+ return manifests.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));
+}
+
+function readManifest(path: string): RegistryManifest | null {
+ if (!existsSync(path)) return null;
+ const raw: unknown = JSON.parse(readFileSync(path, "utf-8"));
+ if (!isRecord(raw)) return null;
+ const name = typeof raw.name === "string" ? raw.name : null;
+ const type = typeof raw.type === "string" ? raw.type : null;
+ const files = Array.isArray(raw.files) ? readFiles(raw.files) : [];
+ return name && type ? { name, type, files } : null;
+}
+
+function fallbackManifest(kind: string, name: string, itemDir: string): RegistryManifest {
+ return {
+ name,
+ type: `hyperframes:${kind.slice(0, -1)}`,
+ files: discoverHtmlFiles(itemDir, itemDir).map((path) => ({
+ path,
+ type: "hyperframes:composition",
+ })),
+ };
+}
+
+function discoverHtmlFiles(root: string, dir: string): string[] {
+ const files: string[] = [];
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
+ const path = join(dir, entry.name);
+ if (entry.isDirectory()) {
+ files.push(...discoverHtmlFiles(root, path));
+ } else if (entry.isFile() && entry.name.endsWith(".html")) {
+ files.push(relative(root, path));
+ }
+ }
+ return files.sort((a, b) => a.localeCompare(b));
+}
+
+function readFiles(files: unknown[]): RegistryFileEntry[] {
+ const entries: RegistryFileEntry[] = [];
+ for (const file of files) {
+ if (!isRecord(file)) continue;
+ if (typeof file.path !== "string" || typeof file.type !== "string") continue;
+ entries.push({ path: file.path, type: file.type });
+ }
+ return entries;
+}
+
+function scanRegistryItem(root: string, manifest: RegistryManifest): RegistryItemReport {
+ const kindDir = kindDirectory(manifest.type);
+ const itemDir = join(root, kindDir, manifest.name);
+ const files = compositionFiles(itemDir, manifest)
+ .map((file) => scanRegistryFile(root, itemDir, file))
+ .sort((a, b) => a.path.localeCompare(b.path));
+ const status = combineStatus(files);
+ return {
+ kind: kindDir.slice(0, -1),
+ name: manifest.name,
+ path: relative(root, itemDir),
+ status,
+ changed: files.some((file) => file.changed),
+ reasons: mergeNotes(files.map((file) => file.reasons)),
+ warnings: mergeNotes(files.map((file) => file.warnings)),
+ files,
+ };
+}
+
+function compositionFiles(itemDir: string, manifest: RegistryManifest): RegistryFileEntry[] {
+ const htmlFiles = manifest.files.filter(
+ (file) =>
+ file.path.endsWith(".html") &&
+ existsSync(join(itemDir, file.path)) &&
+ (file.type === "hyperframes:composition" || file.type === "hyperframes:snippet"),
+ );
+ if (htmlFiles.length > 0) return htmlFiles;
+ const present = manifest.files.filter(
+ (file) => file.path.endsWith(".html") && existsSync(join(itemDir, file.path)),
+ );
+ if (present.length > 0) return present;
+ return readdirSync(itemDir, { withFileTypes: true })
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".html"))
+ .map((entry) => ({ path: entry.name, type: "hyperframes:composition" }));
+}
+
+function scanRegistryFile(
+ root: string,
+ itemDir: string,
+ file: RegistryFileEntry,
+): RegistryFileReport {
+ const path = join(itemDir, file.path);
+ const result = transformHtml(readFileSync(path, "utf-8"));
+ return {
+ path: relative(root, path),
+ status: result.classification.status,
+ changed: result.changed,
+ reasons: result.classification.reasons,
+ warnings: result.classification.warnings,
+ };
+}
+
+function combineStatus(files: RegistryFileReport[]): CodemodStatus {
+ if (files.some((file) => file.status === "manual")) return "manual";
+ if (files.some((file) => file.status === "converted-with-warnings"))
+ return "converted-with-warnings";
+ return "converted";
+}
+
+function kindDirectory(type: string): string {
+ if (type.endsWith(":component")) return "components";
+ if (type.endsWith(":example")) return "examples";
+ return "blocks";
+}
+
+function createTotals(): Record {
+ return {
+ converted: 0,
+ "converted-with-warnings": 0,
+ manual: 0,
+ };
+}
+
+function manualReasonCounts(report: RegistryReport): Map {
+ const counts = new Map();
+ for (const item of report.items) {
+ if (item.status !== "manual") continue;
+ for (const reason of item.reasons) counts.set(reason.code, (counts.get(reason.code) ?? 0) + 1);
+ }
+ return new Map([...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])));
+}
+
+function renderReasonLines(counts: Map): string[] {
+ if (counts.size === 0) return ["- none"];
+ return [...counts.entries()].map(([reason, count]) => `- ${reason}: ${count}`);
+}
+
+function renderItemLine(item: RegistryItemReport): string {
+ const notes =
+ item.reasons.length > 0 ? ` (${item.reasons.map((reason) => reason.code).join(", ")})` : "";
+ return `- ${item.status}: ${item.path}${notes}`;
+}
+
+function isRecord(value: unknown): value is Record {
+ return typeof value === "object" && value !== null;
+}
+
+export function assertTotalsReconcile(report: RegistryReport): void {
+ const total = STATUSES.reduce((sum, status) => sum + report.totals[status], 0);
+ if (total !== report.items.length) {
+ throw new Error(
+ `Dry-run totals do not reconcile: ${total} totals for ${report.items.length} items`,
+ );
+ }
+}
diff --git a/scripts/gsap-to-anime/types.ts b/scripts/gsap-to-anime/types.ts
new file mode 100644
index 0000000000..20261a2e6b
--- /dev/null
+++ b/scripts/gsap-to-anime/types.ts
@@ -0,0 +1,57 @@
+import type { ParsedGsap } from "@hyperframes/parsers";
+
+export type CodemodStatus = "converted" | "converted-with-warnings" | "manual";
+
+export interface ClassificationNote {
+ code: string;
+ message: string;
+}
+
+export interface CodemodClassification {
+ status: CodemodStatus;
+ reasons: ClassificationNote[];
+ warnings: ClassificationNote[];
+ parsed?: ParsedGsap;
+}
+
+export interface TransformResult {
+ html: string;
+ changed: boolean;
+ classification: CodemodClassification;
+}
+
+export interface ScriptBlock {
+ scriptText: string;
+ start: number;
+ end: number;
+}
+
+export interface RegistrationInfo {
+ id: string;
+ trailing: string;
+}
+
+export interface RegistryFileReport {
+ path: string;
+ status: CodemodStatus;
+ changed: boolean;
+ reasons: ClassificationNote[];
+ warnings: ClassificationNote[];
+}
+
+export interface RegistryItemReport {
+ kind: string;
+ name: string;
+ path: string;
+ status: CodemodStatus;
+ changed: boolean;
+ reasons: ClassificationNote[];
+ warnings: ClassificationNote[];
+ files: RegistryFileReport[];
+}
+
+export interface RegistryReport {
+ root: string;
+ totals: Record;
+ items: RegistryItemReport[];
+}