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 +5 -0
- package/bin/pamo.mjs +3 -3
- package/lib/customize.mjs +5 -2
- package/lib/live-board.js +1 -1
- package/lib/registry.mjs +5 -4
- package/package.json +1 -1
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
|
|
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 (
|
|
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)
|
|
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
|
|
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
|