@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.
- package/LICENSE +21 -0
- package/README.md +39 -2
- package/dist/catalog.d.ts +17 -0
- package/dist/catalog.js +51 -0
- package/dist/custom.d.ts +86 -0
- package/dist/custom.js +170 -0
- package/dist/define-scene.d.ts +31 -0
- package/dist/define-scene.js +128 -0
- package/dist/html.d.ts +5 -0
- package/dist/html.js +14 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/position.d.ts +21 -0
- package/dist/position.js +16 -0
- package/dist/registry.d.ts +4 -0
- package/dist/registry.js +68 -0
- package/dist/runtime/easing.d.ts +14 -0
- package/dist/runtime/easing.js +45 -0
- package/dist/runtime/mount.d.ts +50 -0
- package/dist/runtime/mount.js +153 -0
- package/dist/runtime/timeline.d.ts +147 -0
- package/dist/runtime/timeline.js +199 -0
- package/dist/runtime-files.d.ts +2 -0
- package/dist/runtime-files.js +15 -0
- package/dist/scene-contract.d.ts +7 -0
- package/dist/scene-contract.js +104 -0
- package/dist/scene3d/post.d.ts +12 -0
- package/dist/scene3d/post.js +16 -0
- package/dist/scene3d/reflector.d.ts +9 -0
- package/dist/scene3d/reflector.js +17 -0
- package/dist/scene3d/textures.d.ts +9 -0
- package/dist/scene3d/textures.js +11 -0
- package/dist/scene3d/volumetric.d.ts +9 -0
- package/dist/scene3d/volumetric.js +16 -0
- package/dist/scenes/bug/index.d.ts +1 -0
- package/dist/scenes/bug/index.js +45 -0
- package/dist/scenes/bug/scene.d.ts +2 -0
- package/dist/scenes/bug/scene.js +6 -0
- package/dist/scenes/bug/style.css +30 -0
- package/dist/scenes/bullets/index.d.ts +1 -0
- package/dist/scenes/bullets/index.js +128 -0
- package/dist/scenes/bullets/scene.d.ts +2 -0
- package/dist/scenes/bullets/scene.js +6 -0
- package/dist/scenes/bullets/style.css +91 -0
- package/dist/scenes/callout/index.d.ts +9 -0
- package/dist/scenes/callout/index.js +95 -0
- package/dist/scenes/callout/scene.d.ts +2 -0
- package/dist/scenes/callout/scene.js +43 -0
- package/dist/scenes/callout/style.css +97 -0
- package/dist/scenes/caption/index.d.ts +1 -0
- package/dist/scenes/caption/index.js +72 -0
- package/dist/scenes/caption/scene.d.ts +11 -0
- package/dist/scenes/caption/scene.js +33 -0
- package/dist/scenes/caption/style.css +33 -0
- package/dist/scenes/chapter/index.d.ts +1 -0
- package/dist/scenes/chapter/index.js +29 -0
- package/dist/scenes/chapter/scene.d.ts +2 -0
- package/dist/scenes/chapter/scene.js +6 -0
- package/dist/scenes/chapter/style.css +25 -0
- package/dist/scenes/chart/index.d.ts +1 -0
- package/dist/scenes/chart/index.js +24 -0
- package/dist/scenes/chart/render.d.ts +4 -0
- package/dist/scenes/chart/render.js +257 -0
- package/dist/scenes/chart/scale.d.ts +11 -0
- package/dist/scenes/chart/scale.js +53 -0
- package/dist/scenes/chart/scene.d.ts +2 -0
- package/dist/scenes/chart/scene.js +73 -0
- package/dist/scenes/chart/schema.d.ts +36 -0
- package/dist/scenes/chart/schema.js +109 -0
- package/dist/scenes/chart/style.css +425 -0
- package/dist/scenes/code/index.d.ts +1 -0
- package/dist/scenes/code/index.js +109 -0
- package/dist/scenes/code/scene.d.ts +2 -0
- package/dist/scenes/code/scene.js +108 -0
- package/dist/scenes/code/style.css +155 -0
- package/dist/scenes/code/tokenize.d.ts +8 -0
- package/dist/scenes/code/tokenize.js +145 -0
- package/dist/scenes/compare/index.d.ts +1 -0
- package/dist/scenes/compare/index.js +128 -0
- package/dist/scenes/compare/scene.d.ts +2 -0
- package/dist/scenes/compare/scene.js +58 -0
- package/dist/scenes/compare/style.css +187 -0
- package/dist/scenes/countdown/index.d.ts +1 -0
- package/dist/scenes/countdown/index.js +46 -0
- package/dist/scenes/countdown/scene.d.ts +2 -0
- package/dist/scenes/countdown/scene.js +21 -0
- package/dist/scenes/countdown/style.css +32 -0
- package/dist/scenes/flow/index.d.ts +1 -0
- package/dist/scenes/flow/index.js +66 -0
- package/dist/scenes/flow/inspect.d.ts +26 -0
- package/dist/scenes/flow/inspect.js +25 -0
- package/dist/scenes/flow/layout.d.ts +83 -0
- package/dist/scenes/flow/layout.js +222 -0
- package/dist/scenes/flow/measure.d.ts +15 -0
- package/dist/scenes/flow/measure.js +33 -0
- package/dist/scenes/flow/plan.d.ts +42 -0
- package/dist/scenes/flow/plan.js +117 -0
- package/dist/scenes/flow/scene.d.ts +2 -0
- package/dist/scenes/flow/scene.js +6 -0
- package/dist/scenes/flow/schema.d.ts +49 -0
- package/dist/scenes/flow/schema.js +60 -0
- package/dist/scenes/flow/style.css +54 -0
- package/dist/scenes/hero/index.d.ts +3 -0
- package/dist/scenes/hero/index.js +77 -0
- package/dist/scenes/hero/scene.d.ts +2 -0
- package/dist/scenes/hero/scene.js +6 -0
- package/dist/scenes/hero/style.css +143 -0
- package/dist/scenes/icon-grid/index.d.ts +1 -0
- package/dist/scenes/icon-grid/index.js +69 -0
- package/dist/scenes/icon-grid/scene.d.ts +2 -0
- package/dist/scenes/icon-grid/scene.js +6 -0
- package/dist/scenes/icon-grid/style.css +74 -0
- package/dist/scenes/kinetic/index.d.ts +1 -0
- package/dist/scenes/kinetic/index.js +62 -0
- package/dist/scenes/kinetic/scene.d.ts +2 -0
- package/dist/scenes/kinetic/scene.js +98 -0
- package/dist/scenes/kinetic/style.css +94 -0
- package/dist/scenes/kpi-grid/index.d.ts +1 -0
- package/dist/scenes/kpi-grid/index.js +161 -0
- package/dist/scenes/kpi-grid/scene.d.ts +2 -0
- package/dist/scenes/kpi-grid/scene.js +6 -0
- package/dist/scenes/kpi-grid/style.css +141 -0
- package/dist/scenes/lower-third/index.d.ts +1 -0
- package/dist/scenes/lower-third/index.js +33 -0
- package/dist/scenes/lower-third/scene.d.ts +2 -0
- package/dist/scenes/lower-third/scene.js +6 -0
- package/dist/scenes/lower-third/style.css +45 -0
- package/dist/scenes/media-frame/index.d.ts +1 -0
- package/dist/scenes/media-frame/index.js +99 -0
- package/dist/scenes/media-frame/scene.d.ts +2 -0
- package/dist/scenes/media-frame/scene.js +85 -0
- package/dist/scenes/media-frame/style.css +275 -0
- package/dist/scenes/outro/index.d.ts +1 -0
- package/dist/scenes/outro/index.js +54 -0
- package/dist/scenes/outro/scene.d.ts +2 -0
- package/dist/scenes/outro/scene.js +6 -0
- package/dist/scenes/outro/style.css +92 -0
- package/dist/scenes/poster/index.d.ts +3 -0
- package/dist/scenes/poster/index.js +62 -0
- package/dist/scenes/poster/scene.d.ts +2 -0
- package/dist/scenes/poster/scene.js +6 -0
- package/dist/scenes/poster/style.css +102 -0
- package/dist/scenes/progress/index.d.ts +1 -0
- package/dist/scenes/progress/index.js +40 -0
- package/dist/scenes/progress/scene.d.ts +2 -0
- package/dist/scenes/progress/scene.js +17 -0
- package/dist/scenes/progress/style.css +31 -0
- package/dist/scenes/quote/index.d.ts +3 -0
- package/dist/scenes/quote/index.js +69 -0
- package/dist/scenes/quote/scene.d.ts +2 -0
- package/dist/scenes/quote/scene.js +6 -0
- package/dist/scenes/quote/style.css +86 -0
- package/dist/scenes/scene3d/index.d.ts +1 -0
- package/dist/scenes/scene3d/index.js +58 -0
- package/dist/scenes/scene3d/scene.d.ts +2 -0
- package/dist/scenes/scene3d/scene.js +413 -0
- package/dist/scenes/scene3d/style.css +43 -0
- package/dist/scenes/stat/index.d.ts +4 -0
- package/dist/scenes/stat/index.js +72 -0
- package/dist/scenes/stat/scene.d.ts +2 -0
- package/dist/scenes/stat/scene.js +6 -0
- package/dist/scenes/stat/style.css +95 -0
- package/dist/scenes/steps/index.d.ts +1 -0
- package/dist/scenes/steps/index.js +147 -0
- package/dist/scenes/steps/scene.d.ts +2 -0
- package/dist/scenes/steps/scene.js +6 -0
- package/dist/scenes/steps/style.css +190 -0
- package/dist/scenes/sticker/index.d.ts +1 -0
- package/dist/scenes/sticker/index.js +48 -0
- package/dist/scenes/sticker/scene.d.ts +2 -0
- package/dist/scenes/sticker/scene.js +57 -0
- package/dist/scenes/sticker/style.css +88 -0
- package/dist/scenes/table/index.d.ts +1 -0
- package/dist/scenes/table/index.js +131 -0
- package/dist/scenes/table/scene.d.ts +2 -0
- package/dist/scenes/table/scene.js +6 -0
- package/dist/scenes/table/style.css +102 -0
- package/dist/scenes/timeline/index.d.ts +1 -0
- package/dist/scenes/timeline/index.js +68 -0
- package/dist/scenes/timeline/scene.d.ts +2 -0
- package/dist/scenes/timeline/scene.js +6 -0
- package/dist/scenes/timeline/style.css +161 -0
- package/dist/scenes/title/index.d.ts +1 -0
- package/dist/scenes/title/index.js +52 -0
- package/dist/scenes/title/scene.d.ts +2 -0
- package/dist/scenes/title/scene.js +6 -0
- package/dist/scenes/title/style.css +80 -0
- package/dist/scenes/waveform/index.d.ts +1 -0
- package/dist/scenes/waveform/index.js +39 -0
- package/dist/scenes/waveform/scene.d.ts +2 -0
- package/dist/scenes/waveform/scene.js +35 -0
- package/dist/scenes/waveform/style.css +44 -0
- package/dist/shared/fit-text.d.ts +24 -0
- package/dist/shared/fit-text.js +82 -0
- package/dist/shared/format.d.ts +24 -0
- package/dist/shared/format.js +59 -0
- package/dist/shared/frame.d.ts +14 -0
- package/dist/shared/frame.js +21 -0
- package/dist/shared/icon-search.d.ts +9 -0
- package/dist/shared/icon-search.js +26 -0
- package/dist/shared/icon.d.ts +15 -0
- package/dist/shared/icon.js +28 -0
- package/dist/shared/index.d.ts +11 -0
- package/dist/shared/index.js +7 -0
- package/dist/shared/markup.d.ts +12 -0
- package/dist/shared/markup.js +43 -0
- package/dist/shared/measure.d.ts +7 -0
- package/dist/shared/measure.js +34 -0
- package/dist/shared/motion.d.ts +94 -0
- package/dist/shared/motion.js +117 -0
- package/dist/source-hash.d.ts +4 -0
- package/dist/source-hash.js +26 -0
- package/dist/test-fixtures.d.ts +9 -0
- package/dist/test-fixtures.js +22 -0
- package/dist/text-file.d.ts +1 -0
- package/dist/text-file.js +3 -0
- package/dist/words.d.ts +11 -0
- package/dist/words.js +93 -0
- 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,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,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,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,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,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
|
+
});
|