@vosjs/studio-core 0.20.0 → 0.22.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.d.ts +129 -13
- package/dist/index.js +250 -87
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -132,6 +132,9 @@ var DEFAULT_CURSOR_STYLE = {
|
|
|
132
132
|
visible: true,
|
|
133
133
|
smoothing: 0.15,
|
|
134
134
|
size: 24,
|
|
135
|
+
// The dot every earlier take was cut with; a NEW take opens on the
|
|
136
|
+
// arrow (projectFromArtifact sets it), so a document that stores
|
|
137
|
+
// 'default' keeps its dot.
|
|
135
138
|
style: "default",
|
|
136
139
|
hideWhenIdle: true,
|
|
137
140
|
clickFx: DEFAULT_CLICK_FX
|
|
@@ -157,6 +160,10 @@ var BASE_FRAME_STYLE = {
|
|
|
157
160
|
radius: 12,
|
|
158
161
|
shadow: 0.4,
|
|
159
162
|
border: 0,
|
|
163
|
+
// The stage camera: a new take's zoom centres its target and shows the
|
|
164
|
+
// ground past the card's edge. A document without the field keeps the
|
|
165
|
+
// clamped magnifier it was cut with (see FrameStyle.camera).
|
|
166
|
+
camera: "stage",
|
|
160
167
|
// 'native' = the recording's own aspect ratio (meta.width/height). See ASPECT_RATIOS.
|
|
161
168
|
aspectRatio: "native",
|
|
162
169
|
browserBar: DEFAULT_BROWSER_BAR
|
|
@@ -431,7 +438,9 @@ function projectFromArtifact(artifact, videoUrl, opts = {}) {
|
|
|
431
438
|
zoom: [],
|
|
432
439
|
// planner runs once the doc is loaded (editor) or planned (CLI)
|
|
433
440
|
audio: [],
|
|
434
|
-
|
|
441
|
+
// A new take wears the OS pointer; DEFAULT_CURSOR_STYLE keeps the dot
|
|
442
|
+
// so a document that stores it renders as it was cut.
|
|
443
|
+
cursor: { ...DEFAULT_CURSOR_STYLE, style: "arrow" },
|
|
435
444
|
cam: { ...DEFAULT_CAM_STYLE },
|
|
436
445
|
frame: {
|
|
437
446
|
...baseFrame,
|
|
@@ -445,7 +454,9 @@ function projectFromArtifact(artifact, videoUrl, opts = {}) {
|
|
|
445
454
|
url: pageDisplayUrl(artifact.meta.pageUrl)
|
|
446
455
|
}
|
|
447
456
|
},
|
|
448
|
-
|
|
457
|
+
// 60 fps: the footage duplicates frames, the camera does not, and a
|
|
458
|
+
// 0.5 s glide at 30 fps is fifteen visible steps.
|
|
459
|
+
export: { resolution: "1080p", fps: 60, format: "mp4" }
|
|
449
460
|
};
|
|
450
461
|
return { doc, videoUrl };
|
|
451
462
|
}
|
|
@@ -1132,29 +1143,35 @@ function clamp(v, lo, hi) {
|
|
|
1132
1143
|
// src/zoomStyle.ts
|
|
1133
1144
|
var ZOOM_STYLES = {
|
|
1134
1145
|
// Cursorful strategy — one steady zoom per activity session, travel by pans.
|
|
1146
|
+
// The camera's timing was measured against a Cursorful take of the same
|
|
1147
|
+
// steps: a zoom-in of ~0.55 s on a heavy ease-out (most of the move in
|
|
1148
|
+
// the first quarter second, a long settle), a zoom-out of ~0.45 s, and
|
|
1149
|
+
// holds of four to five seconds per beat. The old 1.1 s ramp started
|
|
1150
|
+
// 0.75 s BEFORE the click and landed 0.35 s after it, so the frame was
|
|
1151
|
+
// moving more of the time than it was resting.
|
|
1135
1152
|
glide: {
|
|
1136
1153
|
autoZoom: true,
|
|
1137
1154
|
clusterGap: 3,
|
|
1138
|
-
minClusterClicks:
|
|
1155
|
+
minClusterClicks: 1,
|
|
1139
1156
|
targetFill: 0.42,
|
|
1140
1157
|
minLevel: 1.3,
|
|
1141
1158
|
maxLevel: 1.8,
|
|
1142
|
-
lead: 0.
|
|
1143
|
-
hold:
|
|
1159
|
+
lead: 0.35,
|
|
1160
|
+
hold: 1.2,
|
|
1144
1161
|
followByDefault: true,
|
|
1145
1162
|
typingZoom: true,
|
|
1146
1163
|
typingGap: 2.5,
|
|
1147
1164
|
typingHold: 1.1,
|
|
1148
1165
|
typingMinLevel: 1.4,
|
|
1149
|
-
rampIn:
|
|
1150
|
-
rampInOverlap: 0.
|
|
1151
|
-
rampOut:
|
|
1152
|
-
chainGap:
|
|
1153
|
-
pan:
|
|
1154
|
-
ease: "css-bezier(0.
|
|
1155
|
-
panEase: "css-bezier(0.
|
|
1166
|
+
rampIn: 0.55,
|
|
1167
|
+
rampInOverlap: 0.2,
|
|
1168
|
+
rampOut: 0.5,
|
|
1169
|
+
chainGap: 3,
|
|
1170
|
+
pan: 0.55,
|
|
1171
|
+
ease: "css-bezier(0.16, 1, 0.3, 1)",
|
|
1172
|
+
panEase: "css-bezier(0.22, 1, 0.36, 1)",
|
|
1156
1173
|
followSafeRatio: 0.45,
|
|
1157
|
-
followRecenter: 0.
|
|
1174
|
+
followRecenter: 0.55,
|
|
1158
1175
|
followLookahead: 0.4,
|
|
1159
1176
|
tilt: { intensity: "off" }
|
|
1160
1177
|
},
|
|
@@ -1308,7 +1325,7 @@ var ZOOM_STYLES = {
|
|
|
1308
1325
|
keynote: {
|
|
1309
1326
|
autoZoom: true,
|
|
1310
1327
|
clusterGap: 3,
|
|
1311
|
-
minClusterClicks:
|
|
1328
|
+
minClusterClicks: 1,
|
|
1312
1329
|
targetFill: 0.42,
|
|
1313
1330
|
minLevel: 1.3,
|
|
1314
1331
|
maxLevel: 1.8,
|
|
@@ -1417,13 +1434,17 @@ var DWELL_MIN = 0.45;
|
|
|
1417
1434
|
var DWELL_MAX = 2.6;
|
|
1418
1435
|
var DWELL_SPACING = 1.8;
|
|
1419
1436
|
var DRAG_FIT_LEVEL = 1.15;
|
|
1437
|
+
var PRESS_ECHO_MS = 4;
|
|
1438
|
+
var PRESS_ECHO_PX = 2;
|
|
1420
1439
|
var TYPING_MIN_PINGS = 2;
|
|
1421
1440
|
var TYPING_MIN_DUR = 0.5;
|
|
1422
1441
|
var TYPING_CLICK_ABSORB = 1.5;
|
|
1423
1442
|
var TYPING_REFOCUS_FRAC = 0.08;
|
|
1424
1443
|
function groupTrack(track, opts) {
|
|
1425
|
-
const { width, height, clusterGap, typingGap, typingZoom } = opts;
|
|
1426
|
-
const clicks =
|
|
1444
|
+
const { width, height, clusterGap, typingGap, typingZoom, targetFill } = opts;
|
|
1445
|
+
const clicks = dedupePresses(
|
|
1446
|
+
track.filter((e) => e.type === "down")
|
|
1447
|
+
).map((e) => ({ t: e.t / 1e3, rect: e.rect, x: e.x, y: e.y }));
|
|
1427
1448
|
const sessions = typingZoom ? typingSessions(track, width, height, typingGap) : [];
|
|
1428
1449
|
const absorbed = /* @__PURE__ */ new Set();
|
|
1429
1450
|
for (const s of sessions) {
|
|
@@ -1441,15 +1462,43 @@ function groupTrack(track, opts) {
|
|
|
1441
1462
|
s.events = [best, ...s.events];
|
|
1442
1463
|
}
|
|
1443
1464
|
}
|
|
1444
|
-
const
|
|
1465
|
+
const targets = [];
|
|
1466
|
+
const surfaces = [];
|
|
1445
1467
|
for (const c of clicks) {
|
|
1446
1468
|
if (absorbed.has(c)) continue;
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1469
|
+
if (isSurfacePress(c, width, height, targetFill)) surfaces.push(c);
|
|
1470
|
+
else targets.push(c);
|
|
1471
|
+
}
|
|
1472
|
+
const chain = (list) => {
|
|
1473
|
+
const clusters = [];
|
|
1474
|
+
for (const c of list) {
|
|
1475
|
+
const last = clusters.at(-1);
|
|
1476
|
+
const prev = last?.at(-1);
|
|
1477
|
+
if (last && prev && c.t - prev.t <= clusterGap) last.push(c);
|
|
1478
|
+
else clusters.push([c]);
|
|
1479
|
+
}
|
|
1480
|
+
return clusters;
|
|
1481
|
+
};
|
|
1482
|
+
return { sessions, clusters: chain(targets), surfaces: chain(surfaces) };
|
|
1483
|
+
}
|
|
1484
|
+
function dedupePresses(downs) {
|
|
1485
|
+
const kept = [];
|
|
1486
|
+
for (const e of downs) {
|
|
1487
|
+
const prev = kept.at(-1);
|
|
1488
|
+
if (prev && e.t - prev.t <= PRESS_ECHO_MS && Math.abs(e.x - prev.x) <= PRESS_ECHO_PX && Math.abs(e.y - prev.y) <= PRESS_ECHO_PX) {
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
kept.push(e);
|
|
1451
1492
|
}
|
|
1452
|
-
return
|
|
1493
|
+
return kept;
|
|
1494
|
+
}
|
|
1495
|
+
function fitLevel(rect, width, height, targetFill) {
|
|
1496
|
+
if (!rect || rect.w <= 0 || rect.h <= 0) return null;
|
|
1497
|
+
return Math.min(width * targetFill / rect.w, height * targetFill / rect.h);
|
|
1498
|
+
}
|
|
1499
|
+
function isSurfacePress(c, width, height, targetFill) {
|
|
1500
|
+
const fit = fitLevel(c.rect, width, height, targetFill);
|
|
1501
|
+
return fit !== null && fit < DRAG_FIT_LEVEL;
|
|
1453
1502
|
}
|
|
1454
1503
|
function planAutoZoom(track, options) {
|
|
1455
1504
|
const style = resolveZoomStyle(options.style, options.params);
|
|
@@ -1470,31 +1519,31 @@ function planAutoZoom(track, options) {
|
|
|
1470
1519
|
typingHold = style.typingHold,
|
|
1471
1520
|
typingMinLevel = style.typingMinLevel
|
|
1472
1521
|
} = options;
|
|
1473
|
-
const { sessions, clusters } = groupTrack(track, {
|
|
1522
|
+
const { sessions, clusters, surfaces } = groupTrack(track, {
|
|
1474
1523
|
width,
|
|
1475
1524
|
height,
|
|
1476
1525
|
clusterGap,
|
|
1477
1526
|
typingGap,
|
|
1478
|
-
typingZoom
|
|
1527
|
+
typingZoom,
|
|
1528
|
+
targetFill
|
|
1479
1529
|
});
|
|
1480
1530
|
const eligible = clusters.filter((c) => c.length >= minClusterClicks);
|
|
1481
1531
|
const clickSpans = [];
|
|
1482
|
-
const dragReserved =
|
|
1532
|
+
const dragReserved = surfaces.map((cluster, i) => {
|
|
1533
|
+
const f = clusterFocus(cluster, width, height);
|
|
1534
|
+
return {
|
|
1535
|
+
id: `drag${i}`,
|
|
1536
|
+
in: Math.max(0, cluster[0].t - lead),
|
|
1537
|
+
out: cluster[cluster.length - 1].t + hold,
|
|
1538
|
+
level: 1,
|
|
1539
|
+
cx: f.cx,
|
|
1540
|
+
cy: f.cy
|
|
1541
|
+
};
|
|
1542
|
+
});
|
|
1483
1543
|
for (const cluster of eligible) {
|
|
1484
1544
|
const first = cluster[0];
|
|
1485
1545
|
const last = cluster[cluster.length - 1];
|
|
1486
1546
|
const f = focusFor(cluster, width, height, targetFill, minLevel, maxLevel);
|
|
1487
|
-
if (f.fit !== null && f.fit < DRAG_FIT_LEVEL) {
|
|
1488
|
-
dragReserved.push({
|
|
1489
|
-
id: `drag${dragReserved.length}`,
|
|
1490
|
-
in: Math.max(0, first.t - lead),
|
|
1491
|
-
out: last.t + hold,
|
|
1492
|
-
level: 1,
|
|
1493
|
-
cx: f.cx,
|
|
1494
|
-
cy: f.cy
|
|
1495
|
-
});
|
|
1496
|
-
continue;
|
|
1497
|
-
}
|
|
1498
1547
|
clickSpans.push({
|
|
1499
1548
|
in: Math.max(0, first.t - lead),
|
|
1500
1549
|
out: last.t + hold,
|
|
@@ -1668,15 +1717,8 @@ function focusFor(cluster, width, height, targetFill, minLevel, maxLevel) {
|
|
|
1668
1717
|
const n = cluster.length;
|
|
1669
1718
|
const cx = clamp012(sx / n / width);
|
|
1670
1719
|
const cy = clamp012(sy / n / height);
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
if (maxW > 0 && maxH > 0) {
|
|
1674
|
-
const fitX = width * targetFill / maxW;
|
|
1675
|
-
const fitY = height * targetFill / maxH;
|
|
1676
|
-
level = Math.min(fitX, fitY);
|
|
1677
|
-
fit = level;
|
|
1678
|
-
}
|
|
1679
|
-
return { cx, cy, level: clamp2(level, minLevel, maxLevel), fit };
|
|
1720
|
+
const level = fitLevel({ x: 0, y: 0, w: maxW, h: maxH }, width, height, targetFill) ?? maxLevel;
|
|
1721
|
+
return { cx, cy, level: clamp2(level, minLevel, maxLevel) };
|
|
1680
1722
|
}
|
|
1681
1723
|
function clusterFocus(events, width, height) {
|
|
1682
1724
|
let sx = 0;
|
|
@@ -1840,10 +1882,29 @@ function camBubbleRect(cam, W, H = 1080) {
|
|
|
1840
1882
|
radius: cam.shape === "rounded" ? (cam.radius ?? 18) * s : size / 2
|
|
1841
1883
|
};
|
|
1842
1884
|
}
|
|
1843
|
-
function focusBounds(level, layout) {
|
|
1885
|
+
function focusBounds(level, layout, camera = "card") {
|
|
1844
1886
|
if (level <= 1.001) {
|
|
1845
1887
|
return { minX: 0.5, maxX: 0.5, minY: 0.5, maxY: 0.5 };
|
|
1846
1888
|
}
|
|
1889
|
+
if (camera === "stage") {
|
|
1890
|
+
const x2 = stageAxisBounds(
|
|
1891
|
+
layout.dx,
|
|
1892
|
+
layout.dw,
|
|
1893
|
+
layout.cardX,
|
|
1894
|
+
layout.cardW,
|
|
1895
|
+
layout.W,
|
|
1896
|
+
level
|
|
1897
|
+
);
|
|
1898
|
+
const y2 = stageAxisBounds(
|
|
1899
|
+
layout.dy,
|
|
1900
|
+
layout.dh,
|
|
1901
|
+
layout.cardY,
|
|
1902
|
+
layout.cardH,
|
|
1903
|
+
layout.H,
|
|
1904
|
+
level
|
|
1905
|
+
);
|
|
1906
|
+
return { minX: x2.min, maxX: x2.max, minY: y2.min, maxY: y2.max };
|
|
1907
|
+
}
|
|
1847
1908
|
const x = axisBounds(
|
|
1848
1909
|
layout.dx,
|
|
1849
1910
|
layout.dw,
|
|
@@ -1873,8 +1934,19 @@ function axisBounds(anchorOff, anchorSize, coverOff, coverSize, viewport, level)
|
|
|
1873
1934
|
}
|
|
1874
1935
|
return { min: clamp013(lo), max: clamp013(hi) };
|
|
1875
1936
|
}
|
|
1876
|
-
function
|
|
1877
|
-
const b =
|
|
1937
|
+
function stageAxisBounds(anchorOff, anchorSize, coverOff, coverSize, viewport, level) {
|
|
1938
|
+
const b = CAMERA_COVER_MIN / 2 * viewport;
|
|
1939
|
+
let lo = (coverOff + b / level - anchorOff) / anchorSize;
|
|
1940
|
+
let hi = (coverOff + coverSize - b / level - anchorOff) / anchorSize;
|
|
1941
|
+
if (lo > hi) {
|
|
1942
|
+
const mid = (lo + hi) / 2;
|
|
1943
|
+
lo = mid;
|
|
1944
|
+
hi = mid;
|
|
1945
|
+
}
|
|
1946
|
+
return { min: clamp013(lo), max: clamp013(hi) };
|
|
1947
|
+
}
|
|
1948
|
+
function clampFocus(cx, cy, level, layout, camera = "card") {
|
|
1949
|
+
const b = focusBounds(level, layout, camera);
|
|
1878
1950
|
return {
|
|
1879
1951
|
cx: Math.min(b.maxX, Math.max(b.minX, cx)),
|
|
1880
1952
|
cy: Math.min(b.maxY, Math.max(b.minY, cy))
|
|
@@ -1883,6 +1955,45 @@ function clampFocus(cx, cy, level, layout) {
|
|
|
1883
1955
|
function clamp013(v) {
|
|
1884
1956
|
return Math.max(0, Math.min(1, v));
|
|
1885
1957
|
}
|
|
1958
|
+
function cameraModel(frame) {
|
|
1959
|
+
return frame?.camera === "stage" ? "stage" : "card";
|
|
1960
|
+
}
|
|
1961
|
+
var CAMERA_CENTRE_RAMP = 0.3;
|
|
1962
|
+
var CAMERA_COVER_MIN = 0.8;
|
|
1963
|
+
function cameraCentring(level) {
|
|
1964
|
+
const u = clamp013((level - 1) / CAMERA_CENTRE_RAMP);
|
|
1965
|
+
return u * u * (3 - 2 * u);
|
|
1966
|
+
}
|
|
1967
|
+
function zoomView(level, cx, cy, layout, camera = "card") {
|
|
1968
|
+
const L = Math.max(1, level);
|
|
1969
|
+
const fx = layout.dx + cx * layout.dw;
|
|
1970
|
+
const fy = layout.dy + cy * layout.dh;
|
|
1971
|
+
const ox = layout.W / 2;
|
|
1972
|
+
const oy = layout.H / 2;
|
|
1973
|
+
const t = camera === "stage" ? cameraCentring(L) : 0;
|
|
1974
|
+
const wcx = fx + (ox - fx) / L * (1 - t);
|
|
1975
|
+
const wcy = fy + (oy - fy) / L * (1 - t);
|
|
1976
|
+
return { level: L, fx, fy, wcx, wcy, ox, oy };
|
|
1977
|
+
}
|
|
1978
|
+
function zoomViewport(level, cx, cy, layout, camera = "card") {
|
|
1979
|
+
const v = zoomView(level, cx, cy, layout, camera);
|
|
1980
|
+
const size = 1 / v.level;
|
|
1981
|
+
return {
|
|
1982
|
+
x: (v.wcx - layout.W / (2 * v.level)) / layout.W,
|
|
1983
|
+
y: (v.wcy - layout.H / (2 * v.level)) / layout.H,
|
|
1984
|
+
w: size,
|
|
1985
|
+
h: size
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
function focusForViewportCentre(cX, cY, level, layout, camera = "card") {
|
|
1989
|
+
const L = Math.max(1, level);
|
|
1990
|
+
const t = camera === "stage" ? cameraCentring(L) : 0;
|
|
1991
|
+
const a = 1 - (1 - t) / L;
|
|
1992
|
+
if (a <= 1e-6) return { cx: 0.5, cy: 0.5 };
|
|
1993
|
+
const fx = (cX * layout.W - layout.W / 2 * (1 - t) / L) / a;
|
|
1994
|
+
const fy = (cY * layout.H - layout.H / 2 * (1 - t) / L) / a;
|
|
1995
|
+
return { cx: (fx - layout.dx) / layout.dw, cy: (fy - layout.dy) / layout.dh };
|
|
1996
|
+
}
|
|
1886
1997
|
function levelForFocusFraction(frac) {
|
|
1887
1998
|
if (!(frac > 0)) return clampZoomLevel(Number.POSITIVE_INFINITY);
|
|
1888
1999
|
return clampZoomLevel(Math.max(1.1, 1 / frac));
|
|
@@ -2787,6 +2898,7 @@ function followFocusEvents(span, cursor, space, layout, options = {}) {
|
|
|
2787
2898
|
const safeRatio = options.safeRatio ?? FOLLOW_SAFE_RATIO;
|
|
2788
2899
|
const recenter = options.recenter ?? FOLLOW_RECENTER;
|
|
2789
2900
|
const lookahead = options.lookahead ?? 0;
|
|
2901
|
+
const camera = options.camera ?? "card";
|
|
2790
2902
|
const level = clampZoomLevel(span.level);
|
|
2791
2903
|
if (!cursor.length || !space.w || !space.h || level <= 1.001) {
|
|
2792
2904
|
return { entry: null, events: [] };
|
|
@@ -2797,12 +2909,7 @@ function followFocusEvents(span, cursor, space, layout, options = {}) {
|
|
|
2797
2909
|
ny: clamp014(e.y / space.h)
|
|
2798
2910
|
}));
|
|
2799
2911
|
if (!pts.length) return { entry: null, events: [] };
|
|
2800
|
-
|
|
2801
|
-
for (const p of pts) {
|
|
2802
|
-
if (p.t > span.in) break;
|
|
2803
|
-
entryPt = p;
|
|
2804
|
-
}
|
|
2805
|
-
const entry = clampFocus(entryPt.nx, entryPt.ny, level, layout);
|
|
2912
|
+
const entry = clampFocus(span.cx, span.cy, level, layout, camera);
|
|
2806
2913
|
const thrX = safeRatio * layout.W / (2 * level * layout.dw);
|
|
2807
2914
|
const thrY = safeRatio * layout.H / (2 * level * layout.dh);
|
|
2808
2915
|
const events = [];
|
|
@@ -2815,7 +2922,7 @@ function followFocusEvents(span, cursor, space, layout, options = {}) {
|
|
|
2815
2922
|
if (p.t < nextAllowed) continue;
|
|
2816
2923
|
if (Math.abs(p.nx - cx) > thrX || Math.abs(p.ny - cy) > thrY) {
|
|
2817
2924
|
const target = lookahead > 0 ? sampleAt2(pts, Math.min(p.t + lookahead, span.out)) : p;
|
|
2818
|
-
const f = clampFocus(target.nx, target.ny, level, layout);
|
|
2925
|
+
const f = clampFocus(target.nx, target.ny, level, layout, camera);
|
|
2819
2926
|
if (Math.abs(f.cx - cx) < 1e-3 && Math.abs(f.cy - cy) < 1e-3) continue;
|
|
2820
2927
|
events.push({ t: round2(p.t), cx: round2(f.cx), cy: round2(f.cy) });
|
|
2821
2928
|
cx = f.cx;
|
|
@@ -5819,9 +5926,28 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
5819
5926
|
// ceZs = the zoom transform's device scale, for the two values below that
|
|
5820
5927
|
// are sized in DEVICE pixels inside a scaled user space.
|
|
5821
5928
|
var ceZs = 1
|
|
5929
|
+
// The camera model (cfr.camera; MIRRORS layout.ts zoomView \u2014 change them
|
|
5930
|
+
// together). The magnifier scales the card about the focus, which stays
|
|
5931
|
+
// where it is on screen; the stage camera also slides the card so the
|
|
5932
|
+
// focus lands at the frame's centre, blended in over CAMERA_CENTRE_RAMP
|
|
5933
|
+
// of level (a smoothstep, 0 at level 1) so a ramp through the band slides
|
|
5934
|
+
// without a kink, and past the card's edge the ground shows. wcx/wcy is
|
|
5935
|
+
// the content point at the frame's centre under either model; the two
|
|
5936
|
+
// branches are the same map, the magnifier's kept in its old float order
|
|
5937
|
+
// so every existing document renders byte-identically.
|
|
5938
|
+
var camStage = cfr.camera === 'stage'
|
|
5939
|
+
var fx = dx + zx * dw, fy = dy + zy * dh
|
|
5940
|
+
var wcx = fx, wcy = fy
|
|
5822
5941
|
if (lvl > 1.001 && !d.zoomSuppressed) {
|
|
5823
|
-
|
|
5824
|
-
|
|
5942
|
+
if (camStage) {
|
|
5943
|
+
var ctu = Math.max(0, Math.min(1, (lvl - 1) / ${CAMERA_CENTRE_RAMP}))
|
|
5944
|
+
var ctt = ctu * ctu * (3 - 2 * ctu)
|
|
5945
|
+
wcx = fx + ((W / 2 - fx) / lvl) * (1 - ctt)
|
|
5946
|
+
wcy = fy + ((H / 2 - fy) / lvl) * (1 - ctt)
|
|
5947
|
+
c.translate(W / 2, H / 2); c.scale(lvl, lvl); c.translate(-wcx, -wcy)
|
|
5948
|
+
} else {
|
|
5949
|
+
c.translate(fx, fy); c.scale(lvl, lvl); c.translate(-fx, -fy)
|
|
5950
|
+
}
|
|
5825
5951
|
ceZs = lvl
|
|
5826
5952
|
}
|
|
5827
5953
|
// The card's shadows are cast by a body drawn OFF-CANVAS and brought back
|
|
@@ -5963,6 +6089,34 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
5963
6089
|
// cursor coordinate space + drawn radius (shared by click effects + the dot)
|
|
5964
6090
|
var space = (actM ? actM.cursorSpace : d.cursorSpace) || { w: vw, h: vh }
|
|
5965
6091
|
var curSize = ((d.cursorStyle && d.cursorStyle.size) || 24) * s2 * 0.5
|
|
6092
|
+
// The pointer's shape: an OS-style arrow (tip on the recorded point, a
|
|
6093
|
+
// black body with a white edge, the shape of the pointer on the viewer's
|
|
6094
|
+
// own screen) or the white dot every earlier take was cut with. One
|
|
6095
|
+
// painter for both draw sites (in the card, and the ghost across a
|
|
6096
|
+
// boundary); r is the dot's radius, so an arrow stands 2r tall.
|
|
6097
|
+
var curArrow = !!(d.cursorStyle && d.cursorStyle.style === 'arrow')
|
|
6098
|
+
function drawCur(cc, x, y, r, a, lw) {
|
|
6099
|
+
cc.save()
|
|
6100
|
+
if (curArrow) {
|
|
6101
|
+
var h = 2 * r
|
|
6102
|
+
var P = [[0, 0], [0, 0.72], [0.19, 0.55], [0.31, 0.83], [0.43, 0.78], [0.31, 0.5], [0.55, 0.5]]
|
|
6103
|
+
cc.beginPath()
|
|
6104
|
+
cc.moveTo(x, y)
|
|
6105
|
+
for (var pi = 1; pi < P.length; pi++) cc.lineTo(x + P[pi][0] * h, y + P[pi][1] * h)
|
|
6106
|
+
cc.closePath()
|
|
6107
|
+
cc.lineJoin = 'round'
|
|
6108
|
+
cc.fillStyle = 'rgba(0,0,0,' + (0.92 * a) + ')'
|
|
6109
|
+
cc.strokeStyle = 'rgba(255,255,255,' + (0.95 * a) + ')'
|
|
6110
|
+
cc.lineWidth = Math.max(1, lw * 0.75)
|
|
6111
|
+
cc.fill(); cc.stroke()
|
|
6112
|
+
} else {
|
|
6113
|
+
cc.fillStyle = 'rgba(255,255,255,' + (0.95 * a) + ')'
|
|
6114
|
+
cc.strokeStyle = 'rgba(0,0,0,' + (0.4 * a) + ')'
|
|
6115
|
+
cc.lineWidth = lw
|
|
6116
|
+
cc.beginPath(); cc.arc(x, y, r, 0, Math.PI * 2); cc.fill(); cc.stroke()
|
|
6117
|
+
}
|
|
6118
|
+
cc.restore()
|
|
6119
|
+
}
|
|
5966
6120
|
|
|
5967
6121
|
// click effects \u2014 pure f(t): d.clicks are
|
|
5968
6122
|
// OUTPUT-anchored records baked at lowering (sorted by ot; re-baked on every
|
|
@@ -6136,16 +6290,14 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
6136
6290
|
// moving: each pass leaves its point, mapped out of the zoom.
|
|
6137
6291
|
if (trOut) {
|
|
6138
6292
|
var tqX = ax, tqY = ay
|
|
6139
|
-
if (lvl > 1.001 && !d.zoomSuppressed) {
|
|
6293
|
+
if (lvl > 1.001 && !d.zoomSuppressed) {
|
|
6294
|
+
if (camStage) { tqX = W / 2 + (ax - wcx) * lvl; tqY = H / 2 + (ay - wcy) * lvl }
|
|
6295
|
+
else { tqX = fx + (ax - fx) * lvl; tqY = fy + (ay - fy) * lvl }
|
|
6296
|
+
}
|
|
6140
6297
|
if (tpG) trQa = [tqX, tqY]
|
|
6141
6298
|
else { trQb = [tqX, tqY]; trQs = curSize * ckPress; trQw = s2 }
|
|
6142
6299
|
} else if (cuA > 0.01) {
|
|
6143
|
-
c
|
|
6144
|
-
c.fillStyle = 'rgba(255,255,255,' + (0.95 * cuA) + ')'
|
|
6145
|
-
c.strokeStyle = 'rgba(0,0,0,' + (0.4 * cuA) + ')'
|
|
6146
|
-
c.lineWidth = 2 * s2
|
|
6147
|
-
c.beginPath(); c.arc(ax, ay, curSize * ckPress, 0, Math.PI * 2); c.fill(); c.stroke()
|
|
6148
|
-
c.restore()
|
|
6300
|
+
drawCur(c, ax, ay, curSize * ckPress, cuA, 2 * s2)
|
|
6149
6301
|
}
|
|
6150
6302
|
}
|
|
6151
6303
|
if (fitCover) c.restore()
|
|
@@ -6230,12 +6382,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
6230
6382
|
var tqBx = (tqB[0] - W / 2) * trSc + W / 2 + trOx * W, tqBy = (tqB[1] - H / 2) * trSc + H / 2 - trOy * H
|
|
6231
6383
|
var tqX2 = tqAx + (tqBx - tqAx) * tqP, tqY2 = tqAy + (tqBy - tqAy) * tqP
|
|
6232
6384
|
var tqR = trQs || (((d.cursorStyle && d.cursorStyle.size) || 24) * s * 0.5)
|
|
6233
|
-
ovC
|
|
6234
|
-
ovC.fillStyle = 'rgba(255,255,255,0.95)'
|
|
6235
|
-
ovC.strokeStyle = 'rgba(0,0,0,0.4)'
|
|
6236
|
-
ovC.lineWidth = 2 * (trQw || s)
|
|
6237
|
-
ovC.beginPath(); ovC.arc(tqX2, tqY2, tqR, 0, Math.PI * 2); ovC.fill(); ovC.stroke()
|
|
6238
|
-
ovC.restore()
|
|
6385
|
+
drawCur(ovC, tqX2, tqY2, tqR, 1, 2 * (trQw || s))
|
|
6239
6386
|
}
|
|
6240
6387
|
ov.sig = ovSig
|
|
6241
6388
|
ov.active = camActiveNow || !!trOut
|
|
@@ -6574,6 +6721,7 @@ function lowerToComposition(input) {
|
|
|
6574
6721
|
const layout = docCardLayout(doc);
|
|
6575
6722
|
const meta = doc.source.meta;
|
|
6576
6723
|
const zoomStyle = resolveZoomStyle(doc.zoomStyle, doc.zoomParams);
|
|
6724
|
+
const camera = cameraModel(doc.frame);
|
|
6577
6725
|
const zoomSpans = restSpansThroughTransitions(
|
|
6578
6726
|
doc.zoom,
|
|
6579
6727
|
rated,
|
|
@@ -6589,13 +6737,17 @@ function lowerToComposition(input) {
|
|
|
6589
6737
|
{
|
|
6590
6738
|
safeRatio: zoomStyle.followSafeRatio,
|
|
6591
6739
|
recenter: zoomStyle.followRecenter,
|
|
6592
|
-
lookahead: zoomStyle.followLookahead
|
|
6740
|
+
lookahead: zoomStyle.followLookahead,
|
|
6741
|
+
camera
|
|
6593
6742
|
}
|
|
6594
6743
|
);
|
|
6595
6744
|
if (f.entry)
|
|
6596
6745
|
return { ...z, cx: f.entry.cx, cy: f.entry.cy, followEvents: f.events };
|
|
6597
6746
|
}
|
|
6598
|
-
return {
|
|
6747
|
+
return {
|
|
6748
|
+
...z,
|
|
6749
|
+
...clampFocus(z.cx, z.cy, clampZoomLevel(z.level), layout, camera)
|
|
6750
|
+
};
|
|
6599
6751
|
});
|
|
6600
6752
|
const data = {
|
|
6601
6753
|
videoSrc: doc.source.videoKey,
|
|
@@ -6962,14 +7114,15 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
6962
7114
|
rect: null
|
|
6963
7115
|
});
|
|
6964
7116
|
if (track.length) {
|
|
6965
|
-
const { sessions, clusters } = groupTrack(track, {
|
|
7117
|
+
const { sessions, clusters, surfaces } = groupTrack(track, {
|
|
6966
7118
|
width,
|
|
6967
7119
|
height,
|
|
6968
7120
|
clusterGap: style.clusterGap,
|
|
6969
7121
|
typingGap: style.typingGap,
|
|
6970
|
-
typingZoom: style.typingZoom
|
|
7122
|
+
typingZoom: style.typingZoom,
|
|
7123
|
+
targetFill: style.targetFill
|
|
6971
7124
|
});
|
|
6972
|
-
|
|
7125
|
+
const pressed = (c, surface) => {
|
|
6973
7126
|
const f = clusterFocus(c, width, height);
|
|
6974
7127
|
drafts.push({
|
|
6975
7128
|
kind: "click",
|
|
@@ -6980,9 +7133,12 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
6980
7133
|
at: c[0].t,
|
|
6981
7134
|
focus: { cx: f.cx, cy: f.cy },
|
|
6982
7135
|
rect: f.rect,
|
|
6983
|
-
clicks: c.length
|
|
7136
|
+
clicks: c.length,
|
|
7137
|
+
...surface ? { surface: true } : {}
|
|
6984
7138
|
});
|
|
6985
|
-
}
|
|
7139
|
+
};
|
|
7140
|
+
for (const c of clusters) pressed(c, false);
|
|
7141
|
+
for (const c of surfaces) pressed(c, true);
|
|
6986
7142
|
for (const s of sessions) {
|
|
6987
7143
|
const f = clusterFocus(s.events, width, height);
|
|
6988
7144
|
drafts.push({
|
|
@@ -7102,6 +7258,7 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
7102
7258
|
h: round8(d.rect.h)
|
|
7103
7259
|
} : null,
|
|
7104
7260
|
...d.clicks !== void 0 ? { clicks: d.clicks } : {},
|
|
7261
|
+
...d.surface ? { surface: true } : {},
|
|
7105
7262
|
...d.pings !== void 0 ? { pings: d.pings } : {},
|
|
7106
7263
|
activity: activityOf(opts.bins, d),
|
|
7107
7264
|
proposed,
|
|
@@ -7146,15 +7303,14 @@ function sceneChanges(bins, opts = {}) {
|
|
|
7146
7303
|
}
|
|
7147
7304
|
|
|
7148
7305
|
// src/digest/framing.ts
|
|
7149
|
-
function zoomWindow(span, layout) {
|
|
7306
|
+
function zoomWindow(span, layout, camera = "card") {
|
|
7150
7307
|
const L = Math.max(1, span.level);
|
|
7151
|
-
const { cx, cy } = clampFocus(span.cx, span.cy, L, layout);
|
|
7152
|
-
const
|
|
7153
|
-
const
|
|
7154
|
-
const
|
|
7155
|
-
const
|
|
7156
|
-
const
|
|
7157
|
-
const py1 = fy + (layout.H - fy) / L;
|
|
7308
|
+
const { cx, cy } = clampFocus(span.cx, span.cy, L, layout, camera);
|
|
7309
|
+
const v = zoomViewport(L, cx, cy, layout, camera);
|
|
7310
|
+
const px0 = v.x * layout.W;
|
|
7311
|
+
const px1 = (v.x + v.w) * layout.W;
|
|
7312
|
+
const py0 = v.y * layout.H;
|
|
7313
|
+
const py1 = (v.y + v.h) * layout.H;
|
|
7158
7314
|
return {
|
|
7159
7315
|
x0: (px0 - layout.dx) / layout.dw,
|
|
7160
7316
|
x1: (px1 - layout.dx) / layout.dw,
|
|
@@ -7162,9 +7318,9 @@ function zoomWindow(span, layout) {
|
|
|
7162
7318
|
y1: (py1 - layout.dy) / layout.dh
|
|
7163
7319
|
};
|
|
7164
7320
|
}
|
|
7165
|
-
function zoomCoversRect(span, rect, layout, tol = 0.02) {
|
|
7321
|
+
function zoomCoversRect(span, rect, layout, tol = 0.02, camera = "card") {
|
|
7166
7322
|
if (span.level <= 1.001) return true;
|
|
7167
|
-
const w = zoomWindow(span, layout);
|
|
7323
|
+
const w = zoomWindow(span, layout, camera);
|
|
7168
7324
|
return rect.x >= w.x0 - tol && rect.x + rect.w <= w.x1 + tol && rect.y >= w.y0 - tol && rect.y + rect.h <= w.y1 + tol;
|
|
7169
7325
|
}
|
|
7170
7326
|
|
|
@@ -9782,6 +9938,8 @@ export {
|
|
|
9782
9938
|
AUDIO_PLAN_STEP,
|
|
9783
9939
|
BACKGROUND_Z,
|
|
9784
9940
|
BASE_FRAME_STYLE,
|
|
9941
|
+
CAMERA_CENTRE_RAMP,
|
|
9942
|
+
CAMERA_COVER_MIN,
|
|
9785
9943
|
CAMERA_FAR,
|
|
9786
9944
|
CAMERA_NEAR,
|
|
9787
9945
|
CAM_CHAIN_GAP,
|
|
@@ -9948,6 +10106,8 @@ export {
|
|
|
9948
10106
|
camRectFromPose,
|
|
9949
10107
|
camRestPose,
|
|
9950
10108
|
camTrackFromDoc,
|
|
10109
|
+
cameraCentring,
|
|
10110
|
+
cameraModel,
|
|
9951
10111
|
cardEnter,
|
|
9952
10112
|
cardExit,
|
|
9953
10113
|
cardFields,
|
|
@@ -10002,6 +10162,7 @@ export {
|
|
|
10002
10162
|
exportSizeFor,
|
|
10003
10163
|
extractClicks,
|
|
10004
10164
|
focusBounds,
|
|
10165
|
+
focusForViewportCentre,
|
|
10005
10166
|
followFocusEvents,
|
|
10006
10167
|
frameGeometry,
|
|
10007
10168
|
freezeLane,
|
|
@@ -10129,6 +10290,8 @@ export {
|
|
|
10129
10290
|
zoomLane,
|
|
10130
10291
|
zoomSpanForRange,
|
|
10131
10292
|
zoomTrackFromDoc,
|
|
10293
|
+
zoomView,
|
|
10294
|
+
zoomViewport,
|
|
10132
10295
|
zoomWindow
|
|
10133
10296
|
};
|
|
10134
10297
|
//# sourceMappingURL=index.js.map
|