Adds apps/video, a standalone Remotion package (not part of the pnpm workspace, so it never reaches the web build or the Docker image). It renders six looping films, one per service, in German and English, as 4:5 for LinkedIn and 16:9 for YouTube and ads, together with the text to post and an overview page. Each film is one continuous shot: close on something abstract, the camera pulls back to show what it really is, the form changes, and the picture folds back into its opening. The slide deck code from the earlier attempts is still in the package and no longer used. Also adds the Google Ads concept, brings the relaunch concept in line with the site as built, and a project skill that describes how to make further films. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { curtainEdge, curtainPolygon } from "./curtain";
|
|
|
|
const ROWS = Array.from({ length: 30 }, (_, row) => row);
|
|
|
|
describe("curtainEdge", () => {
|
|
it("is fully closed at progress 0 and fully open at progress 1, in every row", () => {
|
|
for (const row of ROWS) {
|
|
expect(curtainEdge(row, 0)).toBe(0);
|
|
expect(curtainEdge(row, 1)).toBe(1);
|
|
}
|
|
});
|
|
|
|
it("never moves backwards as progress grows", () => {
|
|
for (const row of ROWS) {
|
|
let last = 0;
|
|
for (let step = 0; step <= 20; step++) {
|
|
const edge = curtainEdge(row, step / 20);
|
|
expect(edge).toBeGreaterThanOrEqual(last);
|
|
last = edge;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("is ragged: rows are at different places half way through", () => {
|
|
const places = new Set(ROWS.map((row) => curtainEdge(row, 0.5).toFixed(3)));
|
|
expect(places.size).toBeGreaterThan(10);
|
|
});
|
|
|
|
it("stays within 0..1 and clamps progress outside 0..1", () => {
|
|
expect(curtainEdge(3, -1)).toBe(0);
|
|
expect(curtainEdge(3, 2)).toBe(1);
|
|
});
|
|
|
|
it("is deterministic per row", () => {
|
|
expect(curtainEdge(7, 0.4)).toBe(curtainEdge(7, 0.4));
|
|
});
|
|
});
|
|
|
|
describe("curtainPolygon", () => {
|
|
const box = { width: 200, height: 40, cell: 20 };
|
|
|
|
it("covers nothing at progress 0 and the whole box at progress 1 (behind the edge)", () => {
|
|
expect(curtainPolygon("behind", 0, box)).toBe("polygon(0px 0px, 0px 0px, 0px 20px, 0px 20px, 0px 40px, 0px 40px)");
|
|
expect(curtainPolygon("behind", 1, box)).toBe(
|
|
"polygon(0px 0px, 200px 0px, 200px 20px, 200px 20px, 200px 40px, 0px 40px)",
|
|
);
|
|
});
|
|
|
|
it("is the complement ahead of the edge", () => {
|
|
expect(curtainPolygon("ahead", 1, box)).toBe(
|
|
"polygon(200px 0px, 200px 0px, 200px 20px, 200px 20px, 200px 40px, 200px 40px)",
|
|
);
|
|
expect(curtainPolygon("ahead", 0, box)).toBe(
|
|
"polygon(200px 0px, 0px 0px, 0px 20px, 0px 20px, 0px 40px, 200px 40px)",
|
|
);
|
|
});
|
|
|
|
it("adds a row for a height that is not a multiple of the cell", () => {
|
|
const points = curtainPolygon("behind", 1, { width: 100, height: 50, cell: 20 }).split(",");
|
|
// Two corner points plus two per row, for three rows.
|
|
expect(points).toHaveLength(8);
|
|
});
|
|
});
|