paper-motion 0.0.0-stage → 0.2.0

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.
Files changed (113) hide show
  1. package/README.md +59 -2
  2. package/bin/pamo.mjs +664 -0
  3. package/examples/breath/brief.md +11 -0
  4. package/examples/breath/poster.jpg +0 -0
  5. package/examples/breath/scene.html +59 -0
  6. package/examples/chrome-endcard/brief.md +7 -0
  7. package/examples/chrome-endcard/fonts/GeistMono-Variable.woff2 +0 -0
  8. package/examples/chrome-endcard/fonts/GeistSerif-Regular.woff2 +0 -0
  9. package/examples/chrome-endcard/poster.jpg +0 -0
  10. package/examples/chrome-endcard/scene.html +68 -0
  11. package/examples/chrome-endcard/wordmark.js +16 -0
  12. package/examples/halo-explainer/audio/script.txt +8 -0
  13. package/examples/halo-explainer/audio/words.json +554 -0
  14. package/examples/halo-explainer/brand/IDENTITY.md +24 -0
  15. package/examples/halo-explainer/brand/decisions/2026-11-08-type.md +5 -0
  16. package/examples/halo-explainer/brand/motion.json +9 -0
  17. package/examples/halo-explainer/brand/shader-set/set.json +104 -0
  18. package/examples/halo-explainer/brand/sound.json +33 -0
  19. package/examples/halo-explainer/brand/tokens.json +97 -0
  20. package/examples/halo-explainer/brief.md +34 -0
  21. package/examples/halo-explainer/poster.jpg +0 -0
  22. package/examples/halo-explainer/scene.html +372 -0
  23. package/examples/install-loop/brief.md +7 -0
  24. package/examples/install-loop/fonts/GeistMono-Variable.woff2 +0 -0
  25. package/examples/install-loop/fonts/GeistSerif-Regular.woff2 +0 -0
  26. package/examples/install-loop/poster.jpg +0 -0
  27. package/examples/install-loop/scene.html +46 -0
  28. package/examples/orbit-keynote/audio/script.txt +7 -0
  29. package/examples/orbit-keynote/audio/words.json +494 -0
  30. package/examples/orbit-keynote/brand/IDENTITY.md +25 -0
  31. package/examples/orbit-keynote/brand/decisions/2026-11-08-look.md +6 -0
  32. package/examples/orbit-keynote/brand/decisions/2026-11-08-voice.md +3 -0
  33. package/examples/orbit-keynote/brand/motion.json +9 -0
  34. package/examples/orbit-keynote/brand/shader-set/set.json +45 -0
  35. package/examples/orbit-keynote/brand/sound.json +45 -0
  36. package/examples/orbit-keynote/brand/tokens.json +106 -0
  37. package/examples/orbit-keynote/brief.md +25 -0
  38. package/examples/orbit-keynote/poster.jpg +0 -0
  39. package/examples/orbit-keynote/scene.html +323 -0
  40. package/examples/signal/brief.md +10 -0
  41. package/examples/signal/poster.jpg +0 -0
  42. package/examples/signal/scene.html +50 -0
  43. package/lib/board.mjs +95 -0
  44. package/lib/browser.mjs +88 -0
  45. package/lib/catalog.mjs +77 -0
  46. package/lib/customize.mjs +162 -0
  47. package/lib/keys.mjs +93 -0
  48. package/lib/live-board.js +128 -0
  49. package/lib/modules.mjs +23 -0
  50. package/lib/react-hooks.mjs +9 -0
  51. package/lib/registry.mjs +344 -0
  52. package/lib/render.mjs +193 -0
  53. package/lib/server.mjs +89 -0
  54. package/lib/sfx.mjs +287 -0
  55. package/lib/shaderset.mjs +65 -0
  56. package/lib/wordmark.mjs +64 -0
  57. package/package.json +46 -4
  58. package/runtime/brand.js +71 -0
  59. package/runtime/core.js +269 -0
  60. package/runtime/index.js +349 -0
  61. package/runtime/jsx-shim.js +3 -0
  62. package/runtime/kit.js +98 -0
  63. package/runtime/moves/bar-reveal.js +19 -0
  64. package/runtime/moves/blur-in.js +7 -0
  65. package/runtime/moves/cells.js +17 -0
  66. package/runtime/moves/crossfade.js +6 -0
  67. package/runtime/moves/cut.js +3 -0
  68. package/runtime/moves/dive-in.js +21 -0
  69. package/runtime/moves/draw-on.js +12 -0
  70. package/runtime/moves/drift.js +7 -0
  71. package/runtime/moves/flash.js +15 -0
  72. package/runtime/moves/flip-3d.js +9 -0
  73. package/runtime/moves/flip-in.js +12 -0
  74. package/runtime/moves/flip-lift.js +24 -0
  75. package/runtime/moves/glitch.js +16 -0
  76. package/runtime/moves/iris.js +12 -0
  77. package/runtime/moves/magic-move.js +49 -0
  78. package/runtime/moves/mask-reveal.js +10 -0
  79. package/runtime/moves/odometer.js +32 -0
  80. package/runtime/moves/orbit.js +8 -0
  81. package/runtime/moves/pop-in.js +12 -0
  82. package/runtime/moves/punch.js +9 -0
  83. package/runtime/moves/push.js +14 -0
  84. package/runtime/moves/rgb-split.js +7 -0
  85. package/runtime/moves/roll.js +20 -0
  86. package/runtime/moves/scan-reveal.js +10 -0
  87. package/runtime/moves/scramble.js +21 -0
  88. package/runtime/moves/shake.js +15 -0
  89. package/runtime/moves/slices.js +14 -0
  90. package/runtime/moves/snap-scroll.js +23 -0
  91. package/runtime/moves/squash-drop.js +21 -0
  92. package/runtime/moves/tile-swirl.js +29 -0
  93. package/runtime/moves/track-in.js +7 -0
  94. package/runtime/moves/type-on.js +41 -0
  95. package/runtime/moves/unfold.js +20 -0
  96. package/runtime/moves/whip-pan.js +21 -0
  97. package/runtime/moves/whip.js +14 -0
  98. package/runtime/moves/wipe.js +9 -0
  99. package/runtime/moves/word-rise.js +10 -0
  100. package/runtime/moves/zoom-through.js +13 -0
  101. package/runtime/moves.js +94 -0
  102. package/runtime/player.js +69 -0
  103. package/runtime/react-shim.js +15 -0
  104. package/templates/blank/brief.md +39 -0
  105. package/templates/blank/scene.html +44 -0
  106. package/templates/brand-sheet/index.html +241 -0
  107. package/templates/move-demo/brief.md +4 -0
  108. package/templates/move-demo/scene.html +158 -0
  109. package/templates/piece/brief.md +39 -0
  110. package/templates/piece/scene.html +62 -0
  111. package/templates/shader-set/shot.js +47 -0
  112. package/templates/still/brief.md +3 -0
  113. package/templates/still/scene.html +56 -0
@@ -0,0 +1,11 @@
1
+ # Brief
2
+
3
+ Warm, quiet 4:5 loop: a breathing field of dust-colored light and a single italic word.
4
+
5
+ **Intent** — slow the viewer's breathing down for twelve seconds.
6
+ **Format** — 1080×1350 · 30fps · 12s · seamless loop · social feed / screen.
7
+ **Palette** — near-black umber ground `#1b1a17`, clay `#3d3328`, sand `#a8927a`, bone `#e9dccb`.
8
+ **Material** — MeshGradient with grain (mixer + overlay) so it reads like film, not like a UI gradient.
9
+ **Type** — one word, italic old-style serif, wide tracking; a tiny mono footnote.
10
+ **Motion** — one oscillation of scale per loop (period = duration). Letters arrive with blur→focus, leave in reverse order.
11
+ **Structure** — 0–1s field only · 1.2–3s word arrives · 4–8s hold · 8.2–10s word leaves · 10–12s field returns to frame 0.
Binary file
@@ -0,0 +1,59 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>breath</title>
6
+ <style>
7
+ #stage { color: #f3ece2; }
8
+ .layer { position: absolute; inset: 0; }
9
+ .word {
10
+ position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%);
11
+ text-align: center; font: 400 64px/1 "Iowan Old Style", "Palatino", Georgia, serif;
12
+ letter-spacing: 0.16em; text-indent: 0.16em; font-style: italic;
13
+ color: #e9dccb; mix-blend-mode: difference; /* stays legible over light and dark */
14
+ }
15
+ .foot {
16
+ position: absolute; left: 0; right: 0; bottom: 72px; text-align: center;
17
+ font: 500 13px/1 ui-monospace, Menlo, monospace; letter-spacing: 0.4em; color: #e9dccb; mix-blend-mode: difference;
18
+ }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <main id="stage">
23
+ <div id="field" class="layer"></div>
24
+ <div class="word" id="word">breathe</div>
25
+ <div class="foot" id="foot">IN · OUT</div>
26
+ </main>
27
+ <script type="module">
28
+ import { scene, split, stagger, progress, keys, ease, osc } from 'paper-motion';
29
+
30
+ const s = scene({ width: 1080, height: 1350, fps: 30, duration: 12, background: '#1b1a17' });
31
+
32
+ const field = s.shader('#field', 'MeshGradient', {
33
+ colors: ['#1b1a17', '#3d3328', '#a8927a', '#e9dccb'],
34
+ distortion: 0.9, swirl: 0.12, speed: 0.22,
35
+ grainMixer: 0.18, grainOverlay: 0.22,
36
+ }, { loop: 3 });
37
+
38
+ const chars = split('#word', 'chars');
39
+ const foot = document.querySelector('#foot');
40
+
41
+ s.update((t) => {
42
+ // One slow breath per loop: period = duration keeps the loop seamless.
43
+ field.set({ scale: 1.15 + 0.08 * osc(t, 12, -0.25) });
44
+
45
+ chars.forEach((c, i) => {
46
+ const inP = stagger(t, i, { start: 1.2, each: 0.14, duration: 1.6, easing: ease.outCubic });
47
+ const outP = stagger(t, chars.length - 1 - i, { start: 8.2, each: 0.1, duration: 1.4, easing: ease.inCubic });
48
+ const v = inP * (1 - outP);
49
+ c.style.opacity = v;
50
+ c.style.filter = `blur(${(1 - v) * 10}px)`;
51
+ c.style.transform = `translateY(${(1 - inP) * 14 - outP * 10}px)`;
52
+ });
53
+ foot.style.opacity = 0.8 * keys(t, [[2.5, 0], [4, 1, ease.inOutSine], [8, 1], [9.5, 0, ease.inOutSine]]);
54
+ });
55
+
56
+ await s.start();
57
+ </script>
58
+ </body>
59
+ </html>
@@ -0,0 +1,7 @@
1
+ # chrome-endcard
2
+
3
+ 8 s 16:9 end card: a liquid-metal (chrome) wordmark is uncovered by a soft diagonal wipe while the metal calms down, then a tagline rises and an install command types in, with low synthesized sound.
4
+
5
+ - 1920×1080 · 30 fps · 8 s
6
+ - Shows: an image shader (LiquidMetal) fed a wordmark drawn on a canvas (`wordmark.js`), a CSS mask wipe, animating a shader param per frame with `layer.set()`, `s.sfx()` cues (whoosh, thud), human typing with `typingTimes()` + `keySounds()` and `s.sound()`.
7
+ - Look: chrome on black, Geist Serif and Geist Mono.
@@ -0,0 +1,68 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>chrome-endcard</title>
6
+ <style>
7
+ @font-face { font-family: 'GSerif'; src: url(./fonts/GeistSerif-Regular.woff2); }
8
+ @font-face { font-family: 'GMono'; font-weight: 100 900; src: url(./fonts/GeistMono-Variable.woff2) format('woff2-variations'); }
9
+ #stage { background: #000; overflow: hidden; }
10
+ .abs { position: absolute; box-sizing: border-box; white-space: pre; }
11
+ #logo { left: 0; top: 190px; width: 1920px; height: 520px; }
12
+ #tag { left: 0; width: 1920px; top: 668px; text-align: center; font: 400 26px/32px GMono; color: #8a8a8a; }
13
+ #cmdrow { left: 0; width: 1920px; top: 762px; height: 26px; text-align: center; font: 500 21px/26px GMono; color: #e6e6e6; }
14
+ #cur { display: inline-block; width: 11px; height: 24px; background: #e6e6e6; vertical-align: -5px; margin-left: 2px; }
15
+ </style>
16
+ </head>
17
+ <body>
18
+ <main id="stage">
19
+ <div id="logo" class="abs"></div>
20
+ <div id="tag" class="abs">creative-directed videos for people who use coding agents</div>
21
+ <div id="cmdrow" class="abs"><span id="cmd"></span><span id="cur"></span></div>
22
+ </main>
23
+ <script type="module">
24
+ import { scene, progress, keys, ease } from 'paper-motion';
25
+ import { typeOn, typingTimes, keySounds } from 'paper-motion/moves';
26
+ import { wordmark } from './wordmark.js';
27
+
28
+ const s = scene({ width: 1920, height: 1080, fps: 30, duration: 8, background: '#000' });
29
+ await document.fonts.load('400 26px GMono');
30
+ const logoEl = document.querySelector('#logo');
31
+
32
+ // Look B · chrome, approved from the looks board.
33
+ const CHROME = { colorBack: '#000000', colorTint: '#ffffff', softness: 0.12, repetition: 5, shiftRed: 0.3, shiftBlue: 0.3, distortion: 0.14, contour: 0.6, angle: 20 };
34
+ const metal = s.shader(logoEl, 'LiquidMetal', { image: await wordmark(), fit: 'contain', scale: 0.8, speed: 1, shape: 'none', ...CHROME });
35
+
36
+ const tag = document.querySelector('#tag');
37
+ const cmd = document.querySelector('#cmd'), cur = document.querySelector('#cur');
38
+ const CMD = '$ npx skills add solanodz/paper-motion';
39
+ // A person types the install line: bursts inside words, a beat between them; text and keys share the times.
40
+ const TIMES = typingTimes(CMD, { start: 3.2, cps: 14, seed: 4 });
41
+ const ENTER = TIMES[TIMES.length - 1] + 0.4;
42
+
43
+ s.update((t) => {
44
+ // Entrance: a soft diagonal wipe uncovers the word while it settles from 104% and the metal calms down.
45
+ const w = progress(t, 0.3, 2.0, ease.outCubic) * 150 - 30;
46
+ const m = `linear-gradient(100deg, #000 ${w}%, transparent ${w + 30}%)`;
47
+ logoEl.style.maskImage = m; logoEl.style.webkitMaskImage = m;
48
+ logoEl.style.transform = `scale(${1.04 - 0.04 * progress(t, 0.35, 2.6, ease.outExpo)})`;
49
+ metal.set({ distortion: keys(t, [[0, 0.45], [2.6, 0.14, ease.outCubic]]) });
50
+
51
+ // Tagline rises in; the install line types, then the cursor blinks.
52
+ const a = progress(t, 2.3, 3.0, ease.outCubic);
53
+ tag.style.opacity = a; tag.style.transform = `translateY(${(1 - a) * 14}px)`;
54
+ typeOn(cmd, CMD, t, { times: TIMES, cursor: cur });
55
+ });
56
+
57
+ // Sound: one low swell under the reveal, then soft keys. Nothing bright.
58
+ let seed = 7;
59
+ s.sfx(0.3, 'whoosh', { dur: 1.5, from: 260, to: 900, peak: 0.55, gain: 0.32, body: 0.5, seed: seed++ });
60
+ s.sfx(0.55, 'thud', { freq: 48, gain: 0.3, decay: 0.7 });
61
+ s.sfx(2.3, 'paper', { dur: 0.6, gain: 0.04 });
62
+ keySounds(s, CMD, TIMES, { seed: 3, enter: ENTER }); // synthesized; pass { bank } to use real keystrokes (pamo keys)
63
+ s.sound({ lowpass: 2400, gain: 1 });
64
+
65
+ await s.start();
66
+ </script>
67
+ </body>
68
+ </html>
@@ -0,0 +1,16 @@
1
+ // Draws the "paper-motion" wordmark (Geist Serif) on a transparent canvas and
2
+ // returns it as a PNG data URL. LiquidMetal uses the alpha as the logo shape.
3
+ export async function wordmark({ text = 'paper-motion', size = 420, pad = 60 } = {}) {
4
+ await document.fonts.load(`400 ${size}px GSerif`);
5
+ const c = document.createElement('canvas');
6
+ const g = c.getContext('2d');
7
+ g.font = `400 ${size}px GSerif`;
8
+ const m = g.measureText(text);
9
+ c.width = Math.ceil(m.width + pad * 2);
10
+ c.height = Math.ceil(m.actualBoundingBoxAscent + m.actualBoundingBoxDescent + pad * 2);
11
+ g.font = `400 ${size}px GSerif`;
12
+ g.letterSpacing = '-0.01em';
13
+ g.fillStyle = '#fff';
14
+ g.fillText(text, pad, pad + m.actualBoundingBoxAscent);
15
+ return c.toDataURL('image/png');
16
+ }
@@ -0,0 +1,8 @@
1
+ Your solar panels make the most power at noon.
2
+ But you use the most at night.
3
+ Halo stores the difference.
4
+ One: sunlight hits your panels.
5
+ Two: whatever you don't use flows into Halo, a battery the size of a suitcase.
6
+ Three: when the sun goes down, your home runs on stored sunlight.
7
+ Lower bills. Lights on, even in a blackout.
8
+ Halo. Keep the sun after dark.