@anvpx/motion-scenes 0.0.0-stage → 0.1.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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -2
  3. package/dist/catalog.d.ts +17 -0
  4. package/dist/catalog.js +51 -0
  5. package/dist/custom.d.ts +86 -0
  6. package/dist/custom.js +170 -0
  7. package/dist/define-scene.d.ts +31 -0
  8. package/dist/define-scene.js +128 -0
  9. package/dist/html.d.ts +5 -0
  10. package/dist/html.js +14 -0
  11. package/dist/index.d.ts +11 -0
  12. package/dist/index.js +11 -0
  13. package/dist/position.d.ts +21 -0
  14. package/dist/position.js +16 -0
  15. package/dist/registry.d.ts +4 -0
  16. package/dist/registry.js +68 -0
  17. package/dist/runtime/easing.d.ts +14 -0
  18. package/dist/runtime/easing.js +45 -0
  19. package/dist/runtime/mount.d.ts +50 -0
  20. package/dist/runtime/mount.js +153 -0
  21. package/dist/runtime/timeline.d.ts +147 -0
  22. package/dist/runtime/timeline.js +199 -0
  23. package/dist/runtime-files.d.ts +2 -0
  24. package/dist/runtime-files.js +15 -0
  25. package/dist/scene-contract.d.ts +7 -0
  26. package/dist/scene-contract.js +104 -0
  27. package/dist/scene3d/post.d.ts +12 -0
  28. package/dist/scene3d/post.js +16 -0
  29. package/dist/scene3d/reflector.d.ts +9 -0
  30. package/dist/scene3d/reflector.js +17 -0
  31. package/dist/scene3d/textures.d.ts +9 -0
  32. package/dist/scene3d/textures.js +11 -0
  33. package/dist/scene3d/volumetric.d.ts +9 -0
  34. package/dist/scene3d/volumetric.js +16 -0
  35. package/dist/scenes/bug/index.d.ts +1 -0
  36. package/dist/scenes/bug/index.js +45 -0
  37. package/dist/scenes/bug/scene.d.ts +2 -0
  38. package/dist/scenes/bug/scene.js +6 -0
  39. package/dist/scenes/bug/style.css +30 -0
  40. package/dist/scenes/bullets/index.d.ts +1 -0
  41. package/dist/scenes/bullets/index.js +128 -0
  42. package/dist/scenes/bullets/scene.d.ts +2 -0
  43. package/dist/scenes/bullets/scene.js +6 -0
  44. package/dist/scenes/bullets/style.css +91 -0
  45. package/dist/scenes/callout/index.d.ts +9 -0
  46. package/dist/scenes/callout/index.js +95 -0
  47. package/dist/scenes/callout/scene.d.ts +2 -0
  48. package/dist/scenes/callout/scene.js +43 -0
  49. package/dist/scenes/callout/style.css +97 -0
  50. package/dist/scenes/caption/index.d.ts +1 -0
  51. package/dist/scenes/caption/index.js +72 -0
  52. package/dist/scenes/caption/scene.d.ts +11 -0
  53. package/dist/scenes/caption/scene.js +33 -0
  54. package/dist/scenes/caption/style.css +33 -0
  55. package/dist/scenes/chapter/index.d.ts +1 -0
  56. package/dist/scenes/chapter/index.js +29 -0
  57. package/dist/scenes/chapter/scene.d.ts +2 -0
  58. package/dist/scenes/chapter/scene.js +6 -0
  59. package/dist/scenes/chapter/style.css +25 -0
  60. package/dist/scenes/chart/index.d.ts +1 -0
  61. package/dist/scenes/chart/index.js +24 -0
  62. package/dist/scenes/chart/render.d.ts +4 -0
  63. package/dist/scenes/chart/render.js +257 -0
  64. package/dist/scenes/chart/scale.d.ts +11 -0
  65. package/dist/scenes/chart/scale.js +53 -0
  66. package/dist/scenes/chart/scene.d.ts +2 -0
  67. package/dist/scenes/chart/scene.js +73 -0
  68. package/dist/scenes/chart/schema.d.ts +36 -0
  69. package/dist/scenes/chart/schema.js +109 -0
  70. package/dist/scenes/chart/style.css +425 -0
  71. package/dist/scenes/code/index.d.ts +1 -0
  72. package/dist/scenes/code/index.js +109 -0
  73. package/dist/scenes/code/scene.d.ts +2 -0
  74. package/dist/scenes/code/scene.js +108 -0
  75. package/dist/scenes/code/style.css +155 -0
  76. package/dist/scenes/code/tokenize.d.ts +8 -0
  77. package/dist/scenes/code/tokenize.js +145 -0
  78. package/dist/scenes/compare/index.d.ts +1 -0
  79. package/dist/scenes/compare/index.js +128 -0
  80. package/dist/scenes/compare/scene.d.ts +2 -0
  81. package/dist/scenes/compare/scene.js +58 -0
  82. package/dist/scenes/compare/style.css +187 -0
  83. package/dist/scenes/countdown/index.d.ts +1 -0
  84. package/dist/scenes/countdown/index.js +46 -0
  85. package/dist/scenes/countdown/scene.d.ts +2 -0
  86. package/dist/scenes/countdown/scene.js +21 -0
  87. package/dist/scenes/countdown/style.css +32 -0
  88. package/dist/scenes/flow/index.d.ts +1 -0
  89. package/dist/scenes/flow/index.js +66 -0
  90. package/dist/scenes/flow/inspect.d.ts +26 -0
  91. package/dist/scenes/flow/inspect.js +25 -0
  92. package/dist/scenes/flow/layout.d.ts +83 -0
  93. package/dist/scenes/flow/layout.js +222 -0
  94. package/dist/scenes/flow/measure.d.ts +15 -0
  95. package/dist/scenes/flow/measure.js +33 -0
  96. package/dist/scenes/flow/plan.d.ts +42 -0
  97. package/dist/scenes/flow/plan.js +117 -0
  98. package/dist/scenes/flow/scene.d.ts +2 -0
  99. package/dist/scenes/flow/scene.js +6 -0
  100. package/dist/scenes/flow/schema.d.ts +49 -0
  101. package/dist/scenes/flow/schema.js +60 -0
  102. package/dist/scenes/flow/style.css +54 -0
  103. package/dist/scenes/hero/index.d.ts +3 -0
  104. package/dist/scenes/hero/index.js +77 -0
  105. package/dist/scenes/hero/scene.d.ts +2 -0
  106. package/dist/scenes/hero/scene.js +6 -0
  107. package/dist/scenes/hero/style.css +143 -0
  108. package/dist/scenes/icon-grid/index.d.ts +1 -0
  109. package/dist/scenes/icon-grid/index.js +69 -0
  110. package/dist/scenes/icon-grid/scene.d.ts +2 -0
  111. package/dist/scenes/icon-grid/scene.js +6 -0
  112. package/dist/scenes/icon-grid/style.css +74 -0
  113. package/dist/scenes/kinetic/index.d.ts +1 -0
  114. package/dist/scenes/kinetic/index.js +62 -0
  115. package/dist/scenes/kinetic/scene.d.ts +2 -0
  116. package/dist/scenes/kinetic/scene.js +98 -0
  117. package/dist/scenes/kinetic/style.css +94 -0
  118. package/dist/scenes/kpi-grid/index.d.ts +1 -0
  119. package/dist/scenes/kpi-grid/index.js +161 -0
  120. package/dist/scenes/kpi-grid/scene.d.ts +2 -0
  121. package/dist/scenes/kpi-grid/scene.js +6 -0
  122. package/dist/scenes/kpi-grid/style.css +141 -0
  123. package/dist/scenes/lower-third/index.d.ts +1 -0
  124. package/dist/scenes/lower-third/index.js +33 -0
  125. package/dist/scenes/lower-third/scene.d.ts +2 -0
  126. package/dist/scenes/lower-third/scene.js +6 -0
  127. package/dist/scenes/lower-third/style.css +45 -0
  128. package/dist/scenes/media-frame/index.d.ts +1 -0
  129. package/dist/scenes/media-frame/index.js +99 -0
  130. package/dist/scenes/media-frame/scene.d.ts +2 -0
  131. package/dist/scenes/media-frame/scene.js +85 -0
  132. package/dist/scenes/media-frame/style.css +275 -0
  133. package/dist/scenes/outro/index.d.ts +1 -0
  134. package/dist/scenes/outro/index.js +54 -0
  135. package/dist/scenes/outro/scene.d.ts +2 -0
  136. package/dist/scenes/outro/scene.js +6 -0
  137. package/dist/scenes/outro/style.css +92 -0
  138. package/dist/scenes/poster/index.d.ts +3 -0
  139. package/dist/scenes/poster/index.js +62 -0
  140. package/dist/scenes/poster/scene.d.ts +2 -0
  141. package/dist/scenes/poster/scene.js +6 -0
  142. package/dist/scenes/poster/style.css +102 -0
  143. package/dist/scenes/progress/index.d.ts +1 -0
  144. package/dist/scenes/progress/index.js +40 -0
  145. package/dist/scenes/progress/scene.d.ts +2 -0
  146. package/dist/scenes/progress/scene.js +17 -0
  147. package/dist/scenes/progress/style.css +31 -0
  148. package/dist/scenes/quote/index.d.ts +3 -0
  149. package/dist/scenes/quote/index.js +69 -0
  150. package/dist/scenes/quote/scene.d.ts +2 -0
  151. package/dist/scenes/quote/scene.js +6 -0
  152. package/dist/scenes/quote/style.css +86 -0
  153. package/dist/scenes/scene3d/index.d.ts +1 -0
  154. package/dist/scenes/scene3d/index.js +58 -0
  155. package/dist/scenes/scene3d/scene.d.ts +2 -0
  156. package/dist/scenes/scene3d/scene.js +413 -0
  157. package/dist/scenes/scene3d/style.css +43 -0
  158. package/dist/scenes/stat/index.d.ts +4 -0
  159. package/dist/scenes/stat/index.js +72 -0
  160. package/dist/scenes/stat/scene.d.ts +2 -0
  161. package/dist/scenes/stat/scene.js +6 -0
  162. package/dist/scenes/stat/style.css +95 -0
  163. package/dist/scenes/steps/index.d.ts +1 -0
  164. package/dist/scenes/steps/index.js +147 -0
  165. package/dist/scenes/steps/scene.d.ts +2 -0
  166. package/dist/scenes/steps/scene.js +6 -0
  167. package/dist/scenes/steps/style.css +190 -0
  168. package/dist/scenes/sticker/index.d.ts +1 -0
  169. package/dist/scenes/sticker/index.js +48 -0
  170. package/dist/scenes/sticker/scene.d.ts +2 -0
  171. package/dist/scenes/sticker/scene.js +57 -0
  172. package/dist/scenes/sticker/style.css +88 -0
  173. package/dist/scenes/table/index.d.ts +1 -0
  174. package/dist/scenes/table/index.js +131 -0
  175. package/dist/scenes/table/scene.d.ts +2 -0
  176. package/dist/scenes/table/scene.js +6 -0
  177. package/dist/scenes/table/style.css +102 -0
  178. package/dist/scenes/timeline/index.d.ts +1 -0
  179. package/dist/scenes/timeline/index.js +68 -0
  180. package/dist/scenes/timeline/scene.d.ts +2 -0
  181. package/dist/scenes/timeline/scene.js +6 -0
  182. package/dist/scenes/timeline/style.css +161 -0
  183. package/dist/scenes/title/index.d.ts +1 -0
  184. package/dist/scenes/title/index.js +52 -0
  185. package/dist/scenes/title/scene.d.ts +2 -0
  186. package/dist/scenes/title/scene.js +6 -0
  187. package/dist/scenes/title/style.css +80 -0
  188. package/dist/scenes/waveform/index.d.ts +1 -0
  189. package/dist/scenes/waveform/index.js +39 -0
  190. package/dist/scenes/waveform/scene.d.ts +2 -0
  191. package/dist/scenes/waveform/scene.js +35 -0
  192. package/dist/scenes/waveform/style.css +44 -0
  193. package/dist/shared/fit-text.d.ts +24 -0
  194. package/dist/shared/fit-text.js +82 -0
  195. package/dist/shared/format.d.ts +24 -0
  196. package/dist/shared/format.js +59 -0
  197. package/dist/shared/frame.d.ts +14 -0
  198. package/dist/shared/frame.js +21 -0
  199. package/dist/shared/icon-search.d.ts +9 -0
  200. package/dist/shared/icon-search.js +26 -0
  201. package/dist/shared/icon.d.ts +15 -0
  202. package/dist/shared/icon.js +28 -0
  203. package/dist/shared/index.d.ts +11 -0
  204. package/dist/shared/index.js +7 -0
  205. package/dist/shared/markup.d.ts +12 -0
  206. package/dist/shared/markup.js +43 -0
  207. package/dist/shared/measure.d.ts +7 -0
  208. package/dist/shared/measure.js +34 -0
  209. package/dist/shared/motion.d.ts +94 -0
  210. package/dist/shared/motion.js +117 -0
  211. package/dist/source-hash.d.ts +4 -0
  212. package/dist/source-hash.js +26 -0
  213. package/dist/test-fixtures.d.ts +9 -0
  214. package/dist/test-fixtures.js +22 -0
  215. package/dist/text-file.d.ts +1 -0
  216. package/dist/text-file.js +3 -0
  217. package/dist/words.d.ts +11 -0
  218. package/dist/words.js +93 -0
  219. package/package.json +36 -3
@@ -0,0 +1,92 @@
1
+ #scene-root {
2
+ justify-content: space-between;
3
+ align-items: center;
4
+ text-align: center;
5
+ background: linear-gradient(155deg, var(--mk-color-deep) 0%, var(--mk-color-deepEnd) 100%);
6
+ color: var(--mk-color-onDeep);
7
+ }
8
+ #scene-root::before {
9
+ background:
10
+ radial-gradient(
11
+ 46% 56% at calc(50% + var(--drift, 0) * 3%) 0%,
12
+ color-mix(in srgb, var(--mk-color-accent) 35%, transparent) 0%,
13
+ transparent 100%
14
+ ),
15
+ radial-gradient(
16
+ 40% 50% at 50% 115%,
17
+ color-mix(in srgb, var(--mk-color-accent2) 30%, transparent) 0%,
18
+ transparent 100%
19
+ );
20
+ }
21
+ #scene-root::after {
22
+ background-image: radial-gradient(
23
+ color-mix(in srgb, var(--mk-color-onDeep) 14%, transparent) 2px,
24
+ transparent 2.5px
25
+ );
26
+ }
27
+ #scene-root .mk-orbit {
28
+ position: absolute;
29
+ z-index: -1;
30
+ inset: 0;
31
+ pointer-events: none;
32
+ }
33
+ #scene-root .mk-orbit i {
34
+ position: absolute;
35
+ border-radius: 50%;
36
+ border: 2px solid color-mix(in srgb, var(--mk-color-onDeep) 18%, transparent);
37
+ }
38
+ #scene-root .mk-orbit-1 {
39
+ left: -420px;
40
+ bottom: -760px;
41
+ width: 1300px;
42
+ height: 1300px;
43
+ transform: translateX(calc(var(--drift, 0) * 16px));
44
+ }
45
+ #scene-root .mk-orbit-2 {
46
+ right: -420px;
47
+ top: -560px;
48
+ width: 1100px;
49
+ height: 1100px;
50
+ transform: translateX(calc(var(--drift, 0) * -16px));
51
+ }
52
+ #scene-root .mk-main {
53
+ flex: 1;
54
+ display: flex;
55
+ flex-direction: column;
56
+ align-items: center;
57
+ justify-content: center;
58
+ gap: var(--mk-space-lg);
59
+ max-width: 1400px;
60
+ }
61
+ #scene-root .mk-rule {
62
+ transform-origin: center;
63
+ background: linear-gradient(90deg, var(--mk-color-onDeepMuted), var(--mk-color-accent2));
64
+ }
65
+ #scene-root .mk-heading {
66
+ font-family: var(--mk-font-display, var(--mk-font-sans));
67
+ font-size: var(--mk-font-size-title);
68
+ font-weight: 800;
69
+ line-height: 1.08;
70
+ letter-spacing: -0.03em;
71
+ text-wrap: balance;
72
+ }
73
+ #scene-root .mk-line {
74
+ font-size: var(--mk-font-size-lg);
75
+ line-height: 1.3;
76
+ color: var(--mk-color-onDeepMuted);
77
+ }
78
+ #scene-root .mk-credits {
79
+ list-style: none;
80
+ padding: var(--mk-space-lg) 0 0;
81
+ width: 100%;
82
+ display: flex;
83
+ flex-wrap: wrap;
84
+ justify-content: center;
85
+ gap: var(--mk-space-sm) var(--mk-space-xl);
86
+ border-top: 2px solid color-mix(in srgb, var(--mk-color-onDeep) 24%, transparent);
87
+ }
88
+ #scene-root .mk-credit {
89
+ font-size: var(--mk-font-size-sm);
90
+ letter-spacing: 0.02em;
91
+ color: var(--mk-color-onDeepMuted);
92
+ }
@@ -0,0 +1,3 @@
1
+ /** Type size by the longest line, so one word fills the frame and three still fit. */
2
+ export declare const sizeOf: (lines: readonly string[]) => string;
3
+ export declare const posterScene: import("@anvpx/motion-schema").ScenePlugin;
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+ import { defineScene } from "../../define-scene.js";
3
+ import { animAttrs, esc } from "../../html.js";
4
+ import { frameCss } from "../../shared/frame.js";
5
+ import { accentHtml, stripAccent } from "../../shared/markup.js";
6
+ import { readText } from "../../text-file.js";
7
+ import { limitWords, present, text } from "../../words.js";
8
+ const ALIGN = ["left", "center"];
9
+ const configSchema = limitWords(z.strictObject({
10
+ lines: z.array(text.max(38)).min(1).max(3),
11
+ kicker: text.max(28).optional(),
12
+ foot: text.max(60).optional(),
13
+ align: z.enum(ALIGN).default("left"),
14
+ /** Render the last line as outline type: the one-line trick that makes a poster look designed. */
15
+ outline: z.boolean().default(false),
16
+ }), (props) => [...props.lines.map(stripAccent), ...present(props.kicker, props.foot)]);
17
+ /** Type size by the longest line, so one word fills the frame and three still fit. */
18
+ export const sizeOf = (lines) => {
19
+ const longest = Math.max(...lines.map((line) => stripAccent(line).length), 0);
20
+ if (longest <= 12)
21
+ return "mk-size-1";
22
+ return longest <= 22 ? "mk-size-2" : "mk-size-3";
23
+ };
24
+ const render = (props) => {
25
+ const parts = [];
26
+ let step = 0;
27
+ if (props.kicker) {
28
+ parts.push(`<p class="mk-kicker" ${animAttrs("rise", step, "kicker")}><i class="mk-kicker-dot"></i>${esc(props.kicker)}</p>`);
29
+ step += 1;
30
+ }
31
+ const last = props.lines.length - 1;
32
+ const lines = props.lines.map((line, index) => {
33
+ const outline = props.outline && index === last ? " mk-outline" : "";
34
+ return `<span class="mk-line${outline}" ${animAttrs("rise", step + index, `line-${index}`)}>${accentHtml(line)}</span>`;
35
+ });
36
+ parts.push(`<h1 class="mk-lines" aria-label="${esc(props.lines.map(stripAccent).join(" "))}">\n${lines.join("\n")}\n</h1>`);
37
+ step += props.lines.length;
38
+ if (props.foot) {
39
+ parts.push(`<p class="mk-foot" ${animAttrs("rise", step, "foot")}>${esc(props.foot)}</p>`);
40
+ }
41
+ return `<div class="mk-poster ${sizeOf(props.lines)}" data-align="${props.align}">
42
+ <div class="mk-wash" aria-hidden="true"></div>
43
+ ${parts.join("\n")}
44
+ </div>`;
45
+ };
46
+ /** Tall frames keep the type large but tighten the tracking and drop the wash. */
47
+ const TALL_CSS = `#scene-root .mk-poster { gap: var(--mk-space-md); }
48
+ #scene-root .mk-wash { inset: -10% -40%; }`;
49
+ export const posterScene = defineScene({
50
+ type: "poster",
51
+ configSchema,
52
+ meta: {
53
+ purpose: "Type-led statement: 1-3 oversized lines with [x] accents, optional kicker and foot; outline draws the last line as outline type and accent words keep their gradient fill.",
54
+ example: { lines: ["Make it", "move"], outline: true },
55
+ },
56
+ style: readText("./style.css", import.meta.url),
57
+ script: readText("./scene.js", import.meta.url),
58
+ render: (props, ctx, root) => ({
59
+ body: render(props),
60
+ css: frameCss(ctx, root, { tall: TALL_CSS }),
61
+ }),
62
+ });
@@ -0,0 +1,2 @@
1
+ /** @type {import("../../runtime/mount.js").CreateScene} */
2
+ export declare function createScene(root: HTMLElement, config: import("../../runtime/mount.js").SceneConfig, ctx: import("../../runtime/mount.js").RuntimeContext): import("../../runtime/mount.js").Runtime;
@@ -0,0 +1,6 @@
1
+ // @ts-check
2
+ import { mountTimeline } from "../../runtime/mount.js";
3
+ /** @type {import("../../runtime/mount.js").CreateScene} */
4
+ export function createScene(root, config, ctx) {
5
+ return mountTimeline(root, config, ctx, { baseDelay: 0.2, staggerFrames: 6, rise: 56 });
6
+ }
@@ -0,0 +1,102 @@
1
+ #scene-root .mk-poster {
2
+ position: relative;
3
+ display: flex;
4
+ flex-direction: column;
5
+ justify-content: center;
6
+ gap: var(--mk-space-lg);
7
+ width: 100%;
8
+ }
9
+ #scene-root .mk-poster[data-align="center"] {
10
+ align-items: center;
11
+ text-align: center;
12
+ }
13
+ #scene-root .mk-wash {
14
+ position: absolute;
15
+ inset: -16% -8%;
16
+ z-index: -1;
17
+ pointer-events: none;
18
+ filter: blur(40px);
19
+ opacity: 0.9;
20
+ transform: translate3d(calc(var(--drift, 0) * 9px), calc(var(--drift, 0) * -6px), 0);
21
+ background:
22
+ radial-gradient(
23
+ 44% 50% at 10% 24%,
24
+ color-mix(in srgb, var(--mk-color-accent) 34%, transparent) 0%,
25
+ transparent 72%
26
+ ),
27
+ radial-gradient(
28
+ 40% 44% at 92% 76%,
29
+ color-mix(in srgb, var(--mk-color-accent2) 28%, transparent) 0%,
30
+ transparent 74%
31
+ );
32
+ }
33
+ #scene-root .mk-kicker {
34
+ display: inline-flex;
35
+ align-items: center;
36
+ gap: var(--mk-space-sm);
37
+ align-self: flex-start;
38
+ color: var(--mk-color-muted);
39
+ font-size: var(--mk-font-size-sm);
40
+ font-weight: 700;
41
+ letter-spacing: 0.16em;
42
+ text-transform: uppercase;
43
+ }
44
+ #scene-root .mk-poster[data-align="center"] .mk-kicker {
45
+ align-self: center;
46
+ }
47
+ #scene-root .mk-kicker-dot {
48
+ width: 10px;
49
+ height: 10px;
50
+ border-radius: var(--mk-radius-full);
51
+ background: var(--mk-color-accent);
52
+ }
53
+ #scene-root .mk-lines {
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: 0;
57
+ font-family: var(--mk-font-display, var(--mk-font-sans));
58
+ font-weight: 800;
59
+ line-height: 0.92;
60
+ letter-spacing: -0.045em;
61
+ color: var(--mk-color-fg);
62
+ }
63
+ #scene-root .mk-size-1 .mk-lines {
64
+ font-size: var(--mk-font-size-hero);
65
+ }
66
+ #scene-root .mk-size-2 .mk-lines {
67
+ font-size: var(--mk-font-size-heroMd);
68
+ }
69
+ #scene-root .mk-size-3 .mk-lines {
70
+ font-size: var(--mk-font-size-heroSm);
71
+ }
72
+ #scene-root .mk-line {
73
+ display: block;
74
+ }
75
+ #scene-root .mk-line .accent {
76
+ background: linear-gradient(94deg, var(--mk-color-accent), var(--mk-color-accent2));
77
+ -webkit-background-clip: text;
78
+ background-clip: text;
79
+ color: transparent;
80
+ }
81
+ #scene-root .mk-line.mk-outline {
82
+ color: transparent;
83
+ -webkit-text-stroke: 3px var(--mk-color-accent);
84
+ }
85
+ /* An accent inside an outlined line keeps its gradient fill: stroking a clipped background paints
86
+ the glyph twice and leaves artifacts in the counters. */
87
+ #scene-root .mk-line.mk-outline .accent {
88
+ -webkit-text-stroke: 0;
89
+ background: linear-gradient(94deg, var(--mk-color-accent), var(--mk-color-accent2));
90
+ -webkit-background-clip: text;
91
+ background-clip: text;
92
+ color: transparent;
93
+ }
94
+ #scene-root .mk-foot {
95
+ max-width: 40ch;
96
+ color: var(--mk-color-muted);
97
+ font-size: var(--mk-font-size-md);
98
+ line-height: 1.35;
99
+ }
100
+ #scene-root .mk-poster[data-align="center"] .mk-foot {
101
+ max-width: 52ch;
102
+ }
@@ -0,0 +1 @@
1
+ export declare const progressScene: import("@anvpx/motion-schema").ScenePlugin;
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+ import { defineScene } from "../../define-scene.js";
3
+ import { esc } from "../../html.js";
4
+ import { readText } from "../../text-file.js";
5
+ import { text } from "../../words.js";
6
+ const configSchema = z.strictObject({
7
+ scope: z.enum(["scene", "timeline"]).default("scene"),
8
+ color: z.enum(["accent", "accent2"]).default("accent"),
9
+ label: text.optional(),
10
+ });
11
+ const ROUND = 10_000;
12
+ const round = (value) => Math.round(value * ROUND) / ROUND;
13
+ /** Fill range over the overlay window: its own span, or its share of the whole video. */
14
+ const range = (props, start, duration, total) => props.scope === "timeline" && total > 0
15
+ ? [round(start / total), round((start + duration) / total)]
16
+ : [0, 1];
17
+ export const progressScene = defineScene({
18
+ type: "progress",
19
+ layer: "overlay",
20
+ configSchema,
21
+ meta: {
22
+ purpose: "Thin progress bar over the scene or the whole timeline.",
23
+ example: {},
24
+ },
25
+ style: readText("./style.css", import.meta.url),
26
+ script: readText("./scene.js", import.meta.url),
27
+ render: (props, ctx) => {
28
+ const { start = 0, total = 0 } = ctx.timeline ?? {};
29
+ const [from, to] = range(props, start, ctx.duration, total);
30
+ const label = props.label ? `<p class="mk-label">${esc(props.label)}</p>` : "";
31
+ return {
32
+ body: [
33
+ label,
34
+ `<div class="mk-track" data-from="${from}" data-to="${to}" aria-hidden="true">`,
35
+ `<span class="mk-fill mk-fill-${props.color}"></span>`,
36
+ "</div>",
37
+ ].join("\n"),
38
+ };
39
+ },
40
+ });
@@ -0,0 +1,2 @@
1
+ /** @type {import("../../runtime/mount.js").CreateScene} */
2
+ export declare function createScene(root: HTMLElement, config: import("../../runtime/mount.js").SceneConfig, ctx: import("../../runtime/mount.js").RuntimeContext): import("../../runtime/mount.js").Runtime;
@@ -0,0 +1,17 @@
1
+ // @ts-check
2
+ import { mountOverlay } from "../../runtime/mount.js";
3
+ import { spanProgress } from "../../runtime/timeline.js";
4
+ /** @type {import("../../runtime/mount.js").CreateScene} */
5
+ export function createScene(root, config, ctx) {
6
+ const track = /** @type {HTMLElement} */ (root.querySelector("[data-from]"));
7
+ const fill = /** @type {HTMLElement} */ (root.querySelector(".mk-fill"));
8
+ const from = Number(track.dataset.from);
9
+ const to = Number(track.dataset.to);
10
+ const duration = ctx.duration ?? Number(root.dataset.duration);
11
+ return mountOverlay(root, config, ctx, { baseDelay: 0, staggerFrames: 3, rise: 0 }, {
12
+ update: (t) => {
13
+ fill.style.transform = `scaleX(${spanProgress(t, duration, from, to)})`;
14
+ },
15
+ reset: () => fill.style.removeProperty("transform"),
16
+ });
17
+ }
@@ -0,0 +1,31 @@
1
+ #scene-root {
2
+ justify-content: flex-end;
3
+ gap: var(--mk-space-sm);
4
+ }
5
+ #scene-root .mk-label {
6
+ align-self: flex-start;
7
+ font-size: var(--mk-font-size-sm);
8
+ font-weight: 700;
9
+ color: var(--mk-color-fg);
10
+ }
11
+ #scene-root .mk-track {
12
+ flex: none;
13
+ height: var(--mk-space-md);
14
+ overflow: hidden;
15
+ border-radius: var(--mk-radius-full);
16
+ background: var(--mk-color-divider);
17
+ }
18
+ #scene-root .mk-fill {
19
+ display: block;
20
+ width: 100%;
21
+ height: 100%;
22
+ border-radius: var(--mk-radius-full);
23
+ transform-origin: left center;
24
+ transform: scaleX(0);
25
+ }
26
+ #scene-root .mk-fill-accent {
27
+ background: var(--mk-color-accent);
28
+ }
29
+ #scene-root .mk-fill-accent2 {
30
+ background: var(--mk-color-accent2);
31
+ }
@@ -0,0 +1,3 @@
1
+ /** First letters of the first two words, upper-cased; shown when there is no image. */
2
+ export declare const initials: (name: string) => string;
3
+ export declare const quoteScene: import("@anvpx/motion-schema").ScenePlugin;
@@ -0,0 +1,69 @@
1
+ import { z } from "zod";
2
+ import { defineScene } from "../../define-scene.js";
3
+ import { animAttrs, esc } from "../../html.js";
4
+ import { sceneIcon } from "../../shared/icon.js";
5
+ import { readText } from "../../text-file.js";
6
+ import { limitWords, present, text } from "../../words.js";
7
+ const MAX_STARS = 5;
8
+ const STAR_SIZE = 56;
9
+ const MARK_SIZE = 120;
10
+ /** Quotes up to these lengths get the larger type steps. */
11
+ const BIG_QUOTE = 50;
12
+ const MID_QUOTE = 110;
13
+ const capped = (max) => text.max(max, `max ${max} characters`);
14
+ const configSchema = limitWords(z.strictObject({
15
+ quote: capped(200),
16
+ name: capped(40),
17
+ role: capped(40).optional(),
18
+ org: capped(40).optional(),
19
+ rating: z.number().int().min(1).max(MAX_STARS).optional(),
20
+ image: text.optional(),
21
+ }), (props) => present(props.quote, props.name, props.role, props.org));
22
+ /** First letters of the first two words, upper-cased; shown when there is no image. */
23
+ export const initials = (name) => name
24
+ .split(/\s+/)
25
+ .filter(Boolean)
26
+ .slice(0, 2)
27
+ .map((word) => (Array.from(word)[0] ?? "").toUpperCase())
28
+ .join("");
29
+ const sizeClass = (quote) => {
30
+ const length = Array.from(quote).length;
31
+ if (length <= BIG_QUOTE)
32
+ return "mk-size-1";
33
+ return length <= MID_QUOTE ? "mk-size-2" : "mk-size-3";
34
+ };
35
+ const stars = (rating) => {
36
+ const items = Array.from({ length: MAX_STARS }, (_, i) => {
37
+ const on = i < rating;
38
+ const icon = sceneIcon("star", { at: "props.rating", size: STAR_SIZE });
39
+ return `<span class="mk-star${on ? " mk-on" : ""}">${icon}</span>`;
40
+ });
41
+ return `<span class="mk-stars" role="img" aria-label="${rating} of ${MAX_STARS}">${items.join("")}</span>`;
42
+ };
43
+ const avatar = (props, resolve) => props.image === undefined
44
+ ? `<span class="mk-avatar">${esc(initials(props.name))}</span>`
45
+ : `<span class="mk-avatar"><img class="mk-photo" src="${esc(resolve(props.image))}" alt="" /></span>`;
46
+ const render = (props, resolve) => {
47
+ const meta = [props.role, props.org].filter((part) => part !== undefined);
48
+ const sub = meta.length > 0 ? `<p class="mk-meta">${meta.map(esc).join(" · ")}</p>` : "";
49
+ const rating = props.rating === undefined ? "" : stars(props.rating);
50
+ const mark = sceneIcon("quote", { at: "props.quote", size: MARK_SIZE });
51
+ return `<div class="mk-mark" ${animAttrs("rise", 0, "mark")} aria-hidden="true">${mark}</div>
52
+ <blockquote class="mk-quote ${sizeClass(props.quote)}" ${animAttrs("rise", 1, "quote")}>${esc(props.quote)}</blockquote>
53
+ <footer class="mk-by" ${animAttrs("rise", 5, "name")}>
54
+ ${avatar(props, resolve)}
55
+ <div class="mk-who"><p class="mk-name">${esc(props.name)}</p>${sub}</div>
56
+ ${rating}
57
+ </footer>`;
58
+ };
59
+ export const quoteScene = defineScene({
60
+ type: "quote",
61
+ configSchema,
62
+ meta: {
63
+ purpose: "Testimonial or quotation with attribution, optional 1-5 star rating and avatar.",
64
+ example: { quote: "Simple and fast.", name: "Alex Kim", rating: 5 },
65
+ },
66
+ style: readText("./style.css", import.meta.url),
67
+ script: readText("./scene.js", import.meta.url),
68
+ render: (props, ctx) => ({ body: render(props, ctx.assetResolver) }),
69
+ });
@@ -0,0 +1,2 @@
1
+ /** @type {import("../../runtime/mount.js").CreateScene} */
2
+ export declare function createScene(root: HTMLElement, config: import("../../runtime/mount.js").SceneConfig, ctx: import("../../runtime/mount.js").RuntimeContext): import("../../runtime/mount.js").Runtime;
@@ -0,0 +1,6 @@
1
+ // @ts-check
2
+ import { mountTimeline } from "../../runtime/mount.js";
3
+ /** @type {import("../../runtime/mount.js").CreateScene} */
4
+ export function createScene(root, config, ctx) {
5
+ return mountTimeline(root, config, ctx, { baseDelay: 0.2, staggerFrames: 6, rise: 36 });
6
+ }
@@ -0,0 +1,86 @@
1
+ #scene-root {
2
+ justify-content: center;
3
+ gap: var(--mk-space-lg);
4
+ padding-left: calc(96px + var(--mk-space-xl));
5
+ padding-right: calc(96px + var(--mk-space-xl));
6
+ }
7
+ #scene-root .mk-mark {
8
+ color: var(--mk-color-accent);
9
+ line-height: 0;
10
+ }
11
+ #scene-root .mk-quote {
12
+ font-family: var(--mk-font-display, var(--mk-font-sans));
13
+ font-weight: 700;
14
+ line-height: 1.14;
15
+ letter-spacing: -0.02em;
16
+ text-wrap: balance;
17
+ }
18
+ #scene-root .mk-size-1 {
19
+ font-size: var(--mk-font-size-display);
20
+ }
21
+ #scene-root .mk-size-2 {
22
+ font-size: var(--mk-font-size-title);
23
+ }
24
+ #scene-root .mk-size-3 {
25
+ font-size: var(--mk-font-size-xl);
26
+ }
27
+ #scene-root .mk-by {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: var(--mk-space-lg);
31
+ margin-top: var(--mk-space-md);
32
+ padding-top: var(--mk-space-lg);
33
+ border-top: 2px solid var(--mk-color-divider);
34
+ }
35
+ #scene-root .mk-avatar {
36
+ display: grid;
37
+ place-items: center;
38
+ flex: none;
39
+ width: 128px;
40
+ height: 128px;
41
+ overflow: hidden;
42
+ border-radius: var(--mk-radius-full);
43
+ background: var(--mk-color-accent);
44
+ color: var(--mk-color-onAccent);
45
+ font-size: var(--mk-font-size-lg);
46
+ font-weight: 800;
47
+ }
48
+ #scene-root .mk-photo {
49
+ width: 100%;
50
+ height: 100%;
51
+ object-fit: cover;
52
+ }
53
+ #scene-root .mk-who {
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: var(--mk-space-xs);
57
+ min-width: 0;
58
+ }
59
+ #scene-root .mk-name {
60
+ font-size: var(--mk-font-size-lg);
61
+ font-weight: 800;
62
+ line-height: 1.1;
63
+ }
64
+ #scene-root .mk-meta {
65
+ font-size: var(--mk-font-size-md);
66
+ font-weight: 500;
67
+ color: var(--mk-color-muted);
68
+ }
69
+ #scene-root .mk-stars {
70
+ display: flex;
71
+ gap: var(--mk-space-xs);
72
+ margin-left: auto;
73
+ }
74
+ #scene-root .mk-star {
75
+ display: grid;
76
+ color: color-mix(in srgb, var(--mk-color-muted) 50%, transparent);
77
+ }
78
+ #scene-root .mk-star svg {
79
+ fill: none;
80
+ }
81
+ #scene-root .mk-star.mk-on {
82
+ color: var(--mk-color-accent);
83
+ }
84
+ #scene-root .mk-star.mk-on svg {
85
+ fill: currentColor;
86
+ }
@@ -0,0 +1 @@
1
+ export declare const scene3dScene: import("@anvpx/motion-schema").ScenePlugin;
@@ -0,0 +1,58 @@
1
+ import { z } from "zod";
2
+ import { defineScene } from "../../define-scene.js";
3
+ import { animAttrs, esc } from "../../html.js";
4
+ import { frameCss } from "../../shared/frame.js";
5
+ import { readText } from "../../text-file.js";
6
+ import { limitWords, present, text } from "../../words.js";
7
+ const POST = ["clean", "bloom", "cinematic", "vintage"];
8
+ const LIGHT = ["studio", "sunset", "night", "soft"];
9
+ // NOTE: material/volumetric/reflector presets apply to the three.js WebGL path
10
+ // only. This 2D canvas fallback ignores `material` (kept in schema for
11
+ // forward-compat); `post`/`light`/`hdri` drive the fallback rendering.
12
+ const MATERIAL = ["clay", "gloss", "metal", "emissive"];
13
+ const HDRI = ["neutral", "studio", "sunset", "night"];
14
+ const configSchema = limitWords(z.strictObject({
15
+ title: text.max(60).optional(),
16
+ caption: text.max(140).optional(),
17
+ model: text.optional(),
18
+ post: z.enum(POST).default("cinematic"),
19
+ light: z.enum(LIGHT).default("studio"),
20
+ material: z.enum(MATERIAL).default("clay"),
21
+ hdri: z.enum(HDRI).default("neutral"),
22
+ }), (props) => present(props.title, props.caption));
23
+ const copy = (props) => {
24
+ const parts = [];
25
+ if (props.title !== undefined) {
26
+ parts.push(`<h1 class="mk-title" ${animAttrs("rise", 0, "title")}>${esc(props.title)}</h1>`);
27
+ }
28
+ if (props.caption !== undefined) {
29
+ parts.push(`<p class="mk-caption" ${animAttrs("rise", 1, "caption")}>${esc(props.caption)}</p>`);
30
+ }
31
+ return parts.length > 0 ? `<div class="mk-copy">\n${parts.join("\n")}\n</div>` : "";
32
+ };
33
+ const render = (props) => {
34
+ const data = [
35
+ `data-post="${esc(props.post)}"`,
36
+ `data-light="${esc(props.light)}"`,
37
+ `data-material="${esc(props.material)}"`,
38
+ `data-hdri="${esc(props.hdri)}"`,
39
+ ].join(" ");
40
+ return `<div class="mk-scene3d" data-no-timeline ${data}>
41
+ ${copy(props)}<div class="mk-stage"><canvas class="mk-gl" width="1920" height="1080" aria-label="3D diorama"></canvas></div>
42
+ </div>`;
43
+ };
44
+ export const scene3dScene = defineScene({
45
+ type: "scene3d",
46
+ configSchema,
47
+ meta: {
48
+ purpose: "WebGL diorama (vendored three.js): post, light, material, environment presets; flat 2D canvas fallback without WebGL. 3D output is machine+GPU stable, not portable.",
49
+ example: { title: "Orbit" },
50
+ },
51
+ style: readText("./style.css", import.meta.url),
52
+ script: readText("./scene.js", import.meta.url),
53
+ vendor: ["three", "postprocessing"],
54
+ render: (props, ctx, root) => ({
55
+ body: render(props),
56
+ css: frameCss(ctx, root, {}),
57
+ }),
58
+ });
@@ -0,0 +1,2 @@
1
+ /** @type {import("../../runtime/mount.js").CreateScene} */
2
+ export declare function createScene(root: HTMLElement, config: import("../../runtime/mount.js").SceneConfig, ctx: import("../../runtime/mount.js").RuntimeContext): import("../../runtime/mount.js").Runtime;