@vosjs/studio-core 0.4.1 → 0.5.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/dist/index.d.ts +271 -6
- package/dist/index.js +504 -45
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -153,6 +153,7 @@ var DEFAULT_FRAME_STYLE = BASE_FRAME_STYLE;
|
|
|
153
153
|
var FRAME_BORDER_DEFAULT = 0.35;
|
|
154
154
|
var FRAME_BORDER_WIDTH_DEFAULT = 1.5;
|
|
155
155
|
var FRAME_BORDER_COLOR_DEFAULT = "#ffffff";
|
|
156
|
+
var CARD_EDGE_OVERDRAW = 1;
|
|
156
157
|
function platformBarKind(platform) {
|
|
157
158
|
return platform === "windows" || platform === "linux" ? "windows-light" : "mac-light";
|
|
158
159
|
}
|
|
@@ -1099,7 +1100,7 @@ import {
|
|
|
1099
1100
|
lerpArray,
|
|
1100
1101
|
sample,
|
|
1101
1102
|
segmentRate as segmentRate2,
|
|
1102
|
-
sortKeyframes,
|
|
1103
|
+
sortKeyframes as sortKeyframes2,
|
|
1103
1104
|
sourceToTimeline as sourceToTimeline2,
|
|
1104
1105
|
splitBySpeed,
|
|
1105
1106
|
totalDuration
|
|
@@ -1107,27 +1108,33 @@ import {
|
|
|
1107
1108
|
import { timelineRuntimeCode as timelineRuntimeCode2 } from "@vosjs/timeline/bundle";
|
|
1108
1109
|
|
|
1109
1110
|
// src/layout.ts
|
|
1110
|
-
function computeCardLayout(frame, video, W, H) {
|
|
1111
|
+
function computeCardLayout(frame, video, W, H, zoomFocus) {
|
|
1111
1112
|
const s = H / 1080;
|
|
1112
1113
|
const pad = (frame.padding || 0) * s;
|
|
1114
|
+
const ins = frame.inset ?? {};
|
|
1115
|
+
const padL = ins.left == null ? pad : ins.left * W;
|
|
1116
|
+
const padR = ins.right == null ? pad : ins.right * W;
|
|
1117
|
+
const padT = ins.top == null ? pad : ins.top * H;
|
|
1118
|
+
const padB = ins.bottom == null ? pad : ins.bottom * H;
|
|
1113
1119
|
const bar = frame.browserBar;
|
|
1114
1120
|
const vw = video.width || 16;
|
|
1115
1121
|
const vh = video.height || 9;
|
|
1116
1122
|
const fitCover = frame.fit === "cover";
|
|
1117
1123
|
const cf = fitCover ? 1 : Math.min(1, W / H / (vw / vh));
|
|
1118
1124
|
const barH = bar.kind !== "none" ? (bar.height || 44) * s * cf : 0;
|
|
1119
|
-
const availW = Math.max(1, W -
|
|
1120
|
-
const availH = Math.max(1, H -
|
|
1125
|
+
const availW = Math.max(1, W - padL - padR);
|
|
1126
|
+
const availH = Math.max(1, H - padT - padB - barH);
|
|
1121
1127
|
if (fitCover) {
|
|
1122
1128
|
const sc2 = Math.max(availW / vw, availH / vh);
|
|
1123
1129
|
const dw2 = vw * sc2;
|
|
1124
1130
|
const dh2 = vh * sc2;
|
|
1125
|
-
const
|
|
1126
|
-
const
|
|
1127
|
-
const
|
|
1131
|
+
const follow = frame.focusFollow === "camera" && zoomFocus ? zoomFocus : null;
|
|
1132
|
+
const fcx = clamp013(follow ? follow.cx : frame.focus?.cx ?? 0.5);
|
|
1133
|
+
const fcy = clamp013(follow ? follow.cy : frame.focus?.cy ?? 0.5);
|
|
1134
|
+
const vTop = padT + barH;
|
|
1128
1135
|
const dx2 = Math.min(
|
|
1129
|
-
|
|
1130
|
-
Math.max(
|
|
1136
|
+
padL,
|
|
1137
|
+
Math.max(padL + availW - dw2, padL + availW / 2 - fcx * dw2)
|
|
1131
1138
|
);
|
|
1132
1139
|
const dy2 = Math.min(
|
|
1133
1140
|
vTop,
|
|
@@ -1140,8 +1147,8 @@ function computeCardLayout(frame, video, W, H) {
|
|
|
1140
1147
|
dy: dy2,
|
|
1141
1148
|
dw: dw2,
|
|
1142
1149
|
dh: dh2,
|
|
1143
|
-
cardX:
|
|
1144
|
-
cardY:
|
|
1150
|
+
cardX: padL,
|
|
1151
|
+
cardY: padT,
|
|
1145
1152
|
cardW: availW,
|
|
1146
1153
|
cardH: availH + barH
|
|
1147
1154
|
};
|
|
@@ -1149,8 +1156,8 @@ function computeCardLayout(frame, video, W, H) {
|
|
|
1149
1156
|
const sc = Math.min(availW / vw, availH / vh);
|
|
1150
1157
|
const dw = vw * sc;
|
|
1151
1158
|
const dh = vh * sc;
|
|
1152
|
-
const dx = (
|
|
1153
|
-
const dy = (
|
|
1159
|
+
const dx = padL + (availW - dw) / 2;
|
|
1160
|
+
const dy = padT + barH + (availH - dh) / 2;
|
|
1154
1161
|
return {
|
|
1155
1162
|
W,
|
|
1156
1163
|
H,
|
|
@@ -2741,6 +2748,117 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
2741
2748
|
|
|
2742
2749
|
}`;
|
|
2743
2750
|
|
|
2751
|
+
// src/lower/motion.ts
|
|
2752
|
+
import { sortKeyframes } from "@vosjs/timeline";
|
|
2753
|
+
var ENTRANCE_SECONDS = 1.2;
|
|
2754
|
+
var END_CARD_SECONDS = 2.5;
|
|
2755
|
+
var END_CARD_RECEDE = 0.7;
|
|
2756
|
+
var TILT_IN_POSE = [-9, 14];
|
|
2757
|
+
var PULL_OUT_LEVEL = 1.32;
|
|
2758
|
+
var HOLD_SOURCE_SPAN = 2e-3;
|
|
2759
|
+
function entranceSeconds(e) {
|
|
2760
|
+
if (!e || e.kind === "none") return 0;
|
|
2761
|
+
return Math.max(0.2, Math.min(3, e.seconds ?? ENTRANCE_SECONDS));
|
|
2762
|
+
}
|
|
2763
|
+
function withHolds(docSegments, rated) {
|
|
2764
|
+
const out = [];
|
|
2765
|
+
const pending = docSegments.filter((s) => typeof s.hold === "number" && s.hold > 0).map((s) => ({ out: s.out, hold: s.hold, used: false }));
|
|
2766
|
+
for (let i = 0; i < rated.length; i++) {
|
|
2767
|
+
const piece = rated[i];
|
|
2768
|
+
out.push(piece);
|
|
2769
|
+
const next = rated[i + 1];
|
|
2770
|
+
for (const p of pending) {
|
|
2771
|
+
if (p.used) continue;
|
|
2772
|
+
const endsHere = Math.abs(piece.out - p.out) < 1e-9;
|
|
2773
|
+
const nextContinues = next && Math.abs(next.in - p.out) < 1e-9 && next.out > p.out;
|
|
2774
|
+
if (endsHere && !nextContinues) {
|
|
2775
|
+
out.push({
|
|
2776
|
+
in: p.out - HOLD_SOURCE_SPAN,
|
|
2777
|
+
out: p.out,
|
|
2778
|
+
rate: HOLD_SOURCE_SPAN / p.hold
|
|
2779
|
+
});
|
|
2780
|
+
p.used = true;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
return out;
|
|
2785
|
+
}
|
|
2786
|
+
function entranceTiltKeyframes(e) {
|
|
2787
|
+
const s = entranceSeconds(e);
|
|
2788
|
+
if (!s || e?.kind !== "tilt-in") return [];
|
|
2789
|
+
return [
|
|
2790
|
+
{ t: 0, value: [...TILT_IN_POSE], ease: "none" },
|
|
2791
|
+
{ t: s, value: [0, 0], ease: "power3.out" }
|
|
2792
|
+
];
|
|
2793
|
+
}
|
|
2794
|
+
function entranceZoomKeyframes(e) {
|
|
2795
|
+
const s = entranceSeconds(e);
|
|
2796
|
+
if (!s) return [];
|
|
2797
|
+
if (e?.kind === "pull-out")
|
|
2798
|
+
return [
|
|
2799
|
+
{ t: 0, value: [PULL_OUT_LEVEL, 0.5, 0.42], ease: "none" },
|
|
2800
|
+
{ t: s, value: [1, 0.5, 0.5], ease: "power3.out" }
|
|
2801
|
+
];
|
|
2802
|
+
return [
|
|
2803
|
+
{ t: 0, value: [1, 0.5, 0.5], ease: "none" },
|
|
2804
|
+
{ t: s, value: [1, 0.5, 0.5], ease: "none" }
|
|
2805
|
+
];
|
|
2806
|
+
}
|
|
2807
|
+
function prependEntrance(track, head) {
|
|
2808
|
+
if (!head.length) return track;
|
|
2809
|
+
const end = head[head.length - 1].t;
|
|
2810
|
+
const rest = (track?.keyframes ?? []).filter((k) => k.t > end + 0.1);
|
|
2811
|
+
return { keyframes: sortKeyframes([...head, ...rest]) };
|
|
2812
|
+
}
|
|
2813
|
+
function cardPoseTrack(e, endStart, endSeconds) {
|
|
2814
|
+
const keyframes = [];
|
|
2815
|
+
const s = entranceSeconds(e);
|
|
2816
|
+
if (s) {
|
|
2817
|
+
const from = e?.kind === "rise" ? [0.96, 0.08, 0.7] : e?.kind === "pull-out" ? [1, 0, 1] : [0.94, 0.05, 0.7];
|
|
2818
|
+
keyframes.push({ t: 0, value: from, ease: "none" });
|
|
2819
|
+
keyframes.push({ t: s, value: [1, 0, 1], ease: "power3.out" });
|
|
2820
|
+
}
|
|
2821
|
+
if (endStart !== null && endSeconds > 0) {
|
|
2822
|
+
const t0 = Math.max(endStart, s);
|
|
2823
|
+
keyframes.push({ t: t0, value: [1, 0, 1], ease: "none" });
|
|
2824
|
+
keyframes.push({
|
|
2825
|
+
t: t0 + Math.min(END_CARD_RECEDE, endSeconds * 0.4),
|
|
2826
|
+
value: [0.9, -0.02, 0.22],
|
|
2827
|
+
ease: "power2.out"
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
if (!keyframes.length) return void 0;
|
|
2831
|
+
return { keyframes: sortKeyframes(keyframes) };
|
|
2832
|
+
}
|
|
2833
|
+
function expandEndCard(doc, outputDuration) {
|
|
2834
|
+
const card = doc.endCard;
|
|
2835
|
+
if (!card || !doc.segments.length) return { doc, endStart: null, seconds: 0 };
|
|
2836
|
+
const seconds = Math.max(1, Math.min(8, card.seconds ?? END_CARD_SECONDS));
|
|
2837
|
+
const segments = doc.segments.map(
|
|
2838
|
+
(s, i) => i === doc.segments.length - 1 ? { ...s, hold: (s.hold ?? 0) + seconds } : s
|
|
2839
|
+
);
|
|
2840
|
+
const endStart = outputDuration;
|
|
2841
|
+
const clips = [];
|
|
2842
|
+
const text = (id, body, preset, y, delay) => ({
|
|
2843
|
+
id,
|
|
2844
|
+
kind: "text",
|
|
2845
|
+
text: body,
|
|
2846
|
+
preset,
|
|
2847
|
+
start: endStart + delay,
|
|
2848
|
+
duration: Math.max(0.3, seconds - delay),
|
|
2849
|
+
transform: { x: 0.5, y, scale: 1, rotation: 0 },
|
|
2850
|
+
enter: "rise",
|
|
2851
|
+
exit: "none",
|
|
2852
|
+
align: "center",
|
|
2853
|
+
...card.ink ? { color: card.ink } : {}
|
|
2854
|
+
});
|
|
2855
|
+
if (card.headline?.trim()) clips.push(text("endcard-title", card.headline.trim(), "title", 0.44, 0.35));
|
|
2856
|
+
if (card.sub?.trim()) clips.push(text("endcard-sub", card.sub.trim(), "caption", 0.57, 0.5));
|
|
2857
|
+
if (card.wordmark?.trim()) clips.push(text("endcard-mark", card.wordmark.trim(), "label", 0.88, 0.65));
|
|
2858
|
+
const overlays = [...doc.overlays ?? [], ...clips];
|
|
2859
|
+
return { doc: { ...doc, segments, overlays }, endStart, seconds };
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2744
2862
|
// src/lower/extractClicks.ts
|
|
2745
2863
|
import { segmentRate, sourceToTimeline } from "@vosjs/timeline";
|
|
2746
2864
|
var CLICK_FX_PRE = 0.06;
|
|
@@ -2845,7 +2963,7 @@ var CAM_PAN_EASE = ZOOM_PAN_EASE;
|
|
|
2845
2963
|
var PROGRAM_DURATION = 1;
|
|
2846
2964
|
function ratedSegments(doc) {
|
|
2847
2965
|
const segs = isRecordingDoc(doc) ? doc.segments.length ? doc.segments : [{ in: 0, out: doc.source.meta.durationMs / 1e3 }] : [{ in: 0, out: programDuration(doc) }];
|
|
2848
|
-
return splitBySpeed(segs, doc.speed ?? []);
|
|
2966
|
+
return withHolds(segs, splitBySpeed(segs, doc.speed ?? []));
|
|
2849
2967
|
}
|
|
2850
2968
|
function durationSec(doc, rated) {
|
|
2851
2969
|
const trimmed = totalDuration(rated);
|
|
@@ -2937,7 +3055,7 @@ function zoomTrackFromDoc(zoom, segments, style = ZOOM_STYLES[DEFAULT_ZOOM_STYLE
|
|
|
2937
3055
|
chained = false;
|
|
2938
3056
|
}
|
|
2939
3057
|
}
|
|
2940
|
-
return { keyframes:
|
|
3058
|
+
return { keyframes: sortKeyframes2(keyframes) };
|
|
2941
3059
|
}
|
|
2942
3060
|
function sameVec(a, b) {
|
|
2943
3061
|
return a.length === b.length && a.every((v, i) => Math.abs(v - b[i]) < 1e-6);
|
|
@@ -2978,7 +3096,7 @@ function tiltTrackFromDoc(tilt, segments, motion = {}) {
|
|
|
2978
3096
|
chained = false;
|
|
2979
3097
|
}
|
|
2980
3098
|
}
|
|
2981
|
-
return { keyframes:
|
|
3099
|
+
return { keyframes: sortKeyframes2(keyframes) };
|
|
2982
3100
|
}
|
|
2983
3101
|
function camRestPose(cam, W, H = 1080) {
|
|
2984
3102
|
const r = camBubbleRect(cam, W, H);
|
|
@@ -3020,7 +3138,7 @@ function camTrackFromDoc(cam, spans, segments, W, H = 1080) {
|
|
|
3020
3138
|
chained = false;
|
|
3021
3139
|
}
|
|
3022
3140
|
}
|
|
3023
|
-
return { keyframes:
|
|
3141
|
+
return { keyframes: sortKeyframes2(keyframes) };
|
|
3024
3142
|
}
|
|
3025
3143
|
function camRectFromPose(pose, cam, W, H = 1080) {
|
|
3026
3144
|
const s = H / 1080;
|
|
@@ -3060,7 +3178,7 @@ function motionTrack(base, keys, dur) {
|
|
|
3060
3178
|
spanEase(k.ease, MOTION_EASE)
|
|
3061
3179
|
);
|
|
3062
3180
|
}
|
|
3063
|
-
return { keyframes:
|
|
3181
|
+
return { keyframes: sortKeyframes2(keyframes) };
|
|
3064
3182
|
}
|
|
3065
3183
|
function overlayMotionBase(o) {
|
|
3066
3184
|
return [
|
|
@@ -3750,6 +3868,14 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3750
3868
|
// strip is part of the card: it takes barH from the available height and the video
|
|
3751
3869
|
// sits below it \u2014 bar + video share the rounded clip and zoom together.
|
|
3752
3870
|
var pad = (frame.padding || 0) * s
|
|
3871
|
+
// Per-side placement (frame.inset: fractions of the frame, a negative side
|
|
3872
|
+
// bleeds the card past the edge) overrides the symmetric padding on the
|
|
3873
|
+
// sides it names; absent sides keep pad, so the old math falls out.
|
|
3874
|
+
var ipIns = frame.inset || {}
|
|
3875
|
+
var ipL = ipIns.left == null ? pad : ipIns.left * W
|
|
3876
|
+
var ipR = ipIns.right == null ? pad : ipIns.right * W
|
|
3877
|
+
var ipT = ipIns.top == null ? pad : ipIns.top * H
|
|
3878
|
+
var ipB = ipIns.bottom == null ? pad : ipIns.bottom * H
|
|
3753
3879
|
var bar = frame.browserBar || {}
|
|
3754
3880
|
// Window takes carry a viewport crop (drawImage source rect, capture px) that
|
|
3755
3881
|
// removes the real browser chrome \u2014 the card's source dims are then the CROP
|
|
@@ -3775,35 +3901,48 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3775
3901
|
var fitCover = frame.fit === 'cover'
|
|
3776
3902
|
var cf = fitCover ? 1 : Math.min(1, (W / H) / (vw / vh))
|
|
3777
3903
|
var s2 = s * cf
|
|
3904
|
+
// current zoom \u2014 a standard keyframe track in OUTPUT time (hold + arrival pairs
|
|
3905
|
+
// expanded by the lowering), sampled with the shared deterministic interpolator.
|
|
3906
|
+
// Sampled here, before the rect math, so a cover crop can follow it.
|
|
3907
|
+
var lvl = 1, zx = 0.5, zy = 0.5
|
|
3908
|
+
var zt = d.zoomTrack
|
|
3909
|
+
if (zt && zt.keyframes && zt.keyframes.length) {
|
|
3910
|
+
var z = TL.sample(zt, t, TL.lerpArray)
|
|
3911
|
+
lvl = z[0]; zx = z[1]; zy = z[2]
|
|
3912
|
+
}
|
|
3778
3913
|
var barH = bar.kind && bar.kind !== 'none' ? (bar.height || 44) * s2 : 0
|
|
3779
|
-
var availW = Math.max(1, W -
|
|
3914
|
+
var availW = Math.max(1, W - ipL - ipR), availH = Math.max(1, H - ipT - ipB - barH)
|
|
3780
3915
|
var sc, dw, dh, dx, dy, cardX, cardY, cardW, cardH
|
|
3781
3916
|
if (fitCover) {
|
|
3782
3917
|
sc = Math.max(availW / vw, availH / vh)
|
|
3783
3918
|
dw = vw * sc; dh = vh * sc
|
|
3784
|
-
cardX =
|
|
3919
|
+
cardX = ipL; cardY = ipT; cardW = availW; cardH = availH + barH
|
|
3785
3920
|
var fcv = frame.focus || {}
|
|
3786
3921
|
var fcx = fcv.cx == null ? 0.5 : Math.max(0, Math.min(1, fcv.cx))
|
|
3787
3922
|
var fcy = fcv.cy == null ? 0.5 : Math.max(0, Math.min(1, fcv.cy))
|
|
3788
|
-
|
|
3923
|
+
// focusFollow: the crop keeps the camera's focus in frame (a 9:16 cut).
|
|
3924
|
+
if (frame.focusFollow === 'camera' && zt && zt.keyframes && zt.keyframes.length) { fcx = zx; fcy = zy }
|
|
3925
|
+
var vTop = ipT + barH
|
|
3789
3926
|
dx = Math.min(cardX, Math.max(cardX + availW - dw, cardX + availW / 2 - fcx * dw))
|
|
3790
3927
|
dy = Math.min(vTop, Math.max(vTop + availH - dh, vTop + availH / 2 - fcy * dh))
|
|
3791
3928
|
} else {
|
|
3792
3929
|
sc = Math.min(availW / vw, availH / vh)
|
|
3793
3930
|
dw = vw * sc; dh = vh * sc
|
|
3794
|
-
|
|
3931
|
+
// Centred inside the inset area (equal to (W - dw) / 2 when no side is
|
|
3932
|
+
// inset, the old formula).
|
|
3933
|
+
dx = ipL + (availW - dw) / 2; dy = ipT + barH + (availH - dh) / 2
|
|
3795
3934
|
cardX = dx; cardY = dy - barH; cardW = dw; cardH = dh + barH
|
|
3796
3935
|
}
|
|
3797
3936
|
var radius = (frame.radius || 0) * s2
|
|
3798
3937
|
var shadow = frame.shadow || 0
|
|
3799
|
-
|
|
3800
|
-
//
|
|
3801
|
-
//
|
|
3802
|
-
var
|
|
3803
|
-
var
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3938
|
+
// The shadow's colour (a #rrggbb; the strengths are its alpha) and the
|
|
3939
|
+
// optional tight CONTACT layer that makes a light card sit on a light
|
|
3940
|
+
// ground.
|
|
3941
|
+
var shC = frame.shadowContact || 0
|
|
3942
|
+
var shRgb = '0,0,0'
|
|
3943
|
+
var shHex = frame.shadowColor
|
|
3944
|
+
if (typeof shHex === 'string' && /^#[0-9a-fA-F]{6}$/.test(shHex)) {
|
|
3945
|
+
shRgb = parseInt(shHex.slice(1, 3), 16) + ',' + parseInt(shHex.slice(3, 5), 16) + ',' + parseInt(shHex.slice(5, 7), 16)
|
|
3807
3946
|
}
|
|
3808
3947
|
|
|
3809
3948
|
function rr(x, y, w, h, rd, cx) {
|
|
@@ -3815,19 +3954,49 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3815
3954
|
c.save()
|
|
3816
3955
|
// d.zoomSuppressed = editor aiming mode: the host merges it into ctx.data
|
|
3817
3956
|
// while the focus overlay is up so the full frame renders. Never persisted.
|
|
3957
|
+
// ceZs = the zoom transform's device scale, for the two values below that
|
|
3958
|
+
// are sized in DEVICE pixels inside a scaled user space.
|
|
3959
|
+
var ceZs = 1
|
|
3818
3960
|
if (lvl > 1.001 && !d.zoomSuppressed) {
|
|
3819
3961
|
var fx = dx + zx * dw, fy = dy + zy * dh
|
|
3820
3962
|
c.translate(fx, fy); c.scale(lvl, lvl); c.translate(-fx, -fy)
|
|
3821
|
-
|
|
3963
|
+
ceZs = lvl
|
|
3964
|
+
}
|
|
3965
|
+
// The card's shadows are cast by a body drawn OFF-CANVAS and brought back
|
|
3966
|
+
// by shadowOffsetX alone. Shadow offsets are device space, untouched by
|
|
3967
|
+
// the transform, so the body moves by shD / ceZs in user space and the
|
|
3968
|
+
// offset is shD; shD clears the canvas by the card's own device size, so
|
|
3969
|
+
// no part of the body lands on it at any zoom. Painting the body in place
|
|
3970
|
+
// (opaque black under the footage) left a 1px dark seam along every
|
|
3971
|
+
// straight card edge at a fractional coordinate: the fill's anti-aliased
|
|
3972
|
+
// rim showed through where the clipped footage did not cover it, and the
|
|
3973
|
+
// corners, drawn by the clip alone, hid theirs inside the arc.
|
|
3974
|
+
var shD = ceZs * (cardW + cardH) + 2 * (W + H)
|
|
3975
|
+
var shX = cardX - shD / ceZs
|
|
3822
3976
|
// soft shadow behind the card (bar strip + video)
|
|
3823
3977
|
if (shadow > 0) {
|
|
3824
3978
|
c.save()
|
|
3825
|
-
c.shadowColor = 'rgba(
|
|
3826
|
-
c.shadowBlur = 60 * s2; c.shadowOffsetY = 24 * s2
|
|
3979
|
+
c.shadowColor = 'rgba(' + shRgb + ',' + shadow + ')'
|
|
3980
|
+
c.shadowBlur = 60 * s2; c.shadowOffsetX = shD; c.shadowOffsetY = 24 * s2
|
|
3827
3981
|
c.fillStyle = '#000'
|
|
3828
|
-
rr(
|
|
3982
|
+
rr(shX, cardY, cardW, cardH, radius); c.fill()
|
|
3829
3983
|
c.restore()
|
|
3830
3984
|
}
|
|
3985
|
+
// contact shadow: tight and close, over the ambient layer
|
|
3986
|
+
if (shC > 0) {
|
|
3987
|
+
c.save()
|
|
3988
|
+
c.shadowColor = 'rgba(' + shRgb + ',' + shC + ')'
|
|
3989
|
+
c.shadowBlur = 10 * s2; c.shadowOffsetX = shD; c.shadowOffsetY = 3 * s2
|
|
3990
|
+
c.fillStyle = '#000'
|
|
3991
|
+
rr(shX, cardY, cardW, cardH, radius); c.fill()
|
|
3992
|
+
c.restore()
|
|
3993
|
+
}
|
|
3994
|
+
// The footage and the bar fill overdraw the clip by one device pixel
|
|
3995
|
+
// (CARD_EDGE_OVERDRAW) so the clip is the only edge: a destination rect
|
|
3996
|
+
// that lands exactly on the clip path anti-aliases the edge twice at a
|
|
3997
|
+
// fractional coordinate and leaves a half-covered seam pixel.
|
|
3998
|
+
var ceOv = ${CARD_EDGE_OVERDRAW} / ceZs
|
|
3999
|
+
var ceX = dx - ceOv, ceY = dy - ceOv, ceW = dw + 2 * ceOv, ceH = dh + 2 * ceOv
|
|
3831
4000
|
// video, then bar, clipped to the card's rounded corners. The bar draws
|
|
3832
4001
|
// AFTER the footage: under cover the video rect can overflow ABOVE the
|
|
3833
4002
|
// bar strip (a vertical crop), and bar-first let the footage paint over
|
|
@@ -3840,9 +4009,9 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3840
4009
|
// dimension-checked at attach); false \u21D2 element path (pre-first-seek,
|
|
3841
4010
|
// or the provider never attached).
|
|
3842
4011
|
var wcp3 = video.__voilaWc
|
|
3843
|
-
if (!(wcp3 && wcp3.draw(c, crp,
|
|
3844
|
-
if (crp) c.drawImage(video, crp.x, crp.y, crp.w, crp.h,
|
|
3845
|
-
else c.drawImage(video,
|
|
4012
|
+
if (!(wcp3 && wcp3.draw(c, crp, ceX, ceY, ceW, ceH))) {
|
|
4013
|
+
if (crp) c.drawImage(video, crp.x, crp.y, crp.w, crp.h, ceX, ceY, ceW, ceH)
|
|
4014
|
+
else c.drawImage(video, ceX, ceY, ceW, ceH)
|
|
3846
4015
|
}
|
|
3847
4016
|
} catch (e) {}
|
|
3848
4017
|
if (barH > 0) {
|
|
@@ -3852,7 +4021,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3852
4021
|
// absent = the built-in graphite look
|
|
3853
4022
|
var thm = (minimal && bar.theme) || null
|
|
3854
4023
|
c.fillStyle = minimal ? (thm ? thm.bar : '#141417') : dark ? '#2a2a2e' : '#e9e9eb'
|
|
3855
|
-
c.fillRect(cardX, cardY, cardW, barH)
|
|
4024
|
+
c.fillRect(cardX - ceOv, cardY - ceOv, cardW + 2 * ceOv, barH + ceOv)
|
|
3856
4025
|
c.fillStyle = dark || (minimal && !(thm && thm.light)) ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)'
|
|
3857
4026
|
c.fillRect(cardX, cardY + barH - s2, cardW, s2) // hairline above the video
|
|
3858
4027
|
var midY = cardY + barH / 2
|
|
@@ -4191,6 +4360,20 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4191
4360
|
}
|
|
4192
4361
|
card.mesh.rotation.x = rx
|
|
4193
4362
|
card.mesh.rotation.y = ry
|
|
4363
|
+
// The card's pose through an entrance or an end card: [scale, dy,
|
|
4364
|
+
// opacity]; absent = the rest pose, exactly as before the track existed.
|
|
4365
|
+
var cpk = d.cardPoseTrack
|
|
4366
|
+
var cpH = card.mesh.geometry && card.mesh.geometry.parameters ? card.mesh.geometry.parameters.height : 0
|
|
4367
|
+
if (cpk && cpk.keyframes && cpk.keyframes.length && card.mesh.scale && card.mesh.position) {
|
|
4368
|
+
var cpv = TL.sample(cpk, t, TL.lerpArray)
|
|
4369
|
+
card.mesh.scale.x = cpv[0]; card.mesh.scale.y = cpv[0]
|
|
4370
|
+
card.mesh.position.y = cpv[1] * cpH
|
|
4371
|
+
if (card.mesh.material) card.mesh.material.opacity = cpv[2]
|
|
4372
|
+
} else if (card.mesh.scale && card.mesh.position && ((card.mesh.scale.x !== undefined && card.mesh.scale.x !== 1) || (card.mesh.position.y !== undefined && card.mesh.position.y !== 0))) {
|
|
4373
|
+
card.mesh.scale.x = 1; card.mesh.scale.y = 1
|
|
4374
|
+
card.mesh.position.y = 0
|
|
4375
|
+
if (card.mesh.material) card.mesh.material.opacity = 1
|
|
4376
|
+
}
|
|
4194
4377
|
var tilted = rx * rx + ry * ry > 1e-6
|
|
4195
4378
|
if (card.texture && card.texture.generateMipmaps !== tilted) {
|
|
4196
4379
|
var THREE2 = ctx.THREE
|
|
@@ -4372,7 +4555,10 @@ function studioLayerData(layers, duration) {
|
|
|
4372
4555
|
} : {}
|
|
4373
4556
|
};
|
|
4374
4557
|
}
|
|
4375
|
-
function lowerToComposition(
|
|
4558
|
+
function lowerToComposition(input) {
|
|
4559
|
+
const before = durationSec(input, ratedSegments(input));
|
|
4560
|
+
const expanded = expandEndCard(input, before);
|
|
4561
|
+
const doc = expanded.doc;
|
|
4376
4562
|
const rated = ratedSegments(doc);
|
|
4377
4563
|
const duration = durationSec(doc, rated);
|
|
4378
4564
|
const fx = doc.cursor.clickFx;
|
|
@@ -4445,15 +4631,33 @@ function lowerToComposition(doc) {
|
|
|
4445
4631
|
// intensities/colors become numbers HERE — ON_FRAME reads no registry).
|
|
4446
4632
|
...clickFxData(doc, rated),
|
|
4447
4633
|
// Rated segments so zoom spans land at their speed-adjusted output times.
|
|
4448
|
-
|
|
4634
|
+
// A pull-out entrance writes the track's head.
|
|
4635
|
+
zoomTrack: prependEntrance(
|
|
4636
|
+
zoomTrackFromDoc(zoomSpans, rated, zoomStyle),
|
|
4637
|
+
entranceZoomKeyframes(doc.frame.entrance)
|
|
4638
|
+
),
|
|
4449
4639
|
// Tilt spans: OUTPUT-time [rx, ry] degree track. The rest pose is
|
|
4450
4640
|
// FLAT — there is no static card tilt any more — and the motion constants
|
|
4451
4641
|
// come from the camera style's tilt personality ('drift' slows its
|
|
4452
4642
|
// leans, 'keynote' matches the zoom ramps). Omitted when the doc has no
|
|
4453
|
-
// spans — byte parity.
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4643
|
+
// spans and no tilt-in entrance — byte parity. The entrance is a
|
|
4644
|
+
// producer of this track, never a second pose.
|
|
4645
|
+
...(() => {
|
|
4646
|
+
const head = entranceTiltKeyframes(doc.frame.entrance);
|
|
4647
|
+
const spans = doc.tilt && doc.tilt.length ? tiltTrackFromDoc(doc.tilt, rated, zoomStyle.tilt) : void 0;
|
|
4648
|
+
const track = prependEntrance(spans, head);
|
|
4649
|
+
return track ? { tiltTrack: track } : {};
|
|
4650
|
+
})(),
|
|
4651
|
+
// The card's pose through the entrance and the end card: [scale, dy,
|
|
4652
|
+
// opacity] in output seconds. Absent when neither exists.
|
|
4653
|
+
...(() => {
|
|
4654
|
+
const track = cardPoseTrack(
|
|
4655
|
+
doc.frame.entrance,
|
|
4656
|
+
expanded.endStart,
|
|
4657
|
+
expanded.seconds
|
|
4658
|
+
);
|
|
4659
|
+
return track ? { cardPoseTrack: track } : {};
|
|
4660
|
+
})()
|
|
4457
4661
|
};
|
|
4458
4662
|
const entryData = {
|
|
4459
4663
|
lights: true,
|
|
@@ -5230,7 +5434,7 @@ function lowerProgramDoc(doc, opts = {}) {
|
|
|
5230
5434
|
|
|
5231
5435
|
// src/destinations.ts
|
|
5232
5436
|
var CHANNEL_SPECS_VERIFIED = "2026-08-04";
|
|
5233
|
-
var CHANNEL_SPECS_HASH = "
|
|
5437
|
+
var CHANNEL_SPECS_HASH = "e48926c22737b5dfaabf5cb33916a50a5eccd4b93498c2059a1468bd963a0a86";
|
|
5234
5438
|
var DESTINATIONS = [
|
|
5235
5439
|
{
|
|
5236
5440
|
id: "youtube-main-demo",
|
|
@@ -5248,6 +5452,13 @@ var DESTINATIONS = [
|
|
|
5248
5452
|
format: "mp4",
|
|
5249
5453
|
accepts: "mp4 (H.264+AAC)",
|
|
5250
5454
|
fit: "contain",
|
|
5455
|
+
text: "allowed",
|
|
5456
|
+
safe: {
|
|
5457
|
+
x: 0.05,
|
|
5458
|
+
y: 0.08,
|
|
5459
|
+
w: 0.9,
|
|
5460
|
+
h: 0.84
|
|
5461
|
+
},
|
|
5251
5462
|
notes: "Captions burned in. The same public upload serves the Chrome Web Store promo video and Product Hunt video (both take YouTube URLs only)."
|
|
5252
5463
|
},
|
|
5253
5464
|
{
|
|
@@ -5267,6 +5478,13 @@ var DESTINATIONS = [
|
|
|
5267
5478
|
format: "mp4",
|
|
5268
5479
|
accepts: "mp4 (H.264+AAC ONLY \u2014 HEVC/VP9/AV1 rejected)",
|
|
5269
5480
|
fit: "contain",
|
|
5481
|
+
text: "allowed",
|
|
5482
|
+
safe: {
|
|
5483
|
+
x: 0.05,
|
|
5484
|
+
y: 0.08,
|
|
5485
|
+
w: 0.9,
|
|
5486
|
+
h: 0.84
|
|
5487
|
+
},
|
|
5270
5488
|
notes: "30\u201360s plays best; 140s/512MB is the free-tier ceiling. 1:1 also allowed. Native upload, never a link post."
|
|
5271
5489
|
},
|
|
5272
5490
|
{
|
|
@@ -5285,6 +5503,13 @@ var DESTINATIONS = [
|
|
|
5285
5503
|
format: "mp4",
|
|
5286
5504
|
accepts: "mp4 (H.264+AAC)",
|
|
5287
5505
|
fit: "contain",
|
|
5506
|
+
text: "expected",
|
|
5507
|
+
safe: {
|
|
5508
|
+
x: 0.0833,
|
|
5509
|
+
y: 0.1979,
|
|
5510
|
+
w: 0.8333,
|
|
5511
|
+
h: 0.6042
|
|
5512
|
+
},
|
|
5288
5513
|
notes: "Critical text inside a centered ~900\xD71160 safe zone \u2014 platform chrome covers the rest. Serves YouTube Shorts and LinkedIn native vertical."
|
|
5289
5514
|
},
|
|
5290
5515
|
{
|
|
@@ -5304,6 +5529,13 @@ var DESTINATIONS = [
|
|
|
5304
5529
|
format: "mp4",
|
|
5305
5530
|
accepts: "mp4 (H.264)",
|
|
5306
5531
|
fit: "contain",
|
|
5532
|
+
text: "none",
|
|
5533
|
+
safe: {
|
|
5534
|
+
x: 0.05,
|
|
5535
|
+
y: 0.08,
|
|
5536
|
+
w: 0.9,
|
|
5537
|
+
h: 0.84
|
|
5538
|
+
},
|
|
5307
5539
|
notes: "\u226410MB is the free-plan attachment ceiling; two-pass target bitrate from duration."
|
|
5308
5540
|
},
|
|
5309
5541
|
{
|
|
@@ -5322,6 +5554,14 @@ var DESTINATIONS = [
|
|
|
5322
5554
|
format: "png",
|
|
5323
5555
|
accepts: "png|jpg",
|
|
5324
5556
|
fit: "cover",
|
|
5557
|
+
text: "expected",
|
|
5558
|
+
safe: {
|
|
5559
|
+
x: 0.05,
|
|
5560
|
+
y: 0.08,
|
|
5561
|
+
w: 0.9,
|
|
5562
|
+
h: 0.84
|
|
5563
|
+
},
|
|
5564
|
+
template: "split-cover",
|
|
5325
5565
|
notes: ""
|
|
5326
5566
|
},
|
|
5327
5567
|
{
|
|
@@ -5343,6 +5583,13 @@ var DESTINATIONS = [
|
|
|
5343
5583
|
format: "png",
|
|
5344
5584
|
accepts: "png|jpg",
|
|
5345
5585
|
fit: "cover",
|
|
5586
|
+
text: "none",
|
|
5587
|
+
safe: {
|
|
5588
|
+
x: 0,
|
|
5589
|
+
y: 0,
|
|
5590
|
+
w: 1,
|
|
5591
|
+
h: 1
|
|
5592
|
+
},
|
|
5346
5593
|
notes: "Real UX only \u2014 misleading listing images are a removal-grade violation. Full bleed, square corners."
|
|
5347
5594
|
},
|
|
5348
5595
|
{
|
|
@@ -5360,6 +5607,14 @@ var DESTINATIONS = [
|
|
|
5360
5607
|
format: "png",
|
|
5361
5608
|
accepts: "png|jpg",
|
|
5362
5609
|
fit: "cover",
|
|
5610
|
+
text: "none",
|
|
5611
|
+
safe: {
|
|
5612
|
+
x: 0,
|
|
5613
|
+
y: 0,
|
|
5614
|
+
w: 1,
|
|
5615
|
+
h: 1
|
|
5616
|
+
},
|
|
5617
|
+
template: "card-on-gradient",
|
|
5363
5618
|
notes: "Listings without one rank lower. No text; fill the region; subject centered."
|
|
5364
5619
|
},
|
|
5365
5620
|
{
|
|
@@ -5377,6 +5632,14 @@ var DESTINATIONS = [
|
|
|
5377
5632
|
format: "png",
|
|
5378
5633
|
accepts: "png|jpg",
|
|
5379
5634
|
fit: "cover",
|
|
5635
|
+
text: "allowed",
|
|
5636
|
+
safe: {
|
|
5637
|
+
x: 0.05,
|
|
5638
|
+
y: 0.08,
|
|
5639
|
+
w: 0.9,
|
|
5640
|
+
h: 0.84
|
|
5641
|
+
},
|
|
5642
|
+
template: "split-cover",
|
|
5380
5643
|
notes: "Carousel eligibility."
|
|
5381
5644
|
},
|
|
5382
5645
|
{
|
|
@@ -5393,6 +5656,13 @@ var DESTINATIONS = [
|
|
|
5393
5656
|
format: "png",
|
|
5394
5657
|
accepts: "png",
|
|
5395
5658
|
fit: "cover",
|
|
5659
|
+
text: "none",
|
|
5660
|
+
safe: {
|
|
5661
|
+
x: 0,
|
|
5662
|
+
y: 0,
|
|
5663
|
+
w: 1,
|
|
5664
|
+
h: 1
|
|
5665
|
+
},
|
|
5396
5666
|
notes: "96\xD796 art + 16px transparent padding."
|
|
5397
5667
|
},
|
|
5398
5668
|
{
|
|
@@ -5411,6 +5681,14 @@ var DESTINATIONS = [
|
|
|
5411
5681
|
format: "png",
|
|
5412
5682
|
accepts: "gif|png",
|
|
5413
5683
|
fit: "cover",
|
|
5684
|
+
text: "none",
|
|
5685
|
+
safe: {
|
|
5686
|
+
x: 0.06,
|
|
5687
|
+
y: 0.06,
|
|
5688
|
+
w: 0.88,
|
|
5689
|
+
h: 0.88
|
|
5690
|
+
},
|
|
5691
|
+
template: "card-on-gradient",
|
|
5414
5692
|
notes: "GIF animates on hover only \u2014 the first frame must stand alone."
|
|
5415
5693
|
},
|
|
5416
5694
|
{
|
|
@@ -5432,6 +5710,13 @@ var DESTINATIONS = [
|
|
|
5432
5710
|
format: "png",
|
|
5433
5711
|
accepts: "png|jpg|gif",
|
|
5434
5712
|
fit: "cover",
|
|
5713
|
+
text: "none",
|
|
5714
|
+
safe: {
|
|
5715
|
+
x: 0,
|
|
5716
|
+
y: 0,
|
|
5717
|
+
w: 1,
|
|
5718
|
+
h: 1
|
|
5719
|
+
},
|
|
5435
5720
|
notes: "First image is the hero."
|
|
5436
5721
|
},
|
|
5437
5722
|
{
|
|
@@ -5449,6 +5734,14 @@ var DESTINATIONS = [
|
|
|
5449
5734
|
format: "png",
|
|
5450
5735
|
accepts: "png|jpg",
|
|
5451
5736
|
fit: "cover",
|
|
5737
|
+
text: "allowed",
|
|
5738
|
+
safe: {
|
|
5739
|
+
x: 0.05,
|
|
5740
|
+
y: 0.08,
|
|
5741
|
+
w: 0.9,
|
|
5742
|
+
h: 0.84
|
|
5743
|
+
},
|
|
5744
|
+
template: "split-cover",
|
|
5452
5745
|
notes: ""
|
|
5453
5746
|
},
|
|
5454
5747
|
{
|
|
@@ -5466,6 +5759,14 @@ var DESTINATIONS = [
|
|
|
5466
5759
|
format: "png",
|
|
5467
5760
|
accepts: "png|jpg",
|
|
5468
5761
|
fit: "cover",
|
|
5762
|
+
text: "expected",
|
|
5763
|
+
safe: {
|
|
5764
|
+
x: 0.05,
|
|
5765
|
+
y: 0.08,
|
|
5766
|
+
w: 0.9,
|
|
5767
|
+
h: 0.84
|
|
5768
|
+
},
|
|
5769
|
+
template: "split-cover",
|
|
5469
5770
|
notes: "1080\xD71350 vertical also performs in the mobile feed."
|
|
5470
5771
|
},
|
|
5471
5772
|
{
|
|
@@ -5484,6 +5785,14 @@ var DESTINATIONS = [
|
|
|
5484
5785
|
format: "png",
|
|
5485
5786
|
accepts: "png|jpg",
|
|
5486
5787
|
fit: "cover",
|
|
5788
|
+
text: "expected",
|
|
5789
|
+
safe: {
|
|
5790
|
+
x: 0.05,
|
|
5791
|
+
y: 0.0238,
|
|
5792
|
+
w: 0.9,
|
|
5793
|
+
h: 0.9524
|
|
5794
|
+
},
|
|
5795
|
+
template: "split-cover",
|
|
5487
5796
|
notes: "Text in the center ~1080\xD7600. Set twitter:card=summary_large_image explicitly \u2014 og:image alone gets the small card."
|
|
5488
5797
|
},
|
|
5489
5798
|
{
|
|
@@ -5502,6 +5811,14 @@ var DESTINATIONS = [
|
|
|
5502
5811
|
format: "png",
|
|
5503
5812
|
accepts: "png|jpg",
|
|
5504
5813
|
fit: "cover",
|
|
5814
|
+
text: "expected",
|
|
5815
|
+
safe: {
|
|
5816
|
+
x: 0.0391,
|
|
5817
|
+
y: 0.0781,
|
|
5818
|
+
w: 0.9219,
|
|
5819
|
+
h: 0.8438
|
|
5820
|
+
},
|
|
5821
|
+
template: "card-on-gradient",
|
|
5505
5822
|
notes: "Key text \u226550px from every edge."
|
|
5506
5823
|
}
|
|
5507
5824
|
];
|
|
@@ -5556,6 +5873,131 @@ function studioAudioPlan(clips, duckEnv, duration, step = AUDIO_PLAN_STEP) {
|
|
|
5556
5873
|
return { duration, step, tracks };
|
|
5557
5874
|
}
|
|
5558
5875
|
|
|
5876
|
+
// src/look.ts
|
|
5877
|
+
var HOUSE_GRADIENT = "linear-gradient(135deg, #ff5148, #ffb03a)";
|
|
5878
|
+
var PLATE_GROUND = "#f0f2f4";
|
|
5879
|
+
var DARK_GROUND = "radial-gradient(ellipse at 82% 0%, #2a1d16, #07080b)";
|
|
5880
|
+
var LOOKS = {
|
|
5881
|
+
plate: {
|
|
5882
|
+
kind: "plate",
|
|
5883
|
+
ground: PLATE_GROUND,
|
|
5884
|
+
cardWidth: 0.84,
|
|
5885
|
+
headroom: 0.16,
|
|
5886
|
+
radius: 12,
|
|
5887
|
+
shadow: 0.22,
|
|
5888
|
+
shadowContact: 0.16,
|
|
5889
|
+
border: 0.08,
|
|
5890
|
+
borderColor: "#000000"
|
|
5891
|
+
},
|
|
5892
|
+
gradient: {
|
|
5893
|
+
kind: "gradient",
|
|
5894
|
+
ground: HOUSE_GRADIENT,
|
|
5895
|
+
cardWidth: 0.84,
|
|
5896
|
+
headroom: 0.16,
|
|
5897
|
+
radius: 12,
|
|
5898
|
+
shadow: 0.35,
|
|
5899
|
+
shadowContact: 0.2,
|
|
5900
|
+
border: 0
|
|
5901
|
+
},
|
|
5902
|
+
dark: {
|
|
5903
|
+
kind: "dark",
|
|
5904
|
+
ground: DARK_GROUND,
|
|
5905
|
+
cardWidth: 0.86,
|
|
5906
|
+
headroom: 0.14,
|
|
5907
|
+
radius: 12,
|
|
5908
|
+
shadow: 0.5,
|
|
5909
|
+
shadowContact: 0.3,
|
|
5910
|
+
shadowColor: "#000000",
|
|
5911
|
+
border: 0.12,
|
|
5912
|
+
borderColor: "#ffffff"
|
|
5913
|
+
}
|
|
5914
|
+
};
|
|
5915
|
+
var LOOK_KINDS = ["plate", "gradient", "dark"];
|
|
5916
|
+
function isLookKind(v) {
|
|
5917
|
+
return typeof v === "string" && LOOK_KINDS.includes(v);
|
|
5918
|
+
}
|
|
5919
|
+
function houseLook(kind) {
|
|
5920
|
+
return { ...LOOKS[kind] };
|
|
5921
|
+
}
|
|
5922
|
+
var HEX = /^#([0-9a-f]{6})$/i;
|
|
5923
|
+
function luma(hex) {
|
|
5924
|
+
const m = HEX.exec(hex.trim());
|
|
5925
|
+
if (!m) return null;
|
|
5926
|
+
const n = parseInt(m[1], 16);
|
|
5927
|
+
const r = n >> 16 & 255;
|
|
5928
|
+
const g = n >> 8 & 255;
|
|
5929
|
+
const b = n & 255;
|
|
5930
|
+
return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
|
|
5931
|
+
}
|
|
5932
|
+
function lookKindForGround(bgA) {
|
|
5933
|
+
const l = bgA ? luma(bgA) : null;
|
|
5934
|
+
if (l === null) return "gradient";
|
|
5935
|
+
if (l >= 0.85) return "plate";
|
|
5936
|
+
if (l <= 0.2) return "dark";
|
|
5937
|
+
return "gradient";
|
|
5938
|
+
}
|
|
5939
|
+
function lookFromBrand(brand) {
|
|
5940
|
+
const b = brand ?? {};
|
|
5941
|
+
const kind = isLookKind(b.look) ? b.look : lookKindForGround(b.bgA);
|
|
5942
|
+
const look = houseLook(kind);
|
|
5943
|
+
if (kind === "plate" && b.bgB && HEX.test(b.bgB)) look.ground = b.bgB;
|
|
5944
|
+
if (kind === "gradient" && b.accent && b.bgC && HEX.test(b.accent)) {
|
|
5945
|
+
look.ground = `linear-gradient(135deg, ${b.accent}, ${b.bgC})`;
|
|
5946
|
+
}
|
|
5947
|
+
if (kind === "dark" && b.bgA && HEX.test(b.bgA)) {
|
|
5948
|
+
look.ground = `radial-gradient(ellipse at 82% 0%, ${mix(b.bgA, b.accent ?? "#ffffff", 0.18)}, ${b.bgA})`;
|
|
5949
|
+
}
|
|
5950
|
+
if (b.ground && b.ground.trim()) look.ground = b.ground.trim();
|
|
5951
|
+
return look;
|
|
5952
|
+
}
|
|
5953
|
+
function mix(a, b, t) {
|
|
5954
|
+
const pa = HEX.exec(a);
|
|
5955
|
+
const pb = HEX.exec(b);
|
|
5956
|
+
if (!pa || !pb) return a;
|
|
5957
|
+
const na = parseInt(pa[1], 16);
|
|
5958
|
+
const nb = parseInt(pb[1], 16);
|
|
5959
|
+
const ch = (sh) => {
|
|
5960
|
+
const x = na >> sh & 255;
|
|
5961
|
+
const y = nb >> sh & 255;
|
|
5962
|
+
return Math.round(x + (y - x) * t);
|
|
5963
|
+
};
|
|
5964
|
+
return `#${(ch(16) << 16 | ch(8) << 8 | ch(0)).toString(16).padStart(6, "0")}`;
|
|
5965
|
+
}
|
|
5966
|
+
function cardInset(look, frame, video, placement) {
|
|
5967
|
+
const R = frame.w / Math.max(1, frame.h);
|
|
5968
|
+
const V = video.w / Math.max(1, video.h);
|
|
5969
|
+
const cw = look.cardWidth;
|
|
5970
|
+
const ch = cw * R / V;
|
|
5971
|
+
const side = (1 - cw) / 2;
|
|
5972
|
+
if (placement === "card" && ch <= 1 - look.headroom) {
|
|
5973
|
+
const v = (1 - ch) / 2;
|
|
5974
|
+
return { left: side, right: side, top: v, bottom: v };
|
|
5975
|
+
}
|
|
5976
|
+
const top = look.headroom;
|
|
5977
|
+
const bottom = Math.min(-0.02, 1 - top - ch);
|
|
5978
|
+
return { left: side, right: side, top, bottom };
|
|
5979
|
+
}
|
|
5980
|
+
function applyLook(frame, look, size, video, placement) {
|
|
5981
|
+
const out = {
|
|
5982
|
+
...frame,
|
|
5983
|
+
background: look.ground,
|
|
5984
|
+
fit: "contain",
|
|
5985
|
+
inset: cardInset(look, size, video, placement),
|
|
5986
|
+
radius: look.radius,
|
|
5987
|
+
shadow: look.shadow,
|
|
5988
|
+
shadowContact: look.shadowContact,
|
|
5989
|
+
border: look.border
|
|
5990
|
+
};
|
|
5991
|
+
delete out.focus;
|
|
5992
|
+
if (look.shadowColor) out.shadowColor = look.shadowColor;
|
|
5993
|
+
else delete out.shadowColor;
|
|
5994
|
+
if (look.border > 0) {
|
|
5995
|
+
out.borderWidth = 1;
|
|
5996
|
+
out.borderColor = look.borderColor ?? "#000000";
|
|
5997
|
+
}
|
|
5998
|
+
return out;
|
|
5999
|
+
}
|
|
6000
|
+
|
|
5559
6001
|
// src/timeline/lanes.ts
|
|
5560
6002
|
import {
|
|
5561
6003
|
mapTime as mapTime2,
|
|
@@ -6811,6 +7253,7 @@ export {
|
|
|
6811
7253
|
CAM_SIZE_MIN,
|
|
6812
7254
|
CAM_SPAN_MIN,
|
|
6813
7255
|
CAPTURE_COVERAGE_MIN,
|
|
7256
|
+
CARD_EDGE_OVERDRAW,
|
|
6814
7257
|
CARD_FOV,
|
|
6815
7258
|
CARD_Z,
|
|
6816
7259
|
CHANNEL_SPECS_HASH,
|
|
@@ -6831,6 +7274,7 @@ export {
|
|
|
6831
7274
|
CURSOR_IDLE_FADE_IN,
|
|
6832
7275
|
CURSOR_IDLE_FADE_OUT,
|
|
6833
7276
|
CURSOR_IDLE_HOLD,
|
|
7277
|
+
DARK_GROUND,
|
|
6834
7278
|
DEFAULT_BROWSER_BAR,
|
|
6835
7279
|
DEFAULT_CAM_POSE,
|
|
6836
7280
|
DEFAULT_CAM_STYLE,
|
|
@@ -6848,6 +7292,8 @@ export {
|
|
|
6848
7292
|
DIGEST_VERSION,
|
|
6849
7293
|
DOC_SCHEMA_VERSION,
|
|
6850
7294
|
DRAG_FIT_LEVEL,
|
|
7295
|
+
END_CARD_SECONDS,
|
|
7296
|
+
ENTRANCE_SECONDS,
|
|
6851
7297
|
EXPORT_RESOLUTION_OPTIONS,
|
|
6852
7298
|
EXPORT_SHORT_EDGE,
|
|
6853
7299
|
FOLLOW_RECENTER,
|
|
@@ -6855,6 +7301,8 @@ export {
|
|
|
6855
7301
|
FRAME_BORDER_COLOR_DEFAULT,
|
|
6856
7302
|
FRAME_BORDER_DEFAULT,
|
|
6857
7303
|
FRAME_BORDER_WIDTH_DEFAULT,
|
|
7304
|
+
HOUSE_GRADIENT,
|
|
7305
|
+
LOOK_KINDS,
|
|
6858
7306
|
MINIMAL_BAR_THEMES,
|
|
6859
7307
|
MOTION_DELTA,
|
|
6860
7308
|
MOTION_EASE,
|
|
@@ -6869,6 +7317,7 @@ export {
|
|
|
6869
7317
|
OVERLAY_SIZE_MIN,
|
|
6870
7318
|
OVERLAY_TRANSITION_DUR,
|
|
6871
7319
|
OVERLAY_Z,
|
|
7320
|
+
PLATE_GROUND,
|
|
6872
7321
|
PLAYBACK_ACTIVITY,
|
|
6873
7322
|
PROGRAM_RETIME,
|
|
6874
7323
|
REJECT_OVERLAP,
|
|
@@ -6912,6 +7361,7 @@ export {
|
|
|
6912
7361
|
ZOOM_STYLE_OPTIONS,
|
|
6913
7362
|
anchorKindOf,
|
|
6914
7363
|
anchorSourceDuration,
|
|
7364
|
+
applyLook,
|
|
6915
7365
|
aspectRatioValue,
|
|
6916
7366
|
audioLane,
|
|
6917
7367
|
backdropMedia,
|
|
@@ -6923,7 +7373,9 @@ export {
|
|
|
6923
7373
|
camRectFromPose,
|
|
6924
7374
|
camRestPose,
|
|
6925
7375
|
camTrackFromDoc,
|
|
7376
|
+
cardInset,
|
|
6926
7377
|
cardPointToScreen,
|
|
7378
|
+
cardPoseTrack,
|
|
6927
7379
|
clampCamFrac,
|
|
6928
7380
|
clampCamSize,
|
|
6929
7381
|
clampFocus,
|
|
@@ -6947,8 +7399,10 @@ export {
|
|
|
6947
7399
|
docToFullSpace,
|
|
6948
7400
|
duckCurve,
|
|
6949
7401
|
effectiveSegments,
|
|
7402
|
+
entranceSeconds,
|
|
6950
7403
|
envelopeAt,
|
|
6951
7404
|
envelopeValueAt,
|
|
7405
|
+
expandEndCard,
|
|
6952
7406
|
expectedFrameSize,
|
|
6953
7407
|
exportSizeFor,
|
|
6954
7408
|
extractClicks,
|
|
@@ -6956,13 +7410,17 @@ export {
|
|
|
6956
7410
|
followFocusEvents,
|
|
6957
7411
|
frameGeometry,
|
|
6958
7412
|
hexToRgbTriplet,
|
|
7413
|
+
houseLook,
|
|
6959
7414
|
idleGaps,
|
|
7415
|
+
isLookKind,
|
|
6960
7416
|
isMusicBed,
|
|
6961
7417
|
isPlayback,
|
|
6962
7418
|
isProgramDoc,
|
|
6963
7419
|
isRecordingDoc,
|
|
6964
7420
|
isRejected,
|
|
6965
7421
|
levelForFocusFraction,
|
|
7422
|
+
lookFromBrand,
|
|
7423
|
+
lookKindForGround,
|
|
6966
7424
|
lowerProgramDoc,
|
|
6967
7425
|
lowerStudioDoc,
|
|
6968
7426
|
lowerToComposition,
|
|
@@ -7024,6 +7482,7 @@ export {
|
|
|
7024
7482
|
videoLane,
|
|
7025
7483
|
voiceKey,
|
|
7026
7484
|
withBackdrop,
|
|
7485
|
+
withHolds,
|
|
7027
7486
|
withoutRejected,
|
|
7028
7487
|
wrapProgramLength,
|
|
7029
7488
|
zoomCoversRect,
|