@clipkit/patterns 1.0.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 (107) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +84 -0
  3. package/dist/anchor-default.d.ts +4 -0
  4. package/dist/anchor-default.d.ts.map +1 -0
  5. package/dist/anchor-default.js +34 -0
  6. package/dist/anchor-default.js.map +1 -0
  7. package/dist/bar-chart-row.d.ts +43 -0
  8. package/dist/bar-chart-row.d.ts.map +1 -0
  9. package/dist/bar-chart-row.js +143 -0
  10. package/dist/bar-chart-row.js.map +1 -0
  11. package/dist/beat-sync.d.ts +101 -0
  12. package/dist/beat-sync.d.ts.map +1 -0
  13. package/dist/beat-sync.js +145 -0
  14. package/dist/beat-sync.js.map +1 -0
  15. package/dist/camera-orbit.d.ts +22 -0
  16. package/dist/camera-orbit.d.ts.map +1 -0
  17. package/dist/camera-orbit.js +32 -0
  18. package/dist/camera-orbit.js.map +1 -0
  19. package/dist/cta-outro.d.ts +18 -0
  20. package/dist/cta-outro.d.ts.map +1 -0
  21. package/dist/cta-outro.js +44 -0
  22. package/dist/cta-outro.js.map +1 -0
  23. package/dist/data-scenes.d.ts +66 -0
  24. package/dist/data-scenes.d.ts.map +1 -0
  25. package/dist/data-scenes.js +188 -0
  26. package/dist/data-scenes.js.map +1 -0
  27. package/dist/flow-line.d.ts +27 -0
  28. package/dist/flow-line.d.ts.map +1 -0
  29. package/dist/flow-line.js +53 -0
  30. package/dist/flow-line.js.map +1 -0
  31. package/dist/glass-panel.d.ts +24 -0
  32. package/dist/glass-panel.d.ts.map +1 -0
  33. package/dist/glass-panel.js +32 -0
  34. package/dist/glass-panel.js.map +1 -0
  35. package/dist/header-bar.d.ts +36 -0
  36. package/dist/header-bar.d.ts.map +1 -0
  37. package/dist/header-bar.js +108 -0
  38. package/dist/header-bar.js.map +1 -0
  39. package/dist/hero-reveal.d.ts +17 -0
  40. package/dist/hero-reveal.d.ts.map +1 -0
  41. package/dist/hero-reveal.js +47 -0
  42. package/dist/hero-reveal.js.map +1 -0
  43. package/dist/index.d.ts +21 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +43 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/intro-card.d.ts +23 -0
  48. package/dist/intro-card.d.ts.map +1 -0
  49. package/dist/intro-card.js +123 -0
  50. package/dist/intro-card.js.map +1 -0
  51. package/dist/kinetic-headline.d.ts +18 -0
  52. package/dist/kinetic-headline.d.ts.map +1 -0
  53. package/dist/kinetic-headline.js +39 -0
  54. package/dist/kinetic-headline.js.map +1 -0
  55. package/dist/layers.d.ts +13 -0
  56. package/dist/layers.d.ts.map +1 -0
  57. package/dist/layers.js +19 -0
  58. package/dist/layers.js.map +1 -0
  59. package/dist/liquid-morph.d.ts +62 -0
  60. package/dist/liquid-morph.d.ts.map +1 -0
  61. package/dist/liquid-morph.js +113 -0
  62. package/dist/liquid-morph.js.map +1 -0
  63. package/dist/lit-surface.d.ts +38 -0
  64. package/dist/lit-surface.d.ts.map +1 -0
  65. package/dist/lit-surface.js +63 -0
  66. package/dist/lit-surface.js.map +1 -0
  67. package/dist/lower-third.d.ts +26 -0
  68. package/dist/lower-third.d.ts.map +1 -0
  69. package/dist/lower-third.js +101 -0
  70. package/dist/lower-third.js.map +1 -0
  71. package/dist/morph-shape.d.ts +42 -0
  72. package/dist/morph-shape.d.ts.map +1 -0
  73. package/dist/morph-shape.js +57 -0
  74. package/dist/morph-shape.js.map +1 -0
  75. package/dist/pie-card.d.ts +35 -0
  76. package/dist/pie-card.d.ts.map +1 -0
  77. package/dist/pie-card.js +168 -0
  78. package/dist/pie-card.js.map +1 -0
  79. package/dist/promo.d.ts +38 -0
  80. package/dist/promo.d.ts.map +1 -0
  81. package/dist/promo.js +61 -0
  82. package/dist/promo.js.map +1 -0
  83. package/dist/ranked-list.d.ts +38 -0
  84. package/dist/ranked-list.d.ts.map +1 -0
  85. package/dist/ranked-list.js +130 -0
  86. package/dist/ranked-list.js.map +1 -0
  87. package/dist/stat-block.d.ts +26 -0
  88. package/dist/stat-block.d.ts.map +1 -0
  89. package/dist/stat-block.js +92 -0
  90. package/dist/stat-block.js.map +1 -0
  91. package/dist/theme.d.ts +36 -0
  92. package/dist/theme.d.ts.map +1 -0
  93. package/dist/theme.js +75 -0
  94. package/dist/theme.js.map +1 -0
  95. package/dist/tilted-showcase.d.ts +29 -0
  96. package/dist/tilted-showcase.d.ts.map +1 -0
  97. package/dist/tilted-showcase.js +106 -0
  98. package/dist/tilted-showcase.js.map +1 -0
  99. package/dist/trend-pill.d.ts +33 -0
  100. package/dist/trend-pill.d.ts.map +1 -0
  101. package/dist/trend-pill.js +83 -0
  102. package/dist/trend-pill.js.map +1 -0
  103. package/dist/zoom-rig.d.ts +27 -0
  104. package/dist/zoom-rig.d.ts.map +1 -0
  105. package/dist/zoom-rig.js +46 -0
  106. package/dist/zoom-rig.js.map +1 -0
  107. package/package.json +36 -0
@@ -0,0 +1,39 @@
1
+ // kineticHeadline — a bold headline that flies in letter-by-letter
2
+ // (text-fly), with an optional accent rule. Pair with source motion blur
3
+ // for the smeared, professional look.
4
+ //
5
+ // COMPONENT pattern: returns ONE plain `group`.
6
+ import { assignLayers } from './layers.js';
7
+ import { getFonts, getPalette } from './theme.js';
8
+ export function kineticHeadline(props) {
9
+ const { id, text, subtitle, color, canvasWidth: W, canvasHeight: H, time, duration, layer } = props;
10
+ const theme = props.theme ?? 'cinematic';
11
+ const palette = getPalette(theme, color);
12
+ const fonts = getFonts(theme);
13
+ const cx = W / 2, cy = H / 2;
14
+ const children = [
15
+ {
16
+ id: `${id}-headline`, type: 'text', text, x: cx, y: cy, x_anchor: '50%', y_anchor: '50%',
17
+ font_family: fonts.sans, font_size: 112, font_weight: '800', fill_color: palette.text,
18
+ animations: [{ type: 'text-fly', split: 'letter', stagger: props.stagger ?? 0.05, distance: 160, direction: 'up', duration: 0.6, time: 0.2 }],
19
+ },
20
+ {
21
+ id: `${id}-rule`, type: 'shape', shape: 'rectangle', x: cx, y: cy + 86, x_anchor: '50%', y_anchor: '50%', width: 200, height: 6, border_radius: 3,
22
+ fill_color: palette.accent, time: 0.5,
23
+ keyframe_animations: [{ property: 'x_scale', keyframes: [{ time: 0, value: 0 }, { time: 0.5, value: 1, easing: 'ease-out' }] }],
24
+ },
25
+ ];
26
+ if (subtitle) {
27
+ children.push({
28
+ id: `${id}-sub`, type: 'text', text: subtitle, x: cx, y: cy + 130, x_anchor: '50%', y_anchor: '50%', time: 0.7,
29
+ font_family: fonts.sans, font_size: 26, letter_spacing: 1, fill_color: palette.textMuted,
30
+ animations: [{ type: 'fade-in', duration: 0.5 }],
31
+ });
32
+ }
33
+ return {
34
+ id, type: 'group', layer, time, duration, x: 0, y: 0, width: W, height: H,
35
+ animations: [{ type: 'fade-out', time: 'end', duration: 0.5 }],
36
+ elements: assignLayers(children),
37
+ };
38
+ }
39
+ //# sourceMappingURL=kinetic-headline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kinetic-headline.js","sourceRoot":"","sources":["../src/kinetic-headline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { Element } from '@clipkit/protocol';
2
+ /** Distribute Omit across the Element union so each member keeps its own fields. */
3
+ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
4
+ /** An element as authored by a builder, before its `layer` is stamped. */
5
+ export type UnlayeredElement = DistributiveOmit<Element, 'layer'>;
6
+ /**
7
+ * Stamp dense, unique `layer` values onto an ordered (back-to-front)
8
+ * child list: index 0 (drawn first / behind) → layer N, the last entry
9
+ * (drawn last / in front) → layer 1. Returns fully-typed `Element`s.
10
+ */
11
+ export declare function assignLayers(elements: readonly UnlayeredElement[]): Element[];
12
+ export {};
13
+ //# sourceMappingURL=layers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../src/layers.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,oFAAoF;AACpF,KAAK,gBAAgB,CAAC,CAAC,EAAE,CAAC,SAAS,WAAW,IAAI,CAAC,SAAS,OAAO,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC;AAEzF,0EAA0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAElE;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GAAG,OAAO,EAAE,CAG7E"}
package/dist/layers.js ADDED
@@ -0,0 +1,19 @@
1
+ // Generation-time layer assignment for builder children.
2
+ //
3
+ // Under the layer model every element in a container owns a UNIQUE
4
+ // `layer` (1..1000) and LOWER numbers draw in FRONT (layer 1 on top) —
5
+ // the After Effects convention. Builders author their children in
6
+ // BACK-TO-FRONT array order (the natural "paint in order" reading), so
7
+ // the helper stamps the LAST (front-most) child layer 1 and the FIRST
8
+ // (back-most) child layer N. This preserves the authored stacking and
9
+ // satisfies the per-container uniqueness invariant by construction.
10
+ /**
11
+ * Stamp dense, unique `layer` values onto an ordered (back-to-front)
12
+ * child list: index 0 (drawn first / behind) → layer N, the last entry
13
+ * (drawn last / in front) → layer 1. Returns fully-typed `Element`s.
14
+ */
15
+ export function assignLayers(elements) {
16
+ const n = elements.length;
17
+ return elements.map((el, i) => ({ ...el, layer: n - i }));
18
+ }
19
+ //# sourceMappingURL=layers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.js","sourceRoot":"","sources":["../src/layers.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,mEAAmE;AACnE,uEAAuE;AACvE,kEAAkE;AAClE,uEAAuE;AACvE,sEAAsE;AACtE,sEAAsE;AACtE,oEAAoE;AAUpE;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,QAAqC;IAChE,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC;IAC1B,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,CAAY,CAAC,CAAC;AACvE,CAAC"}
@@ -0,0 +1,62 @@
1
+ import type { Element, Easing } from '@clipkit/protocol';
2
+ type Pt = [number, number];
3
+ /** One waypoint in the flow. */
4
+ export interface MorphShape {
5
+ /**
6
+ * Corner points (clockwise) in `view_box` units. EVERY shape in the flow
7
+ * MUST have the same number of corners so they morph role-for-role. For a
8
+ * triangle/arrow, repeat a collinear point to pad to the shared count.
9
+ */
10
+ corners: Pt[];
11
+ /** Corner radius (view_box units). Clamped per corner to half the shorter edge. */
12
+ radius?: number;
13
+ /** Comp time (seconds) at which this shape is fully formed. */
14
+ at: number;
15
+ /** Hold this shape for N seconds after `at` before morphing onward. Default 0. */
16
+ hold?: number;
17
+ /** Easing as this shape settles. Default `'ease-out-sine'` (a flow-through). */
18
+ easing?: Easing;
19
+ }
20
+ export interface LiquidMorphProps {
21
+ id: string;
22
+ /** Shapes to flow through, in order. Need at least 2. */
23
+ shapes: MorphShape[];
24
+ /** Fill color (hex) or `"url(#id)"`. */
25
+ fill: string;
26
+ /** Center + box (the path's `view_box` maps onto this). */
27
+ x: number;
28
+ y: number;
29
+ width: number;
30
+ height: number;
31
+ /** Path coordinate space. Default `[0, 0, width, height]`. */
32
+ view_box?: [number, number, number, number];
33
+ layer?: number;
34
+ /** Element start time. Default 0 (so `at` values read as comp time). */
35
+ time?: number;
36
+ duration: number;
37
+ /**
38
+ * How liquid the in-between is: 0 = straight morph (no blob), 1 = maximally
39
+ * round blob swell. Default 1.
40
+ */
41
+ liquidity?: number;
42
+ /** Extra fields merged onto the element (e.g. `opacity`, `keyframe_animations`). */
43
+ extra?: Partial<Element>;
44
+ }
45
+ /**
46
+ * Flow a path-form `shape` through `shapes`, liquid morphs between each.
47
+ *
48
+ * @example
49
+ * // a triangle → a line that streaks off-screen left → a play arrow
50
+ * liquidMorph({
51
+ * id: 'flow', fill: '#EF4444',
52
+ * x: 960, y: 540, width: 1920, height: 1080, duration: 6,
53
+ * shapes: [
54
+ * { corners: TRIANGLE, radius: 16, at: 0, hold: 1 },
55
+ * { corners: LINE, radius: 20, at: 1.5, hold: 0.5 }, // the "moving path"
56
+ * { corners: PLAY, radius: 16, at: 2.7 },
57
+ * ],
58
+ * });
59
+ */
60
+ export declare function liquidMorph(props: LiquidMorphProps): Element;
61
+ export {};
62
+ //# sourceMappingURL=liquid-morph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liquid-morph.d.ts","sourceRoot":"","sources":["../src/liquid-morph.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,OAAO,EAAY,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEnE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE3B,gCAAgC;AAChC,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,OAAO,EAAE,EAAE,EAAE,CAAC;IACd,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,EAAE,EAAE,MAAM,CAAC;IACX,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,yDAAyD;IACzD,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,KAAK,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1B;AAkCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAwD5D"}
@@ -0,0 +1,113 @@
1
+ // liquidMorph — flow ONE path-form `shape` through a sequence of shapes, so a
2
+ // shape stretches into a travelling path and reforms as the next shape (the
3
+ // "shape → moving path → shape" move from the Transition Tour).
4
+ //
5
+ // The motion reads as LIQUID because each morph passes through a "blob": the
6
+ // half-way corners rounded as much as possible, so the path swells and flows
7
+ // instead of sliding in a straight line. The blob is a flow-THROUGH waypoint
8
+ // (accelerate in, decelerate out) — never a stop — so a hop A→B is one
9
+ // continuous motion, not two.
10
+ //
11
+ // Every shape is a rounded polygon with the SAME number of corners in the same
12
+ // order, so any two morph point-for-point with no twist (a triangle/arrow uses
13
+ // a hidden collinear corner to reach the shared count). The end shapes are
14
+ // exact; only the in-between is liquid.
15
+ //
16
+ // Emits ONE primitive `shape` element with a keyframed `paths[].d`. The runtime
17
+ // never sees "liquidMorph" — it's authoring-time sugar over the protocol.
18
+ const unit = (a, b) => {
19
+ const dx = b[0] - a[0], dy = b[1] - a[1], L = Math.hypot(dx, dy) || 1;
20
+ return [dx / L, dy / L];
21
+ };
22
+ const r1 = (x) => Math.round(x * 10) / 10;
23
+ // Corners + radius → `M + N·(L Q) + Z`. Same N ⇒ two paths morph. Each corner
24
+ // is one smooth quadratic; radius clamps to half the shorter edge so tight
25
+ // corners can't overlap (no balls/nubs).
26
+ function roundPoly(corners, radius) {
27
+ const n = corners.length;
28
+ const s = corners.map((c, i) => {
29
+ const prev = corners[(i - 1 + n) % n], next = corners[(i + 1) % n];
30
+ const rr = Math.min(radius, Math.hypot(c[0] - prev[0], c[1] - prev[1]) / 2, Math.hypot(c[0] - next[0], c[1] - next[1]) / 2);
31
+ const di = unit(c, prev), doo = unit(c, next);
32
+ return { i: [c[0] + di[0] * rr, c[1] + di[1] * rr], c, o: [c[0] + doo[0] * rr, c[1] + doo[1] * rr] };
33
+ });
34
+ let d = `M ${r1(s[0].o[0])} ${r1(s[0].o[1])}`;
35
+ for (let k = 1; k <= n; k++) {
36
+ const v = s[k % n];
37
+ d += ` L ${r1(v.i[0])} ${r1(v.i[1])} Q ${r1(v.c[0])} ${r1(v.c[1])} ${r1(v.o[0])} ${r1(v.o[1])}`;
38
+ }
39
+ return d + ' Z';
40
+ }
41
+ const lerpC = (a, b, t) => a.map((p, i) => [p[0] + (b[i][0] - p[0]) * t, p[1] + (b[i][1] - p[1]) * t]);
42
+ // The liquid in-between: the half-way corners rounded by `liquidity` (1 = max).
43
+ const blob = (a, b, liquidity) => roundPoly(lerpC(a, b, 0.5), 999 * liquidity);
44
+ /**
45
+ * Flow a path-form `shape` through `shapes`, liquid morphs between each.
46
+ *
47
+ * @example
48
+ * // a triangle → a line that streaks off-screen left → a play arrow
49
+ * liquidMorph({
50
+ * id: 'flow', fill: '#EF4444',
51
+ * x: 960, y: 540, width: 1920, height: 1080, duration: 6,
52
+ * shapes: [
53
+ * { corners: TRIANGLE, radius: 16, at: 0, hold: 1 },
54
+ * { corners: LINE, radius: 20, at: 1.5, hold: 0.5 }, // the "moving path"
55
+ * { corners: PLAY, radius: 16, at: 2.7 },
56
+ * ],
57
+ * });
58
+ */
59
+ export function liquidMorph(props) {
60
+ const { id, shapes, fill, x, y, width, height, duration, view_box = [0, 0, width, height], layer = 1, time = 0, liquidity = 1, extra, } = props;
61
+ if (shapes.length < 2)
62
+ throw new Error('liquidMorph: need at least 2 shapes');
63
+ const n = shapes[0].corners.length;
64
+ for (const s of shapes) {
65
+ if (s.corners.length !== n) {
66
+ throw new Error(`liquidMorph: every shape needs ${n} corners (got ${s.corners.length}); pad triangles/arrows with a collinear point`);
67
+ }
68
+ }
69
+ const d = [];
70
+ const first = shapes[0];
71
+ const d0 = roundPoly(first.corners, first.radius ?? 0);
72
+ d.push({ time: first.at, value: d0 });
73
+ let prevEnd = first.at;
74
+ if (first.hold) {
75
+ prevEnd = first.at + first.hold;
76
+ d.push({ time: prevEnd, value: d0 });
77
+ }
78
+ for (let i = 1; i < shapes.length; i++) {
79
+ const s = shapes[i], p = shapes[i - 1];
80
+ const dTarget = roundPoly(s.corners, s.radius ?? 0);
81
+ if (liquidity > 0) {
82
+ // Flow through a blob at the time-midpoint: accelerate in, decelerate out.
83
+ const mid = (prevEnd + s.at) / 2;
84
+ d.push({ time: mid, value: blob(p.corners, s.corners, liquidity), easing: 'ease-in-sine' });
85
+ d.push({ time: s.at, value: dTarget, easing: s.easing ?? 'ease-out-sine' });
86
+ }
87
+ else {
88
+ d.push({ time: s.at, value: dTarget, easing: s.easing ?? 'ease-in-out-sine' });
89
+ }
90
+ prevEnd = s.at;
91
+ if (s.hold) {
92
+ prevEnd = s.at + s.hold;
93
+ d.push({ time: prevEnd, value: dTarget });
94
+ }
95
+ }
96
+ return {
97
+ id,
98
+ type: 'shape',
99
+ layer,
100
+ time,
101
+ duration,
102
+ x,
103
+ y,
104
+ x_anchor: '50%',
105
+ y_anchor: '50%',
106
+ width,
107
+ height,
108
+ view_box,
109
+ paths: [{ fill, d }],
110
+ ...extra,
111
+ };
112
+ }
113
+ //# sourceMappingURL=liquid-morph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liquid-morph.js","sourceRoot":"","sources":["../src/liquid-morph.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { Element, Material } from '@clipkit/protocol';
2
+ export interface LitSurfaceProps {
3
+ /** Id prefix for produced elements. */
4
+ id: string;
5
+ /** Card center in canvas px. */
6
+ x: number;
7
+ y: number;
8
+ /** Card size in px. Default 1200×720. */
9
+ width?: number;
10
+ height?: number;
11
+ /**
12
+ * Card content — primitive elements positioned RELATIVE to the card's
13
+ * top-left (0,0 = card corner). They flatten into the lit plane.
14
+ */
15
+ elements: Element[];
16
+ /** Fill painted behind the content. Default a dark slate. */
17
+ background?: string;
18
+ /** Corner radius in px. Default 36. */
19
+ borderRadius?: number;
20
+ /**
21
+ * PBR material. Default a glossy dark-UI look (medium-low roughness,
22
+ * fairly metallic) so a key light reads as a soft sweeping sheen.
23
+ */
24
+ material?: Material;
25
+ /**
26
+ * Peak y-rotation in degrees; the card swings ±tilt (ping-pong) over
27
+ * its lifetime so the highlight sweeps. Default 0 (static — drive it
28
+ * with a Source camera instead). Pair with `camera.perspective`.
29
+ */
30
+ tilt?: number;
31
+ /** Push toward (+) / away from (−) the camera, px. Default 0. */
32
+ z?: number;
33
+ time: number;
34
+ duration: number;
35
+ layer: number;
36
+ }
37
+ export declare function litSurface(props: LitSurfaceProps): Element;
38
+ //# sourceMappingURL=lit-surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lit-surface.d.ts","sourceRoot":"","sources":["../src/lit-surface.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAID,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAiD1D"}
@@ -0,0 +1,63 @@
1
+ // litSurface — a UI card that responds to scene light (LIGHTING §4.8).
2
+ //
3
+ // COMPONENT pattern: returns ONE `clip: true` group carrying a `material`.
4
+ // The §4.4.3 flattening rule composites the children into a single layer,
5
+ // and the runtime then shades that layer as ONE lit plane — so a
6
+ // view-dependent sheen / environment reflection sweeps across the whole
7
+ // card as the camera moves (textured-surface lighting).
8
+ //
9
+ // Pair with Source-level lighting to see anything:
10
+ // source.lights — ambient fill + a directional key
11
+ // source.environment — a gradient "sky" the surface mirrors (optional)
12
+ // Without lights and without an environment the card renders unlit (the
13
+ // material is simply ignored — opt-in, zero cost).
14
+ const DEFAULT_MATERIAL = { roughness: 0.38, metalness: 0.8, reflectivity: 1 };
15
+ export function litSurface(props) {
16
+ const { id, x, y, elements, time, duration, layer } = props;
17
+ const W = props.width ?? 1200;
18
+ const H = props.height ?? 720;
19
+ const radius = props.borderRadius ?? 36;
20
+ const tilt = props.tilt ?? 0;
21
+ const body = {
22
+ id: `${id}-bg`,
23
+ type: 'shape',
24
+ shape: 'rectangle',
25
+ layer: 0,
26
+ x: 0,
27
+ y: 0,
28
+ width: W,
29
+ height: H,
30
+ fill_color: props.background ?? '#161f38',
31
+ };
32
+ return {
33
+ id,
34
+ type: 'group',
35
+ layer,
36
+ time,
37
+ duration,
38
+ x,
39
+ y,
40
+ x_anchor: '50%',
41
+ y_anchor: '50%',
42
+ width: W,
43
+ height: H,
44
+ clip: true,
45
+ border_radius: radius,
46
+ material: props.material ?? DEFAULT_MATERIAL,
47
+ ...(props.z !== undefined ? { z: props.z } : {}),
48
+ ...(tilt !== 0
49
+ ? {
50
+ keyframe_animations: [{
51
+ property: 'y_rotation',
52
+ loop: 'ping-pong',
53
+ keyframes: [
54
+ { time: 0, value: -tilt },
55
+ { time: Math.max(0.1, duration / 2), value: tilt, easing: 'ease-in-out' },
56
+ ],
57
+ }],
58
+ }
59
+ : {}),
60
+ elements: [body, ...elements],
61
+ };
62
+ }
63
+ //# sourceMappingURL=lit-surface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lit-surface.js","sourceRoot":"","sources":["../src/lit-surface.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,EAAE;AACF,2EAA2E;AAC3E,0EAA0E;AAC1E,iEAAiE;AACjE,wEAAwE;AACxE,wDAAwD;AACxD,EAAE;AACF,mDAAmD;AACnD,2DAA2D;AAC3D,0EAA0E;AAC1E,wEAAwE;AACxE,mDAAmD;AAwCnD,MAAM,gBAAgB,GAAa,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;AAExF,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAC5D,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC;IAC9B,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,GAAG,CAAC;IAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;IAE7B,MAAM,IAAI,GAAY;QACpB,EAAE,EAAE,GAAG,EAAE,KAAK;QACd,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,CAAC;QACR,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC;QACT,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,SAAS;KAC1C,CAAC;IAEF,OAAO;QACL,EAAE;QACF,IAAI,EAAE,OAAO;QACb,KAAK;QACL,IAAI;QACJ,QAAQ;QACR,CAAC;QACD,CAAC;QACD,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,KAAK;QACf,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC;QACT,IAAI,EAAE,IAAI;QACV,aAAa,EAAE,MAAM;QACrB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,gBAAgB;QAC5C,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,GAAG,CAAC,IAAI,KAAK,CAAC;YACZ,CAAC,CAAC;gBACE,mBAAmB,EAAE,CAAC;wBACpB,QAAQ,EAAE,YAAY;wBACtB,IAAI,EAAE,WAAoB;wBAC1B,SAAS,EAAE;4BACT,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE;4BACzB,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE;yBAC1E;qBACF,CAAC;aACH;YACH,CAAC,CAAC,EAAE,CAAC;QACP,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,QAAQ,CAAC;KAC9B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { Element } from '@clipkit/protocol';
2
+ import { type ColorName, type ThemeName } from './theme.js';
3
+ export interface LowerThirdProps {
4
+ /** Used as the id prefix for every produced element. */
5
+ id: string;
6
+ /** Speaker / subject name — the bold line. */
7
+ name: string;
8
+ /** Optional role / title line under the name. */
9
+ role?: string;
10
+ /** Accent color slot — drives the leading bar. */
11
+ color: ColorName;
12
+ theme?: ThemeName;
13
+ /**
14
+ * Position of the strip's LEFT EDGE / vertical center, in canvas px.
15
+ * Typical: x = 80, y = canvasHeight - 180.
16
+ */
17
+ x: number;
18
+ y: number;
19
+ time: number;
20
+ duration: number;
21
+ layer: number;
22
+ /** Panel width in px. Default 560. */
23
+ width?: number;
24
+ }
25
+ export declare function lowerThird(props: LowerThirdProps): Element;
26
+ //# sourceMappingURL=lower-third.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lower-third.d.ts","sourceRoot":"","sources":["../src/lower-third.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAwB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAElF,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;OAGG;IACH,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CA6F1D"}
@@ -0,0 +1,101 @@
1
+ // LowerThird — the broadcast name/role strip: an accent bar, a white
2
+ // panel with the speaker's name and an optional role line, sliding in
3
+ // from the left and exiting before the element window closes.
4
+ //
5
+ // COMPONENT pattern: returns a single `group` element so the strip is
6
+ // positioned, animated, or time-remapped as one unit, and expands to
7
+ // plain primitives only (no nested-composition schema element exists —
8
+ // reuse is an authoring-function concern, §5.8.3 covers nested time).
9
+ import { assignLayers } from './layers.js';
10
+ import { getFonts, getPalette } from './theme.js';
11
+ export function lowerThird(props) {
12
+ const { id, name, role, color, x, y, time, duration, layer } = props;
13
+ const theme = props.theme ?? 'mux';
14
+ const palette = getPalette(theme, color);
15
+ const fonts = getFonts(theme);
16
+ const W = props.width ?? 560;
17
+ const H = role ? 124 : 96;
18
+ const BAR_W = 12;
19
+ const PAD = 28;
20
+ const SLIDE = 0.45;
21
+ // Children are in group-local coordinates ((0,0) = panel top-left).
22
+ const children = [
23
+ {
24
+ id: `${id}-panel`,
25
+ type: 'shape',
26
+ shape: 'rectangle',
27
+ x: W / 2,
28
+ y: H / 2,
29
+ x_anchor: '50%',
30
+ y_anchor: '50%',
31
+ width: W,
32
+ height: H,
33
+ fill_color: '#ffffff',
34
+ border_radius: 8,
35
+ },
36
+ {
37
+ id: `${id}-bar`,
38
+ type: 'shape',
39
+ shape: 'rectangle',
40
+ x: BAR_W / 2,
41
+ y: H / 2,
42
+ x_anchor: '50%',
43
+ y_anchor: '50%',
44
+ width: BAR_W,
45
+ height: H,
46
+ fill_color: palette.accent,
47
+ border_radius: 4,
48
+ },
49
+ {
50
+ id: `${id}-name`,
51
+ type: 'text',
52
+ time: 0.15,
53
+ text: name,
54
+ x: BAR_W + PAD,
55
+ y: role ? H / 2 - 22 : H / 2,
56
+ x_anchor: 0,
57
+ y_anchor: '50%',
58
+ font_family: fonts.sans,
59
+ font_size: 40,
60
+ font_weight: '700',
61
+ fill_color: palette.text,
62
+ animations: [{ type: 'fade-in', duration: 0.4 }],
63
+ },
64
+ ];
65
+ if (role) {
66
+ children.push({
67
+ id: `${id}-role`,
68
+ type: 'text',
69
+ time: 0.3,
70
+ text: role,
71
+ x: BAR_W + PAD,
72
+ y: H / 2 + 26,
73
+ x_anchor: 0,
74
+ y_anchor: '50%',
75
+ font_family: fonts.sans,
76
+ font_size: 28,
77
+ fill_color: palette.textMuted,
78
+ animations: [{ type: 'fade-in', duration: 0.4 }],
79
+ });
80
+ }
81
+ return {
82
+ id,
83
+ type: 'group',
84
+ layer,
85
+ time,
86
+ duration,
87
+ x,
88
+ y,
89
+ x_anchor: 0,
90
+ y_anchor: 0.5,
91
+ width: W,
92
+ height: H,
93
+ animations: [
94
+ { type: 'fade-in', duration: SLIDE },
95
+ { type: 'slide-left-in', duration: SLIDE, easing: 'ease-out' },
96
+ { type: 'fade-out', time: 'end', duration: 0.4 },
97
+ ],
98
+ elements: assignLayers(children),
99
+ };
100
+ }
101
+ //# sourceMappingURL=lower-third.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lower-third.js","sourceRoot":"","sources":["../src/lower-third.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { Element } from '@clipkit/protocol';
2
+ export interface MorphFrame {
3
+ /** SVG path data. Use `morphPaths.*` (or any path) — all frames must share the
4
+ * same command structure to morph rather than snap. */
5
+ d: string;
6
+ /** Time (seconds, element-local) this shape is reached. */
7
+ at: number;
8
+ /** Easing into this frame (e.g. 'ease-in-out'). */
9
+ easing?: string;
10
+ }
11
+ export interface MorphShapeProps {
12
+ id: string;
13
+ /** Shapes to morph through, in order (≥1). */
14
+ frames: MorphFrame[];
15
+ /** SVG user space the paths are drawn in. Default [0,0,200,200]. */
16
+ viewBox?: [number, number, number, number];
17
+ /** On-canvas size + centre. */
18
+ width: number;
19
+ height: number;
20
+ x: number;
21
+ y: number;
22
+ fill?: string;
23
+ stroke?: string;
24
+ strokeWidth?: number;
25
+ time: number;
26
+ duration: number;
27
+ track: number;
28
+ }
29
+ export declare function morphShape(props: MorphShapeProps): Element;
30
+ export declare function morphPaths(V?: number): {
31
+ /** A fat horizontal line / pill body. */
32
+ bar: (t?: number) => string;
33
+ /** A tapered wedge (thin left → thick right). */
34
+ wedge: (t?: number) => string;
35
+ /** A right-pointing play arrow (▶) — tip vertex doubled. */
36
+ arrow: () => string;
37
+ /** A diamond / rotated square. */
38
+ diamond: () => string;
39
+ /** A parallelogram (slab). */
40
+ slab: (skew?: number) => string;
41
+ };
42
+ //# sourceMappingURL=morph-shape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"morph-shape.d.ts","sourceRoot":"","sources":["../src/morph-shape.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,UAAU;IACzB;4DACwD;IACxD,CAAC,EAAE,MAAM,CAAC;IACV,2DAA2D;IAC3D,EAAE,EAAE,MAAM,CAAC;IACX,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,oEAAoE;IACpE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAuB1D;AASD,wBAAgB,UAAU,CAAC,CAAC,SAAM;IAI9B,yCAAyC;;IAEzC,iDAAiD;;IAEjD,4DAA4D;;IAE5D,kCAAkC;;IAElC,8BAA8B;;EAGjC"}
@@ -0,0 +1,57 @@
1
+ // morphShape — an SVG element whose path `d` morphs through a sequence of shapes
2
+ // over time. The runtime interpolates `d` keyframes point-by-point ONLY when the
3
+ // two paths share the same command structure (same letters, same argument
4
+ // counts) — mismatched paths snap instead of morph, and arcs (A) never morph.
5
+ //
6
+ // So the value here is `morphPaths`: a family of shape builders that ALL emit the
7
+ // same 4-point `M·L·L·L·Z` structure on a shared viewBox, guaranteeing any two
8
+ // morph smoothly (bar → wedge → arrow → diamond, etc.). Combine with travel by
9
+ // dropping the result in a group with an animated `x`, and push in on it with a
10
+ // `zoomRig` for the full "button becomes a line, travels, becomes something else"
11
+ // gesture.
12
+ export function morphShape(props) {
13
+ const { id, frames, width, height, x, y, fill, stroke, strokeWidth, time, duration, track } = props;
14
+ const viewBox = props.viewBox ?? [0, 0, 200, 200];
15
+ const path = {
16
+ d: frames.map((fr) => ({ time: fr.at, value: fr.d, ...(fr.easing ? { easing: fr.easing } : {}) })),
17
+ ...(fill ? { fill } : {}),
18
+ ...(stroke ? { stroke } : {}),
19
+ ...(strokeWidth ? { stroke_width: strokeWidth } : {}),
20
+ };
21
+ return {
22
+ id,
23
+ type: 'svg',
24
+ track,
25
+ time,
26
+ duration,
27
+ view_box: viewBox,
28
+ width,
29
+ height,
30
+ x,
31
+ y,
32
+ paths: [path],
33
+ };
34
+ }
35
+ // ── compatible shape builders ─────────────────────────────────────────────────
36
+ // Every builder returns a 4-point `M x y L x y L x y L x y Z` path on a V×V box,
37
+ // so ANY pair morphs point-by-point. Shapes that are "really" triangles repeat a
38
+ // vertex to keep the 4-point count. `t` = stroke/line half-thickness (px in VB).
39
+ const f = (n) => Math.round(n * 100) / 100;
40
+ const quad = (p) => `M${f(p[0][0])} ${f(p[0][1])} L${f(p[1][0])} ${f(p[1][1])} L${f(p[2][0])} ${f(p[2][1])} L${f(p[3][0])} ${f(p[3][1])} Z`;
41
+ export function morphPaths(V = 200) {
42
+ const c = V / 2;
43
+ const pad = V * 0.12;
44
+ return {
45
+ /** A fat horizontal line / pill body. */
46
+ bar: (t = V * 0.06) => quad([[pad, c - t], [V - pad, c - t], [V - pad, c + t], [pad, c + t]]),
47
+ /** A tapered wedge (thin left → thick right). */
48
+ wedge: (t = V * 0.12) => quad([[pad, c - t * 0.25], [V - pad, c - t], [V - pad, c + t], [pad, c + t * 0.25]]),
49
+ /** A right-pointing play arrow (▶) — tip vertex doubled. */
50
+ arrow: () => quad([[pad, pad], [V - pad, c], [V - pad, c], [pad, V - pad]]),
51
+ /** A diamond / rotated square. */
52
+ diamond: () => quad([[c, pad], [V - pad, c], [c, V - pad], [pad, c]]),
53
+ /** A parallelogram (slab). */
54
+ slab: (skew = V * 0.16) => quad([[pad + skew, c - V * 0.18], [V - pad, c - V * 0.18], [V - pad - skew, c + V * 0.18], [pad, c + V * 0.18]]),
55
+ };
56
+ }
57
+ //# sourceMappingURL=morph-shape.js.map