greenlight-motion 0.1.5 → 0.2.1
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/README.md +9 -9
- package/dist/bundle-inputs.json +14 -0
- package/dist/greenlight-motion-script.mjs +3250 -274
- package/dist/greenlight-motion.js +703 -661
- package/dist/greenlight-motion.mjs +41156 -39509
- package/dist/script-inputs.json +8 -0
- package/package.json +2 -2
- package/schema/capabilities.json +1 -1
- package/skill/SKILL.md +43 -12
- package/skill/references/scene-script.md +48 -6
- package/skill/references/video-camera.md +106 -4
- package/skill/references/video-design.md +5 -0
- package/skill/references/video-effects.md +18 -0
- package/skill/references/video-paths.md +33 -2
- package/skill/references/video-projects.md +32 -3
- package/skill/references/video-text-presets.md +31 -1
- package/skill/references/video-text.md +36 -1
|
@@ -34,15 +34,6 @@ var init_define_import_meta_env = __esm({
|
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
// stand-in:three
|
|
38
|
-
var require_three = __commonJS({
|
|
39
|
-
"stand-in:three"(exports, module) {
|
|
40
|
-
init_define_import_meta_env();
|
|
41
|
-
var f = () => void 0;
|
|
42
|
-
module.exports = new Proxy(f, { get: () => f });
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
37
|
// node_modules/mp4box/dist/mp4box.all.js
|
|
47
38
|
var require_mp4box_all = __commonJS({
|
|
48
39
|
"node_modules/mp4box/dist/mp4box.all.js"(exports) {
|
|
@@ -6700,8 +6691,8 @@ var require_mp4box_all = __commonJS({
|
|
|
6700
6691
|
var require_advancedTransitions = __commonJS({
|
|
6701
6692
|
"stand-in:./advancedTransitions"(exports, module) {
|
|
6702
6693
|
init_define_import_meta_env();
|
|
6703
|
-
var
|
|
6704
|
-
module.exports = new Proxy(
|
|
6694
|
+
var f2 = () => void 0;
|
|
6695
|
+
module.exports = new Proxy(f2, { get: () => f2 });
|
|
6705
6696
|
}
|
|
6706
6697
|
});
|
|
6707
6698
|
|
|
@@ -6718,13 +6709,14 @@ init_define_import_meta_env();
|
|
|
6718
6709
|
init_define_import_meta_env();
|
|
6719
6710
|
var TXA = (function() {
|
|
6720
6711
|
"use strict";
|
|
6721
|
-
const
|
|
6712
|
+
const clamp6 = (v, a, b2) => Math.min(b2, Math.max(a, v));
|
|
6722
6713
|
const lerp = (a, b2, t) => a + (b2 - a) * t;
|
|
6723
|
-
const
|
|
6714
|
+
const num8 = (label, def, min, max, step, unit = "", kf = true, title = "") => ({ type: "num", label, default: def, min, max, step, unit, kf, title });
|
|
6724
6715
|
const en = (label, def, labels, title = "") => ({ type: "enum", label, default: def, values: Object.keys(labels), labels, title });
|
|
6725
6716
|
const bool = (label, def, title = "") => ({ type: "bool", label, default: def, title });
|
|
6726
6717
|
const col = (label, def) => ({ type: "color", label, default: def });
|
|
6727
6718
|
const cols = (label, def) => ({ type: "colors", label, default: def });
|
|
6719
|
+
const bez = (label, title = "") => ({ type: "bezier", label, default: null, title });
|
|
6728
6720
|
const CURVE_NAMES2 = [
|
|
6729
6721
|
"Linear",
|
|
6730
6722
|
"Ease In",
|
|
@@ -6788,12 +6780,19 @@ var TXA = (function() {
|
|
|
6788
6780
|
"In: units arrive from the effects. Out: they leave into them at the layer end. While its turn: the effects light each unit in turn. From its turn on: they stay on"
|
|
6789
6781
|
),
|
|
6790
6782
|
order: en("Order", "forward", { forward: "Forward", backward: "Backward", center: "From centre", edges: "From edges", random: "Random" }),
|
|
6791
|
-
delay:
|
|
6792
|
-
stagger:
|
|
6793
|
-
duration:
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6783
|
+
delay: num8("Delay", 0, -60, 600, 0.01, "s", false, "In: after the layer starts. Out: before the layer ends. Voice: after the word is spoken (negative = before)"),
|
|
6784
|
+
stagger: num8("Stagger", 0.06, 0, 10, 5e-3, "s", false, "Time between one unit's turn and the next"),
|
|
6785
|
+
duration: num8("Duration", 0.6, 0, 20, 0.01, "s", false, "How long each unit takes"),
|
|
6786
|
+
// Custom plays `bezier` (the app's keyframe Custom: a curve shaped by its handles).
|
|
6787
|
+
curve: en(
|
|
6788
|
+
"Curve",
|
|
6789
|
+
"Ease Out",
|
|
6790
|
+
{ ...Object.fromEntries(CURVE_NAMES2.map((n2) => [n2, n2])), Custom: "Custom" },
|
|
6791
|
+
"Each unit's own easing: Back, Bounce and Overshoot land past their rest and settle"
|
|
6792
|
+
),
|
|
6793
|
+
bezier: bez("Custom curve", "The Custom curve's handles, x1 y1 x2 y2 (x 0\u20261, y \u22123\u20264)"),
|
|
6794
|
+
hold: num8("Hold", 0, 0, 600, 0.01, "s", false, "How long a unit stays lit. 0 = until the next unit's turn"),
|
|
6795
|
+
fadeOut: num8("Fade out", 0, 0, 20, 0.01, "s", false),
|
|
6797
6796
|
keepLast: bool("Keep last on", false, "The last unit stays lit"),
|
|
6798
6797
|
spread: bool("Letters follow the voice", true, "Letters of a word take their turns across the time it is spoken, so they type at speaking pace"),
|
|
6799
6798
|
basedOn: en("Based on", "chars", { chars: "Characters", charsNoSpace: "Characters, no spaces", words: "Words", lines: "Lines" }),
|
|
@@ -6805,27 +6804,27 @@ var TXA = (function() {
|
|
|
6805
6804
|
"The unit this animator moves, scales and turns. Same as Based on: a word range turns whole words"
|
|
6806
6805
|
),
|
|
6807
6806
|
units: en("Units", "percent", { percent: "%", index: "Index" }, "Index = count units (Start 2 = the third unit)"),
|
|
6808
|
-
start:
|
|
6809
|
-
end:
|
|
6810
|
-
offset:
|
|
6807
|
+
start: num8("Start", 0, -1e3, 1e3, 1),
|
|
6808
|
+
end: num8("End", 100, -1e3, 1e3, 1),
|
|
6809
|
+
offset: num8("Offset", 0, -1e3, 1e3, 1),
|
|
6811
6810
|
shape: en("Shape", "square", { square: "Square", rampUp: "Ramp up", rampDown: "Ramp down", triangle: "Triangle", round: "Round", smooth: "Smooth" }),
|
|
6812
|
-
smooth:
|
|
6813
|
-
easeHigh:
|
|
6814
|
-
easeLow:
|
|
6815
|
-
amount:
|
|
6811
|
+
smooth: num8("Smoothness", 100, 0, 100, 1, "%", true, "Width of the edge fade, in units: 100 % = one unit"),
|
|
6812
|
+
easeHigh: num8("Ease high", 0, -100, 100, 1, "%"),
|
|
6813
|
+
easeLow: num8("Ease low", 0, -100, 100, 1, "%"),
|
|
6814
|
+
amount: num8("Amount", 100, -100, 100, 1, "%", true, "Negative amounts push every effect the other way"),
|
|
6816
6815
|
randomize: bool("Random order", false),
|
|
6817
|
-
seed:
|
|
6816
|
+
seed: num8("Seed", 1, 0, 9999, 1, "", false),
|
|
6818
6817
|
// Keyframe-free travel: Offset advances this much per second (the Stroke
|
|
6819
6818
|
// options "Flow" idea). Integrated by time, so not keyframable. Without Loop a
|
|
6820
6819
|
// range parked off the text lands at Offset 0 and stays (see rangeEdges).
|
|
6821
|
-
flow:
|
|
6820
|
+
flow: num8("Flow", 0, -400, 400, 1, "/s", false, "Moves the whole range (its Offset) this much per second, no keyframes. From a negative Offset the range travels in and stops in place; from 0 it travels off the text"),
|
|
6822
6821
|
loop: bool("Loop", false, "The range wraps around the ends"),
|
|
6823
6822
|
// Units are cut off at the edge of their own resting box (Transform around's
|
|
6824
6823
|
// unit), so a move slides them in from behind it — Text Animation's Mask.
|
|
6825
6824
|
mask: bool("Mask", false, "Each unit is cut off at the edge of its own box, so it slides in from behind it")
|
|
6826
6825
|
};
|
|
6827
6826
|
const RANGE_ONLY2 = { units: 1, start: 1, end: 1, offset: 1, shape: 1, smooth: 1, easeHigh: 1, easeLow: 1, flow: 1 };
|
|
6828
|
-
const DRIVE_ONLY2 = { animate: 1, order: 1, delay: 1, stagger: 1, duration: 1, curve: 1, hold: 1, fadeOut: 1, keepLast: 1, spread: 1 };
|
|
6827
|
+
const DRIVE_ONLY2 = { animate: 1, order: 1, delay: 1, stagger: 1, duration: 1, curve: 1, bezier: 1, hold: 1, fadeOut: 1, keepLast: 1, spread: 1 };
|
|
6829
6828
|
function rangeShows2(an, key, { voiced = false } = {}) {
|
|
6830
6829
|
const r = an && an.range || {};
|
|
6831
6830
|
const driven = r.drive === "time" || r.drive === "voice";
|
|
@@ -6852,17 +6851,17 @@ var TXA = (function() {
|
|
|
6852
6851
|
const WIGGLE_FIELDS2 = {
|
|
6853
6852
|
enabled: bool("Wiggle", false),
|
|
6854
6853
|
mode: en("Mode", "intersect", { add: "Add", subtract: "Subtract", intersect: "Intersect", min: "Min", max: "Max", difference: "Difference" }),
|
|
6855
|
-
max:
|
|
6856
|
-
min:
|
|
6857
|
-
rate:
|
|
6858
|
-
corr:
|
|
6859
|
-
tPhase:
|
|
6860
|
-
sPhase:
|
|
6861
|
-
seed:
|
|
6854
|
+
max: num8("Max amount", 100, -100, 100, 1, "%"),
|
|
6855
|
+
min: num8("Min amount", -100, -100, 100, 1, "%"),
|
|
6856
|
+
rate: num8("Wiggles / s", 2, 0, 30, 0.1, "/s", false),
|
|
6857
|
+
corr: num8("Correlation", 0, 0, 100, 1, "%"),
|
|
6858
|
+
tPhase: num8("Temporal phase", 0, -360, 360, 1, "\xB0", false),
|
|
6859
|
+
sPhase: num8("Spatial phase", 0, -360, 360, 1, "\xB0", false),
|
|
6860
|
+
seed: num8("Seed", 1, 0, 9999, 1, "", false)
|
|
6862
6861
|
};
|
|
6863
6862
|
const LAYER_FIELDS2 = {
|
|
6864
|
-
pivotX:
|
|
6865
|
-
pivotY:
|
|
6863
|
+
pivotX: num8("Pivot X", 0, -100, 100, 1, "%", false, "% of the unit from its centre: \u2212100 = left edge, 100 = right edge"),
|
|
6864
|
+
pivotY: num8("Pivot Y", 0, -100, 100, 1, "%", false, "% of the unit from its centre: \u2212100 = top, 100 = bottom")
|
|
6866
6865
|
};
|
|
6867
6866
|
const LAYER_ITEM_KEYS2 = { pivotX: "textAnimatorPivotX", pivotY: "textAnimatorPivotY" };
|
|
6868
6867
|
const EFFECTS2 = [
|
|
@@ -6871,21 +6870,21 @@ var TXA = (function() {
|
|
|
6871
6870
|
label: "Position",
|
|
6872
6871
|
group: "Transform",
|
|
6873
6872
|
desc: "Move the selected units",
|
|
6874
|
-
fields: { x:
|
|
6873
|
+
fields: { x: num8("X", 0, -2e3, 2e3, 1, "px"), y: num8("Y", -80, -2e3, 2e3, 1, "px") }
|
|
6875
6874
|
},
|
|
6876
6875
|
{
|
|
6877
6876
|
key: "scale",
|
|
6878
6877
|
label: "Scale",
|
|
6879
6878
|
group: "Transform",
|
|
6880
6879
|
desc: "Grow or shrink each unit",
|
|
6881
|
-
fields: { scale:
|
|
6880
|
+
fields: { scale: num8("Scale", 160, -1e3, 1e3, 1, "%"), x: num8("Width", 100, -1e3, 1e3, 1, "%"), y: num8("Height", 100, -1e3, 1e3, 1, "%") }
|
|
6882
6881
|
},
|
|
6883
6882
|
{
|
|
6884
6883
|
key: "rotation",
|
|
6885
6884
|
label: "Rotation",
|
|
6886
6885
|
group: "Transform",
|
|
6887
6886
|
desc: "Spin each unit around its pivot",
|
|
6888
|
-
fields: { angle:
|
|
6887
|
+
fields: { angle: num8("Angle", 45, -3600, 3600, 1, "\xB0") }
|
|
6889
6888
|
},
|
|
6890
6889
|
{
|
|
6891
6890
|
key: "rotate3d",
|
|
@@ -6893,9 +6892,9 @@ var TXA = (function() {
|
|
|
6893
6892
|
group: "Transform",
|
|
6894
6893
|
desc: "Flip around X or Y, in perspective",
|
|
6895
6894
|
fields: {
|
|
6896
|
-
x:
|
|
6897
|
-
y:
|
|
6898
|
-
perspective:
|
|
6895
|
+
x: num8("Rotate X", 90, -720, 720, 1, "\xB0", true, "Positive tips the top away from you"),
|
|
6896
|
+
y: num8("Rotate Y", 0, -720, 720, 1, "\xB0", true, "Positive turns the right side away from you"),
|
|
6897
|
+
perspective: num8("Perspective", 800, 0, 2e4, 10, "px", true, "Camera distance from the unit: smaller = deeper 3D. 0 = flat, no depth")
|
|
6899
6898
|
}
|
|
6900
6899
|
},
|
|
6901
6900
|
{
|
|
@@ -6903,70 +6902,70 @@ var TXA = (function() {
|
|
|
6903
6902
|
label: "Skew",
|
|
6904
6903
|
group: "Transform",
|
|
6905
6904
|
desc: "Slant along an axis",
|
|
6906
|
-
fields: { skew:
|
|
6905
|
+
fields: { skew: num8("Skew", 25, -85, 85, 1, "\xB0"), axis: num8("Axis", 0, -180, 180, 1, "\xB0") }
|
|
6907
6906
|
},
|
|
6908
6907
|
{
|
|
6909
6908
|
key: "tracking",
|
|
6910
6909
|
label: "Tracking",
|
|
6911
6910
|
group: "Transform",
|
|
6912
6911
|
desc: "Space the units out \u2014 the line reflows",
|
|
6913
|
-
fields: { amount:
|
|
6912
|
+
fields: { amount: num8("Amount", 40, -200, 600, 1, "px") }
|
|
6914
6913
|
},
|
|
6915
6914
|
{
|
|
6916
6915
|
key: "opacity",
|
|
6917
6916
|
label: "Opacity",
|
|
6918
6917
|
group: "Colour",
|
|
6919
6918
|
desc: "Fade the selected units",
|
|
6920
|
-
fields: { opacity:
|
|
6919
|
+
fields: { opacity: num8("Opacity", 0, 0, 100, 1, "%") }
|
|
6921
6920
|
},
|
|
6922
6921
|
{
|
|
6923
6922
|
key: "fill",
|
|
6924
6923
|
label: "Fill colour",
|
|
6925
6924
|
group: "Colour",
|
|
6926
6925
|
desc: "Recolour the selection",
|
|
6927
|
-
fields: { color: col("Colour", "#ffd84d"), mix:
|
|
6926
|
+
fields: { color: col("Colour", "#ffd84d"), mix: num8("Mix", 100, 0, 100, 1, "%") }
|
|
6928
6927
|
},
|
|
6929
6928
|
{
|
|
6930
6929
|
key: "gradient",
|
|
6931
6930
|
label: "Gradient",
|
|
6932
6931
|
group: "Colour",
|
|
6933
6932
|
desc: "Paint the selection with a gradient",
|
|
6934
|
-
fields: { colors: cols("Colours", ["#7cf5ff", "#b36bff", "#ff5fa2"]), angle:
|
|
6933
|
+
fields: { colors: cols("Colours", ["#7cf5ff", "#b36bff", "#ff5fa2"]), angle: num8("Angle", 0, -360, 360, 1, "\xB0"), span: en("Spans", "text", { text: "Text", unit: "Unit" }), shift: num8("Shift", 0, -1e3, 1e3, 1, "%"), repeat: bool("Repeat", false), mix: num8("Mix", 100, 0, 100, 1, "%") }
|
|
6935
6934
|
},
|
|
6936
6935
|
{
|
|
6937
6936
|
key: "hue",
|
|
6938
6937
|
label: "Hue shift",
|
|
6939
6938
|
group: "Colour",
|
|
6940
6939
|
desc: "Rotate hue, saturation and brightness",
|
|
6941
|
-
fields: { hue:
|
|
6940
|
+
fields: { hue: num8("Hue", 180, -720, 720, 1, "\xB0"), saturation: num8("Saturation", 0, -100, 100, 1, "%"), brightness: num8("Brightness", 0, -100, 100, 1, "%") }
|
|
6942
6941
|
},
|
|
6943
6942
|
{
|
|
6944
6943
|
key: "stroke",
|
|
6945
6944
|
label: "Stroke",
|
|
6946
6945
|
group: "Colour",
|
|
6947
6946
|
desc: "Outline around the glyphs",
|
|
6948
|
-
fields: { color: col("Colour", "#ffffff"), width:
|
|
6947
|
+
fields: { color: col("Colour", "#ffffff"), width: num8("Width", 4, 0, 60, 0.5, "px"), outline: bool("Outline only", false) }
|
|
6949
6948
|
},
|
|
6950
6949
|
{
|
|
6951
6950
|
key: "glow",
|
|
6952
6951
|
label: "Glow",
|
|
6953
6952
|
group: "Light",
|
|
6954
6953
|
desc: "Soft light around the glyphs",
|
|
6955
|
-
fields: { color: col("Colour", "#5ef2ff"), size:
|
|
6954
|
+
fields: { color: col("Colour", "#5ef2ff"), size: num8("Size", 30, 0, 200, 1, "px"), strength: num8("Strength", 120, 0, 300, 1, "%") }
|
|
6956
6955
|
},
|
|
6957
6956
|
{
|
|
6958
6957
|
key: "shadow",
|
|
6959
6958
|
label: "Drop shadow",
|
|
6960
6959
|
group: "Light",
|
|
6961
6960
|
desc: "Offset shadow under the glyphs",
|
|
6962
|
-
fields: { color: col("Colour", "#000000"), x:
|
|
6961
|
+
fields: { color: col("Colour", "#000000"), x: num8("X", 0, -400, 400, 1, "px"), y: num8("Y", 18, -400, 400, 1, "px"), blur: num8("Blur", 20, 0, 200, 1, "px"), opacity: num8("Opacity", 70, 0, 100, 1, "%") }
|
|
6963
6962
|
},
|
|
6964
6963
|
{
|
|
6965
6964
|
key: "blur",
|
|
6966
6965
|
label: "Blur",
|
|
6967
6966
|
group: "Light",
|
|
6968
6967
|
desc: "Defocus the selection",
|
|
6969
|
-
fields: { amount:
|
|
6968
|
+
fields: { amount: num8("Blur", 18, 0, 120, 0.5, "px") }
|
|
6970
6969
|
},
|
|
6971
6970
|
{
|
|
6972
6971
|
key: "box",
|
|
@@ -6977,10 +6976,10 @@ var TXA = (function() {
|
|
|
6977
6976
|
color: col("Colour", "#33efab"),
|
|
6978
6977
|
behind: en("Behind", "selected", { selected: "Selected", revealed: "Revealed" }, "Revealed = the text this animator lets through. On a typing animator the box grows with the typing"),
|
|
6979
6978
|
shape: en("Shape", "line", { line: "Per line", unit: "Per unit", block: "Block" }, "Per line = one bar per run of selected units in a row. Per unit = a bar behind each unit on its own. Block = one shape around all of it, growing a line at a time"),
|
|
6980
|
-
padX:
|
|
6981
|
-
padY:
|
|
6982
|
-
radius:
|
|
6983
|
-
opacity:
|
|
6979
|
+
padX: num8("Padding X", 12, 0, 200, 1, "px"),
|
|
6980
|
+
padY: num8("Padding Y", 4, -60, 200, 1, "px"),
|
|
6981
|
+
radius: num8("Radius", 10, 0, 200, 1, "px"),
|
|
6982
|
+
opacity: num8("Opacity", 100, 0, 100, 1, "%"),
|
|
6984
6983
|
grow: bool("Grow with range", true),
|
|
6985
6984
|
withText: bool("Fade with text", false, "Each bar is only as visible as its unit: a word that has not appeared yet has no bar")
|
|
6986
6985
|
}
|
|
@@ -6996,10 +6995,10 @@ var TXA = (function() {
|
|
|
6996
6995
|
style: en("Style", "bar", { bar: "Bar", underscore: "Underscore", block: "Block" }),
|
|
6997
6996
|
ownColor: bool("Own colour", false, "Off = the text colour"),
|
|
6998
6997
|
color: col("Colour", "#ffffff"),
|
|
6999
|
-
width:
|
|
7000
|
-
blink:
|
|
6998
|
+
width: num8("Width", 0, 0, 80, 0.5, "px", false, "0 = automatic"),
|
|
6999
|
+
blink: num8("Blinks / s", 1.1, 0, 10, 0.1, "/s", false, "While the text is not typing. 0 = solid"),
|
|
7001
7000
|
hide: bool("Hide when typed", false),
|
|
7002
|
-
hideAfter:
|
|
7001
|
+
hideAfter: num8("Hide after", 0, 0, 600, 0.1, "s", false, "Seconds after the typing ends")
|
|
7003
7002
|
}
|
|
7004
7003
|
},
|
|
7005
7004
|
{
|
|
@@ -7007,14 +7006,14 @@ var TXA = (function() {
|
|
|
7007
7006
|
label: "Scramble",
|
|
7008
7007
|
group: "Characters",
|
|
7009
7008
|
desc: "Random glyphs that settle into the text",
|
|
7010
|
-
fields: { charset: en("Characters", "upper", { upper: "A\u2013Z", lower: "a\u2013z", digits: "0\u20139", alnum: "A\u2013Z 0\u20139", symbols: "Symbols", binary: "Binary", blocks: "Blocks" }), rate:
|
|
7009
|
+
fields: { charset: en("Characters", "upper", { upper: "A\u2013Z", lower: "a\u2013z", digits: "0\u20139", alnum: "A\u2013Z 0\u20139", symbols: "Symbols", binary: "Binary", blocks: "Blocks" }), rate: num8("Changes / s", 18, 0, 60, 1, "/s", false), seed: num8("Seed", 1, 0, 9999, 1, "", false) }
|
|
7011
7010
|
},
|
|
7012
7011
|
{
|
|
7013
7012
|
key: "charOffset",
|
|
7014
7013
|
label: "Character offset",
|
|
7015
7014
|
group: "Characters",
|
|
7016
7015
|
desc: "Shift letters through the alphabet",
|
|
7017
|
-
fields: { offset:
|
|
7016
|
+
fields: { offset: num8("Offset", 6, -60, 60, 1, "") }
|
|
7018
7017
|
}
|
|
7019
7018
|
];
|
|
7020
7019
|
const EFFECT_BY_KEY2 = Object.fromEntries(EFFECTS2.map((e2) => [e2.key, e2]));
|
|
@@ -7032,33 +7031,42 @@ var TXA = (function() {
|
|
|
7032
7031
|
};
|
|
7033
7032
|
function defaultsOf2(fields) {
|
|
7034
7033
|
const out = {};
|
|
7035
|
-
for (const [k,
|
|
7034
|
+
for (const [k, f2] of Object.entries(fields)) out[k] = Array.isArray(f2.default) ? f2.default.slice() : f2.default;
|
|
7036
7035
|
return out;
|
|
7037
7036
|
}
|
|
7038
|
-
function sanitizeField(
|
|
7039
|
-
switch (
|
|
7037
|
+
function sanitizeField(f2, v) {
|
|
7038
|
+
switch (f2.type) {
|
|
7040
7039
|
case "num": {
|
|
7041
7040
|
const n2 = Number(v);
|
|
7042
|
-
return Number.isFinite(n2) ?
|
|
7041
|
+
return Number.isFinite(n2) ? clamp6(n2, f2.min, f2.max) : f2.default;
|
|
7043
7042
|
}
|
|
7044
7043
|
case "enum":
|
|
7045
|
-
return
|
|
7044
|
+
return f2.values.includes(v) ? v : f2.default;
|
|
7046
7045
|
case "bool":
|
|
7047
|
-
return typeof v === "boolean" ? v :
|
|
7046
|
+
return typeof v === "boolean" ? v : f2.default;
|
|
7048
7047
|
case "color":
|
|
7049
|
-
return typeof v === "string" && /^#[0-9a-f]{6}$/i.test(v) ? v.toLowerCase() :
|
|
7048
|
+
return typeof v === "string" && /^#[0-9a-f]{6}$/i.test(v) ? v.toLowerCase() : f2.default;
|
|
7050
7049
|
case "colors": {
|
|
7051
7050
|
const list = Array.isArray(v) ? v.filter((c) => typeof c === "string" && /^#[0-9a-f]{6}$/i.test(c)).slice(0, 6) : [];
|
|
7052
|
-
return list.length >= 2 ? list.map((c) => c.toLowerCase()) :
|
|
7051
|
+
return list.length >= 2 ? list.map((c) => c.toLowerCase()) : f2.default.slice();
|
|
7052
|
+
}
|
|
7053
|
+
// The app's sanitizeBezier (easingCurves.js): x stays in 0…1 so the solver
|
|
7054
|
+
// converges, y may overshoot; 4 dp so a dragged handle stays short.
|
|
7055
|
+
case "bezier": {
|
|
7056
|
+
if (!Array.isArray(v) || v.length !== 4) return f2.default;
|
|
7057
|
+
const n2 = v.map(Number);
|
|
7058
|
+
if (n2.some((x) => !Number.isFinite(x))) return f2.default;
|
|
7059
|
+
const r42 = (x) => Math.round(x * 1e4) / 1e4;
|
|
7060
|
+
return [r42(clamp6(n2[0], 0, 1)), r42(clamp6(n2[1], -3, 4)), r42(clamp6(n2[2], 0, 1)), r42(clamp6(n2[3], -3, 4))];
|
|
7053
7061
|
}
|
|
7054
7062
|
default:
|
|
7055
|
-
return
|
|
7063
|
+
return f2.default;
|
|
7056
7064
|
}
|
|
7057
7065
|
}
|
|
7058
7066
|
function sanitizeGroup(fields, raw) {
|
|
7059
7067
|
const out = {};
|
|
7060
7068
|
const src = raw && typeof raw === "object" ? raw : {};
|
|
7061
|
-
for (const [k,
|
|
7069
|
+
for (const [k, f2] of Object.entries(fields)) out[k] = sanitizeField(f2, src[k]);
|
|
7062
7070
|
return out;
|
|
7063
7071
|
}
|
|
7064
7072
|
const ID_RE = /^[A-Za-z0-9_-]{1,24}$/;
|
|
@@ -7071,11 +7079,14 @@ var TXA = (function() {
|
|
|
7071
7079
|
if (!raw || typeof raw !== "object" || !ID_RE.test(String(raw.aid || ""))) return null;
|
|
7072
7080
|
const seen = /* @__PURE__ */ new Set();
|
|
7073
7081
|
const effects = (Array.isArray(raw.effects) ? raw.effects : []).map(sanitizeEffect).filter((e2) => e2 && !seen.has(e2.pid) && seen.add(e2.pid)).slice(0, MAX_EFFECTS2);
|
|
7082
|
+
const range = sanitizeGroup(RANGE_FIELDS2, raw.range);
|
|
7083
|
+
if (range.curve !== "Custom") range.bezier = null;
|
|
7084
|
+
else if (!range.bezier) range.curve = RANGE_FIELDS2.curve.default;
|
|
7074
7085
|
return {
|
|
7075
7086
|
aid: raw.aid,
|
|
7076
7087
|
name: typeof raw.name === "string" && raw.name.trim() ? raw.name.trim().slice(0, 40) : `Animator ${i + 1}`,
|
|
7077
7088
|
enabled: raw.enabled !== false,
|
|
7078
|
-
range
|
|
7089
|
+
range,
|
|
7079
7090
|
wiggle: sanitizeGroup(WIGGLE_FIELDS2, raw.wiggle),
|
|
7080
7091
|
effects
|
|
7081
7092
|
};
|
|
@@ -7153,22 +7164,22 @@ var TXA = (function() {
|
|
|
7153
7164
|
const an = (animators || []).find((a) => a && a.aid === aid);
|
|
7154
7165
|
if (!an) return null;
|
|
7155
7166
|
if (chan.startsWith("wig.")) {
|
|
7156
|
-
const
|
|
7157
|
-
return
|
|
7167
|
+
const f3 = WIGGLE_FIELDS2[chan.slice(4)];
|
|
7168
|
+
return f3 && f3.kf ? { an, spec: f3, label: `Wiggle ${f3.label.toLowerCase()}` } : null;
|
|
7158
7169
|
}
|
|
7159
7170
|
const dot = chan.indexOf(".");
|
|
7160
7171
|
if (dot > 0) {
|
|
7161
7172
|
const fx = (an.effects || []).find((e2) => e2 && e2.pid === chan.slice(0, dot));
|
|
7162
7173
|
const def = fx && EFFECT_BY_KEY2[fx.type];
|
|
7163
|
-
const
|
|
7164
|
-
if (!
|
|
7174
|
+
const f3 = def && def.fields[chan.slice(dot + 1)];
|
|
7175
|
+
if (!f3 || f3.type !== "num" || !f3.kf) return null;
|
|
7165
7176
|
const numeric = Object.values(def.fields).filter((x) => x.type === "num" && x.kf).length;
|
|
7166
|
-
return { an, fx, spec:
|
|
7177
|
+
return { an, fx, spec: f3, label: numeric === 1 || f3.label === def.label ? def.label : `${def.label} ${f3.label}` };
|
|
7167
7178
|
}
|
|
7168
|
-
const
|
|
7169
|
-
if (!
|
|
7170
|
-
const unit = chan === "start" || chan === "end" || chan === "offset" ? an.range?.units === "index" ? "" : "%" :
|
|
7171
|
-
return { an, spec: { ...
|
|
7179
|
+
const f2 = RANGE_FIELDS2[chan];
|
|
7180
|
+
if (!f2 || !f2.kf) return null;
|
|
7181
|
+
const unit = chan === "start" || chan === "end" || chan === "offset" ? an.range?.units === "index" ? "" : "%" : f2.unit;
|
|
7182
|
+
return { an, spec: { ...f2, unit }, label: f2.label };
|
|
7172
7183
|
}
|
|
7173
7184
|
function trackLabel2(animators, key) {
|
|
7174
7185
|
const p = parseTrackKey2(key);
|
|
@@ -7196,20 +7207,20 @@ var TXA = (function() {
|
|
|
7196
7207
|
for (const an of list) {
|
|
7197
7208
|
if (!an || !an.aid) continue;
|
|
7198
7209
|
const driven = !!an.range?.drive && an.range.drive !== "range";
|
|
7199
|
-
for (const [k,
|
|
7200
|
-
if (!
|
|
7210
|
+
for (const [k, f2] of Object.entries(RANGE_FIELDS2)) {
|
|
7211
|
+
if (!f2.kf) continue;
|
|
7201
7212
|
if (driven && RANGE_ONLY2[k]) continue;
|
|
7202
7213
|
if (k === "smooth" && an.range?.shape !== "square") continue;
|
|
7203
7214
|
if ((k === "easeHigh" || k === "easeLow") && an.range?.shape === "square") continue;
|
|
7204
7215
|
row(an, k);
|
|
7205
7216
|
}
|
|
7206
7217
|
if (an.wiggle && an.wiggle.enabled) {
|
|
7207
|
-
for (const [k,
|
|
7218
|
+
for (const [k, f2] of Object.entries(WIGGLE_FIELDS2)) if (f2.kf) row(an, `wig.${k}`);
|
|
7208
7219
|
}
|
|
7209
7220
|
for (const fx of an.effects || []) {
|
|
7210
7221
|
const def = EFFECT_BY_KEY2[fx.type];
|
|
7211
7222
|
if (!def) continue;
|
|
7212
|
-
for (const [k,
|
|
7223
|
+
for (const [k, f2] of Object.entries(def.fields)) if (f2.type === "num" && f2.kf) row(an, `${fx.pid}.${k}`);
|
|
7213
7224
|
}
|
|
7214
7225
|
}
|
|
7215
7226
|
return out;
|
|
@@ -7249,10 +7260,10 @@ var TXA = (function() {
|
|
|
7249
7260
|
const hash01 = (a, b2) => hashU32(a, b2) / 4294967295;
|
|
7250
7261
|
function noise1(x, seed) {
|
|
7251
7262
|
const i = Math.floor(x);
|
|
7252
|
-
const
|
|
7263
|
+
const f2 = x - i;
|
|
7253
7264
|
const a = hash01(i, seed) * 2 - 1;
|
|
7254
7265
|
const b2 = hash01(i + 1, seed) * 2 - 1;
|
|
7255
|
-
const s =
|
|
7266
|
+
const s = f2 * f2 * (3 - 2 * f2);
|
|
7256
7267
|
return a + (b2 - a) * s;
|
|
7257
7268
|
}
|
|
7258
7269
|
function mulberry32(seed) {
|
|
@@ -7282,43 +7293,43 @@ var TXA = (function() {
|
|
|
7282
7293
|
permCache.set(key, p);
|
|
7283
7294
|
return p;
|
|
7284
7295
|
}
|
|
7285
|
-
function easeShape2(
|
|
7286
|
-
if (!low && !high) return
|
|
7296
|
+
function easeShape2(f2, low, high) {
|
|
7297
|
+
if (!low && !high) return f2;
|
|
7287
7298
|
const m0 = 1 - low / 100;
|
|
7288
7299
|
const m1 = 1 - high / 100;
|
|
7289
|
-
const
|
|
7290
|
-
return
|
|
7300
|
+
const f22 = f2 * f2, f3 = f22 * f2;
|
|
7301
|
+
return clamp6((f3 - 2 * f22 + f2) * m0 + (-2 * f3 + 3 * f22) + (f3 - f22) * m1, 0, 1);
|
|
7291
7302
|
}
|
|
7292
|
-
function shapeAt2(c, S,
|
|
7303
|
+
function shapeAt2(c, S, E2, r, zeroOutside) {
|
|
7293
7304
|
if (r.shape === "square") {
|
|
7294
7305
|
const w = Math.max(1e-4, r.smooth / 100);
|
|
7295
|
-
return Math.min(
|
|
7306
|
+
return Math.min(clamp6((c - S) / w + 0.5, 0, 1), clamp6((E2 - c) / w + 0.5, 0, 1));
|
|
7296
7307
|
}
|
|
7297
|
-
const width =
|
|
7308
|
+
const width = E2 - S;
|
|
7298
7309
|
if (width <= 1e-6) return 0;
|
|
7299
7310
|
const u = (c - S) / width;
|
|
7300
7311
|
if (u < 0) return !zeroOutside && r.shape === "rampDown" ? 1 : 0;
|
|
7301
7312
|
if (u > 1) return !zeroOutside && r.shape === "rampUp" ? 1 : 0;
|
|
7302
|
-
let
|
|
7313
|
+
let f2;
|
|
7303
7314
|
switch (r.shape) {
|
|
7304
7315
|
case "rampUp":
|
|
7305
|
-
|
|
7316
|
+
f2 = u;
|
|
7306
7317
|
break;
|
|
7307
7318
|
case "rampDown":
|
|
7308
|
-
|
|
7319
|
+
f2 = 1 - u;
|
|
7309
7320
|
break;
|
|
7310
7321
|
case "triangle":
|
|
7311
|
-
|
|
7322
|
+
f2 = 1 - Math.abs(2 * u - 1);
|
|
7312
7323
|
break;
|
|
7313
7324
|
case "round": {
|
|
7314
7325
|
const q = 2 * u - 1;
|
|
7315
|
-
|
|
7326
|
+
f2 = Math.sqrt(Math.max(0, 1 - q * q));
|
|
7316
7327
|
break;
|
|
7317
7328
|
}
|
|
7318
7329
|
default:
|
|
7319
|
-
|
|
7330
|
+
f2 = 0.5 - 0.5 * Math.cos(2 * Math.PI * u);
|
|
7320
7331
|
}
|
|
7321
|
-
return easeShape2(
|
|
7332
|
+
return easeShape2(f2, r.easeLow, r.easeHigh);
|
|
7322
7333
|
}
|
|
7323
7334
|
function combine(a, b2, mode) {
|
|
7324
7335
|
switch (mode) {
|
|
@@ -7339,8 +7350,8 @@ var TXA = (function() {
|
|
|
7339
7350
|
function rangeEdges2(r, N, time) {
|
|
7340
7351
|
let s = r.start, e2 = r.end;
|
|
7341
7352
|
if (r.units === "percent") {
|
|
7342
|
-
s =
|
|
7343
|
-
e2 =
|
|
7353
|
+
s = clamp6(s, 0, 100);
|
|
7354
|
+
e2 = clamp6(e2, 0, 100);
|
|
7344
7355
|
}
|
|
7345
7356
|
if (s > e2) {
|
|
7346
7357
|
const t = s;
|
|
@@ -7363,24 +7374,24 @@ var TXA = (function() {
|
|
|
7363
7374
|
function selection2(r, w, N, time) {
|
|
7364
7375
|
const out = new Float32Array(Math.max(0, N));
|
|
7365
7376
|
if (N <= 0) return { amounts: out, S: 0, E: 0 };
|
|
7366
|
-
const { S, E } = rangeEdges2(r, N, time);
|
|
7377
|
+
const { S, E: E2 } = rangeEdges2(r, N, time);
|
|
7367
7378
|
const perm = r.randomize ? seededPerm(N, r.seed) : null;
|
|
7368
7379
|
const amt = r.amount / 100;
|
|
7369
7380
|
for (let i = 0; i < N; i += 1) {
|
|
7370
7381
|
const c = (perm ? perm[i] : i) + 0.5;
|
|
7371
|
-
let
|
|
7372
|
-
|
|
7382
|
+
let f2 = r.loop ? Math.max(shapeAt2(c - N, S, E2, r, true), shapeAt2(c, S, E2, r, true), shapeAt2(c + N, S, E2, r, true)) : shapeAt2(c, S, E2, r, false);
|
|
7383
|
+
f2 *= amt;
|
|
7373
7384
|
if (w) {
|
|
7374
7385
|
const t = time * w.rate + w.tPhase / 360;
|
|
7375
7386
|
const nu = noise1(t + i * (w.sPhase / 360), hashU32(w.seed, i + 1));
|
|
7376
7387
|
const ng = noise1(t, hashU32(w.seed, 0));
|
|
7377
7388
|
const v = lerp(nu, ng, w.corr / 100);
|
|
7378
7389
|
const wv = (w.min + (v + 1) / 2 * (w.max - w.min)) / 100;
|
|
7379
|
-
|
|
7390
|
+
f2 = combine(f2, wv, w.mode);
|
|
7380
7391
|
}
|
|
7381
|
-
out[i] =
|
|
7392
|
+
out[i] = clamp6(f2, -1, 1);
|
|
7382
7393
|
}
|
|
7383
|
-
return { amounts: out, S, E };
|
|
7394
|
+
return { amounts: out, S, E: E2 };
|
|
7384
7395
|
}
|
|
7385
7396
|
const powIn = (n2) => (p) => Math.pow(p, n2);
|
|
7386
7397
|
const outOf = (fin) => (p) => 1 - fin(1 - p);
|
|
@@ -7401,7 +7412,7 @@ var TXA = (function() {
|
|
|
7401
7412
|
const bx = (t) => 3 * p1x * t * (1 - t) * (1 - t) + 3 * p2x * t * t * (1 - t) + t * t * t;
|
|
7402
7413
|
const by = (t) => 3 * p1y * t * (1 - t) * (1 - t) + 3 * p2y * t * t * (1 - t) + t * t * t;
|
|
7403
7414
|
return (x) => {
|
|
7404
|
-
x =
|
|
7415
|
+
x = clamp6(x, 0, 1);
|
|
7405
7416
|
let lo = 0, hi = 1, t = x;
|
|
7406
7417
|
for (let i = 0; i < 24; i += 1) {
|
|
7407
7418
|
const cx = bx(t);
|
|
@@ -7461,7 +7472,15 @@ var TXA = (function() {
|
|
|
7461
7472
|
"Glide Easing Stop": bezier(0, 0.9125, 1, -0.0338)
|
|
7462
7473
|
};
|
|
7463
7474
|
})();
|
|
7464
|
-
|
|
7475
|
+
let lastBez = null;
|
|
7476
|
+
const curveFn2 = (name, handles) => {
|
|
7477
|
+
if (name === "Custom" && Array.isArray(handles) && handles.length === 4) {
|
|
7478
|
+
const key = handles.join(",");
|
|
7479
|
+
if (!lastBez || lastBez.key !== key) lastBez = { key, fn: bezier(...handles) };
|
|
7480
|
+
return lastBez.fn;
|
|
7481
|
+
}
|
|
7482
|
+
return CURVE_FNS[name] || CURVE_FNS["Ease Out"];
|
|
7483
|
+
};
|
|
7465
7484
|
function ranksOf2(order, N, seed) {
|
|
7466
7485
|
const ranks = new Float32Array(Math.max(0, N));
|
|
7467
7486
|
const c = (N - 1) / 2;
|
|
@@ -7552,9 +7571,9 @@ var TXA = (function() {
|
|
|
7552
7571
|
const amounts = new Float32Array(Math.max(0, N));
|
|
7553
7572
|
const res = { amounts, voiced: false, spans: [], inEnd: 0, outStart: Infinity };
|
|
7554
7573
|
if (N <= 0) return res;
|
|
7555
|
-
const
|
|
7574
|
+
const E2 = curveFn2(r.curve, r.bezier);
|
|
7556
7575
|
const d = Math.max(0, r.duration);
|
|
7557
|
-
const ramp = (t, t0, len) => len > 1e-6 ?
|
|
7576
|
+
const ramp = (t, t0, len) => len > 1e-6 ? clamp6((t - t0) / len, 0, 1) : t >= t0 ? 1 : 0;
|
|
7558
7577
|
const time = Number(ctx.time) || 0;
|
|
7559
7578
|
const clip = Math.max(0, Number(ctx.clipSec) || 0);
|
|
7560
7579
|
const vt = r.drive === "voice" ? voiceTurns2(r, N, ctx.units, ctx.counts, ctx.voice) : null;
|
|
@@ -7583,24 +7602,24 @@ var TXA = (function() {
|
|
|
7583
7602
|
let a;
|
|
7584
7603
|
switch (r.animate) {
|
|
7585
7604
|
case "out":
|
|
7586
|
-
a =
|
|
7605
|
+
a = E2(ramp(time, outT0(i), d));
|
|
7587
7606
|
break;
|
|
7588
7607
|
case "inOut":
|
|
7589
|
-
a = 1 -
|
|
7608
|
+
a = 1 - E2(ramp(tc, t0, d)) + E2(ramp(time, outT0(i), d));
|
|
7590
7609
|
break;
|
|
7591
7610
|
case "stay":
|
|
7592
|
-
a =
|
|
7611
|
+
a = E2(ramp(tc, t0, d));
|
|
7593
7612
|
break;
|
|
7594
7613
|
case "active": {
|
|
7595
7614
|
const isLast = vt ? i === N - 1 : ranks[i] === max;
|
|
7596
7615
|
const off = isLast && r.keepLast && !looping ? Infinity : r.hold > 0 ? t0 + r.hold : t1;
|
|
7597
|
-
a =
|
|
7598
|
-
if (tc >= off) a = Math.min(a, r.fadeOut > 0 ? 1 -
|
|
7616
|
+
a = E2(ramp(tc, t0, d));
|
|
7617
|
+
if (tc >= off) a = Math.min(a, r.fadeOut > 0 ? 1 - E2(ramp(tc, off, r.fadeOut)) : 0);
|
|
7599
7618
|
a1 = Math.max(a1, off === Infinity ? t0 + d : off + r.fadeOut);
|
|
7600
7619
|
break;
|
|
7601
7620
|
}
|
|
7602
7621
|
default:
|
|
7603
|
-
a = 1 -
|
|
7622
|
+
a = 1 - E2(ramp(tc, t0, d));
|
|
7604
7623
|
}
|
|
7605
7624
|
if (r.animate !== "active") a1 = Math.max(a1, t0 + d);
|
|
7606
7625
|
a0 = Math.min(a0, t0);
|
|
@@ -7621,15 +7640,15 @@ var TXA = (function() {
|
|
|
7621
7640
|
const out = res.amounts;
|
|
7622
7641
|
const amt = r.amount / 100;
|
|
7623
7642
|
for (let i = 0; i < N; i += 1) {
|
|
7624
|
-
let
|
|
7643
|
+
let f2 = out[i] * amt;
|
|
7625
7644
|
if (w) {
|
|
7626
7645
|
const t = time * w.rate + w.tPhase / 360;
|
|
7627
7646
|
const nu = noise1(t + i * (w.sPhase / 360), hashU32(w.seed, i + 1));
|
|
7628
7647
|
const ng = noise1(t, hashU32(w.seed, 0));
|
|
7629
7648
|
const v = lerp(nu, ng, w.corr / 100);
|
|
7630
|
-
|
|
7649
|
+
f2 = combine(f2, (w.min + (v + 1) / 2 * (w.max - w.min)) / 100, w.mode);
|
|
7631
7650
|
}
|
|
7632
|
-
out[i] =
|
|
7651
|
+
out[i] = clamp6(f2, -2, 2);
|
|
7633
7652
|
}
|
|
7634
7653
|
return { amounts: out, S: null, E: null, drive: res };
|
|
7635
7654
|
}
|
|
@@ -7693,7 +7712,7 @@ var TXA = (function() {
|
|
|
7693
7712
|
if (s === 0) return [l * 255, l * 255, l * 255];
|
|
7694
7713
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
7695
7714
|
const p = 2 * l - q;
|
|
7696
|
-
const
|
|
7715
|
+
const f2 = (t) => {
|
|
7697
7716
|
if (t < 0) t += 1;
|
|
7698
7717
|
if (t > 1) t -= 1;
|
|
7699
7718
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
@@ -7701,13 +7720,13 @@ var TXA = (function() {
|
|
|
7701
7720
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
7702
7721
|
return p;
|
|
7703
7722
|
};
|
|
7704
|
-
return [
|
|
7723
|
+
return [f2(h + 1 / 3) * 255, f2(h) * 255, f2(h - 1 / 3) * 255];
|
|
7705
7724
|
}
|
|
7706
7725
|
function shiftColor(c, dh, ds, db) {
|
|
7707
7726
|
const [h, s, l] = rgbToHsl(c);
|
|
7708
|
-
const s2 =
|
|
7727
|
+
const s2 = clamp6(s * (1 + ds), 0, 1);
|
|
7709
7728
|
const l2 = db >= 0 ? l + (1 - l) * db : l * (1 + db);
|
|
7710
|
-
return hslToRgb([h + dh, s2,
|
|
7729
|
+
return hslToRgb([h + dh, s2, clamp6(l2, 0, 1)]);
|
|
7711
7730
|
}
|
|
7712
7731
|
const rgbCss2 = (c, a = 1) => `rgba(${Math.round(c[0])},${Math.round(c[1])},${Math.round(c[2])},${a})`;
|
|
7713
7732
|
function gradientStops(colors, shiftPct, repeat) {
|
|
@@ -7715,7 +7734,7 @@ var TXA = (function() {
|
|
|
7715
7734
|
if (!shiftPct && !repeat) return rgb.map((c, i) => ({ p: rgb.length > 1 ? i / (rgb.length - 1) : 0, c }));
|
|
7716
7735
|
const list = repeat ? rgb.concat([rgb[0]]) : rgb;
|
|
7717
7736
|
const at = (q) => {
|
|
7718
|
-
const x = (repeat ? (q % 1 + 1) % 1 :
|
|
7737
|
+
const x = (repeat ? (q % 1 + 1) % 1 : clamp6(q, 0, 1)) * (list.length - 1);
|
|
7719
7738
|
const i = Math.min(Math.floor(x), list.length - 2);
|
|
7720
7739
|
return mixRgb2(list[i], list[i + 1], x - i);
|
|
7721
7740
|
};
|
|
@@ -7833,8 +7852,8 @@ var TXA = (function() {
|
|
|
7833
7852
|
function rangeAt2(an, val) {
|
|
7834
7853
|
const r = { ...an.range };
|
|
7835
7854
|
const driven = r.drive === "time" || r.drive === "voice";
|
|
7836
|
-
for (const [k,
|
|
7837
|
-
if (
|
|
7855
|
+
for (const [k, f2] of Object.entries(RANGE_FIELDS2)) {
|
|
7856
|
+
if (f2.kf && !(driven && RANGE_ONLY2[k])) r[k] = clamp6(val(an.aid, k, r[k]), f2.min, f2.max);
|
|
7838
7857
|
}
|
|
7839
7858
|
return r;
|
|
7840
7859
|
}
|
|
@@ -7857,7 +7876,7 @@ var TXA = (function() {
|
|
|
7857
7876
|
let w = null;
|
|
7858
7877
|
if (an.wiggle && an.wiggle.enabled) {
|
|
7859
7878
|
w = { ...an.wiggle };
|
|
7860
|
-
for (const [k,
|
|
7879
|
+
for (const [k, f2] of Object.entries(WIGGLE_FIELDS2)) if (f2.kf) w[k] = clamp6(val(an.aid, `wig.${k}`, w[k]), f2.min, f2.max);
|
|
7861
7880
|
}
|
|
7862
7881
|
const N = counts[r.basedOn] || 0;
|
|
7863
7882
|
const level = levelOf2(r);
|
|
@@ -7872,9 +7891,9 @@ var TXA = (function() {
|
|
|
7872
7891
|
for (const fx of effects) {
|
|
7873
7892
|
const def = EFFECT_BY_KEY2[fx.type];
|
|
7874
7893
|
const v = {};
|
|
7875
|
-
for (const [k,
|
|
7876
|
-
const stored = fx[k] === void 0 ?
|
|
7877
|
-
v[k] =
|
|
7894
|
+
for (const [k, f2] of Object.entries(def.fields)) {
|
|
7895
|
+
const stored = fx[k] === void 0 ? f2.default : fx[k];
|
|
7896
|
+
v[k] = f2.type === "num" && f2.kf ? clamp6(val(an.aid, `${fx.pid}.${k}`, stored), f2.min, f2.max) : stored;
|
|
7878
7897
|
}
|
|
7879
7898
|
if (fx.type === "box") {
|
|
7880
7899
|
const groups = v.shape === "unit" ? Int32Array.from(units, (u) => {
|
|
@@ -7900,7 +7919,7 @@ var TXA = (function() {
|
|
|
7900
7919
|
}
|
|
7901
7920
|
}
|
|
7902
7921
|
for (const st of states) {
|
|
7903
|
-
st.alpha =
|
|
7922
|
+
st.alpha = clamp6(st.alpha, 0, 1);
|
|
7904
7923
|
st.blur = Math.max(0, st.blur);
|
|
7905
7924
|
}
|
|
7906
7925
|
for (const b2 of boxes) if (b2.v.withText && b2.v.shape === "unit") b2.amt = Float32Array.from(b2.amt, (a, i) => a * (states[i] ? states[i].alpha : 1));
|
|
@@ -7912,7 +7931,7 @@ var TXA = (function() {
|
|
|
7912
7931
|
return xf[level] || (xf[level] = { tx: 0, ty: 0, s: 1, sx: 1, sy: 1, rot: 0, rx: 0, ry: 0, skew: 0, axis: 0, persp: 0 });
|
|
7913
7932
|
}
|
|
7914
7933
|
function applyEffect(st, type, v, k, i, time, g, fillRgb, level = "char") {
|
|
7915
|
-
const kp =
|
|
7934
|
+
const kp = clamp6(k, 0, 1);
|
|
7916
7935
|
switch (type) {
|
|
7917
7936
|
case "position": {
|
|
7918
7937
|
const e2 = xfOf(st, level);
|
|
@@ -8058,7 +8077,7 @@ var TXA = (function() {
|
|
|
8058
8077
|
const sy = e2.s * e2.sy;
|
|
8059
8078
|
let k00 = 1, k01 = 0, k10 = 0, k11 = 1;
|
|
8060
8079
|
if (e2.skew) {
|
|
8061
|
-
const t = Math.tan(
|
|
8080
|
+
const t = Math.tan(clamp6(e2.skew, -85, 85) * Math.PI / 180);
|
|
8062
8081
|
const ca = Math.cos(e2.axis * Math.PI / 180);
|
|
8063
8082
|
const sa = Math.sin(e2.axis * Math.PI / 180);
|
|
8064
8083
|
k00 = 1 + t * ca * sa;
|
|
@@ -8117,6 +8136,77 @@ var TXA = (function() {
|
|
|
8117
8136
|
}
|
|
8118
8137
|
return xf || st.xf;
|
|
8119
8138
|
}
|
|
8139
|
+
function m4Mul2(A, B) {
|
|
8140
|
+
const C = new Array(16);
|
|
8141
|
+
for (let r = 0; r < 4; r += 1) {
|
|
8142
|
+
for (let c = 0; c < 4; c += 1) {
|
|
8143
|
+
C[r * 4 + c] = A[r * 4] * B[c] + A[r * 4 + 1] * B[4 + c] + A[r * 4 + 2] * B[8 + c] + A[r * 4 + 3] * B[12 + c];
|
|
8144
|
+
}
|
|
8145
|
+
}
|
|
8146
|
+
return C;
|
|
8147
|
+
}
|
|
8148
|
+
function levelM4(e2, box2, pivot) {
|
|
8149
|
+
const cx = (box2.x0 + box2.x1) / 2;
|
|
8150
|
+
const cy = (box2.y0 + box2.y1) / 2;
|
|
8151
|
+
const px = cx + pivot.x * (box2.x1 - box2.x0) / 2;
|
|
8152
|
+
const py = cy + pivot.y * (box2.y1 - box2.y0) / 2;
|
|
8153
|
+
const sx = e2.s * e2.sx;
|
|
8154
|
+
const sy = e2.s * e2.sy;
|
|
8155
|
+
let k00 = 1, k01 = 0, k10 = 0, k11 = 1;
|
|
8156
|
+
if (e2.skew) {
|
|
8157
|
+
const t = Math.tan(clamp6(e2.skew, -85, 85) * Math.PI / 180);
|
|
8158
|
+
const ca = Math.cos(e2.axis * Math.PI / 180);
|
|
8159
|
+
const sa = Math.sin(e2.axis * Math.PI / 180);
|
|
8160
|
+
k00 = 1 + t * ca * sa;
|
|
8161
|
+
k01 = -t * ca * ca;
|
|
8162
|
+
k10 = t * sa * sa;
|
|
8163
|
+
k11 = 1 - t * sa * ca;
|
|
8164
|
+
}
|
|
8165
|
+
const a00 = k00 * sx, a01 = k01 * sy, a10 = k10 * sx, a11 = k11 * sy;
|
|
8166
|
+
const az = e2.s;
|
|
8167
|
+
const th = e2.rx * Math.PI / 180;
|
|
8168
|
+
const ph = e2.ry * Math.PI / 180;
|
|
8169
|
+
const ct = Math.cos(th), st = Math.sin(th), cp = Math.cos(ph), sp = Math.sin(ph);
|
|
8170
|
+
const R = [cp, sp * st, -sp * ct, 0, ct, st, sp, -cp * st, cp * ct];
|
|
8171
|
+
const rad = e2.rot * Math.PI / 180;
|
|
8172
|
+
const cr = Math.cos(rad), sr = Math.sin(rad);
|
|
8173
|
+
const RA = [
|
|
8174
|
+
R[0] * a00 + R[1] * a10,
|
|
8175
|
+
R[0] * a01 + R[1] * a11,
|
|
8176
|
+
R[2] * az,
|
|
8177
|
+
R[3] * a00 + R[4] * a10,
|
|
8178
|
+
R[3] * a01 + R[4] * a11,
|
|
8179
|
+
R[5] * az,
|
|
8180
|
+
R[6] * a00 + R[7] * a10,
|
|
8181
|
+
R[6] * a01 + R[7] * a11,
|
|
8182
|
+
R[8] * az
|
|
8183
|
+
];
|
|
8184
|
+
const L = [
|
|
8185
|
+
cr * RA[0] - sr * RA[3],
|
|
8186
|
+
cr * RA[1] - sr * RA[4],
|
|
8187
|
+
cr * RA[2] - sr * RA[5],
|
|
8188
|
+
sr * RA[0] + cr * RA[3],
|
|
8189
|
+
sr * RA[1] + cr * RA[4],
|
|
8190
|
+
sr * RA[2] + cr * RA[5],
|
|
8191
|
+
RA[6],
|
|
8192
|
+
RA[7],
|
|
8193
|
+
RA[8]
|
|
8194
|
+
];
|
|
8195
|
+
const tx = px + e2.tx - (L[0] * px + L[1] * py);
|
|
8196
|
+
const ty = py + e2.ty - (L[3] * px + L[4] * py);
|
|
8197
|
+
const tz = -(L[6] * px + L[7] * py);
|
|
8198
|
+
return [L[0], L[1], L[2], tx, L[3], L[4], L[5], ty, L[6], L[7], L[8], tz, 0, 0, 0, 1];
|
|
8199
|
+
}
|
|
8200
|
+
function unitTransform3d2(xf, boxOf, pivot) {
|
|
8201
|
+
let M = null;
|
|
8202
|
+
for (const lv of LEVELS2) {
|
|
8203
|
+
const e2 = xf && xf[lv];
|
|
8204
|
+
if (!e2) continue;
|
|
8205
|
+
const r = levelM4(e2, boxOf(lv), pivot);
|
|
8206
|
+
M = M ? m4Mul2(r, M) : r;
|
|
8207
|
+
}
|
|
8208
|
+
return M;
|
|
8209
|
+
}
|
|
8120
8210
|
function unitTransform2(xf, boxOf, pivot) {
|
|
8121
8211
|
let H = null;
|
|
8122
8212
|
let projective = false;
|
|
@@ -8256,12 +8346,12 @@ var TXA = (function() {
|
|
|
8256
8346
|
const gv = Math.max(Math.abs(w01 - w00), Math.abs(w11 - w10)) / wMin / Lv;
|
|
8257
8347
|
const K = 2 * (gu + gv) + R * (gu * gu + gv * gv);
|
|
8258
8348
|
const cell = K > 1e-12 ? Math.sqrt(4 * WARP_TOL / K) : Infinity;
|
|
8259
|
-
let nu =
|
|
8260
|
-
let nv =
|
|
8349
|
+
let nu = clamp6(Math.ceil(Lu / cell), 1, 64);
|
|
8350
|
+
let nv = clamp6(Math.ceil(Lv / cell), 1, 64);
|
|
8261
8351
|
if (nu * nv > WARP_CELLS) {
|
|
8262
|
-
const
|
|
8263
|
-
nu = Math.max(1, Math.round(nu *
|
|
8264
|
-
nv = Math.max(1, Math.round(nv *
|
|
8352
|
+
const f2 = Math.sqrt(WARP_CELLS / (nu * nv));
|
|
8353
|
+
nu = Math.max(1, Math.round(nu * f2));
|
|
8354
|
+
nv = Math.max(1, Math.round(nv * f2));
|
|
8265
8355
|
}
|
|
8266
8356
|
const src = [];
|
|
8267
8357
|
const dst = [];
|
|
@@ -8329,7 +8419,7 @@ var TXA = (function() {
|
|
|
8329
8419
|
}
|
|
8330
8420
|
function revealedOf2(amt) {
|
|
8331
8421
|
const out = new Float32Array(amt.length);
|
|
8332
|
-
for (let i = 0; i < amt.length; i += 1) out[i] = Number.isNaN(amt[i]) ? NaN : 1 -
|
|
8422
|
+
for (let i = 0; i < amt.length; i += 1) out[i] = Number.isNaN(amt[i]) ? NaN : 1 - clamp6(amt[i], 0, 1);
|
|
8333
8423
|
return out;
|
|
8334
8424
|
}
|
|
8335
8425
|
const BOX_MIN = 1e-4;
|
|
@@ -8356,10 +8446,10 @@ var TXA = (function() {
|
|
|
8356
8446
|
peak2 = Math.max(peak2, amt[j]);
|
|
8357
8447
|
}
|
|
8358
8448
|
}
|
|
8359
|
-
const
|
|
8360
|
-
const x02 =
|
|
8449
|
+
const f2 = cells[k], l = cells[last];
|
|
8450
|
+
const x02 = f2.x - v.padX, x12 = l.x + l.ink + v.padX, y02 = f2.top - v.padY, y12 = f2.bottom + v.padY;
|
|
8361
8451
|
if (x12 - x02 > 0.5 && y12 - y02 > 0.5) {
|
|
8362
|
-
out.push({ x0: x02, y0: y02, x1: x12, y1: y12, r: Math.max(0, Math.min(v.radius, (y12 - y02) / 2, (x12 - x02) / 2)), alpha: opacity2 *
|
|
8452
|
+
out.push({ x0: x02, y0: y02, x1: x12, y1: y12, r: Math.max(0, Math.min(v.radius, (y12 - y02) / 2, (x12 - x02) / 2)), alpha: opacity2 * clamp6(peak2, 0, 1) });
|
|
8363
8453
|
}
|
|
8364
8454
|
k = j + 1;
|
|
8365
8455
|
}
|
|
@@ -8381,10 +8471,10 @@ var TXA = (function() {
|
|
|
8381
8471
|
j += 1;
|
|
8382
8472
|
}
|
|
8383
8473
|
const last = j - 1;
|
|
8384
|
-
const
|
|
8474
|
+
const f2 = cells[i];
|
|
8385
8475
|
const l = cells[last];
|
|
8386
|
-
const share = (k) => v.grow ?
|
|
8387
|
-
let x02 =
|
|
8476
|
+
const share = (k) => v.grow ? clamp6(amt[k] / peak2, 0, 1) : 1;
|
|
8477
|
+
let x02 = f2.x;
|
|
8388
8478
|
let x12 = l.x + l.ink;
|
|
8389
8479
|
if (v.grow && i < last) {
|
|
8390
8480
|
const sl = share(last);
|
|
@@ -8395,7 +8485,7 @@ var TXA = (function() {
|
|
|
8395
8485
|
for (let k = i; k < last && share(k) < 1; k += 1) x02 += (1 - share(k)) * adv(k);
|
|
8396
8486
|
if (x12 < x02) x12 = x02;
|
|
8397
8487
|
}
|
|
8398
|
-
runs.push({ row:
|
|
8488
|
+
runs.push({ row: f2.row, x0: x02, x1: x12, top: f2.top, bottom: f2.bottom, peak: peak2, covered: x12 - x02 });
|
|
8399
8489
|
i = j;
|
|
8400
8490
|
}
|
|
8401
8491
|
if (!runs.length) return [];
|
|
@@ -8406,7 +8496,7 @@ var TXA = (function() {
|
|
|
8406
8496
|
y02 -= v.padY;
|
|
8407
8497
|
y12 += v.padY;
|
|
8408
8498
|
if (!(x12 - x02 > 0.5 && y12 - y02 > 0.5) || !(a > 0)) return null;
|
|
8409
|
-
return { x0: x02, y0: y02, x1: x12, y1: y12, r: Math.max(0, Math.min(v.radius, (y12 - y02) / 2, (x12 - x02) / 2)), alpha: opacity *
|
|
8499
|
+
return { x0: x02, y0: y02, x1: x12, y1: y12, r: Math.max(0, Math.min(v.radius, (y12 - y02) / 2, (x12 - x02) / 2)), alpha: opacity * clamp6(a, 0, 1) };
|
|
8410
8500
|
};
|
|
8411
8501
|
if (v.shape !== "block") return runs.map((q) => rect(q.x0, q.top, q.x1, q.bottom, q.peak)).filter(Boolean);
|
|
8412
8502
|
const rows = /* @__PURE__ */ new Map();
|
|
@@ -8436,7 +8526,7 @@ var TXA = (function() {
|
|
|
8436
8526
|
for (const r of list) {
|
|
8437
8527
|
const sp = span.get(r.row);
|
|
8438
8528
|
const width = sp && sp.b > sp.a ? sp.b - sp.a : 0;
|
|
8439
|
-
const grown = v.grow ?
|
|
8529
|
+
const grown = v.grow ? clamp6(r.covered / Math.max(1e-6, Math.min(width, r.bottom - r.top)), 0, 1) : 1;
|
|
8440
8530
|
const s = grown * grown * (3 - 2 * grown);
|
|
8441
8531
|
r.h = s * (peak > 0 ? r.peak / peak : 0);
|
|
8442
8532
|
}
|
|
@@ -8462,10 +8552,10 @@ var TXA = (function() {
|
|
|
8462
8552
|
const { v, amt } = c;
|
|
8463
8553
|
const n2 = Math.min(amt.length, cells.length);
|
|
8464
8554
|
if (!n2 || time >= c.outStart) return null;
|
|
8465
|
-
let
|
|
8466
|
-
for (let k = 0; k < n2; k += 1)
|
|
8467
|
-
const done =
|
|
8468
|
-
const typing =
|
|
8555
|
+
let f2 = 0;
|
|
8556
|
+
for (let k = 0; k < n2; k += 1) f2 += Number.isNaN(amt[k]) ? 0 : clamp6(amt[k], 0, 1);
|
|
8557
|
+
const done = f2 >= n2 - 1e-3;
|
|
8558
|
+
const typing = f2 > 1e-3 && !done;
|
|
8469
8559
|
const end = Number.isFinite(c.inEnd) ? c.inEnd : null;
|
|
8470
8560
|
if (v.hide && done && (end === null || time >= end + v.hideAfter)) return null;
|
|
8471
8561
|
if (!typing && v.blink > 0) {
|
|
@@ -8479,10 +8569,10 @@ var TXA = (function() {
|
|
|
8479
8569
|
cell = cells[k];
|
|
8480
8570
|
x = cell.x + cell.ink;
|
|
8481
8571
|
} else {
|
|
8482
|
-
const k = Math.min(n2 - 1, Math.floor(
|
|
8572
|
+
const k = Math.min(n2 - 1, Math.floor(f2));
|
|
8483
8573
|
cell = cells[k];
|
|
8484
8574
|
const adv = k + 1 < n2 && cells[k + 1].row === cell.row ? Math.max(0, cells[k + 1].x - cell.x) : cell.w;
|
|
8485
|
-
x = cell.x + (
|
|
8575
|
+
x = cell.x + (f2 - k) * adv;
|
|
8486
8576
|
}
|
|
8487
8577
|
const fs = Math.max(1, cell.bottom - cell.top);
|
|
8488
8578
|
const y = cell.top;
|
|
@@ -8558,6 +8648,8 @@ var TXA = (function() {
|
|
|
8558
8648
|
groupBoxes: groupBoxes2,
|
|
8559
8649
|
maskedXf: maskedXf2,
|
|
8560
8650
|
unitTransform: unitTransform2,
|
|
8651
|
+
unitTransform3d: unitTransform3d2,
|
|
8652
|
+
m4Mul: m4Mul2,
|
|
8561
8653
|
applyH: applyH2,
|
|
8562
8654
|
hToCanvas: hToCanvas2,
|
|
8563
8655
|
warpBegin: warpBegin2,
|
|
@@ -8629,6 +8721,8 @@ var {
|
|
|
8629
8721
|
groupBoxes,
|
|
8630
8722
|
maskedXf,
|
|
8631
8723
|
unitTransform,
|
|
8724
|
+
unitTransform3d,
|
|
8725
|
+
m4Mul,
|
|
8632
8726
|
applyH,
|
|
8633
8727
|
hToCanvas,
|
|
8634
8728
|
warpBegin,
|
|
@@ -8753,7 +8847,7 @@ var factory = function(T) {
|
|
|
8753
8847
|
}
|
|
8754
8848
|
]
|
|
8755
8849
|
});
|
|
8756
|
-
const
|
|
8850
|
+
const PRESETS3 = [
|
|
8757
8851
|
// ── Sweep Reveal ──
|
|
8758
8852
|
GLIDE_TEXT,
|
|
8759
8853
|
glideThrough(
|
|
@@ -8762,6 +8856,63 @@ var factory = function(T) {
|
|
|
8762
8856
|
"Smooth Glide",
|
|
8763
8857
|
"Letters type in under a chasing blue band as the word keeps gliding through the whole layer, slow in the middle, then slides off to the left"
|
|
8764
8858
|
),
|
|
8859
|
+
// Smart Overshoot = the owner's "Glow Select — text preset" project (board Vibe Motion,
|
|
8860
|
+
// 2026-10-27), third in the list on request. Built on a 54 px layer, so its lengths are
|
|
8861
|
+
// px / 54 em, to 4 places (exact on 54 px, and the skill's × size / 100 rule agrees on
|
|
8862
|
+
// any size). Words rise out of a blur and overshoot into place, glowing hot green and
|
|
8863
|
+
// cooling to white behind the reveal, while the whole line slides back to centre; a soft
|
|
8864
|
+
// glow stays and one key word stays lit green. The project picked by index (word 3); here
|
|
8865
|
+
// the Range is in %, so other text gets the same share: words 60–85 % is exactly that
|
|
8866
|
+
// word on the 5-word sample and still finds a word near the end of a 2- to 8-word line.
|
|
8867
|
+
// The selection box that grew over the end is gone (owner, 2026-10-29): with no keyframe
|
|
8868
|
+
// left, the plate stops repainting once the last word lands.
|
|
8869
|
+
{
|
|
8870
|
+
id: "smart-overshoot",
|
|
8871
|
+
name: "Smart Overshoot",
|
|
8872
|
+
cat: "sweep",
|
|
8873
|
+
sides: "in",
|
|
8874
|
+
anchor: "start",
|
|
8875
|
+
sample: "Just the best stablecoin strategies",
|
|
8876
|
+
tile: "Smart overshoot",
|
|
8877
|
+
labLayer: { size: 54, weight: 400, stage: "#0c0e0d" },
|
|
8878
|
+
description: "Words spring in out of a hot green glow and overshoot into place as the line recentres, and a key word stays lit",
|
|
8879
|
+
animators: [
|
|
8880
|
+
{
|
|
8881
|
+
name: "Recenter",
|
|
8882
|
+
range: kIn("words", "all", { stagger: 0, duration: 1.8, curve: "Power3 Out" }),
|
|
8883
|
+
effects: [["position", { x: em(4.6296), y: 0 }]]
|
|
8884
|
+
},
|
|
8885
|
+
{
|
|
8886
|
+
name: "Spring in",
|
|
8887
|
+
range: kIn("words", "word", { stagger: 0.15, duration: 1.1, curve: "Overshoot" }),
|
|
8888
|
+
effects: [["position", { x: em(1.2963), y: em(1.5741) }], ["scale", { scale: 78 }]]
|
|
8889
|
+
},
|
|
8890
|
+
{
|
|
8891
|
+
name: "Focus in",
|
|
8892
|
+
range: kIn("words", "auto", { stagger: 0.15, duration: 0.38, curve: "Power2 Out" }),
|
|
8893
|
+
effects: [["opacity", { opacity: 0 }], ["blur", { amount: em(0.2593) }]]
|
|
8894
|
+
},
|
|
8895
|
+
{
|
|
8896
|
+
name: "Hot glow",
|
|
8897
|
+
range: kIn("words", "auto", { stagger: 0.15, duration: 0.65, curve: "Sine Smooth" }),
|
|
8898
|
+
effects: [["fill", { color: "#6dff86" }], ["glow", { color: "#3dff6a", size: em(0.6296), strength: 230 }]]
|
|
8899
|
+
},
|
|
8900
|
+
{
|
|
8901
|
+
name: "Soft glow",
|
|
8902
|
+
range: { basedOn: "chars" },
|
|
8903
|
+
effects: [["glow", { color: "#b8ffc6", size: em(0.2222), strength: 30 }]]
|
|
8904
|
+
},
|
|
8905
|
+
{
|
|
8906
|
+
name: "Key word",
|
|
8907
|
+
range: { basedOn: "words", start: 60, end: 85, smooth: 0 },
|
|
8908
|
+
effects: [
|
|
8909
|
+
["fill", { color: "#6dff86" }],
|
|
8910
|
+
["stroke", { color: "#6dff86", width: em(0.0241) }],
|
|
8911
|
+
["glow", { color: "#3dff6a", size: em(0.2963), strength: 60 }]
|
|
8912
|
+
]
|
|
8913
|
+
}
|
|
8914
|
+
]
|
|
8915
|
+
},
|
|
8765
8916
|
glideThrough(
|
|
8766
8917
|
"glide-soft-stop",
|
|
8767
8918
|
"Glide Soft Stop",
|
|
@@ -10061,7 +10212,7 @@ var factory = function(T) {
|
|
|
10061
10212
|
}]
|
|
10062
10213
|
}
|
|
10063
10214
|
];
|
|
10064
|
-
for (const p of
|
|
10215
|
+
for (const p of PRESETS3) for (const a of p.animators) a.range = { basedOn: "charsNoSpace", ...a.range };
|
|
10065
10216
|
const anchorOf2 = (p, a) => a && a.anchor || p.anchor;
|
|
10066
10217
|
function presetSpan2(p) {
|
|
10067
10218
|
if (p.anchor !== "start" && p.anchor !== "end") return 0;
|
|
@@ -10169,7 +10320,7 @@ var factory = function(T) {
|
|
|
10169
10320
|
}]
|
|
10170
10321
|
};
|
|
10171
10322
|
}
|
|
10172
|
-
const api = { CATEGORIES: CATEGORIES2, PRESETS:
|
|
10323
|
+
const api = { CATEGORIES: CATEGORIES2, PRESETS: PRESETS3, anchorOf: anchorOf2, presetSpan: presetSpan2, isTimed: isTimed2, resolveEm: resolveEm2, build: build2, fakeVoice: fakeVoice2, usesVoice: usesVoice2, hasExit: hasExit2, withExit: withExit2 };
|
|
10173
10324
|
return api;
|
|
10174
10325
|
};
|
|
10175
10326
|
var PRESETS_API = factory(textAnimators_default);
|
|
@@ -12439,6 +12590,40 @@ init_define_import_meta_env();
|
|
|
12439
12590
|
|
|
12440
12591
|
// src/components/videoEditor/types.js
|
|
12441
12592
|
init_define_import_meta_env();
|
|
12593
|
+
var CONTAINER_MEDIA_TYPES = /* @__PURE__ */ new Set(["sequence", "grid"]);
|
|
12594
|
+
var isContainerClip = (clip) => !!clip && CONTAINER_MEDIA_TYPES.has(clip.mediaType) && !!clip.sequence;
|
|
12595
|
+
var TransitionType = {
|
|
12596
|
+
NONE: "None",
|
|
12597
|
+
FADE: "Cross Fade",
|
|
12598
|
+
// One type per move, the way is `transitionOptions.direction` (see
|
|
12599
|
+
// DIRECTIONAL_TRANSITIONS). Push absorbed Slide Left/Right and Push Up/Down —
|
|
12600
|
+
// the same move (both clips travel together) on two axes.
|
|
12601
|
+
PUSH: "Push",
|
|
12602
|
+
PARALLAX: "Parallax",
|
|
12603
|
+
WIPE: "Wipe",
|
|
12604
|
+
ZOOM_IN: "Zoom In",
|
|
12605
|
+
ZOOM_OUT: "Zoom Out",
|
|
12606
|
+
// 'Zoom Blur' was retired in 9.0.6 — 'Advanced Zoom' does the same thing with a
|
|
12607
|
+
// pivot you can choose, so it was removed rather than kept as a duplicate.
|
|
12608
|
+
// Renamed from 'Zoom to Point'/'Move to Point' when they grew the fisheye and
|
|
12609
|
+
// wiggle groups: they are no longer only about aiming at a point. Every project
|
|
12610
|
+
// saved before that still holds the old string — see TRANSITION_TYPE_ALIASES.
|
|
12611
|
+
ZOOM_POINT: "Advanced Zoom",
|
|
12612
|
+
MOVE_POINT: "Advanced Movement",
|
|
12613
|
+
CAROUSEL_3D: "3D Carousel",
|
|
12614
|
+
LOUVER_FLIP: "Louver Flip",
|
|
12615
|
+
CIRCULAR_WIPE: "Circular Wipe",
|
|
12616
|
+
PIXEL_BLOCKS: "Pixel Blocks",
|
|
12617
|
+
CAMERA_WHIP: "Camera Whip",
|
|
12618
|
+
ZOOM_RULER: "Zoom Ruler",
|
|
12619
|
+
GLOW_STACK: "Gradient Glow Stack",
|
|
12620
|
+
GRADIENT_MAP: "Gradient Map",
|
|
12621
|
+
SCALE_SWAP: "Scale Swap",
|
|
12622
|
+
SIDE_BY_SIDE: "Side by Side",
|
|
12623
|
+
DIP_TO_BLACK: "Dip to Black",
|
|
12624
|
+
RADIAL_WIPE: "Radial Wipe",
|
|
12625
|
+
CUSTOM: "Custom"
|
|
12626
|
+
};
|
|
12442
12627
|
var TRANSITION_TYPE_ALIASES = Object.freeze({
|
|
12443
12628
|
"Zoom to Point": "Advanced Zoom",
|
|
12444
12629
|
"Move to Point": "Advanced Movement",
|
|
@@ -12611,7 +12796,11 @@ var TRANSITION_EASING_OPTIONS = Object.values(TransitionEasing).filter((e2) => !
|
|
|
12611
12796
|
|
|
12612
12797
|
// src/components/videoEditor/gsapTransitions.js
|
|
12613
12798
|
init_define_import_meta_env();
|
|
12614
|
-
|
|
12799
|
+
|
|
12800
|
+
// stand-in:three
|
|
12801
|
+
init_define_import_meta_env();
|
|
12802
|
+
function f() {
|
|
12803
|
+
}
|
|
12615
12804
|
|
|
12616
12805
|
// src/components/videoEditor/keyframeProps.js
|
|
12617
12806
|
init_define_import_meta_env();
|
|
@@ -12880,8 +13069,8 @@ var KF_PROPS = [
|
|
|
12880
13069
|
{ label: "motionPathOffsetZ", displayLabel: "Path Offset Z", group: "Transform", placeholder: "0", unit: "px", type: "number", min: -1e4, max: 1e4, step: 5 },
|
|
12881
13070
|
{ label: "letterSpacing", group: "Transform", placeholder: "0", unit: "px", type: "number", min: -80, max: 200, step: 1 },
|
|
12882
13071
|
// Text on Path (text layers with textPathEnabled). Progress reuses motionPathT
|
|
12883
|
-
// above; these animate the glyph distribution itself — keyed trims
|
|
12884
|
-
//
|
|
13072
|
+
// above; these animate the glyph distribution itself — keyed trims move the
|
|
13073
|
+
// window the run is laid out in (letters are not hidden past it). No-ops elsewhere, like letterSpacing.
|
|
12885
13074
|
{ label: "textPathSpread", displayLabel: "Text Path Spread", group: "Transform", placeholder: "100", unit: "%", type: "number", min: 10, max: 400, step: 1 },
|
|
12886
13075
|
{ label: "textPathSpacing", displayLabel: "Text Path Spacing", group: "Transform", placeholder: "0", unit: "px", type: "number", min: -200, max: 500, step: 1 },
|
|
12887
13076
|
{ label: "textPathTrimStart", displayLabel: "Text Path Trim Start", group: "Transform", placeholder: "0", unit: "%", type: "number", min: 0, max: 100, step: 0.5 },
|
|
@@ -12999,6 +13188,9 @@ var KF_PROPS = [
|
|
|
12999
13188
|
{ label: "frWindow", displayLabel: "Rim Depth Window", group: "Effects", placeholder: "14", unit: "%", type: "number", min: 1, max: 60, step: 0.5 },
|
|
13000
13189
|
{ label: "frBody", displayLabel: "Rim Body Light", group: "Effects", placeholder: "100", unit: "%", type: "number", min: 0, max: 100, step: 1 },
|
|
13001
13190
|
{ label: "frAmount", displayLabel: "Rim Amount", group: "Effects", placeholder: "100", unit: "%", type: "number", min: 0, max: 100, step: 1 },
|
|
13191
|
+
// Posterize Time (per-layer clock — posterizeTime.js). The rate is integrated,
|
|
13192
|
+
// so a keyed ramp speeds the steps up smoothly.
|
|
13193
|
+
{ label: "ptFps", displayLabel: "Posterize Frame Rate", group: "Effects", placeholder: "12", unit: "fps", type: "number", min: 1, max: 60, step: 1 },
|
|
13002
13194
|
// Motion Blur (per-layer, transform-velocity driven — motionBlurLayer.js).
|
|
13003
13195
|
{ label: "mblurShutter", group: "Effects", placeholder: "180", unit: "deg", type: "number", min: 0, max: 720, step: 5 },
|
|
13004
13196
|
{ label: "mblurAmount", group: "Effects", placeholder: "100", unit: "%", type: "number", min: 0, max: 100, step: 1 },
|
|
@@ -13256,6 +13448,15 @@ var KF_PROPS = [
|
|
|
13256
13448
|
{ label: "bwL2Y", displayLabel: "Bezier Left 2 Y", group: "Effects", placeholder: "33", unit: "%", type: "number", min: -100, max: 200, step: 0.5 }
|
|
13257
13449
|
];
|
|
13258
13450
|
var KF_PROP_LABELS = KF_PROPS.map((p) => p.label);
|
|
13451
|
+
var KF_COLOR_PROPS = /* @__PURE__ */ new Set(["color", "backgroundColor", "textColor", "textBgColor", "shapeFill", "textOutlineColor", "borderColor"]);
|
|
13452
|
+
var MOTION_KF_COLOR_PREFIX = "mpc:";
|
|
13453
|
+
var MASK_PATH_KF_RE = /^msk:[A-Za-z0-9_-]{1,24}:path$/;
|
|
13454
|
+
function isMaskPathTrackKey(prop) {
|
|
13455
|
+
return typeof prop === "string" && MASK_PATH_KF_RE.test(prop);
|
|
13456
|
+
}
|
|
13457
|
+
function isColorTrackKey(prop) {
|
|
13458
|
+
return KF_COLOR_PROPS.has(prop) || STROKE_COLOR_KF_LABELS.has(prop) || typeof prop === "string" && prop.startsWith(MOTION_KF_COLOR_PREFIX);
|
|
13459
|
+
}
|
|
13259
13460
|
|
|
13260
13461
|
// src/utils/cssGradient.js
|
|
13261
13462
|
init_define_import_meta_env();
|
|
@@ -13356,10 +13557,138 @@ function buildGradient(type = "linear", angle = 90, stops = []) {
|
|
|
13356
13557
|
|
|
13357
13558
|
// src/components/videoEditor/shapePath.js
|
|
13358
13559
|
init_define_import_meta_env();
|
|
13560
|
+
|
|
13561
|
+
// src/components/videoEditor/objectMotionPath.js
|
|
13562
|
+
init_define_import_meta_env();
|
|
13563
|
+
var OBJECT_MOTION_PATH_AIMS = ["tangent", "points", "free"];
|
|
13564
|
+
var num = (value, fallback = 0) => {
|
|
13565
|
+
const parsed = Number.parseFloat(value);
|
|
13566
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
13567
|
+
};
|
|
13568
|
+
var vec3 = (value) => {
|
|
13569
|
+
if (!value || typeof value !== "object") return null;
|
|
13570
|
+
if (![value.x, value.y, value.z].every((v) => Number.isFinite(Number(v)))) return null;
|
|
13571
|
+
return { x: num(value.x), y: num(value.y), z: num(value.z) };
|
|
13572
|
+
};
|
|
13573
|
+
function sanitizeObjectMotionPath(path) {
|
|
13574
|
+
if (!path || typeof path !== "object") return null;
|
|
13575
|
+
const points = [];
|
|
13576
|
+
for (const raw of Array.isArray(path.points) ? path.points : []) {
|
|
13577
|
+
const p = vec3(raw);
|
|
13578
|
+
if (!p) continue;
|
|
13579
|
+
const point = p;
|
|
13580
|
+
const hIn = vec3(raw.hIn);
|
|
13581
|
+
const hOut = vec3(raw.hOut);
|
|
13582
|
+
if (hIn) point.hIn = hIn;
|
|
13583
|
+
if (hOut) point.hOut = hOut;
|
|
13584
|
+
if (raw.manual === true) point.manual = true;
|
|
13585
|
+
const rx = num(raw.rx);
|
|
13586
|
+
const ry = num(raw.ry);
|
|
13587
|
+
const rz = num(raw.rz);
|
|
13588
|
+
if (rx || ry || rz) {
|
|
13589
|
+
point.rx = rx;
|
|
13590
|
+
point.ry = ry;
|
|
13591
|
+
point.rz = rz;
|
|
13592
|
+
}
|
|
13593
|
+
points.push(point);
|
|
13594
|
+
}
|
|
13595
|
+
if (!points.length) return null;
|
|
13596
|
+
return {
|
|
13597
|
+
points,
|
|
13598
|
+
closed: path.closed === true,
|
|
13599
|
+
follow: path.follow === true,
|
|
13600
|
+
aim: OBJECT_MOTION_PATH_AIMS.includes(path.aim) ? path.aim : "tangent",
|
|
13601
|
+
keyframesPerPoint: path.keyframesPerPoint === true,
|
|
13602
|
+
...path.keyframesPerPoint === true ? {
|
|
13603
|
+
keyframesPerPointCount: Math.max(0, Math.round(num(path.keyframesPerPointCount))),
|
|
13604
|
+
keyframesPerPointClosed: path.keyframesPerPointClosed === true
|
|
13605
|
+
} : {}
|
|
13606
|
+
};
|
|
13607
|
+
}
|
|
13608
|
+
var DEG2RAD = Math.PI / 180;
|
|
13609
|
+
var MOTION_PATH_KF_KEY = "motionPathPoints";
|
|
13610
|
+
var isMotionPathTrackKey = (prop) => prop === MOTION_PATH_KF_KEY;
|
|
13611
|
+
var decodedMotionPaths = /* @__PURE__ */ new Map();
|
|
13612
|
+
var DECODED_MOTION_PATH_LIMIT = 4096;
|
|
13613
|
+
function decodeMotionPathValue(value) {
|
|
13614
|
+
if (Array.isArray(value)) return sanitizeObjectMotionPath({ points: value })?.points || null;
|
|
13615
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
13616
|
+
const cached = decodedMotionPaths.get(value);
|
|
13617
|
+
if (cached !== void 0) return cached;
|
|
13618
|
+
let points = null;
|
|
13619
|
+
try {
|
|
13620
|
+
points = sanitizeObjectMotionPath({ points: JSON.parse(value) })?.points || null;
|
|
13621
|
+
} catch {
|
|
13622
|
+
points = null;
|
|
13623
|
+
}
|
|
13624
|
+
if (decodedMotionPaths.size >= DECODED_MOTION_PATH_LIMIT) decodedMotionPaths.clear();
|
|
13625
|
+
decodedMotionPaths.set(value, points);
|
|
13626
|
+
return points;
|
|
13627
|
+
}
|
|
13628
|
+
function interpolateMotionPaths(a, b2, t) {
|
|
13629
|
+
if (!Array.isArray(a) || !a.length) return Array.isArray(b2) ? b2 : [];
|
|
13630
|
+
if (!Array.isArray(b2) || !b2.length) return a;
|
|
13631
|
+
if (!Number.isFinite(t) || t <= 0) return a;
|
|
13632
|
+
if (t >= 1) return b2;
|
|
13633
|
+
const lerp = (p, q) => p + (q - p) * t;
|
|
13634
|
+
const lerpVec = (p, q) => ({
|
|
13635
|
+
x: lerp(num(p?.x), num(q?.x)),
|
|
13636
|
+
y: lerp(num(p?.y), num(q?.y)),
|
|
13637
|
+
z: lerp(num(p?.z), num(q?.z))
|
|
13638
|
+
});
|
|
13639
|
+
const n2 = Math.min(a.length, b2.length);
|
|
13640
|
+
const out = new Array(n2);
|
|
13641
|
+
for (let i = 0; i < n2; i += 1) {
|
|
13642
|
+
const p = a[i];
|
|
13643
|
+
const q = b2[i];
|
|
13644
|
+
const point = {
|
|
13645
|
+
x: lerp(p.x, q.x),
|
|
13646
|
+
y: lerp(p.y, q.y),
|
|
13647
|
+
z: lerp(p.z, q.z),
|
|
13648
|
+
hIn: lerpVec(p.hIn, q.hIn),
|
|
13649
|
+
hOut: lerpVec(p.hOut, q.hOut),
|
|
13650
|
+
manual: true
|
|
13651
|
+
};
|
|
13652
|
+
if (p.rx || p.ry || p.rz || q.rx || q.ry || q.rz) {
|
|
13653
|
+
point.rx = lerp(num(p.rx), num(q.rx));
|
|
13654
|
+
point.ry = lerp(num(p.ry), num(q.ry));
|
|
13655
|
+
point.rz = lerp(num(p.rz), num(q.rz));
|
|
13656
|
+
}
|
|
13657
|
+
out[i] = point;
|
|
13658
|
+
}
|
|
13659
|
+
return out;
|
|
13660
|
+
}
|
|
13661
|
+
|
|
13662
|
+
// src/components/videoEditor/shapePath.js
|
|
13663
|
+
var num2 = (value, fallback = 0) => {
|
|
13664
|
+
const parsed = Number.parseFloat(value);
|
|
13665
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
13666
|
+
};
|
|
13359
13667
|
var round4 = (n2) => Math.round(n2 * 1e4) / 1e4;
|
|
13360
13668
|
var clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
13361
13669
|
var KAPPA = 0.5522847498307936 * 0.5;
|
|
13670
|
+
var SHAPE_PATH_KF_KEY = "shapePath";
|
|
13362
13671
|
var SHAPE_PATH_KF_GROUP = "Shape";
|
|
13672
|
+
var vec2 = (v) => {
|
|
13673
|
+
if (!v || typeof v !== "object") return null;
|
|
13674
|
+
return { x: num2(v.x), y: num2(v.y) };
|
|
13675
|
+
};
|
|
13676
|
+
function sanitizeShapePathPoints(points) {
|
|
13677
|
+
if (!Array.isArray(points)) return [];
|
|
13678
|
+
const clean = [];
|
|
13679
|
+
for (const p of points) {
|
|
13680
|
+
if (!p || typeof p !== "object") continue;
|
|
13681
|
+
if (!Number.isFinite(Number(p.x)) || !Number.isFinite(Number(p.y))) continue;
|
|
13682
|
+
const point = { x: clamp(num2(p.x), -8, 8), y: clamp(num2(p.y), -8, 8) };
|
|
13683
|
+
const hIn = vec2(p.hIn);
|
|
13684
|
+
const hOut = vec2(p.hOut);
|
|
13685
|
+
if (hIn) point.hIn = { x: clamp(hIn.x, -8, 8), y: clamp(hIn.y, -8, 8) };
|
|
13686
|
+
if (hOut) point.hOut = { x: clamp(hOut.x, -8, 8), y: clamp(hOut.y, -8, 8) };
|
|
13687
|
+
if (p.manual === true) point.manual = true;
|
|
13688
|
+
clean.push(point);
|
|
13689
|
+
}
|
|
13690
|
+
return clean;
|
|
13691
|
+
}
|
|
13363
13692
|
var corner = (x, y) => ({ x, y, hIn: { x: 0, y: 0 }, hOut: { x: 0, y: 0 }, manual: true });
|
|
13364
13693
|
function ellipsePoints() {
|
|
13365
13694
|
return [
|
|
@@ -13561,6 +13890,50 @@ var LINE_KF_PROPS = [
|
|
|
13561
13890
|
})
|
|
13562
13891
|
];
|
|
13563
13892
|
var LINE_KF_KEYS = LINE_KF_PROPS.map((p) => p.label);
|
|
13893
|
+
function interpolateShapePaths(a, b2, t) {
|
|
13894
|
+
if (!Array.isArray(a) || !a.length) return Array.isArray(b2) ? b2 : [];
|
|
13895
|
+
if (!Array.isArray(b2) || !b2.length) return a;
|
|
13896
|
+
if (!Number.isFinite(t) || t <= 0) return a;
|
|
13897
|
+
if (t >= 1) return b2;
|
|
13898
|
+
const n2 = Math.min(a.length, b2.length);
|
|
13899
|
+
const lerp = (p, q) => p + (q - p) * t;
|
|
13900
|
+
const lerpVec = (p, q) => ({
|
|
13901
|
+
x: lerp(p?.x || 0, q?.x || 0),
|
|
13902
|
+
y: lerp(p?.y || 0, q?.y || 0)
|
|
13903
|
+
});
|
|
13904
|
+
const out = new Array(n2);
|
|
13905
|
+
for (let i = 0; i < n2; i += 1) {
|
|
13906
|
+
out[i] = {
|
|
13907
|
+
x: lerp(a[i].x, b2[i].x),
|
|
13908
|
+
y: lerp(a[i].y, b2[i].y),
|
|
13909
|
+
hIn: lerpVec(a[i].hIn, b2[i].hIn),
|
|
13910
|
+
hOut: lerpVec(a[i].hOut, b2[i].hOut),
|
|
13911
|
+
// The blended handles are explicit geometry — never let an auto-tangent
|
|
13912
|
+
// pass downstream decide it knows better.
|
|
13913
|
+
manual: true
|
|
13914
|
+
};
|
|
13915
|
+
}
|
|
13916
|
+
return out;
|
|
13917
|
+
}
|
|
13918
|
+
function decodeShapePathValue(value) {
|
|
13919
|
+
if (Array.isArray(value)) return sanitizeShapePathPoints(value);
|
|
13920
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
13921
|
+
try {
|
|
13922
|
+
const points = sanitizeShapePathPoints(JSON.parse(value));
|
|
13923
|
+
return points.length ? points : null;
|
|
13924
|
+
} catch {
|
|
13925
|
+
return null;
|
|
13926
|
+
}
|
|
13927
|
+
}
|
|
13928
|
+
function isPathTrackKey(prop) {
|
|
13929
|
+
return prop === SHAPE_PATH_KF_KEY || isMaskPathTrackKey(prop) || isMotionPathTrackKey(prop);
|
|
13930
|
+
}
|
|
13931
|
+
function decodePathTrackValue(prop, value) {
|
|
13932
|
+
return isMotionPathTrackKey(prop) ? decodeMotionPathValue(value) : decodeShapePathValue(value);
|
|
13933
|
+
}
|
|
13934
|
+
function interpolatePathTrackValues(prop, a, b2, t) {
|
|
13935
|
+
return isMotionPathTrackKey(prop) ? interpolateMotionPaths(a, b2, t) : interpolateShapePaths(a, b2, t);
|
|
13936
|
+
}
|
|
13564
13937
|
|
|
13565
13938
|
// src/components/videoEditor/motionTemplates/mtl.js
|
|
13566
13939
|
init_define_import_meta_env();
|
|
@@ -14389,25 +14762,588 @@ init_define_import_meta_env();
|
|
|
14389
14762
|
|
|
14390
14763
|
// src/components/videoEditor/crossTransitionOrder.js
|
|
14391
14764
|
init_define_import_meta_env();
|
|
14765
|
+
var PREV_OVER_NEXT = "Previous over next";
|
|
14766
|
+
function movesPreviousClip(order) {
|
|
14767
|
+
return order === PREV_OVER_NEXT;
|
|
14768
|
+
}
|
|
14392
14769
|
|
|
14393
14770
|
// src/components/videoEditor/speedUtils.js
|
|
14394
14771
|
var SPEED_MIN = 0.1;
|
|
14395
14772
|
var SPEED_MAX = 20;
|
|
14396
14773
|
var AUTO_HANDLE = 1 / 3;
|
|
14774
|
+
var clamp2 = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
14775
|
+
var num3 = (value, fallback = 0) => {
|
|
14776
|
+
const parsed = Number(value);
|
|
14777
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
14778
|
+
};
|
|
14397
14779
|
var LOG_SLOW = -Math.log(SPEED_MIN);
|
|
14398
14780
|
var LOG_FAST = Math.log(SPEED_MAX);
|
|
14781
|
+
var cubic = (a, b2, c, d, t) => {
|
|
14782
|
+
const u = 1 - t;
|
|
14783
|
+
return u * u * u * a + 3 * u * u * t * b2 + 3 * u * t * t * c + t * t * t * d;
|
|
14784
|
+
};
|
|
14785
|
+
var cubicDeriv = (a, b2, c, d, t) => {
|
|
14786
|
+
const u = 1 - t;
|
|
14787
|
+
return 3 * u * u * (b2 - a) + 6 * u * t * (c - b2) + 3 * t * t * (d - c);
|
|
14788
|
+
};
|
|
14789
|
+
var speedCurveCache = /* @__PURE__ */ new WeakMap();
|
|
14790
|
+
function resolveSpeedCurve(points) {
|
|
14791
|
+
if (!Array.isArray(points) || points.length === 0) return null;
|
|
14792
|
+
const cached = speedCurveCache.get(points);
|
|
14793
|
+
if (cached) return cached;
|
|
14794
|
+
const sorted = points.filter((p) => p && Number.isFinite(Number(p.x)) && Number.isFinite(Number(p.y))).map((p) => ({
|
|
14795
|
+
x: num3(p.x),
|
|
14796
|
+
y: clamp2(num3(p.y, 1), SPEED_MIN, SPEED_MAX),
|
|
14797
|
+
manual: p.manual === true,
|
|
14798
|
+
hIn: p.hIn ? { x: num3(p.hIn.x), y: num3(p.hIn.y) } : null,
|
|
14799
|
+
hOut: p.hOut ? { x: num3(p.hOut.x), y: num3(p.hOut.y) } : null
|
|
14800
|
+
})).sort((a, b2) => a.x - b2.x);
|
|
14801
|
+
if (sorted.length === 0) return null;
|
|
14802
|
+
const anchors = sorted.map((p, i) => {
|
|
14803
|
+
const prev = sorted[i - 1];
|
|
14804
|
+
const next = sorted[i + 1];
|
|
14805
|
+
const shape = (raw, neighbour) => {
|
|
14806
|
+
if (!neighbour) return null;
|
|
14807
|
+
const dx = neighbour.x - p.x;
|
|
14808
|
+
const auto = { x: dx * AUTO_HANDLE, y: (neighbour.y - p.y) * AUTO_HANDLE };
|
|
14809
|
+
const h = p.manual && raw ? raw : auto;
|
|
14810
|
+
return {
|
|
14811
|
+
// Inside the segment only: x must never run backwards past its anchor or
|
|
14812
|
+
// forwards past the next one, or the curve stops being a function of time.
|
|
14813
|
+
x: dx >= 0 ? clamp2(h.x, 0, dx) : clamp2(h.x, dx, 0),
|
|
14814
|
+
// Convex hull: a legal control point means a legal curve.
|
|
14815
|
+
y: clamp2(p.y + h.y, SPEED_MIN, SPEED_MAX) - p.y
|
|
14816
|
+
};
|
|
14817
|
+
};
|
|
14818
|
+
return { x: p.x, y: p.y, manual: p.manual, hIn: shape(p.hIn, prev), hOut: shape(p.hOut, next) };
|
|
14819
|
+
});
|
|
14820
|
+
const segments = [];
|
|
14821
|
+
for (let i = 0; i < anchors.length - 1; i += 1) {
|
|
14822
|
+
const a = anchors[i];
|
|
14823
|
+
const b2 = anchors[i + 1];
|
|
14824
|
+
const ah = a.hOut || { x: 0, y: 0 };
|
|
14825
|
+
const bh = b2.hIn || { x: 0, y: 0 };
|
|
14826
|
+
const seg = {
|
|
14827
|
+
x0: a.x,
|
|
14828
|
+
x1: a.x + ah.x,
|
|
14829
|
+
x2: b2.x + bh.x,
|
|
14830
|
+
x3: b2.x,
|
|
14831
|
+
y0: a.y,
|
|
14832
|
+
y1: a.y + ah.y,
|
|
14833
|
+
y2: b2.y + bh.y,
|
|
14834
|
+
y3: b2.y
|
|
14835
|
+
};
|
|
14836
|
+
seg.linear = isLinearSegment(seg);
|
|
14837
|
+
seg.area = segmentArea(seg, 1);
|
|
14838
|
+
segments.push(seg);
|
|
14839
|
+
}
|
|
14840
|
+
const curve = {
|
|
14841
|
+
anchors,
|
|
14842
|
+
segments,
|
|
14843
|
+
area: segments.reduce((sum, s) => sum + s.area, 0),
|
|
14844
|
+
first: anchors[0],
|
|
14845
|
+
last: anchors[anchors.length - 1]
|
|
14846
|
+
};
|
|
14847
|
+
speedCurveCache.set(points, curve);
|
|
14848
|
+
return curve;
|
|
14849
|
+
}
|
|
14850
|
+
function isLinearSegment(seg) {
|
|
14851
|
+
const dx = seg.x3 - seg.x0;
|
|
14852
|
+
const dy = seg.y3 - seg.y0;
|
|
14853
|
+
const eps = 1e-9 * Math.max(1, Math.abs(dx), Math.abs(dy));
|
|
14854
|
+
return Math.abs(seg.x1 - (seg.x0 + dx / 3)) <= eps && Math.abs(seg.x2 - (seg.x0 + 2 * dx / 3)) <= eps && Math.abs(seg.y1 - (seg.y0 + dy / 3)) <= eps && Math.abs(seg.y2 - (seg.y0 + 2 * dy / 3)) <= eps;
|
|
14855
|
+
}
|
|
14856
|
+
function solveSegmentT(seg, x) {
|
|
14857
|
+
const span = seg.x3 - seg.x0;
|
|
14858
|
+
if (span <= 1e-9) return 0;
|
|
14859
|
+
let t = clamp2((x - seg.x0) / span, 0, 1);
|
|
14860
|
+
if (seg.linear) return t;
|
|
14861
|
+
for (let i = 0; i < 8; i += 1) {
|
|
14862
|
+
const err = cubic(seg.x0, seg.x1, seg.x2, seg.x3, t) - x;
|
|
14863
|
+
if (Math.abs(err) < 1e-7) return t;
|
|
14864
|
+
const d = cubicDeriv(seg.x0, seg.x1, seg.x2, seg.x3, t);
|
|
14865
|
+
if (Math.abs(d) < 1e-7) break;
|
|
14866
|
+
t = clamp2(t - err / d, 0, 1);
|
|
14867
|
+
}
|
|
14868
|
+
let lo = 0;
|
|
14869
|
+
let hi = 1;
|
|
14870
|
+
for (let i = 0; i < 24; i += 1) {
|
|
14871
|
+
const mid = (lo + hi) * 0.5;
|
|
14872
|
+
if (cubic(seg.x0, seg.x1, seg.x2, seg.x3, mid) < x) lo = mid;
|
|
14873
|
+
else hi = mid;
|
|
14874
|
+
}
|
|
14875
|
+
return (lo + hi) * 0.5;
|
|
14876
|
+
}
|
|
14877
|
+
var GAUSS = [
|
|
14878
|
+
[-0.8611363115940526, 0.3478548451374538],
|
|
14879
|
+
[-0.3399810435848563, 0.6521451548625461],
|
|
14880
|
+
[0.3399810435848563, 0.6521451548625461],
|
|
14881
|
+
[0.8611363115940526, 0.3478548451374538]
|
|
14882
|
+
];
|
|
14883
|
+
function segmentArea(seg, tEnd) {
|
|
14884
|
+
if (tEnd <= 0) return 0;
|
|
14885
|
+
if (seg.linear) {
|
|
14886
|
+
const width = (seg.x3 - seg.x0) * tEnd;
|
|
14887
|
+
const yAtT = seg.y0 + (seg.y3 - seg.y0) * tEnd;
|
|
14888
|
+
return width * (seg.y0 + yAtT) / 2;
|
|
14889
|
+
}
|
|
14890
|
+
const half = tEnd * 0.5;
|
|
14891
|
+
let sum = 0;
|
|
14892
|
+
for (const [node, weight] of GAUSS) {
|
|
14893
|
+
const t = half * (node + 1);
|
|
14894
|
+
sum += weight * cubic(seg.y0, seg.y1, seg.y2, seg.y3, t) * cubicDeriv(seg.x0, seg.x1, seg.x2, seg.x3, t);
|
|
14895
|
+
}
|
|
14896
|
+
return sum * half;
|
|
14897
|
+
}
|
|
14898
|
+
var getAverageSpeed = (points) => {
|
|
14899
|
+
const curve = resolveSpeedCurve(points);
|
|
14900
|
+
if (!curve) return 1;
|
|
14901
|
+
if (curve.segments.length === 0) return curve.first.y || 1;
|
|
14902
|
+
return curve.area || 1;
|
|
14903
|
+
};
|
|
14904
|
+
var mapTimelineToSource = (points, t) => {
|
|
14905
|
+
const curve = resolveSpeedCurve(points);
|
|
14906
|
+
if (!curve || curve.segments.length === 0) return t;
|
|
14907
|
+
const totalArea = curve.area;
|
|
14908
|
+
if (totalArea === 0) return 0;
|
|
14909
|
+
let currentArea = 0;
|
|
14910
|
+
for (const seg of curve.segments) {
|
|
14911
|
+
if (seg.x3 <= t) {
|
|
14912
|
+
currentArea += seg.area;
|
|
14913
|
+
} else if (seg.x0 < t) {
|
|
14914
|
+
currentArea += segmentArea(seg, solveSegmentT(seg, t));
|
|
14915
|
+
break;
|
|
14916
|
+
} else {
|
|
14917
|
+
break;
|
|
14918
|
+
}
|
|
14919
|
+
}
|
|
14920
|
+
return currentArea / totalArea;
|
|
14921
|
+
};
|
|
14922
|
+
var getTimelineDuration = (sourceDuration, points) => {
|
|
14923
|
+
const avgSpeed = getAverageSpeed(points);
|
|
14924
|
+
return sourceDuration / avgSpeed;
|
|
14925
|
+
};
|
|
14399
14926
|
var UNSNAPPED_TOUCH_EPSILON = 1 / 120;
|
|
14927
|
+
var slideEarlierClipsLater = (layout, anchorEnds, index, amount) => {
|
|
14928
|
+
if (!(amount > 0)) return;
|
|
14929
|
+
for (let j = index; j >= 0; j--) {
|
|
14930
|
+
layout[j].startTime += amount;
|
|
14931
|
+
layout[j].endTime += amount;
|
|
14932
|
+
if (anchorEnds[j] != null) anchorEnds[j] += amount;
|
|
14933
|
+
}
|
|
14934
|
+
};
|
|
14935
|
+
var getTimelineLayout = (timeline, { unsnapped } = {}) => {
|
|
14936
|
+
const layout = [];
|
|
14937
|
+
let currentCursor = 0;
|
|
14938
|
+
const anchorEnds = [];
|
|
14939
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
14940
|
+
const item = timeline[i];
|
|
14941
|
+
const sourceDuration = item.trimEnd - item.trimStart;
|
|
14942
|
+
const tlDuration = getTimelineDuration(sourceDuration, item.speedPoints);
|
|
14943
|
+
if (unsnapped && item.freeStart != null) {
|
|
14944
|
+
const prevAnchorEnd = i > 0 ? anchorEnds[i - 1] : null;
|
|
14945
|
+
const touchingPrev = prevAnchorEnd != null && Math.abs(prevAnchorEnd - item.freeStart) <= UNSNAPPED_TOUCH_EPSILON;
|
|
14946
|
+
let overlap = 0;
|
|
14947
|
+
if (touchingPrev && item.transition !== TransitionType.NONE) {
|
|
14948
|
+
overlap = Math.min(layout[i - 1].duration, tlDuration, item.transitionDuration);
|
|
14949
|
+
}
|
|
14950
|
+
const previousMoves = overlap > 0 && movesPreviousClip(item.transitionOrder);
|
|
14951
|
+
const startTime = overlap > 0 ? previousMoves ? layout[i - 1].endTime : layout[i - 1].endTime - overlap : item.freeStart;
|
|
14952
|
+
const endTime = startTime + tlDuration;
|
|
14953
|
+
layout.push({ startTime, endTime, duration: tlDuration, overlapIn: overlap, touchingPrev });
|
|
14954
|
+
anchorEnds.push(item.freeStart + tlDuration);
|
|
14955
|
+
currentCursor = endTime;
|
|
14956
|
+
if (previousMoves) slideEarlierClipsLater(layout, anchorEnds, i - 1, overlap);
|
|
14957
|
+
} else {
|
|
14958
|
+
let overlap = 0;
|
|
14959
|
+
if (i > 0 && item.transition !== TransitionType.NONE) {
|
|
14960
|
+
const prevLayout = layout[i - 1];
|
|
14961
|
+
const maxOverlap = Math.min(prevLayout.duration, tlDuration, item.transitionDuration);
|
|
14962
|
+
overlap = maxOverlap;
|
|
14963
|
+
}
|
|
14964
|
+
const previousMoves = overlap > 0 && movesPreviousClip(item.transitionOrder);
|
|
14965
|
+
const startTime = previousMoves ? currentCursor : currentCursor - overlap;
|
|
14966
|
+
const endTime = startTime + tlDuration;
|
|
14967
|
+
layout.push({ startTime, endTime, duration: tlDuration, overlapIn: overlap, touchingPrev: i > 0 });
|
|
14968
|
+
anchorEnds.push(endTime);
|
|
14969
|
+
currentCursor = endTime;
|
|
14970
|
+
if (previousMoves) slideEarlierClipsLater(layout, anchorEnds, i - 1, overlap);
|
|
14971
|
+
}
|
|
14972
|
+
}
|
|
14973
|
+
return layout;
|
|
14974
|
+
};
|
|
14400
14975
|
|
|
14401
14976
|
// src/components/videoEditor/keyframeFx.js
|
|
14402
14977
|
init_define_import_meta_env();
|
|
14403
14978
|
|
|
14404
14979
|
// src/components/videoEditor/keyframeExpr.js
|
|
14405
14980
|
init_define_import_meta_env();
|
|
14981
|
+
var ExprError = class extends Error {
|
|
14982
|
+
constructor(message, pos = 0) {
|
|
14983
|
+
super(message);
|
|
14984
|
+
this.name = "ExprError";
|
|
14985
|
+
this.pos = pos;
|
|
14986
|
+
}
|
|
14987
|
+
};
|
|
14988
|
+
var MAX_NODES = 600;
|
|
14989
|
+
var MAX_DEPTH = 40;
|
|
14990
|
+
var MAX_STEPS = 2e4;
|
|
14406
14991
|
var MAX_EXPR_LENGTH = 4e3;
|
|
14407
|
-
var
|
|
14992
|
+
var PUNCT = ["===", "!==", "==", "!=", "<=", ">=", "&&", "||", "**", "(", ")", "[", "]", ",", ".", "+", "-", "*", "/", "%", "^", "<", ">", "?", ":", "!"];
|
|
14993
|
+
function tokenize(src) {
|
|
14994
|
+
const tokens = [];
|
|
14995
|
+
let i = 0;
|
|
14996
|
+
const n2 = src.length;
|
|
14997
|
+
while (i < n2) {
|
|
14998
|
+
const c = src[i];
|
|
14999
|
+
if (c === " " || c === " " || c === "\n" || c === "\r") {
|
|
15000
|
+
i += 1;
|
|
15001
|
+
continue;
|
|
15002
|
+
}
|
|
15003
|
+
if (c === "/" && src[i + 1] === "/") {
|
|
15004
|
+
while (i < n2 && src[i] !== "\n") i += 1;
|
|
15005
|
+
continue;
|
|
15006
|
+
}
|
|
15007
|
+
if (c === "/" && src[i + 1] === "*") {
|
|
15008
|
+
const end = src.indexOf("*/", i + 2);
|
|
15009
|
+
i = end === -1 ? n2 : end + 2;
|
|
15010
|
+
continue;
|
|
15011
|
+
}
|
|
15012
|
+
if (c >= "0" && c <= "9" || c === "." && src[i + 1] >= "0" && src[i + 1] <= "9") {
|
|
15013
|
+
const start = i;
|
|
15014
|
+
while (i < n2 && src[i] >= "0" && src[i] <= "9") i += 1;
|
|
15015
|
+
if (src[i] === ".") {
|
|
15016
|
+
i += 1;
|
|
15017
|
+
while (i < n2 && src[i] >= "0" && src[i] <= "9") i += 1;
|
|
15018
|
+
}
|
|
15019
|
+
if (src[i] === "e" || src[i] === "E") {
|
|
15020
|
+
let j = i + 1;
|
|
15021
|
+
if (src[j] === "+" || src[j] === "-") j += 1;
|
|
15022
|
+
if (src[j] >= "0" && src[j] <= "9") {
|
|
15023
|
+
i = j;
|
|
15024
|
+
while (i < n2 && src[i] >= "0" && src[i] <= "9") i += 1;
|
|
15025
|
+
}
|
|
15026
|
+
}
|
|
15027
|
+
tokens.push({ type: "num", value: Number.parseFloat(src.slice(start, i)), pos: start });
|
|
15028
|
+
continue;
|
|
15029
|
+
}
|
|
15030
|
+
if (c === '"' || c === "'") {
|
|
15031
|
+
const start = i;
|
|
15032
|
+
const quote = c;
|
|
15033
|
+
i += 1;
|
|
15034
|
+
let out = "";
|
|
15035
|
+
while (i < n2 && src[i] !== quote) {
|
|
15036
|
+
if (src[i] === "\\" && i + 1 < n2) {
|
|
15037
|
+
const esc = src[i + 1];
|
|
15038
|
+
out += esc === "n" ? "\n" : esc === "t" ? " " : esc;
|
|
15039
|
+
i += 2;
|
|
15040
|
+
continue;
|
|
15041
|
+
}
|
|
15042
|
+
out += src[i];
|
|
15043
|
+
i += 1;
|
|
15044
|
+
}
|
|
15045
|
+
if (i >= n2) throw new ExprError("Unterminated string", start);
|
|
15046
|
+
i += 1;
|
|
15047
|
+
tokens.push({ type: "str", value: out, pos: start });
|
|
15048
|
+
continue;
|
|
15049
|
+
}
|
|
15050
|
+
if (/[A-Za-z_$]/.test(c)) {
|
|
15051
|
+
const start = i;
|
|
15052
|
+
while (i < n2 && /[A-Za-z0-9_$]/.test(src[i])) i += 1;
|
|
15053
|
+
tokens.push({ type: "ident", value: src.slice(start, i), pos: start });
|
|
15054
|
+
continue;
|
|
15055
|
+
}
|
|
15056
|
+
const punct = PUNCT.find((p) => src.startsWith(p, i));
|
|
15057
|
+
if (punct) {
|
|
15058
|
+
tokens.push({ type: "punct", value: punct, pos: i });
|
|
15059
|
+
i += punct.length;
|
|
15060
|
+
continue;
|
|
15061
|
+
}
|
|
15062
|
+
throw new ExprError(`Unexpected character "${c}"`, i);
|
|
15063
|
+
}
|
|
15064
|
+
tokens.push({ type: "eof", value: "", pos: n2 });
|
|
15065
|
+
return tokens;
|
|
15066
|
+
}
|
|
15067
|
+
var BINARY_PRECEDENCE = {
|
|
15068
|
+
"||": 1,
|
|
15069
|
+
"&&": 2,
|
|
15070
|
+
"==": 3,
|
|
15071
|
+
"!=": 3,
|
|
15072
|
+
"===": 3,
|
|
15073
|
+
"!==": 3,
|
|
15074
|
+
"<": 4,
|
|
15075
|
+
">": 4,
|
|
15076
|
+
"<=": 4,
|
|
15077
|
+
">=": 4,
|
|
15078
|
+
"+": 5,
|
|
15079
|
+
"-": 5,
|
|
15080
|
+
"*": 6,
|
|
15081
|
+
"/": 6,
|
|
15082
|
+
"%": 6,
|
|
15083
|
+
"^": 8,
|
|
15084
|
+
"**": 8
|
|
15085
|
+
};
|
|
15086
|
+
var RIGHT_ASSOC = /* @__PURE__ */ new Set(["^", "**"]);
|
|
15087
|
+
var Parser = class {
|
|
15088
|
+
constructor(tokens) {
|
|
15089
|
+
this.tokens = tokens;
|
|
15090
|
+
this.i = 0;
|
|
15091
|
+
this.nodes = 0;
|
|
15092
|
+
}
|
|
15093
|
+
peek() {
|
|
15094
|
+
return this.tokens[this.i];
|
|
15095
|
+
}
|
|
15096
|
+
next() {
|
|
15097
|
+
return this.tokens[this.i++];
|
|
15098
|
+
}
|
|
15099
|
+
isPunct(v) {
|
|
15100
|
+
const t = this.peek();
|
|
15101
|
+
return t.type === "punct" && t.value === v;
|
|
15102
|
+
}
|
|
15103
|
+
expect(v) {
|
|
15104
|
+
if (!this.isPunct(v)) throw new ExprError(`Expected "${v}"`, this.peek().pos);
|
|
15105
|
+
return this.next();
|
|
15106
|
+
}
|
|
15107
|
+
node(obj) {
|
|
15108
|
+
this.nodes += 1;
|
|
15109
|
+
if (this.nodes > MAX_NODES) throw new ExprError("Expression is too long", 0);
|
|
15110
|
+
return obj;
|
|
15111
|
+
}
|
|
15112
|
+
parseExpression(depth = 0) {
|
|
15113
|
+
if (depth > MAX_DEPTH) throw new ExprError("Expression is nested too deeply", this.peek().pos);
|
|
15114
|
+
const test = this.parseBinary(0, depth + 1);
|
|
15115
|
+
if (this.isPunct("?")) {
|
|
15116
|
+
this.next();
|
|
15117
|
+
const then = this.parseExpression(depth + 1);
|
|
15118
|
+
this.expect(":");
|
|
15119
|
+
const other = this.parseExpression(depth + 1);
|
|
15120
|
+
return this.node({ kind: "cond", test, then, other });
|
|
15121
|
+
}
|
|
15122
|
+
return test;
|
|
15123
|
+
}
|
|
15124
|
+
parseBinary(minPrec, depth) {
|
|
15125
|
+
if (depth > MAX_DEPTH) throw new ExprError("Expression is nested too deeply", this.peek().pos);
|
|
15126
|
+
let left = this.parseUnary(depth + 1);
|
|
15127
|
+
for (; ; ) {
|
|
15128
|
+
const t = this.peek();
|
|
15129
|
+
if (t.type !== "punct") break;
|
|
15130
|
+
const prec = BINARY_PRECEDENCE[t.value];
|
|
15131
|
+
if (prec === void 0 || prec < minPrec) break;
|
|
15132
|
+
this.next();
|
|
15133
|
+
const nextMin = RIGHT_ASSOC.has(t.value) ? prec : prec + 1;
|
|
15134
|
+
const right = this.parseBinary(nextMin, depth + 1);
|
|
15135
|
+
left = this.node({ kind: "bin", op: t.value, left, right, pos: t.pos });
|
|
15136
|
+
}
|
|
15137
|
+
return left;
|
|
15138
|
+
}
|
|
15139
|
+
parseUnary(depth) {
|
|
15140
|
+
const t = this.peek();
|
|
15141
|
+
if (t.type === "punct" && (t.value === "-" || t.value === "+" || t.value === "!")) {
|
|
15142
|
+
this.next();
|
|
15143
|
+
return this.node({ kind: "un", op: t.value, arg: this.parseUnary(depth + 1), pos: t.pos });
|
|
15144
|
+
}
|
|
15145
|
+
return this.parsePostfix(depth);
|
|
15146
|
+
}
|
|
15147
|
+
parsePostfix(depth) {
|
|
15148
|
+
let target = this.parsePrimary(depth);
|
|
15149
|
+
for (; ; ) {
|
|
15150
|
+
if (this.isPunct(".")) {
|
|
15151
|
+
this.next();
|
|
15152
|
+
const name = this.next();
|
|
15153
|
+
if (name.type !== "ident") throw new ExprError('Expected a property name after "."', name.pos);
|
|
15154
|
+
target = this.node({ kind: "member", target, name: name.value, pos: name.pos });
|
|
15155
|
+
} else if (this.isPunct("[")) {
|
|
15156
|
+
const open = this.next();
|
|
15157
|
+
const index = this.parseExpression(depth + 1);
|
|
15158
|
+
this.expect("]");
|
|
15159
|
+
target = this.node({ kind: "index", target, index, pos: open.pos });
|
|
15160
|
+
} else if (this.isPunct("(")) {
|
|
15161
|
+
const open = this.next();
|
|
15162
|
+
const args = [];
|
|
15163
|
+
if (!this.isPunct(")")) {
|
|
15164
|
+
for (; ; ) {
|
|
15165
|
+
args.push(this.parseExpression(depth + 1));
|
|
15166
|
+
if (this.isPunct(",")) {
|
|
15167
|
+
this.next();
|
|
15168
|
+
continue;
|
|
15169
|
+
}
|
|
15170
|
+
break;
|
|
15171
|
+
}
|
|
15172
|
+
}
|
|
15173
|
+
this.expect(")");
|
|
15174
|
+
target = this.node({ kind: "call", callee: target, args, pos: open.pos });
|
|
15175
|
+
} else break;
|
|
15176
|
+
}
|
|
15177
|
+
return target;
|
|
15178
|
+
}
|
|
15179
|
+
parsePrimary(depth) {
|
|
15180
|
+
const t = this.next();
|
|
15181
|
+
if (t.type === "num") return this.node({ kind: "num", value: t.value });
|
|
15182
|
+
if (t.type === "str") return this.node({ kind: "str", value: t.value });
|
|
15183
|
+
if (t.type === "ident") return this.node({ kind: "ident", name: t.value, pos: t.pos });
|
|
15184
|
+
if (t.type === "punct" && t.value === "(") {
|
|
15185
|
+
const inner = this.parseExpression(depth + 1);
|
|
15186
|
+
this.expect(")");
|
|
15187
|
+
return inner;
|
|
15188
|
+
}
|
|
15189
|
+
if (t.type === "punct" && t.value === "[") {
|
|
15190
|
+
const items = [];
|
|
15191
|
+
if (!this.isPunct("]")) {
|
|
15192
|
+
for (; ; ) {
|
|
15193
|
+
items.push(this.parseExpression(depth + 1));
|
|
15194
|
+
if (this.isPunct(",")) {
|
|
15195
|
+
this.next();
|
|
15196
|
+
continue;
|
|
15197
|
+
}
|
|
15198
|
+
break;
|
|
15199
|
+
}
|
|
15200
|
+
}
|
|
15201
|
+
this.expect("]");
|
|
15202
|
+
return this.node({ kind: "array", items });
|
|
15203
|
+
}
|
|
15204
|
+
throw new ExprError(t.type === "eof" ? "Expression is incomplete" : `Unexpected "${t.value}"`, t.pos);
|
|
15205
|
+
}
|
|
15206
|
+
};
|
|
15207
|
+
function parseExpression(src) {
|
|
15208
|
+
const text = String(src ?? "");
|
|
15209
|
+
if (text.length > MAX_EXPR_LENGTH) throw new ExprError("Expression is too long");
|
|
15210
|
+
const trimmed = text.trim().replace(/;+\s*$/, "");
|
|
15211
|
+
if (!trimmed) throw new ExprError("Expression is empty");
|
|
15212
|
+
const parser = new Parser(tokenize(trimmed));
|
|
15213
|
+
const ast = parser.parseExpression();
|
|
15214
|
+
const tail = parser.peek();
|
|
15215
|
+
if (tail.type !== "eof") throw new ExprError(`Unexpected "${tail.value}"`, tail.pos);
|
|
15216
|
+
return ast;
|
|
15217
|
+
}
|
|
15218
|
+
var astCache = /* @__PURE__ */ new Map();
|
|
15219
|
+
var AST_CACHE_MAX = 200;
|
|
15220
|
+
function compileExpression(src) {
|
|
15221
|
+
const key = String(src ?? "");
|
|
15222
|
+
const hit = astCache.get(key);
|
|
15223
|
+
if (hit) return hit;
|
|
15224
|
+
let entry;
|
|
15225
|
+
try {
|
|
15226
|
+
entry = { ast: parseExpression(key), error: null };
|
|
15227
|
+
} catch (err) {
|
|
15228
|
+
entry = { ast: null, error: err instanceof ExprError ? err : new ExprError(String(err?.message || err)) };
|
|
15229
|
+
}
|
|
15230
|
+
if (astCache.size >= AST_CACHE_MAX) astCache.delete(astCache.keys().next().value);
|
|
15231
|
+
astCache.set(key, entry);
|
|
15232
|
+
return entry;
|
|
15233
|
+
}
|
|
15234
|
+
function latticeNoise(i, seed) {
|
|
15235
|
+
let h = Math.imul(i | 0, 374761393) + Math.imul(seed | 0, 668265263);
|
|
15236
|
+
h = Math.imul(h ^ h >>> 13, 1274126177);
|
|
15237
|
+
h ^= h >>> 16;
|
|
15238
|
+
return (h & 2147483647) / 1073741823 - 1;
|
|
15239
|
+
}
|
|
15240
|
+
function stringSeed(str) {
|
|
15241
|
+
let h = 2166136261;
|
|
15242
|
+
const s = String(str || "");
|
|
15243
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
15244
|
+
h ^= s.charCodeAt(i);
|
|
15245
|
+
h = Math.imul(h, 16777619);
|
|
15246
|
+
}
|
|
15247
|
+
return h | 0;
|
|
15248
|
+
}
|
|
15249
|
+
function smoothNoise(x, seed) {
|
|
15250
|
+
const i = Math.floor(x);
|
|
15251
|
+
const f2 = x - i;
|
|
15252
|
+
const u = f2 * f2 * (3 - 2 * f2);
|
|
15253
|
+
return latticeNoise(i, seed) * (1 - u) + latticeNoise(i + 1, seed) * u;
|
|
15254
|
+
}
|
|
15255
|
+
function fractalNoise(x, seed, octaves) {
|
|
15256
|
+
let sum = 0;
|
|
15257
|
+
let amp = 1;
|
|
15258
|
+
let norm = 0;
|
|
15259
|
+
let freq = 1;
|
|
15260
|
+
const n2 = Math.max(1, Math.min(4, Math.round(octaves)));
|
|
15261
|
+
for (let o = 0; o < n2; o += 1) {
|
|
15262
|
+
sum += smoothNoise(x * freq, seed + o * 1013) * amp;
|
|
15263
|
+
norm += amp;
|
|
15264
|
+
amp *= 0.5;
|
|
15265
|
+
freq *= 2;
|
|
15266
|
+
}
|
|
15267
|
+
return norm > 0 ? sum / norm : 0;
|
|
15268
|
+
}
|
|
15269
|
+
var LOOP_TYPES = /* @__PURE__ */ new Set(["cycle", "pingpong", "offset", "continue"]);
|
|
15270
|
+
function loopSegment(keyTimes, n2, isOut, byDuration) {
|
|
15271
|
+
const first = keyTimes[0];
|
|
15272
|
+
const last = keyTimes[keyTimes.length - 1];
|
|
15273
|
+
if (byDuration) {
|
|
15274
|
+
const d = Math.abs(n2);
|
|
15275
|
+
if (!(d > 0)) return [first, last];
|
|
15276
|
+
return isOut ? [Math.max(first, last - d), last] : [first, Math.min(last, first + d)];
|
|
15277
|
+
}
|
|
15278
|
+
const count = Math.max(0, Math.round(n2));
|
|
15279
|
+
if (count <= 0) return [first, last];
|
|
15280
|
+
const idx = isOut ? Math.max(0, keyTimes.length - 1 - count) : Math.min(keyTimes.length - 1, count);
|
|
15281
|
+
return isOut ? [keyTimes[idx], last] : [first, keyTimes[idx]];
|
|
15282
|
+
}
|
|
15283
|
+
function loopValue(ctx, isOut, typeRaw, n2, byDuration) {
|
|
15284
|
+
const keyTimes = ctx.keyTimes;
|
|
15285
|
+
const valueAt = ctx.valueAt;
|
|
15286
|
+
if (typeof valueAt !== "function" || !Array.isArray(keyTimes) || keyTimes.length < 2) return ctx.value;
|
|
15287
|
+
const type = String(typeRaw ?? "cycle").toLowerCase();
|
|
15288
|
+
if (!LOOP_TYPES.has(type)) throw new ExprError(`Unknown loop type "${typeRaw}" \u2014 use cycle, pingpong, offset or continue`);
|
|
15289
|
+
const t = num4(ctx.time);
|
|
15290
|
+
const [segStart, segEnd] = loopSegment(keyTimes, num4(n2), isOut, byDuration);
|
|
15291
|
+
const edge = isOut ? keyTimes[keyTimes.length - 1] : keyTimes[0];
|
|
15292
|
+
const delta = isOut ? t - edge : edge - t;
|
|
15293
|
+
if (delta <= 0) return ctx.value;
|
|
15294
|
+
const span = segEnd - segStart;
|
|
15295
|
+
if (!(span > 0)) return valueAt(edge);
|
|
15296
|
+
if (type === "continue") {
|
|
15297
|
+
const h = Math.min(span * 0.25, 1 / 60);
|
|
15298
|
+
const vEdge = valueAt(edge);
|
|
15299
|
+
const slope = isOut ? (vEdge - valueAt(edge - h)) / h : (valueAt(edge + h) - vEdge) / h;
|
|
15300
|
+
return vEdge + slope * (isOut ? delta : -delta);
|
|
15301
|
+
}
|
|
15302
|
+
const cycles = Math.floor(delta / span);
|
|
15303
|
+
const phase = delta - cycles * span;
|
|
15304
|
+
if (type === "pingpong") {
|
|
15305
|
+
const backwards = cycles % 2 === 0;
|
|
15306
|
+
const at2 = isOut ? backwards ? segEnd - phase : segStart + phase : backwards ? segStart + phase : segEnd - phase;
|
|
15307
|
+
return valueAt(at2);
|
|
15308
|
+
}
|
|
15309
|
+
const at = isOut ? segStart + phase : segEnd - phase;
|
|
15310
|
+
const v = valueAt(at);
|
|
15311
|
+
if (type === "cycle") return v;
|
|
15312
|
+
const rise = valueAt(segEnd) - valueAt(segStart);
|
|
15313
|
+
return v + rise * (cycles + 1) * (isOut ? 1 : -1);
|
|
15314
|
+
}
|
|
15315
|
+
var GLIDE_FAST_TAPS = 48;
|
|
15316
|
+
var GLIDE_SLOW_TAPS = 64;
|
|
15317
|
+
function glideValue(ctx, fastArg, slowArg, weightArg, delayArg) {
|
|
15318
|
+
const valueAt = ctx.valueAt;
|
|
15319
|
+
if (typeof valueAt !== "function") return num4(ctx.value);
|
|
15320
|
+
const fast = clampNumber(fastArg === void 0 ? 0.1 : num4(fastArg), 5e-3, 3);
|
|
15321
|
+
const slow = clampNumber(slowArg === void 0 ? 0.6 : num4(slowArg), 0.01, 8);
|
|
15322
|
+
const weight = clampNumber(weightArg === void 0 ? 0.25 : num4(weightArg), 0, 1);
|
|
15323
|
+
const delay = clampNumber(delayArg === void 0 ? 0 : num4(delayArg), 0, 3);
|
|
15324
|
+
const t = num4(ctx.time);
|
|
15325
|
+
let acc = 0;
|
|
15326
|
+
let total = 0;
|
|
15327
|
+
const part = (share, tau0, timeConstant, taps) => {
|
|
15328
|
+
if (share <= 0) return;
|
|
15329
|
+
const span = 4 * timeConstant;
|
|
15330
|
+
const step = span / taps;
|
|
15331
|
+
for (let i = 0; i <= taps; i += 1) {
|
|
15332
|
+
const tau = i * step;
|
|
15333
|
+
const w = share * Math.exp(-tau / timeConstant) * step / timeConstant;
|
|
15334
|
+
acc += w * valueAt(t - tau0 - tau);
|
|
15335
|
+
total += w;
|
|
15336
|
+
}
|
|
15337
|
+
};
|
|
15338
|
+
part(1 - weight, 0, fast, GLIDE_FAST_TAPS);
|
|
15339
|
+
part(weight, delay, slow, GLIDE_SLOW_TAPS);
|
|
15340
|
+
return total > 0 ? acc / total : num4(ctx.value);
|
|
15341
|
+
}
|
|
15342
|
+
var num4 = (v) => {
|
|
14408
15343
|
const n2 = typeof v === "number" ? v : Number.parseFloat(v);
|
|
14409
15344
|
return Number.isFinite(n2) ? n2 : 0;
|
|
14410
15345
|
};
|
|
15346
|
+
var truthy = (v) => typeof v === "number" ? v !== 0 : !!v;
|
|
14411
15347
|
function clampNumber(v, lo, hi) {
|
|
14412
15348
|
return Math.min(hi, Math.max(lo, v));
|
|
14413
15349
|
}
|
|
@@ -14418,17 +15354,17 @@ function remap(args, shape) {
|
|
|
14418
15354
|
let v1;
|
|
14419
15355
|
let v2;
|
|
14420
15356
|
if (args.length <= 3) {
|
|
14421
|
-
t =
|
|
15357
|
+
t = num4(args[0]);
|
|
14422
15358
|
tMin = 0;
|
|
14423
15359
|
tMax = 1;
|
|
14424
|
-
v1 =
|
|
14425
|
-
v2 =
|
|
15360
|
+
v1 = num4(args[1]);
|
|
15361
|
+
v2 = num4(args[2]);
|
|
14426
15362
|
} else {
|
|
14427
|
-
t =
|
|
14428
|
-
tMin =
|
|
14429
|
-
tMax =
|
|
14430
|
-
v1 =
|
|
14431
|
-
v2 =
|
|
15363
|
+
t = num4(args[0]);
|
|
15364
|
+
tMin = num4(args[1]);
|
|
15365
|
+
tMax = num4(args[2]);
|
|
15366
|
+
v1 = num4(args[3]);
|
|
15367
|
+
v2 = num4(args[4]);
|
|
14432
15368
|
}
|
|
14433
15369
|
if (tMax === tMin) return t <= tMin ? v1 : v2;
|
|
14434
15370
|
const raw = clampNumber((t - tMin) / (tMax - tMin), 0, 1);
|
|
@@ -14443,12 +15379,12 @@ var EASE_SHAPES = {
|
|
|
14443
15379
|
function makeMathNamespace() {
|
|
14444
15380
|
const ns = /* @__PURE__ */ Object.create(null);
|
|
14445
15381
|
const unary = ["abs", "floor", "ceil", "round", "sqrt", "cbrt", "exp", "log", "log2", "log10", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "sign", "trunc"];
|
|
14446
|
-
for (const name of unary) ns[name] = (...a) => Math[name](
|
|
14447
|
-
ns.pow = (a, b2) => Math.pow(
|
|
14448
|
-
ns.atan2 = (a, b2) => Math.atan2(
|
|
14449
|
-
ns.hypot = (...a) => Math.hypot(...a.map(
|
|
14450
|
-
ns.min = (...a) => a.length ? Math.min(...a.map(
|
|
14451
|
-
ns.max = (...a) => a.length ? Math.max(...a.map(
|
|
15382
|
+
for (const name of unary) ns[name] = (...a) => Math[name](num4(a[0]));
|
|
15383
|
+
ns.pow = (a, b2) => Math.pow(num4(a), num4(b2));
|
|
15384
|
+
ns.atan2 = (a, b2) => Math.atan2(num4(a), num4(b2));
|
|
15385
|
+
ns.hypot = (...a) => Math.hypot(...a.map(num4));
|
|
15386
|
+
ns.min = (...a) => a.length ? Math.min(...a.map(num4)) : 0;
|
|
15387
|
+
ns.max = (...a) => a.length ? Math.max(...a.map(num4)) : 0;
|
|
14452
15388
|
ns.PI = Math.PI;
|
|
14453
15389
|
ns.E = Math.E;
|
|
14454
15390
|
return ns;
|
|
@@ -14462,17 +15398,187 @@ var STATIC_GLOBALS = (() => {
|
|
|
14462
15398
|
for (const name of ["abs", "floor", "ceil", "round", "sqrt", "exp", "log", "sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "min", "max", "sign", "hypot"]) {
|
|
14463
15399
|
g[name] = MATH_NS[name];
|
|
14464
15400
|
}
|
|
14465
|
-
g.clamp = (v, lo, hi) => clampNumber(
|
|
14466
|
-
g.lerp = (a, b2, t) =>
|
|
15401
|
+
g.clamp = (v, lo, hi) => clampNumber(num4(v), num4(lo), num4(hi));
|
|
15402
|
+
g.lerp = (a, b2, t) => num4(a) + (num4(b2) - num4(a)) * num4(t);
|
|
14467
15403
|
g.linear = (...a) => remap(a, EASE_SHAPES.linear);
|
|
14468
15404
|
g.ease = (...a) => remap(a, EASE_SHAPES.ease);
|
|
14469
15405
|
g.easeIn = (...a) => remap(a, EASE_SHAPES.easeIn);
|
|
14470
15406
|
g.easeOut = (...a) => remap(a, EASE_SHAPES.easeOut);
|
|
14471
|
-
g.degrees = (r) =>
|
|
14472
|
-
g.radians = (d) =>
|
|
15407
|
+
g.degrees = (r) => num4(r) * 180 / Math.PI;
|
|
15408
|
+
g.radians = (d) => num4(d) * Math.PI / 180;
|
|
14473
15409
|
g.seedRandom = () => 0;
|
|
14474
15410
|
return g;
|
|
14475
15411
|
})();
|
|
15412
|
+
function evaluateAst(ast, ctx) {
|
|
15413
|
+
const state = { steps: 0 };
|
|
15414
|
+
let randomCounter = 0;
|
|
15415
|
+
const seed = ctx.seed | 0;
|
|
15416
|
+
const timeSeed = () => Math.floor(num4(ctx.time) * 1e3);
|
|
15417
|
+
const layerRef = (ref) => {
|
|
15418
|
+
if (!ref) return null;
|
|
15419
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
15420
|
+
obj.__layer = ref;
|
|
15421
|
+
return obj;
|
|
15422
|
+
};
|
|
15423
|
+
const globals = Object.create(STATIC_GLOBALS);
|
|
15424
|
+
globals.value = ctx.value;
|
|
15425
|
+
globals.time = ctx.time;
|
|
15426
|
+
globals.frame = ctx.frame;
|
|
15427
|
+
globals.fps = ctx.fps;
|
|
15428
|
+
globals.duration = ctx.duration;
|
|
15429
|
+
globals.progress = ctx.progress;
|
|
15430
|
+
globals.index = ctx.index;
|
|
15431
|
+
globals.thisProperty = ctx.value;
|
|
15432
|
+
globals.noise = (t, octaves) => fractalNoise(num4(t), seed, octaves === void 0 ? 1 : num4(octaves));
|
|
15433
|
+
globals.wiggle = (freq, amp, octaves) => num4(ctx.value) + num4(amp) * fractalNoise(num4(ctx.time) * num4(freq), seed, octaves === void 0 ? 2 : num4(octaves));
|
|
15434
|
+
globals.random = (...a) => {
|
|
15435
|
+
randomCounter += 1;
|
|
15436
|
+
const r = (latticeNoise(timeSeed() + randomCounter * 7919, seed) + 1) / 2;
|
|
15437
|
+
if (a.length === 0) return r;
|
|
15438
|
+
if (a.length === 1) return r * num4(a[0]);
|
|
15439
|
+
return num4(a[0]) + r * (num4(a[1]) - num4(a[0]));
|
|
15440
|
+
};
|
|
15441
|
+
globals.loopOut = (type, n2) => loopValue(ctx, true, type, n2, false);
|
|
15442
|
+
globals.loopIn = (type, n2) => loopValue(ctx, false, type, n2, false);
|
|
15443
|
+
globals.loopOutDuration = (type, d) => loopValue(ctx, true, type, d, true);
|
|
15444
|
+
globals.loopInDuration = (type, d) => loopValue(ctx, false, type, d, true);
|
|
15445
|
+
globals.numKeys = Array.isArray(ctx.keyTimes) ? ctx.keyTimes.length : 0;
|
|
15446
|
+
globals.valueAtTime = (t) => typeof ctx.valueAt === "function" ? ctx.valueAt(num4(t)) : ctx.value;
|
|
15447
|
+
globals.glide = (fast, slow, weight, delay) => glideValue(ctx, fast, slow, weight, delay);
|
|
15448
|
+
globals.layer = (name) => {
|
|
15449
|
+
const ref = ctx.resolveLayer?.(typeof name === "string" ? name : num4(name));
|
|
15450
|
+
if (!ref) throw new ExprError(`No layer named ${JSON.stringify(String(name))} overlaps this clip`);
|
|
15451
|
+
return layerRef(ref);
|
|
15452
|
+
};
|
|
15453
|
+
globals.thisLayer = layerRef(ctx.resolveSelf?.());
|
|
15454
|
+
globals.thisComp = (() => {
|
|
15455
|
+
const ns = /* @__PURE__ */ Object.create(null);
|
|
15456
|
+
ns.layer = globals.layer;
|
|
15457
|
+
ns.duration = ctx.duration;
|
|
15458
|
+
ns.frameDuration = ctx.fps > 0 ? 1 / ctx.fps : 1 / 30;
|
|
15459
|
+
return ns;
|
|
15460
|
+
})();
|
|
15461
|
+
const walk = (node) => {
|
|
15462
|
+
state.steps += 1;
|
|
15463
|
+
if (state.steps > MAX_STEPS) throw new ExprError("Expression is too slow to evaluate");
|
|
15464
|
+
switch (node.kind) {
|
|
15465
|
+
case "num":
|
|
15466
|
+
case "str":
|
|
15467
|
+
return node.value;
|
|
15468
|
+
case "array":
|
|
15469
|
+
return node.items.map(walk);
|
|
15470
|
+
case "ident": {
|
|
15471
|
+
if (!(node.name in globals)) throw new ExprError(`Unknown name "${node.name}"`, node.pos);
|
|
15472
|
+
return globals[node.name];
|
|
15473
|
+
}
|
|
15474
|
+
case "un": {
|
|
15475
|
+
const v = walk(node.arg);
|
|
15476
|
+
if (node.op === "-") return -num4(v);
|
|
15477
|
+
if (node.op === "+") return num4(v);
|
|
15478
|
+
return truthy(v) ? 0 : 1;
|
|
15479
|
+
}
|
|
15480
|
+
case "bin": {
|
|
15481
|
+
const op = node.op;
|
|
15482
|
+
if (op === "&&") {
|
|
15483
|
+
const l2 = walk(node.left);
|
|
15484
|
+
return truthy(l2) ? walk(node.right) : l2;
|
|
15485
|
+
}
|
|
15486
|
+
if (op === "||") {
|
|
15487
|
+
const l2 = walk(node.left);
|
|
15488
|
+
return truthy(l2) ? l2 : walk(node.right);
|
|
15489
|
+
}
|
|
15490
|
+
const l = walk(node.left);
|
|
15491
|
+
const r = walk(node.right);
|
|
15492
|
+
switch (op) {
|
|
15493
|
+
// `+` concatenates when either side is a string, like JS.
|
|
15494
|
+
case "+":
|
|
15495
|
+
return typeof l === "string" || typeof r === "string" ? `${l}${r}` : num4(l) + num4(r);
|
|
15496
|
+
case "-":
|
|
15497
|
+
return num4(l) - num4(r);
|
|
15498
|
+
case "*":
|
|
15499
|
+
return num4(l) * num4(r);
|
|
15500
|
+
case "/": {
|
|
15501
|
+
const d = num4(r);
|
|
15502
|
+
return d === 0 ? 0 : num4(l) / d;
|
|
15503
|
+
}
|
|
15504
|
+
case "%": {
|
|
15505
|
+
const d = num4(r);
|
|
15506
|
+
return d === 0 ? 0 : num4(l) % d;
|
|
15507
|
+
}
|
|
15508
|
+
case "^":
|
|
15509
|
+
case "**":
|
|
15510
|
+
return Math.pow(num4(l), num4(r));
|
|
15511
|
+
case "<":
|
|
15512
|
+
return num4(l) < num4(r) ? 1 : 0;
|
|
15513
|
+
case ">":
|
|
15514
|
+
return num4(l) > num4(r) ? 1 : 0;
|
|
15515
|
+
case "<=":
|
|
15516
|
+
return num4(l) <= num4(r) ? 1 : 0;
|
|
15517
|
+
case ">=":
|
|
15518
|
+
return num4(l) >= num4(r) ? 1 : 0;
|
|
15519
|
+
case "==":
|
|
15520
|
+
case "===":
|
|
15521
|
+
return l === r ? 1 : 0;
|
|
15522
|
+
case "!=":
|
|
15523
|
+
case "!==":
|
|
15524
|
+
return l !== r ? 1 : 0;
|
|
15525
|
+
default:
|
|
15526
|
+
throw new ExprError(`Unknown operator "${op}"`, node.pos);
|
|
15527
|
+
}
|
|
15528
|
+
}
|
|
15529
|
+
case "cond":
|
|
15530
|
+
return truthy(walk(node.test)) ? walk(node.then) : walk(node.other);
|
|
15531
|
+
case "index": {
|
|
15532
|
+
const target = walk(node.target);
|
|
15533
|
+
const idx = walk(node.index);
|
|
15534
|
+
if (Array.isArray(target)) return target[Math.trunc(num4(idx))] ?? 0;
|
|
15535
|
+
if (target && typeof target === "object" && target.__layer) return target.__layer.get(String(idx));
|
|
15536
|
+
throw new ExprError("Only layers and lists can be indexed", node.pos);
|
|
15537
|
+
}
|
|
15538
|
+
case "member": {
|
|
15539
|
+
const target = walk(node.target);
|
|
15540
|
+
if (target && typeof target === "object") {
|
|
15541
|
+
if (target.__layer) {
|
|
15542
|
+
if (node.name === "layer") {
|
|
15543
|
+
return (inner) => {
|
|
15544
|
+
const child = target.__layer.child?.(String(inner));
|
|
15545
|
+
if (!child) throw new ExprError(`Cannot read layers inside ${JSON.stringify(String(inner))} here`, node.pos);
|
|
15546
|
+
return layerRef(child);
|
|
15547
|
+
};
|
|
15548
|
+
}
|
|
15549
|
+
return target.__layer.get(node.name);
|
|
15550
|
+
}
|
|
15551
|
+
if (node.name in target) return target[node.name];
|
|
15552
|
+
throw new ExprError(`Unknown property "${node.name}"`, node.pos);
|
|
15553
|
+
}
|
|
15554
|
+
if (Array.isArray(target)) return node.name === "length" ? target.length : 0;
|
|
15555
|
+
throw new ExprError(`"${node.name}" is not available here`, node.pos);
|
|
15556
|
+
}
|
|
15557
|
+
case "call": {
|
|
15558
|
+
const fn = walk(node.callee);
|
|
15559
|
+
if (typeof fn !== "function") throw new ExprError("Not a function", node.pos);
|
|
15560
|
+
const args = node.args.map(walk);
|
|
15561
|
+
return fn(...args);
|
|
15562
|
+
}
|
|
15563
|
+
default:
|
|
15564
|
+
throw new ExprError("Unsupported expression", node.pos);
|
|
15565
|
+
}
|
|
15566
|
+
};
|
|
15567
|
+
const out = walk(ast);
|
|
15568
|
+
return out;
|
|
15569
|
+
}
|
|
15570
|
+
function runExpression(src, ctx) {
|
|
15571
|
+
const { ast, error } = compileExpression(src);
|
|
15572
|
+
if (error) return { value: ctx.value, error: error.message };
|
|
15573
|
+
try {
|
|
15574
|
+
const raw = evaluateAst(ast, ctx);
|
|
15575
|
+
const v = typeof raw === "number" ? raw : Number.parseFloat(raw);
|
|
15576
|
+
if (!Number.isFinite(v)) return { value: ctx.value, error: "Result is not a number" };
|
|
15577
|
+
return { value: v, error: null };
|
|
15578
|
+
} catch (err) {
|
|
15579
|
+
return { value: ctx.value, error: err instanceof ExprError ? err.message : String(err?.message || err) };
|
|
15580
|
+
}
|
|
15581
|
+
}
|
|
14476
15582
|
|
|
14477
15583
|
// src/components/videoEditor/textPlaceholders.js
|
|
14478
15584
|
init_define_import_meta_env();
|
|
@@ -14493,14 +15599,116 @@ var COUNTER_DEFAULTS = Object.freeze({
|
|
|
14493
15599
|
textCounterDirection: "auto",
|
|
14494
15600
|
textCounterBlur: 35
|
|
14495
15601
|
});
|
|
15602
|
+
var MAX_COLUMNS = 15;
|
|
14496
15603
|
var RANGE_SEP = "\\s*(?:\\.\\.|\u2192|\u2013|\u2014|to|-)\\s*";
|
|
14497
15604
|
var NUM_SRC = "[-\u2212]?\\d[\\d,]*(?:\\.\\d+)?";
|
|
14498
15605
|
var COUNTER_RE = new RegExp(`^(${NUM_SRC})(?:${RANGE_SEP}(${NUM_SRC}))?$`, "i");
|
|
14499
15606
|
var TIME_SRC = "\\d+(?::\\d+){0,2}(?:\\.\\d+)?";
|
|
14500
15607
|
var TIMER_RE = new RegExp(`^(${TIME_SRC})(?:${RANGE_SEP}(${TIME_SRC}))?$`, "i");
|
|
15608
|
+
function digitCount(n2) {
|
|
15609
|
+
return String(Math.floor(Math.abs(n2))).length;
|
|
15610
|
+
}
|
|
15611
|
+
function parseLiteral(raw) {
|
|
15612
|
+
const s = raw.replace(/−/g, "-");
|
|
15613
|
+
const neg = s[0] === "-";
|
|
15614
|
+
const body = neg ? s.slice(1) : s;
|
|
15615
|
+
const dot = body.indexOf(".");
|
|
15616
|
+
const intRaw = dot >= 0 ? body.slice(0, dot) : body;
|
|
15617
|
+
const frac = dot >= 0 ? body.slice(dot + 1) : "";
|
|
15618
|
+
const intDigits = intRaw.replace(/,/g, "") || "0";
|
|
15619
|
+
return {
|
|
15620
|
+
value: Number(`${neg ? "-" : ""}${intDigits}${frac ? `.${frac}` : ""}`),
|
|
15621
|
+
decimals: frac.length,
|
|
15622
|
+
sep: intRaw.includes(","),
|
|
15623
|
+
pad: intDigits.length > 1 && intDigits[0] === "0" ? intDigits.length : 0
|
|
15624
|
+
};
|
|
15625
|
+
}
|
|
15626
|
+
function parseCounterRange(body) {
|
|
15627
|
+
const m = COUNTER_RE.exec(String(body ?? "").trim());
|
|
15628
|
+
if (!m) return null;
|
|
15629
|
+
const single = m[2] == null;
|
|
15630
|
+
const a = single ? { value: 0, decimals: 0, sep: false, pad: 0 } : parseLiteral(m[1]);
|
|
15631
|
+
const b2 = parseLiteral(single ? m[1] : m[2]);
|
|
15632
|
+
if (!Number.isFinite(a.value) || !Number.isFinite(b2.value)) return null;
|
|
15633
|
+
const decimals = Math.min(4, Math.max(a.decimals, b2.decimals));
|
|
15634
|
+
const scale = 10 ** decimals;
|
|
15635
|
+
const pad = Math.max(a.pad, b2.pad);
|
|
15636
|
+
const from = Math.round(a.value * scale);
|
|
15637
|
+
const to = Math.round(b2.value * scale);
|
|
15638
|
+
const intCols = Math.max(1, pad, digitCount(Math.abs(from) / scale), digitCount(Math.abs(to) / scale));
|
|
15639
|
+
if (intCols + decimals > MAX_COLUMNS) return null;
|
|
15640
|
+
const cols = [];
|
|
15641
|
+
for (let j = 0; j < intCols + decimals; j += 1) {
|
|
15642
|
+
cols.push({ place: 10 ** j, radix: 10, lead: j - decimals >= Math.max(1, pad) });
|
|
15643
|
+
}
|
|
15644
|
+
const sepRight = {};
|
|
15645
|
+
if (decimals > 0) sepRight[decimals] = ".";
|
|
15646
|
+
if (a.sep || b2.sep) for (let i = 3; i < intCols; i += 3) sepRight[decimals + i] = ",";
|
|
15647
|
+
return { kind: "counter", from, to, scale, cols, sepRight, signed: from < 0 || to < 0 };
|
|
15648
|
+
}
|
|
15649
|
+
function parseTime(raw) {
|
|
15650
|
+
const dot = raw.indexOf(".");
|
|
15651
|
+
const main = dot >= 0 ? raw.slice(0, dot) : raw;
|
|
15652
|
+
const frac = dot >= 0 ? raw.slice(dot + 1) : "";
|
|
15653
|
+
const parts = main.split(":");
|
|
15654
|
+
for (let i = 1; i < parts.length; i += 1) {
|
|
15655
|
+
if (parts[i].length > 2 || Number(parts[i]) >= 60) return null;
|
|
15656
|
+
}
|
|
15657
|
+
let seconds = 0;
|
|
15658
|
+
for (const p of parts) seconds = seconds * 60 + Number(p);
|
|
15659
|
+
return { parts, frac, seconds: seconds + (frac ? Number(`0.${frac}`) : 0) };
|
|
15660
|
+
}
|
|
15661
|
+
function parseTimerRange(body) {
|
|
15662
|
+
const m = TIMER_RE.exec(String(body ?? "").trim());
|
|
15663
|
+
if (!m) return null;
|
|
15664
|
+
const a = parseTime(m[1]);
|
|
15665
|
+
const b2 = m[2] != null ? parseTime(m[2]) : { parts: ["0"], frac: "", seconds: 0 };
|
|
15666
|
+
if (!a || !b2) return null;
|
|
15667
|
+
const fields = Math.max(a.parts.length, b2.parts.length);
|
|
15668
|
+
const decimals = Math.min(3, Math.max(a.frac.length, b2.frac.length));
|
|
15669
|
+
const S = 10 ** decimals;
|
|
15670
|
+
const from = Math.round(a.seconds * S);
|
|
15671
|
+
const to = Math.round(b2.seconds * S);
|
|
15672
|
+
const topPlace = fields === 3 ? 3600 * S : fields === 2 ? 60 * S : S;
|
|
15673
|
+
const typedTop = [a, b2].filter((t) => t.parts.length === fields).map((t) => t.parts[0]);
|
|
15674
|
+
const pad = Math.max(0, ...typedTop.map((p) => p.length > 1 && p[0] === "0" ? p.length : 0));
|
|
15675
|
+
const topDigits = Math.max(1, pad, digitCount(Math.floor(from / topPlace)), digitCount(Math.floor(to / topPlace)));
|
|
15676
|
+
const cols = [];
|
|
15677
|
+
for (let k = 0; k < decimals; k += 1) cols.push({ place: 10 ** k, radix: 10, lead: false });
|
|
15678
|
+
const clockField = (place) => {
|
|
15679
|
+
cols.push({ place, radix: 10, lead: false });
|
|
15680
|
+
cols.push({ place: place * 10, radix: 6, lead: false });
|
|
15681
|
+
};
|
|
15682
|
+
if (fields >= 2) clockField(S);
|
|
15683
|
+
if (fields === 3) clockField(60 * S);
|
|
15684
|
+
for (let k = 0; k < topDigits; k += 1) {
|
|
15685
|
+
cols.push({ place: topPlace * 10 ** k, radix: 10, lead: k >= Math.max(1, pad) });
|
|
15686
|
+
}
|
|
15687
|
+
if (cols.length > MAX_COLUMNS) return null;
|
|
15688
|
+
const sepRight = {};
|
|
15689
|
+
if (decimals > 0) sepRight[decimals] = ".";
|
|
15690
|
+
if (fields >= 2) sepRight[decimals + 2] = ":";
|
|
15691
|
+
if (fields === 3) sepRight[decimals + 4] = ":";
|
|
15692
|
+
return { kind: "timer", from, to, scale: S, cols, sepRight, signed: false };
|
|
15693
|
+
}
|
|
15694
|
+
function formatUnits(units, spec) {
|
|
15695
|
+
let out = "";
|
|
15696
|
+
for (let j = spec.cols.length - 1; j >= 0; j -= 1) {
|
|
15697
|
+
const c = spec.cols[j];
|
|
15698
|
+
if (c.lead && units < c.place) continue;
|
|
15699
|
+
out += String(Math.floor(units / c.place) % c.radix);
|
|
15700
|
+
if (j > 0 && spec.sepRight[j]) out += spec.sepRight[j];
|
|
15701
|
+
}
|
|
15702
|
+
return out;
|
|
15703
|
+
}
|
|
15704
|
+
function formatCounterValue(units, spec) {
|
|
15705
|
+
const u = Math.abs(Math.round(units));
|
|
15706
|
+
return `${units < 0 && u > 0 ? "-" : ""}${formatUnits(u, spec)}`;
|
|
15707
|
+
}
|
|
14501
15708
|
var SHUTTER_SEC = 1 / 60;
|
|
14502
15709
|
|
|
14503
15710
|
// src/components/videoEditor/textPlaceholders.js
|
|
15711
|
+
var TOKEN_RE = /\{\{\{\s*([A-Za-z]+)\s*(?::([^{}]*?))?\s*\}\}\}/g;
|
|
14504
15712
|
var PLACEHOLDER_CATALOG = Object.freeze([
|
|
14505
15713
|
{
|
|
14506
15714
|
id: "COUNTER",
|
|
@@ -14551,6 +15759,7 @@ var PLACEHOLDER_CATALOG = Object.freeze([
|
|
|
14551
15759
|
description: "The current frame number. {{{FRAME:clip}}} counts from the layer's start."
|
|
14552
15760
|
}
|
|
14553
15761
|
]);
|
|
15762
|
+
var clamp3 = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
14554
15763
|
var CYCLE_DEFAULTS = Object.freeze({
|
|
14555
15764
|
textCycleStyle: "roll",
|
|
14556
15765
|
textCycleDelay: 0,
|
|
@@ -14576,6 +15785,9 @@ var SCRAMBLE_DEFAULTS = Object.freeze({
|
|
|
14576
15785
|
textScrambleOrder: "ltr",
|
|
14577
15786
|
textScrambleRate: 18
|
|
14578
15787
|
});
|
|
15788
|
+
var TIMECODE_FORMATS = ["hh:mm:ss:ff", "hh:mm:ss", "mm:ss:ff", "mm:ss", "ss:ff", "ss"];
|
|
15789
|
+
var EASING_VALUE_RE = /^[a-z][a-z0-9_]{0,31}$/;
|
|
15790
|
+
var KF_ID_RE = /^[a-z0-9]{1,8}$/;
|
|
14579
15791
|
var PLACEHOLDER_OPTIONS = Object.freeze({
|
|
14580
15792
|
number: {
|
|
14581
15793
|
style: { values: ["odometer", "roll", "count"] },
|
|
@@ -14610,6 +15822,81 @@ var PLACEHOLDER_OPTIONS = Object.freeze({
|
|
|
14610
15822
|
},
|
|
14611
15823
|
readout: {}
|
|
14612
15824
|
});
|
|
15825
|
+
function cleanOption(schema, raw) {
|
|
15826
|
+
const v = String(raw ?? "").trim().toLowerCase();
|
|
15827
|
+
if (!schema || !v) return null;
|
|
15828
|
+
if (schema.values) return schema.values.includes(v) ? v : null;
|
|
15829
|
+
if (schema.easing) return EASING_VALUE_RE.test(v) ? v : null;
|
|
15830
|
+
if (schema.kf) return KF_ID_RE.test(v) ? v : null;
|
|
15831
|
+
const n2 = Number(v);
|
|
15832
|
+
if (!Number.isFinite(n2)) return null;
|
|
15833
|
+
const c = clamp3(n2, schema.num[0], schema.num[1]);
|
|
15834
|
+
return schema.int ? Math.round(c) : Number(c.toFixed(3));
|
|
15835
|
+
}
|
|
15836
|
+
function splitBody(rawBody) {
|
|
15837
|
+
const parts = String(rawBody ?? "").split(";");
|
|
15838
|
+
const main = parts[0].trim();
|
|
15839
|
+
const options = {};
|
|
15840
|
+
for (const part of parts.slice(1)) {
|
|
15841
|
+
const eq = part.indexOf("=");
|
|
15842
|
+
if (eq < 0) continue;
|
|
15843
|
+
const key = part.slice(0, eq).trim().toLowerCase();
|
|
15844
|
+
if (key) options[key] = part.slice(eq + 1).trim();
|
|
15845
|
+
}
|
|
15846
|
+
return { main, options };
|
|
15847
|
+
}
|
|
15848
|
+
function parseReadoutArgs(body) {
|
|
15849
|
+
const words2 = String(body || "").toLowerCase().split(/[\s,]+/).filter(Boolean);
|
|
15850
|
+
const format = words2.find((w) => TIMECODE_FORMATS.includes(w)) || null;
|
|
15851
|
+
const clip = words2.includes("clip");
|
|
15852
|
+
if (words2.some((w) => w !== "clip" && w !== "project" && !TIMECODE_FORMATS.includes(w))) return null;
|
|
15853
|
+
return { clip, format };
|
|
15854
|
+
}
|
|
15855
|
+
function parseUnit(type, rawBody) {
|
|
15856
|
+
const { main, options: rawOptions } = splitBody(rawBody);
|
|
15857
|
+
let unit = null;
|
|
15858
|
+
switch (type) {
|
|
15859
|
+
case "COUNTER": {
|
|
15860
|
+
const spec = parseCounterRange(main);
|
|
15861
|
+
unit = spec ? { type: "number", spec } : null;
|
|
15862
|
+
break;
|
|
15863
|
+
}
|
|
15864
|
+
case "TIMER": {
|
|
15865
|
+
const spec = parseTimerRange(main);
|
|
15866
|
+
unit = spec ? { type: "number", spec } : null;
|
|
15867
|
+
break;
|
|
15868
|
+
}
|
|
15869
|
+
case "CYCLE": {
|
|
15870
|
+
const words2 = main.split("|").map((w) => w.trim()).filter(Boolean);
|
|
15871
|
+
unit = words2.length ? { type: "cycle", words: words2 } : null;
|
|
15872
|
+
break;
|
|
15873
|
+
}
|
|
15874
|
+
case "SCRAMBLE":
|
|
15875
|
+
unit = main ? { type: "scramble", text: main } : null;
|
|
15876
|
+
break;
|
|
15877
|
+
case "TIMECODE": {
|
|
15878
|
+
const args = parseReadoutArgs(main);
|
|
15879
|
+
unit = args ? { type: "readout", mode: "timecode", clip: args.clip, format: args.format || "hh:mm:ss:ff" } : null;
|
|
15880
|
+
break;
|
|
15881
|
+
}
|
|
15882
|
+
case "FRAME": {
|
|
15883
|
+
const args = parseReadoutArgs(main);
|
|
15884
|
+
unit = args && !args.format ? { type: "readout", mode: "frame", clip: args.clip } : null;
|
|
15885
|
+
break;
|
|
15886
|
+
}
|
|
15887
|
+
default:
|
|
15888
|
+
return null;
|
|
15889
|
+
}
|
|
15890
|
+
if (!unit) return null;
|
|
15891
|
+
const schema = PLACEHOLDER_OPTIONS[unit.type];
|
|
15892
|
+
const options = {};
|
|
15893
|
+
for (const [k, v] of Object.entries(rawOptions)) {
|
|
15894
|
+
const clean = cleanOption(schema[k], v);
|
|
15895
|
+
if (clean !== null) options[k] = clean;
|
|
15896
|
+
}
|
|
15897
|
+
return { ...unit, tokenType: type, main, options };
|
|
15898
|
+
}
|
|
15899
|
+
var hasToken = (text) => typeof text === "string" && text.indexOf("{{{") >= 0;
|
|
14613
15900
|
var PLACEHOLDER_LABELS = Object.freeze({
|
|
14614
15901
|
COUNTER: "Counter",
|
|
14615
15902
|
TIMER: "Timer",
|
|
@@ -14618,6 +15905,55 @@ var PLACEHOLDER_LABELS = Object.freeze({
|
|
|
14618
15905
|
TIMECODE: "Timecode",
|
|
14619
15906
|
FRAME: "Frame number"
|
|
14620
15907
|
});
|
|
15908
|
+
function findVariable(variables, name) {
|
|
15909
|
+
if (!Array.isArray(variables)) return null;
|
|
15910
|
+
const key = name.toLowerCase();
|
|
15911
|
+
return variables.find((v) => String(v?.name || "").toLowerCase() === key) || null;
|
|
15912
|
+
}
|
|
15913
|
+
function formatTimecode(sec, fps, format = "hh:mm:ss:ff") {
|
|
15914
|
+
const f2 = Math.max(1, Math.round(Number(fps) || 30));
|
|
15915
|
+
const frames = Math.max(0, Math.floor(sec * f2 + 1e-6));
|
|
15916
|
+
const ff = frames % f2;
|
|
15917
|
+
const total = Math.floor(frames / f2);
|
|
15918
|
+
const ss = total % 60;
|
|
15919
|
+
const mm = Math.floor(total / 60) % 60;
|
|
15920
|
+
const hh = Math.floor(total / 3600);
|
|
15921
|
+
const p2 = (n2) => String(n2).padStart(2, "0");
|
|
15922
|
+
const pf = (n2) => String(n2).padStart(String(f2 - 1).length, "0");
|
|
15923
|
+
switch (format) {
|
|
15924
|
+
case "hh:mm:ss":
|
|
15925
|
+
return `${p2(hh)}:${p2(mm)}:${p2(ss)}`;
|
|
15926
|
+
case "mm:ss:ff":
|
|
15927
|
+
return `${p2(Math.floor(total / 60))}:${p2(ss)}:${pf(ff)}`;
|
|
15928
|
+
case "mm:ss":
|
|
15929
|
+
return `${p2(Math.floor(total / 60))}:${p2(ss)}`;
|
|
15930
|
+
case "ss:ff":
|
|
15931
|
+
return `${p2(total)}:${pf(ff)}`;
|
|
15932
|
+
case "ss":
|
|
15933
|
+
return p2(total);
|
|
15934
|
+
default:
|
|
15935
|
+
return `${p2(hh)}:${p2(mm)}:${p2(ss)}:${pf(ff)}`;
|
|
15936
|
+
}
|
|
15937
|
+
}
|
|
15938
|
+
function resolvePlaceholderText(text, env = {}) {
|
|
15939
|
+
if (!hasToken(text)) return text;
|
|
15940
|
+
const expanded = text.replace(TOKEN_RE, (whole, rawType, body) => {
|
|
15941
|
+
const type = rawType.toUpperCase();
|
|
15942
|
+
if (type === "VAR") {
|
|
15943
|
+
const name = String(body || "").trim();
|
|
15944
|
+
const v = name ? findVariable(env.variables, name) : null;
|
|
15945
|
+
return v && typeof v.value === "string" ? v.value : name || whole;
|
|
15946
|
+
}
|
|
15947
|
+
if (type === "MARKER") return String(body || "").trim() || "Marker";
|
|
15948
|
+
const unit = parseUnit(type, body);
|
|
15949
|
+
if (!unit) return whole;
|
|
15950
|
+
if (unit.type === "number") return formatCounterValue(unit.spec.to, unit.spec);
|
|
15951
|
+
if (unit.type === "cycle") return unit.words[0];
|
|
15952
|
+
if (unit.type === "scramble") return unit.text;
|
|
15953
|
+
return unit.mode === "frame" ? "0" : formatTimecode(0, env.fps, unit.format);
|
|
15954
|
+
});
|
|
15955
|
+
return expanded;
|
|
15956
|
+
}
|
|
14621
15957
|
|
|
14622
15958
|
// src/components/videoEditor/keyframeFx.js
|
|
14623
15959
|
var KEYFRAME_FX_TYPES = [
|
|
@@ -14735,11 +16071,308 @@ function sanitizeKeyframeFxMap(raw, propertyKeyframes = null) {
|
|
|
14735
16071
|
}
|
|
14736
16072
|
return out;
|
|
14737
16073
|
}
|
|
16074
|
+
function hasAnyKeyframeFx(map) {
|
|
16075
|
+
return !!map && typeof map === "object" && Object.keys(map).length > 0;
|
|
16076
|
+
}
|
|
16077
|
+
function numOr(v, fallback) {
|
|
16078
|
+
const n2 = Number.parseFloat(v);
|
|
16079
|
+
return Number.isFinite(n2) ? n2 : fallback;
|
|
16080
|
+
}
|
|
16081
|
+
function elasticProfile(u, wobbles, decay) {
|
|
16082
|
+
if (u <= 0) return 0;
|
|
16083
|
+
if (decay * u > 6.9) return 1;
|
|
16084
|
+
return 1 - Math.exp(-decay * u) * Math.cos(2 * Math.PI * wobbles * u);
|
|
16085
|
+
}
|
|
16086
|
+
function bounceProfile(u, e2, nMax) {
|
|
16087
|
+
if (u <= 0) return 0;
|
|
16088
|
+
const hopSum = 2 * e2 * (1 - Math.pow(e2, nMax)) / (1 - e2);
|
|
16089
|
+
const T0 = 1 / (1 + hopSum);
|
|
16090
|
+
if (u >= 1) return 1;
|
|
16091
|
+
if (u < T0) {
|
|
16092
|
+
const r = u / T0;
|
|
16093
|
+
return r * r;
|
|
16094
|
+
}
|
|
16095
|
+
const g = 2 / (T0 * T0);
|
|
16096
|
+
const v = g * T0;
|
|
16097
|
+
let vk = v * e2;
|
|
16098
|
+
let hop = 2 * vk / g;
|
|
16099
|
+
let tb = u - T0;
|
|
16100
|
+
let nb = 1;
|
|
16101
|
+
while (tb > hop && nb <= nMax) {
|
|
16102
|
+
tb -= hop;
|
|
16103
|
+
vk *= e2;
|
|
16104
|
+
hop *= e2;
|
|
16105
|
+
nb += 1;
|
|
16106
|
+
}
|
|
16107
|
+
if (nb > nMax) return 1;
|
|
16108
|
+
return 1 - (vk * tb - g * tb * tb / 2);
|
|
16109
|
+
}
|
|
16110
|
+
function profiledValue(track, pct, base, durationSec, strength, profile) {
|
|
16111
|
+
if (track.length < 2 || !(strength > 0)) return base;
|
|
16112
|
+
const t = pct / 100 * durationSec;
|
|
16113
|
+
let value = numOr(track[0].value, NaN);
|
|
16114
|
+
if (!Number.isFinite(value)) return base;
|
|
16115
|
+
for (let i = 1; i < track.length; i += 1) {
|
|
16116
|
+
const prev = numOr(track[i - 1].value, NaN);
|
|
16117
|
+
const cur = numOr(track[i].value, NaN);
|
|
16118
|
+
if (!Number.isFinite(prev) || !Number.isFinite(cur)) return base;
|
|
16119
|
+
const step = cur - prev;
|
|
16120
|
+
if (step === 0) continue;
|
|
16121
|
+
const tPrev = Number(track[i - 1].position) / 100 * durationSec;
|
|
16122
|
+
const segSec = Number(track[i].position) / 100 * durationSec - tPrev;
|
|
16123
|
+
if (!(segSec > 0)) continue;
|
|
16124
|
+
value += step * profile((t - tPrev) / segSec);
|
|
16125
|
+
}
|
|
16126
|
+
return base + strength * (value - base);
|
|
16127
|
+
}
|
|
16128
|
+
function elasticValue(cfg, track, pct, base, durationSec) {
|
|
16129
|
+
const { amp, freq, decay } = cfg.params;
|
|
16130
|
+
return profiledValue(track, pct, base, durationSec, amp / 100, (u) => elasticProfile(u, freq, decay));
|
|
16131
|
+
}
|
|
16132
|
+
function bounceValue(cfg, track, pct, base, durationSec) {
|
|
16133
|
+
const { amp, elasticity, bounces } = cfg.params;
|
|
16134
|
+
const e2 = Math.min(0.95, Math.max(0.01, elasticity / 100));
|
|
16135
|
+
const nMax = Math.max(1, Math.round(bounces));
|
|
16136
|
+
return profiledValue(track, pct, base, durationSec, amp / 100, (u) => bounceProfile(u, e2, nMax));
|
|
16137
|
+
}
|
|
16138
|
+
function wiggleOffset(cfg, tSec, seed) {
|
|
16139
|
+
const amp = cfg.params.amp;
|
|
16140
|
+
if (!(amp > 0)) return 0;
|
|
16141
|
+
return amp * smoothNoise(tSec * cfg.params.freq, seed);
|
|
16142
|
+
}
|
|
16143
|
+
var scene = null;
|
|
16144
|
+
var layerNameCache = /* @__PURE__ */ new WeakMap();
|
|
16145
|
+
function layersByName() {
|
|
16146
|
+
if (!scene?.index) return null;
|
|
16147
|
+
const hit = layerNameCache.get(scene.index);
|
|
16148
|
+
if (hit) return hit;
|
|
16149
|
+
const map = /* @__PURE__ */ new Map();
|
|
16150
|
+
for (const entry of scene.index.values()) {
|
|
16151
|
+
const name = String(scene.nameOf?.(entry) || "").trim().toLowerCase();
|
|
16152
|
+
if (name && !map.has(name)) map.set(name, entry);
|
|
16153
|
+
}
|
|
16154
|
+
layerNameCache.set(scene.index, map);
|
|
16155
|
+
return map;
|
|
16156
|
+
}
|
|
16157
|
+
function staticPropValue(item, prop) {
|
|
16158
|
+
switch (prop) {
|
|
16159
|
+
case "opacity":
|
|
16160
|
+
return (Number(item.opacity) || 0) / 100;
|
|
16161
|
+
case "scale":
|
|
16162
|
+
return Number(item.zoom) || 1;
|
|
16163
|
+
case "scaleX":
|
|
16164
|
+
return Number(item.scaleX) || 1;
|
|
16165
|
+
case "scaleY":
|
|
16166
|
+
return Number(item.scaleY) || 1;
|
|
16167
|
+
case "rotate":
|
|
16168
|
+
case "rotateZ":
|
|
16169
|
+
return Number(item.rotateZ ?? item.rotation) || 0;
|
|
16170
|
+
case "rotateX":
|
|
16171
|
+
return Number(item.rotateX) || 0;
|
|
16172
|
+
case "rotateY":
|
|
16173
|
+
return Number(item.rotateY) || 0;
|
|
16174
|
+
case "xPercent":
|
|
16175
|
+
return Number(item.panX) || 0;
|
|
16176
|
+
case "yPercent":
|
|
16177
|
+
return Number(item.panY) || 0;
|
|
16178
|
+
case "translateZ":
|
|
16179
|
+
return Number(item.panZ) || 0;
|
|
16180
|
+
case "motionPathT":
|
|
16181
|
+
return Number(item.motionPathT) || 0;
|
|
16182
|
+
default:
|
|
16183
|
+
return 0;
|
|
16184
|
+
}
|
|
16185
|
+
}
|
|
16186
|
+
var evalDepth = 0;
|
|
16187
|
+
var MAX_LAYER_DEPTH = 2;
|
|
14738
16188
|
var KNOWN_PROP_NAMES = new Set(KF_PROP_LABELS);
|
|
16189
|
+
var innerIndexCache = /* @__PURE__ */ new WeakMap();
|
|
16190
|
+
function innerIndexOf(clip) {
|
|
16191
|
+
const channels = clip?.sequence?.channels;
|
|
16192
|
+
if (!Array.isArray(channels)) return null;
|
|
16193
|
+
const hit = innerIndexCache.get(channels);
|
|
16194
|
+
if (hit) return hit;
|
|
16195
|
+
const clipsMap = new Map((clip.sequence.clips || []).map((c) => [c.id, c]));
|
|
16196
|
+
const byName = /* @__PURE__ */ new Map();
|
|
16197
|
+
channels.forEach((channel) => {
|
|
16198
|
+
const layout = getTimelineLayout(channel.items || [], { unsnapped: channel.unsnapped });
|
|
16199
|
+
(channel.items || []).forEach((item, i) => {
|
|
16200
|
+
const span = layout[i];
|
|
16201
|
+
if (!span) return;
|
|
16202
|
+
const inner = clipsMap.get(item.clipId);
|
|
16203
|
+
const name = String(
|
|
16204
|
+
item.customName || (inner?.mediaType === "text" ? resolvePlaceholderText(item.textContent) || inner?.name : inner?.name) || ""
|
|
16205
|
+
).trim().toLowerCase();
|
|
16206
|
+
if (!name || byName.has(name)) return;
|
|
16207
|
+
byName.set(name, {
|
|
16208
|
+
item,
|
|
16209
|
+
clip: inner,
|
|
16210
|
+
startTime: span.startTime,
|
|
16211
|
+
duration: Math.max(0, span.endTime - span.startTime)
|
|
16212
|
+
});
|
|
16213
|
+
});
|
|
16214
|
+
});
|
|
16215
|
+
innerIndexCache.set(channels, byName);
|
|
16216
|
+
return byName;
|
|
16217
|
+
}
|
|
16218
|
+
function containerInnerTime(item, outerLocal) {
|
|
16219
|
+
const trimStart = Number(item.trimStart) || 0;
|
|
16220
|
+
const sourceDuration = (Number(item.trimEnd) || 0) - trimStart;
|
|
16221
|
+
if (!(sourceDuration > 0)) return Math.max(0, outerLocal);
|
|
16222
|
+
const timelineDuration = getTimelineDuration(sourceDuration, item.speedPoints);
|
|
16223
|
+
const normalized = timelineDuration > 0 ? Math.max(0, Math.min(1, outerLocal / timelineDuration)) : 0;
|
|
16224
|
+
return trimStart + mapTimelineToSource(item.speedPoints, normalized) * sourceDuration;
|
|
16225
|
+
}
|
|
16226
|
+
function layerReader(entry, nowOf, label) {
|
|
16227
|
+
if (!entry) return null;
|
|
16228
|
+
const localTime = () => Math.max(0, (Number(nowOf()) || 0) - (Number(entry.startTime) || 0));
|
|
16229
|
+
return {
|
|
16230
|
+
get(prop) {
|
|
16231
|
+
const item = entry.item;
|
|
16232
|
+
if (!item) return 0;
|
|
16233
|
+
const dur = Number(entry.duration) || 0;
|
|
16234
|
+
const progress = dur > 0 ? Math.max(0, Math.min(1, localTime() / dur)) : 0;
|
|
16235
|
+
if (Array.isArray(item.propertyKeyframes?.[prop]) && typeof scene?.evaluate === "function") {
|
|
16236
|
+
evalDepth += 1;
|
|
16237
|
+
try {
|
|
16238
|
+
const v = scene.evaluate(item, progress)?.[prop];
|
|
16239
|
+
const n2 = Number.parseFloat(v);
|
|
16240
|
+
if (Number.isFinite(n2)) return n2;
|
|
16241
|
+
} finally {
|
|
16242
|
+
evalDepth -= 1;
|
|
16243
|
+
}
|
|
16244
|
+
}
|
|
16245
|
+
if (!KNOWN_PROP_NAMES.has(prop)) {
|
|
16246
|
+
throw new Error(`"${label}" has no property "${prop}"`);
|
|
16247
|
+
}
|
|
16248
|
+
return staticPropValue(item, prop);
|
|
16249
|
+
},
|
|
16250
|
+
// Drill into a container layer's own timeline.
|
|
16251
|
+
child(name) {
|
|
16252
|
+
const clip = entry.clip || scene?.clipOf?.(entry.item);
|
|
16253
|
+
if (!isContainerClip(clip)) throw new Error(`"${label}" is not a sequence, so it has no layers inside it`);
|
|
16254
|
+
if (evalDepth >= MAX_LAYER_DEPTH) return null;
|
|
16255
|
+
const index = innerIndexOf(clip);
|
|
16256
|
+
const found = index?.get(String(name).trim().toLowerCase());
|
|
16257
|
+
if (!found) throw new Error(`"${label}" has no layer named ${JSON.stringify(String(name))} inside it`);
|
|
16258
|
+
const innerNow = () => containerInnerTime(entry.item, localTime());
|
|
16259
|
+
return layerReader(found, innerNow, `${label} \u203A ${name}`);
|
|
16260
|
+
}
|
|
16261
|
+
};
|
|
16262
|
+
}
|
|
16263
|
+
function readerFor(entry) {
|
|
16264
|
+
return layerReader(entry, () => Number(scene?.time) || 0, scene?.nameOf?.(entry) || "that layer");
|
|
16265
|
+
}
|
|
14739
16266
|
var trackSampler = null;
|
|
14740
16267
|
function setKeyframeTrackSampler(fn) {
|
|
14741
16268
|
if (typeof fn === "function") trackSampler = fn;
|
|
14742
16269
|
}
|
|
16270
|
+
function linearSampleTrack(track, pct) {
|
|
16271
|
+
const keys = [...track].sort((a, b2) => Number(a.position) - Number(b2.position));
|
|
16272
|
+
const first = keys[0];
|
|
16273
|
+
const last = keys[keys.length - 1];
|
|
16274
|
+
if (pct <= Number(first.position)) return numOr(first.value, 0);
|
|
16275
|
+
if (pct >= Number(last.position)) return numOr(last.value, 0);
|
|
16276
|
+
for (let i = 0; i < keys.length - 1; i += 1) {
|
|
16277
|
+
const a = keys[i];
|
|
16278
|
+
const b2 = keys[i + 1];
|
|
16279
|
+
const pa = Number(a.position);
|
|
16280
|
+
const pb = Number(b2.position);
|
|
16281
|
+
if (pct >= pa && pct <= pb) {
|
|
16282
|
+
const u = pb > pa ? (pct - pa) / (pb - pa) : 0;
|
|
16283
|
+
return numOr(a.value, 0) + (numOr(b2.value, 0) - numOr(a.value, 0)) * u;
|
|
16284
|
+
}
|
|
16285
|
+
}
|
|
16286
|
+
return numOr(last.value, 0);
|
|
16287
|
+
}
|
|
16288
|
+
function sampleTrackAt(track, pct) {
|
|
16289
|
+
const v = trackSampler ? trackSampler(track, pct) : linearSampleTrack(track, pct);
|
|
16290
|
+
return numOr(v, 0);
|
|
16291
|
+
}
|
|
16292
|
+
function glideTrackValue(cfg, track, pct, base, durationSec) {
|
|
16293
|
+
const { fast, slow, weight, delay } = cfg.params;
|
|
16294
|
+
const ctx = {
|
|
16295
|
+
value: base,
|
|
16296
|
+
time: pct / 100 * durationSec,
|
|
16297
|
+
valueAt: (tSec) => sampleTrackAt(track, tSec / durationSec * 100)
|
|
16298
|
+
};
|
|
16299
|
+
return glideValue(ctx, fast, slow, weight / 100, delay);
|
|
16300
|
+
}
|
|
16301
|
+
function expressionContext(item, track, pct, base, durationSec, seedKey) {
|
|
16302
|
+
const localTime = pct / 100 * durationSec;
|
|
16303
|
+
const fps = Number(scene?.fps) > 0 ? Number(scene.fps) : 30;
|
|
16304
|
+
const selfEntry = scene?.index?.get(item?.id) || null;
|
|
16305
|
+
const keys = Array.isArray(track) ? track : [];
|
|
16306
|
+
return {
|
|
16307
|
+
value: base,
|
|
16308
|
+
time: localTime,
|
|
16309
|
+
frame: Math.floor(localTime * fps + 1e-6),
|
|
16310
|
+
fps,
|
|
16311
|
+
duration: durationSec,
|
|
16312
|
+
progress: pct / 100,
|
|
16313
|
+
index: selfEntry?.channelIndex ?? 0,
|
|
16314
|
+
seed: stringSeed(seedKey),
|
|
16315
|
+
// This property's own keyframes, in clip-local seconds, plus a sampler —
|
|
16316
|
+
// what loopOut/loopIn/valueAtTime read.
|
|
16317
|
+
keyTimes: keys.map((k) => Number(k.position) / 100 * durationSec).filter((t) => Number.isFinite(t)).sort((a, b2) => a - b2),
|
|
16318
|
+
valueAt: (tSec) => durationSec > 0 ? sampleTrackAt(keys, tSec / durationSec * 100) : base,
|
|
16319
|
+
resolveLayer: (name) => {
|
|
16320
|
+
if (evalDepth >= MAX_LAYER_DEPTH) return null;
|
|
16321
|
+
if (typeof name === "number") {
|
|
16322
|
+
for (const entry of scene?.index?.values() || []) {
|
|
16323
|
+
if (entry.channelIndex === Math.trunc(name)) return readerFor(entry);
|
|
16324
|
+
}
|
|
16325
|
+
return null;
|
|
16326
|
+
}
|
|
16327
|
+
const found = layersByName()?.get(String(name).trim().toLowerCase());
|
|
16328
|
+
return found ? readerFor(found) : null;
|
|
16329
|
+
},
|
|
16330
|
+
resolveSelf: () => selfEntry ? readerFor(selfEntry) : null
|
|
16331
|
+
};
|
|
16332
|
+
}
|
|
16333
|
+
function evaluateKeyframeFx(cfg, track, pct, base, durationSec, seedKey = "", item = null) {
|
|
16334
|
+
if (!cfg || !Array.isArray(track) || track.length === 0) return base;
|
|
16335
|
+
if (!Number.isFinite(base) || !(durationSec > 0)) return base;
|
|
16336
|
+
switch (cfg.type) {
|
|
16337
|
+
case "elastic":
|
|
16338
|
+
return elasticValue(cfg, track, pct, base, durationSec);
|
|
16339
|
+
case "bounce":
|
|
16340
|
+
return bounceValue(cfg, track, pct, base, durationSec);
|
|
16341
|
+
case "wiggle":
|
|
16342
|
+
return base + wiggleOffset(cfg, pct / 100 * durationSec, stringSeed(seedKey));
|
|
16343
|
+
case "glide":
|
|
16344
|
+
return glideTrackValue(cfg, track, pct, base, durationSec);
|
|
16345
|
+
case "custom": {
|
|
16346
|
+
const ctx = expressionContext(item || {}, track, pct, base, durationSec, seedKey);
|
|
16347
|
+
return runExpression(cfg.params.code, ctx).value;
|
|
16348
|
+
}
|
|
16349
|
+
default:
|
|
16350
|
+
return base;
|
|
16351
|
+
}
|
|
16352
|
+
}
|
|
16353
|
+
function applyKeyframeFxToValues(item, values, progress, durationSec) {
|
|
16354
|
+
const fxMap = item?.propertyKeyframeFx;
|
|
16355
|
+
if (!values || !hasAnyKeyframeFx(fxMap)) return values;
|
|
16356
|
+
if (evalDepth >= MAX_LAYER_DEPTH) return values;
|
|
16357
|
+
let out = values;
|
|
16358
|
+
let cloned = false;
|
|
16359
|
+
const pct = progress * 100;
|
|
16360
|
+
for (const [prop, cfg] of Object.entries(fxMap)) {
|
|
16361
|
+
const track = item.propertyKeyframes?.[prop];
|
|
16362
|
+
if (!Array.isArray(track) || track.length === 0) continue;
|
|
16363
|
+
const base = Number.parseFloat(out[prop]);
|
|
16364
|
+
if (!Number.isFinite(base)) continue;
|
|
16365
|
+
const v = evaluateKeyframeFx(cfg, track, pct, base, durationSec, `${item.id || ""}|${prop}`, item);
|
|
16366
|
+
if (v !== base) {
|
|
16367
|
+
if (!cloned) {
|
|
16368
|
+
out = { ...values };
|
|
16369
|
+
cloned = true;
|
|
16370
|
+
}
|
|
16371
|
+
out[prop] = v;
|
|
16372
|
+
}
|
|
16373
|
+
}
|
|
16374
|
+
return out;
|
|
16375
|
+
}
|
|
14743
16376
|
|
|
14744
16377
|
// src/components/videoEditor/appEases.js
|
|
14745
16378
|
init_define_import_meta_env();
|
|
@@ -14762,6 +16395,17 @@ function playedEase(easingType) {
|
|
|
14762
16395
|
return parseEase(getGsapEase(easingType));
|
|
14763
16396
|
}
|
|
14764
16397
|
|
|
16398
|
+
// src/components/videoEditor/posterizeTime.js
|
|
16399
|
+
init_define_import_meta_env();
|
|
16400
|
+
var integrateRate = null;
|
|
16401
|
+
function setPosterizeRateIntegrator(fn) {
|
|
16402
|
+
integrateRate = typeof fn === "function" ? fn : null;
|
|
16403
|
+
}
|
|
16404
|
+
var PT_NUMBER_FIELDS = [
|
|
16405
|
+
{ key: "ptFps", label: "Frame Rate", min: 1, max: 60, step: 1, suffix: "fps", kf: true }
|
|
16406
|
+
];
|
|
16407
|
+
var PT_KF_KEYS = PT_NUMBER_FIELDS.filter((f2) => f2.kf).map((f2) => f2.key);
|
|
16408
|
+
|
|
14765
16409
|
// src/components/videoEditor/gsapTransitions.js
|
|
14766
16410
|
var HOLD_EASE = () => 0;
|
|
14767
16411
|
var customEaseCache = /* @__PURE__ */ new Map();
|
|
@@ -14915,7 +16559,92 @@ function evaluatePropertyTrack(track, pct, isColor = false) {
|
|
|
14915
16559
|
const easeFn = resolveKeyEase(low);
|
|
14916
16560
|
return lerpValue(low.value, high.value, easeFn(linearT), isColor);
|
|
14917
16561
|
}
|
|
16562
|
+
function evaluatePathTrack(track, pct, prop = null) {
|
|
16563
|
+
if (!Array.isArray(track) || track.length === 0) return void 0;
|
|
16564
|
+
const sorted = [...track].sort((a2, b3) => a2.position - b3.position);
|
|
16565
|
+
const read = (key) => decodePathTrackValue(prop, key?.value) || void 0;
|
|
16566
|
+
if (sorted.length === 1 || pct <= sorted[0].position) return read(sorted[0]);
|
|
16567
|
+
if (pct >= sorted[sorted.length - 1].position) return read(sorted[sorted.length - 1]);
|
|
16568
|
+
let low = sorted[0];
|
|
16569
|
+
let high = sorted[1];
|
|
16570
|
+
for (let i = 0; i < sorted.length - 1; i += 1) {
|
|
16571
|
+
if (pct >= sorted[i].position && pct <= sorted[i + 1].position) {
|
|
16572
|
+
low = sorted[i];
|
|
16573
|
+
high = sorted[i + 1];
|
|
16574
|
+
break;
|
|
16575
|
+
}
|
|
16576
|
+
}
|
|
16577
|
+
const a = read(low);
|
|
16578
|
+
const b2 = read(high);
|
|
16579
|
+
if (!a) return b2;
|
|
16580
|
+
if (!b2) return a;
|
|
16581
|
+
const range = high.position - low.position;
|
|
16582
|
+
const linearT = range > 0 ? (pct - low.position) / range : 0;
|
|
16583
|
+
const easeFn = resolveKeyEase(low);
|
|
16584
|
+
return interpolatePathTrackValues(prop, a, b2, easeFn(linearT));
|
|
16585
|
+
}
|
|
16586
|
+
function interpolatePropertyKeyframes(propertyKeyframes, progress, transformOrigin = "center") {
|
|
16587
|
+
if (!propertyKeyframes || typeof propertyKeyframes !== "object") return null;
|
|
16588
|
+
const pct = progress * 100;
|
|
16589
|
+
const values = {};
|
|
16590
|
+
let any = false;
|
|
16591
|
+
for (const [prop, track] of Object.entries(propertyKeyframes)) {
|
|
16592
|
+
const v = isPathTrackKey(prop) ? evaluatePathTrack(track, pct, prop) : evaluatePropertyTrack(track, pct, isColorTrackKey(prop));
|
|
16593
|
+
if (v !== void 0) {
|
|
16594
|
+
values[prop] = v;
|
|
16595
|
+
any = true;
|
|
16596
|
+
}
|
|
16597
|
+
}
|
|
16598
|
+
if (!any) return null;
|
|
16599
|
+
values.transformOrigin = typeof transformOrigin === "string" && transformOrigin.trim() ? transformOrigin.trim() : "center";
|
|
16600
|
+
return values;
|
|
16601
|
+
}
|
|
14918
16602
|
setKeyframeTrackSampler((track, pct) => evaluatePropertyTrack(track, pct, false));
|
|
16603
|
+
function getItemKeyframeValues(item, progress) {
|
|
16604
|
+
if (!item) return null;
|
|
16605
|
+
const values = interpolatePropertyKeyframes(item.propertyKeyframes, progress, item.keyframeTransformOrigin);
|
|
16606
|
+
if (!values || !hasAnyKeyframeFx(item.propertyKeyframeFx)) return values;
|
|
16607
|
+
const durationSec = getTimelineDuration(
|
|
16608
|
+
Math.max(0, (Number(item.trimEnd) || 0) - (Number(item.trimStart) || 0)),
|
|
16609
|
+
item.speedPoints
|
|
16610
|
+
);
|
|
16611
|
+
return applyKeyframeFxToValues(item, values, progress, durationSec);
|
|
16612
|
+
}
|
|
16613
|
+
var pathRateIntegralCache = /* @__PURE__ */ new Map();
|
|
16614
|
+
function integratePathRate(item, rateKey, staticValue, localTime, durationSec) {
|
|
16615
|
+
const track = item?.propertyKeyframes?.[rateKey];
|
|
16616
|
+
if (!Array.isArray(track) || track.length === 0 || !(durationSec > 0)) {
|
|
16617
|
+
return localTime * staticValue;
|
|
16618
|
+
}
|
|
16619
|
+
const cacheId = `${item.id}|${rateKey}`;
|
|
16620
|
+
const key = `${JSON.stringify(track)}:${Math.round(durationSec * 1e3)}`;
|
|
16621
|
+
let entry = pathRateIntegralCache.get(cacheId);
|
|
16622
|
+
if (!entry || entry.key !== key) {
|
|
16623
|
+
const steps = Math.max(8, Math.min(3600, Math.ceil(durationSec * 30)));
|
|
16624
|
+
const cum = new Float64Array(steps + 1);
|
|
16625
|
+
const dt = durationSec / steps;
|
|
16626
|
+
const valueAt = (progress) => {
|
|
16627
|
+
const v = getItemKeyframeValues(item, progress)[rateKey];
|
|
16628
|
+
return Number.isFinite(v) ? v : staticValue;
|
|
16629
|
+
};
|
|
16630
|
+
let prev = valueAt(0);
|
|
16631
|
+
for (let i2 = 1; i2 <= steps; i2 += 1) {
|
|
16632
|
+
const v = valueAt(i2 / steps);
|
|
16633
|
+
cum[i2] = cum[i2 - 1] + (prev + v) / 2 * dt;
|
|
16634
|
+
prev = v;
|
|
16635
|
+
}
|
|
16636
|
+
entry = { key, cum, steps };
|
|
16637
|
+
pathRateIntegralCache.set(cacheId, entry);
|
|
16638
|
+
if (pathRateIntegralCache.size > 64) {
|
|
16639
|
+
const first = pathRateIntegralCache.keys().next().value;
|
|
16640
|
+
if (first !== cacheId) pathRateIntegralCache.delete(first);
|
|
16641
|
+
}
|
|
16642
|
+
}
|
|
16643
|
+
const f2 = Math.max(0, Math.min(1, localTime / durationSec)) * entry.steps;
|
|
16644
|
+
const i = Math.min(Math.floor(f2), entry.steps - 1);
|
|
16645
|
+
return entry.cum[i] + (entry.cum[i + 1] - entry.cum[i]) * (f2 - i);
|
|
16646
|
+
}
|
|
16647
|
+
setPosterizeRateIntegrator(integratePathRate);
|
|
14919
16648
|
function commonToPropertyKeyframes(keyframes, allowedProps = KF_PROP_LABELS) {
|
|
14920
16649
|
const propertyKeyframes = {};
|
|
14921
16650
|
const list = Array.isArray(keyframes) ? [...keyframes].sort((a, b2) => a.position - b2.position) : [];
|
|
@@ -15012,7 +16741,7 @@ function parseColorWithAlpha(value) {
|
|
|
15012
16741
|
}
|
|
15013
16742
|
}
|
|
15014
16743
|
try {
|
|
15015
|
-
const color = new
|
|
16744
|
+
const color = new f(str);
|
|
15016
16745
|
return { r: color.r, g: color.g, b: color.b, a: 1 };
|
|
15017
16746
|
} catch {
|
|
15018
16747
|
return null;
|
|
@@ -15050,7 +16779,7 @@ function directionsOf(preset) {
|
|
|
15050
16779
|
return preset.directions === "diagonal" ? DIAGONAL : CARDINAL;
|
|
15051
16780
|
}
|
|
15052
16781
|
var transpose = ([[a, b2], [c, d]]) => [[a, c], [b2, d]];
|
|
15053
|
-
var mul2 = ([[a, b2], [c, d]], [[e2,
|
|
16782
|
+
var mul2 = ([[a, b2], [c, d]], [[e2, f2], [g, h]]) => [[a * e2 + b2 * g, a * f2 + b2 * h], [c * e2 + d * g, c * f2 + d * h]];
|
|
15054
16783
|
var det = ([[a, b2], [c, d]]) => a * d - b2 * c;
|
|
15055
16784
|
function directionMap(baseDir, dir) {
|
|
15056
16785
|
const base = FROM_CANONICAL[baseDir];
|
|
@@ -15058,7 +16787,7 @@ function directionMap(baseDir, dir) {
|
|
|
15058
16787
|
if (!base || !target) return [[1, 0], [0, 1]];
|
|
15059
16788
|
return mul2(target, transpose(base));
|
|
15060
16789
|
}
|
|
15061
|
-
var
|
|
16790
|
+
var num5 = (v) => Number.parseFloat(v);
|
|
15062
16791
|
var fmt = (v) => {
|
|
15063
16792
|
const r = Math.round(v * 1e4) / 1e4;
|
|
15064
16793
|
return String(Object.is(r, -0) ? 0 : r);
|
|
@@ -15071,13 +16800,13 @@ function mapProperties(props, M) {
|
|
|
15071
16800
|
if (!hasX && !hasY) continue;
|
|
15072
16801
|
delete out[px];
|
|
15073
16802
|
delete out[py];
|
|
15074
|
-
const x = hasX ?
|
|
15075
|
-
const y = hasY ?
|
|
16803
|
+
const x = hasX ? num5(props[px]) : 0;
|
|
16804
|
+
const y = hasY ? num5(props[py]) : 0;
|
|
15076
16805
|
const [[a, b2], [c, d]] = M;
|
|
15077
16806
|
if (a !== 0 && hasX || b2 !== 0 && hasY) out[px] = fmt(a * x + b2 * y);
|
|
15078
16807
|
if (c !== 0 && hasX || d !== 0 && hasY) out[py] = fmt(c * x + d * y);
|
|
15079
16808
|
}
|
|
15080
|
-
if (det(M) < 0 && props.rotate != null && props.rotate !== "") out.rotate = fmt(-
|
|
16809
|
+
if (det(M) < 0 && props.rotate != null && props.rotate !== "") out.rotate = fmt(-num5(props.rotate));
|
|
15081
16810
|
return out;
|
|
15082
16811
|
}
|
|
15083
16812
|
function mapFx(fx, M) {
|
|
@@ -15950,6 +17679,966 @@ function applyKeyframePreset(item, preset, { startPct = 0, endPct = 100 } = {})
|
|
|
15950
17679
|
return out;
|
|
15951
17680
|
}
|
|
15952
17681
|
|
|
17682
|
+
// src/components/videoEditor/cameraMotionPresets.js
|
|
17683
|
+
init_define_import_meta_env();
|
|
17684
|
+
var CAMERA_MOTION_NEUTRAL = {
|
|
17685
|
+
camZoom: 1,
|
|
17686
|
+
camX: 0,
|
|
17687
|
+
camY: 0,
|
|
17688
|
+
camRoll: 0,
|
|
17689
|
+
camDolly: 0,
|
|
17690
|
+
camTiltX: 0,
|
|
17691
|
+
camTiltY: 0
|
|
17692
|
+
};
|
|
17693
|
+
var DOF_DEFAULTS = {
|
|
17694
|
+
camDofEnabled: 0,
|
|
17695
|
+
camFocusDistance: 0,
|
|
17696
|
+
camFocusX: 50,
|
|
17697
|
+
camFocusY: 50,
|
|
17698
|
+
camAperture: 24,
|
|
17699
|
+
camBlurLevel: 100,
|
|
17700
|
+
camHighlightGain: 0,
|
|
17701
|
+
camFocusWidth: 0
|
|
17702
|
+
};
|
|
17703
|
+
var E = TransitionEasing;
|
|
17704
|
+
var RAW = [
|
|
17705
|
+
// ── Cinematic ──
|
|
17706
|
+
{
|
|
17707
|
+
id: "cm-quiet-push",
|
|
17708
|
+
category: "Cinematic",
|
|
17709
|
+
name: "Quiet Push",
|
|
17710
|
+
easeLabel: "AE Smooth",
|
|
17711
|
+
duration: 6,
|
|
17712
|
+
description: "A restrained forward creep \u2014 attention without announcing itself",
|
|
17713
|
+
k: [
|
|
17714
|
+
{ p: 0, e: E.AE_SMOOTH, v: { camZoom: 1.02, camY: 1 } },
|
|
17715
|
+
{ p: 100, v: { camZoom: 1.16, camDolly: 80, camY: 0 } }
|
|
17716
|
+
]
|
|
17717
|
+
},
|
|
17718
|
+
{
|
|
17719
|
+
id: "cm-swoop-dive",
|
|
17720
|
+
category: "Cinematic",
|
|
17721
|
+
name: "Swoop Dive",
|
|
17722
|
+
easeLabel: "Expo Out",
|
|
17723
|
+
duration: 4.5,
|
|
17724
|
+
description: "A drone dive from high and behind that levels off at the subject",
|
|
17725
|
+
k: [
|
|
17726
|
+
{ p: 0, e: E.EXPO_OUT, v: { camY: 9, camTiltX: -14, camZoom: 1.35, camDolly: -120, camRoll: -3 } },
|
|
17727
|
+
{ p: 100, v: { camY: -3, camTiltX: 3, camZoom: 1.1, camDolly: 160, camRoll: 0 } }
|
|
17728
|
+
]
|
|
17729
|
+
},
|
|
17730
|
+
{
|
|
17731
|
+
id: "cm-corkscrew",
|
|
17732
|
+
category: "Cinematic",
|
|
17733
|
+
name: "Corkscrew",
|
|
17734
|
+
easeLabel: "AE Smooth",
|
|
17735
|
+
duration: 5,
|
|
17736
|
+
description: "Screws in from a canted wide \u2014 roll, yaw and zoom resolve together",
|
|
17737
|
+
k: [
|
|
17738
|
+
{ p: 0, e: E.AE_SMOOTH, v: { camRoll: -22, camTiltY: -10, camZoom: 1.6, camX: -5, camY: -3 } },
|
|
17739
|
+
{ p: 100, v: { camRoll: 0, camTiltY: 4, camZoom: 1.08, camX: 1, camY: 0 } }
|
|
17740
|
+
]
|
|
17741
|
+
},
|
|
17742
|
+
{
|
|
17743
|
+
id: "cm-fast-orbit",
|
|
17744
|
+
category: "Cinematic",
|
|
17745
|
+
name: "Fast Orbit",
|
|
17746
|
+
easeLabel: "Linear",
|
|
17747
|
+
duration: 3,
|
|
17748
|
+
description: "A brisk constant-speed yaw sweep with real angular strength",
|
|
17749
|
+
k: [
|
|
17750
|
+
{ p: 0, e: E.LINEAR, v: { camTiltY: -20, camX: -8, camZoom: 1.12 } },
|
|
17751
|
+
{ p: 100, v: { camTiltY: 20, camX: 8, camZoom: 1.12 } }
|
|
17752
|
+
]
|
|
17753
|
+
},
|
|
17754
|
+
// Ported from the retired popover catalog (CAMERA_PRESETS 'lateral-glide').
|
|
17755
|
+
{
|
|
17756
|
+
id: "cm-lateral-glide",
|
|
17757
|
+
category: "Cinematic",
|
|
17758
|
+
name: "Lateral Glide",
|
|
17759
|
+
easeLabel: "Smooth",
|
|
17760
|
+
duration: 6,
|
|
17761
|
+
description: "A polished slider move with just enough yaw to reveal depth",
|
|
17762
|
+
k: [
|
|
17763
|
+
{ p: 0, e: E.EASE_IN_OUT, v: { camX: -7, camY: 0.8, camTiltY: -1.8, camZoom: 1.08 } },
|
|
17764
|
+
{ p: 50, e: E.EASE_IN_OUT, v: { camX: 0, camY: 0, camTiltY: 0, camZoom: 1.1 } },
|
|
17765
|
+
{ p: 100, v: { camX: 7, camY: -0.8, camTiltY: 1.8, camZoom: 1.12 } }
|
|
17766
|
+
]
|
|
17767
|
+
},
|
|
17768
|
+
// Ported from the retired popover catalog (CAMERA_PRESETS 'parallax-arc').
|
|
17769
|
+
{
|
|
17770
|
+
id: "cm-parallax-arc",
|
|
17771
|
+
category: "Cinematic",
|
|
17772
|
+
name: "Parallax Arc",
|
|
17773
|
+
easeLabel: "Smooth",
|
|
17774
|
+
duration: 7,
|
|
17775
|
+
description: "An arcing move that lets foreground and background separate",
|
|
17776
|
+
k: [
|
|
17777
|
+
{ p: 0, e: E.EASE_IN_OUT, v: { camX: -8, camTiltY: -4.5, camDolly: -80, camZoom: 1.08 } },
|
|
17778
|
+
{ p: 50, e: E.EASE_IN_OUT, v: { camX: 0, camTiltY: 0, camDolly: 170, camZoom: 1.13 } },
|
|
17779
|
+
{ p: 100, v: { camX: 8, camTiltY: 4.5, camDolly: -80, camZoom: 1.08 } }
|
|
17780
|
+
]
|
|
17781
|
+
},
|
|
17782
|
+
// Ported from the Mockup builder's Arc Close-Up (mockupGenerator/PresetsPanel.jsx).
|
|
17783
|
+
{
|
|
17784
|
+
id: "cm-arc-closeup",
|
|
17785
|
+
category: "Cinematic",
|
|
17786
|
+
name: "Arc Close-Up",
|
|
17787
|
+
easeLabel: "Cubic Out",
|
|
17788
|
+
duration: 6,
|
|
17789
|
+
description: "Sweeps in on an arc and lands in a crisp close detail",
|
|
17790
|
+
k: [
|
|
17791
|
+
{ p: 0, e: E.CUBIC_OUT, v: { camX: -11, camY: 2, camTiltY: -9, camDolly: -140, camZoom: 0.96 } },
|
|
17792
|
+
{ p: 30, e: E.EASE_IN_OUT, v: { camX: -4, camY: 1, camTiltY: -4, camDolly: 40, camZoom: 1.06 } },
|
|
17793
|
+
{ p: 65, e: E.EASE_IN_OUT, v: { camX: 5, camY: -1, camTiltY: 2, camDolly: 230, camZoom: 1.26 } },
|
|
17794
|
+
{ p: 100, v: { camX: 1.5, camY: 0, camTiltY: 3, camDolly: 160, camZoom: 1.16 } }
|
|
17795
|
+
]
|
|
17796
|
+
},
|
|
17797
|
+
// ── Editorial ──
|
|
17798
|
+
{
|
|
17799
|
+
id: "cm-slingshot",
|
|
17800
|
+
category: "Editorial",
|
|
17801
|
+
name: "Slingshot",
|
|
17802
|
+
easeLabel: "Impulse",
|
|
17803
|
+
duration: 1.8,
|
|
17804
|
+
description: "Pulls back to load, then launches forward \u2014 one Impulse segment",
|
|
17805
|
+
k: [
|
|
17806
|
+
{ p: 0, e: E.IMPULSE, v: { camZoom: 1.05 } },
|
|
17807
|
+
{ p: 100, v: { camZoom: 1.75, camDolly: 260, camY: -1 } }
|
|
17808
|
+
]
|
|
17809
|
+
},
|
|
17810
|
+
{
|
|
17811
|
+
id: "cm-step-zoom",
|
|
17812
|
+
category: "Editorial",
|
|
17813
|
+
name: "Step Zoom",
|
|
17814
|
+
easeLabel: "Hold (cuts)",
|
|
17815
|
+
duration: 2.4,
|
|
17816
|
+
description: "Three hard cuts closer \u2014 hold keys, no interpolation at all",
|
|
17817
|
+
k: [
|
|
17818
|
+
{ p: 0, e: E.HOLD, v: {} },
|
|
17819
|
+
{ p: 25, e: E.HOLD, v: { camZoom: 1.16, camX: 2 } },
|
|
17820
|
+
{ p: 50, e: E.HOLD, v: { camZoom: 1.32, camX: -1.5 } },
|
|
17821
|
+
{ p: 75, e: E.HOLD, v: { camZoom: 1.5, camX: 0 } },
|
|
17822
|
+
{ p: 100, v: {} }
|
|
17823
|
+
]
|
|
17824
|
+
},
|
|
17825
|
+
{
|
|
17826
|
+
id: "cm-handheld-breath",
|
|
17827
|
+
category: "Editorial",
|
|
17828
|
+
name: "Handheld Breath",
|
|
17829
|
+
easeLabel: "Sine",
|
|
17830
|
+
duration: 8,
|
|
17831
|
+
description: "Small, irregular operator drift without the synthetic wobble",
|
|
17832
|
+
k: [
|
|
17833
|
+
{ p: 0, e: E.SINE_IN_OUT, v: { camX: -0.4, camY: 0.4, camRoll: -0.15, camZoom: 1.05 } },
|
|
17834
|
+
{ p: 19, e: E.SINE_IN_OUT, v: { camX: 1.1, camY: -0.6, camRoll: 0.3, camZoom: 1.062 } },
|
|
17835
|
+
{ p: 43, e: E.SINE_IN_OUT, v: { camX: -0.8, camY: 0.9, camRoll: -0.25, camZoom: 1.054 } },
|
|
17836
|
+
{ p: 68, e: E.SINE_IN_OUT, v: { camX: 0.6, camY: 0.2, camRoll: 0.2, camZoom: 1.068 } },
|
|
17837
|
+
{ p: 87, e: E.SINE_IN_OUT, v: { camX: -0.9, camY: -0.5, camRoll: -0.22, camZoom: 1.058 } },
|
|
17838
|
+
{ p: 100, v: { camX: -0.4, camY: 0.4, camRoll: -0.15, camZoom: 1.05 } }
|
|
17839
|
+
]
|
|
17840
|
+
},
|
|
17841
|
+
// Ported from the Mockup builder's Soft Land.
|
|
17842
|
+
{
|
|
17843
|
+
id: "cm-soft-land",
|
|
17844
|
+
category: "Editorial",
|
|
17845
|
+
name: "Soft Land",
|
|
17846
|
+
easeLabel: "Expo Out",
|
|
17847
|
+
duration: 4.2,
|
|
17848
|
+
description: "Drops in from above and settles onto the framing without a bump",
|
|
17849
|
+
k: [
|
|
17850
|
+
{ p: 0, e: E.EXPO_OUT, v: { camY: 9, camTiltX: -8, camDolly: -130, camZoom: 0.94, camRoll: -2 } },
|
|
17851
|
+
{ p: 60, e: E.EASE_IN_OUT, v: { camY: -0.6, camTiltX: 1.2, camDolly: 30, camZoom: 1.04, camRoll: 0.4 } },
|
|
17852
|
+
{ p: 100, v: { camY: 0, camTiltX: 0.6, camDolly: 0, camZoom: 1.01, camRoll: 0 } }
|
|
17853
|
+
]
|
|
17854
|
+
},
|
|
17855
|
+
// Ported from the retired popover catalog (CAMERA_PRESETS 'documentary-follow').
|
|
17856
|
+
{
|
|
17857
|
+
id: "cm-documentary-follow",
|
|
17858
|
+
category: "Editorial",
|
|
17859
|
+
name: "Documentary Follow",
|
|
17860
|
+
easeLabel: "Smooth",
|
|
17861
|
+
duration: 5,
|
|
17862
|
+
description: "A gently imperfect follow move that keeps finding the subject",
|
|
17863
|
+
k: [
|
|
17864
|
+
{ p: 0, e: E.EASE_IN_OUT, v: { camX: -3, camY: 1.5, camRoll: -0.15, camZoom: 1.06 } },
|
|
17865
|
+
{ p: 38, e: E.EASE_IN_OUT, v: { camX: 1, camY: -0.5, camRoll: 0.25, camZoom: 1.1 } },
|
|
17866
|
+
{ p: 72, e: E.EASE_IN_OUT, v: { camX: -0.5, camY: 0.8, camRoll: -0.1, camZoom: 1.08 } },
|
|
17867
|
+
{ p: 100, v: { camX: 2.5, camY: 0, camRoll: 0.1, camZoom: 1.12 } }
|
|
17868
|
+
]
|
|
17869
|
+
},
|
|
17870
|
+
// ── Energy ──
|
|
17871
|
+
{
|
|
17872
|
+
id: "cm-weave-run",
|
|
17873
|
+
category: "Energy",
|
|
17874
|
+
name: "Weave Run",
|
|
17875
|
+
easeLabel: "Sine",
|
|
17876
|
+
duration: 5.5,
|
|
17877
|
+
description: "A forward run that slaloms through the frame, banking into each turn",
|
|
17878
|
+
k: [
|
|
17879
|
+
{ p: 0, e: E.SINE_IN_OUT, v: { camX: -8, camRoll: -3, camDolly: -150, camZoom: 1.1, camTiltY: -4 } },
|
|
17880
|
+
{ p: 30, e: E.SINE_IN_OUT, v: { camX: 6, camRoll: 3, camDolly: 0, camTiltY: 3 } },
|
|
17881
|
+
{ p: 60, e: E.SINE_IN_OUT, v: { camX: -5, camRoll: -2.5, camDolly: 140, camTiltY: -3 } },
|
|
17882
|
+
{ p: 100, v: { camX: 7, camRoll: 2, camDolly: 300, camTiltY: 4 } }
|
|
17883
|
+
]
|
|
17884
|
+
},
|
|
17885
|
+
// Ported from the Mockup builder's S-Curve Drift.
|
|
17886
|
+
{
|
|
17887
|
+
id: "cm-s-curve-drift",
|
|
17888
|
+
category: "Energy",
|
|
17889
|
+
name: "S-Curve Drift",
|
|
17890
|
+
easeLabel: "Smooth",
|
|
17891
|
+
duration: 6.5,
|
|
17892
|
+
description: "An elegant S-shaped path that keeps changing direction mid-drift",
|
|
17893
|
+
k: [
|
|
17894
|
+
{ p: 0, e: E.EASE_IN_OUT, v: { camX: -10, camY: 3, camTiltY: -6, camDolly: -90, camZoom: 1.05, camRoll: -1 } },
|
|
17895
|
+
{ p: 33, e: E.EASE_IN_OUT, v: { camX: 4, camY: -1.5, camTiltY: 1, camDolly: 60, camZoom: 1.12, camRoll: 0.5 } },
|
|
17896
|
+
{ p: 66, e: E.EASE_IN_OUT, v: { camX: -3, camY: 1, camTiltY: -2, camDolly: 160, camZoom: 1.18, camRoll: -0.5 } },
|
|
17897
|
+
{ p: 100, v: { camX: 10, camY: -2, camTiltY: 5, camDolly: 90, camZoom: 1.1, camRoll: 1 } }
|
|
17898
|
+
]
|
|
17899
|
+
},
|
|
17900
|
+
// Ported from the Mockup builder's Macro Sweep.
|
|
17901
|
+
{
|
|
17902
|
+
id: "cm-macro-sweep",
|
|
17903
|
+
category: "Energy",
|
|
17904
|
+
name: "Macro Sweep",
|
|
17905
|
+
easeLabel: "Smooth",
|
|
17906
|
+
duration: 5,
|
|
17907
|
+
description: "Stays tight on the subject while sweeping across it side to side",
|
|
17908
|
+
k: [
|
|
17909
|
+
{ p: 0, e: E.EASE_IN_OUT, v: { camZoom: 1.5, camX: -8, camTiltY: -5, camY: 1 } },
|
|
17910
|
+
{ p: 50, e: E.EASE_IN_OUT, v: { camZoom: 1.62, camX: 2, camTiltY: 1, camY: -0.5 } },
|
|
17911
|
+
{ p: 100, v: { camZoom: 1.46, camX: 9, camTiltY: 5, camY: 1 } }
|
|
17912
|
+
]
|
|
17913
|
+
},
|
|
17914
|
+
// Authored by hand in the graph editor (the "8.8 Preview" project) and lifted from
|
|
17915
|
+
// it verbatim. Pure DOLLY, no zoom: the viewpoint really travels, so the parallax
|
|
17916
|
+
// opens up instead of the lens just magnifying. Two very different segments —
|
|
17917
|
+
// 0–71 % is a LINEAR creep of ~137 px that reads as almost nothing, then one
|
|
17918
|
+
// hand-shaped segment holds and detonates (dolly ~490 at its midpoint, ~1420 at
|
|
17919
|
+
// 90 %, 2600 at the end). 2600 is past the comp plane of a 1080-tall project
|
|
17920
|
+
// (perspectiveDistance ≈ 2166), so the last instant flies THROUGH the frame —
|
|
17921
|
+
// that blow-out is the point of the move, not a bug. Park layers on negative panZ
|
|
17922
|
+
// if you want the camera to stop short of them instead. `sparse` keeps it to the
|
|
17923
|
+
// ONE dolly track — no constant zoom/pan/roll/tilt tracks to wade through in the
|
|
17924
|
+
// keyframe builder, and it layers onto a pose you have already framed.
|
|
17925
|
+
{
|
|
17926
|
+
id: "cm-punch-through",
|
|
17927
|
+
category: "Energy",
|
|
17928
|
+
name: "Punch Through",
|
|
17929
|
+
easeLabel: "Custom",
|
|
17930
|
+
duration: 8.7,
|
|
17931
|
+
sparse: true,
|
|
17932
|
+
description: "A long, almost invisible creep forward that detonates into a flight straight through the frame",
|
|
17933
|
+
k: [
|
|
17934
|
+
{ p: 0, e: E.LINEAR, v: { camDolly: 98 } },
|
|
17935
|
+
{ p: 71, e: E.CUSTOM, b: [0.8187, 0.1079, 0.9089, 0.302], v: { camDolly: 234.7692 } },
|
|
17936
|
+
{ p: 100, e: E.AE_IN, v: { camDolly: 2600 } }
|
|
17937
|
+
]
|
|
17938
|
+
},
|
|
17939
|
+
// ── Loops (first and last snapshots match, so trimming loops cleanly) ──
|
|
17940
|
+
{
|
|
17941
|
+
id: "cm-orbit-pulse",
|
|
17942
|
+
category: "Loops",
|
|
17943
|
+
name: "Orbit Pulse",
|
|
17944
|
+
easeLabel: "Sine",
|
|
17945
|
+
duration: 7,
|
|
17946
|
+
description: "A seamless orbit that breathes closer on every pass",
|
|
17947
|
+
k: [
|
|
17948
|
+
{ p: 0, e: E.SINE_IN_OUT, v: { camTiltY: -13, camX: -5, camY: 1, camZoom: 1.06 } },
|
|
17949
|
+
{ p: 50, e: E.SINE_IN_OUT, v: { camTiltY: 13, camX: 5, camY: -1, camZoom: 1.2 } },
|
|
17950
|
+
{ p: 100, v: { camTiltY: -13, camX: -5, camY: 1, camZoom: 1.06 } }
|
|
17951
|
+
]
|
|
17952
|
+
},
|
|
17953
|
+
// Ported from the Mockup builder's Float & Rotate.
|
|
17954
|
+
{
|
|
17955
|
+
id: "cm-float",
|
|
17956
|
+
category: "Loops",
|
|
17957
|
+
name: "Float",
|
|
17958
|
+
easeLabel: "Sine",
|
|
17959
|
+
duration: 5.5,
|
|
17960
|
+
description: "A weightless vertical bob with a slow roll \u2014 ambient and seamless",
|
|
17961
|
+
k: [
|
|
17962
|
+
{ p: 0, e: E.SINE_IN_OUT, v: { camY: 2.2, camRoll: -1.6, camZoom: 1.1 } },
|
|
17963
|
+
{ p: 50, e: E.SINE_IN_OUT, v: { camY: -2.2, camRoll: 1.6, camZoom: 1.17 } },
|
|
17964
|
+
{ p: 100, v: { camY: 2.2, camRoll: -1.6, camZoom: 1.1 } }
|
|
17965
|
+
]
|
|
17966
|
+
},
|
|
17967
|
+
{
|
|
17968
|
+
id: "cm-figure-eight",
|
|
17969
|
+
category: "Loops",
|
|
17970
|
+
name: "Figure Eight",
|
|
17971
|
+
easeLabel: "Sine",
|
|
17972
|
+
duration: 6,
|
|
17973
|
+
description: "Traces a loop through the frame and returns exactly to its start",
|
|
17974
|
+
k: [
|
|
17975
|
+
{ p: 0, e: E.SINE_IN_OUT, v: { camX: -11, camY: 0, camRoll: 0, camZoom: 1.15 } },
|
|
17976
|
+
{ p: 25, e: E.SINE_IN_OUT, v: { camX: 0, camY: 7, camRoll: 2.5 } },
|
|
17977
|
+
{ p: 50, e: E.SINE_IN_OUT, v: { camX: 11, camY: 0, camRoll: 0 } },
|
|
17978
|
+
{ p: 75, e: E.SINE_IN_OUT, v: { camX: 0, camY: -7, camRoll: -2.5 } },
|
|
17979
|
+
{ p: 100, v: { camX: -11, camY: 0, camRoll: 0 } }
|
|
17980
|
+
]
|
|
17981
|
+
},
|
|
17982
|
+
// ── Focus (ported from the retired popover catalog; DOF tracks, so they only
|
|
17983
|
+
// read with the camera's Depth of Field enabled — they enable it themselves) ──
|
|
17984
|
+
{
|
|
17985
|
+
id: "cm-rack-to-near",
|
|
17986
|
+
category: "Focus",
|
|
17987
|
+
name: "Rack to Near",
|
|
17988
|
+
easeLabel: "Smooth",
|
|
17989
|
+
duration: 3.5,
|
|
17990
|
+
description: "Holds the background, then pulls focus onto a near subject",
|
|
17991
|
+
k: [
|
|
17992
|
+
{ p: 0, e: E.EASE_IN_OUT, v: { camDofEnabled: 1, camFocusDistance: 2800, camFocusX: 68, camFocusY: 48, camAperture: 36, camBlurLevel: 110, camHighlightGain: 18 } },
|
|
17993
|
+
{ p: 22, e: E.EASE_IN_OUT, v: {} },
|
|
17994
|
+
{ p: 78, e: E.EASE_IN_OUT, v: { camFocusDistance: 1350, camFocusX: 35, camFocusY: 55 } },
|
|
17995
|
+
{ p: 100, v: {} }
|
|
17996
|
+
]
|
|
17997
|
+
},
|
|
17998
|
+
{
|
|
17999
|
+
id: "cm-rack-to-far",
|
|
18000
|
+
category: "Focus",
|
|
18001
|
+
name: "Rack to Far",
|
|
18002
|
+
easeLabel: "Smooth",
|
|
18003
|
+
duration: 3.5,
|
|
18004
|
+
description: "Transfers attention from a close detail to the scene behind it",
|
|
18005
|
+
k: [
|
|
18006
|
+
{ p: 0, e: E.EASE_IN_OUT, v: { camDofEnabled: 1, camFocusDistance: 1350, camFocusX: 35, camFocusY: 55, camAperture: 36, camBlurLevel: 110, camHighlightGain: 18 } },
|
|
18007
|
+
{ p: 22, e: E.EASE_IN_OUT, v: {} },
|
|
18008
|
+
{ p: 78, e: E.EASE_IN_OUT, v: { camFocusDistance: 2800, camFocusX: 68, camFocusY: 48 } },
|
|
18009
|
+
{ p: 100, v: {} }
|
|
18010
|
+
]
|
|
18011
|
+
},
|
|
18012
|
+
{
|
|
18013
|
+
id: "cm-focus-chase",
|
|
18014
|
+
category: "Focus",
|
|
18015
|
+
name: "Focus Chase",
|
|
18016
|
+
easeLabel: "Smooth",
|
|
18017
|
+
duration: 6,
|
|
18018
|
+
description: "Moves attention across three depth beats for layered layouts",
|
|
18019
|
+
k: [
|
|
18020
|
+
{ p: 0, e: E.EASE_IN_OUT, v: { camDofEnabled: 1, camFocusDistance: 1450, camFocusX: 28, camFocusY: 55, camAperture: 30, camBlurLevel: 95, camHighlightGain: 12 } },
|
|
18021
|
+
{ p: 45, e: E.EASE_IN_OUT, v: { camFocusDistance: 2600, camFocusX: 72, camFocusY: 42 } },
|
|
18022
|
+
{ p: 100, v: { camFocusDistance: 1750, camFocusX: 48, camFocusY: 50 } }
|
|
18023
|
+
]
|
|
18024
|
+
},
|
|
18025
|
+
{
|
|
18026
|
+
id: "cm-macro-bloom",
|
|
18027
|
+
category: "Focus",
|
|
18028
|
+
name: "Macro Bloom",
|
|
18029
|
+
easeLabel: "Smooth",
|
|
18030
|
+
duration: 4.5,
|
|
18031
|
+
description: "Pushes into a detail while focus and highlight bloom tighten",
|
|
18032
|
+
k: [
|
|
18033
|
+
{ p: 0, e: E.EASE_IN_OUT, v: { camZoom: 1.05, camDolly: 0, camDofEnabled: 1, camFocusDistance: 2500, camFocusX: 50, camFocusY: 50, camAperture: 28, camBlurLevel: 100, camHighlightGain: 15 } },
|
|
18034
|
+
{ p: 52, e: E.EASE_IN_OUT, v: { camZoom: 1.16, camDolly: 100, camFocusDistance: 1650, camAperture: 46, camBlurLevel: 125, camHighlightGain: 32 } },
|
|
18035
|
+
{ p: 100, v: { camZoom: 1.23, camDolly: 180, camFocusDistance: 1150, camAperture: 38, camBlurLevel: 110, camHighlightGain: 22 } }
|
|
18036
|
+
]
|
|
18037
|
+
},
|
|
18038
|
+
// Deliberately LAST in the wall (user's call) rather than up with the other
|
|
18039
|
+
// Cinematic moves — array order is card order, in "All" and inside a category chip
|
|
18040
|
+
// alike. The reveal itself is an ENDING beat: hold the close-up, swing open in the
|
|
18041
|
+
// last stretch.
|
|
18042
|
+
{
|
|
18043
|
+
id: "cm-grand-reveal",
|
|
18044
|
+
category: "Cinematic",
|
|
18045
|
+
name: "Grand Reveal",
|
|
18046
|
+
easeLabel: "Smooth",
|
|
18047
|
+
duration: 3.5,
|
|
18048
|
+
description: "Holds an extreme tilted close-up, then swings open right at the end",
|
|
18049
|
+
k: [
|
|
18050
|
+
{ p: 0, e: E.LINEAR, v: { camZoom: 2.3, camRoll: -5, camY: -4, camDolly: 220 } },
|
|
18051
|
+
{ p: 55, e: E.EASE_IN_OUT, v: {} },
|
|
18052
|
+
{ p: 100, v: { camZoom: 1, camRoll: 0, camY: 0, camDolly: 0 } }
|
|
18053
|
+
]
|
|
18054
|
+
}
|
|
18055
|
+
];
|
|
18056
|
+
var CAMERA_MOTION_PRESETS = RAW.map(({ k, sparse, ...meta }) => {
|
|
18057
|
+
const used = new Set(k.flatMap((key) => Object.keys(key.v)));
|
|
18058
|
+
const usesMotion = !sparse && Object.keys(CAMERA_MOTION_NEUTRAL).some((prop) => used.has(prop));
|
|
18059
|
+
let prev = usesMotion ? { ...CAMERA_MOTION_NEUTRAL } : {};
|
|
18060
|
+
for (const prop of Object.keys(DOF_DEFAULTS)) {
|
|
18061
|
+
if (used.has(prop)) prev[prop] = DOF_DEFAULTS[prop];
|
|
18062
|
+
}
|
|
18063
|
+
const keyframes = k.map((key) => {
|
|
18064
|
+
const pose = { ...prev, ...key.v };
|
|
18065
|
+
prev = pose;
|
|
18066
|
+
const properties = {};
|
|
18067
|
+
for (const [prop, value] of Object.entries(pose)) properties[prop] = String(value);
|
|
18068
|
+
return {
|
|
18069
|
+
position: key.p,
|
|
18070
|
+
easing: key.e || TransitionEasing.EASE_IN_OUT,
|
|
18071
|
+
...key.b ? { bezier: [...key.b] } : {},
|
|
18072
|
+
properties
|
|
18073
|
+
};
|
|
18074
|
+
});
|
|
18075
|
+
return { ...meta, keyframes };
|
|
18076
|
+
});
|
|
18077
|
+
|
|
18078
|
+
// src/components/videoEditor/cameraRig.js
|
|
18079
|
+
init_define_import_meta_env();
|
|
18080
|
+
var D2R = Math.PI / 180;
|
|
18081
|
+
var CAMERA_RIG_TYPES = ["orbit", "dolly", "crane", "pushIn", "truck", "spiral", "handheld", "follow"];
|
|
18082
|
+
var CAMERA_RIG_PARAM_META = {
|
|
18083
|
+
radius: { label: "Radius", min: 100, max: 2e4, step: 10, suffix: "px" },
|
|
18084
|
+
height: { label: "Height", min: -8e3, max: 8e3, step: 10, suffix: "px" },
|
|
18085
|
+
startAngle: { label: "Start", min: -360, max: 360, step: 1, suffix: "\xB0" },
|
|
18086
|
+
sweep: { label: "Sweep", min: -720, max: 720, step: 1, suffix: "\xB0" },
|
|
18087
|
+
rise: { label: "Rise", min: -8e3, max: 8e3, step: 10, suffix: "px" },
|
|
18088
|
+
distance: { label: "Travel", min: -2e4, max: 2e4, step: 10, suffix: "px" },
|
|
18089
|
+
turns: { label: "Turns", min: 0.25, max: 8, step: 0.25, suffix: "\xD7" },
|
|
18090
|
+
tilt: { label: "Tilt", min: -80, max: 80, step: 1, suffix: "\xB0" },
|
|
18091
|
+
roll: { label: "Roll", min: -180, max: 180, step: 1, suffix: "\xB0" },
|
|
18092
|
+
amplitude: { label: "Amount", min: 0, max: 2e3, step: 5, suffix: "px" },
|
|
18093
|
+
frequency: { label: "Rate", min: 0, max: 40, step: 0.5, suffix: "\xD7" }
|
|
18094
|
+
};
|
|
18095
|
+
var CAMERA_RIG_AIMS = ["center", "path"];
|
|
18096
|
+
var CAMERA_RIG_DEFAULTS = Object.freeze({
|
|
18097
|
+
type: "orbit",
|
|
18098
|
+
aim: "center",
|
|
18099
|
+
radius: 1800,
|
|
18100
|
+
height: 400,
|
|
18101
|
+
startAngle: -40,
|
|
18102
|
+
sweep: 90,
|
|
18103
|
+
rise: 0,
|
|
18104
|
+
tilt: -8,
|
|
18105
|
+
roll: 0,
|
|
18106
|
+
distance: 1200,
|
|
18107
|
+
turns: 1,
|
|
18108
|
+
amplitude: 0,
|
|
18109
|
+
frequency: 0,
|
|
18110
|
+
seed: 7
|
|
18111
|
+
});
|
|
18112
|
+
function num6(value, fallback, min, max) {
|
|
18113
|
+
const n2 = typeof value === "number" ? value : Number(value);
|
|
18114
|
+
if (!Number.isFinite(n2)) return fallback;
|
|
18115
|
+
if (Number.isFinite(min) && n2 < min) return min;
|
|
18116
|
+
if (Number.isFinite(max) && n2 > max) return max;
|
|
18117
|
+
return n2;
|
|
18118
|
+
}
|
|
18119
|
+
function normalizeCameraRig(raw) {
|
|
18120
|
+
const source = raw && typeof raw === "object" ? raw : {};
|
|
18121
|
+
const D = CAMERA_RIG_DEFAULTS;
|
|
18122
|
+
const out = { type: CAMERA_RIG_TYPES.includes(source.type) ? source.type : D.type };
|
|
18123
|
+
out.aim = CAMERA_RIG_AIMS.includes(source.aim) ? source.aim : D.aim;
|
|
18124
|
+
for (const [key, meta] of Object.entries(CAMERA_RIG_PARAM_META)) {
|
|
18125
|
+
out[key] = num6(source[key], D[key], meta.min, meta.max);
|
|
18126
|
+
}
|
|
18127
|
+
out.seed = Math.round(num6(source.seed, D.seed, 0, 1e5));
|
|
18128
|
+
out.targets = Array.isArray(source.targets) ? source.targets.filter((id) => typeof id === "string" && id).slice(0, 50) : [];
|
|
18129
|
+
return out;
|
|
18130
|
+
}
|
|
18131
|
+
function noiseCell(seed, cell) {
|
|
18132
|
+
const s = Math.sin(cell * 127.1 + seed * 311.7) * 43758.5453123;
|
|
18133
|
+
return (s - Math.floor(s)) * 2 - 1;
|
|
18134
|
+
}
|
|
18135
|
+
function smoothNoise2(seed, x) {
|
|
18136
|
+
const cell = Math.floor(x);
|
|
18137
|
+
const f2 = x - cell;
|
|
18138
|
+
const eased = f2 * f2 * f2 * (f2 * (f2 * 6 - 15) + 10);
|
|
18139
|
+
return noiseCell(seed, cell) * (1 - eased) + noiseCell(seed, cell + 1) * eased;
|
|
18140
|
+
}
|
|
18141
|
+
var drift = (seed, x) => smoothNoise2(seed, x) * 0.65 + smoothNoise2(seed + 19, x * 2.13) * 0.35;
|
|
18142
|
+
function evalCameraRig(rig, u, ctx = null) {
|
|
18143
|
+
const r = normalizeCameraRig(rig);
|
|
18144
|
+
const t = Math.max(0, Math.min(1, num6(u, 0)));
|
|
18145
|
+
const c = ctx?.center ? { x: num6(ctx.center.x, 0), y: num6(ctx.center.y, 0), z: num6(ctx.center.z, 0) } : { x: 0, y: 0, z: 0 };
|
|
18146
|
+
const pos = rigPosition(r, t, c);
|
|
18147
|
+
const lift = -r.height * (r.tilt / 45);
|
|
18148
|
+
if (r.aim === "path") {
|
|
18149
|
+
const dir = rigTravelDirection(r, t, c);
|
|
18150
|
+
if (dir) {
|
|
18151
|
+
const reach = r.radius;
|
|
18152
|
+
return {
|
|
18153
|
+
pos,
|
|
18154
|
+
aim: { x: pos.x + dir.x * reach, y: pos.y + dir.y * reach + lift, z: pos.z + dir.z * reach },
|
|
18155
|
+
roll: r.roll
|
|
18156
|
+
};
|
|
18157
|
+
}
|
|
18158
|
+
}
|
|
18159
|
+
return { pos, aim: { x: c.x, y: c.y + lift, z: c.z }, roll: r.roll };
|
|
18160
|
+
}
|
|
18161
|
+
function rigTravelDirection(r, t, c) {
|
|
18162
|
+
for (const h of [1e-3, 0.01, 0.05]) {
|
|
18163
|
+
const a = rigPosition(r, t - h, c);
|
|
18164
|
+
const b2 = rigPosition(r, t + h, c);
|
|
18165
|
+
const dx = b2.x - a.x;
|
|
18166
|
+
const dy = b2.y - a.y;
|
|
18167
|
+
const dz = b2.z - a.z;
|
|
18168
|
+
const len = Math.hypot(dx, dy, dz);
|
|
18169
|
+
if (len > 1e-3) return { x: dx / len, y: dy / len, z: dz / len };
|
|
18170
|
+
}
|
|
18171
|
+
return null;
|
|
18172
|
+
}
|
|
18173
|
+
function rigPosition(r, t, c) {
|
|
18174
|
+
const ang = (a) => a * D2R;
|
|
18175
|
+
let pos;
|
|
18176
|
+
switch (r.type) {
|
|
18177
|
+
case "follow": {
|
|
18178
|
+
const a = ang(r.startAngle + r.sweep * t);
|
|
18179
|
+
pos = { x: c.x + Math.sin(a) * r.radius, y: c.y - r.height, z: c.z + Math.cos(a) * r.radius };
|
|
18180
|
+
break;
|
|
18181
|
+
}
|
|
18182
|
+
case "dolly": {
|
|
18183
|
+
const a = ang(r.startAngle);
|
|
18184
|
+
const d = r.radius + r.distance * (1 - t);
|
|
18185
|
+
pos = { x: Math.sin(a) * d, y: -r.height, z: Math.cos(a) * d };
|
|
18186
|
+
break;
|
|
18187
|
+
}
|
|
18188
|
+
case "pushIn": {
|
|
18189
|
+
const a = ang(r.startAngle);
|
|
18190
|
+
const e2 = 1 - (1 - t) ** 3;
|
|
18191
|
+
const d = r.radius + r.distance * (1 - e2);
|
|
18192
|
+
pos = { x: Math.sin(a) * d, y: -r.height, z: Math.cos(a) * d };
|
|
18193
|
+
break;
|
|
18194
|
+
}
|
|
18195
|
+
case "truck": {
|
|
18196
|
+
const a = ang(r.startAngle);
|
|
18197
|
+
const off = r.distance * (t - 0.5);
|
|
18198
|
+
pos = {
|
|
18199
|
+
x: Math.sin(a) * r.radius + Math.cos(a) * off,
|
|
18200
|
+
y: -r.height,
|
|
18201
|
+
z: Math.cos(a) * r.radius - Math.sin(a) * off
|
|
18202
|
+
};
|
|
18203
|
+
break;
|
|
18204
|
+
}
|
|
18205
|
+
case "crane": {
|
|
18206
|
+
const a = ang(r.startAngle + r.sweep * t);
|
|
18207
|
+
pos = { x: Math.sin(a) * r.radius, y: -(r.height + r.rise * t), z: Math.cos(a) * r.radius };
|
|
18208
|
+
break;
|
|
18209
|
+
}
|
|
18210
|
+
case "spiral": {
|
|
18211
|
+
const a = ang(r.startAngle + r.sweep * r.turns * t);
|
|
18212
|
+
const rad = r.radius * (1 - 0.35 * t);
|
|
18213
|
+
pos = { x: Math.sin(a) * rad, y: -(r.height + r.rise * t), z: Math.cos(a) * rad };
|
|
18214
|
+
break;
|
|
18215
|
+
}
|
|
18216
|
+
case "handheld": {
|
|
18217
|
+
const a = ang(r.startAngle);
|
|
18218
|
+
const p = t * Math.max(0, r.frequency);
|
|
18219
|
+
pos = {
|
|
18220
|
+
x: Math.sin(a) * r.radius + drift(r.seed, p) * r.amplitude,
|
|
18221
|
+
y: -r.height + drift(r.seed + 51, p) * r.amplitude * 0.7,
|
|
18222
|
+
z: Math.cos(a) * r.radius + drift(r.seed + 97, p) * r.amplitude * 0.5
|
|
18223
|
+
};
|
|
18224
|
+
break;
|
|
18225
|
+
}
|
|
18226
|
+
case "orbit":
|
|
18227
|
+
default: {
|
|
18228
|
+
const a = ang(r.startAngle + r.sweep * t);
|
|
18229
|
+
pos = { x: Math.sin(a) * r.radius, y: -r.height, z: Math.cos(a) * r.radius };
|
|
18230
|
+
break;
|
|
18231
|
+
}
|
|
18232
|
+
}
|
|
18233
|
+
return pos;
|
|
18234
|
+
}
|
|
18235
|
+
var CAMERA_RIG_PRESETS = [
|
|
18236
|
+
{ id: "heroOrbit", name: "Hero Orbit", rig: { type: "orbit", radius: 1800, height: 400, startAngle: -40, sweep: 90, tilt: -8 } },
|
|
18237
|
+
{ id: "slowReveal", name: "Slow Reveal", rig: { type: "orbit", radius: 2400, height: 220, startAngle: -120, sweep: 150, tilt: -4 } },
|
|
18238
|
+
{ id: "turntable", name: "Turntable", rig: { type: "orbit", radius: 1500, height: 300, startAngle: 0, sweep: 360, tilt: -6 } },
|
|
18239
|
+
{ id: "craneDown", name: "Crane Down", rig: { type: "crane", radius: 1700, height: 1400, startAngle: -30, sweep: 55, rise: -1100, tilt: 14 } },
|
|
18240
|
+
{ id: "riseReveal", name: "Rise & Reveal", rig: { type: "crane", radius: 1900, height: 150, startAngle: -60, sweep: 80, rise: 1200, tilt: -16 } },
|
|
18241
|
+
{ id: "pushIn", name: "Push In", rig: { type: "pushIn", radius: 900, height: 260, startAngle: 0, distance: 1800, tilt: -3 } },
|
|
18242
|
+
{ id: "pullBack", name: "Pull Back", rig: { type: "dolly", radius: 2600, height: 380, startAngle: 12, distance: -1700, tilt: -6 } },
|
|
18243
|
+
{ id: "truckLeft", name: "Truck Left", rig: { type: "truck", radius: 1600, height: 300, startAngle: 0, distance: 2200, tilt: -4 } },
|
|
18244
|
+
// Retuned: the spiral pulls IN as it climbs (`radius * (1 - 0.35 * t)`), so the
|
|
18245
|
+
// start radius has to leave room for that. At 2200 the move ended 1430 out and
|
|
18246
|
+
// 1140 up and a normal product left the top of frame. 2800 lands the tightest
|
|
18247
|
+
// moment at ~0.79 of frame height with the climb intact. Framing is a joint
|
|
18248
|
+
// property of radius + rise + tilt here — retuning one alone just moves the clip.
|
|
18249
|
+
{ id: "productSpiral", name: "Product Spiral", rig: { type: "spiral", radius: 2800, height: 240, startAngle: -20, sweep: 300, turns: 1, rise: 800, tilt: -16 } },
|
|
18250
|
+
{ id: "handheldDrift", name: "Handheld Drift", rig: { type: "handheld", radius: 1500, height: 320, startAngle: -8, amplitude: 90, frequency: 1.5, tilt: -5 } },
|
|
18251
|
+
// Follow presets carry NO targets — which layer to ride is project wiring, not
|
|
18252
|
+
// part of a shot. The panel preserves the current list when a preset is applied.
|
|
18253
|
+
{ id: "droneFollow", name: "Drone Follow", rig: { type: "follow", radius: 1600, height: 420, startAngle: -35, sweep: 110, tilt: -7 } },
|
|
18254
|
+
// A rear three-quarter chase: low, tight, and barely swinging — reads as a
|
|
18255
|
+
// camera operator running behind the subject rather than a crane circling it.
|
|
18256
|
+
{ id: "chaseCam", name: "Chase Cam", rig: { type: "follow", radius: 950, height: 170, startAngle: 160, sweep: 35, tilt: -4 } }
|
|
18257
|
+
];
|
|
18258
|
+
function seedCameraRigProgress(propertyKeyframes) {
|
|
18259
|
+
const existing = propertyKeyframes?.camPathT;
|
|
18260
|
+
if (Array.isArray(existing) && existing.length > 0) return null;
|
|
18261
|
+
return {
|
|
18262
|
+
...propertyKeyframes || {},
|
|
18263
|
+
// `position`, NOT `time`, and `'Linear'`, NOT `'linear'`.
|
|
18264
|
+
//
|
|
18265
|
+
// A keyframe in this project is `{ position, value, easing }` with position in
|
|
18266
|
+
// 0-100 percent of the clip. Written as `time` both keys parsed to position 0,
|
|
18267
|
+
// the de-duplicator then collapsed them into one, and the result was a rig with
|
|
18268
|
+
// a single 0 % key — visibly "the rig only made one keyframe", and a camera that
|
|
18269
|
+
// never moved because progress never advanced. The easing string has to be a
|
|
18270
|
+
// TransitionEasing VALUE ('Linear'), not its key name, or the sanitizer silently
|
|
18271
|
+
// substitutes the default. Linear like every Path Progress track
|
|
18272
|
+
// (objectMotionPath.js PATH_PROGRESS_EASING — not imported, this is a leaf).
|
|
18273
|
+
camPathT: [
|
|
18274
|
+
{ position: 0, value: 0, easing: "Linear" },
|
|
18275
|
+
{ position: 100, value: 100, easing: "Linear" }
|
|
18276
|
+
]
|
|
18277
|
+
};
|
|
18278
|
+
}
|
|
18279
|
+
|
|
18280
|
+
// src/components/videoEditor/projectCamera.js
|
|
18281
|
+
init_define_import_meta_env();
|
|
18282
|
+
|
|
18283
|
+
// src/components/videoEditor/keyframeParenting.js
|
|
18284
|
+
init_define_import_meta_env();
|
|
18285
|
+
var PARENTING_ADDITIVE = "additive";
|
|
18286
|
+
var PARENTING_FULL_COPY = "full-copy";
|
|
18287
|
+
var PARENTING_TYPE_LABELS = {
|
|
18288
|
+
[PARENTING_ADDITIVE]: "Additive",
|
|
18289
|
+
[PARENTING_FULL_COPY]: "Full Copy"
|
|
18290
|
+
};
|
|
18291
|
+
var PRS_KF_PROPS = [
|
|
18292
|
+
"translateX",
|
|
18293
|
+
"translateY",
|
|
18294
|
+
"translateZ",
|
|
18295
|
+
"xPercent",
|
|
18296
|
+
"yPercent",
|
|
18297
|
+
"rotate",
|
|
18298
|
+
"rotateX",
|
|
18299
|
+
"rotateY",
|
|
18300
|
+
"scale",
|
|
18301
|
+
"scaleX",
|
|
18302
|
+
"scaleY",
|
|
18303
|
+
// Path Progress belongs here too: a live motion path is the final authority on
|
|
18304
|
+
// local position (see localPrs), so animating it on a layer whose PRS is owned
|
|
18305
|
+
// by something else does nothing at all. Its offsets are the same story — they
|
|
18306
|
+
// only displace the position the path just claimed.
|
|
18307
|
+
"motionPathT",
|
|
18308
|
+
"motionPathOffsetX",
|
|
18309
|
+
"motionPathOffsetY",
|
|
18310
|
+
"motionPathOffsetZ",
|
|
18311
|
+
// The animated path itself (objectMotionPath.js MOTION_PATH_KF_KEY), same reason.
|
|
18312
|
+
"motionPathPoints"
|
|
18313
|
+
];
|
|
18314
|
+
var PRS_KF_PROP_SET = new Set(PRS_KF_PROPS);
|
|
18315
|
+
var DEG2RAD2 = Math.PI / 180;
|
|
18316
|
+
var RAD2DEG = 180 / Math.PI;
|
|
18317
|
+
|
|
18318
|
+
// src/components/videoEditor/smartZoomCamera.js
|
|
18319
|
+
init_define_import_meta_env();
|
|
18320
|
+
|
|
18321
|
+
// src/components/videoEditor/smartZoom.js
|
|
18322
|
+
init_define_import_meta_env();
|
|
18323
|
+
var PRESETS2 = [
|
|
18324
|
+
{
|
|
18325
|
+
id: "studio",
|
|
18326
|
+
hint: "Smooth spring zoom toward your clicks, like Screen Studio.",
|
|
18327
|
+
short: "Like Screen Studio",
|
|
18328
|
+
name: "Studio",
|
|
18329
|
+
note: "Matches Screen Studio as measured: spring zoom, aims part-way to the clicks, the cursor trails your hand a little.",
|
|
18330
|
+
camera: { preset: "studio", stiffness: 170, damping: 50, mass: 3, zoom: 1.45, motion: "independent", zoomSpace: "linear", follow: "clicks", boxRatio: 0.7, aim: 0.6, retargetLeadMs: 1e3, holdMinMs: 1500, rampMs: 40, edgeSnap: 0, preRollMs: 0 },
|
|
18331
|
+
cursor: { smoothing: "spring", preset: "studio", strength: 1, splineTolPx: 2.5, landMs: 120, shakePx: 3, size: 1.5, clickEffect: "press", tilt: 0, hideIdleMs: 0 }
|
|
18332
|
+
},
|
|
18333
|
+
{
|
|
18334
|
+
id: "calm",
|
|
18335
|
+
hint: "A slower, gentler zoom that keeps the clicked spot in place.",
|
|
18336
|
+
short: "Slow, gentle zoom",
|
|
18337
|
+
name: "Calm",
|
|
18338
|
+
note: "Slow, gentle and less zoom. The clicked spot stays put while the camera zooms. The cursor glides with no lag and fades when idle.",
|
|
18339
|
+
camera: { preset: "calm", stiffness: 110, damping: 46, mass: 3, zoom: 1.35, motion: "aboutPoint", zoomSpace: "linear", follow: "clicks", boxRatio: 0.7, aim: 0.5, retargetLeadMs: 1200, holdMinMs: 1800, rampMs: 80, edgeSnap: 0, preRollMs: 0 },
|
|
18340
|
+
cursor: { smoothing: "zerolag", preset: "soft", strength: 0.8, splineTolPx: 2.5, landMs: 160, shakePx: 3, size: 1.4, clickEffect: "press", tilt: 0, hideIdleMs: 2500 }
|
|
18341
|
+
},
|
|
18342
|
+
{
|
|
18343
|
+
id: "precise",
|
|
18344
|
+
hint: "A closer zoom that centres what you click.",
|
|
18345
|
+
short: "Closer, clicks centred",
|
|
18346
|
+
name: "Precise",
|
|
18347
|
+
note: "Closer zoom that puts the clicks near the middle. The cursor follows your real path exactly, minus the shake. A ring shows each click.",
|
|
18348
|
+
camera: { preset: "smooth", stiffness: 200, damping: 49, mass: 3, zoom: 1.6, motion: "aboutPoint", zoomSpace: "linear", follow: "clicks", boxRatio: 0.7, aim: 0.85, retargetLeadMs: 800, holdMinMs: 1500, rampMs: 40, edgeSnap: 0, preRollMs: 0 },
|
|
18349
|
+
cursor: { smoothing: "spline", preset: "studio", strength: 1, splineTolPx: 2, landMs: 80, shakePx: 3, size: 1.3, clickEffect: "ring", tilt: 0, hideIdleMs: 0 }
|
|
18350
|
+
},
|
|
18351
|
+
{
|
|
18352
|
+
id: "lively",
|
|
18353
|
+
hint: "A fast, punchy zoom that goes in further.",
|
|
18354
|
+
short: "Quick, punchy zoom",
|
|
18355
|
+
name: "Lively",
|
|
18356
|
+
note: "Quick, punchy zooms that go in further. The cursor is tight and tilts as it moves, for short social clips.",
|
|
18357
|
+
camera: { preset: "snappy", stiffness: 340, damping: 66, mass: 3, zoom: 1.7, motion: "independent", zoomSpace: "log", follow: "clicks", boxRatio: 0.7, aim: 0.75, retargetLeadMs: 600, holdMinMs: 1200, rampMs: 20, edgeSnap: 0, preRollMs: 0 },
|
|
18358
|
+
cursor: { smoothing: "spring", preset: "tight", strength: 1, splineTolPx: 2.5, landMs: 100, shakePx: 3, size: 1.6, clickEffect: "press", tilt: 10, hideIdleMs: 0 }
|
|
18359
|
+
},
|
|
18360
|
+
{
|
|
18361
|
+
id: "follow",
|
|
18362
|
+
hint: "While zoomed in, the camera follows your cursor.",
|
|
18363
|
+
short: "Follows the cursor",
|
|
18364
|
+
name: "Follow",
|
|
18365
|
+
note: "While zoomed in, the camera follows the cursor, moving only when it leaves a box around the view, not just on clicks. The cursor has no lag.",
|
|
18366
|
+
camera: { preset: "smooth", stiffness: 200, damping: 49, mass: 3, zoom: 1.5, motion: "independent", zoomSpace: "linear", follow: "cursor", boxRatio: 0.6, aim: 0.8, retargetLeadMs: 300, holdMinMs: 1500, rampMs: 60, edgeSnap: 0, preRollMs: 0 },
|
|
18367
|
+
cursor: { smoothing: "zerolag", preset: "studio", strength: 1, splineTolPx: 2.5, landMs: 120, shakePx: 3, size: 1.5, clickEffect: "press", tilt: 0, hideIdleMs: 0 }
|
|
18368
|
+
}
|
|
18369
|
+
];
|
|
18370
|
+
|
|
18371
|
+
// src/components/videoEditor/smartZoomCamera.js
|
|
18372
|
+
var ZOOM_RANGE_MIN_MS = 400;
|
|
18373
|
+
var ZOOM_MIN = 1.05;
|
|
18374
|
+
var ZOOM_MAX = 4;
|
|
18375
|
+
var clamp4 = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
18376
|
+
var r4 = (v) => Math.round(v * 1e4) / 1e4;
|
|
18377
|
+
function sanitizeZoomRanges(list) {
|
|
18378
|
+
if (!Array.isArray(list)) return void 0;
|
|
18379
|
+
const out = [];
|
|
18380
|
+
const ids = /* @__PURE__ */ new Set();
|
|
18381
|
+
for (const r of list.slice(0, 200)) {
|
|
18382
|
+
if (!r || typeof r !== "object") continue;
|
|
18383
|
+
const s = Number(r.startMs);
|
|
18384
|
+
const e2 = Number(r.endMs);
|
|
18385
|
+
if (!Number.isFinite(s) || !Number.isFinite(e2) || e2 - s < 100) continue;
|
|
18386
|
+
let id = typeof r.id === "string" && r.id ? r.id.slice(0, 40) : `z${out.length + 1}`;
|
|
18387
|
+
while (ids.has(id)) id = `${id}x`;
|
|
18388
|
+
ids.add(id);
|
|
18389
|
+
const z = Number(r.zoom);
|
|
18390
|
+
const p = r.point && Number.isFinite(Number(r.point.x)) && Number.isFinite(Number(r.point.y)) ? { x: r4(clamp4(Number(r.point.x), 0, 1)), y: r4(clamp4(Number(r.point.y), 0, 1)) } : null;
|
|
18391
|
+
out.push({
|
|
18392
|
+
id,
|
|
18393
|
+
startMs: Math.max(0, Math.round(s)),
|
|
18394
|
+
endMs: Math.round(e2),
|
|
18395
|
+
...Number.isFinite(z) && z >= 1 ? { zoom: Math.round(clamp4(z, 1, ZOOM_MAX) * 1e3) / 1e3 } : {},
|
|
18396
|
+
mode: r.mode === "fixed" ? "fixed" : "follow",
|
|
18397
|
+
...p ? { point: p } : {},
|
|
18398
|
+
...r.disabled ? { disabled: true } : {}
|
|
18399
|
+
});
|
|
18400
|
+
}
|
|
18401
|
+
return out.sort((a, b2) => a.startMs - b2.startMs);
|
|
18402
|
+
}
|
|
18403
|
+
|
|
18404
|
+
// src/components/videoEditor/projectCamera.js
|
|
18405
|
+
var CAMERA_FOV = 28;
|
|
18406
|
+
var D2R2 = Math.PI / 180;
|
|
18407
|
+
function perspectiveDistance(height, fovDeg = CAMERA_FOV) {
|
|
18408
|
+
return Math.max(1, height) * 0.5 / Math.tan(fovDeg * Math.PI / 180 * 0.5);
|
|
18409
|
+
}
|
|
18410
|
+
var CAMERA_KF_PROPS = [
|
|
18411
|
+
// Zoom is deliberately uncapped upward — extreme push-ins are a valid look;
|
|
18412
|
+
// only the positive floor is structural (fovEff needs zoom > 0).
|
|
18413
|
+
{ label: "camZoom", displayLabel: "Zoom", group: "Motion", placeholder: "1", unit: "\xD7", type: "number", min: 0.1, step: 0.01 },
|
|
18414
|
+
{ label: "camX", displayLabel: "Pan X", group: "Motion", placeholder: "0", unit: "%", type: "number", min: -200, max: 200, step: 0.5 },
|
|
18415
|
+
{ label: "camY", displayLabel: "Pan Y", group: "Motion", placeholder: "0", unit: "%", type: "number", min: -200, max: 200, step: 0.5 },
|
|
18416
|
+
{ label: "camRoll", displayLabel: "Roll", group: "Motion", placeholder: "0", unit: "deg", type: "number", min: -180, max: 180, step: 0.5 },
|
|
18417
|
+
{ label: "camDolly", displayLabel: "Dolly", group: "Motion", placeholder: "0", unit: "px", type: "number", min: -3e3, max: 3e3, step: 10 },
|
|
18418
|
+
{ label: "camTiltX", displayLabel: "Tilt X", group: "Motion", placeholder: "0", unit: "deg", type: "number", min: -60, max: 60, step: 0.5 },
|
|
18419
|
+
{ label: "camTiltY", displayLabel: "Tilt Y", group: "Motion", placeholder: "0", unit: "deg", type: "number", min: -60, max: 60, step: 0.5 },
|
|
18420
|
+
{ label: "camDofEnabled", displayLabel: "Depth of Field", group: "Depth of Field", placeholder: "0", unit: "", type: "number", min: 0, max: 1, step: 1 },
|
|
18421
|
+
{ label: "camFocusDistance", displayLabel: "Focus Distance", group: "Depth of Field", placeholder: "0", unit: "px", type: "number", min: 0, max: 2e4, step: 10 },
|
|
18422
|
+
{ label: "camAperture", displayLabel: "Aperture", group: "Depth of Field", placeholder: "24", unit: "px", type: "number", min: 0, max: 300, step: 1 },
|
|
18423
|
+
{ label: "camBlurLevel", displayLabel: "Blur Level", group: "Depth of Field", placeholder: "100", unit: "%", type: "number", min: 0, max: 300, step: 1 },
|
|
18424
|
+
{ label: "camHighlightGain", displayLabel: "Highlight Gain", group: "Depth of Field", placeholder: "0", unit: "%", type: "number", min: 0, max: 300, step: 1 },
|
|
18425
|
+
{ label: "camFocusX", displayLabel: "Focus Point X", group: "Depth of Field", placeholder: "50", unit: "%", type: "number", min: 0, max: 100, step: 1 },
|
|
18426
|
+
{ label: "camFocusY", displayLabel: "Focus Point Y", group: "Depth of Field", placeholder: "50", unit: "%", type: "number", min: 0, max: 100, step: 1 },
|
|
18427
|
+
// 0 = the focus point is only a depth probe (pure per-layer DOF). Above 0 it also
|
|
18428
|
+
// becomes a SHARP REGION of the frame: blur is held off inside the area and ramps
|
|
18429
|
+
// to the layer's full circle of confusion by its edge, so a subject can stay crisp
|
|
18430
|
+
// while the rest of its own layer falls away.
|
|
18431
|
+
{ label: "camFocusWidth", displayLabel: "Focus Area Width", group: "Depth of Field", placeholder: "0", unit: "%", type: "number", min: 0, max: 200, step: 1 },
|
|
18432
|
+
// How far along the camera path the camera has travelled. Only does anything
|
|
18433
|
+
// while the clip has a path with `follow` on — see resolveCameraPath below. It
|
|
18434
|
+
// lives in the registry so the keyframe builder and the timeline diamonds pick
|
|
18435
|
+
// it up like any other track.
|
|
18436
|
+
{ label: "camPathT", displayLabel: "Path Progress", group: "Motion", placeholder: "0", unit: "%", type: "number", min: 0, max: 100, step: 0.5 },
|
|
18437
|
+
// A constant displacement from the path, in WORLD axes (+X right, +Y down,
|
|
18438
|
+
// +Z toward the viewer — the panel's own signs). The aim is computed AFTER it,
|
|
18439
|
+
// so a camera pushed off a shared path still looks at its target.
|
|
18440
|
+
{ label: "camPathOffsetX", displayLabel: "Path Offset X", group: "Motion", placeholder: "0", unit: "px", type: "number", min: -1e4, max: 1e4, step: 5 },
|
|
18441
|
+
{ label: "camPathOffsetY", displayLabel: "Path Offset Y", group: "Motion", placeholder: "0", unit: "px", type: "number", min: -1e4, max: 1e4, step: 5 },
|
|
18442
|
+
{ label: "camPathOffsetZ", displayLabel: "Path Offset Z", group: "Motion", placeholder: "0", unit: "px", type: "number", min: -1e4, max: 1e4, step: 5 },
|
|
18443
|
+
// Which entry of the Follow Object rig's target list the camera looks at,
|
|
18444
|
+
// 1-based. Fractional values BLEND the two neighbouring targets' positions, so
|
|
18445
|
+
// an eased key from 1 to 2 pans the look-at across smoothly — that blend IS the
|
|
18446
|
+
// hand-off mechanism, not an interpolation artefact to be rounded away.
|
|
18447
|
+
{ label: "camFollowTarget", displayLabel: "Follow Object", group: "Motion", placeholder: "1", unit: "", type: "number", min: 1, step: 1 }
|
|
18448
|
+
];
|
|
18449
|
+
var CAMERA_KF_LABELS = CAMERA_KF_PROPS.map((p) => p.label);
|
|
18450
|
+
function cameraMoveHash(propertyKeyframes) {
|
|
18451
|
+
const easings = new Set(Object.values(TransitionEasing));
|
|
18452
|
+
const parts = [];
|
|
18453
|
+
for (const prop of CAMERA_KF_LABELS) {
|
|
18454
|
+
const track = propertyKeyframes?.[prop];
|
|
18455
|
+
if (!Array.isArray(track) || track.length === 0) continue;
|
|
18456
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18457
|
+
const keys = track.filter((k) => k && Number.isFinite(Number.parseFloat(k.position))).map((k) => {
|
|
18458
|
+
const easing = easings.has(k.easing) ? k.easing : TransitionEasing.EASE_IN_OUT;
|
|
18459
|
+
const bezier = easing === TransitionEasing.CUSTOM && Array.isArray(k.bezier) ? k.bezier.map((n2) => Math.round(Number(n2) * 1e4) / 1e4).join(",") : "";
|
|
18460
|
+
return [Math.round(Number.parseFloat(k.position) * 1e3) / 1e3, String(k.value ?? ""), easing, bezier];
|
|
18461
|
+
}).sort((a, b2) => a[0] - b2[0]).filter((k) => seen.has(k[0]) ? false : (seen.add(k[0]), true));
|
|
18462
|
+
parts.push(`${prop}=${keys.map((k) => k.join("|")).join(";")}`);
|
|
18463
|
+
}
|
|
18464
|
+
const str = parts.join("&");
|
|
18465
|
+
let h = 2166136261;
|
|
18466
|
+
for (let i = 0; i < str.length; i += 1) {
|
|
18467
|
+
h ^= str.charCodeAt(i);
|
|
18468
|
+
h = Math.imul(h, 16777619);
|
|
18469
|
+
}
|
|
18470
|
+
return (h >>> 0).toString(36);
|
|
18471
|
+
}
|
|
18472
|
+
var _pfMat = new f();
|
|
18473
|
+
var _pfVec = new f();
|
|
18474
|
+
var _pfPos = new f();
|
|
18475
|
+
var _pfQuat = new f();
|
|
18476
|
+
var _pfScale = new f(1, 1, 1);
|
|
18477
|
+
var CAMERA_PRESETS = [
|
|
18478
|
+
{
|
|
18479
|
+
id: "quiet-push",
|
|
18480
|
+
category: "Cinematic",
|
|
18481
|
+
name: "Quiet Push",
|
|
18482
|
+
description: "A restrained forward creep that adds attention without announcing itself",
|
|
18483
|
+
duration: 6,
|
|
18484
|
+
keyframes: [
|
|
18485
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camZoom: "1.02", camDolly: "0", camY: "1" } },
|
|
18486
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camZoom: "1.16", camDolly: "80", camY: "0" } }
|
|
18487
|
+
]
|
|
18488
|
+
},
|
|
18489
|
+
{
|
|
18490
|
+
id: "wide-reveal",
|
|
18491
|
+
category: "Cinematic",
|
|
18492
|
+
name: "Wide Reveal",
|
|
18493
|
+
description: "Begins close and slightly low, then opens cleanly onto the full composition",
|
|
18494
|
+
duration: 5.5,
|
|
18495
|
+
keyframes: [
|
|
18496
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camZoom: "1.36", camY: "-5", camTiltX: "2.5", camDolly: "120" } },
|
|
18497
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camZoom: "1", camY: "0", camTiltX: "0", camDolly: "0" } }
|
|
18498
|
+
]
|
|
18499
|
+
},
|
|
18500
|
+
{
|
|
18501
|
+
id: "lateral-glide",
|
|
18502
|
+
category: "Cinematic",
|
|
18503
|
+
name: "Lateral Glide",
|
|
18504
|
+
description: "A polished slider move with just enough yaw to reveal depth between layers",
|
|
18505
|
+
duration: 6,
|
|
18506
|
+
keyframes: [
|
|
18507
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "-7", camY: "0.8", camTiltY: "-1.8", camZoom: "1.08" } },
|
|
18508
|
+
{ position: 50, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "0", camY: "0", camTiltY: "0", camZoom: "1.1" } },
|
|
18509
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "7", camY: "-0.8", camTiltY: "1.8", camZoom: "1.12" } }
|
|
18510
|
+
]
|
|
18511
|
+
},
|
|
18512
|
+
{
|
|
18513
|
+
id: "parallax-arc",
|
|
18514
|
+
category: "Cinematic",
|
|
18515
|
+
name: "Parallax Arc",
|
|
18516
|
+
description: "An arcing move that lets foreground and background layers separate naturally",
|
|
18517
|
+
duration: 7,
|
|
18518
|
+
keyframes: [
|
|
18519
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "-8", camTiltY: "-4.5", camDolly: "-80", camZoom: "1.08" } },
|
|
18520
|
+
{ position: 50, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "0", camTiltY: "0", camDolly: "170", camZoom: "1.13" } },
|
|
18521
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "8", camTiltY: "4.5", camDolly: "-80", camZoom: "1.08" } }
|
|
18522
|
+
]
|
|
18523
|
+
},
|
|
18524
|
+
{
|
|
18525
|
+
id: "crane-rise",
|
|
18526
|
+
category: "Cinematic",
|
|
18527
|
+
name: "Crane Rise",
|
|
18528
|
+
description: "Rises through the composition while easing into a wider, level view",
|
|
18529
|
+
duration: 6,
|
|
18530
|
+
keyframes: [
|
|
18531
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camY: "-7", camTiltX: "4", camZoom: "1.13", camDolly: "0" } },
|
|
18532
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camY: "5", camTiltX: "-2", camZoom: "1.06", camDolly: "120" } }
|
|
18533
|
+
]
|
|
18534
|
+
},
|
|
18535
|
+
{
|
|
18536
|
+
id: "soft-punch",
|
|
18537
|
+
category: "Editorial",
|
|
18538
|
+
name: "Soft Punch",
|
|
18539
|
+
description: "A quick emphasis beat with a controlled overshoot and a clean settle",
|
|
18540
|
+
duration: 1.6,
|
|
18541
|
+
keyframes: [
|
|
18542
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camZoom: "1", camX: "0", camY: "0" } },
|
|
18543
|
+
{ position: 35, easing: TransitionEasing.EASE_IN_OUT, properties: { camZoom: "1.2", camX: "-1", camY: "0.5" } },
|
|
18544
|
+
{ position: 62, easing: TransitionEasing.EASE_IN_OUT, properties: { camZoom: "1.145", camX: "0", camY: "0" } },
|
|
18545
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camZoom: "1.16", camX: "0", camY: "0" } }
|
|
18546
|
+
]
|
|
18547
|
+
},
|
|
18548
|
+
{
|
|
18549
|
+
id: "whip-and-land",
|
|
18550
|
+
category: "Editorial",
|
|
18551
|
+
name: "Whip & Land",
|
|
18552
|
+
description: "A real reframing whip that accelerates late and settles on a new composition",
|
|
18553
|
+
duration: 1.15,
|
|
18554
|
+
keyframes: [
|
|
18555
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "0", camRoll: "0", camZoom: "1.08" } },
|
|
18556
|
+
{ position: 24, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "0", camRoll: "0", camZoom: "1.08" } },
|
|
18557
|
+
{ position: 63, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "49", camRoll: "2", camZoom: "1.13" } },
|
|
18558
|
+
{ position: 80, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "42", camRoll: "0", camZoom: "1.1" } },
|
|
18559
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "42", camRoll: "0", camZoom: "1.1" } }
|
|
18560
|
+
]
|
|
18561
|
+
},
|
|
18562
|
+
{
|
|
18563
|
+
id: "handheld-breath",
|
|
18564
|
+
category: "Editorial",
|
|
18565
|
+
name: "Handheld Breath",
|
|
18566
|
+
description: "Small, irregular operator drift without the synthetic wobble",
|
|
18567
|
+
duration: 8,
|
|
18568
|
+
keyframes: [
|
|
18569
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "-0.2", camY: "0.2", camRoll: "-0.08", camZoom: "1.04" } },
|
|
18570
|
+
{ position: 19, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "0.65", camY: "-0.35", camRoll: "0.16", camZoom: "1.048" } },
|
|
18571
|
+
{ position: 43, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "-0.45", camY: "0.5", camRoll: "-0.13", camZoom: "1.042" } },
|
|
18572
|
+
{ position: 68, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "0.35", camY: "0.12", camRoll: "0.1", camZoom: "1.052" } },
|
|
18573
|
+
{ position: 87, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "-0.55", camY: "-0.28", camRoll: "-0.12", camZoom: "1.046" } },
|
|
18574
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "-0.2", camY: "0.2", camRoll: "-0.08", camZoom: "1.04" } }
|
|
18575
|
+
]
|
|
18576
|
+
},
|
|
18577
|
+
{
|
|
18578
|
+
id: "documentary-follow",
|
|
18579
|
+
category: "Editorial",
|
|
18580
|
+
name: "Documentary Follow",
|
|
18581
|
+
description: "A gently imperfect follow move that keeps finding the subject",
|
|
18582
|
+
duration: 5,
|
|
18583
|
+
keyframes: [
|
|
18584
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "-3", camY: "1.5", camRoll: "-0.15", camZoom: "1.06" } },
|
|
18585
|
+
{ position: 38, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "1", camY: "-0.5", camRoll: "0.25", camZoom: "1.1" } },
|
|
18586
|
+
{ position: 72, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "-0.5", camY: "0.8", camRoll: "-0.1", camZoom: "1.08" } },
|
|
18587
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camX: "2.5", camY: "0", camRoll: "0.1", camZoom: "1.12" } }
|
|
18588
|
+
]
|
|
18589
|
+
},
|
|
18590
|
+
{
|
|
18591
|
+
id: "rack-to-near",
|
|
18592
|
+
category: "Focus",
|
|
18593
|
+
name: "Rack to Near",
|
|
18594
|
+
description: "Holds the background, then pulls focus decisively onto a near subject",
|
|
18595
|
+
duration: 3.5,
|
|
18596
|
+
keyframes: [
|
|
18597
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camDofEnabled: "1", camFocusDistance: "2800", camFocusX: "68", camFocusY: "48", camAperture: "36", camBlurLevel: "110", camHighlightGain: "18" } },
|
|
18598
|
+
{ position: 22, easing: TransitionEasing.EASE_IN_OUT, properties: { camDofEnabled: "1", camFocusDistance: "2800", camFocusX: "68", camFocusY: "48", camAperture: "36", camBlurLevel: "110", camHighlightGain: "18" } },
|
|
18599
|
+
{ position: 78, easing: TransitionEasing.EASE_IN_OUT, properties: { camDofEnabled: "1", camFocusDistance: "1350", camFocusX: "35", camFocusY: "55", camAperture: "36", camBlurLevel: "110", camHighlightGain: "18" } },
|
|
18600
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camDofEnabled: "1", camFocusDistance: "1350", camFocusX: "35", camFocusY: "55", camAperture: "36", camBlurLevel: "110", camHighlightGain: "18" } }
|
|
18601
|
+
]
|
|
18602
|
+
},
|
|
18603
|
+
{
|
|
18604
|
+
id: "rack-to-far",
|
|
18605
|
+
category: "Focus",
|
|
18606
|
+
name: "Rack to Far",
|
|
18607
|
+
description: "Transfers attention from a close detail to the wider scene behind it",
|
|
18608
|
+
duration: 3.5,
|
|
18609
|
+
keyframes: [
|
|
18610
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camDofEnabled: "1", camFocusDistance: "1350", camFocusX: "35", camFocusY: "55", camAperture: "36", camBlurLevel: "110", camHighlightGain: "18" } },
|
|
18611
|
+
{ position: 22, easing: TransitionEasing.EASE_IN_OUT, properties: { camDofEnabled: "1", camFocusDistance: "1350", camFocusX: "35", camFocusY: "55", camAperture: "36", camBlurLevel: "110", camHighlightGain: "18" } },
|
|
18612
|
+
{ position: 78, easing: TransitionEasing.EASE_IN_OUT, properties: { camDofEnabled: "1", camFocusDistance: "2800", camFocusX: "68", camFocusY: "48", camAperture: "36", camBlurLevel: "110", camHighlightGain: "18" } },
|
|
18613
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camDofEnabled: "1", camFocusDistance: "2800", camFocusX: "68", camFocusY: "48", camAperture: "36", camBlurLevel: "110", camHighlightGain: "18" } }
|
|
18614
|
+
]
|
|
18615
|
+
},
|
|
18616
|
+
{
|
|
18617
|
+
id: "focus-chase",
|
|
18618
|
+
category: "Focus",
|
|
18619
|
+
name: "Focus Chase",
|
|
18620
|
+
description: "Moves attention across three depth beats for layered product or title layouts",
|
|
18621
|
+
duration: 6,
|
|
18622
|
+
keyframes: [
|
|
18623
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camDofEnabled: "1", camFocusDistance: "1450", camFocusX: "28", camFocusY: "55", camAperture: "30", camBlurLevel: "95", camHighlightGain: "12" } },
|
|
18624
|
+
{ position: 45, easing: TransitionEasing.EASE_IN_OUT, properties: { camDofEnabled: "1", camFocusDistance: "2600", camFocusX: "72", camFocusY: "42", camAperture: "30", camBlurLevel: "95", camHighlightGain: "12" } },
|
|
18625
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camDofEnabled: "1", camFocusDistance: "1750", camFocusX: "48", camFocusY: "50", camAperture: "30", camBlurLevel: "95", camHighlightGain: "12" } }
|
|
18626
|
+
]
|
|
18627
|
+
},
|
|
18628
|
+
{
|
|
18629
|
+
id: "macro-bloom",
|
|
18630
|
+
category: "Focus",
|
|
18631
|
+
name: "Macro Bloom",
|
|
18632
|
+
description: "Pushes into a detail while focus and highlight bloom tighten around it",
|
|
18633
|
+
duration: 4.5,
|
|
18634
|
+
keyframes: [
|
|
18635
|
+
{ position: 0, easing: TransitionEasing.EASE_IN_OUT, properties: { camZoom: "1.05", camDolly: "0", camDofEnabled: "1", camFocusDistance: "2500", camFocusX: "50", camFocusY: "50", camAperture: "28", camBlurLevel: "100", camHighlightGain: "15" } },
|
|
18636
|
+
{ position: 52, easing: TransitionEasing.EASE_IN_OUT, properties: { camZoom: "1.16", camDolly: "100", camDofEnabled: "1", camFocusDistance: "1650", camFocusX: "50", camFocusY: "50", camAperture: "46", camBlurLevel: "125", camHighlightGain: "32" } },
|
|
18637
|
+
{ position: 100, easing: TransitionEasing.EASE_IN_OUT, properties: { camZoom: "1.23", camDolly: "180", camDofEnabled: "1", camFocusDistance: "1150", camFocusX: "50", camFocusY: "50", camAperture: "38", camBlurLevel: "110", camHighlightGain: "22" } }
|
|
18638
|
+
]
|
|
18639
|
+
}
|
|
18640
|
+
];
|
|
18641
|
+
|
|
15953
18642
|
// src/motionEngine/mediaProbe.js
|
|
15954
18643
|
init_define_import_meta_env();
|
|
15955
18644
|
var LIMITS = { image: 20 * 1024 * 1024, video: 60 * 1024 * 1024, audio: 30 * 1024 * 1024 };
|
|
@@ -16161,16 +18850,54 @@ var SHADERS = [
|
|
|
16161
18850
|
"windowhaze"
|
|
16162
18851
|
];
|
|
16163
18852
|
var CAMERAS = ["none", "push-in", "pull-out", "drift-left", "drift-right", "drift-up"];
|
|
18853
|
+
var MOVES = new Map(CAMERA_MOTION_PRESETS.map((p) => [p.id, p]));
|
|
18854
|
+
var RIGS = new Map(CAMERA_RIG_PRESETS.map((p) => [p.id, p]));
|
|
18855
|
+
var FEELS = PRESETS2.map((p) => p.id);
|
|
18856
|
+
var CAM_TRACKS = { zoom: "camZoom", x: "camX", y: "camY", roll: "camRoll", dolly: "camDolly", tiltX: "camTiltX", tiltY: "camTiltY", focusZ: "camFocusDistance" };
|
|
18857
|
+
var CAM_DRIVEN = { rig: ["x", "y", "roll", "dolly", "tiltX", "tiltY"], smartZoom: ["zoom", "x", "y"] };
|
|
18858
|
+
var CAM_FIELDS = ["move", "rig", "zooms", "feel", "keyframes", "dof", "start", "end"];
|
|
18859
|
+
var RIG_FIELDS = ["preset", "type", "aim", "targets", "seed", ...Object.keys(CAMERA_RIG_PARAM_META)];
|
|
18860
|
+
function cameraCatalog() {
|
|
18861
|
+
return {
|
|
18862
|
+
moves: CAMERA_MOTION_PRESETS.map((p) => `\`${p.id}\` ${p.name}${p.category ? ` (${p.category})` : ""}`),
|
|
18863
|
+
rigs: CAMERA_RIG_PRESETS.map((p) => `\`${p.id}\` ${p.name} (${p.rig.type})`),
|
|
18864
|
+
feels: PRESETS2.map((p) => `\`${p.id}\` ${p.name}`),
|
|
18865
|
+
rigFields: Object.entries(CAMERA_RIG_PARAM_META).map(([k, m]) => `${k} ${m.min}\u2013${m.max}`),
|
|
18866
|
+
rigTypes: CAMERA_RIG_TYPES,
|
|
18867
|
+
rigAims: CAMERA_RIG_AIMS
|
|
18868
|
+
};
|
|
18869
|
+
}
|
|
16164
18870
|
var FONT = "'Helvetica Neue', Helvetica, Arial, sans-serif";
|
|
16165
18871
|
var TEXT = new Map([...PRESETS, ...CLASSIC_PRESETS].map((p) => [p.id, p]));
|
|
16166
18872
|
var TEXT_RENAMED = { "glide-soft": "glide-continuous" };
|
|
18873
|
+
var rgbOf = (c) => {
|
|
18874
|
+
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(String(c || "").trim());
|
|
18875
|
+
if (!m) return null;
|
|
18876
|
+
const h = m[1].length === 3 ? m[1].split("").map((x) => x + x).join("") : m[1];
|
|
18877
|
+
return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
18878
|
+
};
|
|
18879
|
+
var vivid = (c) => {
|
|
18880
|
+
const v = rgbOf(c);
|
|
18881
|
+
return !!v && Math.max(...v) - Math.min(...v) >= 40;
|
|
18882
|
+
};
|
|
18883
|
+
var luminance = (c) => {
|
|
18884
|
+
const v = rgbOf(c);
|
|
18885
|
+
if (!v) return null;
|
|
18886
|
+
const [r, g, b2] = v.map((x) => x / 255 <= 0.03928 ? x / 255 / 12.92 : ((x / 255 + 0.055) / 1.055) ** 2.4);
|
|
18887
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b2;
|
|
18888
|
+
};
|
|
18889
|
+
var contrast = (a, b2) => {
|
|
18890
|
+
const la = luminance(a), lb = luminance(b2);
|
|
18891
|
+
return la == null || lb == null ? 21 : (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
18892
|
+
};
|
|
18893
|
+
var EDGE_FILLED = /* @__PURE__ */ new Set(["Advanced Zoom", "Advanced Movement"]);
|
|
16167
18894
|
var KF_IN = new Map(kfpVariants(KEYFRAME_PRESETS, "in").map((p) => [p.id, p]));
|
|
16168
18895
|
var KF_OUT = new Map(kfpVariants(KEYFRAME_PRESETS, "out").map((p) => [p.id, p]));
|
|
16169
18896
|
var ROLES = { headline: [0.11, 800], subhead: [0.05, 500], body: [0.036, 400], label: [0.028, 600] };
|
|
16170
|
-
var
|
|
16171
|
-
var
|
|
18897
|
+
var clamp5 = (v, a, b2) => Math.max(a, Math.min(b2, v));
|
|
18898
|
+
var num7 = (v, d) => Number.isFinite(Number(v)) ? Number(v) : d;
|
|
16172
18899
|
var r3 = (v) => +(+v).toFixed(3);
|
|
16173
|
-
var KF_TRACKS = { x: "translateX", y: "translateY", scale: "scale", rotate: "rotate", opacity: "opacity" };
|
|
18900
|
+
var KF_TRACKS = { x: "translateX", y: "translateY", z: "translateZ", scale: "scale", rotate: "rotate", opacity: "opacity" };
|
|
16174
18901
|
function scriptSources(script) {
|
|
16175
18902
|
const out = /* @__PURE__ */ new Set();
|
|
16176
18903
|
for (const sc of Array.isArray(script?.scenes) ? script.scenes : []) {
|
|
@@ -16220,16 +18947,16 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16220
18947
|
};
|
|
16221
18948
|
const easings = new Set(caps?.easings?.keyframe?.values || []);
|
|
16222
18949
|
const fileFor = (src, where, want) => {
|
|
16223
|
-
const
|
|
16224
|
-
if (!
|
|
18950
|
+
const f2 = files.get(String(src));
|
|
18951
|
+
if (!f2) {
|
|
16225
18952
|
problems.push(`${where}: no file "${String(src).slice(0, 80)}" \u2014 a src is an https link, or asset:<file name> for the user's own file (they add it on the page); for art you draw use "svg"`);
|
|
16226
18953
|
return null;
|
|
16227
18954
|
}
|
|
16228
|
-
if (want && !want.includes(
|
|
16229
|
-
problems.push(`${where}: "${
|
|
18955
|
+
if (want && !want.includes(f2.kind)) {
|
|
18956
|
+
problems.push(`${where}: "${f2.name || src}" is ${f2.kind === "audio" ? "a sound" : `a ${f2.kind}`}, not ${want.join(" or ")}`);
|
|
16230
18957
|
return null;
|
|
16231
18958
|
}
|
|
16232
|
-
return
|
|
18959
|
+
return f2;
|
|
16233
18960
|
};
|
|
16234
18961
|
const background = (b2, where) => {
|
|
16235
18962
|
if (b2 == null) return null;
|
|
@@ -16243,7 +18970,7 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16243
18970
|
problems.push(`${where}: unknown shader "${b2.shader}" (one of ${SHADERS.join(", ")})`);
|
|
16244
18971
|
return null;
|
|
16245
18972
|
}
|
|
16246
|
-
return { type: "shader", shaderId: b2.shader, ...b2.look ? { shaderLook: String(b2.look) } : {}, shaderSpeed:
|
|
18973
|
+
return { type: "shader", shaderId: b2.shader, ...b2.look ? { shaderLook: String(b2.look) } : {}, shaderSpeed: clamp5(num7(b2.speed, 1), 0, 4), solidColor: b2.color || "#000000" };
|
|
16247
18974
|
}
|
|
16248
18975
|
if (b2 && typeof b2 === "object" && b2.gradient) return { type: "gradient", gradientCss: String(b2.gradient), gradientAnimated: !!b2.animated, solidColor: "#000000" };
|
|
16249
18976
|
problems.push(`${where}: background must be a colour, a CSS gradient, or { "shader": id, "look"? }`);
|
|
@@ -16251,7 +18978,7 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16251
18978
|
};
|
|
16252
18979
|
const projectBg = background(style.background ?? "#0f1112", "style") || { type: "solid", solidColor: "#0f1112" };
|
|
16253
18980
|
const at = (pos) => {
|
|
16254
|
-
if (Array.isArray(pos)) return [
|
|
18981
|
+
if (Array.isArray(pos)) return [num7(pos[0], 0), num7(pos[1], 0)];
|
|
16255
18982
|
const m = {
|
|
16256
18983
|
center: [0, 0],
|
|
16257
18984
|
top: [0, -0.3],
|
|
@@ -16265,6 +18992,176 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16265
18992
|
}[pos || "center"];
|
|
16266
18993
|
return m ? [m[0] * W, m[1] * H] : [0, 0];
|
|
16267
18994
|
};
|
|
18995
|
+
const P = perspectiveDistance(H);
|
|
18996
|
+
const rigSamples = (rig, n2 = 12) => Array.from({ length: n2 + 1 }, (_, i) => ({ u: i / n2, ...evalCameraRig(rig, i / n2) }));
|
|
18997
|
+
const focusFollow = (fz, rig, dolly) => {
|
|
18998
|
+
if (rig && rig.type !== "follow") {
|
|
18999
|
+
return rigSamples(rig).map(({ u, pos, aim }) => {
|
|
19000
|
+
const dx = aim.x - pos.x;
|
|
19001
|
+
const dy = aim.y - pos.y;
|
|
19002
|
+
const dz = aim.z - pos.z;
|
|
19003
|
+
const L = Math.hypot(dx, dy, dz) || 1;
|
|
19004
|
+
const depth = (-pos.x * dx - pos.y * dy + (fz - pos.z) * dz) / L;
|
|
19005
|
+
return { position: r3(u * 100), value: String(Math.round(Math.max(1, depth))), easing: "Linear" };
|
|
19006
|
+
});
|
|
19007
|
+
}
|
|
19008
|
+
if (!rig && Array.isArray(dolly) && dolly.some((k) => Math.abs(Number(k.value) || 0) > 0.5)) {
|
|
19009
|
+
return dolly.map((k) => ({ position: k.position, value: String(Math.round(Math.max(1, P - (Number(k.value) || 0) - fz))), easing: k.easing, ...k.bezier ? { bezier: [...k.bezier] } : {} }));
|
|
19010
|
+
}
|
|
19011
|
+
return null;
|
|
19012
|
+
};
|
|
19013
|
+
const sceneCamera = (c, cw, D, seq, sceneId, elIds, zOf) => {
|
|
19014
|
+
for (const k of Object.keys(c)) if (!CAM_FIELDS.includes(k)) problems.push(`${cw}: unknown field "${k}" (${CAM_FIELDS.join(", ")})`);
|
|
19015
|
+
const kinds = ["move", "rig", "zooms"].filter((k) => c[k] != null);
|
|
19016
|
+
if (kinds.length > 1) problems.push(`${cw}: one of move, rig or zooms \u2014 not ${kinds.join(" and ")}`);
|
|
19017
|
+
if (!kinds.length && c.keyframes == null && c.dof == null) problems.push(`${cw}: a camera needs move, rig, zooms, keyframes or dof`);
|
|
19018
|
+
const start = clamp5(num7(c.start, 0), 0, D - 0.2);
|
|
19019
|
+
const end = clamp5(num7(c.end, D), start + 0.2, D);
|
|
19020
|
+
const len = end - start;
|
|
19021
|
+
const id = uid(`${sceneId}-camera`);
|
|
19022
|
+
const item = { id, clipId: `c-${id}`, freeStart: r3(start), trimStart: 0, trimEnd: r3(len) };
|
|
19023
|
+
let pk = {};
|
|
19024
|
+
let mode = "free";
|
|
19025
|
+
if (c.move != null) {
|
|
19026
|
+
const p = MOVES.get(c.move);
|
|
19027
|
+
if (!p) problems.push(`${cw}: unknown move "${c.move}"${near(c.move, [...MOVES.keys()])}`);
|
|
19028
|
+
else {
|
|
19029
|
+
pk = commonToPropertyKeyframes(p.keyframes, CAMERA_KF_LABELS).propertyKeyframes;
|
|
19030
|
+
item.cameraPreset = { id: p.id, name: p.name, hash: cameraMoveHash(pk) };
|
|
19031
|
+
}
|
|
19032
|
+
}
|
|
19033
|
+
if (c.rig != null) {
|
|
19034
|
+
const spec = typeof c.rig === "string" ? { preset: c.rig } : c.rig && typeof c.rig === "object" && !Array.isArray(c.rig) ? c.rig : null;
|
|
19035
|
+
if (!spec) problems.push(`${cw}: rig is a rig id or { "preset"?, "type"?, \u2026its fields }`);
|
|
19036
|
+
else {
|
|
19037
|
+
const { preset, targets, ...own } = spec;
|
|
19038
|
+
const base = preset != null ? RIGS.get(preset) : null;
|
|
19039
|
+
if (preset != null && !base) problems.push(`${cw}: unknown rig "${preset}"${near(preset, [...RIGS.keys()])}`);
|
|
19040
|
+
for (const k of Object.keys(own)) if (!RIG_FIELDS.includes(k)) problems.push(`${cw}: rig field "${k}" \u2014 one of ${RIG_FIELDS.join(", ")}`);
|
|
19041
|
+
if (own.type != null && !CAMERA_RIG_TYPES.includes(own.type)) problems.push(`${cw}: rig type must be one of ${CAMERA_RIG_TYPES.join(", ")}`);
|
|
19042
|
+
if (own.aim != null && !CAMERA_RIG_AIMS.includes(own.aim)) problems.push(`${cw}: rig aim must be ${CAMERA_RIG_AIMS.join(" or ")}`);
|
|
19043
|
+
const ids = [];
|
|
19044
|
+
for (const t of Array.isArray(targets) ? targets : targets != null ? [targets] : []) {
|
|
19045
|
+
if (elIds.has(String(t))) ids.push(elIds.get(String(t)));
|
|
19046
|
+
else problems.push(`${cw}: rig target "${t}" is not the id of an element in this scene`);
|
|
19047
|
+
}
|
|
19048
|
+
const rig = normalizeCameraRig({ ...base?.rig || {}, ...own, targets: ids });
|
|
19049
|
+
if (rig.type === "follow" && !ids.length) problems.push(`${cw}: a follow rig rides elements: "targets": [element ids]`);
|
|
19050
|
+
item.cameraRig = rig;
|
|
19051
|
+
mode = "rig";
|
|
19052
|
+
pk = seedCameraRigProgress(pk) || pk;
|
|
19053
|
+
const side = Math.max(...rigSamples(rig).map(({ pos, aim }) => Math.acos(clamp5((pos.z - aim.z) / (Math.hypot(pos.x - aim.x, pos.y - aim.y, pos.z - aim.z) || 1), -1, 1)) * (180 / Math.PI)));
|
|
19054
|
+
if (side > 75) warnings.push(`${cw}: the rig swings ${Math.round(side)}\xB0 off the front, so the scene's flat layers turn ${side > 90 ? "round and read mirrored" : "edge-on"} \u2014 keep it within about \xB170\xB0 (e.g. startAngle -35, sweep 70)`);
|
|
19055
|
+
}
|
|
19056
|
+
}
|
|
19057
|
+
if (c.zooms != null) {
|
|
19058
|
+
if (!Array.isArray(c.zooms) || !c.zooms.length) problems.push(`${cw}: zooms is a list of { "start", "end", "at"?, "zoom"? }`);
|
|
19059
|
+
else {
|
|
19060
|
+
const feel = c.feel ?? "studio";
|
|
19061
|
+
if (!FEELS.includes(feel)) problems.push(`${cw}: feel must be one of ${FEELS.join(", ")}`);
|
|
19062
|
+
const ranges = [];
|
|
19063
|
+
c.zooms.forEach((z, i) => {
|
|
19064
|
+
const zw = `${cw}, zoom ${i + 1}`;
|
|
19065
|
+
const s = num7(z?.start, NaN);
|
|
19066
|
+
const e2 = num7(z?.end, NaN);
|
|
19067
|
+
if (!(s >= -1e-6 && e2 <= len + 1e-6 && e2 - s >= ZOOM_RANGE_MIN_MS / 1e3 - 1e-6)) {
|
|
19068
|
+
problems.push(`${zw}: start and end are seconds into the camera (0\u2013${r3(len)}), at least ${ZOOM_RANGE_MIN_MS / 1e3} s apart`);
|
|
19069
|
+
return;
|
|
19070
|
+
}
|
|
19071
|
+
if (z?.zoom != null && !(num7(z.zoom, 0) >= ZOOM_MIN && num7(z.zoom, 0) <= ZOOM_MAX)) {
|
|
19072
|
+
problems.push(`${zw}: zoom must be ${ZOOM_MIN}\u2013${ZOOM_MAX}`);
|
|
19073
|
+
return;
|
|
19074
|
+
}
|
|
19075
|
+
if (ranges.length && s < ranges.at(-1).endMs / 1e3 - 1e-6) {
|
|
19076
|
+
problems.push(`${zw}: overlaps the zoom before it`);
|
|
19077
|
+
return;
|
|
19078
|
+
}
|
|
19079
|
+
const [x, y] = at(z?.at);
|
|
19080
|
+
ranges.push({
|
|
19081
|
+
id: `z${i + 1}`,
|
|
19082
|
+
startMs: Math.round(s * 1e3),
|
|
19083
|
+
endMs: Math.round(e2 * 1e3),
|
|
19084
|
+
mode: "fixed",
|
|
19085
|
+
point: { x: clamp5(0.5 + x / W, 0, 1), y: clamp5(0.5 + y / H, 0, 1) },
|
|
19086
|
+
...z?.zoom != null ? { zoom: num7(z.zoom, 0) } : {}
|
|
19087
|
+
});
|
|
19088
|
+
});
|
|
19089
|
+
item.smartZoom = { clipId: null, preset: FEELS.includes(feel) ? feel : "studio", ranges: sanitizeZoomRanges(ranges) };
|
|
19090
|
+
mode = "smartZoom";
|
|
19091
|
+
}
|
|
19092
|
+
}
|
|
19093
|
+
if (c.keyframes != null) {
|
|
19094
|
+
if (typeof c.keyframes !== "object" || Array.isArray(c.keyframes)) problems.push(`${cw}: keyframes is { ${Object.keys(CAM_TRACKS).join(" | ")}: [[seconds, value, easing?], \u2026] }`);
|
|
19095
|
+
else for (const [name, keys] of Object.entries(c.keyframes)) {
|
|
19096
|
+
const track = CAM_TRACKS[name];
|
|
19097
|
+
if (!track) {
|
|
19098
|
+
problems.push(`${cw}: keyframes "${name}" \u2014 one of ${Object.keys(CAM_TRACKS).join(", ")}`);
|
|
19099
|
+
continue;
|
|
19100
|
+
}
|
|
19101
|
+
if (CAM_DRIVEN[mode]?.includes(name)) {
|
|
19102
|
+
problems.push(`${cw}: keyframes.${name} \u2014 ${mode === "rig" ? "a rig" : "zooms"} drive${mode === "rig" ? "s" : ""} ${CAM_DRIVEN[mode].join(", ")} themselves; key ${Object.keys(CAM_TRACKS).filter((t) => !CAM_DRIVEN[mode].includes(t)).join(", ")}`);
|
|
19103
|
+
continue;
|
|
19104
|
+
}
|
|
19105
|
+
if (!Array.isArray(keys) || keys.length < 2) {
|
|
19106
|
+
problems.push(`${cw}: keyframes.${name} needs two or more [seconds, value] keys`);
|
|
19107
|
+
continue;
|
|
19108
|
+
}
|
|
19109
|
+
const list = [];
|
|
19110
|
+
for (const k of keys) {
|
|
19111
|
+
const [t, v, easing = "AE Smooth"] = Array.isArray(k) ? k : [k?.t, k?.value, k?.easing];
|
|
19112
|
+
if (!Number.isFinite(Number(t)) || !Number.isFinite(Number(v))) {
|
|
19113
|
+
problems.push(`${cw}: keyframes.${name}: each key is [seconds, value, easing?]`);
|
|
19114
|
+
break;
|
|
19115
|
+
}
|
|
19116
|
+
if (easings.size && !easings.has(easing)) {
|
|
19117
|
+
problems.push(`${cw}: unknown easing "${easing}"${near(easing, [...easings])}`);
|
|
19118
|
+
break;
|
|
19119
|
+
}
|
|
19120
|
+
if (name === "zoom" && !(Number(v) > 0)) {
|
|
19121
|
+
problems.push(`${cw}: keyframes.zoom values are above 0 (1 = no zoom)`);
|
|
19122
|
+
break;
|
|
19123
|
+
}
|
|
19124
|
+
if (name === "focusZ" && !(Number(v) < P - 50)) {
|
|
19125
|
+
problems.push(`${cw}: keyframes.focusZ must stay in front of the camera (below ${Math.round(P - 50)})`);
|
|
19126
|
+
break;
|
|
19127
|
+
}
|
|
19128
|
+
const value = name === "x" ? Number(v) / W * 100 : name === "y" ? Number(v) / H * 100 : name === "focusZ" ? P - Number(v) : Number(v);
|
|
19129
|
+
list.push({ position: r3(clamp5(Number(t) / len * 100, 0, 100)), value: String(r3(value)), easing });
|
|
19130
|
+
}
|
|
19131
|
+
if (list.length === keys.length) {
|
|
19132
|
+
if (pk[track] && track !== "camPathT") warnings.push(`${cw}: keyframes.${name} replace the ${track} keys of move "${c.move}"`);
|
|
19133
|
+
pk[track] = list.sort((a, b2) => a.position - b2.position);
|
|
19134
|
+
if (name === "focusZ") item.camDofEnabled = 1;
|
|
19135
|
+
}
|
|
19136
|
+
}
|
|
19137
|
+
}
|
|
19138
|
+
if (c.dof != null) {
|
|
19139
|
+
const d = c.dof === true ? {} : c.dof && typeof c.dof === "object" && !Array.isArray(c.dof) ? c.dof : null;
|
|
19140
|
+
if (!d) problems.push(`${cw}: dof is true or { "aperture"?, "blur"?, "focus"? (an element id) | "focusZ"? }`);
|
|
19141
|
+
else {
|
|
19142
|
+
for (const k of Object.keys(d)) if (!["aperture", "blur", "focus", "focusZ"].includes(k)) problems.push(`${cw}: dof field "${k}" \u2014 aperture, blur, focus or focusZ`);
|
|
19143
|
+
let fz = num7(d.focusZ, 0);
|
|
19144
|
+
if (d.focus != null) {
|
|
19145
|
+
if (elIds.has(String(d.focus))) fz = zOf.get(elIds.get(String(d.focus))) || 0;
|
|
19146
|
+
else problems.push(`${cw}: dof focus "${d.focus}" is not the id of an element in this scene`);
|
|
19147
|
+
}
|
|
19148
|
+
if (!(fz < P - 50)) problems.push(`${cw}: dof focusZ must be in front of the camera (below ${Math.round(P - 50)})`);
|
|
19149
|
+
item.camDofEnabled = 1;
|
|
19150
|
+
item.camAperture = r3(clamp5(num7(d.aperture, 40), 0, 300));
|
|
19151
|
+
if (d.blur != null) item.camBlurLevel = r3(clamp5(num7(d.blur, 100), 0, 300));
|
|
19152
|
+
if (!pk.camFocusDistance) {
|
|
19153
|
+
const follow = focusFollow(fz, mode === "rig" ? item.cameraRig : null, pk.camDolly);
|
|
19154
|
+
if (follow) pk.camFocusDistance = follow;
|
|
19155
|
+
else if (fz) item.camFocusDistance = Math.round(P - fz);
|
|
19156
|
+
}
|
|
19157
|
+
}
|
|
19158
|
+
}
|
|
19159
|
+
if (mode !== "free") item.cameraMoveMode = mode;
|
|
19160
|
+
if (Object.keys(pk).length) item.propertyKeyframes = pk;
|
|
19161
|
+
seq.clips.push({ id: `c-${id}`, source: "LOCAL", mediaType: "camera", url: "", proxyUrl: null, name: "Camera", duration: r3(len) });
|
|
19162
|
+
seq.channels.push({ id: `ch-${id}`, name: "Camera", kind: "camera", visible: true, unsnapped: true, items: [item] });
|
|
19163
|
+
seq.channels[0].cameraLocked = true;
|
|
19164
|
+
};
|
|
16268
19165
|
const project = {
|
|
16269
19166
|
name: String(S.title || "GreenLight Motion video").slice(0, 120),
|
|
16270
19167
|
videoSize: { w: W, h: H },
|
|
@@ -16289,9 +19186,9 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16289
19186
|
let total = 0;
|
|
16290
19187
|
scenes.forEach((sc, si) => {
|
|
16291
19188
|
const where = `scene ${si + 1}`;
|
|
16292
|
-
const dur =
|
|
19189
|
+
const dur = num7(sc?.duration, 3);
|
|
16293
19190
|
if (!(dur >= 0.8 && dur <= 30)) problems.push(`${where}: duration must be 0.8\u201330 s`);
|
|
16294
|
-
const D =
|
|
19191
|
+
const D = clamp5(dur, 0.8, 30);
|
|
16295
19192
|
const els = Array.isArray(sc?.elements) ? sc.elements : [];
|
|
16296
19193
|
if (els.length > 40) problems.push(`${where}: at most 40 elements`);
|
|
16297
19194
|
const seq = { clips: [], channels: [] };
|
|
@@ -16316,64 +19213,65 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16316
19213
|
if (bg.type === "solid" && !sceneBg) fields.push({ bg: true, itemId: bgItem.id, color: bg.solidColor });
|
|
16317
19214
|
const media = (el, ew, id, start, end, pos) => {
|
|
16318
19215
|
const type = el.type;
|
|
16319
|
-
let
|
|
19216
|
+
let f2;
|
|
16320
19217
|
if (el.svg && type === "image") {
|
|
16321
19218
|
try {
|
|
16322
|
-
|
|
19219
|
+
f2 = { ...svgDataUrl(el.svg), kind: "image", type: "svg", name: el.label || "art.svg" };
|
|
16323
19220
|
} catch (e2) {
|
|
16324
19221
|
problems.push(`${ew}: ${e2.message}`);
|
|
16325
19222
|
return null;
|
|
16326
19223
|
}
|
|
16327
|
-
} else
|
|
16328
|
-
if (!
|
|
16329
|
-
const iw =
|
|
16330
|
-
const ih =
|
|
16331
|
-
if (!
|
|
16332
|
-
const s0 = type === "video" ? Math.min(W / iw, H / ih) :
|
|
19224
|
+
} else f2 = fileFor(el.src, ew, [type]);
|
|
19225
|
+
if (!f2) return null;
|
|
19226
|
+
const iw = f2.width || W;
|
|
19227
|
+
const ih = f2.height || H;
|
|
19228
|
+
if (!f2.width) warnings.push(`${ew}: the size of "${f2.name}" is unknown, so it is fitted to the frame`);
|
|
19229
|
+
const s0 = type === "video" ? Math.min(W / iw, H / ih) : f2.type === "svg" ? 1 : Math.min(1, W / iw, H / ih);
|
|
16333
19230
|
const bw = iw * s0;
|
|
16334
19231
|
const bh = ih * s0;
|
|
16335
19232
|
const fit = el.fit || (el.w || el.h ? null : type === "video" ? "cover" : "contain");
|
|
16336
|
-
const zoom = el.w ?
|
|
19233
|
+
const zoom = el.w ? num7(el.w, bw) / bw : el.h ? num7(el.h, bh) / bh : fit === "cover" ? Math.max(W / bw, H / bh) : fit === "contain" ? Math.min(W / bw, H / bh) * (el.fit ? 1 : 0.7) : 1;
|
|
16337
19234
|
const len = Math.max(0.05, end - start);
|
|
16338
|
-
const from = Math.max(0,
|
|
16339
|
-
const srcLen = type === "video" ?
|
|
16340
|
-
if (type === "video" &&
|
|
19235
|
+
const from = Math.max(0, num7(el.startAt, 0));
|
|
19236
|
+
const srcLen = type === "video" ? f2.duration || from + len : len;
|
|
19237
|
+
if (type === "video" && f2.duration && from + len > f2.duration + 0.05) warnings.push(`${ew}: "${f2.name}" is ${f2.duration} s; from ${from} s it ends before the element (it holds its last frame)`);
|
|
16341
19238
|
seq.clips.push({
|
|
16342
19239
|
id: `c-${id}`,
|
|
16343
19240
|
source: "LOCAL",
|
|
16344
19241
|
mediaType: type,
|
|
16345
|
-
url:
|
|
19242
|
+
url: f2.url,
|
|
16346
19243
|
proxyUrl: null,
|
|
16347
|
-
name:
|
|
19244
|
+
name: f2.name || id,
|
|
16348
19245
|
duration: r3(srcLen),
|
|
16349
|
-
...
|
|
19246
|
+
...f2.width ? { width: f2.width, height: f2.height } : {}
|
|
16350
19247
|
});
|
|
16351
|
-
const vol =
|
|
19248
|
+
const vol = clamp5(num7(el.volume, 0), 0, 100);
|
|
16352
19249
|
const item = {
|
|
16353
19250
|
id,
|
|
16354
19251
|
clipId: `c-${id}`,
|
|
16355
19252
|
freeStart: r3(start),
|
|
16356
19253
|
trimStart: r3(from),
|
|
16357
19254
|
trimEnd: r3(from + len),
|
|
16358
|
-
zoom: r3(
|
|
19255
|
+
zoom: r3(clamp5(zoom, 0.02, 20)),
|
|
16359
19256
|
...pos,
|
|
16360
|
-
...el.rotate ? { rotation:
|
|
16361
|
-
...el.opacity != null ? { opacity: r3(clamp2(num3(el.opacity, 1), 0, 1) * 100) } : {},
|
|
16362
|
-
// the item's opacity is 0–100 (its keyframe track 0–1)
|
|
19257
|
+
...el.rotate ? { rotation: num7(el.rotate, 0) } : {},
|
|
16363
19258
|
...type === "video" ? vol > 0 ? { volume: vol } : { volume: 0, muted: true } : {}
|
|
16364
19259
|
};
|
|
16365
|
-
seq.channels.push({ id: `ch-${id}`, name:
|
|
19260
|
+
seq.channels.push({ id: `ch-${id}`, name: f2.name || id, visible: true, unsnapped: true, items: [item] });
|
|
16366
19261
|
return item;
|
|
16367
19262
|
};
|
|
16368
19263
|
if (sceneBg?.type === "media") media({ type: sceneBg.kind, src: sceneBg.src, fit: "cover" }, `${where}, background`, uid(`${sceneId}-bg-${sceneBg.kind}`), 0, D, {});
|
|
16369
19264
|
let firstHeadline = null;
|
|
19265
|
+
const elIds = /* @__PURE__ */ new Map();
|
|
19266
|
+
const zOf = /* @__PURE__ */ new Map();
|
|
16370
19267
|
els.forEach((el, ei) => {
|
|
16371
19268
|
const ew = `${where}, element ${ei + 1}`;
|
|
16372
19269
|
const type = el?.type || "text";
|
|
16373
|
-
const start =
|
|
16374
|
-
const end =
|
|
19270
|
+
const start = clamp5(num7(el?.start, 0.1 + ei * 0.12), 0, D - 0.2);
|
|
19271
|
+
const end = clamp5(num7(el?.end, D), start + 0.2, D);
|
|
16375
19272
|
const [x, y] = at(el?.at);
|
|
16376
|
-
const
|
|
19273
|
+
const z = clamp5(num7(el?.z, 0), -6e3, 1500);
|
|
19274
|
+
const pos = { ...x ? { panX: r3(x / W * 100) } : {}, ...y ? { panY: r3(y / H * 100) } : {}, ...z ? { panZ: r3(z) } : {} };
|
|
16377
19275
|
const id = uid(el?.id || `${sceneId}-${type}-${ei + 1}`);
|
|
16378
19276
|
let item;
|
|
16379
19277
|
if (type === "text") {
|
|
@@ -16383,9 +19281,9 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16383
19281
|
return;
|
|
16384
19282
|
}
|
|
16385
19283
|
const role = ROLES[el?.role] ? el.role : ei === 0 ? "headline" : "subhead";
|
|
16386
|
-
const size =
|
|
16387
|
-
const weight =
|
|
16388
|
-
const width =
|
|
19284
|
+
const size = clamp5(num7(el?.size, Math.round(ROLES[role][0] * Math.min(W, H) * (W > H ? 1 : 1.25))), 8, 600);
|
|
19285
|
+
const weight = clamp5(num7(el?.weight, ROLES[role][1]), 100, 900);
|
|
19286
|
+
const width = clamp5(num7(el?.width, Math.min(W * 0.86, Math.max(...t.split("\n").map((l) => l.length)) * size * 0.62 + size)), 40, W);
|
|
16389
19287
|
const color = paint(el?.color || (role === "headline" ? "text" : role === "label" ? "accent" : "muted"), colors.text);
|
|
16390
19288
|
item = add("text", id, start, end, {
|
|
16391
19289
|
textContent: t,
|
|
@@ -16396,13 +19294,13 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16396
19294
|
textAlign: el?.align || "center",
|
|
16397
19295
|
textWidthUnit: "px",
|
|
16398
19296
|
textWidthPx: Math.round(width),
|
|
16399
|
-
textLineHeight:
|
|
16400
|
-
...el?.letterSpacing ? { textLetterSpacing:
|
|
19297
|
+
textLineHeight: num7(el?.lineHeight, 1.15),
|
|
19298
|
+
...el?.letterSpacing ? { textLetterSpacing: num7(el.letterSpacing, 0) } : {},
|
|
16401
19299
|
...el?.uppercase ? { textLetterCase: "uppercase" } : {},
|
|
16402
19300
|
...pos
|
|
16403
19301
|
});
|
|
16404
19302
|
if (el?.color === "accent" || !el?.color && role === "label") accentTargets.push({ itemId: id, path: "textColor" });
|
|
16405
|
-
if (fields.filter((
|
|
19303
|
+
if (fields.filter((f2) => f2.text).length < 10) fields.push({ text: true, key: id, label: el?.label || t.split(/\s+/).slice(0, 4).join(" "), itemId: id, value: t });
|
|
16406
19304
|
const preset = el?.textAnimation ?? (role === "headline" ? "glide-text" : null);
|
|
16407
19305
|
if (preset) {
|
|
16408
19306
|
const p = TEXT.get(TEXT_RENAMED[preset] || preset);
|
|
@@ -16410,12 +19308,33 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16410
19308
|
else {
|
|
16411
19309
|
const patch = presetPatch(item, p, { clipSec: end - start, fontPx: size, mode: "replace" });
|
|
16412
19310
|
item.textAnimators = el?.textExit ? patch.textAnimators : patch.textAnimators.filter((a) => !(a.range?.drive === "time" && a.range?.animate === "out"));
|
|
19311
|
+
const boxed = [];
|
|
16413
19312
|
item.textAnimators.forEach((a, ai) => (a.effects || []).forEach((fx, fi) => {
|
|
16414
|
-
|
|
19313
|
+
const at2 = `textAnimators.${ai}.effects.${fi}`;
|
|
19314
|
+
if (Array.isArray(fx.colors)) {
|
|
19315
|
+
fx.colors.forEach((c, ci) => {
|
|
19316
|
+
if (vivid(c)) {
|
|
19317
|
+
fx.colors[ci] = colors.accent;
|
|
19318
|
+
accentTargets.push({ itemId: id, path: `${at2}.colors.${ci}` });
|
|
19319
|
+
}
|
|
19320
|
+
});
|
|
19321
|
+
} else if (typeof fx.color === "string" && vivid(fx.color)) {
|
|
16415
19322
|
fx.color = colors.accent;
|
|
16416
|
-
accentTargets.push({ itemId: id, path:
|
|
19323
|
+
accentTargets.push({ itemId: id, path: `${at2}.color` });
|
|
19324
|
+
if (fx.type === "box" && !boxed.includes(a)) boxed.push(a);
|
|
16417
19325
|
}
|
|
16418
19326
|
}));
|
|
19327
|
+
if (boxed.length) {
|
|
19328
|
+
const ink = contrast("#0b0d0c", colors.accent) >= contrast("#ffffff", colors.accent) ? "#0b0d0c" : "#ffffff";
|
|
19329
|
+
const fills = item.textAnimators.flatMap((a) => (a.effects || []).filter((fx) => fx.type === "fill"));
|
|
19330
|
+
if (fills.length) fills.forEach((fx) => {
|
|
19331
|
+
if (contrast(fx.color, colors.accent) < 4.5) fx.color = ink;
|
|
19332
|
+
});
|
|
19333
|
+
else if (contrast(color, colors.accent) < 4.5) {
|
|
19334
|
+
let pidN = Math.max(0, ...item.textAnimators.flatMap((a) => (a.effects || []).map((fx) => Number(String(fx.pid).replace(/^\D+/, "")) || 0)));
|
|
19335
|
+
for (const a of boxed) a.effects = [...a.effects || [], { pid: `p${++pidN}`, type: "fill", color: ink }];
|
|
19336
|
+
}
|
|
19337
|
+
}
|
|
16419
19338
|
item.propertyKeyframes = { ...item.propertyKeyframes || {}, ...patch.propertyKeyframes };
|
|
16420
19339
|
if (patch.textAnimatorPivotX != null) {
|
|
16421
19340
|
item.textAnimatorPivotX = patch.textAnimatorPivotX;
|
|
@@ -16425,8 +19344,8 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16425
19344
|
}
|
|
16426
19345
|
if (role === "headline" && !firstHeadline) firstHeadline = { id, start };
|
|
16427
19346
|
} else if (["rect", "pill", "circle"].includes(type)) {
|
|
16428
|
-
const w =
|
|
16429
|
-
const h =
|
|
19347
|
+
const w = clamp5(num7(el?.w, type === "circle" ? 200 : 400), 1, W * 4);
|
|
19348
|
+
const h = clamp5(num7(el?.h, type === "circle" ? w : 120), 1, H * 4);
|
|
16430
19349
|
const fillBg = typeof el?.color === "object" || /gradient\(/i.test(String(el?.color || "")) ? background(el.color, ew) : null;
|
|
16431
19350
|
const fill = fillBg ? fillBg.solidColor : paint(el?.color || "accent", colors.accent);
|
|
16432
19351
|
item = add("shape", id, start, end, {
|
|
@@ -16436,14 +19355,14 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16436
19355
|
shapeHeightPx: h,
|
|
16437
19356
|
shapeFillColor: fill,
|
|
16438
19357
|
...fillBg && fillBg.type !== "solid" ? { shapeBackground: fillBg } : {},
|
|
16439
|
-
...type === "pill" ? { borderRadius: h / 2 } : el?.radius ? { borderRadius:
|
|
16440
|
-
...el?.border ? { borderWidth:
|
|
19358
|
+
...type === "pill" ? { borderRadius: h / 2 } : el?.radius ? { borderRadius: num7(el.radius, 0) } : {},
|
|
19359
|
+
...el?.border ? { borderWidth: num7(el.border.width, 2), borderColor: paint(el.border.color, colors.text), borderStyle: "solid" } : {},
|
|
16441
19360
|
...el?.shadow ? { shadows: [{ x: 0, y: 12, blur: 40, spread: 0, color: "rgba(0,0,0,0.35)", inset: false }] } : {},
|
|
16442
19361
|
...pos
|
|
16443
19362
|
});
|
|
16444
19363
|
if (!el?.color || el.color === "accent") accentTargets.push({ itemId: id, path: "shapeFillColor" });
|
|
16445
19364
|
} else if (type === "line") {
|
|
16446
|
-
const [x2, y2] = Array.isArray(el?.to) ? [
|
|
19365
|
+
const [x2, y2] = Array.isArray(el?.to) ? [num7(el.to[0], 0), num7(el.to[1], 0)] : [x + 300, y];
|
|
16447
19366
|
const cx = (x + x2) / 2;
|
|
16448
19367
|
const cy = (y + y2) / 2;
|
|
16449
19368
|
const L = Math.max(4, Math.abs(x2 - x), Math.abs(y2 - y));
|
|
@@ -16455,10 +19374,11 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16455
19374
|
shapeHeightPx: L,
|
|
16456
19375
|
shapeFillColor: paint(el?.color || "accent", colors.accent),
|
|
16457
19376
|
shapePath: { points: pts, closed: false },
|
|
16458
|
-
lineWidth:
|
|
19377
|
+
lineWidth: num7(el?.width, 6),
|
|
16459
19378
|
lineCap: "round",
|
|
16460
19379
|
...cx ? { panX: r3(cx / W * 100) } : {},
|
|
16461
|
-
...cy ? { panY: r3(cy / H * 100) } : {}
|
|
19380
|
+
...cy ? { panY: r3(cy / H * 100) } : {},
|
|
19381
|
+
...z ? { panZ: r3(z) } : {}
|
|
16462
19382
|
});
|
|
16463
19383
|
if (el?.draw !== false) item.propertyKeyframes = { lineTrimEnd: [{ position: 0, value: "0", easing: "AE Out" }, { position: r3(Math.min(100, 0.7 / (end - start) * 100)), value: "100", easing: "Linear" }] };
|
|
16464
19384
|
if (!el?.color || el.color === "accent") accentTargets.push({ itemId: id, path: "shapeFillColor" });
|
|
@@ -16469,6 +19389,13 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16469
19389
|
problems.push(`${ew}: type must be text, rect, pill, circle, line, image or video`);
|
|
16470
19390
|
return;
|
|
16471
19391
|
}
|
|
19392
|
+
if (el?.opacity != null) item.opacity = r3(clamp5(num7(el.opacity, 1), 0, 1) * 100);
|
|
19393
|
+
if (el?.id != null) elIds.set(String(el.id), id);
|
|
19394
|
+
zOf.set(id, z);
|
|
19395
|
+
if (el?.locked) {
|
|
19396
|
+
const ch = seq.channels.find((c) => c.items[0] === item);
|
|
19397
|
+
if (ch) ch.cameraLocked = true;
|
|
19398
|
+
}
|
|
16472
19399
|
const len = Math.max(1e-3, end - start);
|
|
16473
19400
|
const span = Math.min(100, KFP_DEFAULT_LENGTH / len * 100);
|
|
16474
19401
|
const enter = el?.enter ?? (["text", "image", "video"].includes(type) ? null : "pop-overshoot");
|
|
@@ -16494,8 +19421,8 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16494
19421
|
continue;
|
|
16495
19422
|
}
|
|
16496
19423
|
const list = [];
|
|
16497
|
-
for (const
|
|
16498
|
-
const [t, v, easing = "AE Smooth"] = Array.isArray(
|
|
19424
|
+
for (const k of keys) {
|
|
19425
|
+
const [t, v, easing = "AE Smooth"] = Array.isArray(k) ? k : [k?.t, k?.value, k?.easing];
|
|
16499
19426
|
if (!Number.isFinite(Number(t)) || !Number.isFinite(Number(v))) {
|
|
16500
19427
|
problems.push(`${ew}: keyframes.${name}: each key is [seconds, value, easing?]`);
|
|
16501
19428
|
break;
|
|
@@ -16504,7 +19431,7 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16504
19431
|
problems.push(`${ew}: unknown easing "${easing}"${near(easing, [...easings])}`);
|
|
16505
19432
|
break;
|
|
16506
19433
|
}
|
|
16507
|
-
list.push({ position: r3(
|
|
19434
|
+
list.push({ position: r3(clamp5(Number(t) / len * 100, 0, 100)), value: String(name === "opacity" ? clamp5(Number(v), 0, 1) : Number(v)), easing });
|
|
16508
19435
|
}
|
|
16509
19436
|
if (list.length === keys.length) {
|
|
16510
19437
|
if (item.propertyKeyframes?.[track]) warnings.push(`${ew}: keyframes.${name} replace the ${track} keys of its entrance / exit`);
|
|
@@ -16530,25 +19457,30 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16530
19457
|
const tr = sc?.transition ?? "Cross Fade";
|
|
16531
19458
|
if (!CROSS_TRANSITIONS.includes(tr)) problems.push(`${where}: unknown transition "${tr}" (one of ${CROSS_TRANSITIONS.join(", ")})`);
|
|
16532
19459
|
else if (tr !== "None") {
|
|
16533
|
-
const prev =
|
|
19460
|
+
const prev = num7(scenes[si - 1]?.duration, 3);
|
|
16534
19461
|
sceneItem.transition = tr;
|
|
16535
|
-
|
|
19462
|
+
if (EDGE_FILLED.has(tr)) sceneItem.transitionOptions = { edgeMode: "none" };
|
|
19463
|
+
sceneItem.transitionDuration = r3(clamp5(num7(sc?.transitionDuration, 0.6), 0.2, Math.min(prev, D) / 2));
|
|
16536
19464
|
total -= sceneItem.transitionDuration;
|
|
16537
19465
|
if ((S.sound ?? "auto") === "auto") project.sounds.push({ sfx: soundOk("soft-swoosh", where) ? "soft-swoosh" : "standard-swoosh", on: project.channels[0].items.at(-1).id, at: "end" });
|
|
16538
19466
|
}
|
|
16539
19467
|
}
|
|
16540
19468
|
const cam = sc?.camera ?? "push-in";
|
|
16541
|
-
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
|
|
16545
|
-
|
|
16546
|
-
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
|
|
16550
|
-
|
|
16551
|
-
|
|
19469
|
+
const real = cam && typeof cam === "object" && !Array.isArray(cam) ? cam : typeof cam === "string" && MOVES.has(cam) ? { move: cam } : typeof cam === "string" && RIGS.has(cam) ? { rig: cam } : null;
|
|
19470
|
+
if (real) sceneCamera(real, `${where}, camera`, D, seq, sceneId, elIds, zOf);
|
|
19471
|
+
else if (!CAMERAS.includes(cam)) problems.push(`${where}: camera must be one of ${CAMERAS.join(", ")}, a camera move or rig id, or { "move" | "rig" | "zooms" | "keyframes", "dof"? }${typeof cam === "string" ? near(cam, [...CAMERAS, ...MOVES.keys(), ...RIGS.keys()]) : ""}`);
|
|
19472
|
+
else {
|
|
19473
|
+
const k = (track, a, b2) => [{ position: 0, value: String(a), easing: "AE Smooth" }, { position: 100, value: String(b2), easing: "Linear" }];
|
|
19474
|
+
const camKeys = {
|
|
19475
|
+
"push-in": { scale: k("scale", 1, 1.06) },
|
|
19476
|
+
"pull-out": { scale: k("scale", 1.06, 1) },
|
|
19477
|
+
"drift-left": { translateX: k("x", 0, -36) },
|
|
19478
|
+
"drift-right": { translateX: k("x", 0, 36) },
|
|
19479
|
+
"drift-up": { translateY: k("y", 0, -28) }
|
|
19480
|
+
}[cam];
|
|
19481
|
+
if (camKeys) sceneItem.propertyKeyframes = camKeys;
|
|
19482
|
+
}
|
|
19483
|
+
for (const s of Array.isArray(sc?.sounds) ? sc.sounds : []) if (s?.sfx && soundOk(s.sfx, where)) project.sounds.push({ sfx: s.sfx, on: sceneId, at: r3(num7(s.at, 0)), ...s.gain ? { gain: num7(s.gain, 0) } : {} });
|
|
16552
19484
|
if ((S.sound ?? "auto") === "auto" && si === 0 && firstHeadline && soundOk("whoosh-3", where)) project.sounds.push({ sfx: "whoosh-3", on: sceneId, at: r3(firstHeadline.start) });
|
|
16553
19485
|
project.channels[0].items.push(sceneItem);
|
|
16554
19486
|
total += D;
|
|
@@ -16556,30 +19488,30 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16556
19488
|
});
|
|
16557
19489
|
const music = typeof S.music === "string" ? { src: S.music } : S.music;
|
|
16558
19490
|
if (music?.src) {
|
|
16559
|
-
const
|
|
16560
|
-
if (
|
|
19491
|
+
const f2 = fileFor(music.src, "music", ["audio", "video"]);
|
|
19492
|
+
if (f2) {
|
|
16561
19493
|
const len = Math.max(0.5, total);
|
|
16562
|
-
const from = Math.max(0,
|
|
16563
|
-
if (
|
|
16564
|
-
project.clips.push({ id: "c-music", source: "LOCAL", mediaType: "audio", url:
|
|
19494
|
+
const from = Math.max(0, num7(music.startAt, 0));
|
|
19495
|
+
if (f2.duration && f2.duration - from < len - 0.05) warnings.push(`music: "${f2.name}" is ${f2.duration} s, the video ${r3(len)} s \u2014 it ends early`);
|
|
19496
|
+
project.clips.push({ id: "c-music", source: "LOCAL", mediaType: "audio", url: f2.url, proxyUrl: null, name: f2.name || "music", duration: r3(f2.duration || from + len) });
|
|
16565
19497
|
project.channels.push({ id: "ch-music", name: "Music", items: [{
|
|
16566
19498
|
id: "music",
|
|
16567
19499
|
clipId: "c-music",
|
|
16568
19500
|
trimStart: r3(from),
|
|
16569
|
-
trimEnd: r3(from + Math.min(len,
|
|
16570
|
-
volume:
|
|
16571
|
-
audioFadeIn: r3(
|
|
16572
|
-
audioFadeOut: r3(
|
|
19501
|
+
trimEnd: r3(from + Math.min(len, f2.duration ? f2.duration - from : len)),
|
|
19502
|
+
volume: clamp5(num7(music.volume, 80), 0, 100),
|
|
19503
|
+
audioFadeIn: r3(clamp5(num7(music.fadeIn, 0.3), 0, 10)),
|
|
19504
|
+
audioFadeOut: r3(clamp5(num7(music.fadeOut, 1.2), 0, 10))
|
|
16573
19505
|
}] });
|
|
16574
19506
|
}
|
|
16575
19507
|
}
|
|
16576
|
-
const tFields = fields.filter((
|
|
16577
|
-
const values = Object.fromEntries(fields.filter((
|
|
19508
|
+
const tFields = fields.filter((f2) => f2.text).map((f2) => ({ key: f2.key, label: f2.label, type: "text", targets: [{ itemId: f2.itemId, path: "textContent" }] }));
|
|
19509
|
+
const values = Object.fromEntries(fields.filter((f2) => f2.text).map((f2) => [f2.key, f2.value]));
|
|
16578
19510
|
if (accentTargets.length) {
|
|
16579
19511
|
tFields.push({ key: "accent", label: "Accent colour", type: "color", targets: accentTargets });
|
|
16580
19512
|
values.accent = colors.accent;
|
|
16581
19513
|
}
|
|
16582
|
-
const bgs = fields.filter((
|
|
19514
|
+
const bgs = fields.filter((f2) => f2.bg);
|
|
16583
19515
|
if (bgs.length) {
|
|
16584
19516
|
tFields.push({ key: "backdrop", label: "Background", type: "color", targets: bgs.map((b2) => ({ itemId: b2.itemId, path: "shapeFillColor" })) });
|
|
16585
19517
|
values.backdrop = bgs[0].color;
|
|
@@ -16593,7 +19525,7 @@ function compileScript(script, caps, files = /* @__PURE__ */ new Map()) {
|
|
|
16593
19525
|
init_define_import_meta_env();
|
|
16594
19526
|
|
|
16595
19527
|
// ../skills/greenlight-dash/references/video-design.md
|
|
16596
|
-
var video_design_default = "# Motion design that looks made, not generated\n\nWhat makes a video read as designed rather than generated. The rules hold however the video is built \u2014 a project\nthrough the app's API, `project.json` with the engine's CLI, or a scene script \u2014 so read them before the first version\nand check every version against them.\n\n## Story and words\n\n- **One idea per scene**: 3\u20136 scenes of 2\u20133 s for a short video. A scene that says two things says neither.\n- **At most ~8 words on screen at once, held at least 1 s.** People read about 3 words a second; text that leaves\n before it is read is noise.\n- **9:16**: bigger text, stacked vertically, everything in the middle 80 %.\n\n## Hierarchy\n\n- One headline per scene; supporting text much smaller. Hierarchy comes from size and weight, not colour alone.\n- One accent colour and strong contrast.\n- **No text shadows unless the user asks for one.** A shadow under type reads as a stock default and softens the\n letter edges. When text has to read over footage or a busy picture, get the contrast from the design instead: a\n calmer or darker part of the frame, a caption bar (`textBgEnabled`), or a shape behind the text. So no\n `textShadowX` / `textShadowY` / `textShadowBlur` / `textShadowSpread` on a text layer, no `shadow` property in a\n Text Animator and no `longShadow` effect on text. A shadow already in the user's project stays.\n- Keep text inside the middle 80 % of the frame (in a project: |panX| and |panY| \u2264 40): players and feeds crop the\n edges.\n\n## Timing\n\n- Stagger related elements by 0.06\u20130.12 s.\n- **One focal move at a time.** If everything moves, nothing reads: supporting elements move less and later.\n- Shapes and pictures enter in 0.4\u20130.8 s; exits are faster than entrances.\n- A text animation takes at least 0.8\u20131 s over the whole line, first unit in to last unit settled, unless the video\n needs snap (a beat hit, a glitch flick): faster text flashes past before it is read. The arithmetic per kind of\n animation: video-projects.md, Text animation pace.\n\n## Easing by intent\n\n- Entrances: `AE Out` or `Expo Out` (fast start, soft landing). Camera and big moves: `AE Smooth`.\n- A layer that passes through (in, glide, out): `Smooth Glide` with the `glide` expression \u2014 the Glide keyframe\n presets build it (video-keyframes.md).\n- Playful arrivals: `Back Out` or `Overshoot`. Exits: `Ease In`.\n- `Linear` only for constant motion: tickers, rotation, progress along a path.\n\n## Text animation\n\n- Animate words or letters, never a whole line at once \u2014 the text presets do this.\n- One family per video. Calm presets for lines and captions: Glide Text (`glide-text`), Classic Fade Up\n (`classic-fade-up`), Classic Rise (Mask) (`classic-rise-mask`); Focus Type (`focus-type`) for a title.\n- Punchy presets that tilt or overshoot \u2014 Swing Pop (`swing-pop`), Whip (`whip`), Letter Vortex (`letter-vortex`) \u2014\n only on 1\u20133 words when the video wants energy. On a longer caption they read cheap.\n- Glide Continuous (`glide-continuous`) and Glide Soft Stop (`glide-soft-stop`) never let the text rest: use them\n only when the user asks for drifting text.\n- On a coloured card, pick a preset without a colour band (Classic Rise (Mask), Classic Fade Up): a band's colour can\n clash with the card.\n\n## Transitions and camera\n\n- Vary two or three kinds of transition across a video: one kind everywhere looks like a template, a new one at\n every cut looks random.\n- Camera moves are slow and small unless they are the point. Fast motion looks best at 60 fps with motion blur.\n\n## Sound\n\n- A soft whoosh on each transition and on the first headline is a good base. `data-1` or `selection-data-click` for\n UI-like pops, `cinematic-transition-13` for one big reveal.\n\n## What to check in every version\n\nLook at real frames across the video (a contact sheet) before the user sees it \u2014 the JSON doesn't show these:\nblank or empty frames, overlapping or cut-off text, text too small or gone too fast, nothing moving or everything\nmoving at once, weak hierarchy, clashing colours. Fix them and look again; two rounds are usually enough.\n";
|
|
19528
|
+
var video_design_default = "# Motion design that looks made, not generated\n\nWhat makes a video read as designed rather than generated. The rules hold however the video is built \u2014 a project\nthrough the app's API, `project.json` with the engine's CLI, or a scene script \u2014 so read them before the first version\nand check every version against them.\n\n## Story and words\n\n- **One idea per scene**: 3\u20136 scenes of 2\u20133 s for a short video. A scene that says two things says neither.\n- **At most ~8 words on screen at once, held at least 1 s.** People read about 3 words a second; text that leaves\n before it is read is noise.\n- **9:16**: bigger text, stacked vertically, everything in the middle 80 %.\n\n## Hierarchy\n\n- One headline per scene; supporting text much smaller. Hierarchy comes from size and weight, not colour alone.\n- One accent colour and strong contrast.\n- **No text shadows unless the user asks for one.** A shadow under type reads as a stock default and softens the\n letter edges. When text has to read over footage or a busy picture, get the contrast from the design instead: a\n calmer or darker part of the frame, a caption bar (`textBgEnabled`), or a shape behind the text. So no\n `textShadowX` / `textShadowY` / `textShadowBlur` / `textShadowSpread` on a text layer, no `shadow` property in a\n Text Animator and no `longShadow` effect on text. A shadow already in the user's project stays.\n- Keep text inside the middle 80 % of the frame (in a project: |panX| and |panY| \u2264 40): players and feeds crop the\n edges.\n\n## Timing\n\n- Stagger related elements by 0.06\u20130.12 s.\n- **One focal move at a time.** If everything moves, nothing reads: supporting elements move less and later.\n- Shapes and pictures enter in 0.4\u20130.8 s; exits are faster than entrances.\n- A text animation takes at least 0.8\u20131 s over the whole line, first unit in to last unit settled, unless the video\n needs snap (a beat hit, a glitch flick): faster text flashes past before it is read. The arithmetic per kind of\n animation: video-projects.md, Text animation pace.\n\n## Easing by intent\n\n- Entrances: `AE Out` or `Expo Out` (fast start, soft landing). Camera and big moves: `AE Smooth`.\n- A layer that passes through (in, glide, out): `Smooth Glide` with the `glide` expression \u2014 the Glide keyframe\n presets build it (video-keyframes.md).\n- Playful arrivals: `Back Out` or `Overshoot`. Exits: `Ease In`.\n- `Linear` only for constant motion: tickers, rotation, progress along a path.\n\n## Text animation\n\n- Animate words or letters, never a whole line at once \u2014 the text presets do this.\n- One family per video. Calm presets for lines and captions: Glide Text (`glide-text`), Classic Fade Up\n (`classic-fade-up`), Classic Rise (Mask) (`classic-rise-mask`); Focus Type (`focus-type`) for a title.\n- Punchy presets that tilt or overshoot \u2014 Swing Pop (`swing-pop`), Whip (`whip`), Letter Vortex (`letter-vortex`) \u2014\n only on 1\u20133 words when the video wants energy. On a longer caption they read cheap.\n- Glide Continuous (`glide-continuous`) and Glide Soft Stop (`glide-soft-stop`) never let the text rest: use them\n only when the user asks for drifting text.\n- On a coloured card, pick a preset without a colour band (Classic Rise (Mask), Classic Fade Up): a band's colour can\n clash with the card.\n\n## Transitions and camera\n\n- Vary two or three kinds of transition across a video: one kind everywhere looks like a template, a new one at\n every cut looks random.\n- Camera moves are slow and small unless they are the point. Fast motion looks best at 60 fps with motion blur.\n- A real 3D camera earns its place on one or two moments, not every scene: a zoom onto the one thing a scene is about\n (a number, the button a screen recording clicks; 1.3\u20132\xD7, then back out), or a slow orbit or push that shows depth.\n- Depth needs layers at different distances: a soft shape or picture far behind, the words at the front. Depth of\n field reads only then; keep the words sharp.\n- Keep an orbit in front of flat layers (within about \xB170\xB0): text turned further goes edge-on, then mirrored.\n\n## Sound\n\n- A soft whoosh on each transition and on the first headline is a good base. `data-1` or `selection-data-click` for\n UI-like pops, `cinematic-transition-13` for one big reveal.\n\n## What to check in every version\n\nLook at real frames across the video (a contact sheet) before the user sees it \u2014 the JSON doesn't show these:\nblank or empty frames, overlapping or cut-off text, text too small or gone too fast, nothing moving or everything\nmoving at once, weak hierarchy, clashing colours. Fix them and look again; two rounds are usually enough.\n";
|
|
16597
19529
|
|
|
16598
19530
|
// src/motionEngine/sceneScriptGuide.js
|
|
16599
19531
|
var PREFERRED = {
|
|
@@ -16606,6 +19538,19 @@ var refOf = (host, topic) => host === "chat" ? `get_motion_reference "${topic}"`
|
|
|
16606
19538
|
function scriptFormatGuide(host = "chat") {
|
|
16607
19539
|
return `## The scene script
|
|
16608
19540
|
|
|
19541
|
+
**Before the script, a direction.** Presets make a video move; they don't give it an idea. Decide in a few lines first,
|
|
19542
|
+
and keep them next to the script:
|
|
19543
|
+
- **The idea**: ONE picture from the subject's own world (its object, place or action: a coffee shop's steam and cup rim,
|
|
19544
|
+
a budgeting app's receipt printing line by line, a running app's track the scenes run along), carried through every scene.
|
|
19545
|
+
- **The palette**: background, text, one accent, plus only the colours that carry meaning (good / bad, before / after).
|
|
19546
|
+
Light or dark on purpose.
|
|
19547
|
+
- **The type**: a big headline against a small support line; aligned left or centred on purpose, not by default.
|
|
19548
|
+
- **Each scene**: what it SHOWS besides its words, built from shapes, lines, \`svg\` you write, keyframes and the camera.
|
|
19549
|
+
The same line centred on a gradient in every scene reads as a template. Judge the stills against the direction, not
|
|
19550
|
+
only for mistakes.${host === "cli" ? `
|
|
19551
|
+
What a script can't say (a custom GLSL shader on a shape or the background, words dipping under a surface with a track
|
|
19552
|
+
matte, masks, particles, 3D) goes on from \`project.json\`: SKILL.md, "Beyond the script".` : ""}
|
|
19553
|
+
|
|
16609
19554
|
\`\`\`json
|
|
16610
19555
|
{
|
|
16611
19556
|
"title": "Bean There \u2014 opening day",
|
|
@@ -16622,8 +19567,10 @@ function scriptFormatGuide(host = "chat") {
|
|
|
16622
19567
|
{ "type": "pill", "w": 560, "h": 116, "color": "accent", "start": 0.15 },
|
|
16623
19568
|
{ "type": "text", "text": "Opening day: 2 for 1", "role": "body", "color": "#1b120d", "start": 0.3, "enter": "pop-overshoot", "sound": "data-1" } ] },
|
|
16624
19569
|
{ "name": "Close", "duration": 2.4, "transition": "Gradient Glow Stack", "background": { "shader": "silk", "look": "sunset" },
|
|
19570
|
+
"camera": { "move": "cm-quiet-push", "dof": { "focus": "street", "aperture": 50 } },
|
|
16625
19571
|
"elements": [
|
|
16626
|
-
{ "type": "
|
|
19572
|
+
{ "type": "circle", "w": 420, "color": "#fff3e0", "opacity": 0.3, "at": [330, -60], "z": -700 },
|
|
19573
|
+
{ "id": "street", "type": "text", "text": "12 Elm Street", "role": "headline", "textAnimation": "word-swing" },
|
|
16627
19574
|
{ "type": "text", "text": "OPEN 7 \u2013 7", "role": "label", "at": [0, 130], "start": 0.8, "letterSpacing": 6 } ] }
|
|
16628
19575
|
]
|
|
16629
19576
|
}
|
|
@@ -16637,15 +19584,36 @@ function scriptFormatGuide(host = "chat") {
|
|
|
16637
19584
|
- **line**: from \`at\` to \`to\`, \`width\`, drawn on (\`draw\`: false to skip).
|
|
16638
19585
|
- **image \xB7 video**: \`src\` (${host === "chat" ? "a link or the user's file \u2014 below" : "a file in the folder's assets/ as `asset:<name>`, or an https link"}) or, for an image, \`svg\` (SVG markup you write); \`w\` or \`h\`
|
|
16639
19586
|
(px; the other follows the file's shape) or \`fit\` cover | contain
|
|
16640
|
-
(default: a video covers the frame, a picture sits at 70 % of contain), \`rotate\` (deg)
|
|
19587
|
+
(default: a video covers the frame, a picture sits at 70 % of contain), \`rotate\` (deg). A video also takes
|
|
16641
19588
|
\`startAt\` (s into the file) and \`volume\` (0\u2013100, default muted). No entrance unless you set \`enter\`.
|
|
16642
19589
|
- **Keyframes on any element** \u2014 your own motion: \`"keyframes": { "x": [[0, -400], [0.8, 0, "Smooth Glide"]], "scale": [[0, 0.9], [2, 1.05]] }\`
|
|
16643
19590
|
\u2014 tracks x, y (px offsets from \`at\`), scale, rotate (deg), opacity (0\u20131); each key [seconds into the element, value, easing of the
|
|
16644
19591
|
segment after it \u2014 Smooth Glide, AE Smooth, Expo Out, Back Out, Elastic, Linear \u2026]. They replace an entrance's keys on the same track.
|
|
16645
19592
|
- **Every element**: \`start\` / \`end\` (s within its scene; default: staggered 0.12 s, to the scene's end), \`enter\` / \`exit\` (keyframe presets, \`direction\` up | down | left | right
|
|
16646
|
-
for directional ones), \`effects\`: [{ "id", "params"? }], \`sound\` (played at its start), \`id\`, \`label\` (its name in the page's options)
|
|
16647
|
-
|
|
19593
|
+
for directional ones), \`effects\`: [{ "id", "params"? }], \`sound\` (played at its start), \`id\`, \`label\` (its name in the page's options), \`opacity\` (0\u20131),
|
|
19594
|
+
\`z\` (px of depth, + toward the viewer, default 0: a real camera's dolly, orbit and depth of field separate layers by it), \`locked\`: true
|
|
19595
|
+
(the scene's camera doesn't move it: a logo, a caption). Keyframes also take a \`z\` track.
|
|
19596
|
+
- **Scene**: \`duration\` (s), \`transition\` into it (default Cross Fade, 0.6 s; \`transitionDuration\`), \`camera\` (below; default push-in),
|
|
16648
19597
|
\`background\` (overrides style), \`sounds\`: [{ "sfx", "at" }]. Script-level \`sound\`: auto (default: a whoosh on each transition and on the first headline) | none.
|
|
19598
|
+
**A transition overlaps the scenes on both sides**, so the video lasts the scenes' durations added up MINUS every
|
|
19599
|
+
transition: five 3 s scenes with the default 0.6 s transitions run 15 \u2212 4 \xD7 0.6 = 12.6 s. To hit a length, add the
|
|
19600
|
+
transitions back to the scenes (${host === "chat" ? "build_video" : "`script`"} prints the total).
|
|
19601
|
+
- **camera** \u2014 a quick move of the whole scene: ${CAMERAS.join(" | ")}. Or a REAL camera that films the scene's elements in 3D (the
|
|
19602
|
+
Video Editor's own camera layer; the scene's backdrop stays put, so its edge never shows). One of:
|
|
19603
|
+
- \`{ "move": id }\` \u2014 a camera move from the catalogue (or just \`"camera": "cm-quiet-push"\`), spread over the scene;
|
|
19604
|
+
- \`{ "rig": id }\` or \`{ "rig": { "preset"?: id, "type"?, \u2026fields } }\` \u2014 an orbit, crane, push-in, truck, spiral, handheld or follow move
|
|
19605
|
+
(\`"camera": "heroOrbit"\` works too). A \`follow\` rig rides elements: \`"targets": [element ids]\`. Layers are flat: keep an orbit within about
|
|
19606
|
+
\xB170\xB0 of the front (heroOrbit, slowReveal) or they turn edge-on and read mirrored;
|
|
19607
|
+
- \`{ "zooms": [{ "start", "end", "at"?, "zoom"? }], "feel"? }\` \u2014 zoom in on spots and back out, like a screen recording's Smart Zoom: \`at\` as for
|
|
19608
|
+
elements (the spot to frame, clamped so the view stays inside the frame), \`zoom\` 1.05\u20134 (default the feel's, about 1.45), each zoom at least
|
|
19609
|
+
0.4 s and none overlapping; \`feel\` studio | calm | precise | lively | follow (the spring). Put a screen recording in as a video element (or the
|
|
19610
|
+
scene's video background) and zoom on what it shows;
|
|
19611
|
+
- \`{ "keyframes": { zoom | x | y | roll | dolly | tiltX | tiltY | focusZ: [[seconds, value, easing?], \u2026] } }\` \u2014 your own move: zoom (\xD7, 1 = none),
|
|
19612
|
+
x / y (px the camera pans right / down), roll / tilt (deg), dolly (px toward the scene: it changes perspective, so layers at different \`z\` part).
|
|
19613
|
+
With a \`move\` they replace its track; a rig drives x, y, roll, dolly and tilt itself and zooms drive zoom, x and y, so key only the others there.
|
|
19614
|
+
- plus \`"dof": { "focus": element id | "focusZ": px, "aperture"?: 40, "blur"?: 100 }\` (or \`true\`) \u2014 depth of field: what sits at another \`z\` blurs;
|
|
19615
|
+
the focus stays on its subject while the camera dollies or rides a rig. \`focusZ\` keys are a rack focus (measured from where the camera starts).
|
|
19616
|
+
- \`start\` / \`end\` (s in the scene; default the whole scene; outside them the camera rests). Keys and zooms count seconds into the camera.
|
|
16649
19617
|
- **background**: a colour, a CSS gradient, { "shader": id, "look"? } \u2014 shaders: ${SHADERS.join(", ")} (looks: ${refOf(host, "video-backgrounds")}) \u2014 or { "image": src } / { "video": src } (covers the frame; b-roll behind the scene).
|
|
16650
19618
|
- **music** (script level): \`"music": { "src": file, "volume": 80, "fadeIn": 0.3, "fadeOut": 1.2, "startAt": 0 }\` \u2014 one track under the video.
|
|
16651
19619
|
|
|
@@ -16658,6 +19626,7 @@ function designGuide() {
|
|
|
16658
19626
|
}
|
|
16659
19627
|
function catalogueGuide(caps, host = "chat") {
|
|
16660
19628
|
const cat = presetCatalog();
|
|
19629
|
+
const cam = cameraCatalog();
|
|
16661
19630
|
const star = (line, ids) => ids.some((id) => line.startsWith(`\`${id}\``)) ? `\u2605 ${line}` : line;
|
|
16662
19631
|
const sounds = {};
|
|
16663
19632
|
for (const [id, sd] of Object.entries(caps?.sounds?.library || {})) if (sd.category !== "Voice Clips & Memes") (sounds[sd.category] ||= []).push(`${id} (${sd.duration}s)`);
|
|
@@ -16672,6 +19641,12 @@ function catalogueGuide(caps, host = "chat") {
|
|
|
16672
19641
|
|
|
16673
19642
|
**Transitions** (\u2605 = preferred): ${CROSS_TRANSITIONS.map((t) => PREFERRED.transitions.includes(t) ? `\u2605 ${t}` : t).join(" \xB7 ")}
|
|
16674
19643
|
|
|
19644
|
+
**Camera moves** (\`camera.move\`; spread over the camera's length): ${cam.moves.join(" \xB7 ")}
|
|
19645
|
+
|
|
19646
|
+
**Camera rigs** (\`camera.rig\`; the fields you can override: type ${cam.rigTypes.join(" | ")}, aim ${cam.rigAims.join(" | ")}, ${cam.rigFields.join(", ")}): ${cam.rigs.join(" \xB7 ")}
|
|
19647
|
+
|
|
19648
|
+
**Zoom feels** (\`camera.feel\`): ${cam.feels.join(" \xB7 ")}. Full camera layers (paths, Smart Zoom on a recording's clicks, the drawn cursor): ${refOf(host, "video-camera")}.
|
|
19649
|
+
|
|
16675
19650
|
**Overlay effects** (\`effects\`): ${fx(caps?.effects?.overlay)}
|
|
16676
19651
|
|
|
16677
19652
|
**Layer effects** (\`effects\`): ${fx(caps?.effects?.layer)}
|
|
@@ -16735,6 +19710,7 @@ export {
|
|
|
16735
19710
|
MIME,
|
|
16736
19711
|
PREFERRED,
|
|
16737
19712
|
SHADERS,
|
|
19713
|
+
cameraCatalog,
|
|
16738
19714
|
catalogueGuide,
|
|
16739
19715
|
checkTemplate,
|
|
16740
19716
|
compileScript,
|