@vectojs/core 1.35.2 → 1.36.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.
@@ -1241,8 +1241,8 @@ var MSDFTextEntity = class extends Entity {
1241
1241
  *
1242
1242
  * Measured on Chromium and Firefox (2026-07-31) with a 600 ms atlas: the
1243
1243
  * scene's own rAF loop never uploaded a decoded atlas in EITHER render mode.
1244
- * `onDemand` skips idle frames outright; `always` throttles to 2 FPS when
1245
- * idle, so whether it recovers is down to whether a throttled tick happens to
1244
+ * `onDemand` skips idle frames outright; `always` throttles to the idle FPS
1245
+ * floor when idle, so whether it recovers is down to whether a throttled tick happens to
1246
1246
  * land after the decode — Chromium got one, Firefox did not. Neither is a
1247
1247
  * mechanism, which is why this listener exists rather than relying on the
1248
1248
  * frame loop to come back around.
@@ -1243,8 +1243,8 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1243
1243
  *
1244
1244
  * Measured on Chromium and Firefox (2026-07-31) with a 600 ms atlas: the
1245
1245
  * scene's own rAF loop never uploaded a decoded atlas in EITHER render mode.
1246
- * `onDemand` skips idle frames outright; `always` throttles to 2 FPS when
1247
- * idle, so whether it recovers is down to whether a throttled tick happens to
1246
+ * `onDemand` skips idle frames outright; `always` throttles to the idle FPS
1247
+ * floor when idle, so whether it recovers is down to whether a throttled tick happens to
1248
1248
  * land after the decode — Chromium got one, Firefox did not. Neither is a
1249
1249
  * mechanism, which is why this listener exists rather than relying on the
1250
1250
  * frame loop to come back around.
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ var _chunkMO24PVGTjs = require('./chunk-MO24PVGT.js');
19
19
 
20
20
 
21
21
 
22
- var _chunkXHPUCU5Pjs = require('./chunk-XHPUCU5P.js');
22
+ var _chunkXE6K7T5Sjs = require('./chunk-XE6K7T5S.js');
23
23
 
24
24
  // src/tree/ComputeParticleEntity.ts
25
25
  var PARTICLE_STRIDE_FLOATS = 8;
@@ -31,7 +31,7 @@ var PARTICLE_OFFSET_ORIGIN_X = 4;
31
31
  var PARTICLE_OFFSET_ORIGIN_Y = 5;
32
32
  var PARTICLE_OFFSET_SIZE = 6;
33
33
  var PARTICLE_OFFSET_LIFE = 7;
34
- var ComputeParticleEntity = (_class = class extends _chunkXHPUCU5Pjs.Entity {
34
+ var ComputeParticleEntity = (_class = class extends _chunkXE6K7T5Sjs.Entity {
35
35
 
36
36
 
37
37
 
@@ -179,8 +179,8 @@ var ComputeParticleEntity = (_class = class extends _chunkXHPUCU5Pjs.Entity {
179
179
  * render-tree walk, but this entity is still a normal tree member that
180
180
  * walk visits — without this override, the base `Entity.hasPendingAnimations()`
181
181
  * (always `false`) is what Scene sees, so `renderMode: 'always'`'s idle
182
- * auto-throttle drops the WHOLE scene to ~2fps the instant nothing else in
183
- * the tree is animating, even while thousands of particles are visibly
182
+ * auto-throttle drops the WHOLE scene to the idle FPS floor the instant
183
+ * nothing else in the tree is animating, even while thousands of particles are visibly
184
184
  * drifting or spring-settling. A spring+damping system asymptotically
185
185
  * approaches zero velocity but never reaches it exactly, so this checks
186
186
  * against a small epsilon rather than `!== 0` — otherwise this would
@@ -3436,6 +3436,7 @@ var SCENE_OPTION_KEYS = [
3436
3436
  "contentSemanticMargin",
3437
3437
  "debugA11y",
3438
3438
  "disableWindowResize",
3439
+ "idleFPS",
3439
3440
  "maxDPR",
3440
3441
  "maxFPS",
3441
3442
  "particleBackend",
@@ -3864,29 +3865,38 @@ var Scene = (_class15 = class _Scene {
3864
3865
  if (value) this._dirty.mark(void 0, this.currentFrame);
3865
3866
  else this._dirty.clear();
3866
3867
  }
3867
- /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
3868
+ /**
3869
+ * Whether to throttle the `'always'` loop when the scene is static to save
3870
+ * power. The idle floor is {@link Scene.idleFPS} (default 60).
3871
+ */
3868
3872
  __init90() {this.autoThrottle = true}
3873
+ /**
3874
+ * Frame-rate floor for an idle `'always'` scene when {@link Scene.autoThrottle}
3875
+ * is on. Default `60`. Set `2` for the legacy aggressive idle sleep, `0` to
3876
+ * keep the scene's `maxFPS` cadence while idle.
3877
+ */
3878
+ __init91() {this.idleFPS = 60}
3869
3879
  // --- Frame telemetry (read via `frameStats`) ---------------------------
3870
3880
  /** Wall-clock ms spent inside the last `render()` call. */
3871
- __init91() {this._lastFrameMs = 0}
3881
+ __init92() {this._lastFrameMs = 0}
3872
3882
  /** Rolling exponential average of rendered-frame intervals, in ms. */
3873
- __init92() {this._avgFrameIntervalMs = 0}
3883
+ __init93() {this._avgFrameIntervalMs = 0}
3874
3884
  /** dt (ms) handed to the last rendered frame. */
3875
- __init93() {this._lastDt = 0}
3885
+ __init94() {this._lastDt = 0}
3876
3886
  /** Count of frames actually rendered since the loop started. */
3877
- __init94() {this._renderedFrames = 0}
3887
+ __init95() {this._renderedFrames = 0}
3878
3888
  /** Count of rAF ticks skipped (idle / capped) since the loop started. */
3879
- __init95() {this._skippedFrames = 0}
3889
+ __init96() {this._skippedFrames = 0}
3880
3890
  /** `time` of the previous *rendered* frame, for interval measurement. */
3881
- __init96() {this._lastRenderTick = 0}
3891
+ __init97() {this._lastRenderTick = 0}
3882
3892
  /**
3883
3893
  * Frame-rate cap (power saving). `0` = uncapped (native refresh). When set,
3884
3894
  * the loop renders at most `maxFPS` times per second; animations still run,
3885
3895
  * just less often. See {@link SceneOptions.maxFPS}.
3886
3896
  */
3887
- __init97() {this.maxFPS = 60}
3897
+ __init98() {this.maxFPS = 60}
3888
3898
  /** Whether the OS prefers-reduced-motion setting auto-caps the loop. */
3889
- __init98() {this.respectReducedMotion = true}
3899
+ __init99() {this.respectReducedMotion = true}
3890
3900
  /**
3891
3901
  * Reading direction for accessibility tab/traversal order (`'ltr'` default,
3892
3902
  * `'rtl'`). Controls the inline sort within a visual row in
@@ -3902,15 +3912,15 @@ var Scene = (_class15 = class _Scene {
3902
3912
  this.a11yOrder.markNeedsReorder();
3903
3913
  }
3904
3914
  }
3905
- __init99() {this._readingDirection = "ltr"}
3915
+ __init100() {this._readingDirection = "ltr"}
3906
3916
  /** Cached media-query list; `.matches` is read live each frame. */
3907
- __init100() {this.reducedMotionQuery = null}
3917
+ __init101() {this.reducedMotionQuery = null}
3908
3918
  /** Cached `(forced-colors: active)` query (Windows High Contrast etc.). A
3909
3919
  * canvas gets NO automatic forced-colors treatment from the browser (it's
3910
3920
  * opaque pixels), so components must read {@link forcedColors} and repaint
3911
3921
  * with system colors themselves; a change listener repaints idle scenes. */
3912
- __init101() {this.forcedColorsQuery = null}
3913
- __init102() {this.forcedColorsChangeHandler = null}
3922
+ __init102() {this.forcedColorsQuery = null}
3923
+ __init103() {this.forcedColorsChangeHandler = null}
3914
3924
  /** True when the OS asks for reduced motion and we respect it. Read by the animation drivers. */
3915
3925
  get prefersReducedMotion() {
3916
3926
  return this.respectReducedMotion && !!_optionalChain([this, 'access', _93 => _93.reducedMotionQuery, 'optionalAccess', _94 => _94.matches]);
@@ -3931,19 +3941,19 @@ var Scene = (_class15 = class _Scene {
3931
3941
  * Throttle interval (ms) for the a11y/automation shadow sync. `0` = every
3932
3942
  * frame. See {@link SceneOptions.a11ySyncInterval}.
3933
3943
  */
3934
- __init103() {this.a11ySyncInterval = 0}
3944
+ __init104() {this.a11ySyncInterval = 0}
3935
3945
  /** Timestamp of the last a11y sync, for throttling. */
3936
- __init104() {this.lastA11ySync = -Infinity}
3946
+ __init105() {this.lastA11ySync = -Infinity}
3937
3947
  /** True if we skipped an a11y sync during animation and need to sync when at rest. */
3938
- __init105() {this.a11yPendingSyncAfterAnimation = false}
3948
+ __init106() {this.a11yPendingSyncAfterAnimation = false}
3939
3949
  // A11y / Automation Layer. `null` in non-DOM (SSR/Node) environments — the
3940
3950
  // whole projection degrades to a no-op so the engine's logic stays usable
3941
3951
  // server-side (e.g. headless layout / vector export) without jsdom.
3942
3952
 
3943
- __init106() {this.a11yElements = /* @__PURE__ */ new Map()}
3953
+ __init107() {this.a11yElements = /* @__PURE__ */ new Map()}
3944
3954
  // --- domain: content-projection — projected DOM, sync state, calibration, selection ---
3945
3955
  /** DOM nodes mirroring static text content, keyed by entity id. */
3946
- __init107() {this.contentElements = /* @__PURE__ */ new Map()}
3956
+ __init108() {this.contentElements = /* @__PURE__ */ new Map()}
3947
3957
  /**
3948
3958
  * What the last completed content-projection sync was built from, per entity.
3949
3959
  *
@@ -3952,9 +3962,9 @@ var Scene = (_class15 = class _Scene {
3952
3962
  * build. Only populated for entities that opt in via
3953
3963
  * {@link Entity.getContentEpoch}. (carryctx CTX-0199)
3954
3964
  */
3955
- __init108() {this.contentSyncState = /* @__PURE__ */ new Map()}
3965
+ __init109() {this.contentSyncState = /* @__PURE__ */ new Map()}
3956
3966
  /** Invalidates grid font calibration after browser font availability changes. */
3957
- __init109() {this.contentFontEpoch = 0}
3967
+ __init110() {this.contentFontEpoch = 0}
3958
3968
  /**
3959
3969
  * Bumped whenever the viewport itself changes shape, which re-tiers blocks
3960
3970
  * without moving any of them.
@@ -3967,7 +3977,7 @@ var Scene = (_class15 = class _Scene {
3967
3977
  * carriers it no longer needs. Scrolling needs no such epoch: it moves the root,
3968
3978
  * so every block's parent world transform changes and the check fails honestly.
3969
3979
  */
3970
- __init110() {this.contentViewportEpoch = 0}
3980
+ __init111() {this.contentViewportEpoch = 0}
3971
3981
  /**
3972
3982
  * One-entry memo for a parent's world transform, held as scalars.
3973
3983
  *
@@ -3982,16 +3992,16 @@ var Scene = (_class15 = class _Scene {
3982
3992
  * sync, so a memo can never survive into a frame in which the parent has
3983
3993
  * moved.
3984
3994
  */
3985
- __init111() {this._pwNode = null}
3986
- __init112() {this._pwSerial = -1}
3987
- __init113() {this._pwa = 1}
3988
- __init114() {this._pwb = 0}
3989
- __init115() {this._pwc = 0}
3990
- __init116() {this._pwd = 1}
3991
- __init117() {this._pwe = 0}
3992
- __init118() {this._pwf = 0}
3995
+ __init112() {this._pwNode = null}
3996
+ __init113() {this._pwSerial = -1}
3997
+ __init114() {this._pwa = 1}
3998
+ __init115() {this._pwb = 0}
3999
+ __init116() {this._pwc = 0}
4000
+ __init117() {this._pwd = 1}
4001
+ __init118() {this._pwe = 0}
4002
+ __init119() {this._pwf = 0}
3993
4003
  /** Incremented once per top-level `syncA11y`, invalidating `_pwNode`. */
3994
- __init119() {this._syncSerial = 0}
4004
+ __init120() {this._syncSerial = 0}
3995
4005
  /**
3996
4006
  * Disables the settled-walk fast path in {@link syncContentProjection}.
3997
4007
  *
@@ -4003,38 +4013,38 @@ var Scene = (_class15 = class _Scene {
4003
4013
  * Not part of the public API and not documented as an option: turning it on only
4004
4014
  * makes a settled document slower, never more correct.
4005
4015
  */
4006
- __init120() {this.disableSettledFastPath = false}
4016
+ __init121() {this.disableSettledFastPath = false}
4007
4017
  /** Cached Canvas-to-client scale for the current font/viewport epoch. */
4008
- __init121() {this.contentMetricScaleEpoch = -1}
4009
- __init122() {this.contentMetricScaleX = 1}
4010
- __init123() {this.contentProjectionEnabled = true}
4018
+ __init122() {this.contentMetricScaleEpoch = -1}
4019
+ __init123() {this.contentMetricScaleX = 1}
4020
+ __init124() {this.contentProjectionEnabled = true}
4011
4021
  // Virtualization margin (px) for the content-projection CARRIER band;
4012
4022
  // `undefined` → one viewport height, resolved at sync time. `Infinity` is
4013
4023
  // unsupported here: it unwindows every carrier, which is O(total glyphs).
4014
- __init124() {this.contentProjectionMargin = void 0}
4024
+ __init125() {this.contentProjectionMargin = void 0}
4015
4025
  // Virtualization margin (px) for the SEMANTIC tier — whether a block has
4016
4026
  // any projected DOM. `undefined` → falls back to contentProjectionMargin, so
4017
4027
  // the default keeps one gate. `Infinity` = every block keeps resident text.
4018
- __init125() {this.contentSemanticMargin = void 0}
4028
+ __init126() {this.contentSemanticMargin = void 0}
4019
4029
  // How many coarse-tier blocks may be MATERIALIZED per sync, spreading the
4020
4030
  // resident tier's document-open cost across frames. `Infinity` = one
4021
4031
  // synchronous pass.
4022
- __init126() {this.contentSemanticBudget = DEFAULT_CONTENT_SEMANTIC_BUDGET}
4032
+ __init127() {this.contentSemanticBudget = DEFAULT_CONTENT_SEMANTIC_BUDGET}
4023
4033
  // Remaining materializations in the CURRENT sync. Reset at the start of each
4024
4034
  // a11y walk; decremented per coarse block that creates its element.
4025
- __init127() {this.contentSemanticBudgetLeft = 0}
4035
+ __init128() {this.contentSemanticBudgetLeft = 0}
4026
4036
  // Set when a sync deferred at least one block, so the scene knows to keep
4027
4037
  // drawing frames until the resident tier is complete. Without it a static
4028
4038
  // scene in `onDemand` mode would stop rendering with the document half
4029
4039
  // materialized and never finish.
4030
- __init128() {this.contentSemanticDeferred = false}
4031
- __init129() {this.contentSelectionEndListener = null}
4040
+ __init129() {this.contentSemanticDeferred = false}
4041
+ __init130() {this.contentSelectionEndListener = null}
4032
4042
  // --- domain: render-scheduler — per-frame walk flags ---
4033
4043
  // Animation/interactive flags collected during the render walk (tree-walk
4034
4044
  // fusion): the loop reads last frame's answers instead of re-walking the
4035
4045
  // tree up to 4× per tick. Start true so the first tick stays conservative.
4036
- __init130() {this.frameHadAnimation = true}
4037
- __init131() {this.frameHadInteractive = true}
4046
+ __init131() {this.frameHadAnimation = true}
4047
+ __init132() {this.frameHadInteractive = true}
4038
4048
  // --- domain: context-and-resize — resize handler, DPR query, canvas observer ---
4039
4049
 
4040
4050
  /** Active `(resolution: Ndppx)` media query watching for a runtime DPR change
@@ -4042,15 +4052,15 @@ var Scene = (_class15 = class _Scene {
4042
4052
  * can be re-scaled — otherwise it stays rasterized at the old DPR and blurs.
4043
4053
  * A resolution media query only fires when leaving its exact value, so the
4044
4054
  * handler re-arms a fresh query for the new DPR each time. */
4045
- __init132() {this.dprMediaQuery = null}
4055
+ __init133() {this.dprMediaQuery = null}
4046
4056
  /** For embedded (`disableWindowResize`) scenes: observes the canvas element so
4047
4057
  * a CSS/layout-driven size change re-runs `resize()`. A window `resize`
4048
4058
  * listener never fires for these (the window isn't what changed), so without
4049
4059
  * this an embedded canvas stayed at its initial size forever. */
4050
- __init133() {this.canvasResizeObserver = null}
4051
- __init134() {this.dprChangeHandler = null}
4060
+ __init134() {this.canvasResizeObserver = null}
4061
+ __init135() {this.dprChangeHandler = null}
4052
4062
  // --- domain: a11y-projection — focus, overlay geometry, DOM ordering, portals ---
4053
- __init135() {this.focusedA11yElement = null}
4063
+ __init136() {this.focusedA11yElement = null}
4054
4064
  /**
4055
4065
  * Canvas box geometry: the CSS↔logical mapping, overlay layer alignment, and
4056
4066
  * the DPR math (extraction 5, `DEC-0024`).
@@ -4077,7 +4087,7 @@ var Scene = (_class15 = class _Scene {
4077
4087
  /** Shadow elements the pointer is currently inside. Lets a removal that happens
4078
4088
  * mid-hover synthesize the `pointerleave` the browser never sends for a
4079
4089
  * detached element, so the entity doesn't keep its hover state. */
4080
- __init136() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
4090
+ __init137() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
4081
4091
  /**
4082
4092
  * Entity ids the application has pinned via {@link requestA11yProjection}.
4083
4093
  *
@@ -4085,13 +4095,13 @@ var Scene = (_class15 = class _Scene {
4085
4095
  * a stale id simply never matches. Cleared per-entity by
4086
4096
  * {@link releaseA11yProjection}.
4087
4097
  */
4088
- __init137() {this.a11yProjectionRequests = /* @__PURE__ */ new Set()}
4098
+ __init138() {this.a11yProjectionRequests = /* @__PURE__ */ new Set()}
4089
4099
  /** Persistent tabindex=-1 element in a11yRoot. When the focused a11y mirror is
4090
4100
  * pruned (virtualization/streaming/removal) while it holds focus, we move
4091
4101
  * focus here instead of letting the browser drop it to <body> — keeping the
4092
4102
  * screen-reader virtual cursor inside the scene's a11y region. */
4093
- __init138() {this.focusSentinel = null}
4094
- __init139() {this.caretBlinkTimer = null}
4103
+ __init139() {this.focusSentinel = null}
4104
+ __init140() {this.caretBlinkTimer = null}
4095
4105
  /**
4096
4106
  * The projected DOM's ordering engine — the reorder flag, the per-pass scratch
4097
4107
  * collections, the visual reading-order sort and the cursor-based
@@ -4101,7 +4111,7 @@ var Scene = (_class15 = class _Scene {
4101
4111
  * delegate here; the collect-and-prune walk stays behind because it needs
4102
4112
  * {@link shouldProjectA11y} and the focus/caret state.
4103
4113
  */
4104
- __init140() {this.a11yOrder = new A11yProjectionManager()}
4114
+ __init141() {this.a11yOrder = new A11yProjectionManager()}
4105
4115
  /**
4106
4116
  * The content projection's selection preservation and grid calibration
4107
4117
  * (extraction 3, `DEC-0022`).
@@ -4153,11 +4163,11 @@ var Scene = (_class15 = class _Scene {
4153
4163
  set a11yNeedsReorder(value) {
4154
4164
  this.a11yOrder.needsReorder = value;
4155
4165
  }
4156
- __init141() {this.portalRoot = null}
4157
- __init142() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
4158
- __init143() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
4159
- __init144() {this.portalEntities = /* @__PURE__ */ new Map()}
4160
- __init145() {this.renderOrderCounter = 0}
4166
+ __init142() {this.portalRoot = null}
4167
+ __init143() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
4168
+ __init144() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
4169
+ __init145() {this.portalEntities = /* @__PURE__ */ new Map()}
4170
+ __init146() {this.renderOrderCounter = 0}
4161
4171
  // --- domain: render-scheduler — authoritative frame counter ---
4162
4172
  /**
4163
4173
  * Monotonic render-frame counter, bumped once per authoritative `render()`
@@ -4167,7 +4177,7 @@ var Scene = (_class15 = class _Scene {
4167
4177
  * back to the ancestor walk. Public for the same reason `Entity._getTrig`/
4168
4178
  * `_setWorldCache` are: it is a cross-class render-internal contract.
4169
4179
  */
4170
- __init146() {this.currentFrame = 0}
4180
+ __init147() {this.currentFrame = 0}
4171
4181
  // --- domain: wasm-backend-facade — EXTRACTED (see scene/WasmBackendFacade.ts) ---
4172
4182
  /**
4173
4183
  * The four invisible WASM accelerators and the resident transform store.
@@ -4237,8 +4247,8 @@ var Scene = (_class15 = class _Scene {
4237
4247
  // Stays on `Scene`: it walks `root` *and* `overlayRoot` for entities the
4238
4248
  // render walk consumes, so only its cache key is store state. The facade owns
4239
4249
  // the key; the walk stays here.
4240
- __init147() {this._computeEntities = []}
4241
- __init148() {this._computeEntitiesVersion = -1}
4250
+ __init148() {this._computeEntities = []}
4251
+ __init149() {this._computeEntitiesVersion = -1}
4242
4252
  /** Invalidate the resident WASM store layout; the next wasm-mode frame rebuilds
4243
4253
  * it. Called by `Entity.add`/`remove` (topology changes only). */
4244
4254
  markStructureChanged() {
@@ -4477,7 +4487,7 @@ var Scene = (_class15 = class _Scene {
4477
4487
  get animBatchedLastFrame() {
4478
4488
  return this._wasmBackend.animBatchedLastFrame;
4479
4489
  }
4480
- __init149() {this.animGate = {
4490
+ __init150() {this.animGate = {
4481
4491
  spring: 128,
4482
4492
  tween: 256,
4483
4493
  mixed: 128
@@ -4592,25 +4602,25 @@ var Scene = (_class15 = class _Scene {
4592
4602
  * sync, so retaining the order prevents a newly opened overlay from spending
4593
4603
  * its first frame below previously projected controls.
4594
4604
  */
4595
- __init150() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
4605
+ __init151() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
4596
4606
  // --- domain: context-and-resize — GL/GPU canvases, device state, pointer, size warnings ---
4597
4607
  // Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
4598
- __init151() {this.pointRenderer = null}
4599
- __init152() {this.glCanvas = null}
4600
- __init153() {this.glContextLostHandler = null}
4601
- __init154() {this.glContextRestoredHandler = null}
4608
+ __init152() {this.pointRenderer = null}
4609
+ __init153() {this.glCanvas = null}
4610
+ __init154() {this.glContextLostHandler = null}
4611
+ __init155() {this.glContextRestoredHandler = null}
4602
4612
 
4603
4613
 
4604
4614
 
4605
- __init155() {this.disableWindowResize = false}
4615
+ __init156() {this.disableWindowResize = false}
4606
4616
  /** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
4607
4617
 
4608
4618
  // WebGPU properties
4609
- __init156() {this.destroyed = false}
4610
- __init157() {this.device = null}
4611
- __init158() {this.deviceLost = false}
4612
- __init159() {this.particleBackend = "auto"}
4613
- __init160() {this._webgpuDisabled = false}
4619
+ __init157() {this.destroyed = false}
4620
+ __init158() {this.device = null}
4621
+ __init159() {this.deviceLost = false}
4622
+ __init160() {this.particleBackend = "auto"}
4623
+ __init161() {this._webgpuDisabled = false}
4614
4624
  get webgpuDisabled() {
4615
4625
  return this._webgpuDisabled || this.particleBackend === "cpu";
4616
4626
  }
@@ -4638,21 +4648,21 @@ var Scene = (_class15 = class _Scene {
4638
4648
  set webgpuDisabled(value) {
4639
4649
  this._webgpuDisabled = value;
4640
4650
  }
4641
- __init161() {this.recoveryTimerId = null}
4642
- __init162() {this.manager = null}
4643
- __init163() {this.initializingWebGPU = false}
4644
- __init164() {this.gpuCanvas = null}
4645
- __init165() {this.gpuContext = null}
4651
+ __init162() {this.recoveryTimerId = null}
4652
+ __init163() {this.manager = null}
4653
+ __init164() {this.initializingWebGPU = false}
4654
+ __init165() {this.gpuCanvas = null}
4655
+ __init166() {this.gpuContext = null}
4646
4656
  /** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
4647
- __init166() {this.gpuHasContent = false}
4648
- __init167() {this.mouseX = -9999}
4649
- __init168() {this.mouseY = -9999}
4650
- __init169() {this.pointerMoveListener = null}
4651
- __init170() {this.pointerLeaveListener = null}
4657
+ __init167() {this.gpuHasContent = false}
4658
+ __init168() {this.mouseX = -9999}
4659
+ __init169() {this.mouseY = -9999}
4660
+ __init170() {this.pointerMoveListener = null}
4661
+ __init171() {this.pointerLeaveListener = null}
4652
4662
  /** Element the pointer listeners are bound to (parent container if present,
4653
4663
  * else the canvas). Stored so `destroy()` detaches from the same element. */
4654
- __init171() {this.pointerEventTarget = null}
4655
- __init172() {this.hasWarnedZeroSize = false}
4664
+ __init172() {this.pointerEventTarget = null}
4665
+ __init173() {this.hasWarnedZeroSize = false}
4656
4666
  /**
4657
4667
  * Latch for {@link Scene.resize}'s invalid-dimension warning.
4658
4668
  *
@@ -4662,8 +4672,8 @@ var Scene = (_class15 = class _Scene {
4662
4672
  * because `resize()` is commonly driven from a `ResizeObserver`, which would
4663
4673
  * otherwise warn on every frame of a drag.
4664
4674
  */
4665
- __init173() {this.hasWarnedInvalidResize = false}
4666
- __init174() {this.fontLoadHandler = null}
4675
+ __init174() {this.hasWarnedInvalidResize = false}
4676
+ __init175() {this.fontLoadHandler = null}
4667
4677
  // --- domain: scene-facade — dev-mode diagnostics ---
4668
4678
  // ── Dev-mode warning infrastructure ──────────────────────────────
4669
4679
  //
@@ -4698,7 +4708,7 @@ var Scene = (_class15 = class _Scene {
4698
4708
  return false;
4699
4709
  }
4700
4710
 
4701
- __init175() {this._devFrameCount = 0}
4711
+ __init176() {this._devFrameCount = 0}
4702
4712
  _devWarn(message) {
4703
4713
  if (!this._devActive) return;
4704
4714
  console.warn(`[vectojs/dev] ${message}`);
@@ -4776,7 +4786,7 @@ var Scene = (_class15 = class _Scene {
4776
4786
  walkProjections(this.root);
4777
4787
  }
4778
4788
  // --- domain: scene-facade — construction (wires every collaborator) ---
4779
- constructor(canvas, options = {}) {;_class15.prototype.__init83.call(this);_class15.prototype.__init84.call(this);_class15.prototype.__init85.call(this);_class15.prototype.__init86.call(this);_class15.prototype.__init87.call(this);_class15.prototype.__init88.call(this);_class15.prototype.__init89.call(this);_class15.prototype.__init90.call(this);_class15.prototype.__init91.call(this);_class15.prototype.__init92.call(this);_class15.prototype.__init93.call(this);_class15.prototype.__init94.call(this);_class15.prototype.__init95.call(this);_class15.prototype.__init96.call(this);_class15.prototype.__init97.call(this);_class15.prototype.__init98.call(this);_class15.prototype.__init99.call(this);_class15.prototype.__init100.call(this);_class15.prototype.__init101.call(this);_class15.prototype.__init102.call(this);_class15.prototype.__init103.call(this);_class15.prototype.__init104.call(this);_class15.prototype.__init105.call(this);_class15.prototype.__init106.call(this);_class15.prototype.__init107.call(this);_class15.prototype.__init108.call(this);_class15.prototype.__init109.call(this);_class15.prototype.__init110.call(this);_class15.prototype.__init111.call(this);_class15.prototype.__init112.call(this);_class15.prototype.__init113.call(this);_class15.prototype.__init114.call(this);_class15.prototype.__init115.call(this);_class15.prototype.__init116.call(this);_class15.prototype.__init117.call(this);_class15.prototype.__init118.call(this);_class15.prototype.__init119.call(this);_class15.prototype.__init120.call(this);_class15.prototype.__init121.call(this);_class15.prototype.__init122.call(this);_class15.prototype.__init123.call(this);_class15.prototype.__init124.call(this);_class15.prototype.__init125.call(this);_class15.prototype.__init126.call(this);_class15.prototype.__init127.call(this);_class15.prototype.__init128.call(this);_class15.prototype.__init129.call(this);_class15.prototype.__init130.call(this);_class15.prototype.__init131.call(this);_class15.prototype.__init132.call(this);_class15.prototype.__init133.call(this);_class15.prototype.__init134.call(this);_class15.prototype.__init135.call(this);_class15.prototype.__init136.call(this);_class15.prototype.__init137.call(this);_class15.prototype.__init138.call(this);_class15.prototype.__init139.call(this);_class15.prototype.__init140.call(this);_class15.prototype.__init141.call(this);_class15.prototype.__init142.call(this);_class15.prototype.__init143.call(this);_class15.prototype.__init144.call(this);_class15.prototype.__init145.call(this);_class15.prototype.__init146.call(this);_class15.prototype.__init147.call(this);_class15.prototype.__init148.call(this);_class15.prototype.__init149.call(this);_class15.prototype.__init150.call(this);_class15.prototype.__init151.call(this);_class15.prototype.__init152.call(this);_class15.prototype.__init153.call(this);_class15.prototype.__init154.call(this);_class15.prototype.__init155.call(this);_class15.prototype.__init156.call(this);_class15.prototype.__init157.call(this);_class15.prototype.__init158.call(this);_class15.prototype.__init159.call(this);_class15.prototype.__init160.call(this);_class15.prototype.__init161.call(this);_class15.prototype.__init162.call(this);_class15.prototype.__init163.call(this);_class15.prototype.__init164.call(this);_class15.prototype.__init165.call(this);_class15.prototype.__init166.call(this);_class15.prototype.__init167.call(this);_class15.prototype.__init168.call(this);_class15.prototype.__init169.call(this);_class15.prototype.__init170.call(this);_class15.prototype.__init171.call(this);_class15.prototype.__init172.call(this);_class15.prototype.__init173.call(this);_class15.prototype.__init174.call(this);_class15.prototype.__init175.call(this);
4789
+ constructor(canvas, options = {}) {;_class15.prototype.__init83.call(this);_class15.prototype.__init84.call(this);_class15.prototype.__init85.call(this);_class15.prototype.__init86.call(this);_class15.prototype.__init87.call(this);_class15.prototype.__init88.call(this);_class15.prototype.__init89.call(this);_class15.prototype.__init90.call(this);_class15.prototype.__init91.call(this);_class15.prototype.__init92.call(this);_class15.prototype.__init93.call(this);_class15.prototype.__init94.call(this);_class15.prototype.__init95.call(this);_class15.prototype.__init96.call(this);_class15.prototype.__init97.call(this);_class15.prototype.__init98.call(this);_class15.prototype.__init99.call(this);_class15.prototype.__init100.call(this);_class15.prototype.__init101.call(this);_class15.prototype.__init102.call(this);_class15.prototype.__init103.call(this);_class15.prototype.__init104.call(this);_class15.prototype.__init105.call(this);_class15.prototype.__init106.call(this);_class15.prototype.__init107.call(this);_class15.prototype.__init108.call(this);_class15.prototype.__init109.call(this);_class15.prototype.__init110.call(this);_class15.prototype.__init111.call(this);_class15.prototype.__init112.call(this);_class15.prototype.__init113.call(this);_class15.prototype.__init114.call(this);_class15.prototype.__init115.call(this);_class15.prototype.__init116.call(this);_class15.prototype.__init117.call(this);_class15.prototype.__init118.call(this);_class15.prototype.__init119.call(this);_class15.prototype.__init120.call(this);_class15.prototype.__init121.call(this);_class15.prototype.__init122.call(this);_class15.prototype.__init123.call(this);_class15.prototype.__init124.call(this);_class15.prototype.__init125.call(this);_class15.prototype.__init126.call(this);_class15.prototype.__init127.call(this);_class15.prototype.__init128.call(this);_class15.prototype.__init129.call(this);_class15.prototype.__init130.call(this);_class15.prototype.__init131.call(this);_class15.prototype.__init132.call(this);_class15.prototype.__init133.call(this);_class15.prototype.__init134.call(this);_class15.prototype.__init135.call(this);_class15.prototype.__init136.call(this);_class15.prototype.__init137.call(this);_class15.prototype.__init138.call(this);_class15.prototype.__init139.call(this);_class15.prototype.__init140.call(this);_class15.prototype.__init141.call(this);_class15.prototype.__init142.call(this);_class15.prototype.__init143.call(this);_class15.prototype.__init144.call(this);_class15.prototype.__init145.call(this);_class15.prototype.__init146.call(this);_class15.prototype.__init147.call(this);_class15.prototype.__init148.call(this);_class15.prototype.__init149.call(this);_class15.prototype.__init150.call(this);_class15.prototype.__init151.call(this);_class15.prototype.__init152.call(this);_class15.prototype.__init153.call(this);_class15.prototype.__init154.call(this);_class15.prototype.__init155.call(this);_class15.prototype.__init156.call(this);_class15.prototype.__init157.call(this);_class15.prototype.__init158.call(this);_class15.prototype.__init159.call(this);_class15.prototype.__init160.call(this);_class15.prototype.__init161.call(this);_class15.prototype.__init162.call(this);_class15.prototype.__init163.call(this);_class15.prototype.__init164.call(this);_class15.prototype.__init165.call(this);_class15.prototype.__init166.call(this);_class15.prototype.__init167.call(this);_class15.prototype.__init168.call(this);_class15.prototype.__init169.call(this);_class15.prototype.__init170.call(this);_class15.prototype.__init171.call(this);_class15.prototype.__init172.call(this);_class15.prototype.__init173.call(this);_class15.prototype.__init174.call(this);_class15.prototype.__init175.call(this);_class15.prototype.__init176.call(this);
4780
4790
  this.canvas = canvas;
4781
4791
  this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
4782
4792
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
@@ -4795,6 +4805,7 @@ var Scene = (_class15 = class _Scene {
4795
4805
  this.maxFPS = _nullishCoalesce(options.maxFPS, () => ( (isTest ? 0 : 60)));
4796
4806
  this.respectReducedMotion = _nullishCoalesce(options.respectReducedMotion, () => ( true));
4797
4807
  this.autoThrottle = _nullishCoalesce(options.autoThrottle, () => ( true));
4808
+ this.idleFPS = _nullishCoalesce(options.idleFPS, () => ( 60));
4798
4809
  this.phases.userTiming = _nullishCoalesce(options.userTiming, () => ( false));
4799
4810
  this.particleBackend = _nullishCoalesce(options.particleBackend, () => ( "auto"));
4800
4811
  this.a11ySyncInterval = _nullishCoalesce(options.a11ySyncInterval, () => ( 0));
@@ -4813,7 +4824,7 @@ var Scene = (_class15 = class _Scene {
4813
4824
  this.forcedColorsChangeHandler = () => this.markDirty();
4814
4825
  _optionalChain([this, 'access', _118 => _118.forcedColorsQuery, 'access', _119 => _119.addEventListener, 'optionalCall', _120 => _120("change", this.forcedColorsChangeHandler)]);
4815
4826
  }
4816
- this.root = new class RootEntity extends _chunkXHPUCU5Pjs.Entity {
4827
+ this.root = new class RootEntity extends _chunkXE6K7T5Sjs.Entity {
4817
4828
  isPointInside() {
4818
4829
  return false;
4819
4830
  }
@@ -4823,7 +4834,7 @@ var Scene = (_class15 = class _Scene {
4823
4834
  }("root");
4824
4835
  this.root._scene = this;
4825
4836
  this._wasmBackend = new WasmBackendFacade(this.root);
4826
- this.overlayRoot = new class OverlayRoot extends _chunkXHPUCU5Pjs.Entity {
4837
+ this.overlayRoot = new class OverlayRoot extends _chunkXE6K7T5Sjs.Entity {
4827
4838
  isPointInside() {
4828
4839
  return false;
4829
4840
  }
@@ -5143,7 +5154,7 @@ var Scene = (_class15 = class _Scene {
5143
5154
  }
5144
5155
  if (this.hoveredA11yElements.has(el)) {
5145
5156
  this.hoveredA11yElements.delete(el);
5146
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("pointerleave", node, void 0, false));
5157
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("pointerleave", node, void 0, false));
5147
5158
  }
5148
5159
  this.preserveFocusOnRemoval(el);
5149
5160
  el.remove();
@@ -5723,6 +5734,8 @@ var Scene = (_class15 = class _Scene {
5723
5734
  el.style.padding = "0";
5724
5735
  el.style.outline = "none";
5725
5736
  el.style.cursor = node.a11yFullViewport ? "default" : "pointer";
5737
+ el.style.userSelect = "none";
5738
+ el.style.webkitUserSelect = "none";
5726
5739
  if (this.debugA11y) {
5727
5740
  el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
5728
5741
  el.style.border = "1px dashed rgba(56, 189, 248, 0.4)";
@@ -5732,20 +5745,20 @@ var Scene = (_class15 = class _Scene {
5732
5745
  el.style.background = "transparent";
5733
5746
  }
5734
5747
  el.addEventListener("click", (e) => {
5735
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("click", node, e));
5748
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("click", node, e));
5736
5749
  });
5737
5750
  el.addEventListener("dblclick", (e) => {
5738
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("dblclick", node, e));
5751
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("dblclick", node, e));
5739
5752
  });
5740
5753
  el.addEventListener("mouseenter", (e) => {
5741
5754
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
5742
5755
  this.hoveredA11yElements.add(el);
5743
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("hover", node, e, false));
5756
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("hover", node, e, false));
5744
5757
  });
5745
5758
  el.addEventListener("mouseleave", (e) => {
5746
5759
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
5747
5760
  this.hoveredA11yElements.delete(el);
5748
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("pointerleave", node, e, false));
5761
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("pointerleave", node, e, false));
5749
5762
  });
5750
5763
  const capEl = el;
5751
5764
  const releasePointer = (event) => {
@@ -5761,24 +5774,24 @@ var Scene = (_class15 = class _Scene {
5761
5774
  };
5762
5775
  el.addEventListener("pointerdown", (e) => {
5763
5776
  if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
5764
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("pointerdown", node, e));
5777
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("pointerdown", node, e));
5765
5778
  });
5766
5779
  el.addEventListener("pointerup", (e) => {
5767
5780
  releasePointer(e);
5768
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("pointerup", node, e));
5781
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("pointerup", node, e));
5769
5782
  });
5770
5783
  el.addEventListener("pointercancel", (e) => {
5771
5784
  releasePointer(e);
5772
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("pointercancel", node, e));
5785
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("pointercancel", node, e));
5773
5786
  });
5774
5787
  el.addEventListener(
5775
5788
  "pointermove",
5776
- (e) => node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("pointermove", node, e))
5789
+ (e) => node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("pointermove", node, e))
5777
5790
  );
5778
5791
  el.addEventListener(
5779
5792
  "wheel",
5780
5793
  (e) => {
5781
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("wheel", node, e));
5794
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("wheel", node, e));
5782
5795
  },
5783
5796
  { passive: false }
5784
5797
  );
@@ -5803,10 +5816,10 @@ var Scene = (_class15 = class _Scene {
5803
5816
  );
5804
5817
  emitInitialScroll = emitScroll;
5805
5818
  el.addEventListener("keydown", (e) => {
5806
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("keydown", node, e));
5819
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("keydown", node, e));
5807
5820
  });
5808
5821
  el.addEventListener("keyup", (e) => {
5809
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("keyup", node, e));
5822
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("keyup", node, e));
5810
5823
  });
5811
5824
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
5812
5825
  const input = el;
@@ -5872,7 +5885,7 @@ var Scene = (_class15 = class _Scene {
5872
5885
  el.addEventListener("keydown", (e) => {
5873
5886
  if (e.key === "Enter" || e.key === " ") {
5874
5887
  e.preventDefault();
5875
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("click", node, e));
5888
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("click", node, e));
5876
5889
  }
5877
5890
  });
5878
5891
  }
@@ -6309,7 +6322,7 @@ var Scene = (_class15 = class _Scene {
6309
6322
  el.addEventListener(
6310
6323
  "wheel",
6311
6324
  (e2) => {
6312
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("wheel", node, e2));
6325
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("wheel", node, e2));
6313
6326
  },
6314
6327
  { passive: false }
6315
6328
  );
@@ -6738,7 +6751,7 @@ var Scene = (_class15 = class _Scene {
6738
6751
  let cap = this.effectiveMaxFPS();
6739
6752
  const isIdle = !this.dirty && !this.frameHadAnimation && !this.contentSemanticDeferred;
6740
6753
  if (isIdle && this.autoThrottle && this.renderMode === "always" && this.maxFPS > 0) {
6741
- cap = Math.min(cap, 2);
6754
+ cap = Math.min(cap, this.idleFPS);
6742
6755
  }
6743
6756
  if (cap > 0 && time - this.lastTime < 1e3 / cap - 1) {
6744
6757
  this._skippedFrames++;
@@ -6978,7 +6991,7 @@ var Scene = (_class15 = class _Scene {
6978
6991
  let walkHadAnimation = false;
6979
6992
  let walkHadInteractive = false;
6980
6993
  const runUpdate = (node) => {
6981
- const overridesUpdate = node.update !== _chunkXHPUCU5Pjs.Entity.prototype.update;
6994
+ const overridesUpdate = node.update !== _chunkXE6K7T5Sjs.Entity.prototype.update;
6982
6995
  let pending = node.hasPendingAnimations();
6983
6996
  if (pending || overridesUpdate) {
6984
6997
  node.update(dt, time);
@@ -6987,9 +7000,9 @@ var Scene = (_class15 = class _Scene {
6987
7000
  if (pending) walkHadAnimation = true;
6988
7001
  if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
6989
7002
  if (this._devActive && this._devFrameCount % 120 === 0) {
6990
- if (overridesUpdate && node.hasPendingAnimations === _chunkXHPUCU5Pjs.Entity.prototype.hasPendingAnimations) {
7003
+ if (overridesUpdate && node.hasPendingAnimations === _chunkXE6K7T5Sjs.Entity.prototype.hasPendingAnimations) {
6991
7004
  this._devWarn(
6992
- `Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to drop the animation to ~2fps. Override hasPendingAnimations() to return true while motion is in flight.`
7005
+ `Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to slow the animation. Override hasPendingAnimations() to return true while motion is in flight.`
6993
7006
  );
6994
7007
  }
6995
7008
  }
@@ -7413,20 +7426,20 @@ function defaultMeasurer() {
7413
7426
  sharedMeasurer ??= _layout.resolveGlyphMeasurer.call(void 0, "sans-serif");
7414
7427
  return sharedMeasurer;
7415
7428
  }
7416
- var TextEntity = (_class16 = class extends _chunkXHPUCU5Pjs.Entity {
7429
+ var TextEntity = (_class16 = class extends _chunkXE6K7T5Sjs.Entity {
7417
7430
 
7418
7431
 
7419
7432
 
7420
7433
 
7421
- __init176() {this.nodes = []}
7434
+ __init177() {this.nodes = []}
7422
7435
 
7423
- __init177() {this.fillStyle = "#94a3b8"}
7424
- __init178() {this.strokeStyle = null}
7425
- __init179() {this.hoveredFillStyle = "#ffffff"}
7426
- __init180() {this.lineWidth = 1}
7427
- __init181() {this.isHovered = false}
7436
+ __init178() {this.fillStyle = "#94a3b8"}
7437
+ __init179() {this.strokeStyle = null}
7438
+ __init180() {this.hoveredFillStyle = "#ffffff"}
7439
+ __init181() {this.lineWidth = 1}
7440
+ __init182() {this.isHovered = false}
7428
7441
  /** Bumped by {@link applyLayout}; read by `Scene` to skip an unchanged sync. */
7429
- __init182() {this.contentEpoch = 0}
7442
+ __init183() {this.contentEpoch = 0}
7430
7443
  /**
7431
7444
  * Visual lines with real canvas geometry, rebuilt by {@link applyLayout}
7432
7445
  * exactly when `this.nodes` changes.
@@ -7438,9 +7451,9 @@ var TextEntity = (_class16 = class extends _chunkXHPUCU5Pjs.Entity {
7438
7451
  * ~0.35 em per line for a 24 px font, Firefox). Per-line carriers pin the DOM
7439
7452
  * baseline to the canvas baseline by construction, same as `ui/Text`.
7440
7453
  */
7441
- __init183() {this.projectionLines = []}
7454
+ __init184() {this.projectionLines = []}
7442
7455
  constructor(text, atlas, maxWidth, fontSize = 24) {
7443
- super();_class16.prototype.__init176.call(this);_class16.prototype.__init177.call(this);_class16.prototype.__init178.call(this);_class16.prototype.__init179.call(this);_class16.prototype.__init180.call(this);_class16.prototype.__init181.call(this);_class16.prototype.__init182.call(this);_class16.prototype.__init183.call(this);;
7456
+ super();_class16.prototype.__init177.call(this);_class16.prototype.__init178.call(this);_class16.prototype.__init179.call(this);_class16.prototype.__init180.call(this);_class16.prototype.__init181.call(this);_class16.prototype.__init182.call(this);_class16.prototype.__init183.call(this);_class16.prototype.__init184.call(this);;
7444
7457
  this.text = text;
7445
7458
  this.atlas = atlas;
7446
7459
  this.fontSize = fontSize;
@@ -7617,17 +7630,17 @@ var TextEntity = (_class16 = class extends _chunkXHPUCU5Pjs.Entity {
7617
7630
  }, _class16);
7618
7631
 
7619
7632
  // src/components/GridTextEntity.ts
7620
- var GridTextEntity = (_class17 = class extends _chunkXHPUCU5Pjs.Entity {
7633
+ var GridTextEntity = (_class17 = class extends _chunkXE6K7T5Sjs.Entity {
7621
7634
 
7622
- __init184() {this.fillStyle = "#ffffff"}
7623
- __init185() {this.grid = []}
7635
+ __init185() {this.fillStyle = "#ffffff"}
7636
+ __init186() {this.grid = []}
7624
7637
  // Array of rows
7625
- __init186() {this.cols = 0}
7626
- __init187() {this.rows = 0}
7638
+ __init187() {this.cols = 0}
7639
+ __init188() {this.rows = 0}
7627
7640
 
7628
7641
 
7629
7642
  constructor(_atlas, fontSize = 10) {
7630
- super();_class17.prototype.__init184.call(this);_class17.prototype.__init185.call(this);_class17.prototype.__init186.call(this);_class17.prototype.__init187.call(this);;
7643
+ super();_class17.prototype.__init185.call(this);_class17.prototype.__init186.call(this);_class17.prototype.__init187.call(this);_class17.prototype.__init188.call(this);;
7631
7644
  this.fontSize = fontSize;
7632
7645
  this.charWidth = fontSize * 1;
7633
7646
  this.charHeight = fontSize * 1.1;
@@ -7711,7 +7724,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
7711
7724
  const ey = py - cy;
7712
7725
  return ex * ex + ey * ey;
7713
7726
  }
7714
- var SplineEntity = (_class18 = class extends _chunkXHPUCU5Pjs.Entity {
7727
+ var SplineEntity = (_class18 = class extends _chunkXE6K7T5Sjs.Entity {
7715
7728
 
7716
7729
 
7717
7730
 
@@ -7719,17 +7732,17 @@ var SplineEntity = (_class18 = class extends _chunkXHPUCU5Pjs.Entity {
7719
7732
 
7720
7733
 
7721
7734
 
7722
- __init188() {this.offscreen = null}
7723
- __init189() {this.baked = false}
7735
+ __init189() {this.offscreen = null}
7736
+ __init190() {this.baked = false}
7724
7737
  /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
7725
- __init190() {this.bakedWidth = 0}
7726
- __init191() {this.bakedHeight = 0}
7738
+ __init191() {this.bakedWidth = 0}
7739
+ __init192() {this.bakedHeight = 0}
7727
7740
  /**
7728
7741
  * Device-pixel ratio the bitmap was rasterized at. Tracked so a DPR change
7729
7742
  * (browser zoom, a monitor move, a renderer whose clamp changed) re-bakes
7730
7743
  * instead of blitting a bitmap at the wrong device density forever.
7731
7744
  */
7732
- __init192() {this.bakedDPR = 0}
7745
+ __init193() {this.bakedDPR = 0}
7733
7746
  /**
7734
7747
  * Gradient strokes can't be baked to a solid-color bitmap; they render
7735
7748
  * per-frame. Derived from `_doc`, so it is NOT readonly — assigning a new
@@ -7737,7 +7750,7 @@ var SplineEntity = (_class18 = class extends _chunkXHPUCU5Pjs.Entity {
7737
7750
  */
7738
7751
 
7739
7752
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
7740
- __init193() {this.polylines = null}
7753
+ __init194() {this.polylines = null}
7741
7754
  /**
7742
7755
  * The spline document to render. Assigning a new document invalidates all
7743
7756
  * caches (baked canvas, flattened polylines, bounds).
@@ -7771,9 +7784,9 @@ var SplineEntity = (_class18 = class extends _chunkXHPUCU5Pjs.Entity {
7771
7784
  * local bounds after painting the curves. Useful for drag feedback and
7772
7785
  * debugging hit areas. Defaults to `false`.
7773
7786
  */
7774
- __init194() {this.showBounds = false}
7787
+ __init195() {this.showBounds = false}
7775
7788
  constructor(doc, opts = {}) {
7776
- super();_class18.prototype.__init188.call(this);_class18.prototype.__init189.call(this);_class18.prototype.__init190.call(this);_class18.prototype.__init191.call(this);_class18.prototype.__init192.call(this);_class18.prototype.__init193.call(this);_class18.prototype.__init194.call(this);;
7789
+ super();_class18.prototype.__init189.call(this);_class18.prototype.__init190.call(this);_class18.prototype.__init191.call(this);_class18.prototype.__init192.call(this);_class18.prototype.__init193.call(this);_class18.prototype.__init194.call(this);_class18.prototype.__init195.call(this);;
7777
7790
  this._doc = doc;
7778
7791
  this._lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
7779
7792
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -8039,7 +8052,7 @@ async function loadSpline(url) {
8039
8052
  }
8040
8053
 
8041
8054
  // src/components/Rect.ts
8042
- var Rect = class extends _chunkXHPUCU5Pjs.Entity {
8055
+ var Rect = class extends _chunkXE6K7T5Sjs.Entity {
8043
8056
 
8044
8057
 
8045
8058
 
@@ -8095,7 +8108,7 @@ var Rect = class extends _chunkXHPUCU5Pjs.Entity {
8095
8108
  };
8096
8109
 
8097
8110
  // src/components/Circle.ts
8098
- var Circle = class extends _chunkXHPUCU5Pjs.Entity {
8111
+ var Circle = class extends _chunkXE6K7T5Sjs.Entity {
8099
8112
 
8100
8113
 
8101
8114
 
@@ -8159,7 +8172,7 @@ var Circle = class extends _chunkXHPUCU5Pjs.Entity {
8159
8172
  };
8160
8173
 
8161
8174
  // src/components/Group.ts
8162
- var Group = class extends _chunkXHPUCU5Pjs.Entity {
8175
+ var Group = class extends _chunkXE6K7T5Sjs.Entity {
8163
8176
  constructor(...children) {
8164
8177
  super();
8165
8178
  if (children.length > 0) this.add(...children);
@@ -8178,21 +8191,21 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
8178
8191
 
8179
8192
 
8180
8193
  // src/tree/DOMPortalEntity.ts
8181
- var DOMPortalEntity = (_class19 = class extends _chunkXHPUCU5Pjs.Entity {
8194
+ var DOMPortalEntity = (_class19 = class extends _chunkXE6K7T5Sjs.Entity {
8182
8195
 
8183
- __init195() {this.isDOMPortal = true}
8184
- __init196() {this.domListeners = []}
8185
- __init197() {this.resizeObserver = null}
8186
- __init198() {this.domBound = false}
8187
- __init199() {this.cachedWidth = 100}
8188
- __init200() {this.cachedHeight = 100}
8189
- __init201() {this.lastWidth = ""}
8190
- __init202() {this.lastHeight = ""}
8191
- __init203() {this.lastTransform = ""}
8192
- __init204() {this.lastZIndex = ""}
8193
- __init205() {this.lastOpacity = ""}
8196
+ __init196() {this.isDOMPortal = true}
8197
+ __init197() {this.domListeners = []}
8198
+ __init198() {this.resizeObserver = null}
8199
+ __init199() {this.domBound = false}
8200
+ __init200() {this.cachedWidth = 100}
8201
+ __init201() {this.cachedHeight = 100}
8202
+ __init202() {this.lastWidth = ""}
8203
+ __init203() {this.lastHeight = ""}
8204
+ __init204() {this.lastTransform = ""}
8205
+ __init205() {this.lastZIndex = ""}
8206
+ __init206() {this.lastOpacity = ""}
8194
8207
  constructor(domElement, width, height, id) {
8195
- super(id);_class19.prototype.__init195.call(this);_class19.prototype.__init196.call(this);_class19.prototype.__init197.call(this);_class19.prototype.__init198.call(this);_class19.prototype.__init199.call(this);_class19.prototype.__init200.call(this);_class19.prototype.__init201.call(this);_class19.prototype.__init202.call(this);_class19.prototype.__init203.call(this);_class19.prototype.__init204.call(this);_class19.prototype.__init205.call(this);;
8208
+ super(id);_class19.prototype.__init196.call(this);_class19.prototype.__init197.call(this);_class19.prototype.__init198.call(this);_class19.prototype.__init199.call(this);_class19.prototype.__init200.call(this);_class19.prototype.__init201.call(this);_class19.prototype.__init202.call(this);_class19.prototype.__init203.call(this);_class19.prototype.__init204.call(this);_class19.prototype.__init205.call(this);_class19.prototype.__init206.call(this);;
8196
8209
  this.domElement = domElement;
8197
8210
  this.width = _nullishCoalesce(width, () => ( 0));
8198
8211
  this.height = _nullishCoalesce(height, () => ( 0));
@@ -8234,7 +8247,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkXHPUCU5Pjs.Entity {
8234
8247
  ];
8235
8248
  for (const type of events) {
8236
8249
  const handler = (e) => {
8237
- this.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)(type, this, e));
8250
+ this.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)(type, this, e));
8238
8251
  };
8239
8252
  this.domElement.addEventListener(type, handler);
8240
8253
  this.domListeners.push({ type, handler, capture: false });
@@ -8245,7 +8258,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkXHPUCU5Pjs.Entity {
8245
8258
  ];
8246
8259
  for (const { native, vecto } of hoverEvents) {
8247
8260
  const handler = (e) => {
8248
- this.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)(vecto, this, e, false));
8261
+ this.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)(vecto, this, e, false));
8249
8262
  };
8250
8263
  this.domElement.addEventListener(native, handler);
8251
8264
  this.domListeners.push({ type: native, handler, capture: false });
@@ -8253,7 +8266,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkXHPUCU5Pjs.Entity {
8253
8266
  const focusEvents = ["focus", "blur"];
8254
8267
  for (const type of focusEvents) {
8255
8268
  const handler = (e) => {
8256
- this.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)(type, this, e, true));
8269
+ this.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)(type, this, e, true));
8257
8270
  };
8258
8271
  this.domElement.addEventListener(type, handler, true);
8259
8272
  this.domListeners.push({ type, handler, capture: true });
@@ -8351,4 +8364,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkMO24PVGTjs.WebGPUParticleSystemM
8351
8364
 
8352
8365
 
8353
8366
 
8354
- exports.CanvasRenderer = _chunkMO24PVGTjs.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DEFAULT_CONTENT_SEMANTIC_BUDGET = DEFAULT_CONTENT_SEMANTIC_BUDGET; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkXHPUCU5Pjs.Entity; exports.GlyphRasterAtlas = _chunkMO24PVGTjs.GlyphRasterAtlas; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkXHPUCU5Pjs.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SCENE_OPTION_KEYS = SCENE_OPTION_KEYS; exports.SVGEntity = _chunkXHPUCU5Pjs.SVGEntity; exports.SVGRenderer = _chunkMO24PVGTjs.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkMO24PVGTjs.TextRasterCache; exports.VECTO_USER_TIMING = VECTO_USER_TIMING; exports.VectoJSEvent = _chunkXHPUCU5Pjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkMO24PVGTjs.WebGPUParticleSystemManager; exports.beginVectoUserTiming = beginVectoUserTiming; exports.contentLineInHint = _chunkXHPUCU5Pjs.contentLineInHint; exports.createWebGLPointRenderer = _chunkMO24PVGTjs.createWebGLPointRenderer; exports.endVectoUserTiming = endVectoUserTiming; exports.installRendererDevTraps = _chunkMO24PVGTjs.installRendererDevTraps; exports.isRendererDevMode = _chunkMO24PVGTjs.isRendererDevMode; exports.isSafeUrl = _chunkMO24PVGTjs.isSafeUrl; exports.loadSpline = loadSpline; exports.measureVectoUserTiming = measureVectoUserTiming; exports.parseColorToRGBA = _chunkMO24PVGTjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkMO24PVGTjs.sanitizeUrl; exports.setRendererDevMode = _chunkMO24PVGTjs.setRendererDevMode;
8367
+ exports.CanvasRenderer = _chunkMO24PVGTjs.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DEFAULT_CONTENT_SEMANTIC_BUDGET = DEFAULT_CONTENT_SEMANTIC_BUDGET; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkXE6K7T5Sjs.Entity; exports.GlyphRasterAtlas = _chunkMO24PVGTjs.GlyphRasterAtlas; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkXE6K7T5Sjs.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SCENE_OPTION_KEYS = SCENE_OPTION_KEYS; exports.SVGEntity = _chunkXE6K7T5Sjs.SVGEntity; exports.SVGRenderer = _chunkMO24PVGTjs.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkMO24PVGTjs.TextRasterCache; exports.VECTO_USER_TIMING = VECTO_USER_TIMING; exports.VectoJSEvent = _chunkXE6K7T5Sjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkMO24PVGTjs.WebGPUParticleSystemManager; exports.beginVectoUserTiming = beginVectoUserTiming; exports.contentLineInHint = _chunkXE6K7T5Sjs.contentLineInHint; exports.createWebGLPointRenderer = _chunkMO24PVGTjs.createWebGLPointRenderer; exports.endVectoUserTiming = endVectoUserTiming; exports.installRendererDevTraps = _chunkMO24PVGTjs.installRendererDevTraps; exports.isRendererDevMode = _chunkMO24PVGTjs.isRendererDevMode; exports.isSafeUrl = _chunkMO24PVGTjs.isSafeUrl; exports.loadSpline = loadSpline; exports.measureVectoUserTiming = measureVectoUserTiming; exports.parseColorToRGBA = _chunkMO24PVGTjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkMO24PVGTjs.sanitizeUrl; exports.setRendererDevMode = _chunkMO24PVGTjs.setRendererDevMode;
package/dist/index.mjs CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  SVGEntity,
19
19
  VectoJSEvent,
20
20
  contentLineInHint
21
- } from "./chunk-CHDJEHML.mjs";
21
+ } from "./chunk-QJC4VZ2E.mjs";
22
22
 
23
23
  // src/tree/ComputeParticleEntity.ts
24
24
  var PARTICLE_STRIDE_FLOATS = 8;
@@ -178,8 +178,8 @@ var ComputeParticleEntity = class extends Entity {
178
178
  * render-tree walk, but this entity is still a normal tree member that
179
179
  * walk visits — without this override, the base `Entity.hasPendingAnimations()`
180
180
  * (always `false`) is what Scene sees, so `renderMode: 'always'`'s idle
181
- * auto-throttle drops the WHOLE scene to ~2fps the instant nothing else in
182
- * the tree is animating, even while thousands of particles are visibly
181
+ * auto-throttle drops the WHOLE scene to the idle FPS floor the instant
182
+ * nothing else in the tree is animating, even while thousands of particles are visibly
183
183
  * drifting or spring-settling. A spring+damping system asymptotically
184
184
  * approaches zero velocity but never reaches it exactly, so this checks
185
185
  * against a small epsilon rather than `!== 0` — otherwise this would
@@ -3435,6 +3435,7 @@ var SCENE_OPTION_KEYS = [
3435
3435
  "contentSemanticMargin",
3436
3436
  "debugA11y",
3437
3437
  "disableWindowResize",
3438
+ "idleFPS",
3438
3439
  "maxDPR",
3439
3440
  "maxFPS",
3440
3441
  "particleBackend",
@@ -3863,8 +3864,17 @@ var Scene = class _Scene {
3863
3864
  if (value) this._dirty.mark(void 0, this.currentFrame);
3864
3865
  else this._dirty.clear();
3865
3866
  }
3866
- /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
3867
+ /**
3868
+ * Whether to throttle the `'always'` loop when the scene is static to save
3869
+ * power. The idle floor is {@link Scene.idleFPS} (default 60).
3870
+ */
3867
3871
  autoThrottle = true;
3872
+ /**
3873
+ * Frame-rate floor for an idle `'always'` scene when {@link Scene.autoThrottle}
3874
+ * is on. Default `60`. Set `2` for the legacy aggressive idle sleep, `0` to
3875
+ * keep the scene's `maxFPS` cadence while idle.
3876
+ */
3877
+ idleFPS = 60;
3868
3878
  // --- Frame telemetry (read via `frameStats`) ---------------------------
3869
3879
  /** Wall-clock ms spent inside the last `render()` call. */
3870
3880
  _lastFrameMs = 0;
@@ -4794,6 +4804,7 @@ var Scene = class _Scene {
4794
4804
  this.maxFPS = options.maxFPS ?? (isTest ? 0 : 60);
4795
4805
  this.respectReducedMotion = options.respectReducedMotion ?? true;
4796
4806
  this.autoThrottle = options.autoThrottle ?? true;
4807
+ this.idleFPS = options.idleFPS ?? 60;
4797
4808
  this.phases.userTiming = options.userTiming ?? false;
4798
4809
  this.particleBackend = options.particleBackend ?? "auto";
4799
4810
  this.a11ySyncInterval = options.a11ySyncInterval ?? 0;
@@ -5722,6 +5733,8 @@ var Scene = class _Scene {
5722
5733
  el.style.padding = "0";
5723
5734
  el.style.outline = "none";
5724
5735
  el.style.cursor = node.a11yFullViewport ? "default" : "pointer";
5736
+ el.style.userSelect = "none";
5737
+ el.style.webkitUserSelect = "none";
5725
5738
  if (this.debugA11y) {
5726
5739
  el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
5727
5740
  el.style.border = "1px dashed rgba(56, 189, 248, 0.4)";
@@ -6737,7 +6750,7 @@ var Scene = class _Scene {
6737
6750
  let cap = this.effectiveMaxFPS();
6738
6751
  const isIdle = !this.dirty && !this.frameHadAnimation && !this.contentSemanticDeferred;
6739
6752
  if (isIdle && this.autoThrottle && this.renderMode === "always" && this.maxFPS > 0) {
6740
- cap = Math.min(cap, 2);
6753
+ cap = Math.min(cap, this.idleFPS);
6741
6754
  }
6742
6755
  if (cap > 0 && time - this.lastTime < 1e3 / cap - 1) {
6743
6756
  this._skippedFrames++;
@@ -6988,7 +7001,7 @@ var Scene = class _Scene {
6988
7001
  if (this._devActive && this._devFrameCount % 120 === 0) {
6989
7002
  if (overridesUpdate && node.hasPendingAnimations === Entity.prototype.hasPendingAnimations) {
6990
7003
  this._devWarn(
6991
- `Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to drop the animation to ~2fps. Override hasPendingAnimations() to return true while motion is in flight.`
7004
+ `Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to slow the animation. Override hasPendingAnimations() to return true while motion is in flight.`
6992
7005
  );
6993
7006
  }
6994
7007
  }
@@ -59,8 +59,8 @@ export declare class MSDFTextEntity extends Entity {
59
59
  *
60
60
  * Measured on Chromium and Firefox (2026-07-31) with a 600 ms atlas: the
61
61
  * scene's own rAF loop never uploaded a decoded atlas in EITHER render mode.
62
- * `onDemand` skips idle frames outright; `always` throttles to 2 FPS when
63
- * idle, so whether it recovers is down to whether a throttled tick happens to
62
+ * `onDemand` skips idle frames outright; `always` throttles to the idle FPS
63
+ * floor when idle, so whether it recovers is down to whether a throttled tick happens to
64
64
  * land after the decode — Chromium got one, Firefox did not. Neither is a
65
65
  * mechanism, which is why this listener exists rather than relying on the
66
66
  * frame loop to come back around.
package/dist/text.js CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkXHPUCU5Pjs = require('./chunk-XHPUCU5P.js');
5
+ var _chunkXE6K7T5Sjs = require('./chunk-XE6K7T5S.js');
6
6
 
7
7
  // src/text/index.ts
8
8
  var _text = require('@vectojs/text'); _createStarExport(_text);
9
9
 
10
10
 
11
11
 
12
- exports.MSDFTextEntity = _chunkXHPUCU5Pjs.MSDFTextEntity; exports.SVGEntity = _chunkXHPUCU5Pjs.SVGEntity;
12
+ exports.MSDFTextEntity = _chunkXE6K7T5Sjs.MSDFTextEntity; exports.SVGEntity = _chunkXE6K7T5Sjs.SVGEntity;
package/dist/text.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MSDFTextEntity,
3
3
  SVGEntity
4
- } from "./chunk-CHDJEHML.mjs";
4
+ } from "./chunk-QJC4VZ2E.mjs";
5
5
 
6
6
  // src/text/index.ts
7
7
  export * from "@vectojs/text";
@@ -113,8 +113,8 @@ export declare class ComputeParticleEntity extends Entity {
113
113
  * render-tree walk, but this entity is still a normal tree member that
114
114
  * walk visits — without this override, the base `Entity.hasPendingAnimations()`
115
115
  * (always `false`) is what Scene sees, so `renderMode: 'always'`'s idle
116
- * auto-throttle drops the WHOLE scene to ~2fps the instant nothing else in
117
- * the tree is animating, even while thousands of particles are visibly
116
+ * auto-throttle drops the WHOLE scene to the idle FPS floor the instant
117
+ * nothing else in the tree is animating, even while thousands of particles are visibly
118
118
  * drifting or spring-settling. A spring+damping system asymptotically
119
119
  * approaches zero velocity but never reaches it exactly, so this checks
120
120
  * against a small epsilon rather than `!== 0` — otherwise this would
@@ -98,10 +98,22 @@ export interface SceneOptions {
98
98
  */
99
99
  maxDPR?: number;
100
100
  /**
101
- * Enable automatic throttling to 2 FPS when the scene is static (no active transitions
102
- * and not marked dirty) to save power/CPU. Default is `true`.
101
+ * Enable automatic throttling of the `'always'` loop when the scene is
102
+ * static (no active transitions and not marked dirty) to save power/CPU.
103
+ * The idle floor is 60 FPS by default; raise or lower it with
104
+ * {@link SceneOptions.idleFPS}. Default is `true`.
103
105
  */
104
106
  autoThrottle?: boolean;
107
+ /**
108
+ * Frame-rate floor for an idle `'always'` scene when
109
+ * {@link SceneOptions.autoThrottle} is on. Default `60` — an idle scene
110
+ * keeps animating smoothly instead of stuttering at the old 2 FPS floor.
111
+ * Set e.g. `2` to restore the aggressive deep sleep for battery-critical
112
+ * scenes (the developer's explicit choice); `0` means "keep the scene's
113
+ * `maxFPS` cadence while idle". Ignored when `autoThrottle` is `false` or
114
+ * `renderMode` is `'onDemand'` (onDemand already renders zero idle frames).
115
+ */
116
+ idleFPS?: number;
105
117
  /**
106
118
  * Emit User Timing marks and measures for render phases. Default `false`.
107
119
  * Intended for short profiler captures; enable only while collecting one.
@@ -196,8 +208,9 @@ export interface SceneOptions {
196
208
  readingDirection?: 'ltr' | 'rtl';
197
209
  /**
198
210
  * When to repaint:
199
- * - `'always'` (default): drive a continuous rAF loop, throttling to 2 FPS
200
- * while the scene is idle if {@link SceneOptions.autoThrottle} is on.
211
+ * - `'always'` (default): drive a continuous rAF loop, throttling to the
212
+ * {@link SceneOptions.idleFPS} floor (default 60) while the scene is idle
213
+ * if {@link SceneOptions.autoThrottle} is on.
201
214
  * - `'onDemand'`: paint only after {@link Scene.markDirty} (or an active
202
215
  * transition), so a genuinely static scene costs zero frames.
203
216
  *
@@ -217,14 +230,14 @@ export interface SceneOptions {
217
230
  * plain untranspiled JS, gets no diagnostic at all. `renderMode` was a public
218
231
  * field with no matching option for several releases, and four `@vectojs` demos
219
232
  * shipped `new Scene(canvas, { renderMode: 'onDemand' })` — reading correctly,
220
- * doing nothing, and sitting on the 2 FPS idle floor.
233
+ * doing nothing, and sitting on the idle FPS floor.
221
234
  *
222
235
  * Kept as a literal rather than derived from a type: `keyof SceneOptions` does
223
236
  * not survive to runtime, so this list is the only form a constructor can check
224
237
  * against. A new option must be added here too — the test suite asserts the two
225
238
  * stay in sync.
226
239
  */
227
- export declare const SCENE_OPTION_KEYS: readonly ['a11ySyncInterval', 'autoThrottle', 'contentProjection', 'contentProjectionMargin', 'contentSemanticBudget', 'contentSemanticMargin', 'debugA11y', 'disableWindowResize', 'maxDPR', 'maxFPS', 'particleBackend', 'pointBackend', 'readingDirection', 'renderer', 'renderMode', 'respectReducedMotion', 'userTiming'];
240
+ export declare const SCENE_OPTION_KEYS: readonly ['a11ySyncInterval', 'autoThrottle', 'contentProjection', 'contentProjectionMargin', 'contentSemanticBudget', 'contentSemanticMargin', 'debugA11y', 'disableWindowResize', 'idleFPS', 'maxDPR', 'maxFPS', 'particleBackend', 'pointBackend', 'readingDirection', 'renderer', 'renderMode', 'respectReducedMotion', 'userTiming'];
228
241
  /** Frame-rate the loop is capped to when the OS requests reduced motion. */
229
242
  export declare const REDUCED_MOTION_FPS = 30;
230
243
  export type { AcceleratorReason, AcceleratorStatus, AcceleratorReport };
@@ -406,8 +419,17 @@ export declare class Scene {
406
419
  */
407
420
  private get dirty();
408
421
  private set dirty(value);
409
- /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
422
+ /**
423
+ * Whether to throttle the `'always'` loop when the scene is static to save
424
+ * power. The idle floor is {@link Scene.idleFPS} (default 60).
425
+ */
410
426
  autoThrottle: boolean;
427
+ /**
428
+ * Frame-rate floor for an idle `'always'` scene when {@link Scene.autoThrottle}
429
+ * is on. Default `60`. Set `2` for the legacy aggressive idle sleep, `0` to
430
+ * keep the scene's `maxFPS` cadence while idle.
431
+ */
432
+ idleFPS: number;
411
433
  /** Wall-clock ms spent inside the last `render()` call. */
412
434
  private _lastFrameMs;
413
435
  /** Rolling exponential average of rendered-frame intervals, in ms. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.35.2",
3
+ "version": "1.36.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },