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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
- run: pnpm lint
- name: Install ffmpeg
run: sudo apt-get update && sudo apt-get install -y ffmpeg
- name: Install Playwright Chromium
Expand Down
8 changes: 8 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
**/dist
out
**/*.d.ts
packages/core/src/textMetrics.ts
packages/reframe-video/preview
benchmark
pnpm-lock.yaml
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"printWidth": 100,
"semi": true,
"singleQuote": false,
"trailingComma": "all"
}
41 changes: 41 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Flat ESLint config. Syntactic (not type-aware): eslint:recommended +
// typescript-eslint recommended, with eslint-config-prettier last to drop
// stylistic rules that would fight a formatter. Type-aware rules (which need
// parserOptions.project) are intentionally off — strict `tsc` already covers
// type correctness; this layer catches obvious bugs (unused vars, unreachable
// code, etc.) without the cost of project-wide type info.
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import prettier from "eslint-config-prettier";
import globals from "globals";

export default tseslint.config(
{
ignores: [
"**/dist/**",
"**/out/**",
"**/node_modules/**",
"**/*.d.ts",
"packages/core/src/textMetrics.ts", // generated by gen-text-metrics.ts
"packages/reframe-video/preview/**", // build-copied from packages/preview (untracked)
"benchmark/**",
],
},
js.configs.recommended,
...tseslint.configs.recommended,
prettier,
{
rules: {
// allow intentionally-unused names prefixed with _ (e.g. a kept-for-signature arg)
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_" },
],
},
},
{
// standalone Node scripts (build/tooling) run outside the bundler — give them Node globals
files: ["**/*.{mjs,cjs,mts,cts}", "**/scripts/**"],
languageOptions: { globals: globals.node },
},
);
2 changes: 1 addition & 1 deletion examples/scenes/character-show.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// The motion is generated, not hand-keyed.

import {
scene, group, ellipse, rect, text,
scene, group, ellipse, text,
seq, tween, wait, oscillate,
humanoid, characterPreset,
} from "@reframe/core";
Expand Down
2 changes: 1 addition & 1 deletion examples/scenes/cursor-fx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import {
scene, group, rect, text,
seq, par, tween, wait,
seq, tween, wait,
cursor, cursorTo, cursorClick,
} from "@reframe/core";

Expand Down
1 change: 0 additions & 1 deletion examples/scenes/data-explainer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
scene,
group,
rect,
text,
seq,
Expand Down
2 changes: 1 addition & 1 deletion examples/scenes/dof-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import {
scene, rect, text, group,
seq, par, tween, wait,
seq, tween, wait,
type NodeIR,
} from "@reframe/core";

Expand Down
1 change: 0 additions & 1 deletion examples/scenes/faux-3d-cards.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
scene,
group,
rect,
text,
seq,
Expand Down
2 changes: 1 addition & 1 deletion examples/scenes/figure-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// characterPreset("wave"). The art is a skin; the rig + motion are the asset.

import {
scene, group, ellipse, text,
scene, ellipse, text,
seq, par, tween, wait, oscillate,
figure, characterPreset,
} from "@reframe/core";
Expand Down
1 change: 0 additions & 1 deletion examples/scenes/flow-diagram.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
scene,
group,
rect,
line,
ellipse,
Expand Down
1 change: 0 additions & 1 deletion examples/scenes/glyph-reveal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const PLATES = 18;
const CUT = 0.15; // seconds per plate — the recipe's 0.12–0.18 sweet spot
const CUTS_END = PLATES * CUT;
const LOGO_HOLD = 1.9;
const TOTAL = CUTS_END + 0.05 + LOGO_HOLD;

// painter's order: later plates stack on top, so a cut is just a reveal
const plates = Array.from({ length: PLATES }, (_, i) => ({
Expand Down
2 changes: 1 addition & 1 deletion examples/scenes/gradient-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from "@reframe/core";

const W = 1920, H = 1080;
const BG = "#0A0C14", CARD = "#11141E", FG = "#EDEFF5", DIM = "#7C859B";
const BG = "#0A0C14", FG = "#EDEFF5", DIM = "#7C859B";

const STAR = "M0 -150 L35 -49 L143 -46 L57 19 L88 121 L0 60 L-88 121 L-57 19 L-143 -46 L-35 -49 Z";

Expand Down
4 changes: 2 additions & 2 deletions examples/scenes/kinetic-manifesto.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
scene, text,
seq, par, beat, tween, wait, cameraTo,
seq, par, beat, wait, cameraTo,
splitText, textIn, textOut, textLoop,
type NodeIR, type BehaviorIR,
type BehaviorIR,
} from "@reframe/core";

// "MANIFESTO" — pure kinetic typography. Five lines each enter with a different
Expand Down
4 changes: 2 additions & 2 deletions examples/scenes/liquid-glass-showcase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// synth score. Pure shapes (no image) → also plays live in `reframe player`.

import {
scene, group, rect, ellipse, path, text,
seq, par, stagger, tween, wait, oscillate, cameraTo,
scene, group, rect, ellipse, text,
seq, par, stagger, tween, wait, oscillate,
linearGradient, radialGradient,
type NodeIR, type TimelineIR,
} from "@reframe/core";
Expand Down
2 changes: 1 addition & 1 deletion examples/scenes/perspective-cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// and a DOLLY (animate camera.perspective to flatten the field). mp4 + live in player.

import {
scene, group, rect, text,
scene, rect, text,
seq, par, stagger, tween, wait, cameraTo, splitText, textIn,
linearGradient,
type NodeIR,
Expand Down
1 change: 0 additions & 1 deletion examples/scenes/reframe-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
oscillate,
type AudioCueIR,
type NodeIR,
type TimelineIR,
} from "@reframe/core";

// The reframe demo, made with reframe. Six chapters in one scene:
Expand Down
2 changes: 1 addition & 1 deletion examples/scenes/rocket-launch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
scene, group, rect, ellipse, path, text,
seq, par, stagger, beat, tween, wait, oscillate, wiggle, cameraTo, motionPath,
seq, par, beat, tween, wait, oscillate, wiggle, cameraTo, motionPath,
linearGradient, radialGradient,
type NodeIR, type BehaviorIR,
} from "@reframe/core";
Expand Down
2 changes: 1 addition & 1 deletion examples/scenes/shadow-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import {
scene, rect, ellipse, text,
seq, par, tween, wait, oscillate,
linearGradient, radialGradient, glow, dropShadow,
linearGradient, radialGradient, dropShadow,
type NodeIR,
} from "@reframe/core";

Expand Down
2 changes: 1 addition & 1 deletion examples/scenes/survive-cut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// for the literal render round-trip). Pure / deterministic. Plays live in player too.

import {
scene, group, ellipse, rect, text, path, cursor, cursorTo, cursorClick,
scene, group, ellipse, rect, text, cursor, cursorTo, cursorClick,
seq, par, tween, wait, oscillate, glow,
type NodeIR,
} from "@reframe/core";
Expand Down
4 changes: 2 additions & 2 deletions examples/scenes/zoom-to-space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
// the zoom. Pure primitives, deterministic.

import {
scene, group, rect, text, path, ellipse, line,
scene, group, rect, text, path, ellipse,
seq, par, tween, wait, oscillate,
} from "@reframe/core";
import { LAND_PATHS } from "./lib/world-earth.js";

const W = 1920, H = 1080, CX = W / 2, CY = 540;
const OCEAN = "#16447E", LAND = "#3E7A52", LAND2 = "#5C8B4A";
const ATMO = "#2E6BC0", WHITE = "#FFFFFF", HUD = "#8FB4E8", DIM = "#5C6B86";
const ATMO = "#2E6BC0", WHITE = "#FFFFFF", HUD = "#8FB4E8";

const EARTH_R = 760; // stage-local Earth radius
const EC: [number, number] = [0, 0]; // top-down: Earth centre = city = pivot at screen centre
Expand Down
2 changes: 1 addition & 1 deletion labs/scenes/character-rig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// the hand meet a target. No hand-authored group tree; the rig compiles to IR.

import {
scene, group, ellipse, rect, text,
scene, group, ellipse, text,
seq, par, tween, wait, oscillate,
humanoid, poseTo, ikReach,
} from "@reframe/core";
Expand Down
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,24 @@
"scripts": {
"reframe": "tsx packages/render-cli/src/reframe.ts",
"typecheck": "pnpm -r --no-bail exec tsc --noEmit",
"lint": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"test": "vitest run",
"preview": "pnpm --filter @reframe/preview dev",
"demo:overlay": "npx tsx examples/scripts/demo-edit-survival.ts",
"gallery": "tsx examples/scripts/render-gallery.ts",
"clean": "mkdir -p out/_keep && find out -mindepth 1 -maxdepth 1 ! -name _keep -exec rm -rf {} +"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"eslint": "^10.5.0",
"eslint-config-prettier": "^10.1.8",
"globals": "^17.6.0",
"prettier": "^3.8.4",
"tsx": "^4.19.0",
"typescript": "^5.8.0",
"typescript-eslint": "^8.61.1",
"vitest": "^3.0.0"
}
}
2 changes: 1 addition & 1 deletion packages/core/src/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function locate(segCount: number, u: number): { i: number; t: number } {
return { i, t: scaled - i };
}

/** The four control points for the segment starting at index i (clamped/wrapped ends). */
/** The four control points for the segment starting at index i (clamped/wrapped ends). */
function controls(points: Pt[], closed: boolean, i: number): [Pt, Pt, Pt, Pt] {
const n = points.length;
const at = (k: number): Pt => {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/rig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export function ikReach(upper: number, lower: number, dx: number, dy: number, fl
return [deg(theta1), deg(theta2)];
}

export interface HumanoidOpts extends Omit<RigOpts, never> {}
export type HumanoidOpts = Omit<RigOpts, never>;

/** A ready upright humanoid skeleton — the one-call body. Joints:
* chest, head, armUpper/LowerL, armUpper/LowerR, legUpper/LowerL, legUpper/LowerR.
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/camera.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { localMatrix, evaluate, type Mat2D } from "../src/evaluate.js";
import { cameraMatrix } from "../src/camera.js";
import { scene, rect, group, text, seq, tween, motionPath, oscillate } from "../src/dsl.js";
import { scene, rect, group, text, tween, motionPath, oscillate } from "../src/dsl.js";
import { compileScene } from "../src/compile.js";
import { SceneValidationError } from "../src/validate.js";

Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/characterPreset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe("characterPreset", () => {
expect(JSON.stringify(a)).toBe(JSON.stringify(a2)); // reproducible
expect(JSON.stringify(a)).not.toBe(JSON.stringify(b)); // seed varies it
// same family: same beat name + same set of targeted joints
expect((a as any).name).toBe((b as any).name);
expect((a as { name?: string }).name).toBe((b as { name?: string }).name);
const ids = (t: TimelineIR) => [...new Set(tweens(t).map((x) => x.target))].sort();
expect(ids(a)).toEqual(ids(b));
});
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/labelAnchor.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { scene, rect, seq, par, beat, tween, wait } from "../src/dsl.js";
import { compileScene } from "../src/compile.js";
import { validateScene, SceneValidationError } from "../src/validate.js";
import { SceneValidationError } from "../src/validate.js";

// A montage-like base: a sequential "track" with labeled steps, plus an overlay
// layer (a title beat) anchored to one of those labels via `at: "<label>"`.
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/onramp.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { compileScene } from "../src/compile.js";
import { scene, text, tween, seq } from "../src/dsl.js";
import { scene, text, tween } from "../src/dsl.js";
import { evaluate } from "../src/evaluate.js";
import { row, column, grid } from "../src/layout.js";

Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/perspective.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { localMatrix, evaluate, type Mat2D } from "../src/evaluate.js";
import { scene, rect, group, text, seq, tween } from "../src/dsl.js";
import { scene, rect, group, seq, tween } from "../src/dsl.js";
import { compileScene } from "../src/compile.js";

const size = { width: 1000, height: 800 };
Expand Down
5 changes: 4 additions & 1 deletion packages/preview/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,10 @@ function updateTkPlayhead() {

function el<K extends keyof HTMLElementTagNameMap>(tag: K, attrs: Record<string, string>, ...kids: (HTMLElement | string)[]): HTMLElementTagNameMap[K] {
const n = document.createElement(tag);
for (const [k, v] of Object.entries(attrs)) k === "class" ? (n.className = v) : n.setAttribute(k, v);
for (const [k, v] of Object.entries(attrs)) {
if (k === "class") n.className = v;
else n.setAttribute(k, v);
}
n.append(...kids);
return n;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/reframe-video/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"esbuild": "^0.27.0",
"esbuild": "^0.28.1",
"playwright": "^1.60.0",
"vite": "^6.0.0"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/render-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"dependencies": {
"@reframe/core": "workspace:*",
"@reframe/renderer-canvas": "workspace:*",
"esbuild": "^0.27.0",
"esbuild": "^0.28.1",
"playwright": "^1.60.0",
"tsx": "^4.19.0"
},
Expand Down
6 changes: 5 additions & 1 deletion packages/render-cli/scripts/record-preview-demo.mts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ async function main() {
await page.goto("http://localhost:5199/");
await page.waitForSelector("#scene-select option", { state: "attached" });
await page.selectOption("#scene-select", { label: "logo-reveal" });
await page.waitForFunction(() => (window as any).__store?.compiled?.ir?.id === "logo-reveal");
await page.waitForFunction(
() =>
(window as unknown as { __store?: { compiled?: { ir?: { id?: string } } } }).__store?.compiled?.ir?.id ===
"logo-reveal",
);
await sleep(600);

// live overlay-draft HUD: the actual exportDraft() content, visualized
Expand Down
2 changes: 1 addition & 1 deletion packages/render-cli/src/assemble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import { writeFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
import { probeMedia } from "./media/probe.js";

const BGM_SYNTHS = ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
Expand Down
2 changes: 1 addition & 1 deletion packages/render-cli/src/browserEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ async function decode(dataUrl: string, label = ""): Promise<HTMLImageElement> {
await img.decode();
return img;
} catch (e) {
throw new Error(`decode failed for ${label} (len=${dataUrl.length}): ${String(e)}`);
throw new Error(`decode failed for ${label} (len=${dataUrl.length}): ${String(e)}`, { cause: e });
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/render-cli/src/reframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ export default scene({
async function main() {
const [command, ...rest] = process.argv.slice(2);

// Every case ends by calling process.exit()/fail() (both never-return), so there
// is no real fall-through — but no-fallthrough is syntactic and can't see that.
/* eslint-disable no-fallthrough */
switch (command) {
case "render": {
const input = rest[0];
Expand Down
Loading
Loading