@vectojs/core 1.35.3 → 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();
@@ -5734,20 +5745,20 @@ var Scene = (_class15 = class _Scene {
5734
5745
  el.style.background = "transparent";
5735
5746
  }
5736
5747
  el.addEventListener("click", (e) => {
5737
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("click", node, e));
5748
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("click", node, e));
5738
5749
  });
5739
5750
  el.addEventListener("dblclick", (e) => {
5740
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("dblclick", node, e));
5751
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("dblclick", node, e));
5741
5752
  });
5742
5753
  el.addEventListener("mouseenter", (e) => {
5743
5754
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
5744
5755
  this.hoveredA11yElements.add(el);
5745
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("hover", node, e, false));
5756
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("hover", node, e, false));
5746
5757
  });
5747
5758
  el.addEventListener("mouseleave", (e) => {
5748
5759
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
5749
5760
  this.hoveredA11yElements.delete(el);
5750
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("pointerleave", node, e, false));
5761
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("pointerleave", node, e, false));
5751
5762
  });
5752
5763
  const capEl = el;
5753
5764
  const releasePointer = (event) => {
@@ -5763,24 +5774,24 @@ var Scene = (_class15 = class _Scene {
5763
5774
  };
5764
5775
  el.addEventListener("pointerdown", (e) => {
5765
5776
  if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
5766
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("pointerdown", node, e));
5777
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("pointerdown", node, e));
5767
5778
  });
5768
5779
  el.addEventListener("pointerup", (e) => {
5769
5780
  releasePointer(e);
5770
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("pointerup", node, e));
5781
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("pointerup", node, e));
5771
5782
  });
5772
5783
  el.addEventListener("pointercancel", (e) => {
5773
5784
  releasePointer(e);
5774
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("pointercancel", node, e));
5785
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("pointercancel", node, e));
5775
5786
  });
5776
5787
  el.addEventListener(
5777
5788
  "pointermove",
5778
- (e) => node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("pointermove", node, e))
5789
+ (e) => node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("pointermove", node, e))
5779
5790
  );
5780
5791
  el.addEventListener(
5781
5792
  "wheel",
5782
5793
  (e) => {
5783
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("wheel", node, e));
5794
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("wheel", node, e));
5784
5795
  },
5785
5796
  { passive: false }
5786
5797
  );
@@ -5805,10 +5816,10 @@ var Scene = (_class15 = class _Scene {
5805
5816
  );
5806
5817
  emitInitialScroll = emitScroll;
5807
5818
  el.addEventListener("keydown", (e) => {
5808
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("keydown", node, e));
5819
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("keydown", node, e));
5809
5820
  });
5810
5821
  el.addEventListener("keyup", (e) => {
5811
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("keyup", node, e));
5822
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("keyup", node, e));
5812
5823
  });
5813
5824
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
5814
5825
  const input = el;
@@ -5874,7 +5885,7 @@ var Scene = (_class15 = class _Scene {
5874
5885
  el.addEventListener("keydown", (e) => {
5875
5886
  if (e.key === "Enter" || e.key === " ") {
5876
5887
  e.preventDefault();
5877
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("click", node, e));
5888
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("click", node, e));
5878
5889
  }
5879
5890
  });
5880
5891
  }
@@ -6311,7 +6322,7 @@ var Scene = (_class15 = class _Scene {
6311
6322
  el.addEventListener(
6312
6323
  "wheel",
6313
6324
  (e2) => {
6314
- node.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)("wheel", node, e2));
6325
+ node.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)("wheel", node, e2));
6315
6326
  },
6316
6327
  { passive: false }
6317
6328
  );
@@ -6740,7 +6751,7 @@ var Scene = (_class15 = class _Scene {
6740
6751
  let cap = this.effectiveMaxFPS();
6741
6752
  const isIdle = !this.dirty && !this.frameHadAnimation && !this.contentSemanticDeferred;
6742
6753
  if (isIdle && this.autoThrottle && this.renderMode === "always" && this.maxFPS > 0) {
6743
- cap = Math.min(cap, 2);
6754
+ cap = Math.min(cap, this.idleFPS);
6744
6755
  }
6745
6756
  if (cap > 0 && time - this.lastTime < 1e3 / cap - 1) {
6746
6757
  this._skippedFrames++;
@@ -6980,7 +6991,7 @@ var Scene = (_class15 = class _Scene {
6980
6991
  let walkHadAnimation = false;
6981
6992
  let walkHadInteractive = false;
6982
6993
  const runUpdate = (node) => {
6983
- const overridesUpdate = node.update !== _chunkXHPUCU5Pjs.Entity.prototype.update;
6994
+ const overridesUpdate = node.update !== _chunkXE6K7T5Sjs.Entity.prototype.update;
6984
6995
  let pending = node.hasPendingAnimations();
6985
6996
  if (pending || overridesUpdate) {
6986
6997
  node.update(dt, time);
@@ -6989,9 +7000,9 @@ var Scene = (_class15 = class _Scene {
6989
7000
  if (pending) walkHadAnimation = true;
6990
7001
  if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
6991
7002
  if (this._devActive && this._devFrameCount % 120 === 0) {
6992
- if (overridesUpdate && node.hasPendingAnimations === _chunkXHPUCU5Pjs.Entity.prototype.hasPendingAnimations) {
7003
+ if (overridesUpdate && node.hasPendingAnimations === _chunkXE6K7T5Sjs.Entity.prototype.hasPendingAnimations) {
6993
7004
  this._devWarn(
6994
- `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.`
6995
7006
  );
6996
7007
  }
6997
7008
  }
@@ -7415,20 +7426,20 @@ function defaultMeasurer() {
7415
7426
  sharedMeasurer ??= _layout.resolveGlyphMeasurer.call(void 0, "sans-serif");
7416
7427
  return sharedMeasurer;
7417
7428
  }
7418
- var TextEntity = (_class16 = class extends _chunkXHPUCU5Pjs.Entity {
7429
+ var TextEntity = (_class16 = class extends _chunkXE6K7T5Sjs.Entity {
7419
7430
 
7420
7431
 
7421
7432
 
7422
7433
 
7423
- __init176() {this.nodes = []}
7434
+ __init177() {this.nodes = []}
7424
7435
 
7425
- __init177() {this.fillStyle = "#94a3b8"}
7426
- __init178() {this.strokeStyle = null}
7427
- __init179() {this.hoveredFillStyle = "#ffffff"}
7428
- __init180() {this.lineWidth = 1}
7429
- __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}
7430
7441
  /** Bumped by {@link applyLayout}; read by `Scene` to skip an unchanged sync. */
7431
- __init182() {this.contentEpoch = 0}
7442
+ __init183() {this.contentEpoch = 0}
7432
7443
  /**
7433
7444
  * Visual lines with real canvas geometry, rebuilt by {@link applyLayout}
7434
7445
  * exactly when `this.nodes` changes.
@@ -7440,9 +7451,9 @@ var TextEntity = (_class16 = class extends _chunkXHPUCU5Pjs.Entity {
7440
7451
  * ~0.35 em per line for a 24 px font, Firefox). Per-line carriers pin the DOM
7441
7452
  * baseline to the canvas baseline by construction, same as `ui/Text`.
7442
7453
  */
7443
- __init183() {this.projectionLines = []}
7454
+ __init184() {this.projectionLines = []}
7444
7455
  constructor(text, atlas, maxWidth, fontSize = 24) {
7445
- 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);;
7446
7457
  this.text = text;
7447
7458
  this.atlas = atlas;
7448
7459
  this.fontSize = fontSize;
@@ -7619,17 +7630,17 @@ var TextEntity = (_class16 = class extends _chunkXHPUCU5Pjs.Entity {
7619
7630
  }, _class16);
7620
7631
 
7621
7632
  // src/components/GridTextEntity.ts
7622
- var GridTextEntity = (_class17 = class extends _chunkXHPUCU5Pjs.Entity {
7633
+ var GridTextEntity = (_class17 = class extends _chunkXE6K7T5Sjs.Entity {
7623
7634
 
7624
- __init184() {this.fillStyle = "#ffffff"}
7625
- __init185() {this.grid = []}
7635
+ __init185() {this.fillStyle = "#ffffff"}
7636
+ __init186() {this.grid = []}
7626
7637
  // Array of rows
7627
- __init186() {this.cols = 0}
7628
- __init187() {this.rows = 0}
7638
+ __init187() {this.cols = 0}
7639
+ __init188() {this.rows = 0}
7629
7640
 
7630
7641
 
7631
7642
  constructor(_atlas, fontSize = 10) {
7632
- 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);;
7633
7644
  this.fontSize = fontSize;
7634
7645
  this.charWidth = fontSize * 1;
7635
7646
  this.charHeight = fontSize * 1.1;
@@ -7713,7 +7724,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
7713
7724
  const ey = py - cy;
7714
7725
  return ex * ex + ey * ey;
7715
7726
  }
7716
- var SplineEntity = (_class18 = class extends _chunkXHPUCU5Pjs.Entity {
7727
+ var SplineEntity = (_class18 = class extends _chunkXE6K7T5Sjs.Entity {
7717
7728
 
7718
7729
 
7719
7730
 
@@ -7721,17 +7732,17 @@ var SplineEntity = (_class18 = class extends _chunkXHPUCU5Pjs.Entity {
7721
7732
 
7722
7733
 
7723
7734
 
7724
- __init188() {this.offscreen = null}
7725
- __init189() {this.baked = false}
7735
+ __init189() {this.offscreen = null}
7736
+ __init190() {this.baked = false}
7726
7737
  /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
7727
- __init190() {this.bakedWidth = 0}
7728
- __init191() {this.bakedHeight = 0}
7738
+ __init191() {this.bakedWidth = 0}
7739
+ __init192() {this.bakedHeight = 0}
7729
7740
  /**
7730
7741
  * Device-pixel ratio the bitmap was rasterized at. Tracked so a DPR change
7731
7742
  * (browser zoom, a monitor move, a renderer whose clamp changed) re-bakes
7732
7743
  * instead of blitting a bitmap at the wrong device density forever.
7733
7744
  */
7734
- __init192() {this.bakedDPR = 0}
7745
+ __init193() {this.bakedDPR = 0}
7735
7746
  /**
7736
7747
  * Gradient strokes can't be baked to a solid-color bitmap; they render
7737
7748
  * per-frame. Derived from `_doc`, so it is NOT readonly — assigning a new
@@ -7739,7 +7750,7 @@ var SplineEntity = (_class18 = class extends _chunkXHPUCU5Pjs.Entity {
7739
7750
  */
7740
7751
 
7741
7752
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
7742
- __init193() {this.polylines = null}
7753
+ __init194() {this.polylines = null}
7743
7754
  /**
7744
7755
  * The spline document to render. Assigning a new document invalidates all
7745
7756
  * caches (baked canvas, flattened polylines, bounds).
@@ -7773,9 +7784,9 @@ var SplineEntity = (_class18 = class extends _chunkXHPUCU5Pjs.Entity {
7773
7784
  * local bounds after painting the curves. Useful for drag feedback and
7774
7785
  * debugging hit areas. Defaults to `false`.
7775
7786
  */
7776
- __init194() {this.showBounds = false}
7787
+ __init195() {this.showBounds = false}
7777
7788
  constructor(doc, opts = {}) {
7778
- 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);;
7779
7790
  this._doc = doc;
7780
7791
  this._lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
7781
7792
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -8041,7 +8052,7 @@ async function loadSpline(url) {
8041
8052
  }
8042
8053
 
8043
8054
  // src/components/Rect.ts
8044
- var Rect = class extends _chunkXHPUCU5Pjs.Entity {
8055
+ var Rect = class extends _chunkXE6K7T5Sjs.Entity {
8045
8056
 
8046
8057
 
8047
8058
 
@@ -8097,7 +8108,7 @@ var Rect = class extends _chunkXHPUCU5Pjs.Entity {
8097
8108
  };
8098
8109
 
8099
8110
  // src/components/Circle.ts
8100
- var Circle = class extends _chunkXHPUCU5Pjs.Entity {
8111
+ var Circle = class extends _chunkXE6K7T5Sjs.Entity {
8101
8112
 
8102
8113
 
8103
8114
 
@@ -8161,7 +8172,7 @@ var Circle = class extends _chunkXHPUCU5Pjs.Entity {
8161
8172
  };
8162
8173
 
8163
8174
  // src/components/Group.ts
8164
- var Group = class extends _chunkXHPUCU5Pjs.Entity {
8175
+ var Group = class extends _chunkXE6K7T5Sjs.Entity {
8165
8176
  constructor(...children) {
8166
8177
  super();
8167
8178
  if (children.length > 0) this.add(...children);
@@ -8180,21 +8191,21 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
8180
8191
 
8181
8192
 
8182
8193
  // src/tree/DOMPortalEntity.ts
8183
- var DOMPortalEntity = (_class19 = class extends _chunkXHPUCU5Pjs.Entity {
8194
+ var DOMPortalEntity = (_class19 = class extends _chunkXE6K7T5Sjs.Entity {
8184
8195
 
8185
- __init195() {this.isDOMPortal = true}
8186
- __init196() {this.domListeners = []}
8187
- __init197() {this.resizeObserver = null}
8188
- __init198() {this.domBound = false}
8189
- __init199() {this.cachedWidth = 100}
8190
- __init200() {this.cachedHeight = 100}
8191
- __init201() {this.lastWidth = ""}
8192
- __init202() {this.lastHeight = ""}
8193
- __init203() {this.lastTransform = ""}
8194
- __init204() {this.lastZIndex = ""}
8195
- __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 = ""}
8196
8207
  constructor(domElement, width, height, id) {
8197
- 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);;
8198
8209
  this.domElement = domElement;
8199
8210
  this.width = _nullishCoalesce(width, () => ( 0));
8200
8211
  this.height = _nullishCoalesce(height, () => ( 0));
@@ -8236,7 +8247,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkXHPUCU5Pjs.Entity {
8236
8247
  ];
8237
8248
  for (const type of events) {
8238
8249
  const handler = (e) => {
8239
- this.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)(type, this, e));
8250
+ this.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)(type, this, e));
8240
8251
  };
8241
8252
  this.domElement.addEventListener(type, handler);
8242
8253
  this.domListeners.push({ type, handler, capture: false });
@@ -8247,7 +8258,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkXHPUCU5Pjs.Entity {
8247
8258
  ];
8248
8259
  for (const { native, vecto } of hoverEvents) {
8249
8260
  const handler = (e) => {
8250
- this.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)(vecto, this, e, false));
8261
+ this.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)(vecto, this, e, false));
8251
8262
  };
8252
8263
  this.domElement.addEventListener(native, handler);
8253
8264
  this.domListeners.push({ type: native, handler, capture: false });
@@ -8255,7 +8266,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkXHPUCU5Pjs.Entity {
8255
8266
  const focusEvents = ["focus", "blur"];
8256
8267
  for (const type of focusEvents) {
8257
8268
  const handler = (e) => {
8258
- this.dispatchEvent(new (0, _chunkXHPUCU5Pjs.VectoJSEvent)(type, this, e, true));
8269
+ this.dispatchEvent(new (0, _chunkXE6K7T5Sjs.VectoJSEvent)(type, this, e, true));
8259
8270
  };
8260
8271
  this.domElement.addEventListener(type, handler, true);
8261
8272
  this.domListeners.push({ type, handler, capture: true });
@@ -8353,4 +8364,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkMO24PVGTjs.WebGPUParticleSystemM
8353
8364
 
8354
8365
 
8355
8366
 
8356
- 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;
@@ -6739,7 +6750,7 @@ var Scene = class _Scene {
6739
6750
  let cap = this.effectiveMaxFPS();
6740
6751
  const isIdle = !this.dirty && !this.frameHadAnimation && !this.contentSemanticDeferred;
6741
6752
  if (isIdle && this.autoThrottle && this.renderMode === "always" && this.maxFPS > 0) {
6742
- cap = Math.min(cap, 2);
6753
+ cap = Math.min(cap, this.idleFPS);
6743
6754
  }
6744
6755
  if (cap > 0 && time - this.lastTime < 1e3 / cap - 1) {
6745
6756
  this._skippedFrames++;
@@ -6990,7 +7001,7 @@ var Scene = class _Scene {
6990
7001
  if (this._devActive && this._devFrameCount % 120 === 0) {
6991
7002
  if (overridesUpdate && node.hasPendingAnimations === Entity.prototype.hasPendingAnimations) {
6992
7003
  this._devWarn(
6993
- `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.`
6994
7005
  );
6995
7006
  }
6996
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.3",
3
+ "version": "1.36.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },