@unseenco/theatre-core 0.4.2 → 0.5.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.mjs CHANGED
@@ -239,16 +239,44 @@ var require_propose = __commonJS({
239
239
  // core/src/coreExports.ts
240
240
  var coreExports_exports = {};
241
241
  __export(coreExports_exports, {
242
+ PAGE_MODE_SEQUENCE_LENGTH: () => PAGE_MODE_SEQUENCE_LENGTH,
243
+ PAGE_MODE_SUB_UNITS_PER_UNIT: () => PAGE_MODE_SUB_UNITS_PER_UNIT,
244
+ attachGsapSequenceBridge: () => attachGsapSequenceBridge,
245
+ attachSheetScrollDriver: () => attachSheetScrollDriver,
246
+ attachTheatrePageScroll: () => attachTheatrePageScroll,
247
+ configureTheatrePageScroll: () => configureTheatrePageScroll,
248
+ createDefaultPageScrollDriver: () => createDefaultPageScrollDriver,
249
+ createElementHorizontalScrollDriver: () => createElementHorizontalScrollDriver,
250
+ createElementScrollDriver: () => createElementScrollDriver,
251
+ createNativeDocumentHorizontalScrollDriver: () => createNativeDocumentHorizontalScrollDriver,
252
+ createNativeDocumentScrollDriver: () => createNativeDocumentScrollDriver,
242
253
  createRafDriver: () => createRafDriver,
254
+ defaultPageScrollContext: () => defaultPageScrollContext,
255
+ getActivePageScrollContext: () => getActivePageScrollContext,
243
256
  getProject: () => getProject,
257
+ getSheetScrollDriver: () => getSheetScrollDriver,
258
+ getTheatreCoreRafDriver: () => getTheatreCoreRafDriver,
259
+ getTheatrePageScrollConfig: () => getTheatrePageScrollConfig,
260
+ getTheatrePageScrollContext: () => getTheatrePageScrollContext,
261
+ isNativeDocumentScroller: () => isNativeDocumentScroller,
262
+ isPageScrollTrigger: () => isPageScrollTrigger,
244
263
  isRemoteEditorWindow: () => isRemoteEditorWindow,
264
+ isVerticalPageScrollTrigger: () => isVerticalPageScrollTrigger,
245
265
  notify: () => notify,
246
266
  onChange: () => onChange,
267
+ pageScrollProgressFromSequence: () => pageScrollProgressFromSequence,
268
+ pageScrollScrollersMatch: () => pageScrollScrollersMatch,
247
269
  privateAPI: () => privateAPI,
270
+ resolvePageScrollAxis: () => resolvePageScrollAxis,
271
+ resolvePageScrollScroller: () => resolvePageScrollScroller,
272
+ setActivePageScrollContext: () => setActivePageScrollContext,
248
273
  setCoreRafDriver: () => setCoreRafDriver,
274
+ setPageScrollProgress: () => setPageScrollProgress,
249
275
  setPrivateAPI: () => setPrivateAPI,
276
+ syncNativeDocumentScrollToSequencePosition: () => syncNativeDocumentScrollToSequencePosition,
277
+ syncPageScrollToSequencePosition: () => syncPageScrollToSequencePosition,
250
278
  types: () => propTypes_exports,
251
- val: () => val10
279
+ val: () => val12
252
280
  });
253
281
 
254
282
  // core/src/projects/projectsSingleton.ts
@@ -273,6 +301,82 @@ var ProjectsSingleton = class {
273
301
  var singleton = new ProjectsSingleton();
274
302
  var projectsSingleton_default = singleton;
275
303
 
304
+ // core/src/rafDrivers.ts
305
+ import { Ticker } from "@unseenco/theatre-dataverse";
306
+ var lastDriverId = 0;
307
+ function createRafDriver(conf) {
308
+ const tick = (time) => {
309
+ ticker.tick(time);
310
+ };
311
+ const ticker = new Ticker({
312
+ onActive() {
313
+ conf?.start?.();
314
+ },
315
+ onDormant() {
316
+ conf?.stop?.();
317
+ }
318
+ });
319
+ const driverPublicApi = {
320
+ tick,
321
+ id: lastDriverId++,
322
+ name: conf?.name ?? "CustomRafDriver-".concat(lastDriverId),
323
+ type: "Theatre_RafDriver_PublicAPI"
324
+ };
325
+ const driverPrivateApi = {
326
+ type: "Theatre_RafDriver_PrivateAPI",
327
+ publicApi: driverPublicApi,
328
+ ticker,
329
+ start: conf?.start,
330
+ stop: conf?.stop
331
+ };
332
+ setPrivateAPI(driverPublicApi, driverPrivateApi);
333
+ return driverPublicApi;
334
+ }
335
+
336
+ // core/src/coreTicker.ts
337
+ function createBasicRafDriver() {
338
+ let rafId = null;
339
+ const start = () => {
340
+ if (typeof window !== "undefined") {
341
+ const onAnimationFrame = (t2) => {
342
+ driver.tick(t2);
343
+ rafId = window.requestAnimationFrame(onAnimationFrame);
344
+ };
345
+ rafId = window.requestAnimationFrame(onAnimationFrame);
346
+ } else {
347
+ driver.tick(0);
348
+ setTimeout(() => driver.tick(1), 0);
349
+ }
350
+ };
351
+ const stop = () => {
352
+ if (typeof window !== "undefined") {
353
+ if (rafId !== null) {
354
+ window.cancelAnimationFrame(rafId);
355
+ }
356
+ } else {
357
+ }
358
+ };
359
+ const driver = createRafDriver({ name: "DefaultCoreRafDriver", start, stop });
360
+ return driver;
361
+ }
362
+ var coreRafDriver;
363
+ function getCoreRafDriver() {
364
+ if (!coreRafDriver) {
365
+ setCoreRafDriver(createBasicRafDriver());
366
+ }
367
+ return coreRafDriver;
368
+ }
369
+ function getCoreTicker() {
370
+ return getCoreRafDriver().ticker;
371
+ }
372
+ function setCoreRafDriver(driver) {
373
+ if (coreRafDriver) {
374
+ throw new Error("`setCoreRafDriver()` is already called.");
375
+ }
376
+ const driverPrivateApi = privateAPI(driver);
377
+ coreRafDriver = driverPrivateApi;
378
+ }
379
+
276
380
  // core/src/privateAPIs.ts
277
381
  var publicAPIToPrivateAPIMap = /* @__PURE__ */ new WeakMap();
278
382
  function privateAPI(pub) {
@@ -281,6 +385,23 @@ function privateAPI(pub) {
281
385
  function setPrivateAPI(pub, priv) {
282
386
  publicAPIToPrivateAPIMap.set(pub, priv);
283
387
  }
388
+ function getTheatreCoreRafDriver() {
389
+ return getCoreRafDriver().publicApi;
390
+ }
391
+
392
+ // core/src/sheets/applySheetRuntimeOptions.ts
393
+ function applySheetRuntimeOptions(sheet, opts) {
394
+ if (!opts) return;
395
+ if (opts.sequenceMode !== void 0) {
396
+ sheet.setSequenceMode(opts.sequenceMode);
397
+ }
398
+ if (opts.scrollDriver !== void 0) {
399
+ sheet.setPageScrollDriver(opts.scrollDriver);
400
+ }
401
+ if (opts.gsap === true) {
402
+ sheet.enableGsapSequenceBridge();
403
+ }
404
+ }
284
405
 
285
406
  // shared/src/utils/index.ts
286
407
  var emptyArray = [];
@@ -3222,11 +3343,11 @@ function sanitizeCompoundProps(props) {
3222
3343
  );
3223
3344
  }
3224
3345
  }
3225
- const val11 = props[key];
3226
- if (isLonghandPropType(val11)) {
3227
- sanitizedProps[key] = val11;
3346
+ const val13 = props[key];
3347
+ if (isLonghandPropType(val13)) {
3348
+ sanitizedProps[key] = val13;
3228
3349
  } else {
3229
- sanitizedProps[key] = toLonghandProp(val11);
3350
+ sanitizedProps[key] = toLonghandProp(val13);
3230
3351
  }
3231
3352
  }
3232
3353
  return sanitizedProps;
@@ -3324,15 +3445,15 @@ var file = (defaultValue, opts = {}) => {
3324
3445
  deserializeAndSanitize: _ensureFile
3325
3446
  };
3326
3447
  };
3327
- var _ensureFile = (val11) => {
3328
- if (!val11) return void 0;
3448
+ var _ensureFile = (val13) => {
3449
+ if (!val13) return void 0;
3329
3450
  let valid = true;
3330
- if (typeof val11.id !== "string" && ![null, void 0].includes(val11.id)) {
3451
+ if (typeof val13.id !== "string" && ![null, void 0].includes(val13.id)) {
3331
3452
  valid = false;
3332
3453
  }
3333
- if (val11.type !== "file") valid = false;
3454
+ if (val13.type !== "file") valid = false;
3334
3455
  if (!valid) return void 0;
3335
- return val11;
3456
+ return val13;
3336
3457
  };
3337
3458
  var image = (defaultValue, opts = {}) => {
3338
3459
  if (process.env.NODE_ENV !== "production") {
@@ -3356,15 +3477,15 @@ var image = (defaultValue, opts = {}) => {
3356
3477
  deserializeAndSanitize: _ensureImage
3357
3478
  };
3358
3479
  };
3359
- var _ensureImage = (val11) => {
3360
- if (!val11) return void 0;
3480
+ var _ensureImage = (val13) => {
3481
+ if (!val13) return void 0;
3361
3482
  let valid = true;
3362
- if (typeof val11.id !== "string" && ![null, void 0].includes(val11.id)) {
3483
+ if (typeof val13.id !== "string" && ![null, void 0].includes(val13.id)) {
3363
3484
  valid = false;
3364
3485
  }
3365
- if (val11.type !== "image") valid = false;
3486
+ if (val13.type !== "image") valid = false;
3366
3487
  if (!valid) return void 0;
3367
- return val11;
3488
+ return val13;
3368
3489
  };
3369
3490
  var number = (defaultValue, opts = {}) => {
3370
3491
  if (process.env.NODE_ENV !== "production") {
@@ -3487,11 +3608,11 @@ var rgba = (defaultValue = { r: 0, g: 0, b: 0, a: 1 }, opts = {}) => {
3487
3608
  deserializeAndSanitize: _sanitizeRgba
3488
3609
  };
3489
3610
  };
3490
- var _sanitizeRgba = (val11) => {
3491
- if (!val11) return void 0;
3611
+ var _sanitizeRgba = (val13) => {
3612
+ if (!val13) return void 0;
3492
3613
  let valid = true;
3493
3614
  for (const c2 of ["r", "g", "b", "a"]) {
3494
- if (!Object.prototype.hasOwnProperty.call(val11, c2) || typeof val11[c2] !== "number") {
3615
+ if (!Object.prototype.hasOwnProperty.call(val13, c2) || typeof val13[c2] !== "number") {
3495
3616
  valid = false;
3496
3617
  }
3497
3618
  }
@@ -3500,7 +3621,7 @@ var _sanitizeRgba = (val11) => {
3500
3621
  for (const c2 of ["r", "g", "b", "a"]) {
3501
3622
  ;
3502
3623
  sanitized[c2] = Math.min(
3503
- Math.max(val11[c2], 0),
3624
+ Math.max(val13[c2], 0),
3504
3625
  1
3505
3626
  );
3506
3627
  }
@@ -3539,8 +3660,8 @@ var boolean = (defaultValue, opts = {}) => {
3539
3660
  deserializeAndSanitize: _ensureBoolean
3540
3661
  };
3541
3662
  };
3542
- var _ensureBoolean = (val11) => {
3543
- return typeof val11 === "boolean" ? val11 : void 0;
3663
+ var _ensureBoolean = (val13) => {
3664
+ return typeof val13 === "boolean" ? val13 : void 0;
3544
3665
  };
3545
3666
  function leftInterpolate(left) {
3546
3667
  return left;
@@ -3747,8 +3868,8 @@ var TheatreSheetObject = class {
3747
3868
  }
3748
3869
  return der.getValue();
3749
3870
  }
3750
- set initialValue(val11) {
3751
- privateAPI(this).setInitialValue(val11);
3871
+ set initialValue(val13) {
3872
+ privateAPI(this).setInitialValue(val13);
3752
3873
  }
3753
3874
  showPropsOf(objects) {
3754
3875
  const host = privateAPI(this);
@@ -4516,9 +4637,9 @@ var SheetObject = class {
4516
4637
  }
4517
4638
  validateValue(pointer3, value) {
4518
4639
  }
4519
- setInitialValue(val11) {
4520
- this.validateValue(this.propsP, val11);
4521
- this._initialValue.set(val11);
4640
+ setInitialValue(val13) {
4641
+ this.validateValue(this.propsP, val13);
4642
+ this._initialValue.set(val13);
4522
4643
  }
4523
4644
  setSessionOverride(path, value) {
4524
4645
  const propConfig = getPropConfigByPath(this.template.staticConfig, path);
@@ -4538,8 +4659,8 @@ var SheetObject = class {
4538
4659
  * Internal: called by `RemoteSync` when a value update for this object
4539
4660
  * arrives from a remote editor window. Not exposed on the public API.
4540
4661
  */
4541
- setRemoteOverride(val11) {
4542
- this._remoteOverride.set(val11);
4662
+ setRemoteOverride(val13) {
4663
+ this._remoteOverride.set(val13);
4543
4664
  }
4544
4665
  /**
4545
4666
  * Internal: used by `RemoteSync` to subscribe to this object's fully
@@ -5519,7 +5640,7 @@ function parsePathToProp(pathToPropInString) {
5519
5640
  }
5520
5641
 
5521
5642
  // core/src/sheets/SheetTemplate.ts
5522
- import { Atom as Atom9 } from "@unseenco/theatre-dataverse";
5643
+ import { Atom as Atom10 } from "@unseenco/theatre-dataverse";
5523
5644
 
5524
5645
  // shared/src/utils/didYouMean.ts
5525
5646
  var import_propose = __toESM(require_propose());
@@ -5536,7 +5657,7 @@ function didYouMean(str, dictionary, prepend = "Did you mean ", append = "?") {
5536
5657
 
5537
5658
  // core/src/sequences/Sequence.ts
5538
5659
  import { getPointerParts as getPointerParts3 } from "@unseenco/theatre-dataverse";
5539
- import { Atom as Atom7 } from "@unseenco/theatre-dataverse";
5660
+ import { Atom as Atom8 } from "@unseenco/theatre-dataverse";
5540
5661
  import { pointer as pointer2 } from "@unseenco/theatre-dataverse";
5541
5662
  import { prism as prism6, val as val6 } from "@unseenco/theatre-dataverse";
5542
5663
 
@@ -5917,11 +6038,11 @@ var AudioPlaybackController = class {
5917
6038
  "The sequence will still play, but only 1000 times. The `iterationCount: ".concat(iterationCount, "` provided to `sequence.play()`\nis too high for a sequence with audio.\n\nTo fix this, either set `iterationCount` to a lower value, or remove the audio from the sequence."),
5918
6039
  [
5919
6040
  {
5920
- url: "https://www.theatrejs.com/docs/latest/manual/audio",
6041
+ url: "https://unseen-theatre.netlify.app/docs/guide/manual/audio",
5921
6042
  title: "Using Audio"
5922
6043
  },
5923
6044
  {
5924
- url: "https://www.theatrejs.com/docs/latest/api/core#sequence.attachaudio",
6045
+ url: "https://unseen-theatre.netlify.app/docs/api/theatre-core#sequence.attachaudio",
5925
6046
  title: "Audio API"
5926
6047
  }
5927
6048
  ]
@@ -5974,82 +6095,6 @@ var AudioPlaybackController = class {
5974
6095
  }
5975
6096
  };
5976
6097
 
5977
- // core/src/rafDrivers.ts
5978
- import { Ticker } from "@unseenco/theatre-dataverse";
5979
- var lastDriverId = 0;
5980
- function createRafDriver(conf) {
5981
- const tick = (time) => {
5982
- ticker.tick(time);
5983
- };
5984
- const ticker = new Ticker({
5985
- onActive() {
5986
- conf?.start?.();
5987
- },
5988
- onDormant() {
5989
- conf?.stop?.();
5990
- }
5991
- });
5992
- const driverPublicApi = {
5993
- tick,
5994
- id: lastDriverId++,
5995
- name: conf?.name ?? "CustomRafDriver-".concat(lastDriverId),
5996
- type: "Theatre_RafDriver_PublicAPI"
5997
- };
5998
- const driverPrivateApi = {
5999
- type: "Theatre_RafDriver_PrivateAPI",
6000
- publicApi: driverPublicApi,
6001
- ticker,
6002
- start: conf?.start,
6003
- stop: conf?.stop
6004
- };
6005
- setPrivateAPI(driverPublicApi, driverPrivateApi);
6006
- return driverPublicApi;
6007
- }
6008
-
6009
- // core/src/coreTicker.ts
6010
- function createBasicRafDriver() {
6011
- let rafId = null;
6012
- const start = () => {
6013
- if (typeof window !== "undefined") {
6014
- const onAnimationFrame = (t2) => {
6015
- driver.tick(t2);
6016
- rafId = window.requestAnimationFrame(onAnimationFrame);
6017
- };
6018
- rafId = window.requestAnimationFrame(onAnimationFrame);
6019
- } else {
6020
- driver.tick(0);
6021
- setTimeout(() => driver.tick(1), 0);
6022
- }
6023
- };
6024
- const stop = () => {
6025
- if (typeof window !== "undefined") {
6026
- if (rafId !== null) {
6027
- window.cancelAnimationFrame(rafId);
6028
- }
6029
- } else {
6030
- }
6031
- };
6032
- const driver = createRafDriver({ name: "DefaultCoreRafDriver", start, stop });
6033
- return driver;
6034
- }
6035
- var coreRafDriver;
6036
- function getCoreRafDriver() {
6037
- if (!coreRafDriver) {
6038
- setCoreRafDriver(createBasicRafDriver());
6039
- }
6040
- return coreRafDriver;
6041
- }
6042
- function getCoreTicker() {
6043
- return getCoreRafDriver().ticker;
6044
- }
6045
- function setCoreRafDriver(driver) {
6046
- if (coreRafDriver) {
6047
- throw new Error("`setCoreRafDriver()` is already called.");
6048
- }
6049
- const driverPrivateApi = privateAPI(driver);
6050
- coreRafDriver = driverPrivateApi;
6051
- }
6052
-
6053
6098
  // core/src/sequences/TheatreSequence.ts
6054
6099
  var TheatreSequence = class {
6055
6100
  get type() {
@@ -6073,7 +6118,7 @@ var TheatreSequence = class {
6073
6118
  "You seem to have called `sequence.play()` before the project has finished loading.\n\nThis would **not** a problem in production when using `@unseenco/theatre-core`, since Theatre.js loads instantly in core mode. However, when using `@unseenco/theatre-studio`, it takes a few milliseconds for it to load your project's state, before which your sequences cannot start playing.\n\nTo fix this, simply defer calling `sequence.play()` until after the project is loaded, like this:\n\n```\nproject.ready.then(() => {\n sequence.play()\n})\n```",
6074
6119
  [
6075
6120
  {
6076
- url: "https://www.theatrejs.com/docs/0.5/api/core#project.ready",
6121
+ url: "https://unseen-theatre.netlify.app/docs/api/theatre-core#project.ready",
6077
6122
  title: "Project.ready"
6078
6123
  }
6079
6124
  ]
@@ -6099,14 +6144,27 @@ var TheatreSequence = class {
6099
6144
  __experimental_getGsapClips() {
6100
6145
  return privateAPI(this).getGsapClips();
6101
6146
  }
6147
+ __experimental_getGsapScrollTriggers() {
6148
+ return privateAPI(this).getGsapScrollTriggers();
6149
+ }
6102
6150
  async attachAudio(args) {
6151
+ const priv = privateAPI(this);
6152
+ if (priv._sheet.getSequenceMode() === "page") {
6153
+ notify.warning(
6154
+ "Audio is not supported in page mode",
6155
+ "Sequence audio expects time in seconds. Use time mode for attachAudio(), or scrub scroll-driven animation without audio."
6156
+ );
6157
+ return Promise.reject(
6158
+ new Error("sequence.attachAudio() is not supported in page mode")
6159
+ );
6160
+ }
6103
6161
  const { audioContext, destinationNode, decodedBuffer, gainNode } = await resolveAudioBuffer(args);
6104
6162
  const playbackController = new AudioPlaybackController(
6105
6163
  decodedBuffer,
6106
6164
  audioContext,
6107
6165
  gainNode
6108
6166
  );
6109
- privateAPI(this).replacePlaybackController(playbackController);
6167
+ priv.replacePlaybackController(playbackController);
6110
6168
  return { audioContext, destinationNode, decodedBuffer, gainNode };
6111
6169
  }
6112
6170
  get pointer() {
@@ -6204,6 +6262,31 @@ async function resolveAudioBuffer(args) {
6204
6262
  };
6205
6263
  }
6206
6264
 
6265
+ // shared/src/gsap/gsapStudioRegistryRevision.ts
6266
+ import { Atom as Atom7 } from "@unseenco/theatre-dataverse";
6267
+ var revisionAtom = new Atom7(0);
6268
+ var gsapStudioRegistryRevisionPointer = revisionAtom.pointer;
6269
+
6270
+ // shared/src/gsap/scrollTriggerRegistry.ts
6271
+ var DEFAULT_SHEET_INSTANCE_ID = "default";
6272
+ var REGISTRY_KEY = "__unseenco_theatre_gsap_scrollTriggerRegistry__";
6273
+ function sheetAddressKey(address) {
6274
+ const sheetInstanceId = address.sheetInstanceId ?? DEFAULT_SHEET_INSTANCE_ID;
6275
+ return "".concat(address.projectId, "|").concat(address.sheetId, "|").concat(sheetInstanceId);
6276
+ }
6277
+ function getStore() {
6278
+ const g2 = globalThis;
6279
+ if (!g2[REGISTRY_KEY]) {
6280
+ g2[REGISTRY_KEY] = { bySheetAddress: /* @__PURE__ */ new Map() };
6281
+ }
6282
+ return g2[REGISTRY_KEY];
6283
+ }
6284
+ function listScrollTriggerEntriesForSheet(sheetKey) {
6285
+ const map = getStore().bySheetAddress.get(sheetKey);
6286
+ if (!map) return [];
6287
+ return [...map.values()].sort((a2, b2) => a2.layout.start - b2.layout.start);
6288
+ }
6289
+
6207
6290
  // shared/src/sequence/trackData.ts
6208
6291
  function isBasicKeyframedTrack(track) {
6209
6292
  return track.type === "BasicKeyframedTrack";
@@ -6211,6 +6294,19 @@ function isBasicKeyframedTrack(track) {
6211
6294
  function isGsapClipTrack(track) {
6212
6295
  return track.type === "GsapClipTrack";
6213
6296
  }
6297
+ function gsapClipEndTime(clip) {
6298
+ return clip.start + clip.duration;
6299
+ }
6300
+ function gsapClipSyncProgress(sequencePosition, clip) {
6301
+ if (clip.duration <= 0) return 0;
6302
+ if (sequencePosition < clip.start) return 0;
6303
+ const clipEnd = gsapClipEndTime(clip);
6304
+ if (sequencePosition >= clipEnd - 1e-5) return 1;
6305
+ const raw = (sequencePosition - clip.start) / clip.duration;
6306
+ if (raw <= 0) return 0;
6307
+ if (raw >= 1) return 1;
6308
+ return raw;
6309
+ }
6214
6310
 
6215
6311
  // core/src/sequences/Sequence.ts
6216
6312
  var possibleDirections = [
@@ -6246,7 +6342,7 @@ var Sequence = class {
6246
6342
  this._logger = _project._logger.named("Sheet", _sheet.address.sheetId).named("Instance", _sheet.address.sheetInstanceId).named("Variant", _sequenceVariant);
6247
6343
  this.address = { ...this._sheet.address, sequenceName: _sequenceVariant };
6248
6344
  this.publicApi = new TheatreSequence(this);
6249
- this._playbackControllerBox = new Atom7(
6345
+ this._playbackControllerBox = new Atom8(
6250
6346
  playbackController ?? new DefaultPlaybackController()
6251
6347
  );
6252
6348
  this._prismOfStatePointer = prism6(
@@ -6258,6 +6354,9 @@ var Sequence = class {
6258
6354
  });
6259
6355
  this._positionFormatterD = prism6(() => {
6260
6356
  const subUnitsPerUnit = val6(this._subUnitsPerUnitD);
6357
+ if (this._sheet.getSequenceMode() === "page") {
6358
+ return new PercentBasedPositionFormatter(subUnitsPerUnit);
6359
+ }
6261
6360
  return new TimeBasedPositionFormatter(subUnitsPerUnit);
6262
6361
  });
6263
6362
  }
@@ -6363,6 +6462,21 @@ var Sequence = class {
6363
6462
  }
6364
6463
  return result.sort((a2, b2) => a2.clip.start - b2.clip.start);
6365
6464
  }
6465
+ getGsapScrollTriggers() {
6466
+ if (this._sheet.getSequenceMode() !== "page") {
6467
+ return [];
6468
+ }
6469
+ const key = sheetAddressKey(this._sheet.address);
6470
+ return listScrollTriggerEntriesForSheet(key).filter((entry) => entry.sheetObject != null).map((entry) => ({
6471
+ objectKey: entry.sheetObject.address.objectKey,
6472
+ scrollTriggerId: entry.id,
6473
+ label: entry.label,
6474
+ layout: entry.layout,
6475
+ kind: entry.kind,
6476
+ animationSpanSeconds: entry.animationSpanSeconds,
6477
+ timelineChildren: entry.timelineChildren
6478
+ }));
6479
+ }
6366
6480
  get positionFormatter() {
6367
6481
  return this._positionFormatterD.getValue();
6368
6482
  }
@@ -6433,6 +6547,13 @@ var Sequence = class {
6433
6547
  return this._playbackControllerBox.get().playDynamicRange(rangeD, ticker);
6434
6548
  }
6435
6549
  async play(conf, ticker) {
6550
+ if (this._sheet.getSequenceMode() === "page") {
6551
+ notify.warning(
6552
+ "Playback is disabled in page mode",
6553
+ "The sequence is driven by page scroll. Scrub the playhead or scroll the page instead of calling sequence.play()."
6554
+ );
6555
+ return false;
6556
+ }
6436
6557
  const sequenceDuration = this.length;
6437
6558
  const range = conf && conf.range ? conf.range : [0, sequenceDuration];
6438
6559
  if (process.env.NODE_ENV !== "production") {
@@ -6465,11 +6586,11 @@ var Sequence = class {
6465
6586
  ), "s) is longer than the duration of the sequence (").concat(sequenceDuration, "s).\n\nTo fix this, either set `conf.range[1]` to be less the duration of the sequence, or adjust the sequence duration in the UI."),
6466
6587
  [
6467
6588
  {
6468
- url: "https://www.theatrejs.com/docs/latest/manual/sequences",
6589
+ url: "https://unseen-theatre.netlify.app/docs/guide/manual/sequences",
6469
6590
  title: "Sequences"
6470
6591
  },
6471
6592
  {
6472
- url: "https://www.theatrejs.com/docs/latest/manual/sequences",
6593
+ url: "https://unseen-theatre.netlify.app/docs/guide/manual/sequences",
6473
6594
  title: "Playback API"
6474
6595
  }
6475
6596
  ]
@@ -6623,6 +6744,33 @@ var TimeBasedPositionFormatter = class {
6623
6744
  return posInUnitSpace.toFixed(2) + "s";
6624
6745
  }
6625
6746
  };
6747
+ var PercentBasedPositionFormatter = class {
6748
+ constructor(_subUnitsPerUnit) {
6749
+ this._subUnitsPerUnit = _subUnitsPerUnit;
6750
+ }
6751
+ formatSubUnitForGrid(posInUnitSpace) {
6752
+ const subUnitSize = 1 / this._subUnitsPerUnit;
6753
+ const withinUnit = posInUnitSpace % 1;
6754
+ const tenths = Math.round(withinUnit / subUnitSize);
6755
+ return tenths + "t";
6756
+ }
6757
+ formatFullUnitForGrid(posInUnitSpace) {
6758
+ return Math.round(posInUnitSpace) + "%";
6759
+ }
6760
+ formatForPlayhead(posInUnitSpace) {
6761
+ const whole = Math.floor(posInUnitSpace);
6762
+ const subUnitSize = 1 / this._subUnitsPerUnit;
6763
+ const withinUnit = posInUnitSpace - whole;
6764
+ const tenths = Math.round(withinUnit / subUnitSize);
6765
+ if (tenths <= 0) {
6766
+ return whole + "%";
6767
+ }
6768
+ return whole + "." + tenths + "%";
6769
+ }
6770
+ formatBasic(posInUnitSpace) {
6771
+ return posInUnitSpace.toFixed(1) + "%";
6772
+ }
6773
+ };
6626
6774
  var second = 1;
6627
6775
  var minute = second * 60;
6628
6776
  var hour = minute * 60;
@@ -6657,11 +6805,11 @@ function validateAndSanitiseSlashedPathOrThrow(unsanitisedPath, fnName) {
6657
6805
  "The path in `".concat(fnName, '("').concat(unsanitisedPath, '")` was sanitized to `"').concat(sanitisedPath, '"`.\n\n') + "Please replace the path with the sanitized one, otherwise it will likely break in the future.",
6658
6806
  [
6659
6807
  {
6660
- url: "https://www.theatrejs.com/docs/latest/manual/objects#creating-sheet-objects",
6808
+ url: "https://unseen-theatre.netlify.app/docs/guide/manual/objects#creating-objects",
6661
6809
  title: "Sheet Objects"
6662
6810
  },
6663
6811
  {
6664
- url: "https://www.theatrejs.com/docs/latest/api/core#sheet.object",
6812
+ url: "https://unseen-theatre.netlify.app/docs/api/theatre-core#sheet.object",
6665
6813
  title: "API"
6666
6814
  }
6667
6815
  ]
@@ -6876,6 +7024,15 @@ var TheatreSheet = class {
6876
7024
  getActiveSequenceVariant() {
6877
7025
  return privateAPI(this).getActiveSequenceVariant();
6878
7026
  }
7027
+ getSequenceMode() {
7028
+ return privateAPI(this).getSequenceMode();
7029
+ }
7030
+ setSequenceMode(mode) {
7031
+ privateAPI(this).setSequenceMode(mode);
7032
+ }
7033
+ setPageScrollDriver(driver) {
7034
+ privateAPI(this).setPageScrollDriver(driver);
7035
+ }
6879
7036
  get project() {
6880
7037
  return privateAPI(this).project.publicApi;
6881
7038
  }
@@ -6933,14 +7090,373 @@ var TheatreSheet = class {
6933
7090
  };
6934
7091
 
6935
7092
  // core/src/sheets/Sheet.ts
6936
- import { Atom as Atom8, prism as prism7, val as val7 } from "@unseenco/theatre-dataverse";
7093
+ import { Atom as Atom9, prism as prism8, val as val9 } from "@unseenco/theatre-dataverse";
7094
+
7095
+ // core/src/sheets/sheetSequenceMode.ts
7096
+ var PAGE_MODE_SEQUENCE_LENGTH = 100;
7097
+ var PAGE_MODE_SUB_UNITS_PER_UNIT = 10;
7098
+
7099
+ // shared/src/gsap/applyTimelineChildTiming.ts
7100
+ function applyTimelineChildTimingToGsap(rootAnimation, timelineChildren, childById, onRebuild) {
7101
+ if (!timelineChildren.length) return true;
7102
+ let ok = applyTimelineChildTimingToGsapInner(
7103
+ rootAnimation,
7104
+ timelineChildren,
7105
+ childById
7106
+ );
7107
+ if (!ok && onRebuild) {
7108
+ const rebuilt = onRebuild();
7109
+ if (rebuilt) {
7110
+ ok = applyTimelineChildTimingToGsapInner(
7111
+ rebuilt,
7112
+ timelineChildren,
7113
+ childById
7114
+ );
7115
+ }
7116
+ }
7117
+ return ok;
7118
+ }
7119
+ function applyTimelineChildTimingToGsapInner(rootAnimation, timelineChildren, childById) {
7120
+ if (!childById || timelineChildren.length === 0) return true;
7121
+ let ok = true;
7122
+ for (const childState of timelineChildren) {
7123
+ const tween = childById.get(childState.childId);
7124
+ if (!tween) continue;
7125
+ try {
7126
+ const t2 = tween;
7127
+ if (typeof t2.startTime === "function") {
7128
+ t2.startTime(childState.localStart);
7129
+ }
7130
+ if (typeof t2.duration === "function") {
7131
+ t2.duration(childState.localDuration);
7132
+ }
7133
+ } catch {
7134
+ ok = false;
7135
+ }
7136
+ }
7137
+ return ok;
7138
+ }
7139
+
7140
+ // shared/src/gsap/syncGsapClipProgress.ts
7141
+ function getGsapAnimationTargetKey(animation, fallbackAnimationId) {
7142
+ const tween = animation;
7143
+ const targets = tween.targets?.();
7144
+ if (targets && targets.length > 0) {
7145
+ return targets[0];
7146
+ }
7147
+ return fallbackAnimationId;
7148
+ }
7149
+ function syncRegisteredGsapAnimationsForClips(sequencePosition, clips) {
7150
+ const enriched = [];
7151
+ for (const clip of clips) {
7152
+ const entry = getAnimationEntryBySheetAddressKey(
7153
+ clip.sheetObjectAddressKey,
7154
+ clip.gsapAnimationId
7155
+ );
7156
+ if (!entry?.animation) continue;
7157
+ enriched.push({
7158
+ ...clip,
7159
+ entry,
7160
+ targetKey: getGsapAnimationTargetKey(
7161
+ entry.animation,
7162
+ clip.gsapAnimationId
7163
+ )
7164
+ });
7165
+ }
7166
+ const byTarget = /* @__PURE__ */ new Map();
7167
+ for (const item of enriched) {
7168
+ const list = byTarget.get(item.targetKey) ?? [];
7169
+ list.push(item);
7170
+ byTarget.set(item.targetKey, list);
7171
+ }
7172
+ for (const group of byTarget.values()) {
7173
+ const sorted = [...group].sort((a2, b2) => a2.start - b2.start);
7174
+ for (const clip of sorted) {
7175
+ const progress = gsapClipSyncProgress(sequencePosition, clip);
7176
+ const entry = clip.entry;
7177
+ const animation = entry.animation;
7178
+ if (entry.kind === "timeline" && clip.timelineChildren && clip.timelineChildren.length > 0) {
7179
+ applyTimelineChildTimingToGsap(
7180
+ entry.animation,
7181
+ clip.timelineChildren,
7182
+ entry.timelineChildById,
7183
+ entry.onRebuildTimeline
7184
+ );
7185
+ const span = clip.timelineSpan ?? readGsapTweenTimelineDuration(entry.animation);
7186
+ const t2 = progress * Math.max(span, 0.01);
7187
+ if (typeof animation.time === "function") {
7188
+ animation.time(t2, true);
7189
+ } else {
7190
+ animation.progress?.(progress, true);
7191
+ }
7192
+ } else {
7193
+ animation.progress?.(progress, true);
7194
+ }
7195
+ }
7196
+ }
7197
+ }
7198
+ function readGsapTweenTimelineDuration(animation) {
7199
+ const tween = animation;
7200
+ const total = tween.totalDuration?.();
7201
+ if (typeof total === "number" && total > 0) return total;
7202
+ const d2 = tween.duration();
7203
+ return d2 > 0 ? d2 : 1;
7204
+ }
7205
+
7206
+ // shared/src/gsap/gsapAnimationRegistry.ts
7207
+ var DEFAULT_SHEET_INSTANCE_ID2 = "default";
7208
+ var REGISTRY_KEY2 = "__unseenco_theatre_gsap_animationRegistry__";
7209
+ function sheetObjectAddressKeyFromParts(address) {
7210
+ const sheetInstanceId = address.sheetInstanceId ?? DEFAULT_SHEET_INSTANCE_ID2;
7211
+ return "".concat(address.projectId, "|").concat(address.sheetId, "|").concat(sheetInstanceId, "|").concat(address.objectKey);
7212
+ }
7213
+ function getStore2() {
7214
+ const g2 = globalThis;
7215
+ if (!g2[REGISTRY_KEY2]) {
7216
+ g2[REGISTRY_KEY2] = {
7217
+ bySheetAddress: /* @__PURE__ */ new Map()
7218
+ };
7219
+ }
7220
+ return g2[REGISTRY_KEY2];
7221
+ }
7222
+ function getAnimationEntryBySheetAddressKey(sheetObjectAddressKey, animationId) {
7223
+ return getStore2().bySheetAddress.get(sheetObjectAddressKey)?.get(animationId);
7224
+ }
7225
+
7226
+ // shared/src/gsap/subscribeGsapClipSyncAtPlayhead.ts
7227
+ import { pointerToPrism, prism as prism7, val as val7 } from "@unseenco/theatre-dataverse";
7228
+
7229
+ // shared/src/gsap/syncGsapClipsAtSequencePosition.ts
7230
+ function syncGsapClipsAtSequencePosition(position, clips) {
7231
+ syncRegisteredGsapAnimationsForClips(
7232
+ position,
7233
+ clips
7234
+ );
7235
+ }
7236
+
7237
+ // shared/src/gsap/subscribeGsapClipSyncAtPlayhead.ts
7238
+ function subscribeGsapClipSyncAtPlayhead(source) {
7239
+ const positionPointer = source.pointer.position;
7240
+ const positionPrism = pointerToPrism(positionPointer);
7241
+ const clipsPrism = prism7(() => source.getGsapClipTimings());
7242
+ const syncNow = () => {
7243
+ const clips = clipsPrism.getValue();
7244
+ if (clips.length === 0) return;
7245
+ syncGsapClipsAtSequencePosition(val7(positionPointer), clips);
7246
+ };
7247
+ const untapPosition = positionPrism.onStale(syncNow);
7248
+ const untapClips = clipsPrism.onStale(syncNow);
7249
+ syncNow();
7250
+ return () => {
7251
+ untapPosition();
7252
+ untapClips();
7253
+ };
7254
+ }
7255
+
7256
+ // core/src/gsap/attachGsapSequenceBridge.ts
7257
+ function attachGsapSequenceBridge(sheet) {
7258
+ const sequence = sheet.sequence;
7259
+ const sheetAddress = privateAPI(sheet).address;
7260
+ return subscribeGsapClipSyncAtPlayhead({
7261
+ pointer: sequence.pointer,
7262
+ getGsapClipTimings: () => sequence.__experimental_getGsapClips().map(({ objectKey, clip }) => ({
7263
+ sheetObjectAddressKey: sheetObjectAddressKeyFromParts({
7264
+ projectId: sheetAddress.projectId,
7265
+ sheetId: sheetAddress.sheetId,
7266
+ sheetInstanceId: sheetAddress.sheetInstanceId,
7267
+ objectKey
7268
+ }),
7269
+ gsapAnimationId: clip.gsapAnimationId,
7270
+ start: clip.start,
7271
+ duration: clip.duration,
7272
+ timelineChildren: clip.timelineChildren,
7273
+ timelineSpan: clip.timelineSpan
7274
+ }))
7275
+ });
7276
+ }
7277
+
7278
+ // core/src/sheets/attachSheetScrollDriver.ts
7279
+ import { val as val8 } from "@unseenco/theatre-dataverse";
7280
+ var sheetScrollDrivers = /* @__PURE__ */ new WeakMap();
7281
+ var pageScrollDrivenSequencePositionListeners = /* @__PURE__ */ new Set();
7282
+ function onPageScrollDrivenSequencePosition(listener) {
7283
+ pageScrollDrivenSequencePositionListeners.add(listener);
7284
+ return () => {
7285
+ pageScrollDrivenSequencePositionListeners.delete(listener);
7286
+ };
7287
+ }
7288
+ function notifyPageScrollDrivenSequencePosition(sheet, position) {
7289
+ for (const listener of pageScrollDrivenSequencePositionListeners) {
7290
+ listener(sheet, position);
7291
+ }
7292
+ }
7293
+ function getSheetScrollDriver(sheet) {
7294
+ return sheetScrollDrivers.get(sheet);
7295
+ }
7296
+ function rememberSheetScrollDriver(sheet, driver) {
7297
+ sheetScrollDrivers.set(sheet, driver);
7298
+ }
7299
+ function progressFromScrollPosition(position, max) {
7300
+ if (max <= 0) return 0;
7301
+ if (position <= 0) return 0;
7302
+ if (position >= max - 1) return 1;
7303
+ return position / max;
7304
+ }
7305
+ function getNativeDocumentScrollingElement() {
7306
+ return document.scrollingElement ?? document.documentElement;
7307
+ }
7308
+ function addNativeDocumentScrollListener(handler) {
7309
+ document.addEventListener("scroll", handler, { passive: true, capture: true });
7310
+ return () => document.removeEventListener("scroll", handler, { capture: true });
7311
+ }
7312
+ function createScrollDriverFromElement(getMaxScroll, getScrollPos, setScrollPos, addScrollListener) {
7313
+ let suppressScrollEvents = false;
7314
+ let suppressGeneration = 0;
7315
+ const getProgress = () => {
7316
+ const max = getMaxScroll();
7317
+ return progressFromScrollPosition(getScrollPos(), max);
7318
+ };
7319
+ const setProgress = (progress) => {
7320
+ const max = getMaxScroll();
7321
+ const clamped = Math.max(0, Math.min(1, progress));
7322
+ const gen = ++suppressGeneration;
7323
+ suppressScrollEvents = true;
7324
+ setScrollPos(clamped * max);
7325
+ const release = () => {
7326
+ if (gen === suppressGeneration) {
7327
+ suppressScrollEvents = false;
7328
+ }
7329
+ };
7330
+ requestAnimationFrame(() => requestAnimationFrame(release));
7331
+ };
7332
+ return {
7333
+ getProgress,
7334
+ setProgress,
7335
+ subscribe(onProgressChange) {
7336
+ let rafId = 0;
7337
+ const flush = () => {
7338
+ rafId = 0;
7339
+ if (suppressScrollEvents) return;
7340
+ onProgressChange(getProgress());
7341
+ };
7342
+ const scheduleFlush = () => {
7343
+ if (rafId !== 0) return;
7344
+ rafId = requestAnimationFrame(flush);
7345
+ };
7346
+ const handler = () => scheduleFlush();
7347
+ const removeScroll = addScrollListener(handler);
7348
+ const onResize = () => scheduleFlush();
7349
+ window.addEventListener("resize", onResize);
7350
+ return () => {
7351
+ removeScroll();
7352
+ window.removeEventListener("resize", onResize);
7353
+ if (rafId !== 0) cancelAnimationFrame(rafId);
7354
+ };
7355
+ }
7356
+ };
7357
+ }
7358
+ function createNativeDocumentScrollDriver() {
7359
+ const getMaxScroll = () => {
7360
+ const el = document.documentElement;
7361
+ return Math.max(0, el.scrollHeight - window.innerHeight);
7362
+ };
7363
+ return createScrollDriverFromElement(
7364
+ getMaxScroll,
7365
+ () => window.scrollY,
7366
+ (y2) => window.scrollTo({ top: y2, behavior: "instant" }),
7367
+ addNativeDocumentScrollListener
7368
+ );
7369
+ }
7370
+ function createNativeDocumentHorizontalScrollDriver() {
7371
+ const getMaxScroll = () => {
7372
+ const el = getNativeDocumentScrollingElement();
7373
+ return Math.max(0, el.scrollWidth - el.clientWidth);
7374
+ };
7375
+ const getScrollLeft = () => {
7376
+ const el = getNativeDocumentScrollingElement();
7377
+ return el.scrollLeft;
7378
+ };
7379
+ return createScrollDriverFromElement(
7380
+ getMaxScroll,
7381
+ getScrollLeft,
7382
+ (x2) => {
7383
+ getNativeDocumentScrollingElement().scrollLeft = x2;
7384
+ },
7385
+ addNativeDocumentScrollListener
7386
+ );
7387
+ }
7388
+ function createElementScrollDriver(element) {
7389
+ const getMaxScroll = () => Math.max(0, element.scrollHeight - element.clientHeight);
7390
+ return createScrollDriverFromElement(
7391
+ getMaxScroll,
7392
+ () => element.scrollTop,
7393
+ (y2) => {
7394
+ element.scrollTop = y2;
7395
+ },
7396
+ (handler) => {
7397
+ element.addEventListener("scroll", handler, { passive: true });
7398
+ return () => element.removeEventListener("scroll", handler);
7399
+ }
7400
+ );
7401
+ }
7402
+ function createElementHorizontalScrollDriver(element) {
7403
+ const getMaxScroll = () => Math.max(0, element.scrollWidth - element.clientWidth);
7404
+ return createScrollDriverFromElement(
7405
+ getMaxScroll,
7406
+ () => element.scrollLeft,
7407
+ (x2) => {
7408
+ element.scrollLeft = x2;
7409
+ },
7410
+ (handler) => {
7411
+ element.addEventListener("scroll", handler, { passive: true });
7412
+ return () => element.removeEventListener("scroll", handler);
7413
+ }
7414
+ );
7415
+ }
7416
+ function attachSheetScrollDriver(sheet, driver = createNativeDocumentScrollDriver()) {
7417
+ rememberSheetScrollDriver(sheet, driver);
7418
+ const sequence = sheet.sequence;
7419
+ const syncPositionFromScroll = (progress) => {
7420
+ const length = val8(sequence.pointer.length);
7421
+ if (length <= 0) return;
7422
+ sequence.position = progress * length;
7423
+ };
7424
+ const untapScroll = driver.subscribe((progress) => {
7425
+ syncPositionFromScroll(progress);
7426
+ notifyPageScrollDrivenSequencePosition(sheet, sequence.position);
7427
+ });
7428
+ syncPositionFromScroll(driver.getProgress());
7429
+ return () => {
7430
+ untapScroll();
7431
+ };
7432
+ }
7433
+ function pageScrollProgressFromSequence(sheet) {
7434
+ const length = val8(sheet.sequence.pointer.length);
7435
+ if (length <= 0) return 0;
7436
+ return sheet.sequence.position / length;
7437
+ }
7438
+ function setPageScrollProgress(sheet, progress) {
7439
+ const length = val8(sheet.sequence.pointer.length);
7440
+ if (length <= 0) return;
7441
+ const clamped = Math.max(0, Math.min(1, progress));
7442
+ sheet.sequence.position = clamped * length;
7443
+ }
7444
+ function syncPageScrollToSequencePosition(sheet, driver = getSheetScrollDriver(sheet) ?? createNativeDocumentScrollDriver()) {
7445
+ const progress = pageScrollProgressFromSequence(sheet);
7446
+ driver.setProgress(progress);
7447
+ }
7448
+ function syncNativeDocumentScrollToSequencePosition(sheet) {
7449
+ syncPageScrollToSequencePosition(sheet, createNativeDocumentScrollDriver());
7450
+ }
7451
+
7452
+ // core/src/sheets/Sheet.ts
6937
7453
  var Sheet = class {
6938
7454
  constructor(template, instanceId) {
6939
7455
  this.template = template;
6940
7456
  this.instanceId = instanceId;
6941
- __publicField(this, "_objects", new Atom8({}));
7457
+ __publicField(this, "_objects", new Atom9({}));
6942
7458
  __publicField(this, "_sequences", {});
6943
- __publicField(this, "_activeSequenceVariant", new Atom8(
7459
+ __publicField(this, "_activeSequenceVariant", new Atom9(
6944
7460
  DEFAULT_SEQUENCE_VARIANT
6945
7461
  ));
6946
7462
  /**
@@ -6948,7 +7464,12 @@ var Sheet = class {
6948
7464
  * value resolution (preview). User code can still update `_activeSequenceVariant`
6949
7465
  * via `setActiveSequenceVariant()` without affecting the Studio preview.
6950
7466
  */
6951
- __publicField(this, "_studioPreviewVariantOverride", new Atom8(void 0));
7467
+ __publicField(this, "_studioPreviewVariantOverride", new Atom9(void 0));
7468
+ __publicField(this, "_sequenceMode", new Atom9("time"));
7469
+ __publicField(this, "sequenceModeP", this._sequenceMode.pointer);
7470
+ __publicField(this, "_pageScrollDisposer");
7471
+ __publicField(this, "_customPageScrollDriver");
7472
+ __publicField(this, "_gsapBridgeDisposer");
6952
7473
  __publicField(this, "activeSequenceVariantP", this._activeSequenceVariant.pointer);
6953
7474
  __publicField(this, "effectiveActiveSequenceVariantD");
6954
7475
  __publicField(this, "address");
@@ -6965,12 +7486,12 @@ var Sheet = class {
6965
7486
  sheetInstanceId: this.instanceId
6966
7487
  };
6967
7488
  this.publicApi = new TheatreSheet(this);
6968
- this.effectiveActiveSequenceVariantD = prism7(() => {
6969
- const studioOverride = val7(this._studioPreviewVariantOverride.pointer);
7489
+ this.effectiveActiveSequenceVariantD = prism8(() => {
7490
+ const studioOverride = val9(this._studioPreviewVariantOverride.pointer);
6970
7491
  if (studioOverride !== void 0) {
6971
7492
  return studioOverride;
6972
7493
  }
6973
- return val7(this._activeSequenceVariant.pointer);
7494
+ return val9(this._activeSequenceVariant.pointer);
6974
7495
  });
6975
7496
  }
6976
7497
  /**
@@ -7021,6 +7542,7 @@ var Sheet = class {
7021
7542
  * this sheet instance from the project. Persisted state is kept.
7022
7543
  */
7023
7544
  unload() {
7545
+ this.disposeRuntimeIntegrations();
7024
7546
  for (const sequence of Object.values(this._sequences)) {
7025
7547
  sequence.pause();
7026
7548
  }
@@ -7032,10 +7554,13 @@ var Sheet = class {
7032
7554
  this.project._unloadSheetInstance(this);
7033
7555
  }
7034
7556
  getSequence(variant) {
7035
- const variantId = variant ?? val7(this._activeSequenceVariant.pointer);
7557
+ const variantId = variant ?? val9(this._activeSequenceVariant.pointer);
7036
7558
  if (!this._sequences[variantId]) {
7037
- const lengthD = prism7(() => {
7038
- const sheetState = val7(
7559
+ const lengthD = prism8(() => {
7560
+ if (val9(this._sequenceMode.pointer) === "page") {
7561
+ return PAGE_MODE_SEQUENCE_LENGTH;
7562
+ }
7563
+ const sheetState = val9(
7039
7564
  this.project.pointers.historic.sheetsById[this.address.sheetId]
7040
7565
  );
7041
7566
  const unsanitized = getSequenceStateFromSheet(
@@ -7044,8 +7569,11 @@ var Sheet = class {
7044
7569
  )?.length;
7045
7570
  return sanitizeSequenceLength(unsanitized);
7046
7571
  });
7047
- const subUnitsPerUnitD = prism7(() => {
7048
- const sheetState = val7(
7572
+ const subUnitsPerUnitD = prism8(() => {
7573
+ if (val9(this._sequenceMode.pointer) === "page") {
7574
+ return PAGE_MODE_SUB_UNITS_PER_UNIT;
7575
+ }
7576
+ const sheetState = val9(
7049
7577
  this.project.pointers.historic.sheetsById[this.address.sheetId]
7050
7578
  );
7051
7579
  const unsanitized = getSequenceStateFromSheet(
@@ -7097,6 +7625,44 @@ var Sheet = class {
7097
7625
  }
7098
7626
  this._studioPreviewVariantOverride.set(variantId);
7099
7627
  }
7628
+ getSequenceMode() {
7629
+ return this._sequenceMode.get();
7630
+ }
7631
+ setSequenceMode(mode) {
7632
+ if (this._sequenceMode.get() === mode) return;
7633
+ this._sequenceMode.set(mode);
7634
+ this.syncPageScrollDriver();
7635
+ if (this._gsapBridgeDisposer) {
7636
+ this._gsapBridgeDisposer();
7637
+ this._gsapBridgeDisposer = attachGsapSequenceBridge(this.publicApi);
7638
+ }
7639
+ }
7640
+ enableGsapSequenceBridge() {
7641
+ if (this._gsapBridgeDisposer) return;
7642
+ this._gsapBridgeDisposer = attachGsapSequenceBridge(this.publicApi);
7643
+ this.syncPageScrollDriver();
7644
+ }
7645
+ disposeRuntimeIntegrations() {
7646
+ this._pageScrollDisposer?.();
7647
+ this._pageScrollDisposer = void 0;
7648
+ this._gsapBridgeDisposer?.();
7649
+ this._gsapBridgeDisposer = void 0;
7650
+ }
7651
+ syncPageScrollDriver() {
7652
+ this._pageScrollDisposer?.();
7653
+ this._pageScrollDisposer = void 0;
7654
+ if (this.getSequenceMode() === "page") {
7655
+ const driver = this._customPageScrollDriver ?? createNativeDocumentScrollDriver();
7656
+ this._pageScrollDisposer = attachSheetScrollDriver(this.publicApi, driver);
7657
+ }
7658
+ }
7659
+ setPageScrollDriver(driver) {
7660
+ this._customPageScrollDriver = driver;
7661
+ this.syncPageScrollDriver();
7662
+ }
7663
+ getPageScrollDriver() {
7664
+ return this._customPageScrollDriver;
7665
+ }
7100
7666
  };
7101
7667
  var sanitizeSequenceLength = (len) => typeof len === "number" && isFinite(len) && len > 0 ? len : 10;
7102
7668
  var sanitizeSequenceSubUnitsPerUnit = (subs) => typeof subs === "number" && isInteger_default(subs) && subs >= 1 && subs <= 1e3 ? subs : 30;
@@ -7107,13 +7673,13 @@ var SheetTemplate = class {
7107
7673
  this.project = project;
7108
7674
  __publicField(this, "type", "Theatre_SheetTemplate");
7109
7675
  __publicField(this, "address");
7110
- __publicField(this, "_instances", new Atom9({}));
7676
+ __publicField(this, "_instances", new Atom10({}));
7111
7677
  __publicField(this, "instancesP", this._instances.pointer);
7112
- __publicField(this, "_objectTemplates", new Atom9({}));
7678
+ __publicField(this, "_objectTemplates", new Atom10({}));
7113
7679
  __publicField(this, "objectTemplatesP", this._objectTemplates.pointer);
7114
7680
  __publicField(this, "_pendingOutlineNamespaces", {});
7115
7681
  __publicField(this, "_sequenceVariants", [DEFAULT_SEQUENCE_VARIANT]);
7116
- __publicField(this, "_visibleInOutline", new Atom9(true));
7682
+ __publicField(this, "_visibleInOutline", new Atom10(true));
7117
7683
  __publicField(this, "visibleInOutlineP", this._visibleInOutline.pointer);
7118
7684
  this.address = { ...project.address, sheetId };
7119
7685
  }
@@ -7196,7 +7762,7 @@ var SheetTemplate = class {
7196
7762
 
7197
7763
  // core/src/projects/Project.ts
7198
7764
  import { PointerProxy } from "@unseenco/theatre-dataverse";
7199
- import { Atom as Atom10 } from "@unseenco/theatre-dataverse";
7765
+ import { Atom as Atom11 } from "@unseenco/theatre-dataverse";
7200
7766
 
7201
7767
  // shared/src/utils/delay.ts
7202
7768
  var delay = (dur) => new Promise((resolve) => setTimeout(resolve, dur));
@@ -7634,8 +8200,165 @@ async function initialiseProjectState(studio, project, onDiskState) {
7634
8200
  });
7635
8201
  }
7636
8202
 
8203
+ // shared/src/sheets/pageScrollContext.ts
8204
+ var defaultPageScrollContext = {
8205
+ scroller: null,
8206
+ axis: "vertical"
8207
+ };
8208
+ var activePageScrollContext = defaultPageScrollContext;
8209
+ function setActivePageScrollContext(context) {
8210
+ activePageScrollContext = {
8211
+ scroller: context.scroller,
8212
+ axis: context.axis ?? "vertical"
8213
+ };
8214
+ }
8215
+ function getActivePageScrollContext() {
8216
+ return activePageScrollContext;
8217
+ }
8218
+ function resolvePageScrollAxis(context = defaultPageScrollContext) {
8219
+ return context.axis ?? "vertical";
8220
+ }
8221
+ function resolvePageScrollScroller(st, defaultsScroller) {
8222
+ const surface = st;
8223
+ const fromSt = surface.scroller ?? surface.vars?.scroller;
8224
+ if (fromSt != null) {
8225
+ return fromSt;
8226
+ }
8227
+ if (defaultsScroller !== void 0) {
8228
+ return defaultsScroller;
8229
+ }
8230
+ return null;
8231
+ }
8232
+ function isWindowLike(value) {
8233
+ return typeof Window !== "undefined" && (value === window || value instanceof Window);
8234
+ }
8235
+ function isNativeDocumentScroller(scroller) {
8236
+ if (scroller == null) return true;
8237
+ if (isWindowLike(scroller)) return true;
8238
+ if (typeof document === "undefined") return false;
8239
+ return scroller === document.documentElement || scroller === document.body;
8240
+ }
8241
+ function pageScrollScrollersMatch(a2, b2) {
8242
+ if (a2 === b2) return true;
8243
+ if (isNativeDocumentScroller(a2) && isNativeDocumentScroller(b2)) return true;
8244
+ return false;
8245
+ }
8246
+ function isScrollTriggerHorizontal(st) {
8247
+ const surface = st;
8248
+ return surface.horizontal === true || surface.vars?.horizontal === true;
8249
+ }
8250
+ function isPageScrollTrigger(st, context = defaultPageScrollContext) {
8251
+ const axis = resolvePageScrollAxis(context);
8252
+ const stHorizontal = isScrollTriggerHorizontal(st);
8253
+ if (stHorizontal !== (axis === "horizontal")) {
8254
+ return false;
8255
+ }
8256
+ const stScroller = resolvePageScrollScroller(st, context.scroller);
8257
+ return pageScrollScrollersMatch(stScroller, context.scroller);
8258
+ }
8259
+ function isVerticalPageScrollTrigger(st, context = defaultPageScrollContext) {
8260
+ return isPageScrollTrigger(st, {
8261
+ scroller: context.scroller,
8262
+ axis: "vertical"
8263
+ });
8264
+ }
8265
+
8266
+ // shared/src/gsap/scrollTriggerLayout.ts
8267
+ function getNativeDocumentMaxScroll(axis = "vertical") {
8268
+ if (typeof document === "undefined" || typeof window === "undefined") {
8269
+ return 0;
8270
+ }
8271
+ const el = document.documentElement;
8272
+ if (axis === "horizontal") {
8273
+ const scrolling = document.scrollingElement ?? el;
8274
+ return Math.max(0, scrolling.scrollWidth - scrolling.clientWidth);
8275
+ }
8276
+ return Math.max(0, el.scrollHeight - window.innerHeight);
8277
+ }
8278
+ function getMaxScrollForScroller(scroller, axis = "vertical") {
8279
+ if (typeof document === "undefined" || typeof window === "undefined") {
8280
+ return 0;
8281
+ }
8282
+ if (isNativeDocumentScroller(scroller)) {
8283
+ return getNativeDocumentMaxScroll(axis);
8284
+ }
8285
+ const el = scroller;
8286
+ if (axis === "horizontal") {
8287
+ return Math.max(0, el.scrollWidth - el.clientWidth);
8288
+ }
8289
+ return Math.max(0, el.scrollHeight - el.clientHeight);
8290
+ }
8291
+ function getMaxScrollForPageScrollContext(scroller, axis = "vertical") {
8292
+ return getMaxScrollForScroller(scroller, axis);
8293
+ }
8294
+
8295
+ // shared/src/gsap/domElementHighlightTarget.ts
8296
+ function resolveDomElementHighlightTarget(target) {
8297
+ if (typeof document === "undefined") {
8298
+ return null;
8299
+ }
8300
+ if (target.type === "id") {
8301
+ return document.getElementById(target.id);
8302
+ }
8303
+ try {
8304
+ const match = document.querySelector(target.selector);
8305
+ return match instanceof Element ? match : null;
8306
+ } catch {
8307
+ return null;
8308
+ }
8309
+ }
8310
+
8311
+ // shared/src/sheets/remoteDomElementHighlight.ts
8312
+ var listeners = /* @__PURE__ */ new Set();
8313
+ function setRemoteDomElementHighlight(element) {
8314
+ for (const listener of listeners) {
8315
+ listener(element);
8316
+ }
8317
+ }
8318
+
8319
+ // shared/src/sheets/remotePageScrollMetrics.ts
8320
+ var remoteMetrics;
8321
+ var listeners2 = /* @__PURE__ */ new Set();
8322
+ function setRemotePageScrollMetrics(metrics) {
8323
+ const prev = remoteMetrics;
8324
+ remoteMetrics = metrics;
8325
+ if (prev?.maxScroll !== metrics?.maxScroll || prev?.axis !== metrics?.axis) {
8326
+ for (const listener of listeners2) {
8327
+ listener();
8328
+ }
8329
+ }
8330
+ }
8331
+
7637
8332
  // core/src/internal/RemoteSync.ts
7638
- import { val as val8 } from "@unseenco/theatre-dataverse";
8333
+ import { pointerToPrism as pointerToPrism2, val as val10 } from "@unseenco/theatre-dataverse";
8334
+
8335
+ // core/src/internal/createDebouncedCallback.ts
8336
+ function createDebouncedCallback(fn2, delayMs) {
8337
+ let timeoutId;
8338
+ const clearPending = () => {
8339
+ if (timeoutId !== void 0) {
8340
+ clearTimeout(timeoutId);
8341
+ timeoutId = void 0;
8342
+ }
8343
+ };
8344
+ return {
8345
+ schedule() {
8346
+ clearPending();
8347
+ timeoutId = setTimeout(() => {
8348
+ timeoutId = void 0;
8349
+ fn2();
8350
+ }, delayMs);
8351
+ },
8352
+ flush() {
8353
+ if (timeoutId === void 0) return;
8354
+ clearPending();
8355
+ fn2();
8356
+ },
8357
+ cancel() {
8358
+ clearPending();
8359
+ }
8360
+ };
8361
+ }
7639
8362
 
7640
8363
  // core/src/internal/remoteEditor.ts
7641
8364
  function isRemoteEditorWindow() {
@@ -7643,6 +8366,10 @@ function isRemoteEditorWindow() {
7643
8366
  }
7644
8367
 
7645
8368
  // core/src/internal/RemoteSync.ts
8369
+ var REMOTE_HISTORIC_SYNC_DEBOUNCE_MS = 100;
8370
+ function shouldSyncPageScrollWhenApplyingTimelineUpdate(isEditor, sequenceMode) {
8371
+ return !isEditor && sequenceMode === "page";
8372
+ }
7646
8373
  var RemoteSync = class {
7647
8374
  constructor(project) {
7648
8375
  this.project = project;
@@ -7653,34 +8380,37 @@ var RemoteSync = class {
7653
8380
  __publicField(this, "objectUnsubs", /* @__PURE__ */ new Map());
7654
8381
  __publicField(this, "studio");
7655
8382
  __publicField(this, "activeSheet");
8383
+ __publicField(this, "historicSyncDebounce");
8384
+ __publicField(this, "lastBroadcastHistoricFingerprint");
8385
+ __publicField(this, "historicSyncUnsubs", []);
8386
+ __publicField(this, "remoteEditorActive", false);
8387
+ __publicField(this, "suppressTimelineBroadcast", false);
8388
+ __publicField(this, "listenerTimelineUnsub");
8389
+ __publicField(this, "listenerMetricsUnsubs", []);
8390
+ __publicField(this, "broadcastPageScrollMetrics");
7656
8391
  if (typeof BroadcastChannel === "undefined") return;
7657
8392
  this.channel = new BroadcastChannel(
7658
8393
  "theatre-remote:".concat(project.address.projectId)
7659
8394
  );
8395
+ this.channel.onmessage = (event) => {
8396
+ this._handleIncoming(event.data);
8397
+ };
7660
8398
  if (!this.isEditor) {
7661
- this.channel.onmessage = (event) => {
7662
- this._handleIncoming(event.data);
7663
- };
8399
+ this.listenerTimelineUnsub = onPageScrollDrivenSequencePosition(
8400
+ (sheet, position) => {
8401
+ this._broadcastTimelineFromListener(sheet, position);
8402
+ }
8403
+ );
7664
8404
  } else {
7665
8405
  const channel = this.channel;
7666
8406
  window.addEventListener("pagehide", () => {
7667
- const data = {};
7668
- if (this.studio) {
7669
- const projectId = this.project.address.projectId;
7670
- const historic = val8(
7671
- this.studio.atomP.historic.coreByProject[projectId]
7672
- );
7673
- const ahistoric = val8(
7674
- this.studio.atomP.ahistoric.coreByProject[projectId]
7675
- );
7676
- if (historic) {
7677
- data.historic = JSON.parse(JSON.stringify(historic));
7678
- if (ahistoric) {
7679
- data.ahistoric = JSON.parse(JSON.stringify(ahistoric));
7680
- }
7681
- }
8407
+ this.historicSyncDebounce?.flush();
8408
+ const data = this._readHistoricSnapshotFromStudio();
8409
+ if (data && this.lastBroadcastHistoricFingerprint === fingerprintHistoricSnapshot(data)) {
8410
+ channel.postMessage({ event: "disconnect", data: {} });
8411
+ return;
7682
8412
  }
7683
- channel.postMessage({ event: "disconnect", data });
8413
+ channel.postMessage({ event: "disconnect", data: data ?? {} });
7684
8414
  });
7685
8415
  }
7686
8416
  }
@@ -7731,8 +8461,13 @@ var RemoteSync = class {
7731
8461
  }
7732
8462
  attachStudio(studio) {
7733
8463
  this.studio = studio;
7734
- if (!this.isEditor || !this.channel) return;
8464
+ if (!this.channel) return;
8465
+ if (!this.isEditor) {
8466
+ this._attachListenerPageScrollMetricsBroadcast();
8467
+ return;
8468
+ }
7735
8469
  const channel = this.channel;
8470
+ channel.postMessage({ event: "editorHello", data: {} });
7736
8471
  const projectId = this.project.address.projectId;
7737
8472
  studio.publicApi.onSelectionChange((selection) => {
7738
8473
  for (const item of selection) {
@@ -7757,7 +8492,7 @@ var RemoteSync = class {
7757
8492
  let lastPosition;
7758
8493
  const ticker = getCoreTicker();
7759
8494
  const pollTimelinePosition = () => {
7760
- if (this.activeSheet) {
8495
+ if (this.activeSheet && !this.suppressTimelineBroadcast) {
7761
8496
  const position = this.activeSheet.publicApi.sequence.position;
7762
8497
  if (position !== lastPosition) {
7763
8498
  lastPosition = position;
@@ -7771,9 +8506,117 @@ var RemoteSync = class {
7771
8506
  ticker.onNextTick(pollTimelinePosition);
7772
8507
  };
7773
8508
  ticker.onNextTick(pollTimelinePosition);
8509
+ this.historicSyncDebounce = createDebouncedCallback(() => {
8510
+ this._broadcastHistoricSnapshot();
8511
+ }, REMOTE_HISTORIC_SYNC_DEBOUNCE_MS);
8512
+ const scheduleHistoricSync = () => {
8513
+ this.historicSyncDebounce?.schedule();
8514
+ };
8515
+ void studio.initialized.then(() => {
8516
+ this.historicSyncUnsubs.push(
8517
+ pointerToPrism2(studio.atomP.historic.coreByProject[projectId]).onChange(
8518
+ studio.ticker,
8519
+ scheduleHistoricSync,
8520
+ true
8521
+ ),
8522
+ pointerToPrism2(
8523
+ studio.atomP.ahistoric.coreByProject[projectId]
8524
+ ).onChange(studio.ticker, scheduleHistoricSync, true)
8525
+ );
8526
+ });
8527
+ }
8528
+ _readHistoricSnapshotFromStudio() {
8529
+ if (!this.studio) return void 0;
8530
+ const projectId = this.project.address.projectId;
8531
+ const historic = val10(
8532
+ this.studio.atomP.historic.coreByProject[projectId]
8533
+ );
8534
+ if (!historic) return void 0;
8535
+ const data = {
8536
+ historic: JSON.parse(JSON.stringify(historic))
8537
+ };
8538
+ const ahistoric = val10(
8539
+ this.studio.atomP.ahistoric.coreByProject[projectId]
8540
+ );
8541
+ if (ahistoric) {
8542
+ data.ahistoric = JSON.parse(JSON.stringify(ahistoric));
8543
+ }
8544
+ return data;
8545
+ }
8546
+ _broadcastHistoricSnapshot(options) {
8547
+ if (!this.channel) return;
8548
+ const data = this._readHistoricSnapshotFromStudio();
8549
+ if (!data) return;
8550
+ const fingerprint = fingerprintHistoricSnapshot(data);
8551
+ if (!options?.force && fingerprint === this.lastBroadcastHistoricFingerprint) {
8552
+ return;
8553
+ }
8554
+ this.lastBroadcastHistoricFingerprint = fingerprint;
8555
+ const message = { event: "updateHistoric", data };
8556
+ this.channel.postMessage(message);
8557
+ }
8558
+ _applyHistoricSnapshot(historic, ahistoric) {
8559
+ if (!this.studio) return;
8560
+ const projectId = this.project.address.projectId;
8561
+ this.studio.transaction(({ drafts }) => {
8562
+ drafts.historic.coreByProject[projectId] = historic;
8563
+ if (ahistoric) {
8564
+ drafts.ahistoric.coreByProject[projectId] = ahistoric;
8565
+ }
8566
+ drafts.ephemeral.coreByProject[projectId].loadingState = {
8567
+ type: "loaded"
8568
+ };
8569
+ });
8570
+ }
8571
+ _broadcastTimelineFromListener(sheet, position) {
8572
+ if (this.isEditor || !this.channel || !this.remoteEditorActive || this.suppressTimelineBroadcast) {
8573
+ return;
8574
+ }
8575
+ const registered = this.sheets.get(sheet.address.sheetId);
8576
+ if (!registered || registered.getSequenceMode() !== "page") return;
8577
+ const message = {
8578
+ event: "updateTimeline",
8579
+ data: { sheet: sheet.address.sheetId, position }
8580
+ };
8581
+ this.channel.postMessage(message);
8582
+ }
8583
+ _postPageScrollMetrics() {
8584
+ if (!this.remoteEditorActive || !this.channel) return;
8585
+ const ctx = getActivePageScrollContext();
8586
+ const axis = resolvePageScrollAxis(ctx);
8587
+ const maxScroll = getMaxScrollForPageScrollContext(ctx.scroller, axis);
8588
+ const message = {
8589
+ event: "pageScrollMetrics",
8590
+ data: { maxScroll, axis }
8591
+ };
8592
+ this.channel.postMessage(message);
8593
+ }
8594
+ _attachListenerPageScrollMetricsBroadcast() {
8595
+ if (this.isEditor || !this.channel) return;
8596
+ const broadcast = () => this._postPageScrollMetrics();
8597
+ this.broadcastPageScrollMetrics = broadcast;
8598
+ const onScroll = () => broadcast();
8599
+ document.addEventListener("scroll", onScroll, { passive: true, capture: true });
8600
+ const onResize = () => broadcast();
8601
+ window.addEventListener("resize", onResize);
8602
+ const ScrollTrigger = globalThis.ScrollTrigger;
8603
+ ScrollTrigger?.addEventListener?.("refresh", broadcast);
8604
+ broadcast();
8605
+ this.listenerMetricsUnsubs.push(() => {
8606
+ document.removeEventListener("scroll", onScroll, { capture: true });
8607
+ window.removeEventListener("resize", onResize);
8608
+ ScrollTrigger?.removeEventListener?.("refresh", broadcast);
8609
+ });
7774
8610
  }
7775
8611
  _handleIncoming(msg) {
7776
8612
  switch (msg.event) {
8613
+ case "editorHello": {
8614
+ if (this.isEditor) break;
8615
+ this.remoteEditorActive = true;
8616
+ this.broadcastPageScrollMetrics?.();
8617
+ this._broadcastHistoricSnapshot({ force: true });
8618
+ break;
8619
+ }
7777
8620
  case "setSheet": {
7778
8621
  const sheet = this.sheets.get(msg.data.sheet);
7779
8622
  if (sheet && this.studio) {
@@ -7798,32 +8641,67 @@ var RemoteSync = class {
7798
8641
  const sheet = this.sheets.get(msg.data.sheet);
7799
8642
  if (sheet) {
7800
8643
  this.activeSheet = sheet;
8644
+ this.suppressTimelineBroadcast = true;
7801
8645
  sheet.publicApi.sequence.position = msg.data.position;
8646
+ if (shouldSyncPageScrollWhenApplyingTimelineUpdate(
8647
+ this.isEditor,
8648
+ sheet.getSequenceMode()
8649
+ )) {
8650
+ syncPageScrollToSequencePosition(sheet.publicApi);
8651
+ }
8652
+ requestAnimationFrame(() => {
8653
+ this.suppressTimelineBroadcast = false;
8654
+ });
8655
+ }
8656
+ break;
8657
+ }
8658
+ case "pageScrollMetrics": {
8659
+ if (!this.isEditor) break;
8660
+ const { maxScroll, axis } = msg.data;
8661
+ setRemotePageScrollMetrics({ maxScroll, axis });
8662
+ break;
8663
+ }
8664
+ case "highlightDomTarget": {
8665
+ if (this.isEditor || !this.remoteEditorActive) break;
8666
+ const target = msg.data.target;
8667
+ const element = resolveDomElementHighlightTarget(target);
8668
+ setRemoteDomElementHighlight(element);
8669
+ break;
8670
+ }
8671
+ case "clearDomHighlight": {
8672
+ if (this.isEditor || !this.remoteEditorActive) break;
8673
+ setRemoteDomElementHighlight(null);
8674
+ break;
8675
+ }
8676
+ case "updateHistoric": {
8677
+ const { historic, ahistoric } = msg.data;
8678
+ if (historic) {
8679
+ this._applyHistoricSnapshot(historic, ahistoric);
7802
8680
  }
7803
8681
  break;
7804
8682
  }
7805
8683
  case "disconnect": {
7806
8684
  const { historic, ahistoric } = msg.data;
7807
- if (historic && this.studio) {
7808
- const projectId = this.project.address.projectId;
7809
- this.studio.transaction(({ drafts }) => {
7810
- drafts.historic.coreByProject[projectId] = historic;
7811
- if (ahistoric) {
7812
- drafts.ahistoric.coreByProject[projectId] = ahistoric;
7813
- }
7814
- drafts.ephemeral.coreByProject[projectId].loadingState = {
7815
- type: "loaded"
7816
- };
7817
- });
8685
+ if (historic) {
8686
+ this._applyHistoricSnapshot(historic, ahistoric);
7818
8687
  }
7819
8688
  for (const obj of this.objects.values()) {
7820
8689
  obj.setRemoteOverride({});
7821
8690
  }
8691
+ if (!this.isEditor) {
8692
+ this.remoteEditorActive = false;
8693
+ setRemoteDomElementHighlight(null);
8694
+ } else {
8695
+ setRemotePageScrollMetrics(void 0);
8696
+ }
7822
8697
  break;
7823
8698
  }
7824
8699
  }
7825
8700
  }
7826
8701
  };
8702
+ function fingerprintHistoricSnapshot(data) {
8703
+ return JSON.stringify(data);
8704
+ }
7827
8705
 
7828
8706
  // core/src/_coreLogger.ts
7829
8707
  function noop2() {
@@ -7850,7 +8728,7 @@ function _coreLogger(config) {
7850
8728
  }
7851
8729
 
7852
8730
  // shared/src/utils/assets.ts
7853
- import { val as val9 } from "@unseenco/theatre-dataverse";
8731
+ import { val as val11 } from "@unseenco/theatre-dataverse";
7854
8732
 
7855
8733
  // shared/src/utils/forEachDeep.ts
7856
8734
  function forEachPropDeep(m, fn2, startingPath = []) {
@@ -7920,7 +8798,7 @@ var Project = class {
7920
8798
  __publicField(this, "_studioReadyDeferred");
7921
8799
  __publicField(this, "_assetStorageReadyDeferred");
7922
8800
  __publicField(this, "_readyPromise");
7923
- __publicField(this, "_sheetTemplates", new Atom10({}));
8801
+ __publicField(this, "_sheetTemplates", new Atom11({}));
7924
8802
  __publicField(this, "sheetTemplatesP", this._sheetTemplates.pointer);
7925
8803
  __publicField(this, "_studio");
7926
8804
  __publicField(this, "_onDiskStateAtom");
@@ -7933,7 +8811,7 @@ var Project = class {
7933
8811
  this._logger.traceDev("creating project");
7934
8812
  this.address = { projectId: id };
7935
8813
  this._remoteSync = new RemoteSync(this);
7936
- const onDiskStateAtom = new Atom10({
8814
+ const onDiskStateAtom = new Atom11({
7937
8815
  ahistoric: {
7938
8816
  ahistoricStuff: ""
7939
8817
  },
@@ -7988,14 +8866,14 @@ var Project = class {
7988
8866
  if (typeof window === "undefined") {
7989
8867
  if (process.env.NODE_ENV === "production") {
7990
8868
  console.error(
7991
- 'Argument config.state in Theatre.getProject("'.concat(id, '", config) is empty. ') + "You can safely ignore this message if you're developing a Next.js/Remix project in development mode. But if you are shipping to your end-users, then you need to set config.state, otherwise your project's state will be empty and nothing will animate. Learn more at https://www.theatrejs.com/docs/latest/manual/projects#state"
8869
+ 'Argument config.state in Theatre.getProject("'.concat(id, '", config) is empty. ') + "You can safely ignore this message if you're developing a Next.js/Remix project in development mode. But if you are shipping to your end-users, then you need to set config.state, otherwise your project's state will be empty and nothing will animate. Learn more at https://unseen-theatre.netlify.app/docs/guide/manual/projects#state"
7992
8870
  );
7993
8871
  }
7994
8872
  } else {
7995
8873
  setTimeout(() => {
7996
8874
  if (!this._studio) {
7997
8875
  throw new Error(
7998
- 'Argument config.state in Theatre.getProject("'.concat(id, '", config) is empty. This is fine ') + "while you are using @unseenco/theatre-core along with @unseenco/theatre-studio. But since @unseenco/theatre-studio " + 'is not loaded, the state of project "'.concat(id, '" will be empty.\n\n') + "To fix this, you need to add @unseenco/theatre-studio into the bundle and export the project's state. Learn how to do that at https://www.theatrejs.com/docs/latest/manual/projects#state\n"
8876
+ 'Argument config.state in Theatre.getProject("'.concat(id, '", config) is empty. This is fine ') + "while you are using @unseenco/theatre-core along with @unseenco/theatre-studio. But since @unseenco/theatre-studio " + 'is not loaded, the state of project "'.concat(id, '" will be empty.\n\n') + "To fix this, you need to add @unseenco/theatre-studio into the bundle and export the project's state. Learn how to do that at https://unseen-theatre.netlify.app/docs/guide/manual/projects#state\n"
7999
8877
  );
8000
8878
  }
8001
8879
  }, 1e3);
@@ -8059,6 +8937,7 @@ var Project = class {
8059
8937
  template.setVisibleInOutline(opts.visible);
8060
8938
  }
8061
8939
  const sheet = template.getInstance(instanceId);
8940
+ applySheetRuntimeOptions(sheet, opts);
8062
8941
  this._remoteSync.registerSheet(sheet);
8063
8942
  return sheet;
8064
8943
  }
@@ -8339,13 +9218,13 @@ var Project = class {
8339
9218
  };
8340
9219
 
8341
9220
  // shared/src/utils/sanitizers.ts
8342
- var _validateSym = (val11, thingy, range) => {
8343
- if (typeof val11 !== "string") {
8344
- return "".concat(thingy, " must be a string. ").concat(userReadableTypeOfValue_default(val11), " given.");
8345
- } else if (val11.trim().length !== val11.length) {
8346
- return "".concat(thingy, " must not have leading or trailing spaces. '").concat(val11, "' given.");
8347
- } else if (val11.length < range[0] || val11.length > range[1]) {
8348
- return "".concat(thingy, " must have between ").concat(range[0], " and ").concat(range[1], " characters. '").concat(val11, "' given.");
9221
+ var _validateSym = (val13, thingy, range) => {
9222
+ if (typeof val13 !== "string") {
9223
+ return "".concat(thingy, " must be a string. ").concat(userReadableTypeOfValue_default(val13), " given.");
9224
+ } else if (val13.trim().length !== val13.length) {
9225
+ return "".concat(thingy, " must not have leading or trailing spaces. '").concat(val13, "' given.");
9226
+ } else if (val13.length < range[0] || val13.length > range[1]) {
9227
+ return "".concat(thingy, " must have between ").concat(range[0], " and ").concat(range[1], " characters. '").concat(val13, "' given.");
8349
9228
  }
8350
9229
  };
8351
9230
  var validateName = (name, thingy, shouldThrow = false) => {
@@ -8462,7 +9341,61 @@ var TheatreProject = class {
8462
9341
  // core/src/coreExports.ts
8463
9342
  var import_fast_deep_equal3 = __toESM(require_fast_deep_equal());
8464
9343
  import { isPointer } from "@unseenco/theatre-dataverse";
8465
- import { isPrism, pointerToPrism } from "@unseenco/theatre-dataverse";
9344
+ import { isPrism, pointerToPrism as pointerToPrism3 } from "@unseenco/theatre-dataverse";
9345
+
9346
+ // core/src/sheets/theatrePageScroll.ts
9347
+ var pageScrollConfig = {};
9348
+ function buildPageScrollContextFromConfig() {
9349
+ return {
9350
+ scroller: pageScrollConfig.scroller ?? defaultPageScrollContext.scroller,
9351
+ axis: pageScrollConfig.axis ?? defaultPageScrollContext.axis
9352
+ };
9353
+ }
9354
+ function configureTheatrePageScroll(config) {
9355
+ const prev = pageScrollConfig;
9356
+ pageScrollConfig = {
9357
+ scroller: config.scroller ?? prev.scroller,
9358
+ axis: config.axis ?? prev.axis
9359
+ };
9360
+ if (config.scroller !== void 0 || config.axis !== void 0) {
9361
+ setActivePageScrollContext(buildPageScrollContextFromConfig());
9362
+ }
9363
+ return {
9364
+ reset() {
9365
+ pageScrollConfig = prev;
9366
+ setActivePageScrollContext(
9367
+ prev.scroller !== void 0 || prev.axis !== void 0 ? {
9368
+ scroller: prev.scroller ?? defaultPageScrollContext.scroller,
9369
+ axis: prev.axis ?? defaultPageScrollContext.axis
9370
+ } : defaultPageScrollContext
9371
+ );
9372
+ }
9373
+ };
9374
+ }
9375
+ function getTheatrePageScrollConfig() {
9376
+ return pageScrollConfig;
9377
+ }
9378
+ function getTheatrePageScrollContext() {
9379
+ const ctx = buildPageScrollContextFromConfig();
9380
+ setActivePageScrollContext(ctx);
9381
+ return ctx;
9382
+ }
9383
+ function createDefaultPageScrollDriver(scroller = getTheatrePageScrollContext().scroller, axis = resolvePageScrollAxis(getTheatrePageScrollContext())) {
9384
+ if (typeof document !== "undefined" && scroller != null && !isNativeDocumentScroller(scroller) && scroller instanceof HTMLElement) {
9385
+ return axis === "horizontal" ? createElementHorizontalScrollDriver(scroller) : createElementScrollDriver(scroller);
9386
+ }
9387
+ return axis === "horizontal" ? createNativeDocumentHorizontalScrollDriver() : createNativeDocumentScrollDriver();
9388
+ }
9389
+ function attachTheatrePageScroll(sheet, options = {}) {
9390
+ const ctx = getTheatrePageScrollContext();
9391
+ const driver = options.driver ?? createDefaultPageScrollDriver(ctx.scroller, resolvePageScrollAxis(ctx));
9392
+ sheet.setPageScrollDriver(driver);
9393
+ return () => {
9394
+ sheet.setPageScrollDriver(void 0);
9395
+ };
9396
+ }
9397
+
9398
+ // core/src/coreExports.ts
8466
9399
  function getProject(id, config = {}) {
8467
9400
  const existingProject = projectsSingleton_default.get(id);
8468
9401
  if (existingProject) {
@@ -8503,7 +9436,7 @@ var shallowValidateOnDiskState = (projectId, s2) => {
8503
9436
  throw new InvalidArgumentError(
8504
9437
  "Error validating conf.state in Theatre.getProject(".concat(JSON.stringify(
8505
9438
  projectId
8506
- ), ", conf). The state seems to be formatted in a way that is unreadable to Theatre.js. Read more at https://www.theatrejs.com/docs/latest/manual/projects#state")
9439
+ ), ", conf). The state seems to be formatted in a way that is unreadable to Theatre.js. Read more at https://unseen-theatre.netlify.app/docs/guide/manual/projects#state")
8507
9440
  );
8508
9441
  }
8509
9442
  };
@@ -8543,7 +9476,7 @@ var validateProjectIdOrThrow = (value) => {
8543
9476
  function onChange(pointer3, callback, rafDriver) {
8544
9477
  const ticker = rafDriver ? privateAPI(rafDriver).ticker : getCoreTicker();
8545
9478
  if (isPointer(pointer3)) {
8546
- const pr = pointerToPrism(pointer3);
9479
+ const pr = pointerToPrism3(pointer3);
8547
9480
  return pr.onChange(ticker, callback, true);
8548
9481
  } else if (isPrism(pointer3)) {
8549
9482
  return pointer3.onChange(ticker, callback, true);
@@ -8553,9 +9486,9 @@ function onChange(pointer3, callback, rafDriver) {
8553
9486
  );
8554
9487
  }
8555
9488
  }
8556
- function val10(pointer3) {
9489
+ function val12(pointer3) {
8557
9490
  if (isPointer(pointer3)) {
8558
- return pointerToPrism(pointer3).getValue();
9491
+ return pointerToPrism3(pointer3).getValue();
8559
9492
  } else {
8560
9493
  throw new Error("Called val(p) where p is not a pointer.");
8561
9494
  }
@@ -8570,7 +9503,7 @@ var CoreBundle = class {
8570
9503
  return "Theatre_CoreBundle";
8571
9504
  }
8572
9505
  get version() {
8573
- return "0.4.2";
9506
+ return "0.5.0";
8574
9507
  }
8575
9508
  getBitsForStudio(studio, callback) {
8576
9509
  if (this._studio) {
@@ -8619,16 +9552,44 @@ function registerCoreBundle() {
8619
9552
  }
8620
9553
  }
8621
9554
  export {
9555
+ PAGE_MODE_SEQUENCE_LENGTH,
9556
+ PAGE_MODE_SUB_UNITS_PER_UNIT,
9557
+ attachGsapSequenceBridge,
9558
+ attachSheetScrollDriver,
9559
+ attachTheatrePageScroll,
9560
+ configureTheatrePageScroll,
9561
+ createDefaultPageScrollDriver,
9562
+ createElementHorizontalScrollDriver,
9563
+ createElementScrollDriver,
9564
+ createNativeDocumentHorizontalScrollDriver,
9565
+ createNativeDocumentScrollDriver,
8622
9566
  createRafDriver,
9567
+ defaultPageScrollContext,
9568
+ getActivePageScrollContext,
8623
9569
  getProject,
9570
+ getSheetScrollDriver,
9571
+ getTheatreCoreRafDriver,
9572
+ getTheatrePageScrollConfig,
9573
+ getTheatrePageScrollContext,
9574
+ isNativeDocumentScroller,
9575
+ isPageScrollTrigger,
8624
9576
  isRemoteEditorWindow,
9577
+ isVerticalPageScrollTrigger,
8625
9578
  notify,
8626
9579
  onChange,
9580
+ pageScrollProgressFromSequence,
9581
+ pageScrollScrollersMatch,
8627
9582
  privateAPI,
9583
+ resolvePageScrollAxis,
9584
+ resolvePageScrollScroller,
9585
+ setActivePageScrollContext,
8628
9586
  setCoreRafDriver,
9587
+ setPageScrollProgress,
8629
9588
  setPrivateAPI,
9589
+ syncNativeDocumentScrollToSequencePosition,
9590
+ syncPageScrollToSequencePosition,
8630
9591
  propTypes_exports as types,
8631
- val10 as val
9592
+ val12 as val
8632
9593
  };
8633
9594
  /*! Bundled license information:
8634
9595