paper-motion 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  All notable changes to the `paper-motion` npm package. Dates are release dates on npm.
4
4
 
5
+ ## 0.2.7 · 2026-11-10
6
+
7
+ - A new resource type, **blocks**: 29 ready-made 4–8 s moments (annotations, interfaces, code, data, type, brand, effects), installed like recipes and customized through their `CONFIG`; each recomposes for 16:9, 9:16 and 1:1.
8
+ - `pamo registry previews` renders blocks; `pamo add --set` writes their values into `CONFIG`.
9
+
5
10
  ## 0.2.6 · 2026-11-10
6
11
 
7
12
  - `camera()` in `paper-motion/kit`: a 3D world for a piece (`place`, `at`, `travel` with keyframes, `focus` with depth of field and gradual exits). Every recipe is rebuilt on it: a corridor the camera travels (launch, manifesto), a column it descends (three-things), a line it dollies along with a pull-back recap (explainer), a window it tilts into and punches (product-demo), a slab it swings around (before-after), a sleeve it orbits (release).
package/bin/pamo.mjs CHANGED
@@ -647,15 +647,15 @@ async function main() {
647
647
  const only = typeof args.only === 'string' ? args.only.split(',') : null;
648
648
  const out = path.resolve(args.out ?? path.join(reg.dir, '..', 'apps', 'web', 'public', 'previews'));
649
649
  fs.mkdirSync(out, { recursive: true });
650
- const items = loadItems(reg.dir).filter((i) => ['component', 'transition', 'text', 'camera', 'recipe', 'shot'].includes(i.type) && (!only || only.includes(i.id)));
651
- const POSTER = { component: 1.5, transition: 1.4, text: 2.6, camera: 0.9, recipe: 2.5, shot: 1.8 };
650
+ const items = loadItems(reg.dir).filter((i) => ['component', 'transition', 'text', 'camera', 'recipe', 'shot', 'block'].includes(i.type) && (!only || only.includes(i.id)));
651
+ const POSTER = { component: 1.5, transition: 1.4, text: 2.6, camera: 0.9, recipe: 2.5, shot: 1.8, block: 2.5 };
652
652
  const demo = path.join(reg.dir, '_demo', 'index.html');
653
653
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'pm-previews-'));
654
654
  try {
655
655
  for (const [k, it] of items.entries()) {
656
656
  process.stdout.write(`\r${k + 1}/${items.length} ${it.id.padEnd(16)}`);
657
657
  const mp4 = path.join(tmp, `${it.id}.mp4`);
658
- if (it.type === 'recipe' || it.type === 'shot') {
658
+ if (it.type === 'recipe' || it.type === 'shot' || it.type === 'block') {
659
659
  // a recipe renders whole, wired to the demo brand in registry/_demo/brand (served as /brand)
660
660
  const here = process.cwd(), copy = path.join(reg.dir, '_demo', `.${it.id}.recipe.html`);
661
661
  // the demo brand's copy goes in through the same generator `pamo add --set` uses (preview.values)
package/lib/customize.mjs CHANGED
@@ -138,6 +138,9 @@ function fontTokens(item, values) {
138
138
  }
139
139
 
140
140
  /** A recipe's CONFIG object from flat values (proof1Title… → proof[0].title, "1920x1080" → [1920, 1080]). */
141
+ // resources whose file is a whole scene, customized through its CONFIG
142
+ const SCENE_TYPES = ['recipe', 'shot', 'block'];
143
+
141
144
  function recipeConfig(values) {
142
145
  const cfg = {}, groups = {};
143
146
  for (const [k, v] of Object.entries(values)) {
@@ -182,7 +185,7 @@ export function apply(item, values) {
182
185
  const files = (item.files ?? []).map((f) => {
183
186
  if (f.content === undefined) return f;
184
187
  if (MOVE_TYPES.includes(item.type)) return { ...f, content: moveFile(item, f.content, v) };
185
- if ((item.type === 'recipe' || item.type === 'shot') && /scene\.html$/.test(f.path)) return { ...f, content: recipeScene(f.content, v) };
188
+ if (SCENE_TYPES.includes(item.type) && /scene\.html$/.test(f.path)) return { ...f, content: recipeScene(f.content, v) };
186
189
  return f;
187
190
  });
188
191
  let entry = item.install?.entry;
@@ -227,7 +230,7 @@ export function code(item, values, { showAll = false, site = 'https://paper-moti
227
230
  { key: 'tokens', label: 'tokens.json', lang: 'json', code: JSON.stringify(e, null, 2) },
228
231
  { key: 'css', label: 'CSS variables', lang: 'css', code: `${Object.values(f).map((x) => `@import url('${x.css}');`).filter((x, i, a) => a.indexOf(x) === i).join('\n')}\n\n:root {\n${Object.entries(f).map(([r, x]) => ` --font-${r}: '${x.family}', ${cat(x.family)};`).join('\n')}\n --display-weight: ${e.type.display.weight};\n --display-tracking: ${e.type.display.tracking};\n}` },
229
232
  ];
230
- } else if (item.type === 'recipe' || item.type === 'shot') {
233
+ } else if (SCENE_TYPES.includes(item.type)) {
231
234
  const scene = out.files.find((f) => /scene\.html$/.test(f.path));
232
235
  tabs = [{ key: 'scene', label: 'scene.html', lang: 'html', code: scene?.content ?? '' }];
233
236
  }
package/lib/live-board.js CHANGED
@@ -9,7 +9,7 @@
9
9
  export const BOARD_VERSION = 1;
10
10
  export const MAX_PAYLOAD = 8 * 1024;
11
11
  const LABELS = ['A', 'B', 'C', 'D'];
12
- const TYPES = ['component', 'transition', 'text', 'camera', 'background', 'palette', 'font-pair', 'recipe', 'shot'];
12
+ const TYPES = ['component', 'transition', 'text', 'camera', 'background', 'palette', 'font-pair', 'recipe', 'shot', 'block'];
13
13
 
14
14
  /**
15
15
  * @typedef {{ v: 1, kind: 'resource'|'kit', title: string, ask?: string, reply?: string,
package/lib/registry.mjs CHANGED
@@ -12,8 +12,8 @@ const ENGINE = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
12
12
  const REPO = path.resolve(ENGINE, '..', '..');
13
13
  // until the paper-motion.app domain is connected, the live site is the vercel URL
14
14
  export const SITE = (process.env.PAPER_MOTION_SITE ?? 'https://paper-motion.vercel.app').replace(/\/$/, '');
15
- const TYPES = ['component', 'transition', 'text', 'camera', 'background', 'palette', 'font-pair', 'recipe', 'shot'];
16
- const TYPE_TITLES = { component: 'Component transitions', transition: 'Transitions', text: 'Text effects', camera: 'Camera and energy', background: 'Backgrounds', palette: 'Palettes', 'font-pair': 'Font pairs', recipe: 'Recipes', shot: 'Shots' };
15
+ const TYPES = ['component', 'transition', 'text', 'camera', 'background', 'palette', 'font-pair', 'recipe', 'shot', 'block'];
16
+ const TYPE_TITLES = { component: 'Component transitions', transition: 'Transitions', text: 'Text effects', camera: 'Camera and energy', background: 'Backgrounds', palette: 'Palettes', 'font-pair': 'Font pairs', recipe: 'Recipes', shot: 'Shots', block: 'Blocks' };
17
17
 
18
18
  /** The registry to use: an explicit folder or URL, $PAPER_MOTION_REGISTRY, this repo's registry/ when present, else the site. */
19
19
  export function resolveRegistry(given) {
@@ -121,7 +121,8 @@ export function llmsTxt(items, site, { full = false } = {}) {
121
121
  through a board, in this order, one board each: palette · font pair · background(s) · moves by role (how things
122
122
  enter, how one thing becomes the next, how text arrives; let them pick several with \`choose\`) · recipe or shots
123
123
  (shots are 3–6 s camera gestures: a close-up, a pass over a component, a cursor that clicks; they chain before a
124
- recipe or stand alone). Skip a
124
+ recipe or stand alone) · blocks for the moments inside it (ready-made 4–8 s pieces: annotations on a screen, a
125
+ form, a terminal, code typing, a chart, captions, a logo sting; each recomposes for 16:9, 9:16 and 1:1). Skip a
125
126
  board only when the brand folder already settles that decision, and say so.
126
127
  3. **Propose with boards.** For each visual decision (palette, type, background, moves, recipe) pick 2–4 options from
127
128
  this registry and share a board link: \`pamo board --url --title … --option id … --look palette,font\` prints it
@@ -157,7 +158,7 @@ ${site}/registry.json is the index; every item carries, beside id, type, title,
157
158
  reply line carries the resulting values. Without a board, \`pamo add <background> --palette <palette-id|brand>\`
158
159
  installs it that way (\`brand\` reads \`brand/tokens.json\`). So choose backgrounds by \`motion\`, \`mood\` and
159
160
  texture first; the color comes from the palette.
160
- - Font pairs: \`voice\` (serif, sans, display, mono). Recipes and shots: \`duration\`, \`formats\` and \`beats\` (the structure as a
161
+ - Font pairs: \`voice\` (serif, sans, display, mono). Recipes, shots and blocks: \`duration\`, \`formats\` and \`beats\` (the structure as a
161
162
  timeline: name, from, to, what). A recipe board plays each structure as a sketch, so the user chooses a rhythm, not a
162
163
  name; you may add one option that is a **structure of your own** for this brief (\`structure\` instead of \`id\`,
163
164
  format below), which you then build from scratch. Give the recipe board \`choose: [1, 3]\`: picking several recipes in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paper-motion",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Frame-exact video renderer for HTML scenes built with Paper Shaders: the engine behind the paper-motion agent skills.",
5
5
  "keywords": [
6
6
  "video",