@vosjs/studio-core 0.30.3 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -665,6 +665,527 @@ function projectFromArtifact(artifact, videoUrl, opts = {}) {
665
665
  return { doc, videoUrl };
666
666
  }
667
667
 
668
+ // src/ingest/traces.ts
669
+ var CLICK_HOLD_MS = 40;
670
+ function traceKindOf(name) {
671
+ const m = /\.([a-z0-9]+)$/i.exec(name);
672
+ const ext = m ? m[1].toLowerCase() : "";
673
+ if (ext === "zip") return "zip";
674
+ if (ext === "csv") return "csv";
675
+ if (ext === "jsonl" || ext === "ndjson") return "jsonl";
676
+ if (ext === "json") return "json";
677
+ if (ext === "trace") return "trace";
678
+ return null;
679
+ }
680
+ function cursorFromTrace(name, text, opts = {}) {
681
+ const kind = traceKindOf(name);
682
+ if (kind === "csv") return cursorFromCsv(text, opts);
683
+ if (kind === "trace" || looksLikePlaywright(text))
684
+ return cursorFromPlaywrightTrace(text, opts);
685
+ return cursorFromRecords(text, opts);
686
+ }
687
+ function looksLikePlaywright(text) {
688
+ const head = text.slice(0, 4e3);
689
+ return head.includes('"type":"context-options"') || head.includes('"type":"before"') && head.includes('"callId"');
690
+ }
691
+ function sortEvents(events) {
692
+ return events.map((e, i) => ({ e, i })).sort((a, b) => a.e.t - b.e.t || a.i - b.i).map((x) => x.e);
693
+ }
694
+ function round(n) {
695
+ return Math.round(n * 1e3) / 1e3;
696
+ }
697
+ var PW_PRESS = /* @__PURE__ */ new Set([
698
+ "click",
699
+ "dblclick",
700
+ "tap",
701
+ "check",
702
+ "uncheck",
703
+ "setChecked",
704
+ "selectOption"
705
+ ]);
706
+ var PW_TYPE = /* @__PURE__ */ new Set(["fill", "type", "press", "pressSequentially"]);
707
+ function cursorFromPlaywrightTrace(text, opts = {}) {
708
+ const befores = [];
709
+ const afters = /* @__PURE__ */ new Map();
710
+ const inputs = /* @__PURE__ */ new Map();
711
+ let viewport;
712
+ let wallTime;
713
+ let monotonicAtOptions;
714
+ for (const line of text.split("\n")) {
715
+ if (!line.trim()) continue;
716
+ let ev;
717
+ try {
718
+ ev = JSON.parse(line);
719
+ } catch {
720
+ continue;
721
+ }
722
+ const type = ev.type;
723
+ if (type === "context-options") {
724
+ const o = ev.options;
725
+ const v = o?.viewport;
726
+ if (v && typeof v.width === "number" && typeof v.height === "number")
727
+ viewport = { width: v.width, height: v.height };
728
+ if (typeof ev.wallTime === "number") wallTime = ev.wallTime;
729
+ if (typeof ev.monotonicTime === "number")
730
+ monotonicAtOptions = ev.monotonicTime;
731
+ } else if (type === "before" && typeof ev.startTime === "number") {
732
+ befores.push({
733
+ callId: String(ev.callId),
734
+ startTime: ev.startTime,
735
+ class: typeof ev.class === "string" ? ev.class : void 0,
736
+ method: typeof ev.method === "string" ? ev.method : void 0,
737
+ params: ev.params && typeof ev.params === "object" ? ev.params : void 0
738
+ });
739
+ } else if (type === "after") {
740
+ afters.set(String(ev.callId), {
741
+ endTime: typeof ev.endTime === "number" ? ev.endTime : void 0,
742
+ point: asPoint(ev.point)
743
+ });
744
+ } else if (type === "input") {
745
+ const p = asPoint(ev.point);
746
+ if (p) inputs.set(String(ev.callId), p);
747
+ }
748
+ }
749
+ const notes = [];
750
+ if (befores.length === 0) {
751
+ return {
752
+ events: [],
753
+ notes: ['not a Playwright trace: no "before" events with a startTime'],
754
+ viewport,
755
+ kind: "playwright",
756
+ placed: 0,
757
+ dropped: 0
758
+ };
759
+ }
760
+ const newPage = befores.find((b) => b.method === "newPage");
761
+ const origin = newPage?.startTime ?? monotonicAtOptions ?? befores[0].startTime;
762
+ const offset = opts.offsetMs ?? 0;
763
+ const t0 = wallTime !== void 0 && monotonicAtOptions !== void 0 ? Math.round(wallTime + (origin - monotonicAtOptions) - offset) : void 0;
764
+ const at = (ms) => round(ms - origin + offset);
765
+ const events = [];
766
+ const cur = { pos: null };
767
+ let placed = 0;
768
+ let dropped = 0;
769
+ const move2 = (t, p) => {
770
+ if (!cur.pos || cur.pos.x !== p.x || cur.pos.y !== p.y) {
771
+ events.push({ t, x: p.x, y: p.y, type: "move" });
772
+ }
773
+ cur.pos = p;
774
+ };
775
+ const press = (start, end, p, times = 1) => {
776
+ move2(start, p);
777
+ let downAt = Math.max(start, end - CLICK_HOLD_MS);
778
+ for (let i = 0; i < times; i++) {
779
+ events.push({ t: round(downAt), x: p.x, y: p.y, type: "down", button: 0 });
780
+ events.push({
781
+ t: round(downAt + CLICK_HOLD_MS),
782
+ x: p.x,
783
+ y: p.y,
784
+ type: "up",
785
+ button: 0
786
+ });
787
+ downAt += CLICK_HOLD_MS * 2;
788
+ }
789
+ };
790
+ for (const b of befores) {
791
+ const after = afters.get(b.callId);
792
+ const end = after?.endTime ?? b.startTime;
793
+ const point = after?.point ?? inputs.get(b.callId);
794
+ const method = b.method ?? "";
795
+ const cls = b.class ?? "";
796
+ const start = at(b.startTime);
797
+ const label = `${cls}.${method}`;
798
+ if (cls === "Page" && method === "mouseMove") {
799
+ const p = asPoint(b.params);
800
+ if (!p) {
801
+ notes.push(`${label} at ${start}ms has no x/y`);
802
+ dropped++;
803
+ continue;
804
+ }
805
+ move2(start, p);
806
+ placed++;
807
+ } else if (cls === "Page" && method === "mouseClick") {
808
+ const p = asPoint(b.params) ?? point;
809
+ if (!p) {
810
+ notes.push(`${label} at ${start}ms has no x/y`);
811
+ dropped++;
812
+ continue;
813
+ }
814
+ press(start, at(end), p, clickCount(b.params));
815
+ placed++;
816
+ } else if (cls === "Page" && (method === "mouseDown" || method === "mouseUp")) {
817
+ if (!cur.pos) {
818
+ notes.push(`${label} at ${start}ms before any pointer position`);
819
+ dropped++;
820
+ continue;
821
+ }
822
+ events.push({
823
+ t: start,
824
+ x: cur.pos.x,
825
+ y: cur.pos.y,
826
+ type: method === "mouseDown" ? "down" : "up",
827
+ button: 0
828
+ });
829
+ placed++;
830
+ } else if (cls === "Page" && method === "mouseWheel") {
831
+ if (!cur.pos) {
832
+ notes.push(`${label} at ${start}ms before any pointer position`);
833
+ dropped++;
834
+ continue;
835
+ }
836
+ events.push({ t: start, x: cur.pos.x, y: cur.pos.y, type: "scroll" });
837
+ placed++;
838
+ } else if (cls === "Frame" && method === "hover") {
839
+ if (!point) {
840
+ notes.push(
841
+ `${label} ${selectorOf(b)} at ${start}ms landed nowhere the trace recorded`
842
+ );
843
+ dropped++;
844
+ continue;
845
+ }
846
+ move2(start, point);
847
+ placed++;
848
+ } else if (cls === "Frame" && PW_PRESS.has(method)) {
849
+ if (!point) {
850
+ notes.push(
851
+ `${label} ${selectorOf(b)} at ${start}ms landed nowhere the trace recorded`
852
+ );
853
+ dropped++;
854
+ continue;
855
+ }
856
+ press(start, at(end), point, method === "dblclick" ? 2 : 1);
857
+ placed++;
858
+ } else if (cls === "Frame" && PW_TYPE.has(method)) {
859
+ if (!point && !cur.pos) {
860
+ notes.push(
861
+ `${label} ${selectorOf(b)} at ${start}ms typed into a field the trace never placed`
862
+ );
863
+ dropped++;
864
+ continue;
865
+ }
866
+ const p = point ?? cur.pos;
867
+ events.push({ t: start, x: p.x, y: p.y, type: "key" });
868
+ placed++;
869
+ } else if (cls === "Frame" && method === "dragAndDrop") {
870
+ notes.push(
871
+ `${label} at ${start}ms: a drag has two points the trace records as one; not placed`
872
+ );
873
+ dropped++;
874
+ }
875
+ }
876
+ const negative = events.filter((e) => e.t < 0).length;
877
+ if (negative)
878
+ notes.push(`${negative} event(s) before the video's start were dropped`);
879
+ return {
880
+ events: sortEvents(events.filter((e) => e.t >= 0)),
881
+ notes,
882
+ viewport,
883
+ t0,
884
+ kind: "playwright",
885
+ placed,
886
+ dropped
887
+ };
888
+ }
889
+ function asPoint(v) {
890
+ if (!v || typeof v !== "object") return void 0;
891
+ const o = v;
892
+ return typeof o.x === "number" && typeof o.y === "number" ? { x: o.x, y: o.y } : void 0;
893
+ }
894
+ function clickCount(params) {
895
+ const n = params?.clickCount;
896
+ return typeof n === "number" && n > 1 ? Math.min(3, Math.floor(n)) : 1;
897
+ }
898
+ function selectorOf(b) {
899
+ const s = b.params?.selector;
900
+ return typeof s === "string" ? s : "";
901
+ }
902
+ var CSV_TYPES = /* @__PURE__ */ new Set(["move", "down", "up", "click", "scroll"]);
903
+ function cursorFromCsv(text, opts = {}) {
904
+ const notes = [];
905
+ const events = [];
906
+ const offset = opts.offsetMs ?? 0;
907
+ let placed = 0;
908
+ let dropped = 0;
909
+ const lines = text.split(/\r?\n/);
910
+ let cols = ["t", "x", "y", "type"];
911
+ let start = 0;
912
+ const first = (lines[0] ?? "").split(",").map((c) => c.trim().toLowerCase());
913
+ if (first.length >= 3 && first.some((c) => c !== "" && Number.isNaN(Number(c)))) {
914
+ cols = first;
915
+ start = 1;
916
+ }
917
+ const ti = cols.indexOf("t");
918
+ const xi = cols.indexOf("x");
919
+ const yi = cols.indexOf("y");
920
+ const ki = cols.indexOf("type");
921
+ if (ti < 0 || xi < 0 || yi < 0) {
922
+ return {
923
+ events: [],
924
+ notes: [
925
+ "the CSV needs t, x and y columns (a header row names them; without one the order is t,x,y,type)"
926
+ ],
927
+ kind: "csv",
928
+ placed: 0,
929
+ dropped: 0
930
+ };
931
+ }
932
+ for (let i = start; i < lines.length; i++) {
933
+ const raw = lines[i];
934
+ if (!raw.trim()) continue;
935
+ const cells = raw.split(",").map((c) => c.trim());
936
+ const t = Number(cells[ti]);
937
+ const x = Number(cells[xi]);
938
+ const y = Number(cells[yi]);
939
+ const type = (ki >= 0 ? cells[ki] : "") || "move";
940
+ if (![t, x, y].every(Number.isFinite)) {
941
+ notes.push(`line ${i + 1}: t, x and y must be numbers`);
942
+ dropped++;
943
+ continue;
944
+ }
945
+ if (!CSV_TYPES.has(type)) {
946
+ notes.push(
947
+ `line ${i + 1}: unknown type "${type}" (move, down, up, click, scroll)`
948
+ );
949
+ dropped++;
950
+ continue;
951
+ }
952
+ const at = round(t + offset);
953
+ if (type === "click") {
954
+ events.push({ t: at, x, y, type: "move" });
955
+ events.push({ t: at, x, y, type: "down", button: 0 });
956
+ events.push({ t: round(at + CLICK_HOLD_MS), x, y, type: "up", button: 0 });
957
+ } else if (type === "down" || type === "up") {
958
+ events.push({ t: at, x, y, type, button: 0 });
959
+ } else {
960
+ events.push({ t: at, x, y, type });
961
+ }
962
+ placed++;
963
+ }
964
+ return { events: sortEvents(events), notes, kind: "csv", placed, dropped };
965
+ }
966
+ var RECORD_TYPES = {
967
+ move: "move",
968
+ hover: "move",
969
+ down: "down",
970
+ mousedown: "down",
971
+ up: "up",
972
+ mouseup: "up",
973
+ click: "click",
974
+ press: "click",
975
+ scroll: "scroll",
976
+ wheel: "scroll",
977
+ key: "key",
978
+ type: "key",
979
+ fill: "key",
980
+ focus: "focus"
981
+ };
982
+ function cursorFromRecords(input, opts = {}) {
983
+ const notes = [];
984
+ const events = [];
985
+ const offset = opts.offsetMs ?? 0;
986
+ let placed = 0;
987
+ let dropped = 0;
988
+ let records;
989
+ if (typeof input === "string") {
990
+ const trimmed = input.trim();
991
+ if (trimmed.startsWith("[")) {
992
+ try {
993
+ records = JSON.parse(trimmed);
994
+ } catch {
995
+ return {
996
+ events: [],
997
+ notes: ["not JSON: the records file could not be parsed"],
998
+ kind: "records",
999
+ placed: 0,
1000
+ dropped: 0
1001
+ };
1002
+ }
1003
+ } else {
1004
+ records = [];
1005
+ let n = 0;
1006
+ for (const line of trimmed.split("\n")) {
1007
+ n++;
1008
+ if (!line.trim()) continue;
1009
+ try {
1010
+ records.push(JSON.parse(line));
1011
+ } catch {
1012
+ notes.push(`line ${n}: not JSON`);
1013
+ dropped++;
1014
+ }
1015
+ }
1016
+ }
1017
+ } else records = input;
1018
+ let epochZero = null;
1019
+ let sawNoTime = 0;
1020
+ let sawNoPoint = 0;
1021
+ records.forEach((r, i) => {
1022
+ if (!r || typeof r !== "object") {
1023
+ notes.push(`record ${i + 1}: not an object`);
1024
+ dropped++;
1025
+ return;
1026
+ }
1027
+ const o = r;
1028
+ let t = numberOf(o.t ?? o.ts ?? o.time ?? o.timestamp);
1029
+ if (t !== void 0 && t > 1e12) {
1030
+ if (epochZero === null) epochZero = t;
1031
+ t -= epochZero;
1032
+ }
1033
+ const kind = recordKind(o);
1034
+ if (!kind) return;
1035
+ const p = asPoint(o) ?? asPoint(o.point) ?? centerOf(o.rect);
1036
+ if (t === void 0) {
1037
+ sawNoTime++;
1038
+ dropped++;
1039
+ return;
1040
+ }
1041
+ if (!p) {
1042
+ sawNoPoint++;
1043
+ dropped++;
1044
+ return;
1045
+ }
1046
+ const at = round(t + offset);
1047
+ const rect = asRect(o.rect);
1048
+ if (kind === "click") {
1049
+ events.push({ t: at, x: p.x, y: p.y, type: "move" });
1050
+ events.push({
1051
+ t: at,
1052
+ x: p.x,
1053
+ y: p.y,
1054
+ type: "down",
1055
+ button: 0,
1056
+ ...rect ? { rect } : {}
1057
+ });
1058
+ events.push({
1059
+ t: round(at + CLICK_HOLD_MS),
1060
+ x: p.x,
1061
+ y: p.y,
1062
+ type: "up",
1063
+ button: 0
1064
+ });
1065
+ } else if (kind === "down" || kind === "up") {
1066
+ events.push({
1067
+ t: at,
1068
+ x: p.x,
1069
+ y: p.y,
1070
+ type: kind,
1071
+ button: 0,
1072
+ ...rect ? { rect } : {}
1073
+ });
1074
+ } else {
1075
+ events.push({
1076
+ t: at,
1077
+ x: p.x,
1078
+ y: p.y,
1079
+ type: kind,
1080
+ ...rect ? { rect } : {}
1081
+ });
1082
+ }
1083
+ placed++;
1084
+ });
1085
+ if (sawNoTime)
1086
+ notes.push(
1087
+ `${sawNoTime} gesture record(s) carry no time (t, ts or time, in ms), so they cannot meet the video; agent-browser's own log has none, so stamp one per command when you keep it`
1088
+ );
1089
+ if (sawNoPoint)
1090
+ notes.push(
1091
+ `${sawNoPoint} gesture record(s) carry no point (x/y, point, or rect); a ref alone says which element, not where it was`
1092
+ );
1093
+ return { events: sortEvents(events), notes, kind: "records", placed, dropped };
1094
+ }
1095
+ function recordKind(o) {
1096
+ const explicit = typeof o.type === "string" ? o.type.toLowerCase() : "";
1097
+ if (explicit && explicit in RECORD_TYPES) return RECORD_TYPES[explicit];
1098
+ const cmd = commandWords(o.command ?? o.action ?? o.kind);
1099
+ const verb = cmd[0] === "mouse" ? `${cmd[0]}${cmd[1] ?? ""}` : cmd[0];
1100
+ if (!verb) return null;
1101
+ const key = verb.toLowerCase();
1102
+ if (key in RECORD_TYPES) return RECORD_TYPES[key];
1103
+ if (key === "mousemove") return "move";
1104
+ if (key === "mouseclick") return "click";
1105
+ if (key === "mousedown") return "down";
1106
+ if (key === "mouseup") return "up";
1107
+ if (key === "dblclick") return "click";
1108
+ return null;
1109
+ }
1110
+ function commandWords(v) {
1111
+ if (Array.isArray(v)) return v.map(String);
1112
+ if (typeof v === "string") return v.trim().split(/\s+/);
1113
+ return [];
1114
+ }
1115
+ function numberOf(v) {
1116
+ if (typeof v === "number" && Number.isFinite(v)) return v;
1117
+ if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v)))
1118
+ return Number(v);
1119
+ return void 0;
1120
+ }
1121
+ function asRect(v) {
1122
+ if (!v || typeof v !== "object") return void 0;
1123
+ const o = v;
1124
+ const x = numberOf(o.x);
1125
+ const y = numberOf(o.y);
1126
+ const w = numberOf(o.w ?? o.width);
1127
+ const h = numberOf(o.h ?? o.height);
1128
+ return x !== void 0 && y !== void 0 && w !== void 0 && h !== void 0 ? { x, y, w, h } : void 0;
1129
+ }
1130
+ function centerOf(v) {
1131
+ const r = asRect(v);
1132
+ return r ? { x: r.x + r.w / 2, y: r.y + r.h / 2 } : void 0;
1133
+ }
1134
+
1135
+ // src/ingest/zip.ts
1136
+ var EOCD = 101010256;
1137
+ var CENTRAL = 33639248;
1138
+ var LOCAL = 67324752;
1139
+ function zipEntries(bytes) {
1140
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1141
+ let eocd = -1;
1142
+ for (let i = bytes.length - 22; i >= Math.max(0, bytes.length - 22 - 65535); i--) {
1143
+ if (view.getUint32(i, true) === EOCD) {
1144
+ eocd = i;
1145
+ break;
1146
+ }
1147
+ }
1148
+ if (eocd < 0) throw new Error("not a zip: no end-of-central-directory record");
1149
+ const count = view.getUint16(eocd + 10, true);
1150
+ let p = view.getUint32(eocd + 16, true);
1151
+ const entries = [];
1152
+ const decoder = new TextDecoder();
1153
+ for (let i = 0; i < count; i++) {
1154
+ if (view.getUint32(p, true) !== CENTRAL)
1155
+ throw new Error("not a zip: central directory entry expected");
1156
+ const method = view.getUint16(p + 10, true);
1157
+ const compressedSize = view.getUint32(p + 20, true);
1158
+ const size = view.getUint32(p + 24, true);
1159
+ const nameLen = view.getUint16(p + 28, true);
1160
+ const extraLen = view.getUint16(p + 30, true);
1161
+ const commentLen = view.getUint16(p + 32, true);
1162
+ const local = view.getUint32(p + 42, true);
1163
+ const name = decoder.decode(bytes.subarray(p + 46, p + 46 + nameLen));
1164
+ if (view.getUint32(local, true) !== LOCAL)
1165
+ throw new Error(`not a zip: local header expected for ${name}`);
1166
+ const localNameLen = view.getUint16(local + 26, true);
1167
+ const localExtraLen = view.getUint16(local + 28, true);
1168
+ entries.push({
1169
+ name,
1170
+ method,
1171
+ compressedSize,
1172
+ size,
1173
+ dataOffset: local + 30 + localNameLen + localExtraLen
1174
+ });
1175
+ p += 46 + nameLen + extraLen + commentLen;
1176
+ }
1177
+ return entries;
1178
+ }
1179
+ function zipEntryBytes(bytes, entry) {
1180
+ return bytes.subarray(
1181
+ entry.dataOffset,
1182
+ entry.dataOffset + entry.compressedSize
1183
+ );
1184
+ }
1185
+ function playwrightTraceEntry(entries) {
1186
+ return entries.find((e) => e.name === "trace.trace") ?? entries.find((e) => /(^|\/)trace\.trace$/.test(e.name)) ?? null;
1187
+ }
1188
+
668
1189
  // src/lower/motion.ts
669
1190
  import { sortKeyframes } from "@vosjs/timeline";
670
1191
 
@@ -1752,7 +2273,7 @@ function planAutoZoom(track, options) {
1752
2273
  typingMinLevel = style.typingMinLevel,
1753
2274
  duration
1754
2275
  } = options;
1755
- const within = (spans2) => duration === void 0 ? spans2 : spans2.map((z) => ({ ...z, out: Math.min(z.out, round(duration)) })).filter((z) => z.out - z.in >= 0.3);
2276
+ const within = (spans2) => duration === void 0 ? spans2 : spans2.map((z) => ({ ...z, out: Math.min(z.out, round2(duration)) })).filter((z) => z.out - z.in >= 0.3);
1756
2277
  const grouped = groupTrack(track, {
1757
2278
  width,
1758
2279
  height,
@@ -1764,17 +2285,17 @@ function planAutoZoom(track, options) {
1764
2285
  });
1765
2286
  const { sessions } = grouped;
1766
2287
  const drags = dragsFromTrack(track, width, height);
1767
- const dragPress = new Set(drags.map((d) => round(d.t0)));
1768
- const withoutDrags = (list) => list.map((cl) => cl.filter((c) => !dragPress.has(round(c.t)))).filter((cl) => cl.length > 0);
2288
+ const dragPress = new Set(drags.map((d) => round2(d.t0)));
2289
+ const withoutDrags = (list) => list.map((cl) => cl.filter((c) => !dragPress.has(round2(c.t)))).filter((cl) => cl.length > 0);
1769
2290
  const clusters = withoutDrags(grouped.clusters);
1770
2291
  const surfaces = withoutDrags(grouped.surfaces);
1771
2292
  const dragSpans = drags.map((d, i) => ({
1772
2293
  id: `g${i}`,
1773
- in: round(Math.max(0, d.t0 - lead)),
1774
- out: round(d.t1 + hold),
2294
+ in: round2(Math.max(0, d.t0 - lead)),
2295
+ out: round2(d.t1 + hold),
1775
2296
  level: clampZoomLevel(Math.min(Math.max(dragLevel, 1.1), maxLevel)),
1776
- cx: round(d.nx),
1777
- cy: round(d.ny),
2297
+ cx: round2(d.nx),
2298
+ cy: round2(d.ny),
1778
2299
  focusMode: "auto",
1779
2300
  source: "auto"
1780
2301
  }));
@@ -1851,11 +2372,11 @@ function planAutoZoom(track, options) {
1851
2372
  typing = typing.filter((t) => t.out - t.in >= 0.3);
1852
2373
  const zSpans = clickSpans.filter((s) => !s.dead).sort((a, b) => a.in - b.in).map((s, i) => ({
1853
2374
  id: `z${i}`,
1854
- in: round(s.in),
1855
- out: round(s.out),
2375
+ in: round2(s.in),
2376
+ out: round2(s.out),
1856
2377
  level: clampZoomLevel(s.level),
1857
- cx: round(s.cx),
1858
- cy: round(s.cy),
2378
+ cx: round2(s.cx),
2379
+ cy: round2(s.cy),
1859
2380
  // Follow styles ride the cursor through the span (entry + dead-zone
1860
2381
  // recenters baked at lowering); typing/dwell spans stay fixed-focus.
1861
2382
  ...followByDefault ? { focusMode: "auto" } : {},
@@ -1863,11 +2384,11 @@ function planAutoZoom(track, options) {
1863
2384
  }));
1864
2385
  const kSpans = typing.map((s, i) => ({
1865
2386
  id: `k${i}`,
1866
- in: round(s.in),
1867
- out: round(s.out),
2387
+ in: round2(s.in),
2388
+ out: round2(s.out),
1868
2389
  level: clampZoomLevel(s.level),
1869
- cx: round(s.cx),
1870
- cy: round(s.cy),
2390
+ cx: round2(s.cx),
2391
+ cy: round2(s.cy),
1871
2392
  // Never focusMode:'auto': the dot is parked (and fading) while typing —
1872
2393
  // the FIELD is the anchor, and a follow would be a no-op at best.
1873
2394
  source: "auto"
@@ -1944,12 +2465,12 @@ function dwellSpans(track, width, height, maxLevel, reserved) {
1944
2465
  if (taken.some((z) => spanIn < z.out && spanOut > z.in)) continue;
1945
2466
  const span = {
1946
2467
  id: `d${accepted.length}`,
1947
- in: round(spanIn),
1948
- out: round(spanOut),
2468
+ in: round2(spanIn),
2469
+ out: round2(spanOut),
1949
2470
  // No element rect on moves → point zoom at the planner ceiling.
1950
2471
  level: clampZoomLevel(maxLevel),
1951
- cx: round(clamp012(c.cx)),
1952
- cy: round(clamp012(c.cy)),
2472
+ cx: round2(clamp012(c.cx)),
2473
+ cy: round2(clamp012(c.cy)),
1953
2474
  source: "auto"
1954
2475
  };
1955
2476
  accepted.push(span);
@@ -2018,7 +2539,7 @@ function clamp3(v, lo, hi) {
2018
2539
  function clamp012(v) {
2019
2540
  return clamp3(v, 0, 1);
2020
2541
  }
2021
- function round(v) {
2542
+ function round2(v) {
2022
2543
  return Math.round(v * 1e3) / 1e3;
2023
2544
  }
2024
2545
 
@@ -2858,14 +3379,14 @@ function resolveOverlayFx(clip, clipDuration) {
2858
3379
  const maxTotal = Math.max(dur, clipDuration * 0.9);
2859
3380
  st = Math.min(st, Math.max(0, (maxTotal - dur) / (upperN - 1)));
2860
3381
  }
2861
- const round13 = (v) => Math.round(v * 1e4) / 1e4;
3382
+ const round14 = (v) => Math.round(v * 1e4) / 1e4;
2862
3383
  return {
2863
3384
  k: spec.fx,
2864
3385
  u: unit,
2865
3386
  d: FX_DIR[spec.direction ?? "forward"] ?? 0,
2866
- st: round13(st),
2867
- dur: round13(dur),
2868
- tt: round13(st * (upperN - 1) + dur),
3387
+ st: round14(st),
3388
+ dur: round14(dur),
3389
+ tt: round14(st * (upperN - 1) + dur),
2869
3390
  units,
2870
3391
  n
2871
3392
  };
@@ -3199,10 +3720,10 @@ function followFocusEvents(span, cursor, space, layout, options = {}) {
3199
3720
  camera
3200
3721
  );
3201
3722
  pathEvents.push({
3202
- t: round2(at),
3723
+ t: round4(at),
3203
3724
  path: true,
3204
- cx: round2(f.cx),
3205
- cy: round2(f.cy)
3725
+ cx: round4(f.cx),
3726
+ cy: round4(f.cy)
3206
3727
  });
3207
3728
  if (at >= to) break;
3208
3729
  }
@@ -3226,7 +3747,7 @@ function followFocusEvents(span, cursor, space, layout, options = {}) {
3226
3747
  const target = lookahead > 0 ? sampleAt2(pts, Math.min(p.t + lookahead, span.out)) : p;
3227
3748
  const f = clampFocus(target.nx, target.ny, level, layout, camera);
3228
3749
  if (Math.abs(f.cx - cx) < 1e-3 && Math.abs(f.cy - cy) < 1e-3) continue;
3229
- events.push({ t: round2(p.t), cx: round2(f.cx), cy: round2(f.cy) });
3750
+ events.push({ t: round4(p.t), cx: round4(f.cx), cy: round4(f.cy) });
3230
3751
  cx = f.cx;
3231
3752
  cy = f.cy;
3232
3753
  nextAllowed = p.t + recenter;
@@ -3250,7 +3771,7 @@ function sampleAt2(pts, t) {
3250
3771
  function clamp014(v) {
3251
3772
  return Math.max(0, Math.min(1, v));
3252
3773
  }
3253
- function round2(v) {
3774
+ function round4(v) {
3254
3775
  return Math.round(v * 1e3) / 1e3;
3255
3776
  }
3256
3777
 
@@ -3540,7 +4061,7 @@ function pinPlacement(input) {
3540
4061
  side,
3541
4062
  track: { keyframes: simplify(poses, [W, H, 1, 1, 1]) },
3542
4063
  tip: { keyframes: simplify(tips, [W, H]) },
3543
- clamped: round4(clamped),
4064
+ clamped: round5(clamped),
3544
4065
  box
3545
4066
  };
3546
4067
  }
@@ -3569,13 +4090,13 @@ function simplify(keys, scalePx) {
3569
4090
  }
3570
4091
  }
3571
4092
  const last = keys[keys.length - 1];
3572
- if (out[out.length - 1].t !== round4(last.t)) out.push(roundKey(last));
4093
+ if (out[out.length - 1].t !== round5(last.t)) out.push(roundKey(last));
3573
4094
  return out;
3574
4095
  }
3575
4096
  function roundKey(k) {
3576
- return { t: round4(k.t), value: k.value.map(round42), ease: k.ease };
4097
+ return { t: round5(k.t), value: k.value.map(round42), ease: k.ease };
3577
4098
  }
3578
- function round4(v) {
4099
+ function round5(v) {
3579
4100
  return Math.round(v * 1e3) / 1e3;
3580
4101
  }
3581
4102
  function round42(v) {
@@ -3587,7 +4108,7 @@ var CURSOR_IDLE_HOLD = 1;
3587
4108
  var CURSOR_IDLE_FADE_OUT = 0.35;
3588
4109
  var CURSOR_IDLE_FADE_IN = 0.18;
3589
4110
  var CURSOR_IDLE_EPS_FRAC = 25e-4;
3590
- var round5 = (n) => Math.round(n * 1e3) / 1e3;
4111
+ var round6 = (n) => Math.round(n * 1e3) / 1e3;
3591
4112
  function cursorIdleFade(track, o) {
3592
4113
  const moves = track.filter((e) => e.type === "move" || e.type === "down" || e.type === "up").map((e) => ({ t: e.t / 1e3, x: e.x, y: e.y }));
3593
4114
  if (moves.length === 0) return [];
@@ -3639,7 +4160,7 @@ function emit(keys, s, e) {
3639
4160
  push(keys, e + CURSOR_IDLE_FADE_IN, 1);
3640
4161
  }
3641
4162
  function push(keys, t, a) {
3642
- const rt = round5(t);
4163
+ const rt = round6(t);
3643
4164
  if (keys.length > 0 && rt <= keys[keys.length - 1].t) {
3644
4165
  keys[keys.length - 1].a = a;
3645
4166
  return;
@@ -5147,7 +5668,7 @@ function gatedRect(rect, x, y, space) {
5147
5668
  return null;
5148
5669
  const m = RECT_CONTAIN_MARGIN;
5149
5670
  const inside = x >= rect.x - m && x <= rect.x + rect.w + m && y >= rect.y - m && y <= rect.y + rect.h + m;
5150
- return inside ? [round6(rect.x), round6(rect.y), round6(rect.w), round6(rect.h)] : null;
5671
+ return inside ? [round7(rect.x), round7(rect.y), round7(rect.w), round7(rect.h)] : null;
5151
5672
  }
5152
5673
  function extractClicks(track, segments, opts) {
5153
5674
  const out = [];
@@ -5172,11 +5693,11 @@ function extractClicks(track, segments, opts) {
5172
5693
  const upSrc = st + Math.max(pressLen, 0.02);
5173
5694
  const up = keptOutputEnd(segments, st, upSrc, opts.media) ?? ot + CLICK_SYNTH_RELEASE;
5174
5695
  const click = {
5175
- ot: round6(ot),
5176
- up: round6(Math.max(up, ot + 0.02)),
5177
- st: round6(st),
5178
- x: round6(e.x),
5179
- y: round6(e.y),
5696
+ ot: round7(ot),
5697
+ up: round7(Math.max(up, ot + 0.02)),
5698
+ st: round7(st),
5699
+ x: round7(e.x),
5700
+ y: round7(e.y),
5180
5701
  b: button
5181
5702
  };
5182
5703
  if (opts.rects && e.rect) {
@@ -5195,7 +5716,7 @@ function hexToRgbTriplet(hex) {
5195
5716
  const n = parseInt(h, 16);
5196
5717
  return [n >> 16, n >> 8 & 255, n & 255];
5197
5718
  }
5198
- function round6(v) {
5719
+ function round7(v) {
5199
5720
  return Math.round(v * 1e3) / 1e3;
5200
5721
  }
5201
5722
 
@@ -5278,7 +5799,7 @@ function trackEmitter() {
5278
5799
  if (tt <= prev.t + 1e-6 && sameVec(prev.value, value)) return prev.t;
5279
5800
  if (tt <= prev.t + 1e-6) tt = prev.t + 1e-3;
5280
5801
  }
5281
- keyframes.push({ t: round7(tt), value: value.map(round7), ease });
5802
+ keyframes.push({ t: round8(tt), value: value.map(round8), ease });
5282
5803
  return tt;
5283
5804
  };
5284
5805
  return { keyframes, push: push2 };
@@ -7278,14 +7799,14 @@ function studioLayerData(layers, duration, pins) {
7278
7799
  // needs a decoded recording, which a sync lowering can't produce.
7279
7800
  audio: (layers.audio ?? []).map((c) => ({
7280
7801
  key: c.key,
7281
- start: round7(c.start),
7282
- in: round7(c.in),
7283
- out: round7(c.out),
7284
- gain: round7(c.gain),
7802
+ start: round8(c.start),
7803
+ in: round8(c.in),
7804
+ out: round8(c.out),
7805
+ gain: round8(c.gain),
7285
7806
  loop: !!c.loop,
7286
- len: round7(clipLength(c)),
7807
+ len: round8(clipLength(c)),
7287
7808
  duck: !!c.duck,
7288
- env: clipEnvelope(c).map((p) => ({ t: round7(p.t), g: round7(p.g) }))
7809
+ env: clipEnvelope(c).map((p) => ({ t: round8(p.t), g: round8(p.g) }))
7289
7810
  })),
7290
7811
  // Object clips: numbers resolved HERE; shapes are the drafted engine spec.
7291
7812
  ...layers.objects && layers.objects.length ? {
@@ -7298,17 +7819,17 @@ function studioLayerData(layers, duration, pins) {
7298
7819
  } : o.asset.kind === "text3d" ? resolveText3dAsset(o.asset) : { kind: "gltf", key: o.asset.key },
7299
7820
  ...o.span ? {
7300
7821
  span: {
7301
- start: round7(o.span.start),
7302
- duration: round7(o.span.duration)
7822
+ start: round8(o.span.start),
7823
+ duration: round8(o.span.duration)
7303
7824
  }
7304
7825
  } : {},
7305
- x: round7(o.transform3d.x),
7306
- y: round7(o.transform3d.y),
7307
- z: round7(o.transform3d.z),
7308
- rx: round7(o.transform3d.rx),
7309
- ry: round7(o.transform3d.ry),
7310
- rz: round7(o.transform3d.rz),
7311
- scale: round7(o.transform3d.scale || OBJECT_DEFAULT_SCALE),
7826
+ x: round8(o.transform3d.x),
7827
+ y: round8(o.transform3d.y),
7828
+ z: round8(o.transform3d.z),
7829
+ rx: round8(o.transform3d.rx),
7830
+ ry: round8(o.transform3d.ry),
7831
+ rz: round8(o.transform3d.rz),
7832
+ scale: round8(o.transform3d.scale || OBJECT_DEFAULT_SCALE),
7312
7833
  anim: idleKey(o),
7313
7834
  // Pose keyframes: clip-local [x,y,z,rx,ry,rz,scale] track,
7314
7835
  // sampled at t − span.start. Omitted when absent — parity.
@@ -7335,12 +7856,12 @@ function studioLayerData(layers, duration, pins) {
7335
7856
  const base = {
7336
7857
  id: o.id,
7337
7858
  kind: o.kind,
7338
- start: round7(o.start),
7339
- dur: round7(Math.max(OVERLAY_MIN_DURATION, o.duration)),
7340
- x: round7(o.transform.x),
7341
- y: round7(o.transform.y),
7342
- scale: round7(o.transform.scale || 1),
7343
- rot: round7(o.transform.rotation || 0),
7859
+ start: round8(o.start),
7860
+ dur: round8(Math.max(OVERLAY_MIN_DURATION, o.duration)),
7861
+ x: round8(o.transform.x),
7862
+ y: round8(o.transform.y),
7863
+ scale: round8(o.transform.scale || 1),
7864
+ rot: round8(o.transform.rotation || 0),
7344
7865
  enter: enterKey(o),
7345
7866
  exit: exitKey(o),
7346
7867
  // Pose keyframes: a CLIP-LOCAL [x, y, scale, rot, opacity]
@@ -7365,10 +7886,10 @@ function studioLayerData(layers, duration, pins) {
7365
7886
  ...base,
7366
7887
  kind: "image",
7367
7888
  key: hl.key,
7368
- w: round7(hl.width),
7889
+ w: round8(hl.width),
7369
7890
  radius: 0,
7370
7891
  shadow: "none",
7371
- opacity: round7(o.opacity ?? 1),
7892
+ opacity: round8(o.opacity ?? 1),
7372
7893
  html: hl.html
7373
7894
  };
7374
7895
  }
@@ -7385,8 +7906,8 @@ function studioLayerData(layers, duration, pins) {
7385
7906
  ...o.frame.browserBar?.theme ? { theme: o.frame.browserBar.theme } : {}
7386
7907
  },
7387
7908
  lean: [
7388
- round7(o.frame.lean?.rx ?? 0),
7389
- round7(o.frame.lean?.ry ?? 0)
7909
+ round8(o.frame.lean?.rx ?? 0),
7910
+ round8(o.frame.lean?.ry ?? 0)
7390
7911
  ],
7391
7912
  shadow: o.frame.shadow ?? LAYER_CARD_SHADOW,
7392
7913
  shadowContact: o.frame.shadowContact ?? 0,
@@ -7402,17 +7923,17 @@ function studioLayerData(layers, duration, pins) {
7402
7923
  for (const ev of lm.cursor) {
7403
7924
  if (ev.type === "down")
7404
7925
  clicks.push({
7405
- t: round7(ev.t / 1e3),
7406
- x: round7(ev.x),
7407
- y: round7(ev.y)
7926
+ t: round8(ev.t / 1e3),
7927
+ x: round8(ev.x),
7928
+ y: round8(ev.y)
7408
7929
  });
7409
7930
  }
7410
7931
  return {
7411
7932
  cur: {
7412
7933
  pts: sm.map((p) => ({
7413
- t: round7(p.t),
7414
- x: round7(p.x),
7415
- y: round7(p.y)
7934
+ t: round8(p.t),
7935
+ x: round8(p.x),
7936
+ y: round8(p.y)
7416
7937
  })),
7417
7938
  space: { w: lm.meta.width, h: lm.meta.height },
7418
7939
  size: rec.cursor.size,
@@ -7427,7 +7948,7 @@ function studioLayerData(layers, duration, pins) {
7427
7948
  ...lm && lm.crop ? { crop: lm.crop } : {},
7428
7949
  ...lmCard ? { card: lmCard } : {},
7429
7950
  ...lmFacts,
7430
- w: round7(o.width ?? OVERLAY_MEDIA_DEFAULT_WIDTH),
7951
+ w: round8(o.width ?? OVERLAY_MEDIA_DEFAULT_WIDTH),
7431
7952
  radius: o.radius ?? OVERLAY_MEDIA_DEFAULT_RADIUS,
7432
7953
  opacity: o.opacity ?? 1,
7433
7954
  loop: !!o.loop,
@@ -7437,7 +7958,7 @@ function studioLayerData(layers, duration, pins) {
7437
7958
  ...o.shadow ? { shadow: o.shadow } : {},
7438
7959
  ...o.border && o.border.width > 0 ? {
7439
7960
  border: {
7440
- width: round7(o.border.width),
7961
+ width: round8(o.border.width),
7441
7962
  color: o.border.color
7442
7963
  }
7443
7964
  } : {}
@@ -7457,11 +7978,11 @@ function studioLayerData(layers, duration, pins) {
7457
7978
  // Style-v2 fields bake only when non-default (byte parity for
7458
7979
  // older docs); ON_FRAME reads them unconditionally.
7459
7980
  ...st.fontStyle === "italic" ? { sty: "italic" } : {},
7460
- ...o.maxWidth ? { mw: round7(o.maxWidth) } : {},
7461
- ...st.letterSpacing ? { ls: round7(st.letterSpacing) } : {},
7462
- ...st.lineHeight !== OVERLAY_LINE_HEIGHT ? { lh: round7(st.lineHeight) } : {},
7981
+ ...o.maxWidth ? { mw: round8(o.maxWidth) } : {},
7982
+ ...st.letterSpacing ? { ls: round8(st.letterSpacing) } : {},
7983
+ ...st.lineHeight !== OVERLAY_LINE_HEIGHT ? { lh: round8(st.lineHeight) } : {},
7463
7984
  ...st.align !== "center" ? { align: st.align } : {},
7464
- ...st.stroke ? { stroke: { c: st.stroke.color, w: round7(st.stroke.width) } } : {},
7985
+ ...st.stroke ? { stroke: { c: st.stroke.color, w: round8(st.stroke.width) } } : {},
7465
7986
  // Hosted face behind an override: ON_FRAME lazy-loads it so a
7466
7987
  // live family/weight edit paints without a LOAD (SETUP only
7467
7988
  // runs on cold load).
@@ -7474,10 +7995,10 @@ function studioLayerData(layers, duration, pins) {
7474
7995
  ...bx ? {
7475
7996
  box: {
7476
7997
  c: bx.color,
7477
- o: round7(bx.opacity),
7478
- px: round7(bx.padX),
7479
- py: round7(bx.padY),
7480
- r: round7(bx.radius)
7998
+ o: round8(bx.opacity),
7999
+ px: round8(bx.padX),
8000
+ py: round8(bx.padY),
8001
+ r: round8(bx.radius)
7481
8002
  }
7482
8003
  } : {},
7483
8004
  // Entrance animation: segmentation + timing normalized
@@ -7549,17 +8070,17 @@ function lowerToComposition(input) {
7549
8070
  // Rated segments (speed spans pre-intersected): ON_FRAME's mapTime and the
7550
8071
  // export's audio splice read the rate straight off each segment.
7551
8072
  segments: rated.map((seg) => ({
7552
- in: round7(seg.in),
7553
- out: round7(seg.out),
7554
- ...seg.rate !== void 0 && seg.rate !== 1 ? { rate: round7(seg.rate) } : {},
8073
+ in: round8(seg.in),
8074
+ out: round8(seg.out),
8075
+ ...seg.rate !== void 0 && seg.rate !== 1 ? { rate: round8(seg.rate) } : {},
7555
8076
  ...seg.media ? { media: seg.media } : {}
7556
8077
  })),
7557
8078
  frame: doc.frame,
7558
8079
  micGain: doc.micGain ?? 1,
7559
8080
  cursor: smoothed.map((p) => ({
7560
- t: round7(p.t),
7561
- x: round7(p.x),
7562
- y: round7(p.y)
8081
+ t: round8(p.t),
8082
+ x: round8(p.x),
8083
+ y: round8(p.y)
7563
8084
  })),
7564
8085
  cursorStyle: doc.cursor,
7565
8086
  cursorSpace: { w: doc.source.meta.width, h: doc.source.meta.height },
@@ -7582,9 +8103,9 @@ function lowerToComposition(input) {
7582
8103
  crop: m.crop ?? null,
7583
8104
  hasAudio: !!m.meta.hasAudio,
7584
8105
  cursor: sm.map((p) => ({
7585
- t: round7(p.t),
7586
- x: round7(p.x),
7587
- y: round7(p.y)
8106
+ t: round8(p.t),
8107
+ x: round8(p.x),
8108
+ y: round8(p.y)
7588
8109
  })),
7589
8110
  cursorSpace: { w: m.meta.width, h: m.meta.height },
7590
8111
  // The card this media wears (its own placement, bar, corner,
@@ -7766,8 +8287,8 @@ function pinCandidates(doc, clip) {
7766
8287
  step: s.id ?? s.step,
7767
8288
  do: s.do,
7768
8289
  ...s.selector ? { selector: s.selector } : {},
7769
- output: { start: round7(ext.start), end: round7(ext.end) },
7770
- overlap: round7(overlap)
8290
+ output: { start: round8(ext.start), end: round8(ext.end) },
8291
+ overlap: round8(overlap)
7771
8292
  });
7772
8293
  }
7773
8294
  return out.sort((x, y) => y.overlap - x.overlap);
@@ -7798,8 +8319,8 @@ function pinsDataOf(doc, pins) {
7798
8319
  if (!ref) continue;
7799
8320
  out.push({
7800
8321
  id: o.id,
7801
- start: round7(o.start),
7802
- dur: round7(Math.max(OVERLAY_MIN_DURATION, o.duration)),
8322
+ start: round8(o.start),
8323
+ dur: round8(Math.max(OVERLAY_MIN_DURATION, o.duration)),
7803
8324
  x: round43(ref.rect.x),
7804
8325
  y: round43(ref.rect.y),
7805
8326
  w: round43(ref.rect.w),
@@ -7812,8 +8333,8 @@ function pinsDataOf(doc, pins) {
7812
8333
  // leader's end with the pointer through [t0, t1], source seconds.
7813
8334
  ...ref.carry ? {
7814
8335
  carry: {
7815
- t0: round7(ref.carry.t0),
7816
- t1: round7(ref.carry.t1),
8336
+ t0: round8(ref.carry.t0),
8337
+ t1: round8(ref.carry.t1),
7817
8338
  x0: round43(ref.carry.x0),
7818
8339
  y0: round43(ref.carry.y0)
7819
8340
  }
@@ -7860,7 +8381,7 @@ function clickFxData(doc, rated) {
7860
8381
  }
7861
8382
  };
7862
8383
  }
7863
- function round7(v) {
8384
+ function round8(v) {
7864
8385
  return Math.round(v * 1e3) / 1e3;
7865
8386
  }
7866
8387
 
@@ -7950,8 +8471,8 @@ function planAutoSpeed(track, opts) {
7950
8471
  accepted.sort((a, b) => a.in - b.in);
7951
8472
  return accepted.map((c, i) => ({
7952
8473
  id: `s${i}`,
7953
- in: round8(c.in),
7954
- out: round8(c.out),
8474
+ in: round9(c.in),
8475
+ out: round9(c.out),
7955
8476
  rate: clampSpeedRate(c.rate),
7956
8477
  source: "auto"
7957
8478
  }));
@@ -8004,7 +8525,7 @@ function collectRuns(evs, gap, minLen, emit2) {
8004
8525
  }
8005
8526
  flush();
8006
8527
  }
8007
- function round8(v) {
8528
+ function round9(v) {
8008
8529
  return Math.round(v * 1e3) / 1e3;
8009
8530
  }
8010
8531
 
@@ -8186,16 +8707,16 @@ function momentsFromDoc(doc, plan, opts = {}) {
8186
8707
  return {
8187
8708
  id: `m${String(i + 1).padStart(2, "0")}`,
8188
8709
  kind: d.kind,
8189
- source: { in: round9(d.in), out: round9(d.out) },
8190
- output: output ? { in: round9(output.start), out: round9(output.end) } : null,
8191
- at: d.at === null ? null : round9(d.at),
8192
- outputAt: outAt ? round9(outAt.start) : null,
8193
- focus: d.focus ? { cx: round9(d.focus.cx), cy: round9(d.focus.cy) } : null,
8710
+ source: { in: round10(d.in), out: round10(d.out) },
8711
+ output: output ? { in: round10(output.start), out: round10(output.end) } : null,
8712
+ at: d.at === null ? null : round10(d.at),
8713
+ outputAt: outAt ? round10(outAt.start) : null,
8714
+ focus: d.focus ? { cx: round10(d.focus.cx), cy: round10(d.focus.cy) } : null,
8194
8715
  rect: d.rect ? {
8195
- x: round9(d.rect.x),
8196
- y: round9(d.rect.y),
8197
- w: round9(d.rect.w),
8198
- h: round9(d.rect.h)
8716
+ x: round10(d.rect.x),
8717
+ y: round10(d.rect.y),
8718
+ w: round10(d.rect.w),
8719
+ h: round10(d.rect.h)
8199
8720
  } : null,
8200
8721
  ...d.clicks !== void 0 ? { clicks: d.clicks } : {},
8201
8722
  ...d.surface ? { surface: true } : {},
@@ -8216,7 +8737,7 @@ function activityOf(bins, d) {
8216
8737
  sum += bins[i];
8217
8738
  n++;
8218
8739
  }
8219
- return n ? round9(sum / n) : null;
8740
+ return n ? round10(sum / n) : null;
8220
8741
  }
8221
8742
  function saidOver(transcript, d) {
8222
8743
  if (!transcript?.length) return null;
@@ -8225,7 +8746,7 @@ function saidOver(transcript, d) {
8225
8746
  const text = transcript.filter((s) => s.start < hi && s.end > lo).map((s) => s.text.trim()).filter(Boolean).join(" ");
8226
8747
  return text || null;
8227
8748
  }
8228
- function round9(v) {
8749
+ function round10(v) {
8229
8750
  return Math.round(v * 1e3) / 1e3;
8230
8751
  }
8231
8752
 
@@ -8732,8 +9253,8 @@ function buildDigest(input) {
8732
9253
  return {
8733
9254
  digestVersion: DIGEST_VERSION,
8734
9255
  take: {
8735
- sourceDuration: round10(meta.durationMs / 1e3),
8736
- outputDuration: round10(input.outputDuration),
9256
+ sourceDuration: round11(meta.durationMs / 1e3),
9257
+ outputDuration: round11(input.outputDuration),
8737
9258
  width: meta.width,
8738
9259
  height: meta.height,
8739
9260
  captureWidth: meta.captureWidth ?? null,
@@ -8785,7 +9306,7 @@ function buildDigest(input) {
8785
9306
  function outputDurationOf(doc) {
8786
9307
  return docOutputDuration(doc);
8787
9308
  }
8788
- function round10(v) {
9309
+ function round11(v) {
8789
9310
  return Math.round(v * 1e3) / 1e3;
8790
9311
  }
8791
9312
 
@@ -8811,20 +9332,20 @@ function setSpeedInRange(spans, srcIn, srcOut, rate) {
8811
9332
  continue;
8812
9333
  }
8813
9334
  if (srcIn - s.in >= MIN_REMAINDER)
8814
- kept.push({ ...s, out: round11(srcIn), source: "manual" });
9335
+ kept.push({ ...s, out: round12(srcIn), source: "manual" });
8815
9336
  if (s.out - srcOut >= MIN_REMAINDER)
8816
9337
  kept.push({
8817
9338
  ...s,
8818
9339
  id: mintId(used),
8819
- in: round11(srcOut),
9340
+ in: round12(srcOut),
8820
9341
  source: "manual"
8821
9342
  });
8822
9343
  }
8823
9344
  if (rate != null)
8824
9345
  kept.push({
8825
9346
  id: mintId(used),
8826
- in: round11(srcIn),
8827
- out: round11(srcOut),
9347
+ in: round12(srcIn),
9348
+ out: round12(srcOut),
8828
9349
  rate: clampSpeedRate(rate),
8829
9350
  source: "manual"
8830
9351
  });
@@ -8840,9 +9361,9 @@ function removeSourceRange(segments, srcIn, srcOut) {
8840
9361
  next.push(seg);
8841
9362
  continue;
8842
9363
  }
8843
- if (srcIn - seg.in >= MIN_SEGMENT) next.push({ ...seg, out: round11(srcIn) });
9364
+ if (srcIn - seg.in >= MIN_SEGMENT) next.push({ ...seg, out: round12(srcIn) });
8844
9365
  if (seg.out - srcOut >= MIN_SEGMENT)
8845
- next.push({ ...seg, in: round11(srcOut) });
9366
+ next.push({ ...seg, in: round12(srcOut) });
8846
9367
  }
8847
9368
  return next.length ? next : [...segments];
8848
9369
  }
@@ -8860,8 +9381,8 @@ function zoomSpanForRange(zoom, srcIn, srcOut) {
8860
9381
  while (used.has(`u${n}`)) n++;
8861
9382
  return {
8862
9383
  id: `u${n}`,
8863
- in: round11(start),
8864
- out: round11(end),
9384
+ in: round12(start),
9385
+ out: round12(end),
8865
9386
  level: DEFAULT_ZOOM_LEVEL,
8866
9387
  cx: 0.5,
8867
9388
  cy: 0.5,
@@ -8875,7 +9396,7 @@ function mintId(used) {
8875
9396
  used.add(id);
8876
9397
  return id;
8877
9398
  }
8878
- function round11(v) {
9399
+ function round12(v) {
8879
9400
  return Math.round(v * 1e3) / 1e3;
8880
9401
  }
8881
9402
 
@@ -9586,8 +10107,8 @@ var videoLane = {
9586
10107
  return segments.map((s, i) => ({
9587
10108
  id: `seg-${i}`,
9588
10109
  kind: "clip",
9589
- t: round12(starts[i]),
9590
- duration: round12(
10110
+ t: round13(starts[i]),
10111
+ duration: round13(
9591
10112
  outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i))
9592
10113
  )
9593
10114
  }));
@@ -9670,7 +10191,7 @@ var videoLane = {
9670
10191
  if (local >= m.t - 1e-9 && local <= m.t + m.duration + 1e-9)
9671
10192
  return null;
9672
10193
  }
9673
- const sourceT = round12(
10194
+ const sourceT = round13(
9674
10195
  mapTime3(segmentPieces(s, speeds, freezes, leading), local)
9675
10196
  );
9676
10197
  if (sourceT - s.in < 0.05 || s.out - sourceT < 0.05) return null;
@@ -9711,7 +10232,7 @@ var videoLane = {
9711
10232
  n === 1 || !contiguous(segments, n - 1)
9712
10233
  )
9713
10234
  ] : []
9714
- ].map(round12);
10235
+ ].map(round13);
9715
10236
  }
9716
10237
  };
9717
10238
  function nextFreezeId(doc) {
@@ -9738,8 +10259,8 @@ var freezeLane = {
9738
10259
  {
9739
10260
  id: f.id,
9740
10261
  kind: "clip",
9741
- t: round12(m.t),
9742
- duration: round12(m.duration),
10262
+ t: round13(m.t),
10263
+ duration: round13(m.duration),
9743
10264
  label: `${trimZeros(f.seconds)}s`
9744
10265
  }
9745
10266
  ] : [];
@@ -9761,7 +10282,7 @@ var freezeLane = {
9761
10282
  if (g.t >= m.t - 1e-9 && g.t <= m.t + m.duration + 1e-9) return null;
9762
10283
  }
9763
10284
  if (g.t > totalDuration7(rated) + 1e-9) return null;
9764
- const srcT = round12(mapTime3(rated, Math.max(0, g.t)));
10285
+ const srcT = round13(mapTime3(rated, Math.max(0, g.t)));
9765
10286
  if (!kept(srcT) || !clear(srcT)) return null;
9766
10287
  const id = nextFreezeId(doc);
9767
10288
  const mediaHere = mediaAtOutput(rated, Math.max(0, g.t));
@@ -9803,7 +10324,7 @@ var freezeLane = {
9803
10324
  const marks = freezeOutputExtents(splitBySpeed4(segs, speeds), freezes);
9804
10325
  const mine = marks.get(f.id);
9805
10326
  const endOut = mine ? mine.t + mine.duration : null;
9806
- const srcT = round12(
10327
+ const srcT = round13(
9807
10328
  Math.min(sourceDuration, mapTime3(base, Math.max(0, g.t)))
9808
10329
  );
9809
10330
  if (!kept(srcT) || !clear(srcT, f.id)) return null;
@@ -9863,8 +10384,8 @@ var zoomLane = {
9863
10384
  {
9864
10385
  id: z.id,
9865
10386
  kind: "clip",
9866
- t: round12(ext.start),
9867
- duration: round12(ext.end - ext.start),
10387
+ t: round13(ext.start),
10388
+ duration: round13(ext.end - ext.start),
9868
10389
  label: `${z.level.toFixed(2)}\xD7`
9869
10390
  }
9870
10391
  ];
@@ -9891,8 +10412,8 @@ var zoomLane = {
9891
10412
  ...d.zoom,
9892
10413
  {
9893
10414
  id,
9894
- in: round12(srcT),
9895
- out: round12(srcT + len),
10415
+ in: round13(srcT),
10416
+ out: round13(srcT + len),
9896
10417
  level: DEFAULT_ZOOM_LEVEL,
9897
10418
  cx,
9898
10419
  cy,
@@ -9924,8 +10445,8 @@ var zoomLane = {
9924
10445
  return (d) => {
9925
10446
  const z = d.zoom.find((x) => x.id === g.id);
9926
10447
  if (!z) return;
9927
- z.in = round12(newIn);
9928
- z.out = round12(newIn + len);
10448
+ z.in = round13(newIn);
10449
+ z.out = round13(newIn + len);
9929
10450
  z.source = "manual";
9930
10451
  d.zoom.sort((a, b) => a.in - b.in);
9931
10452
  };
@@ -9954,8 +10475,8 @@ var zoomLane = {
9954
10475
  return (d) => {
9955
10476
  const z = d.zoom.find((x) => x.id === g.id);
9956
10477
  if (!z) return;
9957
- z.in = round12(next.in);
9958
- z.out = round12(next.out);
10478
+ z.in = round13(next.in);
10479
+ z.out = round13(next.out);
9959
10480
  z.source = "manual";
9960
10481
  };
9961
10482
  }
@@ -9977,8 +10498,8 @@ function cursorFocusAt(doc, srcT) {
9977
10498
  if (Math.abs(e.t - ms) < Math.abs(best.t - ms)) best = e;
9978
10499
  const clamp015 = (v) => Math.max(0, Math.min(1, v));
9979
10500
  return {
9980
- cx: round12(clamp015(best.x / width)),
9981
- cy: round12(clamp015(best.y / height))
10501
+ cx: round13(clamp015(best.x / width)),
10502
+ cy: round13(clamp015(best.y / height))
9982
10503
  };
9983
10504
  }
9984
10505
  var tiltLane = {
@@ -9992,8 +10513,8 @@ var tiltLane = {
9992
10513
  {
9993
10514
  id: z.id,
9994
10515
  kind: "clip",
9995
- t: round12(ext.start),
9996
- duration: round12(ext.end - ext.start),
10516
+ t: round13(ext.start),
10517
+ duration: round13(ext.end - ext.start),
9997
10518
  label: `${formatDeg(z.rx)}\xB0/${formatDeg(z.ry)}\xB0`
9998
10519
  }
9999
10520
  ];
@@ -10019,8 +10540,8 @@ var tiltLane = {
10019
10540
  ...d.tilt ?? [],
10020
10541
  {
10021
10542
  id,
10022
- in: round12(srcT),
10023
- out: round12(srcT + len),
10543
+ in: round13(srcT),
10544
+ out: round13(srcT + len),
10024
10545
  rx: DEFAULT_TILT_POSE.rx,
10025
10546
  ry: DEFAULT_TILT_POSE.ry,
10026
10547
  source: "manual",
@@ -10051,8 +10572,8 @@ var tiltLane = {
10051
10572
  return (d) => {
10052
10573
  const z = (d.tilt ?? []).find((x) => x.id === g.id);
10053
10574
  if (!z || !d.tilt) return;
10054
- z.in = round12(newIn);
10055
- z.out = round12(newIn + len);
10575
+ z.in = round13(newIn);
10576
+ z.out = round13(newIn + len);
10056
10577
  z.source = "manual";
10057
10578
  d.tilt.sort((a, b) => a.in - b.in);
10058
10579
  };
@@ -10081,8 +10602,8 @@ var tiltLane = {
10081
10602
  return (d) => {
10082
10603
  const z = (d.tilt ?? []).find((x) => x.id === g.id);
10083
10604
  if (!z) return;
10084
- z.in = round12(next.in);
10085
- z.out = round12(next.out);
10605
+ z.in = round13(next.in);
10606
+ z.out = round13(next.out);
10086
10607
  z.source = "manual";
10087
10608
  };
10088
10609
  }
@@ -10109,8 +10630,8 @@ var camMoveLane = {
10109
10630
  {
10110
10631
  id: z.id,
10111
10632
  kind: "clip",
10112
- t: round12(ext.start),
10113
- duration: round12(ext.end - ext.start),
10633
+ t: round13(ext.start),
10634
+ duration: round13(ext.end - ext.start),
10114
10635
  label: z.size != null ? `${Math.round(z.size * 100)}%` : "Move"
10115
10636
  }
10116
10637
  ];
@@ -10134,8 +10655,8 @@ var camMoveLane = {
10134
10655
  ...d.camMotion ?? [],
10135
10656
  {
10136
10657
  id,
10137
- in: round12(srcT),
10138
- out: round12(srcT + len),
10658
+ in: round13(srcT),
10659
+ out: round13(srcT + len),
10139
10660
  ...DEFAULT_CAM_POSE,
10140
10661
  source: "manual"
10141
10662
  }
@@ -10164,8 +10685,8 @@ var camMoveLane = {
10164
10685
  return (d) => {
10165
10686
  const z = (d.camMotion ?? []).find((x) => x.id === g.id);
10166
10687
  if (!z || !d.camMotion) return;
10167
- z.in = round12(newIn);
10168
- z.out = round12(newIn + len);
10688
+ z.in = round13(newIn);
10689
+ z.out = round13(newIn + len);
10169
10690
  z.source = "manual";
10170
10691
  d.camMotion.sort((a, b) => a.in - b.in);
10171
10692
  };
@@ -10194,8 +10715,8 @@ var camMoveLane = {
10194
10715
  return (d) => {
10195
10716
  const z = (d.camMotion ?? []).find((x) => x.id === g.id);
10196
10717
  if (!z) return;
10197
- z.in = round12(next.in);
10198
- z.out = round12(next.out);
10718
+ z.in = round13(next.in);
10719
+ z.out = round13(next.out);
10199
10720
  z.source = "manual";
10200
10721
  };
10201
10722
  }
@@ -10228,10 +10749,10 @@ var camLane = {
10228
10749
  items.push({
10229
10750
  id: `cam-${i}`,
10230
10751
  kind: "clip",
10231
- t: round12(
10752
+ t: round13(
10232
10753
  starts[i] + outputLen({ in: s.in, out: a }, speeds, freezes, leading)
10233
10754
  ),
10234
- duration: round12(
10755
+ duration: round13(
10235
10756
  outputLen({ in: a, out: b }, speeds, freezes, a === s.in && leading)
10236
10757
  )
10237
10758
  });
@@ -10260,7 +10781,7 @@ var camLane = {
10260
10781
  Math.max(first.in, last.out - span)
10261
10782
  );
10262
10783
  return (d) => {
10263
- d.cam.window = { in: round12(newIn), out: round12(newIn + span) };
10784
+ d.cam.window = { in: round13(newIn), out: round13(newIn + span) };
10264
10785
  };
10265
10786
  }
10266
10787
  const overlapping = eff.map((s, i) => ({
@@ -10273,8 +10794,8 @@ var camLane = {
10273
10794
  const next = g.edge === "start" ? { in: Math.min(sourceT, current.out - 0.05), out: current.out } : { in: current.in, out: Math.max(sourceT, current.in + 0.05) };
10274
10795
  return (d) => {
10275
10796
  d.cam.window = {
10276
- in: round12(Math.max(0, next.in)),
10277
- out: round12(Math.min(sourceDuration, next.out))
10797
+ in: round13(Math.max(0, next.in)),
10798
+ out: round13(Math.min(sourceDuration, next.out))
10278
10799
  };
10279
10800
  };
10280
10801
  },
@@ -10294,8 +10815,8 @@ var micLane = {
10294
10815
  return segments.map((s, i) => ({
10295
10816
  id: `mic-${i}`,
10296
10817
  kind: "clip",
10297
- t: round12(starts[i]),
10298
- duration: round12(
10818
+ t: round13(starts[i]),
10819
+ duration: round13(
10299
10820
  outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i))
10300
10821
  )
10301
10822
  }));
@@ -10351,8 +10872,8 @@ var speedLane = {
10351
10872
  {
10352
10873
  id: sp.id,
10353
10874
  kind: "clip",
10354
- t: round12(start),
10355
- duration: round12(end - start),
10875
+ t: round13(start),
10876
+ duration: round13(end - start),
10356
10877
  label: `${sp.rate}\xD7`
10357
10878
  }
10358
10879
  ];
@@ -10376,8 +10897,8 @@ var speedLane = {
10376
10897
  ...d.speed ?? [],
10377
10898
  {
10378
10899
  id,
10379
- in: round12(srcT),
10380
- out: round12(srcT + len),
10900
+ in: round13(srcT),
10901
+ out: round13(srcT + len),
10381
10902
  rate: DEFAULT_SPEED_RATE,
10382
10903
  source: "manual",
10383
10904
  ...mediaHere ? { media: mediaHere } : {}
@@ -10408,8 +10929,8 @@ var speedLane = {
10408
10929
  return (d) => {
10409
10930
  const s = d.speed?.find((x) => x.id === g.id);
10410
10931
  if (!s) return;
10411
- s.in = round12(newIn);
10412
- s.out = round12(newIn + len);
10932
+ s.in = round13(newIn);
10933
+ s.out = round13(newIn + len);
10413
10934
  s.source = "manual";
10414
10935
  d.speed.sort((a, b) => a.in - b.in);
10415
10936
  };
@@ -10438,8 +10959,8 @@ var speedLane = {
10438
10959
  return (d) => {
10439
10960
  const s = d.speed?.find((x) => x.id === g.id);
10440
10961
  if (!s) return;
10441
- s.in = round12(next.in);
10442
- s.out = round12(next.out);
10962
+ s.in = round13(next.in);
10963
+ s.out = round13(next.out);
10443
10964
  s.source = "manual";
10444
10965
  };
10445
10966
  }
@@ -10458,8 +10979,8 @@ var audioLane = {
10458
10979
  return doc.audio.map((c) => ({
10459
10980
  id: c.id,
10460
10981
  kind: "clip",
10461
- t: round12(c.start),
10462
- duration: round12(clipLength(c)),
10982
+ t: round13(c.start),
10983
+ duration: round13(clipLength(c)),
10463
10984
  label: c.name
10464
10985
  }));
10465
10986
  },
@@ -10470,7 +10991,7 @@ var audioLane = {
10470
10991
  const start = Math.max(0, g.t);
10471
10992
  return (d) => {
10472
10993
  const c = d.audio.find((x) => x.id === g.id);
10473
- if (c) c.start = round12(start);
10994
+ if (c) c.start = round13(start);
10474
10995
  };
10475
10996
  }
10476
10997
  if (g.type === "resize") {
@@ -10483,8 +11004,8 @@ var audioLane = {
10483
11004
  return (d) => {
10484
11005
  const c = d.audio.find((x) => x.id === g.id);
10485
11006
  if (!c) return;
10486
- c.start = round12(newStart2);
10487
- c.loopLen = round12(end - newStart2);
11007
+ c.start = round13(newStart2);
11008
+ c.loopLen = round13(end - newStart2);
10488
11009
  };
10489
11010
  }
10490
11011
  const delta = g.t - clip.start;
@@ -10493,22 +11014,22 @@ var audioLane = {
10493
11014
  return (d) => {
10494
11015
  const c = d.audio.find((x) => x.id === g.id);
10495
11016
  if (!c) return;
10496
- c.in = round12(newIn);
10497
- c.start = round12(newStart);
11017
+ c.in = round13(newIn);
11018
+ c.start = round13(newStart);
10498
11019
  };
10499
11020
  }
10500
11021
  if (clip.loop) {
10501
11022
  const newLen = Math.max(0.1, g.t - clip.start);
10502
11023
  return (d) => {
10503
11024
  const c = d.audio.find((x) => x.id === g.id);
10504
- if (c) c.loopLen = round12(newLen);
11025
+ if (c) c.loopLen = round13(newLen);
10505
11026
  };
10506
11027
  }
10507
11028
  const span = Math.max(0.05, g.t - clip.start);
10508
11029
  const newOut = Math.min(clip.in + span, clip.duration);
10509
11030
  return (d) => {
10510
11031
  const c = d.audio.find((x) => x.id === g.id);
10511
- if (c) c.out = round12(Math.max(c.in + 0.05, newOut));
11032
+ if (c) c.out = round13(Math.max(c.in + 0.05, newOut));
10512
11033
  };
10513
11034
  }
10514
11035
  if (g.type === "remove") {
@@ -10521,8 +11042,8 @@ var audioLane = {
10521
11042
  },
10522
11043
  magnets(doc) {
10523
11044
  return doc.audio.flatMap((c) => [
10524
- round12(c.start),
10525
- round12(c.start + clipLength(c))
11045
+ round13(c.start),
11046
+ round13(c.start + clipLength(c))
10526
11047
  ]);
10527
11048
  }
10528
11049
  };
@@ -10540,7 +11061,7 @@ function poseItems(clipId, start, duration, motion) {
10540
11061
  return (motion ?? []).map((p, i) => ({
10541
11062
  id: `${clipId}::k${i}`,
10542
11063
  kind: "keyframe",
10543
- t: round12(start + Math.min(Math.max(0, p.at), duration))
11064
+ t: round13(start + Math.min(Math.max(0, p.at), duration))
10544
11065
  }));
10545
11066
  }
10546
11067
  var overlaysLane = {
@@ -10551,8 +11072,8 @@ var overlaysLane = {
10551
11072
  {
10552
11073
  id: o.id,
10553
11074
  kind: "clip",
10554
- t: round12(o.start),
10555
- duration: round12(o.duration),
11075
+ t: round13(o.start),
11076
+ duration: round13(o.duration),
10556
11077
  label: o.kind === "text" ? o.text.split("\n")[0].slice(0, 24) || "Text" : o.kind === "html" ? htmlLayerLabel(o) : o.kind === "image" ? "Image" : "Video"
10557
11078
  },
10558
11079
  // Pose diamonds render after the clips, so they sit on top.
@@ -10572,7 +11093,7 @@ var overlaysLane = {
10572
11093
  return (d) => {
10573
11094
  const o = (d.overlays ?? []).find((x) => x.id === kf.clipId);
10574
11095
  const p = o?.motion?.[kf.index];
10575
- if (p) p.at = round12(at);
11096
+ if (p) p.at = round13(at);
10576
11097
  };
10577
11098
  }
10578
11099
  if (g.type === "remove") {
@@ -10601,8 +11122,8 @@ var overlaysLane = {
10601
11122
  {
10602
11123
  id,
10603
11124
  kind: "text",
10604
- start: round12(start),
10605
- duration: round12(len),
11125
+ start: round13(start),
11126
+ duration: round13(len),
10606
11127
  text: "Title",
10607
11128
  preset: "title",
10608
11129
  // Frame fractions: centered, lower-third — at any aspect ratio.
@@ -10617,7 +11138,7 @@ var overlaysLane = {
10617
11138
  const start = Math.max(0, g.t);
10618
11139
  return (d) => {
10619
11140
  const o = (d.overlays ?? []).find((x) => x.id === g.id);
10620
- if (o) o.start = round12(start);
11141
+ if (o) o.start = round13(start);
10621
11142
  };
10622
11143
  }
10623
11144
  if (g.type === "resize") {
@@ -10627,14 +11148,14 @@ var overlaysLane = {
10627
11148
  return (d) => {
10628
11149
  const o = (d.overlays ?? []).find((x) => x.id === g.id);
10629
11150
  if (!o) return;
10630
- o.start = round12(newStart);
10631
- o.duration = round12(end - newStart);
11151
+ o.start = round13(newStart);
11152
+ o.duration = round13(end - newStart);
10632
11153
  };
10633
11154
  }
10634
11155
  const newLen = Math.max(OVERLAY_MIN_DURATION, g.t - clip.start);
10635
11156
  return (d) => {
10636
11157
  const o = (d.overlays ?? []).find((x) => x.id === g.id);
10637
- if (o) o.duration = round12(newLen);
11158
+ if (o) o.duration = round13(newLen);
10638
11159
  };
10639
11160
  }
10640
11161
  return (d) => {
@@ -10643,8 +11164,8 @@ var overlaysLane = {
10643
11164
  },
10644
11165
  magnets(doc) {
10645
11166
  return (doc.overlays ?? []).flatMap((o) => [
10646
- round12(o.start),
10647
- round12(o.start + o.duration)
11167
+ round13(o.start),
11168
+ round13(o.start + o.duration)
10648
11169
  ]);
10649
11170
  }
10650
11171
  };
@@ -10657,8 +11178,8 @@ var objectsLane = {
10657
11178
  {
10658
11179
  id: o.id,
10659
11180
  kind: "clip",
10660
- t: round12(o.span?.start ?? 0),
10661
- duration: round12(o.span?.duration ?? outDur),
11181
+ t: round13(o.span?.start ?? 0),
11182
+ duration: round13(o.span?.duration ?? outDur),
10662
11183
  label: o.asset.kind === "primitive" ? o.asset.shape : o.asset.kind === "text3d" ? o.asset.text : "model"
10663
11184
  },
10664
11185
  // Pose diamonds: clip-local `at` from the span start (0 span-less).
@@ -10686,7 +11207,7 @@ var objectsLane = {
10686
11207
  return (d) => {
10687
11208
  const o = (d.objects ?? []).find((x) => x.id === kf.clipId);
10688
11209
  const p = o?.motion?.[kf.index];
10689
- if (p) p.at = round12(at);
11210
+ if (p) p.at = round13(at);
10690
11211
  };
10691
11212
  }
10692
11213
  if (g.type === "remove") {
@@ -10707,7 +11228,7 @@ var objectsLane = {
10707
11228
  const start = Math.max(0, g.t);
10708
11229
  return (d) => {
10709
11230
  const o = (d.objects ?? []).find((x) => x.id === g.id);
10710
- if (o?.span) o.span.start = round12(start);
11231
+ if (o?.span) o.span.start = round13(start);
10711
11232
  };
10712
11233
  }
10713
11234
  if (g.type === "resize") {
@@ -10718,14 +11239,14 @@ var objectsLane = {
10718
11239
  return (d) => {
10719
11240
  const o = (d.objects ?? []).find((x) => x.id === g.id);
10720
11241
  if (!o?.span) return;
10721
- o.span.start = round12(newStart);
10722
- o.span.duration = round12(end - newStart);
11242
+ o.span.start = round13(newStart);
11243
+ o.span.duration = round13(end - newStart);
10723
11244
  };
10724
11245
  }
10725
11246
  const newLen = Math.max(OVERLAY_MIN_DURATION, g.t - clip.span.start);
10726
11247
  return (d) => {
10727
11248
  const o = (d.objects ?? []).find((x) => x.id === g.id);
10728
- if (o?.span) o.span.duration = round12(newLen);
11249
+ if (o?.span) o.span.duration = round13(newLen);
10729
11250
  };
10730
11251
  }
10731
11252
  return (d) => {
@@ -10734,7 +11255,7 @@ var objectsLane = {
10734
11255
  },
10735
11256
  magnets(doc) {
10736
11257
  return (doc.objects ?? []).flatMap(
10737
- (o) => o.span ? [round12(o.span.start), round12(o.span.start + o.span.duration)] : []
11258
+ (o) => o.span ? [round13(o.span.start), round13(o.span.start + o.span.duration)] : []
10738
11259
  );
10739
11260
  }
10740
11261
  };
@@ -10761,7 +11282,7 @@ function nextSpeedId(doc) {
10761
11282
  function segIndex(id) {
10762
11283
  return Number(id.replace("seg-", ""));
10763
11284
  }
10764
- function round12(v) {
11285
+ function round13(v) {
10765
11286
  return Math.round(v * 1e3) / 1e3;
10766
11287
  }
10767
11288
 
@@ -10929,6 +11450,7 @@ export {
10929
11450
  CLICK_FX_INTENSITY,
10930
11451
  CLICK_FX_PRE,
10931
11452
  CLICK_HIGHLIGHT_FADE,
11453
+ CLICK_HOLD_MS,
10932
11454
  CLICK_PAIR_MAX,
10933
11455
  CLICK_PULSE_DUR,
10934
11456
  CLICK_RECT_MAX_FRAC,
@@ -11111,6 +11633,10 @@ export {
11111
11633
  cropBox,
11112
11634
  cssFamilies,
11113
11635
  cssWeights,
11636
+ cursorFromCsv,
11637
+ cursorFromPlaywrightTrace,
11638
+ cursorFromRecords,
11639
+ cursorFromTrace,
11114
11640
  cursorIdleFade,
11115
11641
  deltaE,
11116
11642
  deriveViewportCrop,
@@ -11239,6 +11765,7 @@ export {
11239
11765
  planForDigest,
11240
11766
  planeSizeAtDepth,
11241
11767
  platformBarKind,
11768
+ playwrightTraceEntry,
11242
11769
  programDuration,
11243
11770
  projectFromArtifact,
11244
11771
  pumpFreeChainGap,
@@ -11274,6 +11801,7 @@ export {
11274
11801
  studioLayerData,
11275
11802
  tiltLane,
11276
11803
  tiltTrackFromDoc,
11804
+ traceKindOf,
11277
11805
  transitionMult,
11278
11806
  transitionSeconds,
11279
11807
  unionFitLevel,
@@ -11286,6 +11814,8 @@ export {
11286
11814
  withHolds,
11287
11815
  withoutRejected,
11288
11816
  wrapProgramLength,
11817
+ zipEntries,
11818
+ zipEntryBytes,
11289
11819
  zoomCoversRect,
11290
11820
  zoomLane,
11291
11821
  zoomSpanForRange,