@pixerate/flair 0.2.0 → 0.3.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/CHANGELOG.md +10 -0
- package/README.md +44 -8
- package/dist/catalog/index.d.ts +5 -4
- package/dist/catalog/index.js +12 -4
- package/dist/{chunk-JWZCPMFG.js → chunk-D7NLHC7K.js} +117 -674
- package/dist/chunk-D7NLHC7K.js.map +1 -0
- package/dist/{chunk-3FAUZOGL.js → chunk-FIJOMGPG.js} +2 -2
- package/dist/{chunk-UQQNZSYL.js → chunk-GTIAOYOK.js} +211 -7
- package/dist/chunk-GTIAOYOK.js.map +1 -0
- package/dist/{chunk-MWVTQFV5.js → chunk-IWKM5FJK.js} +410 -59
- package/dist/chunk-IWKM5FJK.js.map +1 -0
- package/dist/{chunk-HKXOHRCD.js → chunk-VA74HBPK.js} +2 -2
- package/dist/chunk-WAMA45G3.js +998 -0
- package/dist/chunk-WAMA45G3.js.map +1 -0
- package/dist/{chunk-J55UBNAT.js → chunk-WC3VADZC.js} +42 -36
- package/dist/chunk-WC3VADZC.js.map +1 -0
- package/dist/cli.js +12 -11
- package/dist/cli.js.map +1 -1
- package/dist/{controls-DdXMbovi.d.ts → controls-Syr6drzO.d.ts} +4 -3
- package/dist/editor/index.d.ts +12 -8
- package/dist/editor/index.js +26 -16
- package/dist/editor/index.js.map +1 -1
- package/dist/{export-FEBPEEO7.js → export-DH2HMACO.js} +4 -3
- package/dist/index.d.ts +193 -17
- package/dist/index.js +69 -33
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +2 -2
- package/dist/recorder-KEX3U35D.js +10 -0
- package/dist/{renderer-Oyynz-fs.d.ts → renderer-mzvhKzKa.d.ts} +1 -1
- package/dist/types-CsFUZZIv.d.ts +102 -0
- package/package.json +1 -1
- package/dist/chunk-36DTNSHU.js +0 -160
- package/dist/chunk-36DTNSHU.js.map +0 -1
- package/dist/chunk-J55UBNAT.js.map +0 -1
- package/dist/chunk-JWZCPMFG.js.map +0 -1
- package/dist/chunk-MWVTQFV5.js.map +0 -1
- package/dist/chunk-UQQNZSYL.js.map +0 -1
- package/dist/recorder-WJTWMEQN.js +0 -10
- package/dist/types-CkKkavvB.d.ts +0 -71
- /package/dist/{chunk-3FAUZOGL.js.map → chunk-FIJOMGPG.js.map} +0 -0
- /package/dist/{chunk-HKXOHRCD.js.map → chunk-VA74HBPK.js.map} +0 -0
- /package/dist/{export-FEBPEEO7.js.map → export-DH2HMACO.js.map} +0 -0
- /package/dist/{recorder-WJTWMEQN.js.map → recorder-KEX3U35D.js.map} +0 -0
|
@@ -8,31 +8,21 @@ import {
|
|
|
8
8
|
drawFrame,
|
|
9
9
|
resetGlCaches,
|
|
10
10
|
wrapSource
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import {
|
|
13
|
-
ManualClock
|
|
14
|
-
} from "./chunk-WMZQCUFJ.js";
|
|
11
|
+
} from "./chunk-VA74HBPK.js";
|
|
15
12
|
import {
|
|
16
13
|
CAMERA_MS,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
isDeleteCommand,
|
|
25
|
-
isElementCommand,
|
|
26
|
-
isUpdateCommand,
|
|
27
|
-
overlapArea,
|
|
28
|
-
viewAt,
|
|
29
|
-
visibleRect
|
|
30
|
-
} from "./chunk-36DTNSHU.js";
|
|
14
|
+
CAMERA_STEP_MS,
|
|
15
|
+
EFFECT_TYPES,
|
|
16
|
+
MAX_TILT,
|
|
17
|
+
cameraAt,
|
|
18
|
+
cameraTrack,
|
|
19
|
+
sceneStage
|
|
20
|
+
} from "./chunk-WAMA45G3.js";
|
|
31
21
|
import {
|
|
32
22
|
controlValue,
|
|
33
23
|
imageUrls,
|
|
34
24
|
parseControls
|
|
35
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-WC3VADZC.js";
|
|
36
26
|
|
|
37
27
|
// src/shader/renderer.ts
|
|
38
28
|
var ShaderRenderer = class {
|
|
@@ -1039,228 +1029,6 @@ function getGlslBlendFunction(mode) {
|
|
|
1039
1029
|
}
|
|
1040
1030
|
}
|
|
1041
1031
|
|
|
1042
|
-
// src/time/noise.ts
|
|
1043
|
-
var GOLDEN_GAMMA = 0x9e3779b97f4a7c15n;
|
|
1044
|
-
var MULT1 = 0xbf58476d1ce4e5b9n;
|
|
1045
|
-
var MULT2 = 0x94d049bb133111ebn;
|
|
1046
|
-
var MASK64 = 0xffffffffffffffffn;
|
|
1047
|
-
var BASE_MIX = 0x2545f4914f6cdd1dn;
|
|
1048
|
-
var POW2_53 = 9007199254740992;
|
|
1049
|
-
function hash64(x) {
|
|
1050
|
-
let v = x + GOLDEN_GAMMA & MASK64;
|
|
1051
|
-
v = (v ^ v >> 30n) * MULT1 & MASK64;
|
|
1052
|
-
v = (v ^ v >> 27n) * MULT2 & MASK64;
|
|
1053
|
-
return (v ^ v >> 31n) & MASK64;
|
|
1054
|
-
}
|
|
1055
|
-
function mixSeeds(parts) {
|
|
1056
|
-
let h = BASE_MIX;
|
|
1057
|
-
for (const p of parts) {
|
|
1058
|
-
const val = typeof p === "bigint" ? p : BigInt(Math.floor(p));
|
|
1059
|
-
h = hash64(h ^ hash64(val & MASK64));
|
|
1060
|
-
}
|
|
1061
|
-
return h;
|
|
1062
|
-
}
|
|
1063
|
-
function unitFloat(seed) {
|
|
1064
|
-
const s = typeof seed === "bigint" ? seed : BigInt(Math.floor(seed));
|
|
1065
|
-
const hashed = hash64(s);
|
|
1066
|
-
const shifted = Number(hashed >> 11n);
|
|
1067
|
-
return shifted / POW2_53;
|
|
1068
|
-
}
|
|
1069
|
-
function quinticFade(t) {
|
|
1070
|
-
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
1071
|
-
}
|
|
1072
|
-
function lattice(seed, i) {
|
|
1073
|
-
const mixed = seed ^ hash64(i & MASK64);
|
|
1074
|
-
return unitFloat(mixed) * 2 - 1;
|
|
1075
|
-
}
|
|
1076
|
-
function noise1d(seed, x) {
|
|
1077
|
-
if (!Number.isFinite(x)) return 0;
|
|
1078
|
-
const s = typeof seed === "bigint" ? seed : BigInt(Math.floor(seed));
|
|
1079
|
-
const floorX = Math.floor(x);
|
|
1080
|
-
const u = quinticFade(x - floorX);
|
|
1081
|
-
const i = BigInt(Math.floor(floorX));
|
|
1082
|
-
const a = lattice(s, i);
|
|
1083
|
-
const b = lattice(s, i + 1n);
|
|
1084
|
-
return a + (b - a) * u;
|
|
1085
|
-
}
|
|
1086
|
-
function noise3d(x, y, z, seed = 0) {
|
|
1087
|
-
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
|
|
1088
|
-
return 0;
|
|
1089
|
-
}
|
|
1090
|
-
const s = typeof seed === "bigint" ? seed : BigInt(Math.floor(seed));
|
|
1091
|
-
const fx = Math.floor(x);
|
|
1092
|
-
const fy = Math.floor(y);
|
|
1093
|
-
const fz = Math.floor(z);
|
|
1094
|
-
const u = quinticFade(x - fx);
|
|
1095
|
-
const v = quinticFade(y - fy);
|
|
1096
|
-
const w = quinticFade(z - fz);
|
|
1097
|
-
const ix = BigInt(fx);
|
|
1098
|
-
const iy = BigInt(fy);
|
|
1099
|
-
const iz = BigInt(fz);
|
|
1100
|
-
const g = (dx, dy, dz) => {
|
|
1101
|
-
const combined = mixSeeds([s, ix + dx, iy + dy, iz + dz]);
|
|
1102
|
-
return unitFloat(combined) * 2 - 1;
|
|
1103
|
-
};
|
|
1104
|
-
const lerp = (a, b, t) => a + (b - a) * t;
|
|
1105
|
-
const x00 = lerp(g(0n, 0n, 0n), g(1n, 0n, 0n), u);
|
|
1106
|
-
const x10 = lerp(g(0n, 1n, 0n), g(1n, 1n, 0n), u);
|
|
1107
|
-
const x01 = lerp(g(0n, 0n, 1n), g(1n, 0n, 1n), u);
|
|
1108
|
-
const x11 = lerp(g(0n, 1n, 1n), g(1n, 1n, 1n), u);
|
|
1109
|
-
const y0 = lerp(x00, x10, v);
|
|
1110
|
-
const y1 = lerp(x01, x11, v);
|
|
1111
|
-
return lerp(y0, y1, w);
|
|
1112
|
-
}
|
|
1113
|
-
function fractalWiggle(freq, amp, octaves = 3, ampMult = 0.5, t = 0, seed = 42, dimension = 0) {
|
|
1114
|
-
const oct = Math.max(1, Math.min(16, Math.floor(octaves)));
|
|
1115
|
-
let currentAmp = amp;
|
|
1116
|
-
let currentFreq = freq;
|
|
1117
|
-
let sum = 0;
|
|
1118
|
-
const baseSeed = BigInt(seed);
|
|
1119
|
-
for (let k = 0; k < oct; k++) {
|
|
1120
|
-
const s = mixSeeds([baseSeed, BigInt(dimension), BigInt(k)]);
|
|
1121
|
-
const phaseOffset = unitFloat(s) * 1e3;
|
|
1122
|
-
sum += currentAmp * noise1d(s, currentFreq * t + phaseOffset);
|
|
1123
|
-
currentAmp *= ampMult;
|
|
1124
|
-
currentFreq *= 2;
|
|
1125
|
-
}
|
|
1126
|
-
return sum;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
// src/scene/effects.ts
|
|
1130
|
-
var EFFECT_TYPES = [
|
|
1131
|
-
"gradient",
|
|
1132
|
-
"flow",
|
|
1133
|
-
"glow",
|
|
1134
|
-
"spotlight",
|
|
1135
|
-
"shimmer",
|
|
1136
|
-
"ripple",
|
|
1137
|
-
"timer",
|
|
1138
|
-
"counter",
|
|
1139
|
-
"highlight",
|
|
1140
|
-
"blur",
|
|
1141
|
-
"burst",
|
|
1142
|
-
"media"
|
|
1143
|
-
];
|
|
1144
|
-
var EFFECT_FADE_MS = 300;
|
|
1145
|
-
function effectGone(running, now) {
|
|
1146
|
-
if (running.until !== void 0) {
|
|
1147
|
-
return now >= running.until + EFFECT_FADE_MS;
|
|
1148
|
-
}
|
|
1149
|
-
if (running.effect.duration !== void 0) {
|
|
1150
|
-
return now >= running.from + running.effect.duration + EFFECT_FADE_MS;
|
|
1151
|
-
}
|
|
1152
|
-
return false;
|
|
1153
|
-
}
|
|
1154
|
-
function effectOpacity(running, now) {
|
|
1155
|
-
if (now < running.from) return 0;
|
|
1156
|
-
const inFade = Math.min(1, (now - running.from) / 150);
|
|
1157
|
-
if (running.until !== void 0) {
|
|
1158
|
-
const out = Math.max(0, 1 - (now - running.until) / EFFECT_FADE_MS);
|
|
1159
|
-
return inFade * out;
|
|
1160
|
-
}
|
|
1161
|
-
if (running.effect.duration !== void 0) {
|
|
1162
|
-
const remaining = running.from + running.effect.duration - now;
|
|
1163
|
-
if (remaining < 0) {
|
|
1164
|
-
const out = Math.max(0, 1 + remaining / EFFECT_FADE_MS);
|
|
1165
|
-
return inFade * out;
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
return inFade;
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
// src/scene/check.ts
|
|
1172
|
-
var NO_CHECK = { framing: [], overlaps: [], edges: [], underTitle: [] };
|
|
1173
|
-
var SMALL_TEXT_PX = 24;
|
|
1174
|
-
function titleBox(title, stage) {
|
|
1175
|
-
const width = Math.min(stage.width, title.length * 20 + 64);
|
|
1176
|
-
return { x: (stage.width - width) / 2, y: 24, width, height: 60 };
|
|
1177
|
-
}
|
|
1178
|
-
function touchedIds(shot) {
|
|
1179
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1180
|
-
for (const cmd of shot.run ?? []) {
|
|
1181
|
-
if (isElementCommand(cmd)) {
|
|
1182
|
-
ids.add(cmd.element.id);
|
|
1183
|
-
} else if ("id" in cmd && typeof cmd.id === "string") {
|
|
1184
|
-
ids.add(cmd.id);
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
return [...ids];
|
|
1188
|
-
}
|
|
1189
|
-
var IN_FRAME = 0.95;
|
|
1190
|
-
function checkShot(shot, elements, view, stage, title) {
|
|
1191
|
-
const frameRect = visibleRect(view, stage);
|
|
1192
|
-
const touched = new Set(touchedIds(shot));
|
|
1193
|
-
const framing = [];
|
|
1194
|
-
const edges = [];
|
|
1195
|
-
const overlaps = [];
|
|
1196
|
-
const activeElements = elements.filter((e) => !e.isDeleted && (e.opacity === void 0 || e.opacity > 0));
|
|
1197
|
-
for (const el of activeElements) {
|
|
1198
|
-
const box = boundsOf(el);
|
|
1199
|
-
const overlap = overlapArea(box, frameRect);
|
|
1200
|
-
const boxArea = areaOf(box);
|
|
1201
|
-
if (touched.has(el.id)) {
|
|
1202
|
-
if (overlap === 0) {
|
|
1203
|
-
framing.push({ id: el.id, how: "outside" });
|
|
1204
|
-
} else if (boxArea > 0 && overlap / boxArea < IN_FRAME) {
|
|
1205
|
-
framing.push({ id: el.id, how: "cut" });
|
|
1206
|
-
}
|
|
1207
|
-
} else if (overlap > 0 && boxArea > 0 && overlap / boxArea < IN_FRAME) {
|
|
1208
|
-
edges.push(el.id);
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
const touchedList = activeElements.filter((e) => touched.has(e.id));
|
|
1212
|
-
for (let i = 0; i < touchedList.length; i++) {
|
|
1213
|
-
for (let j = i + 1; j < touchedList.length; j++) {
|
|
1214
|
-
const a = touchedList[i];
|
|
1215
|
-
const b = touchedList[j];
|
|
1216
|
-
const aBox = boundsOf(a);
|
|
1217
|
-
const bBox = boundsOf(b);
|
|
1218
|
-
const ol = overlapArea(aBox, bBox);
|
|
1219
|
-
const minArea = Math.min(areaOf(aBox), areaOf(bBox));
|
|
1220
|
-
if (minArea > 0 && ol / minArea > 0.3) {
|
|
1221
|
-
overlaps.push([a.id, b.id]);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
const smallTexts = [];
|
|
1226
|
-
let smallestText;
|
|
1227
|
-
for (const el of activeElements) {
|
|
1228
|
-
if (el.text && el.fontSize) {
|
|
1229
|
-
const px = Math.round(el.fontSize * view.zoom);
|
|
1230
|
-
const item = { id: el.id, text: el.text, px };
|
|
1231
|
-
if (!smallestText || px < smallestText.px) {
|
|
1232
|
-
smallestText = item;
|
|
1233
|
-
}
|
|
1234
|
-
if (px < SMALL_TEXT_PX) {
|
|
1235
|
-
smallTexts.push(item);
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
const underTitle = [];
|
|
1240
|
-
if (title) {
|
|
1241
|
-
const tBox = titleBox(title, stage);
|
|
1242
|
-
const sceneTBox = {
|
|
1243
|
-
x: tBox.x / view.zoom - view.scrollX,
|
|
1244
|
-
y: tBox.y / view.zoom - view.scrollY,
|
|
1245
|
-
width: tBox.width / view.zoom,
|
|
1246
|
-
height: tBox.height / view.zoom
|
|
1247
|
-
};
|
|
1248
|
-
for (const el of activeElements) {
|
|
1249
|
-
if (overlapArea(boundsOf(el), sceneTBox) > 0) {
|
|
1250
|
-
underTitle.push(el.id);
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
return {
|
|
1255
|
-
framing,
|
|
1256
|
-
overlaps,
|
|
1257
|
-
smallestText,
|
|
1258
|
-
smallTexts: smallTexts.length ? smallTexts : void 0,
|
|
1259
|
-
edges,
|
|
1260
|
-
underTitle
|
|
1261
|
-
};
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
1032
|
// src/scene/validate.ts
|
|
1265
1033
|
var LONG_CAPTION = 120;
|
|
1266
1034
|
var LONG_TITLE = 60;
|
|
@@ -1297,9 +1065,22 @@ function validateScene(scene) {
|
|
|
1297
1065
|
if (!Array.isArray(shot.camera.frame) || shot.camera.frame.length === 0) {
|
|
1298
1066
|
say(shotNum, "Dynamic camera framing requires a non-empty frame array of element IDs.");
|
|
1299
1067
|
}
|
|
1300
|
-
} else if (shot.camera.width <= 0 || shot.camera.height <= 0) {
|
|
1068
|
+
} else if ("width" in shot.camera && (shot.camera.width <= 0 || shot.camera.height <= 0)) {
|
|
1301
1069
|
say(shotNum, "Camera rectangle width and height must be positive.");
|
|
1302
1070
|
}
|
|
1071
|
+
const cam = shot.camera;
|
|
1072
|
+
const angle = (name, max) => {
|
|
1073
|
+
const v = cam[name];
|
|
1074
|
+
if (v !== void 0 && !(typeof v === "number" && Math.abs(v) <= max))
|
|
1075
|
+
say(shotNum, `Camera ${name} is degrees between -${max} and ${max}.`);
|
|
1076
|
+
};
|
|
1077
|
+
angle("tilt", MAX_TILT);
|
|
1078
|
+
angle("turn", MAX_TILT);
|
|
1079
|
+
angle("roll", 180);
|
|
1080
|
+
if (cam.perspective !== void 0 && !(typeof cam.perspective === "number" && cam.perspective >= 0.3))
|
|
1081
|
+
say(shotNum, "Camera perspective is the lens distance as a multiple of the stage width, 0.3 or more (1.2 by default).");
|
|
1082
|
+
if (cam.motionBlur !== void 0 && !(typeof cam.motionBlur === "number" && cam.motionBlur >= 0 && cam.motionBlur <= 3))
|
|
1083
|
+
say(shotNum, "Camera motionBlur is 0 (none) to 3 (a whip); 1 is natural.");
|
|
1303
1084
|
}
|
|
1304
1085
|
if (shot.hold !== void 0 && shot.hold < 0) {
|
|
1305
1086
|
say(shotNum, "Shot hold cannot be negative.");
|
|
@@ -1347,381 +1128,19 @@ function validateScene(scene) {
|
|
|
1347
1128
|
return issues;
|
|
1348
1129
|
}
|
|
1349
1130
|
|
|
1350
|
-
// src/scene/
|
|
1351
|
-
function
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
if (scene.target) {
|
|
1358
|
-
const [minS, maxS] = scene.target;
|
|
1359
|
-
const durS = totalMs / 1e3;
|
|
1360
|
-
if (durS < minS) {
|
|
1361
|
-
warnings.push(`Scene is ${totalSec}s, shorter than target minimum (${minS}s).`);
|
|
1362
|
-
} else if (durS > maxS) {
|
|
1363
|
-
warnings.push(`Scene is ${totalSec}s, longer than target maximum (${maxS}s).`);
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
for (const log of logs) {
|
|
1367
|
-
const shotPrefix = `Shot ${log.index}:`;
|
|
1368
|
-
if (log.failed.length > 0) {
|
|
1369
|
-
warnings.push(`${shotPrefix} ${log.failed.length} command(s) failed: ${log.failed.join(", ")}`);
|
|
1370
|
-
}
|
|
1371
|
-
for (const f of log.framing) {
|
|
1372
|
-
warnings.push(`${shotPrefix} element "${f.id}" is ${f.how} of the camera frame.`);
|
|
1373
|
-
}
|
|
1374
|
-
for (const [a, b] of log.overlaps) {
|
|
1375
|
-
notes.push(`${shotPrefix} elements "${a}" and "${b}" overlap significantly.`);
|
|
1376
|
-
}
|
|
1377
|
-
if (log.smallTexts) {
|
|
1378
|
-
for (const t of log.smallTexts) {
|
|
1379
|
-
if (t.px < minTextPx) {
|
|
1380
|
-
warnings.push(`${shotPrefix} text "${t.text}" is only ${t.px}px on screen (less than ${minTextPx}px minimum).`);
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
for (const id of log.underTitle) {
|
|
1385
|
-
warnings.push(`${shotPrefix} element "${id}" is partially covered by the title bar.`);
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
const lines = [
|
|
1389
|
-
`# Scene Preview Review: ${scene.title || scene.name}`,
|
|
1390
|
-
`**Total Duration:** ${totalSec}s (${logs.length} shots)`,
|
|
1391
|
-
""
|
|
1392
|
-
];
|
|
1393
|
-
if (warnings.length > 0) {
|
|
1394
|
-
lines.push("### \u26A0\uFE0F Warnings to Address:");
|
|
1395
|
-
for (const w of warnings) lines.push(`- ${w}`);
|
|
1396
|
-
lines.push("");
|
|
1397
|
-
} else {
|
|
1398
|
-
lines.push("\u2705 No critical layout or framing warnings.");
|
|
1399
|
-
lines.push("");
|
|
1400
|
-
}
|
|
1401
|
-
if (notes.length > 0) {
|
|
1402
|
-
lines.push("### \u2139\uFE0F Layout Notes:");
|
|
1403
|
-
for (const n of notes) lines.push(`- ${n}`);
|
|
1404
|
-
lines.push("");
|
|
1405
|
-
}
|
|
1406
|
-
lines.push("### Shot Breakdown");
|
|
1407
|
-
lines.push("| Shot | Start | End | Duration | Title / Caption |");
|
|
1408
|
-
lines.push("|:---:|:---:|:---:|:---:|:---|");
|
|
1409
|
-
for (const log of logs) {
|
|
1410
|
-
const dur = ((log.end - log.start) / 1e3).toFixed(1);
|
|
1411
|
-
const text = log.title || log.caption || "\u2014";
|
|
1412
|
-
lines.push(`| ${log.index} | ${(log.start / 1e3).toFixed(1)}s | ${(log.end / 1e3).toFixed(1)}s | ${dur}s | ${text.slice(0, 40)} |`);
|
|
1413
|
-
}
|
|
1414
|
-
return {
|
|
1415
|
-
totalMs,
|
|
1416
|
-
warnings,
|
|
1417
|
-
notes,
|
|
1418
|
-
markdown: lines.join("\n")
|
|
1419
|
-
};
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
// src/scene/behavior.ts
|
|
1423
|
-
function evaluateWriggle(t, freq, amount, octaves = 3, seed = 42) {
|
|
1424
|
-
return fractalWiggle(freq, amount, octaves, 0.5, t, seed, 0);
|
|
1425
|
-
}
|
|
1426
|
-
function evaluateBehavior(behavior, elapsedSec, durationSec, baseValue = 0) {
|
|
1427
|
-
const t = Math.max(0, elapsedSec);
|
|
1428
|
-
switch (behavior.type) {
|
|
1429
|
-
case "oscillate": {
|
|
1430
|
-
const freq = behavior.frequency;
|
|
1431
|
-
const phase = behavior.phase ?? 0;
|
|
1432
|
-
const angle = 2 * Math.PI * freq * t + phase;
|
|
1433
|
-
let waveVal = 0;
|
|
1434
|
-
switch (behavior.wave) {
|
|
1435
|
-
case "square":
|
|
1436
|
-
waveVal = Math.sin(angle) >= 0 ? 1 : -1;
|
|
1437
|
-
break;
|
|
1438
|
-
case "triangle":
|
|
1439
|
-
waveVal = 2 / Math.PI * Math.asin(Math.sin(angle));
|
|
1440
|
-
break;
|
|
1441
|
-
case "sawtooth": {
|
|
1442
|
-
const norm = angle / (2 * Math.PI) % 1;
|
|
1443
|
-
waveVal = 2 * (norm < 0 ? norm + 1 : norm) - 1;
|
|
1444
|
-
break;
|
|
1445
|
-
}
|
|
1446
|
-
case "sine":
|
|
1447
|
-
default:
|
|
1448
|
-
waveVal = Math.sin(angle);
|
|
1449
|
-
break;
|
|
1450
|
-
}
|
|
1451
|
-
const offset = waveVal * behavior.amplitude;
|
|
1452
|
-
return behavior.center !== void 0 ? behavior.center + offset : baseValue + offset;
|
|
1453
|
-
}
|
|
1454
|
-
case "ramp": {
|
|
1455
|
-
const from = behavior.from ?? baseValue;
|
|
1456
|
-
const to = behavior.to;
|
|
1457
|
-
const p = durationSec > 0 ? Math.min(1, Math.max(0, t / durationSec)) : 1;
|
|
1458
|
-
let eased = p;
|
|
1459
|
-
switch (behavior.ease) {
|
|
1460
|
-
case "easeIn":
|
|
1461
|
-
eased = p * p;
|
|
1462
|
-
break;
|
|
1463
|
-
case "easeOut":
|
|
1464
|
-
eased = p * (2 - p);
|
|
1465
|
-
break;
|
|
1466
|
-
case "easeInOut":
|
|
1467
|
-
eased = p < 0.5 ? 2 * p * p : -1 + (4 - 2 * p) * p;
|
|
1468
|
-
break;
|
|
1469
|
-
case "linear":
|
|
1470
|
-
default:
|
|
1471
|
-
eased = p;
|
|
1472
|
-
break;
|
|
1473
|
-
}
|
|
1474
|
-
return from + (to - from) * eased;
|
|
1475
|
-
}
|
|
1476
|
-
case "rate": {
|
|
1477
|
-
return baseValue + behavior.rate * t;
|
|
1478
|
-
}
|
|
1479
|
-
case "wriggle": {
|
|
1480
|
-
const wander = evaluateWriggle(
|
|
1481
|
-
t,
|
|
1482
|
-
behavior.frequency,
|
|
1483
|
-
behavior.amount,
|
|
1484
|
-
behavior.octaves ?? 3,
|
|
1485
|
-
behavior.seed ?? 42
|
|
1486
|
-
);
|
|
1487
|
-
return baseValue + wander;
|
|
1488
|
-
}
|
|
1489
|
-
case "overshoot": {
|
|
1490
|
-
const from = behavior.startValue ?? baseValue;
|
|
1491
|
-
const target = behavior.targetValue;
|
|
1492
|
-
const cycles = behavior.cycles ?? 3;
|
|
1493
|
-
const damping = behavior.damping ?? 4;
|
|
1494
|
-
if (durationSec <= 0) return target;
|
|
1495
|
-
const p = Math.min(1, t / durationSec);
|
|
1496
|
-
if (p >= 1) return target;
|
|
1497
|
-
const envelope = Math.exp(-damping * p);
|
|
1498
|
-
const oscillation = Math.cos(cycles * Math.PI * p);
|
|
1499
|
-
const progress = 1 - envelope * oscillation;
|
|
1500
|
-
return from + (target - from) * progress;
|
|
1501
|
-
}
|
|
1502
|
-
case "throw": {
|
|
1503
|
-
const v0 = behavior.initialVelocity;
|
|
1504
|
-
const k = Math.max(1e-3, behavior.friction ?? 2);
|
|
1505
|
-
const displacement = v0 / k * (1 - Math.exp(-k * t));
|
|
1506
|
-
return baseValue + displacement;
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
// src/scene/player.ts
|
|
1512
|
-
var MemorySceneBoard = class {
|
|
1513
|
-
view;
|
|
1514
|
-
elMap = /* @__PURE__ */ new Map();
|
|
1515
|
-
currentTitle = "";
|
|
1516
|
-
currentCaption = "";
|
|
1517
|
-
runningEffects = [];
|
|
1518
|
-
constructor(stage = DEFAULT_STAGE) {
|
|
1519
|
-
this.view = viewAt(stage.width / 2, stage.height / 2, 1, stage);
|
|
1520
|
-
}
|
|
1521
|
-
getView() {
|
|
1522
|
-
return this.view;
|
|
1523
|
-
}
|
|
1524
|
-
setView(view) {
|
|
1525
|
-
this.view = view;
|
|
1526
|
-
}
|
|
1527
|
-
elements() {
|
|
1528
|
-
return Array.from(this.elMap.values());
|
|
1529
|
-
}
|
|
1530
|
-
runCommand(cmd) {
|
|
1531
|
-
if (isElementCommand(cmd)) {
|
|
1532
|
-
this.elMap.set(cmd.element.id, { ...cmd.element });
|
|
1533
|
-
} else if (isUpdateCommand(cmd)) {
|
|
1534
|
-
const existing = this.elMap.get(cmd.id);
|
|
1535
|
-
if (existing) {
|
|
1536
|
-
this.elMap.set(cmd.id, { ...existing, ...cmd.props });
|
|
1537
|
-
}
|
|
1538
|
-
} else if (isDeleteCommand(cmd)) {
|
|
1539
|
-
this.elMap.delete(cmd.id);
|
|
1540
|
-
} else if (cmd.type === "clear") {
|
|
1541
|
-
this.elMap.clear();
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
setTitle(text) {
|
|
1545
|
-
this.currentTitle = text;
|
|
1546
|
-
}
|
|
1547
|
-
setCaption(text) {
|
|
1548
|
-
this.currentCaption = text;
|
|
1549
|
-
}
|
|
1550
|
-
setEffects(effects) {
|
|
1551
|
-
this.runningEffects = effects;
|
|
1552
|
-
}
|
|
1553
|
-
};
|
|
1554
|
-
async function playScene(scene, board, clock, options = {}) {
|
|
1555
|
-
const stage = {
|
|
1556
|
-
width: scene.stage?.width ?? DEFAULT_STAGE.width,
|
|
1557
|
-
height: scene.stage?.height ?? DEFAULT_STAGE.height,
|
|
1558
|
-
caption: scene.stage?.caption ?? DEFAULT_STAGE.caption
|
|
1559
|
-
};
|
|
1560
|
-
const shots = [];
|
|
1561
|
-
const begin = clock.now();
|
|
1562
|
-
let runningEffects = [];
|
|
1563
|
-
const endsAt = /* @__PURE__ */ new Map();
|
|
1564
|
-
const updateEffects = (next) => {
|
|
1565
|
-
runningEffects = next.filter((r) => !effectGone(r, clock.now()));
|
|
1566
|
-
board.setEffects?.(runningEffects);
|
|
1567
|
-
};
|
|
1568
|
-
for (const [idx, shot] of scene.shots.entries()) {
|
|
1569
|
-
const shotStart = clock.now() - begin;
|
|
1570
|
-
const failedCmds = [];
|
|
1571
|
-
const stopIds = new Set(shot.stopEffects ?? []);
|
|
1572
|
-
if (stopIds.size > 0 || runningEffects.some((r) => endsAt.get(r) === idx)) {
|
|
1573
|
-
const now = clock.now();
|
|
1574
|
-
updateEffects(
|
|
1575
|
-
runningEffects.map(
|
|
1576
|
-
(r) => r.until === void 0 && (stopIds.has(r.effect.id) || endsAt.get(r) === idx) ? { ...r, until: now } : r
|
|
1577
|
-
)
|
|
1578
|
-
);
|
|
1579
|
-
}
|
|
1580
|
-
if (shot.camera) {
|
|
1581
|
-
let targetRect;
|
|
1582
|
-
if ("frame" in shot.camera) {
|
|
1583
|
-
const frameIds = new Set(shot.camera.frame);
|
|
1584
|
-
const targetElements = board.elements().filter((el) => frameIds.has(el.id));
|
|
1585
|
-
if (targetElements.length === 0 && shot.run) {
|
|
1586
|
-
for (const cmd of shot.run) {
|
|
1587
|
-
if (isElementCommand(cmd) && frameIds.has(cmd.element.id)) {
|
|
1588
|
-
targetElements.push(cmd.element);
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
targetRect = calculateFramingRect(targetElements, stage, {
|
|
1593
|
-
padding: shot.camera.padding,
|
|
1594
|
-
minWidth: shot.camera.minWidth,
|
|
1595
|
-
minHeight: shot.camera.minHeight
|
|
1596
|
-
});
|
|
1597
|
-
} else {
|
|
1598
|
-
targetRect = shot.camera;
|
|
1599
|
-
}
|
|
1600
|
-
const targetView = frame(targetRect, stage);
|
|
1601
|
-
if (idx === 0) {
|
|
1602
|
-
board.setView(targetView);
|
|
1603
|
-
} else {
|
|
1604
|
-
const fromView = board.getView();
|
|
1605
|
-
const steps = 15;
|
|
1606
|
-
const stepMs = CAMERA_MS / steps;
|
|
1607
|
-
for (let s = 1; s <= steps; s++) {
|
|
1608
|
-
const t = easeInOutCubic(s / steps);
|
|
1609
|
-
board.setView(between(fromView, targetView, t, stage));
|
|
1610
|
-
await clock.sleep(stepMs);
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
if (shot.title !== void 0) {
|
|
1615
|
-
board.setTitle?.(shot.title, 1);
|
|
1616
|
-
}
|
|
1617
|
-
if (shot.caption !== void 0) {
|
|
1618
|
-
board.setCaption?.(shot.caption);
|
|
1619
|
-
}
|
|
1620
|
-
const startEffects = (shot.effects ?? []).filter((e) => e.at === "start");
|
|
1621
|
-
if (startEffects.length > 0) {
|
|
1622
|
-
const now = clock.now();
|
|
1623
|
-
const added = startEffects.map((effect) => ({ effect, from: now }));
|
|
1624
|
-
for (const r of added) if (r.effect.shots) endsAt.set(r, idx + r.effect.shots);
|
|
1625
|
-
updateEffects([...runningEffects, ...added]);
|
|
1626
|
-
}
|
|
1627
|
-
for (const cmd of shot.run ?? []) {
|
|
1628
|
-
try {
|
|
1629
|
-
await board.runCommand(cmd);
|
|
1630
|
-
} catch (err) {
|
|
1631
|
-
failedCmds.push(err.message || String(err));
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
const drawnEffects = (shot.effects ?? []).filter((e) => e.at !== "start");
|
|
1635
|
-
if (drawnEffects.length > 0) {
|
|
1636
|
-
const now = clock.now();
|
|
1637
|
-
const added = drawnEffects.map((effect) => ({ effect, from: now }));
|
|
1638
|
-
for (const r of added) if (r.effect.shots) endsAt.set(r, idx + r.effect.shots);
|
|
1639
|
-
updateEffects([...runningEffects, ...added]);
|
|
1640
|
-
}
|
|
1641
|
-
const drawnTime = clock.now() - begin;
|
|
1642
|
-
const check = options.check ? checkShot(shot, board.elements(), board.getView(), stage, shot.title) : { framing: [], overlaps: [], edges: [], underTitle: [] };
|
|
1643
|
-
const holdMs = shot.hold ?? 800;
|
|
1644
|
-
const behaviors = shot.behaviors ?? [];
|
|
1645
|
-
if (behaviors.length > 0) {
|
|
1646
|
-
const initialProps = /* @__PURE__ */ new Map();
|
|
1647
|
-
for (const b of behaviors) {
|
|
1648
|
-
const key = `${b.target}:${b.property}`;
|
|
1649
|
-
const el = board.elements().find((e) => e.id === b.target);
|
|
1650
|
-
if (el && typeof el[b.property] === "number") {
|
|
1651
|
-
initialProps.set(key, el[b.property]);
|
|
1652
|
-
} else {
|
|
1653
|
-
initialProps.set(key, 0);
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
const stepMs = 50;
|
|
1657
|
-
const steps = Math.max(1, Math.round(holdMs / stepMs));
|
|
1658
|
-
const actualStepMs = holdMs / steps;
|
|
1659
|
-
const durationSec = holdMs / 1e3;
|
|
1660
|
-
for (let s = 1; s <= steps; s++) {
|
|
1661
|
-
await clock.sleep(actualStepMs);
|
|
1662
|
-
const elapsedSec = s * actualStepMs / 1e3;
|
|
1663
|
-
for (const b of behaviors) {
|
|
1664
|
-
const baseVal = initialProps.get(`${b.target}:${b.property}`) ?? 0;
|
|
1665
|
-
const nextVal = evaluateBehavior(b, elapsedSec, durationSec, baseVal);
|
|
1666
|
-
board.runCommand({
|
|
1667
|
-
type: "update",
|
|
1668
|
-
id: b.target,
|
|
1669
|
-
props: { [b.property]: nextVal }
|
|
1670
|
-
});
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
} else {
|
|
1674
|
-
await clock.sleep(holdMs);
|
|
1675
|
-
}
|
|
1676
|
-
const shotEnd = clock.now() - begin;
|
|
1677
|
-
const log = {
|
|
1678
|
-
...check,
|
|
1679
|
-
index: idx + 1,
|
|
1680
|
-
start: shotStart,
|
|
1681
|
-
drawn: drawnTime,
|
|
1682
|
-
end: shotEnd,
|
|
1683
|
-
caption: shot.caption,
|
|
1684
|
-
title: shot.title,
|
|
1685
|
-
failed: failedCmds,
|
|
1686
|
-
effects: runningEffects.map((r) => r.effect.id)
|
|
1687
|
-
};
|
|
1688
|
-
shots.push(log);
|
|
1689
|
-
if (options.afterShot) {
|
|
1690
|
-
await options.afterShot(log);
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
const duration = clock.now() - begin;
|
|
1694
|
-
const ok = shots.every((s) => s.failed.length === 0 && s.framing.every((f) => f.how !== "outside"));
|
|
1695
|
-
return { ok, duration, shots };
|
|
1696
|
-
}
|
|
1697
|
-
async function simulateScene(scene, options = {}) {
|
|
1698
|
-
const clock = new ManualClock();
|
|
1699
|
-
const board = new MemorySceneBoard();
|
|
1700
|
-
let done = false;
|
|
1701
|
-
let playError = null;
|
|
1702
|
-
const playPromise = playScene(scene, board, clock, { check: true }).then((res) => {
|
|
1703
|
-
done = true;
|
|
1704
|
-
return res;
|
|
1705
|
-
}).catch((err) => {
|
|
1706
|
-
done = true;
|
|
1707
|
-
playError = err;
|
|
1708
|
-
throw err;
|
|
1709
|
-
});
|
|
1710
|
-
for (let i = 0; i < 5e3 && !done; i++) {
|
|
1711
|
-
await clock.advance(50);
|
|
1712
|
-
}
|
|
1713
|
-
if (playError) throw playError;
|
|
1714
|
-
const result = await playPromise;
|
|
1715
|
-
const review = reviewScene(scene, result.shots, {
|
|
1716
|
-
target: scene.target,
|
|
1717
|
-
minTextPx: options.minTextPx
|
|
1131
|
+
// src/scene/standalone.ts
|
|
1132
|
+
function planShots(scene, stage) {
|
|
1133
|
+
return cameraTrack(scene).map((entry) => {
|
|
1134
|
+
const steps = Math.round(entry.moveMs / CAMERA_STEP_MS);
|
|
1135
|
+
const move = Array.from({ length: Math.max(0, steps - 1) }, (_, i) => cameraAt(entry, (i + 1) * CAMERA_STEP_MS, stage));
|
|
1136
|
+
move.push(entry.to);
|
|
1137
|
+
return { move, elements: entry.elements };
|
|
1718
1138
|
});
|
|
1719
|
-
return { result, review };
|
|
1720
1139
|
}
|
|
1721
|
-
|
|
1722
|
-
// src/scene/standalone.ts
|
|
1723
1140
|
function standaloneSceneHtml(scene) {
|
|
1141
|
+
const stage = sceneStage(scene);
|
|
1724
1142
|
const serializedScene = JSON.stringify(scene).replace(/</g, "\\u003c");
|
|
1143
|
+
const serializedPlan = JSON.stringify({ stage, cameraMs: CAMERA_MS, shots: planShots(scene, stage) }).replace(/</g, "\\u003c");
|
|
1725
1144
|
const title = scene.title || scene.name;
|
|
1726
1145
|
return `<!DOCTYPE html>
|
|
1727
1146
|
<html lang="en">
|
|
@@ -1747,7 +1166,7 @@ function standaloneSceneHtml(scene) {
|
|
|
1747
1166
|
position: relative;
|
|
1748
1167
|
width: 100%;
|
|
1749
1168
|
max-width: 1200px;
|
|
1750
|
-
aspect-ratio:
|
|
1169
|
+
aspect-ratio: ${stage.width} / ${stage.height};
|
|
1751
1170
|
background: #000;
|
|
1752
1171
|
border-radius: 12px;
|
|
1753
1172
|
overflow: hidden;
|
|
@@ -1762,6 +1181,12 @@ function standaloneSceneHtml(scene) {
|
|
|
1762
1181
|
height: 100%;
|
|
1763
1182
|
overflow: hidden;
|
|
1764
1183
|
}
|
|
1184
|
+
.board {
|
|
1185
|
+
position: absolute;
|
|
1186
|
+
inset: 0;
|
|
1187
|
+
transform-origin: 50% 50%;
|
|
1188
|
+
will-change: transform, filter;
|
|
1189
|
+
}
|
|
1765
1190
|
canvas.layer {
|
|
1766
1191
|
position: absolute;
|
|
1767
1192
|
top: 0;
|
|
@@ -1858,7 +1283,8 @@ function standaloneSceneHtml(scene) {
|
|
|
1858
1283
|
<div class="player-container">
|
|
1859
1284
|
<div class="viewport" id="viewport">
|
|
1860
1285
|
<canvas id="shaderCanvas" class="layer"></canvas>
|
|
1861
|
-
<canvas id="sceneCanvas" class="layer"></canvas>
|
|
1286
|
+
<div id="board" class="board"><canvas id="sceneCanvas" class="layer"></canvas></div>
|
|
1287
|
+
<svg width="0" height="0" style="position:absolute" aria-hidden="true"><filter id="motionBlur" x="-10%" y="-10%" width="120%" height="120%"><feGaussianBlur id="motionBlurAmount" stdDeviation="0 0"/></filter></svg>
|
|
1862
1288
|
<div id="titleEl" class="overlay-title"></div>
|
|
1863
1289
|
<div id="captionEl" class="overlay-caption"></div>
|
|
1864
1290
|
</div>
|
|
@@ -1875,6 +1301,9 @@ function standaloneSceneHtml(scene) {
|
|
|
1875
1301
|
|
|
1876
1302
|
<script>
|
|
1877
1303
|
const scene = ${serializedScene};
|
|
1304
|
+
// Each shot's camera move (zoom, scroll, 3D angle and blur at each step) and elements, worked out in advance.
|
|
1305
|
+
const plan = ${serializedPlan};
|
|
1306
|
+
const boardEl = document.getElementById('board');
|
|
1878
1307
|
const sceneCanvas = document.getElementById('sceneCanvas');
|
|
1879
1308
|
const shaderCanvas = document.getElementById('shaderCanvas');
|
|
1880
1309
|
const ctx = sceneCanvas.getContext('2d');
|
|
@@ -1888,7 +1317,8 @@ function standaloneSceneHtml(scene) {
|
|
|
1888
1317
|
|
|
1889
1318
|
let currentShotIdx = 0;
|
|
1890
1319
|
let isPlaying = false;
|
|
1891
|
-
let shotStartTime =
|
|
1320
|
+
let shotStartTime = performance.now();
|
|
1321
|
+
let moveFrom = performance.now();
|
|
1892
1322
|
let gl = null;
|
|
1893
1323
|
let shaderProgram = null;
|
|
1894
1324
|
|
|
@@ -1970,64 +1400,98 @@ function standaloneSceneHtml(scene) {
|
|
|
1970
1400
|
progressEl.style.width = pct + '%';
|
|
1971
1401
|
}
|
|
1972
1402
|
|
|
1403
|
+
// The camera at time 'now': a step of the current shot's move, or where it settled.
|
|
1404
|
+
function cameraNow(now) {
|
|
1405
|
+
const move = plan.shots[currentShotIdx].move;
|
|
1406
|
+
const u = Math.min(1, (now - moveFrom) / plan.cameraMs);
|
|
1407
|
+
return move[Math.min(move.length - 1, Math.floor(u * move.length))];
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
function applyCamera(view) {
|
|
1411
|
+
const w = boardEl.clientWidth || plan.stage.width;
|
|
1412
|
+
const angled = view.tilt || view.turn || view.roll;
|
|
1413
|
+
boardEl.style.transform = angled
|
|
1414
|
+
? 'perspective(' + (view.perspective || 1.2) * w + 'px) rotateX(' + (view.tilt || 0) + 'deg) rotateY(' + (view.turn || 0) + 'deg) rotateZ(' + (view.roll || 0) + 'deg)'
|
|
1415
|
+
: 'none';
|
|
1416
|
+
// Motion blur streaks along a pan (taken back through the roll, as the board is blurred before it turns).
|
|
1417
|
+
const blur = (view.blur || 0) * w / plan.stage.width;
|
|
1418
|
+
if (blur >= 0.05) {
|
|
1419
|
+
let sx = blur, sy = blur;
|
|
1420
|
+
if (view.blurAngle !== undefined) {
|
|
1421
|
+
const a = (view.blurAngle - (view.roll || 0)) * Math.PI / 180;
|
|
1422
|
+
sx = Math.max(blur * 0.15, blur * Math.abs(Math.cos(a)));
|
|
1423
|
+
sy = Math.max(blur * 0.15, blur * Math.abs(Math.sin(a)));
|
|
1424
|
+
}
|
|
1425
|
+
document.getElementById('motionBlurAmount').setAttribute('stdDeviation', sx.toFixed(2) + ' ' + sy.toFixed(2));
|
|
1426
|
+
boardEl.style.filter = 'url(#motionBlur)';
|
|
1427
|
+
} else {
|
|
1428
|
+
boardEl.style.filter = 'none';
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1973
1432
|
function draw() {
|
|
1974
|
-
const now = performance.now()
|
|
1975
|
-
renderShader(now);
|
|
1433
|
+
const now = performance.now();
|
|
1434
|
+
renderShader(now / 1000);
|
|
1976
1435
|
|
|
1436
|
+
const view = cameraNow(now);
|
|
1437
|
+
applyCamera(view);
|
|
1438
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1977
1439
|
ctx.clearRect(0, 0, sceneCanvas.width, sceneCanvas.height);
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
// Draw shot elements
|
|
1982
|
-
if (shot && shot.run) {
|
|
1983
|
-
for (const cmd of shot.run) {
|
|
1984
|
-
if (cmd.type === 'element' && cmd.element) {
|
|
1985
|
-
const el = cmd.element;
|
|
1986
|
-
ctx.fillStyle = el.backgroundColor || 'rgba(59, 130, 246, 0.4)';
|
|
1987
|
-
ctx.strokeStyle = el.strokeColor || '#3b82f6';
|
|
1988
|
-
ctx.lineWidth = 3;
|
|
1989
|
-
ctx.strokeRect(el.x, el.y, el.width, el.height);
|
|
1990
|
-
ctx.fillRect(el.x, el.y, el.width, el.height);
|
|
1440
|
+
const k = (sceneCanvas.width / plan.stage.width) * view.zoom;
|
|
1441
|
+
ctx.setTransform(k, 0, 0, k, k * view.scrollX, k * view.scrollY);
|
|
1991
1442
|
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1443
|
+
for (const el of plan.shots[currentShotIdx].elements) {
|
|
1444
|
+
ctx.fillStyle = el.backgroundColor || 'rgba(59, 130, 246, 0.4)';
|
|
1445
|
+
ctx.strokeStyle = el.strokeColor || '#3b82f6';
|
|
1446
|
+
ctx.lineWidth = 3;
|
|
1447
|
+
ctx.fillRect(el.x, el.y, el.width, el.height);
|
|
1448
|
+
ctx.strokeRect(el.x, el.y, el.width, el.height);
|
|
1449
|
+
if (el.text) {
|
|
1450
|
+
ctx.fillStyle = '#fff';
|
|
1451
|
+
ctx.font = '24px sans-serif';
|
|
1452
|
+
ctx.fillText(el.text, el.x + 16, el.y + 40);
|
|
1998
1453
|
}
|
|
1999
1454
|
}
|
|
2000
|
-
ctx.restore();
|
|
2001
1455
|
|
|
2002
|
-
|
|
1456
|
+
// Playing moves on to the next shot once this one's move and hold are over.
|
|
1457
|
+
const shot = scene.shots[currentShotIdx];
|
|
1458
|
+
if (isPlaying && now - shotStartTime > plan.cameraMs + (shot.hold ?? 800) && currentShotIdx < scene.shots.length - 1) {
|
|
1459
|
+
goTo(currentShotIdx + 1, true);
|
|
1460
|
+
}
|
|
1461
|
+
if (isPlaying || now - moveFrom < plan.cameraMs) {
|
|
2003
1462
|
requestAnimationFrame(draw);
|
|
2004
1463
|
}
|
|
2005
1464
|
}
|
|
2006
1465
|
|
|
1466
|
+
// Shows a shot: forwards it plays the camera's move into it, otherwise it cuts.
|
|
1467
|
+
function goTo(idx, animate) {
|
|
1468
|
+
currentShotIdx = idx;
|
|
1469
|
+
shotStartTime = performance.now();
|
|
1470
|
+
moveFrom = animate ? shotStartTime : shotStartTime - plan.cameraMs;
|
|
1471
|
+
updateShotUI();
|
|
1472
|
+
requestAnimationFrame(draw);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
2007
1475
|
playBtn.addEventListener('click', () => {
|
|
2008
1476
|
isPlaying = !isPlaying;
|
|
2009
1477
|
playBtn.textContent = isPlaying ? 'Pause' : 'Play';
|
|
2010
|
-
|
|
1478
|
+
// The current shot counts as settled, so its hold runs from now.
|
|
1479
|
+
if (isPlaying) {
|
|
1480
|
+
shotStartTime = performance.now() - plan.cameraMs;
|
|
1481
|
+
requestAnimationFrame(draw);
|
|
1482
|
+
}
|
|
2011
1483
|
});
|
|
2012
1484
|
|
|
2013
1485
|
prevBtn.addEventListener('click', () => {
|
|
2014
|
-
if (currentShotIdx > 0)
|
|
2015
|
-
currentShotIdx--;
|
|
2016
|
-
updateShotUI();
|
|
2017
|
-
draw();
|
|
2018
|
-
}
|
|
1486
|
+
if (currentShotIdx > 0) goTo(currentShotIdx - 1, false);
|
|
2019
1487
|
});
|
|
2020
1488
|
|
|
2021
1489
|
nextBtn.addEventListener('click', () => {
|
|
2022
|
-
if (currentShotIdx < scene.shots.length - 1)
|
|
2023
|
-
currentShotIdx++;
|
|
2024
|
-
updateShotUI();
|
|
2025
|
-
draw();
|
|
2026
|
-
}
|
|
1490
|
+
if (currentShotIdx < scene.shots.length - 1) goTo(currentShotIdx + 1, true);
|
|
2027
1491
|
});
|
|
2028
1492
|
|
|
2029
|
-
|
|
2030
|
-
|
|
1493
|
+
window.addEventListener('resize', () => requestAnimationFrame(draw));
|
|
1494
|
+
goTo(0, false);
|
|
2031
1495
|
</script>
|
|
2032
1496
|
</body>
|
|
2033
1497
|
</html>`;
|
|
@@ -2047,28 +1511,7 @@ export {
|
|
|
2047
1511
|
ALL_BLEND_MODES,
|
|
2048
1512
|
blendRgba,
|
|
2049
1513
|
getGlslBlendFunction,
|
|
2050
|
-
hash64,
|
|
2051
|
-
mixSeeds,
|
|
2052
|
-
unitFloat,
|
|
2053
|
-
quinticFade,
|
|
2054
|
-
noise1d,
|
|
2055
|
-
noise3d,
|
|
2056
|
-
fractalWiggle,
|
|
2057
|
-
EFFECT_TYPES,
|
|
2058
|
-
EFFECT_FADE_MS,
|
|
2059
|
-
effectGone,
|
|
2060
|
-
effectOpacity,
|
|
2061
|
-
NO_CHECK,
|
|
2062
|
-
SMALL_TEXT_PX,
|
|
2063
|
-
titleBox,
|
|
2064
|
-
touchedIds,
|
|
2065
|
-
checkShot,
|
|
2066
1514
|
validateScene,
|
|
2067
|
-
reviewScene,
|
|
2068
|
-
evaluateBehavior,
|
|
2069
|
-
MemorySceneBoard,
|
|
2070
|
-
playScene,
|
|
2071
|
-
simulateScene,
|
|
2072
1515
|
standaloneSceneHtml
|
|
2073
1516
|
};
|
|
2074
|
-
//# sourceMappingURL=chunk-
|
|
1517
|
+
//# sourceMappingURL=chunk-D7NLHC7K.js.map
|